package com.instaclustr.cassandra.backup.azure; import static com.instaclustr.cassandra.backup.guice.BackupRestoreBindings.installBindings; import static com.instaclustr.kubernetes.KubernetesHelper.isRunningAsClient; import static java.lang.String.format; import java.net.URISyntaxException; import java.util.Map; import com.google.inject.AbstractModule; import com.google.inject.Provider; import com.google.inject.Provides; import com.google.inject.Singleton; import com.instaclustr.cassandra.backup.impl.AbstractOperationRequest; import com.instaclustr.kubernetes.KubernetesHelper; import com.instaclustr.kubernetes.SecretReader; import com.microsoft.azure.storage.CloudStorageAccount; import com.microsoft.azure.storage.StorageCredentialsAccountAndKey; import io.kubernetes.client.apis.CoreV1Api; public class AzureModule extends AbstractModule { @Override protected void configure() { installBindings(binder(), "azure", AzureRestorer.class, AzureBackuper.class, AzureBucketService.class); } @Provides @Singleton CloudStorageAccountFactory provideCloudStorageAccountFactory(final Provider<CoreV1Api> coreV1ApiProvider) { return new CloudStorageAccountFactory(coreV1ApiProvider); } public static class CloudStorageAccountFactory { private final Provider<CoreV1Api> coreV1ApiProvider; public CloudStorageAccountFactory(final Provider<CoreV1Api> coreV1ApiProvider) { this.coreV1ApiProvider = coreV1ApiProvider; } public CloudStorageAccount build(final AbstractOperationRequest operationRequest) throws AzureModuleException, URISyntaxException { return new CloudStorageAccount(provideStorageCredentialsAccountAndKey(coreV1ApiProvider, operationRequest), true); } public boolean isRunningInKubernetes() { return KubernetesHelper.isRunningInKubernetes() || isRunningAsClient(); } private StorageCredentialsAccountAndKey provideStorageCredentialsAccountAndKey(final Provider<CoreV1Api> coreV1ApiProvider, final AbstractOperationRequest operationrequest) throws AzureModuleException { if (isRunningInKubernetes()) { return resolveCredentialsFromK8S(coreV1ApiProvider, operationrequest); } else { return resolveCredentialsFromEnvProperties(); } } private StorageCredentialsAccountAndKey resolveCredentialsFromEnvProperties() { return new StorageCredentialsAccountAndKey(System.getenv("AZURE_STORAGE_ACCOUNT"), System.getenv("AZURE_STORAGE_KEY")); } private StorageCredentialsAccountAndKey resolveCredentialsFromK8S(final Provider<CoreV1Api> coreV1ApiProvider, final AbstractOperationRequest operationrequest) { try { final String namespace = operationrequest.resolveKubernetesNamespace(); final SecretReader secretReader = new SecretReader(coreV1ApiProvider); return secretReader.readIntoObject(namespace, operationrequest.resolveSecretName(), secret -> { final Map<String, byte[]> data = secret.getData(); final byte[] azureStorageAccount = data.get("azurestorageaccount"); final byte[] azureStorageKey = data.get("azurestoragekey"); if (azureStorageAccount == null) { throw new AzureModuleException(format("Secret %s does not contain any entry with key 'azurestorageaccount'", secret.getMetadata().getName())); } if (azureStorageKey == null) { throw new AzureModuleException(format("Secret %s does not contain any entry with key 'azurestoragekey'", secret.getMetadata().getName())); } return new StorageCredentialsAccountAndKey( new String(azureStorageAccount), new String(azureStorageKey) ); }); } catch (final Exception ex) { throw new AzureModuleException("Unable to resolve Azure credentials for backup / restores from Kubernetes ", ex); } } } public static final class AzureModuleException extends RuntimeException { public AzureModuleException(final String message, final Throwable cause) { super(message, cause); } public AzureModuleException(final String message) { super(message); } } }