From 149a8c91cf4291b07f756712a1283746b41c0d27 Mon Sep 17 00:00:00 2001 From: Drew Hudson-Viles Date: Thu, 29 Feb 2024 16:50:24 +0000 Subject: [PATCH] feat: major overhaul to enable multiple builders to be supported in the all phases * updates to provisioners * scan adjustments * refactored build, scanning and signing to remove Openstack dependencies * removed requirement for a digest being provided as it'll now fetch it from the image metadata. * added support for AMD gpus * Added some new options for OpenStack --- .github/workflows/pull_request.yaml | 2 +- .github/workflows/push.yaml | 90 --- .github/workflows/release.yml | 2 +- CHANGELOG.md | 16 +- README.md | 7 +- baski-example.yaml | 104 ++- docs/kubevirt.md | 13 + docs/openstack.md | 2 +- go.mod | 116 ++- go.sum | 549 +++++++++++--- pkg/cmd/build/build.go | 52 +- pkg/cmd/build/process.go | 39 +- pkg/cmd/root.go | 4 +- pkg/cmd/scan/existing.go | 140 ---- pkg/cmd/scan/scan.go | 32 +- pkg/cmd/scan/single.go | 98 --- pkg/cmd/sign/generate.go | 2 +- pkg/cmd/sign/image.go | 49 +- pkg/cmd/sign/validate.go | 10 +- pkg/constants/constants.go | 6 +- pkg/git/gitrepo_test.go | 1 - pkg/k8s/client.go | 63 ++ pkg/k8s/client_test.go | 94 +++ pkg/mock/{interfaces.go => base.go} | 139 ++-- pkg/mock/openstack.go | 425 +++++++++++ pkg/providers/openstack/compute.go | 4 +- pkg/providers/openstack/compute_test.go | 4 +- pkg/providers/openstack/image.go | 34 +- pkg/providers/openstack/image_test.go | 64 -- pkg/providers/packer/client.go | 296 ++++---- pkg/providers/packer/client_test.go | 689 +++++++++++++++++- pkg/providers/packer/kubevirt.go | 8 + pkg/providers/packer/openstack.go | 20 + pkg/providers/scanner/client.go | 273 +------ pkg/providers/scanner/openstack.go | 252 +++++++ pkg/providers/scanner/openstack_test.go | 66 ++ pkg/provisoner/kubevirt.go | 271 +++++++ pkg/provisoner/openstack.go | 391 ++++++++++ pkg/provisoner/openstack_test.go | 137 ++++ pkg/provisoner/provisoners.go | 102 +++ pkg/remote/sshconnect.go | 11 +- pkg/remote/sshconnect_test.go | 119 +-- pkg/s3/client.go | 114 --- pkg/s3/client_test.go | 80 -- pkg/server/handler/handler.go | 10 +- pkg/server/handler/handler_test.go | 6 +- pkg/server/server/server.go | 8 +- pkg/system/make.go | 2 +- pkg/trivy/config.go | 6 +- pkg/util/data/image.go | 51 -- pkg/util/data/image_test.go | 86 --- pkg/util/flags/base.go | 19 + pkg/util/flags/build.go | 73 +- pkg/util/flags/flags.go | 5 + pkg/util/flags/kubernetes.go | 37 + pkg/util/flags/kubevirt.go | 47 ++ pkg/util/flags/openstack.go | 61 +- pkg/util/flags/prefixes.go | 18 +- pkg/util/flags/publish.go | 62 -- pkg/util/flags/qemu.go | 43 ++ pkg/util/flags/s3.go | 6 +- pkg/util/flags/scan.go | 45 +- pkg/util/flags/sign.go | 71 +- .../{interfaces.go => interfaces/base.go} | 16 +- pkg/util/interfaces/openstack.go | 47 ++ pkg/util/sign/vault_test.go | 4 +- testhelpers/test_helpers.go | 77 -- ..._fixtures.go => test_openstack_helpers.go} | 102 ++- 68 files changed, 4021 insertions(+), 1871 deletions(-) delete mode 100644 .github/workflows/push.yaml create mode 100644 docs/kubevirt.md delete mode 100644 pkg/cmd/scan/existing.go delete mode 100644 pkg/cmd/scan/single.go create mode 100644 pkg/k8s/client.go create mode 100644 pkg/k8s/client_test.go rename pkg/mock/{interfaces.go => base.go} (93%) create mode 100644 pkg/mock/openstack.go create mode 100644 pkg/providers/packer/kubevirt.go create mode 100644 pkg/providers/packer/openstack.go create mode 100644 pkg/providers/scanner/openstack.go create mode 100644 pkg/providers/scanner/openstack_test.go create mode 100644 pkg/provisoner/kubevirt.go create mode 100644 pkg/provisoner/openstack.go create mode 100644 pkg/provisoner/openstack_test.go create mode 100644 pkg/provisoner/provisoners.go delete mode 100644 pkg/s3/client.go delete mode 100644 pkg/s3/client_test.go delete mode 100644 pkg/util/data/image.go delete mode 100644 pkg/util/data/image_test.go create mode 100644 pkg/util/flags/base.go create mode 100644 pkg/util/flags/kubernetes.go create mode 100644 pkg/util/flags/kubevirt.go delete mode 100644 pkg/util/flags/publish.go create mode 100644 pkg/util/flags/qemu.go rename pkg/util/{interfaces.go => interfaces/base.go} (80%) create mode 100644 pkg/util/interfaces/openstack.go delete mode 100644 testhelpers/test_helpers.go rename testhelpers/{test_fixtures.go => test_openstack_helpers.go} (77%) diff --git a/.github/workflows/pull_request.yaml b/.github/workflows/pull_request.yaml index f042fd9..8cf0a3f 100644 --- a/.github/workflows/pull_request.yaml +++ b/.github/workflows/pull_request.yaml @@ -3,7 +3,7 @@ name: Validate on Pull Request on: workflow_dispatch: pull_request: - types: [ opened, reopened, synchronized ] + types: [ opened, reopened, synchronize ] jobs: lint: diff --git a/.github/workflows/push.yaml b/.github/workflows/push.yaml deleted file mode 100644 index b81e4e6..0000000 --- a/.github/workflows/push.yaml +++ /dev/null @@ -1,90 +0,0 @@ -name: Validate on push - -on: - workflow_dispatch: - push: - branches-ignore: - - "main" - branch: - - "*" - -jobs: - lint: - name: Lint files - runs-on: ubuntu-22.04 - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-go@v4 - with: - go-version: '1.21.1' - - name: golangci-lint - uses: golangci/golangci-lint-action@v3 - with: - version: latest - skip-cache: true - args: --timeout=30m - test: - name: Run tests - runs-on: 'ubuntu-22.04' - needs: lint - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-go@v4 - with: - go-version: '1.21.1' - - run: go test -v -cover ./... - - security-checks: - runs-on: ubuntu-22.04 - needs: test - permissions: - contents: read - packages: write - id-token: write - steps: - - name: Build Local Baski Image - uses: drewbernetes/container-security-action@v0.0.2 - if: github.event_name != 'pull_request' - id: build-and-scan-baski - with: - image-repo: "registry.hudson-viles.uk/public" - repo-username: ${{ secrets.REGISTRY_PUBLIC_USER }} - repo-password: ${{ secrets.REGISTRY_PUBLIC_PASSWORD }} - image-name: baski - image-tag: ${{ github.ref_name }} - check-severity: CRITICAL - trivyignore-from-s3: true - s3-endpoint: "https://api.s3.hudson-viles.uk/" - s3-access-key: ${{secrets.S3_ACCESS_KEY}} - s3-secret-key: ${{secrets.S3_SECRET_KEY}} - s3-bucket: "trivyignores" - s3-path: "baski" - add-latest-tag: false - publish-image: false - cosign-private-key: ${{secrets.COSIGN_KEY}} - cosign-password: ${{secrets.COSIGN_PASSWORD}} - cosign-tlog: false - dockerfile-path: docker/baski - - name: Build Local Baski Server Image - uses: drewbernetes/container-security-action@v0.0.2 - if: github.event_name != 'pull_request' - id: build-and-scan-server - with: - image-repo: "registry.hudson-viles.uk/public" - repo-username: ${{ secrets.REGISTRY_PUBLIC_USER }} - repo-password: ${{ secrets.REGISTRY_PUBLIC_PASSWORD }} - image-name: baski-server - image-tag: ${{ github.ref_name }} - check-severity: CRITICAL - trivyignore-from-s3: true - s3-endpoint: "https://api.s3.hudson-viles.uk/" - s3-access-key: ${{secrets.S3_ACCESS_KEY}} - s3-secret-key: ${{secrets.S3_SECRET_KEY}} - s3-bucket: "trivyignores" - s3-path: "baski-server" - add-latest-tag: false - publish-image: false - cosign-private-key: ${{secrets.COSIGN_KEY}} - cosign-password: ${{secrets.COSIGN_PASSWORD}} - cosign-tlog: false - dockerfile-path: docker/server diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bd3ec2f..b6a9f36 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -32,7 +32,7 @@ jobs: CGO_ENABLED=0 go build -o "$BINARY_NAME" cmd/baski/main.go - name: Build Baski-Server run: | - CGO_ENABLED=0 go build -o "$SERVER_BINARY_NAME" cmd/baski/main.go + CGO_ENABLED=0 go build -o "$SERVER_BINARY_NAME" cmd/server/main.go - name: Release Notes run: | git log $(git describe HEAD~ --tags --abbrev=0)..HEAD --pretty='format:* %h %s%n * %an <%ae>' --no-merges >> ".github/RELEASE-TEMPLATE.md" diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e919dc..34ce27a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,23 @@ # Changelog +## [ 2024/02/29 - v1.1.0 ] + +### BREAKING CHANGES + +* changed `cloud` prefix to `infra` in flags and config. +* changed `build.nvidia` prefix to `build.gpu` in flags and config. +* changed `build.nvidia.enable-nvidia-support` prefix to `build.gpu.enable-gpu-support` in flags and config. + +### Changed/Added + +* Added KubeVirt as a build option. +* Supports AMD GPUs + ## [ 2024/02/15 - v1.0.0 ] ### Changed/Added + First release with: * Functioning support for OpenStack build, scan and signing. -* Baski Server +* Baski Server diff --git a/README.md b/README.md index 99001b9..1f56f68 100644 --- a/README.md +++ b/README.md @@ -21,9 +21,10 @@ The scanning and signing functionality are separate from the build meaning these | Cloud Provider | |--------------------------------| -| [Openstack](docs/openstack.md) | +| [OpenStack](docs/openstack.md) | +| [Kubevirt](docs/kubevirt.md) | -*More clouds could be supported but may not be maintained by Drewbernetes.* +*More clouds could be supported but may not be maintained directly by Drewbernetes.* # Usage @@ -47,7 +48,7 @@ For more flags and more info, run `baski --help` ### Running locally If you wish to run it locally then you can either build the binary and run it, or you can run it in docker by doing the -following: +following example for OpenStack: ```shell docker build -t baski:v0.0.0 -f docker/baski/Dockerfile . diff --git a/baski-example.yaml b/baski-example.yaml index 4604b40..8529827 100644 --- a/baski-example.yaml +++ b/baski-example.yaml @@ -1,5 +1,8 @@ -# The cloud section defines the cloud to use. -cloud: +# The infra section defines the infrastructure to use. +infra: + # type dictates which settings to use in the infra section below - must match the yaml key. + # IE, openstack, kubevirt etc + type: kubevirt # OpenStack support is a tricky one due to the varying nature of configurations that can exist across different setups. # The approach taken with this is a basic kolla-ansible install with no bells and whistles. Any additional configuration must be seriously considered before adding additional code/options to support it. openstack: @@ -18,24 +21,53 @@ cloud: # Whether to use a floating IP on the instance. use-floating-ip: true # The name of the network from which to get a floating IP from. - floating-ip-network-name: "Internet" + floating-ip-network-name: "public1" + # Specify a specific security group to use instead of "default" + security-group: "" # Sets the image visibility once it has been created. The cloud account being used must have permission to do this. image-visibility: "public" # The disk format image-disk-format: "raw" - # the volume type + # Whether to use block_storage_volume + use-blockstorage-volume: false + # Specify an existing SSH keypair to use + ssh-keypair-name: "drew-rsa" + # The private Key file to use with the ssh-keypair-name + ssh-privatekey-file: "/home//.ssh/id_rsa" + # The volume type volume-type: "" # The size of the storage volume volume-size: 0 # The rootfs-UUID. In testing this has only been required for bare-metal instances as the bare-metal instance needs know about the rootfs, so it knows what to boot. - rootfs-uuid: "ROOT_FS_UUID" + rootfs-uuid: "" + # KubeVirt support is a wrapper around QEMU builds however at the end of the build it will generate a PVC in the target Kubernetes cluster + kubevirt: + # qemu_binary enables the overriding of the qemu-system-x86_64 binary name + qemu_binary: "qemu-system-x86_64" + # disk_size describes the size of the VM disk + disk_size: "10G" + # The directory in which the resulting QCOW2 image will be stored. + # This will have the image-name stored within it resulting in: + # /tmp/image-output/{{build.image-prefix}}-{{date}}-{{unique-code}}/{{build.build-os}}-kube-v{{build.kubernetes-version}} + output-directory: "/tmp/image-output" + # This enables the image to be uploaded to an S3 endpoint as defined in the S3 object and is recommended if using the datavolume-from-S3 approach in KubeVirt. + store-in-s3: false + # The bucket in which to store the resulting image - required if store-in-s3 is true + image-bucket: "images" + # kubevirt_namespace is the namespace in which the DV will be created + image-namespace: "baski-vm-images" +# k8s contains kubernetes cluster options +k8s: + # kubeconfig_path is the path to the kubeconfig that will be used to generate the PVC for Kubevirt + kubeconfig_path: "/tmp/kubeconfig" -# Baski presumes there is an S3 endpoint available for the pulling of any items such as the .trivyignore and nvidia files. -# This is because the image builder defaults to this for the Nvidia support, and it is presumed that most if not all people can have/can set up an S3 endpoint for authenticated storage. +# Baski presumes there is an S3 endpoint available for the pulling of any items such as the .trivyignore and nvidia files (where required) as well as uploading any other items such as scan results and QEMU images (where required). +# This is because the image builder defaults to this for the NVIDIA support, and it is presumed that most if not all people can have/can set up an S3 endpoint for authenticated storage. s3: endpoint: "S3_ENDPOINT_URL" access-key: "ACCESS_KEY" secret-key: "SECRET_KEY" + region: "us-east-1" # If the S3 endpoint is being used, set this to true to instruct the aws ansible role to do the same. is-ceph: true @@ -46,10 +78,10 @@ build: # The OS to build for. This should match the source image. build-os: "ubuntu-2204" # The prefix to prepend to the name of the image that is built. The name will result in -yymmdd-unique_id. - image-prefix: "kube" + image-prefix: "kmi" # The repo to use for image building. This will default to the main image builder repo but can be updated if additional functionality is required in a fork. image-repo: "https://github.com/kubernetes-sigs/image-builder.git" - # The branch to checkout from the image repo + # The branch to use in the image repo image-repo-branch: "main" # The crictl version. crictl-version: "1.26.0" @@ -67,25 +99,40 @@ build: add-trivy: true # Whether to add Falco into the image. add-falco: true - # NVIDIA will soon be supported in the image builder https://github.com/kubernetes-sigs/image-builder/pull/1147. - # However, it has a prerequisite that means the operator should provide the NVIDIA license (.tok) and installer (.run) files via an S3 endpoint - # due to license restrictions by NVIDIA. These are not publicly available which is why this requirement is in place. + # GPU support is available in the image-builder project. + # NVIDIA support has a prerequisite that means the operator should provide the NVIDIA license (.tok) and installer (.run) files via an S3 endpoint + # due to license restrictions by NVIDIA. The vGPU drivers are not publicly available which is why this requirement is in place. # The image builder will not provide these files and if they are not supplied, the build will fail. - nvidia: - # enable NVIDIA driver install in the image. - enable-nvidia-support: true - # The NVIDIA driver version to be installed - Currently used for tagging metadata - may be removed in future in favor of parsing the filename. - nvidia-driver-version: "525.85.05" - # The S3 bucket to get the installer and license files from. + gpu: + # Enable GPU driver installs into the image. + enable-gpu-support: true + # The GPU architecture being used ("nvidia" or "amd" are currently supported). + gpu-vendor: "amd" + # The AMD driver version to be installed + amd-driver-version: "6.0.2" + # The AMD driver version to be installed + amd-deb-version: "6.0.60002-1" + # The AMDGPU Installer usecase + amd-usecase-version: "dkms" + # The NVIDIA driver version to be installed - Currently used for tagging metadata + nvidia-driver-version: "535.129.03" + # The S3 bucket to get the NVIDIA installer and license files from. nvidia-bucket: "nvidia" - # The installer file name in the bucket. - nvidia-installer-location: "NVIDIA-Linux-x86_64-525.85.05-grid.run" - # The license file name in the bucket. + # The NVIDIA installer file name in the bucket. + nvidia-installer-location: "NVIDIA-Linux-x86_64-535.129.03-grid.run" + # The NVIDIA license file name in the bucket. nvidia-tok-location: "client_configuration_token.tok" - # The feature type to configure the GRIDD service with - see NVIDIA docs for more information on this. + # The feature type to configure the NVIDIA GRIDD service with - see NVIDIA docs for more information on this. nvidia-gridd-feature-type: "4" # The additional-images section should be a list of container images to bake into the image. - additional-images: [ ] + additional-images: [] + # Any additional metadata/tags to add to the image as a map[string]string + # The following are available via the `generateBuilderMetadata` func in the provisioner and any additional data added here will be appended in that func + # "os": o.BuildOS, + # "k8s": o.KubeVersion, + # "gpu": gpu, + # "date": time.Now().Format(time.RFC3339), + additional-metadata: {} # Scan stage options scan: @@ -96,7 +143,7 @@ scan: # Used in the `existing` command - takes a wildcard and scans all images that match it. multiple: # The image-search is used to filter images. If this string is within the name of the image, it'll be selected for scanning. - image-search: "kube-" + image-search: "kmi-" # How many concurrent scans to perform. concurrency: 2 # Override the cloud.[provider].flavor for the scan. This can help avoid using a large or gpu enabled node just for scanning. @@ -117,7 +164,7 @@ scan: trivyignore-filename: ".trivyignore" # A list of CVEs to add to the ignore list. If a file is also provided, this list will be appended to the list within the file. # If no file exists, then the file will be created with the list contents - trivyignore-list: [ ] + trivyignore-list: [] # Sign stage options sign: @@ -133,8 +180,8 @@ sign: # The Vault token. token: "VAULT_TOKEN" # The mount path within vault. - mount-path: "kv/baski" - # The name of the secret in the mount path. + mount-path: "baski" + # The name of the secret in the mount path - there should be a `password`, `private-key` and public-key` for cosign stored in here. secret-name: "signing-keys" # The ID of the image to sign. image-id: "" # Used for existing images @@ -142,6 +189,3 @@ sign: private-key: "" # The public key to use in the validation process - this takes precedence over vault. public-key: "" - # If you would like to validate an image signing, this allows you to put the digest in and validate the image. - # This will be deprecated and then removed soon to make way for fetching the digest from the metadata of the image where it is currently stored. - digest: "" diff --git a/docs/kubevirt.md b/docs/kubevirt.md new file mode 100644 index 0000000..45e94ce --- /dev/null +++ b/docs/kubevirt.md @@ -0,0 +1,13 @@ +### KubeVirt guidelines + +It is expected that you have a kubernetes cluster with KubeVirt installed already. + +If you wish to make use of the [CDI](https://kubevirt.io/user-guide/operations/containerized_data_importer/) support +then it should be installed, and you should also have an S3 endpoint to which you can push the image that is built. + +Baski will push the image to the S3 endpoint then create a **DataVolume** (and required credentials if they don't exist) +that will reference the image at the S3 endpoint as a source. + +If you wish to manually do this yourself then set `kubevirt.store-in-s3` to false. + +Then craft a `baski.yaml` file based on the [example](../baski-example.yaml) supplied and run the commands you require. diff --git a/docs/openstack.md b/docs/openstack.md index a6c8169..97acf25 100644 --- a/docs/openstack.md +++ b/docs/openstack.md @@ -1,4 +1,4 @@ -### Openstack guidelines +### OpenStack guidelines It is expected that you have a network and sufficient security groups in place to run this.
It will not create the network or security groups for you. diff --git a/go.mod b/go.mod index 211ccdb..8fa7058 100644 --- a/go.mod +++ b/go.mod @@ -5,23 +5,25 @@ go 1.21 toolchain go1.21.3 require ( - github.com/aws/aws-sdk-go-v2 v1.24.1 - github.com/aws/aws-sdk-go-v2/config v1.26.4 - github.com/aws/aws-sdk-go-v2/credentials v1.16.15 - github.com/aws/aws-sdk-go-v2/service/s3 v1.48.0 github.com/deepmap/oapi-codegen v1.16.2 - github.com/getkin/kin-openapi v0.122.0 + github.com/drewbernetes/simple-s3 v0.1.0 + github.com/getkin/kin-openapi v0.123.0 github.com/go-git/go-git/v5 v5.11.0 - github.com/google/uuid v1.5.0 - github.com/gophercloud/gophercloud v1.8.0 + github.com/google/uuid v1.6.0 + github.com/gophercloud/gophercloud v1.10.0 github.com/gophercloud/utils v0.0.0-20231010081019-80377eca5d56 github.com/gorilla/mux v1.8.1 - github.com/hashicorp/vault/api v1.10.0 + github.com/hashicorp/vault/api v1.12.0 github.com/pkg/sftp v1.13.6 github.com/spf13/cobra v1.8.0 github.com/spf13/viper v1.18.2 go.uber.org/mock v0.4.0 - golang.org/x/crypto v0.18.0 + golang.org/x/crypto v0.20.0 + k8s.io/api v0.29.2 + k8s.io/apimachinery v0.29.2 + k8s.io/client-go v12.0.0+incompatible + kubevirt.io/client-go v1.1.1 + kubevirt.io/containerized-data-importer-api v1.58.1 ) require ( @@ -31,31 +33,38 @@ require ( github.com/CloudyKit/jet/v6 v6.2.0 // indirect github.com/Joker/jade v1.1.3 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c // indirect + github.com/ProtonMail/go-crypto v1.0.0 // indirect github.com/Shopify/goreferrer v0.0.0-20220729165902-8cddb4f5de06 // indirect github.com/andybalholm/brotli v1.1.0 // indirect github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect - github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.4 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10 // indirect - github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2 // indirect - github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.10 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.2.10 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.10 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.18.6 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 // indirect - github.com/aws/smithy-go v1.19.0 // indirect + github.com/aws/aws-sdk-go-v2 v1.25.2 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.1 // indirect + github.com/aws/aws-sdk-go-v2/config v1.27.4 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.17.4 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.15.2 // indirect + github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.16.6 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.2 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.2 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.2 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.1 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.2 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.2 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.2 // indirect + github.com/aws/aws-sdk-go-v2/service/s3 v1.51.1 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.20.1 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.1 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.28.1 // indirect + github.com/aws/smithy-go v1.20.1 // indirect github.com/aymerick/douceur v0.2.0 // indirect - github.com/bytedance/sonic v1.10.2 // indirect + github.com/bytedance/sonic v1.11.2 // indirect github.com/cenkalti/backoff/v3 v3.2.2 // indirect github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect github.com/chenzhuoyu/iasm v0.9.1 // indirect github.com/cloudflare/circl v1.3.7 // indirect github.com/cyphar/filepath-securejoin v0.2.4 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/emicklei/go-restful/v3 v3.11.3 // indirect github.com/emirpasic/gods v1.18.1 // indirect github.com/fatih/structs v1.1.0 // indirect github.com/flosch/pongo2/v4 v4.0.2 // indirect @@ -65,16 +74,22 @@ require ( github.com/gin-gonic/gin v1.9.1 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/go-billy/v5 v5.5.0 // indirect - github.com/go-jose/go-jose/v3 v3.0.1 // indirect + github.com/go-jose/go-jose/v3 v3.0.2 // indirect + github.com/go-logr/logr v1.4.1 // indirect github.com/go-openapi/jsonpointer v0.20.2 // indirect - github.com/go-openapi/swag v0.22.7 // indirect + github.com/go-openapi/jsonreference v0.20.4 // indirect + github.com/go-openapi/swag v0.22.9 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.17.0 // indirect + github.com/go-playground/validator/v10 v10.18.0 // indirect github.com/goccy/go-json v0.10.2 // indirect + github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/protobuf v1.5.3 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/gomarkdown/markdown v0.0.0-20231222211730-1d6d20845b47 // indirect + github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 // indirect + github.com/google/gofuzz v1.2.0 // indirect github.com/gorilla/css v1.0.1 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect @@ -86,25 +101,27 @@ require ( github.com/hashicorp/go-sockaddr v1.0.6 // indirect github.com/hashicorp/go-uuid v1.0.3 // indirect github.com/hashicorp/hcl v1.0.1-vault-5 // indirect + github.com/imdario/mergo v0.3.16 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/invopop/yaml v0.2.0 // indirect github.com/iris-contrib/schema v0.0.6 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/kataras/blocks v0.0.8 // indirect github.com/kataras/golog v0.1.11 // indirect - github.com/kataras/iris/v12 v12.2.9 // indirect + github.com/kataras/iris/v12 v12.2.10 // indirect github.com/kataras/pio v0.0.13 // indirect github.com/kataras/sitemap v0.0.6 // indirect github.com/kataras/tunnel v0.0.4 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect - github.com/klauspost/compress v1.17.4 // indirect - github.com/klauspost/cpuid/v2 v2.2.6 // indirect + github.com/klauspost/compress v1.17.7 // indirect + github.com/klauspost/cpuid/v2 v2.2.7 // indirect github.com/kr/fs v0.1.0 // indirect github.com/labstack/echo/v4 v4.11.4 // indirect github.com/labstack/gommon v0.4.2 // indirect - github.com/leodido/go-urn v1.2.4 // indirect + github.com/leodido/go-urn v1.4.0 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mailgun/raymond/v2 v2.0.48 // indirect github.com/mailru/easyjson v0.7.7 // indirect @@ -116,6 +133,9 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/openshift/api v0.0.0-20240228145634-217662e7e95f // indirect + github.com/openshift/custom-resource-status v1.1.2 // indirect github.com/pelletier/go-toml/v2 v2.1.1 // indirect github.com/perimeterx/marshmallow v1.1.5 // indirect github.com/pjbgf/sha1cd v0.3.0 // indirect @@ -132,8 +152,8 @@ require ( github.com/spf13/cast v1.6.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/subosito/gotenv v1.6.0 // indirect - github.com/tdewolff/minify/v2 v2.20.14 // indirect - github.com/tdewolff/parse/v2 v2.7.9 // indirect + github.com/tdewolff/minify/v2 v2.20.18 // indirect + github.com/tdewolff/parse/v2 v2.7.12 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect @@ -144,16 +164,34 @@ require ( github.com/yosssi/ace v0.0.5 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/arch v0.7.0 // indirect - golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 // indirect - golang.org/x/mod v0.14.0 // indirect - golang.org/x/net v0.20.0 // indirect - golang.org/x/sys v0.16.0 // indirect + golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect + golang.org/x/mod v0.15.0 // indirect + golang.org/x/net v0.21.0 // indirect + golang.org/x/oauth2 v0.17.0 // indirect + golang.org/x/sys v0.17.0 // indirect + golang.org/x/term v0.17.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.5.0 // indirect - golang.org/x/tools v0.17.0 // indirect + golang.org/x/tools v0.18.0 // indirect + google.golang.org/appengine v1.6.8 // indirect google.golang.org/protobuf v1.32.0 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/klog/v2 v2.120.1 // indirect + k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect + k8s.io/utils v0.0.0-20240102154912-e7106e64919e // indirect + kubevirt.io/controller-lifecycle-operator-sdk/api v0.2.4 // indirect + sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect + sigs.k8s.io/yaml v1.4.0 // indirect +) + +replace ( + k8s.io/api => k8s.io/api v0.29.1 + k8s.io/apimachinery => k8s.io/apimachinery v0.29.1 + k8s.io/cli-runtime => k8s.io/cli-runtime v0.29.1 + k8s.io/client-go => k8s.io/client-go v0.29.1 ) diff --git a/go.sum b/go.sum index 7e71a47..4ec4305 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53 h1:sR+/8Yb4slttB4vD+b9btVEnWgL3Q00OBTzVT8B9C0c= @@ -13,8 +15,11 @@ github.com/Joker/jade v1.1.3/go.mod h1:T+2WLyt7VH6Lp0TRxQrUYEs64nRc83wkMQrfeIQKd github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c h1:kMFnB0vCcX7IL/m9Y5LO+KQYv+t1CQOiFe6+SV2J7bE= -github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= +github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= +github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78= +github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= github.com/Shopify/goreferrer v0.0.0-20220729165902-8cddb4f5de06 h1:KkH3I3sJuOLP3TjA/dfr4NAY8bghDwnXiU7cTKxQqo0= github.com/Shopify/goreferrer v0.0.0-20220729165902-8cddb4f5de06/go.mod h1:7erjKLwalezA0k99cWs5L11HWOAPNjdUZ6RxH1BXbbM= @@ -28,52 +33,56 @@ github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7D github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= -github.com/aws/aws-sdk-go-v2 v1.24.1 h1:xAojnj+ktS95YZlDf0zxWBkbFtymPeDP+rvUQIH3uAU= -github.com/aws/aws-sdk-go-v2 v1.24.1/go.mod h1:LNh45Br1YAkEKaAqvmE1m8FUx6a5b/V0oAKV7of29b4= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.4 h1:OCs21ST2LrepDfD3lwlQiOqIGp6JiEUqG84GzTDoyJs= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.4/go.mod h1:usURWEKSNNAcAZuzRn/9ZYPT8aZQkR7xcCtunK/LkJo= -github.com/aws/aws-sdk-go-v2/config v1.26.4 h1:Juj7LhtxNudNUlfX22K5AnLafO+v4eq9PA3VWSCIQs4= -github.com/aws/aws-sdk-go-v2/config v1.26.4/go.mod h1:tioqQ7wvxMYnTDpoTTLHhV3Zh+z261i/f2oz+ds8eNI= -github.com/aws/aws-sdk-go-v2/credentials v1.16.15 h1:P0/m1LU08MF2kRzx4P//+7lNjiJod1z4xI2WpWhdpTQ= -github.com/aws/aws-sdk-go-v2/credentials v1.16.15/go.mod h1:pgtMCf7Dx4GWw5EpHOTc2Sy17LIP0A0N2C9nQ83pQ/0= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11 h1:c5I5iH+DZcH3xOIMlz3/tCKJDaHFwYEmxvlh2fAcFo8= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11/go.mod h1:cRrYDYAMUohBJUtUnOhydaMHtiK/1NZ0Otc9lIb6O0Y= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10 h1:vF+Zgd9s+H4vOXd5BMaPWykta2a6Ih0AKLq/X6NYKn4= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10/go.mod h1:6BkRjejp/GR4411UGqkX8+wFMbFbqsUIimfK4XjOKR4= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10 h1:nYPe006ktcqUji8S2mqXf9c/7NdiKriOwMvWQHgYztw= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10/go.mod h1:6UV4SZkVvmODfXKql4LCbaZUpF7HO2BX38FgBf9ZOLw= -github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2 h1:GrSw8s0Gs/5zZ0SX+gX4zQjRnRsMJDJ2sLur1gRBhEM= -github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2/go.mod h1:6fQQgfuGmw8Al/3M2IgIllycxV7ZW7WCdVSqfBeUiCY= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.10 h1:5oE2WzJE56/mVveuDZPJESKlg/00AaS2pY2QZcnxg4M= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.10/go.mod h1:FHbKWQtRBYUz4vO5WBWjzMD2by126ny5y/1EoaWoLfI= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 h1:/b31bi3YVNlkzkBrm9LfpaKoaYZUxIAj4sHfOTmLfqw= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4/go.mod h1:2aGXHFmbInwgP9ZfpmdIfOELL79zhdNYNmReK8qDfdQ= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.2.10 h1:L0ai8WICYHozIKK+OtPzVJBugL7culcuM4E4JOpIEm8= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.2.10/go.mod h1:byqfyxJBshFk0fF9YmK0M0ugIO8OWjzH2T3bPG4eGuA= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10 h1:DBYTXwIGQSGs9w4jKm60F5dmCQ3EEruxdc0MFh+3EY4= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10/go.mod h1:wohMUQiFdzo0NtxbBg0mSRGZ4vL3n0dKjLTINdcIino= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.10 h1:KOxnQeWy5sXyS37fdKEvAsGHOr9fa/qvwxfJurR/BzE= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.10/go.mod h1:jMx5INQFYFYB3lQD9W0D8Ohgq6Wnl7NYOJ2TQndbulI= -github.com/aws/aws-sdk-go-v2/service/s3 v1.48.0 h1:PJTdBMsyvra6FtED7JZtDpQrIAflYDHFoZAu/sKYkwU= -github.com/aws/aws-sdk-go-v2/service/s3 v1.48.0/go.mod h1:4qXHrG1Ne3VGIMZPCB8OjH/pLFO94sKABIusjh0KWPU= -github.com/aws/aws-sdk-go-v2/service/sso v1.18.6 h1:dGrs+Q/WzhsiUKh82SfTVN66QzyulXuMDTV/G8ZxOac= -github.com/aws/aws-sdk-go-v2/service/sso v1.18.6/go.mod h1:+mJNDdF+qiUlNKNC3fxn74WWNN+sOiGOEImje+3ScPM= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7 h1:QPMJf+Jw8E1l7zqhZmMlFw6w1NmfkfiSK8mS4zOx3BA= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7/go.mod h1:ykf3COxYI0UJmxcfcxcVuz7b6uADi1FkiUz6Eb7AgM8= -github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 h1:NzO4Vrau795RkUdSHKEwiR01FaGzGOH1EETJ+5QHnm0= -github.com/aws/aws-sdk-go-v2/service/sts v1.26.7/go.mod h1:6h2YuIoxaMSCFf5fi1EgZAwdfkGMgDY+DVfa61uLe4U= -github.com/aws/smithy-go v1.19.0 h1:KWFKQV80DpP3vJrrA9sVAHQ5gc2z8i4EzrLhLlWXcBM= -github.com/aws/smithy-go v1.19.0/go.mod h1:NukqUGpCZIILqqiV0NIjeFh24kd/FAa4beRb6nbIUPE= +github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/aws/aws-sdk-go-v2 v1.25.2 h1:/uiG1avJRgLGiQM9X3qJM8+Qa6KRGK5rRPuXE0HUM+w= +github.com/aws/aws-sdk-go-v2 v1.25.2/go.mod h1:Evoc5AsmtveRt1komDwIsjHFyrP5tDuF1D1U+6z6pNo= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.1 h1:gTK2uhtAPtFcdRRJilZPx8uJLL2J85xK11nKtWL0wfU= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.1/go.mod h1:sxpLb+nZk7tIfCWChfd+h4QwHNUR57d8hA1cleTkjJo= +github.com/aws/aws-sdk-go-v2/config v1.27.4 h1:AhfWb5ZwimdsYTgP7Od8E9L1u4sKmDW2ZVeLcf2O42M= +github.com/aws/aws-sdk-go-v2/config v1.27.4/go.mod h1:zq2FFXK3A416kiukwpsd+rD4ny6JC7QSkp4QdN1Mp2g= +github.com/aws/aws-sdk-go-v2/credentials v1.17.4 h1:h5Vztbd8qLppiPwX+y0Q6WiwMZgpd9keKe2EAENgAuI= +github.com/aws/aws-sdk-go-v2/credentials v1.17.4/go.mod h1:+30tpwrkOgvkJL1rUZuRLoxcJwtI/OkeBLYnHxJtVe0= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.15.2 h1:AK0J8iYBFeUk2Ax7O8YpLtFsfhdOByh2QIkHmigpRYk= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.15.2/go.mod h1:iRlGzMix0SExQEviAyptRWRGdYNo3+ufW/lCzvKVTUc= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.16.6 h1:prcsGA3onmpc7ea1W/m+SMj4uOn5vZ63uJp805UhJJs= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.16.6/go.mod h1:7eQrvATnVFDY0WfMYhfKkSQ1YtZlClT71fAAlsA1s34= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.2 h1:bNo4LagzUKbjdxE0tIcR9pMzLR2U/Tgie1Hq1HQ3iH8= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.2/go.mod h1:wRQv0nN6v9wDXuWThpovGQjqF1HFdcgWjporw14lS8k= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.2 h1:EtOU5jsPdIQNP+6Q2C5e3d65NKT1PeCiQk+9OdzO12Q= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.2/go.mod h1:tyF5sKccmDz0Bv4NrstEr+/9YkSPJHrcO7UsUKf7pWM= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.2 h1:en92G0Z7xlksoOylkUhuBSfJgijC7rHVLRdnIlHEs0E= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.2/go.mod h1:HgtQ/wN5G+8QSlK62lbOtNwQ3wTSByJ4wH2rCkPt+AE= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.1 h1:EyBZibRTVAs6ECHZOw5/wlylS9OcTzwyjeQMudmREjE= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.1/go.mod h1:JKpmtYhhPs7D97NL/ltqz7yCkERFW5dOlHyVl66ZYF8= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.2 h1:zSdTXYLwuXDNPUS+V41i1SFDXG7V0ITp0D9UT9Cvl18= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.2/go.mod h1:v8m8k+qVy95nYi7d56uP1QImleIIY25BPiNJYzPBdFE= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.2 h1:5ffmXjPtwRExp1zc7gENLgCPyHFbhEPwVTkTiH9niSk= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.2/go.mod h1:Ru7vg1iQ7cR4i7SZ/JTLYN9kaXtbL69UdgG0OQWQxW0= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.2 h1:1oY1AVEisRI4HNuFoLdRUB0hC63ylDAN6Me3MrfclEg= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.2/go.mod h1:KZ03VgvZwSjkT7fOetQ/wF3MZUvYFirlI1H5NklUNsY= +github.com/aws/aws-sdk-go-v2/service/s3 v1.51.1 h1:juZ+uGargZOrQGNxkVHr9HHR/0N+Yu8uekQnV7EAVRs= +github.com/aws/aws-sdk-go-v2/service/s3 v1.51.1/go.mod h1:SoR0c7Jnq8Tpmt0KSLXIavhjmaagRqQpe9r70W3POJg= +github.com/aws/aws-sdk-go-v2/service/sso v1.20.1 h1:utEGkfdQ4L6YW/ietH7111ZYglLJvS+sLriHJ1NBJEQ= +github.com/aws/aws-sdk-go-v2/service/sso v1.20.1/go.mod h1:RsYqzYr2F2oPDdpy+PdhephuZxTfjHQe7SOBcZGoAU8= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.1 h1:9/GylMS45hGGFCcMrUZDVayQE1jYSIN6da9jo7RAYIw= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.1/go.mod h1:YjAPFn4kGFqKC54VsHs5fn5B6d+PCY2tziEa3U/GB5Y= +github.com/aws/aws-sdk-go-v2/service/sts v1.28.1 h1:3I2cBEYgKhrWlwyZgfpSO2BpaMY1LHPqXYk/QGlu2ew= +github.com/aws/aws-sdk-go-v2/service/sts v1.28.1/go.mod h1:uQ7YYKZt3adCRrdCBREm1CD3efFLOUNH77MrUCvx5oA= +github.com/aws/smithy-go v1.20.1 h1:4SZlSlMr36UEqC7XOyRVb27XMeZubNcBNN+9IgEPIQw= +github.com/aws/smithy-go v1.20.1/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM= -github.com/bytedance/sonic v1.10.2 h1:GQebETVBxYB7JGWJtLBi07OVzWwt+8dWA00gEVW2ZFE= -github.com/bytedance/sonic v1.10.2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= +github.com/bytedance/sonic v1.11.2 h1:ywfwo0a/3j9HR8wsYGWsIWl2mvRsI950HyoxiBERw5A= +github.com/bytedance/sonic v1.11.2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= github.com/cenkalti/backoff/v3 v3.2.2 h1:cfUAAO3yvKMYKPrvhDuHSwQnhZNk/RMHKdZqKTxfm6M= github.com/cenkalti/backoff/v3 v3.2.2/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0= @@ -81,10 +90,15 @@ github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpV github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= github.com/chenzhuoyu/iasm v0.9.1 h1:tUHQJXo3NhBqw6s33wkGn9SP3bvrWLdlVIJ3hQBL7P0= github.com/chenzhuoyu/iasm v0.9.1/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -93,10 +107,22 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/deepmap/oapi-codegen v1.16.2 h1:xGHx0dNqYfy9gE8a7AVgVM8Sd5oF9SEgePzP+UPAUXI= github.com/deepmap/oapi-codegen v1.16.2/go.mod h1:rdYoEA2GE+riuZ91DvpmBX9hJbQpuY9wchXpfQ3n+ho= +github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/drewbernetes/simple-s3 v0.1.0 h1:HW1tAjss6tGpsxCxguU4qsdOHm1svcxrr8U5+0glOKc= +github.com/drewbernetes/simple-s3 v0.1.0/go.mod h1:+adsKWwDFHBdcz4mOU9i2TdI6SrDWOKE3kNNsoik/es= github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU= github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= +github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emicklei/go-restful v2.15.0+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emicklei/go-restful/v3 v3.8.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emicklei/go-restful/v3 v3.11.3 h1:yagOQz/38xJmcNeZJtrUcKjkHRltIaIFXKWeG1SkWGE= +github.com/emicklei/go-restful/v3 v3.11.3/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= @@ -105,12 +131,16 @@ github.com/flosch/pongo2/v4 v4.0.2 h1:gv+5Pe3vaSVmiJvh/BZa82b7/00YUGm0PIyVVLop0H github.com/flosch/pongo2/v4 v4.0.2/go.mod h1:B5ObFANs/36VwxxlgKpdchIJHMvHB562PW+BWPhwZD8= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= -github.com/getkin/kin-openapi v0.122.0 h1:WB9Jbl0Hp/T79/JF9xlSW5Kl9uYdk/AWD0yAd9HOM10= -github.com/getkin/kin-openapi v0.122.0/go.mod h1:PCWw/lfBrJY4HcdqE3jj+QFkaFK8ABoqo7PvqVhXXqw= +github.com/getkin/kin-openapi v0.76.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg= +github.com/getkin/kin-openapi v0.123.0 h1:zIik0mRwFNLyvtXK274Q6ut+dPh6nlxBp0x7mNrPhs8= +github.com/getkin/kin-openapi v0.123.0/go.mod h1:wb1aSZA/iWmorQP9KTAS/phLj/t17B5jT7+fS8ed9NM= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= @@ -125,49 +155,113 @@ github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMj github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= github.com/go-git/go-git/v5 v5.11.0 h1:XIZc1p+8YzypNr34itUfSvYJcv+eYdTnTvOZ2vD3cA4= github.com/go-git/go-git/v5 v5.11.0/go.mod h1:6GFcX2P3NM7FPBfpePbpLd21XxsgdAt+lKqXmCUiUCY= -github.com/go-jose/go-jose/v3 v3.0.1 h1:pWmKFVtt+Jl0vBZTIpz/eAKwsm6LkIxDVVbFHKkchhA= -github.com/go-jose/go-jose/v3 v3.0.1/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8= +github.com/go-jose/go-jose/v3 v3.0.2 h1:2Edjn8Nrb44UvTdp84KU0bBPs1cO7noRCybtS3eJEUQ= +github.com/go-jose/go-jose/v3 v3.0.2/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= +github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= +github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= github.com/go-openapi/jsonpointer v0.20.2 h1:mQc3nmndL8ZBzStEo3JYF8wzmeWffDH4VbXz58sAx6Q= github.com/go-openapi/jsonpointer v0.20.2/go.mod h1:bHen+N0u1KEO3YlmqOjTT9Adn1RfD91Ar825/PuiRVs= -github.com/go-openapi/swag v0.22.7 h1:JWrc1uc/P9cSomxfnsFSVWoE1FW6bNbrVPmpQYpCcR8= -github.com/go-openapi/swag v0.22.7/go.mod h1:Gl91UqO+btAM0plGGxHqJcQZ1ZTy6jbmridBTsDy8A0= +github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= +github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= +github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns= +github.com/go-openapi/jsonreference v0.20.1/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/jsonreference v0.20.4 h1:bKlDxQxQJgwpUSgOENiMPzCTBVuc7vTdXSSgNeAhojU= +github.com/go-openapi/jsonreference v0.20.4/go.mod h1:5pZJyJP2MnYCpoeoMAql78cCHauHj0V9Lhc506VOpw4= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-openapi/swag v0.22.9 h1:XX2DssF+mQKM2DHsbgZK74y/zj4mo9I99+89xUmuZCE= +github.com/go-openapi/swag v0.22.9/go.mod h1:3/OXnFfnMAwBD099SwYRk7GD3xOrr1iL7d/XNLXVVwE= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.17.0 h1:SmVVlfAOtlZncTxRuinDPomC2DkXJ4E5T9gDA0AIH74= -github.com/go-playground/validator/v10 v10.17.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/go-playground/validator/v10 v10.18.0 h1:BvolUXjp4zuvkZ5YN5t7ebzbhlUtPsPm2S9NAZ5nl9U= +github.com/go-playground/validator/v10 v10.18.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= 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/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/gomarkdown/markdown v0.0.0-20231222211730-1d6d20845b47 h1:k4Tw0nt6lwro3Uin8eqoET7MDA4JnT8YgbCjc/g5E3k= github.com/gomarkdown/markdown v0.0.0-20231222211730-1d6d20845b47/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= +github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 h1:0VpGH+cDhbDtdcweoyCVsF3fhN8kejK6rFe/2FFX2nU= +github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49/go.mod h1:BkkQ4L1KS1xMt2aWSPStnn55ChGC0DPOn2FQYj+f25M= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= -github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= +github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= github.com/gophercloud/gophercloud v1.3.0/go.mod h1:aAVqcocTSXh2vYFZ1JTvx4EQmfgzxRcNupUfxZbBNDM= -github.com/gophercloud/gophercloud v1.8.0 h1:TM3Jawprb2NrdOnvcHhWJalmKmAmOGgfZElM/3oBYCk= -github.com/gophercloud/gophercloud v1.8.0/go.mod h1:aAVqcocTSXh2vYFZ1JTvx4EQmfgzxRcNupUfxZbBNDM= +github.com/gophercloud/gophercloud v1.10.0 h1:watRMsaMDlSLuLkpLeLSQ87yvcuwIajNg6A5uLcjoIU= +github.com/gophercloud/gophercloud v1.10.0/go.mod h1:aAVqcocTSXh2vYFZ1JTvx4EQmfgzxRcNupUfxZbBNDM= github.com/gophercloud/utils v0.0.0-20231010081019-80377eca5d56 h1:sH7xkTfYzxIEgzq1tDHIMKRh1vThOEOGNsettdEeLbE= github.com/gophercloud/utils v0.0.0-20231010081019-80377eca5d56/go.mod h1:VSalo4adEk+3sNkmVJLnhHoOyOYYS8sTWLG4mv5BKto= github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8= github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -194,8 +288,12 @@ github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/C github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/hcl v1.0.1-vault-5 h1:kI3hhbbyzr4dldA8UdTb7ZlVVlI2DACdCfz31RPDgJM= github.com/hashicorp/hcl v1.0.1-vault-5/go.mod h1:XYhtn6ijBSAj6n4YqAaf7RBPS4I06AItNorpy+MoQNM= -github.com/hashicorp/vault/api v1.10.0 h1:/US7sIjWN6Imp4o/Rj1Ce2Nr5bki/AXi9vAW3p2tOJQ= -github.com/hashicorp/vault/api v1.10.0/go.mod h1:jo5Y/ET+hNyz+JnKDt8XLAdKs+AM0G5W0Vp1IrFI8N8= +github.com/hashicorp/vault/api v1.12.0 h1:meCpJSesvzQyao8FCOgk2fGdoADAnbDu2WPJN1lDLJ4= +github.com/hashicorp/vault/api v1.12.0/go.mod h1:si+lJCYO7oGkIoNPAN8j3azBLTn9SjMGS+jFaHd1Cck= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= +github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/imkira/go-interpol v1.1.0 h1:KIiKr0VSG2CUW1hl1jpiyuzuJeKUUpC8iM1AIE7N1Vk= github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= @@ -208,8 +306,13 @@ github.com/iris-contrib/schema v0.0.6 h1:CPSBLyx2e91H2yJzPuhGuifVRnZBBJ3pCOMbOvP github.com/iris-contrib/schema v0.0.6/go.mod h1:iYszG0IOsuIsfzjymw1kMzTL8YQcCWlm65f3wX8J5iA= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= @@ -217,8 +320,8 @@ github.com/kataras/blocks v0.0.8 h1:MrpVhoFTCR2v1iOOfGng5VJSILKeZZI+7NGfxEh3SUM= github.com/kataras/blocks v0.0.8/go.mod h1:9Jm5zx6BB+06NwA+OhTbHW1xkMOYxahnqTN5DveZ2Yg= github.com/kataras/golog v0.1.11 h1:dGkcCVsIpqiAMWTlebn/ZULHxFvfG4K43LF1cNWSh20= github.com/kataras/golog v0.1.11/go.mod h1:mAkt1vbPowFUuUGvexyQ5NFW6djEgGyxQBIARJ0AH4A= -github.com/kataras/iris/v12 v12.2.9 h1:vSrjFmZRfXqHjmkvitj32O5JtEVrbPPHXxrAIqidJcM= -github.com/kataras/iris/v12 v12.2.9/go.mod h1:lgp2aaapAct2HRdQMn4zIgKqWET7WfW1LWEwDElxxHA= +github.com/kataras/iris/v12 v12.2.10 h1:rEJVM7qMoyhv8wpgkA1yGxibFcONE0jkJ70LFLibTAA= +github.com/kataras/iris/v12 v12.2.10/go.mod h1:z4+E+kLMqZ7U4WtDsYfFnG7BjMTXLkdzMAXLVMLnMNs= github.com/kataras/pio v0.0.13 h1:x0rXVX0fviDTXOOLOmr4MUxOabu1InVSTu5itF8CXCM= github.com/kataras/pio v0.0.13/go.mod h1:k3HNuSw+eJ8Pm2lA4lRhg3DiCjVgHlP8hmXApSej3oM= github.com/kataras/sitemap v0.0.6 h1:w71CRMMKYMJh6LR2wTgnk5hSgjVNB9KL60n5e2KHvLY= @@ -227,15 +330,19 @@ github.com/kataras/tunnel v0.0.4 h1:sCAqWuJV7nPzGrlb0os3j49lk2JhILT0rID38NHNLpA= github.com/kataras/tunnel v0.0.4/go.mod h1:9FkU4LaeifdMWqZu7o20ojmW4B7hdhv2CMLwfnHGpYw= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= -github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= -github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg= +github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc= -github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= +github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -246,12 +353,15 @@ github.com/labstack/echo/v4 v4.11.4 h1:vDZmA+qNeh1pd/cCkEicDMrjtrnMGQ1QFI9gWN1zG github.com/labstack/echo/v4 v4.11.4/go.mod h1:noh7EvLwqDsmh/X/HWKPUl1AjzJrhyptRyEbQJfxen8= github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= -github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= -github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mailgun/raymond/v2 v2.0.48 h1:5dmlB680ZkFG2RN/0lvTAghrSxIESeu9/2aeDqACtjw= github.com/mailgun/raymond/v2 v2.0.48/go.mod h1:lsgvL50kgt1ylcFJYZiULi5fjPBkkhNfj4KA0W54Z18= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= @@ -265,24 +375,80 @@ github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= +github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= +github.com/onsi/ginkgo/v2 v2.1.4/go.mod h1:um6tUpWM/cxCK3/FK8BXqEiUMUwRgSM4JXG47RKZmLU= +github.com/onsi/ginkgo/v2 v2.1.6/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk= +github.com/onsi/ginkgo/v2 v2.3.0/go.mod h1:Eew0uilEqZmIEZr8JrvYlvOM7Rr6xzTmMV8AyFNU9d0= +github.com/onsi/ginkgo/v2 v2.4.0/go.mod h1:iHkDK1fKGcBoEHT5W7YBq4RFWaQulw+caOMkAt4OrFo= +github.com/onsi/ginkgo/v2 v2.5.0/go.mod h1:Luc4sArBICYCS8THh8v3i3i5CuSZO+RaQRaJoeNwomw= +github.com/onsi/ginkgo/v2 v2.7.0/go.mod h1:yjiuMwPokqY1XauOgju45q3sJt6VzQ/Fict1LFVcsAo= +github.com/onsi/ginkgo/v2 v2.8.1/go.mod h1:N1/NbDngAFcSLdyZ+/aYTYGSlq9qMCS/cNKGJjy+csc= +github.com/onsi/ginkgo/v2 v2.9.0/go.mod h1:4xkjoL/tZv4SMWeww56BU5kAt19mVB47gTWxmrTcxyk= +github.com/onsi/ginkgo/v2 v2.9.1/go.mod h1:FEcmzVcCHl+4o9bQZVab+4dC9+j+91t2FHSzmGAPfuo= +github.com/onsi/ginkgo/v2 v2.9.2/go.mod h1:WHcJJG2dIlcCqVfBAwUCrJxSPFb6v4azBwgxeMeDuts= +github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k= +github.com/onsi/ginkgo/v2 v2.9.7/go.mod h1:cxrmXWykAwTwhQsJOPfdIDiJ+l2RYq7U8hFU+M/1uw0= +github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM= +github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4= +github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o= +github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= +github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= +github.com/onsi/gomega v1.20.1/go.mod h1:DtrZpjmvpn2mPm4YWQa0/ALMDj9v4YxLgojwPeREyVo= +github.com/onsi/gomega v1.21.1/go.mod h1:iYAIXgPSaDHak0LCMA+AWBpIKBr8WZicMxnE8luStNc= +github.com/onsi/gomega v1.22.1/go.mod h1:x6n7VNe4hw0vkyYUM4mjIXx3JbLiPaBPNgB7PRQ1tuM= +github.com/onsi/gomega v1.24.0/go.mod h1:Z/NWtiqwBrwUt4/2loMmHL63EDLnYHmVbuBpDr2vQAg= +github.com/onsi/gomega v1.24.1/go.mod h1:3AOiACssS3/MajrniINInwbfOOtfZvplPzuRSmvt1jM= +github.com/onsi/gomega v1.26.0/go.mod h1:r+zV744Re+DiYCIPRlYOTxn0YkOLcAnW8k1xXdMPGhM= +github.com/onsi/gomega v1.27.1/go.mod h1:aHX5xOykVYzWOV4WqQy0sy8BQptgukenXpCXfadcIAw= +github.com/onsi/gomega v1.27.3/go.mod h1:5vG284IBtfDAmDyrK+eGyZmUgUlmi+Wngqo557cZ6Gw= +github.com/onsi/gomega v1.27.4/go.mod h1:riYq/GJKh8hhoM01HN6Vmuy93AarCXCBGpvFDK3q3fQ= +github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg= +github.com/onsi/gomega v1.27.7/go.mod h1:1p8OOlwo2iUUDsHnOrjE5UKYJ+e3W8eQ3qSlRahPmr4= +github.com/onsi/gomega v1.27.8/go.mod h1:2J8vzI/s+2shY9XHRApDkdgPo1TKT7P2u6fXeJKFnNQ= github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= +github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg= +github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= +github.com/openshift/api v0.0.0-20240228145634-217662e7e95f h1:qp9ySronP+1nkov+9mBOImjYxlF5Yg3JzkGmfSWFMg8= +github.com/openshift/api v0.0.0-20240228145634-217662e7e95f/go.mod h1:CxgbWAlvu2iQB0UmKTtRu1YfepRg1/vJ64n2DlIEVz4= +github.com/openshift/custom-resource-status v1.1.2 h1:C3DL44LEbvlbItfd8mT5jWrqPfHnSOQoQf/sypqA6A4= +github.com/openshift/custom-resource-status v1.1.2/go.mod h1:DB/Mf2oTeiAmVVX1gN+NEqweonAPY0TKUwADizj8+ZA= github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI= github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.13.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo= @@ -290,8 +456,11 @@ github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Q github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= -github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= @@ -314,6 +483,7 @@ github.com/skeema/knownhosts v1.2.1 h1:SHWdIUa82uGZz+F+47k8SY4QhhI291cXCpopT1lK2 github.com/skeema/knownhosts v1.2.1/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= @@ -325,26 +495,27 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= +github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= -github.com/tdewolff/minify/v2 v2.20.14 h1:sktSuVixRwk0ryQjqvKBu/uYS+MWmkwEFMEWtFZ+TdE= -github.com/tdewolff/minify/v2 v2.20.14/go.mod h1:qnIJbnG2dSzk7LIa/UUwgN2OjS8ir6RRlqc0T/1q2xY= -github.com/tdewolff/parse/v2 v2.7.9 h1:4u8nNXNmEGCRVd/slZmZHFL1mv/EVEpHMhSinxdDCqw= -github.com/tdewolff/parse/v2 v2.7.9/go.mod h1:3FbJWZp3XT9OWVN3Hmfp0p/a08v4h8J9W1aghka0soA= +github.com/tdewolff/minify/v2 v2.20.18 h1:y+s6OzlZwFqApgNXWNtaMuEMEPbHT72zrCyb9Az35Xo= +github.com/tdewolff/minify/v2 v2.20.18/go.mod h1:ulkFoeAVWMLEyjuDz1ZIWOA31g5aWOawCFRp9R/MudM= +github.com/tdewolff/parse/v2 v2.7.12 h1:tgavkHc2ZDEQVKy1oWxwIyh5bP4F5fEh/JmBwPP/3LQ= +github.com/tdewolff/parse/v2 v2.7.12/go.mod h1:3FbJWZp3XT9OWVN3Hmfp0p/a08v4h8J9W1aghka0soA= github.com/tdewolff/test v1.0.11-0.20231101010635-f1265d231d52/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE= github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739 h1:IkjBCtQOOjIn03u/dMQK9g+Iw9ewps4mCl1nB8Sscbo= github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8= @@ -376,6 +547,10 @@ github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCO github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M= github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= @@ -386,50 +561,125 @@ golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUu golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc= golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= -golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= -golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= -golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 h1:hNQpMuAJe5CtcUqCXaWga3FHu+kQvCqcsoVaQgSV60o= -golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08= +golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= +golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.20.0 h1:jmAMJJZXr5KiCw05dfYK9QnqaqKLYXijU23lsEdcQqg= +golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ= +golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= +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-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= +golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= +golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8= +golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +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-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-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= +golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= -golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= -golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= +golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= +golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.17.0 h1:6m3ZPmLEFdVxKKWnKq4VqZ60gutO35zm+zrAHVmHyDQ= +golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA= +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-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -437,59 +687,188 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= -golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= +golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= +golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= -golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE= -golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= +golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= +golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.6-0.20210820212750-d4cc65f0b2ff/go.mod h1:YD9qOF0M9xpSpdWTBbzEl5e/RnCefISl8E5Noe10jFM= golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= +golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= +golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= -golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= +golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= +golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= +golang.org/x/tools v0.9.3/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= +golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= +golang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ= +golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +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.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= +google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.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.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +k8s.io/api v0.29.1 h1:DAjwWX/9YT7NQD4INu49ROJuZAAAP/Ijki48GUPzxqw= +k8s.io/api v0.29.1/go.mod h1:7Kl10vBRUXhnQQI8YR/R327zXC8eJ7887/+Ybta+RoQ= +k8s.io/apimachinery v0.29.1 h1:KY4/E6km/wLBguvCZv8cKTeOwwOBqFNjwJIdMkMbbRc= +k8s.io/apimachinery v0.29.1/go.mod h1:6HVkd1FwxIagpYrHSwJlQqZI3G9LfYWRPAkUvLnXTKU= +k8s.io/client-go v0.29.1 h1:19B/+2NGEwnFLzt0uB5kNJnfTsbV8w6TgQRz9l7ti7A= +k8s.io/client-go v0.29.1/go.mod h1:TDG/psL9hdet0TI9mGyHJSgRkW3H9JZk2dNEUS7bRks= +k8s.io/code-generator v0.23.3/go.mod h1:S0Q1JVA+kSzTI1oUvbKAxZY/DYbA/ZUb4Uknog12ETk= +k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= +k8s.io/gengo v0.0.0-20211129171323-c02415ce4185/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= +k8s.io/gengo v0.0.0-20230829151522-9cce18d56c01/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= +k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= +k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= +k8s.io/klog/v2 v2.30.0/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/klog/v2 v2.40.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/klog/v2 v2.110.1/go.mod h1:YGtd1984u+GgbuZ7e08/yBuAfKLSO0+uR1Fhi6ExXjo= +k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= +k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65/go.mod h1:sX9MT8g7NVZM5lVL/j8QyCCJe8YSMW30QvGZWaCIDIk= +k8s.io/kube-openapi v0.0.0-20220124234850-424119656bbf/go.mod h1:sX9MT8g7NVZM5lVL/j8QyCCJe8YSMW30QvGZWaCIDIk= +k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= +k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= +k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= +k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/utils v0.0.0-20240102154912-e7106e64919e h1:eQ/4ljkx21sObifjzXwlPKpdGLrCfRziVtos3ofG/sQ= +k8s.io/utils v0.0.0-20240102154912-e7106e64919e/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +kubevirt.io/client-go v1.1.1 h1:X/fk9kLW65aHRM3GW71UIzXLZsALPoggt4786yLYz1g= +kubevirt.io/client-go v1.1.1/go.mod h1:ZsE3LucLRzbkK/Mc7rhTArCgVifZt7BeNSW2WOGiCm4= +kubevirt.io/containerized-data-importer-api v1.58.1 h1:Zbf0pCvxb4fBvtMR6uI2OIJZ4UfwFxripzOLMO4HPbI= +kubevirt.io/containerized-data-importer-api v1.58.1/go.mod h1:Y/8ETgHS1GjO89bl682DPtQOYEU/1ctPFBz6Sjxm4DM= +kubevirt.io/controller-lifecycle-operator-sdk/api v0.2.4 h1:fZYvD3/Vnitfkx6IJxjLAk8ugnZQ7CXVYcRfkSKmuZY= +kubevirt.io/controller-lifecycle-operator-sdk/api v0.2.4/go.mod h1:018lASpFYBsYN6XwmA2TIrPCx6e0gviTd/ZNtSitKgc= moul.io/http2curl/v2 v2.3.0 h1:9r3JfDzWPcbIklMOs2TnIFzDYvfAZvjeavG6EzP7jYs= moul.io/http2curl/v2 v2.3.0/go.mod h1:RW4hyBjTWSYDOxapodpNEtX0g5Eb16sxklBqmd2RHcE= nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/structured-merge-diff/v4 v4.2.1/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= +sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= +sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= +sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= +sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/pkg/cmd/build/build.go b/pkg/cmd/build/build.go index 7b2a68d..0bc24ec 100644 --- a/pkg/cmd/build/build.go +++ b/pkg/cmd/build/build.go @@ -19,12 +19,10 @@ package build import ( "fmt" "github.com/drewbernetes/baski/pkg/constants" - ostack "github.com/drewbernetes/baski/pkg/providers/openstack" "github.com/drewbernetes/baski/pkg/providers/packer" - "github.com/drewbernetes/baski/pkg/util/data" + "github.com/drewbernetes/baski/pkg/provisoner" "github.com/drewbernetes/baski/pkg/util/flags" "github.com/spf13/cobra" - "os" "path/filepath" ) @@ -42,12 +40,7 @@ func NewBuildCommand() *cobra.Command { Long: `Build image. Building images requires a set of commands to be run on the terminal however this is tedious and time consuming. -By using this, the time is reduced and automation can be enabled. - -Overtime this will become more dynamic to allow for build customised -images such as ones with GPU/HPC drivers/tools. - -To use baski to build an image, an Openstack cluster is required.`, +By using this, the time is reduced and automation can be enabled.`, TraverseChildren: true, RunE: func(cmd *cobra.Command, args []string) error { o.SetOptionsFromViper() @@ -56,38 +49,51 @@ To use baski to build an image, an Openstack cluster is required.`, return fmt.Errorf("an unsupported OS has been entered. Please select a valid OS: %s\n", constants.SupportedOS) } - err := os.Setenv("OS_CLOUD", o.CloudName) + builder := provisoner.NewBuilder(o) + + // Init the provisioner + err := builder.Init() if err != nil { return err } - packerBuildConfig := packer.InitConfig(o) - metadata := ostack.GenerateBuilderMetadata(o) - + // Fetch image-builder from git repo buildGitDir := createRepoDirectory() - fetchBuildRepo(buildGitDir, o) - - err = packer.UpdatePackerBuildersJson(buildGitDir, metadata) + err = fetchBuildRepo(buildGitDir, o) if err != nil { return err } - capiPath := filepath.Join(buildGitDir, "images", "capi") - packerBuildConfig.GenerateVariablesFile(capiPath) - - installDependencies(capiPath, o.Verbose) + // Generate a packer config + packerBuildConfig, err := builder.GeneratePackerConfig() + if err != nil { + return err + } - err = buildImage(capiPath, o.BuildOS, o.Verbose) + // If the builder requires it, modify it directly here. + modifierFunc := packer.BuildersModifier{ + Function: builder.UpdatePackerBuilders, + Metadata: packerBuildConfig.Metadata, + } + err = packer.UpdatePackerBuildersJson(buildGitDir, o.BaseOptions.InfraType, modifierFunc) if err != nil { return err } - imgID, err := data.RetrieveNewImageID() + // Generate a tmp.json file to be consumed by the image-builder for variables. + capiPath := filepath.Join(buildGitDir, "images", "capi") + packerBuildConfig.GenerateVariablesFile(capiPath) + + // Install any dependencies + installDependencies(capiPath, o.InfraType, o.Verbose) + + // Build the image + err = buildImage(capiPath, o.InfraType, o.BuildOS, o.Verbose) if err != nil { return err } - err = saveImageIDToFile(imgID) + err = builder.PostBuildAction() if err != nil { return err } diff --git a/pkg/cmd/build/process.go b/pkg/cmd/build/process.go index acaf7a3..1f9b561 100644 --- a/pkg/cmd/build/process.go +++ b/pkg/cmd/build/process.go @@ -17,6 +17,7 @@ limitations under the License. package build import ( + "fmt" "io" "log" "os" @@ -49,7 +50,7 @@ func createRepoDirectory() string { } // fetchBuildRepo simply pulls the contents of the imageRepo to the specified path -func fetchBuildRepo(path string, o *flags.BuildOptions) { +func fetchBuildRepo(path string, o *flags.BuildOptions) error { branch := plumbing.ReferenceName("refs/heads/" + o.ImageRepoBranch) imageRepo := o.ImageRepo @@ -63,13 +64,19 @@ func fetchBuildRepo(path string, o *flags.BuildOptions) { _, err := gitRepo.GitClone(imageRepo, path, branch) if err != nil { - log.Fatalf("Error cloning repo: %s", err) + return fmt.Errorf("Error cloning repo: %s", err) } + return nil } // installDependencies will run make dep-openstack so that any requirements such as packer, ansible // and goss will be installed. -func installDependencies(repoPath string, verbose bool) { +func installDependencies(repoPath, infra string, verbose bool) { + // change infra to qemu if kubevirt is the infra type as this is what is needed to build + if infra == "kubevirt" { + infra = "qemu" + } + log.Printf("fetching dependencies\n") w, err := os.Create("/tmp/out-deps.txt") @@ -85,7 +92,7 @@ func installDependencies(repoPath string, verbose bool) { wr = w } - err = systemUtils.RunMake("deps-openstack", repoPath, nil, wr) + err = systemUtils.RunMake(fmt.Sprintf("deps-%s", infra), repoPath, nil, wr) if err != nil { log.Fatalln(err) } @@ -100,7 +107,12 @@ func installDependencies(repoPath string, verbose bool) { // buildImage will run make build-openstack-buildOS which will launch an instance in Openstack, // add any requirements as defined in the image-builder imageRepo and then create an image from that build. -func buildImage(capiPath string, buildOS string, verbose bool) error { +func buildImage(capiPath, infra, buildOS string, verbose bool) error { + // change infra to qemu if kubevirt is the infra type as this is what is needed to build + if infra == "kubevirt" { + infra = "qemu" + } + log.Printf("building image\n") w, err := os.Create("/tmp/out-build.txt") @@ -116,26 +128,11 @@ func buildImage(capiPath string, buildOS string, verbose bool) error { wr = w } - args := strings.Join([]string{"build-openstack", buildOS}, "-") + args := strings.Join([]string{fmt.Sprintf("build-%s", infra), buildOS}, "-") env := []string{"PACKER_VAR_FILES=tmp.json"} env = append(env, os.Environ()...) err = systemUtils.RunMake(args, capiPath, env, wr) - if err != nil { - log.Fatalln(err) - } - - return nil -} - -// saveImageIDToFile exports the image ID to a file so that it can be read later by the scan system - this will generally be used by the gitHub action. -func saveImageIDToFile(imgID string) error { - f, err := os.Create("/tmp/imgid.out") - if err != nil { - return err - } - defer f.Close() - _, err = f.Write([]byte(imgID)) if err != nil { return err } diff --git a/pkg/cmd/root.go b/pkg/cmd/root.go index 6bc08d0..5dcda26 100644 --- a/pkg/cmd/root.go +++ b/pkg/cmd/root.go @@ -34,8 +34,8 @@ func init() { cmd = &cobra.Command{ Use: "baski", - Short: "Baski is a tools for building and scanning Kubernetes images within Openstack.", - Long: `Build And Scan Kubernetes Images on Openstack + Short: "Baski is a tools for building and scanning Kubernetes images.", + Long: `Build And Scan Kubernetes Images This tool has been designed to automatically build images for the Openstack potion of the Kubernetes Image Builder. It could be extended out to provide images for a variety of other builders however for now it's main goal is to work with Openstack.`, } diff --git a/pkg/cmd/scan/existing.go b/pkg/cmd/scan/existing.go deleted file mode 100644 index de98a8e..0000000 --- a/pkg/cmd/scan/existing.go +++ /dev/null @@ -1,140 +0,0 @@ -/* -Copyright 2024 Drewbernetes. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package scan - -import ( - "errors" - "github.com/drewbernetes/baski/pkg/providers/openstack" - "github.com/drewbernetes/baski/pkg/providers/scanner" - "github.com/drewbernetes/baski/pkg/s3" - "github.com/drewbernetes/baski/pkg/trivy" - "github.com/drewbernetes/baski/pkg/util/flags" - "github.com/gophercloud/gophercloud/openstack/imageservice/v2/images" - "github.com/spf13/cobra" - "log" - "strings" - "sync" -) - -// NewScanExistingCommand creates a command that allows the scanning of an image. -func NewScanExistingCommand() *cobra.Command { - o := &flags.ScanMultipleOptions{} - - cmd := &cobra.Command{ - Use: "existing", - Short: "Scan multiple existing images", - Long: `Scan multiple existing images. - -Retrospectively scanning images is required to make sure images stay secure or are taken out of circulation -as soon as possible when they are no longer secure. -If the image fails the scan it will be tagged with metadata to mark it as insecure. -It will looks for any images starting with the prefix as defined in the config and scan all of those images. -Depending on how many images there are, this could take some time. -to prevent every single image being launched for a scan, the concurrency is limited to 5, this can be overridden in the config.' -`, - RunE: func(cmd *cobra.Command, args []string) error { - o.SetOptionsFromViper() - - severity := trivy.Severity(strings.ToUpper(o.MaxSeverityType)) - if !trivy.ValidSeverity(severity) { - return errors.New("severity value passed is invalid. Allowed values are: NONE, LOW, MEDIUM, HIGH, CRITICAL") - } - - cloudProvider := ostack.NewCloudsProvider(o.CloudName) - - i, err := ostack.NewImageClient(cloudProvider) - if err != nil { - return err - } - - c, err := ostack.NewComputeClient(cloudProvider) - if err != nil { - return err - } - - n, err := ostack.NewNetworkClient(cloudProvider) - if err != nil { - return err - } - - imgs, err := i.FetchAllImages(o.ImageSearch) - if err != nil { - return err - } - - semaphore := make(chan struct{}, o.Concurrency) - var wg sync.WaitGroup - - for _, img := range imgs { - wg.Add(1) - semaphore <- struct{}{} - go func(image images.Image) { - defer func() { - <-semaphore // Release the slot in the semaphore - }() - - var s3Conn *s3.S3 - - s3Conn, err = s3.New(o.Endpoint, o.AccessKey, o.SecretKey, o.ScanBucket, "") - if err != nil { - log.Println(err) - return - } - - s := scanner.NewScanner(c, i, n, s3Conn) - - err = scanServer(o.ScanOptions, s, severity, &image, &wg) - if err != nil { - log.Println(err) - } - - }(img) - } - wg.Wait() - - close(semaphore) - - return nil - }, - } - - o.AddFlags(cmd) - - return cmd -} - -func scanServer(o flags.ScanOptions, s *scanner.ScannerClient, severity trivy.Severity, img *images.Image, wg *sync.WaitGroup) error { - defer wg.Done() - - log.Printf("Processing Image with ID: %s\n", img.ID) - - err := s.RunScan(&o, severity, img) - if err != nil { - return err - } - err = s.FetchScanResults(img.ID) - if err != nil { - return err - } - err = s.CheckResultsTagImageAndUploadToS3(img, o.AutoDeleteImage, o.SkipCVECheck) - if err != nil { - return err - } - - log.Printf("Finished processing Image ID: %s\n", img.ID) - return nil -} diff --git a/pkg/cmd/scan/scan.go b/pkg/cmd/scan/scan.go index 6f32dc4..e49714a 100644 --- a/pkg/cmd/scan/scan.go +++ b/pkg/cmd/scan/scan.go @@ -17,12 +17,18 @@ limitations under the License. package scan import ( + "errors" + "github.com/drewbernetes/baski/pkg/provisoner" + "github.com/drewbernetes/baski/pkg/trivy" + "github.com/drewbernetes/baski/pkg/util/flags" "github.com/spf13/cobra" + "strings" ) // NewScanCommand creates a command that allows the scanning of an image. func NewScanCommand() *cobra.Command { + o := &flags.ScanOptions{} cmd := &cobra.Command{ Use: "scan", Short: "Scan image", @@ -41,13 +47,29 @@ It does the following: If the checks for CVE flags/config values are set then it will bail out and generate a report with the CVEs that caused it to do so. `, - } + RunE: func(cmd *cobra.Command, args []string) error { + o.SetOptionsFromViper() + if !trivy.ValidSeverity(trivy.Severity(strings.ToUpper(o.MaxSeverityType))) { + return errors.New("severity value passed is invalid. Allowed values are: UNKNOWN, LOW, MEDIUM, HIGH, CRITICAL") + } + + scan := provisoner.NewScanner(o) + + err := scan.Prepare() + if err != nil { + return err + } - commands := []*cobra.Command{ - NewScanSingleCommand(), - NewScanExistingCommand(), + err = scan.ScanImages() + if err != nil { + return err + } + + return nil + }, } - cmd.AddCommand(commands...) + + o.AddFlags(cmd) return cmd } diff --git a/pkg/cmd/scan/single.go b/pkg/cmd/scan/single.go deleted file mode 100644 index 7e1ade8..0000000 --- a/pkg/cmd/scan/single.go +++ /dev/null @@ -1,98 +0,0 @@ -/* -Copyright 2024 Drewbernetes. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package scan - -import ( - "errors" - "github.com/drewbernetes/baski/pkg/providers/openstack" - "github.com/drewbernetes/baski/pkg/providers/scanner" - "github.com/drewbernetes/baski/pkg/s3" - "github.com/drewbernetes/baski/pkg/trivy" - "github.com/drewbernetes/baski/pkg/util/flags" - "github.com/spf13/cobra" - "strings" -) - -// NewScanSingleCommand creates a command that allows the scanning of an image. -func NewScanSingleCommand() *cobra.Command { - o := &flags.ScanSingleOptions{} - - cmd := &cobra.Command{ - Use: "single", - Short: "Scan single image", - Long: `Scan single image. - -Scanning an a single image - useful for when an image has just been built. -`, - RunE: func(cmd *cobra.Command, args []string) error { - o.SetOptionsFromViper() - severity := trivy.Severity(strings.ToUpper(o.MaxSeverityType)) - if !trivy.ValidSeverity(severity) { - return errors.New("severity value passed is invalid. Allowed values are: UNKNOWN, LOW, MEDIUM, HIGH, CRITICAL") - } - - cloudProvider := ostack.NewCloudsProvider(o.CloudName) - - i, err := ostack.NewImageClient(cloudProvider) - if err != nil { - return err - } - - c, err := ostack.NewComputeClient(cloudProvider) - if err != nil { - return err - } - - n, err := ostack.NewNetworkClient(cloudProvider) - if err != nil { - return err - } - - img, err := i.FetchImage(o.ImageID) - - if err != nil { - return err - } - - s3Conn, err := s3.New(o.Endpoint, o.AccessKey, o.SecretKey, o.ScanBucket, "") - if err != nil { - return err - } - - s := scanner.NewScanner(c, i, n, s3Conn) - - err = s.RunScan(&o.ScanOptions, severity, img) - if err != nil { - return err - } - err = s.FetchScanResults(img.ID) - if err != nil { - return err - } - err = s.CheckResultsTagImageAndUploadToS3(img, o.AutoDeleteImage, o.SkipCVECheck) - if err != nil { - return err - } - - return nil - }, - } - - o.AddFlags(cmd) - - return cmd -} diff --git a/pkg/cmd/sign/generate.go b/pkg/cmd/sign/generate.go index 6b8e234..0e309b6 100644 --- a/pkg/cmd/sign/generate.go +++ b/pkg/cmd/sign/generate.go @@ -29,7 +29,7 @@ import ( // NewSignGenerateCommand creates a command that allows the signing of an image. func NewSignGenerateCommand() *cobra.Command { - o := &flags.SignGenerateOptions{} + o := &flags.SignOptions{} cmd := &cobra.Command{ Use: "generate", diff --git a/pkg/cmd/sign/image.go b/pkg/cmd/sign/image.go index f91bf69..2d55a3e 100644 --- a/pkg/cmd/sign/image.go +++ b/pkg/cmd/sign/image.go @@ -17,20 +17,16 @@ limitations under the License. package sign import ( - ostack "github.com/drewbernetes/baski/pkg/providers/openstack" - "github.com/drewbernetes/baski/pkg/util/data" + "github.com/drewbernetes/baski/pkg/provisoner" "github.com/drewbernetes/baski/pkg/util/flags" "github.com/drewbernetes/baski/pkg/util/sign" - "github.com/gophercloud/gophercloud/openstack/imageservice/v2/images" "github.com/spf13/cobra" - "log" "os" ) // NewSignImageCommand creates a command that allows the signing of an image. func NewSignImageCommand() *cobra.Command { - - o := &flags.SignImageOptions{} + o := &flags.SignOptions{} cmd := &cobra.Command{ Use: "image", Short: "Sign image", @@ -50,8 +46,6 @@ If using vault, the key should be stored as follows: var key []byte var err error - imgID := getImageID(o.ImageID) - vaultClient := sign.VaultClient{ Endpoint: o.VaultURL, Token: o.VaultToken, @@ -69,34 +63,17 @@ If using vault, the key should be stored as follows: } } - digest, err := sign.Sign(imgID, key) + digest, err := sign.Sign(o.ImageID, key) if err != nil { return err } - cloudProvider := ostack.NewCloudsProvider(o.CloudName) - - i, err := ostack.NewImageClient(cloudProvider) + signer := provisoner.NewSigner(o) if err != nil { return err } - img, err := i.FetchImage(imgID) - if err != nil { - return err - } - - // Default to replace unless the field isn't found below - operation := images.ReplaceOp - - _, err = data.GetNestedField(img.Properties, "image", "metadata", "digest") - if err != nil { - operation = images.AddOp - } - - log.Println("attaching digest to image metadata") - _, err = i.ModifyImageMetadata(imgID, "digest", digest, operation) - + err = signer.SignImage(digest) if err != nil { return err } @@ -108,19 +85,3 @@ If using vault, the key should be stored as follows: return cmd } - -func getImageID(imageID string) string { - var imgID string - var err error - - if len(imageID) == 0 { - imgID, err = data.RetrieveNewImageID() - if err != nil { - log.Fatalln(err) - } - } else { - imgID = imageID - } - - return imgID -} diff --git a/pkg/cmd/sign/validate.go b/pkg/cmd/sign/validate.go index 5157e57..2a527d2 100644 --- a/pkg/cmd/sign/validate.go +++ b/pkg/cmd/sign/validate.go @@ -17,17 +17,17 @@ limitations under the License. package sign import ( + "github.com/drewbernetes/baski/pkg/provisoner" "github.com/drewbernetes/baski/pkg/util/flags" "github.com/drewbernetes/baski/pkg/util/sign" "github.com/spf13/cobra" - "log" "os" ) // NewSignValidateCommand creates a command that allows the signing of an image. func NewSignValidateCommand() *cobra.Command { - o := &flags.SignValidateOptions{} + o := &flags.SignOptions{} cmd := &cobra.Command{ Use: "validate", @@ -41,7 +41,6 @@ This just validates a signature. It's useful for verifying a signed image. var key []byte var err error - imgID := getImageID(o.ImageID) vaultClient := sign.VaultClient{ Endpoint: o.VaultURL, @@ -58,13 +57,14 @@ This just validates a signature. It's useful for verifying a signed image. return err } } + // TODO: We should look at grabbing the digest from the image tags so the user doesn't have to put it directly in. - valid, err := sign.Validate(imgID, key, o.Digest) + signer := provisoner.NewSigner(o) + err = signer.ValidateImage(key) if err != nil { return err } - log.Printf("The validation result was: %t", valid) return nil }, } diff --git a/pkg/constants/constants.go b/pkg/constants/constants.go index 7314df2..d830ac5 100644 --- a/pkg/constants/constants.go +++ b/pkg/constants/constants.go @@ -17,10 +17,12 @@ limitations under the License. package constants var ( - Version = "v0.1.0-beta.1" + Version = "v1.1.0" SupportedOS = []string{ "ubuntu-2004", + "ubuntu-2004-efi", "ubuntu-2204", + "ubuntu-2204-efi", } - TrivyVersion = "0.48.3" + TrivyVersion = "0.49.1" ) diff --git a/pkg/git/gitrepo_test.go b/pkg/git/gitrepo_test.go index 2f09da7..7f758be 100644 --- a/pkg/git/gitrepo_test.go +++ b/pkg/git/gitrepo_test.go @@ -22,7 +22,6 @@ import ( "testing" ) -// TestGitClone Tests the cloning by cloning the image builder repo. func TestGitClone(t *testing.T) { repo := "https://github.com/drew-viles/image-builder.git" cloneLocation := "/tmp/test" diff --git a/pkg/k8s/client.go b/pkg/k8s/client.go new file mode 100644 index 0000000..ead66f2 --- /dev/null +++ b/pkg/k8s/client.go @@ -0,0 +1,63 @@ +/* +Copyright 2024 Drewbernetes. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package k8s + +import ( + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" + + dv_client "kubevirt.io/client-go/generated/containerized-data-importer/clientset/versioned" + dv_scheme "kubevirt.io/client-go/generated/containerized-data-importer/clientset/versioned/scheme" +) + +type KubernetesClient struct { + Client *kubernetes.Clientset + KubeVirt *dv_client.Clientset + Config *rest.Config +} + +func NewClient(kubeconfig string) (*KubernetesClient, error) { + config, err := clientcmd.BuildConfigFromFlags("", kubeconfig) + + if err != nil { + return nil, err + } + + client, err := kubernetes.NewForConfig(config) + if err != nil { + return nil, err + } + + dvClient, err := dv_client.NewForConfig(config) + if err != nil { + return nil, err + } + + err = dv_scheme.AddToScheme(scheme.Scheme) + if err != nil { + return nil, err + } + + return &KubernetesClient{ + Client: client, + KubeVirt: dvClient, + Config: config, + }, nil + +} diff --git a/pkg/k8s/client_test.go b/pkg/k8s/client_test.go new file mode 100644 index 0000000..606ba91 --- /dev/null +++ b/pkg/k8s/client_test.go @@ -0,0 +1,94 @@ +/* +Copyright 2024 Drewbernetes. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package k8s + +import ( + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + dv_client "kubevirt.io/client-go/generated/containerized-data-importer/clientset/versioned" + "os" + "testing" +) + +var kubeconfig = ` +apiVersion: v1 +clusters: +- cluster: + certificate-authority-data: + server: https://127.0.0.1:6443 + name: default +contexts: +- context: + cluster: default + namespace: default + user: default + name: default@default +current-context: default@default +kind: Config +preferences: {} +users: +- name: default + user: + client-certificate-data: + client-key-data: +` + +func TestNewClient(t *testing.T) { + kubeconfigPath := "/tmp/kubeconfig" + err := os.WriteFile(kubeconfigPath, []byte(kubeconfig), 0700) + if err != nil { + t.Errorf(err.Error()) + } + + tc := []struct { + name string + client *KubernetesClient + }{ + { + name: "Testing we get a Kubernetes client back", + client: &KubernetesClient{ + Client: &kubernetes.Clientset{}, + KubeVirt: &dv_client.Clientset{}, + Config: &rest.Config{ + Host: "https://127.0.0.1:6443", + }, + }, + }, + } + + for _, v := range tc { + t.Run(v.name, func(t *testing.T) { + client, err := NewClient(kubeconfigPath) + if err != nil { + t.Errorf(err.Error()) + } + + //Basic checks for now - much more advanced testing will be required! + //TODO: Build better tests here than basic comparisons + + if client.Client == nil { + t.Errorf("exepected a kubernetes ClientSet, got nil\n") + } + if client.KubeVirt == nil { + t.Errorf("exepected a kubevirt ClientSet, got nil\n") + } + if client.Config == nil { + t.Errorf("exepected a rest Config, got nil\n") + } + }) + } +} diff --git a/pkg/mock/interfaces.go b/pkg/mock/base.go similarity index 93% rename from pkg/mock/interfaces.go rename to pkg/mock/base.go index 07ea6ea..32a3f8f 100644 --- a/pkg/mock/interfaces.go +++ b/pkg/mock/base.go @@ -1,15 +1,14 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: interfaces.go +// Source: base.go // // Generated by this command: // -// mockgen -source=interfaces.go -destination=../mock/interfaces.go -package=mock +// mockgen -source=base.go -destination=../../mock/base.go -package=mock // // Package mock is a generated GoMock package. package mock import ( - io "io" http "net/http" os "os" reflect "reflect" @@ -101,73 +100,6 @@ func (mr *MockHandlerInterfaceMockRecorder) Healthz(w, r any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Healthz", reflect.TypeOf((*MockHandlerInterface)(nil).Healthz), w, r) } -// MockS3Interface is a mock of S3Interface interface. -type MockS3Interface struct { - ctrl *gomock.Controller - recorder *MockS3InterfaceMockRecorder -} - -// MockS3InterfaceMockRecorder is the mock recorder for MockS3Interface. -type MockS3InterfaceMockRecorder struct { - mock *MockS3Interface -} - -// NewMockS3Interface creates a new mock instance. -func NewMockS3Interface(ctrl *gomock.Controller) *MockS3Interface { - mock := &MockS3Interface{ctrl: ctrl} - mock.recorder = &MockS3InterfaceMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockS3Interface) EXPECT() *MockS3InterfaceMockRecorder { - return m.recorder -} - -// Fetch mocks base method. -func (m *MockS3Interface) Fetch(arg0 string) ([]byte, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Fetch", arg0) - ret0, _ := ret[0].([]byte) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// Fetch indicates an expected call of Fetch. -func (mr *MockS3InterfaceMockRecorder) Fetch(arg0 any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Fetch", reflect.TypeOf((*MockS3Interface)(nil).Fetch), arg0) -} - -// List mocks base method. -func (m *MockS3Interface) List() ([]string, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "List") - ret0, _ := ret[0].([]string) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// List indicates an expected call of List. -func (mr *MockS3InterfaceMockRecorder) List() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockS3Interface)(nil).List)) -} - -// Put mocks base method. -func (m *MockS3Interface) Put(arg0, arg1 string, arg2 io.ReadSeeker) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Put", arg0, arg1, arg2) - ret0, _ := ret[0].(error) - return ret0 -} - -// Put indicates an expected call of Put. -func (mr *MockS3InterfaceMockRecorder) Put(arg0, arg1, arg2 any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Put", reflect.TypeOf((*MockS3Interface)(nil).Put), arg0, arg1, arg2) -} - // MockVaultInterface is a mock of VaultInterface interface. type MockVaultInterface struct { ctrl *gomock.Controller @@ -271,3 +203,70 @@ func (mr *MockSSHInterfaceMockRecorder) SSHClose() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SSHClose", reflect.TypeOf((*MockSSHInterface)(nil).SSHClose)) } + +// MockS3Interface is a mock of S3Interface interface. +type MockS3Interface struct { + ctrl *gomock.Controller + recorder *MockS3InterfaceMockRecorder +} + +// MockS3InterfaceMockRecorder is the mock recorder for MockS3Interface. +type MockS3InterfaceMockRecorder struct { + mock *MockS3Interface +} + +// NewMockS3Interface creates a new mock instance. +func NewMockS3Interface(ctrl *gomock.Controller) *MockS3Interface { + mock := &MockS3Interface{ctrl: ctrl} + mock.recorder = &MockS3InterfaceMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockS3Interface) EXPECT() *MockS3InterfaceMockRecorder { + return m.recorder +} + +// Fetch mocks base method. +func (m *MockS3Interface) Fetch(arg0 string) ([]byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Fetch", arg0) + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Fetch indicates an expected call of Fetch. +func (mr *MockS3InterfaceMockRecorder) Fetch(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Fetch", reflect.TypeOf((*MockS3Interface)(nil).Fetch), arg0) +} + +// List mocks base method. +func (m *MockS3Interface) List(arg0 string) ([]string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "List", arg0) + ret0, _ := ret[0].([]string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// List indicates an expected call of List. +func (mr *MockS3InterfaceMockRecorder) List(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockS3Interface)(nil).List), arg0) +} + +// Put mocks base method. +func (m *MockS3Interface) Put(key string, body *os.File) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Put", key, body) + ret0, _ := ret[0].(error) + return ret0 +} + +// Put indicates an expected call of Put. +func (mr *MockS3InterfaceMockRecorder) Put(key, body any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Put", reflect.TypeOf((*MockS3Interface)(nil).Put), key, body) +} diff --git a/pkg/mock/openstack.go b/pkg/mock/openstack.go new file mode 100644 index 0000000..8e7fe19 --- /dev/null +++ b/pkg/mock/openstack.go @@ -0,0 +1,425 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: openstack.go +// +// Generated by this command: +// +// mockgen -source=openstack.go -destination=../../mock/openstack.go -package=mock +// +// Package mock is a generated GoMock package. +package mock + +import ( + reflect "reflect" + + flags "github.com/drewbernetes/baski/pkg/util/flags" + gophercloud "github.com/gophercloud/gophercloud" + keypairs "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/keypairs" + servers "github.com/gophercloud/gophercloud/openstack/compute/v2/servers" + images "github.com/gophercloud/gophercloud/openstack/imageservice/v2/images" + floatingips "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips" + gomock "go.uber.org/mock/gomock" +) + +// MockOpenStackClient is a mock of OpenStackClient interface. +type MockOpenStackClient struct { + ctrl *gomock.Controller + recorder *MockOpenStackClientMockRecorder +} + +// MockOpenStackClientMockRecorder is the mock recorder for MockOpenStackClient. +type MockOpenStackClientMockRecorder struct { + mock *MockOpenStackClient +} + +// NewMockOpenStackClient creates a new mock instance. +func NewMockOpenStackClient(ctrl *gomock.Controller) *MockOpenStackClient { + mock := &MockOpenStackClient{ctrl: ctrl} + mock.recorder = &MockOpenStackClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockOpenStackClient) EXPECT() *MockOpenStackClientMockRecorder { + return m.recorder +} + +// Client mocks base method. +func (m *MockOpenStackClient) Client() (*gophercloud.ProviderClient, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Client") + ret0, _ := ret[0].(*gophercloud.ProviderClient) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Client indicates an expected call of Client. +func (mr *MockOpenStackClientMockRecorder) Client() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Client", reflect.TypeOf((*MockOpenStackClient)(nil).Client)) +} + +// MockOpenStackComputeClient is a mock of OpenStackComputeClient interface. +type MockOpenStackComputeClient struct { + ctrl *gomock.Controller + recorder *MockOpenStackComputeClientMockRecorder +} + +// MockOpenStackComputeClientMockRecorder is the mock recorder for MockOpenStackComputeClient. +type MockOpenStackComputeClientMockRecorder struct { + mock *MockOpenStackComputeClient +} + +// NewMockOpenStackComputeClient creates a new mock instance. +func NewMockOpenStackComputeClient(ctrl *gomock.Controller) *MockOpenStackComputeClient { + mock := &MockOpenStackComputeClient{ctrl: ctrl} + mock.recorder = &MockOpenStackComputeClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockOpenStackComputeClient) EXPECT() *MockOpenStackComputeClientMockRecorder { + return m.recorder +} + +// AttachIP mocks base method. +func (m *MockOpenStackComputeClient) AttachIP(serverID, fip string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AttachIP", serverID, fip) + ret0, _ := ret[0].(error) + return ret0 +} + +// AttachIP indicates an expected call of AttachIP. +func (mr *MockOpenStackComputeClientMockRecorder) AttachIP(serverID, fip any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AttachIP", reflect.TypeOf((*MockOpenStackComputeClient)(nil).AttachIP), serverID, fip) +} + +// CreateKeypair mocks base method. +func (m *MockOpenStackComputeClient) CreateKeypair(keyNamePrefix string) (*keypairs.KeyPair, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateKeypair", keyNamePrefix) + ret0, _ := ret[0].(*keypairs.KeyPair) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateKeypair indicates an expected call of CreateKeypair. +func (mr *MockOpenStackComputeClientMockRecorder) CreateKeypair(keyNamePrefix any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateKeypair", reflect.TypeOf((*MockOpenStackComputeClient)(nil).CreateKeypair), keyNamePrefix) +} + +// CreateServer mocks base method. +func (m *MockOpenStackComputeClient) CreateServer(keypairName, flavor, networkID string, attachConfigDrive *bool, userData []byte, imageID string, securityGroups []string) (*servers.Server, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateServer", keypairName, flavor, networkID, attachConfigDrive, userData, imageID, securityGroups) + ret0, _ := ret[0].(*servers.Server) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateServer indicates an expected call of CreateServer. +func (mr *MockOpenStackComputeClientMockRecorder) CreateServer(keypairName, flavor, networkID, attachConfigDrive, userData, imageID, securityGroups any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateServer", reflect.TypeOf((*MockOpenStackComputeClient)(nil).CreateServer), keypairName, flavor, networkID, attachConfigDrive, userData, imageID, securityGroups) +} + +// GetFlavorIDByName mocks base method. +func (m *MockOpenStackComputeClient) GetFlavorIDByName(name string) (string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetFlavorIDByName", name) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetFlavorIDByName indicates an expected call of GetFlavorIDByName. +func (mr *MockOpenStackComputeClientMockRecorder) GetFlavorIDByName(name any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFlavorIDByName", reflect.TypeOf((*MockOpenStackComputeClient)(nil).GetFlavorIDByName), name) +} + +// GetServerStatus mocks base method. +func (m *MockOpenStackComputeClient) GetServerStatus(sid string) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetServerStatus", sid) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetServerStatus indicates an expected call of GetServerStatus. +func (mr *MockOpenStackComputeClientMockRecorder) GetServerStatus(sid any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetServerStatus", reflect.TypeOf((*MockOpenStackComputeClient)(nil).GetServerStatus), sid) +} + +// RemoveKeypair mocks base method. +func (m *MockOpenStackComputeClient) RemoveKeypair(keyName string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RemoveKeypair", keyName) + ret0, _ := ret[0].(error) + return ret0 +} + +// RemoveKeypair indicates an expected call of RemoveKeypair. +func (mr *MockOpenStackComputeClientMockRecorder) RemoveKeypair(keyName any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveKeypair", reflect.TypeOf((*MockOpenStackComputeClient)(nil).RemoveKeypair), keyName) +} + +// RemoveServer mocks base method. +func (m *MockOpenStackComputeClient) RemoveServer(serverID string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RemoveServer", serverID) + ret0, _ := ret[0].(error) + return ret0 +} + +// RemoveServer indicates an expected call of RemoveServer. +func (mr *MockOpenStackComputeClientMockRecorder) RemoveServer(serverID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveServer", reflect.TypeOf((*MockOpenStackComputeClient)(nil).RemoveServer), serverID) +} + +// MockOpenStackImageClient is a mock of OpenStackImageClient interface. +type MockOpenStackImageClient struct { + ctrl *gomock.Controller + recorder *MockOpenStackImageClientMockRecorder +} + +// MockOpenStackImageClientMockRecorder is the mock recorder for MockOpenStackImageClient. +type MockOpenStackImageClientMockRecorder struct { + mock *MockOpenStackImageClient +} + +// NewMockOpenStackImageClient creates a new mock instance. +func NewMockOpenStackImageClient(ctrl *gomock.Controller) *MockOpenStackImageClient { + mock := &MockOpenStackImageClient{ctrl: ctrl} + mock.recorder = &MockOpenStackImageClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockOpenStackImageClient) EXPECT() *MockOpenStackImageClientMockRecorder { + return m.recorder +} + +// FetchAllImages mocks base method. +func (m *MockOpenStackImageClient) FetchAllImages(wildcard string) ([]images.Image, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "FetchAllImages", wildcard) + ret0, _ := ret[0].([]images.Image) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// FetchAllImages indicates an expected call of FetchAllImages. +func (mr *MockOpenStackImageClientMockRecorder) FetchAllImages(wildcard any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FetchAllImages", reflect.TypeOf((*MockOpenStackImageClient)(nil).FetchAllImages), wildcard) +} + +// FetchImage mocks base method. +func (m *MockOpenStackImageClient) FetchImage(imgID string) (*images.Image, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "FetchImage", imgID) + ret0, _ := ret[0].(*images.Image) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// FetchImage indicates an expected call of FetchImage. +func (mr *MockOpenStackImageClientMockRecorder) FetchImage(imgID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FetchImage", reflect.TypeOf((*MockOpenStackImageClient)(nil).FetchImage), imgID) +} + +// ModifyImageMetadata mocks base method. +func (m *MockOpenStackImageClient) ModifyImageMetadata(imgID, key, value string, operation images.UpdateOp) (*images.Image, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ModifyImageMetadata", imgID, key, value, operation) + ret0, _ := ret[0].(*images.Image) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ModifyImageMetadata indicates an expected call of ModifyImageMetadata. +func (mr *MockOpenStackImageClientMockRecorder) ModifyImageMetadata(imgID, key, value, operation any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ModifyImageMetadata", reflect.TypeOf((*MockOpenStackImageClient)(nil).ModifyImageMetadata), imgID, key, value, operation) +} + +// RemoveImage mocks base method. +func (m *MockOpenStackImageClient) RemoveImage(imgID string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RemoveImage", imgID) + ret0, _ := ret[0].(error) + return ret0 +} + +// RemoveImage indicates an expected call of RemoveImage. +func (mr *MockOpenStackImageClientMockRecorder) RemoveImage(imgID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveImage", reflect.TypeOf((*MockOpenStackImageClient)(nil).RemoveImage), imgID) +} + +// TagImage mocks base method. +func (m *MockOpenStackImageClient) TagImage(properties map[string]any, imgID, value, tagName string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "TagImage", properties, imgID, value, tagName) + ret0, _ := ret[0].(error) + return ret0 +} + +// TagImage indicates an expected call of TagImage. +func (mr *MockOpenStackImageClientMockRecorder) TagImage(properties, imgID, value, tagName any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TagImage", reflect.TypeOf((*MockOpenStackImageClient)(nil).TagImage), properties, imgID, value, tagName) +} + +// MockOpenStackNetworkClient is a mock of OpenStackNetworkClient interface. +type MockOpenStackNetworkClient struct { + ctrl *gomock.Controller + recorder *MockOpenStackNetworkClientMockRecorder +} + +// MockOpenStackNetworkClientMockRecorder is the mock recorder for MockOpenStackNetworkClient. +type MockOpenStackNetworkClientMockRecorder struct { + mock *MockOpenStackNetworkClient +} + +// NewMockOpenStackNetworkClient creates a new mock instance. +func NewMockOpenStackNetworkClient(ctrl *gomock.Controller) *MockOpenStackNetworkClient { + mock := &MockOpenStackNetworkClient{ctrl: ctrl} + mock.recorder = &MockOpenStackNetworkClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockOpenStackNetworkClient) EXPECT() *MockOpenStackNetworkClientMockRecorder { + return m.recorder +} + +// GetFloatingIP mocks base method. +func (m *MockOpenStackNetworkClient) GetFloatingIP(networkName string) (*floatingips.FloatingIP, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetFloatingIP", networkName) + ret0, _ := ret[0].(*floatingips.FloatingIP) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetFloatingIP indicates an expected call of GetFloatingIP. +func (mr *MockOpenStackNetworkClientMockRecorder) GetFloatingIP(networkName any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFloatingIP", reflect.TypeOf((*MockOpenStackNetworkClient)(nil).GetFloatingIP), networkName) +} + +// RemoveFIP mocks base method. +func (m *MockOpenStackNetworkClient) RemoveFIP(fipID string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RemoveFIP", fipID) + ret0, _ := ret[0].(error) + return ret0 +} + +// RemoveFIP indicates an expected call of RemoveFIP. +func (mr *MockOpenStackNetworkClientMockRecorder) RemoveFIP(fipID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveFIP", reflect.TypeOf((*MockOpenStackNetworkClient)(nil).RemoveFIP), fipID) +} + +// MockOpenStackScannerInterface is a mock of OpenStackScannerInterface interface. +type MockOpenStackScannerInterface struct { + ctrl *gomock.Controller + recorder *MockOpenStackScannerInterfaceMockRecorder +} + +// MockOpenStackScannerInterfaceMockRecorder is the mock recorder for MockOpenStackScannerInterface. +type MockOpenStackScannerInterfaceMockRecorder struct { + mock *MockOpenStackScannerInterface +} + +// NewMockOpenStackScannerInterface creates a new mock instance. +func NewMockOpenStackScannerInterface(ctrl *gomock.Controller) *MockOpenStackScannerInterface { + mock := &MockOpenStackScannerInterface{ctrl: ctrl} + mock.recorder = &MockOpenStackScannerInterfaceMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockOpenStackScannerInterface) EXPECT() *MockOpenStackScannerInterfaceMockRecorder { + return m.recorder +} + +// CheckResults mocks base method. +func (m *MockOpenStackScannerInterface) CheckResults() error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CheckResults") + ret0, _ := ret[0].(error) + return ret0 +} + +// CheckResults indicates an expected call of CheckResults. +func (mr *MockOpenStackScannerInterfaceMockRecorder) CheckResults() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CheckResults", reflect.TypeOf((*MockOpenStackScannerInterface)(nil).CheckResults)) +} + +// FetchScanResults mocks base method. +func (m *MockOpenStackScannerInterface) FetchScanResults() error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "FetchScanResults") + ret0, _ := ret[0].(error) + return ret0 +} + +// FetchScanResults indicates an expected call of FetchScanResults. +func (mr *MockOpenStackScannerInterfaceMockRecorder) FetchScanResults() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FetchScanResults", reflect.TypeOf((*MockOpenStackScannerInterface)(nil).FetchScanResults)) +} + +// RunScan mocks base method. +func (m *MockOpenStackScannerInterface) RunScan(o *flags.ScanOptions) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RunScan", o) + ret0, _ := ret[0].(error) + return ret0 +} + +// RunScan indicates an expected call of RunScan. +func (mr *MockOpenStackScannerInterfaceMockRecorder) RunScan(o any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RunScan", reflect.TypeOf((*MockOpenStackScannerInterface)(nil).RunScan), o) +} + +// TagImage mocks base method. +func (m *MockOpenStackScannerInterface) TagImage() error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "TagImage") + ret0, _ := ret[0].(error) + return ret0 +} + +// TagImage indicates an expected call of TagImage. +func (mr *MockOpenStackScannerInterfaceMockRecorder) TagImage() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TagImage", reflect.TypeOf((*MockOpenStackScannerInterface)(nil).TagImage)) +} + +// UploadResultsToS3 mocks base method. +func (m *MockOpenStackScannerInterface) UploadResultsToS3() error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UploadResultsToS3") + ret0, _ := ret[0].(error) + return ret0 +} + +// UploadResultsToS3 indicates an expected call of UploadResultsToS3. +func (mr *MockOpenStackScannerInterfaceMockRecorder) UploadResultsToS3() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UploadResultsToS3", reflect.TypeOf((*MockOpenStackScannerInterface)(nil).UploadResultsToS3)) +} diff --git a/pkg/providers/openstack/compute.go b/pkg/providers/openstack/compute.go index 17c5e3b..c6e2d12 100644 --- a/pkg/providers/openstack/compute.go +++ b/pkg/providers/openstack/compute.go @@ -71,7 +71,7 @@ func (c *ComputeClient) RemoveKeypair(keyName string) error { } // CreateServer creates a compute instance in Openstack. -func (c *ComputeClient) CreateServer(keypairName string, flavor, networkID string, attachConfigDrive *bool, userData []byte, imageID string) (*servers.Server, error) { +func (c *ComputeClient) CreateServer(keypairName string, flavor, networkID string, attachConfigDrive *bool, userData []byte, imageID string, securityGroups []string) (*servers.Server, error) { log.Println("creating server") serverFlavorID, err := c.GetFlavorIDByName(flavor) if err != nil { @@ -82,7 +82,7 @@ func (c *ComputeClient) CreateServer(keypairName string, flavor, networkID strin Name: imageID + "-scanner", FlavorRef: serverFlavorID, ImageRef: imageID, - SecurityGroups: []string{"default"}, + SecurityGroups: securityGroups, UserData: userData, AvailabilityZone: "", Networks: []servers.Network{ diff --git a/pkg/providers/openstack/compute_test.go b/pkg/providers/openstack/compute_test.go index 188dad3..cbc94a3 100644 --- a/pkg/providers/openstack/compute_test.go +++ b/pkg/providers/openstack/compute_test.go @@ -24,7 +24,7 @@ import ( ) func TestNewComputeClient(t *testing.T) { - //TODO: Implement this. + //TODO: Implement this in a better way. // Not sure of the best approach for this yet. } @@ -196,7 +196,7 @@ func TestCreateServer(t *testing.T) { client: th.ServiceClient(), } configDrive := false - s, err := cc.CreateServer("test-key", "1", "d32019d3-bc6e-4319-9c1d-6722fc136a22", &configDrive, []byte{}, "f90f6034-2570-4974-8351-6b49732ef2eb") + s, err := cc.CreateServer("test-key", "1", "d32019d3-bc6e-4319-9c1d-6722fc136a22", &configDrive, []byte{}, "f90f6034-2570-4974-8351-6b49732ef2eb", []string{"default"}) if err != nil { t.Error(err) return diff --git a/pkg/providers/openstack/image.go b/pkg/providers/openstack/image.go index 0295273..b8e820e 100644 --- a/pkg/providers/openstack/image.go +++ b/pkg/providers/openstack/image.go @@ -18,13 +18,12 @@ package ostack import ( "fmt" - "github.com/drewbernetes/baski/pkg/util/flags" + "github.com/drewbernetes/baski/pkg/util/data" "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/openstack" "github.com/gophercloud/gophercloud/openstack/imageservice/v2/images" "log" "strings" - "time" ) type ImageClient struct { @@ -45,21 +44,6 @@ func NewImageClient(provider Provider) (*ImageClient, error) { }, nil } -// GenerateBuilderMetadata generates some glance metadata for the image. -func GenerateBuilderMetadata(o *flags.BuildOptions) map[string]string { - gpu := "no_gpu" - if o.AddNvidiaSupport { - gpu = o.NvidiaVersion - } - return map[string]string{ - "os": o.BuildOS, - "k8s": o.KubeVersion, - "gpu": gpu, - "date": time.Now().Format(time.RFC3339), - "rootfs_uuid": o.RootfsUUID, - } -} - // ModifyImageMetadata allows image metadata to be added, updated or removed. func (c *ImageClient) ModifyImageMetadata(imgID string, key, value string, operation images.UpdateOp) (*images.Image, error) { c.client.Microversion = "2.2" @@ -139,3 +123,19 @@ func (c *ImageClient) FetchImage(imgID string) (*images.Image, error) { return nil, nil } + +// TagImage Tags the image with the passed or failed property. +func (s *ImageClient) TagImage(properties map[string]interface{}, imgID, value, tagName string) error { + // Default to replace unless the field isn't found below + operation := images.ReplaceOp + + field, err := data.GetNestedField(properties, tagName) + if err != nil || field == nil { + operation = images.AddOp + } + _, err = s.ModifyImageMetadata(imgID, tagName, value, operation) + if err != nil { + return err + } + return nil +} diff --git a/pkg/providers/openstack/image_test.go b/pkg/providers/openstack/image_test.go index 49c3c93..26e790e 100644 --- a/pkg/providers/openstack/image_test.go +++ b/pkg/providers/openstack/image_test.go @@ -18,11 +18,9 @@ package ostack import ( "fmt" - "github.com/drewbernetes/baski/pkg/util/flags" th "github.com/drewbernetes/baski/testhelpers" "github.com/gophercloud/gophercloud/openstack/imageservice/v2/images" "net/http" - "reflect" "strconv" "strings" "testing" @@ -127,68 +125,6 @@ func TestNewImageClient(t *testing.T) { // Not sure of the best approach for this yet. } -// TestGenerateBuilderMetadata generates some glance metadata for the image. -func TestGenerateBuilderMetadata(t *testing.T) { - th.SetupPersistentPortHTTP(t, th.Port) - defer th.TeardownHTTP() - - tests := []struct { - name string - options *flags.BuildOptions - expected map[string]string - }{ - { - name: "Test with GPU", - options: &flags.BuildOptions{ - AddNvidiaSupport: true, - NvidiaVersion: "1.2.3", - BuildOS: "ubuntu", - KubeVersion: "1.28", - OpenStackFlags: flags.OpenStackFlags{ - RootfsUUID: "123456", - }, - }, - expected: map[string]string{ - "os": "ubuntu", - "k8s": "1.28", - "gpu": "1.2.3", - "date": "2006-01-02T15:04:05Z07:00", - "rootfs_uuid": "123456", - }, - }, - { - name: "Test without GPU", - options: &flags.BuildOptions{ - AddNvidiaSupport: false, - BuildOS: "ubuntu", - KubeVersion: "1.28", - OpenStackFlags: flags.OpenStackFlags{ - RootfsUUID: "123456", - }, - }, - expected: map[string]string{ - "os": "ubuntu", - "k8s": "1.28", - "gpu": "no_gpu", - "date": "2006-01-02T15:04:05Z07:00", - "rootfs_uuid": "123456", - }, - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - meta := GenerateBuilderMetadata(tc.options) - //We override the dat here as it's based off of time.Now() - meta["date"] = "2006-01-02T15:04:05Z07:00" - - if !reflect.DeepEqual(meta, tc.expected) { - t.Errorf("Expected %+v, got %+v", tc.expected, meta) - } - }) - } - -} - // TestModifyImageMetadata allows image metadata to be added, updated or removed. func TestModifyImageMetadata(t *testing.T) { th.SetupPersistentPortHTTP(t, th.Port) diff --git a/pkg/providers/packer/client.go b/pkg/providers/packer/client.go index 02e23ca..0d19945 100644 --- a/pkg/providers/packer/client.go +++ b/pkg/providers/packer/client.go @@ -19,195 +19,103 @@ package packer import ( "encoding/json" "fmt" + "github.com/google/uuid" "io" "log" "os" "path/filepath" "regexp" - "strconv" "strings" "time" "github.com/drewbernetes/baski/pkg/util/flags" - - "github.com/google/uuid" ) -// Buildconfig exists to allow variables to be parsed into a packer json file which can then be used for a build. -type Buildconfig struct { - ImageName string `json:"image_name,omitempty"` - SourceImage string `json:"source_image"` - Networks string `json:"networks"` - Flavor string `json:"flavor"` - AttachConfigDrive string `json:"attach_config_drive,omitempty"` - UseFloatingIp string `json:"use_floating_ip,omitempty"` - FloatingIpNetwork string `json:"floating_ip_network,omitempty"` - CniVersion string `json:"kubernetes_cni_semver,omitempty"` - CniDebVersion string `json:"kubernetes_cni_deb_version,omitempty"` - CrictlVersion string `json:"crictl_version,omitempty"` - ImageVisibility string `json:"image_visibility,omitempty"` - KubernetesSemver string `json:"kubernetes_semver,omitempty"` - KubernetesRpmVersion string `json:"kubernetes_rpm_version,omitempty"` - KubernetesSeries string `json:"kubernetes_series,omitempty"` - KubernetesDebVersion string `json:"kubernetes_deb_version,omitempty"` - NodeCustomRolesPre string `json:"node_custom_roles_pre,omitempty"` - NodeCustomRolesPost string `json:"node_custom_roles_post,omitempty"` - AnsibleUserVars string `json:"ansible_user_vars,omitempty"` - ExtraDebs string `json:"extra_debs,omitempty"` - ImageDiskFormat string `json:"image_disk_format"` - VolumeType string `json:"volume_type"` - VolumeSize string `json:"volume_size"` -} - -// Extract Kubernetes series with the assumption we want vX.XX -func truncateVersion(version string) string { - re := regexp.MustCompile(`v\d+\.\d+`) - return re.FindString(version) -} - -// generateImageName creates a name for the image that will be built. -func generateImageName(imagePrefix string) string { - imageUUID, err := uuid.NewRandom() - if err != nil { - log.Fatalln(err) - } - - shortDate := time.Now().Format("060102") - shortUUID := imageUUID.String()[:strings.Index(imageUUID.String(), "-")] - - return imagePrefix + "-" + shortDate + "-" + shortUUID -} - -// addMetadataToBuilders inserts the metadata into the packer's builder section. -func addMetadataToBuilders(metadata map[string]string, data []byte) []byte { - jsonStruct := struct { - Builders []map[string]interface{} `json:"builders"` - PostProcessors []map[string]interface{} `json:"post-processors"` - Provisioners []map[string]interface{} `json:"provisioners"` - Variables map[string]interface{} `json:"variables"` - }{} - - err := json.Unmarshal(data, &jsonStruct) - if err != nil { - log.Fatalln(err) - } - - jsonStruct.Builders[0]["metadata"] = metadata - - res, err := json.Marshal(jsonStruct) - if err != nil { - log.Fatalln(err) - } - - return res +type GlobalBuildConfig struct { + CniVersion string `json:"kubernetes_cni_semver,omitempty"` + CniDebVersion string `json:"kubernetes_cni_deb_version,omitempty"` + CrictlVersion string `json:"crictl_version,omitempty"` + KubernetesSemver string `json:"kubernetes_semver,omitempty"` + KubernetesRpmVersion string `json:"kubernetes_rpm_version,omitempty"` + KubernetesSeries string `json:"kubernetes_series,omitempty"` + KubernetesDebVersion string `json:"kubernetes_deb_version,omitempty"` + NodeCustomRolesPre string `json:"node_custom_roles_pre,omitempty"` + NodeCustomRolesPost string `json:"node_custom_roles_post,omitempty"` + AnsibleUserVars string `json:"ansible_user_vars,omitempty"` + ExtraDebs string `json:"extra_debs,omitempty"` + Metadata map[string]string `json:"-"` + OpenStackBuildconfig + KubeVirtBuildConfig } -// UpdatePackerBuildersJson pre-populates the metadata field in the packer.json file as objects cannot be passed as variables in packer. -func UpdatePackerBuildersJson(dir string, metadata map[string]string) error { - file, err := os.OpenFile(filepath.Join(dir, "images", "capi", "packer", "openstack", "packer.json"), os.O_RDWR, 0644) - if err != nil { - return err - } - defer file.Close() - - data, err := io.ReadAll(file) - if err != nil { - return err - } - - res := addMetadataToBuilders(metadata, data) - - err = file.Truncate(0) - if err != nil { - return err - } - _, err = file.Seek(0, 0) - if err != nil { - return err - } - - _, err = file.Write(res) - if err != nil { - return err - } - return nil -} - -// InitConfig takes the application inputs and converts it into a Buildconfig. -func InitConfig(o *flags.BuildOptions) *Buildconfig { - buildConfig := &Buildconfig{ - SourceImage: o.SourceImageID, - Networks: o.NetworkID, - Flavor: o.FlavorName, - AttachConfigDrive: strconv.FormatBool(o.AttachConfigDrive), - UseFloatingIp: strconv.FormatBool(o.UseFloatingIP), - FloatingIpNetwork: o.FloatingIPNetworkName, +func NewCoreBuildconfig(o *flags.BuildOptions) (*GlobalBuildConfig, string, error) { + b := &GlobalBuildConfig{ CniVersion: "v" + o.CniVersion, CniDebVersion: o.CniDebVersion, CrictlVersion: o.CrictlVersion, - ImageVisibility: o.ImageVisibility, KubernetesSemver: "v" + o.KubeVersion, KubernetesSeries: truncateVersion("v" + o.KubeVersion), KubernetesRpmVersion: o.KubeRpmVersion, KubernetesDebVersion: o.KubeDebVersion, ExtraDebs: o.ExtraDebs, - ImageDiskFormat: o.ImageDiskFormat, - VolumeType: o.VolumeType, - VolumeSize: strconv.Itoa(o.VolumeSize), } - var ansibleUserVars string + var customRoles string var additionalImages string var securityVars string - var customRoles string - // Little workaround for people leaving an empty field or not having the field in the yaml. - // viper likes to replace a non-existent entry with the string "[]" even when the default is nil. - if o.AdditionalImages != nil { - if len(o.AdditionalImages) > 0 { - if o.AdditionalImages[0] == "[]" { - o.AdditionalImages = nil + if o.AddGpuSupport { + customRoles = "gpu" + + if o.GpuVendor == "nvidia" { + ansibleUserVars = fmt.Sprintf("gpu_vendor=%s nvidia_s3_url=%s nvidia_bucket=%s nvidia_bucket_access=%s nvidia_bucket_secret=%s nvidia_ceph=%t nvidia_installer_location=%s", + o.GpuVendor, + o.Endpoint, + o.NvidiaBucket, + o.AccessKey, + o.SecretKey, + o.IsCeph, + o.NvidiaInstallerLocation) + + if o.NvidiaTOKLocation != "" { + ansibleUserVars = fmt.Sprintf("%s nvidia_tok_location=%s", + ansibleUserVars, + o.NvidiaTOKLocation) } - } - } - - if o.AddNvidiaSupport { - customRoles = "nvidia" - - ansibleUserVars = fmt.Sprintf("nvidia_s3_url=%s nvidia_bucket=%s nvidia_bucket_access=%s nvidia_bucket_secret=%s nvidia_ceph=%t nvidia_installer_location=%s", - o.Endpoint, - o.NvidiaBucket, - o.AccessKey, - o.SecretKey, - o.IsCeph, - o.NvidiaInstallerLocation) - - if o.NvidiaTOKLocation != "" { - ansibleUserVars = fmt.Sprintf("%s nvidia_tok_location=%s", - ansibleUserVars, - o.NvidiaTOKLocation) - } - if o.NvidiaGriddFeatureType != -1 { - ansibleUserVars = fmt.Sprintf("%s gridd_feature_type=%d", - ansibleUserVars, - o.NvidiaGriddFeatureType) + if o.NvidiaGriddFeatureType != -1 { + ansibleUserVars = fmt.Sprintf("%s gridd_feature_type=%d", + ansibleUserVars, + o.NvidiaGriddFeatureType) + } + } else if o.GpuVendor == "amd" { + ansibleUserVars = fmt.Sprintf("gpu_vendor=%s amd_version=%s amd_deb_version=%s gpu_amd_usecase=%s", + o.GpuVendor, + o.AMDVersion, + o.AMDDebVersion, + o.AMDUseCase) } } if o.AdditionalImages != nil { - for k, v := range o.AdditionalImages { - if k == 0 { - additionalImages = additionalImages + v + // Little workaround for people leaving an empty field or not having the field in the yaml. + // viper likes to replace a non-existent entry with the string "[]" even when the default is nil. + if o.AdditionalImages[0] == "[]" { + o.AdditionalImages = nil + } else { + for k, v := range o.AdditionalImages { + if k == 0 { + additionalImages = additionalImages + v + } else { + additionalImages = additionalImages + "," + v + } + } + if len(ansibleUserVars) == 0 { + ansibleUserVars = "load_additional_components=true additional_registry_images=true additional_registry_images_list=" + additionalImages } else { - additionalImages = additionalImages + "," + v + ansibleUserVars = ansibleUserVars + " load_additional_components=true additional_registry_images=true additional_registry_images_list=" + additionalImages } } - if len(ansibleUserVars) == 0 { - ansibleUserVars = "load_additional_components=true additional_registry_images=true additional_registry_images_list=" + additionalImages - } else { - ansibleUserVars = ansibleUserVars + " load_additional_components=true additional_registry_images=true additional_registry_images_list=" + additionalImages - } } if o.AddFalco || o.AddTrivy { @@ -231,15 +139,64 @@ func InitConfig(o *flags.BuildOptions) *Buildconfig { } } - buildConfig.NodeCustomRolesPre = customRoles - buildConfig.AnsibleUserVars = ansibleUserVars - buildConfig.ImageName = generateImageName(o.ImagePrefix) + b.NodeCustomRolesPre = customRoles + b.AnsibleUserVars = ansibleUserVars + + imgName, err := generateImageName(o.ImagePrefix) + if err != nil { + return nil, "", err + } + + return b, imgName, nil +} - return buildConfig +type BuildersModifier struct { + Function func(metadata map[string]string, data []byte) []byte + Metadata map[string]string } -// GenerateVariablesFile converts the Buildconfig into a build configuration file that packer can use. -func (p *Buildconfig) GenerateVariablesFile(buildGitDir string) { +// UpdatePackerBuildersJson pre-populates the metadata field in the packer.json file as objects cannot be passed as variables in packer. +func UpdatePackerBuildersJson(dir string, infra string, modifier BuildersModifier) error { + // change infra to qemu if kubevirt is the infra type as this is what is needed to build + if infra == "kubevirt" { + infra = "qemu" + } + + file, err := os.OpenFile(filepath.Join(dir, "images", "capi", "packer", infra, "packer.json"), os.O_RDWR, 0644) + if err != nil { + return err + } + defer file.Close() + + data, err := io.ReadAll(file) + if err != nil { + return err + } + + res := modifier.Function(modifier.Metadata, data) + + if res == nil { + return nil + } + + err = file.Truncate(0) + if err != nil { + return err + } + _, err = file.Seek(0, 0) + if err != nil { + return err + } + + _, err = file.Write(res) + if err != nil { + return err + } + return nil +} + +// GenerateVariablesFile converts the GlobalBuildConfig into a build configuration file that packer can use. +func (p *GlobalBuildConfig) GenerateVariablesFile(buildGitDir string) { outputFileName := strings.Join([]string{"tmp", ".json"}, "") outputFile := filepath.Join(buildGitDir, outputFileName) @@ -253,3 +210,22 @@ func (p *Buildconfig) GenerateVariablesFile(buildGitDir string) { log.Fatalln(err) } } + +// Extract Kubernetes series with the assumption we want vX.XX +func truncateVersion(version string) string { + re := regexp.MustCompile(`v\d+\.\d+`) + return re.FindString(version) +} + +// generateImageName creates a name for the image that will be built. +func generateImageName(imagePrefix string) (string, error) { + imageUUID, err := uuid.NewRandom() + if err != nil { + return "", err + } + + shortDate := time.Now().Format("060102") + shortUUID := imageUUID.String()[:strings.Index(imageUUID.String(), "-")] + + return imagePrefix + "-" + shortDate + "-" + shortUUID, nil +} diff --git a/pkg/providers/packer/client_test.go b/pkg/providers/packer/client_test.go index 3431cc8..8337291 100644 --- a/pkg/providers/packer/client_test.go +++ b/pkg/providers/packer/client_test.go @@ -17,25 +17,700 @@ limitations under the License. package packer import ( + "encoding/json" + "github.com/drewbernetes/baski/pkg/util/flags" + "log" + "os" + "path/filepath" + "reflect" + "regexp" "testing" ) -// TestInitPackerConfig takes the application inputs and converts it into a Buildconfig. -func TestInitConfig(t *testing.T) { +var packerConf = `{ + "builders": [ + {} + ], + "post-processors": [], + "provisioners": [], + "variables": {} +}` -} +func TestNewCoreBuildconfig(t *testing.T) { + tc := []struct { + name string + options flags.BuildOptions + expected GlobalBuildConfig + }{ + { + name: "Test basic config with no additions", + options: flags.BuildOptions{ + ImagePrefix: "kmi", + CniVersion: "1.2.0", + CniDebVersion: "1.2.0-2.1", + CrictlVersion: "1.26.0", + KubeVersion: "1.28.2", + KubeRpmVersion: "1.28.2", + KubeDebVersion: "1.28.2-1.1", + ExtraDebs: "nfs-common", + }, + expected: GlobalBuildConfig{ + CniVersion: "v1.2.0", + CniDebVersion: "1.2.0-2.1", + CrictlVersion: "1.26.0", + KubernetesSemver: "v1.28.2", + KubernetesRpmVersion: "1.28.2", + KubernetesSeries: "v1.28", + KubernetesDebVersion: "1.28.2-1.1", + ExtraDebs: "nfs-common", + }, + }, + { + name: "Test config with Falco", + options: flags.BuildOptions{ + ImagePrefix: "kmi", + CniVersion: "1.2.0", + CniDebVersion: "1.2.0-2.1", + CrictlVersion: "1.26.0", + KubeVersion: "1.28.2", + KubeRpmVersion: "1.28.2", + KubeDebVersion: "1.28.2-1.1", + ExtraDebs: "nfs-common", + AddFalco: true, + }, + expected: GlobalBuildConfig{ + CniVersion: "v1.2.0", + CniDebVersion: "1.2.0-2.1", + CrictlVersion: "1.26.0", + KubernetesSemver: "v1.28.2", + KubernetesRpmVersion: "1.28.2", + KubernetesSeries: "v1.28", + KubernetesDebVersion: "1.28.2-1.1", + NodeCustomRolesPre: "security", + AnsibleUserVars: "install_falco=true", + ExtraDebs: "nfs-common", + }, + }, + { + name: "Test config with Trivy", + options: flags.BuildOptions{ + ImagePrefix: "kmi", + CniVersion: "1.2.0", + CniDebVersion: "1.2.0-2.1", + CrictlVersion: "1.26.0", + KubeVersion: "1.28.2", + KubeRpmVersion: "1.28.2", + KubeDebVersion: "1.28.2-1.1", + ExtraDebs: "nfs-common", + AddTrivy: true, + }, + expected: GlobalBuildConfig{ + CniVersion: "v1.2.0", + CniDebVersion: "1.2.0-2.1", + CrictlVersion: "1.26.0", + KubernetesSemver: "v1.28.2", + KubernetesRpmVersion: "1.28.2", + KubernetesSeries: "v1.28", + KubernetesDebVersion: "1.28.2-1.1", + NodeCustomRolesPre: "security", + AnsibleUserVars: "install_trivy=true", + ExtraDebs: "nfs-common", + }, + }, + { + name: "Test config with Falco & trivy", + options: flags.BuildOptions{ + ImagePrefix: "kmi", + CniVersion: "1.2.0", + CniDebVersion: "1.2.0-2.1", + CrictlVersion: "1.26.0", + KubeVersion: "1.28.2", + KubeRpmVersion: "1.28.2", + KubeDebVersion: "1.28.2-1.1", + ExtraDebs: "nfs-common", + AddFalco: true, + AddTrivy: true, + }, + expected: GlobalBuildConfig{ + CniVersion: "v1.2.0", + CniDebVersion: "1.2.0-2.1", + CrictlVersion: "1.26.0", + KubernetesSemver: "v1.28.2", + KubernetesRpmVersion: "1.28.2", + KubernetesSeries: "v1.28", + KubernetesDebVersion: "1.28.2-1.1", + NodeCustomRolesPre: "security", + AnsibleUserVars: "install_falco=true install_trivy=true", + ExtraDebs: "nfs-common", + }, + }, + { + name: "Test config with additional images", + options: flags.BuildOptions{ + ImagePrefix: "kmi", + CniVersion: "1.2.0", + CniDebVersion: "1.2.0-2.1", + CrictlVersion: "1.26.0", + KubeVersion: "1.28.2", + KubeRpmVersion: "1.28.2", + KubeDebVersion: "1.28.2-1.1", + ExtraDebs: "nfs-common", + AdditionalImages: []string{"image1", "image2"}, + }, + expected: GlobalBuildConfig{ + CniVersion: "v1.2.0", + CniDebVersion: "1.2.0-2.1", + CrictlVersion: "1.26.0", + KubernetesSemver: "v1.28.2", + KubernetesRpmVersion: "1.28.2", + KubernetesSeries: "v1.28", + KubernetesDebVersion: "1.28.2-1.1", + AnsibleUserVars: "load_additional_components=true additional_registry_images=true additional_registry_images_list=image1,image2", + ExtraDebs: "nfs-common", + }, + }, + { + name: "Test config with additional images & security", + options: flags.BuildOptions{ + ImagePrefix: "kmi", + CniVersion: "1.2.0", + CniDebVersion: "1.2.0-2.1", + CrictlVersion: "1.26.0", + KubeVersion: "1.28.2", + KubeRpmVersion: "1.28.2", + KubeDebVersion: "1.28.2-1.1", + ExtraDebs: "nfs-common", + AdditionalImages: []string{"image1", "image2"}, + AddFalco: true, + AddTrivy: true, + }, + expected: GlobalBuildConfig{ + CniVersion: "v1.2.0", + CniDebVersion: "1.2.0-2.1", + CrictlVersion: "1.26.0", + KubernetesSemver: "v1.28.2", + KubernetesRpmVersion: "1.28.2", + KubernetesSeries: "v1.28", + KubernetesDebVersion: "1.28.2-1.1", + NodeCustomRolesPre: "security", + AnsibleUserVars: "load_additional_components=true additional_registry_images=true additional_registry_images_list=image1,image2 install_falco=true install_trivy=true", + ExtraDebs: "nfs-common", + }, + }, + { + name: "Test config with AMD GPU", + options: flags.BuildOptions{ + ImagePrefix: "kmi", + CniVersion: "1.2.0", + CniDebVersion: "1.2.0-2.1", + CrictlVersion: "1.26.0", + KubeVersion: "1.28.2", + KubeRpmVersion: "1.28.2", + KubeDebVersion: "1.28.2-1.1", + ExtraDebs: "nfs-common", + AddGpuSupport: true, + GpuVendor: "amd", + AMDVersion: "6.0.2", + AMDDebVersion: "6.0.60002-1", + AMDUseCase: "dkms", + }, + expected: GlobalBuildConfig{ + CniVersion: "v1.2.0", + CniDebVersion: "1.2.0-2.1", + CrictlVersion: "1.26.0", + KubernetesSemver: "v1.28.2", + KubernetesRpmVersion: "1.28.2", + KubernetesSeries: "v1.28", + KubernetesDebVersion: "1.28.2-1.1", + NodeCustomRolesPre: "gpu", + AnsibleUserVars: "gpu_vendor=amd amd_version=6.0.2 amd_deb_version=6.0.60002-1 gpu_amd_usecase=dkms", + ExtraDebs: "nfs-common", + }, + }, + { + name: "Test config with AMD GPU & security", + options: flags.BuildOptions{ + ImagePrefix: "kmi", + CniVersion: "1.2.0", + CniDebVersion: "1.2.0-2.1", + CrictlVersion: "1.26.0", + KubeVersion: "1.28.2", + KubeRpmVersion: "1.28.2", + KubeDebVersion: "1.28.2-1.1", + ExtraDebs: "nfs-common", + AddFalco: true, + AddTrivy: true, + AddGpuSupport: true, + GpuVendor: "amd", + AMDVersion: "6.0.2", + AMDDebVersion: "6.0.60002-1", + AMDUseCase: "dkms", + }, + expected: GlobalBuildConfig{ + CniVersion: "v1.2.0", + CniDebVersion: "1.2.0-2.1", + CrictlVersion: "1.26.0", + KubernetesSemver: "v1.28.2", + KubernetesRpmVersion: "1.28.2", + KubernetesSeries: "v1.28", + KubernetesDebVersion: "1.28.2-1.1", + NodeCustomRolesPre: "gpu security", + AnsibleUserVars: "gpu_vendor=amd amd_version=6.0.2 amd_deb_version=6.0.60002-1 gpu_amd_usecase=dkms install_falco=true install_trivy=true", + ExtraDebs: "nfs-common", + }, + }, + { + name: "Test config with AMD GPU, additional images & security", + options: flags.BuildOptions{ + ImagePrefix: "kmi", + CniVersion: "1.2.0", + CniDebVersion: "1.2.0-2.1", + CrictlVersion: "1.26.0", + KubeVersion: "1.28.2", + KubeRpmVersion: "1.28.2", + KubeDebVersion: "1.28.2-1.1", + ExtraDebs: "nfs-common", + AdditionalImages: []string{"image1", "image2"}, + AddFalco: true, + AddTrivy: true, + AddGpuSupport: true, + GpuVendor: "amd", + AMDVersion: "6.0.2", + AMDDebVersion: "6.0.60002-1", + AMDUseCase: "dkms", + }, + expected: GlobalBuildConfig{ + CniVersion: "v1.2.0", + CniDebVersion: "1.2.0-2.1", + CrictlVersion: "1.26.0", + KubernetesSemver: "v1.28.2", + KubernetesRpmVersion: "1.28.2", + KubernetesSeries: "v1.28", + KubernetesDebVersion: "1.28.2-1.1", + NodeCustomRolesPre: "gpu security", + AnsibleUserVars: "gpu_vendor=amd amd_version=6.0.2 amd_deb_version=6.0.60002-1 gpu_amd_usecase=dkms load_additional_components=true additional_registry_images=true additional_registry_images_list=image1,image2 install_falco=true install_trivy=true", + ExtraDebs: "nfs-common", + }, + }, + { + name: "Test config with NVIDIA GPU", + options: flags.BuildOptions{ + ImagePrefix: "kmi", + CniVersion: "1.2.0", + CniDebVersion: "1.2.0-2.1", + CrictlVersion: "1.26.0", + KubeVersion: "1.28.2", + KubeRpmVersion: "1.28.2", + KubeDebVersion: "1.28.2-1.1", + ExtraDebs: "nfs-common", + AddGpuSupport: true, + GpuVendor: "nvidia", + NvidiaVersion: "535.129.03", + NvidiaBucket: "nvidia", + NvidiaInstallerLocation: "NVIDIA-Linux-x86_64-535.129.03-grid.run", + NvidiaTOKLocation: "client_configuration_token.tok", + NvidiaGriddFeatureType: 4, + S3Flags: flags.S3Flags{ + Endpoint: "https://example.com", + AccessKey: "123456", + SecretKey: "987654", + Region: "us-east-1", + IsCeph: true, + }, + }, + expected: GlobalBuildConfig{ + CniVersion: "v1.2.0", + CniDebVersion: "1.2.0-2.1", + CrictlVersion: "1.26.0", + KubernetesSemver: "v1.28.2", + KubernetesRpmVersion: "1.28.2", + KubernetesSeries: "v1.28", + KubernetesDebVersion: "1.28.2-1.1", + NodeCustomRolesPre: "gpu", + AnsibleUserVars: "gpu_vendor=nvidia nvidia_s3_url=https://example.com nvidia_bucket=nvidia nvidia_bucket_access=123456 nvidia_bucket_secret=987654 nvidia_ceph=true nvidia_installer_location=NVIDIA-Linux-x86_64-535.129.03-grid.run nvidia_tok_location=client_configuration_token.tok gridd_feature_type=4", + ExtraDebs: "nfs-common", + }, + }, + { + name: "Test config with NVIDIA GPU & security", + options: flags.BuildOptions{ + ImagePrefix: "kmi", + CniVersion: "1.2.0", + CniDebVersion: "1.2.0-2.1", + CrictlVersion: "1.26.0", + KubeVersion: "1.28.2", + KubeRpmVersion: "1.28.2", + KubeDebVersion: "1.28.2-1.1", + ExtraDebs: "nfs-common", + AddFalco: true, + AddTrivy: true, + AddGpuSupport: true, + GpuVendor: "nvidia", + NvidiaVersion: "535.129.03", + NvidiaBucket: "nvidia", + NvidiaInstallerLocation: "NVIDIA-Linux-x86_64-535.129.03-grid.run", + NvidiaTOKLocation: "client_configuration_token.tok", + NvidiaGriddFeatureType: 4, + S3Flags: flags.S3Flags{ + Endpoint: "https://example.com", + AccessKey: "123456", + SecretKey: "987654", + Region: "us-east-1", + IsCeph: true, + }, + }, + expected: GlobalBuildConfig{ + CniVersion: "v1.2.0", + CniDebVersion: "1.2.0-2.1", + CrictlVersion: "1.26.0", + KubernetesSemver: "v1.28.2", + KubernetesRpmVersion: "1.28.2", + KubernetesSeries: "v1.28", + KubernetesDebVersion: "1.28.2-1.1", + NodeCustomRolesPre: "gpu security", + AnsibleUserVars: "gpu_vendor=nvidia nvidia_s3_url=https://example.com nvidia_bucket=nvidia nvidia_bucket_access=123456 nvidia_bucket_secret=987654 nvidia_ceph=true nvidia_installer_location=NVIDIA-Linux-x86_64-535.129.03-grid.run nvidia_tok_location=client_configuration_token.tok gridd_feature_type=4 install_falco=true install_trivy=true", + ExtraDebs: "nfs-common", + }, + }, + { + name: "Test config with NVIDIA GPU, additional images & security", + options: flags.BuildOptions{ + ImagePrefix: "kmi", + CniVersion: "1.2.0", + CniDebVersion: "1.2.0-2.1", + CrictlVersion: "1.26.0", + KubeVersion: "1.28.2", + KubeRpmVersion: "1.28.2", + KubeDebVersion: "1.28.2-1.1", + ExtraDebs: "nfs-common", + AdditionalImages: []string{"image1", "image2"}, + AddFalco: true, + AddTrivy: true, + AddGpuSupport: true, + GpuVendor: "nvidia", + NvidiaVersion: "535.129.03", + NvidiaBucket: "nvidia", + NvidiaInstallerLocation: "NVIDIA-Linux-x86_64-535.129.03-grid.run", + NvidiaTOKLocation: "client_configuration_token.tok", + NvidiaGriddFeatureType: 4, + S3Flags: flags.S3Flags{ + Endpoint: "https://example.com", + AccessKey: "123456", + SecretKey: "987654", + Region: "us-east-1", + IsCeph: true, + }, + }, + expected: GlobalBuildConfig{ + CniVersion: "v1.2.0", + CniDebVersion: "1.2.0-2.1", + CrictlVersion: "1.26.0", + KubernetesSemver: "v1.28.2", + KubernetesRpmVersion: "1.28.2", + KubernetesSeries: "v1.28", + KubernetesDebVersion: "1.28.2-1.1", + NodeCustomRolesPre: "gpu security", + AnsibleUserVars: "gpu_vendor=nvidia nvidia_s3_url=https://example.com nvidia_bucket=nvidia nvidia_bucket_access=123456 nvidia_bucket_secret=987654 nvidia_ceph=true nvidia_installer_location=NVIDIA-Linux-x86_64-535.129.03-grid.run nvidia_tok_location=client_configuration_token.tok gridd_feature_type=4 load_additional_components=true additional_registry_images=true additional_registry_images_list=image1,image2 install_falco=true install_trivy=true", + ExtraDebs: "nfs-common", + }, + }, + } + + for _, v := range tc { + t.Run(v.name, func(t *testing.T) { + conf, name, err := NewCoreBuildconfig(&v.options) + if err != nil { + t.Errorf(err.Error()) + } + + re := regexp.MustCompile(`kmi-\d{6}-\w{8}`) + if !re.MatchString(name) { + t.Errorf("expected name %s, but it didn't match the regex\n", name) + } -// TestGenerateBuilderMetadata generates some glance metadata for the image. -func TestGenerateBuilderMetadata(t *testing.T) { + if !reflect.DeepEqual(&v.expected, conf) { + t.Errorf("expected: %v, got %v\n", &v.expected, conf) + } + }) + } } -// TestUpdatePackerBuildersJson pre-populates the metadata field in the packer.json file as objects cannot be passed as variables in packer. func TestUpdatePackerBuildersJson(t *testing.T) { + infra := "test" + dir := "/tmp/test" + expected := `{"builders":[{"metadata":{"test":"test"}}],"post-processors":[],"provisioners":[],"variables":{}}` + + fullPath := filepath.Join(dir, "images", "capi", "packer", infra) + + err := os.MkdirAll(fullPath, 0755) + if err != nil { + t.Fatalf(err.Error()) + } + + err = os.WriteFile(filepath.Join(fullPath, "packer.json"), []byte(packerConf), 0644) + if err != nil { + t.Fatalf(err.Error()) + } + + modifierFunc := BuildersModifier{ + Function: func(metadata map[string]string, data []byte) []byte { + jsonStruct := struct { + Builders []map[string]interface{} `json:"builders"` + PostProcessors []map[string]interface{} `json:"post-processors"` + Provisioners []map[string]interface{} `json:"provisioners"` + Variables map[string]interface{} `json:"variables"` + }{} + + err := json.Unmarshal(data, &jsonStruct) + if err != nil { + log.Fatalln(err) + return nil + } + + jsonStruct.Builders[0]["metadata"] = metadata + res, err := json.Marshal(jsonStruct) + if err != nil { + log.Fatalln(err) + return nil + } + + return res + }, + Metadata: map[string]string{"test": "test"}, + } + + err = UpdatePackerBuildersJson(dir, infra, modifierFunc) + if err != nil { + t.Fatalf(err.Error()) + } + + file, err := os.ReadFile(filepath.Join(fullPath, "packer.json")) + if err != nil { + t.Fatalf(err.Error()) + } + + if expected != string(file) { + t.Errorf("expected: %s, got: %s\n", expected, string(file)) + } } -// TestGenerateVariablesFile converts the Buildconfig into a build configuration file that packer can use. func TestGenerateVariablesFile(t *testing.T) { + tc := []struct { + name string + conf GlobalBuildConfig + expected string + }{ + { + name: "Test basic config with no additions", + conf: GlobalBuildConfig{ + CniVersion: "v1.2.0", + CniDebVersion: "1.2.0-2.1", + CrictlVersion: "1.26.0", + KubernetesSemver: "v1.28.2", + KubernetesRpmVersion: "1.28.2", + KubernetesSeries: "v1.28", + KubernetesDebVersion: "1.28.2-1.1", + ExtraDebs: "nfs-common", + }, + expected: `{"kubernetes_cni_semver":"v1.2.0","kubernetes_cni_deb_version":"1.2.0-2.1","crictl_version":"1.26.0","kubernetes_semver":"v1.28.2","kubernetes_rpm_version":"1.28.2","kubernetes_series":"v1.28","kubernetes_deb_version":"1.28.2-1.1","extra_debs":"nfs-common","source_image":"","networks":"","flavor":"","image_disk_format":"","volume_type":"","volume_size":"","qemu_binary":"","disk_size":"","output_directory":""}`, + }, + { + name: "Test config with Falco", + conf: GlobalBuildConfig{ + CniVersion: "v1.2.0", + CniDebVersion: "1.2.0-2.1", + CrictlVersion: "1.26.0", + KubernetesSemver: "v1.28.2", + KubernetesRpmVersion: "1.28.2", + KubernetesSeries: "v1.28", + KubernetesDebVersion: "1.28.2-1.1", + NodeCustomRolesPre: "security", + AnsibleUserVars: "install_falco=true", + ExtraDebs: "nfs-common", + }, + expected: `{"kubernetes_cni_semver":"v1.2.0","kubernetes_cni_deb_version":"1.2.0-2.1","crictl_version":"1.26.0","kubernetes_semver":"v1.28.2","kubernetes_rpm_version":"1.28.2","kubernetes_series":"v1.28","kubernetes_deb_version":"1.28.2-1.1","node_custom_roles_pre":"security","ansible_user_vars":"install_falco=true","extra_debs":"nfs-common","source_image":"","networks":"","flavor":"","image_disk_format":"","volume_type":"","volume_size":"","qemu_binary":"","disk_size":"","output_directory":""}`, + }, + { + name: "Test config with Trivy", + conf: GlobalBuildConfig{ + CniVersion: "v1.2.0", + CniDebVersion: "1.2.0-2.1", + CrictlVersion: "1.26.0", + KubernetesSemver: "v1.28.2", + KubernetesRpmVersion: "1.28.2", + KubernetesSeries: "v1.28", + KubernetesDebVersion: "1.28.2-1.1", + NodeCustomRolesPre: "security", + AnsibleUserVars: "install_trivy=true", + ExtraDebs: "nfs-common", + }, + expected: `{"kubernetes_cni_semver":"v1.2.0","kubernetes_cni_deb_version":"1.2.0-2.1","crictl_version":"1.26.0","kubernetes_semver":"v1.28.2","kubernetes_rpm_version":"1.28.2","kubernetes_series":"v1.28","kubernetes_deb_version":"1.28.2-1.1","node_custom_roles_pre":"security","ansible_user_vars":"install_trivy=true","extra_debs":"nfs-common","source_image":"","networks":"","flavor":"","image_disk_format":"","volume_type":"","volume_size":"","qemu_binary":"","disk_size":"","output_directory":""}`, + }, + { + name: "Test config with Falco & trivy", + conf: GlobalBuildConfig{ + CniVersion: "v1.2.0", + CniDebVersion: "1.2.0-2.1", + CrictlVersion: "1.26.0", + KubernetesSemver: "v1.28.2", + KubernetesRpmVersion: "1.28.2", + KubernetesSeries: "v1.28", + KubernetesDebVersion: "1.28.2-1.1", + NodeCustomRolesPre: "security", + AnsibleUserVars: "install_falco=true install_trivy=true", + ExtraDebs: "nfs-common", + }, + expected: `{"kubernetes_cni_semver":"v1.2.0","kubernetes_cni_deb_version":"1.2.0-2.1","crictl_version":"1.26.0","kubernetes_semver":"v1.28.2","kubernetes_rpm_version":"1.28.2","kubernetes_series":"v1.28","kubernetes_deb_version":"1.28.2-1.1","node_custom_roles_pre":"security","ansible_user_vars":"install_falco=true install_trivy=true","extra_debs":"nfs-common","source_image":"","networks":"","flavor":"","image_disk_format":"","volume_type":"","volume_size":"","qemu_binary":"","disk_size":"","output_directory":""}`, + }, + { + name: "Test config with additional images", + conf: GlobalBuildConfig{ + CniVersion: "v1.2.0", + CniDebVersion: "1.2.0-2.1", + CrictlVersion: "1.26.0", + KubernetesSemver: "v1.28.2", + KubernetesRpmVersion: "1.28.2", + KubernetesSeries: "v1.28", + KubernetesDebVersion: "1.28.2-1.1", + AnsibleUserVars: "load_additional_components=true additional_registry_images=true additional_registry_images_list=image1,image2", + ExtraDebs: "nfs-common", + }, + expected: `{"kubernetes_cni_semver":"v1.2.0","kubernetes_cni_deb_version":"1.2.0-2.1","crictl_version":"1.26.0","kubernetes_semver":"v1.28.2","kubernetes_rpm_version":"1.28.2","kubernetes_series":"v1.28","kubernetes_deb_version":"1.28.2-1.1","ansible_user_vars":"load_additional_components=true additional_registry_images=true additional_registry_images_list=image1,image2","extra_debs":"nfs-common","source_image":"","networks":"","flavor":"","image_disk_format":"","volume_type":"","volume_size":"","qemu_binary":"","disk_size":"","output_directory":""}`, + }, + { + name: "Test config with additional images & security", + conf: GlobalBuildConfig{ + CniVersion: "v1.2.0", + CniDebVersion: "1.2.0-2.1", + CrictlVersion: "1.26.0", + KubernetesSemver: "v1.28.2", + KubernetesRpmVersion: "1.28.2", + KubernetesSeries: "v1.28", + KubernetesDebVersion: "1.28.2-1.1", + NodeCustomRolesPre: "security", + AnsibleUserVars: "load_additional_components=true additional_registry_images=true additional_registry_images_list=image1,image2 install_falco=true install_trivy=true", + ExtraDebs: "nfs-common", + }, + expected: `{"kubernetes_cni_semver":"v1.2.0","kubernetes_cni_deb_version":"1.2.0-2.1","crictl_version":"1.26.0","kubernetes_semver":"v1.28.2","kubernetes_rpm_version":"1.28.2","kubernetes_series":"v1.28","kubernetes_deb_version":"1.28.2-1.1","node_custom_roles_pre":"security","ansible_user_vars":"load_additional_components=true additional_registry_images=true additional_registry_images_list=image1,image2 install_falco=true install_trivy=true","extra_debs":"nfs-common","source_image":"","networks":"","flavor":"","image_disk_format":"","volume_type":"","volume_size":"","qemu_binary":"","disk_size":"","output_directory":""}`, + }, + { + name: "Test config with AMD GPU", + conf: GlobalBuildConfig{ + CniVersion: "v1.2.0", + CniDebVersion: "1.2.0-2.1", + CrictlVersion: "1.26.0", + KubernetesSemver: "v1.28.2", + KubernetesRpmVersion: "1.28.2", + KubernetesSeries: "v1.28", + KubernetesDebVersion: "1.28.2-1.1", + NodeCustomRolesPre: "gpu", + AnsibleUserVars: "gpu_vendor=amd amd_version=6.0.2 amd_deb_version=6.0.60002-1 gpu_amd_usecase=dkms", + ExtraDebs: "nfs-common", + }, + expected: `{"kubernetes_cni_semver":"v1.2.0","kubernetes_cni_deb_version":"1.2.0-2.1","crictl_version":"1.26.0","kubernetes_semver":"v1.28.2","kubernetes_rpm_version":"1.28.2","kubernetes_series":"v1.28","kubernetes_deb_version":"1.28.2-1.1","node_custom_roles_pre":"gpu","ansible_user_vars":"gpu_vendor=amd amd_version=6.0.2 amd_deb_version=6.0.60002-1 gpu_amd_usecase=dkms","extra_debs":"nfs-common","source_image":"","networks":"","flavor":"","image_disk_format":"","volume_type":"","volume_size":"","qemu_binary":"","disk_size":"","output_directory":""}`, + }, + { + name: "Test config with AMD GPU & security", + conf: GlobalBuildConfig{ + CniVersion: "v1.2.0", + CniDebVersion: "1.2.0-2.1", + CrictlVersion: "1.26.0", + KubernetesSemver: "v1.28.2", + KubernetesRpmVersion: "1.28.2", + KubernetesSeries: "v1.28", + KubernetesDebVersion: "1.28.2-1.1", + NodeCustomRolesPre: "gpu security", + AnsibleUserVars: "gpu_vendor=amd amd_version=6.0.2 amd_deb_version=6.0.60002-1 gpu_amd_usecase=dkms install_falco=true install_trivy=true", + ExtraDebs: "nfs-common", + }, + expected: `{"kubernetes_cni_semver":"v1.2.0","kubernetes_cni_deb_version":"1.2.0-2.1","crictl_version":"1.26.0","kubernetes_semver":"v1.28.2","kubernetes_rpm_version":"1.28.2","kubernetes_series":"v1.28","kubernetes_deb_version":"1.28.2-1.1","node_custom_roles_pre":"gpu security","ansible_user_vars":"gpu_vendor=amd amd_version=6.0.2 amd_deb_version=6.0.60002-1 gpu_amd_usecase=dkms install_falco=true install_trivy=true","extra_debs":"nfs-common","source_image":"","networks":"","flavor":"","image_disk_format":"","volume_type":"","volume_size":"","qemu_binary":"","disk_size":"","output_directory":""}`, + }, + { + name: "Test config with AMD GPU, additional images & security", + conf: GlobalBuildConfig{ + CniVersion: "v1.2.0", + CniDebVersion: "1.2.0-2.1", + CrictlVersion: "1.26.0", + KubernetesSemver: "v1.28.2", + KubernetesRpmVersion: "1.28.2", + KubernetesSeries: "v1.28", + KubernetesDebVersion: "1.28.2-1.1", + NodeCustomRolesPre: "gpu security", + AnsibleUserVars: "gpu_vendor=amd amd_version=6.0.2 amd_deb_version=6.0.60002-1 gpu_amd_usecase=dkms load_additional_components=true additional_registry_images=true additional_registry_images_list=image1,image2 install_falco=true install_trivy=true", + ExtraDebs: "nfs-common", + }, + expected: `{"kubernetes_cni_semver":"v1.2.0","kubernetes_cni_deb_version":"1.2.0-2.1","crictl_version":"1.26.0","kubernetes_semver":"v1.28.2","kubernetes_rpm_version":"1.28.2","kubernetes_series":"v1.28","kubernetes_deb_version":"1.28.2-1.1","node_custom_roles_pre":"gpu security","ansible_user_vars":"gpu_vendor=amd amd_version=6.0.2 amd_deb_version=6.0.60002-1 gpu_amd_usecase=dkms load_additional_components=true additional_registry_images=true additional_registry_images_list=image1,image2 install_falco=true install_trivy=true","extra_debs":"nfs-common","source_image":"","networks":"","flavor":"","image_disk_format":"","volume_type":"","volume_size":"","qemu_binary":"","disk_size":"","output_directory":""}`, + }, + { + name: "Test config with NVIDIA GPU", + conf: GlobalBuildConfig{ + CniVersion: "v1.2.0", + CniDebVersion: "1.2.0-2.1", + CrictlVersion: "1.26.0", + KubernetesSemver: "v1.28.2", + KubernetesRpmVersion: "1.28.2", + KubernetesSeries: "v1.28", + KubernetesDebVersion: "1.28.2-1.1", + NodeCustomRolesPre: "gpu", + AnsibleUserVars: "gpu_vendor=nvidia nvidia_s3_url=https://example.com nvidia_bucket=nvidia nvidia_bucket_access=123456 nvidia_bucket_secret=987654 nvidia_ceph=true nvidia_installer_location=NVIDIA-Linux-x86_64-535.129.03-grid.run nvidia_tok_location=client_configuration_token.tok gridd_feature_type=4", + ExtraDebs: "nfs-common", + }, + expected: `{"kubernetes_cni_semver":"v1.2.0","kubernetes_cni_deb_version":"1.2.0-2.1","crictl_version":"1.26.0","kubernetes_semver":"v1.28.2","kubernetes_rpm_version":"1.28.2","kubernetes_series":"v1.28","kubernetes_deb_version":"1.28.2-1.1","node_custom_roles_pre":"gpu","ansible_user_vars":"gpu_vendor=nvidia nvidia_s3_url=https://example.com nvidia_bucket=nvidia nvidia_bucket_access=123456 nvidia_bucket_secret=987654 nvidia_ceph=true nvidia_installer_location=NVIDIA-Linux-x86_64-535.129.03-grid.run nvidia_tok_location=client_configuration_token.tok gridd_feature_type=4","extra_debs":"nfs-common","source_image":"","networks":"","flavor":"","image_disk_format":"","volume_type":"","volume_size":"","qemu_binary":"","disk_size":"","output_directory":""}`, + }, + { + name: "Test config with NVIDIA GPU & security", + conf: GlobalBuildConfig{ + CniVersion: "v1.2.0", + CniDebVersion: "1.2.0-2.1", + CrictlVersion: "1.26.0", + KubernetesSemver: "v1.28.2", + KubernetesRpmVersion: "1.28.2", + KubernetesSeries: "v1.28", + KubernetesDebVersion: "1.28.2-1.1", + NodeCustomRolesPre: "gpu security", + AnsibleUserVars: "gpu_vendor=nvidia nvidia_s3_url=https://example.com nvidia_bucket=nvidia nvidia_bucket_access=123456 nvidia_bucket_secret=987654 nvidia_ceph=true nvidia_installer_location=NVIDIA-Linux-x86_64-535.129.03-grid.run nvidia_tok_location=client_configuration_token.tok gridd_feature_type=4 install_falco=true install_trivy=true", + ExtraDebs: "nfs-common", + }, + expected: `{"kubernetes_cni_semver":"v1.2.0","kubernetes_cni_deb_version":"1.2.0-2.1","crictl_version":"1.26.0","kubernetes_semver":"v1.28.2","kubernetes_rpm_version":"1.28.2","kubernetes_series":"v1.28","kubernetes_deb_version":"1.28.2-1.1","node_custom_roles_pre":"gpu security","ansible_user_vars":"gpu_vendor=nvidia nvidia_s3_url=https://example.com nvidia_bucket=nvidia nvidia_bucket_access=123456 nvidia_bucket_secret=987654 nvidia_ceph=true nvidia_installer_location=NVIDIA-Linux-x86_64-535.129.03-grid.run nvidia_tok_location=client_configuration_token.tok gridd_feature_type=4 install_falco=true install_trivy=true","extra_debs":"nfs-common","source_image":"","networks":"","flavor":"","image_disk_format":"","volume_type":"","volume_size":"","qemu_binary":"","disk_size":"","output_directory":""}`, + }, + { + name: "Test config with NVIDIA GPU, additional images & security", + conf: GlobalBuildConfig{ + CniVersion: "v1.2.0", + CniDebVersion: "1.2.0-2.1", + CrictlVersion: "1.26.0", + KubernetesSemver: "v1.28.2", + KubernetesRpmVersion: "1.28.2", + KubernetesSeries: "v1.28", + KubernetesDebVersion: "1.28.2-1.1", + NodeCustomRolesPre: "gpu security", + AnsibleUserVars: "gpu_vendor=nvidia nvidia_s3_url=https://example.com nvidia_bucket=nvidia nvidia_bucket_access=123456 nvidia_bucket_secret=987654 nvidia_ceph=true nvidia_installer_location=NVIDIA-Linux-x86_64-535.129.03-grid.run nvidia_tok_location=client_configuration_token.tok gridd_feature_type=4 load_additional_components=true additional_registry_images=true additional_registry_images_list=image1,image2 install_falco=true install_trivy=true", + ExtraDebs: "nfs-common", + }, + expected: `{"kubernetes_cni_semver":"v1.2.0","kubernetes_cni_deb_version":"1.2.0-2.1","crictl_version":"1.26.0","kubernetes_semver":"v1.28.2","kubernetes_rpm_version":"1.28.2","kubernetes_series":"v1.28","kubernetes_deb_version":"1.28.2-1.1","node_custom_roles_pre":"gpu security","ansible_user_vars":"gpu_vendor=nvidia nvidia_s3_url=https://example.com nvidia_bucket=nvidia nvidia_bucket_access=123456 nvidia_bucket_secret=987654 nvidia_ceph=true nvidia_installer_location=NVIDIA-Linux-x86_64-535.129.03-grid.run nvidia_tok_location=client_configuration_token.tok gridd_feature_type=4 load_additional_components=true additional_registry_images=true additional_registry_images_list=image1,image2 install_falco=true install_trivy=true","extra_debs":"nfs-common","source_image":"","networks":"","flavor":"","image_disk_format":"","volume_type":"","volume_size":"","qemu_binary":"","disk_size":"","output_directory":""}`, + }, + } + + for _, v := range tc { + t.Run(v.name, func(t *testing.T) { + v.conf.GenerateVariablesFile("/tmp/") + + res, err := os.ReadFile("/tmp/tmp.json") + if err != nil { + t.Errorf("%s", err.Error()) + } + + if v.expected != string(res) { + t.Errorf("expected: %s, got %s\n", v.expected, string(res)) + } + + err = os.Truncate("/tmp/tmp.json", 0) + if err != nil { + t.Errorf("%s", err.Error()) + } + }) + } } diff --git a/pkg/providers/packer/kubevirt.go b/pkg/providers/packer/kubevirt.go new file mode 100644 index 0000000..8bbfbcd --- /dev/null +++ b/pkg/providers/packer/kubevirt.go @@ -0,0 +1,8 @@ +package packer + +// KubeVirtBuildConfig adds additional packer vars for Kubevirt +type KubeVirtBuildConfig struct { + QemuBinary string `json:"qemu_binary"` + DiskSize string `json:"disk_size"` + OutputDirectory string `json:"output_directory"` +} diff --git a/pkg/providers/packer/openstack.go b/pkg/providers/packer/openstack.go new file mode 100644 index 0000000..5c57e2c --- /dev/null +++ b/pkg/providers/packer/openstack.go @@ -0,0 +1,20 @@ +package packer + +// OpenStackBuildconfig adds additional packer vars for OpenStack builds +type OpenStackBuildconfig struct { + ImageName string `json:"image_name,omitempty"` + SourceImage string `json:"source_image"` + Networks string `json:"networks"` + Flavor string `json:"flavor"` + AttachConfigDrive string `json:"attach_config_drive,omitempty"` + SSHPrivateKeyFile string `json:"ssh_private_key_file,omitempty"` + SSHKeypairName string `json:"ssh_keypair_name,omitempty"` + UseFloatingIp string `json:"use_floating_ip,omitempty"` + FloatingIpNetwork string `json:"floating_ip_network,omitempty"` + SecurityGroup string `json:"security_groups,omitempty"` + ImageVisibility string `json:"image_visibility,omitempty"` + ImageDiskFormat string `json:"image_disk_format"` + UseBlockStorageVolume string `json:"use_blockstorage_volume,omitempty"` + VolumeType string `json:"volume_type"` + VolumeSize string `json:"volume_size"` +} diff --git a/pkg/providers/scanner/client.go b/pkg/providers/scanner/client.go index 869bb6d..a3b35b8 100644 --- a/pkg/providers/scanner/client.go +++ b/pkg/providers/scanner/client.go @@ -4,35 +4,30 @@ import ( "encoding/json" "errors" "fmt" - ostack "github.com/drewbernetes/baski/pkg/providers/openstack" - sshconnect "github.com/drewbernetes/baski/pkg/remote" - "github.com/drewbernetes/baski/pkg/s3" "github.com/drewbernetes/baski/pkg/trivy" - "github.com/drewbernetes/baski/pkg/util" - "github.com/drewbernetes/baski/pkg/util/data" - "github.com/drewbernetes/baski/pkg/util/flags" - "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/keypairs" - "github.com/gophercloud/gophercloud/openstack/compute/v2/servers" - "github.com/gophercloud/gophercloud/openstack/imageservice/v2/images" - "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips" + "github.com/drewbernetes/baski/pkg/util/interfaces" "log" "os" "time" ) -type ScannerClient struct { - computeClient *ostack.ComputeClient - imageClient *ostack.ImageClient - networkClient *ostack.NetworkClient - keyPair *keypairs.KeyPair - fip *floatingips.FloatingIP - s3Credentials *s3.S3 - server *servers.Server - trivyOptions *trivy.TrivyOptions +//TODO: This may not be needed afterall +//type ScannerInterface interface { +// RunScan(o *flags.ScanOptions) error +// FetchScanResults() error +// CheckResults() error +// TagImage() error +// UploadResultsToS3() error +//} + +type BaseScanner struct { + ResultsFile string + MetaTag string + Vulns []trivy.ScanFailedReport } // fetchResultsFromServer pulls the results.json from the remote scanning server. -func fetchResultsFromServer(client util.SSHInterface, imgID string) error { +func fetchResultsFromServer(client interfaces.SSHInterface, imgID string) error { log.Println("Successfully connected to ssh server") log.Println("checking for scan completion") retries := 20 @@ -54,7 +49,7 @@ func fetchResultsFromServer(client util.SSHInterface, imgID string) error { return nil } -func hasScanCompleted(client util.SSHInterface) bool { +func hasScanCompleted(client interfaces.SSHInterface) bool { status, err := client.CopyFromRemoteServer("/tmp/finished", "/tmp/finished") if err != nil { return false @@ -72,24 +67,7 @@ func hasScanCompleted(client util.SSHInterface) bool { return true } -// removeScanningResources cleans up the server and keypair from Openstack to ensure nothing is left lying around. -func removeScanningResources(serverID, keyName string, fip *floatingips.FloatingIP, c *ostack.ComputeClient, n *ostack.NetworkClient) error { - err := c.RemoveServer(serverID) - if err != nil { - return err - } - err = c.RemoveKeypair(keyName) - if err != nil { - return err - } - err = n.RemoveFIP(fip.ID) - if err != nil { - return err - } - return nil -} - -// parsingVulnerabilities will read the results file and parse it into a more user friendly format. +// parsingVulnerabilities will read the results file and parse it into a more user-friendly format. func parsingVulnerabilities(resultsFile string) ([]trivy.ScanFailedReport, error) { log.Println("checking results") rf, err := os.ReadFile(resultsFile) @@ -124,220 +102,3 @@ func parsingVulnerabilities(resultsFile string) ([]trivy.ScanFailedReport, error } return vf, nil } - -func (s *ScannerClient) getKeypair(imgID string) error { - kp, err := s.computeClient.CreateKeypair(imgID) - if err != nil { - return err - } - s.keyPair = kp - return nil -} - -func (s *ScannerClient) getFip(fipNetworkName string) error { - - fip, err := s.networkClient.GetFloatingIP(fipNetworkName) - if err != nil { - e := s.computeClient.RemoveKeypair(s.keyPair.Name) - if e != nil { - return e - } - return err - } - s.fip = fip - return nil -} - -func (s *ScannerClient) buildServer(flavor, networkID, imgID string, attachConfigDrive bool, userData []byte) error { - - server, err := s.computeClient.CreateServer(s.keyPair.Name, flavor, networkID, &attachConfigDrive, userData, imgID) - if err != nil { - e := s.computeClient.RemoveKeypair(s.keyPair.Name) - if e != nil { - return e - } - e = s.networkClient.RemoveFIP(s.fip.ID) - if e != nil { - return e - } - return err - } - - state, err := s.computeClient.GetServerStatus(server.ID) - if err != nil { - e := removeScanningResources(server.ID, s.keyPair.Name, s.fip, s.computeClient, s.networkClient) - if e != nil { - return e - } - return err - } - checkLimit := 0 - for !state { - if checkLimit > 100 { - panic(errors.New("server failed to com online after 500 seconds")) - } - log.Println("server not active, waiting 5 seconds and then checking again") - time.Sleep(5 * time.Second) - state, err = s.computeClient.GetServerStatus(server.ID) - if err != nil { - e := removeScanningResources(server.ID, s.keyPair.Name, s.fip, s.computeClient, s.networkClient) - if e != nil { - return e - } - return err - } - checkLimit++ - } - - err = s.computeClient.AttachIP(server.ID, s.fip.FloatingIP) - if err != nil { - e := removeScanningResources(server.ID, s.keyPair.Name, s.fip, s.computeClient, s.networkClient) - if e != nil { - return e - } - return err - } - - s.server = server - - return nil -} - -func NewScanner(c *ostack.ComputeClient, i *ostack.ImageClient, n *ostack.NetworkClient, s3Conn *s3.S3) *ScannerClient { - return &ScannerClient{ - computeClient: c, - imageClient: i, - networkClient: n, - s3Credentials: s3Conn, - } -} - -func (s *ScannerClient) RunScan(o *flags.ScanOptions, severity trivy.Severity, img *images.Image) error { - s.trivyOptions = trivy.New(o.TrivyignorePath, o.TrivyignoreFilename, o.TrivyignoreList, severity) - err := s.getKeypair(img.ID) - if err != nil { - return err - } - err = s.getFip(o.FloatingIPNetworkName) - if err != nil { - return err - } - - userData, err := s.trivyOptions.GenerateTrivyCommand(s.s3Credentials) - if err != nil { - return err - } - - err = s.buildServer(o.FlavorName, o.NetworkID, img.ID, o.AttachConfigDrive, userData) - if err != nil { - return err - } - return nil -} - -func (s *ScannerClient) FetchScanResults(imgID string) error { - //TODO: We need to capture-ctl c and cleanup resources if it's hit. - client, err := sshconnect.NewSSHClient(s.keyPair, s.fip.FloatingIP) - if err != nil { - return err - } - - err = fetchResultsFromServer(client, imgID) - if err != nil { - e := removeScanningResources(s.server.ID, s.keyPair.Name, s.fip, s.computeClient, s.networkClient) - if e != nil { - return e - } - return err - } - - //Close SSH & SFTP connection - err = client.SFTPClose() - if err != nil { - return err - } - err = client.SSHClose() - if err != nil { - return err - } - - // Cleanup the scanning resources - e := removeScanningResources(s.server.ID, s.keyPair.Name, s.fip, s.computeClient, s.networkClient) - if e != nil { - return e - } - return nil -} - -//TODO split this out - it's horrid - -// CheckResultsTagImageAndUploadToS3 checks the results file for vulns and parses it into a more friendly format. Then it tags the image with the passed or failed property, and then it uploads to S3 - horrible. -func (s *ScannerClient) CheckResultsTagImageAndUploadToS3(img *images.Image, autoDelete, skipCVECheck bool) error { - // Default to replace unless the field isn't found below - operation := images.ReplaceOp - - field, err := data.GetNestedField(img.Properties, "image", "metadata", "security_scan") - if err != nil || field == nil { - operation = images.AddOp - } - metaValue := "passed" - resultsFile := fmt.Sprintf("/tmp/%s.json", img.ID) - - vulns, err := parsingVulnerabilities(resultsFile) - if err != nil { - return err - } - if len(vulns) != 0 { - if autoDelete { - err = s.imageClient.RemoveImage(img.ID) - if err != nil { - return err - } - } - var j []byte - j, err = json.Marshal(vulns) - if err != nil { - return errors.New("couldn't marshall vulnerability trivyIgnoreFile: " + err.Error()) - } - - // write the vulnerabilities into the results file - err = os.WriteFile(resultsFile, j, os.FileMode(0644)) - if err != nil { - return errors.New("couldn't write vulnerability trivyIgnoreFile to file: " + err.Error()) - } - - metaValue = "failed" - } else { - err = os.WriteFile(resultsFile, []byte("{}"), os.FileMode(0644)) - if err != nil { - return errors.New("couldn't write vulnerability trivyIgnoreFile to file: " + err.Error()) - } - } - if !autoDelete { - _, err = s.imageClient.ModifyImageMetadata(img.ID, "security_scan", metaValue, operation) - if err != nil { - return err - } - } - - //Upload results to S3 - f, err := os.Open(resultsFile) - if err != nil { - return err - } - defer f.Close() - - err = s.s3Credentials.Put("text/plain", fmt.Sprintf("scans/%s/%s", img.ID, "results.json"), f) - if err != nil { - return err - } - - if !skipCVECheck { - errMsg := "vulnerabilities detected above threshold. Please see the possible fixes located at '/tmp/results.json' for further information on this" - if autoDelete { - errMsg = fmt.Sprintf("%s - %s", errMsg, ". The image has been removed from the infra.") - } - return fmt.Errorf(errMsg) - } - return nil -} diff --git a/pkg/providers/scanner/openstack.go b/pkg/providers/scanner/openstack.go new file mode 100644 index 0000000..b8d3ce8 --- /dev/null +++ b/pkg/providers/scanner/openstack.go @@ -0,0 +1,252 @@ +package scanner + +import ( + "encoding/json" + "errors" + "fmt" + sshconnect "github.com/drewbernetes/baski/pkg/remote" + "github.com/drewbernetes/baski/pkg/trivy" + "github.com/drewbernetes/baski/pkg/util/flags" + "github.com/drewbernetes/baski/pkg/util/interfaces" + simple_s3 "github.com/drewbernetes/simple-s3" + "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/keypairs" + "github.com/gophercloud/gophercloud/openstack/compute/v2/servers" + "github.com/gophercloud/gophercloud/openstack/imageservice/v2/images" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips" + "log" + "os" + "time" +) + +type OpenStackScannerClient struct { + BaseScanner + Img *images.Image + + computeClient interfaces.OpenStackComputeClient + imageClient interfaces.OpenStackImageClient + networkClient interfaces.OpenStackNetworkClient + keyPair *keypairs.KeyPair + fip *floatingips.FloatingIP + s3Credentials *simple_s3.S3 + server *servers.Server + severity trivy.Severity +} + +// NewOpenStackScanner returns new scanner client. +func NewOpenStackScanner(c interfaces.OpenStackComputeClient, i interfaces.OpenStackImageClient, n interfaces.OpenStackNetworkClient, s3Conn *simple_s3.S3, severity trivy.Severity, img *images.Image) *OpenStackScannerClient { + return &OpenStackScannerClient{ + computeClient: c, + imageClient: i, + networkClient: n, + s3Credentials: s3Conn, + severity: severity, + Img: img, + } +} + +// RunScan builds the server for scanning and starts the scan +func (s *OpenStackScannerClient) RunScan(o *flags.ScanOptions) error { + trivyOptions := trivy.New(o.TrivyignorePath, o.TrivyignoreFilename, o.TrivyignoreList, s.severity) + err := s.getKeypair(s.Img.ID) + if err != nil { + return err + } + err = s.getFip(o.OpenStackFlags.FloatingIPNetworkName) + if err != nil { + return err + } + + userData, err := trivyOptions.GenerateTrivyCommand(s.s3Credentials) + if err != nil { + return err + } + + err = s.buildServer(o.OpenStackInstanceFlags.FlavorName, o.OpenStackInstanceFlags.NetworkID, s.Img.ID, o.OpenStackInstanceFlags.AttachConfigDrive, userData, []string{o.OpenStackFlags.SecurityGroup}) + if err != nil { + return err + } + return nil +} + +func (s *OpenStackScannerClient) FetchScanResults() error { + //TODO: We need to capture-ctl c and cleanup resources if it's hit. + client, err := sshconnect.NewSSHClient("ubuntu", s.keyPair.PrivateKey, s.fip.FloatingIP, "22") + if err != nil { + return err + } + + err = fetchResultsFromServer(client, s.Img.ID) + if err != nil { + e := removeOpenStackResources(s.server.ID, s.keyPair.Name, s.fip, s.computeClient, s.networkClient) + if e != nil { + return e + } + return err + } + + //Close SSH & SFTP connection + err = client.SFTPClose() + if err != nil { + return err + } + err = client.SSHClose() + if err != nil { + return err + } + + // Cleanup the scanning resources + e := removeOpenStackResources(s.server.ID, s.keyPair.Name, s.fip, s.computeClient, s.networkClient) + if e != nil { + return e + } + return nil +} + +// CheckResults checks the results file for vulnerabilitie-s and parses it into a more friendly format. +func (s *OpenStackScannerClient) CheckResults() error { + var err error + j := []byte("{}") + + s.MetaTag = "passed" + s.ResultsFile = fmt.Sprintf("/tmp/%s.json", s.Img.ID) + s.Vulns, err = parsingVulnerabilities(s.ResultsFile) + if err != nil { + return err + } + if len(s.Vulns) != 0 { + j, err = json.Marshal(s.Vulns) + if err != nil { + return errors.New("couldn't marshall vulnerability trivyIgnoreFile: " + err.Error()) + } + s.MetaTag = "failed" + } + + // write the vulnerabilities into the results file + err = os.WriteFile(s.ResultsFile, j, os.FileMode(0644)) + if err != nil { + return errors.New("couldn't write vulnerability trivyIgnoreFile to file: " + err.Error()) + } + + return nil +} + +// TagImage Tags the image with the passed or failed property. +func (s *OpenStackScannerClient) TagImage() error { + err := s.imageClient.TagImage(s.Img.Properties, s.Img.ID, s.MetaTag, "security_scan") + if err != nil { + return err + } + + return nil +} + +// UploadResultsToS3 uploads the scan results to S3. +func (s *OpenStackScannerClient) UploadResultsToS3() error { + //Upload results to S3 + f, err := os.Open(s.ResultsFile) + if err != nil { + return err + } + defer f.Close() + + err = s.s3Credentials.Put(fmt.Sprintf("scans/%s/%s", s.Img.ID, "results.json"), f) + if err != nil { + return err + } + + return nil +} + +func (s *OpenStackScannerClient) getKeypair(imgID string) error { + kp, err := s.computeClient.CreateKeypair(imgID) + if err != nil { + return err + } + s.keyPair = kp + return nil +} + +func (s *OpenStackScannerClient) getFip(fipNetworkName string) error { + fip, err := s.networkClient.GetFloatingIP(fipNetworkName) + if err != nil { + e := s.computeClient.RemoveKeypair(s.keyPair.Name) + if e != nil { + return e + } + return err + } + s.fip = fip + return nil +} + +// buildServer is responsible for building the server +func (s *OpenStackScannerClient) buildServer(flavor, networkID, imgID string, attachConfigDrive bool, userData []byte, securityGroups []string) error { + server, err := s.computeClient.CreateServer(s.keyPair.Name, flavor, networkID, &attachConfigDrive, userData, imgID, securityGroups) + if err != nil { + e := s.computeClient.RemoveKeypair(s.keyPair.Name) + if e != nil { + return e + } + e = s.networkClient.RemoveFIP(s.fip.ID) + if e != nil { + return e + } + return err + } + + state, err := s.computeClient.GetServerStatus(server.ID) + if err != nil { + e := removeOpenStackResources(server.ID, s.keyPair.Name, s.fip, s.computeClient, s.networkClient) + if e != nil { + return e + } + return err + } + checkLimit := 0 + for !state { + if checkLimit > 100 { + panic(errors.New("server failed to com online after 500 seconds")) + } + log.Println("server not active, waiting 5 seconds and then checking again") + time.Sleep(5 * time.Second) + state, err = s.computeClient.GetServerStatus(server.ID) + if err != nil { + e := removeOpenStackResources(server.ID, s.keyPair.Name, s.fip, s.computeClient, s.networkClient) + if e != nil { + return e + } + return err + } + checkLimit++ + } + + err = s.computeClient.AttachIP(server.ID, s.fip.FloatingIP) + if err != nil { + e := removeOpenStackResources(server.ID, s.keyPair.Name, s.fip, s.computeClient, s.networkClient) + if e != nil { + return e + } + return err + } + + s.server = server + + return nil +} + +// removeOpenStackResources cleans up the server and keypair from Openstack to ensure nothing is left lying around. +func removeOpenStackResources(serverID, keyName string, fip *floatingips.FloatingIP, c interfaces.OpenStackComputeClient, n interfaces.OpenStackNetworkClient) error { + err := c.RemoveServer(serverID) + if err != nil { + return err + } + err = c.RemoveKeypair(keyName) + if err != nil { + return err + } + err = n.RemoveFIP(fip.ID) + if err != nil { + return err + } + return nil +} diff --git a/pkg/providers/scanner/openstack_test.go b/pkg/providers/scanner/openstack_test.go new file mode 100644 index 0000000..1852257 --- /dev/null +++ b/pkg/providers/scanner/openstack_test.go @@ -0,0 +1,66 @@ +package scanner + +import ( + "testing" +) + +func TestNewOpenStackScanner(t *testing.T) { + //TODO a test needs writing for this +} + +func TestRunScan(t *testing.T) { + //c := gomock.NewController(t) + //defer c.Finish() + // + //m := mock.NewMockOpenStackScannerInterface(c) + //m.EXPECT().RunScan(&flags.ScanOptions{ + // OpenStackFlags: flags.OpenStackFlags{ + // OpenStackCoreFlags: flags.OpenStackCoreFlags{ + // CloudsPath: "", + // CloudName: "", + // }, + // OpenStackInstanceFlags: flags.OpenStackInstanceFlags{ + // AttachConfigDrive: false, + // NetworkID: "", + // FlavorName: "", + // }, + // SourceImageID: "", + // SSHPrivateKeyFile: "", + // SSHKeypairName: "", + // UseFloatingIP: false, + // FloatingIPNetworkName: "", + // SecurityGroup: "", + // ImageVisibility: "", + // ImageDiskFormat: "", + // UseBlockStorageVolume: "", + // VolumeType: "", + // VolumeSize: 0, + // }, + // ScanSingleOptions: flags.ScanSingleOptions{}, + // ScanMultipleOptions: flags.ScanMultipleOptions{}, + // AutoDeleteImage: false, + // SkipCVECheck: false, + // MaxSeverityScore: 0, + // MaxSeverityType: "", + // ScanBucket: "", + // TrivyignorePath: "", + // TrivyignoreFilename: "", + // TrivyignoreList: nil, + //}) +} + +func TestFetchScanResults(t *testing.T) { + +} + +func TestCheckResults(t *testing.T) { + +} + +func TestTagImage(t *testing.T) { + +} + +func TestUploadResultsToS3(t *testing.T) { + +} diff --git a/pkg/provisoner/kubevirt.go b/pkg/provisoner/kubevirt.go new file mode 100644 index 0000000..fb25295 --- /dev/null +++ b/pkg/provisoner/kubevirt.go @@ -0,0 +1,271 @@ +package provisoner + +import ( + "context" + "fmt" + "github.com/drewbernetes/baski/pkg/k8s" + "github.com/drewbernetes/baski/pkg/providers/packer" + "github.com/drewbernetes/baski/pkg/util/flags" + simple_s3 "github.com/drewbernetes/simple-s3" + v1 "k8s.io/api/core/v1" + errorsv1 "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + dv_client "kubevirt.io/client-go/generated/containerized-data-importer/clientset/versioned" + "kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1" + "log" + "os" +) + +// KubeVirtBuildProvisioner contains the options for the build. +type KubeVirtBuildProvisioner struct { + Opts *flags.BuildOptions +} + +// newKubeVirtBuilder returns a new KubeVirtBuildProvisioner +func newKubeVirtBuilder(o *flags.BuildOptions) *KubeVirtBuildProvisioner { + p := &KubeVirtBuildProvisioner{ + Opts: o, + } + + return p +} + +// Init currently has no action +func (p *KubeVirtBuildProvisioner) Init() error { + return nil +} + +// GeneratePackerConfig generates a packer vars file for KubeVirt builds. +func (p *KubeVirtBuildProvisioner) GeneratePackerConfig() (*packer.GlobalBuildConfig, error) { + o := p.Opts + b, imgName, err := packer.NewCoreBuildconfig(o) + if err != nil { + return nil, err + } + + // Set the image name here as it's + o.KubeVirtFlags.ImageName = imgName + + b.KubeVirtBuildConfig = packer.KubeVirtBuildConfig{ + QemuBinary: o.KubeVirtFlags.QemuBinary, + DiskSize: o.KubeVirtFlags.DiskSize, + OutputDirectory: fmt.Sprintf("%s/%s", o.KubeVirtFlags.OutputDirectory, o.KubeVirtFlags.ImageName), + } + + b.Metadata = generateBuilderMetadata(o) + + return b, nil +} + +// UpdatePackerBuilders currently has no action +func (p *KubeVirtBuildProvisioner) UpdatePackerBuilders(metadata map[string]string, data []byte) []byte { + return nil +} + +// PostBuildAction will upload the image to S3 if the option is enabled. It will then deploy a DataVolume to the cluster referencing the S3 endpoint. +// +// If the S3 endpoint is not enabled then it will just print out the location of the file. +func (p *KubeVirtBuildProvisioner) PostBuildAction() error { + ctx := context.Background() + s3ImgPath := fmt.Sprintf("images/%s.qcow2", p.Opts.KubeVirtFlags.ImageName) + resultImage := fmt.Sprintf("%s/%s/%s", p.Opts.KubeVirtFlags.OutputDirectory, p.Opts.KubeVirtFlags.ImageName, fmt.Sprintf("%s-kube-v%s", p.Opts.BuildOS, p.Opts.KubeVersion)) + + if p.Opts.KubeVirtFlags.StoreInS3 { + ////If S3 enabled, upload to S3 + log.Printf("uploading the image: %s to %s in S3... this may take a few minutes\n", p.Opts.KubeVirtFlags.ImageName, s3ImgPath) + s, err := simple_s3.New(p.Opts.S3Flags.Endpoint, p.Opts.S3Flags.AccessKey, p.Opts.S3Flags.SecretKey, p.Opts.KubeVirtFlags.ImageBucket, p.Opts.S3Flags.Region) + if err != nil { + return err + } + + f, err := os.Open(resultImage) + if err != nil { + return err + } + defer f.Close() + + err = s.Put(s3ImgPath, f) + if err != nil { + return err + } + + // Create DV + client, err := k8s.NewClient(p.Opts.KubernetesClusterFlags.KubeconfigPath) + if err != nil { + return err + } + + log.Printf("checking for Namespace: %s - will create if it doesn't exist\n", p.Opts.KubeVirtFlags.ImageNamespace) + ns, err := createOrGetNamespace(ctx, client.Client, p.Opts.KubeVirtFlags.ImageNamespace) + if err != nil { + return err + } + + log.Printf("checking for Secret: %s - will create if it doesn't exist\n", "baski-image-s3-credentials") + secret, err := createOrGetS3Secret(ctx, client.Client, ns.ObjectMeta.Name, p.Opts.S3Flags) + if err != nil { + return err + } + + log.Printf("Creating DataVolume: %s\n", p.Opts.KubeVirtFlags.ImageName) + err = createDataVolume(ctx, client.KubeVirt, ns.ObjectMeta.Name, secret.ObjectMeta.Name, s3ImgPath, p.Opts) + if err != nil { + return err + } + + } else { + // TODO: Consider options to upload via the CDI proxy. + // To mimic the `virtlctl image-upload` approach would require it all recoding here which seems fruitless. + // It would be easier to just run virtctl as a direct exec than copy-pasta the code over that handles uploading an image to a PVC/DV. + + // If not tell user where file is + log.Printf("the image has been built and exists in %s", resultImage) + } + + return nil +} + +// createOrGetNamespace fetch a Namespace or create it if it doesn't exist +func createOrGetNamespace(ctx context.Context, client *kubernetes.Clientset, namespace string) (*v1.Namespace, error) { + + ns, err := client.CoreV1().Namespaces().Get(ctx, namespace, metav1.GetOptions{}) + if err != nil { + if errorsv1.IsNotFound(err) { + ns, err = client.CoreV1().Namespaces().Create(ctx, &v1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: namespace, + }, + }, metav1.CreateOptions{}) + if err != nil { + return nil, err + } + } else { + return nil, err + } + } + return ns, nil +} + +// createOrGetS3Secret fetch the image-s3 credentials Secret or create it if it doesn't exist +func createOrGetS3Secret(ctx context.Context, client *kubernetes.Clientset, namespace string, s3Opts flags.S3Flags) (*v1.Secret, error) { + secretName := "baski-image-s3-credentials" + + data := map[string][]byte{ + "accessKeyId": []byte(s3Opts.AccessKey), + "secretKey": []byte(s3Opts.SecretKey), + } + + secret, err := client.CoreV1().Secrets(namespace).Get(ctx, secretName, metav1.GetOptions{}) + if err != nil { + if errorsv1.IsNotFound(err) { + secret, err = client.CoreV1().Secrets(namespace).Create(ctx, &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: secretName, + Namespace: namespace, + }, + Data: data, + Type: "Opaque", + }, metav1.CreateOptions{}) + if err != nil { + return nil, err + } + } else { + return nil, err + } + } + return secret, nil +} + +// createDataVolume the DataVolume to enable the image to be pulled from S3 and stored as a PVC +func createDataVolume(ctx context.Context, client *dv_client.Clientset, namespace, secret, s3ImgPath string, opts *flags.BuildOptions) error { + var err error + + dv := &v1beta1.DataVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: opts.KubeVirtFlags.ImageName, + Namespace: namespace, + Labels: map[string]string{ + "builder": "baski", + }, + }, + + Spec: v1beta1.DataVolumeSpec{ + Source: &v1beta1.DataVolumeSource{ + S3: &v1beta1.DataVolumeSourceS3{ + URL: fmt.Sprintf("%s/%s", opts.S3Flags.Endpoint, s3ImgPath), + SecretRef: secret, + }, + }, + PVC: &v1.PersistentVolumeClaimSpec{ + AccessModes: []v1.PersistentVolumeAccessMode{ + v1.ReadWriteOnce, + }, + Resources: v1.VolumeResourceRequirements{ + Requests: map[v1.ResourceName]resource.Quantity{ + v1.ResourceStorage: resource.MustParse("10Gi"), + }, + }, + }, + }, + } + + _, err = client.CdiV1beta1().DataVolumes(namespace).Create(ctx, dv, metav1.CreateOptions{}) + if err != nil { + return err + } + + return nil +} + +// KubeVirtScanProvisioner +type KubeVirtScanProvisioner struct { + Opts *flags.ScanOptions +} + +// newKubeVirtScanner +func newKubeVirtScanner(o *flags.ScanOptions) *KubeVirtScanProvisioner { + p := &KubeVirtScanProvisioner{ + Opts: o, + } + + return p +} + +// Prepare +func (s *KubeVirtScanProvisioner) Prepare() error { + + return nil +} + +// ScanImages +func (s *KubeVirtScanProvisioner) ScanImages() error { + return nil +} + +// KubeVirtSignProvisioner +type KubeVirtSignProvisioner struct { + Opts *flags.SignOptions +} + +// newKubeVirtSigner +func newKubeVirtSigner(o *flags.SignOptions) *KubeVirtSignProvisioner { + p := &KubeVirtSignProvisioner{ + Opts: o, + } + + return p +} + +// SignImage +func (s *KubeVirtSignProvisioner) SignImage(digest string) error { + + return nil +} + +// ValidateImage +func (s *KubeVirtSignProvisioner) ValidateImage(key []byte) error { + + return nil +} diff --git a/pkg/provisoner/openstack.go b/pkg/provisoner/openstack.go new file mode 100644 index 0000000..56c660d --- /dev/null +++ b/pkg/provisoner/openstack.go @@ -0,0 +1,391 @@ +package provisoner + +import ( + "bufio" + "encoding/json" + "fmt" + ostack "github.com/drewbernetes/baski/pkg/providers/openstack" + "github.com/drewbernetes/baski/pkg/providers/packer" + "github.com/drewbernetes/baski/pkg/providers/scanner" + "github.com/drewbernetes/baski/pkg/trivy" + "github.com/drewbernetes/baski/pkg/util/data" + "github.com/drewbernetes/baski/pkg/util/flags" + "github.com/drewbernetes/baski/pkg/util/interfaces" + "github.com/drewbernetes/baski/pkg/util/sign" + simple_s3 "github.com/drewbernetes/simple-s3" + "github.com/gophercloud/gophercloud/openstack/imageservice/v2/images" + "log" + "os" + "regexp" + "strconv" + "strings" + "sync" +) + +// OpenStackBuildProvisioner contains the options for the build. +type OpenStackBuildProvisioner struct { + Opts *flags.BuildOptions +} + +// newOpenStackBuilder returns a new OpenStackBuildProvisioner. +func newOpenStackBuilder(o *flags.BuildOptions) *OpenStackBuildProvisioner { + p := &OpenStackBuildProvisioner{ + Opts: o, + } + + return p +} + +// Init will set an ENV VAR so that the OpenStack builder knows which cloud to use. +func (p *OpenStackBuildProvisioner) Init() error { + err := os.Setenv("OS_CLOUD", p.Opts.OpenStackFlags.CloudName) + if err != nil { + return err + } + return nil +} + +// GeneratePackerConfig generates a packer vars file for OpenStack builds. +func (p *OpenStackBuildProvisioner) GeneratePackerConfig() (*packer.GlobalBuildConfig, error) { + o := p.Opts + b, imgName, err := packer.NewCoreBuildconfig(o) + if err != nil { + return nil, err + } + + b.OpenStackBuildconfig = packer.OpenStackBuildconfig{ + AttachConfigDrive: strconv.FormatBool(o.OpenStackFlags.AttachConfigDrive), + Flavor: o.OpenStackFlags.FlavorName, + FloatingIpNetwork: o.OpenStackFlags.FloatingIPNetworkName, + ImageDiskFormat: o.OpenStackFlags.ImageDiskFormat, + ImageVisibility: o.OpenStackFlags.ImageVisibility, + ImageName: imgName, + Networks: o.OpenStackFlags.NetworkID, + SecurityGroup: o.OpenStackFlags.SecurityGroup, + SourceImage: o.OpenStackFlags.SourceImageID, + UseBlockStorageVolume: o.OpenStackFlags.UseBlockStorageVolume, + UseFloatingIp: strconv.FormatBool(o.OpenStackFlags.UseFloatingIP), + VolumeType: o.OpenStackFlags.VolumeType, + VolumeSize: strconv.Itoa(o.OpenStackFlags.VolumeSize), + } + + if len(o.OpenStackFlags.SSHPrivateKeyFile) > 0 && len(o.OpenStackFlags.SSHKeypairName) > 0 { + b.OpenStackBuildconfig.SSHPrivateKeyFile = o.OpenStackFlags.SSHPrivateKeyFile + b.OpenStackBuildconfig.SSHKeypairName = o.OpenStackFlags.SSHKeypairName + } + + b.Metadata = generateBuilderMetadata(o) + + return b, nil +} + +// UpdatePackerBuilders will update the builders field with the metadata values as required. This is done this way as passing it in via Packer vars is prone to error or just complete failures. +func (p *OpenStackBuildProvisioner) UpdatePackerBuilders(metadata map[string]string, data []byte) []byte { + jsonStruct := struct { + Builders []map[string]interface{} `json:"builders"` + PostProcessors []map[string]interface{} `json:"post-processors"` + Provisioners []map[string]interface{} `json:"provisioners"` + Variables map[string]interface{} `json:"variables"` + }{} + + err := json.Unmarshal(data, &jsonStruct) + if err != nil { + log.Fatalln(err) + return nil + } + + jsonStruct.Builders[0]["metadata"] = metadata + + res, err := json.Marshal(jsonStruct) + if err != nil { + log.Fatalln(err) + return nil + } + + return res +} + +// PostBuildAction retrieves the image ID from the output and stores it into a file. +func (p *OpenStackBuildProvisioner) PostBuildAction() error { + + imgID, err := retrieveNewOpenStackImageID() + if err != nil { + return err + } + + err = saveImageIDToFile(imgID) + if err != nil { + return err + } + + return nil +} + +// retrieveNewOpenStackImageID identifies the new ImageID from the output text so that it can be used/retrieved later. +func retrieveNewOpenStackImageID() (string, error) { + var i string + + //TODO: If the output goes to stdOUT in buildImage, + // we need to figure out if we can pull this from the openstack instance instead. + f, err := os.Open("/tmp/out-build.txt") + if err != nil { + return "", err + } + defer f.Close() + + r := bufio.NewScanner(f) + re := regexp.MustCompile("An image was created: [0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}") + for r.Scan() { + m := re.MatchString(string(r.Bytes())) + if m { + //There is likely two outputs here due to how packer outputs, so we need to break on the first find. + i = strings.Split(r.Text(), ": ")[2] + break + } + } + + return i, nil +} + +// OpenStackScanProvisioner +type OpenStackScanProvisioner struct { + Opts *flags.ScanOptions + imageClient interfaces.OpenStackImageClient + computeClient interfaces.OpenStackComputeClient + networkClient interfaces.OpenStackNetworkClient + imageWildCard string + imageID string +} + +// newOpenStackScanner +func newOpenStackScanner(o *flags.ScanOptions) *OpenStackScanProvisioner { + p := &OpenStackScanProvisioner{ + Opts: o, + } + + return p +} + +// Prepare +func (s *OpenStackScanProvisioner) Prepare() error { + var err error + o := s.Opts + + o.OpenStackFlags.FlavorName = o.FlavorName + + cloudProvider := ostack.NewCloudsProvider(o.OpenStackFlags.CloudName) + + s.imageClient, err = ostack.NewImageClient(cloudProvider) + if err != nil { + return err + } + + s.computeClient, err = ostack.NewComputeClient(cloudProvider) + if err != nil { + return err + } + + s.networkClient, err = ostack.NewNetworkClient(cloudProvider) + if err != nil { + return err + } + + s.imageID = o.ScanSingleOptions.ImageID + s.imageWildCard = o.ScanMultipleOptions.ImageSearch + + return nil +} + +// ScanImages +func (s *OpenStackScanProvisioner) ScanImages() error { + var err error + o := s.Opts + + imgs := []images.Image{} + + // Parse the image ID or wildcard and load the images from OpenStack + if s.imageID != "" { + var img *images.Image + + img, err = s.imageClient.FetchImage(o.ScanSingleOptions.ImageID) + if err != nil { + return err + } + + imgs = append(imgs, *img) + } else if s.imageWildCard != "" { + imgs, err = s.imageClient.FetchAllImages(o.ImageSearch) + if err != nil { + return err + } + } else { + return fmt.Errorf("no image(s) provided") + } + + severity := trivy.Severity(strings.ToUpper(o.MaxSeverityType)) + + var s3Conn *simple_s3.S3 + + s3Conn, err = simple_s3.New(o.S3Flags.Endpoint, o.S3Flags.AccessKey, o.S3Flags.SecretKey, o.ScanBucket, o.S3Flags.Region) + if err != nil { + log.Println(err) + return err + } + + // Let's scan a bunch of images based on the concurrency + semaphore := make(chan struct{}, o.Concurrency) + var wg sync.WaitGroup + + for _, img := range imgs { + wg.Add(1) + semaphore <- struct{}{} + go func(image images.Image) { + defer func() { + <-semaphore // Release the slot in the semaphore + }() + + sc := scanner.NewOpenStackScanner(s.computeClient, s.imageClient, s.networkClient, s3Conn, severity, &image) + err = s.scanServer(sc, &wg) + if err != nil { + log.Println(err) + } + + }(img) + } + wg.Wait() + + close(semaphore) + + return nil +} + +// scanServer will scan, parse the results and upload them to S3. It's in its own function for the purpose of threading. +func (s *OpenStackScanProvisioner) scanServer(sc *scanner.OpenStackScannerClient, wg *sync.WaitGroup) error { + defer wg.Done() + o := s.Opts + + log.Printf("Processing Image with ID: %s\n", sc.Img.ID) + + // Run the scan. + err := sc.RunScan(o) + if err != nil { + return err + } + + // Fetch the results and write them to a file locally. + err = sc.FetchScanResults() + if err != nil { + return err + } + + // Read the local results file and parse them into a more consumable json format, then write out to file. + err = sc.CheckResults() + if err != nil { + return err + } + + // If the image is not set to auto delete, tag the image with the check result. + if !o.AutoDeleteImage { + err = sc.TagImage() + if err != nil { + return err + } + } else { + if len(sc.Vulns) != 0 { + // Remove the image if vulns are found and the flag/config item is set. + err = s.imageClient.RemoveImage(sc.Img.ID) + if err != nil { + return err + } + } + } + + // Upload the parsed results file to S3 + err = sc.UploadResultsToS3() + if err != nil { + return err + } + + log.Printf("Finished processing Image ID: %s\n", sc.Img.ID) + + // Check if the CVE checking is being skipped, if not then bail out here. + if !o.SkipCVECheck { + errMsg := "vulnerabilities detected above threshold. Please see the possible fixes located at '/tmp/results.json' for further information on this" + if o.AutoDeleteImage { + errMsg = fmt.Sprintf("%s - %s", errMsg, ". The image has been removed from the infra.") + } + return fmt.Errorf(errMsg) + } + return nil +} + +type OpenStackSignProvisioner struct { + Opts *flags.SignOptions +} + +// newOpenStackSigner +func newOpenStackSigner(o *flags.SignOptions) *OpenStackSignProvisioner { + p := &OpenStackSignProvisioner{ + Opts: o, + } + + return p +} + +// SignImage +func (s *OpenStackSignProvisioner) SignImage(digest string) error { + o := s.Opts + cloudProvider := ostack.NewCloudsProvider(o.OpenStackCoreFlags.CloudName) + + i, err := ostack.NewImageClient(cloudProvider) + if err != nil { + return err + } + + img, err := i.FetchImage(o.ImageID) + if err != nil { + return err + } + + err = i.TagImage(img.Properties, o.ImageID, digest, "digest") + if err != nil { + return err + } + + return nil +} + +// ValidateImage +func (s *OpenStackSignProvisioner) ValidateImage(key []byte) error { + o := s.Opts + cloudProvider := ostack.NewCloudsProvider(o.OpenStackCoreFlags.CloudName) + + i, err := ostack.NewImageClient(cloudProvider) + if err != nil { + return err + } + + img, err := i.FetchImage(o.ImageID) + if err != nil { + return err + } + + field, err := data.GetNestedField(img.Properties, "digest") + if err != nil { + return err + } + if field == nil { + return fmt.Errorf("the digest field was empty") + } + + digest := field.(string) + + valid, err := sign.Validate(o.ImageID, key, digest) + if err != nil { + return err + } + + log.Printf("The validation result was: %t", valid) + + return nil +} diff --git a/pkg/provisoner/openstack_test.go b/pkg/provisoner/openstack_test.go new file mode 100644 index 0000000..173f2e3 --- /dev/null +++ b/pkg/provisoner/openstack_test.go @@ -0,0 +1,137 @@ +package provisoner + +import ( + "os" + "testing" +) + +func TestInit(t *testing.T) { + // err := os.Setenv("OS_CLOUD", p.CloudName) + // if err != nil { + // return err + // } + // return nil +} + +// TestGeneratePackerConfig generates some glance metadata for the image. +func TestGeneratePackerConfig(t *testing.T) { + // th.SetupPersistentPortHTTP(t, th.Port) + // defer th.TeardownHTTP() + // + // tests := []struct { + // name string + // options *flags.BuildOptions + // expected map[string]string + // }{ + // { + // name: "Test with GPU", + // options: &flags.BuildOptions{ + // AddNvidiaSupport: true, + // NvidiaVersion: "1.2.3", + // BuildOS: "ubuntu", + // KubeVersion: "1.28", + // }, + // expected: map[string]string{ + // "os": "ubuntu", + // "k8s": "1.28", + // "gpu": "1.2.3", + // "date": "2006-01-02T15:04:05Z07:00", + // }, + // }, + // { + // name: "Test without GPU", + // options: &flags.BuildOptions{ + // AddNvidiaSupport: false, + // BuildOS: "ubuntu", + // KubeVersion: "1.28", + // }, + // expected: map[string]string{ + // "os": "ubuntu", + // "k8s": "1.28", + // "gpu": "no_gpu", + // "date": "2006-01-02T15:04:05Z07:00", + // }, + // }, + // } + // for _, tc := range tests { + // t.Run(tc.name, func(t *testing.T) { + // meta := GenerateBuilderMetadata(tc.options) + // //We override the dat here as it's based off of time.Now() + // meta["date"] = "2006-01-02T15:04:05Z07:00" + // + // if !reflect.DeepEqual(meta, tc.expected) { + // t.Errorf("Expected %+v, got %+v", tc.expected, meta) + // } + // }) + // } + // +} + +// TestUpdatePackerBuilders generates some glance metadata for the image. +func TestUpdatePackerBuilders(t *testing.T) { + +} + +// TestPostBuildAction fetches the newly create image's ID from the out.txt file +// that is generated during the buildImage() run. +func TestPostBuildAction(t *testing.T) { + + tc := []struct { + Name string + Msg string + Expected string + }{ + { + Name: "Test valid ID", + Msg: `==> Wait completed after 10 minutes 45 seconds + +==> Builds finished. The artifacts of successful builds are: +--> openstack: An image was created: 42cb1fd0-61aa-4b76-a66d-d0c377cc9c22 +--> openstack: An image was created: 42cb1fd0-61aa-4b76-a66d-d0c377cc9c22`, + Expected: "42cb1fd0-61aa-4b76-a66d-d0c377cc9c22", + }, + { + Name: "Test invalid ID", + Msg: `Just some random text in a file. +This should not be parsed or return anything in any way. +But for good measure, here is an imageID: 42cb1fd0-61aa-4b76-a66d-d0c377cc9c22`, + Expected: "", + }, + } + + for _, test := range tc { + t.Run(test.Name, func(t *testing.T) { + filename := "/tmp/out-build.txt" + f, err := os.Create(filename) + if err != nil { + t.Error(err) + return + } + _, err = f.Write([]byte(test.Msg)) + if err != nil { + t.Error(err) + return + } + err = f.Close() + if err != nil { + t.Error(err) + return + } + + id, err := retrieveNewOpenStackImageID() + if err != nil { + t.Error(err) + return + } + + if id != test.Expected { + t.Errorf("got %s, expected %s", id, test.Expected) + } + + err = os.Remove(filename) + if err != nil { + t.Error(err) + } + }) + } +} diff --git a/pkg/provisoner/provisoners.go b/pkg/provisoner/provisoners.go new file mode 100644 index 0000000..13c027b --- /dev/null +++ b/pkg/provisoner/provisoners.go @@ -0,0 +1,102 @@ +package provisoner + +import ( + "github.com/drewbernetes/baski/pkg/providers/packer" + "github.com/drewbernetes/baski/pkg/util/flags" + "os" + "time" +) + +type BuilderProvisioner interface { + Init() error + GeneratePackerConfig() (*packer.GlobalBuildConfig, error) + UpdatePackerBuilders(metadata map[string]string, data []byte) []byte + PostBuildAction() error +} + +// NewBuilder returns a new provisioner based on the infra type that is used for building images. +func NewBuilder(o *flags.BuildOptions) BuilderProvisioner { + switch o.InfraType { + case "openstack": + return newOpenStackBuilder(o) + case "kubevirt": + return newKubeVirtBuilder(o) + + } + + return nil +} + +type ScannerProvisioner interface { + Prepare() error + ScanImages() error +} + +func NewScanner(o *flags.ScanOptions) ScannerProvisioner { + switch o.InfraType { + case "openstack": + return newOpenStackScanner(o) + case "kubevirt": + return newKubeVirtScanner(o) + } + return nil +} + +type SignerProvisioner interface { + SignImage(digest string) error + ValidateImage(key []byte) error +} + +func NewSigner(o *flags.SignOptions) SignerProvisioner { + switch o.InfraType { + case "openstack": + return newOpenStackSigner(o) + case "kubevirt": + return newKubeVirtSigner(o) + + } + + return nil +} + +// saveImageIDToFile exports the image ID to a file so that it can be read later by the scan system. +func saveImageIDToFile(imgID string) error { + f, err := os.Create("/tmp/imgid.out") + if err != nil { + return err + } + defer f.Close() + _, err = f.Write([]byte(imgID)) + if err != nil { + return err + } + + return nil +} + +// generateBuilderMetadata generates some glance metadata for the image. +func generateBuilderMetadata(o *flags.BuildOptions) map[string]string { + gpu := "no_gpu" + if o.AddGpuSupport { + if o.GpuVendor == "nvidia" { + gpu = o.NvidiaVersion + } else if o.GpuVendor == "amd" { + gpu = o.AMDVersion + } + } + + meta := map[string]string{ + "os": o.BuildOS, + "k8s": o.KubeVersion, + "gpu": gpu, + "gpu_vendor": o.GpuVendor, + "date": time.Now().Format(time.RFC3339), + } + + if len(o.AdditionalMetadata) > 0 { + for k, v := range o.AdditionalMetadata { + meta[k] = v + } + } + return meta +} diff --git a/pkg/remote/sshconnect.go b/pkg/remote/sshconnect.go index 7870f21..76143a4 100644 --- a/pkg/remote/sshconnect.go +++ b/pkg/remote/sshconnect.go @@ -19,7 +19,7 @@ package remote import ( "encoding/base64" "fmt" - "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/keypairs" + "github.com/drewbernetes/baski/pkg/util/interfaces" "github.com/pkg/sftp" "golang.org/x/crypto/ssh" "log" @@ -63,18 +63,18 @@ func trustedHostKeyCallback(key string) ssh.HostKeyCallback { // NewSSHClient creates a new ssh connection to a remote server. // It will attempt to connect up to 10 times with a 10-second gap to prevent a crash // should the first attempt fail while the server is booting. -func NewSSHClient(kp *keypairs.KeyPair, ip string) (*SSHClient, error) { +func NewSSHClient(user, privateKey, ip, port string) (interfaces.SSHInterface, error) { var hostKey string var client *ssh.Client // Create the Signer for this private key. - signer, err := ssh.ParsePrivateKey([]byte(kp.PrivateKey)) + signer, err := ssh.ParsePrivateKey([]byte(privateKey)) if err != nil { return nil, err } config := &ssh.ClientConfig{ - User: "ubuntu", + User: user, Auth: []ssh.AuthMethod{ ssh.PublicKeys(signer), }, @@ -84,7 +84,7 @@ func NewSSHClient(kp *keypairs.KeyPair, ip string) (*SSHClient, error) { log.Println("waiting for server to boot.") for i := 10; i > 0; i-- { // Connect to the remote server and perform the SSH handshake. - client, err = ssh.Dial("tcp", strings.Join([]string{ip, "22"}, ":"), config) + client, err = ssh.Dial("tcp", strings.Join([]string{ip, port}, ":"), config) if err != nil { if i > 0 { log.Printf("ssh unavailable - server is probably still booting. %d retires left\n", i) @@ -109,7 +109,6 @@ func NewSSHClient(kp *keypairs.KeyPair, ip string) (*SSHClient, error) { // CopyFromRemoteServer uses sftp to copy a file from a remotes server to a local directory. func (s *SSHClient) CopyFromRemoteServer(src, dst string) (*os.File, error) { - // Open the source file srcFile, err := s.SFTP.Open(src) if err != nil { diff --git a/pkg/remote/sshconnect_test.go b/pkg/remote/sshconnect_test.go index 6d6a51e..51ce1f8 100644 --- a/pkg/remote/sshconnect_test.go +++ b/pkg/remote/sshconnect_test.go @@ -19,70 +19,87 @@ package remote import ( "github.com/drewbernetes/baski/pkg/mock" "go.uber.org/mock/gomock" - "os" "testing" ) -func generateTestData(t *testing.T, from string) *os.File { - f, err := os.Create(from) - if err != nil { - t.Error(err) - return nil - } +//func generateTestData(t *testing.T, from string) *os.File { +// f, err := os.Create(from) +// if err != nil { +// t.Error(err) +// return nil +// } +// +// return f +//} - return f +func TestNewSSHClient(t *testing.T) { + //TODO a test needs writing for this - we could consider just opening an SSH connection to validate it works } -// TestCopyFromRemoteServer uses sftp to copy a file from a remotes server to a local directory. func TestCopyFromRemoteServer(t *testing.T) { - testFile := generateTestData(t, "/tmp/some-file.json") + //testFile := generateTestData(t, "/tmp/some-file.json") + // + //c := gomock.NewController(t) + //defer c.Finish() + // + //m := mock.NewMockSSHInterface(c) + //m.EXPECT().CopyFromRemoteServer("/tmp/some-file.json", "/tmp/another-file.json").Return(testFile, nil).AnyTimes() + // + //// Define test cases + //testCases := []struct { + // name string + // ssh *mock.MockSSHInterface + // from string + // to string + // filename string + //}{ + // { + // name: "Test case 1: Copy file from remote location", + // ssh: m, + // from: "/tmp/some-file.json", + // to: "/tmp/another-file.json", + // }, + //} + // + //// RunScan the test cases + //for _, tc := range testCases { + // t.Run(tc.name, func(t *testing.T) { + // result, err := tc.ssh.CopyFromRemoteServer(tc.from, tc.to) + // if err != nil { + // t.Error(err) + // return + // } + // + // if testFile != result { + // t.Errorf("Expected data %+v, got: %+v\n", testFile, result) + // } + // + // if err = tc.ssh.SSHClose(); err != nil { + // t.Error(err) + // return + // } + // + // if err = tc.ssh.SFTPClose(); err != nil { + // t.Error(err) + // return + // } + // }) + //} +} + +func TestSSHClose(t *testing.T) { c := gomock.NewController(t) defer c.Finish() m := mock.NewMockSSHInterface(c) - m.EXPECT().CopyFromRemoteServer("/tmp/some-file.json", "/tmp/another-file.json").Return(testFile, nil).AnyTimes() m.EXPECT().SSHClose().Return(nil).AnyTimes() - m.EXPECT().SFTPClose().Return(nil).AnyTimes() - - // Define test cases - testCases := []struct { - name string - ssh *mock.MockSSHInterface - from string - to string - filename string - }{ - { - name: "Test case 1: Copy file from remote location", - ssh: m, - from: "/tmp/some-file.json", - to: "/tmp/another-file.json", - }, - } - - // RunScan the test cases - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - result, err := tc.ssh.CopyFromRemoteServer(tc.from, tc.to) - if err != nil { - t.Error(err) - return - } - - if testFile != result { - t.Errorf("Expected data %+v, got: %+v\n", testFile, result) - } +} - if err = tc.ssh.SSHClose(); err != nil { - t.Error(err) - return - } +func TestSFTPClose(t *testing.T) { + c := gomock.NewController(t) + defer c.Finish() - if err = tc.ssh.SFTPClose(); err != nil { - t.Error(err) - return - } - }) - } + m := mock.NewMockSSHInterface(c) + m.EXPECT().SFTPClose().Return(nil).AnyTimes() } diff --git a/pkg/s3/client.go b/pkg/s3/client.go deleted file mode 100644 index 32d2891..0000000 --- a/pkg/s3/client.go +++ /dev/null @@ -1,114 +0,0 @@ -/* -Copyright 2024 Drewbernetes. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package s3 - -import ( - "context" - "github.com/aws/aws-sdk-go-v2/aws" - "github.com/aws/aws-sdk-go-v2/config" - "github.com/aws/aws-sdk-go-v2/credentials" - "github.com/aws/aws-sdk-go-v2/service/s3" - "io" -) - -type S3 struct { - Bucket string - Client *s3.Client -} - -func New(endpoint, accessKey, secretKey, bucket, region string) (*S3, error) { - const defaultRegion = "us-east-1" - r := defaultRegion - if region != "" { - r = region - } - - resolver := aws.EndpointResolverWithOptionsFunc(func(service, region string, options ...any) (aws.Endpoint, error) { - return aws.Endpoint{ - PartitionID: "aws", - URL: endpoint, - SigningRegion: r, - HostnameImmutable: true, - }, nil - }) - - ctx := context.Background() - - cfg, err := config.LoadDefaultConfig(ctx, - config.WithRegion(r), - config.WithEndpointResolverWithOptions(resolver), - config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(accessKey, secretKey, "")), - ) - if err != nil { - return nil, err - } - return &S3{ - Bucket: bucket, - Client: s3.NewFromConfig(cfg), - }, nil -} - -// Fetch Downloads a file from an S3 bucket and returns its contents as a byte array. -func (s *S3) Fetch(fileName string) ([]byte, error) { - params := &s3.GetObjectInput{ - Bucket: &s.Bucket, - Key: &fileName, - } - - obj, err := s.Client.GetObject(context.Background(), params) - if err != nil { - return nil, err - } - - return io.ReadAll(obj.Body) -} - -// Put Pushes a file to an S3 bucket. -func (s *S3) Put(contentType, key string, body io.ReadSeeker) error { - params := &s3.PutObjectInput{ - Bucket: &s.Bucket, - Key: &key, - ContentType: &contentType, - Body: body, - } - - _, err := s.Client.PutObject(context.Background(), params) - if err != nil { - return err - } - - return nil -} - -// List will list the contents of a bucket -func (s *S3) List() ([]string, error) { - - params := &s3.ListObjectsInput{ - Bucket: &s.Bucket, - } - obj, err := s.Client.ListObjects(context.Background(), params) - if err != nil { - return nil, err - } - - contents := []string{} - for _, v := range obj.Contents { - contents = append(contents, *v.Key) - } - - return contents, nil -} diff --git a/pkg/s3/client_test.go b/pkg/s3/client_test.go deleted file mode 100644 index 0ba4b91..0000000 --- a/pkg/s3/client_test.go +++ /dev/null @@ -1,80 +0,0 @@ -/* -Copyright 2024 Drewbernetes. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package s3 - -import ( - "fmt" - "github.com/drewbernetes/baski/pkg/mock" - "github.com/drewbernetes/baski/pkg/util" - "go.uber.org/mock/gomock" - "os" - "testing" -) - -func put(s util.S3Interface) error { - f := createFile() - t := s.Put("text/plain", "path/results.json", f) - removeFile(f) - return t -} - -func TestFetch(t *testing.T) { - c := gomock.NewController(t) - defer c.Finish() - m := mock.NewMockS3Interface(c) - - m.EXPECT().Fetch(gomock.Eq("trivyignore")).Return([]byte("some data"), nil) - if _, err := m.Fetch("trivyignore"); err != nil { - t.Error(err) - } -} - -func TestPut(t *testing.T) { - c := gomock.NewController(t) - defer c.Finish() - m := mock.NewMockS3Interface(c) - - f := createFile() - - m.EXPECT().Put(gomock.Eq("text/plain"), gomock.Eq("path/results.json"), gomock.Eq(f)).Return(nil) - if err := put(m); err != nil { - t.Error(err) - } - - removeFile(f) -} - -func removeFile(f *os.File) { - err := os.Remove(f.Name()) - if err != nil { - fmt.Println(err) - } -} - -func createFile() *os.File { - f, err := os.Create("/tmp/test.txt") - if err != nil { - fmt.Println(err) - } - _, err = f.Write([]byte("test")) - if err != nil { - fmt.Println(err) - } - defer f.Close() - - return f -} diff --git a/pkg/server/handler/handler.go b/pkg/server/handler/handler.go index 42b4191..8aabfe9 100644 --- a/pkg/server/handler/handler.go +++ b/pkg/server/handler/handler.go @@ -22,7 +22,7 @@ import ( ostack "github.com/drewbernetes/baski/pkg/providers/openstack" "github.com/drewbernetes/baski/pkg/server/generated" "github.com/drewbernetes/baski/pkg/server/server/util" - u "github.com/drewbernetes/baski/pkg/util" + "github.com/drewbernetes/baski/pkg/util/interfaces" "github.com/gophercloud/gophercloud/openstack/imageservice/v2/images" "log" "net/http" @@ -30,8 +30,8 @@ import ( ) type Handler struct { - baskiS3 u.S3Interface - dogkatS3 u.S3Interface + baskiS3 interfaces.S3Interface + dogkatS3 interfaces.S3Interface cloudName string } @@ -46,7 +46,7 @@ type ImageData struct { TestData generated.TestResult `json:"test_data"` } -func New(b, d u.S3Interface, cloudName string) *Handler { +func New(b, d interfaces.S3Interface, cloudName string) *Handler { h := &Handler{ baskiS3: b, dogkatS3: d, @@ -80,7 +80,7 @@ func (h *Handler) ApiV1GetScans(w http.ResponseWriter, r *http.Request) { // fetchScanData grabs the scan data from S3 and parses it into JSON func fetchScansList(h *Handler) ([]string, error) { - contents, err := h.baskiS3.List() + contents, err := h.baskiS3.List("scans") if err != nil { return nil, err } diff --git a/pkg/server/handler/handler_test.go b/pkg/server/handler/handler_test.go index 2430a44..dcfca01 100644 --- a/pkg/server/handler/handler_test.go +++ b/pkg/server/handler/handler_test.go @@ -19,8 +19,8 @@ package handler import ( "fmt" "github.com/drewbernetes/baski/pkg/mock" - "github.com/drewbernetes/baski/pkg/s3" th "github.com/drewbernetes/baski/testhelpers" + simple_s3 "github.com/drewbernetes/simple-s3" "go.uber.org/mock/gomock" "io" "log" @@ -36,12 +36,12 @@ func TestNew(t *testing.T) { secret := "def" bucket := "a_bucket" - b, err := s3.New(endpoint, access, secret, bucket, "") + b, err := simple_s3.New(endpoint, access, secret, bucket, "") if err != nil { log.Println(err) return } - d, err := s3.New(endpoint, access, secret, bucket, "") + d, err := simple_s3.New(endpoint, access, secret, bucket, "") if err != nil { log.Println(err) return diff --git a/pkg/server/server/server.go b/pkg/server/server/server.go index f3ad67d..807c9fd 100644 --- a/pkg/server/server/server.go +++ b/pkg/server/server/server.go @@ -18,9 +18,9 @@ package server import ( "fmt" - "github.com/drewbernetes/baski/pkg/s3" "github.com/drewbernetes/baski/pkg/server/generated" "github.com/drewbernetes/baski/pkg/server/handler" + simple_s3 "github.com/drewbernetes/simple-s3" "github.com/gorilla/mux" "net/http" ) @@ -58,15 +58,15 @@ func (s *Server) NewServer(dev bool) (*http.Server, error) { middleware = append(middleware, CORSAllowOriginAllMiddleware) } - baskiS3, err := s3.New(s.Options.Endpoint, s.Options.AccessKey, s.Options.SecretKey, s.Options.Bucket, "") + baskiS3, err := simple_s3.New(s.Options.Endpoint, s.Options.AccessKey, s.Options.SecretKey, s.Options.Bucket, "") if err != nil { return nil, err } - var dogKatS3 *s3.S3 + var dogKatS3 *simple_s3.S3 if s.Options.EnableDogKat { - dogKatS3, err = s3.New(s.Options.Endpoint, s.Options.AccessKey, s.Options.SecretKey, s.Options.DogKatBucket, "") + dogKatS3, err = simple_s3.New(s.Options.Endpoint, s.Options.AccessKey, s.Options.SecretKey, s.Options.DogKatBucket, "") if err != nil { return nil, err } diff --git a/pkg/system/make.go b/pkg/system/make.go index 18e9c8a..64b2c4a 100644 --- a/pkg/system/make.go +++ b/pkg/system/make.go @@ -28,7 +28,7 @@ import ( // generally speaking the os.Stdout will be used but the option is there to write to a file // in case parsing needs to happen after. func RunMake(makeArgs, path string, env []string, output io.Writer) error { - ctx, cancel := context.WithTimeout(context.Background(), 25*time.Minute) + ctx, cancel := context.WithTimeout(context.Background(), 60*time.Minute) defer cancel() cmd := exec.CommandContext(ctx, "make", makeArgs) diff --git a/pkg/trivy/config.go b/pkg/trivy/config.go index 458732c..e9eaee4 100644 --- a/pkg/trivy/config.go +++ b/pkg/trivy/config.go @@ -19,7 +19,7 @@ package trivy import ( "fmt" "github.com/drewbernetes/baski/pkg/constants" - "github.com/drewbernetes/baski/pkg/util" + "github.com/drewbernetes/baski/pkg/util/interfaces" "log" "strings" ) @@ -51,7 +51,7 @@ func (t *TrivyOptions) GetFilename() string { } // GenerateTrivyCommand Creates the user data that will be passed to the server being created so that a .trivyignore can be added and the scan can be run as per the users wishes. -func (t *TrivyOptions) GenerateTrivyCommand(s3 util.S3Interface) ([]byte, error) { +func (t *TrivyOptions) GenerateTrivyCommand(s3 interfaces.S3Interface) ([]byte, error) { trivyIgnoreData := generateTrivyFile(s3, t.GetFilename(), t.ignoreList) log.Println("generating userdata") @@ -100,7 +100,7 @@ echo done > /tmp/finished; } // generateTrivyFile generates the trivyignore file to be used during the scan. -func generateTrivyFile(s3 util.S3Interface, ignoreFileName string, ignoreList []string) []byte { +func generateTrivyFile(s3 interfaces.S3Interface, ignoreFileName string, ignoreList []string) []byte { var ignoreListData, trivyIgnoreFile []byte var err error diff --git a/pkg/util/data/image.go b/pkg/util/data/image.go deleted file mode 100644 index 906b6e0..0000000 --- a/pkg/util/data/image.go +++ /dev/null @@ -1,51 +0,0 @@ -/* -Copyright 2024 Drewbernetes. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package data - -import ( - "bufio" - "os" - "regexp" - "strings" -) - -// RetrieveNewImageID fetches the newly create image's ID from the /tmp/out-build.txt file -// that is generated during the buildImage() run. -func RetrieveNewImageID() (string, error) { - var i string - - //TODO: If the output goes to stdOUT in buildImage, - // we need to figure out if we can pull this from the openstack instance instead. - f, err := os.Open("/tmp/out-build.txt") - if err != nil { - return "", err - } - defer f.Close() - - r := bufio.NewScanner(f) - re := regexp.MustCompile("An image was created: [0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}") - for r.Scan() { - m := re.MatchString(string(r.Bytes())) - if m { - //There is likely two outputs here due to how packer outputs, so we need to break on the first find. - i = strings.Split(r.Text(), ": ")[2] - break - } - } - - return i, nil -} diff --git a/pkg/util/data/image_test.go b/pkg/util/data/image_test.go deleted file mode 100644 index 2abf4c8..0000000 --- a/pkg/util/data/image_test.go +++ /dev/null @@ -1,86 +0,0 @@ -/* -Copyright 2024 Drewbernetes. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package data - -import ( - "os" - "testing" -) - -// TestRetrieveNewImageID fetches the newly create image's ID from the out.txt file -// that is generated during the buildImage() run. -func TestRetrieveNewImageID(t *testing.T) { - - tc := []struct { - Name string - Msg string - Expected string - }{ - { - Name: "Test valid ID", - Msg: `==> Wait completed after 10 minutes 45 seconds - -==> Builds finished. The artifacts of successful builds are: ---> openstack: An image was created: 42cb1fd0-61aa-4b76-a66d-d0c377cc9c22 ---> openstack: An image was created: 42cb1fd0-61aa-4b76-a66d-d0c377cc9c22`, - Expected: "42cb1fd0-61aa-4b76-a66d-d0c377cc9c22", - }, - { - Name: "Test invalid ID", - Msg: `Just some random text in a file. -This should not be parsed or return anything in any way. -But for good measure, here is an imageID: 42cb1fd0-61aa-4b76-a66d-d0c377cc9c22`, - Expected: "", - }, - } - - for _, test := range tc { - t.Run(test.Name, func(t *testing.T) { - filename := "/tmp/out-build.txt" - f, err := os.Create(filename) - if err != nil { - t.Error(err) - return - } - _, err = f.Write([]byte(test.Msg)) - if err != nil { - t.Error(err) - return - } - err = f.Close() - if err != nil { - t.Error(err) - return - } - - id, err := RetrieveNewImageID() - if err != nil { - t.Error(err) - return - } - - if id != test.Expected { - t.Errorf("got %s, expected %s", id, test.Expected) - } - - err = os.Remove(filename) - if err != nil { - t.Error(err) - } - }) - } -} diff --git a/pkg/util/flags/base.go b/pkg/util/flags/base.go new file mode 100644 index 0000000..b382fb8 --- /dev/null +++ b/pkg/util/flags/base.go @@ -0,0 +1,19 @@ +package flags + +import ( + "fmt" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +type BaseOptions struct { + InfraType string +} + +func (o *BaseOptions) SetOptionsFromViper() { + o.InfraType = viper.GetString(fmt.Sprintf("%s.type", viperInfraPrefix)) +} + +func (o *BaseOptions) AddFlags(cmd *cobra.Command) { + StringVarWithViper(cmd, &o.InfraType, viperInfraPrefix, "type", "kubevirt", "Targets the settings to use in a config file if supplied or dictates which code runs for a build.") +} diff --git a/pkg/util/flags/build.go b/pkg/util/flags/build.go index 330b2db..c22f9eb 100644 --- a/pkg/util/flags/build.go +++ b/pkg/util/flags/build.go @@ -18,15 +18,17 @@ package flags import ( "fmt" - "strings" - "github.com/spf13/cobra" "github.com/spf13/viper" + "strings" ) type BuildOptions struct { - OpenStackFlags + BaseOptions + KubernetesClusterFlags S3Flags + OpenStackFlags + KubeVirtFlags Verbose bool BuildOS string @@ -41,9 +43,14 @@ type BuildOptions struct { KubeDebVersion string ExtraDebs string AdditionalImages []string + AdditionalMetadata map[string]string AddFalco bool AddTrivy bool - AddNvidiaSupport bool + AddGpuSupport bool + GpuVendor string + AMDVersion string + AMDDebVersion string + AMDUseCase string NvidiaVersion string NvidiaBucket string NvidiaInstallerLocation string @@ -52,9 +59,6 @@ type BuildOptions struct { } func (o *BuildOptions) SetOptionsFromViper() { - o.OpenStackFlags.SetOptionsFromViper() - o.S3Flags.SetOptionsFromViper() - // General Flags o.Verbose = viper.GetBool(fmt.Sprintf("%s.verbose", viperBuildPrefix)) o.BuildOS = viper.GetString(fmt.Sprintf("%s.build-os", viperBuildPrefix)) @@ -69,23 +73,34 @@ func (o *BuildOptions) SetOptionsFromViper() { o.KubeRpmVersion = viper.GetString(fmt.Sprintf("%s.kubernetes-rpm-version", viperBuildPrefix)) o.ExtraDebs = viper.GetString(fmt.Sprintf("%s.extra-debs", viperBuildPrefix)) o.AdditionalImages = viper.GetStringSlice(fmt.Sprintf("%s.additional-images", viperBuildPrefix)) + o.AdditionalMetadata = viper.GetStringMapString(fmt.Sprintf("%s.additional-metadata", viperBuildPrefix)) o.AddFalco = viper.GetBool(fmt.Sprintf("%s.add-falco", viperBuildPrefix)) o.AddTrivy = viper.GetBool(fmt.Sprintf("%s.add-trivy", viperBuildPrefix)) + // GPU + o.AddGpuSupport = viper.GetBool(fmt.Sprintf("%s.enable-gpu-support", viperGpuPrefix)) + o.GpuVendor = viper.GetString(fmt.Sprintf("%s.gpu-vendor", viperGpuPrefix)) + // AMD + o.AMDVersion = viper.GetString(fmt.Sprintf("%s.amd-driver-version", viperGpuPrefix)) + o.AMDDebVersion = viper.GetString(fmt.Sprintf("%s.amd-deb-version", viperGpuPrefix)) + o.AMDUseCase = viper.GetString(fmt.Sprintf("%s.amd-usecase", viperGpuPrefix)) // NVIDIA - o.AddNvidiaSupport = viper.GetBool(fmt.Sprintf("%s.enable-nvidia-support", viperNvidiaPrefix)) - o.NvidiaVersion = viper.GetString(fmt.Sprintf("%s.nvidia-driver-version", viperNvidiaPrefix)) - o.NvidiaBucket = viper.GetString(fmt.Sprintf("%s.nvidia-bucket", viperNvidiaPrefix)) - o.NvidiaInstallerLocation = viper.GetString(fmt.Sprintf("%s.nvidia-installer-location", viperNvidiaPrefix)) - o.NvidiaTOKLocation = viper.GetString(fmt.Sprintf("%s.nvidia-tok-location", viperNvidiaPrefix)) - o.NvidiaGriddFeatureType = viper.GetInt(fmt.Sprintf("%s.nvidia-gridd-feature-type", viperNvidiaPrefix)) + o.NvidiaVersion = viper.GetString(fmt.Sprintf("%s.nvidia-driver-version", viperGpuPrefix)) + o.NvidiaBucket = viper.GetString(fmt.Sprintf("%s.nvidia-bucket", viperGpuPrefix)) + o.NvidiaInstallerLocation = viper.GetString(fmt.Sprintf("%s.nvidia-installer-location", viperGpuPrefix)) + o.NvidiaTOKLocation = viper.GetString(fmt.Sprintf("%s.nvidia-tok-location", viperGpuPrefix)) + o.NvidiaGriddFeatureType = viper.GetInt(fmt.Sprintf("%s.nvidia-gridd-feature-type", viperGpuPrefix)) + o.BaseOptions.SetOptionsFromViper() + o.KubernetesClusterFlags.SetOptionsFromViper() + o.S3Flags.SetOptionsFromViper() + o.OpenStackFlags.SetOptionsFromViper() + o.KubeVirtFlags.SetOptionsFromViper() } func (o *BuildOptions) AddFlags(cmd *cobra.Command, imageBuilderRepo string) { - o.OpenStackFlags.AddFlags(cmd, viperOpenStackPrefix) - o.S3Flags.AddFlags(cmd) // Build flags + BoolVarWithViper(cmd, &o.Verbose, viperBuildPrefix, "verbose", false, "Enable verbose output to see the information from packer. Not turning this on will mean the process appears to hang while the image build happens") StringVarWithViper(cmd, &o.BuildOS, viperBuildPrefix, "build-os", "ubuntu-2204", "This is the target os to build. Valid values are currently: ubuntu-2004 and ubuntu-2204") StringVarWithViper(cmd, &o.ImagePrefix, viperBuildPrefix, "image-prefix", "kube", "This will prefix the image with the value provided. Defaults to 'kube' producing an image name of kube-yymmdd-xxxxxxxx") StringVarWithViper(cmd, &o.ImageRepo, viperBuildPrefix, "image-repo", strings.Join([]string{imageBuilderRepo, "git"}, "."), "The imageRepo from which the image builder should be deployed") @@ -95,18 +110,30 @@ func (o *BuildOptions) AddFlags(cmd *cobra.Command, imageBuilderRepo string) { StringVarWithViper(cmd, &o.KubeVersion, viperBuildPrefix, "kubernetes-version", "1.25.3", "The Kubernetes version to add to the built image") StringVarWithViper(cmd, &o.ExtraDebs, viperBuildPrefix, "extra-debs", "", "A space-seperated list of any extra (Debian / Ubuntu) packages that should be installed") StringSliceVarWithViper(cmd, &o.AdditionalImages, viperBuildPrefix, "additional-images", nil, "Add any additional container images which should be baked into the image") + StringMapVarWithViper(cmd, &o.AdditionalMetadata, viperBuildPrefix, "additional-metadata", nil, "Add any additional metadata to tag the image with.") BoolVarWithViper(cmd, &o.AddFalco, viperBuildPrefix, "add-falco", false, "If enabled, will install Falco onto the image") BoolVarWithViper(cmd, &o.AddTrivy, viperBuildPrefix, "add-trivy", false, "If enabled, will install Trivy onto the image") - BoolVarWithViper(cmd, &o.Verbose, viperBuildPrefix, "verbose", false, "Enable verbose output to see the information from packer. Not turning this on will mean the process appears to hang while the image build happens") + // GPU + BoolVarWithViper(cmd, &o.AddGpuSupport, viperGpuPrefix, "enable-gpu-support", false, "This will configure GPU support in the image") + StringVarWithViper(cmd, &o.GpuVendor, viperGpuPrefix, "gpu-vendor", "", "The architecture of the GPU (currently supported: nvidia, amd)") + // AMD + StringVarWithViper(cmd, &o.AMDVersion, viperGpuPrefix, "amd-driver-version", "6.0.2", "The AMD driver version") + StringVarWithViper(cmd, &o.AMDDebVersion, viperGpuPrefix, "amd-deb-version", "6.0.60002-1", "The AMD deb version") + StringVarWithViper(cmd, &o.AMDUseCase, viperGpuPrefix, "amd-usecase", "dkms", "A comma-dliminated string of usecases for the AMDGPU installer") // NVIDIA flags - BoolVarWithViper(cmd, &o.AddNvidiaSupport, viperNvidiaPrefix, "enable-nvidia-support", false, "This will configure NVIDIA support in the image") - StringVarWithViper(cmd, &o.NvidiaVersion, viperNvidiaPrefix, "nvidia-driver-version", "525.85.05", "The NVIDIA driver version") - StringVarWithViper(cmd, &o.NvidiaBucket, viperNvidiaPrefix, "nvidia-bucket", "", "The bucket name in which the NVIDIA components are stored") - StringVarWithViper(cmd, &o.NvidiaInstallerLocation, viperNvidiaPrefix, "nvidia-installer-location", "", "The NVIDIA installer location in the bucket - this must be acquired from NVIDIA and uploaded to your bucket") - StringVarWithViper(cmd, &o.NvidiaTOKLocation, viperNvidiaPrefix, "nvidia-tok-location", "", "The NVIDIA .tok file location in the bucket - this must be acquired from NVIDIA and uploaded to your bucket") - IntVarWithViper(cmd, &o.NvidiaGriddFeatureType, viperNvidiaPrefix, "nvidia-gridd-feature-type", -1, "The gridd feature type - See https://docs.nvidia.com/license-system/latest/nvidia-license-system-quick-start-guide/index.html#configuring-nls-licensed-client-on-linux for more information") + StringVarWithViper(cmd, &o.NvidiaVersion, viperGpuPrefix, "nvidia-driver-version", "525.129.03", "The NVIDIA driver version") + StringVarWithViper(cmd, &o.NvidiaBucket, viperGpuPrefix, "nvidia-bucket", "", "The bucket name in which the NVIDIA components are stored") + StringVarWithViper(cmd, &o.NvidiaInstallerLocation, viperGpuPrefix, "nvidia-installer-location", "", "The NVIDIA installer location in the bucket - this must be acquired from NVIDIA and uploaded to your bucket") + StringVarWithViper(cmd, &o.NvidiaTOKLocation, viperGpuPrefix, "nvidia-tok-location", "", "The NVIDIA .tok file location in the bucket - this must be acquired from NVIDIA and uploaded to your bucket") + IntVarWithViper(cmd, &o.NvidiaGriddFeatureType, viperGpuPrefix, "nvidia-gridd-feature-type", -1, "The gridd feature type - See https://docs.nvidia.com/license-system/latest/nvidia-license-system-quick-start-guide/index.html#configuring-nls-licensed-client-on-linux for more information") + + o.BaseOptions.AddFlags(cmd) + o.KubernetesClusterFlags.AddFlags(cmd) + o.S3Flags.AddFlags(cmd) + o.OpenStackFlags.AddFlags(cmd, viperOpenStackPrefix) + o.KubeVirtFlags.AddFlags(cmd, viperKubeVirtPrefix) - cmd.MarkFlagsRequiredTogether("enable-nvidia-support", "nvidia-driver-version", "nvidia-bucket", "nvidia-installer-location", "nvidia-tok-location", "nvidia-gridd-feature-type") + cmd.MarkFlagsRequiredTogether("nvidia-driver-version", "nvidia-bucket", "nvidia-installer-location", "nvidia-tok-location", "nvidia-gridd-feature-type") cmd.MarkFlagsRequiredTogether("cni-version", "crictl-version", "kubernetes-version") } diff --git a/pkg/util/flags/flags.go b/pkg/util/flags/flags.go index 756ec18..0d5f0dc 100644 --- a/pkg/util/flags/flags.go +++ b/pkg/util/flags/flags.go @@ -32,6 +32,11 @@ func StringSliceVarWithViper(cmd *cobra.Command, p *[]string, viperPrefix, name bindViper(cmd, viperPrefix, name, false) } +func StringMapVarWithViper(cmd *cobra.Command, p *map[string]string, viperPrefix, name string, value map[string]string, usage string) { + cmd.Flags().StringToStringVar(p, name, value, usage) + bindViper(cmd, viperPrefix, name, false) +} + func BoolVarWithViper(cmd *cobra.Command, p *bool, viperPrefix, name string, value bool, usage string) { cmd.Flags().BoolVar(p, name, value, usage) bindViper(cmd, viperPrefix, name, false) diff --git a/pkg/util/flags/kubernetes.go b/pkg/util/flags/kubernetes.go new file mode 100644 index 0000000..9226579 --- /dev/null +++ b/pkg/util/flags/kubernetes.go @@ -0,0 +1,37 @@ +/* +Copyright 2024 Drewbernetes. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package flags + +import ( + "fmt" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +// KubernetesClusterFlags are flags that can be used for the interaction with a kubernetes cluster. +type KubernetesClusterFlags struct { + KubeconfigPath string +} + +// SetOptionsFromViper configures additional options passed in via viper for the struct. +func (k *KubernetesClusterFlags) SetOptionsFromViper() { + k.KubeconfigPath = viper.GetString(fmt.Sprintf("%s.kubeconfig-path", viperKubernetesClusterPrefix)) +} + +func (k *KubernetesClusterFlags) AddFlags(cmd *cobra.Command) { + StringVarWithViper(cmd, &k.KubeconfigPath, viperKubernetesClusterPrefix, "kubeconfig-path", "", "The KubeConfig to use for cluster interaction") +} diff --git a/pkg/util/flags/kubevirt.go b/pkg/util/flags/kubevirt.go new file mode 100644 index 0000000..2c05480 --- /dev/null +++ b/pkg/util/flags/kubevirt.go @@ -0,0 +1,47 @@ +/* +Copyright 2024 Drewbernetes. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package flags + +import ( + "fmt" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +// KubeVirtFlags are explicitly for QEMU image builds. +type KubeVirtFlags struct { + QEMUFlags + StoreInS3 bool + ImageBucket string + ImageName string + ImageNamespace string +} + +// SetOptionsFromViper configures additional options passed in via viper for the struct. +func (k *KubeVirtFlags) SetOptionsFromViper() { + k.QEMUFlags.SetOptionsFromViper() + k.StoreInS3 = viper.GetBool(fmt.Sprintf("%s.store-in-s3", viperKubeVirtPrefix)) + k.ImageBucket = viper.GetString(fmt.Sprintf("%s.image-bucket", viperKubeVirtPrefix)) + k.ImageNamespace = viper.GetString(fmt.Sprintf("%s.image-namespace", viperKubeVirtPrefix)) +} + +func (k *KubeVirtFlags) AddFlags(cmd *cobra.Command, viperPrefix string) { + k.QEMUFlags.AddFlags(cmd, viperPrefix) + BoolVarWithViper(cmd, &k.StoreInS3, viperPrefix, "store-in-s3", false, "Whether to upload the disk image to S3") + StringVarWithViper(cmd, &k.ImageBucket, viperPrefix, "image-bucket", "10G", "The bucket in S3 to store the image in") + StringVarWithViper(cmd, &k.ImageNamespace, viperPrefix, "image-namespace", "vm-images", "The Namespace in which to deploy the data volumes for S3 images") +} diff --git a/pkg/util/flags/openstack.go b/pkg/util/flags/openstack.go index f1fe00d..d18a131 100644 --- a/pkg/util/flags/openstack.go +++ b/pkg/util/flags/openstack.go @@ -61,7 +61,7 @@ func (o *OpenStackInstanceFlags) SetOptionsFromViper() { func (o *OpenStackInstanceFlags) AddFlags(cmd *cobra.Command, viperPrefix string) { StringVarWithViper(cmd, &o.NetworkID, viperPrefix, "network-id", "", "Network ID to deploy the server onto for scanning") StringVarWithViper(cmd, &o.FlavorName, viperPrefix, "flavor-name", "", "The Name of the instance flavor to use for the build") - BoolVarWithViper(cmd, &o.AttachConfigDrive, viperPrefix, "attach-config-drive", false, "Used to enable a config drive on Openstack. This may be required if directly attaching an external network to the instance") + BoolVarWithViper(cmd, &o.AttachConfigDrive, viperPrefix, "attach-config-drive", false, "Whether or not nova should use ConfigDrive for cloud-init metadata.") } // OpenStackFlags are explicitly for OpenStack based clouds and defines settings that pertain only to that cloud type. @@ -70,42 +70,51 @@ type OpenStackFlags struct { OpenStackInstanceFlags SourceImageID string + SSHPrivateKeyFile string + SSHKeypairName string UseFloatingIP bool FloatingIPNetworkName string + SecurityGroup string ImageVisibility string ImageDiskFormat string + UseBlockStorageVolume string VolumeType string VolumeSize int - RootfsUUID string } // SetOptionsFromViper configures additional options passed in via viper for the struct. -func (o *OpenStackFlags) SetOptionsFromViper() { - o.OpenStackCoreFlags.SetOptionsFromViper() - o.OpenStackInstanceFlags.SetOptionsFromViper() - - o.SourceImageID = viper.GetString(fmt.Sprintf("%s.source-image", viperOpenStackPrefix)) - o.UseFloatingIP = viper.GetBool(fmt.Sprintf("%s.use-floating-ip", viperOpenStackPrefix)) - o.FloatingIPNetworkName = viper.GetString(fmt.Sprintf("%s.floating-ip-network-name", viperOpenStackPrefix)) - o.ImageVisibility = viper.GetString(fmt.Sprintf("%s.image-visibility", viperOpenStackPrefix)) - o.ImageDiskFormat = viper.GetString(fmt.Sprintf("%s.image-disk-format", viperOpenStackPrefix)) - o.VolumeType = viper.GetString(fmt.Sprintf("%s.volume-type", viperOpenStackPrefix)) - o.VolumeSize = viper.GetInt(fmt.Sprintf("%s.volume-size", viperOpenStackPrefix)) - o.RootfsUUID = viper.GetString(fmt.Sprintf("%s.rootfs-uuid", viperOpenStackPrefix)) +func (q *OpenStackFlags) SetOptionsFromViper() { + q.SourceImageID = viper.GetString(fmt.Sprintf("%s.source-image", viperOpenStackPrefix)) + q.SSHPrivateKeyFile = viper.GetString(fmt.Sprintf("%s.ssh-privatekey-file", viperOpenStackPrefix)) + q.SSHKeypairName = viper.GetString(fmt.Sprintf("%s.ssh-keypair-name", viperOpenStackPrefix)) + q.UseFloatingIP = viper.GetBool(fmt.Sprintf("%s.use-floating-ip", viperOpenStackPrefix)) + q.FloatingIPNetworkName = viper.GetString(fmt.Sprintf("%s.floating-ip-network-name", viperOpenStackPrefix)) + q.SecurityGroup = viper.GetString(fmt.Sprintf("%s.security-group", viperOpenStackPrefix)) + q.ImageVisibility = viper.GetString(fmt.Sprintf("%s.image-visibility", viperOpenStackPrefix)) + q.ImageDiskFormat = viper.GetString(fmt.Sprintf("%s.image-disk-format", viperOpenStackPrefix)) + q.UseBlockStorageVolume = viper.GetString(fmt.Sprintf("%s.use-blockstorage-volume", viperOpenStackPrefix)) + q.VolumeType = viper.GetString(fmt.Sprintf("%s.volume-type", viperOpenStackPrefix)) + q.VolumeSize = viper.GetInt(fmt.Sprintf("%s.volume-size", viperOpenStackPrefix)) + + q.OpenStackCoreFlags.SetOptionsFromViper() + q.OpenStackInstanceFlags.SetOptionsFromViper() } -func (o *OpenStackFlags) AddFlags(cmd *cobra.Command, viperPrefix string) { - o.OpenStackCoreFlags.AddFlags(cmd, viperPrefix) - o.OpenStackInstanceFlags.AddFlags(cmd, viperPrefix) - - StringVarWithViper(cmd, &o.SourceImageID, viperPrefix, "source-image-id", "ubuntu-2204", "The ID of the image that will be used as a base for the newly built image") - BoolVarWithViper(cmd, &o.UseFloatingIP, viperPrefix, "use-floating-ip", true, "Whether to use a floating IP for the instance") - StringVarWithViper(cmd, &o.FloatingIPNetworkName, viperPrefix, "floating-ip-network-name", "Internet", "The Name of the network in which to create the floating ip") - StringVarWithViper(cmd, &o.ImageVisibility, viperPrefix, "image-visibility", "private", "Change the image visibility in Openstack - you need to ensure the use you're authenticating with has permissions to do so or this will fail") - StringVarWithViper(cmd, &o.ImageDiskFormat, viperPrefix, "image-disk-format", "raw", "The image disk format in Openstack") - StringVarWithViper(cmd, &o.VolumeType, viperPrefix, "volume-type", "", "The volume type in Openstack") - IntVarWithViper(cmd, &o.VolumeSize, viperPrefix, "volume-size", 10, "Size of the Block Storage service volume in GB") - StringVarWithViper(cmd, &o.RootfsUUID, viperPrefix, "rootfs-uuid", "", "The UUID of the root filesystem. This can be acquired from the source image that the resulting image will be built from - (this will be automated soon™)") +func (q *OpenStackFlags) AddFlags(cmd *cobra.Command, viperPrefix string) { + StringVarWithViper(cmd, &q.SourceImageID, viperPrefix, "source-image-id", "ubuntu-2204", "The ID of the image that will be used as a base for the newly built image") + StringVarWithViper(cmd, &q.SSHPrivateKeyFile, viperPrefix, "ssh-privatekey-file", "", "The Private Key to use when using ssh-keypair-name") + StringVarWithViper(cmd, &q.SSHKeypairName, viperPrefix, "ssh-keypair-name", "", "Define an SSH Keypair to use instead of generating automatically") + BoolVarWithViper(cmd, &q.UseFloatingIP, viperPrefix, "use-floating-ip", true, "Whether to use a floating IP for the instance") + StringVarWithViper(cmd, &q.FloatingIPNetworkName, viperPrefix, "floating-ip-network-name", "public1", "The Name of the network in which to create the floating ip") + StringVarWithViper(cmd, &q.SecurityGroup, viperPrefix, "security-group", "", "Specify the security groups to attach") + StringVarWithViper(cmd, &q.ImageVisibility, viperPrefix, "image-visibility", "private", "Change the image visibility in Openstack - you need to ensure the use you're authenticating with has permissions to do so or this will fail") + StringVarWithViper(cmd, &q.ImageDiskFormat, viperPrefix, "image-disk-format", "", "The image disk format in Openstack") + StringVarWithViper(cmd, &q.UseBlockStorageVolume, viperPrefix, "use-blockstorage-volume", "", "Use Block Storage service volume for the instance root volume instead of Compute service local volume") + StringVarWithViper(cmd, &q.VolumeType, viperPrefix, "volume-type", "", "Type of the Block Storage service volume. If this isn't specified, the default enforced by your OpenStack cluster will be used") + IntVarWithViper(cmd, &q.VolumeSize, viperPrefix, "volume-size", 0, "Size of the Block Storage service volume in GB. If this isn't specified, it is set to source image min disk value (if set) or calculated from the source image bytes size. Note that in some cases this needs to be specified, if use_blockstorage_volume is true") + + q.OpenStackCoreFlags.AddFlags(cmd, viperPrefix) + q.OpenStackInstanceFlags.AddFlags(cmd, viperPrefix) cmd.MarkFlagsRequiredTogether("use-floating-ip", "floating-ip-network-name") } diff --git a/pkg/util/flags/prefixes.go b/pkg/util/flags/prefixes.go index dc468fe..cd92f2e 100644 --- a/pkg/util/flags/prefixes.go +++ b/pkg/util/flags/prefixes.go @@ -19,18 +19,18 @@ package flags import "fmt" const ( - viperCloudPrefix = "cloud" - viperS3Prefix = "s3" - viperBuildPrefix = "build" - viperScanPrefix = "scan" - viperSignPrefix = "sign" - viperPublishPrefix = "publish" + viperInfraPrefix = "infra" + viperKubernetesClusterPrefix = "k8s" + viperS3Prefix = "s3" + viperBuildPrefix = "build" + viperScanPrefix = "scan" + viperSignPrefix = "sign" ) var ( - viperOpenStackPrefix = fmt.Sprintf("%s.openstack", viperCloudPrefix) - viperNvidiaPrefix = fmt.Sprintf("%s.nvidia", viperBuildPrefix) - viperGithubPrefix = fmt.Sprintf("%s.github", viperPublishPrefix) + viperOpenStackPrefix = fmt.Sprintf("%s.openstack", viperInfraPrefix) + viperKubeVirtPrefix = fmt.Sprintf("%s.kubevirt", viperInfraPrefix) + viperGpuPrefix = fmt.Sprintf("%s.gpu", viperBuildPrefix) viperVaultPrefix = fmt.Sprintf("%s.vault", viperSignPrefix) viperGeneratePrefix = fmt.Sprintf("%s.generate", viperSignPrefix) viperSinglePrefix = fmt.Sprintf("%s.single", viperScanPrefix) diff --git a/pkg/util/flags/publish.go b/pkg/util/flags/publish.go deleted file mode 100644 index 5f80902..0000000 --- a/pkg/util/flags/publish.go +++ /dev/null @@ -1,62 +0,0 @@ -/* -Copyright 2024 Drewbernetes. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package flags - -import ( - "fmt" - "github.com/spf13/cobra" - "github.com/spf13/viper" -) - -type PublishOptions struct { - OpenStackCoreFlags - ImageID string - GithubUser string - GithubAccount string - GithubProject string - GithubToken string - GithubPagesBranch string - ResultsFile string -} - -func (o *PublishOptions) SetOptionsFromViper() { - o.OpenStackCoreFlags.SetOptionsFromViper() - - o.ImageID = viper.GetString(fmt.Sprintf("%s.image-id", viperPublishPrefix)) - o.GithubUser = viper.GetString(fmt.Sprintf("%s.user", viperGithubPrefix)) - o.GithubAccount = viper.GetString(fmt.Sprintf("%s.account", viperGithubPrefix)) - o.GithubProject = viper.GetString(fmt.Sprintf("%s.project", viperGithubPrefix)) - o.GithubToken = viper.GetString(fmt.Sprintf("%s.token", viperGithubPrefix)) - o.GithubPagesBranch = viper.GetString(fmt.Sprintf("%s.pages-branch", viperGithubPrefix)) - o.ResultsFile = viper.GetString(fmt.Sprintf("%s.image-id", viperPublishPrefix)) -} - -func (o *PublishOptions) AddFlags(cmd *cobra.Command) { - o.OpenStackCoreFlags.AddFlags(cmd, viperOpenStackPrefix) - - StringVarWithViper(cmd, &o.GithubUser, viperGithubPrefix, "user", "", "The user for the GitHub project to which the pages will be pushed") - StringVarWithViper(cmd, &o.GithubProject, viperGithubPrefix, "project", "", "The GitHub project to which the pages will be pushed") - StringVarWithViper(cmd, &o.GithubAccount, viperGithubPrefix, "account", "", "The account in which the project is stored. This will default to the user") - StringVarWithViper(cmd, &o.GithubToken, viperGithubPrefix, "token", "", "The token for the GitHub project to which the pages will be pushed") - StringVarWithViper(cmd, &o.GithubPagesBranch, viperGithubPrefix, "pages-branch", "gh-pages", "The branch name for GitHub project to which the pages will be pushed") - StringVarWithViper(cmd, &o.ImageID, viperPublishPrefix, "image-id", "", "The ID of the image to publish the CVE results for") - - //TODO: this is currently not used or implemented in any way - StringVarWithViper(cmd, &o.ResultsFile, viperPublishPrefix, "results-file", "", "The results file location") - - cmd.MarkFlagsRequiredTogether("user", "project", "token") -} diff --git a/pkg/util/flags/qemu.go b/pkg/util/flags/qemu.go new file mode 100644 index 0000000..c295417 --- /dev/null +++ b/pkg/util/flags/qemu.go @@ -0,0 +1,43 @@ +/* +Copyright 2024 Drewbernetes. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package flags + +import ( + "fmt" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +// QEMUFlags are explicitly for QEMU image builds. +type QEMUFlags struct { + QemuBinary string + DiskSize string + OutputDirectory string +} + +// SetOptionsFromViper configures additional options passed in via viper for the struct. +func (q *QEMUFlags) SetOptionsFromViper() { + q.QemuBinary = viper.GetString(fmt.Sprintf("%s.qemu-binary", viperKubeVirtPrefix)) + q.DiskSize = viper.GetString(fmt.Sprintf("%s.disk-size", viperKubeVirtPrefix)) + q.OutputDirectory = viper.GetString(fmt.Sprintf("%s.output-directory", viperKubeVirtPrefix)) +} + +func (q *QEMUFlags) AddFlags(cmd *cobra.Command, viperPrefix string) { + StringVarWithViper(cmd, &q.QemuBinary, viperPrefix, "qemu-binary", "qemu-system-x86_64", "The name of the qemu-system-x86_64 binary to use") + StringVarWithViper(cmd, &q.DiskSize, viperPrefix, "disk-size", "10G", "The size of the VM disk") + StringVarWithViper(cmd, &q.OutputDirectory, viperPrefix, "output-directory", "/tmp/image-output/", "The directory in which images will be stored once built") +} diff --git a/pkg/util/flags/s3.go b/pkg/util/flags/s3.go index a01dee2..9a45daa 100644 --- a/pkg/util/flags/s3.go +++ b/pkg/util/flags/s3.go @@ -26,6 +26,7 @@ type S3Flags struct { Endpoint string AccessKey string SecretKey string + Region string IsCeph bool } @@ -33,15 +34,16 @@ func (o *S3Flags) SetOptionsFromViper() { o.Endpoint = viper.GetString(fmt.Sprintf("%s.endpoint", viperS3Prefix)) o.AccessKey = viper.GetString(fmt.Sprintf("%s.access-key", viperS3Prefix)) o.SecretKey = viper.GetString(fmt.Sprintf("%s.secret-key", viperS3Prefix)) + o.Region = viper.GetString(fmt.Sprintf("%s.region", viperS3Prefix)) o.IsCeph = viper.GetBool(fmt.Sprintf("%s.is-ceph", viperS3Prefix)) - } func (o *S3Flags) AddFlags(cmd *cobra.Command) { StringVarWithViper(cmd, &o.Endpoint, viperS3Prefix, "endpoint", "", "The endpoint of the bucket from which to download resources") StringVarWithViper(cmd, &o.AccessKey, viperS3Prefix, "access-key", "", "The access key used to access the bucket from which to download resources") StringVarWithViper(cmd, &o.SecretKey, viperS3Prefix, "secret-key", "", "The secret key used to access the bucket from which to download resources") + StringVarWithViper(cmd, &o.Region, viperS3Prefix, "region", "us-east-1", "The region of the S3 endpoint") BoolVarWithViper(cmd, &o.IsCeph, viperS3Prefix, "is-ceph", false, "If the S3 endpoint is CEPH then set this to true to allow ansible to work with the endpoint") - cmd.MarkFlagsRequiredTogether("endpoint", "access-key", "secret-key") + cmd.MarkFlagsRequiredTogether("access-key", "secret-key") } diff --git a/pkg/util/flags/scan.go b/pkg/util/flags/scan.go index c56649a..8430747 100644 --- a/pkg/util/flags/scan.go +++ b/pkg/util/flags/scan.go @@ -23,8 +23,12 @@ import ( ) type ScanOptions struct { + BaseOptions OpenStackFlags + KubeVirtFlags S3Flags + ScanSingleOptions + ScanMultipleOptions AutoDeleteImage bool SkipCVECheck bool @@ -37,8 +41,21 @@ type ScanOptions struct { } func (o *ScanOptions) SetOptionsFromViper() { + o.AutoDeleteImage = viper.GetBool(fmt.Sprintf("%s.auto-delete-image", viperScanPrefix)) + o.SkipCVECheck = viper.GetBool(fmt.Sprintf("%s.skip-cve-check", viperScanPrefix)) + o.MaxSeverityScore = viper.GetFloat64(fmt.Sprintf("%s.max-severity-score", viperScanPrefix)) + o.MaxSeverityType = viper.GetString(fmt.Sprintf("%s.max-severity-type", viperScanPrefix)) + o.ScanBucket = viper.GetString(fmt.Sprintf("%s.scan-bucket", viperScanPrefix)) + o.TrivyignorePath = viper.GetString(fmt.Sprintf("%s.trivyignore-path", viperScanPrefix)) + o.TrivyignoreFilename = viper.GetString(fmt.Sprintf("%s.trivyignore-filename", viperScanPrefix)) + o.TrivyignoreList = viper.GetStringSlice(fmt.Sprintf("%s.trivyignore-list", viperScanPrefix)) + + o.BaseOptions.SetOptionsFromViper() o.OpenStackFlags.SetOptionsFromViper() + o.KubeVirtFlags.SetOptionsFromViper() o.S3Flags.SetOptionsFromViper() + o.ScanSingleOptions.SetOptionsFromViper() + o.ScanMultipleOptions.SetOptionsFromViper() // We can override the value of the instance at the scan level // This isn't available in the flags as it's already a flag that's available. This is viper only. @@ -46,20 +63,9 @@ func (o *ScanOptions) SetOptionsFromViper() { if instance != "" { o.FlavorName = instance } - o.AutoDeleteImage = viper.GetBool(fmt.Sprintf("%s.auto-delete-image", viperScanPrefix)) - o.SkipCVECheck = viper.GetBool(fmt.Sprintf("%s.skip-cve-check", viperScanPrefix)) - o.MaxSeverityScore = viper.GetFloat64(fmt.Sprintf("%s.max-severity-score", viperScanPrefix)) - o.MaxSeverityType = viper.GetString(fmt.Sprintf("%s.max-severity-type", viperScanPrefix)) - o.ScanBucket = viper.GetString(fmt.Sprintf("%s.scan-bucket", viperScanPrefix)) - o.TrivyignorePath = viper.GetString(fmt.Sprintf("%s.trivyignore-path", viperScanPrefix)) - o.TrivyignoreFilename = viper.GetString(fmt.Sprintf("%s.trivyignore-filename", viperScanPrefix)) - o.TrivyignoreList = viper.GetStringSlice(fmt.Sprintf("%s.trivyignore-list", viperScanPrefix)) } func (o *ScanOptions) AddFlags(cmd *cobra.Command) { - o.OpenStackFlags.AddFlags(cmd, viperOpenStackPrefix) - o.S3Flags.AddFlags(cmd) - BoolVarWithViper(cmd, &o.AutoDeleteImage, viperScanPrefix, "auto-delete-image", false, "If true, the image will be deleted if a vulnerability check does not succeed - recommended when building new images.") BoolVarWithViper(cmd, &o.SkipCVECheck, viperScanPrefix, "skip-cve-check", false, "If true, the image will be allowed even if a vulnerability is detected.") Float64VarWithViper(cmd, &o.MaxSeverityScore, viperScanPrefix, "max-severity-score", 7.0, "Can be anything from 0.1 to 10.0. Anything equal to or above this value will cause a failure. (Unless skip-cve-check is supplied)") @@ -68,41 +74,38 @@ func (o *ScanOptions) AddFlags(cmd *cobra.Command) { StringVarWithViper(cmd, &o.TrivyignorePath, viperScanPrefix, "trivyignore-path", "", "The path in the scan-bucket where the trivyignore file is located") StringVarWithViper(cmd, &o.TrivyignoreFilename, viperScanPrefix, "trivyignore-filename", "", "The filename of the trivyignore file") StringSliceVarWithViper(cmd, &o.TrivyignoreList, viperScanPrefix, "trivyignore-list", []string{}, "A list of CVEs to ignore") + + o.BaseOptions.AddFlags(cmd) + o.OpenStackFlags.AddFlags(cmd, viperOpenStackPrefix) + o.KubeVirtFlags.AddFlags(cmd, viperOpenStackPrefix) + o.S3Flags.AddFlags(cmd) + o.ScanSingleOptions.AddFlags(cmd) + o.ScanMultipleOptions.AddFlags(cmd) } type ScanSingleOptions struct { - ScanOptions - ImageID string } func (o *ScanSingleOptions) SetOptionsFromViper() { - o.ScanOptions.SetOptionsFromViper() o.ImageID = viper.GetString(fmt.Sprintf("%s.image-id", viperSinglePrefix)) } func (o *ScanSingleOptions) AddFlags(cmd *cobra.Command) { - o.ScanOptions.AddFlags(cmd) - StringVarWithViper(cmd, &o.ImageID, viperSinglePrefix, "image-id", "", "The ID of the image to scan") } type ScanMultipleOptions struct { - ScanOptions - ImageSearch string Concurrency int } func (o *ScanMultipleOptions) SetOptionsFromViper() { - o.ScanOptions.SetOptionsFromViper() o.Concurrency = viper.GetInt(fmt.Sprintf("%s.concurrency", viperMultiplePrefix)) o.ImageSearch = viper.GetString(fmt.Sprintf("%s.image-search", viperMultiplePrefix)) } func (o *ScanMultipleOptions) AddFlags(cmd *cobra.Command) { - o.ScanOptions.AddFlags(cmd) - IntVarWithViper(cmd, &o.Concurrency, viperMultiplePrefix, "concurrency", 5, "The number of scans that can happen at any one time") StringVarWithViper(cmd, &o.ImageSearch, viperMultiplePrefix, "image-search", "", "The prefix of all the images to scan") } diff --git a/pkg/util/flags/sign.go b/pkg/util/flags/sign.go index 066bba7..776e16b 100644 --- a/pkg/util/flags/sign.go +++ b/pkg/util/flags/sign.go @@ -24,36 +24,51 @@ import ( // SignOptions contains options for the 'sign' command. These will be available to the subcommands and not configured directly for the sign command itself. type SignOptions struct { + BaseOptions OpenStackCoreFlags + SignGenerateOptions VaultURL string VaultToken string VaultMountPath string VaultSecretPath string ImageID string + PrivateKey string + PublicKey string } // SetOptionsFromViper configures additional options passed in via viper for the struct from any subcommands. func (o *SignOptions) SetOptionsFromViper() { - o.OpenStackCoreFlags.SetOptionsFromViper() - o.ImageID = viper.GetString(fmt.Sprintf("%s.image-id", viperSignPrefix)) o.VaultURL = viper.GetString(fmt.Sprintf("%s.url", viperVaultPrefix)) o.VaultToken = viper.GetString(fmt.Sprintf("%s.token", viperVaultPrefix)) o.VaultMountPath = viper.GetString(fmt.Sprintf("%s.mount-path", viperVaultPrefix)) o.VaultSecretPath = viper.GetString(fmt.Sprintf("%s.secret-name", viperVaultPrefix)) + o.PrivateKey = viper.GetString(fmt.Sprintf("%s.private-key", viperSignPrefix)) + o.PublicKey = viper.GetString(fmt.Sprintf("%s.public-key", viperSignPrefix)) + + o.BaseOptions.SetOptionsFromViper() + o.OpenStackCoreFlags.SetOptionsFromViper() + o.SignGenerateOptions.SetOptionsFromViper() } // AddFlags adds additional flags to the subcommands that call this. func (o *SignOptions) AddFlags(cmd *cobra.Command) { - o.OpenStackCoreFlags.AddFlags(cmd, viperOpenStackPrefix) - StringVarWithViper(cmd, &o.ImageID, viperSignPrefix, "image-id", "", "The image ID of the image to sign") StringVarWithViper(cmd, &o.VaultURL, viperVaultPrefix, "url", "", "The Vault URL from which you will pull the private key") StringVarWithViper(cmd, &o.VaultToken, viperVaultPrefix, "token", "", "The token used to log into vault") StringVarWithViper(cmd, &o.VaultMountPath, viperVaultPrefix, "mount-path", "", "The mount path to the secret vault") StringVarWithViper(cmd, &o.VaultSecretPath, viperVaultPrefix, "secret-name", "", "The name of the secret within the mount path") + StringVarWithViper(cmd, &o.PrivateKey, viperSignPrefix, "private-key", "", "The path to the private key that will be used to sign the image") + StringVarWithViper(cmd, &o.PublicKey, viperSignPrefix, "public-key", "", "The path to the private key that will be used to sign the image") + + o.BaseOptions.AddFlags(cmd) + o.OpenStackCoreFlags.AddFlags(cmd, viperOpenStackPrefix) + o.SignGenerateOptions.AddFlags(cmd) + + cmd.MarkFlagsMutuallyExclusive("url", "private-key") + cmd.MarkFlagsMutuallyExclusive("url", "public-key") cmd.MarkFlagsRequiredTogether("url", "token", "mount-path", "secret-name") } @@ -70,53 +85,5 @@ func (o *SignGenerateOptions) SetOptionsFromViper() { // AddFlags adds flags to the 'generate' subcommand and binds them to the 'generate' options. func (o *SignGenerateOptions) AddFlags(cmd *cobra.Command) { StringVarWithViper(cmd, &o.Path, viperGeneratePrefix, "path", "/tmp/baski", "A directory location in which to output the generated keys") -} - -// SignImageOptions contains additional options for the 'image' subcommand. -type SignImageOptions struct { - SignOptions - - PrivateKey string -} - -// SetOptionsFromViper configures options passed in via viper for the struct. -func (o *SignImageOptions) SetOptionsFromViper() { - o.SignOptions.SetOptionsFromViper() - o.PrivateKey = viper.GetString(fmt.Sprintf("%s.private-key", viperSignPrefix)) -} - -// AddFlags adds flags to the sign 'image' command and binds them to the sign 'image' options. -func (o *SignImageOptions) AddFlags(cmd *cobra.Command) { - o.SignOptions.AddFlags(cmd) - - StringVarWithViper(cmd, &o.PrivateKey, viperSignPrefix, "private-key", "", "The path to the private key that will be used to sign the image") - - cmd.MarkFlagsMutuallyExclusive("url", "private-key") -} - -// SignValidateOptions contains additional options for the 'validate' subcommand. -type SignValidateOptions struct { - SignOptions - - PublicKey string - Digest string -} - -// SetOptionsFromViper configures options passed in via viper for the struct. -func (o *SignValidateOptions) SetOptionsFromViper() { - o.SignOptions.SetOptionsFromViper() - - o.PublicKey = viper.GetString(fmt.Sprintf("%s.public-key", viperSignPrefix)) - o.Digest = viper.GetString(fmt.Sprintf("%s.digest", viperSignPrefix)) -} - -// AddFlags adds flags to the 'validate' subcommand and binds them to the 'validate' options. -func (o *SignValidateOptions) AddFlags(cmd *cobra.Command) { - o.SignOptions.AddFlags(cmd) - - StringVarWithViper(cmd, &o.PublicKey, viperSignPrefix, "public-key", "", "The path to the private key that will be used to sign the image") - StringVarWithViper(cmd, &o.Digest, viperSignPrefix, "digest", "", "The digest to verify") - - cmd.MarkFlagsMutuallyExclusive("url", "public-key") } diff --git a/pkg/util/interfaces.go b/pkg/util/interfaces/base.go similarity index 80% rename from pkg/util/interfaces.go rename to pkg/util/interfaces/base.go index 5ee3722..6b04d6b 100644 --- a/pkg/util/interfaces.go +++ b/pkg/util/interfaces/base.go @@ -1,13 +1,12 @@ -package util +package interfaces import ( "github.com/drewbernetes/baski/pkg/server/generated" - "io" "net/http" "os" ) -//go:generate mockgen -source=interfaces.go -destination=../mock/interfaces.go -package=mock +//go:generate mockgen -source=base.go -destination=../../mock/base.go -package=mock type HandlerInterface interface { Healthz(w http.ResponseWriter, r *http.Request) @@ -16,11 +15,6 @@ type HandlerInterface interface { ApiV1GetTests(w http.ResponseWriter, r *http.Request) ApiV1GetTest(w http.ResponseWriter, r *http.Request, imageId generated.ImageID) } -type S3Interface interface { - List() ([]string, error) - Fetch(string) ([]byte, error) - Put(string, string, io.ReadSeeker) error -} type VaultInterface interface { Fetch(mountPath, secretPath, data string) ([]byte, error) @@ -31,3 +25,9 @@ type SSHInterface interface { SSHClose() error SFTPClose() error } + +type S3Interface interface { + List(string) ([]string, error) + Fetch(string) ([]byte, error) + Put(key string, body *os.File) error +} diff --git a/pkg/util/interfaces/openstack.go b/pkg/util/interfaces/openstack.go new file mode 100644 index 0000000..6f28417 --- /dev/null +++ b/pkg/util/interfaces/openstack.go @@ -0,0 +1,47 @@ +package interfaces + +import ( + "github.com/drewbernetes/baski/pkg/util/flags" + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/keypairs" + "github.com/gophercloud/gophercloud/openstack/compute/v2/servers" + "github.com/gophercloud/gophercloud/openstack/imageservice/v2/images" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips" +) + +//go:generate mockgen -source=openstack.go -destination=../../mock/openstack.go -package=mock + +type OpenStackClient interface { + Client() (*gophercloud.ProviderClient, error) +} + +type OpenStackComputeClient interface { + CreateKeypair(keyNamePrefix string) (*keypairs.KeyPair, error) + RemoveKeypair(keyName string) error + CreateServer(keypairName string, flavor, networkID string, attachConfigDrive *bool, userData []byte, imageID string, securityGroups []string) (*servers.Server, error) + GetServerStatus(sid string) (bool, error) + AttachIP(serverID, fip string) error + RemoveServer(serverID string) error + GetFlavorIDByName(name string) (string, error) +} + +type OpenStackImageClient interface { + ModifyImageMetadata(imgID string, key, value string, operation images.UpdateOp) (*images.Image, error) + FetchAllImages(wildcard string) ([]images.Image, error) + RemoveImage(imgID string) error + FetchImage(imgID string) (*images.Image, error) + TagImage(properties map[string]interface{}, imgID, value, tagName string) error +} + +type OpenStackNetworkClient interface { + GetFloatingIP(networkName string) (*floatingips.FloatingIP, error) + RemoveFIP(fipID string) error +} + +type OpenStackScannerInterface interface { + RunScan(o *flags.ScanOptions) error + FetchScanResults() error + CheckResults() error + TagImage() error + UploadResultsToS3() error +} diff --git a/pkg/util/sign/vault_test.go b/pkg/util/sign/vault_test.go index 64c73da..6af72b8 100644 --- a/pkg/util/sign/vault_test.go +++ b/pkg/util/sign/vault_test.go @@ -18,7 +18,7 @@ package sign import ( "github.com/drewbernetes/baski/pkg/mock" - "github.com/drewbernetes/baski/pkg/util" + "github.com/drewbernetes/baski/pkg/util/interfaces" "go.uber.org/mock/gomock" "testing" ) @@ -34,6 +34,6 @@ func TestFetch(t *testing.T) { } } -func fetch(v util.VaultInterface) ([]byte, error) { +func fetch(v interfaces.VaultInterface) ([]byte, error) { return v.Fetch("kv/eso", "some/path", "key") } diff --git a/testhelpers/test_helpers.go b/testhelpers/test_helpers.go deleted file mode 100644 index 30e9379..0000000 --- a/testhelpers/test_helpers.go +++ /dev/null @@ -1,77 +0,0 @@ -package testhelpers - -import ( - "fmt" - "github.com/gophercloud/gophercloud" - "net" - "net/http" - "net/http/httptest" - "os" - "testing" -) - -var ( - // Mux is a multiplexer that can be used to register handlers. - Mux *http.ServeMux - - // Server is an in-memory HTTP server for testing. - Server *httptest.Server -) - -// GenerateCloudsFile creates a cloud file for testing -func GenerateCloudsFile() error { - var err error - f, err := os.Create(CloudPath) - if err != nil { - return err - } - defer f.Close() - - _, err = f.Write([]byte(cloud)) - if err != nil { - return err - } - - err = os.Setenv("OS_CLIENT_CONFIG_FILE", CloudPath) - if err != nil { - return err - } - - return err -} - -// SetupPersistentPortHTTP prepares the Mux and Server listening specific port. -func SetupPersistentPortHTTP(t *testing.T, port int) { - l, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", port)) - if err != nil { - t.Errorf("Failed to listen to 127.0.0.1:%d: %s", port, err) - } - Mux = http.NewServeMux() - Server = httptest.NewUnstartedServer(Mux) - Server.Listener = l - Server.Start() -} - -// TeardownHTTP releases HTTP-related resources. -func TeardownHTTP() { - Server.Close() -} - -// Endpoint returns a fake endpoint that will actually target the Mux. -func Endpoint() string { - return Server.URL + "/" -} - -// ServiceClient returns a generic service client for use in tests. -func ServiceClient() *gophercloud.ServiceClient { - return &gophercloud.ServiceClient{ - ProviderClient: &gophercloud.ProviderClient{TokenID: TokenID}, - Endpoint: Endpoint(), - } -} - -func CommonServiceClient() *gophercloud.ServiceClient { - sc := ServiceClient() - sc.ResourceBase = sc.Endpoint + "v2.0/" - return sc -} diff --git a/testhelpers/test_fixtures.go b/testhelpers/test_openstack_helpers.go similarity index 77% rename from testhelpers/test_fixtures.go rename to testhelpers/test_openstack_helpers.go index 9c90e07..fa27249 100644 --- a/testhelpers/test_fixtures.go +++ b/testhelpers/test_openstack_helpers.go @@ -1,19 +1,14 @@ package testhelpers -import "fmt" - -var cloud = fmt.Sprintf(`clouds: - test-account: - auth: - auth_url: http://127.0.0.1:%d - username: "some-user" - password: "some-password" - project_id: 123456789abcdefghijklmnopqrstuvw - project_name: "test-account" - user_domain_name: "Default" - region_name: "RegionOne" - interface: "public" - identity_api_version: 3`, Port) +import ( + "fmt" + "github.com/gophercloud/gophercloud" + "net" + "net/http" + "net/http/httptest" + "os" + "testing" +) const ( Port = 5001 @@ -168,3 +163,82 @@ const ( } ` ) + +var ( + // Mux is a multiplexer that can be used to register handlers. + Mux *http.ServeMux + + // Server is an in-memory HTTP server for testing. + Server *httptest.Server + + cloud = fmt.Sprintf(`clouds: + test-account: + auth: + auth_url: http://127.0.0.1:%d + username: "some-user" + password: "some-password" + project_id: 123456789abcdefghijklmnopqrstuvw + project_name: "test-account" + user_domain_name: "Default" + region_name: "RegionOne" + interface: "public" + identity_api_version: 3`, Port) +) + +// GenerateCloudsFile creates a cloud file for testing +func GenerateCloudsFile() error { + var err error + f, err := os.Create(CloudPath) + if err != nil { + return err + } + defer f.Close() + + _, err = f.Write([]byte(cloud)) + if err != nil { + return err + } + + err = os.Setenv("OS_CLIENT_CONFIG_FILE", CloudPath) + if err != nil { + return err + } + + return err +} + +// SetupPersistentPortHTTP prepares the Mux and Server listening specific port. +func SetupPersistentPortHTTP(t *testing.T, port int) { + l, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", port)) + if err != nil { + t.Errorf("Failed to listen to 127.0.0.1:%d: %s", port, err) + } + Mux = http.NewServeMux() + Server = httptest.NewUnstartedServer(Mux) + Server.Listener = l + Server.Start() +} + +// TeardownHTTP releases HTTP-related resources. +func TeardownHTTP() { + Server.Close() +} + +// Endpoint returns a fake endpoint that will actually target the Mux. +func Endpoint() string { + return Server.URL + "/" +} + +// ServiceClient returns a generic service client for use in tests. +func ServiceClient() *gophercloud.ServiceClient { + return &gophercloud.ServiceClient{ + ProviderClient: &gophercloud.ProviderClient{TokenID: TokenID}, + Endpoint: Endpoint(), + } +} + +func CommonServiceClient() *gophercloud.ServiceClient { + sc := ServiceClient() + sc.ResourceBase = sc.Endpoint + "v2.0/" + return sc +}