├── .eslintignore ├── .eslintrc.json ├── .gitattributes ├── .github └── workflows │ ├── build.yml │ └── codeql-analysis.yml ├── .gitignore ├── .octopus ├── deployment_process.ocl ├── deployment_settings.ocl └── schema_version.ocl ├── .prettierignore ├── .prettierrc ├── LICENSE.txt ├── README.md ├── embed-octo.cmd ├── embed-octo.ps1 ├── esbuild.mjs ├── img ├── ps-addpowershellstep.png ├── ps-configurepowershellstep.png ├── tfsbuild-addbuildstep.png ├── tfsbuild-configurebuildstep.png ├── tfsbuild-connectedservice1.png ├── tfsbuild-connectedservice2.png └── tfsbuild-releasenotes.png ├── jest.config.ts ├── localBuild.cmd ├── localbuild.sh ├── pack.cmd ├── pack.ps1 ├── package-lock.json ├── package.json ├── publish.ps1 ├── source ├── extension-icon.LocalTest.png ├── extension-icon.Test.png ├── extension-icon.png ├── extension-manifest.LocalTest.json ├── extension-manifest.Production.json ├── extension-manifest.Test.json ├── extension-manifest.json ├── img │ ├── catalog-image.jpg │ ├── create-package-options.png │ ├── create-release-options.png │ ├── deploy-release-options.png │ ├── multiple-widget-preview.jpg │ ├── octopus-add-tasks-deploy.png │ ├── octopus-add-tasks-package.png │ ├── octopus-alltasks.png │ ├── octopus_create-release-04.png │ ├── octopus_deploy-02.png │ ├── octopus_installer.png │ ├── octopus_package-03.png │ ├── octopus_promote-05.png │ ├── octopus_push-01.png │ ├── preview-image.jpg │ ├── promote-release-options.png │ ├── push-metadata-options.png │ ├── service-connection.png │ ├── tfsbuild-addbuildstep.png │ ├── tfsbuild-connectedservice1.png │ ├── tfsbuild-connectedservice2.png │ ├── vstsbuild-octopusendpoint-2.png │ └── widget-icon.jpg ├── tasks │ ├── AwaitTask │ │ └── AwaitTaskV6 │ │ │ ├── icon.png │ │ │ ├── icon.svg │ │ │ ├── index.ts │ │ │ ├── input-parameters.ts │ │ │ ├── task.json │ │ │ └── waiter.ts │ ├── BuildInformation │ │ └── BuildInformationV6 │ │ │ ├── buildInformation.ts │ │ │ ├── icon.png │ │ │ ├── icon.svg │ │ │ ├── index.ts │ │ │ ├── inputCommandBuilder.test.ts │ │ │ ├── inputCommandBuilder.ts │ │ │ ├── overwriteMode.test.ts │ │ │ ├── overwriteMode.ts │ │ │ ├── task.json │ │ │ └── vsts.ts │ ├── CreateOctopusRelease │ │ ├── CreateOctopusReleaseV5 │ │ │ ├── createRelease.test.ts │ │ │ ├── createRelease.ts │ │ │ ├── icon.png │ │ │ ├── icon.svg │ │ │ ├── index.ts │ │ │ └── task.json │ │ └── CreateOctopusReleaseV6 │ │ │ ├── createRelease.ts │ │ │ ├── icon.png │ │ │ ├── icon.svg │ │ │ ├── index.ts │ │ │ ├── inputCommandBuilder.test.ts │ │ │ ├── inputCommandBuilder.ts │ │ │ ├── release.ts │ │ │ └── task.json │ ├── Deploy │ │ ├── DeployV5 │ │ │ ├── deploy.test.ts │ │ │ ├── deploy.ts │ │ │ ├── icon.png │ │ │ ├── icon.svg │ │ │ ├── index.ts │ │ │ └── task.json │ │ └── DeployV6 │ │ │ ├── createDeployment.ts │ │ │ ├── deploy.ts │ │ │ ├── icon.png │ │ │ ├── icon.svg │ │ │ ├── index.ts │ │ │ ├── inputCommandBuilder.test.ts │ │ │ ├── inputCommandBuilder.ts │ │ │ └── task.json │ ├── DeployTenant │ │ └── TenantedDeployV6 │ │ │ ├── createDeployment.ts │ │ │ ├── deploy.ts │ │ │ ├── icon.png │ │ │ ├── icon.svg │ │ │ ├── index.ts │ │ │ ├── inputCommandBuilder.test.ts │ │ │ ├── inputCommandBuilder.ts │ │ │ └── task.json │ ├── OctoCli │ │ └── OctoCliV5 │ │ │ ├── icon.png │ │ │ ├── icon.svg │ │ │ ├── index.ts │ │ │ ├── octoCli.test.ts │ │ │ ├── octoCli.ts │ │ │ └── task.json │ ├── OctoInstaller │ │ ├── OctoInstallerV5 │ │ │ ├── downloadEndpointRetriever.ts │ │ │ ├── icon.png │ │ │ ├── icon.svg │ │ │ ├── index.ts │ │ │ ├── installer.test.ts │ │ │ ├── installer.ts │ │ │ ├── octopusCLIVersionFetcher.test.ts │ │ │ ├── octopusCLIVersionFetcher.ts │ │ │ └── task.json │ │ └── OctoInstallerV6 │ │ │ ├── downloadEndpointRetriever.test.ts │ │ │ ├── downloadEndpointRetriever.ts │ │ │ ├── icon.png │ │ │ ├── icon.svg │ │ │ ├── index.ts │ │ │ ├── installer.ts │ │ │ ├── octopusCLIVersionResolver.test.ts │ │ │ ├── octopusCLIVersionResolver.ts │ │ │ └── task.json │ ├── OctopusMetadata │ │ └── OctopusMetadataV5 │ │ │ ├── buildInformation.test.ts │ │ │ ├── buildInformation.ts │ │ │ ├── icon.png │ │ │ ├── icon.svg │ │ │ ├── index.ts │ │ │ └── task.json │ ├── PackNuGet │ │ └── PackNuGetV6 │ │ │ ├── create-package.ts │ │ │ ├── icon.png │ │ │ ├── icon.svg │ │ │ ├── index.ts │ │ │ ├── input-parameters.ts │ │ │ └── task.json │ ├── PackZip │ │ └── PackZipV6 │ │ │ ├── create-package.ts │ │ │ ├── icon.png │ │ │ ├── icon.svg │ │ │ ├── index.ts │ │ │ ├── input-parameters.ts │ │ │ └── task.json │ ├── Promote │ │ └── PromoteV5 │ │ │ ├── icon.png │ │ │ ├── icon.svg │ │ │ ├── index.ts │ │ │ ├── promote.test.ts │ │ │ ├── promote.ts │ │ │ └── task.json │ ├── Push │ │ ├── PushV5 │ │ │ ├── icon.png │ │ │ ├── icon.svg │ │ │ ├── index.ts │ │ │ ├── push.test.ts │ │ │ ├── push.ts │ │ │ └── task.json │ │ └── PushV6 │ │ │ ├── icon.png │ │ │ ├── icon.svg │ │ │ ├── index.ts │ │ │ ├── push.ts │ │ │ └── task.json │ ├── RunRunbook │ │ └── RunRunbookV6 │ │ │ ├── icon.png │ │ │ ├── icon.svg │ │ │ ├── index.ts │ │ │ ├── inputCommandBuilder.test.ts │ │ │ ├── inputCommandBuilder.ts │ │ │ ├── runRunbook.ts │ │ │ ├── runbookRun.ts │ │ │ └── task.json │ └── Utils │ │ ├── MockTaskWrapper.ts │ │ ├── client.ts │ │ ├── command-line-args.test.ts │ │ ├── connection.test.ts │ │ ├── connection.ts │ │ ├── executionResult.ts │ │ ├── inputs.ts │ │ ├── octopusTasks.ts │ │ ├── pluginInformation.ts │ │ ├── taskInput.ts │ │ ├── testing.ts │ │ └── tool.ts ├── tasksLegacy │ ├── CreateOctopusRelease │ │ ├── CreateOctopusReleaseV3 │ │ │ ├── icon.png │ │ │ ├── icon.svg │ │ │ ├── index.ts │ │ │ └── task.json │ │ └── CreateOctopusReleaseV4 │ │ │ ├── icon.png │ │ │ ├── icon.svg │ │ │ ├── index.ts │ │ │ └── task.json │ ├── Deploy │ │ ├── DeployV3 │ │ │ ├── icon.png │ │ │ ├── icon.svg │ │ │ ├── index.ts │ │ │ └── task.json │ │ └── DeployV4 │ │ │ ├── icon.png │ │ │ ├── icon.svg │ │ │ ├── index.ts │ │ │ └── task.json │ ├── OctoCli │ │ └── OctoCliV4 │ │ │ ├── icon.png │ │ │ ├── icon.svg │ │ │ ├── index.ts │ │ │ └── task.json │ ├── OctoInstaller │ │ └── OctoInstallerV4 │ │ │ ├── icon.png │ │ │ ├── icon.svg │ │ │ ├── index.ts │ │ │ └── task.json │ ├── OctopusMetadata │ │ └── OctopusMetadataV4 │ │ │ ├── icon.png │ │ │ ├── icon.svg │ │ │ ├── index.ts │ │ │ └── task.json │ ├── Pack │ │ └── PackV4 │ │ │ ├── icon.png │ │ │ ├── icon.svg │ │ │ ├── index.ts │ │ │ └── task.json │ ├── Promote │ │ ├── PromoteV3 │ │ │ ├── icon.png │ │ │ ├── icon.svg │ │ │ ├── index.ts │ │ │ └── task.json │ │ └── PromoteV4 │ │ │ ├── icon.png │ │ │ ├── icon.svg │ │ │ ├── index.ts │ │ │ └── task.json │ ├── Push │ │ ├── PushV3 │ │ │ ├── icon.png │ │ │ ├── icon.svg │ │ │ ├── index.ts │ │ │ └── task.json │ │ └── PushV4 │ │ │ ├── icon.png │ │ │ ├── icon.svg │ │ │ ├── index.ts │ │ │ └── task.json │ └── Utils │ │ ├── OctoApiKeyHandler.ts │ │ ├── connection.ts │ │ ├── environment.ts │ │ ├── inputs.ts │ │ ├── install.ts │ │ └── tool.ts ├── vsts.md └── widgets │ └── ProjectStatus │ ├── configuration.html │ ├── js │ ├── configuration.js │ ├── moment.min.js │ └── octo-status.js │ └── octo-status.html ├── task-ids.json └── tsconfig.json /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/**/* 2 | dist/**/* 3 | source/widgets/**/* -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "env": { 4 | "node": true 5 | }, 6 | "parser": "@typescript-eslint/parser", 7 | "plugins": ["@typescript-eslint", "prettier"], 8 | "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended", "prettier", "plugin:prettier/recommended"] 9 | } 10 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Confused? https://help.github.com/articles/dealing-with-line-endings/ 2 | # Set the default behavior, in case people don't have core.autocrlf set. 3 | * text=auto 4 | 5 | *.doc diff=astextplain 6 | *.DOC diff=astextplain 7 | *.docx diff=astextplain 8 | *.DOCX diff=astextplain 9 | *.dot diff=astextplain 10 | *.DOT diff=astextplain 11 | *.pdf diff=astextplain 12 | *.PDF diff=astextplain 13 | *.rtf diff=astextplain 14 | *.RTF diff=astextplain 15 | 16 | *.bmp binary 17 | *.gif binary 18 | *.jpg binary 19 | *.png binary 20 | 21 | *.ascx text 22 | *.cmd text 23 | *.coffee text 24 | *.config text 25 | *.cs text diff=csharp 26 | *.css text 27 | *.less text 28 | *.cshtml text 29 | *.htm text 30 | *.html text 31 | *.htm text 32 | *.js text 33 | *.json text 34 | *.msbuild text 35 | *.resx text 36 | *.ruleset text 37 | *.Stylecop text 38 | *.targets text 39 | *.tt text 40 | *.txt text 41 | *.vb text 42 | *.vbhtml text 43 | *.xml text 44 | *.xunit text 45 | 46 | *.csproj text merge=union 47 | *.vbproj text merge=union 48 | 49 | *.sln text eol=crlf merge=union 50 | 51 | *.approved.* binary -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ main ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ main ] 20 | schedule: 21 | - cron: '18 1 * * 0' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'javascript' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://git.io/codeql-language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v2 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v1 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 52 | 53 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 54 | # If this step fails, then you should remove it and run the build manually (see below) 55 | - name: Autobuild 56 | uses: github/codeql-action/autobuild@v1 57 | 58 | # ℹ️ Command-line programs to run using the OS shell. 59 | # 📚 https://git.io/JvXDl 60 | 61 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 62 | # and modify them (or add more) to build your code if your project 63 | # uses a compiled language 64 | 65 | #- run: | 66 | # make bootstrap 67 | # make release 68 | 69 | - name: Perform CodeQL Analysis 70 | uses: github/codeql-action/analyze@v1 71 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | modules 3 | Artifacts 4 | 5 | # Build Folders (you can keep bin if you'd like, to store dlls and pdbs) 6 | [Bb]in/ 7 | [Oo]bj/ 8 | 9 | # mstest test results 10 | TestResults 11 | tests 12 | 13 | ## Ignore Visual Studio temporary files, build results, and 14 | ## files generated by popular Visual Studio add-ons. 15 | 16 | # User-specific files 17 | *.suo 18 | *.user 19 | *.sln.docstates 20 | 21 | # Build results 22 | [Dd]ebug/ 23 | [Rr]elease/ 24 | x64/ 25 | *_i.c 26 | *_p.c 27 | *.ilk 28 | *.meta 29 | *.obj 30 | *.pch 31 | *.pdb 32 | *.pgc 33 | *.pgd 34 | *.rsp 35 | *.sbr 36 | *.tlb 37 | *.tli 38 | *.tlh 39 | *.tmp 40 | *.log 41 | *.vspscc 42 | *.vssscc 43 | .builds 44 | 45 | # Visual C++ cache files 46 | ipch/ 47 | *.aps 48 | *.ncb 49 | *.opensdf 50 | *.sdf 51 | 52 | # Visual Studio profiler 53 | *.psess 54 | *.vsp 55 | *.vspx 56 | 57 | # Guidance Automation Toolkit 58 | *.gpState 59 | 60 | # ReSharper is a .NET coding add-in 61 | _ReSharper* 62 | 63 | # NCrunch 64 | *.ncrunch* 65 | .*crunch*.local.xml 66 | 67 | # Installshield output folder 68 | [Ee]xpress 69 | 70 | # DocProject is a documentation generator add-in 71 | DocProject/buildhelp/ 72 | DocProject/Help/*.HxT 73 | DocProject/Help/*.HxC 74 | DocProject/Help/*.hhc 75 | DocProject/Help/*.hhk 76 | DocProject/Help/*.hhp 77 | DocProject/Help/Html2 78 | DocProject/Help/html 79 | 80 | # Click-Once directory 81 | publish 82 | 83 | # Publish Web Output 84 | *.Publish.xml 85 | 86 | # NuGet Packages Directory 87 | packages 88 | 89 | # Windows Azure Build Output 90 | csx 91 | *.build.csdef 92 | 93 | # Windows Store app package directory 94 | AppPackages/ 95 | 96 | # Others 97 | [Bb]in 98 | [Oo]bj 99 | sql 100 | TestResults 101 | [Tt]est[Rr]esult* 102 | *.Cache 103 | ClientBin 104 | [Ss]tyle[Cc]op.* 105 | ~$* 106 | *.dbmdl 107 | Generated_Code #added for RIA/Silverlight projects 108 | *.swp 109 | .idea 110 | 111 | # Backup & report files from converting an old project file to a newer 112 | # Visual Studio version. Backup files are not needed, because we have git ;-) 113 | _UpgradeReport_Files/ 114 | Backup*/ 115 | UpgradeLog*.XML 116 | 117 | dist/ 118 | build/Artifacts/ 119 | build/Temp/ 120 | dist/ 121 | .rpt2_cache/ 122 | 123 | *.received.* 124 | *.DS_Store 125 | 126 | *.iml 127 | 128 | modules/* 129 | reports/* 130 | .taskkey 131 | -------------------------------------------------------------------------------- /.octopus/deployment_settings.ocl: -------------------------------------------------------------------------------- 1 | connectivity_policy { 2 | allow_deployments_to_no_targets = true 3 | } 4 | 5 | versioning_strategy { 6 | donor_package { 7 | package = "OctoTFS.vsix" 8 | step = "push-to-azure-marketplace" 9 | } 10 | } -------------------------------------------------------------------------------- /.octopus/schema_version.ocl: -------------------------------------------------------------------------------- 1 | version = 9 -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | **/task.json -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": false, 3 | "printWidth": 250, 4 | "tabWidth": 4, 5 | "trailingComma": "es5", 6 | "endOfLine": "auto", 7 | "overrides": [ 8 | { 9 | "files": "*.yml", 10 | "options": { 11 | "tabWidth": 2 12 | } 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) Octopus Deploy and contributors. All rights reserved. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use 4 | these files except in compliance with the License. You may obtain a copy of the 5 | License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software distributed 10 | under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 11 | CONDITIONS OF ANY KIND, either express or implied. See the License for the 12 | specific language governing permissions and limitations under the License. 13 | -------------------------------------------------------------------------------- /embed-octo.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | setlocal 3 | cd "%~dp0" 4 | powershell -NoProfile -ExecutionPolicy Unrestricted .\embed-octo.ps1 %* -------------------------------------------------------------------------------- /esbuild.mjs: -------------------------------------------------------------------------------- 1 | import { build } from "esbuild"; 2 | import copyStaticFiles from "esbuild-copy-static-files"; 3 | import glob from "glob"; 4 | import { sep } from "path"; 5 | import { statSync } from "fs"; 6 | import os from "os"; 7 | import yargs from "yargs"; 8 | 9 | let __dirname = new URL(".", import.meta.url).pathname; 10 | 11 | if (os.platform() === "win32") { 12 | if (__dirname.startsWith("/")) { 13 | __dirname = __dirname.substring(1); 14 | } 15 | } 16 | 17 | function entryPoints() { 18 | function doReplacements(entries, source) { 19 | const matches = glob.sync(`${__dirname}source/${source}/**/index.ts`); 20 | 21 | for (let match of matches) { 22 | const key = match.replace(`${__dirname}source/${source}/`, "tasks/").replace(".ts", ""); 23 | 24 | entries[key] = match; 25 | } 26 | } 27 | 28 | const entries = {}; 29 | doReplacements(entries, "tasks"); 30 | doReplacements(entries, "tasksLegacy"); 31 | 32 | return entries; 33 | } 34 | 35 | const argv = yargs(process.argv).argv; 36 | 37 | function noTSFiles(src) { 38 | if (src.endsWith(".ts")) { 39 | return false; 40 | } 41 | 42 | return true; 43 | } 44 | 45 | function noFolders(src) { 46 | const isDirectory = statSync(src).isDirectory(); 47 | 48 | if (src.endsWith(`${sep}source`)) { 49 | return true; 50 | } 51 | 52 | return !isDirectory; 53 | } 54 | 55 | const bundleAsMuchAsWeCan = { 56 | name: "my-special-bundle", 57 | setup(build) { 58 | build.onResolve({ filter: /^[^.\/]|^\.[^.\/]|^\.\.[^\/]/ }, (args) => { 59 | console.log(args.path); 60 | console.log(`args.resolveDir=${args.resolveDir}`); 61 | if (args.path.startsWith("azure-pipelines-tool-lib") || args.path.startsWith("azure-pipelines-task-lib")) return { path: args.path, external: true }; 62 | }); 63 | }, 64 | }; 65 | 66 | build({ 67 | entryPoints: entryPoints(), 68 | bundle: true, 69 | target: "es2018", 70 | platform: "node", 71 | outdir: "dist", 72 | metafile: true, 73 | minify: true, 74 | plugins: [ 75 | copyStaticFiles({ src: "./source/img", dest: "dist/img" }), 76 | copyStaticFiles({ src: "./source", dest: "dist", recursive: false, filter: noFolders }), 77 | copyStaticFiles({ src: "./source/widgets", dest: "dist/widgets" }), 78 | copyStaticFiles({ src: "./node_modules/vss-web-extension-sdk/lib", dest: "dist/widgets/ProjectStatus/lib" }), 79 | copyStaticFiles({ src: "./source/tasks", dest: "dist/tasks", filter: noTSFiles }), 80 | copyStaticFiles({ src: "./source/tasksLegacy", dest: "dist/tasks", filter: noTSFiles }), 81 | bundleAsMuchAsWeCan, 82 | ], 83 | logLimit: 0, 84 | logLevel: "info", 85 | define: { "process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV), "process.env.EXTENSION_VERSION": JSON.stringify(argv.extensionVersion) }, 86 | }).catch(() => process.exit(1)); 87 | -------------------------------------------------------------------------------- /img/ps-addpowershellstep.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/img/ps-addpowershellstep.png -------------------------------------------------------------------------------- /img/ps-configurepowershellstep.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/img/ps-configurepowershellstep.png -------------------------------------------------------------------------------- /img/tfsbuild-addbuildstep.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/img/tfsbuild-addbuildstep.png -------------------------------------------------------------------------------- /img/tfsbuild-configurebuildstep.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/img/tfsbuild-configurebuildstep.png -------------------------------------------------------------------------------- /img/tfsbuild-connectedservice1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/img/tfsbuild-connectedservice1.png -------------------------------------------------------------------------------- /img/tfsbuild-connectedservice2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/img/tfsbuild-connectedservice2.png -------------------------------------------------------------------------------- /img/tfsbuild-releasenotes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/img/tfsbuild-releasenotes.png -------------------------------------------------------------------------------- /jest.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from "@jest/types"; 2 | 3 | const config: Config.InitialOptions = { 4 | maxWorkers: 4, 5 | verbose: true, 6 | preset: "ts-jest/presets/js-with-ts", 7 | }; 8 | 9 | export default config; 10 | -------------------------------------------------------------------------------- /localBuild.cmd: -------------------------------------------------------------------------------- 1 | rm -r dist 2 | call npm run build -- --extensionVersion %* 3 | rm -r modules 4 | 5 | setlocal 6 | cd "%~dp0" 7 | powershell -NoProfile -ExecutionPolicy Unrestricted .\pack.ps1 LocalTest %* -setupTaskDependencies 8 | -------------------------------------------------------------------------------- /localbuild.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | rm -r dist 3 | npm run build -- --extensionVersion $1 4 | rm -r modules 5 | mkdir dist 6 | pwsh -NoProfile -ExecutionPolicy Unrestricted ./pack.ps1 LocalTest $1 -setupTaskDependencies -------------------------------------------------------------------------------- /pack.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | setlocal 3 | cd "%~dp0" 4 | powershell -NoProfile -ExecutionPolicy Unrestricted .\pack.ps1 %* -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "test": "jest --ci --reporters=default --reporters=jest-junit", 5 | "build": "tsc --noEmit && node esbuild.mjs", 6 | "lint:fix": "eslint . --fix", 7 | "lint": "eslint ." 8 | }, 9 | "devDependencies": { 10 | "@tsconfig/node10": "^1.0.9", 11 | "@types/archiver": "^5.3.1", 12 | "@types/command-line-args": "^5.2.0", 13 | "@types/express": "^4.17.13", 14 | "@types/glob": "7.2.0", 15 | "@types/jest": "28.1.5", 16 | "@types/node": "^18.0.4", 17 | "@types/ramda": "0.28.15", 18 | "@types/test-console": "^2.0.0", 19 | "@types/uuid": "8.3.4", 20 | "@types/yargs": "^17.0.10", 21 | "@typescript-eslint/eslint-plugin": "^5.30.6", 22 | "@typescript-eslint/parser": "^5.30.6", 23 | "archiver": "^5.3.1", 24 | "esbuild": "^0.14.49", 25 | "esbuild-copy-static-files": "^0.1.0", 26 | "eslint": "^8.19.0", 27 | "eslint-config-prettier": "^8.5.0", 28 | "eslint-plugin-prettier": "^4.2.1", 29 | "express": "^4.18.1", 30 | "jest": "^28.1.3", 31 | "jest-junit": "^14.0.0", 32 | "prettier": "2.7.1", 33 | "rimraf": "3.0.2", 34 | "test-console": "^2.0.0", 35 | "ts-jest": "28.0.5", 36 | "ts-node": "^10.9.0", 37 | "typescript": "^4.7.4", 38 | "yargs": "^17.5.1" 39 | }, 40 | "dependencies": { 41 | "@octopusdeploy/api-client": "^3.5.1", 42 | "azure-devops-node-api": "11.2.0", 43 | "azure-pipelines-task-lib": "3.3.1", 44 | "azure-pipelines-tool-lib": "1.3.2", 45 | "command-line-args": "^5.2.1", 46 | "fp-ts": "1.19.5", 47 | "glob": "7.2.0", 48 | "ramda": "0.28.0", 49 | "semver": "^7.3.7", 50 | "shlex": "^2.1.2", 51 | "typed-rest-client": "1.8.9", 52 | "uuid": "8.3.2", 53 | "vss-web-extension-sdk": "5.141.0" 54 | }, 55 | "jest-junit": { 56 | "outputDirectory": "reports", 57 | "outputName": "jest-junit.xml", 58 | "ancestorSeparator": " › ", 59 | "uniqueOutputName": "false", 60 | "suiteNameTemplate": "{filepath}", 61 | "classNameTemplate": "{classname}", 62 | "titleTemplate": "{title}" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /source/extension-icon.LocalTest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/extension-icon.LocalTest.png -------------------------------------------------------------------------------- /source/extension-icon.Test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/extension-icon.Test.png -------------------------------------------------------------------------------- /source/extension-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/extension-icon.png -------------------------------------------------------------------------------- /source/extension-manifest.LocalTest.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "octopus-deploy-build-release-tasks-localtest", 3 | "name": "[LocalTest] Octopus Deploy Integration", 4 | "version": "set-by-pack.ps1", 5 | "publisher": "octopusdeploy", 6 | "public": false, 7 | "branding": { 8 | "color": "#434a54", 9 | "theme": "dark" 10 | } 11 | } -------------------------------------------------------------------------------- /source/extension-manifest.Production.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "octopus-deploy-build-release-tasks", 3 | "name": "Octopus Deploy Integration", 4 | "version": "set-by-pack.ps1", 5 | "publisher": "octopusdeploy", 6 | "public": true, 7 | "branding": { 8 | "color": "#eee", 9 | "theme": "light" 10 | } 11 | } -------------------------------------------------------------------------------- /source/extension-manifest.Test.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "octopus-deploy-build-release-tasks-test", 3 | "name": "[Test] Octopus Deploy Integration", 4 | "version": "set-by-pack.ps1", 5 | "publisher": "octopusdeploy", 6 | "public": false, 7 | "branding": { 8 | "color": "#434a54", 9 | "theme": "dark" 10 | } 11 | } -------------------------------------------------------------------------------- /source/img/catalog-image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/img/catalog-image.jpg -------------------------------------------------------------------------------- /source/img/create-package-options.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/img/create-package-options.png -------------------------------------------------------------------------------- /source/img/create-release-options.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/img/create-release-options.png -------------------------------------------------------------------------------- /source/img/deploy-release-options.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/img/deploy-release-options.png -------------------------------------------------------------------------------- /source/img/multiple-widget-preview.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/img/multiple-widget-preview.jpg -------------------------------------------------------------------------------- /source/img/octopus-add-tasks-deploy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/img/octopus-add-tasks-deploy.png -------------------------------------------------------------------------------- /source/img/octopus-add-tasks-package.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/img/octopus-add-tasks-package.png -------------------------------------------------------------------------------- /source/img/octopus-alltasks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/img/octopus-alltasks.png -------------------------------------------------------------------------------- /source/img/octopus_create-release-04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/img/octopus_create-release-04.png -------------------------------------------------------------------------------- /source/img/octopus_deploy-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/img/octopus_deploy-02.png -------------------------------------------------------------------------------- /source/img/octopus_installer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/img/octopus_installer.png -------------------------------------------------------------------------------- /source/img/octopus_package-03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/img/octopus_package-03.png -------------------------------------------------------------------------------- /source/img/octopus_promote-05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/img/octopus_promote-05.png -------------------------------------------------------------------------------- /source/img/octopus_push-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/img/octopus_push-01.png -------------------------------------------------------------------------------- /source/img/preview-image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/img/preview-image.jpg -------------------------------------------------------------------------------- /source/img/promote-release-options.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/img/promote-release-options.png -------------------------------------------------------------------------------- /source/img/push-metadata-options.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/img/push-metadata-options.png -------------------------------------------------------------------------------- /source/img/service-connection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/img/service-connection.png -------------------------------------------------------------------------------- /source/img/tfsbuild-addbuildstep.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/img/tfsbuild-addbuildstep.png -------------------------------------------------------------------------------- /source/img/tfsbuild-connectedservice1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/img/tfsbuild-connectedservice1.png -------------------------------------------------------------------------------- /source/img/tfsbuild-connectedservice2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/img/tfsbuild-connectedservice2.png -------------------------------------------------------------------------------- /source/img/vstsbuild-octopusendpoint-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/img/vstsbuild-octopusendpoint-2.png -------------------------------------------------------------------------------- /source/img/widget-icon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/img/widget-icon.jpg -------------------------------------------------------------------------------- /source/tasks/AwaitTask/AwaitTaskV6/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/tasks/AwaitTask/AwaitTaskV6/icon.png -------------------------------------------------------------------------------- /source/tasks/AwaitTask/AwaitTaskV6/index.ts: -------------------------------------------------------------------------------- 1 | import { getDefaultOctopusConnectionDetailsOrThrow } from "../../Utils/connection"; 2 | import { ConcreteTaskWrapper, TaskWrapper } from "tasks/Utils/taskInput"; 3 | import { Logger } from "@octopusdeploy/api-client"; 4 | import * as tasks from "azure-pipelines-task-lib/task"; 5 | import { Waiter } from "./waiter"; 6 | 7 | const connection = getDefaultOctopusConnectionDetailsOrThrow(); 8 | 9 | const logger: Logger = { 10 | debug: (message) => { 11 | tasks.debug(message); 12 | }, 13 | info: (message) => console.log(message), 14 | warn: (message) => tasks.warning(message), 15 | error: (message, err) => { 16 | if (err !== undefined) { 17 | tasks.error(err.message); 18 | } else { 19 | tasks.error(message); 20 | } 21 | }, 22 | }; 23 | 24 | const task: TaskWrapper = new ConcreteTaskWrapper(); 25 | 26 | new Waiter(connection, task, logger).run(); 27 | -------------------------------------------------------------------------------- /source/tasks/AwaitTask/AwaitTaskV6/input-parameters.ts: -------------------------------------------------------------------------------- 1 | import { Logger } from "@octopusdeploy/api-client"; 2 | import { TaskWrapper } from "tasks/Utils/taskInput"; 3 | import { WaitExecutionResult } from "./waiter"; 4 | 5 | export interface InputParameters { 6 | space: string; 7 | step: string; 8 | tasks: WaitExecutionResult[]; 9 | pollingInterval: number; 10 | timeout: number; 11 | showProgress: boolean; 12 | } 13 | 14 | export function getInputParameters(logger: Logger, task: TaskWrapper): InputParameters { 15 | const space = task.getInput("Space"); 16 | if (!space) { 17 | throw new Error("Failed to successfully build parameters: space name is required."); 18 | } 19 | 20 | const step = task.getInput("Step"); 21 | if (!step) { 22 | throw new Error("Failed to successfully build parameters: step name is required."); 23 | } 24 | 25 | const taskJson = task.getOutputVariable(step, "server_tasks"); 26 | if (taskJson === undefined) { 27 | throw new Error(`Failed to successfully build parameters: cannot find '${step}.server_tasks' variable from execution step`); 28 | } 29 | const tasks = JSON.parse(taskJson); 30 | if (!Array.isArray(tasks)) { 31 | throw new Error(`Failed to successfully build parameters: '${step}.server_tasks' variable from execution step is not an array`); 32 | } 33 | 34 | let pollingInterval = 10; 35 | const pollingIntervalField = task.getInput("PollingInterval"); 36 | if (pollingIntervalField) { 37 | pollingInterval = +pollingIntervalField; 38 | } 39 | 40 | let timeoutSeconds = 600; 41 | const timeoutField = task.getInput("TimeoutAfter"); 42 | if (timeoutField) { 43 | timeoutSeconds = +timeoutField; 44 | } 45 | 46 | const showProgress = task.getBoolean("ShowProgress") ?? false; 47 | if (showProgress && tasks.length > 1) { 48 | throw new Error("Failed to successfully build parameters: ShowProgress can only be enabled when waiting for a single task"); 49 | } 50 | 51 | const parameters: InputParameters = { 52 | space: task.getInput("Space") || "", 53 | step: step, 54 | tasks: tasks, 55 | showProgress: showProgress, 56 | pollingInterval: pollingInterval, 57 | timeout: timeoutSeconds, 58 | }; 59 | 60 | const errors: string[] = []; 61 | if (parameters.space === "") { 62 | errors.push("The Octopus space name is required."); 63 | } 64 | 65 | if (errors.length > 0) { 66 | throw new Error("Failed to successfully build parameters.\n" + errors.join("\n")); 67 | } 68 | 69 | logger.debug?.(`Tasks: \n${JSON.stringify(parameters, null, 2)}`); 70 | 71 | return parameters; 72 | } 73 | -------------------------------------------------------------------------------- /source/tasks/BuildInformation/BuildInformationV6/buildInformation.ts: -------------------------------------------------------------------------------- 1 | import { OctoServerConnectionDetails } from "../../Utils/connection"; 2 | import { createCommandFromInputs } from "./inputCommandBuilder"; 3 | import { BuildInformationRepository, Logger } from "@octopusdeploy/api-client"; 4 | import { TaskWrapper } from "tasks/Utils/taskInput"; 5 | import { getOverwriteMode } from "./overwriteMode"; 6 | import { IVstsHelper } from "./vsts"; 7 | import { getClient } from "../../Utils/client"; 8 | 9 | export class BuildInformation { 10 | constructor(readonly connection: OctoServerConnectionDetails, readonly logger: Logger, readonly task: TaskWrapper, readonly vsts: IVstsHelper) {} 11 | 12 | public async run() { 13 | const command = await createCommandFromInputs(this.logger, this.task, this.vsts); 14 | const client = await getClient(this.connection, this.logger, "build-information", "push", 6) 15 | 16 | const overwriteMode = await getOverwriteMode(this.logger, this.task); 17 | this.logger.debug?.(`Build Information:\n${JSON.stringify(command, null, 2)}`); 18 | const repository = new BuildInformationRepository(client, command.spaceName); 19 | await repository.push(command, overwriteMode); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /source/tasks/BuildInformation/BuildInformationV6/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/tasks/BuildInformation/BuildInformationV6/icon.png -------------------------------------------------------------------------------- /source/tasks/BuildInformation/BuildInformationV6/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /source/tasks/BuildInformation/BuildInformationV6/index.ts: -------------------------------------------------------------------------------- 1 | import { Logger } from "@octopusdeploy/api-client"; 2 | import { getDefaultOctopusConnectionDetailsOrThrow } from "tasks/Utils/connection"; 3 | import * as tasks from "azure-pipelines-task-lib/task"; 4 | import { ConcreteTaskWrapper, TaskWrapper } from "tasks/Utils/taskInput"; 5 | import { BuildInformation } from "./buildInformation"; 6 | import { IVstsHelper, VstsHelper } from "./vsts"; 7 | 8 | const connection = getDefaultOctopusConnectionDetailsOrThrow(); 9 | 10 | const logger: Logger = { 11 | debug: (message) => { 12 | tasks.debug(message); 13 | }, 14 | info: (message) => console.log(message), 15 | warn: (message) => tasks.warning(message), 16 | error: (message, err) => { 17 | if (err !== undefined) { 18 | tasks.error(err.message); 19 | } else { 20 | tasks.error(message); 21 | } 22 | }, 23 | }; 24 | 25 | const task: TaskWrapper = new ConcreteTaskWrapper(); 26 | const vsts: IVstsHelper = new VstsHelper(logger); 27 | 28 | new BuildInformation(connection, logger, task, vsts).run(); 29 | -------------------------------------------------------------------------------- /source/tasks/BuildInformation/BuildInformationV6/inputCommandBuilder.ts: -------------------------------------------------------------------------------- 1 | import { CreateOctopusBuildInformationCommand, Logger, PackageIdentity } from "@octopusdeploy/api-client"; 2 | import { getLineSeparatedItems } from "../../Utils/inputs"; 3 | import { TaskWrapper } from "tasks/Utils/taskInput"; 4 | import { IVstsHelper } from "./vsts"; 5 | 6 | export async function createCommandFromInputs(logger: Logger, task: TaskWrapper, vstsHelper: IVstsHelper): Promise { 7 | const vsts = await vstsHelper.getVsts(logger); 8 | const inputPackages = getLineSeparatedItems(task.getInput("PackageIds") || "") || []; 9 | logger.debug?.(`PackageIds: ${inputPackages}`); 10 | const packages: PackageIdentity[] = []; 11 | for (const packageId of inputPackages) { 12 | packages.push({ 13 | Id: packageId, 14 | Version: task.getInput("PackageVersion") || "", 15 | }); 16 | } 17 | 18 | const command: CreateOctopusBuildInformationCommand = { 19 | spaceName: task.getInput("Space") || "", 20 | BuildEnvironment: "Azure DevOps", 21 | BuildNumber: vsts.environment.buildNumber, 22 | BuildUrl: vsts.environment.teamCollectionUri.replace(/\/$/, "") + "/" + vsts.environment.projectName + "/_build/results?buildId=" + vsts.environment.buildId, 23 | Branch: vsts.branch || "", 24 | VcsType: vsts.vcsType, 25 | VcsRoot: vsts.environment.buildRepositoryUri, 26 | VcsCommitNumber: vsts.environment.buildSourceVersion, 27 | Commits: vsts.commits, 28 | Packages: packages, 29 | }; 30 | 31 | const errors: string[] = []; 32 | if (!command.spaceName) { 33 | errors.push("space name is required"); 34 | } 35 | 36 | if (!command.Packages || command.Packages.length === 0) { 37 | errors.push("must specify at least one package name"); 38 | } else { 39 | if (!command.Packages[0].Version || command.Packages[0].Version === "") { 40 | errors.push("must specify a package version number, in SemVer format"); 41 | } 42 | } 43 | 44 | if (errors.length > 0) { 45 | throw new Error(`Failed to successfully build parameters:\n${errors.join("\n")}`); 46 | } 47 | 48 | return command; 49 | } 50 | -------------------------------------------------------------------------------- /source/tasks/BuildInformation/BuildInformationV6/overwriteMode.test.ts: -------------------------------------------------------------------------------- 1 | import { Logger, OverwriteMode } from "@octopusdeploy/api-client"; 2 | import { getOverwriteMode } from "./overwriteMode"; 3 | import { MockTaskWrapper } from "../../Utils/MockTaskWrapper"; 4 | 5 | describe("getInputCommand", () => { 6 | let logger: Logger; 7 | let task: MockTaskWrapper; 8 | beforeEach(() => { 9 | logger = {}; 10 | task = new MockTaskWrapper(); 11 | }); 12 | 13 | test("second run", () => { 14 | task.addVariableString("system.jobAttempt", "2"); 15 | const overwriteMode = getOverwriteMode(logger, task); 16 | expect(overwriteMode).toBe(OverwriteMode.IgnoreIfExists); 17 | }); 18 | 19 | test("user provided", () => { 20 | task.addVariableString("Replace", "true"); 21 | const overwriteMode = getOverwriteMode(logger, task); 22 | expect(overwriteMode).toBe(OverwriteMode.OverwriteExisting); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /source/tasks/BuildInformation/BuildInformationV6/overwriteMode.ts: -------------------------------------------------------------------------------- 1 | import { Logger, OverwriteMode } from "@octopusdeploy/api-client"; 2 | import { ReplaceOverwriteMode } from "../../Utils/inputs"; 3 | import { TaskWrapper } from "../../Utils/taskInput"; 4 | 5 | export function getOverwriteMode(logger: Logger, task: TaskWrapper): OverwriteMode { 6 | const isRetry = parseInt(task.getVariable("system.jobAttempt") || "0") > 1; 7 | const overwriteMode: ReplaceOverwriteMode = 8 | (ReplaceOverwriteMode as any)[task.getInput("Replace", false) || ""] || // eslint-disable-line @typescript-eslint/no-explicit-any 9 | (isRetry ? ReplaceOverwriteMode.IgnoreIfExists : ReplaceOverwriteMode.false); 10 | 11 | let apiOverwriteMode: OverwriteMode; 12 | switch (overwriteMode) { 13 | case ReplaceOverwriteMode.true: 14 | apiOverwriteMode = OverwriteMode.OverwriteExisting; 15 | break; 16 | case ReplaceOverwriteMode.IgnoreIfExists: 17 | apiOverwriteMode = OverwriteMode.IgnoreIfExists; 18 | break; 19 | case ReplaceOverwriteMode.false: 20 | apiOverwriteMode = OverwriteMode.FailIfExists; 21 | break; 22 | default: 23 | apiOverwriteMode = OverwriteMode.FailIfExists; 24 | break; 25 | } 26 | logger.debug?.(`Overwrite mode: ${apiOverwriteMode}`); 27 | return apiOverwriteMode; 28 | } 29 | -------------------------------------------------------------------------------- /source/tasks/BuildInformation/BuildInformationV6/task.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "559b81c9-efc1-40f3-9058-71ab1810d837", 3 | "name": "OctopusBuildInformation", 4 | "friendlyName": "Push Package Build Information to Octopus", 5 | "description": "Collect information related to the build, including work items from commit messages, and push to your Octopus Deploy Server.", 6 | "helpMarkDown": "set-by-pack.ps1", 7 | "category": "Package", 8 | "visibility": [ 9 | "Build" 10 | ], 11 | "author": "Octopus Deploy", 12 | "version": { 13 | "Major": 6, 14 | "Minor": 0, 15 | "Patch": 0 16 | }, 17 | "demands": [], 18 | "minimumAgentVersion": "2.206.1", 19 | "inputs": [ 20 | { 21 | "name": "OctoConnectedServiceName", 22 | "type": "connectedService:OctopusEndpoint", 23 | "label": "Octopus Deploy Server", 24 | "defaultValue": "", 25 | "required": true, 26 | "helpMarkDown": "Octopus Deploy server connection" 27 | }, 28 | { 29 | "name": "Space", 30 | "type": "string", 31 | "label": "Space", 32 | "defaultValue": "", 33 | "required": true, 34 | "helpMarkDown": "The space within Octopus. This must be the name of the space, not the id." 35 | }, 36 | { 37 | "name": "PackageIds", 38 | "type": "multiLine", 39 | "label": "Package IDs", 40 | "defaultValue": "", 41 | "required": true, 42 | "helpMarkDown": "Newline-separated package IDs; e.g.\nMyCompany.MyApp\nMyCompany.MyApp2" 43 | }, 44 | { 45 | "name": "PackageVersion", 46 | "type": "string", 47 | "label": "Package Version", 48 | "defaultValue": "", 49 | "required": true, 50 | "helpMarkDown": "The version of the package; must be a valid [SemVer](http://semver.org/) version." 51 | }, 52 | { 53 | "name": "Replace", 54 | "type": "pickList", 55 | "label": "Overwrite Mode", 56 | "defaultValue": "false", 57 | "required": true, 58 | "helpMarkDown": "Normally, if the same package build information already exists on the server, the server will reject the package build information push. This is a good practice as it ensures build information isn't accidentally overwritten or ignored. Use this setting to override this behavior.", 59 | "options": { 60 | "false": "Fail if exists", 61 | "true": "Overwrite existing", 62 | "IgnoreIfExists": "Ignore if exists" 63 | } 64 | } 65 | ], 66 | "instanceNameFormat": "Push Package Build Information to Octopus", 67 | "execution": { 68 | "Node16": { 69 | "target": "index.js" 70 | } 71 | } 72 | } -------------------------------------------------------------------------------- /source/tasks/CreateOctopusRelease/CreateOctopusReleaseV5/createRelease.test.ts: -------------------------------------------------------------------------------- 1 | import { CreateRelease } from "./createRelease"; 2 | import { executeCommand, MockOctopusToolRunner } from "../../Utils/testing"; 3 | 4 | describe("Create Release", () => { 5 | test("Create a minimum release", async () => { 6 | const output = await executeCommand(() => new CreateRelease(new MockOctopusToolRunner(), { url: "http://octopus.com", apiKey: "myapikey", ignoreSslErrors: false }).run("my space", "my project")); 7 | 8 | expect(output).toContain("create-release --space my space --project my project --enableServiceMessages --server http://octopus.com --apiKey myapikey"); 9 | }); 10 | 11 | test("Create a release and deployment", async () => { 12 | const output = await executeCommand(() => 13 | new CreateRelease(new MockOctopusToolRunner(), { url: "http://octopus.com", apiKey: "myapikey", ignoreSslErrors: false }).run( 14 | "my space", 15 | "my project", 16 | "1.2.3", 17 | "mychannel", 18 | "special release notes", 19 | ["dev", "prod"], 20 | ["tenantA", "tenantB"], 21 | ["tagme", "tagyou"], 22 | false, 23 | "--myAdditionalArgumentToInclude", 24 | "mygitref", 25 | "mygitcommit" 26 | ) 27 | ); 28 | 29 | expect(output).toContain( 30 | "create-release --space my space --project my project --releaseNumber 1.2.3 --channel mychannel --gitCommit mygitcommit --gitRef mygitref --releaseNotes special release notes --enableServiceMessages --deployTo dev --deployTo prod --tenant tenantA --tenant tenantB --tenantTag tagme --tenantTag tagyou --server http://octopus.com --apiKey myapikey --myAdditionalArgumentToInclude" 31 | ); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /source/tasks/CreateOctopusRelease/CreateOctopusReleaseV5/createRelease.ts: -------------------------------------------------------------------------------- 1 | import { OctopusToolRunner } from "../../Utils/tool"; 2 | import { executeTask } from "../../Utils/octopusTasks"; 3 | import { OctoServerConnectionDetails } from "../../Utils/connection"; 4 | 5 | export class CreateRelease { 6 | constructor(readonly tool: OctopusToolRunner, readonly connection: OctoServerConnectionDetails) {} 7 | 8 | public async run( 9 | space: string, 10 | project: string, 11 | releaseNumber?: string | undefined, 12 | channel?: string | undefined, 13 | customReleaseNotes?: string | undefined, 14 | deployToEnvironments: string[] = [], 15 | deployForTenants: string[] = [], 16 | deployForTenantTags: string[] = [], 17 | deploymentProgress?: boolean | undefined, 18 | additionalArguments?: string | undefined, 19 | gitRef?: string | undefined, 20 | gitCommit?: string | undefined 21 | ) { 22 | this.tool.arg("create-release"); 23 | this.tool.arg(["--space", space]); 24 | this.tool.arg(["--project", project]); 25 | this.tool.argIf(releaseNumber, ["--releaseNumber", `${releaseNumber}`]); 26 | this.tool.argIf(channel, ["--channel", `${channel}`]); 27 | this.tool.argIf(gitCommit, ["--gitCommit", `${gitCommit}`]); 28 | this.tool.argIf(gitRef, ["--gitRef", `${gitRef}`]); 29 | this.tool.argIf(customReleaseNotes, ["--releaseNotes", `${customReleaseNotes}`]); 30 | this.tool.arg("--enableServiceMessages"); 31 | this.tool.argIf(deploymentProgress, "--progress"); 32 | for (const item of deployToEnvironments) { 33 | this.tool.arg(["--deployTo", item]); 34 | } 35 | for (const item of deployForTenants) { 36 | this.tool.arg(["--tenant", item]); 37 | } 38 | for (const item of deployForTenantTags) { 39 | this.tool.arg(["--tenantTag", item]); 40 | } 41 | 42 | await executeTask(this.tool, "(release;create;v5)", this.connection, "Create release succeeded.", "Failed to create release.", additionalArguments); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /source/tasks/CreateOctopusRelease/CreateOctopusReleaseV5/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/tasks/CreateOctopusRelease/CreateOctopusReleaseV5/icon.png -------------------------------------------------------------------------------- /source/tasks/CreateOctopusRelease/CreateOctopusReleaseV5/icon.svg: -------------------------------------------------------------------------------- 1 | octopus -------------------------------------------------------------------------------- /source/tasks/CreateOctopusRelease/CreateOctopusReleaseV5/index.ts: -------------------------------------------------------------------------------- 1 | import * as tasks from "azure-pipelines-task-lib/task"; 2 | import { getDelimitedInput, getRequiredInput } from "../../Utils/inputs"; 3 | import { CreateRelease } from "./createRelease"; 4 | import { getDefaultOctopusConnectionDetailsOrThrow } from "../../Utils/connection"; 5 | import { getOctopusCliTool } from "../../Utils/tool"; 6 | 7 | const space = getRequiredInput("Space"); 8 | const project = getRequiredInput("ProjectName"); 9 | const releaseNumber = tasks.getInput("ReleaseNumber"); 10 | const channel = tasks.getInput("Channel"); 11 | const customReleaseNotes = tasks.getInput("CustomReleaseNotes"); 12 | const deployToEnvironments = getDelimitedInput("DeployToEnvironment"); 13 | const deployForTenants = getDelimitedInput("DeployForTenants"); 14 | const deployForTenantTags = getDelimitedInput("DeployForTenantTags"); 15 | const deploymentProgress = tasks.getBoolInput("DeploymentProgress"); 16 | const additionalArguments = tasks.getInput("AdditionalArguments"); 17 | const gitRef = tasks.getInput("GitRef"); 18 | const gitCommit = tasks.getInput("GitCommit"); 19 | 20 | const connection = getDefaultOctopusConnectionDetailsOrThrow(); 21 | 22 | new CreateRelease(getOctopusCliTool(), connection).run(space, project, releaseNumber, channel, customReleaseNotes, deployToEnvironments, deployForTenants, deployForTenantTags, deploymentProgress, additionalArguments, gitRef, gitCommit); 23 | -------------------------------------------------------------------------------- /source/tasks/CreateOctopusRelease/CreateOctopusReleaseV6/createRelease.ts: -------------------------------------------------------------------------------- 1 | import os from "os"; 2 | import { Client, CreateReleaseCommandV1, Logger, ReleaseRepository } from "@octopusdeploy/api-client"; 3 | import { TaskWrapper } from "tasks/Utils/taskInput"; 4 | 5 | // Returns the release number that was actually created in Octopus 6 | export async function createReleaseFromInputs(client: Client, command: CreateReleaseCommandV1, task: TaskWrapper, logger: Logger): Promise { 7 | logger.info?.("🐙 Creating a release in Octopus Deploy..."); 8 | 9 | try { 10 | const repository = new ReleaseRepository(client, command.spaceName); 11 | const response = await repository.create(command); 12 | 13 | if (command.IgnoreIfAlreadyExists) { 14 | client.info(`🎉 Release ${response.ReleaseVersion} is ready for deployment!`); 15 | } else { 16 | client.info(`🎉 Release ${response.ReleaseVersion} created successfully!`); 17 | } 18 | 19 | task.setOutputVariable("release_number", response.ReleaseVersion); 20 | 21 | return response.ReleaseVersion; 22 | } catch (error: unknown) { 23 | if (error instanceof Error) { 24 | task.setFailure(`"Failed to execute command. ${error.message}${os.EOL}${error.stack}`, true); 25 | } else { 26 | task.setFailure(`"Failed to execute command. ${error}`, true); 27 | } 28 | throw error; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /source/tasks/CreateOctopusRelease/CreateOctopusReleaseV6/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/tasks/CreateOctopusRelease/CreateOctopusReleaseV6/icon.png -------------------------------------------------------------------------------- /source/tasks/CreateOctopusRelease/CreateOctopusReleaseV6/index.ts: -------------------------------------------------------------------------------- 1 | import { getDefaultOctopusConnectionDetailsOrThrow } from "../../Utils/connection"; 2 | import { ConcreteTaskWrapper, TaskWrapper } from "tasks/Utils/taskInput"; 3 | import { Logger } from "@octopusdeploy/api-client"; 4 | import * as tasks from "azure-pipelines-task-lib/task"; 5 | import { Release } from "./release"; 6 | 7 | const connection = getDefaultOctopusConnectionDetailsOrThrow(); 8 | 9 | const logger: Logger = { 10 | debug: (message) => { 11 | tasks.debug(message); 12 | }, 13 | info: (message) => console.log(message), 14 | warn: (message) => tasks.warning(message), 15 | error: (message, err) => { 16 | if (err !== undefined) { 17 | tasks.error(err.message); 18 | } else { 19 | tasks.error(message); 20 | } 21 | }, 22 | }; 23 | 24 | const task: TaskWrapper = new ConcreteTaskWrapper(); 25 | 26 | new Release(connection, task, logger).run(); 27 | -------------------------------------------------------------------------------- /source/tasks/CreateOctopusRelease/CreateOctopusReleaseV6/release.ts: -------------------------------------------------------------------------------- 1 | import { Client, CreateReleaseCommandV1, Logger, Project, ProjectRepository, resolveSpaceId } from "@octopusdeploy/api-client"; 2 | import { getDeepLink, OctoServerConnectionDetails } from "../../Utils/connection"; 3 | import { createReleaseFromInputs } from "./createRelease"; 4 | import { createCommandFromInputs } from "./inputCommandBuilder"; 5 | import os from "os"; 6 | import { TaskWrapper } from "tasks/Utils/taskInput"; 7 | import path from "path"; 8 | import { getVstsEnvironmentVariables } from "../../../tasksLegacy/Utils/environment"; 9 | import { v4 as uuidv4 } from "uuid"; 10 | import * as tasks from "azure-pipelines-task-lib"; 11 | import { getClient } from "../../Utils/client"; 12 | 13 | export class Release { 14 | constructor(readonly connection: OctoServerConnectionDetails, readonly task: TaskWrapper, readonly logger: Logger) {} 15 | 16 | public async run() { 17 | try { 18 | const command = createCommandFromInputs(this.logger, this.task); 19 | const client = await getClient(this.connection, this.logger, "release", "create", 6); 20 | const version = await createReleaseFromInputs(client, command, this.task, this.logger); 21 | 22 | await this.tryCreateSummary(client, command, version); 23 | 24 | this.task.setSuccess("Release creation succeeded."); 25 | } catch (error: unknown) { 26 | if (error instanceof Error) { 27 | this.task.setFailure(`"Failed to successfully create release. ${error.message}${os.EOL}${error.stack}`, true); 28 | } else { 29 | this.task.setFailure(`"Failed to successfully create release. ${error}`, true); 30 | } 31 | throw error; 32 | } 33 | } 34 | 35 | private async tryCreateSummary(client: Client, command: CreateReleaseCommandV1, version: string) { 36 | const spaceId = await resolveSpaceId(client, command.spaceName); 37 | const projectRepo = new ProjectRepository(client, command.spaceName); 38 | const projects = await projectRepo.list({ partialName: command.ProjectName }); 39 | const matchedProjects = projects.Items.filter((p: Project) => p.Name.localeCompare(command.ProjectName) === 0); 40 | if (matchedProjects.length === 1) { 41 | const link = getDeepLink(this.connection.url, `${spaceId}/projects/${matchedProjects[0].Id}/deployments/releases/${version}`); 42 | const markdown = `[Release ${version} created for '${matchedProjects[0].Name}'](${link})`; 43 | const markdownFile = path.join(getVstsEnvironmentVariables().defaultWorkingDirectory, `${uuidv4()}.md`); 44 | tasks.writeFile(markdownFile, markdown); 45 | tasks.addAttachment("Distributedtask.Core.Summary", "Octopus Create Release", markdownFile); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /source/tasks/Deploy/DeployV5/deploy.test.ts: -------------------------------------------------------------------------------- 1 | import { executeCommand, MockOctopusToolRunner } from "../../Utils/testing"; 2 | import { Deploy } from "./deploy"; 3 | 4 | describe("Deploy Release", () => { 5 | test("Create a minimum deployment", async () => { 6 | const output = await executeCommand(() => new Deploy(new MockOctopusToolRunner(), { url: "http://octopus.com", apiKey: "myapikey", ignoreSslErrors: false }).run("my space", "my project", "1.2.3", ["dev"])); 7 | 8 | expect(output).toContain("deploy-release --space my space --project my project --releaseNumber 1.2.3 --enableServiceMessages --deployTo dev --server http://octopus.com --apiKey myapikey"); 9 | }); 10 | 11 | test("Create a deployment", async () => { 12 | const output = await executeCommand(() => 13 | new Deploy(new MockOctopusToolRunner(), { url: "http://octopus.com", apiKey: "myapikey", ignoreSslErrors: false }).run( 14 | "my space", 15 | "my project", 16 | "1.2.3", 17 | ["dev", "prod"], 18 | ["tenantA", "tenantB"], 19 | ["tagme", "tagyou"], 20 | false, 21 | "--myAdditionalArgumentToInclude" 22 | ) 23 | ); 24 | 25 | expect(output).toContain( 26 | "deploy-release --space my space --project my project --releaseNumber 1.2.3 --enableServiceMessages --deployTo dev --deployTo prod --tenant tenantA --tenant tenantB --tenantTag tagme --tenantTag tagyou --server http://octopus.com --apiKey myapikey --myAdditionalArgumentToInclude" 27 | ); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /source/tasks/Deploy/DeployV5/deploy.ts: -------------------------------------------------------------------------------- 1 | import { OctopusToolRunner } from "../../Utils/tool"; 2 | import { OctoServerConnectionDetails } from "../../Utils/connection"; 3 | import { executeTask } from "../../Utils/octopusTasks"; 4 | 5 | export class Deploy { 6 | constructor(readonly tool: OctopusToolRunner, readonly connection: OctoServerConnectionDetails) {} 7 | 8 | public async run( 9 | space: string, 10 | project: string, 11 | releaseNumber: string, 12 | deployToEnvironments: string[], 13 | deployForTenants: string[] = [], 14 | deployForTenantTags: string[] = [], 15 | showProgress?: boolean | undefined, 16 | additionalArguments?: string | undefined 17 | ) { 18 | this.tool.arg("deploy-release"); 19 | this.tool.arg(["--space", space]); 20 | this.tool.arg(["--project", project]); 21 | this.tool.argIf(releaseNumber, ["--releaseNumber", releaseNumber]); 22 | this.tool.arg("--enableServiceMessages"); 23 | this.tool.argIf(showProgress, "--progress"); 24 | for (const item of deployToEnvironments) { 25 | this.tool.arg(["--deployTo", item]); 26 | } 27 | for (const item of deployForTenants) { 28 | this.tool.arg(["--tenant", item]); 29 | } 30 | for (const item of deployForTenantTags) { 31 | this.tool.arg(["--tenantTag", item]); 32 | } 33 | 34 | let stepIdentifier = "(release;deploy;v5)"; 35 | if (deployForTenants.length > 0 || deployForTenantTags.length > 0) { 36 | stepIdentifier = "(release;deploy-tenanted;v5)"; 37 | } 38 | 39 | await executeTask(this.tool, stepIdentifier, this.connection, "Deployment succeeded.", "Failed to deploy release.", additionalArguments); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /source/tasks/Deploy/DeployV5/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/tasks/Deploy/DeployV5/icon.png -------------------------------------------------------------------------------- /source/tasks/Deploy/DeployV5/icon.svg: -------------------------------------------------------------------------------- 1 | octopus -------------------------------------------------------------------------------- /source/tasks/Deploy/DeployV5/index.ts: -------------------------------------------------------------------------------- 1 | import * as tasks from "azure-pipelines-task-lib/task"; 2 | import { getDefaultOctopusConnectionDetailsOrThrow } from "../../Utils/connection"; 3 | import { getDelimitedInput, getRequiredInput } from "../../Utils/inputs"; 4 | import { Deploy } from "./deploy"; 5 | import { getOctopusCliTool } from "../../Utils/tool"; 6 | 7 | const space = getRequiredInput("Space"); 8 | const project = getRequiredInput("Project"); 9 | const releaseNumber = getRequiredInput("ReleaseNumber"); 10 | const deployToEnvironments = getDelimitedInput("Environments"); 11 | const deployForTenants = getDelimitedInput("DeployForTenants"); 12 | const deployForTenantTags = getDelimitedInput("DeployForTenantTags"); 13 | const deploymentProgress = tasks.getBoolInput("ShowProgress"); 14 | const additionalArguments = tasks.getInput("AdditionalArguments"); 15 | 16 | const connection = getDefaultOctopusConnectionDetailsOrThrow(); 17 | 18 | new Deploy(getOctopusCliTool(), connection).run(space, project, releaseNumber, deployToEnvironments, deployForTenants, deployForTenantTags, deploymentProgress, additionalArguments); 19 | -------------------------------------------------------------------------------- /source/tasks/Deploy/DeployV6/createDeployment.ts: -------------------------------------------------------------------------------- 1 | import { Client, CreateDeploymentUntenantedCommandV1, DeploymentRepository, EnvironmentRepository, Logger } from "@octopusdeploy/api-client"; 2 | import os from "os"; 3 | import { TaskWrapper } from "../../Utils/taskInput"; 4 | import { ExecutionResult } from "../../Utils/executionResult"; 5 | 6 | export async function createDeploymentFromInputs(client: Client, command: CreateDeploymentUntenantedCommandV1, task: TaskWrapper, logger: Logger): Promise { 7 | logger.info?.("🐙 Deploying a release in Octopus Deploy..."); 8 | 9 | try { 10 | const deploymentRepository = new DeploymentRepository(client, command.spaceName); 11 | const response = await deploymentRepository.create(command); 12 | 13 | client.info(`🎉 ${response.DeploymentServerTasks.length} Deployment${response.DeploymentServerTasks.length > 1 ? "s" : ""} queued successfully!`); 14 | 15 | if (response.DeploymentServerTasks.length === 0) { 16 | throw new Error("Expected at least one deployment to be queued."); 17 | } 18 | if (response.DeploymentServerTasks[0].ServerTaskId === null || response.DeploymentServerTasks[0].ServerTaskId === undefined) { 19 | throw new Error("Server task id was not deserialized correctly."); 20 | } 21 | 22 | const deploymentIds = response.DeploymentServerTasks.map((x) => x.DeploymentId); 23 | 24 | const deployments = await deploymentRepository.list({ ids: deploymentIds, take: deploymentIds.length }); 25 | 26 | const envIds = deployments.Items.map((d) => d.EnvironmentId); 27 | const envRepository = new EnvironmentRepository(client, command.spaceName); 28 | const environments = await envRepository.list({ ids: envIds, take: envIds.length }); 29 | 30 | const results = response.DeploymentServerTasks.map((x) => { 31 | return { 32 | serverTaskId: x.ServerTaskId, 33 | environmentName: environments.Items.filter((e) => e.Id === deployments.Items.filter((d) => d.TaskId === x.ServerTaskId)[0].EnvironmentId)[0].Name, 34 | type: "Deployment", 35 | } as ExecutionResult; 36 | }); 37 | 38 | task.setOutputVariable("server_tasks", JSON.stringify(results)); 39 | 40 | return results; 41 | } catch (error: unknown) { 42 | if (error instanceof Error) { 43 | task.setFailure(`"Failed to execute command. ${error.message}${os.EOL}${error.stack}`, true); 44 | } else { 45 | task.setFailure(`"Failed to execute command. ${error}`, true); 46 | } 47 | throw error; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /source/tasks/Deploy/DeployV6/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/tasks/Deploy/DeployV6/icon.png -------------------------------------------------------------------------------- /source/tasks/Deploy/DeployV6/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /source/tasks/Deploy/DeployV6/index.ts: -------------------------------------------------------------------------------- 1 | import { getDefaultOctopusConnectionDetailsOrThrow } from "../../Utils/connection"; 2 | import { Deploy } from "./deploy"; 3 | import { ConcreteTaskWrapper, TaskWrapper } from "tasks/Utils/taskInput"; 4 | import { Logger } from "@octopusdeploy/api-client"; 5 | import * as tasks from "azure-pipelines-task-lib/task"; 6 | 7 | const connection = getDefaultOctopusConnectionDetailsOrThrow(); 8 | 9 | const logger: Logger = { 10 | debug: (message) => { 11 | tasks.debug(message); 12 | }, 13 | info: (message) => console.log(message), 14 | warn: (message) => tasks.warning(message), 15 | error: (message, err) => { 16 | if (err !== undefined) { 17 | tasks.error(err.message); 18 | } else { 19 | tasks.error(message); 20 | } 21 | }, 22 | }; 23 | 24 | const task: TaskWrapper = new ConcreteTaskWrapper(); 25 | 26 | new Deploy(connection, task, logger).run(); 27 | -------------------------------------------------------------------------------- /source/tasks/DeployTenant/TenantedDeployV6/createDeployment.ts: -------------------------------------------------------------------------------- 1 | import { Client, CreateDeploymentTenantedCommandV1, DeploymentRepository, Logger, TenantRepository } from "@octopusdeploy/api-client"; 2 | import os from "os"; 3 | import { TaskWrapper } from "tasks/Utils/taskInput"; 4 | import { ExecutionResult } from "../../Utils/executionResult"; 5 | 6 | export async function createDeploymentFromInputs(client: Client, command: CreateDeploymentTenantedCommandV1, task: TaskWrapper, logger: Logger): Promise { 7 | logger.info?.("🐙 Deploying a release in Octopus Deploy..."); 8 | 9 | try { 10 | const deploymentRepository = new DeploymentRepository(client, command.spaceName); 11 | const response = await deploymentRepository.createTenanted(command); 12 | 13 | client.info(`🎉 ${response.DeploymentServerTasks.length} Deployment${response.DeploymentServerTasks.length > 1 ? "s" : ""} queued successfully!`); 14 | 15 | if (response.DeploymentServerTasks.length === 0) { 16 | throw new Error("Expected at least one deployment to be queued."); 17 | } 18 | if (response.DeploymentServerTasks[0].ServerTaskId === null || response.DeploymentServerTasks[0].ServerTaskId === undefined) { 19 | throw new Error("Server task id was not deserialized correctly."); 20 | } 21 | 22 | const deploymentIds = response.DeploymentServerTasks.map((x) => x.DeploymentId); 23 | 24 | const deployments = await deploymentRepository.list({ ids: deploymentIds, take: deploymentIds.length }); 25 | 26 | const tenantIds = deployments.Items.map((d) => d.TenantId || ""); 27 | const tenantRepository = new TenantRepository(client, command.spaceName); 28 | const tenants = await tenantRepository.list({ ids: tenantIds, take: tenantIds.length }); 29 | 30 | const results = response.DeploymentServerTasks.map((x) => { 31 | return { 32 | serverTaskId: x.ServerTaskId, 33 | environmentName: command.EnvironmentName, 34 | tenantName: tenants.Items.filter((e) => e.Id === deployments.Items.filter((d) => d.TaskId === x.ServerTaskId)[0].TenantId)[0].Name, 35 | type: "Deployment", 36 | } as ExecutionResult; 37 | }); 38 | 39 | task.setOutputVariable("server_tasks", JSON.stringify(results)); 40 | 41 | return results; 42 | } catch (error: unknown) { 43 | if (error instanceof Error) { 44 | task.setFailure(`"Failed to execute command. ${error.message}${os.EOL}${error.stack}`, true); 45 | } else { 46 | task.setFailure(`"Failed to execute command. ${error}`, true); 47 | } 48 | throw error; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /source/tasks/DeployTenant/TenantedDeployV6/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/tasks/DeployTenant/TenantedDeployV6/icon.png -------------------------------------------------------------------------------- /source/tasks/DeployTenant/TenantedDeployV6/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /source/tasks/DeployTenant/TenantedDeployV6/index.ts: -------------------------------------------------------------------------------- 1 | import { getDefaultOctopusConnectionDetailsOrThrow } from "../../Utils/connection"; 2 | import { Deploy } from "./deploy"; 3 | import { ConcreteTaskWrapper, TaskWrapper } from "tasks/Utils/taskInput"; 4 | import { Logger } from "@octopusdeploy/api-client"; 5 | import * as tasks from "azure-pipelines-task-lib/task"; 6 | 7 | const connection = getDefaultOctopusConnectionDetailsOrThrow(); 8 | 9 | const logger: Logger = { 10 | debug: (message) => { 11 | tasks.debug(message); 12 | }, 13 | info: (message) => console.log(message), 14 | warn: (message) => tasks.warning(message), 15 | error: (message, err) => { 16 | if (err !== undefined) { 17 | tasks.error(err.message); 18 | } else { 19 | tasks.error(message); 20 | } 21 | }, 22 | }; 23 | 24 | const task: TaskWrapper = new ConcreteTaskWrapper(); 25 | 26 | new Deploy(connection, task, logger).run(); 27 | -------------------------------------------------------------------------------- /source/tasks/OctoCli/OctoCliV5/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/tasks/OctoCli/OctoCliV5/icon.png -------------------------------------------------------------------------------- /source/tasks/OctoCli/OctoCliV5/icon.svg: -------------------------------------------------------------------------------- 1 | octopus -------------------------------------------------------------------------------- /source/tasks/OctoCli/OctoCliV5/index.ts: -------------------------------------------------------------------------------- 1 | import * as tasks from "azure-pipelines-task-lib/task"; 2 | import { getDefaultOctopusConnectionDetailsOrThrow } from "../../Utils/connection"; 3 | import { getRequiredInput } from "../../Utils/inputs"; 4 | import { OctoCli } from "./octoCli"; 5 | import { getOctopusCliTool } from "../../Utils/tool"; 6 | 7 | const command = getRequiredInput("command"); 8 | const args = tasks.getInput("args"); 9 | 10 | const connection = getDefaultOctopusConnectionDetailsOrThrow(); 11 | 12 | new OctoCli(getOctopusCliTool(), command, connection).run(args); 13 | -------------------------------------------------------------------------------- /source/tasks/OctoCli/OctoCliV5/octoCli.test.ts: -------------------------------------------------------------------------------- 1 | import { executeCommand, MockOctopusToolRunner } from "../../Utils/testing"; 2 | import { OctoCli } from "./octoCli"; 3 | 4 | describe("Promote Release", () => { 5 | test("Run a simple promote", async () => { 6 | const output = await executeCommand(() => 7 | new OctoCli(new MockOctopusToolRunner(), "list-projects", { 8 | url: "http://octopus.com", 9 | apiKey: "myapikey", 10 | ignoreSslErrors: true, 11 | }).run('--space "my space"') 12 | ); 13 | 14 | expect(output).toContain('list-projects --server http://octopus.com --apiKey myapikey --ignoreSslErrors --space "my space"'); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /source/tasks/OctoCli/OctoCliV5/octoCli.ts: -------------------------------------------------------------------------------- 1 | import { OctoServerConnectionDetails } from "../../Utils/connection"; 2 | import { executeTask } from "../../Utils/octopusTasks"; 3 | import { OctopusToolRunner } from "../../Utils/tool"; 4 | import * as tasks from "azure-pipelines-task-lib"; 5 | 6 | export class OctoCli { 7 | constructor(readonly tool: OctopusToolRunner, readonly command: string, readonly connection: OctoServerConnectionDetails) {} 8 | 9 | public async run(args: string | undefined) { 10 | this.tool.arg(this.command); 11 | tasks.warning("This task is using a deprecated version of the Octopus CLI, we recommend using the latest version."); 12 | await executeTask(this.tool, "(cli;run;v5)", this.connection, "Succeeded executing octo command.", "Failed to execute octo command.", args); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /source/tasks/OctoCli/OctoCliV5/task.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "25fee290-e578-491b-b1db-dbc3980df1d0", 3 | "name": "OctoCli", 4 | "friendlyName": "Invoke Octopus CLI command", 5 | "description": "Invoke an Octopus CLI command", 6 | "helpMarkDown": "set-by-pack.ps1", 7 | "category": "Tool", 8 | "visibility": [ 9 | "Build", 10 | "Release" 11 | ], 12 | "author": "Octopus Deploy", 13 | "version": { 14 | "Major": 5, 15 | "Minor": 0, 16 | "Patch": 0 17 | }, 18 | "demands": ["octo"], 19 | "minimumAgentVersion": "2.144.0", 20 | "groups": [ 21 | { 22 | "name": "advanced", 23 | "displayName": "Advanced Options", 24 | "isExpanded": false 25 | } 26 | ], 27 | "inputs": [ 28 | { 29 | "name": "OctoConnectedServiceName", 30 | "type": "connectedService:OctopusEndpoint", 31 | "label": "Octopus Deploy Server", 32 | "defaultValue": "", 33 | "required": true, 34 | "helpMarkDown": "Octopus Deploy server connection" 35 | }, 36 | { 37 | "name": "command", 38 | "type": "string", 39 | "label": "command", 40 | "defaultValue": "", 41 | "required": true, 42 | "helpMarkDown": "The Octopus CLI command to execute." 43 | }, 44 | { 45 | "name": "args", 46 | "type": "string", 47 | "label": "arguments", 48 | "defaultValue": "", 49 | "required": false, 50 | "helpMarkDown": "The arguments to use for the command." 51 | } 52 | ], 53 | "instanceNameFormat": "Invoke an Octopus CLI command", 54 | "execution": { 55 | "Node10": { 56 | "target": "index.js" 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /source/tasks/OctoInstaller/OctoInstallerV5/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/tasks/OctoInstaller/OctoInstallerV5/icon.png -------------------------------------------------------------------------------- /source/tasks/OctoInstaller/OctoInstallerV5/icon.svg: -------------------------------------------------------------------------------- 1 | command-line -------------------------------------------------------------------------------- /source/tasks/OctoInstaller/OctoInstallerV5/index.ts: -------------------------------------------------------------------------------- 1 | import * as tasks from "azure-pipelines-task-lib/task"; 2 | import { Installer } from "./installer"; 3 | 4 | const version = tasks.getInput("version", true) || ""; 5 | new Installer("https://g.octopushq.com").run(version); 6 | -------------------------------------------------------------------------------- /source/tasks/OctoInstaller/OctoInstallerV5/installer.ts: -------------------------------------------------------------------------------- 1 | import * as tools from "azure-pipelines-tool-lib"; 2 | import * as tasks from "azure-pipelines-task-lib"; 3 | import os from "os"; 4 | import path from "path"; 5 | import { executeWithSetResult } from "../../Utils/octopusTasks"; 6 | import { DownloadEndpointRetriever, Endpoint } from "./downloadEndpointRetriever"; 7 | 8 | const TOOL_NAME = "octo"; 9 | 10 | const osPlat: string = os.platform(); 11 | 12 | export class Installer { 13 | constructor(readonly octopurlsUrl: string) {} 14 | 15 | public async run(versionSpec: string) { 16 | await executeWithSetResult( 17 | async () => { 18 | tasks.warning("This task is using a deprecated version of the Octopus CLI, we recommend using the latest version."); 19 | const endpoint = await new DownloadEndpointRetriever(this.octopurlsUrl).getEndpoint(versionSpec); 20 | let toolPath = tools.findLocalTool(TOOL_NAME, endpoint.version); 21 | 22 | if (!toolPath) { 23 | toolPath = await this.installTool(endpoint); 24 | toolPath = tools.findLocalTool(TOOL_NAME, endpoint.version); 25 | } 26 | 27 | tools.prependPath(toolPath); 28 | }, 29 | `Installed octo v${versionSpec}.`, 30 | `Failed to install octo v${versionSpec}.` 31 | ); 32 | } 33 | 34 | private async installTool(endpoint: Endpoint): Promise { 35 | if (!endpoint.downloadUrl) { 36 | throw Error(`Failed to download Octopus CLI tool version ${endpoint.version}.`); 37 | } 38 | 39 | const downloadPath = await tools.downloadTool(endpoint.downloadUrl); 40 | 41 | // 42 | // Extract 43 | // 44 | let extPath: string; 45 | if (osPlat == "win32") { 46 | extPath = tasks.getVariable("Agent.TempDirectory") || ""; 47 | if (!extPath) { 48 | throw new Error("Expected Agent.TempDirectory to be set"); 49 | } 50 | 51 | extPath = path.join(extPath, "n"); // use as short a path as possible due to nested node_modules folders 52 | extPath = await tools.extractZip(downloadPath, extPath); 53 | } else { 54 | extPath = await tools.extractTar(downloadPath); 55 | } 56 | 57 | return await tools.cacheDir(extPath, TOOL_NAME, endpoint.version); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /source/tasks/OctoInstaller/OctoInstallerV5/octopusCLIVersionFetcher.test.ts: -------------------------------------------------------------------------------- 1 | import { OctopusCLIVersionFetcher } from "./octopusCLIVersionFetcher"; 2 | 3 | describe("OctopusCLIVersionFetcher tests", () => { 4 | test("Gets latest", () => { 5 | const fetcher = new OctopusCLIVersionFetcher(["1.0.0", "2.0.0", "2.1.0"]); 6 | 7 | const version = fetcher.getVersion("*"); 8 | 9 | expect(version).toBe("2.1.0"); 10 | }); 11 | 12 | test("Fixed returns fixed version", () => { 13 | const fetcher = new OctopusCLIVersionFetcher(["1.0.0", "2.0.0", "2.1.0"]); 14 | 15 | const version = fetcher.getVersion("1.0.0"); 16 | 17 | expect(version).toBe("1.0.0"); 18 | }); 19 | 20 | test("When version no exists", () => { 21 | const fetcher = new OctopusCLIVersionFetcher(["1.0.0", "2.0.0", "2.1.0"]); 22 | 23 | expect(() => fetcher.getVersion("5.0.0")).toThrow(); 24 | }); 25 | 26 | test("Get latest minor", () => { 27 | const fetcher = new OctopusCLIVersionFetcher(["1.0.0", "2.0.0", "2.1.0", "3.0.0"]); 28 | 29 | const version = fetcher.getVersion("2.*"); 30 | 31 | expect(version).toBe("2.1.0"); 32 | }); 33 | 34 | test("Get latest patch", () => { 35 | const fetcher = new OctopusCLIVersionFetcher(["1.0.0", "1.0.3", "2.1.0", "3.0.0"]); 36 | 37 | const version = fetcher.getVersion("1.0.*"); 38 | 39 | expect(version).toBe("1.0.3"); 40 | }); 41 | 42 | test("When version spec if invalid", () => { 43 | const fetcher = new OctopusCLIVersionFetcher(["1.0.0", "2.0.0", "2.1.0"]); 44 | 45 | expect(() => fetcher.getVersion("*.*")).toThrow(); 46 | 47 | expect(() => fetcher.getVersion("*.2")).toThrow(); 48 | 49 | expect(() => fetcher.getVersion("sdfs")).toThrow(); 50 | }); 51 | 52 | test("Get latest major", () => { 53 | const fetcher = new OctopusCLIVersionFetcher(["1.0.0", "2.0.0", "2.1.0", "3.0.0"]); 54 | 55 | const version = fetcher.getVersion("2"); 56 | 57 | expect(version).toBe("2.1.0"); 58 | }); 59 | 60 | test("Get latest not pre-release", () => { 61 | const fetcher = new OctopusCLIVersionFetcher(["1.0.0", "1.0.3", "2.1.0", "3.0.0", "4.0.0-pre"]); 62 | 63 | const version = fetcher.getVersion("*"); 64 | 65 | expect(version).toBe("3.0.0"); 66 | }); 67 | }); 68 | -------------------------------------------------------------------------------- /source/tasks/OctoInstaller/OctoInstallerV5/octopusCLIVersionFetcher.ts: -------------------------------------------------------------------------------- 1 | import { maxSatisfying, valid } from "semver"; 2 | 3 | export class OctopusCLIVersionFetcher { 4 | constructor(readonly versions: string[]) {} 5 | 6 | public getVersion(versionSpec: string) { 7 | if (versionSpec === "*") { 8 | return maxSatisfying(this.versions, versionSpec); 9 | } 10 | 11 | if (valid(versionSpec) === null) { 12 | const parts = versionSpec.split("."); 13 | if (parts.length > 3) { 14 | throw new Error(`The '${versionSpec}' is an invalid version, a version needs to be a maximum of three parts.`); 15 | } 16 | 17 | if (parts.length === 1) { 18 | const majorVersion = parts[0]; 19 | if (Number.isNaN(Number.parseInt(majorVersion))) { 20 | throw new Error(`The '${versionSpec}' version needs to specify a number or '*' for its major part.`); 21 | } 22 | } 23 | 24 | if (parts.length === 2) { 25 | const majorVersion = parts[0]; 26 | const minorVersion = parts[1]; 27 | 28 | // the major version number must be a number 29 | if (Number.isNaN(Number.parseInt(majorVersion))) { 30 | throw new Error(`The '${versionSpec}' version needs to specify a number for its major part.`); 31 | } 32 | 33 | if (minorVersion !== "*" && Number.isNaN(Number.parseInt(minorVersion))) { 34 | throw new Error(`The '${versionSpec}' version needs to specify a number or '*' for its minor part.`); 35 | } 36 | } 37 | 38 | if (parts.length === 3) { 39 | const majorVersion = parts[0]; 40 | const minorVersion = parts[1]; 41 | const patchVersion = parts[2]; 42 | 43 | // the major version number must be a number 44 | if (Number.isNaN(Number.parseInt(majorVersion))) { 45 | throw new Error(`The '${versionSpec}' version needs to specify a number for its major part.`); 46 | } 47 | 48 | // the minor version number must be a number 49 | if (Number.isNaN(Number.parseInt(minorVersion))) { 50 | throw new Error(`The '${versionSpec}' version needs to specify a number for its minor part.`); 51 | } 52 | 53 | if (patchVersion !== "*" && Number.isNaN(Number.parseInt(patchVersion))) { 54 | throw new Error(`The '${versionSpec}' version needs to specify a number or '*' for its patch part.`); 55 | } 56 | } 57 | } 58 | 59 | const version = maxSatisfying(this.versions, versionSpec); 60 | 61 | if (!version) { 62 | throw new Error(`A version satisfying '${versionSpec}' could not be found.`); 63 | } 64 | 65 | return version; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /source/tasks/OctoInstaller/OctoInstallerV5/task.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "57342b23-3a76-490a-8e78-25d4ade2f2e3", 3 | "name": "OctoInstaller", 4 | "friendlyName": "Octopus CLI Installer", 5 | "description": "Install a specific version of the Octopus CLI (C#)", 6 | "helpMarkDown": "Install a specific version of the Octopus CLI (C#)", 7 | "category": "Tool", 8 | "runsOn": [ 9 | "Agent", 10 | "DeploymentGroup" 11 | ], 12 | "visibility": [ 13 | "Build", 14 | "Release" 15 | ], 16 | "author": "Octopus Deploy", 17 | "version": { 18 | "Major": 5, 19 | "Minor": 0, 20 | "Patch": 0 21 | }, 22 | "satisfies": ["octo"], 23 | "demands": [], 24 | "minimumAgentVersion": "2.144.0", 25 | "groups": [ 26 | { 27 | "name": "advanced", 28 | "displayName": "Advanced Options", 29 | "isExpanded": false 30 | } 31 | ], 32 | "inputs": [ 33 | { 34 | "name": "version", 35 | "type": "string", 36 | "label": "Octopus CLI Version", 37 | "required": true, 38 | "helpMarkDown": "Specify version of OctopusCLI to install.
Versions can be given in the following formats
  • `8.*` => Install latest in major version.
  • `7.3.*` => Install latest in major and minor version.
  • `8.0.1` => Install exact version.
  • `*` => Install whatever is latest.

  • Find the value of `version` for installing OctopusCLI, from the [this link](https://g.octopushq.com/OctopusCLIVersions)." 39 | } 40 | ], 41 | "instanceNameFormat": "Use Octopus CLI tool version $(version)", 42 | "execution": { 43 | "Node10": { 44 | "target": "index.js" 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /source/tasks/OctoInstaller/OctoInstallerV6/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/tasks/OctoInstaller/OctoInstallerV6/icon.png -------------------------------------------------------------------------------- /source/tasks/OctoInstaller/OctoInstallerV6/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /source/tasks/OctoInstaller/OctoInstallerV6/index.ts: -------------------------------------------------------------------------------- 1 | import * as tasks from "azure-pipelines-task-lib/task"; 2 | import { Logger } from "@octopusdeploy/api-client"; 3 | import { Installer } from "./installer"; 4 | import os from "os"; 5 | 6 | async function run() { 7 | try { 8 | const version = tasks.getInput("octopusVersion", true) || ""; 9 | 10 | const logger: Logger = { 11 | debug: (message) => { 12 | tasks.debug(message); 13 | }, 14 | info: (message) => console.log(message), 15 | warn: (message) => tasks.warning(message), 16 | error: (message, err) => { 17 | if (err !== undefined) { 18 | tasks.error(err.message); 19 | } else { 20 | tasks.error(message); 21 | } 22 | }, 23 | }; 24 | 25 | new Installer("https://raw.githubusercontent.com/OctopusDeploy/cli/main/releases.json", os.platform(), os.arch(), logger).run(version); 26 | } catch (error: unknown) { 27 | if (error instanceof Error) { 28 | tasks.setResult(tasks.TaskResult.Failed, `"Failed to execute pack. ${error.message}${os.EOL}${error.stack}`, true); 29 | } else { 30 | tasks.setResult(tasks.TaskResult.Failed, `"Failed to execute pack. ${error}`, true); 31 | } 32 | } 33 | } 34 | 35 | run(); 36 | -------------------------------------------------------------------------------- /source/tasks/OctoInstaller/OctoInstallerV6/installer.ts: -------------------------------------------------------------------------------- 1 | import * as tools from "azure-pipelines-tool-lib"; 2 | import * as tasks from "azure-pipelines-task-lib"; 3 | import path from "path"; 4 | import { executeWithSetResult } from "../../Utils/octopusTasks"; 5 | import { DownloadEndpointRetriever, Endpoint } from "./downloadEndpointRetriever"; 6 | import { Logger } from "@octopusdeploy/api-client"; 7 | 8 | const TOOL_NAME = "octopus"; 9 | 10 | export class Installer { 11 | constructor(readonly releasesUrl: string, readonly osPlatform: string, readonly osArch: string, readonly logger: Logger) {} 12 | 13 | public async run(versionSpec: string) { 14 | await executeWithSetResult( 15 | async () => { 16 | const endpoint = await new DownloadEndpointRetriever(this.releasesUrl, this.osPlatform, this.osArch, this.logger).getEndpoint(versionSpec); 17 | let toolPath = tools.findLocalTool(TOOL_NAME, endpoint.version); 18 | 19 | if (!toolPath) { 20 | toolPath = await this.installTool(endpoint); 21 | toolPath = tools.findLocalTool(TOOL_NAME, endpoint.version); 22 | } 23 | 24 | tools.prependPath(toolPath); 25 | }, 26 | `Installed octopus v${versionSpec}.`, 27 | `Failed to install octopus v${versionSpec}.` 28 | ); 29 | } 30 | 31 | private async installTool(endpoint: Endpoint): Promise { 32 | if (!endpoint.downloadUrl) { 33 | throw Error(`Failed to download Octopus CLI tool version ${endpoint.version}.`); 34 | } 35 | 36 | const downloadPath = await tools.downloadTool(endpoint.downloadUrl); 37 | 38 | // 39 | // Extract 40 | // 41 | let extPath: string; 42 | if (this.osPlatform == "win32") { 43 | extPath = tasks.getVariable("Agent.TempDirectory") || ""; 44 | if (!extPath) { 45 | throw new Error("Expected Agent.TempDirectory to be set"); 46 | } 47 | 48 | extPath = path.join(extPath, "n"); // use as short a path as possible due to nested node_modules folders 49 | extPath = await tools.extractZip(downloadPath, extPath); 50 | } else { 51 | extPath = await tools.extractTar(downloadPath); 52 | } 53 | 54 | return await tools.cacheDir(extPath, TOOL_NAME, endpoint.version); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /source/tasks/OctoInstaller/OctoInstallerV6/octopusCLIVersionResolver.test.ts: -------------------------------------------------------------------------------- 1 | import { OctopusCLIVersionResolver } from "./octopusCLIVersionResolver"; 2 | 3 | describe("OctopusCLIVersionFetcher tests", () => { 4 | test("Gets latest", () => { 5 | const fetcher = new OctopusCLIVersionResolver(["1.0.0", "2.0.0", "2.1.0"]); 6 | 7 | const version = fetcher.getVersion("*"); 8 | 9 | expect(version).toBe("2.1.0"); 10 | }); 11 | 12 | test("Fixed returns fixed version", () => { 13 | const fetcher = new OctopusCLIVersionResolver(["1.0.0", "2.0.0", "2.1.0"]); 14 | 15 | const version = fetcher.getVersion("1.0.0"); 16 | 17 | expect(version).toBe("1.0.0"); 18 | }); 19 | 20 | test("When version no exists", () => { 21 | const fetcher = new OctopusCLIVersionResolver(["1.0.0", "2.0.0", "2.1.0"]); 22 | 23 | expect(() => fetcher.getVersion("5.0.0")).toThrow(); 24 | }); 25 | 26 | test("Get latest minor", () => { 27 | const fetcher = new OctopusCLIVersionResolver(["1.0.0", "2.0.0", "2.1.0", "3.0.0"]); 28 | 29 | const version = fetcher.getVersion("2.*"); 30 | 31 | expect(version).toBe("2.1.0"); 32 | }); 33 | 34 | test("Get latest patch", () => { 35 | const fetcher = new OctopusCLIVersionResolver(["1.0.0", "1.0.3", "2.1.0", "3.0.0"]); 36 | 37 | const version = fetcher.getVersion("1.0.*"); 38 | 39 | expect(version).toBe("1.0.3"); 40 | }); 41 | 42 | test("When version spec if invalid", () => { 43 | const fetcher = new OctopusCLIVersionResolver(["1.0.0", "2.0.0", "2.1.0"]); 44 | 45 | expect(() => fetcher.getVersion("*.*")).toThrow(); 46 | 47 | expect(() => fetcher.getVersion("*.2")).toThrow(); 48 | 49 | expect(() => fetcher.getVersion("sdfs")).toThrow(); 50 | }); 51 | 52 | test("Get latest major", () => { 53 | const fetcher = new OctopusCLIVersionResolver(["1.0.0", "2.0.0", "2.1.0", "3.0.0"]); 54 | 55 | const version = fetcher.getVersion("2"); 56 | 57 | expect(version).toBe("2.1.0"); 58 | }); 59 | 60 | test("Get latest not pre-release", () => { 61 | const fetcher = new OctopusCLIVersionResolver(["1.0.0", "1.0.3", "2.1.0", "3.0.0", "4.0.0-pre"]); 62 | 63 | const version = fetcher.getVersion("*"); 64 | 65 | expect(version).toBe("3.0.0"); 66 | }); 67 | }); 68 | -------------------------------------------------------------------------------- /source/tasks/OctoInstaller/OctoInstallerV6/task.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "57342b23-3a76-490a-8e78-25d4ade2f2e3", 3 | "name": "OctoInstaller", 4 | "friendlyName": "Octopus CLI Installer", 5 | "description": "Install a specific version of the Octopus CLI (Go)", 6 | "helpMarkDown": "Install a specific version of the Octopus CLI (Go)", 7 | "category": "Tool", 8 | "runsOn": [ 9 | "Agent", 10 | "DeploymentGroup" 11 | ], 12 | "visibility": [ 13 | "Build", 14 | "Release" 15 | ], 16 | "author": "Octopus Deploy", 17 | "version": { 18 | "Major": 6, 19 | "Minor": 0, 20 | "Patch": 0 21 | }, 22 | "satisfies": ["octopus"], 23 | "demands": [], 24 | "minimumAgentVersion": "2.206.1", 25 | "groups": [ 26 | { 27 | "name": "advanced", 28 | "displayName": "Advanced Options", 29 | "isExpanded": false 30 | } 31 | ], 32 | "inputs": [ 33 | { 34 | "name": "octopusVersion", 35 | "type": "string", 36 | "label": "Octopus CLI Version", 37 | "required": true, 38 | "helpMarkDown": "Specify version of Octopus CLI to install.
    Versions can be given in the following formats
  • `1.*` => Install latest in major version.
  • `7.3.*` => Install latest in major and minor version.
  • `8.0.1` => Install exact version.
  • `*` => Install whatever is latest.

  • Find the value of `version` for installing Octopus CLI, from the [this link](https://raw.githubusercontent.com/OctopusDeploy/cli/main/releases.json)." 39 | } 40 | ], 41 | "instanceNameFormat": "Install Octopus CLI tool version $(version)", 42 | "execution": { 43 | "Node16": { 44 | "target": "index.js" 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /source/tasks/OctopusMetadata/OctopusMetadataV5/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/tasks/OctopusMetadata/OctopusMetadataV5/icon.png -------------------------------------------------------------------------------- /source/tasks/OctopusMetadata/OctopusMetadataV5/icon.svg: -------------------------------------------------------------------------------- 1 | octopus -------------------------------------------------------------------------------- /source/tasks/OctopusMetadata/OctopusMetadataV5/index.ts: -------------------------------------------------------------------------------- 1 | import { getLineSeparatedItems, getOverwriteModeFromReplaceInput, getRequiredInput } from "../../Utils/inputs"; 2 | import * as tasks from "azure-pipelines-task-lib"; 3 | import { getDefaultOctopusConnectionDetailsOrThrow } from "../../Utils/connection"; 4 | import { BuildInformation } from "./buildInformation"; 5 | import { getOctopusCliTool } from "../../Utils/tool"; 6 | 7 | const space = getRequiredInput("Space"); 8 | const packageIds = getLineSeparatedItems(tasks.getInput("PackageId", true) || ""); 9 | const packageVersion = tasks.getInput("PackageVersion", true) || ""; 10 | const overwriteMode = getOverwriteModeFromReplaceInput(tasks.getInput("Replace", true) || ""); 11 | const additionalArguments = tasks.getInput("AdditionalArguments"); 12 | 13 | const connection = getDefaultOctopusConnectionDetailsOrThrow(); 14 | 15 | new BuildInformation(getOctopusCliTool(), connection).run(space, packageIds, packageVersion, overwriteMode, additionalArguments); 16 | -------------------------------------------------------------------------------- /source/tasks/PackNuGet/PackNuGetV6/create-package.ts: -------------------------------------------------------------------------------- 1 | import { Logger, NuGetPackageBuilder, NuGetPackArgs } from "@octopusdeploy/api-client"; 2 | import path from "path"; 3 | import fs from "fs"; 4 | import { InputParameters } from "./input-parameters"; 5 | import { isNullOrWhitespace } from "../../../tasksLegacy/Utils/inputs"; 6 | 7 | type createPackageResult = { 8 | filePath: string; 9 | filename: string; 10 | }; 11 | 12 | export async function createPackageFromInputs(parameters: InputParameters, logger: Logger): Promise { 13 | const builder = new NuGetPackageBuilder(); 14 | const inputs: NuGetPackArgs = { 15 | packageId: parameters.packageId, 16 | version: parameters.packageVersion, 17 | outputFolder: parameters.outputPath, 18 | basePath: parameters.sourcePath, 19 | inputFilePatterns: parameters.include, 20 | overwrite: parameters.overwrite, 21 | logger, 22 | }; 23 | 24 | inputs.nuspecArgs = { 25 | title: parameters.nuGetTitle, 26 | description: parameters.nuGetDescription, 27 | authors: parameters.nuGetAuthors, 28 | releaseNotes: parameters.nuGetReleaseNotes, 29 | }; 30 | 31 | if (!isNullOrWhitespace(parameters.nuGetReleaseNotesFile) && fs.existsSync(parameters.nuGetReleaseNotesFile) && fs.lstatSync(parameters.nuGetReleaseNotesFile).isFile()) { 32 | inputs.nuspecArgs.releaseNotes = fs.readFileSync(parameters.nuGetReleaseNotesFile).toString(); 33 | } 34 | 35 | const packageFilename = await builder.pack(inputs); 36 | 37 | return { filePath: path.join(parameters.outputPath, packageFilename), filename: packageFilename }; 38 | } 39 | -------------------------------------------------------------------------------- /source/tasks/PackNuGet/PackNuGetV6/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/tasks/PackNuGet/PackNuGetV6/icon.png -------------------------------------------------------------------------------- /source/tasks/PackNuGet/PackNuGetV6/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /source/tasks/PackNuGet/PackNuGetV6/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/ban-ts-comment */ 2 | import * as tasks from "azure-pipelines-task-lib/task"; 3 | import { Logger } from "@octopusdeploy/api-client"; 4 | import { getInputs } from "./input-parameters"; 5 | import os from "os"; 6 | import { createPackageFromInputs } from "./create-package"; 7 | 8 | async function run() { 9 | try { 10 | const parameters = getInputs(); 11 | 12 | const logger: Logger = { 13 | debug: (message) => { 14 | tasks.debug(message); 15 | }, 16 | info: (message) => console.log(message), 17 | warn: (message) => tasks.warning(message), 18 | error: (message, err) => { 19 | if (err !== undefined) { 20 | tasks.error(err.message); 21 | } else { 22 | tasks.error(message); 23 | } 24 | }, 25 | }; 26 | 27 | const result = await createPackageFromInputs(parameters, logger); 28 | 29 | tasks.setVariable("package_file_path", result.filePath); 30 | tasks.setVariable("package_filename", result.filename); 31 | 32 | tasks.setResult(tasks.TaskResult.Succeeded, "Pack succeeded"); 33 | } catch (error: unknown) { 34 | if (error instanceof Error) { 35 | tasks.setResult(tasks.TaskResult.Failed, `"Failed to execute pack. ${error.message}${os.EOL}${error.stack}`, true); 36 | } else { 37 | tasks.setResult(tasks.TaskResult.Failed, `"Failed to execute pack. ${error}`, true); 38 | } 39 | } 40 | } 41 | 42 | run(); 43 | -------------------------------------------------------------------------------- /source/tasks/PackNuGet/PackNuGetV6/input-parameters.ts: -------------------------------------------------------------------------------- 1 | import * as tasks from "azure-pipelines-task-lib/task"; 2 | import { removeTrailingSlashes, safeTrim } from "tasksLegacy/Utils/inputs"; 3 | import { getLineSeparatedItems } from "../../Utils/inputs"; 4 | 5 | export interface InputParameters { 6 | packageId: string; 7 | packageVersion: string; 8 | outputPath: string; 9 | sourcePath: string; 10 | include: string[]; 11 | nuGetDescription: string; 12 | nuGetAuthors: string[]; 13 | nuGetTitle?: string; 14 | nuGetReleaseNotes?: string; 15 | nuGetReleaseNotesFile?: string; 16 | overwrite?: boolean; 17 | } 18 | 19 | export const getInputs = (): InputParameters => { 20 | return { 21 | packageId: tasks.getInput("PackageId", true) || "", 22 | packageVersion: tasks.getInput("PackageVersion", true) || "", 23 | outputPath: removeTrailingSlashes(safeTrim(tasks.getPathInput("OutputPath"))) || ".", 24 | sourcePath: removeTrailingSlashes(safeTrim(tasks.getPathInput("SourcePath"))) || ".", 25 | include: getLineSeparatedItems(tasks.getInput("Include") || "**"), 26 | nuGetDescription: tasks.getInput("NuGetDescription", true) || "", 27 | nuGetAuthors: getLineSeparatedItems(tasks.getInput("NuGetAuthors", true) || ""), 28 | nuGetTitle: tasks.getInput("NuGetTitle"), 29 | nuGetReleaseNotes: tasks.getInput("NuGetReleaseNotes"), 30 | nuGetReleaseNotesFile: tasks.getInput("NuGetReleaseNotesFile", false), 31 | overwrite: tasks.getBoolInput("Overwrite"), 32 | }; 33 | }; 34 | -------------------------------------------------------------------------------- /source/tasks/PackZip/PackZipV6/create-package.ts: -------------------------------------------------------------------------------- 1 | import { Logger, ZipPackageBuilder } from "@octopusdeploy/api-client"; 2 | import path from "path"; 3 | import { InputParameters } from "./input-parameters"; 4 | 5 | type createPackageResult = { 6 | filePath: string; 7 | filename: string; 8 | }; 9 | 10 | export async function createPackageFromInputs(parameters: InputParameters, logger: Logger): Promise { 11 | const builder = new ZipPackageBuilder(); 12 | const packageFilename = await builder.pack({ 13 | packageId: parameters.packageId, 14 | version: parameters.packageVersion, 15 | outputFolder: parameters.outputPath, 16 | basePath: parameters.sourcePath, 17 | inputFilePatterns: parameters.include, 18 | overwrite: parameters.overwrite, 19 | logger, 20 | }); 21 | 22 | return { filePath: path.join(parameters.outputPath, packageFilename), filename: packageFilename }; 23 | } 24 | -------------------------------------------------------------------------------- /source/tasks/PackZip/PackZipV6/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/tasks/PackZip/PackZipV6/icon.png -------------------------------------------------------------------------------- /source/tasks/PackZip/PackZipV6/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /source/tasks/PackZip/PackZipV6/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/ban-ts-comment */ 2 | import * as tasks from "azure-pipelines-task-lib/task"; 3 | import { Logger } from "@octopusdeploy/api-client"; 4 | import { getInputs } from "./input-parameters"; 5 | import os from "os"; 6 | import { createPackageFromInputs } from "./create-package"; 7 | 8 | async function run() { 9 | try { 10 | const parameters = getInputs(); 11 | 12 | const logger: Logger = { 13 | debug: (message) => { 14 | tasks.debug(message); 15 | }, 16 | info: (message) => console.log(message), 17 | warn: (message) => tasks.warning(message), 18 | error: (message, err) => { 19 | if (err !== undefined) { 20 | tasks.error(err.message); 21 | } else { 22 | tasks.error(message); 23 | } 24 | }, 25 | }; 26 | 27 | const result = await createPackageFromInputs(parameters, logger); 28 | 29 | tasks.setVariable("package_file_path", result.filePath); 30 | tasks.setVariable("package_filename", result.filename); 31 | 32 | tasks.setResult(tasks.TaskResult.Succeeded, "Pack succeeded"); 33 | } catch (error: unknown) { 34 | if (error instanceof Error) { 35 | tasks.setResult(tasks.TaskResult.Failed, `"Failed to execute pack. ${error.message}${os.EOL}${error.stack}`, true); 36 | } else { 37 | tasks.setResult(tasks.TaskResult.Failed, `"Failed to execute pack. ${error}`, true); 38 | } 39 | } 40 | } 41 | 42 | run(); 43 | -------------------------------------------------------------------------------- /source/tasks/PackZip/PackZipV6/input-parameters.ts: -------------------------------------------------------------------------------- 1 | import * as tasks from "azure-pipelines-task-lib/task"; 2 | import { removeTrailingSlashes, safeTrim } from "tasksLegacy/Utils/inputs"; 3 | import { getLineSeparatedItems } from "../../Utils/inputs"; 4 | 5 | export interface InputParameters { 6 | packageId: string; 7 | packageVersion: string; 8 | outputPath: string; 9 | sourcePath: string; 10 | include: string[]; 11 | overwrite?: boolean; 12 | } 13 | 14 | export const getInputs = (): InputParameters => { 15 | return { 16 | packageId: tasks.getInput("PackageId", true) || "", 17 | packageVersion: tasks.getInput("PackageVersion", true) || "", 18 | outputPath: removeTrailingSlashes(safeTrim(tasks.getPathInput("OutputPath"))) || ".", 19 | sourcePath: removeTrailingSlashes(safeTrim(tasks.getPathInput("SourcePath"))) || ".", 20 | include: getLineSeparatedItems(tasks.getInput("Include") || "**"), 21 | overwrite: tasks.getBoolInput("Overwrite"), 22 | }; 23 | }; 24 | -------------------------------------------------------------------------------- /source/tasks/Promote/PromoteV5/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/tasks/Promote/PromoteV5/icon.png -------------------------------------------------------------------------------- /source/tasks/Promote/PromoteV5/index.ts: -------------------------------------------------------------------------------- 1 | import * as tasks from "azure-pipelines-task-lib/task"; 2 | import { getDefaultOctopusConnectionDetailsOrThrow } from "../../Utils/connection"; 3 | import { getDelimitedInput, getRequiredInput } from "../../Utils/inputs"; 4 | import { Promote } from "./promote"; 5 | import { getOctopusCliTool } from "../../Utils/tool"; 6 | 7 | const space = getRequiredInput("Space"); 8 | const project = getRequiredInput("Project"); 9 | const from = getRequiredInput("From"); 10 | const to = getDelimitedInput("To"); 11 | const deployForTenants = getDelimitedInput("DeployForTenants"); 12 | const deployForTenantTags = getDelimitedInput("DeployForTenantTags"); 13 | const deploymentProgress = tasks.getBoolInput("ShowProgress"); 14 | const additionalArguments = tasks.getInput("AdditionalArguments"); 15 | 16 | const connection = getDefaultOctopusConnectionDetailsOrThrow(); 17 | 18 | new Promote(getOctopusCliTool(), connection).run(space, project, from, to, deployForTenants, deployForTenantTags, deploymentProgress, additionalArguments); 19 | -------------------------------------------------------------------------------- /source/tasks/Promote/PromoteV5/promote.test.ts: -------------------------------------------------------------------------------- 1 | import { executeCommand, MockOctopusToolRunner } from "../../Utils/testing"; 2 | import { Promote } from "./promote"; 3 | 4 | describe("Promote Release", () => { 5 | test("Run a simple promote", async () => { 6 | const output = await executeCommand(() => 7 | new Promote(new MockOctopusToolRunner(), { url: "http://octopus.com", apiKey: "myapikey", ignoreSslErrors: true }).run( 8 | "my space", 9 | "my project", 10 | "Dev", 11 | ["int", "prod"], 12 | ["tenantA", "tenantB"], 13 | ["tagme", "tagyou"], 14 | true, 15 | "--myAdditionalArgumentToInclude" 16 | ) 17 | ); 18 | 19 | expect(output).toContain( 20 | "promote-release --space my space --project my project --from Dev --enableServiceMessages --progress --to int --to prod --tenant tenantA --tenant tenantB --tenantTag tagme --tenantTag tagyou --server http://octopus.com --apiKey myapikey --ignoreSslErrors --myAdditionalArgumentToInclude" 21 | ); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /source/tasks/Promote/PromoteV5/promote.ts: -------------------------------------------------------------------------------- 1 | import { OctoServerConnectionDetails } from "../../Utils/connection"; 2 | import { executeTask } from "../../Utils/octopusTasks"; 3 | import { OctopusToolRunner } from "../../Utils/tool"; 4 | 5 | export class Promote { 6 | constructor(readonly tool: OctopusToolRunner, readonly connection: OctoServerConnectionDetails) {} 7 | 8 | public async run(space: string, project: string, from: string, to: string[], deployForTenants: string[] = [], deployForTenantTags: string[] = [], showProgress?: boolean | undefined, additionalArguments?: string | undefined) { 9 | this.tool.arg("promote-release"); 10 | this.tool.arg(["--space", space]); 11 | this.tool.arg(["--project", project]); 12 | this.tool.arg(["--from", from]); 13 | this.tool.arg("--enableServiceMessages"); 14 | this.tool.argIf(showProgress, "--progress"); 15 | for (const item of to) { 16 | this.tool.arg(["--to", item]); 17 | } 18 | for (const item of deployForTenants) { 19 | this.tool.arg(["--tenant", item]); 20 | } 21 | for (const item of deployForTenantTags) { 22 | this.tool.arg(["--tenantTag", item]); 23 | } 24 | 25 | await executeTask(this.tool, "(release;promote;v5)", this.connection, "Promote release succeeded.", "Failed to promote release.", additionalArguments); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /source/tasks/Push/PushV5/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/tasks/Push/PushV5/icon.png -------------------------------------------------------------------------------- /source/tasks/Push/PushV5/icon.svg: -------------------------------------------------------------------------------- 1 | octopus -------------------------------------------------------------------------------- /source/tasks/Push/PushV5/index.ts: -------------------------------------------------------------------------------- 1 | import * as tasks from "azure-pipelines-task-lib/task"; 2 | import { getDefaultOctopusConnectionDetailsOrThrow } from "../../Utils/connection"; 3 | import { getLineSeparatedItems, getOverwriteModeFromReplaceInput, getRequiredInput } from "../../Utils/inputs"; 4 | import { Push } from "./push"; 5 | import { getOctopusCliTool } from "../../Utils/tool"; 6 | 7 | const space = getRequiredInput("Space"); 8 | const packages = getLineSeparatedItems(tasks.getInput("Package", true) || ""); 9 | const overwriteMode = getOverwriteModeFromReplaceInput(tasks.getInput("Replace", true) || ""); 10 | const additionalArguments = tasks.getInput("AdditionalArguments"); 11 | 12 | const connection = getDefaultOctopusConnectionDetailsOrThrow(); 13 | 14 | new Push(getOctopusCliTool(), connection).run(space, packages, overwriteMode, additionalArguments); 15 | -------------------------------------------------------------------------------- /source/tasks/Push/PushV5/push.test.ts: -------------------------------------------------------------------------------- 1 | import { mkdtemp, rm } from "fs/promises"; 2 | import * as path from "path"; 3 | import os, { platform } from "os"; 4 | import { executeCommand, MockOctopusToolRunner } from "../../Utils/testing"; 5 | import { Push } from "./push"; 6 | import { ReplaceOverwriteMode } from "../../Utils/inputs"; 7 | import archiver from "archiver"; 8 | import { createWriteStream } from "fs"; 9 | 10 | describe("Push", () => { 11 | let tempOutDir: string; 12 | let packagePath1: string, packagePath2: string; 13 | 14 | jest.setTimeout(100000); 15 | 16 | beforeAll(async () => { 17 | tempOutDir = await mkdtemp(path.join(os.tmpdir(), "octopus_")); 18 | 19 | packagePath1 = await createPackage("foo", "1.2.3"); 20 | packagePath2 = await createPackage("boo", "1.2.3"); 21 | }); 22 | 23 | afterAll(async () => { 24 | await rm(tempOutDir, { recursive: true }); 25 | }); 26 | 27 | async function createPackage(name: string, version: string) { 28 | const packagePath = path.join(tempOutDir, `${name}.${version}.${platform() === "win32" ? "zip" : "tar.gz"}`); 29 | const archiveFile = createWriteStream(packagePath); 30 | const archive = archiver(platform() === "win32" ? "zip" : "tar", { gzip: true }); 31 | archive.append("Hello world", { name: "readme" }); 32 | archive.pipe(archiveFile); 33 | await archive.finalize(); 34 | 35 | return packagePath; 36 | } 37 | 38 | test("Pushes multiple packages with fixed paths", async () => { 39 | const output = await executeCommand(() => 40 | new Push(new MockOctopusToolRunner(), { 41 | url: "http://octopus.com", 42 | apiKey: "myapikey", 43 | ignoreSslErrors: false, 44 | }).run("my space", [packagePath1, packagePath2], ReplaceOverwriteMode.true, "--myAdditionalArgumentToInclude") 45 | ); 46 | 47 | expect(output).toMatch( 48 | /push --space my space --overwrite-mode OverwriteExisting --enableServiceMessages --package .*(foo|boo)\.1\.2\.3\.(tar.gz|zip) --package .*(foo|boo)\.1\.2\.3\.(tar.gz|zip) --server http:\/\/octopus.com --apiKey myapikey --myAdditionalArgumentToInclude/ 49 | ); 50 | }); 51 | 52 | test("Pushes multiple packages with wildcards", async () => { 53 | const output = await executeCommand(() => 54 | new Push(new MockOctopusToolRunner(), { 55 | url: "http://octopus.com", 56 | apiKey: "myapikey", 57 | ignoreSslErrors: false, 58 | }).run("my space", [path.join(tempOutDir, "*.*")], ReplaceOverwriteMode.false) 59 | ); 60 | 61 | expect(output).toMatch(/push --space my space --overwrite-mode FailIfExists --enableServiceMessages --package .*(foo|boo)\.1\.2\.3\.(tar.gz|zip) --package .*(foo|boo)\.1\.2\.3\.(tar.gz|zip) --server http:\/\/octopus.com --apiKey myapikey/); 62 | }); 63 | }); 64 | -------------------------------------------------------------------------------- /source/tasks/Push/PushV5/push.ts: -------------------------------------------------------------------------------- 1 | import { OctoServerConnectionDetails } from "../../Utils/connection"; 2 | import { ReplaceOverwriteMode } from "../../Utils/inputs"; 3 | import { executeTask } from "../../Utils/octopusTasks"; 4 | import glob from "glob"; 5 | import { OctopusToolRunner } from "../../Utils/tool"; 6 | 7 | export class Push { 8 | constructor(readonly tool: OctopusToolRunner, readonly connection: OctoServerConnectionDetails) {} 9 | 10 | public async run(space: string, packages: string[], overwriteMode: ReplaceOverwriteMode, additionalArguments?: string | undefined) { 11 | const matchedPackages = await this.resolveGlobs(packages); 12 | 13 | this.tool.arg("push"); 14 | this.tool.arg(["--space", space]); 15 | this.tool.arg(["--overwrite-mode", overwriteMode]); 16 | this.tool.arg("--enableServiceMessages"); 17 | for (const item of matchedPackages) { 18 | this.tool.arg(["--package", item]); 19 | } 20 | 21 | await executeTask(this.tool, "(package;push;v5)", this.connection, "Package(s) pushed.", "Failed to push package(s).", additionalArguments); 22 | } 23 | 24 | private resolveGlobs = async (globs: string[]): Promise => { 25 | const globResults = await Promise.all(globs.map(this.pGlobNoNull)); 26 | const results = ([] as string[]).concat(...globResults); 27 | 28 | return results; 29 | }; 30 | 31 | private pGlobNoNull = (pattern: string): Promise => { 32 | return new Promise((resolve, reject) => { 33 | glob(pattern, { nonull: true }, (err, matches) => { 34 | if (err) { 35 | reject(err); 36 | return; 37 | } 38 | resolve(matches); 39 | }); 40 | }); 41 | }; 42 | } 43 | -------------------------------------------------------------------------------- /source/tasks/Push/PushV6/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/tasks/Push/PushV6/icon.png -------------------------------------------------------------------------------- /source/tasks/Push/PushV6/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /source/tasks/Push/PushV6/index.ts: -------------------------------------------------------------------------------- 1 | import { Logger } from "@octopusdeploy/api-client"; 2 | import * as tasks from "azure-pipelines-task-lib/task"; 3 | import { getDefaultOctopusConnectionDetailsOrThrow } from "../../Utils/connection"; 4 | import { getLineSeparatedItems, getOverwriteModeFromReplaceInput, getRequiredInput } from "../../Utils/inputs"; 5 | import { Push } from "./push"; 6 | import os from "os"; 7 | import { getClient } from "../../Utils/client"; 8 | 9 | async function run() { 10 | try { 11 | const spaceName = getRequiredInput("Space"); 12 | const packages = getLineSeparatedItems(tasks.getInput("Packages", true) || ""); 13 | const overwriteMode = getOverwriteModeFromReplaceInput(tasks.getInput("Replace", true) || ""); 14 | 15 | const connection = getDefaultOctopusConnectionDetailsOrThrow(); 16 | 17 | const logger: Logger = { 18 | debug: (message) => { 19 | tasks.debug(message); 20 | }, 21 | info: (message) => console.log(message), 22 | warn: (message) => tasks.warning(message), 23 | error: (message, err) => { 24 | if (err !== undefined) { 25 | tasks.error(err.message); 26 | } else { 27 | tasks.error(message); 28 | } 29 | }, 30 | }; 31 | 32 | const client = await getClient(connection, logger, "package", "push", 6); 33 | await new Push(client).run(spaceName, packages, overwriteMode); 34 | } catch (error: unknown) { 35 | if (error instanceof Error) { 36 | tasks.setResult(tasks.TaskResult.Failed, `"Failed to execute push. ${error.message}${os.EOL}${error.stack}`, true); 37 | } else { 38 | tasks.setResult(tasks.TaskResult.Failed, `"Failed to execute push. ${error}`, true); 39 | } 40 | } 41 | } 42 | 43 | run(); 44 | -------------------------------------------------------------------------------- /source/tasks/Push/PushV6/push.ts: -------------------------------------------------------------------------------- 1 | import { ReplaceOverwriteMode } from "../../Utils/inputs"; 2 | import { Client, OverwriteMode, PackageRepository } from "@octopusdeploy/api-client"; 3 | import glob from "glob"; 4 | 5 | export class Push { 6 | constructor(readonly client: Client) {} 7 | 8 | public async run(spaceName: string, packages: string[], overwriteMode: ReplaceOverwriteMode) { 9 | const matchedPackages = await this.resolveGlobs(packages); 10 | 11 | let mappedOverwriteMode = OverwriteMode.FailIfExists; 12 | if (overwriteMode === ReplaceOverwriteMode.true) { 13 | mappedOverwriteMode = OverwriteMode.OverwriteExisting; 14 | } else if (overwriteMode === ReplaceOverwriteMode.IgnoreIfExists) { 15 | mappedOverwriteMode = OverwriteMode.IgnoreIfExists; 16 | } 17 | 18 | const repository = new PackageRepository(this.client, spaceName); 19 | await repository.push(matchedPackages, mappedOverwriteMode); 20 | 21 | return packages; 22 | } 23 | 24 | private resolveGlobs = async (globs: string[]): Promise => { 25 | const globResults = await Promise.all(globs.map(this.pGlobNoNull)); 26 | const results = ([] as string[]).concat(...globResults); 27 | 28 | return results; 29 | }; 30 | 31 | private pGlobNoNull = (pattern: string): Promise => { 32 | return new Promise((resolve, reject) => { 33 | glob(pattern, { nonull: true }, (err, matches) => { 34 | if (err) { 35 | reject(err); 36 | return; 37 | } 38 | resolve(matches); 39 | }); 40 | }); 41 | }; 42 | } 43 | -------------------------------------------------------------------------------- /source/tasks/Push/PushV6/task.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "d05ad9a2-5d9e-4a1c-a887-14034334d6f2", 3 | "name": "OctopusPush", 4 | "friendlyName": "Push Package(s) to Octopus", 5 | "description": "Push your NuGet or Zip package to your Octopus Deploy Server. **v6 of this step requires Octopus 2022.3+**", 6 | "helpMarkDown": "set-by-pack.ps1", 7 | "category": "Package", 8 | "visibility": [ 9 | "Build", 10 | "Release" 11 | ], 12 | "author": "Octopus Deploy", 13 | "version": { 14 | "Major": 6, 15 | "Minor": 0, 16 | "Patch": 0 17 | }, 18 | "demands": [], 19 | "minimumAgentVersion": "2.206.1", 20 | "inputs": [ 21 | { 22 | "name": "OctoConnectedServiceName", 23 | "type": "connectedService:OctopusEndpoint", 24 | "label": "Octopus Deploy Server", 25 | "defaultValue": "", 26 | "required": true, 27 | "helpMarkDown": "Octopus Deploy server connection" 28 | }, 29 | { 30 | "name": "Space", 31 | "type": "string", 32 | "label": "Space Name", 33 | "defaultValue": "", 34 | "required": true, 35 | "helpMarkDown": "The space name within Octopus." 36 | }, 37 | { 38 | "name": "Packages", 39 | "type": "multiLine", 40 | "label": "Packages", 41 | "defaultValue": "", 42 | "required": true, 43 | "helpMarkDown": "Package files to push. To push multiple packages, enter on multiple lines." 44 | }, 45 | { 46 | "name": "Replace", 47 | "type": "pickList", 48 | "label": "Overwrite Mode", 49 | "defaultValue": "false", 50 | "required": true, 51 | "helpMarkDown": "Normally, if the same package already exists on the server, the server will reject the package push. This is a good practice as it ensures a package isn't accidentally overwritten or ignored. Use this setting to override this behavior.", 52 | "options": { 53 | "false": "Fail if exists", 54 | "true": "Overwrite existing", 55 | "IgnoreIfExists": "Ignore if exists" 56 | } 57 | } 58 | ], 59 | "instanceNameFormat": "Push Packages to Octopus", 60 | "execution": { 61 | "Node16": { 62 | "target": "index.js" 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /source/tasks/RunRunbook/RunRunbookV6/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/tasks/RunRunbook/RunRunbookV6/icon.png -------------------------------------------------------------------------------- /source/tasks/RunRunbook/RunRunbookV6/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /source/tasks/RunRunbook/RunRunbookV6/index.ts: -------------------------------------------------------------------------------- 1 | import { getDefaultOctopusConnectionDetailsOrThrow } from "../../Utils/connection"; 2 | import { ConcreteTaskWrapper, TaskWrapper } from "tasks/Utils/taskInput"; 3 | import { Logger } from "@octopusdeploy/api-client"; 4 | import * as tasks from "azure-pipelines-task-lib/task"; 5 | import { RunbookRun } from "./runbookRun"; 6 | 7 | const connection = getDefaultOctopusConnectionDetailsOrThrow(); 8 | 9 | const logger: Logger = { 10 | debug: (message) => { 11 | tasks.debug(message); 12 | }, 13 | info: (message) => console.log(message), 14 | warn: (message) => tasks.warning(message), 15 | error: (message, err) => { 16 | if (err !== undefined) { 17 | tasks.error(err.message); 18 | } else { 19 | tasks.error(message); 20 | } 21 | }, 22 | }; 23 | 24 | const task: TaskWrapper = new ConcreteTaskWrapper(); 25 | 26 | new RunbookRun(connection, task, logger).run(); 27 | -------------------------------------------------------------------------------- /source/tasks/RunRunbook/RunRunbookV6/runbookRun.ts: -------------------------------------------------------------------------------- 1 | import { Logger } from "@octopusdeploy/api-client"; 2 | import { OctoServerConnectionDetails } from "../../Utils/connection"; 3 | import { createRunbookRunFromInputs } from "./runRunbook"; 4 | import { createCommandFromInputs } from "./inputCommandBuilder"; 5 | import os from "os"; 6 | import { TaskWrapper } from "tasks/Utils/taskInput"; 7 | import { getClient } from "../../Utils/client"; 8 | 9 | export class RunbookRun { 10 | constructor(readonly connection: OctoServerConnectionDetails, readonly task: TaskWrapper, readonly logger: Logger) {} 11 | 12 | public async run() { 13 | try { 14 | const command = createCommandFromInputs(this.logger, this.task); 15 | const client = await getClient(this.connection, this.logger, "runbook", "run", 6); 16 | 17 | createRunbookRunFromInputs(client, command, this.task, this.logger); 18 | 19 | this.task.setSuccess("Runbook run succeeded."); 20 | } catch (error: unknown) { 21 | if (error instanceof Error) { 22 | this.task.setFailure(`"Failed to successfully run runbook. ${error.message}${os.EOL}${error.stack}`, true); 23 | } else { 24 | this.task.setFailure(`"Failed to successfully run runbook. ${error}`, true); 25 | } 26 | throw error; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /source/tasks/Utils/MockTaskWrapper.ts: -------------------------------------------------------------------------------- 1 | import { TaskWrapper } from "tasks/Utils/taskInput"; 2 | import * as tasks from "azure-pipelines-task-lib/task"; 3 | 4 | export class MockTaskWrapper implements TaskWrapper { 5 | lastResult?: tasks.TaskResult | undefined = undefined; 6 | lastResultMessage: string | undefined = undefined; 7 | lastResultDone: boolean | undefined = undefined; 8 | 9 | stringValues: Map = new Map(); 10 | boolValues: Map = new Map(); 11 | outputVariables: Map = new Map(); 12 | 13 | addVariableString(name: string, value: string) { 14 | this.stringValues.set(name, value); 15 | } 16 | 17 | addVariableBoolean(name: string, value: boolean) { 18 | this.boolValues.set(name, value); 19 | } 20 | 21 | getInput(name: string, required?: boolean | undefined): string | undefined { 22 | const value = this.stringValues.get(name); 23 | if (required && !value) { 24 | // this replicates the functionality in the Azure Pipeline Task library 25 | throw new Error(`Input required: ${name}`); 26 | } 27 | return value; 28 | } 29 | 30 | getBoolean(name: string, _required?: boolean | undefined): boolean | undefined { 31 | return this.boolValues.get(name); 32 | } 33 | 34 | setSuccess(message: string, done?: boolean | undefined): void { 35 | this.lastResult = tasks.TaskResult.Succeeded; 36 | this.lastResultMessage = message; 37 | this.lastResultDone = done; 38 | } 39 | setFailure(message: string, done?: boolean | undefined): void { 40 | this.lastResult = tasks.TaskResult.Failed; 41 | this.lastResultMessage = message; 42 | this.lastResultDone = done; 43 | } 44 | 45 | setOutputVariable(name: string, value: string): void { 46 | this.outputVariables.set(name, value); 47 | } 48 | 49 | getVariable(name: string): string | undefined { 50 | return this.stringValues.get(name); 51 | } 52 | 53 | getOutputVariable(_step: string, name: string): string | undefined { 54 | return this.getVariable(name); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /source/tasks/Utils/client.ts: -------------------------------------------------------------------------------- 1 | import { Client, ClientConfiguration, Logger } from "@octopusdeploy/api-client"; 2 | import { getUserAgentApp } from "./pluginInformation"; 3 | import https from "https"; 4 | import { OctoServerConnectionDetails } from "./connection"; 5 | 6 | export async function getClient(connection: OctoServerConnectionDetails, logger: Logger, stepNoun: string, stepVerb: string, stepVersion: number) { 7 | const config: ClientConfiguration = { 8 | userAgentApp: getUserAgentApp(stepNoun, stepVerb, stepVersion), 9 | instanceURL: connection.url, 10 | apiKey: connection.apiKey, 11 | logging: logger, 12 | }; 13 | if (connection.ignoreSslErrors) { 14 | config.httpsAgent = new https.Agent({ rejectUnauthorized: false }); 15 | } 16 | 17 | return await Client.create(config); 18 | } 19 | -------------------------------------------------------------------------------- /source/tasks/Utils/command-line-args.test.ts: -------------------------------------------------------------------------------- 1 | import commandLineArgs from "command-line-args"; 2 | import shlex from "shlex"; 3 | 4 | describe("cli args", () => { 5 | test("simple args", async () => { 6 | const optionDefs = [{ name: "variable", type: String, multiple: false }]; 7 | 8 | const options = commandLineArgs(optionDefs, { argv: ["--variable", "value"] }); 9 | expect(options.variable).toBe("value"); 10 | }); 11 | 12 | test("simple args with alias", async () => { 13 | const optionDefs = [{ name: "variable", alias: "v", type: String, multiple: false }]; 14 | 15 | const options = commandLineArgs(optionDefs, { argv: ["-v", "value"] }); 16 | expect(options.variable).toBe("value"); 17 | }); 18 | 19 | test("simple args with alias 2", async () => { 20 | const optionDefs = [{ name: "variable", alias: "v", type: String, multiple: false }]; 21 | 22 | const options = commandLineArgs(optionDefs, { argv: ["-v", '"value with space"'] }); 23 | expect(options.variable).toBe('"value with space"'); 24 | }); 25 | 26 | test("basic split on space", async () => { 27 | const optionDefs = [{ name: "variable", alias: "v", type: String, multiple: true }]; 28 | const args = "--variable value1 --variable value2 -v value3"; 29 | const splitArgs = shlex.split(args); 30 | const options = commandLineArgs(optionDefs, { argv: splitArgs }); 31 | console.log(options); 32 | expect(options.variable).toStrictEqual(["value1", "value2", "value3"]); 33 | }); 34 | 35 | test("split on space with quoted values", async () => { 36 | const optionDefs = [{ name: "variable", alias: "v", type: String, multiple: true }]; 37 | const args = '--variable value1 --variable "value 2" -v value3'; 38 | const splitArgs = shlex.split(args); 39 | const options = commandLineArgs(optionDefs, { argv: splitArgs }); 40 | console.log(options); 41 | expect(options.variable).toStrictEqual(["value1", "value 2", "value3"]); 42 | }); 43 | 44 | test("split on space with quoted values", async () => { 45 | const optionDefs = [{ name: "variable", alias: "v", type: String, multiple: true }]; 46 | const args = "--variable value1 --variable 'value 2' -v value3"; 47 | const splitArgs = shlex.split(args); 48 | const options = commandLineArgs(optionDefs, { argv: splitArgs }); 49 | console.log(options); 50 | expect(options.variable).toStrictEqual(["value1", "value 2", "value3"]); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /source/tasks/Utils/connection.test.ts: -------------------------------------------------------------------------------- 1 | import { getDeepLink } from "./connection"; 2 | 3 | describe("DeepLinks", () => { 4 | test("URL ends with / returns correctly formatted deep link", async () => { 5 | const url = "https://octopus.com/vdir/"; 6 | const expectedDeepLink = "https://octopus.com/vdir/app#/Spaces-1/deployments/Deployments-1"; 7 | expect(getDeepLink(url, "Spaces-1/deployments/Deployments-1")).toBe(expectedDeepLink); 8 | }); 9 | 10 | test("URL does not end with / return correctly formatted deep link", async () => { 11 | const url = "https://octopus.com/vdir"; 12 | const expectedDeepLink = "https://octopus.com/vdir/app#/Spaces-1/deployments/Deployments-1"; 13 | expect(getDeepLink(url, "Spaces-1/deployments/Deployments-1")).toBe(expectedDeepLink); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /source/tasks/Utils/connection.ts: -------------------------------------------------------------------------------- 1 | import * as tasks from "azure-pipelines-task-lib/task"; 2 | 3 | export interface OctoServerConnectionDetails { 4 | url: string; 5 | apiKey: string; 6 | ignoreSslErrors: boolean; 7 | } 8 | 9 | export const DefaultOctoConnectionInputName = "OctoConnectedServiceName"; 10 | 11 | export function getDefaultOctopusConnectionDetailsOrThrow() { 12 | const result = getOctopusConnectionDetails(tasks.getInput(DefaultOctoConnectionInputName, true) || ""); 13 | if (!result) { 14 | throw new Error("Could not retrieve default Octo connection information"); 15 | } 16 | return result; 17 | } 18 | 19 | function getOctopusConnectionDetails(name: string): OctoServerConnectionDetails { 20 | const octoEndpointAuthorization = tasks.getEndpointAuthorization(name, false); 21 | 22 | if (!octoEndpointAuthorization) { 23 | throw new Error(`Could not retrieve the endpoint authorization named ${name}.`); 24 | } 25 | 26 | const ignoreSSL = tasks.getEndpointDataParameter(name, "ignoreSslErrors", true); 27 | return { 28 | url: tasks.getEndpointUrl(name, false) || "", 29 | apiKey: octoEndpointAuthorization.parameters["apitoken"], 30 | ignoreSslErrors: !!ignoreSSL && ignoreSSL.toLowerCase() === "true", 31 | }; 32 | } 33 | 34 | export function getDeepLink(baseUrl: string, path: string): string { 35 | if (baseUrl.endsWith("/")) { 36 | baseUrl = baseUrl.substring(0, baseUrl.length - 1); 37 | } 38 | return `${baseUrl}/app#/${path}`; 39 | } 40 | -------------------------------------------------------------------------------- /source/tasks/Utils/executionResult.ts: -------------------------------------------------------------------------------- 1 | export interface ExecutionResult { 2 | serverTaskId: string; 3 | environmentName: string; 4 | tenantName: string | null; 5 | type: string; 6 | } 7 | -------------------------------------------------------------------------------- /source/tasks/Utils/octopusTasks.ts: -------------------------------------------------------------------------------- 1 | import * as tasks from "azure-pipelines-task-lib"; 2 | import os from "os"; 3 | import { OctoServerConnectionDetails } from "./connection"; 4 | import { connectionArguments, includeAdditionalArgumentsAndProxyConfig } from "./inputs"; 5 | import { OctopusToolRunner, runOctopusCli } from "./tool"; 6 | 7 | export async function executeTask(tool: OctopusToolRunner, stepIdentifier: string, connection: OctoServerConnectionDetails, successMessage: string, failureMessage: string, additionalArguments?: string | undefined) { 8 | return executeWithSetResult( 9 | async () => { 10 | connectionArguments(connection, tool); 11 | includeAdditionalArgumentsAndProxyConfig(connection.url, additionalArguments, tool); 12 | 13 | await runOctopusCli(tool, stepIdentifier); 14 | }, 15 | successMessage, 16 | failureMessage 17 | ); 18 | } 19 | 20 | export async function executeWithSetResult(func: () => Promise, successMessage: string, failureMessage: string) { 21 | try { 22 | await func(); 23 | 24 | tasks.setResult(tasks.TaskResult.Succeeded, successMessage); 25 | } catch (error: unknown) { 26 | if (error instanceof Error) { 27 | tasks.setResult(tasks.TaskResult.Failed, `"${failureMessage}${os.EOL}${error.message}${os.EOL}${error.stack}`, true); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /source/tasks/Utils/pluginInformation.ts: -------------------------------------------------------------------------------- 1 | function getPluginAndVersionInformation() { 2 | return `plugin/${process.env.EXTENSION_VERSION}`; 3 | } 4 | 5 | export function getUserAgentApp(stepNoun: string, stepVerb: string, stepVersion: number): string { 6 | return `${getPluginAndVersionInformation()} (${stepNoun};${stepVerb};v${stepVersion})`; 7 | } 8 | -------------------------------------------------------------------------------- /source/tasks/Utils/taskInput.ts: -------------------------------------------------------------------------------- 1 | import * as tasks from "azure-pipelines-task-lib/task"; 2 | 3 | export interface TaskWrapper { 4 | getInput(name: string, required?: boolean): string | undefined; 5 | getBoolean(name: string, required?: boolean): boolean | undefined; 6 | setSuccess(message: string, done?: boolean): void; 7 | setFailure(message: string, done?: boolean): void; 8 | setOutputVariable(name: string, value: string): void; 9 | getVariable(name: string): string | undefined; 10 | getOutputVariable(step: string, name: string): string | undefined; 11 | } 12 | 13 | export class ConcreteTaskWrapper implements TaskWrapper { 14 | public getInput(name: string, required?: boolean): string | undefined { 15 | return tasks.getInput(name, required); 16 | } 17 | 18 | public getBoolean(name: string, required?: boolean): boolean | undefined { 19 | return tasks.getBoolInput(name, required); 20 | } 21 | 22 | public setSuccess(message: string, done?: boolean) { 23 | tasks.setResult(tasks.TaskResult.Succeeded, message, done); 24 | } 25 | 26 | public setFailure(message: string, done?: boolean) { 27 | tasks.setResult(tasks.TaskResult.Failed, message, done); 28 | } 29 | 30 | public setOutputVariable(name: string, value: string) { 31 | tasks.setVariable(name, value, false, true); 32 | } 33 | 34 | public getVariable(name: string): string | undefined { 35 | return tasks.getVariable(name); 36 | } 37 | 38 | public getOutputVariable(step: string, name: string): string | undefined { 39 | return tasks.getVariable(`${step}.${name}`); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /source/tasks/Utils/testing.ts: -------------------------------------------------------------------------------- 1 | import { IExecOptions } from "azure-pipelines-task-lib/toolrunner"; 2 | import Q from "q"; 3 | import { OctopusToolRunner } from "./tool"; 4 | import { stdout } from "test-console"; 5 | import os from "os"; 6 | 7 | export class MockOctopusToolRunner implements OctopusToolRunner { 8 | arguments: string[] = []; 9 | 10 | arg(val: string | string[]): void { 11 | if (typeof val === "string") { 12 | this.arguments.push(val); 13 | } else { 14 | val.map((s) => this.arguments.push(s)); 15 | } 16 | } 17 | 18 | argIf(condition: string | boolean | undefined, val: string | string[]): void { 19 | if (condition) { 20 | this.arg(val); 21 | } 22 | } 23 | 24 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 25 | exec(_?: IExecOptions): Q.Promise { 26 | console.log(this.arguments.join(" ")); 27 | return Q.resolve(0); 28 | } 29 | 30 | line(val: string): void { 31 | this.arg(val); 32 | } 33 | } 34 | 35 | export async function executeCommand(command: () => Promise) { 36 | const output = (await stdout.inspectAsync(command)).join(os.EOL); 37 | 38 | console.log(output); 39 | 40 | expect(output).toContain("task.complete result=Succeeded"); 41 | 42 | return output; 43 | } 44 | -------------------------------------------------------------------------------- /source/tasks/Utils/tool.ts: -------------------------------------------------------------------------------- 1 | import { IExecOptions } from "azure-pipelines-task-lib/toolrunner"; 2 | import Q from "q"; 3 | import * as tasks from "azure-pipelines-task-lib"; 4 | 5 | function getExecOptions(stepIdentifier: string): IExecOptions { 6 | return { env: { OCTOEXTENSION: `${process.env.EXTENSION_VERSION} ${stepIdentifier}`, ...process.env } }; 7 | } 8 | 9 | export function runOctopusCli(tool: OctopusToolRunner, stepIdentifier: string) { 10 | return tool.exec(getExecOptions(stepIdentifier)); 11 | } 12 | 13 | export interface OctopusToolRunner { 14 | arg(val: string | string[]): void; 15 | argIf(condition: string | boolean | undefined, val: string | string[]): void; 16 | line(val: string): void; 17 | exec(options?: IExecOptions): Q.Promise; 18 | } 19 | 20 | export function getOctopusCliTool() { 21 | return tasks.tool("octo"); 22 | } 23 | -------------------------------------------------------------------------------- /source/tasksLegacy/CreateOctopusRelease/CreateOctopusReleaseV3/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/tasksLegacy/CreateOctopusRelease/CreateOctopusReleaseV3/icon.png -------------------------------------------------------------------------------- /source/tasksLegacy/CreateOctopusRelease/CreateOctopusReleaseV3/icon.svg: -------------------------------------------------------------------------------- 1 | octopus -------------------------------------------------------------------------------- /source/tasksLegacy/CreateOctopusRelease/CreateOctopusReleaseV4/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/tasksLegacy/CreateOctopusRelease/CreateOctopusReleaseV4/icon.png -------------------------------------------------------------------------------- /source/tasksLegacy/CreateOctopusRelease/CreateOctopusReleaseV4/icon.svg: -------------------------------------------------------------------------------- 1 | octopus -------------------------------------------------------------------------------- /source/tasksLegacy/Deploy/DeployV3/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/tasksLegacy/Deploy/DeployV3/icon.png -------------------------------------------------------------------------------- /source/tasksLegacy/Deploy/DeployV3/icon.svg: -------------------------------------------------------------------------------- 1 | octopus -------------------------------------------------------------------------------- /source/tasksLegacy/Deploy/DeployV3/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/ban-ts-comment */ 2 | import * as tasks from "azure-pipelines-task-lib/task"; 3 | import { multiArgument, connectionArguments, includeAdditionalArgumentsAndProxyConfig, flag, argumentEnquote, argumentIfSet, getOrInstallOctoCommandRunner } from "../../Utils/tool"; 4 | import { getDefaultOctopusConnectionDetailsOrThrow, resolveProjectName } from "../../Utils/connection"; 5 | import { getOptionalCsvInput, getRequiredCsvInput } from "../../Utils/inputs"; 6 | import os from "os"; 7 | 8 | async function run() { 9 | try { 10 | tasks.warning("There is a later version of this task, we recommend using the latest version."); 11 | const connection = getDefaultOctopusConnectionDetailsOrThrow(); 12 | 13 | const space = tasks.getInput("Space"); 14 | const releaseNumber = tasks.getInput("ReleaseNumber", true); 15 | const environments = getRequiredCsvInput("Environments"); 16 | const showProgress = tasks.getBoolInput("ShowProgress"); 17 | const deploymentForTenants = getOptionalCsvInput("DeployForTenants"); 18 | const deployForTenantTags = getOptionalCsvInput("DeployForTenantTags"); 19 | const additionalArguments = tasks.getInput("AdditionalArguments"); 20 | // @ts-ignore 21 | const project = await resolveProjectName(connection, tasks.getInput("Project", true)).then((x) => x.value); 22 | 23 | const octo = await getOrInstallOctoCommandRunner("deploy-release"); 24 | 25 | const configure = [ 26 | // @ts-ignore 27 | argumentIfSet(argumentEnquote, "space", space), 28 | argumentEnquote("project", project), 29 | // @ts-ignore 30 | argumentEnquote("releaseNumber", releaseNumber), 31 | connectionArguments(connection), 32 | multiArgument(argumentEnquote, "deployTo", environments), 33 | multiArgument(argumentEnquote, "tenant", deploymentForTenants), 34 | multiArgument(argumentEnquote, "tenanttag", deployForTenantTags), 35 | flag("progress", showProgress), 36 | // @ts-ignore 37 | includeAdditionalArgumentsAndProxyConfig(connection.url, additionalArguments), 38 | ]; 39 | 40 | let stepIdentifier = "(release;deploy;v3)"; 41 | if (deploymentForTenants.length > 0 || deployForTenantTags.length > 0) { 42 | stepIdentifier = "(release;deploy-tenanted;v3)"; 43 | } 44 | 45 | const code: number = await octo 46 | .map((x) => x.launchOcto(configure, stepIdentifier)) 47 | .getOrElseL((x) => { 48 | throw new Error(x); 49 | }); 50 | 51 | tasks.setResult(tasks.TaskResult.Succeeded, "Deploy succeeded with code " + code); 52 | } catch (error: unknown) { 53 | if (error instanceof Error) { 54 | tasks.setResult(tasks.TaskResult.Failed, `"Failed to deploy release. ${error.message}${os.EOL}${error.stack}`, true); 55 | } 56 | } 57 | } 58 | 59 | run(); 60 | -------------------------------------------------------------------------------- /source/tasksLegacy/Deploy/DeployV4/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/tasksLegacy/Deploy/DeployV4/icon.png -------------------------------------------------------------------------------- /source/tasksLegacy/Deploy/DeployV4/icon.svg: -------------------------------------------------------------------------------- 1 | octopus -------------------------------------------------------------------------------- /source/tasksLegacy/Deploy/DeployV4/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/ban-ts-comment */ 2 | import * as tasks from "azure-pipelines-task-lib/task"; 3 | import { argumentEnquote, argumentIfSet, assertOctoVersionAcceptsIds, connectionArguments, flag, getOrInstallOctoCommandRunner, includeAdditionalArgumentsAndProxyConfig, multiArgument } from "../../Utils/tool"; 4 | import { getOptionalCsvInput, getRequiredCsvInput } from "../../Utils/inputs"; 5 | import { getDefaultOctopusConnectionDetailsOrThrow } from "../../Utils/connection"; 6 | import os from "os"; 7 | 8 | async function run() { 9 | try { 10 | tasks.warning("There is a later version of this task, we recommend using the latest version."); 11 | const connection = getDefaultOctopusConnectionDetailsOrThrow(); 12 | 13 | const space = tasks.getInput("Space"); 14 | const project = tasks.getInput("Project", true); 15 | const releaseNumber = tasks.getInput("ReleaseNumber", true); 16 | const deployToEnvironments = getRequiredCsvInput("Environments"); 17 | const deployForTenants = getOptionalCsvInput("DeployForTenants"); 18 | const deployForTenantTags = getOptionalCsvInput("DeployForTenantTags"); 19 | const showProgress = tasks.getBoolInput("ShowProgress"); 20 | const additionalArguments = tasks.getInput("AdditionalArguments"); 21 | await assertOctoVersionAcceptsIds(); 22 | const octo = await getOrInstallOctoCommandRunner("deploy-release"); 23 | 24 | const configure = [ 25 | // @ts-ignore 26 | argumentIfSet(argumentEnquote, "space", space), 27 | // @ts-ignore 28 | argumentEnquote("project", project), 29 | // @ts-ignore 30 | argumentEnquote("releaseNumber", releaseNumber), 31 | connectionArguments(connection), 32 | multiArgument(argumentEnquote, "deployTo", deployToEnvironments), 33 | multiArgument(argumentEnquote, "tenant", deployForTenants), 34 | multiArgument(argumentEnquote, "tenanttag", deployForTenantTags), 35 | flag("progress", showProgress), 36 | // @ts-ignore 37 | includeAdditionalArgumentsAndProxyConfig(connection.url, additionalArguments), 38 | ]; 39 | 40 | let stepIdentifier = "(release;deploy;v4)"; 41 | if (deployForTenants.length > 0 || deployForTenantTags.length > 0) { 42 | stepIdentifier = "(release;deploy-tenanted;v4)"; 43 | } 44 | 45 | const code: number = await octo 46 | .map((x) => x.launchOcto(configure, stepIdentifier)) 47 | .getOrElseL((x) => { 48 | throw new Error(x); 49 | }); 50 | 51 | tasks.setResult(tasks.TaskResult.Succeeded, "Deploy succeeded with code " + code); 52 | } catch (error: unknown) { 53 | if (error instanceof Error) { 54 | tasks.setResult(tasks.TaskResult.Failed, `"Failed to deploy release. ${error.message}${os.EOL}${error.stack}`, true); 55 | } 56 | } 57 | } 58 | 59 | run(); 60 | -------------------------------------------------------------------------------- /source/tasksLegacy/OctoCli/OctoCliV4/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/tasksLegacy/OctoCli/OctoCliV4/icon.png -------------------------------------------------------------------------------- /source/tasksLegacy/OctoCli/OctoCliV4/icon.svg: -------------------------------------------------------------------------------- 1 | octopus -------------------------------------------------------------------------------- /source/tasksLegacy/OctoCli/OctoCliV4/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/ban-ts-comment */ 2 | import * as tasks from "azure-pipelines-task-lib/task"; 3 | import { connectionArguments, getOrInstallOctoCommandRunner, includeAdditionalArguments } from "../../Utils/tool"; 4 | import { getDefaultOctopusConnectionDetailsOrThrow } from "../../Utils/connection"; 5 | import os from "os"; 6 | 7 | async function run() { 8 | try { 9 | tasks.warning("There is a later version of this task, we recommend using the latest version."); 10 | const connection = getDefaultOctopusConnectionDetailsOrThrow(); 11 | const args = tasks.getInput("args", false); 12 | const command = tasks.getInput("command", true); 13 | // @ts-ignore 14 | const octo = await getOrInstallOctoCommandRunner(command); 15 | 16 | // @ts-ignore 17 | const configure = [connectionArguments(connection), includeAdditionalArguments(args)]; 18 | 19 | const code: number = await octo 20 | .map((x) => x.launchOcto(configure, "(cli;run;v4)")) 21 | .getOrElseL((x) => { 22 | throw new Error(x); 23 | }); 24 | 25 | tasks.setResult(tasks.TaskResult.Succeeded, `Succeeded executing octo command ${command} with code ${code}`); 26 | } catch (error: unknown) { 27 | if (error instanceof Error) { 28 | tasks.setResult(tasks.TaskResult.Failed, `"Failed to execute octo command. ${error.message}${os.EOL}${error.stack}`, true); 29 | } 30 | } 31 | } 32 | 33 | run(); 34 | -------------------------------------------------------------------------------- /source/tasksLegacy/OctoCli/OctoCliV4/task.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "25fee290-e578-491b-b1db-dbc3980df1d0", 3 | "name": "OctoCli", 4 | "friendlyName": "Invoke Octopus CLI command", 5 | "description": "There is a later version of this task, we recommend using the latest version. Invoke an Octopus CLI command", 6 | "helpMarkDown": "set-by-pack.ps1", 7 | "category": "Tool", 8 | "visibility": ["Build", "Release"], 9 | "author": "Octopus Deploy", 10 | "version": { 11 | "Major": 4, 12 | "Minor": 3, 13 | "Patch": 0 14 | }, 15 | "demands": [], 16 | "minimumAgentVersion": "2.144.0", 17 | "groups": [ 18 | { 19 | "name": "advanced", 20 | "displayName": "Advanced Options", 21 | "isExpanded": false 22 | } 23 | ], 24 | "inputs": [ 25 | { 26 | "name": "OctoConnectedServiceName", 27 | "type": "connectedService:OctopusEndpoint", 28 | "label": "Octopus Deploy Server", 29 | "defaultValue": "", 30 | "required": true, 31 | "helpMarkDown": "Octopus Deploy server connection" 32 | }, 33 | { 34 | "name": "command", 35 | "type": "string", 36 | "label": "command", 37 | "defaultValue": "", 38 | "required": true, 39 | "helpMarkDown": "The Octopus CLI command to execute." 40 | }, 41 | { 42 | "name": "args", 43 | "type": "string", 44 | "label": "arguments", 45 | "defaultValue": "", 46 | "required": false, 47 | "helpMarkDown": "The arguments to use for the command." 48 | } 49 | ], 50 | "instanceNameFormat": "Invoke an Octopus CLI command", 51 | "execution": { 52 | "Node10": { 53 | "target": "index.js" 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /source/tasksLegacy/OctoInstaller/OctoInstallerV4/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/tasksLegacy/OctoInstaller/OctoInstallerV4/icon.png -------------------------------------------------------------------------------- /source/tasksLegacy/OctoInstaller/OctoInstallerV4/icon.svg: -------------------------------------------------------------------------------- 1 | command-line -------------------------------------------------------------------------------- /source/tasksLegacy/OctoInstaller/OctoInstallerV4/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/ban-ts-comment */ 2 | import { getOrDownloadOcto, resolvePublishedOctoVersion, addToolToPath, getEmbeddedOcto } from "../../Utils/install"; 3 | import * as tasks from "azure-pipelines-task-lib/task"; 4 | import * as os from "os"; 5 | 6 | async function run() { 7 | tasks.warning("This task is deprecated, please use latest version instead."); 8 | const version = tasks.getInput("version"); 9 | // @ts-ignore 10 | const forceEmbedded = /embedded/i.test(version); 11 | 12 | try { 13 | if (forceEmbedded) { 14 | console.log("Forcing the use of the embedded Octopus CLI tool."); 15 | await getEmbeddedOcto(tasks.resolve(__dirname, "embedded")).then(addToolToPath); 16 | } else { 17 | const option = await resolvePublishedOctoVersion(version); 18 | console.log(`Using Octopus CLI tool version ${option.version}`); 19 | await getOrDownloadOcto(option).then(addToolToPath); 20 | } 21 | 22 | tasks.setResult(tasks.TaskResult.Succeeded, ""); 23 | } catch (error: unknown) { 24 | if (error instanceof Error) { 25 | if (forceEmbedded) { 26 | tasks.setResult(tasks.TaskResult.Failed, `${error.message}${os.EOL}${error.stack}`, true); 27 | return; 28 | } 29 | 30 | console.log(`Failed to resolve Octopus CLI tool version ${version}. Using the embedded version. ${error}`); 31 | 32 | try { 33 | await getEmbeddedOcto(tasks.resolve(__dirname, "embedded")).then(addToolToPath); 34 | } catch (embeddedOctoError: unknown) { 35 | if (embeddedOctoError instanceof Error) { 36 | tasks.setResult(tasks.TaskResult.Failed, `${embeddedOctoError.message}${os.EOL}${embeddedOctoError.stack}`, true); 37 | } 38 | } 39 | } 40 | } 41 | } 42 | 43 | run(); 44 | -------------------------------------------------------------------------------- /source/tasksLegacy/OctoInstaller/OctoInstallerV4/task.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "57342b23-3a76-490a-8e78-25d4ade2f2e3", 3 | "name": "OctoInstaller", 4 | "friendlyName": "[DEPRECATED] Octopus CLI Installer", 5 | "description": "This task is deprecated, please use latest version instead. Install a specific version of the Octopus CLI", 6 | "helpMarkDown": "Install a specific version of the Octopus CLI", 7 | "category": "Tool", 8 | "runsOn": [ 9 | "Agent", 10 | "DeploymentGroup" 11 | ], 12 | "visibility": [ 13 | "Build", 14 | "Release" 15 | ], 16 | "author": "Octopus Deploy", 17 | "version": { 18 | "Major": 4, 19 | "Minor": 3, 20 | "Patch": 0 21 | }, 22 | "deprecated": true, 23 | "satisfies": ["Octo"], 24 | "demands": [], 25 | "minimumAgentVersion": "2.144.0", 26 | "groups": [ 27 | { 28 | "name": "advanced", 29 | "displayName": "Advanced Options", 30 | "isExpanded": false 31 | } 32 | ], 33 | "inputs": [ 34 | { 35 | "name": "version", 36 | "type": "string", 37 | "label": "Octopus CLI Version", 38 | "defaultValue": "embedded", 39 | "required": true, 40 | "helpMarkDown": "Specify `latest` or the version number to download. If you specify `embedded`, or the download fails, a built-in copy of the tool will be used." 41 | } 42 | ], 43 | "instanceNameFormat": "[DEPRECATED] Use Octopus CLI tool version $(version)", 44 | "execution": { 45 | "Node10": { 46 | "target": "index.js" 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /source/tasksLegacy/OctopusMetadata/OctopusMetadataV4/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/tasksLegacy/OctopusMetadata/OctopusMetadataV4/icon.png -------------------------------------------------------------------------------- /source/tasksLegacy/OctopusMetadata/OctopusMetadataV4/icon.svg: -------------------------------------------------------------------------------- 1 | octopus -------------------------------------------------------------------------------- /source/tasksLegacy/Pack/PackV4/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/tasksLegacy/Pack/PackV4/icon.png -------------------------------------------------------------------------------- /source/tasksLegacy/Pack/PackV4/icon.svg: -------------------------------------------------------------------------------- 1 | octopus -------------------------------------------------------------------------------- /source/tasksLegacy/Promote/PromoteV3/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/tasksLegacy/Promote/PromoteV3/icon.png -------------------------------------------------------------------------------- /source/tasksLegacy/Promote/PromoteV3/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/ban-ts-comment */ 2 | import * as tasks from "azure-pipelines-task-lib/task"; 3 | import { multiArgument, connectionArguments, includeAdditionalArgumentsAndProxyConfig, flag, argumentEnquote, argumentIfSet, getOrInstallOctoCommandRunner } from "../../Utils/tool"; 4 | import { getOptionalCsvInput, getRequiredCsvInput } from "../../Utils/inputs"; 5 | import { getDefaultOctopusConnectionDetailsOrThrow, resolveProjectName } from "../../Utils/connection"; 6 | import os from "os"; 7 | 8 | async function run() { 9 | try { 10 | tasks.warning("There is a later version of this task, we recommend using the latest version."); 11 | const connection = getDefaultOctopusConnectionDetailsOrThrow(); 12 | 13 | const space = tasks.getInput("Space"); 14 | // @ts-expect-error 15 | const project = await resolveProjectName(connection, tasks.getInput("Project", true)).then((x) => x.value); 16 | 17 | const from = tasks.getInput("From", true); 18 | const to = getRequiredCsvInput("To"); 19 | const showProgress = tasks.getBoolInput("ShowProgress"); 20 | const deploymentForTenants = getOptionalCsvInput("DeployForTenants"); 21 | const deployForTenantTags = getOptionalCsvInput("DeployForTentantTags"); 22 | const additionalArguments = tasks.getInput("AdditionalArguments"); 23 | 24 | const octo = await getOrInstallOctoCommandRunner("promote-release"); 25 | 26 | const configure = [ 27 | // @ts-expect-error 28 | argumentIfSet(argumentEnquote, "space", space), 29 | argumentEnquote("project", project), 30 | connectionArguments(connection), 31 | // @ts-expect-error 32 | argumentEnquote("from", from), 33 | multiArgument(argumentEnquote, "to", to), 34 | multiArgument(argumentEnquote, "tenant", deploymentForTenants), 35 | multiArgument(argumentEnquote, "tenanttag", deployForTenantTags), 36 | flag("progress", showProgress), 37 | // @ts-expect-error 38 | includeAdditionalArgumentsAndProxyConfig(connection.url, additionalArguments), 39 | ]; 40 | 41 | const code: number = await octo 42 | .map((x) => x.launchOcto(configure, "(release;promote;v3)")) 43 | .getOrElseL((x) => { 44 | throw new Error(x); 45 | }); 46 | 47 | tasks.setResult(tasks.TaskResult.Succeeded, "Succeeded promoting release with code " + code); 48 | } catch (error: unknown) { 49 | if (error instanceof Error) { 50 | tasks.setResult(tasks.TaskResult.Failed, `"Failed to promote release. ${error.message}${os.EOL}${error.stack}`, true); 51 | } 52 | } 53 | } 54 | 55 | run(); 56 | -------------------------------------------------------------------------------- /source/tasksLegacy/Promote/PromoteV4/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/tasksLegacy/Promote/PromoteV4/icon.png -------------------------------------------------------------------------------- /source/tasksLegacy/Promote/PromoteV4/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/ban-ts-comment */ 2 | import * as tasks from "azure-pipelines-task-lib/task"; 3 | import { argumentEnquote, argumentIfSet, assertOctoVersionAcceptsIds, connectionArguments, flag, getOrInstallOctoCommandRunner, includeAdditionalArgumentsAndProxyConfig, multiArgument } from "../../Utils/tool"; 4 | import { getOptionalCsvInput, getRequiredCsvInput } from "../../Utils/inputs"; 5 | import { getDefaultOctopusConnectionDetailsOrThrow } from "../../Utils/connection"; 6 | import os from "os"; 7 | 8 | async function run() { 9 | try { 10 | tasks.warning("There is a later version of this task, we recommend using the latest version."); 11 | const connection = getDefaultOctopusConnectionDetailsOrThrow(); 12 | 13 | const space = tasks.getInput("Space"); 14 | const project = tasks.getInput("Project", true); 15 | const from = tasks.getInput("From", true); 16 | const to = getRequiredCsvInput("To"); 17 | const deployForTenants = getOptionalCsvInput("DeployForTenants"); 18 | const deployForTenantTags = getOptionalCsvInput("DeployForTenantTags"); 19 | const showProgress = tasks.getBoolInput("ShowProgress"); 20 | const additionalArguments = tasks.getInput("AdditionalArguments"); 21 | 22 | await assertOctoVersionAcceptsIds(); 23 | const octo = await getOrInstallOctoCommandRunner("promote-release"); 24 | 25 | const configure = [ 26 | // @ts-expect-error 27 | argumentIfSet(argumentEnquote, "space", space), 28 | // @ts-expect-error 29 | argumentEnquote("project", project), 30 | connectionArguments(connection), 31 | // @ts-expect-error 32 | argumentEnquote("from", from), 33 | multiArgument(argumentEnquote, "to", to), 34 | multiArgument(argumentEnquote, "tenant", deployForTenants), 35 | multiArgument(argumentEnquote, "tenanttag", deployForTenantTags), 36 | flag("progress", showProgress), 37 | // @ts-expect-error 38 | includeAdditionalArgumentsAndProxyConfig(connection.url, additionalArguments), 39 | ]; 40 | 41 | const code: number = await octo 42 | .map((x) => x.launchOcto(configure, "(release;promote;v4)")) 43 | .getOrElseL((x) => { 44 | throw new Error(x); 45 | }); 46 | 47 | tasks.setResult(tasks.TaskResult.Succeeded, "Succeeded promoting release with code " + code); 48 | } catch (error: unknown) { 49 | if (error instanceof Error) { 50 | tasks.setResult(tasks.TaskResult.Failed, `"Failed to promote release. ${error.message}${os.EOL}${error.stack}`, true); 51 | } 52 | } 53 | } 54 | 55 | run(); 56 | -------------------------------------------------------------------------------- /source/tasksLegacy/Push/PushV3/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/tasksLegacy/Push/PushV3/icon.png -------------------------------------------------------------------------------- /source/tasksLegacy/Push/PushV3/icon.svg: -------------------------------------------------------------------------------- 1 | octopus -------------------------------------------------------------------------------- /source/tasksLegacy/Push/PushV3/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/ban-ts-comment */ 2 | import * as tasks from "azure-pipelines-task-lib/task"; 3 | 4 | import { multiArgument, connectionArguments, includeAdditionalArgumentsAndProxyConfig, flag, argumentEnquote, argumentIfSet, getOrInstallOctoCommandRunner } from "../../Utils/tool"; 5 | import { getLineSeparatedItems, resolveGlobs } from "../../Utils/inputs"; 6 | import { getDefaultOctopusConnectionDetailsOrThrow } from "../../Utils/connection"; 7 | import os from "os"; 8 | 9 | async function run() { 10 | try { 11 | tasks.warning("There is a later version of this task, we recommend using the latest version."); 12 | const connection = getDefaultOctopusConnectionDetailsOrThrow(); 13 | 14 | const space = tasks.getInput("Space"); 15 | // @ts-expect-error 16 | const packages = getLineSeparatedItems(tasks.getInput("Package", true)); 17 | const replace = tasks.getBoolInput("Replace"); 18 | const additionalArguments = tasks.getInput("AdditionalArguments"); 19 | 20 | const octo = await getOrInstallOctoCommandRunner("push"); 21 | const matchedPackages = await resolveGlobs(packages); 22 | 23 | const configure = [ 24 | connectionArguments(connection), 25 | // @ts-expect-error 26 | argumentIfSet(argumentEnquote, "space", space), 27 | multiArgument(argumentEnquote, "package", matchedPackages), 28 | flag("replace-existing", replace), 29 | // @ts-expect-error 30 | includeAdditionalArgumentsAndProxyConfig(connection.url, additionalArguments), 31 | ]; 32 | 33 | const code: number = await octo 34 | .map((x) => x.launchOcto(configure, "(package;push;v3)")) 35 | .getOrElseL((x) => { 36 | throw new Error(x); 37 | }); 38 | 39 | tasks.setResult(tasks.TaskResult.Succeeded, "Succeeded with code " + code); 40 | } catch (error: unknown) { 41 | if (error instanceof Error) { 42 | tasks.setResult(tasks.TaskResult.Failed, `"Failed to push package. ${error.message}${os.EOL}${error.stack}`, true); 43 | } 44 | } 45 | } 46 | 47 | run(); 48 | -------------------------------------------------------------------------------- /source/tasksLegacy/Push/PushV3/task.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "d05ad9a2-5d9e-4a1c-a887-14034334d6f2", 3 | "name": "OctopusPush", 4 | "friendlyName": "Push Package(s) to Octopus", 5 | "description": "There is a later version of this task, we recommend using the latest version. Push your NuGet or Zip package to your Octopus Deploy Server", 6 | "helpMarkDown": "set-by-pack.ps1", 7 | "category": "Package", 8 | "visibility": ["Build", "Release"], 9 | "author": "Octopus Deploy", 10 | "version": { 11 | "Major": 3, 12 | "Minor": 1, 13 | "Patch": 0 14 | }, 15 | "demands": [], 16 | "minimumAgentVersion": "2.144.0", 17 | "groups": [ 18 | { 19 | "name": "advanced", 20 | "displayName": "Advanced Options", 21 | "isExpanded": false 22 | } 23 | ], 24 | "inputs": [ 25 | { 26 | "name": "OctoConnectedServiceName", 27 | "type": "connectedService:OctopusEndpoint", 28 | "label": "Octopus Deploy Server", 29 | "defaultValue": "", 30 | "required": true, 31 | "helpMarkDown": "Octopus Deploy server connection" 32 | }, 33 | { 34 | "name": "Space", 35 | "type": "string", 36 | "label": "Space (Legacy - Use version 4 of this task)", 37 | "defaultValue": "", 38 | "required": false, 39 | "properties": { 40 | "EditableOptions": "False" 41 | }, 42 | "helpMarkDown": "Version 3 of this task has limited support for spaces. We recommend using version 4 of this task for a better experience." 43 | }, 44 | { 45 | "name": "Package", 46 | "type": "multiLine", 47 | "label": "Package", 48 | "defaultValue": "", 49 | "required": true, 50 | "helpMarkDown": "Package file to push. To push multiple packages, enter on multiple lines." 51 | }, 52 | { 53 | "name": "Replace", 54 | "type": "boolean", 55 | "label": "Replace Existing", 56 | "defaultValue": "False", 57 | "required": true, 58 | "helpMarkDown": "If the package already exists in the repository, the default behavior is to reject the new package being pushed. Set this flag to 'True' to overwrite the existing package." 59 | }, 60 | { 61 | "name": "AdditionalArguments", 62 | "type": "string", 63 | "label": "Additional Arguments", 64 | "defaultValue": "", 65 | "required": false, 66 | "helpMarkDown": "Additional arguments to be supplied to the Octopus CLI. See the [Octopus CLI documentation](https://g.octopushq.com/OctoExePush) for available parameters.", 67 | "groupName": "advanced" 68 | } 69 | ], 70 | "instanceNameFormat": "Push Packages to Octopus", 71 | "execution": { 72 | "Node10": { 73 | "target": "index.js" 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /source/tasksLegacy/Push/PushV4/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OctopusDeploy/OctoTFS/68c4e63d11fd8e010a7edc5bdcb82f02438896fc/source/tasksLegacy/Push/PushV4/icon.png -------------------------------------------------------------------------------- /source/tasksLegacy/Push/PushV4/icon.svg: -------------------------------------------------------------------------------- 1 | octopus -------------------------------------------------------------------------------- /source/tasksLegacy/Push/PushV4/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/ban-ts-comment */ 2 | import * as tasks from "azure-pipelines-task-lib/task"; 3 | 4 | import { getLineSeparatedItems, getOverwriteModeFromReplaceInput, resolveGlobs } from "../../Utils/inputs"; 5 | import { argument, argumentEnquote, argumentIfSet, assertOctoVersionAcceptsIds, connectionArguments, getOrInstallOctoCommandRunner, includeAdditionalArgumentsAndProxyConfig, multiArgument } from "../../Utils/tool"; 6 | import { getDefaultOctopusConnectionDetailsOrThrow } from "../../Utils/connection"; 7 | import os from "os"; 8 | 9 | async function run() { 10 | try { 11 | tasks.warning("There is a later version of this task, we recommend using the latest version."); 12 | const connection = getDefaultOctopusConnectionDetailsOrThrow(); 13 | 14 | const space = tasks.getInput("Space"); 15 | // @ts-expect-error 16 | const packages = getLineSeparatedItems(tasks.getInput("Package", true)); 17 | // @ts-expect-error 18 | const overwriteMode = getOverwriteModeFromReplaceInput(tasks.getInput("Replace", true)); 19 | const additionalArguments = tasks.getInput("AdditionalArguments"); 20 | 21 | await assertOctoVersionAcceptsIds(); 22 | const octo = await getOrInstallOctoCommandRunner("push"); 23 | const matchedPackages = await resolveGlobs(packages); 24 | 25 | const configure = [ 26 | connectionArguments(connection), 27 | // @ts-expect-error 28 | argumentIfSet(argumentEnquote, "space", space), 29 | multiArgument(argumentEnquote, "package", matchedPackages), 30 | argument("overwrite-mode", overwriteMode), 31 | // @ts-expect-error 32 | includeAdditionalArgumentsAndProxyConfig(connection.url, additionalArguments), 33 | ]; 34 | 35 | const code: number = await octo 36 | .map((x) => x.launchOcto(configure, "(package;push;v4)")) 37 | .getOrElseL((x) => { 38 | throw new Error(x); 39 | }); 40 | 41 | tasks.setResult(tasks.TaskResult.Succeeded, "Succeeded with code " + code); 42 | } catch (error: unknown) { 43 | if (error instanceof Error) { 44 | tasks.setResult(tasks.TaskResult.Failed, `"Failed to push package. ${error.message}${os.EOL}${error.stack}`, true); 45 | } 46 | } 47 | } 48 | 49 | run(); 50 | -------------------------------------------------------------------------------- /source/tasksLegacy/Utils/OctoApiKeyHandler.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/ban-ts-comment */ 2 | import { IRequestHandler, IHttpClientResponse } from "typed-rest-client/Interfaces"; 3 | import * as http from "http"; 4 | 5 | export class OctoApiKeyHandler implements IRequestHandler { 6 | key: string; 7 | 8 | constructor(key: string) { 9 | this.key = key; 10 | } 11 | 12 | prepareRequest(options: http.RequestOptions): void { 13 | // @ts-expect-error 14 | options.headers["X-Octopus-ApiKey"] = this.key; 15 | } 16 | 17 | canHandleAuthentication(): boolean { 18 | return false; 19 | } 20 | 21 | handleAuthentication(): Promise { 22 | throw "This handler does not handle authentication."; 23 | } 24 | } 25 | 26 | export default OctoApiKeyHandler; 27 | -------------------------------------------------------------------------------- /source/tasksLegacy/Utils/connection.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/ban-ts-comment */ 2 | import * as tasks from "azure-pipelines-task-lib/task"; 3 | import { RestClient } from "typed-rest-client/RestClient"; 4 | import { getDefaultOctoConnectionInputValue } from "./inputs"; 5 | import { either } from "fp-ts"; 6 | import OctoApiKeyHandler from "./OctoApiKeyHandler"; 7 | 8 | export interface OctoServerConnectionDetails { 9 | url: string; 10 | apiKey: string; 11 | ignoreSslErrors: boolean; 12 | } 13 | 14 | export function getDefaultOctopusConnectionDetailsOrThrow() { 15 | const result = getDefaultOctoConnectionInputValue().map(getOctopusConnectionDetails).toNullable(); 16 | if (!result) { 17 | throw new Error("Could not retrieve default Octo connection information"); 18 | } 19 | return result; 20 | } 21 | 22 | export function getOctopusConnectionDetails(name: string): OctoServerConnectionDetails { 23 | const octoEndpointAuthorization = tasks.getEndpointAuthorization(name, false); 24 | const ignoreSSL = tasks.getEndpointDataParameter(name, "ignoreSslErrors", true); 25 | return { 26 | // @ts-expect-error 27 | url: tasks.getEndpointUrl(name, false), 28 | // @ts-expect-error 29 | apiKey: octoEndpointAuthorization.parameters["apitoken"], 30 | ignoreSslErrors: !!ignoreSSL && ignoreSSL.toLowerCase() === "true", 31 | }; 32 | } 33 | 34 | export function fetchProjectName(details: OctoServerConnectionDetails, projectId: string) { 35 | console.log("Ignore SSL: " + details.ignoreSslErrors); 36 | const client = new RestClient("OctoTFS", details.url, [new OctoApiKeyHandler(details.apiKey)], { ignoreSslError: details.ignoreSslErrors }); 37 | return client 38 | .get<{ Name: string }>(`api/projects/${projectId}`) 39 | .then((x) => { 40 | if (x.result) { 41 | return either.right(x.result.Name); 42 | } 43 | 44 | return either.left(`Could not resolve project name given id "${projectId}". Server returned status code: ${x.statusCode}`); 45 | }) 46 | .catch((error) => either.left(error)); 47 | } 48 | 49 | export const isProjectId = (projectNameOrId: string) => /\w*Projects-\d*/.test(projectNameOrId); 50 | 51 | export function resolveProjectName(connection: OctoServerConnectionDetails, projectNameOrId: string) { 52 | if (isProjectId(projectNameOrId)) { 53 | return fetchProjectName(connection, projectNameOrId); 54 | } 55 | 56 | return Promise.resolve(either.right(projectNameOrId)); 57 | } 58 | -------------------------------------------------------------------------------- /source/tasksLegacy/Utils/inputs.ts: -------------------------------------------------------------------------------- 1 | import * as tasks from "azure-pipelines-task-lib/task"; 2 | import { option } from "fp-ts"; 3 | import glob from "glob"; 4 | import { flatten } from "ramda"; 5 | 6 | export enum ReplaceOverwriteMode { 7 | false = "FailIfExists", 8 | true = "OverwriteExisting", 9 | IgnoreIfExists = "IgnoreIfExists", 10 | } 11 | 12 | export function getOverwriteModeFromReplaceInput(replace: string): ReplaceOverwriteMode { 13 | return ReplaceOverwriteMode[replace as keyof typeof ReplaceOverwriteMode] || ReplaceOverwriteMode.false; 14 | } 15 | 16 | export const DefaultOctoConnectionInputName = "OctoConnectedServiceName"; 17 | 18 | export const pGlobNoNull = (pattern: string): Promise => { 19 | return new Promise((resolve, reject) => { 20 | glob(pattern, { nonull: true }, (err, matches) => { 21 | if (err) { 22 | reject(err); 23 | return; 24 | } 25 | resolve(matches); 26 | }); 27 | }); 28 | }; 29 | 30 | export function isNullOrWhitespace(value: string | null | undefined): value is null | undefined { 31 | return !value || !/\S/.test(value); 32 | } 33 | 34 | export function safeTrim(value: string | null | undefined): string | null | undefined { 35 | return value ? value.trim() : value; 36 | } 37 | 38 | export function removeTrailingSlashes(value: string | null | undefined): string | null | undefined { 39 | return value ? value.replace(/[/\\]+(?=\s*)$/, "") : value; 40 | } 41 | 42 | export function getLineSeparatedItems(value: string): Array { 43 | return value ? value.split(/[\r\n]+/g).map((x) => x.trim()) : []; 44 | } 45 | 46 | const getRequiredInput = (name: string) => { 47 | return option.fromNullable(tasks.getInput(name, true)); 48 | }; 49 | 50 | const splitComma = (x: string) => x.split(",").map((x) => x.trim()); 51 | 52 | const getOptionalInput = (name: string) => { 53 | return option.fromNullable(tasks.getInput(name, false)); 54 | }; 55 | 56 | export const getOptionalCsvInput = (name: string) => { 57 | return getOptionalInput(name).map(splitComma).getOrElse([]); 58 | }; 59 | 60 | export const getRequiredCsvInput = (name: string) => { 61 | return getRequiredInput(name).map(splitComma).getOrElse([]); 62 | }; 63 | 64 | export { getRequiredInput, getOptionalInput }; 65 | 66 | export function getDefaultOctoConnectionInputValue() { 67 | return getRequiredInput(DefaultOctoConnectionInputName); 68 | } 69 | 70 | export const resolveGlobs = (globs: string[]): Promise => { 71 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 72 | // @ts-ignore 73 | return Promise.all(globs.map(pGlobNoNull)).then((x) => flatten(x)); 74 | }; 75 | -------------------------------------------------------------------------------- /source/widgets/ProjectStatus/configuration.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
    6 | 12 | 18 | 24 | 30 | 36 |
    37 | 38 | 39 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /source/widgets/ProjectStatus/octo-status.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 51 | 65 | 66 | 67 | 68 | 69 | 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /task-ids.json: -------------------------------------------------------------------------------- 1 | { 2 | "localtest": { 3 | "OctopusAwaitTask": "885046af-620f-4ac8-961d-e7aaaf8d24ea", 4 | "OctopusCreateRelease": "69084338-2108-493c-98b0-c4410f3e8b7a", 5 | "OctopusDeployRelease": "947a9649-a21f-4402-9a4d-f0267fa69c9c", 6 | "OctopusDeployReleaseTenanted": "091ac6f1-da1b-47d1-acf9-d2b93c4f2d67", 7 | "OctopusPack": "ba79430a-bd26-4fc0-b827-6e442de623e4", 8 | "OctopusPackNuGet": "cbdb1435-36a5-42b1-a419-18888f0df707", 9 | "OctopusPackZip": "8f600674-2a6a-489a-a447-2cce0e26bfb3", 10 | "OctopusPromote": "60c1aa6c-2d9a-455a-a4c6-5835eeddbd2d", 11 | "OctopusPush": "72153241-00c1-4f78-a64f-8b2c0d187b3c", 12 | "OctopusMetadata": "2032df04-eeb2-4afc-9854-cec4d7169e4c", 13 | "OctopusBuildInformation": "00a6aea4-412d-48a5-8af3-369e8654913f", 14 | "OctoInstaller": "0bb4020f-14f3-409b-ac87-b6ec75066419", 15 | "OctoCli": "92d8efb7-1c1f-4803-865e-54baac6ba644", 16 | "OctopusRunRunbook": "01e38835-484e-46eb-a9ed-5c265396146f" 17 | }, 18 | "test": { 19 | "OctopusAwaitTask": "38df691d-23eb-48d4-8638-61764f48bacb", 20 | "OctopusCreateRelease": "4E131B60-5532-4362-95B6-7C67D9841B4F", 21 | "OctopusDeployRelease": "8ca1d96a-151d-44b7-bc4f-9251e2ea6971", 22 | "OctopusDeployReleaseTenanted": "a847e2d1-5435-4d52-a774-6d300953e85f", 23 | "OctopusPack": "179fac12-2402-486e-80cf-5a6a8571f7c0", 24 | "OctopusPackNuGet": "72e7a1b6-19bc-48e6-8d20-a81f201d65a3", 25 | "OctopusPackZip": "3f248d80-a755-498d-863c-f936c5821318", 26 | "OctopusPromote": "1627fcfe-f292-4904-adac-26cfb14bdb07", 27 | "OctopusPush": "d05ad9a2-5d9e-4a1c-a887-14034334d6f2", 28 | "OctopusMetadata": "b1861ef4-b62e-40c1-bcb0-be00d454a8a7", 29 | "OctopusBuildInformation": "559b81c9-efc1-40f3-9058-71ab1810d837", 30 | "OctoInstaller": "ccdc7423-0f15-48d1-a104-78e181ea8c2f", 31 | "OctoCli": "ca025f3d-d8b6-4ca2-aa94-7785e784581f", 32 | "OctopusRunRunbook": "5a2273e0-aa4f-4502-bcba-6817835e2bbd" 33 | }, 34 | "production": { 35 | "OctopusAwaitTask": "38df691d-23eb-48d4-8638-61764f48bacb", 36 | "OctopusCreateRelease": "4E131B60-5532-4362-95B6-7C67D9841B4F", 37 | "OctopusDeployRelease": "8ca1d96a-151d-44b7-bc4f-9251e2ea6971", 38 | "OctopusDeployReleaseTenanted": "a847e2d1-5435-4d52-a774-6d300953e85f", 39 | "OctopusPack": "179fac12-2402-486e-80cf-5a6a8571f7c0", 40 | "OctopusPackNuGet": "72e7a1b6-19bc-48e6-8d20-a81f201d65a3", 41 | "OctopusPackZip": "3f248d80-a755-498d-863c-f936c5821318", 42 | "OctopusPromote": "1627fcfe-f292-4904-adac-26cfb14bdb07", 43 | "OctopusPush": "d05ad9a2-5d9e-4a1c-a887-14034334d6f2", 44 | "OctopusMetadata": "b1861ef4-b62e-40c1-bcb0-be00d454a8a7", 45 | "OctopusBuildInformation": "559b81c9-efc1-40f3-9058-71ab1810d837", 46 | "OctoInstaller": "7aeae2f5-8529-4326-b127-a66c334c6e60", 47 | "OctoCli": "ac1f0794-ad67-4c60-a386-8704226392be", 48 | "OctopusRunRunbook": "5a2273e0-aa4f-4502-bcba-6817835e2bbd" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/node10/tsconfig.json", 3 | "compilerOptions": { 4 | "declaration": false, 5 | "sourceMap": true, 6 | "noUnusedLocals": true, 7 | "noUnusedParameters": true, 8 | "noImplicitReturns": true, 9 | "noFallthroughCasesInSwitch": true, 10 | "baseUrl": "./source", 11 | "allowSyntheticDefaultImports": true, 12 | } 13 | } 14 | --------------------------------------------------------------------------------