Issue 191: started on chef api support

This commit is contained in:
Adrian Cole 2010-06-03 18:35:59 -07:00
parent 66d6326dd9
commit 7e12d36870
41 changed files with 2691 additions and 16 deletions

View File

@ -56,7 +56,6 @@ import org.jclouds.aws.filters.FormSigner;
import org.jclouds.aws.handlers.AWSClientErrorRetryHandler;
import org.jclouds.aws.handlers.AWSRedirectionRetryHandler;
import org.jclouds.aws.handlers.ParseAWSErrorFromXmlContent;
import org.jclouds.aws.util.RequestSigner;
import org.jclouds.concurrent.internal.SyncProxy;
import org.jclouds.date.DateService;
import org.jclouds.date.TimeStamp;
@ -70,6 +69,7 @@ import org.jclouds.net.IPSocket;
import org.jclouds.predicates.RetryablePredicate;
import org.jclouds.predicates.SocketOpen;
import org.jclouds.rest.ConfiguresRestClient;
import org.jclouds.rest.RequestSigner;
import org.jclouds.rest.RestClientFactory;
import com.google.common.base.Predicate;

View File

@ -42,7 +42,6 @@ import javax.ws.rs.core.HttpHeaders;
import org.jclouds.Constants;
import org.jclouds.aws.reference.AWSConstants;
import org.jclouds.aws.util.RequestSigner;
import org.jclouds.date.TimeStamp;
import org.jclouds.encryption.EncryptionService;
import org.jclouds.http.HttpException;
@ -51,6 +50,7 @@ import org.jclouds.http.HttpRequestFilter;
import org.jclouds.http.HttpUtils;
import org.jclouds.http.internal.SignatureWire;
import org.jclouds.logging.Logger;
import org.jclouds.rest.RequestSigner;
import org.jclouds.rest.internal.GeneratedHttpRequest;
import org.jclouds.rest.internal.RestAnnotationProcessor;
import org.jclouds.util.Utils;
@ -103,7 +103,7 @@ public class FormSigner implements HttpRequestFilter, RequestSigner {
addSigningParams(decodedParams);
validateParams(decodedParams);
String stringToSign = createStringToSign(request, decodedParams);
String signature = signString(stringToSign);
String signature = sign(stringToSign);
addSignature(decodedParams, signature);
setPayload(request, decodedParams);
HttpUtils.logRequest(signatureLog, request, "<<");
@ -153,7 +153,7 @@ public class FormSigner implements HttpRequestFilter, RequestSigner {
}
@VisibleForTesting
public String signString(String stringToSign) {
public String sign(String stringToSign) {
String signature;
try {
signature = encryptionService.hmacSha256Base64(stringToSign, secretKey.getBytes());

View File

@ -35,7 +35,6 @@ import org.jclouds.aws.s3.S3AsyncClient;
import org.jclouds.aws.s3.S3Client;
import org.jclouds.aws.s3.filters.RequestAuthorizeSignature;
import org.jclouds.aws.s3.reference.S3Constants;
import org.jclouds.aws.util.RequestSigner;
import org.jclouds.concurrent.ExpirableSupplier;
import org.jclouds.concurrent.internal.SyncProxy;
import org.jclouds.date.DateService;
@ -47,6 +46,7 @@ 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.RequestSigner;
import org.jclouds.rest.RestClientFactory;
import com.google.common.base.Supplier;

View File

@ -35,7 +35,6 @@ import javax.ws.rs.core.HttpHeaders;
import org.jclouds.Constants;
import org.jclouds.aws.s3.reference.S3Constants;
import org.jclouds.aws.util.RequestSigner;
import org.jclouds.date.TimeStamp;
import org.jclouds.encryption.EncryptionService;
import org.jclouds.http.HttpException;
@ -44,6 +43,7 @@ import org.jclouds.http.HttpRequestFilter;
import org.jclouds.http.HttpUtils;
import org.jclouds.http.internal.SignatureWire;
import org.jclouds.logging.Logger;
import org.jclouds.rest.RequestSigner;
import org.jclouds.util.Utils;
import com.google.common.annotations.VisibleForTesting;
@ -108,14 +108,14 @@ public class RequestAuthorizeSignature implements HttpRequestFilter, RequestSign
private void calculateAndReplaceAuthHeader(HttpRequest request, String toSign)
throws HttpException {
String signature = signString(toSign);
String signature = sign(toSign);
if (signatureWire.enabled())
signatureWire.input(Utils.toInputStream(signature));
request.getHeaders().replaceValues(HttpHeaders.AUTHORIZATION,
Collections.singletonList("AWS " + accessKey + ":" + signature));
}
public String signString(String toSign) {
public String sign(String toSign) {
String signature;
try {
signature = encryptionService.hmacSha1Base64(toSign, secretKey.getBytes());

View File

@ -34,7 +34,6 @@ import org.jclouds.aws.sqs.SQS;
import org.jclouds.aws.sqs.SQSAsyncClient;
import org.jclouds.aws.sqs.SQSClient;
import org.jclouds.aws.sqs.reference.SQSConstants;
import org.jclouds.aws.util.RequestSigner;
import org.jclouds.concurrent.internal.SyncProxy;
import org.jclouds.date.DateService;
import org.jclouds.date.TimeStamp;
@ -45,6 +44,7 @@ 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.RequestSigner;
import org.jclouds.rest.RestClientFactory;
import com.google.common.collect.ImmutableBiMap;

View File

@ -29,6 +29,7 @@ import org.jclouds.aws.xml.ErrorHandler;
import org.jclouds.http.HttpCommand;
import org.jclouds.http.HttpResponse;
import org.jclouds.http.functions.ParseSax;
import org.jclouds.rest.RequestSigner;
/**
* Needed to sign and verify requests and responses.
@ -51,7 +52,7 @@ public class AWSUtils {
AWSError error = (AWSError) factory.create(errorHandlerProvider.get()).parse(content);
if ("SignatureDoesNotMatch".equals(error.getCode())) {
error.setStringSigned(signer.createStringToSign(command.getRequest()));
error.setSignature(signer.signString(error.getStringSigned()));
error.setSignature(signer.sign(error.getStringSigned()));
}
return error;
}

9
chef/.gitignore vendored Normal file
View File

@ -0,0 +1,9 @@
# use glob syntax.
syntax: glob
target
.settings
.classpath
.project
jclouds-chef.iml
jclouds-chef.ipr
jclouds-chef.iws

97
chef/pom.xml Normal file
View File

@ -0,0 +1,97 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
$HeadURL$
$Revision$
$Date$
Copyright (C) 2009 Cloud Conscious, LLC <info@cloudconscious.com>
====================================================================
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.html
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.
====================================================================
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.jclouds</groupId>
<artifactId>jclouds-project</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath>../project/pom.xml</relativePath>
</parent>
<groupId>org.jclouds</groupId>
<artifactId>jclouds-chef</artifactId>
<name>jclouds Chef core</name>
<description>jclouds components to access Chef</description>
<scm>
<connection>scm:svn:http://jclouds.googlecode.com/svn/trunk/chef</connection>
<developerConnection>scm:svn:https://jclouds.googlecode.com/svn/trunk/chef</developerConnection>
<url>http://jclouds.googlecode.com/svn/trunk/chef</url>
</scm>
<!-- bootstrapping: need to fetch the project POM -->
<repositories>
<repository>
<id>jclouds-googlecode-deploy</id>
<url>http://jclouds.googlecode.com/svn/repo</url>
</repository>
<repository>
<id>jclouds-rimu-snapshots-nexus</id>
<url>http://jclouds.rimuhosting.com:8081/nexus/content/repositories/snapshots</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
<properties>
<jclouds.test.user>user</jclouds.test.user>
<jclouds.test.key>key</jclouds.test.key>
</properties>
<dependencies>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15</artifactId>
<version>1.44</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>jclouds-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>jclouds-core</artifactId>
<version>${project.version}</version>
<type>test-jar</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.14</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>jclouds-log4j</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,62 @@
/**
*
* Copyright (C) 2009 Cloud Conscious, LLC. <info@cloudconscious.com>
*
* ====================================================================
* 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. <info@cloudconscious.com>
*
* ====================================================================
* 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;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.inject.Qualifier;
/**
* Related to a Chef resource.
*
* @author Adrian Cole
*
*/
@Retention(value = RetentionPolicy.RUNTIME)
@Target(value = { ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Qualifier
public @interface Chef {
}

View File

@ -0,0 +1,136 @@
/**
*
* Copyright (C) 2010 Cloud Conscious, LLC. <info@cloudconscious.com>
*
* ====================================================================
* 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;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
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.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.ParseOrganizationFromJson;
import org.jclouds.chef.functions.ParseUserFromJson;
import org.jclouds.chef.functions.Username;
import org.jclouds.rest.annotations.BinderParam;
import org.jclouds.rest.annotations.Endpoint;
import org.jclouds.rest.annotations.ExceptionParser;
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.ReturnNullOnNotFoundOr404;
import com.google.common.util.concurrent.ListenableFuture;
/**
* Provides asynchronous access to Chef via their REST API.
* <p/>
*
* @see ChefClient
* @see <a href="TODO: insert URL of provider documentation" />
* @author Adrian Cole
*/
@Endpoint(Chef.class)
@RequestFilters(SignedHeaderAuth.class)
@Consumes(MediaType.APPLICATION_JSON)
public interface ChefAsyncClient {
/**
* @see ChefAsyncClient#createUser
*/
@POST
@Path("/users")
@ResponseParser(ParseKeyFromJson.class)
ListenableFuture<String> createUser(@BinderParam(BindToJsonPayload.class) User user);
/**
* @see ChefAsyncClient#updateUser
*/
@PUT
@Path("/users/{username}")
@ResponseParser(ParseUserFromJson.class)
ListenableFuture<User> updateUser(
@PathParam("username") @ParamParser(Username.class) @BinderParam(BindToJsonPayload.class) User user);
/**
* @see ChefAsyncClient#getUser
*/
@GET
@Path("/users/{username}")
@ExceptionParser(ReturnNullOnNotFoundOr404.class)
@ResponseParser(ParseUserFromJson.class)
ListenableFuture<User> getUser(@PathParam("username") String username);
/**
* @see ChefAsyncClient#deleteUser
*/
@DELETE
@Path("/users/{username}")
@ResponseParser(ParseUserFromJson.class)
ListenableFuture<User> deleteUser(@PathParam("username") String username);
/**
* @see ChefAsyncClient#createOrganization
*/
@POST
@Path("/organizations")
@ResponseParser(ParseKeyFromJson.class)
ListenableFuture<String> createOrganization(
@BinderParam(BindToJsonPayload.class) Organization org);
/**
* @see ChefAsyncClient#updateOrganization
*/
@PUT
@Path("/organizations/{orgname}")
@ResponseParser(ParseOrganizationFromJson.class)
ListenableFuture<Organization> updateOrganization(
@PathParam("orgname") @ParamParser(OrganizationName.class) @BinderParam(BindToJsonPayload.class) Organization org);
/**
* @see ChefAsyncClient#getOrganization
*/
@GET
@Path("/organizations/{orgname}")
@ExceptionParser(ReturnNullOnNotFoundOr404.class)
@ResponseParser(ParseOrganizationFromJson.class)
ListenableFuture<Organization> getOrganization(@PathParam("orgname") String orgname);
/**
* @see ChefAsyncClient#deleteOrganization
*/
@DELETE
@Path("/organizations/{orgname}")
@ResponseParser(ParseOrganizationFromJson.class)
ListenableFuture<Organization> deleteOrganization(@PathParam("orgname") String orgname);
}

View File

@ -0,0 +1,157 @@
/**
*
* Copyright (C) 2009 Cloud Conscious, LLC. <info@cloudconscious.com>
*
* ====================================================================
* 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. <info@cloudconscious.com>
*
* ====================================================================
* 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;
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.rest.AuthorizationException;
import org.jclouds.rest.ResourceNotFoundException;
/**
* Provides synchronous access to Chef.
* <p/>
*
* @see ChefAsyncClient
* @see <a href="TODO: insert URL of Chef documentation" />
* @author Adrian Cole
*/
@Timeout(duration = 4, timeUnit = TimeUnit.SECONDS)
public interface ChefClient {
/**
* creates a new user
*
* @return the private key of the user. You can then use this user name and private key to access
* the Opscode API.
* @throws AuthorizationException
* <p/>
* "401 Unauthorized" if the caller is not a recognized user.
* <p/>
* "403 Forbidden" if the caller is not authorized to create a user.
*/
String createUser(User user);
/**
* updates an existing user. Note: you must have update rights on the user.
*
* @throws AuthorizationException
* <p/>
* 401 Unauthorized if you are not a recognized user.
* <p/>
* 403 Forbidden if you do not have Update rights on the user.
* @throws ResourceNotFoundException
* if the user does not exist.
*/
User updateUser(User user);
/**
* retrieves an existing user. Note: you must have update rights on the user.
*
* @return null, if the user is not found
*/
User getUser(String username);
/**
* deletes an existing user. Note: you must have delete rights on the user.
*
* @return the last state of the User object in question. * @throws AuthorizationException
* <p/>
* 401 Unauthorized if you are not a recognized user.
* <p/>
* 403 Forbidden if you do not have Delete rights on the user.
* @throws ResourceNotFoundException
* <p/>
* 404 Not Found if the user does not exist.
*/
User deleteUser(String username);
/**
* creates a new organization
*
* @return the private key of the organization. You can then use this organization name and
* private key to access the Opscode API.
* @throws AuthorizationException
* <p/>
* "401 Unauthorized" if the caller is not a recognized organization.
* <p/>
* "403 Forbidden" if the caller is not authorized to create a organization.
*/
String createOrganization(Organization organization);
/**
* updates an existing organization. Note: you must have update rights on the organization.
*
* @throws AuthorizationException
* <p/>
* 401 Unauthorized if you are not a recognized organization.
* <p/>
* 403 Forbidden if you do not have Update rights on the organization.
* @throws ResourceNotFoundException
* if the organization does not exist.
*/
Organization updateOrganization(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);
/**
* deletes an existing organization. Note: you must have delete rights on the organization.
*
* @return the last state of the Organization object in question. * @throws
* AuthorizationException
* <p/>
* 401 Unauthorized if you are not a recognized organization.
* <p/>
* 403 Forbidden if you do not have Delete rights on the organization.
* @throws ResourceNotFoundException
* <p/>
* 404 Not Found if the organization does not exist.
*/
Organization deleteOrganization(String organizationname);
}

View File

@ -0,0 +1,65 @@
/**
*
* Copyright (C) 2010 Cloud Conscious, LLC. <info@cloudconscious.com>
*
* ====================================================================
* 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;
import static com.google.common.base.Preconditions.checkNotNull;
import java.util.List;
import java.util.Properties;
import org.jclouds.rest.RestContextBuilder;
import org.jclouds.chef.ChefAsyncClient;
import org.jclouds.chef.ChefClient;
import org.jclouds.chef.config.ChefContextModule;
import org.jclouds.chef.config.ChefRestClientModule;
import org.jclouds.chef.reference.ChefConstants;
import com.google.inject.Module;
import com.google.inject.TypeLiteral;
/**
*
* @author Adrian Cole
*/
public class ChefContextBuilder extends RestContextBuilder<ChefAsyncClient, ChefClient> {
public ChefContextBuilder(String providerName, Properties props) {
super(providerName, new TypeLiteral<ChefAsyncClient>() {
}, new TypeLiteral<ChefClient>() {
}, props);
checkNotNull(properties.getProperty(ChefConstants.PROPERTY_CHEF_USER_ID));
checkNotNull(properties.getProperty(ChefConstants.PROPERTY_CHEF_PRIVATE_KEY));
}
protected void addClientModule(List<Module> modules) {
modules.add(new ChefRestClientModule());
}
@Override
protected void addContextModule(String providerName, List<Module> modules) {
modules.add(new ChefContextModule(providerName));
}
}

View File

@ -0,0 +1,69 @@
/**
*
* Copyright (C) 2010 Cloud Conscious, LLC. <info@cloudconscious.com>
*
* ====================================================================
* 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;
import java.net.URI;
import java.util.Properties;
import org.jclouds.http.config.JavaUrlHttpCommandExecutorServiceModule;
import org.jclouds.logging.jdk.config.JDKLoggingModule;
import org.jclouds.rest.RestContext;
import com.google.inject.Module;
/**
* Creates {@link RestContext} for {@link ChefClient} instances based on the most commonly
* requested arguments.
* <p/>
* Note that Threadsafe objects will be bound as singletons to the Injector or Context provided.
* <p/>
* <p/>
* If no <code>Module</code>s are specified, the default {@link JDKLoggingModule logging} and
* {@link JavaUrlHttpCommandExecutorServiceModule http transports} will be installed.
*
* @author Adrian Cole
* @see RestContext
* @see ChefClient
* @see ChefAsyncClient
*/
public class ChefContextFactory {
public static RestContext<ChefAsyncClient, ChefClient> createContext(String user, String password,
Module... modules) {
return new ChefContextBuilder("chef", new ChefPropertiesBuilder(user, password).build())
.withModules(modules).buildContext();
}
public static RestContext<ChefAsyncClient, ChefClient> createContext(URI endpoint, String user, String password,
Module... modules) {
return new ChefContextBuilder("chef", new ChefPropertiesBuilder(user, password).withEndpoint(endpoint).build())
.withModules(modules).buildContext();
}
public static RestContext<ChefAsyncClient, ChefClient> createContext(Properties properties, Module... modules) {
return new ChefContextBuilder("chef", new ChefPropertiesBuilder(properties).build())
.withModules(modules).buildContext();
}
}

View File

@ -0,0 +1,70 @@
/**
*
* Copyright (C) 2010 Cloud Conscious, LLC. <info@cloudconscious.com>
*
* ====================================================================
* 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;
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_TIMESTAMP_INTERVAL;
import static org.jclouds.chef.reference.ChefConstants.PROPERTY_CHEF_USER_ID;
import java.net.URI;
import java.util.Properties;
import org.jclouds.PropertiesBuilder;
/**
* Builds properties used in Chef Clients
*
* @author Adrian Cole
*/
public class ChefPropertiesBuilder extends PropertiesBuilder {
@Override
protected Properties defaultProperties() {
Properties properties = super.defaultProperties();
properties.setProperty(PROPERTY_CHEF_ENDPOINT, "https://api.opscode.com");
properties.setProperty(PROPERTY_CHEF_TIMESTAMP_INTERVAL, "1");
return properties;
}
public ChefPropertiesBuilder(Properties properties) {
super(properties);
}
public ChefPropertiesBuilder(String id, String secret) {
super();
withCredentials(id, secret);
}
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"));
return this;
}
public ChefPropertiesBuilder withEndpoint(URI endpoint) {
properties.setProperty(PROPERTY_CHEF_ENDPOINT, checkNotNull(endpoint, "endpoint").toString());
return this;
}
}

View File

@ -0,0 +1,84 @@
/**
*
* Copyright (C) 2009 Cloud Conscious, LLC. <info@cloudconscious.com>
*
* ====================================================================
* 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. <info@cloudconscious.com>
*
* ====================================================================
* 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.config;
import java.net.URI;
import javax.inject.Named;
import javax.inject.Singleton;
import org.jclouds.lifecycle.Closer;
import org.jclouds.rest.RestContext;
import org.jclouds.rest.internal.RestContextImpl;
import org.jclouds.chef.Chef;
import org.jclouds.chef.ChefAsyncClient;
import org.jclouds.chef.ChefClient;
import org.jclouds.chef.reference.ChefConstants;
import com.google.inject.AbstractModule;
import com.google.inject.Provides;
/**
* Configures the Chef connection, including logging and http transport.
*
* @author Adrian Cole
*/
public class ChefContextModule extends AbstractModule {
public ChefContextModule(String providerName) {
// providerName ignored right now
}
@Override
protected void configure() {
// example of how to customize bindings
// bind(DateAdapter.class).to(CDateAdapter.class);
}
@Provides
@Singleton
RestContext<ChefAsyncClient, ChefClient> provideContext(Closer closer, ChefAsyncClient asyncApi,
ChefClient syncApi, @Chef URI endPoint, @Named(ChefConstants.PROPERTY_CHEF_USER_ID) String account) {
return new RestContextImpl<ChefAsyncClient, ChefClient>(closer, asyncApi, syncApi, endPoint, account);
}
}

View File

@ -0,0 +1,147 @@
/**
*
* Copyright (C) 2009 Cloud Conscious, LLC. <info@cloudconscious.com>
*
* ====================================================================
* 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. <info@cloudconscious.com>
*
* ====================================================================
* 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.config;
import java.io.IOException;
import java.io.StringReader;
import java.net.URI;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.Security;
import java.util.concurrent.TimeUnit;
import javax.inject.Named;
import javax.inject.Singleton;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.PEMReader;
import org.jclouds.chef.Chef;
import org.jclouds.chef.ChefAsyncClient;
import org.jclouds.chef.ChefClient;
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.RequiresHttp;
import org.jclouds.rest.ConfiguresRestClient;
import org.jclouds.rest.RestClientFactory;
import com.google.common.base.Supplier;
import com.google.inject.AbstractModule;
import com.google.inject.Provides;
/**
* Configures the Chef connection.
*
* @author Adrian Cole
*/
@RequiresHttp
@ConfiguresRestClient
public class ChefRestClientModule extends AbstractModule {
@Provides
@TimeStamp
protected String provideTimeStamp(@TimeStamp Supplier<String> cache) {
return cache.get();
}
/**
* borrowing concurrency code to ensure that caching takes place properly
*/
@Provides
@TimeStamp
Supplier<String> provideTimeStampCache(
@Named(ChefConstants.PROPERTY_CHEF_TIMESTAMP_INTERVAL) long seconds,
final DateService dateService) {
return new ExpirableSupplier<String>(new Supplier<String>() {
public String get() {
return dateService.iso8601DateFormat();
}
}, seconds, TimeUnit.SECONDS);
}
@Override
protected void configure() {
bindErrorHandlers();
bindRetryHandlers();
}
@Provides
@Singleton
public PrivateKey provideKey(@Named(ChefConstants.PROPERTY_CHEF_PRIVATE_KEY) String key)
throws IOException {
// TODO do this without adding a provider
Security.addProvider(new BouncyCastleProvider());
KeyPair pair = KeyPair.class.cast(new PEMReader(new StringReader(key)).readObject());
return pair.getPrivate();
}
@Provides
@Singleton
protected ChefAsyncClient provideClient(RestClientFactory factory) {
return factory.create(ChefAsyncClient.class);
}
@Provides
@Singleton
public ChefClient provideClient(ChefAsyncClient provider) throws IllegalArgumentException,
SecurityException, NoSuchMethodException {
return SyncProxy.create(ChefClient.class, provider);
}
@Provides
@Singleton
@Chef
protected URI provideURI(@Named(ChefConstants.PROPERTY_CHEF_ENDPOINT) String endpoint) {
return URI.create(endpoint);
}
protected void bindErrorHandlers() {
// TODO
}
protected void bindRetryHandlers() {
// TODO
}
}

View File

@ -0,0 +1,131 @@
/**
*
* Copyright (C) 2010 Cloud Conscious, LLC. <info@cloudconscious.com>
*
* ====================================================================
* 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.domain;
import com.google.gson.annotations.SerializedName;
/**
* User object.
*
* @author Adrian Cole
*/
public class Organization implements Comparable<Organization> {
private String name;
@SerializedName("full_name")
private String fullName;
@SerializedName("org_type")
private String orgType;
private String clientname;
public Organization(String name) {
super();
this.name = name;
}
public Organization() {
super();
}
@Override
public int compareTo(Organization o) {
return name.compareTo(o.name);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getFullName() {
return fullName;
}
public void setFullName(String fullName) {
this.fullName = fullName;
}
public String getOrgType() {
return orgType;
}
public void setOrgType(String orgType) {
this.orgType = orgType;
}
public String getClientname() {
return clientname;
}
public void setClientname(String clientname) {
this.clientname = clientname;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((clientname == null) ? 0 : clientname.hashCode());
result = prime * result + ((fullName == null) ? 0 : fullName.hashCode());
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + ((orgType == null) ? 0 : orgType.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Organization other = (Organization) obj;
if (clientname == null) {
if (other.clientname != null)
return false;
} else if (!clientname.equals(other.clientname))
return false;
if (fullName == null) {
if (other.fullName != null)
return false;
} else if (!fullName.equals(other.fullName))
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
if (orgType == null) {
if (other.orgType != null)
return false;
} else if (!orgType.equals(other.orgType))
return false;
return true;
}
@Override
public String toString() {
return "Organization [clientname=" + clientname + ", fullName=" + fullName + ", name=" + name
+ ", orgType=" + orgType + "]";
}
}

View File

@ -0,0 +1,162 @@
/**
*
* Copyright (C) 2010 Cloud Conscious, LLC. <info@cloudconscious.com>
*
* ====================================================================
* 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.domain;
import com.google.gson.annotations.SerializedName;
/**
* User object.
*
* @author Adrian Cole
*/
public class User implements Comparable<User> {
private String username;
@SerializedName("first_name")
private String firstName;
@SerializedName("middle_name")
private String middleName;
@SerializedName("last_name")
private String lastName;
@SerializedName("display_name")
private String displayName;
private String email;
public User(String username) {
this.username = username;
}
public User() {
super();
}
@Override
public int compareTo(User o) {
return username.compareTo(o.username);
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getMiddleName() {
return middleName;
}
public void setMiddleName(String middleName) {
this.middleName = middleName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getDisplayName() {
return displayName;
}
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((displayName == null) ? 0 : displayName.hashCode());
result = prime * result + ((email == null) ? 0 : email.hashCode());
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 + ((username == null) ? 0 : username.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
User other = (User) obj;
if (displayName == null) {
if (other.displayName != null)
return false;
} else if (!displayName.equals(other.displayName))
return false;
if (email == null) {
if (other.email != null)
return false;
} else if (!email.equals(other.email))
return false;
if (firstName == null) {
if (other.firstName != null)
return false;
} else if (!firstName.equals(other.firstName))
return false;
if (lastName == null) {
if (other.lastName != null)
return false;
} else if (!lastName.equals(other.lastName))
return false;
if (middleName == null) {
if (other.middleName != null)
return false;
} else if (!middleName.equals(other.middleName))
return false;
if (username == null) {
if (other.username != null)
return false;
} else if (!username.equals(other.username))
return false;
return true;
}
@Override
public String toString() {
return "User [username=" + username + ", displayName=" + displayName + ", firstName="
+ firstName + ", middleName=" + middleName + ", lastName=" + lastName + ", email="
+ email + "]";
}
}

View File

@ -0,0 +1,173 @@
/**
*
* Copyright (C) 2010 Cloud Conscious, LLC. <info@cloudconscious.com>
*
* ====================================================================
* 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.filters;
import static com.google.common.base.Preconditions.checkArgument;
import java.security.PrivateKey;
import java.util.Collections;
import javax.annotation.Resource;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Provider;
import javax.inject.Singleton;
import org.jclouds.Constants;
import org.jclouds.chef.reference.ChefConstants;
import org.jclouds.date.TimeStamp;
import org.jclouds.encryption.EncryptionService;
import org.jclouds.http.HttpException;
import org.jclouds.http.HttpRequest;
import org.jclouds.http.HttpRequestFilter;
import org.jclouds.http.HttpUtils;
import org.jclouds.http.Payload;
import org.jclouds.http.Payloads;
import org.jclouds.http.internal.SignatureWire;
import org.jclouds.logging.Logger;
import org.jclouds.util.Utils;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Splitter;
import com.google.common.base.Throwables;
import com.google.common.collect.Iterables;
/**
* Ported from mixlib-authentication in order to sign Chef requests.
*
* @see <a href= "http://github.com/opscode/mixlib-authentication" />
* @author Adrian Cole
*
*/
@Singleton
public class SignedHeaderAuth implements HttpRequestFilter {
public static final String SIGNING_DESCRIPTION = "version=1.0";
private final SignatureWire signatureWire;
private final String userId;
private final PrivateKey privateKey;
private final Provider<String> timeStampProvider;
private final EncryptionService encryptionService;
private final String emptyStringHash;
@Resource
@Named(Constants.LOGGER_SIGNATURE)
Logger signatureLog = Logger.NULL;
@Inject
public SignedHeaderAuth(SignatureWire signatureWire,
@Named(ChefConstants.PROPERTY_CHEF_USER_ID) String userId, PrivateKey privateKey,
@TimeStamp Provider<String> timeStampProvider, EncryptionService encryptionService) {
this.signatureWire = signatureWire;
this.userId = userId;
this.privateKey = privateKey;
this.timeStampProvider = timeStampProvider;
this.encryptionService = encryptionService;
this.emptyStringHash = hashBody(Payloads.newStringPayload(""));
}
public void filter(HttpRequest request) throws HttpException {
String contentHash = hashBody(request.getPayload());
request.getHeaders().replaceValues("X-Ops-Content-Hash",
Collections.singletonList(contentHash));
String timestamp = timeStampProvider.get();
String toSign = createStringToSign(request.getMethod(), hashPath(request.getEndpoint()
.getPath()), contentHash, timestamp);
request.getHeaders().replaceValues("X-Ops-Userid", Collections.singletonList(userId));
request.getHeaders().replaceValues("X-Ops-Sign",
Collections.singletonList(SIGNING_DESCRIPTION));
calculateAndReplaceAuthorizationHeaders(request, toSign);
request.getHeaders().replaceValues("X-Ops-Timestamp", Collections.singletonList(timestamp));
HttpUtils.logRequest(signatureLog, request, "<<");
}
@VisibleForTesting
void calculateAndReplaceAuthorizationHeaders(HttpRequest request, String toSign)
throws HttpException {
String signature = sign(toSign);
if (signatureWire.enabled())
signatureWire.input(Utils.toInputStream(signature));
String[] signatureLines = Iterables.toArray(Splitter.fixedLength(60).split(signature),
String.class);
for (int i = 0; i < signatureLines.length; i++) {
request.getHeaders().replaceValues("X-Ops-Authorization-" + (i + 1),
Collections.singletonList(signatureLines[i]));
}
}
public String createStringToSign(String httpMethod, String hashedPath, String contentHash,
String timestamp) {
return new StringBuilder().append("Method:").append(httpMethod).append("\n").append(
"Hashed Path:").append(hashedPath).append("\n").append("X-Ops-Content-Hash:")
.append(contentHash).append("\n").append("X-Ops-Timestamp:").append(timestamp)
.append("\n").append("X-Ops-UserId:").append(userId).toString();
}
@VisibleForTesting
String hashPath(String path) {
try {
return encryptionService.sha1Base64(canonicalPath(path));
} catch (Exception e) {
Throwables.propagateIfPossible(e);
throw new HttpException("error creating sigature for path: " + path, e);
}
}
/**
* Build the canonicalized path, which collapses multiple slashes (/) and removes a trailing
* slash unless the path is only "/"
*/
@VisibleForTesting
String canonicalPath(String path) {
path = path.replaceAll("\\/+", "/");
return path.endsWith("/") && path.length() > 1 ? path.substring(0, path.length() - 1) : path;
}
@VisibleForTesting
String hashBody(Payload payload) {
if (payload == null)
return emptyStringHash;
checkArgument(payload != null, "payload was null");
checkArgument(payload.isRepeatable(), "payload must be repeatable");
try {
return encryptionService.sha1Base64(Utils.toStringAndClose(payload.getContent()));
} catch (Exception e) {
Throwables.propagateIfPossible(e);
throw new HttpException("error creating sigature for payload: " + payload, e);
}
}
public String sign(String toSign) {
try {
byte[] encrypted = encryptionService.rsaPrivateEncrypt(toSign, privateKey);
return encryptionService.toBase64String(encrypted);
} catch (Exception e) {
throw new HttpException("error signing request", e);
}
}
}

View File

@ -0,0 +1,38 @@
/**
*
* Copyright (C) 2010 Cloud Conscious, LLC. <info@cloudconscious.com>
*
* ====================================================================
* 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.functions;
import javax.inject.Singleton;
import org.jclouds.chef.domain.Organization;
import com.google.common.base.Function;
/**
*
* @author Adrian Cole
*/
@Singleton
public class OrganizationName implements Function<Object, String> {
public String apply(Object from) {
return ((Organization) from).getName();
}
}

View File

@ -0,0 +1,70 @@
/**
*
* Copyright (C) 2010 Cloud Conscious, LLC. <info@cloudconscious.com>
*
* ====================================================================
* 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 ParseKeyFromJson implements Function<HttpResponse, String> {
Pattern pattern = Pattern.compile(".*private_key\": \"([^\"]+)\".*");
@Override
public String apply(HttpResponse response) {
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);
}
assert false : String.format("pattern: %s didn't match %s", pattern, in);
return null;
}
}

View File

@ -0,0 +1,74 @@
/**
*
* Copyright (C) 2009 Cloud Conscious, LLC. <info@cloudconscious.com>
*
* ====================================================================
* 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. <info@cloudconscious.com>
*
* ====================================================================
* 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 javax.inject.Inject;
import javax.inject.Singleton;
import org.jclouds.chef.domain.Organization;
import org.jclouds.http.functions.ParseJson;
import com.google.gson.Gson;
/**
* @author Adrian Cole
*/
@Singleton
public class ParseOrganizationFromJson extends ParseJson<Organization> {
@Inject
public ParseOrganizationFromJson(Gson gson) {
super(gson);
}
@Override
protected Organization apply(InputStream stream) {
try {
return gson.fromJson(new InputStreamReader(stream, "UTF-8"), Organization.class);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("jclouds requires UTF-8 encoding", e);
}
}
}

View File

@ -0,0 +1,75 @@
/**
*
* Copyright (C) 2009 Cloud Conscious, LLC. <info@cloudconscious.com>
*
* ====================================================================
* 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. <info@cloudconscious.com>
*
* ====================================================================
* 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 javax.inject.Inject;
import javax.inject.Singleton;
import org.jclouds.chef.domain.User;
import org.jclouds.http.functions.ParseJson;
import com.google.gson.Gson;
/**
* @author Adrian Cole
*/
@Singleton
public class ParseUserFromJson extends ParseJson<User> {
@Inject
public ParseUserFromJson(Gson gson) {
super(gson);
}
@Override
protected User apply(InputStream stream) {
try {
return gson.fromJson(new InputStreamReader(stream, "UTF-8"), User.class);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("jclouds requires UTF-8 encoding", e);
}
}
}

View File

@ -0,0 +1,38 @@
/**
*
* Copyright (C) 2010 Cloud Conscious, LLC. <info@cloudconscious.com>
*
* ====================================================================
* 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.functions;
import javax.inject.Singleton;
import org.jclouds.chef.domain.User;
import com.google.common.base.Function;
/**
*
* @author Adrian Cole
*/
@Singleton
public class Username implements Function<Object, String> {
public String apply(Object from) {
return ((User) from).getUsername();
}
}

View File

@ -0,0 +1,61 @@
/**
*
* Copyright (C) 2009 Cloud Conscious, LLC. <info@cloudconscious.com>
*
* ====================================================================
* 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. <info@cloudconscious.com>
*
* ====================================================================
* 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.reference;
/**
* Configuration properties and constants used in Chef connections.
*
* @author Adrian Cole
*/
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";
/**
* The PEM-encoded key
*/
public static final String PROPERTY_CHEF_PRIVATE_KEY = "jclouds.chef.private-key";
/**
* how often to refresh timestamps in seconds.
*/
public static final String PROPERTY_CHEF_TIMESTAMP_INTERVAL = "jclouds.chef.timestamp-interval";
}

View File

@ -0,0 +1,270 @@
/**
*
* Copyright (C) 2010 Cloud Conscious, LLC. <info@cloudconscious.com>
*
* ====================================================================
* 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;
import static org.testng.Assert.assertEquals;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Properties;
import org.jclouds.chef.config.ChefRestClientModule;
import org.jclouds.chef.domain.Organization;
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.ParseOrganizationFromJson;
import org.jclouds.chef.functions.ParseUserFromJson;
import org.jclouds.date.TimeStamp;
import org.jclouds.logging.config.NullLoggingModule;
import org.jclouds.rest.RestClientTest;
import org.jclouds.rest.functions.ReturnNullOnNotFoundOr404;
import org.jclouds.rest.internal.GeneratedHttpRequest;
import org.jclouds.rest.internal.RestAnnotationProcessor;
import org.jclouds.util.Jsr330;
import org.testng.annotations.Test;
import com.google.common.base.Supplier;
import com.google.common.collect.Iterables;
import com.google.inject.Module;
import com.google.inject.TypeLiteral;
/**
* Tests annotation parsing of {@code ChefAsyncClient}
*
* @author Adrian Cole
*/
@Test(groups = "unit", testName = "chef.ChefAsyncClientTest")
public class ChefAsyncClientTest extends RestClientTest<ChefAsyncClient> {
public void testCreateUser() throws SecurityException, NoSuchMethodException, IOException {
Method method = ChefAsyncClient.class.getMethod("createUser", User.class);
GeneratedHttpRequest<ChefAsyncClient> httpRequest = processor.createRequest(method, new User(
"myuser"));
assertRequestLineEquals(httpRequest, "POST https://api.opscode.com/users HTTP/1.1");
assertHeadersEqual(httpRequest,
"Accept: application/json\nContent-Length: 21\nContent-Type: application/json\n");
assertPayloadEquals(httpRequest, "{\"username\":\"myuser\"}");
// now make sure request filters apply by replaying
Iterables.getOnlyElement(httpRequest.getFilters()).filter(httpRequest);
Iterables.getOnlyElement(httpRequest.getFilters()).filter(httpRequest);
assertRequestLineEquals(httpRequest, "POST https://api.opscode.com/users HTTP/1.1");
assertHeadersEqual(
httpRequest,
new StringBuilder("Accept: application/json")
.append("\n")
.append("Content-Length: 21")
.append("\n")
.append("Content-Type: application/json")
.append("\n")
.append(
"X-Ops-Authorization-1: kfrkDpfgNU26k70R1vl1bEWk0Q0f9Fs/3kxOX7gHd7iNoJq03u7RrcrAOSgL")
.append("\n")
.append(
"X-Ops-Authorization-2: ETj5JNeCk18BmFkHMAbCA9hXVo1T4rlHCpbuzAzFlFxUGAT4wj8UoO7V886X")
.append("\n")
.append(
"X-Ops-Authorization-3: Kf8DvihP6ElthCNuu1xuhN0B4GEmWC9+ut7UMLe0L2T34VzkbCtuInGbf42/")
.append("\n")
.append(
"X-Ops-Authorization-4: G7iu94/xFOT1gN9cex4pNyTnRCHzob4JVU1usxt/2g5grN2SyYwRS5+4MNLN")
.append("\n")
.append(
"X-Ops-Authorization-5: WY/iLUPb/9dwtiIQsnUOXqDrs28zNswZulQW4AzYRd7MczJVKU4y4+4XRcB4")
.append("\n")
.append(
"X-Ops-Authorization-6: 2+BFLT5o6P6G0D+eCu3zSuaqEJRucPJPaDGWdKIMag==")
.append("\n").append("X-Ops-Content-Hash: yLHOxvgIEtNw5UrZDxslOeMw1gw=")
.append("\n").append("X-Ops-Sign: version=1.0").append("\n").append(
"X-Ops-Timestamp: timestamp").append("\n").append(
"X-Ops-Userid: user").append("\n").toString());
assertPayloadEquals(httpRequest, "{\"username\":\"myuser\"}");
assertResponseParserClassEquals(method, httpRequest, ParseKeyFromJson.class);
assertSaxResponseParserClassEquals(method, null);
assertExceptionParserClassEquals(method, null);
checkFilters(httpRequest);
}
public void testUpdateUser() throws SecurityException, NoSuchMethodException, IOException {
Method method = ChefAsyncClient.class.getMethod("updateUser", User.class);
GeneratedHttpRequest<ChefAsyncClient> httpRequest = processor.createRequest(method, new User(
"myuser"));
assertRequestLineEquals(httpRequest, "PUT https://api.opscode.com/users/myuser HTTP/1.1");
assertHeadersEqual(httpRequest,
"Accept: application/json\nContent-Length: 21\nContent-Type: application/json\n");
assertPayloadEquals(httpRequest, "{\"username\":\"myuser\"}");
assertResponseParserClassEquals(method, httpRequest, ParseUserFromJson.class);
assertSaxResponseParserClassEquals(method, null);
assertExceptionParserClassEquals(method, null);
checkFilters(httpRequest);
}
public void testGetUser() throws SecurityException, NoSuchMethodException, IOException {
Method method = ChefAsyncClient.class.getMethod("getUser", String.class);
GeneratedHttpRequest<ChefAsyncClient> httpRequest = processor.createRequest(method, "myuser");
assertRequestLineEquals(httpRequest, "GET https://api.opscode.com/users/myuser HTTP/1.1");
assertHeadersEqual(httpRequest, "Accept: application/json\n");
assertPayloadEquals(httpRequest, null);
assertResponseParserClassEquals(method, httpRequest, ParseUserFromJson.class);
assertSaxResponseParserClassEquals(method, null);
assertExceptionParserClassEquals(method, ReturnNullOnNotFoundOr404.class);
checkFilters(httpRequest);
}
public void testDeleteUser() throws SecurityException, NoSuchMethodException, IOException {
Method method = ChefAsyncClient.class.getMethod("deleteUser", String.class);
GeneratedHttpRequest<ChefAsyncClient> httpRequest = processor.createRequest(method, "myuser");
assertRequestLineEquals(httpRequest, "DELETE https://api.opscode.com/users/myuser HTTP/1.1");
assertHeadersEqual(httpRequest, "Accept: application/json\n");
assertPayloadEquals(httpRequest, null);
assertResponseParserClassEquals(method, httpRequest, ParseUserFromJson.class);
assertSaxResponseParserClassEquals(method, null);
assertExceptionParserClassEquals(method, null);
checkFilters(httpRequest);
}
public void testCreateOrganization() throws SecurityException, NoSuchMethodException,
IOException {
Method method = ChefAsyncClient.class.getMethod("createOrganization", Organization.class);
GeneratedHttpRequest<ChefAsyncClient> httpRequest = processor.createRequest(method,
new Organization("myorganization"));
assertRequestLineEquals(httpRequest, "POST https://api.opscode.com/organizations HTTP/1.1");
assertHeadersEqual(httpRequest,
"Accept: application/json\nContent-Length: 25\nContent-Type: application/json\n");
assertPayloadEquals(httpRequest, "{\"name\":\"myorganization\"}");
assertResponseParserClassEquals(method, httpRequest, ParseKeyFromJson.class);
assertSaxResponseParserClassEquals(method, null);
assertExceptionParserClassEquals(method, null);
checkFilters(httpRequest);
}
public void testUpdateOrganization() throws SecurityException, NoSuchMethodException,
IOException {
Method method = ChefAsyncClient.class.getMethod("updateOrganization", Organization.class);
GeneratedHttpRequest<ChefAsyncClient> httpRequest = processor.createRequest(method,
new Organization("myorganization"));
assertRequestLineEquals(httpRequest,
"PUT https://api.opscode.com/organizations/myorganization HTTP/1.1");
assertHeadersEqual(httpRequest,
"Accept: application/json\nContent-Length: 25\nContent-Type: application/json\n");
assertPayloadEquals(httpRequest, "{\"name\":\"myorganization\"}");
assertResponseParserClassEquals(method, httpRequest, ParseOrganizationFromJson.class);
assertSaxResponseParserClassEquals(method, null);
assertExceptionParserClassEquals(method, null);
checkFilters(httpRequest);
}
public void testGetOrganization() throws SecurityException, NoSuchMethodException, IOException {
Method method = ChefAsyncClient.class.getMethod("getOrganization", String.class);
GeneratedHttpRequest<ChefAsyncClient> httpRequest = processor.createRequest(method,
"myorganization");
assertRequestLineEquals(httpRequest,
"GET https://api.opscode.com/organizations/myorganization HTTP/1.1");
assertHeadersEqual(httpRequest, "Accept: application/json\n");
assertPayloadEquals(httpRequest, null);
assertResponseParserClassEquals(method, httpRequest, ParseOrganizationFromJson.class);
assertSaxResponseParserClassEquals(method, null);
assertExceptionParserClassEquals(method, ReturnNullOnNotFoundOr404.class);
checkFilters(httpRequest);
}
public void testDeleteOrganization() throws SecurityException, NoSuchMethodException,
IOException {
Method method = ChefAsyncClient.class.getMethod("deleteOrganization", String.class);
GeneratedHttpRequest<ChefAsyncClient> httpRequest = processor.createRequest(method,
"myorganization");
assertRequestLineEquals(httpRequest,
"DELETE https://api.opscode.com/organizations/myorganization HTTP/1.1");
assertHeadersEqual(httpRequest, "Accept: application/json\n");
assertPayloadEquals(httpRequest, null);
assertResponseParserClassEquals(method, httpRequest, ParseOrganizationFromJson.class);
assertSaxResponseParserClassEquals(method, null);
assertExceptionParserClassEquals(method, null);
checkFilters(httpRequest);
}
@Override
protected void checkFilters(GeneratedHttpRequest<ChefAsyncClient> httpRequest) {
assertEquals(httpRequest.getFilters().size(), 1);
assertEquals(httpRequest.getFilters().get(0).getClass(), SignedHeaderAuth.class);
}
@Override
protected TypeLiteral<RestAnnotationProcessor<ChefAsyncClient>> createTypeLiteral() {
return new TypeLiteral<RestAnnotationProcessor<ChefAsyncClient>>() {
};
}
@Override
protected Module createModule() {
return new ChefRestClientModule() {
@Override
protected void configure() {
Jsr330.bindProperties(binder(), new ChefPropertiesBuilder(new Properties())
.withCredentials("user", SignedHeaderAuthTest.PRIVATE_KEY).build());
install(new NullLoggingModule());
}
@Override
protected String provideTimeStamp(@TimeStamp Supplier<String> cache) {
return "timestamp";
}
};
}
}

View File

@ -0,0 +1,105 @@
/**
*
* Copyright (C) 2010 Cloud Conscious, LLC. <info@cloudconscious.com>
*
* ====================================================================
* 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;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.testng.Assert.assertNotNull;
import java.io.File;
import java.io.IOException;
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.testng.annotations.Test;
import com.google.common.base.Charsets;
import com.google.common.io.Files;
/**
* Tests behavior of {@code ChefClient}
*
* @author Adrian Cole
*/
@Test(groups = "live", testName = "chef.ChefClientLiveTest")
public class ChefClientLiveTest {
private ChefClient client;
private String username;
@BeforeGroups(groups = { "live" })
public void setupClient() throws IOException {
username = 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();
}
@Test(enabled = false)
public void testGetUser() throws Exception {
User user = client.getUser(username);
assertNotNull(user);
}
@Test(enabled = false)
public void testCreateUser() throws Exception {
// TODO
}
@Test(enabled = false)
public void testUpdateUser() throws Exception {
// TODO
}
@Test(enabled = false)
public void testDeleteUser() throws Exception {
// TODO
}
@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 {
// TODO
}
@Test(enabled = false)
public void testDeleteOrganization() throws Exception {
// TODO
}
}

View File

@ -0,0 +1,234 @@
/**
*
* Copyright (C) 2010 Cloud Conscious, LLC. <info@cloudconscious.com>
*
* ====================================================================
* 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.filters;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.util.concurrent.MoreExecutors.sameThreadExecutor;
import static org.testng.Assert.assertEquals;
import java.io.IOException;
import java.io.StringReader;
import java.net.URI;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.Security;
import javax.inject.Provider;
import javax.ws.rs.HttpMethod;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.UriBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.PEMReader;
import org.jboss.resteasy.specimpl.UriBuilderImpl;
import org.jclouds.chef.ChefPropertiesBuilder;
import org.jclouds.chef.config.ChefRestClientModule;
import org.jclouds.concurrent.config.ExecutorServiceModule;
import org.jclouds.encryption.EncryptionService;
import org.jclouds.http.HttpRequest;
import org.jclouds.http.functions.config.ParserModule;
import org.jclouds.http.internal.SignatureWire;
import org.jclouds.util.Jsr330;
import org.jclouds.util.Utils;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import com.google.common.base.Joiner;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.Multimap;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
/**
*
* @author Adrian Cole
*/
@Test(groups = "unit", testName = "chef.SignedHeaderAuthTest")
public class SignedHeaderAuthTest {
public static final String USER_ID = "spec-user";
public static final String BODY = "Spec Body";
// Base64.encode64(Digest::SHA1.digest("Spec Body")).chomp
public static final String HASHED_BODY = "DFteJZPVv6WKdQmMqZUQUumUyRs=";
public static final String TIMESTAMP_ISO8601 = "2009-01-01T12:00:00Z";
public static final String PATH = "/organizations/clownco";
// Base64.encode64(Digest::SHA1.digest("/organizations/clownco")).chomp
public static final String HASHED_CANONICAL_PATH = "YtBWDn1blGGuFIuKksdwXzHU9oE=";
public static final String REQUESTING_ACTOR_ID = "c0f8a68c52bffa1020222a56b23cccfa";
// Content hash is ???TODO
public static final String X_OPS_CONTENT_HASH = "DFteJZPVv6WKdQmMqZUQUumUyRs=";
public static final String[] X_OPS_AUTHORIZATION_LINES = new String[] {
"jVHrNniWzpbez/eGWjFnO6lINRIuKOg40ZTIQudcFe47Z9e/HvrszfVXlKG4",
"NMzYZgyooSvU85qkIUmKuCqgG2AIlvYa2Q/2ctrMhoaHhLOCWWoqYNMaEqPc",
"3tKHE+CfvP+WuPdWk4jv4wpIkAz6ZLxToxcGhXmZbXpk56YTmqgBW2cbbw4O",
"IWPZDHSiPcw//AYNgW1CCDptt+UFuaFYbtqZegcBd2n/jzcWODA7zL4KWEUy",
"9q4rlh/+1tBReg60QdsmDRsw/cdO1GZrKtuCwbuD4+nbRdVBKv72rqHX9cu0",
"utju9jzczCyB+sSAQWrxSsXB/b8vV2qs0l4VD2ML+w==" };
// We expect Mixlib::Authentication::SignedHeaderAuth//sign to return this
// if passed the BODY above.
public static final Multimap<String, String> EXPECTED_SIGN_RESULT = ImmutableMultimap
.<String, String> builder().put("X-Ops-Content-Hash", X_OPS_CONTENT_HASH).put(
"X-Ops-Userid", USER_ID).put("X-Ops-Sign", "version=1.0").put(
"X-Ops-Authorization-1", X_OPS_AUTHORIZATION_LINES[0]).put(
"X-Ops-Authorization-2", X_OPS_AUTHORIZATION_LINES[1]).put(
"X-Ops-Authorization-3", X_OPS_AUTHORIZATION_LINES[2]).put(
"X-Ops-Authorization-4", X_OPS_AUTHORIZATION_LINES[3]).put(
"X-Ops-Authorization-5", X_OPS_AUTHORIZATION_LINES[4]).put(
"X-Ops-Authorization-6", X_OPS_AUTHORIZATION_LINES[5]).put("X-Ops-Timestamp",
TIMESTAMP_ISO8601).build();
// Content hash for empty string
public static final String X_OPS_CONTENT_HASH_EMPTY = "2jmj7l5rSw0yVb/vlWAYkK/YBwk=";
public static final Multimap<String, String> EXPECTED_SIGN_RESULT_EMPTY = ImmutableMultimap
.<String, String> 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();
public static String PUBLIC_KEY;
public static String PRIVATE_KEY;
static {
try {
PUBLIC_KEY = Utils.toStringAndClose(SignedHeaderAuthTest.class
.getResourceAsStream("/pubkey.txt"));
PRIVATE_KEY = Utils.toStringAndClose(SignedHeaderAuthTest.class
.getResourceAsStream("/privkey.txt"));
} catch (IOException e) {
Throwables.propagate(e);
}
}
@Test
void canonicalizedPathRemovesMultipleSlashes() {
assertEquals(signing_obj.canonicalPath("///"), "/");
}
@Test
void canonicalizedPathRemovesTrailingSlash() {
assertEquals(signing_obj.canonicalPath("/path/"), "/path");
}
@Test
void shouldGenerateTheCorrectStringToSignAndSignature() {
URI host = URI.create("http://localhost/" + PATH);
HttpRequest request = new HttpRequest(HttpMethod.POST, host);
request.setPayload(BODY);
String expected_string_to_sign = new StringBuilder().append("Method:POST").append("\n")
.append("Hashed Path:").append(HASHED_CANONICAL_PATH).append("\n").append(
"X-Ops-Content-Hash:").append(HASHED_BODY).append("\n").append(
"X-Ops-Timestamp:").append(TIMESTAMP_ISO8601).append("\n").append(
"X-Ops-UserId:").append(USER_ID).toString();
assertEquals(signing_obj.createStringToSign("POST", HASHED_CANONICAL_PATH, HASHED_BODY,
TIMESTAMP_ISO8601), expected_string_to_sign);
assertEquals(signing_obj.sign(expected_string_to_sign), Joiner.on("").join(
X_OPS_AUTHORIZATION_LINES));
signing_obj.filter(request);
Multimap<String, String> headersWithoutContentLength = LinkedHashMultimap.create(request
.getHeaders());
headersWithoutContentLength.removeAll(HttpHeaders.CONTENT_LENGTH);
assertEquals(headersWithoutContentLength.values(), EXPECTED_SIGN_RESULT.values());
}
@Test
void shouldGenerateTheCorrectStringToSignAndSignatureWithNoBody() {
URI host = URI.create("http://localhost/" + PATH);
HttpRequest request = new HttpRequest(HttpMethod.DELETE, host);
signing_obj.filter(request);
Multimap<String, String> headersWithoutContentLength = LinkedHashMultimap.create(request
.getHeaders());
assertEquals(headersWithoutContentLength.entries(), EXPECTED_SIGN_RESULT_EMPTY.entries());
}
@Test
void shouldNotChokeWhenSigningARequestForAResourceWithALongName() {
StringBuilder path = new StringBuilder("nodes/");
for (int i = 0; i < 100; i++)
path.append('A');
URI host = URI.create("http://localhost/" + path.toString());
HttpRequest request = new HttpRequest(HttpMethod.PUT, host);
request.setPayload(BODY);
signing_obj.filter(request);
}
private SignedHeaderAuth signing_obj;
private EncryptionService encryptionService;
/**
* before class, as we need to ensure that the filter is threadsafe.
*
* @throws IOException
*
*/
@BeforeClass
protected void createFilter() throws IOException {
Injector injector = Guice.createInjector(new ChefRestClientModule(),
new ExecutorServiceModule(sameThreadExecutor(), sameThreadExecutor()),
new ParserModule(), new AbstractModule() {
protected void configure() {
Jsr330.bindProperties(binder(), checkNotNull(
new ChefPropertiesBuilder("foo", "bar")).build());
bind(UriBuilder.class).to(UriBuilderImpl.class);
}
});
encryptionService = injector.getInstance(EncryptionService.class);
Security.addProvider(new BouncyCastleProvider());
KeyPair pair = KeyPair.class.cast(new PEMReader(new StringReader(PRIVATE_KEY)).readObject());
PrivateKey privateKey = pair.getPrivate();
signing_obj = new SignedHeaderAuth(new SignatureWire(), USER_ID, privateKey,
new Provider<String>() {
@Override
public String get() {
return TIMESTAMP_ISO8601;
}
}, encryptionService);
}
}

View File

@ -0,0 +1,40 @@
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.inject.Guice;
import com.google.inject.Injector;
/**
* Tests behavior of {@code ParseKeyFromJson}
*
* @author Adrian Cole
*/
@Test(groups = "unit", sequential = true, testName = "chef.ParseKeyFromJsonTest")
public class ParseKeyFromJsonTest {
private ParseKeyFromJson handler;
@BeforeTest
protected void setUpInjector() throws IOException {
Injector injector = Guice.createInjector(new ParserModule());
handler = injector.getInstance(ParseKeyFromJson.class);
}
public void testRegex() {
assertEquals(
handler
.apply(new HttpResponse(
Utils
.toInputStream("{\n\"uri\": \"https://api.opscode.com/users/bobo\", \"private_key\": \"RSA_PRIVATE_KEY\",}"))),
"RSA_PRIVATE_KEY");
}
}

View File

@ -0,0 +1,45 @@
package org.jclouds.chef.functions;
import static org.testng.Assert.assertEquals;
import java.io.IOException;
import org.jclouds.chef.domain.Organization;
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.inject.Guice;
import com.google.inject.Injector;
/**
* Tests behavior of {@code ParseOrganizationFromJson}
*
* @author Adrian Cole
*/
@Test(groups = "unit", sequential = true, testName = "chef.ParseOrganizationFromJsonTest")
public class ParseOrganizationFromJsonTest {
private ParseOrganizationFromJson handler;
@BeforeTest
protected void setUpInjector() throws IOException {
Injector injector = Guice.createInjector(new ParserModule());
handler = injector.getInstance(ParseOrganizationFromJson.class);
}
public void test() {
Organization org = new Organization();
org.setName("opscode");
org.setFullName("Opscode, Inc.");
org.setOrgType("Business");
org.setClientname("opscode-validator");
String toParse = "{\"name\": \"opscode\",\"full_name\": \"Opscode, Inc.\", \"org_type\": \"Business\",\"clientname\": \"opscode-validator\" }";
assertEquals(handler.apply(new HttpResponse(Utils.toInputStream(toParse))), org);
}
}

View File

@ -0,0 +1,47 @@
package org.jclouds.chef.functions;
import static org.testng.Assert.assertEquals;
import java.io.IOException;
import org.jclouds.chef.domain.User;
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.inject.Guice;
import com.google.inject.Injector;
/**
* Tests behavior of {@code ParseUserFromJson}
*
* @author Adrian Cole
*/
@Test(groups = "unit", sequential = true, testName = "chef.ParseUserFromJsonTest")
public class ParseUserFromJsonTest {
private ParseUserFromJson handler;
@BeforeTest
protected void setUpInjector() throws IOException {
Injector injector = Guice.createInjector(new ParserModule());
handler = injector.getInstance(ParseUserFromJson.class);
}
public void test() {
User user = new User();
user.setUsername("bobo");
user.setFirstName("Bobo");
user.setMiddleName("Tiberion");
user.setLastName("Clown");
user.setDisplayName("Bobo T. Clown");
user.setEmail("bobo@clownco.com");
String toParse = "{\n\"username\": \"bobo\",\n\"first_name\": \"Bobo\",\n\"middle_name\": \"Tiberion\",\n\"last_name\": \"Clown\",\n\"display_name\": \"Bobo T. Clown\",\n\"email\": \"bobo@clownco.com\" \n}";
assertEquals(handler.apply(new HttpResponse(Utils.toInputStream(toParse))), user);
}
}

View File

@ -0,0 +1,110 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (C) 2009 Cloud Conscious, LLC. <info@cloudconscious.com>
====================================================================
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.
====================================================================
-->
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<!--
For more configuration infromation and examples see the Apache Log4j
website: http://logging.apache.org/log4j/
-->
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/"
debug="false">
<!-- A time/date based rolling appender -->
<appender name="WIREFILE" class="org.apache.log4j.DailyRollingFileAppender">
<param name="File" value="target/test-data/jclouds-wire.log" />
<param name="Append" value="true" />
<!-- Rollover at midnight each day -->
<param name="DatePattern" value="'.'yyyy-MM-dd" />
<param name="Threshold" value="TRACE" />
<layout class="org.apache.log4j.PatternLayout">
<!-- The default pattern: Date Priority [Category] Message${symbol_escape}n -->
<param name="ConversionPattern" value="%d %-5p [%c] (%t) %m%n" />
<!--
The full pattern: Date MS Priority [Category] (Thread:NDC) Message${symbol_escape}n
<param name="ConversionPattern" value="%d %-5r %-5p [%c] (%t:%x)
%m%n"/>
-->
</layout>
</appender>
<!-- A time/date based rolling appender -->
<appender name="FILE" class="org.apache.log4j.DailyRollingFileAppender">
<param name="File" value="target/test-data/jclouds.log" />
<param name="Append" value="true" />
<!-- Rollover at midnight each day -->
<param name="DatePattern" value="'.'yyyy-MM-dd" />
<param name="Threshold" value="TRACE" />
<layout class="org.apache.log4j.PatternLayout">
<!-- The default pattern: Date Priority [Category] Message${symbol_escape}n -->
<param name="ConversionPattern" value="%d %-5p [%c] (%t) %m%n" />
<!--
The full pattern: Date MS Priority [Category] (Thread:NDC) Message${symbol_escape}n
<param name="ConversionPattern" value="%d %-5r %-5p [%c] (%t:%x)
%m%n"/>
-->
</layout>
</appender>
<appender name="ASYNC" class="org.apache.log4j.AsyncAppender">
<appender-ref ref="FILE" />
</appender>
<appender name="ASYNCWIRE" class="org.apache.log4j.AsyncAppender">
<appender-ref ref="WIREFILE" />
</appender>
<!-- ================ -->
<!-- Limit categories -->
<!-- ================ -->
<category name="org.jclouds">
<priority value="DEBUG" />
<appender-ref ref="ASYNC" />
</category>
<category name="jclouds.headers">
<priority value="DEBUG" />
<appender-ref ref="ASYNCWIRE" />
</category><!--
<category name="jclouds.wire">
<priority value="DEBUG" />
<appender-ref ref="ASYNCWIRE" />
</category>
--><!-- ======================= -->
<!-- Setup the Root category -->
<!-- ======================= -->
<root>
<priority value="WARN" />
</root>
</log4j:configuration>

View File

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA0ueqo76MXuP6XqZBILFziH/9AI7C6PaN5W0dSvkr9yInyGHS
z/IR1+4tqvP2qlfKVKI4CP6BFH251Ft9qMUBuAsnlAVQ1z0exDtIFFOyQCdR7iXm
jBIWMSS4buBwRQXwDK7id1OxtU23qVJv+xwEV0IzaaSJmaGLIbvRBD+qatfUuQJB
MU/04DdJIwvLtZBYdC2219m5dUBQaa4bimL+YN9EcsDzD9h9UxQo5ReK7b3cNMzJ
BKJWLzFBcJuePMzAnLFktr/RufX4wpXe6XJxoVPaHo72GorLkwnQ0HYMTY8rehT4
mDi1FI969LHCFFaFHSAaRnwdXaQkJmSfcxzCYQIDAQABAoIBAQCW3I4sKN5B9jOe
xq/pkeWBq4OvhW8Ys1yW0zFT8t6nHbB1XrwscQygd8gE9BPqj3e0iIEqtdphbPmj
VHqTYbC0FI6QDClifV7noTwTBjeIOlgZ0NSUN0/WgVzIOxUz2mZ2vBZUovKILPqG
TOi7J7RXMoySMdcXpP1f+PgvYNcnKsT72UcWaSXEV8/zo+Zm/qdGPVWwJonri5Mp
DVm5EQSENBiRyt028rU6ElXORNmoQpVjDVqZ1gipzXkifdjGyENw2rt4V/iKYD7V
5iqXOsvP6Cemf4gbrjunAgDG08S00kiUgvVWcdXW+dlsR2nCvH4DOEe3AYYh/aH8
DxEE7FbtAoGBAPcNO8fJ56mNw0ow4Qg38C+Zss/afhBOCfX4O/SZKv/roRn5+gRM
KRJYSVXNnsjPI1plzqR4OCyOrjAhtuvL4a0DinDzf1+fiztyNohwYsW1vYmqn3ti
EN0GhSgE7ppZjqvLQ3f3LUTxynhA0U+k9wflb4irIlViTUlCsOPkrNJDAoGBANqL
Q+vvuGSsmRLU/Cenjy+Mjj6+QENg51dz34o8JKuVKIPKU8pNnyeLa5fat0qD2MHm
OB9opeQOcw0dStodxr6DB3wi83bpjeU6BWUGITNiWEaZEBrQ0aiqNJJKrrHm8fAZ
9o4l4oHc4hI0kYVYYDuxtKuVJrzZiEapTwoOcYiLAoGBAI/EWbeIHZIj9zOjgjEA
LHvm25HtulLOtyk2jd1njQhlHNk7CW2azIPqcLLH99EwCYi/miNH+pijZ2aHGCXb
/bZrSxM0ADmrZKDxdB6uGCyp+GS2sBxjEyEsfCyvwhJ8b3Q100tqwiNO+d5FCglp
HICx2dgUjuRVUliBwOK93nx1AoGAUI8RhIEjOYkeDAESyhNMBr0LGjnLOosX+/as
qiotYkpjWuFULbibOFp+WMW41vDvD9qrSXir3fstkeIAW5KqVkO6mJnRoT3Knnra
zjiKOITCAZQeiaP8BO5o3pxE9TMqb9VCO3ffnPstIoTaN4syPg7tiGo8k1SklVeH
2S8lzq0CgYAKG2fljIYWQvGH628rp4ZcXS4hWmYohOxsnl1YrszbJ+hzR+IQOhGl
YlkUQYXhy9JixmUUKtH+NXkKX7Lyc8XYw5ETr7JBT3ifs+G7HruDjVG78EJVojbd
8uLA+DdQm5mg4vd1GTiSK65q/3EeoBlUaVor3HhLFki+i9qpT8CBsg==
-----END RSA PRIVATE KEY-----

View File

@ -0,0 +1,9 @@
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0ueqo76MXuP6XqZBILFz
iH/9AI7C6PaN5W0dSvkr9yInyGHSz/IR1+4tqvP2qlfKVKI4CP6BFH251Ft9qMUB
uAsnlAVQ1z0exDtIFFOyQCdR7iXmjBIWMSS4buBwRQXwDK7id1OxtU23qVJv+xwE
V0IzaaSJmaGLIbvRBD+qatfUuQJBMU/04DdJIwvLtZBYdC2219m5dUBQaa4bimL+
YN9EcsDzD9h9UxQo5ReK7b3cNMzJBKJWLzFBcJuePMzAnLFktr/RufX4wpXe6XJx
oVPaHo72GorLkwnQ0HYMTY8rehT4mDi1FI969LHCFFaFHSAaRnwdXaQkJmSfcxzC
YQIDAQAB
-----END PUBLIC KEY-----

View File

@ -0,0 +1,8 @@
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="jclouds-chef">
<test verbose="2" name="org.jclouds.chef.ChefClientLiveTest" annotations="JDK">
<classes>
<class name="org.jclouds.chef.ChefClientLiveTest"/>
</classes>
</test>
</suite>

View File

@ -26,9 +26,14 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import org.jclouds.encryption.internal.JCEEncryptionService;
import com.google.inject.ImplementedBy;
@ -47,6 +52,9 @@ public interface EncryptionService {
String hmacSha256Base64(String toEncode, byte[] key) throws NoSuchAlgorithmException,
NoSuchProviderException, InvalidKeyException;
String sha1Base64(String toEncode) throws NoSuchAlgorithmException, NoSuchProviderException,
InvalidKeyException;
String hmacSha1Base64(String toEncode, byte[] key) throws NoSuchAlgorithmException,
NoSuchProviderException, InvalidKeyException;
@ -92,4 +100,8 @@ public interface EncryptionService {
byte[] fromBase64String(String encoded);
byte[] rsaPrivateEncrypt(String toSign, Key privateKey) throws NoSuchAlgorithmException,
NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException,
BadPaddingException;
}

View File

@ -24,11 +24,16 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.security.DigestOutputStream;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.Mac;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
import com.google.common.io.Closeables;
@ -97,7 +102,7 @@ public class JCEEncryptionService extends BaseEncryptionService {
}
public String toBase64String(byte[] resBuf) {
return Base64.encodeBytes(resBuf);
return Base64.encodeBytes(resBuf, Base64.DONT_BREAK_LINES);
}
public MD5InputStreamResult generateMD5Result(InputStream toEncode) {
@ -154,4 +159,21 @@ public class JCEEncryptionService extends BaseEncryptionService {
return digest.digest();
}
}
@Override
public String sha1Base64(String toEncode) throws NoSuchAlgorithmException,
NoSuchProviderException, InvalidKeyException {
MessageDigest sha1 = MessageDigest.getInstance("SHA1");
byte[] digest = sha1.digest(toEncode.getBytes());
return toBase64String(digest);
}
@Override
public byte[] rsaPrivateEncrypt(String toSign, Key key) throws NoSuchAlgorithmException,
NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException,
BadPaddingException {
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, key);
return cipher.doFinal(toSign.getBytes());
}
}

View File

@ -20,7 +20,7 @@ package org.jclouds.http;
import java.util.Collection;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
@ -35,7 +35,7 @@ public class HttpMessage {
/**
* synchronized as there is no concurrent version. Headers may change in flight due to redirects.
*/
protected Multimap<String, String> headers = Multimaps.synchronizedMultimap(HashMultimap
protected Multimap<String, String> headers = Multimaps.synchronizedMultimap(LinkedHashMultimap
.<String, String> create());
public Multimap<String, String> getHeaders() {

View File

@ -16,7 +16,7 @@
* limitations under the License.
* ====================================================================
*/
package org.jclouds.aws.util;
package org.jclouds.rest;
import org.jclouds.http.HttpRequest;
@ -28,6 +28,6 @@ public interface RequestSigner {
String createStringToSign(HttpRequest input);
String signString(String toSign);
String sign(String toSign);
}

View File

@ -23,9 +23,14 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.inject.Singleton;
import org.bouncycastle.crypto.Digest;
@ -156,4 +161,25 @@ public class BouncyCastleEncryptionService extends BaseEncryptionService {
return resBuf;
}
}
@Override
public String sha1Base64(String toEncode) throws NoSuchAlgorithmException,
NoSuchProviderException, InvalidKeyException {
byte[] plainBytes = toEncode.getBytes();
Digest digest = new SHA1Digest();
byte[] resBuf = new byte[digest.getDigestSize()];
digest.update(plainBytes, 0, plainBytes.length);
digest.doFinal(resBuf, 0);
return toBase64String(resBuf);
}
@Override
public byte[] rsaPrivateEncrypt(String toSign, Key key) throws NoSuchAlgorithmException,
NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException,
BadPaddingException {
// TODO convert this to BC code
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, key);
return cipher.doFinal(toSign.getBytes());
}
}

View File

@ -50,7 +50,8 @@
<module>twitter</module>
<module>vcloud</module>
<module>gogrid</module>
</modules>
<module>chef</module>
</modules>
<build>
<plugins>
<plugin>