From 150608bc140648f9084df2c51a52d67532e16314 Mon Sep 17 00:00:00 2001 From: Raphael Ribeiro Date: Fri, 12 May 2023 15:54:15 -0300 Subject: [PATCH] backstage rule improvements --- package.json | 3 + rules/common/backstage.ts | 23 +- .../common/schemas/backstage.annotations.json | 301 ++++++++++++++++++ yarn.lock | 28 ++ 4 files changed, 353 insertions(+), 2 deletions(-) create mode 100644 rules/common/schemas/backstage.annotations.json diff --git a/package.json b/package.json index f3d17de..1c7c37e 100644 --- a/package.json +++ b/package.json @@ -61,5 +61,8 @@ "yarn prettier --write", "git add" ] + }, + "dependencies": { + "axios": "^1.4.0" } } diff --git a/rules/common/backstage.ts b/rules/common/backstage.ts index eafc249..9cb3bd1 100644 --- a/rules/common/backstage.ts +++ b/rules/common/backstage.ts @@ -1,9 +1,13 @@ import { danger, fail } from "danger" +import axios from "axios" +import { createWriteStream, existsSync } from "fs" import { validate } from "@roadiehq/roadie-backstage-entity-validator" const backstage = async () => { const pr = danger.github.pr const utils = danger.github.utils + const schemaURL = + "https://raw.githubusercontent.com/loadsmart/peril-settings/f56651f1f11a45d6e251427d8936b309cd6dba47/rules/common/schemas/backstage.annotations.json" const isOpen = pr.state === "open" @@ -15,13 +19,28 @@ const backstage = async () => { const fileContent = await utils.fileContents(filePath, `${pr.head.user.login}/${pr.head.repo.name}`, pr.head.sha) if (fileContent) { + const schemaPath = `/tmp/backstage.annotations.json` + + // Check if the schema file exists + if (!existsSync(schemaPath)) { + const response = await axios.get(schemaURL, { responseType: "stream" }) + const fileStream = createWriteStream(schemaPath) + response.data.pipe(fileStream) + + // Wait for the file to finish downloading + await new Promise((resolve, reject) => { + fileStream.on("finish", resolve) + fileStream.on("error", reject) + }) + } + try { - await validate(fileContent, true, "./schemas/backstage.annotations.json") + await validate(fileContent, true, schemaPath) } catch (e) { fail(`The 'catalog-info.yaml' file is not valid for Backstage. Error details:\n\n\`\`\`\n${e}\n\`\`\``) } } else { - fail(`The '${filePath}' file doesn't exist in the pull request.`) + fail(`'${filePath}' file doesn't exist.`) } } diff --git a/rules/common/schemas/backstage.annotations.json b/rules/common/schemas/backstage.annotations.json new file mode 100644 index 0000000..e8cefc5 --- /dev/null +++ b/rules/common/schemas/backstage.annotations.json @@ -0,0 +1,301 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "$id": "Entity metadata annotations", + "description": "Individual annotation format validations", + "type": "object", + "required": ["metadata"], + "additionalProperties": true, + "properties": { + "metadata": { + "type": "object", + "required": ["annotations", "tags"], + "properties": { + "annotations": { + "type": "object", + "description": "Key/value pairs of non-identifying auxiliary information attached to the entity.", + "additionalProperties": false, + "patternProperties": { + "^.+$": { + "type": "string" + } + }, + "required": ["github.com/project-slug", "circleci.com/project-slug", "opslevel.com/tier"], + "allOf": [ + { + "properties": { + "backstage.io/managed-by-location": { + "type": "string", + "pattern": "(url|gitlab|github|azure/api|dir):.*" + } + } + }, + { + "properties": { + "backstage.io/managed-by-origin-location": { + "type": "string", + "pattern": "(url|gitlab|github|azure/api|dir):.*" + } + } + }, + { + "properties": { + "backstage.io/techdocs-ref": { + "type": "string", + "pattern": "(url|gitlab|github|azure/api|dir):.*" + } + } + }, + { + "properties": { + "backstage.io/source-location": { + "type": "string", + "pattern": "((url|gitlab|github|azure/api):.*|(dir):.*/)$" + } + } + }, + { + "properties": { + "backstage.io/view-url": { + "type": "string", + "format": "uri" + } + } + }, + { + "properties": { + "backstage.io/edit-url": { + "type": "string", + "format": "uri" + } + } + }, + { + "properties": { + "graph.microsoft.com/group-id": { + "type": "string", + "format": "uuid" + } + } + }, + { + "properties": { + "graph.microsoft.com/user-id": { + "type": "string", + "format": "uuid" + } + } + }, + { + "properties": { + "datadog/dashboard-url": { + "type": "string", + "format": "uri" + } + } + }, + { + "properties": { + "backstage.io/ldap-uuid": { + "type": "string", + "format": "uuid" + } + } + }, + { + "properties": { + "backstage.io/ldap-dn": { + "type": "string" + } + } + }, + { + "properties": { + "backstage.io/ldap-rdn": { + "type": "string" + } + } + }, + { + "properties": { + "jenkins.io/github-folder": { + "type": "string" + } + } + }, + { + "properties": { + "github.com/project-slug": { + "type": "string" + } + } + }, + { + "properties": { + "github.com/team-slug": { + "type": "string" + } + } + }, + { + "properties": { + "github.com/user-login": { + "type": "string" + } + } + }, + { + "properties": { + "rollbar.com/project-slug": { + "type": "string" + } + } + }, + { + "properties": { + "circleci.com/project-slug": { + "type": "string" + } + } + }, + { + "properties": { + "sonarqube.org/project-key": { + "type": "string" + } + } + }, + { + "properties": { + "backstage.io/code-coverage": { + "type": "string" + } + } + }, + { + "properties": { + "github.com/project-slug": { + "type": "string" + } + } + }, + { + "properties": { + "sentry.io/project-slug": { + "type": "string" + } + } + }, + { + "properties": { + "aws.com/lambda-function-name": { + "type": "string" + } + } + }, + { + "properties": { + "aws.com/lambda-region": { + "type": "string" + } + } + }, + { + "properties": { + "jira/project-key": { + "type": "string" + } + } + }, + { + "properties": { + "snyk.io/org-name": { + "type": "string" + } + } + }, + { + "properties": { + "graph.microsoft.com/tenant-id": { + "type": "string" + } + } + }, + { + "properties": { + "github.com/project-slug": { + "type": "string", + "pattern": "(loadsmart/).*" + } + } + }, + { + "properties": { + "circleci.com/project-slug": { + "type": "string", + "pattern": "(github/loadsmart/).*" + } + } + }, + { + "properties": { + "sentry.io/project-slug": { + "type": "string" + } + } + }, + { + "properties": { + "snyk.io/org-name": { + "type": "string", + "pattern": "loadsmart" + } + } + }, + { + "properties": { + "snyk.io/project-ids": { + "type": "string", + "pattern": "^(\\b[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}\\b)(,\\b[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}\\b)*$" + } + } + }, + { + "properties": { + "opslevel.com/tier": { + "type": "string", + "pattern": "^tier_[0-4]$" + } + } + } + ] + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "labels": { + "type": "object", + "description": "Optional key/value pairs of that are attached to the entity, and their use is identical to Kubernetes object labels.", + "additionalProperties": false, + "patternProperties": { + "^.+$": { + "type": "string" + } + }, + "allOf": [ + { + "properties": { + "loadsmart.com/product": { + "type": "string", + "pattern": "^(website|tms-integrations|shipper-guide|opendock|loadboard|kamion|internal-product|incident-management|alice|abgail|platform)$" + } + } + } + ] + } + } + } + } +} diff --git a/yarn.lock b/yarn.lock index 4348e5f..87cd124 100644 --- a/yarn.lock +++ b/yarn.lock @@ -646,6 +646,15 @@ axios@^0.18.0: follow-redirects "1.5.10" is-buffer "^2.0.2" +axios@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.4.0.tgz#38a7bf1224cd308de271146038b551d725f0be1f" + integrity sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA== + dependencies: + follow-redirects "^1.15.0" + form-data "^4.0.0" + proxy-from-env "^1.1.0" + babel-code-frame@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" @@ -2254,6 +2263,11 @@ follow-redirects@1.5.10: dependencies: debug "=3.1.0" +follow-redirects@^1.15.0: + version "1.15.2" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" + integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== + for-in@^1.0.1, for-in@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" @@ -2291,6 +2305,15 @@ form-data@^2.3.3: combined-stream "^1.0.6" mime-types "^2.1.12" +form-data@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + form-data@~2.1.1: version "2.1.4" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.1.4.tgz#33c183acf193276ecaa98143a69e94bfee1750d1" @@ -4735,6 +4758,11 @@ process-nextick-args@~1.0.6: version "1.0.7" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" +proxy-from-env@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + prr@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476"