diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..249c0ae --- /dev/null +++ b/.dockerignore @@ -0,0 +1,11 @@ +.vscode +.idea +.git +Dockerfile +*.md +/dist +/images +/.github +/tmp +target +debug diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..5ab924d --- /dev/null +++ b/.editorconfig @@ -0,0 +1,14 @@ +root = true + +[*] +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.go] +indent_style = tab + +[*.{rs,sh}] +indent_style = space +indent_size = 4 diff --git a/.errcheck_excludes.txt b/.errcheck_excludes.txt new file mode 100644 index 0000000..306a6f6 --- /dev/null +++ b/.errcheck_excludes.txt @@ -0,0 +1 @@ +(github.com/go-kit/log.Logger).Log diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..5f2a048 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,74 @@ +name: Build + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +env: + # renovate: datasource=go depName=mvdan.cc/gofumpt + GOFUMPT_VERSION: v0.3.1 + # renovate: datasource=go depName=github.com/golangci/golangci-lint + GOLANGCI_LINT_VERSION: v1.50.1 + +jobs: + skip-check: + name: Skip check + continue-on-error: true + runs-on: ubuntu-latest + outputs: + should_skip: ${{ steps.skip-check.outputs.should_skip }} + permissions: + actions: write + contents: read + steps: + - id: skip-check + uses: fkirc/skip-duplicate-actions@9d116fa7e55f295019cfab7e3ab72b478bcf7fdd # tag=v4.0.0 + with: + do_not_skip: '["schedule", "workflow_dispatch"]' + paths: |- + [ + "**.go", + ".github/workflows/build.yml", + "Makefile", + "go.mod", + "go.sum" + ] + skip_after_successful_duplicate: false + + go-build-test: + name: Go Build + runs-on: ubuntu-latest + needs: skip-check + if: ${{ needs.skip-check.outputs.should_skip != 'true' }} + steps: + - name: Check out the code + uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # tag=v3.0.2 + + - name: Set up Go + uses: actions/setup-go@84cbf8094393cdc5fe1fe1671ff2647332956b1a # tag=v3.2.1 + with: + go-version-file: 'go.mod' + check-latest: true + cache: true + + - name: Build + run: make build + + - name: Test + run: | + make test + + - name: Format + run: | + make format + git diff --exit-code + + - name: Vet + run: make vet + + - name: Lint + uses: golangci/golangci-lint-action@0ad9a0988b3973e851ab0a07adf248ec2e100376 # tag=v3.3.1 + with: + version: ${{ env.GOLANGCI_LINT_VERSION }} diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 0000000..b9c213c --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,71 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ main ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ main ] + schedule: + - cron: '28 16 * * 3' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'go' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] + # Learn more: + # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed + + steps: + - name: Check out the code + uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # tag=v3.0.2 + + - name: Set up Go + uses: actions/setup-go@84cbf8094393cdc5fe1fe1671ff2647332956b1a # tag=v3.2.1 + with: + go-version-file: 'go.mod' + cache: true + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@0c670bbf0414f39666df6ce8e718ec5662c21e03 # tag=v2.1.17 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + # - name: Autobuild + # uses: github/codeql-action/autobuild@v1 + + # ℹī¸ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + - run: make build + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@0c670bbf0414f39666df6ce8e718ec5662c21e03 # tag=v2.1.17 diff --git a/.github/workflows/container.yml b/.github/workflows/container.yml new file mode 100644 index 0000000..0c1b43e --- /dev/null +++ b/.github/workflows/container.yml @@ -0,0 +1,135 @@ +name: Container + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +env: + # renovate: datasource=go depName=github.com/goreleaser/goreleaser + GORELEASER_VERSION: v1.10.2 + +jobs: + skip-check: + name: Skip check + continue-on-error: true + runs-on: ubuntu-latest + outputs: + should_skip: ${{ steps.skip-check.outputs.should_skip }} + permissions: + actions: write + contents: read + steps: + - id: skip-check + uses: fkirc/skip-duplicate-actions@9d116fa7e55f295019cfab7e3ab72b478bcf7fdd # tag=v4.0.0 + with: + do_not_skip: '["schedule", "workflow_dispatch"]' + paths: |- + [ + "**.go", + ".dockerignore", + ".github/workflows/container.yml", + "Dockerfile*", + "Makefile", + "go.mod", + "go.sum" + ] + skip_after_successful_duplicate: false + + + build-binaries: + name: Build binaries using goreleaser + needs: skip-check + if: ${{ needs.skip-check.outputs.should_skip != 'true' }} + runs-on: ubuntu-latest + container: + image: docker.io/goreleaser/goreleaser-cross:v1.18.3 + options: --privileged + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GORELEASER_CURRENT_TAG: "${{ env.goreleaser_current_tag }}" + steps: + - name: Check out the code + uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # tag=v3.0.2 + + - name: Set up Go + uses: actions/setup-go@84cbf8094393cdc5fe1fe1671ff2647332956b1a # tag=v3.2.1 + with: + go-version-file: 'go.mod' + cache: true + + - name: Run Goreleaser + run: goreleaser release --rm-dist --skip-validate --skip-publish --snapshot --debug + + - name: Archive generated artifacts + uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8 # tag=v3.1.0 + with: + name: parca-push-dist-container + if-no-files-found: error + path: | + goreleaser/dist + !goreleaser/dist/*.txt + + build-and-push-container: + name: Container build and push (when merged) + needs: build-binaries + runs-on: ubuntu-latest + container: + # https://github.com/containers/podman/tree/main/contrib/podmanimage + # Specifying SHA repeatedly fails: + # @sha256:421ac576cebff98e90c531e7b9ce4482370ecc7cee59abc2341714031bfb5f43 + image: quay.io/containers/podman:v4.1.1 + options: >- + --device /dev/fuse:rw + --privileged + --security-opt label=disable + --security-opt seccomp=unconfined + permissions: + id-token: write + packages: write + contents: read + steps: + - name: Install dependencies + run: dnf install --assumeyes --repo fedora git make jq + + - name: Check out code into the Go module directory + uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # tag=v3.0.2 + + - name: Set up Go + uses: actions/setup-go@84cbf8094393cdc5fe1fe1671ff2647332956b1a # tag=v3.2.1 + with: + go-version-file: 'go.mod' + check-latest: true + + - uses: actions/download-artifact@v3 + with: + name: parca-push-dist-container + path: goreleaser/dist + + - name: Build container + run: make container + + - name: Check images are created + run: podman images | grep 'ghcr.io/parca-dev/parca-push' + + - name: Install cosign + if: ${{ github.event_name != 'pull_request' }} + uses: sigstore/cosign-installer@09a077b27eb1310dcfb21981bee195b30ce09de0 # tag=v2.5.0 + + - name: Login to registry + if: ${{ github.event_name != 'pull_request' }} + run: | + echo "${{ secrets.GITHUB_TOKEN }}" | podman login -u parca-dev --password-stdin ghcr.io + + - name: Install crane + if: ${{ github.event_name != 'pull_request' }} + uses: imjasonh/setup-crane@e82f1b9a8007d399333baba4d75915558e9fb6a4 # tag=v0.2 + + - name: Push and sign container + if: ${{ github.event_name != 'pull_request' }} + env: + COSIGN_EXPERIMENTAL: true + run: | + make push-container + make sign-container diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..7beeba8 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,58 @@ +name: Documents + +on: + push: + branches: [ main ] + paths: + pull_request: + branches: [ main ] + +jobs: + skip-check: + name: Skip check + continue-on-error: true + runs-on: ubuntu-latest + outputs: + should_skip: ${{ steps.skip-check.outputs.should_skip }} + permissions: + actions: write + contents: read + steps: + - id: skip-check + uses: fkirc/skip-duplicate-actions@9d116fa7e55f295019cfab7e3ab72b478bcf7fdd # tag=v4.0.0 + with: + do_not_skip: '["schedule", "workflow_dispatch"]' + paths: |- + [ + ".github/workflows/docs.yml", + "Makefile", + "cmd/parca-push/main.go", + "docs" + ] + skip_after_successful_duplicate: false + + docs: + name: Generate documentation + needs: skip-check + if: ${{ needs.skip-check.outputs.should_skip != 'true' }} + runs-on: ubuntu-latest + steps: + - name: Check out the code + uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # tag=v3.0.2 + + - name: Set up Go + uses: actions/setup-go@84cbf8094393cdc5fe1fe1671ff2647332956b1a # tag=v3.2.1 + with: + go-version-file: 'go.mod' + cache: true + + - name: Set up tools + run: ./env.sh + + - name: Build + run: make build + + - name: Docs check + run: | + make README.md + git diff --exit-code diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..5970a47 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,130 @@ +name: Release + +on: + push: + tags: + - v* + +permissions: + contents: write + +env: + # renovate: datasource=go depName=github.com/goreleaser/goreleaser + GORELEASER_VERSION: v1.10.2 + +jobs: + binaries: + name: Goreleaser release + runs-on: ubuntu-latest + if: startsWith(github.ref, 'refs/tags/') + container: + image: docker.io/goreleaser/goreleaser-cross:v1.18.3 + options: --privileged + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: + - name: Check out the code + uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # tag=v3.0.2 + with: + fetch-depth: 0 + + - name: Set up Go + uses: actions/setup-go@84cbf8094393cdc5fe1fe1671ff2647332956b1a # tag=v3.2.1 + with: + go-version-file: 'go.mod' + cache: true + + - name: Fetch all tags + run: git fetch --force --tags + + - name: Run Goreleaser + run: goreleaser release --rm-dist --debug + + - name: Archive generated artifacts + uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8 # tag=v3.1.0 + with: + name: parca-push-dist-release + if-no-files-found: error + path: | + goreleaser/dist + !goreleaser/dist/*.txt + + docs: + name: Publish Docs + runs-on: ubuntu-latest + needs: binaries + steps: + - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # tag=v3.1.0 + + - name: Publish Vercel + run: | + curl -X POST "https://api.vercel.com/v1/integrations/deploy/${{ secrets.VERCEL_WEBHOOK }}" + + container: + name: Build and release container images + runs-on: ubuntu-latest + needs: binaries + container: + # https://github.com/containers/podman/tree/main/contrib/podmanimage + # Specifying SHA repeatedly fails: + # @sha256:421ac576cebff98e90c531e7b9ce4482370ecc7cee59abc2341714031bfb5f43 + image: quay.io/containers/podman:v4.1.1 + options: >- + --device /dev/fuse:rw + --privileged + --security-opt label=disable + --security-opt seccomp=unconfined + permissions: + id-token: write + packages: write + contents: read + steps: + - name: Install dependencies + run: dnf install --assumeyes --repo fedora git make jq + + - name: Check out code into the Go module directory + uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # tag=v3.0.2 + + - name: Set up Go + uses: actions/setup-go@84cbf8094393cdc5fe1fe1671ff2647332956b1a # tag=v3.2.1 + with: + go-version-file: 'go.mod' + check-latest: true + + - name: Get branch name + shell: bash + run: echo "GITHUB_BRANCH_NAME=$(echo ${GITHUB_REF#refs/heads/} | tr / -)" >> $GITHUB_ENV + + - uses: actions/download-artifact@v3 + with: + name: parca-push-dist-release + path: goreleaser/dist + + - name: Build container + run: make container + + - name: Check images are created + run: podman images | grep 'ghcr.io/parca-dev/parca-push' + + - name: Login to registry + if: ${{ github.event_name != 'pull_request' }} + run: | + echo "${{ secrets.PERSONAL_ACCESS_TOKEN }}" | podman login -u parca-dev --password-stdin ghcr.io + + - name: Install cosign + uses: sigstore/cosign-installer@09a077b27eb1310dcfb21981bee195b30ce09de0 # tag=v2.5.0 + + - name: Install crane + if: ${{ github.event_name != 'pull_request' }} + uses: imjasonh/setup-crane@e82f1b9a8007d399333baba4d75915558e9fb6a4 # tag=v0.2 + + - name: Push container + if: ${{ github.event_name != 'pull_request' }} + run: | + make push-container + + - name: Sign container + env: + COSIGN_EXPERIMENTAL: true + run: | + make sign-container diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d264d2b --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +.idea +/goreleaser +/dist +/tmp +/out +/bin +TODO.md diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..5f51e6e --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,70 @@ +run: + deadline: 5m + +linters: + presets: + - bugs + - comment + - error + - format + - import + - metalinter + - performance + - style + - unused + disable: + - exhaustivestruct + - exhaustruct + - funlen + - gci + - gochecknoglobals + - godox + - goerr113 + - golint + - gomnd + - gomoddirectives + - interfacer + - ireturn + - lll + - maligned + - nlreturn + - paralleltest + - scopelint + - testpackage + - varnamelen + - wrapcheck + - wsl + - nosnakecase + +issues: + exclude-rules: + - path: _test.go + linters: + - errcheck + +linters-settings: + depguard: + list-type: blacklist + include-go-root: true + packages-with-error-message: + - sync/atomic: "Use go.uber.org/atomic instead of sync/atomic" + - github.com/stretchr/testify/assert: "Use github.com/stretchr/testify/require instead of github.com/stretchr/testify/assert" + - github.com/go-kit/kit/log: "Use github.com/go-kit/log instead of github.com/go-kit/kit/log" + - github.com/pkg/errors: "Use fmt.Errorf instead" + errcheck: + exclude: ./.errcheck_excludes.txt + goimports: + local-prefixes: github.com/parca-dev/parca-push + gofumpt: + extra-rules: true + misspell: + locale: US + revive: + rules: + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#unexported-return + - name: unexported-return + severity: warning + disabled: true + cyclop: + # The maximal code complexity to report. + max-complexity: 15 diff --git a/.goreleaser.yml b/.goreleaser.yml new file mode 100644 index 0000000..7691984 --- /dev/null +++ b/.goreleaser.yml @@ -0,0 +1,61 @@ +# NOTICE: This file is written with the assumption that it will be used in parca-dev/cross-builder. +# - There are paths in this file that are specific to parca-dev/cross-builder and Github Actions. +# - Unfortunately, Goreleaser does not support templating environment variables per build config. +project_name: parca-push +dist: ./goreleaser/dist +before: + hooks: + - go mod tidy +builds: + - main: ./cmd/parca-push/ + id: "parca-push" + binary: parca-push + # https://goreleaser.com/customization/build/#reproducible-builds + mod_timestamp: '{{ .CommitTimestamp }}' + env: + - CGO_ENABLED=0 + goos: + - linux + - darwin + goarch: + - amd64 + - arm64 + flags: + - -trimpath + - -v + ldflags: + # https://goreleaser.com/customization/build/#reproducible-builds + # {{.CommitDate}} is the date of the commit to make builds reproducible. + - -X main.version={{.Version}} -X main.commit={{.FullCommit}} -X main.date={{.CommitDate}} -X main.goArch={{.Runtime.Goarch}} +archives: + - replacements: + linux: Linux + darwin: Darwin + amd64: x86_64 + format_overrides: + - goos: windows + format: zip +checksum: + name_template: 'checksums.txt' +snapshot: + name_template: "{{ incpatch .Tag }}-next" +release: + prerelease: auto + # Defaults to empty. + footer: | + ## Docker images + + `docker pull ghcr.io/parca-dev/parca-push:{{ .Tag }}` + + ## Thanks! + + Join our [Discord server](https://discord.com/invite/ZgUpYgpzXy); + Follow us on [Twitter](https://twitter.com/ParcaDev); + Read the [documentation](https://www.parca.dev/docs/overview). +changelog: + sort: asc + use: github + filters: + exclude: + - '^docs:' + - '^test:' diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..511353e --- /dev/null +++ b/Dockerfile @@ -0,0 +1,23 @@ +FROM --platform="${BUILDPLATFORM:-linux/amd64}" docker.io/library/busybox:1.35.0 as builder +RUN mkdir /.cache && touch -t 202101010000.00 /.cache + +ARG TARGETOS=linux +ARG TARGETARCH=amd64 +ARG TARGETVARIANT + +WORKDIR /app +COPY goreleaser/dist dist + +# NOTICE: See goreleaser.yml for the build paths. +RUN if [ "${TARGETARCH}" == 'amd64' ]; then \ + cp "dist/parca-push_${TARGETOS}_${TARGETARCH}_${TARGETVARIANT:-v1}/parca-push" . ; \ + elif [ "${TARGETARCH}" == 'arm' ]; then \ + cp "dist/parca-push_${TARGETOS}_${TARGETARCH}_${TARGETVARIANT##v}/parca-push" . ; \ + else \ + cp "dist/parca-push_${TARGETOS}_${TARGETARCH}/parca-push" . ; \ + fi +RUN chmod +x parca-push + +FROM --platform="${TARGETPLATFORM:-linux/amd64}" gcr.io/distroless/static@sha256:21d3f84a4f37c36199fd07ad5544dcafecc17776e3f3628baf9a57c8c0181b3f +COPY --chown=0:0 --from=builder /app/parca-push /bin/parca-push +CMD ["/bin/parca-push"] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..7628417 --- /dev/null +++ b/Makefile @@ -0,0 +1,125 @@ +SHELL := /usr/bin/env bash + +# tools: +GO ?= go +CMD_DOCKER ?= docker +CMD_GIT ?= git +CMD_EMBEDMD ?= embedmd +CMD_GORELEASER ?= goreleaser +CMD_GOLANGCI_LINT ?= golangci-lint + +# environment: +ARCH ?= $(shell go env GOARCH) + +# version: +ifeq ($(GITHUB_BRANCH_NAME),) + BRANCH := $(shell git rev-parse --abbrev-ref HEAD)- +else + BRANCH := $(GITHUB_BRANCH_NAME)- +endif +ifeq ($(GITHUB_SHA),) + COMMIT := $(shell git describe --no-match --dirty --always --abbrev=8) +else + COMMIT := $(shell echo $(GITHUB_SHA) | cut -c1-8) +endif +VERSION ?= $(if $(RELEASE_TAG),$(RELEASE_TAG),$(shell $(CMD_GIT) describe --tags 2>/dev/null || echo '$(BRANCH)$(COMMIT)')) + +# inputs and outputs: +OUT_DIR ?= dist +GO_SRC := $(shell find . -type f -name '*.go') +OUT_BIN := $(OUT_DIR)/parca-push +OUT_DOCKER ?= ghcr.io/parca-dev/parca-push +OUT_DOCKER_DEV ?= parca-dev/parca-push + +.PHONY: all +all: build + +$(OUT_DIR): + mkdir -p $@ + +.PHONY: build +build: $(OUT_BIN) + +GO_ENV := CGO_ENABLED=0 GOOS=linux GOARCH=$(ARCH) +GO_BUILD_FLAGS := -tags osusergo,netgo -mod=readonly -trimpath -v + +$(OUT_BIN): $(filter-out *_test.go,$(GO_SRC)) go/deps | $(OUT_DIR) + find dist -exec touch -t 202101010000.00 {} + + $(GO) build -trimpath -v -o $(OUT_BIN) ./cmd/parca-push + +.PHONY: go/deps +go/deps: + $(GO) mod tidy + +# static analysis: +lint: check-license go/lint vet + +.PHONY: check-license +check-license: + ./scripts/check-license.sh + +.PHONY: go/lint +go/lint: + $(CMD_GOLANGCI_LINT) run + +.PHONY: test +test: $(GO_SRC) + $(GO_ENV) $(CGO_ENV) $(GO) test -v $(shell $(GO) list ./...) + +.PHONY: format +format: + $(GO) fmt $(shell $(GO) list ./...) + +.PHONY: vet +vet: $(GO_SRC) + $(GO_ENV) $(CGO_ENV) $(GO) vet -v $(shell $(GO) list ./...) + +# clean: +.PHONY: mostlyclean +mostlyclean: + -rm -rf $(OUT_BIN) + +.PHONY: clean +clean: mostlyclean + -rm -rf $(OUT_DIR) + +# container: +.PHONY: container +container: $(OUT_DIR) + podman build \ + --platform linux/amd64,linux/arm64 \ + --timestamp 0 \ + --manifest $(OUT_DOCKER):$(VERSION) . + +.PHONY: container-dev +container-dev: + docker build -t $(OUT_DOCKER_DEV):$(VERSION) --build-arg=GOLANG_BASE=golang:1.18.3-bullseye --build-arg=DEBIAN_BASE=debian:bullseye-slim . + +.PHONY: sign-container +sign-container: + crane digest $(OUT_DOCKER):$(VERSION) + cosign sign --force -a GIT_HASH=$(COMMIT) -a GIT_VERSION=$(VERSION) $(OUT_DOCKER)@$(shell crane digest $(OUT_DOCKER):$(VERSION)) + +.PHONY: push-container +push-container: + podman manifest push --all $(OUT_DOCKER):$(VERSION) docker://$(OUT_DOCKER):$(VERSION) + +.PHONY: push-local-container +push-local-container: + podman push $(OUT_DOCKER):$(VERSION) docker-daemon:docker.io/$(OUT_DOCKER):$(VERSION) + +# other artifacts: +$(OUT_DIR)/help.txt: $(OUT_BIN) + $(OUT_BIN) --help > $@ + +README.md: $(OUT_DIR)/help.txt + $(CMD_EMBEDMD) -w README.md + +# test cross-compile release pipeline: +.PHONY: release-dry-run +release-dry-run: + $(CMD_GORELEASER) release --rm-dist --auto-snapshot --skip-validate --skip-publish --debug + +.PHONY: release-build +release-build: + $(CMD_GORELEASER) build --rm-dist --skip-validate --snapshot --debug diff --git a/README.md b/README.md new file mode 100644 index 0000000..e964681 --- /dev/null +++ b/README.md @@ -0,0 +1,38 @@ +![Build](https://github.com/parca-dev/parca-push/actions/workflows/build.yml/badge.svg) +![Container](https://github.com/parca-dev/parca-push/actions/workflows/container.yml/badge.svg) +[![Apache 2 License](https://img.shields.io/badge/license-Apache%202-blue.svg)](LICENSE) + +# parca-push + +A command line utility to push individual pprof formatted profiles to a Parca compatible API. + +## Configuration + +Flags: + +[embedmd]:# (dist/help.txt) +```txt +Usage: parca-push + +Arguments: + Path to the profile data. + +Flags: + -h, --help Show context-sensitive help. + -l, --labels=KEY=VALUE;... Labels to attach to the profile data. + For example --labels=__name__=process_cpu + --labels=node=foo + --normalized Whether the profile sample addresses are + already normalized by the mapping offset. + --remote-store-address=STRING + gRPC address to send profiles and symbols to. + --remote-store-bearer-token=STRING + Bearer token to authenticate with store. + --remote-store-bearer-token-file=STRING + File to read bearer token from to authenticate + with store. + --remote-store-insecure Send gRPC requests via plaintext instead of + TLS. + --remote-store-insecure-skip-verify + Skip TLS certificate verification. +``` diff --git a/cmd/parca-push/main.go b/cmd/parca-push/main.go new file mode 100644 index 0000000..1d29c98 --- /dev/null +++ b/cmd/parca-push/main.go @@ -0,0 +1,170 @@ +// Copyright (c) 2023 The Parca 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. +// + +package main + +import ( + "context" + "crypto/tls" + "fmt" + "io" + "os" + + "github.com/alecthomas/kong" + grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus" + grun "github.com/oklog/run" + profilestorepb "github.com/parca-dev/parca/gen/proto/go/parca/profilestore/v1alpha1" + "github.com/prometheus/client_golang/prometheus" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/credentials/insecure" +) + +type flags struct { + Path string `kong:"arg,help='Path to the profile data.'"` + Labels map[string]string `kong:"help='Labels to attach to the profile data. For example --labels=__name__=process_cpu --labels=node=foo',short='l'"` + Normalized bool `kong:"help='Whether the profile sample addresses are already normalized by the mapping offset.',default='false'"` + + RemoteStore FlagsRemoteStore `embed:"" prefix:"remote-store-"` +} + +// FlagsRemoteStore provides remote store configuration flags. +type FlagsRemoteStore struct { + Address string `kong:"help='gRPC address to send profiles and symbols to.'"` + BearerToken string `kong:"help='Bearer token to authenticate with store.'"` + BearerTokenFile string `kong:"help='File to read bearer token from to authenticate with store.'"` + Insecure bool `kong:"help='Send gRPC requests via plaintext instead of TLS.'"` + InsecureSkipVerify bool `kong:"help='Skip TLS certificate verification.'"` +} + +func main() { + flags := flags{} + kong.Parse(&flags) + if err := run(flags); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +} + +func run(flags flags) error { + var g grun.Group + ctx, cancel := context.WithCancel(context.Background()) + g.Add(func() error { + labels := []*profilestorepb.Label{} + for name, value := range flags.Labels { + labels = append(labels, &profilestorepb.Label{ + Name: name, + Value: value, + }) + } + + conn, err := grpcConn(prometheus.NewRegistry(), flags.RemoteStore) + if err != nil { + return fmt.Errorf("create gRPC connection: %w", err) + } + defer conn.Close() + + var profile []byte + if flags.Path == "-" { + profile, err = io.ReadAll(os.Stdin) + if err != nil { + return fmt.Errorf("read profile from stdin: %w", err) + } + } else { + profile, err = os.ReadFile(flags.Path) + if err != nil { + return fmt.Errorf("read profile file: %w", err) + } + } + + profilestoreClient := profilestorepb.NewProfileStoreServiceClient(conn) + _, err = profilestoreClient.WriteRaw(ctx, &profilestorepb.WriteRawRequest{ + Series: []*profilestorepb.RawProfileSeries{{ + Labels: &profilestorepb.LabelSet{ + Labels: labels, + }, + Samples: []*profilestorepb.RawSample{{ + RawProfile: profile, + }}, + }}, + Normalized: flags.Normalized, + }) + if err != nil { + return fmt.Errorf("write profile: %w", err) + } + + return nil + }, func(error) { + cancel() + }) + + g.Add(grun.SignalHandler(ctx, os.Interrupt, os.Kill)) + return g.Run() +} + +func grpcConn(reg prometheus.Registerer, flags FlagsRemoteStore) (*grpc.ClientConn, error) { + met := grpc_prometheus.NewClientMetrics() + met.EnableClientHandlingTimeHistogram() + reg.MustRegister(met) + + opts := []grpc.DialOption{ + grpc.WithUnaryInterceptor( + met.UnaryClientInterceptor(), + ), + } + if flags.Insecure { + opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials())) + } else { + config := &tls.Config{ + //nolint:gosec + InsecureSkipVerify: flags.InsecureSkipVerify, + } + opts = append(opts, grpc.WithTransportCredentials(credentials.NewTLS(config))) + } + + if flags.BearerToken != "" { + opts = append(opts, grpc.WithPerRPCCredentials(&perRequestBearerToken{ + token: flags.BearerToken, + insecure: flags.Insecure, + })) + } + + if flags.BearerTokenFile != "" { + b, err := os.ReadFile(flags.BearerTokenFile) + if err != nil { + return nil, fmt.Errorf("failed to read bearer token from file: %w", err) + } + opts = append(opts, grpc.WithPerRPCCredentials(&perRequestBearerToken{ + token: string(b), + insecure: flags.Insecure, + })) + } + + return grpc.Dial(flags.Address, opts...) +} + +type perRequestBearerToken struct { + token string + insecure bool +} + +func (t *perRequestBearerToken) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) { + return map[string]string{ + "authorization": "Bearer " + t.token, + }, nil +} + +func (t *perRequestBearerToken) RequireTransportSecurity() bool { + return !t.insecure +} diff --git a/env.sh b/env.sh new file mode 100755 index 0000000..5fa2b03 --- /dev/null +++ b/env.sh @@ -0,0 +1,28 @@ +#! /usr/bin/env bash +# Copyright (c) 2022 The Parca 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 -euo pipefail + +# renovate: datasource=go depName=github.com/campoy/embedmd +EMBEDMD_VERSION='v2.0.0' +go install "github.com/campoy/embedmd/v2@${EMBEDMD_VERSION}" + +# renovate: datasource=go depName=mvdan.cc/gofumpt +GOFUMPT_VERSION='v0.3.1' +go install "mvdan.cc/gofumpt@${GOFUMPT_VERSION}" + +# renovate: datasource=go depName=github.com/golangci/golangci-lint +GOLANGCI_LINT_VERSION='v1.47.2' +go install "github.com/golangci/golangci-lint/cmd/golangci-lint@${GOLANGCI_LINT_VERSION}" diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..0e14417 --- /dev/null +++ b/go.mod @@ -0,0 +1,31 @@ +module github.com/parca-dev/parca-push + +go 1.19 + +require ( + github.com/alecthomas/kong v0.7.1 + github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 + github.com/oklog/run v1.1.0 + github.com/parca-dev/parca v0.15.0 + github.com/prometheus/client_golang v1.14.0 + google.golang.org/grpc v1.53.0 +) + +require ( + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/golang/protobuf v1.5.2 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.14.0 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect + github.com/prometheus/client_model v0.3.0 // indirect + github.com/prometheus/common v0.39.0 // indirect + github.com/prometheus/procfs v0.9.0 // indirect + golang.org/x/net v0.5.0 // indirect + golang.org/x/sys v0.5.0 // indirect + golang.org/x/text v0.6.0 // indirect + google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect + google.golang.org/protobuf v1.28.1 // indirect +) + +// required by https://github.com/grpc-ecosystem/grpc-gateway/releases/tag/v2.10.3 +replace cloud.google.com/go/storage v1.19.0 => cloud.google.com/go/storage v1.10.0 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..8040f6d --- /dev/null +++ b/go.sum @@ -0,0 +1,55 @@ +github.com/alecthomas/assert/v2 v2.1.0 h1:tbredtNcQnoSd3QBhQWI7QZ3XHOVkw1Moklp2ojoH/0= +github.com/alecthomas/kong v0.7.1 h1:azoTh0IOfwlAX3qN9sHWTxACE2oV8Bg2gAwBsMwDQY4= +github.com/alecthomas/kong v0.7.1/go.mod h1:n1iCIO2xS46oE8ZfYCNDqdR0b0wZNrXAIAqro/2132U= +github.com/alecthomas/repr v0.1.0 h1:ENn2e1+J3k09gyj2shc0dHr/yjaWSHRlrJ4DPMevDqE= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.14.0 h1:t7uX3JBHdVwAi3G7sSSdbsk8NfgA+LnUS88V/2EKaA0= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.14.0/go.mod h1:4OGVnY4qf2+gw+ssiHbW+pq4mo2yko94YxxMmXZ7jCA= +github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= +github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= +github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= +github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= +github.com/parca-dev/parca v0.15.0 h1:4BMYVLjkyJInM8FQNSqPRgIPfV69osOX0iShP8b5jBc= +github.com/parca-dev/parca v0.15.0/go.mod h1:wwZlGYTZuUuj97Y+KINw1sgixjj9rr7FGM5weNLg494= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= +github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= +github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= +github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= +github.com/prometheus/common v0.39.0 h1:oOyhkDq05hPZKItWVBkJ6g6AtGxi+fy7F4JvUV8uhsI= +github.com/prometheus/common v0.39.0/go.mod h1:6XBZ7lYdLCbkAVhwRsWTZn+IN5AB9F/NXd5w0BbEX0Y= +github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= +github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw= +golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k= +golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f h1:BWUVssLB0HVOSY78gIdvk1dTVYtT1y8SBWtPYuTJ/6w= +google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/grpc v1.53.0 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc= +google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/scripts/check-license.sh b/scripts/check-license.sh new file mode 100755 index 0000000..7605cfe --- /dev/null +++ b/scripts/check-license.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +# exit immediately when a command fails +set -e +# only exit with zero if all commands of the pipeline exit successfully +set -o pipefail +# error on unset variables +set -u + +licRes=$( + find . -type f -iname '*.go' ! -path '*/vendor/*' ! -path '*/internal/go/*' ! -path '*/internal/pprof/*' -exec \ + sh -c 'head -n3 $1 | grep -Eq "(Copyright|generated|GENERATED)" || echo -e $1' {} {} \; +) + +if [ -n "${licRes}" ]; then + echo -e "license header checking failed:\\n${licRes}" + exit 255 +fi