diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index deec24a..0000000 --- a/.editorconfig +++ /dev/null @@ -1,16 +0,0 @@ -root = true - -[*] -indent_style = space -indent_size = 2 -tab_width = 2 -end_of_line = lf -charset = utf-8 -trim_trailing_whitespace = true -insert_final_newline = true - -[*.{go}] -indent_style = tab - -[Makefile] -indent_style = tab diff --git a/.forgejo/cascading-pr-setup-forgejo b/.forgejo/cascading-pr-setup-forgejo deleted file mode 100755 index 06472a7..0000000 --- a/.forgejo/cascading-pr-setup-forgejo +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash - -set -ex - -setup_forgejo=$1 -setup_forgejo_pr=$2 -runner=$3 -runner_pr=$4 - -url=$(jq --raw-output .head.repo.html_url < $runner_pr) -test "$url" != null -branch=$(jq --raw-output .head.ref < $runner_pr) -test "$branch" != null -cd $setup_forgejo -./utils/upgrade-runner.sh $url @$branch -date > last-upgrade diff --git a/.forgejo/labelscompare.py b/.forgejo/labelscompare.py deleted file mode 100644 index 2274d38..0000000 --- a/.forgejo/labelscompare.py +++ /dev/null @@ -1,24 +0,0 @@ -import json - -expectedLabels = { - "maintainer": "contact@forgejo.org", - "org.opencontainers.image.authors": "Forgejo", - "org.opencontainers.image.url": "https://forgejo.org", - "org.opencontainers.image.documentation": "https://forgejo.org/docs/latest/admin/actions/#forgejo-runner", - "org.opencontainers.image.source": "https://code.forgejo.org/forgejo/runner", - "org.opencontainers.image.version": "1.2.3", - "org.opencontainers.image.vendor": "Forgejo", - "org.opencontainers.image.licenses": "MIT", - "org.opencontainers.image.title": "Forgejo Runner", - "org.opencontainers.image.description": "A runner for Forgejo Actions.", -} -inspect = None -with open("./labels.json", "r") as f: - inspect = json.load(f) - -assert inspect -labels = inspect[0]["Config"]["Labels"] - -for k, v in expectedLabels.items(): - assert k in labels, f"'{k}' is missing from labels" - assert labels[k] == v, f"expected {v} in key {k}, found {labels[k]}" diff --git a/.forgejo/testdata/ipv6.yml b/.forgejo/testdata/ipv6.yml deleted file mode 100644 index e0f7588..0000000 --- a/.forgejo/testdata/ipv6.yml +++ /dev/null @@ -1,11 +0,0 @@ ---- -on: push -jobs: - ipv6: - runs-on: docker - container: - image: code.forgejo.org/oci/debian:bookworm - steps: - - run: | - apt update -qq ; apt --quiet install -qq --yes iputils-ping - ping -c 1 -6 ::1 diff --git a/.forgejo/workflows/build-release-integration.yml b/.forgejo/workflows/build-release-integration.yml deleted file mode 100644 index 7f92218..0000000 --- a/.forgejo/workflows/build-release-integration.yml +++ /dev/null @@ -1,90 +0,0 @@ -name: Integration tests for the release process - -on: - push: - paths: - - go.mod - - Dockerfile - - .forgejo/workflows/build-release.yml - - .forgejo/workflows/build-release-integration.yml - pull_request: - paths: - - go.mod - - Dockerfile - - .forgejo/workflows/build-release.yml - - .forgejo/workflows/build-release-integration.yml - -jobs: - release-simulation: - runs-on: self-hosted - if: github.repository_owner != 'forgejo-integration' && github.repository_owner != 'forgejo-release' - steps: - - uses: actions/checkout@v3 - - - id: forgejo - uses: https://code.forgejo.org/actions/setup-forgejo@v1 - with: - user: root - password: admin1234 - image-version: 1.20 - lxc-ip-prefix: 10.0.9 - - - name: publish - run: | - set -x - - version=1.2.3 - cat > /etc/docker/daemon.json < $binary$suffix - if test "$suffix" = .xz ; then - unxz --keep $binary$suffix - fi - chmod +x $binary - ./$binary --version | grep $version - curl --fail -L -sS $url/root/runner/releases/download/v$version/$binary$suffix.sha256 > $binary$suffix.sha256 - shasum -a 256 --check $binary$suffix.sha256 - rm $binary$suffix - done - done - - docker pull ${{ steps.forgejo.outputs.host-port }}/root/runner:$version - - docker inspect ${{ steps.forgejo.outputs.host-port}}/root/runner:$version > labels.json - python3 .forgejo/labelscompare.py diff --git a/.forgejo/workflows/build-release.yml b/.forgejo/workflows/build-release.yml deleted file mode 100644 index 162befb..0000000 --- a/.forgejo/workflows/build-release.yml +++ /dev/null @@ -1,103 +0,0 @@ -# SPDX-License-Identifier: MIT -# -# https://code.forgejo.org/forgejo/runner -# -# Build the runner binaries and OCI images -# -# ROLE: forgejo-integration -# DOER: forgejo-ci -# TOKEN: -# -name: Build release - -on: - push: - tags: 'v*' - -jobs: - release: - runs-on: self-hosted - # root is used for testing, allow it - if: secrets.ROLE == 'forgejo-integration' || github.repository_owner == 'root' - steps: - - uses: actions/checkout@v3 - - - name: Increase the verbosity when there are no secrets - id: verbose - run: | - if test -z "${{ secrets.TOKEN }}"; then - value=true - else - value=false - fi - echo "value=$value" >> "$GITHUB_OUTPUT" - - - name: Sanitize the name of the repository - id: repository - run: | - echo "value=${GITHUB_REPOSITORY##*/}" >> "$GITHUB_OUTPUT" - - - name: create test TOKEN - id: token - if: ${{ secrets.TOKEN == '' }} - run: | - apt-get -qq install -y jq - url="${{ env.GITHUB_SERVER_URL }}" - hostport=${url##http*://} - hostport=${hostport%%/} - doer=root - api=http://$doer:admin1234@$hostport/api/v1/users/$doer/tokens - curl -sS -X DELETE $api/release - token=$(curl -sS -X POST -H 'Content-Type: application/json' --data-raw '{"name": "release", "scopes": ["all"]}' $api | jq --raw-output .sha1) - echo "value=${token}" >> "$GITHUB_OUTPUT" - - - name: version from ref_name - id: tag-version - run: | - version=${GITHUB_REF_NAME##*v} - echo "value=$version" >> "$GITHUB_OUTPUT" - - - name: release notes - id: release-notes - run: | - anchor=${{ steps.tag-version.outputs.value }} - anchor=${anchor//./-} - cat >> "$GITHUB_OUTPUT" < /dev/null - apt-get update -qq - apt-get install -qq -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin=2.20.2-1~debian.11~bullseye - docker version - # - # docker compose is prone to non backward compatible changes, pin it - # - apt-get install -qq -y docker-compose-plugin=2.20.2-1~debian.11~bullseye - docker compose version - - - name: run the example - run: | - set -x - cd examples/docker-compose - secret=$(openssl rand -hex 20) - sed -i -e "s/{SHARED_SECRET}/$secret/" compose-forgejo-and-runner.yml - cli="docker compose --progress quiet -f compose-forgejo-and-runner.yml" - # - # Launch Forgejo & the runner - # - $cli up -d - for delay in $(seq 60) ; do test -f /srv/runner-data/.runner && break ; sleep 30 ; done - test -f /srv/runner-data/.runner - # - # Run the demo workflow - # - cli="$cli -f compose-demo-workflow.yml" - $cli up -d demo-workflow - # - # Wait for the demo workflow to complete - # - success='DEMO WORKFLOW SUCCESS' - failure='DEMO WORKFLOW FAILURE' - for delay in $(seq 60) ; do - $cli logs demo-workflow > /tmp/out - grep --quiet "$success" /tmp/out && break - grep --quiet "$failure" /tmp/out && break - $cli ps --all - $cli logs --tail=20 runner-daemon demo-workflow - sleep 30 - done - grep --quiet "$success" /tmp/out - $cli logs runner-daemon > /tmp/runner.log - grep --quiet 'Start image=code.forgejo.org/oci/node:20-bookworm' /tmp/runner.log - - - name: full docker compose logs - if: always() - run: | - cd examples/docker-compose - docker compose -f compose-forgejo-and-runner.yml -f compose-demo-workflow.yml logs diff --git a/.forgejo/workflows/integration.yml b/.forgejo/workflows/integration.yml new file mode 100644 index 0000000..025fc3d --- /dev/null +++ b/.forgejo/workflows/integration.yml @@ -0,0 +1,55 @@ +name: Integration tests for the release process + +on: + push: + paths: + - go.mod + - .forgejo/workflows/release.yml + - .forgejo/workflows/integration.yml + +jobs: + release-simulation: + runs-on: self-hosted + steps: + - uses: actions/checkout@v3 + + - id: forgejo + uses: https://code.forgejo.org/actions/setup-forgejo@v1 + with: + user: root + password: admin1234 + image-version: 1.19 + lxc-ip-prefix: 10.0.9 + + - name: publish the runner release + run: | + set -x + + dir=$(mktemp -d) + trap "rm -fr $dir" EXIT + + url=http://root:admin1234@${{ steps.forgejo.outputs.host-port }} + export FORGEJO_RUNNER_LOGS="${{ steps.forgejo.outputs.runner-logs }}" + + # + # Create a new project with the runner and the release workflow only + # + rsync -a --exclude .git ./ $dir/ + rm $(find $dir/.forgejo/workflows/*.yml | grep -v release.yml) + forgejo-test-helper.sh push $dir $url root runner |& tee $dir/pushed + eval $(grep '^sha=' < $dir/pushed) + + # + # Push a tag to trigger the release workflow and wait for it to complete + # + forgejo-test-helper.sh api POST $url repos/root/runner/tags ${{ steps.forgejo.outputs.token }} --data-raw '{"tag_name": "v1.2.3", "target": "'$sha'"}' + LOOPS=180 forgejo-test-helper.sh wait_success "$url" root/runner $sha + + # + # Minimal sanity checks. e2e test is for the setup-forgejo action + # and the infrastructure playbook. + # + curl -L -sS $url/root/runner/releases/download/v1.2.3/forgejo-runner-amd64 > forgejo-runner + chmod +x forgejo-runner + ./forgejo-runner --version | grep 1.2.3 + diff --git a/.forgejo/workflows/publish-release.yml b/.forgejo/workflows/publish-release.yml deleted file mode 100644 index 35d8662..0000000 --- a/.forgejo/workflows/publish-release.yml +++ /dev/null @@ -1,42 +0,0 @@ -# SPDX-License-Identifier: MIT -# -# https://forgejo.octopuce.forgejo.org/forgejo-release/runner -# -# Copies & sign a release from code.forgejo.org/forgejo-integration/runner to code.forgejo.org/forgejo/runner -# -# ROLE: forgejo-release -# FORGEJO: https://code.forgejo.org -# FROM_OWNER: forgejo-integration -# TO_OWNER: forgejo -# DOER: release-team -# TOKEN: -# GPG_PRIVATE_KEY: -# GPG_PASSPHRASE: -# -name: publish - -on: - push: - tags: 'v*' - -jobs: - publish: - runs-on: self-hosted - if: secrets.DOER != '' && secrets.FORGEJO != '' && secrets.TO_OWNER != '' && secrets.FROM_OWNER != '' && secrets.TOKEN != '' - steps: - - uses: actions/checkout@v3 - - - name: copy & sign - uses: https://code.forgejo.org/forgejo/forgejo-build-publish/publish@v1 - with: - forgejo: ${{ secrets.FORGEJO }} - from-owner: ${{ secrets.FROM_OWNER }} - to-owner: ${{ secrets.TO_OWNER }} - repo: "runner" - ref-name: ${{ github.ref_name }} - container-suffixes: " " - doer: ${{ secrets.DOER }} - token: ${{ secrets.TOKEN }} - gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }} - gpg-passphrase: ${{ secrets.GPG_PASSPHRASE }} - verbose: ${{ secrets.VERBOSE }} diff --git a/.forgejo/workflows/release.yml b/.forgejo/workflows/release.yml new file mode 100644 index 0000000..3337b8d --- /dev/null +++ b/.forgejo/workflows/release.yml @@ -0,0 +1,114 @@ +name: Publish release + +on: + push: + tags: 'v*' + +jobs: + release: + runs-on: self-hosted + steps: + - uses: actions/checkout@v3 + + - id: verbose + run: | + # if there are no secrets, be verbose + if test -z "${{ secrets.TOKEN }}"; then + value=true + else + value=false + fi + echo "value=$value" >> "$GITHUB_OUTPUT" + echo "shell=set -x" >> "$GITHUB_OUTPUT" + + - id: registry + run: | + ${{ steps.verbose.outputs.shell }} + url="${{ env.GITHUB_SERVER_URL }}" + hostport=${url##http*://} + hostport=${hostport%%/} + echo "host-port=${hostport}" >> "$GITHUB_OUTPUT" + if ! [[ $url =~ ^http:// ]] ; then + exit 0 + fi + cat >> "$GITHUB_OUTPUT" <> "$GITHUB_OUTPUT" + echo "doer=${doer}" >> "$GITHUB_OUTPUT" + + - name: allow docker pull/push to forgejo + if: ${{ steps.registry.outputs.insecure }} + run: |- + mkdir /etc/docker + cat > /etc/docker/daemon.json < ~/.docker/config.json + env: + CI_REGISTRY: "${{ env.GITHUB_SERVER_URL }}${{ env.GITHUB_REPOSITORY_OWNER }}" + + - id: build + run: | + ${{ steps.verbose.outputs.shell }} + tag="${{ github.ref_name }}" + tag=${tag##*v} + echo "tag=$tag" >> "$GITHUB_OUTPUT" + echo "image=${{ steps.registry.outputs.host-port }}/${{ github.repository }}:${tag}" >> "$GITHUB_OUTPUT" + + - uses: https://github.com/docker/build-push-action@v4 + with: + context: . + push: true + platforms: linux/amd64,linux/arm64 + tags: ${{ steps.build.outputs.image }} + + - run: | + ${{ steps.verbose.outputs.shell }} + mkdir -p release + for arch in amd64 arm64; do + docker create --platform linux/$arch --name runner ${{ steps.build.outputs.image }} + docker cp runner:/bin/forgejo-runner release/forgejo-runner-$arch + shasum -a 256 < release/forgejo-runner-$arch > release/forgejo-runner-$arch.sha256 + docker rm runner + done + + - uses: https://code.forgejo.org/actions/forgejo-release@v1 + with: + direction: upload + release-dir: release + release-notes: "RELEASE-NOTES#${{ steps.build.outputs.tag }}" + token: ${{ steps.secrets.outputs.token }} + verbose: ${{ steps.verbose.outputs.value }} diff --git a/.forgejo/workflows/test.yml b/.forgejo/workflows/test.yml index 677ab68..4b410e5 100644 --- a/.forgejo/workflows/test.yml +++ b/.forgejo/workflows/test.yml @@ -1,108 +1,23 @@ name: checks -on: - push: - branches: - - 'main' - pull_request: +on: + - pull_request + - push env: - FORGEJO_HOST_PORT: 'forgejo:3000' - FORGEJO_ADMIN_USER: 'root' - FORGEJO_ADMIN_PASSWORD: 'admin1234' - FORGEJO_RUNNER_SECRET: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' - FORGEJO_SCRIPT: | - /bin/s6-svscan /etc/s6 & sleep 10 ; su -c "forgejo admin user create --admin --username $FORGEJO_ADMIN_USER --password $FORGEJO_ADMIN_PASSWORD --email root@example.com" git && su -c "forgejo forgejo-cli actions register --labels docker --name therunner --secret $FORGEJO_RUNNER_SECRET" git && sleep infinity GOPROXY: https://goproxy.io,direct jobs: - build-and-tests: - name: build and test - if: github.repository_owner != 'forgejo-integration' && github.repository_owner != 'forgejo-experimental' && github.repository_owner != 'forgejo-release' - runs-on: docker - - services: - forgejo: - image: codeberg.org/forgejo/forgejo:1.21 - env: - FORGEJO__security__INSTALL_LOCK: "true" - FORGEJO__log__LEVEL: "debug" - FORGEJO__actions__ENABLED: "true" - FORGEJO_ADMIN_USER: ${{ env.FORGEJO_ADMIN_USER }} - FORGEJO_ADMIN_PASSWORD: ${{ env.FORGEJO_ADMIN_PASSWORD }} - FORGEJO_RUNNER_SECRET: ${{ env.FORGEJO_RUNNER_SECRET }} - cmd: - - 'bash' - - '-c' - - ${{ env.FORGEJO_SCRIPT }} - + lint: + name: check and test + runs-on: ubuntu-latest steps: - uses: actions/setup-go@v3 with: - go-version: '1.21' - - - uses: actions/checkout@v4 - - - run: make vet - - - run: make build - - - uses: https://code.forgejo.org/actions/upload-artifact@v3 - with: - name: forgejo-runner - path: forgejo-runner - - - name: check the forgejo server is responding - run: | - apt-get update -qq - apt-get install -y -qq jq curl - test $FORGEJO_ADMIN_USER = $(curl -sS http://$FORGEJO_ADMIN_USER:$FORGEJO_ADMIN_PASSWORD@$FORGEJO_HOST_PORT/api/v1/user | jq --raw-output .login) - - - run: make FORGEJO_URL=http://$FORGEJO_HOST_PORT test - - runner-exec-tests: - needs: [build-and-tests] - name: runner exec tests - if: github.repository_owner != 'forgejo-integration' && github.repository_owner != 'forgejo-experimental' && github.repository_owner != 'forgejo-release' - runs-on: self-hosted - - steps: - - - uses: actions/checkout@v4 - - - uses: https://code.forgejo.org/actions/download-artifact@v3 - with: - name: forgejo-runner - - - name: install docker - run: | - mkdir /etc/docker - cat > /etc/docker/daemon.json <& /tmp/out ; then - cat /tmp/out - echo "IPv6 not enabled, should fail" - exit 1 - fi + go-version: 1.19 + - uses: actions/checkout@v3 + - name: vet checks + run: make vet + - name: build + run: make build + - name: test + run: make test diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index 6313b56..0000000 --- a/.gitattributes +++ /dev/null @@ -1 +0,0 @@ -* text=auto eol=lf diff --git a/.gitea/workflows/test.yml b/.gitea/workflows/test.yml new file mode 100644 index 0000000..372f61a --- /dev/null +++ b/.gitea/workflows/test.yml @@ -0,0 +1,43 @@ +name: checks +on: + - push + - pull_request + +env: + GOPROXY: https://goproxy.io,direct + GOPATH: /go_path + GOCACHE: /go_cache + +jobs: + lint: + name: check and test + runs-on: ubuntu-latest + steps: + - name: cache go path + id: cache-go-path + uses: https://github.com/actions/cache@v3 + with: + path: /go_path + key: go_path-${{ github.repository }}-${{ github.ref_name }} + restore-keys: | + go_path-${{ github.repository }}- + go_path- + - name: cache go cache + id: cache-go-cache + uses: https://github.com/actions/cache@v3 + with: + path: /go_cache + key: go_cache-${{ github.repository }}-${{ github.ref_name }} + restore-keys: | + go_cache-${{ github.repository }}- + go_cache- + - uses: actions/setup-go@v3 + with: + go-version: 1.20 + - uses: actions/checkout@v3 + - name: vet checks + run: make vet + - name: build + run: make build + - name: test + run: make test \ No newline at end of file diff --git a/.gitignore b/.gitignore index 3a3808c..2fe3a1b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ *~ - forgejo-runner .env .runner @@ -10,5 +9,3 @@ coverage.txt # MS VSCode .vscode __debug_bin -# gorelease binary folder -dist diff --git a/.golangci.yml b/.golangci.yml index 41f9683..8505828 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,11 +1,14 @@ linters: enable: - gosimple + - deadcode - typecheck - govet - errcheck - staticcheck - unused + - structcheck + - varcheck - dupl #- gocyclo # The cyclomatic complexety of a lot of functions is too high, we should refactor those another time. - gofmt @@ -109,6 +112,7 @@ issues: - gocritic - linters: - unused + - deadcode text: "swagger" - path: contrib/pr/checkout.go linters: @@ -150,6 +154,9 @@ issues: - path: cmd/dump.go linters: - dupl + - path: services/webhook/webhook.go + linters: + - structcheck - text: "commentFormatting: put a space between `//` and comment text" linters: - gocritic diff --git a/Dockerfile b/Dockerfile index 50f1965..0fdae4a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,47 +1,15 @@ -FROM --platform=$BUILDPLATFORM code.forgejo.org/oci/tonistiigi/xx AS xx +#Build stage +FROM golang:1.20-alpine3.17 AS build-env -FROM --platform=$BUILDPLATFORM code.forgejo.org/oci/golang:1.21-alpine3.19 as build-env - -# -# Transparently cross compile for the target platform -# -COPY --from=xx / / -ARG TARGETPLATFORM -RUN apk --no-cache add clang lld -RUN xx-apk --no-cache add gcc musl-dev -RUN xx-go --wrap - -# Do not remove `git` here, it is required for getting runner version when executing `make build` -RUN apk add --no-cache build-base git +RUN apk --no-cache add build-base git COPY . /srv WORKDIR /srv +RUN make build -RUN make clean && make build - -FROM code.forgejo.org/oci/alpine:3.19 -ARG RELEASE_VERSION -RUN apk add --no-cache git bash +FROM alpine:3.17 +LABEL maintainer="contact@forgejo.org" COPY --from=build-env /srv/forgejo-runner /bin/forgejo-runner -LABEL maintainer="contact@forgejo.org" \ - org.opencontainers.image.authors="Forgejo" \ - org.opencontainers.image.url="https://forgejo.org" \ - org.opencontainers.image.documentation="https://forgejo.org/docs/latest/admin/actions/#forgejo-runner" \ - org.opencontainers.image.source="https://code.forgejo.org/forgejo/runner" \ - org.opencontainers.image.version="${RELEASE_VERSION}" \ - org.opencontainers.image.vendor="Forgejo" \ - org.opencontainers.image.licenses="MIT" \ - org.opencontainers.image.title="Forgejo Runner" \ - org.opencontainers.image.description="A runner for Forgejo Actions." - -ENV HOME=/data - -USER 1000:1000 - -WORKDIR /data - -VOLUME ["/data"] - -CMD ["/bin/forgejo-runner"] +ENTRYPOINT ["/bin/forgejo-runner"] diff --git a/LICENSE b/LICENSE index ce49c38..4ee9327 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,3 @@ -Copyright (c) 2023 The Forgejo Authors Copyright (c) 2022 The Gitea Authors Permission is hereby granted, free of charge, to any person obtaining a copy diff --git a/Makefile b/Makefile index 3413168..8496e1f 100644 --- a/Makefile +++ b/Makefile @@ -7,8 +7,9 @@ GO ?= go SHASUM ?= shasum -a 256 HAS_GO = $(shell hash $(GO) > /dev/null 2>&1 && echo "GO" || echo "NOGO" ) XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest -XGO_VERSION := go-1.21.x +XGO_VERSION := go-1.18.x GXZ_PAGAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.10 +RUNNER_CMD_PACKAGE_PATH := codeberg.org/forgejo/runner/cmd LINUX_ARCHS ?= linux/amd64,linux/arm64 DARWIN_ARCHS ?= darwin-12/amd64,darwin-12/arm64 @@ -16,11 +17,6 @@ WINDOWS_ARCHS ?= windows/amd64 GO_FMT_FILES := $(shell find . -type f -name "*.go" ! -name "generated.*") GOFILES := $(shell find . -type f -name "*.go" -o -name "go.mod" ! -name "generated.*") -DOCKER_IMAGE ?= gitea/act_runner -DOCKER_TAG ?= nightly -DOCKER_REF := $(DOCKER_IMAGE):$(DOCKER_TAG) -DOCKER_ROOTLESS_REF := $(DOCKER_IMAGE):$(DOCKER_TAG)-dind-rootless - EXTLDFLAGS = -extldflags "-static" $(null) ifeq ($(HAS_GO), GO) @@ -62,11 +58,8 @@ else endif endif -GO_PACKAGES_TO_VET ?= $(filter-out gitea.com/gitea/act_runner/internal/pkg/client/mocks,$(shell $(GO) list ./...)) - - TAGS ?= -LDFLAGS ?= -X "gitea.com/gitea/act_runner/internal/pkg/ver.version=v$(RELASE_VERSION)" +LDFLAGS ?= -X "$(RUNNER_CMD_PACKAGE_PATH).version=$(RELASE_VERSION)" all: build @@ -76,6 +69,9 @@ fmt: fi $(GOFMT) -w $(GO_FMT_FILES) +vet: + $(GO) vet ./... + .PHONY: go-check go-check: $(eval MIN_GO_VERSION_STR := $(shell grep -Eo '^go\s+[0-9]+\.[0-9]+' go.mod | cut -d' ' -f2)) @@ -101,11 +97,6 @@ fmt-check: test: fmt-check @$(GO) test -v -cover -coverprofile coverage.txt ./... && echo "\n==>\033[32m Ok\033[m\n" || exit 1 -.PHONY: vet -vet: - @echo "Running go vet..." - @$(GO) vet $(GO_PACKAGES_TO_VET) - install: $(GOFILES) $(GO) install -v -tags '$(TAGS)' -ldflags '$(EXTLDFLAGS)-s -w $(LDFLAGS)' @@ -159,14 +150,6 @@ release-check: | $(DIST_DIRS) release-compress: | $(DIST_DIRS) cd $(DIST)/release/; for file in `find . -type f -name "*"`; do echo "compressing $${file}" && $(GO) run $(GXZ_PAGAGE) -k -9 $${file}; done; -.PHONY: docker -docker: - if ! docker buildx version >/dev/null 2>&1; then \ - ARG_DISABLE_CONTENT_TRUST=--disable-content-trust=false; \ - fi; \ - docker build $${ARG_DISABLE_CONTENT_TRUST} -t $(DOCKER_REF) . - docker build $${ARG_DISABLE_CONTENT_TRUST} -t $(DOCKER_ROOTLESS_REF) -f Dockerfile.rootless . - clean: $(GO) clean -x -i ./... rm -rf coverage.txt $(EXECUTABLE) $(DIST) diff --git a/README.md b/README.md index ccf2e8b..d07a565 100644 --- a/README.md +++ b/README.md @@ -1,94 +1,6 @@ -# Forgejo Runner +# Forgejo Actions runner -**WARNING:** this is [alpha release quality](https://en.wikipedia.org/wiki/Software_release_life_cycle#Alpha) code and should not be considered secure enough to deploy in production. +Runs workflows found in `.forgejo/workflows`, using a format similar to GitHub actions but with a Free Software implementation. -A daemon that connects to a Forgejo instance and runs jobs for continous integration. The [installation and usage instructions](https://forgejo.org/docs/next/admin/actions/) are part of the Forgejo documentation. +It is compatible with Forgejo v1.19.0-0-rc0 -# Reporting bugs - -When filing a bug in [the issue tracker](https://code.forgejo.org/forgejo/runner/issues), it is very helpful to propose a pull request [in the end-to-end tests](https://code.forgejo.org/forgejo/end-to-end/src/branch/main/actions) repository that adds a reproducer. It will fail the CI and unambiguously demonstrate that the problem exists. In most cases it is enough to add a workflow ([see the echo example](https://code.forgejo.org/forgejo/end-to-end/src/branch/main/actions/example-echo)). For more complicated cases it is also possible to add a runner config file as well as shell scripts to setup and teardown the test case ([see the service example](https://code.forgejo.org/forgejo/end-to-end/src/branch/main/actions/example-service)). - -# Hacking - -The Forgejo runner depends on [a fork of ACT](https://code.forgejo.org/forgejo/act) and is a dependency of the [setup-forgejo action](https://code.forgejo.org/actions/setup-forgejo). See [the full dependency graph](https://code.forgejo.org/actions/cascading-pr/#forgejo-dependencies) for a global view. - -## Local debug - -The repositories are checked out in the same directory: - -- **runner**: [Forgejo runner](https://code.forgejo.org/forgejo/runner) -- **act**: [ACT](https://code.forgejo.org/forgejo/act) -- **setup-forgejo**: [setup-forgejo](https://code.forgejo.org/actions/setup-forgejo) - -### Install dependencies - -The dependencies are installed manually or with: - -```shell -setup-forgejo/forgejo-dependencies.sh -``` - -### Build the Forgejo runner with the local ACT - -The Forgejo runner is rebuilt with the ACT directory by changing the `runner/go.mod` file to: - -``` -replace github.com/nektos/act => ../act -``` - -Running: - -``` -cd runner ; go mod tidy -``` - -Building: - -```shell -cd runner ; rm -f forgejo-runner ; make forgejo-runner -``` - -### Launch Forgejo and the runner - -A Forgejo instance is launched with: - -```shell -cd setup-forgejo -./forgejo.sh setup -firefox $(cat forgejo-url) -``` - -The user is `root` with password `admin1234`. The runner is registered with: - -``` -cd setup-forgejo -docker exec --user 1000 forgejo forgejo actions generate-runner-token > forgejo-runner-token -../runner/forgejo-runner register --no-interactive --instance "$(cat forgejo-url)" --name runner --token $(cat forgejo-runner-token) --labels docker:docker://node:20-bullseye,self-hosted:host://-self-hosted,lxc:lxc://debian:bullseye -``` - -And launched with: - -```shell -cd setup-forgejo ; ../runner/forgejo-runner --config runner-config.yml daemon -``` - -Note that the `runner-config.yml` is required in that particular case -to configure the network in `bridge` mode, otherwise the runner will -create a network that cannot reach the forgejo instance. - -### Try a sample workflow - -From the Forgejo web interface, create a repository and add the -following to `.forgejo/workflows/try.yaml`. It will launch the job and -the result can be observed from the `actions` tab. - -```yaml -on: [push] -jobs: - ls: - runs-on: docker - steps: - - uses: actions/checkout@v3 - - run: | - ls ${{ github.workspace }} -``` diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md deleted file mode 100644 index c4d9db3..0000000 --- a/RELEASE-NOTES.md +++ /dev/null @@ -1,102 +0,0 @@ -# Release Notes - -## 3.5.2 - -* Fix [crash in some cases when the YAML structure is not as expected](https://code.forgejo.org/forgejo/runner/issues/267). - -## 3.5.1 - -* Fix [CVE-2024-24557](https://nvd.nist.gov/vuln/detail/CVE-2024-24557) -* [Add report_interval option to config](https://code.forgejo.org/forgejo/runner/pulls/220) to allow setting the interval of status and log reports - -## 3.5.0 - -* [Allow graceful shutdowns](https://code.forgejo.org/forgejo/runner/pulls/202): when receiving a signal (INT or TERM) wait for running jobs to complete (up to shutdown_timeout). -* [Fix label declaration](https://code.forgejo.org/forgejo/runner/pulls/176): Runner in daemon mode now takes labels found in config.yml into account when declaration was successful. -* [Fix the docker compose example](https://code.forgejo.org/forgejo/runner/pulls/175) to workaround the race on labels. -* [Fix the kubernetes dind example](https://code.forgejo.org/forgejo/runner/pulls/169). -* [Rewrite ::group:: and ::endgroup:: commands like github](https://code.forgejo.org/forgejo/runner/pulls/183). -* [Added opencontainers labels to the image](https://code.forgejo.org/forgejo/runner/pulls/195) -* [Upgrade the default container to node:20](https://code.forgejo.org/forgejo/runner/pulls/203) - -## 3.4.1 - -* Fixes a regression introduced in 3.4.0 by which a job with no image explicitly set would - [be bound to the host](https://code.forgejo.org/forgejo/runner/issues/165) - network instead of a custom network (empty string in the configuration file). - -## 3.4.0 - -Although this version is able to run [actions/upload-artifact@v4](https://code.forgejo.org/actions/upload-artifact/src/tag/v4) and [actions/download-artifact@v4](https://code.forgejo.org/actions/download-artifact/src/tag/v4), these actions will fail because it does not run against GitHub.com. A fork of those two actions with this check disabled is made available at: - -* https://code.forgejo.org/forgejo/upload-artifact/src/tag/v4 -* https://code.forgejo.org/forgejo/download-artifact/src/tag/v4 - -and they can be used as shown in [an example from the end-to-end test suite](https://code.forgejo.org/forgejo/end-to-end/src/branch/main/actions/example-artifacts-v4/.forgejo/workflows/test.yml). - -* When running against codeberg.org, the default poll frequency is 30s instead of 2s. -* Fix compatibility issue with actions/{upload,download}-artifact@v4. -* Upgrade ACT v1.20.0 which brings: - * `[container].options` from the config file is exposed in containers created by the workflows - * the expressions in the value of `jobs..runs-on` are evaluated - * fix a bug causing the evaluated expression of `jobs..runs-on` to fail if it was an array - * mount `act-toolcache:/opt/hostedtoolcache` instead of `act-toolcache:/toolcache` - * a few improvements to the readability of the error messages displayed in the logs - * `amd64` can be used instead of `x86_64` and `arm64` intead of `aarch64` when specifying the architecture - * fixed YAML parsing bugs preventing dispatch workflows to be parsed correctly - * add support for `runs-on.labels` which is equivalent to `runs-on` followed by a list of labels - * the expressions in the service `ports` and `volumes` values are evaluated - * network aliases are only supported when the network is user specified, not when it is provided by the runner -* If `[runner].insecure` is true in the configuration, insecure cloning actions is allowed - -## 3.3.0 - -* Support IPv6 with addresses from a private range and NAT for - docker:// with --enable-ipv6 and [container].enable_ipv6 - lxc:// always - -## 3.2.0 - -* Support LXC container capabilities via `lxc:lxc://debian:bookworm:k8s` or `lxc:lxc://debian:bookworm:docker lxc k8s` -* Update ACT v1.16.0 to resolve a [race condition when bootstraping LXC templates](https://code.forgejo.org/forgejo/act/pulls/23) - -## 3.1.0 - -The `self-hosted` label that was hardwired to be a LXC container -running `debian:bullseye` was reworked and documented ([user guide](https://forgejo.org/docs/next/user/actions/#jobsjob_idruns-on) and [admin guide](https://forgejo.org/docs/next/admin/actions/#labels-and-runs-on)). - -There now are two different schemes: `lxc://` for LXC containers and -`host://` for running directly on the host. - -* Support the `host://` scheme for running directly on the host. -* Support the `lxc://` scheme in labels -* Update [code.forgejo.org/forgejo/act v1.14.0](https://code.forgejo.org/forgejo/act/pulls/19) to implement both self-hosted and LXC schemes - -## 3.0.3 - -* Update [code.forgejo.org/forgejo/act v1.13.0](https://code.forgejo.org/forgejo/runner/pulls/106) to keep up with github.com/nektos/act - -## 3.0.2 - -* Update [code.forgejo.org/forgejo/act v1.12.0](https://code.forgejo.org/forgejo/runner/pulls/106) to upgrade the node installed in the LXC container to node20 - -## 3.0.1 - -* Update [code.forgejo.org/forgejo/act v1.11.0](https://code.forgejo.org/forgejo/runner/pulls/86) to resolve a bug preventing actions based on node20 from running, such as [checkout@v4](https://code.forgejo.org/actions/checkout/src/tag/v4). - -## 3.0.0 - -* Publish a rootless OCI image -* Refactor the release process - -## 2.5.0 - -* Update [code.forgejo.org/forgejo/act v1.10.0](https://code.forgejo.org/forgejo/runner/pulls/71) - -## 2.4.0 - -* Update [code.forgejo.org/forgejo/act v1.9.0](https://code.forgejo.org/forgejo/runner/pulls/64) - -## 2.3.0 - -* Add support for [offline registration](https://forgejo.org/docs/next/admin/actions/#offline-registration). diff --git a/artifactcache/README.md b/artifactcache/README.md new file mode 100644 index 0000000..0a84d84 --- /dev/null +++ b/artifactcache/README.md @@ -0,0 +1,7 @@ +Inspired by: +https://github.com/sp-ricard-valverde/github-act-cache-server + +TODO: +- Authorization +- [Restrictions for accessing a cache](https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#restrictions-for-accessing-a-cache) +- [Force deleting cache entries](https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#force-deleting-cache-entries) diff --git a/artifactcache/handler.go b/artifactcache/handler.go new file mode 100644 index 0000000..86e7aa5 --- /dev/null +++ b/artifactcache/handler.go @@ -0,0 +1,416 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package artifactcache + +import ( + "context" + "fmt" + "net" + "net/http" + "os" + "path/filepath" + "strconv" + "strings" + "sync/atomic" + "time" + + "github.com/go-chi/chi/v5" + "github.com/go-chi/chi/v5/middleware" + "github.com/go-chi/render" + log "github.com/sirupsen/logrus" + _ "modernc.org/sqlite" + "xorm.io/builder" + "xorm.io/xorm" +) + +const ( + urlBase = "/_apis/artifactcache" +) + +var logger = log.StandardLogger().WithField("module", "cache_request") + +type Handler struct { + engine engine + storage *Storage + router *chi.Mux + listener net.Listener + + gc atomic.Bool + gcAt time.Time + + outboundIP string +} + +func NewHandler(dir, outboundIP string, port uint16) (*Handler, error) { + h := &Handler{} + + if dir == "" { + if home, err := os.UserHomeDir(); err != nil { + return nil, err + } else { + dir = filepath.Join(home, ".cache", "actcache") + } + } + if err := os.MkdirAll(dir, 0o755); err != nil { + return nil, err + } + + e, err := xorm.NewEngine("sqlite", filepath.Join(dir, "sqlite.db")) + if err != nil { + return nil, err + } + if err := e.Sync(&Cache{}); err != nil { + return nil, err + } + h.engine = engine{e: e} + + storage, err := NewStorage(filepath.Join(dir, "cache")) + if err != nil { + return nil, err + } + h.storage = storage + + if outboundIP != "" { + h.outboundIP = outboundIP + } else if ip, err := getOutboundIP(); err != nil { + return nil, err + } else { + h.outboundIP = ip.String() + } + + router := chi.NewRouter() + router.Use(middleware.RequestLogger(&middleware.DefaultLogFormatter{Logger: logger})) + router.Use(func(handler http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + handler.ServeHTTP(w, r) + go h.gcCache() + }) + }) + router.Use(middleware.Logger) + router.Route(urlBase, func(r chi.Router) { + r.Get("/cache", h.find) + r.Route("/caches", func(r chi.Router) { + r.Post("/", h.reserve) + r.Route("/{id}", func(r chi.Router) { + r.Patch("/", h.upload) + r.Post("/", h.commit) + }) + }) + r.Get("/artifacts/{id}", h.get) + r.Post("/clean", h.clean) + }) + + h.router = router + + h.gcCache() + + listener, err := net.Listen("tcp", fmt.Sprintf(":%d", port)) // listen on all interfaces + if err != nil { + return nil, err + } + go func() { + if err := http.Serve(listener, h.router); err != nil { + logger.Errorf("http serve: %v", err) + } + }() + h.listener = listener + + return h, nil +} + +func (h *Handler) ExternalURL() string { + // TODO: make the external url configurable if necessary + return fmt.Sprintf("http://%s:%d", + h.outboundIP, + h.listener.Addr().(*net.TCPAddr).Port) +} + +// GET /_apis/artifactcache/cache +func (h *Handler) find(w http.ResponseWriter, r *http.Request) { + keys := strings.Split(r.URL.Query().Get("keys"), ",") + version := r.URL.Query().Get("version") + + cache, err := h.findCache(r.Context(), keys, version) + if err != nil { + responseJson(w, r, 500, err) + return + } + if cache == nil { + responseJson(w, r, 204) + return + } + + if ok, err := h.storage.Exist(cache.ID); err != nil { + responseJson(w, r, 500, err) + return + } else if !ok { + _ = h.engine.Exec(func(sess *xorm.Session) error { + _, err := sess.Delete(cache) + return err + }) + responseJson(w, r, 204) + return + } + responseJson(w, r, 200, map[string]any{ + "result": "hit", + "archiveLocation": fmt.Sprintf("%s%s/artifacts/%d", h.ExternalURL(), urlBase, cache.ID), + "cacheKey": cache.Key, + }) +} + +// POST /_apis/artifactcache/caches +func (h *Handler) reserve(w http.ResponseWriter, r *http.Request) { + cache := &Cache{} + if err := render.Bind(r, cache); err != nil { + responseJson(w, r, 400, err) + return + } + + if ok, err := h.engine.ExecBool(func(sess *xorm.Session) (bool, error) { + return sess.Where(builder.Eq{"key": cache.Key, "version": cache.Version}).Get(&Cache{}) + }); err != nil { + responseJson(w, r, 500, err) + return + } else if ok { + responseJson(w, r, 400, fmt.Errorf("already exist")) + return + } + + if err := h.engine.Exec(func(sess *xorm.Session) error { + _, err := sess.Insert(cache) + return err + }); err != nil { + responseJson(w, r, 500, err) + return + } + responseJson(w, r, 200, map[string]any{ + "cacheId": cache.ID, + }) + return +} + +// PATCH /_apis/artifactcache/caches/:id +func (h *Handler) upload(w http.ResponseWriter, r *http.Request) { + id, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64) + if err != nil { + responseJson(w, r, 400, err) + return + } + + cache := &Cache{ + ID: id, + } + + if ok, err := h.engine.ExecBool(func(sess *xorm.Session) (bool, error) { + return sess.Get(cache) + }); err != nil { + responseJson(w, r, 500, err) + return + } else if !ok { + responseJson(w, r, 400, fmt.Errorf("cache %d: not reserved", id)) + return + } + + if cache.Complete { + responseJson(w, r, 400, fmt.Errorf("cache %v %q: already complete", cache.ID, cache.Key)) + return + } + start, _, err := parseContentRange(r.Header.Get("Content-Range")) + if err != nil { + responseJson(w, r, 400, err) + return + } + if err := h.storage.Write(cache.ID, start, r.Body); err != nil { + responseJson(w, r, 500, err) + } + h.useCache(r.Context(), id) + responseJson(w, r, 200) +} + +// POST /_apis/artifactcache/caches/:id +func (h *Handler) commit(w http.ResponseWriter, r *http.Request) { + id, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64) + if err != nil { + responseJson(w, r, 400, err) + return + } + + cache := &Cache{ + ID: id, + } + if ok, err := h.engine.ExecBool(func(sess *xorm.Session) (bool, error) { + return sess.Get(cache) + }); err != nil { + responseJson(w, r, 500, err) + return + } else if !ok { + responseJson(w, r, 400, fmt.Errorf("cache %d: not reserved", id)) + return + } + + if cache.Complete { + responseJson(w, r, 400, fmt.Errorf("cache %v %q: already complete", cache.ID, cache.Key)) + return + } + + if err := h.storage.Commit(cache.ID, cache.Size); err != nil { + responseJson(w, r, 500, err) + return + } + + cache.Complete = true + if err := h.engine.Exec(func(sess *xorm.Session) error { + _, err := sess.ID(cache.ID).Cols("complete").Update(cache) + return err + }); err != nil { + responseJson(w, r, 500, err) + return + } + + responseJson(w, r, 200) +} + +// GET /_apis/artifactcache/artifacts/:id +func (h *Handler) get(w http.ResponseWriter, r *http.Request) { + id, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64) + if err != nil { + responseJson(w, r, 400, err) + return + } + h.useCache(r.Context(), id) + h.storage.Serve(w, r, id) +} + +// POST /_apis/artifactcache/clean +func (h *Handler) clean(w http.ResponseWriter, r *http.Request) { + // TODO: don't support force deleting cache entries + // see: https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#force-deleting-cache-entries + + responseJson(w, r, 200) +} + +// if not found, return (nil, nil) instead of an error. +func (h *Handler) findCache(ctx context.Context, keys []string, version string) (*Cache, error) { + if len(keys) == 0 { + return nil, nil + } + key := keys[0] // the first key is for exact match. + + cache := &Cache{} + if ok, err := h.engine.ExecBool(func(sess *xorm.Session) (bool, error) { + return sess.Where(builder.Eq{"key": key, "version": version, "complete": true}).Get(cache) + }); err != nil { + return nil, err + } else if ok { + return cache, nil + } + + for _, prefix := range keys[1:] { + if ok, err := h.engine.ExecBool(func(sess *xorm.Session) (bool, error) { + return sess.Where(builder.And( + builder.Like{"key", prefix + "%"}, + builder.Eq{"version": version, "complete": true}, + )).OrderBy("id DESC").Get(cache) + }); err != nil { + return nil, err + } else if ok { + return cache, nil + } + } + return nil, nil +} + +func (h *Handler) useCache(ctx context.Context, id int64) { + // keep quiet + _ = h.engine.Exec(func(sess *xorm.Session) error { + _, err := sess.Context(ctx).Cols("used_at").Update(&Cache{ + ID: id, + UsedAt: time.Now().Unix(), + }) + return err + }) +} + +func (h *Handler) gcCache() { + if h.gc.Load() { + return + } + if !h.gc.CompareAndSwap(false, true) { + return + } + defer h.gc.Store(false) + + if time.Since(h.gcAt) < time.Hour { + logger.Infof("skip gc: %v", h.gcAt.String()) + return + } + h.gcAt = time.Now() + logger.Infof("gc: %v", h.gcAt.String()) + + const ( + keepUsed = 30 * 24 * time.Hour + keepUnused = 7 * 24 * time.Hour + keepTemp = 5 * time.Minute + ) + + var caches []*Cache + if err := h.engine.Exec(func(sess *xorm.Session) error { + return sess.Where(builder.And(builder.Lt{"used_at": time.Now().Add(-keepTemp).Unix()}, builder.Eq{"complete": false})). + Find(&caches) + }); err != nil { + logger.Warnf("find caches: %v", err) + } else { + for _, cache := range caches { + h.storage.Remove(cache.ID) + if err := h.engine.Exec(func(sess *xorm.Session) error { + _, err := sess.Delete(cache) + return err + }); err != nil { + logger.Warnf("delete cache: %v", err) + continue + } + logger.Infof("deleted cache: %+v", cache) + } + } + + caches = caches[:0] + if err := h.engine.Exec(func(sess *xorm.Session) error { + return sess.Where(builder.Lt{"used_at": time.Now().Add(-keepUnused).Unix()}). + Find(&caches) + }); err != nil { + logger.Warnf("find caches: %v", err) + } else { + for _, cache := range caches { + h.storage.Remove(cache.ID) + if err := h.engine.Exec(func(sess *xorm.Session) error { + _, err := sess.Delete(cache) + return err + }); err != nil { + logger.Warnf("delete cache: %v", err) + continue + } + logger.Infof("deleted cache: %+v", cache) + } + } + + caches = caches[:0] + if err := h.engine.Exec(func(sess *xorm.Session) error { + return sess.Where(builder.Lt{"created_at": time.Now().Add(-keepUsed).Unix()}). + Find(&caches) + }); err != nil { + logger.Warnf("find caches: %v", err) + } else { + for _, cache := range caches { + h.storage.Remove(cache.ID) + if err := h.engine.Exec(func(sess *xorm.Session) error { + _, err := sess.Delete(cache) + return err + }); err != nil { + logger.Warnf("delete cache: %v", err) + continue + } + logger.Infof("deleted cache: %+v", cache) + } + } +} diff --git a/artifactcache/model.go b/artifactcache/model.go new file mode 100644 index 0000000..766594e --- /dev/null +++ b/artifactcache/model.go @@ -0,0 +1,30 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package artifactcache + +import ( + "fmt" + "net/http" +) + +type Cache struct { + ID int64 `xorm:"id pk autoincr" json:"-"` + Key string `xorm:"TEXT index unique(key_version)" json:"key"` + Version string `xorm:"TEXT unique(key_version)" json:"version"` + Size int64 `json:"cacheSize"` + Complete bool `xorm:"index(complete_used_at)" json:"-"` + UsedAt int64 `xorm:"index(complete_used_at) updated" json:"-"` + CreatedAt int64 `xorm:"index created" json:"-"` +} + +// Bind implements render.Binder +func (c *Cache) Bind(_ *http.Request) error { + if c.Key == "" { + return fmt.Errorf("missing key") + } + if c.Version == "" { + return fmt.Errorf("missing version") + } + return nil +} diff --git a/artifactcache/storage.go b/artifactcache/storage.go new file mode 100644 index 0000000..95c1bc2 --- /dev/null +++ b/artifactcache/storage.go @@ -0,0 +1,129 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package artifactcache + +import ( + "fmt" + "io" + "net/http" + "os" + "path/filepath" +) + +type Storage struct { + rootDir string +} + +func NewStorage(rootDir string) (*Storage, error) { + if err := os.MkdirAll(rootDir, 0o755); err != nil { + return nil, err + } + return &Storage{ + rootDir: rootDir, + }, nil +} + +func (s *Storage) Exist(id int64) (bool, error) { + name := s.filename(id) + if _, err := os.Stat(name); os.IsNotExist(err) { + return false, nil + } else if err != nil { + return false, err + } + return true, nil +} + +func (s *Storage) Write(id int64, offset int64, reader io.Reader) error { + name := s.tempName(id, offset) + if err := os.MkdirAll(filepath.Dir(name), 0o755); err != nil { + return err + } + file, err := os.Create(name) + if err != nil { + return err + } + defer file.Close() + + _, err = io.Copy(file, reader) + return err +} + +func (s *Storage) Commit(id int64, size int64) error { + defer func() { + _ = os.RemoveAll(s.tempDir(id)) + }() + + name := s.filename(id) + tempNames, err := s.tempNames(id) + if err != nil { + return err + } + + if err := os.MkdirAll(filepath.Dir(name), 0o755); err != nil { + return err + } + file, err := os.Create(name) + if err != nil { + return err + } + defer file.Close() + + var written int64 + for _, v := range tempNames { + f, err := os.Open(v) + if err != nil { + return err + } + n, err := io.Copy(file, f) + _ = f.Close() + if err != nil { + return err + } + written += n + } + + if written != size { + _ = file.Close() + _ = os.Remove(name) + return fmt.Errorf("broken file: %v != %v", written, size) + } + return nil +} + +func (s *Storage) Serve(w http.ResponseWriter, r *http.Request, id int64) { + name := s.filename(id) + http.ServeFile(w, r, name) +} + +func (s *Storage) Remove(id int64) { + _ = os.Remove(s.filename(id)) + _ = os.RemoveAll(s.tempDir(id)) +} + +func (s *Storage) filename(id int64) string { + return filepath.Join(s.rootDir, fmt.Sprintf("%02x", id%0xff), fmt.Sprint(id)) +} + +func (s *Storage) tempDir(id int64) string { + return filepath.Join(s.rootDir, "tmp", fmt.Sprint(id)) +} + +func (s *Storage) tempName(id, offset int64) string { + return filepath.Join(s.tempDir(id), fmt.Sprintf("%016x", offset)) +} + +func (s *Storage) tempNames(id int64) ([]string, error) { + dir := s.tempDir(id) + files, err := os.ReadDir(dir) + if err != nil { + return nil, err + } + var names []string + for _, v := range files { + if !v.IsDir() { + names = append(names, filepath.Join(dir, v.Name())) + } + } + return names, nil +} diff --git a/artifactcache/util.go b/artifactcache/util.go new file mode 100644 index 0000000..eca173a --- /dev/null +++ b/artifactcache/util.go @@ -0,0 +1,100 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package artifactcache + +import ( + "fmt" + "net" + "net/http" + "strconv" + "strings" + "sync" + + "github.com/go-chi/render" + "xorm.io/xorm" +) + +func responseJson(w http.ResponseWriter, r *http.Request, code int, v ...any) { + render.Status(r, code) + if len(v) == 0 || v[0] == nil { + render.JSON(w, r, struct{}{}) + } else if err, ok := v[0].(error); ok { + logger.Errorf("%v %v: %v", r.Method, r.RequestURI, err) + render.JSON(w, r, map[string]any{ + "error": err.Error(), + }) + } else { + render.JSON(w, r, v[0]) + } +} + +func parseContentRange(s string) (int64, int64, error) { + // support the format like "bytes 11-22/*" only + s, _, _ = strings.Cut(strings.TrimPrefix(s, "bytes "), "/") + s1, s2, _ := strings.Cut(s, "-") + + start, err := strconv.ParseInt(s1, 10, 64) + if err != nil { + return 0, 0, fmt.Errorf("parse %q: %w", s, err) + } + stop, err := strconv.ParseInt(s2, 10, 64) + if err != nil { + return 0, 0, fmt.Errorf("parse %q: %w", s, err) + } + return start, stop, nil +} + +func getOutboundIP() (net.IP, error) { + // FIXME: It makes more sense to use the gateway IP address of container network + if conn, err := net.Dial("udp", "8.8.8.8:80"); err == nil { + defer conn.Close() + return conn.LocalAddr().(*net.UDPAddr).IP, nil + } + if ifaces, err := net.Interfaces(); err == nil { + for _, i := range ifaces { + if addrs, err := i.Addrs(); err == nil { + for _, addr := range addrs { + var ip net.IP + switch v := addr.(type) { + case *net.IPNet: + ip = v.IP + case *net.IPAddr: + ip = v.IP + } + if ip.IsGlobalUnicast() { + return ip, nil + } + } + } + } + } + return nil, fmt.Errorf("no outbound IP address found") +} + +// engine is a wrapper of *xorm.Engine, with a lock. +// To avoid racing of sqlite, we don't care performance here. +type engine struct { + e *xorm.Engine + m sync.Mutex +} + +func (e *engine) Exec(f func(*xorm.Session) error) error { + e.m.Lock() + defer e.m.Unlock() + + sess := e.e.NewSession() + defer sess.Close() + + return f(sess) +} + +func (e *engine) ExecBool(f func(*xorm.Session) (bool, error)) (bool, error) { + e.m.Lock() + defer e.m.Unlock() + + sess := e.e.NewSession() + defer sess.Close() + + return f(sess) +} diff --git a/internal/pkg/client/client.go b/client/client.go similarity index 71% rename from internal/pkg/client/client.go rename to client/client.go index 57f91ad..04b0ae5 100644 --- a/internal/pkg/client/client.go +++ b/client/client.go @@ -1,6 +1,3 @@ -// Copyright 2022 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - package client import ( @@ -9,8 +6,6 @@ import ( ) // A Client manages communication with the runner. -// -//go:generate mockery --name Client type Client interface { pingv1connect.PingServiceClient runnerv1connect.RunnerServiceClient diff --git a/internal/pkg/client/header.go b/client/header.go similarity index 55% rename from internal/pkg/client/header.go rename to client/header.go index 24844fa..df8627a 100644 --- a/internal/pkg/client/header.go +++ b/client/header.go @@ -4,8 +4,7 @@ package client const ( - UUIDHeader = "x-runner-uuid" - TokenHeader = "x-runner-token" - // Deprecated: could be removed after Gitea 1.20 released + UUIDHeader = "x-runner-uuid" + TokenHeader = "x-runner-token" VersionHeader = "x-runner-version" ) diff --git a/internal/pkg/client/http.go b/client/http.go similarity index 74% rename from internal/pkg/client/http.go rename to client/http.go index d365a77..557b4df 100644 --- a/internal/pkg/client/http.go +++ b/client/http.go @@ -1,6 +1,3 @@ -// Copyright 2022 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - package client import ( @@ -11,10 +8,10 @@ import ( "code.gitea.io/actions-proto-go/ping/v1/pingv1connect" "code.gitea.io/actions-proto-go/runner/v1/runnerv1connect" - "connectrpc.com/connect" + "github.com/bufbuild/connect-go" ) -func getHTTPClient(endpoint string, insecure bool) *http.Client { +func getHttpClient(endpoint string, insecure bool) *http.Client { if strings.HasPrefix(endpoint, "https://") && insecure { return &http.Client{ Transport: &http.Transport{ @@ -28,7 +25,7 @@ func getHTTPClient(endpoint string, insecure bool) *http.Client { } // New returns a new runner client. -func New(endpoint string, insecure bool, uuid, token, version string, opts ...connect.ClientOption) *HTTPClient { +func New(endpoint string, insecure bool, uuid, token, runnerVersion string, opts ...connect.ClientOption) *HTTPClient { baseURL := strings.TrimRight(endpoint, "/") + "/api/actions" opts = append(opts, connect.WithInterceptors(connect.UnaryInterceptorFunc(func(next connect.UnaryFunc) connect.UnaryFunc { @@ -39,9 +36,8 @@ func New(endpoint string, insecure bool, uuid, token, version string, opts ...co if token != "" { req.Header().Set(TokenHeader, token) } - // TODO: version will be removed from request header after Gitea 1.20 released. - if version != "" { - req.Header().Set(VersionHeader, version) + if runnerVersion != "" { + req.Header().Set(VersionHeader, runnerVersion) } return next(ctx, req) } @@ -49,12 +45,12 @@ func New(endpoint string, insecure bool, uuid, token, version string, opts ...co return &HTTPClient{ PingServiceClient: pingv1connect.NewPingServiceClient( - getHTTPClient(endpoint, insecure), + getHttpClient(endpoint, insecure), baseURL, opts..., ), RunnerServiceClient: runnerv1connect.NewRunnerServiceClient( - getHTTPClient(endpoint, insecure), + getHttpClient(endpoint, insecure), baseURL, opts..., ), diff --git a/internal/app/cmd/cmd.go b/cmd/cmd.go similarity index 63% rename from internal/app/cmd/cmd.go rename to cmd/cmd.go index 48341dc..3b82c14 100644 --- a/internal/app/cmd/cmd.go +++ b/cmd/cmd.go @@ -1,6 +1,4 @@ -// Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT - package cmd import ( @@ -10,17 +8,19 @@ import ( "github.com/spf13/cobra" - "gitea.com/gitea/act_runner/internal/pkg/config" - "gitea.com/gitea/act_runner/internal/pkg/ver" + "codeberg.org/forgejo/runner/config" ) +// the version of act_runner +var version = "develop" + func Execute(ctx context.Context) { // ./act_runner rootCmd := &cobra.Command{ - Use: "forgejo-runner [event name to run]\nIf no event name passed, will default to \"on: push\"", - Short: "Run Forgejo Actions locally by specifying the event name (e.g. `push`) or an action name directly.", + Use: "act_runner [event name to run]\nIf no event name passed, will default to \"on: push\"", + Short: "Run GitHub actions locally by specifying the event name (e.g. `push`) or an action name directly.", Args: cobra.MaximumNArgs(1), - Version: ver.Version(), + Version: version, SilenceUsage: true, } configFile := "" @@ -41,8 +41,6 @@ func Execute(ctx context.Context) { registerCmd.Flags().StringVar(®Args.Labels, "labels", "", "Runner tags, comma separated") rootCmd.AddCommand(registerCmd) - rootCmd.AddCommand(createRunnerFileCmd(ctx, &configFile)) - // ./act_runner daemon daemonCmd := &cobra.Command{ Use: "daemon", @@ -65,19 +63,6 @@ func Execute(ctx context.Context) { }, }) - // ./act_runner cache-server - var cacheArgs cacheServerArgs - cacheCmd := &cobra.Command{ - Use: "cache-server", - Short: "Start a cache server for the cache action", - Args: cobra.MaximumNArgs(0), - RunE: runCacheServer(ctx, &configFile, &cacheArgs), - } - cacheCmd.Flags().StringVarP(&cacheArgs.Dir, "dir", "d", "", "Cache directory") - cacheCmd.Flags().StringVarP(&cacheArgs.Host, "host", "s", "", "Host of the cache server") - cacheCmd.Flags().Uint16VarP(&cacheArgs.Port, "port", "p", 0, "Port of the cache server") - rootCmd.AddCommand(cacheCmd) - // hide completion command rootCmd.CompletionOptions.HiddenDefaultCmd = true diff --git a/cmd/daemon.go b/cmd/daemon.go new file mode 100644 index 0000000..e8352b9 --- /dev/null +++ b/cmd/daemon.go @@ -0,0 +1,141 @@ +package cmd + +import ( + "context" + "fmt" + "os" + + "github.com/mattn/go-isatty" + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + "golang.org/x/sync/errgroup" + + "codeberg.org/forgejo/runner/artifactcache" + "codeberg.org/forgejo/runner/client" + "codeberg.org/forgejo/runner/config" + "codeberg.org/forgejo/runner/engine" + "codeberg.org/forgejo/runner/poller" + "codeberg.org/forgejo/runner/runtime" +) + +func runDaemon(ctx context.Context, configFile *string) func(cmd *cobra.Command, args []string) error { + return func(cmd *cobra.Command, args []string) error { + log.Infoln("Starting runner daemon") + + cfg, err := config.LoadDefault(*configFile) + if err != nil { + return fmt.Errorf("invalid configuration: %w", err) + } + + initLogging(cfg) + + reg, err := config.LoadRegistration(cfg.Runner.File) + if os.IsNotExist(err) { + log.Error("registration file not found, please register the runner first") + return err + } else if err != nil { + return fmt.Errorf("failed to load registration file: %w", err) + } + + // require docker if a runner label uses a docker backend + needsDocker := false + for _, l := range reg.Labels { + _, schema, _, _ := runtime.ParseLabel(l) + if schema == "docker" { + needsDocker = true + break + } + } + + if needsDocker { + // try to connect to docker daemon + // if failed, exit with error + if err := engine.Start(ctx); err != nil { + log.WithError(err).Fatalln("failed to connect docker daemon engine") + } + } + + var g errgroup.Group + + cli := client.New( + reg.Address, + cfg.Runner.Insecure, + reg.UUID, + reg.Token, + version, + ) + + if cfg.Runner.Envs == nil { + cfg.Runner.Envs = make(map[string]string, 10) + } + + if _, ok := cfg.Runner.Envs["GITHUB_SERVER_URL"]; !ok { + cfg.Runner.Envs["GITHUB_SERVER_URL"] = reg.Address + } + + runner := &runtime.Runner{ + Client: cli, + Machine: reg.Name, + ForgeInstance: reg.Address, + Environ: cfg.Runner.Envs, + Labels: reg.Labels, + Network: cfg.Container.Network, + Version: version, + } + + if *cfg.Cache.Enabled { + if handler, err := artifactcache.NewHandler(cfg.Cache.Dir, cfg.Cache.Host, cfg.Cache.Port); err != nil { + log.Errorf("cannot init cache server, it will be disabled: %v", err) + } else { + log.Infof("cache handler listens on: %v", handler.ExternalURL()) + runner.CacheHandler = handler + } + } + + poller := poller.New( + cli, + runner.Run, + cfg, + ) + + g.Go(func() error { + l := log.WithField("capacity", cfg.Runner.Capacity). + WithField("endpoint", reg.Address) + l.Infoln("polling the remote server") + + if err := poller.Poll(ctx); err != nil { + l.Errorf("poller error: %v", err) + } + poller.Wait() + return nil + }) + + err = g.Wait() + if err != nil { + log.WithError(err). + Errorln("shutting down the server") + } + return err + } +} + +// initLogging setup the global logrus logger. +func initLogging(cfg *config.Config) { + isTerm := isatty.IsTerminal(os.Stdout.Fd()) + log.SetFormatter(&log.TextFormatter{ + DisableColors: !isTerm, + FullTimestamp: true, + }) + + if l := cfg.Log.Level; l != "" { + level, err := log.ParseLevel(l) + if err != nil { + log.WithError(err). + Errorf("invalid log level: %q", l) + } + if log.GetLevel() != level { + log.Infof("log level changed to %v", level) + log.SetLevel(level) + } + } +} diff --git a/internal/app/cmd/exec.go b/cmd/exec.go similarity index 84% rename from internal/app/cmd/exec.go rename to cmd/exec.go index 3e111fe..82b365b 100644 --- a/internal/app/cmd/exec.go +++ b/cmd/exec.go @@ -13,9 +13,7 @@ import ( "strings" "time" - "github.com/docker/docker/api/types/container" "github.com/joho/godotenv" - "github.com/nektos/act/pkg/artifactcache" "github.com/nektos/act/pkg/artifacts" "github.com/nektos/act/pkg/common" "github.com/nektos/act/pkg/model" @@ -23,6 +21,8 @@ import ( log "github.com/sirupsen/logrus" "github.com/spf13/cobra" "golang.org/x/term" + + "codeberg.org/forgejo/runner/artifactcache" ) type executeArgs struct { @@ -39,7 +39,7 @@ type executeArgs struct { envs []string envfile string secrets []string - defaultActionsURL string + defaultActionsUrl string insecureSecrets bool privileged bool usernsMode string @@ -48,7 +48,6 @@ type executeArgs struct { useGitIgnore bool containerCapAdd []string containerCapDrop []string - containerOptions string artifactServerPath string artifactServerAddr string artifactServerPort string @@ -57,9 +56,6 @@ type executeArgs struct { dryrun bool image string cacheHandler *artifactcache.Handler - network string - enableIPv6 bool - githubInstance string } // WorkflowsPath returns path to workflow file(s) @@ -253,7 +249,7 @@ func runExecList(ctx context.Context, planner model.WorkflowPlanner, execArgs *e var filterPlan *model.Plan // Determine the event name to be filtered - var filterEventName string + var filterEventName string = "" if len(execArgs.event) > 0 { log.Infof("Using chosed event for filtering: %s", execArgs.event) @@ -290,7 +286,7 @@ func runExecList(ctx context.Context, planner model.WorkflowPlanner, execArgs *e } } - _ = printList(filterPlan) + printList(filterPlan) return nil } @@ -317,7 +313,7 @@ func runExec(ctx context.Context, execArgs *executeArgs) func(cmd *cobra.Command if len(execArgs.event) > 0 { log.Infof("Using chosed event for filtering: %s", execArgs.event) - eventName = execArgs.event + eventName = args[0] } else if len(events) == 1 && len(events[0]) > 0 { log.Infof("Using the only detected workflow event: %s", events[0]) eventName = events[0] @@ -352,31 +348,13 @@ func runExec(ctx context.Context, execArgs *executeArgs) func(cmd *cobra.Command } // init a cache server - handler, err := artifactcache.StartHandler("", "", 0, log.StandardLogger().WithField("module", "cache_request")) + handler, err := artifactcache.NewHandler("", "", 0) if err != nil { return err } log.Infof("cache handler listens on: %v", handler.ExternalURL()) execArgs.cacheHandler = handler - if len(execArgs.artifactServerAddr) == 0 { - ip := common.GetOutboundIP() - if ip == nil { - return fmt.Errorf("unable to determine outbound IP address") - } - execArgs.artifactServerAddr = ip.String() - } - - if len(execArgs.artifactServerPath) == 0 { - tempDir, err := os.MkdirTemp("", "gitea-act-") - if err != nil { - fmt.Println(err) - } - defer os.RemoveAll(tempDir) - - execArgs.artifactServerPath = tempDir - } - // run the plan config := &runner.Config{ Workdir: execArgs.Workdir(), @@ -394,46 +372,46 @@ func runExec(ctx context.Context, execArgs *executeArgs) func(cmd *cobra.Command ContainerArchitecture: execArgs.containerArchitecture, ContainerDaemonSocket: execArgs.containerDaemonSocket, UseGitIgnore: execArgs.useGitIgnore, - GitHubInstance: execArgs.githubInstance, - ContainerCapAdd: execArgs.containerCapAdd, - ContainerCapDrop: execArgs.containerCapDrop, - ContainerOptions: execArgs.containerOptions, - AutoRemove: true, - ArtifactServerPath: execArgs.artifactServerPath, - ArtifactServerPort: execArgs.artifactServerPort, - ArtifactServerAddr: execArgs.artifactServerAddr, - NoSkipCheckout: execArgs.noSkipCheckout, - // PresetGitHubContext: preset, - // EventJSON: string(eventJSON), - ContainerNamePrefix: fmt.Sprintf("FORGEJO-ACTIONS-TASK-%s", eventName), - ContainerMaxLifetime: maxLifetime, - ContainerNetworkMode: container.NetworkMode(execArgs.network), - ContainerNetworkEnableIPv6: execArgs.enableIPv6, - DefaultActionInstance: execArgs.defaultActionsURL, + // GitHubInstance: t.client.Address(), + ContainerCapAdd: execArgs.containerCapAdd, + ContainerCapDrop: execArgs.containerCapDrop, + AutoRemove: true, + ArtifactServerPath: execArgs.artifactServerPath, + ArtifactServerPort: execArgs.artifactServerPort, + NoSkipCheckout: execArgs.noSkipCheckout, + // PresetGitHubContext: preset, + // EventJSON: string(eventJSON), + ContainerNamePrefix: fmt.Sprintf("GITEA-ACTIONS-TASK-%s", eventName), + ContainerMaxLifetime: maxLifetime, + ContainerNetworkMode: "bridge", + DefaultActionInstance: execArgs.defaultActionsUrl, PlatformPicker: func(_ []string) string { return execArgs.image }, - ValidVolumes: []string{"**"}, // All volumes are allowed for `exec` command } - config.Env["ACT_EXEC"] = "true" - - if t := config.Secrets["GITEA_TOKEN"]; t != "" { - config.Token = t - } else if t := config.Secrets["GITHUB_TOKEN"]; t != "" { - config.Token = t - } - - if !execArgs.debug { - logLevel := log.InfoLevel - config.JobLoggerLevel = &logLevel - } + // TODO: handle log level config + // waiting https://gitea.com/gitea/act/pulls/19 + // if !execArgs.debug { + // logLevel := log.Level(log.InfoLevel) + // config.JobLoggerLevel = &logLevel + // } r, err := runner.New(config) if err != nil { return err } + if len(execArgs.artifactServerPath) == 0 { + tempDir, err := os.MkdirTemp("", "gitea-act-") + if err != nil { + fmt.Println(err) + } + defer os.RemoveAll(tempDir) + + execArgs.artifactServerPath = tempDir + } + artifactCancel := artifacts.Serve(ctx, execArgs.artifactServerPath, execArgs.artifactServerAddr, execArgs.artifactServerPort) log.Debugf("artifacts server started at %s:%s", execArgs.artifactServerPath, execArgs.artifactServerPort) @@ -478,18 +456,13 @@ func loadExecCmd(ctx context.Context) *cobra.Command { execCmd.Flags().BoolVar(&execArg.useGitIgnore, "use-gitignore", true, "Controls whether paths specified in .gitignore should be copied into container") execCmd.Flags().StringArrayVarP(&execArg.containerCapAdd, "container-cap-add", "", []string{}, "kernel capabilities to add to the workflow containers (e.g. --container-cap-add SYS_PTRACE)") execCmd.Flags().StringArrayVarP(&execArg.containerCapDrop, "container-cap-drop", "", []string{}, "kernel capabilities to remove from the workflow containers (e.g. --container-cap-drop SYS_PTRACE)") - execCmd.Flags().StringVarP(&execArg.containerOptions, "container-opts", "", "", "container options") execCmd.PersistentFlags().StringVarP(&execArg.artifactServerPath, "artifact-server-path", "", ".", "Defines the path where the artifact server stores uploads and retrieves downloads from. If not specified the artifact server will not start.") - execCmd.PersistentFlags().StringVarP(&execArg.artifactServerAddr, "artifact-server-addr", "", "", "Defines the address where the artifact server listens") execCmd.PersistentFlags().StringVarP(&execArg.artifactServerPort, "artifact-server-port", "", "34567", "Defines the port where the artifact server listens (will only bind to localhost).") - execCmd.PersistentFlags().StringVarP(&execArg.defaultActionsURL, "default-actions-url", "", "https://code.forgejo.org", "Defines the default base url of the action.") + execCmd.PersistentFlags().StringVarP(&execArg.defaultActionsUrl, "default-actions-url", "", "https://code.forgejo.org", "Defines the default url of action instance.") execCmd.PersistentFlags().BoolVarP(&execArg.noSkipCheckout, "no-skip-checkout", "", false, "Do not skip actions/checkout") execCmd.PersistentFlags().BoolVarP(&execArg.debug, "debug", "d", false, "enable debug log") execCmd.PersistentFlags().BoolVarP(&execArg.dryrun, "dryrun", "n", false, "dryrun mode") - execCmd.PersistentFlags().StringVarP(&execArg.image, "image", "i", "node:20-bullseye", "Docker image to use. Use \"-self-hosted\" to run directly on the host.") - execCmd.PersistentFlags().StringVarP(&execArg.network, "network", "", "", "Specify the network to which the container will connect") - execCmd.PersistentFlags().BoolVarP(&execArg.enableIPv6, "enable-ipv6", "6", false, "Create network with IPv6 enabled.") - execCmd.PersistentFlags().StringVarP(&execArg.githubInstance, "gitea-instance", "", "", "Gitea instance to use.") + execCmd.PersistentFlags().StringVarP(&execArg.image, "image", "i", "node:16-bullseye", "docker image to use") return execCmd } diff --git a/internal/app/cmd/register.go b/cmd/register.go similarity index 67% rename from internal/app/cmd/register.go rename to cmd/register.go index 803511a..91c84a8 100644 --- a/internal/app/cmd/register.go +++ b/cmd/register.go @@ -15,15 +15,15 @@ import ( pingv1 "code.gitea.io/actions-proto-go/ping/v1" runnerv1 "code.gitea.io/actions-proto-go/runner/v1" - "connectrpc.com/connect" + + "codeberg.org/forgejo/runner/client" + "codeberg.org/forgejo/runner/config" + "codeberg.org/forgejo/runner/runtime" + + "github.com/bufbuild/connect-go" "github.com/mattn/go-isatty" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" - - "gitea.com/gitea/act_runner/internal/pkg/client" - "gitea.com/gitea/act_runner/internal/pkg/config" - "gitea.com/gitea/act_runner/internal/pkg/labels" - "gitea.com/gitea/act_runner/internal/pkg/ver" ) // runRegister registers a runner to the server @@ -38,7 +38,7 @@ func runRegister(ctx context.Context, regArgs *registerArgs, configFile *string) log.SetLevel(log.DebugLevel) log.Infof("Registering runner, arch=%s, os=%s, version=%s.", - goruntime.GOARCH, goruntime.GOOS, ver.Version()) + goruntime.GOARCH, goruntime.GOOS, version) // runner always needs root permission if os.Getuid() != 0 { @@ -47,12 +47,12 @@ func runRegister(ctx context.Context, regArgs *registerArgs, configFile *string) } if regArgs.NoInteractive { - if err := registerNoInteractive(ctx, *configFile, regArgs); err != nil { + if err := registerNoInteractive(*configFile, regArgs); err != nil { return err } } else { go func() { - if err := registerInteractive(ctx, *configFile); err != nil { + if err := registerInteractive(*configFile); err != nil { log.Fatal(err) return } @@ -85,20 +85,23 @@ const ( StageInputInstance StageInputToken StageInputRunnerName - StageInputLabels + StageInputCustomLabels StageWaitingForRegistration StageExit ) var defaultLabels = []string{ - "docker:docker://node:20-bullseye", + "ubuntu-latest:docker://node:16-bullseye", + "ubuntu-22.04:docker://node:16-bullseye", // There's no node:16-bookworm yet + "ubuntu-20.04:docker://node:16-bullseye", + "ubuntu-18.04:docker://node:16-buster", } type registerInputs struct { InstanceAddr string Token string RunnerName string - Labels []string + CustomLabels []string } func (r *registerInputs) validate() error { @@ -108,22 +111,22 @@ func (r *registerInputs) validate() error { if r.Token == "" { return fmt.Errorf("token is empty") } - if len(r.Labels) > 0 { - return validateLabels(r.Labels) + if len(r.CustomLabels) > 0 { + return validateLabels(r.CustomLabels) } return nil } -func validateLabels(ls []string) error { - for _, label := range ls { - if _, err := labels.Parse(label); err != nil { +func validateLabels(labels []string) error { + for _, label := range labels { + if _, _, _, err := runtime.ParseLabel(label); err != nil { return err } } return nil } -func (r *registerInputs) assignToNext(stage registerStage, value string, cfg *config.Config) registerStage { +func (r *registerInputs) assignToNext(stage registerStage, value string) registerStage { // must set instance address and token. // if empty, keep current stage. if stage == StageInputInstance || stage == StageInputToken { @@ -151,40 +154,23 @@ func (r *registerInputs) assignToNext(stage registerStage, value string, cfg *co return StageInputRunnerName case StageInputRunnerName: r.RunnerName = value - // if there are some labels configured in config file, skip input labels stage - if len(cfg.Runner.Labels) > 0 { - ls := make([]string, 0, len(cfg.Runner.Labels)) - for _, l := range cfg.Runner.Labels { - _, err := labels.Parse(l) - if err != nil { - log.WithError(err).Warnf("ignored invalid label %q", l) - continue - } - ls = append(ls, l) - } - if len(ls) == 0 { - log.Warn("no valid labels configured in config file, runner may not be able to pick up jobs") - } - r.Labels = ls - return StageWaitingForRegistration - } - return StageInputLabels - case StageInputLabels: - r.Labels = defaultLabels + return StageInputCustomLabels + case StageInputCustomLabels: + r.CustomLabels = defaultLabels if value != "" { - r.Labels = strings.Split(value, ",") + r.CustomLabels = strings.Split(value, ",") } - if validateLabels(r.Labels) != nil { - log.Infoln("Invalid labels, please input again, leave blank to use the default labels (for example, ubuntu-20.04:docker://node:20-bookworm,ubuntu-18.04:docker://node:20-bookworm)") - return StageInputLabels + if validateLabels(r.CustomLabels) != nil { + log.Infoln("Invalid labels, please input again, leave blank to use the default labels (for example, ubuntu-20.04:docker://node:16-bullseye,ubuntu-18.04:docker://node:16-buster,linux_arm:host)") + return StageInputCustomLabels } return StageWaitingForRegistration } return StageUnknown } -func registerInteractive(ctx context.Context, configFile string) error { +func registerInteractive(configFile string) error { var ( reader = bufio.NewReader(os.Stdin) stage = StageInputInstance @@ -206,14 +192,15 @@ func registerInteractive(ctx context.Context, configFile string) error { if err != nil { return err } - stage = inputs.assignToNext(stage, strings.TrimSpace(cmdString), cfg) + stage = inputs.assignToNext(stage, strings.TrimSpace(cmdString)) if stage == StageWaitingForRegistration { - log.Infof("Registering runner, name=%s, instance=%s, labels=%v.", inputs.RunnerName, inputs.InstanceAddr, inputs.Labels) - if err := doRegister(ctx, cfg, inputs); err != nil { - return fmt.Errorf("Failed to register runner: %w", err) + log.Infof("Registering runner, name=%s, instance=%s, labels=%v.", inputs.RunnerName, inputs.InstanceAddr, inputs.CustomLabels) + if err := doRegister(cfg, inputs); err != nil { + log.Errorf("Failed to register runner: %v", err) + } else { + log.Infof("Runner registered successfully.") } - log.Infof("Runner registered successfully.") return nil } @@ -233,20 +220,20 @@ func printStageHelp(stage registerStage) { case StageOverwriteLocalConfig: log.Infoln("Runner is already registered, overwrite local config? [y/N]") case StageInputInstance: - log.Infoln("Enter the Forgejo instance URL (for example, https://next.forgejo.org/):") + log.Infoln("Enter the Gitea instance URL (for example, https://gitea.com/):") case StageInputToken: log.Infoln("Enter the runner token:") case StageInputRunnerName: hostname, _ := os.Hostname() log.Infof("Enter the runner name (if set empty, use hostname: %s):\n", hostname) - case StageInputLabels: - log.Infoln("Enter the runner labels, leave blank to use the default labels (comma-separated, for example, ubuntu-20.04:docker://node:20-bookworm,ubuntu-18.04:docker://node:20-bookworm):") + case StageInputCustomLabels: + log.Infoln("Enter the runner labels, leave blank to use the default labels (comma-separated, for example, ubuntu-20.04:docker://node:16-bullseye,ubuntu-18.04:docker://node:16-buster,linux_arm:host):") case StageWaitingForRegistration: log.Infoln("Waiting for registration...") } } -func registerNoInteractive(ctx context.Context, configFile string, regArgs *registerArgs) error { +func registerNoInteractive(configFile string, regArgs *registerArgs) error { cfg, err := config.LoadDefault(configFile) if err != nil { return err @@ -255,21 +242,12 @@ func registerNoInteractive(ctx context.Context, configFile string, regArgs *regi InstanceAddr: regArgs.InstanceAddr, Token: regArgs.Token, RunnerName: regArgs.RunnerName, - Labels: defaultLabels, + CustomLabels: defaultLabels, } regArgs.Labels = strings.TrimSpace(regArgs.Labels) - // command line flag. if regArgs.Labels != "" { - inputs.Labels = strings.Split(regArgs.Labels, ",") + inputs.CustomLabels = strings.Split(regArgs.Labels, ",") } - // specify labels in config file. - if len(cfg.Runner.Labels) > 0 { - if regArgs.Labels != "" { - log.Warn("Labels from command will be ignored, use labels defined in config file.") - } - inputs.Labels = cfg.Runner.Labels - } - if inputs.RunnerName == "" { inputs.RunnerName, _ = os.Hostname() log.Infof("Runner name is empty, use hostname '%s'.", inputs.RunnerName) @@ -278,21 +256,24 @@ func registerNoInteractive(ctx context.Context, configFile string, regArgs *regi log.WithError(err).Errorf("Invalid input, please re-run act command.") return nil } - if err := doRegister(ctx, cfg, inputs); err != nil { - return fmt.Errorf("Failed to register runner: %w", err) + if err := doRegister(cfg, inputs); err != nil { + log.Errorf("Failed to register runner: %v", err) + return nil } log.Infof("Runner registered successfully.") return nil } -func doRegister(ctx context.Context, cfg *config.Config, inputs *registerInputs) error { +func doRegister(cfg *config.Config, inputs *registerInputs) error { + ctx := context.Background() + // initial http client cli := client.New( inputs.InstanceAddr, cfg.Runner.Insecure, "", "", - ver.Version(), + version, ) for { @@ -301,7 +282,7 @@ func doRegister(ctx context.Context, cfg *config.Config, inputs *registerInputs) })) select { case <-ctx.Done(): - return ctx.Err() + return nil default: } if ctx.Err() != nil { @@ -309,11 +290,11 @@ func doRegister(ctx context.Context, cfg *config.Config, inputs *registerInputs) } if err != nil { log.WithError(err). - Errorln("Cannot ping the Forgejo instance server") + Errorln("Cannot ping the Gitea instance server") // TODO: if ping failed, retry or exit time.Sleep(time.Second) } else { - log.Debugln("Successfully pinged the Forgejo instance server") + log.Debugln("Successfully pinged the Gitea instance server") break } } @@ -322,21 +303,19 @@ func doRegister(ctx context.Context, cfg *config.Config, inputs *registerInputs) Name: inputs.RunnerName, Token: inputs.Token, Address: inputs.InstanceAddr, - Labels: inputs.Labels, + Labels: inputs.CustomLabels, } - ls := make([]string, len(reg.Labels)) + labels := make([]string, len(reg.Labels)) for i, v := range reg.Labels { - l, _ := labels.Parse(v) - ls[i] = l.Name + l, _, _, _ := runtime.ParseLabel(v) + labels[i] = l } // register new runner. resp, err := cli.Register(ctx, connect.NewRequest(&runnerv1.RegisterRequest{ Name: reg.Name, Token: reg.Token, - Version: ver.Version(), - AgentLabels: ls, // Could be removed after Gitea 1.20 - Labels: ls, + AgentLabels: labels, })) if err != nil { log.WithError(err).Error("poller: cannot register new runner") diff --git a/config/config.example.yaml b/config/config.example.yaml new file mode 100644 index 0000000..1f05f68 --- /dev/null +++ b/config/config.example.yaml @@ -0,0 +1,42 @@ +# Example configuration file, it's safe to copy this as the default config file without any modification. + +log: + # The level of logging, can be trace, debug, info, warn, error, fatal + level: info + +runner: + # Where to store the registration result. + file: .runner + # Execute how many tasks concurrently at the same time. + capacity: 1 + # Extra environment variables to run jobs. + envs: + A_TEST_ENV_NAME_1: a_test_env_value_1 + A_TEST_ENV_NAME_2: a_test_env_value_2 + # Extra environment variables to run jobs from a file. + # It will be ignored if it's empty or the file doesn't exist. + env_file: .env + # The timeout for a job to be finished. + # Please note that the Gitea instance also has a timeout (3h by default) for the job. + # So the job could be stopped by the Gitea instance if it's timeout is shorter than this. + timeout: 3h + # Whether skip verifying the TLS certificate of the Gitea instance. + insecure: false + +cache: + # Enable cache server to use actions/cache. + enabled: true + # The directory to store the cache data. + # If it's empty, the cache data will be stored in $HOME/.cache/actcache. + dir: "" + # The host of the cache server. + # It's not for the address to listen, but the address to connect from job containers. + # So 0.0.0.0 is a bad choice, leave it empty to detect automatically. + host: "" + # The port of the cache server. + # 0 means to use a random available port. + port: 0 + +container: + # Which network to use for the job containers. + network: bridge diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000..40fef9a --- /dev/null +++ b/config/config.go @@ -0,0 +1,92 @@ +package config + +import ( + "fmt" + "os" + "path/filepath" + "time" + + "github.com/joho/godotenv" + "gopkg.in/yaml.v3" +) + +type Config struct { + Log struct { + Level string `yaml:"level"` + } `yaml:"log"` + Runner struct { + File string `yaml:"file"` + Capacity int `yaml:"capacity"` + Envs map[string]string `yaml:"envs"` + EnvFile string `yaml:"env_file"` + Timeout time.Duration `yaml:"timeout"` + Insecure bool `yaml:"insecure"` + } `yaml:"runner"` + Cache struct { + Enabled *bool `yaml:"enabled"` // pointer to distinguish between false and not set, and it will be true if not set + Dir string `yaml:"dir"` + Host string `yaml:"host"` + Port uint16 `yaml:"port"` + } `yaml:"cache"` + Container struct { + Network string `yaml:"network"` + } +} + +// LoadDefault returns the default configuration. +// If file is not empty, it will be used to load the configuration. +func LoadDefault(file string) (*Config, error) { + cfg := &Config{} + if file != "" { + f, err := os.Open(file) + if err != nil { + return nil, err + } + defer f.Close() + decoder := yaml.NewDecoder(f) + if err := decoder.Decode(&cfg); err != nil { + return nil, err + } + } + compatibleWithOldEnvs(file != "", cfg) + + if cfg.Runner.EnvFile != "" { + if stat, err := os.Stat(cfg.Runner.EnvFile); err == nil && !stat.IsDir() { + envs, err := godotenv.Read(cfg.Runner.EnvFile) + if err != nil { + return nil, fmt.Errorf("read env file %q: %w", cfg.Runner.EnvFile, err) + } + for k, v := range envs { + cfg.Runner.Envs[k] = v + } + } + } + + if cfg.Log.Level == "" { + cfg.Log.Level = "info" + } + if cfg.Runner.File == "" { + cfg.Runner.File = ".runner" + } + if cfg.Runner.Capacity <= 0 { + cfg.Runner.Capacity = 1 + } + if cfg.Runner.Timeout <= 0 { + cfg.Runner.Timeout = 3 * time.Hour + } + if cfg.Cache.Enabled == nil { + b := true + cfg.Cache.Enabled = &b + } + if *cfg.Cache.Enabled { + if cfg.Cache.Dir == "" { + home, _ := os.UserHomeDir() + cfg.Cache.Dir = filepath.Join(home, ".cache", "actcache") + } + } + if cfg.Container.Network == "" { + cfg.Container.Network = "bridge" + } + + return cfg, nil +} diff --git a/internal/pkg/config/deprecated.go b/config/deprecated.go similarity index 100% rename from internal/pkg/config/deprecated.go rename to config/deprecated.go diff --git a/internal/pkg/config/embed.go b/config/embed.go similarity index 100% rename from internal/pkg/config/embed.go rename to config/embed.go diff --git a/internal/pkg/config/registration.go b/config/registration.go similarity index 100% rename from internal/pkg/config/registration.go rename to config/registration.go diff --git a/contrib/forgejo-runner.service b/contrib/forgejo-runner.service deleted file mode 100644 index ace922a..0000000 --- a/contrib/forgejo-runner.service +++ /dev/null @@ -1,18 +0,0 @@ -[Unit] -Description=Forgejo Runner -Documentation=https://forgejo.org/docs/latest/admin/actions/ -After=docker.service - -[Service] -ExecStart=forgejo-runner daemon -ExecReload=/bin/kill -s HUP $MAINPID - -# This user and working directory must already exist -User=runner -WorkingDirectory=/home/runner -Restart=on-failure -TimeoutSec=0 -RestartSec=10 - -[Install] -WantedBy=multi-user.target diff --git a/engine/docker.go b/engine/docker.go new file mode 100644 index 0000000..c3017e9 --- /dev/null +++ b/engine/docker.go @@ -0,0 +1,37 @@ +package engine + +import ( + "context" + + "github.com/docker/docker/client" +) + +type Docker struct { + client client.APIClient + hidePull bool +} + +func New(opts ...Option) (*Docker, error) { + cli, err := client.NewClientWithOpts(client.FromEnv) + if err != nil { + return nil, err + } + + srv := &Docker{ + client: cli, + } + + // Loop through each option + for _, opt := range opts { + // Call the option giving the instantiated + opt.Apply(srv) + } + + return srv, nil +} + +// Ping pings the Docker daemon. +func (e *Docker) Ping(ctx context.Context) error { + _, err := e.client.Ping(ctx) + return err +} diff --git a/engine/engine.go b/engine/engine.go new file mode 100644 index 0000000..6a37b4b --- /dev/null +++ b/engine/engine.go @@ -0,0 +1,43 @@ +package engine + +import ( + "context" + "fmt" + "time" + + log "github.com/sirupsen/logrus" +) + +// Start start docker engine api loop +func Start(ctx context.Context) error { + engine, err := New() + if err != nil { + return err + } + + count := 0 + for { + err := engine.Ping(ctx) + if err == context.Canceled { + break + } + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + if err != nil { + log.WithError(err). + Errorln("cannot ping the docker daemon") + count++ + if count == 5 { + return fmt.Errorf("retry connect to docker daemon failed: %d times", count) + } + time.Sleep(time.Second) + } else { + log.Infoln("successfully ping the docker daemon") + break + } + } + return nil +} diff --git a/engine/options.go b/engine/options.go new file mode 100644 index 0000000..46c6f9d --- /dev/null +++ b/engine/options.go @@ -0,0 +1,30 @@ +package engine + +import "github.com/docker/docker/client" + +// An Option configures a mutex. +type Option interface { + Apply(*Docker) +} + +// OptionFunc is a function that configure a value. +type OptionFunc func(*Docker) + +// Apply calls f(option) +func (f OptionFunc) Apply(docker *Docker) { + f(docker) +} + +// WithClient set custom client +func WithClient(c client.APIClient) Option { + return OptionFunc(func(q *Docker) { + q.client = c + }) +} + +// WithHidePull hide pull event. +func WithHidePull(v bool) Option { + return OptionFunc(func(q *Docker) { + q.hidePull = v + }) +} diff --git a/examples/README.md b/examples/README.md deleted file mode 100644 index f9dd774..0000000 --- a/examples/README.md +++ /dev/null @@ -1,10 +0,0 @@ -This directory contains a collection of usage and deployment examples. - -Workflow examples can be found [in the documentation](https://forgejo.org/docs/next/user/actions/) -and in the [sources of the setup-forgejo](https://code.forgejo.org/actions/setup-forgejo/src/branch/main/testdata) action. - -| Section | Description | -|-----------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| [`docker`](docker) | using the host docker server by mounting the socket | -| [`docker-compose`](docker-compose) | all in one docker-compose with the Forgejo server, the runner and docker in docker | -| [`kubernetes`](kubernetes) | a sample deployment for the Forgejo runner | diff --git a/examples/docker-compose/README.md b/examples/docker-compose/README.md deleted file mode 100644 index a3e6e9b..0000000 --- a/examples/docker-compose/README.md +++ /dev/null @@ -1,113 +0,0 @@ -## Docker compose with docker-in-docker - -The `compose-forgejo-and-runner.yml` compose file runs a Forgejo -instance and registers a `Forgejo runner`. A docker server is also -launched within a container (using -[dind](https://hub.docker.com/_/docker/tags?name=dind)) and will be -used by the `Forgejo runner` to execute the workflows. - -### Quick start - -```sh -rm -fr /srv/runner-data /srv/forgejo-data -secret=$(openssl rand -hex 20) -sed -i -e "s/{SHARED_SECRET}/$secret/" compose-forgejo-and-runner.yml -docker compose -f compose-forgejo-and-runner.yml up -d -``` - -Visit http://0.0.0.0:8080/admin/actions/runners with login `root` and password `{ROOT_PASSWORD}` and see the runner is registered with the label `docker`. - -> NOTE: the `Your ROOT_URL in app.ini is "http://localhost:3000/", it's unlikely matching the site you are visiting.` message is a warning that can be ignored in the context of this example. - -```sh -docker compose -f compose-forgejo-and-runner.yml -f compose-demo-workflow.yml up demo-workflow -``` - -Visit http://0.0.0.0:8080/root/test/actions/runs/1 and see that the job ran. - - -### Running - -Create a shared secret with: - -```sh -openssl rand -hex 20 -``` - -Replace all occurences of {SHARED_SECRET} in -[compose-forgejo-and-runner.yml](compose-forgejo-and-runner.yml). - -> **NOTE:** a token obtained from the Forgejo web interface cannot be used as a shared secret. - -Replace {ROOT_PASSWORD} with a secure password in -[compose-forgejo-and-runner.yml](compose-forgejo-and-runner.yml). - -```sh -docker compose -f compose-forgejo-and-runner.yml up -Creating docker-compose_docker-in-docker_1 ... done -Creating docker-compose_forgejo_1 ... done -Creating docker-compose_runner-register_1 ... done -... -docker-in-docker_1 | time="2023-08-24T10:22:15.023338461Z" level=warning msg="WARNING: API is accessible on http://0.0.0.0:2376 -... -forgejo_1 | 2023/08/24 10:22:14 ...s/graceful/server.go:75:func1() [D] Starting server on tcp:0.0.0.0:3000 (PID: 19) -... -runner-daemon_1 | time="2023-08-24T10:22:16Z" level=info msg="Starting runner daemon" -``` - -### Manual testing - -To login the Forgejo instance: - -* URL: http://0.0.0.0:8080 -* user: `root` -* password: `{ROOT_PASSWORD}` - -`Forgejo Actions` is enabled by default when creating a repository. - -## Tests workflow - -The `compose-demo-workflow.yml` compose file runs two demo workflows: -* one to verify the `Forgejo runner` can pick up a task from the Forgejo instance -and run it to completion. -* one to verify docker can be run inside the `Forgejo runner` container. - -A new repository is created in root/test with the following workflows: - -#### `.forgejo/workflows/demo.yml`: - -```yaml -on: [push] -jobs: - test: - runs-on: docker - steps: - - run: echo All Good -``` - -#### `.forgejo/workflows/demo_docker.yml` - -```yaml -on: [push] -jobs: - test_docker: - runs-on: ubuntu-22.04 - steps: - - run: docker info -``` - -A wait loop expects the status of the check associated with the -commit in Forgejo to show "success" to assert the workflow was run. - -### Running - -```sh -$ docker-compose -f compose-forgejo-and-runner.yml -f compose-demo-workflow.yml up demo-workflow -... -demo-workflow_1 | To http://forgejo:3000/root/test -demo-workflow_1 | + 5ce134e...261cc79 main -> main (forced update) -demo-workflow_1 | branch 'main' set up to track 'http://root:admin1234@forgejo:3000/root/test/main'. -... -demo-workflow_1 | running -... -``` diff --git a/examples/docker-compose/compose-demo-workflow.yml b/examples/docker-compose/compose-demo-workflow.yml deleted file mode 100644 index 90e7d52..0000000 --- a/examples/docker-compose/compose-demo-workflow.yml +++ /dev/null @@ -1,35 +0,0 @@ -# Copyright 2024 The Forgejo Authors. -# SPDX-License-Identifier: MIT - -services: - - demo-workflow: - image: code.forgejo.org/oci/alpine:3.19 - links: - - forgejo - command: >- - sh -ec ' - apk add --quiet git curl jq ; - mkdir -p /srv/demo ; - cd /srv/demo ; - git init --initial-branch=main ; - mkdir -p .forgejo/workflows ; - echo "{ on: [push], jobs: { test: { runs-on: docker, steps: [ {uses: actions/checkout@v4}, { run: echo All Good } ] } } }" > .forgejo/workflows/demo.yml ; - echo "{ on: [push], jobs: { test_docker: { runs-on: ubuntu-22.04, steps: [ { run: docker info } ] } } }" > .forgejo/workflows/demo_docker.yml ; - git add . ; - git config user.email root@example.com ; - git config user.name username ; - git commit -m demo ; - while : ; do - git push --set-upstream --force http://root:{ROOT_PASSWORD}@forgejo:3000/root/test main && break ; - sleep 5 ; - done ; - sha=`git rev-parse HEAD` ; - for delay in 1 1 1 1 2 5 5 10 10 10 15 30 30 30 30 30 30 30 ; do - curl -sS -f http://forgejo:3000/api/v1/repos/root/test/commits/$$sha/status | jq --raw-output .state | tee status ; - if grep success status ; then echo DEMO WORKFLOW SUCCESS && break ; fi ; - if grep failure status ; then echo DEMO WORKFLOW FAILURE && break ; fi ; - sleep $$delay ; - done ; - grep success status || echo DEMO WORKFLOW FAILURE - ' diff --git a/examples/docker-compose/compose-forgejo-and-runner.yml b/examples/docker-compose/compose-forgejo-and-runner.yml deleted file mode 100644 index 4794985..0000000 --- a/examples/docker-compose/compose-forgejo-and-runner.yml +++ /dev/null @@ -1,93 +0,0 @@ -# Copyright 2024 The Forgejo Authors. -# SPDX-License-Identifier: MIT - -# -# Create a secret with: -# -# openssl rand -hex 20 -# -# Replace all occurences of {SHARED_SECRET} below with the output. -# -# NOTE: a token obtained from the Forgejo web interface cannot be used -# as a shared secret. -# -# Replace {ROOT_PASSWORD} with a secure password -# - -volumes: - docker_certs: - -services: - - docker-in-docker: - image: code.forgejo.org/oci/docker:dind - hostname: docker # Must set hostname as TLS certificates are only valid for docker or localhost - privileged: true - environment: - DOCKER_TLS_CERTDIR: /certs - DOCKER_HOST: docker-in-docker - volumes: - - docker_certs:/certs - - forgejo: - image: codeberg.org/forgejo/forgejo:1.21 - command: >- - bash -c ' - /bin/s6-svscan /etc/s6 & - sleep 10 ; - su -c "forgejo forgejo-cli actions register --secret {SHARED_SECRET}" git ; - su -c "forgejo admin user create --admin --username root --password {ROOT_PASSWORD} --email root@example.com" git ; - sleep infinity - ' - environment: - FORGEJO__security__INSTALL_LOCK: "true" - FORGEJO__log__LEVEL: "debug" - FORGEJO__repository__ENABLE_PUSH_CREATE_USER: "true" - FORGEJO__repository__DEFAULT_PUSH_CREATE_PRIVATE: "false" - FORGEJO__repository__DEFAULT_REPO_UNITS: "repo.code,repo.actions" - volumes: - - /srv/forgejo-data:/data - ports: - - 8080:3000 - - runner-register: - image: code.forgejo.org/forgejo/runner:3.4.1 - links: - - docker-in-docker - - forgejo - environment: - DOCKER_HOST: tcp://docker-in-docker:2376 - volumes: - - /srv/runner-data:/data - user: 0:0 - command: >- - bash -ec ' - while : ; do - forgejo-runner create-runner-file --connect --instance http://forgejo:3000 --name runner --secret {SHARED_SECRET} && break ; - sleep 1 ; - done ; - sed -i -e "s|\"labels\": null|\"labels\": [\"docker:docker://code.forgejo.org/oci/node:20-bookworm\", \"ubuntu-22.04:docker://catthehacker/ubuntu:act-22.04\"]|" .runner ; - forgejo-runner generate-config > config.yml ; - sed -i -e "s|network: .*|network: host|" config.yml ; - sed -i -e "s|^ envs:$$| envs:\n DOCKER_HOST: tcp://docker:2376\n DOCKER_TLS_VERIFY: 1\n DOCKER_CERT_PATH: /certs/client|" config.yml ; - sed -i -e "s|^ options:| options: -v /certs/client:/certs/client|" config.yml ; - sed -i -e "s| valid_volumes: \[\]$$| valid_volumes:\n - /certs/client|" config.yml ; - chown -R 1000:1000 /data - ' - - runner-daemon: - image: code.forgejo.org/forgejo/runner:3.4.1 - links: - - docker-in-docker - - forgejo - environment: - DOCKER_HOST: tcp://docker:2376 - DOCKER_CERT_PATH: /certs/client - DOCKER_TLS_VERIFY: "1" - volumes: - - /srv/runner-data:/data - - docker_certs:/certs - command: >- - bash -c ' - while : ; do test -w .runner && forgejo-runner --config config.yml daemon ; sleep 1 ; done - ' diff --git a/examples/docker/README.md b/examples/docker/README.md deleted file mode 100644 index 628c99c..0000000 --- a/examples/docker/README.md +++ /dev/null @@ -1,12 +0,0 @@ -The following assumes: - -* a docker server runs on the host -* the docker group of the host is GID 133 -* a `.runner` file exists in /tmp/data -* a `runner-config.yml` file exists in /tmp/data - -```sh -docker run -v /var/run/docker.sock:/var/run/docker.sock -v /tmp/data:/data --user 1000:133 --rm code.forgejo.org/forgejo/runner:3.0.0 forgejo-runner --config runner-config.yaml daemon -``` - -The workflows will run using the host docker srever diff --git a/examples/kubernetes/README.md b/examples/kubernetes/README.md deleted file mode 100644 index d00cf1a..0000000 --- a/examples/kubernetes/README.md +++ /dev/null @@ -1,7 +0,0 @@ -## Kubernetes Docker in Docker Deployment - -Registers Kubernetes pod runners using [offline registration](https://forgejo.org/docs/v1.21/admin/actions/#offline-registration), allowing the scaling of runners as needed. - -NOTE: Docker in Docker (dind) requires elevated privileges on Kubernetes. The current way to achieve this is to set the pod `SecurityContext` to `privileged`. Keep in mind that this is a potential security issue that has the potential for a malicious application to break out of the container context. - -[`dind-docker.yaml`](dind-docker.yaml) creates a deployment and secret for Kubernetes to act as a runner. The Docker credentials are re-generated each time the pod connects and does not need to be persisted. diff --git a/examples/kubernetes/dind-docker.yaml b/examples/kubernetes/dind-docker.yaml deleted file mode 100644 index 534432d..0000000 --- a/examples/kubernetes/dind-docker.yaml +++ /dev/null @@ -1,87 +0,0 @@ -# Secret data. -# You will need to retrive this from the web UI, and your Forgejo instance must be running v1.21+ -# Alternatively, create this with -# kubectl create secret generic runner-secret --from-literal=token=your_offline_token_here -apiVersion: v1 -stringData: - token: your_offline_secret_here -kind: Secret -metadata: - name: runner-secret ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - labels: - app: forgejo-runner - name: forgejo-runner -spec: - # Two replicas means that if one is busy, the other can pick up jobs. - replicas: 2 - selector: - matchLabels: - app: forgejo-runner - strategy: {} - template: - metadata: - creationTimestamp: null - labels: - app: forgejo-runner - spec: - restartPolicy: Always - volumes: - - name: docker-certs - emptyDir: {} - - name: runner-data - emptyDir: {} - # Initialise our configuration file using offline registration - # https://forgejo.org/docs/v1.21/admin/actions/#offline-registration - initContainers: - - name: runner-register - image: code.forgejo.org/forgejo/runner:3.2.0 - command: ["forgejo-runner", "register", "--no-interactive", "--token", $(RUNNER_SECRET), "--name", $(RUNNER_NAME), "--instance", $(FORGEJO_INSTANCE_URL)] - env: - - name: RUNNER_NAME - valueFrom: - fieldRef: - fieldPath: metadata.name - - name: RUNNER_SECRET - valueFrom: - secretKeyRef: - name: runner-secret - key: token - - name: FORGEJO_INSTANCE_URL - value: http://forgejo-http.forgejo.svc.cluster.local:3000 - resources: - limits: - cpu: "0.50" - memory: "64Mi" - volumeMounts: - - name: runner-data - mountPath: /data - containers: - - name: runner - image: code.forgejo.org/forgejo/runner:3.0.0 - command: ["sh", "-c", "while ! nc -z localhost 2376 code.forgejo.org/forgejo/act v1.21.3 +replace github.com/nektos/act => code.forgejo.org/forgejo/act v1.3.0 diff --git a/go.sum b/go.sum index 498a542..dcf76d8 100644 --- a/go.sum +++ b/go.sum @@ -1,332 +1,895 @@ -code.forgejo.org/forgejo/act v1.21.3 h1:EeJbrz0aar2QhIcBlOW5gjK1rjrQxcAvQSPpG/R1h5w= -code.forgejo.org/forgejo/act v1.21.3/go.mod h1:+PcvJ9iv+NTFeJSh79ra9Jbk9l0vvyA9D9me5/dbxYM= -code.gitea.io/actions-proto-go v0.4.0 h1:OsPBPhodXuQnsspG1sQ4eRE1PeoZyofd7+i73zCwnsU= -code.gitea.io/actions-proto-go v0.4.0/go.mod h1:mn7Wkqz6JbnTOHQpot3yDeHx+O5C9EGhMEE+htvHBas= -code.gitea.io/gitea-vet v0.2.3 h1:gdFmm6WOTM65rE8FUBTRzeQZYzXePKSSB1+r574hWwI= -code.gitea.io/gitea-vet v0.2.3/go.mod h1:zcNbT/aJEmivCAhfmkHOlT645KNOf9W2KnkLgFjGGfE= -connectrpc.com/connect v1.17.0 h1:W0ZqMhtVzn9Zhn2yATuUokDLO5N+gIuBWMOnsQrfmZk= -connectrpc.com/connect v1.17.0/go.mod h1:0292hj1rnx8oFrStN7cB4jjVBeqs+Yx5yDIC2prWDO8= -dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= -dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= -github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= -github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= -github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= -github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +code.forgejo.org/forgejo/act v1.3.0 h1:AWHBqaTsPo/qnlovUq6sqXUmVogqmFYOg7cCi6Usvuk= +code.forgejo.org/forgejo/act v1.3.0/go.mod h1:mabw6AZAiDgxGlK83orWLrNERSPvgBJzEUS3S7u2bHI= +code.gitea.io/actions-proto-go v0.2.0 h1:nYh9nhhfk67YA4wVNLsCzd//RCvXnljwXClJ33+HPVk= +code.gitea.io/actions-proto-go v0.2.0/go.mod h1:00ys5QDo1iHN1tHNvvddAcy2W/g+425hQya1cCSvq9A= +code.gitea.io/gitea-vet v0.2.3-0.20230113022436-2b1561217fa5 h1:daBEK2GQeqGikJESctP5Cu1i33z5ztAD4kyQWiw185M= +code.gitea.io/gitea-vet v0.2.3-0.20230113022436-2b1561217fa5/go.mod h1:zcNbT/aJEmivCAhfmkHOlT645KNOf9W2KnkLgFjGGfE= +gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s= +gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU= +gitee.com/travelliu/dm v1.8.11192/go.mod h1:DHTzyhCrM843x9VdKVbZ+GKXGRbKM2sJ4LxihRxShkE= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= +github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= +github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= +github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= -github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= -github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= -github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 h1:kkhsdkhsCvIsutKu5zLMgWtgh9YxGCNAw8Ad8hjwfYg= -github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= -github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= -github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= +github.com/Microsoft/hcsshim v0.9.6 h1:VwnDOgLeoi2du6dAznfmspNqTiwczvjv4K7NxuY9jsY= +github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= +github.com/ProtonMail/go-crypto v0.0.0-20220404123522-616f957b79ad h1:K3cVQxnwoVf5R2XLZknct3+tJWocEuJUmF7ZGwB2FK8= +github.com/ProtonMail/go-crypto v0.0.0-20220404123522-616f957b79ad/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= +github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= +github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= +github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk= +github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= +github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= +github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= +github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA= +github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= +github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= -github.com/avast/retry-go/v4 v4.6.0 h1:K9xNA+KeB8HHc2aWFuLb25Offp+0iVRXEvFx8IinRJA= -github.com/avast/retry-go/v4 v4.6.0/go.mod h1:gvWlPhBVsvBbLkVGDg/KwvBv0bEkCOLRRSHKIr2PyOE= -github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= -github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= -github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= -github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= -github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= -github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= -github.com/containerd/containerd v1.7.13 h1:wPYKIeGMN8vaggSKuV1X0wZulpMz4CrgEsZdaCyB6Is= -github.com/containerd/containerd v1.7.13/go.mod h1:zT3up6yTRfEUa6+GsITYIJNgSVL9NQ4x4h1RPzk0Wu4= -github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= -github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0= -github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= -github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= -github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= +github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= +github.com/avast/retry-go/v4 v4.3.1 h1:Mtg11F9PdAIMkMiio2RKcYauoVHjl2aB3zQJJlzD4cE= +github.com/avast/retry-go/v4 v4.3.1/go.mod h1:rg6XFaiuFYII0Xu3RDbZQkxCofFwruZKW8oEF1jpWiU= +github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= +github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bufbuild/connect-go v1.3.1 h1:doJP6Q8Ypg6haUT2IAZJPWHUN9rAUp+F9MfK7yhu1zs= +github.com/bufbuild/connect-go v1.3.1/go.mod h1:9iNvh/NOsfhNBUH5CtvXeVUskQO1xsrEviH7ZArwZ3I= +github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= +github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E= +github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA= +github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= +github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= +github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= +github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= +github.com/containerd/containerd v1.6.18 h1:qZbsLvmyu+Vlty0/Ex5xc0z2YtKpIsb5n45mAMI+2Ns= +github.com/containerd/containerd v1.6.18/go.mod h1:1RdCUu95+gc2v9t3IL+zIlpClSmew7/0YS8O5eQZrOw= +github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= +github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= -github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/cli v25.0.3+incompatible h1:KLeNs7zws74oFuVhgZQ5ONGZiXUUdgsdy6/EsX/6284= -github.com/docker/cli v25.0.3+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= -github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v25.0.6+incompatible h1:5cPwbwriIcsua2REJe8HqQV+6WlWc1byg2QSXzBxBGg= -github.com/docker/docker v25.0.6+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker-credential-helpers v0.8.0 h1:YQFtbBQb4VrpoPxhFuzEBPQ9E16qz5SpHLS+uswaCp8= -github.com/docker/docker-credential-helpers v0.8.0/go.mod h1:UGFXcuoQ5TxPiB54nHOZ32AWRqQdECoh/Mg0AlEYb40= -github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= -github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= +github.com/denisenkom/go-mssqldb v0.10.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/docker/cli v23.0.1+incompatible h1:LRyWITpGzl2C9e9uGxzisptnxAn1zfZKXy13Ul2Q5oM= +github.com/docker/cli v23.0.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68= +github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v23.0.1+incompatible h1:vjgvJZxprTTE1A37nm+CLNAdwu6xZekyoiVlUZEINcY= +github.com/docker/docker v23.0.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A= +github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0= +github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU= -github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= -github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= -github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= -github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= -github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= -github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= -github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY= -github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4= -github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= -github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= -github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= -github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= -github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= -github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= -github.com/go-git/go-git/v5 v5.11.0 h1:XIZc1p+8YzypNr34itUfSvYJcv+eYdTnTvOZ2vD3cA4= -github.com/go-git/go-git/v5 v5.11.0/go.mod h1:6GFcX2P3NM7FPBfpePbpLd21XxsgdAt+lKqXmCUiUCY= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= -github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= -github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= +github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= +github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= +github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= +github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= +github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= +github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= +github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0= +github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= +github.com/go-chi/chi/v5 v5.0.8 h1:lD+NLqFcAi1ovnVZpsnObHGW4xb4J8lNmoYVfECH1Y0= +github.com/go-chi/chi/v5 v5.0.8/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/go-chi/render v1.0.2 h1:4ER/udB0+fMWB2Jlf15RV3F4A2FDuYi/9f+lFttR/Lg= +github.com/go-chi/render v1.0.2/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0= +github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4= +github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= +github.com/go-git/go-billy/v5 v5.2.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= +github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= +github.com/go-git/go-billy/v5 v5.4.1 h1:Uwp5tDRkPr+l/TnbHOQzp+tmJfLceOlbVucgpTz8ix4= +github.com/go-git/go-billy/v5 v5.4.1/go.mod h1:vjbugF6Fz7JIflbVpl1hJsGjSHNltrSw45YK/ukIvQg= +github.com/go-git/go-git-fixtures/v4 v4.2.1 h1:n9gGL1Ct/yIw+nfsfr8s4+sbhT+Ncu2SubfXjIWgci8= +github.com/go-git/go-git-fixtures/v4 v4.2.1/go.mod h1:K8zd3kDUAykwTdDCr+I0per6Y6vMiRR/nnVTBtavnB0= +github.com/go-git/go-git/v5 v5.4.2 h1:BXyZu9t0VkbiHtqrsvdq39UDhGJTl1h55VW6CSC4aY4= +github.com/go-git/go-git/v5 v5.4.2/go.mod h1:gQ1kArt6d+n+BGd+/B/I74HwRTLhth2+zti4ihgckDc= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= +github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/goccy/go-json v0.8.1 h1:4/Wjm0JIJaTDm8K1KcGrLHJoa8EsJ13YWeX+6Kfq6uI= +github.com/goccy/go-json v0.8.1/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +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/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= -github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= -github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= -github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= -github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= +github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= +github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= +github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= +github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= +github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= +github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= +github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= +github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= +github.com/jackc/pgconn v1.4.0/go.mod h1:Y2O3ZDF0q4mMacyWV3AstPJpeHXWGEetiFttmq5lahk= +github.com/jackc/pgconn v1.5.0/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI= +github.com/jackc/pgconn v1.5.1-0.20200601181101-fa742c524853/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI= +github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= +github.com/jackc/pgconn v1.8.1/go.mod h1:JV6m6b6jhjdmzchES0drzCcYcAHS1OPD5xu3OZ/lE2g= +github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= +github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= +github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= +github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= +github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgservicefile v0.0.0-20200307190119-3430c5407db8/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= +github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= +github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= +github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= +github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= +github.com/jackc/pgtype v1.2.0/go.mod h1:5m2OfMh1wTK7x+Fk952IDmI4nw3nPrvtQdM0ZT4WpC0= +github.com/jackc/pgtype v1.3.1-0.20200510190516-8cd94a14c75a/go.mod h1:vaogEUkALtxZMCH411K+tKzNpwzCKU+AnPzBKZ+I+Po= +github.com/jackc/pgtype v1.3.1-0.20200606141011-f6355165a91c/go.mod h1:cvk9Bgu/VzJ9/lxTO5R5sf80p0DiucVtN7ZxvaC4GmQ= +github.com/jackc/pgtype v1.7.0/go.mod h1:ZnHF+rMePVqDKaOfJVI4Q8IVvAQMryDlDkZnKOI75BE= +github.com/jackc/pgtype v1.8.0/go.mod h1:PqDKcEBtllAtk/2p6z6SHdXW5UB+MhE75tUol2OKexE= +github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= +github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= +github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= +github.com/jackc/pgx/v4 v4.5.0/go.mod h1:EpAKPLdnTorwmPUUsqrPxy5fphV18j9q3wrfRXgo+kA= +github.com/jackc/pgx/v4 v4.6.1-0.20200510190926-94ba730bb1e9/go.mod h1:t3/cdRQl6fOLDxqtlyhe9UWgfIi9R8+8v8GKV5TRA/o= +github.com/jackc/pgx/v4 v4.6.1-0.20200606145419-4e5062306904/go.mod h1:ZDaNWkt9sW1JMiNn0kdYBaLelIhw7Pg4qd+Vk6tw7Hg= +github.com/jackc/pgx/v4 v4.11.0/go.mod h1:i62xJgdrtVDsnL3U8ekyrQXEwGNTRoG7/8r+CIdYfcc= +github.com/jackc/pgx/v4 v4.12.0/go.mod h1:fE547h6VulLPA3kySjfnSG/e2D861g/50JlVUa/ub60= +github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.1.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.1.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= -github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/klauspost/compress v1.15.12 h1:YClS/PImqYbn+UILDnqxQCZ3RehC9N318SU3kElDUEM= +github.com/klauspost/compress v1.15.12/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= +github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= +github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= +github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A= +github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= +github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= -github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= -github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/moby/buildkit v0.13.2 h1:nXNszM4qD9E7QtG7bFWPnDI1teUQFQglBzon/IU3SzI= -github.com/moby/buildkit v0.13.2/go.mod h1:2cyVOv9NoHM7arphK9ZfHIWKn9YVZRFd1wXB8kKmEzY= -github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= -github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= +github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= +github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= +github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-sqlite3 v1.14.9 h1:10HX2Td0ocZpYEjhilsuo6WWtUqttj2Kb0KtD86/KYA= +github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/moby/buildkit v0.11.4 h1:mleVHr+n7HUD65QNUkgkT3d8muTzhYUoHE9FM3Ej05s= +github.com/moby/buildkit v0.11.4/go.mod h1:P5Qi041LvCfhkfYBHry+Rwoo3Wi6H971J2ggE+PcIoo= +github.com/moby/patternmatcher v0.5.0 h1:YCZgJOeULcxLw1Q+sVR636pmS7sPEn1Qo2iAN6M7DBo= +github.com/moby/patternmatcher v0.5.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= +github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= -github.com/moby/sys/user v0.1.0 h1:WmZ93f5Ux6het5iituh9x2zAG7NFY9Aqi49jjE1PaQg= -github.com/moby/sys/user v0.1.0/go.mod h1:fKJhFOnsCN6xZ5gSfbM6zaHGgDJMrqt9/reuj4T7MmU= -github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= -github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/moby/term v0.0.0-20200312100748-672ec06f55cd h1:aY7OQNf2XqY/JQ6qREWamhI/81os/agb2BAGpcx5yWI= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= -github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= -github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= -github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= +github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= +github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= +github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= +github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= +github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= +github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1 h1:mFwc4LvZ0xpSvDZ3E+k8Yte0hLOMxXUlP+yXtJqkYfQ= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.3 h1:gph6h/qe9GSUw1NhH1gp+qb+h8rXD8Cy60Z32Qw3ELA= +github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= +github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.1.0-rc5 h1:Ygwkfw9bpDvs+c9E34SdgGOj41dX/cbdlwvlWt0pnFI= -github.com/opencontainers/image-spec v1.1.0-rc5/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8= +github.com/opencontainers/image-spec v1.1.0-rc2 h1:2zx/Stx4Wc5pIPDvIxHXvXtQFW/7XWJGmnM7r3wg034= +github.com/opencontainers/image-spec v1.1.0-rc2/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ= +github.com/opencontainers/runc v1.1.3 h1:vIXrkId+0/J2Ymu2m7VjGvbSlAId9XNRPhn2p4b+d8w= +github.com/opencontainers/runc v1.1.3/go.mod h1:1J5XiS+vdZ3wCyZybsuxXZWGrgSr8fFJHLXuG2PsnNg= +github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI= github.com/opencontainers/selinux v1.11.0 h1:+5Zbo97w3Lbmb3PeqQtpmTkMwsW5nRI3YaLpt7tQ7oU= github.com/opencontainers/selinux v1.11.0/go.mod h1:E5dMC3VPuVvVHDYmi78qvhJp8+M586T4DlDRYpFkyec= -github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= -github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= +github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= +github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= +github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= +github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= +github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= +github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= +github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= +github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rhysd/actionlint v1.6.27 h1:xxwe8YmveBcC8lydW6GoHMGmB6H/MTqUU60F2p10wjw= -github.com/rhysd/actionlint v1.6.27/go.mod h1:m2nFUjAnOrxCMXuOMz9evYBRCLUsMnKY2IJl/N5umbk= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk= +github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/rhysd/actionlint v1.6.23 h1:041VOXgZddfvSJa9Il+WT3Iwuo/j0Nmu4bhpAScrds4= +github.com/rhysd/actionlint v1.6.23/go.mod h1:o5qc1K3I9taGMBhL7mVkpRd64hx3YqI+3t8ewGfYXfE= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= -github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= -github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= -github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= -github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/rivo/uniseg v0.4.3 h1:utMvzDsuh3suAEnhH0RdHmoPbU648o6CvXxTx4SBMOw= +github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ= +github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= +github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= +github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= -github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/seccomp/libseccomp-golang v0.9.2-0.20220502022130-f33da4d89646/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= +github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= +github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/skeema/knownhosts v1.2.1 h1:SHWdIUa82uGZz+F+47k8SY4QhhI291cXCpopT1lK2AQ= -github.com/skeema/knownhosts v1.2.1/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo= -github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= -github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= +github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= +github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= +github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/timshannon/bolthold v0.0.0-20210913165410-232392fc8a6a h1:oIi7H/bwFUYKYhzKbHc+3MvHRWqhQwXVB4LweLMiVy0= -github.com/timshannon/bolthold v0.0.0-20210913165410-232392fc8a6a/go.mod h1:iSvujNDmpZ6eQX+bg/0X3lF7LEmZ8N77g2a/J/+Zt2U= -github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= -github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= +github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= +github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= +github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= +github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= +github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0= +github.com/xanzy/ssh-agent v0.3.1 h1:AmzO1SSWxw73zxFZPRwaMN1MohDw8UyHnmuxyceTEGo= +github.com/xanzy/ssh-agent v0.3.1/go.mod h1:QIE4lCeL7nkC25x+yA3LBIYfwCc1TFziCtG7cBAac6w= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= -github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= -go.etcd.io/bbolt v1.3.9 h1:8x7aARPEXiXbHmtUwAIv7eV2fQFHrLLavdiJ3uzJXoI= -go.etcd.io/bbolt v1.3.9/go.mod h1:zaO32+Ti0PK1ivdPtgMESzuzL2VPoIG1PCQNvOdo/dE= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 h1:aFJWCqJMNjENlcleuuOkGAPH82y0yULBScfXcIEdS24= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1/go.mod h1:sEGXWArGqc3tVa+ekntsN65DmVbVeW+7lTKTjZF3/Fo= -go.opentelemetry.io/otel v1.21.0 h1:hzLeKBZEL7Okw2mGzZ0cc4k/A7Fta0uoPgaJCr8fsFc= -go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 h1:cl5P5/GIfFh4t6xyruOgJP5QiA1pw4fYYdv6nc6CBWw= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0/go.mod h1:zgBdWWAu7oEEMC06MMKc5NLbA/1YDXV1sMpSqEeLQLg= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0 h1:digkEZCJWobwBqMwC0cwCq8/wkkRy/OowZg5OArWZrM= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0/go.mod h1:/OpE/y70qVkndM0TrxT4KBoN3RsFZP0QaofcfYrj76I= -go.opentelemetry.io/otel/metric v1.21.0 h1:tlYWfeo+Bocx5kLEloTjbcDwBuELRrIFxwdQ36PlJu4= -go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM= -go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8= -go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E= -go.opentelemetry.io/otel/trace v1.21.0 h1:WD9i5gzvoUPuXIXH24ZNBudiarZDKuekPqi/E8fpfLc= -go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ= -go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= -go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= +github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= +github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= +go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= +go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= -golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= -golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= -golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.2.0 h1:BRXPfhNivWL5Yq0BGQ39a2sW6t44aODpfxkWjYdzewE= +golang.org/x/crypto v0.2.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= -golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= -golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= -golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k= +golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= -golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201126233918-771906719818/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210902050250-f475640dd07b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= -golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= -golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= -golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= +golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.1.0 h1:xYY+Bajn2a7VBmTM5GikTmnK8ZuX8YgnQCqZpbBNtmA= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200325010219-a49f79bcc224/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= -golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= +golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b h1:+YaDE2r2OG8t/z5qmsh7Y+XXwCbvadxxZ0YY6mTdrVA= -google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b h1:CIC2YMXmIhYw6evmhPxBKJ4fmLbOFtXQN/GV3XOZR8k= -google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:IBQ646DjkDkvUIsVq/cc03FUFQ9wbZu7yE396YcL870= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b h1:ZlWIi1wSK56/8hn4QcBp/j9M7Gt3U/3hZw3mC7vDICo= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:swOH3j0KzcDDgGUWr+SNpyTen5YrXjS3eyPzFYKc6lc= -google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= -google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.27.1/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/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= +gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= -gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +lukechampine.com/uint128 v1.1.1 h1:pnxCASz787iMf+02ssImqk6OLt+Z5QHMoZyUXR4z6JU= +lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= +modernc.org/cc/v3 v3.33.6/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= +modernc.org/cc/v3 v3.33.9/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= +modernc.org/cc/v3 v3.33.11/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= +modernc.org/cc/v3 v3.34.0/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= +modernc.org/cc/v3 v3.35.0/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= +modernc.org/cc/v3 v3.35.4/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= +modernc.org/cc/v3 v3.35.5/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= +modernc.org/cc/v3 v3.35.7/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= +modernc.org/cc/v3 v3.35.8/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= +modernc.org/cc/v3 v3.35.10/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= +modernc.org/cc/v3 v3.35.15/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= +modernc.org/cc/v3 v3.35.16/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= +modernc.org/cc/v3 v3.35.17/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= +modernc.org/cc/v3 v3.35.18 h1:rMZhRcWrba0y3nVmdiQ7kxAgOOSq2m2f2VzjHLgEs6U= +modernc.org/cc/v3 v3.35.18/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= +modernc.org/ccgo/v3 v3.9.5/go.mod h1:umuo2EP2oDSBnD3ckjaVUXMrmeAw8C8OSICVa0iFf60= +modernc.org/ccgo/v3 v3.10.0/go.mod h1:c0yBmkRFi7uW4J7fwx/JiijwOjeAeR2NoSaRVFPmjMw= +modernc.org/ccgo/v3 v3.11.0/go.mod h1:dGNposbDp9TOZ/1KBxghxtUp/bzErD0/0QW4hhSaBMI= +modernc.org/ccgo/v3 v3.11.1/go.mod h1:lWHxfsn13L3f7hgGsGlU28D9eUOf6y3ZYHKoPaKU0ag= +modernc.org/ccgo/v3 v3.11.3/go.mod h1:0oHunRBMBiXOKdaglfMlRPBALQqsfrCKXgw9okQ3GEw= +modernc.org/ccgo/v3 v3.12.4/go.mod h1:Bk+m6m2tsooJchP/Yk5ji56cClmN6R1cqc9o/YtbgBQ= +modernc.org/ccgo/v3 v3.12.6/go.mod h1:0Ji3ruvpFPpz+yu+1m0wk68pdr/LENABhTrDkMDWH6c= +modernc.org/ccgo/v3 v3.12.8/go.mod h1:Hq9keM4ZfjCDuDXxaHptpv9N24JhgBZmUG5q60iLgUo= +modernc.org/ccgo/v3 v3.12.11/go.mod h1:0jVcmyDwDKDGWbcrzQ+xwJjbhZruHtouiBEvDfoIsdg= +modernc.org/ccgo/v3 v3.12.14/go.mod h1:GhTu1k0YCpJSuWwtRAEHAol5W7g1/RRfS4/9hc9vF5I= +modernc.org/ccgo/v3 v3.12.18/go.mod h1:jvg/xVdWWmZACSgOiAhpWpwHWylbJaSzayCqNOJKIhs= +modernc.org/ccgo/v3 v3.12.20/go.mod h1:aKEdssiu7gVgSy/jjMastnv/q6wWGRbszbheXgWRHc8= +modernc.org/ccgo/v3 v3.12.21/go.mod h1:ydgg2tEprnyMn159ZO/N4pLBqpL7NOkJ88GT5zNU2dE= +modernc.org/ccgo/v3 v3.12.22/go.mod h1:nyDVFMmMWhMsgQw+5JH6B6o4MnZ+UQNw1pp52XYFPRk= +modernc.org/ccgo/v3 v3.12.25/go.mod h1:UaLyWI26TwyIT4+ZFNjkyTbsPsY3plAEB6E7L/vZV3w= +modernc.org/ccgo/v3 v3.12.29/go.mod h1:FXVjG7YLf9FetsS2OOYcwNhcdOLGt8S9bQ48+OP75cE= +modernc.org/ccgo/v3 v3.12.36/go.mod h1:uP3/Fiezp/Ga8onfvMLpREq+KUjUmYMxXPO8tETHtA8= +modernc.org/ccgo/v3 v3.12.38/go.mod h1:93O0G7baRST1vNj4wnZ49b1kLxt0xCW5Hsa2qRaZPqc= +modernc.org/ccgo/v3 v3.12.43/go.mod h1:k+DqGXd3o7W+inNujK15S5ZYuPoWYLpF5PYougCmthU= +modernc.org/ccgo/v3 v3.12.46/go.mod h1:UZe6EvMSqOxaJ4sznY7b23/k13R8XNlyWsO5bAmSgOE= +modernc.org/ccgo/v3 v3.12.47/go.mod h1:m8d6p0zNps187fhBwzY/ii6gxfjob1VxWb919Nk1HUk= +modernc.org/ccgo/v3 v3.12.50/go.mod h1:bu9YIwtg+HXQxBhsRDE+cJjQRuINuT9PUK4orOco/JI= +modernc.org/ccgo/v3 v3.12.51/go.mod h1:gaIIlx4YpmGO2bLye04/yeblmvWEmE4BBBls4aJXFiE= +modernc.org/ccgo/v3 v3.12.53/go.mod h1:8xWGGTFkdFEWBEsUmi+DBjwu/WLy3SSOrqEmKUjMeEg= +modernc.org/ccgo/v3 v3.12.54/go.mod h1:yANKFTm9llTFVX1FqNKHE0aMcQb1fuPJx6p8AcUx+74= +modernc.org/ccgo/v3 v3.12.55/go.mod h1:rsXiIyJi9psOwiBkplOaHye5L4MOOaCjHg1Fxkj7IeU= +modernc.org/ccgo/v3 v3.12.56/go.mod h1:ljeFks3faDseCkr60JMpeDb2GSO3TKAmrzm7q9YOcMU= +modernc.org/ccgo/v3 v3.12.57/go.mod h1:hNSF4DNVgBl8wYHpMvPqQWDQx8luqxDnNGCMM4NFNMc= +modernc.org/ccgo/v3 v3.12.60/go.mod h1:k/Nn0zdO1xHVWjPYVshDeWKqbRWIfif5dtsIOCUVMqM= +modernc.org/ccgo/v3 v3.12.65/go.mod h1:D6hQtKxPNZiY6wDBtehSGKFKmyXn53F8nGTpH+POmS4= +modernc.org/ccgo/v3 v3.12.66/go.mod h1:jUuxlCFZTUZLMV08s7B1ekHX5+LIAurKTTaugUr/EhQ= +modernc.org/ccgo/v3 v3.12.67/go.mod h1:Bll3KwKvGROizP2Xj17GEGOTrlvB1XcVaBrC90ORO84= +modernc.org/ccgo/v3 v3.12.73/go.mod h1:hngkB+nUUqzOf3iqsM48Gf1FZhY599qzVg1iX+BT3cQ= +modernc.org/ccgo/v3 v3.12.81/go.mod h1:p2A1duHoBBg1mFtYvnhAnQyI6vL0uw5PGYLSIgF6rYY= +modernc.org/ccgo/v3 v3.12.82 h1:wudcnJyjLj1aQQCXF3IM9Gz2X6UNjw+afIghzdtn0v8= +modernc.org/ccgo/v3 v3.12.82/go.mod h1:ApbflUfa5BKadjHynCficldU1ghjen84tuM5jRynB7w= +modernc.org/ccorpus v1.11.1 h1:K0qPfpVG1MJh5BYazccnmhywH4zHuOgJXgbjzyp6dWA= +modernc.org/ccorpus v1.11.1/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ= +modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM= +modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= +modernc.org/libc v1.9.8/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w= +modernc.org/libc v1.9.11/go.mod h1:NyF3tsA5ArIjJ83XB0JlqhjTabTCHm9aX4XMPHyQn0Q= +modernc.org/libc v1.11.0/go.mod h1:2lOfPmj7cz+g1MrPNmX65QCzVxgNq2C5o0jdLY2gAYg= +modernc.org/libc v1.11.2/go.mod h1:ioIyrl3ETkugDO3SGZ+6EOKvlP3zSOycUETe4XM4n8M= +modernc.org/libc v1.11.5/go.mod h1:k3HDCP95A6U111Q5TmG3nAyUcp3kR5YFZTeDS9v8vSU= +modernc.org/libc v1.11.6/go.mod h1:ddqmzR6p5i4jIGK1d/EiSw97LBcE3dK24QEwCFvgNgE= +modernc.org/libc v1.11.11/go.mod h1:lXEp9QOOk4qAYOtL3BmMve99S5Owz7Qyowzvg6LiZso= +modernc.org/libc v1.11.13/go.mod h1:ZYawJWlXIzXy2Pzghaf7YfM8OKacP3eZQI81PDLFdY8= +modernc.org/libc v1.11.16/go.mod h1:+DJquzYi+DMRUtWI1YNxrlQO6TcA5+dRRiq8HWBWRC8= +modernc.org/libc v1.11.19/go.mod h1:e0dgEame6mkydy19KKaVPBeEnyJB4LGNb0bBH1EtQ3I= +modernc.org/libc v1.11.24/go.mod h1:FOSzE0UwookyT1TtCJrRkvsOrX2k38HoInhw+cSCUGk= +modernc.org/libc v1.11.26/go.mod h1:SFjnYi9OSd2W7f4ct622o/PAYqk7KHv6GS8NZULIjKY= +modernc.org/libc v1.11.27/go.mod h1:zmWm6kcFXt/jpzeCgfvUNswM0qke8qVwxqZrnddlDiE= +modernc.org/libc v1.11.28/go.mod h1:Ii4V0fTFcbq3qrv3CNn+OGHAvzqMBvC7dBNyC4vHZlg= +modernc.org/libc v1.11.31/go.mod h1:FpBncUkEAtopRNJj8aRo29qUiyx5AvAlAxzlx9GNaVM= +modernc.org/libc v1.11.34/go.mod h1:+Tzc4hnb1iaX/SKAutJmfzES6awxfU1BPvrrJO0pYLg= +modernc.org/libc v1.11.37/go.mod h1:dCQebOwoO1046yTrfUE5nX1f3YpGZQKNcITUYWlrAWo= +modernc.org/libc v1.11.39/go.mod h1:mV8lJMo2S5A31uD0k1cMu7vrJbSA3J3waQJxpV4iqx8= +modernc.org/libc v1.11.42/go.mod h1:yzrLDU+sSjLE+D4bIhS7q1L5UwXDOw99PLSX0BlZvSQ= +modernc.org/libc v1.11.44/go.mod h1:KFq33jsma7F5WXiYelU8quMJasCCTnHK0mkri4yPHgA= +modernc.org/libc v1.11.45/go.mod h1:Y192orvfVQQYFzCNsn+Xt0Hxt4DiO4USpLNXBlXg/tM= +modernc.org/libc v1.11.47/go.mod h1:tPkE4PzCTW27E6AIKIR5IwHAQKCAtudEIeAV1/SiyBg= +modernc.org/libc v1.11.49/go.mod h1:9JrJuK5WTtoTWIFQ7QjX2Mb/bagYdZdscI3xrvHbXjE= +modernc.org/libc v1.11.51/go.mod h1:R9I8u9TS+meaWLdbfQhq2kFknTW0O3aw3kEMqDDxMaM= +modernc.org/libc v1.11.53/go.mod h1:5ip5vWYPAoMulkQ5XlSJTy12Sz5U6blOQiYasilVPsU= +modernc.org/libc v1.11.54/go.mod h1:S/FVnskbzVUrjfBqlGFIPA5m7UwB3n9fojHhCNfSsnw= +modernc.org/libc v1.11.55/go.mod h1:j2A5YBRm6HjNkoSs/fzZrSxCuwWqcMYTDPLNx0URn3M= +modernc.org/libc v1.11.56/go.mod h1:pakHkg5JdMLt2OgRadpPOTnyRXm/uzu+Yyg/LSLdi18= +modernc.org/libc v1.11.58/go.mod h1:ns94Rxv0OWyoQrDqMFfWwka2BcaF6/61CqJRK9LP7S8= +modernc.org/libc v1.11.70/go.mod h1:DUOmMYe+IvKi9n6Mycyx3DbjfzSKrdr/0Vgt3j7P5gw= +modernc.org/libc v1.11.71/go.mod h1:DUOmMYe+IvKi9n6Mycyx3DbjfzSKrdr/0Vgt3j7P5gw= +modernc.org/libc v1.11.75/go.mod h1:dGRVugT6edz361wmD9gk6ax1AbDSe0x5vji0dGJiPT0= +modernc.org/libc v1.11.82/go.mod h1:NF+Ek1BOl2jeC7lw3a7Jj5PWyHPwWD4aq3wVKxqV1fI= +modernc.org/libc v1.11.86/go.mod h1:ePuYgoQLmvxdNT06RpGnaDKJmDNEkV7ZPKI2jnsvZoE= +modernc.org/libc v1.11.87 h1:PzIzOqtlzMDDcCzJ5cUP6h/Ku6Fa9iyflP2ccTY64aE= +modernc.org/libc v1.11.87/go.mod h1:Qvd5iXTeLhI5PS0XSyqMY99282y+3euapQFxM7jYnpY= +modernc.org/mathutil v1.1.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/mathutil v1.4.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/mathutil v1.4.1 h1:ij3fYGe8zBF4Vu+g0oT7mB06r8sqGWKuJu1yXeR4by8= +modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/memory v1.0.4/go.mod h1:nV2OApxradM3/OVbs2/0OsP6nPfakXpi50C7dcoHXlc= +modernc.org/memory v1.0.5 h1:XRch8trV7GgvTec2i7jc33YlUI0RKVDBvZ5eZ5m8y14= +modernc.org/memory v1.0.5/go.mod h1:B7OYswTRnfGg+4tDH1t1OeUNnsy2viGTdME4tzd+IjM= +modernc.org/opt v0.1.1 h1:/0RX92k9vwVeDXj+Xn23DKp2VJubL7k8qNffND6qn3A= +modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= +modernc.org/sqlite v1.14.2 h1:ohsW2+e+Qe2To1W6GNezzKGwjXwSax6R+CrhRxVaFbE= +modernc.org/sqlite v1.14.2/go.mod h1:yqfn85u8wVOE6ub5UT8VI9JjhrwBUUCNyTACN0h6Sx8= +modernc.org/strutil v1.1.1 h1:xv+J1BXY3Opl2ALrBwyfEikFAj8pmqcpnfmuwUwcozs= +modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw= +modernc.org/tcl v1.8.13 h1:V0sTNBw0Re86PvXZxuCub3oO9WrSTqALgrwNZNvLFGw= +modernc.org/tcl v1.8.13/go.mod h1:V+q/Ef0IJaNUSECieLU4o+8IScapxnMyFV6i/7uQlAY= +modernc.org/token v1.0.0 h1:a0jaWiNMDhDUtqOj09wvjWWAqd3q7WpBulmL9H2egsk= +modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= +modernc.org/z v1.2.19 h1:BGyRFWhDVn5LFS5OcX4Yd/MlpRTOc7hOPTdcIpCiUao= +modernc.org/z v1.2.19/go.mod h1:+ZpP0pc4zz97eukOzW3xagV/lS82IpPN9NGG5pNF9vY= +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= +sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= +xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978 h1:bvLlAPW1ZMTWA32LuZMBEGHAUOcATZjzHcotf3SWweM= +xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE= +xorm.io/xorm v1.3.2 h1:uTRRKF2jYzbZ5nsofXVUx6ncMaek+SHjWYtCXyZo1oM= +xorm.io/xorm v1.3.2/go.mod h1:9NbjqdnjX6eyjRRhh01GHm64r6N9shTb/8Ak3YRt8Nw= diff --git a/internal/app/cmd/cache-server.go b/internal/app/cmd/cache-server.go deleted file mode 100644 index 21b3352..0000000 --- a/internal/app/cmd/cache-server.go +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright 2023 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package cmd - -import ( - "context" - "fmt" - "os" - "os/signal" - - "gitea.com/gitea/act_runner/internal/pkg/config" - - "github.com/nektos/act/pkg/artifactcache" - log "github.com/sirupsen/logrus" - "github.com/spf13/cobra" -) - -type cacheServerArgs struct { - Dir string - Host string - Port uint16 -} - -func runCacheServer(ctx context.Context, configFile *string, cacheArgs *cacheServerArgs) func(cmd *cobra.Command, args []string) error { - return func(cmd *cobra.Command, args []string) error { - cfg, err := config.LoadDefault(*configFile) - if err != nil { - return fmt.Errorf("invalid configuration: %w", err) - } - - initLogging(cfg) - - var ( - dir = cfg.Cache.Dir - host = cfg.Cache.Host - port = cfg.Cache.Port - ) - - // cacheArgs has higher priority - if cacheArgs.Dir != "" { - dir = cacheArgs.Dir - } - if cacheArgs.Host != "" { - host = cacheArgs.Host - } - if cacheArgs.Port != 0 { - port = cacheArgs.Port - } - - cacheHandler, err := artifactcache.StartHandler( - dir, - host, - port, - log.StandardLogger().WithField("module", "cache_request"), - ) - if err != nil { - return err - } - - log.Infof("cache server is listening on %v", cacheHandler.ExternalURL()) - - c := make(chan os.Signal, 1) - signal.Notify(c, os.Interrupt) - <-c - - return nil - } -} diff --git a/internal/app/cmd/create-runner-file.go b/internal/app/cmd/create-runner-file.go deleted file mode 100644 index a972624..0000000 --- a/internal/app/cmd/create-runner-file.go +++ /dev/null @@ -1,164 +0,0 @@ -// SPDX-License-Identifier: MIT - -package cmd - -import ( - "context" - "encoding/hex" - "fmt" - "os" - - pingv1 "code.gitea.io/actions-proto-go/ping/v1" - "connectrpc.com/connect" - gouuid "github.com/google/uuid" - log "github.com/sirupsen/logrus" - "github.com/spf13/cobra" - - "gitea.com/gitea/act_runner/internal/app/run" - "gitea.com/gitea/act_runner/internal/pkg/client" - "gitea.com/gitea/act_runner/internal/pkg/config" - "gitea.com/gitea/act_runner/internal/pkg/ver" -) - -type createRunnerFileArgs struct { - Connect bool - InstanceAddr string - Secret string - Name string -} - -func createRunnerFileCmd(ctx context.Context, configFile *string) *cobra.Command { - var argsVar createRunnerFileArgs - cmd := &cobra.Command{ - Use: "create-runner-file", - Short: "Create a runner file using a shared secret used to pre-register the runner on the Forgejo instance", - Args: cobra.MaximumNArgs(0), - RunE: runCreateRunnerFile(ctx, &argsVar, configFile), - } - cmd.Flags().BoolVar(&argsVar.Connect, "connect", false, "tries to connect to the instance using the secret (Forgejo v1.21 instance or greater)") - cmd.Flags().StringVar(&argsVar.InstanceAddr, "instance", "", "Forgejo instance address") - cmd.MarkFlagRequired("instance") - cmd.Flags().StringVar(&argsVar.Secret, "secret", "", "secret shared with the Forgejo instance via forgejo-cli actions register") - cmd.MarkFlagRequired("secret") - cmd.Flags().StringVar(&argsVar.Name, "name", "", "Runner name") - - return cmd -} - -// must be exactly the same as fogejo/models/actions/forgejo.go -func uuidFromSecret(secret string) (string, error) { - uuid, err := gouuid.FromBytes([]byte(secret[:16])) - if err != nil { - return "", fmt.Errorf("gouuid.FromBytes %v", err) - } - return uuid.String(), nil -} - -// should be exactly the same as forgejo/cmd/forgejo/actions.go -func validateSecret(secret string) error { - secretLen := len(secret) - if secretLen != 40 { - return fmt.Errorf("the secret must be exactly 40 characters long, not %d", secretLen) - } - if _, err := hex.DecodeString(secret); err != nil { - return fmt.Errorf("the secret must be an hexadecimal string: %w", err) - } - return nil -} - -func ping(cfg *config.Config, reg *config.Registration) error { - // initial http client - cli := client.New( - reg.Address, - cfg.Runner.Insecure, - "", - "", - ver.Version(), - ) - - _, err := cli.Ping(context.Background(), connect.NewRequest(&pingv1.PingRequest{ - Data: reg.UUID, - })) - if err != nil { - return fmt.Errorf("ping %s failed %w", reg.Address, err) - } - return nil -} - -func runCreateRunnerFile(ctx context.Context, args *createRunnerFileArgs, configFile *string) func(cmd *cobra.Command, args []string) error { - return func(*cobra.Command, []string) error { - log.SetLevel(log.DebugLevel) - log.Info("Creating runner file") - - // - // Prepare the registration data - // - cfg, err := config.LoadDefault(*configFile) - if err != nil { - return fmt.Errorf("invalid configuration: %w", err) - } - - if err := validateSecret(args.Secret); err != nil { - return err - } - - uuid, err := uuidFromSecret(args.Secret) - if err != nil { - return err - } - - name := args.Name - if name == "" { - name, _ = os.Hostname() - log.Infof("Runner name is empty, use hostname '%s'.", name) - } - - reg := &config.Registration{ - Name: name, - UUID: uuid, - Token: args.Secret, - Address: args.InstanceAddr, - } - - // - // Verify the Forgejo instance is reachable - // - if err := ping(cfg, reg); err != nil { - return err - } - - // - // Save the registration file - // - if err := config.SaveRegistration(cfg.Runner.File, reg); err != nil { - return fmt.Errorf("failed to save runner config to %s: %w", cfg.Runner.File, err) - } - - // - // Verify the secret works - // - if args.Connect { - cli := client.New( - reg.Address, - cfg.Runner.Insecure, - reg.UUID, - reg.Token, - ver.Version(), - ) - - runner := run.NewRunner(cfg, reg, cli) - resp, err := runner.Declare(ctx, cfg.Runner.Labels) - - if err != nil && connect.CodeOf(err) == connect.CodeUnimplemented { - log.Warn("Cannot verify the connection because the Forgejo instance is lower than v1.21") - } else if err != nil { - log.WithError(err).Error("fail to invoke Declare") - return err - } else { - log.Infof("connection successful: %s, with version: %s, with labels: %v", - resp.Msg.Runner.Name, resp.Msg.Runner.Version, resp.Msg.Runner.Labels) - } - } - return nil - } -} diff --git a/internal/app/cmd/create-runner-file_test.go b/internal/app/cmd/create-runner-file_test.go deleted file mode 100644 index 4f3acb8..0000000 --- a/internal/app/cmd/create-runner-file_test.go +++ /dev/null @@ -1,118 +0,0 @@ -// SPDX-License-Identifier: MIT - -package cmd - -import ( - "bytes" - "context" - "os" - "testing" - - runnerv1 "code.gitea.io/actions-proto-go/runner/v1" - "connectrpc.com/connect" - "gitea.com/gitea/act_runner/internal/pkg/client" - "gitea.com/gitea/act_runner/internal/pkg/config" - "gitea.com/gitea/act_runner/internal/pkg/ver" - - "github.com/spf13/cobra" - "github.com/stretchr/testify/assert" - "gopkg.in/yaml.v3" -) - -func executeCommand(ctx context.Context, cmd *cobra.Command, args ...string) (string, error) { - buf := new(bytes.Buffer) - cmd.SetOut(buf) - cmd.SetErr(buf) - cmd.SetArgs(args) - - err := cmd.ExecuteContext(ctx) - - return buf.String(), err -} - -func Test_createRunnerFileCmd(t *testing.T) { - configFile := "config.yml" - ctx := context.Background() - cmd := createRunnerFileCmd(ctx, &configFile) - output, err := executeCommand(ctx, cmd) - assert.ErrorContains(t, err, `required flag(s) "instance", "secret" not set`) - assert.Contains(t, output, "Usage:") -} - -func Test_validateSecret(t *testing.T) { - assert.ErrorContains(t, validateSecret("abc"), "exactly 40 characters") - assert.ErrorContains(t, validateSecret("ZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"), "must be an hexadecimal") -} - -func Test_uuidFromSecret(t *testing.T) { - uuid, err := uuidFromSecret("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA") - assert.NoError(t, err) - assert.EqualValues(t, uuid, "41414141-4141-4141-4141-414141414141") -} - -func Test_ping(t *testing.T) { - cfg := &config.Config{} - address := os.Getenv("FORGEJO_URL") - if address == "" { - address = "https://code.forgejo.org" - } - reg := &config.Registration{ - Address: address, - UUID: "create-runner-file_test.go", - } - assert.NoError(t, ping(cfg, reg)) -} - -func Test_runCreateRunnerFile(t *testing.T) { - // - // Set the .runner file to be in a temporary directory - // - dir := t.TempDir() - configFile := dir + "/config.yml" - runnerFile := dir + "/.runner" - cfg, err := config.LoadDefault("") - cfg.Runner.File = runnerFile - yamlData, err := yaml.Marshal(cfg) - assert.NoError(t, err) - assert.NoError(t, os.WriteFile(configFile, yamlData, 0o666)) - - instance, has := os.LookupEnv("FORGEJO_URL") - if !has { - instance = "https://code.forgejo.org" - } - secret, has := os.LookupEnv("FORGEJO_RUNNER_SECRET") - assert.True(t, has) - name := "testrunner" - - // - // Run create-runner-file - // - ctx := context.Background() - cmd := createRunnerFileCmd(ctx, &configFile) - output, err := executeCommand(ctx, cmd, "--connect", "--secret", secret, "--instance", instance, "--name", name) - assert.NoError(t, err) - assert.EqualValues(t, "", output) - - // - // Read back the runner file and verify its content - // - reg, err := config.LoadRegistration(runnerFile) - assert.NoError(t, err) - assert.EqualValues(t, secret, reg.Token) - assert.EqualValues(t, instance, reg.Address) - - // - // Verify that fetching a task successfully returns there is - // no task for this runner - // - cli := client.New( - reg.Address, - cfg.Runner.Insecure, - reg.UUID, - reg.Token, - ver.Version(), - ) - resp, err := cli.FetchTask(ctx, connect.NewRequest(&runnerv1.FetchTaskRequest{})) - assert.NoError(t, err) - assert.Nil(t, resp.Msg.Task) -} diff --git a/internal/app/cmd/daemon.go b/internal/app/cmd/daemon.go deleted file mode 100644 index a613546..0000000 --- a/internal/app/cmd/daemon.go +++ /dev/null @@ -1,208 +0,0 @@ -// Copyright 2022 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package cmd - -import ( - "context" - "fmt" - "os" - "path" - "path/filepath" - "runtime" - "strconv" - "strings" - - "connectrpc.com/connect" - "github.com/mattn/go-isatty" - log "github.com/sirupsen/logrus" - "github.com/spf13/cobra" - - "gitea.com/gitea/act_runner/internal/app/poll" - "gitea.com/gitea/act_runner/internal/app/run" - "gitea.com/gitea/act_runner/internal/pkg/client" - "gitea.com/gitea/act_runner/internal/pkg/config" - "gitea.com/gitea/act_runner/internal/pkg/envcheck" - "gitea.com/gitea/act_runner/internal/pkg/labels" - "gitea.com/gitea/act_runner/internal/pkg/ver" -) - -func runDaemon(ctx context.Context, configFile *string) func(cmd *cobra.Command, args []string) error { - return func(cmd *cobra.Command, args []string) error { - cfg, err := config.LoadDefault(*configFile) - if err != nil { - return fmt.Errorf("invalid configuration: %w", err) - } - - initLogging(cfg) - log.Infoln("Starting runner daemon") - - reg, err := config.LoadRegistration(cfg.Runner.File) - if os.IsNotExist(err) { - log.Error("registration file not found, please register the runner first") - return err - } else if err != nil { - return fmt.Errorf("failed to load registration file: %w", err) - } - - cfg.Tune(reg.Address) - - lbls := reg.Labels - if len(cfg.Runner.Labels) > 0 { - lbls = cfg.Runner.Labels - } - - ls := labels.Labels{} - for _, l := range lbls { - label, err := labels.Parse(l) - if err != nil { - log.WithError(err).Warnf("ignored invalid label %q", l) - continue - } - ls = append(ls, label) - } - if len(ls) == 0 { - log.Warn("no labels configured, runner may not be able to pick up jobs") - } - - if ls.RequireDocker() { - dockerSocketPath, err := getDockerSocketPath(cfg.Container.DockerHost) - if err != nil { - return err - } - if err := envcheck.CheckIfDockerRunning(ctx, dockerSocketPath); err != nil { - return err - } - // if dockerSocketPath passes the check, override DOCKER_HOST with dockerSocketPath - os.Setenv("DOCKER_HOST", dockerSocketPath) - // empty cfg.Container.DockerHost means act_runner need to find an available docker host automatically - // and assign the path to cfg.Container.DockerHost - if cfg.Container.DockerHost == "" { - cfg.Container.DockerHost = dockerSocketPath - } - // check the scheme, if the scheme is not npipe or unix - // set cfg.Container.DockerHost to "-" because it can't be mounted to the job container - if protoIndex := strings.Index(cfg.Container.DockerHost, "://"); protoIndex != -1 { - scheme := cfg.Container.DockerHost[:protoIndex] - if !strings.EqualFold(scheme, "npipe") && !strings.EqualFold(scheme, "unix") { - cfg.Container.DockerHost = "-" - } - } - } - - cli := client.New( - reg.Address, - cfg.Runner.Insecure, - reg.UUID, - reg.Token, - ver.Version(), - ) - - runner := run.NewRunner(cfg, reg, cli) - // declare the labels of the runner before fetching tasks - resp, err := runner.Declare(ctx, ls.Names()) - if err != nil && connect.CodeOf(err) == connect.CodeUnimplemented { - // Gitea instance is older version. skip declare step. - log.Warn("Because the Forgejo instance is an old version, skipping declaring the labels and version.") - } else if err != nil { - log.WithError(err).Error("fail to invoke Declare") - return err - } else { - log.Infof("runner: %s, with version: %s, with labels: %v, declared successfully", - resp.Msg.Runner.Name, resp.Msg.Runner.Version, resp.Msg.Runner.Labels) - // if declared successfully, override the labels in the.runner file with valid labels in the config file (if specified) - runner.Update(ctx, ls) - reg.Labels = ls.ToStrings() - if err := config.SaveRegistration(cfg.Runner.File, reg); err != nil { - return fmt.Errorf("failed to save runner config: %w", err) - } - } - - poller := poll.New(cfg, cli, runner) - - go poller.Poll() - - <-ctx.Done() - log.Infof("runner: %s shutdown initiated, waiting [runner].shutdown_timeout=%s for running jobs to complete before shutting down", resp.Msg.Runner.Name, cfg.Runner.ShutdownTimeout) - - ctx, cancel := context.WithTimeout(context.Background(), cfg.Runner.ShutdownTimeout) - defer cancel() - - err = poller.Shutdown(ctx) - if err != nil { - log.Warnf("runner: %s cancelled in progress jobs during shutdown", resp.Msg.Runner.Name) - } - return nil - } -} - -// initLogging setup the global logrus logger. -func initLogging(cfg *config.Config) { - isTerm := isatty.IsTerminal(os.Stdout.Fd()) - format := &log.TextFormatter{ - DisableColors: !isTerm, - FullTimestamp: true, - } - log.SetFormatter(format) - - if l := cfg.Log.Level; l != "" { - level, err := log.ParseLevel(l) - if err != nil { - log.WithError(err). - Errorf("invalid log level: %q", l) - } - - // debug level - if level == log.DebugLevel { - log.SetReportCaller(true) - format.CallerPrettyfier = func(f *runtime.Frame) (string, string) { - // get function name - s := strings.Split(f.Function, ".") - funcname := "[" + s[len(s)-1] + "]" - // get file name and line number - _, filename := path.Split(f.File) - filename = "[" + filename + ":" + strconv.Itoa(f.Line) + "]" - return funcname, filename - } - log.SetFormatter(format) - } - - if log.GetLevel() != level { - log.Infof("log level changed to %v", level) - log.SetLevel(level) - } - } -} - -var commonSocketPaths = []string{ - "/var/run/docker.sock", - "/run/podman/podman.sock", - "$HOME/.colima/docker.sock", - "$XDG_RUNTIME_DIR/docker.sock", - "$XDG_RUNTIME_DIR/podman/podman.sock", - `\\.\pipe\docker_engine`, - "$HOME/.docker/run/docker.sock", -} - -func getDockerSocketPath(configDockerHost string) (string, error) { - // a `-` means don't mount the docker socket to job containers - if configDockerHost != "" && configDockerHost != "-" { - return configDockerHost, nil - } - - socket, found := os.LookupEnv("DOCKER_HOST") - if found { - return socket, nil - } - - for _, p := range commonSocketPaths { - if _, err := os.Lstat(os.ExpandEnv(p)); err == nil { - if strings.HasPrefix(p, `\\.\`) { - return "npipe://" + filepath.ToSlash(os.ExpandEnv(p)), nil - } - return "unix://" + filepath.ToSlash(os.ExpandEnv(p)), nil - } - } - - return "", fmt.Errorf("daemon Docker Engine socket not found and docker_host config was invalid") -} diff --git a/internal/app/poll/poller.go b/internal/app/poll/poller.go deleted file mode 100644 index cc89fa5..0000000 --- a/internal/app/poll/poller.go +++ /dev/null @@ -1,167 +0,0 @@ -// Copyright 2023 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package poll - -import ( - "context" - "errors" - "fmt" - "sync" - "sync/atomic" - - runnerv1 "code.gitea.io/actions-proto-go/runner/v1" - "connectrpc.com/connect" - log "github.com/sirupsen/logrus" - "golang.org/x/time/rate" - - "gitea.com/gitea/act_runner/internal/app/run" - "gitea.com/gitea/act_runner/internal/pkg/client" - "gitea.com/gitea/act_runner/internal/pkg/config" -) - -const PollerID = "PollerID" - -type Poller interface { - Poll() - Shutdown(ctx context.Context) error -} - -type poller struct { - client client.Client - runner run.RunnerInterface - cfg *config.Config - tasksVersion atomic.Int64 // tasksVersion used to store the version of the last task fetched from the Gitea. - - pollingCtx context.Context - shutdownPolling context.CancelFunc - - jobsCtx context.Context - shutdownJobs context.CancelFunc - - done chan any -} - -func New(cfg *config.Config, client client.Client, runner run.RunnerInterface) Poller { - return (&poller{}).init(cfg, client, runner) -} - -func (p *poller) init(cfg *config.Config, client client.Client, runner run.RunnerInterface) Poller { - pollingCtx, shutdownPolling := context.WithCancel(context.Background()) - - jobsCtx, shutdownJobs := context.WithCancel(context.Background()) - - done := make(chan any) - - p.client = client - p.runner = runner - p.cfg = cfg - - p.pollingCtx = pollingCtx - p.shutdownPolling = shutdownPolling - - p.jobsCtx = jobsCtx - p.shutdownJobs = shutdownJobs - p.done = done - - return p -} - -func (p *poller) Poll() { - limiter := rate.NewLimiter(rate.Every(p.cfg.Runner.FetchInterval), 1) - wg := &sync.WaitGroup{} - for i := 0; i < p.cfg.Runner.Capacity; i++ { - wg.Add(1) - go p.poll(i, wg, limiter) - } - wg.Wait() - - // signal the poller is finished - close(p.done) -} - -func (p *poller) Shutdown(ctx context.Context) error { - p.shutdownPolling() - - select { - case <-p.done: - log.Trace("all jobs are complete") - return nil - - case <-ctx.Done(): - log.Trace("forcing the jobs to shutdown") - p.shutdownJobs() - <-p.done - log.Trace("all jobs have been shutdown") - return ctx.Err() - } -} - -func (p *poller) poll(id int, wg *sync.WaitGroup, limiter *rate.Limiter) { - log.Infof("[poller %d] launched", id) - defer wg.Done() - for { - if err := limiter.Wait(p.pollingCtx); err != nil { - log.Infof("[poller %d] shutdown", id) - return - } - task, ok := p.fetchTask(p.pollingCtx) - if !ok { - continue - } - p.runTaskWithRecover(p.jobsCtx, task) - } -} - -func (p *poller) runTaskWithRecover(ctx context.Context, task *runnerv1.Task) { - defer func() { - if r := recover(); r != nil { - err := fmt.Errorf("panic: %v", r) - log.WithError(err).Error("panic in runTaskWithRecover") - } - }() - - if err := p.runner.Run(ctx, task); err != nil { - log.WithError(err).Error("failed to run task") - } -} - -func (p *poller) fetchTask(ctx context.Context) (*runnerv1.Task, bool) { - reqCtx, cancel := context.WithTimeout(ctx, p.cfg.Runner.FetchTimeout) - defer cancel() - - // Load the version value that was in the cache when the request was sent. - v := p.tasksVersion.Load() - resp, err := p.client.FetchTask(reqCtx, connect.NewRequest(&runnerv1.FetchTaskRequest{ - TasksVersion: v, - })) - if errors.Is(err, context.DeadlineExceeded) { - log.Trace("deadline exceeded") - err = nil - } - if err != nil { - if errors.Is(err, context.Canceled) { - log.WithError(err).Debugf("shutdown, fetch task canceled") - } else { - log.WithError(err).Error("failed to fetch task") - } - return nil, false - } - - if resp == nil || resp.Msg == nil { - return nil, false - } - - if resp.Msg.TasksVersion > v { - p.tasksVersion.CompareAndSwap(v, resp.Msg.TasksVersion) - } - - if resp.Msg.Task == nil { - return nil, false - } - - // got a task, set `tasksVersion` to zero to focre query db in next request. - p.tasksVersion.CompareAndSwap(resp.Msg.TasksVersion, 0) - - return resp.Msg.Task, true -} diff --git a/internal/app/poll/poller_test.go b/internal/app/poll/poller_test.go deleted file mode 100644 index 04b1a84..0000000 --- a/internal/app/poll/poller_test.go +++ /dev/null @@ -1,263 +0,0 @@ -// Copyright The Forgejo Authors. -// SPDX-License-Identifier: MIT - -package poll - -import ( - "context" - "fmt" - "testing" - "time" - - "connectrpc.com/connect" - - "code.gitea.io/actions-proto-go/ping/v1/pingv1connect" - runnerv1 "code.gitea.io/actions-proto-go/runner/v1" - "code.gitea.io/actions-proto-go/runner/v1/runnerv1connect" - "gitea.com/gitea/act_runner/internal/pkg/config" - - log "github.com/sirupsen/logrus" - "github.com/stretchr/testify/assert" -) - -type mockPoller struct { - poller -} - -func (o *mockPoller) Poll() { - o.poller.Poll() -} - -type mockClient struct { - pingv1connect.PingServiceClient - runnerv1connect.RunnerServiceClient - - sleep time.Duration - cancel bool - err error - noTask bool -} - -func (o mockClient) Address() string { - return "" -} - -func (o mockClient) Insecure() bool { - return true -} - -func (o *mockClient) FetchTask(ctx context.Context, req *connect.Request[runnerv1.FetchTaskRequest]) (*connect.Response[runnerv1.FetchTaskResponse], error) { - if o.sleep > 0 { - select { - case <-ctx.Done(): - log.Trace("fetch task done") - return nil, context.DeadlineExceeded - case <-time.After(o.sleep): - log.Trace("slept") - return nil, fmt.Errorf("unexpected") - } - } - if o.cancel { - return nil, context.Canceled - } - if o.err != nil { - return nil, o.err - } - task := &runnerv1.Task{} - if o.noTask { - task = nil - o.noTask = false - } - - return connect.NewResponse(&runnerv1.FetchTaskResponse{ - Task: task, - TasksVersion: int64(1), - }), nil -} - -type mockRunner struct { - cfg *config.Runner - log chan string - panics bool - err error -} - -func (o *mockRunner) Run(ctx context.Context, task *runnerv1.Task) error { - o.log <- "runner starts" - if o.panics { - log.Trace("panics") - o.log <- "runner panics" - o.panics = false - panic("whatever") - } - if o.err != nil { - log.Trace("error") - o.log <- "runner error" - err := o.err - o.err = nil - return err - } - for { - select { - case <-ctx.Done(): - log.Trace("shutdown") - o.log <- "runner shutdown" - return nil - case <-time.After(o.cfg.Timeout): - log.Trace("after") - o.log <- "runner timeout" - return nil - } - } -} - -func setTrace(t *testing.T) { - t.Helper() - log.SetReportCaller(true) - log.SetLevel(log.TraceLevel) -} - -func TestPoller_New(t *testing.T) { - p := New(&config.Config{}, &mockClient{}, &mockRunner{}) - assert.NotNil(t, p) -} - -func TestPoller_Runner(t *testing.T) { - setTrace(t) - for _, testCase := range []struct { - name string - timeout time.Duration - noTask bool - panics bool - err error - expected string - contextTimeout time.Duration - }{ - { - name: "Simple", - timeout: 10 * time.Second, - expected: "runner shutdown", - }, - { - name: "Panics", - timeout: 10 * time.Second, - panics: true, - expected: "runner panics", - }, - { - name: "Error", - timeout: 10 * time.Second, - err: fmt.Errorf("ERROR"), - expected: "runner error", - }, - { - name: "PollTaskError", - timeout: 10 * time.Second, - noTask: true, - expected: "runner shutdown", - }, - { - name: "ShutdownTimeout", - timeout: 1 * time.Second, - contextTimeout: 1 * time.Minute, - expected: "runner timeout", - }, - } { - t.Run(testCase.name, func(t *testing.T) { - runnerLog := make(chan string, 3) - configRunner := config.Runner{ - FetchInterval: 1, - Capacity: 1, - Timeout: testCase.timeout, - } - p := &mockPoller{} - p.init( - &config.Config{ - Runner: configRunner, - }, - &mockClient{ - noTask: testCase.noTask, - }, - &mockRunner{ - cfg: &configRunner, - log: runnerLog, - panics: testCase.panics, - err: testCase.err, - }) - go p.Poll() - assert.Equal(t, "runner starts", <-runnerLog) - var ctx context.Context - var cancel context.CancelFunc - if testCase.contextTimeout > 0 { - ctx, cancel = context.WithTimeout(context.Background(), testCase.contextTimeout) - defer cancel() - } else { - ctx, cancel = context.WithCancel(context.Background()) - cancel() - } - p.Shutdown(ctx) - <-p.done - assert.Equal(t, testCase.expected, <-runnerLog) - }) - } -} - -func TestPoller_Fetch(t *testing.T) { - setTrace(t) - for _, testCase := range []struct { - name string - noTask bool - sleep time.Duration - err error - cancel bool - success bool - }{ - { - name: "Success", - success: true, - }, - { - name: "Timeout", - sleep: 100 * time.Millisecond, - }, - { - name: "Canceled", - cancel: true, - }, - { - name: "NoTask", - noTask: true, - }, - { - name: "Error", - err: fmt.Errorf("random error"), - }, - } { - t.Run(testCase.name, func(t *testing.T) { - configRunner := config.Runner{ - FetchTimeout: 1 * time.Millisecond, - } - p := &mockPoller{} - p.init( - &config.Config{ - Runner: configRunner, - }, - &mockClient{ - sleep: testCase.sleep, - cancel: testCase.cancel, - noTask: testCase.noTask, - err: testCase.err, - }, - &mockRunner{}, - ) - task, ok := p.fetchTask(context.Background()) - if testCase.success { - assert.True(t, ok) - assert.NotNil(t, task) - } else { - assert.False(t, ok) - assert.Nil(t, task) - } - }) - } -} diff --git a/internal/app/run/runner.go b/internal/app/run/runner.go deleted file mode 100644 index e8654b6..0000000 --- a/internal/app/run/runner.go +++ /dev/null @@ -1,260 +0,0 @@ -// Copyright 2022 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package run - -import ( - "context" - "encoding/json" - "fmt" - "path/filepath" - "strings" - "sync" - "time" - - runnerv1 "code.gitea.io/actions-proto-go/runner/v1" - "connectrpc.com/connect" - "github.com/docker/docker/api/types/container" - "github.com/nektos/act/pkg/artifactcache" - "github.com/nektos/act/pkg/common" - "github.com/nektos/act/pkg/model" - "github.com/nektos/act/pkg/runner" - log "github.com/sirupsen/logrus" - - "gitea.com/gitea/act_runner/internal/pkg/client" - "gitea.com/gitea/act_runner/internal/pkg/config" - "gitea.com/gitea/act_runner/internal/pkg/labels" - "gitea.com/gitea/act_runner/internal/pkg/report" - "gitea.com/gitea/act_runner/internal/pkg/ver" -) - -// Runner runs the pipeline. -type Runner struct { - name string - - cfg *config.Config - - client client.Client - labels labels.Labels - envs map[string]string - - runningTasks sync.Map -} - -type RunnerInterface interface { - Run(ctx context.Context, task *runnerv1.Task) error -} - -func NewRunner(cfg *config.Config, reg *config.Registration, cli client.Client) *Runner { - ls := labels.Labels{} - for _, v := range reg.Labels { - if l, err := labels.Parse(v); err == nil { - ls = append(ls, l) - } - } - - if cfg.Runner.Envs == nil { - cfg.Runner.Envs = make(map[string]string, 10) - } - - cfg.Runner.Envs["GITHUB_SERVER_URL"] = reg.Address - - envs := make(map[string]string, len(cfg.Runner.Envs)) - for k, v := range cfg.Runner.Envs { - envs[k] = v - } - if cfg.Cache.Enabled == nil || *cfg.Cache.Enabled { - if cfg.Cache.ExternalServer != "" { - envs["ACTIONS_CACHE_URL"] = cfg.Cache.ExternalServer - } else { - cacheHandler, err := artifactcache.StartHandler( - cfg.Cache.Dir, - cfg.Cache.Host, - cfg.Cache.Port, - log.StandardLogger().WithField("module", "cache_request"), - ) - if err != nil { - log.Errorf("cannot init cache server, it will be disabled: %v", err) - // go on - } else { - envs["ACTIONS_CACHE_URL"] = cacheHandler.ExternalURL() + "/" - } - } - } - - // set artifact gitea api - artifactGiteaAPI := strings.TrimSuffix(cli.Address(), "/") + "/api/actions_pipeline/" - envs["ACTIONS_RUNTIME_URL"] = artifactGiteaAPI - envs["ACTIONS_RESULTS_URL"] = strings.TrimSuffix(cli.Address(), "/") - - // Set specific environments to distinguish between Gitea and GitHub - envs["GITEA_ACTIONS"] = "true" - envs["GITEA_ACTIONS_RUNNER_VERSION"] = ver.Version() - - return &Runner{ - name: reg.Name, - cfg: cfg, - client: cli, - labels: ls, - envs: envs, - } -} - -func (r *Runner) Run(ctx context.Context, task *runnerv1.Task) error { - if _, ok := r.runningTasks.Load(task.Id); ok { - return fmt.Errorf("task %d is already running", task.Id) - } - r.runningTasks.Store(task.Id, struct{}{}) - defer r.runningTasks.Delete(task.Id) - - ctx, cancel := context.WithTimeout(ctx, r.cfg.Runner.Timeout) - defer cancel() - reporter := report.NewReporter(ctx, cancel, r.client, task, r.cfg.Runner.ReportInterval) - var runErr error - defer func() { - lastWords := "" - if runErr != nil { - lastWords = runErr.Error() - } - _ = reporter.Close(lastWords) - }() - reporter.RunDaemon() - runErr = r.run(ctx, task, reporter) - - return nil -} - -func (r *Runner) run(ctx context.Context, task *runnerv1.Task, reporter *report.Reporter) (err error) { - defer func() { - if r := recover(); r != nil { - err = fmt.Errorf("panic: %v", r) - } - }() - - reporter.Logf("%s(version:%s) received task %v of job %v, be triggered by event: %s", r.name, ver.Version(), task.Id, task.Context.Fields["job"].GetStringValue(), task.Context.Fields["event_name"].GetStringValue()) - - workflow, jobID, err := generateWorkflow(task) - if err != nil { - return err - } - - plan, err := model.CombineWorkflowPlanner(workflow).PlanJob(jobID) - if err != nil { - return err - } - job := workflow.GetJob(jobID) - reporter.ResetSteps(len(job.Steps)) - - taskContext := task.Context.Fields - - log.Infof("task %v repo is %v %v %v", task.Id, taskContext["repository"].GetStringValue(), - taskContext["gitea_default_actions_url"].GetStringValue(), - r.client.Address()) - - preset := &model.GithubContext{ - Event: taskContext["event"].GetStructValue().AsMap(), - RunID: taskContext["run_id"].GetStringValue(), - RunNumber: taskContext["run_number"].GetStringValue(), - Actor: taskContext["actor"].GetStringValue(), - Repository: taskContext["repository"].GetStringValue(), - EventName: taskContext["event_name"].GetStringValue(), - Sha: taskContext["sha"].GetStringValue(), - Ref: taskContext["ref"].GetStringValue(), - RefName: taskContext["ref_name"].GetStringValue(), - RefType: taskContext["ref_type"].GetStringValue(), - HeadRef: taskContext["head_ref"].GetStringValue(), - BaseRef: taskContext["base_ref"].GetStringValue(), - Token: taskContext["token"].GetStringValue(), - RepositoryOwner: taskContext["repository_owner"].GetStringValue(), - RetentionDays: taskContext["retention_days"].GetStringValue(), - } - if t := task.Secrets["GITEA_TOKEN"]; t != "" { - preset.Token = t - } else if t := task.Secrets["GITHUB_TOKEN"]; t != "" { - preset.Token = t - } - - giteaRuntimeToken := taskContext["gitea_runtime_token"].GetStringValue() - if giteaRuntimeToken == "" { - // use task token to action api token for previous Gitea Server Versions - giteaRuntimeToken = preset.Token - } - r.envs["ACTIONS_RUNTIME_TOKEN"] = giteaRuntimeToken - - eventJSON, err := json.Marshal(preset.Event) - if err != nil { - return err - } - - maxLifetime := 3 * time.Hour - if deadline, ok := ctx.Deadline(); ok { - maxLifetime = time.Until(deadline) - } - - var inputs map[string]string - if preset.EventName == "workflow_dispatch" { - if inputsRaw, ok := preset.Event["inputs"]; ok { - inputs, _ = inputsRaw.(map[string]string) - } - } - - runnerConfig := &runner.Config{ - // On Linux, Workdir will be like "///" - // On Windows, Workdir will be like "\\\" - Workdir: filepath.FromSlash(filepath.Clean(fmt.Sprintf("/%s/%s", r.cfg.Container.WorkdirParent, preset.Repository))), - BindWorkdir: false, - ActionCacheDir: filepath.FromSlash(r.cfg.Host.WorkdirParent), - - ReuseContainers: false, - ForcePull: r.cfg.Container.ForcePull, - ForceRebuild: false, - LogOutput: true, - JSONLogger: false, - Env: r.envs, - Secrets: task.Secrets, - GitHubInstance: strings.TrimSuffix(r.client.Address(), "/"), - AutoRemove: true, - NoSkipCheckout: true, - PresetGitHubContext: preset, - EventJSON: string(eventJSON), - ContainerNamePrefix: fmt.Sprintf("GITEA-ACTIONS-TASK-%d", task.Id), - ContainerMaxLifetime: maxLifetime, - ContainerNetworkMode: container.NetworkMode(r.cfg.Container.Network), - ContainerNetworkEnableIPv6: r.cfg.Container.EnableIPv6, - ContainerOptions: r.cfg.Container.Options, - ContainerDaemonSocket: r.cfg.Container.DockerHost, - Privileged: r.cfg.Container.Privileged, - DefaultActionInstance: taskContext["gitea_default_actions_url"].GetStringValue(), - PlatformPicker: r.labels.PickPlatform, - Vars: task.Vars, - ValidVolumes: r.cfg.Container.ValidVolumes, - InsecureSkipTLS: r.cfg.Runner.Insecure, - Inputs: inputs, - } - - rr, err := runner.New(runnerConfig) - if err != nil { - return err - } - executor := rr.NewPlanExecutor(plan) - - reporter.Logf("workflow prepared") - - // add logger recorders - ctx = common.WithLoggerHook(ctx, reporter) - - execErr := executor(ctx) - reporter.SetOutputs(job.Outputs) - return execErr -} - -func (r *Runner) Declare(ctx context.Context, labels []string) (*connect.Response[runnerv1.DeclareResponse], error) { - return r.client.Declare(ctx, connect.NewRequest(&runnerv1.DeclareRequest{ - Version: ver.Version(), - Labels: labels, - })) -} - -func (r *Runner) Update(ctx context.Context, labels labels.Labels) { - r.labels = labels -} diff --git a/internal/app/run/runner_test.go b/internal/app/run/runner_test.go deleted file mode 100644 index 0145c70..0000000 --- a/internal/app/run/runner_test.go +++ /dev/null @@ -1,37 +0,0 @@ -package run - -import ( - "context" - "testing" - - "gitea.com/gitea/act_runner/internal/pkg/labels" - "github.com/stretchr/testify/assert" -) - -func TestLabelUpdate(t *testing.T) { - ctx := context.Background() - ls := labels.Labels{} - - initialLabel, err := labels.Parse("testlabel:docker://alpine") - assert.NoError(t, err) - ls = append(ls, initialLabel) - - newLs := labels.Labels{} - - newLabel, err := labels.Parse("next label:host") - assert.NoError(t, err) - newLs = append(newLs, initialLabel) - newLs = append(newLs, newLabel) - - runner := Runner{ - labels: ls, - } - - assert.Contains(t, runner.labels, initialLabel) - assert.NotContains(t, runner.labels, newLabel) - - runner.Update(ctx, newLs) - - assert.Contains(t, runner.labels, initialLabel) - assert.Contains(t, runner.labels, newLabel) -} diff --git a/internal/app/run/workflow.go b/internal/app/run/workflow.go deleted file mode 100644 index a6fbb71..0000000 --- a/internal/app/run/workflow.go +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 2023 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package run - -import ( - "bytes" - "fmt" - "sort" - "strings" - - runnerv1 "code.gitea.io/actions-proto-go/runner/v1" - "github.com/nektos/act/pkg/model" - "gopkg.in/yaml.v3" -) - -func generateWorkflow(task *runnerv1.Task) (*model.Workflow, string, error) { - workflow, err := model.ReadWorkflow(bytes.NewReader(task.WorkflowPayload)) - if err != nil { - return nil, "", err - } - - jobIDs := workflow.GetJobIDs() - if len(jobIDs) != 1 { - return nil, "", fmt.Errorf("multiple jobs found: %v", jobIDs) - } - jobID := jobIDs[0] - - needJobIDs := make([]string, 0, len(task.Needs)) - for id, need := range task.Needs { - needJobIDs = append(needJobIDs, id) - needJob := &model.Job{ - Outputs: need.Outputs, - Result: strings.ToLower(strings.TrimPrefix(need.Result.String(), "RESULT_")), - } - workflow.Jobs[id] = needJob - } - sort.Strings(needJobIDs) - - rawNeeds := yaml.Node{ - Kind: yaml.SequenceNode, - Content: make([]*yaml.Node, 0, len(needJobIDs)), - } - for _, id := range needJobIDs { - rawNeeds.Content = append(rawNeeds.Content, &yaml.Node{ - Kind: yaml.ScalarNode, - Value: id, - }) - } - - workflow.Jobs[jobID].RawNeeds = rawNeeds - - return workflow, jobID, nil -} diff --git a/internal/app/run/workflow_test.go b/internal/app/run/workflow_test.go deleted file mode 100644 index 4ab31b1..0000000 --- a/internal/app/run/workflow_test.go +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright 2023 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package run - -import ( - "testing" - - runnerv1 "code.gitea.io/actions-proto-go/runner/v1" - "github.com/nektos/act/pkg/model" - "github.com/stretchr/testify/require" - "gotest.tools/v3/assert" -) - -func Test_generateWorkflow(t *testing.T) { - type args struct { - task *runnerv1.Task - } - tests := []struct { - name string - args args - assert func(t *testing.T, wf *model.Workflow, err error) - want1 string - wantErr bool - }{ - { - name: "has needs", - args: args{ - task: &runnerv1.Task{ - WorkflowPayload: []byte(` -name: Build and deploy -on: push - -jobs: - job9: - needs: build - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - run: ./deploy --build ${{ needs.job1.outputs.output1 }} - - run: ./deploy --build ${{ needs.job2.outputs.output2 }} -`), - Needs: map[string]*runnerv1.TaskNeed{ - "job1": { - Outputs: map[string]string{ - "output1": "output1 value", - }, - Result: runnerv1.Result_RESULT_SUCCESS, - }, - "job2": { - Outputs: map[string]string{ - "output2": "output2 value", - }, - Result: runnerv1.Result_RESULT_SUCCESS, - }, - }, - }, - }, - assert: func(t *testing.T, wf *model.Workflow, err error) { - assert.DeepEqual(t, wf.GetJob("job9").Needs(), []string{"job1", "job2"}) - }, - want1: "job9", - wantErr: false, - }, - { - name: "valid YAML syntax in top level env but wrong value type", - args: args{ - task: &runnerv1.Task{ - WorkflowPayload: []byte(` -on: push - -env: - value: {{ }} -`), - }, - }, - assert: func(t *testing.T, wf *model.Workflow, err error) { - require.Nil(t, wf) - assert.ErrorContains(t, err, "cannot unmarshal") - }, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, got1, err := generateWorkflow(tt.args.task) - if tt.wantErr { - require.Error(t, err) - } else { - require.NoError(t, err) - assert.Equal(t, got1, tt.want1) - } - tt.assert(t, got, err) - }) - } -} diff --git a/internal/pkg/client/mocks/Client.go b/internal/pkg/client/mocks/Client.go deleted file mode 100644 index a8bfdb1..0000000 --- a/internal/pkg/client/mocks/Client.go +++ /dev/null @@ -1,219 +0,0 @@ -// Code generated by mockery v2.26.1. DO NOT EDIT. - -package mocks - -import ( - context "context" - - connect "connectrpc.com/connect" - - mock "github.com/stretchr/testify/mock" - - pingv1 "code.gitea.io/actions-proto-go/ping/v1" - - runnerv1 "code.gitea.io/actions-proto-go/runner/v1" -) - -// Client is an autogenerated mock type for the Client type -type Client struct { - mock.Mock -} - -// Address provides a mock function with given fields: -func (_m *Client) Address() string { - ret := _m.Called() - - var r0 string - if rf, ok := ret.Get(0).(func() string); ok { - r0 = rf() - } else { - r0 = ret.Get(0).(string) - } - - return r0 -} - -// Declare provides a mock function with given fields: _a0, _a1 -func (_m *Client) Declare(_a0 context.Context, _a1 *connect.Request[runnerv1.DeclareRequest]) (*connect.Response[runnerv1.DeclareResponse], error) { - ret := _m.Called(_a0, _a1) - - var r0 *connect.Response[runnerv1.DeclareResponse] - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, *connect.Request[runnerv1.DeclareRequest]) (*connect.Response[runnerv1.DeclareResponse], error)); ok { - return rf(_a0, _a1) - } - if rf, ok := ret.Get(0).(func(context.Context, *connect.Request[runnerv1.DeclareRequest]) *connect.Response[runnerv1.DeclareResponse]); ok { - r0 = rf(_a0, _a1) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*connect.Response[runnerv1.DeclareResponse]) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, *connect.Request[runnerv1.DeclareRequest]) error); ok { - r1 = rf(_a0, _a1) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// FetchTask provides a mock function with given fields: _a0, _a1 -func (_m *Client) FetchTask(_a0 context.Context, _a1 *connect.Request[runnerv1.FetchTaskRequest]) (*connect.Response[runnerv1.FetchTaskResponse], error) { - ret := _m.Called(_a0, _a1) - - var r0 *connect.Response[runnerv1.FetchTaskResponse] - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, *connect.Request[runnerv1.FetchTaskRequest]) (*connect.Response[runnerv1.FetchTaskResponse], error)); ok { - return rf(_a0, _a1) - } - if rf, ok := ret.Get(0).(func(context.Context, *connect.Request[runnerv1.FetchTaskRequest]) *connect.Response[runnerv1.FetchTaskResponse]); ok { - r0 = rf(_a0, _a1) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*connect.Response[runnerv1.FetchTaskResponse]) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, *connect.Request[runnerv1.FetchTaskRequest]) error); ok { - r1 = rf(_a0, _a1) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// Insecure provides a mock function with given fields: -func (_m *Client) Insecure() bool { - ret := _m.Called() - - var r0 bool - if rf, ok := ret.Get(0).(func() bool); ok { - r0 = rf() - } else { - r0 = ret.Get(0).(bool) - } - - return r0 -} - -// Ping provides a mock function with given fields: _a0, _a1 -func (_m *Client) Ping(_a0 context.Context, _a1 *connect.Request[pingv1.PingRequest]) (*connect.Response[pingv1.PingResponse], error) { - ret := _m.Called(_a0, _a1) - - var r0 *connect.Response[pingv1.PingResponse] - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, *connect.Request[pingv1.PingRequest]) (*connect.Response[pingv1.PingResponse], error)); ok { - return rf(_a0, _a1) - } - if rf, ok := ret.Get(0).(func(context.Context, *connect.Request[pingv1.PingRequest]) *connect.Response[pingv1.PingResponse]); ok { - r0 = rf(_a0, _a1) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*connect.Response[pingv1.PingResponse]) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, *connect.Request[pingv1.PingRequest]) error); ok { - r1 = rf(_a0, _a1) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// Register provides a mock function with given fields: _a0, _a1 -func (_m *Client) Register(_a0 context.Context, _a1 *connect.Request[runnerv1.RegisterRequest]) (*connect.Response[runnerv1.RegisterResponse], error) { - ret := _m.Called(_a0, _a1) - - var r0 *connect.Response[runnerv1.RegisterResponse] - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, *connect.Request[runnerv1.RegisterRequest]) (*connect.Response[runnerv1.RegisterResponse], error)); ok { - return rf(_a0, _a1) - } - if rf, ok := ret.Get(0).(func(context.Context, *connect.Request[runnerv1.RegisterRequest]) *connect.Response[runnerv1.RegisterResponse]); ok { - r0 = rf(_a0, _a1) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*connect.Response[runnerv1.RegisterResponse]) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, *connect.Request[runnerv1.RegisterRequest]) error); ok { - r1 = rf(_a0, _a1) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// UpdateLog provides a mock function with given fields: _a0, _a1 -func (_m *Client) UpdateLog(_a0 context.Context, _a1 *connect.Request[runnerv1.UpdateLogRequest]) (*connect.Response[runnerv1.UpdateLogResponse], error) { - ret := _m.Called(_a0, _a1) - - var r0 *connect.Response[runnerv1.UpdateLogResponse] - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, *connect.Request[runnerv1.UpdateLogRequest]) (*connect.Response[runnerv1.UpdateLogResponse], error)); ok { - return rf(_a0, _a1) - } - if rf, ok := ret.Get(0).(func(context.Context, *connect.Request[runnerv1.UpdateLogRequest]) *connect.Response[runnerv1.UpdateLogResponse]); ok { - r0 = rf(_a0, _a1) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*connect.Response[runnerv1.UpdateLogResponse]) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, *connect.Request[runnerv1.UpdateLogRequest]) error); ok { - r1 = rf(_a0, _a1) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// UpdateTask provides a mock function with given fields: _a0, _a1 -func (_m *Client) UpdateTask(_a0 context.Context, _a1 *connect.Request[runnerv1.UpdateTaskRequest]) (*connect.Response[runnerv1.UpdateTaskResponse], error) { - ret := _m.Called(_a0, _a1) - - var r0 *connect.Response[runnerv1.UpdateTaskResponse] - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, *connect.Request[runnerv1.UpdateTaskRequest]) (*connect.Response[runnerv1.UpdateTaskResponse], error)); ok { - return rf(_a0, _a1) - } - if rf, ok := ret.Get(0).(func(context.Context, *connect.Request[runnerv1.UpdateTaskRequest]) *connect.Response[runnerv1.UpdateTaskResponse]); ok { - r0 = rf(_a0, _a1) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*connect.Response[runnerv1.UpdateTaskResponse]) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, *connect.Request[runnerv1.UpdateTaskRequest]) error); ok { - r1 = rf(_a0, _a1) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -type mockConstructorTestingTNewClient interface { - mock.TestingT - Cleanup(func()) -} - -// NewClient creates a new instance of Client. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewClient(t mockConstructorTestingTNewClient) *Client { - mock := &Client{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/internal/pkg/config/config.example.yaml b/internal/pkg/config/config.example.yaml deleted file mode 100644 index 32dfb68..0000000 --- a/internal/pkg/config/config.example.yaml +++ /dev/null @@ -1,100 +0,0 @@ -# Example configuration file, it's safe to copy this as the default config file without any modification. - -# You don't have to copy this file to your instance, -# just run `./act_runner generate-config > config.yaml` to generate a config file. - -log: - # The level of logging, can be trace, debug, info, warn, error, fatal - level: info - -runner: - # Where to store the registration result. - file: .runner - # Execute how many tasks concurrently at the same time. - capacity: 1 - # Extra environment variables to run jobs. - envs: - A_TEST_ENV_NAME_1: a_test_env_value_1 - A_TEST_ENV_NAME_2: a_test_env_value_2 - # Extra environment variables to run jobs from a file. - # It will be ignored if it's empty or the file doesn't exist. - env_file: .env - # The timeout for a job to be finished. - # Please note that the Forgejo instance also has a timeout (3h by default) for the job. - # So the job could be stopped by the Forgejo instance if it's timeout is shorter than this. - timeout: 3h - # The timeout for the runner to wait for running jobs to finish when - # shutting down because a TERM or INT signal has been received. Any - # running jobs that haven't finished after this timeout will be - # cancelled. - # If unset or zero the jobs will be cancelled immediately. - shutdown_timeout: 3h - # Whether skip verifying the TLS certificate of the instance. - insecure: false - # The timeout for fetching the job from the Forgejo instance. - fetch_timeout: 5s - # The interval for fetching the job from the Forgejo instance. - fetch_interval: 2s - # The interval for reporting the job status and logs to the Forgejo instance. - report_interval: 1s - # The labels of a runner are used to determine which jobs the runner can run, and how to run them. - # Like: ["macos-arm64:host", "ubuntu-latest:docker://node:20-bookworm", "ubuntu-22.04:docker://node:20-bookworm"] - # If it's empty when registering, it will ask for inputting labels. - # If it's empty when execute `deamon`, will use labels in `.runner` file. - labels: [] - -cache: - # Enable cache server to use actions/cache. - enabled: true - # The directory to store the cache data. - # If it's empty, the cache data will be stored in $HOME/.cache/actcache. - dir: "" - # The host of the cache server. - # It's not for the address to listen, but the address to connect from job containers. - # So 0.0.0.0 is a bad choice, leave it empty to detect automatically. - host: "" - # The port of the cache server. - # 0 means to use a random available port. - port: 0 - # The external cache server URL. Valid only when enable is true. - # If it's specified, act_runner will use this URL as the ACTIONS_CACHE_URL rather than start a server by itself. - # The URL should generally end with "/". - external_server: "" - -container: - # Specifies the network to which the container will connect. - # Could be host, bridge or the name of a custom network. - # If it's empty, create a network automatically. - network: "" - # Whether to create networks with IPv6 enabled. Requires the Docker daemon to be set up accordingly. - # Only takes effect if "network" is set to "". - enable_ipv6: false - # Whether to use privileged mode or not when launching task containers (privileged mode is required for Docker-in-Docker). - privileged: false - # And other options to be used when the container is started (eg, --add-host=my.forgejo.url:host-gateway). - options: - # The parent directory of a job's working directory. - # If it's empty, /workspace will be used. - workdir_parent: - # Volumes (including bind mounts) can be mounted to containers. Glob syntax is supported, see https://github.com/gobwas/glob - # You can specify multiple volumes. If the sequence is empty, no volumes can be mounted. - # For example, if you only allow containers to mount the `data` volume and all the json files in `/src`, you should change the config to: - # valid_volumes: - # - data - # - /src/*.json - # If you want to allow any volume, please use the following configuration: - # valid_volumes: - # - '**' - valid_volumes: [] - # overrides the docker client host with the specified one. - # If it's empty, act_runner will find an available docker host automatically. - # If it's "-", act_runner will find an available docker host automatically, but the docker host won't be mounted to the job containers and service containers. - # If it's not empty or "-", the specified docker host will be used. An error will be returned if it doesn't work. - docker_host: "" - # Pull docker image(s) even if already present - force_pull: false - -host: - # The parent directory of a job's working directory. - # If it's empty, $HOME/.cache/act/ will be used. - workdir_parent: diff --git a/internal/pkg/config/config.go b/internal/pkg/config/config.go deleted file mode 100644 index a1536b3..0000000 --- a/internal/pkg/config/config.go +++ /dev/null @@ -1,166 +0,0 @@ -// Copyright 2022 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package config - -import ( - "fmt" - "os" - "path/filepath" - "time" - - "github.com/joho/godotenv" - log "github.com/sirupsen/logrus" - "gopkg.in/yaml.v3" -) - -// Log represents the configuration for logging. -type Log struct { - Level string `yaml:"level"` // Level indicates the logging level. -} - -// Runner represents the configuration for the runner. -type Runner struct { - File string `yaml:"file"` // File specifies the file path for the runner. - Capacity int `yaml:"capacity"` // Capacity specifies the capacity of the runner. - Envs map[string]string `yaml:"envs"` // Envs stores environment variables for the runner. - EnvFile string `yaml:"env_file"` // EnvFile specifies the path to the file containing environment variables for the runner. - Timeout time.Duration `yaml:"timeout"` // Timeout specifies the duration for runner timeout. - ShutdownTimeout time.Duration `yaml:"shutdown_timeout"` // ShutdownTimeout specifies the duration to wait for running jobs to complete during a shutdown of the runner. - Insecure bool `yaml:"insecure"` // Insecure indicates whether the runner operates in an insecure mode. - FetchTimeout time.Duration `yaml:"fetch_timeout"` // FetchTimeout specifies the timeout duration for fetching resources. - FetchInterval time.Duration `yaml:"fetch_interval"` // FetchInterval specifies the interval duration for fetching resources. - ReportInterval time.Duration `yaml:"report_interval"` // ReportInterval specifies the interval duration for reporting status and logs of a running job. - Labels []string `yaml:"labels"` // Labels specify the labels of the runner. Labels are declared on each startup -} - -// Cache represents the configuration for caching. -type Cache struct { - Enabled *bool `yaml:"enabled"` // Enabled indicates whether caching is enabled. It is a pointer to distinguish between false and not set. If not set, it will be true. - Dir string `yaml:"dir"` // Dir specifies the directory path for caching. - Host string `yaml:"host"` // Host specifies the caching host. - Port uint16 `yaml:"port"` // Port specifies the caching port. - ExternalServer string `yaml:"external_server"` // ExternalServer specifies the URL of external cache server -} - -// Container represents the configuration for the container. -type Container struct { - Network string `yaml:"network"` // Network specifies the network for the container. - NetworkMode string `yaml:"network_mode"` // Deprecated: use Network instead. Could be removed after Gitea 1.20 - EnableIPv6 bool `yaml:"enable_ipv6"` // EnableIPv6 indicates whether the network is created with IPv6 enabled. - Privileged bool `yaml:"privileged"` // Privileged indicates whether the container runs in privileged mode. - Options string `yaml:"options"` // Options specifies additional options for the container. - WorkdirParent string `yaml:"workdir_parent"` // WorkdirParent specifies the parent directory for the container's working directory. - ValidVolumes []string `yaml:"valid_volumes"` // ValidVolumes specifies the volumes (including bind mounts) can be mounted to containers. - DockerHost string `yaml:"docker_host"` // DockerHost specifies the Docker host. It overrides the value specified in environment variable DOCKER_HOST. - ForcePull bool `yaml:"force_pull"` // Pull docker image(s) even if already present -} - -// Host represents the configuration for the host. -type Host struct { - WorkdirParent string `yaml:"workdir_parent"` // WorkdirParent specifies the parent directory for the host's working directory. -} - -// Config represents the overall configuration. -type Config struct { - Log Log `yaml:"log"` // Log represents the configuration for logging. - Runner Runner `yaml:"runner"` // Runner represents the configuration for the runner. - Cache Cache `yaml:"cache"` // Cache represents the configuration for caching. - Container Container `yaml:"container"` // Container represents the configuration for the container. - Host Host `yaml:"host"` // Host represents the configuration for the host. -} - -// Tune the config settings accordingly to the Forgejo instance that will be used. -func (c *Config) Tune(instanceURL string) { - if instanceURL == "https://codeberg.org" { - if c.Runner.FetchInterval < 30*time.Second { - log.Info("The runner is configured to be used by a public instance, fetch interval is set to 30 seconds.") - c.Runner.FetchInterval = 30 * time.Second - } - } -} - -// LoadDefault returns the default configuration. -// If file is not empty, it will be used to load the configuration. -func LoadDefault(file string) (*Config, error) { - cfg := &Config{} - if file != "" { - content, err := os.ReadFile(file) - if err != nil { - return nil, fmt.Errorf("open config file %q: %w", file, err) - } - if err := yaml.Unmarshal(content, cfg); err != nil { - return nil, fmt.Errorf("parse config file %q: %w", file, err) - } - } - compatibleWithOldEnvs(file != "", cfg) - - if cfg.Runner.EnvFile != "" { - if stat, err := os.Stat(cfg.Runner.EnvFile); err == nil && !stat.IsDir() { - envs, err := godotenv.Read(cfg.Runner.EnvFile) - if err != nil { - return nil, fmt.Errorf("read env file %q: %w", cfg.Runner.EnvFile, err) - } - if cfg.Runner.Envs == nil { - cfg.Runner.Envs = map[string]string{} - } - for k, v := range envs { - cfg.Runner.Envs[k] = v - } - } - } - - if cfg.Log.Level == "" { - cfg.Log.Level = "info" - } - if cfg.Runner.File == "" { - cfg.Runner.File = ".runner" - } - if cfg.Runner.Capacity <= 0 { - cfg.Runner.Capacity = 1 - } - if cfg.Runner.Timeout <= 0 { - cfg.Runner.Timeout = 3 * time.Hour - } - if cfg.Cache.Enabled == nil { - b := true - cfg.Cache.Enabled = &b - } - if *cfg.Cache.Enabled { - if cfg.Cache.Dir == "" { - home, _ := os.UserHomeDir() - cfg.Cache.Dir = filepath.Join(home, ".cache", "actcache") - } - } - if cfg.Container.WorkdirParent == "" { - cfg.Container.WorkdirParent = "workspace" - } - if cfg.Host.WorkdirParent == "" { - home, _ := os.UserHomeDir() - cfg.Host.WorkdirParent = filepath.Join(home, ".cache", "act") - } - if cfg.Runner.FetchTimeout <= 0 { - cfg.Runner.FetchTimeout = 5 * time.Second - } - if cfg.Runner.FetchInterval <= 0 { - cfg.Runner.FetchInterval = 2 * time.Second - } - if cfg.Runner.ReportInterval <= 0 { - cfg.Runner.ReportInterval = time.Second - } - - // although `container.network_mode` will be deprecated, but we have to be compatible with it for now. - if cfg.Container.NetworkMode != "" && cfg.Container.Network == "" { - log.Warn("You are trying to use deprecated configuration item of `container.network_mode`, please use `container.network` instead.") - if cfg.Container.NetworkMode == "bridge" { - // Previously, if the value of `container.network_mode` is `bridge`, we will create a new network for job. - // But “bridge” is easily confused with the bridge network created by Docker by default. - // So we set the value of `container.network` to empty string to make `act_runner` automatically create a new network for job. - cfg.Container.Network = "" - } else { - cfg.Container.Network = cfg.Container.NetworkMode - } - } - - return cfg, nil -} diff --git a/internal/pkg/config/config_test.go b/internal/pkg/config/config_test.go deleted file mode 100644 index d2ddf2f..0000000 --- a/internal/pkg/config/config_test.go +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright 2024 The Forgejo Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package config - -import ( - "testing" - "time" - - "github.com/stretchr/testify/assert" -) - -func TestConfigTune(t *testing.T) { - c := &Config{ - Runner: Runner{}, - } - - t.Run("Public instance tuning", func(t *testing.T) { - c.Runner.FetchInterval = 60 * time.Second - c.Tune("https://codeberg.org") - assert.EqualValues(t, 60*time.Second, c.Runner.FetchInterval) - - c.Runner.FetchInterval = 2 * time.Second - c.Tune("https://codeberg.org") - assert.EqualValues(t, 30*time.Second, c.Runner.FetchInterval) - }) - - t.Run("Non-public instance tuning", func(t *testing.T) { - c.Runner.FetchInterval = 60 * time.Second - c.Tune("https://example.com") - assert.EqualValues(t, 60*time.Second, c.Runner.FetchInterval) - - c.Runner.FetchInterval = 2 * time.Second - c.Tune("https://codeberg.com") - assert.EqualValues(t, 2*time.Second, c.Runner.FetchInterval) - }) -} diff --git a/internal/pkg/envcheck/doc.go b/internal/pkg/envcheck/doc.go deleted file mode 100644 index 8641a77..0000000 --- a/internal/pkg/envcheck/doc.go +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright 2023 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -// Package envcheck provides a simple way to check if the environment is ready to run jobs. -package envcheck diff --git a/internal/pkg/envcheck/docker.go b/internal/pkg/envcheck/docker.go deleted file mode 100644 index f115bc7..0000000 --- a/internal/pkg/envcheck/docker.go +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2023 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package envcheck - -import ( - "context" - "fmt" - - "github.com/docker/docker/client" -) - -func CheckIfDockerRunning(ctx context.Context, configDockerHost string) error { - opts := []client.Opt{ - client.FromEnv, - } - - if configDockerHost != "" { - opts = append(opts, client.WithHost(configDockerHost)) - } - - cli, err := client.NewClientWithOpts(opts...) - if err != nil { - return err - } - defer cli.Close() - - _, err = cli.Ping(ctx) - if err != nil { - return fmt.Errorf("cannot ping the docker daemon. is it running? %w", err) - } - - return nil -} diff --git a/internal/pkg/labels/labels.go b/internal/pkg/labels/labels.go deleted file mode 100644 index f448fdf..0000000 --- a/internal/pkg/labels/labels.go +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright 2023 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package labels - -import ( - "fmt" - "strings" -) - -const ( - SchemeHost = "host" - SchemeDocker = "docker" - SchemeLXC = "lxc" -) - -type Label struct { - Name string - Schema string - Arg string -} - -func Parse(str string) (*Label, error) { - splits := strings.SplitN(str, ":", 3) - label := &Label{ - Name: splits[0], - Schema: "host", - Arg: "", - } - if len(splits) >= 2 { - label.Schema = splits[1] - } - if len(splits) >= 3 { - label.Arg = splits[2] - } - if label.Schema != SchemeHost && label.Schema != SchemeDocker && label.Schema != SchemeLXC { - return nil, fmt.Errorf("unsupported schema: %s", label.Schema) - } - return label, nil -} - -type Labels []*Label - -func (l Labels) RequireDocker() bool { - for _, label := range l { - if label.Schema == SchemeDocker { - return true - } - } - return false -} - -func (l Labels) PickPlatform(runsOn []string) string { - platforms := make(map[string]string, len(l)) - for _, label := range l { - switch label.Schema { - case SchemeDocker: - // "//" will be ignored - platforms[label.Name] = strings.TrimPrefix(label.Arg, "//") - case SchemeHost: - platforms[label.Name] = "-self-hosted" - case SchemeLXC: - platforms[label.Name] = "lxc:" + strings.TrimPrefix(label.Arg, "//") - default: - // It should not happen, because Parse has checked it. - continue - } - } - for _, v := range runsOn { - if v, ok := platforms[v]; ok { - return v - } - } - - // TODO: support multiple labels - // like: - // ["ubuntu-22.04"] => "ubuntu:22.04" - // ["with-gpu"] => "linux:with-gpu" - // ["ubuntu-22.04", "with-gpu"] => "ubuntu:22.04_with-gpu" - - // return default. - // So the runner receives a task with a label that the runner doesn't have, - // it happens when the user have edited the label of the runner in the web UI. - // TODO: it may be not correct, what if the runner is used as host mode only? - return "node:20-bullseye" -} - -func (l Labels) Names() []string { - names := make([]string, 0, len(l)) - for _, label := range l { - names = append(names, label.Name) - } - return names -} - -func (l Labels) ToStrings() []string { - ls := make([]string, 0, len(l)) - for _, label := range l { - lbl := label.Name - if label.Schema != "" { - lbl += ":" + label.Schema - if label.Arg != "" { - lbl += ":" + label.Arg - } - } - ls = append(ls, lbl) - } - return ls -} diff --git a/internal/pkg/labels/labels_test.go b/internal/pkg/labels/labels_test.go deleted file mode 100644 index e46a27b..0000000 --- a/internal/pkg/labels/labels_test.go +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright 2023 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package labels - -import ( - "testing" - - "github.com/stretchr/testify/require" - "gotest.tools/v3/assert" -) - -func TestParse(t *testing.T) { - tests := []struct { - args string - want *Label - wantErr bool - }{ - { - args: "ubuntu:docker://node:18", - want: &Label{ - Name: "ubuntu", - Schema: "docker", - Arg: "//node:18", - }, - wantErr: false, - }, - { - args: "ubuntu:host", - want: &Label{ - Name: "ubuntu", - Schema: "host", - Arg: "", - }, - wantErr: false, - }, - { - args: "ubuntu", - want: &Label{ - Name: "ubuntu", - Schema: "host", - Arg: "", - }, - wantErr: false, - }, - { - args: "ubuntu:vm:ubuntu-18.04", - want: nil, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.args, func(t *testing.T) { - got, err := Parse(tt.args) - if tt.wantErr { - require.Error(t, err) - return - } - require.NoError(t, err) - assert.DeepEqual(t, got, tt.want) - }) - } -} diff --git a/internal/pkg/report/reporter_test.go b/internal/pkg/report/reporter_test.go deleted file mode 100644 index 524e972..0000000 --- a/internal/pkg/report/reporter_test.go +++ /dev/null @@ -1,198 +0,0 @@ -// Copyright 2023 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package report - -import ( - "context" - "strings" - "testing" - "time" - - runnerv1 "code.gitea.io/actions-proto-go/runner/v1" - connect_go "connectrpc.com/connect" - log "github.com/sirupsen/logrus" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" - "google.golang.org/protobuf/types/known/structpb" - - "gitea.com/gitea/act_runner/internal/pkg/client/mocks" -) - -func TestReporter_parseLogRow(t *testing.T) { - tests := []struct { - name string - debugOutputEnabled bool - args []string - want []string - }{ - { - "No command", false, - []string{"Hello, world!"}, - []string{"Hello, world!"}, - }, - { - "Add-mask", false, - []string{ - "foo mysecret bar", - "::add-mask::mysecret", - "foo mysecret bar", - }, - []string{ - "foo mysecret bar", - "", - "foo *** bar", - }, - }, - { - "Debug enabled", true, - []string{ - "::debug::GitHub Actions runtime token access controls", - }, - []string{ - "GitHub Actions runtime token access controls", - }, - }, - { - "Debug not enabled", false, - []string{ - "::debug::GitHub Actions runtime token access controls", - }, - []string{ - "", - }, - }, - { - "notice", false, - []string{ - "::notice file=file.name,line=42,endLine=48,title=Cool Title::Gosh, that's not going to work", - }, - []string{ - "::notice file=file.name,line=42,endLine=48,title=Cool Title::Gosh, that's not going to work", - }, - }, - { - "warning", false, - []string{ - "::warning file=file.name,line=42,endLine=48,title=Cool Title::Gosh, that's not going to work", - }, - []string{ - "::warning file=file.name,line=42,endLine=48,title=Cool Title::Gosh, that's not going to work", - }, - }, - { - "error", false, - []string{ - "::error file=file.name,line=42,endLine=48,title=Cool Title::Gosh, that's not going to work", - }, - []string{ - "::error file=file.name,line=42,endLine=48,title=Cool Title::Gosh, that's not going to work", - }, - }, - { - "group", false, - []string{ - "::group::", - "::endgroup::", - }, - []string{ - "##[group]", - "##[endgroup]", - }, - }, - { - "stop-commands", false, - []string{ - "::add-mask::foo", - "::stop-commands::myverycoolstoptoken", - "::add-mask::bar", - "::debug::Stuff", - "myverycoolstoptoken", - "::add-mask::baz", - "::myverycoolstoptoken::", - "::add-mask::wibble", - "foo bar baz wibble", - }, - []string{ - "", - "", - "::add-mask::bar", - "::debug::Stuff", - "myverycoolstoptoken", - "::add-mask::baz", - "", - "", - "*** bar baz ***", - }, - }, - { - "unknown command", false, - []string{ - "::set-mask::foo", - }, - []string{ - "::set-mask::foo", - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - r := &Reporter{ - logReplacer: strings.NewReplacer(), - debugOutputEnabled: tt.debugOutputEnabled, - } - for idx, arg := range tt.args { - rv := r.parseLogRow(&log.Entry{Message: arg}) - got := "" - - if rv != nil { - got = rv.Content - } - - assert.Equal(t, tt.want[idx], got) - } - }) - } -} - -func TestReporter_Fire(t *testing.T) { - t.Run("ignore command lines", func(t *testing.T) { - client := mocks.NewClient(t) - client.On("UpdateLog", mock.Anything, mock.Anything).Return(func(_ context.Context, req *connect_go.Request[runnerv1.UpdateLogRequest]) (*connect_go.Response[runnerv1.UpdateLogResponse], error) { - t.Logf("Received UpdateLog: %s", req.Msg.String()) - return connect_go.NewResponse(&runnerv1.UpdateLogResponse{ - AckIndex: req.Msg.Index + int64(len(req.Msg.Rows)), - }), nil - }) - client.On("UpdateTask", mock.Anything, mock.Anything).Return(func(_ context.Context, req *connect_go.Request[runnerv1.UpdateTaskRequest]) (*connect_go.Response[runnerv1.UpdateTaskResponse], error) { - t.Logf("Received UpdateTask: %s", req.Msg.String()) - return connect_go.NewResponse(&runnerv1.UpdateTaskResponse{}), nil - }) - ctx, cancel := context.WithCancel(context.Background()) - taskCtx, err := structpb.NewStruct(map[string]interface{}{}) - require.NoError(t, err) - reporter := NewReporter(ctx, cancel, client, &runnerv1.Task{ - Context: taskCtx, - }, time.Second) - defer func() { - assert.NoError(t, reporter.Close("")) - }() - reporter.ResetSteps(5) - - dataStep0 := map[string]interface{}{ - "stage": "Main", - "stepNumber": 0, - "raw_output": true, - } - - assert.NoError(t, reporter.Fire(&log.Entry{Message: "regular log line", Data: dataStep0})) - assert.NoError(t, reporter.Fire(&log.Entry{Message: "::debug::debug log line", Data: dataStep0})) - assert.NoError(t, reporter.Fire(&log.Entry{Message: "regular log line", Data: dataStep0})) - assert.NoError(t, reporter.Fire(&log.Entry{Message: "::debug::debug log line", Data: dataStep0})) - assert.NoError(t, reporter.Fire(&log.Entry{Message: "::debug::debug log line", Data: dataStep0})) - assert.NoError(t, reporter.Fire(&log.Entry{Message: "regular log line", Data: dataStep0})) - - assert.Equal(t, int64(3), reporter.state.Steps[0].LogLength) - }) -} diff --git a/internal/pkg/ver/version.go b/internal/pkg/ver/version.go deleted file mode 100644 index 3c07a18..0000000 --- a/internal/pkg/ver/version.go +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright 2023 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package ver - -// go build -ldflags "-X gitea.com/gitea/act_runner/internal/pkg/ver.version=1.2.3" -var version = "dev" - -func Version() string { - return version -} diff --git a/main.go b/main.go index 4adbd13..8562842 100644 --- a/main.go +++ b/main.go @@ -1,19 +1,34 @@ -// Copyright 2022 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - package main import ( "context" + "os" "os/signal" "syscall" - "gitea.com/gitea/act_runner/internal/app/cmd" + "codeberg.org/forgejo/runner/cmd" ) +func withContextFunc(ctx context.Context, f func()) context.Context { + ctx, cancel := context.WithCancel(ctx) + go func() { + c := make(chan os.Signal, 1) + signal.Notify(c, syscall.SIGINT, syscall.SIGTERM) + defer signal.Stop(c) + + select { + case <-ctx.Done(): + case <-c: + cancel() + f() + } + }() + + return ctx +} + func main() { - ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) - defer stop() + ctx := withContextFunc(context.Background(), func() {}) // run the command cmd.Execute(ctx) } diff --git a/poller/metric.go b/poller/metric.go new file mode 100644 index 0000000..08f0fe7 --- /dev/null +++ b/poller/metric.go @@ -0,0 +1,33 @@ +package poller + +import "sync/atomic" + +// Metric interface +type Metric interface { + IncBusyWorker() int64 + DecBusyWorker() int64 + BusyWorkers() int64 +} + +var _ Metric = (*metric)(nil) + +type metric struct { + busyWorkers int64 +} + +// NewMetric for default metric structure +func NewMetric() Metric { + return &metric{} +} + +func (m *metric) IncBusyWorker() int64 { + return atomic.AddInt64(&m.busyWorkers, 1) +} + +func (m *metric) DecBusyWorker() int64 { + return atomic.AddInt64(&m.busyWorkers, -1) +} + +func (m *metric) BusyWorkers() int64 { + return atomic.LoadInt64(&m.busyWorkers) +} diff --git a/poller/poller.go b/poller/poller.go new file mode 100644 index 0000000..9825551 --- /dev/null +++ b/poller/poller.go @@ -0,0 +1,156 @@ +package poller + +import ( + "context" + "errors" + "sync" + "time" + + runnerv1 "code.gitea.io/actions-proto-go/runner/v1" + "github.com/bufbuild/connect-go" + log "github.com/sirupsen/logrus" + + "codeberg.org/forgejo/runner/client" + "codeberg.org/forgejo/runner/config" +) + +var ErrDataLock = errors.New("Data Lock Error") + +func New(cli client.Client, dispatch func(context.Context, *runnerv1.Task) error, cfg *config.Config) *Poller { + return &Poller{ + Client: cli, + Dispatch: dispatch, + routineGroup: newRoutineGroup(), + metric: &metric{}, + ready: make(chan struct{}, 1), + cfg: cfg, + } +} + +type Poller struct { + Client client.Client + Dispatch func(context.Context, *runnerv1.Task) error + + sync.Mutex + routineGroup *routineGroup + metric *metric + ready chan struct{} + cfg *config.Config +} + +func (p *Poller) schedule() { + p.Lock() + defer p.Unlock() + if int(p.metric.BusyWorkers()) >= p.cfg.Runner.Capacity { + return + } + + select { + case p.ready <- struct{}{}: + default: + } +} + +func (p *Poller) Wait() { + p.routineGroup.Wait() +} + +func (p *Poller) handle(ctx context.Context, l *log.Entry) { + defer func() { + if r := recover(); r != nil { + l.Errorf("handle task panic: %+v", r) + } + }() + + for { + select { + case <-ctx.Done(): + return + default: + task, err := p.pollTask(ctx) + if task == nil || err != nil { + if err != nil { + l.Errorf("can't find the task: %v", err.Error()) + } + time.Sleep(5 * time.Second) + break + } + + p.metric.IncBusyWorker() + p.routineGroup.Run(func() { + defer p.schedule() + defer p.metric.DecBusyWorker() + if err := p.dispatchTask(ctx, task); err != nil { + l.Errorf("execute task: %v", err.Error()) + } + }) + return + } + } +} + +func (p *Poller) Poll(ctx context.Context) error { + l := log.WithField("func", "Poll") + + for { + // check worker number + p.schedule() + + select { + // wait worker ready + case <-p.ready: + case <-ctx.Done(): + return nil + } + p.handle(ctx, l) + } +} + +func (p *Poller) pollTask(ctx context.Context) (*runnerv1.Task, error) { + l := log.WithField("func", "pollTask") + l.Info("poller: request stage from remote server") + + reqCtx, cancel := context.WithTimeout(ctx, 5*time.Second) + defer cancel() + + // request a new build stage for execution from the central + // build server. + resp, err := p.Client.FetchTask(reqCtx, connect.NewRequest(&runnerv1.FetchTaskRequest{})) + if err == context.Canceled || err == context.DeadlineExceeded { + l.WithError(err).Trace("poller: no stage returned") + return nil, nil + } + + if err != nil && err == ErrDataLock { + l.WithError(err).Info("task accepted by another runner") + return nil, nil + } + + if err != nil { + l.WithError(err).Error("cannot accept task") + return nil, err + } + + // exit if a nil or empty stage is returned from the system + // and allow the runner to retry. + if resp.Msg.Task == nil || resp.Msg.Task.Id == 0 { + return nil, nil + } + + return resp.Msg.Task, nil +} + +func (p *Poller) dispatchTask(ctx context.Context, task *runnerv1.Task) error { + l := log.WithField("func", "dispatchTask") + defer func() { + e := recover() + if e != nil { + l.Errorf("panic error: %v", e) + } + }() + + runCtx, cancel := context.WithTimeout(ctx, p.cfg.Runner.Timeout) + defer cancel() + + return p.Dispatch(runCtx, task) +} diff --git a/poller/thread.go b/poller/thread.go new file mode 100644 index 0000000..fe29a4a --- /dev/null +++ b/poller/thread.go @@ -0,0 +1,24 @@ +package poller + +import "sync" + +type routineGroup struct { + waitGroup sync.WaitGroup +} + +func newRoutineGroup() *routineGroup { + return new(routineGroup) +} + +func (g *routineGroup) Run(fn func()) { + g.waitGroup.Add(1) + + go func() { + defer g.waitGroup.Done() + fn() + }() +} + +func (g *routineGroup) Wait() { + g.waitGroup.Wait() +} diff --git a/renovate.json b/renovate.json deleted file mode 100644 index 31da118..0000000 --- a/renovate.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "$schema": "https://docs.renovatebot.com/renovate-schema.json", - "extends": ["local>forgejo/renovate-config"], - "packageRules": [ - { - "description": "Disable nektos/act, it's replaced", - "matchDepNames": ["github.com/nektos/act"], - "enabled": false - } - ] -} diff --git a/runtime/label.go b/runtime/label.go new file mode 100644 index 0000000..c7aa001 --- /dev/null +++ b/runtime/label.go @@ -0,0 +1,26 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package runtime + +import ( + "fmt" + "strings" +) + +func ParseLabel(str string) (label, schema, arg string, err error) { + splits := strings.SplitN(str, ":", 3) + label = splits[0] + schema = "host" + arg = "" + if len(splits) >= 2 { + schema = splits[1] + } + if len(splits) >= 3 { + arg = splits[2] + } + if schema != "host" && schema != "docker" { + return "", "", "", fmt.Errorf("unsupported schema: %s", schema) + } + return +} diff --git a/runtime/label_test.go b/runtime/label_test.go new file mode 100644 index 0000000..f17c372 --- /dev/null +++ b/runtime/label_test.go @@ -0,0 +1,63 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package runtime + +import "testing" + +func TestParseLabel(t *testing.T) { + tests := []struct { + args string + wantLabel string + wantSchema string + wantArg string + wantErr bool + }{ + { + args: "ubuntu:docker://node:18", + wantLabel: "ubuntu", + wantSchema: "docker", + wantArg: "//node:18", + wantErr: false, + }, + { + args: "ubuntu:host", + wantLabel: "ubuntu", + wantSchema: "host", + wantArg: "", + wantErr: false, + }, + { + args: "ubuntu", + wantLabel: "ubuntu", + wantSchema: "host", + wantArg: "", + wantErr: false, + }, + { + args: "ubuntu:vm:ubuntu-18.04", + wantLabel: "", + wantSchema: "", + wantArg: "", + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.args, func(t *testing.T) { + gotLabel, gotSchema, gotArg, err := ParseLabel(tt.args) + if (err != nil) != tt.wantErr { + t.Errorf("parseLabel() error = %v, wantErr %v", err, tt.wantErr) + return + } + if gotLabel != tt.wantLabel { + t.Errorf("parseLabel() gotLabel = %v, want %v", gotLabel, tt.wantLabel) + } + if gotSchema != tt.wantSchema { + t.Errorf("parseLabel() gotSchema = %v, want %v", gotSchema, tt.wantSchema) + } + if gotArg != tt.wantArg { + t.Errorf("parseLabel() gotArg = %v, want %v", gotArg, tt.wantArg) + } + }) + } +} diff --git a/internal/pkg/report/reporter.go b/runtime/reporter.go similarity index 54% rename from internal/pkg/report/reporter.go rename to runtime/reporter.go index cee5062..51d5e59 100644 --- a/internal/pkg/report/reporter.go +++ b/runtime/reporter.go @@ -1,24 +1,20 @@ -// Copyright 2022 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package report +package runtime import ( "context" "fmt" - "regexp" "strings" "sync" "time" runnerv1 "code.gitea.io/actions-proto-go/runner/v1" - "connectrpc.com/connect" + "codeberg.org/forgejo/runner/client" + retry "github.com/avast/retry-go/v4" + "github.com/bufbuild/connect-go" log "github.com/sirupsen/logrus" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/timestamppb" - - "gitea.com/gitea/act_runner/internal/pkg/client" ) type Reporter struct { @@ -29,54 +25,36 @@ type Reporter struct { client client.Client clientM sync.Mutex - logOffset int - logRows []*runnerv1.LogRow - logReplacer *strings.Replacer - oldnew []string - reportInterval time.Duration - - state *runnerv1.TaskState - stateMu sync.RWMutex - outputs sync.Map - - debugOutputEnabled bool - stopCommandEndToken string + logOffset int + logRows []*runnerv1.LogRow + logReplacer *strings.Replacer + state *runnerv1.TaskState + stateM sync.RWMutex } -func NewReporter(ctx context.Context, cancel context.CancelFunc, client client.Client, task *runnerv1.Task, reportInterval time.Duration) *Reporter { +func NewReporter(ctx context.Context, cancel context.CancelFunc, client client.Client, task *runnerv1.Task) *Reporter { var oldnew []string if v := task.Context.Fields["token"].GetStringValue(); v != "" { oldnew = append(oldnew, v, "***") } - if v := task.Context.Fields["gitea_runtime_token"].GetStringValue(); v != "" { - oldnew = append(oldnew, v, "***") - } for _, v := range task.Secrets { oldnew = append(oldnew, v, "***") } - rv := &Reporter{ - ctx: ctx, - cancel: cancel, - client: client, - oldnew: oldnew, - reportInterval: reportInterval, - logReplacer: strings.NewReplacer(oldnew...), + return &Reporter{ + ctx: ctx, + cancel: cancel, + client: client, + logReplacer: strings.NewReplacer(oldnew...), state: &runnerv1.TaskState{ Id: task.Id, }, } - - if task.Secrets["ACTIONS_STEP_DEBUG"] == "true" { - rv.debugOutputEnabled = true - } - - return rv } func (r *Reporter) ResetSteps(l int) { - r.stateMu.Lock() - defer r.stateMu.Unlock() + r.stateM.Lock() + defer r.stateM.Unlock() for i := 0; i < l; i++ { r.state.Steps = append(r.state.Steps, &runnerv1.StepState{ Id: int64(i), @@ -88,16 +66,9 @@ func (r *Reporter) Levels() []log.Level { return log.AllLevels } -func appendIfNotNil[T any](s []*T, v *T) []*T { - if v != nil { - return append(s, v) - } - return s -} - func (r *Reporter) Fire(entry *log.Entry) error { - r.stateMu.Lock() - defer r.stateMu.Unlock() + r.stateM.Lock() + defer r.stateM.Unlock() log.WithFields(entry.Data).Trace(entry.Message) @@ -116,15 +87,12 @@ func (r *Reporter) Fire(entry *log.Entry) error { for _, s := range r.state.Steps { if s.Result == runnerv1.Result_RESULT_UNSPECIFIED { s.Result = runnerv1.Result_RESULT_CANCELLED - if jobResult == runnerv1.Result_RESULT_SKIPPED { - s.Result = runnerv1.Result_RESULT_SKIPPED - } } } } } if !r.duringSteps() { - r.logRows = appendIfNotNil(r.logRows, r.parseLogRow(entry)) + r.logRows = append(r.logRows, r.parseLogRow(entry)) } return nil } @@ -137,7 +105,7 @@ func (r *Reporter) Fire(entry *log.Entry) error { } if step == nil { if !r.duringSteps() { - r.logRows = appendIfNotNil(r.logRows, r.parseLogRow(entry)) + r.logRows = append(r.logRows, r.parseLogRow(entry)) } return nil } @@ -147,16 +115,14 @@ func (r *Reporter) Fire(entry *log.Entry) error { } if v, ok := entry.Data["raw_output"]; ok { if rawOutput, ok := v.(bool); ok && rawOutput { - if row := r.parseLogRow(entry); row != nil { - if step.LogLength == 0 { - step.LogIndex = int64(r.logOffset + len(r.logRows)) - } - step.LogLength++ - r.logRows = append(r.logRows, row) + if step.LogLength == 0 { + step.LogIndex = int64(r.logOffset + len(r.logRows)) } + step.LogLength++ + r.logRows = append(r.logRows, r.parseLogRow(entry)) } } else if !r.duringSteps() { - r.logRows = appendIfNotNil(r.logRows, r.parseLogRow(entry)) + r.logRows = append(r.logRows, r.parseLogRow(entry)) } if v, ok := entry.Data["stepResult"]; ok { if stepResult, ok := r.parseResult(v); ok { @@ -182,17 +148,13 @@ func (r *Reporter) RunDaemon() { _ = r.ReportLog(false) _ = r.ReportState() - time.AfterFunc(r.reportInterval, r.RunDaemon) + time.AfterFunc(time.Second, r.RunDaemon) } func (r *Reporter) Logf(format string, a ...interface{}) { - r.stateMu.Lock() - defer r.stateMu.Unlock() + r.stateM.Lock() + defer r.stateM.Unlock() - r.logf(format, a...) -} - -func (r *Reporter) logf(format string, a ...interface{}) { if !r.duringSteps() { r.logRows = append(r.logRows, &runnerv1.LogRow{ Time: timestamppb.Now(), @@ -201,30 +163,10 @@ func (r *Reporter) logf(format string, a ...interface{}) { } } -func (r *Reporter) SetOutputs(outputs map[string]string) { - r.stateMu.Lock() - defer r.stateMu.Unlock() - - for k, v := range outputs { - if len(k) > 255 { - r.logf("ignore output because the key is too long: %q", k) - continue - } - if l := len(v); l > 1024*1024 { - log.Println("ignore output because the value is too long:", k, l) - r.logf("ignore output because the value %q is too long: %d", k, l) - } - if _, ok := r.outputs.Load(k); ok { - continue - } - r.outputs.Store(k, v) - } -} - func (r *Reporter) Close(lastWords string) error { r.closed = true - r.stateMu.Lock() + r.stateM.Lock() if r.state.Result == runnerv1.Result_RESULT_UNSPECIFIED { if lastWords == "" { lastWords = "Early termination" @@ -234,19 +176,18 @@ func (r *Reporter) Close(lastWords string) error { v.Result = runnerv1.Result_RESULT_CANCELLED } } - r.state.Result = runnerv1.Result_RESULT_FAILURE r.logRows = append(r.logRows, &runnerv1.LogRow{ Time: timestamppb.Now(), Content: lastWords, }) - r.state.StoppedAt = timestamppb.Now() + return nil } else if lastWords != "" { r.logRows = append(r.logRows, &runnerv1.LogRow{ Time: timestamppb.Now(), Content: lastWords, }) } - r.stateMu.Unlock() + r.stateM.Unlock() return retry.Do(func() error { if err := r.ReportLog(true); err != nil { @@ -260,9 +201,9 @@ func (r *Reporter) ReportLog(noMore bool) error { r.clientM.Lock() defer r.clientM.Unlock() - r.stateMu.RLock() + r.stateM.RLock() rows := r.logRows - r.stateMu.RUnlock() + r.stateM.RUnlock() resp, err := r.client.UpdateLog(r.ctx, connect.NewRequest(&runnerv1.UpdateLogRequest{ TaskId: r.state.Id, @@ -279,10 +220,10 @@ func (r *Reporter) ReportLog(noMore bool) error { return fmt.Errorf("submitted logs are lost") } - r.stateMu.Lock() + r.stateM.Lock() r.logRows = r.logRows[ack-r.logOffset:] r.logOffset = ack - r.stateMu.Unlock() + r.stateM.Unlock() if noMore && ack < r.logOffset+len(rows) { return fmt.Errorf("not all logs are submitted") @@ -295,45 +236,21 @@ func (r *Reporter) ReportState() error { r.clientM.Lock() defer r.clientM.Unlock() - r.stateMu.RLock() + r.stateM.RLock() state := proto.Clone(r.state).(*runnerv1.TaskState) - r.stateMu.RUnlock() - - outputs := make(map[string]string) - r.outputs.Range(func(k, v interface{}) bool { - if val, ok := v.(string); ok { - outputs[k.(string)] = val - } - return true - }) + r.stateM.RUnlock() resp, err := r.client.UpdateTask(r.ctx, connect.NewRequest(&runnerv1.UpdateTaskRequest{ - State: state, - Outputs: outputs, + State: state, })) if err != nil { return err } - for _, k := range resp.Msg.SentOutputs { - r.outputs.Store(k, struct{}{}) - } - if resp.Msg.State != nil && resp.Msg.State.Result == runnerv1.Result_RESULT_CANCELLED { r.cancel() } - var noSent []string - r.outputs.Range(func(k, v interface{}) bool { - if _, ok := v.(string); ok { - noSent = append(noSent, k.(string)) - } - return true - }) - if len(noSent) > 0 { - return fmt.Errorf("there are still outputs that have not been sent: %v", noSent) - } - return nil } @@ -367,71 +284,11 @@ func (r *Reporter) parseResult(result interface{}) (runnerv1.Result, bool) { return ret, ok } -var cmdRegex = regexp.MustCompile(`^::([^ :]+)( .*)?::(.*)$`) - -func (r *Reporter) handleCommand(originalContent, command, parameters, value string) *string { - if r.stopCommandEndToken != "" && command != r.stopCommandEndToken { - return &originalContent - } - - switch command { - case "add-mask": - r.addMask(value) - return nil - case "debug": - if r.debugOutputEnabled { - return &value - } - return nil - - case "notice": - // Not implemented yet, so just return the original content. - return &originalContent - case "warning": - // Not implemented yet, so just return the original content. - return &originalContent - case "error": - // Not implemented yet, so just return the original content. - return &originalContent - case "group": - // Rewriting into ##[] syntax which the frontend understands - content := "##[group]" + value - return &content - case "endgroup": - // Ditto - content := "##[endgroup]" - return &content - case "stop-commands": - r.stopCommandEndToken = value - return nil - case r.stopCommandEndToken: - r.stopCommandEndToken = "" - return nil - } - return &originalContent -} - func (r *Reporter) parseLogRow(entry *log.Entry) *runnerv1.LogRow { content := strings.TrimRightFunc(entry.Message, func(r rune) bool { return r == '\r' || r == '\n' }) - - matches := cmdRegex.FindStringSubmatch(content) - if matches != nil { - if output := r.handleCommand(content, matches[1], matches[2], matches[3]); output != nil { - content = *output - } else { - return nil - } - } - content = r.logReplacer.Replace(content) - return &runnerv1.LogRow{ Time: timestamppb.New(entry.Time), - Content: strings.ToValidUTF8(content, "?"), + Content: content, } } - -func (r *Reporter) addMask(msg string) { - r.oldnew = append(r.oldnew, msg, "***") - r.logReplacer = strings.NewReplacer(r.oldnew...) -} diff --git a/runtime/runtime.go b/runtime/runtime.go new file mode 100644 index 0000000..068dd4f --- /dev/null +++ b/runtime/runtime.go @@ -0,0 +1,74 @@ +package runtime + +import ( + "context" + "strings" + + runnerv1 "code.gitea.io/actions-proto-go/runner/v1" + "codeberg.org/forgejo/runner/artifactcache" + "codeberg.org/forgejo/runner/client" + log "github.com/sirupsen/logrus" +) + +// Runner runs the pipeline. +type Runner struct { + Machine string + Version string + ForgeInstance string + Environ map[string]string + Client client.Client + Labels []string + Network string + CacheHandler *artifactcache.Handler +} + +// Run runs the pipeline stage. +func (s *Runner) Run(ctx context.Context, task *runnerv1.Task) error { + env := map[string]string{} + for k, v := range s.Environ { + env[k] = v + } + if s.CacheHandler != nil { + env["ACTIONS_CACHE_URL"] = s.CacheHandler.ExternalURL() + "/" + } + return NewTask(task.Id, s.Client, env, s.Network, s.platformPicker).Run(ctx, task, s.Machine, s.Version) +} + +func (s *Runner) platformPicker(labels []string) string { + platforms := make(map[string]string, len(s.Labels)) + for _, l := range s.Labels { + label, schema, arg, err := ParseLabel(l) + if err != nil { + log.Errorf("invaid label %q: %v", l, err) + continue + } + + switch schema { + case "docker": + // TODO "//" will be ignored, maybe we should use 'ubuntu-18.04:docker:node:16-buster' instead + platforms[label] = strings.TrimPrefix(arg, "//") + case "host": + platforms[label] = "-self-hosted" + default: + // It should not happen, because ParseLabel has checked it. + continue + } + } + + for _, label := range labels { + if v, ok := platforms[label]; ok { + return v + } + } + + // TODO: support multiple labels + // like: + // ["ubuntu-22.04"] => "ubuntu:22.04" + // ["with-gpu"] => "linux:with-gpu" + // ["ubuntu-22.04", "with-gpu"] => "ubuntu:22.04_with-gpu" + + // return default. + // So the runner receives a task with a label that the runner doesn't have, + // it happens when the user have edited the label of the runner in the web UI. + return "node:16-bullseye" // TODO: it may be not correct, what if the runner is used as host mode only? +} diff --git a/runtime/task.go b/runtime/task.go new file mode 100644 index 0000000..ece4ad0 --- /dev/null +++ b/runtime/task.go @@ -0,0 +1,262 @@ +package runtime + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "os" + "path/filepath" + "sync" + "time" + + runnerv1 "code.gitea.io/actions-proto-go/runner/v1" + "github.com/nektos/act/pkg/common" + "github.com/nektos/act/pkg/model" + "github.com/nektos/act/pkg/runner" + log "github.com/sirupsen/logrus" + + "codeberg.org/forgejo/runner/client" +) + +var globalTaskMap sync.Map + +type TaskInput struct { + repoDirectory string + // actor string + // workdir string + // workflowsPath string + // autodetectEvent bool + // eventPath string + // reuseContainers bool + // bindWorkdir bool + // secrets []string + envs map[string]string + // platforms []string + // dryrun bool + forcePull bool + forceRebuild bool + // noOutput bool + // envfile string + // secretfile string + insecureSecrets bool + // defaultBranch string + privileged bool + usernsMode string + containerArchitecture string + containerDaemonSocket string + // noWorkflowRecurse bool + useGitIgnore bool + containerCapAdd []string + containerCapDrop []string + // autoRemove bool + artifactServerPath string + artifactServerPort string + jsonLogger bool + // noSkipCheckout bool + // remoteName string + + EnvFile string + + containerNetworkMode string +} + +type Task struct { + BuildID int64 + Input *TaskInput + + client client.Client + log *log.Entry + platformPicker func([]string) string +} + +// NewTask creates a new task +func NewTask(buildID int64, client client.Client, runnerEnvs map[string]string, network string, picker func([]string) string) *Task { + task := &Task{ + Input: &TaskInput{ + envs: runnerEnvs, + containerNetworkMode: network, + }, + BuildID: buildID, + + client: client, + log: log.WithField("buildID", buildID), + platformPicker: picker, + } + task.Input.repoDirectory, _ = os.Getwd() + return task +} + +// getWorkflowsPath return the workflows directory, it will try .gitea first and then fallback to .github +func getWorkflowsPath(dir string) (string, error) { + p := filepath.Join(dir, ".gitea/workflows") + _, err := os.Stat(p) + if err != nil { + if !os.IsNotExist(err) { + return "", err + } + return filepath.Join(dir, ".github/workflows"), nil + } + return p, nil +} + +func getToken(task *runnerv1.Task) string { + token := task.Secrets["GITHUB_TOKEN"] + if task.Secrets["GITEA_TOKEN"] != "" { + token = task.Secrets["GITEA_TOKEN"] + } + if task.Context.Fields["token"].GetStringValue() != "" { + token = task.Context.Fields["token"].GetStringValue() + } + return token +} + +func (t *Task) Run(ctx context.Context, task *runnerv1.Task, runnerName, runnerVersion string) (lastErr error) { + ctx, cancel := context.WithCancel(ctx) + defer cancel() + _, exist := globalTaskMap.Load(task.Id) + if exist { + return fmt.Errorf("task %d already exists", task.Id) + } + + // set task ve to global map + // when task is done or canceled, it will be removed from the map + globalTaskMap.Store(task.Id, t) + defer globalTaskMap.Delete(task.Id) + + lastWords := "" + reporter := NewReporter(ctx, cancel, t.client, task) + defer func() { + // set the job to failed on an error return value + if lastErr != nil { + reporter.Fire(&log.Entry{ + Data: log.Fields{ + "jobResult": "failure", + }, + Time: time.Now(), + }) + } + _ = reporter.Close(lastWords) + }() + reporter.RunDaemon() + + reporter.Logf("%s(version:%s) received task %v of job %v, be triggered by event: %s", runnerName, runnerVersion, task.Id, task.Context.Fields["job"].GetStringValue(), task.Context.Fields["event_name"].GetStringValue()) + + workflowsPath, err := getWorkflowsPath(t.Input.repoDirectory) + if err != nil { + lastWords = err.Error() + return err + } + t.log.Debugf("workflows path: %s", workflowsPath) + + workflow, err := model.ReadWorkflow(bytes.NewReader(task.WorkflowPayload)) + if err != nil { + lastWords = err.Error() + return err + } + + jobIDs := workflow.GetJobIDs() + if len(jobIDs) != 1 { + err := fmt.Errorf("multiple jobs found: %v", jobIDs) + lastWords = err.Error() + return err + } + jobID := jobIDs[0] + plan, err := model.CombineWorkflowPlanner(workflow).PlanJob(jobID) + if err != nil { + lastWords = err.Error() + return err + } + job := workflow.GetJob(jobID) + reporter.ResetSteps(len(job.Steps)) + + log.Infof("plan: %+v", plan.Stages[0].Runs) + + token := getToken(task) + dataContext := task.Context.Fields + + log.Infof("task %v repo is %v %v %v", task.Id, dataContext["repository"].GetStringValue(), + dataContext["gitea_default_actions_url"].GetStringValue(), + t.client.Address()) + + preset := &model.GithubContext{ + Event: dataContext["event"].GetStructValue().AsMap(), + RunID: dataContext["run_id"].GetStringValue(), + RunNumber: dataContext["run_number"].GetStringValue(), + Actor: dataContext["actor"].GetStringValue(), + Repository: dataContext["repository"].GetStringValue(), + EventName: dataContext["event_name"].GetStringValue(), + Sha: dataContext["sha"].GetStringValue(), + Ref: dataContext["ref"].GetStringValue(), + RefName: dataContext["ref_name"].GetStringValue(), + RefType: dataContext["ref_type"].GetStringValue(), + HeadRef: dataContext["head_ref"].GetStringValue(), + BaseRef: dataContext["base_ref"].GetStringValue(), + Token: token, + RepositoryOwner: dataContext["repository_owner"].GetStringValue(), + RetentionDays: dataContext["retention_days"].GetStringValue(), + } + eventJSON, err := json.Marshal(preset.Event) + if err != nil { + lastWords = err.Error() + return err + } + + maxLifetime := 3 * time.Hour + if deadline, ok := ctx.Deadline(); ok { + maxLifetime = time.Until(deadline) + } + + input := t.Input + config := &runner.Config{ + Workdir: "." + string(filepath.Separator), + BindWorkdir: false, + ReuseContainers: false, + ForcePull: input.forcePull, + ForceRebuild: input.forceRebuild, + LogOutput: true, + JSONLogger: input.jsonLogger, + Env: input.envs, + Secrets: task.Secrets, + InsecureSecrets: input.insecureSecrets, + Privileged: input.privileged, + UsernsMode: input.usernsMode, + ContainerArchitecture: input.containerArchitecture, + ContainerDaemonSocket: input.containerDaemonSocket, + UseGitIgnore: input.useGitIgnore, + GitHubInstance: t.client.Address(), + ContainerCapAdd: input.containerCapAdd, + ContainerCapDrop: input.containerCapDrop, + AutoRemove: true, + ArtifactServerPath: input.artifactServerPath, + ArtifactServerPort: input.artifactServerPort, + NoSkipCheckout: true, + PresetGitHubContext: preset, + EventJSON: string(eventJSON), + ContainerNamePrefix: fmt.Sprintf("GITEA-ACTIONS-TASK-%d", task.Id), + ContainerMaxLifetime: maxLifetime, + ContainerNetworkMode: input.containerNetworkMode, + DefaultActionInstance: dataContext["gitea_default_actions_url"].GetStringValue(), + PlatformPicker: t.platformPicker, + } + r, err := runner.New(config) + if err != nil { + lastWords = err.Error() + return err + } + + executor := r.NewPlanExecutor(plan) + + t.log.Infof("workflow prepared") + reporter.Logf("workflow prepared") + + // add logger recorders + ctx = common.WithLoggerHook(ctx, reporter) + + if err := executor(ctx); err != nil { + lastWords = err.Error() + return err + } + + return nil +} diff --git a/scripts/rootless.sh b/scripts/rootless.sh deleted file mode 100755 index 310a03b..0000000 --- a/scripts/rootless.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env bash - -# wait for docker daemon -while ! nc -z localhost 2376 &1 | tee /tmp/reg.log - - cat /tmp/reg.log | grep 'Runner registered successfully' > /dev/null - if [[ $? -eq 0 ]]; then - echo "SUCCESS" - success=1 - else - echo "Waiting to retry ..." - sleep 5 - fi - done -fi -# Prevent reading the token from the forgejo-runner process -unset GITEA_RUNNER_REGISTRATION_TOKEN - -forgejo-runner daemon ${CONFIG_ARG} diff --git a/scripts/supervisord.conf b/scripts/supervisord.conf deleted file mode 100644 index 8c45f5b..0000000 --- a/scripts/supervisord.conf +++ /dev/null @@ -1,13 +0,0 @@ -[supervisord] -nodaemon=true -logfile=/dev/null -logfile_maxbytes=0 - -[program:dockerd] -command=/usr/local/bin/dockerd-entrypoint.sh - -[program:act_runner] -stdout_logfile=/dev/fd/1 -stdout_logfile_maxbytes=0 -redirect_stderr=true -command=/opt/act/rootless.sh diff --git a/scripts/systemd.md b/scripts/systemd.md deleted file mode 100644 index 089dd61..0000000 --- a/scripts/systemd.md +++ /dev/null @@ -1,67 +0,0 @@ -# Forgejo Runner with systemd User Services - -It is possible to use systemd's user services together with -[podman](https://podman.io/) to run `forgejo-runner` using a normal user -account without any privileges and automatically start on boot. - -This was last tested on Fedora 39 on 2024-02-19, but should work elsewhere as -well. - -Place the `forgejo-runner` binary in `/usr/local/bin/forgejo-runner` and make -sure it can be executed (`chmod +x /usr/local/bin/forgejo-runner`). - -Install and enable `podman` as a user service: - -```bash -$ sudo dnf -y install podman -``` - -You *may* need to reboot your system after installing `podman` as it -modifies some system configuration(s) that may need to be activated. Without -rebooting the system my runner errored out when trying to set firewall rules, a -reboot fixed it. - -Enable `podman` as a user service: - -``` -$ systemctl --user start podman.socket -$ systemctl --user enable podman.socket -``` - -Make sure processes remain after your user account logs out: - -```bash -$ loginctl enable-linger -``` - -Create the file `/etc/systemd/user/forgejo-runner.service` with the following -content: - -``` -[Unit] -Description=Forgejo Runner - -[Service] -Type=simple -ExecStart=/usr/local/bin/forgejo-runner daemon -Restart=on-failure - -[Install] -WantedBy=default.target -``` - -Now activate it as a user service: - -```bash -$ systemctl --user daemon-reload -$ systemctl --user start forgejo-runner -$ systemctl --user enable forgejo-runner -``` - -To see/follow the log of `forgejo-runner`: - -```bash -$ journalctl -f -t forgejo-runner -``` - -If you reboot your system, all should come back automatically.