diff --git a/.github/workflows/build-tool-packages.yml b/.github/workflows/build-tool-packages.yml new file mode 100644 index 0000000..8f82d17 --- /dev/null +++ b/.github/workflows/build-tool-packages.yml @@ -0,0 +1,201 @@ +# This reusable workflow is used by actions/*-versions repositories +# It is designed to +# - build and test new versions of a tool (Go, Node) +# - publish a release with a new tool version +# The GITHUB_TOKEN secret is used to trigger workflow runs and publish releases + +name: Generate tool packages +on: + workflow_call: + inputs: + tool-name: + description: "Tool name to build and upload. Supported values are: 'go' and 'node'" + required: true + type: string + tool-version: + description: "Tool version to build and upload" + required: true + type: string + publish-release: + description: "Whether to publish releases" + required: true + type: boolean + +defaults: + run: + shell: pwsh + +jobs: + build: + name: Build ${{ inputs.tool-name }} ${{ inputs.tool-version }} [${{ matrix.platform }}] + runs-on: ubuntu-latest + env: + ARTIFACT_NAME: ${{ inputs.tool-name }}-${{ inputs.tool-version }}-${{ matrix.platform }}-x64 + strategy: + fail-fast: false + matrix: + platform: [linux, darwin, win32] + steps: + - uses: actions/checkout@v3 + with: + submodules: true + + - name: Build ${{ inputs.tool-name }} ${{ inputs.tool-version }} + run: | + ./builders/build-${{ inputs.tool-name }}.ps1 -Version ${{ inputs.tool-version }} ` + -Platform ${{ matrix.platform }} + + - name: Publish artifact + uses: actions/upload-artifact@v3 + with: + name: ${{ env.ARTIFACT_NAME }} + path: ${{ runner.temp }}/artifact + + test: + name: Test ${{ inputs.tool-name }} ${{ inputs.tool-version }} [${{ matrix.platform }}] + needs: build + runs-on: ${{ matrix.os }} + env: + ARTIFACT_NAME: ${{ inputs.tool-name }}-${{ inputs.tool-version }}-${{ matrix.platform }}-x64 + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-latest + platform: linux + - os: macos-latest + platform: darwin + - os: windows-latest + platform: win32 + steps: + - uses: actions/checkout@v3 + with: + submodules: true + + - name: Fully cleanup the toolcache directory before testing + run: ./helpers/clean-toolcache.ps1 -ToolName "${{ inputs.tool-name }}" + + - name: Download artifact + uses: actions/download-artifact@v3 + with: + path: ${{ runner.temp }} + + - name: Extract files + run: | + if ('${{ matrix.platform }}' -eq 'win32') { + if ('${{ inputs.tool-name }}' -eq 'node') { + $artifactName = "${{ env.ARTIFACT_NAME }}.7z" + } elseif ('${{ inputs.tool-name }}' -eq 'go') { + $artifactName = "${{ env.ARTIFACT_NAME }}.zip" + } else { + Write-Host "Unsupported tool - ${{ inputs.tool-name }}" + exit 1 + } + + 7z.exe x "$artifactName" -y | Out-Null + } else { + $artifactName = "${{ env.ARTIFACT_NAME }}.tar.gz" + tar -xzf $artifactName + } + working-directory: ${{ runner.temp }}/${{ env.ARTIFACT_NAME }} + + - name: Apply build artifact to the local machine + run: | + if ('${{ matrix.platform }}' -eq 'win32') { powershell ./setup.ps1 } else { sh ./setup.sh } + working-directory: ${{ runner.temp }}/${{ env.ARTIFACT_NAME }} + + - name: Setup Node.js ${{ inputs.tool-version }} + if: inputs.tool-name == 'node' + uses: actions/setup-node@v3 + with: + node-version: ${{ inputs.tool-version }} + + - name: Setup Go ${{ inputs.tool-version }} + if: inputs.tool-name == 'go' + uses: actions/setup-go@v3 + with: + go-version: ${{ inputs.tool-version }} + + - name: Wait for the logs + run: | + Write-Host "Fake step that does nothing" + Write-Host "We need it because log from the previous step 'Setup ${{ inputs.tool-name }}' is not available here yet." + Write-Host "In testing step we analyze build log of 'Setup ${{ inputs.tool-name }}' task" + Write-Host "to determine if ${{ inputs.tool-name }} version was consumed from cache or if it was downloaded" + for ($i = 0; $i -lt 200; $i++) { Get-Random } + + - name: Run tests + env: + VERSION: ${{ inputs.tool-version }} + run: | + Install-Module Pester -Force -Scope CurrentUser + Import-Module Pester + $toolName = (Get-Culture).TextInfo.ToTitleCase("${{ inputs.tool-name }}") + Invoke-Pester -Script ./$toolName.Tests.ps1 -EnableExit + working-directory: ./tests + + publish_release: + name: Publish release + if: inputs.publish-release + needs: test + runs-on: ubuntu-latest + steps: + - uses: actions/download-artifact@v3 + + - name: Generate release body + id: generate-release-body + run: | + if ('${{ inputs.tool-name }}' -eq 'node') { + $releaseBody = 'Node.js ${{ inputs.tool-version }}' + } else { + $releaseBody = 'Go ${{ inputs.tool-version }}' + } + echo "RELEASE_BODY=$releaseBody" >> $env:GITHUB_OUTPUT + + - name: Publish Release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ inputs.tool-version }}-${{ github.run_id }} + release_name: ${{ inputs.tool-version }} + body: | + ${{ steps.generate-release-body.outputs.RELEASE_BODY }} + + - name: Upload release assets + uses: actions/github-script@v6 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const fs = require('fs'); + + for (let artifactDir of fs.readdirSync('.')) { + let artifactName = fs.readdirSync(`${artifactDir}`)[0]; + + console.log(`Upload ${artifactName} asset`); + github.rest.repos.uploadReleaseAsset({ + owner: context.repo.owner, + repo: context.repo.repo, + release_id: ${{ steps.create_release.outputs.id }}, + name: artifactName, + data: fs.readFileSync(`./${artifactDir}/${artifactName}`) + }); + } + + trigger_pr: + name: Trigger "Create Pull Request" workflow + needs: publish_release + runs-on: ubuntu-latest + steps: + - name: Trigger "Create Pull Request" workflow + uses: actions/github-script@v6 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + github.rest.actions.createWorkflowDispatch({ + owner: context.repo.owner, + repo: context.repo.repo, + workflow_id: 'create-pr.yml', + ref: 'main' + }); \ No newline at end of file diff --git a/.github/workflows/create-pr-to-update-manifest.yml b/.github/workflows/create-pr-to-update-manifest.yml new file mode 100644 index 0000000..c820ec5 --- /dev/null +++ b/.github/workflows/create-pr-to-update-manifest.yml @@ -0,0 +1,43 @@ +# This reusable workflow is used by actions/*-versions repositories +# It is designed to create a PR with update of versions-manifest.json when a new release is published +# The GITHUB_TOKEN secret is used to create versions-manifest.json and publish related PR + +name: Create Pull Request +on: + workflow_call: + inputs: + tool-name: + description: 'Name of the tool for which PR is created' + required: true + type: string + +defaults: + run: + shell: pwsh + +jobs: + create_pr: + name: Create Pull Request + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + submodules: true + + - name: Create versions-manifest.json + run: | + ./helpers/packages-generation/manifest-generator.ps1 -RepositoryFullName "$env:GITHUB_REPOSITORY" ` + -GitHubAccessToken "${{ secrets.GITHUB_TOKEN }}" ` + -OutputFile "./versions-manifest.json" ` + -ConfigurationFile "./config/${{ inputs.tool-name }}-manifest-config.json" + + - name: Create GitHub PR + run: | + $formattedDate = Get-Date -Format "MM/dd/yyyy" + ./helpers/github/create-pull-request.ps1 ` + -RepositoryFullName "$env:GITHUB_REPOSITORY" ` + -AccessToken "${{ secrets.GITHUB_TOKEN }}" ` + -BranchName "update-versions-manifest-file" ` + -CommitMessage "Update versions-manifest" ` + -PullRequestTitle "[versions-manifest] Update for release from ${formattedDate}" ` + -PullRequestBody "Update versions-manifest.json for release from ${formattedDate}" \ No newline at end of file diff --git a/.github/workflows/get-new-tool-versions.yml b/.github/workflows/get-new-tool-versions.yml new file mode 100644 index 0000000..25f359e --- /dev/null +++ b/.github/workflows/get-new-tool-versions.yml @@ -0,0 +1,109 @@ +# This reusable workflow is used by actions/*-versions repositories +# It is designed to check for new versions of a tool (Python, Node, etc.) +# The 'SLACK_CHANNEL_URL' secret must be added to the repository containing the caller workflow +# in order to publish messages to Slack. +# The 'Get Available Tools Versions - Publishing Approval' environment must be created in the repository containing the caller workflow +# The 'trigger_builds' job requires manual approval +# The GITHUB_TOKEN secret is used to cancel and trigger workflow runs + +name: Get new tool versions +on: + workflow_call: + inputs: + tool-name: + description: 'Name of the tool for which versions are searched' + required: true + type: string + image-url: + description: 'Tool image to be attached to Slack posts' + required: true + type: string + +defaults: + run: + shell: pwsh + +jobs: + find_new_versions: + name: Find new versions + runs-on: ubuntu-latest + outputs: + versions_output: ${{ steps.Get_new_versions.outputs.TOOL_VERSIONS }} + steps: + - uses: actions/checkout@v3 + with: + submodules: true + + - id: Get_new_versions + name: Get new versions + run: ./helpers/get-new-tool-versions/get-new-tool-versions.ps1 -ToolName ${{ inputs.tool-name }} + + check_new_versions: + name: Check new versions + runs-on: ubuntu-latest + needs: find_new_versions + env: + TOOL_VERSIONS: ${{needs.find_new_versions.outputs.versions_output}} + steps: + - uses: actions/checkout@v3 + with: + submodules: true + + - name: Check Versions + if: env.TOOL_VERSIONS == '' + run: | + Write-Host "No new versions were found" + Import-Module "./helpers/github/github-api.psm1" + $gitHubApi = Get-GitHubApi -RepositoryFullName "$env:GITHUB_REPOSITORY" ` + -AccessToken "${{ secrets.GITHUB_TOKEN }}" + $gitHubApi.CancelWorkflow("$env:GITHUB_RUN_ID") + Start-Sleep -Seconds 60 + + - name: Send Slack notification + run: | + $pipelineUrl = "$env:GITHUB_SERVER_URL/$env:GITHUB_REPOSITORY/actions/runs/$env:GITHUB_RUN_ID" + $message = "The following versions of '${{ inputs.tool-name }}' are available to upload: ${{ env.TOOL_VERSIONS }}\nLink to the pipeline: $pipelineUrl" + ./helpers/get-new-tool-versions/send-slack-notification.ps1 -Url "${{ secrets.SLACK_CHANNEL_URL }}" ` + -ToolName "${{ inputs.tool-name }}" ` + -ImageUrl "${{ inputs.image-url }}" ` + -Text "$message" + trigger_builds: + name: Trigger builds + runs-on: ubuntu-latest + needs: [find_new_versions, check_new_versions] + env: + TOOL_VERSIONS: ${{needs.find_new_versions.outputs.versions_output}} + environment: Get Available Tools Versions - Publishing Approval + steps: + - uses: actions/checkout@v3 + with: + submodules: true + + - name: Trigger "Build ${{ inputs.tool-name }} packages" workflow + run: | + $workflowFileName = "build-${{ inputs.tool-name }}-packages.yml".ToLower() + ./helpers/github/run-ci-builds.ps1 -RepositoryFullName "$env:GITHUB_REPOSITORY" ` + -AccessToken "${{ secrets.GITHUB_TOKEN }}" ` + -WorkflowFileName "$workflowFileName" ` + -WorkflowDispatchRef "main" ` + -ToolVersions "${{ env.TOOL_VERSIONS }}" ` + -PublishReleases "true" + + check_build: + name: Check build for failures + runs-on: ubuntu-latest + needs: [find_new_versions, check_new_versions, trigger_builds] + if: failure() + steps: + - uses: actions/checkout@v3 + with: + submodules: true + + - name: Send Slack notification if build fails + run: | + $pipelineUrl = "$env:GITHUB_SERVER_URL/$env:GITHUB_REPOSITORY/actions/runs/$env:GITHUB_RUN_ID" + $message = "The build of the '${{ inputs.tool-name }}' detection pipeline failed :progress-error:\nLink to the pipeline: $pipelineUrl" + ./helpers/get-new-tool-versions/send-slack-notification.ps1 -Url "${{ secrets.SLACK_CHANNEL_URL }}" ` + -ToolName "${{ inputs.tool-name }}" ` + -Text "$message" ` + -ImageUrl "${{ inputs.image-url }}" diff --git a/.github/workflows/validate-manifest.yml b/.github/workflows/validate-manifest.yml new file mode 100644 index 0000000..5a89911 --- /dev/null +++ b/.github/workflows/validate-manifest.yml @@ -0,0 +1,51 @@ +# This reusable workflow is used by actions/*-versions repositories +# It is designed to validate the versions-manifest.json file +# The 'SLACK_CHANNEL_URL' secret must be added to the repository containing the caller workflow +# in order to publish messages to Slack + +name: Validate manifest +on: + workflow_call: + inputs: + tool-name: + description: 'Name of the tool for which manifest is validated' + required: true + type: string + image-url: + description: 'Tool image to be attached to Slack posts' + required: true + type: string + +defaults: + run: + shell: pwsh + +jobs: + validation: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + submodules: true + + - name: Validate manifest + run: .\helpers\packages-generation\manifest-validator.ps1 -ManifestPath '.\versions-manifest.json' + + check_build: + name: Check validation for failures + runs-on: ubuntu-latest + needs: [validation] + if: failure() + steps: + - uses: actions/checkout@v3 + with: + submodules: true + + - name: Send Slack notification if validation fails + run: | + $pipelineUrl = "$env:GITHUB_SERVER_URL/$env:GITHUB_REPOSITORY/actions/runs/$env:GITHUB_RUN_ID" + $message = "The validation of ${{ inputs.tool-name }} manifest failed. \nLink to the pipeline: $pipelineUrl" + .\helpers\get-new-tool-versions\send-slack-notification.ps1 -Url "${{ secrets.SLACK_CHANNEL_URL }}" ` + -ToolName "${{ inputs.tool-name }}" ` + -Text "$message" ` + -ImageUrl "${{ inputs.image-url }}"