From 0690d52f5bc70e5655b08a7cd00833716af6da93 Mon Sep 17 00:00:00 2001 From: Jerome Laban Date: Fri, 17 May 2024 15:57:27 -0400 Subject: [PATCH] chore: Remove unused files, build extension --- .github/workflows/main.yml | 39 +- extensions/azuredevops/BREAKING_CHANGES.md | 11 - extensions/azuredevops/CODE_OF_CONDUCT.md | 78 ---- extensions/azuredevops/CONTRIBUTING.md | 86 ----- extensions/azuredevops/GitVersion.yml | 5 - extensions/azuredevops/overview.md | 20 +- .../releaseNotesCompiler/Readme.md | 13 - .../azuredevops/releaseNotesCompiler/icon.png | Bin 20423 -> 0 bytes .../releaseNotesCompiler/package.json | 22 -- .../releaseNotesCompiler/task.json | 130 ------- .../azuredevops/releaseNotesCompiler/task.ts | 191 ---------- extensions/azuredevops/vss-extension.json | 34 +- .../azuredevops/websiteVersion/Readme.md | 11 - .../azure-arm-rest/AzureServiceClient.ts | 275 -------------- .../azure-arm-rest/azure-arm-common.ts | 283 -------------- .../azure-arm-rest/azure-arm-endpoint.ts | 152 -------- .../azure-arm-rest/azure-arm-storage.ts | 201 ---------- .../azure-arm-rest/azureModels.ts | 355 ------------------ .../azure-arm-rest/constants.ts | 29 -- .../azure-arm-rest/webClient.ts | 117 ------ .../azuredevops/websiteVersion/icon.png | Bin 20423 -> 0 bytes .../azuredevops/websiteVersion/package.json | 29 -- .../azuredevops/websiteVersion/task.json | 80 ---- extensions/azuredevops/websiteVersion/task.ts | 266 ------------- 24 files changed, 40 insertions(+), 2387 deletions(-) delete mode 100644 extensions/azuredevops/BREAKING_CHANGES.md delete mode 100644 extensions/azuredevops/CODE_OF_CONDUCT.md delete mode 100644 extensions/azuredevops/CONTRIBUTING.md delete mode 100644 extensions/azuredevops/GitVersion.yml delete mode 100644 extensions/azuredevops/releaseNotesCompiler/Readme.md delete mode 100644 extensions/azuredevops/releaseNotesCompiler/icon.png delete mode 100644 extensions/azuredevops/releaseNotesCompiler/package.json delete mode 100644 extensions/azuredevops/releaseNotesCompiler/task.json delete mode 100644 extensions/azuredevops/releaseNotesCompiler/task.ts delete mode 100644 extensions/azuredevops/websiteVersion/Readme.md delete mode 100644 extensions/azuredevops/websiteVersion/azure-arm-rest/AzureServiceClient.ts delete mode 100644 extensions/azuredevops/websiteVersion/azure-arm-rest/azure-arm-common.ts delete mode 100644 extensions/azuredevops/websiteVersion/azure-arm-rest/azure-arm-endpoint.ts delete mode 100644 extensions/azuredevops/websiteVersion/azure-arm-rest/azure-arm-storage.ts delete mode 100644 extensions/azuredevops/websiteVersion/azure-arm-rest/azureModels.ts delete mode 100644 extensions/azuredevops/websiteVersion/azure-arm-rest/constants.ts delete mode 100644 extensions/azuredevops/websiteVersion/azure-arm-rest/webClient.ts delete mode 100644 extensions/azuredevops/websiteVersion/icon.png delete mode 100644 extensions/azuredevops/websiteVersion/package.json delete mode 100644 extensions/azuredevops/websiteVersion/task.json delete mode 100644 extensions/azuredevops/websiteVersion/task.ts diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1704014..54e12d8 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -11,8 +11,8 @@ on: - main jobs: - build: - name: Build + build-tools: + name: Build Tools runs-on: windows-2022 steps: - name: Checkout @@ -58,12 +58,43 @@ jobs: name: NuGet path: .\artifacts - publish: + build-extensions: + name: Build Extensions + runs-on: windows-2022 + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + fetch-depth: 0 + + - name: Install tfx + working-directory: extensions/azuredevops + run: npm install tfx-cli@0.7.x -g --no-audit --no-fund + + - name: npm install + working-directory: extensions/azuredevops + run: npm install tfx-cli@0.7.x -g --no-audit --no-fund + + - name: Compile + working-directory: extensions/azuredevops + run: .\node_modules\.bin\tsc -project .\tsconfig.json --listEmittedFiles --locale en-US --isolatedModules + + - name: Package Extension + working-directory: extensions/azuredevops + run: tfx extension create --json --no-color --output-path .\artifacts\Build.Tasks.$(GitVersion.MajorMinorPatch).vsix --override "{\"version\":\"$(GitVersion.MajorMinorPatch)\"}" + + - name: Upload Artifacts + uses: actions/upload-artifact@v2 + with: + name: extensions + path: .\artifacts + + publish-tools: name: Publish if: ${{ github.event_name == 'push' && (github.ref == 'refs/heads/develop' || startsWith(github.ref, 'refs/heads/master')) }} runs-on: windows-latest needs: - - build + - build-tools steps: - name: Checkout uses: actions/checkout@v2 diff --git a/extensions/azuredevops/BREAKING_CHANGES.md b/extensions/azuredevops/BREAKING_CHANGES.md deleted file mode 100644 index cb35164..0000000 --- a/extensions/azuredevops/BREAKING_CHANGES.md +++ /dev/null @@ -1,11 +0,0 @@ -# Breaking Changes - - diff --git a/extensions/azuredevops/CODE_OF_CONDUCT.md b/extensions/azuredevops/CODE_OF_CONDUCT.md deleted file mode 100644 index a8939c1..0000000 --- a/extensions/azuredevops/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,78 +0,0 @@ -# Contributor Covenant Code of Conduct - -## Our Pledge - -In the interest of fostering an open and welcoming environment, we as -contributors and maintainers pledge to making participation in our project and -our community a harassment-free experience for everyone, regardless of age, body -size, disability, ethnicity, sex characteristics, gender identity and -expression, level of experience, education, socio-economic status, nationality, -personal appearance, race, religion, or sexual identity and orientation. - -## Our Standards - -Examples of behavior that contributes to creating a positive environment -include: - -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members - -Examples of unacceptable behavior by participants include: - -* The use of sexualized language or imagery and unwelcome sexual attention or - advances -* Trolling, insulting/derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or electronic - address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting - -## Our Responsibilities - -Project maintainers are responsible for clarifying the standards of acceptable -behavior and are expected to take appropriate and fair corrective action in -response to any instances of unacceptable behavior. - -Project maintainers have the right and responsibility to remove, edit, or reject -comments, commits, code, wiki edits, issues, and other contributions that are -not aligned to this Code of Conduct, or to ban temporarily or permanently any -contributor for other behaviors that they deem inappropriate, threatening, -offensive, or harmful. - -## Scope - -This Code of Conduct applies within all project spaces, and it also applies when -an individual is representing the project or its community in public spaces. -Examples of representing a project or community include using an official -project e-mail address, posting via an official social media account, or acting -as an appointed representative at an online or offline event. Representation of -a project may be further defined and clarified by project maintainers. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported by contacting the project team at info@unoplatform.com. All -complaints will be reviewed and investigated and will result in a response that -is deemed necessary and appropriate to the circumstances. The project team is -obligated to maintain confidentiality with regard to the reporter of an -incident. Further details of specific enforcement policies may be posted -separately. - -Project maintainers who do not follow or enforce the Code of Conduct in good -faith may face temporary or permanent repercussions as determined by other -members of the project's leadership. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], -version 1.4, available at -https://www.contributor-covenant.org/version/1/4/code-of-conduct.html - -[homepage]: https://www.contributor-covenant.org - -For answers to common questions about this code of conduct, see -https://www.contributor-covenant.org/faq diff --git a/extensions/azuredevops/CONTRIBUTING.md b/extensions/azuredevops/CONTRIBUTING.md deleted file mode 100644 index 164f871..0000000 --- a/extensions/azuredevops/CONTRIBUTING.md +++ /dev/null @@ -1,86 +0,0 @@ -# How to Contribute - -We'd love to accept your patches, contributions and suggestions to this project. -Here are a few small guidelines you need to follow. - -## Code of conduct - -To better foster an open, innovative and inclusive community please refer to our -[Code of Conduct](CODE_OF_CONDUCT.md) when contributing. - -### Report a bug - -If you think you've found a bug, please log a new issue in the [GitHub issue -tracker. When filing issues, please use our [issue -template](.github/ISSUE_TEMPLATE.md). The best way to get your bug fixed is to -be as detailed as you can be about the problem. Providing a minimal project with -steps to reproduce the problem is ideal. Here are questions you can answer -before you file a bug to make sure you're not missing any important information. - -1. Did you read the documentation? -2. Did you include the snippet of broken code in the issue? -3. What are the *EXACT* steps to reproduce this problem? -4. What specific version or build are you using? -5. What operating system are you using? - -GitHub supports -[markdown](https://help.github.com/articles/github-flavored-markdown/), so when -filing bugs make sure you check the formatting before clicking submit. - -### Make a suggestion - -If you have an idea for a new feature or enhancement let us know by filing an -issue. To help us understand and prioritize your idea please provide as much -detail about your scenario and why the feature or enhancement would be useful. - -## Contributing code and content - -This is an open source project and we welcome code and content contributions -from the community. - -**Identifying the scale** - -If you would like to contribute to this project, first identify the scale of -what you would like to contribute. If it is small (grammar/spelling or a bug -fix) feel free to start working on a fix. - -If you are submitting a feature or substantial code contribution, please discuss -it with the team. You might also read these two blogs posts on contributing -code: [Open Source Contribution -Etiquette](http://tirania.org/blog/archive/2010/Dec-31.html) by Miguel de Icaza -and [Don't "Push" Your Pull -Requests](https://www.igvita.com/2011/12/19/dont-push-your-pull-requests/) by -Ilya Grigorik. Note that all code submissions will be rigorously reviewed and -tested by the project team, and only those that meet an extremely high bar for -both quality and design/roadmap appropriateness will be merged into the source. - -**Obtaining the source code** - -If you are an outside contributor, please fork the repository to your account. -See the GitHub documentation for [forking a -repo](https://help.github.com/articles/fork-a-repo/) if you have any questions -about this. - -**Submitting a pull request** - -If you don't know what a pull request is read this article: -https://help.github.com/articles/using-pull-requests. Make sure the repository -can build and all tests pass, as well as follow the current coding guidelines. -When submitting a pull request, please use our [pull request -template](.github/PULL_REQUEST_TEMPLATE.md). - -Pull requests should all be done to the **master** branch. - ---- - -## Code reviews - -All submissions, including submissions by project members, require review. We -use GitHub pull requests for this purpose. Consult [GitHub -Help](https://help.github.com/articles/about-pull-requests/) for more -information on using pull requests. - -## Community Guidelines - -This project follows [Google's Open Source Community -Guidelines](https://opensource.google.com/conduct/). \ No newline at end of file diff --git a/extensions/azuredevops/GitVersion.yml b/extensions/azuredevops/GitVersion.yml deleted file mode 100644 index 804d5d8..0000000 --- a/extensions/azuredevops/GitVersion.yml +++ /dev/null @@ -1,5 +0,0 @@ -assembly-versioning-scheme: MajorMinorPatch -mode: Mainline -next-version: 6.0 -ignore: - sha: [] diff --git a/extensions/azuredevops/overview.md b/extensions/azuredevops/overview.md index 8f25686..b2c5f04 100644 --- a/extensions/azuredevops/overview.md +++ b/extensions/azuredevops/overview.md @@ -1,25 +1,7 @@ -This extensions gives acess the following build tasks : +This extensions gives access the following build tasks : # Canary Updater A task allowing to automatically update NuGet packages to the latest version. The canary process is meant to be run in its own branch and is a two step process: - Merge a working branch in the canary branch - Use [NuGet.Updater](https://github.com/unoplatform/NuGet.Updater/blob/develop/src/NvGet.Tools.Updater/Readme.md) to update the packages to the latest matching version - -# Release Notes Compiler -A task generating a simple set of release notes in the markdown format. By default, these notes contain the following information: -- The name and a link to the branch from which the build was run -- The commit id and a link to it -- A link the pipeline run where this task was executed -- Optionally, the name of an environment provided to the task -- Another release note file can also be appended to the notes generated. - -A truncated version of the release notes can be generated to accomodate limitations from certain services. -# Website versioning -A task providing a custom solution for website versioning. It works as follows: -- At the location where the website is stored, a "versions" folder is created, in which a version-specific folder is created for the current version of the website being deployed -- The current version of the website is uploaded in the version folder -- The index page at the root of website is configured to redirect to the version folder -- Another index file is present in the versions folder, allowing access to all previous versions - -This task currently only supports a website hosted in an Azure Storage account. \ No newline at end of file diff --git a/extensions/azuredevops/releaseNotesCompiler/Readme.md b/extensions/azuredevops/releaseNotesCompiler/Readme.md deleted file mode 100644 index 5cd7461..0000000 --- a/extensions/azuredevops/releaseNotesCompiler/Readme.md +++ /dev/null @@ -1,13 +0,0 @@ -# unoplatform Release Notes compiler task - -This straightforward task is used to help with the generation of basic release notes in the markdown format. The primary use case for those notes was AppCenter, but it can be adapted to fit any use. The flow is pretty simple, and consists mostly of gathering information on the pipeline run and writing those in a file. Here's a list of the information included in the resulting file: -- An optional environment passed as a parameter to the task (`EnvironmentName`) - useful to make sure that we're using the right build -- The name of the source branch, including a link to Azure DevOps -- The ID of the source commit, including a link to Azure DevOps -- The URL of the pipeline run, including a link to Azure DevOps -- Additional release notes taken from another file specified in the parameters (`AdditionalReleaseNotesFile`) - -This task is able to produce a truncated version of the resulting release note file. This is very useful since some services (especially AppCenter) have a limit on the number of characters that can be included in the release notes. To do so, the `CreateTruncatedVersion`, `CharacterLimit` and `TruncatedOutputFilePath` parameters must be set. -It is also possible to remove the hyperlinks present in the release notes if they are not deemed necessary using the `RemoveHyperlinks` parameter. - -The full-size file will be generated under the path specified in the `OutputFilePath` parameter. The task also provides an output variable called `ReleaseNotesPath` to use in subsequent steps. \ No newline at end of file diff --git a/extensions/azuredevops/releaseNotesCompiler/icon.png b/extensions/azuredevops/releaseNotesCompiler/icon.png deleted file mode 100644 index 1a199b961ba0e18eaebb962cb88f921118642bf3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20423 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 diff --git a/extensions/azuredevops/releaseNotesCompiler/package.json b/extensions/azuredevops/releaseNotesCompiler/package.json deleted file mode 100644 index 303581a..0000000 --- a/extensions/azuredevops/releaseNotesCompiler/package.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "releasenotes.compiler", - "description": "Task allowing the compiling of release notes", - "scripts": { - "test": "echo 'Hello world" - }, - "license": "ISC", - "devDependencies": { - "azure-pipelines-task-lib": "^2.7.7", - "azure-devops-node-api": "^9.0.1" - }, - "repository": { - "type": "git", - "url": "https://unoplatform.visualstudio.com/DevOps/_git/Build.Tasks" - }, - "keywords": [ - "Azure DevOps", - "Azure Pipelines", - "Task" - ], - "author": "unoplatform" -} diff --git a/extensions/azuredevops/releaseNotesCompiler/task.json b/extensions/azuredevops/releaseNotesCompiler/task.json deleted file mode 100644 index 4531df2..0000000 --- a/extensions/azuredevops/releaseNotesCompiler/task.json +++ /dev/null @@ -1,130 +0,0 @@ -{ - "id": "b4cb4ecf-9677-4a0a-bc98-45fc0dd140c1", - "name": "nventiveReleaseNotesCompiler", - "friendlyName": "Release Notes Compiler", - "description": "A task to generate a markdown file containing release notes for a given release.", - "helpMarkDown": "[unoplatform](http://www.unoplatform.com/)", - "category": "Azure Pipelines", - "author": "unoplatform", - "version": { - "Major": 0, - "Minor": 0, - "Patch": 0 - }, - "instanceNameFormat": "Compile release notes", - "groups": [ - { - "name": "input", - "displayName": "Input", - "isExpanded": true - }, - { - "name": "output", - "displayName": "Output", - "isExpanded": true - } - ], - "inputs": [ - { - "name": "EnvironmentName", - "type": "string", - "label": "Application environment", - "defaultValue": "", - "group": "input", - "required": false, - "helpMarkDown": "The name of the environment targeted by the application (Staging, Production, etc.)", - "properties": { - "DisableManageLink": "True" - } - }, - { - "name": "AdditionalReleaseNotesFile", - "type": "filePath", - "label": "Additional release notes file", - "group": "input", - "defaultValue": "", - "required": false, - "helpMarkDown": "A file containing additional release notes to append at the end of the ones generated.", - "properties": { - "DisableManageLink": "True" - } - }, - { - "name": "OutputFilePath", - "type": "filePath", - "label": "Output file path", - "group": "output", - "defaultValue": "", - "required": true, - "helpMarkDown": "A file where to save the release notes.", - "properties": { - "DisableManageLink": "True" - } - }, - { - "name": "CreateTruncatedVersion", - "type": "boolean", - "label": "Create a truncated version of the release notes", - "group": "output", - "defaultValue": false, - "helpMarkDown": "Creating a truncated version of the release notes can be helpful when using services with a character limit on release notes markdonwn (like AppCenter); this will cause 2 files to be generated." - }, - { - "name": "TruncatedOutputFilePath", - "type": "filePath", - "label": "Truncated output file path", - "group": "output", - "defaultValue": "", - "required": true, - "visibleRule": "CreateTruncatedVersion = true", - "helpMarkDown": "A file where to save the truncated release notes.", - "properties": { - "DisableManageLink": "True" - } - }, - { - "name": "CharacterLimit", - "type": "string", - "label": "Character limit", - "group": "output", - "defaultValue": "", - "required": true, - "visibleRule": "CreateTruncatedVersion = true", - "helpMarkDown": "The maximum number of character the markdown file should have.", - "properties": { - "DisableManageLink": "True" - } - }, - { - "name": "RemoveHyperlinks", - "type": "bool", - "label": "Remove hyperlinks from release notes", - "group": "output", - "defaultValue": false, - "required": true, - "visibleRule": "CreateTruncatedVersion = true", - "helpMarkDown": "Replace all markdown hyperlinks with only the name (ex: `[Check this out](https://dev.azure.com/)` will be replace with Check this out.", - "properties": { - "DisableManageLink": "True" - } - } - ], - "dataSourceBindings": [], - "sourceDefinitions": [], - "OutputVariables": [ - { - "name" : "ReleaseNotesPath", - "description" : "The path to the full release notes." - }, - { - "name" : "TruncatedReleaseNotesPath", - "description" : "The path to the truncated release notes." - } - ], - "execution": - { - "Node": { - "target": "task.js" - } - } -} diff --git a/extensions/azuredevops/releaseNotesCompiler/task.ts b/extensions/azuredevops/releaseNotesCompiler/task.ts deleted file mode 100644 index b5682c4..0000000 --- a/extensions/azuredevops/releaseNotesCompiler/task.ts +++ /dev/null @@ -1,191 +0,0 @@ -import tl = require("azure-pipelines-task-lib"); -import fs = require('fs'); - -async function run() { - let output = addLine("", "# Details"); - - output = addLine(output, getEnvironmentContent()); - - output = addLine(output, getSourceBranchContent()); - - output = addLine(output, getSourceCommitContent()); - - output = addLine(output, getPipelineUrl()); - - output = addLine(output, getAdditionalReleaseNotesContent()); - - let outputFile = tl.getInput("OutputFilePath"); - - console.log("Generating release notes in " + outputFile); - - await writeOutputToFile(output, outputFile); - - tl.setVariable("ReleaseNotesPath", outputFile); - - if(tl.getBoolInput("CreateTruncatedVersion")) { - let characterLimit = parseInt(tl.getInput("CharacterLimit")); - let truncatedFilePath = tl.getInput("TruncatedOutputFilePath"); - - console.log("Generating release notes truncated at " + characterLimit + " characters in " + truncatedFilePath); - - await writeOutputToFile(trimOutput(output, characterLimit, "..."), truncatedFilePath); - - tl.setVariable("TruncatedReleaseNotesPath", truncatedFilePath); - } -} - -function getEnvironmentContent() : string { - let environment = tl.getInput("EnvironmentName"); - - if(environment) { - return "- Environment: " + environment; - } - - return null; -} - -function getSourceBranchContent() : string { - let branchName = tl.getVariable("Build.SourceBranch").replace("refs/heads/",""); - let repositoryUrl = GetRepositoryUrl(); - let branchUrl: string; - - if(repositoryUrl) { - branchUrl = repositoryUrl + "?version=GB" + encodeURIComponent(branchName); - } - - return "- Source branch: " + getHyperlinkMarkdown(branchName, branchUrl); -} - -function getSourceCommitContent() : string { - let sourceVersion = tl.getVariable("Build.SourceVersion"); - let repositoryUrl = GetRepositoryUrl(); - let commitUrl: string; - - if(repositoryUrl) { - commitUrl = repositoryUrl + "/commit/" + sourceVersion + "?refName=" + encodeURIComponent(tl.getVariable("Build.SourceBranch")); - } - - return "- Source commit: " + getHyperlinkMarkdown(sourceVersion, commitUrl); -} - -function getPipelineUrl() : string { - let buildNumber = tl.getVariable("Build.BuildNumber"); - let buildId = tl.getVariable("Build.BuildId"); - let projectUrl = GetProjectUrl(); - let pipelineUrl: string; - - if(projectUrl && buildNumber) { - pipelineUrl = projectUrl + "/_build/results?buildId=" + buildId + "&view=results"; - } - - return "- Pipeline run : " + getHyperlinkMarkdown(buildNumber, pipelineUrl); -} - -function getAdditionalReleaseNotesContent() : string { - let additionalNotes = tl.getInput("AdditionalReleaseNotesFile"); - if(additionalNotes && fs.existsSync(additionalNotes)) { - return fs.readFileSync(additionalNotes).toString(); - } - - return null; -} - -function GetRepositoryUrl() : string { - let repositoryUrl = tl.getVariable("Build.Repository.Uri"); - if(repositoryUrl) { - repositoryUrl = repositoryUrl.replace(/https:\/\/.*@/gi, "https://"); //Remove user@ in the repo URL - } - - return repositoryUrl; -} - -function GetProjectUrl() : string { - let collectionUrl = tl.getVariable("System.TeamFoundationCollectionUri"); - let project = tl.getVariable("System.TeamProject"); - - return collectionUrl + project.replace(" ", "%20"); -} - -function getHyperlinkMarkdown(text: string, url: string) : string { - if(url) { - return "[" + text + "](" + url + ")"; - } - else { - return text; - } -} - -function trimOutput(output: string, limit: number, trimCharacters: string) : string { - if(tl.getBoolInput("RemoveHyperlinks")) { - output = output.replace(/\[(.*?)\]\(.*?\)/g, '$1'); - } - - let characterCount = 0; - let trimmedOutput : string[] = []; - let limitWithCharacter = limit - trimCharacters.length; - - for(var line of output.split('\n')) { - var newCharacterCount = characterCount + line.length + 1; //Adding 1 for the newline character we will add at the end - //new line doesn't fit - if(newCharacterCount > limit) { - tl.debug("Cannot fit current line; character count is " + characterCount); - var lineLimit = limitWithCharacter - characterCount; - //Check if we have enough space to put the lines + the trim characters - if(lineLimit >= 0) { - tl.debug("Trimming current line to " + lineLimit + " characters"); - trimmedOutput.push(line.substring(0, lineLimit) + trimCharacters); - } - //otherwise, we add the trim characters to the previous eligible line - else if(trimmedOutput.length > 0) { - var previousLine = trimmedOutput.pop(); - var previousLineLimit = limitWithCharacter - characterCount - previousLine.length; - - tl.debug("Trimming previous line to " + previousLineLimit + " characters"); - - trimmedOutput.push(previousLine.substring(0, previousLineLimit) + trimCharacters); - } - break; - } - - characterCount = newCharacterCount; - - trimmedOutput.push(line); - } - return trimmedOutput.join('\n'); -} - -function addLine(input: string, line: string) : string { - if(line) { - return input + line + '\n'; - } - else { - return input; - } -} - -async function writeOutputToFile(output: string, outputFilePath: string): Promise { - if(!outputFilePath) { - return; - } - - try { - if(fs.existsSync(outputFilePath)) { - const fileStates = fs.statSync(outputFilePath); - var file = fs.createWriteStream(outputFilePath, { flags: "r+", autoClose: true, start: fileStates.size }); - - file.write(output); - } else { - var file = fs.createWriteStream(outputFilePath, { autoClose: true }); - - file.write(output); - } - - return true; - } catch(ex) - { - tl.error(ex); - return false; - } -} - -run(); \ No newline at end of file diff --git a/extensions/azuredevops/vss-extension.json b/extensions/azuredevops/vss-extension.json index 4c445d4..202a4b1 100644 --- a/extensions/azuredevops/vss-extension.json +++ b/extensions/azuredevops/vss-extension.json @@ -1,10 +1,10 @@ { "manifestVersion": 1, - "id": "unoplatform", + "id": "unoplatform-build-tools", "version": "0.0.0", "name": "unoplatform Build Tools", - "description": "A set of build tools developed by unoplatform", - "publisher": "nventivecorp", + "description": "The Canary Updater tool by Uno Platform", + "publisher": "unoplatform", "targets": [ { "id": "Microsoft.VisualStudio.Services" @@ -35,26 +35,6 @@ "properties": { "name": "canaryUpdater" } - }, - { - "id": "unoplatform.releaseNotesCompiler", - "type": "ms.vss-distributed-task.task", - "targets": [ - "ms.vss-distributed-task.tasks" - ], - "properties": { - "name": "releaseNotesCompiler" - } - }, - { - "id": "unoplatform.websiteVersion", - "type": "ms.vss-distributed-task.task", - "targets": [ - "ms.vss-distributed-task.tasks" - ], - "properties": { - "name": "websiteVersion" - } } ], "scopes": [ @@ -64,14 +44,8 @@ { "path": "canaryUpdater" }, - { - "path": "releaseNotesCompiler" - }, - { - "path": "websiteVersion" - }, { "path": "node_modules" } ] -} \ No newline at end of file +} diff --git a/extensions/azuredevops/websiteVersion/Readme.md b/extensions/azuredevops/websiteVersion/Readme.md deleted file mode 100644 index 894866f..0000000 --- a/extensions/azuredevops/websiteVersion/Readme.md +++ /dev/null @@ -1,11 +0,0 @@ -# unoplatform Website versioning - -This task is meant to help with versioning a static website. It was developed as a mean to maintain multiple versions of WASM-based Uno applications. The concept is very simple: -- The contents of the website are stored in an Azure Blob storage -- All the versions of the website can be found under a `Versions` folder -- The list of the available versions can be found by navigating to /Versions -- When navigating to the root of the website, the user is immediately redirected to the latest version, using `http-equiv="refresh"` - -This task is in charge of uploading the files to the storage account and generating the 2 HTML files - the root index and the versions index. - -The website should be somehow versioned for this to work properly, using a file named Version.txt placed at the root of the website. \ No newline at end of file diff --git a/extensions/azuredevops/websiteVersion/azure-arm-rest/AzureServiceClient.ts b/extensions/azuredevops/websiteVersion/azure-arm-rest/AzureServiceClient.ts deleted file mode 100644 index 17c2151..0000000 --- a/extensions/azuredevops/websiteVersion/azure-arm-rest/AzureServiceClient.ts +++ /dev/null @@ -1,275 +0,0 @@ -import tl = require('azure-pipelines-task-lib'); -import util = require("util") -import msRestAzure = require("./azure-arm-common"); -import webClient = require("./webClient"); - -export class ApiResult { - public error; - public result; - public request; - public response; - - constructor(error, result?, request?, response?) { - this.error = error; - this.result = result; - this.request = request; - this.response = response; - } -} - -export class AzureError { - public code; - public message; - public statusCode; - public details; -} - -export interface ApiCallback { - (error: any, result?: any, request?: any, response?: any): void -} - -export function ToError(response: webClient.WebResponse): AzureError { - var error = new AzureError(); - error.statusCode = response.statusCode; - error.message = response.body - if (response.body && response.body.error) { - error.code = response.body.error.code; - error.message = response.body.error.message; - error.details = response.body.error.details; - - console.log("##vso[task.logissue type=error;code="+error.code+";]"); - } - - return error; -} - -export class ServiceClient { - private credentials: msRestAzure.ApplicationTokenCredentials; - protected apiVersion: string; - protected baseUri: string; - protected acceptLanguage: string; - protected longRunningOperationRetryTimeout: number; - protected generateClientRequestId: boolean; - - public subscriptionId: string; - - constructor(credentials: msRestAzure.ApplicationTokenCredentials, subscriptionId: string, timeout?: number) { - this.validateInputs(credentials, subscriptionId); - - this.credentials = credentials; - this.subscriptionId = subscriptionId - this.baseUri = this.credentials.baseUrl; - this.longRunningOperationRetryTimeout = !!timeout ? timeout : 0; // In minutes - } - - protected validateInputs(credentials: msRestAzure.ApplicationTokenCredentials, subscriptionId: string) { - if (!credentials) { - throw new Error(tl.loc("CredentialsCannotBeNull")); - } - if (!subscriptionId) { - throw new Error(tl.loc("SubscriptionIdCannotBeNull")); - } - } - - public getCredentials(): msRestAzure.ApplicationTokenCredentials { - return this.credentials; - } - - public getRequestUri(uriFormat: string, parameters: {}, queryParameters?: string[], apiVersion?: string): string { - return this.getRequestUriForBaseUri(this.baseUri, uriFormat, parameters, queryParameters, apiVersion); - } - - public getRequestUriForBaseUri(baseUri: string, uriFormat: string, parameters: {}, queryParameters?: string[], apiVersion?: string): string { - var requestUri = baseUri + uriFormat; - requestUri = requestUri.replace('{subscriptionId}', encodeURIComponent(this.subscriptionId)); - for (var key in parameters) { - requestUri = requestUri.replace(key, encodeURIComponent(parameters[key])); - } - - // trim all duplicate forward slashes in the url - var regex = /([^:]\/)\/+/gi; - requestUri = requestUri.replace(regex, '$1'); - - // process query paramerters - queryParameters = queryParameters || []; - queryParameters.push('api-version=' + encodeURIComponent(apiVersion || this.apiVersion)); - if (queryParameters.length > 0) { - requestUri += '?' + queryParameters.join('&'); - } - - return requestUri - } - - public setCustomHeaders(options: Object): {} { - var headers = {}; - if (options) { - for (var headerName in options['customHeaders']) { - if (options['customHeaders'].hasOwnProperty(headerName)) { - headers[headerName] = options['customHeaders'][headerName]; - } - } - } - return headers; - } - - public async beginRequest(request: webClient.WebRequest): Promise { - var token = await this.credentials.getToken(); - - request.headers = request.headers || {}; - request.headers["Authorization"] = "Bearer " + token; - if (this.acceptLanguage) { - request.headers['accept-language'] = this.acceptLanguage; - } - request.headers['Content-Type'] = 'application/json; charset=utf-8'; - - var httpResponse = null; - - try - { - httpResponse = await webClient.sendRequest(request); - if (httpResponse.statusCode === 401 && httpResponse.body && httpResponse.body.error && httpResponse.body.error.code === "ExpiredAuthenticationToken") { - // The access token might have expire. Re-issue the request after refreshing the token. - token = await this.credentials.getToken(true); - request.headers["Authorization"] = "Bearer " + token; - httpResponse = await webClient.sendRequest(request); - } - } catch(exception) { - let exceptionString: string = exception.toString(); - if(exceptionString.indexOf("Hostname/IP doesn't match certificates's altnames") != -1 - || exceptionString.indexOf("unable to verify the first certificate") != -1 - || exceptionString.indexOf("unable to get local issuer certificate") != -1) { - tl.warning(tl.loc('ASE_SSLIssueRecommendation')); - } - - throw exception; - } - - return httpResponse; - } - - public async getLongRunningOperationResult(response: webClient.WebResponse, timeoutInMinutes?: number): Promise { - timeoutInMinutes = timeoutInMinutes || this.longRunningOperationRetryTimeout; - var timeout = new Date().getTime() + timeoutInMinutes * 60 * 1000; - var waitIndefinitely = timeoutInMinutes == 0; - var request = new webClient.WebRequest(); - request.method = "GET"; - request.uri = response.headers["azure-asyncoperation"] || response.headers["location"]; - if (!request.uri) { - throw new Error(tl.loc("InvalidResponseLongRunningOperation")); - } - - while (true) { - response = await this.beginRequest(request); - tl.debug(`Response status code : ${response.statusCode}`); - if (response.statusCode === 202 || (response.body && (response.body.status == "Accepted" || response.body.status == "Running" || response.body.status == "InProgress"))) { - // If timeout; throw; - if (!waitIndefinitely && timeout < new Date().getTime()) { - throw new Error(tl.loc("TimeoutWhileWaiting")); - } - - // Retry after given interval. - var sleepDuration = 15; - if (response.headers["retry-after"]) { - sleepDuration = parseInt(response.headers["retry-after"]); - } - await this.sleepFor(sleepDuration); - } else { - break; - } - } - - return response; - } - - public async beginRequestExpBackoff(request: webClient.WebRequest, maxAttempt: number): Promise { - var sleepDuration = 1; - for(var i = 1; true; i++) { - var response : webClient.WebResponse = await this.beginRequest(request); - //not a server error; - if(response.statusCode <500) { - return response; - } - - // response of last attempt - if(i == maxAttempt) { - return response; - } - - // Retry after given interval. - sleepDuration = sleepDuration + i; - if (response.headers["retry-after"]) { - sleepDuration = parseInt(response.headers["retry-after"]); - } - - tl.debug(tl.loc("RetryingRequest", sleepDuration)); - await this.sleepFor(sleepDuration); - } - } - - public async accumulateResultFromPagedResult(nextLinkUrl: string): Promise { - var result = []; - while (nextLinkUrl) { - var nextRequest = new webClient.WebRequest(); - nextRequest.method = 'GET'; - nextRequest.uri = nextLinkUrl; - var response = await this.beginRequest(nextRequest); - if (response.statusCode == 200 && response.body) { - if (response.body.value) { - result = result.concat(response.body.value); - } - - nextLinkUrl = response.body.nextLink; - } - else { - return new ApiResult(ToError(response)); - } - } - - return new ApiResult(null, result); - } - - public isValidResourceGroupName(resourceGroupName: string) { - if (!resourceGroupName === null || resourceGroupName === undefined || typeof resourceGroupName.valueOf() !== 'string') { - throw new Error(tl.loc("ResourceGroupCannotBeNull")); - } - if (resourceGroupName !== null && resourceGroupName !== undefined) { - if (resourceGroupName.length > 90) { - throw new Error(tl.loc("ResourceGroupExceededLength")); - } - if (resourceGroupName.length < 1) { - throw new Error(tl.loc("ResourceGroupDeceededLength")); - } - if (resourceGroupName.match(/^[-\w\._\(\)]+$/) === null) { - throw new Error(tl.loc("ResourceGroupDoesntMatchPattern")); - } - } - } - - public isNameValid(name: string): boolean { - if (name === null || name === undefined || typeof name.valueOf() !== 'string') { - return false; - }else{ - return true; - } - } - - public getFormattedError(error: any): string { - if(error && error.message) { - if(error.statusCode) { - var errorMessage = typeof error.message.valueOf() == 'string' ? error.message - : (error.message.Code || error.message.code) + " - " + (error.message.Message || error.message.message) - error.message = `${errorMessage} (CODE: ${error.statusCode})` - } - - return error.message; - } - - return error; - } - - private sleepFor(sleepDurationInSeconds): Promise { - return new Promise((resolve, reeject) => { - setTimeout(resolve, sleepDurationInSeconds * 1000); - }); - } -} \ No newline at end of file diff --git a/extensions/azuredevops/websiteVersion/azure-arm-rest/azure-arm-common.ts b/extensions/azuredevops/websiteVersion/azure-arm-rest/azure-arm-common.ts deleted file mode 100644 index 6a2a96a..0000000 --- a/extensions/azuredevops/websiteVersion/azure-arm-rest/azure-arm-common.ts +++ /dev/null @@ -1,283 +0,0 @@ -import tl = require('azure-pipelines-task-lib'); -import Q = require('q'); -import querystring = require('querystring'); -import webClient = require('./webClient'); -import AzureModels = require('./azureModels'); -import constants = require('./constants'); -import path = require('path'); -import fs = require('fs'); -var jwt = require('jsonwebtoken'); - -export class ApplicationTokenCredentials { - private clientId: string; - private domain: string; - private authType: string; - private secret?: string; - private certFilePath?: string; - private isADFSEnabled?: boolean; - public baseUrl: string; - public authorityUrl: string; - public activeDirectoryResourceId: string; - public isAzureStackEnvironment: boolean; - public scheme: number; - public msiClientId: string; - private token_deferred: Q.Promise; - - constructor(clientId: string, domain: string, secret: string, baseUrl: string, authorityUrl: string, activeDirectoryResourceId: string, isAzureStackEnvironment: boolean, scheme?: string, msiClientId?: string, authType?: string, certFilePath?: string, isADFSEnabled?: boolean) { - - if (!Boolean(domain) || typeof domain.valueOf() !== 'string') { - throw new Error(tl.loc("DomainCannotBeEmpty")); - } - - if((!scheme ||scheme ==='ServicePrincipal')){ - if (!Boolean(clientId) || typeof clientId.valueOf() !== 'string') { - throw new Error(tl.loc("ClientIdCannotBeEmpty")); - } - - if(!authType || authType == constants.AzureServicePrinicipalAuthentications.servicePrincipalKey) { - if (!Boolean(secret) || typeof secret.valueOf() !== 'string') { - throw new Error(tl.loc("SecretCannotBeEmpty")); - } - } - else { - if (!Boolean(certFilePath) || typeof certFilePath.valueOf() !== 'string') { - throw new Error(tl.loc("InvalidCertFileProvided")); - } - } - - } - - if (!Boolean(baseUrl) || typeof baseUrl.valueOf() !== 'string') { - throw new Error(tl.loc("armUrlCannotBeEmpty")); - } - - if (!Boolean(authorityUrl) || typeof authorityUrl.valueOf() !== 'string') { - throw new Error(tl.loc("authorityUrlCannotBeEmpty")); - } - - if (!Boolean(activeDirectoryResourceId) || typeof activeDirectoryResourceId.valueOf() !== 'string') { - throw new Error(tl.loc("activeDirectoryResourceIdUrlCannotBeEmpty")); - } - - if(!Boolean(isAzureStackEnvironment) || typeof isAzureStackEnvironment.valueOf() != 'boolean') { - isAzureStackEnvironment = false; - } - - this.clientId = clientId; - this.domain = domain; - this.baseUrl = baseUrl; - this.authorityUrl = authorityUrl; - this.activeDirectoryResourceId = activeDirectoryResourceId; - this.isAzureStackEnvironment = isAzureStackEnvironment; - - this.scheme = scheme ? AzureModels.Scheme[scheme] : AzureModels.Scheme['ServicePrincipal'] ; - this.msiClientId = msiClientId ; - if(this.scheme == AzureModels.Scheme['ServicePrincipal']) { - this.authType = authType ? authType : constants.AzureServicePrinicipalAuthentications.servicePrincipalKey; - if(this.authType == constants.AzureServicePrinicipalAuthentications.servicePrincipalKey) { - this.secret = secret; - } - else { - this.certFilePath = certFilePath; - } - } - - this.isADFSEnabled = isADFSEnabled; - - } - - public getToken(force?: boolean): Q.Promise { - if (!this.token_deferred || force) { - if(this.scheme === AzureModels.Scheme.ManagedServiceIdentity) - { - this.token_deferred = this._getMSIAuthorizationToken(0, 0); - } - else - { - this.token_deferred = this._getSPNAuthorizationToken(); - } - } - - return this.token_deferred; - } - - public getDomain(): string { - return this.domain; - } - - public getClientId(): string { - return this.clientId; - } - - private _getMSIAuthorizationToken(retyCount: number ,timeToWait: number): Q.Promise { - var deferred = Q.defer(); - let webRequest = new webClient.WebRequest(); - webRequest.method = "GET"; - let apiVersion = "2018-02-01"; - const retryLimit = 5; - let msiClientId = this.msiClientId ? "&client_id=" + this.msiClientId : ""; - webRequest.uri = "http://169.254.169.254/metadata/identity/oauth2/token?api-version=" + apiVersion + "&resource="+ this.baseUrl + msiClientId; - webRequest.headers = { - "Metadata": true - }; - - webClient.sendRequest(webRequest).then( - (response: webClient.WebResponse) => { - if (response.statusCode == 200) - { - deferred.resolve(response.body.access_token); - } - else if (response.statusCode == 429 || response.statusCode == 500) - { - if(retyCount < retryLimit) - { - let waitedTime = 2000 + timeToWait * 2; - retyCount +=1; - setTimeout(() => { - deferred.resolve(this._getMSIAuthorizationToken(retyCount, waitedTime)); - }, waitedTime); - } - else - { - deferred.reject(tl.loc('CouldNotFetchAccessTokenforMSIStatusCode', response.statusCode, response.statusMessage)); - } - - } - else - { - deferred.reject(tl.loc('CouldNotFetchAccessTokenforMSIDueToMSINotConfiguredProperlyStatusCode', response.statusCode, response.statusMessage)); - } - }, - (error) => { - deferred.reject(error) - } - ); - - return deferred.promise; - } - - private _getSPNAuthorizationToken(): Q.Promise { - if(this.authType == constants.AzureServicePrinicipalAuthentications.servicePrincipalKey) { - return this._getSPNAuthorizationTokenFromKey(); - } - - return this._getSPNAuthorizationTokenFromCertificate() - } - - private _getSPNAuthorizationTokenFromCertificate(): Q.Promise { - var deferred = Q.defer(); - let webRequest = new webClient.WebRequest(); - webRequest.method = "POST"; - webRequest.uri = this.authorityUrl + (this.isADFSEnabled ? "" : this.domain) + "/oauth2/token/"; - webRequest.body = querystring.stringify({ - resource: this.activeDirectoryResourceId, - client_id: this.clientId, - grant_type: "client_credentials", - client_assertion: this._getSPNCertificateAuthorizationToken(), - client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer" - }); - - webClient.sendRequest(webRequest).then( - (response: webClient.WebResponse) => { - if (response.statusCode == 200) { - deferred.resolve(response.body.access_token); - } - else { - deferred.reject(tl.loc('CouldNotFetchAccessTokenforAzureStatusCode', response.statusCode, response.statusMessage)); - } - }, - (error) => { - deferred.reject(error) - } - ); - return deferred.promise; - } - - - private _getSPNAuthorizationTokenFromKey(): Q.Promise { - var deferred = Q.defer(); - let webRequest = new webClient.WebRequest(); - webRequest.method = "POST"; - webRequest.uri = this.authorityUrl + this.domain + "/oauth2/token/"; - webRequest.body = querystring.stringify({ - resource: this.activeDirectoryResourceId, - client_id: this.clientId, - grant_type: "client_credentials", - client_secret: this.secret - }); - webRequest.headers = { - "Content-Type": "application/x-www-form-urlencoded; charset=utf-8" - }; - - webClient.sendRequest(webRequest).then( - (response: webClient.WebResponse) => { - if (response.statusCode == 200) - { - deferred.resolve(response.body.access_token); - } - else - { - deferred.reject(tl.loc('CouldNotFetchAccessTokenforAzureStatusCode', response.statusCode, response.statusMessage)); - } - }, - (error) => { - deferred.reject(error) - } - ); - - return deferred.promise; - } - - private _getSPNCertificateAuthorizationToken(): string { - var openSSLPath = tl.osType().match(/^Win/) ? tl.which(path.join(__dirname, 'openssl', 'openssl')) : tl.which('openssl'); - var openSSLArgsArray= [ - "x509", - "-noout", - "-in" , - this.certFilePath, - "-fingerprint" - ]; - - var pemExecutionResult = tl.execSync(openSSLPath, openSSLArgsArray); - var additionalHeaders = { - "alg": "RS256", - "typ": "JWT", - }; - - if(pemExecutionResult.code == 0) { - tl.debug("FINGERPRINT CREATION SUCCESSFUL"); - let shaFingerprint = pemExecutionResult.stdout; - let shaFingerPrintHashCode = shaFingerprint.split("=")[1].replace(new RegExp(":", 'g'), ""); - let fingerPrintHashBase64: string = Buffer.from( - shaFingerPrintHashCode.match(/\w{2}/g).map(function(a) { - return String.fromCharCode(parseInt(a, 16)); - } ).join(""), - 'binary' - ).toString('base64'); - additionalHeaders["x5t"] = fingerPrintHashBase64; - } - else { - console.log(pemExecutionResult); - throw new Error(pemExecutionResult.stderr); - } - - return getJWT(this.authorityUrl, this.clientId, this.domain, this.certFilePath, additionalHeaders, this.isADFSEnabled); - } - -} - -function getJWT(url: string, clientId: string, tenantId: string, pemFilePath: string, additionalHeaders, isADFSEnabled: boolean) { - - var pemFileContent = fs.readFileSync(pemFilePath); - var jwtObject = { - "aud": (`${url}/${!isADFSEnabled ? tenantId : ""}/oauth2/token`).replace(/([^:]\/)\/+/g, "$1"), - "iss": clientId, - "sub": clientId, - "jti": "" + Math.random(), - "nbf": (Math.floor(Date.now()/1000)-1000), - "exp": (Math.floor(Date.now()/1000)+8640000) - }; - - var token = jwt.sign(jwtObject, pemFileContent,{ algorithm: 'RS256', header :additionalHeaders }); - return token; -} \ No newline at end of file diff --git a/extensions/azuredevops/websiteVersion/azure-arm-rest/azure-arm-endpoint.ts b/extensions/azuredevops/websiteVersion/azure-arm-rest/azure-arm-endpoint.ts deleted file mode 100644 index 5c177f6..0000000 --- a/extensions/azuredevops/websiteVersion/azure-arm-rest/azure-arm-endpoint.ts +++ /dev/null @@ -1,152 +0,0 @@ -import tl = require('azure-pipelines-task-lib'); -import Q = require('q'); -import webClient = require("./webClient"); -import { AzureEndpoint } from "./azureModels"; -import { ApplicationTokenCredentials } from './azure-arm-common'; -import constants = require('./constants'); -import fs = require('fs'); -import path = require('path'); -const certFilePath: string = path.join(tl.getVariable('Agent.TempDirectory'), 'spnCert.pem'); - -export class AzureRMEndpoint { - public endpoint: AzureEndpoint; - private _connectedServiceName: string; - private applicationTokenCredentials: ApplicationTokenCredentials; - - // Add an entry here and separate function for each new environment - private _environments = { - 'AzureStack': 'azurestack' - } - - constructor(connectedServiceName: string) { - this._connectedServiceName = connectedServiceName; - this.endpoint = null; - } - - public async getEndpoint(): Promise { - if(!!this.endpoint) { - return this.endpoint; - } - else { - this.endpoint = { - subscriptionID: tl.getEndpointDataParameter(this._connectedServiceName, 'subscriptionid', true), - subscriptionName: tl.getEndpointDataParameter(this._connectedServiceName, 'subscriptionname', true), - servicePrincipalClientID: tl.getEndpointAuthorizationParameter(this._connectedServiceName, 'serviceprincipalid', true), - environmentAuthorityUrl: tl.getEndpointDataParameter(this._connectedServiceName, 'environmentAuthorityUrl', true), - tenantID: tl.getEndpointAuthorizationParameter(this._connectedServiceName, 'tenantid', false), - url: tl.getEndpointUrl(this._connectedServiceName, true), - environment: tl.getEndpointDataParameter(this._connectedServiceName, 'environment', true), - scheme: tl.getEndpointAuthorizationScheme(this._connectedServiceName, true), - msiClientId: tl.getEndpointDataParameter(this._connectedServiceName, 'msiclientId', true), - activeDirectoryResourceID: tl.getEndpointDataParameter(this._connectedServiceName, 'activeDirectoryServiceEndpointResourceId', true), - azureKeyVaultServiceEndpointResourceId: tl.getEndpointDataParameter(this._connectedServiceName, 'AzureKeyVaultServiceEndpointResourceId', true), - azureKeyVaultDnsSuffix: tl.getEndpointDataParameter(this._connectedServiceName, 'AzureKeyVaultDnsSuffix', true), - } as AzureEndpoint; - - this.endpoint.authenticationType = tl.getEndpointAuthorizationParameter(this._connectedServiceName, 'authenticationType', true); - - // if scheme is null, we assume the scheme to be ServicePrincipal - let isServicePrincipalAuthenticationScheme = !this.endpoint.scheme || this.endpoint.scheme.toLowerCase() == constants.AzureRmEndpointAuthenticationScheme.ServicePrincipal; - if (isServicePrincipalAuthenticationScheme) { - if(this.endpoint.authenticationType && this.endpoint.authenticationType == constants.AzureServicePrinicipalAuthentications.servicePrincipalCertificate) { - tl.debug('certificate spn endpoint'); - this.endpoint.servicePrincipalCertificate = tl.getEndpointAuthorizationParameter(this._connectedServiceName, 'servicePrincipalCertificate', false); - this.endpoint.servicePrincipalCertificatePath = certFilePath; - fs.writeFileSync(this.endpoint.servicePrincipalCertificatePath, this.endpoint.servicePrincipalCertificate); - } - else { - tl.debug('credentials spn endpoint'); - this.endpoint.servicePrincipalKey = tl.getEndpointAuthorizationParameter(this._connectedServiceName, 'serviceprincipalkey', false); - } - } - - var isADFSEnabled = tl.getEndpointDataParameter(this._connectedServiceName, 'EnableAdfsAuthentication', true); - this.endpoint.isADFSEnabled = isADFSEnabled && (isADFSEnabled.toLowerCase() == "true"); - - if(!!this.endpoint.environment && this.endpoint.environment.toLowerCase() == this._environments.AzureStack) { - if(!this.endpoint.environmentAuthorityUrl || !this.endpoint.activeDirectoryResourceID) { - this.endpoint = await this._updateAzureStackData(this.endpoint); - } - } - else { - this.endpoint.environmentAuthorityUrl = (!!this.endpoint.environmentAuthorityUrl) ? this.endpoint.environmentAuthorityUrl : "https://login.windows.net/"; - this.endpoint.activeDirectoryResourceID = this.endpoint.url; - } - - this.endpoint.applicationTokenCredentials = new ApplicationTokenCredentials(this.endpoint.servicePrincipalClientID, this.endpoint.tenantID, this.endpoint.servicePrincipalKey, - this.endpoint.url, this.endpoint.environmentAuthorityUrl, this.endpoint.activeDirectoryResourceID, !!this.endpoint.environment && this.endpoint.environment.toLowerCase() == constants.AzureEnvironments.AzureStack, this.endpoint.scheme, this.endpoint.msiClientId, this.endpoint.authenticationType, this.endpoint.servicePrincipalCertificatePath, this.endpoint.isADFSEnabled); - } - - tl.debug(JSON.stringify(this.endpoint)); - return this.endpoint; - } - - private async _updateAzureStackData(endpoint: AzureEndpoint): Promise { - let dataDeferred = Q.defer(); - let webRequest = new webClient.WebRequest(); - webRequest.uri = `${endpoint.url}metadata/endpoints?api-version=2015-01-01`; - webRequest.method = 'GET'; - webRequest.headers = { - 'Content-Type': 'application/json' - } - - let azureStackResult; - try { - let response: webClient.WebResponse = await webClient.sendRequest(webRequest); - if(response.statusCode != 200) { - tl.debug("Action: _updateAzureStackData, Response: " + JSON.stringify(response)); - throw new Error(response.statusCode + ' ' + response.statusMessage) - } - - azureStackResult = response.body; - } - catch(error) { - throw new Error(tl.loc("FailedToFetchAzureStackDependencyData", error.toString())); - } - - endpoint.graphEndpoint = azureStackResult.graphEndpoint; - endpoint.galleryUrl = azureStackResult.galleryUrl; - endpoint.portalEndpoint = azureStackResult.portalEndpoint; - var authenticationData = azureStackResult.authentication; - if(!!authenticationData) { - var loginEndpoint: string = authenticationData.loginEndpoint; - if(!!loginEndpoint) { - loginEndpoint += (loginEndpoint[loginEndpoint.length - 1] == "/") ? "" : "/"; - endpoint.activeDirectoryAuthority = loginEndpoint; - endpoint.environmentAuthorityUrl = loginEndpoint; - endpoint.isADFSEnabled = loginEndpoint.endsWith('/adfs/'); - } - else { - // change to login endpoint - throw new Error(tl.loc('UnableToFetchAuthorityURL')); - } - - var audiences = authenticationData.audiences; - if(audiences && audiences.length > 0) { - endpoint.activeDirectoryResourceID = audiences[0]; - } - - try { - var endpointUrl = endpoint.url; - endpointUrl += (endpointUrl[endpointUrl.length-1] == "/") ? "" : "/"; - var index = endpointUrl.indexOf('.'); - var domain = endpointUrl.substring(index+1); - domain = (domain.lastIndexOf("/") == domain.length-1) ? domain.substring(0, domain.length-1): domain; - endpoint.azureKeyVaultDnsSuffix = ("vault." + domain).toLowerCase(); - endpoint.azureKeyVaultServiceEndpointResourceId = ("https://vault." + domain).toLowerCase(); - } - catch(error) { - throw new Error(tl.loc("SpecifiedAzureRmEndpointIsInvalid", endpointUrl)); - } - } - - return endpoint; - } -} - -export function dispose() { - if(tl.exist(certFilePath)) { - tl.rmRF(certFilePath); - tl.debug('Removed cert endpoint file'); - } -} \ No newline at end of file diff --git a/extensions/azuredevops/websiteVersion/azure-arm-rest/azure-arm-storage.ts b/extensions/azuredevops/websiteVersion/azure-arm-rest/azure-arm-storage.ts deleted file mode 100644 index d393b9c..0000000 --- a/extensions/azuredevops/websiteVersion/azure-arm-rest/azure-arm-storage.ts +++ /dev/null @@ -1,201 +0,0 @@ -import msRestAzure = require("./azure-arm-common"); -import azureServiceClient = require("./AzureServiceClient"); -import Model = require("./azureModels"); -import webClient = require("./webClient"); -import tl = require('azure-pipelines-task-lib'); -import Q = require('q'); -import util = require("util"); - -export class StorageManagementClient extends azureServiceClient.ServiceClient { - public storageAccounts: StorageAccounts; - - constructor(credentials: msRestAzure.ApplicationTokenCredentials, subscriptionId: string, baseUri?: any, options?: any) { - super(credentials, subscriptionId); - - this.acceptLanguage = 'en-US'; - this.generateClientRequestId = true; - this.apiVersion = (credentials.isAzureStackEnvironment) ? '2015-06-15' : '2017-06-01'; - - if (!options) - options = {}; - - if (baseUri) { - this.baseUri = baseUri; - } - - if (options.acceptLanguage) { - this.acceptLanguage = options.acceptLanguage; - } - if (options.longRunningOperationRetryTimeout) { - this.longRunningOperationRetryTimeout = options.longRunningOperationRetryTimeout; - } - if (options.generateClientRequestId) { - this.generateClientRequestId = options.generateClientRequestId; - } - this.storageAccounts = new StorageAccounts(this); - } -} - -export class StorageAccounts { - private client: StorageManagementClient; - - constructor(client) { - this.client = client; - } - - public async list(options): Promise { - var httpRequest = new webClient.WebRequest(); - httpRequest.method = 'GET'; - httpRequest.headers = this.client.setCustomHeaders(options); - // Getting all azure rm storage accounts (along with resource group names) for the given subscription. - httpRequest.uri = this.client.getRequestUri('//subscriptions/{subscriptionId}/providers/Microsoft.Storage/storageAccounts', {}); - - var deferred = Q.defer(); - var result = []; - this.client.beginRequest(httpRequest).then(async (response) => { - if (response.statusCode == 200) { - if (response.body.value) { - let storageAccounts: Model.StorageAccount[] = response.body.value; - result = result.concat(storageAccounts); - } - - if (response.body.nextLink) { - var nextResult = await this.client.accumulateResultFromPagedResult(response.body.nextLink); - if (nextResult.error) { - deferred.reject(nextResult.error); - } - - let storageAccounts: Model.StorageAccount[] = nextResult.result; - result = result.concat(storageAccounts); - } - - deferred.resolve(result); - } - else { - deferred.reject(azureServiceClient.ToError(response)); - } - }).catch(function (error) { - deferred.reject(error); - }); - - return deferred.promise; - } - - public async listClassicAndRMAccounts(options): Promise { - var httpRequest = new webClient.WebRequest(); - httpRequest.method = 'GET'; - httpRequest.headers = this.client.setCustomHeaders(options); - - // Getting all storage accounts (azure rm and classic, along with resource group names) for the given subscription. - httpRequest.uri = "https://management.azure.com/resources?api-version=2014-04-01-preview&%24filter=(subscriptionId%20eq%20'{subscriptionId}')%20and%20(resourceType%20eq%20'microsoft.storage%2Fstorageaccounts'%20or%20resourceType%20eq%20'microsoft.classicstorage%2Fstorageaccounts')"; - httpRequest.uri = httpRequest.uri.replace('{subscriptionId}', this.client.subscriptionId); - - var deferred = Q.defer(); - var result = []; - this.client.beginRequest(httpRequest).then(async (response) => { - if (response.statusCode == 200) { - if (response.body.value) { - let storageAccounts: Model.StorageAccount[] = response.body.value; - result = result.concat(storageAccounts); - } - - if (response.body.nextLink) { - var nextResult = await this.client.accumulateResultFromPagedResult(response.body.nextLink); - if (nextResult.error) { - deferred.reject(nextResult.error); - } - - let storageAccounts: Model.StorageAccount[] = nextResult.result; - result = result.concat(storageAccounts); - } - - deferred.resolve(result); - } - else { - deferred.reject(azureServiceClient.ToError(response)); - } - }).catch(function(error) { - deferred.reject(error); - }); - - return deferred.promise; - } - - public async listKeys(resourceGroupName: string, accountName: string, options, storageAccountType?: string): Promise { - if (resourceGroupName === null || resourceGroupName === undefined || typeof resourceGroupName.valueOf() !== 'string') { - throw new Error(tl.loc("ResourceGroupCannotBeNull")); - } - - if (accountName === null || accountName === undefined || typeof accountName.valueOf() !== 'string') { - throw new Error(tl.loc("StorageAccountCannotBeNull")); - } - - var apiVersion = "2017-06-01"; - var resourceProvider = "Microsoft.Storage"; - if (!!storageAccountType && storageAccountType.toLowerCase().indexOf("classicstorage") > 0) { - resourceProvider = "Microsoft.ClassicStorage"; - apiVersion = "2015-12-01"; - } - - var httpRequest = new webClient.WebRequest(); - httpRequest.method = 'POST'; - httpRequest.headers = this.client.setCustomHeaders(options); - httpRequest.uri = this.client.getRequestUri( - '//subscriptions/{subscriptionId}/resourcegroups/{resourceGroupName}/providers/{provider}/storageAccounts/{storageAccountName}/listKeys', - { - '{resourceGroupName}': resourceGroupName, - '{storageAccountName}': accountName, - '{provider}': resourceProvider - }, - [], - apiVersion - ); - - var deferred = Q.defer(); - var accessKeys: string[] = []; - this.client.beginRequest(httpRequest).then((response) => { - if (response.statusCode == 200) { - if (resourceProvider === "Microsoft.ClassicStorage") { - accessKeys[0] = response.body.primaryKey; - accessKeys[1] = response.body.secondaryKey; - } else if (response.body.keys) { - let keys = response.body.keys; - for (let i = 0; i < keys.length; i++) { - accessKeys[i] = keys[i]["value"]; - } - } - - deferred.resolve(accessKeys); - } else { - deferred.reject(azureServiceClient.ToError(response)); - } - }).catch(function (error) { - deferred.reject(error); - }); - - return deferred.promise; - } - - public async get(storageAccountName: string): Promise { - let storageAccounts = await this.list(null); - let index = storageAccounts.findIndex(account => account.name.toLowerCase() === storageAccountName.toLowerCase()); - - if (index < 0) { - throw new Error(tl.loc("StorageAccountDoesNotExist", storageAccountName)); - } - - return storageAccounts[index]; - } - - public static getResourceGroupNameFromUri(resourceUri: string): string { - if (this.isNonEmptyInternal(resourceUri)) { - resourceUri = resourceUri.toLowerCase(); - return resourceUri.substring(resourceUri.indexOf("resourcegroups/") + "resourcegroups/".length, resourceUri.indexOf("/providers")); - } - return ""; - } - - private static isNonEmptyInternal(str: string): boolean { - return (!!str && !!str.trim()); - } -} \ No newline at end of file diff --git a/extensions/azuredevops/websiteVersion/azure-arm-rest/azureModels.ts b/extensions/azuredevops/websiteVersion/azure-arm-rest/azureModels.ts deleted file mode 100644 index fc05049..0000000 --- a/extensions/azuredevops/websiteVersion/azure-arm-rest/azureModels.ts +++ /dev/null @@ -1,355 +0,0 @@ -import { ApplicationTokenCredentials } from "./azure-arm-common"; - -export interface AzureBaseObject { - name?: string; - id: string; -} - -export interface LoadBalancerProperties { - inboundNatRules: InboundNatRule[]; - backendAddressPools: BackendAddressPool[]; - frontendIPConfigurations: IPConfiguration[] -} - -export interface InboundNatRuleProperties { - frontendPort: number; - backendPort: number; - backendIPConfiguration?: IPConfiguration; - frontendIPConfiguration: IPConfiguration; - protocol: string; - idleTimeoutInMinutes: number; - enableFloatingIP: boolean; -} - -export interface BackendAddressPoolProperties { - backendIPConfigurations: IPConfiguration[]; -} - -export interface NetworkInterfaceProperties { - ipConfigurations: IPConfiguration[] -} - -export interface IPConfigurationProperties { - publicIPAddress: PublicIPAddress; - loadBalancerInboundNatRules: InboundNatRule[]; -} - -export interface PublicIPAddressProperties { - ipAddress: string; - dnsSettings: DnsSettings; -} - -export interface VMProperties { - networkProfile: NetworkProfile; - instanceView: InstanceView; - storageProfile: StorageProfile -} - -export interface VirtualMachineProfile { - networkProfile?: NetworkProfile; - instanceView?: InstanceView; - storageProfile?: StorageProfile; - extensionProfile?:ExtensionProfile; -} - -export interface VMSSProperties { - virtualMachineProfile: VirtualMachineProfile; - provisioningState?: string; -} - -export interface VMExtensionProperties { - provisioningState?: string; - type: string; - publisher: string; - typeHandlerVersion: string; - autoUpgradeMinorVersion?: boolean; - settings?: Object; - protectedSettings?: Object; -} - -export interface StorageProfile{ - imageReference?: Map; - osDisk: OSDisk; - dataDisks?: Map[]; -} - -export interface OSDisk{ - osType: string; - name: string; - createOption: string; - caching: string; - image: ImageUrl; -} - -export interface ImageUrl{ - uri: string; -} - -export interface DnsSettings { - fqdn: string; -} - -export interface NetworkProfile { - networkInterfaces: NetworkInterface[] -} - -export interface ExtensionProfile { - extensions: VMExtension[]; -} - -export interface InstanceView { - statuses: Status[]; -} - -export interface Status{ - code: string; -} - -export interface LoadBalancer extends AzureBaseObject { - location: string; - properties: LoadBalancerProperties -} - -export interface VM extends AzureBaseObject { - properties: VMProperties, - location: string, - tags?: string ; -} - -export interface VMSS extends AzureBaseObject { - properties?: VMSSProperties, - location?: string, - tags?: string ; -} - -export interface VMExtension { - name?: string; - id?: string; - properties: VMExtensionProperties, - sku?: VMSku; -} - -export interface VMSku { - name?: string, - tier?: string; - capacity?: string; -} - -export interface NetworkInterface extends AzureBaseObject { - properties: NetworkInterfaceProperties -} - -export interface InboundNatRule extends AzureBaseObject { - properties: InboundNatRuleProperties -} - -export interface IPConfiguration extends AzureBaseObject { - properties?: IPConfigurationProperties; -} - -export interface BackendAddressPool extends AzureBaseObject { - properties: BackendAddressPoolProperties -} - -export interface PublicIPAddress extends AzureBaseObject { - properties: PublicIPAddressProperties; -} - -export interface VMExtensionMetadata { - type: string; - publisher: string; - typeHandlerVersion: string; -} - -export enum ComputeResourceType { - VirtualMachine, - VirtualMachineScaleSet -} - -export enum Scheme { - ManagedServiceIdentity, - SPN -} - -export interface StorageAccountSku { - name: string; - tier?: string; -} - -export interface StorageAccountEndpoints { - blob?: string; - table?: string; - file?: string; - queue?: string; -} - -export interface StorageAccountProperties { - creationTime?: string; - primaryLocation?: string; - primaryEndpoints?: StorageAccountEndpoints; - provisioningState?: string; - secondaryLocation?: string; - secondaryndpoints?: StorageAccountEndpoints; - statusOfPrimary?: string; - statusOfSecondary?: string; - supportsHttpsTrafficOnly?: boolean; -} - -export interface StorageAccount extends AzureBaseObject { - type: string; - location?: string; - sku?: StorageAccountSku; - kind?: string; - tags?: Map; - properties?: StorageAccountProperties; -} - -export interface AzureEndpoint { - subscriptionID: string; - subscriptionName: string; - servicePrincipalClientID?: string; - authenticationType?: string; - servicePrincipalKey?: string; - servicePrincipalCertificate?: string; - servicePrincipalCertificatePath?: string - tenantID: string; - environmentAuthorityUrl: string; - url: string; - environment: string; - activeDirectoryResourceID: string; - activeDirectoryAuthority?: string; - graphEndpoint?: string; - galleryUrl?: string; - portalEndpoint?: string; - azureKeyVaultDnsSuffix?: string; - azureKeyVaultServiceEndpointResourceId?: string; - msiClientId?: string; - scheme?: string; - applicationTokenCredentials: ApplicationTokenCredentials; - isADFSEnabled?: boolean; -} - -export interface AzureAppServiceConfigurationDetails { - id: string; - name: string; - type: string; - kind?: string; - location: string; - tags: string; - properties?: {[key: string]: any}; -} - -export interface WebJob { - name: string; - status: string; - runCommand: string; - log_url: string; - url: string; - type: string; -} - -export interface SiteExtension { - id: string; - title: string; - description: string; - extension_url: string; - local_path: string; - version: string; - project_url: string; - authors: Array; - provisioningState: string; - local_is_latest_version: boolean; -} - -export interface WebTest { - id?: string; - name: string; - type: string; - location: string; - tags: {[key: string]: string}, - kind?: string, - etag?: string; - properties?: {[key: string]: any}; -} - - -export interface ApplicationInsights { - id?: string; - name: string; - type: string; - location: string; - tags: {[key: string]: string}, - kind?: string, - etag?: string; - properties?: {[key: string]: any}; -} - -export interface AKSClusterProperties { - provisioningState: string; - kubernetesVersion: string; -} - -export interface AKSCluster extends AzureBaseObject { - properties: AKSClusterProperties -} - -export interface AKSClusterAccessProfileProperties { - kubeConfig: string; -} - -export interface AKSClusterAccessProfile extends AzureBaseObject { - properties: AKSClusterAccessProfileProperties -} - -export interface IThresholdRuleConditionDataSource { - "odata.type": string; - resourceUri: string; - metricName: string; -} - -export interface IThresholdRuleCondition { - "odata.type": string; // "Microsoft.Azure.Management.Insights.Models.ThresholdRuleCondition" - dataSource: IThresholdRuleConditionDataSource; - threshold: string; - operator: string; - windowSize: string; -} - -export interface IAzureMetricAlertRequestBodyProperties { - name: string; - description?: string; - isEnabled: boolean; - condition: IThresholdRuleCondition; - actions: IRuleEmailAction[]; -} - -export interface IRuleEmailAction { - "odata.type": string; //"Microsoft.Azure.Management.Insights.Models.RuleEmailAction", - sendToServiceOwners: boolean; - customEmails: string[] -} - -export interface IAzureMetricAlertRequestBody { - location: string; - tags: { [key: string] : string }; - properties: IAzureMetricAlertRequestBodyProperties; -} - -export interface IMetric { - value: string; - displayValue: string; - unit: string; -} - -export interface IAzureMetricAlertRule { - alertName: string; - metric: IMetric; - thresholdCondition: string; - thresholdValue: string; - timePeriod: string; -} - -export interface IAzureMetricAlertRulesList { - resourceId: string; - rules: IAzureMetricAlertRule[]; -} \ No newline at end of file diff --git a/extensions/azuredevops/websiteVersion/azure-arm-rest/constants.ts b/extensions/azuredevops/websiteVersion/azure-arm-rest/constants.ts deleted file mode 100644 index dac071d..0000000 --- a/extensions/azuredevops/websiteVersion/azure-arm-rest/constants.ts +++ /dev/null @@ -1,29 +0,0 @@ -export const AzureEnvironments = { - AzureStack: 'azurestack' -}; - -export const APPLICATION_INSIGHTS_EXTENSION_NAME: string = "Microsoft.ApplicationInsights.AzureWebSites"; - -export const productionSlot: string = "production"; - -export const mysqlApiVersion: string = '2017-12-01'; - -export const APIVersions = { - azure_arm_appinsights: '2015-05-01', - azure_arm_metric_alerts: '2016-03-01' -} - -export const KUDU_DEPLOYMENT_CONSTANTS = { - SUCCESS: 4, - FAILED: 3 -} - -export const AzureServicePrinicipalAuthentications = { - "servicePrincipalKey": "spnKey", - "servicePrincipalCertificate": "spnCertificate" -} - -export const AzureRmEndpointAuthenticationScheme = { - "ServicePrincipal": "serviceprincipal", - "ManagedServiceIdentity": "managedserviceidentity" -} \ No newline at end of file diff --git a/extensions/azuredevops/websiteVersion/azure-arm-rest/webClient.ts b/extensions/azuredevops/websiteVersion/azure-arm-rest/webClient.ts deleted file mode 100644 index a7a20dc..0000000 --- a/extensions/azuredevops/websiteVersion/azure-arm-rest/webClient.ts +++ /dev/null @@ -1,117 +0,0 @@ -import tl = require('azure-pipelines-task-lib'); -import util = require("util"); -import fs = require('fs'); -import httpClient = require("typed-rest-client/HttpClient"); -import httpInterfaces = require("typed-rest-client/Interfaces"); - -let proxyUrl: string = tl.getVariable("agent.proxyurl"); -var requestOptions: httpInterfaces.IRequestOptions = proxyUrl ? { - proxy: { - proxyUrl: proxyUrl, - proxyUsername: tl.getVariable("agent.proxyusername"), - proxyPassword: tl.getVariable("agent.proxypassword"), - proxyBypassHosts: tl.getVariable("agent.proxybypasslist") ? JSON.parse(tl.getVariable("agent.proxybypasslist")) : null - } -} : {}; - -let ignoreSslErrors: string = tl.getVariable("VSTS_ARM_REST_IGNORE_SSL_ERRORS"); -requestOptions.ignoreSslError = ignoreSslErrors && ignoreSslErrors.toLowerCase() == "true"; - -var httpCallbackClient = new httpClient.HttpClient(tl.getVariable("AZURE_HTTP_USER_AGENT"), null, requestOptions); - -export class WebRequest { - public method: string; - public uri: string; - // body can be string or ReadableStream - public body: string | NodeJS.ReadableStream; - public headers: any; -} - -export class WebResponse { - public statusCode: number; - public statusMessage: string; - public headers: any; - public body: any; -} - -export class WebRequestOptions { - public retriableErrorCodes: string[]; - public retryCount: number; - public retryIntervalInSeconds: number; - public retriableStatusCodes: number[]; - public retryRequestTimedout: boolean; -} - -export async function sendRequest(request: WebRequest, options?: WebRequestOptions): Promise { - let i = 0; - let retryCount = options && options.retryCount ? options.retryCount : 5; - let retryIntervalInSeconds = options && options.retryIntervalInSeconds ? options.retryIntervalInSeconds : 2; - let retriableErrorCodes = options && options.retriableErrorCodes ? options.retriableErrorCodes : ["ETIMEDOUT", "ECONNRESET", "ENOTFOUND", "ESOCKETTIMEDOUT", "ECONNREFUSED", "EHOSTUNREACH", "EPIPE", "EA_AGAIN"]; - let retriableStatusCodes = options && options.retriableStatusCodes ? options.retriableStatusCodes : [408, 409, 500, 502, 503, 504]; - let timeToWait: number = retryIntervalInSeconds; - while (true) { - try { - if (request.body && typeof(request.body) !== 'string' && !request.body["readable"]) { - request.body = fs.createReadStream(request.body["path"]); - } - - let response: WebResponse = await sendRequestInternal(request); - if (retriableStatusCodes.indexOf(response.statusCode) != -1 && ++i < retryCount) { - tl.debug(util.format("Encountered a retriable status code: %s. Message: '%s'.", response.statusCode, response.statusMessage)); - await sleepFor(timeToWait); - timeToWait = timeToWait * retryIntervalInSeconds + retryIntervalInSeconds; - continue; - } - - return response; - } - catch (error) { - if (retriableErrorCodes.indexOf(error.code) != -1 && ++i < retryCount) { - tl.debug(util.format("Encountered a retriable error:%s. Message: %s.", error.code, error.message)); - await sleepFor(timeToWait); - timeToWait = timeToWait * retryIntervalInSeconds + retryIntervalInSeconds; - } - else { - if (error.code) { - console.log("##vso[task.logissue type=error;code=" + error.code + ";]"); - } - - throw error; - } - } - } -} - -export function sleepFor(sleepDurationInSeconds): Promise { - return new Promise((resolve, reject) => { - setTimeout(resolve, sleepDurationInSeconds * 1000); - }); -} - -async function sendRequestInternal(request: WebRequest): Promise { - tl.debug(util.format("[%s]%s", request.method, request.uri)); - var response: httpClient.HttpClientResponse = await httpCallbackClient.request(request.method, request.uri, request.body, request.headers); - return await toWebResponse(response); -} - -async function toWebResponse(response: httpClient.HttpClientResponse): Promise { - var res = new WebResponse(); - if (response) { - res.statusCode = response.message.statusCode; - res.statusMessage = response.message.statusMessage; - res.headers = response.message.headers; - var body = await response.readBody(); - if (body) { - try { - res.body = JSON.parse(body); - } - catch (error) { - tl.debug("Could not parse response: " + JSON.stringify(error)); - tl.debug("Response: " + JSON.stringify(res.body)); - res.body = body; - } - } - } - - return res; -} \ No newline at end of file diff --git a/extensions/azuredevops/websiteVersion/icon.png b/extensions/azuredevops/websiteVersion/icon.png deleted file mode 100644 index 1a199b961ba0e18eaebb962cb88f921118642bf3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20423 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 diff --git a/extensions/azuredevops/websiteVersion/package.json b/extensions/azuredevops/websiteVersion/package.json deleted file mode 100644 index b06eb54..0000000 --- a/extensions/azuredevops/websiteVersion/package.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "name": "websiteversioning", - "version": "1.0.0", - "description": "An Azure DevOps task allowing the versioning of websites. Compatible with Azure Storage with Static Website.", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "repository": { - "type": "git", - "url": "https://dev.azure.com/unoplatform/DevOps/_git/Build.Tasks" - }, - "author": "unoplatform", - "license": "ISC", - "keywords": [ - "Azure Pipelines", - "Azure DevOps", - "Task" - ], - "dependencies": { - "azure-pipelines-task-lib": "^2.9.3", - "azure-storage": "^2.10.3", - "jsonwebtoken": "^8.5.1", - "typed-rest-client": "^1.7.3" - }, - "devDependencies": { - "@types/node": "^12.12.14", - "@types/q": "^1.5.2" - } -} diff --git a/extensions/azuredevops/websiteVersion/task.json b/extensions/azuredevops/websiteVersion/task.json deleted file mode 100644 index b8684db..0000000 --- a/extensions/azuredevops/websiteVersion/task.json +++ /dev/null @@ -1,80 +0,0 @@ -{ - "$schema": "https://raw.githubusercontent.com/Microsoft/azure-pipelines-task-lib/master/tasks.schema.json", - "id": "6d3bc6dd-afd3-4c2f-913d-14ec309c770e", - "name": "websiteVersion", - "friendlyName": "Website Versioning", - "description": "Make mutliple versions of a website available at once.", - "helpMarkDown": "Currently only compatible with an Azure Storage Static Website", - "category": "Deploy", - "author": "unoplatform", - "version": { - "Major": 0, - "Minor": 0, - "Patch": 0 - }, - "visibility": [ - "Build", - "Release" - ], - "demands": [ - "azureps" - ], - "instanceNameFormat": "Deploy $(WebsitePath) to $(AzureStorageAccount)", - "groups": [ - { - "displayName": "Advanced", - "name": "Advanced", - "isExpanded": false - } - ], - "inputs": [ - { - "name": "WebsitePath", - "type": "filePath", - "label": "Website location", - "defaultValue": "", - "required": true, - "helpMarkDown": "Absolute path of the folder where the website is located." - }, - { - "name": "AzureSubscription", - "type": "connectedService:AzureRM", - "label": "Azure Subscription", - "defaultValue": "", - "required": true, - "helpMarkDown": "Azure Resource Manager subscription to target for copying the files." - }, - { - "name": "AzureStorageAccount", - "type": "pickList", - "label": "RM Storage Account", - "defaultValue": "", - "required": true, - "helpMarkDown": "Azure Storage Account to target for copying the files.", - "properties": { - "EditableOptions": "True" - } - }, - { - "name": "VersionsFolderName", - "type": "string", - "label": "Versions folder", - "defaultValue": "versions", - "required": false, - "helpMarkDown": "The name of the folder where to store the different versions.", - "groupName": "Advanced" - } - ], - "dataSourceBindings": [ - { - "target": "AzureStorageAccount", - "endpointId": "$(AzureSubscription)", - "dataSourceName": "AzureStorageAccountRM" - } - ], - "execution": { - "Node10": { - "target": "task.js" - } - } -} \ No newline at end of file diff --git a/extensions/azuredevops/websiteVersion/task.ts b/extensions/azuredevops/websiteVersion/task.ts deleted file mode 100644 index 685551d..0000000 --- a/extensions/azuredevops/websiteVersion/task.ts +++ /dev/null @@ -1,266 +0,0 @@ -import tl = require("azure-pipelines-task-lib"); -import path = require("path"); -import azure = require("azure-storage"); - -import armStorage = require('./azure-arm-rest/azure-arm-storage'); -import { AzureRMEndpoint } from './azure-arm-rest/azure-arm-endpoint'; -import { AzureEndpoint, StorageAccount } from './azure-arm-rest/azureModels'; - -import * as fs from 'fs'; - -class FileToUpload { - localPath: string; - name: string; - remotePath: string; - - constructor(localPath: string, remotePrefix?: string, name?: string, ) { - this.localPath = localPath; - - if (!name) { - name = path.basename(localPath); - } - - this.name = name; - - if (remotePrefix) { - this.remotePath = remotePrefix + '/' + name; - } else { - this.remotePath = name; - } - } -} - -const VersionFileName = 'Version.txt' -const ContainerName = '$web'; -const IndexFileName = 'index.html'; - -let VersionsFolderName: string; -let BlobService: azure.BlobService; - -async function run(): Promise { - try { - BlobService = await createBlobService(); - - let websitePath = tl.getInput("WebsitePath", true); - VersionsFolderName = tl.getInput("VersionsFolderName", true); - - let currentVersion = await retrieveCurrentVersion(websitePath); - - await pushCurrentVersion(websitePath, currentVersion); - - await updateVersionsIndex(); - await updateRootIndex(currentVersion); - } - catch (ex) { - tl.error(ex); - tl.setResult(tl.TaskResult.Failed, ex.message); - } -} - -async function createBlobService() : Promise { - let subscription = tl.getInput('AzureSubscription', true); - let storageAccountName = tl.getInput('AzureStorageAccount', true); - - var azureEndpoint: AzureEndpoint = await new AzureRMEndpoint(subscription).getEndpoint(); - const storageArmClient = new armStorage.StorageManagementClient(azureEndpoint.applicationTokenCredentials, azureEndpoint.subscriptionID); - - let storageAccount: StorageAccount = await storageArmClient.storageAccounts.get(storageAccountName); - let storageAccountResourceGroupName = getResourceGroupNameFromUri(storageAccount.id); - - let accessKeys = await storageArmClient.storageAccounts.listKeys(storageAccountResourceGroupName, storageAccountName, null); - let accessKey: string = accessKeys[0]; - - return azure.createBlobService(storageAccountName, accessKey); -} - -async function retrieveCurrentVersion(folderPath: string): Promise { - console.log("Retrieving website version"); - - let fullVersionFilePath = path.join(folderPath, VersionFileName); - if (fs.existsSync(fullVersionFilePath)) { - let version = fs.readFileSync(fullVersionFilePath, "utf8").trim(); - - if (version) { - - console.log("Version " + version + " found."); - - return version; - } - } - - throw "Could not determine version of the website. Ensure that " + VersionFileName + " exists and is correctly updated."; -} - -async function listExistingVersions(): Promise { - - console.log("Retrieving existing versions"); - - var promise = new Promise((resolve, reject) => { - BlobService.listBlobDirectoriesSegmentedWithPrefix(ContainerName, VersionsFolderName + "/", null, function (error, result, response) { - if (!!error) { - reject(error); - } else { - let versions = new Array(); - result.entries.forEach(directory => { - let version = directory.name.replace(VersionsFolderName, "").replace(/\//gi, ""); - - console.log("Found version " + version); - - versions.push(version); - }); - - resolve(versions); - } - }) - }); - - return promise; -} - -async function pushCurrentVersion(localPath: string, version: string) { - var files = listFiles(localPath, VersionsFolderName + "/" + version); - - for (var i in files) { - let file = files[i]; - await uploadFile(file); - - if(file.name.endsWith('.wasm')) { - await updateFile(file, { contentType: "application/wasm" }); - } - } -} - -async function updateVersionsIndex() { - - let availableVersions = await listExistingVersions(); - - let html = ''; - html += '\n' + ''; - html += '\n' + 'Available version'; - html += '\n' + ''; - html += '\n' + ''; - html += '\n' + '

