From 6b350fb6b75abbe93a8077d52b50eaa93f2d1165 Mon Sep 17 00:00:00 2001 From: Ignasi Barrera Date: Fri, 5 Dec 2014 16:51:53 +0100 Subject: [PATCH] JCLOUDS-792: Configure chef-client security attributes This commit also improves some internal behavior related to the bootstrap configuration generation, and deprecated some methods that will be removed in upcoming versions. * Removes all methods that return the bootstrap DataBag or the raw configuration json to return the BootstrapConfig object instead. * Deprecates all methods that provide support for old Chef versions. * Formats the Enterprise Chef provider according to the jclouds formatting guidelines. * Deprecates the ChefContext view. That view is not an abstraction and only provides access to the ChefService. It can be obtained from the ChefApi itself. --- .../main/java/org/jclouds/chef/ChefApi.java | 8 + .../java/org/jclouds/chef/ChefContext.java | 5 + .../java/org/jclouds/chef/ChefService.java | 24 +-- .../chef/config/BaseChefHttpApiModule.java | 12 +- .../jclouds/chef/config/ChefParserModule.java | 2 +- .../jclouds/chef/config/CookbookParser.java | 2 + .../chef/config/CookbookVersionsParser.java | 2 + .../jclouds/chef/domain/BootstrapConfig.java | 128 ++++++++++++-- .../functions/BootstrapConfigForGroup.java | 27 ++- .../chef/functions/GroupToBootScript.java | 137 ++++++++------- ...CookbookDefinitionCheckingChefVersion.java | 2 + ...seCookbookVersionsCheckingChefVersion.java | 2 + .../ParseCookbookVersionsV09FromJson.java | 2 + .../chef/functions/RunListForGroup.java | 61 ------- .../chef/internal/BaseChefService.java | 105 +++++------ .../chef/internal/ChefContextImpl.java | 7 + .../BootstrapConfigForGroupTest.java | 51 ++++-- .../chef/functions/GroupToBootScriptTest.java | 163 +++++++++--------- .../chef/functions/RunListForGroupTest.java | 92 ---------- .../chef/internal/BaseChefServiceTest.java | 14 +- apis/chef/src/test/resources/bootstrap-env.sh | 11 +- .../src/test/resources/bootstrap-node-env.sh | 11 +- apis/chef/src/test/resources/bootstrap-ssl.sh | 93 ++++++++++ apis/chef/src/test/resources/bootstrap.sh | 6 +- apis/chef/src/test/resources/chef.crt | 30 ++++ project/pom.xml | 1 + .../enterprisechef/EnterpriseChefApi.java | 132 +++++++------- .../EnterpriseChefProviderMetadata.java | 89 +++++----- 28 files changed, 667 insertions(+), 552 deletions(-) delete mode 100644 apis/chef/src/main/java/org/jclouds/chef/functions/RunListForGroup.java delete mode 100644 apis/chef/src/test/java/org/jclouds/chef/functions/RunListForGroupTest.java create mode 100755 apis/chef/src/test/resources/bootstrap-ssl.sh create mode 100644 apis/chef/src/test/resources/chef.crt diff --git a/apis/chef/src/main/java/org/jclouds/chef/ChefApi.java b/apis/chef/src/main/java/org/jclouds/chef/ChefApi.java index 13f49712c7f..0c4889b754d 100644 --- a/apis/chef/src/main/java/org/jclouds/chef/ChefApi.java +++ b/apis/chef/src/main/java/org/jclouds/chef/ChefApi.java @@ -85,6 +85,8 @@ import org.jclouds.rest.annotations.WrapWith; import org.jclouds.rest.binders.BindToJsonPayload; +import com.google.inject.Provides; + /** * Provides synchronous access to Chef. */ @@ -92,6 +94,12 @@ @Headers(keys = "X-Chef-Version", values = "{" + Constants.PROPERTY_API_VERSION + "}") @Consumes(MediaType.APPLICATION_JSON) public interface ChefApi extends Closeable { + + /** + * Provides access to high level Chef features. + */ + @Provides + ChefService chefService(); // Clients diff --git a/apis/chef/src/main/java/org/jclouds/chef/ChefContext.java b/apis/chef/src/main/java/org/jclouds/chef/ChefContext.java index 78096344119..794116d4942 100644 --- a/apis/chef/src/main/java/org/jclouds/chef/ChefContext.java +++ b/apis/chef/src/main/java/org/jclouds/chef/ChefContext.java @@ -25,8 +25,13 @@ /** * Provides an entry point to Chef features. + * + * @deprecated Will be removed in next version. Directly create the + * {@link ChefApi} instead and access the {@link ChefService} from + * it. */ @ImplementedBy(ChefContextImpl.class) +@Deprecated public interface ChefContext extends View, Closeable { /** diff --git a/apis/chef/src/main/java/org/jclouds/chef/ChefService.java b/apis/chef/src/main/java/org/jclouds/chef/ChefService.java index 6f136fa9a31..e15d00ce498 100644 --- a/apis/chef/src/main/java/org/jclouds/chef/ChefService.java +++ b/apis/chef/src/main/java/org/jclouds/chef/ChefService.java @@ -16,23 +16,26 @@ */ package org.jclouds.chef; -import com.google.common.io.InputSupplier; -import com.google.inject.ImplementedBy; +import java.io.IOException; +import java.io.InputStream; +import java.util.List; +import java.util.concurrent.ExecutorService; + +import org.jclouds.chef.config.ChefProperties; import org.jclouds.chef.domain.BootstrapConfig; import org.jclouds.chef.domain.Client; import org.jclouds.chef.domain.CookbookVersion; import org.jclouds.chef.domain.Environment; import org.jclouds.chef.domain.Node; import org.jclouds.chef.internal.BaseChefService; -import org.jclouds.domain.JsonBall; +import org.jclouds.chef.util.ChefUtils; import org.jclouds.javax.annotation.Nullable; +import org.jclouds.ohai.config.OhaiModule; import org.jclouds.rest.annotations.SinceApiVersion; import org.jclouds.scriptbuilder.domain.Statement; -import java.io.IOException; -import java.io.InputStream; -import java.util.List; -import java.util.concurrent.ExecutorService; +import com.google.common.io.InputSupplier; +import com.google.inject.ImplementedBy; /** * Provides high level Chef operations. @@ -105,19 +108,18 @@ public interface ChefService { * * @param The group to get the configured run list for. * @return run list for all nodes bootstrapped with a certain group + * @deprecated USe {{@link #getBootstrapConfigForGroup(String)}. */ + @Deprecated List getRunListForGroup(String group); /** * Gets the bootstrap configuration for a given group. - *

- * The bootstrap configuration is a Json object containing the run list and - * the configured attributes. * * @param group The name of the group. * @return The bootstrap configuration for the given group. */ - JsonBall getBootstrapConfigForGroup(String group); + BootstrapConfig getBootstrapConfigForGroup(String group); // Nodes / Clients diff --git a/apis/chef/src/main/java/org/jclouds/chef/config/BaseChefHttpApiModule.java b/apis/chef/src/main/java/org/jclouds/chef/config/BaseChefHttpApiModule.java index 2fc4201d37e..c8b2faa1b83 100644 --- a/apis/chef/src/main/java/org/jclouds/chef/config/BaseChefHttpApiModule.java +++ b/apis/chef/src/main/java/org/jclouds/chef/config/BaseChefHttpApiModule.java @@ -27,17 +27,16 @@ import java.io.IOException; import java.security.PrivateKey; import java.security.spec.InvalidKeySpecException; -import java.util.List; import java.util.concurrent.TimeUnit; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; +import org.jclouds.chef.domain.BootstrapConfig; import org.jclouds.chef.domain.Client; import org.jclouds.chef.functions.BootstrapConfigForGroup; import org.jclouds.chef.functions.ClientForGroup; -import org.jclouds.chef.functions.RunListForGroup; import org.jclouds.chef.handlers.ChefApiErrorRetryHandler; import org.jclouds.chef.handlers.ChefErrorHandler; import org.jclouds.crypto.Crypto; @@ -45,7 +44,6 @@ import org.jclouds.date.DateService; import org.jclouds.date.TimeStamp; import org.jclouds.domain.Credentials; -import org.jclouds.domain.JsonBall; import org.jclouds.http.HttpErrorHandler; import org.jclouds.http.HttpRetryHandler; import org.jclouds.http.annotation.ClientError; @@ -177,13 +175,7 @@ public Optional provideValidatorCredential(Crypto crypto, Injector i @Provides @Singleton - CacheLoader> runListForGroup(RunListForGroup runListForGroup) { - return CacheLoader.from(runListForGroup); - } - - @Provides - @Singleton - CacheLoader bootstrapConfigForGroup(BootstrapConfigForGroup bootstrapConfigForGroup) { + CacheLoader bootstrapConfigForGroup(BootstrapConfigForGroup bootstrapConfigForGroup) { return CacheLoader.from(bootstrapConfigForGroup); } diff --git a/apis/chef/src/main/java/org/jclouds/chef/config/ChefParserModule.java b/apis/chef/src/main/java/org/jclouds/chef/config/ChefParserModule.java index c214236810f..c00eda524ca 100644 --- a/apis/chef/src/main/java/org/jclouds/chef/config/ChefParserModule.java +++ b/apis/chef/src/main/java/org/jclouds/chef/config/ChefParserModule.java @@ -288,7 +288,7 @@ protected TypeAdapter newAdapter(TypeAdapter keyAdapter, TypeAda return (TypeAdapter) new KeepLastRepeatedKeyMapTypeAdapter(keyAdapter, valueAdapter); } } - + @Provides @Singleton public Map provideCustomAdapterBindings(DataBagItemAdapter adapter, PrivateKeyAdapter privateAdapter, diff --git a/apis/chef/src/main/java/org/jclouds/chef/config/CookbookParser.java b/apis/chef/src/main/java/org/jclouds/chef/config/CookbookParser.java index 34eedde8b5e..22048c5d64a 100644 --- a/apis/chef/src/main/java/org/jclouds/chef/config/CookbookParser.java +++ b/apis/chef/src/main/java/org/jclouds/chef/config/CookbookParser.java @@ -32,10 +32,12 @@ * Chef Server version 0.9 and 0.10 return a different Json when rquesting the * cookbook definitions. This annotation can be used to setup the cookbook * parser. + * @deprecated Support for Chef 0.9 and 0.10 will be removed in upcoming versions. */ @Target({ METHOD, PARAMETER, FIELD }) @Retention(RUNTIME) @Qualifier +@Deprecated public @interface CookbookParser { } diff --git a/apis/chef/src/main/java/org/jclouds/chef/config/CookbookVersionsParser.java b/apis/chef/src/main/java/org/jclouds/chef/config/CookbookVersionsParser.java index c9469178852..5fd2ba5d08e 100644 --- a/apis/chef/src/main/java/org/jclouds/chef/config/CookbookVersionsParser.java +++ b/apis/chef/src/main/java/org/jclouds/chef/config/CookbookVersionsParser.java @@ -32,10 +32,12 @@ * Chef Server version 0.9 and 0.10 return a different Json when rquesting the * cookbook versions. This annotation can be used to setup the cookbook versions * parser. + * @deprecated Support for Chef 0.9 and 0.10 will be removed in upcoming versions. */ @Target({ METHOD, PARAMETER, FIELD }) @Retention(RUNTIME) @Qualifier +@Deprecated public @interface CookbookVersionsParser { } diff --git a/apis/chef/src/main/java/org/jclouds/chef/domain/BootstrapConfig.java b/apis/chef/src/main/java/org/jclouds/chef/domain/BootstrapConfig.java index c10c1500be4..7b10edc6c26 100644 --- a/apis/chef/src/main/java/org/jclouds/chef/domain/BootstrapConfig.java +++ b/apis/chef/src/main/java/org/jclouds/chef/domain/BootstrapConfig.java @@ -18,12 +18,14 @@ import static com.google.common.base.Preconditions.checkNotNull; +import java.beans.ConstructorProperties; import java.util.List; import org.jclouds.domain.JsonBall; +import org.jclouds.javax.annotation.Nullable; -import com.google.common.base.Optional; import com.google.common.collect.ImmutableList; +import com.google.gson.annotations.SerializedName; /** * Configures how the nodes in a group will bootstrap. @@ -31,6 +33,16 @@ * @since 1.7 */ public class BootstrapConfig { + + public static enum SSLVerifyMode { + NONE, PEER; + + @Override + public String toString() { + return ":verify_" + name().toLowerCase(); + } + } + public static Builder builder() { return new Builder(); } @@ -39,6 +51,10 @@ public static class Builder { private ImmutableList.Builder runList = ImmutableList.builder(); private String environment; private JsonBall attribtues; + private String sslCAFile; + private String sslCAPath; + private SSLVerifyMode sslVerifyMode; + private Boolean verifyApiCert; /** * Sets the run list that will be executed in the nodes of the group. @@ -64,32 +80,118 @@ public Builder attributes(JsonBall attributes) { return this; } + /** + * The file in which the OpenSSL key is saved. To be used by the Chef + * client to verify the certificate of the Chef Server. + */ + public Builder sslCAFile(String sslCAFile) { + this.sslCAFile = checkNotNull(sslCAFile, "sslCAFile"); + return this; + } + + /** + * The path to where the OpenSSL keys that are used by the Chef client are + * located. + */ + public Builder sslCAPath(String sslCAPath) { + this.sslCAPath = checkNotNull(sslCAPath, "sslCAPath"); + return this; + } + + /** + * The verify mode for HTTPS requests. + *

+ */ + public Builder sslVerifyMode(SSLVerifyMode sslVerifyMode) { + this.sslVerifyMode = checkNotNull(sslVerifyMode, "sslVerifyMode"); + return this; + } + + /** + * Use to only do SSL validation of the Chef server connection; may be + * needed if the Chef client needs to talk to other services that have + * broken SSL certificates. + */ + public Builder verifyApiCert(boolean verifyApiCert) { + this.verifyApiCert = verifyApiCert; + return this; + } + public BootstrapConfig build() { - return new BootstrapConfig(runList.build(), Optional.fromNullable(environment), - Optional.fromNullable(attribtues)); + return new BootstrapConfig(runList.build(), environment, attribtues, sslCAFile, sslCAPath, sslVerifyMode, + verifyApiCert); } } + @SerializedName("run_list") private final List runList; - private final Optional environment; - private final Optional attribtues; - - protected BootstrapConfig(List runList, Optional environment, Optional attribtues) { - this.runList = checkNotNull(runList, "runList"); - this.environment = checkNotNull(environment, "environment"); - this.attribtues = checkNotNull(attribtues, "attributes"); + @Nullable + private final String environment; + @Nullable + private final JsonBall attributes; + @SerializedName("ssl_ca_file") + @Nullable + private final String sslCAFile; + @SerializedName("ssl_ca_path") + @Nullable + private final String sslCAPath; + @SerializedName("ssl_verify_mode") + @Nullable + private final SSLVerifyMode sslVerifyMode; + @SerializedName("verify_api_cert") + @Nullable + private final Boolean verifyApiCert; + + @ConstructorProperties({ "run_list", "environment", "attributes", "ssl_ca_file", "ssl_ca_path", "ssl_verify_mode", + "verify_api_cert" }) + protected BootstrapConfig(List runList, @Nullable String environment, @Nullable JsonBall attributes, + @Nullable String sslCAFile, @Nullable String sslCAPath, @Nullable SSLVerifyMode sslVerifyMode, + @Nullable Boolean verifyApiCert) { + this.runList = ImmutableList.copyOf(checkNotNull(runList, "runList")); + this.environment = environment; + this.attributes = attributes; + this.sslCAFile = sslCAFile; + this.sslCAPath = sslCAPath; + this.sslVerifyMode = sslVerifyMode; + this.verifyApiCert = verifyApiCert; } public List getRunList() { return runList; } - public Optional getEnvironment() { + @Nullable + public String getEnvironment() { return environment; } - public Optional getAttribtues() { - return attribtues; + @Nullable + public JsonBall getAttributes() { + return attributes; + } + + @Nullable + public String getSslCAFile() { + return sslCAFile; + } + + @Nullable + public String getSslCAPath() { + return sslCAPath; + } + + @Nullable + public SSLVerifyMode getSslVerifyMode() { + return sslVerifyMode; + } + + @Nullable + public Boolean getVerifyApiCert() { + return verifyApiCert; } } diff --git a/apis/chef/src/main/java/org/jclouds/chef/functions/BootstrapConfigForGroup.java b/apis/chef/src/main/java/org/jclouds/chef/functions/BootstrapConfigForGroup.java index ec75e479bc8..32f7c4b3a6c 100644 --- a/apis/chef/src/main/java/org/jclouds/chef/functions/BootstrapConfigForGroup.java +++ b/apis/chef/src/main/java/org/jclouds/chef/functions/BootstrapConfigForGroup.java @@ -16,46 +16,43 @@ */ package org.jclouds.chef.functions; -import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; import static org.jclouds.chef.config.ChefProperties.CHEF_BOOTSTRAP_DATABAG; -import java.lang.reflect.Type; -import java.util.Map; - import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import org.jclouds.chef.ChefApi; +import org.jclouds.chef.domain.BootstrapConfig; import org.jclouds.chef.domain.DatabagItem; -import org.jclouds.domain.JsonBall; +import org.jclouds.json.Json; import com.google.common.base.Function; -import com.google.inject.TypeLiteral; /** - * * Retrieves the bootstrap configuration for a specific group */ @Singleton -public class BootstrapConfigForGroup implements Function { - public static final Type BOOTSTRAP_CONFIG_TYPE = new TypeLiteral>() { - }.getType(); +public class BootstrapConfigForGroup implements Function { + private final ChefApi api; private final String databag; + private final Json json; @Inject - public BootstrapConfigForGroup(@Named(CHEF_BOOTSTRAP_DATABAG) String databag, ChefApi api) { - this.databag = checkNotNull(databag, "databag"); - this.api = checkNotNull(api, "api"); + BootstrapConfigForGroup(@Named(CHEF_BOOTSTRAP_DATABAG) String databag, ChefApi api, Json json) { + this.databag = databag; + this.api = api; + this.json = json; } @Override - public DatabagItem apply(String from) { + public BootstrapConfig apply(String from) { DatabagItem bootstrapConfig = api.getDatabagItem(databag, from); checkState(bootstrapConfig != null, "databag item %s/%s not found", databag, from); - return bootstrapConfig; + // A DatabagItem is already a JsonBall, to we can easily deserialize it + return json.fromJson(bootstrapConfig.toString(), BootstrapConfig.class); } } diff --git a/apis/chef/src/main/java/org/jclouds/chef/functions/GroupToBootScript.java b/apis/chef/src/main/java/org/jclouds/chef/functions/GroupToBootScript.java index 60103533d41..fad4fb6edfe 100644 --- a/apis/chef/src/main/java/org/jclouds/chef/functions/GroupToBootScript.java +++ b/apis/chef/src/main/java/org/jclouds/chef/functions/GroupToBootScript.java @@ -17,17 +17,13 @@ package org.jclouds.chef.functions; import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.base.Throwables.propagate; -import static org.jclouds.scriptbuilder.domain.Statements.appendFile; +import static com.google.common.collect.Iterables.transform; +import static org.jclouds.scriptbuilder.domain.Statements.createOrOverwriteFile; import static org.jclouds.scriptbuilder.domain.Statements.exec; -import static org.jclouds.scriptbuilder.domain.Statements.newStatementList; -import java.lang.reflect.Type; import java.net.URI; import java.security.PrivateKey; import java.util.Collections; -import java.util.List; -import java.util.Map; import java.util.regex.Pattern; import javax.inject.Inject; @@ -35,100 +31,119 @@ import org.jclouds.chef.config.InstallChef; import org.jclouds.chef.config.Validator; +import org.jclouds.chef.domain.BootstrapConfig; import org.jclouds.crypto.Pems; -import org.jclouds.domain.JsonBall; import org.jclouds.javax.annotation.Nullable; -import org.jclouds.json.Json; import org.jclouds.location.Provider; import org.jclouds.scriptbuilder.ExitInsteadOfReturn; import org.jclouds.scriptbuilder.domain.Statement; +import org.jclouds.scriptbuilder.domain.StatementList; -import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.base.Optional; import com.google.common.base.Splitter; import com.google.common.base.Supplier; +import com.google.common.base.Throwables; import com.google.common.cache.CacheLoader; import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import com.google.inject.TypeLiteral; /** - * * Generates a bootstrap script relevant for a particular group */ @Singleton public class GroupToBootScript { private static final Pattern newLinePattern = Pattern.compile("(\\r\\n)|(\\n)"); - - @VisibleForTesting - static final Type RUN_LIST_TYPE = new TypeLiteral>>() { - }.getType(); + private final Supplier endpoint; - private final Json json; - private final CacheLoader bootstrapConfigForGroup; + private final CacheLoader bootstrapConfigForGroup; private final Statement installChef; private final Optional validatorName; private final Optional validatorCredential; @Inject - public GroupToBootScript(@Provider Supplier endpoint, Json json, - CacheLoader bootstrapConfigForGroup, + GroupToBootScript(@Provider Supplier endpoint, CacheLoader bootstrapConfigForGroup, @InstallChef Statement installChef, @Validator Optional validatorName, @Validator Optional validatorCredential) { - this.endpoint = checkNotNull(endpoint, "endpoint"); - this.json = checkNotNull(json, "json"); - this.bootstrapConfigForGroup = checkNotNull(bootstrapConfigForGroup, "bootstrapConfigForGroup"); - this.installChef = checkNotNull(installChef, "installChef"); - this.validatorName = checkNotNull(validatorName, "validatorName"); - this.validatorCredential = checkNotNull(validatorCredential, validatorCredential); + this.endpoint = endpoint; + this.bootstrapConfigForGroup = bootstrapConfigForGroup; + this.installChef = installChef; + this.validatorName = validatorName; + this.validatorCredential = validatorCredential; } public Statement apply(String group, @Nullable String nodeName) { - checkNotNull(group, "group"); - String validatorClientName = validatorName.get(); - PrivateKey validatorKey = validatorCredential.get(); - - JsonBall bootstrapConfig = null; + BootstrapConfig config = null; try { - bootstrapConfig = bootstrapConfigForGroup.load(group); - } catch (Exception e) { - throw propagate(e); + config = bootstrapConfigForGroup.load(checkNotNull(group, "group")); + } catch (Exception ex) { + throw Throwables.propagate(ex); } - Map config = json.fromJson(bootstrapConfig.toString(), - BootstrapConfigForGroup.BOOTSTRAP_CONFIG_TYPE); - Optional environment = Optional.fromNullable(config.get("environment")); - String chefConfigDir = "{root}etc{fs}chef"; - Statement createChefConfigDir = exec("{md} " + chefConfigDir); - String createNodeName; - if (nodeName != null) { - createNodeName = String.format("node_name \"%s\"", nodeName); - } else { - createNodeName = String.format("node_name \"%s-\" + o[:ipaddress]", group); - } - Statement createClientRb = appendFile(chefConfigDir + "{fs}client.rb", ImmutableList.of("require 'rubygems'", - "require 'ohai'", "o = Ohai::System.new", "o.all_plugins", createNodeName, "log_level :info", "log_location STDOUT", - String.format("validation_client_name \"%s\"", validatorClientName), - String.format("chef_server_url \"%s\"", endpoint.get()))); + String chefBootFile = chefConfigDir + "{fs}first-boot.json"; - Statement createValidationPem = appendFile(chefConfigDir + "{fs}validation.pem", - Splitter.on(newLinePattern).split(Pems.pem(validatorKey))); + ImmutableList.Builder statements = ImmutableList.builder(); + statements.add(new ExitInsteadOfReturn(installChef)); + statements.add(exec("{md} " + chefConfigDir)); + if (config.getSslCAFile() != null) { + statements.add(createOrOverwriteFile(chefConfigDir + "{fs}chef-server.crt", + Splitter.on(newLinePattern).split(config.getSslCAFile()))); + } + statements.add(createClientRbFile(chefConfigDir + "{fs}client.rb", group, nodeName, config)); + statements.add(createOrOverwriteFile(chefConfigDir + "{fs}validation.pem", + Splitter.on(newLinePattern).split(Pems.pem(validatorCredential.get())))); + statements.add(createAttributesFile(chefBootFile, config)); + statements.add(exec("chef-client -j " + chefBootFile)); - String chefBootFile = chefConfigDir + "{fs}first-boot.json"; - Statement createFirstBoot = appendFile(chefBootFile, Collections.singleton(json.toJson(bootstrapConfig))); + return new StatementList(statements.build()); + } - ImmutableMap.Builder options = ImmutableMap.builder(); - options.put("-j", chefBootFile); - if (environment.isPresent()) { - options.put("-E", environment.get().toString()); + private Statement createClientRbFile(String clientRbFile, String group, String nodeName, BootstrapConfig config) { + ImmutableList.Builder clientRb = ImmutableList.builder(); + clientRb.add("require 'rubygems'"); + clientRb.add("require 'ohai'"); + clientRb.add("o = Ohai::System.new"); + clientRb.add("o.all_plugins"); + clientRb.add("node_name \"" + (nodeName != null ? nodeName + "\"" : group + "-\" + o[:ipaddress]")); + clientRb.add("log_level :info"); + clientRb.add("log_location STDOUT"); + clientRb.add(String.format("validation_client_name \"%s\"", validatorName.get())); + clientRb.add(String.format("chef_server_url \"%s\"", endpoint.get())); + addIfPresent(clientRb, "environment", config.getEnvironment()); + if (config.getSslCAFile() != null) { + addIfPresent(clientRb, "ssl_ca_file", "/etc/chef/chef-server.crt"); } - String strOptions = Joiner.on(' ').withKeyValueSeparator(" ").join(options.build()); - Statement runChef = exec("chef-client " + strOptions); + addIfPresent(clientRb, "ssl_ca_path", config.getSslCAPath()); + addIfPresent(clientRb, "ssl_verify_mode", config.getSslVerifyMode()); + addIfPresent(clientRb, "verify_api_cert", config.getVerifyApiCert()); + return createOrOverwriteFile(clientRbFile, clientRb.build()); + } - return newStatementList(new ExitInsteadOfReturn(installChef), createChefConfigDir, createClientRb, createValidationPem, - createFirstBoot, runChef); + private Statement createAttributesFile(String chefBootFile, BootstrapConfig config) { + String attributes = config.getAttributes().toString(); + String runlist = Joiner.on(',').join(transform(config.getRunList(), new Function() { + @Override + public String apply(String input) { + return "\"" + input + "\""; + } + })); + + // Append the runlist to the json attributes + StringBuilder sb = new StringBuilder(); + // Strip the json ending character + sb.append(attributes.trim().substring(0, attributes.length() - 1)); + sb.append(",\"run_list\":[").append(runlist).append("]"); + sb.append("}"); + + return createOrOverwriteFile(chefBootFile, Collections.singleton(sb.toString())); + } + + private void addIfPresent(ImmutableList.Builder lines, String key, Object value) { + if (value != null) { + // Quote the value if it is a String + lines.add(String.format("%s %s", key, value instanceof String ? "\"" + value + "\"" : value.toString())); + } } } diff --git a/apis/chef/src/main/java/org/jclouds/chef/functions/ParseCookbookDefinitionCheckingChefVersion.java b/apis/chef/src/main/java/org/jclouds/chef/functions/ParseCookbookDefinitionCheckingChefVersion.java index ffb4201c3e5..53c6ea7374c 100644 --- a/apis/chef/src/main/java/org/jclouds/chef/functions/ParseCookbookDefinitionCheckingChefVersion.java +++ b/apis/chef/src/main/java/org/jclouds/chef/functions/ParseCookbookDefinitionCheckingChefVersion.java @@ -30,8 +30,10 @@ /** * Parses a cookbook definition from a Json response, taking care of using the * appropriate parser. + * @deprecated Support for Chef 0.9 and 0.10 will be removed in upcoming verions. */ @Singleton +@Deprecated public class ParseCookbookDefinitionCheckingChefVersion implements Function> { @VisibleForTesting diff --git a/apis/chef/src/main/java/org/jclouds/chef/functions/ParseCookbookVersionsCheckingChefVersion.java b/apis/chef/src/main/java/org/jclouds/chef/functions/ParseCookbookVersionsCheckingChefVersion.java index f82a9002936..e98b27ab993 100644 --- a/apis/chef/src/main/java/org/jclouds/chef/functions/ParseCookbookVersionsCheckingChefVersion.java +++ b/apis/chef/src/main/java/org/jclouds/chef/functions/ParseCookbookVersionsCheckingChefVersion.java @@ -30,8 +30,10 @@ /** * Parses a cookbook versions from a Json response, taking care of using the * appropriate parser. + * @deprecated Support for Chef 0.9 and 0.10 will be removed in upcoming verions. */ @Singleton +@Deprecated public class ParseCookbookVersionsCheckingChefVersion implements Function> { @VisibleForTesting diff --git a/apis/chef/src/main/java/org/jclouds/chef/functions/ParseCookbookVersionsV09FromJson.java b/apis/chef/src/main/java/org/jclouds/chef/functions/ParseCookbookVersionsV09FromJson.java index 4421b3e3274..4bc76d416cd 100644 --- a/apis/chef/src/main/java/org/jclouds/chef/functions/ParseCookbookVersionsV09FromJson.java +++ b/apis/chef/src/main/java/org/jclouds/chef/functions/ParseCookbookVersionsV09FromJson.java @@ -30,8 +30,10 @@ /** * Parses the cookbook versions in a Chef Server <= 0.9.8. + * @deprecated Support for Chef 0.9 and 0.10 will be removed in upcoming versions. */ @Singleton +@Deprecated public class ParseCookbookVersionsV09FromJson implements Function> { private final ParseJson>> json; diff --git a/apis/chef/src/main/java/org/jclouds/chef/functions/RunListForGroup.java b/apis/chef/src/main/java/org/jclouds/chef/functions/RunListForGroup.java deleted file mode 100644 index b14ae71656b..00000000000 --- a/apis/chef/src/main/java/org/jclouds/chef/functions/RunListForGroup.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * 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.chef.functions; - -import static com.google.common.base.Preconditions.checkNotNull; - -import java.lang.reflect.Type; -import java.util.List; -import java.util.Map; - -import javax.inject.Inject; -import javax.inject.Singleton; - -import org.jclouds.chef.domain.DatabagItem; -import org.jclouds.domain.JsonBall; -import org.jclouds.json.Json; - -import com.google.common.base.Function; -import com.google.inject.TypeLiteral; - -/** - * Retrieves the run-list for a specific group - */ -@Singleton -public class RunListForGroup implements Function> { - public static final Type RUN_LIST_TYPE = new TypeLiteral>() { - }.getType(); - private final BootstrapConfigForGroup bootstrapConfigForGroup; - - private final Json json; - - @Inject - public RunListForGroup(BootstrapConfigForGroup bootstrapConfigForGroup, Json json) { - this.bootstrapConfigForGroup = checkNotNull(bootstrapConfigForGroup, "bootstrapConfigForGroup"); - this.json = checkNotNull(json, "json"); - } - - @Override - public List apply(String from) { - DatabagItem bootstrapConfig = bootstrapConfigForGroup.apply(from); - Map config = json.fromJson(bootstrapConfig.toString(), - BootstrapConfigForGroup.BOOTSTRAP_CONFIG_TYPE); - JsonBall runlist = config.get("run_list"); - return json.fromJson(runlist.toString(), RUN_LIST_TYPE); - } - -} diff --git a/apis/chef/src/main/java/org/jclouds/chef/internal/BaseChefService.java b/apis/chef/src/main/java/org/jclouds/chef/internal/BaseChefService.java index 838fa4fb959..ca79b772c6d 100644 --- a/apis/chef/src/main/java/org/jclouds/chef/internal/BaseChefService.java +++ b/apis/chef/src/main/java/org/jclouds/chef/internal/BaseChefService.java @@ -43,7 +43,6 @@ import org.jclouds.chef.domain.Node; import org.jclouds.chef.functions.BootstrapConfigForGroup; import org.jclouds.chef.functions.GroupToBootScript; -import org.jclouds.chef.functions.RunListForGroup; import org.jclouds.chef.strategy.CleanupStaleNodesAndClients; import org.jclouds.chef.strategy.CreateNodeAndPopulateAutomaticAttributes; import org.jclouds.chef.strategy.DeleteAllClientsInList; @@ -56,7 +55,6 @@ import org.jclouds.chef.strategy.ListNodesInEnvironment; import org.jclouds.chef.strategy.UpdateAutomaticAttributesOnNode; import org.jclouds.crypto.Crypto; -import org.jclouds.domain.JsonBall; import org.jclouds.io.ByteStreams2; import org.jclouds.io.Payloads; import org.jclouds.io.payloads.RSADecryptingPayload; @@ -67,8 +65,8 @@ import org.jclouds.scriptbuilder.domain.Statement; import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Optional; import com.google.common.base.Supplier; -import com.google.common.collect.Maps; import com.google.common.io.InputSupplier; @Singleton @@ -87,7 +85,6 @@ public class BaseChefService implements ChefService { private final GroupToBootScript groupToBootScript; private final String databag; private final BootstrapConfigForGroup bootstrapConfigForGroup; - private final RunListForGroup runListForGroup; private final ListCookbookVersions listCookbookVersions; private final ListCookbookVersionsInEnvironment listCookbookVersionsInEnvironment; private final ListEnvironments listEnvironments; @@ -100,38 +97,34 @@ public class BaseChefService implements ChefService { protected Logger logger = Logger.NULL; @Inject - protected BaseChefService(ChefContext chefContext, ChefApi api, - CleanupStaleNodesAndClients cleanupStaleNodesAndClients, + BaseChefService(ChefContext chefContext, ChefApi api, CleanupStaleNodesAndClients cleanupStaleNodesAndClients, CreateNodeAndPopulateAutomaticAttributes createNodeAndPopulateAutomaticAttributes, DeleteAllNodesInList deleteAllNodesInList, ListNodes listNodes, DeleteAllClientsInList deleteAllClientsInList, ListClients listClients, ListCookbookVersions listCookbookVersions, UpdateAutomaticAttributesOnNode updateAutomaticAttributesOnNode, Supplier privateKey, @Named(CHEF_BOOTSTRAP_DATABAG) String databag, GroupToBootScript groupToBootScript, - BootstrapConfigForGroup bootstrapConfigForGroup, RunListForGroup runListForGroup, - ListEnvironments listEnvironments, ListNodesInEnvironment listNodesInEnvironment, + BootstrapConfigForGroup bootstrapConfigForGroup, ListEnvironments listEnvironments, + ListNodesInEnvironment listNodesInEnvironment, ListCookbookVersionsInEnvironment listCookbookVersionsInEnvironment, Json json, Crypto crypto) { - this.chefContext = checkNotNull(chefContext, "chefContext"); - this.api = checkNotNull(api, "api"); - this.cleanupStaleNodesAndClients = checkNotNull(cleanupStaleNodesAndClients, "cleanupStaleNodesAndClients"); - this.createNodeAndPopulateAutomaticAttributes = checkNotNull(createNodeAndPopulateAutomaticAttributes, - "createNodeAndPopulateAutomaticAttributes"); - this.deleteAllNodesInList = checkNotNull(deleteAllNodesInList, "deleteAllNodesInList"); - this.listNodes = checkNotNull(listNodes, "listNodes"); - this.deleteAllClientsInList = checkNotNull(deleteAllClientsInList, "deleteAllClientsInList"); - this.listClients = checkNotNull(listClients, "listClients"); - this.listCookbookVersions = checkNotNull(listCookbookVersions, "listCookbookVersions"); - this.updateAutomaticAttributesOnNode = checkNotNull(updateAutomaticAttributesOnNode, - "updateAutomaticAttributesOnNode"); - this.privateKey = checkNotNull(privateKey, "privateKey"); - this.groupToBootScript = checkNotNull(groupToBootScript, "groupToBootScript"); - this.databag = checkNotNull(databag, "databag"); - this.bootstrapConfigForGroup = checkNotNull(bootstrapConfigForGroup, "bootstrapConfigForGroup"); - this.runListForGroup = checkNotNull(runListForGroup, "runListForGroup"); - this.listEnvironments = checkNotNull(listEnvironments, "listEnvironments"); - this.listNodesInEnvironment = checkNotNull(listNodesInEnvironment, "listNodesInEnvironment"); - this.listCookbookVersionsInEnvironment = checkNotNull(listCookbookVersionsInEnvironment, "listCookbookVersionsInEnvironment"); - this.json = checkNotNull(json, "json"); - this.crypto = checkNotNull(crypto, "crypto"); + this.chefContext = chefContext; + this.api = api; + this.cleanupStaleNodesAndClients = cleanupStaleNodesAndClients; + this.createNodeAndPopulateAutomaticAttributes = createNodeAndPopulateAutomaticAttributes; + this.deleteAllNodesInList = deleteAllNodesInList; + this.listNodes = listNodes; + this.deleteAllClientsInList = deleteAllClientsInList; + this.listClients = listClients; + this.listCookbookVersions = listCookbookVersions; + this.updateAutomaticAttributesOnNode = updateAutomaticAttributesOnNode; + this.privateKey = privateKey; + this.groupToBootScript = groupToBootScript; + this.databag = databag; + this.bootstrapConfigForGroup = bootstrapConfigForGroup; + this.listEnvironments = listEnvironments; + this.listNodesInEnvironment = listNodesInEnvironment; + this.listCookbookVersionsInEnvironment = listCookbookVersionsInEnvironment; + this.json = json; + this.crypto = crypto; } @Override @@ -141,34 +134,20 @@ public ChefContext getContext() { @Override public byte[] encrypt(InputSupplier supplier) throws IOException { - return ByteStreams2.toByteArrayAndClose(new RSAEncryptingPayload(crypto, Payloads.newPayload(supplier.getInput()), privateKey - .get()).openStream()); + return ByteStreams2.toByteArrayAndClose(new RSAEncryptingPayload(crypto, + Payloads.newPayload(supplier.getInput()), privateKey.get()).openStream()); } @Override public byte[] decrypt(InputSupplier supplier) throws IOException { - return ByteStreams2.toByteArrayAndClose(new RSADecryptingPayload(crypto, Payloads.newPayload(supplier.getInput()), privateKey - .get()).openStream()); + return ByteStreams2.toByteArrayAndClose(new RSADecryptingPayload(crypto, + Payloads.newPayload(supplier.getInput()), privateKey.get()).openStream()); } - @VisibleForTesting - String buildBootstrapConfiguration(BootstrapConfig bootstrapConfig) { - checkNotNull(bootstrapConfig, "bootstrapConfig must not be null"); - - Map configMap = Maps.newLinkedHashMap(); - configMap.put("run_list", bootstrapConfig.getRunList()); - - if (bootstrapConfig.getEnvironment().isPresent()) { - configMap.put("environment", bootstrapConfig.getEnvironment().get()); - } - - if (bootstrapConfig.getAttribtues().isPresent()) { - Map attributes = json.fromJson(bootstrapConfig.getAttribtues().get().toString(), - BootstrapConfigForGroup.BOOTSTRAP_CONFIG_TYPE); - configMap.putAll(attributes); + private static void putIfPresent(Map configMap, Optional configProperty, String name) { + if (configProperty.isPresent()) { + configMap.put(name, configProperty.get().toString()); } - - return json.toJson(configMap); } @Override @@ -183,6 +162,7 @@ public Statement createBootstrapScriptForGroup(String group) { @Override public void updateBootstrapConfigForGroup(String group, BootstrapConfig bootstrapConfig) { + checkNotNull(bootstrapConfig, "bootstrapConfig cannot be null"); try { api.createDatabag(databag); } catch (IllegalStateException e) { @@ -190,22 +170,31 @@ public void updateBootstrapConfigForGroup(String group, BootstrapConfig bootstra } String jsonConfig = buildBootstrapConfiguration(bootstrapConfig); - DatabagItem runlist = new DatabagItem(group, jsonConfig); + DatabagItem config = new DatabagItem(group, jsonConfig); if (api.getDatabagItem(databag, group) == null) { - api.createDatabagItem(databag, runlist); + api.createDatabagItem(databag, config); } else { - api.updateDatabagItem(databag, runlist); + api.updateDatabagItem(databag, config); } } + + @VisibleForTesting + String buildBootstrapConfiguration(BootstrapConfig config) { + return json.toJson(config); + } + /** + * @deprecated Use {{@link #getBootstrapConfigForGroup(String)}. + */ @Override + @Deprecated public List getRunListForGroup(String group) { - return runListForGroup.apply(group); + return getBootstrapConfigForGroup(group).getRunList(); } @Override - public JsonBall getBootstrapConfigForGroup(String group) { + public BootstrapConfig getBootstrapConfigForGroup(String group) { return bootstrapConfigForGroup.apply(group); } @@ -259,8 +248,8 @@ public Iterable listCookbookVersions() { return listCookbookVersions.execute(); } - @Override public Iterable listCookbookVersions( - ExecutorService executorService) { + @Override + public Iterable listCookbookVersions(ExecutorService executorService) { return listCookbookVersions.execute(executorService); } diff --git a/apis/chef/src/main/java/org/jclouds/chef/internal/ChefContextImpl.java b/apis/chef/src/main/java/org/jclouds/chef/internal/ChefContextImpl.java index 8aef880a253..e7d1170f5cf 100644 --- a/apis/chef/src/main/java/org/jclouds/chef/internal/ChefContextImpl.java +++ b/apis/chef/src/main/java/org/jclouds/chef/internal/ChefContextImpl.java @@ -24,6 +24,7 @@ import javax.inject.Singleton; import org.jclouds.Context; +import org.jclouds.chef.ChefApi; import org.jclouds.chef.ChefContext; import org.jclouds.chef.ChefService; import org.jclouds.internal.BaseView; @@ -31,7 +32,13 @@ import com.google.common.reflect.TypeToken; +/** + * @deprecated Will be removed in next version. Directly create the + * {@link ChefApi} instead and access the {@link ChefService} from + * it. + */ @Singleton +@Deprecated public class ChefContextImpl extends BaseView implements ChefContext { private final ChefService chefService; diff --git a/apis/chef/src/test/java/org/jclouds/chef/functions/BootstrapConfigForGroupTest.java b/apis/chef/src/test/java/org/jclouds/chef/functions/BootstrapConfigForGroupTest.java index f3264825ba8..c704ee6f10b 100644 --- a/apis/chef/src/test/java/org/jclouds/chef/functions/BootstrapConfigForGroupTest.java +++ b/apis/chef/src/test/java/org/jclouds/chef/functions/BootstrapConfigForGroupTest.java @@ -25,50 +25,65 @@ import java.io.IOException; import org.jclouds.chef.ChefApi; +import org.jclouds.chef.domain.BootstrapConfig; import org.jclouds.chef.domain.Client; import org.jclouds.chef.domain.DatabagItem; -import org.jclouds.rest.annotations.Api; +import org.jclouds.json.Json; +import org.jclouds.json.config.GsonModule; +import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; +import com.google.inject.Guice; +import com.google.inject.Injector; + @Test(groups = "unit", testName = "BootstrapConfigForGroupTest") public class BootstrapConfigForGroupTest { + private Json json; + + @BeforeClass + public void setup() { + Injector injector = Guice.createInjector(new GsonModule()); + json = injector.getInstance(Json.class); + } + @Test(expectedExceptions = IllegalStateException.class) public void testWhenNoDatabagItem() throws IOException { ChefApi chefApi = createMock(ChefApi.class); Client client = createMock(Client.class); - BootstrapConfigForGroup fn = new BootstrapConfigForGroup("jclouds", chefApi); + BootstrapConfigForGroup fn = new BootstrapConfigForGroup("jclouds", chefApi, json); expect(chefApi.getDatabagItem("jclouds", "foo")).andReturn(null); - - replay(client); - replay(chefApi); + replay(client, chefApi); fn.apply("foo"); - verify(client); - verify(chefApi); + verify(client, chefApi); } @Test public void testReturnsItem() throws IOException { ChefApi chefApi = createMock(ChefApi.class); - Api api = createMock(Api.class); - - BootstrapConfigForGroup fn = new BootstrapConfigForGroup("jclouds", chefApi); - DatabagItem config = new DatabagItem("foo", - "{\"tomcat6\":{\"ssl_port\":8433},\"run_list\":[\"recipe[apache2]\",\"role[webserver]\"]}"); + Client client = createMock(Client.class); - expect(chefApi.getDatabagItem("jclouds", "foo")).andReturn(config); + BootstrapConfigForGroup fn = new BootstrapConfigForGroup("jclouds", chefApi, json); + DatabagItem databag = new DatabagItem("foo", + "{\"environment\":\"development\",\"ssl_ca_file\":\"/etc/certs/chef-server.crt\"," + + "\"run_list\":[\"recipe[apache2]\",\"role[webserver]\"]," + + "\"attributes\":{\"tomcat6\":{\"ssl_port\":8433}}}"); - replay(api); - replay(chefApi); + expect(chefApi.getDatabagItem("jclouds", "foo")).andReturn(databag); + replay(client, chefApi); - assertEquals(fn.apply("foo"), config); + BootstrapConfig config = fn.apply("foo"); + assertEquals(config.getEnvironment(), "development"); + assertEquals(config.getSslCAFile(), "/etc/certs/chef-server.crt"); + assertEquals(config.getRunList().get(0), "recipe[apache2]"); + assertEquals(config.getRunList().get(1), "role[webserver]"); + assertEquals(config.getAttributes().toString(), "{\"tomcat6\":{\"ssl_port\":8433}}"); - verify(api); - verify(chefApi); + verify(client, chefApi); } } diff --git a/apis/chef/src/test/java/org/jclouds/chef/functions/GroupToBootScriptTest.java b/apis/chef/src/test/java/org/jclouds/chef/functions/GroupToBootScriptTest.java index 89b6ec0039c..9cf0690004b 100644 --- a/apis/chef/src/test/java/org/jclouds/chef/functions/GroupToBootScriptTest.java +++ b/apis/chef/src/test/java/org/jclouds/chef/functions/GroupToBootScriptTest.java @@ -33,10 +33,11 @@ import org.jclouds.chef.config.ChefBootstrapModule; import org.jclouds.chef.config.ChefParserModule; import org.jclouds.chef.config.InstallChef; -import org.jclouds.chef.domain.DatabagItem; +import org.jclouds.chef.domain.BootstrapConfig; +import org.jclouds.chef.domain.BootstrapConfig.SSLVerifyMode; +import org.jclouds.chef.util.RunListBuilder; import org.jclouds.crypto.PemsTest; import org.jclouds.domain.JsonBall; -import org.jclouds.json.Json; import org.jclouds.json.config.GsonModule; import org.jclouds.rest.annotations.ApiVersion; import org.jclouds.scriptbuilder.domain.OsFamily; @@ -61,7 +62,6 @@ @Test(groups = "unit", testName = "GroupToBootScriptTest") public class GroupToBootScriptTest { - private Json json; private Statement installChefGems; private Statement installChefOmnibus; private Optional validatorName; @@ -88,7 +88,6 @@ protected void configure() { } }, new ChefParserModule(), new GsonModule(), new ChefBootstrapModule()); - json = injectorGems.getInstance(Json.class); installChefGems = injectorGems.getInstance(Key.get(Statement.class, InstallChef.class)); installChefOmnibus = injectorOmnibus.getInstance(Key.get(Statement.class, InstallChef.class)); validatorName = Optional. of("chef-validator"); @@ -97,25 +96,25 @@ protected void configure() { @Test(expectedExceptions = IllegalStateException.class) public void testMustHaveValidatorName() { Optional validatorCredential = Optional.of(createMock(PrivateKey.class)); - GroupToBootScript fn = new GroupToBootScript(Suppliers.ofInstance(URI.create("http://localhost:4000")), json, - CacheLoader.from(Functions.forMap(ImmutableMap. of())), installChefGems, - Optional. absent(), validatorCredential); + GroupToBootScript fn = new GroupToBootScript(Suppliers.ofInstance(URI.create("http://localhost:4000")), + CacheLoader.from(Functions.forMap(ImmutableMap.of("foo", BootstrapConfig.builder().build()))), + installChefOmnibus, Optional. absent(), validatorCredential); fn.apply("foo", null); } @Test(expectedExceptions = IllegalStateException.class) public void testMustHaveValidatorCredential() { - GroupToBootScript fn = new GroupToBootScript(Suppliers.ofInstance(URI.create("http://localhost:4000")), json, - CacheLoader.from(Functions.forMap(ImmutableMap. of())), installChefGems, - validatorName, Optional. absent()); + GroupToBootScript fn = new GroupToBootScript(Suppliers.ofInstance(URI.create("http://localhost:4000")), + CacheLoader.from(Functions.forMap(ImmutableMap.of("foo", BootstrapConfig.builder().build()))), + installChefOmnibus, validatorName, Optional. absent()); fn.apply("foo", null); } @Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = "Key 'foo' not present in map") public void testMustHaveRunScriptsName() { Optional validatorCredential = Optional.of(createMock(PrivateKey.class)); - GroupToBootScript fn = new GroupToBootScript(Suppliers.ofInstance(URI.create("http://localhost:4000")), json, - CacheLoader.from(Functions.forMap(ImmutableMap. of())), installChefGems, + GroupToBootScript fn = new GroupToBootScript(Suppliers.ofInstance(URI.create("http://localhost:4000")), + CacheLoader.from(Functions.forMap(ImmutableMap. of())), installChefOmnibus, validatorName, validatorCredential); fn.apply("foo", null); } @@ -123,18 +122,18 @@ public void testMustHaveRunScriptsName() { @Test(expectedExceptions = NullPointerException.class, expectedExceptionsMessageRegExp = "null value in entry: foo=null") public void testMustHaveRunScriptsValue() { Optional validatorCredential = Optional.of(createMock(PrivateKey.class)); - GroupToBootScript fn = new GroupToBootScript(Suppliers.ofInstance(URI.create("http://localhost:4000")), json, - CacheLoader.from(Functions.forMap(ImmutableMap. of("foo", (DatabagItem) null))), - installChefGems, validatorName, validatorCredential); + GroupToBootScript fn = new GroupToBootScript(Suppliers.ofInstance(URI.create("http://localhost:4000")), + CacheLoader.from(Functions.forMap(ImmutableMap.of("foo", (BootstrapConfig) null))), installChefOmnibus, + validatorName, validatorCredential); fn.apply("foo", null); } public void testOneRecipe() throws IOException { Optional validatorCredential = Optional.of(createMock(PrivateKey.class)); - GroupToBootScript fn = new GroupToBootScript(Suppliers.ofInstance(URI.create("http://localhost:4000")), json, - CacheLoader.from(Functions.forMap(ImmutableMap. of("foo", new JsonBall( - "{\"tomcat6\":{\"ssl_port\":8433},\"run_list\":[\"recipe[apache2]\",\"role[webserver]\"]}")))), - installChefGems, validatorName, validatorCredential); + BootstrapConfig config = BootstrapConfig.builder().attributes(new JsonBall("{\"tomcat6\":{\"ssl_port\":8433}}")) + .runList(new RunListBuilder().addRecipe("apache2").addRole("webserver").build()).build(); + + GroupToBootScript fn = groupToBootScriptFor(config, validatorCredential, false); PrivateKey validatorKey = validatorCredential.get(); expect(validatorKey.getEncoded()).andReturn(PemsTest.PRIVATE_KEY.getBytes()); @@ -142,26 +141,19 @@ public void testOneRecipe() throws IOException { assertEquals( fn.apply("foo", null).render(OsFamily.UNIX), - exitInsteadOfReturn( - OsFamily.UNIX, - Resources.toString(Resources.getResource("test_install_ruby." + ShellToken.SH.to(OsFamily.UNIX)), - Charsets.UTF_8) - + Resources.toString( - Resources.getResource("test_install_rubygems." + ShellToken.SH.to(OsFamily.UNIX)), - Charsets.UTF_8) - + "gem install chef --no-rdoc --no-ri\n" - + Resources.toString(Resources.getResource("bootstrap.sh"), Charsets.UTF_8))); + exitInsteadOfReturn(OsFamily.UNIX, readContent("test_install_ruby.sh") + + readContent("test_install_rubygems.sh")) + + "gem install chef --no-rdoc --no-ri\n" + readContent("bootstrap.sh")); verify(validatorKey); } public void testOneRecipeAndEnvironment() throws IOException { Optional validatorCredential = Optional.of(createMock(PrivateKey.class)); - GroupToBootScript fn = new GroupToBootScript(Suppliers.ofInstance(URI.create("http://localhost:4000")), json, - CacheLoader.from(Functions.forMap(ImmutableMap. of("foo", new JsonBall( - "{\"tomcat6\":{\"ssl_port\":8433},\"environment\":\"env\"," - + "\"run_list\":[\"recipe[apache2]\",\"role[webserver]\"]}")))), installChefGems, - validatorName, validatorCredential); + BootstrapConfig config = BootstrapConfig.builder().attributes(new JsonBall("{\"tomcat6\":{\"ssl_port\":8433}}")) + .environment("env").runList(new RunListBuilder().addRecipe("apache2").addRole("webserver").build()).build(); + + GroupToBootScript fn = groupToBootScriptFor(config, validatorCredential, false); PrivateKey validatorKey = validatorCredential.get(); expect(validatorKey.getEncoded()).andReturn(PemsTest.PRIVATE_KEY.getBytes()); @@ -169,82 +161,99 @@ public void testOneRecipeAndEnvironment() throws IOException { assertEquals( fn.apply("foo", null).render(OsFamily.UNIX), - exitInsteadOfReturn( - OsFamily.UNIX, - Resources.toString(Resources.getResource("test_install_ruby." + ShellToken.SH.to(OsFamily.UNIX)), - Charsets.UTF_8) - + Resources.toString( - Resources.getResource("test_install_rubygems." + ShellToken.SH.to(OsFamily.UNIX)), - Charsets.UTF_8) - + "gem install chef --no-rdoc --no-ri\n" - + Resources.toString(Resources.getResource("bootstrap-env.sh"), Charsets.UTF_8))); + exitInsteadOfReturn(OsFamily.UNIX, readContent("test_install_ruby.sh") + + readContent("test_install_rubygems.sh") + "gem install chef --no-rdoc --no-ri\n" + + readContent("bootstrap-env.sh"))); verify(validatorKey); } public void testOneRecipeOmnibus() throws IOException { Optional validatorCredential = Optional.of(createMock(PrivateKey.class)); - GroupToBootScript fn = new GroupToBootScript(Suppliers.ofInstance(URI.create("http://localhost:4000")), json, - CacheLoader.from(Functions.forMap(ImmutableMap. of("foo", new JsonBall( - "{\"tomcat6\":{\"ssl_port\":8433},\"run_list\":[\"recipe[apache2]\",\"role[webserver]\"]}")))), - installChefOmnibus, validatorName, validatorCredential); + BootstrapConfig config = BootstrapConfig.builder().attributes(new JsonBall("{\"tomcat6\":{\"ssl_port\":8433}}")) + .runList(new RunListBuilder().addRecipe("apache2").addRole("webserver").build()).build(); + + GroupToBootScript fn = groupToBootScriptFor(config, validatorCredential, true); PrivateKey validatorKey = validatorCredential.get(); expect(validatorKey.getEncoded()).andReturn(PemsTest.PRIVATE_KEY.getBytes()); replay(validatorKey); - assertEquals( - fn.apply("foo", null).render(OsFamily.UNIX), + assertEquals(fn.apply("foo", null).render(OsFamily.UNIX), "setupPublicCurl || exit 1\ncurl -q -s -S -L --connect-timeout 10 --max-time 600 --retry 20 " - + "-X GET https://www.opscode.com/chef/install.sh |(bash)\n" - + Resources.toString(Resources.getResource("bootstrap.sh"), Charsets.UTF_8)); + + "-X GET https://www.opscode.com/chef/install.sh |(bash)\n" + readContent("bootstrap.sh")); verify(validatorKey); } public void testOneRecipeAndEnvironmentOmnibus() throws IOException { Optional validatorCredential = Optional.of(createMock(PrivateKey.class)); - GroupToBootScript fn = new GroupToBootScript(Suppliers.ofInstance(URI.create("http://localhost:4000")), json, - CacheLoader.from(Functions.forMap(ImmutableMap. of("foo", new JsonBall( - "{\"tomcat6\":{\"ssl_port\":8433},\"environment\":\"env\"," - + "\"run_list\":[\"recipe[apache2]\",\"role[webserver]\"]}")))), installChefOmnibus, - validatorName, validatorCredential); + BootstrapConfig config = BootstrapConfig.builder().attributes(new JsonBall("{\"tomcat6\":{\"ssl_port\":8433}}")) + .environment("env").runList(new RunListBuilder().addRecipe("apache2").addRole("webserver").build()).build(); + + GroupToBootScript fn = groupToBootScriptFor(config, validatorCredential, true); PrivateKey validatorKey = validatorCredential.get(); expect(validatorKey.getEncoded()).andReturn(PemsTest.PRIVATE_KEY.getBytes()); replay(validatorKey); - assertEquals( - fn.apply("foo", null).render(OsFamily.UNIX), + assertEquals(fn.apply("foo", null).render(OsFamily.UNIX), "setupPublicCurl || exit 1\ncurl -q -s -S -L --connect-timeout 10 --max-time 600 --retry 20 " - + "-X GET https://www.opscode.com/chef/install.sh |(bash)\n" - + Resources.toString(Resources.getResource("bootstrap-env.sh"), Charsets.UTF_8)); + + "-X GET https://www.opscode.com/chef/install.sh |(bash)\n" + readContent("bootstrap-env.sh")); verify(validatorKey); } - private static String exitInsteadOfReturn(OsFamily family, String input) { - return input.replaceAll(ShellToken.RETURN.to(family), ShellToken.EXIT.to(family)); + public void testCustomNodeName() throws IOException { + Optional validatorCredential = Optional.of(createMock(PrivateKey.class)); + BootstrapConfig config = BootstrapConfig.builder().attributes(new JsonBall("{\"tomcat6\":{\"ssl_port\":8433}}")) + .environment("env").runList(new RunListBuilder().addRecipe("apache2").addRole("webserver").build()).build(); + + GroupToBootScript fn = groupToBootScriptFor(config, validatorCredential, true); + + PrivateKey validatorKey = validatorCredential.get(); + expect(validatorKey.getEncoded()).andReturn(PemsTest.PRIVATE_KEY.getBytes()); + replay(validatorKey); + + assertEquals(fn.apply("foo", "bar").render(OsFamily.UNIX), + "setupPublicCurl || exit 1\ncurl -q -s -S -L --connect-timeout 10 --max-time 600 --retry 20 " + + "-X GET https://www.opscode.com/chef/install.sh |(bash)\n" + readContent("bootstrap-node-env.sh")); + + verify(validatorKey); } - public void testCustomNodeName() throws IOException { - Optional validatorCredential = Optional.of(createMock(PrivateKey.class)); - GroupToBootScript fn = new GroupToBootScript(Suppliers.ofInstance(URI.create("http://localhost:4000")), json, - CacheLoader.from(Functions.forMap(ImmutableMap. of("foo", new JsonBall( - "{\"tomcat6\":{\"ssl_port\":8433},\"environment\":\"env\"," - + "\"run_list\":[\"recipe[apache2]\",\"role[webserver]\"]}")))), installChefOmnibus, - validatorName, validatorCredential); + public void testCustomSecurityOptions() throws IOException { + Optional validatorCredential = Optional.of(createMock(PrivateKey.class)); + BootstrapConfig config = BootstrapConfig.builder().attributes(new JsonBall("{\"tomcat6\":{\"ssl_port\":8433}}")) + .runList(new RunListBuilder().addRecipe("apache2").addRole("webserver").build()) + .sslCAFile(readContent("chef.crt")).sslCAPath("/etc/chef").sslVerifyMode(SSLVerifyMode.PEER) + .verifyApiCert(true).build(); - PrivateKey validatorKey = validatorCredential.get(); - expect(validatorKey.getEncoded()).andReturn(PemsTest.PRIVATE_KEY.getBytes()); - replay(validatorKey); + GroupToBootScript fn = groupToBootScriptFor(config, validatorCredential, true); - assertEquals( - fn.apply("foo", "bar").render(OsFamily.UNIX), + PrivateKey validatorKey = validatorCredential.get(); + expect(validatorKey.getEncoded()).andReturn(PemsTest.PRIVATE_KEY.getBytes()); + replay(validatorKey); + + assertEquals(fn.apply("foo", null).render(OsFamily.UNIX), "setupPublicCurl || exit 1\ncurl -q -s -S -L --connect-timeout 10 --max-time 600 --retry 20 " - + "-X GET https://www.opscode.com/chef/install.sh |(bash)\n" - + Resources.toString(Resources.getResource("bootstrap-node-env.sh"), Charsets.UTF_8)); + + "-X GET https://www.opscode.com/chef/install.sh |(bash)\n" + readContent("bootstrap-ssl.sh")); - verify(validatorKey); - } + verify(validatorKey); + } + + private static String exitInsteadOfReturn(OsFamily family, String input) { + return input.replaceAll(ShellToken.RETURN.to(family), ShellToken.EXIT.to(family)); + } + + private GroupToBootScript groupToBootScriptFor(BootstrapConfig config, Optional validatorCredential, + boolean useOmnibus) { + return new GroupToBootScript(Suppliers.ofInstance(URI.create("http://localhost:4000")), + CacheLoader.from(Functions.forMap(ImmutableMap.of("foo", config))), useOmnibus ? installChefOmnibus + : installChefGems, validatorName, validatorCredential); + } + + private static String readContent(String resource) throws IOException { + return Resources.toString(Resources.getResource(resource), Charsets.UTF_8); + } } diff --git a/apis/chef/src/test/java/org/jclouds/chef/functions/RunListForGroupTest.java b/apis/chef/src/test/java/org/jclouds/chef/functions/RunListForGroupTest.java deleted file mode 100644 index 28b8833704c..00000000000 --- a/apis/chef/src/test/java/org/jclouds/chef/functions/RunListForGroupTest.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * 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.chef.functions; - -import static org.easymock.EasyMock.createMock; -import static org.easymock.EasyMock.expect; -import static org.easymock.EasyMock.replay; -import static org.easymock.EasyMock.verify; -import static org.testng.Assert.assertEquals; - -import java.io.IOException; - -import org.jclouds.chef.ChefApi; -import org.jclouds.chef.ChefApiMetadata; -import org.jclouds.chef.config.ChefParserModule; -import org.jclouds.chef.domain.Client; -import org.jclouds.chef.domain.DatabagItem; -import org.jclouds.json.Json; -import org.jclouds.json.config.GsonModule; -import org.jclouds.rest.annotations.Api; -import org.jclouds.rest.annotations.ApiVersion; -import org.testng.annotations.Test; - -import com.google.common.collect.ImmutableList; -import com.google.inject.AbstractModule; -import com.google.inject.Guice; -import com.google.inject.Injector; - -@Test(groups = "unit", testName = "RunListForGroupTest") -public class RunListForGroupTest { - private Injector injector = Guice.createInjector(new AbstractModule() { - @Override - protected void configure() { - bind(String.class).annotatedWith(ApiVersion.class).toInstance(ChefApiMetadata.DEFAULT_API_VERSION); - } - }, new ChefParserModule(), new GsonModule()); - - private Json json = injector.getInstance(Json.class); - - @Test(expectedExceptions = IllegalStateException.class) - public void testWhenNoDatabagItem() throws IOException { - ChefApi chefApi = createMock(ChefApi.class); - Client client = createMock(Client.class); - - RunListForGroup fn = new RunListForGroup(new BootstrapConfigForGroup("jclouds", chefApi), json); - - expect(chefApi.getDatabagItem("jclouds", "foo")).andReturn(null); - - replay(client); - replay(chefApi); - - fn.apply("foo"); - - verify(client); - verify(chefApi); - } - - @Test - public void testReadRunList() throws IOException { - ChefApi chefApi = createMock(ChefApi.class); - Api api = createMock(Api.class); - - RunListForGroup fn = new RunListForGroup(new BootstrapConfigForGroup("jclouds", chefApi), json); - DatabagItem config = new DatabagItem("foo", - "{\"tomcat6\":{\"ssl_port\":8433},\"run_list\":[\"recipe[apache2]\",\"role[webserver]\"]}"); - - expect(chefApi.getDatabagItem("jclouds", "foo")).andReturn(config); - - replay(api); - replay(chefApi); - - assertEquals(fn.apply("foo"), ImmutableList.of("recipe[apache2]", "role[webserver]")); - - verify(api); - verify(chefApi); - } - -} diff --git a/apis/chef/src/test/java/org/jclouds/chef/internal/BaseChefServiceTest.java b/apis/chef/src/test/java/org/jclouds/chef/internal/BaseChefServiceTest.java index d998534964e..e6de8e3750d 100644 --- a/apis/chef/src/test/java/org/jclouds/chef/internal/BaseChefServiceTest.java +++ b/apis/chef/src/test/java/org/jclouds/chef/internal/BaseChefServiceTest.java @@ -53,11 +53,6 @@ public void setup() { chefService = injector.getInstance(BaseChefService.class); } - @Test(expectedExceptions = NullPointerException.class, expectedExceptionsMessageRegExp = "bootstrapConfig must not be null") - public void testBuildBootstrapConfigurationWithNullConfig() { - chefService.buildBootstrapConfiguration(null); - } - public void testBuildBootstrapConfigurationWithEmptyRunlist() { BootstrapConfig bootstrapConfig = BootstrapConfig.builder().runList(ImmutableList. of()).build(); String config = chefService.buildBootstrapConfiguration(bootstrapConfig); @@ -76,7 +71,7 @@ public void testBuildBootstrapConfigurationWithRunlistAndEmptyAttributes() { BootstrapConfig bootstrapConfig = BootstrapConfig.builder().runList(runlist).attributes(new JsonBall("{}")) .build(); String config = chefService.buildBootstrapConfiguration(bootstrapConfig); - assertEquals(config, "{\"run_list\":[\"recipe[apache2]\",\"role[webserver]\"]}"); + assertEquals(config, "{\"run_list\":[\"recipe[apache2]\",\"role[webserver]\"],\"attributes\":{}}"); } public void testBuildBootstrapConfigurationWithRunlistAndAttributes() { @@ -84,7 +79,8 @@ public void testBuildBootstrapConfigurationWithRunlistAndAttributes() { BootstrapConfig bootstrapConfig = BootstrapConfig.builder().runList(runlist) .attributes(new JsonBall("{\"tomcat6\":{\"ssl_port\":8433}}")).build(); String config = chefService.buildBootstrapConfiguration(bootstrapConfig); - assertEquals(config, "{\"run_list\":[\"recipe[apache2]\",\"role[webserver]\"],\"tomcat6\":{\"ssl_port\":8433}}"); + assertEquals(config, + "{\"run_list\":[\"recipe[apache2]\",\"role[webserver]\"],\"attributes\":{\"tomcat6\":{\"ssl_port\":8433}}}"); } public void testBuildBootstrapConfigurationWithRunlistAndAttributesAndEnvironment() { @@ -92,8 +88,8 @@ public void testBuildBootstrapConfigurationWithRunlistAndAttributesAndEnvironmen BootstrapConfig bootstrapConfig = BootstrapConfig.builder().runList(runlist) .attributes(new JsonBall("{\"tomcat6\":{\"ssl_port\":8433}}")).environment("env").build(); String config = chefService.buildBootstrapConfiguration(bootstrapConfig); - assertEquals(config, - "{\"run_list\":[\"recipe[apache2]\",\"role[webserver]\"],\"environment\":\"env\",\"tomcat6\":{\"ssl_port\":8433}}"); + assertEquals(config, "{\"run_list\":[\"recipe[apache2]\",\"role[webserver]\"],\"environment\":\"env\"," + + "\"attributes\":{\"tomcat6\":{\"ssl_port\":8433}}}"); } } diff --git a/apis/chef/src/test/resources/bootstrap-env.sh b/apis/chef/src/test/resources/bootstrap-env.sh index 315e24889bd..0bd241d9086 100755 --- a/apis/chef/src/test/resources/bootstrap-env.sh +++ b/apis/chef/src/test/resources/bootstrap-env.sh @@ -1,5 +1,5 @@ mkdir -p /etc/chef -cat >> /etc/chef/client.rb <<-'END_OF_JCLOUDS_FILE' +cat > /etc/chef/client.rb <<-'END_OF_JCLOUDS_FILE' require 'rubygems' require 'ohai' o = Ohai::System.new @@ -9,8 +9,9 @@ cat >> /etc/chef/client.rb <<-'END_OF_JCLOUDS_FILE' log_location STDOUT validation_client_name "chef-validator" chef_server_url "http://localhost:4000" + environment "env" END_OF_JCLOUDS_FILE -cat >> /etc/chef/validation.pem <<-'END_OF_JCLOUDS_FILE' +cat > /etc/chef/validation.pem <<-'END_OF_JCLOUDS_FILE' -----BEGIN PRIVATE KEY----- LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcFFJQkFBS0NBUUVB eWIyWkpKcUdtMEtLUis4bmZRSk5zU2QrRjl0WE5NVjdDZk9jVzZqc3FzOEVaZ2lW @@ -50,7 +51,7 @@ cat >> /etc/chef/validation.pem <<-'END_OF_JCLOUDS_FILE' -----END PRIVATE KEY----- END_OF_JCLOUDS_FILE -cat >> /etc/chef/first-boot.json <<-'END_OF_JCLOUDS_FILE' - {"tomcat6":{"ssl_port":8433},"environment":"env","run_list":["recipe[apache2]","role[webserver]"]} +cat > /etc/chef/first-boot.json <<-'END_OF_JCLOUDS_FILE' + {"tomcat6":{"ssl_port":8433},"run_list":["recipe[apache2]","role[webserver]"]} END_OF_JCLOUDS_FILE -chef-client -j /etc/chef/first-boot.json -E "env" +chef-client -j /etc/chef/first-boot.json diff --git a/apis/chef/src/test/resources/bootstrap-node-env.sh b/apis/chef/src/test/resources/bootstrap-node-env.sh index 84713d65642..bf73cc6ca54 100755 --- a/apis/chef/src/test/resources/bootstrap-node-env.sh +++ b/apis/chef/src/test/resources/bootstrap-node-env.sh @@ -1,5 +1,5 @@ mkdir -p /etc/chef -cat >> /etc/chef/client.rb <<-'END_OF_JCLOUDS_FILE' +cat > /etc/chef/client.rb <<-'END_OF_JCLOUDS_FILE' require 'rubygems' require 'ohai' o = Ohai::System.new @@ -9,8 +9,9 @@ cat >> /etc/chef/client.rb <<-'END_OF_JCLOUDS_FILE' log_location STDOUT validation_client_name "chef-validator" chef_server_url "http://localhost:4000" + environment "env" END_OF_JCLOUDS_FILE -cat >> /etc/chef/validation.pem <<-'END_OF_JCLOUDS_FILE' +cat > /etc/chef/validation.pem <<-'END_OF_JCLOUDS_FILE' -----BEGIN PRIVATE KEY----- LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcFFJQkFBS0NBUUVB eWIyWkpKcUdtMEtLUis4bmZRSk5zU2QrRjl0WE5NVjdDZk9jVzZqc3FzOEVaZ2lW @@ -50,7 +51,7 @@ cat >> /etc/chef/validation.pem <<-'END_OF_JCLOUDS_FILE' -----END PRIVATE KEY----- END_OF_JCLOUDS_FILE -cat >> /etc/chef/first-boot.json <<-'END_OF_JCLOUDS_FILE' - {"tomcat6":{"ssl_port":8433},"environment":"env","run_list":["recipe[apache2]","role[webserver]"]} +cat > /etc/chef/first-boot.json <<-'END_OF_JCLOUDS_FILE' + {"tomcat6":{"ssl_port":8433},"run_list":["recipe[apache2]","role[webserver]"]} END_OF_JCLOUDS_FILE -chef-client -j /etc/chef/first-boot.json -E "env" +chef-client -j /etc/chef/first-boot.json diff --git a/apis/chef/src/test/resources/bootstrap-ssl.sh b/apis/chef/src/test/resources/bootstrap-ssl.sh new file mode 100755 index 00000000000..0299f8be936 --- /dev/null +++ b/apis/chef/src/test/resources/bootstrap-ssl.sh @@ -0,0 +1,93 @@ +mkdir -p /etc/chef +cat > /etc/chef/chef-server.crt <<-'END_OF_JCLOUDS_FILE' + -----BEGIN CERTIFICATE----- + MIIFDTCCA/WgAwIBAgIQBZ8R1sZP2Lbc8x554UUQ2DANBgkqhkiG9w0BAQsFADBN + MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMScwJQYDVQQDEx5E + aWdpQ2VydCBTSEEyIFNlY3VyZSBTZXJ2ZXIgQ0EwHhcNMTQxMTEwMDAwMDAwWhcN + MTcxMTE0MTIwMDAwWjBlMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv + bjEQMA4GA1UEBxMHU2VhdHRsZTEbMBkGA1UEChMSQ2hlZiBTb2Z0d2FyZSwgSW5j + MRIwEAYDVQQDDAkqLmNoZWYuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK + AoIBAQC3xCIczkV10O5jTDpbd4YlPLC6kfnVoOkno2N/OOlcLQu3ulj/Lj1j4r6e + 2XthJLcFgTO+y+1/IKnnpLKDfkx1YngWEBXEBP+MrrpDUKKs053s45/bI9QBPISA + tXgnYxMH9Glo6FWWd13TUq++OKGw1p1wazH64XK4MAf5y/lkmWXIWumNuO35ZqtB + ME3wJISwVHzHB2CQjlDklt+Mb0APEiIFIZflgu9JNBYzLdvUtxiz15FUZQI7SsYL + TfXOD1KBNMWqN8snG2e5gRAzB2D161DFvAZt8OiYUe+3QurNlTYVzeHv1ok6UqgM + ZcLzg8m801rRip0D7FCGvMCU/ktdAgMBAAGjggHPMIIByzAfBgNVHSMEGDAWgBQP + gGEcgjFh1S8o541GOLQs4cbZ4jAdBgNVHQ4EFgQUwldjw4Pb4HV+wxGZ7MSSRh+d + pm4wHQYDVR0RBBYwFIIJKi5jaGVmLmlvggdjaGVmLmlvMA4GA1UdDwEB/wQEAwIF + oDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwawYDVR0fBGQwYjAvoC2g + K4YpaHR0cDovL2NybDMuZGlnaWNlcnQuY29tL3NzY2Etc2hhMi1nMy5jcmwwL6At + oCuGKWh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9zc2NhLXNoYTItZzMuY3JsMEIG + A1UdIAQ7MDkwNwYJYIZIAYb9bAEBMCowKAYIKwYBBQUHAgEWHGh0dHBzOi8vd3d3 + LmRpZ2ljZXJ0LmNvbS9DUFMwfAYIKwYBBQUHAQEEcDBuMCQGCCsGAQUFBzABhhho + dHRwOi8vb2NzcC5kaWdpY2VydC5jb20wRgYIKwYBBQUHMAKGOmh0dHA6Ly9jYWNl + cnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFNIQTJTZWN1cmVTZXJ2ZXJDQS5jcnQw + DAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAQEAvcTWenNuvvrhX2omm8LQ + zWOuu8jqpoflACwD4lOSZ4TgOe4pQGCjXq8aRBD5k+goqQrPVf9lHnelUHFQac0Q + 5WT4YUmisUbF0S4uY5OGQymM52MvUWG4ODL4gaWhFvN+HAXrDPP/9iitsjV0QOnl + CDq7Q4/XYRYW3opu5nLLbfW6v4QvF5yzZagEACGs7Vt32p6l391UcU8f6wiB3uMD + eioCvjpv/+2YOUNlDPCM3uBubjUhHOwO817wBxXkzdk1OSRe4jzcw/uX6wL7birt + fbaSkpilvVX529pSzB2Lvi9xWOoGMM578dpQ0h3PwhmmvKhhCWP+pI05k3oSkYCP + ng== + -----END CERTIFICATE----- + +END_OF_JCLOUDS_FILE +cat > /etc/chef/client.rb <<-'END_OF_JCLOUDS_FILE' + require 'rubygems' + require 'ohai' + o = Ohai::System.new + o.all_plugins + node_name "foo-" + o[:ipaddress] + log_level :info + log_location STDOUT + validation_client_name "chef-validator" + chef_server_url "http://localhost:4000" + ssl_ca_file "/etc/chef/chef-server.crt" + ssl_ca_path "/etc/chef" + ssl_verify_mode :verify_peer + verify_api_cert true +END_OF_JCLOUDS_FILE +cat > /etc/chef/validation.pem <<-'END_OF_JCLOUDS_FILE' + -----BEGIN PRIVATE KEY----- + LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcFFJQkFBS0NBUUVB + eWIyWkpKcUdtMEtLUis4bmZRSk5zU2QrRjl0WE5NVjdDZk9jVzZqc3FzOEVaZ2lW + ClIwOWhEMUlZT2o0WXFNMHFKT05sZ3lnNHhSV2V3ZFNHN1FUUGoxbEpwVkFpZGE5 + c1h5MitrenlhZ1pBMUFtME8KWmNicWI1aG9lSURnY1grZURhNzlzMHUwRG9tamNm + TzlFS2h2SExCeit6TSszUXFQUmtQVjhuWVRiZnMrSGpWegp6T1U2RDFCMFhSMytJ + UFpabDJBbldzMmQwcWhuU3RIY0RVdm5SVlEwUDQ4Mll3TjlWZ2NlT1p0cFB6MERD + S0VKCjVUeDVTVHViOGswL3p0L1ZBTUhRYWZMU3VRTUxkMnM0Wkx1T1pwdE4vL3VB + c1RteGlyZXFkMzd6KzhaVGRCYkoKOExFcEoraUNYdVNmbTVhVWg3aXc2b3h2VG9Z + MkFMNTMraksyVVFJREFRQUJBb0lCQVFEQTg4QjNpL3hXbjB2WApCVnhGYW1DWW9l + Y3VOakd3WFhrU3laZXc2MTZBK0VPQ3U0N2JoNGFUdXJkRmJZTDBZRmFBdGFXdnps + YU4yZUhnCkRiK0hEdVRlZkUyOStXa2NHazZTc2hQbWl6NVQwWE9DQUlDV3c2d1NW + RGtIbUd3UzRqWnZiQUZtN1c4bndHazkKWWh4Z3hGaVJuZ3N3SlpGb3BPTG9GNVdY + czJ0ZDhndUlZTnNsTXBvN3R1NTBpRm5CSHdLTzJac1BBazh0OW5uUwp4bERhdkty + dXltRW1xSENyMytkdGlvNWVhZW5KY3AzZmpvWEJRT0tVazNpcElJMjlYUkI4TnFl + Q1ZWLzdLeHdxCmNrcU9CRWJSd0JjbGNreUliRCtSaUFnS3ZPZWxPUmpFaUU5UjQy + dnVxdnhSQTZrOWtkOW83dXRsWDBBVXRwRW4KM2daYzZMZXBBb0dCQVA5YWVsNVk3 + NStzSzJKSlVOT09oTzhhZTQ1Y2RzaWxwMnlJMFgrVUJhU3VRczIrZHlQcAprcEVI + QXhkNHBtbVN2bi84YzlUbEVaaHIrcVliQUJYVlBsRG5jeHBJdXcyQWpiazdzL1M0 + WGFTS3NScXBYTDU3CnpqL1FPcUxrUms4K09WVjlxNmxNZVFOcUx0RWoxdTZKUHZp + WDcwUm8rRlF0UnR0Tk9ZYmZkUC9mQW9HQkFNcEEKWGpSNXdvVjVzVWIrUkVnOXZF + dVlvOFJTeU9hcnhxS0ZDSVhWVU5zTE94KzIyK0FLNCtDUXBidWVXTjdqb3RybApZ + RDZ1VDZzdldpM0FBQzdraVkwVUkvZmpWUFJDVWk4dFZvUVVFMFRhVTVWTElUYVlP + QitXL2JCYURFNE05NTYwCjFOdURXTzkwYmFBNWRmVTQ0aXV6dmEwMnJHSlhLOStu + UzNvOG5rL1BBb0dCQUxPTDZkam5EZTRtd0FhRzZKY28KY2Q0eHI4amt5UHpDUlp1 + eUJDU0Jid3BoSVVYTGM3aERwclBreTA2NG5jSkQxVURtd0lka1hkL2ZwTWtnMlFt + QQovQ1VrNkxFRmpNaXNxSG9qT2FDTDlnUVpKUGhMTjVRVU4yeDFQSldHanMxdlFo + OFRreDBpVVVDT2E4YlFQWE5SCiszNE9Uc1c2VFVuYTRDU1pBeWNMZmhmZkFvR0JB + SWdnVnNlZkJDdnVRa0YwTmVVaG1EQ1JaZmhuZDh5NTVSSFIKMUhDdnFLSWxwdity + aGNYL3pteUJMdXRlb3BZeVJKUnNPaUUyRlcwMGk4K3JJUFJ1NFozUTVueWJ4N3cz + UHpWOQpvSE41UjViYUU5T3lJNEtwWld6dHBZWWl0WkY2N05jbkF2VlVMSEhPdlZK + UUduS1lmTEhKWW1ySkY3R0Exb2pNCkF1TWRGYmpGQW9HQVB4VWh4d0Z5OGdhcUJh + aEtVRVpuNEY4MUhGUDVpaEdoa1Q0UUw2QUZQTzJlK0poSUdqdVIKMjcrODVoY0Zx + UStISFZ0RnNtODFiL2ErUjdQNFV1Q1JnYzhlQ2p4UU1vSjFYbDRuN1ZialBiSE1u + SU4wUnl2ZApPNFpwV0RXWW5DTzAyMUpUT1VVT0o0Si95MDQxNkJ2a3cwejU5eTdz + Tlg3d0RCQkhIYksvWENjPQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo= + -----END PRIVATE KEY----- + +END_OF_JCLOUDS_FILE +cat > /etc/chef/first-boot.json <<-'END_OF_JCLOUDS_FILE' + {"tomcat6":{"ssl_port":8433},"run_list":["recipe[apache2]","role[webserver]"]} +END_OF_JCLOUDS_FILE +chef-client -j /etc/chef/first-boot.json diff --git a/apis/chef/src/test/resources/bootstrap.sh b/apis/chef/src/test/resources/bootstrap.sh index 0eb402e0628..c5381f442c8 100755 --- a/apis/chef/src/test/resources/bootstrap.sh +++ b/apis/chef/src/test/resources/bootstrap.sh @@ -1,5 +1,5 @@ mkdir -p /etc/chef -cat >> /etc/chef/client.rb <<-'END_OF_JCLOUDS_FILE' +cat > /etc/chef/client.rb <<-'END_OF_JCLOUDS_FILE' require 'rubygems' require 'ohai' o = Ohai::System.new @@ -10,7 +10,7 @@ cat >> /etc/chef/client.rb <<-'END_OF_JCLOUDS_FILE' validation_client_name "chef-validator" chef_server_url "http://localhost:4000" END_OF_JCLOUDS_FILE -cat >> /etc/chef/validation.pem <<-'END_OF_JCLOUDS_FILE' +cat > /etc/chef/validation.pem <<-'END_OF_JCLOUDS_FILE' -----BEGIN PRIVATE KEY----- LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcFFJQkFBS0NBUUVB eWIyWkpKcUdtMEtLUis4bmZRSk5zU2QrRjl0WE5NVjdDZk9jVzZqc3FzOEVaZ2lW @@ -50,7 +50,7 @@ cat >> /etc/chef/validation.pem <<-'END_OF_JCLOUDS_FILE' -----END PRIVATE KEY----- END_OF_JCLOUDS_FILE -cat >> /etc/chef/first-boot.json <<-'END_OF_JCLOUDS_FILE' +cat > /etc/chef/first-boot.json <<-'END_OF_JCLOUDS_FILE' {"tomcat6":{"ssl_port":8433},"run_list":["recipe[apache2]","role[webserver]"]} END_OF_JCLOUDS_FILE chef-client -j /etc/chef/first-boot.json diff --git a/apis/chef/src/test/resources/chef.crt b/apis/chef/src/test/resources/chef.crt new file mode 100644 index 00000000000..d2cfb31c9ff --- /dev/null +++ b/apis/chef/src/test/resources/chef.crt @@ -0,0 +1,30 @@ +-----BEGIN CERTIFICATE----- +MIIFDTCCA/WgAwIBAgIQBZ8R1sZP2Lbc8x554UUQ2DANBgkqhkiG9w0BAQsFADBN +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMScwJQYDVQQDEx5E +aWdpQ2VydCBTSEEyIFNlY3VyZSBTZXJ2ZXIgQ0EwHhcNMTQxMTEwMDAwMDAwWhcN +MTcxMTE0MTIwMDAwWjBlMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv +bjEQMA4GA1UEBxMHU2VhdHRsZTEbMBkGA1UEChMSQ2hlZiBTb2Z0d2FyZSwgSW5j +MRIwEAYDVQQDDAkqLmNoZWYuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQC3xCIczkV10O5jTDpbd4YlPLC6kfnVoOkno2N/OOlcLQu3ulj/Lj1j4r6e +2XthJLcFgTO+y+1/IKnnpLKDfkx1YngWEBXEBP+MrrpDUKKs053s45/bI9QBPISA +tXgnYxMH9Glo6FWWd13TUq++OKGw1p1wazH64XK4MAf5y/lkmWXIWumNuO35ZqtB +ME3wJISwVHzHB2CQjlDklt+Mb0APEiIFIZflgu9JNBYzLdvUtxiz15FUZQI7SsYL +TfXOD1KBNMWqN8snG2e5gRAzB2D161DFvAZt8OiYUe+3QurNlTYVzeHv1ok6UqgM +ZcLzg8m801rRip0D7FCGvMCU/ktdAgMBAAGjggHPMIIByzAfBgNVHSMEGDAWgBQP +gGEcgjFh1S8o541GOLQs4cbZ4jAdBgNVHQ4EFgQUwldjw4Pb4HV+wxGZ7MSSRh+d +pm4wHQYDVR0RBBYwFIIJKi5jaGVmLmlvggdjaGVmLmlvMA4GA1UdDwEB/wQEAwIF +oDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwawYDVR0fBGQwYjAvoC2g +K4YpaHR0cDovL2NybDMuZGlnaWNlcnQuY29tL3NzY2Etc2hhMi1nMy5jcmwwL6At +oCuGKWh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9zc2NhLXNoYTItZzMuY3JsMEIG +A1UdIAQ7MDkwNwYJYIZIAYb9bAEBMCowKAYIKwYBBQUHAgEWHGh0dHBzOi8vd3d3 +LmRpZ2ljZXJ0LmNvbS9DUFMwfAYIKwYBBQUHAQEEcDBuMCQGCCsGAQUFBzABhhho +dHRwOi8vb2NzcC5kaWdpY2VydC5jb20wRgYIKwYBBQUHMAKGOmh0dHA6Ly9jYWNl +cnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFNIQTJTZWN1cmVTZXJ2ZXJDQS5jcnQw +DAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAQEAvcTWenNuvvrhX2omm8LQ +zWOuu8jqpoflACwD4lOSZ4TgOe4pQGCjXq8aRBD5k+goqQrPVf9lHnelUHFQac0Q +5WT4YUmisUbF0S4uY5OGQymM52MvUWG4ODL4gaWhFvN+HAXrDPP/9iitsjV0QOnl +CDq7Q4/XYRYW3opu5nLLbfW6v4QvF5yzZagEACGs7Vt32p6l391UcU8f6wiB3uMD +eioCvjpv/+2YOUNlDPCM3uBubjUhHOwO817wBxXkzdk1OSRe4jzcw/uX6wL7birt +fbaSkpilvVX529pSzB2Lvi9xWOoGMM578dpQ0h3PwhmmvKhhCWP+pI05k3oSkYCP +ng== +-----END CERTIFICATE----- diff --git a/project/pom.xml b/project/pom.xml index 0e5eedce4a8..9dfbf7400e5 100644 --- a/project/pom.xml +++ b/project/pom.xml @@ -423,6 +423,7 @@ **/src/test/resources/**/*.txt **/src/test/resources/**/*.gz **/src/test/resources/**/*.xml + **/src/test/resources/**/*.crt **/services/*LoggingModule diff --git a/providers/enterprisechef/src/main/java/org/jclouds/enterprisechef/EnterpriseChefApi.java b/providers/enterprisechef/src/main/java/org/jclouds/enterprisechef/EnterpriseChefApi.java index 8f1a0ed5dad..159cb1bb6ce 100644 --- a/providers/enterprisechef/src/main/java/org/jclouds/enterprisechef/EnterpriseChefApi.java +++ b/providers/enterprisechef/src/main/java/org/jclouds/enterprisechef/EnterpriseChefApi.java @@ -51,74 +51,78 @@ @RequestFilters(SignedHeaderAuth.class) @Consumes(MediaType.APPLICATION_JSON) @Headers(keys = "X-Chef-Version", values = "{" + Constants.PROPERTY_API_VERSION + "}") -public interface EnterpriseChefApi extends ChefApi -{ - /** - * Retrieves an existing user. - * - * @param name The name of the user to get. - * @return The details of the user or null if not found. - */ - @Named("user:get") - @GET - @Path("/users/{name}") - @Fallback(NullOnNotFoundOr404.class) - User getUser(@PathParam("name") String name); +public interface EnterpriseChefApi extends ChefApi { + /** + * Retrieves an existing user. + * + * @param name + * The name of the user to get. + * @return The details of the user or null if not found. + */ + @Named("user:get") + @GET + @Path("/users/{name}") + @Fallback(NullOnNotFoundOr404.class) + User getUser(@PathParam("name") String name); - /** - * List all existing groups. - * - * @return The list of groups. - */ - @Named("group:list") - @GET - @Path("/groups") - @ResponseParser(ParseKeySetFromJson.class) - Set listGroups(); + /** + * List all existing groups. + * + * @return The list of groups. + */ + @Named("group:list") + @GET + @Path("/groups") + @ResponseParser(ParseKeySetFromJson.class) + Set listGroups(); - /** - * Retrieves an existing group. - * - * @param name The name of the group to get. - * @return The details of the group or null if not found. - */ - @Named("group:get") - @GET - @Path("/groups/{name}") - @Fallback(NullOnNotFoundOr404.class) - Group getGroup(@PathParam("name") String name); + /** + * Retrieves an existing group. + * + * @param name + * The name of the group to get. + * @return The details of the group or null if not found. + */ + @Named("group:get") + @GET + @Path("/groups/{name}") + @Fallback(NullOnNotFoundOr404.class) + Group getGroup(@PathParam("name") String name); - /** - * Creates a new group. - * - * @param name The name of the group to create. - */ - @Named("group:create") - @POST - @Path("/groups") - void createGroup(@WrapWith("groupname") String name); + /** + * Creates a new group. + * + * @param name + * The name of the group to create. + */ + @Named("group:create") + @POST + @Path("/groups") + void createGroup(@WrapWith("groupname") String name); - /** - * Updates a group. - *

- * This method can be used to add actors (clients, groups) to the group. - * - * @param group The group with the updated information. - */ - @Named("group:update") - @PUT - @Path("/groups/{name}") - void updateGroup( - @PathParam("name") @ParamParser(GroupName.class) @BinderParam(BindGroupToUpdateRequestJsonPayload.class) Group group); + /** + * Updates a group. + *

+ * This method can be used to add actors (clients, groups) to the group. + * + * @param group + * The group with the updated information. + */ + @Named("group:update") + @PUT + @Path("/groups/{name}") + void updateGroup( + @PathParam("name") @ParamParser(GroupName.class) @BinderParam(BindGroupToUpdateRequestJsonPayload.class) Group group); - /** - * Deletes a group. - * - * @param name The name of the group to delete. - */ - @Named("group:delete") - @DELETE - @Path("/groups/{name}") - void deleteGroup(@PathParam("name") String name); + /** + * Deletes a group. + * + * @param name + * The name of the group to delete. + */ + @Named("group:delete") + @DELETE + @Path("/groups/{name}") + void deleteGroup(@PathParam("name") String name); } diff --git a/providers/enterprisechef/src/main/java/org/jclouds/enterprisechef/EnterpriseChefProviderMetadata.java b/providers/enterprisechef/src/main/java/org/jclouds/enterprisechef/EnterpriseChefProviderMetadata.java index 336ea7ab868..d72cc780b55 100644 --- a/providers/enterprisechef/src/main/java/org/jclouds/enterprisechef/EnterpriseChefProviderMetadata.java +++ b/providers/enterprisechef/src/main/java/org/jclouds/enterprisechef/EnterpriseChefProviderMetadata.java @@ -23,64 +23,55 @@ import org.jclouds.providers.internal.BaseProviderMetadata; /** - * Implementation of @ link org.jclouds.types.ProviderMetadata} for Enterprise Chef + * Implementation of @ link org.jclouds.types.ProviderMetadata} for Enterprise + * Chef */ -public class EnterpriseChefProviderMetadata extends BaseProviderMetadata -{ +public class EnterpriseChefProviderMetadata extends BaseProviderMetadata { - public static Builder builder() - { - return new Builder(); - } + public static Builder builder() { + return new Builder(); + } - @Override - public Builder toBuilder() - { - return builder().fromProviderMetadata(this); - } + @Override + public Builder toBuilder() { + return builder().fromProviderMetadata(this); + } - public EnterpriseChefProviderMetadata() - { - super(builder()); - } + public EnterpriseChefProviderMetadata() { + super(builder()); + } - public EnterpriseChefProviderMetadata(Builder builder) - { - super(builder); - } + public EnterpriseChefProviderMetadata(final Builder builder) { + super(builder); + } - public static Properties defaultProperties() - { - Properties properties = new Properties(); - return properties; - } + public static Properties defaultProperties() { + Properties properties = new Properties(); + return properties; + } - public static class Builder extends BaseProviderMetadata.Builder - { + public static class Builder extends BaseProviderMetadata.Builder { - protected Builder() - { - id("enterprisechef") // - .name("OpsCode Enterprise Chef") // - .endpoint("https://api.opscode.com") // - .homepage(URI.create("https://manage.opscode.com")) // - .console(URI.create("https://manage.opscode.com")) // - .apiMetadata(new EnterpriseChefApiMetadata()) // - .defaultProperties(EnterpriseChefProviderMetadata.defaultProperties()); - } + protected Builder() { + id("enterprisechef") // + .name("OpsCode Enterprise Chef") // + .endpoint("https://api.opscode.com") // + .homepage(URI.create("https://manage.opscode.com")) // + .console(URI.create("https://manage.opscode.com")) // + .apiMetadata(new EnterpriseChefApiMetadata()) // + .defaultProperties(EnterpriseChefProviderMetadata.defaultProperties()); + } - @Override - public EnterpriseChefProviderMetadata build() - { - return new EnterpriseChefProviderMetadata(this); - } + @Override + public EnterpriseChefProviderMetadata build() { + return new EnterpriseChefProviderMetadata(this); + } - @Override - public Builder fromProviderMetadata(ProviderMetadata in) - { - super.fromProviderMetadata(in); - return this; - } + @Override + public Builder fromProviderMetadata(final ProviderMetadata in) { + super.fromProviderMetadata(in); + return this; + } - } + } }