Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 56 additions & 14 deletions src/targets/__tests__/awsLambda.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { vi, type Mock, type MockInstance, type Mocked, type MockedFunction } from 'vitest';
import {
vi,
type Mock,

Check warning on line 3 in src/targets/__tests__/awsLambda.test.ts

View workflow job for this annotation

GitHub Actions / Lint fixes

[@typescript-eslint/no-unused-vars] 'Mock' is defined but never used.
type MockInstance,

Check warning on line 4 in src/targets/__tests__/awsLambda.test.ts

View workflow job for this annotation

GitHub Actions / Lint fixes

[@typescript-eslint/no-unused-vars] 'MockInstance' is defined but never used.
type Mocked,

Check warning on line 5 in src/targets/__tests__/awsLambda.test.ts

View workflow job for this annotation

GitHub Actions / Lint fixes

[@typescript-eslint/no-unused-vars] 'Mocked' is defined but never used.
type MockedFunction,
} from 'vitest';
import { NoneArtifactProvider } from '../../artifact_providers/none';
import { ConfigurationError } from '../../utils/errors';
import { AwsLambdaLayerTarget } from '../awsLambdaLayer';
Expand All @@ -12,7 +18,7 @@
name: 'aws-lambda-layer',
['testKey']: 'testValue',
},
new NoneArtifactProvider()
new NoneArtifactProvider(),
);
}