'; - html += '\n' + '

Available Versions for ' + tl.getVariable("Build.DefinitionName") + '

'; - html += '\n' + '
    '; - - availableVersions - .sort((a, b) => -compareVersions(a, b)) - .forEach(v => { - html += '\n' + '
  • ' + v + '
  • '; - }); - - html += '\n' + '
'; - html += '\n' + '
'; - html += '\n' + ''; - - console.log("Generated " + VersionsFolderName + IndexFileName); - console.log(html); - - let indexPath = path.join(__dirname, IndexFileName); - fs.writeFileSync(indexPath, html); - - await uploadFile(new FileToUpload(indexPath, VersionsFolderName)); -} - -async function updateRootIndex(currentVersion: string) { - let html = ''; - html += '\n' + ''; - html += '\n' + ''; - html += '\n' + ''; - html += '\n' + ''; - - console.log("Generated " + IndexFileName); - console.log(html); - - let indexPath = path.join(__dirname, IndexFileName); - fs.writeFileSync(indexPath, html); - - await uploadFile(new FileToUpload(indexPath)); -} - -async function uploadFile(file: FileToUpload) { - - let upload = new Promise((resolve, reject) => { - BlobService.createBlockBlobFromLocalFile(ContainerName, file.remotePath, file.localPath, function (error, result, response) { - if (!error) { - console.log("Pushed " + result.name); - resolve(); - } else { - reject(error); - } - }); - }); - - await upload; -} - -async function updateFile(file: FileToUpload, options: azure.BlobService.SetBlobPropertiesRequestOptions) { - - let upload = new Promise((resolve, reject) => { - BlobService.setBlobProperties(ContainerName, file.remotePath, options, function (error, result, response) { - if (!error) { - console.log("Updated " + result.name); - resolve(); - } else { - reject(error); - } - }); - }); - - await upload; -} -function listFiles(folderPath: string, prefix: string): FileToUpload[] { - let files = new Array(); - - fs.readdirSync(folderPath).forEach(item => { - let itemPath = path.join(folderPath, item); - if (fs.statSync(itemPath).isDirectory()) { - listFiles(itemPath, prefix + "/" + path.basename(item)).forEach(i => files.push(i)); - } else { - files.push(new FileToUpload(itemPath, prefix)); - } - }); - - return files; -} - -function compareVersions(versionA: string, versionB: string): number { - let partsA = versionA.split("."); - let partsB = versionB.split("."); - - let partsSize = Math.max(partsA.length, partsB.length); - - for (var i = 0; i < partsSize; i++) { - let a = tryGetNumber(partsA, i, 0); - let b = tryGetNumber(partsB, i, 0); - - if (a == b) { - continue; - } - - return a - b; - } - - return 0; -} - -function tryGetNumber(values: string[], index: number, defaultValue: number): number { - if (index >= values.length) { - return defaultValue; - } else { - return +values[index]; - } -} - - -function getResourceGroupNameFromUri(resourceUri: string): string { - if (!!resourceUri && !!resourceUri.trim()) { - resourceUri = resourceUri.toLowerCase(); - return resourceUri.substring(resourceUri.indexOf("resourcegroups/") + "resourcegroups/".length, resourceUri.indexOf("/providers")); - } - - return ""; -} -run(); \ No newline at end of file