Skip to content

Commit

Permalink
feat: Create release v6 (#283)
Browse files Browse the repository at this point in the history
  • Loading branch information
slewis74 authored Jan 19, 2023
1 parent 9b63cfd commit 92cb7ad
Show file tree
Hide file tree
Showing 11 changed files with 399 additions and 25 deletions.
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
**/task.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import os from "os";
import { Client, CreateReleaseCommandV1, Logger, ReleaseRepository } from "@octopusdeploy/api-client";
import { InputParameters } from "./input-parameters";
import { TaskWrapper } from "tasks/Utils/taskInput";

// Returns the release number that was actually created in Octopus
export async function createReleaseFromInputs(client: Client, parameters: InputParameters, task: TaskWrapper, logger: Logger): Promise<string> {
logger.info?.("🐙 Creating a release in Octopus Deploy...");
const command: CreateReleaseCommandV1 = {
spaceName: parameters.space,
ProjectName: parameters.project,
ReleaseVersion: parameters.releaseNumber,
ChannelName: parameters.channel,
PackageVersion: parameters.defaultPackageVersion,
Packages: parameters.packages,
ReleaseNotes: parameters.releaseNotes,
GitRef: parameters.gitRef,
GitCommit: parameters.gitCommit,
};

try {
const repository = new ReleaseRepository(client, parameters.space);
const response = await repository.create(command);

client.info(`🎉 Release ${response.ReleaseVersion} created successfully!`);

task.setOutputVariable("release_number", response.ReleaseVersion);

return response.ReleaseVersion;
} catch (error: unknown) {
if (error instanceof Error) {
task.setFailure(`"Failed to execute command. ${error.message}${os.EOL}${error.stack}`, true);
} else {
task.setFailure(`"Failed to execute command. ${error}`, true);
}
throw error;
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
26 changes: 26 additions & 0 deletions source/tasks/CreateOctopusRelease/CreateOctopusReleaseV6/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { getDefaultOctopusConnectionDetailsOrThrow } from "../../Utils/connection";
import { ConcreteTaskWrapper, TaskWrapper } from "tasks/Utils/taskInput";
import { Logger } from "@octopusdeploy/api-client";
import * as tasks from "azure-pipelines-task-lib/task";
import { Release } from "./release";

const connection = getDefaultOctopusConnectionDetailsOrThrow();

const logger: Logger = {
debug: (message) => {
tasks.debug(message);
},
info: (message) => console.log(message),
warn: (message) => tasks.warning(message),
error: (message, err) => {
if (err !== undefined) {
tasks.error(err.message);
} else {
tasks.error(message);
}
},
};

const task: TaskWrapper = new ConcreteTaskWrapper();

new Release(connection, task, logger).run();
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { Logger } from "@octopusdeploy/api-client";
import { getInputParameters } from "./input-parameters";
import { MockTaskWrapper } from "../../Utils/MockTaskWrapper";

describe("getInputParameters", () => {
let logger: Logger;
let task: MockTaskWrapper;
beforeEach(() => {
logger = {};
task = new MockTaskWrapper();
});

test("all regular fields supplied", () => {
task.addVariableString("Space", "Default");
task.addVariableString("Project", "Awesome project");
task.addVariableString("Channel", "Beta");
task.addVariableString("ReleaseNumber", "1.0.0");
task.addVariableString("DefaultPackageVersion", "1.0.1");
task.addVariableString("Packages", "Step1:Foo:1.0.0\nBar:2.0.0");
task.addVariableString("GitRef", "main");

const inputParameters = getInputParameters(logger, task);
expect(inputParameters.space).toBe("Default");
expect(inputParameters.project).toBe("Awesome project");
expect(inputParameters.channel).toBe("Beta");
expect(inputParameters.releaseNumber).toBe("1.0.0");
expect(inputParameters.defaultPackageVersion).toBe("1.0.1");
expect(inputParameters.packages).toStrictEqual(["Step1:Foo:1.0.0", "Bar:2.0.0"]);
expect(inputParameters.gitRef).toBe("main");

expect(task.lastResult).toBeUndefined();
expect(task.lastResultMessage).toBeUndefined();
expect(task.lastResultDone).toBeUndefined();
});

test("packages in additional fields", () => {
task.addVariableString("Space", "Default");
task.addVariableString("Project", "Awesome project");
task.addVariableString("Packages", "Step1:Foo:1.0.0\nBar:2.0.0");
task.addVariableString("AdditionalArguments", "--package Baz:2.5.0");

const inputParameters = getInputParameters(logger, task);
expect(inputParameters.packages).toStrictEqual(["Baz:2.5.0", "Step1:Foo:1.0.0", "Bar:2.0.0"]);
});

test("duplicate variable name, variables field takes precedence", () => {
task.addVariableString("Space", "Default");
task.addVariableString("Project", "Awesome project");
task.addVariableString("Packages", "Step1:Foo:1.0.0\nBar:2.0.0");
task.addVariableString("AdditionalArguments", "--package Bar:2.0.0");

const inputParameters = getInputParameters(logger, task);
expect(inputParameters.packages).toStrictEqual(["Bar:2.0.0", "Step1:Foo:1.0.0"]);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import commandLineArgs from "command-line-args";
import shlex from "shlex";
import { getLineSeparatedItems } from "../../Utils/inputs";
import { Logger } from "@octopusdeploy/api-client";
import { TaskWrapper } from "tasks/Utils/taskInput";

export interface InputParameters {
space: string;
project: string;
releaseNumber: string | undefined;
channel: string | undefined;
defaultPackageVersion: string | undefined;
packages: string[] | undefined;
releaseNotes: string | undefined;
gitRef: string | undefined;
gitCommit: string | undefined;
}

export function getInputParameters(logger: Logger, task: TaskWrapper): InputParameters {
const packages: string[] = [];
let defaultPackageVersion: string | undefined = undefined;

const additionalArguments = task.getInput("AdditionalArguments");
logger.debug?.("AdditionalArguments:" + additionalArguments);
if (additionalArguments) {
const optionDefs = [
{ name: "package", type: String, multiple: true },
{ name: "defaultPackageVersion", type: String },
{ name: "packageVersion", type: String },
];
const splitArgs = shlex.split(additionalArguments);
const options = commandLineArgs(optionDefs, { argv: splitArgs });
logger.debug?.(JSON.stringify(options));
for (const pkg of options.package) {
packages.push(pkg.trim());
}

// defaultPackageVersion and packageVersion both represent the default package version
if (options.defaultPackageVersion) {
defaultPackageVersion = options.defaultPackageVersion;
}
if (options.packageVersion) {
defaultPackageVersion = options.packageVersion;
}
}

const packagesField = task.getInput("Packages");
logger.debug?.("Packages:" + packagesField);
if (packagesField) {
const packagesFieldData = getLineSeparatedItems(packagesField).map((p) => p.trim()) || undefined;
if (packagesFieldData) {
for (const packageLine of packagesFieldData) {
const trimmedPackageLine = packageLine.trim();
if (packages.indexOf(trimmedPackageLine) < 0) {
packages.push(trimmedPackageLine);
}
}
}
}

const defaultPackageVersionField = task.getInput("DefaultPackageVersion");
if (defaultPackageVersionField) {
defaultPackageVersion = defaultPackageVersionField;
}

const parameters: InputParameters = {
space: task.getInput("Space", true) || "",
project: task.getInput("Project", true) || "",
releaseNumber: task.getInput("ReleaseNumber"),
channel: task.getInput("Channel"),
defaultPackageVersion: defaultPackageVersion,
packages: packages.length > 0 ? packages : undefined,
releaseNotes: task.getInput("ReleaseNotes"),
gitRef: task.getInput("GitRef"),
gitCommit: task.getInput("GitCommit"),
};

logger.debug?.(JSON.stringify(parameters));

return parameters;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { Client, ClientConfiguration, Logger } from "@octopusdeploy/api-client";
import { OctoServerConnectionDetails } from "../../Utils/connection";
import { createReleaseFromInputs } from "./createRelease";
import { getInputParameters } from "./input-parameters";
import os from "os";
import { TaskWrapper } from "tasks/Utils/taskInput";

export class Release {
constructor(readonly connection: OctoServerConnectionDetails, readonly task: TaskWrapper, readonly logger: Logger) {}

public async run() {
try {
const inputParameters = getInputParameters(this.logger, this.task);

const config: ClientConfiguration = {
userAgentApp: "AzureDevOps (release;create;v6)",
instanceURL: this.connection.url,
apiKey: this.connection.apiKey,
logging: this.logger,
};
const client = await Client.create(config);

await createReleaseFromInputs(client, inputParameters, this.task, this.logger);

this.task.setSuccess("Release creation succeeded.");
} catch (error: unknown) {
if (error instanceof Error) {
this.task.setFailure(`"Failed to successfully create release. ${error.message}${os.EOL}${error.stack}`, true);
} else {
this.task.setFailure(`"Failed to successfully create release. ${error}`, true);
}
throw error;
}
}
}
137 changes: 137 additions & 0 deletions source/tasks/CreateOctopusRelease/CreateOctopusReleaseV6/task.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
{
"id": "4E131B60-5532-4362-95B6-7C67D9841B4F",
"name": "OctopusCreateRelease",
"friendlyName": "Create Octopus Release",
"description": "Create a Release in Octopus Deploy",
"helpMarkDown": "set-by-pack.ps1",
"category": "Deploy",
"visibility": [
"Build",
"Release"
],
"author": "Octopus Deploy",
"version": {
"Major": 6,
"Minor": 0,
"Patch": 0
},
"demands": [ ],
"minimumAgentVersion": "2.144.0",
"groups": [
{
"name": "versionControl",
"displayName": "Version Control",
"isExpanded": false
},
{
"name": "additional",
"displayName": "Additional Options",
"isExpanded": false
}
],
"inputs": [
{
"name": "OctoConnectedServiceName",
"type": "connectedService:OctopusEndpoint",
"label": "Octopus Deploy Server",
"defaultValue": "",
"required": true,
"helpMarkDown": "Octopus Deploy server connection"
},
{
"name": "Space",
"type": "string",
"label": "Space",
"defaultValue": "",
"required": true,
"helpMarkDown": "The space within Octopus. This must be the name of the space, not the id."
},
{
"name": "Project",
"type": "string",
"label": "Project",
"defaultValue": "",
"required": true,
"helpMarkDown": "The project within Octopus. This must be the name of the project, not the id."
},
{
"name": "ReleaseNumber",
"type": "string",
"label": "Release Number",
"defaultValue": "",
"required": false,
"helpMarkDown": "The number to use for this release. You can leave this blank if the release number is calculated by Octopus."
},
{
"name": "Channel",
"type": "string",
"label": "Channel",
"defaultValue": "",
"required": false,
"helpMarkDown": "The [channel](https://g.octopushq.com/Channels) to use for the release. This must be the name of the channel, not the id."
},
{
"name": "DefaultPackageVersion",
"type": "string",
"label": "Default Package Version",
"defaultValue": "",
"required": false,
"helpMarkDown": "Set this to provide a default package version to use for all packages on all steps. Can be used in conjunction with the Packages field, which can be used to override versions for specific packages."
},
{
"name": "Packages",
"type": "multiLine",
"label": "Packages",
"defaultValue": "",
"required": false,
"helpMarkDown": "A multi-line list of version numbers to use for a package in the release. Format: StepName:Version or PackageID:Version or StepName:PackageName:Version. StepName, PackageID, and PackageName can be replaced with an asterisk ('*'). An asterisk will be assumed for StepName, PackageID, or PackageName if they are omitted."
},
{
"name": "ReleaseNotes",
"type": "string",
"label": "Release Notes",
"defaultValue": "",
"required": false,
"helpMarkDown": "Octopus Release notes. This field supports markdown. To include newlines, you can use HTML linebreaks."
},
{
"name": "GitRef",
"type": "string",
"label": "Git Reference",
"defaultValue": "",
"required": false,
"helpMarkDown": "Git branch reference to use when creating the release for version controlled Projects.",
"groupName": "versionControl"
},
{
"name": "GitCommit",
"type": "string",
"label": "Git Commit",
"defaultValue": "",
"required": false,
"helpMarkDown": "Git commit to use when creating the release for version controlled Projects. Use in conjunction with the gitRef parameter to select any previous commit.",
"groupName": "versionControl"
},
{
"name": "AdditionalArguments",
"type": "string",
"label": "Additional Arguments",
"defaultValue": "",
"required": false,
"helpMarkDown": "Additional arguments are no longer supported. This field has been retained to ease migration from earlier versions of the step but values should be moved to the appropriate fields.",
"groupName": "additional"
}
],
"OutputVariables": [
{
"name": "release_number",
"description": "The Octopus Deploy release number assigned to the Release."
}
],
"instanceNameFormat": "Create Octopus Release",
"execution": {
"Node16": {
"target": "index.js"
}
}
}
Loading

0 comments on commit 92cb7ad

Please sign in to comment.