Expand Down Expand Up @@ -91,7 +97,7 @@
} catch (error) {
expect(error instanceof ConfigurationError).toBe(true);
expect(
/Missing project configuration parameter/.test(error.message)
/Missing project configuration parameter/.test(error.message),
).toBe(true);
}
});
Expand All @@ -109,9 +115,8 @@
// throw an error and avoid the whole `publish` to be executed. So, if
// the error in the mocked function is thrown, the project config test
// was successful; on the other hand, if it's not thrown, the test fails.
awsTarget.getArtifactsForRevision = getArtifactsFailingMock.bind(
AwsLambdaLayerTarget
);
awsTarget.getArtifactsForRevision =
getArtifactsFailingMock.bind(AwsLambdaLayerTarget);
await awsTarget.publish('', ''); // Should break the mocked function.
fail('Should not reach here');
} catch (error) {
Expand Down Expand Up @@ -141,7 +146,8 @@

test('layer name with multiple version variables', () => {
const awsTarget = getAwsLambdaTarget();
awsTarget.config.layerName = 'SentrySDKv{{{major}}}-{{{minor}}}-{{{patch}}}';
awsTarget.config.layerName =
'SentrySDKv{{{major}}}-{{{minor}}}-{{{patch}}}';
const resolved = awsTarget.resolveLayerName('10.2.3');
expect(resolved).toBe('SentrySDKv10-2-3');
});
Expand Down Expand Up @@ -173,9 +179,8 @@
test('error on missing artifact', async () => {
const awsTarget = getAwsLambdaTarget();
setTestingProjectConfig(awsTarget);
awsTarget.getArtifactsForRevision = noArtifactsForRevision.bind(
AwsLambdaLayerTarget
);
awsTarget.getArtifactsForRevision =
noArtifactsForRevision.bind(AwsLambdaLayerTarget);
// `publish` should report an error. When it's not dry run, the error is
// thrown; when it's on dry run, the error is logged and `undefined` is
// returned. Thus, both alternatives have been considered.
Expand All @@ -196,16 +201,15 @@
test('error on having too many artifacts', async () => {
const awsTarget = getAwsLambdaTarget();
setTestingProjectConfig(awsTarget);
awsTarget.getArtifactsForRevision = twoArtifactsForRevision.bind(
AwsLambdaLayerTarget
);
awsTarget.getArtifactsForRevision =
twoArtifactsForRevision.bind(AwsLambdaLayerTarget);
// `publish` should report an error. When it's not dry run, the error is
// thrown; when it's on dry run, the error is logged and `undefined` is
// returned. Thus, both alternatives have been considered.
try {
const multiplePackagesFound = await awsTarget.publish(
'version',
'revision'
'revision',
);
expect(multiplePackagesFound).toBe(undefined);
} catch (error) {
Expand All @@ -214,4 +218,42 @@
expect(multiplePackagesPattern.test(error.message)).toBe(true);
}
});

test('skips publishing for pre-release versions', async () => {
const awsTarget = getAwsLambdaTarget();
setTestingProjectConfig(awsTarget);

// Test various pre-release version formats
const preReleaseVersions = [
'1.0.0-alpha.1',
'2.5.3-beta.2',
'3.0.0-rc.1',
'1.2.3-dev',
'4.0.0-preview.5',
];

for (const version of preReleaseVersions) {
const result = await awsTarget.publish(version, 'revision');
expect(result).toBe(undefined);
}
});

test('publishes for pre-release when linkPrereleases is true', async () => {
const awsTarget = getAwsLambdaTarget();
setTestingProjectConfig(awsTarget);
awsTarget.awsLambdaConfig.linkPrereleases = true;

const getArtifactsMock = vi.fn().mockImplementation(() => ['artifact.zip']);
awsTarget.getArtifactsForRevision =
getArtifactsMock.bind(AwsLambdaLayerTarget);

// This should proceed to call getArtifactsForRevision for a pre-release
try {
await awsTarget.publish('1.0.0-alpha.1', 'revision');
} catch (error) {
// Expected to fail at a later stage, but getArtifactsForRevision should be called
}

expect(getArtifactsMock).toHaveBeenCalled();
});
});
78 changes: 43 additions & 35 deletions src/targets/awsLambdaLayer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export class AwsLambdaLayerTarget extends BaseTarget {

public constructor(
config: TargetConfig,
artifactProvider: BaseArtifactProvider
artifactProvider: BaseArtifactProvider,
) {
super(config, artifactProvider);
this.github = getGitHubClient();
Expand All @@ -69,7 +69,7 @@ export class AwsLambdaLayerTarget extends BaseTarget {
if (!process.env.AWS_ACCESS_KEY_ID || !process.env.AWS_SECRET_ACCESS_KEY) {
throw new ConfigurationError(
`Cannot publish AWS Lambda Layer: missing credentials.
Please use AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables.`
Please use AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables.`,
);
}
return {
Expand Down Expand Up @@ -98,7 +98,7 @@ export class AwsLambdaLayerTarget extends BaseTarget {
}
if (missingConfigOptions.length > 0) {
throw new ConfigurationError(
'Missing project configuration parameter(s): ' + missingConfigOptions
'Missing project configuration parameter(s): ' + missingConfigOptions,
);
}
}
Expand Down Expand Up @@ -139,6 +139,14 @@ export class AwsLambdaLayerTarget extends BaseTarget {
public async publish(version: string, revision: string): Promise<any> {
this.checkProjectConfig();

// Skip publishing AWS Lambda layers for pre-releases unless explicitly configured
if (isPreviewRelease(version) && !this.awsLambdaConfig.linkPrereleases) {
this.logger.info(
`Skipping AWS Lambda layer publication for pre-release version ${version}`,
);
return undefined;
}

this.logger.debug('Fetching artifact list...');
const packageFiles = await this.getArtifactsForRevision(revision, {
includeNames:
Expand All @@ -154,13 +162,13 @@ export class AwsLambdaLayerTarget extends BaseTarget {
reportError(
'Cannot publish AWS Lambda Layer: ' +
'multiple packages with matching patterns were found. You may want ' +
'to include or modify the includeNames parameter in the project config'
'to include or modify the includeNames parameter in the project config',
);
return undefined;
}

const artifactBuffer = fs.readFileSync(
await this.artifactProvider.downloadArtifact(packageFiles[0])
await this.artifactProvider.downloadArtifact(packageFiles[0]),
);

const awsRegions = await getRegionsFromAws();
Expand All @@ -172,31 +180,31 @@ export class AwsLambdaLayerTarget extends BaseTarget {
await withTempDir(
async directory => {
this.logger.info(
`Cloning ${remote.getRemoteString()} to ${directory}...`
`Cloning ${remote.getRemoteString()} to ${directory}...`,
);
const git = await cloneRepo(remote.getRemoteStringWithAuth(), directory);

await safeExec(
async () => {
await this.publishRuntimes(
version,
directory,
awsRegions,
artifactBuffer
);
this.logger.debug('Finished publishing runtimes.');
},
'publishRuntimes(...)'
const git = await cloneRepo(
remote.getRemoteStringWithAuth(),
directory,
);

await safeExec(async () => {
await this.publishRuntimes(
version,
directory,
awsRegions,
artifactBuffer,
);
this.logger.debug('Finished publishing runtimes.');
}, 'publishRuntimes(...)');

await git.add(['.']);
await git.checkout('master');
const runtimeNames = this.config.compatibleRuntimes.map(
(runtime: CompatibleRuntime) => runtime.name
(runtime: CompatibleRuntime) => runtime.name,
);
await git.commit(
'craft(aws-lambda): AWS Lambda layers published\n\n' +
`v${version} for ${runtimeNames}`
`v${version} for ${runtimeNames}`,
);

if (this.isPushableToRegistry(version)) {
Expand All @@ -205,7 +213,7 @@ export class AwsLambdaLayerTarget extends BaseTarget {
}
},
true,
'craft-release-awslambdalayer-'
'craft-release-awslambdalayer-',
);
}

Expand All @@ -224,7 +232,7 @@ export class AwsLambdaLayerTarget extends BaseTarget {
if (isPreviewRelease(version) && !this.awsLambdaConfig.linkPrereleases) {
// preview release
this.logger.info(
"Preview release detected, not updating the layer's data."
"Preview release detected, not updating the layer's data.",
);
return false;
}
Expand All @@ -240,7 +248,7 @@ export class AwsLambdaLayerTarget extends BaseTarget {
private createVersionSymlinks(
directory: string,
version: string,
versionFilepath: string
versionFilepath: string,
): void {
this.logger.debug('Creating symlinks...');
const latestVersionPath = path.posix.join(directory, 'latest.json');
Expand All @@ -266,7 +274,7 @@ export class AwsLambdaLayerTarget extends BaseTarget {
version: string,
directory: string,
awsRegions: string[],
artifactBuffer: Buffer
artifactBuffer: Buffer,
): Promise<void> {
const resolvedLayerName = this.resolveLayerName(version);
this.logger.debug(`Resolved layer name: ${resolvedLayerName}`);
Expand All @@ -280,7 +288,7 @@ export class AwsLambdaLayerTarget extends BaseTarget {
this.config.license,
artifactBuffer,
awsRegions,
version
version,
);

let publishedLayers = [];
Expand All @@ -290,7 +298,7 @@ export class AwsLambdaLayerTarget extends BaseTarget {
} catch (error) {
this.logger.error(
`Did not publish layers for ${runtime.name}.`,
error
error,
);
return;
}
Expand All @@ -301,19 +309,19 @@ export class AwsLambdaLayerTarget extends BaseTarget {
return;
} else {
this.logger.info(
`${runtime.name}: ${publishedLayers.length} layers published.`
`${runtime.name}: ${publishedLayers.length} layers published.`,
);
}

// Base directory for the layer files of the current runtime.
const runtimeBaseDir = path.posix.join(
directory,
this.AWS_REGISTRY_DIR,
runtime.name
runtime.name,
);
if (!fs.existsSync(runtimeBaseDir)) {
this.logger.warn(
`Directory structure for ${runtime.name} is missing, skipping file creation.`
`Directory structure for ${runtime.name} is missing, skipping file creation.`,
);
return;
}
Expand All @@ -336,11 +344,11 @@ export class AwsLambdaLayerTarget extends BaseTarget {

const baseFilepath = path.posix.join(
runtimeBaseDir,
this.BASE_FILENAME
this.BASE_FILENAME,
);
const newVersionFilepath = path.posix.join(
runtimeBaseDir,
`${version}.json`
`${version}.json`,
);

if (!fs.existsSync(baseFilepath)) {
Expand All @@ -350,7 +358,7 @@ export class AwsLambdaLayerTarget extends BaseTarget {
fs.writeFileSync(newVersionFilepath, manifestString);
} else {
const baseData = JSON.parse(
fs.readFileSync(baseFilepath, { encoding: 'utf-8' }).toString()
fs.readFileSync(baseFilepath, { encoding: 'utf-8' }).toString(),
);
const manifestString =
JSON.stringify({ ...baseData, ...runtimeData }, undefined, 2) +
Expand All @@ -360,9 +368,9 @@ export class AwsLambdaLayerTarget extends BaseTarget {

this.createVersionSymlinks(runtimeBaseDir, version, newVersionFilepath);
this.logger.info(
`${runtime.name}: created files and updated symlinks.`
`${runtime.name}: created files and updated symlinks.`,
);
})
}),
);
}
}
Loading