diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c4b6612f..f30c0a26 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,7 +13,21 @@ jobs: strategy: matrix: platform: [amd64] - action: [archive2disk,cexec,grub2disk,image2disk,kexec,oci2disk,qemuimg2disk,rootio,slurp,syslinux,writefile] + action: + [ + archive2disk, + cexec, + cidataio, + grub2disk, + image2disk, + kexec, + oci2disk, + qemuimg2disk, + rootio, + slurp, + syslinux, + writefile, + ] steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 44600813..83384fac 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -5,34 +5,62 @@ on: - "main" workflow_dispatch: {} -env: - REGISTRY: quay.io - jobs: build: name: Release runs-on: ubuntu-latest strategy: matrix: - action: [archive2disk,cexec,grub2disk,image2disk,kexec,oci2disk,qemuimg2disk,rootio,slurp,syslinux,writefile] + action: + [ + archive2disk, + cexec, + cidataio, + grub2disk, + image2disk, + kexec, + oci2disk, + qemuimg2disk, + rootio, + slurp, + syslinux, + writefile, + ] + permissions: + contents: read + packages: write + id-token: write steps: - uses: actions/checkout@v4 + - name: Set Registry + id: registry + run: | + if [ -n "${{ secrets.QUAY_USERNAME }}" ] && [ -n "${{ secrets.QUAY_PASSWORD }}" ]; then + echo "registry=quay.io" >> $GITHUB_OUTPUT + else + echo "registry=ghcr.io" >> $GITHUB_OUTPUT + fi + - name: Set up QEMU uses: docker/setup-qemu-action@v3 - + - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - - name: Login to Quay.io + - name: Login to Registry uses: docker/login-action@v3 with: - registry: ${{ env.REGISTRY }} - username: ${{ secrets.QUAY_USERNAME }} - password: ${{ secrets.QUAY_PASSWORD }} + registry: ${{ steps.registry.outputs.registry }} + username: ${{ secrets.QUAY_USERNAME || github.actor }} + password: ${{ secrets.QUAY_PASSWORD || github.token }} - name: Prepare Release run: make prepare-release + env: + CONTAINER_REPOSITORY: ${{ steps.registry.outputs.registry }}/${{ github.repository }} - name: Run Release run: make release-${{ matrix.action }} -j $(nprox) + env: + CONTAINER_REPOSITORY: ${{ steps.registry.outputs.registry }}/${{ github.repository }} diff --git a/Makefile b/Makefile index 5fdd30be..4604e044 100644 --- a/Makefile +++ b/Makefile @@ -9,13 +9,13 @@ SHELL := bash .SECONDEXPANSION: # Define the list of actions that can be built. -ACTIONS := archive2disk cexec grub2disk image2disk kexec oci2disk qemuimg2disk rootio slurp syslinux writefile +ACTIONS := archive2disk cidataio cexec grub2disk image2disk kexec oci2disk qemuimg2disk rootio slurp syslinux writefile ubootenv # Define the commit for tagging images. GIT_COMMIT := $(shell git rev-parse HEAD) # Define container registry details. -CONTAINER_REPOSITORY := quay.io/tinkerbell/actions +CONTAINER_REPOSITORY ?= quay.io/tinkerbell/actions include Rules.mk @@ -28,7 +28,7 @@ help: ## Print this help .PHONY: $(ACTIONS) $(ACTIONS): ## Build a specific action image. - docker buildx build --platform linux/amd64 --load -t $@:latest -f ./$@/Dockerfile . + docker buildx build --platform linux/arm64 --load -t $@:latest -f ./$@/Dockerfile . .PHONY: images images: ## Build all action images. diff --git a/archive2disk/Dockerfile b/archive2disk/Dockerfile index e33b88b7..fa89e4b1 100644 --- a/archive2disk/Dockerfile +++ b/archive2disk/Dockerfile @@ -1,6 +1,6 @@ # syntax=docker/dockerfile:1 -FROM golang:1.21-alpine AS archive2disk +FROM golang:1.23-alpine AS archive2disk RUN apk add --no-cache git ca-certificates gcc musl-dev COPY . /src WORKDIR /src/archive2disk diff --git a/cidataio/Dockerfile b/cidataio/Dockerfile new file mode 100644 index 00000000..16b8f5cf --- /dev/null +++ b/cidataio/Dockerfile @@ -0,0 +1,12 @@ +FROM golang:1.24-alpine AS cidataio +RUN apk add --no-cache git ca-certificates gcc linux-headers musl-dev +COPY . /src +WORKDIR /src/cidataio +RUN --mount=type=cache,sharing=locked,id=gomod,target=/go/pkg/mod/cache \ + --mount=type=cache,sharing=locked,id=goroot,target=/root/.cache/go-build \ + CGO_ENABLED=1 GOOS=linux go build -a -ldflags "-linkmode external -extldflags '-static' -s -w" -o cidataio + +FROM alpine:latest +RUN apk add --no-cache sgdisk dosfstools util-linux +COPY --from=cidataio /src/cidataio/cidataio /usr/bin/cidataio +ENTRYPOINT ["/usr/bin/cidataio"] diff --git a/cidataio/README.md b/cidataio/README.md new file mode 100644 index 00000000..bbc77117 --- /dev/null +++ b/cidataio/README.md @@ -0,0 +1,47 @@ +# Tinkerbell `cidataio` Action + +This action creates a `cidata` partition and writes cloud-init data to it. + +## Description + +The `cidataio` action is a Go-based tool designed to run in a Tinkerbell workflow. It prepares a disk that has just been flashed with an OS image (e.g., Talos "nocloud") by adding a new partition for cloud-init data. + +It performs the following steps: +1. Finds the target disk from the `DEST_DISK` environment variable. +2. Creates a new partition using all remaining free space on the disk. +3. Formats this new partition as `vfat` with the label `cidata`. +4. Mounts the partition. +5. Writes the contents of `USER_DATA`, `META_DATA`, and `VENDOR_DATA` environment variables to `user-data`, `meta-data`, and `vendor-data` files, respectively. +6. Unmounts the partition. + +## Environment Variables + +* **`DEST_DISK`** (Required): The block device to operate on (e.g., `/dev/sda`, `/dev/nvme0n1`). +* **`USER_DATA`** (Optional): The content for the `user-data` file. +* **`META_DATA`** (Optional): The content for the `meta-data` file. +* **`NETWORK_CONFIG`** (Optional): The content for the `network-config` file. + +## Example Workflow YAML + +This action is typically run immediately after `image2disk`. + +```yaml +actions: + - name: "stream talos nocloud image" + image: quay.io/tinkerbell/actions/image2disk:latest + timeout: 9600 + environment: + DEST_DISK: {{ index .Hardware.Disks 0 }} + IMG_URL: "..." + COMPRESSED: "true" + + - name: "create cidata partition and write files" + image: ghcr.io/tinkerbell/cidataio:latest + timeout: 120 + environment: + DEST_DISK: {{ index .Hardware.Disks 0 }} + USER_DATA: | + # user-data content here + META_DATA: | + local-hostname: my-node-1 +``` diff --git a/cidataio/cidataio b/cidataio/cidataio new file mode 100755 index 00000000..861efe96 Binary files /dev/null and b/cidataio/cidataio differ diff --git a/cidataio/main.go b/cidataio/main.go new file mode 100644 index 00000000..295f1ea4 --- /dev/null +++ b/cidataio/main.go @@ -0,0 +1,128 @@ +package main + +import ( + "fmt" + "log" + "os" + "os/exec" + "path/filepath" + "strings" + "time" +) + +const ( + configISOLabel = "cidata" + configNetworkConfigPath = "network-config" + configMetaDataPath = "meta-data" + configUserDataPath = "user-data" +) + +// run is a helper to run a shell command and log it. +func run(cmdStr string, args ...string) { + log.Printf("Running: %s %s", cmdStr, strings.Join(args, " ")) + cmd := exec.Command(cmdStr, args...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + if err := cmd.Run(); err != nil { + log.Fatalf("Command failed: %v", err) + } +} + +// runWithOutput runs a command and returns its stdout. +func runWithOutput(cmdStr string, args ...string) string { + log.Printf("Running (for output): %s %s", cmdStr, strings.Join(args, " ")) + out, err := exec.Command(cmdStr, args...).CombinedOutput() + if err != nil { + log.Printf("Command failed: %s - %v", string(out), err) + // Don't fatalf, as some commands (like ls) might fail gracefully + } + return strings.TrimSpace(string(out)) +} + +// findNewPartition compares a list of partitions before and after an operation. +func findNewPartition(before, after string) string { + beforeSet := make(map[string]bool) + for _, p := range strings.Split(before, "\n") { + if p != "" { + beforeSet[p] = true + } + } + + for _, p := range strings.Split(after, "\n") { + if p != "" && !beforeSet[p] { + return p // Found the new one + } + } + return "" +} + +// writeFileIfEnv writes content from an env var to a file. +func writeFileIfEnv(envVar, path string) { + content := os.Getenv(envVar) + if content == "" { + log.Printf("Env var %s not set, skipping file.", envVar) + return + } + + log.Printf("Writing content from %s to %s", envVar, path) + err := os.WriteFile(path, []byte(content), 0644) + if err != nil { + log.Fatalf("Failed to write file %s: %v", path, err) + } +} + +func main() { + log.Println("Starting cidataio action...") + + // 1. Get DEST_DISK + disk := os.Getenv("DEST_DISK") + if disk == "" { + log.Fatalf("DEST_DISK environment variable not set.") + } + + // 2. Force kernel to read partition table and get "before" list + run("partprobe", disk) + time.Sleep(1 * time.Second) // Give udev time to create devices + + // List all partitions for this disk using regex to match both: + // - Standard devices: /dev/sda1, /dev/sdb2, /dev/vda3 + // - NVMe/MMC devices: /dev/nvme0n1p1, /dev/mmcblk0p2 + globPattern := fmt.Sprintf("ls -1 %s* 2>/dev/null | grep -E '%sp?[0-9]+$' || true", disk, disk) + partsBefore := runWithOutput("sh", "-c", globPattern) + + // 3. Create the new partition + log.Printf("Creating new partition on %s", disk) + run("sgdisk", "-n", "0:0:+2M", "-t", "0:0700", disk) + + // 4. Force kernel to re-read and find the new partition + run("partprobe", disk) + time.Sleep(2 * time.Second) // Give udev time to settle + partsAfter := runWithOutput("sh", "-c", globPattern) + + newPart := findNewPartition(partsBefore, partsAfter) + if newPart == "" { + log.Fatalf("Could not find a new partition. Before: [%s], After: [%s]", partsBefore, partsAfter) + } + log.Printf("Found new partition: %s", newPart) + + // 5. Format the new partition + log.Printf("Formatting %s as vfat with label cidata", newPart) + run("mkfs.vfat", "-n", configISOLabel, newPart) + + // 6. Mount, Write, Unmount + mountPoint := "/mnt/cidata" + log.Printf("Mounting %s to %s", newPart, mountPoint) + run("mkdir", "-p", mountPoint) + run("mount", newPart, mountPoint) + + // 7. Write data from Env Vars + writeFileIfEnv("USER_DATA", filepath.Join(mountPoint, configUserDataPath)) + writeFileIfEnv("META_DATA", filepath.Join(mountPoint, configMetaDataPath)) + writeFileIfEnv("NETWORK_CONFIG", filepath.Join(mountPoint, configNetworkConfigPath)) + + // 8. Unmount + log.Printf("Unmounting %s", mountPoint) + run("umount", mountPoint) + + log.Println("cidataio action completed successfully.") +} \ No newline at end of file diff --git a/go.mod b/go.mod index c6fcb3dd..62cd0419 100644 --- a/go.mod +++ b/go.mod @@ -1,43 +1,32 @@ module github.com/tinkerbell/actions -go 1.21 +go 1.23.0 require ( github.com/cenkalti/backoff v2.2.1+incompatible - github.com/containerd/containerd v1.4.4 - github.com/deislabs/oras v0.11.1 github.com/diskfs/go-diskfs v1.4.1 github.com/dustin/go-humanize v1.0.1 github.com/klauspost/compress v1.17.9 github.com/lmittmann/tint v1.0.5 github.com/mattn/go-isatty v0.0.3 github.com/opencontainers/go-digest v1.0.0 + github.com/opencontainers/image-spec v1.1.1 github.com/peterbourgon/ff/v3 v3.4.0 github.com/pkg/errors v0.9.1 github.com/sirupsen/logrus v1.9.4-0.20230606125235-dd1b4c2e81af github.com/spf13/cobra v1.8.1 github.com/ulikunitz/xz v0.5.12 golang.org/x/sys v0.24.0 + oras.land/oras-go/v2 v2.6.0 ) require ( - github.com/Microsoft/go-winio v0.4.16 // indirect - github.com/Microsoft/hcsshim v0.8.14 // indirect - github.com/containerd/cgroups v0.0.0-20200531161412-0dbf7f05ba59 // indirect github.com/djherbis/times v1.6.0 // indirect github.com/elliotwutingfeng/asciiset v0.0.0-20230602022725-51bbb787efab // indirect - github.com/gogo/protobuf v1.3.1 // indirect - github.com/golang/protobuf v1.3.2 // indirect github.com/google/uuid v1.3.0 // indirect - github.com/hashicorp/golang-lru v0.5.1 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/opencontainers/image-spec v1.0.1 // indirect github.com/pierrec/lz4/v4 v4.1.17 // indirect github.com/pkg/xattr v0.4.9 // indirect github.com/spf13/pflag v1.0.5 // indirect - go.opencensus.io v0.22.0 // indirect - golang.org/x/net v0.0.0-20191004110552-13f9640d40b9 // indirect - golang.org/x/sync v0.0.0-20201207232520-09787c993a3a // indirect - google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a // indirect - google.golang.org/grpc v1.23.1 // indirect + golang.org/x/sync v0.14.0 // indirect ) diff --git a/go.sum b/go.sum index e2d9c933..3e665546 100644 --- a/go.sum +++ b/go.sum @@ -1,489 +1,65 @@ -bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= -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.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= -github.com/Azure/go-autorest v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/Microsoft/go-winio v0.4.16-0.20201130162521-d1ffc52c7331/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= -github.com/Microsoft/go-winio v0.4.16 h1:FtSW/jqD+l4ba5iPBj9CODVtgfYAD8w2wS923g/cFDk= -github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= -github.com/Microsoft/hcsshim v0.8.14 h1:lbPVK25c1cu5xTLITwpUcxoA9vKrKErASPYygvouJns= -github.com/Microsoft/hcsshim v0.8.14/go.mod h1:NtVKoYxQuTLx6gEq0L96c9Ju4JbRJ4nY2ow3VK6a9Lg= -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs= -github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= -github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= -github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= -github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= -github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= -github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= -github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= -github.com/bshuster-repo/logrus-logstash-hook v0.4.1 h1:pgAtgj+A31JBVtEHu2uHuEx0n+2ukqUJnS2vVe5pQNA= -github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= -github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd h1:rFt+Y/IK1aEZkEHchZRSq9OQbsSzIT/OrI8YFFmRIng= -github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= -github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b h1:otBG+dV+YK+Soembjv71DPz3uX/V/6MMlSyD9JBQ6kQ= -github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50= -github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 h1:nvj0OLI3YqYXer/kZD8Ri1aaunCxIEsOst1BVJswV0o= -github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/cilium/ebpf v0.0.0-20200110133405-4032b1d8aae3/go.mod h1:MA5e5Lr8slmEg9bt0VpxxWqJlO4iwu3FBdHUzV7wQVg= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/containerd/cgroups v0.0.0-20200531161412-0dbf7f05ba59 h1:qWj4qVYZ95vLWwqyNJCQg7rDsG5wPdze0UaPolH7DUk= -github.com/containerd/cgroups v0.0.0-20200531161412-0dbf7f05ba59/go.mod h1:pA0z1pT8KYB3TCXK/ocprsh7MAkoW8bZVzPdih9snmM= -github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= -github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.4.4 h1:rtRG4N6Ct7GNssATwgpvMGfnjnwfjnu/Zs9W3Ikzq+M= -github.com/containerd/containerd v1.4.4/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= -github.com/containerd/continuity v0.0.0-20201208142359-180525291bb7 h1:6ejg6Lkk8dskcM7wQ28gONkukbQkM4qpj4RnYbpFzrI= -github.com/containerd/continuity v0.0.0-20201208142359-180525291bb7/go.mod h1:kR3BEg7bDFaEddKm54WSmrol1fKWDU1nKYkgrcgZT7Y= -github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= -github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= -github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= -github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc= -github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= -github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= -github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 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/deislabs/oras v0.11.1 h1:oo2J/3vXdcti8cjFi8ghMOkx0OacONxHC8dhJ17NdJ0= -github.com/deislabs/oras v0.11.1/go.mod h1:39lCtf8Q6WDC7ul9cnyWXONNzKvabEKk+AX+L0ImnQk= -github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0= -github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/diskfs/go-diskfs v1.4.1 h1:iODgkzHLmvXS+1VDztpW53T+dQm8GQzi20y9yUd5UCA= github.com/diskfs/go-diskfs v1.4.1/go.mod h1:+tOkQs8CMMog6Nvljg8DGIxEXrgL48iyT3OM3IlSz74= github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c= github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0= -github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= -github.com/docker/cli v20.10.5+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/distribution v0.0.0-20191216044856-a8371794149d h1:jC8tT/S0OGx2cswpeUTn4gOIea8P08lD3VFQT0cOZ50= -github.com/docker/distribution v0.0.0-20191216044856-a8371794149d/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY= -github.com/docker/docker v17.12.0-ce-rc1.0.20200618181300-9dc6525e6118+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y= -github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= -github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916 h1:yWHOI+vFjEsAakUTSrtqc/SAHrhSkmn48pqjidZX3QA= -github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI= -github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 h1:ZClxb8laGDf5arXfYcAtECDFgAgHklGI8CxgjHnXKJ4= -github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= -github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/elliotwutingfeng/asciiset v0.0.0-20230602022725-51bbb787efab h1:h1UgjJdAAhj+uPL68n7XASS6bU+07ZX1WJvVS2eyoeY= github.com/elliotwutingfeng/asciiset v0.0.0-20230602022725-51bbb787efab/go.mod h1:GLo/8fDswSAniFG+BFIaiSPcK610jyzgEhWYPQwuQdw= -github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7 h1:LofdAjjjqCSXMwLGgOgnE+rdPuvX9DxCqaHwKy7i/ko= -github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= -github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= -github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= -github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/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/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 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -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/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 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/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/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33 h1:893HsJqtxp9z1SF76gg6hY70hRY1wVlTSnC/h1yUDCo= -github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= -github.com/gorilla/mux v1.7.2 h1:zoNxOV7WjqXptQOVngLmcSQgXmgk4NMz1HibBchjl/I= -github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= -github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= -github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= -github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= -github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= -github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= -github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= -github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= -github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= -github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= -github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= -github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= -github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/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/lmittmann/tint v1.0.5 h1:NQclAutOfYsqs2F1Lenue6OoWCajs5wJcP3DfWVpePw= github.com/lmittmann/tint v1.0.5/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE= -github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho= -github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.3 h1:ns/ykhmWi7G9O+8a448SecJU3nSMBXJfqQkl0upE1jI= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= -github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= -github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= -github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM= -github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= -github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= -github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= 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.0.0/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI= -github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= -github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= -github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= +github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= github.com/peterbourgon/ff/v3 v3.4.0 h1:QBvM/rizZM1cB0p0lGMdmR7HxZeI/ZrBWB4DqLkMUBc= github.com/peterbourgon/ff/v3 v3.4.0/go.mod h1:zjJVUhx+twciwfDl0zBcFzl4dW8axCRyXE/eKY9RztQ= -github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc= -github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE= github.com/pierrec/lz4/v4 v4.1.17 h1:kV4Ip+/hUBC+8T6+2EgburRtkE9ef4nbY3f4dFhGjMc= github.com/pierrec/lz4/v4 v4.1.17/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/xattr v0.4.9 h1:5883YPCtkSd8LFbs13nXplj9g9tlrwoJRjgpgMu1/fE= github.com/pkg/xattr v0.4.9/go.mod h1:di8WF84zAKk8jzR1UBTEWh9AUlIZZ7M/JNt8e9B6ktU= 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/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= -github.com/prometheus/client_golang v0.0.0-20180209125602-c332b6f63c06/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.3 h1:9iH4JKXLzFbOAdtqv/a+j8aewx2Y8lAjAydhbaScPF8= -github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= -github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/common v0.0.0-20180110214958-89604d197083/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.4.0 h1:7etb9YClo3a6HjLzfl6rIQaU+FDfi0VSX39io3aQ+DM= -github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084 h1:sofwID9zm4tzrgykg80hfFph1mryUeLRsUfoocVVmRY= -github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= -github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/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/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= -github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= -github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.4-0.20230606125235-dd1b4c2e81af h1:Sp5TG9f7K39yfB+If0vjp97vuT74F72r8hfRpP8jLU0= github.com/sirupsen/logrus v1.9.4-0.20230606125235-dd1b4c2e81af/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= -github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -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.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= -github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc= github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= -github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= -github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43 h1:+lm10QQTNSBd8DVTNGHx7o/IKu9HYDvLMffDhbyLccI= -github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= -github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50 h1:hlE8//ciYMztlGpl/VA+Zm1AcTPHYkHJPbHqE6WJUXE= -github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA= -github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f h1:ERexzlUfuTvpE74urLSbIQW0Z/6hF9t8U4NsJLaioAY= -github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= -go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0 h1:C9hSCOW830chIVkdja34wa6Ky+IzWllkUinR+BtRZd4= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-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-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY= -golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -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/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/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/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-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-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-20190619014844-b5b0513f8c1b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191004110552-13f9640d40b9 h1:rjwSpXsdiK0dV8/Naq3kAw9ymfAeJIyd0upUIElB+lI= -golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -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/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-20201207232520-09787c993a3a h1:DcqTD9SDLc+1P/r1EmRBwnVsrOwW+kk2vWf9n+1sGhs= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200120151820-655fe14d7479/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= +golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= -golang.org/x/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 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -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/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/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-20190624222133-a101b041ded4/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-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= -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/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/cloud v0.0.0-20151119220103-975617b05ea8/go.mod h1:0H1ncTHf11KCFhTc/+EFRbzSCOZx+VUbRMk55Yv5MYk= -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 h1:Ob5/580gVHBJZgXnff1cZDbG+xLtMVE5mDRTe+nIsX4= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= -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.1 h1:q4XQuHFC6I28BKZpo6IYyb3mNO+l7lSOxRuYTCiDfXk= -google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20141024133853-64131543e789/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= -gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= -gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= -gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= -gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= -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= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +oras.land/oras-go/v2 v2.6.0 h1:X4ELRsiGkrbeox69+9tzTu492FMUu7zJQW6eJU+I2oc= +oras.land/oras-go/v2 v2.6.0/go.mod h1:magiQDfG6H1O9APp+rOsvCPcW1GD2MM7vgnKY0Y+u1o= diff --git a/kexec/Dockerfile b/kexec/Dockerfile index 11a53b63..b9d5b724 100644 --- a/kexec/Dockerfile +++ b/kexec/Dockerfile @@ -1,6 +1,6 @@ # syntax=docker/dockerfile:1 -FROM golang:1.21-alpine AS kexec +FROM golang:1.23-alpine AS kexec RUN apk add --no-cache git ca-certificates gcc linux-headers musl-dev COPY . /src WORKDIR /src/kexec diff --git a/oci2disk/Dockerfile b/oci2disk/Dockerfile index de99ff30..60729f0f 100644 --- a/oci2disk/Dockerfile +++ b/oci2disk/Dockerfile @@ -1,6 +1,6 @@ # syntax=docker/dockerfile:1 -FROM golang:1.21-alpine AS oci2disk +FROM golang:1.23-alpine AS oci2disk RUN apk add --no-cache git ca-certificates gcc linux-headers musl-dev COPY . /src WORKDIR /src/oci2disk diff --git a/oci2disk/README.md b/oci2disk/README.md index 36a4424c..78f860ff 100644 --- a/oci2disk/README.md +++ b/oci2disk/README.md @@ -18,7 +18,7 @@ The below example will push a `debian` image to a registry: oras push 192.168.0.173/test/debian:raw.gz ./debian.raw.gz --insecure ``` -We can then use this image by referring too it with teh `IMG_URL` environment variable. +We can then use this image by referring to it with the `IMG_URL` environment variable. ```yaml actions: @@ -28,12 +28,9 @@ actions: environment: DEST_DISK: /dev/nvme0n1 IMG_URL: "192.168.0.173/test/debian:raw.gz" - COMPRESSED: true ``` -## Compression format supported: +## Environment Variables: -- bzip2 (`.bzip2`) -- gzip (`.gz`) -- xz (`.xz`) -- xs (`.xs`) +- `DEST_DISK`: Target block device to write the image to (required) +- `IMG_URL`: OCI image reference (required) diff --git a/oci2disk/image/image.go b/oci2disk/image/image.go index 01a1855e..106391b8 100644 --- a/oci2disk/image/image.go +++ b/oci2disk/image/image.go @@ -6,81 +6,229 @@ import ( "compress/bzip2" "compress/gzip" "context" - "crypto/tls" - "errors" + "encoding/json" "fmt" "io" - "net/http" + "math" "os" "path/filepath" + "runtime" + "sync/atomic" + "time" - "github.com/containerd/containerd/reference" - "github.com/containerd/containerd/remotes/docker" - "github.com/deislabs/oras/pkg/oras" "github.com/klauspost/compress/zstd" + ocispec "github.com/opencontainers/image-spec/specs-go/v1" log "github.com/sirupsen/logrus" "github.com/ulikunitz/xz" "golang.org/x/sys/unix" + "oras.land/oras-go/v2" + "oras.land/oras-go/v2/content" + "oras.land/oras-go/v2/registry/remote" + "oras.land/oras-go/v2/registry/remote/auth" ) -// WriteCounter counts the number of bytes written to it. It implements to the io.Writer interface -// and we can pass this into io.TeeReader() which will report progress on each write cycle. -type WriteCounter struct { - Total uint64 +// BLKRRPART is the ioctl request to re-read partition table (Linux-specific) +const BLKRRPART = 0x125f + +type Progress struct { + w io.Writer + r io.Reader + wBytes atomic.Int64 + rBytes atomic.Int64 +} + +func NewProgress(w io.Writer, r io.Reader) *Progress { + return &Progress{w: w, r: r} +} + +func (p *Progress) Write(b []byte) (n int, err error) { + nu, err := p.w.Write(b) + p.wBytes.Add(int64(nu)) + return nu, err +} + +func (p *Progress) Read(b []byte) (n int, err error) { + nu, err := p.r.Read(b) + p.rBytes.Add(int64(nu)) + return nu, err } -func (wc *WriteCounter) Write(p []byte) (int, error) { - n := len(p) - wc.Total += uint64(n) - return n, nil +func (p *Progress) readBytes() int64 { + return p.rBytes.Load() +} + +func (p *Progress) writeBytes() int64 { + return p.wBytes.Load() +} + +func prettyByteSize(b int64) string { + bf := float64(b) + for _, unit := range []string{"", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi"} { + if math.Abs(bf) < 1024.0 { + return fmt.Sprintf("%3.6f%sB", bf, unit) + } + bf /= 1024.0 + } + return fmt.Sprintf("%.6fYiB", bf) } // Write will pull an image and write it to local storage device -// with compress set to true it will use gzip compression to expand the data before -// writing to an underlying device. -func Write(sourceImage, destinationDevice string, compressed bool) error { +// Compression type is automatically detected from the layer's org.opencontainers.image.title annotation. +// Platform is automatically detected from the runtime (linux/arm64, linux/amd64, etc.). +func Write(sourceImage, destinationDevice string) error { ctx := context.Background() - client := http.DefaultClient - opts := docker.ResolverOptions{} - client.Transport = &http.Transport{ - TLSClientConfig: &tls.Config{ - InsecureSkipVerify: true, //nolint:gosec // GA402 TODO - }, - } - opts.Client = client + // Detect platform from runtime + platformOS := "linux" + platformArch := runtime.GOARCH - resolver := docker.NewResolver(opts) + // Create repository connection + repo, err := remote.NewRepository(sourceImage) + if err != nil { + return fmt.Errorf("failed to create repository: %w", err) + } + // Configure repository client with custom HTTP client + repo.Client = auth.DefaultClient + + // Open destination device fileOut, err := os.OpenFile(destinationDevice, os.O_CREATE|os.O_WRONLY, 0o644) if err != nil { return err } defer fileOut.Close() - customMediaType := "application/vnd.oci.image.layer.v1.tar" - allowedMediaTypes := []string{customMediaType} - - f := NewDiskImageStore(sourceImage, compressed, fileOut) log.Infof("Beginning write of image [%s] to disk [%s]", filepath.Base(sourceImage), destinationDevice) - pullOpts := []oras.PullOpt{ - oras.WithAllowedMediaTypes(allowedMediaTypes), - oras.WithPullStatusTrack(os.Stdout), + + // Resolve the manifest descriptor with optional platform filtering + tagOrDigest := repo.Reference.Reference + if tagOrDigest == "" { + return fmt.Errorf("image reference format is invalid. Please specify ") } - _, _, err = oras.Pull(ctx, resolver, sourceImage, f, pullOpts...) + + // Configure resolution options with platform filtering if specified + resolveOpts := oras.ResolveOptions{} + if platformOS != "" && platformArch != "" { + resolveOpts.TargetPlatform = &ocispec.Platform{ + OS: platformOS, + Architecture: platformArch, + } + log.Infof("Filtering for platform: %s/%s", platformOS, platformArch) + } + + manifestDescriptor, err := oras.Resolve(ctx, repo, tagOrDigest, resolveOpts) + if err != nil { + return fmt.Errorf("failed to resolve manifest: %w", err) + } + + // Fetch the manifest + manifestBytes, err := content.FetchAll(ctx, repo, manifestDescriptor) if err != nil { - if errors.Is(err, reference.ErrObjectRequired) { - return fmt.Errorf("image reference format is invalid. Please specify ") + return fmt.Errorf("failed to fetch manifest: %w", err) + } + + // Parse the manifest + var manifest ocispec.Manifest + if err := json.Unmarshal(manifestBytes, &manifest); err != nil { + return fmt.Errorf("failed to parse manifest: %w", err) + } + + // Filter and process layers + customMediaType := "application/vnd.oci.image.layer.v1.tar" + var totalBytes int64 + var processedLayers int + + for _, layer := range manifest.Layers { + // Skip layers that don't match our media type + if layer.MediaType != customMediaType { + log.Debugf("Skipping layer with media type: %s", layer.MediaType) + continue } - return err + + log.Infof("Fetching layer: %s (size: %d bytes)", layer.Digest, layer.Size) + + // Fetch the layer content + layerReader, err := repo.Fetch(ctx, layer) + if err != nil { + return fmt.Errorf("failed to fetch layer %s: %w", layer.Digest, err) + } + + // Determine compression and create appropriate reader + var sourceReader io.Reader = layerReader + var decompressor io.ReadCloser + + // Determine compression type from annotation + if layer.Annotations != nil { + if titleAnnotation, ok := layer.Annotations[ocispec.AnnotationTitle]; ok { + // Create decompressor if needed + var err error + decompressor, err = findDecompressor(titleAnnotation, layerReader) + if err != nil { + layerReader.Close() + return fmt.Errorf("failed to create decompressor: %w", err) + } + sourceReader = decompressor + } + } + + // Create progress tracker that wraps the writer and source reader + progress := NewProgress(fileOut, sourceReader) + + // Start progress reporting + ticker := time.NewTicker(5 * time.Second) + done := make(chan bool) + go func() { + layerSize := layer.Size + for { + select { + case <-done: + log.Infof("Progress: written=%s, compressed=%s, read=%s", + prettyByteSize(progress.writeBytes()), + prettyByteSize(layerSize), + prettyByteSize(progress.readBytes())) + return + case <-ticker.C: + log.Infof("Progress: written=%s, compressed=%s, read=%s", + prettyByteSize(progress.writeBytes()), + prettyByteSize(layerSize), + prettyByteSize(progress.readBytes())) + } + } + }() + + // Copy to destination device: read from progress (which reads from sourceReader) and write to progress + _, err = io.Copy(progress, progress) + + // Stop progress reporting + ticker.Stop() + done <- true + + // Close in correct order: decompressor first (if exists), then underlying layerReader + if decompressor != nil { + decompressor.Close() + } + layerReader.Close() + + if err != nil { + return fmt.Errorf("failed to write layer to device: %w", err) + } + + totalBytes += progress.writeBytes() + processedLayers++ } + if processedLayers == 0 { + return fmt.Errorf("no layers with media type %s found in manifest", customMediaType) + } + + log.Infof("Successfully processed %d layer(s), total: %s", processedLayers, prettyByteSize(totalBytes)) + // Do the equivalent of partprobe on the device if err := fileOut.Sync(); err != nil { log.Warnf("Failed to sync the block device") } - if err := unix.IoctlSetInt(int(fileOut.Fd()), unix.BLKRRPART, 0); err != nil { + if err := unix.IoctlSetInt(int(fileOut.Fd()), BLKRRPART, 0); err != nil { log.Warnf("Error re-probing the partitions for the specified device") } @@ -89,7 +237,7 @@ func Write(sourceImage, destinationDevice string, compressed bool) error { func findDecompressor(imageURL string, r io.Reader) (io.ReadCloser, error) { switch filepath.Ext(imageURL) { - case ".bzip2": + case ".bzip2", ".bz2": return io.NopCloser(bzip2.NewReader(r)), nil case ".gz": reader, err := gzip.NewReader(r) @@ -103,7 +251,7 @@ func findDecompressor(imageURL string, r io.Reader) (io.ReadCloser, error) { return nil, fmt.Errorf("[ERROR] New xz reader: %w", err) } return io.NopCloser(reader), nil - case ".zs": + case ".zs", ".zst": reader, err := zstd.NewReader(r) if err != nil { return nil, fmt.Errorf("[ERROR] New zs reader: %w", err) diff --git a/oci2disk/image/image_test.go b/oci2disk/image/image_test.go index 7cb42c01..a8df050c 100644 --- a/oci2disk/image/image_test.go +++ b/oci2disk/image/image_test.go @@ -45,44 +45,64 @@ func xzReader(t *testing.T) io.Reader { func Test_findDecompressor(t *testing.T) { tests := []struct { name string - imageURL string + filename string reader func(*testing.T) io.Reader wantOut io.Reader wantErr bool }{ { - "tar gzip", - "http://192.168.0.1/a.tar.gz", + "gzip", + "image.raw.gz", gzipReader, nil, false, }, { "broken gzip", - "http://192.168.0.1/a.gz", + "image.raw.gz", xzReader, nil, true, }, { "xz", - "http://192.168.0.1/a.xz", + "image.raw.xz", xzReader, nil, false, }, { - "unknown", - "http://192.168.0.1/a.abc", + "unknown extension", + "image.raw.abc", xzReader, nil, true, }, - // TODO: Add test cases. + { + "no extension", + "image.raw", + gzipReader, + nil, + true, + }, + { + "bzip2", + "image.raw.bz2", + gzipReader, + nil, + false, + }, + { + "zstd .zst", + "image.raw.zst", + gzipReader, + nil, + false, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - _, err := findDecompressor(tt.imageURL, tt.reader(t)) + _, err := findDecompressor(tt.filename, tt.reader(t)) if (err != nil) != tt.wantErr { t.Errorf("findDecompressor() error = %v, wantErr %v", err, tt.wantErr) return diff --git a/oci2disk/image/writer.go b/oci2disk/image/writer.go deleted file mode 100644 index e8879dad..00000000 --- a/oci2disk/image/writer.go +++ /dev/null @@ -1,198 +0,0 @@ -package image - -import ( - "context" - "fmt" - "io" - "log" - - ctrcontent "github.com/containerd/containerd/content" - "github.com/deislabs/oras/pkg/content" - "github.com/opencontainers/go-digest" -) - -// DiskImageStore -. -type DiskImageStore struct { - sourceImage string - writer io.Writer - compressed bool -} - -// NewDiskImageStore -. -func NewDiskImageStore(sourceImage string, compressed bool, w io.Writer) DiskImageStore { - // we have to reprocess the opts to find the blocksize - // var wOpts := content.DefaultWriterOpts() - // for _, opt := range opts { - // if err := opt(&wOpts); err != nil { - // // TODO: we probably should handle errors here - // continueå - // } - // } - return DiskImageStore{sourceImage: sourceImage, writer: w, compressed: compressed} -} - -// Writer get a writer. -func (d DiskImageStore) Writer(_ context.Context, opts ...ctrcontent.WriterOpt) (ctrcontent.Writer, error) { - // the logic is straightforward: - // - if there is a desc in the opts, and the mediatype is tar or tar+gzip, then pass the correct decompress writer - // - else, pass the regular writer - // var ( - // writer ctrcontent.Writer - // err error - // multiIngester MultiWriterIngester - // ok bool - // ) - - // // check to see if we are supposed to use a MultiWriterIngester - // if d.multiWriterIngester { - // multiIngester, ok = d.ingester.(MultiWriterIngester) - // if !ok { - // return nil, errors.New("configured to use multiwriter ingester, but ingester does not implement multiwriter") - // } - // } - - // // we have to reprocess the opts to find the desc - // var wOpts ctrcontent.WriterOpts - // for _, opt := range opts { - // if err := opt(&wOpts); err != nil { - // return nil, err - // } - // } - // // figure out if compression and/or archive exists - // desc := wOpts.Desc - // // before we pass it down, we need to strip anything we are removing here - // // and possibly update the digest, since the store indexes things by digest - // hasGzip, hasTar, modifiedMediaType := checkCompression(desc.MediaType) - // wOpts.Desc.MediaType = modifiedMediaType - // opts = append(opts, ctrcontent.WithDescriptor(wOpts.Desc)) - // // determine if we pass it blocksize, only if positive - // writerOpts := []WriterOpt{} - // if d.blocksize > 0 { - // writerOpts = append(writerOpts, WithBlocksize(d.blocksize)) - // } - - // writer, err = d.ingester.Writer(ctx, opts...) - // if err != nil { - // return nil, err - // } - - // // do we need to wrap with an untar writer? - // if hasTar { - // // if not multiingester, get a regular writer - // if multiIngester == nil { - // writer = NewUntarWriter(writer, writerOpts...) - // } else { - // writers, err := multiIngester.Writers(ctx, opts...) - // if err != nil { - // return nil, err - // } - // writer = NewUntarWriterByName(writers, writerOpts...) - // } - // } - // if hasGzip { - // if writer == nil { - // writer, err = d.ingester.Writer(ctx, opts...) - // if err != nil { - // return nil, err - // } - // } - // writer = NewGunzipWriter(writer, writerOpts...) - // } - // return writer, nil - var f func(r io.Reader, w io.Writer, done chan<- error) - - wOpts := content.DefaultWriterOpts() - - di := &DiskImage{ - writer: d.writer, - digester: digest.Canonical.Digester(), - // we take the OutputHash, since the InputHash goes to the passthrough writer, - // which then passes the processed output to us - // hash: wOpts.OutputHash, - } - - // we have to reprocess the opts to find the desc - var wOpts1 ctrcontent.WriterOpts - for _, opt := range opts { - if err := opt(&wOpts1); err != nil { - return nil, err - } - } - // figure out if compression and/or archive exists - desc := wOpts1.Desc - fmt.Printf("%v\n", desc.Annotations["org.opencontainers.image.title"]) - if desc.Annotations["org.opencontainers.image.title"] == "" { - return content.NewIoContentWriter(io.Discard, content.WithOutputHash(desc.Digest)), nil - } - if !d.compressed { - // Without compression send raw output - f = func(r io.Reader, w io.Writer, done chan<- error) { - var err error - b := make([]byte, wOpts.Blocksize) - _, err = io.CopyBuffer(w, r, b) - done <- err - } - } else { - f = func(r io.Reader, w io.Writer, done chan<- error) { - decompressReader, err := findDecompressor(d.sourceImage, r) - if err != nil { - log.Fatalf(err.Error()) //nolint:revive // this is fine - } - defer decompressReader.Close() - b := make([]byte, wOpts.Blocksize) - _, err = io.CopyBuffer(w, decompressReader, b) - done <- err - } - } - writerOpts := []content.WriterOpt{} - return content.NewPassthroughWriter(di, f, writerOpts...), nil - // return nil, err -} - -// DiskImage -. -type DiskImage struct { - writer io.Writer - digester digest.Digester - size int64 - hash *digest.Digest -} - -func (w *DiskImage) Write(p []byte) (n int, err error) { - n, err = w.writer.Write(p) - if err != nil { - return 0, err - } - w.size += int64(n) - if w.hash == nil { - w.digester.Hash().Write(p[:n]) - } - return -} - -// Close -. -func (w *DiskImage) Close() error { - return nil -} - -// Digest may return empty digest or panics until committed. -func (w *DiskImage) Digest() digest.Digest { - return w.digester.Digest() -} - -// Commit commits the blob (but no roll-back is guaranteed on an error). -// size and expected can be zero-value when unknown. -// Commit always closes the writer, even on error. -// ErrAlreadyExists aborts the writer. -func (w *DiskImage) Commit(context.Context, int64, digest.Digest, ...ctrcontent.Opt) error { - return nil -} - -// Status returns the current state of write. -func (w *DiskImage) Status() (ctrcontent.Status, error) { - return ctrcontent.Status{}, nil -} - -// Truncate updates the size of the target blob. -func (w *DiskImage) Truncate(int64) error { - return nil -} diff --git a/oci2disk/main.go b/oci2disk/main.go index b5de1147..ed497990 100644 --- a/oci2disk/main.go +++ b/oci2disk/main.go @@ -3,7 +3,6 @@ package main import ( "fmt" "os" - "strconv" log "github.com/sirupsen/logrus" "github.com/tinkerbell/actions/oci2disk/image" @@ -13,13 +12,9 @@ func main() { fmt.Printf("OCI2DISK - OCI Container Disk image streamer\n------------------------\n") disk := os.Getenv("DEST_DISK") img := os.Getenv("IMG_URL") - compressedEnv := os.Getenv("COMPRESSED") - // We can ignore the error and default compressed to false. - cmp, _ := strconv.ParseBool(compressedEnv) - - // Write the image to disk - err := image.Write(img, disk, cmp) + // Write the image to disk (platform auto-detected) + err := image.Write(img, disk) if err != nil { log.Fatal(err) } diff --git a/qemuimg2disk/Dockerfile b/qemuimg2disk/Dockerfile index 1c64f478..b8e0a15e 100644 --- a/qemuimg2disk/Dockerfile +++ b/qemuimg2disk/Dockerfile @@ -40,7 +40,7 @@ RUN sed -e 's|-lbrotlidec|-lbrotlicommon-static -lbrotlidec-static -lbrotlienc-s RUN make -j$(nproc) qemu-img RUN cp build/qemu-img /bin/qemu-img -FROM golang:1.21-alpine AS qemuimg2disk +FROM golang:1.23-alpine AS qemuimg2disk COPY . /src WORKDIR /src/qemuimg2disk RUN go mod tidy diff --git a/rootio/Dockerfile b/rootio/Dockerfile index 87271d8b..064a23ce 100644 --- a/rootio/Dockerfile +++ b/rootio/Dockerfile @@ -19,7 +19,7 @@ RUN make LDFLAGS="-all-static" swapon RUN make LDFLAGS="-all-static" mkswap # Build rootio -FROM golang:1.21-alpine AS rootio +FROM golang:1.23-alpine AS rootio RUN apk add --no-cache git ca-certificates gcc linux-headers musl-dev COPY . /src WORKDIR /src/rootio diff --git a/slurp/Dockerfile b/slurp/Dockerfile index 0d28925e..82ef8160 100644 --- a/slurp/Dockerfile +++ b/slurp/Dockerfile @@ -1,6 +1,6 @@ # syntax=docker/dockerfile:1 -FROM golang:1.21-alpine AS slurp +FROM golang:1.23-alpine AS slurp RUN apk add --no-cache git ca-certificates gcc linux-headers musl-dev COPY . /src WORKDIR /src/slurp diff --git a/ubootenv/Dockerfile b/ubootenv/Dockerfile new file mode 100644 index 00000000..5c6cf1e4 --- /dev/null +++ b/ubootenv/Dockerfile @@ -0,0 +1,12 @@ +# syntax=docker/dockerfile:1 + +FROM golang:1.24-alpine AS ubootenv +RUN apk add --no-cache git ca-certificates gcc musl-dev +COPY . /src +WORKDIR /src/ubootenv +RUN CGO_ENABLED=1 GOOS=linux go build -a -ldflags "-linkmode external -extldflags '-static' -s -w" -o ubootenv + +FROM scratch +COPY --from=ubootenv /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt +COPY --from=ubootenv /src/ubootenv/ubootenv /usr/bin/ubootenv +ENTRYPOINT ["/usr/bin/ubootenv"] diff --git a/ubootenv/README.md b/ubootenv/README.md new file mode 100644 index 00000000..6918218c --- /dev/null +++ b/ubootenv/README.md @@ -0,0 +1,54 @@ +# UBoot Env + +``` +quay.io/tinkerbell/actions/ubootenv:latest +``` + +This action reads and modifies U-Boot environment variables stored in a `uboot.env` file +on a VFAT partition. It is a pure Go port of the U-Boot `fw_setenv` utility, supporting +the non-redundant VFAT file format (offset `0x0000`, default size `0x4000`). + +Environment variables are provided as a JSON object via the `ENV_VARS` environment variable. +Setting a variable's value to an empty string (`""`) deletes it from the environment. + +## Environment Variables + +| Variable | Description | Required | Default | +|---------------|--------------------------------------------------------|----------|------------------| +| `DEST_DISK` | Block device / partition to mount (e.g. `/dev/sda1`) | Yes | | +| `FS_TYPE` | Filesystem type for mounting | No | `vfat` | +| `ENV_FILE` | Path to `uboot.env` relative to mount root | No | `/boot/uboot.env`| +| `ENV_VARS` | JSON object of key/value pairs to set | Yes | | + +## Example Workflow Action + +```yaml +actions: + - name: "set u-boot environment" + image: quay.io/tinkerbell/actions/ubootenv:latest + timeout: 90 + environment: + DEST_DISK: /dev/sda1 + FS_TYPE: vfat + ENV_FILE: /boot/uboot.env + ENV_VARS: '{"bootcmd":"run distro_bootcmd","bootdelay":"3"}' +``` + +## Deleting Variables + +To delete a variable, set its value to an empty string: + +```yaml + ENV_VARS: '{"obsolete_var":""}' +``` + +## U-Boot Environment Format + +The action expects the standard non-redundant U-Boot environment file format: + +| Offset | Size | Field | +|--------|---------------|--------| +| 0x0000 | 4 bytes | CRC32 (IEEE, little-endian, computed over data) | +| 0x0004 | env_size - 4 | Data: null-terminated `key=value` pairs, double-null terminated, zero-padded | + +Default environment size: `0x4000` (16,384 bytes). diff --git a/ubootenv/main.go b/ubootenv/main.go new file mode 100644 index 00000000..176db75a --- /dev/null +++ b/ubootenv/main.go @@ -0,0 +1,112 @@ +//go:build linux + +package main + +import ( + "encoding/json" + "fmt" + "log/slog" + "os" + "path/filepath" + "syscall" + + "github.com/tinkerbell/actions/ubootenv/ubootenv" +) + +const mountAction = "/mountAction" + +func main() { + logger := slog.New(slog.NewTextHandler(os.Stdout, nil)) + logger.Info("UBOOTENV - U-Boot Environment Variable Writer") + + blockDevice := os.Getenv("DEST_DISK") + fsType := os.Getenv("FS_TYPE") + envPath := os.Getenv("ENV_FILE") + envVarsJSON := os.Getenv("ENV_VARS") + + if blockDevice == "" { + logger.Error("DEST_DISK is required") + os.Exit(1) + } + + if fsType == "" { + fsType = "vfat" + } + + if envPath == "" { + envPath = "/boot/uboot.env" + } + + if envVarsJSON == "" { + logger.Error("ENV_VARS is required (JSON object of key/value pairs)") + os.Exit(1) + } + + var newVars map[string]string + if err := json.Unmarshal([]byte(envVarsJSON), &newVars); err != nil { + logger.Error("Failed to parse ENV_VARS as JSON", "error", err) + os.Exit(1) + } + + if len(newVars) == 0 { + logger.Info("No environment variables to set, nothing to do") + return + } + + if err := os.MkdirAll(mountAction, 0o755); err != nil { + logger.Error("Error creating mountpoint", "path", mountAction, "error", err) + os.Exit(1) + } + + if err := syscall.Mount(blockDevice, mountAction, fsType, 0, ""); err != nil { + logger.Error("Failed to mount block device", "device", blockDevice, "mountpoint", mountAction, "fstype", fsType, "error", err) + os.Exit(1) + } + defer func() { + if err := syscall.Unmount(mountAction, 0); err != nil { + logger.Error("Error unmounting device", "source", blockDevice, "destination", mountAction, "error", err) + } else { + logger.Info("Unmounted device", "source", blockDevice, "destination", mountAction) + } + }() + logger.Info("Mounted device", "source", blockDevice, "destination", mountAction) + + fullPath := filepath.Join(mountAction, envPath) + + data, err := os.ReadFile(fullPath) + if err != nil { + logger.Error("Failed to read U-Boot environment file", "path", fullPath, "error", err) + os.Exit(1) + } + + env, err := ubootenv.Parse(data) + if err != nil { + logger.Error("Failed to parse U-Boot environment", "error", err) + os.Exit(1) + } + + logger.Info(fmt.Sprintf("Parsed U-Boot environment: %d existing variables", len(env.Vars))) + + for k, v := range newVars { + if v == "" { + logger.Info("Deleting variable", "key", k) + delete(env.Vars, k) + } else { + logger.Info("Setting variable", "key", k, "value", v) + env.Vars[k] = v + } + } + + out, err := env.Marshal() + if err != nil { + logger.Error("Failed to marshal U-Boot environment", "error", err) + os.Exit(1) + } + + if err := os.WriteFile(fullPath, out, 0o644); err != nil { + logger.Error("Failed to write U-Boot environment file", "path", fullPath, "error", err) + os.Exit(1) + } + + logger.Info("U-Boot environment updated successfully", "path", envPath, "variables_set", len(newVars)) +} diff --git a/ubootenv/ubootenv/ubootenv.go b/ubootenv/ubootenv/ubootenv.go new file mode 100644 index 00000000..b9b29ff1 --- /dev/null +++ b/ubootenv/ubootenv/ubootenv.go @@ -0,0 +1,110 @@ +package ubootenv + +import ( + "encoding/binary" + "fmt" + "hash/crc32" + "sort" + "strings" +) + +// DefaultEnvSize is the default total size of a U-Boot environment file (0x4000 = 16384 bytes). +const DefaultEnvSize = 0x4000 + +// crcSize is the size of the CRC32 header in bytes. +const crcSize = 4 + +// Env represents a parsed U-Boot environment. +type Env struct { + Vars map[string]string + Size int +} + +// Parse reads a U-Boot environment from raw bytes. +// The expected format is: +// +// [4 bytes CRC32 little-endian][data...] +// +// Data consists of null-terminated "key=value" strings. +// The end of variables is marked by a double null byte. +func Parse(data []byte) (*Env, error) { + if len(data) < crcSize+1 { + return nil, fmt.Errorf("data too short: %d bytes", len(data)) + } + + storedCRC := binary.LittleEndian.Uint32(data[:crcSize]) + payload := data[crcSize:] + + computedCRC := crc32.ChecksumIEEE(payload) + if storedCRC != computedCRC { + return nil, fmt.Errorf("CRC mismatch: stored=0x%08x computed=0x%08x", storedCRC, computedCRC) + } + + vars := make(map[string]string) + pos := 0 + for pos < len(payload) { + if payload[pos] == 0 { + break // end of environment + } + + // Find the null terminator for this entry. + end := pos + for end < len(payload) && payload[end] != 0 { + end++ + } + + entry := string(payload[pos:end]) + k, v, ok := strings.Cut(entry, "=") + if !ok { + return nil, fmt.Errorf("malformed entry at offset %d: %q", crcSize+pos, entry) + } + vars[k] = v + + pos = end + 1 // skip null terminator + } + + return &Env{ + Vars: vars, + Size: len(data), + }, nil +} + +// Marshal serializes the environment back to the binary format. +// Keys are sorted for deterministic output. +func (e *Env) Marshal() ([]byte, error) { + if e.Size < crcSize+2 { + return nil, fmt.Errorf("env size too small: %d", e.Size) + } + + buf := make([]byte, e.Size) + dataSize := e.Size - crcSize + + // Build the payload: sorted key=value pairs separated by null bytes. + keys := make([]string, 0, len(e.Vars)) + for k := range e.Vars { + keys = append(keys, k) + } + sort.Strings(keys) + + pos := 0 + for _, k := range keys { + entry := k + "=" + e.Vars[k] + needed := len(entry) + 1 // +1 for null terminator + if pos+needed+1 > dataSize { + return nil, fmt.Errorf("environment data exceeds available space (%d bytes)", dataSize) + } + copy(buf[crcSize+pos:], entry) + pos += len(entry) + buf[crcSize+pos] = 0 // null terminator + pos++ + } + + // The double-null terminator is already present since the buffer is zero-initialized. + + // Compute and store CRC32 over the data portion. + payload := buf[crcSize:] + checksum := crc32.ChecksumIEEE(payload) + binary.LittleEndian.PutUint32(buf[:crcSize], checksum) + + return buf, nil +} diff --git a/ubootenv/ubootenv/ubootenv_test.go b/ubootenv/ubootenv/ubootenv_test.go new file mode 100644 index 00000000..8ba960c0 --- /dev/null +++ b/ubootenv/ubootenv/ubootenv_test.go @@ -0,0 +1,144 @@ +package ubootenv + +import ( + "encoding/binary" + "hash/crc32" + "testing" +) + +func makeEnv(size int, vars map[string]string) []byte { + buf := make([]byte, size) + pos := crcSize + for k, v := range vars { + entry := k + "=" + v + copy(buf[pos:], entry) + pos += len(entry) + buf[pos] = 0 + pos++ + } + crc := crc32.ChecksumIEEE(buf[crcSize:]) + binary.LittleEndian.PutUint32(buf[:crcSize], crc) + return buf +} + +func TestParseAndMarshalRoundTrip(t *testing.T) { + original := map[string]string{ + "bootcmd": "run distro_bootcmd", + "bootdelay": "3", + "ethaddr": "00:11:22:33:44:55", + } + data := makeEnv(DefaultEnvSize, original) + env, err := Parse(data) + if err != nil { + t.Fatalf("Parse() error: %v", err) + } + if len(env.Vars) != len(original) { + t.Fatalf("expected %d vars, got %d", len(original), len(env.Vars)) + } + for k, want := range original { + got, ok := env.Vars[k] + if !ok { + t.Errorf("missing key %q", k) + continue + } + if got != want { + t.Errorf("key %q: got %q, want %q", k, got, want) + } + } + out, err := env.Marshal() + if err != nil { + t.Fatalf("Marshal() error: %v", err) + } + if len(out) != DefaultEnvSize { + t.Fatalf("expected output size %d, got %d", DefaultEnvSize, len(out)) + } + env2, err := Parse(out) + if err != nil { + t.Fatalf("Parse(round-trip) error: %v", err) + } + for k, want := range original { + got := env2.Vars[k] + if got != want { + t.Errorf("round-trip key %q: got %q, want %q", k, got, want) + } + } +} + +func TestParseInvalidCRC(t *testing.T) { + data := makeEnv(DefaultEnvSize, map[string]string{"foo": "bar"}) + data[0] ^= 0xFF + _, err := Parse(data) + if err == nil { + t.Fatal("expected CRC mismatch error") + } +} + +func TestParseTooShort(t *testing.T) { + _, err := Parse([]byte{0, 0, 0}) + if err == nil { + t.Fatal("expected error for short data") + } +} + +func TestParseEmptyEnvironment(t *testing.T) { + data := makeEnv(DefaultEnvSize, map[string]string{}) + env, err := Parse(data) + if err != nil { + t.Fatalf("Parse() error: %v", err) + } + if len(env.Vars) != 0 { + t.Fatalf("expected 0 vars, got %d", len(env.Vars)) + } +} + +func TestSetAndDeleteVars(t *testing.T) { + original := map[string]string{ + "bootcmd": "bootm", + "bootdelay": "5", + "toremove": "gone", + } + data := makeEnv(DefaultEnvSize, original) + env, err := Parse(data) + if err != nil { + t.Fatalf("Parse() error: %v", err) + } + env.Vars["newvar"] = "hello" + env.Vars["bootdelay"] = "1" + delete(env.Vars, "toremove") + out, err := env.Marshal() + if err != nil { + t.Fatalf("Marshal() error: %v", err) + } + env2, err := Parse(out) + if err != nil { + t.Fatalf("Parse(after modification) error: %v", err) + } + expected := map[string]string{ + "bootcmd": "bootm", + "bootdelay": "1", + "newvar": "hello", + } + if len(env2.Vars) != len(expected) { + t.Fatalf("expected %d vars, got %d", len(expected), len(env2.Vars)) + } + for k, want := range expected { + if got := env2.Vars[k]; got != want { + t.Errorf("key %q: got %q, want %q", k, got, want) + } + } + if _, ok := env2.Vars["toremove"]; ok { + t.Error("expected toremove to be deleted") + } +} + +func TestMarshalOverflow(t *testing.T) { + env := &Env{ + Vars: make(map[string]string), + Size: 20, + } + env.Vars["a_very_long_variable_name"] = "a_very_long_value_that_exceeds_capacity" + _, err := env.Marshal() + if err == nil { + t.Fatal("expected overflow error") + } +}