diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml new file mode 100644 index 00000000000..967e33848a5 --- /dev/null +++ b/.github/workflows/build-and-test.yml @@ -0,0 +1,127 @@ +name: build-and-test +on: + push: + branches: [master] + tags: + - v[0-9]+.[0-9]+.[0-9]+ + pull_request: + +jobs: + windows-test: + runs-on: windows-latest + env: + TEST_RESULTS: testbed/tests/results/junit/results.xml + steps: + - name: Checkout Repo + uses: actions/checkout@v2 + - name: Setup Go + uses: actions/setup-go@v2.1.3 + with: + go-version: 1.15 + - name: Setup env + run: | + echo "GOPATH=$(go env GOPATH)" >> $GITHUB_ENV + echo "$(go env GOPATH)/bin" >> $GITHUB_PATH + shell: bash + - name: Restore module cache + uses: actions/cache@v2 + env: + cache-name: cache-go-modules + with: + path: \Users\runneradmin\go\pkg\mod + key: go-pkg-mod-${{ runner.os }}-${{ hashFiles('./go.mod') }} + - name: Run Unit tests + run: go test ./... + - name: GitHub issue generator + if: ${{ failure() && github.ref == 'ref/head/master' }} + run: | + go run cmd/issuegenerator/main.go $TEST_RESULTS + setup-environment: + runs-on: ubuntu-latest + steps: + - name: Checkout Repo + uses: actions/checkout@v2 + - name: Setup Go + uses: actions/setup-go@v2.1.3 + with: + go-version: 1.15 + - name: Setup env + run: | + echo "GOPATH=$(go env GOPATH)" >> $GITHUB_ENV + echo "$(go env GOPATH)/bin" >> $GITHUB_PATH + - name: Install tools + run: make install-tools + - name: Upload tool binaries + uses: actions/upload-artifact@v2 + with: + name: tool-binaries + path: /home/runner/go/bin + lint: + runs-on: ubuntu-latest + needs: [setup-environment] + steps: + - name: Checkout Repo + uses: actions/checkout@v2 + - name: Setup Go + uses: actions/setup-go@v2.1.3 + with: + go-version: 1.15 + - name: Setup env + run: | + echo "GOPATH=$(go env GOPATH)" >> $GITHUB_ENV + echo "$(go env GOPATH)/bin" >> $GITHUB_PATH + - name: Restore module cache + uses: actions/cache@v2 + env: + cache-name: cache-go-modules + with: + path: /home/runner/go/pkg/mod + key: go-pkg-mod-${{ runner.os }}-${{ hashFiles('./go.mod') }} + - name: Download tool binaries + uses: actions/download-artifact@v2 + with: + name: tool-binaries + path: /home/runner/go/bin + - name: Add execute permissions to tool binaries + run: chmod -R +x /home/runner/go/bin + - name: Lint + run: make -j4 gochecklicense goimpi golint gomisspell + - name: Gen Metadata + run: make genmdata + - name: Check generated metadata + run: git diff --exit-code || (echo 'Generated code is out of date, please run "make genmdata" and commit the changes in this PR.' && exit 1) + cross-compile: + runs-on: ubuntu-latest + needs: [setup-environment] + steps: + - name: Checkout Repo + uses: actions/checkout@v2 + - name: Setup Go + uses: actions/setup-go@v2.1.3 + with: + go-version: 1.15 + - name: Setup env + run: | + echo "GOPATH=$(go env GOPATH)" >> $GITHUB_ENV + echo "$(go env GOPATH)/bin" >> $GITHUB_PATH + - name: Restore module cache + uses: actions/cache@v2 + env: + cache-name: cache-go-modules + with: + path: /home/runner/go/pkg/mod + key: go-pkg-mod-${{ runner.os }}-${{ hashFiles('./go.mod') }} + - name: Download tool binaries + uses: actions/download-artifact@v2 + with: + name: tool-binaries + path: /home/runner/go/bin + - name: Add execute permissions to tool binaries + run: chmod -R +x /home/runner/go/bin + - name: Build collector for all archs + run: grep ^binaries-all-sys Makefile|fmt -w 1|tail -n +2|xargs make + - name: Upload collector binaries + uses: actions/upload-artifact@v2 + with: + name: collector-binaries + path: ./bin diff --git a/.github/workflows/check-links.yaml b/.github/workflows/check-links.yaml new file mode 100644 index 00000000000..b2baf53a689 --- /dev/null +++ b/.github/workflows/check-links.yaml @@ -0,0 +1,24 @@ +name: check-links +on: + push: + branches: [master] + pull_request: + +jobs: + check-links: + runs-on: ubuntu-latest + steps: + - name: Checkout Repo + uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Setup Node + uses: actions/setup-node@v2-beta + with: + node-version: '9' + - name: Check links in markdown docs + run: | + pushd $HOME + npm install --save-dev markdown-link-check@3.8.1 + popd + - run: ./.github/workflows/check-links/check-links.sh diff --git a/.github/workflows/check-links/README.md b/.github/workflows/check-links/README.md new file mode 100644 index 00000000000..182ee8fac80 --- /dev/null +++ b/.github/workflows/check-links/README.md @@ -0,0 +1,11 @@ +# Markdown Link Checker + +Check links in markdown docs based on +[`markdown-link-check`](https://github.com/tcort/markdown-link-check). +The app in this repo has been updated to ignore 429 responses (too many +requests). + +Update [config.json](./config.json) to exclude specific links from being +checked (e.g. examples or links requiring authentication). See +[here](https://github.com/tcort/markdown-link-check/blob/master/README.md#config-file-format) +for details and other options. diff --git a/.github/workflows/check-links/check-links.sh b/.github/workflows/check-links/check-links.sh new file mode 100755 index 00000000000..c3790184c01 --- /dev/null +++ b/.github/workflows/check-links/check-links.sh @@ -0,0 +1,40 @@ +#!/bin/bash + +# Copyright The OpenTelemetry Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -eu + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +REPO_DIR="$( cd "${SCRIPT_DIR}/../../../" && pwd )" +GITHUB_REF=${GITHUB_REF:-} + +diff_files="$( git diff HEAD origin/master --name-only )" +check_all_files=1 +if [[ "$GITHUB_REF" = "ref/heads/master" ]] || [[ -n "$( echo "$diff_files" | grep ".github/workflows/check-links" )" ]]; then + check_all_files=0 +fi + +nfailed=0 + +# check all docs in master/tags or new/modified docs in PR +for md in $(find "$REPO_DIR" -name "*.md" | sort); do + if [[ $check_all_files ]] || [[ -n "$( echo "$diff_files" | grep "^${md/#$REPO_DIR\//}" )" ]]; then + node $SCRIPT_DIR/markdown-link-check -c ${SCRIPT_DIR}/config.json -v "$md" || (( nfailed += $? )) + # wait to scan files so that we don't overload github with requests which may result in 429 responses + sleep 2 + fi +done + +exit $nfailed diff --git a/.github/workflows/check-links/config.json b/.github/workflows/check-links/config.json new file mode 100644 index 00000000000..6119b53355c --- /dev/null +++ b/.github/workflows/check-links/config.json @@ -0,0 +1,13 @@ +{ + "ignorePatterns": [ + { + "pattern": "http(s)?://\\d+\\.\\d+\\.\\d+\\.\\d+" + }, + { + "pattern": "http(s)?://localhost" + }, + { + "pattern": "http(s)?://example.com" + } + ] +} diff --git a/.github/workflows/check-links/markdown-link-check b/.github/workflows/check-links/markdown-link-check new file mode 100755 index 00000000000..7e1c8e13273 --- /dev/null +++ b/.github/workflows/check-links/markdown-link-check @@ -0,0 +1,165 @@ +#!/usr/bin/env node + +'use strict'; + +const chalk = require('chalk'); +const fs = require('fs'); +const markdownLinkCheck = require('markdown-link-check'); +const program = require('commander'); +const request = require('request'); +const url = require('url'); +const path = require('path'); + +const statusLabels = { + alive: chalk.green('✓'), + dead: chalk.red('✖'), + ignored: chalk.gray('/'), + error: chalk.yellow('⚠'), +}; + +const opts = {}; +let filenameForOutput = ''; +let stream = process.stdin; // read from stdin unless a filename is given + +program + .option('-p, --progress', 'show progress bar') + .option('-c, --config [config]', 'apply a config file (JSON), holding e.g. url specific header configuration') + .option('-q, --quiet', 'displays errors only') + .option('-v, --verbose', 'displays detailed error information') + .arguments('[filenameOrUrl]') + .action(function (filenameOrUrl) { + filenameForOutput = filenameOrUrl; + if (/https?:/.test(filenameOrUrl)) { + request(filenameOrUrl, function (error, response, body) { + if (error) { + console.error(chalk.red('\nERROR: Unable to connect! Please provide a valid URL as an argument.')); + process.exit(1); + } + else if (response.statusCode === 404){ + console.error(chalk.red('\nERROR: 404 - File not found! Please provide a valid URL as an argument.')); + process.exit(1); + } else { + stream = request.get(filenameOrUrl); + } + + }); + try { // extract baseUrl from supplied URL + const parsed = url.parse(filenameOrUrl); + delete parsed.search; + delete parsed.hash; + if (parsed.pathname.lastIndexOf('/') !== -1) { + parsed.pathname = parsed.pathname.substr(0, parsed.pathname.lastIndexOf('/') + 1); + } + opts.baseUrl = url.format(parsed); + } catch (err) { /* ignore error */ + } + } else { + fs.stat(filenameOrUrl, function(error , stats){ + if (!error && stats.isDirectory()){ + console.error(chalk.red('\nERROR: ' + filenameOrUrl + ' is a directory! Please provide a vaild filename as an argument.')); + process.exit(1); + } + }); + opts.baseUrl = 'file://' + path.dirname(path.resolve(filenameOrUrl)); + stream = fs.createReadStream(filenameOrUrl); + } + +}).parse(process.argv); + +opts.showProgressBar = (program.progress === true); // force true or undefined to be true or false. +opts.quiet = (program.quiet === true); +opts.verbose = (program.verbose === true); + +let markdown = ''; // collect the markdown data, then process it + +stream + .on('data', function (chunk) { + markdown += chunk.toString(); + }) + .on('error', function(error) { + if (error.code === 'ENOENT') { + console.error(chalk.red('\nERROR: File not found! Please provide a vaild filename as an argument.')); + } else { + console.error(chalk.red(error)); + } + return process.exit(1); + }) + .on('end', function () { + if (filenameForOutput) { + console.log(chalk.cyan('\nFILE: ' + filenameForOutput)); + } + + if (program.config) { + fs.access(program.config, (fs.constants || fs).R_OK, function (err) { + if (!err) { + let configStream = fs.createReadStream(program.config); + let configData = ''; + + configStream.on('data', function (chunk) { + configData += chunk.toString(); + }).on('end', function () { + let config = JSON.parse(configData); + + opts.ignorePatterns = config.ignorePatterns; + opts.replacementPatterns = config.replacementPatterns; + opts.httpHeaders = config.httpHeaders; + opts.ignoreDisable = config.ignoreDisable; + + runMarkdownLinkCheck(markdown, opts); + }); + } + else { + console.error(chalk.red('\nERROR: Config file not accessible.')); + process.exit(1); + } + }); + } + else { + runMarkdownLinkCheck(markdown, opts); + } +}); + +function runMarkdownLinkCheck(markdown, opts) { + markdownLinkCheck(markdown, opts, function (err, results) { + if (err) { + console.error(chalk.red('\nERROR: something went wrong!')); + console.error(err.stack); + process.exit(1); + } + + if (results.length === 0 && !opts.quiet) { + console.log(chalk.yellow('No hyperlinks found!')); + } + results.forEach(function (result) { + // workaround to ignore 429 responses (too many requests) + if (result.statusCode === 429) { + result.status = 'ignored' + } + + // Skip messages for non-deadlinks in quiet mode. + if (opts.quiet && result.status !== 'dead') { + return; + } + + if (opts.verbose) { + if (result.err) { + console.log('[%s] %s → Status: %s %s', statusLabels[result.status], result.link, result.statusCode, result.err); + } else { + console.log('[%s] %s → Status: %s', statusLabels[result.status], result.link, result.statusCode); + } + } + else { + console.log('[%s] %s', statusLabels[result.status], result.link); + } + }); + console.log('\n%s links checked.', results.length); + if (results.some((result) => result.status === 'dead')) { + let deadLinks = results.filter(result => { return result.status === 'dead'; }); + console.error(chalk.red('\nERROR: %s dead links found!'), deadLinks.length); + deadLinks.forEach(function (result) { + console.log('[%s] %s → Status: %s', statusLabels[result.status], result.link, result.statusCode); + }); + process.exit(1); + } + }); +}