View Javadoc

1   /***
2    * 
3    */
4   package de.cohesion.bssh;
5   
6   import java.io.File;
7   import java.io.IOException;
8   import java.io.PrintStream;
9   import java.util.Collections;
10  import java.util.HashMap;
11  import java.util.LinkedList;
12  import java.util.List;
13  import java.util.Map;
14  import java.util.Set;
15  import java.util.concurrent.TimeUnit;
16  
17  import ch.ethz.ssh2.Connection;
18  import de.cohesion.bssh.impl.CommandProcessor;
19  import de.cohesion.bssh.impl.ExecuteSSHCommand;
20  import de.cohesion.bssh.impl.FileMemberSource;
21  import de.cohesion.bssh.impl.PBEPasswordStore;
22  import de.cohesion.bssh.impl.SSHCommand;
23  import de.cohesion.bssh.impl.SSHConnectionFactory;
24  import de.cohesion.bssh.impl.io.PasswordHelper;
25  import de.cohesion.bssh.impl.util.Cache;
26  import de.cohesion.bssh.impl.util.Cache.ReplacementStrategy;
27  
28  /***
29   * @author schulzs
30   */
31  public class BatchSSH {
32  
33  	private enum ReturnCodes {
34  		OK("Success"), INVALID_OPTION("Invalid option"), UNRECOGNIZED_OPTION(
35  				"Unrecognized option"), GROUP_FILE_ERROR(
36  				"Error opening the group file"), PASSPHRASE_ERROR(
37  				"Error reading passphrase"), PASSWORD_STORE_ERROR(
38  				"Failed to open password store");
39  
40  		private String message;
41  
42  		ReturnCodes(final String message) {
43  			this.message = message;
44  		}
45  
46  		public String getMessage() {
47  			return message;
48  		}
49  
50  		public void exit() {
51  			exit(null, null);
52  		}
53  
54  		public void exit(final String msg) {
55  			exit(msg, null);
56  		}
57  
58  		public void exit(final Throwable t) {
59  			exit(null, t);
60  		}
61  
62  		public void exit(final String msg, final Throwable t) {
63  			PrintStream ps = null;
64  			if (this == OK) {
65  				ps = System.out;
66  			} else {
67  				ps = System.err;
68  			}
69  			ps.print(getMessage() + " (code: " + ordinal());
70  			if (msg != null) {
71  				ps.print(", message: " + msg);
72  			}
73  			if (t != null) {
74  				ps.print(", cause: " + t.getMessage());
75  			}
76  			ps.println(").");
77  			System.exit(ordinal());
78  		}
79  
80  	}
81  
82  	private static Cache<Member, Connection> ccache;
83  
84  	private static final Map<String, Option> OPTIONS = new HashMap<String, Option>();
85  	static {
86  		for (Option o : new Option[] { new Concurrency(), new Help(),
87  				new Timeout(), new Password() }) {
88  			OPTIONS.put(o.getKey(), o);
89  		}
90  	}
91  
92  	private static char[] getPassphrase() {
93  		Password p = (Password) OPTIONS.get(Password.KEY);
94  		if (p.isActive()) {
95  			return p.getPassword();
96  		} else {
97  			try {
98  				return PasswordHelper.getPassword(System.in, "Password: ");
99  			} catch (IOException ioe) {
100 				ReturnCodes.PASSPHRASE_ERROR.exit(ioe);
101 			}
102 		}
103 		return null;
104 	}
105 
106 	public static void main(final String[] args) {
107 
108 		if (args.length == 0) {
109 			showHelp();
110 		}
111 
112 		int offset = parseOptions(args);
113 
114 		/* Read in the members file. */
115 		MemberSource source = null;
116 		try {
117 			source = new FileMemberSource(new File(args[offset]));
118 		} catch (IOException ioe) {
119 			ReturnCodes.GROUP_FILE_ERROR.exit(ioe);
120 		}
121 		Set<Member> members = source.getMembers();
122 
123 		/* Build the command string. */
124 		StringBuilder b = new StringBuilder();
125 		for (int i = 1; i < args.length - offset; i++) {
126 			b.append(args[offset + i]);
127 			b.append(" ");
128 		}
129 
130 		/* Get the passphrase (from option or console) */
131 		char[] passphrase = getPassphrase();
132 
133 		PasswordStore store = null;
134 		try {
135 			store = new PBEPasswordStore(new File(System
136 					.getProperty("user.home")
137 					+ "/.bssh"), passphrase);
138 		} catch (IOException ioe) {
139 			ReturnCodes.PASSWORD_STORE_ERROR.exit(ioe);
140 		}
141 
142 		/* Construct the connection cache. */
143 		ccache = new Cache<Member, Connection>(1024, new SSHConnectionFactory(
144 				3, store), ReplacementStrategy.LRU);
145 
146 		SSHCommand command = new ExecuteSSHCommand(b.toString(), ccache);
147 
148 		CommandProcessor processor = new CommandProcessor(Collections
149 				.singleton(command), members, Timeout.class.cast(
150 				OPTIONS.get(Timeout.KEY)).getTimeout());
151 		processor.setConcurrency(Concurrency.class.cast(
152 				OPTIONS.get(Concurrency.KEY)).getLevel());
153 
154 		/* Collect results before printing. */
155 		long start = System.nanoTime();
156 		List<Result> results = new LinkedList<Result>();
157 		showProgress(System.out, members.size(), 0);
158 		for (Result r : processor.getResults()) {
159 			results.add(r);
160 			showProgress(System.out, members.size(), results.size()
161 					/ (float) members.size());
162 		}
163 		long end = System.nanoTime();
164 		System.out.println("\n");
165 
166 		/* Print the results as soon as they are available. */
167 		ResultFormat rf = ResultFormat.SIMPLE;
168 		for (Result r : results) {
169 			System.out.println(rf.format(r) + "\n");
170 		}
171 
172 		long diff = end - start;
173 		System.out.println("Total execution time: "
174 				+ TimeUnit.SECONDS.convert(diff, TimeUnit.NANOSECONDS) + "s "
175 				+ TimeUnit.MILLISECONDS.convert(diff, TimeUnit.NANOSECONDS)
176 				% 1000 + "ms");
177 
178 		ccache.invalidate();
179 
180 		System.exit(0);
181 
182 	}
183 
184 	private static int parseOptions(final String[] args) {
185 
186 		int offset = 0;
187 		for (int i = 0; i < args.length;) {
188 			String arg = args[i];
189 
190 			/* Determine option class. */
191 			Option o = null;
192 			if (arg.startsWith("--")) {
193 				o = OPTIONS.get(arg.substring(2));
194 			} else if (arg.startsWith("-")) {
195 				for (String key : OPTIONS.keySet()) {
196 					if (key.startsWith(arg.substring(1, 2))) {
197 						o = OPTIONS.get(key);
198 						break;
199 					}
200 				}
201 			} else {
202 				break;
203 			}
204 
205 			if (o == null) {
206 				ReturnCodes.UNRECOGNIZED_OPTION.exit(arg);
207 			}
208 
209 			assert o != null;
210 
211 			/* Build raw option string (e.g. --concurrent 5). */
212 			StringBuilder b = new StringBuilder();
213 			int j = 0;
214 			while (j <= o.getArity()) {
215 				if (j > 0 && args[i + j].startsWith("-")) {
216 					break;
217 				}
218 				b.append(args[i + j]);
219 				b.append(' ');
220 				j++;
221 			}
222 			i += j;
223 
224 			/* Parse option. */
225 			try {
226 				o.parse(b.toString());
227 			} catch (ParseException pe) {
228 				ReturnCodes.INVALID_OPTION.exit(o.getKey(), pe);
229 			}
230 
231 			offset = i;
232 		}
233 
234 		/* Process active options. */
235 		for (Option o : OPTIONS.values()) {
236 			if (o.isActive()) {
237 				if (o instanceof Help) {
238 					showHelp();
239 				}
240 			}
241 		}
242 
243 		return offset;
244 	}
245 
246 	private static void showHelp() {
247 		StringBuilder b = new StringBuilder();
248 		b.append("usage: bssh [-options] <group-file> <command>\n\n");
249 		b.append("where options include:\n");
250 		for (Option o : OPTIONS.values()) {
251 			b.append(" -" + o.getKey().charAt(0) + ", --" + o.getKey() + " ");
252 			b.append(o.getHelpText() + "\n");
253 		}
254 		System.out.println(b.toString());
255 		ReturnCodes.OK.exit();
256 	}
257 
258 	private static void showProgress(final PrintStream p, int width, float ratio) {
259 		p.print("\015" + "[");
260 		for (int i = 0; i < (int) Math.floor(width * ratio); i++) {
261 			p.print("#");
262 		}
263 		for (int i = (int) Math.floor(width * ratio); i < width; i++) {
264 			p.print("-");
265 		}
266 		p.print("]");
267 	}
268 
269 }