diff --git a/OWNERS_ALIASES b/OWNERS_ALIASES index 5011a53a87..fdfc30f025 100644 --- a/OWNERS_ALIASES +++ b/OWNERS_ALIASES @@ -158,3 +158,21 @@ aliases: - prestist - marmijo - RishabhSaini + agent-reviewers: + - andfasano + - bfournie + - celebdor + - dhellmann + - lranjbar + - pawanpinjarkar + - rwsu + - zaneb + agent-approvers: + - andfasano + - bfournie + - celebdor + - dhellmann + - lranjbar + - pawanpinjarkar + - rwsu + - zaneb diff --git a/cmd/openshift-install/agent.go b/cmd/openshift-install/agent.go new file mode 100644 index 0000000000..c3a4643fa7 --- /dev/null +++ b/cmd/openshift-install/agent.go @@ -0,0 +1,91 @@ +package main + +import ( + "github.com/spf13/cobra" + + "github.com/openshift/installer/cmd/openshift-install/agent" + "github.com/openshift/installer/pkg/asset" + "github.com/openshift/installer/pkg/asset/agent/agentconfig" + "github.com/openshift/installer/pkg/asset/agent/image" + "github.com/openshift/installer/pkg/asset/agent/manifests" + "github.com/openshift/installer/pkg/asset/agent/mirror" + "github.com/openshift/installer/pkg/asset/kubeconfig" +) + +func newAgentCmd() *cobra.Command { + agentCmd := &cobra.Command{ + Use: "agent", + Short: "Commands for supporting cluster installation using agent installer", + RunE: func(cmd *cobra.Command, args []string) error { + return cmd.Help() + }, + } + + agentCmd.AddCommand(newAgentCreateCmd()) + agentCmd.AddCommand(agent.NewWaitForCmd()) + return agentCmd +} + +var ( + agentConfigTarget = target{ + // TODO: remove template wording when interactive survey has been implemented + name: "Agent Config Template", + command: &cobra.Command{ + Use: "agent-config-template", + Short: "Generates a template of the agent config manifest used by the agent installer", + Args: cobra.ExactArgs(0), + }, + assets: []asset.WritableAsset{ + &agentconfig.AgentConfig{}, + }, + } + + agentManifestsTarget = target{ + name: "Cluster Manifests", + command: &cobra.Command{ + Use: "cluster-manifests", + Short: "Generates the cluster definition manifests used by the agent installer", + Args: cobra.ExactArgs(0), + }, + assets: []asset.WritableAsset{ + &manifests.AgentManifests{}, + &mirror.RegistriesConf{}, + &mirror.CaBundle{}, + }, + } + + agentImageTarget = target{ + name: "Image", + command: &cobra.Command{ + Use: "image", + Short: "Generates a bootable image containing all the information needed to deploy a cluster", + Args: cobra.ExactArgs(0), + }, + assets: []asset.WritableAsset{ + &image.AgentImage{}, + &kubeconfig.AgentAdminClient{}, + }, + } + + agentTargets = []target{agentConfigTarget, agentManifestsTarget, agentImageTarget} +) + +func newAgentCreateCmd() *cobra.Command { + + cmd := &cobra.Command{ + Use: "create", + Short: "Commands for generating agent installation artifacts", + Args: cobra.ExactArgs(0), + RunE: func(cmd *cobra.Command, args []string) error { + return cmd.Help() + }, + } + + for _, t := range agentTargets { + t.command.Args = cobra.ExactArgs(0) + t.command.Run = runTargetCmd(t.assets...) + cmd.AddCommand(t.command) + } + + return cmd +} diff --git a/cmd/openshift-install/agent/OWNERS b/cmd/openshift-install/agent/OWNERS new file mode 100644 index 0000000000..51d22e4bb6 --- /dev/null +++ b/cmd/openshift-install/agent/OWNERS @@ -0,0 +1,7 @@ +# See the OWNERS docs: https://git.k8s.io/community/contributors/guide/owners.md +# This file just uses aliases defined in OWNERS_ALIASES. + +approvers: + - agent-approvers +reviewers: + - agent-reviewers \ No newline at end of file diff --git a/cmd/openshift-install/agent/waitfor.go b/cmd/openshift-install/agent/waitfor.go new file mode 100644 index 0000000000..e8d05eb8eb --- /dev/null +++ b/cmd/openshift-install/agent/waitfor.go @@ -0,0 +1,89 @@ +package agent + +import ( + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + + agentpkg "github.com/openshift/installer/pkg/agent" +) + +const ( + exitCodeInstallConfigError = iota + 3 + exitCodeInfrastructureFailed + exitCodeBootstrapFailed + exitCodeInstallFailed +) + +// NewWaitForCmd create the commands for waiting the completion of the agent based cluster installation. +func NewWaitForCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "wait-for", + Short: "Wait for install-time events", + Args: cobra.ExactArgs(0), + RunE: func(cmd *cobra.Command, args []string) error { + return cmd.Help() + }, + } + + cmd.AddCommand(newWaitForBootstrapCompleteCmd()) + cmd.AddCommand(newWaitForInstallCompleteCmd()) + return cmd +} + +func newWaitForBootstrapCompleteCmd() *cobra.Command { + return &cobra.Command{ + Use: "bootstrap-complete", + Short: "Wait until the cluster bootstrap is complete", + Args: cobra.ExactArgs(0), + Run: func(cmd *cobra.Command, args []string) { + assetDir := cmd.Flags().Lookup("dir").Value.String() + logrus.Debugf("asset directory: %s", assetDir) + if len(assetDir) == 0 { + logrus.Fatal("No cluster installation directory found") + } + cluster, err := agentpkg.WaitForBootstrapComplete(assetDir) + if err != nil { + logrus.Debug("Printing the event list gathered from the Agent Rest API") + cluster.PrintInfraEnvRestAPIEventList() + err2 := cluster.API.OpenShift.LogClusterOperatorConditions() + if err2 != nil { + logrus.Error("Attempted to gather ClusterOperator status after wait failure: ", err2) + } + logrus.Info("Use the following commands to gather logs from the cluster") + logrus.Info("openshift-install gather bootstrap --help") + logrus.Error(errors.Wrap(err, "Bootstrap failed to complete: ")) + logrus.Exit(exitCodeBootstrapFailed) + } + }, + } +} + +func newWaitForInstallCompleteCmd() *cobra.Command { + return &cobra.Command{ + Use: "install-complete", + Short: "Wait until the cluster installation is complete", + Args: cobra.ExactArgs(0), + Run: func(cmd *cobra.Command, args []string) { + assetDir := cmd.Flags().Lookup("dir").Value.String() + logrus.Debugf("asset directory: %s", assetDir) + if len(assetDir) == 0 { + logrus.Fatal("No cluster installation directory found") + } + cluster, err := agentpkg.WaitForInstallComplete(assetDir) + if err != nil { + logrus.Debug("Printing the event list gathered from the Agent Rest API") + cluster.PrintInfraEnvRestAPIEventList() + err2 := cluster.API.OpenShift.LogClusterOperatorConditions() + if err2 != nil { + logrus.Error("Attempted to gather ClusterOperator status after wait failure: ", err2) + } + logrus.Error(`Cluster initialization failed because one or more operators are not functioning properly. + The cluster should be accessible for troubleshooting as detailed in the documentation linked below, + https://docs.openshift.com/container-platform/latest/support/troubleshooting/troubleshooting-installations.html`) + logrus.Exit(exitCodeInstallFailed) + } + cluster.PrintInstallationComplete() + }, + } +} diff --git a/cmd/openshift-install/create.go b/cmd/openshift-install/create.go index b3d1cd14cc..6ed134df59 100644 --- a/cmd/openshift-install/create.go +++ b/cmd/openshift-install/create.go @@ -240,6 +240,15 @@ func newCreateCmd() *cobra.Command { return cmd } +func asFileWriter(a asset.WritableAsset) asset.FileWriter { + switch v := a.(type) { + case asset.FileWriter: + return v + default: + return asset.NewDefaultFileWriter(a) + } +} + func runTargetCmd(targets ...asset.WritableAsset) func(cmd *cobra.Command, args []string) { runner := func(directory string) error { assetStore, err := assetstore.NewStore(directory) @@ -253,7 +262,8 @@ func runTargetCmd(targets ...asset.WritableAsset) func(cmd *cobra.Command, args err = errors.Wrapf(err, "failed to fetch %s", a.Name()) } - if err2 := asset.PersistToFile(a, directory); err2 != nil { + err2 := asFileWriter(a).PersistToFile(directory) + if err2 != nil { err2 = errors.Wrapf(err2, "failed to write asset (%s) to disk", a.Name()) if err != nil { logrus.Error(err2) @@ -289,7 +299,9 @@ func runTargetCmd(targets ...asset.WritableAsset) func(cmd *cobra.Command, args } logrus.Fatal(err) } - if cmd.Name() != "cluster" { + switch cmd.Name() { + case "cluster", "image": + default: logrus.Infof(logging.LogCreatedFiles(cmd.Name(), rootOpts.dir, targets)) } diff --git a/cmd/openshift-install/main.go b/cmd/openshift-install/main.go index 44988a5a9d..40fe6d9669 100644 --- a/cmd/openshift-install/main.go +++ b/cmd/openshift-install/main.go @@ -53,6 +53,7 @@ func installerMain() { newCompletionCmd(), newMigrateCmd(), newExplainCmd(), + newAgentCmd(), } { rootCmd.AddCommand(subCmd) } diff --git a/data/data/agent/OWNERS b/data/data/agent/OWNERS new file mode 100644 index 0000000000..51d22e4bb6 --- /dev/null +++ b/data/data/agent/OWNERS @@ -0,0 +1,7 @@ +# See the OWNERS docs: https://git.k8s.io/community/contributors/guide/owners.md +# This file just uses aliases defined in OWNERS_ALIASES. + +approvers: + - agent-approvers +reviewers: + - agent-reviewers \ No newline at end of file diff --git a/data/data/agent/files/etc/containers/containers.conf b/data/data/agent/files/etc/containers/containers.conf new file mode 100644 index 0000000000..79aed96f79 --- /dev/null +++ b/data/data/agent/files/etc/containers/containers.conf @@ -0,0 +1,3 @@ +[engine] +# By default use the infra image build by podman +infra_image = "" diff --git a/data/data/agent/files/etc/issue b/data/data/agent/files/etc/issue new file mode 100644 index 0000000000..34b9af1ca3 --- /dev/null +++ b/data/data/agent/files/etc/issue @@ -0,0 +1,2 @@ +\S +This image built by agent installer. diff --git a/data/data/agent/files/etc/motd b/data/data/agent/files/etc/motd new file mode 100644 index 0000000000..b71f6c2d19 --- /dev/null +++ b/data/data/agent/files/etc/motd @@ -0,0 +1,10 @@ +** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** +This is a host being installed by the OpenShift Assisted Installer. +It will be installed from scratch during the installation. + +The primary service is agent.service. To watch its status, run: +sudo journalctl -u agent.service + +To view the agent log, run: +sudo journalctl TAG=agent +** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** \ No newline at end of file diff --git a/data/data/agent/files/etc/multipath.conf b/data/data/agent/files/etc/multipath.conf new file mode 100644 index 0000000000..721682a92b --- /dev/null +++ b/data/data/agent/files/etc/multipath.conf @@ -0,0 +1,10 @@ +defaults { + user_friendly_names yes + find_multipaths yes + enable_foreign "^$" +} +blacklist_exceptions { + property "(SCSI_IDENT_|ID_WWN)" +} +blacklist { +} diff --git a/data/data/agent/files/root/.docker/config.json.template b/data/data/agent/files/root/.docker/config.json.template new file mode 100644 index 0000000000..82ce0734fa --- /dev/null +++ b/data/data/agent/files/root/.docker/config.json.template @@ -0,0 +1 @@ +{{.PullSecret}} \ No newline at end of file diff --git a/data/data/agent/files/root/assisted.te b/data/data/agent/files/root/assisted.te new file mode 100644 index 0000000000..648927d5d8 --- /dev/null +++ b/data/data/agent/files/root/assisted.te @@ -0,0 +1,13 @@ +module assisted 1.0; +require { + type chronyd_t; + type container_file_t; + type spc_t; + class unix_dgram_socket sendto; + class dir search; + class sock_file write; +} +#============= chronyd_t ============== +allow chronyd_t container_file_t:dir search; +allow chronyd_t container_file_t:sock_file write; +allow chronyd_t spc_t:unix_dgram_socket sendto; \ No newline at end of file diff --git a/data/data/agent/files/usr/local/bin/common.sh.template b/data/data/agent/files/usr/local/bin/common.sh.template new file mode 100644 index 0000000000..4be6ed5076 --- /dev/null +++ b/data/data/agent/files/usr/local/bin/common.sh.template @@ -0,0 +1,9 @@ +#!/bin/bash + +wait_for_assisted_service() { + echo "Waiting for assisted-service to be ready" + until $(curl --output /dev/null --silent --fail {{.ServiceBaseURL}}/api/assisted-install/v2/infra-envs); do + printf '.' + sleep 5 + done +} diff --git a/data/data/agent/files/usr/local/bin/extract-agent.sh.template b/data/data/agent/files/usr/local/bin/extract-agent.sh.template new file mode 100644 index 0000000000..2dde05c762 --- /dev/null +++ b/data/data/agent/files/usr/local/bin/extract-agent.sh.template @@ -0,0 +1,13 @@ +#!/bin/bash +set -euo pipefail + +/usr/local/bin/release-image-download.sh + +# shellcheck disable=SC1091 +. /usr/local/bin/release-image.sh + +IMAGE=$(image_for agent-installer-node-agent) + +echo "Using agent image: ${IMAGE} to copy bin" + +/usr/bin/podman run --privileged --rm -v /usr/local/bin:/hostbin ${IMAGE} cp /usr/bin/agent /hostbin diff --git a/data/data/agent/files/usr/local/bin/get-container-images.sh b/data/data/agent/files/usr/local/bin/get-container-images.sh new file mode 100644 index 0000000000..13042a511b --- /dev/null +++ b/data/data/agent/files/usr/local/bin/get-container-images.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +set -euo pipefail + +/usr/local/bin/release-image-download.sh + +# shellcheck disable=SC1091 +. /usr/local/bin/release-image.sh + +# Store images in the environment file used by services and passed to assisted-service +# The agent image will be also retrieved when its script is run +cat </usr/local/share/assisted-service/agent-images.env +SERVICE_IMAGE=$(image_for agent-installer-api-server) +CONTROLLER_IMAGE=$(image_for agent-installer-csr-approver) +INSTALLER_IMAGE=$(image_for agent-installer-orchestrator) +AGENT_DOCKER_IMAGE=$(image_for agent-installer-node-agent) +EOF diff --git a/data/data/agent/files/usr/local/bin/set-hostname.sh b/data/data/agent/files/usr/local/bin/set-hostname.sh new file mode 100644 index 0000000000..19105e35ca --- /dev/null +++ b/data/data/agent/files/usr/local/bin/set-hostname.sh @@ -0,0 +1,29 @@ +#!/bin/bash +# +# The hostnames defined in agent-config.yaml are written out +# to files at /etc/assisted/hostnames/. +# +# If a host has multiple interfaces, the host's first network +# interface's MAC address is used. +# +# This script compares the MAC addresses on the current host +# with the addresses in /etc/assisted/hostnames/. +# +# If a match is found, then the hostname in the file is set +# as this host's hostname. +# + +HOSTNAMES_PATH=/etc/assisted/hostnames +FILES=$(ls $HOSTNAMES_PATH) +for filename in $FILES +do + MATCHED_MAC_ADDRESS_WITH_HOST=$(ip address | grep $filename) + if [ "$MATCHED_MAC_ADDRESS_WITH_HOST" != "" ]; then + HOSTNAME=$(cat ${HOSTNAMES_PATH}/${filename}) + echo "Host has matching MAC address: $filename" 1>&2 + echo "Setting hostname to $HOSTNAME" 1>&2 + hostnamectl set-hostname $HOSTNAME + else + echo "MAC address, $filename, does not exist on this host" 1>&2 + fi +done diff --git a/data/data/agent/files/usr/local/bin/set-node-zero.sh.template b/data/data/agent/files/usr/local/bin/set-node-zero.sh.template new file mode 100644 index 0000000000..cdb05ee8d8 --- /dev/null +++ b/data/data/agent/files/usr/local/bin/set-node-zero.sh.template @@ -0,0 +1,30 @@ +#!/bin/bash + +set -e + +timeout=$((SECONDS + 30)) + +while [[ $SECONDS -lt $timeout ]] +do + IS_NODE_ZERO=$(ip -j address | jq '[.[].addr_info] | flatten | map(.local=="{{.NodeZeroIP}}") | any') + if [ "${IS_NODE_ZERO}" = "true" ]; then + break + fi + sleep 5 +done + +if [ "${IS_NODE_ZERO}" = "true" ]; then + echo "Node 0 IP {{.NodeZeroIP}} found on this host" 1>&2 + + NODE0_PATH=/etc/assisted-service/node0 + mkdir -p "$(dirname "${NODE0_PATH}")" + + NODE_ZERO_MAC=$(ip -j address | jq -r '.[] | select(.addr_info | map(select(.local == "{{.NodeZeroIP}}")) | any).address') + echo "MAC Address for Node 0: ${NODE_ZERO_MAC}" + + cat >"${NODE0_PATH}" <&2 echo "Waiting for infra-env-id to be available" +INFRA_ENV_ID="" +until [[ $INFRA_ENV_ID != "" && $INFRA_ENV_ID != "null" ]]; do + sleep 5 + >&2 echo "Querying assisted-service for infra-env-id..." + INFRA_ENV_ID=$(curl -s -S '{{.ServiceBaseURL}}/api/assisted-install/v2/infra-envs' | jq -r .[0].id) +done +echo "Fetched infra-env-id and found: $INFRA_ENV_ID" + +# shellcheck disable=SC1091 +. /usr/local/bin/release-image.sh + +IMAGE=$(image_for agent-installer-node-agent) + +echo "Using agent image: ${IMAGE} to start agent" + +# use infra-env-id to have agent register this host with assisted-service +exec /usr/local/bin/agent --url '{{.ServiceBaseURL}}' --infra-env-id '{{.InfraEnvID}}' --agent-version ${IMAGE} --insecure=true diff --git a/data/data/agent/files/usr/local/bin/start-cluster-installation.sh.template b/data/data/agent/files/usr/local/bin/start-cluster-installation.sh.template new file mode 100644 index 0000000000..f917d98acc --- /dev/null +++ b/data/data/agent/files/usr/local/bin/start-cluster-installation.sh.template @@ -0,0 +1,73 @@ +#!/bin/bash +set -e + +source common.sh + +wait_for_assisted_service + +BASE_URL="{{.ServiceBaseURL}}api/assisted-install/v2" + +cluster_id="" +while [[ "${cluster_id}" = "" ]] +do + # Get cluster id + cluster_id=$(curl -s -S "${BASE_URL}/clusters" | jq -r .[].id) + if [[ "${cluster_id}" = "" ]]; then + sleep 2 + fi +done + +infra_env_id="{{.InfraEnvID}}" +echo -e "\nInfra env id is $infra_env_id" 1>&2 + +required_master_nodes={{.ControlPlaneAgents}} +required_worker_nodes={{.WorkerAgents}} +total_required_nodes=$(( ${required_master_nodes}+${required_worker_nodes} )) +echo "Number of required master nodes: ${required_master_nodes}" 1>&2 +echo "Number of required worker nodes: ${required_worker_nodes}" 1>&2 +echo "Total number of required nodes: ${total_required_nodes}" 1>&2 + + +num_known_hosts() { + local known_hosts=0 + host_status=$(curl -s -S "${BASE_URL}/infra-envs/${infra_env_id}/hosts" | jq -r .[].status) + if [[ -n ${host_status} ]]; then + for status in ${host_status}; do + if [[ "${status}" == "known" ]]; then + ((known_hosts+=1)) + echo "Hosts known and ready for cluster installation (${known_hosts}/${total_required_nodes})" 1>&2 + fi + done + fi + echo "${known_hosts}" +} + +while [[ "${total_required_nodes}" != $(num_known_hosts) ]] +do + echo "Waiting for hosts to become ready for cluster installation..." 1>&2 + sleep 10 +done + +echo "All ${total_required_nodes} hosts are ready." 1>&2 + +if [[ "${APIVIP}" != "" ]]; then + api_vip=$(curl -s -S "${BASE_URL}/clusters" | jq -r .[].api_vip) + if [ "${api_vip}" == null ]; then + echo "Setting api vip" 1>&2 + curl -s -S -X PATCH "${BASE_URL}/clusters/${cluster_id}" -H "Content-Type: application/json" -d '{"api_vip": "{{.APIVIP}}"}' + fi +fi + +while [[ "${cluster_status}" != "ready" ]] +do + cluster_status=$(curl -s -S "${BASE_URL}/clusters" | jq -r .[].status) + echo "Cluster status: ${cluster_status}" 1>&2 + sleep 5 + if [[ "${cluster_status}" == "ready" ]]; then + echo "Starting cluster installation..." 1>&2 + curl -s -S -X POST "${BASE_URL}/clusters/${cluster_id}/actions/install" \ + -H 'accept: application/json' \ + -d '' + echo "Cluster installation started" 1>&2 + fi +done diff --git a/data/data/agent/files/usr/local/bin/wait-for-assisted-service.sh b/data/data/agent/files/usr/local/bin/wait-for-assisted-service.sh new file mode 100644 index 0000000000..a35f1fec0e --- /dev/null +++ b/data/data/agent/files/usr/local/bin/wait-for-assisted-service.sh @@ -0,0 +1,6 @@ +#!/bin/bash +set -e + +source common.sh + +wait_for_assisted_service diff --git a/data/data/agent/files/usr/local/share/assisted-service/assisted-db.env b/data/data/agent/files/usr/local/share/assisted-service/assisted-db.env new file mode 100644 index 0000000000..521e8cc702 --- /dev/null +++ b/data/data/agent/files/usr/local/share/assisted-service/assisted-db.env @@ -0,0 +1,3 @@ +POSTGRESQL_DATABASE=installer +POSTGRESQL_PASSWORD=admin +POSTGRESQL_USER=admin diff --git a/data/data/agent/files/usr/local/share/assisted-service/assisted-service.env.template b/data/data/agent/files/usr/local/share/assisted-service/assisted-service.env.template new file mode 100644 index 0000000000..84729a8370 --- /dev/null +++ b/data/data/agent/files/usr/local/share/assisted-service/assisted-service.env.template @@ -0,0 +1,21 @@ +AUTH_TYPE=none +DB_HOST=127.0.0.1 +DB_NAME=installer +DB_PASS=admin +DB_PORT=5432 +DB_USER=admin +DEPLOY_TARGET=onprem +DISK_ENCRYPTION_SUPPORT=true +DUMMY_IGNITION=false +ENABLE_SINGLE_NODE_DNSMASQ=true +EPHEMERAL_INSTALLER_CLUSTER_TLS_CERTS_OVERRIDE_DIR=/opt/agent/tls +HW_VALIDATOR_REQUIREMENTS=[{"version":"default","master":{"cpu_cores":4,"ram_mib":16384,"disk_size_gb":120,"installation_disk_speed_threshold_ms":10,"network_latency_threshold_ms":100,"packet_loss_percentage":0},"worker":{"cpu_cores":2,"ram_mib":8192,"disk_size_gb":120,"installation_disk_speed_threshold_ms":10,"network_latency_threshold_ms":1000,"packet_loss_percentage":10},"sno":{"cpu_cores":8,"ram_mib":16384,"disk_size_gb":120,"installation_disk_speed_threshold_ms":10}}] +IMAGE_SERVICE_BASE_URL=http://{{.NodeZeroIP}}:8888 +IPV6_SUPPORT=true +NTP_DEFAULT_SERVER= +PUBLIC_CONTAINER_REGISTRIES=quay.io +RELEASE_IMAGES={{.ReleaseImages}} +OPENSHIFT_INSTALL_RELEASE_IMAGE_MIRROR={{.ReleaseImageMirror}} +SERVICE_BASE_URL={{.ServiceBaseURL}} +STORAGE=filesystem +INFRA_ENV_ID={{.InfraEnvID}} diff --git a/data/data/agent/files/usr/local/share/assisted-service/images.env.template b/data/data/agent/files/usr/local/share/assisted-service/images.env.template new file mode 100644 index 0000000000..ae5c44a0a6 --- /dev/null +++ b/data/data/agent/files/usr/local/share/assisted-service/images.env.template @@ -0,0 +1,3 @@ +ASSISTED_SERVICE_HOST={{.AssistedServiceHost}} +ASSISTED_SERVICE_SCHEME={{.ServiceProtocol}} +OS_IMAGES=[{"openshift_version":"4.10","cpu_architecture":"x86_64","url":"https://mirror.openshift.com/pub/openshift-v4/dependencies/rhcos/pre-release/4.10.0-rc.1/rhcos-4.10.0-rc.1-x86_64-live.x86_64.iso","rootfs_url":"https://mirror.openshift.com/pub/openshift-v4/dependencies/rhcos/pre-release/4.10.0-rc.1/rhcos-4.10.0-rc.1-x86_64-live-rootfs.x86_64.img","version":"410.84.202201251210-0"},{"openshift_version":"4.11","cpu_architecture":"x86_64","url":"https://rhcos-redirector.apps.art.xq1c.p1.openshiftapps.com/art/storage/releases/rhcos-4.11/411.85.202203181601-0/x86_64/rhcos-411.85.202203181601-0-live.x86_64.iso","rootfs_url":"https://rhcos-redirector.apps.art.xq1c.p1.openshiftapps.com/art/storage/releases/rhcos-4.11/411.85.202203181601-0/x86_64/rhcos-411.85.202203181601-0-live-rootfs.x86_64.img","version":"411.85.202203181601-0"}] diff --git a/data/data/agent/systemd/units/agent.service.template b/data/data/agent/systemd/units/agent.service.template new file mode 100644 index 0000000000..51bcf4b0b4 --- /dev/null +++ b/data/data/agent/systemd/units/agent.service.template @@ -0,0 +1,24 @@ +[Service] +Type=simple +Restart=always +RestartSec=3 +StartLimitInterval=0 +Environment=HTTP_PROXY= +Environment=http_proxy= +Environment=HTTPS_PROXY= +Environment=https_proxy= +Environment=NO_PROXY= +Environment=no_proxy= +# TODO: If AUTH_TYPE != none, then PULL_SECRET_TOKEN needs to be updated +# https://github.com/openshift/assisted-service/blob/master/internal/ignition/ignition.go#L1381 +Environment=PULL_SECRET_TOKEN={{.PullSecretToken}} +TimeoutStartSec=3000 +ExecStartPre=/usr/local/bin/extract-agent.sh +ExecStart=/usr/local/bin/start-agent.sh + +[Unit] +Wants=network-online.target set-hostname.service +After=network-online.target set-hostname.service + +[Install] +WantedBy=multi-user.target diff --git a/data/data/agent/systemd/units/apply-host-config.service.template b/data/data/agent/systemd/units/apply-host-config.service.template new file mode 100644 index 0000000000..f078b4fe67 --- /dev/null +++ b/data/data/agent/systemd/units/apply-host-config.service.template @@ -0,0 +1,26 @@ +[Unit] +Description=Service that applies host-specific configuration +Wants=network-online.target +Requires=create-cluster-and-infraenv.service +PartOf=assisted-service-pod.service +After=network-online.target create-cluster-and-infraenv.service +ConditionPathExists=/etc/assisted-service/node0 + +[Service] +Environment=PODMAN_SYSTEMD_UNIT=%n +Environment=SERVICE_BASE_URL={{.ServiceBaseURL}} +Environment=INFRA_ENV_ID={{.InfraEnvID}} +EnvironmentFile=/usr/local/share/assisted-service/agent-images.env +ExecStartPre=/bin/rm -f %t/%n.ctr-id +ExecStartPre=/bin/mkdir -p %t/agent-installer /etc/assisted/hostconfig +ExecStartPre=/usr/local/bin/wait-for-assisted-service.sh +ExecStart=podman run --cidfile=%t/%n.ctr-id --cgroups=no-conmon --log-driver=journald --restart=on-failure:10 --pod-id-file=%t/assisted-service-pod.pod-id --replace --name=apply-host-config -v /etc/assisted/hostconfig:/etc/assisted/hostconfig -v %t/agent-installer:/var/run/agent-installer:z --env SERVICE_BASE_URL --env INFRA_ENV_ID $SERVICE_IMAGE /usr/local/bin/agent-installer-client configure +ExecStop=/usr/bin/podman stop --ignore --cidfile=%t/%n.ctr-id +ExecStopPost=/usr/bin/podman rm -f --ignore --cidfile=%t/%n.ctr-id + +KillMode=none +Type=oneshot +RemainAfterExit=true + +[Install] +WantedBy=multi-user.target diff --git a/data/data/agent/systemd/units/assisted-service-db.service b/data/data/agent/systemd/units/assisted-service-db.service new file mode 100644 index 0000000000..088c0d2efa --- /dev/null +++ b/data/data/agent/systemd/units/assisted-service-db.service @@ -0,0 +1,21 @@ +[Unit] +Description=Assisted Service database +Wants=network.target +RequiresMountsFor=%t/containers +BindsTo=assisted-service-pod.service +After=network-online.target assisted-service-pod.service + +[Service] +Environment=PODMAN_SYSTEMD_UNIT=%n +EnvironmentFile=/usr/local/share/assisted-service/agent-images.env +Restart=on-failure +TimeoutStopSec=300 +ExecStartPre=/bin/rm -f %t/%n.ctr-id +ExecStart=/usr/bin/podman run --user=postgres --cidfile=%t/%n.ctr-id --cgroups=no-conmon --log-driver=journald --rm --pod-id-file=%t/assisted-service-pod.pod-id --sdnotify=conmon --replace -d --name=assisted-db --env-file=/usr/local/share/assisted-service/assisted-db.env $SERVICE_IMAGE /bin/bash start_db.sh +ExecStop=/usr/bin/podman stop --ignore --cidfile=%t/%n.ctr-id +ExecStopPost=/usr/bin/podman rm -f --ignore --cidfile=%t/%n.ctr-id +Type=notify +NotifyAccess=all + +[Install] +WantedBy=multi-user.target diff --git a/data/data/agent/systemd/units/assisted-service-pod.service b/data/data/agent/systemd/units/assisted-service-pod.service new file mode 100644 index 0000000000..6e0035d337 --- /dev/null +++ b/data/data/agent/systemd/units/assisted-service-pod.service @@ -0,0 +1,24 @@ +[Unit] +Description=Assisted Service pod +Wants=network.target node-zero.service +After=network-online.target node-zero.service +ConditionPathExists=/etc/assisted-service/node0 +RequiresMountsFor= +Requires=assisted-service-db.service assisted-service.service +Before=assisted-service-db.service assisted-service.service + +[Service] +Environment=PODMAN_SYSTEMD_UNIT=%n +Restart=on-failure +TimeoutStopSec=70 +ExecStartPre=/bin/rm -f %t/%n.pid %t/%N.pod-id +ExecStartPre=/usr/bin/podman pod create --infra-conmon-pidfile %t/%n.pid --pod-id-file %t/%N.pod-id -n assisted-service --publish=8090:8090 --publish=8080:8080 --publish=8888:8888 +ExecStartPre=/usr/local/bin/get-container-images.sh +ExecStart=/usr/bin/podman pod start --pod-id-file=%t/%N.pod-id +ExecStop=/usr/bin/podman pod stop --ignore --pod-id-file=%t/%N.pod-id -t 10 +ExecStopPost=/usr/bin/podman pod rm --ignore -f --pod-id-file=%t/%N.pod-id +PIDFile=%t/%n.pid +Type=forking + +[Install] +WantedBy=multi-user.target diff --git a/data/data/agent/systemd/units/assisted-service.service.template b/data/data/agent/systemd/units/assisted-service.service.template new file mode 100644 index 0000000000..32343d41d2 --- /dev/null +++ b/data/data/agent/systemd/units/assisted-service.service.template @@ -0,0 +1,22 @@ +[Unit] +Description=Assisted Service container +Wants=network.target +RequiresMountsFor=%t/containers +Requires=assisted-service-db.service +BindsTo=assisted-service-pod.service +After=network-online.target assisted-service-pod.service + +[Service] +Environment=PODMAN_SYSTEMD_UNIT=%n +EnvironmentFile=/usr/local/share/assisted-service/agent-images.env +Restart=on-failure +TimeoutStopSec=300 +ExecStartPre=/bin/rm -f %t/%n.ctr-id +ExecStart=/usr/bin/podman run --cidfile=%t/%n.ctr-id --cgroups=no-conmon --log-driver=journald --rm --pod-id-file=%t/assisted-service-pod.pod-id --sdnotify=conmon --replace -d --name=service -v /opt/agent/tls:/opt/agent/tls:z {{ if .HaveMirrorConfig }}-v /etc/containers:/etc/containers{{ end }} -v /etc/pki/ca-trust:/etc/pki/ca-trust --env-file=/usr/local/share/assisted-service/assisted-service.env --env-file=/usr/local/share/assisted-service/images.env --env-file=/etc/assisted-service/node0 --env-file=/usr/local/share/assisted-service/agent-images.env $SERVICE_IMAGE +ExecStop=/usr/bin/podman stop --ignore --cidfile=%t/%n.ctr-id +ExecStopPost=/usr/bin/podman rm -f --ignore --cidfile=%t/%n.ctr-id +Type=notify +NotifyAccess=all + +[Install] +WantedBy=multi-user.target diff --git a/data/data/agent/systemd/units/create-cluster-and-infraenv.service.template b/data/data/agent/systemd/units/create-cluster-and-infraenv.service.template new file mode 100644 index 0000000000..91f054335c --- /dev/null +++ b/data/data/agent/systemd/units/create-cluster-and-infraenv.service.template @@ -0,0 +1,25 @@ +[Unit] +Description=Service that creates initial cluster and infraenv +Wants=network-online.target +Requires=assisted-service.service +PartOf=assisted-service-pod.service +After=network-online.target assisted-service.service +ConditionPathExists=/etc/assisted-service/node0 + +[Service] +Environment=PODMAN_SYSTEMD_UNIT=%n +Environment=SERVICE_BASE_URL={{.ServiceBaseURL}} +Environment=OPENSHIFT_INSTALL_RELEASE_IMAGE_MIRROR={{.ReleaseImageMirror}} +EnvironmentFile=/usr/local/share/assisted-service/agent-images.env +ExecStartPre=/bin/rm -f %t/%n.ctr-id +ExecStartPre=/usr/local/bin/wait-for-assisted-service.sh +ExecStart=podman run --cidfile=%t/%n.ctr-id --cgroups=no-conmon --log-driver=journald --rm --pod-id-file=%t/assisted-service-pod.pod-id --replace --name=create-cluster-and-infraenv -v /etc/assisted/manifests:/manifests -v /etc/assisted/extra-manifests:/extra-manifests -v /etc/pki/ca-trust:/etc/pki/ca-trust:z --env SERVICE_BASE_URL --env OPENSHIFT_INSTALL_RELEASE_IMAGE_MIRROR $SERVICE_IMAGE /usr/local/bin/agent-installer-client register +ExecStop=/usr/bin/podman stop --ignore --cidfile=%t/%n.ctr-id +ExecStopPost=/usr/bin/podman rm -f --ignore --cidfile=%t/%n.ctr-id + +KillMode=none +Type=oneshot +RemainAfterExit=true + +[Install] +WantedBy=multi-user.target diff --git a/data/data/agent/systemd/units/node-zero.service b/data/data/agent/systemd/units/node-zero.service new file mode 100644 index 0000000000..41171f6331 --- /dev/null +++ b/data/data/agent/systemd/units/node-zero.service @@ -0,0 +1,12 @@ +[Unit] +Description=Identify node zero to run OpenShift Assisted Installation Service on +Wants=network-online.target +After=network-online.target + +[Service] +Type=oneshot +RemainAfterExit=True +ExecStart=/usr/local/bin/set-node-zero.sh + +[Install] +WantedBy=multi-user.target diff --git a/data/data/agent/systemd/units/pre-network-manager-config.service b/data/data/agent/systemd/units/pre-network-manager-config.service new file mode 100644 index 0000000000..f70e043db2 --- /dev/null +++ b/data/data/agent/systemd/units/pre-network-manager-config.service @@ -0,0 +1,18 @@ +[Unit] +Description=Prepare network manager config content +Before=dracut-initqueue.service +After=dracut-cmdline.service +DefaultDependencies=no + +[Service] +User=root +ExecStart=/usr/local/bin/pre-network-manager-config.sh + +TimeoutSec=60 +KillMode=none +Type=oneshot +PrivateTmp=true +RemainAfterExit=no + +[Install] +WantedBy=multi-user.target diff --git a/data/data/agent/systemd/units/selinux.service b/data/data/agent/systemd/units/selinux.service new file mode 100644 index 0000000000..ab45cbd71f --- /dev/null +++ b/data/data/agent/systemd/units/selinux.service @@ -0,0 +1,8 @@ +[Service] +Type=oneshot +ExecStartPre=checkmodule -M -m -o /root/assisted.mod /root/assisted.te +ExecStartPre=semodule_package -o /root/assisted.pp -m /root/assisted.mod +ExecStart=semodule -i /root/assisted.pp + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/data/data/agent/systemd/units/set-hostname.service b/data/data/agent/systemd/units/set-hostname.service new file mode 100644 index 0000000000..8d44e30130 --- /dev/null +++ b/data/data/agent/systemd/units/set-hostname.service @@ -0,0 +1,14 @@ +[Unit] +Description=Agent-based installer hostname update service +Wants=network-online.target +After=local-fs.target + +[Service] +ExecStart=/usr/local/bin/set-hostname.sh + +KillMode=none +Type=oneshot +RemainAfterExit=true + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/data/data/agent/systemd/units/start-cluster-installation.service b/data/data/agent/systemd/units/start-cluster-installation.service new file mode 100644 index 0000000000..a077b06151 --- /dev/null +++ b/data/data/agent/systemd/units/start-cluster-installation.service @@ -0,0 +1,17 @@ +[Unit] +Description=Service that starts cluster installation +Wants=network-online.target +Requires=apply-host-config.service +PartOf=assisted-service-pod.service +After=network-online.target apply-host-config.service +ConditionPathExists=/etc/assisted-service/node0 + +[Service] +ExecStart=/usr/local/bin/start-cluster-installation.sh + +KillMode=none +Type=oneshot +RemainAfterExit=true + +[Install] +WantedBy=multi-user.target diff --git a/go.mod b/go.mod index e2d63ac94d..040249f6e2 100644 --- a/go.mod +++ b/go.mod @@ -24,17 +24,18 @@ require ( github.com/aws/aws-sdk-go v1.43.19 github.com/clarketm/json v1.14.1 github.com/containers/image v3.0.2+incompatible - github.com/coreos/ignition/v2 v2.9.0 + github.com/coreos/ignition/v2 v2.13.0 github.com/coreos/stream-metadata-go v0.1.8 github.com/form3tech-oss/jwt-go v3.2.3+incompatible github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 + github.com/go-openapi/strfmt v0.21.2 github.com/go-playground/validator/v10 v10.2.0 github.com/go-yaml/yaml v2.1.0+incompatible github.com/golang-jwt/jwt v3.2.2+incompatible github.com/golang/mock v1.6.0 github.com/golang/protobuf v1.5.2 github.com/google/go-cmp v0.5.8 - github.com/google/uuid v1.2.0 + github.com/google/uuid v1.3.0 github.com/gophercloud/gophercloud v0.24.0 github.com/gophercloud/utils v0.0.0-20220307143606-8e7800759d16 github.com/h2non/filetype v1.0.12 @@ -45,17 +46,21 @@ require ( github.com/metal3-io/baremetal-operator/apis v0.0.0 github.com/metal3-io/baremetal-operator/pkg/hardwareutils v0.0.0 github.com/nutanix-cloud-native/prism-go-client v0.2.0 - github.com/openshift/api v0.0.0-20220823143838-5768cc618ba0 + github.com/openshift/api v3.9.1-0.20191111211345-a27ff30ebf09+incompatible + github.com/openshift/assisted-image-service v0.0.0-20220307202600-054a1afa8d28 + github.com/openshift/assisted-service v1.0.10-0.20220223093655-7ada9949bf1d github.com/openshift/client-go v0.0.0-20211209144617-7385dd6338e3 github.com/openshift/cloud-credential-operator v0.0.0-20200316201045-d10080b52c9e github.com/openshift/cluster-api-provider-baremetal v0.0.0-20220408122422-7a548effc26e github.com/openshift/cluster-api-provider-ibmcloud v0.0.1-0.20220201105455-8014e5e894b0 github.com/openshift/cluster-api-provider-libvirt v0.2.1-0.20191219173431-2336783d4603 github.com/openshift/cluster-api-provider-ovirt v0.1.1-0.20220323121149-e3f2850dd519 + github.com/openshift/hive/apis v0.0.0-20210506000654-5c038fb05190 github.com/openshift/library-go v0.0.0-20220121154930-b7889002d63e github.com/openshift/machine-config-operator v0.0.0 - github.com/ovirt/go-ovirt v0.0.0-20210308100159-ac0bcbc88d7c + github.com/ovirt/go-ovirt v0.0.0-20210809163552-d4276e35d3db github.com/pborman/uuid v1.2.0 + github.com/pelletier/go-toml v1.9.3 github.com/pkg/errors v0.9.1 github.com/pkg/sftp v1.10.1 github.com/prometheus/client_golang v1.12.1 @@ -64,8 +69,9 @@ require ( github.com/sirupsen/logrus v1.8.1 github.com/spf13/cobra v1.5.0 github.com/stretchr/testify v1.7.2 - github.com/ulikunitz/xz v0.5.8 - github.com/vincent-petithory/dataurl v0.0.0-20191104211930-d1553a71de50 + github.com/thedevsaddam/retry v0.0.0-20200324223450-9769a859cc6d + github.com/ulikunitz/xz v0.5.10 + github.com/vincent-petithory/dataurl v1.0.0 github.com/vmware/govmomi v0.27.4 golang.org/x/crypto v0.0.0-20220214200702-86341886e292 golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 @@ -74,7 +80,7 @@ require ( google.golang.org/api v0.44.0 google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368 google.golang.org/grpc v1.46.0 - gopkg.in/ini.v1 v1.66.2 + gopkg.in/ini.v1 v1.66.4 gopkg.in/yaml.v2 v2.4.0 k8s.io/api v0.24.3 k8s.io/apiextensions-apiserver v0.24.3 @@ -131,13 +137,15 @@ require ( github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f // indirect github.com/beorn7/perks v1.0.1 // indirect + github.com/cavaliercoder/go-cpio v0.0.0-20180626203310-925f9528c45e // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/coreos/go-semver v0.3.0 // indirect github.com/coreos/go-systemd/v22 v22.3.2 // indirect - github.com/coreos/vcontext v0.0.0-20201120045928-b0e13dab675c // indirect + github.com/coreos/vcontext v0.0.0-20211021162308-f1dbbca7bef4 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dimchansky/utfbom v1.1.0 // indirect - github.com/emicklei/go-restful v2.10.0+incompatible // indirect + github.com/diskfs/go-diskfs v1.2.1-0.20210727185522-a769efacd235 // indirect + github.com/emicklei/go-restful v2.14.2+incompatible // indirect github.com/evanphx/json-patch v4.12.0+incompatible // indirect github.com/fatih/color v1.13.0 // indirect github.com/fsnotify/fsnotify v1.5.1 // indirect @@ -149,7 +157,6 @@ require ( github.com/go-openapi/loads v0.21.1 // indirect github.com/go-openapi/runtime v0.23.0 // indirect github.com/go-openapi/spec v0.20.4 // indirect - github.com/go-openapi/strfmt v0.21.2 // indirect github.com/go-openapi/swag v0.21.1 // indirect github.com/go-openapi/validate v0.20.3 // indirect github.com/go-playground/locales v0.14.0 // indirect @@ -161,7 +168,9 @@ require ( github.com/google/gnostic v0.5.7-v3refs // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/googleapis/gax-go/v2 v2.0.5 // indirect + github.com/hashicorp/errwrap v1.0.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-retryablehttp v0.7.0 // indirect github.com/hashicorp/go-version v1.5.0 // indirect github.com/hashicorp/logutils v1.0.0 // indirect @@ -169,6 +178,8 @@ require ( github.com/hashicorp/terraform-plugin-sdk/v2 v2.17.0 // indirect github.com/imdario/mergo v0.3.12 // indirect github.com/inconshreveable/mousetrap v1.0.1 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.2 // 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 @@ -188,16 +199,21 @@ require ( github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/oklog/ulid v1.3.1 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/opencontainers/image-spec v1.0.2-0.20190823105129-775207bd45b6 // indirect + github.com/opencontainers/image-spec v1.0.2 // indirect github.com/openshift/cluster-api v0.0.0-20190805113604-f8de78af80fc // indirect + github.com/openshift/custom-resource-status v1.1.0 // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect + github.com/pierrec/lz4 v2.3.0+incompatible // indirect + github.com/pkg/xattr v0.4.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/procfs v0.7.3 // indirect - github.com/satori/go.uuid v1.2.0 // indirect golang.org/x/net v0.0.0-20220812174116-3211cb980234 // indirect + golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect + gopkg.in/djherbis/times.v1 v1.2.0 // indirect gopkg.in/gcfg.v1 v1.2.3 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect + gorm.io/gorm v1.22.3 // indirect ) // OpenShift Forks @@ -207,6 +223,7 @@ replace ( github.com/metal3-io/baremetal-operator/pkg/hardwareutils => github.com/openshift/baremetal-operator/pkg/hardwareutils v0.0.0-20220128094204-28771f489634 k8s.io/cloud-provider-vsphere => github.com/openshift/cloud-provider-vsphere v1.19.1-0.20211222185833-7829863d0558 sigs.k8s.io/cluster-api => sigs.k8s.io/cluster-api v0.4.5 + sigs.k8s.io/cluster-api-provider-aws => github.com/openshift/cluster-api-provider-aws v0.2.1-0.20200929152424-eab2e087f366 // Indirect dependency through MAO from cluster API providers sigs.k8s.io/cluster-api-provider-azure => github.com/openshift/cluster-api-provider-azure v0.1.0-alpha.3.0.20210626224711-5d94c794092f // Indirect dependency through MAO from cluster API providers sigs.k8s.io/cluster-api-provider-openstack => github.com/openshift/cluster-api-provider-openstack v0.0.0-20211111204942-611d320170af ) @@ -220,3 +237,8 @@ replace k8s.io/client-go => k8s.io/client-go v0.24.0 // Needed so that the InstallConfig CRD can be created. Later versions of controller-gen balk at using IPNet as a field. replace sigs.k8s.io/controller-tools => sigs.k8s.io/controller-tools v0.3.1-0.20200617211605-651903477185 + +// Override the OpenShift API version in hive +replace github.com/openshift/api => github.com/openshift/api v0.0.0-20220823143838-5768cc618ba0 + +replace github.com/terraform-providers/terraform-provider-nutanix => github.com/nutanix/terraform-provider-nutanix v1.5.0 diff --git a/go.sum b/go.sum index 12a711984f..fda21103cf 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +4d63.com/gochecknoinits v0.0.0-20200108094044-eb73b47b9fc4/go.mod h1:4o1i5aXtIF5tJFt3UD1knCVmWOXg7fLYdHVu6jeNcnM= +bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= @@ -41,6 +43,8 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.9.0/go.mod h1:m+/etGaqZbylxaNT876QGXqEHp4PR2Rq5GMqICWb9bU= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +contrib.go.opencensus.io/exporter/prometheus v0.1.0/go.mod h1:cGFniUXGZlKRjzOyuZJ6mgB+PgBcCIa79kEKR8YCW+A= +contrib.go.opencensus.io/exporter/prometheus v0.2.0/go.mod h1:TYmVAyE8Tn1lyPcltF5IYYfWp2KHu7lQGIZnj8iZMys= dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU= @@ -50,6 +54,7 @@ git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGy github.com/14rcole/gopopulate v0.0.0-20180821133914-b175b219e774/go.mod h1:6/0dYRLLXyJjbkIPeeGyoJ/eKOSI0eU6eTlCBYibgd0= github.com/AlecAivazis/survey/v2 v2.3.5 h1:A8cYupsAZkjaUmhtTYv3sSqc7LO5mp1XDfqe5E/9wRQ= github.com/AlecAivazis/survey/v2 v2.3.5/go.mod h1:4AuI9b7RjAR+G7v9+C4YSlX/YL3K3cWNXgWXOhllqvI= +github.com/Azure/azure-sdk-for-go v48.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/azure-sdk-for-go v51.2.0+incompatible h1:qQNk//OOHK0GZcgMMgdJ4tZuuh0zcOeUkpTxjvKFpSQ= github.com/Azure/azure-sdk-for-go v51.2.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/azure-sdk-for-go/sdk/azcore v0.21.1 h1:qoVeMsc9/fh/yhxVaA0obYjVH/oI/ihrOoMwsLS9KSA= @@ -61,15 +66,18 @@ github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.3.0 h1:Px2UA+2RvSSvv+RvJ github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.3.0/go.mod h1:tPaiy8S5bQ+S5sOiDlINkp7+Ef339+Nz5L5XO+cnOHo= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/Azure/go-ansiterm v0.0.0-20210608223527-2377c96fe795/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= github.com/Azure/go-autorest/autorest v0.9.2/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= +github.com/Azure/go-autorest/autorest v0.11.12/go.mod h1:eipySxLmqSyC5s5k1CLupqet0PSENBEDP93LQ9a8QYw= github.com/Azure/go-autorest/autorest v0.11.18 h1:90Y4srNYrwOtAgVo3ndrQkTYn6kf1Eg/AjTFJ8Is2aM= github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= github.com/Azure/go-autorest/autorest/adal v0.8.0/go.mod h1:Z6vX6WXXuyieHAXwMj0S6HY6e6wcHn37qQMBQlvY3lc= +github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= github.com/Azure/go-autorest/autorest/adal v0.9.13 h1:Mp5hbtOePIzM8pJVRa3YLrWWmZtoxRXqUEzCfJt3+/Q= github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= github.com/Azure/go-autorest/autorest/azure/auth v0.4.1 h1:VDSqmaEc8ECZdfavoa1KmVpIVTGTc+v/2jvHGmCYvSE= @@ -85,11 +93,13 @@ github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxB github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM= github.com/Azure/go-autorest/autorest/mocks v0.4.1 h1:K0laFcLE6VLTOwNgSxaGbUcLPuGXlNkbVvq4cW4nIHk= github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= +github.com/Azure/go-autorest/autorest/to v0.3.0/go.mod h1:MgwOyqaIuKdG4TL/2ywSsIWKAfJfgHDo8ObuUk3t5sA= github.com/Azure/go-autorest/autorest/to v0.4.0 h1:oXVqrxakqqV1UZdSazDOPOLvOIz+XA683u8EctwboHk= github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE= github.com/Azure/go-autorest/autorest/validation v0.3.1 h1:AgyqjAd94fwNAoTjl/WQXg4VvFeRFpO+UhNyRXqF1ac= github.com/Azure/go-autorest/autorest/validation v0.3.1/go.mod h1:yhLgjC0Wda5DYXl6JAsWyUe4KVNffhoDhG0zVzUMo3E= github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= +github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg= github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= @@ -117,20 +127,26 @@ github.com/IBM/platform-services-go-sdk v0.18.16/go.mod h1:awc7TZUeGMlToSeMSaWEz github.com/IBM/vpc-go-sdk v0.20.0 h1:xetXFYv/GDSOVTm2h7MSki2D9x2dpNsiwHVRmdSIrPc= github.com/IBM/vpc-go-sdk v0.20.0/go.mod h1:YPyIfI+/qhPqlYp+I7dyx2U1GLcXgp/jzVvsZfUH4y8= github.com/InVisionApp/go-health v2.1.0+incompatible/go.mod h1:/+Gv1o8JUsrjC6pi6MN6/CgKJo4OqZ6x77XAnImrzhg= +github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E= +github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/Masterminds/sprig v2.20.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= -github.com/Microsoft/go-winio v0.5.0 h1:Elr9Wn+sGKPlkaBvwu4mTrxtmOp3F3yV9qhaHbXGjwU= +github.com/Microsoft/go-winio v0.5.1 h1:aPJp2QD7OOrhO5tQXqQoGSJc+DjDtWTGLOmNyAm6FgY= +github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/hcsshim v0.8.9/go.mod h1:5692vkUqntj1idxauYlpoINNKeqCiG6Sg38RRsjT5y8= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s= github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w= +github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= +github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OpenPeeDeeP/depguard v1.0.0/go.mod h1:7/4sitnI9YlQgTLLk734QlzXT8DuHVnAyztLplQjk+o= github.com/OpenPeeDeeP/depguard v1.0.1/go.mod h1:xsIw86fROiiwelg+jB2uM9PiKihMMmUx/1V+TNhjQvM= @@ -141,6 +157,7 @@ github.com/PaesslerAG/jsonpath v0.1.1 h1:c1/AToHQMVsduPAa4Vh6xp2U0evy4t8SWp8imEs github.com/PaesslerAG/jsonpath v0.1.1/go.mod h1:lVboNxFGal/VwW6d9JzIy56bUsYAP6tH/x80vjnCseY= github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7 h1:YoJbenK9C67SkzkDfmQuVln04ygHj3vjZfd9FL+GmQQ= github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= +github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= @@ -148,12 +165,16 @@ github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbt github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= +github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA= +github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= github.com/a8m/tree v0.0.0-20210115125333-10a5fd5b637d/go.mod h1:FSdwKX97koS5efgm8WevNf7XS3PqtyFkKDDXrz778cg= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk= github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= +github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/agext/levenshtein v1.2.2/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= @@ -163,15 +184,20 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d h1:UQZhZ2O0vMHr2cI+DC1Mbh0TJxzA3RcLoMsFw+aXw7E= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= github.com/aliyun/alibaba-cloud-sdk-go v1.61.1264 h1:67Ky9Wy6qmYRBrS9DRxlXRYgosNnb/4uEDLu2H554JU= github.com/aliyun/alibaba-cloud-sdk-go v1.61.1264/go.mod h1:9CMdKNL3ynIGPpfTcdwTvIm8SGuAZYYC4jFVSSvE1YQ= github.com/aliyun/aliyun-oss-go-sdk v2.1.8+incompatible h1:hLUNPbx10wawWW7DeNExvTrlb90db3UnnNTFKHZEFhE= github.com/aliyun/aliyun-oss-go-sdk v2.1.8+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= +github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20210826220005-b48c857c3a0e/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY= +github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apparentlymart/go-cidr v1.0.0/go.mod h1:EBcsNrHc3zQeuaeCeCtQruQm+n9/YjEn/vI25Lg7Gwc= github.com/apparentlymart/go-cidr v1.1.0 h1:2mAhrMoF+nhXqxTzSZMUzDHkLjmIHC+Zzn4tdgBZjnU= github.com/apparentlymart/go-cidr v1.1.0/go.mod h1:EBcsNrHc3zQeuaeCeCtQruQm+n9/YjEn/vI25Lg7Gwc= @@ -185,7 +211,9 @@ github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hC github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= @@ -196,11 +224,17 @@ github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:W github.com/ashcrow/osrelease v0.0.0-20180626175927-9b292693c55c/go.mod h1:BRljTyotlu+6N+Qlu5MhjxpdmccCnp9lDvZjNNV8qr4= github.com/awalterschulze/gographviz v0.0.0-20190522210029-fa59802746ab h1:+cdNqtOJWjvepyhxy23G7z7vmpYCoC65AP0nqi1f53s= github.com/awalterschulze/gographviz v0.0.0-20190522210029-fa59802746ab/go.mod h1:GEV5wmg4YquNw7v1kkyoX9etIk8yVmXj+AkDHuuETHs= +github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= +github.com/aws/aws-sdk-go v1.15.66/go.mod h1:E3/ieXAlvM0XWO57iftYVDLLvQ824smPP3ATZkfNZeM= github.com/aws/aws-sdk-go v1.19.11/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.30.28/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= +github.com/aws/aws-sdk-go v1.32.6/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= github.com/aws/aws-sdk-go v1.34.28/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48= github.com/aws/aws-sdk-go v1.43.19 h1:n7YAreaCpcstusW7F0+XiocZxh7rwmcAPO4HTEPJ6mE= github.com/aws/aws-sdk-go v1.43.19/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= +github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= +github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f h1:ZNv7On9kyUzm7fvRZumSyy/IUiSC7AzL0I1jKKtwooA= github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc= github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= @@ -210,26 +244,42 @@ github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+Ce github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= +github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= +github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= +github.com/cavaliercoder/go-cpio v0.0.0-20180626203310-925f9528c45e h1:hHg27A0RSSp2Om9lubZpiMgVbvn39bsUmW9U5h0twqc= +github.com/cavaliercoder/go-cpio v0.0.0-20180626203310-925f9528c45e/go.mod h1:oDpT4efm8tSYHXV5tHSdRvBet/b/QzxZ+XyyPehvm3A= +github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= +github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= +github.com/cenkalti/backoff/v4 v4.0.0/go.mod h1:eEew/i+1Q6OrCDZh3WiXYv3+nJwBASZ8Bog/87DQnVg= +github.com/cenkalti/backoff/v4 v4.1.2 h1:6Yo7N8UP2K6LWZnW94DLVSSrbobcWdVzAYOisuDPIFo= +github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/certifi/gocertifi v0.0.0-20180905225744-ee1a9a0726d2/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4= github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.0/go.mod h1:dgIUBU3pDso/gPgZ1osOZ0iQf77oPR28Tjxl5dIMyVM= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5/go.mod h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw= +github.com/checkpoint-restore/go-criu/v4 v4.0.2/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw= +github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/cilium/ebpf v0.0.0-20200507155900-a9f01edf17e3/go.mod h1:XT+cAw5wfvsodedcijoh1l9cf7v1x9FlFB/3VmF/O8s= +github.com/cilium/ebpf v0.6.2/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= github.com/clarketm/json v1.14.1 h1:43bkbTTKKdDx7crs3WHzkrnH6S1EvAF1VZrdFGMmmz4= github.com/clarketm/json v1.14.1/go.mod h1:ynr2LRfb0fQU34l07csRNBTcivjySLLiY1YzQqKVfdo= +github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= @@ -239,16 +289,22 @@ github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo= github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA= github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI= +github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko= github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= +github.com/containerd/console v1.0.0/go.mod h1:8Pf4gM6VEbTNRIT26AyyU7hxdQU3MvAvxVI0sc00XBE= +github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ= github.com/containerd/containerd v1.2.10/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/containerd/continuity v0.0.0-20190827140505-75bee3e2ccb6/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= +github.com/containerd/continuity v0.0.0-20200710164510-efbc4488d8fe h1:PEmIrUvwG9Yyv+0WKZqjXfSFDeZjs/q15g0m08BYS9k= +github.com/containerd/continuity v0.0.0-20200710164510-efbc4488d8fe/go.mod h1:cECdGN1O8G9bgKTlLhuPJimka6Xb/Gg7vYzCTNVxhvo= github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= @@ -256,16 +312,21 @@ github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kw github.com/containers/image v3.0.2+incompatible h1:B1lqAE8MUPCrsBLE86J0gnXleeRq8zJnQryhiiGQNyE= github.com/containers/image v3.0.2+incompatible/go.mod h1:8Vtij257IWSanUQKe1tAeNOm2sRVkSqQTVQ1IlwI3+M= github.com/containers/image/v5 v5.5.1/go.mod h1:4PyNYR0nwlGq/ybVJD9hWlhmIsNra4Q8uOQX2s6E2uM= +github.com/containers/image/v5 v5.7.0/go.mod h1:8aOy+YaItukxghRORkvhq5ibWttHErzDLy6egrKfKos= github.com/containers/libtrust v0.0.0-20190913040956-14b96171aa3b/go.mod h1:9rfv8iPl1ZP7aqh9YA68wnZv2NUDbXdcdPHVz0pFbPY= github.com/containers/ocicrypt v1.0.2/go.mod h1:nsOhbP19flrX6rE7ieGFvBlr7modwmNjsqWarIUce4M= +github.com/containers/ocicrypt v1.0.3/go.mod h1:CUBa+8MRNL/VkpxYIpaMtgn1WgXGyvPQj8jcy0EVG6g= github.com/containers/storage v1.20.2/go.mod h1:oOB9Ie8OVPojvoaKWEGSEtHbXUAs+tSyr7RO7ZGteMc= +github.com/containers/storage v1.23.6/go.mod h1:haFs0HRowKwyzvWEx9EgI3WsL8XCSnBDb5f8P5CAxJY= +github.com/coreos/bbolt v1.3.1-coreos.6/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/container-linux-config-transpiler v0.9.0/go.mod h1:SlcxXZQ2c42knj8pezMiQsM1f+ADxFMjGetuMKR/YSQ= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/etcd v3.3.15+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/fcct v0.5.0/go.mod h1:cbE+j77YSQwFB2fozWVB3qsI2Pi3YiVEbDz/b6Yywdo= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= -github.com/coreos/go-json v0.0.0-20170920214419-6a2fe990e083/go.mod h1:FmxyHfvrCFfCsXRylD4QQRlQmvzl+DG6iTHyEEykPfU= +github.com/coreos/go-json v0.0.0-20211020211907-c63f628265de/go.mod h1:lryFBkhadOfv8Jue2Vr/f/Yviw8h1DQPQojbXqEChY0= github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= @@ -284,8 +345,8 @@ github.com/coreos/ignition v0.33.0/go.mod h1:WJQapxzEn9DE0ryxsGvm8QnBajm/XsS/Pkr github.com/coreos/ignition v0.35.0/go.mod h1:WJQapxzEn9DE0ryxsGvm8QnBajm/XsS/PkrDqSpz+bA= github.com/coreos/ignition/v2 v2.1.1/go.mod h1:RqmqU64zxarUJa3l4cHtbhcSwfQLpUhv0WVziZwoXvE= github.com/coreos/ignition/v2 v2.3.0/go.mod h1:85dmM/CERMZXNrJsXqtNLIxR/dn8G9qlL1CmEjCugp0= -github.com/coreos/ignition/v2 v2.9.0 h1:Zl5N08OyqlECB8BrBlMDp3Jf1ShwVTtREPcUq/YO034= -github.com/coreos/ignition/v2 v2.9.0/go.mod h1:A5lFFzA2/zvZQPVEvI1lR5WPLWRb7KZ7Q1QOeUMtcAc= +github.com/coreos/ignition/v2 v2.13.0 h1:1ouW+d0nOuPUbLjxxOCnC+dWQxynr8Mt5exqJoCD7b4= +github.com/coreos/ignition/v2 v2.13.0/go.mod h1:HO1HWYWcvAIbHu6xewoKxPGBTyZ32FLwGIuipw5d63o= github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= @@ -293,9 +354,10 @@ github.com/coreos/stream-metadata-go v0.1.8 h1:EbLlLia+Ekuqgh8nF4NNFs0jUqmhUbN4m github.com/coreos/stream-metadata-go v0.1.8/go.mod h1:RTjQyHgO/G37oJ3qnqYK6Z4TPZ5EsaabOtfMjVXmgko= github.com/coreos/vcontext v0.0.0-20190529201340-22b159166068/go.mod h1:E+6hug9bFSe0KZ2ZAzr8M9F5JlArJjv5D1JS7KSkPKE= github.com/coreos/vcontext v0.0.0-20191017033345-260217907eb5/go.mod h1:E+6hug9bFSe0KZ2ZAzr8M9F5JlArJjv5D1JS7KSkPKE= -github.com/coreos/vcontext v0.0.0-20201120045928-b0e13dab675c h1:jA28WeORitsxGFVWhyWB06sAG2HbLHPQuHwDydhU2CQ= -github.com/coreos/vcontext v0.0.0-20201120045928-b0e13dab675c/go.mod h1:z4pMVvaUrxs98RROlIYdAQCKhEicjnTirOaVyDRH5h8= +github.com/coreos/vcontext v0.0.0-20211021162308-f1dbbca7bef4 h1:pfSsrvbjUFGINaPGy0mm2QKQKTdq7IcbUa+nQwsz2UM= +github.com/coreos/vcontext v0.0.0-20211021162308-f1dbbca7bef4/go.mod h1:HckqHnP/HI41vS0bfVjJ20u6jD0biI5+68QwZm5Xb9U= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= @@ -304,6 +366,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI= github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= +github.com/danielerez/go-dns-client v0.0.0-20200630114514-0b60d1703f0b/go.mod h1:2l39JZ3DOxVtByPDmp0Zhh4xS7603UHmeRtLCKzqQdQ= github.com/dave/dst v0.26.2/go.mod h1:UMDJuIRPfyUCC78eFuB+SV/WI8oDeyFDvM/JR6NI3IU= github.com/dave/gopackages v0.0.0-20170318123100-46e7023ec56e/go.mod h1:i00+b/gKdIDIxuLDFob7ustLAVqhsZRk2qVZrArELGQ= github.com/dave/jennifer v1.2.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg= @@ -315,23 +379,34 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-xdr v0.0.0-20161123171359-e6a2ba005892/go.mod h1:CTDl0pzVzE5DEzZhPfvhY/9sPFMQIxaJ9VAMs9AagrE= github.com/daviddengcn/go-colortext v0.0.0-20160507010035-511bcaf42ccd/go.mod h1:dv4zxwHi5C/8AeI+4gX4dCWOIvNi7I6JCSX0HvlKPgE= +github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= +github.com/denisenkom/go-mssqldb v0.0.0-20200428022330-06a60b6afbbc/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dimchansky/utfbom v1.1.0 h1:FcM3g+nofKgUteL8dm/UpdRXNC9KmADgTpLKsu0TRo4= github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= +github.com/diskfs/go-diskfs v1.1.2-0.20210216073915-ba492710e2d8/go.mod h1:ZTeTbzixuyfnZW5y5qKMtjV2o+GLLHo1KfMhotJI4Rk= +github.com/diskfs/go-diskfs v1.2.1-0.20210727185522-a769efacd235 h1:+NFKI4ptfB3AKeut6a538wanUHOKEMwZfznBZZ6a5Qc= +github.com/diskfs/go-diskfs v1.2.1-0.20210727185522-a769efacd235/go.mod h1:IoDpuEbpS+D+yCGdoOm6GNfyTeEws77ALvcMQFxmenw= github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko= github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI= github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= +github.com/docker/cli v20.10.11+incompatible h1:tXU1ezXcruZQRrMP8RN2z9N91h+6egZTS1gsPsKantc= +github.com/docker/cli v20.10.11+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v0.0.0-20180920194744-16128bbac47f/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v1.4.2-0.20191219165747-a9416c67da9f/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v20.10.7+incompatible h1:Z6O9Nhsjv+ayUEeI1IojKbYcsGdgYSNqxe1s2MYzUhQ= +github.com/docker/docker v20.10.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y= github.com/docker/go-connections v0.3.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/libnetwork v0.0.0-20190731215715-7f13a5c99f4b/go.mod h1:93m0aTqz6z+g32wla4l4WxTrdtvBRmVzYRkYvasA5Z8= github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= @@ -340,6 +415,14 @@ github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c/go.mod h1:Qh8CwZ github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= +github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= +github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +github.com/elastic/go-licenser v0.3.1/go.mod h1:D8eNQk70FOCVBl3smCGQt/lv7meBeQno2eI1S5apiHQ= +github.com/elastic/go-sysinfo v1.1.1/go.mod h1:i1ZYdU10oLNfRzq4vq62BEwD2fH8KaWh6eh0ikPT9F0= +github.com/elastic/go-windows v1.0.0/go.mod h1:TsU0Nrp7/y3+VwE82FoZF8gC/XFg/Elz6CcloAxnPgU= +github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/elazarl/goproxy v0.0.0-20190911111923-ecfe977594f1/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8= @@ -347,10 +430,13 @@ github.com/elazarl/goproxy/ext v0.0.0-20190911111923-ecfe977594f1/go.mod h1:gNh8 github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful v2.9.0+incompatible/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.10.0+incompatible h1:l6Soi8WCOOVAeCo4W98iBFC6Og7/X8bpRt51oNLZ2C8= github.com/emicklei/go-restful v2.10.0+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emicklei/go-restful v2.12.0+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emicklei/go-restful v2.14.2+incompatible h1:uyx8VgUCryEkh7qbr8rEtrA0rGDEJ73F5lOJdB5m3V8= +github.com/emicklei/go-restful v2.14.2+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= +github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -360,10 +446,12 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.m github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= github.com/evanphx/json-patch v4.0.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.1.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= @@ -375,11 +463,17 @@ github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5Kwzbycv github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/filanov/stateswitch v0.0.0-20200714113403-51a42a34c604/go.mod h1:GYnXtGE0e/uuFBz4CbjJL0JmP3DWwzGtcpjZYYC9ikc= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/form3tech-oss/jwt-go v3.2.1+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/form3tech-oss/jwt-go v3.2.3+incompatible h1:7ZaBxOI7TMoYBfyA3cQHErNNyAWIKUMIwqxEtgHOs5c= github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= +github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= +github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= +github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= +github.com/frankban/quicktest v1.13.0 h1:yNZif1OkDfNoDfb9zZa9aXIpejNR4F23Wely0c+Qdqk= +github.com/frankban/quicktest v1.13.0/go.mod h1:qLE0fzW0VuyUAJgPU19zByoIr0HtCHN/r/VLSOOIySU= 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.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI= @@ -394,11 +488,14 @@ github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2H github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 h1:Mn26/9ZMNWSw9C9ERFA1PUxfmGpolnw2v0bKOREu5ew= github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/go-bindata/go-bindata v3.1.2+incompatible/go.mod h1:xK8Dsgwmeed+BBsSy2XTopBn/8uK2HWuGSnA11C3Joo= +github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= github.com/go-critic/go-critic v0.3.5-0.20190526074819-1df300866540/go.mod h1:+sE8vrLDS2M0pZkBk0wy6+nLdKexVDrl/jBqQOTDThA= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4= @@ -412,8 +509,10 @@ github.com/go-git/go-git/v5 v5.4.2/go.mod h1:gQ1kArt6d+n+BGd+/B/I74HwRTLhth2+zti github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gormigrate/gormigrate/v2 v2.0.0/go.mod h1:YuVJ+D/dNt4HWrThTBnjgZuRbt7AuwINeg4q52ZE3Jw= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-lintpack/lintpack v0.5.2/go.mod h1:NwZuYi2nUHho8XEIZ6SIxihrnPoqBTDqfpXvXAN0sXM= github.com/go-log/log v0.0.0-20181211034820-a514cf01a3eb/go.mod h1:4mBwpdRMFLiuXZDCwU2lKQFsoSCo72j3HqBK9d81N2M= @@ -422,6 +521,7 @@ github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= 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 v0.2.1/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v0.4.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.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= @@ -538,6 +638,7 @@ github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+ github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA= github.com/go-openapi/validate v0.19.3/go.mod h1:90Vh6jjkTn+OT1Eefm0ZixWNFjhtOH7vS9k0lo6zwJo= github.com/go-openapi/validate v0.19.5/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4= +github.com/go-openapi/validate v0.19.8/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4= github.com/go-openapi/validate v0.19.10/go.mod h1:RKEZTUWDkxKQxN2jDT7ZnZi2bhZlbNMAuKvKB+IaGx8= github.com/go-openapi/validate v0.19.12/go.mod h1:Rzou8hA/CBw8donlS6WNEUQupNvUZ0waH08tGe6kAQ4= github.com/go-openapi/validate v0.19.15/go.mod h1:tbn/fdOwYHgrhPBzidZfJC2MIVvs9GA7monOmWBbeCI= @@ -556,6 +657,8 @@ github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/j github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY= github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= +github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= @@ -586,6 +689,7 @@ github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598 github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= github.com/gobuffalo/flect v0.1.3/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= github.com/gobuffalo/flect v0.2.0/go.mod h1:W3K3X9ksuZfir8f/LrfVtWmCDQFfayuylOJ7sz/Fj80= +github.com/gobuffalo/flect v0.2.2/go.mod h1:vmkQwuZYhN5Pc4ljYQZzP+1sq+NEkK+lh20jmEmX3jc= github.com/gobuffalo/flect v0.2.5 h1:H6vvsv2an0lalEaCDRThvtBfmg44W/QHXBCYUXf/6S4= github.com/gobuffalo/flect v0.2.5/go.mod h1:1ZyCLIbg0YD7sDkzvFdPoOydPtD8y9JQnrOROolUcM8= github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk= @@ -596,6 +700,7 @@ github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360= github.com/gobuffalo/gogen v0.1.0/go.mod h1:8NTelM5qd8RZ15VjQTFkAW6qOMx5wBbW4dSCS3BY8gg= github.com/gobuffalo/gogen v0.1.1/go.mod h1:y8iBtmHmGc4qa3urIyo1shvOD8JftTtfcKi+71xfDNE= +github.com/gobuffalo/here v0.6.0/go.mod h1:wAG085dHOYqUpf+Ap+WOdrPTp5IYcDAs/x7PLa8Y5fM= github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8= github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= @@ -609,21 +714,31 @@ github.com/godbus/dbus v0.0.0-20181025153459-66d97aec3384/go.mod h1:/YcGZj5zSblf github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= +github.com/gogo/protobuf v1.0.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A= +github.com/golang-collections/go-datastructures v0.0.0-20150211160725-59788d5eb259/go.mod h1:9Qcha0gTWLw//0VNka1Cbnjvg3pNKGFdAm7E9sBabxE= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= +github.com/golang-jwt/jwt/v4 v4.2.0 h1:besgBTC8w8HjP6NzQdxwKH9Z5oQMZ24ThTrHp3cZ8eU= +github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20180513044358-24b0969c4cb7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= @@ -660,6 +775,7 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2/go.mod h1:k9Qvh+8juN+UKMCS/3jFtGICgW8O96FVaZsaxdzDkR4= github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a/go.mod h1:ryS0uhF+x9jgbj/N71xsEqODy9BN81/GonCZiOzirOk= @@ -733,35 +849,48 @@ github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/renameio v1.0.1/go.mod h1:t/HQoYBZSsWSNK35C6CO/TpPLDVWvxOHboWUAweKUpk= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v0.0.0-20170306145142-6a5e28554805/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gnostic v0.0.0-20170426233943-68f4ded48ba9/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/googleapis/gnostic v0.1.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/googleapis/gnostic v0.2.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= +github.com/googleapis/gnostic v0.3.1/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1awfrALZdbtU= github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= +github.com/googleapis/gnostic v0.5.4/go.mod h1:TRWw1s4gxBGjSe301Dai3c7wXJAZy57+/6tawkOvqHQ= github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= github.com/gophercloud/gophercloud v0.15.1-0.20210202035223-633d73521055/go.mod h1:wRtmUelyIIv3CSSDI47aUwbs075O6i+LY+pXsKCBsb4= github.com/gophercloud/gophercloud v0.19.0/go.mod h1:wRtmUelyIIv3CSSDI47aUwbs075O6i+LY+pXsKCBsb4= github.com/gophercloud/gophercloud v0.20.0/go.mod h1:wRtmUelyIIv3CSSDI47aUwbs075O6i+LY+pXsKCBsb4= +github.com/gophercloud/gophercloud v0.22.0/go.mod h1:wRtmUelyIIv3CSSDI47aUwbs075O6i+LY+pXsKCBsb4= github.com/gophercloud/gophercloud v0.24.0 h1:jDsIMGJ1KZpAjYfQgGI2coNQj5Q83oPzuiGJRFWgMzw= github.com/gophercloud/gophercloud v0.24.0/go.mod h1:Q8fZtyi5zZxPS/j9aj3sSxtvj41AdQMDwyo1myduD5c= github.com/gophercloud/utils v0.0.0-20210720165645-8a3ad2ad9e70/go.mod h1:wx8HMD8oQD0Ryhz6+6ykq75PJ79iPyEqYHfwZ4l7OsA= github.com/gophercloud/utils v0.0.0-20220307143606-8e7800759d16 h1:slt/exMiitZNY+5OrKJ6ZvSogqN+SyzeYzAtyI6db9A= github.com/gophercloud/utils v0.0.0-20220307143606-8e7800759d16/go.mod h1:qOGlfG6OIJ193/c3Xt/XjOfHataNZdQcVgiu93LxBUM= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gordonklaus/ineffassign v0.0.0-20190601041439-ed7b1b5ee0f8/go.mod h1:cuNKsD1zp2v6XfE/orVX2QE1LC+i254ceGcVeDT3pTU= +github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= github.com/gorilla/mux v0.0.0-20191024121256-f395758b854c/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= @@ -781,7 +910,10 @@ github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFb github.com/h2non/filetype v1.0.12 h1:yHCsIe0y2cvbDARtJhGBTD2ecvqMSTvlIcph9En/Zao= github.com/h2non/filetype v1.0.12/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-checkpoint v0.5.0/go.mod h1:7nfLNL10NsxqO4iWuW6tWW0HjZuDrwkBuEQsVcpCOgg= github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= @@ -796,6 +928,8 @@ github.com/hashicorp/go-hclog v1.2.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39E github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-plugin v1.4.4/go.mod h1:viDMjcLJuDui6pXb8U4HVfb8AamCWhHGUjr2IrTF67s= github.com/hashicorp/go-retryablehttp v0.6.6/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= @@ -809,6 +943,7 @@ github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.3.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.4.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.5.0 h1:O293SZ2Eg+AAYijkVK3jR786Am1bhDEh2GHT0tIVE5E= @@ -847,29 +982,115 @@ github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4= +github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= +github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= +github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0= +github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= +github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= +github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= +github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= +github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= +github.com/jackc/pgconn v1.4.0/go.mod h1:Y2O3ZDF0q4mMacyWV3AstPJpeHXWGEetiFttmq5lahk= +github.com/jackc/pgconn v1.5.0/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI= +github.com/jackc/pgconn v1.5.1-0.20200601181101-fa742c524853/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI= +github.com/jackc/pgconn v1.6.4/go.mod h1:w2pne1C2tZgP+TvjqLpOigGzNqjBgQW9dUw/4Chex78= +github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= +github.com/jackc/pgconn v1.8.1/go.mod h1:JV6m6b6jhjdmzchES0drzCcYcAHS1OPD5xu3OZ/lE2g= +github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= +github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= +github.com/jackc/pgconn v1.10.1 h1:DzdIHIjG1AxGwoEEqS+mGsURyjt4enSmqzACXvVzOT8= +github.com/jackc/pgconn v1.10.1/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= +github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= +github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= +github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= +github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= +github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A= +github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= +github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.0.2/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.2.0 h1:r7JypeP2D3onoQTCxWdTpCtJ4D+qpKr0TxvoyMhZ5ns= +github.com/jackc/pgproto3/v2 v2.2.0/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgservicefile v0.0.0-20200307190119-3430c5407db8/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= +github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg= +github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= +github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= +github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= +github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= +github.com/jackc/pgtype v1.2.0/go.mod h1:5m2OfMh1wTK7x+Fk952IDmI4nw3nPrvtQdM0ZT4WpC0= +github.com/jackc/pgtype v1.3.1-0.20200510190516-8cd94a14c75a/go.mod h1:vaogEUkALtxZMCH411K+tKzNpwzCKU+AnPzBKZ+I+Po= +github.com/jackc/pgtype v1.3.1-0.20200606141011-f6355165a91c/go.mod h1:cvk9Bgu/VzJ9/lxTO5R5sf80p0DiucVtN7ZxvaC4GmQ= +github.com/jackc/pgtype v1.4.2/go.mod h1:JCULISAZBFGrHaOXIIFiyfzW5VY0GRitRr8NeJsrdig= +github.com/jackc/pgtype v1.7.0/go.mod h1:ZnHF+rMePVqDKaOfJVI4Q8IVvAQMryDlDkZnKOI75BE= +github.com/jackc/pgtype v1.8.0/go.mod h1:PqDKcEBtllAtk/2p6z6SHdXW5UB+MhE75tUol2OKexE= +github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= +github.com/jackc/pgtype v1.9.0 h1:/SH1RxEtltvJgsDqp3TbiTFApD3mey3iygpuEGeuBXk= +github.com/jackc/pgtype v1.9.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= +github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= +github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= +github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= +github.com/jackc/pgx/v4 v4.5.0/go.mod h1:EpAKPLdnTorwmPUUsqrPxy5fphV18j9q3wrfRXgo+kA= +github.com/jackc/pgx/v4 v4.6.1-0.20200510190926-94ba730bb1e9/go.mod h1:t3/cdRQl6fOLDxqtlyhe9UWgfIi9R8+8v8GKV5TRA/o= +github.com/jackc/pgx/v4 v4.6.1-0.20200606145419-4e5062306904/go.mod h1:ZDaNWkt9sW1JMiNn0kdYBaLelIhw7Pg4qd+Vk6tw7Hg= +github.com/jackc/pgx/v4 v4.8.1/go.mod h1:4HOLxrl8wToZJReD04/yB20GDwf4KBYETvlHciCnwW0= +github.com/jackc/pgx/v4 v4.11.0/go.mod h1:i62xJgdrtVDsnL3U8ekyrQXEwGNTRoG7/8r+CIdYfcc= +github.com/jackc/pgx/v4 v4.12.0/go.mod h1:fE547h6VulLPA3kySjfnSG/e2D861g/50JlVUa/ub60= +github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= +github.com/jackc/pgx/v4 v4.14.0 h1:TgdrmgnM7VY72EuSQzBbBd4JA1RLqJolrw9nQVZABVc= +github.com/jackc/pgx/v4 v4.14.0/go.mod h1:jT3ibf/A0ZVCp89rtCIN0zCJxcE74ypROmHEZYsG/j8= +github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.1.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.1.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.2.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/jcchavezs/porto v0.1.0/go.mod h1:fESH0gzDHiutHRdX2hv27ojnOVFco37hg1W6E9EZF4A= github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= +github.com/jgautheron/goconst v0.0.0-20170703170152-9740945f5dcb/go.mod h1:82TxjOpWQiPmywlbIaB2ZkqJoSYJdLGPgAJDvM3PbKc= github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74= +github.com/jinzhu/copier v0.3.5/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= +github.com/jinzhu/gorm v1.9.12/go.mod h1:vhTjlKSJUTWNtcbQtrMBFCxy7eXTzeCAzfL5fBZT/Qs= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jinzhu/now v1.1.2 h1:eVKgfIdy9b6zbWBMgFpfDPoAMifwSZagU9HmEU6zgiI= +github.com/jinzhu/now v1.1.2/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= 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/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901/go.mod h1:Z86h9688Y0wesXCyonoVr47MasHilkuLMqGhRZ4Hpak= github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= @@ -882,6 +1103,7 @@ github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCV github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= @@ -892,12 +1114,15 @@ github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVY github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/justinas/alice v1.2.0/go.mod h1:fN5HRH/reO/zrUflLfTN43t3vXvKzvZIENsNEe7i7qA= github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kdomanski/iso9660 v0.2.1 h1:IepyfCeEqx77rZeOM4XZgWB4XJWEF7Jp+1ehMTrSElg= github.com/kdomanski/iso9660 v0.2.1/go.mod h1:LY50s7BlG+ES6V99oxYGd0ub9giLrKdHZb3LLOweBj0= +github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= +github.com/kennygrant/sanitize v1.2.4/go.mod h1:LGsjYYtgxbetdg5owWB2mpgUL6e2nfw2eObZ0u0qvak= github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 h1:DowS9hvgyYSX4TO5NpyC606/Z4SxnNYbT+WX27or6Ck= github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= @@ -910,10 +1135,12 @@ github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0 github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.10.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.10.8/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.11.1/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/cpuid v0.0.0-20180405133222-e7e905edc00e/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/pgzip v1.2.4/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= +github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -934,17 +1161,29 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kubernetes-sigs/kube-storage-version-migrator v0.0.0-20191127225502-51849bc15f17/go.mod h1:enH0BVV+4+DAgWdwSlMefG8bBzTfVMTr1lApzdLZ/cc= github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/labstack/echo/v4 v4.1.16/go.mod h1:awO+5TzAjvL8XpibdsfXxPgHr+orhtXZJZIQCVjogKI= +github.com/labstack/echo/v4 v4.1.17/go.mod h1:Tn2yRQL/UclUalpb5rPdXDevbkJ+lp/2svdyFBg6CHQ= +github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= +github.com/lib/pq v0.0.0-20180327071824-d34b9ff171c2/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/libvirt/libvirt-go v4.10.0+incompatible/go.mod h1:34zsnB4iGeOv7Byj6qotuW8Ya4v4Tr43ttjz/F0wjLE= github.com/libvirt/libvirt-go v5.10.0+incompatible h1:01fwkdUHH2hk4YyFNCr48OvSGqXYLzp9cofUpeyeLNc= github.com/libvirt/libvirt-go v5.10.0+incompatible/go.mod h1:34zsnB4iGeOv7Byj6qotuW8Ya4v4Tr43ttjz/F0wjLE= github.com/libvirt/libvirt-go-xml v4.10.0+incompatible/go.mod h1:oBlgD3xOA01ihiK5stbhFzvieyW+jVS6kbbsMVF623A= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= +github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= +github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= +github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/magiconair/properties v1.7.6/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= @@ -960,17 +1199,24 @@ github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJ github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= +github.com/markbates/pkger v0.17.1/go.mod h1:0JoVlrol20BSywW79rN3kdFFsE5xYM+rSCQDXbLhiuI= github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= @@ -980,13 +1226,18 @@ github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzp github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-shellwords v1.0.10/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= +github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus= +github.com/mattn/go-sqlite3 v2.0.1+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= +github.com/mibk/dupl v1.0.0/go.mod h1:pCr4pNxxIbFGvtyCOi0c7LVjmV6duhKWV+ex5vh38ME= github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= +github.com/microcosm-cc/bluemonday v1.0.15/go.mod h1:ZLvAzeakRwrGnzQEvstVzVt3ZpqOF2+sdFr0Om+ce30= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mistifyio/go-zfs v2.1.1+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= @@ -1013,10 +1264,16 @@ github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/moby/moby v20.10.12+incompatible/go.mod h1:fDXVQ6+S340veQPv35CzDahGBmHsiclFwfEygB/TWMc= github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= +github.com/moby/sys/mountinfo v0.1.3/go.mod h1:w2t2Avltqx8vE7gX5l+QiBKxODu2TX0+Syr3h52Tw4o= +github.com/moby/sys/mountinfo v0.3.1/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= +github.com/moby/sys/mountinfo v0.4.1/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo= +github.com/moby/term v0.0.0-20200915141129-7f0af18e79f2/go.mod h1:TjQg8pa4iejrUrjiz0MCtMV38jdMNW4doKSiBrEvCQQ= github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc= github.com/moby/term v0.0.0-20210610120745-9d4ed1856297/go.mod h1:vgPCkQMyxTZ7IDy8SXRufE172gr8+K/JE/7hHFxHW3A= +github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 h1:dcztxKSvZ4Id8iPpHERQBbIJfabdt4wUm5qy3wOL2Zc= github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw= 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= @@ -1032,6 +1289,8 @@ github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJ github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/mozilla/tls-observatory v0.0.0-20180409132520-8791a200eb40/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk= github.com/mozilla/tls-observatory v0.0.0-20190404164649-a3c1b6cfecfd/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk= +github.com/mrunalp/fileutils v0.0.0-20171103030105-7d4729fb3618/go.mod h1:x8F1gnqOkIEiO4rqoeEEEqQbo7HjGMTvyoq3gej4iT0= +github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= github.com/mtrmac/gpgme v0.1.2/go.mod h1:GYYHnGSuS7HK3zVS2n3y73y0okK/BeKzwnn5jgiVFNI= 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= @@ -1039,6 +1298,13 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8m github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= +github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= +github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= +github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= +github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/nbutton23/zxcvbn-go v0.0.0-20160627004424-a22cb81b2ecd/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU= github.com/nbutton23/zxcvbn-go v0.0.0-20171102151520-eafdab6b0663/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU= github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU= @@ -1051,6 +1317,7 @@ github.com/nutanix-cloud-native/prism-go-client v0.2.0/go.mod h1:3Nbek8ajnUyYUNJ github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= @@ -1059,46 +1326,65 @@ github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FW 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.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= 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.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/ginkgo v1.15.0/go.mod h1:hF8qUzuuC8DJGygJH3726JnCZX4MYbRB8yFfISqnKUg= +github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.2/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.8.1/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= github.com/onsi/gomega v1.10.5/go.mod h1:gza4q3jKQJijlu05nKWRCW/GavJumGt8aNRxWg7mt48= +github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY= github.com/onsi/gomega v1.14.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0= +github.com/onsi/gomega v1.15.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0= github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= -github.com/onsi/gomega v1.17.0 h1:9Luw4uT5HTjHTN8+aNcSThgH1vdXnmdJ8xIfZ4wyTRE= +github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/onsi/gomega v1.18.0 h1:ngbYoRctxjl8SiF7XgP0NxBFbfHcg3wfHMMaFHWwMTM= +github.com/onsi/gomega v1.18.0/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= +github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/opencontainers/image-spec v1.0.2-0.20190823105129-775207bd45b6 h1:yN8BPXVwMBAm3Cuvh1L5XE8XpvYRMdsVLd82ILprhUU= github.com/opencontainers/image-spec v1.0.2-0.20190823105129-775207bd45b6/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= +github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/opencontainers/runc v0.0.0-20191031171055-b133feaeeb2e/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/opencontainers/runc v1.0.0-rc90/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v1.0.0-rc91/go.mod h1:3Sm6Dt7OT8z88EbdQqqcRN2oCT54jbi72tT/HqgflT8= +github.com/opencontainers/runc v1.0.2 h1:opHZMaswlyxz1OuGpBE53Dwe4/xF7EZTY0A2L/FpCOg= +github.com/opencontainers/runc v1.0.2/go.mod h1:aTaHFFwQXuA71CiyxOdFFIorAoemI04suvGRQFzWTD0= github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.0.3-0.20200520003142-237cc4f519e2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/selinux v1.5.1/go.mod h1:yTcKuYAh6R95iDpefGLQaPaRwJFwyzAJufJyiTt7s0g= github.com/opencontainers/selinux v1.5.2/go.mod h1:yTcKuYAh6R95iDpefGLQaPaRwJFwyzAJufJyiTt7s0g= -github.com/openshift/api v0.0.0-20200326160804-ecb9283fe820/go.mod h1:RKMJ5CBnljLfnej+BJ/xnOWc3kZDvJUaIAEq2oKSPtE= -github.com/openshift/api v0.0.0-20200827090112-c05698d102cf/go.mod h1:M3xexPhgM8DISzzRpuFUy+jfPjQPIcs9yqEYj17mXV8= -github.com/openshift/api v0.0.0-20200829102639-8a3a835f1acf/go.mod h1:M3xexPhgM8DISzzRpuFUy+jfPjQPIcs9yqEYj17mXV8= -github.com/openshift/api v0.0.0-20210730095913-85e1d547cdee/go.mod h1:ntkQrC1Z6AxxkhDlVpDVjkD+pzdwVUalWyfH40rSyyM= -github.com/openshift/api v0.0.0-20211025104849-a11323ccb6ea/go.mod h1:RsQCVJu4qhUawxxDP7pGlwU3IA4F01wYm3qKEu29Su8= -github.com/openshift/api v0.0.0-20211108165917-be1be0e89115/go.mod h1:RsQCVJu4qhUawxxDP7pGlwU3IA4F01wYm3qKEu29Su8= -github.com/openshift/api v0.0.0-20211209135129-c58d9f695577/go.mod h1:DoslCwtqUpr3d/gsbq4ZlkaMEdYqKxuypsDjorcHhME= +github.com/opencontainers/selinux v1.6.0/go.mod h1:VVGKuOLlE7v4PJyT6h7mNWvq1rzqiriPsEqVhc+svHE= +github.com/opencontainers/selinux v1.8.2/go.mod h1:MUIHuUEvKB1wtJjQdOyYRgOnLD2xAPP8dBsCoU0KuF8= +github.com/openshift-online/ocm-sdk-go v0.1.205/go.mod h1:iOhkt/6nFp9v7hasdmGbYlR/YKeUoaZKq5ZBrtyKwKg= github.com/openshift/api v0.0.0-20220823143838-5768cc618ba0 h1:sa/uPkgtfTsdtvw4O7WuHAkRiW/3tTNw5I4/T6Qgbn0= github.com/openshift/api v0.0.0-20220823143838-5768cc618ba0/go.mod h1:6lal7JeRpbdE/h4S12xH1ZRKMmrcyNQHnSKJ3akGXNc= +github.com/openshift/assisted-image-service v0.0.0-20220307202600-054a1afa8d28 h1:TOB7zzhxIfUBJG0RXoU0LGRj3p9AzEKEVUaNJNKhFNA= +github.com/openshift/assisted-image-service v0.0.0-20220307202600-054a1afa8d28/go.mod h1:bH4+AsmPy8mQQvtgedBm2Crs93TDWeXEMlIPrlEMpjA= +github.com/openshift/assisted-service v1.0.10-0.20220223093655-7ada9949bf1d h1:n5eonnzsP0tD9EEF4uiu9D+ayTRamid11snRUc+BmfU= +github.com/openshift/assisted-service v1.0.10-0.20220223093655-7ada9949bf1d/go.mod h1:4CDUwTbuv+XO0JAdnXi0+lFIwLzLwxrQMmT+WaIn264= github.com/openshift/baremetal-operator v0.0.0-20220128094204-28771f489634 h1:HMxwhxQKSamgM3RpvbUwUKrnD39fEAWrEYaueTHVRmY= github.com/openshift/baremetal-operator v0.0.0-20220128094204-28771f489634/go.mod h1:fKoRJ8B07os5e+LFLvhFSS2g8nTODZW/J5jKw4dsQ18= github.com/openshift/baremetal-operator/apis v0.0.0-20220128094204-28771f489634 h1:TfLz0GbuVWt0bKNK+71JeYHIAemjcnE1fAVgqMgw2Ac= @@ -1106,9 +1392,16 @@ github.com/openshift/baremetal-operator/apis v0.0.0-20220128094204-28771f489634/ github.com/openshift/baremetal-operator/pkg/hardwareutils v0.0.0-20220128094204-28771f489634 h1:sGL5yvlRTpCVM2Ck47Tn09PPSgSTLq2V+B2nSNkBDIs= github.com/openshift/baremetal-operator/pkg/hardwareutils v0.0.0-20220128094204-28771f489634/go.mod h1:/PSTQInIZmfuOmAp/pSgZAs4txs6T49woC0MYIa4QzE= github.com/openshift/build-machinery-go v0.0.0-20200211121458-5e3d6e570160/go.mod h1:1CkcsT3aVebzRBzVTSbiKSkJMsC/CASqxesfqEMfJEc= +github.com/openshift/build-machinery-go v0.0.0-20200424080330-082bf86082cc/go.mod h1:1CkcsT3aVebzRBzVTSbiKSkJMsC/CASqxesfqEMfJEc= github.com/openshift/build-machinery-go v0.0.0-20200819073603-48aa266c95f7/go.mod h1:b1BuldmJlbA/xYtdZvKi+7j5YGB44qJUJDZ9zwiNCfE= +github.com/openshift/build-machinery-go v0.0.0-20210209125900-0da259a2c359/go.mod h1:b1BuldmJlbA/xYtdZvKi+7j5YGB44qJUJDZ9zwiNCfE= github.com/openshift/build-machinery-go v0.0.0-20210712174854-1bb7fd1518d3/go.mod h1:b1BuldmJlbA/xYtdZvKi+7j5YGB44qJUJDZ9zwiNCfE= +github.com/openshift/build-machinery-go v0.0.0-20211213093930-7e33a7eb4ce3/go.mod h1:b1BuldmJlbA/xYtdZvKi+7j5YGB44qJUJDZ9zwiNCfE= +github.com/openshift/client-go v0.0.0-20200326155132-2a6cd50aedd0/go.mod h1:uUQ4LClRO+fg5MF/P6QxjMCb1C9f7Oh4RKepftDnEJE= github.com/openshift/client-go v0.0.0-20200827190008-3062137373b5/go.mod h1:5rGmrkQ8DJEUXA+AR3rEjfH+HFyg4/apY9iCQFgvPfE= +github.com/openshift/client-go v0.0.0-20201020074620-f8fd44879f7c/go.mod h1:yZ3u8vgWC19I9gbDMRk8//9JwG/0Sth6v7C+m6R8HXs= +github.com/openshift/client-go v0.0.0-20210331195552-cf6c2669e01f/go.mod h1:hHaRJ6vp2MRd/CpuZ1oJkqnMGy5eEnoAkQmKPZKcUPI= +github.com/openshift/client-go v0.0.0-20210409155308-a8e62c60e930/go.mod h1:uBPbAyIbjMuhPQy4NgF8q1alNGX2qA8bXIkAycsSDc0= github.com/openshift/client-go v0.0.0-20210730113412-1811c1b3fc0e/go.mod h1:P1pjphFOgm/nYjmtouHGaSLGtdP25dQICJnYtcYhfEs= github.com/openshift/client-go v0.0.0-20211025111749-96ca2abfc56c/go.mod h1:xigLF97kzy1PZuDsC0Lfu6GlzChRt62+2Ts/nG3sPHY= github.com/openshift/client-go v0.0.0-20211209144617-7385dd6338e3 h1:SG1aqwleU6bGD0X4mhkTNupjVnByMYYuW4XbnCPavQU= @@ -1120,8 +1413,15 @@ github.com/openshift/cloud-provider-vsphere v1.19.1-0.20211222185833-7829863d055 github.com/openshift/cluster-api v0.0.0-20190805113604-f8de78af80fc h1:6Nf3TaiooyEZNk+3ZHonNOujT+HacuSj9cxyeGTc/zg= github.com/openshift/cluster-api v0.0.0-20190805113604-f8de78af80fc/go.mod h1:mNsD1dsD4T57kV4/C6zTHke/Ro166xgnyyRZqkamiEU= github.com/openshift/cluster-api-actuator-pkg v0.0.0-20190614215203-42228d06a2ca/go.mod h1:KNPaA64x3Ok7z538kvS2acwC5fEwvPfF0RdTx2geQEE= +github.com/openshift/cluster-api-provider-aws v0.2.1-0.20200929152424-eab2e087f366/go.mod h1:jUPi2GGrdWiL7tuaoaqAIDnMWr4njJVv4+lGthx1jws= +github.com/openshift/cluster-api-provider-azure v0.1.0-alpha.3.0.20210626224711-5d94c794092f/go.mod h1:GR+ocB8I+Z7JTSBdO+DMu/diBfH66lRlRpnc1KWysUM= github.com/openshift/cluster-api-provider-baremetal v0.0.0-20220408122422-7a548effc26e h1:FWzYb0sH16yVOyySUwY5yXtZFW/U2bPoK38SEGjC5D8= github.com/openshift/cluster-api-provider-baremetal v0.0.0-20220408122422-7a548effc26e/go.mod h1:Q5WzHV1JZw/XNRnXCo8JfyOSegL13a+lhV4sc44lpSI= +github.com/openshift/cluster-api-provider-gcp v0.0.1-0.20200701112720-3a7d727c9a10/go.mod h1:wgkZrOlcIMWTzo8khB4Js2PoDJDlIUUdzCBm7BuDdqw= +github.com/openshift/cluster-api-provider-gcp v0.0.1-0.20200713133651-5c8a640669ac/go.mod h1:XVYX9JE339nKbDDa/W481XD+1GTeqeaBm8bDPr7WE7I= +github.com/openshift/cluster-api-provider-gcp v0.0.1-0.20200901173901-9056dbc8c9b9/go.mod h1:rcwAydGZX+z4l91wtOdbq+fqDwuo6iu0YuFik3UUc+8= +github.com/openshift/cluster-api-provider-gcp v0.0.1-0.20201002065957-9854f7420570/go.mod h1:7NRECVE26rvP1/fs1CbhfY5gsgnnFQNhb9txTFzWmUw= +github.com/openshift/cluster-api-provider-gcp v0.0.1-0.20201201000827-1117a4fc438c/go.mod h1:21N0wWjiTQypZ7WosEYhcGJHr9JoDR1RBFztE0NvdYM= github.com/openshift/cluster-api-provider-ibmcloud v0.0.1-0.20220201105455-8014e5e894b0 h1:G68R/I4HB4F4LawreWxKruqThNpmmXf5DSAsJALc9FY= github.com/openshift/cluster-api-provider-ibmcloud v0.0.1-0.20220201105455-8014e5e894b0/go.mod h1:CLnQ32mWHZtlQeHX0lYLMA+QTrrdXbg9K8smrWOPXMk= github.com/openshift/cluster-api-provider-libvirt v0.2.1-0.20191219173431-2336783d4603 h1:MC6BSZYxFPoqqKj9PdlGjHGVKcMsvn6Kv1NiVzQErZ8= @@ -1131,25 +1431,52 @@ github.com/openshift/cluster-api-provider-openstack v0.0.0-20211111204942-611d32 github.com/openshift/cluster-api-provider-ovirt v0.1.1-0.20220323121149-e3f2850dd519 h1:foU7/s6DQczTFdZ/8H++pUC2Pzygqdz5ZgqUakksR5w= github.com/openshift/cluster-api-provider-ovirt v0.1.1-0.20220323121149-e3f2850dd519/go.mod h1:C7unCUThP8eqT4xQfbvg3oIDn2S9TYtb0wbBoH/SR2U= github.com/openshift/cluster-autoscaler-operator v0.0.0-20190521201101-62768a6ba480/go.mod h1:/XmV44Fh28Vo3Ye93qFrxAbcFJ/Uy+7LPD+jGjmfJYc= +github.com/openshift/custom-resource-status v1.1.0 h1:EjSh0f3vF6eaS3zAToVHUXcS7N2jVEosUFJ0sRKvmZ0= +github.com/openshift/custom-resource-status v1.1.0/go.mod h1:GDjWl0tX6FNIj82vIxeudWeSx2Ff6nDZ8uJn0ohUFvo= +github.com/openshift/generic-admission-server v1.14.1-0.20210422140326-da96454c926d/go.mod h1:m+wYlVQdnPe8JGqoKVpCYnFRIVraqC1SrUowQXh6XlA= +github.com/openshift/hive/apis v0.0.0-20210506000654-5c038fb05190 h1:8eShHtqtKwgJWdJh/H0ytAsAblDCA1bAjvokIfuU2jM= +github.com/openshift/hive/apis v0.0.0-20210506000654-5c038fb05190/go.mod h1:Ujw9ImzSYvo9VlUX6Gjy7zPFP7xYUAU50tdf1wPpN6c= github.com/openshift/library-go v0.0.0-20191003152030-97c62d8a2901/go.mod h1:NBttNjZpWwup/nthuLbPAPSYC8Qyo+BBK5bCtFoyYjo= +github.com/openshift/library-go v0.0.0-20200512120242-21a1ff978534/go.mod h1:2kWwXTkpoQJUN3jZ3QW88EIY1hdRMqxgRs2hheEW/pg= github.com/openshift/library-go v0.0.0-20200831114015-2ab0c61c15de/go.mod h1:6vwp+YhYOIlj8MpkQKkebTTSn2TuYyvgiAFQ206jIEQ= +github.com/openshift/library-go v0.0.0-20200909173121-1d055d971916/go.mod h1:6vwp+YhYOIlj8MpkQKkebTTSn2TuYyvgiAFQ206jIEQ= +github.com/openshift/library-go v0.0.0-20210408164723-7a65fdb398e2/go.mod h1:pnz961veImKsbn7pQcuFbcVpCQosYiC1fUOjzEDeOLU= github.com/openshift/library-go v0.0.0-20210811133500-5e31383de2a7/go.mod h1:3GagmGg6gikg+hAqma7E7axBzs2pjx4+GrAbdl4OYdY= github.com/openshift/library-go v0.0.0-20220121154930-b7889002d63e h1:XDK1ZB6Q1YmYkxfEkRq9z92yzinaJMf+vvjeELKj+2I= github.com/openshift/library-go v0.0.0-20220121154930-b7889002d63e/go.mod h1:6AmNM4N4nHftckybV/U7bQW+5AvK5TW81ndSI6KEidw= github.com/openshift/machine-api-operator v0.0.0-20190312153711-9650e16c9880/go.mod h1:7HeAh0v04zQn1L+4ItUjvpBQYsm2Nf81WaZLiXTcnkc= +github.com/openshift/machine-api-operator v0.2.1-0.20200611014855-9a69f85c32dd/go.mod h1:6vMi+R3xqznBdq5rgeal9N3ak3sOpy50t0fdRCcQXjE= +github.com/openshift/machine-api-operator v0.2.1-0.20200701225707-950912b03628/go.mod h1:cxjy/RUzv5C2T5FNl1KKXUgtakWsezWQ642B/CD9VQA= +github.com/openshift/machine-api-operator v0.2.1-0.20200722104429-f4f9b84df9b7/go.mod h1:XDsNRAVEJtkI00e51SAZ/PnqNJl1zv0rHXSdl9L1oOY= +github.com/openshift/machine-api-operator v0.2.1-0.20200926044412-b7d860f8074c/go.mod h1:cp/wPVzxHZeLUjOLkNPNqrk4wyyW6HuHd3Kz9+hl5xw= +github.com/openshift/machine-api-operator v0.2.1-0.20201002104344-6abfb5440597/go.mod h1:+oAfoCl+TUd2TM79/6NdqLpFUHIJpmqkKdmiHe2O7mw= +github.com/openshift/machine-api-operator v0.2.1-0.20210504014029-a132ec00f7dd/go.mod h1:DFZBMPtC2TYZH5NE9+2JQIpbZAnruqc9F26QmbOm9pw= github.com/openshift/machine-api-operator v0.2.1-0.20211111133920-c8bba3e64310/go.mod h1:8kWuB/zhnlBsNOyD1yz0epINZX5IodG3Z4iDwQrYKx0= github.com/openshift/machine-config-operator v0.0.1-0.20201009041932-4fe8559913b8 h1:C4gCipkWTDp0B9jb0wZdLgB+HWC7EzVVwQOeNaKnTRA= github.com/openshift/machine-config-operator v0.0.1-0.20201009041932-4fe8559913b8/go.mod h1:fjKreLaKEeUKsyIkT4wlzIQwUVJ2ZKDUh3CI73ckYIY= github.com/openshift/runtime-utils v0.0.0-20200415173359-c45d4ff3f912/go.mod h1:0OXNy7VoqFexkxKqyQbHJLPwn1MFp1/CxRJAgKHM+/o= +github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= +github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= +github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= +github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= +github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= +github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= github.com/operator-framework/operator-sdk v0.5.1-0.20190301204940-c2efe6f74e7b/go.mod h1:iVyukRkam5JZa8AnjYf+/G3rk7JI1+M6GsU0sq0B9NA= +github.com/ory/dockertest/v3 v3.8.1 h1:vU/8d1We4qIad2YM0kOwRVtnyue7ExvacPiw1yDm17g= +github.com/ory/dockertest/v3 v3.8.1/go.mod h1:wSRQ3wmkz+uSARYMk7kVJFDBGm8x5gSxIhI7NDc+BAQ= github.com/ostreedev/ostree-go v0.0.0-20190702140239-759a8c1ac913/go.mod h1:J6OG6YJVEWopen4avK3VNQSnALmmjvniMmni/YFYAwc= -github.com/ovirt/go-ovirt v0.0.0-20210308100159-ac0bcbc88d7c h1:2SbYZedeIawU8sGFnohfrdEcEMBA4V8SDouG6hly+H4= -github.com/ovirt/go-ovirt v0.0.0-20210308100159-ac0bcbc88d7c/go.mod h1:fLDxPk1Sf64DBYtwIYxrnx3gPZ1q0xPdWdI1y9vxUaw= +github.com/ovirt/go-ovirt v0.0.0-20210809163552-d4276e35d3db h1:ahvAlEurj4TF1SExDJHNeqknQC8lAwnZEPLyZJuRyd0= +github.com/ovirt/go-ovirt v0.0.0-20210809163552-d4276e35d3db/go.mod h1:Zkdj9/rW6eyuw0uOeEns6O3pP5G2ak+bI/tgkQ/tEZI= +github.com/ovirt/go-ovirt-client v0.7.1/go.mod h1:LvCGQgqp9TmreCfbYXkVxDVNgmZ9/6e2fWtO58IH+oc= +github.com/ovirt/go-ovirt-client-log/v2 v2.1.0/go.mod h1:mDoU3KIwftpsgZGzXGk5d2UEJYTY0bYMfg/GwPapXL0= +github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pborman/uuid v0.0.0-20170612153648-e790cca94e6c/go.mod h1:VyrYX9gd7irzKovcSS6BIIEwPRkP2Wm2m9ufcdFSJ34= github.com/pborman/uuid v0.0.0-20180906182336-adf5a7427709/go.mod h1:VyrYX9gd7irzKovcSS6BIIEwPRkP2Wm2m9ufcdFSJ34= github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g= @@ -1158,16 +1485,27 @@ github.com/pelletier/go-toml v1.1.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/9 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo= github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= +github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= +github.com/pelletier/go-toml v1.9.3 h1:zeC5b1GviRUyKYd6OJPvBU/mcVDVoL1OhT17FCt5dSQ= github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= +github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pierrec/lz4 v2.3.0+incompatible h1:CZzRn4Ut9GbUkHlQ7jqBXeZQV41ZSKWFc302ZU6lUTk= +github.com/pierrec/lz4 v2.3.0+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pin/tftp v2.1.0+incompatible/go.mod h1:xVpZOMCXTy+A5QMjEVN0Glwa1sUvaJhFXbr/aAxuxGY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= github.com/pkg/profile v1.3.0/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= github.com/pkg/sftp v1.10.1 h1:VasscCm72135zRysgrJDKsntdmPN+OuU3+nnHYA9wyc= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= +github.com/pkg/xattr v0.4.1 h1:dhclzL6EqOXNaPDWqoeb9tIxATfBSmjqL0b4DpSjwRw= +github.com/pkg/xattr v0.4.1/go.mod h1:W2cGD0TBEus7MkUgv0tNZ9JutLtVO3cXu+IBRuHqnFs= github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -1175,20 +1513,28 @@ github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndr github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= github.com/pquerna/ffjson v0.0.0-20181028064349-e517b90714f7/go.mod h1:YARuvh7BUWHNhzDq2OM5tzR2RiCcN2D7sapiKyCel/M= github.com/pquerna/ffjson v0.0.0-20190813045741-dac163c6c0a9/go.mod h1:YARuvh7BUWHNhzDq2OM5tzR2RiCcN2D7sapiKyCel/M= +github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.49.0/go.mod h1:3WYi4xqXxGGXWDdQIITnLNmuDzO5n6wYva9spVhR4fg= github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= +github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= +github.com/prometheus/client_golang v1.2.1/go.mod h1:XMU6Z2MjaRKVu/dC1qupJI9SiNkDYzz3xecMgSW/F+U= +github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= +github.com/prometheus/client_golang v1.6.0/go.mod h1:ZLOG9ck3JLRdB5MgO8f+lLTe83AXG6ro35rLTxvnIl4= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.9.0/go.mod h1:FqZLKOZnGdFAhOK4nqGHa7D66IdsO+O441Eve7ptJDU= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk= github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= @@ -1198,7 +1544,10 @@ github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8 github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= +github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= +github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4= github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= @@ -1206,17 +1555,25 @@ github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190227231451-bbced9601137/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.0-20190425082905-87a4384529e0/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= +github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= +github.com/prometheus/procfs v0.0.6/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/prometheus/procfs v0.0.11/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/statsd_exporter v0.15.0/go.mod h1:Dv8HnkoLQkeEjkIE4/2ndAA7WL1zHKK7WMqFQqu72rw= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c/go.mod h1:5STLWrekHfjyYwxBRVRXNOSewLJ3PWfDJd1VyTS21fI= +github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M= github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= @@ -1225,15 +1582,22 @@ github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYe github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rs/cors v1.8.2/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= +github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= +github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= +github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= +github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= +github.com/santhosh-tekuri/jsonschema v1.2.4/go.mod h1:TEAUOeZSmIxTTuHatJzrvARHiuO9LYd+cIxzgEHCQI4= github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sebdah/goldie v1.0.0/go.mod h1:jXP4hmWywNEwZzhMuv2ccnqTSFpuq8iyQhtQdkkZBH4= +github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= github.com/securego/gosec v0.0.0-20191002120514-e680875ea14d/go.mod h1:w5+eXa0mYznDkHaMCXA4XYffjlH+cy1oyKbfzJXa2Do= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= @@ -1241,6 +1605,9 @@ github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/shirou/gopsutil v0.0.0-20180427012116-c95755e4bcd7/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc= +github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= +github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY= github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM= github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0= @@ -1267,6 +1634,7 @@ github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYED github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd h1:ug7PpSOB5RBPK1Kg6qskGBoP3Vnj/aNYFTznWvlkGo0= github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw= github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw= +github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/sirupsen/logrus v1.0.5/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= @@ -1276,11 +1644,14 @@ github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrf github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/slok/go-http-metrics v0.8.0/go.mod h1:f22ekj0Ht4taz2clntVmLRSK4D+feX33zkdDW0Eytvk= +github.com/slok/go-http-metrics v0.9.0/go.mod h1:VCio4Xl8m11JM/0Sl9265RdKyiMypzMo3w1M8xcZGtk= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v0.0.0-20190222223459-a17d461953aa/go.mod h1:2RVY1rIf+2J2o/IM9+vPq9RzmHDSseB7FoXiSNIUsoU= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= +github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE= github.com/sourcegraph/go-diff v0.5.1/go.mod h1:j2dHj3m8aZgQO8lMTcTnBcXkRRRqi34cd2MNlA9u1mE= github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= @@ -1292,6 +1663,7 @@ github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z github.com/spf13/cast v1.2.0/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.2/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= @@ -1306,6 +1678,7 @@ github.com/spf13/jwalterweatherman v0.0.0-20180109140146-7c0cea34c8ec/go.mod h1: github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= @@ -1317,6 +1690,9 @@ github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/y github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= +github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= @@ -1330,36 +1706,54 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= +github.com/stripe/safesql v0.2.0/go.mod h1:q7b2n0JmzM1mVGfcYpanfVb2j23cXZeWFxcILPn3JV4= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= +github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= github.com/tchap/go-patricia v2.3.0+incompatible/go.mod h1:bmLyhP68RS6kStMGxByiQ23RP/odRBOTVjwp2cDyi6I= +github.com/thedevsaddam/retry v0.0.0-20200324223450-9769a859cc6d h1:U8ZUZKRBI+Xgo3QWD/alCQ2vIysyl0Efx8yCJWPdaGQ= +github.com/thedevsaddam/retry v0.0.0-20200324223450-9769a859cc6d/go.mod h1:2rz2mY+1qEXG47loLDkV+ZJHGFwmhax5rOTpP+5aR80= +github.com/thoas/go-funk v0.9.1 h1:O549iLZqPpTUQ10ykd26sZhzD+rmR5pWhuElrhbC20M= +github.com/thoas/go-funk v0.9.1/go.mod h1:+IWnUfUmFO1+WVYQWQtIJHeRRdaIyyYglZN7xzUPe4Q= github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/timakin/bodyclose v0.0.0-20190721030226-87058b9bfcec/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tsenart/deadcode v0.0.0-20160724212837-210d2dc333e9/go.mod h1:q+QjxYvZ+fpjMXqs+XEriussHjSYqeXVnAdSV1tkMYk= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= +github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= github.com/ulikunitz/xz v0.5.7/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= -github.com/ulikunitz/xz v0.5.8 h1:ERv8V6GKqVi23rgu5cj9pVfVzJbOqAY2Ntl88O6c2nQ= github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/ulikunitz/xz v0.5.10 h1:t92gobL9l3HE202wg3rlk19F6X+JOxl9BBrCCMYEYd8= +github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/ultraware/funlen v0.0.1/go.mod h1:Dp4UiAus7Wdb9KUZsYWZEWiRzGuM2kXM1lPbfaF6xhA= github.com/ultraware/funlen v0.0.2/go.mod h1:Dp4UiAus7Wdb9KUZsYWZEWiRzGuM2kXM1lPbfaF6xhA= github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasthttp v1.2.0/go.mod h1:4vX61m6KN+xDduDNwXrhIAVZaZaZiQ1luJk8LWSxF3s= +github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= +github.com/valyala/fasttemplate v1.1.0/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= +github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/valyala/quicktemplate v1.1.1/go.mod h1:EH+4AkTd43SvgIbQHYu59/cJyxDoOVRUAfrukLPuGJ4= github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= github.com/vbatts/tar-split v0.11.1/go.mod h1:LEuURwDEiWjRjwu46yU3KVGuUdVv/dcnpcEPSzR8z6g= github.com/vbauerster/mpb/v5 v5.2.2/go.mod h1:W5Fvgw4dm3/0NhqzV8j6EacfuTe5SvnzBRwiXxDR9ww= +github.com/vbauerster/mpb/v5 v5.3.0/go.mod h1:4yTkvAb8Cm4eylAp6t0JRq6pXDkFJ4krUlDqWYkakAs= github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw= github.com/vincent-petithory/dataurl v0.0.0-20160330182126-9a301d65acbb/go.mod h1:FHafX5vmDzyP+1CQATJn7WFKc9CvnvxyvZy6I1MrG/U= -github.com/vincent-petithory/dataurl v0.0.0-20191104211930-d1553a71de50 h1:uxE3GYdXIOfhMv3unJKETJEhw78gvzuQqRX/rVirc2A= -github.com/vincent-petithory/dataurl v0.0.0-20191104211930-d1553a71de50/go.mod h1:FHafX5vmDzyP+1CQATJn7WFKc9CvnvxyvZy6I1MrG/U= +github.com/vincent-petithory/dataurl v1.0.0 h1:cXw+kPto8NLuJtlMsI152irrVw9fRDX8AbShPRpg2CI= +github.com/vincent-petithory/dataurl v1.0.0/go.mod h1:FHafX5vmDzyP+1CQATJn7WFKc9CvnvxyvZy6I1MrG/U= github.com/vishvananda/netlink v1.0.0/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= +github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= @@ -1370,6 +1764,7 @@ github.com/vmware/govmomi v0.27.4 h1:5kY8TAkhB20lsjzrjE073eRb8+HixBI29PVMG5lxq6I github.com/vmware/govmomi v0.27.4/go.mod h1:daTuJEcQosNMXYJOeku0qdBJP9SOLLWB3Mqz8THtv6o= github.com/vmware/vmw-guestinfo v0.0.0-20170707015358-25eff159a728/go.mod h1:x9oS4Wk2s2u4tS29nEaDLdzvuHdB19CvSGJjPgkZJNk= github.com/vmware/vmw-ovflib v0.0.0-20170608004843-1f217b9dc714/go.mod h1:jiPk45kn7klhByRvUq5i2vo1RtHKBHj+iWGFpxbXuuI= +github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= github.com/xanzy/ssh-agent v0.3.0 h1:wUMzuKtKilRgBAD1sUb8gOwwRr2FGoBVumcjoOACClI= github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= @@ -1379,8 +1774,11 @@ github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhe github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonpointer v0.0.0-20190809123943-df4f5c81cb3b/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1/go.mod h1:QcJo0QPSfTONNIgpN5RA8prR7fF8nkF6cTWTcNerRO8= @@ -1401,6 +1799,11 @@ github.com/zclconf/go-cty v1.9.1/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUA github.com/zclconf/go-cty v1.10.0 h1:mp9ZXQeIcN8kAwuqorjH+Q+njbJKjLrvB2yIh4q7U+0= github.com/zclconf/go-cty v1.10.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8= +github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= +go.elastic.co/apm v1.15.0/go.mod h1:dylGv2HKR0tiCV+wliJz1KHtDyuD8SPe69oV7VyK6WY= +go.elastic.co/apm/module/apmhttp v1.15.0/go.mod h1:NruY6Jq8ALLzWUVUQ7t4wIzn+onKoiP5woJJdTV7GMg= +go.elastic.co/apm/module/apmlogrus v1.15.0/go.mod h1:mvs7soORJBrUyqNgj+9/5f8Z8//W1L849RkCF7Thhu0= +go.elastic.co/fastjson v1.1.0/go.mod h1:boNGISWMjQsUPy/t6yqt2/1Wx4YNPSe+mZjlyw9vKKI= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= @@ -1433,7 +1836,10 @@ go.mongodb.org/mongo-driver v1.7.3/go.mod h1:NqaYOwnXWr5Pm7AOpO5QFxKJ503nbMse/R7 go.mongodb.org/mongo-driver v1.7.5/go.mod h1:VXEWRZ6URJIkUq2SCAyapmhH0ZLRBP+FT4xhp5Zvxng= go.mongodb.org/mongo-driver v1.8.3 h1:TDKlTkGDKm9kkJVUOAXDK5/fkqKHJVwYQSpoRfB43R4= go.mongodb.org/mongo-driver v1.8.3/go.mod h1:0sQWfOeY63QTntERDJJ/0SuKK0T1uVSgKCuAROlKEPY= +go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= +go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -1457,15 +1863,22 @@ go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqe go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5/go.mod h1:nmDLcffg48OtT/PSW0Hg7FvpRQsQh5OSqIylirxKC7o= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= @@ -1473,8 +1886,10 @@ go.uber.org/zap v1.19.1 h1:ue41HOKd1vGURxrmeKIgELGb3jPW9DMUDGtsinblHwI= go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= go4.org v0.0.0-20191010144846-132d2879e1e9/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= go4.org v0.0.0-20200104003542-c7e774b10ea0/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= +goji.io v2.0.2+incompatible/go.mod h1:sbqFwrtqZACxLBTQcdgVjFh54yGVCvwq8+w49MVMMIk= golang.org/x/arch v0.0.0-20180920145803-b19384d3c130/go.mod h1:cYlCBUl1MsqxdiKgmc4uh7TxZfWSFLOGSRR090WDxt8= golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw= +golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -1485,6 +1900,8 @@ golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190418165655-df01cb2cc480/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -1495,19 +1912,27 @@ golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200423211502-4bdfaf469ed5/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211202192323-5770296d904e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220214200702-86341886e292 h1:f+lwQ+GtmgoY+A2YaQxlSOnDjXcQ7ZRLWOHbC6HtRqE= @@ -1557,6 +1982,7 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVD golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20170915142106-8351a756f30f/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1571,6 +1997,7 @@ golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190228165749-92fc7df08ae7/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -1597,6 +2024,7 @@ golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200421231249-e086a090c8fd/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= @@ -1663,6 +2091,8 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20171026204733-164713f0dfce/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1670,10 +2100,12 @@ golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20180903190138-2b024373dcd9/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181021155630-eda9bb28ed51/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1690,10 +2122,13 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190514135907-3a4b5fb9f71f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1702,12 +2137,16 @@ golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191002063906-3421d5a6bb1c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191025021431-6c3a3bfe00ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191110163157-d32e6e3b99c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191113165036-4c7a9d0fe056/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1719,7 +2158,9 @@ golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200327173247-9dae0f8f5775/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1730,17 +2171,24 @@ golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1757,6 +2205,7 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210817190340-bfb29a6856f2/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= @@ -1767,6 +2216,7 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab h1:2QkjZIsXupsJbJIdSjjUOgWK3 golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -1815,6 +2265,7 @@ golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190521203540-521d6ed310dd/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= @@ -1826,11 +2277,14 @@ golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgw golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190909030654-5b82db07426d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190930201159-7c411dea38b0/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -1840,7 +2294,8 @@ golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200115044656-831fdb1e1868/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200102200121-6de373a2766c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= @@ -1869,6 +2324,7 @@ golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201020123448-f5c826d1900e/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= @@ -1883,10 +2339,14 @@ golang.org/x/tools v0.1.6-0.20210820212750-d4cc65f0b2ff/go.mod h1:YD9qOF0M9xpSpd golang.org/x/tools v0.1.10-0.20220218145154-897bd77cd717/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gomodules.xyz/jsonpatch/v2 v2.0.1/go.mod h1:IhYNNY4jnS53ZnfE4PAmpKtDpTCj1JFXc+3mwe7XcUU= +gomodules.xyz/jsonpatch/v2 v2.1.0/go.mod h1:IhYNNY4jnS53ZnfE4PAmpKtDpTCj1JFXc+3mwe7XcUU= gomodules.xyz/jsonpatch/v2 v2.2.0 h1:4pT439QV83L+G9FkcCriY6EkpcK6r6bK+A5FBUMI7qY= gomodules.xyz/jsonpatch/v2 v2.2.0/go.mod h1:WXp+iVDkoLQqPudfQ9GBlwB2eZ5DKOnjQZCYdOS8GPY= gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0= @@ -1895,6 +2355,7 @@ gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e/go.mod h1:kS+toOQn6AQKjmK google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y= +google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= @@ -1912,6 +2373,7 @@ google.golang.org/api v0.26.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.33.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= @@ -1939,6 +2401,7 @@ google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRn google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= @@ -1991,9 +2454,11 @@ google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmE google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= @@ -2043,6 +2508,8 @@ gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= +gopkg.in/djherbis/times.v1 v1.2.0 h1:UCvDKl1L/fmBygl2Y7hubXCnY7t4Yj46ZrBFNUipFbM= +gopkg.in/djherbis/times.v1 v1.2.0/go.mod h1:AQlg6unIsrsCEdQYhTzERy542dz6SFdQFZFv6mUY0P8= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gcfg.v1 v1.2.3 h1:m8OOJ4ccYHnx2f4gQwpno8nAX5OGOh7RLaaz0pj3Ogs= @@ -2053,17 +2520,20 @@ gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8 gopkg.in/go-playground/validator.v9 v9.30.0/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= gopkg.in/go-playground/validator.v9 v9.31.0 h1:bmXmP2RSNtFES+bn4uYuHT7iJFJv7Vj+an+ZQdDaD1M= gopkg.in/go-playground/validator.v9 v9.31.0/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= +gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= +gopkg.in/inf.v0 v0.9.0/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 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.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/ini.v1 v1.66.2 h1:XfR1dOYubytKy4Shzc2LHrrGhU0lDCfDGG1yLPmpgsI= -gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.66.4 h1:SsAcf+mM7mRZo2nJNGt8mZCjG8ZRaNGMURJw7BsIST4= +gopkg.in/ini.v1 v1.66.4/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ldap.v2 v2.5.1/go.mod h1:oI0cpe/D7HRtBQl8aTg+ZmzFUAvu4lsv3eLXMLGFxWk= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/src-d/go-billy.v4 v4.3.0/go.mod h1:tm33zBoOwxjYHZIE+OV8bxTWFMJLrconzFMd38aARFk= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= @@ -2075,6 +2545,7 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.7/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= @@ -2088,6 +2559,16 @@ gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/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= +gorm.io/driver/mysql v1.0.1/go.mod h1:KtqSthtg55lFp3S5kUXqlGaelnWpKitn4k1xZTnoiPw= +gorm.io/driver/postgres v1.0.0/go.mod h1:wtMFcOzmuA5QigNsgEIb7O5lhvH1tHAF1RbWmLWV4to= +gorm.io/driver/postgres v1.2.3 h1:f4t0TmNMy9gh3TU2PX+EppoA6YsgFnyq8Ojtddb42To= +gorm.io/driver/postgres v1.2.3/go.mod h1:pJV6RgYQPG47aM1f0QeOzFH9HxQc8JcmAgjRCgS0wjs= +gorm.io/driver/sqlite v1.1.1/go.mod h1:hm2olEcl8Tmsc6eZyxYSeznnsDaMqamBvEXLNtBg4cI= +gorm.io/driver/sqlserver v1.0.2/go.mod h1:gb0Y9QePGgqjzrVyTQUZeh9zkd5v0iz71cM1B4ZycEY= +gorm.io/gorm v1.9.19/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw= +gorm.io/gorm v1.20.0/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw= +gorm.io/gorm v1.22.3 h1:/JS6z+GStEQvJNW3t1FTwJwG/gZ+A7crFdRqtvG5ehA= +gorm.io/gorm v1.22.3/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= @@ -2100,12 +2581,24 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0= k8s.io/api v0.0.0-20190409021203-6e4e0e4f393b/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA= +k8s.io/api v0.0.0-20190725062911-6607c48751ae/go.mod h1:1O0xzX/RAtnm7l+5VEUxZ1ysO2ghatfq/OZED4zM9kA= k8s.io/api v0.18.0-beta.2/go.mod h1:2oeNnWEqcSmaM/ibSh3t7xcIqbkGXhzZdn4ezV9T4m0= +k8s.io/api v0.18.0-rc.1/go.mod h1:ZOh6SbHjOYyaMLlWmB2+UOQKEWDpCnVEVpEyt7S2J9s= k8s.io/api v0.18.0/go.mod h1:q2HRQkfDzHMBZL9l/y9rH63PkQl4vae0xRT+8prbrK8= k8s.io/api v0.18.2/go.mod h1:SJCWI7OLzhZSvbY7U8zwNl9UA4o1fizoug34OV/2r78= +k8s.io/api v0.18.3/go.mod h1:UOaMwERbqJMfeeeHc8XJKawj4P9TgDRnViIqqBeH2QA= +k8s.io/api v0.18.6/go.mod h1:eeyxr+cwCjMdLAmr2W3RyDI0VvTawSg/3RFFBEnmZGI= k8s.io/api v0.19.0/go.mod h1:I1K45XlvTrDjmj5LoM5LuP/KYrhWbjUKT/SoPG0qTjw= +k8s.io/api v0.19.2/go.mod h1:IQpK0zFQ1xc5iNIQPqzgoOwuFugaYHK4iCknlAQP9nI= +k8s.io/api v0.19.5/go.mod h1:yGZReuNa0vj56op6eT+NLrXJne0R0u9ktexZ8jdJzpc= +k8s.io/api v0.20.0/go.mod h1:HyLC5l5eoS/ygQYl1BXBgFzWNlkHiAuyNAbevIn+FKg= +k8s.io/api v0.21.0-rc.0/go.mod h1:Dkc/ZauWJrgZhjOjeBgW89xZQiTBJA2RaBKYHXPsi2Y= +k8s.io/api v0.21.0/go.mod h1:+YbrhBBGgsxbF6o6Kj4KJPJnBmAKuXDeS3E18bgHNVU= +k8s.io/api v0.21.1/go.mod h1:FstGROTmsSHBarKc8bylzXih8BLNYTiS3TZcsoEDg2s= k8s.io/api v0.21.3/go.mod h1:hUgeYHUbBp23Ue4qdX9tR8/ANi/g3ehylAqDn9NWVOg= +k8s.io/api v0.21.4/go.mod h1:fTVGP+M4D8+00FN2cMnJqk/eb/GH53bvmNs2SVTmpFk= k8s.io/api v0.22.0-rc.0/go.mod h1:EUcKB6RvpW74HMRUSSNwpUzrIHBdGT1FeAvOV+txic0= k8s.io/api v0.22.0/go.mod h1:0AoXXqst47OI/L0oGKq9DG61dvGRPXs7X4/B7KyjBCU= k8s.io/api v0.22.1/go.mod h1:bh13rkTp3F1XEaLGykbyRD2QaTTzPm0e/BMd8ptFONY= @@ -2116,17 +2609,34 @@ k8s.io/api v0.24.3/go.mod h1:elGR/XSZrS7z7cSZPzVWaycpJuGIw57j9b95/1PdJNI= k8s.io/apiextensions-apiserver v0.0.0-20190409022649-727a075fdec8/go.mod h1:IxkesAMoaCRoLrPJdZNZUQp9NfZnzqaVzLhb2VEQzXE= k8s.io/apiextensions-apiserver v0.18.0-beta.2/go.mod h1:Hnrg5jx8/PbxRbUoqDGxtQkULjwx8FDW4WYJaKNK+fk= k8s.io/apiextensions-apiserver v0.18.2/go.mod h1:q3faSnRGmYimiocj6cHQ1I3WpLqmDgJFlKL37fC4ZvY= +k8s.io/apiextensions-apiserver v0.18.3/go.mod h1:TMsNGs7DYpMXd+8MOCX8KzPOCx8fnZMoIGB24m03+JE= +k8s.io/apiextensions-apiserver v0.18.6/go.mod h1:lv89S7fUysXjLZO7ke783xOwVTm6lKizADfvUM/SS/M= k8s.io/apiextensions-apiserver v0.19.0/go.mod h1:znfQxNpjqz/ZehvbfMg5N6fvBJW5Lqu5HVLTJQdP4Fs= +k8s.io/apiextensions-apiserver v0.21.0-rc.0/go.mod h1:ItIoMBJU1gy93Qwr/B2699r4b0VmZqAOU+15BvozxMY= +k8s.io/apiextensions-apiserver v0.21.0/go.mod h1:gsQGNtGkc/YoDG9loKI0V+oLZM4ljRPjc/sql5tmvzc= +k8s.io/apiextensions-apiserver v0.21.1/go.mod h1:KESQFCGjqVcVsZ9g0xX5bacMjyX5emuWcS2arzdEouA= k8s.io/apiextensions-apiserver v0.21.3/go.mod h1:kl6dap3Gd45+21Jnh6utCx8Z2xxLm8LGDkprcd+KbsE= +k8s.io/apiextensions-apiserver v0.21.4/go.mod h1:OoC8LhI9LnV+wKjZkXIBbLUwtnOGJiTRE33qctH5CIk= k8s.io/apiextensions-apiserver v0.22.0-rc.0/go.mod h1:KSr+2VJ6ye8Fy50q7xHZ/Tw8vrRII82KIKbz9eUFmeo= k8s.io/apiextensions-apiserver v0.24.3 h1:kyx+Tmro1qEsTUr07ZGQOfvTsF61yn+AxnxytBWq8As= k8s.io/apiextensions-apiserver v0.24.3/go.mod h1:cL0xkmUefpYM4f6IuOau+6NMFEIh6/7wXe/O4vPVJ8A= k8s.io/apimachinery v0.0.0-20190404173353-6a84e37a896d/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0= +k8s.io/apimachinery v0.0.0-20190719140911-bfcf53abc9f8/go.mod h1:sBJWIJZfxLhp7mRsRyuAE/NfKTr3kXGR1iaqg8O0gJo= k8s.io/apimachinery v0.18.0-beta.2/go.mod h1:9SnR/e11v5IbyPCGbvJViimtJ0SwHG4nfZFjU77ftcA= +k8s.io/apimachinery v0.18.0-rc.1/go.mod h1:9SnR/e11v5IbyPCGbvJViimtJ0SwHG4nfZFjU77ftcA= k8s.io/apimachinery v0.18.0/go.mod h1:9SnR/e11v5IbyPCGbvJViimtJ0SwHG4nfZFjU77ftcA= k8s.io/apimachinery v0.18.2/go.mod h1:9SnR/e11v5IbyPCGbvJViimtJ0SwHG4nfZFjU77ftcA= +k8s.io/apimachinery v0.18.3/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCko= +k8s.io/apimachinery v0.18.6/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCko= k8s.io/apimachinery v0.19.0/go.mod h1:DnPGDnARWFvYa3pMHgSxtbZb7gpzzAZ1pTfaUNDVlmA= +k8s.io/apimachinery v0.19.2/go.mod h1:DnPGDnARWFvYa3pMHgSxtbZb7gpzzAZ1pTfaUNDVlmA= +k8s.io/apimachinery v0.19.5/go.mod h1:6sRbGRAVY5DOCuZwB5XkqguBqpqLU6q/kOaOdk29z6Q= +k8s.io/apimachinery v0.20.0/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= +k8s.io/apimachinery v0.21.0-rc.0/go.mod h1:jbreFvJo3ov9rj7eWT7+sYiRx+qZuCYXwWT1bcDswPY= +k8s.io/apimachinery v0.21.0/go.mod h1:jbreFvJo3ov9rj7eWT7+sYiRx+qZuCYXwWT1bcDswPY= +k8s.io/apimachinery v0.21.1/go.mod h1:jbreFvJo3ov9rj7eWT7+sYiRx+qZuCYXwWT1bcDswPY= k8s.io/apimachinery v0.21.3/go.mod h1:H/IM+5vH9kZRNJ4l3x/fXP/5bOPJaVP/guptnZPeCFI= +k8s.io/apimachinery v0.21.4/go.mod h1:H/IM+5vH9kZRNJ4l3x/fXP/5bOPJaVP/guptnZPeCFI= k8s.io/apimachinery v0.22.0-rc.0/go.mod h1:O3oNtNadZdeOMxHFVxOreoznohCpy0z6mocxbZr7oJ0= k8s.io/apimachinery v0.22.0/go.mod h1:O3oNtNadZdeOMxHFVxOreoznohCpy0z6mocxbZr7oJ0= k8s.io/apimachinery v0.22.1/go.mod h1:O3oNtNadZdeOMxHFVxOreoznohCpy0z6mocxbZr7oJ0= @@ -2136,35 +2646,65 @@ k8s.io/apimachinery v0.24.3 h1:hrFiNSA2cBZqllakVYyH/VyEh4B581bQRmqATJSeQTg= k8s.io/apimachinery v0.24.3/go.mod h1:82Bi4sCzVBdpYjyI4jY6aHX+YCUchUIrZrXKedjd2UM= k8s.io/apiserver v0.18.0-beta.2/go.mod h1:bnblMkMoCFnIfVnVftd0SXJPzyvrk3RtaqSbblphF/A= k8s.io/apiserver v0.18.2/go.mod h1:Xbh066NqrZO8cbsoenCwyDJ1OSi8Ag8I2lezeHxzwzw= +k8s.io/apiserver v0.18.3/go.mod h1:tHQRmthRPLUtwqsOnJJMoI8SW3lnoReZeE861lH8vUw= +k8s.io/apiserver v0.18.6/go.mod h1:Zt2XvTHuaZjBz6EFYzpp+X4hTmgWGy8AthNVnTdm3Wg= k8s.io/apiserver v0.19.0/go.mod h1:XvzqavYj73931x7FLtyagh8WibHpePJ1QwWrSJs2CLk= +k8s.io/apiserver v0.20.0/go.mod h1:6gRIWiOkvGvQt12WTYmsiYoUyYW0FXSiMdNl4m+sxY8= +k8s.io/apiserver v0.21.0-rc.0/go.mod h1:QlW7+1CZTZtAcKvJ34/n4DIb8sC93FeQpkd1KSU+Sok= +k8s.io/apiserver v0.21.0/go.mod h1:w2YSn4/WIwYuxG5zJmcqtRdtqgW/J2JRgFAqps3bBpg= +k8s.io/apiserver v0.21.1/go.mod h1:nLLYZvMWn35glJ4/FZRhzLG/3MPxAaZTgV4FJZdr+tY= k8s.io/apiserver v0.21.3/go.mod h1:eDPWlZG6/cCCMj/JBcEpDoK+I+6i3r9GsChYBHSbAzU= +k8s.io/apiserver v0.21.4/go.mod h1:SErUuFBBPZUcD2nsUU8hItxoYheqyYr2o/pCINEPW8g= k8s.io/apiserver v0.22.0-rc.0/go.mod h1:1AfFSkRbaPVFzfSIWd0m/onp49mmAOqXR9qrLJFixlw= k8s.io/apiserver v0.22.0/go.mod h1:04kaIEzIQrTGJ5syLppQWvpkLJXQtJECHmae+ZGc/nc= k8s.io/apiserver v0.24.3/go.mod h1:aXfwtIn4U27B7lYs5f2BKgz6DRbgWy+HJeYReN1jLJ8= +k8s.io/cli-runtime v0.18.0-rc.1/go.mod h1:yuKZYDG8raONmwjwIkT77lCfIuPwX+Bsp88MKYf1TlU= k8s.io/cli-runtime v0.19.0/go.mod h1:tun9l0eUklT8IHIM0jors17KmUjcrAxn0myoBYwuNuo= +k8s.io/cli-runtime v0.21.0/go.mod h1:XoaHP93mGPF37MkLbjGVYqg3S1MnsFdKtiA/RZzzxOo= k8s.io/cli-runtime v0.22.0/go.mod h1:An6zELQ7udUI0GaXvkuMqyopPA14dIgNqpH8cZu1vig= k8s.io/client-go v0.24.0 h1:lbE4aB1gTHvYFSwm6eD3OF14NhFDKCejlnsGYlSJe5U= k8s.io/client-go v0.24.0/go.mod h1:VFPQET+cAFpYxh6Bq6f4xyMY80G6jKKktU6G0m00VDw= k8s.io/cluster-bootstrap v0.0.0-20190202014938-c9acc0c1bea2/go.mod h1:iBSm2nwo3OaiuW8VDvc3ySDXK5SKfUrxwPvBloKG7zg= +k8s.io/code-generator v0.0.0-20190717022600-77f3a1fe56bb/go.mod h1:cDx5jQmWH25Ff74daM7NVYty9JWw9dvIS9zT9eIubCY= k8s.io/code-generator v0.0.0-20191003035328-700b1226c0bd/go.mod h1:HC9p4y3SBN+txSs8x57qmNPXFZ/CxdCHiDTNnocCSEw= k8s.io/code-generator v0.18.0-beta.2/go.mod h1:+UHX5rSbxmR8kzS+FAv7um6dtYrZokQvjHpDSYRVkTc= +k8s.io/code-generator v0.18.0-rc.1/go.mod h1:+UHX5rSbxmR8kzS+FAv7um6dtYrZokQvjHpDSYRVkTc= k8s.io/code-generator v0.18.0/go.mod h1:+UHX5rSbxmR8kzS+FAv7um6dtYrZokQvjHpDSYRVkTc= k8s.io/code-generator v0.18.2/go.mod h1:+UHX5rSbxmR8kzS+FAv7um6dtYrZokQvjHpDSYRVkTc= +k8s.io/code-generator v0.18.3/go.mod h1:TgNEVx9hCyPGpdtCWA34olQYLkh3ok9ar7XfSsr8b6c= +k8s.io/code-generator v0.18.6/go.mod h1:TgNEVx9hCyPGpdtCWA34olQYLkh3ok9ar7XfSsr8b6c= k8s.io/code-generator v0.19.0/go.mod h1:moqLn7w0t9cMs4+5CQyxnfA/HV8MF6aAVENF+WZZhgk= +k8s.io/code-generator v0.19.2/go.mod h1:moqLn7w0t9cMs4+5CQyxnfA/HV8MF6aAVENF+WZZhgk= +k8s.io/code-generator v0.20.0/go.mod h1:UsqdF+VX4PU2g46NC2JRs4gc+IfrctnwHb76RNbWHJg= +k8s.io/code-generator v0.21.0-rc.0/go.mod h1:hUlps5+9QaTrKx+jiM4rmq7YmH8wPOIko64uZCHDh6Q= +k8s.io/code-generator v0.21.0/go.mod h1:hUlps5+9QaTrKx+jiM4rmq7YmH8wPOIko64uZCHDh6Q= +k8s.io/code-generator v0.21.1/go.mod h1:hUlps5+9QaTrKx+jiM4rmq7YmH8wPOIko64uZCHDh6Q= k8s.io/code-generator v0.21.3/go.mod h1:K3y0Bv9Cz2cOW2vXUrNZlFbflhuPvuadW6JdnN6gGKo= +k8s.io/code-generator v0.21.4/go.mod h1:K3y0Bv9Cz2cOW2vXUrNZlFbflhuPvuadW6JdnN6gGKo= k8s.io/code-generator v0.22.0-rc.0/go.mod h1:eV77Y09IopzeXOJzndrDyCI88UBok2h6WxAlBwpxa+o= k8s.io/code-generator v0.22.0/go.mod h1:eV77Y09IopzeXOJzndrDyCI88UBok2h6WxAlBwpxa+o= k8s.io/code-generator v0.22.1/go.mod h1:eV77Y09IopzeXOJzndrDyCI88UBok2h6WxAlBwpxa+o= k8s.io/code-generator v0.23.0/go.mod h1:vQvOhDXhuzqiVfM/YHp+dmg10WDZCchJVObc9MvowsE= +k8s.io/code-generator v0.24.0/go.mod h1:dpVhs00hTuTdTY6jvVxvTFCk6gSMrtfRydbhZwHI15w= k8s.io/code-generator v0.24.3/go.mod h1:dpVhs00hTuTdTY6jvVxvTFCk6gSMrtfRydbhZwHI15w= k8s.io/component-base v0.18.0-beta.2/go.mod h1:HVk5FpRnyzQ/MjBr9//e/yEBjTVa2qjGXCTuUzcD7ks= +k8s.io/component-base v0.18.0-rc.1/go.mod h1:NNlRaxZEdLqTs2+6yXiU2SHl8gKsbcy19Ii+Sfq53RM= k8s.io/component-base v0.18.2/go.mod h1:kqLlMuhJNHQ9lz8Z7V5bxUUtjFZnrypArGl58gmDfUM= +k8s.io/component-base v0.18.3/go.mod h1:bp5GzGR0aGkYEfTj+eTY0AN/vXTgkJdQXjNTTVUaa3k= +k8s.io/component-base v0.18.6/go.mod h1:knSVsibPR5K6EW2XOjEHik6sdU5nCvKMrzMt2D4In14= k8s.io/component-base v0.19.0/go.mod h1:dKsY8BxkA+9dZIAh2aWJLL/UdASFDNtGYTCItL4LM7Y= +k8s.io/component-base v0.19.5/go.mod h1:5N/uv5A7fyr0d+t/b1HynXKkUVPEhc8ljkMaBJv4Tp8= +k8s.io/component-base v0.20.0/go.mod h1:wKPj+RHnAr8LW2EIBIK7AxOHPde4gme2lzXwVSoRXeA= +k8s.io/component-base v0.21.0-rc.0/go.mod h1:XlP0bM7QJFWRGZYPc5NmphkvsYQ+o7804HWH3GTGjDY= +k8s.io/component-base v0.21.0/go.mod h1:qvtjz6X0USWXbgmbfXR+Agik4RZ3jv2Bgr5QnZzdPYw= +k8s.io/component-base v0.21.1/go.mod h1:NgzFZ2qu4m1juby4TnrmpR8adRk6ka62YdH5DkIIyKA= k8s.io/component-base v0.21.3/go.mod h1:kkuhtfEHeZM6LkX0saqSK8PbdO7A0HigUngmhhrwfGQ= +k8s.io/component-base v0.21.4/go.mod h1:ZKG0eHVX+tUDcaoIGpU3Vtk4TIjMddN9uhEWDmW6Nyg= k8s.io/component-base v0.22.0-rc.0/go.mod h1:DKSub/kewg24bK+3ZJ/csu86fSBYpGdYk837eCTvEKg= k8s.io/component-base v0.22.0/go.mod h1:SXj6Z+V6P6GsBhHZVbWCw9hFjUdUYnJerlhhPnYCBCg= k8s.io/component-base v0.24.3 h1:u99WjuHYCRJjS1xeLOx72DdRaghuDnuMgueiGMFy1ec= k8s.io/component-base v0.24.3/go.mod h1:bqom2IWN9Lj+vwAkPNOv2TflsP1PeVDIwIN0lRthxYY= +k8s.io/component-helpers v0.21.0/go.mod h1:tezqefP7lxfvJyR+0a+6QtVrkZ/wIkyMLK4WcQ3Cj8U= k8s.io/component-helpers v0.22.0/go.mod h1:YNIbQI59ayNiU8JHlPIxVkOUYycbKhk5Niy0pcyJOEY= k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20190822140433-26a664648505/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= @@ -2172,16 +2712,19 @@ k8s.io/gengo v0.0.0-20190907103519-ebc107f98eab/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8 k8s.io/gengo v0.0.0-20200114144118-36b2048a9120/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20200428234225-8167cfdcfc14/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/gengo v0.0.0-20201113003025-83324d819ded/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= k8s.io/gengo v0.0.0-20201214224949-b6c5ce23f027/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= 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/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/klog v0.3.1/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= 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.3.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= +k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/klog/v2 v2.8.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= k8s.io/klog/v2 v2.30.0/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= @@ -2189,29 +2732,43 @@ k8s.io/klog/v2 v2.60.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/klog/v2 v2.70.1 h1:7aaoSdahviPmR+XkS7FyxlkkXs6tHISSG03RxleQAVQ= k8s.io/klog/v2 v2.70.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-aggregator v0.18.0-beta.2/go.mod h1:O3Td9mheraINbLHH4pzoFP2gRzG0Wk1COqzdSL4rBPk= +k8s.io/kube-aggregator v0.18.2/go.mod h1:ijq6FnNUoKinA6kKbkN6svdTacSoQVNtKqmQ1+XJEYQ= k8s.io/kube-aggregator v0.19.0/go.mod h1:1Ln45PQggFAG8xOqWPIYMxUq8WNtpPnYsbUJ39DpF/A= +k8s.io/kube-aggregator v0.20.0/go.mod h1:3Is/gzzWmhhG/rA3CpA1+eVye87lreBQDFGcAGT7gzo= +k8s.io/kube-aggregator v0.21.0-rc.0/go.mod h1:M+whOmsAeQf8ObJ0/eO9Af1Dz2UQEB9OW9BWmt9b2sU= k8s.io/kube-aggregator v0.22.0-rc.0/go.mod h1:g0xtiBSsbMKvewN7xR/Icib4TrHxtvrJcHtYvFsgw7k= k8s.io/kube-openapi v0.0.0-20180731170545-e3762e86a74c/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc= k8s.io/kube-openapi v0.0.0-20190228160746-b3a7cee44a30/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc= +k8s.io/kube-openapi v0.0.0-20190709113604-33be087ad058/go.mod h1:nfDlWeOsu3pUf4yWGL+ERqohP4YsZcBJXWMK+gkzOA4= k8s.io/kube-openapi v0.0.0-20190816220812-743ec37842bf/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= k8s.io/kube-openapi v0.0.0-20200121204235-bf4fb3bd569c/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E= +k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E= k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6/go.mod h1:UuqjUnNftUyPE5H64/qeyjQoUZhGpeFDVdxjTeEVN2o= +k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM= k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7/go.mod h1:wXW5VT87nVfh/iLV8FpR2uDvrFyomxbtb1KivDbvPTE= k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65/go.mod h1:sX9MT8g7NVZM5lVL/j8QyCCJe8YSMW30QvGZWaCIDIk= k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42 h1:Gii5eqf+GmIEwGNKQYQClCayuJCe2/4fZUvF7VG99sU= k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42/go.mod h1:Z/45zLw8lUo4wdiUkI+v/ImEGAvu3WatcZl3lPMR4Rk= +k8s.io/kubectl v0.18.0-rc.1/go.mod h1:UpG1w7klD633nyMS73/29cNl2tMdEbXU0nWupttyha4= k8s.io/kubectl v0.19.0/go.mod h1:gPCjjsmE6unJzgaUNXIFGZGafiUp5jh0If3F/x7/rRg= +k8s.io/kubectl v0.21.0/go.mod h1:EU37NukZRXn1TpAkMUoy8Z/B2u6wjHDS4aInsDzVvks= k8s.io/kubectl v0.22.0/go.mod h1:eeuP92uZbVL2UnOaf0nj9OjtI0hi/nekHU+0isURer0= k8s.io/kubelet v0.19.0/go.mod h1:cGds22piF/LnFzfAaIT+efvOYBHVYdunqka6NVuNw9g= +k8s.io/metrics v0.18.0-rc.1/go.mod h1:ME3EkXCyiZ7mVFEiAYKBfuo3JkpgggeATG+DBUQby5o= k8s.io/metrics v0.19.0/go.mod h1:WykpW8B60OeAJx1imdwUgyOID2kDljr/Q+1zrPJ98Wo= +k8s.io/metrics v0.21.0/go.mod h1:L3Ji9EGPP1YBbfm9sPfEXSpnj8i24bfQbAFAsW0NueQ= k8s.io/metrics v0.22.0/go.mod h1:eYnwafAUNLLpVmY/msoq0RKIKH5C4TzfjKnMZ0Xrt3A= k8s.io/utils v0.0.0-20190506122338-8fab8cb257d5/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= k8s.io/utils v0.0.0-20190529001817-6999998975a7/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= k8s.io/utils v0.0.0-20200229041039-0a110f9eb7ab/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= +k8s.io/utils v0.0.0-20200327001022-6496210b90e8/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= +k8s.io/utils v0.0.0-20200603063816-c1c6865ac451/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20200729134348-d5654de09c73/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20210111153108-fddb29f9d009/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20210527160623-6fdb442a123b/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20210707171843-4b05e18ac7d9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20210722164352-7f3ee0f31471/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= @@ -2232,12 +2789,20 @@ rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.7/go.mod h1:PHgbrJT7lCHcxMU+mDHEm+nx46H4zuuHZkDP6icnhu0= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.9/go.mod h1:dzAXnQbTRyDlZPJX2SUPEqvnB+j7AJjtlox7PEwigU0= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.14/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.15/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.19/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.21/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.22/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.30/go.mod h1:fEO7lRTdivWO2qYVCVG7dEADOMo/MLDCVr8So2g88Uw= sigs.k8s.io/controller-runtime v0.0.0-20190520212815-96b67f231945/go.mod h1:TSH2R0nSz4WAlUUlNnOFcOR/VUhfwBLlmtq2X6AiQCA= +sigs.k8s.io/controller-runtime v0.6.0/go.mod h1:CpYf5pdNY/B352A1TFLAS2JVSlnGQ5O2cftPHndTroo= +sigs.k8s.io/controller-runtime v0.6.2/go.mod h1:vhcq/rlnENJ09SIRp3EveTaZ0yqH526hjf9iJdbUJ/E= +sigs.k8s.io/controller-runtime v0.9.0-alpha.1.0.20210413130450-7ef2da0bc161/go.mod h1:ufPDuvefw2Y1KnBgHQrLdOjueYlj+XJV2AszbT+WTxs= +sigs.k8s.io/controller-runtime v0.9.0-beta.1.0.20210512131817-ce2f0c92d77e/go.mod h1:ufPDuvefw2Y1KnBgHQrLdOjueYlj+XJV2AszbT+WTxs= +sigs.k8s.io/controller-runtime v0.9.0/go.mod h1:TgkfvrhhEw3PlI0BRL/5xM+89y3/yc0ZDfdbTl84si8= sigs.k8s.io/controller-runtime v0.9.6/go.mod h1:q6PpkM5vqQubEKUKOM6qr06oXGzOBcCby1DA9FbyZeA= +sigs.k8s.io/controller-runtime v0.9.7/go.mod h1:nExcHcQ2zvLMeoO9K7rOesGCmgu32srN5SENvpAEbGA= sigs.k8s.io/controller-runtime v0.11.0 h1:DqO+c8mywcZLFJWILq4iktoECTyn30Bkj0CwgqMpZWQ= sigs.k8s.io/controller-runtime v0.11.0/go.mod h1:KKwLiTooNGu+JmLZGn9Sl3Gjmfj66eMbCQznLP5zcqA= sigs.k8s.io/controller-tools v0.3.1-0.20200617211605-651903477185 h1:wLsmaqTEgs3DIfNzr0u/AfPHSVJbWHj/eevcS4AFvFE= @@ -2246,17 +2811,24 @@ sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6/go.mod h1:p4QtZmO4uMYipTQNza sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2/go.mod h1:B+TnT182UBxE84DiCz4CVE26eOSDAeYCpfDnC2kdKMY= sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k= sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/kube-storage-version-migrator v0.0.3/go.mod h1:mXfSLkx9xbJHQsgNDDUZK/iQTs2tMbx/hsJlWe6Fthw= sigs.k8s.io/kube-storage-version-migrator v0.0.4/go.mod h1:mXfSLkx9xbJHQsgNDDUZK/iQTs2tMbx/hsJlWe6Fthw= sigs.k8s.io/kustomize v2.0.3+incompatible/go.mod h1:MkjgH3RdOWrievjo6c9T245dYlB5QeXV4WCbnt/PEpU= +sigs.k8s.io/kustomize/api v0.8.5/go.mod h1:M377apnKT5ZHJS++6H4rQoCHmWtt6qTpp3mbe7p6OLY= sigs.k8s.io/kustomize/api v0.8.11/go.mod h1:a77Ls36JdfCWojpUqR6m60pdGY1AYFix4AH83nJtY1g= +sigs.k8s.io/kustomize/cmd/config v0.9.7/go.mod h1:MvXCpHs77cfyxRmCNUQjIqCmZyYsbn5PyQpWiq44nW0= sigs.k8s.io/kustomize/cmd/config v0.9.13/go.mod h1:7547FLF8W/lTaDf0BDqFTbZxM9zqwEJqCKN9sSR0xSs= +sigs.k8s.io/kustomize/kustomize/v4 v4.0.5/go.mod h1:C7rYla7sI8EnxHE/xEhRBSHMNfcL91fx0uKmUlUhrBk= sigs.k8s.io/kustomize/kustomize/v4 v4.2.0/go.mod h1:MOkR6fmhwG7hEDRXBYELTi5GSFcLwfqwzTRHW3kv5go= +sigs.k8s.io/kustomize/kyaml v0.10.15/go.mod h1:mlQFagmkm1P+W4lZJbJ/yaxMd8PqMRSC4cPcfUVt5Hg= sigs.k8s.io/kustomize/kyaml v0.11.0/go.mod h1:GNMwjim4Ypgp/MueD3zXHLRJEjz7RvtPae0AwlvEMFM= sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200116222232-67a7b8c61874/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= sigs.k8s.io/structured-merge-diff/v3 v3.0.0/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= sigs.k8s.io/structured-merge-diff/v4 v4.0.1/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/structured-merge-diff/v4 v4.1.0/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/structured-merge-diff/v4 v4.1.1/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= 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 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= @@ -2266,6 +2838,7 @@ sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= +sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck= sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0= vbom.ml/util v0.0.0-20160121211510-db5cfe13f5cc/go.mod h1:so/NYdZXCz+E3ZpW0uAoCj6uzU2+8OWDFv/HxUSs7kI= diff --git a/pkg/agent/OWNERS b/pkg/agent/OWNERS new file mode 100644 index 0000000000..51d22e4bb6 --- /dev/null +++ b/pkg/agent/OWNERS @@ -0,0 +1,7 @@ +# See the OWNERS docs: https://git.k8s.io/community/contributors/guide/owners.md +# This file just uses aliases defined in OWNERS_ALIASES. + +approvers: + - agent-approvers +reviewers: + - agent-reviewers \ No newline at end of file diff --git a/pkg/agent/cluster.go b/pkg/agent/cluster.go new file mode 100644 index 0000000000..3541527f71 --- /dev/null +++ b/pkg/agent/cluster.go @@ -0,0 +1,433 @@ +package agent + +import ( + "context" + "path/filepath" + "time" + + "github.com/go-openapi/strfmt" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + + "github.com/openshift/assisted-service/client/installer" + "github.com/openshift/assisted-service/models" +) + +// Cluster is a struct designed to help interact with the cluster that is +// currently being installed by agent installer. +type Cluster struct { + Ctx context.Context + API *clientSet + assetDir string + clusterConsoleRouteURL string + clusterID *strfmt.UUID + clusterInfraEnvID *strfmt.UUID + installHistory *clusterInstallStatusHistory +} + +type clientSet struct { + Kube *ClusterKubeAPIClient + OpenShift *ClusterOpenShiftAPIClient + Rest *NodeZeroRestClient +} + +type clusterInstallStatusHistory struct { + RestAPISeen bool + RestAPIClusterStatusAddingHostsSeen bool + RestAPIClusterStatusCancelledSeen bool + RestAPIClusterStatusInstallingSeen bool + RestAPIClusterStatusInstallingPendingUserActionSeen bool + RestAPIClusterStatusInsufficientSeen bool + RestAPIClusterStatusFinalizingSeen bool + RestAPIClusterStatusErrorSeen bool + RestAPIClusterStatusPendingForInputSeen bool + RestAPIClusterStatusPreparingForInstallationSeen bool + RestAPIClusterStatusReadySeen bool + RestAPIInfraEnvEventList models.EventList + RestAPIPreviousClusterStatus string + RestAPIPreviousEventMessage string + RestAPIHostValidationsPassed bool + ClusterKubeAPISeen bool + ClusterBootstrapComplete bool + ClusterOperatorsInitialized bool + ClusterConsoleRouteCreated bool + ClusterConsoleRouteURLCreated bool + ClusterInstallComplete bool +} + +// NewCluster initializes a Cluster object +func NewCluster(ctx context.Context, assetDir string) (*Cluster, error) { + + czero := &Cluster{} + capi := &clientSet{} + + restclient, err := NewNodeZeroRestClient(ctx, assetDir) + if err != nil { + logrus.Fatal(err) + } + kubeclient, err := NewClusterKubeAPIClient(ctx, assetDir) + if err != nil { + logrus.Fatal(err) + } + + ocpclient, err := NewClusterOpenShiftAPIClient(ctx, assetDir) + if err != nil { + logrus.Fatal(err) + } + + capi.Rest = restclient + capi.Kube = kubeclient + capi.OpenShift = ocpclient + + cinstallstatushistory := &clusterInstallStatusHistory{ + RestAPISeen: false, + RestAPIClusterStatusAddingHostsSeen: false, + RestAPIClusterStatusCancelledSeen: false, + RestAPIClusterStatusInstallingSeen: false, + RestAPIClusterStatusInstallingPendingUserActionSeen: false, + RestAPIClusterStatusInsufficientSeen: false, + RestAPIClusterStatusFinalizingSeen: false, + RestAPIClusterStatusErrorSeen: false, + RestAPIClusterStatusPendingForInputSeen: false, + RestAPIClusterStatusPreparingForInstallationSeen: false, + RestAPIClusterStatusReadySeen: false, + RestAPIInfraEnvEventList: nil, + RestAPIPreviousClusterStatus: "", + RestAPIPreviousEventMessage: "", + RestAPIHostValidationsPassed: false, + ClusterKubeAPISeen: false, + ClusterBootstrapComplete: false, + ClusterOperatorsInitialized: false, + ClusterConsoleRouteCreated: false, + ClusterConsoleRouteURLCreated: false, + ClusterInstallComplete: false, + } + + czero.Ctx = ctx + czero.API = capi + czero.clusterID = nil + czero.clusterInfraEnvID = nil + czero.assetDir = assetDir + czero.clusterConsoleRouteURL = "" + czero.installHistory = cinstallstatushistory + return czero, nil +} + +// IsBootstrapComplete Determine if the cluster has completed the bootstrap process. +func (czero *Cluster) IsBootstrapComplete() (bool, error) { + + if czero.installHistory.ClusterBootstrapComplete { + logrus.Info("Bootstrap is complete") + return true, nil + } + + clusterKubeAPILive, clusterKubeAPIErr := czero.API.Kube.IsKubeAPILive() + if clusterKubeAPIErr != nil { + logrus.Trace(errors.Wrap(clusterKubeAPIErr, "Cluster Kube API is not available")) + } + + if clusterKubeAPILive { + + // First time we see the cluster Kube API + if !czero.installHistory.ClusterKubeAPISeen { + logrus.Info("Cluster Kube API Initialized") + czero.installHistory.ClusterKubeAPISeen = true + } + + configmap, err := czero.API.Kube.IsBootstrapConfigMapComplete() + if configmap { + logrus.Info("Bootstrap configMap status is complete") + czero.installHistory.ClusterBootstrapComplete = true + return true, nil + } + if err != nil { + logrus.Debug(err) + } + } + + agentRestAPILive, agentRestAPIErr := czero.API.Rest.IsRestAPILive() + if agentRestAPIErr != nil { + logrus.Trace(errors.Wrap(agentRestAPIErr, "Agent Rest API is not available")) + } + + if agentRestAPILive { + + // First time we see the agent Rest API + if !czero.installHistory.RestAPISeen { + logrus.Debug("Agent Rest API Initialized") + czero.installHistory.RestAPISeen = true + } + + // Lazy loading of the clusterID and clusterInfraEnvID + if czero.clusterID == nil { + clusterID, err := czero.API.Rest.getClusterID() + if err != nil { + return false, errors.Wrap(err, "Unable to retrieve clusterID from Agent Rest API") + } + czero.clusterID = clusterID + } + + if czero.clusterInfraEnvID == nil { + clusterInfraEnvID, err := czero.API.Rest.getClusterInfraEnvID() + if err != nil { + return false, errors.Wrap(err, "Unable to retrieve clusterInfraEnvID from Agent Rest API") + } + czero.clusterInfraEnvID = clusterInfraEnvID + } + + logrus.Trace("Getting cluster metadata from Agent Rest API") + clusterMetadata, err := czero.GetClusterRestAPIMetadata() + if err != nil { + return false, errors.Wrap(err, "Unable to retrieve cluster metadata from Agent Rest API") + } + + if clusterMetadata == nil { + return false, errors.New("cluster metadata returned nil from Agent Rest API") + } + + if !checkHostsValidations(clusterMetadata, logrus.StandardLogger()) { + return false, errors.New("cluster host validations failed") + } + + czero.PrintInstallStatus(clusterMetadata) + czero.installHistory.RestAPIPreviousClusterStatus = *clusterMetadata.Status + + // Update Install History object when we see these states + czero.updateInstallHistoryClusterStatus(clusterMetadata) + + installing, _ := czero.IsInstalling(*clusterMetadata.Status) + if !installing { + logrus.Warn("Cluster has stopped installing... working to recover installation") + errored, _ := czero.HasErrored(*clusterMetadata.Status) + if errored { + return false, errors.New("cluster installation has stopped due to errors") + } else if *clusterMetadata.Status == models.ClusterStatusCancelled { + return false, errors.New("cluster installation was cancelled") + } + } + + // Print most recent event associated with the clusterInfraEnvID + eventList, err := czero.API.Rest.GetInfraEnvEvents(czero.clusterInfraEnvID) + if err != nil { + return false, errors.Wrap(err, "Unable to retrieve events about the cluster from the Agent Rest API") + } + if len(eventList) == 0 { + logrus.Trace("No cluster events detected from the Agent Rest API") + } else { + mostRecentEvent := eventList[len(eventList)-1] + // Don't print the same status message back to back + if *mostRecentEvent.Message != czero.installHistory.RestAPIPreviousEventMessage { + if *mostRecentEvent.Severity == models.EventSeverityInfo { + logrus.Info(*mostRecentEvent.Message) + } else { + logrus.Warn(*mostRecentEvent.Message) + } + } + czero.installHistory.RestAPIPreviousEventMessage = *mostRecentEvent.Message + czero.installHistory.RestAPIInfraEnvEventList = eventList + } + + } + + // both API's are not available + if !agentRestAPILive && !clusterKubeAPILive { + logrus.Trace("Current API Status: Node Zero Agent API: down, Cluster Kube API: down") + if !czero.installHistory.RestAPISeen && !czero.installHistory.ClusterKubeAPISeen { + logrus.Debug("Node zero Agent Rest API never initialized. Cluster API never initialized") + logrus.Info("Waiting for cluster install to initialize. Sleeping for 30 seconds") + time.Sleep(30 * time.Second) + return false, nil + } + + if czero.installHistory.RestAPISeen && !czero.installHistory.ClusterKubeAPISeen { + logrus.Debug("Cluster API never initialized") + logrus.Debugf("Cluster install status from Agent Rest API last seen was: %s", czero.installHistory.RestAPIPreviousClusterStatus) + return false, errors.New("cluster bootstrap did not complete") + } + } + + logrus.Trace("cluster bootstrap is not complete") + return false, nil +} + +// IsInstallComplete Determine if the cluster has completed installation. +func (czero *Cluster) IsInstallComplete() (bool, error) { + + if czero.installHistory.ClusterInstallComplete { + logrus.Info("Cluster installation is complete") + return true, nil + } + + if !czero.installHistory.ClusterOperatorsInitialized { + initialized, err := czero.API.OpenShift.AreClusterOperatorsInitialized() + if initialized && err == nil { + czero.installHistory.ClusterOperatorsInitialized = true + } + if err != nil { + return false, errors.Wrap(err, "Error while initializing cluster operators") + } + + } + + if !czero.installHistory.ClusterConsoleRouteCreated { + route, err := czero.API.OpenShift.IsConsoleRouteAvailable() + if route && err == nil { + czero.installHistory.ClusterConsoleRouteCreated = true + } + if err != nil { + return false, errors.Wrap(err, "Error while waiting for console route") + } + + } + + if !czero.installHistory.ClusterConsoleRouteURLCreated { + available, url, err := czero.API.OpenShift.IsConsoleRouteURLAvailable() + if available && url != "" && err == nil { + czero.clusterConsoleRouteURL = url + czero.installHistory.ClusterConsoleRouteURLCreated = true + } + if err != nil { + return false, errors.Wrap(err, "Error while waiting for console route URL") + } + } + + if czero.installHistory.ClusterOperatorsInitialized && + czero.installHistory.ClusterConsoleRouteCreated && + czero.installHistory.ClusterConsoleRouteURLCreated { + czero.installHistory.ClusterInstallComplete = true + return true, nil + } + + return false, nil +} + +// GetClusterRestAPIMetadata Retrieve the current cluster metadata from the Agent Rest API +func (czero *Cluster) GetClusterRestAPIMetadata() (*models.Cluster, error) { + // GET /v2/clusters/{cluster_zero_id} + if czero.clusterID != nil { + getClusterParams := &installer.V2GetClusterParams{ClusterID: *czero.clusterID} + result, err := czero.API.Rest.Client.Installer.V2GetCluster(czero.Ctx, getClusterParams) + if err != nil { + return nil, err + } + return result.Payload, nil + } + return nil, errors.New("no clusterID known for the cluster") +} + +// HasErrored Determine if the cluster installation has errored using the models from the Agent Rest API. +func (czero *Cluster) HasErrored(status string) (bool, string) { + clusterErrorStates := map[string]bool{ + models.ClusterStatusAddingHosts: false, + models.ClusterStatusCancelled: false, + models.ClusterStatusInstalling: false, + models.ClusterStatusInstallingPendingUserAction: true, + models.ClusterStatusInsufficient: true, + models.ClusterStatusError: true, + models.ClusterStatusFinalizing: false, + models.ClusterStatusPendingForInput: false, + models.ClusterStatusPreparingForInstallation: false, + models.ClusterStatusReady: false, + } + return clusterErrorStates[status], status +} + +// IsInstalling Determine if the cluster is still installing using the models from the Agent Rest API. +func (czero *Cluster) IsInstalling(status string) (bool, string) { + clusterInstallingStates := map[string]bool{ + models.ClusterStatusAddingHosts: true, + models.ClusterStatusCancelled: false, + models.ClusterStatusInstalling: true, + models.ClusterStatusInstallingPendingUserAction: false, + models.ClusterStatusInsufficient: false, + models.ClusterStatusError: false, + models.ClusterStatusFinalizing: true, + models.ClusterStatusPendingForInput: true, + models.ClusterStatusPreparingForInstallation: true, + models.ClusterStatusReady: true, + } + return clusterInstallingStates[status], status +} + +// PrintInfraEnvRestAPIEventList Prints the whole event list for debugging +func (czero *Cluster) PrintInfraEnvRestAPIEventList() { + if czero.installHistory.RestAPIInfraEnvEventList != nil { + for i := 0; i < len(czero.installHistory.RestAPIInfraEnvEventList); i++ { + logrus.Debug(*czero.installHistory.RestAPIInfraEnvEventList[i].Message) + } + } else { + logrus.Debug("No events logged from the Agent Rest API") + } +} + +// PrintInstallationComplete Prints the installation complete information +func (czero *Cluster) PrintInstallationComplete() error { + absDir, err := filepath.Abs(czero.assetDir) + if err != nil { + return err + } + kubeconfig := filepath.Join(absDir, "auth", "kubeconfig") + logrus.Info("Install complete!") + logrus.Infof("To access the cluster as the system:admin user when using 'oc', run\n export KUBECONFIG=%s", kubeconfig) + logrus.Infof("Access the OpenShift web-console here: %s", czero.clusterConsoleRouteURL) + // TODO: log kubeadmin password for the console + return nil + +} + +// PrintInstallStatus Print a human friendly message using the models from the Agent Rest API. +func (czero *Cluster) PrintInstallStatus(cluster *models.Cluster) error { + + friendlyStatus := humanFriendlyClusterInstallStatus(*cluster.Status) + logrus.Trace(friendlyStatus) + // Don't print the same status message back to back + if *cluster.Status != czero.installHistory.RestAPIPreviousClusterStatus { + logrus.Info(friendlyStatus) + } + + return nil +} + +// Human friendly install status strings mapped to the Agent Rest API cluster statuses +func humanFriendlyClusterInstallStatus(status string) string { + clusterStoppedInstallingStates := map[string]string{ + models.ClusterStatusAddingHosts: "Cluster is adding hosts", + models.ClusterStatusCancelled: "Cluster installation cancelled", + models.ClusterStatusError: "Cluster has hosts in error", + models.ClusterStatusFinalizing: "Finalizing cluster installation", + models.ClusterStatusInstalling: "Cluster installation in progress", + models.ClusterStatusInstallingPendingUserAction: "Cluster has hosts requiring user input", + models.ClusterStatusInsufficient: "Cluster is not ready for install. Check host validations", + models.ClusterStatusPendingForInput: "User input is required to continue cluster installation", + models.ClusterStatusPreparingForInstallation: "Preparing cluster for installation", + models.ClusterStatusReady: "Cluster is ready for install", + } + return clusterStoppedInstallingStates[status] + +} + +// Update the install history struct when we see the status from the Agent Rest API +func (czero *Cluster) updateInstallHistoryClusterStatus(cluster *models.Cluster) { + switch *cluster.Status { + case models.ClusterStatusAddingHosts: + czero.installHistory.RestAPIClusterStatusAddingHostsSeen = true + case models.ClusterStatusCancelled: + czero.installHistory.RestAPIClusterStatusCancelledSeen = true + case models.ClusterStatusError: + czero.installHistory.RestAPIClusterStatusErrorSeen = true + case models.ClusterStatusFinalizing: + czero.installHistory.RestAPIClusterStatusFinalizingSeen = true + case models.ClusterStatusInsufficient: + czero.installHistory.RestAPIClusterStatusInsufficientSeen = true + case models.ClusterStatusInstalling: + czero.installHistory.RestAPIClusterStatusInstallingSeen = true + case models.ClusterStatusInstallingPendingUserAction: + czero.installHistory.RestAPIClusterStatusInstallingPendingUserActionSeen = true + case models.ClusterStatusPendingForInput: + czero.installHistory.RestAPIClusterStatusPendingForInputSeen = true + case models.ClusterStatusPreparingForInstallation: + czero.installHistory.RestAPIClusterStatusPreparingForInstallationSeen = true + case models.ClusterStatusReady: + czero.installHistory.RestAPIClusterStatusReadySeen = true + } +} diff --git a/pkg/agent/kube.go b/pkg/agent/kube.go new file mode 100644 index 0000000000..fe3357dd7c --- /dev/null +++ b/pkg/agent/kube.go @@ -0,0 +1,90 @@ +package agent + +import ( + "context" + "path/filepath" + + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" +) + +// ClusterKubeAPIClient is a kube client to interact with the cluster that agent installer is installing. +type ClusterKubeAPIClient struct { + Client *kubernetes.Clientset + ctx context.Context + config *rest.Config + configPath string +} + +// NewClusterKubeAPIClient Create a new kube client to interact with the cluster under install. +func NewClusterKubeAPIClient(ctx context.Context, assetDir string) (*ClusterKubeAPIClient, error) { + + kubeClient := &ClusterKubeAPIClient{} + + kubeconfigpath := filepath.Join(assetDir, "auth", "kubeconfig") + kubeconfig, err := clientcmd.BuildConfigFromFlags("", kubeconfigpath) + if err != nil { + return nil, errors.Wrap(err, "error loading kubeconfig from assets") + } + + kubeclient, err := kubernetes.NewForConfig(kubeconfig) + if err != nil { + return nil, errors.Wrap(err, "creating a Kubernetes client from assets failed") + } + + kubeClient.Client = kubeclient + kubeClient.ctx = ctx + kubeClient.config = kubeconfig + kubeClient.configPath = kubeconfigpath + + return kubeClient, nil +} + +// IsKubeAPILive Determine if the cluster under install has initailized the kubenertes API. +func (kube *ClusterKubeAPIClient) IsKubeAPILive() (bool, error) { + + discovery := kube.Client.Discovery() + version, err := discovery.ServerVersion() + if err != nil { + return false, err + } + logrus.Debugf("cluster API is up and running %s", version) + return true, nil +} + +// DoesKubeConfigExist Determine if the kubeconfig for the cluster can be used without errors. +func (kube *ClusterKubeAPIClient) DoesKubeConfigExist() (bool, error) { + + _, err := clientcmd.LoadFromFile(kube.configPath) + if err != nil { + return false, errors.Wrap(err, "error loading kubeconfig from file") + } + return true, nil +} + +// IsBootstrapConfigMapComplete Detemine if the cluster's bootstrap configmap has the status complete. +func (kube *ClusterKubeAPIClient) IsBootstrapConfigMapComplete() (bool, error) { + + // Get latest version of bootstrap configmap + bootstrap, err := kube.Client.CoreV1().ConfigMaps("kube-system").Get(kube.ctx, "bootstrap", v1.GetOptions{}) + + if err != nil { + return false, errors.Wrap(err, "bootstrap configmap not found") + } + // Found a bootstrap configmap need to check its status + if bootstrap != nil { + status, ok := bootstrap.Data["status"] + if !ok { + logrus.Debug("no status found in bootstrap configmap") + return false, nil + } + if status == "complete" { + return true, nil + } + } + return false, nil +} diff --git a/pkg/agent/ocp.go b/pkg/agent/ocp.go new file mode 100644 index 0000000000..968dc0f2f3 --- /dev/null +++ b/pkg/agent/ocp.go @@ -0,0 +1,152 @@ +package agent + +import ( + "context" + "path/filepath" + + configv1 "github.com/openshift/api/config/v1" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" + + configclient "github.com/openshift/client-go/config/clientset/versioned" + routeclient "github.com/openshift/client-go/route/clientset/versioned" + cov1helpers "github.com/openshift/library-go/pkg/config/clusteroperator/v1helpers" + "github.com/openshift/library-go/pkg/route/routeapihelpers" +) + +// ClusterOpenShiftAPIClient Kube client using the OpenShift clientset instead of the Kubernetes clientset +type ClusterOpenShiftAPIClient struct { + ConfigClient *configclient.Clientset + RouteClient *routeclient.Clientset + ctx context.Context + config *rest.Config + configPath string +} + +const ( + // Need to keep these updated if they change + consoleNamespace = "openshift-console" + consoleRouteName = "console" +) + +// NewClusterOpenShiftAPIClient Create a kube client with OCP understanding +func NewClusterOpenShiftAPIClient(ctx context.Context, assetDir string) (*ClusterOpenShiftAPIClient, error) { + + ocpClient := &ClusterOpenShiftAPIClient{} + + kubeconfigpath := filepath.Join(assetDir, "auth", "kubeconfig") + kubeconfig, err := clientcmd.BuildConfigFromFlags("", kubeconfigpath) + if err != nil { + return nil, errors.Wrap(err, "creating kubeconfig for ocp config client") + } + + configClient, err := configclient.NewForConfig(kubeconfig) + if err != nil { + return nil, errors.Wrap(err, "creating an ocp config client") + } + + routeClient, err := routeclient.NewForConfig(kubeconfig) + if err != nil { + return nil, errors.Wrap(err, "creating an ocp route client") + } + + ocpClient.ConfigClient = configClient + ocpClient.RouteClient = routeClient + ocpClient.ctx = ctx + ocpClient.config = kubeconfig + ocpClient.configPath = kubeconfigpath + + return ocpClient, nil + +} + +// AreClusterOperatorsInitialized Waits for all Openshift cluster operators to initialize +func (ocp *ClusterOpenShiftAPIClient) AreClusterOperatorsInitialized() (bool, error) { + + var lastError string + failing := configv1.ClusterStatusConditionType("Failing") + + version, err := ocp.ConfigClient.ConfigV1().ClusterVersions().Get(ocp.ctx, "version", metav1.GetOptions{}) + if err != nil { + return false, errors.Wrap(err, "Getting ClusterVersion object") + } + + if cov1helpers.IsStatusConditionTrue(version.Status.Conditions, configv1.OperatorAvailable) && + cov1helpers.IsStatusConditionFalse(version.Status.Conditions, failing) && + cov1helpers.IsStatusConditionFalse(version.Status.Conditions, configv1.OperatorProgressing) { + return true, nil + } + + if cov1helpers.IsStatusConditionTrue(version.Status.Conditions, failing) { + lastError = cov1helpers.FindStatusCondition(version.Status.Conditions, failing).Message + } else if cov1helpers.IsStatusConditionTrue(version.Status.Conditions, configv1.OperatorProgressing) { + lastError = cov1helpers.FindStatusCondition(version.Status.Conditions, configv1.OperatorProgressing).Message + } + logrus.Debugf("Still waiting for the cluster to initialize: %s", lastError) + + return false, nil +} + +// IsConsoleRouteAvailable Check if the OCP console route is created +func (ocp *ClusterOpenShiftAPIClient) IsConsoleRouteAvailable() (bool, error) { + route, err := ocp.RouteClient.RouteV1().Routes(consoleNamespace).Get(ocp.ctx, consoleRouteName, metav1.GetOptions{}) + if err == nil { + logrus.Debugf("Route found in openshift-console namespace: %s", consoleRouteName) + if _, _, err2 := routeapihelpers.IngressURI(route, ""); err2 == nil { + logrus.Debug("OpenShift console route is admitted") + return true, nil + } else if err2 != nil { + err = err2 + } + } + return false, errors.Wrap(err, "Waiting for openshift-console route") + +} + +// IsConsoleRouteURLAvailable Check if the console route URL is available +func (ocp *ClusterOpenShiftAPIClient) IsConsoleRouteURLAvailable() (bool, string, error) { + url := "" + route, err := ocp.RouteClient.RouteV1().Routes(consoleNamespace).Get(ocp.ctx, consoleRouteName, metav1.GetOptions{}) + if err == nil { + if uri, _, err2 := routeapihelpers.IngressURI(route, ""); err2 == nil { + url = uri.String() + } else { + err = err2 + } + } + if url == "" { + return false, url, errors.Wrap(err, "Waiting for openshift-console URL") + } + return true, url, nil +} + +// LogClusterOperatorConditions Log OCP cluster operator conditions +func (ocp *ClusterOpenShiftAPIClient) LogClusterOperatorConditions() error { + + operators, err := ocp.ConfigClient.ConfigV1().ClusterOperators().List(ocp.ctx, metav1.ListOptions{}) + if err != nil { + return errors.Wrap(err, "Listing ClusterOperator objects") + } + + for _, operator := range operators.Items { + for _, condition := range operator.Status.Conditions { + if condition.Type == configv1.OperatorUpgradeable { + continue + } else if condition.Type == configv1.OperatorAvailable && condition.Status == configv1.ConditionTrue { + continue + } else if (condition.Type == configv1.OperatorDegraded || condition.Type == configv1.OperatorProgressing) && condition.Status == configv1.ConditionFalse { + continue + } + if condition.Type == configv1.OperatorDegraded { + logrus.Errorf("Cluster operator %s %s is %s with %s: %s", operator.ObjectMeta.Name, condition.Type, condition.Status, condition.Reason, condition.Message) + } else { + logrus.Infof("Cluster operator %s %s is %s with %s: %s", operator.ObjectMeta.Name, condition.Type, condition.Status, condition.Reason, condition.Message) + } + } + } + + return nil +} diff --git a/pkg/agent/rest.go b/pkg/agent/rest.go new file mode 100644 index 0000000000..715a0e78a8 --- /dev/null +++ b/pkg/agent/rest.go @@ -0,0 +1,156 @@ +package agent + +import ( + "context" + "net" + "net/url" + + "github.com/go-openapi/strfmt" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + + "github.com/openshift/assisted-service/api/v1beta1" + "github.com/openshift/assisted-service/client" + "github.com/openshift/assisted-service/client/events" + "github.com/openshift/assisted-service/client/installer" + "github.com/openshift/assisted-service/models" + + "github.com/openshift/installer/pkg/asset/agent/agentconfig" + "github.com/openshift/installer/pkg/asset/agent/image" + "github.com/openshift/installer/pkg/asset/agent/manifests" + assetstore "github.com/openshift/installer/pkg/asset/store" + "github.com/openshift/installer/pkg/types/agent" +) + +// NodeZeroRestClient is a struct to interact with the Agent Rest API that is on node zero. +type NodeZeroRestClient struct { + Client *client.AssistedInstall + ctx context.Context + config client.Config + NodeZeroIP string +} + +// NewNodeZeroRestClient Initialize a new rest client to interact with the Agent Rest API on node zero. +func NewNodeZeroRestClient(ctx context.Context, assetDir string) (*NodeZeroRestClient, error) { + restClient := &NodeZeroRestClient{} + agentConfigAsset := &agentconfig.AgentConfig{} + agentManifestsAsset := &manifests.AgentManifests{} + + assetStore, err := assetstore.NewStore(assetDir) + if err != nil { + return nil, errors.Wrap(err, "failed to create asset store") + } + + agentConfig, agentConfigError := assetStore.Load(agentConfigAsset) + agentManifests, manifestError := assetStore.Load(agentManifestsAsset) + + if agentConfigError != nil { + logrus.Debug(errors.Wrapf(agentConfigError, "failed to load %s", agentConfigAsset.Name())) + } + if manifestError != nil { + logrus.Debug(errors.Wrapf(manifestError, "failed to load %s", agentManifestsAsset.Name())) + } + if agentConfigError != nil || manifestError != nil { + return nil, errors.New("failed to load AgentConfig or NMStateConfig") + } + + var RendezvousIP string + var rendezvousIPError error + var emptyNMStateConfigs []*v1beta1.NMStateConfig + + if agentConfig != nil && agentManifests != nil { + RendezvousIP, rendezvousIPError = image.RetrieveRendezvousIP(agentConfig.(*agentconfig.AgentConfig).Config, agentManifests.(*manifests.AgentManifests).NMStateConfigs) + } else if agentConfig == nil && agentManifests != nil { + RendezvousIP, rendezvousIPError = image.RetrieveRendezvousIP(&agent.Config{}, agentManifests.(*manifests.AgentManifests).NMStateConfigs) + } else if agentConfig != nil && agentManifests == nil { + RendezvousIP, rendezvousIPError = image.RetrieveRendezvousIP(agentConfig.(*agentconfig.AgentConfig).Config, emptyNMStateConfigs) + } else { + return nil, errors.New("both AgentConfig and NMStateConfig are empty") + } + if rendezvousIPError != nil { + return nil, rendezvousIPError + } + + config := client.Config{} + config.URL = &url.URL{ + Scheme: "http", + Host: net.JoinHostPort(RendezvousIP, "8090"), + Path: client.DefaultBasePath, + } + client := client.New(config) + + restClient.Client = client + restClient.ctx = ctx + restClient.config = config + restClient.NodeZeroIP = RendezvousIP + + return restClient, nil +} + +// IsRestAPILive Determine if the Agent Rest API on node zero has initialized +func (rest *NodeZeroRestClient) IsRestAPILive() (bool, error) { + // GET /v2/infraenvs + listInfraEnvsParams := installer.NewListInfraEnvsParams() + _, err := rest.Client.Installer.ListInfraEnvs(rest.ctx, listInfraEnvsParams) + if err != nil { + return false, err + } + return true, nil +} + +// GetRestAPIServiceBaseURL Return the url of the Agent Rest API on node zero +func (rest *NodeZeroRestClient) GetRestAPIServiceBaseURL() *url.URL { + return rest.config.URL +} + +// GetInfraEnvEvents Return the event list for the provided infraEnvID from the Agent Rest API +func (rest *NodeZeroRestClient) GetInfraEnvEvents(infraEnvID *strfmt.UUID) (models.EventList, error) { + listEventsParams := &events.V2ListEventsParams{InfraEnvID: infraEnvID} + clusterEventsResult, err := rest.Client.Events.V2ListEvents(rest.ctx, listEventsParams) + if err != nil { + return nil, err + } + return clusterEventsResult.Payload, nil +} + +// getClusterID Return the cluster ID assigned by the Agent Rest API +func (rest *NodeZeroRestClient) getClusterID() (*strfmt.UUID, error) { + // GET /v2/clusters and return first result + listClusterParams := installer.NewV2ListClustersParams() + clusterResult, err := rest.Client.Installer.V2ListClusters(rest.ctx, listClusterParams) + clusterList := clusterResult.Payload + if err != nil { + return nil, err + } + if len(clusterList) == 1 { + clusterID := clusterList[0].ID + return clusterID, nil + } else if len(clusterList) == 0 { + logrus.Debug("cluster is not registered in rest API") + return nil, nil + } else { + logrus.Infof("found too many clusters. number of clusters found: %d", len(clusterList)) + return nil, nil + } +} + +// getClusterID Return the infraEnv ID associated with the cluster in the Agent Rest API +func (rest *NodeZeroRestClient) getClusterInfraEnvID() (*strfmt.UUID, error) { + // GET /v2/infraenvs and return first result + listInfraEnvParams := installer.NewListInfraEnvsParams() + infraEnvResult, err := rest.Client.Installer.ListInfraEnvs(rest.ctx, listInfraEnvParams) + infraEnvList := infraEnvResult.Payload + if err != nil { + return nil, err + } + if len(infraEnvList) == 1 { + clusterInfraEnvID := infraEnvList[0].ID + return clusterInfraEnvID, nil + } else if len(infraEnvList) == 0 { + logrus.Debug("infraenv is not registered in rest API") + return nil, nil + } else { + logrus.Infof("found too many infraenvs. number of infraenvs found: %d", len(infraEnvList)) + return nil, nil + } +} diff --git a/pkg/agent/validations.go b/pkg/agent/validations.go new file mode 100644 index 0000000000..3f5cda91bb --- /dev/null +++ b/pkg/agent/validations.go @@ -0,0 +1,139 @@ +package agent + +import ( + "encoding/json" + "fmt" + "reflect" + "sort" + + "github.com/openshift/assisted-service/api/common" + "github.com/openshift/assisted-service/models" + "github.com/sirupsen/logrus" +) + +const ( + validationFailure string = "failure" + validationError string = "error" +) + +// Re-using Assisted UI host validation labels (see https://github.com/openshift-assisted/assisted-ui-lib) +// for logging human-friendly messages in case of validation failures +var hostValidationLabels = map[string]string{ + "odf-requirements-satisfied": "ODF requirements", + "disk-encryption-requirements-satisfied": "Disk encryption requirements", + "compatible-with-cluster-platform": "", + "has-default-route": "Default route to host", + "sufficient-network-latency-requirement-for-role": "Network latency", + "sufficient-packet-loss-requirement-for-role": "Packet loss", + "has-inventory": "Hardware information", + "has-min-cpu-cores": "Minimum CPU cores", + "has-min-memory": "Minimum Memory", + "has-min-valid-disks": "Minimum disks of required size", + "has-cpu-cores-for-role": "Minimum CPU cores for selected role", + "has-memory-for-role": "Minimum memory for selected role", + "hostname-unique": "Unique hostname", + "hostname-valid": "Valid hostname", + "connected": "Connected", + "media-connected": "Media Connected", + "machine-cidr-defined": "Machine CIDR", + "belongs-to-machine-cidr": "Belongs to machine CIDR", + "ignition-downloadable": "Ignition file downloadable", + "belongs-to-majority-group": "Belongs to majority connected group", + "valid-platform-network-settings": "Platform network settings", + "ntp-synced": "NTP synchronization", + "container-images-available": "Container images availability", + "lso-requirements-satisfied": "LSO requirements", + "ocs-requirements-satisfied": "OCS requirements", + "sufficient-installation-disk-speed": "Installation disk speed", + "cnv-requirements-satisfied": "CNV requirements", + "api-domain-name-resolved-correctly": "API domain name resolution", + "api-int-domain-name-resolved-correctly": "API internal domain name resolution", + "apps-domain-name-resolved-correctly": "Application ingress domain name resolution", + "dns-wildcard-not-configured": "DNS wildcard not configured", + "non-overlapping-subnets": "Non overlapping subnets", + "vsphere-disk-uuid-enabled": "Vsphere disk uuidenabled", +} + +type validationTrace struct { + header string + category string + label string + message string +} + +var previousValidations []validationTrace + +func logValidationsStatus(errorMsg string, validations string, log *logrus.Logger) []validationTrace { + + traces := []validationTrace{} + if validations == "" { + return traces + } + + validationsInfo := common.ValidationsStatus{} + err := json.Unmarshal([]byte(validations), &validationsInfo) + if err != nil { + return []validationTrace{{header: errorMsg, message: "unable to verify validations"}} + } + + for category, validationResults := range validationsInfo { + for _, r := range validationResults { + switch r.Status { + case validationFailure, validationError: + label := r.ID + if v, ok := hostValidationLabels[r.ID]; ok { + label = v + } + + traces = append(traces, validationTrace{ + header: errorMsg, + category: category, + label: label, + message: r.Message, + }) + } + } + } + + return traces +} + +func checkHostsValidations(cluster *models.Cluster, log *logrus.Logger) bool { + + var currentValidations []validationTrace + + currentValidations = append(currentValidations, logValidationsStatus("Validation failure found for cluster", cluster.ValidationsInfo, log)...) + for _, h := range cluster.Hosts { + currentValidations = append(currentValidations, logValidationsStatus(fmt.Sprintf("Validation failure found for %s", h.RequestedHostname), h.ValidationsInfo, log)...) + } + + sort.Slice(currentValidations, func(i, j int) bool { + if currentValidations[i].header != currentValidations[j].header { + return currentValidations[i].header < currentValidations[j].header + } + if currentValidations[i].category != currentValidations[j].category { + return currentValidations[i].category < currentValidations[j].category + } + return currentValidations[i].label < currentValidations[j].label + }) + + if !reflect.DeepEqual(currentValidations, previousValidations) { + previousValidations = currentValidations + + if len(previousValidations) == 0 { + log.Info("Pre-installation validations are OK") + return true + } + + log.Info("Checking for validation failures ----------------------------------------------") + for _, v := range previousValidations { + log.WithFields(logrus.Fields{ + "category": v.category, + "label": v.label, + "message": v.message, + }).Error(v.header) + } + } + + return len(previousValidations) == 0 +} diff --git a/pkg/agent/validations_test.go b/pkg/agent/validations_test.go new file mode 100644 index 0000000000..4e732b77f9 --- /dev/null +++ b/pkg/agent/validations_test.go @@ -0,0 +1,106 @@ +package agent + +import ( + "regexp" + "testing" + + "github.com/openshift/assisted-service/models" + "github.com/sirupsen/logrus/hooks/test" + "github.com/stretchr/testify/assert" +) + +func TestCheckHostsValidation(t *testing.T) { + tests := []struct { + name string + hosts []*models.Host + expectedResult bool + expectedLogs []string + }{ + { + name: "no-validations", + expectedResult: true, + }, + { + name: "no-failures", + hosts: []*models.Host{ + { + RequestedHostname: "master-0.ostest.test.metalkube.org", + ValidationsInfo: "{\"hardware\":[{\"id\":\"has-inventory\",\"status\":\"success\",\"message\":\"Valid inventory exists for the host\"}]}", + }, + }, + expectedResult: true, + }, + { + name: "single-host-failure", + hosts: []*models.Host{ + { + RequestedHostname: "master-0.ostest.test.metalkube.org", + ValidationsInfo: `{"hardware":[{"id":"has-min-valid-disks","status":"failure","message":"No eligible disks were found, please check specific disks to see why they are not eligible"},{"id":"has-cpu-cores-for-role","status":"success","message":"Sufficient CPU cores for role master"},{"id":"has-memory-for-role","status":"success","message":"Sufficient RAM for role master"}]}`, + }, + }, + expectedResult: false, + expectedLogs: []string{ + `Checking for validation failures ----------------------------------------------`, + `level=error msg="Validation failure found for master\-0.ostest.test.metalkube.org" category=hardware label="Minimum disks of required size" message="No eligible disks were found, please check specific disks to see why they are not eligible"`, + }, + }, + { + name: "multiple-hosts-failure", + hosts: []*models.Host{ + { + RequestedHostname: "master-0.ostest.test.metalkube.org", + ValidationsInfo: `{"hardware":[{"id":"has-min-valid-disks","status":"failure","message":"No eligible disks were found, please check specific disks to see why they are not eligible"},{"id":"has-cpu-cores-for-role","status":"success","message":"Sufficient CPU cores for role master"},{"id":"has-memory-for-role","status":"success","message":"Sufficient RAM for role master"}]}`, + }, + { + RequestedHostname: "master-1.ostest.test.metalkube.org", + ValidationsInfo: `{"hardware":[{"id":"has-min-valid-disks","status":"failure","message":"No eligible disks were found, please check specific disks to see why they are not eligible"},{"id":"has-cpu-cores-for-role","status":"success","message":"Sufficient CPU cores for role master"},{"id":"has-memory-for-role","status":"success","message":"Sufficient RAM for role master"}]}`, + }, + }, + expectedResult: false, + expectedLogs: []string{ + `Checking for validation failures ----------------------------------------------`, + `level=error msg="Validation failure found for master\-0.ostest.test.metalkube.org" category=hardware label="Minimum disks of required size" message="No eligible disks were found, please check specific disks to see why they are not eligible"`, + `level=error msg="Validation failure found for master\-1.ostest.test.metalkube.org" category=hardware label="Minimum disks of required size" message="No eligible disks were found, please check specific disks to see why they are not eligible"`, + }, + }, + { + name: "malformed-json", + hosts: []*models.Host{ + { + RequestedHostname: "master-0.ostest.test.metalkube.org", + ValidationsInfo: `not a valid info`, + }, + }, + expectedResult: false, + expectedLogs: []string{ + `Checking for validation failures ----------------------------------------------"`, + `Validation failure found for master-0.ostest.test.metalkube.org`, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + + cluster := &models.Cluster{ + Hosts: tt.hosts, + } + + logger, hook := test.NewNullLogger() + assert.Equal(t, tt.expectedResult, checkHostsValidations(cluster, logger)) + + assert.Equal(t, len(tt.expectedLogs), len(hook.Entries)) + for _, expectedMsg := range tt.expectedLogs { + + matchFound := false + for _, s := range hook.AllEntries() { + logLine, err := s.String() + assert.NoError(t, err) + if regexp.MustCompile(expectedMsg).Match([]byte(logLine)) { + matchFound = true + } + } + assert.True(t, matchFound, "Unable to find log trace for `%s`", expectedMsg) + } + }) + } +} diff --git a/pkg/agent/waitfor.go b/pkg/agent/waitfor.go new file mode 100644 index 0000000000..3eb767f56c --- /dev/null +++ b/pkg/agent/waitfor.go @@ -0,0 +1,88 @@ +package agent + +import ( + "context" + "time" + + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "k8s.io/apimachinery/pkg/util/wait" +) + +// WaitForBootstrapComplete Wait for the bootstrap process to complete on +// cluster installations triggered by the agent installer. +func WaitForBootstrapComplete(assetDir string) (*Cluster, error) { + + ctx := context.Background() + cluster, err := NewCluster(ctx, assetDir) + if err != nil { + logrus.Warn("unable to make cluster object to track installation") + return nil, err + } + + start := time.Now() + previous := time.Now() + timeout := 30 * time.Minute + waitContext, cancel := context.WithTimeout(cluster.Ctx, timeout) + defer cancel() + + wait.Until(func() { + bootstrap, err := cluster.IsBootstrapComplete() + if bootstrap && err == nil { + logrus.Info("cluster bootstrap is complete") + cancel() + } + + current := time.Now() + elapsed := current.Sub(previous) + elapsedTotal := current.Sub(start) + if elapsed >= 1*time.Minute { + logrus.Tracef("elapsed: %s, elapsedTotal: %s", elapsed.String(), elapsedTotal.String()) + previous = current + } + + }, 2*time.Second, waitContext.Done()) + + waitErr := waitContext.Err() + if waitErr != nil && waitErr != context.Canceled { + if err != nil { + return cluster, errors.Wrap(err, "bootstrap process returned error") + } + return cluster, errors.Wrap(waitErr, "bootstrap process timed out") + } + + return cluster, nil +} + +// WaitForInstallComplete Waits for the cluster installation triggered by the +// agent installer to be complete. +func WaitForInstallComplete(assetDir string) (*Cluster, error) { + + cluster, err := WaitForBootstrapComplete(assetDir) + + if err != nil { + return cluster, errors.Wrap(err, "error occured during bootstrap process") + } + + timeout := 90 * time.Minute + waitContext, cancel := context.WithTimeout(cluster.Ctx, timeout) + defer cancel() + + wait.Until(func() { + installed, err := cluster.IsInstallComplete() + if installed && err == nil { + logrus.Info("Cluster is installed") + cancel() + } + + }, 2*time.Second, waitContext.Done()) + + waitErr := waitContext.Err() + if waitErr != nil && waitErr != context.Canceled { + if err != nil { + return cluster, errors.Wrap(err, "Error occurred during installation") + } + return cluster, errors.Wrap(waitErr, "Cluster installation timed out") + } + return cluster, nil +} diff --git a/pkg/asset/agent/OWNERS b/pkg/asset/agent/OWNERS new file mode 100644 index 0000000000..51d22e4bb6 --- /dev/null +++ b/pkg/asset/agent/OWNERS @@ -0,0 +1,7 @@ +# See the OWNERS docs: https://git.k8s.io/community/contributors/guide/owners.md +# This file just uses aliases defined in OWNERS_ALIASES. + +approvers: + - agent-approvers +reviewers: + - agent-reviewers \ No newline at end of file diff --git a/pkg/asset/agent/agentconfig/agent_config.go b/pkg/asset/agent/agentconfig/agent_config.go new file mode 100644 index 0000000000..02af454389 --- /dev/null +++ b/pkg/asset/agent/agentconfig/agent_config.go @@ -0,0 +1,278 @@ +package agentconfig + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/pkg/errors" + "k8s.io/apimachinery/pkg/util/validation/field" + "sigs.k8s.io/yaml" + + "github.com/openshift/installer/pkg/asset" + "github.com/openshift/installer/pkg/types/agent" + "github.com/openshift/installer/pkg/types/agent/conversion" +) + +var ( + agentConfigFilename = "agent-config.yaml" +) + +// AgentConfig reads the agent-config.yaml file. +type AgentConfig struct { + File *asset.File + Config *agent.Config + Template string +} + +var _ asset.WritableAsset = (*AgentConfig)(nil) + +// Name returns a human friendly name for the asset. +func (*AgentConfig) Name() string { + return "Agent Config" +} + +// Dependencies returns all of the dependencies directly needed to generate +// the asset. +func (*AgentConfig) Dependencies() []asset.Asset { + return []asset.Asset{} +} + +// Generate generates the Agent Config manifest. +func (a *AgentConfig) Generate(dependencies asset.Parents) error { + + // TODO: We are temporarily generating a template of the agent-config.yaml + // Change this when its interactive survey is implemented. + agentConfigTemplate := `# +# Note: This is a sample AgentConfig file showing +# which fields are available to aid you in creating your +# own agent-config.yaml file. +# +apiVersion: v1alpha1 +kind: AgentConfig +metadata: + name: example-agent-config + namespace: cluster0 +# All fields are optional +rendezvousIP: your-node0-ip +hosts: +# If a host is listed, then at least one interface +# needs to be specified. +- hostname: change-to-hostname + role: master + # For more information about rootDeviceHints: + # https://docs.openshift.com/container-platform/4.10/installing/installing_bare_metal_ipi/ipi-install-installation-workflow.html#root-device-hints_ipi-install-installation-workflow + rootDeviceHints: + deviceName: /dev/sda + interfaces are used to identify the host to apply this configuration to + interfaces: + - macAddress: 00:00:00:00:00:00 + name: host-network-interface-name + # networkConfig contains the network configuration for the host in NMState format. + # See https://nmstate.io/examples.html for examples. + networkConfig: + interfaces: + - name: eth0 + type: ethernet + state: up + mac-address: 00:00:00:00:00:00 + ipv4: + enabled: true + address: + - ip: 192.168.122.2 + prefix-length: 23 + dhcp: false +` + + a.Template = agentConfigTemplate + + // TODO: template is not validated + return nil +} + +// PersistToFile writes the agent-config.yaml file to the assets folder +func (a *AgentConfig) PersistToFile(directory string) error { + templatePath := filepath.Join(directory, agentConfigFilename) + templateByte := []byte(a.Template) + + err := os.WriteFile(templatePath, templateByte, 0644) + if err != nil { + return err + } + + return nil +} + +// Files returns the files generated by the asset. +func (a *AgentConfig) Files() []*asset.File { + if a.File != nil { + return []*asset.File{a.File} + } + return []*asset.File{} +} + +// Load returns agent config asset from the disk. +func (a *AgentConfig) Load(f asset.FileFetcher) (bool, error) { + + file, err := f.FetchByName(agentConfigFilename) + if err != nil { + if os.IsNotExist(err) { + return false, nil + } + return false, errors.Wrap(err, fmt.Sprintf("failed to load %s file", agentConfigFilename)) + } + + config := &agent.Config{} + if err := yaml.UnmarshalStrict(file.Data, config); err != nil { + return false, errors.Wrapf(err, "failed to unmarshal %s", agentConfigFilename) + } + + // Upconvert any deprecated fields + if err := conversion.ConvertAgentConfig(config); err != nil { + return false, err + } + + a.File, a.Config = file, config + if err = a.finish(); err != nil { + return false, err + } + + return true, nil +} + +func (a *AgentConfig) finish() error { + if err := a.validateAgent().ToAggregate(); err != nil { + return errors.Wrapf(err, "invalid Agent Config configuration") + } + + return nil +} + +func (a *AgentConfig) validateAgent() field.ErrorList { + allErrs := field.ErrorList{} + + if err := a.validateNodesHaveAtLeastOneMacAddressDefined(); err != nil { + allErrs = append(allErrs, err...) + } + + if err := a.validateRootDeviceHints(); err != nil { + allErrs = append(allErrs, err...) + } + + if err := a.validateRoles(); err != nil { + allErrs = append(allErrs, err...) + } + + return allErrs +} + +func (a *AgentConfig) validateNodesHaveAtLeastOneMacAddressDefined() field.ErrorList { + var allErrs field.ErrorList + + if len(a.Config.Hosts) == 0 { + return allErrs + } + + rootPath := field.NewPath("Hosts") + + for i := range a.Config.Hosts { + node := a.Config.Hosts[i] + interfacePath := rootPath.Index(i).Child("Interfaces") + if len(node.Interfaces) == 0 { + allErrs = append(allErrs, field.Required(interfacePath, "at least one interface must be defined for each node")) + } + + for j := range node.Interfaces { + if node.Interfaces[j].MacAddress == "" { + macAddressPath := interfacePath.Index(j).Child("macAddress") + allErrs = append(allErrs, field.Required(macAddressPath, "each interface must have a MAC address defined")) + } + } + } + return allErrs +} + +func (a *AgentConfig) validateRootDeviceHints() field.ErrorList { + var allErrs field.ErrorList + rootPath := field.NewPath("Hosts") + + for i, host := range a.Config.Hosts { + hostPath := rootPath.Index(i) + if host.RootDeviceHints.WWNWithExtension != "" { + allErrs = append(allErrs, field.Forbidden( + hostPath.Child("RootDeviceHints", "WWNWithExtension"), + "WWN extensions are not supported in root device hints")) + } + if host.RootDeviceHints.WWNVendorExtension != "" { + allErrs = append(allErrs, field.Forbidden( + hostPath.Child("RootDeviceHints", "WWNVendorExtension"), + "WWN vendor extensions are not supported in root device hints")) + } + } + + return allErrs +} + +func (a *AgentConfig) validateRoles() field.ErrorList { + var allErrs field.ErrorList + rootPath := field.NewPath("Hosts") + + for i, host := range a.Config.Hosts { + hostPath := rootPath.Index(i) + if len(host.Role) > 0 && host.Role != "master" && host.Role != "worker" { + allErrs = append(allErrs, field.Forbidden( + hostPath.Child("Host"), + "host role has incorrect value. Role must either be 'master' or 'worker'")) + } + } + + return allErrs +} + +// HostConfigFileMap is a map from a filepath ("/") to file content +// for hostconfig files. +type HostConfigFileMap map[string][]byte + +// HostConfigFiles returns a map from filename to contents of the files used for +// host-specific configuration by the agent installer client +func (a *AgentConfig) HostConfigFiles() (HostConfigFileMap, error) { + if a == nil || a.Config == nil { + return nil, nil + } + + files := HostConfigFileMap{} + for i, host := range a.Config.Hosts { + name := fmt.Sprintf("host-%d", i) + if host.Hostname != "" { + name = host.Hostname + } + + macs := []string{} + for _, iface := range host.Interfaces { + macs = append(macs, strings.ToLower(iface.MacAddress)+"\n") + } + + if len(macs) > 0 { + files[filepath.Join(name, "mac_addresses")] = []byte(strings.Join(macs, "")) + } + + rdh, err := yaml.Marshal(host.RootDeviceHints) + if err != nil { + return nil, err + } + if len(rdh) > 0 && string(rdh) != "{}\n" { + files[filepath.Join(name, "root-device-hints.yaml")] = rdh + } + + if len(host.Role) > 0 { + files[filepath.Join(name, "role")] = []byte(host.Role) + } + } + return files, nil +} + +func unmarshalJSON(b []byte) []byte { + output, _ := yaml.JSONToYAML(b) + return output +} diff --git a/pkg/asset/agent/agentconfig/agent_config_test.go b/pkg/asset/agent/agentconfig/agent_config_test.go new file mode 100644 index 0000000000..44885df380 --- /dev/null +++ b/pkg/asset/agent/agentconfig/agent_config_test.go @@ -0,0 +1,442 @@ +package agentconfig + +import ( + "os" + "testing" + + "github.com/golang/mock/gomock" + aiv1beta1 "github.com/openshift/assisted-service/api/v1beta1" + "github.com/openshift/installer/pkg/asset" + "github.com/openshift/installer/pkg/asset/mock" + "github.com/openshift/installer/pkg/types/agent" + "github.com/openshift/installer/pkg/types/baremetal" + "github.com/pkg/errors" + "github.com/stretchr/testify/assert" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// func TestAgentConfig_Generate(t *testing.T) { + +// cases := []struct { +// name string +// expectedError string +// expectedConfig *agent.Config +// }{ +// { +// name: "generate-basic-template", +// expectedConfig: &agent.Config{ +// TypeMeta: metav1.TypeMeta{ +// Kind: "AgentConfig", +// APIVersion: agent.AgentConfigVersion, +// }, +// ObjectMeta: metav1.ObjectMeta{ +// Name: "example-agent-config", +// Namespace: "cluster0", +// }, +// Spec: agent.Spec{ +// RendezvousIP: "your-node0-ip", +// Hosts: []agent.Host{ +// { +// Hostname: "change-to-hostname", +// Role: "master", +// RootDeviceHints: baremetal.RootDeviceHints{ +// DeviceName: "/dev/sda", +// }, +// Interfaces: []*aiv1beta1.Interface{ +// { +// Name: "your-network-interface-name", +// MacAddress: "00:00:00:00:00", +// }, +// }, +// }, +// }, +// }, +// }, +// }, +// } +// for _, tc := range cases { +// t.Run(tc.name, func(t *testing.T) { + +// parents := asset.Parents{} +// asset := &AgentConfig{} +// err := asset.Generate(parents) + +// if tc.expectedError != "" { +// assert.Equal(t, tc.expectedError, err.Error()) +// } else { +// assert.NoError(t, err) +// assert.Equal(t, tc.expectedConfig, asset.Config) +// assert.NotEmpty(t, asset.Files()) + +// configFile := asset.Files()[0] +// assert.Equal(t, "agent-config.yaml", configFile.Filename) + +// var actualConfig agent.Config +// err = yaml.Unmarshal(configFile.Data, &actualConfig) +// assert.NoError(t, err) +// assert.Equal(t, *tc.expectedConfig, actualConfig) +// } +// }) +// } + +// } + +func TestAgentConfig_LoadedFromDisk(t *testing.T) { + falseBool := false + falsePtr := &falseBool + + cases := []struct { + name string + data string + fetchError error + expectedFound bool + expectedError string + expectedConfig *agent.Config + }{ + { + name: "valid-config-single-node", + data: ` +apiVersion: v1alpha1 +metadata: + name: agent-config-cluster0 +rendezvousIP: 192.168.111.80 +hosts: + - hostname: control-0.example.org + role: master + rootDeviceHints: + deviceName: "/dev/sda" + hctl: "hctl-value" + model: "model-value" + vendor: "vendor-value" + serialNumber: "serial-number-value" + minSizeGigabytes: 20 + wwn: "wwn-value" + rotational: false + interfaces: + - name: enp2s0 + macAddress: 98:af:65:a5:8d:01 + - name: enp3s1 + macAddress: 28:d2:44:d2:b2:1a + networkConfig: + interfaces:`, + expectedFound: true, + expectedConfig: &agent.Config{ + TypeMeta: metav1.TypeMeta{ + APIVersion: agent.AgentConfigVersion, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "agent-config-cluster0", + }, + RendezvousIP: "192.168.111.80", + Hosts: []agent.Host{ + { + Hostname: "control-0.example.org", + Role: "master", + RootDeviceHints: baremetal.RootDeviceHints{ + DeviceName: "/dev/sda", + HCTL: "hctl-value", + Model: "model-value", + Vendor: "vendor-value", + SerialNumber: "serial-number-value", + MinSizeGigabytes: 20, + WWN: "wwn-value", + Rotational: falsePtr, + }, + Interfaces: []*aiv1beta1.Interface{ + { + Name: "enp2s0", + MacAddress: "98:af:65:a5:8d:01", + }, + { + Name: "enp3s1", + MacAddress: "28:d2:44:d2:b2:1a", + }, + }, + NetworkConfig: aiv1beta1.NetConfig{ + Raw: unmarshalJSON([]byte("interfaces:")), + }, + }, + }, + }, + }, + { + name: "valid-config-multiple-nodes", + data: ` +apiVersion: v1alpha1 +metadata: + name: agent-config-cluster0 +rendezvousIP: 192.168.111.80 +hosts: + - hostname: control-0.example.org + role: master + rootDeviceHints: + deviceName: "/dev/sda" + hctl: "hctl-value" + model: "model-value" + vendor: "vendor-value" + serialNumber: "serial-number-value" + minSizeGigabytes: 20 + wwn: "wwn-value" + rotational: false + interfaces: + - name: enp2s0 + macAddress: 98:af:65:a5:8d:01 + - name: enp3s1 + macAddress: 28:d2:44:d2:b2:1a + networkConfig: + interfaces: + - hostname: control-1.example.org + role: master + interfaces: + - name: enp2s0 + macAddress: 98:af:65:a5:8d:02 + - name: enp3s1 + macAddress: 28:d2:44:d2:b2:1b`, + expectedFound: true, + expectedConfig: &agent.Config{ + TypeMeta: metav1.TypeMeta{ + APIVersion: agent.AgentConfigVersion, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "agent-config-cluster0", + }, + RendezvousIP: "192.168.111.80", + Hosts: []agent.Host{ + { + Hostname: "control-0.example.org", + Role: "master", + RootDeviceHints: baremetal.RootDeviceHints{ + DeviceName: "/dev/sda", + HCTL: "hctl-value", + Model: "model-value", + Vendor: "vendor-value", + SerialNumber: "serial-number-value", + MinSizeGigabytes: 20, + WWN: "wwn-value", + Rotational: falsePtr, + }, + Interfaces: []*aiv1beta1.Interface{ + { + Name: "enp2s0", + MacAddress: "98:af:65:a5:8d:01", + }, + { + Name: "enp3s1", + MacAddress: "28:d2:44:d2:b2:1a", + }, + }, + NetworkConfig: aiv1beta1.NetConfig{ + Raw: unmarshalJSON([]byte("interfaces:")), + }, + }, + { + Hostname: "control-1.example.org", + Role: "master", + Interfaces: []*aiv1beta1.Interface{ + { + Name: "enp2s0", + MacAddress: "98:af:65:a5:8d:02", + }, + { + Name: "enp3s1", + MacAddress: "28:d2:44:d2:b2:1b", + }, + }, + }, + }, + }, + }, + { + name: "not-yaml", + data: `This is not a yaml file`, + expectedError: "failed to unmarshal agent-config.yaml: error unmarshaling JSON: while decoding JSON: json: cannot unmarshal string into Go value of type agent.Config", + }, + { + name: "file-not-found", + fetchError: &os.PathError{Err: os.ErrNotExist}, + }, + { + name: "error-fetching-file", + fetchError: errors.New("fetch failed"), + expectedError: "failed to load agent-config.yaml file: fetch failed", + }, + { + name: "unknown-field", + data: ` +apiVersion: v1alpha1 +metadata: + name: agent-config-wrong +wrongField: wrongValue`, + expectedError: "failed to unmarshal agent-config.yaml: error unmarshaling JSON: while decoding JSON: json: unknown field \"wrongField\"", + }, + { + name: "interface-missing-mac-address-error", + data: ` +apiVersion: v1alpha1 +metadata: + name: agent-config-cluster0 +rendezvousIP: 192.168.111.80 +hosts: + - hostname: control-0.example.org + interfaces: + - name: enp2s0 + - name: enp3s1 + macAddress: 28:d2:44:d2:b2:1a`, + expectedError: "invalid Agent Config configuration: Hosts[0].Interfaces[0].macAddress: Required value: each interface must have a MAC address defined", + }, + { + name: "unsupported wwn extension root device hint", + data: ` +apiVersion: v1alpha1 +metadata: + name: agent-config-cluster0 +rendezvousIP: 192.168.111.80 +hosts: + - hostname: control-0.example.org + interfaces: + - name: enp2s0 + macAddress: 98:af:65:a5:8d:01 + rootDeviceHints: + wwnWithExtension: "wwn-with-extension-value"`, + expectedError: "invalid Agent Config configuration: Hosts[0].RootDeviceHints.WWNWithExtension: Forbidden: WWN extensions are not supported in root device hints", + }, + { + name: "unsupported wwn vendor extension root device hint", + data: ` +apiVersion: v1alpha1 +metadata: + name: agent-config-cluster0 +rendezvousIP: 192.168.111.80 +hosts: + - hostname: control-0.example.org + interfaces: + - name: enp2s0 + macAddress: 98:af:65:a5:8d:01 + rootDeviceHints: + wwnVendorExtension: "wwn-with-vendor-extension-value"`, + expectedError: "invalid Agent Config configuration: Hosts[0].RootDeviceHints.WWNVendorExtension: Forbidden: WWN vendor extensions are not supported in root device hints", + }, + { + name: "node-hostname-and-role-are-not-required", + data: ` +apiVersion: v1alpha1 +metadata: + name: agent-config-cluster0 +rendezvousIP: 192.168.111.80 +hosts: + - interfaces: + - name: enp3s1 + macAddress: 28:d2:44:d2:b2:1a`, + expectedFound: true, + expectedConfig: &agent.Config{ + TypeMeta: metav1.TypeMeta{ + APIVersion: agent.AgentConfigVersion, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "agent-config-cluster0", + }, + RendezvousIP: "192.168.111.80", + Hosts: []agent.Host{ + { + Interfaces: []*aiv1beta1.Interface{ + { + Name: "enp3s1", + MacAddress: "28:d2:44:d2:b2:1a", + }, + }, + }, + }, + }, + }, + { + name: "host-roles-have-correct-values", + data: ` +apiVersion: v1alpha1 +metadata: + name: agent-config-cluster0 +rendezvousIP: 192.168.111.80 +hosts: + - role: master + interfaces: + - name: enp3s1 + macAddress: 28:d2:44:d2:b2:1a + - role: worker + interfaces: + - name: enp3s1 + macAddress: 28:d2:44:d2:b2:1b`, + expectedFound: true, + expectedConfig: &agent.Config{ + TypeMeta: metav1.TypeMeta{ + APIVersion: agent.AgentConfigVersion, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "agent-config-cluster0", + }, + RendezvousIP: "192.168.111.80", + Hosts: []agent.Host{ + { + Role: "master", + Interfaces: []*aiv1beta1.Interface{ + { + Name: "enp3s1", + MacAddress: "28:d2:44:d2:b2:1a", + }, + }, + }, + { + Role: "worker", + Interfaces: []*aiv1beta1.Interface{ + { + Name: "enp3s1", + MacAddress: "28:d2:44:d2:b2:1b", + }, + }, + }, + }, + }, + }, + { + name: "host-roles-have-incorrect-values", + data: ` +apiVersion: v1alpha1 +metadata: + name: agent-config-cluster0 +rendezvousIP: 192.168.111.80 +hosts: + - role: invalid-role + interfaces: + - name: enp3s1 + macAddress: 28:d2:44:d2:b2:1a`, + expectedError: "invalid Agent Config configuration: Hosts[0].Host: Forbidden: host role has incorrect value. Role must either be 'master' or 'worker'", + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + fileFetcher := mock.NewMockFileFetcher(mockCtrl) + fileFetcher.EXPECT().FetchByName(agentConfigFilename). + Return( + &asset.File{ + Filename: agentConfigFilename, + Data: []byte(tc.data)}, + tc.fetchError, + ) + + asset := &AgentConfig{} + found, err := asset.Load(fileFetcher) + if tc.expectedError != "" { + assert.Equal(t, tc.expectedError, err.Error()) + } else { + assert.NoError(t, err) + } + assert.Equal(t, tc.expectedFound, found, "unexpected found value returned from Load") + if tc.expectedFound { + assert.Equal(t, tc.expectedConfig, asset.Config, "unexpected Config in AgentConfig") + } + }) + } + +} diff --git a/pkg/asset/agent/image/agentimage.go b/pkg/asset/agent/image/agentimage.go new file mode 100644 index 0000000000..1adc648710 --- /dev/null +++ b/pkg/asset/agent/image/agentimage.go @@ -0,0 +1,96 @@ +package image + +import ( + "encoding/json" + "errors" + "io" + "os" + "path/filepath" + + "github.com/openshift/assisted-image-service/pkg/isoeditor" + "github.com/openshift/installer/pkg/asset" +) + +const ( + // TODO: Make this relative to the directory passed as --dir rather than + // the current working directory + agentISOFilename = "agent.iso" +) + +// AgentImage is an asset that generates the bootable image used to install clusters. +type AgentImage struct { + imageReader isoeditor.ImageReader +} + +var _ asset.WritableAsset = (*AgentImage)(nil) + +// Dependencies returns the assets on which the Bootstrap asset depends. +func (a *AgentImage) Dependencies() []asset.Asset { + return []asset.Asset{ + &Ignition{}, + &BaseIso{}, + } +} + +// Generate generates the image file for to ISO asset. +func (a *AgentImage) Generate(dependencies asset.Parents) error { + ignition := &Ignition{} + dependencies.Get(ignition) + + baseImage := &BaseIso{} + dependencies.Get(baseImage) + + ignitionByte, err := json.Marshal(ignition.Config) + if err != nil { + return err + } + + ignitionContent := &isoeditor.IgnitionContent{Config: ignitionByte} + custom, err := isoeditor.NewRHCOSStreamReader(baseImage.File.Filename, ignitionContent, nil) + if err != nil { + return err + } + + a.imageReader = custom + return nil +} + +// PersistToFile writes the iso image in the assets folder +func (a *AgentImage) PersistToFile(directory string) error { + if a.imageReader == nil { + return errors.New("image reader not available") + } + + defer a.imageReader.Close() + agentIsoFile := filepath.Join(directory, agentISOFilename) + + // Remove symlink if it exists + os.Remove(agentIsoFile) + + output, err := os.Create(agentIsoFile) + if err != nil { + return err + } + defer output.Close() + + _, err = io.Copy(output, a.imageReader) + return err +} + +// Name returns the human-friendly name of the asset. +func (a *AgentImage) Name() string { + return "Agent Installer ISO" +} + +// Load returns the ISO from disk. +func (a *AgentImage) Load(f asset.FileFetcher) (bool, error) { + // The ISO will not be needed by another asset so load is noop. + // This is implemented because it is required by WritableAsset + return false, nil +} + +// Files returns the files generated by the asset. +func (a *AgentImage) Files() []*asset.File { + // Return empty array because File will never be loaded. + return []*asset.File{} +} diff --git a/pkg/asset/agent/image/baseiso.go b/pkg/asset/agent/image/baseiso.go new file mode 100644 index 0000000000..f66ffc71a3 --- /dev/null +++ b/pkg/asset/agent/image/baseiso.go @@ -0,0 +1,181 @@ +package image + +import ( + "context" + "fmt" + "os" + "os/exec" + "time" + + "github.com/pkg/errors" + + "github.com/openshift/assisted-service/pkg/executer" + "github.com/openshift/installer/pkg/asset" + "github.com/openshift/installer/pkg/asset/agent" + "github.com/openshift/installer/pkg/asset/agent/manifests" + "github.com/openshift/installer/pkg/asset/agent/mirror" + "github.com/openshift/installer/pkg/rhcos" + "github.com/sirupsen/logrus" +) + +// BaseIso generates the base ISO file for the image +type BaseIso struct { + File *asset.File +} + +const ( + // TODO - add support for other architectures + archName = "x86_64" +) + +var ( + baseIsoFilename = "" +) + +var _ asset.WritableAsset = (*BaseIso)(nil) + +// Name returns the human-friendly name of the asset. +func (i *BaseIso) Name() string { + return "BaseIso Image" +} + +// getIsoFile is a pluggable function that gets the base ISO file +type getIsoFile func() (string, error) + +type getIso struct { + getter getIsoFile +} + +func newGetIso(getter getIsoFile) *getIso { + return &getIso{getter: getter} +} + +// GetIsoPluggable defines the method to use get the baseIso file +var GetIsoPluggable = downloadIso + +// Download the ISO using the URL in rhcos.json +func downloadIso() (string, error) { + + ctx, cancel := context.WithTimeout(context.TODO(), 30*time.Second) + defer cancel() + + // Get the ISO to use from rhcos.json + st, err := rhcos.FetchCoreOSBuild(ctx) + if err != nil { + return "", err + } + + // Defaults to using the x86_64 baremetal ISO for all platforms + // archName := arch.RpmArch(string(config.ControlPlane.Architecture)) + streamArch, err := st.GetArchitecture(archName) + if err != nil { + return "", err + } + if artifacts, ok := streamArch.Artifacts["metal"]; ok { + if format, ok := artifacts.Formats["iso"]; ok { + url := format.Disk.Location + + cachedImage, err := DownloadImageFile(url) + if err != nil { + return "", errors.Wrapf(err, "failed to download base ISO image %s", url) + } + return cachedImage, nil + } + } else { + return "", errors.Wrap(err, "invalid artifact") + } + + return "", fmt.Errorf("no ISO found to download for %s", archName) +} + +func getIsoFromReleasePayload() (string, error) { + + // TODO + return "", nil +} + +// Dependencies returns dependencies used by the asset. +func (i *BaseIso) Dependencies() []asset.Asset { + return []asset.Asset{ + &manifests.AgentManifests{}, + &agent.OptionalInstallConfig{}, + &mirror.RegistriesConf{}, + } +} + +// Generate the baseIso +func (i *BaseIso) Generate(dependencies asset.Parents) error { + + log := logrus.New() + // TODO - if image registry location is defined in InstallConfig, + // ic := &agent.OptionalInstallConfig{} + // p.Get(ic) + + // use the GetIso function to get the BaseIso from the release payload + agentManifests := &manifests.AgentManifests{} + dependencies.Get(agentManifests) + + var baseIsoFileName string + var err error + if agentManifests.ClusterImageSet != nil { + releaseImage := agentManifests.ClusterImageSet.Spec.ReleaseImage + pullSecret := agentManifests.GetPullSecretData() + registriesConf := &mirror.RegistriesConf{} + dependencies.Get(agentManifests, registriesConf) + + // If we have the image registry location and 'oc' command is available then get from release payload + ocRelease := NewRelease(&executer.CommonExecuter{}, + Config{MaxTries: OcDefaultTries, RetryDelay: OcDefaultRetryDelay}) + + log.Info("Extracting base ISO from release payload") + baseIsoFileName, err = ocRelease.GetBaseIso(log, releaseImage, pullSecret, registriesConf.MirrorConfig, archName) + if err == nil { + log.Debugf("Extracted base ISO image %s from release payload", baseIsoFileName) + i.File = &asset.File{Filename: baseIsoFileName} + return nil + } + if !errors.Is(err, &exec.Error{}) { // Already warned about missing oc binary + log.Warning("Failed to extract base ISO from release payload - check registry configuration") + } + } + + log.Info("Downloading base ISO") + isoGetter := newGetIso(GetIsoPluggable) + baseIsoFileName, err2 := isoGetter.getter() + if err2 == nil { + log.Debugf("Using base ISO image %s", baseIsoFileName) + i.File = &asset.File{Filename: baseIsoFileName} + return nil + } + log.Debugf("Failed to download base ISO: %s", err2) + + return errors.Wrap(err, "failed to get base ISO image") +} + +// Files returns the files generated by the asset. +func (i *BaseIso) Files() []*asset.File { + + if i.File != nil { + return []*asset.File{i.File} + } + return []*asset.File{} +} + +// Load returns the cached baseIso +func (i *BaseIso) Load(f asset.FileFetcher) (bool, error) { + + if baseIsoFilename == "" { + return false, nil + } + + baseIso, err := f.FetchByName(baseIsoFilename) + if err != nil { + if os.IsNotExist(err) { + return false, nil + } + return false, errors.Wrap(err, fmt.Sprintf("failed to load %s file", baseIsoFilename)) + } + + i.File = baseIso + return true, nil +} diff --git a/pkg/asset/agent/image/baseiso_test.go b/pkg/asset/agent/image/baseiso_test.go new file mode 100644 index 0000000000..6289504167 --- /dev/null +++ b/pkg/asset/agent/image/baseiso_test.go @@ -0,0 +1,30 @@ +package image + +import ( + "testing" + + "github.com/openshift/installer/pkg/asset" + "github.com/openshift/installer/pkg/asset/agent" + "github.com/openshift/installer/pkg/asset/agent/manifests" + "github.com/stretchr/testify/assert" +) + +func TestInfraBaseIso_Generate(t *testing.T) { + + GetIsoPluggable = func() (string, error) { + return "some-openshift-release.iso", nil + } + + parents := asset.Parents{} + manifests := &manifests.AgentManifests{} + installConfig := &agent.OptionalInstallConfig{} + parents.Add(manifests, installConfig) + + asset := &BaseIso{} + err := asset.Generate(parents) + assert.NoError(t, err) + + assert.NotEmpty(t, asset.Files()) + baseIso := asset.Files()[0] + assert.Equal(t, baseIso.Filename, "some-openshift-release.iso") +} diff --git a/pkg/asset/agent/image/cache.go b/pkg/asset/agent/image/cache.go new file mode 100644 index 0000000000..fe83cd5844 --- /dev/null +++ b/pkg/asset/agent/image/cache.go @@ -0,0 +1,270 @@ +package image + +import ( + "bytes" + "compress/gzip" + "crypto/sha256" + "fmt" + "io" + "net/http" + "net/url" + "os" + "path/filepath" + "strings" + + "github.com/h2non/filetype/matchers" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "github.com/ulikunitz/xz" + + "golang.org/x/sys/unix" +) + +// Note this code resides in tfvars/internal so it can't be imported and was therefore +// copied here. Will move this to a common location where it can be used by all pkgs. + +const ( + applicationName = "agent" + imageDataType = "image" +) + +// GetFileFromCache returns path of the cached file if found, otherwise returns an empty string +// or error +func GetFileFromCache(fileName string, cacheDir string) (string, error) { + + filePath := filepath.Join(cacheDir, fileName) + + // If the file has already been cached, return its path + _, err := os.Stat(filePath) + if err == nil { + logrus.Debugf("The file was found in cache: %v. Reusing...", filePath) + return filePath, nil + } + if !os.IsNotExist(err) { + return "", err + } + + return "", nil +} + +// GetCacheDir returns a local path of the cache, where the installer should put the data: +// /agent/_cache +// If the directory doesn't exist, it will be automatically created. +func GetCacheDir(dataType string) (string, error) { + if dataType == "" { + return "", errors.Errorf("data type can't be an empty string") + } + + userCacheDir, err := os.UserCacheDir() + if err != nil { + return "", err + } + + cacheDir := filepath.Join(userCacheDir, applicationName, dataType+"_cache") + + _, err = os.Stat(cacheDir) + if err != nil { + if os.IsNotExist(err) { + err = os.MkdirAll(cacheDir, 0755) + if err != nil { + return "", err + } + } else { + return "", err + } + } + + return cacheDir, nil +} + +// cacheFile puts data in the cache +func cacheFile(reader io.Reader, filePath string, sha256Checksum string) (err error) { + logrus.Debugf("Unpacking file into %q...", filePath) + + flockPath := fmt.Sprintf("%s.lock", filePath) + flock, err := os.Create(flockPath) + if err != nil { + return err + } + defer flock.Close() + defer func() { + err2 := os.Remove(flockPath) + if err == nil { + err = err2 + } + }() + + err = unix.Flock(int(flock.Fd()), unix.LOCK_EX) + if err != nil { + return err + } + defer func() { + err2 := unix.Flock(int(flock.Fd()), unix.LOCK_UN) + if err == nil { + err = err2 + } + }() + + _, err = os.Stat(filePath) + if err != nil && !os.IsNotExist(err) { + return nil // another cacheFile beat us to it + } + + tempPath := fmt.Sprintf("%s.tmp", filePath) + + // Delete the temporary file that may have been left over from previous launches. + err = os.Remove(tempPath) + if err != nil { + if !os.IsNotExist(err) { + return errors.Errorf("failed to clean up %s: %v", tempPath, err) + } + } else { + logrus.Debugf("Temporary file %v that remained after the previous launches was deleted", tempPath) + } + + file, err := os.OpenFile(tempPath, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0444) + if err != nil { + return err + } + closed := false + defer func() { + if !closed { + file.Close() + } + }() + + // Detect whether we know how to decompress the file + // See http://golang.org/pkg/net/http/#DetectContentType for why we use 512 + buf := make([]byte, 512) + _, err = reader.Read(buf) + if err != nil { + return err + } + + reader = io.MultiReader(bytes.NewReader(buf), reader) + switch { + case matchers.Gz(buf): + logrus.Debug("decompressing the image archive as gz") + uncompressor, err := gzip.NewReader(reader) + if err != nil { + return err + } + defer uncompressor.Close() + reader = uncompressor + case matchers.Xz(buf): + logrus.Debug("decompressing the image archive as xz") + uncompressor, err := xz.NewReader(reader) + if err != nil { + return err + } + reader = uncompressor + default: + // No need for an interposer otherwise + logrus.Debug("no known archive format detected for image, assuming no decompression necessary") + } + + // Wrap the reader in TeeReader to calculate sha256 checksum on the fly + hasher := sha256.New() + if sha256Checksum != "" { + reader = io.TeeReader(reader, hasher) + } + + _, err = io.Copy(file, reader) + if err != nil { + return err + } + + err = file.Close() + if err != nil { + return err + } + closed = true + + // Validate sha256 checksum + if sha256Checksum != "" { + foundChecksum := fmt.Sprintf("%x", hasher.Sum(nil)) + if sha256Checksum != foundChecksum { + logrus.Error("File sha256 checksum is invalid.") + return errors.Errorf("Checksum mismatch for %s; expected=%s found=%s", filePath, sha256Checksum, foundChecksum) + } + + logrus.Debug("Checksum validation is complete...") + } + + return os.Rename(tempPath, filePath) +} + +// urlWithIntegrity pairs a URL with an optional expected sha256 checksum (after decompression, if any) +// If the query string contains sha256 parameter (i.e. https://example.com/data.bin?sha256=098a5a...), +// then the downloaded data checksum will be compared with the provided value. +type urlWithIntegrity struct { + location url.URL + uncompressedSHA256 string +} + +func (u *urlWithIntegrity) uncompressedName() string { + n := filepath.Base(u.location.Path) + return strings.TrimSuffix(strings.TrimSuffix(n, ".gz"), ".xz") +} + +// download obtains a file from a given URL, puts it in the cache folder, defined by dataType parameter, +// and returns the local file path. +func (u *urlWithIntegrity) download(dataType string) (string, error) { + fileName := u.uncompressedName() + + cacheDir, err := GetCacheDir(dataType) + if err != nil { + return "", err + } + + filePath, err := GetFileFromCache(fileName, cacheDir) + if err != nil { + return "", err + } + if filePath != "" { + // Found cached file + return filePath, nil + } + + // Send a request to get the file + resp, err := http.Get(u.location.String()) + if err != nil { + return "", err + } + defer resp.Body.Close() + + // Check server response + if resp.StatusCode != http.StatusOK { + return "", errors.Errorf("bad status: %s", resp.Status) + } + + filePath = filepath.Join(cacheDir, fileName) + err = cacheFile(resp.Body, filePath, u.uncompressedSHA256) + if err != nil { + return "", err + } + + return filePath, nil +} + +// DownloadImageFile is a helper function that obtains an image file from a given URL, +// puts it in the cache and returns the local file path. If the file is compressed +// by a known compressor, the file is uncompressed prior to being returned. +func DownloadImageFile(baseURL string) (string, error) { + logrus.Debugf("Obtaining RHCOS image file from '%v'", baseURL) + + var u urlWithIntegrity + parsedURL, err := url.ParseRequestURI(baseURL) + if err != nil { + return "", err + } + q := parsedURL.Query() + if uncompressedSHA256, ok := q["sha256"]; ok { + u.uncompressedSHA256 = uncompressedSHA256[0] + q.Del("sha256") + parsedURL.RawQuery = q.Encode() + } + u.location = *parsedURL + + return u.download(imageDataType) +} diff --git a/pkg/asset/agent/image/ignition.go b/pkg/asset/agent/image/ignition.go new file mode 100644 index 0000000000..4813069925 --- /dev/null +++ b/pkg/asset/agent/image/ignition.go @@ -0,0 +1,401 @@ +package image + +import ( + "fmt" + "net" + "net/url" + "path" + "path/filepath" + "regexp" + "strings" + + "github.com/coreos/ignition/v2/config/util" + igntypes "github.com/coreos/ignition/v2/config/v3_2/types" + "github.com/google/uuid" + "github.com/sirupsen/logrus" + + hiveext "github.com/openshift/assisted-service/api/hiveextension/v1beta1" + "github.com/openshift/assisted-service/api/v1beta1" + "github.com/openshift/assisted-service/models" + "github.com/openshift/installer/pkg/asset" + "github.com/openshift/installer/pkg/asset/agent/agentconfig" + "github.com/openshift/installer/pkg/asset/agent/manifests" + "github.com/openshift/installer/pkg/asset/agent/mirror" + "github.com/openshift/installer/pkg/asset/ignition" + "github.com/openshift/installer/pkg/asset/ignition/bootstrap" + "github.com/openshift/installer/pkg/asset/tls" + "github.com/openshift/installer/pkg/types/agent" + "github.com/pkg/errors" +) + +const manifestPath = "/etc/assisted/manifests" +const hostnamesPath = "/etc/assisted/hostnames" +const nmConnectionsPath = "/etc/assisted/network" +const extraManifestPath = "/etc/assisted/extra-manifests" + +// Ignition is an asset that generates the agent installer ignition file. +type Ignition struct { + Config *igntypes.Config +} + +// agentTemplateData is the data used to replace values in agent template +// files. +type agentTemplateData struct { + ServiceProtocol string + ServiceBaseURL string + PullSecret string + // PullSecretToken is token to use for authentication when AUTH_TYPE=rhsso + // in assisted-service + PullSecretToken string + NodeZeroIP string + AssistedServiceHost string + APIVIP string + ControlPlaneAgents int + WorkerAgents int + ReleaseImages string + ReleaseImage string + ReleaseImageMirror string + HaveMirrorConfig bool + InfraEnvID string +} + +var ( + agentEnabledServices = []string{ + "agent.service", + "assisted-service-db.service", + "assisted-service-pod.service", + "assisted-service.service", + "create-cluster-and-infraenv.service", + "node-zero.service", + "multipathd.service", + "pre-network-manager-config.service", + "selinux.service", + "set-hostname.service", + "start-cluster-installation.service", + } +) + +// Name returns the human-friendly name of the asset. +func (a *Ignition) Name() string { + return "Agent Installer Ignition" +} + +// Dependencies returns the assets on which the Ignition asset depends. +func (a *Ignition) Dependencies() []asset.Asset { + return []asset.Asset{ + &manifests.AgentManifests{}, + &manifests.ExtraManifests{}, + &tls.KubeAPIServerLBSignerCertKey{}, + &tls.KubeAPIServerLocalhostSignerCertKey{}, + &tls.KubeAPIServerServiceNetworkSignerCertKey{}, + &tls.AdminKubeConfigSignerCertKey{}, + &tls.AdminKubeConfigClientCertKey{}, + &agentconfig.AgentConfig{}, + &mirror.RegistriesConf{}, + &mirror.CaBundle{}, + } +} + +// Generate generates the agent installer ignition. +func (a *Ignition) Generate(dependencies asset.Parents) error { + agentManifests := &manifests.AgentManifests{} + agentConfigAsset := &agentconfig.AgentConfig{} + extraManifests := &manifests.ExtraManifests{} + dependencies.Get(agentManifests, agentConfigAsset, extraManifests) + + infraEnv := agentManifests.InfraEnv + + config := igntypes.Config{ + Ignition: igntypes.Ignition{ + Version: igntypes.MaxVersion.String(), + }, + Passwd: igntypes.Passwd{ + Users: []igntypes.PasswdUser{ + { + Name: "core", + SSHAuthorizedKeys: []igntypes.SSHAuthorizedKey{ + igntypes.SSHAuthorizedKey(infraEnv.Spec.SSHAuthorizedKey), + }, + }, + }, + }, + } + + if len(agentManifests.NMStateConfigs) == 0 { + return errors.New("at least one NMState configuration must be provided") + } + + nodeZeroIP, err := RetrieveRendezvousIP(agentConfigAsset.Config, agentManifests.NMStateConfigs) + if err != nil { + return err + } + + // TODO: don't hard-code target arch + releaseImageList, err := releaseImageList(agentManifests.ClusterImageSet.Spec.ReleaseImage, "x86_64") + if err != nil { + return err + } + + registriesConfig := &mirror.RegistriesConf{} + registryCABundle := &mirror.CaBundle{} + dependencies.Get(registriesConfig, registryCABundle) + + releaseImageMirror := getMirrorFromRelease(agentManifests.ClusterImageSet.Spec.ReleaseImage, registriesConfig) + + infraEnvID := uuid.New().String() + logrus.Debug("Generated random infra-env id ", infraEnvID) + + agentTemplateData := getTemplateData( + agentManifests.GetPullSecretData(), + nodeZeroIP, + releaseImageList, + agentManifests.ClusterImageSet.Spec.ReleaseImage, + releaseImageMirror, + len(registriesConfig.MirrorConfig) > 0, + agentManifests.AgentClusterInstall, + infraEnvID) + + err = bootstrap.AddStorageFiles(&config, "/", "agent/files", agentTemplateData) + if err != nil { + return err + } + + // Set up bootstrap service recording + if err := bootstrap.AddStorageFiles(&config, + "/usr/local/bin/bootstrap-service-record.sh", + "bootstrap/files/usr/local/bin/bootstrap-service-record.sh", + nil); err != nil { + return err + } + + // Use bootstrap script to get container images + relImgData := struct{ ReleaseImage string }{ + ReleaseImage: agentManifests.ClusterImageSet.Spec.ReleaseImage, + } + for _, script := range []string{"release-image.sh", "release-image-download.sh"} { + if err := bootstrap.AddStorageFiles(&config, + "/usr/local/bin/"+script, + "bootstrap/files/usr/local/bin/"+script+".template", + relImgData); err != nil { + return err + } + } + + // add ZTP manifests to manifestPath + for _, file := range agentManifests.FileList { + manifestFile := ignition.FileFromBytes(filepath.Join(manifestPath, filepath.Base(file.Filename)), + "root", 0600, file.Data) + config.Storage.Files = append(config.Storage.Files, manifestFile) + } + + // add AgentConfig if provided + if agentConfigAsset.Config != nil { + agentConfigFile := ignition.FileFromBytes(filepath.Join(manifestPath, filepath.Base(agentConfigAsset.File.Filename)), + "root", 0600, agentConfigAsset.File.Data) + config.Storage.Files = append(config.Storage.Files, agentConfigFile) + } + + addMacAddressToHostnameMappings(&config, agentConfigAsset) + + err = addStaticNetworkConfig(&config, agentManifests.StaticNetworkConfigs) + if err != nil { + return err + } + + err = bootstrap.AddSystemdUnits(&config, "agent/systemd/units", agentTemplateData, agentEnabledServices) + if err != nil { + return err + } + + addTLSData(&config, dependencies) + + addMirrorData(&config, registriesConfig, registryCABundle) + + addHostConfig(&config, agentConfigAsset) + + addExtraManifests(&config, extraManifests) + + a.Config = &config + return nil +} + +func getTemplateData(pullSecret, nodeZeroIP, releaseImageList, releaseImage, + releaseImageMirror string, haveMirrorConfig bool, + agentClusterInstall *hiveext.AgentClusterInstall, + infraEnvID string) *agentTemplateData { + serviceBaseURL := url.URL{ + Scheme: "http", + Host: net.JoinHostPort(nodeZeroIP, "8090"), + Path: "/", + } + + return &agentTemplateData{ + ServiceProtocol: serviceBaseURL.Scheme, + ServiceBaseURL: serviceBaseURL.String(), + PullSecret: pullSecret, + PullSecretToken: "", + NodeZeroIP: serviceBaseURL.Hostname(), + AssistedServiceHost: serviceBaseURL.Host, + APIVIP: agentClusterInstall.Spec.APIVIP, + ControlPlaneAgents: agentClusterInstall.Spec.ProvisionRequirements.ControlPlaneAgents, + WorkerAgents: agentClusterInstall.Spec.ProvisionRequirements.WorkerAgents, + ReleaseImages: releaseImageList, + ReleaseImage: releaseImage, + ReleaseImageMirror: releaseImageMirror, + HaveMirrorConfig: haveMirrorConfig, + InfraEnvID: infraEnvID, + } +} + +func addStaticNetworkConfig(config *igntypes.Config, staticNetworkConfig []*models.HostStaticNetworkConfig) (err error) { + if len(staticNetworkConfig) == 0 { + return nil + } + + // Get the static network configuration from nmstate and generate NetworkManager ignition files + filesList, err := manifests.GetNMIgnitionFiles(staticNetworkConfig) + if err != nil { + return err + } + + for i := range filesList { + nmFilePath := path.Join(nmConnectionsPath, filesList[i].FilePath) + nmStateIgnFile := ignition.FileFromBytes(nmFilePath, "root", 0600, []byte(filesList[i].FileContents)) + config.Storage.Files = append(config.Storage.Files, nmStateIgnFile) + } + + nmStateScriptFilePath := "/usr/local/bin/pre-network-manager-config.sh" + // A local version of the assisted-service internal script is currently used + nmStateScript := ignition.FileFromBytes(nmStateScriptFilePath, "root", 0755, []byte(manifests.PreNetworkConfigScript)) + config.Storage.Files = append(config.Storage.Files, nmStateScript) + + return nil +} + +func addTLSData(config *igntypes.Config, dependencies asset.Parents) { + certKeys := []asset.Asset{ + &tls.KubeAPIServerLBSignerCertKey{}, + &tls.KubeAPIServerLocalhostSignerCertKey{}, + &tls.KubeAPIServerServiceNetworkSignerCertKey{}, + &tls.AdminKubeConfigSignerCertKey{}, + &tls.AdminKubeConfigClientCertKey{}, + } + dependencies.Get(certKeys...) + + for _, ck := range certKeys { + for _, d := range ck.(asset.WritableAsset).Files() { + f := ignition.FileFromBytes(path.Join("/opt/agent", d.Filename), "root", 0600, d.Data) + config.Storage.Files = append(config.Storage.Files, f) + } + } +} + +func addMirrorData(config *igntypes.Config, registriesConfig *mirror.RegistriesConf, registryCABundle *mirror.CaBundle) { + + // This is required for assisted-service to build the ICSP for openshift-install + if registriesConfig.File != nil { + registriesFile := ignition.FileFromBytes("/etc/containers/registries.conf", + "root", 0600, registriesConfig.File.Data) + config.Storage.Files = append(config.Storage.Files, registriesFile) + } + + // This is required for the agent to run the podman commands to the mirror + if registryCABundle.File != nil && len(registryCABundle.File.Data) > 0 { + caFile := ignition.FileFromBytes("/etc/pki/ca-trust/source/anchors/domain.crt", + "root", 0600, registryCABundle.File.Data) + config.Storage.Files = append(config.Storage.Files, caFile) + } +} + +// Creates a file named with a host's MAC address. The desired hostname +// is the file's content. The files are read by a systemd service that +// sets the hostname using "hostnamectl set-hostname" when the ISO boots. +func addMacAddressToHostnameMappings( + config *igntypes.Config, + agentConfigAsset *agentconfig.AgentConfig) { + if agentConfigAsset.Config == nil || len(agentConfigAsset.Config.Hosts) == 0 { + return + } + for _, host := range agentConfigAsset.Config.Hosts { + if host.Hostname != "" { + file := ignition.FileFromBytes(filepath.Join(hostnamesPath, + strings.ToLower(filepath.Base(host.Interfaces[0].MacAddress))), + "root", 0600, []byte(host.Hostname)) + config.Storage.Files = append(config.Storage.Files, file) + } + } +} + +func addHostConfig(config *igntypes.Config, agentConfig *agentconfig.AgentConfig) error { + confs, err := agentConfig.HostConfigFiles() + if err != nil { + return err + } + + for path, content := range confs { + hostConfigFile := ignition.FileFromBytes(filepath.Join("/etc/assisted/hostconfig", path), "root", 0644, content) + config.Storage.Files = append(config.Storage.Files, hostConfigFile) + } + return nil +} + +func addExtraManifests(config *igntypes.Config, extraManifests *manifests.ExtraManifests) { + + user := "root" + mode := 0644 + + config.Storage.Directories = append(config.Storage.Directories, igntypes.Directory{ + Node: igntypes.Node{ + Path: extraManifestPath, + User: igntypes.NodeUser{ + Name: &user, + }, + Overwrite: util.BoolToPtr(true), + }, + DirectoryEmbedded1: igntypes.DirectoryEmbedded1{ + Mode: &mode, + }, + }) + + for _, file := range extraManifests.FileList { + extraFile := ignition.FileFromBytes(filepath.Join(extraManifestPath, filepath.Base(file.Filename)), user, mode, file.Data) + config.Storage.Files = append(config.Storage.Files, extraFile) + } +} + +// RetrieveRendezvousIP Returns the Rendezvous IP from either AgentConfig or NMStateConfig +func RetrieveRendezvousIP(agentConfig *agent.Config, nmStateConfigs []*v1beta1.NMStateConfig) (string, error) { + var err error + var rendezvousIP string + + if agentConfig != nil && agentConfig.RendezvousIP != "" { + rendezvousIP = agentConfig.RendezvousIP + logrus.Debug("RendezvousIP from the AgentConfig ", rendezvousIP) + + } else if len(nmStateConfigs) > 0 { + rendezvousIP, err = manifests.GetNodeZeroIP(nmStateConfigs) + logrus.Debug("RendezvousIP from the NMStateConfig ", rendezvousIP) + } else { + err = errors.New("missing rendezvousIP in agent-config or at least one NMStateConfig manifest") + } + return rendezvousIP, err +} + +func getMirrorFromRelease(releaseImage string, registriesConfig *mirror.RegistriesConf) string { + + releaseImageMirror := "" + source := regexp.MustCompile(`^(.+?)(@sha256)?:(.+)`).FindStringSubmatch(releaseImage) + for _, config := range registriesConfig.MirrorConfig { + if config.Location == source[1] { + // include the tag with the build release image + if len(source) == 4 { + // Has Sha256 + releaseImageMirror = fmt.Sprintf("%s%s:%s", config.Mirror, source[2], source[3]) + } else if len(source) == 3 { + releaseImageMirror = fmt.Sprintf("%s:%s", config.Mirror, source[2]) + } + } + } + + return releaseImageMirror +} diff --git a/pkg/asset/agent/image/ignition_test.go b/pkg/asset/agent/image/ignition_test.go new file mode 100644 index 0000000000..8446aef9fd --- /dev/null +++ b/pkg/asset/agent/image/ignition_test.go @@ -0,0 +1,497 @@ +package image + +import ( + "encoding/base64" + "fmt" + "os" + "path" + "strings" + "testing" + + igntypes "github.com/coreos/ignition/v2/config/v3_2/types" + + hiveext "github.com/openshift/assisted-service/api/hiveextension/v1beta1" + "github.com/openshift/assisted-service/api/v1beta1" + aiv1beta1 "github.com/openshift/assisted-service/api/v1beta1" + "github.com/openshift/assisted-service/models" + hivev1 "github.com/openshift/hive/apis/hive/v1" + "github.com/openshift/installer/pkg/asset" + "github.com/openshift/installer/pkg/asset/agent/agentconfig" + "github.com/openshift/installer/pkg/asset/agent/manifests" + "github.com/openshift/installer/pkg/asset/agent/mirror" + "github.com/openshift/installer/pkg/asset/tls" + "github.com/openshift/installer/pkg/types/agent" + "github.com/stretchr/testify/assert" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// Unable to test Generate because bootstrap.AddStorageFiles +// returns error in unit test: +// open data/agent/files: no such file or directory +// Unit test working directory is ./pkg/asset/agent/image +// While normal execution working directory is ./data +// func TestIgnition_Generate(t *testing.T) {} + +func TestIgnition_getTemplateData(t *testing.T) { + clusterImageSet := &hivev1.ClusterImageSet{ + ObjectMeta: metav1.ObjectMeta{ + Name: "openshift-v4.10.0", + }, + Spec: hivev1.ClusterImageSetSpec{ + ReleaseImage: "quay.io:443/openshift-release-dev/ocp-release:4.10.0-rc.1-x86_64", + }, + } + pullSecret := "pull-secret" + nodeZeroIP := "192.168.111.80" + agentClusterInstall := &hiveext.AgentClusterInstall{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-agent-cluster-install", + Namespace: "cluster0", + }, + Spec: hiveext.AgentClusterInstallSpec{ + APIVIP: "192.168.111.2", + SSHPublicKey: "ssh-rsa AAAAmyKey", + ProvisionRequirements: hiveext.ProvisionRequirements{ + ControlPlaneAgents: 3, + WorkerAgents: 5, + }, + }, + } + releaseImage := "quay.io:443/openshift-release-dev/ocp-release:4.10.0-rc.1-x86_64" + releaseImageMirror := "virthost.ostest.test.metalkube.org:5000/localimages/local-release-image" + infraEnvID := "random-infra-env-id" + haveMirrorConfig := true + + releaseImageList, err := releaseImageList(clusterImageSet.Spec.ReleaseImage, "x86_64") + assert.NoError(t, err) + templateData := getTemplateData(pullSecret, nodeZeroIP, releaseImageList, releaseImage, releaseImageMirror, haveMirrorConfig, agentClusterInstall, infraEnvID) + assert.Equal(t, "http", templateData.ServiceProtocol) + assert.Equal(t, "http://"+nodeZeroIP+":8090/", templateData.ServiceBaseURL) + assert.Equal(t, pullSecret, templateData.PullSecret) + assert.Equal(t, "", templateData.PullSecretToken) + assert.Equal(t, nodeZeroIP, templateData.NodeZeroIP) + assert.Equal(t, nodeZeroIP+":8090", templateData.AssistedServiceHost) + assert.Equal(t, agentClusterInstall.Spec.APIVIP, templateData.APIVIP) + assert.Equal(t, agentClusterInstall.Spec.ProvisionRequirements.ControlPlaneAgents, templateData.ControlPlaneAgents) + assert.Equal(t, agentClusterInstall.Spec.ProvisionRequirements.WorkerAgents, templateData.WorkerAgents) + assert.Equal(t, releaseImageList, templateData.ReleaseImages) + assert.Equal(t, releaseImage, templateData.ReleaseImage) + assert.Equal(t, releaseImageMirror, templateData.ReleaseImageMirror) + assert.Equal(t, haveMirrorConfig, templateData.HaveMirrorConfig) + assert.Equal(t, infraEnvID, templateData.InfraEnvID) +} + +func TestIgnition_addStaticNetworkConfig(t *testing.T) { + cases := []struct { + Name string + staticNetworkConfig []*models.HostStaticNetworkConfig + expectedError string + expectedFileList []string + }{ + { + Name: "default", + staticNetworkConfig: []*models.HostStaticNetworkConfig{ + { + MacInterfaceMap: models.MacInterfaceMap{ + {LogicalNicName: "eth0", MacAddress: "52:54:01:aa:aa:a1"}, + }, + NetworkYaml: "interfaces:\n- ipv4:\n address:\n - ip: 192.168.122.21\n prefix-length: 24\n enabled: true\n mac-address: 52:54:01:aa:aa:a1\n name: eth0\n state: up\n type: ethernet\n", + }, + }, + expectedError: "", + expectedFileList: []string{ + "/etc/assisted/network/host0/eth0.nmconnection", + "/etc/assisted/network/host0/mac_interface.ini", + "/usr/local/bin/pre-network-manager-config.sh", + }, + }, + { + Name: "no-static-network-configs", + staticNetworkConfig: []*models.HostStaticNetworkConfig{}, + expectedError: "", + expectedFileList: nil, + }, + { + Name: "error-processing-config", + staticNetworkConfig: []*models.HostStaticNetworkConfig{ + { + MacInterfaceMap: models.MacInterfaceMap{ + {LogicalNicName: "eth0", MacAddress: "52:54:01:aa:aa:a1"}, + }, + NetworkYaml: "interfaces:\n- ipv4:\n address:\n - ip: bad-ip\n prefix-length: 24\n enabled: true\n mac-address: 52:54:01:aa:aa:a1\n name: eth0\n state: up\n type: ethernet\n", + }, + }, + expectedError: "'bad-ip' does not appear to be an IPv4 or IPv6 address", + expectedFileList: nil, + }, + } + + for _, tc := range cases { + t.Run(tc.Name, func(t *testing.T) { + config := igntypes.Config{} + err := addStaticNetworkConfig(&config, tc.staticNetworkConfig) + + if tc.expectedError != "" { + assert.Regexp(t, tc.expectedError, err.Error()) + } else { + assert.NoError(t, err) + } + + var fileList []string + for _, file := range config.Storage.Files { + fileList = append(fileList, file.Node.Path) + } + assert.Equal(t, tc.expectedFileList, fileList) + }) + } +} + +func TestRetrieveRendezvousIP(t *testing.T) { + rawConfig := `interfaces: + - ipv4: + address: + - ip: "192.168.122.21"` + cases := []struct { + Name string + agentConfig *agent.Config + nmStateConfigs []*v1beta1.NMStateConfig + expectedRendezvousIP string + expectedError string + }{ + { + Name: "valid-agent-config-provided-with-RendezvousIP", + agentConfig: &agent.Config{ + RendezvousIP: "192.168.122.21", + Hosts: []agent.Host{ + { + Hostname: "control-0.example.org", + Role: "master", + }, + }, + }, + expectedRendezvousIP: "192.168.122.21", + }, + { + Name: "no-agent-config-provided-so-read-from-nmstateconfig", + nmStateConfigs: []*v1beta1.NMStateConfig{ + { + Spec: v1beta1.NMStateConfigSpec{ + NetConfig: v1beta1.NetConfig{ + Raw: []byte(rawConfig), + }, + }, + }, + }, + expectedRendezvousIP: "192.168.122.21", + }, + { + Name: "neither-agent-config-was-provided-with-RendezvousIP-nor-nmstateconfig-manifest", + agentConfig: &agent.Config{ + Hosts: []agent.Host{ + { + Hostname: "control-0.example.org", + Role: "master", + }, + }, + }, + expectedError: "missing rendezvousIP in agent-config or at least one NMStateConfig manifest", + }, + } + for _, tc := range cases { + t.Run(tc.Name, func(t *testing.T) { + rendezvousIP, err := RetrieveRendezvousIP(tc.agentConfig, tc.nmStateConfigs) + if tc.expectedError != "" { + assert.Regexp(t, tc.expectedError, err.Error()) + } else { + assert.NoError(t, err) + assert.Equal(t, tc.expectedRendezvousIP, rendezvousIP) + } + }) + } + +} + +func TestAddHostConfig_Roles(t *testing.T) { + cases := []struct { + Name string + agentConfig *agentconfig.AgentConfig + expectedNumberOfHostConfigFiles int + }{ + { + Name: "one-host-role-defined", + agentConfig: &agentconfig.AgentConfig{ + Config: &agent.Config{ + Hosts: []agent.Host{ + { + Role: "master", + }, + }, + }, + }, + expectedNumberOfHostConfigFiles: 1, + }, + { + Name: "multiple-host-roles-defined", + agentConfig: &agentconfig.AgentConfig{ + Config: &agent.Config{ + Hosts: []agent.Host{ + { + Role: "master", + }, + { + Role: "master", + }, + { + Role: "master", + }, + { + Role: "worker", + }, + { + Role: "worker", + }, + }, + }, + }, + expectedNumberOfHostConfigFiles: 5, + }, + { + Name: "zero-host-roles-defined", + expectedNumberOfHostConfigFiles: 0, + }, + } + for _, tc := range cases { + t.Run(tc.Name, func(t *testing.T) { + config := &igntypes.Config{} + err := addHostConfig(config, tc.agentConfig) + assert.NoError(t, err) + assert.Equal(t, len(config.Storage.Files), tc.expectedNumberOfHostConfigFiles) + for _, file := range config.Storage.Files { + assert.Equal(t, true, strings.HasPrefix(file.Path, "/etc/assisted/hostconfig")) + assert.Equal(t, true, strings.HasSuffix(file.Path, "role")) + } + }) + } + +} + +func TestIgnition_Generate(t *testing.T) { + + // This patch currently allows testing the Ignition asset using the embedded resources. + // TODO: Replace it by mocking the filesystem in bootstrap.AddStorageFiles() + workingDirectory, _ := os.Getwd() + os.Chdir(path.Join(workingDirectory, "../../../../data")) + + cases := []struct { + name string + overrideDeps []asset.Asset + expectedError string + expectedFiles []string + }{ + { + name: "no-extra-manifests", + expectedFiles: []string{}, + }, + { + name: "default", + overrideDeps: []asset.Asset{ + &manifests.ExtraManifests{ + FileList: []*asset.File{ + { + Filename: "openshift/test-configmap.yaml", + }, + }, + }, + }, + expectedFiles: []string{ + "/etc/assisted/extra-manifests/test-configmap.yaml", + }, + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + + deps := buildIgnitionAssetDefaultDependencies() + + for _, od := range tc.overrideDeps { + for i, d := range deps { + if d.Name() == od.Name() { + deps[i] = od + break + } + } + } + + parents := asset.Parents{} + parents.Add(deps...) + + ignitionAsset := &Ignition{} + err := ignitionAsset.Generate(parents) + + if tc.expectedError != "" { + assert.Equal(t, tc.expectedError, err.Error()) + } else { + assert.NoError(t, err) + + assert.Len(t, ignitionAsset.Config.Storage.Directories, 1) + assert.Equal(t, "/etc/assisted/extra-manifests", ignitionAsset.Config.Storage.Directories[0].Node.Path) + + for _, f := range tc.expectedFiles { + found := false + for _, i := range ignitionAsset.Config.Storage.Files { + if i.Node.Path == f { + found = true + break + } + } + assert.True(t, found, fmt.Sprintf("Expected file %s not found", f)) + } + } + }) + } +} + +// This test util create the minimum valid set of dependencies for the +// Ignition asset +func buildIgnitionAssetDefaultDependencies() []asset.Asset { + secretDataBytes, _ := base64.StdEncoding.DecodeString("super-secret") + + return []asset.Asset{ + &manifests.AgentManifests{ + InfraEnv: &v1beta1.InfraEnv{ + Spec: v1beta1.InfraEnvSpec{ + SSHAuthorizedKey: "my-ssh-key", + }, + }, + ClusterImageSet: &hivev1.ClusterImageSet{ + Spec: hivev1.ClusterImageSetSpec{ + ReleaseImage: "registry.ci.openshift.org/origin/release:4.11", + }, + }, + PullSecret: &v1.Secret{ + Data: map[string][]byte{ + ".dockerconfigjson": secretDataBytes, + }, + }, + AgentClusterInstall: &hiveext.AgentClusterInstall{ + Spec: hiveext.AgentClusterInstallSpec{ + APIVIP: "192.168.111.5", + ProvisionRequirements: hiveext.ProvisionRequirements{ + ControlPlaneAgents: 3, + WorkerAgents: 5, + }, + }, + }, + NMStateConfigs: []*aiv1beta1.NMStateConfig{ + { + Spec: aiv1beta1.NMStateConfigSpec{ + Interfaces: []*aiv1beta1.Interface{ + { + Name: "eth0", + MacAddress: "00:01:02:03:04:05", + }, + }, + }, + }, + }, + }, + &agentconfig.AgentConfig{ + Config: &agent.Config{ + RendezvousIP: "192.168.111.80", + }, + File: &asset.File{ + Filename: "/cluster-manifests/agent-config.yaml", + }, + }, + &manifests.ExtraManifests{}, + &mirror.RegistriesConf{}, + &mirror.CaBundle{}, + &tls.KubeAPIServerLBSignerCertKey{}, + &tls.KubeAPIServerLocalhostSignerCertKey{}, + &tls.KubeAPIServerServiceNetworkSignerCertKey{}, + &tls.AdminKubeConfigSignerCertKey{}, + &tls.AdminKubeConfigClientCertKey{}, + } +} + +func TestIgnition_getMirrorFromRelease(t *testing.T) { + + cases := []struct { + name string + release string + registriesConf mirror.RegistriesConf + expectedMirror string + }{ + { + name: "no-mirror", + release: "registry.ci.openshift.org/ocp/release:latest", + registriesConf: mirror.RegistriesConf{}, + expectedMirror: "", + }, + { + name: "mirror-no-match", + release: "registry.ci.openshift.org/ocp/release:4.11.0-0.nightly-foo", + registriesConf: mirror.RegistriesConf{ + File: &asset.File{ + Filename: "registries.conf", + Data: []byte(""), + }, + MirrorConfig: []mirror.RegistriesConfig{ + { + Location: "some.registry.org/release", + Mirror: "some.mirror.org", + }, + }, + }, + expectedMirror: "", + }, + { + name: "mirror-match", + release: "registry.ci.openshift.org/ocp/release:4.11.0-0.nightly-foo", + registriesConf: mirror.RegistriesConf{ + File: &asset.File{ + Filename: "registries.conf", + Data: []byte(""), + }, + MirrorConfig: []mirror.RegistriesConfig{ + { + Location: "registry.ci.openshift.org/ocp/release", + Mirror: "virthost.ostest.test.metalkube.org:5000/localimages/local-release-image", + }, + }, + }, + expectedMirror: "virthost.ostest.test.metalkube.org:5000/localimages/local-release-image:4.11.0-0.nightly-foo", + }, + { + name: "mirror-match-with-checksum", + release: "quay.io/openshift-release-dev/ocp-release@sha256:300bce8246cf880e792e106607925de0a404484637627edf5f517375517d54a4", + registriesConf: mirror.RegistriesConf{ + File: &asset.File{ + Filename: "registries.conf", + Data: []byte(""), + }, + MirrorConfig: []mirror.RegistriesConfig{ + { + Location: "quay.io/openshift-release-dev/ocp-v4.0-art-dev", + Mirror: "localhost:5000/openshift4/openshift/release", + }, + { + Location: "quay.io/openshift-release-dev/ocp-release", + Mirror: "localhost:5000/openshift-release-dev/ocp-release", + }, + }, + }, + expectedMirror: "localhost:5000/openshift-release-dev/ocp-release@sha256:300bce8246cf880e792e106607925de0a404484637627edf5f517375517d54a4", + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + + mirror := getMirrorFromRelease(tc.release, &tc.registriesConf) + + assert.Equal(t, tc.expectedMirror, mirror) + + }) + } +} diff --git a/pkg/asset/agent/image/oc.go b/pkg/asset/agent/image/oc.go new file mode 100644 index 0000000000..d09e2419eb --- /dev/null +++ b/pkg/asset/agent/image/oc.go @@ -0,0 +1,234 @@ +package image + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "os" + "os/exec" + "path" + "path/filepath" + "strings" + "time" + + operatorv1alpha1 "github.com/openshift/api/operator/v1alpha1" + "github.com/openshift/assisted-service/pkg/executer" + "github.com/openshift/installer/pkg/asset/agent/mirror" + "github.com/sirupsen/logrus" + "github.com/thedevsaddam/retry" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/yaml" +) + +const ( + machineOsImageName = "machine-os-images" + coreOsFileName = "/coreos/coreos-%s.iso" + //OcDefaultTries is the number of times to execute the oc command on failues + OcDefaultTries = 5 + // OcDefaultRetryDelay is the time between retries + OcDefaultRetryDelay = time.Second * 5 +) + +// Config is used to set up the retries for extracting the base ISO +type Config struct { + MaxTries uint + RetryDelay time.Duration +} + +// Release is the interface to use the oc command to the get image info +type Release interface { + GetBaseIso(log logrus.FieldLogger, releaseImage string, pullSecret string, mirrorConfig []mirror.RegistriesConfig, architecture string) (string, error) +} + +type release struct { + executer executer.Executer + config Config +} + +// NewRelease is used to set up the executor to run oc commands +func NewRelease(executer executer.Executer, config Config) Release { + return &release{executer: executer, config: config} +} + +const ( + templateGetImage = "oc adm release info --image-for=%s --insecure=%t %s" + templateImageExtract = "oc image extract --path %s:%s --confirm %s" + templateImageExtractWithIcsp = "oc image extract --path %s:%s --confirm --icsp-file=%s %s" +) + +// Get the CoreOS ISO from the releaseImage +func (r *release) GetBaseIso(log logrus.FieldLogger, releaseImage string, pullSecret string, mirrorConfig []mirror.RegistriesConfig, architecture string) (string, error) { + + // Get the machine-os-images pullspec from the release and use that to get the CoreOS ISO + image, err := r.getImageFromRelease(log, machineOsImageName, releaseImage, pullSecret, len(mirrorConfig) > 0) + if err != nil { + return "", err + } + + cacheDir, err := GetCacheDir(imageDataType) + if err != nil { + return "", err + } + + filename := fmt.Sprintf(coreOsFileName, architecture) + // Check if file is already cached + filePath, err := GetFileFromCache(path.Base(filename), cacheDir) + if err != nil { + return "", err + } + if filePath != "" { + // Found cached file + return filePath, nil + } + + path, err := r.extractFileFromImage(log, image, filename, cacheDir, pullSecret, mirrorConfig) + if err != nil { + return "", err + } + return path, err +} + +func (r *release) getImageFromRelease(log logrus.FieldLogger, imageName, releaseImage, pullSecret string, haveMirror bool) (string, error) { + // This requires the 'oc' command so make sure its available + _, err := exec.LookPath("oc") + if err != nil { + if haveMirror { + log.Warning("Unable to validate mirror config because \"oc\" command is not available") + } else { + log.Debug("Skipping ISO extraction; \"oc\" command is not available") + } + return "", err + } + + cmd := fmt.Sprintf(templateGetImage, imageName, true, releaseImage) + + log.Debugf("Fetching image from OCP release (%s)", cmd) + image, err := execute(log, r.executer, pullSecret, cmd) + if err != nil { + return "", err + } + + return image, nil +} + +func (r *release) extractFileFromImage(log logrus.FieldLogger, image, file, cacheDir, pullSecret string, mirrorConfig []mirror.RegistriesConfig) (string, error) { + + var cmd string + if len(mirrorConfig) > 0 { + log.Debugf("Using mirror configuration") + icspFile, err := getIcspFileFromRegistriesConfig(log, mirrorConfig) + if err != nil { + return "", err + } + defer removeIcspFile(icspFile) + cmd = fmt.Sprintf(templateImageExtractWithIcsp, file, cacheDir, icspFile, image) + } else { + cmd = fmt.Sprintf(templateImageExtract, file, cacheDir, image) + } + + log.Debugf("extracting %s to %s, %s", file, cacheDir, cmd) + _, err := retry.Do(r.config.MaxTries, r.config.RetryDelay, execute, log, r.executer, pullSecret, cmd) + if err != nil { + return "", err + } + // set path + path := filepath.Join(cacheDir, path.Base(file)) + log.Info("Successfully extracted base ISO from the release") + log.Debugf("Base ISO %s cached at %s", file, path) + return path, nil +} + +func execute(log logrus.FieldLogger, executer executer.Executer, pullSecret string, command string) (string, error) { + + ps, err := executer.TempFile("", "registry-config") + if err != nil { + return "", err + } + defer func() { + ps.Close() + os.Remove(ps.Name()) + }() + _, err = ps.Write([]byte(pullSecret)) + if err != nil { + return "", err + } + // flush the buffer to ensure the file can be read + ps.Close() + executeCommand := command[:] + " --registry-config=" + ps.Name() + args := strings.Split(executeCommand, " ") + + stdout, stderr, exitCode := executer.Execute(args[0], args[1:]...) + + if exitCode == 0 { + return strings.TrimSpace(stdout), nil + } + + err = fmt.Errorf("command '%s' exited with non-zero exit code %d: %s\n%s", executeCommand, exitCode, stdout, stderr) + log.Error(err) + return "", err +} + +// Create a temporary file containing the ImageContentPolicySources +func getIcspFileFromRegistriesConfig(log logrus.FieldLogger, mirrorConfig []mirror.RegistriesConfig) (string, error) { + + contents, err := getIcspContents(mirrorConfig) + if err != nil { + return "", err + } + if contents == nil { + log.Debugf("No registry entries to build ICSP file") + return "", nil + } + + icspFile, err := ioutil.TempFile("", "icsp-file") + if err != nil { + return "", err + } + log.Debugf("Building ICSP file from registries.conf with contents %s", contents) + if _, err := icspFile.Write(contents); err != nil { + icspFile.Close() + os.Remove(icspFile.Name()) + return "", err + } + icspFile.Close() + + return icspFile.Name(), nil +} + +// Convert the data in registries.conf into ICSP format +func getIcspContents(mirrorConfig []mirror.RegistriesConfig) ([]byte, error) { + + icsp := operatorv1alpha1.ImageContentSourcePolicy{ + TypeMeta: metav1.TypeMeta{ + APIVersion: operatorv1alpha1.SchemeGroupVersion.String(), + Kind: "ImageContentSourcePolicy", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "image-policy", + // not namespaced + }, + } + + icsp.Spec.RepositoryDigestMirrors = make([]operatorv1alpha1.RepositoryDigestMirrors, len(mirrorConfig)) + for i, mirrorRegistries := range mirrorConfig { + icsp.Spec.RepositoryDigestMirrors[i] = operatorv1alpha1.RepositoryDigestMirrors{Source: mirrorRegistries.Location, Mirrors: []string{mirrorRegistries.Mirror}} + } + + // Convert to json first so json tags are handled + jsonData, err := json.Marshal(&icsp) + if err != nil { + return nil, err + } + contents, err := yaml.JSONToYAML(jsonData) + if err != nil { + return nil, err + } + + return contents, nil +} + +func removeIcspFile(filename string) { + if filename != "" { + os.Remove(filename) + } +} diff --git a/pkg/asset/agent/image/oc_test.go b/pkg/asset/agent/image/oc_test.go new file mode 100644 index 0000000000..0294934a9f --- /dev/null +++ b/pkg/asset/agent/image/oc_test.go @@ -0,0 +1,52 @@ +package image + +import ( + "testing" + + "github.com/openshift/installer/pkg/asset/agent/mirror" + "github.com/stretchr/testify/assert" +) + +func TestGetIcspContents(t *testing.T) { + + cases := []struct { + name string + mirrorConfig []mirror.RegistriesConfig + expectedError string + expectedConfig string + }{ + { + name: "valid-config", + mirrorConfig: []mirror.RegistriesConfig{ + { + Location: "registry.ci.openshift.org/ocp/release", + Mirror: "virthost.ostest.test.metalkube.org:5000/localimages/local-release-image", + }, + { + Location: "quay.io/openshift-release-dev/ocp-v4.0-art-dev", + Mirror: "virthost.ostest.test.metalkube.org:5000/localimages/local-release-image", + }, + }, + expectedConfig: "apiVersion: operator.openshift.io/v1alpha1\nkind: ImageContentSourcePolicy\nmetadata:\n creationTimestamp: null\n name: image-policy\nspec:\n repositoryDigestMirrors:\n - mirrors:\n - virthost.ostest.test.metalkube.org:5000/localimages/local-release-image\n source: registry.ci.openshift.org/ocp/release\n - mirrors:\n - virthost.ostest.test.metalkube.org:5000/localimages/local-release-image\n source: quay.io/openshift-release-dev/ocp-v4.0-art-dev\n", + expectedError: "", + }, + { + name: "empty-config", + mirrorConfig: []mirror.RegistriesConfig{}, + expectedConfig: "apiVersion: operator.openshift.io/v1alpha1\nkind: ImageContentSourcePolicy\nmetadata:\n creationTimestamp: null\n name: image-policy\nspec:\n repositoryDigestMirrors: []\n", + expectedError: "", + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + contents, err := getIcspContents(tc.mirrorConfig) + if tc.expectedError != "" { + assert.Equal(t, tc.expectedError, err.Error()) + } else { + assert.NoError(t, err) + } + + assert.Equal(t, tc.expectedConfig, string(contents)) + }) + } +} diff --git a/pkg/asset/agent/image/releaseimage.go b/pkg/asset/agent/image/releaseimage.go new file mode 100644 index 0000000000..b86a6bc33e --- /dev/null +++ b/pkg/asset/agent/image/releaseimage.go @@ -0,0 +1,72 @@ +package image + +import ( + "encoding/json" + "fmt" + "regexp" + "strings" + + "github.com/openshift/installer/pkg/version" +) + +type releaseImage struct { + ReleaseVersion string `json:"openshift_version"` + Arch string `json:"cpu_architecture"` + PullSpec string `json:"url"` + Tag string `json:"version"` +} + +func isDigest(pullspec string) bool { + return regexp.MustCompile(`.*sha256:[a-fA-F0-9]{64}$`).MatchString(pullspec) +} + +func releaseImageFromPullSpec(pullSpec, arch string) (releaseImage, error) { + + // When the pullspec it's a digest let's use the current version + // stored in the installer + if isDigest(pullSpec) { + versionString, err := version.Version() + if err != nil { + return releaseImage{}, err + } + + return releaseImage{ + ReleaseVersion: versionString, + Arch: arch, + PullSpec: pullSpec, + Tag: versionString, + }, nil + } + + components := strings.Split(pullSpec, ":") + if len(components) < 2 { + return releaseImage{}, fmt.Errorf("invalid release image \"%s\"", pullSpec) + } + lastIndex := len(components) - 1 + tag := strings.TrimSuffix(components[lastIndex], fmt.Sprintf("-%s", arch)) + + versionComponents := strings.Split(tag, ".") + if len(versionComponents) < 2 { + return releaseImage{}, fmt.Errorf("invalid release image version \"%s\"", tag) + } + relVersion := strings.Join(versionComponents[:2], ".") + + return releaseImage{ + ReleaseVersion: relVersion, + Arch: arch, + PullSpec: pullSpec, + Tag: tag, + }, nil +} + +func releaseImageList(pullSpec, arch string) (string, error) { + + relImage, err := releaseImageFromPullSpec(pullSpec, arch) + if err != nil { + return "", err + } + + imageList := []interface{}{relImage} + text, err := json.Marshal(imageList) + return string(text), err +} diff --git a/pkg/asset/agent/image/releaseimage_test.go b/pkg/asset/agent/image/releaseimage_test.go new file mode 100644 index 0000000000..8d9639dec4 --- /dev/null +++ b/pkg/asset/agent/image/releaseimage_test.go @@ -0,0 +1,72 @@ +package image + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestReleaseImageList(t *testing.T) { + cases := []struct { + name string + pullSpec string + arch string + result string + }{ + { + name: "4.10rc", + pullSpec: "quay.io/openshift-release-dev/ocp-release:4.10.0-rc.1-x86_64", + arch: "x86_64", + result: "[{\"openshift_version\":\"4.10\",\"cpu_architecture\":\"x86_64\",\"url\":\"quay.io/openshift-release-dev/ocp-release:4.10.0-rc.1-x86_64\",\"version\":\"4.10.0-rc.1\"}]", + }, + { + name: "pull-spec-includes-port-number", + pullSpec: "quay.io:433/openshift-release-dev/ocp-release:4.10.0-rc.1-x86_64", + arch: "x86_64", + result: "[{\"openshift_version\":\"4.10\",\"cpu_architecture\":\"x86_64\",\"url\":\"quay.io:433/openshift-release-dev/ocp-release:4.10.0-rc.1-x86_64\",\"version\":\"4.10.0-rc.1\"}]", + }, + { + name: "arm", + pullSpec: "quay.io/openshift-release-dev/ocp-release:4.10.0-rc.1-aarch64", + arch: "aarch64", + result: "[{\"openshift_version\":\"4.10\",\"cpu_architecture\":\"aarch64\",\"url\":\"quay.io/openshift-release-dev/ocp-release:4.10.0-rc.1-aarch64\",\"version\":\"4.10.0-rc.1\"}]", + }, + { + name: "4.11ci", + pullSpec: "registry.ci.openshift.org/ocp/release:4.11.0-0.ci-2022-05-16-202609", + arch: "x86_64", + result: "[{\"openshift_version\":\"4.11\",\"cpu_architecture\":\"x86_64\",\"url\":\"registry.ci.openshift.org/ocp/release:4.11.0-0.ci-2022-05-16-202609\",\"version\":\"4.11.0-0.ci-2022-05-16-202609\"}]", + }, + { + name: "CI-ephemeral", + pullSpec: "registry.build04.ci.openshift.org/ci-op-m7rfgytz/release@sha256:ebb203f24ee060d61bdb466696a9c20b3841f9929badf9b81fc99cbedc2a679e", + arch: "x86_64", + result: "[{\"openshift_version\":\"was not built correctly\",\"cpu_architecture\":\"x86_64\",\"url\":\"registry.build04.ci.openshift.org/ci-op-m7rfgytz/release@sha256:ebb203f24ee060d61bdb466696a9c20b3841f9929badf9b81fc99cbedc2a679e\",\"version\":\"was not built correctly\"}]", + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + output, err := releaseImageList(tc.pullSpec, tc.arch) + assert.NoError(t, err) + if err == nil { + assert.Equal(t, tc.result, output) + } + }) + } +} + +func TestReleaseImageListErrors(t *testing.T) { + cases := []string{ + "", + "quay.io/openshift-release-dev/ocp-release-4.10", + "quay.io/openshift-release-dev/ocp-release:4", + } + + for _, tc := range cases { + t.Run(tc, func(t *testing.T) { + _, err := releaseImageList(tc, "x86_64") + assert.Error(t, err) + }) + } +} diff --git a/pkg/asset/agent/installconfig.go b/pkg/asset/agent/installconfig.go new file mode 100644 index 0000000000..18a607143f --- /dev/null +++ b/pkg/asset/agent/installconfig.go @@ -0,0 +1,186 @@ +package agent + +import ( + "fmt" + "os" + "strings" + + "github.com/openshift/installer/pkg/asset" + "github.com/openshift/installer/pkg/asset/installconfig" + "github.com/openshift/installer/pkg/types" + "github.com/openshift/installer/pkg/types/baremetal" + "github.com/openshift/installer/pkg/types/none" + "github.com/openshift/installer/pkg/types/vsphere" + "github.com/pkg/errors" + "k8s.io/apimachinery/pkg/util/validation/field" + "sigs.k8s.io/yaml" +) + +const ( + installConfigFilename = "install-config.yaml" +) + +// supportedPlatforms lists the supported platforms for agent installer +var supportedPlatforms = []string{baremetal.Name, vsphere.Name, none.Name} + +// OptionalInstallConfig is an InstallConfig where the default is empty, rather +// than generated from running the survey. +type OptionalInstallConfig struct { + installconfig.InstallConfig + Supplied bool +} + +// Dependencies returns all of the dependencies directly needed by an +// InstallConfig asset. +func (a *OptionalInstallConfig) Dependencies() []asset.Asset { + // Return no dependencies for the Agent install config, because it is + // optional. We don't need to run the survey if it doesn't exist, since the + // user may have supplied cluster-manifests that fully define the cluster. + return []asset.Asset{} +} + +// Generate generates the install-config.yaml file. +func (a *OptionalInstallConfig) Generate(parents asset.Parents) error { + // Just generate an empty install config, since we have no dependencies. + return nil +} + +// Load returns the installconfig from disk. +func (a *OptionalInstallConfig) Load(f asset.FileFetcher) (bool, error) { + + var found bool + + // First load the provided install config to early validate + // as per agent installer specific requirements + // Detailed generic validations of install config are + // done by pkg/asset/installconfig/installconfig.go + installConfig, err := a.loadEarly(f) + if err != nil { + return found, err + } + + if err := a.validateInstallConfig(installConfig).ToAggregate(); err != nil { + return found, errors.Wrapf(err, "invalid install-config configuration") + } + + found, err = a.InstallConfig.Load(f) + if found && err == nil { + a.Supplied = true + } + return found, err +} + +// loadEarly loads the install config from the disk +// to be able to validate early for agent installer +func (a *OptionalInstallConfig) loadEarly(f asset.FileFetcher) (*types.InstallConfig, error) { + + file, err := f.FetchByName(installConfigFilename) + config := &types.InstallConfig{} + if err != nil { + if os.IsNotExist(err) { + return config, nil + } + return config, errors.Wrap(err, asset.InstallConfigError) + } + + if err := yaml.UnmarshalStrict(file.Data, config, yaml.DisallowUnknownFields); err != nil { + if strings.Contains(err.Error(), "unknown field") { + err = errors.Wrapf(err, "failed to parse first occurence of unknown field") + } + err = errors.Wrapf(err, "failed to unmarshal %s", installConfigFilename) + return config, errors.Wrap(err, asset.InstallConfigError) + } + return config, nil +} + +func (a *OptionalInstallConfig) validateInstallConfig(installConfig *types.InstallConfig) field.ErrorList { + var allErrs field.ErrorList + + if err := a.validateSupportedPlatforms(installConfig); err != nil { + allErrs = append(allErrs, err...) + } + + if err := a.validateVIPsAreSet(installConfig); err != nil { + allErrs = append(allErrs, err...) + } + + if err := a.validateSNOConfiguration(installConfig); err != nil { + allErrs = append(allErrs, err...) + } + + return allErrs +} + +func (a *OptionalInstallConfig) validateSupportedPlatforms(installConfig *types.InstallConfig) field.ErrorList { + var allErrs field.ErrorList + + fieldPath := field.NewPath("Platform") + + if installConfig.Platform.Name() != "" && !a.contains(installConfig.Platform.Name(), supportedPlatforms) { + allErrs = append(allErrs, field.NotSupported(fieldPath, installConfig.Platform.Name(), supportedPlatforms)) + } + return allErrs +} + +func (a *OptionalInstallConfig) validateVIPsAreSet(installConfig *types.InstallConfig) field.ErrorList { + var allErrs field.ErrorList + var fieldPath *field.Path + + if installConfig.Platform.Name() == baremetal.Name { + if installConfig.Platform.BareMetal.APIVIP == "" { + fieldPath = field.NewPath("Platform", "Baremetal", "ApiVip") + allErrs = append(allErrs, field.Required(fieldPath, fmt.Sprintf("apiVip must be set for %s platform", baremetal.Name))) + } + if installConfig.Platform.BareMetal.IngressVIP == "" { + fieldPath = field.NewPath("Platform", "Baremetal", "IngressVip") + allErrs = append(allErrs, field.Required(fieldPath, fmt.Sprintf("ingressVip must be set for %s platform", baremetal.Name))) + } + } + + if installConfig.Platform.Name() == vsphere.Name { + if installConfig.Platform.VSphere.APIVIP == "" { + fieldPath = field.NewPath("Platform", "VSphere", "ApiVip") + allErrs = append(allErrs, field.Required(fieldPath, fmt.Sprintf("apiVip must be set for %s platform", vsphere.Name))) + } + if installConfig.Platform.VSphere.IngressVIP == "" { + fieldPath = field.NewPath("Platform", "VSphere", "IngressVip") + allErrs = append(allErrs, field.Required(fieldPath, fmt.Sprintf("ingressVip must be set for %s platform", vsphere.Name))) + } + } + return allErrs +} + +func (a *OptionalInstallConfig) validateSNOConfiguration(installConfig *types.InstallConfig) field.ErrorList { + var allErrs field.ErrorList + var fieldPath *field.Path + + // platform None always imply SNO cluster + if installConfig.Platform.Name() == none.Name { + if *installConfig.ControlPlane.Replicas != 1 { + fieldPath = field.NewPath("ControlPlane", "Replicas") + allErrs = append(allErrs, field.Required(fieldPath, fmt.Sprintf("control plane replicas must be 1 for %s platform. Found %v", none.Name, *installConfig.ControlPlane.Replicas))) + } else if len(installConfig.Compute) == 0 { + fieldPath = field.NewPath("Compute", "Replicas") + allErrs = append(allErrs, field.Required(fieldPath, "Installing a Single Node Openshift requires explicitly setting compute replicas to zero")) + } + + var workers int + for _, worker := range installConfig.Compute { + workers = workers + int(*worker.Replicas) + } + if workers != 0 { + fieldPath = field.NewPath("Compute", "Replicas") + allErrs = append(allErrs, field.Required(fieldPath, fmt.Sprintf("total number of worker replicas must be 0 for %s platform. Found %v", none.Name, workers))) + } + } + return allErrs +} + +func (a *OptionalInstallConfig) contains(platform string, supportedPlatforms []string) bool { + for _, p := range supportedPlatforms { + if p == platform { + return true + } + } + return false +} diff --git a/pkg/asset/agent/installconfig_test.go b/pkg/asset/agent/installconfig_test.go new file mode 100644 index 0000000000..fb0eef2a13 --- /dev/null +++ b/pkg/asset/agent/installconfig_test.go @@ -0,0 +1,213 @@ +package agent + +import ( + "testing" + + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/pointer" + + "github.com/openshift/installer/pkg/asset" + "github.com/openshift/installer/pkg/asset/mock" + "github.com/openshift/installer/pkg/ipnet" + "github.com/openshift/installer/pkg/types" + "github.com/openshift/installer/pkg/types/none" +) + +func TestInstallConfigLoad(t *testing.T) { + cases := []struct { + name string + data string + fetchError error + expectedFound bool + expectedError string + expectedConfig *types.InstallConfig + }{ + { + name: "unsupported platform", + data: ` +apiVersion: v1 +metadata: + name: test-cluster +baseDomain: test-domain +platform: + aws: + region: us-east-1 +pullSecret: "{\"auths\":{\"example.com\":{\"auth\":\"authorization value\"}}}" +`, + expectedFound: false, + expectedError: `invalid install-config configuration: Platform: Unsupported value: "aws": supported values: "baremetal", "vsphere", "none"`, + }, + { + name: "apiVip not set for baremetal platform", + data: ` +apiVersion: v1 +metadata: + name: test-cluster +baseDomain: test-domain +platform: + baremetal: + hosts: + - name: host1 + bootMACAddress: 52:54:01:xx:zz:z1 + ingressVip: 192.168.122.11 +pullSecret: "{\"auths\":{\"example.com\":{\"auth\":\"authorization value\"}}}" +`, + expectedFound: false, + expectedError: "invalid install-config configuration: Platform.Baremetal.ApiVip: Required value: apiVip must be set for baremetal platform", + }, + { + name: "ingressVip not set for vsphere platform", + data: ` +apiVersion: v1 +metadata: + name: test-cluster +baseDomain: test-domain +platform: + vsphere: + apiVip: 192.168.122.10 +pullSecret: "{\"auths\":{\"example.com\":{\"auth\":\"authorization value\"}}}" +`, + expectedFound: false, + expectedError: "invalid install-config configuration: Platform.VSphere.IngressVip: Required value: ingressVip must be set for vsphere platform", + }, + { + name: "invalid configuration for none platform for sno", + data: ` +apiVersion: v1 +metadata: + name: test-cluster +baseDomain: test-domain +compute: + - architecture: amd64 + hyperthreading: Enabled + name: worker + platform: {} + replicas: 2 +controlPlane: + architecture: amd64 + hyperthreading: Enabled + name: master + platform: {} + replicas: 3 +platform: + none : {} +pullSecret: "{\"auths\":{\"example.com\":{\"auth\":\"authorization value\"}}}" +`, + expectedFound: false, + expectedError: "invalid install-config configuration: [ControlPlane.Replicas: Required value: control plane replicas must be 1 for none platform. Found 3, Compute.Replicas: Required value: total number of worker replicas must be 0 for none platform. Found 2]", + }, + { + name: "no compute.replicas set for SNO", + data: ` +apiVersion: v1 +metadata: + name: test-cluster +baseDomain: test-domain +controlPlane: + architecture: amd64 + hyperthreading: Enabled + name: master + platform: {} + replicas: 1 +platform: + none : {} +pullSecret: "{\"auths\":{\"example.com\":{\"auth\":\"authorization value\"}}}" +`, + expectedFound: false, + expectedError: "invalid install-config configuration: Compute.Replicas: Required value: Installing a Single Node Openshift requires explicitly setting compute replicas to zero", + }, + { + name: "valid configuration for none platform for sno", + data: ` +apiVersion: v1 +metadata: + name: test-cluster +baseDomain: test-domain +compute: + - architecture: amd64 + hyperthreading: Enabled + name: worker + platform: {} + replicas: 0 +controlPlane: + architecture: amd64 + hyperthreading: Enabled + name: master + platform: {} + replicas: 1 +platform: + none : {} +pullSecret: "{\"auths\":{\"example.com\":{\"auth\":\"authorization value\"}}}" +`, + expectedFound: true, + expectedConfig: &types.InstallConfig{ + TypeMeta: metav1.TypeMeta{ + APIVersion: types.InstallConfigVersion, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test-cluster", + }, + BaseDomain: "test-domain", + Networking: &types.Networking{ + MachineNetwork: []types.MachineNetworkEntry{ + {CIDR: *ipnet.MustParseCIDR("10.0.0.0/16")}, + }, + NetworkType: "OpenShiftSDN", + ServiceNetwork: []ipnet.IPNet{*ipnet.MustParseCIDR("172.30.0.0/16")}, + ClusterNetwork: []types.ClusterNetworkEntry{ + { + CIDR: *ipnet.MustParseCIDR("10.128.0.0/14"), + HostPrefix: 23, + }, + }, + }, + ControlPlane: &types.MachinePool{ + Name: "master", + Replicas: pointer.Int64Ptr(1), + Hyperthreading: types.HyperthreadingEnabled, + Architecture: types.ArchitectureAMD64, + }, + Compute: []types.MachinePool{ + { + Name: "worker", + Replicas: pointer.Int64Ptr(0), + Hyperthreading: types.HyperthreadingEnabled, + Architecture: types.ArchitectureAMD64, + }, + }, + Platform: types.Platform{None: &none.Platform{}}, + PullSecret: `{"auths":{"example.com":{"auth":"authorization value"}}}`, + Publish: types.ExternalPublishingStrategy, + }, + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + fileFetcher := mock.NewMockFileFetcher(mockCtrl) + fileFetcher.EXPECT().FetchByName(installConfigFilename). + Return( + &asset.File{ + Filename: installConfigFilename, + Data: []byte(tc.data)}, + tc.fetchError, + ).MaxTimes(2) + + asset := &OptionalInstallConfig{} + found, err := asset.Load(fileFetcher) + assert.Equal(t, tc.expectedFound, found, "unexpected found value returned from Load") + if tc.expectedError != "" { + assert.Equal(t, tc.expectedError, err.Error()) + } else { + assert.NoError(t, err) + } + if tc.expectedFound { + assert.Equal(t, tc.expectedConfig, asset.Config, "unexpected Config in InstallConfig") + } + }) + } +} diff --git a/pkg/asset/agent/manifests/agent.go b/pkg/asset/agent/manifests/agent.go new file mode 100644 index 0000000000..e9034df945 --- /dev/null +++ b/pkg/asset/agent/manifests/agent.go @@ -0,0 +1,142 @@ +package manifests + +import ( + "fmt" + "reflect" + + hiveext "github.com/openshift/assisted-service/api/hiveextension/v1beta1" + aiv1beta1 "github.com/openshift/assisted-service/api/v1beta1" + "github.com/openshift/assisted-service/models" + hivev1 "github.com/openshift/hive/apis/hive/v1" + "github.com/openshift/installer/pkg/asset" + "github.com/pkg/errors" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/util/validation/field" +) + +const ( + // This could be change to "cluster-manifests" once all the agent code will be migrated to using + // assets (and will stop reading from the hard-code "manifests" relative path) + clusterManifestDir = "cluster-manifests" +) + +var ( + _ asset.WritableAsset = (*AgentManifests)(nil) +) + +// AgentManifests generates all the required manifests by the agent installer. +type AgentManifests struct { + FileList []*asset.File + + PullSecret *corev1.Secret + InfraEnv *aiv1beta1.InfraEnv + StaticNetworkConfigs []*models.HostStaticNetworkConfig + NMStateConfigs []*aiv1beta1.NMStateConfig + AgentClusterInstall *hiveext.AgentClusterInstall + ClusterDeployment *hivev1.ClusterDeployment + ClusterImageSet *hivev1.ClusterImageSet +} + +// Name returns a human friendly name. +func (m *AgentManifests) Name() string { + return "Agent Manifests" +} + +// Dependencies returns all of the dependencies directly needed the asset. +func (m *AgentManifests) Dependencies() []asset.Asset { + return []asset.Asset{ + &AgentPullSecret{}, + &InfraEnv{}, + &NMStateConfig{}, + &AgentClusterInstall{}, + &ClusterDeployment{}, + &ClusterImageSet{}, + } +} + +// Generate generates the respective manifest files. +func (m *AgentManifests) Generate(dependencies asset.Parents) error { + for _, a := range []asset.WritableAsset{ + &AgentPullSecret{}, + &InfraEnv{}, + &NMStateConfig{}, + &AgentClusterInstall{}, + &ClusterDeployment{}, + &ClusterImageSet{}, + } { + dependencies.Get(a) + m.FileList = append(m.FileList, a.Files()...) + + switch v := a.(type) { + case *AgentPullSecret: + m.PullSecret = v.Config + case *InfraEnv: + m.InfraEnv = v.Config + case *NMStateConfig: + m.StaticNetworkConfigs = append(m.StaticNetworkConfigs, v.StaticNetworkConfig...) + m.NMStateConfigs = append(m.NMStateConfigs, v.Config...) + case *AgentClusterInstall: + m.AgentClusterInstall = v.Config + case *ClusterDeployment: + m.ClusterDeployment = v.Config + case *ClusterImageSet: + m.ClusterImageSet = v.Config + } + } + + asset.SortFiles(m.FileList) + + return m.finish() +} + +// Files returns the files generated by the asset. +func (m *AgentManifests) Files() []*asset.File { + return m.FileList +} + +// Load currently does nothing +func (m *AgentManifests) Load(f asset.FileFetcher) (bool, error) { + return false, nil +} + +// GetPullSecretData returns the content of the pull secret +func (m *AgentManifests) GetPullSecretData() string { + return m.PullSecret.StringData[".dockerconfigjson"] +} + +func (m *AgentManifests) finish() error { + if err := m.validateAgentManifests().ToAggregate(); err != nil { + return errors.Wrapf(err, "invalid agent configuration") + } + + return nil +} + +func (m *AgentManifests) validateAgentManifests() field.ErrorList { + allErrs := field.ErrorList{} + + if err := m.validateNMStateLabelSelector(); err != nil { + allErrs = append(allErrs, err...) + } + + return allErrs +} + +func (m *AgentManifests) validateNMStateLabelSelector() field.ErrorList { + + var allErrs field.ErrorList + + fieldPath := field.NewPath("Spec", "NMStateConfigLabelSelector", "MatchLabels") + + for _, networkConfig := range m.NMStateConfigs { + if !reflect.DeepEqual(m.InfraEnv.Spec.NMStateConfigLabelSelector.MatchLabels, networkConfig.ObjectMeta.Labels) { + allErrs = append(allErrs, field.Required(fieldPath, fmt.Sprintf("infraEnv and %s.NMStateConfig labels do not match. Expected: %s Found: %s", + networkConfig.Name, + m.InfraEnv.Spec.NMStateConfigLabelSelector.MatchLabels, + networkConfig.ObjectMeta.Labels))) + } + + } + + return allErrs +} diff --git a/pkg/asset/agent/manifests/agent_test.go b/pkg/asset/agent/manifests/agent_test.go new file mode 100644 index 0000000000..18d9c962a8 --- /dev/null +++ b/pkg/asset/agent/manifests/agent_test.go @@ -0,0 +1,123 @@ +package manifests + +import ( + "testing" + + hiveext "github.com/openshift/assisted-service/api/hiveextension/v1beta1" + aiv1beta1 "github.com/openshift/assisted-service/api/v1beta1" + "github.com/openshift/assisted-service/models" + hivev1 "github.com/openshift/hive/apis/hive/v1" + "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/openshift/installer/pkg/asset" +) + +func TestAgentManifests_Generate(t *testing.T) { + + fakeSecret := &corev1.Secret{ + ObjectMeta: v1.ObjectMeta{Name: "fake-secret"}, + } + fakeInfraEnv := &aiv1beta1.InfraEnv{ + ObjectMeta: v1.ObjectMeta{Name: "fake-infraEnv"}, + } + fakeStaticNetworkConfig := []*models.HostStaticNetworkConfig{{NetworkYaml: "some-yaml"}} + fakeNMStatConfig := []*aiv1beta1.NMStateConfig{{ObjectMeta: v1.ObjectMeta{Name: "fake-nmState"}}} + fakeAgentClusterInstall := &hiveext.AgentClusterInstall{ObjectMeta: v1.ObjectMeta{Name: "fake-agentClusterInstall"}} + fakeClusterDeployment := &hivev1.ClusterDeployment{ObjectMeta: v1.ObjectMeta{Name: "fake-clusterDeployment"}} + fakeClusterImageSet := &hivev1.ClusterImageSet{ObjectMeta: v1.ObjectMeta{Name: "fake-clusterImageSet"}} + + tests := []struct { + Name string + Assets []asset.WritableAsset + ExpectedPullSecret *corev1.Secret + ExpectedInfraEnv *aiv1beta1.InfraEnv + ExpectedStaticNetworkConfig []*models.HostStaticNetworkConfig + ExpectedNMStateConfig []*aiv1beta1.NMStateConfig + ExpectedAgentClusterInstall *hiveext.AgentClusterInstall + ExpectedClusterDeployment *hivev1.ClusterDeployment + ExpectedClusterImageSet *hivev1.ClusterImageSet + ExpectedError string + }{ + { + Name: "default", + Assets: []asset.WritableAsset{ + &AgentPullSecret{Config: fakeSecret}, + &InfraEnv{Config: fakeInfraEnv}, + &NMStateConfig{ + StaticNetworkConfig: fakeStaticNetworkConfig, + Config: fakeNMStatConfig, + }, + &AgentClusterInstall{Config: fakeAgentClusterInstall}, + &ClusterDeployment{Config: fakeClusterDeployment}, + &ClusterImageSet{Config: fakeClusterImageSet}, + }, + ExpectedPullSecret: fakeSecret, + ExpectedInfraEnv: fakeInfraEnv, + ExpectedStaticNetworkConfig: fakeStaticNetworkConfig, + ExpectedNMStateConfig: fakeNMStatConfig, + ExpectedAgentClusterInstall: fakeAgentClusterInstall, + ExpectedClusterDeployment: fakeClusterDeployment, + ExpectedClusterImageSet: fakeClusterImageSet, + }, + { + Name: "invalid-NMStateLabelSelector", + Assets: []asset.WritableAsset{ + &AgentPullSecret{}, + &InfraEnv{Config: &aiv1beta1.InfraEnv{ + Spec: aiv1beta1.InfraEnvSpec{ + NMStateConfigLabelSelector: v1.LabelSelector{ + MatchLabels: map[string]string{ + "missing-label": "missing-label", + }, + }, + }, + }}, + &NMStateConfig{ + StaticNetworkConfig: fakeStaticNetworkConfig, + Config: fakeNMStatConfig, + }, + &AgentClusterInstall{}, + &ClusterDeployment{}, + &ClusterImageSet{}, + }, + ExpectedError: "invalid agent configuration: Spec.NMStateConfigLabelSelector.MatchLabels: Required value: infraEnv and fake-nmState.NMStateConfig labels do not match. Expected: map[missing-label:missing-label] Found: map[]", + }, + } + for _, tt := range tests { + t.Run(tt.Name, func(t *testing.T) { + m := &AgentManifests{} + + fakeParent := asset.Parents{} + for _, a := range tt.Assets { + fakeParent.Add(a) + } + + err := m.Generate(fakeParent) + if tt.ExpectedError != "" { + assert.Equal(t, tt.ExpectedError, err.Error()) + } else { + assert.NoError(t, err) + } + if tt.ExpectedPullSecret != nil { + assert.Equal(t, tt.ExpectedPullSecret, m.PullSecret) + } + if tt.ExpectedInfraEnv != nil { + assert.Equal(t, tt.ExpectedInfraEnv, m.InfraEnv) + } + if tt.ExpectedStaticNetworkConfig != nil { + assert.Equal(t, tt.ExpectedStaticNetworkConfig, m.StaticNetworkConfigs) + } + if tt.ExpectedNMStateConfig != nil { + assert.Equal(t, tt.ExpectedNMStateConfig, m.NMStateConfigs) + } + if tt.ExpectedClusterDeployment != nil { + assert.Equal(t, tt.ExpectedClusterDeployment, m.ClusterDeployment) + } + if tt.ExpectedClusterImageSet != nil { + assert.Equal(t, tt.ExpectedClusterImageSet, m.ClusterImageSet) + } + }) + } +} diff --git a/pkg/asset/agent/manifests/agentclusterinstall.go b/pkg/asset/agent/manifests/agentclusterinstall.go new file mode 100644 index 0000000000..b7b22cb743 --- /dev/null +++ b/pkg/asset/agent/manifests/agentclusterinstall.go @@ -0,0 +1,174 @@ +package manifests + +import ( + "fmt" + "net" + "os" + "path/filepath" + "strings" + + hiveext "github.com/openshift/assisted-service/api/hiveextension/v1beta1" + hivev1 "github.com/openshift/hive/apis/hive/v1" + "github.com/openshift/installer/pkg/asset" + "github.com/openshift/installer/pkg/asset/agent" + "github.com/openshift/installer/pkg/ipnet" + "github.com/openshift/installer/pkg/validate" + "github.com/pkg/errors" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/yaml" +) + +var ( + agentClusterInstallFilename = filepath.Join(clusterManifestDir, "agent-cluster-install.yaml") +) + +// AgentClusterInstall generates the agent-cluster-install.yaml file. +type AgentClusterInstall struct { + File *asset.File + Config *hiveext.AgentClusterInstall +} + +var _ asset.WritableAsset = (*AgentClusterInstall)(nil) + +// Name returns a human friendly name for the asset. +func (*AgentClusterInstall) Name() string { + return "AgentClusterInstall Config" +} + +// Dependencies returns all of the dependencies directly needed to generate +// the asset. +func (*AgentClusterInstall) Dependencies() []asset.Asset { + return []asset.Asset{ + &agent.OptionalInstallConfig{}, + } +} + +// Generate generates the AgentClusterInstall manifest. +func (a *AgentClusterInstall) Generate(dependencies asset.Parents) error { + installConfig := &agent.OptionalInstallConfig{} + dependencies.Get(installConfig) + + if installConfig.Config != nil { + var numberOfWorkers int = 0 + for _, compute := range installConfig.Config.Compute { + numberOfWorkers = numberOfWorkers + int(*compute.Replicas) + } + + clusterNetwork := []hiveext.ClusterNetworkEntry{} + for _, cn := range installConfig.Config.Networking.ClusterNetwork { + _, cidr, err := net.ParseCIDR(cn.CIDR.String()) + if err != nil { + return errors.Wrap(err, "failed to parse ClusterNetwork CIDR") + } + err = validate.SubnetCIDR(cidr) + if err != nil { + return errors.Wrap(err, "failed to validate ClusterNetwork CIDR") + } + + entry := hiveext.ClusterNetworkEntry{ + CIDR: cidr.String(), + HostPrefix: cn.HostPrefix, + } + clusterNetwork = append(clusterNetwork, entry) + } + + serviceNetwork := []string{} + for _, sn := range installConfig.Config.Networking.ServiceNetwork { + cidr, err := ipnet.ParseCIDR(sn.String()) + if err != nil { + return errors.Wrap(err, "failed to parse ServiceNetwork CIDR") + } + serviceNetwork = append(serviceNetwork, cidr.String()) + } + + agentClusterInstall := &hiveext.AgentClusterInstall{ + ObjectMeta: metav1.ObjectMeta{ + Name: getAgentClusterInstallName(installConfig), + Namespace: getObjectMetaNamespace(installConfig), + }, + Spec: hiveext.AgentClusterInstallSpec{ + ImageSetRef: &hivev1.ClusterImageSetReference{ + Name: getClusterImageSetReferenceName(), + }, + ClusterDeploymentRef: corev1.LocalObjectReference{ + Name: getClusterDeploymentName(installConfig), + }, + Networking: hiveext.Networking{ + ClusterNetwork: clusterNetwork, + ServiceNetwork: serviceNetwork, + }, + SSHPublicKey: strings.Trim(installConfig.Config.SSHKey, "|\n\t"), + ProvisionRequirements: hiveext.ProvisionRequirements{ + ControlPlaneAgents: int(*installConfig.Config.ControlPlane.Replicas), + WorkerAgents: numberOfWorkers, + }, + }, + } + + apiVIP, ingressVIP := getVIPs(&installConfig.Config.Platform) + + // set APIVIP and IngressVIP only for non SNO cluster for Baremetal and Vsphere platforms + // SNO cluster is determined by number of ControlPlaneAgents which should be 1 + if int(*installConfig.Config.ControlPlane.Replicas) > 1 && apiVIP != "" && ingressVIP != "" { + agentClusterInstall.Spec.APIVIP = apiVIP + agentClusterInstall.Spec.IngressVIP = ingressVIP + } + + a.Config = agentClusterInstall + + agentClusterInstallData, err := yaml.Marshal(agentClusterInstall) + if err != nil { + return errors.Wrap(err, "failed to marshal agent installer AgentClusterInstall") + } + + a.File = &asset.File{ + Filename: agentClusterInstallFilename, + Data: agentClusterInstallData, + } + } + return a.finish() +} + +// Files returns the files generated by the asset. +func (a *AgentClusterInstall) Files() []*asset.File { + if a.File != nil { + return []*asset.File{a.File} + } + return []*asset.File{} +} + +// Load returns agentclusterinstall asset from the disk. +func (a *AgentClusterInstall) Load(f asset.FileFetcher) (bool, error) { + + agentClusterInstallFile, err := f.FetchByName(agentClusterInstallFilename) + if err != nil { + if os.IsNotExist(err) { + return false, nil + } + return false, errors.Wrap(err, fmt.Sprintf("failed to load %s file", agentClusterInstallFilename)) + } + + a.File = agentClusterInstallFile + + agentClusterInstall := &hiveext.AgentClusterInstall{} + if err := yaml.UnmarshalStrict(agentClusterInstallFile.Data, agentClusterInstall); err != nil { + err = errors.Wrapf(err, "failed to unmarshal %s", agentClusterInstallFilename) + return false, err + } + a.Config = agentClusterInstall + + if err = a.finish(); err != nil { + return false, err + } + return true, nil +} + +func (a *AgentClusterInstall) finish() error { + + if a.Config == nil { + return errors.New("missing configuration or manifest file") + } + + return nil +} diff --git a/pkg/asset/agent/manifests/agentclusterinstall_test.go b/pkg/asset/agent/manifests/agentclusterinstall_test.go new file mode 100644 index 0000000000..6be742dbc9 --- /dev/null +++ b/pkg/asset/agent/manifests/agentclusterinstall_test.go @@ -0,0 +1,282 @@ +package manifests + +import ( + "os" + "strings" + "testing" + + "github.com/golang/mock/gomock" + hiveext "github.com/openshift/assisted-service/api/hiveextension/v1beta1" + hivev1 "github.com/openshift/hive/apis/hive/v1" + "github.com/openshift/installer/pkg/asset" + + "github.com/openshift/installer/pkg/asset/agent" + "github.com/openshift/installer/pkg/asset/mock" + "github.com/pkg/errors" + "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/yaml" +) + +func TestAgentClusterInstall_Generate(t *testing.T) { + + cases := []struct { + name string + dependencies []asset.Asset + expectedError string + expectedConfig *hiveext.AgentClusterInstall + }{ + { + name: "missing install config", + dependencies: []asset.Asset{ + &agent.OptionalInstallConfig{}, + }, + expectedError: "missing configuration or manifest file", + }, + { + name: "valid configuration", + dependencies: []asset.Asset{ + getValidOptionalInstallConfig(), + }, + expectedConfig: &hiveext.AgentClusterInstall{ + ObjectMeta: metav1.ObjectMeta{ + Name: getAgentClusterInstallName(getValidOptionalInstallConfig()), + Namespace: getObjectMetaNamespace(getValidOptionalInstallConfig()), + }, + Spec: hiveext.AgentClusterInstallSpec{ + ImageSetRef: &hivev1.ClusterImageSetReference{ + Name: getClusterImageSetReferenceName(), + }, + ClusterDeploymentRef: corev1.LocalObjectReference{ + Name: getClusterDeploymentName(getValidOptionalInstallConfig()), + }, + Networking: hiveext.Networking{ + ClusterNetwork: []hiveext.ClusterNetworkEntry{ + { + CIDR: "192.168.111.0/24", + HostPrefix: 23, + }, + }, + ServiceNetwork: []string{"172.30.0.0/16"}, + }, + SSHPublicKey: strings.Trim(TestSSHKey, "|\n\t"), + ProvisionRequirements: hiveext.ProvisionRequirements{ + ControlPlaneAgents: 3, + WorkerAgents: 5, + }, + APIVIP: "192.168.122.10", + IngressVIP: "192.168.122.11", + }, + }, + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + + parents := asset.Parents{} + parents.Add(tc.dependencies...) + + asset := &AgentClusterInstall{} + err := asset.Generate(parents) + + if tc.expectedError != "" { + assert.Equal(t, tc.expectedError, err.Error()) + } else { + assert.NoError(t, err) + assert.Equal(t, tc.expectedConfig, asset.Config) + assert.NotEmpty(t, asset.Files()) + + configFile := asset.Files()[0] + assert.Equal(t, "cluster-manifests/agent-cluster-install.yaml", configFile.Filename) + + var actualConfig hiveext.AgentClusterInstall + err = yaml.Unmarshal(configFile.Data, &actualConfig) + assert.NoError(t, err) + assert.Equal(t, *tc.expectedConfig, actualConfig) + } + }) + } +} + +// func TestAgentClusterInstall_Generate(t *testing.T) { + +// installConfig := &agent.OptionalInstallConfig{ +// Config: &types.InstallConfig{ +// ObjectMeta: v1.ObjectMeta{ +// Name: "cluster0-name", +// Namespace: "cluster0-namespace", +// }, +// SSHKey: "ssh-key", +// ControlPlane: &types.MachinePool{ +// Name: "master", +// Replicas: pointer.Int64Ptr(3), +// Platform: types.MachinePoolPlatform{}, +// }, +// Compute: []types.MachinePool{ +// { +// Name: "worker-machine-pool-1", +// Replicas: pointer.Int64Ptr(2), +// }, +// { +// Name: "worker-machine-pool-2", +// Replicas: pointer.Int64Ptr(3), +// }, +// }, +// }, +// } + +// parents := asset.Parents{} +// parents.Add(installConfig) + +// asset := &AgentClusterInstall{} +// err := asset.Generate(parents) +// assert.NoError(t, err) + +// assert.NotEmpty(t, asset.Files()) +// aciFile := asset.Files()[0] +// assert.Equal(t, "cluster-manifests/agent-cluster-install.yaml", aciFile.Filename) + +// aci := &hiveext.AgentClusterInstall{} +// err = yaml.Unmarshal(aciFile.Data, &aci) +// assert.NoError(t, err) + +// assert.Equal(t, "agent-cluster-install", aci.Name) +// assert.Equal(t, "cluster0-namespace", aci.Namespace) +// assert.Equal(t, "cluster0-name", aci.Spec.ClusterDeploymentRef.Name) +// assert.Equal(t, 3, aci.Spec.ProvisionRequirements.ControlPlaneAgents) + +// assert.Equal(t, 5, aci.Spec.ProvisionRequirements.WorkerAgents) +// assert.Equal(t, "ssh-key", aci.Spec.SSHPublicKey) +// } + +func TestAgentClusterInstall_LoadedFromDisk(t *testing.T) { + + cases := []struct { + name string + data string + fetchError error + expectedFound bool + expectedError bool + expectedConfig *hiveext.AgentClusterInstall + }{ + { + name: "valid-config-file", + data: ` +metadata: + name: test-agent-cluster-install + namespace: cluster0 +spec: + apiVIP: 192.168.111.5 + ingressVIP: 192.168.111.4 + clusterDeploymentRef: + name: ostest + imageSetRef: + name: openshift-v4.10.0 + networking: + clusterNetwork: + - cidr: 10.128.0.0/14 + hostPrefix: 23 + serviceNetwork: + - 172.30.0.0/16 + provisionRequirements: + controlPlaneAgents: 3 + workerAgents: 2 + sshPublicKey: | + ssh-rsa AAAAmyKey`, + expectedFound: true, + expectedConfig: &hiveext.AgentClusterInstall{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-agent-cluster-install", + Namespace: "cluster0", + }, + Spec: hiveext.AgentClusterInstallSpec{ + APIVIP: "192.168.111.5", + IngressVIP: "192.168.111.4", + ClusterDeploymentRef: corev1.LocalObjectReference{ + Name: "ostest", + }, + ImageSetRef: &hivev1.ClusterImageSetReference{ + Name: "openshift-v4.10.0", + }, + Networking: hiveext.Networking{ + ClusterNetwork: []hiveext.ClusterNetworkEntry{ + { + CIDR: "10.128.0.0/14", + HostPrefix: 23, + }, + }, + ServiceNetwork: []string{ + "172.30.0.0/16", + }, + }, + ProvisionRequirements: hiveext.ProvisionRequirements{ + ControlPlaneAgents: 3, + WorkerAgents: 2, + }, + SSHPublicKey: "ssh-rsa AAAAmyKey", + }, + }, + }, + { + name: "not-yaml", + data: `This is not a yaml file`, + expectedError: true, + }, + { + name: "empty", + data: "", + expectedFound: true, + expectedConfig: &hiveext.AgentClusterInstall{}, + expectedError: false, + }, + { + name: "file-not-found", + fetchError: &os.PathError{Err: os.ErrNotExist}, + }, + { + name: "error-fetching-file", + fetchError: errors.New("fetch failed"), + expectedError: true, + }, + { + name: "unknown-field", + data: ` +metadata: + name: test-agent-cluster-install + namespace: cluster0 +spec: + wrongField: wrongValue`, + expectedError: true, + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + fileFetcher := mock.NewMockFileFetcher(mockCtrl) + fileFetcher.EXPECT().FetchByName(agentClusterInstallFilename). + Return( + &asset.File{ + Filename: agentClusterInstallFilename, + Data: []byte(tc.data)}, + tc.fetchError, + ) + + asset := &AgentClusterInstall{} + found, err := asset.Load(fileFetcher) + assert.Equal(t, tc.expectedFound, found, "unexpected found value returned from Load") + if tc.expectedError { + assert.Error(t, err, "expected error from Load") + } else { + assert.NoError(t, err, "unexpected error from Load") + } + if tc.expectedFound { + assert.Equal(t, tc.expectedConfig, asset.Config, "unexpected Config in AgentClusterInstall") + } + }) + } + +} diff --git a/pkg/asset/agent/manifests/agentpullsecret.go b/pkg/asset/agent/manifests/agentpullsecret.go new file mode 100644 index 0000000000..ffa6d37624 --- /dev/null +++ b/pkg/asset/agent/manifests/agentpullsecret.go @@ -0,0 +1,157 @@ +package manifests + +import ( + "fmt" + "os" + "path/filepath" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/validation/field" + "sigs.k8s.io/yaml" + + "github.com/openshift/installer/pkg/asset" + "github.com/openshift/installer/pkg/asset/agent" + "github.com/pkg/errors" +) + +var ( + agentPullSecretName = "pull-secret" + agentPullSecretFilename = filepath.Join(clusterManifestDir, fmt.Sprintf("%s.yaml", agentPullSecretName)) +) + +// AgentPullSecret generates the pull-secret file used by the agent installer. +type AgentPullSecret struct { + File *asset.File + Config *corev1.Secret +} + +var _ asset.WritableAsset = (*AgentPullSecret)(nil) + +// Name returns a human friendly name for the asset. +func (*AgentPullSecret) Name() string { + return "Agent PullSecret" +} + +// Dependencies returns all of the dependencies directly needed to generate +// the asset. +func (*AgentPullSecret) Dependencies() []asset.Asset { + return []asset.Asset{ + &agent.OptionalInstallConfig{}, + } +} + +// Generate generates the AgentPullSecret manifest. +func (a *AgentPullSecret) Generate(dependencies asset.Parents) error { + + installConfig := &agent.OptionalInstallConfig{} + dependencies.Get(installConfig) + + if installConfig.Config != nil { + secret := &corev1.Secret{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Secret", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: getPullSecretName(installConfig), + Namespace: getObjectMetaNamespace(installConfig), + }, + StringData: map[string]string{ + ".dockerconfigjson": installConfig.Config.PullSecret, + }, + } + a.Config = secret + + secretData, err := yaml.Marshal(secret) + if err != nil { + return errors.Wrap(err, "failed to marshal agent secret") + } + + a.File = &asset.File{ + Filename: agentPullSecretFilename, + Data: secretData, + } + } + + return a.finish() +} + +// Files returns the files generated by the asset. +func (a *AgentPullSecret) Files() []*asset.File { + if a.File != nil { + return []*asset.File{a.File} + } + return []*asset.File{} +} + +// Load returns the asset from disk. +func (a *AgentPullSecret) Load(f asset.FileFetcher) (bool, error) { + file, err := f.FetchByName(agentPullSecretFilename) + if err != nil { + if os.IsNotExist(err) { + return false, nil + } + return false, errors.Wrap(err, fmt.Sprintf("failed to load %s file", agentPullSecretFilename)) + } + + config := &corev1.Secret{} + if err := yaml.UnmarshalStrict(file.Data, config); err != nil { + return false, errors.Wrapf(err, "failed to unmarshal %s", agentPullSecretFilename) + } + + a.File, a.Config = file, config + if err = a.finish(); err != nil { + return false, err + } + + return true, nil +} + +func (a *AgentPullSecret) finish() error { + + if a.Config == nil { + return errors.New("missing configuration or manifest file") + } + + if err := a.validatePullSecret().ToAggregate(); err != nil { + return errors.Wrapf(err, "invalid PullSecret configuration") + } + + return nil +} + +func (a *AgentPullSecret) validatePullSecret() field.ErrorList { + allErrs := field.ErrorList{} + + if err := a.validateSecretIsNotEmpty(); err != nil { + allErrs = append(allErrs, err...) + } + + return allErrs +} + +func (a *AgentPullSecret) validateSecretIsNotEmpty() field.ErrorList { + + var allErrs field.ErrorList + + fieldPath := field.NewPath("StringData") + + if len(a.Config.StringData) == 0 { + allErrs = append(allErrs, field.Required(fieldPath, "the pull secret is empty")) + return allErrs + } + + pullSecret, ok := a.Config.StringData[".dockerconfigjson"] + if !ok { + allErrs = append(allErrs, field.Required(fieldPath, "the pull secret key '.dockerconfigjson' is not defined")) + return allErrs + } + + if pullSecret == "" { + allErrs = append(allErrs, field.Required(fieldPath, "the pull secret does not contain any data")) + return allErrs + } + + return allErrs +} diff --git a/pkg/asset/agent/manifests/agentpullsecret_test.go b/pkg/asset/agent/manifests/agentpullsecret_test.go new file mode 100644 index 0000000000..f6f5a1c657 --- /dev/null +++ b/pkg/asset/agent/manifests/agentpullsecret_test.go @@ -0,0 +1,197 @@ +package manifests + +import ( + "os" + "testing" + + "github.com/golang/mock/gomock" + "github.com/openshift/installer/pkg/asset" + "github.com/openshift/installer/pkg/asset/agent" + "github.com/openshift/installer/pkg/asset/mock" + "github.com/pkg/errors" + "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/yaml" +) + +func TestAgentPullSecret_Generate(t *testing.T) { + + cases := []struct { + name string + dependencies []asset.Asset + expectedError string + expectedConfig *corev1.Secret + }{ + { + name: "missing install config", + dependencies: []asset.Asset{ + &agent.OptionalInstallConfig{}, + }, + expectedError: "missing configuration or manifest file", + }, + { + name: "valid configuration", + dependencies: []asset.Asset{ + getValidOptionalInstallConfig(), + }, + expectedConfig: &corev1.Secret{ + TypeMeta: v1.TypeMeta{ + Kind: "Secret", + APIVersion: "v1", + }, + ObjectMeta: v1.ObjectMeta{ + Name: getPullSecretName(getValidOptionalInstallConfig()), + Namespace: getObjectMetaNamespace(getValidOptionalInstallConfig()), + }, + StringData: map[string]string{ + ".dockerconfigjson": TestSecret, + }, + }, + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + + parents := asset.Parents{} + parents.Add(tc.dependencies...) + + asset := &AgentPullSecret{} + err := asset.Generate(parents) + + if tc.expectedError != "" { + assert.Equal(t, tc.expectedError, err.Error()) + } else { + assert.NoError(t, err) + assert.Equal(t, tc.expectedConfig, asset.Config) + assert.NotEmpty(t, asset.Files()) + + configFile := asset.Files()[0] + assert.Equal(t, "cluster-manifests/pull-secret.yaml", configFile.Filename) + + var actualConfig corev1.Secret + err = yaml.Unmarshal(configFile.Data, &actualConfig) + assert.NoError(t, err) + assert.Equal(t, *tc.expectedConfig, actualConfig) + } + }) + } +} + +func TestAgentPullSecret_LoadedFromDisk(t *testing.T) { + + cases := []struct { + name string + data string + fetchError error + expectedFound bool + expectedError string + expectedConfig *corev1.Secret + }{ + { + name: "valid-config-file", + data: ` +apiVersion: v1 +kind: Secret +metadata: + name: pull-secret + namespace: cluster-0 +stringData: + .dockerconfigjson: c3VwZXItc2VjcmV0Cg==`, + expectedFound: true, + expectedConfig: &corev1.Secret{ + TypeMeta: v1.TypeMeta{ + Kind: "Secret", + APIVersion: "v1", + }, + ObjectMeta: v1.ObjectMeta{ + Name: "pull-secret", + Namespace: "cluster-0", + }, + StringData: map[string]string{ + ".dockerconfigjson": "c3VwZXItc2VjcmV0Cg==", + }, + }, + }, + { + name: "not-yaml", + data: `This is not a yaml file`, + expectedError: "failed to unmarshal cluster-manifests/pull-secret.yaml: error unmarshaling JSON: while decoding JSON: json: cannot unmarshal string into Go value of type v1.Secret", + }, + { + name: "empty", + data: "", + expectedError: "invalid PullSecret configuration: StringData: Required value: the pull secret is empty", + }, + { + name: "missing-string-data", + data: ` +apiVersion: v1 +kind: Secret +metadata: + name: pull-secret + namespace: cluster-0`, + expectedError: "invalid PullSecret configuration: StringData: Required value: the pull secret is empty", + }, + { + name: "missing-secret-key", + data: ` +apiVersion: v1 +kind: Secret +metadata: + name: pull-secret + namespace: cluster-0 +stringData: + .dockerconfigjson:`, + expectedError: "invalid PullSecret configuration: StringData: Required value: the pull secret does not contain any data", + }, + { + name: "file-not-found", + fetchError: &os.PathError{Err: os.ErrNotExist}, + }, + { + name: "error-fetching-file", + fetchError: errors.New("fetch failed"), + expectedError: "failed to load cluster-manifests/pull-secret.yaml file: fetch failed", + }, + { + name: "unknown-field", + data: ` + metadata: + name: pull-secret + namespace: cluster0 + spec: + wrongField: wrongValue`, + expectedError: "failed to unmarshal cluster-manifests/pull-secret.yaml: error converting YAML to JSON: yaml: line 2: found character that cannot start any token", + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + fileFetcher := mock.NewMockFileFetcher(mockCtrl) + fileFetcher.EXPECT().FetchByName(agentPullSecretFilename). + Return( + &asset.File{ + Filename: agentPullSecretFilename, + Data: []byte(tc.data)}, + tc.fetchError, + ) + + asset := &AgentPullSecret{} + found, err := asset.Load(fileFetcher) + assert.Equal(t, tc.expectedFound, found, "unexpected found value returned from Load") + if tc.expectedError != "" { + assert.Equal(t, tc.expectedError, err.Error()) + } else { + assert.NoError(t, err) + } + if tc.expectedFound { + assert.Equal(t, tc.expectedConfig, asset.Config, "unexpected Config in AgentPullSecret") + } + }) + } + +} diff --git a/pkg/asset/agent/manifests/clusterdeployment.go b/pkg/asset/agent/manifests/clusterdeployment.go new file mode 100644 index 0000000000..5b22369022 --- /dev/null +++ b/pkg/asset/agent/manifests/clusterdeployment.go @@ -0,0 +1,128 @@ +package manifests + +import ( + "fmt" + "os" + "path/filepath" + + hivev1 "github.com/openshift/hive/apis/hive/v1" + "github.com/pkg/errors" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/yaml" + + "github.com/openshift/installer/pkg/asset" + "github.com/openshift/installer/pkg/asset/agent" +) + +var ( + clusterDeploymentFilename = filepath.Join(clusterManifestDir, "cluster-deployment.yaml") +) + +// ClusterDeployment generates the cluster-deployment.yaml file. +type ClusterDeployment struct { + File *asset.File + Config *hivev1.ClusterDeployment +} + +var _ asset.WritableAsset = (*ClusterDeployment)(nil) + +// Name returns a human friendly name for the asset. +func (*ClusterDeployment) Name() string { + return "ClusterDeployment Config" +} + +// Dependencies returns all of the dependencies directly needed to generate +// the asset. +func (*ClusterDeployment) Dependencies() []asset.Asset { + return []asset.Asset{ + &agent.OptionalInstallConfig{}, + } +} + +// Generate generates the ClusterDeployment manifest. +func (cd *ClusterDeployment) Generate(dependencies asset.Parents) error { + installConfig := &agent.OptionalInstallConfig{} + dependencies.Get(installConfig) + + if installConfig.Config != nil { + clusterDeployment := &hivev1.ClusterDeployment{ + TypeMeta: metav1.TypeMeta{ + Kind: "ClusterDeployment", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: getClusterDeploymentName(installConfig), + Namespace: getObjectMetaNamespace(installConfig), + }, + Spec: hivev1.ClusterDeploymentSpec{ + ClusterName: getClusterDeploymentName(installConfig), + BaseDomain: installConfig.Config.BaseDomain, + PullSecretRef: &corev1.LocalObjectReference{ + Name: getPullSecretName(installConfig), + }, + ClusterInstallRef: &hivev1.ClusterInstallLocalReference{ + Group: "extensions.hive.openshift.io", + Version: "v1beta1", + Kind: "AgentClusterInstall", + Name: getAgentClusterInstallName(installConfig), + }, + }, + } + + cd.Config = clusterDeployment + clusterDeploymentData, err := yaml.Marshal(clusterDeployment) + if err != nil { + return errors.Wrap(err, "failed to marshal agent installer ClusterDeployment") + } + + cd.File = &asset.File{ + Filename: clusterDeploymentFilename, + Data: clusterDeploymentData, + } + + } + + return cd.finish() +} + +// Files returns the files generated by the asset. +func (cd *ClusterDeployment) Files() []*asset.File { + if cd.File != nil { + return []*asset.File{cd.File} + } + return []*asset.File{} +} + +// Load returns ClusterDeployment asset from the disk. +func (cd *ClusterDeployment) Load(f asset.FileFetcher) (bool, error) { + + file, err := f.FetchByName(clusterDeploymentFilename) + if err != nil { + if os.IsNotExist(err) { + return false, nil + } + return false, errors.Wrap(err, fmt.Sprintf("failed to load %s file", clusterDeploymentFilename)) + } + + config := &hivev1.ClusterDeployment{} + if err := yaml.UnmarshalStrict(file.Data, config); err != nil { + return false, errors.Wrapf(err, "failed to unmarshal %s", clusterDeploymentFilename) + } + + cd.File, cd.Config = file, config + if err = cd.finish(); err != nil { + return false, err + } + + return true, nil +} + +func (cd *ClusterDeployment) finish() error { + + if cd.Config == nil { + return errors.New("missing configuration or manifest file") + } + + return nil +} diff --git a/pkg/asset/agent/manifests/clusterdeployment_test.go b/pkg/asset/agent/manifests/clusterdeployment_test.go new file mode 100644 index 0000000000..4c364f4ce3 --- /dev/null +++ b/pkg/asset/agent/manifests/clusterdeployment_test.go @@ -0,0 +1,219 @@ +package manifests + +import ( + "os" + "testing" + + "github.com/golang/mock/gomock" + hivev1 "github.com/openshift/hive/apis/hive/v1" + hivev1agent "github.com/openshift/hive/apis/hive/v1/agent" + "github.com/openshift/installer/pkg/asset" + "github.com/openshift/installer/pkg/asset/agent" + "github.com/openshift/installer/pkg/asset/mock" + "github.com/pkg/errors" + "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/yaml" +) + +func TestClusterDeployment_Generate(t *testing.T) { + + cases := []struct { + name string + dependencies []asset.Asset + expectedError string + expectedConfig *hivev1.ClusterDeployment + }{ + { + name: "missing config", + dependencies: []asset.Asset{ + &agent.OptionalInstallConfig{}, + }, + expectedError: "missing configuration or manifest file", + }, + { + name: "valid configurations", + dependencies: []asset.Asset{ + getValidOptionalInstallConfig(), + }, + expectedConfig: &hivev1.ClusterDeployment{ + TypeMeta: metav1.TypeMeta{ + Kind: "ClusterDeployment", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: getClusterDeploymentName(getValidOptionalInstallConfig()), + Namespace: getObjectMetaNamespace(getValidOptionalInstallConfig()), + }, + Spec: hivev1.ClusterDeploymentSpec{ + ClusterName: getClusterDeploymentName(getValidOptionalInstallConfig()), + BaseDomain: "testing.com", + PullSecretRef: &corev1.LocalObjectReference{ + Name: getPullSecretName(getValidOptionalInstallConfig()), + }, + ClusterInstallRef: &hivev1.ClusterInstallLocalReference{ + Group: "extensions.hive.openshift.io", + Version: "v1beta1", + Kind: "AgentClusterInstall", + Name: getAgentClusterInstallName(getValidOptionalInstallConfig()), + }, + }, + }, + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + + parents := asset.Parents{} + parents.Add(tc.dependencies...) + + asset := &ClusterDeployment{} + err := asset.Generate(parents) + + if tc.expectedError != "" { + assert.Equal(t, tc.expectedError, err.Error()) + } else { + assert.NoError(t, err) + assert.Equal(t, tc.expectedConfig, asset.Config) + assert.NotEmpty(t, asset.Files()) + + configFile := asset.Files()[0] + assert.Equal(t, "cluster-manifests/cluster-deployment.yaml", configFile.Filename) + + var actualConfig hivev1.ClusterDeployment + err = yaml.Unmarshal(configFile.Data, &actualConfig) + assert.NoError(t, err) + assert.Equal(t, *tc.expectedConfig, actualConfig) + } + }) + } + +} + +func TestClusterDeployment_LoadedFromDisk(t *testing.T) { + + cases := []struct { + name string + data string + fetchError error + expectedFound bool + expectedError bool + expectedConfig *hivev1.ClusterDeployment + }{ + { + name: "valid-config-file", + data: ` +metadata: + name: compact-cluster + namespace: cluster0 +spec: + baseDomain: agent.example.com + clusterInstallRef: + group: extensions.hive.openshift.io + kind: AgentClusterInstall + name: test-agent-cluster-install + version: v1beta1 + clusterName: compact-cluster + controlPlaneConfig: + servingCertificates: {} + platform: + agentBareMetal: + agentSelector: + matchLabels: + bla: aaa + pullSecretRef: + name: pull-secret`, + expectedFound: true, + expectedConfig: &hivev1.ClusterDeployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "compact-cluster", + Namespace: "cluster0", + }, + Spec: hivev1.ClusterDeploymentSpec{ + BaseDomain: "agent.example.com", + ClusterInstallRef: &hivev1.ClusterInstallLocalReference{ + Group: "extensions.hive.openshift.io", + Kind: "AgentClusterInstall", + Name: "test-agent-cluster-install", + Version: "v1beta1", + }, + ClusterName: "compact-cluster", + ControlPlaneConfig: hivev1.ControlPlaneConfigSpec{}, + Platform: hivev1.Platform{ + AgentBareMetal: &hivev1agent.BareMetalPlatform{ + AgentSelector: metav1.LabelSelector{ + MatchLabels: map[string]string{ + "bla": "aaa", + }, + }, + }, + }, + PullSecretRef: &corev1.LocalObjectReference{ + Name: "pull-secret", + }, + }, + }, + }, + { + name: "not-yaml", + data: `This is not a yaml file`, + expectedError: true, + }, + { + name: "empty", + data: "", + expectedFound: true, + expectedConfig: &hivev1.ClusterDeployment{}, + expectedError: false, + }, + { + name: "file-not-found", + fetchError: &os.PathError{Err: os.ErrNotExist}, + }, + { + name: "error-fetching-file", + fetchError: errors.New("fetch failed"), + expectedError: true, + }, + { + name: "unknown-field", + data: ` +metadata: + name: cluster-deployment-bad + namespace: cluster0 +spec: + wrongField: wrongValue`, + expectedError: true, + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + fileFetcher := mock.NewMockFileFetcher(mockCtrl) + fileFetcher.EXPECT().FetchByName(clusterDeploymentFilename). + Return( + &asset.File{ + Filename: clusterDeploymentFilename, + Data: []byte(tc.data)}, + tc.fetchError, + ) + + asset := &ClusterDeployment{} + found, err := asset.Load(fileFetcher) + assert.Equal(t, tc.expectedFound, found, "unexpected found value returned from Load") + if tc.expectedError { + assert.Error(t, err, "expected error from Load") + } else { + assert.NoError(t, err, "unexpected error from Load") + } + if tc.expectedFound { + assert.Equal(t, tc.expectedConfig, asset.Config, "unexpected Config in ClusterDeployment") + } + }) + } + +} diff --git a/pkg/asset/agent/manifests/clusterimageset.go b/pkg/asset/agent/manifests/clusterimageset.go new file mode 100644 index 0000000000..e33417b8e9 --- /dev/null +++ b/pkg/asset/agent/manifests/clusterimageset.go @@ -0,0 +1,154 @@ +package manifests + +import ( + "fmt" + "os" + "path/filepath" + + hivev1 "github.com/openshift/hive/apis/hive/v1" + "github.com/openshift/installer/pkg/asset" + "github.com/openshift/installer/pkg/asset/agent" + "github.com/openshift/installer/pkg/asset/releaseimage" + "github.com/openshift/installer/pkg/version" + "github.com/pkg/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/validation/field" + "sigs.k8s.io/yaml" +) + +var ( + clusterImageSetFilename = filepath.Join(clusterManifestDir, "cluster-image-set.yaml") +) + +// ClusterImageSet generates the cluster-image-set.yaml file. +type ClusterImageSet struct { + File *asset.File + Config *hivev1.ClusterImageSet +} + +var _ asset.WritableAsset = (*ClusterImageSet)(nil) + +// Name returns a human friendly name for the asset. +func (*ClusterImageSet) Name() string { + return "ClusterImageSet Config" +} + +// Dependencies returns all of the dependencies directly needed to generate +// the asset. +func (*ClusterImageSet) Dependencies() []asset.Asset { + return []asset.Asset{ + &releaseimage.Image{}, + &agent.OptionalInstallConfig{}, + } +} + +// Generate generates the ClusterImageSet manifest. +func (a *ClusterImageSet) Generate(dependencies asset.Parents) error { + + releaseImage := &releaseimage.Image{} + installConfig := &agent.OptionalInstallConfig{} + dependencies.Get(releaseImage, installConfig) + + currentVersion, err := version.Version() + if err != nil { + return err + } + + if installConfig.Config != nil { + clusterImageSet := &hivev1.ClusterImageSet{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("openshift-%s", currentVersion), + Namespace: getObjectMetaNamespace(installConfig), + }, + Spec: hivev1.ClusterImageSetSpec{ + ReleaseImage: releaseImage.PullSpec, + }, + } + a.Config = clusterImageSet + + configData, err := yaml.Marshal(clusterImageSet) + if err != nil { + return errors.Wrap(err, "failed to marshal agent cluster image set") + } + + a.File = &asset.File{ + Filename: clusterImageSetFilename, + Data: configData, + } + } + + return a.finish() +} + +// Files returns the files generated by the asset. +func (a *ClusterImageSet) Files() []*asset.File { + if a.File != nil { + return []*asset.File{a.File} + } + return []*asset.File{} +} + +// Load returns ClusterImageSet asset from the disk. +func (a *ClusterImageSet) Load(f asset.FileFetcher) (bool, error) { + + clusterImageSetFile, err := f.FetchByName(clusterImageSetFilename) + if err != nil { + if os.IsNotExist(err) { + return false, nil + } + return false, errors.Wrap(err, fmt.Sprintf("failed to load %s file", clusterImageSetFilename)) + } + + a.File = clusterImageSetFile + + clusterImageSet := &hivev1.ClusterImageSet{} + if err := yaml.UnmarshalStrict(clusterImageSetFile.Data, clusterImageSet); err != nil { + err = errors.Wrapf(err, "failed to unmarshal %s", clusterImageSetFilename) + return false, err + } + a.Config = clusterImageSet + + if err = a.finish(); err != nil { + return false, err + } + return true, nil +} + +func (a *ClusterImageSet) finish() error { + + if a.Config == nil { + return errors.New("missing configuration or manifest file") + } + + if err := a.validateClusterImageSet().ToAggregate(); err != nil { + return errors.Wrapf(err, "invalid ClusterImageSet configuration") + } + + return nil +} + +func (a *ClusterImageSet) validateClusterImageSet() field.ErrorList { + allErrs := field.ErrorList{} + + if err := a.validateReleaseVersion(); err != nil { + allErrs = append(allErrs, err...) + } + + return allErrs +} + +func (a *ClusterImageSet) validateReleaseVersion() field.ErrorList { + + var allErrs field.ErrorList + + fieldPath := field.NewPath("Spec", "ReleaseImage") + + releaseImage := &releaseimage.Image{} + releaseImage.Generate(asset.Parents{}) + + if a.Config.Spec.ReleaseImage != releaseImage.PullSpec { + allErrs = append(allErrs, field.Forbidden(fieldPath, fmt.Sprintf("value must be equal to %s", releaseImage.PullSpec))) + } + + return allErrs +} diff --git a/pkg/asset/agent/manifests/clusterimageset_test.go b/pkg/asset/agent/manifests/clusterimageset_test.go new file mode 100644 index 0000000000..71e5f5c0d1 --- /dev/null +++ b/pkg/asset/agent/manifests/clusterimageset_test.go @@ -0,0 +1,190 @@ +package manifests + +import ( + "fmt" + "os" + "testing" + + "github.com/golang/mock/gomock" + hivev1 "github.com/openshift/hive/apis/hive/v1" + "github.com/openshift/installer/pkg/asset" + "github.com/openshift/installer/pkg/asset/agent" + "github.com/openshift/installer/pkg/asset/mock" + "github.com/openshift/installer/pkg/asset/releaseimage" + "github.com/pkg/errors" + "github.com/stretchr/testify/assert" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/yaml" +) + +func TestClusterImageSet_Generate(t *testing.T) { + cases := []struct { + name string + dependencies []asset.Asset + expectedError string + expectedConfig *hivev1.ClusterImageSet + }{ + { + name: "missing install config", + dependencies: []asset.Asset{ + &agent.OptionalInstallConfig{}, + &releaseimage.Image{}, + }, + expectedError: "missing configuration or manifest file", + }, + { + name: "invalid ClusterImageSet configuration", + dependencies: []asset.Asset{ + getValidOptionalInstallConfig(), + &releaseimage.Image{}, + }, + expectedError: "invalid ClusterImageSet configuration: Spec.ReleaseImage: Forbidden: value must be equal to " + TestReleaseImage, + }, + { + name: "valid configuration", + dependencies: []asset.Asset{ + getValidOptionalInstallConfig(), + &releaseimage.Image{ + PullSpec: TestReleaseImage, + }, + }, + expectedConfig: &hivev1.ClusterImageSet{ + ObjectMeta: metav1.ObjectMeta{ + Name: "openshift-was not built correctly", + Namespace: getObjectMetaNamespace(getValidOptionalInstallConfig()), + }, + Spec: hivev1.ClusterImageSetSpec{ + ReleaseImage: TestReleaseImage, + }, + }, + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + + parents := asset.Parents{} + parents.Add(tc.dependencies...) + + asset := &ClusterImageSet{} + err := asset.Generate(parents) + + if tc.expectedError != "" { + assert.Equal(t, tc.expectedError, err.Error()) + } else { + assert.NoError(t, err) + assert.Equal(t, tc.expectedConfig, asset.Config) + assert.NotEmpty(t, asset.Files()) + + configFile := asset.Files()[0] + assert.Equal(t, "cluster-manifests/cluster-image-set.yaml", configFile.Filename) + + var actualConfig hivev1.ClusterImageSet + err = yaml.Unmarshal(configFile.Data, &actualConfig) + assert.NoError(t, err) + assert.Equal(t, *tc.expectedConfig, actualConfig) + } + + }) + } + +} + +func TestClusterImageSet_LoadedFromDisk(t *testing.T) { + + currentRelease, err := releaseimage.Default() + assert.NoError(t, err) + + cases := []struct { + name string + data string + fetchError error + expectedFound bool + expectedError string + expectedConfig *hivev1.ClusterImageSet + }{ + { + name: "valid-config-file", + data: ` +metadata: + name: openshift-v4.10.0 +spec: + releaseImage: ` + currentRelease, + expectedFound: true, + expectedConfig: &hivev1.ClusterImageSet{ + ObjectMeta: metav1.ObjectMeta{ + Name: "openshift-v4.10.0", + }, + Spec: hivev1.ClusterImageSetSpec{ + ReleaseImage: currentRelease, + }, + }, + }, + { + name: "different-version-not-supported", + data: ` +metadata: + name: openshift-v4.10.0 +spec: + releaseImage: 99.999`, + expectedError: fmt.Sprintf("invalid ClusterImageSet configuration: Spec.ReleaseImage: Forbidden: value must be equal to %s", currentRelease), + }, + { + name: "not-yaml", + data: `This is not a yaml file`, + expectedError: "failed to unmarshal cluster-manifests/cluster-image-set.yaml: error unmarshaling JSON: while decoding JSON: json: cannot unmarshal string into Go value of type v1.ClusterImageSet", + }, + { + name: "empty", + data: "", + expectedError: fmt.Sprintf("invalid ClusterImageSet configuration: Spec.ReleaseImage: Forbidden: value must be equal to %s", currentRelease), + }, + { + name: "file-not-found", + fetchError: &os.PathError{Err: os.ErrNotExist}, + }, + { + name: "error-fetching-file", + fetchError: errors.New("fetch failed"), + expectedError: "failed to load cluster-manifests/cluster-image-set.yaml file: fetch failed", + }, + { + name: "unknown-field", + data: ` +metadata: + name: test-cluster-image-set + namespace: cluster0 +spec: + wrongField: wrongValue`, + expectedError: "failed to unmarshal cluster-manifests/cluster-image-set.yaml: error unmarshaling JSON: while decoding JSON: json: unknown field \"wrongField\"", + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + fileFetcher := mock.NewMockFileFetcher(mockCtrl) + fileFetcher.EXPECT().FetchByName(clusterImageSetFilename). + Return( + &asset.File{ + Filename: clusterImageSetFilename, + Data: []byte(tc.data)}, + tc.fetchError, + ) + + asset := &ClusterImageSet{} + found, err := asset.Load(fileFetcher) + assert.Equal(t, tc.expectedFound, found, "unexpected found value returned from Load") + if tc.expectedError != "" { + assert.Equal(t, tc.expectedError, err.Error()) + } else { + assert.NoError(t, err) + } + if tc.expectedFound { + assert.Equal(t, tc.expectedConfig, asset.Config, "unexpected Config in ClusterImageSet") + } + }) + } + +} diff --git a/pkg/asset/agent/manifests/common.go b/pkg/asset/agent/manifests/common.go new file mode 100644 index 0000000000..cfccdc748e --- /dev/null +++ b/pkg/asset/agent/manifests/common.go @@ -0,0 +1,68 @@ +package manifests + +import ( + "github.com/openshift/installer/pkg/asset/agent" + "github.com/openshift/installer/pkg/asset/agent/agentconfig" + "github.com/openshift/installer/pkg/types" + "github.com/openshift/installer/pkg/version" +) + +func getAgentClusterInstallName(ic *agent.OptionalInstallConfig) string { + return ic.Config.ObjectMeta.Name +} + +func getClusterDeploymentName(ic *agent.OptionalInstallConfig) string { + return ic.Config.ObjectMeta.Name +} + +func getInfraEnvName(ic *agent.OptionalInstallConfig) string { + return ic.Config.ObjectMeta.Name +} + +func getPullSecretName(ic *agent.OptionalInstallConfig) string { + return ic.Config.ObjectMeta.Name + "-pull-secret" +} + +func getObjectMetaNamespace(ic *agent.OptionalInstallConfig) string { + return ic.Config.Namespace +} + +func getNMStateConfigName(a *agentconfig.AgentConfig) string { + return a.Config.ObjectMeta.Name +} + +func getNMStateConfigNamespace(a *agentconfig.AgentConfig) string { + return a.Config.Namespace +} + +func getNMStateConfigLabelsFromOptionalInstallConfig(ic *agent.OptionalInstallConfig) map[string]string { + return map[string]string{ + "infraenvs.agent-install.openshift.io": getInfraEnvName(ic), + } +} + +func getNMStateConfigLabelsFromAgentConfig(a *agentconfig.AgentConfig) map[string]string { + return map[string]string{ + "infraenvs.agent-install.openshift.io": getNMStateConfigName(a), + } +} + +func getClusterImageSetReferenceName() string { + versionString, _ := version.Version() + return "openshift-" + versionString +} + +// getVIPs returns a string representation of the platform's API VIP and Ingress VIP. +// It returns an empty string if the platform does not configure a VIP +func getVIPs(p *types.Platform) (string, string) { + switch { + case p == nil: + return "", "" + case p.BareMetal != nil: + return p.BareMetal.APIVIP, p.BareMetal.IngressVIP + case p.VSphere != nil: + return p.VSphere.APIVIP, p.VSphere.IngressVIP + default: + return "", "" + } +} diff --git a/pkg/asset/agent/manifests/extramanifests.go b/pkg/asset/agent/manifests/extramanifests.go new file mode 100644 index 0000000000..cd227bba9c --- /dev/null +++ b/pkg/asset/agent/manifests/extramanifests.go @@ -0,0 +1,60 @@ +package manifests + +import ( + "path/filepath" + + "github.com/openshift/installer/pkg/asset" + "github.com/pkg/errors" +) + +const ( + openshiftManifestDir = "openshift" +) + +// ExtraManifests manages the additional manifests for cluster customization +type ExtraManifests struct { + FileList []*asset.File +} + +var ( + _ asset.WritableAsset = (*ExtraManifests)(nil) +) + +// Name returns a human friendly name for the operator +func (em *ExtraManifests) Name() string { + return "Extra Manifests" +} + +// Dependencies returns all of the dependencies directly needed by the +// Master asset +func (em *ExtraManifests) Dependencies() []asset.Asset { + return []asset.Asset{} +} + +// Generate is not required for ExtraManifests. +func (em *ExtraManifests) Generate(dependencies asset.Parents) error { + return nil +} + +// Files returns the files generated by the asset. +func (em *ExtraManifests) Files() []*asset.File { + return em.FileList +} + +// Load reads the asset files from disk. +func (em *ExtraManifests) Load(f asset.FileFetcher) (found bool, err error) { + yamlFileList, err := f.FetchByPattern(filepath.Join(openshiftManifestDir, "*.yaml")) + if err != nil { + return false, errors.Wrap(err, "failed to load *.yaml files") + } + ymlFileList, err := f.FetchByPattern(filepath.Join(openshiftManifestDir, "*.yml")) + if err != nil { + return false, errors.Wrap(err, "failed to load *.yml files") + } + + em.FileList = append(em.FileList, yamlFileList...) + em.FileList = append(em.FileList, ymlFileList...) + asset.SortFiles(em.FileList) + + return len(em.FileList) > 0, nil +} diff --git a/pkg/asset/agent/manifests/extramanifests_test.go b/pkg/asset/agent/manifests/extramanifests_test.go new file mode 100644 index 0000000000..320df17d65 --- /dev/null +++ b/pkg/asset/agent/manifests/extramanifests_test.go @@ -0,0 +1,125 @@ +package manifests + +import ( + "fmt" + "os" + "path/filepath" + "testing" + + "github.com/golang/mock/gomock" + "github.com/openshift/installer/pkg/asset" + "github.com/openshift/installer/pkg/asset/mock" + "github.com/stretchr/testify/assert" +) + +func TestExtraManifests_Load(t *testing.T) { + cases := []struct { + name string + files []string + fetchError error + + expectedFound bool + expectedFiles []string + expectedError string + }{ + { + name: "no-extras", + files: []string{}, + + expectedFound: false, + expectedFiles: []string{}, + }, + { + name: "just-yaml", + files: []string{"/openshift/test-configmap.yaml"}, + + expectedFound: true, + expectedFiles: []string{"/openshift/test-configmap.yaml"}, + }, + { + name: "just-yml", + files: []string{"/openshift/another-test-configmap.yml"}, + + expectedFound: true, + expectedFiles: []string{"/openshift/another-test-configmap.yml"}, + }, + { + name: "mixed", + files: []string{ + "/openshift/test-configmap.yaml", + "/openshift/another-test-configmap.yml", + }, + + expectedFound: true, + expectedFiles: []string{ + "/openshift/test-configmap.yaml", + "/openshift/another-test-configmap.yml", + }, + }, + { + name: "error", + fetchError: os.ErrNotExist, + + expectedError: "failed to load *.yaml files: file does not exist", + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + + yamlFiles := []*asset.File{} + ymlFiles := []*asset.File{} + for _, f := range tc.files { + assetFile := &asset.File{ + Filename: f, + Data: []byte(f), + } + + switch filepath.Ext(f) { + case ".yaml": + yamlFiles = append(yamlFiles, assetFile) + case ".yml": + ymlFiles = append(ymlFiles, assetFile) + default: + t.Error("extension not valid") + } + } + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + fileFetcher := mock.NewMockFileFetcher(mockCtrl) + fileFetcher.EXPECT().FetchByPattern("openshift/*.yaml").Return( + yamlFiles, + tc.fetchError, + ) + if tc.fetchError == nil { + fileFetcher.EXPECT().FetchByPattern("openshift/*.yml").Return( + ymlFiles, + nil, + ) + } + + extraManifestsAsset := &ExtraManifests{} + found, err := extraManifestsAsset.Load(fileFetcher) + + assert.Equal(t, tc.expectedFound, found) + if tc.expectedError != "" { + assert.Equal(t, tc.expectedError, err.Error()) + } else { + assert.NoError(t, err) + assert.Equal(t, len(tc.expectedFiles), len(extraManifestsAsset.FileList)) + for _, f := range tc.expectedFiles { + found := false + for _, a := range extraManifestsAsset.FileList { + if a.Filename == f { + found = true + break + } + } + assert.True(t, found, fmt.Sprintf("Expected file %s not found", f)) + } + } + }) + } +} diff --git a/pkg/asset/agent/manifests/infraenv.go b/pkg/asset/agent/manifests/infraenv.go new file mode 100644 index 0000000000..0d5b78099b --- /dev/null +++ b/pkg/asset/agent/manifests/infraenv.go @@ -0,0 +1,125 @@ +package manifests + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + aiv1beta1 "github.com/openshift/assisted-service/api/v1beta1" + "github.com/pkg/errors" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/yaml" + + "github.com/openshift/installer/pkg/asset" + "github.com/openshift/installer/pkg/asset/agent" +) + +var ( + infraEnvFilename = filepath.Join(clusterManifestDir, "infraenv.yaml") +) + +// InfraEnv generates the infraenv.yaml file. +type InfraEnv struct { + File *asset.File + Config *aiv1beta1.InfraEnv +} + +var _ asset.WritableAsset = (*InfraEnv)(nil) + +// Name returns a human friendly name for the asset. +func (*InfraEnv) Name() string { + return "InfraEnv Config" +} + +// Dependencies returns all of the dependencies directly needed to generate +// the asset. +func (*InfraEnv) Dependencies() []asset.Asset { + return []asset.Asset{ + &agent.OptionalInstallConfig{}, + } +} + +// Generate generates the InfraEnv manifest. +func (i *InfraEnv) Generate(dependencies asset.Parents) error { + + installConfig := &agent.OptionalInstallConfig{} + dependencies.Get(installConfig) + + if installConfig.Config != nil { + infraEnv := &aiv1beta1.InfraEnv{ + ObjectMeta: metav1.ObjectMeta{ + Name: getInfraEnvName(installConfig), + Namespace: getObjectMetaNamespace(installConfig), + }, + Spec: aiv1beta1.InfraEnvSpec{ + ClusterRef: &aiv1beta1.ClusterReference{ + Name: getClusterDeploymentName(installConfig), + Namespace: getObjectMetaNamespace(installConfig), + }, + SSHAuthorizedKey: strings.Trim(installConfig.Config.SSHKey, "|\n\t"), + PullSecretRef: &corev1.LocalObjectReference{ + Name: getPullSecretName(installConfig), + }, + NMStateConfigLabelSelector: metav1.LabelSelector{ + MatchLabels: getNMStateConfigLabelsFromOptionalInstallConfig(installConfig), + }, + }, + } + i.Config = infraEnv + + infraEnvData, err := yaml.Marshal(infraEnv) + if err != nil { + return errors.Wrap(err, "failed to marshal agent installer infraEnv") + } + + i.File = &asset.File{ + Filename: infraEnvFilename, + Data: infraEnvData, + } + } + + return i.finish() +} + +// Files returns the files generated by the asset. +func (i *InfraEnv) Files() []*asset.File { + if i.File != nil { + return []*asset.File{i.File} + } + return []*asset.File{} +} + +// Load returns infraenv asset from the disk. +func (i *InfraEnv) Load(f asset.FileFetcher) (bool, error) { + + file, err := f.FetchByName(infraEnvFilename) + if err != nil { + if os.IsNotExist(err) { + return false, nil + } + return false, errors.Wrap(err, fmt.Sprintf("failed to load %s file", infraEnvFilename)) + } + + config := &aiv1beta1.InfraEnv{} + if err := yaml.UnmarshalStrict(file.Data, config); err != nil { + return false, errors.Wrapf(err, "failed to unmarshal %s", infraEnvFilename) + } + + i.File, i.Config = file, config + if err = i.finish(); err != nil { + return false, err + } + + return true, nil +} + +func (i *InfraEnv) finish() error { + + if i.Config == nil { + return errors.New("missing configuration or manifest file") + } + + return nil +} diff --git a/pkg/asset/agent/manifests/infraenv_test.go b/pkg/asset/agent/manifests/infraenv_test.go new file mode 100644 index 0000000000..d1893bfc2c --- /dev/null +++ b/pkg/asset/agent/manifests/infraenv_test.go @@ -0,0 +1,194 @@ +package manifests + +import ( + "errors" + "os" + "strings" + "testing" + + "github.com/golang/mock/gomock" + aiv1beta1 "github.com/openshift/assisted-service/api/v1beta1" + "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/yaml" + + "github.com/openshift/installer/pkg/asset" + "github.com/openshift/installer/pkg/asset/agent" + "github.com/openshift/installer/pkg/asset/mock" +) + +func TestInfraEnv_Generate(t *testing.T) { + + cases := []struct { + name string + dependencies []asset.Asset + expectedError string + expectedConfig *aiv1beta1.InfraEnv + }{ + { + name: "missing-config", + dependencies: []asset.Asset{ + &agent.OptionalInstallConfig{}, + }, + expectedError: "missing configuration or manifest file", + }, + { + name: "valid configuration", + dependencies: []asset.Asset{ + getValidOptionalInstallConfig(), + }, + expectedConfig: &aiv1beta1.InfraEnv{ + ObjectMeta: metav1.ObjectMeta{ + Name: getInfraEnvName(getValidOptionalInstallConfig()), + Namespace: getObjectMetaNamespace(getValidOptionalInstallConfig()), + }, + Spec: aiv1beta1.InfraEnvSpec{ + ClusterRef: &aiv1beta1.ClusterReference{ + Name: getClusterDeploymentName(getValidOptionalInstallConfig()), + Namespace: getObjectMetaNamespace(getValidOptionalInstallConfig()), + }, + SSHAuthorizedKey: strings.Trim(TestSSHKey, "|\n\t"), + PullSecretRef: &corev1.LocalObjectReference{ + Name: getPullSecretName(getValidOptionalInstallConfig()), + }, + NMStateConfigLabelSelector: metav1.LabelSelector{ + MatchLabels: getNMStateConfigLabelsFromOptionalInstallConfig(getValidOptionalInstallConfig()), + }, + }, + }, + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + + parents := asset.Parents{} + parents.Add(tc.dependencies...) + + asset := &InfraEnv{} + err := asset.Generate(parents) + + if tc.expectedError != "" { + assert.Equal(t, tc.expectedError, err.Error()) + } else { + assert.NoError(t, err) + assert.Equal(t, tc.expectedConfig, asset.Config) + assert.NotEmpty(t, asset.Files()) + + configFile := asset.Files()[0] + assert.Equal(t, "cluster-manifests/infraenv.yaml", configFile.Filename) + + var actualConfig aiv1beta1.InfraEnv + err = yaml.Unmarshal(configFile.Data, &actualConfig) + assert.NoError(t, err) + assert.Equal(t, *tc.expectedConfig, actualConfig) + } + }) + } +} + +func TestInfraEnv_LoadedFromDisk(t *testing.T) { + + cases := []struct { + name string + data string + fetchError error + expectedFound bool + expectedError string + expectedConfig *aiv1beta1.InfraEnv + }{ + { + name: "valid-config-file", + data: ` +metadata: + name: infraEnv + namespace: cluster0 +spec: + clusterRef: + name: ocp-edge-cluster-0 + namespace: cluster0 + nmStateConfigLabelSelector: + matchLabels: + cluster0-nmstate-label-name: cluster0-nmstate-label-value + pullSecretRef: + name: pull-secret + sshAuthorizedKey: | + ssh-rsa AAAAmyKey`, + expectedFound: true, + expectedConfig: &aiv1beta1.InfraEnv{ + ObjectMeta: metav1.ObjectMeta{ + Name: "infraEnv", + Namespace: "cluster0", + }, + Spec: aiv1beta1.InfraEnvSpec{ + ClusterRef: &aiv1beta1.ClusterReference{ + Name: "ocp-edge-cluster-0", + Namespace: "cluster0", + }, + NMStateConfigLabelSelector: metav1.LabelSelector{ + MatchLabels: map[string]string{ + "cluster0-nmstate-label-name": "cluster0-nmstate-label-value", + }, + }, + PullSecretRef: &corev1.LocalObjectReference{ + Name: "pull-secret", + }, + SSHAuthorizedKey: "ssh-rsa AAAAmyKey", + }, + }, + }, + { + name: "not-yaml", + data: `This is not a yaml file`, + expectedError: "failed to unmarshal cluster-manifests/infraenv.yaml: error unmarshaling JSON: while decoding JSON: json: cannot unmarshal string into Go value of type v1beta1.InfraEnv", + }, + { + name: "file-not-found", + fetchError: &os.PathError{Err: os.ErrNotExist}, + }, + { + name: "error-fetching-file", + fetchError: errors.New("fetch failed"), + expectedError: "failed to load cluster-manifests/infraenv.yaml file: fetch failed", + }, + { + name: "unknown-field", + data: ` + metadata: + name: infraEnv + namespace: cluster0 + spec: + wrongField: wrongValue`, + expectedError: "failed to unmarshal cluster-manifests/infraenv.yaml: error converting YAML to JSON: yaml: line 2: found character that cannot start any token", + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + fileFetcher := mock.NewMockFileFetcher(mockCtrl) + fileFetcher.EXPECT().FetchByName(infraEnvFilename). + Return( + &asset.File{ + Filename: infraEnvFilename, + Data: []byte(tc.data)}, + tc.fetchError, + ) + + asset := &InfraEnv{} + found, err := asset.Load(fileFetcher) + if tc.expectedError != "" { + assert.Equal(t, tc.expectedError, err.Error()) + } else { + assert.NoError(t, err) + } + assert.Equal(t, tc.expectedFound, found, "unexpected found value returned from Load") + if tc.expectedFound { + assert.Equal(t, tc.expectedConfig, asset.Config, "unexpected Config in InfraEnv") + } + }) + } + +} diff --git a/pkg/asset/agent/manifests/network-scripts.go b/pkg/asset/agent/manifests/network-scripts.go new file mode 100644 index 0000000000..2f066cdf38 --- /dev/null +++ b/pkg/asset/agent/manifests/network-scripts.go @@ -0,0 +1,173 @@ +package manifests + +// This file is copied from https://github.com/openshift/assisted-service/blob/master/internal/constants/scripts.go +// as its in the internal directory so can't be imported + +// PreNetworkConfigScript script runs on hosts before network manager service starts in order to apply +// user's provided network configuration on the host. +// If the user provides static network configuration, the network config files will be stored in directory +// /etc/assisted/network in the following structure: +// /etc/assisted/network/ +// +-- host1 +// | +--- *.nmconnection +// | +--- mac_interface.ini +// +-- host2 +// +--- *.nmconnection +// +--- mac_interface.ini +// 1. *.nmconnections - files generated by nmstate based on yaml files provided by the user +// 2. mac_interface.ini - the file contains mapping of mac-address to logical interface name. +// There are two usages for the file: +// 1. Map logical interface name to MAC Address of the host. The logical interface name is a +// name provided by the user for the interface. It will be replaced by the script with the +// actual network interface name. +// 2. Identify the host directory which belongs to the current host by matching a MAC Address +// from the mapping file with host network interfaces. +// +// Applying the network configuration of each host will be done by: +// 1. Associate the current host with its matching hostX directory. The association will be done by +// matching host's mac addresses with those in mac_interface.ini. +// 2. Replace logical interface name in nmconnection files with the interface name as set on the host +// 3. Rename nmconnection files to start with the interface name (instead of the logical interface name) +// 4. Copy the nmconnection files to /NetworkManager/system-connections/ +const PreNetworkConfigScript = `#!/bin/bash + +# The directory that contains nmconnection files of all nodes +NMCONNECTIONS_DIR=/etc/assisted/network +MAC_NIC_MAPPING_FILE=mac_interface.ini + +if [ ! -d "$NMCONNECTIONS_DIR" ] +then + exit 0 +fi + +# A map of host mac addresses to interface names +declare -A host_macs_to_hw_iface + +# The directory that contains nmconnection files for the current host +host_dir="" + +# The mapping file of the current host +mapping_file="" + +# A nic-to-mac map created from the mapping file associated with the host +declare -A logical_nic_mac_map + +# Find destination directory based on ISO mode +if [[ -f /etc/initrd-release ]]; then + ETC_NETWORK_MANAGER="/run/NetworkManager/system-connections" +else + ETC_NETWORK_MANAGER="/etc/NetworkManager/system-connections" +fi + +# remove default connection file create by NM(nm-initrd-generator). This is a WA until +# NM is back to supporting priority between nmconnections +rm -f ${ETC_NETWORK_MANAGER}/* + +# Create a map of host mac addresses to their network interfaces +function map_host_macs_to_interfaces() { + SYS_CLASS_NET_DIR='/sys/class/net' + for nic in $( ls $SYS_CLASS_NET_DIR ) + do + mac=$(cat $SYS_CLASS_NET_DIR/$nic/address | tr '[:lower:]' '[:upper:]') + host_macs_to_hw_iface[$mac]=$nic + done +} + +function find_host_directory_by_mac_address() { + for d in $(ls -d ${NMCONNECTIONS_DIR}/host*) + do + mapping_file="${d}/${MAC_NIC_MAPPING_FILE}" + if [ ! -f $mapping_file ] + then + echo "Mapping file '$mapping_file' is missing. Skipping on directory '$d'" + continue + fi + + # check if mapping file contains mac-address that exists on the current host + for mac_address in $(cat $mapping_file | cut -d= -f1 | tr '[:lower:]' '[:upper:]') + do + if [[ ! -z "${host_macs_to_hw_iface[${mac_address}]:-}" ]] + then + host_dir=$(mktemp -d) + cp ${d}/* $host_dir + return + fi + done + done + + if [ -z "$host_dir" ] + then + echo "None of host directories are a match for the current host" + exit 0 + fi +} + +function set_logical_nic_mac_mapping() { + # initialize logical_nic_mac_map with mapping file entries + readarray -t lines < "${mapping_file}" + for line in "${lines[@]}" + do + mac=${line%%=*} + nic=${line#*=} + logical_nic_mac_map[$nic]=${mac^^} + done +} + +# Replace logical interface name in nmconnection files with the interface name from the mapping file +# of host's directory. Replacement is done based on mac-address matching +function update_interface_names_by_mapping_file() { + + # iterate over host's nmconnection files and replace logical interface name with host's nic name + for nmconn_file in $(ls -1 ${host_dir}/*.nmconnection) + do + # iterate over mapping to find nmconnection files with logical interface name + for nic in "${!logical_nic_mac_map[@]}" + do + mac=${logical_nic_mac_map[$nic]} + + # the pattern should match '=eth0' (interface name) or '=eth0.' (for vlan devices) + if grep -q -e "=$nic$" -e "=$nic\." "$nmconn_file" + then + # get host interface name + host_iface=${host_macs_to_hw_iface[$mac]} + if [ -z "$host_iface" ] + then + echo "Mapping file contains MAC Address '$mac' (for logical interface name '$nic') that doesn't exist on the host" + continue + fi + + # replace logical interface name with host interface name + sed -i -e "s/=$nic$/=$host_iface/g" -e "s/=$nic\./=$host_iface\./g" $nmconn_file + fi + done + done +} + +function copy_nmconnection_files_to_nm_config_dir() { + for nmconn_file in $(ls -1 ${host_dir}/*.nmconnection) + do + # rename nmconnection files based on the actual interface name + filename=$(basename $nmconn_file) + prefix="${filename%%.*}" + extension="${filename#*.}" + if [ ! -z "${logical_nic_mac_map[$prefix]}" ] + then + dir_path=$(dirname $nmconn_file) + mac_address=${logical_nic_mac_map[$prefix]} + host_iface=${host_macs_to_hw_iface[$mac_address]} + if [ ! -z "$host_iface" ] + then + mv $nmconn_file "${dir_path}/${host_iface}.${extension}" + fi + fi + done + + cp ${host_dir}/*.nmconnection ${ETC_NETWORK_MANAGER}/ +} + +map_host_macs_to_interfaces +find_host_directory_by_mac_address +set_logical_nic_mac_mapping +update_interface_names_by_mapping_file +copy_nmconnection_files_to_nm_config_dir +` diff --git a/pkg/asset/agent/manifests/nmstateconfig.go b/pkg/asset/agent/manifests/nmstateconfig.go new file mode 100644 index 0000000000..47ac09838a --- /dev/null +++ b/pkg/asset/agent/manifests/nmstateconfig.go @@ -0,0 +1,330 @@ +package manifests + +import ( + "bytes" + "context" + "fmt" + "io" + "net" + "os" + "path/filepath" + + aiv1beta1 "github.com/openshift/assisted-service/api/v1beta1" + "github.com/openshift/assisted-service/models" + "github.com/openshift/assisted-service/pkg/staticnetworkconfig" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/apimachinery/pkg/util/yaml" + + "github.com/openshift/installer/pkg/asset" + "github.com/openshift/installer/pkg/asset/agent/agentconfig" + k8syaml "sigs.k8s.io/yaml" +) + +var ( + nmStateConfigFilename = filepath.Join(clusterManifestDir, "nmstateconfig.yaml") +) + +// NMStateConfig generates the nmstateconfig.yaml file. +type NMStateConfig struct { + File *asset.File + StaticNetworkConfig []*models.HostStaticNetworkConfig + Config []*aiv1beta1.NMStateConfig +} + +type nmStateConfig struct { + Interfaces []struct { + IPV4 struct { + Address []struct { + IP string `yaml:"ip,omitempty"` + } `yaml:"address,omitempty"` + } `yaml:"ipv4,omitempty"` + IPV6 struct { + Address []struct { + IP string `yaml:"ip,omitempty"` + } `yaml:"address,omitempty"` + } `yaml:"ipv6,omitempty"` + } `yaml:"interfaces,omitempty"` +} + +var _ asset.WritableAsset = (*NMStateConfig)(nil) + +// Name returns a human friendly name for the asset. +func (*NMStateConfig) Name() string { + return "NMState Config" +} + +// Dependencies returns all of the dependencies directly needed to generate +// the asset. +func (*NMStateConfig) Dependencies() []asset.Asset { + return []asset.Asset{ + &agentconfig.AgentConfig{}, + } +} + +// Generate generates the NMStateConfig manifest. +func (n *NMStateConfig) Generate(dependencies asset.Parents) error { + + agentConfig := &agentconfig.AgentConfig{} + dependencies.Get(agentConfig) + + staticNetworkConfig := []*models.HostStaticNetworkConfig{} + nmStateConfigs := []*aiv1beta1.NMStateConfig{} + var data string + + if agentConfig.Config != nil { + for i, host := range agentConfig.Config.Hosts { + nmStateConfig := aiv1beta1.NMStateConfig{ + TypeMeta: metav1.TypeMeta{ + Kind: "NMStateConfig", + APIVersion: "agent-install.openshift.io/v1beta1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf(getNMStateConfigName(agentConfig)+"-%d", i), + Namespace: getNMStateConfigNamespace(agentConfig), + Labels: getNMStateConfigLabelsFromAgentConfig(agentConfig), + }, + Spec: aiv1beta1.NMStateConfigSpec{ + NetConfig: aiv1beta1.NetConfig{ + Raw: []byte(host.NetworkConfig.Raw), + }, + }, + } + for _, hostInterface := range host.Interfaces { + intrfc := aiv1beta1.Interface{ + Name: hostInterface.Name, + MacAddress: hostInterface.MacAddress, + } + nmStateConfig.Spec.Interfaces = append(nmStateConfig.Spec.Interfaces, &intrfc) + + } + nmStateConfigs = append(nmStateConfigs, &nmStateConfig) + + staticNetworkConfig = append(staticNetworkConfig, &models.HostStaticNetworkConfig{ + MacInterfaceMap: buildMacInterfaceMap(nmStateConfig), + NetworkYaml: string(nmStateConfig.Spec.NetConfig.Raw), + }) + + // Marshal the nmStateConfig one at a time + // and add a yaml seperator with new line + // so as not to marshal the nmStateConfigs + // as a yaml list in the generated nmstateconfig.yaml + nmStateConfigData, err := k8syaml.Marshal(nmStateConfig) + + if err != nil { + return errors.Wrap(err, "failed to marshal agent installer NMStateConfig") + } + data = fmt.Sprint(data, fmt.Sprint(string(nmStateConfigData), "---\n")) + } + + n.Config = nmStateConfigs + n.StaticNetworkConfig = staticNetworkConfig + + n.File = &asset.File{ + Filename: nmStateConfigFilename, + Data: []byte(data), + } + } + + return n.finish() +} + +// Files returns the files generated by the asset. +func (n *NMStateConfig) Files() []*asset.File { + if n.File != nil { + return []*asset.File{n.File} + } + return []*asset.File{} +} + +// Load returns the NMStateConfig asset from the disk. +func (n *NMStateConfig) Load(f asset.FileFetcher) (bool, error) { + + file, err := f.FetchByName(nmStateConfigFilename) + if err != nil { + if os.IsNotExist(err) { + return false, nil + } + return false, errors.Wrapf(err, "failed to load file %s", nmStateConfigFilename) + } + + // Split up the file into multiple YAMLs if it contains NMStateConfig for more than one node + var decoder nmStateConfigYamlDecoder + yamlList, err := getMultipleYamls(file.Data, &decoder) + if err != nil { + return false, errors.Wrapf(err, "could not decode YAML for %s", nmStateConfigFilename) + } + + var staticNetworkConfig []*models.HostStaticNetworkConfig + var nmStateConfigList []*aiv1beta1.NMStateConfig + + for i := range yamlList { + nmStateConfig := yamlList[i].(*aiv1beta1.NMStateConfig) + staticNetworkConfig = append(staticNetworkConfig, &models.HostStaticNetworkConfig{ + MacInterfaceMap: buildMacInterfaceMap(*nmStateConfig), + NetworkYaml: string(nmStateConfig.Spec.NetConfig.Raw), + }) + nmStateConfigList = append(nmStateConfigList, nmStateConfig) + } + + log := logrus.New() + log.Level = logrus.WarnLevel + staticNetworkConfigGenerator := staticnetworkconfig.New(log.WithField("pkg", "manifests"), staticnetworkconfig.Config{MaxConcurrentGenerations: 2}) + + // Validate the network config using nmstatectl + if err = staticNetworkConfigGenerator.ValidateStaticConfigParams(context.Background(), staticNetworkConfig); err != nil { + return false, errors.Wrapf(err, "staticNetwork configuration is not valid") + } + + n.File, n.StaticNetworkConfig, n.Config = file, staticNetworkConfig, nmStateConfigList + if err = n.finish(); err != nil { + return false, err + } + return true, nil +} + +func (n *NMStateConfig) finish() error { + + if n.Config == nil { + return errors.New("missing configuration or manifest file") + } + + if err := n.validateNMStateConfig().ToAggregate(); err != nil { + return errors.Wrapf(err, "invalid NMStateConfig configuration") + } + return nil +} + +func (n *NMStateConfig) validateNMStateConfig() field.ErrorList { + allErrs := field.ErrorList{} + + if err := n.validateNMStateLabels(); err != nil { + allErrs = append(allErrs, err...) + } + + return allErrs +} + +func (n *NMStateConfig) validateNMStateLabels() field.ErrorList { + + var allErrs field.ErrorList + + fieldPath := field.NewPath("ObjectMeta", "Labels") + + for _, nmStateConfig := range n.Config { + if len(nmStateConfig.ObjectMeta.Labels) == 0 { + allErrs = append(allErrs, field.Required(fieldPath, fmt.Sprintf("%s does not have any label set", nmStateConfig.Name))) + } + } + + return allErrs +} + +func getFirstIP(nmStateConfig *nmStateConfig) string { + for _, intf := range nmStateConfig.Interfaces { + for _, addr4 := range intf.IPV4.Address { + if addr4.IP != "" { + return addr4.IP + } + } + for _, addr6 := range intf.IPV6.Address { + if addr6.IP != "" { + return addr6.IP + } + } + } + return "" +} + +// GetNodeZeroIP retrieves the first IP from the user provided NMStateConfigs to set as the node0 IP +func GetNodeZeroIP(nmStateConfigs []*aiv1beta1.NMStateConfig) (string, error) { + for i := range nmStateConfigs { + var nmStateConfig nmStateConfig + err := yaml.Unmarshal(nmStateConfigs[i].Spec.NetConfig.Raw, &nmStateConfig) + if err != nil { + return "", fmt.Errorf("error unmarshalling NMStateConfig: %v", err) + } + if nodeZeroIP := getFirstIP(&nmStateConfig); nodeZeroIP != "" { + if net.ParseIP(nodeZeroIP) == nil { + return "", fmt.Errorf("could not parse static IP: %s", nodeZeroIP) + } + + return nodeZeroIP, nil + } + + } + + return "", fmt.Errorf("invalid NMStateConfig yaml, no interface IPs set") +} + +// GetNMIgnitionFiles returns the list of NetworkManager configuration files +func GetNMIgnitionFiles(staticNetworkConfig []*models.HostStaticNetworkConfig) ([]staticnetworkconfig.StaticNetworkConfigData, error) { + log := logrus.New() + staticNetworkConfigGenerator := staticnetworkconfig.New(log.WithField("pkg", "manifests"), staticnetworkconfig.Config{MaxConcurrentGenerations: 2}) + + networkConfigStr, err := staticNetworkConfigGenerator.FormatStaticNetworkConfigForDB(staticNetworkConfig) + if err != nil { + err = fmt.Errorf("error marshalling StaticNetwork configuration: %w", err) + return nil, err + } + + filesList, err := staticNetworkConfigGenerator.GenerateStaticNetworkConfigData(context.Background(), networkConfigStr) + if err != nil { + err = fmt.Errorf("failed to create StaticNetwork config data: %w", err) + return nil, err + } + + return filesList, err +} + +type nmStateConfigYamlDecoder int + +type decodeFormat interface { + NewDecodedYaml(decoder *yaml.YAMLToJSONDecoder) (interface{}, error) +} + +func (d *nmStateConfigYamlDecoder) NewDecodedYaml(yamlDecoder *yaml.YAMLToJSONDecoder) (interface{}, error) { + decodedData := new(aiv1beta1.NMStateConfig) + err := yamlDecoder.Decode(&decodedData) + + return decodedData, err +} + +// Read a YAML file containing multiple YAML definitions of the same format +// Each specific format must be of type DecodeFormat +func getMultipleYamls(contents []byte, decoder decodeFormat) ([]interface{}, error) { + + r := bytes.NewReader(contents) + dec := yaml.NewYAMLToJSONDecoder(r) + + var outputList []interface{} + for { + decodedData, err := decoder.NewDecodedYaml(dec) + if errors.Is(err, io.EOF) { + break + } + if err != nil { + return nil, errors.Wrapf(err, "Error reading multiple YAMLs") + } + + outputList = append(outputList, decodedData) + } + + return outputList, nil +} + +func buildMacInterfaceMap(nmStateConfig aiv1beta1.NMStateConfig) models.MacInterfaceMap { + + // TODO - this eventually will move to another asset so the interface definition can be shared with Butane + macInterfaceMap := make(models.MacInterfaceMap, 0, len(nmStateConfig.Spec.Interfaces)) + for _, cfg := range nmStateConfig.Spec.Interfaces { + logrus.Debug("adding MAC interface map to host static network config - Name: ", cfg.Name, " MacAddress:", cfg.MacAddress) + macInterfaceMap = append(macInterfaceMap, &models.MacInterfaceMapItems0{ + MacAddress: cfg.MacAddress, + LogicalNicName: cfg.Name, + }) + } + return macInterfaceMap +} diff --git a/pkg/asset/agent/manifests/nmstateconfig_test.go b/pkg/asset/agent/manifests/nmstateconfig_test.go new file mode 100644 index 0000000000..f81f3d5f7e --- /dev/null +++ b/pkg/asset/agent/manifests/nmstateconfig_test.go @@ -0,0 +1,577 @@ +package manifests + +import ( + "errors" + "fmt" + "os" + "os/exec" + "testing" + + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + aiv1beta1 "github.com/openshift/assisted-service/api/v1beta1" + "github.com/openshift/assisted-service/models" + "github.com/openshift/installer/pkg/asset" + "github.com/openshift/installer/pkg/asset/agent/agentconfig" + "github.com/openshift/installer/pkg/asset/mock" +) + +func TestNMStateConfig_Generate(t *testing.T) { + + cases := []struct { + name string + dependencies []asset.Asset + expectedError string + expectedConfig []*aiv1beta1.NMStateConfig + }{ + { + name: "missing-config", + dependencies: []asset.Asset{ + &agentconfig.AgentConfig{}, + }, + expectedError: "missing configuration or manifest file", + }, + { + name: "valid config", + dependencies: []asset.Asset{ + getValidAgentConfig(), + }, + expectedConfig: []*aiv1beta1.NMStateConfig{ + { + TypeMeta: metav1.TypeMeta{ + Kind: "NMStateConfig", + APIVersion: "agent-install.openshift.io/v1beta1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprint(getNMStateConfigName(getValidAgentConfig()), "-0"), + Namespace: getNMStateConfigNamespace(getValidAgentConfig()), + Labels: getNMStateConfigLabelsFromAgentConfig(getValidAgentConfig()), + }, + Spec: aiv1beta1.NMStateConfigSpec{ + Interfaces: []*aiv1beta1.Interface{ + { + Name: "enp2s0", + MacAddress: "98:af:65:a5:8d:01", + }, + { + Name: "enp3s1", + MacAddress: "28:d2:44:d2:b2:1a", + }, + }, + NetConfig: aiv1beta1.NetConfig{ + Raw: unmarshalJSON([]byte("interfaces:")), + }, + }, + }, + { + TypeMeta: metav1.TypeMeta{ + Kind: "NMStateConfig", + APIVersion: "agent-install.openshift.io/v1beta1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprint(getNMStateConfigName(getValidAgentConfig()), "-1"), + Namespace: getNMStateConfigNamespace(getValidAgentConfig()), + Labels: getNMStateConfigLabelsFromAgentConfig(getValidAgentConfig()), + }, + Spec: aiv1beta1.NMStateConfigSpec{ + Interfaces: []*aiv1beta1.Interface{ + { + Name: "enp2t0", + MacAddress: "98:af:65:a5:8d:02", + }, + }, + NetConfig: aiv1beta1.NetConfig{ + Raw: unmarshalJSON([]byte("interfaces:")), + }, + }, + }, + { + TypeMeta: metav1.TypeMeta{ + Kind: "NMStateConfig", + APIVersion: "agent-install.openshift.io/v1beta1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprint(getNMStateConfigName(getValidAgentConfig()), "-2"), + Namespace: getNMStateConfigNamespace(getValidAgentConfig()), + Labels: getNMStateConfigLabelsFromAgentConfig(getValidAgentConfig()), + }, + Spec: aiv1beta1.NMStateConfigSpec{ + Interfaces: []*aiv1beta1.Interface{ + { + Name: "enp2u0", + MacAddress: "98:af:65:a5:8d:03", + }, + }, + NetConfig: aiv1beta1.NetConfig{ + Raw: unmarshalJSON([]byte("interfaces:")), + }, + }, + }, + }, + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + + parents := asset.Parents{} + parents.Add(tc.dependencies...) + + asset := &NMStateConfig{} + err := asset.Generate(parents) + + if tc.expectedError != "" { + assert.Equal(t, tc.expectedError, err.Error()) + } else { + assert.NoError(t, err) + assert.Equal(t, tc.expectedConfig, asset.Config) + assert.NotEmpty(t, asset.Files()) + + configFile := asset.Files()[0] + assert.Equal(t, "cluster-manifests/nmstateconfig.yaml", configFile.Filename) + + // Split up the file into multiple YAMLs if it contains NMStateConfig for more than one node + var decoder nmStateConfigYamlDecoder + yamlList, err := getMultipleYamls(configFile.Data, &decoder) + + assert.NoError(t, err) + assert.Equal(t, len(tc.expectedConfig), len(yamlList)) + + for i := range tc.expectedConfig { + assert.Equal(t, tc.expectedConfig[i], yamlList[i]) + + } + + assert.Equal(t, len(tc.expectedConfig), len(asset.StaticNetworkConfig)) + } + }) + } + +} + +func TestNMStateConfig_LoadedFromDisk(t *testing.T) { + + cases := []struct { + name string + data string + fetchError error + expectedFound bool + expectedError string + requiresNmstatectl bool + expectedConfig []*models.HostStaticNetworkConfig + }{ + { + name: "valid-config-file", + data: ` +metadata: + name: mynmstateconfig + namespace: spoke-cluster + labels: + cluster0-nmstate-label-name: cluster0-nmstate-label-value +spec: + config: + interfaces: + - name: eth0 + type: ethernet + state: up + mac-address: 52:54:01:aa:aa:a1 + ipv4: + enabled: true + address: + - ip: 192.168.122.21 + prefix-length: 24 + dhcp: false + dns-resolver: + config: + server: + - 192.168.122.1 + routes: + config: + - destination: 0.0.0.0/0 + next-hop-address: 192.168.122.1 + next-hop-interface: eth0 + table-id: 254 + interfaces: + - name: "eth0" + macAddress: "52:54:01:aa:aa:a1" + - name: "eth1" + macAddress: "52:54:01:bb:bb:b1"`, + requiresNmstatectl: true, + expectedFound: true, + expectedConfig: []*models.HostStaticNetworkConfig{ + { + MacInterfaceMap: models.MacInterfaceMap{ + {LogicalNicName: "eth0", MacAddress: "52:54:01:aa:aa:a1"}, + {LogicalNicName: "eth1", MacAddress: "52:54:01:bb:bb:b1"}, + }, + NetworkYaml: "dns-resolver:\n config:\n server:\n - 192.168.122.1\ninterfaces:\n- ipv4:\n address:\n - ip: 192.168.122.21\n prefix-length: 24\n dhcp: false\n enabled: true\n mac-address: 52:54:01:aa:aa:a1\n name: eth0\n state: up\n type: ethernet\nroutes:\n config:\n - destination: 0.0.0.0/0\n next-hop-address: 192.168.122.1\n next-hop-interface: eth0\n table-id: 254\n", + }, + }, + }, + + { + name: "valid-config-multiple-yamls", + data: ` +metadata: + name: mynmstateconfig + namespace: spoke-cluster + labels: + cluster0-nmstate-label-name: cluster0-nmstate-label-value +spec: + config: + interfaces: + - name: eth0 + type: ethernet + state: up + mac-address: 52:54:01:aa:aa:a1 + ipv4: + enabled: true + address: + - ip: 192.168.122.21 + prefix-length: 24 + interfaces: + - name: "eth0" + macAddress: "52:54:01:aa:aa:a1" +--- +metadata: + name: mynmstateconfig-2 + namespace: spoke-cluster + labels: + cluster0-nmstate-label-name: cluster0-nmstate-label-value +spec: + config: + interfaces: + - name: eth0 + type: ethernet + state: up + mac-address: 52:54:01:cc:cc:c1 + ipv4: + enabled: true + address: + - ip: 192.168.122.22 + prefix-length: 24 + interfaces: + - name: "eth0" + macAddress: "52:54:01:cc:cc:c1"`, + requiresNmstatectl: true, + expectedFound: true, + expectedConfig: []*models.HostStaticNetworkConfig{ + { + MacInterfaceMap: models.MacInterfaceMap{ + {LogicalNicName: "eth0", MacAddress: "52:54:01:aa:aa:a1"}, + }, + NetworkYaml: "interfaces:\n- ipv4:\n address:\n - ip: 192.168.122.21\n prefix-length: 24\n enabled: true\n mac-address: 52:54:01:aa:aa:a1\n name: eth0\n state: up\n type: ethernet\n", + }, + { + MacInterfaceMap: models.MacInterfaceMap{ + {LogicalNicName: "eth0", MacAddress: "52:54:01:cc:cc:c1"}, + }, + NetworkYaml: "interfaces:\n- ipv4:\n address:\n - ip: 192.168.122.22\n prefix-length: 24\n enabled: true\n mac-address: 52:54:01:cc:cc:c1\n name: eth0\n state: up\n type: ethernet\n", + }, + }, + }, + + { + name: "invalid-interfaces", + data: ` +metadata: + name: mynmstateconfig + namespace: spoke-cluster + labels: + cluster0-nmstate-label-name: cluster0-nmstate-label-value +spec: + interfaces: + - name: "eth0" + macAddress: "52:54:01:aa:aa:a1" + - name: "eth0" + macAddress: "52:54:01:bb:bb:b1"`, + requiresNmstatectl: true, + expectedError: "staticNetwork configuration is not valid", + }, + + { + name: "invalid-address-for-type", + data: ` +metadata: + name: mynmstateconfig + namespace: spoke-cluster + labels: + cluster0-nmstate-label-name: cluster0-nmstate-label-value +spec: + config: + interfaces: + - name: eth0 + type: ethernet + state: up + mac-address: 52:54:01:aa:aa:a1 + ipv6: + enabled: true + address: + - ip: 192.168.122.21 + prefix-length: 24 + interfaces: + - name: "eth0" + macAddress: "52:54:01:aa:aa:a1"`, + requiresNmstatectl: true, + expectedError: "staticNetwork configuration is not valid", + }, + + { + name: "missing-label", + data: ` +metadata: + name: mynmstateconfig + namespace: spoke-cluster +spec: + config: + interfaces: + - name: eth0 + type: ethernet + state: up + mac-address: 52:54:01:aa:aa:a1 + ipv4: + enabled: true + address: + - ip: 192.168.122.21 + prefix-length: 24 + interfaces: + - name: "eth0" + macAddress: "52:54:01:aa:aa:a1"`, + expectedError: "invalid NMStateConfig configuration: ObjectMeta.Labels: Required value: mynmstateconfig does not have any label set", + }, + + { + name: "not-yaml", + data: `This is not a yaml file`, + expectedError: "could not decode YAML for cluster-manifests/nmstateconfig.yaml: Error reading multiple YAMLs: error unmarshaling JSON: while decoding JSON: json: cannot unmarshal string into Go value of type v1beta1.NMStateConfig", + }, + { + name: "file-not-found", + fetchError: &os.PathError{Err: os.ErrNotExist}, + }, + { + name: "error-fetching-file", + fetchError: errors.New("fetch failed"), + expectedError: "failed to load file cluster-manifests/nmstateconfig.yaml: fetch failed", + }, + } + for _, tc := range cases { + // nmstate may not be installed yet in CI so skip this test if not + if tc.requiresNmstatectl { + _, execErr := exec.LookPath("nmstatectl") + if execErr != nil { + continue + } + } + + t.Run(tc.name, func(t *testing.T) { + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + fileFetcher := mock.NewMockFileFetcher(mockCtrl) + fileFetcher.EXPECT().FetchByName(nmStateConfigFilename). + Return( + &asset.File{ + Filename: nmStateConfigFilename, + Data: []byte(tc.data)}, + tc.fetchError, + ) + + asset := &NMStateConfig{} + found, err := asset.Load(fileFetcher) + assert.Equal(t, tc.expectedFound, found, "unexpected found value returned from Load") + if tc.expectedError != "" { + assert.ErrorContains(t, err, tc.expectedError) + } else { + assert.NoError(t, err) + } + if tc.expectedFound { + assert.Equal(t, tc.expectedConfig, asset.StaticNetworkConfig, "unexpected Config in NMStateConfig") + assert.Equal(t, len(tc.expectedConfig), len(asset.Config)) + for i := 0; i < len(tc.expectedConfig); i++ { + + staticNetworkConfig := asset.StaticNetworkConfig[i] + nmStateConfig := asset.Config[i] + + for n := 0; n < len(staticNetworkConfig.MacInterfaceMap); n++ { + macInterfaceMap := staticNetworkConfig.MacInterfaceMap[n] + iface := nmStateConfig.Spec.Interfaces[n] + + assert.Equal(t, macInterfaceMap.LogicalNicName, iface.Name) + assert.Equal(t, macInterfaceMap.MacAddress, iface.MacAddress) + } + assert.YAMLEq(t, staticNetworkConfig.NetworkYaml, string(nmStateConfig.Spec.NetConfig.Raw)) + } + + } + }) + } +} + +func TestGetNodeZeroIP(t *testing.T) { + cases := []struct { + name string + expectedIP string + expectedError string + configs []string + }{ + { + name: "no interfaces", + expectedError: "no interface IPs set", + }, + { + name: "first interface", + expectedIP: "192.168.122.21", + configs: []string{ + ` +interfaces: + - name: eth0 + type: ethernet + ipv4: + address: + - ip: 192.168.122.21 + - name: eth1 + type: ethernet + ipv4: + address: + - ip: 192.168.122.22 +`, + }, + }, + { + name: "second interface", + expectedIP: "192.168.122.22", + configs: []string{ + ` +interfaces: + - name: eth0 + type: ethernet + - name: eth1 + type: ethernet + ipv4: + address: + - ip: 192.168.122.22 +`, + }, + }, + { + name: "second host", + expectedIP: "192.168.122.22", + configs: []string{ + ` +interfaces: + - name: eth0 + type: ethernet + - name: eth1 + type: ethernet +`, + ` +interfaces: + - name: eth0 + type: ethernet + - name: eth1 + type: ethernet + ipv4: + address: + - ip: 192.168.122.22 +`, + }, + }, + { + name: "ipv4 first", + expectedIP: "192.168.122.22", + configs: []string{ + ` +interfaces: + - name: eth0 + type: ethernet + ipv6: + address: + - ip: "2001:0db8::0001" + ipv4: + address: + - ip: 192.168.122.22 +`, + }, + }, + { + name: "ipv6 host first", + expectedIP: "2001:0db8::0001", + configs: []string{ + ` +interfaces: + - name: eth0 + type: ethernet + ipv6: + address: + - ip: "2001:0db8::0001" +`, + ` +interfaces: + - name: eth0 + type: ethernet + ipv4: + address: + - ip: 192.168.122.31 +`, + }, + }, + { + name: "ipv6 first", + expectedIP: "2001:0db8::0001", + configs: []string{ + ` +interfaces: + - name: eth0 + type: ethernet + ipv6: + address: + - ip: "2001:0db8::0001" + - name: eth1 + type: ethernet + ipv4: + address: + - ip: 192.168.122.22 +`, + }, + }, + { + name: "ipv6", + expectedIP: "2001:0db8::0001", + configs: []string{ + ` +interfaces: + - name: eth0 + type: ethernet + ipv6: + address: + - ip: "2001:0db8::0001" +`, + }, + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + var configs []*aiv1beta1.NMStateConfig + for _, hostRaw := range tc.configs { + configs = append(configs, &aiv1beta1.NMStateConfig{ + Spec: aiv1beta1.NMStateConfigSpec{ + NetConfig: aiv1beta1.NetConfig{ + Raw: aiv1beta1.RawNetConfig(hostRaw), + }, + }, + }) + } + + ip, err := GetNodeZeroIP(configs) + if tc.expectedError == "" { + assert.NoError(t, err) + assert.Equal(t, tc.expectedIP, ip) + } else { + assert.ErrorContains(t, err, tc.expectedError) + } + }) + } +} diff --git a/pkg/asset/agent/manifests/util_test.go b/pkg/asset/agent/manifests/util_test.go new file mode 100644 index 0000000000..76a0494c0e --- /dev/null +++ b/pkg/asset/agent/manifests/util_test.go @@ -0,0 +1,177 @@ +package manifests + +import ( + "net" + + "github.com/openshift/assisted-service/api/v1beta1" + "github.com/openshift/installer/pkg/asset/agent" + "github.com/openshift/installer/pkg/asset/agent/agentconfig" + "github.com/openshift/installer/pkg/asset/installconfig" + "github.com/openshift/installer/pkg/ipnet" + "github.com/openshift/installer/pkg/types" + agenttypes "github.com/openshift/installer/pkg/types/agent" + "github.com/openshift/installer/pkg/types/baremetal" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/pointer" + "sigs.k8s.io/yaml" +) + +var ( + // TestSSHKey provides a ssh key for unit tests + TestSSHKey = `| + ssh-rsa AAAAB3NzaC1y1LJe3zew1ghc= root@localhost.localdomain` + // TestSecret provides a ssh key for unit tests + TestSecret = `'{"auths":{"cloud.openshift.com":{"auth":"b3BlUTA=","email":"test@redhat.com"}}}` + // TestReleaseImage provides a release image url for unit tests + TestReleaseImage = "registry.ci.openshift.org/origin/release:4.11" +) + +// GetValidOptionalInstallConfig returns a valid optional install config +func getValidOptionalInstallConfig() *agent.OptionalInstallConfig { + _, newCidr, _ := net.ParseCIDR("192.168.111.0/24") + + return &agent.OptionalInstallConfig{ + InstallConfig: installconfig.InstallConfig{ + Config: &types.InstallConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ocp-edge-cluster-0", + Namespace: "cluster-0", + }, + BaseDomain: "testing.com", + PullSecret: TestSecret, + SSHKey: TestSSHKey, + ControlPlane: &types.MachinePool{ + Name: "master", + Replicas: pointer.Int64Ptr(3), + Platform: types.MachinePoolPlatform{}, + }, + Compute: []types.MachinePool{ + { + Name: "worker-machine-pool-1", + Replicas: pointer.Int64Ptr(2), + }, + { + Name: "worker-machine-pool-2", + Replicas: pointer.Int64Ptr(3), + }, + }, + Networking: &types.Networking{ + ClusterNetwork: []types.ClusterNetworkEntry{ + { + CIDR: ipnet.IPNet{IPNet: *newCidr}, + HostPrefix: 23, + }, + }, + ServiceNetwork: []ipnet.IPNet{ + *ipnet.MustParseCIDR("172.30.0.0/16"), + }, + }, + Platform: types.Platform{ + BareMetal: &baremetal.Platform{ + APIVIP: "192.168.122.10", + IngressVIP: "192.168.122.11", + }, + }, + }, + }, + Supplied: true, + } +} + +func getValidAgentConfig() *agentconfig.AgentConfig { + return &agentconfig.AgentConfig{ + Config: &agenttypes.Config{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ocp-edge-cluster-0", + Namespace: "cluster-0", + }, + RendezvousIP: "192.168.122.2", + Hosts: []agenttypes.Host{ + { + Hostname: "control-0.example.org", + Role: "master", + RootDeviceHints: baremetal.RootDeviceHints{ + DeviceName: "/dev/sda", + HCTL: "hctl-value", + Model: "model-value", + Vendor: "vendor-value", + SerialNumber: "serial-number-value", + MinSizeGigabytes: 20, + WWN: "wwn-value", + WWNWithExtension: "wwn-with-extension-value", + WWNVendorExtension: "wwn-vendor-extension-value", + Rotational: new(bool), + }, + Interfaces: []*v1beta1.Interface{ + { + Name: "enp2s0", + MacAddress: "98:af:65:a5:8d:01", + }, + { + Name: "enp3s1", + MacAddress: "28:d2:44:d2:b2:1a", + }, + }, + NetworkConfig: v1beta1.NetConfig{ + Raw: unmarshalJSON([]byte("interfaces:")), + }, + }, + { + Hostname: "control-1.example.org", + Role: "master", + RootDeviceHints: baremetal.RootDeviceHints{ + DeviceName: "/dev/sdb", + HCTL: "hctl-value", + Model: "model-value", + Vendor: "vendor-value", + SerialNumber: "serial-number-value", + MinSizeGigabytes: 40, + WWN: "wwn-value", + WWNWithExtension: "wwn-with-extension-value", + WWNVendorExtension: "wwn-vendor-extension-value", + Rotational: new(bool), + }, + Interfaces: []*v1beta1.Interface{ + { + Name: "enp2t0", + MacAddress: "98:af:65:a5:8d:02", + }, + }, + NetworkConfig: v1beta1.NetConfig{ + Raw: unmarshalJSON([]byte("interfaces:")), + }, + }, + { + Hostname: "control-2.example.org", + Role: "master", + RootDeviceHints: baremetal.RootDeviceHints{ + DeviceName: "/dev/sdc", + HCTL: "hctl-value", + Model: "model-value", + Vendor: "vendor-value", + SerialNumber: "serial-number-value", + MinSizeGigabytes: 60, + WWN: "wwn-value", + WWNWithExtension: "wwn-with-extension-value", + WWNVendorExtension: "wwn-vendor-extension-value", + Rotational: new(bool), + }, + Interfaces: []*v1beta1.Interface{ + { + Name: "enp2u0", + MacAddress: "98:af:65:a5:8d:03", + }, + }, + NetworkConfig: v1beta1.NetConfig{ + Raw: unmarshalJSON([]byte("interfaces:")), + }, + }, + }, + }, + } +} + +func unmarshalJSON(b []byte) []byte { + output, _ := yaml.JSONToYAML(b) + return output +} diff --git a/pkg/asset/agent/mirror/cabundle.go b/pkg/asset/agent/mirror/cabundle.go new file mode 100644 index 0000000000..1387da47d4 --- /dev/null +++ b/pkg/asset/agent/mirror/cabundle.go @@ -0,0 +1,102 @@ +package mirror + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/openshift/installer/pkg/asset" + "github.com/openshift/installer/pkg/asset/agent" + "github.com/openshift/installer/pkg/asset/manifests" + "github.com/pkg/errors" +) + +var ( + // CaBundleFilename defines the name of the file on disk + CaBundleFilename = filepath.Join(mirrorConfigDir, "ca-bundle.crt") +) + +// CaBundle generates the cetificate file for disconnected mirrors. +type CaBundle struct { + File *asset.File +} + +var _ asset.WritableAsset = (*CaBundle)(nil) + +// Name returns a human friendly name for the asset. +func (*CaBundle) Name() string { + return "Mirror Registries Certificate File" +} + +// Dependencies returns all of the dependencies directly needed to generate +// the asset. +func (*CaBundle) Dependencies() []asset.Asset { + return []asset.Asset{ + &agent.OptionalInstallConfig{}, + } +} + +// Generate generates the Mirror Registries certificate file from install-config. +func (i *CaBundle) Generate(dependencies asset.Parents) error { + installConfig := &agent.OptionalInstallConfig{} + dependencies.Get(installConfig) + if !installConfig.Supplied { + return nil + } + + if installConfig.Config.AdditionalTrustBundle == "" { + i.File = &asset.File{ + Filename: CaBundleFilename, + Data: []byte{}, + } + return nil + } + + return i.parseCertificates(installConfig.Config.AdditionalTrustBundle) +} + +func (i *CaBundle) parseCertificates(certs string) error { + if len(certs) == 0 { + return nil + } + + data, err := manifests.ParseCertificates(certs) + if err != nil { + return err + } + + for filename, content := range data { + if filepath.Base(CaBundleFilename) == filename { + i.File = &asset.File{ + Filename: CaBundleFilename, + Data: []byte(content), + } + } else { + return fmt.Errorf("unexpected CA Bundle filename %s", filename) + } + } + + return nil +} + +// Files returns the files generated by the asset. +func (i *CaBundle) Files() []*asset.File { + if i.File != nil { + return []*asset.File{i.File} + } + return []*asset.File{} +} + +// Load returns the Mirror Registries certificate file from the disk. +func (i *CaBundle) Load(f asset.FileFetcher) (bool, error) { + + file, err := f.FetchByName(CaBundleFilename) + if err != nil { + if os.IsNotExist(err) { + return false, nil + } + return false, errors.Wrap(err, fmt.Sprintf("failed to load %s file", CaBundleFilename)) + } + + return true, i.parseCertificates(string(file.Data)) +} diff --git a/pkg/asset/agent/mirror/cabundle_test.go b/pkg/asset/agent/mirror/cabundle_test.go new file mode 100644 index 0000000000..61b9d1ec0b --- /dev/null +++ b/pkg/asset/agent/mirror/cabundle_test.go @@ -0,0 +1,230 @@ +package mirror + +import ( + "errors" + "os" + "testing" + + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/openshift/installer/pkg/asset" + "github.com/openshift/installer/pkg/asset/agent" + "github.com/openshift/installer/pkg/asset/installconfig" + "github.com/openshift/installer/pkg/asset/mock" + "github.com/openshift/installer/pkg/types" +) + +func TestCaBundle_Generate(t *testing.T) { + + cases := []struct { + name string + dependencies []asset.Asset + expectedError string + expectedConfig string + }{ + { + name: "missing-config", + dependencies: []asset.Asset{ + &agent.OptionalInstallConfig{}, + }, + }, + { + name: "default", + dependencies: []asset.Asset{ + &agent.OptionalInstallConfig{ + Supplied: true, + InstallConfig: installconfig.InstallConfig{ + Config: &types.InstallConfig{ + ObjectMeta: v1.ObjectMeta{ + Namespace: "cluster-0", + }, + }, + }, + }, + }, + }, + { + name: "additional-trust-bundle", + dependencies: []asset.Asset{ + &agent.OptionalInstallConfig{ + Supplied: true, + InstallConfig: installconfig.InstallConfig{ + Config: &types.InstallConfig{ + ObjectMeta: v1.ObjectMeta{ + Namespace: "cluster-0", + }, + AdditionalTrustBundle: ` +-----BEGIN CERTIFICATE----- +MIIDZTCCAk2gAwIBAgIURbA8lR+5xlJZUoOXK66AHFWd3uswDQYJKoZIhvcNAQEL +BQAwQjELMAkGA1UEBhMCWFgxFTATBgNVBAcMDERlZmF1bHQgQ2l0eTEcMBoGA1UE +CgwTRGVmYXVsdCBDb21wYW55IEx0ZDAeFw0yMjA3MDgxOTUzMTVaFw0yMjA4MDcx +OTUzMTVaMEIxCzAJBgNVBAYTAlhYMRUwEwYDVQQHDAxEZWZhdWx0IENpdHkxHDAa +BgNVBAoME0RlZmF1bHQgQ29tcGFueSBMdGQwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQCroH9c2PLWI0O/nBrmKtS2IuReyWaR0DOMJY7C/vc12l9zlH0D +xTOUfEtdqRktjVsUn1vIIiFakxd0QLIPcMyKplmbavIBUQp+MZr0pNVX+lwcctbA +7FVHEnbWYNVepoV7kZkTVvMXAqFylMXU4gDmuZzIxhVMMxjialJNED+3ngqvX4w3 +4q4KSk1ytaHGwjREIErwPJjv5PK48KVJL2nlCuA+tbxu1r8eVkOUvZlxAuNNXk/U +mf3QX5EiUlTtsmRAct6fIUT3jkrsHSS/tZ66EYJ9Q0OBoX2lL/Msmi27OQvA7uYn +uqYlwJzU43tCsiip9E9z/UrLcMYyXx3oPJyPAgMBAAGjUzBRMB0GA1UdDgQWBBTI +ahE8DDT4T1vta6cXVVaRjnel0zAfBgNVHSMEGDAWgBTIahE8DDT4T1vta6cXVVaR +jnel0zAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCQbsMtPFkq +PxwOAIds3IoupuyIKmsF32ECEH/OlS+7Sj7MUJnGTQrwgjrsVS5sl8AmnGx4hPdL +VX98nEcKMNkph3Hkvh4EvgjSfmYGUXuJBcYU5jqNQrlrGv37rEf5FnvdHV1F3MG8 +A0Mj0TLtcTdtaJFoOrnQuD/k0/1d+cMiYGTSaT5XK/unARqGEMd4BlWPh5P3SflV +/Vy2hHlMpv7OcZ8yaAI3htENZLus+L5kjHWKu6dxlPHKu6ef5k64su2LTNE07Vr9 +S655uiFW5AX2wDVUcQEDCOiEn6SI9DTt5oQjWPMxPf+rEyfQ2f1QwVez7cyr6Qc5 +OIUk31HnM/Fj +-----END CERTIFICATE----- +`, + }, + }, + }, + }, + expectedConfig: `-----BEGIN CERTIFICATE----- +MIIDZTCCAk2gAwIBAgIURbA8lR+5xlJZUoOXK66AHFWd3uswDQYJKoZIhvcNAQEL +BQAwQjELMAkGA1UEBhMCWFgxFTATBgNVBAcMDERlZmF1bHQgQ2l0eTEcMBoGA1UE +CgwTRGVmYXVsdCBDb21wYW55IEx0ZDAeFw0yMjA3MDgxOTUzMTVaFw0yMjA4MDcx +OTUzMTVaMEIxCzAJBgNVBAYTAlhYMRUwEwYDVQQHDAxEZWZhdWx0IENpdHkxHDAa +BgNVBAoME0RlZmF1bHQgQ29tcGFueSBMdGQwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQCroH9c2PLWI0O/nBrmKtS2IuReyWaR0DOMJY7C/vc12l9zlH0D +xTOUfEtdqRktjVsUn1vIIiFakxd0QLIPcMyKplmbavIBUQp+MZr0pNVX+lwcctbA +7FVHEnbWYNVepoV7kZkTVvMXAqFylMXU4gDmuZzIxhVMMxjialJNED+3ngqvX4w3 +4q4KSk1ytaHGwjREIErwPJjv5PK48KVJL2nlCuA+tbxu1r8eVkOUvZlxAuNNXk/U +mf3QX5EiUlTtsmRAct6fIUT3jkrsHSS/tZ66EYJ9Q0OBoX2lL/Msmi27OQvA7uYn +uqYlwJzU43tCsiip9E9z/UrLcMYyXx3oPJyPAgMBAAGjUzBRMB0GA1UdDgQWBBTI +ahE8DDT4T1vta6cXVVaRjnel0zAfBgNVHSMEGDAWgBTIahE8DDT4T1vta6cXVVaR +jnel0zAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCQbsMtPFkq +PxwOAIds3IoupuyIKmsF32ECEH/OlS+7Sj7MUJnGTQrwgjrsVS5sl8AmnGx4hPdL +VX98nEcKMNkph3Hkvh4EvgjSfmYGUXuJBcYU5jqNQrlrGv37rEf5FnvdHV1F3MG8 +A0Mj0TLtcTdtaJFoOrnQuD/k0/1d+cMiYGTSaT5XK/unARqGEMd4BlWPh5P3SflV +/Vy2hHlMpv7OcZ8yaAI3htENZLus+L5kjHWKu6dxlPHKu6ef5k64su2LTNE07Vr9 +S655uiFW5AX2wDVUcQEDCOiEn6SI9DTt5oQjWPMxPf+rEyfQ2f1QwVez7cyr6Qc5 +OIUk31HnM/Fj +-----END CERTIFICATE----- +`, + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + + parents := asset.Parents{} + parents.Add(tc.dependencies...) + + asset := &CaBundle{} + err := asset.Generate(parents) + + if tc.expectedError != "" { + assert.EqualError(t, err, tc.expectedError) + } else { + assert.NoError(t, err) + + files := asset.Files() + if tc.expectedConfig != "" { + assert.Len(t, files, 1) + assert.Equal(t, CaBundleFilename, files[0].Filename) + assert.Equal(t, tc.expectedConfig, string(files[0].Data)) + } else { + if len(files) == 1 { + assert.Equal(t, CaBundleFilename, files[0].Filename) + assert.Equal(t, []byte{}, files[0].Data) + } else { + assert.Empty(t, files) + } + } + } + }) + } +} + +func TestCaBundle_LoadedFromDisk(t *testing.T) { + + cases := []struct { + name string + data string + fetchError error + expectedFound bool + expectedError string + }{ + { + name: "valid-config-file", + data: ` +-----BEGIN CERTIFICATE----- +MIIDZTCCAk2gAwIBAgIURbA8lR+5xlJZUoOXK66AHFWd3uswDQYJKoZIhvcNAQEL +BQAwQjELMAkGA1UEBhMCWFgxFTATBgNVBAcMDERlZmF1bHQgQ2l0eTEcMBoGA1UE +CgwTRGVmYXVsdCBDb21wYW55IEx0ZDAeFw0yMjA3MDgxOTUzMTVaFw0yMjA4MDcx +OTUzMTVaMEIxCzAJBgNVBAYTAlhYMRUwEwYDVQQHDAxEZWZhdWx0IENpdHkxHDAa +BgNVBAoME0RlZmF1bHQgQ29tcGFueSBMdGQwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQCroH9c2PLWI0O/nBrmKtS2IuReyWaR0DOMJY7C/vc12l9zlH0D +xTOUfEtdqRktjVsUn1vIIiFakxd0QLIPcMyKplmbavIBUQp+MZr0pNVX+lwcctbA +7FVHEnbWYNVepoV7kZkTVvMXAqFylMXU4gDmuZzIxhVMMxjialJNED+3ngqvX4w3 +4q4KSk1ytaHGwjREIErwPJjv5PK48KVJL2nlCuA+tbxu1r8eVkOUvZlxAuNNXk/U +mf3QX5EiUlTtsmRAct6fIUT3jkrsHSS/tZ66EYJ9Q0OBoX2lL/Msmi27OQvA7uYn +uqYlwJzU43tCsiip9E9z/UrLcMYyXx3oPJyPAgMBAAGjUzBRMB0GA1UdDgQWBBTI +ahE8DDT4T1vta6cXVVaRjnel0zAfBgNVHSMEGDAWgBTIahE8DDT4T1vta6cXVVaR +jnel0zAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCQbsMtPFkq +PxwOAIds3IoupuyIKmsF32ECEH/OlS+7Sj7MUJnGTQrwgjrsVS5sl8AmnGx4hPdL +VX98nEcKMNkph3Hkvh4EvgjSfmYGUXuJBcYU5jqNQrlrGv37rEf5FnvdHV1F3MG8 +A0Mj0TLtcTdtaJFoOrnQuD/k0/1d+cMiYGTSaT5XK/unARqGEMd4BlWPh5P3SflV +/Vy2hHlMpv7OcZ8yaAI3htENZLus+L5kjHWKu6dxlPHKu6ef5k64su2LTNE07Vr9 +S655uiFW5AX2wDVUcQEDCOiEn6SI9DTt5oQjWPMxPf+rEyfQ2f1QwVez7cyr6Qc5 +OIUk31HnM/Fj +-----END CERTIFICATE----- +`, + expectedFound: true, + expectedError: "", + }, + { + name: "invalid-config-file", + data: ` +-----BEGIN CERTIFICATE----- +nope +-----END CERTIFICATE----- +`, + expectedFound: true, + expectedError: "x509: malformed certificate", + }, + { + name: "empty", + data: "", + expectedFound: true, + expectedError: "", + }, + { + name: "file-not-found", + fetchError: &os.PathError{Err: os.ErrNotExist}, + }, + { + name: "error-fetching-file", + fetchError: errors.New("fetch failed"), + expectedError: "failed to load mirror/ca-bundle.crt file: fetch failed", + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + fileFetcher := mock.NewMockFileFetcher(mockCtrl) + fileFetcher.EXPECT().FetchByName(CaBundleFilename). + Return( + &asset.File{ + Filename: CaBundleFilename, + Data: []byte(tc.data)}, + tc.fetchError, + ) + + asset := &CaBundle{} + found, err := asset.Load(fileFetcher) + assert.Equal(t, tc.expectedFound, found, "unexpected found value returned from Load") + if tc.expectedError != "" { + assert.Equal(t, tc.expectedError, err.Error()) + } else { + assert.NoError(t, err) + } + }) + } + +} diff --git a/pkg/asset/agent/mirror/mirror.go b/pkg/asset/agent/mirror/mirror.go new file mode 100644 index 0000000000..4971171b84 --- /dev/null +++ b/pkg/asset/agent/mirror/mirror.go @@ -0,0 +1,3 @@ +package mirror + +const mirrorConfigDir = "mirror" diff --git a/pkg/asset/agent/mirror/registriesconf.go b/pkg/asset/agent/mirror/registriesconf.go new file mode 100644 index 0000000000..829f7f1e30 --- /dev/null +++ b/pkg/asset/agent/mirror/registriesconf.go @@ -0,0 +1,228 @@ +package mirror + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/containers/image/pkg/sysregistriesv2" + "github.com/openshift/installer/pkg/asset" + "github.com/openshift/installer/pkg/asset/agent" + "github.com/openshift/installer/pkg/asset/ignition/bootstrap" + "github.com/pelletier/go-toml" + "github.com/pkg/errors" +) + +var ( + // RegistriesConfFilename defines the name of the file on disk + RegistriesConfFilename = filepath.Join(mirrorConfigDir, "registries.conf") +) + +// The default registries.conf file is the podman default as it appears in +// CoreOS, with no unqualified-search-registries. +const defaultRegistriesConf = ` +# NOTE: RISK OF USING UNQUALIFIED IMAGE NAMES +# We recommend always using fully qualified image names including the registry +# server (full dns name), namespace, image name, and tag +# (e.g., registry.redhat.io/ubi8/ubi:latest). Pulling by digest (i.e., +# quay.io/repository/name@digest) further eliminates the ambiguity of tags. +# When using short names, there is always an inherent risk that the image being +# pulled could be spoofed. For example, a user wants to pull an image named +# 'foobar' from a registry and expects it to come from myregistry.com. If +# myregistry.com is not first in the search list, an attacker could place a +# different 'foobar' image at a registry earlier in the search list. The user +# would accidentally pull and run the attacker's image and code rather than the +# intended content. We recommend only adding registries which are completely +# trusted (i.e., registries which don't allow unknown or anonymous users to +# create accounts with arbitrary names). This will prevent an image from being +# spoofed, squatted or otherwise made insecure. If it is necessary to use one +# of these registries, it should be added at the end of the list. +# +# # An array of host[:port] registries to try when pulling an unqualified image, in order. + +unqualified-search-registries = [] + +# [[registry]] +# # The "prefix" field is used to choose the relevant [[registry]] TOML table; +# # (only) the TOML table with the longest match for the input image name +# # (taking into account namespace/repo/tag/digest separators) is used. +# # +# # The prefix can also be of the form: *.example.com for wildcard subdomain +# # matching. +# # +# # If the prefix field is missing, it defaults to be the same as the "location" field. +# prefix = "example.com/foo" +# +# # If true, unencrypted HTTP as well as TLS connections with untrusted +# # certificates are allowed. +# insecure = false +# +# # If true, pulling images with matching names is forbidden. +# blocked = false +# +# # The physical location of the "prefix"-rooted namespace. +# # +# # By default, this is equal to "prefix" (in which case "prefix" can be omitted +# # and the [[registry]] TOML table can only specify "location"). +# # +# # Example: Given +# # prefix = "example.com/foo" +# # location = "internal-registry-for-example.net/bar" +# # requests for the image example.com/foo/myimage:latest will actually work with the +# # internal-registry-for-example.net/bar/myimage:latest image. +# +# # The location can be empty iff prefix is in a +# # wildcarded format: "*.example.com". In this case, the input reference will +# # be used as-is without any rewrite. +# location = internal-registry-for-example.com/bar" +# +# # (Possibly-partial) mirrors for the "prefix"-rooted namespace. +# # +# # The mirrors are attempted in the specified order; the first one that can be +# # contacted and contains the image will be used (and if none of the mirrors contains the image, +# # the primary location specified by the "registry.location" field, or using the unmodified +# # user-specified reference, is tried last). +# # +# # Each TOML table in the "mirror" array can contain the following fields, with the same semantics +# # as if specified in the [[registry]] TOML table directly: +# # - location +# # - insecure +# [[registry.mirror]] +# location = "example-mirror-0.local/mirror-for-foo" +# [[registry.mirror]] +# location = "example-mirror-1.local/mirrors/foo" +# insecure = true +# # Given the above, a pull of example.com/foo/image:latest will try: +# # 1. example-mirror-0.local/mirror-for-foo/image:latest +# # 2. example-mirror-1.local/mirrors/foo/image:latest +# # 3. internal-registry-for-example.net/bar/image:latest +# # in order, and use the first one that exists. +` + +// RegistriesConf generates the registries.conf file. +type RegistriesConf struct { + File *asset.File + MirrorConfig []RegistriesConfig +} + +// RegistriesConfig holds the data extracted from registries.conf +type RegistriesConfig struct { + Location string + Mirror string +} + +var _ asset.WritableAsset = (*RegistriesConf)(nil) + +// Name returns a human friendly name for the asset. +func (*RegistriesConf) Name() string { + return "Mirror Registries Config" +} + +// Dependencies returns all of the dependencies directly needed to generate +// the asset. +func (*RegistriesConf) Dependencies() []asset.Asset { + return []asset.Asset{ + &agent.OptionalInstallConfig{}, + } +} + +// Generate generates the registries.conf file from install-config. +func (i *RegistriesConf) Generate(dependencies asset.Parents) error { + installConfig := &agent.OptionalInstallConfig{} + dependencies.Get(installConfig) + if !installConfig.Supplied || len(installConfig.Config.ImageContentSources) == 0 { + i.File = &asset.File{ + Filename: RegistriesConfFilename, + Data: []byte(defaultRegistriesConf), + } + return i.finish() + } + + registries := sysregistriesv2.V2RegistriesConf{ + Registries: []sysregistriesv2.Registry{}, + } + for _, group := range bootstrap.MergedMirrorSets(installConfig.Config.ImageContentSources) { + if len(group.Mirrors) == 0 { + continue + } + + registry := sysregistriesv2.Registry{} + registry.Endpoint.Location = group.Source + registry.MirrorByDigestOnly = true + for _, mirror := range group.Mirrors { + registry.Mirrors = append(registry.Mirrors, sysregistriesv2.Endpoint{Location: mirror}) + } + registries.Registries = append(registries.Registries, registry) + } + + data, err := toml.Marshal(registries) + if err != nil { + return err + } + + i.File = &asset.File{ + Filename: RegistriesConfFilename, + Data: data, + } + + return i.finish() +} + +// Files returns the files generated by the asset. +func (i *RegistriesConf) Files() []*asset.File { + if i.File != nil { + return []*asset.File{i.File} + } + return []*asset.File{} +} + +// Load returns RegistriesConf asset from the disk. +func (i *RegistriesConf) Load(f asset.FileFetcher) (bool, error) { + + file, err := f.FetchByName(RegistriesConfFilename) + if err != nil { + if os.IsNotExist(err) { + return false, nil + } + return false, errors.Wrap(err, fmt.Sprintf("failed to load %s file", RegistriesConfFilename)) + } + + i.File = file + + if err = i.finish(); err != nil { + return false, err + } + + return true, nil +} + +func (i *RegistriesConf) finish() error { + + config, err := extractLocationMirrorDataFromRegistries(i.File.Data) + if err != nil { + return errors.Wrap(err, fmt.Sprintf("failed to parse mirrors in %s", RegistriesConfFilename)) + } + + i.MirrorConfig = config + + return nil +} + +// From assisted-service pkg/mirrorregistries/generator.go +func extractLocationMirrorDataFromRegistries(registriesConfToml []byte) ([]RegistriesConfig, error) { + registries := sysregistriesv2.V2RegistriesConf{} + err := toml.Unmarshal(registriesConfToml, ®istries) + if err != nil { + return nil, err + } + + registriesConfList := make([]RegistriesConfig, len(registries.Registries)) + for i, reg := range registries.Registries { + registriesConfList[i] = RegistriesConfig{ + Location: reg.Location, + Mirror: reg.Mirrors[0].Location, + } + } + + return registriesConfList, nil +} diff --git a/pkg/asset/agent/mirror/registriesconf_test.go b/pkg/asset/agent/mirror/registriesconf_test.go new file mode 100644 index 0000000000..cc335cef8a --- /dev/null +++ b/pkg/asset/agent/mirror/registriesconf_test.go @@ -0,0 +1,184 @@ +package mirror + +import ( + "errors" + "os" + "testing" + + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/openshift/installer/pkg/asset" + "github.com/openshift/installer/pkg/asset/agent" + "github.com/openshift/installer/pkg/asset/installconfig" + "github.com/openshift/installer/pkg/asset/mock" + "github.com/openshift/installer/pkg/types" +) + +func TestRegistriesConf_Generate(t *testing.T) { + + cases := []struct { + name string + dependencies []asset.Asset + expectedError string + expectedConfig string + }{ + { + name: "missing-config", + dependencies: []asset.Asset{ + &agent.OptionalInstallConfig{}, + }, + expectedConfig: defaultRegistriesConf, + }, + { + name: "default", + dependencies: []asset.Asset{ + &agent.OptionalInstallConfig{ + Supplied: true, + InstallConfig: installconfig.InstallConfig{ + Config: &types.InstallConfig{ + ObjectMeta: v1.ObjectMeta{ + Namespace: "cluster-0", + }, + }, + }, + }, + }, + expectedConfig: defaultRegistriesConf, + }, + { + name: "image-content-sources", + dependencies: []asset.Asset{ + &agent.OptionalInstallConfig{ + Supplied: true, + InstallConfig: installconfig.InstallConfig{ + Config: &types.InstallConfig{ + ObjectMeta: v1.ObjectMeta{ + Namespace: "cluster-0", + }, + ImageContentSources: []types.ImageContentSource{ + { + Source: "registry.ci.openshift.org/ocp/release", + Mirrors: []string{ + "virthost.ostest.test.metalkube.org:5000/localimages/local-release-image", + }, + }, + { + Source: "quay.io/openshift-release-dev/ocp-v4.0-art-dev", + Mirrors: []string{ + "virthost.ostest.test.metalkube.org:5000/localimages/local-release-image", + }, + }, + }, + }, + }, + }, + }, + expectedConfig: `unqualified-search-registries = [] + +[[registry]] + location = "registry.ci.openshift.org/ocp/release" + mirror-by-digest-only = true + prefix = "" + + [[registry.mirror]] + location = "virthost.ostest.test.metalkube.org:5000/localimages/local-release-image" + +[[registry]] + location = "quay.io/openshift-release-dev/ocp-v4.0-art-dev" + mirror-by-digest-only = true + prefix = "" + + [[registry.mirror]] + location = "virthost.ostest.test.metalkube.org:5000/localimages/local-release-image" +`, + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + + parents := asset.Parents{} + parents.Add(tc.dependencies...) + + asset := &RegistriesConf{} + err := asset.Generate(parents) + + if tc.expectedError != "" { + assert.EqualError(t, err, tc.expectedError) + } else { + assert.NoError(t, err) + + files := asset.Files() + assert.Len(t, files, 1) + assert.Equal(t, tc.expectedConfig, string(files[0].Data)) + } + }) + } +} + +func TestRegistries_LoadedFromDisk(t *testing.T) { + + cases := []struct { + name string + data string + fetchError error + expectedFound bool + expectedError string + }{ + { + name: "valid-config-file", + data: ` +[[registry]] +location = "registry.ci.openshift.org/ocp/release" +mirror-by-digest-only = false + +[[registry.mirror]] +location = "virthost.ostest.test.metalkube.org:5000/localimages/local-release-image" + +[[registry]] +location = "quay.io/openshift-release-dev/ocp-v4.0-art-dev" +mirror-by-digest-only = false + +[[registry.mirror]] +location = "virthost.ostest.test.metalkube.org:5000/localimages/local-release-image"`, + expectedFound: true, + expectedError: "", + }, + { + name: "file-not-found", + fetchError: &os.PathError{Err: os.ErrNotExist}, + }, + { + name: "error-fetching-file", + fetchError: errors.New("fetch failed"), + expectedError: "failed to load mirror/registries.conf file: fetch failed", + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + fileFetcher := mock.NewMockFileFetcher(mockCtrl) + fileFetcher.EXPECT().FetchByName(RegistriesConfFilename). + Return( + &asset.File{ + Filename: RegistriesConfFilename, + Data: []byte(tc.data)}, + tc.fetchError, + ) + + asset := &RegistriesConf{} + found, err := asset.Load(fileFetcher) + assert.Equal(t, tc.expectedFound, found, "unexpected found value returned from Load") + if tc.expectedError != "" { + assert.Equal(t, tc.expectedError, err.Error()) + } else { + assert.NoError(t, err) + } + }) + } + +} diff --git a/pkg/asset/filewriter.go b/pkg/asset/filewriter.go new file mode 100644 index 0000000000..ce37699464 --- /dev/null +++ b/pkg/asset/filewriter.go @@ -0,0 +1,20 @@ +package asset + +// FileWriter interface is used to write all the files in the specified location +type FileWriter interface { + PersistToFile(directory string) error +} + +// NewDefaultFileWriter create a new adapter to expose the default implementation as a FileWriter +func NewDefaultFileWriter(a WritableAsset) FileWriter { + return &fileWriterAdapter{a: a} +} + +type fileWriterAdapter struct { + a WritableAsset +} + +// PersistToFile wraps the default implementation +func (fwa *fileWriterAdapter) PersistToFile(directory string) error { + return PersistToFile(fwa.a, directory) +} diff --git a/pkg/asset/ignition/bootstrap/bootstrap_in_place.go b/pkg/asset/ignition/bootstrap/bootstrap_in_place.go index e9587b39eb..319aac96e4 100644 --- a/pkg/asset/ignition/bootstrap/bootstrap_in_place.go +++ b/pkg/asset/ignition/bootstrap/bootstrap_in_place.go @@ -41,10 +41,10 @@ func (a *SingleNodeBootstrapInPlace) Generate(dependencies asset.Parents) error if err := a.generateConfig(dependencies, templateData); err != nil { return err } - if err := a.addStorageFiles("/", "bootstrap/bootstrap-in-place/files", templateData); err != nil { + if err := AddStorageFiles(a.Config, "/", "bootstrap/bootstrap-in-place/files", templateData); err != nil { return err } - if err := a.addSystemdUnits("bootstrap/bootstrap-in-place/systemd/units", templateData, bootstrapInPlaceEnabledServices); err != nil { + if err := AddSystemdUnits(a.Config, "bootstrap/bootstrap-in-place/systemd/units", templateData, bootstrapInPlaceEnabledServices); err != nil { return err } if err := a.Common.generateFile(singleNodeBootstrapInPlaceIgnFilename); err != nil { diff --git a/pkg/asset/ignition/bootstrap/common.go b/pkg/asset/ignition/bootstrap/common.go index e5fa4b029b..c0b76f8808 100644 --- a/pkg/asset/ignition/bootstrap/common.go +++ b/pkg/asset/ignition/bootstrap/common.go @@ -164,10 +164,10 @@ func (a *Common) generateConfig(dependencies asset.Parents, templateData *bootst }, } - if err := a.addStorageFiles("/", "bootstrap/files", templateData); err != nil { + if err := AddStorageFiles(a.Config, "/", "bootstrap/files", templateData); err != nil { return err } - if err := a.addSystemdUnits("bootstrap/systemd/units", templateData, commonEnabledServices); err != nil { + if err := AddSystemdUnits(a.Config, "bootstrap/systemd/units", templateData, commonEnabledServices); err != nil { return err } @@ -177,7 +177,7 @@ func (a *Common) generateConfig(dependencies asset.Parents, templateData *bootst directory, err := data.Assets.Open(platformFilePath) if err == nil { directory.Close() - err = a.addStorageFiles("/", platformFilePath, templateData) + err = AddStorageFiles(a.Config, "/", platformFilePath, templateData) if err != nil { return err } @@ -187,7 +187,7 @@ func (a *Common) generateConfig(dependencies asset.Parents, templateData *bootst directory, err = data.Assets.Open(platformUnitPath) if err == nil { directory.Close() - if err = a.addSystemdUnits(platformUnitPath, templateData, commonEnabledServices); err != nil { + if err = AddSystemdUnits(a.Config, platformUnitPath, templateData, commonEnabledServices); err != nil { return err } } @@ -242,7 +242,7 @@ func (a *Common) getTemplateData(dependencies asset.Parents, bootstrapInPlace bo } registries := []sysregistriesv2.Registry{} - for _, group := range mergedMirrorSets(installConfig.Config.ImageContentSources) { + for _, group := range MergedMirrorSets(installConfig.Config.ImageContentSources) { if len(group.Mirrors) == 0 { continue } @@ -304,7 +304,13 @@ func (a *Common) getTemplateData(dependencies asset.Parents, bootstrapInPlace bo } } -func (a *Common) addStorageFiles(base string, uri string, templateData *bootstrapTemplateData) (err error) { +// AddStorageFiles adds files to a Ignition config. +// Parameters: +// config - the ignition config to be modified +// base - path were the files are written to in to config +// uri - path under data/data specifying the files to be included +// templateData - struct to used to render templates +func AddStorageFiles(config *igntypes.Config, base string, uri string, templateData interface{}) (err error) { file, err := data.Assets.Open(uri) if err != nil { return err @@ -327,7 +333,7 @@ func (a *Common) addStorageFiles(base string, uri string, templateData *bootstra for _, childInfo := range children { name := childInfo.Name() - err = a.addStorageFiles(path.Join(base, name), path.Join(uri, name), templateData) + err = AddStorageFiles(config, path.Join(base, name), path.Join(uri, name), templateData) if err != nil { return err } @@ -348,7 +354,7 @@ func (a *Common) addStorageFiles(base string, uri string, templateData *bootstra appendToFile := false if parentDir == "bin" || parentDir == "dispatcher.d" { mode = 0555 - } else if filename == "motd" { + } else if filename == "motd" || filename == "containers.conf" { mode = 0644 appendToFile = true } else { @@ -360,12 +366,18 @@ func (a *Common) addStorageFiles(base string, uri string, templateData *bootstra } // Replace files that already exist in the slice with ones added later, otherwise append them - a.Config.Storage.Files = replaceOrAppend(a.Config.Storage.Files, ign) + config.Storage.Files = replaceOrAppend(config.Storage.Files, ign) return nil } -func (a *Common) addSystemdUnits(uri string, templateData *bootstrapTemplateData, enabledServices []string) (err error) { +// AddSystemdUnits adds systemd units to a Ignition config. +// Parameters: +// config - the ignition config to be modified +// uri - path under data/data specifying the systemd units files to be included +// templateData - struct to used to render templates +// enabledServices - a list of systemd units to be enabled by default +func AddSystemdUnits(config *igntypes.Config, uri string, templateData interface{}, enabledServices []string) (err error) { enabled := make(map[string]struct{}, len(enabledServices)) for _, s := range enabledServices { enabled[s] = struct{}{} @@ -436,7 +448,7 @@ func (a *Common) addSystemdUnits(uri string, templateData *bootstrapTemplateData if _, ok := enabled[name]; ok { unit.Enabled = ignutil.BoolToPtr(true) } - a.Config.Systemd.Units = append(a.Config.Systemd.Units, unit) + config.Systemd.Units = append(config.Systemd.Units, unit) } else { name, contents, err := readFile(childInfo.Name(), file, templateData) if err != nil { @@ -450,7 +462,7 @@ func (a *Common) addSystemdUnits(uri string, templateData *bootstrapTemplateData if _, ok := enabled[name]; ok { unit.Enabled = ignutil.BoolToPtr(true) } - a.Config.Systemd.Units = append(a.Config.Systemd.Units, unit) + config.Systemd.Units = append(config.Systemd.Units, unit) } } diff --git a/pkg/asset/ignition/bootstrap/registries.go b/pkg/asset/ignition/bootstrap/registries.go index 99389b855e..e1cc5e54c9 100644 --- a/pkg/asset/ignition/bootstrap/registries.go +++ b/pkg/asset/ignition/bootstrap/registries.go @@ -6,7 +6,9 @@ import ( "github.com/openshift/installer/pkg/types" ) -func mergedMirrorSets(sources []types.ImageContentSource) []types.ImageContentSource { +// MergedMirrorSets consolidates a list of ImageContentSources so that each +// source appears only once. +func MergedMirrorSets(sources []types.ImageContentSource) []types.ImageContentSource { sourceSet := make(map[string][]string) mirrorSet := make(map[string]sets.String) orderedSources := []string{} diff --git a/pkg/asset/ignition/bootstrap/registries_test.go b/pkg/asset/ignition/bootstrap/registries_test.go index 604fda314f..623695173c 100644 --- a/pkg/asset/ignition/bootstrap/registries_test.go +++ b/pkg/asset/ignition/bootstrap/registries_test.go @@ -120,7 +120,7 @@ func TestMergedMirrorSets(t *testing.T) { }} for _, test := range tests { t.Run(test.name, func(t *testing.T) { - assert.Equal(t, test.expected, mergedMirrorSets(test.input)) + assert.Equal(t, test.expected, MergedMirrorSets(test.input)) }) } } diff --git a/pkg/asset/kubeconfig/agent.go b/pkg/asset/kubeconfig/agent.go new file mode 100644 index 0000000000..7d9cac83e2 --- /dev/null +++ b/pkg/asset/kubeconfig/agent.go @@ -0,0 +1,46 @@ +package kubeconfig + +import ( + "fmt" + "strings" + + "github.com/openshift/installer/pkg/asset" + agentmanifests "github.com/openshift/installer/pkg/asset/agent/manifests" + "github.com/openshift/installer/pkg/asset/tls" +) + +// AgentAdminClient is the asset for the agent admin kubeconfig. +type AgentAdminClient struct { + AdminClient +} + +// Dependencies returns the dependency of the kubeconfig. +func (k *AgentAdminClient) Dependencies() []asset.Asset { + return []asset.Asset{ + &tls.AdminKubeConfigClientCertKey{}, + &tls.KubeAPIServerCompleteCABundle{}, + &agentmanifests.ClusterDeployment{}, + } +} + +// Generate generates the kubeconfig. +func (k *AgentAdminClient) Generate(parents asset.Parents) error { + ca := &tls.KubeAPIServerCompleteCABundle{} + clientCertKey := &tls.AdminKubeConfigClientCertKey{} + parents.Get(ca, clientCertKey) + + clusterDeployment := &agentmanifests.ClusterDeployment{} + parents.Get(clusterDeployment) + + clusterName := clusterDeployment.Config.Spec.ClusterName + extAPIServerURL := fmt.Sprintf("https://api.%s.%s:6443", clusterName, strings.TrimSuffix(clusterDeployment.Config.Spec.BaseDomain, ".")) + + return k.kubeconfig.generate( + ca, + clientCertKey, + extAPIServerURL, + clusterName, + "admin", + kubeconfigAdminPath, + ) +} diff --git a/pkg/asset/manifests/additionaltrustbundleconfig.go b/pkg/asset/manifests/additionaltrustbundleconfig.go index c13482e59e..ae11aa3956 100644 --- a/pkg/asset/manifests/additionaltrustbundleconfig.go +++ b/pkg/asset/manifests/additionaltrustbundleconfig.go @@ -56,7 +56,7 @@ func (atbc *AdditionalTrustBundleConfig) Generate(dependencies asset.Parents) er if installConfig.Config.AdditionalTrustBundle == "" { return nil } - data, err := parseCertificates(installConfig.Config.AdditionalTrustBundle) + data, err := ParseCertificates(installConfig.Config.AdditionalTrustBundle) if err != nil { return err @@ -99,7 +99,8 @@ func (atbc *AdditionalTrustBundleConfig) Load(f asset.FileFetcher) (bool, error) return false, nil } -func parseCertificates(certificates string) (map[string]string, error) { +// ParseCertificates parses and verifies a PEM certificate bundle +func ParseCertificates(certificates string) (map[string]string, error) { rest := []byte(certificates) var sb strings.Builder for { diff --git a/pkg/types/agent/OWNERS b/pkg/types/agent/OWNERS new file mode 100644 index 0000000000..51d22e4bb6 --- /dev/null +++ b/pkg/types/agent/OWNERS @@ -0,0 +1,7 @@ +# See the OWNERS docs: https://git.k8s.io/community/contributors/guide/owners.md +# This file just uses aliases defined in OWNERS_ALIASES. + +approvers: + - agent-approvers +reviewers: + - agent-reviewers \ No newline at end of file diff --git a/pkg/types/agent/agent_config_type.go b/pkg/types/agent/agent_config_type.go new file mode 100644 index 0000000000..81deeddd17 --- /dev/null +++ b/pkg/types/agent/agent_config_type.go @@ -0,0 +1,34 @@ +package agent + +import ( + aiv1beta1 "github.com/openshift/assisted-service/api/v1beta1" + "github.com/openshift/installer/pkg/types/baremetal" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// AgentConfigVersion is the version supported by this package. +// If you bump this, you must also update the list of convertable values in +// pkg/types/conversion/agentconfig.go +const AgentConfigVersion = "v1alpha1" + +// Config or aka AgentConfig is the API for specifying additional +// configuration for the agent-based installer not covered by +// install-config. +type Config struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + // ip address of node0 + RendezvousIP string `json:"rendezvousIP,omitempty"` + Hosts []Host `json:"hosts,omitempty"` +} + +// Host defines per host configurations +type Host struct { + Hostname string `json:"hostname,omitempty"` + Role string `json:"role,omitempty"` + RootDeviceHints baremetal.RootDeviceHints `json:"rootDeviceHints,omitempty"` + // list of interfaces and mac addresses + Interfaces []*aiv1beta1.Interface `json:"interfaces,omitempty"` + NetworkConfig aiv1beta1.NetConfig `json:"networkConfig,omitempty"` +} diff --git a/pkg/types/agent/conversion/agentconfig.go b/pkg/types/agent/conversion/agentconfig.go new file mode 100644 index 0000000000..17f56a96d6 --- /dev/null +++ b/pkg/types/agent/conversion/agentconfig.go @@ -0,0 +1,28 @@ +package conversion + +import ( + "fmt" + + "k8s.io/apimachinery/pkg/util/validation/field" + + "github.com/openshift/installer/pkg/types/agent" +) + +// ConvertAgentConfig is modeled after the k8s conversion schemes, which is +// how deprecated values are upconverted. +// This updates the APIVersion to reflect the fact that we've internally +// upconverted. +func ConvertAgentConfig(config *agent.Config) error { + // check that the version is convertible + switch config.APIVersion { + case agent.AgentConfigVersion: + // works + case "": + return field.Required(field.NewPath("apiVersion"), "no version was provided") + default: + return field.Invalid(field.NewPath("apiVersion"), config.APIVersion, fmt.Sprintf("cannot upconvert from version %s", config.APIVersion)) + } + + config.APIVersion = agent.AgentConfigVersion + return nil +} diff --git a/pkg/types/agent/conversion/agentconfig_test.go b/pkg/types/agent/conversion/agentconfig_test.go new file mode 100644 index 0000000000..694cff4e4b --- /dev/null +++ b/pkg/types/agent/conversion/agentconfig_test.go @@ -0,0 +1,59 @@ +package conversion + +import ( + "testing" + + "github.com/stretchr/testify/assert" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/openshift/installer/pkg/types/agent" +) + +func TestConvertAgentConfig(t *testing.T) { + cases := []struct { + name string + config *agent.Config + expected *agent.Config + expectedError string + }{ + { + name: "empty", + config: &agent.Config{ + TypeMeta: metav1.TypeMeta{ + APIVersion: agent.AgentConfigVersion, + }, + }, + expected: &agent.Config{ + TypeMeta: metav1.TypeMeta{ + APIVersion: agent.AgentConfigVersion, + }, + }, + }, + { + name: "no version", + config: &agent.Config{}, + expectedError: "no version was provided", + }, + { + name: "bad version", + config: &agent.Config{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1alpha0", + }, + }, + expectedError: "cannot upconvert from version v1alpha0", + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + err := ConvertAgentConfig(tc.config) + if tc.expectedError == "" { + assert.NoError(t, err) + assert.Equal(t, tc.expected, tc.config, "unexpected install config") + } else { + assert.Regexp(t, tc.expectedError, err) + } + }) + } +} diff --git a/vendor/github.com/cavaliercoder/go-cpio/.gitignore b/vendor/github.com/cavaliercoder/go-cpio/.gitignore new file mode 100644 index 0000000000..b6a80842d4 --- /dev/null +++ b/vendor/github.com/cavaliercoder/go-cpio/.gitignore @@ -0,0 +1,3 @@ +.fuzz/ +*.zip + diff --git a/vendor/github.com/cavaliercoder/go-cpio/.travis.yml b/vendor/github.com/cavaliercoder/go-cpio/.travis.yml new file mode 100644 index 0000000000..580243c933 --- /dev/null +++ b/vendor/github.com/cavaliercoder/go-cpio/.travis.yml @@ -0,0 +1,10 @@ +language: go + +go: + - 1.4.3 + - 1.5.4 + - 1.6.4 + - 1.7.6 + - 1.8.3 + +script: make check diff --git a/vendor/github.com/cavaliercoder/go-cpio/LICENSE b/vendor/github.com/cavaliercoder/go-cpio/LICENSE new file mode 100644 index 0000000000..7f377a1b68 --- /dev/null +++ b/vendor/github.com/cavaliercoder/go-cpio/LICENSE @@ -0,0 +1,26 @@ +Copyright (c) 2017 Ryan Armstrong. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/cavaliercoder/go-cpio/Makefile b/vendor/github.com/cavaliercoder/go-cpio/Makefile new file mode 100644 index 0000000000..8294588313 --- /dev/null +++ b/vendor/github.com/cavaliercoder/go-cpio/Makefile @@ -0,0 +1,18 @@ +PACKAGE = github.com/cavaliercoder/go-cpio + +all: check + +check: + go test -v + +cpio-fuzz.zip: *.go + go-fuzz-build $(PACKAGE) + +fuzz: cpio-fuzz.zip + go-fuzz -bin=./cpio-fuzz.zip -workdir=.fuzz/ + +clean-fuzz: + rm -rf cpio-fuzz.zip .fuzz/crashers/* .fuzz/suppressions/* + + +.PHONY: all check diff --git a/vendor/github.com/cavaliercoder/go-cpio/README.md b/vendor/github.com/cavaliercoder/go-cpio/README.md new file mode 100644 index 0000000000..0a322ed9a0 --- /dev/null +++ b/vendor/github.com/cavaliercoder/go-cpio/README.md @@ -0,0 +1,62 @@ +# go-cpio [![GoDoc](https://godoc.org/github.com/cavaliercoder/go-cpio?status.svg)](https://godoc.org/github.com/cavaliercoder/go-cpio) [![Build Status](https://travis-ci.org/cavaliercoder/go-cpio.svg?branch=master)](https://travis-ci.org/cavaliercoder/go-cpio) [![Go Report Card](https://goreportcard.com/badge/github.com/cavaliercoder/go-cpio)](https://goreportcard.com/report/github.com/cavaliercoder/go-cpio) + +This package provides a Go native implementation of the CPIO archive file +format. + +Currently, only the SVR4 (New ASCII) format is supported, both with and without +checksums. + +```go +// Create a buffer to write our archive to. +buf := new(bytes.Buffer) + +// Create a new cpio archive. +w := cpio.NewWriter(buf) + +// Add some files to the archive. +var files = []struct { + Name, Body string +}{ + {"readme.txt", "This archive contains some text files."}, + {"gopher.txt", "Gopher names:\nGeorge\nGeoffrey\nGonzo"}, + {"todo.txt", "Get animal handling license."}, +} +for _, file := range files { + hdr := &cpio.Header{ + Name: file.Name, + Mode: 0600, + Size: int64(len(file.Body)), + } + if err := w.WriteHeader(hdr); err != nil { + log.Fatalln(err) + } + if _, err := w.Write([]byte(file.Body)); err != nil { + log.Fatalln(err) + } +} +// Make sure to check the error on Close. +if err := w.Close(); err != nil { + log.Fatalln(err) +} + +// Open the cpio archive for reading. +b := bytes.NewReader(buf.Bytes()) +r := cpio.NewReader(b) + +// Iterate through the files in the archive. +for { + hdr, err := r.Next() + if err == io.EOF { + // end of cpio archive + break + } + if err != nil { + log.Fatalln(err) + } + fmt.Printf("Contents of %s:\n", hdr.Name) + if _, err := io.Copy(os.Stdout, r); err != nil { + log.Fatalln(err) + } + fmt.Println() +} +``` diff --git a/vendor/github.com/cavaliercoder/go-cpio/cpio.go b/vendor/github.com/cavaliercoder/go-cpio/cpio.go new file mode 100644 index 0000000000..beebf39f55 --- /dev/null +++ b/vendor/github.com/cavaliercoder/go-cpio/cpio.go @@ -0,0 +1,8 @@ +/* +Package cpio implements access to CPIO archives. Currently, only the SVR4 (New +ASCII) format is supported, both with and without checksums. + +References: + https://www.freebsd.org/cgi/man.cgi?query=cpio&sektion=5 +*/ +package cpio diff --git a/vendor/github.com/cavaliercoder/go-cpio/fileinfo.go b/vendor/github.com/cavaliercoder/go-cpio/fileinfo.go new file mode 100644 index 0000000000..55adab3321 --- /dev/null +++ b/vendor/github.com/cavaliercoder/go-cpio/fileinfo.go @@ -0,0 +1,75 @@ +package cpio + +import ( + "os" + "path" + "time" +) + +// headerFileInfo implements os.FileInfo. +type headerFileInfo struct { + h *Header +} + +// Name returns the base name of the file. +func (fi headerFileInfo) Name() string { + if fi.IsDir() { + return path.Base(path.Clean(fi.h.Name)) + } + return path.Base(fi.h.Name) +} + +func (fi headerFileInfo) Size() int64 { return fi.h.Size } +func (fi headerFileInfo) IsDir() bool { return fi.Mode().IsDir() } +func (fi headerFileInfo) ModTime() time.Time { return fi.h.ModTime } +func (fi headerFileInfo) Sys() interface{} { return fi.h } + +func (fi headerFileInfo) Mode() (mode os.FileMode) { + // Set file permission bits. + mode = os.FileMode(fi.h.Mode).Perm() + + // Set setuid, setgid and sticky bits. + if fi.h.Mode&ModeSetuid != 0 { + // setuid + mode |= os.ModeSetuid + } + if fi.h.Mode&ModeSetgid != 0 { + // setgid + mode |= os.ModeSetgid + } + if fi.h.Mode&ModeSticky != 0 { + // sticky + mode |= os.ModeSticky + } + + // Set file mode bits. + // clear perm, setuid, setgid and sticky bits. + m := os.FileMode(fi.h.Mode) & 0170000 + if m == ModeDir { + // directory + mode |= os.ModeDir + } + if m == ModeNamedPipe { + // named pipe (FIFO) + mode |= os.ModeNamedPipe + } + if m == ModeSymlink { + // symbolic link + mode |= os.ModeSymlink + } + if m == ModeDevice { + // device file + mode |= os.ModeDevice + } + if m == ModeCharDevice { + // Unix character device + mode |= os.ModeDevice + mode |= os.ModeCharDevice + } + if m == ModeSocket { + // Unix domain socket + mode |= os.ModeSocket + } + + return mode +} diff --git a/vendor/github.com/cavaliercoder/go-cpio/fuzz.go b/vendor/github.com/cavaliercoder/go-cpio/fuzz.go new file mode 100644 index 0000000000..7d13d4cb02 --- /dev/null +++ b/vendor/github.com/cavaliercoder/go-cpio/fuzz.go @@ -0,0 +1,35 @@ +// +build gofuzz + +package cpio + +import "bytes" +import "io" + +// Fuzz tests the parsing and error handling of random byte arrays using +// https://github.com/dvyukov/go-fuzz. +func Fuzz(data []byte) int { + r := NewReader(bytes.NewReader(data)) + h := NewHash() + for { + hdr, err := r.Next() + if err != nil { + if hdr != nil { + panic("hdr != nil on error") + } + if err == io.EOF { + // everything worked with random input... interesting + return 1 + } + // error returned for random input. Good! + return -1 + } + + // hash file + h.Reset() + io.CopyN(h, r, hdr.Size) + h.Sum32() + + // convert file header + FileInfoHeader(hdr.FileInfo()) + } +} diff --git a/vendor/github.com/cavaliercoder/go-cpio/hash.go b/vendor/github.com/cavaliercoder/go-cpio/hash.go new file mode 100644 index 0000000000..5eae7e8bd6 --- /dev/null +++ b/vendor/github.com/cavaliercoder/go-cpio/hash.go @@ -0,0 +1,45 @@ +package cpio + +import ( + "encoding/binary" + "hash" +) + +type digest struct { + sum uint32 +} + +// NewHash returns a new hash.Hash32 computing the SVR4 checksum. +func NewHash() hash.Hash32 { + return &digest{} +} + +func (d *digest) Write(p []byte) (n int, err error) { + for _, b := range p { + d.sum += uint32(b & 0xFF) + } + + return len(p), nil +} + +func (d *digest) Sum(b []byte) []byte { + out := [4]byte{} + binary.LittleEndian.PutUint32(out[:], d.sum) + return append(b, out[:]...) +} + +func (d *digest) Sum32() uint32 { + return d.sum +} + +func (d *digest) Reset() { + d.sum = 0 +} + +func (d *digest) Size() int { + return 4 +} + +func (d *digest) BlockSize() int { + return 1 +} diff --git a/vendor/github.com/cavaliercoder/go-cpio/header.go b/vendor/github.com/cavaliercoder/go-cpio/header.go new file mode 100644 index 0000000000..cba24e3c37 --- /dev/null +++ b/vendor/github.com/cavaliercoder/go-cpio/header.go @@ -0,0 +1,153 @@ +package cpio + +import ( + "errors" + "fmt" + "os" + "time" +) + +// Mode constants from the cpio spec. +const ( + ModeSetuid = 04000 // Set uid + ModeSetgid = 02000 // Set gid + ModeSticky = 01000 // Save text (sticky bit) + ModeDir = 040000 // Directory + ModeNamedPipe = 010000 // FIFO + ModeRegular = 0100000 // Regular file + ModeSymlink = 0120000 // Symbolic link + ModeDevice = 060000 // Block special file + ModeCharDevice = 020000 // Character special file + ModeSocket = 0140000 // Socket + + ModeType = 0170000 // Mask for the type bits + ModePerm = 0777 // Unix permission bits +) + +const ( + // headerEOF is the value of the filename of the last header in a CPIO archive. + headerEOF = "TRAILER!!!" +) + +var ( + ErrHeader = errors.New("cpio: invalid cpio header") +) + +// A FileMode represents a file's mode and permission bits. +type FileMode int64 + +func (m FileMode) String() string { + return fmt.Sprintf("%#o", m) +} + +// IsDir reports whether m describes a directory. That is, it tests for the +// ModeDir bit being set in m. +func (m FileMode) IsDir() bool { + return m&ModeDir != 0 +} + +// IsRegular reports whether m describes a regular file. That is, it tests for +// the ModeRegular bit being set in m. +func (m FileMode) IsRegular() bool { + return m&^ModePerm == ModeRegular +} + +// Perm returns the Unix permission bits in m. +func (m FileMode) Perm() FileMode { + return m & ModePerm +} + +// Checksum is the sum of all bytes in the file data. This sum is computed +// treating all bytes as unsigned values and using unsigned arithmetic. Only +// the least-significant 32 bits of the sum are stored. Use NewHash to compute +// the actual checksum of an archived file. +type Checksum uint32 + +func (c Checksum) String() string { + return fmt.Sprintf("%08X", uint32(c)) +} + +// A Header represents a single header in a CPIO archive. +type Header struct { + DeviceID int + Inode int64 // inode number + Mode FileMode // permission and mode bits + UID int // user id of the owner + GID int // group id of the owner + Links int // number of inbound links + ModTime time.Time // modified time + Size int64 // size in bytes + Name string // filename + Linkname string // target name of link + Checksum Checksum // computed checksum + + pad int64 // bytes to pad before next header +} + +// FileInfo returns an os.FileInfo for the Header. +func (h *Header) FileInfo() os.FileInfo { + return headerFileInfo{h} +} + +// FileInfoHeader creates a partially-populated Header from fi. +// If fi describes a symlink, FileInfoHeader records link as the link target. +// If fi describes a directory, a slash is appended to the name. +// Because os.FileInfo's Name method returns only the base name of +// the file it describes, it may be necessary to modify the Name field +// of the returned header to provide the full path name of the file. +func FileInfoHeader(fi os.FileInfo, link string) (*Header, error) { + if fi == nil { + return nil, errors.New("cpio: FileInfo is nil") + } + + if sys, ok := fi.Sys().(*Header); ok { + // This FileInfo came from a Header (not the OS). Return a copy of the + // original Header. + h := &Header{} + *h = *sys + return h, nil + } + + fm := fi.Mode() + h := &Header{ + Name: fi.Name(), + Mode: FileMode(fi.Mode().Perm()), // or'd with Mode* constants later + ModTime: fi.ModTime(), + Size: fi.Size(), + } + + switch { + case fm.IsRegular(): + h.Mode |= ModeRegular + case fi.IsDir(): + h.Mode |= ModeDir + h.Name += "/" + h.Size = 0 + case fm&os.ModeSymlink != 0: + h.Mode |= ModeSymlink + h.Linkname = link + case fm&os.ModeDevice != 0: + if fm&os.ModeCharDevice != 0 { + h.Mode |= ModeCharDevice + } else { + h.Mode |= ModeDevice + } + case fm&os.ModeNamedPipe != 0: + h.Mode |= ModeNamedPipe + case fm&os.ModeSocket != 0: + h.Mode |= ModeSocket + default: + return nil, fmt.Errorf("cpio: unknown file mode %v", fm) + } + if fm&os.ModeSetuid != 0 { + h.Mode |= ModeSetuid + } + if fm&os.ModeSetgid != 0 { + h.Mode |= ModeSetgid + } + if fm&os.ModeSticky != 0 { + h.Mode |= ModeSticky + } + + return h, nil +} diff --git a/vendor/github.com/cavaliercoder/go-cpio/reader.go b/vendor/github.com/cavaliercoder/go-cpio/reader.go new file mode 100644 index 0000000000..81912b6658 --- /dev/null +++ b/vendor/github.com/cavaliercoder/go-cpio/reader.go @@ -0,0 +1,72 @@ +package cpio + +import ( + "io" + "io/ioutil" +) + +// A Reader provides sequential access to the contents of a CPIO archive. A CPIO +// archive consists of a sequence of files. The Next method advances to the next +// file in the archive (including the first), and then it can be treated as an +// io.Reader to access the file's data. +type Reader struct { + r io.Reader // underlying file reader + hdr *Header // current Header + eof int64 // bytes until the end of the current file +} + +// NewReader creates a new Reader reading from r. +func NewReader(r io.Reader) *Reader { + return &Reader{ + r: r, + } +} + +// Read reads from the current entry in the CPIO archive. It returns 0, io.EOF +// when it reaches the end of that entry, until Next is called to advance to the +// next entry. +func (r *Reader) Read(p []byte) (n int, err error) { + if r.hdr == nil || r.eof == 0 { + return 0, io.EOF + } + rn := len(p) + if r.eof < int64(rn) { + rn = int(r.eof) + } + n, err = r.r.Read(p[0:rn]) + r.eof -= int64(n) + return +} + +// Next advances to the next entry in the CPIO archive. +// io.EOF is returned at the end of the input. +func (r *Reader) Next() (*Header, error) { + if r.hdr == nil { + return r.next() + } + skp := r.eof + r.hdr.pad + if skp > 0 { + _, err := io.CopyN(ioutil.Discard, r.r, skp) + if err != nil { + return nil, err + } + } + return r.next() +} + +func (r *Reader) next() (*Header, error) { + r.eof = 0 + hdr, err := readHeader(r.r) + if err != nil { + return nil, err + } + r.hdr = hdr + r.eof = hdr.Size + return hdr, nil +} + +// ReadHeader creates a new Header, reading from r. +func readHeader(r io.Reader) (*Header, error) { + // currently only SVR4 format is supported + return readSVR4Header(r) +} diff --git a/vendor/github.com/cavaliercoder/go-cpio/svr4.go b/vendor/github.com/cavaliercoder/go-cpio/svr4.go new file mode 100644 index 0000000000..f965554789 --- /dev/null +++ b/vendor/github.com/cavaliercoder/go-cpio/svr4.go @@ -0,0 +1,152 @@ +package cpio + +import ( + "bytes" + "fmt" + "io" + "strconv" + "time" +) + +const ( + svr4MaxNameSize = 4096 // MAX_PATH + svr4MaxFileSize = 4294967295 +) + +var svr4Magic = []byte{0x30, 0x37, 0x30, 0x37, 0x30, 0x31} // 070701 + +func readHex(s string) int64 { + // errors are ignored and 0 returned + i, _ := strconv.ParseInt(s, 16, 64) + return i +} + +func writeHex(b []byte, i int64) { + // i needs to be in range of uint32 + copy(b, fmt.Sprintf("%08X", i)) +} + +func readSVR4Header(r io.Reader) (*Header, error) { + var buf [110]byte + if _, err := io.ReadFull(r, buf[:]); err != nil { + return nil, err + } + + // TODO: check endianness + + // check magic + hasCRC := false + if !bytes.HasPrefix(buf[:], svr4Magic[:5]) { + return nil, ErrHeader + } + if buf[5] == 0x32 { // '2' + hasCRC = true + } else if buf[5] != 0x31 { // '1' + return nil, ErrHeader + } + + asc := string(buf[:]) + hdr := &Header{} + + hdr.Inode = readHex(asc[6:14]) + hdr.Mode = FileMode(readHex(asc[14:22])) + hdr.UID = int(readHex(asc[22:30])) + hdr.GID = int(readHex(asc[30:38])) + hdr.Links = int(readHex(asc[38:46])) + hdr.ModTime = time.Unix(readHex(asc[46:54]), 0) + hdr.Size = readHex(asc[54:62]) + if hdr.Size > svr4MaxFileSize { + return nil, ErrHeader + } + nameSize := readHex(asc[94:102]) + if nameSize < 1 || nameSize > svr4MaxNameSize { + return nil, ErrHeader + } + hdr.Checksum = Checksum(readHex(asc[102:110])) + if !hasCRC && hdr.Checksum != 0 { + return nil, ErrHeader + } + + name := make([]byte, nameSize) + if _, err := io.ReadFull(r, name); err != nil { + return nil, err + } + hdr.Name = string(name[:nameSize-1]) + if hdr.Name == headerEOF { + return nil, io.EOF + } + + // store padding between end of file and next header + hdr.pad = (4 - (hdr.Size % 4)) % 4 + + // skip to end of header/start of file + pad := (4 - (len(buf)+len(name))%4) % 4 + if pad > 0 { + if _, err := io.ReadFull(r, buf[:pad]); err != nil { + return nil, err + } + } + + // read link name + if hdr.Mode&^ModePerm == ModeSymlink { + if hdr.Size < 1 || hdr.Size > svr4MaxNameSize { + return nil, ErrHeader + } + b := make([]byte, hdr.Size) + if _, err := io.ReadFull(r, b); err != nil { + return nil, err + } + hdr.Linkname = string(b) + hdr.Size = 0 + } + + return hdr, nil +} + +func writeSVR4Header(w io.Writer, hdr *Header) (pad int64, err error) { + var hdrBuf [110]byte + for i := 0; i < len(hdrBuf); i++ { + hdrBuf[i] = '0' + } + magic := svr4Magic + if hdr.Checksum != 0 { + magic[5] = 0x32 + } + copy(hdrBuf[:], magic) + writeHex(hdrBuf[6:14], hdr.Inode) + writeHex(hdrBuf[14:22], int64(hdr.Mode)) + writeHex(hdrBuf[22:30], int64(hdr.UID)) + writeHex(hdrBuf[30:38], int64(hdr.GID)) + writeHex(hdrBuf[38:46], int64(hdr.Links)) + if !hdr.ModTime.IsZero() { + writeHex(hdrBuf[46:54], hdr.ModTime.Unix()) + } + writeHex(hdrBuf[54:62], hdr.Size) + writeHex(hdrBuf[94:102], int64(len(hdr.Name)+1)) + if hdr.Checksum != 0 { + writeHex(hdrBuf[102:110], int64(hdr.Checksum)) + } + + // write header + _, err = w.Write(hdrBuf[:]) + if err != nil { + return + } + + // write filename + _, err = io.WriteString(w, hdr.Name+"\x00") + if err != nil { + return + } + + // pad to end of filename + npad := (4 - ((len(hdrBuf) + len(hdr.Name) + 1) % 4)) % 4 + _, err = w.Write(zeroBlock[:npad]) + if err != nil { + return + } + + // compute padding to end of file + pad = (4 - (hdr.Size % 4)) % 4 + return +} diff --git a/vendor/github.com/cavaliercoder/go-cpio/writer.go b/vendor/github.com/cavaliercoder/go-cpio/writer.go new file mode 100644 index 0000000000..e8ed127792 --- /dev/null +++ b/vendor/github.com/cavaliercoder/go-cpio/writer.go @@ -0,0 +1,128 @@ +package cpio + +import ( + "errors" + "fmt" + "io" +) + +var ( + ErrWriteTooLong = errors.New("cpio: write too long") + ErrWriteAfterClose = errors.New("cpio: write after close") +) + +var trailer = &Header{ + Name: string(headerEOF), + Links: 1, +} + +var zeroBlock [4]byte + +// A Writer provides sequential writing of a CPIO archive. A CPIO archive +// consists of a sequence of files. Call WriteHeader to begin a new file, and +// then call Write to supply that file's data, writing at most hdr.Size bytes in +// total. +type Writer struct { + w io.Writer + nb int64 // number of unwritten bytes for current file entry + pad int64 // amount of padding to write after current file entry + inode int64 + err error + closed bool +} + +// NewWriter creates a new Writer writing to w. +func NewWriter(w io.Writer) *Writer { + return &Writer{w: w} +} + +// Flush finishes writing the current file (optional). +func (w *Writer) Flush() error { + if w.nb > 0 { + w.err = fmt.Errorf("cpio: missed writing %d bytes", w.nb) + return w.err + } + _, w.err = w.w.Write(zeroBlock[:w.pad]) + if w.err != nil { + return w.err + } + w.nb = 0 + w.pad = 0 + return w.err +} + +// WriteHeader writes hdr and prepares to accept the file's contents. +// WriteHeader calls Flush if it is not the first header. Calling after a Close +// will return ErrWriteAfterClose. +func (w *Writer) WriteHeader(hdr *Header) (err error) { + if w.closed { + return ErrWriteAfterClose + } + if w.err == nil { + w.Flush() + } + if w.err != nil { + return w.err + } + + if hdr.Name != headerEOF { + // TODO: should we be mutating hdr here? + // ensure all inodes are unique + w.inode++ + if hdr.Inode == 0 { + hdr.Inode = w.inode + } + + // ensure file type is set + if hdr.Mode&^ModePerm == 0 { + hdr.Mode |= ModeRegular + } + + // ensure regular files have at least 1 inbound link + if hdr.Links < 1 && hdr.Mode.IsRegular() { + hdr.Links = 1 + } + } + + w.nb = hdr.Size + w.pad, w.err = writeSVR4Header(w.w, hdr) + return +} + +// Write writes to the current entry in the CPIO archive. Write returns the +// error ErrWriteTooLong if more than hdr.Size bytes are written after +// WriteHeader. +func (w *Writer) Write(p []byte) (n int, err error) { + if w.closed { + err = ErrWriteAfterClose + return + } + overwrite := false + if int64(len(p)) > w.nb { + p = p[0:w.nb] + overwrite = true + } + n, err = w.w.Write(p) + w.nb -= int64(n) + if err == nil && overwrite { + err = ErrWriteTooLong + return + } + w.err = err + return +} + +// Close closes the CPIO archive, flushing any unwritten data to the underlying +// writer. +func (w *Writer) Close() error { + if w.err != nil || w.closed { + return w.err + } + w.err = w.WriteHeader(trailer) + if w.err != nil { + return w.err + } + w.Flush() + w.closed = true + return w.err +} diff --git a/vendor/github.com/coreos/ignition/v2/config/shared/errors/errors.go b/vendor/github.com/coreos/ignition/v2/config/shared/errors/errors.go index 4d9906d9a3..7761280d07 100644 --- a/vendor/github.com/coreos/ignition/v2/config/shared/errors/errors.go +++ b/vendor/github.com/coreos/ignition/v2/config/shared/errors/errors.go @@ -37,6 +37,7 @@ var ( ErrFileUsedSymlink = errors.New("file path includes link in config") ErrDirectoryUsedSymlink = errors.New("directory path includes link in config") ErrLinkUsedSymlink = errors.New("link path includes link in config") + ErrLinkTargetRequired = errors.New("link target is required") ErrHardLinkToDirectory = errors.New("hard link target is a directory") ErrDiskDeviceRequired = errors.New("disk device is required") ErrPartitionNumbersCollide = errors.New("partition numbers collide") @@ -55,6 +56,7 @@ var ( ErrLuksLabelTooLong = errors.New("luks device labels cannot be longer than 47 characters") ErrLuksNameContainsSlash = errors.New("device names cannot contain slashes") ErrInvalidLuksKeyFile = errors.New("invalid key-file source") + ErrClevisPinRequired = errors.New("missing required custom clevis pin") ErrUnknownClevisPin = errors.New("unsupported clevis pin") ErrClevisConfigRequired = errors.New("missing required custom clevis config") ErrClevisCustomWithOthers = errors.New("cannot use custom clevis config with tpm2, tang, or threshold") @@ -67,8 +69,10 @@ var ( ErrNoPath = errors.New("path not specified") ErrPathRelative = errors.New("path not absolute") ErrDirtyPath = errors.New("path is not fully simplified") - ErrSparesUnsupportedForLevel = errors.New("spares unsupported for arrays with a level greater than 0") + ErrRaidLevelRequired = errors.New("raid level is required") + ErrSparesUnsupportedForLevel = errors.New("spares unsupported for linear and raid0 arrays") ErrUnrecognizedRaidLevel = errors.New("unrecognized raid level") + ErrRaidDevicesRequired = errors.New("raid devices required") ErrShouldNotExistWithOthers = errors.New("shouldExist specified false with other options also specified") ErrZeroesWithShouldNotExist = errors.New("shouldExist is false for a partition and other partition(s) has start or size 0") ErrNeedLabelOrNumber = errors.New("a partition number >= 1 or a label must be specified") diff --git a/vendor/github.com/coreos/ignition/v2/config/util/config.go b/vendor/github.com/coreos/ignition/v2/config/util/config.go new file mode 100644 index 0000000000..85cd7fa7c4 --- /dev/null +++ b/vendor/github.com/coreos/ignition/v2/config/util/config.go @@ -0,0 +1,45 @@ +// Copyright 2021 Red Hat, Inc. +// +// 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 util + +import ( + "github.com/coreos/ignition/v2/config/shared/errors" + + "github.com/coreos/go-semver/semver" + "github.com/coreos/vcontext/report" +) + +type versionStub struct { + Ignition struct { + Version string + } +} + +// GetConfigVersion parses the version from the given raw config +func GetConfigVersion(raw []byte) (semver.Version, report.Report, error) { + if len(raw) == 0 { + return semver.Version{}, report.Report{}, errors.ErrEmpty + } + + stub := versionStub{} + if rpt, err := HandleParseErrors(raw, &stub); err != nil { + return semver.Version{}, rpt, err + } + + version, err := semver.NewVersion(stub.Ignition.Version) + if err != nil { + return semver.Version{}, report.Report{}, errors.ErrInvalidVersion + } + return *version, report.Report{}, nil +} diff --git a/vendor/github.com/coreos/ignition/v2/config/util/helpers.go b/vendor/github.com/coreos/ignition/v2/config/util/helpers.go index 9c8b04413f..9be42800b9 100644 --- a/vendor/github.com/coreos/ignition/v2/config/util/helpers.go +++ b/vendor/github.com/coreos/ignition/v2/config/util/helpers.go @@ -33,3 +33,11 @@ func NilOrEmpty(s *string) bool { func NotEmpty(s *string) bool { return s != nil && *s != "" } + +func IsTrue(b *bool) bool { + return b != nil && *b +} + +func IsFalse(b *bool) bool { + return b != nil && !*b +} diff --git a/vendor/github.com/coreos/ignition/v2/config/util/reflection.go b/vendor/github.com/coreos/ignition/v2/config/util/reflection.go index 561a8706ba..51df4e124d 100644 --- a/vendor/github.com/coreos/ignition/v2/config/util/reflection.go +++ b/vendor/github.com/coreos/ignition/v2/config/util/reflection.go @@ -15,6 +15,7 @@ package util import ( + "fmt" "reflect" ) @@ -53,3 +54,38 @@ func IsInvalidInConfig(k reflect.Kind) bool { return true } } + +// Return a fully non-zero value for the specified type, recursively +// setting all fields and slices. +func NonZeroValue(t reflect.Type) reflect.Value { + v := reflect.New(t).Elem() + setNonZero(v) + return v +} + +func setNonZero(v reflect.Value) { + switch v.Kind() { + case reflect.Bool: + v.SetBool(true) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + v.SetInt(1) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + v.SetUint(1) + case reflect.Float32, reflect.Float64: + v.SetFloat(1) + case reflect.String: + v.SetString("aardvark") + case reflect.Ptr: + v.Set(reflect.New(v.Type().Elem())) + setNonZero(v.Elem()) + case reflect.Slice: + v.Set(reflect.MakeSlice(v.Type(), 1, 1)) + setNonZero(v.Index(0)) + case reflect.Struct: + for i := 0; i < v.NumField(); i++ { + setNonZero(v.Field(i)) + } + default: + panic(fmt.Sprintf("unexpected kind %s", v.Kind())) + } +} diff --git a/vendor/github.com/coreos/ignition/v2/config/v3_2/types/custom.go b/vendor/github.com/coreos/ignition/v2/config/v3_2/types/custom.go index 1b0c77b8b1..2a1231cb34 100644 --- a/vendor/github.com/coreos/ignition/v2/config/v3_2/types/custom.go +++ b/vendor/github.com/coreos/ignition/v2/config/v3_2/types/custom.go @@ -21,10 +21,6 @@ import ( "github.com/coreos/vcontext/report" ) -func (cu Custom) Key() string { - return cu.Pin -} - func (cu Custom) Validate(c path.ContextPath) (r report.Report) { if cu.Pin == "" && cu.Config == "" { return diff --git a/vendor/github.com/coreos/ignition/v2/config/v3_2/types/disk.go b/vendor/github.com/coreos/ignition/v2/config/v3_2/types/disk.go index 17856e07a6..8caf8499d7 100644 --- a/vendor/github.com/coreos/ignition/v2/config/v3_2/types/disk.go +++ b/vendor/github.com/coreos/ignition/v2/config/v3_2/types/disk.go @@ -16,6 +16,7 @@ package types import ( "github.com/coreos/ignition/v2/config/shared/errors" + "github.com/coreos/ignition/v2/config/util" "github.com/coreos/vcontext/path" "github.com/coreos/vcontext/report" @@ -127,7 +128,7 @@ func (n Disk) partitionsMixZeroesAndNonexistence() bool { hasZero := false hasShouldNotExist := false for _, p := range n.Partitions { - hasShouldNotExist = hasShouldNotExist || (p.ShouldExist != nil && !*p.ShouldExist) + hasShouldNotExist = hasShouldNotExist || util.IsFalse(p.ShouldExist) hasZero = hasZero || (p.Number == 0) } return hasZero && hasShouldNotExist diff --git a/vendor/github.com/coreos/ignition/v2/config/v3_2/types/file.go b/vendor/github.com/coreos/ignition/v2/config/v3_2/types/file.go index 04b14288d9..9b71bb26aa 100644 --- a/vendor/github.com/coreos/ignition/v2/config/v3_2/types/file.go +++ b/vendor/github.com/coreos/ignition/v2/config/v3_2/types/file.go @@ -16,6 +16,7 @@ package types import ( "github.com/coreos/ignition/v2/config/shared/errors" + "github.com/coreos/ignition/v2/config/util" "github.com/coreos/vcontext/path" "github.com/coreos/vcontext/report" @@ -29,7 +30,7 @@ func (f File) Validate(c path.ContextPath) (r report.Report) { } func (f File) validateOverwrite() error { - if f.Overwrite != nil && *f.Overwrite && f.Contents.Source == nil { + if util.IsTrue(f.Overwrite) && f.Contents.Source == nil { return errors.ErrOverwriteAndNilSource } return nil diff --git a/vendor/github.com/coreos/ignition/v2/config/v3_2/types/filesystem.go b/vendor/github.com/coreos/ignition/v2/config/v3_2/types/filesystem.go index 39a158969c..3bf064f355 100644 --- a/vendor/github.com/coreos/ignition/v2/config/v3_2/types/filesystem.go +++ b/vendor/github.com/coreos/ignition/v2/config/v3_2/types/filesystem.go @@ -50,7 +50,7 @@ func (f Filesystem) validateFormat() error { if util.NotEmpty(f.Path) || util.NotEmpty(f.Label) || util.NotEmpty(f.UUID) || - f.WipeFilesystem != nil && *f.WipeFilesystem || + util.IsTrue(f.WipeFilesystem) || len(f.MountOptions) != 0 || len(f.Options) != 0 { return errors.ErrFormatNilWithOthers diff --git a/vendor/github.com/coreos/ignition/v2/config/v3_2/types/luks.go b/vendor/github.com/coreos/ignition/v2/config/v3_2/types/luks.go index d7cd29bac2..123392b25d 100644 --- a/vendor/github.com/coreos/ignition/v2/config/v3_2/types/luks.go +++ b/vendor/github.com/coreos/ignition/v2/config/v3_2/types/luks.go @@ -46,7 +46,7 @@ func (l Luks) Validate(c path.ContextPath) (r report.Report) { } if l.Clevis != nil { - if l.Clevis.Custom != nil && (len(l.Clevis.Tang) > 0 || (l.Clevis.Tpm2 != nil && *l.Clevis.Tpm2) || (l.Clevis.Threshold != nil && *l.Clevis.Threshold != 0)) { + if l.Clevis.Custom != nil && (len(l.Clevis.Tang) > 0 || util.IsTrue(l.Clevis.Tpm2) || (l.Clevis.Threshold != nil && *l.Clevis.Threshold != 0)) { r.AddOnError(c.Append("clevis"), errors.ErrClevisCustomWithOthers) } } diff --git a/vendor/github.com/coreos/ignition/v2/config/v3_2/types/node.go b/vendor/github.com/coreos/ignition/v2/config/v3_2/types/node.go index 52576a924a..248276e737 100644 --- a/vendor/github.com/coreos/ignition/v2/config/v3_2/types/node.go +++ b/vendor/github.com/coreos/ignition/v2/config/v3_2/types/node.go @@ -18,6 +18,7 @@ import ( "path" "github.com/coreos/ignition/v2/config/shared/errors" + "github.com/coreos/ignition/v2/config/util" vpath "github.com/coreos/vcontext/path" "github.com/coreos/vcontext/report" @@ -41,7 +42,7 @@ func (n Node) Depth() int { } func validateIDorName(id *int, name *string) error { - if id != nil && (name != nil && *name != "") { + if id != nil && util.NotEmpty(name) { return errors.ErrBothIDAndNameSet } return nil diff --git a/vendor/github.com/coreos/ignition/v2/config/v3_2/types/partition.go b/vendor/github.com/coreos/ignition/v2/config/v3_2/types/partition.go index 08dca8eaf2..1b2d97edf1 100644 --- a/vendor/github.com/coreos/ignition/v2/config/v3_2/types/partition.go +++ b/vendor/github.com/coreos/ignition/v2/config/v3_2/types/partition.go @@ -20,6 +20,7 @@ import ( "strings" "github.com/coreos/ignition/v2/config/shared/errors" + "github.com/coreos/ignition/v2/config/util" "github.com/coreos/vcontext/path" "github.com/coreos/vcontext/report" @@ -44,8 +45,8 @@ func (p Partition) Key() string { } func (p Partition) Validate(c path.ContextPath) (r report.Report) { - if p.ShouldExist != nil && !*p.ShouldExist && - (p.Label != nil || (p.TypeGUID != nil && *p.TypeGUID != "") || (p.GUID != nil && *p.GUID != "") || p.StartMiB != nil || p.SizeMiB != nil) { + if util.IsFalse(p.ShouldExist) && + (p.Label != nil || util.NotEmpty(p.TypeGUID) || util.NotEmpty(p.GUID) || p.StartMiB != nil || p.SizeMiB != nil) { r.AddOnError(c, errors.ErrShouldNotExistWithOthers) } if p.Number == 0 && p.Label == nil { diff --git a/vendor/github.com/coreos/ignition/v2/config/v3_2/types/raid.go b/vendor/github.com/coreos/ignition/v2/config/v3_2/types/raid.go index 039e54e666..5ae4f8c099 100644 --- a/vendor/github.com/coreos/ignition/v2/config/v3_2/types/raid.go +++ b/vendor/github.com/coreos/ignition/v2/config/v3_2/types/raid.go @@ -33,6 +33,9 @@ func (r Raid) IgnoreDuplicates() map[string]struct{} { func (ra Raid) Validate(c path.ContextPath) (r report.Report) { r.AddOnError(c.Append("level"), ra.validateLevel()) + if len(ra.Devices) == 0 { + r.AddOnError(c.Append("devices"), errors.ErrRaidDevicesRequired) + } return } diff --git a/vendor/github.com/coreos/ignition/v2/config/v3_2/types/storage.go b/vendor/github.com/coreos/ignition/v2/config/v3_2/types/storage.go index db7e44acd4..fd1b8cecf6 100644 --- a/vendor/github.com/coreos/ignition/v2/config/v3_2/types/storage.go +++ b/vendor/github.com/coreos/ignition/v2/config/v3_2/types/storage.go @@ -19,6 +19,7 @@ import ( "strings" "github.com/coreos/ignition/v2/config/shared/errors" + "github.com/coreos/ignition/v2/config/util" vpath "github.com/coreos/vcontext/path" "github.com/coreos/vcontext/report" @@ -53,7 +54,7 @@ func (s Storage) Validate(c vpath.ContextPath) (r report.Report) { r.AddOnError(c.Append("links", i), errors.ErrLinkUsedSymlink) } } - if l1.Hard == nil || !*l1.Hard { + if !util.IsTrue(l1.Hard) { continue } target := path.Clean(l1.Target) diff --git a/vendor/github.com/coreos/ignition/v2/config/v3_2/types/unit.go b/vendor/github.com/coreos/ignition/v2/config/v3_2/types/unit.go index 8d1a5f00ff..bc2d3299c4 100644 --- a/vendor/github.com/coreos/ignition/v2/config/v3_2/types/unit.go +++ b/vendor/github.com/coreos/ignition/v2/config/v3_2/types/unit.go @@ -21,6 +21,7 @@ import ( "github.com/coreos/ignition/v2/config/shared/errors" "github.com/coreos/ignition/v2/config/shared/validations" + "github.com/coreos/ignition/v2/config/util" "github.com/coreos/go-systemd/v22/unit" cpath "github.com/coreos/vcontext/path" @@ -41,8 +42,7 @@ func (u Unit) Validate(c cpath.ContextPath) (r report.Report) { opts, err := validateUnitContent(u.Contents) r.AddOnError(c, err) - isEnabled := u.Enabled != nil && *u.Enabled - r.AddOnWarn(c, validations.ValidateInstallSection(u.Name, isEnabled, (u.Contents == nil || *u.Contents == ""), opts)) + r.AddOnWarn(c, validations.ValidateInstallSection(u.Name, util.IsTrue(u.Enabled), util.NilOrEmpty(u.Contents), opts)) return } diff --git a/vendor/github.com/coreos/vcontext/path/path.go b/vendor/github.com/coreos/vcontext/path/path.go index 3daadc784b..58be8baa1a 100644 --- a/vendor/github.com/coreos/vcontext/path/path.go +++ b/vendor/github.com/coreos/vcontext/path/path.go @@ -41,6 +41,10 @@ func (c ContextPath) String() string { return strings.Join(strs, ".") } +// Append returns a new ContextPath with the specified elements appended. +// The underlying array is sometimes reused, so if the original path might +// be used in future Append operations, the returned ContextPath should not +// be stored into a long-lived data structure. (Store a copy instead.) func (c ContextPath) Append(e ...interface{}) ContextPath { return ContextPath{ Path: append(c.Path, e...), @@ -48,6 +52,11 @@ func (c ContextPath) Append(e ...interface{}) ContextPath { } } +// Copy returns an identical ContextPath that shares no state with the +// original. When storing a ContextPath into a data structure, usually a +// copy should be stored instead of the original (for example, Report does +// this), since Append sometimes modifies the path's underlying array in +// place. func (c ContextPath) Copy() ContextPath { // make sure to preserve reflect.DeepEqual() equality var path []interface{} diff --git a/vendor/github.com/coreos/vcontext/report/report.go b/vendor/github.com/coreos/vcontext/report/report.go index 5378e8433c..618bc7536f 100644 --- a/vendor/github.com/coreos/vcontext/report/report.go +++ b/vendor/github.com/coreos/vcontext/report/report.go @@ -131,7 +131,7 @@ func (r *Report) AddOn(c path.ContextPath, err error, k EntryKind) { } r.Entries = append(r.Entries, Entry{ Message: err.Error(), - Context: c, + Context: c.Copy(), Kind: k, }) } diff --git a/vendor/github.com/diskfs/go-diskfs/.gitignore b/vendor/github.com/diskfs/go-diskfs/.gitignore new file mode 100644 index 0000000000..48b8bf9072 --- /dev/null +++ b/vendor/github.com/diskfs/go-diskfs/.gitignore @@ -0,0 +1 @@ +vendor/ diff --git a/vendor/github.com/diskfs/go-diskfs/LICENSE b/vendor/github.com/diskfs/go-diskfs/LICENSE new file mode 100644 index 0000000000..7f4b860b5f --- /dev/null +++ b/vendor/github.com/diskfs/go-diskfs/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Avi Deitcher + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/diskfs/go-diskfs/Makefile b/vendor/github.com/diskfs/go-diskfs/Makefile new file mode 100644 index 0000000000..1a41f5ffee --- /dev/null +++ b/vendor/github.com/diskfs/go-diskfs/Makefile @@ -0,0 +1,78 @@ +.PHONY: test image unit_test + +PACKAGE_NAME?=github.com/diskfs/go-diskfs +IMAGE ?= diskfs/go-diskfs:build +GOENV ?= GO111MODULE=on CGO_ENABLED=0 +GO_FILES ?= $(shell $(GOENV) go list ./...) +GOBIN ?= $(shell go env GOPATH)/bin +LINTER ?= $(GOBIN)/golangci-lint + + +# BUILDARCH is the host architecture +# ARCH is the target architecture +# we need to keep track of them separately +BUILDARCH ?= $(shell uname -m) +BUILDOS ?= $(shell uname -s | tr A-Z a-z) + +# canonicalized names for host architecture +ifeq ($(BUILDARCH),aarch64) +BUILDARCH=arm64 +endif +ifeq ($(BUILDARCH),x86_64) +BUILDARCH=amd64 +endif + +# unless otherwise set, I am building for my own architecture, i.e. not cross-compiling +# and for my OS +ARCH ?= $(BUILDARCH) +OS ?= $(BUILDOS) + +# canonicalized names for target architecture +ifeq ($(ARCH),aarch64) + override ARCH=arm64 +endif +ifeq ($(ARCH),x86_64) + override ARCH=amd64 +endif + +BUILD_CMD = CGO_ENABLED=0 GOOS=$(OS) GOARCH=$(ARCH) +ifdef DOCKERBUILD +BUILD_CMD = docker run --rm \ + -e GOARCH=$(ARCH) \ + -e GOOS=linux \ + -e CGO_ENABLED=0 \ + -v $(CURDIR):/go/src/$(PACKAGE_NAME) \ + -w /go/src/$(PACKAGE_NAME) \ + $(BUILDER_IMAGE) +endif + +image: + docker build -t $(IMAGE) testhelper/docker + +# because we keep making the same typo +unit-test: unit_test +unit_test: + @$(GOENV) go test $(GO_FILES) + +test: image + TEST_IMAGE=$(IMAGE) $(GOENV) go test $(GO_FILES) + +golangci-lint: $(LINTER) +$(LINTER): + curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(GOBIN) v1.41.0 + + +## Check the file format +fmt-check: + @if [ -n "$(shell $(BUILD_CMD) gofmt -l ${GO_FILES})" ]; then \ + $(BUILD_CMD) gofmt -s -e -d ${GO_FILES}; \ + exit 1; \ + fi + +## Lint the files +lint: golangci-lint + @$(BUILD_CMD) $(LINTER) run --disable-all --enable=revive ./... + +## Vet the files +vet: + @$(BUILD_CMD) go vet ${GO_FILES} diff --git a/vendor/github.com/diskfs/go-diskfs/README.md b/vendor/github.com/diskfs/go-diskfs/README.md new file mode 100644 index 0000000000..44331f40f3 --- /dev/null +++ b/vendor/github.com/diskfs/go-diskfs/README.md @@ -0,0 +1,158 @@ +# go-diskfs +go-diskfs is a [go](https://golang.org) library for performing manipulation of disks, disk images and filesystems natively in go. + +You can do nearly everything that go-diskfs provides using shell tools like gdisk/fdisk/mkfs.vfat/mtools/sgdisk/sfdisk/dd. However, these have the following limitations: + +* they need to be installed on your system +* you need to fork/exec to the command (and possibly a shell) to run them +* some are difficult to run without mounting disks, which may not be possible or may be risky in your environment, and almost certainly will require root privileges +* you do not want to launch a VM to run the excellent [libguestfs](https://libguestfs.org) and it may not be installed + +go-diskfs performs all modifications _natively_ in go, without mounting any disks. + +## Usage +Note: detailed go documentation is available at [godoc.org](https://godoc.org/github.com/diskfs/go-diskfs). + +### Concepts +`go-diskfs` has a few basic concepts: + +* Disk +* Partition +* Filesystem + +#### Disk +A disk represents either a file or block device that you access and manipulate. With access to the disk, you can: + +* read, modify or create a partition table +* open an existing or create a new filesystem + +#### Partition +A partition is a slice of a disk, beginning at one point and ending at a later one. You can have multiple partitions on a disk, and a partition table that describes how partitions are laid out on the disk. + +#### Filesystem +A filesystem is a construct that gives you access to create, read and write directories and files. + +You do *not* need a partitioned disk to work with a filesystem; filesystems can be an entire `disk`, just as they can be an entire block device. However, they also can be in a partition in a `disk` + +### Working With a Disk +Before you can do anything with a disk - partitions or filesystems - you need to access it. + +* If you have an existing disk or image file, you `Open()` it +* If you are creating a new one, usually just disk image files, you `Create()` it + +The disk will be opened read-write, with exclusive access. If it cannot do either, it will fail. + +Once you have a `Disk`, you can work with partitions or filesystems in it. + +#### Partitions on a Disk + +The following are the partition actions you can take on a disk: + +* `GetPartitionTable()` - if one exists. Will report the table layout and type. +* `Partition()` - partition the disk, overwriting any previous table if it exists + +As of this writing, supported partition formats are Master Boot Record (`mbr`) and GUID Partition Table (`gpt`). + +#### Filesystems on a Disk +Once you have a valid disk, and optionally partition, you can access filesystems on that disk image or partition. + +* `CreateFilesystem()` - create a filesystem in an individual partition or the entire disk +* `GetFilesystem()` - access an existing filesystem in a partition or the entire disk + +As of this writing, supported filesystems include `FAT32` and `ISO9660` (a.k.a. `.iso`). + +With a filesystem in hand, you can create, access and modify directories and files. + +* `Mkdir()` - make a directory in a filesystem +* `Readdir()` - read all of the entries in a directory +* `OpenFile()` - open a file for read, optionally write, create and append + +Note that `OpenFile()` is intended to match [os.OpenFile](https://golang.org/pkg/os/#OpenFile) and returns a `godiskfs.File` that closely matches [os.File](https://golang.org/pkg/os/#File) + +With a `File` in hand, you then can: + +* `Write(p []byte)` to the file +* `Read(b []byte)` from the file +* `Seek(offset int64, whence int)` to set the next read or write to an offset in the file + +### Read-Only Filesystems +Some filesystem types are intended to be created once, after which they are read-only, for example `ISO9660`/`.iso` and `squashfs`. + +`godiskfs` recognizes read-only filesystems and limits working with them to the following: + +* You can `GetFilesystem()` a read-only filesystem and do all read activities, but cannot write to them. Any attempt to `Mkdir()` or `OpenFile()` in write/append/create modes or `Write()` to the file will result in an error. +* You can `CreateFilesystem()` a read-only filesystem and write anything to it that you want. It will do all of its work in a "scratch" area, or temporary "workspace" directory on your local filesystem. When you are ready to complete it, you call `Finalize()`, after which it becomes read-only. If you forget to `Finalize()` it, you get... nothing. The `Finalize()` function exists only on read-only filesystems. + +### Example + +There are examples in the [examples/](./examples/) directory. Here is one to get you started. + +The following example will create a fully bootable EFI disk image. It assumes you have a bootable EFI file (any modern Linux kernel compiled with `CONFIG_EFI_STUB=y` will work) available. + +```go +import diskfs "github.com/diskfs/go-diskfs" + +espSize int := 100*1024*1024 // 100 MB +diskSize int := espSize + 4*1024*1024 // 104 MB + + +// create a disk image +diskImg := "/tmp/disk.img" +disk := diskfs.Create(diskImg, diskSize, diskfs.Raw) +// create a partition table +blkSize int := 512 +partitionSectors int := espSize / blkSize +partitionStart int := 2048 +partitionEnd int := partitionSectors - partitionStart + 1 +table := PartitionTable{ + type: partition.GPT, + partitions:[ + Partition{Start: partitionStart, End: partitionEnd, Type: partition.EFISystemPartition, Name: "EFI System"} + ] +} +// apply the partition table +err = disk.Partition(table) + + +/* + * create an ESP partition with some contents + */ +kernel, err := ioutil.ReadFile("/some/kernel/file") + +fs, err := disk.CreateFilesystem(0, diskfs.TypeFat32) + +// make our directories +err = fs.Mkdir("/EFI/BOOT") +rw, err := fs.OpenFile("/EFI/BOOT/BOOTX64.EFI", os.O_CREATE|os.O_RDRWR) + +err = rw.Write(kernel) + +``` + +## Tests +There are two ways to run tests: unit and integration (somewhat loosely defined). + +* Unit: these tests run entirely within the go process, primarily test unexported and some exported functions, and may use pre-defined test fixtures in a directory's `testdata/` subdirectory. By default, these are run by running `go test ./...` or just `make unit_test`. +* Integration: these test the exported functions and their ability to create or manipulate correct files. They are validated by running a [docker](https://docker.com) container with the right utilities to validate the output. These are run by running `TEST_IMAGE=diskfs/godiskfs go test ./...` or just `make test`. The value of `TEST_IMAGE` will be the image to use to run tests. + +For integration tests to work, the correct docker image must be available. You can create it by running `make image`. Check the [Makefile](./Makefile) to see the `docker build` command used to create it. Running `make test` automatically creates the image for you. + +### Integration Test Image +The integration test image contains the various tools necessary to test images: `mtools`, `fdisk`, `gdisk`, etc. It works on precisely one file at a time. In order to avoid docker volume mounting limitations with various OSes, instead of mounting the image `-v`, it expects to receive the image as a `stdin` stream, and saves it internally to the container as `/file.img`. + +For example, to test the existence of directory `/abc` on file `$PWD/foo.img`: + +``` +cat $PWD/foo.img | docker run -i --rm $INT_IMAGE mdir -i /file.img /abc +``` + + +## Plans +Future plans are to add the following: + +* embed boot code in `mbr` e.g. `altmbr.bin` (no need for `gpt` since an ESP with `/EFI/BOOT/BOOT.EFI` will boot) +* `ext4` filesystem +* `Joliet` extensions to `iso9660` +* `Rock Ridge` sparse file support - supports the flag, but not yet reading or writing +* `squashfs` sparse file support - currently treats sparse files as regular files +* `qcow` disk format diff --git a/vendor/github.com/diskfs/go-diskfs/disk/disk.go b/vendor/github.com/diskfs/go-diskfs/disk/disk.go new file mode 100644 index 0000000000..9fa0583b57 --- /dev/null +++ b/vendor/github.com/diskfs/go-diskfs/disk/disk.go @@ -0,0 +1,246 @@ +// Package disk provides utilities for working directly with a disk +// +// Most of the provided functions are intelligent wrappers around implementations of +// github.com/diskfs/go-diskfs/partition and github.com/diskfs/go-diskfs/filesystem +package disk + +import ( + "errors" + "fmt" + "io" + "os" + + log "github.com/sirupsen/logrus" + + "github.com/diskfs/go-diskfs/filesystem" + "github.com/diskfs/go-diskfs/filesystem/fat32" + "github.com/diskfs/go-diskfs/filesystem/iso9660" + "github.com/diskfs/go-diskfs/filesystem/squashfs" + "github.com/diskfs/go-diskfs/partition" +) + +// Disk is a reference to a single disk block device or image that has been Create() or Open() +type Disk struct { + File *os.File + Info os.FileInfo + Type Type + Size int64 + LogicalBlocksize int64 + PhysicalBlocksize int64 + Table partition.Table + Writable bool + DefaultBlocks bool +} + +// Type represents the type of disk this is +type Type int + +const ( + // File is a file-based disk image + File Type = iota + // Device is an OS-managed block device + Device +) + +var ( + errIncorrectOpenMode = errors.New("disk file or device not open for write") +) + +// GetPartitionTable retrieves a PartitionTable for a Disk +// +// If the table is able to be retrieved from the disk, it is saved in the instance. +// +// returns an error if the Disk is invalid or does not exist, or the partition table is unknown +func (d *Disk) GetPartitionTable() (partition.Table, error) { + t, err := partition.Read(d.File, int(d.LogicalBlocksize), int(d.PhysicalBlocksize)) + if err != nil { + return nil, err + } + d.Table = t + return t, nil +} + +// Partition applies a partition.Table implementation to a Disk +// +// The Table can have zero, one or more Partitions, each of which is unique to its +// implementation. E.g. MBR partitions in mbr.Table look different from GPT partitions in gpt.Table +// +// Actual writing of the table is delegated to the individual implementation +func (d *Disk) Partition(table partition.Table) error { + if !d.Writable { + return errIncorrectOpenMode + } + // fill in the uuid + err := table.Write(d.File, d.Size) + if err != nil { + return fmt.Errorf("Failed to write partition table: %v", err) + } + d.Table = table + // the partition table needs to be re-read only if + // the disk file is an actual block device + if d.Type == Device { + err = d.ReReadPartitionTable() + if err != nil { + return fmt.Errorf("Unable to re-read the partition table. Kernel still uses old partition table: %v", err) + } + } + return nil +} + +// WritePartitionContents writes the contents of an io.Reader to a given partition +// +// if successful, returns the number of bytes written +// +// returns an error if there was an error writing to the disk, reading from the reader, the table +// is invalid, or the partition is invalid +func (d *Disk) WritePartitionContents(partition int, reader io.Reader) (int64, error) { + if !d.Writable { + return -1, errIncorrectOpenMode + } + if d.Table == nil { + return -1, fmt.Errorf("cannot write contents of a partition on a disk without a partition table") + } + if partition < 0 { + return -1, fmt.Errorf("cannot write contents of a partition without specifying a partition") + } + partitions := d.Table.GetPartitions() + // API indexes from 1, but slice from 0 + if partition > len(partitions) { + return -1, fmt.Errorf("cannot write contents of partition %d which is greater than max partition %d", partition, len(partitions)) + } + written, err := partitions[partition-1].WriteContents(d.File, reader) + return int64(written), err +} + +// ReadPartitionContents reads the contents of a partition to an io.Writer +// +// if successful, returns the number of bytes read +// +// returns an error if there was an error reading from the disk, writing to the writer, the table +// is invalid, or the partition is invalid +func (d *Disk) ReadPartitionContents(partition int, writer io.Writer) (int64, error) { + if d.Table == nil { + return -1, fmt.Errorf("cannot read contents of a partition on a disk without a partition table") + } + if partition < 0 { + return -1, fmt.Errorf("cannot read contents of a partition without specifying a partition") + } + partitions := d.Table.GetPartitions() + // API indexes from 1, but slice from 0 + if partition > len(partitions) { + return -1, fmt.Errorf("cannot read contents of partition %d which is greater than max partition %d", partition, len(partitions)) + } + return partitions[partition-1].ReadContents(d.File, writer) +} + +// FilesystemSpec represents the specification of a filesystem to be created +type FilesystemSpec struct { + Partition int + FSType filesystem.Type + VolumeLabel string + WorkDir string +} + +// CreateFilesystem creates a filesystem on a disk image, the equivalent of mkfs. +// +// Required: +// * desired partition number, or 0 to create the filesystem on the entire block device or +// disk image, +// * the filesystem type from github.com/diskfs/go-diskfs/filesystem +// +// Optional: +// * volume label for those filesystems that support it; under Linux this shows +// in '/dev/disks/by-label/