diff --git a/.github/workflows/check-registry.yml b/.github/workflows/check-registry.yml new file mode 100644 index 000000000000..6c80aabfed18 --- /dev/null +++ b/.github/workflows/check-registry.yml @@ -0,0 +1,30 @@ +name: Registry Validation + +on: + pull_request: + # Make sure this only runs when registry entries are touched. + paths: + - 'data/registry/**' + +jobs: + registry-validation: + name: REGISTRY validation + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Create and use reduced-dependencies package.json + run: | + mkdir -p tmp + jq '.devDependencies |= with_entries( + select(.key | test("gulp|markdown|through|require|yargs|js-yaml|ajv|ajv-formats|ajv-errors")) + ) + | del(.dependencies, .optionalDependencies)' \ + package.json > tmp/package-ci.json + cp tmp/package-ci.json package.json + - uses: actions/setup-node@v4 + with: + node-version-file: .nvmrc + cache: npm + cache-dependency-path: tmp/package-ci.json + - run: npm install --ignore-scripts --omit=optional + - run: npm run check:registry diff --git a/.github/workflows/check-text.yml b/.github/workflows/check-text.yml index 6f0ada090584..df81709b7cf0 100644 --- a/.github/workflows/check-text.yml +++ b/.github/workflows/check-text.yml @@ -33,7 +33,7 @@ jobs: run: | mkdir -p tmp jq '.devDependencies |= with_entries( - select(.key | test("gulp|markdown|through|require|yargs")) + select(.key | test("gulp|markdown|through|require|yargs|js-yaml|ajv|ajv-formats|ajv-errors")) ) | del(.dependencies, .optionalDependencies)' \ package.json > tmp/package-ci.json diff --git a/data/registry-schema.json b/data/registry-schema.json new file mode 100644 index 000000000000..0defdcb304db --- /dev/null +++ b/data/registry-schema.json @@ -0,0 +1,188 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://opentelemetry.io/ecosystem/registry/schema.json", + "$comment": "https://opentelemetry.io/ecosystem/registry/adding/", + "description": "A schema for entries in the OpenTelemetry registry", + "type": "object", + "properties": { + "title": { + "type": "string", + "description": "The title of the entry", + "errorMessage": "The title of the entry must be a string and can not be empty", + "minLength": 1 + }, + "registryType": { + "type": "string", + "description": "The type of the entry", + "enum": [ + "core", + "exporter", + "extension", + "instrumentation", + "log-bridge", + "processor", + "receiver", + "resource-detector", + "utilities" + ] + }, + "language": { + "type": "string", + "description": "The programming language of the entry", + "examples": ["collector"] + }, + "description": { + "type": "string", + "description": "A description of the entry", + "errorMessage": "The description of the entry must be a string and can not be empty" + }, + "tags": { + "type": "array", + "items": { + "type": "string" + }, + "description": "A list of tags for the entry", + "uniqueItems": true + }, + "license": { + "type": "string", + "description": "The license of the entry", + "examples": ["Apache 2.0", "MIT"] + }, + "createdAt": { + "type": "string", + "description": "The date the entry was created", + "format": "date" + }, + "authors": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "oneOf": [ + { + "pattern": "OpenTelemetry Authors", + "errorMessage": "The name of the author can not contain 'OpenTelemetry' except for 'OpenTelemetry Authors'" + }, + { + "pattern": "^(?!.*OpenTelemetry).*$" + } + ], + "description": "The name of the author", + "errorMessage": "The name of the author must be a string" + }, + "email": { + "type": "string", + "description": "The email address of the author", + "format": "email" + }, + "url": { + "type": "string", + "description": "The URL of the author", + "pattern": "^https:\\/\\/github\\.com\\/([a-zA-Z0-9](?:-?[a-zA-Z0-9]){0,38})$", + "errorMessage": "The URL of the author must be a valid GitHub user URL" + } + }, + "if": { + "properties": { + "name": { + "const": "OpenTelemetry Authors" + } + } + }, + "then": { + "required": ["name"] + }, + "else": { + "required": ["name"], + "anyOf": [ + { + "required": ["email"] + }, + { + "required": ["url"] + } + ], + "errorMessage": "An author must have an email or a URL" + }, + "additionalProperties": false + }, + "description": "A list of authors of the entry", + "minItems": 1 + }, + "urls": { + "type": "object", + "properties": { + "repo": { + "type": "string", + "description": "The URL of the repository" + }, + "docs": { + "type": "string", + "description": "The URL of the documentation" + } + }, + "required": ["repo"], + "additionalProperties": false + }, + "package": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the package" + }, + "version": { + "type": "string", + "description": "The version of the package" + }, + "registry": { + "type": "string", + "description": "The registry of the package", + "enum": [ + "crates", + "npm", + "pip", + "gems", + "maven", + "nuget", + "packagist", + "go", + "go-collector", + "hex" + ] + }, + "quickInstall": { + "type": "boolean", + "description": "A flag to indicate if a quick install can be shown" + } + }, + "required": ["name", "registry"], + "additionalProperties": false + }, + "deprecated": { + "type": "object", + "properties": { + "reason": { + "type": "string", + "description": "The reason for deprecation", + "errorMessage": "The reason for deprecation must be a string and can not be empty" + } + }, + "required": ["reason"], + "additionalProperties": false + }, + "isNative": { + "type": "boolean", + "description": "A flag to indicate if the entry is native" + }, + "isFirstParty": { + "type": "boolean", + "description": "A flag to indicate if the entry is first party" + } + }, + "additionalProperties": false, + "required": ["title", "description", "urls", "authors"] +} diff --git a/data/registry/collector-extension-googleclientauth.yml b/data/registry/collector-extension-googleclientauth.yml index dbf6c5cd7df7..b3f11b149076 100644 --- a/data/registry/collector-extension-googleclientauth.yml +++ b/data/registry/collector-extension-googleclientauth.yml @@ -12,6 +12,8 @@ urls: repo: https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/extension/googleclientauthextension license: Apache 2.0 description: + This extension provides Google OAuth2 Client Credentials and Metadata for gRPC + and HTTP based exporters. authors: - name: OpenTelemetry Authors createdAt: 2024-04-18 diff --git a/data/registry/collector-receiver-splunkenterprise.yml b/data/registry/collector-receiver-splunkenterprise.yml index 9bbda4f74ab9..dee59b9dae73 100644 --- a/data/registry/collector-receiver-splunkenterprise.yml +++ b/data/registry/collector-receiver-splunkenterprise.yml @@ -8,6 +8,9 @@ tags: - collector license: Apache 2.0 description: + The Splunk Enterprise Receiver is a pull based tool which enables the + ingestion of performance metrics describing the operational status of a user's + Splunk Enterprise deployment to an appropriate observability tool. authors: - name: OpenTelemetry Authors urls: diff --git a/data/registry/exporter-dotnet-prometheus-aspnetcore.yml b/data/registry/exporter-dotnet-prometheus-aspnetcore.yml index 2b1f98c96890..ff19ad374680 100644 --- a/data/registry/exporter-dotnet-prometheus-aspnetcore.yml +++ b/data/registry/exporter-dotnet-prometheus-aspnetcore.yml @@ -8,7 +8,6 @@ tags: - dotnet - core - exporter - - dotnet license: Apache 2.0 description: An OpenTelemetry Prometheus exporter for configuring an ASP.NET Core diff --git a/data/registry/instrumentation-js-dataloader.yml b/data/registry/instrumentation-js-dataloader.yml index beaa9f3b65bc..0110e127beac 100644 --- a/data/registry/instrumentation-js-dataloader.yml +++ b/data/registry/instrumentation-js-dataloader.yml @@ -6,6 +6,8 @@ tags: - instrumentation - js license: Apache 2.0 +authors: + - name: OpenTelemetry Authors description: This module provides an instrumentation library for the injection of trace context to dataloader diff --git a/data/registry/instrumentation-js-nextjs.yml b/data/registry/instrumentation-js-nextjs.yml index e4c96168c71a..5023692f76fe 100644 --- a/data/registry/instrumentation-js-nextjs.yml +++ b/data/registry/instrumentation-js-nextjs.yml @@ -10,7 +10,7 @@ license: MIT description: Next.js supports OpenTelemetry instrumentation out of the box. authors: - name: Vercel, Inc. - url: https://github.com/vercel/ + url: https://github.com/vercel urls: repo: https://github.com/vercel/next.js/tree/canary/packages/next/src/server/lib/trace docs: https://nextjs.org/docs/app/building-your-application/optimizing/open-telemetry diff --git a/data/registry/instrumentation-js-redis-4.yml b/data/registry/instrumentation-js-redis-4.yml index 649cad42a6b7..858bfb18358d 100644 --- a/data/registry/instrumentation-js-redis-4.yml +++ b/data/registry/instrumentation-js-redis-4.yml @@ -6,6 +6,8 @@ tags: - instrumentation - js license: Apache 2.0 +authors: + - name: OpenTelemetry Authors description: This module provides automatic instrumentation for the `redis@^4.0.0` package. urls: diff --git a/data/registry/otel-lua.yml b/data/registry/otel-lua.yml index 7b34db0de486..3172bfc786d9 100644 --- a/data/registry/otel-lua.yml +++ b/data/registry/otel-lua.yml @@ -9,6 +9,7 @@ license: Apache 2.0 description: An unofficial implementation of OpenTelemetry in lua. authors: - name: yangxikun + url: https://github.com/yangxikun urls: repo: https://github.com/yangxikun/opentelemetry-lua createdAt: 2022-12-17 diff --git a/data/registry/otel-ocaml.yml b/data/registry/otel-ocaml.yml index dec67fb84a83..f0c897aade18 100644 --- a/data/registry/otel-ocaml.yml +++ b/data/registry/otel-ocaml.yml @@ -11,7 +11,7 @@ description: several exporters to talk to signal collectors authors: - name: Imandra - url: https://imandra.ai + url: https://github.com/imandra-ai urls: repo: https://github.com/imandra-ai/ocaml-opentelemetry/ createdAt: 2022-10-26 diff --git a/data/registry/tools-ansible-grafana.yml b/data/registry/tools-ansible-grafana.yml index f6ecf53516f4..d7da4ff225d8 100644 --- a/data/registry/tools-ansible-grafana.yml +++ b/data/registry/tools-ansible-grafana.yml @@ -12,7 +12,7 @@ description: multiple machines authors: - name: Ishan Jain - url: https://www.github.com/ishanjainn + url: https://github.com/ishanjainn - name: Grafana Labs urls: repo: https://github.com/grafana/grafana-ansible-collection/tree/main/roles/opentelemetry_collector diff --git a/data/registry/tools-cpp-conan.yml b/data/registry/tools-cpp-conan.yml index 779f481d720f..a7d81e2dc953 100644 --- a/data/registry/tools-cpp-conan.yml +++ b/data/registry/tools-cpp-conan.yml @@ -9,7 +9,7 @@ tags: license: MIT description: Conan package for `opentelemetry-cpp`. authors: - - name: + - name: The conan authors urls: repo: https://conan.io/center/recipes/opentelemetry-cpp createdAt: 2023-02-13 diff --git a/data/registry/tools-cpp-vcpkg.yml b/data/registry/tools-cpp-vcpkg.yml index 3aee04cf2125..187d5e97490e 100644 --- a/data/registry/tools-cpp-vcpkg.yml +++ b/data/registry/tools-cpp-vcpkg.yml @@ -9,7 +9,7 @@ tags: license: MIT description: A vcpkg package for opentelemetry-cpp. authors: - - name: + - name: The vcpkg Authors urls: repo: https://github.com/microsoft/vcpkg/tree/master/ports/opentelemetry-cpp createdAt: 2023-02-13 diff --git a/data/registry/tools-python-resourcedetector-gcp.yml b/data/registry/tools-python-resourcedetector-gcp.yml index c8cf3c52ab1e..615b94f6c27d 100644 --- a/data/registry/tools-python-resourcedetector-gcp.yml +++ b/data/registry/tools-python-resourcedetector-gcp.yml @@ -10,9 +10,9 @@ tags: license: Apache 2.0 description: > This library provides support for detecting GCP resources like GCE, GKE, etc. - authors: - name: Google + url: https://github.com/GoogleCloudPlatform urls: repo: https://github.com/GoogleCloudPlatform/opentelemetry-operations-python/tree/main/opentelemetry-resourcedetector-gcp createdAt: 2020-08-13 diff --git a/data/registry/tools-rust-aws.yml b/data/registry/tools-rust-aws.yml index 85896238a5c7..ca65a518a824 100644 --- a/data/registry/tools-rust-aws.yml +++ b/data/registry/tools-rust-aws.yml @@ -11,7 +11,6 @@ tags: license: Apache 2.0 authors: - name: OpenTelemetry Authors - urls: repo: https://github.com/open-telemetry/opentelemetry-rust-contrib/tree/main/opentelemetry-aws createdAt: 2021-02-08 diff --git a/gulp-src/validate-registry.js b/gulp-src/validate-registry.js new file mode 100644 index 000000000000..f2ff5553dbdf --- /dev/null +++ b/gulp-src/validate-registry.js @@ -0,0 +1,184 @@ +const gulp = require('gulp'); +const { taskArgs } = require('./_util'); +const through2 = require('through2'); +const yaml = require('js-yaml'); +const Ajv = require('ajv'); +const addFormats = require('ajv-formats'); +const addErrors = require('ajv-errors'); + +const defaultGlobs = ['data/registry/*.yml']; + +let numFilesProcessed = 0, + numFilesWithIssues = 0; + +const schema = require('../data/registry-schema.json'); +const ajv = new Ajv({ + allErrors: true, +}); +addFormats(ajv); +addErrors(ajv); +const validate = ajv.compile(schema); + +function logFiles(debug) { + return through2.obj(function (file, enc, cb) { + if (debug) { + console.log('Processing file:', file.path); + } + cb(null, file); + }); +} + +function createSourceMap(yamlText, yamlData) { + const lines = yamlText.split('\n'); + const sourceMap = {}; + + function traverse(node, path = '', startLine = 0) { + if (Array.isArray(node)) { + node.forEach((item, index) => { + const newPath = `${path}/${index}`; + const line = findArrayElementLine(lines, path, index, startLine); + if (line !== -1) { + sourceMap['/' + newPath] = line + 1; // line numbers are 1-based + traverse(item, newPath, line); + } + }); + } else if (typeof node === 'object' && node !== null) { + for (const key in node) { + const newPath = path ? `${path}/${key}` : key; + const line = findKeyLine(lines, key, startLine); + if (line !== -1) { + sourceMap['/' + newPath] = line + 1; // line numbers are 1-based + traverse(node[key], newPath, line + 1); + } + } + } else if (node === null) { + sourceMap['/' + path] = startLine; + } + } + + function findKeyLine(lines, key, startLine) { + const regex = new RegExp(`^\\s*-?\\s*${key}:`); + for (let i = startLine; i < lines.length; i++) { + if (regex.test(lines[i])) { + return i; + } + } + return -1; + } + + function findArrayElementLine(lines, path, index, startLine) { + // const parentPath = path ? path.split('.').join('\\.') : ''; + const regex = new RegExp(`^\\s*-`); + let currentIndex = -1; + for (let i = startLine; i < lines.length; i++) { + const trimmedLine = lines[i].trim(); + if (regex.test(trimmedLine)) { + currentIndex++; + if (currentIndex === index) { + return i; + } + } + } + return -1; + } + traverse(yamlData); + return sourceMap; +} + +function getLineNumber(sourceMap, yamlPath) { + return sourceMap[yamlPath] || null; +} + +function validateRegistryEntry(file, enc, cb) { + const registryEntry = yaml.load(file.contents.toString(), { + schema: yaml.JSON_SCHEMA, + }); + + const sourceMap = createSourceMap(file.contents.toString(), registryEntry); + + const valid = validate(registryEntry); + if (!valid) { + // some validation issues let to warning/notices not errors, so we need to check for those + let hasErrors = false; + for (const error of validate.errors) { + const lineNumber = getLineNumber(sourceMap, error.instancePath); + + if (!lineNumber) { + console.log(registryEntry); + console.log(yaml.load(file.contents.toString())); + console.log(sourceMap); + console.log(error.instancePath); + } + + let logLevel = 'error'; + + if (error.message === 'An author must have an email or a URL') { + logLevel = 'warning'; + } else if (error.message === 'must match "else" schema') { + logLevel = 'notice'; + } else { + // Real error, this counts! + hasErrors = true; + } + + if (process.env.GITHUB_ACTIONS) { + console.log( + `::${logLevel} file=${file.path},line=${lineNumber},endLine=${lineNumber},title=Registry Schema Validation::${error.message}`, + ); + } else { + console.log(error); + console.error( + `${logLevel} in ${file.path}:${lineNumber}: ${error.message}`, + ); + } + } + if (hasErrors) { + numFilesWithIssues++; + } + } + numFilesProcessed++; + cb(null, file); +} + +function validateRegistry() { + const argv = taskArgs().options({ + glob: { + alias: 'g', + type: 'array', + description: + 'Globs of files to run through json schema validation. List flag more than once for multiple values.', + default: defaultGlobs, + }, + debug: { + type: 'boolean', + description: 'Output debugging information.', + default: false, + }, + }).argv; + + if (argv.info) { + // Info about options was already displayed by yargs.help(). + return Promise.resolve(); + } + + const globs = argv.glob; + if (argv.debug) { + console.log('Globs being used:', globs); + } + + return gulp + .src(globs, { followSymlinks: false }) + .pipe(logFiles(argv.debug)) + .pipe(through2.obj(validateRegistryEntry)) + .on('end', () => { + const fileOrFiles = 'file' + (numFilesProcessed == 1 ? '' : 's'); + const msg = `Processed ${numFilesProcessed} ${fileOrFiles}, ${numFilesWithIssues} had issues.`; + if (numFilesWithIssues > 0) { + throw new Error(msg); + } else { + console.log(msg); + } + }); +} + +gulp.task('validate-registry', validateRegistry); diff --git a/package.json b/package.json index 30fe93774f64..61987ab8ccc8 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "_check:links:internal": "npm run __check:links", "_check:links": "HTMLTEST_ARGS='--log-level 1' npm run __check:links", "_check:markdown": "npx gulp lint-md", + "_check:registry": "npx gulp validate-registry", "_check:text": "npx textlint content data `ls *.md`", "_diff:check": "git diff --name-only --exit-code", "_diff:fail": "git diff --name-only --exit-code || exit 1", @@ -45,6 +46,7 @@ "check:links:internal": "npm run _check:links:internal", "check:links": "npm run _check:links", "check:markdown": "scripts/check-markdown-wrapper.sh", + "check:registry": "npm run _check:registry", "check:spelling": "npx cspell --no-progress -c .cspell.yml content/en data 'layouts/**/*.md'", "check:text": "npm run _check:text -- ", "check": "npm run seq -- $(npm run -s _list:check:*)", @@ -103,9 +105,14 @@ "update:submodule": "set -x && git submodule update --remote ${DEPTH:- --depth 1}" }, "devDependencies": { + "ajv": "^8.16.0", + "ajv-errors": "^3.0.0", + "ajv-formats": "^3.0.1", "autoprefixer": "^10.4.19", "cspell": "^8.12.1", "gulp": "^5.0.0", + "hugo-extended": "0.128.0", + "js-yaml": "^4.1.0", "hugo-extended": "0.129.0", "markdown-link-check": "^3.12.2", "markdownlint": "^0.34.0", diff --git a/static/refcache.json b/static/refcache.json index 18dda0d765c1..ada945d393e9 100644 --- a/static/refcache.json +++ b/static/refcache.json @@ -2299,6 +2299,10 @@ "StatusCode": 200, "LastSeen": "2024-01-18T19:37:06.447831-05:00" }, + "https://github.com/GoogleCloudPlatform": { + "StatusCode": 200, + "LastSeen": "2024-07-08T15:23:37.97537+02:00" + }, "https://github.com/GoogleCloudPlatform/guest-agent": { "StatusCode": 200, "LastSeen": "2024-01-18T08:06:57.71306-05:00" @@ -2863,6 +2867,10 @@ "StatusCode": 200, "LastSeen": "2024-05-08T10:07:06.998803+02:00" }, + "https://github.com/imandra-ai": { + "StatusCode": 200, + "LastSeen": "2024-07-05T23:26:58.569631+02:00" + }, "https://github.com/imandra-ai/ocaml-opentelemetry/": { "StatusCode": 200, "LastSeen": "2024-01-18T19:13:40.755331-05:00" @@ -4891,6 +4899,10 @@ "StatusCode": 200, "LastSeen": "2024-01-18T19:55:45.931119-05:00" }, + "https://github.com/vercel": { + "StatusCode": 200, + "LastSeen": "2024-07-05T23:26:49.504427+02:00" + }, "https://github.com/vercel/": { "StatusCode": 200, "LastSeen": "2024-01-08T12:17:24.045228+01:00" @@ -4915,6 +4927,10 @@ "StatusCode": 200, "LastSeen": "2024-01-18T20:05:15.591448-05:00" }, + "https://github.com/yangxikun": { + "StatusCode": 200, + "LastSeen": "2024-07-08T15:23:32.918333+02:00" + }, "https://github.com/yangxikun/opentelemetry-lua": { "StatusCode": 200, "LastSeen": "2024-01-18T19:13:35.287029-05:00"