diff --git a/chef/src/main/java/org/jclouds/chef/ChefAsyncClient.java b/chef/src/main/java/org/jclouds/chef/ChefAsyncClient.java index 867b75b619..a934bf4f5d 100644 --- a/chef/src/main/java/org/jclouds/chef/ChefAsyncClient.java +++ b/chef/src/main/java/org/jclouds/chef/ChefAsyncClient.java @@ -23,20 +23,26 @@ */ package org.jclouds.chef; +import java.util.Set; + import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; import javax.ws.rs.GET; +import javax.ws.rs.HEAD; import javax.ws.rs.POST; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.core.MediaType; +import org.jclouds.chef.binders.BindClientnameToJsonPayload; +import org.jclouds.chef.binders.BindGenerateKeyForClientToJsonPayload; import org.jclouds.chef.domain.Organization; import org.jclouds.chef.domain.User; import org.jclouds.chef.filters.SignedHeaderAuth; import org.jclouds.chef.functions.OrganizationName; import org.jclouds.chef.functions.ParseKeyFromJson; +import org.jclouds.chef.functions.ParseKeySetFromJson; import org.jclouds.chef.functions.ParseOrganizationFromJson; import org.jclouds.chef.functions.ParseUserFromJson; import org.jclouds.chef.functions.Username; @@ -47,7 +53,9 @@ import org.jclouds.rest.annotations.ParamParser; import org.jclouds.rest.annotations.RequestFilters; import org.jclouds.rest.annotations.ResponseParser; import org.jclouds.rest.binders.BindToJsonPayload; +import org.jclouds.rest.functions.ReturnFalseOnNotFoundOr404; import org.jclouds.rest.functions.ReturnNullOnNotFoundOr404; +import org.jclouds.rest.functions.ReturnVoidOnNotFoundOr404; import com.google.common.util.concurrent.ListenableFuture; @@ -63,9 +71,53 @@ import com.google.common.util.concurrent.ListenableFuture; @RequestFilters(SignedHeaderAuth.class) @Consumes(MediaType.APPLICATION_JSON) public interface ChefAsyncClient { + /** + * @see ChefClient#createClientInOrganization + */ + @POST + @Path("/organizations/{orgname}/clients") + @ResponseParser(ParseKeyFromJson.class) + ListenableFuture createClientInOrg(@PathParam("orgname") String orgname, + @BinderParam(BindClientnameToJsonPayload.class) String clientname); /** - * @see ChefAsyncClient#createUser + * @see ChefClient#generateKeyForClientInOrg + */ + @PUT + @Path("/organizations/{orgname}/clients/{clientname}") + @ResponseParser(ParseKeyFromJson.class) + ListenableFuture generateKeyForClientInOrg( + @PathParam("orgname") String orgname, + @PathParam("clientname") @BinderParam(BindGenerateKeyForClientToJsonPayload.class) String clientname); + + /** + * @see ChefClient#clientExistsInOrg + */ + @HEAD + @Path("/organizations/{orgname}/clients/{clientname}") + @ExceptionParser(ReturnFalseOnNotFoundOr404.class) + ListenableFuture clientExistsInOrg(@PathParam("orgname") String orgname, + @PathParam("clientname") String clientname); + + /** + * @see ChefClient#deleteClientInOrg + */ + @DELETE + @Path("/organizations/{orgname}/clients/{clientname}") + @ExceptionParser(ReturnVoidOnNotFoundOr404.class) + ListenableFuture deleteClientInOrg(@PathParam("orgname") String orgname, + @PathParam("clientname") String clientname); + + /** + * @see ChefClient#createClientInOrganization + */ + @GET + @Path("/organizations/{orgname}/clients") + @ResponseParser(ParseKeySetFromJson.class) + ListenableFuture> listClientsInOrg(@PathParam("orgname") String orgname); + + /** + * @see ChefClient#createUser */ @POST @Path("/users") @@ -73,7 +125,7 @@ public interface ChefAsyncClient { ListenableFuture createUser(@BinderParam(BindToJsonPayload.class) User user); /** - * @see ChefAsyncClient#updateUser + * @see ChefClient#updateUser */ @PUT @Path("/users/{username}") @@ -82,7 +134,7 @@ public interface ChefAsyncClient { @PathParam("username") @ParamParser(Username.class) @BinderParam(BindToJsonPayload.class) User user); /** - * @see ChefAsyncClient#getUser + * @see ChefClient#getUser */ @GET @Path("/users/{username}") @@ -91,7 +143,7 @@ public interface ChefAsyncClient { ListenableFuture getUser(@PathParam("username") String username); /** - * @see ChefAsyncClient#deleteUser + * @see ChefClient#deleteUser */ @DELETE @Path("/users/{username}") @@ -99,38 +151,37 @@ public interface ChefAsyncClient { ListenableFuture deleteUser(@PathParam("username") String username); /** - * @see ChefAsyncClient#createOrganization + * @see ChefClient#createOrg */ @POST @Path("/organizations") @ResponseParser(ParseKeyFromJson.class) - ListenableFuture createOrganization( - @BinderParam(BindToJsonPayload.class) Organization org); + ListenableFuture createOrg(@BinderParam(BindToJsonPayload.class) Organization org); /** - * @see ChefAsyncClient#updateOrganization + * @see ChefClient#updateOrg */ @PUT @Path("/organizations/{orgname}") @ResponseParser(ParseOrganizationFromJson.class) - ListenableFuture updateOrganization( + ListenableFuture updateOrg( @PathParam("orgname") @ParamParser(OrganizationName.class) @BinderParam(BindToJsonPayload.class) Organization org); /** - * @see ChefAsyncClient#getOrganization + * @see ChefClient#getOrg */ @GET @Path("/organizations/{orgname}") @ExceptionParser(ReturnNullOnNotFoundOr404.class) @ResponseParser(ParseOrganizationFromJson.class) - ListenableFuture getOrganization(@PathParam("orgname") String orgname); + ListenableFuture getOrg(@PathParam("orgname") String orgname); /** - * @see ChefAsyncClient#deleteOrganization + * @see ChefClient#deleteOrg */ @DELETE @Path("/organizations/{orgname}") @ResponseParser(ParseOrganizationFromJson.class) - ListenableFuture deleteOrganization(@PathParam("orgname") String orgname); + ListenableFuture deleteOrg(@PathParam("orgname") String orgname); } diff --git a/chef/src/main/java/org/jclouds/chef/ChefClient.java b/chef/src/main/java/org/jclouds/chef/ChefClient.java index acf08c8188..28519aaaa5 100644 --- a/chef/src/main/java/org/jclouds/chef/ChefClient.java +++ b/chef/src/main/java/org/jclouds/chef/ChefClient.java @@ -41,11 +41,13 @@ */ package org.jclouds.chef; +import java.util.Set; import java.util.concurrent.TimeUnit; import org.jclouds.chef.domain.Organization; import org.jclouds.chef.domain.User; import org.jclouds.concurrent.Timeout; +import org.jclouds.http.HttpResponseException; import org.jclouds.rest.AuthorizationException; import org.jclouds.rest.ResourceNotFoundException; @@ -57,9 +59,34 @@ import org.jclouds.rest.ResourceNotFoundException; * @see * @author Adrian Cole */ -@Timeout(duration = 4, timeUnit = TimeUnit.SECONDS) +@Timeout(duration = 30, timeUnit = TimeUnit.SECONDS) public interface ChefClient { + /** + * creates a new client + * + * @return the private key of the client. You can then use this client name and private key to + * access the Opscode API. + * @throws AuthorizationException + *

+ * "401 Unauthorized" if the caller is not a recognized user. + *

+ * "403 Forbidden" if the caller is not authorized to create a client. + * @throws HttpResponseException + * "409 Conflict" if the client already exists + */ + @Timeout(duration = 120, timeUnit = TimeUnit.SECONDS) + String createClientInOrg(String orgname, String name); + + @Timeout(duration = 120, timeUnit = TimeUnit.SECONDS) + String generateKeyForClientInOrg(String orgname, String name); + + Set listClientsInOrg(String orgname); + + boolean clientExistsInOrg(String orgname, String name); + + void deleteClientInOrg(String orgname, String name); + /** * creates a new user * @@ -118,7 +145,7 @@ public interface ChefClient { *

* "403 Forbidden" if the caller is not authorized to create a organization. */ - String createOrganization(Organization organization); + String createOrg(Organization organization); /** * updates an existing organization. Note: you must have update rights on the organization. @@ -131,14 +158,14 @@ public interface ChefClient { * @throws ResourceNotFoundException * if the organization does not exist. */ - Organization updateOrganization(Organization organization); + Organization updateOrg(Organization organization); /** * retrieves an existing organization. Note: you must have update rights on the organization. * * @return null, if the organization is not found */ - Organization getOrganization(String organizationname); + Organization getOrg(String organizationname); /** * deletes an existing organization. Note: you must have delete rights on the organization. @@ -153,5 +180,5 @@ public interface ChefClient { *

* “404 Not Found” if the organization does not exist. */ - Organization deleteOrganization(String organizationname); + Organization deleteOrg(String organizationname); } diff --git a/chef/src/main/java/org/jclouds/chef/ChefContextBuilder.java b/chef/src/main/java/org/jclouds/chef/ChefContextBuilder.java index 46f7f93d60..f1107a1f30 100644 --- a/chef/src/main/java/org/jclouds/chef/ChefContextBuilder.java +++ b/chef/src/main/java/org/jclouds/chef/ChefContextBuilder.java @@ -49,8 +49,8 @@ public class ChefContextBuilder extends RestContextBuilder() { }, new TypeLiteral() { }, props); - checkNotNull(properties.getProperty(ChefConstants.PROPERTY_CHEF_USER_ID)); - checkNotNull(properties.getProperty(ChefConstants.PROPERTY_CHEF_PRIVATE_KEY)); + checkNotNull(properties.getProperty(ChefConstants.PROPERTY_CHEF_IDENTITY)); + checkNotNull(properties.getProperty(ChefConstants.PROPERTY_CHEF_RSA_KEY)); } protected void addClientModule(List modules) { diff --git a/chef/src/main/java/org/jclouds/chef/ChefContextFactory.java b/chef/src/main/java/org/jclouds/chef/ChefContextFactory.java index 6948f54dea..dff363e101 100644 --- a/chef/src/main/java/org/jclouds/chef/ChefContextFactory.java +++ b/chef/src/main/java/org/jclouds/chef/ChefContextFactory.java @@ -49,15 +49,15 @@ import com.google.inject.Module; */ public class ChefContextFactory { - public static RestContext createContext(String user, String password, + public static RestContext createContext(String identity, String rsaKey, Module... modules) { - return new ChefContextBuilder("chef", new ChefPropertiesBuilder(user, password).build()) + return new ChefContextBuilder("chef", new ChefPropertiesBuilder(identity, rsaKey).build()) .withModules(modules).buildContext(); } - public static RestContext createContext(URI endpoint, String user, String password, + public static RestContext createContext(URI endpoint, String identity, String rsaKey, Module... modules) { - return new ChefContextBuilder("chef", new ChefPropertiesBuilder(user, password).withEndpoint(endpoint).build()) + return new ChefContextBuilder("chef", new ChefPropertiesBuilder(identity, rsaKey).withEndpoint(endpoint).build()) .withModules(modules).buildContext(); } diff --git a/chef/src/main/java/org/jclouds/chef/ChefPropertiesBuilder.java b/chef/src/main/java/org/jclouds/chef/ChefPropertiesBuilder.java index 9c04d4dc9f..09c1aedc92 100644 --- a/chef/src/main/java/org/jclouds/chef/ChefPropertiesBuilder.java +++ b/chef/src/main/java/org/jclouds/chef/ChefPropertiesBuilder.java @@ -25,9 +25,9 @@ package org.jclouds.chef; import static com.google.common.base.Preconditions.checkNotNull; import static org.jclouds.chef.reference.ChefConstants.PROPERTY_CHEF_ENDPOINT; -import static org.jclouds.chef.reference.ChefConstants.PROPERTY_CHEF_PRIVATE_KEY; +import static org.jclouds.chef.reference.ChefConstants.PROPERTY_CHEF_RSA_KEY; import static org.jclouds.chef.reference.ChefConstants.PROPERTY_CHEF_TIMESTAMP_INTERVAL; -import static org.jclouds.chef.reference.ChefConstants.PROPERTY_CHEF_USER_ID; +import static org.jclouds.chef.reference.ChefConstants.PROPERTY_CHEF_IDENTITY; import java.net.URI; import java.util.Properties; @@ -52,14 +52,14 @@ public class ChefPropertiesBuilder extends PropertiesBuilder { super(properties); } - public ChefPropertiesBuilder(String id, String secret) { + public ChefPropertiesBuilder(String identity, String rsaKey) { super(); - withCredentials(id, secret); + withCredentials(identity, rsaKey); } - public ChefPropertiesBuilder withCredentials(String id, String secret) { - properties.setProperty(PROPERTY_CHEF_USER_ID, checkNotNull(id, "user")); - properties.setProperty(PROPERTY_CHEF_PRIVATE_KEY, checkNotNull(secret, "password")); + public ChefPropertiesBuilder withCredentials(String identity, String rsaKey) { + properties.setProperty(PROPERTY_CHEF_IDENTITY, checkNotNull(identity, "identity")); + properties.setProperty(PROPERTY_CHEF_RSA_KEY, checkNotNull(rsaKey, "password")); return this; } diff --git a/chef/src/main/java/org/jclouds/chef/binders/BindClientnameToJsonPayload.java b/chef/src/main/java/org/jclouds/chef/binders/BindClientnameToJsonPayload.java new file mode 100644 index 0000000000..d3fbe17eaf --- /dev/null +++ b/chef/src/main/java/org/jclouds/chef/binders/BindClientnameToJsonPayload.java @@ -0,0 +1,48 @@ +/** + * + * Copyright (C) 2010 Cloud Conscious, LLC. + * + * ==================================================================== + * 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.binders; + +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MediaType; + +import org.jclouds.http.HttpRequest; +import org.jclouds.rest.binders.BindToStringPayload; + +import com.google.common.collect.ImmutableSet; + +/** + * + * @author Adrian Cole + * + */ +public class BindClientnameToJsonPayload extends BindToStringPayload { + + @Override + public void bindToRequest(HttpRequest request, Object payload) { + request.getHeaders().replaceValues(HttpHeaders.CONTENT_TYPE, + ImmutableSet.of(MediaType.APPLICATION_JSON)); + super.bindToRequest(request, String.format("{\"clientname\":\"%s\"}", payload)); + } + +} diff --git a/chef/src/main/java/org/jclouds/chef/binders/BindGenerateKeyForClientToJsonPayload.java b/chef/src/main/java/org/jclouds/chef/binders/BindGenerateKeyForClientToJsonPayload.java new file mode 100644 index 0000000000..243e2102e6 --- /dev/null +++ b/chef/src/main/java/org/jclouds/chef/binders/BindGenerateKeyForClientToJsonPayload.java @@ -0,0 +1,49 @@ +/** + * + * Copyright (C) 2010 Cloud Conscious, LLC. + * + * ==================================================================== + * 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.binders; + +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MediaType; + +import org.jclouds.http.HttpRequest; +import org.jclouds.rest.binders.BindToStringPayload; + +import com.google.common.collect.ImmutableSet; + +/** + * + * @author Adrian Cole + * + */ +public class BindGenerateKeyForClientToJsonPayload extends BindToStringPayload { + + @Override + public void bindToRequest(HttpRequest request, Object payload) { + request.getHeaders().replaceValues(HttpHeaders.CONTENT_TYPE, + ImmutableSet.of(MediaType.APPLICATION_JSON)); + super.bindToRequest(request, String.format("{\"clientname\":\"%s\", \"private_key\": true}", + payload)); + } + +} diff --git a/chef/src/main/java/org/jclouds/chef/config/ChefContextModule.java b/chef/src/main/java/org/jclouds/chef/config/ChefContextModule.java index 11e2280235..5facd86183 100644 --- a/chef/src/main/java/org/jclouds/chef/config/ChefContextModule.java +++ b/chef/src/main/java/org/jclouds/chef/config/ChefContextModule.java @@ -77,7 +77,7 @@ public class ChefContextModule extends AbstractModule { @Provides @Singleton RestContext provideContext(Closer closer, ChefAsyncClient asyncApi, - ChefClient syncApi, @Chef URI endPoint, @Named(ChefConstants.PROPERTY_CHEF_USER_ID) String account) { + ChefClient syncApi, @Chef URI endPoint, @Named(ChefConstants.PROPERTY_CHEF_IDENTITY) String account) { return new RestContextImpl(closer, asyncApi, syncApi, endPoint, account); } diff --git a/chef/src/main/java/org/jclouds/chef/config/ChefRestClientModule.java b/chef/src/main/java/org/jclouds/chef/config/ChefRestClientModule.java index c1b465a48a..1c29da9be8 100644 --- a/chef/src/main/java/org/jclouds/chef/config/ChefRestClientModule.java +++ b/chef/src/main/java/org/jclouds/chef/config/ChefRestClientModule.java @@ -57,12 +57,17 @@ import org.bouncycastle.openssl.PEMReader; import org.jclouds.chef.Chef; import org.jclouds.chef.ChefAsyncClient; import org.jclouds.chef.ChefClient; +import org.jclouds.chef.handlers.ChefErrorHandler; import org.jclouds.chef.reference.ChefConstants; import org.jclouds.concurrent.ExpirableSupplier; import org.jclouds.concurrent.internal.SyncProxy; import org.jclouds.date.DateService; import org.jclouds.date.TimeStamp; +import org.jclouds.http.HttpErrorHandler; import org.jclouds.http.RequiresHttp; +import org.jclouds.http.annotation.ClientError; +import org.jclouds.http.annotation.Redirection; +import org.jclouds.http.annotation.ServerError; import org.jclouds.rest.ConfiguresRestClient; import org.jclouds.rest.RestClientFactory; @@ -95,7 +100,7 @@ public class ChefRestClientModule extends AbstractModule { final DateService dateService) { return new ExpirableSupplier(new Supplier() { public String get() { - return dateService.iso8601DateFormat(); + return dateService.iso8601SecondsDateFormat(); } }, seconds, TimeUnit.SECONDS); } @@ -108,7 +113,7 @@ public class ChefRestClientModule extends AbstractModule { @Provides @Singleton - public PrivateKey provideKey(@Named(ChefConstants.PROPERTY_CHEF_PRIVATE_KEY) String key) + public PrivateKey provideKey(@Named(ChefConstants.PROPERTY_CHEF_RSA_KEY) String key) throws IOException { // TODO do this without adding a provider Security.addProvider(new BouncyCastleProvider()); @@ -135,9 +140,14 @@ public class ChefRestClientModule extends AbstractModule { protected URI provideURI(@Named(ChefConstants.PROPERTY_CHEF_ENDPOINT) String endpoint) { return URI.create(endpoint); } - + protected void bindErrorHandlers() { - // TODO + bind(HttpErrorHandler.class).annotatedWith(Redirection.class).to( + ChefErrorHandler.class); + bind(HttpErrorHandler.class).annotatedWith(ClientError.class).to( + ChefErrorHandler.class); + bind(HttpErrorHandler.class).annotatedWith(ServerError.class).to( + ChefErrorHandler.class); } protected void bindRetryHandlers() { diff --git a/chef/src/main/java/org/jclouds/chef/domain/User.java b/chef/src/main/java/org/jclouds/chef/domain/User.java index 735720435c..6e9719f81c 100644 --- a/chef/src/main/java/org/jclouds/chef/domain/User.java +++ b/chef/src/main/java/org/jclouds/chef/domain/User.java @@ -36,6 +36,7 @@ public class User implements Comparable { @SerializedName("display_name") private String displayName; private String email; + private String password; public User(String username) { this.username = username; @@ -107,6 +108,7 @@ public class User implements Comparable { result = prime * result + ((firstName == null) ? 0 : firstName.hashCode()); result = prime * result + ((lastName == null) ? 0 : lastName.hashCode()); result = prime * result + ((middleName == null) ? 0 : middleName.hashCode()); + result = prime * result + ((password == null) ? 0 : password.hashCode()); result = prime * result + ((username == null) ? 0 : username.hashCode()); return result; } @@ -145,6 +147,11 @@ public class User implements Comparable { return false; } else if (!middleName.equals(other.middleName)) return false; + if (password == null) { + if (other.password != null) + return false; + } else if (!password.equals(other.password)) + return false; if (username == null) { if (other.username != null) return false; @@ -159,4 +166,12 @@ public class User implements Comparable { + firstName + ", middleName=" + middleName + ", lastName=" + lastName + ", email=" + email + "]"; } + + public void setPassword(String password) { + this.password = password; + } + + public String getPassword() { + return password; + } } diff --git a/chef/src/main/java/org/jclouds/chef/filters/SignedHeaderAuth.java b/chef/src/main/java/org/jclouds/chef/filters/SignedHeaderAuth.java index 00afbc5d42..0e98d7b988 100644 --- a/chef/src/main/java/org/jclouds/chef/filters/SignedHeaderAuth.java +++ b/chef/src/main/java/org/jclouds/chef/filters/SignedHeaderAuth.java @@ -77,7 +77,7 @@ public class SignedHeaderAuth implements HttpRequestFilter { @Inject public SignedHeaderAuth(SignatureWire signatureWire, - @Named(ChefConstants.PROPERTY_CHEF_USER_ID) String userId, PrivateKey privateKey, + @Named(ChefConstants.PROPERTY_CHEF_IDENTITY) String userId, PrivateKey privateKey, @TimeStamp Provider timeStampProvider, EncryptionService encryptionService) { this.signatureWire = signatureWire; this.userId = userId; diff --git a/chef/src/main/java/org/jclouds/chef/functions/ParseErrorFromJsonOrNull.java b/chef/src/main/java/org/jclouds/chef/functions/ParseErrorFromJsonOrNull.java new file mode 100644 index 0000000000..f777a055d0 --- /dev/null +++ b/chef/src/main/java/org/jclouds/chef/functions/ParseErrorFromJsonOrNull.java @@ -0,0 +1,71 @@ +/** + * + * Copyright (C) 2010 Cloud Conscious, LLC. + * + * ==================================================================== + * 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 java.io.IOException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.inject.Singleton; + +import org.jclouds.http.HttpResponse; +import org.jclouds.util.Utils; + +import com.google.common.base.Function; +import com.google.common.base.Throwables; + +/** + * + * + * @author Adrian Cole + */ +@Singleton +public class ParseErrorFromJsonOrNull implements Function { + Pattern pattern = Pattern.compile(".*error\": *\"([^\"]+)\".*"); + + @Override + public String apply(HttpResponse response) { + if (response.getContent() == null) + return null; + try { + return parse(Utils.toStringAndClose(response.getContent())); + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + try { + response.getContent().close(); + } catch (IOException e) { + Throwables.propagate(e); + } + } + } + + public String parse(String in) { + Matcher matcher = pattern.matcher(in); + while (matcher.find()) { + return matcher.group(1); + } + return null; + } +} \ No newline at end of file diff --git a/chef/src/main/java/org/jclouds/chef/functions/ParseKeyFromJson.java b/chef/src/main/java/org/jclouds/chef/functions/ParseKeyFromJson.java index e2fab289bb..06dd2f7fc4 100644 --- a/chef/src/main/java/org/jclouds/chef/functions/ParseKeyFromJson.java +++ b/chef/src/main/java/org/jclouds/chef/functions/ParseKeyFromJson.java @@ -42,7 +42,7 @@ import com.google.common.base.Throwables; */ @Singleton public class ParseKeyFromJson implements Function { - Pattern pattern = Pattern.compile(".*private_key\": \"([^\"]+)\".*"); + Pattern pattern = Pattern.compile(".*private_key\": *\"([^\"]+)\".*"); @Override public String apply(HttpResponse response) { @@ -62,7 +62,7 @@ public class ParseKeyFromJson implements Function { public String parse(String in) { Matcher matcher = pattern.matcher(in); while (matcher.find()) { - return matcher.group(1); + return matcher.group(1).replaceAll("\\\\n", "\n"); } assert false : String.format("pattern: %s didn't match %s", pattern, in); return null; diff --git a/chef/src/main/java/org/jclouds/chef/functions/ParseKeySetFromJson.java b/chef/src/main/java/org/jclouds/chef/functions/ParseKeySetFromJson.java new file mode 100644 index 0000000000..1fcf30ee5b --- /dev/null +++ b/chef/src/main/java/org/jclouds/chef/functions/ParseKeySetFromJson.java @@ -0,0 +1,81 @@ +/** + * + * Copyright (C) 2009 Cloud Conscious, LLC. + * + * ==================================================================== + * Licensed 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. + * ==================================================================== + */ +/** + * + * Copyright (C) 2009 Cloud Conscious, LLC. + * + * ==================================================================== + * 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 java.io.InputStream; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; +import java.lang.reflect.Type; +import java.util.Map; +import java.util.Set; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import org.jclouds.http.functions.ParseJson; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; + +/** + * @author Adrian Cole + */ +@Singleton +public class ParseKeySetFromJson extends ParseJson> { + @Inject + public ParseKeySetFromJson(Gson gson) { + super(gson); + } + + @SuppressWarnings("unchecked") + @Override + protected Set apply(InputStream stream) { + try { + Type map = new TypeToken>() { + }.getType(); + return ((Map) gson.fromJson(new InputStreamReader(stream, "UTF-8"), map)) + .keySet(); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException("jclouds requires UTF-8 encoding", e); + } + } +} \ No newline at end of file diff --git a/chef/src/main/java/org/jclouds/chef/handlers/ChefErrorHandler.java b/chef/src/main/java/org/jclouds/chef/handlers/ChefErrorHandler.java new file mode 100644 index 0000000000..793cb9734b --- /dev/null +++ b/chef/src/main/java/org/jclouds/chef/handlers/ChefErrorHandler.java @@ -0,0 +1,77 @@ +/** + * + * Copyright (C) 2009 Cloud Conscious, LLC. + * + * ==================================================================== + * Licensed 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.handlers; + +import javax.annotation.Resource; +import javax.inject.Inject; +import javax.inject.Singleton; + +import org.jclouds.chef.functions.ParseErrorFromJsonOrNull; +import org.jclouds.http.HttpCommand; +import org.jclouds.http.HttpErrorHandler; +import org.jclouds.http.HttpResponse; +import org.jclouds.http.HttpResponseException; +import org.jclouds.logging.Logger; +import org.jclouds.rest.AuthorizationException; +import org.jclouds.rest.ResourceNotFoundException; + +import com.google.common.io.Closeables; + +/** + * This will parse and set an appropriate exception on the command object. + * + * @author Adrian Cole + * + */ +@Singleton +public class ChefErrorHandler implements HttpErrorHandler { + @Resource + protected Logger logger = Logger.NULL; + private final ParseErrorFromJsonOrNull errorParser; + + @Inject + ChefErrorHandler(ParseErrorFromJsonOrNull errorParser) { + this.errorParser = errorParser; + } + + public void handleError(HttpCommand command, HttpResponse response) { + String message = errorParser.apply(response); + Exception exception = message != null ? new HttpResponseException(command, response, message) + : new HttpResponseException(command, response); + try { + message = message != null ? message : String.format("%s -> %s", command.getRequest() + .getRequestLine(), response.getStatusLine()); + switch (response.getStatusCode()) { + case 401: + case 403: + exception = new AuthorizationException(message, exception); + break; + case 404: + if (!command.getRequest().getMethod().equals("DELETE")) { + exception = new ResourceNotFoundException(message, exception); + } + break; + } + } finally { + Closeables.closeQuietly(response.getContent()); + command.setException(exception); + } + } + +} \ No newline at end of file diff --git a/chef/src/main/java/org/jclouds/chef/reference/ChefConstants.java b/chef/src/main/java/org/jclouds/chef/reference/ChefConstants.java index 03bc21a0fe..ea0f4c7c69 100644 --- a/chef/src/main/java/org/jclouds/chef/reference/ChefConstants.java +++ b/chef/src/main/java/org/jclouds/chef/reference/ChefConstants.java @@ -48,11 +48,20 @@ package org.jclouds.chef.reference; */ public interface ChefConstants { public static final String PROPERTY_CHEF_ENDPOINT = "jclouds.chef.endpoint"; - public static final String PROPERTY_CHEF_USER_ID = "jclouds.chef.user-id"; + /** + * There are generally 3 types of identities + *

    + *
  • validator - used to create clients within an organization; {@code orgname}-validator
  • + *
  • client - scoped to an organization, used on nodes to run chef
  • + *
  • user - used to run commands like knife and access cookbook sites
  • + *
+ * + */ + public static final String PROPERTY_CHEF_IDENTITY = "jclouds.chef.identity"; /** * The PEM-encoded key */ - public static final String PROPERTY_CHEF_PRIVATE_KEY = "jclouds.chef.private-key"; + public static final String PROPERTY_CHEF_RSA_KEY = "jclouds.chef.rsa-key"; /** * how often to refresh timestamps in seconds. diff --git a/chef/src/test/java/org/jclouds/chef/ChefAsyncClientTest.java b/chef/src/test/java/org/jclouds/chef/ChefAsyncClientTest.java index cdfeaffd84..bd604f08ca 100644 --- a/chef/src/test/java/org/jclouds/chef/ChefAsyncClientTest.java +++ b/chef/src/test/java/org/jclouds/chef/ChefAsyncClientTest.java @@ -35,12 +35,17 @@ import org.jclouds.chef.domain.User; import org.jclouds.chef.filters.SignedHeaderAuth; import org.jclouds.chef.filters.SignedHeaderAuthTest; import org.jclouds.chef.functions.ParseKeyFromJson; +import org.jclouds.chef.functions.ParseKeySetFromJson; import org.jclouds.chef.functions.ParseOrganizationFromJson; import org.jclouds.chef.functions.ParseUserFromJson; import org.jclouds.date.TimeStamp; +import org.jclouds.http.functions.CloseContentAndReturn; +import org.jclouds.http.functions.ReturnTrueIf2xx; import org.jclouds.logging.config.NullLoggingModule; import org.jclouds.rest.RestClientTest; +import org.jclouds.rest.functions.ReturnFalseOnNotFoundOr404; import org.jclouds.rest.functions.ReturnNullOnNotFoundOr404; +import org.jclouds.rest.functions.ReturnVoidOnNotFoundOr404; import org.jclouds.rest.internal.GeneratedHttpRequest; import org.jclouds.rest.internal.RestAnnotationProcessor; import org.jclouds.util.Jsr330; @@ -58,6 +63,98 @@ import com.google.inject.TypeLiteral; */ @Test(groups = "unit", testName = "chef.ChefAsyncClientTest") public class ChefAsyncClientTest extends RestClientTest { + public void testClientExistsInOrg() throws SecurityException, NoSuchMethodException, IOException { + Method method = ChefAsyncClient.class.getMethod("clientExistsInOrg", String.class, + String.class); + GeneratedHttpRequest httpRequest = processor.createRequest(method, "org", + "client"); + assertRequestLineEquals(httpRequest, + "HEAD https://api.opscode.com/organizations/org/clients/client HTTP/1.1"); + assertHeadersEqual(httpRequest, "Accept: application/json\n"); + assertPayloadEquals(httpRequest, null); + + assertResponseParserClassEquals(method, httpRequest, ReturnTrueIf2xx.class); + assertSaxResponseParserClassEquals(method, null); + assertExceptionParserClassEquals(method, ReturnFalseOnNotFoundOr404.class); + + checkFilters(httpRequest); + + } + + public void testDeleteClientInOrg() throws SecurityException, NoSuchMethodException, IOException { + Method method = ChefAsyncClient.class.getMethod("deleteClientInOrg", String.class, + String.class); + GeneratedHttpRequest httpRequest = processor.createRequest(method, "org", + "client"); + assertRequestLineEquals(httpRequest, + "DELETE https://api.opscode.com/organizations/org/clients/client HTTP/1.1"); + assertHeadersEqual(httpRequest, "Accept: application/json\n"); + assertPayloadEquals(httpRequest, null); + + assertResponseParserClassEquals(method, httpRequest, CloseContentAndReturn.class); + assertSaxResponseParserClassEquals(method, null); + assertExceptionParserClassEquals(method, ReturnVoidOnNotFoundOr404.class); + + checkFilters(httpRequest); + + } + + public void testGenerateKeyForClientInOrg() throws SecurityException, NoSuchMethodException, + IOException { + Method method = ChefAsyncClient.class.getMethod("generateKeyForClientInOrg", String.class, + String.class); + GeneratedHttpRequest httpRequest = processor.createRequest(method, "org", + "client"); + assertRequestLineEquals(httpRequest, + "PUT https://api.opscode.com/organizations/org/clients/client HTTP/1.1"); + assertHeadersEqual(httpRequest, + "Accept: application/json\nContent-Length: 44\nContent-Type: application/json\n"); + assertPayloadEquals(httpRequest, "{\"clientname\":\"client\", \"private_key\": true}"); + + assertResponseParserClassEquals(method, httpRequest, ParseKeyFromJson.class); + assertSaxResponseParserClassEquals(method, null); + assertExceptionParserClassEquals(method, null); + + checkFilters(httpRequest); + + } + + public void testCreateClientInOrg() throws SecurityException, NoSuchMethodException, IOException { + Method method = ChefAsyncClient.class.getMethod("createClientInOrg", String.class, + String.class); + GeneratedHttpRequest httpRequest = processor.createRequest(method, "org", + "client"); + + assertRequestLineEquals(httpRequest, + "POST https://api.opscode.com/organizations/org/clients HTTP/1.1"); + assertHeadersEqual(httpRequest, + "Accept: application/json\nContent-Length: 23\nContent-Type: application/json\n"); + assertPayloadEquals(httpRequest, "{\"clientname\":\"client\"}"); + + assertResponseParserClassEquals(method, httpRequest, ParseKeyFromJson.class); + assertSaxResponseParserClassEquals(method, null); + assertExceptionParserClassEquals(method, null); + + checkFilters(httpRequest); + + } + + public void testListClientsInOrg() throws SecurityException, NoSuchMethodException, IOException { + Method method = ChefAsyncClient.class.getMethod("listClientsInOrg", String.class); + GeneratedHttpRequest httpRequest = processor.createRequest(method, "org"); + + assertRequestLineEquals(httpRequest, + "GET https://api.opscode.com/organizations/org/clients HTTP/1.1"); + assertHeadersEqual(httpRequest, "Accept: application/json\n"); + assertPayloadEquals(httpRequest, null); + + assertResponseParserClassEquals(method, httpRequest, ParseKeySetFromJson.class); + assertSaxResponseParserClassEquals(method, null); + assertExceptionParserClassEquals(method, null); + + checkFilters(httpRequest); + + } public void testCreateUser() throws SecurityException, NoSuchMethodException, IOException { Method method = ChefAsyncClient.class.getMethod("createUser", User.class); @@ -163,9 +260,8 @@ public class ChefAsyncClientTest extends RestClientTest { } - public void testCreateOrganization() throws SecurityException, NoSuchMethodException, - IOException { - Method method = ChefAsyncClient.class.getMethod("createOrganization", Organization.class); + public void testCreateOrg() throws SecurityException, NoSuchMethodException, IOException { + Method method = ChefAsyncClient.class.getMethod("createOrg", Organization.class); GeneratedHttpRequest httpRequest = processor.createRequest(method, new Organization("myorganization")); @@ -182,9 +278,8 @@ public class ChefAsyncClientTest extends RestClientTest { } - public void testUpdateOrganization() throws SecurityException, NoSuchMethodException, - IOException { - Method method = ChefAsyncClient.class.getMethod("updateOrganization", Organization.class); + public void testUpdateOrg() throws SecurityException, NoSuchMethodException, IOException { + Method method = ChefAsyncClient.class.getMethod("updateOrg", Organization.class); GeneratedHttpRequest httpRequest = processor.createRequest(method, new Organization("myorganization")); @@ -202,8 +297,8 @@ public class ChefAsyncClientTest extends RestClientTest { } - public void testGetOrganization() throws SecurityException, NoSuchMethodException, IOException { - Method method = ChefAsyncClient.class.getMethod("getOrganization", String.class); + public void testGetOrg() throws SecurityException, NoSuchMethodException, IOException { + Method method = ChefAsyncClient.class.getMethod("getOrg", String.class); GeneratedHttpRequest httpRequest = processor.createRequest(method, "myorganization"); @@ -220,9 +315,8 @@ public class ChefAsyncClientTest extends RestClientTest { } - public void testDeleteOrganization() throws SecurityException, NoSuchMethodException, - IOException { - Method method = ChefAsyncClient.class.getMethod("deleteOrganization", String.class); + public void testDeleteOrg() throws SecurityException, NoSuchMethodException, IOException { + Method method = ChefAsyncClient.class.getMethod("deleteOrg", String.class); GeneratedHttpRequest httpRequest = processor.createRequest(method, "myorganization"); diff --git a/chef/src/test/java/org/jclouds/chef/ChefClientLiveTest.java b/chef/src/test/java/org/jclouds/chef/ChefClientLiveTest.java index dbd1d09ab8..8cf12b42ff 100644 --- a/chef/src/test/java/org/jclouds/chef/ChefClientLiveTest.java +++ b/chef/src/test/java/org/jclouds/chef/ChefClientLiveTest.java @@ -28,11 +28,14 @@ import static org.testng.Assert.assertNotNull; import java.io.File; import java.io.IOException; +import java.util.Set; -import org.jclouds.chef.domain.Organization; import org.jclouds.chef.domain.User; import org.jclouds.logging.log4j.config.Log4JLoggingModule; -import org.testng.annotations.BeforeGroups; +import org.jclouds.rest.AuthorizationException; +import org.jclouds.rest.RestContext; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; import com.google.common.base.Charsets; @@ -46,23 +49,73 @@ import com.google.common.io.Files; @Test(groups = "live", testName = "chef.ChefClientLiveTest") public class ChefClientLiveTest { - private ChefClient client; - private String username; + private RestContext validatorConnection; + private RestContext clientConnection; - @BeforeGroups(groups = { "live" }) + private String orgname; + private String clientKey; + + public static final String PREFIX = System.getProperty("user.name") + "-jcloudstest"; + + @BeforeClass(groups = { "live" }) public void setupClient() throws IOException { - username = checkNotNull(System.getProperty("jclouds.test.user"), "jclouds.test.user"); + orgname = checkNotNull(System.getProperty("jclouds.test.user"), "jclouds.test.user"); String keyfile = System.getProperty("jclouds.test.key"); if (keyfile == null || keyfile.equals("")) - keyfile = System.getProperty("user.home") + "/chef/client.pem"; - client = ChefContextFactory.createContext(username, - Files.toString(new File(keyfile), Charsets.UTF_8), new Log4JLoggingModule()) - .getApi(); + keyfile = System.getProperty("user.home") + "/chef/validation.pem"; + validatorConnection = createConnection(orgname + "-validator", Files.toString(new File( + keyfile), Charsets.UTF_8)); + } + + private RestContext createConnection(String identity, String key) + throws IOException { + return ChefContextFactory.createContext(identity, key, new Log4JLoggingModule()); + } + + @Test + public void testListClientsInOrg() throws Exception { + Set clients = validatorConnection.getApi().listClientsInOrg(orgname); + assertNotNull(clients); + assert clients.contains(orgname + "-validator"); + } + + @Test(dependsOnMethods = "testListClientsInOrg") + public void testCreateClientInOrg() throws Exception { + validatorConnection.getApi().deleteClientInOrg(orgname, PREFIX); + clientKey = validatorConnection.getApi().createClientInOrg(orgname, PREFIX); + assertNotNull(clientKey); + System.out.println(clientKey); + clientConnection = createConnection(PREFIX, clientKey); + clientConnection.getApi().clientExistsInOrg(orgname, PREFIX); + } + + @Test(dependsOnMethods = "testCreateClientInOrg") + public void testGenerateKeyForClientInOrg() throws Exception { + clientKey = validatorConnection.getApi().generateKeyForClientInOrg(orgname, PREFIX); + assertNotNull(clientKey); + clientConnection.close(); + clientConnection = createConnection(PREFIX, clientKey); + clientConnection.getApi().clientExistsInOrg(orgname, PREFIX); + } + + @Test(dependsOnMethods = "testCreateClientInOrg") + public void testClientExistsInOrg() throws Exception { + assertNotNull(validatorConnection.getApi().clientExistsInOrg(orgname, PREFIX)); + } + + @Test(expectedExceptions = AuthorizationException.class) + public void testGetOrgFailsForValidationKey() throws Exception { + validatorConnection.getApi().getOrg(orgname); + } + + @Test(dependsOnMethods = "testGenerateKeyForClientInOrg", expectedExceptions = AuthorizationException.class) + public void testGetOrgFailsForClient() throws Exception { + clientConnection.getApi().getOrg(orgname); } @Test(enabled = false) public void testGetUser() throws Exception { - User user = client.getUser(username); + User user = validatorConnection.getApi().getUser(orgname); assertNotNull(user); } @@ -82,24 +135,26 @@ public class ChefClientLiveTest { } @Test(enabled = false) - public void testCreateOrganization() throws Exception { - // TODO - } - - @Test - public void testGetOrganization() throws Exception { - Organization organization = client.getOrganization("jclouds"); - assertNotNull(organization); - } - - @Test(enabled = false) - public void testUpdateOrganization() throws Exception { + public void testCreateOrg() throws Exception { // TODO } @Test(enabled = false) - public void testDeleteOrganization() throws Exception { + public void testUpdateOrg() throws Exception { + // TODO + } + + @Test(enabled = false) + public void testDeleteOrg() throws Exception { // TODO } + + @AfterClass(groups = { "live" }) + public void teardownClient() throws IOException { + if (clientConnection != null) + clientConnection.close(); + if (validatorConnection != null) + validatorConnection.close(); + } } diff --git a/chef/src/test/java/org/jclouds/chef/filters/SignedHeaderAuthTest.java b/chef/src/test/java/org/jclouds/chef/filters/SignedHeaderAuthTest.java index beab628010..08cfd7ca3d 100644 --- a/chef/src/test/java/org/jclouds/chef/filters/SignedHeaderAuthTest.java +++ b/chef/src/test/java/org/jclouds/chef/filters/SignedHeaderAuthTest.java @@ -111,13 +111,18 @@ public class SignedHeaderAuthTest { public static final Multimap EXPECTED_SIGN_RESULT_EMPTY = ImmutableMultimap . builder().put("X-Ops-Content-Hash", X_OPS_CONTENT_HASH_EMPTY).put( "X-Ops-Userid", USER_ID).put("X-Ops-Sign", "version=1.0").put( - "X-Ops-Authorization-1", "N6U75kopDK64cEFqrB6vw+PnubnXr0w5LQeXnIGNGLRP2LvifwIeisk7QxEx").put( - "X-Ops-Authorization-2", "mtpQOWAw8HvnWErjzuk9AvUsqVmWpv14ficvkaD79qsPMvbje+aLcIrCGT1P").put( - "X-Ops-Authorization-3", "3d2uvf4w7iqwzrIscPnkxLR6o6pymR90gvJXDPzV7Le0jbfD8kmZ8AAK0sGG").put( - "X-Ops-Authorization-4", "09F1ftW80bLatJTA66Cw2wBz261r6x/abZhIKFJFDWLzyQGJ8ZNOkUrDDtgI").put( - "X-Ops-Authorization-5", "svLVXpOJKZZfKunsElpWjjsyNt3k8vpI1Y4ANO8Eg2bmeCPeEK+YriGm5fbC").put( - "X-Ops-Authorization-6", "DzWNPylHJqMeGKVYwGQKpg62QDfe5yXh3wZLiQcXow==").put("X-Ops-Timestamp", - TIMESTAMP_ISO8601).build(); + "X-Ops-Authorization-1", + "N6U75kopDK64cEFqrB6vw+PnubnXr0w5LQeXnIGNGLRP2LvifwIeisk7QxEx").put( + "X-Ops-Authorization-2", + "mtpQOWAw8HvnWErjzuk9AvUsqVmWpv14ficvkaD79qsPMvbje+aLcIrCGT1P").put( + "X-Ops-Authorization-3", + "3d2uvf4w7iqwzrIscPnkxLR6o6pymR90gvJXDPzV7Le0jbfD8kmZ8AAK0sGG").put( + "X-Ops-Authorization-4", + "09F1ftW80bLatJTA66Cw2wBz261r6x/abZhIKFJFDWLzyQGJ8ZNOkUrDDtgI").put( + "X-Ops-Authorization-5", + "svLVXpOJKZZfKunsElpWjjsyNt3k8vpI1Y4ANO8Eg2bmeCPeEK+YriGm5fbC").put( + "X-Ops-Authorization-6", "DzWNPylHJqMeGKVYwGQKpg62QDfe5yXh3wZLiQcXow==").put( + "X-Ops-Timestamp", TIMESTAMP_ISO8601).build(); public static String PUBLIC_KEY; public static String PRIVATE_KEY; @@ -195,6 +200,12 @@ public class SignedHeaderAuthTest { private SignedHeaderAuth signing_obj; private EncryptionService encryptionService; + @Test + void canParseKeyFromCreateClient() throws IOException { + String key = "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEA50Iwgq8OIfm5vY9Gwfb6UBt17D7V4djyFSLJ1AbCU/o8Zlrr\nW73JqaK5dC3IO6Dcu+/qPYGtBUWvhFAXrsFOooz0mTod/LtBN1YVurJ60goJrR6w\nKhUYC9H45OW/qcdIM7kdDwiyMZfbHqW6fo0xPqjvgxtZoI+v7pgThacOG6pw7PO6\nGgnJa3MGK3xEbzlI6+EBJWG3EiwexguwOpTD4a4TDIAqKrlVDPeUpU7rFbsBPRS8\nkypR3lU58+WRz/zi9fiH/Sy2X+S3yZg14HiutJjxc8zJsazF3eDxyLGPQmhv3Mso\nA0wbjGusbe6hPdDkzh/B2KO9u96QCdlGu/rc6QIDAQABAoIBAA/7OgD9+fsNF/Hq\nodgrqja4/xg5a2x1Ip2lTs9RPEKza1Mje1pWrkYD0c8ejtTYFAkE1mozuPJBU5TQ\nOCLChx2iohCovIPHqQUa9Nt3FBfJy8tj8Ian+IZwl0OyQOGJvQqeA00Tq8TTmrfu\negne1gVfhVXJIROAeocBiW/WEJqGti0OE5zQQMld3cJ5viTdEsaWYCu2HaEoblKB\nH6KfRGM2N3L3KjKFGtEg+cX1UdaMlzmp+O5/yvjBykZy6cuUOIsgz2e5nQV4hYEq\ntJ/+6E0QVTXfnVZi4IxKlkVMhyonqOxAOKGG+dWeWh3DqPJFzjmp3kcbRN9E3u+2\nqKU5gpECgYEA+a/i5z2jFCJ8rMpoCPPxm2eiIYZVs3LE33WU5FNNieBRC+KqO06h\nMB3rQ3k8KJDNJYWD5UwIrgjCD5mgkmcSDI6SbOn6PA1Mtw6qZlbeg17V9L9ryXxt\nSfC5AC+qVWd6unrLh0LgkvLS8rgG4GjLZY0HDDMrJWodcc+uWVk3Mo0CgYEA7RsG\nC9gOcHWi6WJ2uEDvLj4IkSkB4IFipEVcKl7VVDSnUgxaBZm3o0DLYDYhIOw7XcQL\n6vpxbRZdlApGyu1ahfMbk3+quFNMQuGxZcv9EhHz7ASnXK6mlrWkJzCGjLz6/MdI\nU0VGbtkBtOY/GaLXdTkQksWowVNoedISfQV9as0CgYEA0Tj1JVecw05yskeZDYd8\nOQCJ9xWd0pSlK6pXbUvweUwiHZd9ldy5bJxle1CnfEZ54KsUbptb2pk0I+ZTitob\nYbJGOEWHjbKHSg1b9A1uvx5EoqWUKG2/FmpEW0eVr6LaUFB9I4aCsCARa5mRCZJG\nfX3DHhHyYZOdwLSKIAyGGDECgYALEwkMQpIiFIyAZBXxcy74tPMHfKfWyZRG4ep1\nHCrQnQj3nxYRTuWx3VPicYTImeAH+CEqX3ouwy2pvXUjA0UIHpu6HutlYpacRRhZ\nDdcLIgWHj4wVmx6yyVcacXzHVAhRCCnLod+xS7d1sI9f7igsFHc+s7a3GOM3VWWB\nq2D5PQKBgQDY9eSb5pc5EYbPy0a/wKFLMNCVLXlDT8jRSC2UnhmcHbhi1IdUbn1j\nR+SuUgrAVNAKzJRY9wmF6Zt0pJ2YLFX7L8HaGyfzkib8kli2sXFonlQ4d0dTdcJo\nVGR1jTxfZQicdPcDPOLPpQz/rP31ZqdHtfaegTOxHebX7W2E5QvPZg==\n-----END RSA PRIVATE KEY-----\n"; + KeyPair.class.cast(new PEMReader(new StringReader(key)).readObject()); + } + /** * before class, as we need to ensure that the filter is threadsafe. * diff --git a/chef/src/test/java/org/jclouds/chef/functions/ParseKeyFromJsonTest.java b/chef/src/test/java/org/jclouds/chef/functions/ParseKeyFromJsonTest.java index b2cb960db5..c326f7f7d9 100644 --- a/chef/src/test/java/org/jclouds/chef/functions/ParseKeyFromJsonTest.java +++ b/chef/src/test/java/org/jclouds/chef/functions/ParseKeyFromJsonTest.java @@ -37,4 +37,11 @@ public class ParseKeyFromJsonTest { .toInputStream("{\n\"uri\": \"https://api.opscode.com/users/bobo\", \"private_key\": \"RSA_PRIVATE_KEY\",}"))), "RSA_PRIVATE_KEY"); } + + public void test2() { + String key = handler.apply(new HttpResponse(ParseKeyFromJsonTest.class + .getResourceAsStream("/newclient.txt"))); + assert key.startsWith("-----BEGIN RSA PRIVATE KEY-----\n"); + } + } diff --git a/chef/src/test/java/org/jclouds/chef/functions/ParseKeySetFromJsonTest.java b/chef/src/test/java/org/jclouds/chef/functions/ParseKeySetFromJsonTest.java new file mode 100644 index 0000000000..bf6b1528b5 --- /dev/null +++ b/chef/src/test/java/org/jclouds/chef/functions/ParseKeySetFromJsonTest.java @@ -0,0 +1,41 @@ +package org.jclouds.chef.functions; + +import static org.testng.Assert.assertEquals; + +import java.io.IOException; + +import org.jclouds.http.HttpResponse; +import org.jclouds.http.functions.config.ParserModule; +import org.jclouds.util.Utils; +import org.testng.annotations.BeforeTest; +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableSet; +import com.google.inject.Guice; +import com.google.inject.Injector; + +/** + * Tests behavior of {@code ParseKeySetFromJson} + * + * @author Adrian Cole + */ +@Test(groups = "unit", sequential = true, testName = "chef.ParseKeySetFromJsonTest") +public class ParseKeySetFromJsonTest { + + private ParseKeySetFromJson handler; + + @BeforeTest + protected void setUpInjector() throws IOException { + Injector injector = Guice.createInjector(new ParserModule()); + handler = injector.getInstance(ParseKeySetFromJson.class); + } + + public void testRegex() { + assertEquals( + handler + .apply(new HttpResponse( + Utils + .toInputStream("{\n\"opscode-validator\": \"https://api.opscode.com/...\", \"pimp-validator\": \"https://api.opscode.com/...\"}"))), + ImmutableSet.of("opscode-validator","pimp-validator")); + } +} diff --git a/chef/src/test/resources/log4j.xml b/chef/src/test/resources/log4j.xml index 6e388b9373..347362575f 100644 --- a/chef/src/test/resources/log4j.xml +++ b/chef/src/test/resources/log4j.xml @@ -1,32 +1,31 @@ - + 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. + ==================================================================== + --> - + + debug="false"> @@ -39,72 +38,80 @@ - + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + - - - - - - - - + + + + + + + + - - + + - - - - - - + + + + + + + \ No newline at end of file diff --git a/chef/src/test/resources/newclient.txt b/chef/src/test/resources/newclient.txt new file mode 100644 index 0000000000..c1e93687b7 --- /dev/null +++ b/chef/src/test/resources/newclient.txt @@ -0,0 +1 @@ +{"clientname":"adriancole-jcloudstest","private_key":"-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEAuzaE6azgUxwESX1rCGdJ5xpdrc1XC311bOGZBCE8NA+CpFh2\npopCBQwjpOnlgpdd/+C+TESl30ojauvVej9AbgJb30Jl7e7dEX4Brncnj03G+mo+\nG4osf7I2PA/6+9Ol7xamK9GL/cs8nOb17cRTWmhTRW7+3Rrli/s6wzqQXjGjWzgz\nthXv7FOPHA87UjJzrePBFta7+S8BxKCG2QaTxzNGytSAy8KBX8BUrSt5+X22QjEM\nQF3zA4TPtoWp/lcDRzCMdffMYoVPZzKqIeEFSexwvNlJ/qU6hbcyAxab1lYawjKU\nRgvPCflVYTIw6teHNqkyvTPX+lpIAVXigSVQXwIDAQABAoIBAHz81xvTSSjzaYFO\n9Gh13QcnuSdSEi0fo4f/zdLOBY2UVVo3nW9umskX46w0ZAAd4qn0d9gfdMZwjtjR\nfoLRO8i2VnPltnt1n64P/DtoXcb03EVPLQvh4feXGVJcMOpz0TKgYmyax+W3DE6M\ne+Az1JplUELo6crgLCSapA63SK85PEuWAcMUQg9s6MnzB/qXz95yJlzgjVMIJUyb\n9jFdq2s0gefTpK2cKeSYWQAFPd41Ea5v/3j0LN8qs/dImNnzxDXu+hi8+16/4PTK\npl+1bJXwE9YkWPdd39EfjVkk6q/HyFijK3VpHnOy7n3iaJTUKwBJLRsFrQ5Eor3U\nvNKyGXECgYEA3RZdFC6MRBAo76GKd9axbA0G9Bmct9pQT4B+od2AGGOdiYzYRWfF\nlyTgctY9dcfsX5DBQFUHNnYXMHHI0wHQk1m20UpFLa7IV3RWkW5JwYkbQHmeP4pn\np8GtJEXC+4PrT0Pc32acfWozArokUju7nLLazCPCDdfc8t9MPX1W230CgYEA2MbB\ndwdwLZx9zEqZ0MciRxjsOA30b6OYPOqMP1ADVeExPN8fHLCAQjwUExQa86+FMV4H\nOtu+DXlisp+TSTRQzpXMfGppupbK1j5cqz2sU37upKuz/uf0XyeyBLOi0y9/DMl5\njG2StLLIMawRqJRUuq/fyA/6oTzADNwoW6LjCgsCgYBGvCj7lAj8nc77HEwZG2+Y\ninJ3Ftq1V/vp88qQLzYUl4qHv7BSRGlLelj1ZOY1EMnnqYCq/IlaO14f+ceu+x2o\nh0OeooyPmSQwFuC7lvWyHhPCBSdEXRvc6HJk8Iz5u7NFoQjB0SqwVZIMhVGpncLg\n17h5J9emZjIi4p6Z7cgkYQKBgHt+/8in3Cif9qrj9S0TxVtrv2dPy+mt8ZUCqlOH\nad8LI9nh4v+dLfSN9YHI+nHJlL/DKatGdMeIV8obTvVtcHvAq3ZVyVYbggL8FB8a\nS4plzd7SUwDtdDKhkrFLBX/6lw7Z2P0/j0ySbaqetJCtsHeKqpp3P/mLen3ZDsTl\nzyJxAoGBAIxl1SGzu3lO3BQ5+EPaprNw3eN3nzG4WLQvnzZwpeAFS+E5pllMkIfs\nu01Vfv68NC4u6LFgdXSY1vQt6hiA5TNqQk0TyVfFAunbXgTekF6XqDPQUf1nq9aZ\nlMvo4vlaLDKBkhG5HJE/pIa0iB+RMZLS0GhxsIWerEDmYdHKM25o\n-----END RSA PRIVATE KEY-----\n","uri":"https://api.opscode.com/organizations/jclouds/clients/adriancole-jcloudstest","certificate":"-----BEGIN CERTIFICATE-----\nMIIClzCCAgCgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBnjELMAkGA1UEBhMCVVMx\nEzARBgNVBAgMCldhc2hpbmd0b24xEDAOBgNVBAcMB1NlYXR0bGUxFjAUBgNVBAoM\nDU9wc2NvZGUsIEluYy4xHDAaBgNVBAsME0NlcnRpZmljYXRlIFNlcnZpY2UxMjAw\nBgNVBAMMKW9wc2NvZGUuY29tL2VtYWlsQWRkcmVzcz1hdXRoQG9wc2NvZGUuY29t\nMB4XDTEwMDYwNDIzMzM0NloXDTIwMDYwMTIzMzM0NlowADCCASIwDQYJKoZIhvcN\nAQEBBQADggEPADCCAQoCggEBALs2hOms4FMcBEl9awhnSecaXa3NVwt9dWzhmQQh\nPDQPgqRYdqaKQgUMI6Tp5YKXXf/gvkxEpd9KI2rr1Xo/QG4CW99CZe3u3RF+Aa53\nJ49NxvpqPhuKLH+yNjwP+vvTpe8WpivRi/3LPJzm9e3EU1poU0Vu/t0a5Yv7OsM6\nkF4xo1s4M7YV7+xTjxwPO1Iyc63jwRbWu/kvAcSghtkGk8czRsrUgMvCgV/AVK0r\nefl9tkIxDEBd8wOEz7aFqf5XA0cwjHX3zGKFT2cyqiHhBUnscLzZSf6lOoW3MgMW\nm9ZWGsIylEYLzwn5VWEyMOrXhzapMr0z1/paSAFV4oElUF8CAwEAATANBgkqhkiG\n9w0BAQUFAAOBgQCTllbpWNagYjCiaU5UnjIFXn0YyNfZzqCh8SQ0Asj8MtksVbFG\nAErp03+Cb9a7GTdNE7fIyPsLTnGzFhqTwKN+3jIj4wgxhrbYXF73x1+rDRyHjJu7\na7gdTEYZqWiAHdW47vXj69W1dB5e4vNm1F29gOSL/x7BMAyjLFWbdbKw0w==\n-----END CERTIFICATE-----\n","orgname":"jclouds} \ No newline at end of file diff --git a/chef/temp-testng-customsuite.xml b/chef/temp-testng-customsuite.xml index 690648ff59..f9f6db5754 100644 --- a/chef/temp-testng-customsuite.xml +++ b/chef/temp-testng-customsuite.xml @@ -1,8 +1,8 @@ - + - +