blob: 2fc4201d37ee35d043b39047633179d454e356ca [file] [log] [blame]
/*
* 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 static com.google.common.base.Suppliers.compose;
import static com.google.common.base.Suppliers.memoizeWithExpiration;
import static com.google.common.base.Throwables.propagate;
import static org.jclouds.Constants.PROPERTY_SESSION_INTERVAL;
import static org.jclouds.chef.config.ChefProperties.CHEF_VALIDATOR_CREDENTIAL;
import static org.jclouds.chef.config.ChefProperties.CHEF_VALIDATOR_NAME;
import static org.jclouds.crypto.Pems.privateKeySpec;
import java.io.IOException;
import java.security.PrivateKey;
import java.security.spec.InvalidKeySpecException;
import java.util.List;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import org.jclouds.chef.domain.Client;
import org.jclouds.chef.functions.BootstrapConfigForGroup;
import org.jclouds.chef.functions.ClientForGroup;
import org.jclouds.chef.functions.RunListForGroup;
import org.jclouds.chef.handlers.ChefApiErrorRetryHandler;
import org.jclouds.chef.handlers.ChefErrorHandler;
import org.jclouds.crypto.Crypto;
import org.jclouds.crypto.Pems;
import org.jclouds.date.DateService;
import org.jclouds.date.TimeStamp;
import org.jclouds.domain.Credentials;
import org.jclouds.domain.JsonBall;
import org.jclouds.http.HttpErrorHandler;
import org.jclouds.http.HttpRetryHandler;
import org.jclouds.http.annotation.ClientError;
import org.jclouds.http.annotation.Redirection;
import org.jclouds.http.annotation.ServerError;
import org.jclouds.rest.ConfiguresHttpApi;
import org.jclouds.rest.config.HttpApiModule;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Charsets;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.base.Supplier;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.io.ByteSource;
import com.google.inject.ConfigurationException;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Provides;
import com.google.inject.name.Names;
/**
* Configures the Chef connection.
*/
@ConfiguresHttpApi
public abstract class BaseChefHttpApiModule<S> extends HttpApiModule<S> {
@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(PROPERTY_SESSION_INTERVAL) long seconds, final DateService dateService) {
return memoizeWithExpiration(new Supplier<String>() {
@Override
public String get() {
return dateService.iso8601SecondsDateFormat();
}
}, seconds, TimeUnit.SECONDS);
}
// TODO: potentially change this
@Provides
@Singleton
public Supplier<PrivateKey> supplyKey(final LoadingCache<Credentials, PrivateKey> keyCache,
@org.jclouds.location.Provider final Supplier<Credentials> creds) {
return compose(new Function<Credentials, PrivateKey>() {
@Override
public PrivateKey apply(Credentials in) {
return keyCache.getUnchecked(in);
}
}, creds);
}
@Provides
@Singleton
LoadingCache<Credentials, PrivateKey> privateKeyCache(PrivateKeyForCredentials loader) {
// throw out the private key related to old credentials
return CacheBuilder.newBuilder().maximumSize(2).build(loader);
}
/**
* it is relatively expensive to extract a private key from a PEM. cache the
* relationship between current credentials so that the private key is only
* recalculated once.
*/
@VisibleForTesting
@Singleton
private static class PrivateKeyForCredentials extends CacheLoader<Credentials, PrivateKey> {
private final Crypto crypto;
@Inject
private PrivateKeyForCredentials(Crypto crypto) {
this.crypto = crypto;
}
@Override
public PrivateKey load(Credentials in) {
try {
return crypto.rsaKeyFactory().generatePrivate(
privateKeySpec(ByteSource.wrap(in.credential.getBytes(Charsets.UTF_8))));
} catch (InvalidKeySpecException e) {
throw propagate(e);
} catch (IOException e) {
throw propagate(e);
}
}
}
@Provides
@Singleton
@Validator
public Optional<String> provideValidatorName(Injector injector) {
// Named properties can not be injected as optional here, so let's use the
// injector to bypass it
Key<String> key = Key.get(String.class, Names.named(CHEF_VALIDATOR_NAME));
try {
return Optional.<String> of(injector.getInstance(key));
} catch (ConfigurationException ex) {
return Optional.<String> absent();
}
}
@Provides
@Singleton
@Validator
public Optional<PrivateKey> provideValidatorCredential(Crypto crypto, Injector injector)
throws InvalidKeySpecException, IOException {
// Named properties can not be injected as optional here, so let's use the
// injector to bypass it
Key<String> key = Key.get(String.class, Names.named(CHEF_VALIDATOR_CREDENTIAL));
try {
String validatorCredential = injector.getInstance(key);
PrivateKey validatorKey = crypto.rsaKeyFactory().generatePrivate(
Pems.privateKeySpec(ByteSource.wrap(validatorCredential.getBytes(Charsets.UTF_8))));
return Optional.<PrivateKey> of(validatorKey);
} catch (ConfigurationException ex) {
return Optional.<PrivateKey> absent();
}
}
@Provides
@Singleton
CacheLoader<String, List<String>> runListForGroup(RunListForGroup runListForGroup) {
return CacheLoader.from(runListForGroup);
}
@Provides
@Singleton
CacheLoader<String, ? extends JsonBall> bootstrapConfigForGroup(BootstrapConfigForGroup bootstrapConfigForGroup) {
return CacheLoader.from(bootstrapConfigForGroup);
}
@Provides
@Singleton
CacheLoader<String, Client> groupToClient(ClientForGroup clientForGroup) {
return CacheLoader.from(clientForGroup);
}
@Override
protected void bindErrorHandlers() {
bind(HttpErrorHandler.class).annotatedWith(Redirection.class).to(ChefErrorHandler.class);
bind(HttpErrorHandler.class).annotatedWith(ClientError.class).to(ChefErrorHandler.class);
bind(HttpErrorHandler.class).annotatedWith(ServerError.class).to(ChefErrorHandler.class);
}
@Override
protected void bindRetryHandlers() {
bind(HttpRetryHandler.class).annotatedWith(ClientError.class).to(ChefApiErrorRetryHandler.class);
}
}