name: Validate Recipe (Unit Test) on: [pull_request, workflow_dispatch] jobs: log-context: runs-on: ubuntu-latest steps: # Dump all contexts - name: Dump GitHub context env: GITHUB_CONTEXT: ${{ toJson(github) }} run: echo "$GITHUB_CONTEXT" - name: Dump job context env: JOB_CONTEXT: ${{ toJson(job) }} run: echo "$JOB_CONTEXT" - name: Dump steps context env: STEPS_CONTEXT: ${{ toJson(steps) }} run: echo "$STEPS_CONTEXT" - name: Dump runner context env: RUNNER_CONTEXT: ${{ toJson(runner) }} run: echo "$RUNNER_CONTEXT" - name: Dump strategy context env: STRATEGY_CONTEXT: ${{ toJson(strategy) }} run: echo "$STRATEGY_CONTEXT" - name: Dump matrix context env: MATRIX_CONTEXT: ${{ toJson(matrix) }} run: echo "$MATRIX_CONTEXT" validate-schema: name: Validation runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Setup Node.js uses: actions/setup-node@v1 with: node-version: "14" - name: Install dependencies run: npm --prefix validator install - name: Validate run: npm --prefix validator run check get-test-definition-files: name: Get Test Definition Files needs: [validate-schema] runs-on: ubuntu-latest outputs: matrix: ${{ steps.get-test-definition-files.outputs.result }} steps: - name: Checkout Repo uses: actions/checkout@v2 with: fetch-depth: 0 - name: Ensure no fork repo if: ${{ github.event_name == 'pull_request' }} run: | SOURCE_NAME=$(echo ${{ github.event.pull_request.head.repo.full_name }}) REMOTE_NAME=$(echo ${{ github.event.pull_request.base.repo.full_name }}) if [ $SOURCE_NAME != $REMOTE_NAME ]; then echo "PR should be created from a branch on the source repo, not from a fork, so the E2E tests can run. If you need access please reach out to the #help-virtuoso channel." exit 1 fi # 1) Check for all incoming changes to files under `recipes` directory - name: Get Changed Files id: getfile run: | RECIPES=$(git diff --name-only ${{ github.event.pull_request.base.sha }} ${{ github.event.pull_request.head.sha }} | grep "recipes" || true) if [ -z "$RECIPES" ]; then echo "No recipe files detected." else echo "RECIPES<<EOF" >> $GITHUB_ENV echo $RECIPES >> $GITHUB_ENV echo "EOF" >> $GITHUB_ENV fi TEST_DEFINITIONS=$(git diff --name-only ${{ github.event.pull_request.base.sha }} ${{ github.event.pull_request.head.sha }} | grep "test/definitions" || true) if [ -z "$TEST_DEFINITIONS" ]; then echo "No test definitions files detected." else echo "TEST_DEFINITIONS<<EOF" >> $GITHUB_ENV echo $TEST_DEFINITIONS >> $GITHUB_ENV echo "EOF" >> $GITHUB_ENV fi # 2) Use javascript action / yq to pull the test definition file from the recipe yaml - name: Get Test Definition Files id: get-test-definition-files uses: actions/github-script@v3 with: script: | const fs = require('fs'); const fsp = fs.promises; const path = require('path'); // readdir recursive directory search const { resolve } = path; const { readdir } = fsp; async function getFiles(dir) { const dirents = await readdir(dir, { withFileTypes: true }); const files = await Promise.all(dirents.map((dirent) => { const res = path.join(dir, dirent.name); return dirent.isDirectory() ? getFiles(res) : res; })); return Array.prototype.concat(...files); } var outputTestFiles = [] if (process.env.TEST_DEFINITIONS != '') { // Get incoming added/updated test definitions files const testDefinitionFiles = process.env.TEST_DEFINITIONS ? process.env.TEST_DEFINITIONS.split(' ') : [] console.log(`Detected Test Definitions Files: ${JSON.stringify(testDefinitionFiles, null, 2)}`) testDefinitionFiles.forEach(testDefinitionFile => { if (!outputTestFiles.includes(testDefinitionFile)) { outputTestFiles.push(testDefinitionFile) } }) } if (process.env.RECIPES != '') { // Get incoming added/updated recipe files const recipeFiles = process.env.RECIPES ? process.env.RECIPES.split(' ') : [] console.log(`Detected Recipe Files: ${JSON.stringify(recipeFiles, null, 2)}`) if (recipeFiles.length) { // Get all deploy config files const deployConfigsUS = await getFiles('test/definitions'); const deployConfigsEU = await getFiles('test/definitions-eu'); const deployConfigs = deployConfigsUS.concat(deployConfigsEU); console.log("All deployConfigs:", deployConfigs); // Build up list of Deploy Configs to run based on recipes that have changed const testDefinitionFilesToRun = deployConfigs.reduce((p, c) => { const contents = require(`${process.env.GITHUB_WORKSPACE}/${c}`); var recipes = [] if (contents.instrumentations && contents.instrumentations.resources) { contents.instrumentations.resources.forEach(resource => { if (resource.params && resource.params.recipe_content_url) { recipes = recipes.concat(resource.params.recipe_content_url); } }); } // returns list of matched recipes from the deploy config file const matchedRecipes = recipes.filter( (r) => recipeFiles.filter((rf) => r.includes(rf)).length > 0 ); // Add the current deploy config file to our output if there were matches return matchedRecipes.length > 0 ? [`${c}`, ...p] : p; }, []); console.log('testDefinitionFilesToRun:', testDefinitionFilesToRun); testDefinitionFilesToRun.forEach(testDefinitionFile => { if (!outputTestFiles.includes(testDefinitionFile)) { outputTestFiles.push(testDefinitionFile) } }) } } const outputTestFilesMap = outputTestFiles.map(testDefinitionFile => { return { testDefinitionFile } }) const output = { "include": outputTestFilesMap } console.log("Output: ", output); return output; test-deploy-recipe: name: Test Deploy Recipe needs: [get-test-definition-files] if: ${{ fromJSON(needs.get-test-definition-files.outputs.matrix).include[0] }} # Avoids empty matrix validation error runs-on: ubuntu-latest strategy: matrix: ${{ fromJSON(needs.get-test-definition-files.outputs.matrix) }} fail-fast: false max-parallel: 10 env: MATRIX: ${{ toJSON(matrix) }} steps: - name: Checkout Repo uses: actions/checkout@v2 with: fetch-depth: 0 - name: Update Test Definition Files URLs id: get-test-definition-files env: TEST_DEFINITION_FILE: ${{ matrix.testDefinitionFile }} uses: actions/github-script@v3 with: script: | const fs = require('fs'); const fsp = fs.promises; const path = require('path'); // before returning, we need to edit the deploy config files in-place so they // use the right URLs from the branch async function getDeployConfigFile(file, outputDir) { const data = await fsp.readFile(path.join(outputDir, file)); return JSON.parse(data); } // Get testDefinitonFile from MATRIX env var const testDefinitionFile = process.env.TEST_DEFINITION_FILE; console.log(`Detected Deploy Config: ${JSON.stringify(testDefinitionFile, null, 2)}`) // Update URLs to use branch this PR is opened with const data = await getDeployConfigFile(testDefinitionFile, process.env.GITHUB_WORKSPACE); // Update github source URLs with branch name let jsonContent = JSON.stringify(data, null, 2); const replacementString = `$1$2-b ${process.env.GITHUB_HEAD_REF} $3$4`; const sourceRepositoryRegex = /(.*)(\")(https:\/\/github.com\/newrelic\/open-install-library)(.*)/gi; jsonContent = jsonContent.replace(sourceRepositoryRegex, replacementString); // Update raw URLs with branch name const replacementString2 = `$1${process.env.GITHUB_HEAD_REF}$3`; const sourceRepositoryRegex2 = /(raw.githubusercontent.com\/newrelic\/open-install-library\/)(main)(\/newrelic\/recipes\/)*/gi; jsonContent = jsonContent.replace(sourceRepositoryRegex2, replacementString2); // Write file back to workspace const outputPath = `${process.env.GITHUB_WORKSPACE}/${testDefinitionFile}`; console.log("Updated Deploy Config File: ", outputPath); console.log("Deploy Config content: ", jsonContent); fs.writeFileSync(outputPath, jsonContent); return testDefinitionFile; - name: Write AWS Certificate to File env: AWS_PEM: ${{ secrets.GIT_DEPLOYER_CANADA_AWS_PEM }} run: | mkdir -p configs rm -f configs/gitdeployerCanada.pem echo "$AWS_PEM" > configs/gitdeployerCanada.pem sudo chmod 400 configs/gitdeployerCanada.pem - name: Write Test Definition File JSON to file env: USER_JSON: ${{ secrets.GIT_DEPLOYER_DOCKER_USER_CONFIG }} run: | echo "$USER_JSON" > configs/gitusdkr${{ github.run_id }}.json - name: Pull Deployer image run: | docker pull newrelic/deployer:latest docker images newrelic/deployer:latest - name: Run deployer id: deployerRun continue-on-error: true run: | set +e testDefinitionFile=$(echo $MATRIX | jq -c -r '.testDefinitionFile') echo $testDefinitionFile docker run \ -v ${{ github.workspace }}/configs/:/mnt/deployer/configs/\ -v ${{ github.workspace }}/test/:/mnt/deployer/test/\ --entrypoint ruby newrelic/deployer:latest main.rb -c configs/gitusdkr${{ github.run_id }}.json -d $testDefinitionFile -l debug echo ::set-output name=exit_status::$? - name: Teardown any previous deployment if: always() id: cleanupResources continue-on-error: true run: | testDefinitionFile=$(echo $MATRIX | jq -c -r '.testDefinitionFile') echo $testDefinitionFile docker run \ -v ${{ github.workspace }}/configs/:/mnt/deployer/configs/\ -v ${{ github.workspace }}/test/:/mnt/deployer/test/\ --entrypoint ruby newrelic/deployer:latest main.rb -c configs/gitusdkr${{ github.run_id }}.json -d $testDefinitionFile -t - name: Report any error if: steps.deployerRun.outputs.exit_status != 0 run: exit 1 slack-notify: runs-on: ubuntu-latest needs: [test-deploy-recipe] if: always() steps: - name: Build Result Slack Notification uses: 8398a7/action-slack@v3 with: author_name: GitHub Actions status: custom fields: commit,repo,ref,author,eventName,message,workflow custom_payload: | { username: "GitHub Actions", icon_emoji: ":octocat:", attachments: [{ color: ${{ needs.test-deploy-recipe.result == 'success' }} === true ? '#43cc11' : '#e05d44', blocks: [ { type: "section", text: { type: "mrkdwn", text: `Build for ${process.env.AS_REPO}` } }, { type: "section", fields: [ { type: "mrkdwn", text: `*Commit:*\n${process.env.AS_COMMIT}` }, { type: "mrkdwn", text: `*Author:*\n${process.env.AS_AUTHOR}` }, { type: "mrkdwn", text: `*Branch:*\n${process.env.AS_REF}` }, { type: "mrkdwn", text: `*Message:*\n${process.env.AS_MESSAGE}` }, { type: "mrkdwn", text: `*Type:*\n${process.env.AS_EVENT_NAME}` }, { type: "mrkdwn", text: "*PR:*\n${{ github.event.pull_request.html_url }}" }, { type: "mrkdwn", text: `*Workflow:*\n${ process.env.AS_WORKFLOW }` } ] }, { type: "section", text: { type: "mrkdwn", text: [ "*Result:*", `• ${ ${{ needs.test-deploy-recipe.result == 'success' }} === true ? '✅' : '❌' } AWS recipe validation test: ${{ needs.test-deploy-recipe.result }}` ].join('\n') } }, { type: "context", elements: [ { type: "image", image_url: "https://avatars2.githubusercontent.com/in/15368", alt_text: "Github Actions" }, { type: "mrkdwn", text: "This message was created automatically by GitHub Actions." } ] } ] }] } env: GITHUB_TOKEN: ${{ github.token }} SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}