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
4 changes: 4 additions & 0 deletions e2e/tests/api/releases/release.spec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,7 @@ environments:
type: identifier
operator: contains
value: "{{ prefix }}"

agents:
- name: "{{ prefix }}-agent"
type: "{{ prefix }}-agent-type"
135 changes: 135 additions & 0 deletions e2e/tests/api/releases/version-release.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ test.describe("Version Release Creation", () => {
await builder.upsertSystemFixture();
await builder.upsertResourcesFixtures();
await builder.upsertEnvironmentFixtures();
await builder.upsertAgentFixtures();
await new Promise((resolve) => setTimeout(resolve, 1_000));
});

Expand Down Expand Up @@ -102,4 +103,138 @@ test.describe("Version Release Creation", () => {
const release = releases.find((rel) => rel.version.tag === versionTag);
expect(release).toBeDefined();
});

test("should not create a release when a new version is created but is older than the current version deployed to a target", async ({
api,
page,
workspace,
}) => {
const systemPrefix = builder.refs.system.slug.split("-")[0]!;
const { id: agentId } = builder.refs.oneAgent();
const deploymentName = `${systemPrefix}-${faker.string.alphanumeric(10)}`;
const deploymentCreateResponse = await api.POST("/v1/deployments", {
body: {
name: deploymentName,
slug: deploymentName,
systemId: builder.refs.system.id,
jobAgentId: agentId,
},
});
expect(deploymentCreateResponse.response.status).toBe(201);
const deploymentId = deploymentCreateResponse.data?.id ?? "";

const newerVersionTag = faker.string.alphanumeric(10);
const olderVersionTag = faker.string.alphanumeric(10);

const newerCreatedAtDate = new Date(Date.now() - 1000);
const olderCreatedAtDate = new Date(Date.now() - 2000);

const versionResponse = await api.POST("/v1/deployment-versions", {
body: {
deploymentId,
tag: newerVersionTag,
metadata: { e2e: "true" },
createdAt: newerCreatedAtDate.toISOString(),
},
});
expect(versionResponse.response.status).toBe(201);

const importedResource = builder.refs.resources.at(0)!;
const resourceResponse = await api.GET(
"/v1/workspaces/{workspaceId}/resources/identifier/{identifier}",
{
params: {
path: {
workspaceId: workspace.id,
identifier: importedResource.identifier,
},
},
},
);
const resourceId = resourceResponse.data?.id ?? "";

const releaseTargetResponse = await api.GET(
"/v1/resources/{resourceId}/release-targets",
{
params: {
path: {
resourceId,
},
},
},
);
expect(releaseTargetResponse.response.status).toBe(200);
const releaseTargets = releaseTargetResponse.data ?? [];
const releaseTarget = releaseTargets.find(
(rt) => rt.deployment.id === deploymentId,
);
expect(releaseTarget).toBeDefined();

await page.waitForTimeout(12_000);

const releaseResponse = await api.GET(
"/v1/release-targets/{releaseTargetId}/releases",
{
params: {
path: {
releaseTargetId: releaseTarget?.id ?? "",
},
},
},
);
expect(releaseResponse.response.status).toBe(200);
const releases = releaseResponse.data ?? [];
const newerRelease = releases.find(
(rel) => rel.version.tag === newerVersionTag,
);
expect(newerRelease).toBeDefined();

const nextJobResponse = await api.GET(
"/v1/job-agents/{agentId}/queue/next",
{
params: {
path: { agentId },
},
},
);
expect(nextJobResponse.response.status).toBe(200);
const nextJobId = nextJobResponse.data?.jobs?.[0]?.id;
expect(nextJobId).toBeDefined();

const successfulJobResponse = await api.PATCH("/v1/jobs/{jobId}", {
params: { path: { jobId: nextJobId ?? "" } },
body: { status: "successful" },
});

expect(successfulJobResponse.response.status).toBe(200);

const olderVersionResponse = await api.POST("/v1/deployment-versions", {
body: {
deploymentId,
tag: olderVersionTag,
metadata: { e2e: "true" },
createdAt: olderCreatedAtDate.toISOString(),
},
});
expect(olderVersionResponse.response.status).toBe(201);

await page.waitForTimeout(12_000);

const nextReleaseResponse = await api.GET(
"/v1/release-targets/{releaseTargetId}/releases",
{
params: {
path: {
releaseTargetId: releaseTarget?.id ?? "",
},
},
},
);
expect(nextReleaseResponse.response.status).toBe(200);
const nextReleases = nextReleaseResponse.data ?? [];
const nextRelease = nextReleases.find(
(rel) => rel.version.tag === olderVersionTag,
);
expect(nextRelease).toBeUndefined();
});
});
21 changes: 17 additions & 4 deletions packages/rule-engine/src/manager/version-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
desc,
eq,
inArray,
or,
selector,
takeFirst,
takeFirstOrNull,
Expand Down Expand Up @@ -102,7 +103,10 @@ export class VersionReleaseManager implements ReleaseManager {
.innerJoin(schema.job, eq(schema.releaseJob.jobId, schema.job.id))
.where(
and(
eq(schema.job.status, JobStatus.Successful),
or(
eq(schema.job.status, JobStatus.Successful),
eq(schema.job.status, JobStatus.InProgress),
),
eq(schema.versionRelease.releaseTargetId, this.releaseTarget.id),
),
)
Expand Down Expand Up @@ -179,6 +183,15 @@ export class VersionReleaseManager implements ReleaseManager {
if (desiredVersion != null)
return versions.filter((version) => version.id === desiredVersion.id);

const latestDeployedVersion = await this.findLastestDeployedVersion();
const versionsNewerThanLatest = versions.filter((version) =>
isAfter(
version.createdAt,
latestDeployedVersion?.createdAt ?? new Date(0),
),
);
if (versionsNewerThanLatest.length === 0) return [];

const policy = await this.getPolicy();
const deploymentVersionSelector =
policy?.deploymentVersionSelector?.deploymentVersionSelector;
Expand All @@ -190,11 +203,11 @@ export class VersionReleaseManager implements ReleaseManager {
.sql();

const isTargetedId =
versions.length === 1
? eq(schema.deploymentVersion.id, versions.at(0)!.id)
versionsNewerThanLatest.length === 1
? eq(schema.deploymentVersion.id, versionsNewerThanLatest.at(0)!.id)
: inArray(
schema.deploymentVersion.id,
versions.map((v) => v.id),
versionsNewerThanLatest.map((v) => v.id),
);

const isReady = eq(
Expand Down
Loading