techdocs: add support for integrations.awsS3
Signed-off-by: Vincenzo Scamporlino <vincenzos@spotify.com>
This commit is contained in:
@@ -270,25 +270,177 @@ describe('AwsS3Publish', () => {
|
||||
expect(getCredProviderMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should fall back to deprecated method of retrieving credentials', async () => {
|
||||
it('should use awsS3.credentials if they are provided', async () => {
|
||||
await jest.isolateModulesAsync(async () => {
|
||||
jest.doMock('@aws-sdk/client-s3', () => ({
|
||||
...jest.requireActual('@aws-sdk/client-s3'),
|
||||
S3Client: jest.fn(),
|
||||
}));
|
||||
|
||||
const { S3Client: MockS3Client } = require('@aws-sdk/client-s3');
|
||||
const { AwsS3Publish: IsolatedAwsS3Publish } = require('./awsS3');
|
||||
const mockConfig = new ConfigReader({
|
||||
techdocs: {
|
||||
publisher: {
|
||||
type: 'awsS3',
|
||||
awsS3: {
|
||||
credentials: {
|
||||
accessKeyId: 'accessKeyId',
|
||||
secretAccessKey: 'secretAccessKey',
|
||||
},
|
||||
bucketName: 'bucketName',
|
||||
bucketRootPath: '/',
|
||||
},
|
||||
},
|
||||
},
|
||||
integrations: {
|
||||
awsS3: [
|
||||
{
|
||||
accessKeyId: 'access-key-from-integrations',
|
||||
secretAccessKey: 'secret-access-key-from-integrations',
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
await IsolatedAwsS3Publish.fromConfig(mockConfig, logger);
|
||||
expect(getCredProviderMock).toHaveBeenCalledTimes(0);
|
||||
expect(MockS3Client).toHaveBeenCalledTimes(1);
|
||||
await expect(
|
||||
MockS3Client.mock.calls[0][0]!.credentialDefaultProvider!(
|
||||
undefined!,
|
||||
)(),
|
||||
).resolves.toEqual({
|
||||
accessKeyId: 'accessKeyId',
|
||||
secretAccessKey: 'secretAccessKey',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should use credentials from integrations if awsS3.credentials is not provided', async () => {
|
||||
await jest.isolateModulesAsync(async () => {
|
||||
jest.doMock('@aws-sdk/client-s3', () => ({
|
||||
...jest.requireActual('@aws-sdk/client-s3'),
|
||||
S3Client: jest.fn(),
|
||||
}));
|
||||
|
||||
const { S3Client: MockS3Client } = require('@aws-sdk/client-s3');
|
||||
const { AwsS3Publish: IsolatedAwsS3Publish } = require('./awsS3');
|
||||
|
||||
const mockConfig = new ConfigReader({
|
||||
techdocs: {
|
||||
publisher: {
|
||||
type: 'awsS3',
|
||||
awsS3: {
|
||||
credentials: {},
|
||||
bucketName: 'bucketName',
|
||||
bucketRootPath: '/',
|
||||
},
|
||||
},
|
||||
},
|
||||
integrations: {
|
||||
awsS3: [
|
||||
{
|
||||
accessKeyId: 'access-key-from-integrations',
|
||||
secretAccessKey: 'secret-access-key-from-integrations',
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
await IsolatedAwsS3Publish.fromConfig(mockConfig, logger);
|
||||
expect(getCredProviderMock).toHaveBeenCalledTimes(0);
|
||||
expect(MockS3Client).toHaveBeenCalledTimes(1);
|
||||
await expect(
|
||||
MockS3Client.mock.calls[0][0]!.credentialDefaultProvider!(
|
||||
undefined!,
|
||||
)(),
|
||||
).resolves.toEqual({
|
||||
accessKeyId: 'access-key-from-integrations',
|
||||
secretAccessKey: 'secret-access-key-from-integrations',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should retrieve default credentials if multiple integrations are present', async () => {
|
||||
const mockConfig = new ConfigReader({
|
||||
techdocs: {
|
||||
publisher: {
|
||||
type: 'awsS3',
|
||||
awsS3: {
|
||||
credentials: {
|
||||
accessKeyId: 'accessKeyId',
|
||||
secretAccessKey: 'secretAccessKey',
|
||||
},
|
||||
credentials: {},
|
||||
bucketName: 'bucketName',
|
||||
bucketRootPath: '/',
|
||||
},
|
||||
},
|
||||
},
|
||||
integrations: {
|
||||
awsS3: [
|
||||
{
|
||||
accessKeyId: 'access-key-from-integrations',
|
||||
secretAccessKey: 'secret-access-key-from-integrations',
|
||||
},
|
||||
{
|
||||
accessKeyId: 'access-key-from-integrations-2',
|
||||
secretAccessKey: 'secret-access-key-from-integrations-2',
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
await AwsS3Publish.fromConfig(mockConfig, logger);
|
||||
expect(getCredProviderMock).toHaveBeenCalledTimes(0);
|
||||
expect(getCredProviderMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should retrieve the target integration if multiple integrations are provided and credentials are not provided', async () => {
|
||||
await jest.isolateModulesAsync(async () => {
|
||||
jest.doMock('@aws-sdk/client-s3', () => ({
|
||||
...jest.requireActual('@aws-sdk/client-s3'),
|
||||
S3Client: jest.fn(),
|
||||
}));
|
||||
|
||||
const { S3Client: MockS3Client } = require('@aws-sdk/client-s3');
|
||||
const { AwsS3Publish: IsolatedAwsS3Publish } = require('./awsS3');
|
||||
|
||||
const mockConfig = new ConfigReader({
|
||||
techdocs: {
|
||||
publisher: {
|
||||
type: 'awsS3',
|
||||
awsS3: {
|
||||
credentials: {
|
||||
accessKeyId: 'access-key-from-integrations-2',
|
||||
},
|
||||
bucketName: 'bucketName',
|
||||
bucketRootPath: '/',
|
||||
},
|
||||
},
|
||||
},
|
||||
integrations: {
|
||||
awsS3: [
|
||||
{
|
||||
accessKeyId: 'access-key-from-integrations',
|
||||
secretAccessKey: 'secret-access-key-from-integrations',
|
||||
},
|
||||
{
|
||||
accessKeyId: 'access-key-from-integrations-2',
|
||||
secretAccessKey: 'secret-access-key-from-integrations-2',
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
await IsolatedAwsS3Publish.fromConfig(mockConfig, logger);
|
||||
expect(getCredProviderMock).toHaveBeenCalledTimes(0);
|
||||
expect(MockS3Client).toHaveBeenCalledTimes(1);
|
||||
await expect(
|
||||
MockS3Client.mock.calls[0][0]!.credentialDefaultProvider!(
|
||||
undefined!,
|
||||
)(),
|
||||
).resolves.toEqual({
|
||||
accessKeyId: 'access-key-from-integrations-2',
|
||||
secretAccessKey: 'secret-access-key-from-integrations-2',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -139,12 +139,19 @@ export class AwsS3Publish implements PublisherBase {
|
||||
const credentialsConfig = config.getOptionalConfig(
|
||||
'techdocs.publisher.awsS3.credentials',
|
||||
);
|
||||
|
||||
const credsManager = DefaultAwsCredentialsManager.fromConfig(config);
|
||||
|
||||
const awsS3IntegrationConfig =
|
||||
config.getOptionalConfigArray('integrations.awsS3');
|
||||
|
||||
const sdkCredentialProvider = await AwsS3Publish.buildCredentials(
|
||||
credsManager,
|
||||
accountId,
|
||||
credentialsConfig,
|
||||
region,
|
||||
awsS3IntegrationConfig,
|
||||
logger,
|
||||
);
|
||||
|
||||
// AWS endpoint is an optional config. If missing, the default endpoint is built from
|
||||
@@ -213,8 +220,10 @@ export class AwsS3Publish implements PublisherBase {
|
||||
private static async buildCredentials(
|
||||
credsManager: AwsCredentialsManager,
|
||||
accountId?: string,
|
||||
config?: Config,
|
||||
credentialsConfig?: Config,
|
||||
region?: string,
|
||||
awsS3IntegrationConfig?: Config[],
|
||||
logger: LoggerService,
|
||||
): Promise<AwsCredentialIdentityProvider> {
|
||||
// Pull credentials for the specified account ID from the 'aws' config section
|
||||
if (accountId) {
|
||||
@@ -222,21 +231,14 @@ export class AwsS3Publish implements PublisherBase {
|
||||
.sdkCredentialProvider;
|
||||
}
|
||||
|
||||
// Fall back to the default credential chain if neither account ID
|
||||
// nor explicit credentials are provided
|
||||
if (!config) {
|
||||
return (await credsManager.getCredentialProvider()).sdkCredentialProvider;
|
||||
}
|
||||
const explicitCredentials = await AwsS3Publish.getExplicitCredentials({
|
||||
credsManager,
|
||||
credentialsConfig,
|
||||
awsS3IntegrationConfig,
|
||||
logger,
|
||||
});
|
||||
|
||||
// Pull credentials from the techdocs config section (deprecated)
|
||||
const accessKeyId = config.getOptionalString('accessKeyId');
|
||||
const secretAccessKey = config.getOptionalString('secretAccessKey');
|
||||
const explicitCredentials: AwsCredentialIdentityProvider =
|
||||
accessKeyId && secretAccessKey
|
||||
? AwsS3Publish.buildStaticCredentials(accessKeyId, secretAccessKey)
|
||||
: (await credsManager.getCredentialProvider()).sdkCredentialProvider;
|
||||
|
||||
const roleArn = config.getOptionalString('roleArn');
|
||||
const roleArn = credentialsConfig?.getOptionalString('roleArn');
|
||||
if (roleArn) {
|
||||
return fromTemporaryCredentials({
|
||||
masterCredentials: explicitCredentials,
|
||||
@@ -251,6 +253,72 @@ export class AwsS3Publish implements PublisherBase {
|
||||
return explicitCredentials;
|
||||
}
|
||||
|
||||
private static async getExplicitCredentials({
|
||||
credentialsConfig,
|
||||
awsS3IntegrationConfig,
|
||||
credsManager,
|
||||
logger,
|
||||
}: {
|
||||
credentialsConfig?: Config;
|
||||
awsS3IntegrationConfig?: Config[];
|
||||
credsManager: AwsCredentialsManager;
|
||||
logger: LoggerService;
|
||||
}): Promise<AwsCredentialIdentityProvider> {
|
||||
const accessKeyId = credentialsConfig?.getOptionalString('accessKeyId');
|
||||
const secretAccessKey =
|
||||
credentialsConfig?.getOptionalString('secretAccessKey');
|
||||
|
||||
if (accessKeyId && secretAccessKey) {
|
||||
return AwsS3Publish.buildStaticCredentials(accessKeyId, secretAccessKey);
|
||||
}
|
||||
if (awsS3IntegrationConfig && awsS3IntegrationConfig.length > 0) {
|
||||
if (awsS3IntegrationConfig.length === 1) {
|
||||
const singleAwsS3IntegrationConfig = awsS3IntegrationConfig[0];
|
||||
|
||||
const singleAwsS3IntegrationAccessKeyId =
|
||||
singleAwsS3IntegrationConfig.getOptionalString('accessKeyId');
|
||||
const singleAwsS3IntegrationSecretAccessKey =
|
||||
singleAwsS3IntegrationConfig.getOptionalString('secretAccessKey');
|
||||
|
||||
if (
|
||||
singleAwsS3IntegrationAccessKeyId &&
|
||||
singleAwsS3IntegrationSecretAccessKey
|
||||
) {
|
||||
return AwsS3Publish.buildStaticCredentials(
|
||||
singleAwsS3IntegrationAccessKeyId,
|
||||
singleAwsS3IntegrationSecretAccessKey,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (accessKeyId) {
|
||||
const targetAwsS3IntegrationConfig = awsS3IntegrationConfig.find(
|
||||
c => c.getOptionalString('accessKeyId') === accessKeyId,
|
||||
);
|
||||
|
||||
if (!targetAwsS3IntegrationConfig) {
|
||||
logger.warn(
|
||||
`No AWS S3 integration config under integrations.awsS3 found for access key id ${accessKeyId}.`,
|
||||
);
|
||||
}
|
||||
const targetAwsS3IntegrationAccessKeyId =
|
||||
targetAwsS3IntegrationConfig?.getOptionalString('accessKeyId');
|
||||
const targetAwsS3IntegrationSecretAccessKey =
|
||||
targetAwsS3IntegrationConfig?.getOptionalString('secretAccessKey');
|
||||
if (
|
||||
targetAwsS3IntegrationAccessKeyId &&
|
||||
targetAwsS3IntegrationSecretAccessKey
|
||||
) {
|
||||
return AwsS3Publish.buildStaticCredentials(
|
||||
targetAwsS3IntegrationAccessKeyId,
|
||||
targetAwsS3IntegrationSecretAccessKey,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return (await credsManager.getCredentialProvider()).sdkCredentialProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the defined bucket exists. Being able to connect means the configuration is good
|
||||
* and the storage client will work.
|
||||
|
||||
Reference in New Issue
Block a user