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
22 changes: 3 additions & 19 deletions src/features/projects/releases/deployments/deploymentRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
CreateDeploymentUntenantedCommandV1,
CreateDeploymentUntenantedResponseV1,
} from ".";
import { lt } from "semver";
import { ensureServerVersionAtLeast } from "../../../../versionCheck";

// WARNING: we've had to do this to cover a mistake in Octopus' API. The API has been corrected to return PascalCase, but was returning camelCase
// for a number of versions, so we'll deserialize both and use whichever actually has a value
Expand Down Expand Up @@ -54,15 +54,7 @@ export class DeploymentRepository {
}

async create(command: CreateDeploymentUntenantedCommandV1): Promise<CreateDeploymentUntenantedResponseV1> {
const serverInformation = await this.client.getServerInformation();
if (lt(serverInformation.version, "2022.3.5512")) {
this.client.error?.(
"The Octopus instance doesn't support deploying releases using the Executions API, it will need to be upgraded to at least 2022.3.5512 in order to access this API."
);
throw new Error(
"The Octopus instance doesn't support deploying releases using the Executions API, it will need to be upgraded to at least 2022.3.5512 in order to access this API."
);
}
await ensureServerVersionAtLeast(this.client, "2022.3.5512", "deploying releases using the Executions API");

this.client.debug(`Deploying a release...`);

Expand Down Expand Up @@ -92,15 +84,7 @@ export class DeploymentRepository {
}

async createTenanted(command: CreateDeploymentTenantedCommandV1): Promise<CreateDeploymentTenantedResponseV1> {
const serverInformation = await this.client.getServerInformation();
if (lt(serverInformation.version, "2022.3.5512")) {
this.client.error?.(
"The Octopus instance doesn't support deploying tenanted releases using the Executions API, it will need to be upgraded to at least 2022.3.5512 in order to access this API."
);
throw new Error(
"The Octopus instance doesn't support deploying tenanted releases using the Executions API, it will need to be upgraded to at least 2022.3.5512 in order to access this API."
);
}
await ensureServerVersionAtLeast(this.client, "2022.3.5512", "deploying tenanted releases using the Executions API");

this.client.debug(`Deploying a tenanted release...`);

Expand Down
12 changes: 2 additions & 10 deletions src/features/projects/releases/releaseRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { CreateReleaseCommandV1 } from "./createReleaseCommandV1";
import { CreateReleaseResponseV1 } from "./createReleaseResponseV1";
import { Release } from "./release";
import { ResourceCollection } from "../../../resourceCollection";
import { lt } from "semver";
import { ensureServerVersionAtLeast } from "../../../versionCheck";

type ReleaseListArgs = {
skip?: number;
Expand All @@ -25,15 +25,7 @@ export class ReleaseRepository {
}

async create(command: CreateReleaseCommandV1): Promise<CreateReleaseResponseV1> {
const serverInformation = await this.client.getServerInformation();
if (lt(serverInformation.version, "2022.3.5512")) {
this.client.error?.(
"The Octopus instance doesn't support creating releases using the Executions API, it will need to be upgraded to at least 2022.3.5512 in order to access this API."
);
throw new Error(
"The Octopus instance doesn't support creating releases using the Executions API, it will need to be upgraded to at least 2022.3.5512 in order to access this API."
);
}
await ensureServerVersionAtLeast(this.client, "2022.3.5512", "creating releases using the Executions API");

this.client.debug(`Creating a release...`);

Expand Down
22 changes: 3 additions & 19 deletions src/features/projects/runbooks/runs/runbookRunRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { spaceScopedRoutePrefix } from "../../../../spaceScopedRoutePrefix";
import { ListArgs } from "../../../basicRepository";
import { ResourceCollection } from "../../../../resourceCollection";
import { CreateRunbookRunCommandV1, CreateRunbookRunResponseV1 } from "./createRunbookRunCommandV1";
import { lt } from "semver";
import { ensureServerVersionAtLeast } from "../../../../versionCheck";
import { GitRef, Project } from "../../project";
import { RunbookRepository } from "../runbookRepository";
import { RunGitRunbookCommand } from "./RunGitRunbookCommand";
Expand Down Expand Up @@ -56,15 +56,7 @@ export class RunbookRunRepository {
}

async create(command: CreateRunbookRunCommandV1): Promise<CreateRunbookRunResponseV1> {
const serverInformation = await this.client.getServerInformation();
if (lt(serverInformation.version, "2022.3.5512")) {
this.client.error?.(
"The Octopus instance doesn't support running runbooks using the Executions API, it will need to be upgraded to at least 2022.3.5512 in order to access this API."
);
throw new Error(
"The Octopus instance doesn't support running runbooks using the Executions API, it will need to be upgraded to at least 2022.3.5512 in order to access this API."
);
}
await ensureServerVersionAtLeast(this.client, "2022.3.5512", "running runbooks using the Executions API");

this.client.debug(`Running a runbook...`);

Expand Down Expand Up @@ -94,15 +86,7 @@ export class RunbookRunRepository {
}

async createGit(command: RunGitRunbookCommand, gitRef: GitRef): Promise<RunGitRunbookResponse> {
const serverInformation = await this.client.getServerInformation();
if (lt(serverInformation.version, "2022.3.5512")) {
this.client.error?.(
"The Octopus instance doesn't support running runbooks using the Executions API, it will need to be upgraded to at least 2022.3.5512 in order to access this API."
);
throw new Error(
"The Octopus instance doesn't support running runbooks using the Executions API, it will need to be upgraded to at least 2022.3.5512 in order to access this API."
);
}
await ensureServerVersionAtLeast(this.client, "2022.3.5512", "running runbooks using the Executions API");

this.client.debug(`Running a runbook...`);

Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,4 @@ export * from "./spaceScopedResource";
export * from "./spaceScopedRoutePrefix";
export * from "./subscriptionRecord";
export * from "./utils";
export * from "./versionCheck";
90 changes: 90 additions & 0 deletions src/versionCheck.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { isLocalOctopusVersion, isServerVersionAtLeast, ensureServerVersionAtLeast } from "./versionCheck";
import type { Client } from "./client";

describe("isLocalOctopusVersion", () => {
test.each([
["0.0.0", true],
["0.0.0-local", true],
["0.0.0-local-build.5", true],
["0.0.0+sha1234", true],
["0.0.0-alpha+sha1234", true],
["0.0.1", false],
["0.0.10", false],
["1.0.0", false],
["2022.3.5512", false],
["", false],
])("isLocalOctopusVersion(%s) === %s", (version, expected) => {
expect(isLocalOctopusVersion(version as string)).toBe(expected);
});
});

describe("isServerVersionAtLeast", () => {
const min = "2022.3.5512";

test("returns true when server version is greater than minimum", () => {
expect(isServerVersionAtLeast("2023.1.0", min)).toBe(true);
});

test("returns true when server version equals minimum", () => {
expect(isServerVersionAtLeast("2022.3.5512", min)).toBe(true);
});

test("returns false when server version is below minimum", () => {
expect(isServerVersionAtLeast("2022.3.5511", min)).toBe(false);
expect(isServerVersionAtLeast("2021.1.0", min)).toBe(false);
});

test("returns true for local development versions regardless of minimum", () => {
expect(isServerVersionAtLeast("0.0.0", min)).toBe(true);
expect(isServerVersionAtLeast("0.0.0-local", min)).toBe(true);
expect(isServerVersionAtLeast("0.0.0+sha1234", min)).toBe(true);
});

test("returns false for invalid version strings", () => {
expect(isServerVersionAtLeast("not-a-version", min)).toBe(false);
expect(isServerVersionAtLeast("", min)).toBe(false);
});
});

describe("ensureServerVersionAtLeast", () => {
const min = "2022.3.5512";
const feature = "creating releases using the Executions API";
const expectedMessage =
`The Octopus instance doesn't support creating releases using the Executions API, ` +
`it will need to be upgraded to at least 2022.3.5512 in order to access this API.`;

function stubClient(version: string): { client: Client; errorCalls: string[] } {
const errorCalls: string[] = [];
const client = {
getServerInformation: async () => ({ version, installationId: "test" }),
error: (msg: string) => errorCalls.push(msg),
} as unknown as Client;
return { client, errorCalls };
}

test("resolves silently when server version satisfies minimum", async () => {
const { client, errorCalls } = stubClient("2022.3.5512");
await expect(ensureServerVersionAtLeast(client, min, feature)).resolves.toBeUndefined();
expect(errorCalls).toEqual([]);
});

test("resolves silently for local development versions", async () => {
const { client, errorCalls } = stubClient("0.0.0-local");
await expect(ensureServerVersionAtLeast(client, min, feature)).resolves.toBeUndefined();
expect(errorCalls).toEqual([]);
});

test("throws and logs identical message when server version is too old", async () => {
const { client, errorCalls } = stubClient("2022.3.5511");
await expect(ensureServerVersionAtLeast(client, min, feature)).rejects.toThrow(expectedMessage);
expect(errorCalls).toEqual([expectedMessage]);
});

test("does not throw if client.error is undefined", async () => {
const client = {
getServerInformation: async () => ({ version: "2022.3.5511", installationId: "test" }),
error: undefined,
} as unknown as Client;
await expect(ensureServerVersionAtLeast(client, min, feature)).rejects.toThrow(expectedMessage);
});
});
29 changes: 29 additions & 0 deletions src/versionCheck.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { lt, valid } from "semver";
import type { Client } from "./client";

// Local development builds of Octopus report a version of "0.0.0" (optionally with a
// prerelease tag like "-local" or build metadata). Treat those as "latest" so version
// gates do not block developers running against a locally-built server.
export function isLocalOctopusVersion(version: string): boolean {
return /^0\.0\.0(?:[-+].*)?$/.test(version);
}

// Returns true when the running server's version satisfies the minimum, OR when the
// running server is a local development build.
export function isServerVersionAtLeast(serverVersion: string, minimumVersion: string): boolean {
if (isLocalOctopusVersion(serverVersion)) return true;
if (!valid(serverVersion)) return false;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a new check, but I think it's fine. It should always be semver afaik

return !lt(serverVersion, minimumVersion);
}

// Looks up the running server's version and throws a uniform error if it is too old.
// `featureDescription` is the snippet that fills the "<FEATURE>" slot in the error
// template (e.g. "creating releases using the Executions API").
export async function ensureServerVersionAtLeast(client: Client, minimumVersion: string, featureDescription: string): Promise<void> {
const serverInformation = await client.getServerInformation();
if (isServerVersionAtLeast(serverInformation.version, minimumVersion)) return;

const message = `The Octopus instance doesn't support ${featureDescription}, it will need to be upgraded to at least ${minimumVersion} in order to access this API.`;
client.error?.(message);
throw new Error(message);
}
Loading