| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| package org.jclouds.examples.chef.basics; |
| |
| import static com.google.common.base.Charsets.UTF_8; |
| import static com.google.common.base.Preconditions.checkArgument; |
| import static com.google.common.base.Predicates.not; |
| import static com.google.common.collect.Iterables.concat; |
| import static com.google.common.collect.Iterables.contains; |
| import static com.google.common.collect.Iterables.getOnlyElement; |
| import static org.jclouds.aws.ec2.reference.AWSEC2Constants.PROPERTY_EC2_AMI_QUERY; |
| import static org.jclouds.aws.ec2.reference.AWSEC2Constants.PROPERTY_EC2_CC_AMI_QUERY; |
| import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_SCRIPT_COMPLETE; |
| import static org.jclouds.compute.options.TemplateOptions.Builder.overrideLoginCredentials; |
| import static org.jclouds.compute.options.TemplateOptions.Builder.runScript; |
| import static org.jclouds.compute.predicates.NodePredicates.TERMINATED; |
| import static org.jclouds.compute.predicates.NodePredicates.inGroup; |
| |
| import java.io.File; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.Properties; |
| import java.util.Set; |
| import java.util.concurrent.TimeUnit; |
| |
| import org.jclouds.ContextBuilder; |
| import org.jclouds.apis.ApiMetadata; |
| import org.jclouds.apis.Apis; |
| import org.jclouds.chef.ChefApiMetadata; |
| import org.jclouds.chef.ChefContext; |
| import org.jclouds.chef.ChefService; |
| import org.jclouds.chef.config.ChefProperties; |
| import org.jclouds.chef.util.RunListBuilder; |
| import org.jclouds.compute.ComputeService; |
| import org.jclouds.compute.ComputeServiceContext; |
| import org.jclouds.compute.RunNodesException; |
| import org.jclouds.compute.RunScriptOnNodesException; |
| import org.jclouds.compute.domain.ExecResponse; |
| import org.jclouds.compute.domain.NodeMetadata; |
| import org.jclouds.compute.domain.TemplateBuilder; |
| import org.jclouds.domain.LoginCredentials; |
| import org.jclouds.enterprise.config.EnterpriseConfigurationModule; |
| import org.jclouds.logging.slf4j.config.SLF4JLoggingModule; |
| import org.jclouds.providers.ProviderMetadata; |
| import org.jclouds.providers.Providers; |
| import org.jclouds.scriptbuilder.domain.Statement; |
| import org.jclouds.scriptbuilder.domain.StatementList; |
| import org.jclouds.scriptbuilder.domain.chef.RunList; |
| import org.jclouds.scriptbuilder.statements.chef.ChefSolo; |
| import org.jclouds.scriptbuilder.statements.git.CloneGitRepo; |
| import org.jclouds.scriptbuilder.statements.git.InstallGit; |
| import org.jclouds.scriptbuilder.statements.login.AdminAccess; |
| import org.jclouds.scriptbuilder.statements.ruby.InstallRuby; |
| import org.jclouds.scriptbuilder.statements.ruby.InstallRubyGems; |
| import org.jclouds.sshj.config.SshjSshClientModule; |
| |
| import com.google.common.base.Predicates; |
| import com.google.common.base.Splitter; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.common.collect.Iterables; |
| import com.google.common.collect.Maps; |
| import com.google.common.io.Files; |
| import com.google.inject.Module; |
| |
| /** |
| * Demonstrates the use of {@link ComputeService}. |
| * <p/> |
| * Usage is: |
| * {@code java MainApp provider identity credential groupName (add|chef|destroy)} |
| * if {@code chef} is used, the following parameter is a list of recipes to be |
| * installed in the node separated by commas. |
| * |
| * @author Adrian Cole |
| * @author Ignasi Barrera |
| */ |
| public class MainApp { |
| |
| public static enum Action { |
| ADD, CHEF, SOLO, DESTROY; |
| } |
| |
| public static final Map<String, ApiMetadata> allApis = Maps.uniqueIndex( |
| Apis.viewableAs(ComputeServiceContext.class), Apis.idFunction()); |
| |
| public static final Map<String, ProviderMetadata> appProviders = Maps.uniqueIndex( |
| Providers.viewableAs(ComputeServiceContext.class), Providers.idFunction()); |
| |
| public static final Set<String> allKeys = ImmutableSet.copyOf(Iterables.concat(appProviders.keySet(), |
| allApis.keySet())); |
| |
| public static int PARAMETERS = 5; |
| public static String INVALID_SYNTAX = "Invalid number of parameters. Syntax is: provider identity credential groupName (add|chef|solo|destroy)"; |
| |
| public static void main(String[] args) { |
| if (args.length < PARAMETERS) { |
| throw new IllegalArgumentException(INVALID_SYNTAX); |
| } |
| |
| String provider = args[0]; |
| String identity = args[1]; |
| String credential = args[2]; |
| String groupName = args[3]; |
| Action action = Action.valueOf(args[4].toUpperCase()); |
| if ((action == Action.CHEF || action == Action.SOLO) && args.length < PARAMETERS + 1) { |
| throw new IllegalArgumentException("please provide the list of recipes to install, separated by commas"); |
| } |
| String recipes = action == Action.CHEF || action == Action.SOLO ? args[5] : "apache2"; |
| |
| String minRam = System.getProperty("minRam"); |
| |
| // note that you can check if a provider is present ahead of time |
| checkArgument(contains(allKeys, provider), "provider %s not in supported list: %s", provider, allKeys); |
| |
| LoginCredentials login = action != Action.DESTROY ? getLoginForCommandExecution(action) : null; |
| |
| ComputeService compute = initComputeService(provider, identity, credential); |
| |
| try { |
| switch (action) { |
| case ADD: |
| System.out.printf(">> adding node to group %s%n", groupName); |
| |
| // Default template chooses the smallest size on an operating |
| // system that tested to work with java, which tends to be Ubuntu |
| // or CentOS |
| TemplateBuilder templateBuilder = compute.templateBuilder(); |
| |
| // If you want to up the ram and leave everything default, you |
| // can just tweak minRam |
| if (minRam != null) { |
| templateBuilder.minRam(Integer.parseInt(minRam)); |
| } |
| |
| // note this will create a user with the same name as you on the |
| // node. ex. you can connect via ssh publicip |
| Statement bootInstructions = AdminAccess.standard(); |
| |
| // to run commands as root, we use the runScript option in the |
| // template. |
| templateBuilder.options(runScript(bootInstructions)); |
| |
| NodeMetadata node = getOnlyElement(compute.createNodesInGroup(groupName, 1, templateBuilder.build())); |
| System.out.printf("<< node %s: %s%n", node.getId(), |
| concat(node.getPrivateAddresses(), node.getPublicAddresses())); |
| |
| case SOLO: |
| System.out.printf(">> installing [%s] on group %s as %s%n", recipes, groupName, login.identity); |
| |
| Iterable<String> recipeList = Splitter.on(',').split(recipes); |
| ImmutableList.Builder<Statement> bootstrapBuilder = ImmutableList.builder(); |
| bootstrapBuilder.add(new InstallGit()); |
| |
| // Clone community cookbooks into the node |
| for (String recipe : recipeList) { |
| bootstrapBuilder.add(CloneGitRepo.builder() |
| .repository("git://github.com/opscode-cookbooks/" + recipe + ".git") |
| .directory("/var/chef/cookbooks/" + recipe) // |
| .build()); |
| } |
| |
| // Configure Chef Solo to bootstrap the selected recipes |
| bootstrapBuilder.add(InstallRuby.builder().build()); |
| bootstrapBuilder.add(InstallRubyGems.builder().build()); |
| bootstrapBuilder.add(ChefSolo.builder() // |
| .cookbookPath("/var/chef/cookbooks") // |
| .runlist(RunList.builder().recipes(recipeList).build()) // |
| .build()); |
| |
| // Build the statement that will perform all the operations above |
| StatementList bootstrap = new StatementList(bootstrapBuilder.build()); |
| |
| // Run the script in the nodes of the group |
| runScriptOnGroup(compute, login, groupName, bootstrap); |
| break; |
| case CHEF: |
| // Create the connection to the Chef server |
| ChefService chef = initChefService(System.getProperty("chef.client"), |
| System.getProperty("chef.validator")); |
| |
| // Build the runlist for the deployed nodes |
| System.out.println("Configuring node runlist in the Chef server..."); |
| List<String> runlist = new RunListBuilder().addRecipes(recipes.split(",")).build(); |
| chef.updateRunListForGroup(runlist, groupName); |
| Statement chefServerBootstrap = chef.createBootstrapScriptForGroup(groupName); |
| |
| // Run the script in the nodes of the group |
| System.out.printf(">> installing [%s] on group %s as %s%n", recipes, groupName, login.identity); |
| runScriptOnGroup(compute, login, groupName, chefServerBootstrap); |
| break; |
| case DESTROY: |
| System.out.printf(">> destroying nodes in group %s%n", groupName); |
| // you can use predicates to select which nodes you wish to |
| // destroy. |
| Set<? extends NodeMetadata> destroyed = compute.destroyNodesMatching(// |
| Predicates.<NodeMetadata> and(not(TERMINATED), inGroup(groupName))); |
| System.out.printf("<< destroyed nodes %s%n", destroyed); |
| break; |
| } |
| } catch (RunNodesException e) { |
| System.err.println("error adding node to group " + groupName + ": " + e.getMessage()); |
| error = 1; |
| } catch (RunScriptOnNodesException e) { |
| System.err.println("error installing " + recipes + " on group " + groupName + ": " + e.getMessage()); |
| error = 1; |
| } catch (Exception e) { |
| System.err.println("error: " + e.getMessage()); |
| error = 1; |
| } finally { |
| compute.getContext().close(); |
| System.exit(error); |
| } |
| } |
| |
| static int error = 0; |
| |
| private static void runScriptOnGroup(ComputeService compute, LoginCredentials login, String groupName, |
| Statement command) throws RunScriptOnNodesException { |
| // when you run commands, you can pass options to decide whether |
| // to run it as root, supply or own credentials vs from cache, |
| // and wrap in an init script vs directly invoke |
| Map<? extends NodeMetadata, ExecResponse> execResponses = compute.runScriptOnNodesMatching(// |
| inGroup(groupName), // predicate used to select nodes |
| command, // what you actually intend to run |
| overrideLoginCredentials(login) // use the local user & ssh key |
| .runAsRoot(false)); // don't attempt to run as root (sudo) |
| |
| for (Entry<? extends NodeMetadata, ExecResponse> response : execResponses.entrySet()) { |
| System.out.printf("<< node %s: %s%n", response.getKey().getId(), |
| concat(response.getKey().getPrivateAddresses(), response.getKey().getPublicAddresses())); |
| System.out.printf("<< %s%n", response.getValue()); |
| } |
| } |
| |
| private static ComputeService initComputeService(String provider, String identity, String credential) { |
| |
| // example of specific properties, in this case optimizing image list to |
| // only amazon supplied |
| Properties properties = new Properties(); |
| properties.setProperty(PROPERTY_EC2_AMI_QUERY, "owner-id=137112412989;state=available;image-type=machine"); |
| properties.setProperty(PROPERTY_EC2_CC_AMI_QUERY, ""); |
| long scriptTimeout = TimeUnit.MILLISECONDS.convert(20, TimeUnit.MINUTES); |
| properties.setProperty(TIMEOUT_SCRIPT_COMPLETE, scriptTimeout + ""); |
| |
| // example of injecting a ssh implementation |
| Iterable<Module> modules = ImmutableSet.<Module> of(new SshjSshClientModule(), new SLF4JLoggingModule(), |
| new EnterpriseConfigurationModule()); |
| |
| ContextBuilder builder = ContextBuilder.newBuilder(provider).credentials(identity, credential).modules(modules) |
| .overrides(properties); |
| |
| System.out.printf(">> initializing %s%n", builder.getApiMetadata()); |
| |
| return builder.buildView(ComputeServiceContext.class).getComputeService(); |
| } |
| |
| private static ChefService initChefService(String client, String validator) { |
| try { |
| Properties chefConfig = new Properties(); |
| chefConfig.put(ChefProperties.CHEF_VALIDATOR_NAME, validator); |
| chefConfig.put(ChefProperties.CHEF_VALIDATOR_CREDENTIAL, credentialForClient(validator)); |
| |
| ContextBuilder builder = ContextBuilder.newBuilder(new ChefApiMetadata()) // |
| .credentials(client, credentialForClient(client)) // |
| .modules(ImmutableSet.<Module> of(new SLF4JLoggingModule())) // |
| .overrides(chefConfig); // |
| |
| System.out.printf(">> initializing %s%n", builder.getApiMetadata()); |
| |
| ChefContext context = builder.build(); |
| return context.getChefService(); |
| } catch (Exception e) { |
| System.err.println("error reading private key " + e.getMessage()); |
| System.exit(1); |
| return null; |
| } |
| } |
| |
| private static LoginCredentials getLoginForCommandExecution(Action action) { |
| try { |
| String user = System.getProperty("user.name"); |
| String privateKey = Files.toString(new File(System.getProperty("user.home") + "/.ssh/id_rsa"), UTF_8); |
| return LoginCredentials.builder().user(user).privateKey(privateKey).build(); |
| } catch (Exception e) { |
| System.err.println("error reading ssh key " + e.getMessage()); |
| System.exit(1); |
| return null; |
| } |
| } |
| |
| private static String credentialForClient(final String client) throws Exception { |
| String pemFile = System.getProperty("user.home") + "/.chef/" + client + ".pem"; |
| return Files.toString(new File(pemFile), UTF_8); |
| } |
| |
| } |