From fd6da745157539bd0145c4fdb6db34ed705fa87b Mon Sep 17 00:00:00 2001 From: Dean Sheather Date: Tue, 18 Apr 2023 22:15:04 +0000 Subject: [PATCH 1/2] delete everything --- .github/workflows/ci.yaml | 201 --- .gitignore | 4 - .golangci.yaml | 260 ---- .vscode/settings.json | 4 - LICENSE | 661 --------- Makefile | 40 - README.md | 36 - background/doc.go | 3 - background/process.go | 267 ---- buildlog/coder.go | 138 -- buildlog/coder_test.go | 117 -- buildlog/doc.go | 3 - buildlog/json.go | 67 - buildlog/json_test.go | 96 -- buildlog/logger.go | 79 - cli/cliflag/cliflag.go | 183 --- cli/cliflag/cliflag_test.go | 277 ---- cli/clitest/cli.go | 86 -- cli/clitest/fake.go | 62 - cli/clitest/fs.go | 24 - cli/clitest/net.go | 28 - cli/doc.go | 2 - cli/docker.go | 791 ---------- cli/docker_test.go | 580 -------- cli/root.go | 22 - cmd/envbox/main.go | 20 - deploy/Dockerfile | 71 - deploy/files/etc/apt/preferences.d/docker | 19 - .../files/etc/apt/sources.list.d/docker.list | 1 - deploy/files/etc/docker/daemon.json | 7 - deploy/files/usr/share/keyrings/docker.gpg | Bin 2760 -> 0 bytes deploy/update-keys.sh | 22 - dockerutil/client.go | 65 - dockerutil/container.go | 175 --- dockerutil/daemon.go | 56 - dockerutil/doc.go | 3 - dockerutil/dockerfake/client.go | 281 ---- dockerutil/dockerfake/doc.go | 3 - dockerutil/exec.go | 94 -- dockerutil/image.go | 255 ---- dockerutil/network.go | 45 - go.mod | 187 --- go.sum | 1273 ----------------- integration/doc.go | 3 - integration/docker_test.go | 258 ---- integration/integrationtest/docker.go | 296 ---- integration/main_test.go | 36 - scripts/check_unstaged.sh | 45 - scripts/rules.go | 38 - slogkubeterminate/doc.go | 5 - slogkubeterminate/slogger.go | 33 - slogkubeterminate/slogger_test.go | 60 - sysboxutil/manager.go | 45 - template.tf | 309 ---- xio/limitwriter.go | 120 -- xio/limitwriter_internal_test.go | 56 - xio/syncwriter.go | 18 - xunix/device.go | 128 -- xunix/doc.go | 3 - xunix/env.go | 42 - xunix/env_test.go | 39 - xunix/error.go | 11 - xunix/exec.go | 175 --- xunix/fs.go | 49 - xunix/gpu.go | 212 --- xunix/gpu_test.go | 114 -- xunix/mount.go | 34 - xunix/net.go | 15 - xunix/proc.go | 23 - xunix/proc_test.go | 38 - xunix/sys.go | 48 - xunix/sys_test.go | 45 - xunix/user.go | 60 - xunix/xunixfake/exec.go | 68 - xunix/xunixfake/fs.go | 53 - 75 files changed, 9087 deletions(-) delete mode 100644 .github/workflows/ci.yaml delete mode 100644 .gitignore delete mode 100644 .golangci.yaml delete mode 100644 .vscode/settings.json delete mode 100644 LICENSE delete mode 100644 Makefile delete mode 100644 README.md delete mode 100644 background/doc.go delete mode 100644 background/process.go delete mode 100644 buildlog/coder.go delete mode 100644 buildlog/coder_test.go delete mode 100644 buildlog/doc.go delete mode 100644 buildlog/json.go delete mode 100644 buildlog/json_test.go delete mode 100644 buildlog/logger.go delete mode 100644 cli/cliflag/cliflag.go delete mode 100644 cli/cliflag/cliflag_test.go delete mode 100644 cli/clitest/cli.go delete mode 100644 cli/clitest/fake.go delete mode 100644 cli/clitest/fs.go delete mode 100644 cli/clitest/net.go delete mode 100644 cli/doc.go delete mode 100644 cli/docker.go delete mode 100644 cli/docker_test.go delete mode 100644 cli/root.go delete mode 100644 cmd/envbox/main.go delete mode 100644 deploy/Dockerfile delete mode 100644 deploy/files/etc/apt/preferences.d/docker delete mode 100644 deploy/files/etc/apt/sources.list.d/docker.list delete mode 100644 deploy/files/etc/docker/daemon.json delete mode 100644 deploy/files/usr/share/keyrings/docker.gpg delete mode 100644 deploy/update-keys.sh delete mode 100644 dockerutil/client.go delete mode 100644 dockerutil/container.go delete mode 100644 dockerutil/daemon.go delete mode 100644 dockerutil/doc.go delete mode 100644 dockerutil/dockerfake/client.go delete mode 100644 dockerutil/dockerfake/doc.go delete mode 100644 dockerutil/exec.go delete mode 100644 dockerutil/image.go delete mode 100644 dockerutil/network.go delete mode 100644 go.mod delete mode 100644 go.sum delete mode 100644 integration/doc.go delete mode 100644 integration/docker_test.go delete mode 100644 integration/integrationtest/docker.go delete mode 100644 integration/main_test.go delete mode 100755 scripts/check_unstaged.sh delete mode 100644 scripts/rules.go delete mode 100644 slogkubeterminate/doc.go delete mode 100644 slogkubeterminate/slogger.go delete mode 100644 slogkubeterminate/slogger_test.go delete mode 100644 sysboxutil/manager.go delete mode 100644 template.tf delete mode 100644 xio/limitwriter.go delete mode 100644 xio/limitwriter_internal_test.go delete mode 100644 xio/syncwriter.go delete mode 100644 xunix/device.go delete mode 100644 xunix/doc.go delete mode 100644 xunix/env.go delete mode 100644 xunix/env_test.go delete mode 100644 xunix/error.go delete mode 100644 xunix/exec.go delete mode 100644 xunix/fs.go delete mode 100644 xunix/gpu.go delete mode 100644 xunix/gpu_test.go delete mode 100644 xunix/mount.go delete mode 100644 xunix/net.go delete mode 100644 xunix/proc.go delete mode 100644 xunix/proc_test.go delete mode 100644 xunix/sys.go delete mode 100644 xunix/sys_test.go delete mode 100644 xunix/user.go delete mode 100644 xunix/xunixfake/exec.go delete mode 100644 xunix/xunixfake/fs.go diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml deleted file mode 100644 index 856d3b5..0000000 --- a/.github/workflows/ci.yaml +++ /dev/null @@ -1,201 +0,0 @@ -name: ci - -on: - push: - branches: - - main - - pull_request: - - workflow_dispatch: - -permissions: - actions: none - checks: none - contents: read - deployments: none - issues: none - packages: write - pull-requests: none - repository-projects: none - security-events: none - statuses: none - -# Cancel in-progress runs for pull requests when developers push -# additional changes -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: ${{ github.event_name == 'pull_request' }} - -jobs: - lint: - runs-on: ${{ github.repository_owner == 'coder' && 'ubuntu-latest-8-cores' || 'ubuntu-latest' }} - steps: - - name: Checkout - uses: actions/checkout@v3 - - # Install Go! - - uses: actions/setup-go@v3 - with: - go-version: "~1.20" - - # Check for Go linting errors! - - name: Lint Go - uses: golangci/golangci-lint-action@v3.3.1 - with: - version: v1.51.0 - args: "--out-${NO_FUTURE}format colored-line-number" - - - name: Lint shell scripts - uses: ludeeus/action-shellcheck@2.0.0 - env: - SHELLCHECK_OPTS: --external-sources - with: - ignore: node_modules - - - uses: hashicorp/setup-terraform@v2 - with: - terraform_version: 1.1.9 - terraform_wrapper: false - - - name: Terraform init - run: terraform init - - - name: Terraform validate - run: terraform validate - - fmt: - runs-on: ubuntu-latest - timeout-minutes: 5 - steps: - - name: Checkout - uses: actions/checkout@v3 - with: - fetch-depth: 0 - submodules: true - - - uses: hashicorp/setup-terraform@v2 - with: - terraform_version: 1.1.9 - terraform_wrapper: false - - - name: Install markdownfmt - run: go install github.com/Kunde21/markdownfmt/v3/cmd/markdownfmt@latest - - - name: make fmt - run: | - export PATH=${PATH}:$(go env GOPATH)/bin - make --output-sync -j -B fmt - - - name: Check for unstaged files - run: ./scripts/check_unstaged.sh - - unit-tests: - runs-on: ubuntu-latest - timeout-minutes: 20 - steps: - - uses: actions/checkout@v3 - - - uses: actions/setup-go@v3 - with: - go-version: "~1.20" - - # Sadly the new "set output" syntax (of writing env vars to - # $GITHUB_OUTPUT) does not work on both powershell and bash so we use the - # deprecated syntax here. - - name: Echo Go Cache Paths - id: go-cache-paths - run: | - echo "::set-output name=GOCACHE::$(go env GOCACHE)" - echo "::set-output name=GOMODCACHE::$(go env GOMODCACHE)" - - - name: Go Build Cache - uses: actions/cache@v3 - with: - path: ${{ steps.go-cache-paths.outputs.GOCACHE }} - key: ${{ runner.os }}-go-build-${{ hashFiles('**/go.**', '**.go') }} - - - name: Go Mod Cache - uses: actions/cache@v3 - with: - path: ${{ steps.go-cache-paths.outputs.GOMODCACHE }} - key: ${{ runner.os }}-go-mod-${{ hashFiles('**/go.sum') }} - - - name: Run unit tests - id: test - shell: bash - run: go test ./... - - integration-tests: - runs-on: ubuntu-20.04 - timeout-minutes: 20 - steps: - - name: Install dependencies - run: sudo apt update && sudo apt install -y gcc - - uses: actions/checkout@v3 - - - uses: actions/setup-go@v3 - with: - go-version: "~1.20" - - # Sadly the new "set output" syntax (of writing env vars to - # $GITHUB_OUTPUT) does not work on both powershell and bash so we use the - # deprecated syntax here. - - name: Echo Go Cache Paths - id: go-cache-paths - run: | - echo "::set-output name=GOCACHE::$(go env GOCACHE)" - echo "::set-output name=GOMODCACHE::$(go env GOMODCACHE)" - - - name: Go Build Cache - uses: actions/cache@v3 - with: - path: ${{ steps.go-cache-paths.outputs.GOCACHE }} - key: ${{ runner.os }}-go-build-${{ hashFiles('**/go.**', '**.go') }} - - - name: Go Mod Cache - uses: actions/cache@v3 - with: - path: ${{ steps.go-cache-paths.outputs.GOMODCACHE }} - key: ${{ runner.os }}-go-mod-${{ hashFiles('**/go.sum') }} - - - name: Run integration tests - id: test - shell: bash - run: go test -tags=integration ./... - - build: - runs-on: ubuntu-20.04 - if: github.ref != 'refs/heads/main' - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-go@v3 - with: - go-version: "~1.20" - - name: build image - run: make -j build/image/envbox - publish: - runs-on: ubuntu-20.04 - if: github.ref == 'refs/heads/main' - steps: - - name: Docker Login - uses: docker/login-action@v2 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - uses: actions/checkout@v3 - - - uses: actions/setup-go@v3 - with: - go-version: "~1.20" - - - name: build image - run: make -j build/image/envbox - - - name: tag image - run: docker tag envbox ghcr.io/coder/envbox:latest - - - name: push image - run: docker push ghcr.io/coder/envbox:latest diff --git a/.gitignore b/.gitignore deleted file mode 100644 index dabe111..0000000 --- a/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -build -cmd/envbox/envbox -.terraform/ -*.lock.hcl diff --git a/.golangci.yaml b/.golangci.yaml deleted file mode 100644 index 424f575..0000000 --- a/.golangci.yaml +++ /dev/null @@ -1,260 +0,0 @@ -# See https://golangci-lint.run/usage/configuration/ -# Over time we should try tightening some of these. - -linters-settings: - gocognit: - min-complexity: 46 # Min code complexity (def 30). - - goconst: - min-len: 4 # Min length of string consts (def 3). - min-occurrences: 3 # Min number of const occurrences (def 3). - - gocritic: - enabled-checks: - # - appendAssign - # - appendCombine - - argOrder - # - assignOp - # - badCall - - badCond - - badLock - - badRegexp - - boolExprSimplify - # - builtinShadow - - builtinShadowDecl - - captLocal - - caseOrder - - codegenComment - # - commentedOutCode - - commentedOutImport - - commentFormatting - - defaultCaseOrder - - deferUnlambda - # - deprecatedComment - # - docStub - - dupArg - - dupBranchBody - - dupCase - - dupImport - - dupSubExpr - # - elseif - - emptyFallthrough - # - emptyStringTest - # - equalFold - # - evalOrder - # - exitAfterDefer - # - exposedSyncMutex - # - filepathJoin - - flagDeref - - flagName - - hexLiteral - # - httpNoBody - # - hugeParam - # - ifElseChain - # - importShadow - - indexAlloc - - initClause - - ioutilDeprecated - - mapKey - - methodExprCall - # - nestingReduce - - newDeref - - nilValReturn - # - octalLiteral - - offBy1 - # - paramTypeCombine - # - preferStringWriter - # - preferWriteByte - # - ptrToRefParam - # - rangeExprCopy - # - rangeValCopy - - regexpMust - - regexpPattern - # - regexpSimplify - - ruleguard - - singleCaseSwitch - - sloppyLen - # - sloppyReassign - - sloppyTypeAssert - - sortSlice - - sprintfQuotedString - - sqlQuery - # - stringConcatSimplify - # - stringXbytes - # - suspiciousSorting - - switchTrue - - truncateCmp - - typeAssertChain - # - typeDefFirst - - typeSwitchVar - # - typeUnparen - - underef - # - unlabelStmt - # - unlambda - # - unnamedResult - # - unnecessaryBlock - # - unnecessaryDefer - # - unslice - - valSwap - - weakCond - # - whyNoLint - # - wrapperFunc - # - yodaStyleExpr - settings: - ruleguard: - failOn: all - rules: "${configDir}/scripts/rules.go" - - staticcheck: - # https://staticcheck.io/docs/options#checks - # We disable SA1019 because it gets angry about our usage of xerrors. We - # intentionally xerrors because stack frame support didn't make it into the - # stdlib port. - checks: ["all", "-SA1019"] - - goimports: - local-prefixes: coder.com,cdr.dev,go.coder.com,github.com/cdr,github.com/coder - - gocyclo: - min-complexity: 50 - - importas: - no-unaliased: true - - misspell: - locale: US - ignore-words: - - trialer - - nestif: - min-complexity: 4 # Min complexity of if statements (def 5, goal 4) - - revive: - # see https://github.com/mgechev/revive#available-rules for details. - ignore-generated-header: true - severity: warning - rules: - - name: atomic - - name: bare-return - - name: blank-imports - - name: bool-literal-in-expr - - name: call-to-gc - - name: confusing-naming - - name: confusing-results - - name: constant-logical-expr - - name: context-as-argument - - name: context-keys-type - - name: deep-exit - - name: defer - - name: dot-imports - - name: duplicated-imports - - name: early-return - - name: empty-block - - name: empty-lines - - name: error-naming - - name: error-return - - name: error-strings - - name: errorf - - name: exported - - name: flag-parameter - - name: get-return - - name: identical-branches - - name: if-return - - name: import-shadowing - - name: increment-decrement - - name: indent-error-flow - # - name: modifies-parameter - - name: modifies-value-receiver - - name: package-comments - - name: range - - name: range-val-address - - name: range-val-in-closure - - name: receiver-naming - - name: redefines-builtin-id - - name: string-of-int - - name: struct-tag - - name: superfluous-else - - name: time-naming - - name: unconditional-recursion - - name: unexported-naming - - name: unexported-return - - name: unhandled-error - - name: unnecessary-stmt - - name: unreachable-code - - name: unused-parameter - - name: unused-receiver - - name: var-declaration - - name: var-naming - - name: waitgroup-by-value - -issues: - # Rules listed here: https://github.com/securego/gosec#available-rules - exclude-rules: - - path: _test\.go - linters: - # We use assertions rather than explicitly checking errors in tests - - errcheck - - fix: true - max-issues-per-linter: 0 - max-same-issues: 0 - -run: - concurrency: 4 - skip-dirs: - - node_modules - skip-files: - - scripts/rules.go - timeout: 5m - -# Over time, add more and more linters from -# https://golangci-lint.run/usage/linters/ as the code improves. -linters: - disable-all: true - enable: - - asciicheck - - bidichk - - bodyclose - - dogsled - - errcheck - - errname - - errorlint - - exportloopref - - forcetypeassert - - gocritic - - gocyclo - - goimports - - gomodguard - - gosec - - gosimple - - govet - - importas - - ineffassign - - makezero - - misspell - - nilnil - - noctx - - paralleltest - - revive - - # These don't work until the following issue is solved. - # https://github.com/golangci/golangci-lint/issues/2649 - # - rowserrcheck - # - sqlclosecheck - # - structcheck - # - wastedassign - - - staticcheck - - tenv - # In Go, it's possible for a package to test it's internal functionality - # without testing any exported functions. This is enabled to promote - # decomposing a package before testing it's internals. A function caller - # should be able to test most of the functionality from exported functions. - # - # There are edge-cases to this rule, but they should be carefully considered - # to avoid structural inconsistency. - - testpackage - - tparallel - - typecheck - - unconvert - - unused diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 6e380ce..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "go.lintTool": "golangci-lint", - "go.lintFlags": ["--fast"] -} diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 0ad25db..0000000 --- a/LICENSE +++ /dev/null @@ -1,661 +0,0 @@ - GNU AFFERO GENERAL PUBLIC LICENSE - Version 3, 19 November 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU Affero General Public License is a free, copyleft license for -software and other kinds of works, specifically designed to ensure -cooperation with the community in the case of network server software. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -our General Public Licenses are intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - Developers that use our General Public Licenses protect your rights -with two steps: (1) assert copyright on the software, and (2) offer -you this License which gives you legal permission to copy, distribute -and/or modify the software. - - A secondary benefit of defending all users' freedom is that -improvements made in alternate versions of the program, if they -receive widespread use, become available for other developers to -incorporate. Many developers of free software are heartened and -encouraged by the resulting cooperation. However, in the case of -software used on network servers, this result may fail to come about. -The GNU General Public License permits making a modified version and -letting the public access it on a server without ever releasing its -source code to the public. - - The GNU Affero General Public License is designed specifically to -ensure that, in such cases, the modified source code becomes available -to the community. It requires the operator of a network server to -provide the source code of the modified version running there to the -users of that server. Therefore, public use of a modified version, on -a publicly accessible server, gives the public access to the source -code of the modified version. - - An older license, called the Affero General Public License and -published by Affero, was designed to accomplish similar goals. This is -a different license, not a version of the Affero GPL, but Affero has -released a new version of the Affero GPL which permits relicensing under -this license. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU Affero General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Remote Network Interaction; Use with the GNU General Public License. - - Notwithstanding any other provision of this License, if you modify the -Program, your modified version must prominently offer all users -interacting with it remotely through a computer network (if your version -supports such interaction) an opportunity to receive the Corresponding -Source of your version by providing access to the Corresponding Source -from a network server at no charge, through some standard or customary -means of facilitating copying of software. This Corresponding Source -shall include the Corresponding Source for any work covered by version 3 -of the GNU General Public License that is incorporated pursuant to the -following paragraph. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the work with which it is combined will remain governed by version -3 of the GNU General Public License. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU Affero General Public License from time to time. Such new versions -will be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU Affero General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU Affero General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU Affero General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If your software can interact with users remotely through a computer -network, you should also make sure that it provides a way for users to -get its source. For example, if your program is a web application, its -interface could display a "Source" link that leads users to an archive -of the code. There are many ways you could offer source, and different -solutions will be better for different programs; see section 13 for the -specific requirements. - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU AGPL, see -. diff --git a/Makefile b/Makefile deleted file mode 100644 index 61084c8..0000000 --- a/Makefile +++ /dev/null @@ -1,40 +0,0 @@ -PROJECT_ROOT := $(shell git rev-parse --show-toplevel) -GO_FILES := $(shell git ls-files '*.go' '*.sum') -IMAGE_FILES := $(shell find deploy) - -.PHONY: clean -clean: - rm -rf build - -build/envbox: $(GO_FILES) - go build -o build/envbox ./cmd/envbox - -.PHONY: build/image/envbox -build/image/envbox: build/image/envbox/.ctx - -build/image/envbox/.ctx: build/envbox $(IMAGE_FILES) - mkdir -p $(@D) - cp -r build/envbox deploy/. $(@D) - docker build -t envbox $(@D) - touch $@ - -.PHONY: fmt -fmt: fmt/go fmt/tf fmt/md - -.PHONY: fmt/go -fmt/go: - # VS Code users should check out - # https://github.com/mvdan/gofumpt#visual-studio-code - go run mvdan.cc/gofumpt@v0.4.0 -w -l . - -.PHONY: fmt/tf -fmt/tf: - # VS Code users should check out - # https://github.com/mvdan/gofumpt#visual-studio-code - terraform fmt ./template.tf - -.PHONY: fmt/md -fmt/tf: - # VS Code users should check out - # https://github.com/mvdan/gofumpt#visual-studio-code - markdownfmt -w ./README.md diff --git a/README.md b/README.md deleted file mode 100644 index 6304327..0000000 --- a/README.md +++ /dev/null @@ -1,36 +0,0 @@ -# envbox - -## Introduction - -`envbox` is an image that enables creating non-privileged containers capable of running system-level software (e.g. `dockerd`, `systemd`, etc) in Kubernetes. - -It mainly acts as a wrapper for the excellent [sysbox runtime](https://github.com/nestybox/sysbox/) developed by [Nestybox](https://www.nestybox.com/). For more details on the security of `sysbox` containers see sysbox's [official documentation](https://github.com/nestybox/sysbox/blob/master/docs/user-guide/security.md). - -## Envbox Configuration - -The environment variables can be used to configure various aspects of the inner and outer container. - -| env | usage | required | -|----------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------| -| `CODER_INNER_IMAGE` | The image to use for the inner container. | True | -| `CODER_INNER_USERNAME` | The username to use for the inner container. | True | -| `CODER_AGENT_TOKEN` | The [Coder Agent](https://coder.com/docs/v2/latest/about/architecture#agents) token to pass to the inner container. | True | -| `CODER_INNER_ENVS` | The environment variables to pass to the inner container. A wildcard can be used to match a prefix. Ex: `CODER_INNER_ENVS=KUBERNETES_*,MY_ENV,MY_OTHER_ENV` | false | -| `CODER_INNER_HOSTNAME` | The hostname to use for the inner container. | false | -| `CODER_IMAGE_PULL_SECRET` | The docker credentials to use when pulling the inner container. The recommended way to do this is to create an [Image Pull Secret](https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/#registry-secret-existing-credentials) and then reference the secret using an [environment variable](https://kubernetes.io/docs/tasks/inject-data-application/distribute-credentials-secure/#define-container-environment-variables-using-secret-data). | false | -| `CODER_DOCKER_BRIDGE_CIDR` | The bridge CIDR to start the Docker daemon with. | false | -| `CODER_MOUNTS` | A list of mounts to mount into the inner container. Mounts default to `rw`. Ex: `CODER_MOUNTS=/home/coder:/home/coder,/var/run/mysecret:/var/run/mysecret:ro` | false | -| `CODER_USR_LIB_DIR` | The mountpoint of the host `/usr/lib` directory. Only required when using GPUs. | false | -| `CODER_ADD_TUN` | If `CODER_ADD_TUN=true` add a TUN device to the inner container. | false | -| `CODER_ADD_FUSE` | If `CODER_ADD_FUSE=true` add a FUSE device to the inner container. | false | -| `CODER_ADD_GPU` | If `CODER_ADD_GPU=true` add detected GPUs and related files to the inner container. Requires setting `CODER_USR_LIB_DIR` and mounting in the hosts `/usr/lib/` directory. | false | -| `CODER_CPUS` | Dictates the number of CPUs to allocate the inner container. It is recommended to set this using the Kubernetes [Downward API](https://kubernetes.io/docs/tasks/inject-data-application/environment-variable-expose-pod-information/#use-container-fields-as-values-for-environment-variables). | false | -| `CODER_MEMORY` | Dictates the max memory (in bytes) to allocate the inner container. It is recommended to set this using the Kubernetes [Downward API](https://kubernetes.io/docs/tasks/inject-data-application/environment-variable-expose-pod-information/#use-container-fields-as-values-for-environment-variables). | false | - -## Coder Template - -A [Coder Template](https://coder.com/docs/v2/latest/templates) can be found in the [root of the repo](./template.tf) to provide a starting point for customizing an envbox container. - -## Development - -It is not possible to develop `envbox` effectively using a containerized environment (includes developing `envbox` using `envbox`). A VM, personal machine, or similar environment is required to run the [integration](./integration/) test suite. diff --git a/background/doc.go b/background/doc.go deleted file mode 100644 index ac72f25..0000000 --- a/background/doc.go +++ /dev/null @@ -1,3 +0,0 @@ -// Package background contains an abstraction for running processes in the -// background. -package background diff --git a/background/process.go b/background/process.go deleted file mode 100644 index 810af59..0000000 --- a/background/process.go +++ /dev/null @@ -1,267 +0,0 @@ -package background - -import ( - "bufio" - "bytes" - "context" - "fmt" - "io" - "os" - "strings" - "sync" - "sync/atomic" - "syscall" - "time" - - "github.com/spf13/afero" - "golang.org/x/xerrors" - - "cdr.dev/slog" - "github.com/coder/envbox/xio" - "github.com/coder/envbox/xunix" -) - -// Process is an abstraction for running a command as a background process. -type Process struct { - ctx context.Context - cancel context.CancelFunc - log slog.Logger - cmd xunix.Cmd - binName string - - userKilled *int64 - waitCh chan error - mu sync.Mutex -} - -// New returns an instantiated daemon. -func New(ctx context.Context, log slog.Logger, cmd string, args ...string) *Process { - ctx, cancel := context.WithCancel(ctx) - return &Process{ - ctx: ctx, - cancel: cancel, - waitCh: make(chan error, 1), - cmd: xunix.GetExecer(ctx).CommandContext(ctx, cmd, args...), - log: log.Named(cmd), - userKilled: i64ptr(0), - binName: cmd, - } -} - -// Start starts the daemon. It functions akin to ox/exec.Command.Start(). -func (d *Process) Start() error { - d.mu.Lock() - defer d.mu.Unlock() - - return d.startProcess() -} - -// Wait waits for the process to exit, returning the error on the provided -// channel. -func (d *Process) Wait() <-chan error { - d.mu.Lock() - waitCh := d.waitCh - d.mu.Unlock() - - return waitCh -} - -// Run runs the command and waits for it to exit. It is a convenience -// function that combines both Start() and Wait(). -func (d *Process) Run() <-chan error { - err := d.Start() - if err != nil { - ch := make(chan error, 1) - ch <- err - return ch - } - - return d.Wait() -} - -// Restart kill the running process and reruns the command with the updated -// cmd and args. -func (d *Process) Restart(ctx context.Context, cmd string, args ...string) error { - d.mu.Lock() - defer d.mu.Unlock() - - err := d.kill(syscall.SIGTERM) - if err != nil { - return xerrors.Errorf("kill cmd: %w", err) - } - - ctx, cancel := context.WithCancel(ctx) - d.ctx = ctx - d.cancel = cancel - d.cmd = xunix.GetExecer(ctx).CommandContext(ctx, cmd, args...) - d.waitCh = make(chan error, 1) - d.userKilled = i64ptr(0) - d.binName = cmd - - return d.startProcess() -} - -func (d *Process) startProcess() error { - var ( - buf bytes.Buffer - - pr, pw = io.Pipe() - // Wrap our buffer in a limiter to - // avoid ballooning our memory use over time. - psw = &xio.PrefixSuffixWriter{ - N: 1 << 10, - W: &buf, - } - - w = &xio.SyncWriter{W: pw} - out = &xio.SyncWriter{W: psw} - - mw = io.MultiWriter(w, out) - cmd = d.cmd - ) - - cmd.SetStdout(mw) - cmd.SetStderr(mw) - - go scanIntoLog(d.ctx, d.log, bufio.NewScanner(pr), d.binName) - - err := d.cmd.Start() - if err != nil { - return xerrors.Errorf("start: %w", err) - } - - userKilled := d.userKilled - - go func() { - defer d.cancel() - defer close(d.waitCh) - - err := cmd.Wait() - _ = psw.Flush() - - // If the user killed the application the actual error returned - // from wait doesn't really matter. - if atomic.LoadInt64(userKilled) == 1 { - d.waitCh <- ErrUserKilled - return - } - - if err == nil { - d.waitCh <- nil - } else { - d.waitCh <- xerrors.Errorf("%s: %w", buf.Bytes(), err) - } - }() - return nil -} - -func (d *Process) kill(sig syscall.Signal) error { - if d.cmd.OSProcess() == nil { - return xerrors.Errorf("cmd has not been started") - } - - atomic.StoreInt64(d.userKilled, 1) - - pid := d.cmd.OSProcess().Pid - err := d.cmd.OSProcess().Signal(sig) - if err != nil { - return xerrors.Errorf("kill proc: %w", err) - } - - ticker := time.NewTicker(time.Millisecond * 10) - defer ticker.Stop() - - fs := xunix.GetFS(d.ctx) - - // Try to find the process in the procfs. If we can't find - // it, it means the process has exited. It's also possible that - // we find the same PID but the cmd is different indicating the PID - // has been reused. - exited, err := isProcExited(fs, pid, d.binName) - if err != nil { - return xerrors.Errorf("is proc cmd: %w", err) - } - - if exited { - return nil - } - - for { - select { - case <-d.ctx.Done(): - return d.ctx.Err() - case <-ticker.C: - exited, err = isProcExited(fs, pid, d.binName) - if err != nil { - return xerrors.Errorf("is proc cmd: %w", err) - } - - if exited { - return nil - } - } - } -} - -// isProcExited checks if the provided PID has exited. It does this -// by attempting to read its entry in /proc/. If it can't find the -// entry then the process has exited. If the entry exists we check to see -// if the cmd is the same since it is possible (even if extremely unlikely) -// that the PID may be reclaimed and reused for a separate process. -func isProcExited(fs afero.Fs, pid int, cmd string) (bool, error) { - cmdline, err := afero.ReadFile(fs, fmt.Sprintf("/proc/%d/cmdline", pid)) - if xerrors.Is(err, os.ErrNotExist) { - return true, nil - } - if err != nil { - return false, xerrors.Errorf("read file: %w", err) - } - - args := bytes.Split(cmdline, []byte{'0'}) - if len(args) < 1 { - // Honestly idk. - return false, xerrors.Errorf("cmdline has no output (%s)?", cmdline) - } - - // If the cmd doesn't match then the PID has been reused for a different - // process indicating the proc we're looking for has successfully exited. - return cmd != string(args[0]), nil -} - -func scanIntoLog(ctx context.Context, log slog.Logger, scanner *bufio.Scanner, binaryName string) { - for scanner.Scan() { - select { - case <-ctx.Done(): - return - default: - } - - var ( - line = scanner.Text() - logFn = log.Info - ) - - if strings.Contains(line, "level=debug") { - logFn = log.Debug - } else if strings.Contains(line, "level=info") { - logFn = log.Info - } else if strings.Contains(line, "level=warning") { - logFn = log.Warn - } else if strings.Contains(line, "level=error") { - logFn = log.Error - } else if strings.Contains(line, "level=fatal") { - logFn = log.Error - } - - logFn(ctx, "child log", - slog.F("process", binaryName), - slog.F("content", line), - ) - } -} - -var ErrUserKilled = xerrors.Errorf("daemon killed by user") - -func i64ptr(i int64) *int64 { - return &i -} diff --git a/buildlog/coder.go b/buildlog/coder.go deleted file mode 100644 index 062aea6..0000000 --- a/buildlog/coder.go +++ /dev/null @@ -1,138 +0,0 @@ -package buildlog - -import ( - "context" - "fmt" - "time" - - "cdr.dev/slog" - "github.com/coder/coder/codersdk/agentsdk" -) - -const ( - // To avoid excessive DB calls we batch our output. - // We'll keep at most 20KB of output in memory at a given time. - CoderLoggerMaxLogs = 20 - MaxCoderLogSize = 1 << 10 -) - -type StartupLog struct { - CreatedAt time.Time `json:"created_at"` - Output string `json:"output"` -} - -type CoderClient interface { - PatchStartupLogs(ctx context.Context, req agentsdk.PatchStartupLogs) error -} - -type CoderLogger struct { - ctx context.Context - cancel context.CancelFunc - client CoderClient - logger slog.Logger - logChan chan string - err error -} - -func OpenCoderLogger(ctx context.Context, client CoderClient, log slog.Logger) Logger { - ctx, cancel := context.WithCancel(ctx) - - coder := &CoderLogger{ - ctx: ctx, - cancel: cancel, - client: client, - logger: log, - logChan: make(chan string), - } - - go coder.processLogs() - - return coder -} - -func (c *CoderLogger) Infof(format string, a ...any) { - c.Info(fmt.Sprintf(format, a...)) -} - -func (c *CoderLogger) Info(output string) { - c.log(output) -} - -func (c *CoderLogger) Errorf(format string, a ...any) { - c.Error(fmt.Sprintf(format, a...)) -} - -func (c *CoderLogger) Error(output string) { - c.log("ERROR: " + output) -} - -func (c *CoderLogger) log(output string) { - if c.err != nil { - return - } - c.logChan <- output -} - -func (c *CoderLogger) Write(p []byte) (int, error) { - c.Info(string(p)) - return len(p), nil -} - -func (c *CoderLogger) Close() { - c.cancel() -} - -func (c *CoderLogger) processLogs() { - for { - var ( - line string - logs = make([]agentsdk.StartupLog, 0, CoderLoggerMaxLogs) - ) - - select { - case line = <-c.logChan: - lines := cutString(line, MaxCoderLogSize) - - for _, output := range lines { - logs = append(logs, agentsdk.StartupLog{ - CreatedAt: time.Now(), - Output: output, - }) - } - - case <-c.ctx.Done(): - close(c.logChan) - return - } - - // Send the logs in a goroutine so that we can avoid blocking - // too long on the channel. - cpLogs := logs - go func(startupLogs []agentsdk.StartupLog) { - err := c.client.PatchStartupLogs(c.ctx, agentsdk.PatchStartupLogs{ - Logs: startupLogs, - }) - if err != nil { - c.logger.Error(c.ctx, "send startup logs", slog.Error(err)) - } - }(cpLogs) - } -} - -// cutString cuts a string up into smaller strings that have a len no greater -// than the provided max size. -// If the string is less than the max size the return slice has one -// element with a value of the provided string. -func cutString(s string, maxSize int) []string { - if len(s) <= maxSize { - return []string{s} - } - - toks := []string{} - for len(s) > maxSize { - toks = append(toks, s[:maxSize]) - s = s[maxSize:] - } - - return append(toks, s) -} diff --git a/buildlog/coder_test.go b/buildlog/coder_test.go deleted file mode 100644 index 72ac46c..0000000 --- a/buildlog/coder_test.go +++ /dev/null @@ -1,117 +0,0 @@ -package buildlog_test - -import ( - "sync" - "testing" - "time" - - "golang.org/x/exp/slices" - "golang.org/x/net/context" - - "cdr.dev/slog/sloggers/slogtest" - - "github.com/stretchr/testify/require" - - "github.com/coder/coder/codersdk/agentsdk" - "github.com/coder/coder/cryptorand" - "github.com/coder/envbox/buildlog" -) - -func TestCoderLog(t *testing.T) { - t.Parallel() - - t.Run("OK", func(t *testing.T) { - t.Parallel() - - var ( - client = &fakeCoderClient{} - ctx = context.Background() - slogger = slogtest.Make(t, nil) - logMu sync.Mutex - ) - - expectedLog, err := cryptorand.String(10) - require.NoError(t, err) - var actualLog string - client.PatchStartupLogsFn = func(ctx context.Context, logs agentsdk.PatchStartupLogs) error { - logMu.Lock() - defer logMu.Unlock() - - require.Len(t, logs.Logs, 1) - require.NotZero(t, logs.Logs[0].CreatedAt) - require.Equal(t, expectedLog, logs.Logs[0].Output) - actualLog = logs.Logs[0].Output - return nil - } - - log := buildlog.OpenCoderLogger(ctx, client, slogger) - - log.Info(expectedLog) - - require.Eventually(t, func() bool { - logMu.Lock() - defer logMu.Unlock() - equal := actualLog == expectedLog - if !equal { - t.Logf("actual log %q does not equal expected log %q", actualLog, expectedLog) - } - return equal - }, time.Millisecond*5, time.Millisecond) - }) - - // Try sending a large line that exceeds the maximum Coder accepts (1KiB). - // Assert that it is sent as two logs instead. - t.Run("OutputNotDropped", func(t *testing.T) { - t.Parallel() - - var ( - maxLogs = 2 - client = fakeCoderClient{} - ctx = context.Background() - slogger = slogtest.Make(t, nil) - actualLogs = make([]string, 0, maxLogs) - logMu sync.Mutex - ) - client.PatchStartupLogsFn = func(ctx context.Context, logs agentsdk.PatchStartupLogs) error { - logMu.Lock() - defer logMu.Unlock() - - require.Len(t, logs.Logs, maxLogs) - for _, l := range logs.Logs { - require.NotZero(t, l.CreatedAt) - actualLogs = append(actualLogs, l.Output) - } - return nil - } - - log := buildlog.OpenCoderLogger(ctx, client, slogger) - - bigLine, err := cryptorand.String(buildlog.MaxCoderLogSize + buildlog.MaxCoderLogSize/2) - require.NoError(t, err) - // The line should be chopped up into smaller logs so that we don't - // drop output. - expectedLogs := []string{ - bigLine[:buildlog.MaxCoderLogSize], - bigLine[buildlog.MaxCoderLogSize:], - } - log.Info(bigLine) - // Close the logger to flush the logs. - log.Close() - require.Eventually(t, func() bool { - logMu.Lock() - defer logMu.Unlock() - return slices.Equal(expectedLogs, actualLogs) - }, time.Millisecond*5, time.Millisecond) - }) -} - -type fakeCoderClient struct { - PatchStartupLogsFn func(context.Context, agentsdk.PatchStartupLogs) error -} - -func (f fakeCoderClient) PatchStartupLogs(ctx context.Context, req agentsdk.PatchStartupLogs) error { - if f.PatchStartupLogsFn != nil { - return f.PatchStartupLogsFn(ctx, req) - } - return nil -} diff --git a/buildlog/doc.go b/buildlog/doc.go deleted file mode 100644 index 99149b7..0000000 --- a/buildlog/doc.go +++ /dev/null @@ -1,3 +0,0 @@ -// Package startuplog contains logic for writing logs related to startup -// of the workspace. -package buildlog diff --git a/buildlog/json.go b/buildlog/json.go deleted file mode 100644 index 78a41e9..0000000 --- a/buildlog/json.go +++ /dev/null @@ -1,67 +0,0 @@ -package buildlog - -import ( - "encoding/json" - "fmt" - "time" -) - -const ( - JSONLogTypeDone = "done" - JSONLogTypeInfo = "info" - JSONLogTypeError = "error" -) - -type JSONLog struct { - Output string `json:"output"` - Time time.Time `json:"time"` - Type string `json:"type"` -} - -type JSONLogger struct { - Encoder *json.Encoder -} - -func (j JSONLogger) Write(p []byte) (int, error) { - j.Info(string(p)) - return len(p), nil -} - -func (j JSONLogger) Infof(format string, a ...any) { - j.Info(fmt.Sprintf(format, a...)) -} - -func (j JSONLogger) Info(output string) { - j.log(JSONLog{ - Output: output, - Time: time.Now(), - Type: JSONLogTypeInfo, - }) -} - -func (j JSONLogger) Errorf(format string, a ...any) { - j.Error(fmt.Sprintf(format, a...)) -} - -func (j JSONLogger) Error(output string) { - j.log(JSONLog{ - Output: output, - Time: time.Now(), - Type: JSONLogTypeError, - }) -} - -// nolint -func (j JSONLogger) log(jlog JSONLog) { - err := j.Encoder.Encode(jlog) - if err != nil { - panic(err) - } -} - -func (j JSONLogger) Close() { - j.log(JSONLog{ - Type: JSONLogTypeDone, - Time: time.Now(), - }) -} diff --git a/buildlog/json_test.go b/buildlog/json_test.go deleted file mode 100644 index d6349f3..0000000 --- a/buildlog/json_test.go +++ /dev/null @@ -1,96 +0,0 @@ -package buildlog_test - -import ( - "bytes" - "encoding/json" - "testing" - - "golang.org/x/xerrors" - - "github.com/stretchr/testify/require" - - "github.com/coder/envbox/buildlog" -) - -func TestJSONLog(t *testing.T) { - t.Parallel() - - type testcase struct { - name string - expectedLog buildlog.JSONLog - logFn func(l buildlog.Logger) - } - - cases := []testcase{ - { - name: "Info", - expectedLog: buildlog.JSONLog{ - Output: "foo", - Type: buildlog.JSONLogTypeInfo, - }, - logFn: func(l buildlog.Logger) { - l.Info("foo") - }, - }, - { - name: "Infof", - expectedLog: buildlog.JSONLog{ - Output: "foo: bar", - Type: buildlog.JSONLogTypeInfo, - }, - logFn: func(l buildlog.Logger) { - l.Infof("foo: %s", "bar") - }, - }, - { - name: "Error", - expectedLog: buildlog.JSONLog{ - Output: "some error", - Type: buildlog.JSONLogTypeError, - }, - logFn: func(l buildlog.Logger) { - l.Error("some error") - }, - }, - { - name: "Errorf", - expectedLog: buildlog.JSONLog{ - Output: "some error: my error", - Type: buildlog.JSONLogTypeError, - }, - logFn: func(l buildlog.Logger) { - l.Errorf("some error: %v", xerrors.New("my error")) - }, - }, - { - name: "Close", - expectedLog: buildlog.JSONLog{ - Output: "", - Type: buildlog.JSONLogTypeDone, - }, - logFn: func(l buildlog.Logger) { - l.Close() - }, - }, - } - - for _, c := range cases { - c := c - t.Run(c.name, func(t *testing.T) { - t.Parallel() - - var buf bytes.Buffer - jlog := buildlog.JSONLogger{ - Encoder: json.NewEncoder(&buf), - } - - c.logFn(jlog) - - var actualLog buildlog.JSONLog - json.NewDecoder(&buf).Decode(&actualLog) - require.NotZero(t, actualLog.Time) - require.Equal(t, c.expectedLog.Output, actualLog.Output) - require.Equal(t, c.expectedLog.Type, actualLog.Type) - }) - } -} diff --git a/buildlog/logger.go b/buildlog/logger.go deleted file mode 100644 index b0f8312..0000000 --- a/buildlog/logger.go +++ /dev/null @@ -1,79 +0,0 @@ -package buildlog - -import ( - "context" - "fmt" - "io" -) - -type loggerCtxKey struct{} - -func GetLogger(ctx context.Context) Logger { - l := ctx.Value(loggerCtxKey{}) - if l == nil { - return nopLogger{} - } - //nolint - return l.(Logger) -} - -func WithLogger(ctx context.Context, l Logger) context.Context { - return context.WithValue(ctx, loggerCtxKey{}, l) -} - -type Logger interface { - Info(output string) - Infof(format string, a ...any) - Error(output string) - Errorf(format string, a ...any) - Close() - io.Writer -} - -func MultiLogger(loggers ...Logger) Logger { - return multiLogger{loggers} -} - -type multiLogger struct { - loggers []Logger -} - -func (m multiLogger) Infof(format string, a ...any) { - m.Info(fmt.Sprintf(format, a...)) -} - -func (m multiLogger) Info(output string) { - for _, log := range m.loggers { - log.Info(output) - } -} - -func (m multiLogger) Errorf(format string, a ...any) { - m.Error(fmt.Sprintf(format, a...)) -} - -func (m multiLogger) Error(output string) { - for _, log := range m.loggers { - log.Error(output) - } -} - -func (m multiLogger) Write(p []byte) (int, error) { - m.Info(string(p)) - return len(p), nil -} - -func (m multiLogger) Close() { - for _, log := range m.loggers { - log.Close() - } -} - -type nopLogger struct{} - -func (nopLogger) Info(string) {} -func (nopLogger) Infof(string, ...any) {} -func (nopLogger) Errorf(string, ...any) {} -func (nopLogger) Error(string) {} -func (nopLogger) Write([]byte) (int, error) { return 0, nil } -func (nopLogger) Close() {} diff --git a/cli/cliflag/cliflag.go b/cli/cliflag/cliflag.go deleted file mode 100644 index 10f4b77..0000000 --- a/cli/cliflag/cliflag.go +++ /dev/null @@ -1,183 +0,0 @@ -// Package cliflag extends flagset with environment variable defaults. -// -// Usage: -// -// cliflag.String(root.Flags(), &address, "address", "a", "CODER_ADDRESS", "127.0.0.1:3000", "The address to serve the API and dashboard") -// -// Will produce the following usage docs: -// -// -a, --address string The address to serve the API and dashboard (uses $CODER_ADDRESS). (default "127.0.0.1:3000") -package cliflag - -import ( - "fmt" - "os" - "strconv" - "strings" - "time" - - "github.com/spf13/cobra" - "github.com/spf13/pflag" -) - -// IsSetBool returns the value of the boolean flag if it is set. -// It returns false if the flag isn't set or if any error occurs attempting -// to parse the value of the flag. -func IsSetBool(cmd *cobra.Command, name string) bool { - val, ok := IsSet(cmd, name) - if !ok { - return false - } - - b, err := strconv.ParseBool(val) - return err == nil && b -} - -// IsSet returns the string value of the flag and whether it was set. -func IsSet(cmd *cobra.Command, name string) (string, bool) { - flag := cmd.Flag(name) - if flag == nil { - return "", false - } - - return flag.Value.String(), flag.Changed -} - -// String sets a string flag on the given flag set. -func String(flagset *pflag.FlagSet, name, shorthand, env, def, usage string) { - v, ok := os.LookupEnv(env) - if !ok || v == "" { - v = def - } - flagset.StringP(name, shorthand, v, fmtUsage(usage, env)) -} - -// StringVarP sets a string flag on the given flag set. -func StringVarP(flagset *pflag.FlagSet, p *string, name string, shorthand string, env string, def string, usage string) { - v, ok := os.LookupEnv(env) - if !ok || v == "" { - v = def - } - flagset.StringVarP(p, name, shorthand, v, fmtUsage(usage, env)) -} - -func StringArray(flagset *pflag.FlagSet, name, shorthand, env string, def []string, usage string) { - v, ok := os.LookupEnv(env) - if !ok || v == "" { - if v == "" { - def = []string{} - } else { - def = strings.Split(v, ",") - } - } - flagset.StringArrayP(name, shorthand, def, fmtUsage(usage, env)) -} - -func StringArrayVarP(flagset *pflag.FlagSet, ptr *[]string, name string, shorthand string, env string, def []string, usage string) { - val, ok := os.LookupEnv(env) - if ok { - if val == "" { - def = []string{} - } else { - def = strings.Split(val, ",") - } - } - flagset.StringArrayVarP(ptr, name, shorthand, def, fmtUsage(usage, env)) -} - -// Uint8VarP sets a uint8 flag on the given flag set. -func Uint8VarP(flagset *pflag.FlagSet, ptr *uint8, name string, shorthand string, env string, def uint8, usage string) { - val, ok := os.LookupEnv(env) - if !ok || val == "" { - flagset.Uint8VarP(ptr, name, shorthand, def, fmtUsage(usage, env)) - return - } - - vi64, err := strconv.ParseUint(val, 10, 8) - if err != nil { - flagset.Uint8VarP(ptr, name, shorthand, def, fmtUsage(usage, env)) - return - } - - flagset.Uint8VarP(ptr, name, shorthand, uint8(vi64), fmtUsage(usage, env)) -} - -// IntVarP sets a uint8 flag on the given flag set. -func IntVarP(flagset *pflag.FlagSet, ptr *int, name string, shorthand string, env string, def int, usage string) { - val, ok := os.LookupEnv(env) - if !ok || val == "" { - flagset.IntVarP(ptr, name, shorthand, def, fmtUsage(usage, env)) - return - } - - vi64, err := strconv.ParseUint(val, 10, 8) - if err != nil { - flagset.IntVarP(ptr, name, shorthand, def, fmtUsage(usage, env)) - return - } - - flagset.IntVarP(ptr, name, shorthand, int(vi64), fmtUsage(usage, env)) -} - -func Bool(flagset *pflag.FlagSet, name, shorthand, env string, def bool, usage string) { - val, ok := os.LookupEnv(env) - if !ok || val == "" { - flagset.BoolP(name, shorthand, def, fmtUsage(usage, env)) - return - } - - valb, err := strconv.ParseBool(val) - if err != nil { - flagset.BoolP(name, shorthand, def, fmtUsage(usage, env)) - return - } - - flagset.BoolP(name, shorthand, valb, fmtUsage(usage, env)) -} - -// BoolVarP sets a bool flag on the given flag set. -func BoolVarP(flagset *pflag.FlagSet, ptr *bool, name string, shorthand string, env string, def bool, usage string) { - val, ok := os.LookupEnv(env) - if !ok || val == "" { - flagset.BoolVarP(ptr, name, shorthand, def, fmtUsage(usage, env)) - return - } - - valb, err := strconv.ParseBool(val) - if err != nil { - flagset.BoolVarP(ptr, name, shorthand, def, fmtUsage(usage, env)) - return - } - - flagset.BoolVarP(ptr, name, shorthand, valb, fmtUsage(usage, env)) -} - -// DurationVarP sets a time.Duration flag on the given flag set. -func DurationVarP(flagset *pflag.FlagSet, ptr *time.Duration, name string, shorthand string, env string, def time.Duration, usage string) { - val, ok := os.LookupEnv(env) - if !ok || val == "" { - flagset.DurationVarP(ptr, name, shorthand, def, fmtUsage(usage, env)) - return - } - - valb, err := time.ParseDuration(val) - if err != nil { - flagset.DurationVarP(ptr, name, shorthand, def, fmtUsage(usage, env)) - return - } - - flagset.DurationVarP(ptr, name, shorthand, valb, fmtUsage(usage, env)) -} - -func fmtUsage(u string, env string) string { - if env != "" { - // Avoid double dotting. - dot := "." - if strings.HasSuffix(u, ".") { - dot = "" - } - u = fmt.Sprintf("%s%s\n(Consumes $%s", u, dot, env) - } - - return u -} diff --git a/cli/cliflag/cliflag_test.go b/cli/cliflag/cliflag_test.go deleted file mode 100644 index d05468c..0000000 --- a/cli/cliflag/cliflag_test.go +++ /dev/null @@ -1,277 +0,0 @@ -package cliflag_test - -import ( - "fmt" - "strconv" - "testing" - "time" - - "github.com/spf13/pflag" - "github.com/stretchr/testify/require" - - "github.com/coder/coder/cryptorand" - "github.com/coder/envbox/cli/cliflag" -) - -// Testcliflag cannot run in parallel because it uses t.Setenv. -// -//nolint:paralleltest -func TestCliflag(t *testing.T) { - t.Run("StringDefault", func(t *testing.T) { - flagset, name, shorthand, env, usage := randomFlag() - def, _ := cryptorand.String(10) - cliflag.String(flagset, name, shorthand, env, def, usage) - got, err := flagset.GetString(name) - require.NoError(t, err) - require.Equal(t, def, got) - require.Contains(t, flagset.FlagUsages(), usage) - require.Contains(t, flagset.FlagUsages(), fmt.Sprintf("Consumes $%s", env)) - }) - - t.Run("StringEnvVar", func(t *testing.T) { - flagset, name, shorthand, env, usage := randomFlag() - envValue, _ := cryptorand.String(10) - t.Setenv(env, envValue) - def, _ := cryptorand.String(10) - cliflag.String(flagset, name, shorthand, env, def, usage) - got, err := flagset.GetString(name) - require.NoError(t, err) - require.Equal(t, envValue, got) - }) - - t.Run("StringVarPDefault", func(t *testing.T) { - var ptr string - flagset, name, shorthand, env, usage := randomFlag() - def, _ := cryptorand.String(10) - - cliflag.StringVarP(flagset, &ptr, name, shorthand, env, def, usage) - got, err := flagset.GetString(name) - require.NoError(t, err) - require.Equal(t, def, got) - require.Contains(t, flagset.FlagUsages(), usage) - require.Contains(t, flagset.FlagUsages(), fmt.Sprintf("Consumes $%s", env)) - }) - - t.Run("StringVarPEnvVar", func(t *testing.T) { - var ptr string - flagset, name, shorthand, env, usage := randomFlag() - envValue, _ := cryptorand.String(10) - t.Setenv(env, envValue) - def, _ := cryptorand.String(10) - - cliflag.StringVarP(flagset, &ptr, name, shorthand, env, def, usage) - got, err := flagset.GetString(name) - require.NoError(t, err) - require.Equal(t, envValue, got) - }) - - t.Run("EmptyEnvVar", func(t *testing.T) { - var ptr string - flagset, name, shorthand, _, usage := randomFlag() - def, _ := cryptorand.String(10) - - cliflag.StringVarP(flagset, &ptr, name, shorthand, "", def, usage) - got, err := flagset.GetString(name) - require.NoError(t, err) - require.Equal(t, def, got) - require.Contains(t, flagset.FlagUsages(), usage) - require.NotContains(t, flagset.FlagUsages(), "Consumes") - }) - - t.Run("StringArrayDefault", func(t *testing.T) { - var ptr []string - flagset, name, shorthand, env, usage := randomFlag() - def := []string{"hello"} - cliflag.StringArrayVarP(flagset, &ptr, name, shorthand, env, def, usage) - got, err := flagset.GetStringArray(name) - require.NoError(t, err) - require.Equal(t, def, got) - }) - - t.Run("StringArrayEnvVar", func(t *testing.T) { - var ptr []string - flagset, name, shorthand, env, usage := randomFlag() - t.Setenv(env, "wow,test") - cliflag.StringArrayVarP(flagset, &ptr, name, shorthand, env, nil, usage) - got, err := flagset.GetStringArray(name) - require.NoError(t, err) - require.Equal(t, []string{"wow", "test"}, got) - }) - - t.Run("StringArrayEnvVarEmpty", func(t *testing.T) { - var ptr []string - flagset, name, shorthand, env, usage := randomFlag() - t.Setenv(env, "") - cliflag.StringArrayVarP(flagset, &ptr, name, shorthand, env, nil, usage) - got, err := flagset.GetStringArray(name) - require.NoError(t, err) - require.Equal(t, []string{}, got) - }) - - t.Run("UInt8Default", func(t *testing.T) { - var ptr uint8 - flagset, name, shorthand, env, usage := randomFlag() - def, _ := cryptorand.Int63n(10) - - cliflag.Uint8VarP(flagset, &ptr, name, shorthand, env, uint8(def), usage) - got, err := flagset.GetUint8(name) - require.NoError(t, err) - require.Equal(t, uint8(def), got) - require.Contains(t, flagset.FlagUsages(), usage) - require.Contains(t, flagset.FlagUsages(), fmt.Sprintf("Consumes $%s", env)) - }) - - t.Run("UInt8EnvVar", func(t *testing.T) { - var ptr uint8 - flagset, name, shorthand, env, usage := randomFlag() - envValue, _ := cryptorand.Int63n(10) - t.Setenv(env, strconv.FormatUint(uint64(envValue), 10)) - def, _ := cryptorand.Int() - - cliflag.Uint8VarP(flagset, &ptr, name, shorthand, env, uint8(def), usage) - got, err := flagset.GetUint8(name) - require.NoError(t, err) - require.Equal(t, uint8(envValue), got) - }) - - t.Run("UInt8FailParse", func(t *testing.T) { - var ptr uint8 - flagset, name, shorthand, env, usage := randomFlag() - envValue, _ := cryptorand.String(10) - t.Setenv(env, envValue) - def, _ := cryptorand.Int63n(10) - - cliflag.Uint8VarP(flagset, &ptr, name, shorthand, env, uint8(def), usage) - got, err := flagset.GetUint8(name) - require.NoError(t, err) - require.Equal(t, uint8(def), got) - }) - - t.Run("IntDefault", func(t *testing.T) { - var ptr int - flagset, name, shorthand, env, usage := randomFlag() - def, _ := cryptorand.Int63n(10) - - cliflag.IntVarP(flagset, &ptr, name, shorthand, env, int(def), usage) - got, err := flagset.GetInt(name) - require.NoError(t, err) - require.Equal(t, int(def), got) - require.Contains(t, flagset.FlagUsages(), usage) - require.Contains(t, flagset.FlagUsages(), fmt.Sprintf("Consumes $%s", env)) - }) - - t.Run("IntEnvVar", func(t *testing.T) { - var ptr int - flagset, name, shorthand, env, usage := randomFlag() - envValue, _ := cryptorand.Int63n(10) - t.Setenv(env, strconv.FormatUint(uint64(envValue), 10)) - def, _ := cryptorand.Int() - - cliflag.IntVarP(flagset, &ptr, name, shorthand, env, def, usage) - got, err := flagset.GetInt(name) - require.NoError(t, err) - require.Equal(t, int(envValue), got) - }) - - t.Run("IntFailParse", func(t *testing.T) { - var ptr int - flagset, name, shorthand, env, usage := randomFlag() - envValue, _ := cryptorand.String(10) - t.Setenv(env, envValue) - def, _ := cryptorand.Int63n(10) - - cliflag.IntVarP(flagset, &ptr, name, shorthand, env, int(def), usage) - got, err := flagset.GetInt(name) - require.NoError(t, err) - require.Equal(t, int(def), got) - }) - - t.Run("BoolDefault", func(t *testing.T) { - var ptr bool - flagset, name, shorthand, env, usage := randomFlag() - def, _ := cryptorand.Bool() - - cliflag.BoolVarP(flagset, &ptr, name, shorthand, env, def, usage) - got, err := flagset.GetBool(name) - require.NoError(t, err) - require.Equal(t, def, got) - require.Contains(t, flagset.FlagUsages(), usage) - require.Contains(t, flagset.FlagUsages(), fmt.Sprintf("Consumes $%s", env)) - }) - - t.Run("BoolEnvVar", func(t *testing.T) { - var ptr bool - flagset, name, shorthand, env, usage := randomFlag() - envValue, _ := cryptorand.Bool() - t.Setenv(env, strconv.FormatBool(envValue)) - def, _ := cryptorand.Bool() - - cliflag.BoolVarP(flagset, &ptr, name, shorthand, env, def, usage) - got, err := flagset.GetBool(name) - require.NoError(t, err) - require.Equal(t, envValue, got) - }) - - t.Run("BoolFailParse", func(t *testing.T) { - var ptr bool - flagset, name, shorthand, env, usage := randomFlag() - envValue, _ := cryptorand.String(10) - t.Setenv(env, envValue) - def, _ := cryptorand.Bool() - - cliflag.BoolVarP(flagset, &ptr, name, shorthand, env, def, usage) - got, err := flagset.GetBool(name) - require.NoError(t, err) - require.Equal(t, def, got) - }) - - t.Run("DurationDefault", func(t *testing.T) { - var ptr time.Duration - flagset, name, shorthand, env, usage := randomFlag() - def, _ := cryptorand.Duration() - - cliflag.DurationVarP(flagset, &ptr, name, shorthand, env, def, usage) - got, err := flagset.GetDuration(name) - require.NoError(t, err) - require.Equal(t, def, got) - require.Contains(t, flagset.FlagUsages(), usage) - require.Contains(t, flagset.FlagUsages(), fmt.Sprintf("Consumes $%s", env)) - }) - - t.Run("DurationEnvVar", func(t *testing.T) { - var ptr time.Duration - flagset, name, shorthand, env, usage := randomFlag() - envValue, _ := cryptorand.Duration() - t.Setenv(env, envValue.String()) - def, _ := cryptorand.Duration() - - cliflag.DurationVarP(flagset, &ptr, name, shorthand, env, def, usage) - got, err := flagset.GetDuration(name) - require.NoError(t, err) - require.Equal(t, envValue, got) - }) - - t.Run("DurationFailParse", func(t *testing.T) { - var ptr time.Duration - flagset, name, shorthand, env, usage := randomFlag() - envValue, _ := cryptorand.String(10) - t.Setenv(env, envValue) - def, _ := cryptorand.Duration() - - cliflag.DurationVarP(flagset, &ptr, name, shorthand, env, def, usage) - got, err := flagset.GetDuration(name) - require.NoError(t, err) - require.Equal(t, def, got) - }) -} - -func randomFlag() (*pflag.FlagSet, string, string, string, string) { - fsname, _ := cryptorand.String(10) - flagset := pflag.NewFlagSet(fsname, pflag.PanicOnError) - name, _ := cryptorand.String(10) - shorthand, _ := cryptorand.String(1) - env, _ := cryptorand.String(10) - usage, _ := cryptorand.String(10) - - return flagset, name, shorthand, env, usage -} diff --git a/cli/clitest/cli.go b/cli/clitest/cli.go deleted file mode 100644 index f6ff8cf..0000000 --- a/cli/clitest/cli.go +++ /dev/null @@ -1,86 +0,0 @@ -package clitest - -import ( - "context" - "testing" - "time" - - "github.com/spf13/cobra" - "github.com/stretchr/testify/require" - "k8s.io/mount-utils" - - "github.com/coder/envbox/cli" - "github.com/coder/envbox/dockerutil" - "github.com/coder/envbox/dockerutil/dockerfake" - "github.com/coder/envbox/xunix" - "github.com/coder/envbox/xunix/xunixfake" -) - -func Execer(ctx context.Context) *xunixfake.FakeExec { - //nolint we should panic if this isn't the case. - return xunix.GetExecer(ctx).(*xunixfake.FakeExec) -} - -func FS(ctx context.Context) *xunixfake.MemFS { - //nolint we should panic if this isn't the case. - return xunix.GetFS(ctx).(*xunixfake.MemFS) -} - -func Mounter(ctx context.Context) *mount.FakeMounter { - //nolint we should panic if this isn't the case. - return xunix.Mounter(ctx).(*mount.FakeMounter) -} - -// nolint -func DockerClient(t *testing.T, ctx context.Context) *dockerfake.MockClient { - t.Helper() - - client, err := dockerutil.Client(ctx) - require.NoError(t, err) - //nolint we should panic if this isn't the case. - return client.(*dockerfake.MockClient) -} - -// New returns an instantiated Command as well as a context populated with mocked -// values for the command. All mock/fakes have been minimally configured to -// induce a successful call to the command. -func New(t *testing.T, cmd string, args ...string) (context.Context, *cobra.Command) { - t.Helper() - - var ( - execer = NewFakeExecer() - fs = NewMemFS() - mnt = &mount.FakeMounter{} - client = NewFakeDockerClient() - iface = GetNetLink(t) - ctx = ctx(t, fs, execer, mnt, client) - ) - - root := cli.Root() - // This is the one thing that isn't really mocked for the tests. - // I cringe at the thought of introducing yet another mock so - // let's avoid it for now. - // If a consumer sets the ethlink arg it should overwrite our - // default we set here. - args = append([]string{cmd, "--ethlink=" + iface.Attrs().Name, "--no-startup-log"}, args...) - root.SetArgs(args) - - FakeSysboxManagerReady(t, fs) - FakeCPUGroups(t, fs, "1234", "5678") - - return ctx, root -} - -func ctx(t *testing.T, fs xunix.FS, ex xunix.Execer, mnt mount.Interface, client dockerutil.DockerClient) context.Context { - t.Helper() - - ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) - t.Cleanup(cancel) - - ctx = xunix.WithFS(ctx, fs) - ctx = xunix.WithExecer(ctx, ex) - ctx = xunix.WithMounter(ctx, mnt) - ctx = dockerutil.WithClient(ctx, client) - - return ctx -} diff --git a/cli/clitest/fake.go b/cli/clitest/fake.go deleted file mode 100644 index 2d14f7f..0000000 --- a/cli/clitest/fake.go +++ /dev/null @@ -1,62 +0,0 @@ -package clitest - -import ( - "bufio" - "context" - "net" - "os" - "strings" - - dockertypes "github.com/docker/docker/api/types" - "github.com/spf13/afero" - testingexec "k8s.io/utils/exec/testing" - - "github.com/coder/envbox/dockerutil" - "github.com/coder/envbox/dockerutil/dockerfake" - "github.com/coder/envbox/xunix/xunixfake" -) - -func NewMemFS() *xunixfake.MemFS { - return &xunixfake.MemFS{ - MemMapFs: &afero.MemMapFs{}, - Owner: map[string]xunixfake.FileOwner{}, - } -} - -func NewFakeExecer() *xunixfake.FakeExec { - return &xunixfake.FakeExec{ - Commands: map[string]*xunixfake.FakeCmd{}, - DefaultFakeCmd: &xunixfake.FakeCmd{ - FakeCmd: &testingexec.FakeCmd{}, - FakeProcess: &os.Process{Pid: 1111}, - // The main use of exec commands in this repo - // are to spawn daemon processes so ideally the - // default behavior is that they do not exist. - // nolint - WaitFn: func() error { select {} }, - }, - } -} - -func NewFakeDockerClient() dockerutil.DockerClient { - client := &dockerfake.MockClient{} - - client.ContainerInspectFn = func(_ context.Context, container string) (dockertypes.ContainerJSON, error) { - return dockertypes.ContainerJSON{ - ContainerJSONBase: &dockertypes.ContainerJSONBase{ - GraphDriver: dockertypes.GraphDriverData{ - Data: map[string]string{"MergedDir": "blah"}, - }, - }, - }, nil - } - - client.ContainerExecAttachFn = func(_ context.Context, execID string, config dockertypes.ExecStartCheck) (dockertypes.HijackedResponse, error) { - return dockertypes.HijackedResponse{ - Reader: bufio.NewReader(strings.NewReader("root:x:0:0:root:/root:/bin/bash")), - Conn: &net.IPConn{}, - }, nil - } - - return client -} diff --git a/cli/clitest/fs.go b/cli/clitest/fs.go deleted file mode 100644 index 6ad6677..0000000 --- a/cli/clitest/fs.go +++ /dev/null @@ -1,24 +0,0 @@ -package clitest - -import ( - "testing" - - "github.com/spf13/afero" - "github.com/stretchr/testify/require" - - "github.com/coder/envbox/sysboxutil" - "github.com/coder/envbox/xunix" -) - -func FakeSysboxManagerReady(t *testing.T, fs afero.Fs) { - err := afero.WriteFile(fs, sysboxutil.ManagerSocketPath, []byte(""), 0o644) - require.NoError(t, err) -} - -func FakeCPUGroups(t *testing.T, fs afero.Fs, quota, period string) { - err := afero.WriteFile(fs, xunix.CPUPeriodPath, []byte(period), 0o600) - require.NoError(t, err) - - err = afero.WriteFile(fs, xunix.CPUQuotaPath, []byte(quota), 0o600) - require.NoError(t, err) -} diff --git a/cli/clitest/net.go b/cli/clitest/net.go deleted file mode 100644 index 19c525e..0000000 --- a/cli/clitest/net.go +++ /dev/null @@ -1,28 +0,0 @@ -package clitest - -import ( - "testing" - - "github.com/stretchr/testify/require" - "github.com/vishvananda/netlink" -) - -func GetNetLink(t *testing.T) netlink.Link { - t.Helper() - - addrs, err := netlink.AddrList(nil, netlink.FAMILY_V4) - require.NoError(t, err) - - for _, addr := range addrs { - if !addr.IP.IsGlobalUnicast() || addr.IP.To4() == nil || addr.Label == "docker0" { - continue - } - - nl, err := netlink.LinkByName(addr.Label) - require.NoError(t, err) - return nl - } - - t.Fatalf("failed to find a valid network interface") - return nil -} diff --git a/cli/doc.go b/cli/doc.go deleted file mode 100644 index 6f4bac6..0000000 --- a/cli/doc.go +++ /dev/null @@ -1,2 +0,0 @@ -// Package cli contains commands for the envbox tool. -package cli diff --git a/cli/docker.go b/cli/docker.go deleted file mode 100644 index ce327b3..0000000 --- a/cli/docker.go +++ /dev/null @@ -1,791 +0,0 @@ -package cli - -import ( - "context" - "encoding/json" - "fmt" - "io" - "net/url" - "os" - "path" - "path/filepath" - "strconv" - "strings" - - dockertypes "github.com/docker/docker/api/types" - "github.com/docker/docker/api/types/container" - "github.com/spf13/cobra" - "golang.org/x/exp/slices" - "golang.org/x/xerrors" - - "cdr.dev/slog" - "cdr.dev/slog/sloggers/slogjson" - "github.com/coder/coder/codersdk/agentsdk" - "github.com/coder/envbox/background" - "github.com/coder/envbox/buildlog" - "github.com/coder/envbox/cli/cliflag" - "github.com/coder/envbox/dockerutil" - "github.com/coder/envbox/slogkubeterminate" - "github.com/coder/envbox/sysboxutil" - "github.com/coder/envbox/xunix" -) - -const ( - // EnvBoxPullImageSecretEnvVar defines the environment variable at which the - // pull image secret is mounted for envbox. - // Suppresses warning: G101: Potential hardcoded credentials - // EnvBoxContainerName is the name of the inner user container. - EnvBoxPullImageSecretEnvVar = "CODER_IMAGE_PULL_SECRET" //nolint:gosec - EnvBoxContainerName = "CODER_CVM_CONTAINER_NAME" -) - -const ( - defaultNetLink = "eth0" - defaultDockerBridge = "docker0" - // From https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts-technical-overview.html - awsWebIdentityTokenFilePath = "/var/run/secrets/eks.amazonaws.com/serviceaccount/token" //nolint - sysboxErrMsg = "Sysbox exited, possibly because of an unsupported kernel version. Please contact an infrastructure administrator and request a node kernel with seccomp API level >= 5." - - // noSpaceDataDir is the directory to use for the data directory - // for dockerd when the default directory (/var/lib/docker pointing - // to the user's pvc) is at capacity. This directory points to - // ephemeral storage allocated by the node and should be more likely - // to have capacity. - noSpaceDataDir = "/var/lib/docker.bak" - // noSpaceDockerDriver is the storage driver to use in cases where - // the default data dir (residing in the user's PVC) is at capacity. - // In such cases we must use the vfs storage driver because overlay2 - // does not work on top of overlay. - noSpaceDockerDriver = "vfs" - - OuterFUSEPath = "/tmp/coder-fuse" - InnerFUSEPath = "/dev/fuse" - - OuterTUNPath = "/tmp/coder-tun" - InnerTUNPath = "/dev/net/tun" - - InnerContainerName = "workspace_cvm" - - // Required for userns mapping. - // This is the ID of the user we apply in `envbox/Dockerfile`. - // - // There should be caution changing this value. - // Source directory permissions on the host are offset by this - // value. For example, folder `/home/coder` inside the container - // with UID/GID 1000 will be mapped to `UserNamespaceOffset` + 1000 - // on the host. Changing this value will result in improper mappings - // on existing containers. - UserNamespaceOffset = 100000 - devDir = "/dev" -) - -var ( - EnvInnerImage = "CODER_INNER_IMAGE" - EnvInnerUsername = "CODER_INNER_USERNAME" - EnvInnerEnvs = "CODER_INNER_ENVS" - EnvInnerWorkDir = "CODER_INNER_WORK_DIR" - EnvInnerHostname = "CODER_INNER_HOSTNAME" - EnvAddTun = "CODER_ADD_TUN" - EnvAddFuse = "CODER_ADD_FUSE" - EnvBridgeCIDR = "CODER_DOCKER_BRIDGE_CIDR" - //nolint - EnvAgentToken = "CODER_AGENT_TOKEN" - EnvAgentURL = "CODER_AGENT_URL" - EnvBootstrap = "CODER_BOOTSTRAP_SCRIPT" - EnvMounts = "CODER_MOUNTS" - EnvCPUs = "CODER_CPUS" - EnvMemory = "CODER_MEMORY" - EnvAddGPU = "CODER_ADD_GPU" - EnvUsrLibDir = "CODER_USR_LIB_DIR" -) - -var envboxPrivateMounts = map[string]struct{}{ - "/var/lib/containers": {}, - "/var/lib/docker": {}, - "/var/lib/sysbox": {}, - "/lib/modules": {}, - "/usr/src": {}, - // /var/lib/coder is not technically a mount - // private to envbox but it is specially handled - // by sysbox so it does not require any effort - // on our part. - "/var/lib/coder": {}, -} - -type flags struct { - innerImage string - innerUsername string - agentToken string - - // Optional flags. - innerEnvs string - innerWorkDir string - innerHostname string - imagePullSecret string - coderURL string - addTUN bool - addFUSE bool - addGPU bool - noStartupLogs bool - dockerdBridgeCIDR string - boostrapScript string - ethlink string - containerMounts string - hostUsrLibDir string - cpus int - memory int -} - -func dockerCmd() *cobra.Command { - var flags flags - - cmd := &cobra.Command{ - Use: "docker", - Short: "Create a docker-based CVM", - RunE: func(cmd *cobra.Command, args []string) (err error) { - var ( - ctx = cmd.Context() - log = slog.Make(slogjson.Sink(io.Discard)) - blog = buildlog.GetLogger(ctx) - ) - - if !flags.noStartupLogs { - log = slog.Make(slogjson.Sink(cmd.ErrOrStderr()), slogkubeterminate.Make()).Leveled(slog.LevelDebug) - blog = buildlog.JSONLogger{Encoder: json.NewEncoder(os.Stderr)} - } - - if !flags.noStartupLogs && flags.agentToken != "" && flags.coderURL != "" { - coderURL, err := url.Parse(flags.coderURL) - if err != nil { - return xerrors.Errorf("parse coder URL %q: %w", flags.coderURL, err) - } - - agent := agentsdk.New(coderURL) - agent.SetSessionToken(flags.agentToken) - - blog = buildlog.MultiLogger( - buildlog.OpenCoderLogger(ctx, agent, log), - blog, - ) - - ctx = buildlog.WithLogger(ctx, blog) - } - defer blog.Close() - - defer func(err *error) { - if *err != nil { - blog.Errorf("Failed to run envbox: %v", *err) - } - }(&err) - - blog.Info("Waiting for dockerd to startup...") - - go func() { - select { - // Start sysbox-mgr and sysbox-fs in order to run - // sysbox containers. - case err := <-background.New(ctx, log, "sysbox-mgr").Run(): - blog.Info(sysboxErrMsg) - //nolint - log.Fatal(ctx, "sysbox-mgr exited", slog.Error(err)) - case err := <-background.New(ctx, log, "sysbox-fs").Run(): - blog.Info(sysboxErrMsg) - //nolint - log.Fatal(ctx, "sysbox-fs exited", slog.Error(err)) - } - }() - - cidr := dockerutil.DefaultBridgeCIDR - if flags.dockerdBridgeCIDR != "" { - cidr = flags.dockerdBridgeCIDR - log.Debug(ctx, "using custom docker bridge CIDR", slog.F("cidr", cidr)) - } - - dargs, err := dockerdArgs(ctx, log, flags.ethlink, cidr, false) - if err != nil { - return xerrors.Errorf("dockerd args: %w", err) - } - - log.Debug(ctx, "starting dockerd", slog.F("args", args)) - - dockerd := background.New(ctx, log, "dockerd", dargs...) - err = dockerd.Start() - if err != nil { - return xerrors.Errorf("start dockerd: %w", err) - } - - log.Debug(ctx, "waiting for manager") - - err = sysboxutil.WaitForManager(ctx) - if err != nil { - return xerrors.Errorf("wait for sysbox-mgr: %w", err) - } - - client, err := dockerutil.Client(ctx) - if err != nil { - return xerrors.Errorf("new docker client: %w", err) - } - - go func() { - err := <-dockerd.Wait() - // It's possible the for the docker daemon to run out of disk - // while trying to startup, in such cases we should restart - // it and point it to an ephemeral directory. Since this - // directory is going to be on top of an overlayfs filesystem - // we have to use the vfs storage driver. - if xunix.IsNoSpaceErr(err) { - args, err = dockerdArgs(ctx, log, flags.ethlink, cidr, true) - if err != nil { - blog.Info("Failed to create Container-based Virtual Machine: " + err.Error()) - //nolint - log.Fatal(ctx, "dockerd exited, failed getting args for restart", slog.Error(err)) - } - - err = dockerd.Restart(ctx, "dockerd", args...) - if err != nil { - blog.Info("Failed to create Container-based Virtual Machine: " + err.Error()) - //nolint - log.Fatal(ctx, "restart dockerd", slog.Error(err)) - } - - err = <-dockerd.Wait() - } - - // It's possible lower down in the call stack to restart - // the docker daemon if we run out of disk while starting the - // container. - if err != nil && !xerrors.Is(err, background.ErrUserKilled) { - blog.Info("Failed to create Container-based Virtual Machine: " + err.Error()) - //nolint - log.Fatal(ctx, "dockerd exited", slog.Error(err)) - } - }() - - log.Debug(ctx, "waiting for dockerd") - - // We wait for the daemon after spawning the goroutine in case - // startup causes the daemon to encounter encounter a 'no space left - // on device' error. - err = dockerutil.WaitForDaemon(ctx, client) - if err != nil { - return xerrors.Errorf("wait for dockerd: %w", err) - } - - err = runDockerCVM(ctx, log, client, flags) - if err != nil { - // It's possible we failed because we ran out of disk while - // pulling the image. We should restart the daemon and use - // the vfs storage driver to try to get the container up so that - // a user can access their workspace and try to delete whatever - // is causing their disk to fill up. - if xunix.IsNoSpaceErr(err) { - blog.Info("Insufficient space to start inner container. Restarting dockerd using the vfs driver. Your performance will be degraded. Clean up your home volume and then restart the workspace to improve performance.") - log.Debug(ctx, "encountered 'no space left on device' error while starting workspace", slog.Error(err)) - args, err := dockerdArgs(ctx, log, flags.ethlink, cidr, true) - if err != nil { - return xerrors.Errorf("dockerd args for restart: %w", err) - } - - log.Debug(ctx, "restarting dockerd", slog.F("args", args)) - - err = dockerd.Restart(ctx, "dockerd", args...) - if err != nil { - return xerrors.Errorf("restart dockerd: %w", err) - } - go func() { - err = <-dockerd.Wait() - blog.Errorf("restarted dockerd exited: %v", err) - //nolint - log.Fatal(ctx, "restarted dockerd exited", slog.Error(err)) - }() - - log.Debug(ctx, "reattempting container creation") - err = runDockerCVM(ctx, log, client, flags) - } - if err != nil { - blog.Errorf("Failed to run envbox: %v", err) - return xerrors.Errorf("run: %w", err) - } - } - - return nil - }, - } - - // Required flags. - cliflag.StringVarP(cmd.Flags(), &flags.innerImage, "image", "", EnvInnerImage, "", "The image for the inner container. Required.") - cliflag.StringVarP(cmd.Flags(), &flags.innerUsername, "username", "", EnvInnerUsername, "", "The username to use for the inner container. Required.") - cliflag.StringVarP(cmd.Flags(), &flags.agentToken, "agent-token", "", EnvAgentToken, "", "The token to be used by the workspace agent to estabish a connection with the control plane. Required.") - cliflag.StringVarP(cmd.Flags(), &flags.coderURL, "coder-url", "", EnvAgentURL, "", "The URL of the Coder deployement.") - - // Optional flags. - cliflag.StringVarP(cmd.Flags(), &flags.innerEnvs, "envs", "", EnvInnerEnvs, "", "Comma separated list of envs to add to the inner container.") - cliflag.StringVarP(cmd.Flags(), &flags.innerWorkDir, "work-dir", "", EnvInnerWorkDir, "", "The working directory of the inner container.") - cliflag.StringVarP(cmd.Flags(), &flags.innerHostname, "hostname", "", EnvInnerHostname, "", "The hostname to use for the inner container.") - cliflag.StringVarP(cmd.Flags(), &flags.imagePullSecret, "image-secret", "", EnvBoxPullImageSecretEnvVar, "", fmt.Sprintf("The secret to use to pull the image. It is highly encouraged to provide this via the %s environment variable.", EnvBoxPullImageSecretEnvVar)) - cliflag.StringVarP(cmd.Flags(), &flags.dockerdBridgeCIDR, "bridge-cidr", "", EnvBridgeCIDR, "", "The CIDR to use for the docker bridge.") - cliflag.StringVarP(cmd.Flags(), &flags.boostrapScript, "boostrap-script", "", EnvBootstrap, "", "The script to use to bootstrap the container. This should typically install and start the agent.") - cliflag.StringVarP(cmd.Flags(), &flags.containerMounts, "mounts", "", EnvMounts, "", "Comma separated list of mounts in the form of ':[:options]' (e.g. /var/lib/docker:/var/lib/docker:ro,/usr/src:/usr/src).") - cliflag.StringVarP(cmd.Flags(), &flags.hostUsrLibDir, "usr-lib-dir", "", EnvUsrLibDir, "", "The host /usr/lib mountpoint. Used to detect GPU drivers to mount into inner container.") - cliflag.BoolVarP(cmd.Flags(), &flags.addTUN, "add-tun", "", EnvAddTun, false, "Add a TUN device to the inner container.") - cliflag.BoolVarP(cmd.Flags(), &flags.addFUSE, "add-fuse", "", EnvAddFuse, false, "Add a FUSE device to the inner container.") - cliflag.BoolVarP(cmd.Flags(), &flags.addGPU, "add-gpu", "", EnvAddGPU, false, "Add detected GPUs to the inner container.") - cliflag.IntVarP(cmd.Flags(), &flags.cpus, "cpus", "", EnvCPUs, 0, "Number of CPUs to allocate inner container. e.g. 2") - cliflag.IntVarP(cmd.Flags(), &flags.memory, "memory", "", EnvMemory, 0, "Max memory to allocate to the inner container in bytes.") - - // Test flags. - cliflag.BoolVarP(cmd.Flags(), &flags.noStartupLogs, "no-startup-log", "", "", false, "Do not log startup logs. Useful for testing.") - cliflag.StringVarP(cmd.Flags(), &flags.ethlink, "ethlink", "", "", defaultNetLink, "The ethernet link to query for the MTU that is passed to docerd. Used for tests.") - - return cmd -} - -func runDockerCVM(ctx context.Context, log slog.Logger, client dockerutil.DockerClient, flags flags) error { - var ( - fs = xunix.GetFS(ctx) - blog = buildlog.GetLogger(ctx) - ) - - // Set our OOM score to something really unfavorable to avoid getting killed - // in memory-scarce scenarios. - err := xunix.SetOOMScore(ctx, "self", "-1000") - if err != nil { - return xerrors.Errorf("set oom score: %w", err) - } - - var dockerAuth dockerutil.AuthConfig - if flags.imagePullSecret != "" { - dockerAuth, err = dockerutil.ParseAuthConfig(flags.imagePullSecret) - if err != nil { - return xerrors.Errorf("parse auth config: %w", err) - } - } - - envs := defaultContainerEnvs(flags.agentToken) - - innerEnvsTokens := strings.Split(flags.innerEnvs, ",") - envs = append(envs, filterElements(xunix.Environ(ctx), innerEnvsTokens...)...) - - mounts := defaultMounts() - // Add any user-specified mounts to our mounts list. - extraMounts, err := parseMounts(flags.containerMounts) - if err != nil { - return xerrors.Errorf("read mounts: %w", err) - } - mounts = append(mounts, extraMounts...) - - log.Debug(ctx, "using mounts", slog.F("mounts", mounts)) - - devices := make([]container.DeviceMapping, 0, 2) - if flags.addTUN { - log.Debug(ctx, "creating TUN device", slog.F("path", OuterTUNPath)) - blog.Info("Creating TUN device") - dev, err := xunix.CreateTUNDevice(ctx, OuterTUNPath) - if err != nil { - return xerrors.Errorf("creat tun device: %w", err) - } - - devices = append(devices, container.DeviceMapping{ - PathOnHost: dev.Path, - PathInContainer: InnerTUNPath, - CgroupPermissions: "rwm", - }) - } - - if flags.addFUSE { - log.Debug(ctx, "creating FUSE device", slog.F("path", OuterFUSEPath)) - blog.Info("Creating FUSE device") - dev, err := xunix.CreateFuseDevice(ctx, OuterFUSEPath) - if err != nil { - return xerrors.Errorf("create fuse device: %w", err) - } - - devices = append(devices, container.DeviceMapping{ - PathOnHost: dev.Path, - PathInContainer: InnerFUSEPath, - CgroupPermissions: "rwm", - }) - } - - log.Debug(ctx, "using devices", slog.F("devices", devices)) - - // ID shift the devices so that they reflect the root user - // inside the container. - for _, device := range devices { - log.Debug(ctx, "chowning device", - slog.F("device", device.PathOnHost), - slog.F("uid", UserNamespaceOffset), - slog.F("gid", UserNamespaceOffset), - ) - err = fs.Chown(device.PathOnHost, UserNamespaceOffset, UserNamespaceOffset) - if err != nil { - return xerrors.Errorf("chown device %q: %w", device.PathOnHost, err) - } - } - - log.Debug(ctx, "pulling image", slog.F("image", flags.innerImage)) - - err = dockerutil.PullImage(ctx, &dockerutil.PullImageConfig{ - Client: client, - Image: flags.innerImage, - Auth: dockerAuth, - ProgressFn: dockerutil.DefaultLogImagePullFn(blog), - }) - if err != nil { - return xerrors.Errorf("pull image: %w", err) - } - - log.Debug(ctx, "remounting /sys") - - // After image pull we remount /sys so sysbox can have appropriate perms to create a container. - err = xunix.MountFS(ctx, "/sys", "/sys", "", "remount", "rw") - if err != nil { - return xerrors.Errorf("remount /sys: %w", err) - } - - if flags.addGPU { - if flags.hostUsrLibDir == "" { - return xerrors.Errorf("when using GPUs, %q must be specified", EnvUsrLibDir) - } - // Unmount GPU drivers in /proc as it causes issues when creating any - // container in some cases (even the image metadata container). - _, err = xunix.TryUnmountProcGPUDrivers(ctx, log) - if err != nil { - return xerrors.Errorf("unmount /proc GPU drivers: %w", err) - } - - devs, binds, err := xunix.GPUs(ctx, log, flags.hostUsrLibDir) - if err != nil { - return xerrors.Errorf("find gpus: %w", err) - } - - for _, dev := range devs { - devices = append(devices, container.DeviceMapping{ - PathOnHost: dev.Path, - PathInContainer: dev.Path, - CgroupPermissions: "rwm", - }) - } - - for _, bind := range binds { - // If the bind has a path that points to the host-mounted /usr/lib - // directory we need to remap it to /usr/lib inside the container. - mountpoint := bind.Path - if strings.HasPrefix(mountpoint, flags.hostUsrLibDir) { - mountpoint = filepath.Join( - "/usr/lib", - strings.TrimPrefix(mountpoint, strings.TrimSuffix(flags.hostUsrLibDir, "/")), - ) - } - mounts = append(mounts, xunix.Mount{ - Source: bind.Path, - Mountpoint: mountpoint, - ReadOnly: slices.Contains(bind.Opts, "ro"), - }) - } - envs = append(envs, xunix.GPUEnvs(ctx)...) - } - - log.Debug(ctx, "fetching image metadata", - slog.F("image", flags.innerImage), - slog.F("username", flags.innerUsername), - ) - - blog.Info("Getting image metadata...") - // Get metadata about the image. We need to know things like the UID/GID - // of the user so that we can chown directories to the namespaced UID inside - // the inner container as well as whether we should be starting the container - // with /sbin/init or something simple like 'sleep infinity'. - imgMeta, err := dockerutil.GetImageMetadata(ctx, client, flags.innerImage, flags.innerUsername) - if err != nil { - return xerrors.Errorf("get image metadata: %w", err) - } - - blog.Infof("Detected entrypoint user '%s:%s' with home directory %q", imgMeta.UID, imgMeta.UID, imgMeta.HomeDir) - - log.Debug(ctx, "fetched image metadata", - slog.F("uid", imgMeta.UID), - slog.F("gid", imgMeta.GID), - slog.F("has_init", imgMeta.HasInit), - ) - - uid, err := strconv.ParseInt(imgMeta.UID, 10, 32) - if err != nil { - return xerrors.Errorf("parse image uid: %w", err) - } - gid, err := strconv.ParseInt(imgMeta.GID, 10, 32) - if err != nil { - return xerrors.Errorf("parse image gid: %w", err) - } - - for _, m := range mounts { - // Don't modify anything private to envbox. - if isPrivateMount(m) { - continue - } - - log.Debug(ctx, "chmod'ing directory", - slog.F("path", m.Source), - slog.F("mode", "02755"), - ) - - // If a mount is read-only we have to remount it rw so that we - // can id shift it correctly. We'll still mount it read-only into - // the inner container. - if m.ReadOnly { - mounter := xunix.Mounter(ctx) - err := mounter.Mount("", m.Source, "", []string{"remount,rw"}) - if err != nil { - return xerrors.Errorf("remount: %w", err) - } - } - - err := fs.Chmod(m.Source, 0o2755) - if err != nil { - return xerrors.Errorf("chmod mountpoint %q: %w", m.Source, err) - } - - var ( - shiftedUID = shiftedID(0) - shiftedGID = shiftedID(0) - ) - - if isHomeDir(m.Source) { - // We want to ensure that the inner directory is ID shifted to - // the namespaced UID of the user in the inner container otherwise - // they won't be able to write files. - shiftedUID = shiftedID(int(uid)) - shiftedGID = shiftedID(int(gid)) - } - - log.Debug(ctx, "chowning mount", - slog.F("source", m.Source), - slog.F("target", m.Mountpoint), - slog.F("uid", shiftedUID), - slog.F("gid", shiftedGID), - ) - - // Any non-home directory we assume should be owned by id-shifted root - // user. - err = fs.Chown(m.Source, shiftedUID, shiftedGID) - if err != nil { - return xerrors.Errorf("chown mountpoint %q: %w", m.Source, err) - } - } - - blog.Info("Creating workspace...") - - // Create the inner container. - containerID, err := dockerutil.CreateContainer(ctx, client, &dockerutil.ContainerConfig{ - Log: log, - Mounts: mounts, - Devices: devices, - Envs: envs, - Name: InnerContainerName, - WorkingDir: flags.innerWorkDir, - HasInit: imgMeta.HasInit, - Image: flags.innerImage, - CPUs: int64(flags.cpus), - MemoryLimit: int64(flags.memory), - }) - if err != nil { - return xerrors.Errorf("create container: %w", err) - } - - blog.Info("Pruning images to free up disk...") - // Prune images to avoid taking up any unnecessary disk from the user. - _, err = dockerutil.PruneImages(ctx, client) - if err != nil { - return xerrors.Errorf("prune images: %w", err) - } - - // TODO fix iptables when istio detected. - - blog.Info("Starting up workspace...") - err = client.ContainerStart(ctx, containerID, dockertypes.ContainerStartOptions{}) - if err != nil { - if err != nil { - return xerrors.Errorf("start container: %w", err) - } - } - - log.Debug(ctx, "creating bootstrap directory", slog.F("directory", imgMeta.HomeDir)) - - // Create the directory to which we will download the agent. - bootDir := filepath.Join(imgMeta.HomeDir, ".coder") - - blog.Infof("Creating %q directory to host Coder assets...", bootDir) - _, err = dockerutil.ExecContainer(ctx, client, dockerutil.ExecConfig{ - ContainerID: containerID, - User: imgMeta.UID, - Cmd: "mkdir", - Args: []string{"-p", bootDir}, - }) - if err != nil { - return xerrors.Errorf("make bootstrap dir: %w", err) - } - - cpuQuota, err := xunix.ReadCPUQuota(ctx) - if err != nil { - return xerrors.Errorf("read CPU quota: %w", err) - } - - log.Debug(ctx, "setting CPU quota", - slog.F("quota", cpuQuota.Quota), - slog.F("period", cpuQuota.Period), - ) - - // We want the inner container to have the same limits as the outer container - // so that processes inside the container know what they're working with. - err = dockerutil.SetContainerCPUQuota(ctx, containerID, cpuQuota.Quota, cpuQuota.Period) - if err != nil { - return xerrors.Errorf("set inner container CPU quota: %w", err) - } - - blog.Info("Envbox startup complete!") - - // Bootstrap the container if a script has been provided. - blog.Infof("Bootstrapping workspace...") - err = dockerutil.BootstrapContainer(ctx, client, dockerutil.BootstrapConfig{ - ContainerID: containerID, - User: imgMeta.UID, - Script: flags.boostrapScript, - // We set this because the default behavior is to download the agent - // to /tmp/coder.XXXX. This causes a race to happen where we finish - // downloading the binary but before we can execute systemd remounts - // /tmp. - Env: []string{fmt.Sprintf("BINARY_DIR=%s", bootDir)}, - }) - if err != nil { - return xerrors.Errorf("boostrap container: %w", err) - } - - return nil -} - -// nolint -func dockerdArgs(ctx context.Context, log slog.Logger, link, cidr string, isNoSpace bool) ([]string, error) { - // We need to adjust the MTU for the host otherwise packets will fail delivery. - // 1500 is the standard, but certain deployments (like GKE) use custom MTU values. - // See: https://www.atlantis-press.com/journals/ijndc/125936177/view#sec-s3.1 - - mtu, err := xunix.NetlinkMTU(link) - if err != nil { - return nil, xerrors.Errorf("custom mtu: %w", err) - } - - // We set the Docker Bridge IP explicitly here for a number of reasons: - // 1) It sometimes picks the 172.17.x.x address which conflicts with that of the Docker daemon in the inner container. - // 2) It defaults to a /16 network which is way more than we need for envbox. - // 3) The default may conflict with existing internal network resources, and an operator may wish to override it. - dockerBip, prefixLen := dockerutil.BridgeIPFromCIDR(cidr) - - args := []string{ - "--debug", - "--log-level=debug", - fmt.Sprintf("--mtu=%d", mtu), - "--userns-remap=coder", - "--storage-driver=overlay2", - fmt.Sprintf("--bip=%s/%d", dockerBip, prefixLen), - } - - if isNoSpace { - args = append(args, - fmt.Sprintf("--data-root=%s", noSpaceDataDir), - fmt.Sprintf("--storage-driver=%s", noSpaceDockerDriver), - ) - } - - return args, nil -} - -// TODO This is bad code. -func filterElements(ss []string, filters ...string) []string { - filtered := make([]string, 0, len(ss)) - for _, f := range filters { - for _, s := range ss { - filter := f - if strings.HasSuffix(filter, "*") { - filter = strings.TrimSuffix(filter, "*") - if strings.HasPrefix(s, filter) { - filtered = append(filtered, s) - } - } else if s == filter { - filtered = append(filtered, s) - } - } - } - - return filtered -} - -// parseMounts parses a list of mounts from containerMounts. The format should -// be "src:dst[:ro],src:dst[:ro]". -func parseMounts(containerMounts string) ([]xunix.Mount, error) { - if containerMounts == "" { - return nil, nil - } - - mountsStr := strings.Split(containerMounts, ",") - - mounts := make([]xunix.Mount, 0, len(mountsStr)) - for _, mount := range mountsStr { - tokens := strings.Split(mount, ":") - if len(tokens) < 2 { - return nil, xerrors.Errorf("malformed mounts value %q", containerMounts) - } - m := xunix.Mount{ - Source: tokens[0], - Mountpoint: tokens[1], - } - if len(tokens) == 3 { - m.ReadOnly = tokens[2] == "ro" - } - mounts = append(mounts, m) - } - - return mounts, nil -} - -// defaultContainerEnvs returns environment variables that should always -// be passed to the inner container. -func defaultContainerEnvs(agentToken string) []string { - return []string{fmt.Sprintf("%s=%s", EnvAgentToken, agentToken)} -} - -// defaultMounts are bind mounts that are always provided to the inner -// container. -func defaultMounts() []xunix.Mount { - return []xunix.Mount{ - { - Source: "/var/lib/coder/docker", - Mountpoint: "/var/lib/docker", - }, - { - Source: "/var/lib/coder/containers", - Mountpoint: "/var/lib/containers", - }, - } -} - -// isPrivateMount returns true if the provided mount points to a mount -// private to the envbox container itself. -func isPrivateMount(m xunix.Mount) bool { - _, ok := envboxPrivateMounts[m.Mountpoint] - return ok -} - -func isHomeDir(fpath string) bool { - if fpath == "/root" { - return true - } - - dir, _ := path.Split(fpath) - return dir == "/home/" -} - -// shiftedID returns the ID but shifted to the user namespace offset we -// use for the inner container. -func shiftedID(id int) int { - return id + UserNamespaceOffset -} diff --git a/cli/docker_test.go b/cli/docker_test.go deleted file mode 100644 index 760f9a4..0000000 --- a/cli/docker_test.go +++ /dev/null @@ -1,580 +0,0 @@ -package cli_test - -import ( - "bufio" - "bytes" - "context" - "encoding/base64" - "fmt" - "io" - "net" - "os" - "path/filepath" - "strings" - "testing" - - dockertypes "github.com/docker/docker/api/types" - "github.com/docker/docker/api/types/container" - "github.com/docker/docker/api/types/network" - v1 "github.com/opencontainers/image-spec/specs-go/v1" - "github.com/spf13/afero" - "github.com/stretchr/testify/require" - "k8s.io/mount-utils" - testingexec "k8s.io/utils/exec/testing" - - "github.com/coder/envbox/cli" - "github.com/coder/envbox/cli/clitest" - "github.com/coder/envbox/dockerutil" - "github.com/coder/envbox/xunix" - "github.com/coder/envbox/xunix/xunixfake" -) - -func TestDocker(t *testing.T) { - t.Parallel() - - // Test the basic use case. This test doesn't test much beyond - // establishing that the framework is returning a default - // successful test. This makes it easier for individual tests - // to test various criteria of the command without extensive - // setup. - t.Run("OK", func(t *testing.T) { - t.Parallel() - - ctx, cmd := clitest.New(t, "docker", - "--image=ubuntu", - "--username=root", - "--agent-token=hi", - ) - - err := cmd.ExecuteContext(ctx) - require.NoError(t, err) - }) - - // Test that dockerd is configured correctly. - t.Run("DockerdConfigured", func(t *testing.T) { - t.Parallel() - - var ( - nl = clitest.GetNetLink(t) - bridgeCIDR = "172.31.0.129/30" - ) - - ctx, cmd := clitest.New(t, "docker", - "--image=ubuntu", - "--username=root", - "--agent-token=hi", - fmt.Sprintf("--bridge-cidr=%s", bridgeCIDR), - ) - - execer := clitest.Execer(ctx) - execer.AddCommands(&xunixfake.FakeCmd{ - FakeCmd: &testingexec.FakeCmd{ - Argv: []string{ - "dockerd", - "--debug", - "--log-level=debug", - fmt.Sprintf("--mtu=%d", nl.Attrs().MTU), - "--userns-remap=coder", - "--storage-driver=overlay2", - fmt.Sprintf("--bip=%s", bridgeCIDR), - }, - }, - }) - - err := cmd.ExecuteContext(ctx) - require.NoError(t, err) - execer.AssertCommandsCalled(t) - }) - - // Test that the oom_score_adj of the envbox - // process is set to an extremely undesirable - // number for the OOM killer. - t.Run("SetOOMScore", func(t *testing.T) { - t.Parallel() - - ctx, cmd := clitest.New(t, "docker", - "--image=ubuntu", - "--username=root", - "--agent-token=hi", - ) - - fs := clitest.FS(ctx) - - err := cmd.ExecuteContext(ctx) - require.NoError(t, err) - - score, err := afero.ReadFile(fs, "/proc/self/oom_score_adj") - require.NoError(t, err) - require.Equal(t, []byte("-1000"), score) - }) - - // Test that user-provided env vars are passed through. - // It is valid to specify a wildcard so that all matching - // env vars are passed through. - t.Run("PassesThroughEnvVars", func(t *testing.T) { - t.Parallel() - var ( - cntEnvs = []string{ - "FOO=bar", - "CODER_VAR=baz", - "bar=123", - // Test that wildcard works. - "KUBERNETES_*", - "US_*", - } - - osEnvs = append([]string{ - "USER=root", - "USA=yay", - "HOME=/root", - "PATH=/usr/bin:/sbin:/bin", - "KUBERNETES_SERVICE_HOST=10.0.0.1", - "KUBERNETES_PORT=tcp://10.0.0.1:443", - "KUBERNETES_PORT_443_TCP_PORT=443", - // Don't include the wildcards. - }, cntEnvs[:3]...) - - expectedEnvs = []string{ - "CODER_AGENT_TOKEN=hi", - "FOO=bar", - "CODER_VAR=baz", - "bar=123", - "KUBERNETES_SERVICE_HOST=10.0.0.1", - "KUBERNETES_PORT=tcp://10.0.0.1:443", - "KUBERNETES_PORT_443_TCP_PORT=443", - } - ) - - ctx, cmd := clitest.New(t, "docker", - "docker", - "--image=ubuntu", - "--username=root", - "--agent-token=hi", - fmt.Sprintf("--envs=%s", strings.Join(cntEnvs, ",")), - ) - - ctx = xunix.WithEnvironFn(ctx, func() []string { return osEnvs }) - - client := clitest.DockerClient(t, ctx) - var called bool - client.ContainerCreateFn = func(_ context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, _ *v1.Platform, containerName string) (container.ContainerCreateCreatedBody, error) { - if containerName == cli.InnerContainerName { - called = true - require.Equal(t, expectedEnvs, config.Env) - } - return container.ContainerCreateCreatedBody{}, nil - } - - err := cmd.ExecuteContext(ctx) - require.NoError(t, err) - require.True(t, called, "create function was not called") - }) - - // Test that we parse mounts correctly. - t.Run("Mounts", func(t *testing.T) { - t.Parallel() - - var ( - userMounts = []string{"/home/coder:/home/coder", "/etc/hosts:/etc/hosts:ro", "/etc/hostname:/idc/where:ro", "/usr/src:/a/b/c"} - expectedMounts = append([]string{"/var/lib/coder/docker:/var/lib/docker", "/var/lib/coder/containers:/var/lib/containers"}, userMounts...) - ) - ctx, cmd := clitest.New(t, "docker", - "docker", - "--image=ubuntu", - "--username=root", - "--agent-token=hi", - fmt.Sprintf("--mounts=%s", strings.Join(userMounts, ",")), - ) - - var ( - client = clitest.DockerClient(t, ctx) - fs = clitest.FS(ctx) - ) - - for _, mount := range userMounts { - src := strings.Split(mount, ":")[0] - - err := afero.WriteFile(fs, src, []byte("hi"), 0o777) - require.NoError(t, err) - } - - // Set the exec response from inspecting the image to some ID - // greater than 0. - client.ContainerExecAttachFn = func(_ context.Context, execID string, config dockertypes.ExecStartCheck) (dockertypes.HijackedResponse, error) { - return dockertypes.HijackedResponse{ - Reader: bufio.NewReader(strings.NewReader("root:x:1001:1001:root:/root:/bin/bash")), - Conn: &net.IPConn{}, - }, nil - } - - var called bool - client.ContainerCreateFn = func(_ context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, _ *v1.Platform, containerName string) (container.ContainerCreateCreatedBody, error) { - if containerName == cli.InnerContainerName { - called = true - require.Equal(t, expectedMounts, hostConfig.Binds) - } - - return container.ContainerCreateCreatedBody{}, nil - } - - err := cmd.ExecuteContext(ctx) - require.NoError(t, err) - require.True(t, called, "container create fn not called") - - fi, err := fs.Stat("/home/coder") - require.NoError(t, err) - require.Equal(t, os.FileMode(0o755), fi.Mode().Perm()) - // Check that we're calling chown and shifting the ID. - owner, ok := fs.GetFileOwner("/home/coder") - require.True(t, ok) - require.Equal(t, cli.UserNamespaceOffset+1001, owner.UID) - require.Equal(t, cli.UserNamespaceOffset+1001, owner.GID) - }) - - // Test that we remount /sys once we pull the image so that - // sysbox can use it properly. - t.Run("RemountSysfs", func(t *testing.T) { - t.Parallel() - - ctx, cmd := clitest.New(t, "docker", - "--image=ubuntu", - "--username=root", - "--agent-token=hi", - ) - - mounter := clitest.Mounter(ctx) - - err := cmd.ExecuteContext(ctx) - require.NoError(t, err) - - actions := mounter.GetLog() - require.Len(t, actions, 1) - action := actions[0] - require.Equal(t, "mount", action.Action) - require.Equal(t, "", action.FSType) - require.Equal(t, "/sys", action.Source) - require.Equal(t, "/sys", action.Target) - }) - - // Test that devices are created and passed through to the docker - // daemon. - t.Run("Devices", func(t *testing.T) { - t.Parallel() - - ctx, cmd := clitest.New(t, "docker", - "docker", - "--image=ubuntu", - "--username=root", - "--agent-token=hi", - "--add-tun", - "--add-fuse", - ) - - var ( - client = clitest.DockerClient(t, ctx) - fs = clitest.FS(ctx) - expectedDevices = []container.DeviceMapping{ - { - PathOnHost: cli.OuterTUNPath, - PathInContainer: cli.InnerTUNPath, - CgroupPermissions: "rwm", - }, - { - PathOnHost: cli.OuterFUSEPath, - PathInContainer: cli.InnerFUSEPath, - CgroupPermissions: "rwm", - }, - } - ) - - var called bool - client.ContainerCreateFn = func(_ context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, _ *v1.Platform, containerName string) (container.ContainerCreateCreatedBody, error) { - if containerName == cli.InnerContainerName { - called = true - require.Equal(t, expectedDevices, hostConfig.Devices) - } - - return container.ContainerCreateCreatedBody{}, nil - } - - err := cmd.ExecuteContext(ctx) - require.NoError(t, err) - require.True(t, called, "container create fn not called") - - // Check that we're calling chown and shifting the ID to - // it maps to root of the inner container. - owner, ok := fs.GetFileOwner(cli.OuterFUSEPath) - require.True(t, ok) - require.Equal(t, cli.UserNamespaceOffset, owner.UID) - require.Equal(t, cli.UserNamespaceOffset, owner.GID) - - owner, ok = fs.GetFileOwner(cli.OuterTUNPath) - require.True(t, ok) - require.Equal(t, cli.UserNamespaceOffset, owner.UID) - require.Equal(t, cli.UserNamespaceOffset, owner.GID) - }) - - // Tests that 'sleep infinity' is used if /sbin/init - // isn't detected. - t.Run("NoInit", func(t *testing.T) { - t.Parallel() - - ctx, cmd := clitest.New(t, "docker", - "docker", - "--image=ubuntu", - "--username=root", - "--agent-token=hi", - "--add-tun", - "--add-fuse", - ) - - var ( - client = clitest.DockerClient(t, ctx) - statExecID = "hi" - ) - - client.ContainerExecCreateFn = func(_ context.Context, container string, config dockertypes.ExecConfig) (dockertypes.IDResponse, error) { - if config.Cmd[0] == "stat" { - return dockertypes.IDResponse{ - ID: statExecID, - }, nil - } - return dockertypes.IDResponse{}, nil - } - - // Set the exec response from inspecting the image to some ID - // greater than 0. - client.ContainerExecInspectFn = func(_ context.Context, execID string) (dockertypes.ContainerExecInspect, error) { - if execID == statExecID { - return dockertypes.ContainerExecInspect{ExitCode: 1}, nil - } - - return dockertypes.ContainerExecInspect{}, nil - } - - var called bool - client.ContainerCreateFn = func(_ context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, _ *v1.Platform, containerName string) (container.ContainerCreateCreatedBody, error) { - if containerName == cli.InnerContainerName { - called = true - require.Equal(t, []string{"sleep", "infinity"}, []string(config.Entrypoint)) - } - - return container.ContainerCreateCreatedBody{}, nil - } - - err := cmd.ExecuteContext(ctx) - require.NoError(t, err) - require.True(t, called, "container create fn not called") - }) - - t.Run("DockerAuth", func(t *testing.T) { - t.Parallel() - - ctx, cmd := clitest.New(t, "docker", - "--image=ubuntu", - "--username=root", - "--agent-token=hi", - fmt.Sprintf("--image-secret=%s", rawDockerAuth), - ) - - raw := []byte(`{"username":"_json_key","password":"{\"type\": \"service_account\", \"project_id\": \"some-test\", \"private_key_id\": \"blahblah\", \"private_key\": \"-----BEGIN PRIVATE KEY-----mykey-----END PRIVATE KEY-----\", \"client_email\": \"test@test.iam.gserviceaccount.com\", \"client_id\": \"123\", \"auth_uri\": \"https://accounts.google.com/o/oauth2/auth\", \"token_uri\": \"https://oauth2.googleapis.com/token\", \"auth_provider_x509_cert_url\": \"https://www.googleapis.com/oauth2/v1/certs\", \"client_x509_cert_url\": \"https://www.googleapis.com/robot/v1/metadata/x509/test.iam.gserviceaccount.com\" }","auth":"X2pzb25fa2V5OnsKCgkidHlwZSI6ICJzZXJ2aWNlX2FjY291bnQiLAoJInByb2plY3RfaWQiOiAic29tZS10ZXN0IiwKCSJwcml2YXRlX2tleV9pZCI6ICJibGFoYmxhaCIsCgkicHJpdmF0ZV9rZXkiOiAiLS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCm15a2V5LS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLQoiLAoJImNsaWVudF9lbWFpbCI6ICJ0ZXN0QHRlc3QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLAoJImNsaWVudF9pZCI6ICIxMjMiLAoJImF1dGhfdXJpIjogImh0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbS9vL29hdXRoMi9hdXRoIiwKCSJ0b2tlbl91cmkiOiAiaHR0cHM6Ly9vYXV0aDIuZ29vZ2xlYXBpcy5jb20vdG9rZW4iLAoJImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6ICJodHRwczovL3d3dy5nb29nbGVhcGlzLmNvbS9vYXV0aDIvdjEvY2VydHMiLAoJImNsaWVudF94NTA5X2NlcnRfdXJsIjogImh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL3JvYm90L3YxL21ldGFkYXRhL3g1MDkvdGVzdC5pYW0uZ3NlcnZpY2VhY2NvdW50LmNvbSIKfQo=","email":"test@test.iam.gserviceaccount.com"}`) - authB64 := base64.StdEncoding.EncodeToString(raw) - - client := clitest.DockerClient(t, ctx) - client.ImagePullFn = func(_ context.Context, ref string, options dockertypes.ImagePullOptions) (io.ReadCloser, error) { - // Assert that we call the image pull function with the credentials. - require.Equal(t, authB64, options.RegistryAuth) - return io.NopCloser(bytes.NewReader(nil)), nil - } - - err := cmd.ExecuteContext(ctx) - require.NoError(t, err) - }) - - t.Run("SetsResources", func(t *testing.T) { - t.Parallel() - - const ( - // 4GB. - memory = 4 << 30 - cpus = 6 - ) - - ctx, cmd := clitest.New(t, "docker", - "--image=ubuntu", - "--username=root", - "--agent-token=hi", - fmt.Sprintf("--cpus=%d", cpus), - fmt.Sprintf("--memory=%d", memory), - ) - - var called bool - client := clitest.DockerClient(t, ctx) - client.ContainerCreateFn = func(_ context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, _ *v1.Platform, containerName string) (container.ContainerCreateCreatedBody, error) { - if containerName == cli.InnerContainerName { - called = true - require.Equal(t, int64(memory), hostConfig.Memory) - require.Equal(t, int64(cpus*dockerutil.DefaultCPUPeriod), hostConfig.CPUQuota) - require.Equal(t, int64(dockerutil.DefaultCPUPeriod), hostConfig.CPUPeriod) - } - - return container.ContainerCreateCreatedBody{}, nil - } - - err := cmd.ExecuteContext(ctx) - require.NoError(t, err) - require.True(t, called, "create function was not called for inner container") - }) - - t.Run("GPUNoUsrLibDir", func(t *testing.T) { - t.Parallel() - - ctx, cmd := clitest.New(t, "docker", - "--image=ubuntu", - "--username=root", - "--agent-token=hi", - "--add-gpu=true", - ) - - err := cmd.ExecuteContext(ctx) - require.Error(t, err) - require.ErrorContains(t, err, fmt.Sprintf("when using GPUs, %q must be specified", cli.EnvUsrLibDir)) - }) - - t.Run("GPU", func(t *testing.T) { - t.Parallel() - - ctx, cmd := clitest.New(t, "docker", - "--image=ubuntu", - "--username=root", - "--agent-token=hi", - "--add-gpu=true", - "--usr-lib-dir=/var/coder/usr/lib", - ) - - var ( - mounter = clitest.Mounter(ctx) - afs = clitest.FS(ctx) - - procGPUDrivers = []string{ - "/proc/vulkan/foo", - "/proc/nvidia/bar", - "/proc/cuda/baz", - } - - // This path intentionally has a trailing '/' to ensure we are - // trimming correctly when remapping host-mounted /usr/lib dirs to - // /usr/lib inside the container. - usrLibMountpoint = "/var/coder/usr/lib/" - // expectedUsrLibFiles are files that we expect to be returned as bind mounts. - expectedUsrLibFiles = []string{ - filepath.Join(usrLibMountpoint, "nvidia", "libglxserver_nvidia.so"), - filepath.Join(usrLibMountpoint, "libnvidia-ml.so"), - } - expectedEnvs = []string{ - "NVIDIA_TEST=1", - "TEST_NVIDIA=1", - "nvidia_test=1", - } - ) - - environ := func() []string { - return append( - []string{ - "LIBGL_TEST=1", - "VULKAN_TEST=1", - }, expectedEnvs...) - } - - ctx = xunix.WithEnvironFn(ctx, environ) - - // Fake all the files. - for _, file := range append(expectedUsrLibFiles, procGPUDrivers...) { - _, err := afs.Create(file) - require.NoError(t, err) - } - - mounter.MountPoints = []mount.MountPoint{ - { - Device: "/dev/sda1", - Path: "/usr/local/nvidia", - Opts: []string{"rw"}, - }, - { - Device: "/dev/sda2", - Path: "/etc/hosts", - }, - { - Path: "/dev/nvidia0", - }, - { - Path: "/dev/nvidia1", - }, - } - - for _, driver := range procGPUDrivers { - mounter.MountPoints = append(mounter.MountPoints, mount.MountPoint{ - Path: driver, - }) - } - - _, err := afs.Create("/usr/local/nvidia") - require.NoError(t, err) - - unmounts := []string{} - mounter.UnmountFunc = func(path string) error { - unmounts = append(unmounts, path) - return nil - } - - var called bool - client := clitest.DockerClient(t, ctx) - client.ContainerCreateFn = func(_ context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, _ *v1.Platform, containerName string) (container.ContainerCreateCreatedBody, error) { - if containerName == cli.InnerContainerName { - called = true - // Test that '/dev' mounts are passed as devices. - require.Contains(t, hostConfig.Devices, container.DeviceMapping{ - PathOnHost: "/dev/nvidia0", - PathInContainer: "/dev/nvidia0", - CgroupPermissions: "rwm", - }) - require.Contains(t, hostConfig.Devices, container.DeviceMapping{ - PathOnHost: "/dev/nvidia1", - PathInContainer: "/dev/nvidia1", - CgroupPermissions: "rwm", - }) - - // Test that the mountpoint that we provided that is not under - // '/dev' is passed as a bind mount. - require.Contains(t, hostConfig.Binds, fmt.Sprintf("%s:%s", "/usr/local/nvidia", "/usr/local/nvidia")) - - // Test that host /usr/lib bind mounts were passed through as read-only. - for _, file := range expectedUsrLibFiles { - require.Contains(t, hostConfig.Binds, fmt.Sprintf("%s:%s:ro", - file, - strings.Replace(file, usrLibMountpoint, "/usr/lib/", -1), - )) - } - - // Test that we captured the GPU-related env vars. - for _, env := range expectedEnvs { - require.Contains(t, config.Env, env) - } - } - - return container.ContainerCreateCreatedBody{}, nil - } - - err = cmd.ExecuteContext(ctx) - require.NoError(t, err) - require.True(t, called, "create function was not called for inner container") - // Assert that we unmounted /proc GPU drivers. - for _, driver := range procGPUDrivers { - require.Contains(t, unmounts, driver) - } - }) -} - -// rawDockerAuth is sample input for a kubernetes secret to a gcr.io private -// registry. -const rawDockerAuth = `{"auths":{"us.gcr.io":{"username":"_json_key","password":"{\"type\": \"service_account\", \"project_id\": \"some-test\", \"private_key_id\": \"blahblah\", \"private_key\": \"-----BEGIN PRIVATE KEY-----mykey-----END PRIVATE KEY-----\", \"client_email\": \"test@test.iam.gserviceaccount.com\", \"client_id\": \"123\", \"auth_uri\": \"https://accounts.google.com/o/oauth2/auth\", \"token_uri\": \"https://oauth2.googleapis.com/token\", \"auth_provider_x509_cert_url\": \"https://www.googleapis.com/oauth2/v1/certs\", \"client_x509_cert_url\": \"https://www.googleapis.com/robot/v1/metadata/x509/test.iam.gserviceaccount.com\" }","email":"test@test.iam.gserviceaccount.com","auth":"X2pzb25fa2V5OnsKCgkidHlwZSI6ICJzZXJ2aWNlX2FjY291bnQiLAoJInByb2plY3RfaWQiOiAic29tZS10ZXN0IiwKCSJwcml2YXRlX2tleV9pZCI6ICJibGFoYmxhaCIsCgkicHJpdmF0ZV9rZXkiOiAiLS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCm15a2V5LS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLQoiLAoJImNsaWVudF9lbWFpbCI6ICJ0ZXN0QHRlc3QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLAoJImNsaWVudF9pZCI6ICIxMjMiLAoJImF1dGhfdXJpIjogImh0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbS9vL29hdXRoMi9hdXRoIiwKCSJ0b2tlbl91cmkiOiAiaHR0cHM6Ly9vYXV0aDIuZ29vZ2xlYXBpcy5jb20vdG9rZW4iLAoJImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6ICJodHRwczovL3d3dy5nb29nbGVhcGlzLmNvbS9vYXV0aDIvdjEvY2VydHMiLAoJImNsaWVudF94NTA5X2NlcnRfdXJsIjogImh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL3JvYm90L3YxL21ldGFkYXRhL3g1MDkvdGVzdC5pYW0uZ3NlcnZpY2VhY2NvdW50LmNvbSIKfQo="}}}` diff --git a/cli/root.go b/cli/root.go deleted file mode 100644 index 0af9baf..0000000 --- a/cli/root.go +++ /dev/null @@ -1,22 +0,0 @@ -package cli - -import ( - "github.com/spf13/cobra" -) - -func Root() *cobra.Command { - cmd := &cobra.Command{ - Use: "envbox", - SilenceErrors: true, - SilenceUsage: true, - Args: func(cmd *cobra.Command, args []string) error { - return cobra.NoArgs(cmd, args) - }, - RunE: func(cmd *cobra.Command, args []string) error { - return cmd.Help() - }, - } - - cmd.AddCommand(dockerCmd()) - return cmd -} diff --git a/cmd/envbox/main.go b/cmd/envbox/main.go deleted file mode 100644 index 2dfdb94..0000000 --- a/cmd/envbox/main.go +++ /dev/null @@ -1,20 +0,0 @@ -package main - -import ( - "fmt" - "os" - "runtime" - - "github.com/coder/envbox/cli" -) - -func main() { - _, err := cli.Root().ExecuteC() - if err != nil { - _, _ = fmt.Fprintln(os.Stderr, err.Error()) - os.Exit(1) - } - - // We exit the main thread while keepin all the other procs goin strong. - runtime.Goexit() -} diff --git a/deploy/Dockerfile b/deploy/Dockerfile deleted file mode 100644 index c81d6be..0000000 --- a/deploy/Dockerfile +++ /dev/null @@ -1,71 +0,0 @@ -# Ubuntu 20.04 LTS (Focal Fossa) -FROM ubuntu:focal - -# Copy configuration files to appropriate locations -COPY files / - -LABEL \ - org.opencontainers.image.title="Envbox" \ - org.opencontainers.image.url="https://github.com/coder/envbox" \ - org.opencontainers.image.source="https://github.com/coder/envbox" \ - org.opencontainers.image.description="Run Docker in Docker in Kubernetes" - -# Basic utilities -ARG DEBIAN_FRONTEND=noninteractive -# Ignore other repositories, as some require HTTPS -RUN apt-get update --quiet --option Dir::Etc::SourceParts="" && \ - apt-get upgrade -y && \ - apt-get install --no-install-recommends --yes --quiet --option Dir::Etc::SourceParts="" \ - apt-transport-https \ - apt-utils \ - binutils \ - ca-certificates \ - curl \ - dialog \ - fuse \ - iproute2 \ - jq \ - kmod \ - lsb-release \ - make \ - mokutil \ - rsync \ - systemctl \ - wget \ - vim && \ - # Install packages from third-party repositories - apt-get update --quiet && \ - apt-get install --no-install-recommends --yes --quiet \ - containerd.io \ - docker-ce \ - docker-ce-cli && \ - # Delete package cache to avoid consuming space in layer - apt-get clean && \ - rm -rf /var/lib/apt/lists/* - -RUN wget https://downloads.nestybox.com/sysbox/releases/v0.5.2/sysbox-ce_0.5.2-0.linux_amd64.deb && \ - echo "f13fc0e156f72c6f8bd48e206c59482f83f19acc229701c74e0f23baafa724d8 sysbox-ce_0.5.2-0.linux_amd64.deb" | sha256sum --check --status && \ - apt install -y ./sysbox-ce_0.5.2-0.linux_amd64.deb && \ - rm ./sysbox-ce_0.5.2-0.linux_amd64.deb && \ - userdel -r sysbox - -# This is jank but sysbox adds their own /etc/docker/daemon.json that overwrites ours when it gets installed, -# so we copy over their changes to get the configurations we actually want. -COPY files / - -# Add coder user -RUN useradd coder \ - --create-home \ - --shell=/bin/bash \ - --groups=docker \ - --uid=1000 \ - --user-group && \ - usermod coder \ - --add-subuids 100000-165535 \ - --add-subgids 100000-165535 - -# Do this last so hotswapping is fast! -ARG ENVBOX_BIN=envbox -COPY $ENVBOX_BIN / - -CMD ["/envbox"] diff --git a/deploy/files/etc/apt/preferences.d/docker b/deploy/files/etc/apt/preferences.d/docker deleted file mode 100644 index a92c0ab..0000000 --- a/deploy/files/etc/apt/preferences.d/docker +++ /dev/null @@ -1,19 +0,0 @@ -# Ignore all packages from this repository by default -Package: * -Pin: origin download.docker.com -Pin-Priority: 1 - -# Docker Community Edition -Package: docker-ce -Pin: origin download.docker.com -Pin-Priority: 500 - -# Docker command-line tool -Package: docker-ce-cli -Pin: origin download.docker.com -Pin-Priority: 500 - -# containerd runtime -Package: containerd.io -Pin: origin download.docker.com -Pin-Priority: 500 diff --git a/deploy/files/etc/apt/sources.list.d/docker.list b/deploy/files/etc/apt/sources.list.d/docker.list deleted file mode 100644 index 256f965..0000000 --- a/deploy/files/etc/apt/sources.list.d/docker.list +++ /dev/null @@ -1 +0,0 @@ -deb [signed-by=/usr/share/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu focal stable diff --git a/deploy/files/etc/docker/daemon.json b/deploy/files/etc/docker/daemon.json deleted file mode 100644 index a86bbde..0000000 --- a/deploy/files/etc/docker/daemon.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "runtimes": { - "sysbox-runc": { - "path": "/usr/bin/sysbox-runc" - } - } -} diff --git a/deploy/files/usr/share/keyrings/docker.gpg b/deploy/files/usr/share/keyrings/docker.gpg deleted file mode 100644 index e5dc8cfda8e5d37f69956520048140c9baab9803..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2760 zcmV;(3ODtc0u2OMt=cL95CGv?mVEyU+3FP&iF2?(b<6@*g&o7k_7E+vfpyDoj$zjA zGV5WMs<5X`yaKG4`1D^?%Ti#*f9W@2In1 z#V#$cv(vuM$1G5W?m=#;?M(Cxek`gIB|ZeE>e*?4HA0Yo?Le89KO(!1UAgKnfVKJp ze7*UXLf?I!keb9u+BFqeeB``A$gwvu)M9q}dT8YU+=NzEb9$;fT&a6fycOmt+QBrl zSljK4NaNyiOYqwZ!pA8r^c00OKI|6ITnqr2;lfcg2)^}~s|^iuXkp-Z9zw?u9f%Gl zIKx%?805>Gz6o0*0IGj52V2W@R3^r4ggg+8qe2>{F;knjCB39B|n)&}Ia))TWmVOS1zJD$Q<&mo|g~V`#5B$6N zxLlw5L@k&9cvMyuB!wfYMH5Y?I18^yQU0Cn< zQ+Vm-4&d0rzki{yJhx4HVp!v=n%$Eu4}XG1@@3Rpmx4E2z!ZF5gVt7hXhF3JhQ)dC z^v|>E6|i%rp_>2^0RRECD@1Q&Yh`jEQe|vqVRL05C__acWMyJ0AUtGmV{2t{KxA)Y zYh`jSV{dIfi2^qS69EbUAq4_ht>?f38!rV52?z%R1r-Vj2nz)k0s{d60v-VZ7k~f? z2@s8efIJSr&4{we5B?+>qpu&7G$uCr{9l#Rccf8iLHFK8*j}rX=-CG)$dc?$piG&n zyvm)ljwUsM!bnCjBbuvmg?VD7{XegYqwDC-jwi9@5G?Wk0W>(My&0lUwT?!h+_)r; ziSkkZTf)_`7M(d9Eygf&;f2K#dl0cev@e`hmk( zZtk3Hs%->NGPyLrr#y%lgx{LEI^lyjO4KBwd}kap{2xYFqV-F2>Yq zG-gdq-7QDsOB?=ysoxG@7KH&vE_?hnRc?txWkz9<=VtFx@Ut8hfLi2;JwF@%ZMK$zRb;~8!vOdFX75Fk8*e>XpOrG|YsSZ2f#t_(HJ z+2iiq+kTKEd{!m%PjyDuMW8T;FZ!)Cg>O6x2SR3fyfZ=kBSDUz=aV8M^lA(&u0B2M z-aM5?LcHpf3Iqah6nv_W(wZrA8IAR4qXOaf%g7n?TNrw7a0Kc^OVl3Z8#a2R3m+9$ z8(5MM+x77e+YoN$TgPo5x1IH2GV6I8ege0YQtX?0EQiH**C+5Ml4{T8)OO+-PfE3sg1Paga|nw;9NrvW?0Q{d=P|r_7drn! z8&M^%eloEvv)?t~lG>1q+=qlCndr6=1Yy(%>dfgbh&%TXeRWyM$f8?S{8ygGsA8pS zM?IBQwFu-HaGRib&`sVMSJXjhuE(AOvYeGL$vD)^dqADy%5oai-WdX=OaMym$#l_A z7d>5VEn*PN1N}x~{PYrGX`90HOmI4|Rc$_R*_61pBoGZVu(mO4MgBSA z3G&qBk^c}(l#fx^d_Tr93{<%g;efsvX)qQ8<7p74rQ;AUmvbi*yka|wYGA+9!(&uJ z3tZ#|GLLIrw5-@~{uvdM_93`x8jgYT%ZPhr3MqBNEu`I@f@nl~3G(!ilEW9!nGG{5 zNIRRPhlryvj{p$?00D^vJ_Hy62mlEM0$8ouDgqk<0x1a)je&qX4!_Na!CfE(8370Y z1_c6Gt=cL83JDN?psB<1bNtxVU=ROn3Hco$6RNCn?dy%AGv~v}na?1gs^YJhXA)JR zJ_hRT#t5-)YKUBmhDT{(!zP43W=13FLVlQQY&Uywe9iI|Dk@tr8RUEXt!L9asCk14 z$moeFun}{2z@`Y8KUEy#Y?ttc*0nt%%r%bCd4pClxDY!t`M2qFddF+NHq%TDA5Z73 zoZ<)UWl<6+4{!S>HvV2YFUNmbNfe7l7outUhag5HvTFpov{9)%SU3wB^qK~XMv`AX!x<6%-nu+;S&pdG~rCcpO z05M&Fwf!q>>kU>E8(Zk`CG@{MMFpYoH>2^}r{N(ze}#nyK^=2d^FwnaCSIuyoty8V z$MgLSEc6&fC;Zgt2oP+BME)7IvYQ`*`)m(a>t+0)T%TWxv;Hw42=h!wN&j`JBw0E> z50`dHHM+RTjBnAsX^_gE8Q9$kh)YxA+2aP#nvkSPSGK0POS-qBfqQ0U`6_z!bL?8k zW-GmuFEE@S55O+}&SbUnxDWqc+d9(t{vtC%96$nq3|U&n$e6E6Na09qb+{@cv1jZ| z3ANPzBC8hPZX%fd!AicAGHUi1CEtQTkg6rlJ&izkT=Qe0t#FL~*@%Q@afty20kH zP@b&1>Szr#R<^(R$ZDQ+tX1BmAvCn7XbkFG{bvJsln04BkS2;}7+r)m!j=C|-@2Mb zZaVA|!c_0vpuO@|Zgh7CYc|rUFc^1cmciEIZ-OsoUfh8=!gs&KS$I6fh;IjUD`52- z$hYta7J<u zKyfV{PWt21mmL1oc+`{DV3Y`cYIUjP(OqJCF?#$b(-lq(eagmRKXj;3eca9O(@/dev/null 2>&1 - # Upstream Docker signing key - curl "${curl_flags[@]}" "https://download.docker.com/linux/ubuntu/gpg" | \ - gpg "${gpg_flags[@]}" --output="docker.gpg" -popd >/dev/null 2>&1 diff --git a/dockerutil/client.go b/dockerutil/client.go deleted file mode 100644 index 0a22e3e..0000000 --- a/dockerutil/client.go +++ /dev/null @@ -1,65 +0,0 @@ -package dockerutil - -import ( - "context" - "encoding/base64" - "encoding/json" - - dockertypes "github.com/docker/docker/api/types" - dockerclient "github.com/docker/docker/client" - "golang.org/x/xerrors" -) - -type clientKey struct{} - -// WithClient sets the provided DockerClient on the context. -// It should only be used for tests. -func WithClient(ctx context.Context, client DockerClient) context.Context { - return context.WithValue(ctx, clientKey{}, client) -} - -// Client returns the DockerClient set on the context. If one can't be -// found a default client is returned. -func Client(ctx context.Context) (DockerClient, error) { - client := ctx.Value(clientKey{}) - if client == nil { - client, err := dockerclient.NewClientWithOpts(dockerclient.FromEnv) - if err != nil { - return nil, xerrors.Errorf("new env client: %w", err) - } - - return client, nil - } - - //nolint we should panic if this isn't the case. - return client.(DockerClient), nil -} - -type AuthConfig dockertypes.AuthConfig - -func (a AuthConfig) Base64() (string, error) { - authStr, err := json.Marshal(a) - if err != nil { - return "", xerrors.Errorf("json marshal: %w", err) - } - return base64.StdEncoding.EncodeToString(authStr), nil -} - -func ParseAuthConfig(raw string) (AuthConfig, error) { - type dockerConfig struct { - AuthConfigs map[string]dockertypes.AuthConfig `json:"auths"` - } - - var conf dockerConfig - if err := json.Unmarshal([]byte(raw), &conf); err != nil { - return AuthConfig{}, xerrors.Errorf("parse docker auth config secret: %w", err) - } - if len(conf.AuthConfigs) != 1 { - return AuthConfig{}, xerrors.Errorf("number of image pull auth configs not equal to 1 (%d)", len(conf.AuthConfigs)) - } - for _, regConfig := range conf.AuthConfigs { - return AuthConfig(regConfig), nil - } - - return AuthConfig{}, xerrors.New("no auth configs parsed.") -} diff --git a/dockerutil/container.go b/dockerutil/container.go deleted file mode 100644 index 49c2c7f..0000000 --- a/dockerutil/container.go +++ /dev/null @@ -1,175 +0,0 @@ -package dockerutil - -import ( - "context" - "fmt" - "path/filepath" - "strconv" - "strings" - "time" - - "github.com/docker/docker/api/types/container" - dockerclient "github.com/docker/docker/client" - "github.com/spf13/afero" - "golang.org/x/xerrors" - - "cdr.dev/slog" - "github.com/coder/envbox/xunix" - "github.com/coder/retry" -) - -const ( - runtime = "sysbox-runc" - // Default CPU period for containers. - DefaultCPUPeriod uint64 = 1e5 -) - -type DockerClient interface { - dockerclient.SystemAPIClient - dockerclient.ContainerAPIClient - dockerclient.ImageAPIClient -} - -type ContainerConfig struct { - Log slog.Logger - Mounts []xunix.Mount - Devices []container.DeviceMapping - Envs []string - Name string - Image string - WorkingDir string - Hostname string - // HasInit dictates whether the entrypoint of the container is /sbin/init - // or 'sleep infinity'. - HasInit bool - CPUs int64 - MemoryLimit int64 -} - -// CreateContainer creates a sysbox-runc container. -func CreateContainer(ctx context.Context, client DockerClient, conf *ContainerConfig) (string, error) { - host := &container.HostConfig{ - Runtime: runtime, - AutoRemove: true, - Resources: container.Resources{ - Devices: conf.Devices, - // Set resources for the inner container. - // This is important for processes inside the container to know what they - // have to work with. - // TODO: Sysbox does not copy cpu.cfs_{period,quota}_us into syscont-cgroup-root cgroup. - // These will not be visible inside the child container. - // See: https://github.com/nestybox/sysbox/issues/582 - CPUPeriod: int64(DefaultCPUPeriod), - CPUQuota: conf.CPUs * int64(DefaultCPUPeriod), - Memory: conf.MemoryLimit, - }, - ExtraHosts: []string{"host.docker.internal:host-gateway"}, - Binds: generateBindMounts(conf.Mounts), - } - - entrypoint := []string{"sleep", "infinity"} - if conf.HasInit { - entrypoint = []string{"/sbin/init"} - } - - if conf.Hostname == "" { - conf.Hostname = conf.Name - } - - cnt := &container.Config{ - Image: conf.Image, - Entrypoint: entrypoint, - Cmd: []string{}, - Env: conf.Envs, - Hostname: conf.Hostname, - WorkingDir: conf.WorkingDir, - Tty: false, - User: "root", - } - - c, err := client.ContainerCreate(ctx, cnt, host, nil, nil, conf.Name) - if err != nil { - return "", xerrors.Errorf("create container: %w", err) - } - return c.ID, nil -} - -type BootstrapConfig struct { - ContainerID string - User string - Script string - Env []string - Detach bool -} - -// BoostrapContainer runs a script inside the container as the provided user. -// If conf.Script is empty then it is a noop. -func BootstrapContainer(ctx context.Context, client DockerClient, conf BootstrapConfig) error { - if conf.Script == "" { - return nil - } - - var err error - for r, n := retry.New(time.Second, time.Second*2), 0; r.Wait(ctx) && n < 10; n++ { - var out []byte - out, err = ExecContainer(ctx, client, ExecConfig{ - ContainerID: conf.ContainerID, - User: conf.User, - Cmd: "/bin/sh", - Args: []string{"-s"}, - Stdin: strings.NewReader(conf.Script), - Env: conf.Env, - }) - if err != nil { - err = xerrors.Errorf("boostrap container (%s): %w", out, err) - continue - } - break - } - - if err != nil { - return xerrors.Errorf("timed out boostrapping container: %w", err) - } - - return nil -} - -// copyCPUQuotaToInnerCGroup writes the contents of the following files to -// their corresponding locations under cgroupBase: -// - /sys/fs/cgroup/cpu,cpuacct/cpu.cfs_period_us -// - /sys/fs/cgroup/cpu,cpuacct/cpu.cfs_quota_us -// -// HACK: until https://github.com/nestybox/sysbox/issues/582 is resolved, we need to set cfs_quota_us -// and cfs_period_us inside the container to ensure that applications inside the container know how much -// CPU they have to work with. -func SetContainerCPUQuota(ctx context.Context, containerID string, quota, period int) error { - var ( - fs = xunix.GetFS(ctx) - cgroupBase = fmt.Sprintf("/sys/fs/cgroup/cpu,cpuacct/docker/%s/syscont-cgroup-root/", containerID) - ) - - err := afero.WriteFile(fs, filepath.Join(cgroupBase, "cpu.cfs_period_us"), []byte(strconv.Itoa(period)), 0o644) - if err != nil { - return xerrors.Errorf("write cpu.cfs_period_us to inner container cgroup: %w", err) - } - - err = afero.WriteFile(fs, filepath.Join(cgroupBase, "cpu.cfs_quota_us"), []byte(strconv.Itoa(quota)), 0o644) - if err != nil { - return xerrors.Errorf("write cpu.cfs_quota_us to inner container cgroup: %w", err) - } - - return nil -} - -func generateBindMounts(mounts []xunix.Mount) []string { - binds := make([]string, 0, len(mounts)) - for _, mount := range mounts { - bind := fmt.Sprintf("%s:%s", mount.Source, mount.Mountpoint) - if mount.ReadOnly { - bind += ":ro" - } - binds = append(binds, bind) - } - - return binds -} diff --git a/dockerutil/daemon.go b/dockerutil/daemon.go deleted file mode 100644 index 0cc12c3..0000000 --- a/dockerutil/daemon.go +++ /dev/null @@ -1,56 +0,0 @@ -package dockerutil - -import ( - "context" - "time" - - dockerclient "github.com/docker/docker/client" -) - -// WaitForDaemon waits for a Docker daemon to startup. It waits a max -// of 30s before giving up. -func WaitForDaemon(ctx context.Context, client DockerClient) error { - ticker := time.NewTicker(time.Millisecond * 250) - defer ticker.Stop() - - ctx, cancel := context.WithTimeout(ctx, time.Second*30) - defer cancel() - - pingCtx, cancel := context.WithTimeout(ctx, time.Second) - defer cancel() - - _, err := client.Ping(pingCtx) - if err == nil { - // We pinged successfully! - return nil - } - - for { - select { - case <-ctx.Done(): - return ctx.Err() - case <-ticker.C: - } - - err := func() error { - pingCtx, cancel := context.WithTimeout(ctx, time.Second) - defer cancel() - - _, err := client.Ping(pingCtx) - return err - }() - if err == nil { - // We pinged successfully! - return nil - } - - // If its a connection failed error we can ignore and continue polling. - // It's likely that dockerd just isn't fully setup yet. - if dockerclient.IsErrConnectionFailed(err) || pingCtx.Err() != nil { - continue - } - - // If its something else, we return it. - return err - } -} diff --git a/dockerutil/doc.go b/dockerutil/doc.go deleted file mode 100644 index 9653176..0000000 --- a/dockerutil/doc.go +++ /dev/null @@ -1,3 +0,0 @@ -// Package dockerutil contains convenience functions for interacting with -// Docker. -package dockerutil diff --git a/dockerutil/dockerfake/client.go b/dockerutil/dockerfake/client.go deleted file mode 100644 index e629e97..0000000 --- a/dockerutil/dockerfake/client.go +++ /dev/null @@ -1,281 +0,0 @@ -package dockerfake - -import ( - "context" - "io" - "strings" - - dockertypes "github.com/docker/docker/api/types" - containertypes "github.com/docker/docker/api/types/container" - "github.com/docker/docker/api/types/events" - "github.com/docker/docker/api/types/filters" - "github.com/docker/docker/api/types/image" - networktypes "github.com/docker/docker/api/types/network" - "github.com/docker/docker/api/types/registry" - specs "github.com/opencontainers/image-spec/specs-go/v1" - - "github.com/coder/envbox/dockerutil" -) - -var _ dockerutil.DockerClient = MockClient{} - -// MockClient provides overrides for functions that are called in envbox. -type MockClient struct { - ImagePullFn func(_ context.Context, ref string, options dockertypes.ImagePullOptions) (io.ReadCloser, error) - ContainerCreateFn func(_ context.Context, config *containertypes.Config, hostConfig *containertypes.HostConfig, networkingConfig *networktypes.NetworkingConfig, _ *specs.Platform, containerName string) (containertypes.ContainerCreateCreatedBody, error) - ImagePruneFn func(_ context.Context, pruneFilter filters.Args) (dockertypes.ImagesPruneReport, error) - ContainerStartFn func(_ context.Context, container string, options dockertypes.ContainerStartOptions) error - ContainerExecAttachFn func(_ context.Context, execID string, config dockertypes.ExecStartCheck) (dockertypes.HijackedResponse, error) - ContainerExecCreateFn func(_ context.Context, container string, config dockertypes.ExecConfig) (dockertypes.IDResponse, error) - ContainerExecStartFn func(_ context.Context, execID string, config dockertypes.ExecStartCheck) error - ContainerExecInspectFn func(_ context.Context, execID string) (dockertypes.ContainerExecInspect, error) - ContainerInspectFn func(_ context.Context, container string) (dockertypes.ContainerJSON, error) - ContainerRemoveFn func(_ context.Context, container string, options dockertypes.ContainerRemoveOptions) error - PingFn func(_ context.Context) (dockertypes.Ping, error) -} - -func (MockClient) ImageBuild(_ context.Context, _ io.Reader, _ dockertypes.ImageBuildOptions) (dockertypes.ImageBuildResponse, error) { - panic("not implemented") -} - -func (MockClient) BuildCachePrune(_ context.Context, _ dockertypes.BuildCachePruneOptions) (*dockertypes.BuildCachePruneReport, error) { - panic("not implemented") -} - -func (MockClient) BuildCancel(_ context.Context, _ string) error { - panic("not implemented") -} - -func (MockClient) ImageCreate(_ context.Context, _ string, _ dockertypes.ImageCreateOptions) (io.ReadCloser, error) { - panic("not implemented") -} - -func (MockClient) ImageHistory(_ context.Context, _ string) ([]image.HistoryResponseItem, error) { - panic("not implemented") -} - -func (MockClient) ImageImport(_ context.Context, _ dockertypes.ImageImportSource, _ string, _ dockertypes.ImageImportOptions) (io.ReadCloser, error) { - panic("not implemented") -} - -func (MockClient) ImageInspectWithRaw(_ context.Context, _ string) (dockertypes.ImageInspect, []byte, error) { - panic("not implemented") -} - -func (MockClient) ImageList(_ context.Context, _ dockertypes.ImageListOptions) ([]dockertypes.ImageSummary, error) { - panic("not implemented") -} - -func (MockClient) ImageLoad(_ context.Context, _ io.Reader, _ bool) (dockertypes.ImageLoadResponse, error) { - panic("not implemented") -} - -func (m MockClient) ImagePull(ctx context.Context, ref string, options dockertypes.ImagePullOptions) (io.ReadCloser, error) { - if m.ImagePullFn == nil { - return io.NopCloser(strings.NewReader("")), nil - } - return m.ImagePullFn(ctx, ref, options) -} - -func (MockClient) ImagePush(_ context.Context, _ string, _ dockertypes.ImagePushOptions) (io.ReadCloser, error) { - panic("not implemented") -} - -func (MockClient) ImageRemove(_ context.Context, _ string, _ dockertypes.ImageRemoveOptions) ([]dockertypes.ImageDeleteResponseItem, error) { - panic("not implemented") -} - -func (MockClient) ImageSearch(_ context.Context, _ string, _ dockertypes.ImageSearchOptions) ([]registry.SearchResult, error) { - panic("not implemented") -} - -func (MockClient) ImageSave(_ context.Context, _ []string) (io.ReadCloser, error) { - panic("not implemented") -} - -func (MockClient) ImageTag(_ context.Context, _ string, _ string) error { - panic("not implemented") -} - -func (m MockClient) ImagesPrune(ctx context.Context, pruneFilter filters.Args) (dockertypes.ImagesPruneReport, error) { - if m.ImagePruneFn == nil { - return dockertypes.ImagesPruneReport{}, nil - } - return m.ImagePruneFn(ctx, pruneFilter) -} - -func (MockClient) Events(_ context.Context, _ dockertypes.EventsOptions) (<-chan events.Message, <-chan error) { - panic("not implemented") -} - -func (MockClient) Info(_ context.Context) (dockertypes.Info, error) { - panic("not implemented") -} - -func (MockClient) RegistryLogin(_ context.Context, _ dockertypes.AuthConfig) (registry.AuthenticateOKBody, error) { - panic("not implemented") -} - -func (MockClient) DiskUsage(_ context.Context, _ dockertypes.DiskUsageOptions) (dockertypes.DiskUsage, error) { - panic("not implemented") -} - -func (m MockClient) Ping(ctx context.Context) (dockertypes.Ping, error) { - if m.PingFn == nil { - return dockertypes.Ping{}, nil - } - return m.PingFn(ctx) -} - -func (MockClient) ContainerAttach(_ context.Context, _ string, _ dockertypes.ContainerAttachOptions) (dockertypes.HijackedResponse, error) { - panic("not implemented") -} - -func (MockClient) ContainerCommit(_ context.Context, _ string, _ dockertypes.ContainerCommitOptions) (dockertypes.IDResponse, error) { - panic("not implemented") -} - -func (m MockClient) ContainerCreate(ctx context.Context, config *containertypes.Config, hostConfig *containertypes.HostConfig, networkingConfig *networktypes.NetworkingConfig, pspecs *specs.Platform, containerName string) (containertypes.ContainerCreateCreatedBody, error) { - if m.ContainerCreateFn == nil { - return containertypes.ContainerCreateCreatedBody{}, nil - } - return m.ContainerCreateFn(ctx, config, hostConfig, networkingConfig, pspecs, containerName) -} - -func (MockClient) ContainerDiff(_ context.Context, _ string) ([]containertypes.ContainerChangeResponseItem, error) { - panic("not implemented") -} - -func (m MockClient) ContainerExecAttach(ctx context.Context, execID string, config dockertypes.ExecStartCheck) (dockertypes.HijackedResponse, error) { - if m.ContainerExecAttachFn == nil { - return dockertypes.HijackedResponse{}, nil - } - return m.ContainerExecAttachFn(ctx, execID, config) -} - -func (m MockClient) ContainerExecCreate(ctx context.Context, name string, config dockertypes.ExecConfig) (dockertypes.IDResponse, error) { - if m.ContainerExecCreateFn == nil { - return dockertypes.IDResponse{}, nil - } - return m.ContainerExecCreateFn(ctx, name, config) -} - -func (m MockClient) ContainerExecInspect(ctx context.Context, id string) (dockertypes.ContainerExecInspect, error) { - if m.ContainerExecInspectFn == nil { - return dockertypes.ContainerExecInspect{}, nil - } - - return m.ContainerExecInspectFn(ctx, id) -} - -func (MockClient) ContainerExecResize(_ context.Context, _ string, _ dockertypes.ResizeOptions) error { - panic("not implemented") -} - -func (m MockClient) ContainerExecStart(ctx context.Context, execID string, config dockertypes.ExecStartCheck) error { - if m.ContainerExecStartFn == nil { - return nil - } - return m.ContainerExecStartFn(ctx, execID, config) -} - -func (MockClient) ContainerExport(_ context.Context, _ string) (io.ReadCloser, error) { - panic("not implemented") -} - -func (m MockClient) ContainerInspect(ctx context.Context, name string) (dockertypes.ContainerJSON, error) { - if m.ContainerInspectFn == nil { - return dockertypes.ContainerJSON{}, nil - } - return m.ContainerInspectFn(ctx, name) -} - -func (MockClient) ContainerInspectWithRaw(_ context.Context, _ string, _ bool) (dockertypes.ContainerJSON, []byte, error) { - panic("not implemented") -} - -func (MockClient) ContainerKill(_ context.Context, _ string, _ string) error { - panic("not implemented") -} - -func (MockClient) ContainerList(_ context.Context, _ dockertypes.ContainerListOptions) ([]dockertypes.Container, error) { - panic("not implemented") -} - -func (MockClient) ContainerLogs(_ context.Context, _ string, _ dockertypes.ContainerLogsOptions) (io.ReadCloser, error) { - panic("not implemented") -} - -func (MockClient) ContainerPause(_ context.Context, _ string) error { - panic("not implemented") -} - -func (m MockClient) ContainerRemove(ctx context.Context, name string, options dockertypes.ContainerRemoveOptions) error { - if m.ContainerRemoveFn == nil { - return nil - } - return m.ContainerRemoveFn(ctx, name, options) -} - -func (MockClient) ContainerRename(_ context.Context, _ string, _ string) error { - panic("not implemented") -} - -func (MockClient) ContainerResize(_ context.Context, _ string, _ dockertypes.ResizeOptions) error { - panic("not implemented") -} - -func (MockClient) ContainerRestart(_ context.Context, _ string, _ containertypes.StopOptions) error { - panic("not implemented") -} - -func (MockClient) ContainerStatPath(_ context.Context, _ string, _ string) (dockertypes.ContainerPathStat, error) { - panic("not implemented") -} - -func (MockClient) ContainerStats(_ context.Context, _ string, _ bool) (dockertypes.ContainerStats, error) { - panic("not implemented") -} - -func (m MockClient) ContainerStart(ctx context.Context, name string, options dockertypes.ContainerStartOptions) error { - if m.ContainerStartFn == nil { - return nil - } - return m.ContainerStartFn(ctx, name, options) -} - -func (MockClient) ContainerStop(_ context.Context, _ string, _ containertypes.StopOptions) error { - panic("not implemented") -} - -func (MockClient) ContainerTop(_ context.Context, _ string, _ []string) (containertypes.ContainerTopOKBody, error) { - panic("not implemented") -} - -func (MockClient) ContainerUnpause(_ context.Context, _ string) error { - panic("not implemented") -} - -func (MockClient) ContainerUpdate(_ context.Context, _ string, _ containertypes.UpdateConfig) (containertypes.ContainerUpdateOKBody, error) { - panic("not implemented") -} - -func (MockClient) ContainerWait(_ context.Context, _ string, _ containertypes.WaitCondition) (<-chan containertypes.ContainerWaitOKBody, <-chan error) { - panic("not implemented") -} - -func (MockClient) CopyFromContainer(_ context.Context, _ string, _ string) (io.ReadCloser, dockertypes.ContainerPathStat, error) { - panic("not implemented") -} - -func (MockClient) CopyToContainer(_ context.Context, _ string, _ string, _ io.Reader, _ dockertypes.CopyToContainerOptions) error { - panic("not implemented") -} - -func (MockClient) ContainersPrune(_ context.Context, _ filters.Args) (dockertypes.ContainersPruneReport, error) { - panic("not implemented") -} - -func (MockClient) ContainerStatsOneShot(_ context.Context, _ string) (dockertypes.ContainerStats, error) { - panic("not implemented") -} diff --git a/dockerutil/dockerfake/doc.go b/dockerutil/dockerfake/doc.go deleted file mode 100644 index b00ea2b..0000000 --- a/dockerutil/dockerfake/doc.go +++ /dev/null @@ -1,3 +0,0 @@ -// Package dockerfake contains logic for mocking out Docker-related -// functionality. -package dockerfake diff --git a/dockerutil/exec.go b/dockerutil/exec.go deleted file mode 100644 index 6f78822..0000000 --- a/dockerutil/exec.go +++ /dev/null @@ -1,94 +0,0 @@ -package dockerutil - -import ( - "bytes" - "context" - "io" - - dockertypes "github.com/docker/docker/api/types" - "golang.org/x/xerrors" - - "github.com/coder/envbox/xio" -) - -type ExecConfig struct { - ContainerID string - User string - Cmd string - Args []string - Stdin io.Reader - StdOutErr io.Writer - Env []string - Detach bool -} - -// ExecContainer runs a command in a container. It returns the output and any error. -// If an error occurs during the execution of the command, the output is appended to the error. -func ExecContainer(ctx context.Context, client DockerClient, config ExecConfig) ([]byte, error) { - exec, err := client.ContainerExecCreate(ctx, config.ContainerID, dockertypes.ExecConfig{ - Detach: true, - Cmd: append([]string{config.Cmd}, config.Args...), - User: config.User, - AttachStderr: true, - AttachStdout: true, - AttachStdin: config.Stdin != nil, - Env: config.Env, - }) - if err != nil { - return nil, xerrors.Errorf("exec create: %w", err) - } - - resp, err := client.ContainerExecAttach(ctx, exec.ID, dockertypes.ExecStartCheck{}) - if err != nil { - return nil, xerrors.Errorf("attach to exec: %w", err) - } - defer resp.Close() - - if config.Stdin != nil { - _, err = io.Copy(resp.Conn, config.Stdin) - if err != nil { - return nil, xerrors.Errorf("copy stdin: %w", err) - } - err = resp.CloseWrite() - if err != nil { - return nil, xerrors.Errorf("close write: %w", err) - } - } - - var ( - buf bytes.Buffer - // Avoid capturing too much output. We want to prevent - // a memory leak. This is especially important when - // we run the bootstrap script since we do not return. - psw = &xio.PrefixSuffixWriter{ - W: &buf, - N: 1 << 10, - } - wr io.Writer = psw - ) - - if config.StdOutErr != nil { - wr = io.MultiWriter(psw, config.StdOutErr) - } - - _, err = io.Copy(wr, resp.Reader) - if err != nil { - return nil, xerrors.Errorf("copy cmd output: %w", err) - } - resp.Close() - - inspect, err := client.ContainerExecInspect(ctx, exec.ID) - if err != nil { - return nil, xerrors.Errorf("exec inspect: %w", err) - } - - if inspect.Running { - return nil, xerrors.Errorf("unexpectedly still running") - } - - if inspect.ExitCode > 0 { - return nil, xerrors.Errorf("%s: exit code %d", buf.Bytes(), inspect.ExitCode) - } - - return buf.Bytes(), nil -} diff --git a/dockerutil/image.go b/dockerutil/image.go deleted file mode 100644 index 9ba196e..0000000 --- a/dockerutil/image.go +++ /dev/null @@ -1,255 +0,0 @@ -package dockerutil - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "io" - "time" - - dockertypes "github.com/docker/docker/api/types" - "github.com/docker/docker/api/types/container" - "github.com/docker/docker/api/types/filters" - "golang.org/x/xerrors" - - "github.com/coder/envbox/buildlog" - "github.com/coder/envbox/xunix" - "github.com/coder/retry" -) - -const diskFullStorageDriver = "vfs" - -type PullImageConfig struct { - Client DockerClient - Image string - Auth AuthConfig - ProgressFn ImagePullProgressFn -} - -type ImagePullEvent struct { - Status string `json:"status"` - Error string `json:"error"` - Progress string `json:"progress"` - ProgressDetail struct { - Current int `json:"current"` - Total int `json:"total"` - } `json:"progressDetail"` -} - -// ImagePullProgressFn provides a way for a consumer to process -// image pull progress. -type ImagePullProgressFn func(e ImagePullEvent) error - -// PullImage pulls the provided image. -func PullImage(ctx context.Context, config *PullImageConfig) error { - authStr, err := config.Auth.Base64() - if err != nil { - return xerrors.Errorf("base64 encode auth: %w", err) - } - - pullImageFn := func() error { - var rd io.ReadCloser - rd, err = config.Client.ImagePull(ctx, config.Image, dockertypes.ImagePullOptions{ - RegistryAuth: authStr, - }) - if err != nil { - return xerrors.Errorf("pull image: %w", err) - } - - err = processImagePullEvents(rd, config.ProgressFn) - if err != nil { - return xerrors.Errorf("process image pull events: %w", err) - } - return nil - } - - err = pullImageFn() - if err == nil { - return nil - } - - var pruned bool - for r, n := retry.New(time.Second, time.Second*3), 0; r.Wait(ctx) && n < 10; n++ { - err = pullImageFn() - if err != nil { - // If we failed to pull the image, try to prune existing images - // to free up space. - if xunix.IsNoSpaceErr(err) && !pruned { - pruned = true - // Pruning is best effort. - _, _ = PruneImages(ctx, config.Client) - } - // If we've already pruned and we still can't pull the image we - // should just exit. - if xunix.IsNoSpaceErr(err) && pruned { - return xerrors.Errorf("insufficient disk to pull image: %w", err) - } - continue - } - return nil - } - if err != nil { - return xerrors.Errorf("pull image: %w", err) - } - - return nil -} - -// PruneImage runs a simple 'docker prune'. -func PruneImages(ctx context.Context, client DockerClient) (dockertypes.ImagesPruneReport, error) { - report, err := client.ImagesPrune(ctx, - filters.NewArgs(filters.Arg("dangling", "false")), - ) - if err != nil { - return dockertypes.ImagesPruneReport{}, xerrors.Errorf("images prune: %w", err) - } - - return report, nil -} - -func processImagePullEvents(r io.Reader, fn ImagePullProgressFn) error { - if fn == nil { - // This effectively waits until the image is pulled before returning, - // reporting no progress. - _, _ = io.Copy(io.Discard, r) - return nil - } - - decoder := json.NewDecoder(r) - - var event ImagePullEvent - for { - if err := decoder.Decode(&event); err != nil { - if xerrors.Is(err, io.EOF) { - break - } - - return xerrors.Errorf("decode image pull output: %w", err) - } - - err := fn(event) - if err != nil { - return xerrors.Errorf("process image pull event: %w", err) - } - } - - return nil -} - -type ImageMetadata struct { - UID string - GID string - HomeDir string - HasInit bool -} - -// GetImageMetadata returns metadata about an image such as the UID/GID of the -// provided // username and whether it contains an /sbin/init that we should run. -func GetImageMetadata(ctx context.Context, client DockerClient, image, username string) (ImageMetadata, error) { - // Creating a dummy container to inspect the filesystem. - created, err := client.ContainerCreate(ctx, - &container.Config{ - Image: image, - Entrypoint: []string{ - "sleep", - }, - Cmd: []string{ - "infinity", - }, - }, - &container.HostConfig{ - Runtime: "sysbox-runc", - }, nil, nil, "") - if err != nil { - return ImageMetadata{}, xerrors.Errorf("create container: %w", err) - } - - defer func() { - // We wanna remove this, but it's not a huge deal if it fails. - _ = client.ContainerRemove(ctx, created.ID, dockertypes.ContainerRemoveOptions{ - Force: true, - }) - }() - - err = client.ContainerStart(ctx, created.ID, dockertypes.ContainerStartOptions{}) - if err != nil { - return ImageMetadata{}, xerrors.Errorf("start container: %w", err) - } - - inspect, err := client.ContainerInspect(ctx, created.ID) - if err != nil { - return ImageMetadata{}, xerrors.Errorf("inspect: %w", err) - } - - mergedDir := inspect.GraphDriver.Data["MergedDir"] - // The mergedDir might be empty if we're running dockerd in recovery - // mode. - if mergedDir == "" && inspect.GraphDriver.Name != diskFullStorageDriver { - // The MergedDir is empty when the underlying filesystem does not support - // OverlayFS as an extension. A customer ran into this when using NFS as - // a provider for a PVC. - return ImageMetadata{}, xerrors.Errorf("CVMs do not support NFS volumes") - } - - _, err = ExecContainer(ctx, client, ExecConfig{ - ContainerID: inspect.ID, - Cmd: "stat", - Args: []string{"/sbin/init"}, - }) - initExists := err == nil - - out, err := ExecContainer(ctx, client, ExecConfig{ - ContainerID: inspect.ID, - Cmd: "getent", - Args: []string{"passwd", username}, - }) - if err != nil { - return ImageMetadata{}, xerrors.Errorf("get /etc/passwd entry for %s: %w", username, err) - } - - users, err := xunix.ParsePasswd(bytes.NewReader(out)) - if err != nil { - return ImageMetadata{}, xerrors.Errorf("parse passwd entry for (%s): %w", out, err) - } - if len(users) == 0 { - return ImageMetadata{}, xerrors.Errorf("no users returned for username %s", username) - } - - return ImageMetadata{ - UID: users[0].Uid, - GID: users[0].Gid, - HomeDir: users[0].HomeDir, - HasInit: initExists, - }, nil -} - -func DefaultLogImagePullFn(log buildlog.Logger) func(ImagePullEvent) error { - var ( - // Avoid spamming too frequently, the messages can come quickly - delayDur = time.Second * 2 - // We use a zero-value time.Time to start since we want to log - // the first event we get. - lastLog time.Time - ) - return func(e ImagePullEvent) error { - if e.Error != "" { - log.Errorf(e.Error) - return xerrors.Errorf("pull image: %s", e.Error) - } - - // Not enough time has transpired, return without logging. - if time.Since(lastLog) < delayDur { - return nil - } - - msg := e.Status - if e.Progress != "" { - msg = fmt.Sprintf("%s: %s", e.Status, e.Progress) - } - log.Info(msg) - lastLog = time.Now() - - return nil - } -} diff --git a/dockerutil/network.go b/dockerutil/network.go deleted file mode 100644 index 7c613a9..0000000 --- a/dockerutil/network.go +++ /dev/null @@ -1,45 +0,0 @@ -package dockerutil - -import ( - "net" - - "golang.org/x/xerrors" -) - -var DefaultBridgeCIDR = "172.19.0.1/30" - -func BridgeIPFromCIDR(cidr string) (net.IP, int) { - ipNet := mustParseIPv4Net(cidr) - prefixLen, _ := ipNet.Mask.Size() - bridgeIP := mustNextIPv4(ipNet.IP, 1) - return bridgeIP, prefixLen -} - -func mustParseIPv4Net(s string) *net.IPNet { - _, n, err := net.ParseCIDR(s) - if err != nil { - panic(err) - } - if n.IP.To4() == nil { - panic(xerrors.New("must specify an IPv4 network")) - } - return n -} - -// Adapted from https://gist.github.com/udhos/b468fbfd376aa0b655b6b0c539a88c03#file-nextip-go-L31 -func mustNextIPv4(ip net.IP, inc int) net.IP { - ip4 := ip.To4() - if ip4 == nil { - panic(xerrors.Errorf("invalid IPv4 addr %s", ip.String())) - } - v := uint32(ip4[0]) << 24 - v += uint32(ip4[1]) << 16 - v += uint32(ip4[2]) << 8 - v += uint32(ip4[3]) - v += uint32(inc) - v3 := byte(v & 0xFF) - v2 := byte((v >> 8) & 0xFF) - v1 := byte((v >> 16) & 0xFF) - v0 := byte((v >> 24) & 0xFF) - return net.IPv4(v0, v1, v2, v3) -} diff --git a/go.mod b/go.mod deleted file mode 100644 index 57517bb..0000000 --- a/go.mod +++ /dev/null @@ -1,187 +0,0 @@ -module github.com/coder/envbox - -go 1.20 - -// Coder runs a fork of tailscale, since we depend on that repo we need to make -// sure go.mod doesn't try to use the actual tailscale libs otherwise things -// won't compile. -replace tailscale.com => github.com/coder/tailscale v1.1.1-0.20230314023417-d9efcc0ac972 - -require ( - cdr.dev/slog v1.4.2-0.20230228204227-60d22dceaf04 - github.com/coder/coder v0.20.2-0.20230323233815-982274536509 - github.com/coder/retry v1.3.1-0.20230210155434-e90a2e1e091d - github.com/docker/docker v23.0.3+incompatible - github.com/opencontainers/image-spec v1.1.0-rc2 - github.com/ory/dockertest/v3 v3.9.1 - github.com/quasilyte/go-ruleguard/dsl v0.3.22 - github.com/spf13/afero v1.9.4 - github.com/spf13/cobra v1.6.1 - github.com/spf13/pflag v1.0.5 - github.com/stretchr/testify v1.8.2 - github.com/vishvananda/netlink v1.1.1-0.20211118161826-650dca95af54 - golang.org/x/exp v0.0.0-20230321023759-10a507213a29 - golang.org/x/net v0.8.0 - golang.org/x/sys v0.6.0 - golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 - k8s.io/mount-utils v0.26.2 - k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5 -) - -require ( - cloud.google.com/go/compute v1.18.0 // indirect - cloud.google.com/go/compute/metadata v0.2.3 // indirect - filippo.io/edwards25519 v1.0.0-rc.1 // indirect - github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect - github.com/Microsoft/go-winio v0.6.0 // indirect - github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect - github.com/OneOfOne/xxhash v1.2.8 // indirect - github.com/agext/levenshtein v1.2.3 // indirect - github.com/agnivade/levenshtein v1.1.1 // indirect - github.com/akutz/memconn v0.1.0 // indirect - github.com/alecthomas/chroma v0.10.0 // indirect - github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 // indirect - github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect - github.com/beorn7/perks v1.0.1 // indirect - github.com/cenkalti/backoff/v4 v4.2.0 // indirect - github.com/cespare/xxhash/v2 v2.2.0 // indirect - github.com/coder/terraform-provider-coder v0.6.21 // indirect - github.com/containerd/continuity v0.3.0 // indirect - github.com/coreos/go-iptables v0.6.0 // indirect - github.com/coreos/go-oidc/v3 v3.4.0 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/dlclark/regexp2 v1.8.1 // indirect - github.com/docker/cli v23.0.1+incompatible // indirect - github.com/docker/distribution v2.8.1+incompatible // indirect - github.com/docker/go-connections v0.4.0 // indirect - github.com/docker/go-units v0.5.0 // indirect - github.com/fatih/color v1.14.1 // indirect - github.com/fxamacker/cbor/v2 v2.4.0 // indirect - github.com/ghodss/yaml v1.0.0 // indirect - github.com/go-chi/chi/v5 v5.0.7 // indirect - github.com/go-logr/logr v1.2.3 // indirect - github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-ole/go-ole v1.2.6 // indirect - github.com/gobwas/glob v0.2.3 // indirect - github.com/godbus/dbus/v5 v5.1.0 // indirect - github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/glog v1.0.0 // indirect - github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/golang/protobuf v1.5.2 // indirect - github.com/google/btree v1.1.2 // indirect - github.com/google/go-cmp v0.5.9 // indirect - github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect - github.com/google/uuid v1.3.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.1 // indirect - github.com/hashicorp/errwrap v1.1.0 // indirect - github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 // indirect - github.com/hashicorp/go-hclog v1.2.1 // indirect - github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/hashicorp/go-uuid v1.0.3 // indirect - github.com/hashicorp/go-version v1.6.0 // indirect - github.com/hashicorp/golang-lru/v2 v2.0.1 // indirect - github.com/hashicorp/hcl/v2 v2.14.0 // indirect - github.com/hashicorp/logutils v1.0.0 // indirect - github.com/hashicorp/terraform-plugin-go v0.12.0 // indirect - github.com/hashicorp/terraform-plugin-log v0.7.0 // indirect - github.com/hashicorp/terraform-plugin-sdk/v2 v2.20.0 // indirect - github.com/hashicorp/yamux v0.0.0-20220718163420-dd80a7ee44ce // indirect - github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3 // indirect - github.com/iancoleman/strcase v0.2.0 // indirect - github.com/illarion/gonotify v1.0.1 // indirect - github.com/imdario/mergo v0.3.13 // indirect - github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/insomniacslk/dhcp v0.0.0-20221215072855-de60144f33f8 // indirect - github.com/jmoiron/sqlx v1.3.5 // indirect - github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86 // indirect - github.com/jsimonetti/rtnetlink v1.1.2-0.20220408201609-d380b505068b // indirect - github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/compress v1.15.15 // indirect - github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a // indirect - github.com/lib/pq v1.10.6 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.17 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect - github.com/mdlayher/genetlink v1.2.0 // indirect - github.com/mdlayher/netlink v1.7.1 // indirect - github.com/mdlayher/sdnotify v1.0.0 // indirect - github.com/mdlayher/socket v0.4.0 // indirect - github.com/mitchellh/copystructure v1.2.0 // indirect - github.com/mitchellh/go-ps v1.0.0 // indirect - github.com/mitchellh/go-testing-interface v1.14.1 // indirect - github.com/mitchellh/go-wordwrap v1.0.1 // indirect - github.com/mitchellh/mapstructure v1.5.0 // indirect - github.com/mitchellh/reflectwalk v1.0.2 // indirect - github.com/moby/sys/mountinfo v0.6.2 // indirect - github.com/moby/term v0.0.0-20221205130635-1aeaba878587 // indirect - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/open-policy-agent/opa v0.44.0 // indirect - github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/opencontainers/runc v1.1.5 // indirect - github.com/pkg/errors v0.9.1 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_golang v1.14.0 // indirect - github.com/prometheus/client_model v0.3.0 // indirect - github.com/prometheus/common v0.40.0 // indirect - github.com/prometheus/procfs v0.9.0 // indirect - github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 // indirect - github.com/sirupsen/logrus v1.9.0 // indirect - github.com/tabbed/pqtype v0.1.1 // indirect - github.com/tailscale/certstore v0.1.1-0.20220316223106-78d6e1c49d8d // indirect - github.com/tailscale/golang-x-crypto v0.0.0-20221102133106-bc99ab8c2d17 // indirect - github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05 // indirect - github.com/tailscale/netlink v1.1.1-0.20211101221916-cabfb018fe85 // indirect - github.com/tailscale/wireguard-go v0.0.0-20221219190806-4fa124729667 // indirect - github.com/tchap/go-patricia/v2 v2.3.1 // indirect - github.com/tcnksm/go-httpstat v0.2.0 // indirect - github.com/u-root/uio v0.0.0-20221213070652-c3537552635f // indirect - github.com/valyala/fasthttp v1.44.0 // indirect - github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect - github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect - github.com/vmihailenco/msgpack/v4 v4.3.12 // indirect - github.com/vmihailenco/tagparser v0.1.1 // indirect - github.com/x448/float16 v0.8.4 // indirect - github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect - github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect - github.com/xeipuuv/gojsonschema v1.2.0 // indirect - github.com/yashtewari/glob-intersection v0.1.0 // indirect - github.com/zclconf/go-cty v1.10.0 // indirect - github.com/zeebo/errs v1.3.0 // indirect - go.nhat.io/otelsql v0.7.0 // indirect - go.opencensus.io v0.24.0 // indirect - go.opentelemetry.io/otel v1.11.1 // indirect - go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.11.1 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.11.1 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.11.1 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.11.1 // indirect - go.opentelemetry.io/otel/metric v0.33.0 // indirect - go.opentelemetry.io/otel/sdk v1.11.1 // indirect - go.opentelemetry.io/otel/trace v1.11.1 // indirect - go.opentelemetry.io/proto/otlp v0.19.0 // indirect - go4.org/mem v0.0.0-20210711025021-927187094b94 // indirect - go4.org/netipx v0.0.0-20220725152314-7e7bdc8411bf // indirect - golang.org/x/crypto v0.6.0 // indirect - golang.org/x/mod v0.9.0 // indirect - golang.org/x/oauth2 v0.5.0 // indirect - golang.org/x/sync v0.1.0 // indirect - golang.org/x/term v0.6.0 // indirect - golang.org/x/text v0.8.0 // indirect - golang.org/x/time v0.3.0 // indirect - golang.org/x/tools v0.7.0 // indirect - golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect - golang.zx2c4.com/wireguard/windows v0.5.3 // indirect - google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20230223222841-637eb2293923 // indirect - google.golang.org/grpc v1.53.0 // indirect - google.golang.org/protobuf v1.28.2-0.20230118093459-a9481185b34d // indirect - gopkg.in/square/go-jose.v2 v2.6.0 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect - gvisor.dev/gvisor v0.0.0-20221203005347-703fd9b7fbc0 // indirect - inet.af/peercred v0.0.0-20210906144145-0893ea02156a // indirect - k8s.io/klog/v2 v2.80.1 // indirect - nhooyr.io/websocket v1.8.7 // indirect - storj.io/drpc v0.0.33-0.20220622181519-9206537a4db7 // indirect - tailscale.com v1.38.2 // indirect -) diff --git a/go.sum b/go.sum deleted file mode 100644 index 9314308..0000000 --- a/go.sum +++ /dev/null @@ -1,1273 +0,0 @@ -cdr.dev/slog v1.4.2-0.20230228204227-60d22dceaf04 h1:d5MQ+iI2zk7t0HrHwBP9p7k2XfRsXnRclSe8Kpp3xOo= -cdr.dev/slog v1.4.2-0.20230228204227-60d22dceaf04/go.mod h1:YPVZsUbRMaLaPgme0RzlPWlC7fI7YmDj/j/kZLuvICs= -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= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= -cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= -cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= -cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= -cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= -cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= -cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= -cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= -cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= -cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= -cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= -cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= -cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= -cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= -cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= -cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc= -cloud.google.com/go v0.107.0 h1:qkj22L7bgkl6vIeZDlOY2po43Mx/TIa2Wsa7VR+PEww= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow= -cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM= -cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M= -cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s= -cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= -cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U= -cloud.google.com/go/compute v1.18.0 h1:FEigFqoDbys2cvFkZ9Fjq4gnHBP55anJ0yQyau2f9oY= -cloud.google.com/go/compute v1.18.0/go.mod h1:1X7yHxec2Ga+Ss6jPyjxRxpu2uu7PLgsOVXvgU0yacs= -cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= -cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY= -cloud.google.com/go/logging v1.6.1 h1:ZBsZK+JG+oCDT+vaxwqF2egKNRjz8soXiS6Xv79benI= -cloud.google.com/go/longrunning v0.3.0 h1:NjljC+FYPV3uh5/OwWT6pVU+doBqMg2x/rZlE+CamDs= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= -cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -filippo.io/edwards25519 v1.0.0-rc.1 h1:m0VOOB23frXZvAOK44usCgLWvtsxIoMCTBGJZlpmGfU= -filippo.io/edwards25519 v1.0.0-rc.1/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns= -filippo.io/mkcert v1.4.3 h1:axpnmtrZMM8u5Hf4N3UXxboGemMOV+Tn+e+pkHM6E3o= -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= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= -github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= -github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg= -github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE= -github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= -github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/OneOfOne/xxhash v1.2.8 h1:31czK/TI9sNkxIKfaUfGlU47BAxQ0ztGgd9vPyqimf8= -github.com/OneOfOne/xxhash v1.2.8/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= -github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls= -github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= -github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= -github.com/agnivade/levenshtein v1.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRBij0P8= -github.com/agnivade/levenshtein v1.1.1/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo= -github.com/akutz/memconn v0.1.0 h1:NawI0TORU4hcOMsMr11g7vwlCdkYeLKXBcxWu2W/P8A= -github.com/akutz/memconn v0.1.0/go.mod h1:Jo8rI7m0NieZyLI5e2CDlRdRqRRB4S7Xp77ukDjH+Fw= -github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek= -github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s= -github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 h1:Kk6a4nehpJ3UuJRqlA3JxYxBZEqCeOmATOvrbT4p9RA= -github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= -github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= -github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= -github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= -github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= -github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/apparentlymart/go-dump v0.0.0-20190214190832-042adf3cf4a0 h1:MzVXffFUye+ZcSR6opIgz9Co7WcDx6ZcY+RjfFHoA0I= -github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJEynmyUenKwP++x/+DdGV/Ec= -github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw= -github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= -github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q= -github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= -github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2 h1:7Ip0wMmLHLRJdrloDxZfhMm0xrLXZS8+COSu2bXmEQs= -github.com/aymanbagabas/go-osc52 v1.2.1 h1:q2sWUyDcozPLcLabEMd+a+7Ea2DitxZVN9hTxab9L4E= -github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= -github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= -github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bep/godartsass v0.16.0 h1:nTpenrZBQjVSjLkCw3AgnYmBB2czauTJa4BLLv448qg= -github.com/bep/golibsass v1.1.0 h1:pjtXr00IJZZaOdfryNa9wARTB3Q0BmxC3/V1KNcgyTw= -github.com/bool64/shared v0.1.5 h1:fp3eUhBsrSjNCQPcSdQqZxxh9bBwrYiZ+zOKFkM0/2E= -github.com/bytecodealliance/wasmtime-go v0.36.0 h1:B6thr7RMM9xQmouBtUqm1RpkJjuLS37m6nxX+iwsQSc= -github.com/cakturk/go-netstat v0.0.0-20200220111822-e5b49efee7a5 h1:BjkPE3785EwPhhyuFkbINB+2a1xATwk8SNDWnJiD41g= -github.com/cenkalti/backoff/v4 v4.2.0 h1:HN5dHm3WBOgndBH6E8V0q2jIYIR3s9yglV8k/+MN3u4= -github.com/cenkalti/backoff/v4 v4.2.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= -github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/charmbracelet/glamour v0.6.0 h1:wi8fse3Y7nfcabbbDuwolqTqMQPMnVPeZhDM273bISc= -github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA= -github.com/cilium/ebpf v0.8.1 h1:bLSSEbBLqGPXxls55pGr5qWZaTqcmfDJHhou7t254ao= -github.com/cilium/ebpf v0.8.1/go.mod h1:f5zLIM0FSNuAkSyLAN7X+Hy6yznlF1mNiWUMfxMtrgk= -github.com/clbanning/mxj/v2 v2.5.7 h1:7q5lvUpaPF/WOkqgIDiwjBJaznaLCCBd78pi8ZyAnE0= -github.com/cli/safeexec v1.0.0 h1:0VngyaIyqACHdcMNWfo6+KdUYnqEr2Sg+bSP1pdF+dI= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= -github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/coder/coder v0.20.2-0.20230323233815-982274536509 h1:WX1saYyaObbpVhFIT5+KDHALCqGQV8QvTTkTYZJ+YYQ= -github.com/coder/coder v0.20.2-0.20230323233815-982274536509/go.mod h1:vr6YNLhNoBvcL73cBhs4FOQiOnirlqlVxxlACFLipRg= -github.com/coder/retry v1.3.1-0.20230210155434-e90a2e1e091d h1:09JG37IgTB6n3ouX9BXdUiibGzkGGbslFuDZO9Ru9aw= -github.com/coder/retry v1.3.1-0.20230210155434-e90a2e1e091d/go.mod h1:r+1J5i/989wt6CUeNSuvFKKA9hHuKKPMxdzDbTuvwwk= -github.com/coder/tailscale v1.1.1-0.20230314023417-d9efcc0ac972 h1:193YGsJz8hc4yxqAclE36paKl+9CQ6KGLgdleIguCVE= -github.com/coder/tailscale v1.1.1-0.20230314023417-d9efcc0ac972/go.mod h1:jpg+77g19FpXL43U1VoIqoSg1K/Vh5CVxycGldQ8KhA= -github.com/coder/terraform-provider-coder v0.6.21 h1:TIH6+/VQFreT8q/CkRvpHtbIeM5cOAhuDS5Sh1Nm21Q= -github.com/coder/terraform-provider-coder v0.6.21/go.mod h1:UIfU3bYNeSzJJvHyJ30tEKjD6Z9utloI+HUM/7n94CY= -github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= -github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg= -github.com/containerd/continuity v0.3.0/go.mod h1:wJEAIwKOm/pBZuBd0JmeTvnLquTB1Ag8espWhkykbPM= -github.com/coreos/go-iptables v0.6.0 h1:is9qnZMPYjLd8LYqmm/qlE+wwEgJIkTYdhV3rfZo4jk= -github.com/coreos/go-iptables v0.6.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q= -github.com/coreos/go-oidc/v3 v3.4.0 h1:xz7elHb/LDwm/ERpwHd+5nb7wFHL32rsr6bBOgaeu6g= -github.com/coreos/go-oidc/v3 v3.4.0/go.mod h1:eHUXhZtXPQLgEaDrOVTgwbgmz1xGOkJNye6h3zkD2Pw= -github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -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.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= -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/dgraph-io/badger/v3 v3.2103.5 h1:ylPa6qzbjYRQMU6jokoj4wzcaweHylt//CH0AKt0akg= -github.com/dgraph-io/ristretto v0.1.0 h1:Jv3CGQHp9OjuMBSne1485aDpUkTKEcUqF+jm/LuerPI= -github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48 h1:fRzb/w+pyskVMQ+UbP35JkH8yB7MYb4q/qhBarqZE6g= -github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= -github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= -github.com/dlclark/regexp2 v1.8.1 h1:6Lcdwya6GjPUNsBct8Lg/yRPwMhABj269AAzdGSiR+0= -github.com/dlclark/regexp2 v1.8.1/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= -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.3+incompatible h1:9GhVsShNWz1hO//9BNg/dpMnZW25KydO4wtVxWAIbho= -github.com/docker/docker v23.0.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -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/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= -github.com/elastic/go-sysinfo v1.9.0 h1:usICqY/Nw4Mpn9f4LdtpFrKxXroJDe81GaxxUlCckIo= -github.com/elastic/go-windows v1.0.0 h1:qLURgZFkkrYyTTkvYpsZIgf83AUsdIHfvlJaqaZ7aSY= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= -github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= -github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= -github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/fanliao/go-promise v0.0.0-20141029170127-1890db352a72/go.mod h1:PjfxuH4FZdUyfMdtBio2lsRr1AKEaVPwelzuHuh8Lqc= -github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= -github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w= -github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg= -github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= -github.com/foxcpp/go-mockdns v0.0.0-20210729171921-fb145fc6f897 h1:E52jfcE64UG42SwLmrW0QByONfGynWuzBvm86BoB9z8= -github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= -github.com/frankban/quicktest v1.14.0 h1:+cqqvzZV87b4adx/5ayVOaYZ2CrvM4ejQvUdBzPPUss= -github.com/frankban/quicktest v1.14.0/go.mod h1:NeW+ay9A/U67EYXNFA1nPE8e/tnQv/09mUdL/ijj8og= -github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa h1:RDBNVkRviHZtvDvId8XSGPu3rmpmSe+wKRcEWNgsfWU= -github.com/fxamacker/cbor/v2 v2.4.0 h1:ri0ArlOR+5XunOP8CRUowT0pSJOwhW098ZCUyskZD88= -github.com/fxamacker/cbor/v2 v2.4.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= -github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= -github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= -github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= -github.com/gin-gonic/gin v1.7.7 h1:3DoBmSbJbZAWqXJC3SLjAPfutPJJRN1U5pALB7EeTTs= -github.com/github/fakeca v0.1.0 h1:Km/MVOFvclqxPM9dZBC4+QE564nU4gz4iZ0D9pMw28I= -github.com/github/fakeca v0.1.0/go.mod h1:+bormgoGMMuamOscx7N91aOuUST7wdaJ2rNjeohylyo= -github.com/gliderlabs/ssh v0.3.4 h1:+AXBtim7MTKaLVPgvE+3mhewYRawNLTd+jEEz/wExZw= -github.com/go-chi/chi v1.5.4 h1:QHdzF2szwjqVV4wmByUnTcsbIg7UGaQ0tPF2t5GcAIs= -github.com/go-chi/chi/v5 v5.0.7 h1:rDTPXLDHGATaeHvVlLcR4Qe0zftYethFucbjVQ1PxU8= -github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= -github.com/go-chi/httprate v0.7.1 h1:d5kXARdms2PREQfU4pHvq44S6hJ1hPu4OXLeBKmCKWs= -github.com/go-chi/render v1.0.1 h1:4/5tis2cKaNdnv9zFLfXzcquC9HbeZgCnxGnKrltBS8= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= -github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -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/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= -github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= -github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= -github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA= -github.com/go-openapi/spec v0.20.6 h1:ich1RQ3WDbfoeTqTAb+5EIxNmpKVJZWBNah9RAT0jIQ= -github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM= -github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= -github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= -github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= -github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= -github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= -github.com/go-playground/validator/v10 v10.11.0 h1:0W+xRM511GY47Yy3bZUbJVitCNg2BOGlCyvTqsp/xIw= -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-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= -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/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0= -github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= -github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8= -github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= -github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= -github.com/gobwas/ws v1.1.0 h1:7RFti/xnNkMJnrK7D1yQ/iCIB5OrrY/54/H930kIbHA= -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/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= -github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/gohugoio/hugo v0.110.0 h1:FBBypy+UXD9BWaMReIZ+y2FSxFwlJCqoutVi7jKa90o= -github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= -github.com/golang-migrate/migrate/v4 v4.15.2 h1:vU+M05vs6jWHKDdmE1Ecwj0BznygFc4QsdRe2E/L7kc= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ= -github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -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/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= -github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= -github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -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.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= -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.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= -github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= -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/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= -github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= -github.com/google/flatbuffers v23.1.21+incompatible h1:bUqzx/MXCDxuS0hRJL2EfjyZL3uQrPbMocUa8zGqsTA= -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.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2/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.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-github/v43 v43.0.1-0.20220414155304-00e42332e405 h1:DdHws/YnnPrSywrjNYu2lEHqYHWp/LnEx56w59esd54= -github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -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.1.2/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/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= -github.com/googleapis/enterprise-certificate-proxy v0.2.1 h1:RY7tHKZcRlk788d5WSo/e83gOyyy742E8GSs771ySpg= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= -github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= -github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM= -github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM= -github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c= -github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= -github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= -github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM= -github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.1 h1:I6ITHEanAwjB0FvaxmGm8pKqmCLR7QIe05ZmO4QAXMw= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.1/go.mod h1:gYC+WX4YJFarA2ie73G2epzt7TBWpo9pzcBnK1g0MSw= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= -github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-checkpoint v0.5.0 h1:MFYpPZCnQqQTE18jFwSII6eUQrD/oxMFp3mlgcqk5mU= -github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= -github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 h1:1/D3zfFHttUKaCaGKZ/dR2roBXv0vKbSCnssIldfQdI= -github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320/go.mod h1:EiZBMaudVLy8fmjf9Npq1dq9RalhveqZG5w/yz3mHWs= -github.com/hashicorp/go-hclog v1.2.1 h1:YQsLlGDJgwhXFpucSPyVbCBviQtjlHv3jLTlp8YmtEw= -github.com/hashicorp/go-hclog v1.2.1/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= -github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= -github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/go-plugin v1.4.4 h1:NVdrSdFRt3SkZtNckJ6tog7gbpRrcbOjQi/rgF7JYWQ= -github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= -github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= -github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -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/golang-lru/v2 v2.0.1 h1:5pv5N1lT1fjLg2VQ5KWc7kmucp2x/kvFOnxuVTqZ6x4= -github.com/hashicorp/golang-lru/v2 v2.0.1/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= -github.com/hashicorp/hc-install v0.4.1-0.20220912074615-4487b02cbcbb h1:0AmumMAu6gi5zXEyXvLKDu/HALK+rIcVBZU5XJNyjRM= -github.com/hashicorp/hcl/v2 v2.14.0 h1:jX6+Q38Ly9zaAJlAjnFVyeNSNCKKW8D0wvyg7vij5Wc= -github.com/hashicorp/hcl/v2 v2.14.0/go.mod h1:e4z5nxYlWNPdDSNYX+ph14EvWYMFm3eP0zIUqPc2jr0= -github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= -github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= -github.com/hashicorp/terraform-exec v0.17.2 h1:EU7i3Fh7vDUI9nNRdMATCEfnm9axzTnad8zszYZ73Go= -github.com/hashicorp/terraform-json v0.14.0 h1:sh9iZ1Y8IFJLx+xQiKHGud6/TSUCM0N8e17dKDpqV7s= -github.com/hashicorp/terraform-plugin-go v0.12.0 h1:6wW9mT1dSs0Xq4LR6HXj1heQ5ovr5GxXNJwkErZzpJw= -github.com/hashicorp/terraform-plugin-go v0.12.0/go.mod h1:kwhmaWHNDvT1B3QiSJdAtrB/D4RaKSY/v3r2BuoWK4M= -github.com/hashicorp/terraform-plugin-log v0.7.0 h1:SDxJUyT8TwN4l5b5/VkiTIaQgY6R+Y2BQ0sRZftGKQs= -github.com/hashicorp/terraform-plugin-log v0.7.0/go.mod h1:p4R1jWBXRTvL4odmEkFfDdhUjHf9zcs/BCoNHAc7IK4= -github.com/hashicorp/terraform-plugin-sdk/v2 v2.20.0 h1:+KxZULPsbjpAVoP0WNj/8aVW6EqpcX5JcUcQ5wl7Da4= -github.com/hashicorp/terraform-plugin-sdk/v2 v2.20.0/go.mod h1:DwGJG3KNxIPluVk6hexvDfYR/MS/eKGpiztJoT3Bbbw= -github.com/hashicorp/terraform-registry-address v0.0.0-20220623143253-7d51757b572c h1:D8aRO6+mTqHfLsK/BC3j5OAoogv1WLRWzY1AaTo3rBg= -github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734 h1:HKLsbzeOsfXmKNpr3GiT18XAblV0BjCbzL8KQAMZGa0= -github.com/hashicorp/yamux v0.0.0-20220718163420-dd80a7ee44ce h1:7FO+LmZwiG/eDsBWo50ZeqV5PoH0gwiM1mxFajXAkas= -github.com/hashicorp/yamux v0.0.0-20220718163420-dd80a7ee44ce/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= -github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3 h1:aSVUgRRRtOrZOC1fYmY9gV0e9z/Iu+xNVSASWjsuyGU= -github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3/go.mod h1:5PC6ZNPde8bBqU/ewGZig35+UIZtw9Ytxez8/q5ZyFE= -github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Goc3h8EklBH5mspfHFxBnEoURQCGzQQH1ga9Myjvis= -github.com/iancoleman/orderedmap v0.2.0 h1:sq1N/TFpYH++aViPcaKjys3bDClUEU7s5B+z6jq8pNA= -github.com/iancoleman/strcase v0.2.0 h1:05I4QRnGpI0m37iZQRuskXh+w77mr6Z41lwQzuHLwW0= -github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/illarion/gonotify v1.0.1 h1:F1d+0Fgbq/sDWjj/r66ekjDG+IDeecQKUFH4wNwsoio= -github.com/illarion/gonotify v1.0.1/go.mod h1:zt5pmDofZpU1f8aqlK0+95eQhoEAn/d4G4B/FjVW4jE= -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.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= -github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/insomniacslk/dhcp v0.0.0-20221215072855-de60144f33f8 h1:Z72DOke2yOK0Ms4Z2LK1E1OrRJXOxSj5DllTz2FYTRg= -github.com/insomniacslk/dhcp v0.0.0-20221215072855-de60144f33f8/go.mod h1:m5WMe03WCvWcXjRnhvaAbAAXdCnu20J5P+mmH44ZzpE= -github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= -github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= -github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901 h1:rp+c0RAYOWj8l6qbCUTSiRLG/iKnW3K3/QfPPuSsBt4= -github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= -github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= -github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= -github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86 h1:elKwZS1OcdQ0WwEDBeqxKwb7WB62QX8bvZ/FJnVXIfk= -github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86/go.mod h1:aFAMtuldEgx/4q7iSGazk22+IcgvtiC+HIimFO9XlS8= -github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw= -github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ= -github.com/jsimonetti/rtnetlink v0.0.0-20201009170750-9c6f07d100c1/go.mod h1:hqoO/u39cqLeBLebZ8fWdE96O7FxrAsRYhnVOdgHxok= -github.com/jsimonetti/rtnetlink v0.0.0-20201110080708-d2c240429e6c/go.mod h1:huN4d1phzjhlOsNIjFsw2SVRbwIHj3fJDMEU2SDPTmg= -github.com/jsimonetti/rtnetlink v1.1.2-0.20220408201609-d380b505068b h1:Yws7RV6kZr2O7PPdT+RkbSmmOponA8i/1DuGHe8BRsM= -github.com/jsimonetti/rtnetlink v1.1.2-0.20220408201609-d380b505068b/go.mod h1:TzDCVOZKUa79z6iXbbXqhtAflVgUKaFkZ21M5tK5tzY= -github.com/json-iterator/go v1.1.9/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/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/justinas/nosurf v1.1.1 h1:92Aw44hjSK4MxJeMSyDa7jwuI9GR2J/JCQiaKvXXSlk= -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.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= -github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= -github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw= -github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4= -github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a h1:+RR6SqnTkDLWyICxS1xpjCi/3dhyV+TgZwA6Ww3KncQ= -github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a/go.mod h1:YTtCCM3ryyfiu4F7t8HQ1mxvp1UBdWM2r6Xa+nGWvDk= -github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= -github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= -github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -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/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= -github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= -github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= -github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.10.6 h1:jbk+ZieJ0D7EVGJYpL9QTz7/YW6UHbmdnZWYyK5cdBs= -github.com/lib/pq v1.10.6/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= -github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= -github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= -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.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.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.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= -github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= -github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI= -github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= -github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= -github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7/go.mod h1:U6ZQobyTjI/tJyq2HG+i/dfSoFUt8/aZCM+GKtmFk/Y= -github.com/mdlayher/genetlink v1.2.0 h1:4yrIkRV5Wfk1WfpWTcoOlGmsWgQj3OtQN9ZsbrE+XtU= -github.com/mdlayher/genetlink v1.2.0/go.mod h1:ra5LDov2KrUCZJiAtEvXXZBxGMInICMXIwshlJ+qRxQ= -github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA= -github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M= -github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcKp9uZHgmY= -github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZmb2XgV7o= -github.com/mdlayher/netlink v1.6.0/go.mod h1:0o3PlBmGst1xve7wQ7j/hwpNaFaH4qCRyWCdcZk8/vA= -github.com/mdlayher/netlink v1.7.1 h1:FdUaT/e33HjEXagwELR8R3/KL1Fq5x3G5jgHLp/BTmg= -github.com/mdlayher/netlink v1.7.1/go.mod h1:nKO5CSjE/DJjVhk/TNp6vCE1ktVxEA8VEh8drhZzxsQ= -github.com/mdlayher/raw v0.0.0-20190606142536-fef19f00fc18/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg= -github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg= -github.com/mdlayher/sdnotify v1.0.0 h1:Ma9XeLVN/l0qpyx1tNeMSeTjCPH6NtuD6/N9XdTlQ3c= -github.com/mdlayher/sdnotify v1.0.0/go.mod h1:HQUmpM4XgYkhDLtd+Uad8ZFK1T9D5+pNxnXQjCeJlGE= -github.com/mdlayher/socket v0.1.1/go.mod h1:mYV5YIZAfHh4dzDVzI8x8tWLWCliuX8Mon5Awbj+qDs= -github.com/mdlayher/socket v0.4.0 h1:280wsy40IC9M9q1uPGcLBwXpcTQDtoGwVt+BNoITxIw= -github.com/mdlayher/socket v0.4.0/go.mod h1:xxFqz5GRCUN3UEOm9CZqEJsAbe1C8OwSK46NlmWuVoc= -github.com/microcosm-cc/bluemonday v1.0.21 h1:dNH3e4PSyE4vNX+KlRGHT5KrSvjeUkoNPwEORjffHJg= -github.com/miekg/dns v1.1.45 h1:g5fRIhm9nx7g8osrAvgb16QJfmyMsyOCb+J7LSv+Qzk= -github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= -github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= -github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= -github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= -github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= -github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= -github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= -github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= -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/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= -github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/moby/moby v20.10.23+incompatible h1:5+Q6jGL7oH89tx+ms0fGsTYEXrQ3P4vuL3i7DayMUuM= -github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU= -github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78= -github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI= -github.com/moby/term v0.0.0-20221205130635-1aeaba878587 h1:HfkjXDfhgVaN5rmueG8cL8KKeFNecRCXFhaJ2qZ5SKA= -github.com/moby/term v0.0.0-20221205130635-1aeaba878587/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= -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.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/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= -github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= -github.com/muesli/termenv v0.14.0 h1:8x9NFfOe8lmIWK4pgy3IfVEy47f+ppe3tUqdPZG2Uy0= -github.com/niklasfasching/go-org v1.6.5 h1:5YAIqNTdl6lAOb7lD2AyQ1RuFGPVrAKvUexphk8PGbo= -github.com/nsf/jsondiff v0.0.0-20200515183724-f29ed568f4ce h1:RPclfga2SEJmgMmz2k+Mg7cowZ8yv4Trqw9UsJby758= -github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= -github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= -github.com/open-policy-agent/opa v0.44.0 h1:sEZthsrWBqIN+ShTMJ0Hcz6a3GkYsY4FaB2S/ou2hZk= -github.com/open-policy-agent/opa v0.44.0/go.mod h1:YpJaFIk5pq89n/k72c1lVvfvR5uopdJft2tMg1CW/yU= -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-rc2 h1:2zx/Stx4Wc5pIPDvIxHXvXtQFW/7XWJGmnM7r3wg034= -github.com/opencontainers/image-spec v1.1.0-rc2/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ= -github.com/opencontainers/runc v1.1.5 h1:L44KXEpKmfWDcS02aeGm8QNTFXTo2D+8MYGDIJ/GDEs= -github.com/opencontainers/runc v1.1.5/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/ory/dockertest/v3 v3.9.1 h1:v4dkG+dlu76goxMiTT2j8zV7s4oPPEppKT8K8p2f1kY= -github.com/ory/dockertest/v3 v3.9.1/go.mod h1:42Ir9hmvaAPm0Mgibk6mBPi7SFvTXxEcnztDYOJ//uM= -github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM= -github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= -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/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= -github.com/pkg/sftp v1.13.6-0.20221018182125-7da137aa03f0 h1:QJypP3NZEUt+ka49zyp/MSdpjjM9EYkg0WA1NZQaxT0= -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/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= -github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= -github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= -github.com/prometheus/common v0.40.0 h1:Afz7EVRqGg2Mqqf4JuF9vdvp1pi220m55Pi9T2JnO4Q= -github.com/prometheus/common v0.40.0/go.mod h1:L65ZJPSmfn/UBWLQIHV7dBrKFidB/wPlF1y5TlSt9OE= -github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= -github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= -github.com/quasilyte/go-ruleguard/dsl v0.3.22 h1:wd8zkOhSNr+I+8Qeciml08ivDt1pSXe60+5DqOpCjPE= -github.com/quasilyte/go-ruleguard/dsl v0.3.22/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU= -github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 h1:MkV+77GLUNo5oJ0jf870itWm3D0Sjh7+Za9gazKc5LQ= -github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= -github.com/rivo/uniseg v0.4.3 h1:utMvzDsuh3suAEnhH0RdHmoPbU648o6CvXxTx4SBMOw= -github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= -github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.8.1-0.20211023094830-115ce09fd6b4 h1:Ha8xCaq6ln1a+R91Km45Oq6lPXj2Mla6CRJYcuV2h1w= -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/seccomp/libseccomp-golang v0.9.2-0.20220502022130-f33da4d89646/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= -github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -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/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.9.4 h1:Sd43wM1IWz/s1aVXdOBkjJvuP8UdyqioeE4AmM0QsBs= -github.com/spf13/afero v1.9.4/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= -github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= -github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= -github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= -github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= -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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -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.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/swaggest/assertjson v1.7.0 h1:SKw5Rn0LQs6UvmGrIdaKQbMR1R3ncXm5KNon+QJ7jtw= -github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe h1:K8pHPVoTgxFJt1lXuIzzOX7zZhZFldJQK/CgKx9BFIc= -github.com/swaggo/http-swagger v1.3.3 h1:Hu5Z0L9ssyBLofaama21iYaF2VbWyA8jdohaaCGpHsc= -github.com/swaggo/swag v1.8.6 h1:2rgOaLbonWu1PLP6G+/rYjSvPg0jQE0HtrEKuE380eg= -github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= -github.com/tabbed/pqtype v0.1.1 h1:PhEcb9JZ8jr7SUjJDFjRPxny0M8fkXZrxn/a9yQfoZg= -github.com/tabbed/pqtype v0.1.1/go.mod h1:HLt2kLJPcUhODQkYn3mJkMHXVsuv3Z2n5NZEeKXL0Uk= -github.com/tailscale/certstore v0.1.1-0.20220316223106-78d6e1c49d8d h1:K3j02b5j2Iw1xoggN9B2DIEkhWGheqFOeDkdJdBrJI8= -github.com/tailscale/certstore v0.1.1-0.20220316223106-78d6e1c49d8d/go.mod h1:2P+hpOwd53e7JMX/L4f3VXkv1G+33ES6IWZSrkIeWNs= -github.com/tailscale/golang-x-crypto v0.0.0-20221102133106-bc99ab8c2d17 h1:cSm67hIDABvL13S0n9TNoVhzYwjb24M46znbABLll18= -github.com/tailscale/golang-x-crypto v0.0.0-20221102133106-bc99ab8c2d17/go.mod h1:95n9fbUCixVSI4QXLEvdKJjnYK2eUlkTx9+QwLPXFKU= -github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05 h1:4chzWmimtJPxRs2O36yuGRW3f9SYV+bMTTvMBI0EKio= -github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05/go.mod h1:PdCqy9JzfWMJf1H5UJW2ip33/d4YkoKN0r67yKH1mG8= -github.com/tailscale/netlink v1.1.1-0.20211101221916-cabfb018fe85 h1:zrsUcqrG2uQSPhaUPjUQwozcRdDdSxxqhNgNZ3drZFk= -github.com/tailscale/netlink v1.1.1-0.20211101221916-cabfb018fe85/go.mod h1:NzVQi3Mleb+qzq8VmcWpSkcSYxXIg0DkI6XDzpVkhJ0= -github.com/tailscale/wireguard-go v0.0.0-20221219190806-4fa124729667 h1:etWp6uUwKu8NEj37K2OuMBnZ7EnVMKA7gJg5AqPFy/o= -github.com/tailscale/wireguard-go v0.0.0-20221219190806-4fa124729667/go.mod h1:iiClgxBTruKI+nmzlQxbFw6c3nB/wb4Td/WCyX2berY= -github.com/tchap/go-patricia/v2 v2.3.1 h1:6rQp39lgIYZ+MHmdEq4xzuk1t7OdC35z/xm0BGhTkes= -github.com/tchap/go-patricia/v2 v2.3.1/go.mod h1:VZRHKAb53DLaG+nA9EaYYiaEx6YztwDlLElMsnSHD4k= -github.com/tcnksm/go-httpstat v0.2.0 h1:rP7T5e5U2HfmOBmZzGgGZjBQ5/GluWUylujl0tJ04I0= -github.com/tcnksm/go-httpstat v0.2.0/go.mod h1:s3JVJFtQxtBEBC9dwcdTTXS9xFnM3SXAZwPG41aurT8= -github.com/tdewolff/parse/v2 v2.6.4 h1:KCkDvNUMof10e3QExio9OPZJT8SbdKojLBumw8YZycQ= -github.com/u-root/u-root v0.11.0 h1:6gCZLOeRyevw7gbTwMj3fKxnr9+yHFlgF3N7udUVNO8= -github.com/u-root/uio v0.0.0-20221213070652-c3537552635f h1:dpx1PHxYqAnXzbryJrWP1NQLzEjwcVgFLhkknuFQ7ww= -github.com/u-root/uio v0.0.0-20221213070652-c3537552635f/go.mod h1:IogEAUBXDEwX7oR/BMmCctShYs80ql4hF0ySdzGxf7E= -github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= -github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= -github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= -github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= -github.com/unrolled/secure v1.13.0 h1:sdr3Phw2+f8Px8HE5sd1EHdj1aV3yUwed/uZXChLFsk= -github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= -github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasthttp v1.44.0 h1:R+gLUhldIsfg1HokMuQjdQ5bh9nuXHPIfvkYUu9eR5Q= -github.com/valyala/fasthttp v1.44.0/go.mod h1:f6VbjjoI3z1NDOZOv17o6RvtRSWxC77seBFc2uWtgiY= -github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= -github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= -github.com/vishvananda/netlink v1.1.1-0.20211118161826-650dca95af54 h1:8mhqcHPqTMhSPoslhGYihEgSfc77+7La1P6kiB6+9So= -github.com/vishvananda/netlink v1.1.1-0.20211118161826-650dca95af54/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= -github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= -github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= -github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg= -github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= -github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= -github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI= -github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= -github.com/vmihailenco/msgpack/v4 v4.3.12 h1:07s4sz9IReOgdikxLTKNbBdqDMLsjPKXwvCazn8G65U= -github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4= -github.com/vmihailenco/tagparser v0.1.1 h1:quXMXlA39OCbd2wAdTsGDlK9RkOk6Wuw+x37wVyIuWY= -github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= -github.com/wagslane/go-password-validator v0.3.0 h1:vfxOPzGHkz5S146HDpavl0cw1DSVP061Ry2PX0/ON6I= -github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= -github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= -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/yashtewari/glob-intersection v0.1.0 h1:6gJvMYQlTDOL3dMsPF6J0+26vwX9MB8/1q3uAdhmTrg= -github.com/yashtewari/glob-intersection v0.1.0/go.mod h1:LK7pIC3piUjovexikBbJ26Yml7g8xa5bsjfx2v1fwok= -github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA= -github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M= -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.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.5.3 h1:3HUJmBFbQW9fhQOzMgseU134xfi6hU+mjWywx5Ty+/M= -github.com/yuin/goldmark-emoji v1.0.1 h1:ctuWEyzGBwiucEqxzwe0SOYDXPAucOrE9NQC18Wa1os= -github.com/zclconf/go-cty v1.10.0 h1:mp9ZXQeIcN8kAwuqorjH+Q+njbJKjLrvB2yIh4q7U+0= -github.com/zclconf/go-cty v1.10.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= -github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= -github.com/zeebo/errs v1.3.0 h1:hmiaKqgYZzcVgRL1Vkc1Mn2914BbzB0IBxs+ebeutGs= -github.com/zeebo/errs v1.3.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= -go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1 h1:A/5uWzF44DlIgdm/PQFwfMkW0JX+cIcQi/SwLAmZP5M= -go.nhat.io/otelsql v0.7.0 h1:TBxa7dbHokzEZdo1m4ZGbgjrO822DDJVHbIKvaVRDBI= -go.nhat.io/otelsql v0.7.0/go.mod h1:eSIg4NPdvODcSUUCnypyMpi7CXDurAT0a4JvJBuPJTE= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= -go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= -go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= -go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.opentelemetry.io/otel v1.11.1 h1:4WLLAmcfkmDk2ukNXJyq3/kiz/3UzCaYq6PskJsaou4= -go.opentelemetry.io/otel v1.11.1/go.mod h1:1nNhXBbWSD0nsL38H6btgnFN2k4i0sNLHNNMZMSbUGE= -go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.11.1 h1:X2GndnMCsUPh6CiY2a+frAbNsXaPLbB0soHRYhAZ5Ig= -go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.11.1/go.mod h1:i8vjiSzbiUC7wOQplijSXMYUpNM93DtlS5CbUT+C6oQ= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.11.1 h1:MEQNafcNCB0uQIti/oHgU7CZpUMYQ7qigBwMVKycHvc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.11.1/go.mod h1:19O5I2U5iys38SsmT2uDJja/300woyzE1KPIQxEUBUc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.11.1 h1:LYyG/f1W/jzAix16jbksJfMQFpOH/Ma6T639pVPMgfI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.11.1/go.mod h1:QrRRQiY3kzAoYPNLP0W/Ikg0gR6V3LMc+ODSxr7yyvg= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.11.1 h1:tFl63cpAAcD9TOU6U8kZU7KyXuSRYAZlbx1C61aaB74= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.11.1/go.mod h1:X620Jww3RajCJXw/unA+8IRTgxkdS7pi+ZwK9b7KUJk= -go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v0.33.0 h1:hlnyYcK61UzruaUssIZvCHl72qSxGB1R55RexLKjFs8= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.11.1 h1:3Yvzs7lgOw8MmbxmLRsQGwYdCubFmUHSooKaEhQunFQ= -go.opentelemetry.io/otel/metric v0.33.0 h1:xQAyl7uGEYvrLAiV/09iTJlp1pZnQ9Wl793qbVvED1E= -go.opentelemetry.io/otel/metric v0.33.0/go.mod h1:QlTYc+EnYNq/M2mNk1qDDMRLpqCOj2f/r5c7Fd5FYaI= -go.opentelemetry.io/otel/sdk v1.11.1 h1:F7KmQgoHljhUuJyA+9BiU+EkJfyX5nVVF4wyzWZpKxs= -go.opentelemetry.io/otel/sdk v1.11.1/go.mod h1:/l3FE4SupHJ12TduVjUkZtlfFqDCQJlOlithYrdktys= -go.opentelemetry.io/otel/sdk/metric v0.33.0 h1:oTqyWfksgKoJmbrs2q7O7ahkJzt+Ipekihf8vhpa9qo= -go.opentelemetry.io/otel/trace v1.11.1 h1:ofxdnzsNrGBYXbP7t7zpUK281+go5rF7dvdIZXF8gdQ= -go.opentelemetry.io/otel/trace v1.11.1/go.mod h1:f/Q9G7vzk5u91PhbmKbg1Qn0rzH1LJ4vbPHFGkTPtOk= -go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -go.opentelemetry.io/proto/otlp v0.19.0 h1:IVN6GR+mhC4s5yfcTbmzHYODqvWAp3ZedA2SJPI1Nnw= -go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= -go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= -go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= -go4.org/intern v0.0.0-20211027215823-ae77deb06f29 h1:UXLjNohABv4S58tHmeuIZDO6e3mHpW2Dx33gaNt03LE= -go4.org/mem v0.0.0-20210711025021-927187094b94 h1:OAAkygi2Js191AJP1Ds42MhJRgeofeKGjuoUqNp1QC4= -go4.org/mem v0.0.0-20210711025021-927187094b94/go.mod h1:reUoABIJ9ikfM5sgtSF3Wushcza7+WeD01VB9Lirh3g= -go4.org/netipx v0.0.0-20220725152314-7e7bdc8411bf h1:IdwJUzqoIo5lkr2EOyKoe5qipUaEjbOKKY5+fzPBZ3A= -go4.org/netipx v0.0.0-20220725152314-7e7bdc8411bf/go.mod h1:+QXzaoURFd0rGDIjDNpyIkv+F9R7EmeKorvlKRnhqgA= -go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760 h1:FyBZqvoA/jbNzuAWLQE2kG820zMAkcilx6BMjGbL/E4= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc= -golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug= -golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= -golang.org/x/exp/typeparams v0.0.0-20221208152030-732eee02a75a h1:Jw5wfR+h9mnIYH+OtGT2im5wV1YGGDora5vTv/aa5bE= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -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-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/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.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs= -golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -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-20190108225652-1e06a53dbb7e/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-20190419010253-1f3472d942ba/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/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-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/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-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/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-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= -golang.org/x/net v0.0.0-20220906165146-f3363e06e74c/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= -golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= -golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= -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/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= -golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= -golang.org/x/oauth2 v0.5.0 h1:HuArIo48skDwlrvM3sEdHXElYslAMsf3KwRkkW4MC4s= -golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= -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-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/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-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/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/sys v0.0.0-20180830151530-49385e6e1522/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-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190418153312-f0ce4c0180be/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-20190606122018-79a91cf218c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200217220822-9197077df867/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-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210301091718-77cc2087c03b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -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-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/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-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/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-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/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-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/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-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.4.1-0.20230131160137-e7d7f63158de/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/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.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw= -golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/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.5/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.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= -golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/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.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -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-20190312151545-0bb0c0a6e846/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-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/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-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= -golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= -golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= -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/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= -golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg= -golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= -golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE= -golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= -google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= -google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= -google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= -google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= -google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= -google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= -google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= -google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= -google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= -google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= -google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= -google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= -google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= -google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= -google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g= -google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA= -google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8= -google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs= -google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= -google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw= -google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg= -google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o= -google.golang.org/api v0.108.0 h1:WVBc/faN0DkKtR43Q/7+tPny9ZoLZdIiAyG5Q9vFClg= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -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-20190418145605-e7d98fc518a7/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-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210329143202-679c6ae281ee/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= -google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= -google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= -google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= -google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= -google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= -google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= -google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= -google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= -google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= -google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= -google.golang.org/genproto v0.0.0-20220518221133-4f43b3371335/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= -google.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= -google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/genproto v0.0.0-20230223222841-637eb2293923 h1:znp6mq/drrY+6khTAlJUDNFFcDGV2ENLYKpMq8SyCds= -google.golang.org/genproto v0.0.0-20230223222841-637eb2293923/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= -google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= -google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= -google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= -google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= -google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= -google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= -google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.53.0 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc= -google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= -google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= -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.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.28.2-0.20230118093459-a9481185b34d h1:qp0AnQCvRCMlu9jBjtdbTaaEmThIgZOrbVyDEOcmKhQ= -google.golang.org/protobuf v1.28.2-0.20230118093459-a9481185b34d/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -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-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI= -gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8/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.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o= -gvisor.dev/gvisor v0.0.0-20221203005347-703fd9b7fbc0 h1:Wobr37noukisGxpKo5jAsLREcpj61RxrWYzD8uwveOY= -gvisor.dev/gvisor v0.0.0-20221203005347-703fd9b7fbc0/go.mod h1:Dn5idtptoW1dIos9U6A2rpebLs/MtTwFacjKb8jLdQA= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/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= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.4.0-0.dev.0.20230130122044-c30b15588105 h1:2OzOQ+1scFmv2dt7x+wNxgikA/Rn2qKrvc/CJCVuAJM= -howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM= -inet.af/peercred v0.0.0-20210906144145-0893ea02156a h1:qdkS8Q5/i10xU2ArJMKYhVa1DORzBfYS/qA2UK2jheg= -inet.af/peercred v0.0.0-20210906144145-0893ea02156a/go.mod h1:FjawnflS/udxX+SvpsMgZfdqx2aykOlkISeAsADi5IU= -k8s.io/klog/v2 v2.80.1 h1:atnLQ121W371wYYFawwYx1aEY2eUfs4l3J72wtgAwV4= -k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= -k8s.io/mount-utils v0.26.2 h1:KoRKqCAAK2l37l71YMvKx6vaLToh52RkNx1RU/dSLGQ= -k8s.io/mount-utils v0.26.2/go.mod h1:95yx9K6N37y8YZ0/lUh9U6ITosMODNaW0/v4wvaa0Xw= -k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5 h1:kmDqav+P+/5e1i9tFfHq1qcF3sOrDp+YEkVDAHu7Jwk= -k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g= -nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -software.sslmate.com/src/go-pkcs12 v0.2.0 h1:nlFkj7bTysH6VkC4fGphtjXRbezREPgrHuJG20hBGPE= -storj.io/drpc v0.0.33-0.20220622181519-9206537a4db7 h1:6jIp39oQGZMjfrG3kiafK2tcL0Fbprh2kvaoJNfhvuM= -storj.io/drpc v0.0.33-0.20220622181519-9206537a4db7/go.mod h1:6rcOyR/QQkSTX/9L5ZGtlZaE2PtXTTZl8d+ulSeeYEg= diff --git a/integration/doc.go b/integration/doc.go deleted file mode 100644 index 7f37830..0000000 --- a/integration/doc.go +++ /dev/null @@ -1,3 +0,0 @@ -// Package integration contains integration tests for envbox. They create -// real envbox containers and validate functionality. -package integration diff --git a/integration/docker_test.go b/integration/docker_test.go deleted file mode 100644 index 37ef0e1..0000000 --- a/integration/docker_test.go +++ /dev/null @@ -1,258 +0,0 @@ -//go:build integration -// +build integration - -package integration_test - -import ( - "fmt" - "os" - "path/filepath" - "strings" - "testing" - "time" - - dockertest "github.com/ory/dockertest/v3" - "github.com/stretchr/testify/require" - - "github.com/coder/envbox/cli" - "github.com/coder/envbox/integration/integrationtest" -) - -func TestDocker(t *testing.T) { - t.Parallel() - - // Dockerd just tests that dockerd can spin up and function correctly. - t.Run("Dockerd", func(t *testing.T) { - t.Parallel() - - pool, err := dockertest.NewPool("") - require.NoError(t, err) - - var ( - tmpdir = integrationtest.TmpDir(t) - binds = integrationtest.DefaultBinds(t, tmpdir) - ) - - runEnvbox := func() *dockertest.Resource { - // Run the envbox container. - resource := integrationtest.RunEnvbox(t, pool, &integrationtest.CreateDockerCVMConfig{ - Image: integrationtest.DockerdImage, - Username: "root", - Binds: binds, - }) - - // Wait for the inner container's docker daemon. - integrationtest.WaitForCVMDocker(t, pool, resource, time.Minute) - - // Assert that we can run docker in the inner container. - _, err = integrationtest.ExecInnerContainer(t, pool, integrationtest.ExecConfig{ - ContainerID: resource.Container.ID, - Cmd: []string{"docker", "run", integrationtest.HelloWorldImage}, - }) - - require.NoError(t, err) - return resource - } - - // Run envbox initially, this tests the initial creation of a workspace. - resource := runEnvbox() - - t.Logf("envbox %q started successfully, recreating...", resource.Container.ID) - - // Destroy the container, we're going to recreate it to ensure that when volumes are reused - // IDs are still mapped correctly. - err = resource.Close() - require.NoError(t, err) - - // Run envbox again to test that when we restart a workspace things still - // work correctly. - _ = runEnvbox() - }) - - // EnvboxArgs validates that arguments passed to envbox function correctly. - // Most cases should be covered with unit tests, the intent with this is to - // test cases that do not garner a high degree of confidence from mocking - // (such as creating devices e.g. FUSE, TUN). - t.Run("EnvboxArgs", func(t *testing.T) { - t.Parallel() - - pool, err := dockertest.NewPool("") - require.NoError(t, err) - - var ( - tmpdir = integrationtest.TmpDir(t) - binds = integrationtest.DefaultBinds(t, tmpdir) - ) - - homeDir := filepath.Join(tmpdir, "home") - err = os.MkdirAll(homeDir, 0o777) - require.NoError(t, err) - - // Emulate someone wanting to mount a secret into envbox. - secretDir := filepath.Join(tmpdir, "secrets") - err = os.MkdirAll(secretDir, 0o777) - require.NoError(t, err) - - binds = append(binds, - bindMount(homeDir, "/home/coder", false), - bindMount(secretDir, "/var/secrets", true), - ) - - var ( - envFilter = []string{ - "KUBERNETES_*", - "HELLO=world", - "TEST_ME=pls", - } - - envs = []string{ - "KUBERNETES_PORT=123", - "KUBERNETES_SERVICE_HOST=10.0.1", - "HELLO=world", - "TEST_ME=pls", - "ENVBOX_ONLY=hi", - "TEST_ME_PLS=hmm", - // Add a mount mapping to the inner container. - fmt.Sprintf("%s=%s:%s,%s:%s:ro", cli.EnvMounts, "/home/coder", "/home/coder", "/var/secrets", "/var/secrets"), - } - ) - - // We touch /home/coder/.coder/foo because it asserts that we're - // making the directory that ultimately will host the agent for Coder. - // We set this as the BINARY_DIR that we pass to the startup script - // so that we can avoid the race that occurs where systemd is remounting - // /tmp while we are downloading the agent binary /tmp/coder.XXX. - bootstrapScript := `#!/usr/bin/env bash - - echo "hello" > /home/coder/bootstrap - mkdir /home/coder/bar - touch /home/coder/.coder/foo -` - - // Run the envbox container. - resource := integrationtest.RunEnvbox(t, pool, &integrationtest.CreateDockerCVMConfig{ - Image: integrationtest.UbuntuImage, - Username: "coder", - InnerEnvFilter: envFilter, - Envs: envs, - Binds: binds, - AddFUSE: true, - AddTUN: true, - BootstrapScript: bootstrapScript, - }) - - // Validate that the envs are set correctly. - vars, err := integrationtest.ExecInnerContainer(t, pool, integrationtest.ExecConfig{ - ContainerID: resource.Container.ID, - Cmd: []string{"env"}, - }) - require.NoError(t, err) - - envVars := strings.Split(string(vars), "\n") - - requireSliceContains(t, envVars, - "KUBERNETES_PORT=123", - "KUBERNETES_SERVICE_HOST=10.0.1", - "HELLO=world", - "TEST_ME=pls", - ) - requireSliceNoContains(t, envVars, - "ENVBOX_ONLY=hi", - "TEST_ME_PLS=hmm", - ) - - // Assert that the FUSE device exists. - _, err = integrationtest.ExecInnerContainer(t, pool, integrationtest.ExecConfig{ - ContainerID: resource.Container.ID, - Cmd: []string{"stat", cli.InnerFUSEPath}, - }) - require.NoError(t, err) - - // Assert that the TUN device exists. - _, err = integrationtest.ExecInnerContainer(t, pool, integrationtest.ExecConfig{ - ContainerID: resource.Container.ID, - Cmd: []string{"stat", cli.InnerTUNPath}, - }) - require.NoError(t, err) - - // Assert that the home directory exists and has the correct shifted permissions. - homeDirUID, err := integrationtest.ExecEnvbox(t, pool, integrationtest.ExecConfig{ - ContainerID: resource.Container.ID, - Cmd: []string{"stat", `--format=%u`, "/home/coder"}, - }) - require.NoError(t, err) - require.Equal(t, "101000", strings.TrimSpace(string(homeDirUID))) - - // Validate that we can touch files in the home directory. - _, err = integrationtest.ExecInnerContainer(t, pool, integrationtest.ExecConfig{ - ContainerID: resource.Container.ID, - Cmd: []string{"touch", "/home/coder/foo"}, - User: "coder", - }) - require.NoError(t, err) - - secretsDirUID, err := integrationtest.ExecEnvbox(t, pool, integrationtest.ExecConfig{ - ContainerID: resource.Container.ID, - Cmd: []string{"stat", "--format=%u", "/var/secrets"}, - }) - require.NoError(t, err) - require.Equal(t, "100000", strings.TrimSpace(string(secretsDirUID))) - - // Validate that we cannot touch files in this case since it should be a - // read only mount. - _, err = integrationtest.ExecInnerContainer(t, pool, integrationtest.ExecConfig{ - ContainerID: resource.Container.ID, - Cmd: []string{"touch", "/var/secrets/foo"}, - }) - require.Error(t, err) - // Make sure the error is actually because of a read only filesystem - // and not some random other error. - require.Contains(t, err.Error(), "Read-only file system") - - // Validate that the bootstrap script ran. - out, err := integrationtest.ExecInnerContainer(t, pool, integrationtest.ExecConfig{ - ContainerID: resource.Container.ID, - Cmd: []string{"cat", "/home/coder/bootstrap"}, - }) - require.NoError(t, err) - require.Equal(t, "hello", strings.TrimSpace(string(out))) - - // Validate that the bootstrap script ran. - out, err = integrationtest.ExecInnerContainer(t, pool, integrationtest.ExecConfig{ - ContainerID: resource.Container.ID, - Cmd: []string{"stat", "--format=%u", "/home/coder/bar"}, - }) - require.NoError(t, err) - require.Equal(t, "1000", strings.TrimSpace(string(out))) - }) -} - -func requireSliceNoContains(t *testing.T, ss []string, els ...string) { - for _, e := range els { - for _, s := range ss { - if s == e { - t.Fatalf("unexpectedly found %q in %+v", e, ss) - } - } - } -} - -func requireSliceContains(t *testing.T, ss []string, els ...string) { - for _, e := range els { - var found bool - for _, s := range ss { - if s == e { - found = true - break - } - } - require.True(t, found, "expected to find %s in %+v", e, ss) - } -} - -func bindMount(src, dest string, ro bool) string { - if ro { - return fmt.Sprintf("%s:%s:%s", src, dest, "ro") - } - return fmt.Sprintf("%s:%s", src, dest) -} diff --git a/integration/integrationtest/docker.go b/integration/integrationtest/docker.go deleted file mode 100644 index 0da772e..0000000 --- a/integration/integrationtest/docker.go +++ /dev/null @@ -1,296 +0,0 @@ -package integrationtest - -import ( - "bufio" - "bytes" - "context" - "encoding/json" - "fmt" - "io" - "os" - "path/filepath" - "strings" - "testing" - "time" - - "github.com/ory/dockertest/v3" - "github.com/ory/dockertest/v3/docker" - "github.com/stretchr/testify/require" - "golang.org/x/xerrors" - - "github.com/coder/envbox/buildlog" - "github.com/coder/envbox/cli" - "github.com/coder/retry" -) - -const ( - // DockerdImage is a large image (~1GB) and should only be used to test - // dockerd. - DockerdImage = "gcr.io/coder-dev-1/sreya/enterprise-base:ubuntu" - // HelloWorldImage is useful for testing a CVM's dockerd is functioning - // correctly - HelloWorldImage = "gcr.io/coder-dev-1/sreya/hello-world" - // UbuntuImage is just vanilla ubuntu (80MB) but the user is set to a non-root - // user . - UbuntuImage = "gcr.io/coder-dev-1/sreya/ubuntu-coder" -) - -// TODO use df to determine if an environment is running in a docker container or not. - -type CreateDockerCVMConfig struct { - Image string - Username string - BootstrapScript string - InnerEnvFilter []string - Envs []string - Binds []string - Mounts []string - AddFUSE bool - AddTUN bool -} - -func (c CreateDockerCVMConfig) validate(t *testing.T) { - t.Helper() - - if c.Image == "" { - t.Fatalf("an image must be provided") - } - - if c.Username == "" { - t.Fatalf("a username must be provided") - } -} - -// RunEnvbox runs envbox, it returns once the inner container has finished -// spinning up. -func RunEnvbox(t *testing.T, pool *dockertest.Pool, conf *CreateDockerCVMConfig) *dockertest.Resource { - t.Helper() - - conf.validate(t) - - // If binds aren't passed then we'll just create the minimum amount. - // If someone is passing them we'll assume they know what they're doing. - if conf.Binds == nil { - tmpdir := TmpDir(t) - conf.Binds = DefaultBinds(t, tmpdir) - } - - conf.Envs = append(conf.Envs, cmdLineEnvs(conf)...) - - resource, err := pool.RunWithOptions(&dockertest.RunOptions{ - Repository: "envbox", - Tag: "latest", - Entrypoint: []string{"/envbox", "docker"}, - Env: conf.Envs, - }, func(host *docker.HostConfig) { - host.Binds = conf.Binds - host.Privileged = true - }) - require.NoError(t, err) - // t.Cleanup(func() { _ = pool.Purge(resource) }) - - waitForCVM(t, pool, resource) - - return resource -} - -// TmpDir returns a subdirectory in /tmp that can be used for test files. -func TmpDir(t *testing.T) string { - // We use os.MkdirTemp as oposed to t.TempDir since the envbox container will - // chown some of the created directories here to root:root causing the cleanup - // function to fail once the test exits. - tmpdir, err := os.MkdirTemp("", strings.Replace(t.Name(), "/", "_", -1)) - require.NoError(t, err) - t.Logf("using tmpdir %s", tmpdir) - return tmpdir -} - -// DefaultBinds returns the minimum amount of mounts necessary to spawn -// envbox successfully. Since envbox will chown some of these directories -// to root, they cannot be cleaned up post-test, meaning that it may be -// necesssary to manually clear /tmp from time to time. -func DefaultBinds(t *testing.T, rootDir string) []string { - t.Helper() - - // Create a bunch of mounts for the envbox container. Some proceses - // cannot run ontop of overlayfs because they also use overlayfs - // and so we need to pass vanilla ext4 filesystems for these processes - // to use. - - // Create a mount for the inner docker directory. - cntDockerDir := filepath.Join(rootDir, "coder", "docker") - err := os.MkdirAll(cntDockerDir, 0o777) - require.NoError(t, err) - - // Create a mount for the inner docker directory. - cntDir := filepath.Join(rootDir, "coder", "containers") - err = os.MkdirAll(cntDir, 0o777) - require.NoError(t, err) - - // Create a mount for envbox's docker directory. - dockerDir := filepath.Join(rootDir, "docker") - err = os.MkdirAll(dockerDir, 0o777) - require.NoError(t, err) - - // Create a mount for sysbox. - sysbox := filepath.Join(rootDir, "sysbox") - err = os.MkdirAll(sysbox, 0o777) - require.NoError(t, err) - - return []string{ - fmt.Sprintf("%s:%s", cntDockerDir, "/var/lib/coder/docker"), - fmt.Sprintf("%s:%s", cntDir, "/var/lib/coder/containers"), - "/usr/src:/usr/src", - "/lib/modules:/lib/modules", - fmt.Sprintf("%s:/var/lib/sysbox", sysbox), - fmt.Sprintf("%s:/var/lib/docker", dockerDir), - } -} - -// WaitForCVMDocker waits for the inner container docker daemon to spin up. -func WaitForCVMDocker(t *testing.T, pool *dockertest.Pool, resource *dockertest.Resource, timeout time.Duration) { - t.Helper() - - ctx, cancel := context.WithTimeout(context.Background(), timeout) - defer cancel() - - for r := retry.New(time.Second, time.Second); r.Wait(ctx); { - _, err := ExecInnerContainer(t, pool, ExecConfig{ - ContainerID: resource.Container.ID, - Cmd: []string{"docker", "info"}, - }) - if err == nil { - break - } - } -} - -// waitForCVM waits for the inner container to spin up. -func waitForCVM(t *testing.T, pool *dockertest.Pool, resource *dockertest.Resource) { - t.Helper() - - rd, wr := io.Pipe() - defer rd.Close() - defer wr.Close() - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - go func() { - defer wr.Close() - err := pool.Client.Logs(docker.LogsOptions{ - Context: ctx, - Container: resource.Container.ID, - OutputStream: wr, - ErrorStream: wr, - Follow: true, - Stdout: true, - Stderr: true, - }) - if ctx.Err() == nil { - // Only check if error is nil if we didn't cancel the context. - require.NoError(t, err) - } - }() - - scanner := bufio.NewScanner(rd) - var finished bool - for scanner.Scan() { - log := scanner.Text() - - t.Log(log) - var blog buildlog.JSONLog - if err := json.Unmarshal([]byte(log), &blog); err != nil { - continue - } - - if blog.Type == buildlog.JSONLogTypeDone { - finished = true - break - } - - if blog.Type == buildlog.JSONLogTypeError { - t.Fatalf("envbox failed (%s)", blog.Output) - } - } - require.NoError(t, scanner.Err()) - require.True(t, finished, "unexpected logger exit") -} - -type ExecConfig struct { - ContainerID string - Cmd []string - User string -} - -// ExecInnerContainer runs a command in the inner container. -func ExecInnerContainer(t *testing.T, pool *dockertest.Pool, conf ExecConfig) ([]byte, error) { - t.Helper() - - conf.Cmd = append([]string{"docker", "exec", "workspace_cvm"}, conf.Cmd...) - return ExecEnvbox(t, pool, conf) -} - -// ExecEnvbox runs a command in the outer container. -func ExecEnvbox(t *testing.T, pool *dockertest.Pool, conf ExecConfig) ([]byte, error) { - t.Helper() - - exec, err := pool.Client.CreateExec(docker.CreateExecOptions{ - Cmd: conf.Cmd, - AttachStdout: true, - AttachStderr: true, - User: conf.User, - Container: conf.ContainerID, - }) - require.NoError(t, err) - - var buf bytes.Buffer - err = pool.Client.StartExec(exec.ID, docker.StartExecOptions{ - OutputStream: &buf, - ErrorStream: &buf, - }) - require.NoError(t, err) - - insp, err := pool.Client.InspectExec(exec.ID) - require.NoError(t, err) - require.Equal(t, false, insp.Running) - - if insp.ExitCode > 0 { - return nil, xerrors.Errorf("output(%s): exit code %d", buf.Bytes(), insp.ExitCode) - } - - return buf.Bytes(), nil -} - -// cmdLineEnvs returns args passed to the /envbox command -// but using their env var alias. -func cmdLineEnvs(c *CreateDockerCVMConfig) []string { - envs := []string{ - envVar(cli.EnvInnerImage, c.Image), - envVar(cli.EnvInnerUsername, c.Username), - } - - if len(c.InnerEnvFilter) > 0 { - envs = append(envs, envVar(cli.EnvInnerEnvs, strings.Join(c.InnerEnvFilter, ","))) - } - - if len(c.Mounts) > 0 { - envs = append(envs, envVar(cli.EnvMounts, strings.Join(c.Mounts, ","))) - } - - if c.AddFUSE { - envs = append(envs, envVar(cli.EnvAddFuse, "true")) - } - - if c.AddTUN { - envs = append(envs, envVar(cli.EnvAddTun, "true")) - } - - if c.BootstrapScript != "" { - envs = append(envs, envVar(cli.EnvBootstrap, c.BootstrapScript)) - } - - return envs -} - -func envVar(k, v string) string { - return fmt.Sprintf("%s=%s", k, v) -} diff --git a/integration/main_test.go b/integration/main_test.go deleted file mode 100644 index b631d7f..0000000 --- a/integration/main_test.go +++ /dev/null @@ -1,36 +0,0 @@ -//go:build integration -// +build integration - -package integration - -import ( - "fmt" - "os" - "os/exec" - "strings" - "testing" -) - -func TestMain(m *testing.M) { - // Build the latest version of envbox to keep in sync with any developer - // changes. - buildEnvbox() - - os.Exit(m.Run()) -} - -func buildEnvbox() { - dir, err := exec.Command("git", "rev-parse", "--show-toplevel").CombinedOutput() - mustf(err, "output (%s)", string(dir)) - - cmd := exec.Command("make", "-j", "build/image/envbox") - cmd.Dir = strings.TrimSpace(string(dir)) - out, err := cmd.CombinedOutput() - mustf(err, "make output (%s)", string(out)) -} - -func mustf(err error, msg string, args ...interface{}) { - if err != nil { - panic(fmt.Sprintf(msg+": %v", append(args, err)...)) - } -} diff --git a/scripts/check_unstaged.sh b/scripts/check_unstaged.sh deleted file mode 100755 index 9bab70a..0000000 --- a/scripts/check_unstaged.sh +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -# cdroot changes directory to the root of the repository. -PROJECT_ROOT="$(git rev-parse --show-toplevel)" - -cdroot() { - cd "$PROJECT_ROOT" || error "Could not change directory to '$PROJECT_ROOT'" -} - -# error prints an error message and returns an error exit code. -error() { - log "ERROR: $*" - exit 1 -} - -# log prints a message to stderr. -log() { - echo "$*" 1>&2 -} - -cdroot - -FILES="$(git ls-files --other --modified --exclude-standard)" -if [[ "$FILES" != "" ]]; then - mapfile -t files <<<"$FILES" - - log - log "The following files contain unstaged changes:" - log - for file in "${files[@]}"; do - log " - $file" - done - - log - log "These are the changes:" - log - for file in "${files[@]}"; do - git --no-pager diff "$file" 1>&2 - done - - log - error "Unstaged changes, see above for details." -fi diff --git a/scripts/rules.go b/scripts/rules.go deleted file mode 100644 index 1dd77a2..0000000 --- a/scripts/rules.go +++ /dev/null @@ -1,38 +0,0 @@ -// Package gorules defines custom lint rules for ruleguard. -// -// golangci-lint runs these rules via go-critic, which includes support -// for ruleguard. All Go files in this directory define lint rules -// in the Ruleguard DSL; see: -// -// - https://go-ruleguard.github.io/by-example/ -// - https://pkg.go.dev/github.com/quasilyte/go-ruleguard/dsl -// -// You run one of the following commands to execute your go rules only: -// -// golangci-lint run -// golangci-lint run --disable-all --enable=gocritic -// -// Note: don't forget to run `golangci-lint cache clean`! -package gorules - -import ( - "github.com/quasilyte/go-ruleguard/dsl" -) - -// Use xerrors everywhere! It provides additional stacktrace info! -// -//nolint:unused,deadcode,varnamelen -func xerrors(m dsl.Matcher) { - m.Import("errors") - m.Import("fmt") - m.Import("golang.org/x/xerrors") - - m.Match("fmt.Errorf($*args)"). - Suggest("xerrors.New($args)"). - Report("Use xerrors to provide additional stacktrace information!") - - m.Match("errors.$_($msg)"). - Where(m["msg"].Type.Is("string")). - Suggest("xerrors.New($msg)"). - Report("Use xerrors to provide additional stacktrace information!") -} diff --git a/slogkubeterminate/doc.go b/slogkubeterminate/doc.go deleted file mode 100644 index f396688..0000000 --- a/slogkubeterminate/doc.go +++ /dev/null @@ -1,5 +0,0 @@ -// Package slogkubeterminate defines a slogger meant for use in Kubernetes -// containers to log the termination "reason" on log of slog.LevelFatal. -// -// reference: https://kubernetes.io/docs/tasks/debug-application-cluster/determine-reason-pod-failure/ -package slogkubeterminate diff --git a/slogkubeterminate/slogger.go b/slogkubeterminate/slogger.go deleted file mode 100644 index 045e5d0..0000000 --- a/slogkubeterminate/slogger.go +++ /dev/null @@ -1,33 +0,0 @@ -package slogkubeterminate - -import ( - "context" - "os" - - "cdr.dev/slog" -) - -const defaultKubeTerminationLog = "/dev/termination-log" - -// Make a sink that populates the given termination log on calls to .Fatal(). -func MakeCustom(log string) slog.Sink { - return slogger{log: log} -} - -// Make a sink that populates the default kube termination log on calls to .Fatal(). -func Make() slog.Sink { - return slogger{log: defaultKubeTerminationLog} -} - -type slogger struct { - log string -} - -func (s slogger) LogEntry(_ context.Context, e slog.SinkEntry) { - // write to the termination file so that the Pod failure "reason" is populated properly - if e.Level == slog.LevelFatal { - _ = os.WriteFile(s.log, []byte(e.Message), 0o600) - } -} - -func (slogger) Sync() {} diff --git a/slogkubeterminate/slogger_test.go b/slogkubeterminate/slogger_test.go deleted file mode 100644 index 80eee77..0000000 --- a/slogkubeterminate/slogger_test.go +++ /dev/null @@ -1,60 +0,0 @@ -package slogkubeterminate_test - -import ( - "context" - "io" - "os" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/coder/envbox/slogkubeterminate" - - "cdr.dev/slog" - "cdr.dev/slog/sloggers/sloghuman" -) - -func TestSlogKubeTerminate(t *testing.T) { - t.Parallel() - - ctx := context.Background() - - terminationLog, err := os.CreateTemp("", "slogkubeterminate-test") - require.NoError(t, err, "make temp file") - t.Cleanup(func() { _ = os.Remove(terminationLog.Name()) }) - - logger := slog.Make(sloghuman.Sink(io.Discard)) - - logger.Info(ctx, "message") - assertContent(t, terminationLog, "") - - logger.Log(ctx, slog.SinkEntry{ - Message: "whoooops", - Level: slog.LevelFatal, - }) - assertContent(t, terminationLog, "") - - logger = logger.AppendSinks(slogkubeterminate.MakeCustom(terminationLog.Name())) - const terminationReason = "whooops" - - logger.Error(ctx, "message") - assertContent(t, terminationLog, "") - - logger.Log(ctx, slog.SinkEntry{ - Message: terminationReason, - Level: slog.LevelCritical, - }) - assertContent(t, terminationLog, "") - - logger.Log(ctx, slog.SinkEntry{ - Message: terminationReason, - Level: slog.LevelFatal, - }) - assertContent(t, terminationLog, terminationReason) -} - -func assertContent(t *testing.T, f *os.File, exp string) { - content, err := io.ReadAll(f) - require.NoError(t, err, "read temp file") - require.Equal(t, exp, string(content), "temp file empty") -} diff --git a/sysboxutil/manager.go b/sysboxutil/manager.go deleted file mode 100644 index 61ea658..0000000 --- a/sysboxutil/manager.go +++ /dev/null @@ -1,45 +0,0 @@ -package sysboxutil - -import ( - "context" - "os" - "time" - - "github.com/coder/envbox/xunix" - "golang.org/x/xerrors" -) - -const ManagerSocketPath = "/run/sysbox/sysmgr.sock" - -// WaitForManager waits for the sysbox-mgr to startup. -func WaitForManager(ctx context.Context) error { - fs := xunix.GetFS(ctx) - - _, err := fs.Stat(ManagerSocketPath) - if err == nil { - return nil - } - - const ( - period = time.Second - ) - - t := time.NewTicker(period) - defer t.Stop() - - for { - select { - case <-ctx.Done(): - return ctx.Err() - case <-t.C: - _, err := fs.Stat(ManagerSocketPath) - if err != nil { - if !xerrors.Is(err, os.ErrNotExist) { - return xerrors.Errorf("unexpected stat err %s: %w", ManagerSocketPath, err) - } - continue - } - return nil - } - } -} diff --git a/template.tf b/template.tf deleted file mode 100644 index 7b3994d..0000000 --- a/template.tf +++ /dev/null @@ -1,309 +0,0 @@ -terraform { - required_providers { - coder = { - source = "coder/coder" - version = "0.6.12" - } - kubernetes = { - source = "hashicorp/kubernetes" - version = "~> 2.12.1" - } - } -} - -data "coder_parameter" "home_disk" { - name = "Disk Size" - description = "How large should the disk storing the home directory be?" - icon = "https://cdn-icons-png.flaticon.com/512/2344/2344147.png" - type = "number" - default = 10 - mutable = true - validation { - min = 10 - max = 100 - } -} - -variable "use_kubeconfig" { - type = bool - sensitive = true - description = <<-EOF - Use host kubeconfig? (true/false) - - Set this to false if the Coder host is itself running as a Pod on the same - Kubernetes cluster as you are deploying workspaces to. - - Set this to true if the Coder host is running outside the Kubernetes cluster - for workspaces. A valid "~/.kube/config" must be present on the Coder host. - EOF -} - -variable "namespace" { - type = string - sensitive = true - description = "The namespace to create workspaces in (must exist prior to creating workspaces)" -} - -variable "create_tun" { - type = bool - sensitive = true - description = "Add a TUN device to the workspace." -} - -variable "create_fuse" { - type = bool - description = "Add a FUSE device to the workspace." - sensitive = true -} - -variable "max_cpus" { - type = string - sensitive = true - description = "Max number of CPUs the workspace may use (e.g. 2)." -} - -variable "min_cpus" { - type = string - sensitive = true - description = "Mininum number of CPUs the workspace may use (e.g. .1)." -} - -variable "max_memory" { - type = string - description = "Maximum amount of memory to allocate the workspace (in GB)." - sensitive = true -} - -variable "min_memory" { - type = string - description = "Minimum amount of memory to allocate the workspace (in GB)." - sensitive = true -} - -provider "kubernetes" { - # Authenticate via ~/.kube/config or a Coder-specific ServiceAccount, depending on admin preferences - config_path = var.use_kubeconfig == true ? "~/.kube/config" : null -} - -data "coder_workspace" "me" {} - -resource "coder_agent" "main" { - os = "linux" - arch = "amd64" - startup_script = < 0 { - err = p.writeSkipMessageOnce() - if err != nil { - return 0, err - } - } - - p.overwriteSuffix(b) - - return lenb, nil -} - -func (p *PrefixSuffixWriter) fillSuffix(b []byte) []byte { - if len(p.suffix) == p.N { - return b - } - - if remain := p.N - len(p.suffix); remain > 0 { - add := minInt(len(b), remain) - p.suffix = append(p.suffix, b[:add]...) - b = b[add:] - } - - return b -} - -func (p *PrefixSuffixWriter) overwriteSuffix(b []byte) { - for len(b) > 0 { - n := copy(p.suffix[p.suffixOff:], b) - b = b[n:] - p.suffixOff += n - if p.suffixOff == p.N { - p.suffixOff = 0 - } - } -} - -const skipMessage = "\nTruncating output...\n\n" - -func (p *PrefixSuffixWriter) writeSkipMessageOnce() error { - if p.skipped { - return nil - } - - p.skipped = true - - _, err := io.Copy(p.W, strings.NewReader(skipMessage)) - return err -} - -func (p *PrefixSuffixWriter) writePrefix(b []byte) (int, error) { - limit := p.N - p.prefix - if limit <= 0 { - return 0, nil - } - - if limit > len(b) { - limit = len(b) - } - - n, err := p.W.Write(b[:limit]) - p.prefix += n - return n, err -} - -func minInt(a, b int) int { - if a < b { - return a - } - return b -} diff --git a/xio/limitwriter_internal_test.go b/xio/limitwriter_internal_test.go deleted file mode 100644 index f63c80a..0000000 --- a/xio/limitwriter_internal_test.go +++ /dev/null @@ -1,56 +0,0 @@ -package xio - -import ( - "bytes" - "io" - "strings" - "testing" - - "github.com/stretchr/testify/require" -) - -func TestPrefixSuffixWriter(t *testing.T) { - t.Parallel() - - type testcase struct { - Name string - Input string - ExpectedOutput string - N int - } - - testcases := []testcase{ - { - Name: "NoTruncate", - Input: "Test", - ExpectedOutput: "Test", - N: 2, - }, - { - Name: "OutputTruncated", - Input: "Testing", - ExpectedOutput: "Te" + skipMessage + "ng", - N: 2, - }, - } - - for _, test := range testcases { - test := test - t.Run(test.Name, func(t *testing.T) { - t.Parallel() - var ( - w = bytes.Buffer{} - psw = &PrefixSuffixWriter{ - W: &w, - N: test.N, - } - ) - - _, err := io.Copy(psw, strings.NewReader(test.Input)) - require.NoError(t, err, "copy") - err = psw.Flush() - require.NoError(t, err, "flush") - require.Equal(t, test.ExpectedOutput, w.String(), "unexpected output") - }) - } -} diff --git a/xio/syncwriter.go b/xio/syncwriter.go deleted file mode 100644 index e73dc08..0000000 --- a/xio/syncwriter.go +++ /dev/null @@ -1,18 +0,0 @@ -package xio - -import ( - "io" - "sync" -) - -// SyncWriter synchronizes concurrent writes to an underlying writer. -type SyncWriter struct { - mu sync.Mutex - W io.Writer -} - -func (sw *SyncWriter) Write(b []byte) (int, error) { - sw.mu.Lock() - defer sw.mu.Unlock() - return sw.W.Write(b) -} diff --git a/xunix/device.go b/xunix/device.go deleted file mode 100644 index e7c3332..0000000 --- a/xunix/device.go +++ /dev/null @@ -1,128 +0,0 @@ -package xunix - -import ( - "context" - "os" - "path/filepath" - - "golang.org/x/sys/unix" - "golang.org/x/xerrors" -) - -type DeviceType string - -const ( - DeviceTypeChar = "c" -) - -const ( - charDevMode = 0o666 - // The file type constant of a character-oriented device file - charFileType = unix.S_IFCHR -) - -type Device struct { - Path string - Type string - Major int64 - Minor int64 - FileMode os.FileMode - UID int32 - GID int32 -} - -func CreateTUNDevice(ctx context.Context, path string) (Device, error) { - const ( - major uint = 10 - // See https://github.com/torvalds/linux/blob/master/Documentation/admin-guide/devices.txt#L336 - minor uint = 200 - ) - - // TODO offset (from legacy.go) - err := createDevice(ctx, deviceConfig{ - path: path, - mode: charDevMode, - dev: dev(major, minor), - major: major, - minor: minor, - ftype: charFileType, - }) - if err != nil { - return Device{}, xerrors.Errorf("create device: %w", err) - } - - return Device{ - Path: path, - Type: DeviceTypeChar, - Major: int64(major), - Minor: int64(minor), - FileMode: charDevMode, - }, nil -} - -func CreateFuseDevice(ctx context.Context, path string) (Device, error) { - const ( - major uint = 10 - - // See https://github.com/torvalds/linux/blob/master/Documentation/admin-guide/devices.txt#L365 - minor uint = 229 - ) - - err := createDevice(ctx, deviceConfig{ - path: path, - mode: charDevMode, - dev: dev(major, minor), - major: major, - minor: minor, - ftype: charFileType, - }) - if err != nil { - return Device{}, xerrors.Errorf("create device: %w", err) - } - - return Device{ - Path: path, - Type: DeviceTypeChar, - Major: int64(major), - Minor: int64(minor), - FileMode: charDevMode, - }, nil -} - -type deviceConfig struct { - path string - mode uint32 - dev uint - major uint - minor uint - ftype uint32 -} - -func createDevice(ctx context.Context, conf deviceConfig) error { - var ( - fs = GetFS(ctx) - dir = filepath.Dir(conf.path) - ) - - err := fs.MkdirAll(dir, 0o700) - if err != nil { - return xerrors.Errorf("ensure parent dir: %w", err) - } - - err = fs.Mknod(conf.path, conf.ftype|conf.mode, int(conf.dev)) - if err != nil { - return xerrors.Errorf("mknod %s c %d %d: %w", conf.path, conf.major, conf.minor, err) - } - - err = fs.Chmod(conf.path, os.FileMode(conf.mode)) - if err != nil { - return xerrors.Errorf("chown %v %q: %w", conf.mode, conf.path, err) - } - - return nil -} - -func dev(major, minor uint) uint { - // This is lifted from the Linux kernel's makedev function. - return ((major & 0xfff) << 8) | (minor & 0xff) -} diff --git a/xunix/doc.go b/xunix/doc.go deleted file mode 100644 index 819440d..0000000 --- a/xunix/doc.go +++ /dev/null @@ -1,3 +0,0 @@ -// Package xunix contains convenience functions for interacting with -// various aspects of Linux. -package xunix diff --git a/xunix/env.go b/xunix/env.go deleted file mode 100644 index beba73e..0000000 --- a/xunix/env.go +++ /dev/null @@ -1,42 +0,0 @@ -package xunix - -import ( - "context" - "fmt" - "os" -) - -type environKey struct{} - -type EnvironFn func() []string - -func WithEnvironFn(ctx context.Context, fn EnvironFn) context.Context { - return context.WithValue(ctx, environKey{}, fn) -} - -func Environ(ctx context.Context) []string { - fn := ctx.Value(environKey{}) - if fn == nil { - return os.Environ() - } - - //nolint we should panic if this isn't the case. - return fn.(EnvironFn)() -} - -type Env struct { - Name string - Value string -} - -func (e Env) String() string { - return fmt.Sprintf("%s=%s", e.Name, e.Value) -} - -func MustLookupEnv(e string) string { - env, ok := os.LookupEnv(e) - if !ok { - panic(fmt.Sprintf("%q env var not found", e)) - } - return env -} diff --git a/xunix/env_test.go b/xunix/env_test.go deleted file mode 100644 index 0592c2e..0000000 --- a/xunix/env_test.go +++ /dev/null @@ -1,39 +0,0 @@ -package xunix_test - -import ( - "os" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/coder/envbox/xunix" -) - -func TestMustLookupEnv(t *testing.T) { - t.Parallel() - - t.Run("OK", func(t *testing.T) { - t.Parallel() - - const ( - key = "MY_ENV" - value = "value" - ) - - //nolint can't use t.SetEnv in parallel tests. - os.Setenv(key, value) - - val := xunix.MustLookupEnv(key) - require.Equal(t, value, val) - }) - - t.Run("Panic", func(t *testing.T) { - t.Parallel() - - defer func() { - e := recover() - require.NotNil(t, e, "function should panic") - }() - _ = xunix.MustLookupEnv("ASDasdf") - }) -} diff --git a/xunix/error.go b/xunix/error.go deleted file mode 100644 index 340fab7..0000000 --- a/xunix/error.go +++ /dev/null @@ -1,11 +0,0 @@ -package xunix - -import "strings" - -func IsNoSpaceErr(err error) bool { - if err == nil { - return false - } - - return strings.Contains(strings.ToLower(err.Error()), "no space left on device") -} diff --git a/xunix/exec.go b/xunix/exec.go deleted file mode 100644 index 666a3ea..0000000 --- a/xunix/exec.go +++ /dev/null @@ -1,175 +0,0 @@ -package xunix - -import ( - "context" - "errors" - "io" - "io/fs" - "os" - osexec "os/exec" - "syscall" - "time" - - utilexec "k8s.io/utils/exec" -) - -type execerKey struct{} - -func WithExecer(ctx context.Context, execer Execer) context.Context { - return context.WithValue(ctx, execerKey{}, execer) -} - -func GetExecer(ctx context.Context) Execer { - execer := ctx.Value(execerKey{}) - if execer == nil { - // This is a typical os/exec implementation. - return &executor{} - } - - //nolint we should panic if this isn't the case. - return execer.(Execer) -} - -// The code henceforth is copied straight and modified slightly from -// "k8s.io/utils/exec". Their interface doesn't allow for a reference to -// exec.Cmd.Process which we use to kill and wait for a process to exit. - -type Execer interface { - // CommandContext returns a Cmd instance which can be used to run a single command. - // - // The provided context is used to kill the process if the context becomes done - // before the command completes on its own. For example, a timeout can be set in - // the context. - CommandContext(ctx context.Context, cmd string, args ...string) Cmd -} - -type Cmd interface { - utilexec.Cmd - OSProcess() *os.Process -} - -type executor struct{} - -// CommandContext is part of the Interface interface. -func (*executor) CommandContext(ctx context.Context, cmd string, args ...string) Cmd { - return (*cmdWrapper)(maskErrDotCmd(osexec.CommandContext(ctx, cmd, args...))) -} - -// Wraps exec.Cmd so we can capture errors. -type cmdWrapper osexec.Cmd - -var _ Cmd = &cmdWrapper{} - -func (cmd *cmdWrapper) SetDir(dir string) { - cmd.Dir = dir -} - -func (cmd *cmdWrapper) SetStdin(in io.Reader) { - cmd.Stdin = in -} - -func (cmd *cmdWrapper) SetStdout(out io.Writer) { - cmd.Stdout = out -} - -func (cmd *cmdWrapper) SetStderr(out io.Writer) { - cmd.Stderr = out -} - -func (cmd *cmdWrapper) SetEnv(env []string) { - cmd.Env = env -} - -func (cmd *cmdWrapper) StdoutPipe() (io.ReadCloser, error) { - r, err := (*osexec.Cmd)(cmd).StdoutPipe() - return r, handleError(err) -} - -func (cmd *cmdWrapper) StderrPipe() (io.ReadCloser, error) { - r, err := (*osexec.Cmd)(cmd).StderrPipe() - return r, handleError(err) -} - -func (cmd *cmdWrapper) Start() error { - err := (*osexec.Cmd)(cmd).Start() - return handleError(err) -} - -func (cmd *cmdWrapper) Wait() error { - err := (*osexec.Cmd)(cmd).Wait() - return handleError(err) -} - -// Run is part of the Cmd interface. -func (cmd *cmdWrapper) Run() error { - err := (*osexec.Cmd)(cmd).Run() - return handleError(err) -} - -// CombinedOutput is part of the Cmd interface. -func (cmd *cmdWrapper) CombinedOutput() ([]byte, error) { - out, err := (*osexec.Cmd)(cmd).CombinedOutput() - return out, handleError(err) -} - -func (cmd *cmdWrapper) Output() ([]byte, error) { - out, err := (*osexec.Cmd)(cmd).Output() - return out, handleError(err) -} - -func (cmd *cmdWrapper) OSProcess() *os.Process { - return (*osexec.Cmd)(cmd).Process -} - -// Stop is part of the Cmd interface. -func (cmd *cmdWrapper) Stop() { - c := (*osexec.Cmd)(cmd) - - if c.Process == nil { - return - } - - _ = c.Process.Signal(syscall.SIGTERM) - - time.AfterFunc(10*time.Second, func() { - if !c.ProcessState.Exited() { - _ = c.Process.Signal(syscall.SIGKILL) - } - }) -} - -func handleError(err error) error { - if err == nil { - return nil - } - - //nolint copied code from k8s. - switch e := err.(type) { - case *osexec.ExitError: - return &utilexec.ExitErrorWrapper{ExitError: e} - case *fs.PathError: - return utilexec.ErrExecutableNotFound - // nolint copied code from k8s - case *osexec.Error: - if e.Err == osexec.ErrNotFound { - return utilexec.ErrExecutableNotFound - } - } - - return err -} - -// maskErrDotCmd reverts the behavior of osexec.Cmd to what it was before go1.19 -// specifically set the Err field to nil (LookPath returns a new error when the file -// is resolved to the current directory. -func maskErrDotCmd(cmd *osexec.Cmd) *osexec.Cmd { - cmd.Err = maskErrDot(cmd.Err) - return cmd -} - -func maskErrDot(err error) error { - if err != nil && errors.Is(err, osexec.ErrDot) { - return nil - } - return err -} diff --git a/xunix/fs.go b/xunix/fs.go deleted file mode 100644 index e1dd3c6..0000000 --- a/xunix/fs.go +++ /dev/null @@ -1,49 +0,0 @@ -package xunix - -import ( - "context" - "io/fs" - "os" - - "github.com/spf13/afero" - "golang.org/x/sys/unix" -) - -type FS interface { - afero.Fs - Mknod(path string, mode uint32, dev int) error - LStat(path string) (fs.FileInfo, error) - Readlink(path string) (string, error) -} - -type fsKey struct{} - -func WithFS(ctx context.Context, f FS) context.Context { - return context.WithValue(ctx, fsKey{}, f) -} - -func GetFS(ctx context.Context) FS { - f := ctx.Value(fsKey{}) - if f == nil { - return &osFS{&afero.OsFs{}} - } - - //nolint we should panic if this isn't the case. - return f.(FS) -} - -type osFS struct { - *afero.OsFs -} - -func (*osFS) Mknod(path string, mode uint32, dev int) error { - return unix.Mknod(path, mode, dev) -} - -func (*osFS) LStat(path string) (fs.FileInfo, error) { - return os.Lstat(path) -} - -func (*osFS) Readlink(path string) (string, error) { - return os.Readlink(path) -} diff --git a/xunix/gpu.go b/xunix/gpu.go deleted file mode 100644 index e678699..0000000 --- a/xunix/gpu.go +++ /dev/null @@ -1,212 +0,0 @@ -package xunix - -import ( - "context" - "io/fs" - "os" - "path/filepath" - "regexp" - "sort" - "strings" - - "github.com/spf13/afero" - "golang.org/x/xerrors" - mount "k8s.io/mount-utils" - - "cdr.dev/slog" -) - -var ( - gpuMountRegex = regexp.MustCompile("(?i)(nvidia|vulkan|cuda)") - gpuExtraRegex = regexp.MustCompile("(?i)(libgl|nvidia|vulkan|cuda)") - gpuEnvRegex = regexp.MustCompile("(?i)nvidia") -) - -func GPUEnvs(ctx context.Context) []string { - envs := Environ(ctx) - - gpus := []string{} - for _, env := range envs { - name := strings.Split(env, "=")[0] - if gpuEnvRegex.MatchString(name) { - gpus = append(gpus, env) - } - } - - return gpus -} - -func GPUs(ctx context.Context, log slog.Logger, usrLibDir string) ([]Device, []mount.MountPoint, error) { - var ( - mounter = Mounter(ctx) - devices = []Device{} - binds = []mount.MountPoint{} - ) - - mounts, err := mounter.List() - if err != nil { - return nil, nil, xerrors.Errorf("list mounts: %w", err) - } - - for _, m := range mounts { - if gpuMountRegex.MatchString(m.Path) { - // If we find the GPU in /dev treat it as a device. - if strings.HasPrefix(m.Path, "/dev/") { - // TODO(JonA): We could populate the rest of the fields but it - // doesn't seem like we need them. We'll have to expand - // the FS interface to allow for a real unix stat. - devices = append(devices, Device{ - Path: m.Path, - }) - continue - } - - // If it's not in /dev treat it as a bind mount. - binds = append(binds, m) - } - } - - extraGPUS, err := usrLibGPUs(ctx, log, usrLibDir) - if err != nil { - return nil, nil, xerrors.Errorf("find %q gpus: %w", usrLibDir, err) - } - - for _, gpu := range extraGPUS { - var duplicate bool - for _, bind := range binds { - if gpu.Path == bind.Path { - duplicate = true - break - } - } - if !duplicate { - binds = append(binds, gpu) - } - } - - return devices, binds, nil -} - -func usrLibGPUs(ctx context.Context, log slog.Logger, usrLibDir string) ([]mount.MountPoint, error) { - var ( - afs = GetFS(ctx) - binds = []string{} - ) - - err := afero.Walk(afs, usrLibDir, - func(path string, info fs.FileInfo, err error) error { - if path == usrLibDir && err != nil { - return xerrors.Errorf("stat /usr/lib mountpoint %q: %w", usrLibDir, err) - } - if err != nil { - log.Error(ctx, "list directory", slog.F("dir", path), slog.Error(err)) - return nil - } - - if filepath.Ext(path) != ".so" || !gpuExtraRegex.MatchString(path) { - return nil - } - - paths, err := recursiveSymlinks(afs, usrLibDir, path) - if err != nil { - log.Error(ctx, "find recursive symlinks", slog.F("path", path), slog.Error(err)) - } - - binds = append(binds, paths...) - - return nil - }) - if err != nil { - return nil, xerrors.Errorf("walk %q for GPU drivers: %w", usrLibDir, err) - } - - mounts := make([]mount.MountPoint, 0, len(binds)) - for _, bind := range binds { - mounts = append(mounts, mount.MountPoint{ - Path: bind, - Opts: []string{"ro"}, - }) - } - - return mounts, nil -} - -// recursiveSymlinks returns all of the paths in the chain of symlinks starting -// at `path`. If `path` isn't a symlink, only `path` is returned. If at any -// point the symlink chain goes outside of `mountpoint` then nil is returned. -// Despite its name it's interestingly enough not implemented recursively. -func recursiveSymlinks(afs FS, mountpoint string, path string) ([]string, error) { - if !strings.HasSuffix(mountpoint, "/") { - mountpoint += "/" - } - - paths := []string{} - for { - if !strings.HasPrefix(path, mountpoint) { - return nil, nil - } - - stat, err := afs.LStat(path) - if err != nil { - return nil, xerrors.Errorf("lstat %q: %w", path, err) - } - - paths = append(paths, path) - if stat.Mode()&os.ModeSymlink == 0 { - break - } - - newPath, err := afs.Readlink(path) - if err != nil { - return nil, xerrors.Errorf("readlink %q: %w", path, err) - } - if newPath == "" { - break - } - - if filepath.IsAbs(newPath) { - path = newPath - } else { - dir := filepath.Dir(path) - path = filepath.Join(dir, newPath) - } - } - - return paths, nil -} - -// TryUnmountProcGPUDrivers unmounts any GPU-related mounts under /proc as it causes -// issues when creating any container in some cases. Errors encountered while -// unmounting are treated as non-fatal. -func TryUnmountProcGPUDrivers(ctx context.Context, log slog.Logger) ([]mount.MountPoint, error) { - mounter := Mounter(ctx) - - mounts, err := mounter.List() - if err != nil { - return nil, xerrors.Errorf("list mounts: %w", err) - } - - // Sort mounts list by longest paths (by segments) first. - sort.Slice(mounts, func(i, j int) bool { - // Sort paths with more slashes as "less". - return strings.Count(mounts[i].Path, "/") > strings.Count(mounts[j].Path, "/") - }) - - drivers := []mount.MountPoint{} - for _, m := range mounts { - if strings.HasPrefix(m.Path, "/proc/") && gpuMountRegex.MatchString(m.Path) { - err := mounter.Unmount(m.Path) - if err != nil { - log.Warn(ctx, - "umount potentially problematic mount", - slog.F("path", m.Path), - slog.Error(err), - ) - continue - } - drivers = append(drivers, m) - } - } - - return drivers, nil -} diff --git a/xunix/gpu_test.go b/xunix/gpu_test.go deleted file mode 100644 index 4cbf5f0..0000000 --- a/xunix/gpu_test.go +++ /dev/null @@ -1,114 +0,0 @@ -package xunix_test - -import ( - "context" - "path/filepath" - "testing" - - "github.com/spf13/afero" - "github.com/stretchr/testify/require" - "k8s.io/mount-utils" - - "cdr.dev/slog/sloggers/slogtest" - - "github.com/coder/envbox/xunix" - "github.com/coder/envbox/xunix/xunixfake" -) - -func TestGPUEnvs(t *testing.T) { - t.Parallel() - - t.Run("OK", func(t *testing.T) { - t.Parallel() - - ctx := xunix.WithEnvironFn(context.Background(), func() []string { - return []string{ - "NVIDIA_TEST=1", - "VULKAN_TEST=1", - "LIBGL_TEST=1", - "TEST_NVIDIA=1", - "nvidia_test=1", - } - }) - - envs := xunix.GPUEnvs(ctx) - require.Contains(t, envs, "NVIDIA_TEST=1") - require.Contains(t, envs, "TEST_NVIDIA=1") - require.Contains(t, envs, "nvidia_test=1") - require.NotContains(t, envs, "VULKAN_TEST=1") - require.NotContains(t, envs, "LIBGL_TEST=1") - }) -} - -func TestGPUs(t *testing.T) { - t.Parallel() - - t.Run("OK", func(t *testing.T) { - t.Parallel() - - var ( - fs = &xunixfake.MemFS{MemMapFs: &afero.MemMapFs{}} - mounter = &mount.FakeMounter{} - log = slogtest.Make(t, nil) - usrLibMountpoint = "/var/coder/usr/lib" - // expectedUsrLibFiles are files that we expect to be returned bind mounts - // for. - expectedUsrLibFiles = []string{ - filepath.Join(usrLibMountpoint, "nvidia", "libglxserver_nvidia.so"), - filepath.Join(usrLibMountpoint, "libnvidia-ml.so"), - } - - // fakeUsrLibFiles are files that should be written to the "mounted" - // /usr/lib directory. It includes files that shouldn't be returned. - fakeUsrLibFiles = append([]string{ - filepath.Join(usrLibMountpoint, "libcurl-gnutls.so"), - filepath.Join(usrLibMountpoint, "nvidia", "libglxserver_nvidia.so.1"), - }, expectedUsrLibFiles...) - ) - - ctx := xunix.WithFS(context.Background(), fs) - ctx = xunix.WithMounter(ctx, mounter) - - mounter.MountPoints = []mount.MountPoint{ - { - Device: "/dev/sda1", - Path: "/usr/local/nvidia", - Opts: []string{"ro"}, - }, - { - Device: "/dev/sda2", - Path: "/etc/hosts", - }, - { - Path: "/dev/nvidia0", - }, - { - Path: "/dev/nvidia1", - }, - } - - err := fs.MkdirAll(filepath.Join(usrLibMountpoint, "nvidia"), 0o755) - require.NoError(t, err) - - for _, file := range fakeUsrLibFiles { - _, err = fs.Create(file) - require.NoError(t, err) - } - - devices, binds, err := xunix.GPUs(ctx, log, usrLibMountpoint) - require.NoError(t, err) - require.Len(t, devices, 2, "unexpected 2 nvidia devices") - require.Len(t, binds, 3, "expected 4 nvidia binds") - require.Contains(t, binds, mount.MountPoint{ - Device: "/dev/sda1", - Path: "/usr/local/nvidia", - Opts: []string{"ro"}, - }) - for _, file := range expectedUsrLibFiles { - require.Contains(t, binds, mount.MountPoint{ - Path: file, - Opts: []string{"ro"}, - }) - } - }) -} diff --git a/xunix/mount.go b/xunix/mount.go deleted file mode 100644 index 84ce288..0000000 --- a/xunix/mount.go +++ /dev/null @@ -1,34 +0,0 @@ -package xunix - -import ( - "context" - - mount "k8s.io/mount-utils" -) - -type mounterKey struct{} - -func WithMounter(ctx context.Context, i mount.Interface) context.Context { - return context.WithValue(ctx, mounterKey{}, i) -} - -func Mounter(ctx context.Context) mount.Interface { - m := ctx.Value(mounterKey{}) - if m == nil { - return mount.New("/bin/mount") - } - - //nolint we should panic if this isn't the case. - return m.(mount.Interface) -} - -type Mount struct { - Source string - Mountpoint string - ReadOnly bool -} - -func MountFS(ctx context.Context, source, mountpoint, fstype string, options ...string) error { - return Mounter(ctx). - Mount(source, mountpoint, fstype, options) -} diff --git a/xunix/net.go b/xunix/net.go deleted file mode 100644 index c228c70..0000000 --- a/xunix/net.go +++ /dev/null @@ -1,15 +0,0 @@ -package xunix - -import ( - "github.com/vishvananda/netlink" - "golang.org/x/xerrors" -) - -func NetlinkMTU(name string) (int, error) { - defaultLink, err := netlink.LinkByName(name) - if err != nil { - return 0, xerrors.Errorf("get %s: %w", name, err) - } - - return defaultLink.Attrs().MTU, nil -} diff --git a/xunix/proc.go b/xunix/proc.go deleted file mode 100644 index ffafa33..0000000 --- a/xunix/proc.go +++ /dev/null @@ -1,23 +0,0 @@ -package xunix - -import ( - "context" - "fmt" - - "github.com/spf13/afero" - "golang.org/x/xerrors" -) - -func SetOOMScore(ctx context.Context, pid, score string) error { - var ( - fs = GetFS(ctx) - file = fmt.Sprintf("/proc/%v/oom_score_adj", pid) - ) - - err := afero.WriteFile(fs, file, []byte(score), 0o644) - if err != nil { - return xerrors.Errorf("write file: %w", err) - } - - return nil -} diff --git a/xunix/proc_test.go b/xunix/proc_test.go deleted file mode 100644 index 0f4a3ec..0000000 --- a/xunix/proc_test.go +++ /dev/null @@ -1,38 +0,0 @@ -package xunix_test - -import ( - "context" - "fmt" - "testing" - - "github.com/spf13/afero" - "github.com/stretchr/testify/require" - - "github.com/coder/envbox/xunix" - "github.com/coder/envbox/xunix/xunixfake" -) - -func TestSetOOMScore(t *testing.T) { - t.Parallel() - - t.Run("OK", func(t *testing.T) { - t.Parallel() - - var ( - fs = &xunixfake.MemFS{MemMapFs: &afero.MemMapFs{}} - ctx = xunix.WithFS(context.Background(), fs) - ) - - const ( - pid = "123" - score = "-1000" - ) - - err := xunix.SetOOMScore(ctx, pid, score) - require.NoError(t, err) - - actualScore, err := afero.ReadFile(fs, fmt.Sprintf("/proc/%s/oom_score_adj", pid)) - require.NoError(t, err) - require.Equal(t, score, string(actualScore)) - }) -} diff --git a/xunix/sys.go b/xunix/sys.go deleted file mode 100644 index d95f508..0000000 --- a/xunix/sys.go +++ /dev/null @@ -1,48 +0,0 @@ -package xunix - -import ( - "bytes" - "context" - "strconv" - - "github.com/spf13/afero" - "golang.org/x/xerrors" -) - -type CPUQuota struct { - Quota int - Period int -} - -const ( - CPUPeriodPath = "/sys/fs/cgroup/cpu,cpuacct/cpu.cfs_period_us" - CPUQuotaPath = "/sys/fs/cgroup/cpu,cpuacct/cpu.cfs_quota_us" -) - -func ReadCPUQuota(ctx context.Context) (CPUQuota, error) { - fs := GetFS(ctx) - periodStr, err := afero.ReadFile(fs, "/sys/fs/cgroup/cpu,cpuacct/cpu.cfs_period_us") - if err != nil { - return CPUQuota{}, xerrors.Errorf("read cpu.cfs_period_us outside container: %w", err) - } - - quotaStr, err := afero.ReadFile(fs, "/sys/fs/cgroup/cpu,cpuacct/cpu.cfs_quota_us") - if err != nil { - return CPUQuota{}, xerrors.Errorf("read cpu.cfs_quota_us outside container: %w", err) - } - - period, err := strconv.Atoi(string(bytes.TrimSpace(periodStr))) - if err != nil { - return CPUQuota{}, xerrors.Errorf("period %s not an int: %w", periodStr, err) - } - - quota, err := strconv.Atoi(string(bytes.TrimSpace(quotaStr))) - if err != nil { - return CPUQuota{}, xerrors.Errorf("quota %s not an int: %w", quotaStr, err) - } - - return CPUQuota{ - Quota: quota, - Period: period, - }, nil -} diff --git a/xunix/sys_test.go b/xunix/sys_test.go deleted file mode 100644 index 198d8a1..0000000 --- a/xunix/sys_test.go +++ /dev/null @@ -1,45 +0,0 @@ -package xunix_test - -import ( - "context" - "strconv" - "testing" - - "github.com/spf13/afero" - "github.com/stretchr/testify/require" - - "github.com/coder/envbox/xunix" - "github.com/coder/envbox/xunix/xunixfake" -) - -func TestReadCPUQuota(t *testing.T) { - t.Parallel() - - t.Run("OK", func(t *testing.T) { - t.Parallel() - - var ( - fs = &xunixfake.MemFS{MemMapFs: &afero.MemMapFs{}} - ctx = xunix.WithFS(context.Background(), fs) - ) - - const ( - period = 1234 - quota = 5678 - periodPath = "/sys/fs/cgroup/cpu,cpuacct/cpu.cfs_period_us" - quotaPath = "/sys/fs/cgroup/cpu,cpuacct/cpu.cfs_quota_us" - ) - - err := afero.WriteFile(fs, periodPath, []byte(strconv.Itoa(period)), 0o644) - require.NoError(t, err) - - err = afero.WriteFile(fs, quotaPath, []byte(strconv.Itoa(quota)), 0o644) - require.NoError(t, err) - - cpuQuota, err := xunix.ReadCPUQuota(ctx) - require.NoError(t, err) - - require.Equal(t, period, cpuQuota.Period) - require.Equal(t, quota, cpuQuota.Quota) - }) -} diff --git a/xunix/user.go b/xunix/user.go deleted file mode 100644 index f363767..0000000 --- a/xunix/user.go +++ /dev/null @@ -1,60 +0,0 @@ -package xunix - -import ( - "bufio" - "io" - "os/user" - "strings" - - "golang.org/x/xerrors" -) - -// User is a linux user from /etc/passwd. -// It is basically a user.User with addition of the -// user's shell. -type User struct { - user.User - Shell string -} - -// ParsePasswd parses user entries from an /etc/passwd. -func ParsePasswd(r io.Reader) ([]*User, error) { - var ( - scanner = bufio.NewScanner(r) - users = make([]*User, 0) - ) - - for scanner.Scan() { - usr, err := parsePasswdEntry(scanner.Text()) - if err != nil { - return nil, xerrors.Errorf("failed to parse user entry: %w", err) - } - - users = append(users, usr) - } - - err := scanner.Err() - if err != nil { - return nil, xerrors.Errorf("failed to parse passwd: %w", err) - } - - return users, nil -} - -func parsePasswdEntry(entry string) (*User, error) { - entry = strings.TrimSpace(entry) - fields := strings.Split(entry, ":") - if len(fields) < 7 { - return nil, xerrors.Errorf("user info (%s) contained an unexpected number of fields", fields) - } - - return &User{ - User: user.User{ - Username: fields[0], - Uid: fields[2], - Gid: fields[3], - HomeDir: fields[5], - }, - Shell: fields[6], - }, nil -} diff --git a/xunix/xunixfake/exec.go b/xunix/xunixfake/exec.go deleted file mode 100644 index c379439..0000000 --- a/xunix/xunixfake/exec.go +++ /dev/null @@ -1,68 +0,0 @@ -package xunixfake - -import ( - "context" - "os" - "strings" - "testing" - - "github.com/stretchr/testify/require" - testexec "k8s.io/utils/exec/testing" - - "github.com/coder/envbox/xunix" -) - -var _ xunix.Execer = &FakeExec{} - -type FakeExec struct { - Commands map[string]*FakeCmd - DefaultFakeCmd *FakeCmd -} - -func cmdKey(cmd string, args ...string) string { - return cmd + " " + strings.Join(args, " ") -} - -func (f *FakeExec) CommandContext(_ context.Context, cmd string, args ...string) xunix.Cmd { - // TODO: This isn't a great key because you may have multiple of the same commands - // but desire different output. - if c, ok := f.Commands[cmdKey(cmd, args...)]; ok { - c.Called = true - return c - } - return f.DefaultFakeCmd -} - -func (f *FakeExec) AddCommands(cmds ...*FakeCmd) { - for _, cmd := range cmds { - key := cmdKey(cmd.Argv[0], cmd.Argv[1:]...) - f.Commands[key] = cmd - } -} - -func (f *FakeExec) AssertCommandsCalled(t *testing.T) { - t.Helper() - for k, cmd := range f.Commands { - require.True(t, cmd.Called, "%q not called", k) - } -} - -var _ xunix.Cmd = &FakeCmd{} - -type FakeCmd struct { - *testexec.FakeCmd - FakeProcess *os.Process - WaitFn func() error - Called bool -} - -func (f *FakeCmd) Wait() error { - if f.WaitFn == nil { - return nil - } - return f.WaitFn() -} - -func (f *FakeCmd) OSProcess() *os.Process { - return f.FakeProcess -} diff --git a/xunix/xunixfake/fs.go b/xunix/xunixfake/fs.go deleted file mode 100644 index 36eb2ad..0000000 --- a/xunix/xunixfake/fs.go +++ /dev/null @@ -1,53 +0,0 @@ -package xunixfake - -import ( - "io/fs" - "os" - "strconv" - - "github.com/spf13/afero" - "golang.org/x/xerrors" -) - -type FileOwner struct { - UID int - GID int -} - -type MemFS struct { - *afero.MemMapFs - Owner map[string]FileOwner -} - -func (m *MemFS) Mknod(path string, mode uint32, dev int) error { - return afero.WriteFile(m.MemMapFs, path, []byte(strconv.Itoa(dev)), os.FileMode(mode)) -} - -// This is so annoying... -func (m *MemFS) Chown(path string, uid int, gid int) error { - err := m.MemMapFs.Chown(path, uid, gid) - if err != nil { - return xerrors.Errorf("chown: %w", err) - } - m.Owner[path] = FileOwner{ - UID: uid, - GID: gid, - } - return nil -} - -func (m *MemFS) GetFileOwner(path string) (FileOwner, bool) { - owner, ok := m.Owner[path] - return owner, ok -} - -// LStat doesn't follow symbolic links since this is a in-mem fake. -func (m *MemFS) LStat(path string) (fs.FileInfo, error) { - return m.MemMapFs.Stat(path) -} - -// Readlink doesn't actually read symbolic links since this is a in-mem -// fake. -func (*MemFS) Readlink(path string) (string, error) { - return path, nil -} From f735f126a6419582c5414abbb52e3b9b82ec3b66 Mon Sep 17 00:00:00 2001 From: Dean Sheather Date: Tue, 18 Apr 2023 22:15:14 +0000 Subject: [PATCH 2/2] Revert "delete everything" This reverts commit fd6da745157539bd0145c4fdb6db34ed705fa87b. --- .github/workflows/ci.yaml | 201 +++ .gitignore | 4 + .golangci.yaml | 260 ++++ .vscode/settings.json | 4 + LICENSE | 661 +++++++++ Makefile | 40 + README.md | 36 + background/doc.go | 3 + background/process.go | 267 ++++ buildlog/coder.go | 138 ++ buildlog/coder_test.go | 117 ++ buildlog/doc.go | 3 + buildlog/json.go | 67 + buildlog/json_test.go | 96 ++ buildlog/logger.go | 79 + cli/cliflag/cliflag.go | 183 +++ cli/cliflag/cliflag_test.go | 277 ++++ cli/clitest/cli.go | 86 ++ cli/clitest/fake.go | 62 + cli/clitest/fs.go | 24 + cli/clitest/net.go | 28 + cli/doc.go | 2 + cli/docker.go | 791 ++++++++++ cli/docker_test.go | 580 ++++++++ cli/root.go | 22 + cmd/envbox/main.go | 20 + deploy/Dockerfile | 71 + deploy/files/etc/apt/preferences.d/docker | 19 + .../files/etc/apt/sources.list.d/docker.list | 1 + deploy/files/etc/docker/daemon.json | 7 + deploy/files/usr/share/keyrings/docker.gpg | Bin 0 -> 2760 bytes deploy/update-keys.sh | 22 + dockerutil/client.go | 65 + dockerutil/container.go | 175 +++ dockerutil/daemon.go | 56 + dockerutil/doc.go | 3 + dockerutil/dockerfake/client.go | 281 ++++ dockerutil/dockerfake/doc.go | 3 + dockerutil/exec.go | 94 ++ dockerutil/image.go | 255 ++++ dockerutil/network.go | 45 + go.mod | 187 +++ go.sum | 1273 +++++++++++++++++ integration/doc.go | 3 + integration/docker_test.go | 258 ++++ integration/integrationtest/docker.go | 296 ++++ integration/main_test.go | 36 + scripts/check_unstaged.sh | 45 + scripts/rules.go | 38 + slogkubeterminate/doc.go | 5 + slogkubeterminate/slogger.go | 33 + slogkubeterminate/slogger_test.go | 60 + sysboxutil/manager.go | 45 + template.tf | 309 ++++ xio/limitwriter.go | 120 ++ xio/limitwriter_internal_test.go | 56 + xio/syncwriter.go | 18 + xunix/device.go | 128 ++ xunix/doc.go | 3 + xunix/env.go | 42 + xunix/env_test.go | 39 + xunix/error.go | 11 + xunix/exec.go | 175 +++ xunix/fs.go | 49 + xunix/gpu.go | 212 +++ xunix/gpu_test.go | 114 ++ xunix/mount.go | 34 + xunix/net.go | 15 + xunix/proc.go | 23 + xunix/proc_test.go | 38 + xunix/sys.go | 48 + xunix/sys_test.go | 45 + xunix/user.go | 60 + xunix/xunixfake/exec.go | 68 + xunix/xunixfake/fs.go | 53 + 75 files changed, 9087 insertions(+) create mode 100644 .github/workflows/ci.yaml create mode 100644 .gitignore create mode 100644 .golangci.yaml create mode 100644 .vscode/settings.json create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100644 background/doc.go create mode 100644 background/process.go create mode 100644 buildlog/coder.go create mode 100644 buildlog/coder_test.go create mode 100644 buildlog/doc.go create mode 100644 buildlog/json.go create mode 100644 buildlog/json_test.go create mode 100644 buildlog/logger.go create mode 100644 cli/cliflag/cliflag.go create mode 100644 cli/cliflag/cliflag_test.go create mode 100644 cli/clitest/cli.go create mode 100644 cli/clitest/fake.go create mode 100644 cli/clitest/fs.go create mode 100644 cli/clitest/net.go create mode 100644 cli/doc.go create mode 100644 cli/docker.go create mode 100644 cli/docker_test.go create mode 100644 cli/root.go create mode 100644 cmd/envbox/main.go create mode 100644 deploy/Dockerfile create mode 100644 deploy/files/etc/apt/preferences.d/docker create mode 100644 deploy/files/etc/apt/sources.list.d/docker.list create mode 100644 deploy/files/etc/docker/daemon.json create mode 100644 deploy/files/usr/share/keyrings/docker.gpg create mode 100644 deploy/update-keys.sh create mode 100644 dockerutil/client.go create mode 100644 dockerutil/container.go create mode 100644 dockerutil/daemon.go create mode 100644 dockerutil/doc.go create mode 100644 dockerutil/dockerfake/client.go create mode 100644 dockerutil/dockerfake/doc.go create mode 100644 dockerutil/exec.go create mode 100644 dockerutil/image.go create mode 100644 dockerutil/network.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 integration/doc.go create mode 100644 integration/docker_test.go create mode 100644 integration/integrationtest/docker.go create mode 100644 integration/main_test.go create mode 100755 scripts/check_unstaged.sh create mode 100644 scripts/rules.go create mode 100644 slogkubeterminate/doc.go create mode 100644 slogkubeterminate/slogger.go create mode 100644 slogkubeterminate/slogger_test.go create mode 100644 sysboxutil/manager.go create mode 100644 template.tf create mode 100644 xio/limitwriter.go create mode 100644 xio/limitwriter_internal_test.go create mode 100644 xio/syncwriter.go create mode 100644 xunix/device.go create mode 100644 xunix/doc.go create mode 100644 xunix/env.go create mode 100644 xunix/env_test.go create mode 100644 xunix/error.go create mode 100644 xunix/exec.go create mode 100644 xunix/fs.go create mode 100644 xunix/gpu.go create mode 100644 xunix/gpu_test.go create mode 100644 xunix/mount.go create mode 100644 xunix/net.go create mode 100644 xunix/proc.go create mode 100644 xunix/proc_test.go create mode 100644 xunix/sys.go create mode 100644 xunix/sys_test.go create mode 100644 xunix/user.go create mode 100644 xunix/xunixfake/exec.go create mode 100644 xunix/xunixfake/fs.go diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..856d3b5 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,201 @@ +name: ci + +on: + push: + branches: + - main + + pull_request: + + workflow_dispatch: + +permissions: + actions: none + checks: none + contents: read + deployments: none + issues: none + packages: write + pull-requests: none + repository-projects: none + security-events: none + statuses: none + +# Cancel in-progress runs for pull requests when developers push +# additional changes +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +jobs: + lint: + runs-on: ${{ github.repository_owner == 'coder' && 'ubuntu-latest-8-cores' || 'ubuntu-latest' }} + steps: + - name: Checkout + uses: actions/checkout@v3 + + # Install Go! + - uses: actions/setup-go@v3 + with: + go-version: "~1.20" + + # Check for Go linting errors! + - name: Lint Go + uses: golangci/golangci-lint-action@v3.3.1 + with: + version: v1.51.0 + args: "--out-${NO_FUTURE}format colored-line-number" + + - name: Lint shell scripts + uses: ludeeus/action-shellcheck@2.0.0 + env: + SHELLCHECK_OPTS: --external-sources + with: + ignore: node_modules + + - uses: hashicorp/setup-terraform@v2 + with: + terraform_version: 1.1.9 + terraform_wrapper: false + + - name: Terraform init + run: terraform init + + - name: Terraform validate + run: terraform validate + + fmt: + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + submodules: true + + - uses: hashicorp/setup-terraform@v2 + with: + terraform_version: 1.1.9 + terraform_wrapper: false + + - name: Install markdownfmt + run: go install github.com/Kunde21/markdownfmt/v3/cmd/markdownfmt@latest + + - name: make fmt + run: | + export PATH=${PATH}:$(go env GOPATH)/bin + make --output-sync -j -B fmt + + - name: Check for unstaged files + run: ./scripts/check_unstaged.sh + + unit-tests: + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - uses: actions/checkout@v3 + + - uses: actions/setup-go@v3 + with: + go-version: "~1.20" + + # Sadly the new "set output" syntax (of writing env vars to + # $GITHUB_OUTPUT) does not work on both powershell and bash so we use the + # deprecated syntax here. + - name: Echo Go Cache Paths + id: go-cache-paths + run: | + echo "::set-output name=GOCACHE::$(go env GOCACHE)" + echo "::set-output name=GOMODCACHE::$(go env GOMODCACHE)" + + - name: Go Build Cache + uses: actions/cache@v3 + with: + path: ${{ steps.go-cache-paths.outputs.GOCACHE }} + key: ${{ runner.os }}-go-build-${{ hashFiles('**/go.**', '**.go') }} + + - name: Go Mod Cache + uses: actions/cache@v3 + with: + path: ${{ steps.go-cache-paths.outputs.GOMODCACHE }} + key: ${{ runner.os }}-go-mod-${{ hashFiles('**/go.sum') }} + + - name: Run unit tests + id: test + shell: bash + run: go test ./... + + integration-tests: + runs-on: ubuntu-20.04 + timeout-minutes: 20 + steps: + - name: Install dependencies + run: sudo apt update && sudo apt install -y gcc + - uses: actions/checkout@v3 + + - uses: actions/setup-go@v3 + with: + go-version: "~1.20" + + # Sadly the new "set output" syntax (of writing env vars to + # $GITHUB_OUTPUT) does not work on both powershell and bash so we use the + # deprecated syntax here. + - name: Echo Go Cache Paths + id: go-cache-paths + run: | + echo "::set-output name=GOCACHE::$(go env GOCACHE)" + echo "::set-output name=GOMODCACHE::$(go env GOMODCACHE)" + + - name: Go Build Cache + uses: actions/cache@v3 + with: + path: ${{ steps.go-cache-paths.outputs.GOCACHE }} + key: ${{ runner.os }}-go-build-${{ hashFiles('**/go.**', '**.go') }} + + - name: Go Mod Cache + uses: actions/cache@v3 + with: + path: ${{ steps.go-cache-paths.outputs.GOMODCACHE }} + key: ${{ runner.os }}-go-mod-${{ hashFiles('**/go.sum') }} + + - name: Run integration tests + id: test + shell: bash + run: go test -tags=integration ./... + + build: + runs-on: ubuntu-20.04 + if: github.ref != 'refs/heads/main' + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-go@v3 + with: + go-version: "~1.20" + - name: build image + run: make -j build/image/envbox + publish: + runs-on: ubuntu-20.04 + if: github.ref == 'refs/heads/main' + steps: + - name: Docker Login + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - uses: actions/checkout@v3 + + - uses: actions/setup-go@v3 + with: + go-version: "~1.20" + + - name: build image + run: make -j build/image/envbox + + - name: tag image + run: docker tag envbox ghcr.io/coder/envbox:latest + + - name: push image + run: docker push ghcr.io/coder/envbox:latest diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..dabe111 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +build +cmd/envbox/envbox +.terraform/ +*.lock.hcl diff --git a/.golangci.yaml b/.golangci.yaml new file mode 100644 index 0000000..424f575 --- /dev/null +++ b/.golangci.yaml @@ -0,0 +1,260 @@ +# See https://golangci-lint.run/usage/configuration/ +# Over time we should try tightening some of these. + +linters-settings: + gocognit: + min-complexity: 46 # Min code complexity (def 30). + + goconst: + min-len: 4 # Min length of string consts (def 3). + min-occurrences: 3 # Min number of const occurrences (def 3). + + gocritic: + enabled-checks: + # - appendAssign + # - appendCombine + - argOrder + # - assignOp + # - badCall + - badCond + - badLock + - badRegexp + - boolExprSimplify + # - builtinShadow + - builtinShadowDecl + - captLocal + - caseOrder + - codegenComment + # - commentedOutCode + - commentedOutImport + - commentFormatting + - defaultCaseOrder + - deferUnlambda + # - deprecatedComment + # - docStub + - dupArg + - dupBranchBody + - dupCase + - dupImport + - dupSubExpr + # - elseif + - emptyFallthrough + # - emptyStringTest + # - equalFold + # - evalOrder + # - exitAfterDefer + # - exposedSyncMutex + # - filepathJoin + - flagDeref + - flagName + - hexLiteral + # - httpNoBody + # - hugeParam + # - ifElseChain + # - importShadow + - indexAlloc + - initClause + - ioutilDeprecated + - mapKey + - methodExprCall + # - nestingReduce + - newDeref + - nilValReturn + # - octalLiteral + - offBy1 + # - paramTypeCombine + # - preferStringWriter + # - preferWriteByte + # - ptrToRefParam + # - rangeExprCopy + # - rangeValCopy + - regexpMust + - regexpPattern + # - regexpSimplify + - ruleguard + - singleCaseSwitch + - sloppyLen + # - sloppyReassign + - sloppyTypeAssert + - sortSlice + - sprintfQuotedString + - sqlQuery + # - stringConcatSimplify + # - stringXbytes + # - suspiciousSorting + - switchTrue + - truncateCmp + - typeAssertChain + # - typeDefFirst + - typeSwitchVar + # - typeUnparen + - underef + # - unlabelStmt + # - unlambda + # - unnamedResult + # - unnecessaryBlock + # - unnecessaryDefer + # - unslice + - valSwap + - weakCond + # - whyNoLint + # - wrapperFunc + # - yodaStyleExpr + settings: + ruleguard: + failOn: all + rules: "${configDir}/scripts/rules.go" + + staticcheck: + # https://staticcheck.io/docs/options#checks + # We disable SA1019 because it gets angry about our usage of xerrors. We + # intentionally xerrors because stack frame support didn't make it into the + # stdlib port. + checks: ["all", "-SA1019"] + + goimports: + local-prefixes: coder.com,cdr.dev,go.coder.com,github.com/cdr,github.com/coder + + gocyclo: + min-complexity: 50 + + importas: + no-unaliased: true + + misspell: + locale: US + ignore-words: + - trialer + + nestif: + min-complexity: 4 # Min complexity of if statements (def 5, goal 4) + + revive: + # see https://github.com/mgechev/revive#available-rules for details. + ignore-generated-header: true + severity: warning + rules: + - name: atomic + - name: bare-return + - name: blank-imports + - name: bool-literal-in-expr + - name: call-to-gc + - name: confusing-naming + - name: confusing-results + - name: constant-logical-expr + - name: context-as-argument + - name: context-keys-type + - name: deep-exit + - name: defer + - name: dot-imports + - name: duplicated-imports + - name: early-return + - name: empty-block + - name: empty-lines + - name: error-naming + - name: error-return + - name: error-strings + - name: errorf + - name: exported + - name: flag-parameter + - name: get-return + - name: identical-branches + - name: if-return + - name: import-shadowing + - name: increment-decrement + - name: indent-error-flow + # - name: modifies-parameter + - name: modifies-value-receiver + - name: package-comments + - name: range + - name: range-val-address + - name: range-val-in-closure + - name: receiver-naming + - name: redefines-builtin-id + - name: string-of-int + - name: struct-tag + - name: superfluous-else + - name: time-naming + - name: unconditional-recursion + - name: unexported-naming + - name: unexported-return + - name: unhandled-error + - name: unnecessary-stmt + - name: unreachable-code + - name: unused-parameter + - name: unused-receiver + - name: var-declaration + - name: var-naming + - name: waitgroup-by-value + +issues: + # Rules listed here: https://github.com/securego/gosec#available-rules + exclude-rules: + - path: _test\.go + linters: + # We use assertions rather than explicitly checking errors in tests + - errcheck + + fix: true + max-issues-per-linter: 0 + max-same-issues: 0 + +run: + concurrency: 4 + skip-dirs: + - node_modules + skip-files: + - scripts/rules.go + timeout: 5m + +# Over time, add more and more linters from +# https://golangci-lint.run/usage/linters/ as the code improves. +linters: + disable-all: true + enable: + - asciicheck + - bidichk + - bodyclose + - dogsled + - errcheck + - errname + - errorlint + - exportloopref + - forcetypeassert + - gocritic + - gocyclo + - goimports + - gomodguard + - gosec + - gosimple + - govet + - importas + - ineffassign + - makezero + - misspell + - nilnil + - noctx + - paralleltest + - revive + + # These don't work until the following issue is solved. + # https://github.com/golangci/golangci-lint/issues/2649 + # - rowserrcheck + # - sqlclosecheck + # - structcheck + # - wastedassign + + - staticcheck + - tenv + # In Go, it's possible for a package to test it's internal functionality + # without testing any exported functions. This is enabled to promote + # decomposing a package before testing it's internals. A function caller + # should be able to test most of the functionality from exported functions. + # + # There are edge-cases to this rule, but they should be carefully considered + # to avoid structural inconsistency. + - testpackage + - tparallel + - typecheck + - unconvert + - unused diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..6e380ce --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "go.lintTool": "golangci-lint", + "go.lintFlags": ["--fast"] +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..0ad25db --- /dev/null +++ b/LICENSE @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..61084c8 --- /dev/null +++ b/Makefile @@ -0,0 +1,40 @@ +PROJECT_ROOT := $(shell git rev-parse --show-toplevel) +GO_FILES := $(shell git ls-files '*.go' '*.sum') +IMAGE_FILES := $(shell find deploy) + +.PHONY: clean +clean: + rm -rf build + +build/envbox: $(GO_FILES) + go build -o build/envbox ./cmd/envbox + +.PHONY: build/image/envbox +build/image/envbox: build/image/envbox/.ctx + +build/image/envbox/.ctx: build/envbox $(IMAGE_FILES) + mkdir -p $(@D) + cp -r build/envbox deploy/. $(@D) + docker build -t envbox $(@D) + touch $@ + +.PHONY: fmt +fmt: fmt/go fmt/tf fmt/md + +.PHONY: fmt/go +fmt/go: + # VS Code users should check out + # https://github.com/mvdan/gofumpt#visual-studio-code + go run mvdan.cc/gofumpt@v0.4.0 -w -l . + +.PHONY: fmt/tf +fmt/tf: + # VS Code users should check out + # https://github.com/mvdan/gofumpt#visual-studio-code + terraform fmt ./template.tf + +.PHONY: fmt/md +fmt/tf: + # VS Code users should check out + # https://github.com/mvdan/gofumpt#visual-studio-code + markdownfmt -w ./README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..6304327 --- /dev/null +++ b/README.md @@ -0,0 +1,36 @@ +# envbox + +## Introduction + +`envbox` is an image that enables creating non-privileged containers capable of running system-level software (e.g. `dockerd`, `systemd`, etc) in Kubernetes. + +It mainly acts as a wrapper for the excellent [sysbox runtime](https://github.com/nestybox/sysbox/) developed by [Nestybox](https://www.nestybox.com/). For more details on the security of `sysbox` containers see sysbox's [official documentation](https://github.com/nestybox/sysbox/blob/master/docs/user-guide/security.md). + +## Envbox Configuration + +The environment variables can be used to configure various aspects of the inner and outer container. + +| env | usage | required | +|----------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------| +| `CODER_INNER_IMAGE` | The image to use for the inner container. | True | +| `CODER_INNER_USERNAME` | The username to use for the inner container. | True | +| `CODER_AGENT_TOKEN` | The [Coder Agent](https://coder.com/docs/v2/latest/about/architecture#agents) token to pass to the inner container. | True | +| `CODER_INNER_ENVS` | The environment variables to pass to the inner container. A wildcard can be used to match a prefix. Ex: `CODER_INNER_ENVS=KUBERNETES_*,MY_ENV,MY_OTHER_ENV` | false | +| `CODER_INNER_HOSTNAME` | The hostname to use for the inner container. | false | +| `CODER_IMAGE_PULL_SECRET` | The docker credentials to use when pulling the inner container. The recommended way to do this is to create an [Image Pull Secret](https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/#registry-secret-existing-credentials) and then reference the secret using an [environment variable](https://kubernetes.io/docs/tasks/inject-data-application/distribute-credentials-secure/#define-container-environment-variables-using-secret-data). | false | +| `CODER_DOCKER_BRIDGE_CIDR` | The bridge CIDR to start the Docker daemon with. | false | +| `CODER_MOUNTS` | A list of mounts to mount into the inner container. Mounts default to `rw`. Ex: `CODER_MOUNTS=/home/coder:/home/coder,/var/run/mysecret:/var/run/mysecret:ro` | false | +| `CODER_USR_LIB_DIR` | The mountpoint of the host `/usr/lib` directory. Only required when using GPUs. | false | +| `CODER_ADD_TUN` | If `CODER_ADD_TUN=true` add a TUN device to the inner container. | false | +| `CODER_ADD_FUSE` | If `CODER_ADD_FUSE=true` add a FUSE device to the inner container. | false | +| `CODER_ADD_GPU` | If `CODER_ADD_GPU=true` add detected GPUs and related files to the inner container. Requires setting `CODER_USR_LIB_DIR` and mounting in the hosts `/usr/lib/` directory. | false | +| `CODER_CPUS` | Dictates the number of CPUs to allocate the inner container. It is recommended to set this using the Kubernetes [Downward API](https://kubernetes.io/docs/tasks/inject-data-application/environment-variable-expose-pod-information/#use-container-fields-as-values-for-environment-variables). | false | +| `CODER_MEMORY` | Dictates the max memory (in bytes) to allocate the inner container. It is recommended to set this using the Kubernetes [Downward API](https://kubernetes.io/docs/tasks/inject-data-application/environment-variable-expose-pod-information/#use-container-fields-as-values-for-environment-variables). | false | + +## Coder Template + +A [Coder Template](https://coder.com/docs/v2/latest/templates) can be found in the [root of the repo](./template.tf) to provide a starting point for customizing an envbox container. + +## Development + +It is not possible to develop `envbox` effectively using a containerized environment (includes developing `envbox` using `envbox`). A VM, personal machine, or similar environment is required to run the [integration](./integration/) test suite. diff --git a/background/doc.go b/background/doc.go new file mode 100644 index 0000000..ac72f25 --- /dev/null +++ b/background/doc.go @@ -0,0 +1,3 @@ +// Package background contains an abstraction for running processes in the +// background. +package background diff --git a/background/process.go b/background/process.go new file mode 100644 index 0000000..810af59 --- /dev/null +++ b/background/process.go @@ -0,0 +1,267 @@ +package background + +import ( + "bufio" + "bytes" + "context" + "fmt" + "io" + "os" + "strings" + "sync" + "sync/atomic" + "syscall" + "time" + + "github.com/spf13/afero" + "golang.org/x/xerrors" + + "cdr.dev/slog" + "github.com/coder/envbox/xio" + "github.com/coder/envbox/xunix" +) + +// Process is an abstraction for running a command as a background process. +type Process struct { + ctx context.Context + cancel context.CancelFunc + log slog.Logger + cmd xunix.Cmd + binName string + + userKilled *int64 + waitCh chan error + mu sync.Mutex +} + +// New returns an instantiated daemon. +func New(ctx context.Context, log slog.Logger, cmd string, args ...string) *Process { + ctx, cancel := context.WithCancel(ctx) + return &Process{ + ctx: ctx, + cancel: cancel, + waitCh: make(chan error, 1), + cmd: xunix.GetExecer(ctx).CommandContext(ctx, cmd, args...), + log: log.Named(cmd), + userKilled: i64ptr(0), + binName: cmd, + } +} + +// Start starts the daemon. It functions akin to ox/exec.Command.Start(). +func (d *Process) Start() error { + d.mu.Lock() + defer d.mu.Unlock() + + return d.startProcess() +} + +// Wait waits for the process to exit, returning the error on the provided +// channel. +func (d *Process) Wait() <-chan error { + d.mu.Lock() + waitCh := d.waitCh + d.mu.Unlock() + + return waitCh +} + +// Run runs the command and waits for it to exit. It is a convenience +// function that combines both Start() and Wait(). +func (d *Process) Run() <-chan error { + err := d.Start() + if err != nil { + ch := make(chan error, 1) + ch <- err + return ch + } + + return d.Wait() +} + +// Restart kill the running process and reruns the command with the updated +// cmd and args. +func (d *Process) Restart(ctx context.Context, cmd string, args ...string) error { + d.mu.Lock() + defer d.mu.Unlock() + + err := d.kill(syscall.SIGTERM) + if err != nil { + return xerrors.Errorf("kill cmd: %w", err) + } + + ctx, cancel := context.WithCancel(ctx) + d.ctx = ctx + d.cancel = cancel + d.cmd = xunix.GetExecer(ctx).CommandContext(ctx, cmd, args...) + d.waitCh = make(chan error, 1) + d.userKilled = i64ptr(0) + d.binName = cmd + + return d.startProcess() +} + +func (d *Process) startProcess() error { + var ( + buf bytes.Buffer + + pr, pw = io.Pipe() + // Wrap our buffer in a limiter to + // avoid ballooning our memory use over time. + psw = &xio.PrefixSuffixWriter{ + N: 1 << 10, + W: &buf, + } + + w = &xio.SyncWriter{W: pw} + out = &xio.SyncWriter{W: psw} + + mw = io.MultiWriter(w, out) + cmd = d.cmd + ) + + cmd.SetStdout(mw) + cmd.SetStderr(mw) + + go scanIntoLog(d.ctx, d.log, bufio.NewScanner(pr), d.binName) + + err := d.cmd.Start() + if err != nil { + return xerrors.Errorf("start: %w", err) + } + + userKilled := d.userKilled + + go func() { + defer d.cancel() + defer close(d.waitCh) + + err := cmd.Wait() + _ = psw.Flush() + + // If the user killed the application the actual error returned + // from wait doesn't really matter. + if atomic.LoadInt64(userKilled) == 1 { + d.waitCh <- ErrUserKilled + return + } + + if err == nil { + d.waitCh <- nil + } else { + d.waitCh <- xerrors.Errorf("%s: %w", buf.Bytes(), err) + } + }() + return nil +} + +func (d *Process) kill(sig syscall.Signal) error { + if d.cmd.OSProcess() == nil { + return xerrors.Errorf("cmd has not been started") + } + + atomic.StoreInt64(d.userKilled, 1) + + pid := d.cmd.OSProcess().Pid + err := d.cmd.OSProcess().Signal(sig) + if err != nil { + return xerrors.Errorf("kill proc: %w", err) + } + + ticker := time.NewTicker(time.Millisecond * 10) + defer ticker.Stop() + + fs := xunix.GetFS(d.ctx) + + // Try to find the process in the procfs. If we can't find + // it, it means the process has exited. It's also possible that + // we find the same PID but the cmd is different indicating the PID + // has been reused. + exited, err := isProcExited(fs, pid, d.binName) + if err != nil { + return xerrors.Errorf("is proc cmd: %w", err) + } + + if exited { + return nil + } + + for { + select { + case <-d.ctx.Done(): + return d.ctx.Err() + case <-ticker.C: + exited, err = isProcExited(fs, pid, d.binName) + if err != nil { + return xerrors.Errorf("is proc cmd: %w", err) + } + + if exited { + return nil + } + } + } +} + +// isProcExited checks if the provided PID has exited. It does this +// by attempting to read its entry in /proc/. If it can't find the +// entry then the process has exited. If the entry exists we check to see +// if the cmd is the same since it is possible (even if extremely unlikely) +// that the PID may be reclaimed and reused for a separate process. +func isProcExited(fs afero.Fs, pid int, cmd string) (bool, error) { + cmdline, err := afero.ReadFile(fs, fmt.Sprintf("/proc/%d/cmdline", pid)) + if xerrors.Is(err, os.ErrNotExist) { + return true, nil + } + if err != nil { + return false, xerrors.Errorf("read file: %w", err) + } + + args := bytes.Split(cmdline, []byte{'0'}) + if len(args) < 1 { + // Honestly idk. + return false, xerrors.Errorf("cmdline has no output (%s)?", cmdline) + } + + // If the cmd doesn't match then the PID has been reused for a different + // process indicating the proc we're looking for has successfully exited. + return cmd != string(args[0]), nil +} + +func scanIntoLog(ctx context.Context, log slog.Logger, scanner *bufio.Scanner, binaryName string) { + for scanner.Scan() { + select { + case <-ctx.Done(): + return + default: + } + + var ( + line = scanner.Text() + logFn = log.Info + ) + + if strings.Contains(line, "level=debug") { + logFn = log.Debug + } else if strings.Contains(line, "level=info") { + logFn = log.Info + } else if strings.Contains(line, "level=warning") { + logFn = log.Warn + } else if strings.Contains(line, "level=error") { + logFn = log.Error + } else if strings.Contains(line, "level=fatal") { + logFn = log.Error + } + + logFn(ctx, "child log", + slog.F("process", binaryName), + slog.F("content", line), + ) + } +} + +var ErrUserKilled = xerrors.Errorf("daemon killed by user") + +func i64ptr(i int64) *int64 { + return &i +} diff --git a/buildlog/coder.go b/buildlog/coder.go new file mode 100644 index 0000000..062aea6 --- /dev/null +++ b/buildlog/coder.go @@ -0,0 +1,138 @@ +package buildlog + +import ( + "context" + "fmt" + "time" + + "cdr.dev/slog" + "github.com/coder/coder/codersdk/agentsdk" +) + +const ( + // To avoid excessive DB calls we batch our output. + // We'll keep at most 20KB of output in memory at a given time. + CoderLoggerMaxLogs = 20 + MaxCoderLogSize = 1 << 10 +) + +type StartupLog struct { + CreatedAt time.Time `json:"created_at"` + Output string `json:"output"` +} + +type CoderClient interface { + PatchStartupLogs(ctx context.Context, req agentsdk.PatchStartupLogs) error +} + +type CoderLogger struct { + ctx context.Context + cancel context.CancelFunc + client CoderClient + logger slog.Logger + logChan chan string + err error +} + +func OpenCoderLogger(ctx context.Context, client CoderClient, log slog.Logger) Logger { + ctx, cancel := context.WithCancel(ctx) + + coder := &CoderLogger{ + ctx: ctx, + cancel: cancel, + client: client, + logger: log, + logChan: make(chan string), + } + + go coder.processLogs() + + return coder +} + +func (c *CoderLogger) Infof(format string, a ...any) { + c.Info(fmt.Sprintf(format, a...)) +} + +func (c *CoderLogger) Info(output string) { + c.log(output) +} + +func (c *CoderLogger) Errorf(format string, a ...any) { + c.Error(fmt.Sprintf(format, a...)) +} + +func (c *CoderLogger) Error(output string) { + c.log("ERROR: " + output) +} + +func (c *CoderLogger) log(output string) { + if c.err != nil { + return + } + c.logChan <- output +} + +func (c *CoderLogger) Write(p []byte) (int, error) { + c.Info(string(p)) + return len(p), nil +} + +func (c *CoderLogger) Close() { + c.cancel() +} + +func (c *CoderLogger) processLogs() { + for { + var ( + line string + logs = make([]agentsdk.StartupLog, 0, CoderLoggerMaxLogs) + ) + + select { + case line = <-c.logChan: + lines := cutString(line, MaxCoderLogSize) + + for _, output := range lines { + logs = append(logs, agentsdk.StartupLog{ + CreatedAt: time.Now(), + Output: output, + }) + } + + case <-c.ctx.Done(): + close(c.logChan) + return + } + + // Send the logs in a goroutine so that we can avoid blocking + // too long on the channel. + cpLogs := logs + go func(startupLogs []agentsdk.StartupLog) { + err := c.client.PatchStartupLogs(c.ctx, agentsdk.PatchStartupLogs{ + Logs: startupLogs, + }) + if err != nil { + c.logger.Error(c.ctx, "send startup logs", slog.Error(err)) + } + }(cpLogs) + } +} + +// cutString cuts a string up into smaller strings that have a len no greater +// than the provided max size. +// If the string is less than the max size the return slice has one +// element with a value of the provided string. +func cutString(s string, maxSize int) []string { + if len(s) <= maxSize { + return []string{s} + } + + toks := []string{} + for len(s) > maxSize { + toks = append(toks, s[:maxSize]) + s = s[maxSize:] + } + + return append(toks, s) +} diff --git a/buildlog/coder_test.go b/buildlog/coder_test.go new file mode 100644 index 0000000..72ac46c --- /dev/null +++ b/buildlog/coder_test.go @@ -0,0 +1,117 @@ +package buildlog_test + +import ( + "sync" + "testing" + "time" + + "golang.org/x/exp/slices" + "golang.org/x/net/context" + + "cdr.dev/slog/sloggers/slogtest" + + "github.com/stretchr/testify/require" + + "github.com/coder/coder/codersdk/agentsdk" + "github.com/coder/coder/cryptorand" + "github.com/coder/envbox/buildlog" +) + +func TestCoderLog(t *testing.T) { + t.Parallel() + + t.Run("OK", func(t *testing.T) { + t.Parallel() + + var ( + client = &fakeCoderClient{} + ctx = context.Background() + slogger = slogtest.Make(t, nil) + logMu sync.Mutex + ) + + expectedLog, err := cryptorand.String(10) + require.NoError(t, err) + var actualLog string + client.PatchStartupLogsFn = func(ctx context.Context, logs agentsdk.PatchStartupLogs) error { + logMu.Lock() + defer logMu.Unlock() + + require.Len(t, logs.Logs, 1) + require.NotZero(t, logs.Logs[0].CreatedAt) + require.Equal(t, expectedLog, logs.Logs[0].Output) + actualLog = logs.Logs[0].Output + return nil + } + + log := buildlog.OpenCoderLogger(ctx, client, slogger) + + log.Info(expectedLog) + + require.Eventually(t, func() bool { + logMu.Lock() + defer logMu.Unlock() + equal := actualLog == expectedLog + if !equal { + t.Logf("actual log %q does not equal expected log %q", actualLog, expectedLog) + } + return equal + }, time.Millisecond*5, time.Millisecond) + }) + + // Try sending a large line that exceeds the maximum Coder accepts (1KiB). + // Assert that it is sent as two logs instead. + t.Run("OutputNotDropped", func(t *testing.T) { + t.Parallel() + + var ( + maxLogs = 2 + client = fakeCoderClient{} + ctx = context.Background() + slogger = slogtest.Make(t, nil) + actualLogs = make([]string, 0, maxLogs) + logMu sync.Mutex + ) + client.PatchStartupLogsFn = func(ctx context.Context, logs agentsdk.PatchStartupLogs) error { + logMu.Lock() + defer logMu.Unlock() + + require.Len(t, logs.Logs, maxLogs) + for _, l := range logs.Logs { + require.NotZero(t, l.CreatedAt) + actualLogs = append(actualLogs, l.Output) + } + return nil + } + + log := buildlog.OpenCoderLogger(ctx, client, slogger) + + bigLine, err := cryptorand.String(buildlog.MaxCoderLogSize + buildlog.MaxCoderLogSize/2) + require.NoError(t, err) + // The line should be chopped up into smaller logs so that we don't + // drop output. + expectedLogs := []string{ + bigLine[:buildlog.MaxCoderLogSize], + bigLine[buildlog.MaxCoderLogSize:], + } + log.Info(bigLine) + // Close the logger to flush the logs. + log.Close() + require.Eventually(t, func() bool { + logMu.Lock() + defer logMu.Unlock() + return slices.Equal(expectedLogs, actualLogs) + }, time.Millisecond*5, time.Millisecond) + }) +} + +type fakeCoderClient struct { + PatchStartupLogsFn func(context.Context, agentsdk.PatchStartupLogs) error +} + +func (f fakeCoderClient) PatchStartupLogs(ctx context.Context, req agentsdk.PatchStartupLogs) error { + if f.PatchStartupLogsFn != nil { + return f.PatchStartupLogsFn(ctx, req) + } + return nil +} diff --git a/buildlog/doc.go b/buildlog/doc.go new file mode 100644 index 0000000..99149b7 --- /dev/null +++ b/buildlog/doc.go @@ -0,0 +1,3 @@ +// Package startuplog contains logic for writing logs related to startup +// of the workspace. +package buildlog diff --git a/buildlog/json.go b/buildlog/json.go new file mode 100644 index 0000000..78a41e9 --- /dev/null +++ b/buildlog/json.go @@ -0,0 +1,67 @@ +package buildlog + +import ( + "encoding/json" + "fmt" + "time" +) + +const ( + JSONLogTypeDone = "done" + JSONLogTypeInfo = "info" + JSONLogTypeError = "error" +) + +type JSONLog struct { + Output string `json:"output"` + Time time.Time `json:"time"` + Type string `json:"type"` +} + +type JSONLogger struct { + Encoder *json.Encoder +} + +func (j JSONLogger) Write(p []byte) (int, error) { + j.Info(string(p)) + return len(p), nil +} + +func (j JSONLogger) Infof(format string, a ...any) { + j.Info(fmt.Sprintf(format, a...)) +} + +func (j JSONLogger) Info(output string) { + j.log(JSONLog{ + Output: output, + Time: time.Now(), + Type: JSONLogTypeInfo, + }) +} + +func (j JSONLogger) Errorf(format string, a ...any) { + j.Error(fmt.Sprintf(format, a...)) +} + +func (j JSONLogger) Error(output string) { + j.log(JSONLog{ + Output: output, + Time: time.Now(), + Type: JSONLogTypeError, + }) +} + +// nolint +func (j JSONLogger) log(jlog JSONLog) { + err := j.Encoder.Encode(jlog) + if err != nil { + panic(err) + } +} + +func (j JSONLogger) Close() { + j.log(JSONLog{ + Type: JSONLogTypeDone, + Time: time.Now(), + }) +} diff --git a/buildlog/json_test.go b/buildlog/json_test.go new file mode 100644 index 0000000..d6349f3 --- /dev/null +++ b/buildlog/json_test.go @@ -0,0 +1,96 @@ +package buildlog_test + +import ( + "bytes" + "encoding/json" + "testing" + + "golang.org/x/xerrors" + + "github.com/stretchr/testify/require" + + "github.com/coder/envbox/buildlog" +) + +func TestJSONLog(t *testing.T) { + t.Parallel() + + type testcase struct { + name string + expectedLog buildlog.JSONLog + logFn func(l buildlog.Logger) + } + + cases := []testcase{ + { + name: "Info", + expectedLog: buildlog.JSONLog{ + Output: "foo", + Type: buildlog.JSONLogTypeInfo, + }, + logFn: func(l buildlog.Logger) { + l.Info("foo") + }, + }, + { + name: "Infof", + expectedLog: buildlog.JSONLog{ + Output: "foo: bar", + Type: buildlog.JSONLogTypeInfo, + }, + logFn: func(l buildlog.Logger) { + l.Infof("foo: %s", "bar") + }, + }, + { + name: "Error", + expectedLog: buildlog.JSONLog{ + Output: "some error", + Type: buildlog.JSONLogTypeError, + }, + logFn: func(l buildlog.Logger) { + l.Error("some error") + }, + }, + { + name: "Errorf", + expectedLog: buildlog.JSONLog{ + Output: "some error: my error", + Type: buildlog.JSONLogTypeError, + }, + logFn: func(l buildlog.Logger) { + l.Errorf("some error: %v", xerrors.New("my error")) + }, + }, + { + name: "Close", + expectedLog: buildlog.JSONLog{ + Output: "", + Type: buildlog.JSONLogTypeDone, + }, + logFn: func(l buildlog.Logger) { + l.Close() + }, + }, + } + + for _, c := range cases { + c := c + t.Run(c.name, func(t *testing.T) { + t.Parallel() + + var buf bytes.Buffer + jlog := buildlog.JSONLogger{ + Encoder: json.NewEncoder(&buf), + } + + c.logFn(jlog) + + var actualLog buildlog.JSONLog + json.NewDecoder(&buf).Decode(&actualLog) + require.NotZero(t, actualLog.Time) + require.Equal(t, c.expectedLog.Output, actualLog.Output) + require.Equal(t, c.expectedLog.Type, actualLog.Type) + }) + } +} diff --git a/buildlog/logger.go b/buildlog/logger.go new file mode 100644 index 0000000..b0f8312 --- /dev/null +++ b/buildlog/logger.go @@ -0,0 +1,79 @@ +package buildlog + +import ( + "context" + "fmt" + "io" +) + +type loggerCtxKey struct{} + +func GetLogger(ctx context.Context) Logger { + l := ctx.Value(loggerCtxKey{}) + if l == nil { + return nopLogger{} + } + //nolint + return l.(Logger) +} + +func WithLogger(ctx context.Context, l Logger) context.Context { + return context.WithValue(ctx, loggerCtxKey{}, l) +} + +type Logger interface { + Info(output string) + Infof(format string, a ...any) + Error(output string) + Errorf(format string, a ...any) + Close() + io.Writer +} + +func MultiLogger(loggers ...Logger) Logger { + return multiLogger{loggers} +} + +type multiLogger struct { + loggers []Logger +} + +func (m multiLogger) Infof(format string, a ...any) { + m.Info(fmt.Sprintf(format, a...)) +} + +func (m multiLogger) Info(output string) { + for _, log := range m.loggers { + log.Info(output) + } +} + +func (m multiLogger) Errorf(format string, a ...any) { + m.Error(fmt.Sprintf(format, a...)) +} + +func (m multiLogger) Error(output string) { + for _, log := range m.loggers { + log.Error(output) + } +} + +func (m multiLogger) Write(p []byte) (int, error) { + m.Info(string(p)) + return len(p), nil +} + +func (m multiLogger) Close() { + for _, log := range m.loggers { + log.Close() + } +} + +type nopLogger struct{} + +func (nopLogger) Info(string) {} +func (nopLogger) Infof(string, ...any) {} +func (nopLogger) Errorf(string, ...any) {} +func (nopLogger) Error(string) {} +func (nopLogger) Write([]byte) (int, error) { return 0, nil } +func (nopLogger) Close() {} diff --git a/cli/cliflag/cliflag.go b/cli/cliflag/cliflag.go new file mode 100644 index 0000000..10f4b77 --- /dev/null +++ b/cli/cliflag/cliflag.go @@ -0,0 +1,183 @@ +// Package cliflag extends flagset with environment variable defaults. +// +// Usage: +// +// cliflag.String(root.Flags(), &address, "address", "a", "CODER_ADDRESS", "127.0.0.1:3000", "The address to serve the API and dashboard") +// +// Will produce the following usage docs: +// +// -a, --address string The address to serve the API and dashboard (uses $CODER_ADDRESS). (default "127.0.0.1:3000") +package cliflag + +import ( + "fmt" + "os" + "strconv" + "strings" + "time" + + "github.com/spf13/cobra" + "github.com/spf13/pflag" +) + +// IsSetBool returns the value of the boolean flag if it is set. +// It returns false if the flag isn't set or if any error occurs attempting +// to parse the value of the flag. +func IsSetBool(cmd *cobra.Command, name string) bool { + val, ok := IsSet(cmd, name) + if !ok { + return false + } + + b, err := strconv.ParseBool(val) + return err == nil && b +} + +// IsSet returns the string value of the flag and whether it was set. +func IsSet(cmd *cobra.Command, name string) (string, bool) { + flag := cmd.Flag(name) + if flag == nil { + return "", false + } + + return flag.Value.String(), flag.Changed +} + +// String sets a string flag on the given flag set. +func String(flagset *pflag.FlagSet, name, shorthand, env, def, usage string) { + v, ok := os.LookupEnv(env) + if !ok || v == "" { + v = def + } + flagset.StringP(name, shorthand, v, fmtUsage(usage, env)) +} + +// StringVarP sets a string flag on the given flag set. +func StringVarP(flagset *pflag.FlagSet, p *string, name string, shorthand string, env string, def string, usage string) { + v, ok := os.LookupEnv(env) + if !ok || v == "" { + v = def + } + flagset.StringVarP(p, name, shorthand, v, fmtUsage(usage, env)) +} + +func StringArray(flagset *pflag.FlagSet, name, shorthand, env string, def []string, usage string) { + v, ok := os.LookupEnv(env) + if !ok || v == "" { + if v == "" { + def = []string{} + } else { + def = strings.Split(v, ",") + } + } + flagset.StringArrayP(name, shorthand, def, fmtUsage(usage, env)) +} + +func StringArrayVarP(flagset *pflag.FlagSet, ptr *[]string, name string, shorthand string, env string, def []string, usage string) { + val, ok := os.LookupEnv(env) + if ok { + if val == "" { + def = []string{} + } else { + def = strings.Split(val, ",") + } + } + flagset.StringArrayVarP(ptr, name, shorthand, def, fmtUsage(usage, env)) +} + +// Uint8VarP sets a uint8 flag on the given flag set. +func Uint8VarP(flagset *pflag.FlagSet, ptr *uint8, name string, shorthand string, env string, def uint8, usage string) { + val, ok := os.LookupEnv(env) + if !ok || val == "" { + flagset.Uint8VarP(ptr, name, shorthand, def, fmtUsage(usage, env)) + return + } + + vi64, err := strconv.ParseUint(val, 10, 8) + if err != nil { + flagset.Uint8VarP(ptr, name, shorthand, def, fmtUsage(usage, env)) + return + } + + flagset.Uint8VarP(ptr, name, shorthand, uint8(vi64), fmtUsage(usage, env)) +} + +// IntVarP sets a uint8 flag on the given flag set. +func IntVarP(flagset *pflag.FlagSet, ptr *int, name string, shorthand string, env string, def int, usage string) { + val, ok := os.LookupEnv(env) + if !ok || val == "" { + flagset.IntVarP(ptr, name, shorthand, def, fmtUsage(usage, env)) + return + } + + vi64, err := strconv.ParseUint(val, 10, 8) + if err != nil { + flagset.IntVarP(ptr, name, shorthand, def, fmtUsage(usage, env)) + return + } + + flagset.IntVarP(ptr, name, shorthand, int(vi64), fmtUsage(usage, env)) +} + +func Bool(flagset *pflag.FlagSet, name, shorthand, env string, def bool, usage string) { + val, ok := os.LookupEnv(env) + if !ok || val == "" { + flagset.BoolP(name, shorthand, def, fmtUsage(usage, env)) + return + } + + valb, err := strconv.ParseBool(val) + if err != nil { + flagset.BoolP(name, shorthand, def, fmtUsage(usage, env)) + return + } + + flagset.BoolP(name, shorthand, valb, fmtUsage(usage, env)) +} + +// BoolVarP sets a bool flag on the given flag set. +func BoolVarP(flagset *pflag.FlagSet, ptr *bool, name string, shorthand string, env string, def bool, usage string) { + val, ok := os.LookupEnv(env) + if !ok || val == "" { + flagset.BoolVarP(ptr, name, shorthand, def, fmtUsage(usage, env)) + return + } + + valb, err := strconv.ParseBool(val) + if err != nil { + flagset.BoolVarP(ptr, name, shorthand, def, fmtUsage(usage, env)) + return + } + + flagset.BoolVarP(ptr, name, shorthand, valb, fmtUsage(usage, env)) +} + +// DurationVarP sets a time.Duration flag on the given flag set. +func DurationVarP(flagset *pflag.FlagSet, ptr *time.Duration, name string, shorthand string, env string, def time.Duration, usage string) { + val, ok := os.LookupEnv(env) + if !ok || val == "" { + flagset.DurationVarP(ptr, name, shorthand, def, fmtUsage(usage, env)) + return + } + + valb, err := time.ParseDuration(val) + if err != nil { + flagset.DurationVarP(ptr, name, shorthand, def, fmtUsage(usage, env)) + return + } + + flagset.DurationVarP(ptr, name, shorthand, valb, fmtUsage(usage, env)) +} + +func fmtUsage(u string, env string) string { + if env != "" { + // Avoid double dotting. + dot := "." + if strings.HasSuffix(u, ".") { + dot = "" + } + u = fmt.Sprintf("%s%s\n(Consumes $%s", u, dot, env) + } + + return u +} diff --git a/cli/cliflag/cliflag_test.go b/cli/cliflag/cliflag_test.go new file mode 100644 index 0000000..d05468c --- /dev/null +++ b/cli/cliflag/cliflag_test.go @@ -0,0 +1,277 @@ +package cliflag_test + +import ( + "fmt" + "strconv" + "testing" + "time" + + "github.com/spf13/pflag" + "github.com/stretchr/testify/require" + + "github.com/coder/coder/cryptorand" + "github.com/coder/envbox/cli/cliflag" +) + +// Testcliflag cannot run in parallel because it uses t.Setenv. +// +//nolint:paralleltest +func TestCliflag(t *testing.T) { + t.Run("StringDefault", func(t *testing.T) { + flagset, name, shorthand, env, usage := randomFlag() + def, _ := cryptorand.String(10) + cliflag.String(flagset, name, shorthand, env, def, usage) + got, err := flagset.GetString(name) + require.NoError(t, err) + require.Equal(t, def, got) + require.Contains(t, flagset.FlagUsages(), usage) + require.Contains(t, flagset.FlagUsages(), fmt.Sprintf("Consumes $%s", env)) + }) + + t.Run("StringEnvVar", func(t *testing.T) { + flagset, name, shorthand, env, usage := randomFlag() + envValue, _ := cryptorand.String(10) + t.Setenv(env, envValue) + def, _ := cryptorand.String(10) + cliflag.String(flagset, name, shorthand, env, def, usage) + got, err := flagset.GetString(name) + require.NoError(t, err) + require.Equal(t, envValue, got) + }) + + t.Run("StringVarPDefault", func(t *testing.T) { + var ptr string + flagset, name, shorthand, env, usage := randomFlag() + def, _ := cryptorand.String(10) + + cliflag.StringVarP(flagset, &ptr, name, shorthand, env, def, usage) + got, err := flagset.GetString(name) + require.NoError(t, err) + require.Equal(t, def, got) + require.Contains(t, flagset.FlagUsages(), usage) + require.Contains(t, flagset.FlagUsages(), fmt.Sprintf("Consumes $%s", env)) + }) + + t.Run("StringVarPEnvVar", func(t *testing.T) { + var ptr string + flagset, name, shorthand, env, usage := randomFlag() + envValue, _ := cryptorand.String(10) + t.Setenv(env, envValue) + def, _ := cryptorand.String(10) + + cliflag.StringVarP(flagset, &ptr, name, shorthand, env, def, usage) + got, err := flagset.GetString(name) + require.NoError(t, err) + require.Equal(t, envValue, got) + }) + + t.Run("EmptyEnvVar", func(t *testing.T) { + var ptr string + flagset, name, shorthand, _, usage := randomFlag() + def, _ := cryptorand.String(10) + + cliflag.StringVarP(flagset, &ptr, name, shorthand, "", def, usage) + got, err := flagset.GetString(name) + require.NoError(t, err) + require.Equal(t, def, got) + require.Contains(t, flagset.FlagUsages(), usage) + require.NotContains(t, flagset.FlagUsages(), "Consumes") + }) + + t.Run("StringArrayDefault", func(t *testing.T) { + var ptr []string + flagset, name, shorthand, env, usage := randomFlag() + def := []string{"hello"} + cliflag.StringArrayVarP(flagset, &ptr, name, shorthand, env, def, usage) + got, err := flagset.GetStringArray(name) + require.NoError(t, err) + require.Equal(t, def, got) + }) + + t.Run("StringArrayEnvVar", func(t *testing.T) { + var ptr []string + flagset, name, shorthand, env, usage := randomFlag() + t.Setenv(env, "wow,test") + cliflag.StringArrayVarP(flagset, &ptr, name, shorthand, env, nil, usage) + got, err := flagset.GetStringArray(name) + require.NoError(t, err) + require.Equal(t, []string{"wow", "test"}, got) + }) + + t.Run("StringArrayEnvVarEmpty", func(t *testing.T) { + var ptr []string + flagset, name, shorthand, env, usage := randomFlag() + t.Setenv(env, "") + cliflag.StringArrayVarP(flagset, &ptr, name, shorthand, env, nil, usage) + got, err := flagset.GetStringArray(name) + require.NoError(t, err) + require.Equal(t, []string{}, got) + }) + + t.Run("UInt8Default", func(t *testing.T) { + var ptr uint8 + flagset, name, shorthand, env, usage := randomFlag() + def, _ := cryptorand.Int63n(10) + + cliflag.Uint8VarP(flagset, &ptr, name, shorthand, env, uint8(def), usage) + got, err := flagset.GetUint8(name) + require.NoError(t, err) + require.Equal(t, uint8(def), got) + require.Contains(t, flagset.FlagUsages(), usage) + require.Contains(t, flagset.FlagUsages(), fmt.Sprintf("Consumes $%s", env)) + }) + + t.Run("UInt8EnvVar", func(t *testing.T) { + var ptr uint8 + flagset, name, shorthand, env, usage := randomFlag() + envValue, _ := cryptorand.Int63n(10) + t.Setenv(env, strconv.FormatUint(uint64(envValue), 10)) + def, _ := cryptorand.Int() + + cliflag.Uint8VarP(flagset, &ptr, name, shorthand, env, uint8(def), usage) + got, err := flagset.GetUint8(name) + require.NoError(t, err) + require.Equal(t, uint8(envValue), got) + }) + + t.Run("UInt8FailParse", func(t *testing.T) { + var ptr uint8 + flagset, name, shorthand, env, usage := randomFlag() + envValue, _ := cryptorand.String(10) + t.Setenv(env, envValue) + def, _ := cryptorand.Int63n(10) + + cliflag.Uint8VarP(flagset, &ptr, name, shorthand, env, uint8(def), usage) + got, err := flagset.GetUint8(name) + require.NoError(t, err) + require.Equal(t, uint8(def), got) + }) + + t.Run("IntDefault", func(t *testing.T) { + var ptr int + flagset, name, shorthand, env, usage := randomFlag() + def, _ := cryptorand.Int63n(10) + + cliflag.IntVarP(flagset, &ptr, name, shorthand, env, int(def), usage) + got, err := flagset.GetInt(name) + require.NoError(t, err) + require.Equal(t, int(def), got) + require.Contains(t, flagset.FlagUsages(), usage) + require.Contains(t, flagset.FlagUsages(), fmt.Sprintf("Consumes $%s", env)) + }) + + t.Run("IntEnvVar", func(t *testing.T) { + var ptr int + flagset, name, shorthand, env, usage := randomFlag() + envValue, _ := cryptorand.Int63n(10) + t.Setenv(env, strconv.FormatUint(uint64(envValue), 10)) + def, _ := cryptorand.Int() + + cliflag.IntVarP(flagset, &ptr, name, shorthand, env, def, usage) + got, err := flagset.GetInt(name) + require.NoError(t, err) + require.Equal(t, int(envValue), got) + }) + + t.Run("IntFailParse", func(t *testing.T) { + var ptr int + flagset, name, shorthand, env, usage := randomFlag() + envValue, _ := cryptorand.String(10) + t.Setenv(env, envValue) + def, _ := cryptorand.Int63n(10) + + cliflag.IntVarP(flagset, &ptr, name, shorthand, env, int(def), usage) + got, err := flagset.GetInt(name) + require.NoError(t, err) + require.Equal(t, int(def), got) + }) + + t.Run("BoolDefault", func(t *testing.T) { + var ptr bool + flagset, name, shorthand, env, usage := randomFlag() + def, _ := cryptorand.Bool() + + cliflag.BoolVarP(flagset, &ptr, name, shorthand, env, def, usage) + got, err := flagset.GetBool(name) + require.NoError(t, err) + require.Equal(t, def, got) + require.Contains(t, flagset.FlagUsages(), usage) + require.Contains(t, flagset.FlagUsages(), fmt.Sprintf("Consumes $%s", env)) + }) + + t.Run("BoolEnvVar", func(t *testing.T) { + var ptr bool + flagset, name, shorthand, env, usage := randomFlag() + envValue, _ := cryptorand.Bool() + t.Setenv(env, strconv.FormatBool(envValue)) + def, _ := cryptorand.Bool() + + cliflag.BoolVarP(flagset, &ptr, name, shorthand, env, def, usage) + got, err := flagset.GetBool(name) + require.NoError(t, err) + require.Equal(t, envValue, got) + }) + + t.Run("BoolFailParse", func(t *testing.T) { + var ptr bool + flagset, name, shorthand, env, usage := randomFlag() + envValue, _ := cryptorand.String(10) + t.Setenv(env, envValue) + def, _ := cryptorand.Bool() + + cliflag.BoolVarP(flagset, &ptr, name, shorthand, env, def, usage) + got, err := flagset.GetBool(name) + require.NoError(t, err) + require.Equal(t, def, got) + }) + + t.Run("DurationDefault", func(t *testing.T) { + var ptr time.Duration + flagset, name, shorthand, env, usage := randomFlag() + def, _ := cryptorand.Duration() + + cliflag.DurationVarP(flagset, &ptr, name, shorthand, env, def, usage) + got, err := flagset.GetDuration(name) + require.NoError(t, err) + require.Equal(t, def, got) + require.Contains(t, flagset.FlagUsages(), usage) + require.Contains(t, flagset.FlagUsages(), fmt.Sprintf("Consumes $%s", env)) + }) + + t.Run("DurationEnvVar", func(t *testing.T) { + var ptr time.Duration + flagset, name, shorthand, env, usage := randomFlag() + envValue, _ := cryptorand.Duration() + t.Setenv(env, envValue.String()) + def, _ := cryptorand.Duration() + + cliflag.DurationVarP(flagset, &ptr, name, shorthand, env, def, usage) + got, err := flagset.GetDuration(name) + require.NoError(t, err) + require.Equal(t, envValue, got) + }) + + t.Run("DurationFailParse", func(t *testing.T) { + var ptr time.Duration + flagset, name, shorthand, env, usage := randomFlag() + envValue, _ := cryptorand.String(10) + t.Setenv(env, envValue) + def, _ := cryptorand.Duration() + + cliflag.DurationVarP(flagset, &ptr, name, shorthand, env, def, usage) + got, err := flagset.GetDuration(name) + require.NoError(t, err) + require.Equal(t, def, got) + }) +} + +func randomFlag() (*pflag.FlagSet, string, string, string, string) { + fsname, _ := cryptorand.String(10) + flagset := pflag.NewFlagSet(fsname, pflag.PanicOnError) + name, _ := cryptorand.String(10) + shorthand, _ := cryptorand.String(1) + env, _ := cryptorand.String(10) + usage, _ := cryptorand.String(10) + + return flagset, name, shorthand, env, usage +} diff --git a/cli/clitest/cli.go b/cli/clitest/cli.go new file mode 100644 index 0000000..f6ff8cf --- /dev/null +++ b/cli/clitest/cli.go @@ -0,0 +1,86 @@ +package clitest + +import ( + "context" + "testing" + "time" + + "github.com/spf13/cobra" + "github.com/stretchr/testify/require" + "k8s.io/mount-utils" + + "github.com/coder/envbox/cli" + "github.com/coder/envbox/dockerutil" + "github.com/coder/envbox/dockerutil/dockerfake" + "github.com/coder/envbox/xunix" + "github.com/coder/envbox/xunix/xunixfake" +) + +func Execer(ctx context.Context) *xunixfake.FakeExec { + //nolint we should panic if this isn't the case. + return xunix.GetExecer(ctx).(*xunixfake.FakeExec) +} + +func FS(ctx context.Context) *xunixfake.MemFS { + //nolint we should panic if this isn't the case. + return xunix.GetFS(ctx).(*xunixfake.MemFS) +} + +func Mounter(ctx context.Context) *mount.FakeMounter { + //nolint we should panic if this isn't the case. + return xunix.Mounter(ctx).(*mount.FakeMounter) +} + +// nolint +func DockerClient(t *testing.T, ctx context.Context) *dockerfake.MockClient { + t.Helper() + + client, err := dockerutil.Client(ctx) + require.NoError(t, err) + //nolint we should panic if this isn't the case. + return client.(*dockerfake.MockClient) +} + +// New returns an instantiated Command as well as a context populated with mocked +// values for the command. All mock/fakes have been minimally configured to +// induce a successful call to the command. +func New(t *testing.T, cmd string, args ...string) (context.Context, *cobra.Command) { + t.Helper() + + var ( + execer = NewFakeExecer() + fs = NewMemFS() + mnt = &mount.FakeMounter{} + client = NewFakeDockerClient() + iface = GetNetLink(t) + ctx = ctx(t, fs, execer, mnt, client) + ) + + root := cli.Root() + // This is the one thing that isn't really mocked for the tests. + // I cringe at the thought of introducing yet another mock so + // let's avoid it for now. + // If a consumer sets the ethlink arg it should overwrite our + // default we set here. + args = append([]string{cmd, "--ethlink=" + iface.Attrs().Name, "--no-startup-log"}, args...) + root.SetArgs(args) + + FakeSysboxManagerReady(t, fs) + FakeCPUGroups(t, fs, "1234", "5678") + + return ctx, root +} + +func ctx(t *testing.T, fs xunix.FS, ex xunix.Execer, mnt mount.Interface, client dockerutil.DockerClient) context.Context { + t.Helper() + + ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) + t.Cleanup(cancel) + + ctx = xunix.WithFS(ctx, fs) + ctx = xunix.WithExecer(ctx, ex) + ctx = xunix.WithMounter(ctx, mnt) + ctx = dockerutil.WithClient(ctx, client) + + return ctx +} diff --git a/cli/clitest/fake.go b/cli/clitest/fake.go new file mode 100644 index 0000000..2d14f7f --- /dev/null +++ b/cli/clitest/fake.go @@ -0,0 +1,62 @@ +package clitest + +import ( + "bufio" + "context" + "net" + "os" + "strings" + + dockertypes "github.com/docker/docker/api/types" + "github.com/spf13/afero" + testingexec "k8s.io/utils/exec/testing" + + "github.com/coder/envbox/dockerutil" + "github.com/coder/envbox/dockerutil/dockerfake" + "github.com/coder/envbox/xunix/xunixfake" +) + +func NewMemFS() *xunixfake.MemFS { + return &xunixfake.MemFS{ + MemMapFs: &afero.MemMapFs{}, + Owner: map[string]xunixfake.FileOwner{}, + } +} + +func NewFakeExecer() *xunixfake.FakeExec { + return &xunixfake.FakeExec{ + Commands: map[string]*xunixfake.FakeCmd{}, + DefaultFakeCmd: &xunixfake.FakeCmd{ + FakeCmd: &testingexec.FakeCmd{}, + FakeProcess: &os.Process{Pid: 1111}, + // The main use of exec commands in this repo + // are to spawn daemon processes so ideally the + // default behavior is that they do not exist. + // nolint + WaitFn: func() error { select {} }, + }, + } +} + +func NewFakeDockerClient() dockerutil.DockerClient { + client := &dockerfake.MockClient{} + + client.ContainerInspectFn = func(_ context.Context, container string) (dockertypes.ContainerJSON, error) { + return dockertypes.ContainerJSON{ + ContainerJSONBase: &dockertypes.ContainerJSONBase{ + GraphDriver: dockertypes.GraphDriverData{ + Data: map[string]string{"MergedDir": "blah"}, + }, + }, + }, nil + } + + client.ContainerExecAttachFn = func(_ context.Context, execID string, config dockertypes.ExecStartCheck) (dockertypes.HijackedResponse, error) { + return dockertypes.HijackedResponse{ + Reader: bufio.NewReader(strings.NewReader("root:x:0:0:root:/root:/bin/bash")), + Conn: &net.IPConn{}, + }, nil + } + + return client +} diff --git a/cli/clitest/fs.go b/cli/clitest/fs.go new file mode 100644 index 0000000..6ad6677 --- /dev/null +++ b/cli/clitest/fs.go @@ -0,0 +1,24 @@ +package clitest + +import ( + "testing" + + "github.com/spf13/afero" + "github.com/stretchr/testify/require" + + "github.com/coder/envbox/sysboxutil" + "github.com/coder/envbox/xunix" +) + +func FakeSysboxManagerReady(t *testing.T, fs afero.Fs) { + err := afero.WriteFile(fs, sysboxutil.ManagerSocketPath, []byte(""), 0o644) + require.NoError(t, err) +} + +func FakeCPUGroups(t *testing.T, fs afero.Fs, quota, period string) { + err := afero.WriteFile(fs, xunix.CPUPeriodPath, []byte(period), 0o600) + require.NoError(t, err) + + err = afero.WriteFile(fs, xunix.CPUQuotaPath, []byte(quota), 0o600) + require.NoError(t, err) +} diff --git a/cli/clitest/net.go b/cli/clitest/net.go new file mode 100644 index 0000000..19c525e --- /dev/null +++ b/cli/clitest/net.go @@ -0,0 +1,28 @@ +package clitest + +import ( + "testing" + + "github.com/stretchr/testify/require" + "github.com/vishvananda/netlink" +) + +func GetNetLink(t *testing.T) netlink.Link { + t.Helper() + + addrs, err := netlink.AddrList(nil, netlink.FAMILY_V4) + require.NoError(t, err) + + for _, addr := range addrs { + if !addr.IP.IsGlobalUnicast() || addr.IP.To4() == nil || addr.Label == "docker0" { + continue + } + + nl, err := netlink.LinkByName(addr.Label) + require.NoError(t, err) + return nl + } + + t.Fatalf("failed to find a valid network interface") + return nil +} diff --git a/cli/doc.go b/cli/doc.go new file mode 100644 index 0000000..6f4bac6 --- /dev/null +++ b/cli/doc.go @@ -0,0 +1,2 @@ +// Package cli contains commands for the envbox tool. +package cli diff --git a/cli/docker.go b/cli/docker.go new file mode 100644 index 0000000..ce327b3 --- /dev/null +++ b/cli/docker.go @@ -0,0 +1,791 @@ +package cli + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/url" + "os" + "path" + "path/filepath" + "strconv" + "strings" + + dockertypes "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/container" + "github.com/spf13/cobra" + "golang.org/x/exp/slices" + "golang.org/x/xerrors" + + "cdr.dev/slog" + "cdr.dev/slog/sloggers/slogjson" + "github.com/coder/coder/codersdk/agentsdk" + "github.com/coder/envbox/background" + "github.com/coder/envbox/buildlog" + "github.com/coder/envbox/cli/cliflag" + "github.com/coder/envbox/dockerutil" + "github.com/coder/envbox/slogkubeterminate" + "github.com/coder/envbox/sysboxutil" + "github.com/coder/envbox/xunix" +) + +const ( + // EnvBoxPullImageSecretEnvVar defines the environment variable at which the + // pull image secret is mounted for envbox. + // Suppresses warning: G101: Potential hardcoded credentials + // EnvBoxContainerName is the name of the inner user container. + EnvBoxPullImageSecretEnvVar = "CODER_IMAGE_PULL_SECRET" //nolint:gosec + EnvBoxContainerName = "CODER_CVM_CONTAINER_NAME" +) + +const ( + defaultNetLink = "eth0" + defaultDockerBridge = "docker0" + // From https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts-technical-overview.html + awsWebIdentityTokenFilePath = "/var/run/secrets/eks.amazonaws.com/serviceaccount/token" //nolint + sysboxErrMsg = "Sysbox exited, possibly because of an unsupported kernel version. Please contact an infrastructure administrator and request a node kernel with seccomp API level >= 5." + + // noSpaceDataDir is the directory to use for the data directory + // for dockerd when the default directory (/var/lib/docker pointing + // to the user's pvc) is at capacity. This directory points to + // ephemeral storage allocated by the node and should be more likely + // to have capacity. + noSpaceDataDir = "/var/lib/docker.bak" + // noSpaceDockerDriver is the storage driver to use in cases where + // the default data dir (residing in the user's PVC) is at capacity. + // In such cases we must use the vfs storage driver because overlay2 + // does not work on top of overlay. + noSpaceDockerDriver = "vfs" + + OuterFUSEPath = "/tmp/coder-fuse" + InnerFUSEPath = "/dev/fuse" + + OuterTUNPath = "/tmp/coder-tun" + InnerTUNPath = "/dev/net/tun" + + InnerContainerName = "workspace_cvm" + + // Required for userns mapping. + // This is the ID of the user we apply in `envbox/Dockerfile`. + // + // There should be caution changing this value. + // Source directory permissions on the host are offset by this + // value. For example, folder `/home/coder` inside the container + // with UID/GID 1000 will be mapped to `UserNamespaceOffset` + 1000 + // on the host. Changing this value will result in improper mappings + // on existing containers. + UserNamespaceOffset = 100000 + devDir = "/dev" +) + +var ( + EnvInnerImage = "CODER_INNER_IMAGE" + EnvInnerUsername = "CODER_INNER_USERNAME" + EnvInnerEnvs = "CODER_INNER_ENVS" + EnvInnerWorkDir = "CODER_INNER_WORK_DIR" + EnvInnerHostname = "CODER_INNER_HOSTNAME" + EnvAddTun = "CODER_ADD_TUN" + EnvAddFuse = "CODER_ADD_FUSE" + EnvBridgeCIDR = "CODER_DOCKER_BRIDGE_CIDR" + //nolint + EnvAgentToken = "CODER_AGENT_TOKEN" + EnvAgentURL = "CODER_AGENT_URL" + EnvBootstrap = "CODER_BOOTSTRAP_SCRIPT" + EnvMounts = "CODER_MOUNTS" + EnvCPUs = "CODER_CPUS" + EnvMemory = "CODER_MEMORY" + EnvAddGPU = "CODER_ADD_GPU" + EnvUsrLibDir = "CODER_USR_LIB_DIR" +) + +var envboxPrivateMounts = map[string]struct{}{ + "/var/lib/containers": {}, + "/var/lib/docker": {}, + "/var/lib/sysbox": {}, + "/lib/modules": {}, + "/usr/src": {}, + // /var/lib/coder is not technically a mount + // private to envbox but it is specially handled + // by sysbox so it does not require any effort + // on our part. + "/var/lib/coder": {}, +} + +type flags struct { + innerImage string + innerUsername string + agentToken string + + // Optional flags. + innerEnvs string + innerWorkDir string + innerHostname string + imagePullSecret string + coderURL string + addTUN bool + addFUSE bool + addGPU bool + noStartupLogs bool + dockerdBridgeCIDR string + boostrapScript string + ethlink string + containerMounts string + hostUsrLibDir string + cpus int + memory int +} + +func dockerCmd() *cobra.Command { + var flags flags + + cmd := &cobra.Command{ + Use: "docker", + Short: "Create a docker-based CVM", + RunE: func(cmd *cobra.Command, args []string) (err error) { + var ( + ctx = cmd.Context() + log = slog.Make(slogjson.Sink(io.Discard)) + blog = buildlog.GetLogger(ctx) + ) + + if !flags.noStartupLogs { + log = slog.Make(slogjson.Sink(cmd.ErrOrStderr()), slogkubeterminate.Make()).Leveled(slog.LevelDebug) + blog = buildlog.JSONLogger{Encoder: json.NewEncoder(os.Stderr)} + } + + if !flags.noStartupLogs && flags.agentToken != "" && flags.coderURL != "" { + coderURL, err := url.Parse(flags.coderURL) + if err != nil { + return xerrors.Errorf("parse coder URL %q: %w", flags.coderURL, err) + } + + agent := agentsdk.New(coderURL) + agent.SetSessionToken(flags.agentToken) + + blog = buildlog.MultiLogger( + buildlog.OpenCoderLogger(ctx, agent, log), + blog, + ) + + ctx = buildlog.WithLogger(ctx, blog) + } + defer blog.Close() + + defer func(err *error) { + if *err != nil { + blog.Errorf("Failed to run envbox: %v", *err) + } + }(&err) + + blog.Info("Waiting for dockerd to startup...") + + go func() { + select { + // Start sysbox-mgr and sysbox-fs in order to run + // sysbox containers. + case err := <-background.New(ctx, log, "sysbox-mgr").Run(): + blog.Info(sysboxErrMsg) + //nolint + log.Fatal(ctx, "sysbox-mgr exited", slog.Error(err)) + case err := <-background.New(ctx, log, "sysbox-fs").Run(): + blog.Info(sysboxErrMsg) + //nolint + log.Fatal(ctx, "sysbox-fs exited", slog.Error(err)) + } + }() + + cidr := dockerutil.DefaultBridgeCIDR + if flags.dockerdBridgeCIDR != "" { + cidr = flags.dockerdBridgeCIDR + log.Debug(ctx, "using custom docker bridge CIDR", slog.F("cidr", cidr)) + } + + dargs, err := dockerdArgs(ctx, log, flags.ethlink, cidr, false) + if err != nil { + return xerrors.Errorf("dockerd args: %w", err) + } + + log.Debug(ctx, "starting dockerd", slog.F("args", args)) + + dockerd := background.New(ctx, log, "dockerd", dargs...) + err = dockerd.Start() + if err != nil { + return xerrors.Errorf("start dockerd: %w", err) + } + + log.Debug(ctx, "waiting for manager") + + err = sysboxutil.WaitForManager(ctx) + if err != nil { + return xerrors.Errorf("wait for sysbox-mgr: %w", err) + } + + client, err := dockerutil.Client(ctx) + if err != nil { + return xerrors.Errorf("new docker client: %w", err) + } + + go func() { + err := <-dockerd.Wait() + // It's possible the for the docker daemon to run out of disk + // while trying to startup, in such cases we should restart + // it and point it to an ephemeral directory. Since this + // directory is going to be on top of an overlayfs filesystem + // we have to use the vfs storage driver. + if xunix.IsNoSpaceErr(err) { + args, err = dockerdArgs(ctx, log, flags.ethlink, cidr, true) + if err != nil { + blog.Info("Failed to create Container-based Virtual Machine: " + err.Error()) + //nolint + log.Fatal(ctx, "dockerd exited, failed getting args for restart", slog.Error(err)) + } + + err = dockerd.Restart(ctx, "dockerd", args...) + if err != nil { + blog.Info("Failed to create Container-based Virtual Machine: " + err.Error()) + //nolint + log.Fatal(ctx, "restart dockerd", slog.Error(err)) + } + + err = <-dockerd.Wait() + } + + // It's possible lower down in the call stack to restart + // the docker daemon if we run out of disk while starting the + // container. + if err != nil && !xerrors.Is(err, background.ErrUserKilled) { + blog.Info("Failed to create Container-based Virtual Machine: " + err.Error()) + //nolint + log.Fatal(ctx, "dockerd exited", slog.Error(err)) + } + }() + + log.Debug(ctx, "waiting for dockerd") + + // We wait for the daemon after spawning the goroutine in case + // startup causes the daemon to encounter encounter a 'no space left + // on device' error. + err = dockerutil.WaitForDaemon(ctx, client) + if err != nil { + return xerrors.Errorf("wait for dockerd: %w", err) + } + + err = runDockerCVM(ctx, log, client, flags) + if err != nil { + // It's possible we failed because we ran out of disk while + // pulling the image. We should restart the daemon and use + // the vfs storage driver to try to get the container up so that + // a user can access their workspace and try to delete whatever + // is causing their disk to fill up. + if xunix.IsNoSpaceErr(err) { + blog.Info("Insufficient space to start inner container. Restarting dockerd using the vfs driver. Your performance will be degraded. Clean up your home volume and then restart the workspace to improve performance.") + log.Debug(ctx, "encountered 'no space left on device' error while starting workspace", slog.Error(err)) + args, err := dockerdArgs(ctx, log, flags.ethlink, cidr, true) + if err != nil { + return xerrors.Errorf("dockerd args for restart: %w", err) + } + + log.Debug(ctx, "restarting dockerd", slog.F("args", args)) + + err = dockerd.Restart(ctx, "dockerd", args...) + if err != nil { + return xerrors.Errorf("restart dockerd: %w", err) + } + go func() { + err = <-dockerd.Wait() + blog.Errorf("restarted dockerd exited: %v", err) + //nolint + log.Fatal(ctx, "restarted dockerd exited", slog.Error(err)) + }() + + log.Debug(ctx, "reattempting container creation") + err = runDockerCVM(ctx, log, client, flags) + } + if err != nil { + blog.Errorf("Failed to run envbox: %v", err) + return xerrors.Errorf("run: %w", err) + } + } + + return nil + }, + } + + // Required flags. + cliflag.StringVarP(cmd.Flags(), &flags.innerImage, "image", "", EnvInnerImage, "", "The image for the inner container. Required.") + cliflag.StringVarP(cmd.Flags(), &flags.innerUsername, "username", "", EnvInnerUsername, "", "The username to use for the inner container. Required.") + cliflag.StringVarP(cmd.Flags(), &flags.agentToken, "agent-token", "", EnvAgentToken, "", "The token to be used by the workspace agent to estabish a connection with the control plane. Required.") + cliflag.StringVarP(cmd.Flags(), &flags.coderURL, "coder-url", "", EnvAgentURL, "", "The URL of the Coder deployement.") + + // Optional flags. + cliflag.StringVarP(cmd.Flags(), &flags.innerEnvs, "envs", "", EnvInnerEnvs, "", "Comma separated list of envs to add to the inner container.") + cliflag.StringVarP(cmd.Flags(), &flags.innerWorkDir, "work-dir", "", EnvInnerWorkDir, "", "The working directory of the inner container.") + cliflag.StringVarP(cmd.Flags(), &flags.innerHostname, "hostname", "", EnvInnerHostname, "", "The hostname to use for the inner container.") + cliflag.StringVarP(cmd.Flags(), &flags.imagePullSecret, "image-secret", "", EnvBoxPullImageSecretEnvVar, "", fmt.Sprintf("The secret to use to pull the image. It is highly encouraged to provide this via the %s environment variable.", EnvBoxPullImageSecretEnvVar)) + cliflag.StringVarP(cmd.Flags(), &flags.dockerdBridgeCIDR, "bridge-cidr", "", EnvBridgeCIDR, "", "The CIDR to use for the docker bridge.") + cliflag.StringVarP(cmd.Flags(), &flags.boostrapScript, "boostrap-script", "", EnvBootstrap, "", "The script to use to bootstrap the container. This should typically install and start the agent.") + cliflag.StringVarP(cmd.Flags(), &flags.containerMounts, "mounts", "", EnvMounts, "", "Comma separated list of mounts in the form of ':[:options]' (e.g. /var/lib/docker:/var/lib/docker:ro,/usr/src:/usr/src).") + cliflag.StringVarP(cmd.Flags(), &flags.hostUsrLibDir, "usr-lib-dir", "", EnvUsrLibDir, "", "The host /usr/lib mountpoint. Used to detect GPU drivers to mount into inner container.") + cliflag.BoolVarP(cmd.Flags(), &flags.addTUN, "add-tun", "", EnvAddTun, false, "Add a TUN device to the inner container.") + cliflag.BoolVarP(cmd.Flags(), &flags.addFUSE, "add-fuse", "", EnvAddFuse, false, "Add a FUSE device to the inner container.") + cliflag.BoolVarP(cmd.Flags(), &flags.addGPU, "add-gpu", "", EnvAddGPU, false, "Add detected GPUs to the inner container.") + cliflag.IntVarP(cmd.Flags(), &flags.cpus, "cpus", "", EnvCPUs, 0, "Number of CPUs to allocate inner container. e.g. 2") + cliflag.IntVarP(cmd.Flags(), &flags.memory, "memory", "", EnvMemory, 0, "Max memory to allocate to the inner container in bytes.") + + // Test flags. + cliflag.BoolVarP(cmd.Flags(), &flags.noStartupLogs, "no-startup-log", "", "", false, "Do not log startup logs. Useful for testing.") + cliflag.StringVarP(cmd.Flags(), &flags.ethlink, "ethlink", "", "", defaultNetLink, "The ethernet link to query for the MTU that is passed to docerd. Used for tests.") + + return cmd +} + +func runDockerCVM(ctx context.Context, log slog.Logger, client dockerutil.DockerClient, flags flags) error { + var ( + fs = xunix.GetFS(ctx) + blog = buildlog.GetLogger(ctx) + ) + + // Set our OOM score to something really unfavorable to avoid getting killed + // in memory-scarce scenarios. + err := xunix.SetOOMScore(ctx, "self", "-1000") + if err != nil { + return xerrors.Errorf("set oom score: %w", err) + } + + var dockerAuth dockerutil.AuthConfig + if flags.imagePullSecret != "" { + dockerAuth, err = dockerutil.ParseAuthConfig(flags.imagePullSecret) + if err != nil { + return xerrors.Errorf("parse auth config: %w", err) + } + } + + envs := defaultContainerEnvs(flags.agentToken) + + innerEnvsTokens := strings.Split(flags.innerEnvs, ",") + envs = append(envs, filterElements(xunix.Environ(ctx), innerEnvsTokens...)...) + + mounts := defaultMounts() + // Add any user-specified mounts to our mounts list. + extraMounts, err := parseMounts(flags.containerMounts) + if err != nil { + return xerrors.Errorf("read mounts: %w", err) + } + mounts = append(mounts, extraMounts...) + + log.Debug(ctx, "using mounts", slog.F("mounts", mounts)) + + devices := make([]container.DeviceMapping, 0, 2) + if flags.addTUN { + log.Debug(ctx, "creating TUN device", slog.F("path", OuterTUNPath)) + blog.Info("Creating TUN device") + dev, err := xunix.CreateTUNDevice(ctx, OuterTUNPath) + if err != nil { + return xerrors.Errorf("creat tun device: %w", err) + } + + devices = append(devices, container.DeviceMapping{ + PathOnHost: dev.Path, + PathInContainer: InnerTUNPath, + CgroupPermissions: "rwm", + }) + } + + if flags.addFUSE { + log.Debug(ctx, "creating FUSE device", slog.F("path", OuterFUSEPath)) + blog.Info("Creating FUSE device") + dev, err := xunix.CreateFuseDevice(ctx, OuterFUSEPath) + if err != nil { + return xerrors.Errorf("create fuse device: %w", err) + } + + devices = append(devices, container.DeviceMapping{ + PathOnHost: dev.Path, + PathInContainer: InnerFUSEPath, + CgroupPermissions: "rwm", + }) + } + + log.Debug(ctx, "using devices", slog.F("devices", devices)) + + // ID shift the devices so that they reflect the root user + // inside the container. + for _, device := range devices { + log.Debug(ctx, "chowning device", + slog.F("device", device.PathOnHost), + slog.F("uid", UserNamespaceOffset), + slog.F("gid", UserNamespaceOffset), + ) + err = fs.Chown(device.PathOnHost, UserNamespaceOffset, UserNamespaceOffset) + if err != nil { + return xerrors.Errorf("chown device %q: %w", device.PathOnHost, err) + } + } + + log.Debug(ctx, "pulling image", slog.F("image", flags.innerImage)) + + err = dockerutil.PullImage(ctx, &dockerutil.PullImageConfig{ + Client: client, + Image: flags.innerImage, + Auth: dockerAuth, + ProgressFn: dockerutil.DefaultLogImagePullFn(blog), + }) + if err != nil { + return xerrors.Errorf("pull image: %w", err) + } + + log.Debug(ctx, "remounting /sys") + + // After image pull we remount /sys so sysbox can have appropriate perms to create a container. + err = xunix.MountFS(ctx, "/sys", "/sys", "", "remount", "rw") + if err != nil { + return xerrors.Errorf("remount /sys: %w", err) + } + + if flags.addGPU { + if flags.hostUsrLibDir == "" { + return xerrors.Errorf("when using GPUs, %q must be specified", EnvUsrLibDir) + } + // Unmount GPU drivers in /proc as it causes issues when creating any + // container in some cases (even the image metadata container). + _, err = xunix.TryUnmountProcGPUDrivers(ctx, log) + if err != nil { + return xerrors.Errorf("unmount /proc GPU drivers: %w", err) + } + + devs, binds, err := xunix.GPUs(ctx, log, flags.hostUsrLibDir) + if err != nil { + return xerrors.Errorf("find gpus: %w", err) + } + + for _, dev := range devs { + devices = append(devices, container.DeviceMapping{ + PathOnHost: dev.Path, + PathInContainer: dev.Path, + CgroupPermissions: "rwm", + }) + } + + for _, bind := range binds { + // If the bind has a path that points to the host-mounted /usr/lib + // directory we need to remap it to /usr/lib inside the container. + mountpoint := bind.Path + if strings.HasPrefix(mountpoint, flags.hostUsrLibDir) { + mountpoint = filepath.Join( + "/usr/lib", + strings.TrimPrefix(mountpoint, strings.TrimSuffix(flags.hostUsrLibDir, "/")), + ) + } + mounts = append(mounts, xunix.Mount{ + Source: bind.Path, + Mountpoint: mountpoint, + ReadOnly: slices.Contains(bind.Opts, "ro"), + }) + } + envs = append(envs, xunix.GPUEnvs(ctx)...) + } + + log.Debug(ctx, "fetching image metadata", + slog.F("image", flags.innerImage), + slog.F("username", flags.innerUsername), + ) + + blog.Info("Getting image metadata...") + // Get metadata about the image. We need to know things like the UID/GID + // of the user so that we can chown directories to the namespaced UID inside + // the inner container as well as whether we should be starting the container + // with /sbin/init or something simple like 'sleep infinity'. + imgMeta, err := dockerutil.GetImageMetadata(ctx, client, flags.innerImage, flags.innerUsername) + if err != nil { + return xerrors.Errorf("get image metadata: %w", err) + } + + blog.Infof("Detected entrypoint user '%s:%s' with home directory %q", imgMeta.UID, imgMeta.UID, imgMeta.HomeDir) + + log.Debug(ctx, "fetched image metadata", + slog.F("uid", imgMeta.UID), + slog.F("gid", imgMeta.GID), + slog.F("has_init", imgMeta.HasInit), + ) + + uid, err := strconv.ParseInt(imgMeta.UID, 10, 32) + if err != nil { + return xerrors.Errorf("parse image uid: %w", err) + } + gid, err := strconv.ParseInt(imgMeta.GID, 10, 32) + if err != nil { + return xerrors.Errorf("parse image gid: %w", err) + } + + for _, m := range mounts { + // Don't modify anything private to envbox. + if isPrivateMount(m) { + continue + } + + log.Debug(ctx, "chmod'ing directory", + slog.F("path", m.Source), + slog.F("mode", "02755"), + ) + + // If a mount is read-only we have to remount it rw so that we + // can id shift it correctly. We'll still mount it read-only into + // the inner container. + if m.ReadOnly { + mounter := xunix.Mounter(ctx) + err := mounter.Mount("", m.Source, "", []string{"remount,rw"}) + if err != nil { + return xerrors.Errorf("remount: %w", err) + } + } + + err := fs.Chmod(m.Source, 0o2755) + if err != nil { + return xerrors.Errorf("chmod mountpoint %q: %w", m.Source, err) + } + + var ( + shiftedUID = shiftedID(0) + shiftedGID = shiftedID(0) + ) + + if isHomeDir(m.Source) { + // We want to ensure that the inner directory is ID shifted to + // the namespaced UID of the user in the inner container otherwise + // they won't be able to write files. + shiftedUID = shiftedID(int(uid)) + shiftedGID = shiftedID(int(gid)) + } + + log.Debug(ctx, "chowning mount", + slog.F("source", m.Source), + slog.F("target", m.Mountpoint), + slog.F("uid", shiftedUID), + slog.F("gid", shiftedGID), + ) + + // Any non-home directory we assume should be owned by id-shifted root + // user. + err = fs.Chown(m.Source, shiftedUID, shiftedGID) + if err != nil { + return xerrors.Errorf("chown mountpoint %q: %w", m.Source, err) + } + } + + blog.Info("Creating workspace...") + + // Create the inner container. + containerID, err := dockerutil.CreateContainer(ctx, client, &dockerutil.ContainerConfig{ + Log: log, + Mounts: mounts, + Devices: devices, + Envs: envs, + Name: InnerContainerName, + WorkingDir: flags.innerWorkDir, + HasInit: imgMeta.HasInit, + Image: flags.innerImage, + CPUs: int64(flags.cpus), + MemoryLimit: int64(flags.memory), + }) + if err != nil { + return xerrors.Errorf("create container: %w", err) + } + + blog.Info("Pruning images to free up disk...") + // Prune images to avoid taking up any unnecessary disk from the user. + _, err = dockerutil.PruneImages(ctx, client) + if err != nil { + return xerrors.Errorf("prune images: %w", err) + } + + // TODO fix iptables when istio detected. + + blog.Info("Starting up workspace...") + err = client.ContainerStart(ctx, containerID, dockertypes.ContainerStartOptions{}) + if err != nil { + if err != nil { + return xerrors.Errorf("start container: %w", err) + } + } + + log.Debug(ctx, "creating bootstrap directory", slog.F("directory", imgMeta.HomeDir)) + + // Create the directory to which we will download the agent. + bootDir := filepath.Join(imgMeta.HomeDir, ".coder") + + blog.Infof("Creating %q directory to host Coder assets...", bootDir) + _, err = dockerutil.ExecContainer(ctx, client, dockerutil.ExecConfig{ + ContainerID: containerID, + User: imgMeta.UID, + Cmd: "mkdir", + Args: []string{"-p", bootDir}, + }) + if err != nil { + return xerrors.Errorf("make bootstrap dir: %w", err) + } + + cpuQuota, err := xunix.ReadCPUQuota(ctx) + if err != nil { + return xerrors.Errorf("read CPU quota: %w", err) + } + + log.Debug(ctx, "setting CPU quota", + slog.F("quota", cpuQuota.Quota), + slog.F("period", cpuQuota.Period), + ) + + // We want the inner container to have the same limits as the outer container + // so that processes inside the container know what they're working with. + err = dockerutil.SetContainerCPUQuota(ctx, containerID, cpuQuota.Quota, cpuQuota.Period) + if err != nil { + return xerrors.Errorf("set inner container CPU quota: %w", err) + } + + blog.Info("Envbox startup complete!") + + // Bootstrap the container if a script has been provided. + blog.Infof("Bootstrapping workspace...") + err = dockerutil.BootstrapContainer(ctx, client, dockerutil.BootstrapConfig{ + ContainerID: containerID, + User: imgMeta.UID, + Script: flags.boostrapScript, + // We set this because the default behavior is to download the agent + // to /tmp/coder.XXXX. This causes a race to happen where we finish + // downloading the binary but before we can execute systemd remounts + // /tmp. + Env: []string{fmt.Sprintf("BINARY_DIR=%s", bootDir)}, + }) + if err != nil { + return xerrors.Errorf("boostrap container: %w", err) + } + + return nil +} + +// nolint +func dockerdArgs(ctx context.Context, log slog.Logger, link, cidr string, isNoSpace bool) ([]string, error) { + // We need to adjust the MTU for the host otherwise packets will fail delivery. + // 1500 is the standard, but certain deployments (like GKE) use custom MTU values. + // See: https://www.atlantis-press.com/journals/ijndc/125936177/view#sec-s3.1 + + mtu, err := xunix.NetlinkMTU(link) + if err != nil { + return nil, xerrors.Errorf("custom mtu: %w", err) + } + + // We set the Docker Bridge IP explicitly here for a number of reasons: + // 1) It sometimes picks the 172.17.x.x address which conflicts with that of the Docker daemon in the inner container. + // 2) It defaults to a /16 network which is way more than we need for envbox. + // 3) The default may conflict with existing internal network resources, and an operator may wish to override it. + dockerBip, prefixLen := dockerutil.BridgeIPFromCIDR(cidr) + + args := []string{ + "--debug", + "--log-level=debug", + fmt.Sprintf("--mtu=%d", mtu), + "--userns-remap=coder", + "--storage-driver=overlay2", + fmt.Sprintf("--bip=%s/%d", dockerBip, prefixLen), + } + + if isNoSpace { + args = append(args, + fmt.Sprintf("--data-root=%s", noSpaceDataDir), + fmt.Sprintf("--storage-driver=%s", noSpaceDockerDriver), + ) + } + + return args, nil +} + +// TODO This is bad code. +func filterElements(ss []string, filters ...string) []string { + filtered := make([]string, 0, len(ss)) + for _, f := range filters { + for _, s := range ss { + filter := f + if strings.HasSuffix(filter, "*") { + filter = strings.TrimSuffix(filter, "*") + if strings.HasPrefix(s, filter) { + filtered = append(filtered, s) + } + } else if s == filter { + filtered = append(filtered, s) + } + } + } + + return filtered +} + +// parseMounts parses a list of mounts from containerMounts. The format should +// be "src:dst[:ro],src:dst[:ro]". +func parseMounts(containerMounts string) ([]xunix.Mount, error) { + if containerMounts == "" { + return nil, nil + } + + mountsStr := strings.Split(containerMounts, ",") + + mounts := make([]xunix.Mount, 0, len(mountsStr)) + for _, mount := range mountsStr { + tokens := strings.Split(mount, ":") + if len(tokens) < 2 { + return nil, xerrors.Errorf("malformed mounts value %q", containerMounts) + } + m := xunix.Mount{ + Source: tokens[0], + Mountpoint: tokens[1], + } + if len(tokens) == 3 { + m.ReadOnly = tokens[2] == "ro" + } + mounts = append(mounts, m) + } + + return mounts, nil +} + +// defaultContainerEnvs returns environment variables that should always +// be passed to the inner container. +func defaultContainerEnvs(agentToken string) []string { + return []string{fmt.Sprintf("%s=%s", EnvAgentToken, agentToken)} +} + +// defaultMounts are bind mounts that are always provided to the inner +// container. +func defaultMounts() []xunix.Mount { + return []xunix.Mount{ + { + Source: "/var/lib/coder/docker", + Mountpoint: "/var/lib/docker", + }, + { + Source: "/var/lib/coder/containers", + Mountpoint: "/var/lib/containers", + }, + } +} + +// isPrivateMount returns true if the provided mount points to a mount +// private to the envbox container itself. +func isPrivateMount(m xunix.Mount) bool { + _, ok := envboxPrivateMounts[m.Mountpoint] + return ok +} + +func isHomeDir(fpath string) bool { + if fpath == "/root" { + return true + } + + dir, _ := path.Split(fpath) + return dir == "/home/" +} + +// shiftedID returns the ID but shifted to the user namespace offset we +// use for the inner container. +func shiftedID(id int) int { + return id + UserNamespaceOffset +} diff --git a/cli/docker_test.go b/cli/docker_test.go new file mode 100644 index 0000000..760f9a4 --- /dev/null +++ b/cli/docker_test.go @@ -0,0 +1,580 @@ +package cli_test + +import ( + "bufio" + "bytes" + "context" + "encoding/base64" + "fmt" + "io" + "net" + "os" + "path/filepath" + "strings" + "testing" + + dockertypes "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/network" + v1 "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/spf13/afero" + "github.com/stretchr/testify/require" + "k8s.io/mount-utils" + testingexec "k8s.io/utils/exec/testing" + + "github.com/coder/envbox/cli" + "github.com/coder/envbox/cli/clitest" + "github.com/coder/envbox/dockerutil" + "github.com/coder/envbox/xunix" + "github.com/coder/envbox/xunix/xunixfake" +) + +func TestDocker(t *testing.T) { + t.Parallel() + + // Test the basic use case. This test doesn't test much beyond + // establishing that the framework is returning a default + // successful test. This makes it easier for individual tests + // to test various criteria of the command without extensive + // setup. + t.Run("OK", func(t *testing.T) { + t.Parallel() + + ctx, cmd := clitest.New(t, "docker", + "--image=ubuntu", + "--username=root", + "--agent-token=hi", + ) + + err := cmd.ExecuteContext(ctx) + require.NoError(t, err) + }) + + // Test that dockerd is configured correctly. + t.Run("DockerdConfigured", func(t *testing.T) { + t.Parallel() + + var ( + nl = clitest.GetNetLink(t) + bridgeCIDR = "172.31.0.129/30" + ) + + ctx, cmd := clitest.New(t, "docker", + "--image=ubuntu", + "--username=root", + "--agent-token=hi", + fmt.Sprintf("--bridge-cidr=%s", bridgeCIDR), + ) + + execer := clitest.Execer(ctx) + execer.AddCommands(&xunixfake.FakeCmd{ + FakeCmd: &testingexec.FakeCmd{ + Argv: []string{ + "dockerd", + "--debug", + "--log-level=debug", + fmt.Sprintf("--mtu=%d", nl.Attrs().MTU), + "--userns-remap=coder", + "--storage-driver=overlay2", + fmt.Sprintf("--bip=%s", bridgeCIDR), + }, + }, + }) + + err := cmd.ExecuteContext(ctx) + require.NoError(t, err) + execer.AssertCommandsCalled(t) + }) + + // Test that the oom_score_adj of the envbox + // process is set to an extremely undesirable + // number for the OOM killer. + t.Run("SetOOMScore", func(t *testing.T) { + t.Parallel() + + ctx, cmd := clitest.New(t, "docker", + "--image=ubuntu", + "--username=root", + "--agent-token=hi", + ) + + fs := clitest.FS(ctx) + + err := cmd.ExecuteContext(ctx) + require.NoError(t, err) + + score, err := afero.ReadFile(fs, "/proc/self/oom_score_adj") + require.NoError(t, err) + require.Equal(t, []byte("-1000"), score) + }) + + // Test that user-provided env vars are passed through. + // It is valid to specify a wildcard so that all matching + // env vars are passed through. + t.Run("PassesThroughEnvVars", func(t *testing.T) { + t.Parallel() + var ( + cntEnvs = []string{ + "FOO=bar", + "CODER_VAR=baz", + "bar=123", + // Test that wildcard works. + "KUBERNETES_*", + "US_*", + } + + osEnvs = append([]string{ + "USER=root", + "USA=yay", + "HOME=/root", + "PATH=/usr/bin:/sbin:/bin", + "KUBERNETES_SERVICE_HOST=10.0.0.1", + "KUBERNETES_PORT=tcp://10.0.0.1:443", + "KUBERNETES_PORT_443_TCP_PORT=443", + // Don't include the wildcards. + }, cntEnvs[:3]...) + + expectedEnvs = []string{ + "CODER_AGENT_TOKEN=hi", + "FOO=bar", + "CODER_VAR=baz", + "bar=123", + "KUBERNETES_SERVICE_HOST=10.0.0.1", + "KUBERNETES_PORT=tcp://10.0.0.1:443", + "KUBERNETES_PORT_443_TCP_PORT=443", + } + ) + + ctx, cmd := clitest.New(t, "docker", + "docker", + "--image=ubuntu", + "--username=root", + "--agent-token=hi", + fmt.Sprintf("--envs=%s", strings.Join(cntEnvs, ",")), + ) + + ctx = xunix.WithEnvironFn(ctx, func() []string { return osEnvs }) + + client := clitest.DockerClient(t, ctx) + var called bool + client.ContainerCreateFn = func(_ context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, _ *v1.Platform, containerName string) (container.ContainerCreateCreatedBody, error) { + if containerName == cli.InnerContainerName { + called = true + require.Equal(t, expectedEnvs, config.Env) + } + return container.ContainerCreateCreatedBody{}, nil + } + + err := cmd.ExecuteContext(ctx) + require.NoError(t, err) + require.True(t, called, "create function was not called") + }) + + // Test that we parse mounts correctly. + t.Run("Mounts", func(t *testing.T) { + t.Parallel() + + var ( + userMounts = []string{"/home/coder:/home/coder", "/etc/hosts:/etc/hosts:ro", "/etc/hostname:/idc/where:ro", "/usr/src:/a/b/c"} + expectedMounts = append([]string{"/var/lib/coder/docker:/var/lib/docker", "/var/lib/coder/containers:/var/lib/containers"}, userMounts...) + ) + ctx, cmd := clitest.New(t, "docker", + "docker", + "--image=ubuntu", + "--username=root", + "--agent-token=hi", + fmt.Sprintf("--mounts=%s", strings.Join(userMounts, ",")), + ) + + var ( + client = clitest.DockerClient(t, ctx) + fs = clitest.FS(ctx) + ) + + for _, mount := range userMounts { + src := strings.Split(mount, ":")[0] + + err := afero.WriteFile(fs, src, []byte("hi"), 0o777) + require.NoError(t, err) + } + + // Set the exec response from inspecting the image to some ID + // greater than 0. + client.ContainerExecAttachFn = func(_ context.Context, execID string, config dockertypes.ExecStartCheck) (dockertypes.HijackedResponse, error) { + return dockertypes.HijackedResponse{ + Reader: bufio.NewReader(strings.NewReader("root:x:1001:1001:root:/root:/bin/bash")), + Conn: &net.IPConn{}, + }, nil + } + + var called bool + client.ContainerCreateFn = func(_ context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, _ *v1.Platform, containerName string) (container.ContainerCreateCreatedBody, error) { + if containerName == cli.InnerContainerName { + called = true + require.Equal(t, expectedMounts, hostConfig.Binds) + } + + return container.ContainerCreateCreatedBody{}, nil + } + + err := cmd.ExecuteContext(ctx) + require.NoError(t, err) + require.True(t, called, "container create fn not called") + + fi, err := fs.Stat("/home/coder") + require.NoError(t, err) + require.Equal(t, os.FileMode(0o755), fi.Mode().Perm()) + // Check that we're calling chown and shifting the ID. + owner, ok := fs.GetFileOwner("/home/coder") + require.True(t, ok) + require.Equal(t, cli.UserNamespaceOffset+1001, owner.UID) + require.Equal(t, cli.UserNamespaceOffset+1001, owner.GID) + }) + + // Test that we remount /sys once we pull the image so that + // sysbox can use it properly. + t.Run("RemountSysfs", func(t *testing.T) { + t.Parallel() + + ctx, cmd := clitest.New(t, "docker", + "--image=ubuntu", + "--username=root", + "--agent-token=hi", + ) + + mounter := clitest.Mounter(ctx) + + err := cmd.ExecuteContext(ctx) + require.NoError(t, err) + + actions := mounter.GetLog() + require.Len(t, actions, 1) + action := actions[0] + require.Equal(t, "mount", action.Action) + require.Equal(t, "", action.FSType) + require.Equal(t, "/sys", action.Source) + require.Equal(t, "/sys", action.Target) + }) + + // Test that devices are created and passed through to the docker + // daemon. + t.Run("Devices", func(t *testing.T) { + t.Parallel() + + ctx, cmd := clitest.New(t, "docker", + "docker", + "--image=ubuntu", + "--username=root", + "--agent-token=hi", + "--add-tun", + "--add-fuse", + ) + + var ( + client = clitest.DockerClient(t, ctx) + fs = clitest.FS(ctx) + expectedDevices = []container.DeviceMapping{ + { + PathOnHost: cli.OuterTUNPath, + PathInContainer: cli.InnerTUNPath, + CgroupPermissions: "rwm", + }, + { + PathOnHost: cli.OuterFUSEPath, + PathInContainer: cli.InnerFUSEPath, + CgroupPermissions: "rwm", + }, + } + ) + + var called bool + client.ContainerCreateFn = func(_ context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, _ *v1.Platform, containerName string) (container.ContainerCreateCreatedBody, error) { + if containerName == cli.InnerContainerName { + called = true + require.Equal(t, expectedDevices, hostConfig.Devices) + } + + return container.ContainerCreateCreatedBody{}, nil + } + + err := cmd.ExecuteContext(ctx) + require.NoError(t, err) + require.True(t, called, "container create fn not called") + + // Check that we're calling chown and shifting the ID to + // it maps to root of the inner container. + owner, ok := fs.GetFileOwner(cli.OuterFUSEPath) + require.True(t, ok) + require.Equal(t, cli.UserNamespaceOffset, owner.UID) + require.Equal(t, cli.UserNamespaceOffset, owner.GID) + + owner, ok = fs.GetFileOwner(cli.OuterTUNPath) + require.True(t, ok) + require.Equal(t, cli.UserNamespaceOffset, owner.UID) + require.Equal(t, cli.UserNamespaceOffset, owner.GID) + }) + + // Tests that 'sleep infinity' is used if /sbin/init + // isn't detected. + t.Run("NoInit", func(t *testing.T) { + t.Parallel() + + ctx, cmd := clitest.New(t, "docker", + "docker", + "--image=ubuntu", + "--username=root", + "--agent-token=hi", + "--add-tun", + "--add-fuse", + ) + + var ( + client = clitest.DockerClient(t, ctx) + statExecID = "hi" + ) + + client.ContainerExecCreateFn = func(_ context.Context, container string, config dockertypes.ExecConfig) (dockertypes.IDResponse, error) { + if config.Cmd[0] == "stat" { + return dockertypes.IDResponse{ + ID: statExecID, + }, nil + } + return dockertypes.IDResponse{}, nil + } + + // Set the exec response from inspecting the image to some ID + // greater than 0. + client.ContainerExecInspectFn = func(_ context.Context, execID string) (dockertypes.ContainerExecInspect, error) { + if execID == statExecID { + return dockertypes.ContainerExecInspect{ExitCode: 1}, nil + } + + return dockertypes.ContainerExecInspect{}, nil + } + + var called bool + client.ContainerCreateFn = func(_ context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, _ *v1.Platform, containerName string) (container.ContainerCreateCreatedBody, error) { + if containerName == cli.InnerContainerName { + called = true + require.Equal(t, []string{"sleep", "infinity"}, []string(config.Entrypoint)) + } + + return container.ContainerCreateCreatedBody{}, nil + } + + err := cmd.ExecuteContext(ctx) + require.NoError(t, err) + require.True(t, called, "container create fn not called") + }) + + t.Run("DockerAuth", func(t *testing.T) { + t.Parallel() + + ctx, cmd := clitest.New(t, "docker", + "--image=ubuntu", + "--username=root", + "--agent-token=hi", + fmt.Sprintf("--image-secret=%s", rawDockerAuth), + ) + + raw := []byte(`{"username":"_json_key","password":"{\"type\": \"service_account\", \"project_id\": \"some-test\", \"private_key_id\": \"blahblah\", \"private_key\": \"-----BEGIN PRIVATE KEY-----mykey-----END PRIVATE KEY-----\", \"client_email\": \"test@test.iam.gserviceaccount.com\", \"client_id\": \"123\", \"auth_uri\": \"https://accounts.google.com/o/oauth2/auth\", \"token_uri\": \"https://oauth2.googleapis.com/token\", \"auth_provider_x509_cert_url\": \"https://www.googleapis.com/oauth2/v1/certs\", \"client_x509_cert_url\": \"https://www.googleapis.com/robot/v1/metadata/x509/test.iam.gserviceaccount.com\" }","auth":"X2pzb25fa2V5OnsKCgkidHlwZSI6ICJzZXJ2aWNlX2FjY291bnQiLAoJInByb2plY3RfaWQiOiAic29tZS10ZXN0IiwKCSJwcml2YXRlX2tleV9pZCI6ICJibGFoYmxhaCIsCgkicHJpdmF0ZV9rZXkiOiAiLS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCm15a2V5LS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLQoiLAoJImNsaWVudF9lbWFpbCI6ICJ0ZXN0QHRlc3QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLAoJImNsaWVudF9pZCI6ICIxMjMiLAoJImF1dGhfdXJpIjogImh0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbS9vL29hdXRoMi9hdXRoIiwKCSJ0b2tlbl91cmkiOiAiaHR0cHM6Ly9vYXV0aDIuZ29vZ2xlYXBpcy5jb20vdG9rZW4iLAoJImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6ICJodHRwczovL3d3dy5nb29nbGVhcGlzLmNvbS9vYXV0aDIvdjEvY2VydHMiLAoJImNsaWVudF94NTA5X2NlcnRfdXJsIjogImh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL3JvYm90L3YxL21ldGFkYXRhL3g1MDkvdGVzdC5pYW0uZ3NlcnZpY2VhY2NvdW50LmNvbSIKfQo=","email":"test@test.iam.gserviceaccount.com"}`) + authB64 := base64.StdEncoding.EncodeToString(raw) + + client := clitest.DockerClient(t, ctx) + client.ImagePullFn = func(_ context.Context, ref string, options dockertypes.ImagePullOptions) (io.ReadCloser, error) { + // Assert that we call the image pull function with the credentials. + require.Equal(t, authB64, options.RegistryAuth) + return io.NopCloser(bytes.NewReader(nil)), nil + } + + err := cmd.ExecuteContext(ctx) + require.NoError(t, err) + }) + + t.Run("SetsResources", func(t *testing.T) { + t.Parallel() + + const ( + // 4GB. + memory = 4 << 30 + cpus = 6 + ) + + ctx, cmd := clitest.New(t, "docker", + "--image=ubuntu", + "--username=root", + "--agent-token=hi", + fmt.Sprintf("--cpus=%d", cpus), + fmt.Sprintf("--memory=%d", memory), + ) + + var called bool + client := clitest.DockerClient(t, ctx) + client.ContainerCreateFn = func(_ context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, _ *v1.Platform, containerName string) (container.ContainerCreateCreatedBody, error) { + if containerName == cli.InnerContainerName { + called = true + require.Equal(t, int64(memory), hostConfig.Memory) + require.Equal(t, int64(cpus*dockerutil.DefaultCPUPeriod), hostConfig.CPUQuota) + require.Equal(t, int64(dockerutil.DefaultCPUPeriod), hostConfig.CPUPeriod) + } + + return container.ContainerCreateCreatedBody{}, nil + } + + err := cmd.ExecuteContext(ctx) + require.NoError(t, err) + require.True(t, called, "create function was not called for inner container") + }) + + t.Run("GPUNoUsrLibDir", func(t *testing.T) { + t.Parallel() + + ctx, cmd := clitest.New(t, "docker", + "--image=ubuntu", + "--username=root", + "--agent-token=hi", + "--add-gpu=true", + ) + + err := cmd.ExecuteContext(ctx) + require.Error(t, err) + require.ErrorContains(t, err, fmt.Sprintf("when using GPUs, %q must be specified", cli.EnvUsrLibDir)) + }) + + t.Run("GPU", func(t *testing.T) { + t.Parallel() + + ctx, cmd := clitest.New(t, "docker", + "--image=ubuntu", + "--username=root", + "--agent-token=hi", + "--add-gpu=true", + "--usr-lib-dir=/var/coder/usr/lib", + ) + + var ( + mounter = clitest.Mounter(ctx) + afs = clitest.FS(ctx) + + procGPUDrivers = []string{ + "/proc/vulkan/foo", + "/proc/nvidia/bar", + "/proc/cuda/baz", + } + + // This path intentionally has a trailing '/' to ensure we are + // trimming correctly when remapping host-mounted /usr/lib dirs to + // /usr/lib inside the container. + usrLibMountpoint = "/var/coder/usr/lib/" + // expectedUsrLibFiles are files that we expect to be returned as bind mounts. + expectedUsrLibFiles = []string{ + filepath.Join(usrLibMountpoint, "nvidia", "libglxserver_nvidia.so"), + filepath.Join(usrLibMountpoint, "libnvidia-ml.so"), + } + expectedEnvs = []string{ + "NVIDIA_TEST=1", + "TEST_NVIDIA=1", + "nvidia_test=1", + } + ) + + environ := func() []string { + return append( + []string{ + "LIBGL_TEST=1", + "VULKAN_TEST=1", + }, expectedEnvs...) + } + + ctx = xunix.WithEnvironFn(ctx, environ) + + // Fake all the files. + for _, file := range append(expectedUsrLibFiles, procGPUDrivers...) { + _, err := afs.Create(file) + require.NoError(t, err) + } + + mounter.MountPoints = []mount.MountPoint{ + { + Device: "/dev/sda1", + Path: "/usr/local/nvidia", + Opts: []string{"rw"}, + }, + { + Device: "/dev/sda2", + Path: "/etc/hosts", + }, + { + Path: "/dev/nvidia0", + }, + { + Path: "/dev/nvidia1", + }, + } + + for _, driver := range procGPUDrivers { + mounter.MountPoints = append(mounter.MountPoints, mount.MountPoint{ + Path: driver, + }) + } + + _, err := afs.Create("/usr/local/nvidia") + require.NoError(t, err) + + unmounts := []string{} + mounter.UnmountFunc = func(path string) error { + unmounts = append(unmounts, path) + return nil + } + + var called bool + client := clitest.DockerClient(t, ctx) + client.ContainerCreateFn = func(_ context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, _ *v1.Platform, containerName string) (container.ContainerCreateCreatedBody, error) { + if containerName == cli.InnerContainerName { + called = true + // Test that '/dev' mounts are passed as devices. + require.Contains(t, hostConfig.Devices, container.DeviceMapping{ + PathOnHost: "/dev/nvidia0", + PathInContainer: "/dev/nvidia0", + CgroupPermissions: "rwm", + }) + require.Contains(t, hostConfig.Devices, container.DeviceMapping{ + PathOnHost: "/dev/nvidia1", + PathInContainer: "/dev/nvidia1", + CgroupPermissions: "rwm", + }) + + // Test that the mountpoint that we provided that is not under + // '/dev' is passed as a bind mount. + require.Contains(t, hostConfig.Binds, fmt.Sprintf("%s:%s", "/usr/local/nvidia", "/usr/local/nvidia")) + + // Test that host /usr/lib bind mounts were passed through as read-only. + for _, file := range expectedUsrLibFiles { + require.Contains(t, hostConfig.Binds, fmt.Sprintf("%s:%s:ro", + file, + strings.Replace(file, usrLibMountpoint, "/usr/lib/", -1), + )) + } + + // Test that we captured the GPU-related env vars. + for _, env := range expectedEnvs { + require.Contains(t, config.Env, env) + } + } + + return container.ContainerCreateCreatedBody{}, nil + } + + err = cmd.ExecuteContext(ctx) + require.NoError(t, err) + require.True(t, called, "create function was not called for inner container") + // Assert that we unmounted /proc GPU drivers. + for _, driver := range procGPUDrivers { + require.Contains(t, unmounts, driver) + } + }) +} + +// rawDockerAuth is sample input for a kubernetes secret to a gcr.io private +// registry. +const rawDockerAuth = `{"auths":{"us.gcr.io":{"username":"_json_key","password":"{\"type\": \"service_account\", \"project_id\": \"some-test\", \"private_key_id\": \"blahblah\", \"private_key\": \"-----BEGIN PRIVATE KEY-----mykey-----END PRIVATE KEY-----\", \"client_email\": \"test@test.iam.gserviceaccount.com\", \"client_id\": \"123\", \"auth_uri\": \"https://accounts.google.com/o/oauth2/auth\", \"token_uri\": \"https://oauth2.googleapis.com/token\", \"auth_provider_x509_cert_url\": \"https://www.googleapis.com/oauth2/v1/certs\", \"client_x509_cert_url\": \"https://www.googleapis.com/robot/v1/metadata/x509/test.iam.gserviceaccount.com\" }","email":"test@test.iam.gserviceaccount.com","auth":"X2pzb25fa2V5OnsKCgkidHlwZSI6ICJzZXJ2aWNlX2FjY291bnQiLAoJInByb2plY3RfaWQiOiAic29tZS10ZXN0IiwKCSJwcml2YXRlX2tleV9pZCI6ICJibGFoYmxhaCIsCgkicHJpdmF0ZV9rZXkiOiAiLS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCm15a2V5LS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLQoiLAoJImNsaWVudF9lbWFpbCI6ICJ0ZXN0QHRlc3QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLAoJImNsaWVudF9pZCI6ICIxMjMiLAoJImF1dGhfdXJpIjogImh0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbS9vL29hdXRoMi9hdXRoIiwKCSJ0b2tlbl91cmkiOiAiaHR0cHM6Ly9vYXV0aDIuZ29vZ2xlYXBpcy5jb20vdG9rZW4iLAoJImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6ICJodHRwczovL3d3dy5nb29nbGVhcGlzLmNvbS9vYXV0aDIvdjEvY2VydHMiLAoJImNsaWVudF94NTA5X2NlcnRfdXJsIjogImh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL3JvYm90L3YxL21ldGFkYXRhL3g1MDkvdGVzdC5pYW0uZ3NlcnZpY2VhY2NvdW50LmNvbSIKfQo="}}}` diff --git a/cli/root.go b/cli/root.go new file mode 100644 index 0000000..0af9baf --- /dev/null +++ b/cli/root.go @@ -0,0 +1,22 @@ +package cli + +import ( + "github.com/spf13/cobra" +) + +func Root() *cobra.Command { + cmd := &cobra.Command{ + Use: "envbox", + SilenceErrors: true, + SilenceUsage: true, + Args: func(cmd *cobra.Command, args []string) error { + return cobra.NoArgs(cmd, args) + }, + RunE: func(cmd *cobra.Command, args []string) error { + return cmd.Help() + }, + } + + cmd.AddCommand(dockerCmd()) + return cmd +} diff --git a/cmd/envbox/main.go b/cmd/envbox/main.go new file mode 100644 index 0000000..2dfdb94 --- /dev/null +++ b/cmd/envbox/main.go @@ -0,0 +1,20 @@ +package main + +import ( + "fmt" + "os" + "runtime" + + "github.com/coder/envbox/cli" +) + +func main() { + _, err := cli.Root().ExecuteC() + if err != nil { + _, _ = fmt.Fprintln(os.Stderr, err.Error()) + os.Exit(1) + } + + // We exit the main thread while keepin all the other procs goin strong. + runtime.Goexit() +} diff --git a/deploy/Dockerfile b/deploy/Dockerfile new file mode 100644 index 0000000..c81d6be --- /dev/null +++ b/deploy/Dockerfile @@ -0,0 +1,71 @@ +# Ubuntu 20.04 LTS (Focal Fossa) +FROM ubuntu:focal + +# Copy configuration files to appropriate locations +COPY files / + +LABEL \ + org.opencontainers.image.title="Envbox" \ + org.opencontainers.image.url="https://github.com/coder/envbox" \ + org.opencontainers.image.source="https://github.com/coder/envbox" \ + org.opencontainers.image.description="Run Docker in Docker in Kubernetes" + +# Basic utilities +ARG DEBIAN_FRONTEND=noninteractive +# Ignore other repositories, as some require HTTPS +RUN apt-get update --quiet --option Dir::Etc::SourceParts="" && \ + apt-get upgrade -y && \ + apt-get install --no-install-recommends --yes --quiet --option Dir::Etc::SourceParts="" \ + apt-transport-https \ + apt-utils \ + binutils \ + ca-certificates \ + curl \ + dialog \ + fuse \ + iproute2 \ + jq \ + kmod \ + lsb-release \ + make \ + mokutil \ + rsync \ + systemctl \ + wget \ + vim && \ + # Install packages from third-party repositories + apt-get update --quiet && \ + apt-get install --no-install-recommends --yes --quiet \ + containerd.io \ + docker-ce \ + docker-ce-cli && \ + # Delete package cache to avoid consuming space in layer + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +RUN wget https://downloads.nestybox.com/sysbox/releases/v0.5.2/sysbox-ce_0.5.2-0.linux_amd64.deb && \ + echo "f13fc0e156f72c6f8bd48e206c59482f83f19acc229701c74e0f23baafa724d8 sysbox-ce_0.5.2-0.linux_amd64.deb" | sha256sum --check --status && \ + apt install -y ./sysbox-ce_0.5.2-0.linux_amd64.deb && \ + rm ./sysbox-ce_0.5.2-0.linux_amd64.deb && \ + userdel -r sysbox + +# This is jank but sysbox adds their own /etc/docker/daemon.json that overwrites ours when it gets installed, +# so we copy over their changes to get the configurations we actually want. +COPY files / + +# Add coder user +RUN useradd coder \ + --create-home \ + --shell=/bin/bash \ + --groups=docker \ + --uid=1000 \ + --user-group && \ + usermod coder \ + --add-subuids 100000-165535 \ + --add-subgids 100000-165535 + +# Do this last so hotswapping is fast! +ARG ENVBOX_BIN=envbox +COPY $ENVBOX_BIN / + +CMD ["/envbox"] diff --git a/deploy/files/etc/apt/preferences.d/docker b/deploy/files/etc/apt/preferences.d/docker new file mode 100644 index 0000000..a92c0ab --- /dev/null +++ b/deploy/files/etc/apt/preferences.d/docker @@ -0,0 +1,19 @@ +# Ignore all packages from this repository by default +Package: * +Pin: origin download.docker.com +Pin-Priority: 1 + +# Docker Community Edition +Package: docker-ce +Pin: origin download.docker.com +Pin-Priority: 500 + +# Docker command-line tool +Package: docker-ce-cli +Pin: origin download.docker.com +Pin-Priority: 500 + +# containerd runtime +Package: containerd.io +Pin: origin download.docker.com +Pin-Priority: 500 diff --git a/deploy/files/etc/apt/sources.list.d/docker.list b/deploy/files/etc/apt/sources.list.d/docker.list new file mode 100644 index 0000000..256f965 --- /dev/null +++ b/deploy/files/etc/apt/sources.list.d/docker.list @@ -0,0 +1 @@ +deb [signed-by=/usr/share/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu focal stable diff --git a/deploy/files/etc/docker/daemon.json b/deploy/files/etc/docker/daemon.json new file mode 100644 index 0000000..a86bbde --- /dev/null +++ b/deploy/files/etc/docker/daemon.json @@ -0,0 +1,7 @@ +{ + "runtimes": { + "sysbox-runc": { + "path": "/usr/bin/sysbox-runc" + } + } +} diff --git a/deploy/files/usr/share/keyrings/docker.gpg b/deploy/files/usr/share/keyrings/docker.gpg new file mode 100644 index 0000000000000000000000000000000000000000..e5dc8cfda8e5d37f69956520048140c9baab9803 GIT binary patch literal 2760 zcmV;(3ODtc0u2OMt=cL95CGv?mVEyU+3FP&iF2?(b<6@*g&o7k_7E+vfpyDoj$zjA zGV5WMs<5X`yaKG4`1D^?%Ti#*f9W@2In1 z#V#$cv(vuM$1G5W?m=#;?M(Cxek`gIB|ZeE>e*?4HA0Yo?Le89KO(!1UAgKnfVKJp ze7*UXLf?I!keb9u+BFqeeB``A$gwvu)M9q}dT8YU+=NzEb9$;fT&a6fycOmt+QBrl zSljK4NaNyiOYqwZ!pA8r^c00OKI|6ITnqr2;lfcg2)^}~s|^iuXkp-Z9zw?u9f%Gl zIKx%?805>Gz6o0*0IGj52V2W@R3^r4ggg+8qe2>{F;knjCB39B|n)&}Ia))TWmVOS1zJD$Q<&mo|g~V`#5B$6N zxLlw5L@k&9cvMyuB!wfYMH5Y?I18^yQU0Cn< zQ+Vm-4&d0rzki{yJhx4HVp!v=n%$Eu4}XG1@@3Rpmx4E2z!ZF5gVt7hXhF3JhQ)dC z^v|>E6|i%rp_>2^0RRECD@1Q&Yh`jEQe|vqVRL05C__acWMyJ0AUtGmV{2t{KxA)Y zYh`jSV{dIfi2^qS69EbUAq4_ht>?f38!rV52?z%R1r-Vj2nz)k0s{d60v-VZ7k~f? z2@s8efIJSr&4{we5B?+>qpu&7G$uCr{9l#Rccf8iLHFK8*j}rX=-CG)$dc?$piG&n zyvm)ljwUsM!bnCjBbuvmg?VD7{XegYqwDC-jwi9@5G?Wk0W>(My&0lUwT?!h+_)r; ziSkkZTf)_`7M(d9Eygf&;f2K#dl0cev@e`hmk( zZtk3Hs%->NGPyLrr#y%lgx{LEI^lyjO4KBwd}kap{2xYFqV-F2>Yq zG-gdq-7QDsOB?=ysoxG@7KH&vE_?hnRc?txWkz9<=VtFx@Ut8hfLi2;JwF@%ZMK$zRb;~8!vOdFX75Fk8*e>XpOrG|YsSZ2f#t_(HJ z+2iiq+kTKEd{!m%PjyDuMW8T;FZ!)Cg>O6x2SR3fyfZ=kBSDUz=aV8M^lA(&u0B2M z-aM5?LcHpf3Iqah6nv_W(wZrA8IAR4qXOaf%g7n?TNrw7a0Kc^OVl3Z8#a2R3m+9$ z8(5MM+x77e+YoN$TgPo5x1IH2GV6I8ege0YQtX?0EQiH**C+5Ml4{T8)OO+-PfE3sg1Paga|nw;9NrvW?0Q{d=P|r_7drn! z8&M^%eloEvv)?t~lG>1q+=qlCndr6=1Yy(%>dfgbh&%TXeRWyM$f8?S{8ygGsA8pS zM?IBQwFu-HaGRib&`sVMSJXjhuE(AOvYeGL$vD)^dqADy%5oai-WdX=OaMym$#l_A z7d>5VEn*PN1N}x~{PYrGX`90HOmI4|Rc$_R*_61pBoGZVu(mO4MgBSA z3G&qBk^c}(l#fx^d_Tr93{<%g;efsvX)qQ8<7p74rQ;AUmvbi*yka|wYGA+9!(&uJ z3tZ#|GLLIrw5-@~{uvdM_93`x8jgYT%ZPhr3MqBNEu`I@f@nl~3G(!ilEW9!nGG{5 zNIRRPhlryvj{p$?00D^vJ_Hy62mlEM0$8ouDgqk<0x1a)je&qX4!_Na!CfE(8370Y z1_c6Gt=cL83JDN?psB<1bNtxVU=ROn3Hco$6RNCn?dy%AGv~v}na?1gs^YJhXA)JR zJ_hRT#t5-)YKUBmhDT{(!zP43W=13FLVlQQY&Uywe9iI|Dk@tr8RUEXt!L9asCk14 z$moeFun}{2z@`Y8KUEy#Y?ttc*0nt%%r%bCd4pClxDY!t`M2qFddF+NHq%TDA5Z73 zoZ<)UWl<6+4{!S>HvV2YFUNmbNfe7l7outUhag5HvTFpov{9)%SU3wB^qK~XMv`AX!x<6%-nu+;S&pdG~rCcpO z05M&Fwf!q>>kU>E8(Zk`CG@{MMFpYoH>2^}r{N(ze}#nyK^=2d^FwnaCSIuyoty8V z$MgLSEc6&fC;Zgt2oP+BME)7IvYQ`*`)m(a>t+0)T%TWxv;Hw42=h!wN&j`JBw0E> z50`dHHM+RTjBnAsX^_gE8Q9$kh)YxA+2aP#nvkSPSGK0POS-qBfqQ0U`6_z!bL?8k zW-GmuFEE@S55O+}&SbUnxDWqc+d9(t{vtC%96$nq3|U&n$e6E6Na09qb+{@cv1jZ| z3ANPzBC8hPZX%fd!AicAGHUi1CEtQTkg6rlJ&izkT=Qe0t#FL~*@%Q@afty20kH zP@b&1>Szr#R<^(R$ZDQ+tX1BmAvCn7XbkFG{bvJsln04BkS2;}7+r)m!j=C|-@2Mb zZaVA|!c_0vpuO@|Zgh7CYc|rUFc^1cmciEIZ-OsoUfh8=!gs&KS$I6fh;IjUD`52- z$hYta7J<u zKyfV{PWt21mmL1oc+`{DV3Y`cYIUjP(OqJCF?#$b(-lq(eagmRKXj;3eca9O(@/dev/null 2>&1 + # Upstream Docker signing key + curl "${curl_flags[@]}" "https://download.docker.com/linux/ubuntu/gpg" | \ + gpg "${gpg_flags[@]}" --output="docker.gpg" +popd >/dev/null 2>&1 diff --git a/dockerutil/client.go b/dockerutil/client.go new file mode 100644 index 0000000..0a22e3e --- /dev/null +++ b/dockerutil/client.go @@ -0,0 +1,65 @@ +package dockerutil + +import ( + "context" + "encoding/base64" + "encoding/json" + + dockertypes "github.com/docker/docker/api/types" + dockerclient "github.com/docker/docker/client" + "golang.org/x/xerrors" +) + +type clientKey struct{} + +// WithClient sets the provided DockerClient on the context. +// It should only be used for tests. +func WithClient(ctx context.Context, client DockerClient) context.Context { + return context.WithValue(ctx, clientKey{}, client) +} + +// Client returns the DockerClient set on the context. If one can't be +// found a default client is returned. +func Client(ctx context.Context) (DockerClient, error) { + client := ctx.Value(clientKey{}) + if client == nil { + client, err := dockerclient.NewClientWithOpts(dockerclient.FromEnv) + if err != nil { + return nil, xerrors.Errorf("new env client: %w", err) + } + + return client, nil + } + + //nolint we should panic if this isn't the case. + return client.(DockerClient), nil +} + +type AuthConfig dockertypes.AuthConfig + +func (a AuthConfig) Base64() (string, error) { + authStr, err := json.Marshal(a) + if err != nil { + return "", xerrors.Errorf("json marshal: %w", err) + } + return base64.StdEncoding.EncodeToString(authStr), nil +} + +func ParseAuthConfig(raw string) (AuthConfig, error) { + type dockerConfig struct { + AuthConfigs map[string]dockertypes.AuthConfig `json:"auths"` + } + + var conf dockerConfig + if err := json.Unmarshal([]byte(raw), &conf); err != nil { + return AuthConfig{}, xerrors.Errorf("parse docker auth config secret: %w", err) + } + if len(conf.AuthConfigs) != 1 { + return AuthConfig{}, xerrors.Errorf("number of image pull auth configs not equal to 1 (%d)", len(conf.AuthConfigs)) + } + for _, regConfig := range conf.AuthConfigs { + return AuthConfig(regConfig), nil + } + + return AuthConfig{}, xerrors.New("no auth configs parsed.") +} diff --git a/dockerutil/container.go b/dockerutil/container.go new file mode 100644 index 0000000..49c2c7f --- /dev/null +++ b/dockerutil/container.go @@ -0,0 +1,175 @@ +package dockerutil + +import ( + "context" + "fmt" + "path/filepath" + "strconv" + "strings" + "time" + + "github.com/docker/docker/api/types/container" + dockerclient "github.com/docker/docker/client" + "github.com/spf13/afero" + "golang.org/x/xerrors" + + "cdr.dev/slog" + "github.com/coder/envbox/xunix" + "github.com/coder/retry" +) + +const ( + runtime = "sysbox-runc" + // Default CPU period for containers. + DefaultCPUPeriod uint64 = 1e5 +) + +type DockerClient interface { + dockerclient.SystemAPIClient + dockerclient.ContainerAPIClient + dockerclient.ImageAPIClient +} + +type ContainerConfig struct { + Log slog.Logger + Mounts []xunix.Mount + Devices []container.DeviceMapping + Envs []string + Name string + Image string + WorkingDir string + Hostname string + // HasInit dictates whether the entrypoint of the container is /sbin/init + // or 'sleep infinity'. + HasInit bool + CPUs int64 + MemoryLimit int64 +} + +// CreateContainer creates a sysbox-runc container. +func CreateContainer(ctx context.Context, client DockerClient, conf *ContainerConfig) (string, error) { + host := &container.HostConfig{ + Runtime: runtime, + AutoRemove: true, + Resources: container.Resources{ + Devices: conf.Devices, + // Set resources for the inner container. + // This is important for processes inside the container to know what they + // have to work with. + // TODO: Sysbox does not copy cpu.cfs_{period,quota}_us into syscont-cgroup-root cgroup. + // These will not be visible inside the child container. + // See: https://github.com/nestybox/sysbox/issues/582 + CPUPeriod: int64(DefaultCPUPeriod), + CPUQuota: conf.CPUs * int64(DefaultCPUPeriod), + Memory: conf.MemoryLimit, + }, + ExtraHosts: []string{"host.docker.internal:host-gateway"}, + Binds: generateBindMounts(conf.Mounts), + } + + entrypoint := []string{"sleep", "infinity"} + if conf.HasInit { + entrypoint = []string{"/sbin/init"} + } + + if conf.Hostname == "" { + conf.Hostname = conf.Name + } + + cnt := &container.Config{ + Image: conf.Image, + Entrypoint: entrypoint, + Cmd: []string{}, + Env: conf.Envs, + Hostname: conf.Hostname, + WorkingDir: conf.WorkingDir, + Tty: false, + User: "root", + } + + c, err := client.ContainerCreate(ctx, cnt, host, nil, nil, conf.Name) + if err != nil { + return "", xerrors.Errorf("create container: %w", err) + } + return c.ID, nil +} + +type BootstrapConfig struct { + ContainerID string + User string + Script string + Env []string + Detach bool +} + +// BoostrapContainer runs a script inside the container as the provided user. +// If conf.Script is empty then it is a noop. +func BootstrapContainer(ctx context.Context, client DockerClient, conf BootstrapConfig) error { + if conf.Script == "" { + return nil + } + + var err error + for r, n := retry.New(time.Second, time.Second*2), 0; r.Wait(ctx) && n < 10; n++ { + var out []byte + out, err = ExecContainer(ctx, client, ExecConfig{ + ContainerID: conf.ContainerID, + User: conf.User, + Cmd: "/bin/sh", + Args: []string{"-s"}, + Stdin: strings.NewReader(conf.Script), + Env: conf.Env, + }) + if err != nil { + err = xerrors.Errorf("boostrap container (%s): %w", out, err) + continue + } + break + } + + if err != nil { + return xerrors.Errorf("timed out boostrapping container: %w", err) + } + + return nil +} + +// copyCPUQuotaToInnerCGroup writes the contents of the following files to +// their corresponding locations under cgroupBase: +// - /sys/fs/cgroup/cpu,cpuacct/cpu.cfs_period_us +// - /sys/fs/cgroup/cpu,cpuacct/cpu.cfs_quota_us +// +// HACK: until https://github.com/nestybox/sysbox/issues/582 is resolved, we need to set cfs_quota_us +// and cfs_period_us inside the container to ensure that applications inside the container know how much +// CPU they have to work with. +func SetContainerCPUQuota(ctx context.Context, containerID string, quota, period int) error { + var ( + fs = xunix.GetFS(ctx) + cgroupBase = fmt.Sprintf("/sys/fs/cgroup/cpu,cpuacct/docker/%s/syscont-cgroup-root/", containerID) + ) + + err := afero.WriteFile(fs, filepath.Join(cgroupBase, "cpu.cfs_period_us"), []byte(strconv.Itoa(period)), 0o644) + if err != nil { + return xerrors.Errorf("write cpu.cfs_period_us to inner container cgroup: %w", err) + } + + err = afero.WriteFile(fs, filepath.Join(cgroupBase, "cpu.cfs_quota_us"), []byte(strconv.Itoa(quota)), 0o644) + if err != nil { + return xerrors.Errorf("write cpu.cfs_quota_us to inner container cgroup: %w", err) + } + + return nil +} + +func generateBindMounts(mounts []xunix.Mount) []string { + binds := make([]string, 0, len(mounts)) + for _, mount := range mounts { + bind := fmt.Sprintf("%s:%s", mount.Source, mount.Mountpoint) + if mount.ReadOnly { + bind += ":ro" + } + binds = append(binds, bind) + } + + return binds +} diff --git a/dockerutil/daemon.go b/dockerutil/daemon.go new file mode 100644 index 0000000..0cc12c3 --- /dev/null +++ b/dockerutil/daemon.go @@ -0,0 +1,56 @@ +package dockerutil + +import ( + "context" + "time" + + dockerclient "github.com/docker/docker/client" +) + +// WaitForDaemon waits for a Docker daemon to startup. It waits a max +// of 30s before giving up. +func WaitForDaemon(ctx context.Context, client DockerClient) error { + ticker := time.NewTicker(time.Millisecond * 250) + defer ticker.Stop() + + ctx, cancel := context.WithTimeout(ctx, time.Second*30) + defer cancel() + + pingCtx, cancel := context.WithTimeout(ctx, time.Second) + defer cancel() + + _, err := client.Ping(pingCtx) + if err == nil { + // We pinged successfully! + return nil + } + + for { + select { + case <-ctx.Done(): + return ctx.Err() + case <-ticker.C: + } + + err := func() error { + pingCtx, cancel := context.WithTimeout(ctx, time.Second) + defer cancel() + + _, err := client.Ping(pingCtx) + return err + }() + if err == nil { + // We pinged successfully! + return nil + } + + // If its a connection failed error we can ignore and continue polling. + // It's likely that dockerd just isn't fully setup yet. + if dockerclient.IsErrConnectionFailed(err) || pingCtx.Err() != nil { + continue + } + + // If its something else, we return it. + return err + } +} diff --git a/dockerutil/doc.go b/dockerutil/doc.go new file mode 100644 index 0000000..9653176 --- /dev/null +++ b/dockerutil/doc.go @@ -0,0 +1,3 @@ +// Package dockerutil contains convenience functions for interacting with +// Docker. +package dockerutil diff --git a/dockerutil/dockerfake/client.go b/dockerutil/dockerfake/client.go new file mode 100644 index 0000000..e629e97 --- /dev/null +++ b/dockerutil/dockerfake/client.go @@ -0,0 +1,281 @@ +package dockerfake + +import ( + "context" + "io" + "strings" + + dockertypes "github.com/docker/docker/api/types" + containertypes "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/events" + "github.com/docker/docker/api/types/filters" + "github.com/docker/docker/api/types/image" + networktypes "github.com/docker/docker/api/types/network" + "github.com/docker/docker/api/types/registry" + specs "github.com/opencontainers/image-spec/specs-go/v1" + + "github.com/coder/envbox/dockerutil" +) + +var _ dockerutil.DockerClient = MockClient{} + +// MockClient provides overrides for functions that are called in envbox. +type MockClient struct { + ImagePullFn func(_ context.Context, ref string, options dockertypes.ImagePullOptions) (io.ReadCloser, error) + ContainerCreateFn func(_ context.Context, config *containertypes.Config, hostConfig *containertypes.HostConfig, networkingConfig *networktypes.NetworkingConfig, _ *specs.Platform, containerName string) (containertypes.ContainerCreateCreatedBody, error) + ImagePruneFn func(_ context.Context, pruneFilter filters.Args) (dockertypes.ImagesPruneReport, error) + ContainerStartFn func(_ context.Context, container string, options dockertypes.ContainerStartOptions) error + ContainerExecAttachFn func(_ context.Context, execID string, config dockertypes.ExecStartCheck) (dockertypes.HijackedResponse, error) + ContainerExecCreateFn func(_ context.Context, container string, config dockertypes.ExecConfig) (dockertypes.IDResponse, error) + ContainerExecStartFn func(_ context.Context, execID string, config dockertypes.ExecStartCheck) error + ContainerExecInspectFn func(_ context.Context, execID string) (dockertypes.ContainerExecInspect, error) + ContainerInspectFn func(_ context.Context, container string) (dockertypes.ContainerJSON, error) + ContainerRemoveFn func(_ context.Context, container string, options dockertypes.ContainerRemoveOptions) error + PingFn func(_ context.Context) (dockertypes.Ping, error) +} + +func (MockClient) ImageBuild(_ context.Context, _ io.Reader, _ dockertypes.ImageBuildOptions) (dockertypes.ImageBuildResponse, error) { + panic("not implemented") +} + +func (MockClient) BuildCachePrune(_ context.Context, _ dockertypes.BuildCachePruneOptions) (*dockertypes.BuildCachePruneReport, error) { + panic("not implemented") +} + +func (MockClient) BuildCancel(_ context.Context, _ string) error { + panic("not implemented") +} + +func (MockClient) ImageCreate(_ context.Context, _ string, _ dockertypes.ImageCreateOptions) (io.ReadCloser, error) { + panic("not implemented") +} + +func (MockClient) ImageHistory(_ context.Context, _ string) ([]image.HistoryResponseItem, error) { + panic("not implemented") +} + +func (MockClient) ImageImport(_ context.Context, _ dockertypes.ImageImportSource, _ string, _ dockertypes.ImageImportOptions) (io.ReadCloser, error) { + panic("not implemented") +} + +func (MockClient) ImageInspectWithRaw(_ context.Context, _ string) (dockertypes.ImageInspect, []byte, error) { + panic("not implemented") +} + +func (MockClient) ImageList(_ context.Context, _ dockertypes.ImageListOptions) ([]dockertypes.ImageSummary, error) { + panic("not implemented") +} + +func (MockClient) ImageLoad(_ context.Context, _ io.Reader, _ bool) (dockertypes.ImageLoadResponse, error) { + panic("not implemented") +} + +func (m MockClient) ImagePull(ctx context.Context, ref string, options dockertypes.ImagePullOptions) (io.ReadCloser, error) { + if m.ImagePullFn == nil { + return io.NopCloser(strings.NewReader("")), nil + } + return m.ImagePullFn(ctx, ref, options) +} + +func (MockClient) ImagePush(_ context.Context, _ string, _ dockertypes.ImagePushOptions) (io.ReadCloser, error) { + panic("not implemented") +} + +func (MockClient) ImageRemove(_ context.Context, _ string, _ dockertypes.ImageRemoveOptions) ([]dockertypes.ImageDeleteResponseItem, error) { + panic("not implemented") +} + +func (MockClient) ImageSearch(_ context.Context, _ string, _ dockertypes.ImageSearchOptions) ([]registry.SearchResult, error) { + panic("not implemented") +} + +func (MockClient) ImageSave(_ context.Context, _ []string) (io.ReadCloser, error) { + panic("not implemented") +} + +func (MockClient) ImageTag(_ context.Context, _ string, _ string) error { + panic("not implemented") +} + +func (m MockClient) ImagesPrune(ctx context.Context, pruneFilter filters.Args) (dockertypes.ImagesPruneReport, error) { + if m.ImagePruneFn == nil { + return dockertypes.ImagesPruneReport{}, nil + } + return m.ImagePruneFn(ctx, pruneFilter) +} + +func (MockClient) Events(_ context.Context, _ dockertypes.EventsOptions) (<-chan events.Message, <-chan error) { + panic("not implemented") +} + +func (MockClient) Info(_ context.Context) (dockertypes.Info, error) { + panic("not implemented") +} + +func (MockClient) RegistryLogin(_ context.Context, _ dockertypes.AuthConfig) (registry.AuthenticateOKBody, error) { + panic("not implemented") +} + +func (MockClient) DiskUsage(_ context.Context, _ dockertypes.DiskUsageOptions) (dockertypes.DiskUsage, error) { + panic("not implemented") +} + +func (m MockClient) Ping(ctx context.Context) (dockertypes.Ping, error) { + if m.PingFn == nil { + return dockertypes.Ping{}, nil + } + return m.PingFn(ctx) +} + +func (MockClient) ContainerAttach(_ context.Context, _ string, _ dockertypes.ContainerAttachOptions) (dockertypes.HijackedResponse, error) { + panic("not implemented") +} + +func (MockClient) ContainerCommit(_ context.Context, _ string, _ dockertypes.ContainerCommitOptions) (dockertypes.IDResponse, error) { + panic("not implemented") +} + +func (m MockClient) ContainerCreate(ctx context.Context, config *containertypes.Config, hostConfig *containertypes.HostConfig, networkingConfig *networktypes.NetworkingConfig, pspecs *specs.Platform, containerName string) (containertypes.ContainerCreateCreatedBody, error) { + if m.ContainerCreateFn == nil { + return containertypes.ContainerCreateCreatedBody{}, nil + } + return m.ContainerCreateFn(ctx, config, hostConfig, networkingConfig, pspecs, containerName) +} + +func (MockClient) ContainerDiff(_ context.Context, _ string) ([]containertypes.ContainerChangeResponseItem, error) { + panic("not implemented") +} + +func (m MockClient) ContainerExecAttach(ctx context.Context, execID string, config dockertypes.ExecStartCheck) (dockertypes.HijackedResponse, error) { + if m.ContainerExecAttachFn == nil { + return dockertypes.HijackedResponse{}, nil + } + return m.ContainerExecAttachFn(ctx, execID, config) +} + +func (m MockClient) ContainerExecCreate(ctx context.Context, name string, config dockertypes.ExecConfig) (dockertypes.IDResponse, error) { + if m.ContainerExecCreateFn == nil { + return dockertypes.IDResponse{}, nil + } + return m.ContainerExecCreateFn(ctx, name, config) +} + +func (m MockClient) ContainerExecInspect(ctx context.Context, id string) (dockertypes.ContainerExecInspect, error) { + if m.ContainerExecInspectFn == nil { + return dockertypes.ContainerExecInspect{}, nil + } + + return m.ContainerExecInspectFn(ctx, id) +} + +func (MockClient) ContainerExecResize(_ context.Context, _ string, _ dockertypes.ResizeOptions) error { + panic("not implemented") +} + +func (m MockClient) ContainerExecStart(ctx context.Context, execID string, config dockertypes.ExecStartCheck) error { + if m.ContainerExecStartFn == nil { + return nil + } + return m.ContainerExecStartFn(ctx, execID, config) +} + +func (MockClient) ContainerExport(_ context.Context, _ string) (io.ReadCloser, error) { + panic("not implemented") +} + +func (m MockClient) ContainerInspect(ctx context.Context, name string) (dockertypes.ContainerJSON, error) { + if m.ContainerInspectFn == nil { + return dockertypes.ContainerJSON{}, nil + } + return m.ContainerInspectFn(ctx, name) +} + +func (MockClient) ContainerInspectWithRaw(_ context.Context, _ string, _ bool) (dockertypes.ContainerJSON, []byte, error) { + panic("not implemented") +} + +func (MockClient) ContainerKill(_ context.Context, _ string, _ string) error { + panic("not implemented") +} + +func (MockClient) ContainerList(_ context.Context, _ dockertypes.ContainerListOptions) ([]dockertypes.Container, error) { + panic("not implemented") +} + +func (MockClient) ContainerLogs(_ context.Context, _ string, _ dockertypes.ContainerLogsOptions) (io.ReadCloser, error) { + panic("not implemented") +} + +func (MockClient) ContainerPause(_ context.Context, _ string) error { + panic("not implemented") +} + +func (m MockClient) ContainerRemove(ctx context.Context, name string, options dockertypes.ContainerRemoveOptions) error { + if m.ContainerRemoveFn == nil { + return nil + } + return m.ContainerRemoveFn(ctx, name, options) +} + +func (MockClient) ContainerRename(_ context.Context, _ string, _ string) error { + panic("not implemented") +} + +func (MockClient) ContainerResize(_ context.Context, _ string, _ dockertypes.ResizeOptions) error { + panic("not implemented") +} + +func (MockClient) ContainerRestart(_ context.Context, _ string, _ containertypes.StopOptions) error { + panic("not implemented") +} + +func (MockClient) ContainerStatPath(_ context.Context, _ string, _ string) (dockertypes.ContainerPathStat, error) { + panic("not implemented") +} + +func (MockClient) ContainerStats(_ context.Context, _ string, _ bool) (dockertypes.ContainerStats, error) { + panic("not implemented") +} + +func (m MockClient) ContainerStart(ctx context.Context, name string, options dockertypes.ContainerStartOptions) error { + if m.ContainerStartFn == nil { + return nil + } + return m.ContainerStartFn(ctx, name, options) +} + +func (MockClient) ContainerStop(_ context.Context, _ string, _ containertypes.StopOptions) error { + panic("not implemented") +} + +func (MockClient) ContainerTop(_ context.Context, _ string, _ []string) (containertypes.ContainerTopOKBody, error) { + panic("not implemented") +} + +func (MockClient) ContainerUnpause(_ context.Context, _ string) error { + panic("not implemented") +} + +func (MockClient) ContainerUpdate(_ context.Context, _ string, _ containertypes.UpdateConfig) (containertypes.ContainerUpdateOKBody, error) { + panic("not implemented") +} + +func (MockClient) ContainerWait(_ context.Context, _ string, _ containertypes.WaitCondition) (<-chan containertypes.ContainerWaitOKBody, <-chan error) { + panic("not implemented") +} + +func (MockClient) CopyFromContainer(_ context.Context, _ string, _ string) (io.ReadCloser, dockertypes.ContainerPathStat, error) { + panic("not implemented") +} + +func (MockClient) CopyToContainer(_ context.Context, _ string, _ string, _ io.Reader, _ dockertypes.CopyToContainerOptions) error { + panic("not implemented") +} + +func (MockClient) ContainersPrune(_ context.Context, _ filters.Args) (dockertypes.ContainersPruneReport, error) { + panic("not implemented") +} + +func (MockClient) ContainerStatsOneShot(_ context.Context, _ string) (dockertypes.ContainerStats, error) { + panic("not implemented") +} diff --git a/dockerutil/dockerfake/doc.go b/dockerutil/dockerfake/doc.go new file mode 100644 index 0000000..b00ea2b --- /dev/null +++ b/dockerutil/dockerfake/doc.go @@ -0,0 +1,3 @@ +// Package dockerfake contains logic for mocking out Docker-related +// functionality. +package dockerfake diff --git a/dockerutil/exec.go b/dockerutil/exec.go new file mode 100644 index 0000000..6f78822 --- /dev/null +++ b/dockerutil/exec.go @@ -0,0 +1,94 @@ +package dockerutil + +import ( + "bytes" + "context" + "io" + + dockertypes "github.com/docker/docker/api/types" + "golang.org/x/xerrors" + + "github.com/coder/envbox/xio" +) + +type ExecConfig struct { + ContainerID string + User string + Cmd string + Args []string + Stdin io.Reader + StdOutErr io.Writer + Env []string + Detach bool +} + +// ExecContainer runs a command in a container. It returns the output and any error. +// If an error occurs during the execution of the command, the output is appended to the error. +func ExecContainer(ctx context.Context, client DockerClient, config ExecConfig) ([]byte, error) { + exec, err := client.ContainerExecCreate(ctx, config.ContainerID, dockertypes.ExecConfig{ + Detach: true, + Cmd: append([]string{config.Cmd}, config.Args...), + User: config.User, + AttachStderr: true, + AttachStdout: true, + AttachStdin: config.Stdin != nil, + Env: config.Env, + }) + if err != nil { + return nil, xerrors.Errorf("exec create: %w", err) + } + + resp, err := client.ContainerExecAttach(ctx, exec.ID, dockertypes.ExecStartCheck{}) + if err != nil { + return nil, xerrors.Errorf("attach to exec: %w", err) + } + defer resp.Close() + + if config.Stdin != nil { + _, err = io.Copy(resp.Conn, config.Stdin) + if err != nil { + return nil, xerrors.Errorf("copy stdin: %w", err) + } + err = resp.CloseWrite() + if err != nil { + return nil, xerrors.Errorf("close write: %w", err) + } + } + + var ( + buf bytes.Buffer + // Avoid capturing too much output. We want to prevent + // a memory leak. This is especially important when + // we run the bootstrap script since we do not return. + psw = &xio.PrefixSuffixWriter{ + W: &buf, + N: 1 << 10, + } + wr io.Writer = psw + ) + + if config.StdOutErr != nil { + wr = io.MultiWriter(psw, config.StdOutErr) + } + + _, err = io.Copy(wr, resp.Reader) + if err != nil { + return nil, xerrors.Errorf("copy cmd output: %w", err) + } + resp.Close() + + inspect, err := client.ContainerExecInspect(ctx, exec.ID) + if err != nil { + return nil, xerrors.Errorf("exec inspect: %w", err) + } + + if inspect.Running { + return nil, xerrors.Errorf("unexpectedly still running") + } + + if inspect.ExitCode > 0 { + return nil, xerrors.Errorf("%s: exit code %d", buf.Bytes(), inspect.ExitCode) + } + + return buf.Bytes(), nil +} diff --git a/dockerutil/image.go b/dockerutil/image.go new file mode 100644 index 0000000..9ba196e --- /dev/null +++ b/dockerutil/image.go @@ -0,0 +1,255 @@ +package dockerutil + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "time" + + dockertypes "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/filters" + "golang.org/x/xerrors" + + "github.com/coder/envbox/buildlog" + "github.com/coder/envbox/xunix" + "github.com/coder/retry" +) + +const diskFullStorageDriver = "vfs" + +type PullImageConfig struct { + Client DockerClient + Image string + Auth AuthConfig + ProgressFn ImagePullProgressFn +} + +type ImagePullEvent struct { + Status string `json:"status"` + Error string `json:"error"` + Progress string `json:"progress"` + ProgressDetail struct { + Current int `json:"current"` + Total int `json:"total"` + } `json:"progressDetail"` +} + +// ImagePullProgressFn provides a way for a consumer to process +// image pull progress. +type ImagePullProgressFn func(e ImagePullEvent) error + +// PullImage pulls the provided image. +func PullImage(ctx context.Context, config *PullImageConfig) error { + authStr, err := config.Auth.Base64() + if err != nil { + return xerrors.Errorf("base64 encode auth: %w", err) + } + + pullImageFn := func() error { + var rd io.ReadCloser + rd, err = config.Client.ImagePull(ctx, config.Image, dockertypes.ImagePullOptions{ + RegistryAuth: authStr, + }) + if err != nil { + return xerrors.Errorf("pull image: %w", err) + } + + err = processImagePullEvents(rd, config.ProgressFn) + if err != nil { + return xerrors.Errorf("process image pull events: %w", err) + } + return nil + } + + err = pullImageFn() + if err == nil { + return nil + } + + var pruned bool + for r, n := retry.New(time.Second, time.Second*3), 0; r.Wait(ctx) && n < 10; n++ { + err = pullImageFn() + if err != nil { + // If we failed to pull the image, try to prune existing images + // to free up space. + if xunix.IsNoSpaceErr(err) && !pruned { + pruned = true + // Pruning is best effort. + _, _ = PruneImages(ctx, config.Client) + } + // If we've already pruned and we still can't pull the image we + // should just exit. + if xunix.IsNoSpaceErr(err) && pruned { + return xerrors.Errorf("insufficient disk to pull image: %w", err) + } + continue + } + return nil + } + if err != nil { + return xerrors.Errorf("pull image: %w", err) + } + + return nil +} + +// PruneImage runs a simple 'docker prune'. +func PruneImages(ctx context.Context, client DockerClient) (dockertypes.ImagesPruneReport, error) { + report, err := client.ImagesPrune(ctx, + filters.NewArgs(filters.Arg("dangling", "false")), + ) + if err != nil { + return dockertypes.ImagesPruneReport{}, xerrors.Errorf("images prune: %w", err) + } + + return report, nil +} + +func processImagePullEvents(r io.Reader, fn ImagePullProgressFn) error { + if fn == nil { + // This effectively waits until the image is pulled before returning, + // reporting no progress. + _, _ = io.Copy(io.Discard, r) + return nil + } + + decoder := json.NewDecoder(r) + + var event ImagePullEvent + for { + if err := decoder.Decode(&event); err != nil { + if xerrors.Is(err, io.EOF) { + break + } + + return xerrors.Errorf("decode image pull output: %w", err) + } + + err := fn(event) + if err != nil { + return xerrors.Errorf("process image pull event: %w", err) + } + } + + return nil +} + +type ImageMetadata struct { + UID string + GID string + HomeDir string + HasInit bool +} + +// GetImageMetadata returns metadata about an image such as the UID/GID of the +// provided // username and whether it contains an /sbin/init that we should run. +func GetImageMetadata(ctx context.Context, client DockerClient, image, username string) (ImageMetadata, error) { + // Creating a dummy container to inspect the filesystem. + created, err := client.ContainerCreate(ctx, + &container.Config{ + Image: image, + Entrypoint: []string{ + "sleep", + }, + Cmd: []string{ + "infinity", + }, + }, + &container.HostConfig{ + Runtime: "sysbox-runc", + }, nil, nil, "") + if err != nil { + return ImageMetadata{}, xerrors.Errorf("create container: %w", err) + } + + defer func() { + // We wanna remove this, but it's not a huge deal if it fails. + _ = client.ContainerRemove(ctx, created.ID, dockertypes.ContainerRemoveOptions{ + Force: true, + }) + }() + + err = client.ContainerStart(ctx, created.ID, dockertypes.ContainerStartOptions{}) + if err != nil { + return ImageMetadata{}, xerrors.Errorf("start container: %w", err) + } + + inspect, err := client.ContainerInspect(ctx, created.ID) + if err != nil { + return ImageMetadata{}, xerrors.Errorf("inspect: %w", err) + } + + mergedDir := inspect.GraphDriver.Data["MergedDir"] + // The mergedDir might be empty if we're running dockerd in recovery + // mode. + if mergedDir == "" && inspect.GraphDriver.Name != diskFullStorageDriver { + // The MergedDir is empty when the underlying filesystem does not support + // OverlayFS as an extension. A customer ran into this when using NFS as + // a provider for a PVC. + return ImageMetadata{}, xerrors.Errorf("CVMs do not support NFS volumes") + } + + _, err = ExecContainer(ctx, client, ExecConfig{ + ContainerID: inspect.ID, + Cmd: "stat", + Args: []string{"/sbin/init"}, + }) + initExists := err == nil + + out, err := ExecContainer(ctx, client, ExecConfig{ + ContainerID: inspect.ID, + Cmd: "getent", + Args: []string{"passwd", username}, + }) + if err != nil { + return ImageMetadata{}, xerrors.Errorf("get /etc/passwd entry for %s: %w", username, err) + } + + users, err := xunix.ParsePasswd(bytes.NewReader(out)) + if err != nil { + return ImageMetadata{}, xerrors.Errorf("parse passwd entry for (%s): %w", out, err) + } + if len(users) == 0 { + return ImageMetadata{}, xerrors.Errorf("no users returned for username %s", username) + } + + return ImageMetadata{ + UID: users[0].Uid, + GID: users[0].Gid, + HomeDir: users[0].HomeDir, + HasInit: initExists, + }, nil +} + +func DefaultLogImagePullFn(log buildlog.Logger) func(ImagePullEvent) error { + var ( + // Avoid spamming too frequently, the messages can come quickly + delayDur = time.Second * 2 + // We use a zero-value time.Time to start since we want to log + // the first event we get. + lastLog time.Time + ) + return func(e ImagePullEvent) error { + if e.Error != "" { + log.Errorf(e.Error) + return xerrors.Errorf("pull image: %s", e.Error) + } + + // Not enough time has transpired, return without logging. + if time.Since(lastLog) < delayDur { + return nil + } + + msg := e.Status + if e.Progress != "" { + msg = fmt.Sprintf("%s: %s", e.Status, e.Progress) + } + log.Info(msg) + lastLog = time.Now() + + return nil + } +} diff --git a/dockerutil/network.go b/dockerutil/network.go new file mode 100644 index 0000000..7c613a9 --- /dev/null +++ b/dockerutil/network.go @@ -0,0 +1,45 @@ +package dockerutil + +import ( + "net" + + "golang.org/x/xerrors" +) + +var DefaultBridgeCIDR = "172.19.0.1/30" + +func BridgeIPFromCIDR(cidr string) (net.IP, int) { + ipNet := mustParseIPv4Net(cidr) + prefixLen, _ := ipNet.Mask.Size() + bridgeIP := mustNextIPv4(ipNet.IP, 1) + return bridgeIP, prefixLen +} + +func mustParseIPv4Net(s string) *net.IPNet { + _, n, err := net.ParseCIDR(s) + if err != nil { + panic(err) + } + if n.IP.To4() == nil { + panic(xerrors.New("must specify an IPv4 network")) + } + return n +} + +// Adapted from https://gist.github.com/udhos/b468fbfd376aa0b655b6b0c539a88c03#file-nextip-go-L31 +func mustNextIPv4(ip net.IP, inc int) net.IP { + ip4 := ip.To4() + if ip4 == nil { + panic(xerrors.Errorf("invalid IPv4 addr %s", ip.String())) + } + v := uint32(ip4[0]) << 24 + v += uint32(ip4[1]) << 16 + v += uint32(ip4[2]) << 8 + v += uint32(ip4[3]) + v += uint32(inc) + v3 := byte(v & 0xFF) + v2 := byte((v >> 8) & 0xFF) + v1 := byte((v >> 16) & 0xFF) + v0 := byte((v >> 24) & 0xFF) + return net.IPv4(v0, v1, v2, v3) +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..57517bb --- /dev/null +++ b/go.mod @@ -0,0 +1,187 @@ +module github.com/coder/envbox + +go 1.20 + +// Coder runs a fork of tailscale, since we depend on that repo we need to make +// sure go.mod doesn't try to use the actual tailscale libs otherwise things +// won't compile. +replace tailscale.com => github.com/coder/tailscale v1.1.1-0.20230314023417-d9efcc0ac972 + +require ( + cdr.dev/slog v1.4.2-0.20230228204227-60d22dceaf04 + github.com/coder/coder v0.20.2-0.20230323233815-982274536509 + github.com/coder/retry v1.3.1-0.20230210155434-e90a2e1e091d + github.com/docker/docker v23.0.3+incompatible + github.com/opencontainers/image-spec v1.1.0-rc2 + github.com/ory/dockertest/v3 v3.9.1 + github.com/quasilyte/go-ruleguard/dsl v0.3.22 + github.com/spf13/afero v1.9.4 + github.com/spf13/cobra v1.6.1 + github.com/spf13/pflag v1.0.5 + github.com/stretchr/testify v1.8.2 + github.com/vishvananda/netlink v1.1.1-0.20211118161826-650dca95af54 + golang.org/x/exp v0.0.0-20230321023759-10a507213a29 + golang.org/x/net v0.8.0 + golang.org/x/sys v0.6.0 + golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 + k8s.io/mount-utils v0.26.2 + k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5 +) + +require ( + cloud.google.com/go/compute v1.18.0 // indirect + cloud.google.com/go/compute/metadata v0.2.3 // indirect + filippo.io/edwards25519 v1.0.0-rc.1 // indirect + github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect + github.com/Microsoft/go-winio v0.6.0 // indirect + github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect + github.com/OneOfOne/xxhash v1.2.8 // indirect + github.com/agext/levenshtein v1.2.3 // indirect + github.com/agnivade/levenshtein v1.1.1 // indirect + github.com/akutz/memconn v0.1.0 // indirect + github.com/alecthomas/chroma v0.10.0 // indirect + github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 // indirect + github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/cenkalti/backoff/v4 v4.2.0 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/coder/terraform-provider-coder v0.6.21 // indirect + github.com/containerd/continuity v0.3.0 // indirect + github.com/coreos/go-iptables v0.6.0 // indirect + github.com/coreos/go-oidc/v3 v3.4.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dlclark/regexp2 v1.8.1 // indirect + github.com/docker/cli v23.0.1+incompatible // indirect + github.com/docker/distribution v2.8.1+incompatible // indirect + github.com/docker/go-connections v0.4.0 // indirect + github.com/docker/go-units v0.5.0 // indirect + github.com/fatih/color v1.14.1 // indirect + github.com/fxamacker/cbor/v2 v2.4.0 // indirect + github.com/ghodss/yaml v1.0.0 // indirect + github.com/go-chi/chi/v5 v5.0.7 // indirect + github.com/go-logr/logr v1.2.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-ole/go-ole v1.2.6 // indirect + github.com/gobwas/glob v0.2.3 // indirect + github.com/godbus/dbus/v5 v5.1.0 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/glog v1.0.0 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/protobuf v1.5.2 // indirect + github.com/google/btree v1.1.2 // indirect + github.com/google/go-cmp v0.5.9 // indirect + github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect + github.com/google/uuid v1.3.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.1 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 // indirect + github.com/hashicorp/go-hclog v1.2.1 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/hashicorp/go-uuid v1.0.3 // indirect + github.com/hashicorp/go-version v1.6.0 // indirect + github.com/hashicorp/golang-lru/v2 v2.0.1 // indirect + github.com/hashicorp/hcl/v2 v2.14.0 // indirect + github.com/hashicorp/logutils v1.0.0 // indirect + github.com/hashicorp/terraform-plugin-go v0.12.0 // indirect + github.com/hashicorp/terraform-plugin-log v0.7.0 // indirect + github.com/hashicorp/terraform-plugin-sdk/v2 v2.20.0 // indirect + github.com/hashicorp/yamux v0.0.0-20220718163420-dd80a7ee44ce // indirect + github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3 // indirect + github.com/iancoleman/strcase v0.2.0 // indirect + github.com/illarion/gonotify v1.0.1 // indirect + github.com/imdario/mergo v0.3.13 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/insomniacslk/dhcp v0.0.0-20221215072855-de60144f33f8 // indirect + github.com/jmoiron/sqlx v1.3.5 // indirect + github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86 // indirect + github.com/jsimonetti/rtnetlink v1.1.2-0.20220408201609-d380b505068b // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/compress v1.15.15 // indirect + github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a // indirect + github.com/lib/pq v1.10.6 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.17 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect + github.com/mdlayher/genetlink v1.2.0 // indirect + github.com/mdlayher/netlink v1.7.1 // indirect + github.com/mdlayher/sdnotify v1.0.0 // indirect + github.com/mdlayher/socket v0.4.0 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/go-ps v1.0.0 // indirect + github.com/mitchellh/go-testing-interface v1.14.1 // indirect + github.com/mitchellh/go-wordwrap v1.0.1 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/moby/sys/mountinfo v0.6.2 // indirect + github.com/moby/term v0.0.0-20221205130635-1aeaba878587 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/open-policy-agent/opa v0.44.0 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/runc v1.1.5 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/prometheus/client_golang v1.14.0 // indirect + github.com/prometheus/client_model v0.3.0 // indirect + github.com/prometheus/common v0.40.0 // indirect + github.com/prometheus/procfs v0.9.0 // indirect + github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 // indirect + github.com/sirupsen/logrus v1.9.0 // indirect + github.com/tabbed/pqtype v0.1.1 // indirect + github.com/tailscale/certstore v0.1.1-0.20220316223106-78d6e1c49d8d // indirect + github.com/tailscale/golang-x-crypto v0.0.0-20221102133106-bc99ab8c2d17 // indirect + github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05 // indirect + github.com/tailscale/netlink v1.1.1-0.20211101221916-cabfb018fe85 // indirect + github.com/tailscale/wireguard-go v0.0.0-20221219190806-4fa124729667 // indirect + github.com/tchap/go-patricia/v2 v2.3.1 // indirect + github.com/tcnksm/go-httpstat v0.2.0 // indirect + github.com/u-root/uio v0.0.0-20221213070652-c3537552635f // indirect + github.com/valyala/fasthttp v1.44.0 // indirect + github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect + github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect + github.com/vmihailenco/msgpack/v4 v4.3.12 // indirect + github.com/vmihailenco/tagparser v0.1.1 // indirect + github.com/x448/float16 v0.8.4 // indirect + github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect + github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect + github.com/xeipuuv/gojsonschema v1.2.0 // indirect + github.com/yashtewari/glob-intersection v0.1.0 // indirect + github.com/zclconf/go-cty v1.10.0 // indirect + github.com/zeebo/errs v1.3.0 // indirect + go.nhat.io/otelsql v0.7.0 // indirect + go.opencensus.io v0.24.0 // indirect + go.opentelemetry.io/otel v1.11.1 // indirect + go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.11.1 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.11.1 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.11.1 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.11.1 // indirect + go.opentelemetry.io/otel/metric v0.33.0 // indirect + go.opentelemetry.io/otel/sdk v1.11.1 // indirect + go.opentelemetry.io/otel/trace v1.11.1 // indirect + go.opentelemetry.io/proto/otlp v0.19.0 // indirect + go4.org/mem v0.0.0-20210711025021-927187094b94 // indirect + go4.org/netipx v0.0.0-20220725152314-7e7bdc8411bf // indirect + golang.org/x/crypto v0.6.0 // indirect + golang.org/x/mod v0.9.0 // indirect + golang.org/x/oauth2 v0.5.0 // indirect + golang.org/x/sync v0.1.0 // indirect + golang.org/x/term v0.6.0 // indirect + golang.org/x/text v0.8.0 // indirect + golang.org/x/time v0.3.0 // indirect + golang.org/x/tools v0.7.0 // indirect + golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect + golang.zx2c4.com/wireguard/windows v0.5.3 // indirect + google.golang.org/appengine v1.6.7 // indirect + google.golang.org/genproto v0.0.0-20230223222841-637eb2293923 // indirect + google.golang.org/grpc v1.53.0 // indirect + google.golang.org/protobuf v1.28.2-0.20230118093459-a9481185b34d // indirect + gopkg.in/square/go-jose.v2 v2.6.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + gvisor.dev/gvisor v0.0.0-20221203005347-703fd9b7fbc0 // indirect + inet.af/peercred v0.0.0-20210906144145-0893ea02156a // indirect + k8s.io/klog/v2 v2.80.1 // indirect + nhooyr.io/websocket v1.8.7 // indirect + storj.io/drpc v0.0.33-0.20220622181519-9206537a4db7 // indirect + tailscale.com v1.38.2 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..9314308 --- /dev/null +++ b/go.sum @@ -0,0 +1,1273 @@ +cdr.dev/slog v1.4.2-0.20230228204227-60d22dceaf04 h1:d5MQ+iI2zk7t0HrHwBP9p7k2XfRsXnRclSe8Kpp3xOo= +cdr.dev/slog v1.4.2-0.20230228204227-60d22dceaf04/go.mod h1:YPVZsUbRMaLaPgme0RzlPWlC7fI7YmDj/j/kZLuvICs= +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= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= +cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= +cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= +cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= +cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= +cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= +cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= +cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= +cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= +cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= +cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= +cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= +cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc= +cloud.google.com/go v0.107.0 h1:qkj22L7bgkl6vIeZDlOY2po43Mx/TIa2Wsa7VR+PEww= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow= +cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM= +cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M= +cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s= +cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= +cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U= +cloud.google.com/go/compute v1.18.0 h1:FEigFqoDbys2cvFkZ9Fjq4gnHBP55anJ0yQyau2f9oY= +cloud.google.com/go/compute v1.18.0/go.mod h1:1X7yHxec2Ga+Ss6jPyjxRxpu2uu7PLgsOVXvgU0yacs= +cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= +cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY= +cloud.google.com/go/logging v1.6.1 h1:ZBsZK+JG+oCDT+vaxwqF2egKNRjz8soXiS6Xv79benI= +cloud.google.com/go/longrunning v0.3.0 h1:NjljC+FYPV3uh5/OwWT6pVU+doBqMg2x/rZlE+CamDs= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= +cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +filippo.io/edwards25519 v1.0.0-rc.1 h1:m0VOOB23frXZvAOK44usCgLWvtsxIoMCTBGJZlpmGfU= +filippo.io/edwards25519 v1.0.0-rc.1/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns= +filippo.io/mkcert v1.4.3 h1:axpnmtrZMM8u5Hf4N3UXxboGemMOV+Tn+e+pkHM6E3o= +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= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= +github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= +github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg= +github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE= +github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= +github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/OneOfOne/xxhash v1.2.8 h1:31czK/TI9sNkxIKfaUfGlU47BAxQ0ztGgd9vPyqimf8= +github.com/OneOfOne/xxhash v1.2.8/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= +github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls= +github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= +github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= +github.com/agnivade/levenshtein v1.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRBij0P8= +github.com/agnivade/levenshtein v1.1.1/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo= +github.com/akutz/memconn v0.1.0 h1:NawI0TORU4hcOMsMr11g7vwlCdkYeLKXBcxWu2W/P8A= +github.com/akutz/memconn v0.1.0/go.mod h1:Jo8rI7m0NieZyLI5e2CDlRdRqRRB4S7Xp77ukDjH+Fw= +github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek= +github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s= +github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 h1:Kk6a4nehpJ3UuJRqlA3JxYxBZEqCeOmATOvrbT4p9RA= +github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= +github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= +github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/apparentlymart/go-dump v0.0.0-20190214190832-042adf3cf4a0 h1:MzVXffFUye+ZcSR6opIgz9Co7WcDx6ZcY+RjfFHoA0I= +github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJEynmyUenKwP++x/+DdGV/Ec= +github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw= +github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= +github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q= +github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= +github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2 h1:7Ip0wMmLHLRJdrloDxZfhMm0xrLXZS8+COSu2bXmEQs= +github.com/aymanbagabas/go-osc52 v1.2.1 h1:q2sWUyDcozPLcLabEMd+a+7Ea2DitxZVN9hTxab9L4E= +github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bep/godartsass v0.16.0 h1:nTpenrZBQjVSjLkCw3AgnYmBB2czauTJa4BLLv448qg= +github.com/bep/golibsass v1.1.0 h1:pjtXr00IJZZaOdfryNa9wARTB3Q0BmxC3/V1KNcgyTw= +github.com/bool64/shared v0.1.5 h1:fp3eUhBsrSjNCQPcSdQqZxxh9bBwrYiZ+zOKFkM0/2E= +github.com/bytecodealliance/wasmtime-go v0.36.0 h1:B6thr7RMM9xQmouBtUqm1RpkJjuLS37m6nxX+iwsQSc= +github.com/cakturk/go-netstat v0.0.0-20200220111822-e5b49efee7a5 h1:BjkPE3785EwPhhyuFkbINB+2a1xATwk8SNDWnJiD41g= +github.com/cenkalti/backoff/v4 v4.2.0 h1:HN5dHm3WBOgndBH6E8V0q2jIYIR3s9yglV8k/+MN3u4= +github.com/cenkalti/backoff/v4 v4.2.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/charmbracelet/glamour v0.6.0 h1:wi8fse3Y7nfcabbbDuwolqTqMQPMnVPeZhDM273bISc= +github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA= +github.com/cilium/ebpf v0.8.1 h1:bLSSEbBLqGPXxls55pGr5qWZaTqcmfDJHhou7t254ao= +github.com/cilium/ebpf v0.8.1/go.mod h1:f5zLIM0FSNuAkSyLAN7X+Hy6yznlF1mNiWUMfxMtrgk= +github.com/clbanning/mxj/v2 v2.5.7 h1:7q5lvUpaPF/WOkqgIDiwjBJaznaLCCBd78pi8ZyAnE0= +github.com/cli/safeexec v1.0.0 h1:0VngyaIyqACHdcMNWfo6+KdUYnqEr2Sg+bSP1pdF+dI= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/coder/coder v0.20.2-0.20230323233815-982274536509 h1:WX1saYyaObbpVhFIT5+KDHALCqGQV8QvTTkTYZJ+YYQ= +github.com/coder/coder v0.20.2-0.20230323233815-982274536509/go.mod h1:vr6YNLhNoBvcL73cBhs4FOQiOnirlqlVxxlACFLipRg= +github.com/coder/retry v1.3.1-0.20230210155434-e90a2e1e091d h1:09JG37IgTB6n3ouX9BXdUiibGzkGGbslFuDZO9Ru9aw= +github.com/coder/retry v1.3.1-0.20230210155434-e90a2e1e091d/go.mod h1:r+1J5i/989wt6CUeNSuvFKKA9hHuKKPMxdzDbTuvwwk= +github.com/coder/tailscale v1.1.1-0.20230314023417-d9efcc0ac972 h1:193YGsJz8hc4yxqAclE36paKl+9CQ6KGLgdleIguCVE= +github.com/coder/tailscale v1.1.1-0.20230314023417-d9efcc0ac972/go.mod h1:jpg+77g19FpXL43U1VoIqoSg1K/Vh5CVxycGldQ8KhA= +github.com/coder/terraform-provider-coder v0.6.21 h1:TIH6+/VQFreT8q/CkRvpHtbIeM5cOAhuDS5Sh1Nm21Q= +github.com/coder/terraform-provider-coder v0.6.21/go.mod h1:UIfU3bYNeSzJJvHyJ30tEKjD6Z9utloI+HUM/7n94CY= +github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= +github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg= +github.com/containerd/continuity v0.3.0/go.mod h1:wJEAIwKOm/pBZuBd0JmeTvnLquTB1Ag8espWhkykbPM= +github.com/coreos/go-iptables v0.6.0 h1:is9qnZMPYjLd8LYqmm/qlE+wwEgJIkTYdhV3rfZo4jk= +github.com/coreos/go-iptables v0.6.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q= +github.com/coreos/go-oidc/v3 v3.4.0 h1:xz7elHb/LDwm/ERpwHd+5nb7wFHL32rsr6bBOgaeu6g= +github.com/coreos/go-oidc/v3 v3.4.0/go.mod h1:eHUXhZtXPQLgEaDrOVTgwbgmz1xGOkJNye6h3zkD2Pw= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +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.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= +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/dgraph-io/badger/v3 v3.2103.5 h1:ylPa6qzbjYRQMU6jokoj4wzcaweHylt//CH0AKt0akg= +github.com/dgraph-io/ristretto v0.1.0 h1:Jv3CGQHp9OjuMBSne1485aDpUkTKEcUqF+jm/LuerPI= +github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48 h1:fRzb/w+pyskVMQ+UbP35JkH8yB7MYb4q/qhBarqZE6g= +github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= +github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= +github.com/dlclark/regexp2 v1.8.1 h1:6Lcdwya6GjPUNsBct8Lg/yRPwMhABj269AAzdGSiR+0= +github.com/dlclark/regexp2 v1.8.1/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +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.3+incompatible h1:9GhVsShNWz1hO//9BNg/dpMnZW25KydO4wtVxWAIbho= +github.com/docker/docker v23.0.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +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/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/elastic/go-sysinfo v1.9.0 h1:usICqY/Nw4Mpn9f4LdtpFrKxXroJDe81GaxxUlCckIo= +github.com/elastic/go-windows v1.0.0 h1:qLURgZFkkrYyTTkvYpsZIgf83AUsdIHfvlJaqaZ7aSY= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= +github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fanliao/go-promise v0.0.0-20141029170127-1890db352a72/go.mod h1:PjfxuH4FZdUyfMdtBio2lsRr1AKEaVPwelzuHuh8Lqc= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w= +github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg= +github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= +github.com/foxcpp/go-mockdns v0.0.0-20210729171921-fb145fc6f897 h1:E52jfcE64UG42SwLmrW0QByONfGynWuzBvm86BoB9z8= +github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= +github.com/frankban/quicktest v1.14.0 h1:+cqqvzZV87b4adx/5ayVOaYZ2CrvM4ejQvUdBzPPUss= +github.com/frankban/quicktest v1.14.0/go.mod h1:NeW+ay9A/U67EYXNFA1nPE8e/tnQv/09mUdL/ijj8og= +github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa h1:RDBNVkRviHZtvDvId8XSGPu3rmpmSe+wKRcEWNgsfWU= +github.com/fxamacker/cbor/v2 v2.4.0 h1:ri0ArlOR+5XunOP8CRUowT0pSJOwhW098ZCUyskZD88= +github.com/fxamacker/cbor/v2 v2.4.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= +github.com/gin-gonic/gin v1.7.7 h1:3DoBmSbJbZAWqXJC3SLjAPfutPJJRN1U5pALB7EeTTs= +github.com/github/fakeca v0.1.0 h1:Km/MVOFvclqxPM9dZBC4+QE564nU4gz4iZ0D9pMw28I= +github.com/github/fakeca v0.1.0/go.mod h1:+bormgoGMMuamOscx7N91aOuUST7wdaJ2rNjeohylyo= +github.com/gliderlabs/ssh v0.3.4 h1:+AXBtim7MTKaLVPgvE+3mhewYRawNLTd+jEEz/wExZw= +github.com/go-chi/chi v1.5.4 h1:QHdzF2szwjqVV4wmByUnTcsbIg7UGaQ0tPF2t5GcAIs= +github.com/go-chi/chi/v5 v5.0.7 h1:rDTPXLDHGATaeHvVlLcR4Qe0zftYethFucbjVQ1PxU8= +github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/go-chi/httprate v0.7.1 h1:d5kXARdms2PREQfU4pHvq44S6hJ1hPu4OXLeBKmCKWs= +github.com/go-chi/render v1.0.1 h1:4/5tis2cKaNdnv9zFLfXzcquC9HbeZgCnxGnKrltBS8= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= +github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +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/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= +github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA= +github.com/go-openapi/spec v0.20.6 h1:ich1RQ3WDbfoeTqTAb+5EIxNmpKVJZWBNah9RAT0jIQ= +github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= +github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= +github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= +github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= +github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= +github.com/go-playground/validator/v10 v10.11.0 h1:0W+xRM511GY47Yy3bZUbJVitCNg2BOGlCyvTqsp/xIw= +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-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= +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/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0= +github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= +github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8= +github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= +github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= +github.com/gobwas/ws v1.1.0 h1:7RFti/xnNkMJnrK7D1yQ/iCIB5OrrY/54/H930kIbHA= +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/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= +github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/gohugoio/hugo v0.110.0 h1:FBBypy+UXD9BWaMReIZ+y2FSxFwlJCqoutVi7jKa90o= +github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= +github.com/golang-migrate/migrate/v4 v4.15.2 h1:vU+M05vs6jWHKDdmE1Ecwj0BznygFc4QsdRe2E/L7kc= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ= +github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +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/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +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.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +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.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +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/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= +github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= +github.com/google/flatbuffers v23.1.21+incompatible h1:bUqzx/MXCDxuS0hRJL2EfjyZL3uQrPbMocUa8zGqsTA= +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.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/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.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-github/v43 v43.0.1-0.20220414155304-00e42332e405 h1:DdHws/YnnPrSywrjNYu2lEHqYHWp/LnEx56w59esd54= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +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.1.2/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/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= +github.com/googleapis/enterprise-certificate-proxy v0.2.1 h1:RY7tHKZcRlk788d5WSo/e83gOyyy742E8GSs771ySpg= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= +github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= +github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM= +github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM= +github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c= +github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= +github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= +github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM= +github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.1 h1:I6ITHEanAwjB0FvaxmGm8pKqmCLR7QIe05ZmO4QAXMw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.1/go.mod h1:gYC+WX4YJFarA2ie73G2epzt7TBWpo9pzcBnK1g0MSw= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-checkpoint v0.5.0 h1:MFYpPZCnQqQTE18jFwSII6eUQrD/oxMFp3mlgcqk5mU= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 h1:1/D3zfFHttUKaCaGKZ/dR2roBXv0vKbSCnssIldfQdI= +github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320/go.mod h1:EiZBMaudVLy8fmjf9Npq1dq9RalhveqZG5w/yz3mHWs= +github.com/hashicorp/go-hclog v1.2.1 h1:YQsLlGDJgwhXFpucSPyVbCBviQtjlHv3jLTlp8YmtEw= +github.com/hashicorp/go-hclog v1.2.1/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-plugin v1.4.4 h1:NVdrSdFRt3SkZtNckJ6tog7gbpRrcbOjQi/rgF7JYWQ= +github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= +github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= +github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +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/golang-lru/v2 v2.0.1 h1:5pv5N1lT1fjLg2VQ5KWc7kmucp2x/kvFOnxuVTqZ6x4= +github.com/hashicorp/golang-lru/v2 v2.0.1/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/hashicorp/hc-install v0.4.1-0.20220912074615-4487b02cbcbb h1:0AmumMAu6gi5zXEyXvLKDu/HALK+rIcVBZU5XJNyjRM= +github.com/hashicorp/hcl/v2 v2.14.0 h1:jX6+Q38Ly9zaAJlAjnFVyeNSNCKKW8D0wvyg7vij5Wc= +github.com/hashicorp/hcl/v2 v2.14.0/go.mod h1:e4z5nxYlWNPdDSNYX+ph14EvWYMFm3eP0zIUqPc2jr0= +github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/terraform-exec v0.17.2 h1:EU7i3Fh7vDUI9nNRdMATCEfnm9axzTnad8zszYZ73Go= +github.com/hashicorp/terraform-json v0.14.0 h1:sh9iZ1Y8IFJLx+xQiKHGud6/TSUCM0N8e17dKDpqV7s= +github.com/hashicorp/terraform-plugin-go v0.12.0 h1:6wW9mT1dSs0Xq4LR6HXj1heQ5ovr5GxXNJwkErZzpJw= +github.com/hashicorp/terraform-plugin-go v0.12.0/go.mod h1:kwhmaWHNDvT1B3QiSJdAtrB/D4RaKSY/v3r2BuoWK4M= +github.com/hashicorp/terraform-plugin-log v0.7.0 h1:SDxJUyT8TwN4l5b5/VkiTIaQgY6R+Y2BQ0sRZftGKQs= +github.com/hashicorp/terraform-plugin-log v0.7.0/go.mod h1:p4R1jWBXRTvL4odmEkFfDdhUjHf9zcs/BCoNHAc7IK4= +github.com/hashicorp/terraform-plugin-sdk/v2 v2.20.0 h1:+KxZULPsbjpAVoP0WNj/8aVW6EqpcX5JcUcQ5wl7Da4= +github.com/hashicorp/terraform-plugin-sdk/v2 v2.20.0/go.mod h1:DwGJG3KNxIPluVk6hexvDfYR/MS/eKGpiztJoT3Bbbw= +github.com/hashicorp/terraform-registry-address v0.0.0-20220623143253-7d51757b572c h1:D8aRO6+mTqHfLsK/BC3j5OAoogv1WLRWzY1AaTo3rBg= +github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734 h1:HKLsbzeOsfXmKNpr3GiT18XAblV0BjCbzL8KQAMZGa0= +github.com/hashicorp/yamux v0.0.0-20220718163420-dd80a7ee44ce h1:7FO+LmZwiG/eDsBWo50ZeqV5PoH0gwiM1mxFajXAkas= +github.com/hashicorp/yamux v0.0.0-20220718163420-dd80a7ee44ce/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= +github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3 h1:aSVUgRRRtOrZOC1fYmY9gV0e9z/Iu+xNVSASWjsuyGU= +github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3/go.mod h1:5PC6ZNPde8bBqU/ewGZig35+UIZtw9Ytxez8/q5ZyFE= +github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Goc3h8EklBH5mspfHFxBnEoURQCGzQQH1ga9Myjvis= +github.com/iancoleman/orderedmap v0.2.0 h1:sq1N/TFpYH++aViPcaKjys3bDClUEU7s5B+z6jq8pNA= +github.com/iancoleman/strcase v0.2.0 h1:05I4QRnGpI0m37iZQRuskXh+w77mr6Z41lwQzuHLwW0= +github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/illarion/gonotify v1.0.1 h1:F1d+0Fgbq/sDWjj/r66ekjDG+IDeecQKUFH4wNwsoio= +github.com/illarion/gonotify v1.0.1/go.mod h1:zt5pmDofZpU1f8aqlK0+95eQhoEAn/d4G4B/FjVW4jE= +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.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/insomniacslk/dhcp v0.0.0-20221215072855-de60144f33f8 h1:Z72DOke2yOK0Ms4Z2LK1E1OrRJXOxSj5DllTz2FYTRg= +github.com/insomniacslk/dhcp v0.0.0-20221215072855-de60144f33f8/go.mod h1:m5WMe03WCvWcXjRnhvaAbAAXdCnu20J5P+mmH44ZzpE= +github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= +github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= +github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901 h1:rp+c0RAYOWj8l6qbCUTSiRLG/iKnW3K3/QfPPuSsBt4= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= +github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= +github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86 h1:elKwZS1OcdQ0WwEDBeqxKwb7WB62QX8bvZ/FJnVXIfk= +github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86/go.mod h1:aFAMtuldEgx/4q7iSGazk22+IcgvtiC+HIimFO9XlS8= +github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw= +github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ= +github.com/jsimonetti/rtnetlink v0.0.0-20201009170750-9c6f07d100c1/go.mod h1:hqoO/u39cqLeBLebZ8fWdE96O7FxrAsRYhnVOdgHxok= +github.com/jsimonetti/rtnetlink v0.0.0-20201110080708-d2c240429e6c/go.mod h1:huN4d1phzjhlOsNIjFsw2SVRbwIHj3fJDMEU2SDPTmg= +github.com/jsimonetti/rtnetlink v1.1.2-0.20220408201609-d380b505068b h1:Yws7RV6kZr2O7PPdT+RkbSmmOponA8i/1DuGHe8BRsM= +github.com/jsimonetti/rtnetlink v1.1.2-0.20220408201609-d380b505068b/go.mod h1:TzDCVOZKUa79z6iXbbXqhtAflVgUKaFkZ21M5tK5tzY= +github.com/json-iterator/go v1.1.9/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/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/justinas/nosurf v1.1.1 h1:92Aw44hjSK4MxJeMSyDa7jwuI9GR2J/JCQiaKvXXSlk= +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.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= +github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw= +github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4= +github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a h1:+RR6SqnTkDLWyICxS1xpjCi/3dhyV+TgZwA6Ww3KncQ= +github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a/go.mod h1:YTtCCM3ryyfiu4F7t8HQ1mxvp1UBdWM2r6Xa+nGWvDk= +github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +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/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= +github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.10.6 h1:jbk+ZieJ0D7EVGJYpL9QTz7/YW6UHbmdnZWYyK5cdBs= +github.com/lib/pq v1.10.6/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= +github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +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.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.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.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= +github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI= +github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= +github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7/go.mod h1:U6ZQobyTjI/tJyq2HG+i/dfSoFUt8/aZCM+GKtmFk/Y= +github.com/mdlayher/genetlink v1.2.0 h1:4yrIkRV5Wfk1WfpWTcoOlGmsWgQj3OtQN9ZsbrE+XtU= +github.com/mdlayher/genetlink v1.2.0/go.mod h1:ra5LDov2KrUCZJiAtEvXXZBxGMInICMXIwshlJ+qRxQ= +github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA= +github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M= +github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcKp9uZHgmY= +github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZmb2XgV7o= +github.com/mdlayher/netlink v1.6.0/go.mod h1:0o3PlBmGst1xve7wQ7j/hwpNaFaH4qCRyWCdcZk8/vA= +github.com/mdlayher/netlink v1.7.1 h1:FdUaT/e33HjEXagwELR8R3/KL1Fq5x3G5jgHLp/BTmg= +github.com/mdlayher/netlink v1.7.1/go.mod h1:nKO5CSjE/DJjVhk/TNp6vCE1ktVxEA8VEh8drhZzxsQ= +github.com/mdlayher/raw v0.0.0-20190606142536-fef19f00fc18/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg= +github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg= +github.com/mdlayher/sdnotify v1.0.0 h1:Ma9XeLVN/l0qpyx1tNeMSeTjCPH6NtuD6/N9XdTlQ3c= +github.com/mdlayher/sdnotify v1.0.0/go.mod h1:HQUmpM4XgYkhDLtd+Uad8ZFK1T9D5+pNxnXQjCeJlGE= +github.com/mdlayher/socket v0.1.1/go.mod h1:mYV5YIZAfHh4dzDVzI8x8tWLWCliuX8Mon5Awbj+qDs= +github.com/mdlayher/socket v0.4.0 h1:280wsy40IC9M9q1uPGcLBwXpcTQDtoGwVt+BNoITxIw= +github.com/mdlayher/socket v0.4.0/go.mod h1:xxFqz5GRCUN3UEOm9CZqEJsAbe1C8OwSK46NlmWuVoc= +github.com/microcosm-cc/bluemonday v1.0.21 h1:dNH3e4PSyE4vNX+KlRGHT5KrSvjeUkoNPwEORjffHJg= +github.com/miekg/dns v1.1.45 h1:g5fRIhm9nx7g8osrAvgb16QJfmyMsyOCb+J7LSv+Qzk= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= +github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= +github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= +github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= +github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= +github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= +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/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/moby/moby v20.10.23+incompatible h1:5+Q6jGL7oH89tx+ms0fGsTYEXrQ3P4vuL3i7DayMUuM= +github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU= +github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78= +github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI= +github.com/moby/term v0.0.0-20221205130635-1aeaba878587 h1:HfkjXDfhgVaN5rmueG8cL8KKeFNecRCXFhaJ2qZ5SKA= +github.com/moby/term v0.0.0-20221205130635-1aeaba878587/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +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.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/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= +github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= +github.com/muesli/termenv v0.14.0 h1:8x9NFfOe8lmIWK4pgy3IfVEy47f+ppe3tUqdPZG2Uy0= +github.com/niklasfasching/go-org v1.6.5 h1:5YAIqNTdl6lAOb7lD2AyQ1RuFGPVrAKvUexphk8PGbo= +github.com/nsf/jsondiff v0.0.0-20200515183724-f29ed568f4ce h1:RPclfga2SEJmgMmz2k+Mg7cowZ8yv4Trqw9UsJby758= +github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/open-policy-agent/opa v0.44.0 h1:sEZthsrWBqIN+ShTMJ0Hcz6a3GkYsY4FaB2S/ou2hZk= +github.com/open-policy-agent/opa v0.44.0/go.mod h1:YpJaFIk5pq89n/k72c1lVvfvR5uopdJft2tMg1CW/yU= +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-rc2 h1:2zx/Stx4Wc5pIPDvIxHXvXtQFW/7XWJGmnM7r3wg034= +github.com/opencontainers/image-spec v1.1.0-rc2/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ= +github.com/opencontainers/runc v1.1.5 h1:L44KXEpKmfWDcS02aeGm8QNTFXTo2D+8MYGDIJ/GDEs= +github.com/opencontainers/runc v1.1.5/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/ory/dockertest/v3 v3.9.1 h1:v4dkG+dlu76goxMiTT2j8zV7s4oPPEppKT8K8p2f1kY= +github.com/ory/dockertest/v3 v3.9.1/go.mod h1:42Ir9hmvaAPm0Mgibk6mBPi7SFvTXxEcnztDYOJ//uM= +github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM= +github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= +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/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= +github.com/pkg/sftp v1.13.6-0.20221018182125-7da137aa03f0 h1:QJypP3NZEUt+ka49zyp/MSdpjjM9EYkg0WA1NZQaxT0= +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/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= +github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= +github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= +github.com/prometheus/common v0.40.0 h1:Afz7EVRqGg2Mqqf4JuF9vdvp1pi220m55Pi9T2JnO4Q= +github.com/prometheus/common v0.40.0/go.mod h1:L65ZJPSmfn/UBWLQIHV7dBrKFidB/wPlF1y5TlSt9OE= +github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= +github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= +github.com/quasilyte/go-ruleguard/dsl v0.3.22 h1:wd8zkOhSNr+I+8Qeciml08ivDt1pSXe60+5DqOpCjPE= +github.com/quasilyte/go-ruleguard/dsl v0.3.22/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU= +github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 h1:MkV+77GLUNo5oJ0jf870itWm3D0Sjh7+Za9gazKc5LQ= +github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rivo/uniseg v0.4.3 h1:utMvzDsuh3suAEnhH0RdHmoPbU648o6CvXxTx4SBMOw= +github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.8.1-0.20211023094830-115ce09fd6b4 h1:Ha8xCaq6ln1a+R91Km45Oq6lPXj2Mla6CRJYcuV2h1w= +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/seccomp/libseccomp-golang v0.9.2-0.20220502022130-f33da4d89646/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= +github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +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/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.9.4 h1:Sd43wM1IWz/s1aVXdOBkjJvuP8UdyqioeE4AmM0QsBs= +github.com/spf13/afero v1.9.4/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= +github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= +github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= +github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= +github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= +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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +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.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/swaggest/assertjson v1.7.0 h1:SKw5Rn0LQs6UvmGrIdaKQbMR1R3ncXm5KNon+QJ7jtw= +github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe h1:K8pHPVoTgxFJt1lXuIzzOX7zZhZFldJQK/CgKx9BFIc= +github.com/swaggo/http-swagger v1.3.3 h1:Hu5Z0L9ssyBLofaama21iYaF2VbWyA8jdohaaCGpHsc= +github.com/swaggo/swag v1.8.6 h1:2rgOaLbonWu1PLP6G+/rYjSvPg0jQE0HtrEKuE380eg= +github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= +github.com/tabbed/pqtype v0.1.1 h1:PhEcb9JZ8jr7SUjJDFjRPxny0M8fkXZrxn/a9yQfoZg= +github.com/tabbed/pqtype v0.1.1/go.mod h1:HLt2kLJPcUhODQkYn3mJkMHXVsuv3Z2n5NZEeKXL0Uk= +github.com/tailscale/certstore v0.1.1-0.20220316223106-78d6e1c49d8d h1:K3j02b5j2Iw1xoggN9B2DIEkhWGheqFOeDkdJdBrJI8= +github.com/tailscale/certstore v0.1.1-0.20220316223106-78d6e1c49d8d/go.mod h1:2P+hpOwd53e7JMX/L4f3VXkv1G+33ES6IWZSrkIeWNs= +github.com/tailscale/golang-x-crypto v0.0.0-20221102133106-bc99ab8c2d17 h1:cSm67hIDABvL13S0n9TNoVhzYwjb24M46znbABLll18= +github.com/tailscale/golang-x-crypto v0.0.0-20221102133106-bc99ab8c2d17/go.mod h1:95n9fbUCixVSI4QXLEvdKJjnYK2eUlkTx9+QwLPXFKU= +github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05 h1:4chzWmimtJPxRs2O36yuGRW3f9SYV+bMTTvMBI0EKio= +github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05/go.mod h1:PdCqy9JzfWMJf1H5UJW2ip33/d4YkoKN0r67yKH1mG8= +github.com/tailscale/netlink v1.1.1-0.20211101221916-cabfb018fe85 h1:zrsUcqrG2uQSPhaUPjUQwozcRdDdSxxqhNgNZ3drZFk= +github.com/tailscale/netlink v1.1.1-0.20211101221916-cabfb018fe85/go.mod h1:NzVQi3Mleb+qzq8VmcWpSkcSYxXIg0DkI6XDzpVkhJ0= +github.com/tailscale/wireguard-go v0.0.0-20221219190806-4fa124729667 h1:etWp6uUwKu8NEj37K2OuMBnZ7EnVMKA7gJg5AqPFy/o= +github.com/tailscale/wireguard-go v0.0.0-20221219190806-4fa124729667/go.mod h1:iiClgxBTruKI+nmzlQxbFw6c3nB/wb4Td/WCyX2berY= +github.com/tchap/go-patricia/v2 v2.3.1 h1:6rQp39lgIYZ+MHmdEq4xzuk1t7OdC35z/xm0BGhTkes= +github.com/tchap/go-patricia/v2 v2.3.1/go.mod h1:VZRHKAb53DLaG+nA9EaYYiaEx6YztwDlLElMsnSHD4k= +github.com/tcnksm/go-httpstat v0.2.0 h1:rP7T5e5U2HfmOBmZzGgGZjBQ5/GluWUylujl0tJ04I0= +github.com/tcnksm/go-httpstat v0.2.0/go.mod h1:s3JVJFtQxtBEBC9dwcdTTXS9xFnM3SXAZwPG41aurT8= +github.com/tdewolff/parse/v2 v2.6.4 h1:KCkDvNUMof10e3QExio9OPZJT8SbdKojLBumw8YZycQ= +github.com/u-root/u-root v0.11.0 h1:6gCZLOeRyevw7gbTwMj3fKxnr9+yHFlgF3N7udUVNO8= +github.com/u-root/uio v0.0.0-20221213070652-c3537552635f h1:dpx1PHxYqAnXzbryJrWP1NQLzEjwcVgFLhkknuFQ7ww= +github.com/u-root/uio v0.0.0-20221213070652-c3537552635f/go.mod h1:IogEAUBXDEwX7oR/BMmCctShYs80ql4hF0ySdzGxf7E= +github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= +github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= +github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= +github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= +github.com/unrolled/secure v1.13.0 h1:sdr3Phw2+f8Px8HE5sd1EHdj1aV3yUwed/uZXChLFsk= +github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.44.0 h1:R+gLUhldIsfg1HokMuQjdQ5bh9nuXHPIfvkYUu9eR5Q= +github.com/valyala/fasthttp v1.44.0/go.mod h1:f6VbjjoI3z1NDOZOv17o6RvtRSWxC77seBFc2uWtgiY= +github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= +github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= +github.com/vishvananda/netlink v1.1.1-0.20211118161826-650dca95af54 h1:8mhqcHPqTMhSPoslhGYihEgSfc77+7La1P6kiB6+9So= +github.com/vishvananda/netlink v1.1.1-0.20211118161826-650dca95af54/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= +github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= +github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= +github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg= +github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= +github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= +github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI= +github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= +github.com/vmihailenco/msgpack/v4 v4.3.12 h1:07s4sz9IReOgdikxLTKNbBdqDMLsjPKXwvCazn8G65U= +github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4= +github.com/vmihailenco/tagparser v0.1.1 h1:quXMXlA39OCbd2wAdTsGDlK9RkOk6Wuw+x37wVyIuWY= +github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= +github.com/wagslane/go-password-validator v0.3.0 h1:vfxOPzGHkz5S146HDpavl0cw1DSVP061Ry2PX0/ON6I= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +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/yashtewari/glob-intersection v0.1.0 h1:6gJvMYQlTDOL3dMsPF6J0+26vwX9MB8/1q3uAdhmTrg= +github.com/yashtewari/glob-intersection v0.1.0/go.mod h1:LK7pIC3piUjovexikBbJ26Yml7g8xa5bsjfx2v1fwok= +github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA= +github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M= +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.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.5.3 h1:3HUJmBFbQW9fhQOzMgseU134xfi6hU+mjWywx5Ty+/M= +github.com/yuin/goldmark-emoji v1.0.1 h1:ctuWEyzGBwiucEqxzwe0SOYDXPAucOrE9NQC18Wa1os= +github.com/zclconf/go-cty v1.10.0 h1:mp9ZXQeIcN8kAwuqorjH+Q+njbJKjLrvB2yIh4q7U+0= +github.com/zclconf/go-cty v1.10.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= +github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= +github.com/zeebo/errs v1.3.0 h1:hmiaKqgYZzcVgRL1Vkc1Mn2914BbzB0IBxs+ebeutGs= +github.com/zeebo/errs v1.3.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= +go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1 h1:A/5uWzF44DlIgdm/PQFwfMkW0JX+cIcQi/SwLAmZP5M= +go.nhat.io/otelsql v0.7.0 h1:TBxa7dbHokzEZdo1m4ZGbgjrO822DDJVHbIKvaVRDBI= +go.nhat.io/otelsql v0.7.0/go.mod h1:eSIg4NPdvODcSUUCnypyMpi7CXDurAT0a4JvJBuPJTE= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/otel v1.11.1 h1:4WLLAmcfkmDk2ukNXJyq3/kiz/3UzCaYq6PskJsaou4= +go.opentelemetry.io/otel v1.11.1/go.mod h1:1nNhXBbWSD0nsL38H6btgnFN2k4i0sNLHNNMZMSbUGE= +go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.11.1 h1:X2GndnMCsUPh6CiY2a+frAbNsXaPLbB0soHRYhAZ5Ig= +go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.11.1/go.mod h1:i8vjiSzbiUC7wOQplijSXMYUpNM93DtlS5CbUT+C6oQ= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.11.1 h1:MEQNafcNCB0uQIti/oHgU7CZpUMYQ7qigBwMVKycHvc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.11.1/go.mod h1:19O5I2U5iys38SsmT2uDJja/300woyzE1KPIQxEUBUc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.11.1 h1:LYyG/f1W/jzAix16jbksJfMQFpOH/Ma6T639pVPMgfI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.11.1/go.mod h1:QrRRQiY3kzAoYPNLP0W/Ikg0gR6V3LMc+ODSxr7yyvg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.11.1 h1:tFl63cpAAcD9TOU6U8kZU7KyXuSRYAZlbx1C61aaB74= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.11.1/go.mod h1:X620Jww3RajCJXw/unA+8IRTgxkdS7pi+ZwK9b7KUJk= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v0.33.0 h1:hlnyYcK61UzruaUssIZvCHl72qSxGB1R55RexLKjFs8= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.11.1 h1:3Yvzs7lgOw8MmbxmLRsQGwYdCubFmUHSooKaEhQunFQ= +go.opentelemetry.io/otel/metric v0.33.0 h1:xQAyl7uGEYvrLAiV/09iTJlp1pZnQ9Wl793qbVvED1E= +go.opentelemetry.io/otel/metric v0.33.0/go.mod h1:QlTYc+EnYNq/M2mNk1qDDMRLpqCOj2f/r5c7Fd5FYaI= +go.opentelemetry.io/otel/sdk v1.11.1 h1:F7KmQgoHljhUuJyA+9BiU+EkJfyX5nVVF4wyzWZpKxs= +go.opentelemetry.io/otel/sdk v1.11.1/go.mod h1:/l3FE4SupHJ12TduVjUkZtlfFqDCQJlOlithYrdktys= +go.opentelemetry.io/otel/sdk/metric v0.33.0 h1:oTqyWfksgKoJmbrs2q7O7ahkJzt+Ipekihf8vhpa9qo= +go.opentelemetry.io/otel/trace v1.11.1 h1:ofxdnzsNrGBYXbP7t7zpUK281+go5rF7dvdIZXF8gdQ= +go.opentelemetry.io/otel/trace v1.11.1/go.mod h1:f/Q9G7vzk5u91PhbmKbg1Qn0rzH1LJ4vbPHFGkTPtOk= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.opentelemetry.io/proto/otlp v0.19.0 h1:IVN6GR+mhC4s5yfcTbmzHYODqvWAp3ZedA2SJPI1Nnw= +go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= +go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= +go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= +go4.org/intern v0.0.0-20211027215823-ae77deb06f29 h1:UXLjNohABv4S58tHmeuIZDO6e3mHpW2Dx33gaNt03LE= +go4.org/mem v0.0.0-20210711025021-927187094b94 h1:OAAkygi2Js191AJP1Ds42MhJRgeofeKGjuoUqNp1QC4= +go4.org/mem v0.0.0-20210711025021-927187094b94/go.mod h1:reUoABIJ9ikfM5sgtSF3Wushcza7+WeD01VB9Lirh3g= +go4.org/netipx v0.0.0-20220725152314-7e7bdc8411bf h1:IdwJUzqoIo5lkr2EOyKoe5qipUaEjbOKKY5+fzPBZ3A= +go4.org/netipx v0.0.0-20220725152314-7e7bdc8411bf/go.mod h1:+QXzaoURFd0rGDIjDNpyIkv+F9R7EmeKorvlKRnhqgA= +go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760 h1:FyBZqvoA/jbNzuAWLQE2kG820zMAkcilx6BMjGbL/E4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc= +golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug= +golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +golang.org/x/exp/typeparams v0.0.0-20221208152030-732eee02a75a h1:Jw5wfR+h9mnIYH+OtGT2im5wV1YGGDora5vTv/aa5bE= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +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-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/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.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs= +golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +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-20190108225652-1e06a53dbb7e/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-20190419010253-1f3472d942ba/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/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-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/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-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/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-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.0.0-20220906165146-f3363e06e74c/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +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/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= +golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.5.0 h1:HuArIo48skDwlrvM3sEdHXElYslAMsf3KwRkkW4MC4s= +golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= +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-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/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-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/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/sys v0.0.0-20180830151530-49385e6e1522/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-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190418153312-f0ce4c0180be/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-20190606122018-79a91cf218c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200217220822-9197077df867/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-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210301091718-77cc2087c03b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +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-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/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-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/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-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/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-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/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-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.4.1-0.20230131160137-e7d7f63158de/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/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.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/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.5/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.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/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.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +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-20190312151545-0bb0c0a6e846/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-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/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-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= +golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= +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/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg= +golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= +golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE= +golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= +google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= +google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= +google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= +google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= +google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= +google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= +google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= +google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= +google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g= +google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA= +google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8= +google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs= +google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= +google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw= +google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg= +google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o= +google.golang.org/api v0.108.0 h1:WVBc/faN0DkKtR43Q/7+tPny9ZoLZdIiAyG5Q9vFClg= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +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-20190418145605-e7d98fc518a7/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-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210329143202-679c6ae281ee/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= +google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= +google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= +google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220518221133-4f43b3371335/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20230223222841-637eb2293923 h1:znp6mq/drrY+6khTAlJUDNFFcDGV2ENLYKpMq8SyCds= +google.golang.org/genproto v0.0.0-20230223222841-637eb2293923/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= +google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.53.0 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc= +google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= +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.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.28.2-0.20230118093459-a9481185b34d h1:qp0AnQCvRCMlu9jBjtdbTaaEmThIgZOrbVyDEOcmKhQ= +google.golang.org/protobuf v1.28.2-0.20230118093459-a9481185b34d/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +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-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI= +gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/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.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o= +gvisor.dev/gvisor v0.0.0-20221203005347-703fd9b7fbc0 h1:Wobr37noukisGxpKo5jAsLREcpj61RxrWYzD8uwveOY= +gvisor.dev/gvisor v0.0.0-20221203005347-703fd9b7fbc0/go.mod h1:Dn5idtptoW1dIos9U6A2rpebLs/MtTwFacjKb8jLdQA= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/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= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.4.0-0.dev.0.20230130122044-c30b15588105 h1:2OzOQ+1scFmv2dt7x+wNxgikA/Rn2qKrvc/CJCVuAJM= +howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM= +inet.af/peercred v0.0.0-20210906144145-0893ea02156a h1:qdkS8Q5/i10xU2ArJMKYhVa1DORzBfYS/qA2UK2jheg= +inet.af/peercred v0.0.0-20210906144145-0893ea02156a/go.mod h1:FjawnflS/udxX+SvpsMgZfdqx2aykOlkISeAsADi5IU= +k8s.io/klog/v2 v2.80.1 h1:atnLQ121W371wYYFawwYx1aEY2eUfs4l3J72wtgAwV4= +k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/mount-utils v0.26.2 h1:KoRKqCAAK2l37l71YMvKx6vaLToh52RkNx1RU/dSLGQ= +k8s.io/mount-utils v0.26.2/go.mod h1:95yx9K6N37y8YZ0/lUh9U6ITosMODNaW0/v4wvaa0Xw= +k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5 h1:kmDqav+P+/5e1i9tFfHq1qcF3sOrDp+YEkVDAHu7Jwk= +k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g= +nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +software.sslmate.com/src/go-pkcs12 v0.2.0 h1:nlFkj7bTysH6VkC4fGphtjXRbezREPgrHuJG20hBGPE= +storj.io/drpc v0.0.33-0.20220622181519-9206537a4db7 h1:6jIp39oQGZMjfrG3kiafK2tcL0Fbprh2kvaoJNfhvuM= +storj.io/drpc v0.0.33-0.20220622181519-9206537a4db7/go.mod h1:6rcOyR/QQkSTX/9L5ZGtlZaE2PtXTTZl8d+ulSeeYEg= diff --git a/integration/doc.go b/integration/doc.go new file mode 100644 index 0000000..7f37830 --- /dev/null +++ b/integration/doc.go @@ -0,0 +1,3 @@ +// Package integration contains integration tests for envbox. They create +// real envbox containers and validate functionality. +package integration diff --git a/integration/docker_test.go b/integration/docker_test.go new file mode 100644 index 0000000..37ef0e1 --- /dev/null +++ b/integration/docker_test.go @@ -0,0 +1,258 @@ +//go:build integration +// +build integration + +package integration_test + +import ( + "fmt" + "os" + "path/filepath" + "strings" + "testing" + "time" + + dockertest "github.com/ory/dockertest/v3" + "github.com/stretchr/testify/require" + + "github.com/coder/envbox/cli" + "github.com/coder/envbox/integration/integrationtest" +) + +func TestDocker(t *testing.T) { + t.Parallel() + + // Dockerd just tests that dockerd can spin up and function correctly. + t.Run("Dockerd", func(t *testing.T) { + t.Parallel() + + pool, err := dockertest.NewPool("") + require.NoError(t, err) + + var ( + tmpdir = integrationtest.TmpDir(t) + binds = integrationtest.DefaultBinds(t, tmpdir) + ) + + runEnvbox := func() *dockertest.Resource { + // Run the envbox container. + resource := integrationtest.RunEnvbox(t, pool, &integrationtest.CreateDockerCVMConfig{ + Image: integrationtest.DockerdImage, + Username: "root", + Binds: binds, + }) + + // Wait for the inner container's docker daemon. + integrationtest.WaitForCVMDocker(t, pool, resource, time.Minute) + + // Assert that we can run docker in the inner container. + _, err = integrationtest.ExecInnerContainer(t, pool, integrationtest.ExecConfig{ + ContainerID: resource.Container.ID, + Cmd: []string{"docker", "run", integrationtest.HelloWorldImage}, + }) + + require.NoError(t, err) + return resource + } + + // Run envbox initially, this tests the initial creation of a workspace. + resource := runEnvbox() + + t.Logf("envbox %q started successfully, recreating...", resource.Container.ID) + + // Destroy the container, we're going to recreate it to ensure that when volumes are reused + // IDs are still mapped correctly. + err = resource.Close() + require.NoError(t, err) + + // Run envbox again to test that when we restart a workspace things still + // work correctly. + _ = runEnvbox() + }) + + // EnvboxArgs validates that arguments passed to envbox function correctly. + // Most cases should be covered with unit tests, the intent with this is to + // test cases that do not garner a high degree of confidence from mocking + // (such as creating devices e.g. FUSE, TUN). + t.Run("EnvboxArgs", func(t *testing.T) { + t.Parallel() + + pool, err := dockertest.NewPool("") + require.NoError(t, err) + + var ( + tmpdir = integrationtest.TmpDir(t) + binds = integrationtest.DefaultBinds(t, tmpdir) + ) + + homeDir := filepath.Join(tmpdir, "home") + err = os.MkdirAll(homeDir, 0o777) + require.NoError(t, err) + + // Emulate someone wanting to mount a secret into envbox. + secretDir := filepath.Join(tmpdir, "secrets") + err = os.MkdirAll(secretDir, 0o777) + require.NoError(t, err) + + binds = append(binds, + bindMount(homeDir, "/home/coder", false), + bindMount(secretDir, "/var/secrets", true), + ) + + var ( + envFilter = []string{ + "KUBERNETES_*", + "HELLO=world", + "TEST_ME=pls", + } + + envs = []string{ + "KUBERNETES_PORT=123", + "KUBERNETES_SERVICE_HOST=10.0.1", + "HELLO=world", + "TEST_ME=pls", + "ENVBOX_ONLY=hi", + "TEST_ME_PLS=hmm", + // Add a mount mapping to the inner container. + fmt.Sprintf("%s=%s:%s,%s:%s:ro", cli.EnvMounts, "/home/coder", "/home/coder", "/var/secrets", "/var/secrets"), + } + ) + + // We touch /home/coder/.coder/foo because it asserts that we're + // making the directory that ultimately will host the agent for Coder. + // We set this as the BINARY_DIR that we pass to the startup script + // so that we can avoid the race that occurs where systemd is remounting + // /tmp while we are downloading the agent binary /tmp/coder.XXX. + bootstrapScript := `#!/usr/bin/env bash + + echo "hello" > /home/coder/bootstrap + mkdir /home/coder/bar + touch /home/coder/.coder/foo +` + + // Run the envbox container. + resource := integrationtest.RunEnvbox(t, pool, &integrationtest.CreateDockerCVMConfig{ + Image: integrationtest.UbuntuImage, + Username: "coder", + InnerEnvFilter: envFilter, + Envs: envs, + Binds: binds, + AddFUSE: true, + AddTUN: true, + BootstrapScript: bootstrapScript, + }) + + // Validate that the envs are set correctly. + vars, err := integrationtest.ExecInnerContainer(t, pool, integrationtest.ExecConfig{ + ContainerID: resource.Container.ID, + Cmd: []string{"env"}, + }) + require.NoError(t, err) + + envVars := strings.Split(string(vars), "\n") + + requireSliceContains(t, envVars, + "KUBERNETES_PORT=123", + "KUBERNETES_SERVICE_HOST=10.0.1", + "HELLO=world", + "TEST_ME=pls", + ) + requireSliceNoContains(t, envVars, + "ENVBOX_ONLY=hi", + "TEST_ME_PLS=hmm", + ) + + // Assert that the FUSE device exists. + _, err = integrationtest.ExecInnerContainer(t, pool, integrationtest.ExecConfig{ + ContainerID: resource.Container.ID, + Cmd: []string{"stat", cli.InnerFUSEPath}, + }) + require.NoError(t, err) + + // Assert that the TUN device exists. + _, err = integrationtest.ExecInnerContainer(t, pool, integrationtest.ExecConfig{ + ContainerID: resource.Container.ID, + Cmd: []string{"stat", cli.InnerTUNPath}, + }) + require.NoError(t, err) + + // Assert that the home directory exists and has the correct shifted permissions. + homeDirUID, err := integrationtest.ExecEnvbox(t, pool, integrationtest.ExecConfig{ + ContainerID: resource.Container.ID, + Cmd: []string{"stat", `--format=%u`, "/home/coder"}, + }) + require.NoError(t, err) + require.Equal(t, "101000", strings.TrimSpace(string(homeDirUID))) + + // Validate that we can touch files in the home directory. + _, err = integrationtest.ExecInnerContainer(t, pool, integrationtest.ExecConfig{ + ContainerID: resource.Container.ID, + Cmd: []string{"touch", "/home/coder/foo"}, + User: "coder", + }) + require.NoError(t, err) + + secretsDirUID, err := integrationtest.ExecEnvbox(t, pool, integrationtest.ExecConfig{ + ContainerID: resource.Container.ID, + Cmd: []string{"stat", "--format=%u", "/var/secrets"}, + }) + require.NoError(t, err) + require.Equal(t, "100000", strings.TrimSpace(string(secretsDirUID))) + + // Validate that we cannot touch files in this case since it should be a + // read only mount. + _, err = integrationtest.ExecInnerContainer(t, pool, integrationtest.ExecConfig{ + ContainerID: resource.Container.ID, + Cmd: []string{"touch", "/var/secrets/foo"}, + }) + require.Error(t, err) + // Make sure the error is actually because of a read only filesystem + // and not some random other error. + require.Contains(t, err.Error(), "Read-only file system") + + // Validate that the bootstrap script ran. + out, err := integrationtest.ExecInnerContainer(t, pool, integrationtest.ExecConfig{ + ContainerID: resource.Container.ID, + Cmd: []string{"cat", "/home/coder/bootstrap"}, + }) + require.NoError(t, err) + require.Equal(t, "hello", strings.TrimSpace(string(out))) + + // Validate that the bootstrap script ran. + out, err = integrationtest.ExecInnerContainer(t, pool, integrationtest.ExecConfig{ + ContainerID: resource.Container.ID, + Cmd: []string{"stat", "--format=%u", "/home/coder/bar"}, + }) + require.NoError(t, err) + require.Equal(t, "1000", strings.TrimSpace(string(out))) + }) +} + +func requireSliceNoContains(t *testing.T, ss []string, els ...string) { + for _, e := range els { + for _, s := range ss { + if s == e { + t.Fatalf("unexpectedly found %q in %+v", e, ss) + } + } + } +} + +func requireSliceContains(t *testing.T, ss []string, els ...string) { + for _, e := range els { + var found bool + for _, s := range ss { + if s == e { + found = true + break + } + } + require.True(t, found, "expected to find %s in %+v", e, ss) + } +} + +func bindMount(src, dest string, ro bool) string { + if ro { + return fmt.Sprintf("%s:%s:%s", src, dest, "ro") + } + return fmt.Sprintf("%s:%s", src, dest) +} diff --git a/integration/integrationtest/docker.go b/integration/integrationtest/docker.go new file mode 100644 index 0000000..0da772e --- /dev/null +++ b/integration/integrationtest/docker.go @@ -0,0 +1,296 @@ +package integrationtest + +import ( + "bufio" + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "os" + "path/filepath" + "strings" + "testing" + "time" + + "github.com/ory/dockertest/v3" + "github.com/ory/dockertest/v3/docker" + "github.com/stretchr/testify/require" + "golang.org/x/xerrors" + + "github.com/coder/envbox/buildlog" + "github.com/coder/envbox/cli" + "github.com/coder/retry" +) + +const ( + // DockerdImage is a large image (~1GB) and should only be used to test + // dockerd. + DockerdImage = "gcr.io/coder-dev-1/sreya/enterprise-base:ubuntu" + // HelloWorldImage is useful for testing a CVM's dockerd is functioning + // correctly + HelloWorldImage = "gcr.io/coder-dev-1/sreya/hello-world" + // UbuntuImage is just vanilla ubuntu (80MB) but the user is set to a non-root + // user . + UbuntuImage = "gcr.io/coder-dev-1/sreya/ubuntu-coder" +) + +// TODO use df to determine if an environment is running in a docker container or not. + +type CreateDockerCVMConfig struct { + Image string + Username string + BootstrapScript string + InnerEnvFilter []string + Envs []string + Binds []string + Mounts []string + AddFUSE bool + AddTUN bool +} + +func (c CreateDockerCVMConfig) validate(t *testing.T) { + t.Helper() + + if c.Image == "" { + t.Fatalf("an image must be provided") + } + + if c.Username == "" { + t.Fatalf("a username must be provided") + } +} + +// RunEnvbox runs envbox, it returns once the inner container has finished +// spinning up. +func RunEnvbox(t *testing.T, pool *dockertest.Pool, conf *CreateDockerCVMConfig) *dockertest.Resource { + t.Helper() + + conf.validate(t) + + // If binds aren't passed then we'll just create the minimum amount. + // If someone is passing them we'll assume they know what they're doing. + if conf.Binds == nil { + tmpdir := TmpDir(t) + conf.Binds = DefaultBinds(t, tmpdir) + } + + conf.Envs = append(conf.Envs, cmdLineEnvs(conf)...) + + resource, err := pool.RunWithOptions(&dockertest.RunOptions{ + Repository: "envbox", + Tag: "latest", + Entrypoint: []string{"/envbox", "docker"}, + Env: conf.Envs, + }, func(host *docker.HostConfig) { + host.Binds = conf.Binds + host.Privileged = true + }) + require.NoError(t, err) + // t.Cleanup(func() { _ = pool.Purge(resource) }) + + waitForCVM(t, pool, resource) + + return resource +} + +// TmpDir returns a subdirectory in /tmp that can be used for test files. +func TmpDir(t *testing.T) string { + // We use os.MkdirTemp as oposed to t.TempDir since the envbox container will + // chown some of the created directories here to root:root causing the cleanup + // function to fail once the test exits. + tmpdir, err := os.MkdirTemp("", strings.Replace(t.Name(), "/", "_", -1)) + require.NoError(t, err) + t.Logf("using tmpdir %s", tmpdir) + return tmpdir +} + +// DefaultBinds returns the minimum amount of mounts necessary to spawn +// envbox successfully. Since envbox will chown some of these directories +// to root, they cannot be cleaned up post-test, meaning that it may be +// necesssary to manually clear /tmp from time to time. +func DefaultBinds(t *testing.T, rootDir string) []string { + t.Helper() + + // Create a bunch of mounts for the envbox container. Some proceses + // cannot run ontop of overlayfs because they also use overlayfs + // and so we need to pass vanilla ext4 filesystems for these processes + // to use. + + // Create a mount for the inner docker directory. + cntDockerDir := filepath.Join(rootDir, "coder", "docker") + err := os.MkdirAll(cntDockerDir, 0o777) + require.NoError(t, err) + + // Create a mount for the inner docker directory. + cntDir := filepath.Join(rootDir, "coder", "containers") + err = os.MkdirAll(cntDir, 0o777) + require.NoError(t, err) + + // Create a mount for envbox's docker directory. + dockerDir := filepath.Join(rootDir, "docker") + err = os.MkdirAll(dockerDir, 0o777) + require.NoError(t, err) + + // Create a mount for sysbox. + sysbox := filepath.Join(rootDir, "sysbox") + err = os.MkdirAll(sysbox, 0o777) + require.NoError(t, err) + + return []string{ + fmt.Sprintf("%s:%s", cntDockerDir, "/var/lib/coder/docker"), + fmt.Sprintf("%s:%s", cntDir, "/var/lib/coder/containers"), + "/usr/src:/usr/src", + "/lib/modules:/lib/modules", + fmt.Sprintf("%s:/var/lib/sysbox", sysbox), + fmt.Sprintf("%s:/var/lib/docker", dockerDir), + } +} + +// WaitForCVMDocker waits for the inner container docker daemon to spin up. +func WaitForCVMDocker(t *testing.T, pool *dockertest.Pool, resource *dockertest.Resource, timeout time.Duration) { + t.Helper() + + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + + for r := retry.New(time.Second, time.Second); r.Wait(ctx); { + _, err := ExecInnerContainer(t, pool, ExecConfig{ + ContainerID: resource.Container.ID, + Cmd: []string{"docker", "info"}, + }) + if err == nil { + break + } + } +} + +// waitForCVM waits for the inner container to spin up. +func waitForCVM(t *testing.T, pool *dockertest.Pool, resource *dockertest.Resource) { + t.Helper() + + rd, wr := io.Pipe() + defer rd.Close() + defer wr.Close() + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + go func() { + defer wr.Close() + err := pool.Client.Logs(docker.LogsOptions{ + Context: ctx, + Container: resource.Container.ID, + OutputStream: wr, + ErrorStream: wr, + Follow: true, + Stdout: true, + Stderr: true, + }) + if ctx.Err() == nil { + // Only check if error is nil if we didn't cancel the context. + require.NoError(t, err) + } + }() + + scanner := bufio.NewScanner(rd) + var finished bool + for scanner.Scan() { + log := scanner.Text() + + t.Log(log) + var blog buildlog.JSONLog + if err := json.Unmarshal([]byte(log), &blog); err != nil { + continue + } + + if blog.Type == buildlog.JSONLogTypeDone { + finished = true + break + } + + if blog.Type == buildlog.JSONLogTypeError { + t.Fatalf("envbox failed (%s)", blog.Output) + } + } + require.NoError(t, scanner.Err()) + require.True(t, finished, "unexpected logger exit") +} + +type ExecConfig struct { + ContainerID string + Cmd []string + User string +} + +// ExecInnerContainer runs a command in the inner container. +func ExecInnerContainer(t *testing.T, pool *dockertest.Pool, conf ExecConfig) ([]byte, error) { + t.Helper() + + conf.Cmd = append([]string{"docker", "exec", "workspace_cvm"}, conf.Cmd...) + return ExecEnvbox(t, pool, conf) +} + +// ExecEnvbox runs a command in the outer container. +func ExecEnvbox(t *testing.T, pool *dockertest.Pool, conf ExecConfig) ([]byte, error) { + t.Helper() + + exec, err := pool.Client.CreateExec(docker.CreateExecOptions{ + Cmd: conf.Cmd, + AttachStdout: true, + AttachStderr: true, + User: conf.User, + Container: conf.ContainerID, + }) + require.NoError(t, err) + + var buf bytes.Buffer + err = pool.Client.StartExec(exec.ID, docker.StartExecOptions{ + OutputStream: &buf, + ErrorStream: &buf, + }) + require.NoError(t, err) + + insp, err := pool.Client.InspectExec(exec.ID) + require.NoError(t, err) + require.Equal(t, false, insp.Running) + + if insp.ExitCode > 0 { + return nil, xerrors.Errorf("output(%s): exit code %d", buf.Bytes(), insp.ExitCode) + } + + return buf.Bytes(), nil +} + +// cmdLineEnvs returns args passed to the /envbox command +// but using their env var alias. +func cmdLineEnvs(c *CreateDockerCVMConfig) []string { + envs := []string{ + envVar(cli.EnvInnerImage, c.Image), + envVar(cli.EnvInnerUsername, c.Username), + } + + if len(c.InnerEnvFilter) > 0 { + envs = append(envs, envVar(cli.EnvInnerEnvs, strings.Join(c.InnerEnvFilter, ","))) + } + + if len(c.Mounts) > 0 { + envs = append(envs, envVar(cli.EnvMounts, strings.Join(c.Mounts, ","))) + } + + if c.AddFUSE { + envs = append(envs, envVar(cli.EnvAddFuse, "true")) + } + + if c.AddTUN { + envs = append(envs, envVar(cli.EnvAddTun, "true")) + } + + if c.BootstrapScript != "" { + envs = append(envs, envVar(cli.EnvBootstrap, c.BootstrapScript)) + } + + return envs +} + +func envVar(k, v string) string { + return fmt.Sprintf("%s=%s", k, v) +} diff --git a/integration/main_test.go b/integration/main_test.go new file mode 100644 index 0000000..b631d7f --- /dev/null +++ b/integration/main_test.go @@ -0,0 +1,36 @@ +//go:build integration +// +build integration + +package integration + +import ( + "fmt" + "os" + "os/exec" + "strings" + "testing" +) + +func TestMain(m *testing.M) { + // Build the latest version of envbox to keep in sync with any developer + // changes. + buildEnvbox() + + os.Exit(m.Run()) +} + +func buildEnvbox() { + dir, err := exec.Command("git", "rev-parse", "--show-toplevel").CombinedOutput() + mustf(err, "output (%s)", string(dir)) + + cmd := exec.Command("make", "-j", "build/image/envbox") + cmd.Dir = strings.TrimSpace(string(dir)) + out, err := cmd.CombinedOutput() + mustf(err, "make output (%s)", string(out)) +} + +func mustf(err error, msg string, args ...interface{}) { + if err != nil { + panic(fmt.Sprintf(msg+": %v", append(args, err)...)) + } +} diff --git a/scripts/check_unstaged.sh b/scripts/check_unstaged.sh new file mode 100755 index 0000000..9bab70a --- /dev/null +++ b/scripts/check_unstaged.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash + +set -euo pipefail + +# cdroot changes directory to the root of the repository. +PROJECT_ROOT="$(git rev-parse --show-toplevel)" + +cdroot() { + cd "$PROJECT_ROOT" || error "Could not change directory to '$PROJECT_ROOT'" +} + +# error prints an error message and returns an error exit code. +error() { + log "ERROR: $*" + exit 1 +} + +# log prints a message to stderr. +log() { + echo "$*" 1>&2 +} + +cdroot + +FILES="$(git ls-files --other --modified --exclude-standard)" +if [[ "$FILES" != "" ]]; then + mapfile -t files <<<"$FILES" + + log + log "The following files contain unstaged changes:" + log + for file in "${files[@]}"; do + log " - $file" + done + + log + log "These are the changes:" + log + for file in "${files[@]}"; do + git --no-pager diff "$file" 1>&2 + done + + log + error "Unstaged changes, see above for details." +fi diff --git a/scripts/rules.go b/scripts/rules.go new file mode 100644 index 0000000..1dd77a2 --- /dev/null +++ b/scripts/rules.go @@ -0,0 +1,38 @@ +// Package gorules defines custom lint rules for ruleguard. +// +// golangci-lint runs these rules via go-critic, which includes support +// for ruleguard. All Go files in this directory define lint rules +// in the Ruleguard DSL; see: +// +// - https://go-ruleguard.github.io/by-example/ +// - https://pkg.go.dev/github.com/quasilyte/go-ruleguard/dsl +// +// You run one of the following commands to execute your go rules only: +// +// golangci-lint run +// golangci-lint run --disable-all --enable=gocritic +// +// Note: don't forget to run `golangci-lint cache clean`! +package gorules + +import ( + "github.com/quasilyte/go-ruleguard/dsl" +) + +// Use xerrors everywhere! It provides additional stacktrace info! +// +//nolint:unused,deadcode,varnamelen +func xerrors(m dsl.Matcher) { + m.Import("errors") + m.Import("fmt") + m.Import("golang.org/x/xerrors") + + m.Match("fmt.Errorf($*args)"). + Suggest("xerrors.New($args)"). + Report("Use xerrors to provide additional stacktrace information!") + + m.Match("errors.$_($msg)"). + Where(m["msg"].Type.Is("string")). + Suggest("xerrors.New($msg)"). + Report("Use xerrors to provide additional stacktrace information!") +} diff --git a/slogkubeterminate/doc.go b/slogkubeterminate/doc.go new file mode 100644 index 0000000..f396688 --- /dev/null +++ b/slogkubeterminate/doc.go @@ -0,0 +1,5 @@ +// Package slogkubeterminate defines a slogger meant for use in Kubernetes +// containers to log the termination "reason" on log of slog.LevelFatal. +// +// reference: https://kubernetes.io/docs/tasks/debug-application-cluster/determine-reason-pod-failure/ +package slogkubeterminate diff --git a/slogkubeterminate/slogger.go b/slogkubeterminate/slogger.go new file mode 100644 index 0000000..045e5d0 --- /dev/null +++ b/slogkubeterminate/slogger.go @@ -0,0 +1,33 @@ +package slogkubeterminate + +import ( + "context" + "os" + + "cdr.dev/slog" +) + +const defaultKubeTerminationLog = "/dev/termination-log" + +// Make a sink that populates the given termination log on calls to .Fatal(). +func MakeCustom(log string) slog.Sink { + return slogger{log: log} +} + +// Make a sink that populates the default kube termination log on calls to .Fatal(). +func Make() slog.Sink { + return slogger{log: defaultKubeTerminationLog} +} + +type slogger struct { + log string +} + +func (s slogger) LogEntry(_ context.Context, e slog.SinkEntry) { + // write to the termination file so that the Pod failure "reason" is populated properly + if e.Level == slog.LevelFatal { + _ = os.WriteFile(s.log, []byte(e.Message), 0o600) + } +} + +func (slogger) Sync() {} diff --git a/slogkubeterminate/slogger_test.go b/slogkubeterminate/slogger_test.go new file mode 100644 index 0000000..80eee77 --- /dev/null +++ b/slogkubeterminate/slogger_test.go @@ -0,0 +1,60 @@ +package slogkubeterminate_test + +import ( + "context" + "io" + "os" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/coder/envbox/slogkubeterminate" + + "cdr.dev/slog" + "cdr.dev/slog/sloggers/sloghuman" +) + +func TestSlogKubeTerminate(t *testing.T) { + t.Parallel() + + ctx := context.Background() + + terminationLog, err := os.CreateTemp("", "slogkubeterminate-test") + require.NoError(t, err, "make temp file") + t.Cleanup(func() { _ = os.Remove(terminationLog.Name()) }) + + logger := slog.Make(sloghuman.Sink(io.Discard)) + + logger.Info(ctx, "message") + assertContent(t, terminationLog, "") + + logger.Log(ctx, slog.SinkEntry{ + Message: "whoooops", + Level: slog.LevelFatal, + }) + assertContent(t, terminationLog, "") + + logger = logger.AppendSinks(slogkubeterminate.MakeCustom(terminationLog.Name())) + const terminationReason = "whooops" + + logger.Error(ctx, "message") + assertContent(t, terminationLog, "") + + logger.Log(ctx, slog.SinkEntry{ + Message: terminationReason, + Level: slog.LevelCritical, + }) + assertContent(t, terminationLog, "") + + logger.Log(ctx, slog.SinkEntry{ + Message: terminationReason, + Level: slog.LevelFatal, + }) + assertContent(t, terminationLog, terminationReason) +} + +func assertContent(t *testing.T, f *os.File, exp string) { + content, err := io.ReadAll(f) + require.NoError(t, err, "read temp file") + require.Equal(t, exp, string(content), "temp file empty") +} diff --git a/sysboxutil/manager.go b/sysboxutil/manager.go new file mode 100644 index 0000000..61ea658 --- /dev/null +++ b/sysboxutil/manager.go @@ -0,0 +1,45 @@ +package sysboxutil + +import ( + "context" + "os" + "time" + + "github.com/coder/envbox/xunix" + "golang.org/x/xerrors" +) + +const ManagerSocketPath = "/run/sysbox/sysmgr.sock" + +// WaitForManager waits for the sysbox-mgr to startup. +func WaitForManager(ctx context.Context) error { + fs := xunix.GetFS(ctx) + + _, err := fs.Stat(ManagerSocketPath) + if err == nil { + return nil + } + + const ( + period = time.Second + ) + + t := time.NewTicker(period) + defer t.Stop() + + for { + select { + case <-ctx.Done(): + return ctx.Err() + case <-t.C: + _, err := fs.Stat(ManagerSocketPath) + if err != nil { + if !xerrors.Is(err, os.ErrNotExist) { + return xerrors.Errorf("unexpected stat err %s: %w", ManagerSocketPath, err) + } + continue + } + return nil + } + } +} diff --git a/template.tf b/template.tf new file mode 100644 index 0000000..7b3994d --- /dev/null +++ b/template.tf @@ -0,0 +1,309 @@ +terraform { + required_providers { + coder = { + source = "coder/coder" + version = "0.6.12" + } + kubernetes = { + source = "hashicorp/kubernetes" + version = "~> 2.12.1" + } + } +} + +data "coder_parameter" "home_disk" { + name = "Disk Size" + description = "How large should the disk storing the home directory be?" + icon = "https://cdn-icons-png.flaticon.com/512/2344/2344147.png" + type = "number" + default = 10 + mutable = true + validation { + min = 10 + max = 100 + } +} + +variable "use_kubeconfig" { + type = bool + sensitive = true + description = <<-EOF + Use host kubeconfig? (true/false) + + Set this to false if the Coder host is itself running as a Pod on the same + Kubernetes cluster as you are deploying workspaces to. + + Set this to true if the Coder host is running outside the Kubernetes cluster + for workspaces. A valid "~/.kube/config" must be present on the Coder host. + EOF +} + +variable "namespace" { + type = string + sensitive = true + description = "The namespace to create workspaces in (must exist prior to creating workspaces)" +} + +variable "create_tun" { + type = bool + sensitive = true + description = "Add a TUN device to the workspace." +} + +variable "create_fuse" { + type = bool + description = "Add a FUSE device to the workspace." + sensitive = true +} + +variable "max_cpus" { + type = string + sensitive = true + description = "Max number of CPUs the workspace may use (e.g. 2)." +} + +variable "min_cpus" { + type = string + sensitive = true + description = "Mininum number of CPUs the workspace may use (e.g. .1)." +} + +variable "max_memory" { + type = string + description = "Maximum amount of memory to allocate the workspace (in GB)." + sensitive = true +} + +variable "min_memory" { + type = string + description = "Minimum amount of memory to allocate the workspace (in GB)." + sensitive = true +} + +provider "kubernetes" { + # Authenticate via ~/.kube/config or a Coder-specific ServiceAccount, depending on admin preferences + config_path = var.use_kubeconfig == true ? "~/.kube/config" : null +} + +data "coder_workspace" "me" {} + +resource "coder_agent" "main" { + os = "linux" + arch = "amd64" + startup_script = < 0 { + err = p.writeSkipMessageOnce() + if err != nil { + return 0, err + } + } + + p.overwriteSuffix(b) + + return lenb, nil +} + +func (p *PrefixSuffixWriter) fillSuffix(b []byte) []byte { + if len(p.suffix) == p.N { + return b + } + + if remain := p.N - len(p.suffix); remain > 0 { + add := minInt(len(b), remain) + p.suffix = append(p.suffix, b[:add]...) + b = b[add:] + } + + return b +} + +func (p *PrefixSuffixWriter) overwriteSuffix(b []byte) { + for len(b) > 0 { + n := copy(p.suffix[p.suffixOff:], b) + b = b[n:] + p.suffixOff += n + if p.suffixOff == p.N { + p.suffixOff = 0 + } + } +} + +const skipMessage = "\nTruncating output...\n\n" + +func (p *PrefixSuffixWriter) writeSkipMessageOnce() error { + if p.skipped { + return nil + } + + p.skipped = true + + _, err := io.Copy(p.W, strings.NewReader(skipMessage)) + return err +} + +func (p *PrefixSuffixWriter) writePrefix(b []byte) (int, error) { + limit := p.N - p.prefix + if limit <= 0 { + return 0, nil + } + + if limit > len(b) { + limit = len(b) + } + + n, err := p.W.Write(b[:limit]) + p.prefix += n + return n, err +} + +func minInt(a, b int) int { + if a < b { + return a + } + return b +} diff --git a/xio/limitwriter_internal_test.go b/xio/limitwriter_internal_test.go new file mode 100644 index 0000000..f63c80a --- /dev/null +++ b/xio/limitwriter_internal_test.go @@ -0,0 +1,56 @@ +package xio + +import ( + "bytes" + "io" + "strings" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestPrefixSuffixWriter(t *testing.T) { + t.Parallel() + + type testcase struct { + Name string + Input string + ExpectedOutput string + N int + } + + testcases := []testcase{ + { + Name: "NoTruncate", + Input: "Test", + ExpectedOutput: "Test", + N: 2, + }, + { + Name: "OutputTruncated", + Input: "Testing", + ExpectedOutput: "Te" + skipMessage + "ng", + N: 2, + }, + } + + for _, test := range testcases { + test := test + t.Run(test.Name, func(t *testing.T) { + t.Parallel() + var ( + w = bytes.Buffer{} + psw = &PrefixSuffixWriter{ + W: &w, + N: test.N, + } + ) + + _, err := io.Copy(psw, strings.NewReader(test.Input)) + require.NoError(t, err, "copy") + err = psw.Flush() + require.NoError(t, err, "flush") + require.Equal(t, test.ExpectedOutput, w.String(), "unexpected output") + }) + } +} diff --git a/xio/syncwriter.go b/xio/syncwriter.go new file mode 100644 index 0000000..e73dc08 --- /dev/null +++ b/xio/syncwriter.go @@ -0,0 +1,18 @@ +package xio + +import ( + "io" + "sync" +) + +// SyncWriter synchronizes concurrent writes to an underlying writer. +type SyncWriter struct { + mu sync.Mutex + W io.Writer +} + +func (sw *SyncWriter) Write(b []byte) (int, error) { + sw.mu.Lock() + defer sw.mu.Unlock() + return sw.W.Write(b) +} diff --git a/xunix/device.go b/xunix/device.go new file mode 100644 index 0000000..e7c3332 --- /dev/null +++ b/xunix/device.go @@ -0,0 +1,128 @@ +package xunix + +import ( + "context" + "os" + "path/filepath" + + "golang.org/x/sys/unix" + "golang.org/x/xerrors" +) + +type DeviceType string + +const ( + DeviceTypeChar = "c" +) + +const ( + charDevMode = 0o666 + // The file type constant of a character-oriented device file + charFileType = unix.S_IFCHR +) + +type Device struct { + Path string + Type string + Major int64 + Minor int64 + FileMode os.FileMode + UID int32 + GID int32 +} + +func CreateTUNDevice(ctx context.Context, path string) (Device, error) { + const ( + major uint = 10 + // See https://github.com/torvalds/linux/blob/master/Documentation/admin-guide/devices.txt#L336 + minor uint = 200 + ) + + // TODO offset (from legacy.go) + err := createDevice(ctx, deviceConfig{ + path: path, + mode: charDevMode, + dev: dev(major, minor), + major: major, + minor: minor, + ftype: charFileType, + }) + if err != nil { + return Device{}, xerrors.Errorf("create device: %w", err) + } + + return Device{ + Path: path, + Type: DeviceTypeChar, + Major: int64(major), + Minor: int64(minor), + FileMode: charDevMode, + }, nil +} + +func CreateFuseDevice(ctx context.Context, path string) (Device, error) { + const ( + major uint = 10 + + // See https://github.com/torvalds/linux/blob/master/Documentation/admin-guide/devices.txt#L365 + minor uint = 229 + ) + + err := createDevice(ctx, deviceConfig{ + path: path, + mode: charDevMode, + dev: dev(major, minor), + major: major, + minor: minor, + ftype: charFileType, + }) + if err != nil { + return Device{}, xerrors.Errorf("create device: %w", err) + } + + return Device{ + Path: path, + Type: DeviceTypeChar, + Major: int64(major), + Minor: int64(minor), + FileMode: charDevMode, + }, nil +} + +type deviceConfig struct { + path string + mode uint32 + dev uint + major uint + minor uint + ftype uint32 +} + +func createDevice(ctx context.Context, conf deviceConfig) error { + var ( + fs = GetFS(ctx) + dir = filepath.Dir(conf.path) + ) + + err := fs.MkdirAll(dir, 0o700) + if err != nil { + return xerrors.Errorf("ensure parent dir: %w", err) + } + + err = fs.Mknod(conf.path, conf.ftype|conf.mode, int(conf.dev)) + if err != nil { + return xerrors.Errorf("mknod %s c %d %d: %w", conf.path, conf.major, conf.minor, err) + } + + err = fs.Chmod(conf.path, os.FileMode(conf.mode)) + if err != nil { + return xerrors.Errorf("chown %v %q: %w", conf.mode, conf.path, err) + } + + return nil +} + +func dev(major, minor uint) uint { + // This is lifted from the Linux kernel's makedev function. + return ((major & 0xfff) << 8) | (minor & 0xff) +} diff --git a/xunix/doc.go b/xunix/doc.go new file mode 100644 index 0000000..819440d --- /dev/null +++ b/xunix/doc.go @@ -0,0 +1,3 @@ +// Package xunix contains convenience functions for interacting with +// various aspects of Linux. +package xunix diff --git a/xunix/env.go b/xunix/env.go new file mode 100644 index 0000000..beba73e --- /dev/null +++ b/xunix/env.go @@ -0,0 +1,42 @@ +package xunix + +import ( + "context" + "fmt" + "os" +) + +type environKey struct{} + +type EnvironFn func() []string + +func WithEnvironFn(ctx context.Context, fn EnvironFn) context.Context { + return context.WithValue(ctx, environKey{}, fn) +} + +func Environ(ctx context.Context) []string { + fn := ctx.Value(environKey{}) + if fn == nil { + return os.Environ() + } + + //nolint we should panic if this isn't the case. + return fn.(EnvironFn)() +} + +type Env struct { + Name string + Value string +} + +func (e Env) String() string { + return fmt.Sprintf("%s=%s", e.Name, e.Value) +} + +func MustLookupEnv(e string) string { + env, ok := os.LookupEnv(e) + if !ok { + panic(fmt.Sprintf("%q env var not found", e)) + } + return env +} diff --git a/xunix/env_test.go b/xunix/env_test.go new file mode 100644 index 0000000..0592c2e --- /dev/null +++ b/xunix/env_test.go @@ -0,0 +1,39 @@ +package xunix_test + +import ( + "os" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/coder/envbox/xunix" +) + +func TestMustLookupEnv(t *testing.T) { + t.Parallel() + + t.Run("OK", func(t *testing.T) { + t.Parallel() + + const ( + key = "MY_ENV" + value = "value" + ) + + //nolint can't use t.SetEnv in parallel tests. + os.Setenv(key, value) + + val := xunix.MustLookupEnv(key) + require.Equal(t, value, val) + }) + + t.Run("Panic", func(t *testing.T) { + t.Parallel() + + defer func() { + e := recover() + require.NotNil(t, e, "function should panic") + }() + _ = xunix.MustLookupEnv("ASDasdf") + }) +} diff --git a/xunix/error.go b/xunix/error.go new file mode 100644 index 0000000..340fab7 --- /dev/null +++ b/xunix/error.go @@ -0,0 +1,11 @@ +package xunix + +import "strings" + +func IsNoSpaceErr(err error) bool { + if err == nil { + return false + } + + return strings.Contains(strings.ToLower(err.Error()), "no space left on device") +} diff --git a/xunix/exec.go b/xunix/exec.go new file mode 100644 index 0000000..666a3ea --- /dev/null +++ b/xunix/exec.go @@ -0,0 +1,175 @@ +package xunix + +import ( + "context" + "errors" + "io" + "io/fs" + "os" + osexec "os/exec" + "syscall" + "time" + + utilexec "k8s.io/utils/exec" +) + +type execerKey struct{} + +func WithExecer(ctx context.Context, execer Execer) context.Context { + return context.WithValue(ctx, execerKey{}, execer) +} + +func GetExecer(ctx context.Context) Execer { + execer := ctx.Value(execerKey{}) + if execer == nil { + // This is a typical os/exec implementation. + return &executor{} + } + + //nolint we should panic if this isn't the case. + return execer.(Execer) +} + +// The code henceforth is copied straight and modified slightly from +// "k8s.io/utils/exec". Their interface doesn't allow for a reference to +// exec.Cmd.Process which we use to kill and wait for a process to exit. + +type Execer interface { + // CommandContext returns a Cmd instance which can be used to run a single command. + // + // The provided context is used to kill the process if the context becomes done + // before the command completes on its own. For example, a timeout can be set in + // the context. + CommandContext(ctx context.Context, cmd string, args ...string) Cmd +} + +type Cmd interface { + utilexec.Cmd + OSProcess() *os.Process +} + +type executor struct{} + +// CommandContext is part of the Interface interface. +func (*executor) CommandContext(ctx context.Context, cmd string, args ...string) Cmd { + return (*cmdWrapper)(maskErrDotCmd(osexec.CommandContext(ctx, cmd, args...))) +} + +// Wraps exec.Cmd so we can capture errors. +type cmdWrapper osexec.Cmd + +var _ Cmd = &cmdWrapper{} + +func (cmd *cmdWrapper) SetDir(dir string) { + cmd.Dir = dir +} + +func (cmd *cmdWrapper) SetStdin(in io.Reader) { + cmd.Stdin = in +} + +func (cmd *cmdWrapper) SetStdout(out io.Writer) { + cmd.Stdout = out +} + +func (cmd *cmdWrapper) SetStderr(out io.Writer) { + cmd.Stderr = out +} + +func (cmd *cmdWrapper) SetEnv(env []string) { + cmd.Env = env +} + +func (cmd *cmdWrapper) StdoutPipe() (io.ReadCloser, error) { + r, err := (*osexec.Cmd)(cmd).StdoutPipe() + return r, handleError(err) +} + +func (cmd *cmdWrapper) StderrPipe() (io.ReadCloser, error) { + r, err := (*osexec.Cmd)(cmd).StderrPipe() + return r, handleError(err) +} + +func (cmd *cmdWrapper) Start() error { + err := (*osexec.Cmd)(cmd).Start() + return handleError(err) +} + +func (cmd *cmdWrapper) Wait() error { + err := (*osexec.Cmd)(cmd).Wait() + return handleError(err) +} + +// Run is part of the Cmd interface. +func (cmd *cmdWrapper) Run() error { + err := (*osexec.Cmd)(cmd).Run() + return handleError(err) +} + +// CombinedOutput is part of the Cmd interface. +func (cmd *cmdWrapper) CombinedOutput() ([]byte, error) { + out, err := (*osexec.Cmd)(cmd).CombinedOutput() + return out, handleError(err) +} + +func (cmd *cmdWrapper) Output() ([]byte, error) { + out, err := (*osexec.Cmd)(cmd).Output() + return out, handleError(err) +} + +func (cmd *cmdWrapper) OSProcess() *os.Process { + return (*osexec.Cmd)(cmd).Process +} + +// Stop is part of the Cmd interface. +func (cmd *cmdWrapper) Stop() { + c := (*osexec.Cmd)(cmd) + + if c.Process == nil { + return + } + + _ = c.Process.Signal(syscall.SIGTERM) + + time.AfterFunc(10*time.Second, func() { + if !c.ProcessState.Exited() { + _ = c.Process.Signal(syscall.SIGKILL) + } + }) +} + +func handleError(err error) error { + if err == nil { + return nil + } + + //nolint copied code from k8s. + switch e := err.(type) { + case *osexec.ExitError: + return &utilexec.ExitErrorWrapper{ExitError: e} + case *fs.PathError: + return utilexec.ErrExecutableNotFound + // nolint copied code from k8s + case *osexec.Error: + if e.Err == osexec.ErrNotFound { + return utilexec.ErrExecutableNotFound + } + } + + return err +} + +// maskErrDotCmd reverts the behavior of osexec.Cmd to what it was before go1.19 +// specifically set the Err field to nil (LookPath returns a new error when the file +// is resolved to the current directory. +func maskErrDotCmd(cmd *osexec.Cmd) *osexec.Cmd { + cmd.Err = maskErrDot(cmd.Err) + return cmd +} + +func maskErrDot(err error) error { + if err != nil && errors.Is(err, osexec.ErrDot) { + return nil + } + return err +} diff --git a/xunix/fs.go b/xunix/fs.go new file mode 100644 index 0000000..e1dd3c6 --- /dev/null +++ b/xunix/fs.go @@ -0,0 +1,49 @@ +package xunix + +import ( + "context" + "io/fs" + "os" + + "github.com/spf13/afero" + "golang.org/x/sys/unix" +) + +type FS interface { + afero.Fs + Mknod(path string, mode uint32, dev int) error + LStat(path string) (fs.FileInfo, error) + Readlink(path string) (string, error) +} + +type fsKey struct{} + +func WithFS(ctx context.Context, f FS) context.Context { + return context.WithValue(ctx, fsKey{}, f) +} + +func GetFS(ctx context.Context) FS { + f := ctx.Value(fsKey{}) + if f == nil { + return &osFS{&afero.OsFs{}} + } + + //nolint we should panic if this isn't the case. + return f.(FS) +} + +type osFS struct { + *afero.OsFs +} + +func (*osFS) Mknod(path string, mode uint32, dev int) error { + return unix.Mknod(path, mode, dev) +} + +func (*osFS) LStat(path string) (fs.FileInfo, error) { + return os.Lstat(path) +} + +func (*osFS) Readlink(path string) (string, error) { + return os.Readlink(path) +} diff --git a/xunix/gpu.go b/xunix/gpu.go new file mode 100644 index 0000000..e678699 --- /dev/null +++ b/xunix/gpu.go @@ -0,0 +1,212 @@ +package xunix + +import ( + "context" + "io/fs" + "os" + "path/filepath" + "regexp" + "sort" + "strings" + + "github.com/spf13/afero" + "golang.org/x/xerrors" + mount "k8s.io/mount-utils" + + "cdr.dev/slog" +) + +var ( + gpuMountRegex = regexp.MustCompile("(?i)(nvidia|vulkan|cuda)") + gpuExtraRegex = regexp.MustCompile("(?i)(libgl|nvidia|vulkan|cuda)") + gpuEnvRegex = regexp.MustCompile("(?i)nvidia") +) + +func GPUEnvs(ctx context.Context) []string { + envs := Environ(ctx) + + gpus := []string{} + for _, env := range envs { + name := strings.Split(env, "=")[0] + if gpuEnvRegex.MatchString(name) { + gpus = append(gpus, env) + } + } + + return gpus +} + +func GPUs(ctx context.Context, log slog.Logger, usrLibDir string) ([]Device, []mount.MountPoint, error) { + var ( + mounter = Mounter(ctx) + devices = []Device{} + binds = []mount.MountPoint{} + ) + + mounts, err := mounter.List() + if err != nil { + return nil, nil, xerrors.Errorf("list mounts: %w", err) + } + + for _, m := range mounts { + if gpuMountRegex.MatchString(m.Path) { + // If we find the GPU in /dev treat it as a device. + if strings.HasPrefix(m.Path, "/dev/") { + // TODO(JonA): We could populate the rest of the fields but it + // doesn't seem like we need them. We'll have to expand + // the FS interface to allow for a real unix stat. + devices = append(devices, Device{ + Path: m.Path, + }) + continue + } + + // If it's not in /dev treat it as a bind mount. + binds = append(binds, m) + } + } + + extraGPUS, err := usrLibGPUs(ctx, log, usrLibDir) + if err != nil { + return nil, nil, xerrors.Errorf("find %q gpus: %w", usrLibDir, err) + } + + for _, gpu := range extraGPUS { + var duplicate bool + for _, bind := range binds { + if gpu.Path == bind.Path { + duplicate = true + break + } + } + if !duplicate { + binds = append(binds, gpu) + } + } + + return devices, binds, nil +} + +func usrLibGPUs(ctx context.Context, log slog.Logger, usrLibDir string) ([]mount.MountPoint, error) { + var ( + afs = GetFS(ctx) + binds = []string{} + ) + + err := afero.Walk(afs, usrLibDir, + func(path string, info fs.FileInfo, err error) error { + if path == usrLibDir && err != nil { + return xerrors.Errorf("stat /usr/lib mountpoint %q: %w", usrLibDir, err) + } + if err != nil { + log.Error(ctx, "list directory", slog.F("dir", path), slog.Error(err)) + return nil + } + + if filepath.Ext(path) != ".so" || !gpuExtraRegex.MatchString(path) { + return nil + } + + paths, err := recursiveSymlinks(afs, usrLibDir, path) + if err != nil { + log.Error(ctx, "find recursive symlinks", slog.F("path", path), slog.Error(err)) + } + + binds = append(binds, paths...) + + return nil + }) + if err != nil { + return nil, xerrors.Errorf("walk %q for GPU drivers: %w", usrLibDir, err) + } + + mounts := make([]mount.MountPoint, 0, len(binds)) + for _, bind := range binds { + mounts = append(mounts, mount.MountPoint{ + Path: bind, + Opts: []string{"ro"}, + }) + } + + return mounts, nil +} + +// recursiveSymlinks returns all of the paths in the chain of symlinks starting +// at `path`. If `path` isn't a symlink, only `path` is returned. If at any +// point the symlink chain goes outside of `mountpoint` then nil is returned. +// Despite its name it's interestingly enough not implemented recursively. +func recursiveSymlinks(afs FS, mountpoint string, path string) ([]string, error) { + if !strings.HasSuffix(mountpoint, "/") { + mountpoint += "/" + } + + paths := []string{} + for { + if !strings.HasPrefix(path, mountpoint) { + return nil, nil + } + + stat, err := afs.LStat(path) + if err != nil { + return nil, xerrors.Errorf("lstat %q: %w", path, err) + } + + paths = append(paths, path) + if stat.Mode()&os.ModeSymlink == 0 { + break + } + + newPath, err := afs.Readlink(path) + if err != nil { + return nil, xerrors.Errorf("readlink %q: %w", path, err) + } + if newPath == "" { + break + } + + if filepath.IsAbs(newPath) { + path = newPath + } else { + dir := filepath.Dir(path) + path = filepath.Join(dir, newPath) + } + } + + return paths, nil +} + +// TryUnmountProcGPUDrivers unmounts any GPU-related mounts under /proc as it causes +// issues when creating any container in some cases. Errors encountered while +// unmounting are treated as non-fatal. +func TryUnmountProcGPUDrivers(ctx context.Context, log slog.Logger) ([]mount.MountPoint, error) { + mounter := Mounter(ctx) + + mounts, err := mounter.List() + if err != nil { + return nil, xerrors.Errorf("list mounts: %w", err) + } + + // Sort mounts list by longest paths (by segments) first. + sort.Slice(mounts, func(i, j int) bool { + // Sort paths with more slashes as "less". + return strings.Count(mounts[i].Path, "/") > strings.Count(mounts[j].Path, "/") + }) + + drivers := []mount.MountPoint{} + for _, m := range mounts { + if strings.HasPrefix(m.Path, "/proc/") && gpuMountRegex.MatchString(m.Path) { + err := mounter.Unmount(m.Path) + if err != nil { + log.Warn(ctx, + "umount potentially problematic mount", + slog.F("path", m.Path), + slog.Error(err), + ) + continue + } + drivers = append(drivers, m) + } + } + + return drivers, nil +} diff --git a/xunix/gpu_test.go b/xunix/gpu_test.go new file mode 100644 index 0000000..4cbf5f0 --- /dev/null +++ b/xunix/gpu_test.go @@ -0,0 +1,114 @@ +package xunix_test + +import ( + "context" + "path/filepath" + "testing" + + "github.com/spf13/afero" + "github.com/stretchr/testify/require" + "k8s.io/mount-utils" + + "cdr.dev/slog/sloggers/slogtest" + + "github.com/coder/envbox/xunix" + "github.com/coder/envbox/xunix/xunixfake" +) + +func TestGPUEnvs(t *testing.T) { + t.Parallel() + + t.Run("OK", func(t *testing.T) { + t.Parallel() + + ctx := xunix.WithEnvironFn(context.Background(), func() []string { + return []string{ + "NVIDIA_TEST=1", + "VULKAN_TEST=1", + "LIBGL_TEST=1", + "TEST_NVIDIA=1", + "nvidia_test=1", + } + }) + + envs := xunix.GPUEnvs(ctx) + require.Contains(t, envs, "NVIDIA_TEST=1") + require.Contains(t, envs, "TEST_NVIDIA=1") + require.Contains(t, envs, "nvidia_test=1") + require.NotContains(t, envs, "VULKAN_TEST=1") + require.NotContains(t, envs, "LIBGL_TEST=1") + }) +} + +func TestGPUs(t *testing.T) { + t.Parallel() + + t.Run("OK", func(t *testing.T) { + t.Parallel() + + var ( + fs = &xunixfake.MemFS{MemMapFs: &afero.MemMapFs{}} + mounter = &mount.FakeMounter{} + log = slogtest.Make(t, nil) + usrLibMountpoint = "/var/coder/usr/lib" + // expectedUsrLibFiles are files that we expect to be returned bind mounts + // for. + expectedUsrLibFiles = []string{ + filepath.Join(usrLibMountpoint, "nvidia", "libglxserver_nvidia.so"), + filepath.Join(usrLibMountpoint, "libnvidia-ml.so"), + } + + // fakeUsrLibFiles are files that should be written to the "mounted" + // /usr/lib directory. It includes files that shouldn't be returned. + fakeUsrLibFiles = append([]string{ + filepath.Join(usrLibMountpoint, "libcurl-gnutls.so"), + filepath.Join(usrLibMountpoint, "nvidia", "libglxserver_nvidia.so.1"), + }, expectedUsrLibFiles...) + ) + + ctx := xunix.WithFS(context.Background(), fs) + ctx = xunix.WithMounter(ctx, mounter) + + mounter.MountPoints = []mount.MountPoint{ + { + Device: "/dev/sda1", + Path: "/usr/local/nvidia", + Opts: []string{"ro"}, + }, + { + Device: "/dev/sda2", + Path: "/etc/hosts", + }, + { + Path: "/dev/nvidia0", + }, + { + Path: "/dev/nvidia1", + }, + } + + err := fs.MkdirAll(filepath.Join(usrLibMountpoint, "nvidia"), 0o755) + require.NoError(t, err) + + for _, file := range fakeUsrLibFiles { + _, err = fs.Create(file) + require.NoError(t, err) + } + + devices, binds, err := xunix.GPUs(ctx, log, usrLibMountpoint) + require.NoError(t, err) + require.Len(t, devices, 2, "unexpected 2 nvidia devices") + require.Len(t, binds, 3, "expected 4 nvidia binds") + require.Contains(t, binds, mount.MountPoint{ + Device: "/dev/sda1", + Path: "/usr/local/nvidia", + Opts: []string{"ro"}, + }) + for _, file := range expectedUsrLibFiles { + require.Contains(t, binds, mount.MountPoint{ + Path: file, + Opts: []string{"ro"}, + }) + } + }) +} diff --git a/xunix/mount.go b/xunix/mount.go new file mode 100644 index 0000000..84ce288 --- /dev/null +++ b/xunix/mount.go @@ -0,0 +1,34 @@ +package xunix + +import ( + "context" + + mount "k8s.io/mount-utils" +) + +type mounterKey struct{} + +func WithMounter(ctx context.Context, i mount.Interface) context.Context { + return context.WithValue(ctx, mounterKey{}, i) +} + +func Mounter(ctx context.Context) mount.Interface { + m := ctx.Value(mounterKey{}) + if m == nil { + return mount.New("/bin/mount") + } + + //nolint we should panic if this isn't the case. + return m.(mount.Interface) +} + +type Mount struct { + Source string + Mountpoint string + ReadOnly bool +} + +func MountFS(ctx context.Context, source, mountpoint, fstype string, options ...string) error { + return Mounter(ctx). + Mount(source, mountpoint, fstype, options) +} diff --git a/xunix/net.go b/xunix/net.go new file mode 100644 index 0000000..c228c70 --- /dev/null +++ b/xunix/net.go @@ -0,0 +1,15 @@ +package xunix + +import ( + "github.com/vishvananda/netlink" + "golang.org/x/xerrors" +) + +func NetlinkMTU(name string) (int, error) { + defaultLink, err := netlink.LinkByName(name) + if err != nil { + return 0, xerrors.Errorf("get %s: %w", name, err) + } + + return defaultLink.Attrs().MTU, nil +} diff --git a/xunix/proc.go b/xunix/proc.go new file mode 100644 index 0000000..ffafa33 --- /dev/null +++ b/xunix/proc.go @@ -0,0 +1,23 @@ +package xunix + +import ( + "context" + "fmt" + + "github.com/spf13/afero" + "golang.org/x/xerrors" +) + +func SetOOMScore(ctx context.Context, pid, score string) error { + var ( + fs = GetFS(ctx) + file = fmt.Sprintf("/proc/%v/oom_score_adj", pid) + ) + + err := afero.WriteFile(fs, file, []byte(score), 0o644) + if err != nil { + return xerrors.Errorf("write file: %w", err) + } + + return nil +} diff --git a/xunix/proc_test.go b/xunix/proc_test.go new file mode 100644 index 0000000..0f4a3ec --- /dev/null +++ b/xunix/proc_test.go @@ -0,0 +1,38 @@ +package xunix_test + +import ( + "context" + "fmt" + "testing" + + "github.com/spf13/afero" + "github.com/stretchr/testify/require" + + "github.com/coder/envbox/xunix" + "github.com/coder/envbox/xunix/xunixfake" +) + +func TestSetOOMScore(t *testing.T) { + t.Parallel() + + t.Run("OK", func(t *testing.T) { + t.Parallel() + + var ( + fs = &xunixfake.MemFS{MemMapFs: &afero.MemMapFs{}} + ctx = xunix.WithFS(context.Background(), fs) + ) + + const ( + pid = "123" + score = "-1000" + ) + + err := xunix.SetOOMScore(ctx, pid, score) + require.NoError(t, err) + + actualScore, err := afero.ReadFile(fs, fmt.Sprintf("/proc/%s/oom_score_adj", pid)) + require.NoError(t, err) + require.Equal(t, score, string(actualScore)) + }) +} diff --git a/xunix/sys.go b/xunix/sys.go new file mode 100644 index 0000000..d95f508 --- /dev/null +++ b/xunix/sys.go @@ -0,0 +1,48 @@ +package xunix + +import ( + "bytes" + "context" + "strconv" + + "github.com/spf13/afero" + "golang.org/x/xerrors" +) + +type CPUQuota struct { + Quota int + Period int +} + +const ( + CPUPeriodPath = "/sys/fs/cgroup/cpu,cpuacct/cpu.cfs_period_us" + CPUQuotaPath = "/sys/fs/cgroup/cpu,cpuacct/cpu.cfs_quota_us" +) + +func ReadCPUQuota(ctx context.Context) (CPUQuota, error) { + fs := GetFS(ctx) + periodStr, err := afero.ReadFile(fs, "/sys/fs/cgroup/cpu,cpuacct/cpu.cfs_period_us") + if err != nil { + return CPUQuota{}, xerrors.Errorf("read cpu.cfs_period_us outside container: %w", err) + } + + quotaStr, err := afero.ReadFile(fs, "/sys/fs/cgroup/cpu,cpuacct/cpu.cfs_quota_us") + if err != nil { + return CPUQuota{}, xerrors.Errorf("read cpu.cfs_quota_us outside container: %w", err) + } + + period, err := strconv.Atoi(string(bytes.TrimSpace(periodStr))) + if err != nil { + return CPUQuota{}, xerrors.Errorf("period %s not an int: %w", periodStr, err) + } + + quota, err := strconv.Atoi(string(bytes.TrimSpace(quotaStr))) + if err != nil { + return CPUQuota{}, xerrors.Errorf("quota %s not an int: %w", quotaStr, err) + } + + return CPUQuota{ + Quota: quota, + Period: period, + }, nil +} diff --git a/xunix/sys_test.go b/xunix/sys_test.go new file mode 100644 index 0000000..198d8a1 --- /dev/null +++ b/xunix/sys_test.go @@ -0,0 +1,45 @@ +package xunix_test + +import ( + "context" + "strconv" + "testing" + + "github.com/spf13/afero" + "github.com/stretchr/testify/require" + + "github.com/coder/envbox/xunix" + "github.com/coder/envbox/xunix/xunixfake" +) + +func TestReadCPUQuota(t *testing.T) { + t.Parallel() + + t.Run("OK", func(t *testing.T) { + t.Parallel() + + var ( + fs = &xunixfake.MemFS{MemMapFs: &afero.MemMapFs{}} + ctx = xunix.WithFS(context.Background(), fs) + ) + + const ( + period = 1234 + quota = 5678 + periodPath = "/sys/fs/cgroup/cpu,cpuacct/cpu.cfs_period_us" + quotaPath = "/sys/fs/cgroup/cpu,cpuacct/cpu.cfs_quota_us" + ) + + err := afero.WriteFile(fs, periodPath, []byte(strconv.Itoa(period)), 0o644) + require.NoError(t, err) + + err = afero.WriteFile(fs, quotaPath, []byte(strconv.Itoa(quota)), 0o644) + require.NoError(t, err) + + cpuQuota, err := xunix.ReadCPUQuota(ctx) + require.NoError(t, err) + + require.Equal(t, period, cpuQuota.Period) + require.Equal(t, quota, cpuQuota.Quota) + }) +} diff --git a/xunix/user.go b/xunix/user.go new file mode 100644 index 0000000..f363767 --- /dev/null +++ b/xunix/user.go @@ -0,0 +1,60 @@ +package xunix + +import ( + "bufio" + "io" + "os/user" + "strings" + + "golang.org/x/xerrors" +) + +// User is a linux user from /etc/passwd. +// It is basically a user.User with addition of the +// user's shell. +type User struct { + user.User + Shell string +} + +// ParsePasswd parses user entries from an /etc/passwd. +func ParsePasswd(r io.Reader) ([]*User, error) { + var ( + scanner = bufio.NewScanner(r) + users = make([]*User, 0) + ) + + for scanner.Scan() { + usr, err := parsePasswdEntry(scanner.Text()) + if err != nil { + return nil, xerrors.Errorf("failed to parse user entry: %w", err) + } + + users = append(users, usr) + } + + err := scanner.Err() + if err != nil { + return nil, xerrors.Errorf("failed to parse passwd: %w", err) + } + + return users, nil +} + +func parsePasswdEntry(entry string) (*User, error) { + entry = strings.TrimSpace(entry) + fields := strings.Split(entry, ":") + if len(fields) < 7 { + return nil, xerrors.Errorf("user info (%s) contained an unexpected number of fields", fields) + } + + return &User{ + User: user.User{ + Username: fields[0], + Uid: fields[2], + Gid: fields[3], + HomeDir: fields[5], + }, + Shell: fields[6], + }, nil +} diff --git a/xunix/xunixfake/exec.go b/xunix/xunixfake/exec.go new file mode 100644 index 0000000..c379439 --- /dev/null +++ b/xunix/xunixfake/exec.go @@ -0,0 +1,68 @@ +package xunixfake + +import ( + "context" + "os" + "strings" + "testing" + + "github.com/stretchr/testify/require" + testexec "k8s.io/utils/exec/testing" + + "github.com/coder/envbox/xunix" +) + +var _ xunix.Execer = &FakeExec{} + +type FakeExec struct { + Commands map[string]*FakeCmd + DefaultFakeCmd *FakeCmd +} + +func cmdKey(cmd string, args ...string) string { + return cmd + " " + strings.Join(args, " ") +} + +func (f *FakeExec) CommandContext(_ context.Context, cmd string, args ...string) xunix.Cmd { + // TODO: This isn't a great key because you may have multiple of the same commands + // but desire different output. + if c, ok := f.Commands[cmdKey(cmd, args...)]; ok { + c.Called = true + return c + } + return f.DefaultFakeCmd +} + +func (f *FakeExec) AddCommands(cmds ...*FakeCmd) { + for _, cmd := range cmds { + key := cmdKey(cmd.Argv[0], cmd.Argv[1:]...) + f.Commands[key] = cmd + } +} + +func (f *FakeExec) AssertCommandsCalled(t *testing.T) { + t.Helper() + for k, cmd := range f.Commands { + require.True(t, cmd.Called, "%q not called", k) + } +} + +var _ xunix.Cmd = &FakeCmd{} + +type FakeCmd struct { + *testexec.FakeCmd + FakeProcess *os.Process + WaitFn func() error + Called bool +} + +func (f *FakeCmd) Wait() error { + if f.WaitFn == nil { + return nil + } + return f.WaitFn() +} + +func (f *FakeCmd) OSProcess() *os.Process { + return f.FakeProcess +} diff --git a/xunix/xunixfake/fs.go b/xunix/xunixfake/fs.go new file mode 100644 index 0000000..36eb2ad --- /dev/null +++ b/xunix/xunixfake/fs.go @@ -0,0 +1,53 @@ +package xunixfake + +import ( + "io/fs" + "os" + "strconv" + + "github.com/spf13/afero" + "golang.org/x/xerrors" +) + +type FileOwner struct { + UID int + GID int +} + +type MemFS struct { + *afero.MemMapFs + Owner map[string]FileOwner +} + +func (m *MemFS) Mknod(path string, mode uint32, dev int) error { + return afero.WriteFile(m.MemMapFs, path, []byte(strconv.Itoa(dev)), os.FileMode(mode)) +} + +// This is so annoying... +func (m *MemFS) Chown(path string, uid int, gid int) error { + err := m.MemMapFs.Chown(path, uid, gid) + if err != nil { + return xerrors.Errorf("chown: %w", err) + } + m.Owner[path] = FileOwner{ + UID: uid, + GID: gid, + } + return nil +} + +func (m *MemFS) GetFileOwner(path string) (FileOwner, bool) { + owner, ok := m.Owner[path] + return owner, ok +} + +// LStat doesn't follow symbolic links since this is a in-mem fake. +func (m *MemFS) LStat(path string) (fs.FileInfo, error) { + return m.MemMapFs.Stat(path) +} + +// Readlink doesn't actually read symbolic links since this is a in-mem +// fake. +func (*MemFS) Readlink(path string) (string, error) { + return path, nil +}