diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 5c391ac..8432114 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -43,4 +43,3 @@ jobs: # builds. cache-from: type=gha cache-to: type=gha,mode=max - diff --git a/.gitignore b/.gitignore index e439962..12b12a3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ -kubeaid-bootstrap-script.config.yaml -*.test.config.yaml +scripts/export-aws-credentials.sh +scripts/export-hetzner-credentials.sh outputs/ + +*.swp diff --git a/Makefile b/Makefile index 1f18cc3..7bc48e4 100644 --- a/Makefile +++ b/Makefile @@ -1,31 +1,65 @@ # Needed for shell expansion SHELL = /bin/bash +CURRENT_DIR := $(CURDIR) +CONTAINER_NAME=kubeaid-bootstrap-script-dev +NETWORK_NAME=k3d-management-cluster +IMAGE_NAME=kubeaid-bootstrap-script-dev:latest .PHONY: build-image-dev build-image-dev: - @docker build -f ./build/Dockerfile.dev --build-arg CPU_ARCHITECTURE=arm64 -t kubeaid-bootstrap-script-dev . + @docker build -f ./build/Dockerfile.dev --build-arg CPU_ARCHITECTURE=arm64 -t $(IMAGE_NAME) . + +.PHONY: remove-image-dev +remove-image-dev: + @docker rmi $(IMAGE_NAME) .PHONY: run-container-dev -run-container-dev: - @docker run --name kubeaid-bootstrap-script-dev \ - -v /var/run/docker.sock:/var/run/docker.sock \ - -v .:/app \ - kubeaid-bootstrap-script-dev +run-container-dev: build-image-dev + @if ! docker network ls | grep -q $(NETWORK_NAME); then \ + docker network create $(NETWORK_NAME); \ + fi + @docker run --name $(CONTAINER_NAME) \ + --network $(NETWORK_NAME) \ + --detach \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -v $(CURRENT_DIR):/app \ + $(IMAGE_NAME) + +# -e SSH_AUTH_SOCK=/ssh-agent \ +# -v /dev/bus/usb:/dev/bus/usb \ +# -v $(SSH_AUTH_SOCK):/ssh-agent \ .PHONY: exec-container-dev exec-container-dev: - @docker exec -it kubeaid-bootstrap-script-dev /bin/sh + @docker exec -it $(CONTAINER_NAME) /bin/sh + +.PHONY: stop-container-dev +stop-container-dev: + @docker stop $(CONTAINER_NAME) + +.PHONY: remove-container-dev +remove-container-dev: stop-container-dev + @docker rm $(CONTAINER_NAME) .PHONY: generate-sample-config-aws-dev generate-sample-config-aws-dev: - @go run ./cmd generate-sample-config \ - --cloud aws \ - --k8s-version v1.31.0 + @go run ./cmd config generate aws + +.PHONY: bootstrap-cluster-dev-aws +bootstrap-cluster-dev-aws: + @go run ./cmd cluster bootstrap aws \ + --debug \ + --config /app/outputs/kubeaid-bootstrap-script.config.yaml \ + --skip-clusterctl-move +# --skip-kubeaid-config-setup -.PHONY: bootstrap-cluster-dev -bootstrap-cluster-dev: - @go run ./cmd bootstrap-cluster \ - --config-file /app/outputs/kubeaid-bootstrap-script.config.yaml +.PHONY: bootstrap-cluster-dev-hetzner +bootstrap-cluster-dev-hetzner: + @go run ./cmd cluster bootstrap hetzner \ + --debug \ + --config /app/outputs/kubeaid-bootstrap-script.config.yaml \ + --skip-clusterctl-move +# --skip-kubeaid-config-setup .PHONY: use-management-cluster use-management-cluster: @@ -36,9 +70,9 @@ use-provisioned-cluster: export KUBECONFIG=./outputs/provisioned-cluster.kubeconfig.yaml .PHONY: delete-provisioned-cluster -delete-provisioned-cluster: - KUBECONFIG=./outputs/management-cluster.kubeconfig.yaml \ - kubectl delete clusters/kubeaid-demo -n capi-cluster +delete-provisioned-cluster-dev: + @go run ./cmd cluster delete \ + --config /app/outputs/kubeaid-bootstrap-script.config.yaml .PHONY: delete-management-cluster delete-management-cluster: diff --git a/README.md b/README.md index fc43764..b6e68fd 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,27 @@ Once you're inside the container, use `make generate-sample-config-aws-dev` to g Then run `make bootstrap-cluster-dev` to bootstrap the cluster! +## TODOs + +- [ ] Check Git URL if SSH agent is used. +- [ ] Validation for sshagentauth (should not accept https url). +- [x] `--debug` flag to print command execution outputs. +- [ ] Support adding admin SSH keys via config file. +- [ ] Support using HTTPS for ArgoCD apps. +- [ ] Use ArgoCD sync waves so that we don't need to explicitly sync the Infrastructure Provider component first. + ## REFERENCES - [Server-Side Apply](https://kubernetes.io/docs/reference/using-api/server-side-apply/#comparison-with-client-side-apply) + +- [The definitive guide to building Golang based CLI](https://www.youtube.com/watch?v=SSRIn5DAmyw) + +- [AWS S3 Sync Command – Guide with Examples](https://spacelift.io/blog/aws-s3-sync) + +- How KubeAid backs up Sealed Secrets using a CRONJob : https://github.com/Obmondo/kubeaid/blob/master/argocd-helm-charts/sealed-secrets/templates/configmap.yaml + +- [Key Management](https://playbook.stakater.com/content/workshop/sealed-secrets/management.html) + +- [Secret Rotation](https://github.com/bitnami-labs/sealed-secrets?tab=readme-ov-file#secret-rotation) + +- [Kubernetes Backups, Upgrades, Migrations - with Velero](https://youtu.be/zybLTQER0yY?si=qOZcizBqPOeouJ7y) diff --git a/build/Dockerfile b/build/Dockerfile index dc32935..b3d2a01 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -25,4 +25,4 @@ RUN CPU_ARCHITECTURE=$([ "$(uname -m)" = "x86_64" ] && echo "amd64" || echo "arm COPY --from=builder /app/kubeaid-bootstrap-script /usr/local/bin/kubeaid-bootstrap-script -CMD sleep infinity +CMD ["sleep", "infinity"] diff --git a/build/Dockerfile.dev b/build/Dockerfile.dev index 6e66ffe..07b5897 100644 --- a/build/Dockerfile.dev +++ b/build/Dockerfile.dev @@ -11,4 +11,4 @@ RUN CPU_ARCHITECTURE=$([ "$(uname -m)" = "x86_64" ] && echo "amd64" || echo "arm WORKDIR /app -CMD sleep infinity +CMD ["sleep", "infinity"] diff --git a/cmd/bootstrap_cluster/bootstrap_cluster.go b/cmd/bootstrap_cluster/bootstrap_cluster.go deleted file mode 100644 index 3b09508..0000000 --- a/cmd/bootstrap_cluster/bootstrap_cluster.go +++ /dev/null @@ -1,284 +0,0 @@ -package bootstrap_cluster - -import ( - "fmt" - "log" - "log/slog" - "os" - "path" - "strconv" - "strings" - "time" - - "github.com/Obmondo/kubeaid-bootstrap-script/config" - "github.com/Obmondo/kubeaid-bootstrap-script/constants" - "github.com/Obmondo/kubeaid-bootstrap-script/utils" - "github.com/go-git/go-git/v5" - "github.com/go-git/go-git/v5/plumbing" - "github.com/urfave/cli/v2" -) - -var currentTime = time.Now().Unix() - -func BootstrapCluster(ctx *cli.Context) error { - // Initialize temp dir where repositories will be cloned. - utils.InitTempDir() - - // Parse the config file. - configFilePath := ctx.Path(constants.FlagNameConfigFile) - constants.ParsedConfig = config.ParseConfigFile(configFilePath) - - // Set environment variables. - utils.SetEnvs() - - // Detect git authentication method. - gitAuthMethod := utils.GetGitAuthMethod() - - // Create the management cluster (using K3d). - managementClusterName := "management-cluster" - slog.Info("Spinning up K3d management cluster (in the host machine)", slog.String("name", managementClusterName)) - utils.ExecuteCommandOrDie(fmt.Sprintf(` - k3d cluster create %s \ - --servers 1 --agents 2 \ - --image rancher/k3s:v1.31.1-k3s1 \ - --wait - `, managementClusterName)) - - // Install Sealed Secrets. - utils.InstallSealedSecrets() - - var ( - kubeaidConfigForkDir = path.Join(constants.TempDir, "kubeaid-config") - clusterDir = path.Join(kubeaidConfigForkDir, "k8s", constants.ParsedConfig.Cluster.ClusterName) - - // Clone the KubeAid config fork locally. - kubeaidConfigFork = utils.GitCloneRepo(constants.ParsedConfig.Forks.KubeaidConfigForkURL, kubeaidConfigForkDir, gitAuthMethod) - defaultBranchName = utils.GetDefaultBranchName(kubeaidConfigFork) - ) - - // Prepare the KubeAid config fork. - { - // Create and checkout to a new branch, in the kubeaid-config fork. - workTree, err := kubeaidConfigFork.Worktree() - if err != nil { - log.Fatalf("Failed getting kubeaid-config fork worktree : %v", err) - } - newBranchName := fmt.Sprintf("kubeaid-%s-%d", constants.ParsedConfig.Cluster.ClusterName, currentTime) - utils.CreateAndCheckoutToBranch(kubeaidConfigFork, newBranchName, workTree) - - // Create files in the new branch of the KubeAid config fork. - createKubeaidConfigFiles(clusterDir, gitAuthMethod) - - // Add, commit and push the changes. - commitMessage := fmt.Sprintf("KubeAid bootstrap setup for argo-cd applications on %s\n", constants.ParsedConfig.Cluster.ClusterName) - commitHash := utils.AddCommitAndPushChanges(kubeaidConfigFork, workTree, newBranchName, gitAuthMethod, constants.ParsedConfig.Cluster.ClusterName, commitMessage) - - // The user now needs to go ahead and create a PR from the new to the default branch. Then he - // needs to merge that branch. - // We can't create the PR for the user, since PRs are not part of the core git lib. They are - // specific to the git platform the user is on. - - // Wait until the PR gets merged. - utils.WaitUntilPRMerged(kubeaidConfigFork, defaultBranchName, commitHash, gitAuthMethod, newBranchName) - } - - // Cloud specific tasks. - switch { - case constants.ParsedConfig.Cloud.AWS != nil: - // The clusterawsadm utility takes the credentials that you set as environment variables and - // uses them to create a CloudFormation stack in your AWS account with the correct IAM resources. - // NOTE : This requires admin privileges. - output, err := utils.ExecuteCommand("clusterawsadm bootstrap iam create-cloudformation-stack") - // - // If an error occurs and it's not about AWS Cloudformation stack already existing, then panic. - if err != nil && !strings.Contains(output, "already exists, updating") { - log.Fatalf("Command execution failed : %v", output) - } - - default: - utils.Unreachable() - } - - // Provisioning the main cluster. - { - // Install and setup ArgoCD. - utils.InstallAndSetupArgoCD(clusterDir) - - // Create the capi-cluster / capi-cluster- namespace, where the 'cloud-credentials' - // Kubernetes Secret will exist. - capiClusterNamespace := utils.GetCapiClusterNamespace() - utils.ExecuteCommandOrDie(fmt.Sprintf("kubectl create namespace %s", capiClusterNamespace)) - - // Sync the root, CertManager, Secrets and ClusterAPI ArgoCD Apps one by one. - argocdAppsToBeSynced := []string{ - "root", - "cert-manager", - "secrets", - "cluster-api", - } - for _, argoCDApp := range argocdAppsToBeSynced { - utils.ExecuteCommandOrDie(fmt.Sprintf("argocd app sync argo-cd/%s", argoCDApp)) - } - - // Sync the Infrastructure Provider component and then the whole CAPI Cluster ArgoCD App. - // TODO : Use ArgoCD sync waves so that we don't need to explicitly sync the Infrastructure - // Provider component first. - utils.SyncInfrastructureProvider() - for { - /* - Sometimes, we get this error : - - one or more objects failed to apply, reason: - - (1) Internal error occurred: failed calling webhook - "default.kubeadmcontrolplane.controlplane.cluster.x-k8s.io": failed to call webhook: - Post "https://capi-kubeadm-control-plane-webhook-service.kubeadm-control-plane-system.svc:443/mutate-controlplane-cluster-x-k8s-io-v1beta1-kubeadmcontrolplane?timeout=10s": - no endpoints available for service "capi-kubeadm-control-plane-webhook-service" - - (2) Internal error occurred: failed calling webhook - "default.kubeadmconfig.bootstrap.cluster.x-k8s.io": failed to call webhook: - Post "https://capi-kubeadm-bootstrap-webhook-service.kubeadm-bootstrap-system.svc:443/mutate-bootstrap-cluster-x-k8s-io-v1beta1-kubeadmconfig?timeout=10s": - no endpoints available for service "capi-kubeadm-bootstrap-webhook-service" - - In that case, we'll retry. Otherwise, we'll fail if the 'error' word exists in the output. - If not that means the command execution is successfull. - */ - output, _ := utils.ExecuteCommand("argocd app sync argo-cd/capi-cluster") - if strings.Contains(output, "failed to call webhook") { - slog.Info("Waiting for kubeadm-control-plane-system and kubeadm-bootstrap-system webhooks to be available....") - time.Sleep(5 * time.Second) - continue - } - - if strings.Contains(output, "error") { - log.Fatalf("Failed syncing CAPI Cluster ArgoCD App : %v", output) - } - - break - } - - // Wait for the main cluster to be provisioned and ready. - for { - clusterPhase := utils.ExecuteCommandOrDie(fmt.Sprintf( - "kubectl get cluster/%s -n %s -o jsonpath=\"{.status.phase}\"", - constants.ParsedConfig.Cluster.ClusterName, capiClusterNamespace, - )) - clusterCondition := utils.ExecuteCommandOrDie(fmt.Sprintf( - "kubectl get cluster/%s -n %s -o jsonpath=\"{.status.conditions[0].type}\"", - constants.ParsedConfig.Cluster.ClusterName, capiClusterNamespace, - )) - if clusterPhase == "Provisioned" && clusterCondition == "Ready" { - break - } - - slog.Info("Waiting for the main cluster to be provisioned and ready....") - time.Sleep(time.Minute) - } - - // Get the provisioned cluster's kubeconifg. - utils.ExecuteCommandOrDie(fmt.Sprintf(` - clusterctl get kubeconfig %s -n %s > %s - chmod 600 %s - `, constants.ParsedConfig.Cluster.ClusterName, capiClusterNamespace, constants.OutputPathProvisionedClusterKubeconfig, constants.OutputPathProvisionedClusterKubeconfig)) - } - - // Let the provisioned cluster manage itself. - { - // Update the KUBECONFIG environment variable's value to the provisioned cluster's kubeconfig. - os.Setenv("KUBECONFIG", constants.OutputPathProvisionedClusterKubeconfig) - - // Wait for the Kubernetes API server to be reachable and atleast 1 worker node to be - // initialized. - for { - initializedNodeCountAsString, _ := utils.ExecuteCommand(` - kubectl get nodes --no-headers -o custom-columns=NAME:.metadata.name,TAINTS:.spec.taints \ - | grep -v node.cluster.x-k8s.io/uninitialized \ - | wc -l - `) - initializedNodeCountAsString = strings.TrimSpace(initializedNodeCountAsString) - - initializedNodeCount, _ := strconv.Atoi(initializedNodeCountAsString) - if initializedNodeCount > constants.ParsedConfig.Cloud.AWS.ControlPlaneReplicas { - break - } - - slog.Info("Waiting for the provisioned cluster's Kubernetes API server to be reachable and atleast 1 worker node to be initialized....") - time.Sleep(time.Minute) - } - - // Install Sealed Secrets. - utils.InstallSealedSecrets() - - // We need to update the Sealed Secrets in the cluster specific directory in the kubeaid-config - // fork. - // Those represent Kubernetes Secrets encyrpted using the private key of the Sealed Secrets - // controller installed in the K3d management cluster. - // We need to update them, by encrypting the underlying Kubernetes Secrets using the private - // key of the Sealed Secrets controller installed in the provisioned main cluster. - { - // Checkout to default branch. - workTree, err := kubeaidConfigFork.Worktree() - if err != nil { - log.Fatalf("Failed getting kubeaid-config fork worktree : %v", err) - } - if err = workTree.Checkout(&git.CheckoutOptions{ - Branch: plumbing.ReferenceName("refs/heads/" + defaultBranchName), - }); err != nil { - log.Fatalf("Failed checking out to branch %s : %v", defaultBranchName, err) - } - - // Pull changes done by the merged PR. - if err := workTree.Pull(&git.PullOptions{}); err != nil { - log.Fatalf("Failed pulling changes in the default branch for kubeaid-config fork") - } - - // Update cloud credentials Kubernetes Secret. - generateCloudCredentialsSealedSecret(clusterDir) - // - // Update Kubernetes Secret containing credentials for ArgoCD to access the kubeaid-config - // fork. - embeddedTemplateName := constants.TemplateNameKubeaidConfigRepo - destinationFilePath := path.Join(clusterDir, strings.TrimSuffix(embeddedTemplateName, ".tmpl")) - createFileFromTemplate(destinationFilePath, embeddedTemplateName, getTemplateValues()) - - // Add, commit and push changes. - commitMessage := fmt.Sprintf("Updating Kubernetes Secrets for cluster %s by encrypting them with private key of Sealed Secrets controller in the provisioned main cluster\n", constants.ParsedConfig.Cluster.ClusterName) - utils.AddCommitAndPushChanges(kubeaidConfigFork, workTree, defaultBranchName, gitAuthMethod, constants.ParsedConfig.Cluster.ClusterName, commitMessage) - } - - // Install and setup ArgoCD. - utils.InstallAndSetupArgoCD(clusterDir) - - // Create the capi-cluster / capi-cluster- namespace, where the 'cloud-credentials' - // Kubernetes Secret will exist. - capiClusterNamespace := utils.GetCapiClusterNamespace() - utils.ExecuteCommandOrDie(fmt.Sprintf("kubectl create namespace %s", capiClusterNamespace)) - - // Sync the root, cert-manager, sealed-secrets, secrets, kube-prometheus and cluster-api ArgoCD - // Apps. - argocdAppsToBeSynced := []string{ - "root", - "cert-manager", - "secrets", - "cluster-api", - } - for _, argoCDApp := range argocdAppsToBeSynced { - utils.ExecuteCommandOrDie(fmt.Sprintf("argocd app sync argo-cd/%s", argoCDApp)) - } - - // Sync the Infrastructure Provider component of the CAPI Cluster ArgoCD App. - utils.SyncInfrastructureProvider() - - // Move ClusterAPI manifests to the provisioned cluster. - utils.ExecuteCommandOrDie(fmt.Sprintf( - "clusterctl move --kubeconfig %s --namespace %s --to-kubeconfig %s", - constants.OutputPathManagementClusterKubeconfig, capiClusterNamespace, constants.OutputPathProvisionedClusterKubeconfig, - )) - - // Sync cluster-autoscaler ArgoCD App. - utils.ExecuteCommandOrDie("argocd app sync argo-cd/cluster-autoscaler") - } - - slog.Info("Cluster provisioned successfully 🎉🎉 !", slog.String("kubeconfig", constants.OutputPathProvisionedClusterKubeconfig)) - return nil -} diff --git a/cmd/bootstrap_cluster/create_kubeaid_config_files.go b/cmd/bootstrap_cluster/create_kubeaid_config_files.go deleted file mode 100644 index e387348..0000000 --- a/cmd/bootstrap_cluster/create_kubeaid_config_files.go +++ /dev/null @@ -1,175 +0,0 @@ -package bootstrap_cluster - -import ( - "embed" - "fmt" - "log" - "log/slog" - "os" - "path" - "strings" - - "github.com/Obmondo/kubeaid-bootstrap-script/config" - "github.com/Obmondo/kubeaid-bootstrap-script/constants" - "github.com/Obmondo/kubeaid-bootstrap-script/utils" - "github.com/go-git/go-git/v5/plumbing/transport" -) - -//go:embed templates/* -var KubeaidConfigFileTemplates embed.FS - -type TemplateValues struct { - CustomerID string - - GitUsername, - GitPassword string - - config.ClusterConfig - config.ForksConfig - config.AWSConfig - config.MonitoringConfig - - CAPIClusterNamespace string -} - -func getTemplateValues() *TemplateValues { - return &TemplateValues{ - CustomerID: constants.ParsedConfig.CustomerID, - GitUsername: constants.ParsedConfig.Git.Username, - GitPassword: constants.ParsedConfig.Git.Password, - ClusterConfig: constants.ParsedConfig.Cluster, - ForksConfig: constants.ParsedConfig.Forks, - AWSConfig: *constants.ParsedConfig.Cloud.AWS, - MonitoringConfig: constants.ParsedConfig.Monitoring, - CAPIClusterNamespace: utils.GetCapiClusterNamespace(), - } -} - -// Responsible for bootstrapping all the files for the given cluster in the kubeaid-config fork. -func createKubeaidConfigFiles(clusterDir string, gitAuthMethod transport.AuthMethod) { - // Get templates. - embeddedTemplateNames := getEmbeddedTemplateNames() - templateValues := getTemplateValues() - - // Create a file from each template. - for _, embeddedTemplateName := range embeddedTemplateNames { - destinationFilePath := path.Join(clusterDir, strings.TrimSuffix(embeddedTemplateName, ".tmpl")) - createFileFromTemplate(destinationFilePath, embeddedTemplateName, templateValues) - } - - // Generate Kubernetes Sealed Secret containing cloud credentials. - generateCloudCredentialsSealedSecret(clusterDir) - - // Build KubePrometheus. - buildKubePrometheus(clusterDir, gitAuthMethod, templateValues) -} - -// Returns the list of embedded template names based on the underlying cloud provider. -func getEmbeddedTemplateNames() []string { - // Templates common for all cloud providers. - embeddedTemplateNames := constants.CommonEmbeddedTemplateNames - - // Add cloud provider specific templates. - var cloudSpecificEmbeddedTemplateNames []string - switch { - case constants.ParsedConfig.Cloud.AWS != nil: - cloudSpecificEmbeddedTemplateNames = constants.AWSSpecificEmbeddedTemplateNames - - case (constants.ParsedConfig.Cloud.Azure != nil) || (constants.ParsedConfig.Cloud.Hetzner != nil): - utils.Unreachable() - - default: - utils.Unreachable() - } - embeddedTemplateNames = append(embeddedTemplateNames, cloudSpecificEmbeddedTemplateNames...) - - // Add Obmondo K8s Agent related templates if 'monitoring.connectObmondo' is set to true. - if constants.ParsedConfig.Monitoring.ConnectObmondo { - embeddedTemplateNames = append(embeddedTemplateNames, - "argocd-apps/templates/obmondo-k8s-agent.app.yaml.tmpl", - "argocd-apps/obmondo-k8s-agent.values.yaml.tmpl", - ) - } - - return embeddedTemplateNames -} - -// Creates file from the given template. -func createFileFromTemplate(destinationFilePath, embeddedTemplateName string, templateValues *TemplateValues) { - utils.CreateIntermediateDirectories(destinationFilePath) - - // Open the destination file. - destinationFile, err := os.OpenFile(destinationFilePath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644) - if err != nil { - log.Fatalf("Failed opening file at %s : %v", destinationFilePath, err) - } - defer destinationFile.Close() - - // Execute the corresponding template with the template values. Then write the execution result - // to that file. - content := utils.ParseAndExecuteTemplate(&KubeaidConfigFileTemplates, path.Join("templates/", embeddedTemplateName), templateValues) - destinationFile.Write(content) - - // If the destination file contains a Kubernetes Secret, then generate and replace it's contents - // with corresponding Sealed Secret. - if strings.HasPrefix(embeddedTemplateName, "sealed-secrets") { - utils.GenerateSealedSecret(destinationFilePath) - } - - slog.Info("Created file in KubeAid config fork", slog.String("file-path", destinationFilePath)) -} - -// Creates the jsonnet vars file for the cluster. Then executes KubeAid's kube-prometheus build -// script. -func buildKubePrometheus(clusterDir string, gitAuthMethod transport.AuthMethod, templateValues *TemplateValues) { - // Create the jsonnet vars file. - jsonnetVarsFilePath := fmt.Sprintf("%s/%s-vars.jsonnet", clusterDir, constants.ParsedConfig.Cluster.ClusterName) - createFileFromTemplate(jsonnetVarsFilePath, constants.TemplateNameJsonnet, templateValues) - - // Create the kube-prometheus folder for the given cluster, in the kubeaid-config fork. - kubePrometheusDir := fmt.Sprintf("%s/kube-prometheus", clusterDir) - if err := os.MkdirAll(kubePrometheusDir, os.ModePerm); err != nil { - log.Fatalf("Failed creating intermediate paths for dir %s : %v", kubePrometheusDir, err) - } - - // Clone the KubeAid fork locally. - kubeaidForkDir := constants.TempDir + "/kubeaid" - utils.GitCloneRepo(constants.ParsedConfig.Forks.KubeaidForkURL, kubeaidForkDir, gitAuthMethod) - - // Run the KubePrometheus build script. - slog.Info("Running KubePrometheus build script....") - kubePrometheusBuildScriptPath := fmt.Sprintf("%s/build/kube-prometheus/build.sh", kubeaidForkDir) - utils.ExecuteCommandOrDie(fmt.Sprintf("%s %s", kubePrometheusBuildScriptPath, clusterDir)) -} - -// Generates a Kubernetes Secret named 'cloud-credentials', which will be used by Cluster API to -// communicate with the underlying cloud provider. -// It then encrypts the Kubernetes Secret with the private key of the Sealed Secrets controller -// installed in the underlying Kubernetes cluster. -func generateCloudCredentialsSealedSecret(clusterDir string) { - capiClusterNamespace := utils.GetCapiClusterNamespace() - - destinationDir := path.Join(clusterDir, "sealed-secrets/", capiClusterNamespace) - if err := os.MkdirAll(destinationDir, 0700); err != nil { - log.Fatalf("Failed creating intermediate directories for %s : %v", destinationDir, err) - } - destinationFilePath := fmt.Sprintf("%s/cloud-credentials.secret.yaml", destinationDir) - - switch { - case constants.ParsedConfig.Cloud.AWS != nil: - utils.ExecuteCommandOrDie(fmt.Sprintf(` - kubectl create secret generic cloud-credentials \ - --dry-run=client \ - --namespace %s \ - --from-literal=AWS_B64ENCODED_CREDENTIALS=%s \ - -o yaml > %s - `, - capiClusterNamespace, os.Getenv(constants.EnvNameAWSB64EcodedCredentials), destinationFilePath), - ) - - default: - utils.Unreachable() - } - - utils.GenerateSealedSecret(destinationFilePath) -} diff --git a/cmd/bootstrap_cluster/templates/argocd-apps/capi-cluster.values.yaml.tmpl b/cmd/bootstrap_cluster/templates/argocd-apps/capi-cluster.values.yaml.tmpl deleted file mode 100644 index c4d5f95..0000000 --- a/cmd/bootstrap_cluster/templates/argocd-apps/capi-cluster.values.yaml.tmpl +++ /dev/null @@ -1,35 +0,0 @@ -global: - clusterName: {{ .ClusterName }} - {{- if .CustomerID }} - customerid: {{ .CustomerID }} - {{- end }} - kubernetes: - version: {{ .K8sVersion }} - kubeaid: - repo: {{ .KubeaidForkURL }} - kubeaidConfig: - repo: {{ .KubeaidConfigForkURL }} - -provider: - aws: true - -aws: - secretName: cloud-credentials - sshKeyName: {{ .SSHKeyName }} - region: {{ .Region }} - bastion: - enabled: true - vpc: - cidrBlock: 10.14.0.0/22 - pods: - cidrBlock: 10.244.0.0/16 - controlPlane: - loadBalancer: - loadBalancerType: nlb - region: {{ .Region }} - replicas: {{ .ControlPlaneReplicas }} - instanceType: {{ .ControlPlaneInstanceType }} - ami: - id: {{ .ControlPlaneAMI }} - nodeGroups: -{{ .NodeGroups | toYaml | indent 2 }} diff --git a/cmd/bootstrap_cluster/templates/argocd-apps/cluster-autoscaler.values.yaml.tmpl b/cmd/bootstrap_cluster/templates/argocd-apps/cluster-autoscaler.values.yaml.tmpl deleted file mode 100644 index 5b44f34..0000000 --- a/cmd/bootstrap_cluster/templates/argocd-apps/cluster-autoscaler.values.yaml.tmpl +++ /dev/null @@ -1,19 +0,0 @@ -cluster-autoscaler: - cloudProvider: clusterapi - - # Cluster API mode : incluster-incluster / incluster-kubeconfig / kubeconfig-incluster / kubeconfig-kubeconfig / single-kubeconfig. - # Syntax: workloadClusterMode-ManagementClusterMode - # - # For 'kubeconfig-kubeconfig', 'incluster-kubeconfig' and 'single-kubeconfig' you always must - # mount the external kubeconfig using either 'extraVolumeSecrets' or 'extraMounts' and - # 'extraVolumes'. - # - # If you dont set 'clusterAPIKubeconfigSecret'and thus use an in-cluster config or want to use a - # non CAPI generated kubeconfig you must do so for the workload kubeconfig as well - # - # REFER : https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/cloudprovider/clusterapi/README.md#connecting-cluster-autoscaler-to-cluster-api-management-and-workload-clusters - clusterAPIMode: incluster-incluster - - autoDiscovery: - clusterName: {{ .ClusterName }} - namespace: {{ .CAPIClusterNamespace }} diff --git a/cmd/bootstrap_cluster/templates/argocd-apps/sealed-secrets.values.yaml.tmpl b/cmd/bootstrap_cluster/templates/argocd-apps/sealed-secrets.values.yaml.tmpl deleted file mode 100644 index ed97d53..0000000 --- a/cmd/bootstrap_cluster/templates/argocd-apps/sealed-secrets.values.yaml.tmpl +++ /dev/null @@ -1 +0,0 @@ ---- diff --git a/cmd/bootstrap_cluster/templates/argocd-apps/traefik.values.yaml.tmpl b/cmd/bootstrap_cluster/templates/argocd-apps/traefik.values.yaml.tmpl deleted file mode 100644 index ed97d53..0000000 --- a/cmd/bootstrap_cluster/templates/argocd-apps/traefik.values.yaml.tmpl +++ /dev/null @@ -1 +0,0 @@ ---- diff --git a/cmd/cluster/bootstrap/aws.go b/cmd/cluster/bootstrap/aws.go new file mode 100644 index 0000000..0e807fc --- /dev/null +++ b/cmd/cluster/bootstrap/aws.go @@ -0,0 +1,20 @@ +package bootstrap + +import ( + "github.com/Obmondo/kubeaid-bootstrap-script/config" + "github.com/Obmondo/kubeaid-bootstrap-script/pkg/cloud/aws" + "github.com/Obmondo/kubeaid-bootstrap-script/pkg/core" + "github.com/spf13/cobra" +) + +var AWSCmd = &cobra.Command{ + Use: "aws", + Run: func(cmd *cobra.Command, args []string) { + core.BootstrapCluster(cmd.Context(), skipKubeAidConfigSetup, skipClusterctlMove, aws.NewAWSCloudProvider(), false) + }, +} + +func init() { + // Flags. + config.RegisterAWSCredentialsFlags(AWSCmd) +} diff --git a/cmd/cluster/bootstrap/bootstrap.go b/cmd/cluster/bootstrap/bootstrap.go new file mode 100644 index 0000000..8ca55cc --- /dev/null +++ b/cmd/cluster/bootstrap/bootstrap.go @@ -0,0 +1,32 @@ +package bootstrap + +import ( + "github.com/Obmondo/kubeaid-bootstrap-script/constants" + "github.com/spf13/cobra" +) + +var BootstrapCmd = &cobra.Command{ + Use: "bootstrap", + RunE: func(cmd *cobra.Command, args []string) error { + return cmd.Help() + }, +} + +var ( + skipKubeAidConfigSetup, + skipClusterctlMove bool +) + +func init() { + // Subcommands. + BootstrapCmd.AddCommand(AWSCmd) + BootstrapCmd.AddCommand(HetznerCmd) + + // Flags. + + BootstrapCmd.PersistentFlags(). + BoolVar(&skipKubeAidConfigSetup, constants.FlagNameSkipKubeAidConfigSetup, false, "Skip the initial KubeAid config repo setup step") + + BootstrapCmd.PersistentFlags(). + BoolVar(&skipClusterctlMove, constants.FlagNameSkipClusterctlMove, false, "Skip executing the 'clusterctl move' command") +} diff --git a/cmd/cluster/bootstrap/hetzner.go b/cmd/cluster/bootstrap/hetzner.go new file mode 100644 index 0000000..561816c --- /dev/null +++ b/cmd/cluster/bootstrap/hetzner.go @@ -0,0 +1,20 @@ +package bootstrap + +import ( + "github.com/Obmondo/kubeaid-bootstrap-script/config" + "github.com/Obmondo/kubeaid-bootstrap-script/pkg/cloud/hetzner" + "github.com/Obmondo/kubeaid-bootstrap-script/pkg/core" + "github.com/spf13/cobra" +) + +var HetznerCmd = &cobra.Command{ + Use: "hetzner", + Run: func(cmd *cobra.Command, args []string) { + core.BootstrapCluster(cmd.Context(), skipKubeAidConfigSetup, skipClusterctlMove, hetzner.NewHetznerCloudProvider(), false) + }, +} + +func init() { + // Flags. + config.RegisterHetznerCredentialsFlags(HetznerCmd) +} diff --git a/cmd/cluster/cluster.go b/cmd/cluster/cluster.go new file mode 100644 index 0000000..85b3bb9 --- /dev/null +++ b/cmd/cluster/cluster.go @@ -0,0 +1,34 @@ +package cluster + +import ( + "github.com/Obmondo/kubeaid-bootstrap-script/cmd/cluster/bootstrap" + delete_ "github.com/Obmondo/kubeaid-bootstrap-script/cmd/cluster/delete" + "github.com/Obmondo/kubeaid-bootstrap-script/config" + "github.com/Obmondo/kubeaid-bootstrap-script/utils" + "github.com/spf13/cobra" +) + +var ClusterCmd = &cobra.Command{ + Use: "cluster", + + PersistentPreRun: func(cmd *cobra.Command, args []string) { + // Initialize config. + config.InitConfig() + + // Initialize temp directory. + utils.InitTempDir() + }, + + RunE: func(cmd *cobra.Command, args []string) error { + return cmd.Help() + }, +} + +func init() { + // Subcommands. + ClusterCmd.AddCommand(bootstrap.BootstrapCmd) + ClusterCmd.AddCommand(delete_.DeleteCmd) + + // Flags. + config.RegisterConfigFilePathFlag(ClusterCmd) +} diff --git a/cmd/cluster/delete/delete.go b/cmd/cluster/delete/delete.go new file mode 100644 index 0000000..4a2a5d4 --- /dev/null +++ b/cmd/cluster/delete/delete.go @@ -0,0 +1,13 @@ +package delete + +import ( + "github.com/Obmondo/kubeaid-bootstrap-script/pkg/core" + "github.com/spf13/cobra" +) + +var DeleteCmd = &cobra.Command{ + Use: "delete", + Run: func(cmd *cobra.Command, args []string) { + core.DeleteCluster(cmd.Context()) + }, +} diff --git a/cmd/cluster/recover/aws.go b/cmd/cluster/recover/aws.go new file mode 100644 index 0000000..02ccfe7 --- /dev/null +++ b/cmd/cluster/recover/aws.go @@ -0,0 +1,20 @@ +package recover + +import ( + "github.com/Obmondo/kubeaid-bootstrap-script/config" + "github.com/Obmondo/kubeaid-bootstrap-script/pkg/cloud/aws" + "github.com/Obmondo/kubeaid-bootstrap-script/pkg/core" + "github.com/spf13/cobra" +) + +var AWSCmd = &cobra.Command{ + Use: "aws", + Run: func(cmd *cobra.Command, args []string) { + core.RecoverCluster(cmd.Context(), aws.NewAWSCloudProvider()) + }, +} + +func init() { + // Flags. + config.RegisterAWSCredentialsFlags(AWSCmd) +} diff --git a/cmd/cluster/recover/hetzner.go b/cmd/cluster/recover/hetzner.go new file mode 100644 index 0000000..a6ab96e --- /dev/null +++ b/cmd/cluster/recover/hetzner.go @@ -0,0 +1,20 @@ +package recover + +import ( + "github.com/Obmondo/kubeaid-bootstrap-script/config" + "github.com/Obmondo/kubeaid-bootstrap-script/pkg/cloud/hetzner" + "github.com/Obmondo/kubeaid-bootstrap-script/pkg/core" + "github.com/spf13/cobra" +) + +var HetznerCmd = &cobra.Command{ + Use: "hetzner", + Run: func(cmd *cobra.Command, args []string) { + core.RecoverCluster(cmd.Context(), hetzner.NewHetznerCloudProvider()) + }, +} + +func init() { + // Flags. + config.RegisterHetznerCredentialsFlags(HetznerCmd) +} diff --git a/cmd/cluster/recover/recover.go b/cmd/cluster/recover/recover.go new file mode 100644 index 0000000..85ca29e --- /dev/null +++ b/cmd/cluster/recover/recover.go @@ -0,0 +1,18 @@ +package recover + +import ( + "github.com/spf13/cobra" +) + +var RecoverCmd = &cobra.Command{ + Use: "recover", + RunE: func(cmd *cobra.Command, args []string) error { + return cmd.Help() + }, +} + +func init() { + // Subcommands. + RecoverCmd.AddCommand(AWSCmd) + RecoverCmd.AddCommand(HetznerCmd) +} diff --git a/cmd/config/config.go b/cmd/config/config.go new file mode 100644 index 0000000..cc59ce2 --- /dev/null +++ b/cmd/config/config.go @@ -0,0 +1,27 @@ +package config + +import ( + "github.com/Obmondo/kubeaid-bootstrap-script/cmd/config/generate" + "github.com/Obmondo/kubeaid-bootstrap-script/constants" + "github.com/spf13/cobra" +) + +var ConfigCmd = &cobra.Command{ + Use: "config", + RunE: func(cmd *cobra.Command, args []string) error { + return cmd.Help() + }, +} + +var ( + ConfigFilePath string +) + +func init() { + // Subcommands. + ConfigCmd.AddCommand(generate.GenerateCmd) + + // Flags + ConfigCmd.PersistentFlags(). + StringVar(&ConfigFilePath, constants.FlagNameConfig, constants.OutputPathGeneratedConfig, "Path to the KubeAid Bootstrap Script config file") +} diff --git a/cmd/config/generate/aws.go b/cmd/config/generate/aws.go new file mode 100644 index 0000000..cca9df1 --- /dev/null +++ b/cmd/config/generate/aws.go @@ -0,0 +1,16 @@ +package generate + +import ( + "context" + + "github.com/Obmondo/kubeaid-bootstrap-script/config" + "github.com/Obmondo/kubeaid-bootstrap-script/constants" + "github.com/spf13/cobra" +) + +var AWSCmd = &cobra.Command{ + Use: "aws", + Run: func(cmd *cobra.Command, args []string) { + config.GenerateSampleConfig(context.Background(), constants.CloudProviderAWS) + }, +} diff --git a/cmd/config/generate/generate.go b/cmd/config/generate/generate.go new file mode 100644 index 0000000..60e74cb --- /dev/null +++ b/cmd/config/generate/generate.go @@ -0,0 +1,32 @@ +package generate + +import ( + "context" + "log/slog" + "os" + + "github.com/Obmondo/kubeaid-bootstrap-script/constants" + "github.com/spf13/cobra" +) + +var GenerateCmd = &cobra.Command{ + Use: "generate", + + PersistentPreRun: func(cmd *cobra.Command, args []string) { + // Verify that file doesn't already exist. + if _, err := os.Stat(constants.OutputPathGeneratedConfig); err == nil { + slog.ErrorContext(context.Background(), "Config file already exists", slog.String("path", constants.OutputPathGeneratedConfig)) + os.Exit(1) + } + }, + + RunE: func(cmd *cobra.Command, args []string) error { + return cmd.Help() + }, +} + +func init() { + // Subcommands. + GenerateCmd.AddCommand(AWSCmd) + GenerateCmd.AddCommand(HetznerCmd) +} diff --git a/cmd/config/generate/hetzner.go b/cmd/config/generate/hetzner.go new file mode 100644 index 0000000..47b68a4 --- /dev/null +++ b/cmd/config/generate/hetzner.go @@ -0,0 +1,16 @@ +package generate + +import ( + "context" + + "github.com/Obmondo/kubeaid-bootstrap-script/config" + "github.com/Obmondo/kubeaid-bootstrap-script/constants" + "github.com/spf13/cobra" +) + +var HetznerCmd = &cobra.Command{ + Use: "hetzner", + Run: func(cmd *cobra.Command, args []string) { + config.GenerateSampleConfig(context.Background(), constants.CloudProviderHetzner) + }, +} diff --git a/cmd/generate_sample_config.go b/cmd/generate_sample_config.go deleted file mode 100644 index a7b622a..0000000 --- a/cmd/generate_sample_config.go +++ /dev/null @@ -1,67 +0,0 @@ -package main - -import ( - "fmt" - "log" - "os" - "slices" - - "github.com/Obmondo/kubeaid-bootstrap-script/config" - "github.com/Obmondo/kubeaid-bootstrap-script/constants" - "github.com/Obmondo/kubeaid-bootstrap-script/utils" - "github.com/urfave/cli/v2" -) - -type TemplateValues struct { - K8sVersion, - AMI string -} - -func generateSampleConfig(ctx *cli.Context) error { - // Verify that file doesn't already exist. - if _, err := os.Stat(constants.OutputPathGeneratedConfig); err == nil { - log.Fatalf("Config file already exists at %s", constants.OutputPathGeneratedConfig) - } - - // Verify that the (user specified if not default) Kubernetes version is supported. - k8sVersion := ctx.String(constants.FlagNameK8sVersion) - if !slices.Contains(constants.SupportedK8sVersions, k8sVersion) { - log.Fatalf("Unsupported K8s version : %s", k8sVersion) - } - - var ( - cloud = ctx.String(constants.FlagNameCloud) - templateName = fmt.Sprintf("templates/%s.sample.config.yaml.tmpl", cloud) - - templateValues = &TemplateValues{ - K8sVersion: ctx.String(constants.FlagNameK8sVersion), - } - ) - - // Cloud specific actions. - switch cloud { - case constants.CloudProviderAWS: - // By default, use Obmondo published ARM and Ubuntu based AMI for the given Kubernetes version. - templateValues.AMI = constants.ObmondoPublishedAMIs[k8sVersion] - - case constants.CloudProviderAzure: - case constants.CloudProviderHetzner: - log.Fatalf("Support for Azure and Hetzner is coming soon....") - - default: - log.Fatalf("Unknown type of cloud provider : %s", cloud) - } - - // Execute the template. - content := utils.ParseAndExecuteTemplate(&config.SampleConfigs, templateName, templateValues) - - // Write the template execution result to the sample config file. - destinationFile, err := os.OpenFile(constants.OutputPathGeneratedConfig, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644) - if err != nil { - log.Fatalf("Failed opening file at %s : %v", constants.OutputPathGeneratedConfig, err) - } - defer destinationFile.Close() - destinationFile.Write(content) - - return nil -} diff --git a/cmd/main.go b/cmd/main.go index 9176117..8ed9dc7 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -1,52 +1,42 @@ package main import ( - "log" + "log/slog" "os" - "github.com/Obmondo/kubeaid-bootstrap-script/cmd/bootstrap_cluster" + "github.com/Obmondo/kubeaid-bootstrap-script/cmd/cluster" + "github.com/Obmondo/kubeaid-bootstrap-script/cmd/config" "github.com/Obmondo/kubeaid-bootstrap-script/constants" - "github.com/urfave/cli/v2" + "github.com/Obmondo/kubeaid-bootstrap-script/utils/logger" + "github.com/spf13/cobra" ) +var rootCmd = &cobra.Command{ + Use: "kubeaid-bootstrap-script", + RunE: func(cmd *cobra.Command, args []string) error { + return cmd.Help() + }, +} + +func init() { + // Subcommands. + rootCmd.AddCommand(cluster.ClusterCmd) + rootCmd.AddCommand(config.ConfigCmd) + + // Flags. + var isDebugModeEnabled bool + rootCmd.PersistentFlags(). + BoolVar(&isDebugModeEnabled, constants.FlagNameDebug, false, "Run the script in debug mode") + + // Initialization tasks. + + // Initialize logger. + logger.InitLogger(isDebugModeEnabled) +} + func main() { - app := &cli.App{ - Name: "KubeAid Bootstrap Script", - Usage: "Bootstrap a Kubernetes cluster using KubeAid and ClusterAPI", - Commands: []*cli.Command{ - { - Name: "generate-sample-config", - Usage: "Generate a sample configuration file", - Action: generateSampleConfig, - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: constants.FlagNameK8sVersion, - Usage: "Kubernetes version (v1.31.0 / v1.30.0)", - Required: false, - Value: "v1.31.0", - }, - &cli.StringFlag{ - Name: constants.FlagNameCloud, - Usage: "Cloud provider (AWS / Azure / Hetzner)", - Required: true, - }, - }, - }, - { - Name: "bootstrap-cluster", - Usage: "Bootstrap a Kubernetes cluster and install KubeAid", - Action: bootstrap_cluster.BootstrapCluster, - Flags: []cli.Flag{ - &cli.PathFlag{ - Name: constants.FlagNameConfigFile, - Usage: "Path to the config file", - Required: true, - }, - }, - }, - }, - } - if err := app.Run(os.Args); err != nil { - log.Fatalf("Failed running KubeAid Bootstrap script : %v", err) + if err := rootCmd.Execute(); err != nil { + slog.Error(err.Error()) + os.Exit(1) } } diff --git a/config/config.go b/config/config.go index 4acd588..72ea169 100644 --- a/config/config.go +++ b/config/config.go @@ -1,15 +1,9 @@ package config import ( - "embed" - "fmt" - "log" - "log/slog" - "os" - - "github.com/creasty/defaults" - "gopkg.in/yaml.v3" - v1 "k8s.io/api/core/v1" + "context" + + coreV1 "k8s.io/api/core/v1" ) type ( @@ -19,7 +13,7 @@ type ( Cluster ClusterConfig `yaml:"cluster" validate:"required"` Forks ForksConfig `yaml:"forks" validate:"required"` Cloud CloudConfig `yaml:"cloud" validate:"required"` - Monitoring MonitoringConfig `yaml:"monitoring" validate:"required"` + Monitoring MonitoringConfig `yaml:"monitoring"` } GitConfig struct { @@ -30,92 +24,117 @@ type ( } ForksConfig struct { - KubeaidForkURL string `yaml:"kubeaid" validate:"required"` - KubeaidConfigForkURL string `yaml:"kubeaidConfig" validate:"required"` + KubeaidForkURL string `yaml:"kubeaid" default:"https://github.com/Obmondo/KubeAid"` + KubeaidConfigForkURL string `yaml:"kubeaidConfig" validate:"required,notblank"` } ClusterConfig struct { - ClusterName string `yaml:"name" validate:"required"` - - // NOTE : Currently, only Kubernetes v1.30.0 and v1.31.0 are supported. - K8sVersion string `yaml:"k8sVersion" validate:"required"` + Name string `yaml:"name" validate:"required,notblank"` + K8sVersion string `yaml:"k8sVersion" validate:"required,notblank"` } CloudConfig struct { AWS *AWSConfig `yaml:"aws"` - Azure *AzureConfig `yaml:"azure"` Hetzner *HetznerConfig `yaml:"hetzner"` + Azure *AzureConfig `yaml:"azure"` } + SSHKeyPairConfig struct { + PublicKeyFilePath string `yaml:"publicKeyFilePath" validate:"required,notblank"` + PublicKey string `validate:"required,notblank"` + + PrivateKeyFilePath string `yaml:"privateKeyFilePath" validate:"required,notblank"` + PrivateKey string `validate:"required,notblank"` + } + + MonitoringConfig struct { + KubePrometheusVersion string `yaml:"kubePrometheusVersion" default:"v0.14.0"` + GrafanaURL string `yaml:"grafanaURL"` + ConnectObmondo bool `yaml:"connectObmondo" default:"False"` + } +) + +// AWS specific. +type ( AWSConfig struct { - AccessKey string `yaml:"accessKey" validate:"required"` - SecretKey string `yaml:"secretKey" validate:"required"` - SessionToken string `yaml:"sessionToken"` - Region string `yaml:"region" validate:"required"` + Credentials AWSCredentials + + Region string `yaml:"region"` + BastionEnabled bool `yaml:"bastionEnabled" default:"True"` + ControlPlane ControlPlaneConfig `yaml:"controlPlane" validate:"required"` + NodeGroups []NodeGroups `yaml:"nodeGroups" validate:"required"` + SSHKeyName string `yaml:"sshKeyName" validate:"required,notblank"` - SSHKeyName string `yaml:"sshKeyName" validate:"required"` + DisasterRecovery *AWSDisasterRecoveryConfig `yaml:"disasterRecovery"` + } - ControlPlaneInstanceType string `yaml:"controlPlaneInstanceType" validate:"required"` - ControlPlaneAMI string `yaml:"controlPlaneAMI" validate:"required"` - ControlPlaneReplicas int `yaml:"controlPlaneReplicas" validate:"required"` + AWSCredentials struct { + AWSAccessKey string `validate:"required,notblank"` + AWSSecretKey string `validate:"required,notblank"` + AWSSessionToken string + AWSRegion string `validate:"required,notblank"` + } - NodeGroups []NodeGroups `yaml:"nodeGroups"` + ControlPlaneConfig struct { + Replicas int `yaml:"replicas" validate:"required"` + InstanceType string `yaml:"instanceType" validate:"required,notblank"` + AMI AMIConfig `yaml:"ami" validate:"required"` } NodeGroups struct { - Name string `yaml:"name" validate:"required"` + Name string `yaml:"name" validate:"required,notblank"` Replicas int `yaml:"replicas" validate:"required"` - InstanceType string `yaml:"instanceType" validate:"required"` - SSHKeyName string `yaml:"sshKeyName" validate:"required"` + InstanceType string `yaml:"instanceType" validate:"required,notblank"` + SSHKeyName string `yaml:"sshKeyName" validate:"required,notblank"` AMI AMIConfig `yaml:"ami" validate:"required"` RootVolumeSize int `yaml:"rootVolumeSize" validate:"required"` Labels map[string]string `yaml:"labels" default:"[]"` - Taints []v1.Taint `yaml:"taints" default:"[]"` + Taints []*coreV1.Taint `yaml:"taints" default:"[]"` } AMIConfig struct { - ID string `yaml:"id" validate:"required"` + ID string `yaml:"id" validate:"required,notblank"` } - AzureConfig struct{} - - HetznerConfig struct{} - - MonitoringConfig struct { - KubePrometheusVersion string `yaml:"kubePrometheusVersion" validate:"required"` - GrafanaURL string `yaml:"grafanaURL"` - ConnectObmondo bool `yaml:"connectObmondo"` + AWSDisasterRecoveryConfig struct { + VeleroBackupsS3BucketName string `yaml:"veleroBackupsS3BucketName" validate:"required,notblank"` + SealedSecretsBackupS3BucketName string `yaml:"sealedSecretsBackupS3BucketName" validate:"required,notblank"` } ) -//go:embed templates/* -var SampleConfigs embed.FS +// Hetzner specific. +type ( + HetznerConfig struct { + Credentials HetznerCredentials -func ParseConfigFile(configFile string) *Config { - configFileContents, err := os.ReadFile(configFile) - if err != nil { - log.Fatalf("Failed reading config file : %v", err) - } - parsedConfig, err := ParseConfig(string(configFileContents)) - if err != nil { - log.Fatalf("Failed parsing config file : %v", err) + // Robot is Hetzner's administration panel for dedicated root servers, colocation, Storage Boxes, + // and domains (via the Domain Registration Robot add-on). + RobotSSHKeyPair SSHKeyPairConfig `yaml:"robotSSHKey" validate:"required"` + + ControlPlaneEndpoint string `yaml:"controlPlaneEndpoint" validate:"required,notblank"` + BareMetalNodes map[string]HetznerNodeConfigs `yaml:"bareMetalNodes" validate:"required"` } - return parsedConfig -} -func ParseConfig(configAsString string) (*Config, error) { - parsedConfig := &Config{} - if err := yaml.Unmarshal([]byte(configAsString), parsedConfig); err != nil { - return nil, fmt.Errorf("failed unmarshalling config : %v", err) + HetznerCredentials struct { + HetznerAPIToken string `validate:"required,notblank"` + HetznerRobotUser string `validate:"required,notblank"` + HetznerRobotPassword string `validate:"required,notblank"` } - slog.Info("Parsed config") - // Set defaults. - if err := defaults.Set(parsedConfig); err != nil { - log.Fatalf("Failed setting defaults for parsed config : %v", err) + HetznerNodeConfigs struct { + Name string `yaml:"name" validate:"required,notblank"` + WWN []string `yaml:"wwn" validate:"required,notblank"` // World Wide Name, a unique identifier. } +) + +// Azure specific. +type ( + AzureConfig struct{} +) - validateConfig(parsedConfig) +var ParsedConfig = &Config{} - return parsedConfig, nil +// Read config file from the given file path. Then, parse and validate it. +func InitConfig() { + parseConfigFile(context.Background(), ConfigFilePath) } diff --git a/config/flags.go b/config/flags.go new file mode 100644 index 0000000..43a54ae --- /dev/null +++ b/config/flags.go @@ -0,0 +1,93 @@ +package config + +import ( + "context" + "log/slog" + "os" + "strings" + + "github.com/Obmondo/kubeaid-bootstrap-script/constants" + "github.com/Obmondo/kubeaid-bootstrap-script/utils/assert" + "github.com/spf13/cobra" + "github.com/spf13/pflag" +) + +var ConfigFilePath string + +func RegisterConfigFilePathFlag(command *cobra.Command) { + command.PersistentFlags(). + StringVar(&ConfigFilePath, constants.FlagNameConfig, constants.OutputPathGeneratedConfig, "Path to the KubeAid Bootstrap Script config file") + command.MarkPersistentFlagRequired(constants.FlagNameConfig) +} + +var ( + AWSAccessKey, + AWSSecretKey, + AWSSessionToken, + AWSRegion string +) + +func RegisterAWSCredentialsFlags(command *cobra.Command) { + flagSet := pflag.NewFlagSet("aws-credentials", pflag.ExitOnError) + + flagSet.StringVar(&AWSAccessKey, constants.FlagNameAWSAccessKey, "", "AWS access key ID") + cobra.MarkFlagRequired(flagSet, constants.FlagNameAWSAccessKey) + + flagSet.StringVar(&AWSSecretKey, constants.FlagNameAWSSecretKey, "", "AWS secret access key") + cobra.MarkFlagRequired(flagSet, constants.FlagNameAWSSecretKey) + + flagSet.StringVar(&AWSSessionToken, constants.FlagNameAWSSessionToken, "", "AWS session token (optional)") + + flagSet.StringVar(&AWSRegion, constants.FlagNameAWSRegion, "", "AWS region") + cobra.MarkFlagRequired(flagSet, constants.FlagNameAWSRegion) + + flagSet.VisitAll(bindFlagToEnv) + + command.Flags().AddFlagSet(flagSet) +} + +var ( + HetznerAPIToken, + HetznerRobotUser, + HetznerRobotPassword string +) + +func RegisterHetznerCredentialsFlags(command *cobra.Command) { + flagSet := pflag.NewFlagSet("aws-credentials", pflag.ExitOnError) + + flagSet.StringVar(&HetznerAPIToken, constants.FlagNameHetznerAPIToken, "", "Hetzner API token") + command.MarkFlagRequired(constants.FlagNameHetznerAPIToken) + + flagSet.StringVar(&HetznerRobotUser, constants.FlagNameHetznerRobotUser, "", "Hetzner robot user") + command.MarkFlagRequired(constants.FlagNameHetznerRobotUser) + + flagSet.StringVar(&HetznerRobotPassword, constants.FlagNameHetznerRobotPassword, "", "Hetzner robot password") + command.MarkFlagRequired(constants.FlagNameHetznerRobotPassword) + + flagSet.VisitAll(bindFlagToEnv) + + command.Flags().AddFlagSet(flagSet) +} + +// Usage : flagSet.VisitAll(getFlagOrEnvValue) +// +// If a flag isn't set, then we try to get its value from the corresponding environment variable. +// Panics, if the flag and environment variable aren't set and there's no default flag value. +func bindFlagToEnv(flag *pflag.Flag) { + if len(flag.Value.String()) > 0 { + return + } + + correspondingEnvName := strings.ReplaceAll(strings.ToUpper(flag.Name), "-", "_") + + envValue, envFound := os.LookupEnv(correspondingEnvName) + if envFound { + err := flag.Value.Set(envValue) + assert.AssertErrNil(context.Background(), err, "Failed setting flag value from environment variable") + + flag.Changed = true + + slog.Debug("Flag value picked up from environment variable", slog.String("flag", flag.Name), slog.String("env", correspondingEnvName)) + return + } +} diff --git a/config/generate_sample_config.go b/config/generate_sample_config.go new file mode 100644 index 0000000..e3f9528 --- /dev/null +++ b/config/generate_sample_config.go @@ -0,0 +1,38 @@ +package config + +import ( + "context" + "embed" + "log/slog" + "os" + + "github.com/Obmondo/kubeaid-bootstrap-script/constants" + "github.com/Obmondo/kubeaid-bootstrap-script/utils/assert" + "github.com/Obmondo/kubeaid-bootstrap-script/utils/templates" +) + +//go:embed templates/* +var SampleConfigs embed.FS + +func GenerateSampleConfig(ctx context.Context, cloudProvider string) { + var templateName string + switch cloudProvider { + case constants.CloudProviderAWS: + templateName = constants.TemplateNameAWSSampleConfig + + case constants.CloudProviderHetzner: + templateName = constants.TemplateNameHetznerSampleConfig + + default: + panic("unreachable") + } + + content := templates.ParseAndExecuteTemplate(ctx, &SampleConfigs, templateName, nil) + + destinationFile, err := os.OpenFile(constants.OutputPathGeneratedConfig, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644) + assert.AssertErrNil(ctx, err, "Failed opening file", slog.String("path", constants.OutputPathGeneratedConfig)) + defer destinationFile.Close() + + _, err = destinationFile.Write(content) + assert.AssertErrNil(ctx, err, "Failed writing sample config to file", slog.String("path", constants.OutputPathGeneratedConfig)) +} diff --git a/config/templates/aws.sample.config.yaml.tmpl b/config/templates/aws.sample.config.yaml.tmpl index 2d9392b..60d86a7 100644 --- a/config/templates/aws.sample.config.yaml.tmpl +++ b/config/templates/aws.sample.config.yaml.tmpl @@ -1,36 +1,35 @@ forks: - kubeaid: https://github.com/Obmondo/KubeAid - kubeaidConfig: https://github.com/Archisman-Mridha/kubeaid-config + kubeaidConfig: https://github.com/xxxxxxxxxx/kubeaid-config git: - username: "...." - password: "...." + username: xxxxxxxxxx + password: xxxxxxxxxx cluster: - name: kubeaid-demo - k8sVersion: {{ .K8sVersion }} + name: kubeaid-demo-aws + k8sVersion: v1.31.0 cloud: aws: - accessKey: "...." - secretKey: "...." - region: us-east-1 + region: us-east-1 sshKeyName: kubeaid-demo - controlPlaneInstanceType: t4g.medium - controlPlaneAMI: {{ .AMI }} - controlPlaneReplicas: 1 + bastionEnabled: False + + controlPlane: + instanceType: t4g.medium + ami: + id: ami-xxxxxxxxxxxxxxxxx + replicas: 1 nodeGroups: - name: primary ami: - id: {{ .AMI }} + id: ami-xxxxxxxxxxxxxxxxx instanceType: t4g.medium - - replicas: 2 + replicas: 1 rootVolumeSize: 35 - sshKeyName: kubeaid-demo # Label should meet one of the following criterias to propagate to Node : @@ -40,12 +39,12 @@ cloud: # (3) Belongs to node.cluster.x-k8s.io domain. # # REFER : https://cluster-api.sigs.k8s.io/developer/architecture/controllers/metadata-propagation#machine - # labels: [] - - # taints [] + labels: + node.cluster.x-k8s.io/nodegroup: primary + node-role.kubernetes.io/bootstrapper: "" -monitoring: - kubePrometheusVersion: v0.14.0 - grafanaURL: "" + taints: [] - connectObmondo: False + disasterRecovery: + veleroBackupsS3BucketName: kubeaid-demo-kubernetes-objects + sealedSecretsBackupS3BucketName: kubeaid-demo-sealed-secrets diff --git a/config/templates/hetzner.sample.config.yaml.tmpl b/config/templates/hetzner.sample.config.yaml.tmpl new file mode 100644 index 0000000..2abcf95 --- /dev/null +++ b/config/templates/hetzner.sample.config.yaml.tmpl @@ -0,0 +1,38 @@ +forks: + kubeaidConfig: https://github.com/xxxxxxxxxx/kubeaid-config + +git: + username: xxxxxxxxxx + password: xxxxxxxxxx + +cluster: + name: kubeaid-demo-hetzner + k8sVersion: v1.31.0 + +cloud: + hetzner: + robot: true + robotSSHKey: + publicKeyFilePath: ./outputs/.ssh/default.pub + privateKeyFilePath: ./outputs/.ssh/default + + controlPlaneEndpoint: xxx.xxx.xxx.xxx + bareMetalNodes: + 1866472: + name: xxxxxxxx.enableit.dk + maintenanceMode: false + wwn: + - xxxxxxxxxxxxxxxxxx + - xxxxxxxxxxxxxxxxxx + 1866482: + name: xxxxxxxx.enableit.dk + maintenanceMode: false + wwn: + - xxxxxxxxxxxxxxxxxx + - xxxxxxxxxxxxxxxxxx + 1866484: + name: xxxxxxxx.enableit.dk + maintenanceMode: false + wwn: + - xxxxxxxxxxxxxxxxxx + - xxxxxxxxxxxxxxxxxx diff --git a/config/utils.go b/config/utils.go new file mode 100644 index 0000000..98f3142 --- /dev/null +++ b/config/utils.go @@ -0,0 +1,96 @@ +package config + +import ( + "context" + "log/slog" + "os" + + "github.com/Obmondo/kubeaid-bootstrap-script/utils/assert" + "github.com/Obmondo/kubeaid-bootstrap-script/utils/logger" + "github.com/creasty/defaults" + "golang.org/x/crypto/ssh" + "gopkg.in/yaml.v3" +) + +func parseConfigFile(ctx context.Context, configFilePath string) { + ctx = logger.AppendSlogAttributesToCtx(ctx, []slog.Attr{ + slog.String("path", configFilePath), + }) + + configFileContents, err := os.ReadFile(configFilePath) + assert.AssertErrNil(ctx, err, "Failed reading config file") + + ParseConfig(ctx, string(configFileContents)) +} + +func ParseConfig(ctx context.Context, configAsString string) { + err := yaml.Unmarshal([]byte(configAsString), ParsedConfig) + assert.AssertErrNil(ctx, err, "Failed unmarshalling config") + + slog.InfoContext(ctx, "Parsed config") + + // Set defaults. + err = defaults.Set(ParsedConfig) + assert.AssertErrNil(ctx, err, "Failed setting defaults for parsed config") + // + // Read cloud credentials from CLI flags and store them in config. + readCloudCredentialsFromFlagsToConfig() + // + // Read SSH key-pairs from provided file paths and store them in config. + readSSHKeys(ParsedConfig) + + // Validate. + validateConfig(ParsedConfig) +} + +func readCloudCredentialsFromFlagsToConfig() { + switch { + case ParsedConfig.Cloud.AWS != nil: + ParsedConfig.Cloud.AWS.Credentials = AWSCredentials{ + AWSAccessKey, + AWSSecretKey, + AWSSessionToken, + AWSRegion, + } + + case ParsedConfig.Cloud.Hetzner != nil: + ParsedConfig.Cloud.Hetzner.Credentials = HetznerCredentials{ + HetznerAPIToken, + HetznerRobotUser, + HetznerRobotPassword, + } + } +} + +func readSSHKeys(config *Config) { + switch { + case config.Cloud.Hetzner != nil: + readSSHKey(&config.Cloud.Hetzner.RobotSSHKeyPair) + } +} + +// Reads and validates an SSH key-pair from the provided file paths. +// The key-pair is then stored in the SSH key config struct itself. +func readSSHKey(sshKeyConfig *SSHKeyPairConfig) { + ctx := context.Background() + + // Read and validate the SSH public key. + + publicKey, err := os.ReadFile(sshKeyConfig.PublicKeyFilePath) + assert.AssertErrNil(ctx, err, "Failed reading file", slog.String("path", sshKeyConfig.PublicKeyFilePath)) + + _, _, _, _, err = ssh.ParseAuthorizedKey(publicKey) + assert.AssertErrNil(ctx, err, "SSH public key is invalid : failed parsing", slog.String("path", sshKeyConfig.PublicKeyFilePath)) + + sshKeyConfig.PublicKey = string(publicKey) + + // Read and validate the SSH private key. + + privateKey, err := os.ReadFile(sshKeyConfig.PrivateKeyFilePath) + assert.AssertErrNil(ctx, err, "Failed reading file", slog.String("path", sshKeyConfig.PrivateKeyFilePath)) + + _, err = ssh.ParsePrivateKey(privateKey) + assert.AssertErrNil(ctx, err, "SSH private key is invalid : failed parsing", slog.String("path", sshKeyConfig.PrivateKeyFilePath)) + + sshKeyConfig.PrivateKey = string(privateKey) +} diff --git a/config/validate.go b/config/validate.go index 0451dde..4367fd8 100644 --- a/config/validate.go +++ b/config/validate.go @@ -1,77 +1,141 @@ package config import ( + "context" "fmt" + "io" "log" "log/slog" + "net/http" "os" "strings" - "github.com/go-playground/validator/v10" - "github.com/siderolabs/talos/pkg/machinery/labels" -) - -var ( - // A user defined NodeGroup label key should belong to one of these domains. - // REFER : https://cluster-api.sigs.k8s.io/developer/architecture/controllers/metadata-propagation#machine. - ValidNodeGroupLabelDomains = []string{ - "node.cluster.x-k8s.io/", - "node-role.kubernetes.io/", - "node-restriction.kubernetes.io/", - } + "github.com/Obmondo/kubeaid-bootstrap-script/utils/assert" + "github.com/Obmondo/kubeaid-bootstrap-script/utils/logger" + validatorV10 "github.com/go-playground/validator/v10" + goNonStandardValidtors "github.com/go-playground/validator/v10/non-standard/validators" + labelsPkg "github.com/siderolabs/talos/pkg/machinery/labels" + coreV1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/util/version" ) // Validates the parsed config. -// Panics on failure. -// TODO : Extract the NodeGroup labels and taints validation task from 'cloud specifics' section. func validateConfig(config *Config) { + ctx := context.Background() + + validator := validatorV10.New(validatorV10.WithRequiredStructEnabled()) + err := validator.RegisterValidation("notblank", goNonStandardValidtors.NotBlank) + assert.AssertErrNil(ctx, err, "Failed registering notblank validator") + // Validate based on struct tags. - validate := validator.New(validator.WithRequiredStructEnabled()) - if err := validate.Struct(config); err != nil { - log.Fatalf("config validation failed : %v", err) - } + err = validator.Struct(config) + assert.AssertErrNil(ctx, err, "Config validation failed") + + // Validate K8s version. + ValidateK8sVersion(ctx, config.Cluster.K8sVersion) - // Cloud provider specific validations. switch { case config.Cloud.AWS != nil: - for _, nodeGroup := range config.Cloud.AWS.NodeGroups { - // Validate NodeGroups labels. - // - // (1) according to Kubernetes specifications. - if err := labels.Validate(nodeGroup.Labels); err != nil { - log.Fatalf("NodeGroup labels validation failed : %v", err) - } - // - // (2) according to ClusterAPI specifications. - for key := range nodeGroup.Labels { - // Check if the label belongs to a domain considered valid by ClusterAPI. - isValidNodeGroupLabelDomain := false - for _, nodeGroupLabelDomains := range ValidNodeGroupLabelDomains { - if strings.HasPrefix(key, nodeGroupLabelDomains) { - isValidNodeGroupLabelDomain = true - break - } - } - if !isValidNodeGroupLabelDomain { - slog.Error("NodeGroup label key should belong to one of these domains", slog.Any("domains", ValidNodeGroupLabelDomains)) - os.Exit(1) - } - } + // Validate labels and taints. + validateLabelsAndTaints(ctx, nodeGroup.Name, nodeGroup.Labels, nodeGroup.Taints) + } - taintsAsKVPairs := map[string]string{} - for _, taint := range nodeGroup.Taints { - taintsAsKVPairs[taint.Key] = fmt.Sprintf("%s:%s", taint.Value, taint.Effect) - } - // - // Validate NodeGroup taints. - if err := labels.ValidateTaints(taintsAsKVPairs); err != nil { - log.Fatalf("NodeGroup taint validation failed : %v", err) + case config.Cloud.Hetzner != nil: + break + + case config.Cloud.Azure != nil: + log.Fatal("Support for Azure is coming soon") + + default: + log.Fatal("No cloud specific details provided") + } +} + +// Checks whether the given string represents a valid and supported Kubernetes version or not. +// If not, then panics. +func ValidateK8sVersion(ctx context.Context, k8sVersion string) { + parsedK8sVersion, err := version.ParseSemantic(k8sVersion) + assert.AssertErrNil(ctx, err, "Failed parsing K8s semantic version") + + const leastSupportedK8sVersion = "v1.30.0" + parsedLeastSupportedK8sVersion, err := version.ParseSemantic(leastSupportedK8sVersion) + assert.AssertErrNil(ctx, err, "Failed parsing least supported K8s version") + + latestStableK8sVersion := getLatestStableK8sVersion(ctx) + parsedLatestStableK8sVersion, err := version.ParseSemantic(latestStableK8sVersion) + assert.AssertErrNil(ctx, err, "Failed parsing latest stable K8s version") + + if !parsedK8sVersion.AtLeast(parsedLeastSupportedK8sVersion) && + !(parsedK8sVersion.LessThan(parsedLatestStableK8sVersion) || parsedK8sVersion.EqualTo(parsedLatestStableK8sVersion)) { + + slog.ErrorContext(ctx, "K8s versions below v1.30.0 aren't supported") + os.Exit(1) + } +} + +// Fetches and returns the latest stable Kubernetes version, from the Kubeadm API endpoint. +func getLatestStableK8sVersion(ctx context.Context) string { + const kubeadmAPIURL = "https://dl.k8s.io/release/stable.txt" + + slog.InfoContext(ctx, "Fetching latest stable K8s version", slog.String("URL", kubeadmAPIURL)) + + response, err := http.Get(kubeadmAPIURL) + assert.AssertErrNil(ctx, err, "Failed fetching latest stable K8s version") + if response.StatusCode != http.StatusOK { + slog.ErrorContext(ctx, "Failed fetching latest stable Kubernetes version") + os.Exit(1) + } + defer response.Body.Close() + + latestStableK8sVersion, err := io.ReadAll(response.Body) + assert.AssertErrNil(ctx, err, "Failed reading latest stable K8s version from response body") + + return string(latestStableK8sVersion) +} + +// A user defined NodeGroup label key should belong to one of these domains. +// REFER : https://cluster-api.sigs.k8s.io/developer/architecture/controllers/metadata-propagation#machine. +var validNodeGroupLabelDomains = []string{ + "node.cluster.x-k8s.io/", + "node-role.kubernetes.io/", + "node-restriction.kubernetes.io/", +} + +// Validates node-group labels and taints. +func validateLabelsAndTaints(ctx context.Context, nodeGroupName string, labels map[string]string, taints []*coreV1.Taint) { + ctx = logger.AppendSlogAttributesToCtx(ctx, []slog.Attr{ + slog.String("node-group", nodeGroupName), + }) + + // Validate labels. + // + // (1) according to Kubernetes specifications. + err := labelsPkg.Validate(labels) + assert.AssertErrNil(ctx, err, "MachinePool labels validation failed") + // + // (2) according to ClusterAPI specifications. + for key := range labels { + // Check if the label belongs to a domain considered valid by ClusterAPI. + isValidNodeGroupLabelDomain := false + for _, nodeGroupLabelDomains := range validNodeGroupLabelDomains { + if strings.HasPrefix(key, nodeGroupLabelDomains) { + isValidNodeGroupLabelDomain = true + break } } + if !isValidNodeGroupLabelDomain { + slog.Error("NodeGroup label key should belong to one of these domains", slog.Any("domains", validNodeGroupLabelDomains)) + os.Exit(1) + } + } - case config.Cloud.Azure != nil: - case config.Cloud.Hetzner != nil: - log.Fatal("Support for Azure and Hetzner are coming soon") + taintsAsKVPairs := map[string]string{} + for _, taint := range taints { + taintsAsKVPairs[taint.Key] = fmt.Sprintf("%s:%s", taint.Value, taint.Effect) } + // + // Validate taints. + err = labelsPkg.ValidateTaints(taintsAsKVPairs) + assert.AssertErrNil(ctx, err, "NodeGroup taints validation failed") } diff --git a/constants/constants.go b/constants/constants.go index 6a7f7d8..646a6f9 100644 --- a/constants/constants.go +++ b/constants/constants.go @@ -1,89 +1,169 @@ package constants import ( - "github.com/Obmondo/kubeaid-bootstrap-script/config" + "io" + + "github.com/argoproj/argo-cd/v2/pkg/apiclient/application" +) + +// Environment variable names. +const ( + EnvNameAWSAccessKey = "AWS_ACCESS_KEY_ID" + EnvNameAWSSecretKey = "AWS_SECRET_ACCESS_KEY" + EnvNameAWSSessionToken = "AWS_SESSION_TOKEN" + EnvNameAWSRegion = "AWS_REGION" + EnvNameAWSB64EcodedCredentials = "AWS_B64ENCODED_CREDENTIALS" + + EnvNameHCloudToken = "HCLOUD_TOKEN" + EnvNameRobotUser = "ROBOT_USER" + EnvNameRobotPassword = "ROBOT_PASSWORD" + + EnvNameKubeconfig = "KUBECONFIG" ) +// CLI flags. const ( - FlagNameConfigFile = "config-file" + FlagNameDebug = "debug" + FlagNameK8sVersion = "k8s-version" - FlagNameCloud = "cloud" - CloudProviderAWS = "aws" - CloudProviderAzure = "azure" - CloudProviderHetzner = "hetzner" + FlagNameConfig = "config" - EnvNameAWSB64EcodedCredentials = "AWS_B64ENCODED_CREDENTIALS" + FlagNameSkipKubeAidConfigSetup = "skip-kubeaid-config-setup" + FlagNameSkipClusterctlMove = "skip-clusterctl-move" + + FlagNameDeleteOldCluster = "delete-old-cluster" - TemplateNameAWSSampleConfig = "aws.sample.config.yaml.tmpl" - TemplateNameJsonnet = "cluster-vars.jsonnet.tmpl" - TemplateNameKubeaidConfigRepo = "sealed-secrets/argo-cd/kubeaid-config.yaml.tmpl" + FlagNameHetznerAPIToken = "hetzner-api-token" + FlagNameHetznerRobotUser = "hetzner-robot-user" + FlagNameHetznerRobotPassword = "hetzner-robot-password" + FlagNameAWSAccessKey = "aws-access-key-id" + FlagNameAWSSecretKey = "aws-secret-access-key" + FlagNameAWSSessionToken = "aws-session-token" + FlagNameAWSRegion = "aws-region" +) + +// Cloud providers. +const ( + CloudProviderAWS = "aws" + CloudProviderHetzner = "hetzner" + CloudProviderAzure = "azure" +) + +// Output paths. +const ( OutputPathManagementClusterKubeconfig = "./outputs/management-cluster.kubeconfig.yaml" OutputPathProvisionedClusterKubeconfig = "./outputs/provisioned-cluster.kubeconfig.yaml" OutputPathGeneratedConfig = "./outputs/kubeaid-bootstrap-script.config.yaml" - - // Supported Kubernetes versions. - K8s_v1_30_0 = "v1.30.0" - K8s_v1_31_0 = "v1.31.0" ) -var ( - SupportedK8sVersions = []string{K8s_v1_31_0, K8s_v1_30_0} +// ArgoCD App names. +const ( + ArgoCDAppRoot = "root" + ArgoCDAppCapiCluster = "capi-cluster" + ArgoCDAppClusterAutoscaler = "cluster-autoscaler" + ArgoCDAppVelero = "velero" +) - // Custom ARM and Ubuntu based AMI for each supported Kubernetes version, built by and published - // from Obmondo's AWS account. - ObmondoPublishedAMIs = map[string]string{ - K8s_v1_31_0: "ami-0fc044fa0d061d18d", - K8s_v1_30_0: "ami-0c4219b11327ef260", - } +// Uncategorized. +const ( + RepoURLObmondoKubeAid = "https://github.com/Obmondo/KubeAid" - TempDir string + NamespaceArgoCD = "argo-cd" +) - ParsedConfig *config.Config +// Template names. +var ( + TemplateNameAWSSampleConfig = "templates/aws.sample.config.yaml.tmpl" + TemplateNameHetznerSampleConfig = "templates/hetzner.sample.config.yaml.tmpl" - CommonEmbeddedTemplateNames = []string{ - // ArgoCD. + CommonNonSecretTemplateNames = []string{ + // For ArgoCD. "argocd-apps/templates/argo-cd.app.yaml.tmpl", "argocd-apps/argo-cd.values.yaml.tmpl", - // Root ArgoCD App. + // For Root ArgoCD App. "argocd-apps/Chart.yaml", "argocd-apps/templates/root.yaml.tmpl", - // KubePrometheus. + // For KubePrometheus. "argocd-apps/templates/kube-prometheus.app.yaml.tmpl", - // CertManager. + // For CertManager. "argocd-apps/templates/cert-manager.app.yaml.tmpl", "argocd-apps/cert-manager.values.yaml.tmpl", - // Sealed Secrets. + // For Sealed Secrets. "argocd-apps/templates/sealed-secrets.app.yaml.tmpl", "argocd-apps/sealed-secrets.values.yaml.tmpl", "argocd-apps/templates/secrets.app.yaml.tmpl", - TemplateNameKubeaidConfigRepo, - // Cluster API. + // For Cluster API. "argocd-apps/templates/cluster-api.app.yaml.tmpl", "argocd-apps/cluster-api.values.yaml.tmpl", - // Cluster Autoscaler. + // For CAPI Cluster. + "argocd-apps/templates/capi-cluster.app.yaml.tmpl", + "argocd-apps/capi-cluster.values.yaml.tmpl", + + // For Cluster Autoscaler. "argocd-apps/templates/cluster-autoscaler.app.yaml.tmpl", "argocd-apps/cluster-autoscaler.values.yaml.tmpl", - - // Traefik. - "argocd-apps/templates/traefik.app.yaml.tmpl", - "argocd-apps/traefik.values.yaml.tmpl", } - AWSSpecificEmbeddedTemplateNames = []string{ - // CAPI Cluster. - "argocd-apps/templates/capi-cluster.app.yaml.tmpl", - "argocd-apps/capi-cluster.values.yaml.tmpl", + CommonSecretTemplateNames = []string{ + // For ArgoCD. + "sealed-secrets/argo-cd/kubeaid-config.yaml.tmpl", - // AWS Cloud Controller Manager. - "argocd-apps/templates/ccm-aws.yaml.tmpl", + // For Cluster API. + "sealed-secrets/capi-cluster/cloud-credentials.yaml.tmpl", + } + + AWSSpecificNonSecretTemplateNames = []string{ + // For AWS Cloud Controller Manager. + "argocd-apps/templates/ccm-aws.app.yaml.tmpl", "argocd-apps/ccm-aws.values.yaml.tmpl", } + + AWSDisasterRecoverySpecificTemplateNames = []string{ + // For Kube2IAM. + "argocd-apps/templates/kube2iam.app.yaml.tmpl", + "argocd-apps/kube2iam.values.yaml.tmpl", + + // For Velero. + "argocd-apps/templates/velero.app.yaml.tmpl", + "argocd-apps/velero.values.yaml.tmpl", + + // For K8sConfigs. + "argocd-apps/templates/k8s-configs.app.yaml.tmpl", + "k8s-configs/sealed-secrets.namespace.yaml.tmpl", + "k8s-configs/velero.namespace.yaml.tmpl", + } + + HetznerSpecificNonSecretTemplateNames = []string{ + // For Hetzner Robot Failover. + "argocd-apps/templates/hetzner-robot.app.yaml.tmpl", + "argocd-apps/hetzner-robot.values.yaml.tmpl", + + // For Hetzner Cloud Controller Manager. + "argocd-apps/templates/ccm-hetzner.app.yaml.tmpl", + "argocd-apps/ccm-hetzner.values.yaml.tmpl", + } + + HetznerSpecificSecretTemplateNames = []string{ + // For Cluster API. + "sealed-secrets/capi-cluster/hetzner-robot-ssh-keypair.yaml.tmpl", + "sealed-secrets/kube-system/cloud-credentials.yaml.tmpl", + } + + TemplateNameJsonnet = "cluster-vars.jsonnet.tmpl" +) + +// Global states. +var ( + TempDir string + + ArgoCDApplicationClientCloser io.Closer + ArgoCDApplicationClient application.ApplicationServiceClient ) diff --git a/go.mod b/go.mod index 1ce0cd6..3104d58 100644 --- a/go.mod +++ b/go.mod @@ -1,73 +1,280 @@ module github.com/Obmondo/kubeaid-bootstrap-script -go 1.22.7 +go 1.22.8 + +toolchain go1.22.10 require ( + github.com/argoproj/argo-cd/v2 v2.13.0 + github.com/avast/retry-go/v4 v4.6.0 + github.com/aws/aws-sdk-go-v2 v1.32.5 + github.com/aws/aws-sdk-go-v2/config v1.26.3 + github.com/aws/aws-sdk-go-v2/service/iam v1.38.1 + github.com/aws/aws-sdk-go-v2/service/s3 v1.69.0 + github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 github.com/creasty/defaults v1.8.0 github.com/go-git/go-git/v5 v5.12.0 - github.com/go-playground/validator/v10 v10.22.1 + github.com/go-playground/validator/v10 v10.23.0 github.com/go-sprout/sprout v0.6.0 - github.com/siderolabs/talos/pkg/machinery v1.8.2 - github.com/urfave/cli/v2 v2.27.4 + github.com/k3d-io/k3d/v5 v5.7.4 + github.com/sagikazarmark/slog-shim v0.1.0 + github.com/siderolabs/talos/pkg/machinery v1.8.3 + github.com/spf13/cobra v1.8.1 + github.com/spf13/pflag v1.0.5 + golang.org/x/crypto v0.29.0 gopkg.in/yaml.v3 v3.0.1 + helm.sh/helm/v3 v3.16.3 k8s.io/api v0.31.2 + k8s.io/apimachinery v0.31.2 + k8s.io/client-go v0.31.2 + k8s.io/kubernetes v1.31.2 + sigs.k8s.io/cluster-api v1.8.5 + sigs.k8s.io/controller-runtime v0.19.1 ) require ( - dario.cat/mergo v1.0.0 // indirect - github.com/Masterminds/semver/v3 v3.2.1 // indirect - github.com/Microsoft/go-winio v0.6.1 // indirect + cloud.google.com/go/compute/metadata v0.3.0 // indirect + dario.cat/mergo v1.0.1 // indirect + github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect + github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect + github.com/BurntSushi/toml v1.3.2 // indirect + github.com/MakeNowJust/heredoc v1.0.0 // indirect + github.com/Masterminds/goutils v1.1.1 // indirect + github.com/Masterminds/semver/v3 v3.3.0 // indirect + github.com/Masterminds/sprig/v3 v3.3.0 // indirect + github.com/Masterminds/squirrel v1.5.4 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect github.com/ProtonMail/go-crypto v1.1.0-alpha.5.0.20240827111422-b5837fa4476e // indirect + github.com/argoproj/gitops-engine v0.7.1-0.20240905010810-bd7681ae3f8b // indirect + github.com/argoproj/pkg v0.13.7-0.20230626144333-d56162821bd1 // indirect + github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.16.16 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.24 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.24 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.24 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.5 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.5 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.5 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.18.7 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7 // indirect + github.com/aws/smithy-go v1.22.1 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/blang/semver/v4 v4.0.0 // indirect + github.com/bmatcuk/doublestar/v4 v4.6.1 // indirect + github.com/bombsimon/logrusr/v2 v2.0.1 // indirect + github.com/bradleyfalzon/ghinstallation/v2 v2.11.0 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/chai2010/gettext-go v1.0.2 // indirect github.com/cloudflare/circl v1.3.9 // indirect + github.com/containerd/containerd v1.7.23 // indirect + github.com/containerd/errdefs v0.3.0 // indirect github.com/containerd/go-cni v1.1.10 // indirect + github.com/containerd/log v0.1.0 // indirect + github.com/containerd/platforms v0.2.1 // indirect + github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect github.com/containernetworking/cni v1.2.3 // indirect - github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect - github.com/cyphar/filepath-securejoin v0.2.4 // indirect + github.com/coreos/go-oidc/v3 v3.11.0 // indirect + github.com/cyphar/filepath-securejoin v0.3.4 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/dimchansky/utfbom v1.1.1 // indirect + github.com/distribution/reference v0.6.0 // indirect + github.com/dlclark/regexp2 v1.11.4 // indirect + github.com/docker/cli v27.0.3+incompatible // indirect + github.com/docker/distribution v2.8.3+incompatible // indirect + github.com/docker/docker v27.0.3+incompatible // indirect + github.com/docker/docker-credential-helpers v0.8.1 // indirect + github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c // indirect + github.com/docker/go-connections v0.5.0 // indirect + github.com/docker/go-metrics v0.0.1 // indirect + github.com/docker/go-units v0.5.0 // indirect + github.com/emicklei/go-restful/v3 v3.12.1 // indirect github.com/emirpasic/gods v1.18.1 // indirect + github.com/evanphx/json-patch v5.9.0+incompatible // indirect + github.com/evanphx/json-patch/v5 v5.9.0 // indirect + github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect + github.com/fatih/camelcase v1.0.0 // indirect + github.com/fatih/color v1.17.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/fvbommel/sortorder v1.1.0 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect + github.com/go-errors/errors v1.4.2 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/go-billy/v5 v5.5.0 // indirect + github.com/go-gorp/gorp/v3 v3.1.0 // indirect + github.com/go-jose/go-jose/v4 v4.0.2 // indirect github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-openapi/jsonpointer v0.21.0 // indirect + github.com/go-openapi/jsonreference v0.21.0 // indirect + github.com/go-openapi/swag v0.23.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-redis/cache/v9 v9.0.0 // indirect + github.com/go-test/deep v1.1.0 // indirect + github.com/gobwas/glob v0.2.3 // indirect github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang-jwt/jwt/v4 v4.5.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/goodhosts/hostsfile v0.1.6 // indirect + github.com/google/btree v1.1.3 // indirect + github.com/google/gnostic-models v0.6.8 // indirect + github.com/google/go-cmp v0.6.0 // indirect + github.com/google/go-containerregistry v0.19.1 // indirect + github.com/google/go-github/v62 v62.0.0 // indirect + github.com/google/go-querystring v1.1.0 // indirect github.com/google/gofuzz v1.2.0 // indirect + github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/uuid v1.6.0 // indirect + github.com/gorilla/mux v1.8.0 // indirect + github.com/gorilla/websocket v1.5.3 // indirect + github.com/gosuri/uitable v0.0.4 // indirect + github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect + github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect + github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect github.com/hashicorp/errwrap v1.1.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.7 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/huandu/xstrings v1.5.0 // indirect + github.com/imdario/mergo v0.3.16 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect + github.com/jmoiron/sqlx v1.4.0 // indirect + github.com/jonboulle/clockwork v0.2.2 // indirect + github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect + github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect + github.com/klauspost/compress v1.17.9 // indirect + github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect + github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect github.com/leodido/go-urn v1.4.0 // indirect + github.com/lib/pq v1.10.9 // indirect + github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect + github.com/magefile/mage v1.15.0 // indirect + github.com/magiconair/properties v1.8.7 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/miekg/pkcs11 v1.1.1 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/go-wordwrap v1.0.1 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect + github.com/moby/locker v1.0.1 // indirect + github.com/moby/patternmatcher v0.5.0 // indirect + github.com/moby/spdystream v0.4.0 // indirect + github.com/moby/sys/sequential v0.5.0 // indirect + github.com/moby/sys/user v0.3.0 // indirect + github.com/moby/sys/userns v0.1.0 // indirect + github.com/moby/term v0.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.1.0 // indirect + github.com/patrickmn/go-cache v2.1.0+incompatible // indirect + github.com/pelletier/go-toml/v2 v2.2.2 // indirect + github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pjbgf/sha1cd v0.3.0 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/prometheus/client_golang v1.20.3 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.55.0 // indirect + github.com/prometheus/procfs v0.15.1 // indirect + github.com/r3labs/diff v1.1.0 // indirect + github.com/rancher/wharfie v0.6.2 // indirect + github.com/redis/go-redis/v9 v9.6.1 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/robfig/cron/v3 v3.0.1 // indirect + github.com/rubenv/sql-migrate v1.7.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect + github.com/shopspring/decimal v1.4.0 // indirect github.com/siderolabs/crypto v0.5.0 // indirect github.com/siderolabs/gen v0.5.0 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect github.com/skeema/knownhosts v1.2.2 // indirect - github.com/spf13/cast v1.6.0 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/afero v1.11.0 // indirect + github.com/spf13/cast v1.7.0 // indirect + github.com/spf13/viper v1.19.0 // indirect + github.com/subosito/gotenv v1.6.0 // indirect + github.com/theupdateframework/notary v0.7.0 // indirect + github.com/vbatts/tar-split v0.11.3 // indirect + github.com/vmihailenco/go-tinylfu v0.2.2 // indirect + github.com/vmihailenco/msgpack/v5 v5.3.4 // indirect + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect - github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect - golang.org/x/crypto v0.26.0 // indirect - golang.org/x/mod v0.17.0 // indirect - golang.org/x/net v0.28.0 // indirect - golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.24.0 // indirect - golang.org/x/text v0.17.0 // indirect - golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect + github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect + github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect + github.com/xeipuuv/gojsonschema v1.2.0 // indirect + github.com/xlab/treeprint v1.2.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.55.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect + go.opentelemetry.io/otel v1.30.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.28.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.30.0 // indirect + go.opentelemetry.io/otel/metric v1.30.0 // indirect + go.opentelemetry.io/otel/sdk v1.30.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.28.0 // indirect + go.opentelemetry.io/otel/trace v1.30.0 // indirect + go.opentelemetry.io/proto/otlp v1.3.1 // indirect + go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect + go.uber.org/multierr v1.11.0 // indirect + go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect + golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect + golang.org/x/net v0.29.0 // indirect + golang.org/x/oauth2 v0.23.0 // indirect + golang.org/x/sync v0.9.0 // indirect + golang.org/x/sys v0.27.0 // indirect + golang.org/x/term v0.26.0 // indirect + golang.org/x/text v0.20.0 // indirect + golang.org/x/time v0.6.0 // indirect + google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect + google.golang.org/grpc v1.66.3 // indirect + google.golang.org/protobuf v1.34.2 // indirect + gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - k8s.io/apimachinery v0.31.2 // indirect + k8s.io/apiextensions-apiserver v0.31.2 // indirect + k8s.io/apiserver v0.31.2 // indirect + k8s.io/cli-runtime v0.31.2 // indirect + k8s.io/component-base v0.31.2 // indirect + k8s.io/component-helpers v0.31.2 // indirect k8s.io/klog/v2 v2.130.1 // indirect + k8s.io/kube-aggregator v0.31.2 // indirect + k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect + k8s.io/kubectl v0.31.2 // indirect k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect + oras.land/oras-go v1.2.5 // indirect + oras.land/oras-go/v2 v2.5.0 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect + sigs.k8s.io/kustomize/api v0.17.2 // indirect + sigs.k8s.io/kustomize/kyaml v0.17.1 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect + sigs.k8s.io/yaml v1.4.0 // indirect ) diff --git a/go.sum b/go.sum index abf5676..7b18373 100644 --- a/go.sum +++ b/go.sum @@ -1,44 +1,279 @@ -dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= -dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= -github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= -github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc= +cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= +dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= +dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= +github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= +github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= +github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= +github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= +github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= +github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= +github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= +github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= +github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= +github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0= +github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs= +github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0= +github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM= +github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= -github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/Microsoft/hcsshim v0.11.7 h1:vl/nj3Bar/CvJSYo7gIQPyRWc9f3c6IeSNavBTSZNZQ= +github.com/Microsoft/hcsshim v0.11.7/go.mod h1:MV8xMfmECjl5HdO7U/3/hFVnkmSBjAjmA09d4bExKcU= +github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/ProtonMail/go-crypto v1.1.0-alpha.5.0.20240827111422-b5837fa4476e h1:O1cSHAcGcbGEO66Qi2AIJeYmXO8iP4L/PNrbdN+RjJA= github.com/ProtonMail/go-crypto v1.1.0-alpha.5.0.20240827111422-b5837fa4476e/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= +github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/Shopify/logrus-bugsnag v0.0.0-20170309145241-6dbc35f2c30d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= +github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs= +github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a h1:HbKu58rmZpUGpz5+4FfNmIU+FmZg2P3Xaj2v2bfNWmk= +github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc= +github.com/alicebob/miniredis/v2 v2.33.0 h1:uvTF0EDeu9RLnUEG27Db5I68ESoIxTiXbNUiji6lZrA= +github.com/alicebob/miniredis/v2 v2.33.0/go.mod h1:MhP4a3EU7aENRi9aO+tHfTBZicLqQevyi/DJpoj6mi0= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/argoproj/argo-cd/v2 v2.13.0 h1:arfqqaSmvDBF6KBQtSR/zsF9TIipO4Ns2jHB2q8x62s= +github.com/argoproj/argo-cd/v2 v2.13.0/go.mod h1:RC23V2744nhZstZVpLCWTQLT2gR0+IXGC3GTBCI6M+I= +github.com/argoproj/gitops-engine v0.7.1-0.20240905010810-bd7681ae3f8b h1:wOPWJ5MBScQO767WpU55oUJDXObfvPL0EfAYWxogbSw= +github.com/argoproj/gitops-engine v0.7.1-0.20240905010810-bd7681ae3f8b/go.mod h1:b1vuwkyMUszyUK+USUJqC8vJijnQsEPNDpC+sDdDLtM= +github.com/argoproj/pkg v0.13.7-0.20230626144333-d56162821bd1 h1:qsHwwOJ21K2Ao0xPju1sNuqphyMnMYkyB3ZLoLtxWpo= +github.com/argoproj/pkg v0.13.7-0.20230626144333-d56162821bd1/go.mod h1:CZHlkyAD1/+FbEn6cB2DQTj48IoLGvEYsWEvtzP3238= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= +github.com/avast/retry-go/v4 v4.6.0 h1:K9xNA+KeB8HHc2aWFuLb25Offp+0iVRXEvFx8IinRJA= +github.com/avast/retry-go/v4 v4.6.0/go.mod h1:gvWlPhBVsvBbLkVGDg/KwvBv0bEkCOLRRSHKIr2PyOE= +github.com/aws/aws-sdk-go v1.44.289/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go-v2 v1.32.5 h1:U8vdWJuY7ruAkzaOdD7guwJjD06YSKmnKCJs7s3IkIo= +github.com/aws/aws-sdk-go-v2 v1.32.5/go.mod h1:P5WJBrYqqbWVaOxgH0X/FYYD47/nooaPOZPlQdmiN2U= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7 h1:lL7IfaFzngfx0ZwUGOZdsFFnQ5uLvR0hWqqhyE7Q9M8= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7/go.mod h1:QraP0UcVlQJsmHfioCrveWOC1nbiWUl3ej08h4mXWoc= +github.com/aws/aws-sdk-go-v2/config v1.26.3 h1:dKuc2jdp10y13dEEvPqWxqLoc0vF3Z9FC45MvuQSxOA= +github.com/aws/aws-sdk-go-v2/config v1.26.3/go.mod h1:Bxgi+DeeswYofcYO0XyGClwlrq3DZEXli0kLf4hkGA0= +github.com/aws/aws-sdk-go-v2/credentials v1.16.16 h1:8q6Rliyv0aUFAVtzaldUEcS+T5gbadPbWdV1WcAddK8= +github.com/aws/aws-sdk-go-v2/credentials v1.16.16/go.mod h1:UHVZrdUsv63hPXFo1H7c5fEneoVo9UXiz36QG1GEPi0= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11 h1:c5I5iH+DZcH3xOIMlz3/tCKJDaHFwYEmxvlh2fAcFo8= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11/go.mod h1:cRrYDYAMUohBJUtUnOhydaMHtiK/1NZ0Otc9lIb6O0Y= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.24 h1:4usbeaes3yJnCFC7kfeyhkdkPtoRYPa/hTmCqMpKpLI= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.24/go.mod h1:5CI1JemjVwde8m2WG3cz23qHKPOxbpkq0HaoreEgLIY= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.24 h1:N1zsICrQglfzaBnrfM0Ys00860C+QFwu6u/5+LomP+o= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.24/go.mod h1:dCn9HbJ8+K31i8IQ8EWmWj0EiIk0+vKiHNMxTTYveAg= +github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2 h1:GrSw8s0Gs/5zZ0SX+gX4zQjRnRsMJDJ2sLur1gRBhEM= +github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2/go.mod h1:6fQQgfuGmw8Al/3M2IgIllycxV7ZW7WCdVSqfBeUiCY= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.24 h1:JX70yGKLj25+lMC5Yyh8wBtvB01GDilyRuJvXJ4piD0= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.24/go.mod h1:+Ln60j9SUTD0LEwnhEB0Xhg61DHqplBrbZpLgyjoEHg= +github.com/aws/aws-sdk-go-v2/service/iam v1.38.1 h1:hfkzDZHBp9jAT4zcd5mtqckpU4E3Ax0LQaEWWk1VgN8= +github.com/aws/aws-sdk-go-v2/service/iam v1.38.1/go.mod h1:u36ahDtZcQHGmVm/r+0L1sfKX4fzLEMdCqiKRKkUMVM= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 h1:iXtILhvDxB6kPvEXgsDhGaZCSC6LQET5ZHSdJozeI0Y= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1/go.mod h1:9nu0fVANtYiAePIBh2/pFUSwtJ402hLnp854CNoDOeE= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.5 h1:gvZOjQKPxFXy1ft3QnEyXmT+IqneM9QAUWlM3r0mfqw= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.5/go.mod h1:DLWnfvIcm9IET/mmjdxeXbBKmTCm0ZB8p1za9BVteM8= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.5 h1:wtpJ4zcwrSbwhECWQoI/g6WM9zqCcSpHDJIWSbMLOu4= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.5/go.mod h1:qu/W9HXQbbQ4+1+JcZp0ZNPV31ym537ZJN+fiS7Ti8E= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.5 h1:P1doBzv5VEg1ONxnJss1Kh5ZG/ewoIE4MQtKKc6Crgg= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.5/go.mod h1:NOP+euMW7W3Ukt28tAxPuoWao4rhhqJD3QEBk7oCg7w= +github.com/aws/aws-sdk-go-v2/service/s3 v1.69.0 h1:Q2ax8S21clKOnHhhr933xm3JxdJebql+R7aNo7p7GBQ= +github.com/aws/aws-sdk-go-v2/service/s3 v1.69.0/go.mod h1:ralv4XawHjEMaHOWnTFushl0WRqim/gQWesAMF6hTow= +github.com/aws/aws-sdk-go-v2/service/sso v1.18.7 h1:eajuO3nykDPdYicLlP3AGgOyVN3MOlFmZv7WGTuJPow= +github.com/aws/aws-sdk-go-v2/service/sso v1.18.7/go.mod h1:+mJNDdF+qiUlNKNC3fxn74WWNN+sOiGOEImje+3ScPM= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7 h1:QPMJf+Jw8E1l7zqhZmMlFw6w1NmfkfiSK8mS4zOx3BA= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7/go.mod h1:ykf3COxYI0UJmxcfcxcVuz7b6uADi1FkiUz6Eb7AgM8= +github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 h1:NzO4Vrau795RkUdSHKEwiR01FaGzGOH1EETJ+5QHnm0= +github.com/aws/aws-sdk-go-v2/service/sts v1.26.7/go.mod h1:6h2YuIoxaMSCFf5fi1EgZAwdfkGMgDY+DVfa61uLe4U= +github.com/aws/smithy-go v1.22.1 h1:/HPHZQ0g7f4eUeK6HKglFz8uwVfZKgoI25rb/J+dnro= +github.com/aws/smithy-go v1.22.1/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/beorn7/perks v0.0.0-20150223135152-b965b613227f/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +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/bitly/go-hostpool v0.1.0/go.mod h1:4gOCgp6+NZnVqlKyZ/iBZFTAJKembaVENUpMkpg42fw= +github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= +github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= +github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= +github.com/bmatcuk/doublestar/v4 v4.6.1 h1:FH9SifrbvJhnlQpztAx++wlkk70QBf0iBWDwNy7PA4I= +github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= +github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= +github.com/bombsimon/logrusr/v2 v2.0.1 h1:1VgxVNQMCvjirZIYaT9JYn6sAVGVEcNtRE0y4mvaOAM= +github.com/bombsimon/logrusr/v2 v2.0.1/go.mod h1:ByVAX+vHdLGAfdroiMg6q0zgq2FODY2lc5YJvzmOJio= +github.com/bradleyfalzon/ghinstallation/v2 v2.11.0 h1:R9d0v+iobRHSaE4wKUnXFiZp53AL4ED5MzgEMwGTZag= +github.com/bradleyfalzon/ghinstallation/v2 v2.11.0/go.mod h1:0LWKQwOHewXO/1acI6TtyE0Xc4ObDb2rFN7eHBAG71M= +github.com/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuPGoOVeF2fE4Og9otCc70= +github.com/bshuster-repo/logrus-logstash-hook v1.0.0/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= +github.com/bugsnag/bugsnag-go v1.0.5-0.20150529004307-13fd6b8acda0 h1:s7+5BfS4WFJoVF9pnB8kBk03S7pZXRdKamnV0FOl5Sc= +github.com/bugsnag/bugsnag-go v1.0.5-0.20150529004307-13fd6b8acda0/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= +github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b h1:otBG+dV+YK+Soembjv71DPz3uX/V/6MMlSyD9JBQ6kQ= +github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50= +github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 h1:nvj0OLI3YqYXer/kZD8Ri1aaunCxIEsOst1BVJswV0o= +github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNSjIRk= +github.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004 h1:lkAMpLVBDaj17e85keuznYcH5rqI438v41pKcBl4ZxQ= +github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004/go.mod h1:yMWuSON2oQp+43nFtAV/uvKQIFpSPerB57DCt9t8sSA= github.com/cloudflare/circl v1.3.9 h1:QFrlgFYf2Qpi8bSpVPK1HBvWpx16v/1TZivyo7pGuBE= github.com/cloudflare/circl v1.3.9/go.mod h1:PDRU+oXvdD7KCtgKxW95M5Z8BpSCJXQORiZFnBQS5QU= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM= +github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw= +github.com/containerd/containerd v1.7.23 h1:H2CClyUkmpKAGlhQp95g2WXHfLYc7whAuvZGBNYOOwQ= +github.com/containerd/containerd v1.7.23/go.mod h1:7QUzfURqZWCZV7RLNEn1XjUCQLEf0bkaK4GjUaZehxw= +github.com/containerd/continuity v0.4.2 h1:v3y/4Yz5jwnvqPKJJ+7Wf93fyWoCB3F5EclWG023MDM= +github.com/containerd/continuity v0.4.2/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ= +github.com/containerd/errdefs v0.3.0 h1:FSZgGOeK4yuT/+DnF07/Olde/q4KBoMsaamhXxIMDp4= +github.com/containerd/errdefs v0.3.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= github.com/containerd/go-cni v1.1.10 h1:c2U73nld7spSWfiJwSh/8W9DK+/qQwYM2rngIhCyhyg= github.com/containerd/go-cni v1.1.10/go.mod h1:/Y/sL8yqYQn1ZG1om1OncJB1W4zN3YmjfP/ShCzG/OY= +github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= +github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= +github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= +github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= +github.com/containerd/stargz-snapshotter/estargz v0.14.3 h1:OqlDCK3ZVUO6C3B/5FSkDwbkEETK84kQgEeFwDC+62k= +github.com/containerd/stargz-snapshotter/estargz v0.14.3/go.mod h1:KY//uOCIkSuNAHhJogcZtrNHdKrA99/FCCRjE3HD36o= github.com/containernetworking/cni v1.2.3 h1:hhOcjNVUQTnzdRJ6alC5XF+wd9mfGIUaj8FuJbEslXM= github.com/containernetworking/cni v1.2.3/go.mod h1:DuLgF+aPd3DzcTQTtp/Nvl1Kim23oFKdm2okJzBQA5M= -github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= +github.com/coreos/go-oidc/v3 v3.11.0 h1:Ia3MxdwpSw702YW0xgfmP1GVCMA9aEFWu12XUZ3/OtI= +github.com/coreos/go-oidc/v3 v3.11.0/go.mod h1:gE3LgjOgFoHi9a4ce4/tJczr0Ai2/BoDhf0r5lltWI0= +github.com/corpix/uarand v0.0.0-20170723150923-031be390f409 h1:9A+mfQmwzZ6KwUXPc8nHxFtKgn9VIvO3gXAOspIcE3s= +github.com/corpix/uarand v0.0.0-20170723150923-031be390f409/go.mod h1:JSm890tOkDN+M1jqN8pUGDKnzJrsVbJwSMHBY4zwz7M= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= +github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/creasty/defaults v1.8.0 h1:z27FJxCAa0JKt3utc0sCImAEb+spPucmKoOdLHvHYKk= github.com/creasty/defaults v1.8.0/go.mod h1:iGzKe6pbEHnpMPtfDXZEr0NVxWnPTjb1bbDy08fPzYM= -github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= -github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= +github.com/cyphar/filepath-securejoin v0.3.4 h1:VBWugsJh2ZxJmLFSM06/0qzQyiQX2Qs0ViKrUAcqdZ8= +github.com/cyphar/filepath-securejoin v0.3.4/go.mod h1:8s/MCNJREmFK0H02MF6Ihv1nakJe4L/w3WZLHNkvlYM= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/denisenkom/go-mssqldb v0.0.0-20191128021309-1d7a30a10f73/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U= +github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE= +github.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2 h1:aBfCb7iqHmDEIp6fBvC/hQUddQfg+3qdYjwzaiP9Hnc= +github.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2/go.mod h1:WHNsWjnIn2V1LYOrME7e8KxSeKunYHsxEm4am0BUtcI= +github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo= +github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/docker/cli v27.0.3+incompatible h1:usGs0/BoBW8MWxGeEtqPMkzOY56jZ6kYlSN5BLDioCQ= +github.com/docker/cli v27.0.3+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= +github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v27.0.3+incompatible h1:aBGI9TeQ4MPlhquTQKq9XbK79rKFVwXNUAYz9aXyEBE= +github.com/docker/docker v27.0.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker-credential-helpers v0.8.1 h1:j/eKUktUltBtMzKqmfLB0PAgqYyMHOp5vfsD1807oKo= +github.com/docker/docker-credential-helpers v0.8.1/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M= +github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c h1:lzqkGL9b3znc+ZUgi7FlLnqjQhcXxkNM/quxIjBVMD0= +github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c/go.mod h1:CADgU4DSXK5QUlFslkQu2yW2TKzFZcXq/leZfM0UH5Q= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= +github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= +github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8= +github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= +github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI= +github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8= +github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 h1:UhxFibDNY/bfvqU5CAUmr9zpesgbU6SWc8/B4mflAE4= +github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= +github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/dvsekhvalnov/jose2go v0.0.0-20170216131308-f21a8cedbbae/go.mod h1:7BvyPhdbLxMXIYTFPLsyJRFMsKmOZnQmzh6Gb+uquuM= +github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU= github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= +github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emicklei/go-restful/v3 v3.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU= +github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/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 v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v5.9.0+incompatible h1:fBXyNpNMuTTDdquAq/uisOr2lShz4oaXpDTX2bLe7ls= +github.com/evanphx/json-patch v5.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg= +github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= +github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwCFad8crR9dcMQWvV9Hvulu6hwUh4tWPJnM= +github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4= +github.com/fatih/camelcase v1.0.0 h1:hxNvNX/xYBp0ovncs8WyWZrOrpBNub/JfaMvbURyft8= +github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= +github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= +github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= +github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/foxcpp/go-mockdns v1.1.0 h1:jI0rD8M0wuYAxL7r/ynTrCQQq0BVqfB99Vgk7DlmewI= +github.com/foxcpp/go-mockdns v1.1.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld2qVbOu7Wk= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/fvbommel/sortorder v1.1.0 h1:fUmoe+HLsBTctBDoaBwpQo5N+nrCp8g/BjKb/6ZQmYw= +github.com/fvbommel/sortorder v1.1.0/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= +github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gliderlabs/ssh v0.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE= github.com/gliderlabs/ssh v0.3.7/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8= +github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= +github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= @@ -47,191 +282,919 @@ github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMj github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys= github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY= +github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs= +github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw= +github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k= +github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= +github.com/go-jose/go-jose/v4 v4.0.2 h1:R3l3kkBds16bO7ZFAEEcofK0MkrAJt3jlJznWZG0nvk= +github.com/go-jose/go-jose/v4 v4.0.2/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= +github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= +github.com/go-logr/logr v1.0.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= +github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= +github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= +github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= +github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= +github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= +github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= +github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= +github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA= -github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/go-playground/validator/v10 v10.23.0 h1:/PwmTwZhS0dPkav3cdK9kV1FsAmrL8sThn8IHr/sO+o= +github.com/go-playground/validator/v10 v10.23.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/go-playground/webhooks/v6 v6.4.0 h1:KLa6y7bD19N48rxJDHM0DpE3T4grV7GxMy1b/aHMWPY= +github.com/go-playground/webhooks/v6 v6.4.0/go.mod h1:5lBxopx+cAJiBI4+kyRbuHrEi+hYRDdRHuRR4Ya5Ums= +github.com/go-redis/cache/v9 v9.0.0 h1:0thdtFo0xJi0/WXbRVu8B066z8OvVymXTJGaXrVWnN0= +github.com/go-redis/cache/v9 v9.0.0/go.mod h1:cMwi1N8ASBOufbIvk7cdXe2PbPjK/WMRL95FFHWsSgI= github.com/go-sprout/sprout v0.6.0 h1:YIcSYNKjFFMORcjts5/N6S0toNcJiX1cbXi619qVjA0= github.com/go-sprout/sprout v0.6.0/go.mod h1:P6ETppcGn1BR0HZ8r+660aP2hJH7xiamIGiWjA+AE4o= +github.com/go-sql-driver/mysql v1.3.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= +github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg= +github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= +github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= +github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/gogits/go-gogs-client v0.0.0-20200905025246-8bb8a50cb355 h1:HTVNOdTWO/gHYeFnr/HwpYwY6tgMcYd+Rgf1XrHnORY= +github.com/gogits/go-gogs-client v0.0.0-20200905025246-8bb8a50cb355/go.mod h1:cY2AIrMgHm6oOHmR7jY+9TtjzSjQ3iG7tURJG3Y6XH0= +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.2-0.20190723190241-65acae22fc9d/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/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= +github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/gomodule/redigo v1.8.2 h1:H5XSIre1MB5NbPYFp+i1NBbb5qN1W8Y8YAQoAYbkm8k= +github.com/gomodule/redigo v1.8.2/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0= +github.com/goodhosts/hostsfile v0.1.6 h1:aK6DxpNV6pZ1NbdvNE2vYBMTnvIJF5O2J/8ZOlp2eMY= +github.com/goodhosts/hostsfile v0.1.6/go.mod h1:bkCocEIf3Ca0hcBustUZoWYhOgKUaIK+47m8fBjoBx8= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= +github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= +github.com/google/certificate-transparency-go v1.0.10-0.20180222191210-5ab67e519c93 h1:jc2UWq7CbdszqeH6qu1ougXMIUBfSy8Pbh/anURYbGI= +github.com/google/certificate-transparency-go v1.0.10-0.20180222191210-5ab67e519c93/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg= +github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= +github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-containerregistry v0.19.1 h1:yMQ62Al6/V0Z7CqIrrS1iYoA5/oQCm88DeNujc7C1KY= +github.com/google/go-containerregistry v0.19.1/go.mod h1:YCMFNQeeXeLF+dnhhWkqDItx/JSkH01j1Kis4PsjzFI= +github.com/google/go-github/v62 v62.0.0 h1:/6mGCaRywZz9MuHyw9gD1CwsbmBX8GWsbFkwMmHdhl4= +github.com/google/go-github/v62 v62.0.0/go.mod h1:EMxeUqGJq2xRu9DYBMwel/mr7kZrzUOfQmmpYrZn2a4= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af h1:kmjWCqn2qkEml422C2Rrd27c3VGxi6a/6HNq8QmHRKM= github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= +github.com/google/uuid v1.1.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.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= +github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= +github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE= +github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w= +github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY= +github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= +github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= +github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= +github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= +github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8= +github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= +github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= +github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= +github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/icrowley/fake v0.0.0-20221112152111-d7b7e2276db2 h1:qU3v73XG4QAqCPHA4HOpfC1EfUvtLIDvQK4mNQ0LvgI= +github.com/icrowley/fake v0.0.0-20221112152111-d7b7e2276db2/go.mod h1:dQ6TM/OGAe+cMws81eTe4Btv1dKxfPZ2CX+YaAFAPN4= +github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= +github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/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/jinzhu/gorm v0.0.0-20170222002820-5409931a1bb8 h1:CZkYfurY6KGhVtlalI4QwQ6T0Cu6iuY3e0x5RLu96WE= +github.com/jinzhu/gorm v0.0.0-20170222002820-5409931a1bb8/go.mod h1:Vla75njaFJ8clLU1W44h34PjIkijhjHIYnZxMqCdxqo= +github.com/jinzhu/inflection v0.0.0-20170102125226-1c35d901db3d h1:jRQLvyVGL+iVtDElaEIDdKwpPqUIZJfzkNLV34htpEc= +github.com/jinzhu/inflection v0.0.0-20170102125226-1c35d901db3d/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= +github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= +github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ= +github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/k3d-io/k3d/v5 v5.7.4 h1:L7Ix6zFMqjoPuEJ5dctLb7kCFXXlF5js6I7bm+tEgo0= +github.com/k3d-io/k3d/v5 v5.7.4/go.mod h1:bFlhRV/R1cPT42ZZzQAHPHUF33CbCT8VSbjtjTr3J1Y= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.3/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= +github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw= +github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= +github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= +github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/lib/pq v0.0.0-20150723085316-0dad96c0b94f/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= +github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= +github.com/lithammer/dedent v1.1.0 h1:VNzHMVCBNG1j0fh3OrsFRkVUwStdDArbgBWoPAffktY= +github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= +github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg= +github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= +github.com/magiconair/properties v1.5.3/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-sqlite3 v1.6.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM= +github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk= +github.com/miekg/pkcs11 v1.0.2/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= +github.com/miekg/pkcs11 v1.1.1 h1:Ugu9pdy6vAYku5DEpVWVFPYnzV+bxB+iRdbuFSu7TvU= +github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= +github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM= +github.com/minio/minio-go/v7 v7.0.58/go.mod h1:NUDy4A4oXPq1l2yK6LTSvCEzAMeIcoz9lcj5dbzSrRE= +github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= +github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= +github.com/mitchellh/mapstructure v0.0.0-20150613213606-2caf8efc9366/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= +github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= +github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= +github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= +github.com/moby/patternmatcher v0.5.0 h1:YCZgJOeULcxLw1Q+sVR636pmS7sPEn1Qo2iAN6M7DBo= +github.com/moby/patternmatcher v0.5.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= +github.com/moby/spdystream v0.4.0 h1:Vy79D6mHeJJjiPdFEL2yku1kl0chZpJfZcPpb16BRl8= +github.com/moby/spdystream v0.4.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= +github.com/moby/sys/mountinfo v0.7.1 h1:/tTvQaSJRr2FshkhXiIpux6fQ2Zvc4j7tAhMTStAG2g= +github.com/moby/sys/mountinfo v0.7.1/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI= +github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= +github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= +github.com/moby/sys/user v0.3.0 h1:9ni5DlcW5an3SvRSx4MouotOygvzaXbaSrc/wGDFWPo= +github.com/moby/sys/user v0.3.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= +github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= +github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= +github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= +github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA= -github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To= -github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= -github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= +github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0= +github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/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/oliveagle/jsonpath v0.0.0-20180606110733-2e52cf6e6852/go.mod h1:eqOVx5Vwu4gd2mmMZvVZsgIqNSaW3xxRThUJ0k/TPk4= +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.10.1/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.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.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= +github.com/onsi/ginkgo/v2 v2.1.4/go.mod h1:um6tUpWM/cxCK3/FK8BXqEiUMUwRgSM4JXG47RKZmLU= +github.com/onsi/ginkgo/v2 v2.1.6/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk= +github.com/onsi/ginkgo/v2 v2.3.0/go.mod h1:Eew0uilEqZmIEZr8JrvYlvOM7Rr6xzTmMV8AyFNU9d0= +github.com/onsi/ginkgo/v2 v2.4.0/go.mod h1:iHkDK1fKGcBoEHT5W7YBq4RFWaQulw+caOMkAt4OrFo= +github.com/onsi/ginkgo/v2 v2.5.0/go.mod h1:Luc4sArBICYCS8THh8v3i3i5CuSZO+RaQRaJoeNwomw= +github.com/onsi/ginkgo/v2 v2.7.0/go.mod h1:yjiuMwPokqY1XauOgju45q3sJt6VzQ/Fict1LFVcsAo= +github.com/onsi/ginkgo/v2 v2.19.1 h1:QXgq3Z8Crl5EL1WBAC98A5sEBHARrAJNzAmMxzLcRF0= +github.com/onsi/ginkgo/v2 v2.19.1/go.mod h1:O3DtEWQkPa/F7fBMgmZQKKsluAy8pd3rEQdrjkPb9zA= +github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +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.9.0/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.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= +github.com/onsi/gomega v1.20.1/go.mod h1:DtrZpjmvpn2mPm4YWQa0/ALMDj9v4YxLgojwPeREyVo= +github.com/onsi/gomega v1.21.1/go.mod h1:iYAIXgPSaDHak0LCMA+AWBpIKBr8WZicMxnE8luStNc= +github.com/onsi/gomega v1.22.1/go.mod h1:x6n7VNe4hw0vkyYUM4mjIXx3JbLiPaBPNgB7PRQ1tuM= +github.com/onsi/gomega v1.24.0/go.mod h1:Z/NWtiqwBrwUt4/2loMmHL63EDLnYHmVbuBpDr2vQAg= +github.com/onsi/gomega v1.24.1/go.mod h1:3AOiACssS3/MajrniINInwbfOOtfZvplPzuRSmvt1jM= +github.com/onsi/gomega v1.25.0/go.mod h1:r+zV744Re+DiYCIPRlYOTxn0YkOLcAnW8k1xXdMPGhM= +github.com/onsi/gomega v1.34.0 h1:eSSPsPNp6ZpsG8X1OVmOTxig+CblTc4AxpPBykhe2Os= +github.com/onsi/gomega v1.34.0/go.mod h1:MIKI8c+f+QLWk+hxbePD4i0LMJSExPaZOVfkoex4cAo= +github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/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.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= +github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= +github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= +github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= +github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= +github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI= +github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE= github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/poy/onpar v1.1.2 h1:QaNrNiZx0+Nar5dLgTVp5mXkyoVFIbepjyEoGSnhbAY= +github.com/poy/onpar v1.1.2/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjzg= +github.com/prometheus/client_golang v0.9.0-pre1.0.20180209125602-c332b6f63c06/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang 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.20.3 h1:oPksm4K8B+Vt35tUhw6GbSNSgVlVSBH0qELP/7u83l4= +github.com/prometheus/client_golang v1.20.3/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= +github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/common v0.0.0-20180110214958-89604d197083/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +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.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= +github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= +github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/r3labs/diff v1.1.0 h1:V53xhrbTHrWFWq3gI4b94AjgEJOerO1+1l0xyHOBi8M= +github.com/r3labs/diff v1.1.0/go.mod h1:7WjXasNzi0vJetRcB/RqNl5dlIsmXcTTLmF5IoH6Xig= +github.com/rancher/dynamiclistener v0.3.5 h1:5TaIHvkDGmZKvc96Huur16zfTKOiLhDtK4S+WV0JA6A= +github.com/rancher/dynamiclistener v0.3.5/go.mod h1:dW/YF6/m2+uEyJ5VtEcd9THxda599HP6N9dSXk81+k0= +github.com/rancher/wharfie v0.6.2 h1:ZTrZ0suU0abWwLLf2zaqjhwpxK8+BkbnMocnU2u1bSQ= +github.com/rancher/wharfie v0.6.2/go.mod h1:7ii0+eehBwUEFaJMiRHWCbvN11bsfVHT1oc+P/6IBSg= +github.com/redis/go-redis/v9 v9.0.0-rc.4/go.mod h1:Vo3EsyWnicKnSKCA7HhgnvnyA74wOA69Cd2Meli5mmA= +github.com/redis/go-redis/v9 v9.6.1 h1:HHDteefn6ZkTtY5fGUE8tj8uy85AHk6zP7CpzIAM0y4= +github.com/redis/go-redis/v9 v9.6.1/go.mod h1:0C0c6ycQsdpVNQpxb1njEQIqkx5UcsM8FJCQLgE9+RA= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= +github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rubenv/sql-migrate v1.7.0 h1:HtQq1xyTN2ISmQDggnh0c9U3JlP8apWh8YO2jzlXpTI= +github.com/rubenv/sql-migrate v1.7.0/go.mod h1:S4wtDEG1CKn+0ShpTtzWhFpHHI5PvCUtiGI+C+Z2THE= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= +github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= +github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= +github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= +github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= +github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/siderolabs/crypto v0.5.0 h1:+Sox0aYLCcD0PAH2cbEcx557zUrONLtuj1Ws+2MFXGc= github.com/siderolabs/crypto v0.5.0/go.mod h1:hsR3tJ3aaeuhCChsLF4dBd9vlJVPvmhg4vvx2ez4aD4= github.com/siderolabs/gen v0.5.0 h1:Afdjx+zuZDf53eH5DB+E+T2JeCwBXGinV66A6osLgQI= github.com/siderolabs/gen v0.5.0/go.mod h1:1GUMBNliW98Xeq8GPQeVMYqQE09LFItE8enR3wgMh3Q= -github.com/siderolabs/talos/pkg/machinery v1.8.2 h1:oBNBIPjOo6SNvVaUb4B4PHGefR6HYgYVl1/GPGppj98= -github.com/siderolabs/talos/pkg/machinery v1.8.2/go.mod h1:cNR2TELu2T9AzYOHAoNr/7ZS3ZVDLzM/KnuOr4XW4s4= +github.com/siderolabs/talos/pkg/machinery v1.8.3 h1:raK1oLzSMpwpy/AqkeFyBYkJS+QuOnlRMznVl/rZ25k= +github.com/siderolabs/talos/pkg/machinery v1.8.3/go.mod h1:cNR2TELu2T9AzYOHAoNr/7ZS3ZVDLzM/KnuOr4XW4s4= +github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sirupsen/logrus v1.9.2/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/skeema/knownhosts v1.2.2 h1:Iug2P4fLmDw9f41PB6thxUkNUkJzB5i+1/exaj40L3A= github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo= -github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= -github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= +github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= +github.com/spf13/cast v0.0.0-20150508191742-4d07383ffe94/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg= +github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= +github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cobra v0.0.1/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= +github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= +github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= +github.com/spf13/jwalterweatherman v0.0.0-20141219030609-3d60171a6431/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.0/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v0.0.0-20150530192845-be5ff3e4840c/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM= +github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= +github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= 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= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/urfave/cli/v2 v2.27.4 h1:o1owoI+02Eb+K107p27wEX9Bb8eqIoZCfLXloLUSWJ8= -github.com/urfave/cli/v2 v2.27.4/go.mod h1:m4QzxcD2qpra4z7WhzEGn74WZLViBnMpb1ToCAKdGRQ= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/theupdateframework/notary v0.7.0 h1:QyagRZ7wlSpjT5N2qQAh/pN+DVqgekv4DzbAiAiEL3c= +github.com/theupdateframework/notary v0.7.0/go.mod h1:c9DRxcmhHmVLDay4/2fUYdISnHqbFDGRSlXPO0AhYWw= +github.com/urfave/cli v1.22.12/go.mod h1:sSBEIC79qR6OvcmsD4U3KABeOTxDqQtdDnaFuUN30b8= +github.com/vbatts/tar-split v0.11.3 h1:hLFqsOLQ1SsppQNTMpkpPXClLDfC2A3Zgy9OUU+RVck= +github.com/vbatts/tar-split v0.11.3/go.mod h1:9QlHN18E+fEH7RdG+QAJJcuya3rqT7eXSTY7wGrAokY= github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8= github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= +github.com/vmihailenco/go-tinylfu v0.2.2 h1:H1eiG6HM36iniK6+21n9LLpzx1G9R3DJa2UjUjbynsI= +github.com/vmihailenco/go-tinylfu v0.2.2/go.mod h1:CutYi2Q9puTxfcolkliPq4npPuofg9N9t8JVrjzwa3Q= +github.com/vmihailenco/msgpack/v5 v5.3.4 h1:qMKAwOV+meBw2Y8k9cVwAy7qErtYCwBzZ2ellBfvnqc= +github.com/vmihailenco/msgpack/v5 v5.3.4/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= +github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= -github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= -github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= +github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= +github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M= +github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= +github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43 h1:+lm10QQTNSBd8DVTNGHx7o/IKu9HYDvLMffDhbyLccI= +github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= +github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50 h1:hlE8//ciYMztlGpl/VA+Zm1AcTPHYkHJPbHqE6WJUXE= +github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA= +github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f h1:ERexzlUfuTvpE74urLSbIQW0Z/6hF9t8U4NsJLaioAY= +github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.55.0 h1:hCq2hNMwsegUvPzI7sPOvtO9cqyy5GbWt/Ybp2xrx8Q= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.55.0/go.mod h1:LqaApwGx/oUmzsbqxkzuBvyoPpkxk3JQWnqfVrJ3wCA= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg= +go.opentelemetry.io/otel v1.30.0 h1:F2t8sK4qf1fAmY9ua4ohFS/K+FUuOPemHUIXHtktrts= +go.opentelemetry.io/otel v1.30.0/go.mod h1:tFw4Br9b7fOS+uEao81PJjVMjW/5fvNCbpsDIXqP0pc= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.28.0 h1:U2guen0GhqH8o/G2un8f/aG/y++OuW6MyCo6hT9prXk= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.28.0/go.mod h1:yeGZANgEcpdx/WK0IvvRFC+2oLiMS2u4L/0Rj2M2Qr0= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0 h1:lsInsfvhVIfOI6qHVyysXMNDnjO9Npvl7tlDPJFBVd4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0/go.mod h1:KQsVNh4OjgjTG0G6EiNi1jVpnaeeKsKMRwbLN+f1+8M= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.30.0 h1:m0yTiGDLUvVYaTFbAvCkVYIYcvwKt3G7OLoN77NUs/8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.30.0/go.mod h1:wBQbT4UekBfegL2nx0Xk1vBcnzyBPsIVm9hRG4fYcr4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU= +go.opentelemetry.io/otel/metric v1.30.0 h1:4xNulvn9gjzo4hjg+wzIKG7iNFEaBMX00Qd4QIZs7+w= +go.opentelemetry.io/otel/metric v1.30.0/go.mod h1:aXTfST94tswhWEb+5QjlSqG+cZlmyXy/u8jFpor3WqQ= +go.opentelemetry.io/otel/sdk v1.30.0 h1:cHdik6irO49R5IysVhdn8oaiR9m8XluDaJAs4DfOrYE= +go.opentelemetry.io/otel/sdk v1.30.0/go.mod h1:p14X4Ok8S+sygzblytT1nqG98QG2KYKv++HE0LY/mhg= +go.opentelemetry.io/otel/sdk/metric v1.28.0 h1:OkuaKgKrgAbYrrY0t92c+cC+2F6hsFNnCQArXCKlg08= +go.opentelemetry.io/otel/sdk/metric v1.28.0/go.mod h1:cWPjykihLAPvXKi4iZc1dpER3Jdq2Z0YLse3moQUCpg= +go.opentelemetry.io/otel/trace v1.30.0 h1:7UBkkYzeg3C7kQX8VAidWh2biiQbtAKjyIML8dQ9wmc= +go.opentelemetry.io/otel/trace v1.30.0/go.mod h1:5EyKqTzzmyqB9bwtCCq6pDLktPK6fmGf/Dph+8VI02o= +go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= +go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= +go.starlark.net v0.0.0-20230525235612-a134d8f9ddca h1:VdD38733bfYv5tUZwEIskMM93VanwNIi5bIKnDrJdEY= +go.starlark.net v0.0.0-20230525235612-a134d8f9ddca/go.mod h1:jxU+3+j+71eXOW14274+SmmuW82qJzl6iZSeqEtTGds= +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.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M= +go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= -golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= +golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= +golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I= +golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= +golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= +golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= +golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.0.0-20170114055629-f2499483f923/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-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= -golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= +golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ= +golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= +golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= +golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= +golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/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-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210608053332-aa57babbf139/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= -golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220906165534-d0df966e6959/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= +golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= -golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= +golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= +golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo= +golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU= +golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E= +golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= -golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= +golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= +golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= +golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= 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= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY= +google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:VUhTRKeHn9wwcdrk73nvdC9gF178Tzhmt/qyaFcPLSo= +google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 h1:hjSy6tcFQZ171igDaN5QHOw2n6vx40juYbC/x67CEhc= +google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= +google.golang.org/grpc v1.0.5/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.66.3 h1:TWlsh8Mv0QI/1sIbs1W36lqRclxrmF+eFJ4DbI0fuhA= +google.golang.org/grpc v1.66.3/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/cenkalti/backoff.v2 v2.2.1 h1:eJ9UAg01/HIHG987TwxvnzK2MgxXq97YY6rYDpY9aII= +gopkg.in/cenkalti/backoff.v2 v2.2.1/go.mod h1:S0QdOvT2AlerfSBkp0O+dk+bbIMaNbEmVk876gPCthU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= +gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/rethinkdb/rethinkdb-go.v6 v6.2.1 h1:d4KQkxAaAiRY2h5Zqis161Pv91A37uZyJOx73duwUwM= +gopkg.in/rethinkdb/rethinkdb-go.v6 v6.2.1/go.mod h1:WbjuEoo1oadwzQ4apSDU+JTvmllEHtsNHS6y7vFc7iw= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.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.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-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= +gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o= +gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g= +helm.sh/helm/v3 v3.16.3 h1:kb8bSxMeRJ+knsK/ovvlaVPfdis0X3/ZhYCSFRP+YmY= +helm.sh/helm/v3 v3.16.3/go.mod h1:zeVWGDR4JJgiRbT3AnNsjYaX8OTJlIE9zC+Q7F7iUSU= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +k8s.io/api v0.17.8/go.mod h1:N++Llhs8kCixMUoCaXXAyMMPbo8dDVnh+IQ36xZV2/0= k8s.io/api v0.31.2 h1:3wLBbL5Uom/8Zy98GRPXpJ254nEFpl+hwndmk9RwmL0= k8s.io/api v0.31.2/go.mod h1:bWmGvrGPssSK1ljmLzd3pwCQ9MgoTsRCuK35u6SygUk= +k8s.io/apiextensions-apiserver v0.31.2 h1:W8EwUb8+WXBLu56ser5IudT2cOho0gAKeTOnywBLxd0= +k8s.io/apiextensions-apiserver v0.31.2/go.mod h1:i+Geh+nGCJEGiCGR3MlBDkS7koHIIKWVfWeRFiOsUcM= +k8s.io/apimachinery v0.17.8/go.mod h1:Lg8zZ5iC/O8UjCqW6DNhcQG2m4TdjF9kwG3891OWbbA= k8s.io/apimachinery v0.31.2 h1:i4vUt2hPK56W6mlT7Ry+AO8eEsyxMD1U44NR22CLTYw= k8s.io/apimachinery v0.31.2/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= +k8s.io/apiserver v0.31.2 h1:VUzOEUGRCDi6kX1OyQ801m4A7AUPglpsmGvdsekmcI4= +k8s.io/apiserver v0.31.2/go.mod h1:o3nKZR7lPlJqkU5I3Ove+Zx3JuoFjQobGX1Gctw6XuE= +k8s.io/cli-runtime v0.31.2 h1:7FQt4C4Xnqx8V1GJqymInK0FFsoC+fAZtbLqgXYVOLQ= +k8s.io/cli-runtime v0.31.2/go.mod h1:XROyicf+G7rQ6FQJMbeDV9jqxzkWXTYD6Uxd15noe0Q= +k8s.io/client-go v0.17.8/go.mod h1:SJsDS64AAtt9VZyeaQMb4Ck5etCitZ/FwajWdzua5eY= +k8s.io/client-go v0.31.2 h1:Y2F4dxU5d3AQj+ybwSMqQnpZH9F30//1ObxOKlTI9yc= +k8s.io/client-go v0.31.2/go.mod h1:NPa74jSVR/+eez2dFsEIHNa+3o09vtNaWwWwb1qSxSs= +k8s.io/component-base v0.31.2 h1:Z1J1LIaC0AV+nzcPRFqfK09af6bZ4D1nAOpWsy9owlA= +k8s.io/component-base v0.31.2/go.mod h1:9PeyyFN/drHjtJZMCTkSpQJS3U9OXORnHQqMLDz0sUQ= +k8s.io/component-helpers v0.31.2 h1:V2yjoNeyg8WfvwrJwzfYz+RUwjlbcAIaDaHEStBbaZM= +k8s.io/component-helpers v0.31.2/go.mod h1:cNz+1ck38R0qWrjcw/rhQgGP6+Gwgw8ngr2ziDNmSXM= +k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +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 v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= +k8s.io/klog/v2 v2.5.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/kube-aggregator v0.31.2 h1:Uw1zUP2D/4wiSjKWVVzSOcCGLuW/+IdRwjjC0FJooYU= +k8s.io/kube-aggregator v0.31.2/go.mod h1:41/VIXH+/Qcg9ERNAY6bRF/WQR6xL1wFgYagdHac1X4= +k8s.io/kube-openapi v0.0.0-20200410145947-bcb3869e6f29/go.mod h1:F+5wygcW0wmRTnM3cOgIqGivxkwSWIWT5YdsDbeAOaU= +k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= +k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= +k8s.io/kubectl v0.31.2 h1:gTxbvRkMBwvTSAlobiTVqsH6S8Aa1aGyBcu5xYLsn8M= +k8s.io/kubectl v0.31.2/go.mod h1:EyASYVU6PY+032RrTh5ahtSOMgoDRIux9V1JLKtG5xM= +k8s.io/kubernetes v1.31.2 h1:VNSu4O7Xn5FFRsh9ePXyEPg6ucR21fOftarSdi053Gs= +k8s.io/kubernetes v1.31.2/go.mod h1:9xmT2buyTYj8TRKwRae7FcuY8k5+xlxv7VivvO0KKfs= +k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A= k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +oras.land/oras-go v1.2.5 h1:XpYuAwAb0DfQsunIyMfeET92emK8km3W4yEzZvUbsTo= +oras.land/oras-go v1.2.5/go.mod h1:PuAwRShRZCsZb7g8Ar3jKKQR/2A/qN+pkYxIOd/FAoo= +oras.land/oras-go/v2 v2.5.0 h1:o8Me9kLY74Vp5uw07QXPiitjsw7qNXi8Twd+19Zf02c= +oras.land/oras-go/v2 v2.5.0/go.mod h1:z4eisnLP530vwIOUOJeBIj0aGI0L1C3d53atvCBqZHg= +sigs.k8s.io/cluster-api v1.8.5 h1:lNA2fPN4fkXEs+oOQlnwxT/4VwRFBpv5kkSoJG8nqBA= +sigs.k8s.io/cluster-api v1.8.5/go.mod h1:pXv5LqLxuIbhGIXykyNKiJh+KrLweSBajVHHitPLyoY= +sigs.k8s.io/controller-runtime v0.19.1 h1:Son+Q40+Be3QWb+niBXAg2vFiYWolDjjRfO8hn/cxOk= +sigs.k8s.io/controller-runtime v0.19.1/go.mod h1:iRmWllt8IlaLjvTTDLhRBXIEtkCK6hwVBJJsYS9Ajf4= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/kustomize/api v0.17.2 h1:E7/Fjk7V5fboiuijoZHgs4aHuexi5Y2loXlVOAVAG5g= +sigs.k8s.io/kustomize/api v0.17.2/go.mod h1:UWTz9Ct+MvoeQsHcJ5e+vziRRkwimm3HytpZgIYqye0= +sigs.k8s.io/kustomize/kyaml v0.17.1 h1:TnxYQxFXzbmNG6gOINgGWQt09GghzgTP6mIurOgrLCQ= +sigs.k8s.io/kustomize/kyaml v0.17.1/go.mod h1:9V0mCjIEYjlXuCdYsSXvyoy2BTsLESH7TlGV81S282U= +sigs.k8s.io/structured-merge-diff/v2 v2.0.1/go.mod h1:Wb7vfKAodbKgf6tn1Kl0VvGj7mRH6DGaRcixXEJXTsE= sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/pkg/cloud/aws/aws.go b/pkg/cloud/aws/aws.go new file mode 100644 index 0000000..49c2986 --- /dev/null +++ b/pkg/cloud/aws/aws.go @@ -0,0 +1,56 @@ +package aws + +import ( + "context" + "os" + "strings" + + "github.com/Obmondo/kubeaid-bootstrap-script/config" + "github.com/Obmondo/kubeaid-bootstrap-script/constants" + "github.com/Obmondo/kubeaid-bootstrap-script/utils" + "github.com/Obmondo/kubeaid-bootstrap-script/utils/assert" + awsSDKGoV2Config "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/service/iam" + "github.com/aws/aws-sdk-go-v2/service/s3" +) + +type AWS struct { + s3Client *s3.Client + iamClient *iam.Client +} + +func NewAWSCloudProvider() *AWS { + ctx := context.Background() + + // Load AWS SDK config. + awsSDKConfig, err := awsSDKGoV2Config.LoadDefaultConfig(ctx) + assert.AssertErrNil(ctx, err, "Failed initiating AWS SDK config") + + return &AWS{ + s3Client: s3.NewFromConfig(awsSDKConfig), + iamClient: iam.NewFromConfig(awsSDKConfig), + } +} + +// Sets AWS specific environment variables, required by the 'clusterawsadm bootstrap iam' command / +// core.getTemplateValues( ) / AWS SDK. +func SetAWSSpecificEnvs() { + awsCredentials := config.ParsedConfig.Cloud.AWS.Credentials + + os.Setenv(constants.EnvNameAWSAccessKey, awsCredentials.AWSAccessKey) + os.Setenv(constants.EnvNameAWSSecretKey, awsCredentials.AWSSecretKey) + os.Setenv(constants.EnvNameAWSSessionToken, awsCredentials.AWSSessionToken) + os.Setenv(constants.EnvNameAWSRegion, awsCredentials.AWSRegion) + + awsB64EncodedCredentials := strings.TrimSpace( + strings.Split( + utils.ExecuteCommandOrDie("clusterawsadm bootstrap credentials encode-as-profile"), + "WARNING: `encode-as-profile` should only be used for bootstrapping.", + )[1], + ) + os.Setenv(constants.EnvNameAWSB64EcodedCredentials, awsB64EncodedCredentials) +} + +func (*AWS) GetSealedSecretsBackupBucketName() string { + return config.ParsedConfig.Cloud.AWS.DisasterRecovery.SealedSecretsBackupS3BucketName +} diff --git a/pkg/cloud/aws/policies.go b/pkg/cloud/aws/policies.go new file mode 100644 index 0000000..d7f166c --- /dev/null +++ b/pkg/cloud/aws/policies.go @@ -0,0 +1,68 @@ +package aws + +import ( + "fmt" + + "github.com/Obmondo/kubeaid-bootstrap-script/config" + "github.com/Obmondo/kubeaid-bootstrap-script/pkg/cloud/aws/services" +) + +func getSealedSecretsBackuperIAMPolicy() services.PolicyDocument { + sealedSecretBackupsS3BucketName := config.ParsedConfig.Cloud.AWS.DisasterRecovery.SealedSecretsBackupS3BucketName + + return services.PolicyDocument{ + Version: "2012-10-17", + Statement: []services.PolicyStatement{ + { + Action: []string{ + "s3:PutObject", + "s3:AbortMultipartUpload", + "s3:ListMultipartUploadParts", + }, + Effect: "Allow", + Resource: fmt.Sprintf("arn:aws:s3:::-%s", sealedSecretBackupsS3BucketName), + }, + }, + } +} + +func getVeleroIAMPolicy() services.PolicyDocument { + veleroBackupsS3BucketName := config.ParsedConfig.Cloud.AWS.DisasterRecovery.VeleroBackupsS3BucketName + + return services.PolicyDocument{ + Version: "2012-10-17", + Statement: []services.PolicyStatement{ + { + Action: []string{ + "ec2:DescribeVolumes", + "ec2:DescribeSnapshots", + "ec2:CreateTags", + "ec2:CreateVolume", + "ec2:CreateSnapshot", + "ec2:DeleteSnapshot", + "ec2:CopySnapshot", + }, + Effect: "Allow", + Resource: "*", + }, + { + Action: []string{ + "s3:GetObject", + "s3:DeleteObject", + "s3:PutObject", + "s3:AbortMultipartUpload", + "s3:ListMultipartUploadParts", + }, + Effect: "Allow", + Resource: fmt.Sprintf("arn:aws:s3:::%s/*", veleroBackupsS3BucketName), + }, + { + Action: []string{ + "s3:ListBucket", + }, + Effect: "Allow", + Resource: fmt.Sprintf("arn:aws:s3:::%s", veleroBackupsS3BucketName), + }, + }, + } +} diff --git a/pkg/cloud/aws/services/iam.go b/pkg/cloud/aws/services/iam.go new file mode 100644 index 0000000..f9bb5e1 --- /dev/null +++ b/pkg/cloud/aws/services/iam.go @@ -0,0 +1,54 @@ +package services + +import ( + "context" + "encoding/json" + "log/slog" + "strings" + + "github.com/Obmondo/kubeaid-bootstrap-script/utils/assert" + "github.com/Obmondo/kubeaid-bootstrap-script/utils/logger" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/iam" +) + +type ( + // PolicyDocument defines a policy document as a Go struct that can be serialized + // to JSON. + PolicyDocument struct { + Version string + Statement []PolicyStatement + } + + // PolicyStatement defines a statement in a policy document. + PolicyStatement struct { + Effect string + Action []string + Principal map[string]string `json:",omitempty"` + Resource string `json:",omitempty"` + } +) + +// CreateIAMPolicy creates an IAM policy. +func CreateIAMPolicy(ctx context.Context, iamClient *iam.Client, name string, document PolicyDocument) { + ctx = logger.AppendSlogAttributesToCtx(ctx, []slog.Attr{ + slog.String("iam-policy", name), + }) + + documentAsBytes, err := json.Marshal(document) + assert.AssertErrNil(ctx, err, "Failed JSON marshalling IAM Policy document") + + _, err = iamClient.CreatePolicy(ctx, &iam.CreatePolicyInput{ + PolicyName: aws.String(name), + PolicyDocument: aws.String(string(documentAsBytes)), + }) + switch { + // IAM Policy already exists. + case strings.Contains(err.Error(), "already exists"): + slog.InfoContext(ctx, "IAM Policy already exists") + + default: + assert.AssertErrNil(ctx, err, "Failed creating IAM Policy") + slog.InfoContext(ctx, "Created IAM Policy") + } +} diff --git a/pkg/cloud/aws/services/s3.go b/pkg/cloud/aws/services/s3.go new file mode 100644 index 0000000..65dc072 --- /dev/null +++ b/pkg/cloud/aws/services/s3.go @@ -0,0 +1,127 @@ +package services + +import ( + "compress/gzip" + "context" + "io" + "os" + "path/filepath" + "strings" + "time" + + "github.com/Obmondo/kubeaid-bootstrap-script/config" + "github.com/Obmondo/kubeaid-bootstrap-script/utils" + "github.com/Obmondo/kubeaid-bootstrap-script/utils/assert" + "github.com/Obmondo/kubeaid-bootstrap-script/utils/logger" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/s3" + s3Types "github.com/aws/aws-sdk-go-v2/service/s3/types" + "github.com/sagikazarmark/slog-shim" +) + +// Creates S3 Bucket. +func CreateS3Bucket(ctx context.Context, s3Client *s3.Client, name string) { + ctx = logger.AppendSlogAttributesToCtx(ctx, []slog.Attr{ + slog.String("s3Bucket", name), + }) + + createBucketInput := &s3.CreateBucketInput{ + Bucket: aws.String(name), + } + if config.ParsedConfig.Cloud.AWS.Region != "us-east-1" { + createBucketInput.CreateBucketConfiguration = &s3Types.CreateBucketConfiguration{ + LocationConstraint: s3Types.BucketLocationConstraint(config.ParsedConfig.Cloud.AWS.Region), + } + } + _, err := s3Client.CreateBucket(ctx, createBucketInput) + switch err.(type) { + // S3 Bucket already exists and is owned by the user. + case *s3Types.BucketAlreadyOwnedByYou: + slog.WarnContext(ctx, "S3 bucket already exists and is owned by you") + + default: + assert.AssertErrNil(ctx, err, "Failed creating S3 bucket") + + // Wait fo the S3 bucket to be created. + err = s3.NewBucketExistsWaiter(s3Client). + Wait(ctx, &s3.HeadBucketInput{Bucket: aws.String(name)}, time.Minute) + assert.AssertErrNil(ctx, err, "Failed waiting for S3 bucket to be created") + slog.InfoContext(ctx, "Created S3 bucket") + } +} + +// Downloads the contents of the given S3 bucket locally. +// If the contents are gZip encoded, then you can choose to gZip decode them after download. +func DownloadS3BucketContents(ctx context.Context, s3Client *s3.Client, bucketName string, gzipDecode bool) { + ctx = logger.AppendSlogAttributesToCtx(ctx, []slog.Attr{ + slog.String("s3-bucket", bucketName), + }) + + slog.InfoContext(ctx, "Downloading contents of S3 bucket") + + // Create directory where S3 objects will be downloaded. + downloadDir := utils.GetDirPathForDownloadedStorageBucketContents(bucketName) + err := os.MkdirAll(downloadDir, os.ModePerm) + assert.AssertErrNil(ctx, err, "Failed creating directory", slog.String("path", downloadDir)) + + listObjectsInput := s3.ListObjectsV2Input{ + Bucket: aws.String(bucketName), + } + for { + listObjectsOutput, err := s3Client.ListObjectsV2(ctx, &listObjectsInput) + assert.AssertErrNil(ctx, err, "Failed listing objects in S3 bucket") + + // Iterate through the S3 objects and download content of each object. + for _, object := range listObjectsOutput.Contents { + downloadS3Object(ctx, s3Client, &bucketName, &downloadDir, object.Key, gzipDecode) + } + + if !*listObjectsOutput.IsTruncated { + break + } + listObjectsInput.ContinuationToken = listObjectsOutput.ContinuationToken + } +} + +// Downloads the content of the given S3 object locally. +func downloadS3Object(ctx context.Context, s3Client *s3.Client, bucketName, downloadDir, objectKey *string, gzipDecode bool) { + ctx = logger.AppendSlogAttributesToCtx(ctx, []slog.Attr{ + slog.String("object", *objectKey), + }) + + filePath := filepath.Join(*downloadDir, *objectKey) + + // Create intermediate directories (if required). + if strings.Contains(*objectKey, "/") { + err := os.MkdirAll(filePath, os.ModePerm) + assert.AssertErrNil(ctx, err, "Failed creating directory", slog.String("path", *downloadDir)) + } + + // Create the file where the contents of the given S3 object will be stored. + destinationFile, err := os.OpenFile(filePath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644) + assert.AssertErrNil(ctx, err, "Failed opening file", slog.String("path", filePath)) + defer destinationFile.Close() + + // Fetch the S3 object. + getObjectOutput, err := s3Client.GetObject(ctx, &s3.GetObjectInput{ + Bucket: bucketName, + Key: objectKey, + }) + assert.AssertErrNil(ctx, err, "Failed getting S3 Object") + defer getObjectOutput.Body.Close() + + s3ObjectContentReader := getObjectOutput.Body + + // gZip decode S3 object content (if required). + if gzipDecode { + gzipReader, err := gzip.NewReader(getObjectOutput.Body) + assert.AssertErrNil(ctx, err, "Failed creating a gZip reader") + defer gzipReader.Close() + + s3ObjectContentReader = gzipReader + } + + // Copy contents of the fetched S3 object to the file. + _, err = io.Copy(destinationFile, s3ObjectContentReader) + assert.AssertErrNil(ctx, err, "Failed writing S3 Object contents to file") +} diff --git a/pkg/cloud/aws/setup_disaster_recovery.go b/pkg/cloud/aws/setup_disaster_recovery.go new file mode 100644 index 0000000..6ce8e74 --- /dev/null +++ b/pkg/cloud/aws/setup_disaster_recovery.go @@ -0,0 +1,51 @@ +package aws + +import ( + "context" + "fmt" + + "github.com/Obmondo/kubeaid-bootstrap-script/config" + "github.com/Obmondo/kubeaid-bootstrap-script/pkg/cloud/aws/services" + "github.com/Obmondo/kubeaid-bootstrap-script/utils" + argoCDV1Alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" + "github.com/sagikazarmark/slog-shim" +) + +// Sets up the provisioned cluster for Disaster Recovery. +// NOTE : Picks up AWS credentials from the environment. +func (a *AWS) SetupDisasterRecovery(ctx context.Context) { + if config.ParsedConfig.Cloud.AWS.DisasterRecovery == nil { + return + } + + slog.InfoContext(ctx, "Setting up Disaster Recovery") + + // Create S3 bucket where Sealed Secrets will be backed up. + sealedSecretBackupsS3BucketName := config.ParsedConfig.Cloud.AWS.DisasterRecovery.SealedSecretsBackupS3BucketName + services.CreateS3Bucket(ctx, a.s3Client, sealedSecretBackupsS3BucketName) + // + // Create S3 bucket where Kubernetes Objects will be backed up (by Velero). + veleroBackupsS3BucketName := config.ParsedConfig.Cloud.AWS.DisasterRecovery.VeleroBackupsS3BucketName + services.CreateS3Bucket(ctx, a.s3Client, veleroBackupsS3BucketName) + + clusterName := config.ParsedConfig.Cluster.Name + + // Create IAM Policy for Sealed Secrets Backuper. + sealedSecretsBackuperIAMPolicyName := fmt.Sprintf("sealed-secrets-backuper-%s", clusterName) + services.CreateIAMPolicy(ctx, a.iamClient, sealedSecretsBackuperIAMPolicyName, getSealedSecretsBackuperIAMPolicy()) + // + // Create IAM Policy for Velero. + veleroIAMPolicyName := fmt.Sprintf("velero-%s", clusterName) + services.CreateIAMPolicy(ctx, a.iamClient, veleroIAMPolicyName, getVeleroIAMPolicy()) + + // Sync Kube2IAM, K8sConfigs, Velero and SealedSecrets ArgoCD Apps. + argocdAppsToBeSynced := []string{ + "kube2iam", + "k8s-configs", + "velero", + "sealed-secrets", + } + for _, argoCDApp := range argocdAppsToBeSynced { + utils.SyncArgoCDApp(ctx, argoCDApp, []*argoCDV1Alpha1.SyncOperationResource{}) + } +} diff --git a/pkg/cloud/aws/utils.go b/pkg/cloud/aws/utils.go new file mode 100644 index 0000000..47c21dd --- /dev/null +++ b/pkg/cloud/aws/utils.go @@ -0,0 +1,40 @@ +package aws + +import ( + "context" + "log/slog" + "strings" + + "github.com/Obmondo/kubeaid-bootstrap-script/utils" + "github.com/Obmondo/kubeaid-bootstrap-script/utils/assert" + awsSDKGoV2Config "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/service/sts" +) + +// Returns the AWS Account ID. +// NOTE : Picks up AWS credentials from the environment. +func GetAccountID(ctx context.Context) string { + // Load AWS SDK config. + awsSDKConfig, err := awsSDKGoV2Config.LoadDefaultConfig(ctx) + assert.AssertErrNil(ctx, err, "Failed initiating AWS SDK config") + + stsClient := sts.NewFromConfig(awsSDKConfig) + output, err := stsClient.GetCallerIdentity(ctx, &sts.GetCallerIdentityInput{}) + assert.AssertErrNil(ctx, err, "Failed getting AWS account ID") + + return *output.Account +} + +// Creates / updates the AWS CloudFormation Stack containing necessary IAM role-policies, required +// by ClusterAPI and the EC2 instance of the provisioned cluster. +func CreateIAMCloudFormationStack() { + // The clusterawsadm CLI utility picks up the credentials that you set as environment variables + // and uses them to create the CloudFormation stack. + // NOTE : This requires admin privileges. + output, err := utils.ExecuteCommand("clusterawsadm bootstrap iam create-cloudformation-stack") + + // Panic if an error occurs (except regarding the AWS Cloudformation stack already existing). + if !strings.Contains(output, "already exists, updating") { + assert.AssertErrNil(context.Background(), err, "Failed bootstrapping IAM CloudFormation Stack", slog.String("output", output)) + } +} diff --git a/pkg/cloud/cloud_provider.go b/pkg/cloud/cloud_provider.go new file mode 100644 index 0000000..fbf754d --- /dev/null +++ b/pkg/cloud/cloud_provider.go @@ -0,0 +1,9 @@ +package cloud + +import "context" + +type CloudProvider interface { + SetupDisasterRecovery(ctx context.Context) + + GetSealedSecretsBackupBucketName() string +} diff --git a/pkg/cloud/hetzner/hetzner.go b/pkg/cloud/hetzner/hetzner.go new file mode 100644 index 0000000..d629489 --- /dev/null +++ b/pkg/cloud/hetzner/hetzner.go @@ -0,0 +1,21 @@ +package hetzner + +import "context" + +type Hetzner struct{} + +func NewHetznerCloudProvider() *Hetzner { + return &Hetzner{} +} + +func (*Hetzner) SetupDisasterRecovery(ctx context.Context) { + panic("unimplemented") +} + +func (*Hetzner) GetSealedSecretsBackupBucketName() string { + panic("unreachable") +} + +func (*Hetzner) GetLatestBackupName(ctx context.Context) string { + panic("unreachable") +} diff --git a/pkg/core/bootstrap_cluster.go b/pkg/core/bootstrap_cluster.go new file mode 100644 index 0000000..7b86adf --- /dev/null +++ b/pkg/core/bootstrap_cluster.go @@ -0,0 +1,116 @@ +package core + +import ( + "context" + "fmt" + "log/slog" + "os" + + "github.com/Obmondo/kubeaid-bootstrap-script/config" + "github.com/Obmondo/kubeaid-bootstrap-script/constants" + "github.com/Obmondo/kubeaid-bootstrap-script/pkg/cloud" + "github.com/Obmondo/kubeaid-bootstrap-script/pkg/cloud/aws" + "github.com/Obmondo/kubeaid-bootstrap-script/utils" + argoCDV1Alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" + "github.com/go-git/go-git/v5/plumbing/transport" +) + +func BootstrapCluster(ctx context.Context, skipKubeAidConfigSetup, skipClusterctlMove bool, cloudProvider cloud.CloudProvider, isPartOfDisasterRecovery bool) { + // Detect git authentication method. + gitAuthMethod := utils.GetGitAuthMethod(ctx) + + // Any cloud specific tasks. + switch { + case config.ParsedConfig.Cloud.AWS != nil: + aws.SetAWSSpecificEnvs() + aws.CreateIAMCloudFormationStack() + } + + os.Setenv(constants.EnvNameKubeconfig, constants.OutputPathManagementClusterKubeconfig) + + // Provision the main cluster + provisionMainCluster(ctx, gitAuthMethod, skipKubeAidConfigSetup) + + // Let the provisioned cluster manage itself. + dogfoodProvisionedCluster(ctx, gitAuthMethod, skipClusterctlMove, cloudProvider, isPartOfDisasterRecovery) + + // If the diasterRecovery section is specified in the cloud-provider specific config, then + // setup Disaster Recovery. + cloudProvider.SetupDisasterRecovery(ctx) +} + +func provisionMainCluster(ctx context.Context, gitAuthMethod transport.AuthMethod, skipKubeAidConfigSetup bool) { + // Create the management cluster (using K3d), if it doesn't already exist. + utils.CreateK3DCluster(ctx, "management-cluster") + managementClusterClient := utils.CreateKubernetesClient(ctx, constants.OutputPathManagementClusterKubeconfig) + + // Install Sealed Secrets. + utils.InstallSealedSecrets(ctx) + + // Setup cluster directory in the user's KubeAid config repo. + if !skipKubeAidConfigSetup { + SetupKubeAidConfig(ctx, gitAuthMethod, false) + } + + // Setup the management cluster. + SetupCluster(ctx, managementClusterClient) + + // Sync the complete capi-cluster ArgoCD App. + // TODO : Make it compatible with the retry feature, when `clusterctl move` is already performed. + utils.SyncArgoCDApp(ctx, constants.ArgoCDAppCapiCluster, []*argoCDV1Alpha1.SyncOperationResource{}) + + // Close ArgoCD application client. + constants.ArgoCDApplicationClientCloser.Close() + + // Wait for the main cluster to be provisioned and ready. + utils.WaitForMainClusterToBeProvisioned(ctx, managementClusterClient) + + // Save kubeconfig locally. + utils.SaveKubeconfig(ctx, managementClusterClient) + + slog.Info("Cluster has been provisioned successfully 🎉🎉 !", slog.String("kubeconfig", constants.OutputPathProvisionedClusterKubeconfig)) +} + +func dogfoodProvisionedCluster(ctx context.Context, gitAuthMethod transport.AuthMethod, skipClusterctlMove bool, cloudProvider cloud.CloudProvider, isPartOfDisasterRecovery bool) { + // Update the KUBECONFIG environment variable's value to the provisioned cluster's kubeconfig. + os.Setenv("KUBECONFIG", constants.OutputPathProvisionedClusterKubeconfig) + provisionedClusterClient := utils.CreateKubernetesClient(ctx, constants.OutputPathProvisionedClusterKubeconfig) + + // Wait for atleast 1 worker node to be initialized, so that we can deploy our application + // workloads. + utils.WaitForMainClusterToBeReady(ctx, provisionedClusterClient) + + if isPartOfDisasterRecovery { + // If this is a part of the disaster recovery process, then + // restore Kubernetes Secrets containing a Sealed Secrets keys. + + sealedSecretsBackupBucketName := cloudProvider.GetSealedSecretsBackupBucketName() + manifestsDirPath := utils.GetDirPathForDownloadedStorageBucketContents(sealedSecretsBackupBucketName) + + utils.ExecuteCommandOrDie(fmt.Sprintf("kubectl apply -f %s", manifestsDirPath)) + } + + // Install Sealed Secrets. + utils.InstallSealedSecrets(ctx) + + // We need to update the Sealed Secrets in the kubeaid-config fork. + // Those represent Kubernetes Secrets encyrpted using the private key of the Sealed Secrets + // controller installed in the K3d management cluster. + // We need to update them, by encrypting the underlying Kubernetes Secrets using the private + // key of the Sealed Secrets controller installed in the provisioned main cluster. + SetupKubeAidConfig(ctx, gitAuthMethod, true) + + // Setup the provisioned cluster. + SetupCluster(ctx, provisionedClusterClient) + + if !skipClusterctlMove { + // Move ClusterAPI manifests to the provisioned cluster. + utils.ExecuteCommandOrDie(fmt.Sprintf( + "clusterctl move --kubeconfig %s --namespace %s --to-kubeconfig %s", + constants.OutputPathManagementClusterKubeconfig, utils.GetCapiClusterNamespace(), constants.OutputPathProvisionedClusterKubeconfig, + )) + + // Sync cluster-autoscaler ArgoCD App. + utils.SyncArgoCDApp(ctx, constants.ArgoCDAppClusterAutoscaler, []*argoCDV1Alpha1.SyncOperationResource{}) + } +} diff --git a/pkg/core/delete_cluster.go b/pkg/core/delete_cluster.go new file mode 100644 index 0000000..4113a26 --- /dev/null +++ b/pkg/core/delete_cluster.go @@ -0,0 +1,88 @@ +package core + +import ( + "context" + "fmt" + "log/slog" + "time" + + "github.com/Obmondo/kubeaid-bootstrap-script/config" + "github.com/Obmondo/kubeaid-bootstrap-script/constants" + "github.com/Obmondo/kubeaid-bootstrap-script/utils" + "github.com/Obmondo/kubeaid-bootstrap-script/utils/assert" + "github.com/avast/retry-go/v4" + "k8s.io/apimachinery/pkg/api/errors" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/wait" + clusterAPIV1Beta1 "sigs.k8s.io/cluster-api/api/v1beta1" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +func DeleteCluster(ctx context.Context) { + cluster := &clusterAPIV1Beta1.Cluster{ + ObjectMeta: v1.ObjectMeta{ + Name: config.ParsedConfig.Cluster.Name, + Namespace: utils.GetCapiClusterNamespace(), + }, + } + + { + provisionedClusterClient := utils.CreateKubernetesClient(ctx, constants.OutputPathProvisionedClusterKubeconfig) + + // Try to find the Cluster resource in the provisioned cluster. + err := utils.GetClusterResource(ctx, provisionedClusterClient, cluster) + + // The Cluster resource exists in the provisioned cluster. + // The means, the 'clusterctl move' command has been executed. + if err == nil { + slog.InfoContext(ctx, "Detected that the 'clusterctl move' command has been executed") + + // Move back the ClusterAPI manifests back from the provisioned cluster to the management + // cluster. + // NOTE : We need to retry, since we can get 'failed to call webhook' error sometimes. + retry.Do(func() error { + _, err := utils.ExecuteCommand(fmt.Sprintf( + "clusterctl move --kubeconfig %s --to-kubeconfig %s -n %s", + constants.OutputPathProvisionedClusterKubeconfig, constants.OutputPathManagementClusterKubeconfig, utils.GetCapiClusterNamespace(), + )) + return err + }) + } + } + + managementClusterClient := utils.CreateKubernetesClient(ctx, constants.OutputPathManagementClusterKubeconfig) + + // Get the Cluster resource from the management cluster. + err := utils.GetClusterResource(ctx, managementClusterClient, cluster) + assert.AssertErrNil(ctx, err, "Cluster resource was suppossed to be present in the management cluster") + + // If the cluster gets marked as paused, then unmark it first. + if cluster.Spec.Paused { + err := managementClusterClient.Update(ctx, cluster) + assert.AssertErrNil(ctx, err, "Failed unmarking paused cluster") + } + + // Delete the Cluster resource from the management cluster. + // This will cause the actual provisioned cluster to be deleted. + + clusterDeletionTimeout := 10 * time.Minute.Milliseconds() // (10 minutes) + err = managementClusterClient.Delete(ctx, cluster, &client.DeleteOptions{ + GracePeriodSeconds: &clusterDeletionTimeout, + }) + assert.AssertErrNil(ctx, err, "Failed deleting cluster") + + // Wait for the infrastructure to be destroyed. + wait.PollUntilContextCancel(ctx, 2*time.Minute, false, func(ctx context.Context) (bool, error) { + slog.InfoContext(ctx, "Waiting for cluster infrastructure to be destroyed") + + err := managementClusterClient.Get(ctx, types.NamespacedName{ + Name: cluster.Name, + Namespace: cluster.Namespace, + }, cluster) + isInfrastructureDeleted := errors.IsNotFound(err) + return isInfrastructureDeleted, nil + }) + + slog.InfoContext(ctx, "Deleted cluster successully") +} diff --git a/pkg/core/recover_cluster.go b/pkg/core/recover_cluster.go new file mode 100644 index 0000000..e08bfdd --- /dev/null +++ b/pkg/core/recover_cluster.go @@ -0,0 +1,50 @@ +package core + +import ( + "context" + + "github.com/Obmondo/kubeaid-bootstrap-script/config" + "github.com/Obmondo/kubeaid-bootstrap-script/pkg/cloud" + "github.com/Obmondo/kubeaid-bootstrap-script/pkg/cloud/aws/services" + "github.com/Obmondo/kubeaid-bootstrap-script/utils/assert" + awsSDKGoV2Config "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/service/s3" +) + +func RecoverCluster(ctx context.Context, cloudProvider cloud.CloudProvider) { + switch { + case config.ParsedConfig.Cloud.Hetzner != nil: + assert.AssertNil(ctx, config.ParsedConfig.Cloud.Hetzner, "Disaster recovery isn't supported for Hetzner") + + case config.ParsedConfig.Cloud.AWS != nil: + assert.AssertNotNil(ctx, config.ParsedConfig.Cloud.AWS.DisasterRecovery, "disasterRecovery section in the config file, can't be empty") + } + + // Load AWS SDK config. + awsSDKConfig, err := awsSDKGoV2Config.LoadDefaultConfig(ctx) + assert.AssertErrNil(ctx, err, "Failed initiating AWS SDK config") + + s3Client := s3.NewFromConfig(awsSDKConfig) + + /* + Pull and gzip decode backed up (by Sealed Secrets backuper CRONJob) Kubernetes Secrets from S3 + bucket. Each Kubernetes Secret contains a Sealed Secrets encryption key. + + The script uresponsible for this backup process can be found here : + https://github.com/Obmondo/kubeaid/blob/master/argocd-helm-charts/sealed-secrets/templates/configmap.yaml + + And you can read about Sealed Secrets key rotation from these references : + (1) https://playbook.stakater.com/content/workshop/sealed-secrets/management.html. + (2) https://github.com/bitnami-labs/sealed-secrets?tab=readme-ov-file#secret-rotation + */ + services.DownloadS3BucketContents(ctx, s3Client, config.ParsedConfig.Cloud.AWS.DisasterRecovery.SealedSecretsBackupS3BucketName, true) + + // Bootstrap the new cluster. + BootstrapCluster(ctx, true, false, cloudProvider, true) + + panic("unimplemented") + + // Identify the latest Velero Backup. + + // Restore the latest Velero Backup. +} diff --git a/pkg/core/setup_cluster.go b/pkg/core/setup_cluster.go new file mode 100644 index 0000000..b82fcdf --- /dev/null +++ b/pkg/core/setup_cluster.go @@ -0,0 +1,99 @@ +package core + +import ( + "context" + "log/slog" + "time" + + "github.com/Obmondo/kubeaid-bootstrap-script/config" + "github.com/Obmondo/kubeaid-bootstrap-script/constants" + "github.com/Obmondo/kubeaid-bootstrap-script/utils" + "github.com/Obmondo/kubeaid-bootstrap-script/utils/assert" + "github.com/Obmondo/kubeaid-bootstrap-script/utils/logger" + argoCDV1Alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" + coreV1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/util/wait" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// NOTE : Sealed Secrets must already be installed in the cluster. +func SetupCluster(ctx context.Context, kubeClient client.Client) { + slog.InfoContext(ctx, "Setting up cluster") + + // Install and setup ArgoCD. + utils.InstallAndSetupArgoCD(ctx, utils.GetClusterDir(), kubeClient) + + // Create the capi-cluster / capi-cluster- namespace, where the 'cloud-credentials' + // Kubernetes Secret will exist. + utils.CreateNamespace(ctx, utils.GetCapiClusterNamespace(), kubeClient) + + // Sync the Root, CertManager, Secrets and ClusterAPI ArgoCD Apps one by one. + argocdAppsToBeSynced := []string{ + "root", + "cert-manager", + "secrets", + "cluster-api", + } + for _, argoCDApp := range argocdAppsToBeSynced { + utils.SyncArgoCDApp(ctx, argoCDApp, []*argoCDV1Alpha1.SyncOperationResource{}) + } + + // Sync the Infrastructure Provider component of the capi-cluster ArgoCD App. + // TODO : Use ArgoCD sync waves so that we don't need to explicitly sync the Infrastructure + // Provider component first. + syncInfrastructureProvider(ctx, kubeClient) +} + +// Syncs the Infrastructure Provider component of the CAPI Cluster ArgoCD App and waits for the +// infrastructure specific CRDs to be installed and pod to be running. +func syncInfrastructureProvider(ctx context.Context, kubeClient client.Client) { + // Determine the name of the Infrastructure Provider component. + + // Sync the Infrastructure Provider component. + utils.SyncArgoCDApp(ctx, constants.ArgoCDAppCapiCluster, []*argoCDV1Alpha1.SyncOperationResource{{ + Group: "operator.cluster.x-k8s.io", + Kind: "InfrastructureProvider", + Name: getInfrastructureProviderName(), + }}) + + capiClusterNamespace := utils.GetCapiClusterNamespace() + + // Wait for the infrastructure specific CRDs to be installed and infrastructure provider component + // pod to be running. + + ctx = logger.AppendSlogAttributesToCtx(ctx, []slog.Attr{ + slog.String("namespace", capiClusterNamespace), + }) + + wait.PollUntilContextCancel(ctx, time.Minute, false, func(ctx context.Context) (bool, error) { + podList := &coreV1.PodList{} + err := kubeClient.List(ctx, podList, &client.ListOptions{ + Namespace: capiClusterNamespace, + }) + assert.AssertErrNil(ctx, err, "Failed listing pods") + + if (len(podList.Items) > 0) && (podList.Items[0].Status.Phase == coreV1.PodRunning) { + return true, nil + } + + slog.InfoContext(ctx, "Waiting for the infrastructure provider component pod to come up") + return false, nil + }) +} + +// Returns the name of the InfrastructureProvider component. +func getInfrastructureProviderName() (infrastructureProviderName string) { + switch { + case config.ParsedConfig.Cloud.AWS != nil: + infrastructureProviderName = "aws" + + case config.ParsedConfig.Cloud.Hetzner != nil: + infrastructureProviderName = "hetzner" + } + + if len(config.ParsedConfig.CustomerID) > 0 { + infrastructureProviderName = infrastructureProviderName + "-" + config.ParsedConfig.CustomerID + } + + return +} diff --git a/pkg/core/setup_kubeaid_config.go b/pkg/core/setup_kubeaid_config.go new file mode 100644 index 0000000..5721a15 --- /dev/null +++ b/pkg/core/setup_kubeaid_config.go @@ -0,0 +1,157 @@ +package core + +import ( + "context" + "fmt" + "log/slog" + "os" + "path" + "strings" + "time" + + "github.com/Obmondo/kubeaid-bootstrap-script/config" + "github.com/Obmondo/kubeaid-bootstrap-script/constants" + "github.com/Obmondo/kubeaid-bootstrap-script/utils" + "github.com/Obmondo/kubeaid-bootstrap-script/utils/assert" + "github.com/Obmondo/kubeaid-bootstrap-script/utils/templates" + "github.com/go-git/go-git/v5" + "github.com/go-git/go-git/v5/plumbing/transport" +) + +/* +Does the following : + + (1) Creates / updates all necessary files for the given cluster, in the user's KubeAid config + repository. + + (2) Commits and pushes those changes to the upstream. + + (3) Waits for those changes to get merged into the default branch. +*/ +func SetupKubeAidConfig(ctx context.Context, gitAuthMethod transport.AuthMethod, onlyUpdateSealedSecrets bool) { + slog.InfoContext(ctx, "Setting up KubeAid config repo") + + // Clone the KubeAid config fork locally (if not already cloned). + repoDir := path.Join(constants.TempDir, "kubeaid-config") + repo := utils.GitCloneRepo(ctx, config.ParsedConfig.Forks.KubeaidConfigForkURL, repoDir, gitAuthMethod) + + workTree, err := repo.Worktree() + assert.AssertErrNil(ctx, err, "Failed getting worktree") + + // Remove any unstaged changes, by hard resetting to the latest commit. + // Otherwise, we'll get error when checking out to a new branch. + + headRef, err := repo.Head() + assert.AssertErrNil(ctx, err, "Failed getting head ref") + + err = workTree.Reset(&git.ResetOptions{ + Commit: headRef.Hash(), + Mode: git.HardReset, + }) + assert.AssertErrNil(ctx, err, "Failed hard resetting to latest commit") + + // Create and checkout to a new branch. + newBranchName := fmt.Sprintf("kubeaid-%s-%d", config.ParsedConfig.Cluster.Name, time.Now().Unix()) + utils.CreateAndCheckoutToBranch(repo, newBranchName, workTree) + + clusterDir := utils.GetClusterDir() + + if !onlyUpdateSealedSecrets { + // Create / update non Secret files. + createOrUpdateKubeAidConfigFiles(ctx, clusterDir, gitAuthMethod) + } + // Create / update Secret files. + CreateOrUpdateSealedSecretFiles(ctx, clusterDir) + + // Add, commit and push the changes. + commitMessage := fmt.Sprintf("(cluster/%s) : created / updated KubeAid config files", config.ParsedConfig.Cluster.Name) + commitHash := utils.AddCommitAndPushChanges(ctx, repo, workTree, newBranchName, gitAuthMethod, config.ParsedConfig.Cluster.Name, commitMessage) + + // The user now needs to go ahead and create a PR from the new to the default branch. Then he + // needs to merge that branch. + // We can't create the PR for the user, since PRs are not part of the core git lib. They are + // specific to the git platform the user is on. + + // Wait until the PR gets merged. + defaultBranchName := utils.GetDefaultBranchName(ctx, repo) + utils.WaitUntilPRMerged(ctx, repo, defaultBranchName, commitHash, gitAuthMethod, newBranchName) +} + +// Creates / updates all necessary files for the given cluster, in the user's KubeAid config +// repository. +func createOrUpdateKubeAidConfigFiles(ctx context.Context, clusterDir string, gitAuthMethod transport.AuthMethod) { + // Get non Secret templates. + embeddedTemplateNames := getEmbeddedNonSecretTemplateNames() + templateValues := getTemplateValues() + // + // Create a file from each template. + for _, embeddedTemplateName := range embeddedTemplateNames { + destinationFilePath := path.Join(clusterDir, strings.TrimSuffix(embeddedTemplateName, ".tmpl")) + createFileFromTemplate(ctx, destinationFilePath, embeddedTemplateName, templateValues) + } + + // Build KubePrometheus. + buildKubePrometheus(ctx, clusterDir, gitAuthMethod, templateValues) +} + +// Creates / updates all necessary Sealed Secrets files for the given cluster, in the user's KubeAid +// config repository. +func CreateOrUpdateSealedSecretFiles(ctx context.Context, clusterDir string) { + // Get Secret templates. + embeddedTemplateNames := getEmbeddedSecretTemplateNames() + templateValues := getTemplateValues() + + // Create a file from each template. + for _, embeddedTemplateName := range embeddedTemplateNames { + destinationFilePath := path.Join(clusterDir, strings.TrimSuffix(embeddedTemplateName, ".tmpl")) + createFileFromTemplate(ctx, destinationFilePath, embeddedTemplateName, templateValues) + + // Encrypt the Secret to a Sealed Secret. + utils.GenerateSealedSecret(ctx, destinationFilePath) + } +} + +// Creates file from the given template. +func createFileFromTemplate(ctx context.Context, destinationFilePath, embeddedTemplateName string, templateValues *TemplateValues) { + utils.CreateIntermediateDirsForFile(ctx, destinationFilePath) + + // Open the destination file. + destinationFile, err := os.OpenFile(destinationFilePath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644) + assert.AssertErrNil(ctx, err, "Failed opening file", slog.String("path", destinationFilePath)) + defer destinationFile.Close() + + // Execute the corresponding template with the template values. Then write the execution result + // to that file. + content := templates.ParseAndExecuteTemplate(ctx, &KubeaidConfigFileTemplates, path.Join("templates/", embeddedTemplateName), templateValues) + destinationFile.Write(content) + + slog.Info("Created file in KubeAid config fork", slog.String("file-path", destinationFilePath)) +} + +// Creates the jsonnet vars file for the cluster. +// Then executes KubeAid's kube-prometheus build script. +func buildKubePrometheus(ctx context.Context, clusterDir string, gitAuthMethod transport.AuthMethod, templateValues *TemplateValues) { + // Create the jsonnet vars file. + jsonnetVarsFilePath := fmt.Sprintf("%s/%s-vars.jsonnet", clusterDir, config.ParsedConfig.Cluster.Name) + createFileFromTemplate(ctx, jsonnetVarsFilePath, constants.TemplateNameJsonnet, templateValues) + + // Create the kube-prometheus folder. + kubePrometheusDir := fmt.Sprintf("%s/kube-prometheus", clusterDir) + err := os.MkdirAll(kubePrometheusDir, os.ModePerm) + assert.AssertErrNil(ctx, err, "Failed creating intermediate paths", slog.String("path", kubePrometheusDir)) + + // If we're going to use the original KubeAid repo (https://github.com/Obmondo/KubeAid), then we + // don't need any Git authentication method + if config.ParsedConfig.Forks.KubeaidForkURL == constants.RepoURLObmondoKubeAid { + gitAuthMethod = nil + } + + // Clone the KubeAid fork locally (if not already cloned). + kubeaidForkDir := constants.TempDir + "/kubeaid" + utils.GitCloneRepo(ctx, config.ParsedConfig.Forks.KubeaidForkURL, kubeaidForkDir, gitAuthMethod) + + // Run the KubePrometheus build script. + slog.Info("Running KubePrometheus build script...") + kubePrometheusBuildScriptPath := fmt.Sprintf("%s/build/kube-prometheus/build.sh", kubeaidForkDir) + utils.ExecuteCommandOrDie(fmt.Sprintf("%s %s", kubePrometheusBuildScriptPath, clusterDir)) +} diff --git a/pkg/core/templates.go b/pkg/core/templates.go new file mode 100644 index 0000000..f988924 --- /dev/null +++ b/pkg/core/templates.go @@ -0,0 +1,97 @@ +package core + +import ( + "context" + "embed" + "os" + + "github.com/Obmondo/kubeaid-bootstrap-script/config" + "github.com/Obmondo/kubeaid-bootstrap-script/constants" + "github.com/Obmondo/kubeaid-bootstrap-script/pkg/cloud/aws" + "github.com/Obmondo/kubeaid-bootstrap-script/utils" +) + +//go:embed templates/* +var KubeaidConfigFileTemplates embed.FS + +type TemplateValues struct { + CustomerID, + GitUsername, + GitPassword string + config.ClusterConfig + config.ForksConfig + AWSConfig *config.AWSConfig + HetznerConfig *config.HetznerConfig + config.MonitoringConfig + CAPIClusterNamespace, + AWSB64EncodedCredentials, + AWSAccountID string +} + +func getTemplateValues() *TemplateValues { + templateValues := &TemplateValues{ + CustomerID: config.ParsedConfig.CustomerID, + GitUsername: config.ParsedConfig.Git.Username, + GitPassword: config.ParsedConfig.Git.Password, + ClusterConfig: config.ParsedConfig.Cluster, + ForksConfig: config.ParsedConfig.Forks, + AWSConfig: config.ParsedConfig.Cloud.AWS, + HetznerConfig: config.ParsedConfig.Cloud.Hetzner, + MonitoringConfig: config.ParsedConfig.Monitoring, + CAPIClusterNamespace: utils.GetCapiClusterNamespace(), + } + + // Set cloud provider specific values. + switch { + case config.ParsedConfig.Cloud.AWS != nil: + templateValues.AWSAccountID = aws.GetAccountID(context.Background()) + templateValues.AWSB64EncodedCredentials = os.Getenv(constants.EnvNameAWSB64EcodedCredentials) + } + + return templateValues +} + +// Returns the list of embedded (non Secret) template names based on the underlying cloud provider. +func getEmbeddedNonSecretTemplateNames() []string { + // Templates common for all cloud providers. + embeddedTemplateNames := constants.CommonNonSecretTemplateNames + + // Add cloud provider specific templates. + switch { + case config.ParsedConfig.Cloud.AWS != nil: + embeddedTemplateNames = append(embeddedTemplateNames, constants.AWSSpecificNonSecretTemplateNames...) + // + // Add Disaster Recovery related templates, if disasterRecovery section is specified in the + // cloud-provider specific config. + if config.ParsedConfig.Cloud.AWS.DisasterRecovery != nil { + embeddedTemplateNames = append(embeddedTemplateNames, constants.AWSDisasterRecoverySpecificTemplateNames...) + } + + case config.ParsedConfig.Cloud.Hetzner != nil: + embeddedTemplateNames = append(embeddedTemplateNames, constants.HetznerSpecificNonSecretTemplateNames...) + } + + // Add Obmondo K8s Agent related templates, if 'monitoring.connectObmondo' is set to true. + if config.ParsedConfig.Monitoring.ConnectObmondo { + embeddedTemplateNames = append(embeddedTemplateNames, + "argocd-apps/templates/obmondo-k8s-agent.app.yaml.tmpl", + "argocd-apps/obmondo-k8s-agent.values.yaml.tmpl", + ) + } + + return embeddedTemplateNames +} + +// Returns the list of embedded Secret template names based on the underlying cloud provider. +func getEmbeddedSecretTemplateNames() []string { + // Templates common for all cloud providers. + embeddedTemplateNames := constants.CommonSecretTemplateNames + + // Add cloud provider specific templates, if required. + switch { + case config.ParsedConfig.Cloud.Hetzner != nil: + embeddedTemplateNames = append(embeddedTemplateNames, constants.HetznerSpecificSecretTemplateNames...) + } + + return embeddedTemplateNames +} diff --git a/cmd/bootstrap_cluster/templates/argocd-apps/Chart.yaml b/pkg/core/templates/argocd-apps/Chart.yaml similarity index 100% rename from cmd/bootstrap_cluster/templates/argocd-apps/Chart.yaml rename to pkg/core/templates/argocd-apps/Chart.yaml diff --git a/cmd/bootstrap_cluster/templates/argocd-apps/argo-cd.values.yaml.tmpl b/pkg/core/templates/argocd-apps/argo-cd.values.yaml.tmpl similarity index 100% rename from cmd/bootstrap_cluster/templates/argocd-apps/argo-cd.values.yaml.tmpl rename to pkg/core/templates/argocd-apps/argo-cd.values.yaml.tmpl diff --git a/pkg/core/templates/argocd-apps/capi-cluster.values.yaml.tmpl b/pkg/core/templates/argocd-apps/capi-cluster.values.yaml.tmpl new file mode 100644 index 0000000..ec5b8ef --- /dev/null +++ b/pkg/core/templates/argocd-apps/capi-cluster.values.yaml.tmpl @@ -0,0 +1,63 @@ +global: + clusterName: {{ .ClusterConfig.Name }} + {{- if .CustomerID }} + customerid: {{ .CustomerID }} + {{- end }} + kubernetes: + version: {{ .K8sVersion }} + kubeaid: + repo: {{ .KubeaidForkURL }} + kubeaidConfig: + repo: {{ .KubeaidConfigForkURL }} + +{{- if .AWSConfig }} +provider: + aws: true + +aws: + secretName: cloud-credentials + sshKeyName: {{ .AWSConfig.SSHKeyName }} + region: {{ .AWSConfig.Credentials.AWSRegion }} + bastion: + enabled: {{ .AWSConfig.BastionEnabled }} + vpc: + cidrBlock: 10.14.0.0/22 + pods: + cidrBlock: 10.244.0.0/16 + controlPlane: + loadBalancer: + loadBalancerType: nlb + region: {{ .AWSConfig.Credentials.AWSRegion }} + replicas: {{ .AWSConfig.ControlPlane.Replicas }} + instanceType: {{ .AWSConfig.ControlPlane.InstanceType }} + ami: + id: {{ .AWSConfig.ControlPlane.AMI.ID }} + nodeGroups: +{{ .AWSConfig.NodeGroups | toYaml | indent 2 }} +{{- end }} + +{{- if .HetznerConfig }} +provider: + hetzner: true + +hetzner: + pods: + cidrBlock: 10.244.0.0/16 + controlPlaneEndpoint: + host: {{ .HetznerConfig.ControlPlaneEndpoint }} + port: 6443 + controlPlaneLoadBalancer: + enabled: false + controlPlaneRegions: + - hel1 + nodes: + {{- range $id, $node := .HetznerConfig.BareMetalNodes }} + {{ $id }}: + name: {{ $node.Name }} + maintenanceMode: false + wwn: + {{- range $wwn := $node.WWN }} + - {{ $wwn }} + {{- end }} + {{- end }} +{{- end }} diff --git a/cmd/bootstrap_cluster/templates/argocd-apps/ccm-aws.values.yaml.tmpl b/pkg/core/templates/argocd-apps/ccm-aws.values.yaml.tmpl similarity index 100% rename from cmd/bootstrap_cluster/templates/argocd-apps/ccm-aws.values.yaml.tmpl rename to pkg/core/templates/argocd-apps/ccm-aws.values.yaml.tmpl diff --git a/pkg/core/templates/argocd-apps/ccm-hetzner.values.yaml.tmpl b/pkg/core/templates/argocd-apps/ccm-hetzner.values.yaml.tmpl new file mode 100644 index 0000000..c0ad1c9 --- /dev/null +++ b/pkg/core/templates/argocd-apps/ccm-hetzner.values.yaml.tmpl @@ -0,0 +1,5 @@ +--- +secret: + name: cloud-credentials + privateNetwork: + enabled: false diff --git a/cmd/bootstrap_cluster/templates/argocd-apps/cert-manager.values.yaml.tmpl b/pkg/core/templates/argocd-apps/cert-manager.values.yaml.tmpl similarity index 100% rename from cmd/bootstrap_cluster/templates/argocd-apps/cert-manager.values.yaml.tmpl rename to pkg/core/templates/argocd-apps/cert-manager.values.yaml.tmpl diff --git a/cmd/bootstrap_cluster/templates/argocd-apps/cluster-api.values.yaml.tmpl b/pkg/core/templates/argocd-apps/cluster-api.values.yaml.tmpl similarity index 100% rename from cmd/bootstrap_cluster/templates/argocd-apps/cluster-api.values.yaml.tmpl rename to pkg/core/templates/argocd-apps/cluster-api.values.yaml.tmpl diff --git a/pkg/core/templates/argocd-apps/cluster-autoscaler.values.yaml.tmpl b/pkg/core/templates/argocd-apps/cluster-autoscaler.values.yaml.tmpl new file mode 100644 index 0000000..77899f7 --- /dev/null +++ b/pkg/core/templates/argocd-apps/cluster-autoscaler.values.yaml.tmpl @@ -0,0 +1,19 @@ +cluster-autoscaler: + cloudProvider: clusterapi + + # Cluster API mode : incluster-incluster / incluster-kubeconfig / kubeconfig-incluster / kubeconfig-kubeconfig / single-kubeconfig. + # Syntax: workloadClusterMode-ManagementClusterMode + # + # For 'kubeconfig-kubeconfig', 'incluster-kubeconfig' and 'single-kubeconfig' you always must + # mount the external kubeconfig using either 'extraVolumeSecrets' or 'extraMounts' and + # 'extraVolumes'. + # + # If you dont set 'clusterAPIKubeconfigSecret'and thus use an in-cluster config or want to use a + # non CAPI generated kubeconfig you must do so for the workload kubeconfig as well + # + # REFER : https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/cloudprovider/clusterapi/README.md#connecting-cluster-autoscaler-to-cluster-api-management-and-workload-clusters + clusterAPIMode: incluster-incluster + + autoDiscovery: + clusterName: {{ .ClusterConfig.Name }} + namespace: {{ .CAPIClusterNamespace }} diff --git a/pkg/core/templates/argocd-apps/hetzner-robot.values.yaml.tmpl b/pkg/core/templates/argocd-apps/hetzner-robot.values.yaml.tmpl new file mode 100644 index 0000000..77150ba --- /dev/null +++ b/pkg/core/templates/argocd-apps/hetzner-robot.values.yaml.tmpl @@ -0,0 +1,2 @@ +--- +failoverIP: {{ .HetznerConfig.ControlPlaneEndpoint }} diff --git a/pkg/core/templates/argocd-apps/kube2iam.values.yaml.tmpl b/pkg/core/templates/argocd-apps/kube2iam.values.yaml.tmpl new file mode 100644 index 0000000..c3ccc29 --- /dev/null +++ b/pkg/core/templates/argocd-apps/kube2iam.values.yaml.tmpl @@ -0,0 +1,15 @@ +kube2iam: + host: + iptables: false + interface: lxc+ + rbac: + create: true + podAnnotations: + cluster-autoscaler.kubernetes.io/safe-to-evict: "true" + tolerations: + - effect: NoSchedule + operator: Exists + - effect: NoExecute + operator: Exists + extraArgs: + namespace-restrictions: "" diff --git a/cmd/bootstrap_cluster/templates/argocd-apps/obmondo-k8s-agent.values.yaml.tmpl b/pkg/core/templates/argocd-apps/obmondo-k8s-agent.values.yaml.tmpl similarity index 100% rename from cmd/bootstrap_cluster/templates/argocd-apps/obmondo-k8s-agent.values.yaml.tmpl rename to pkg/core/templates/argocd-apps/obmondo-k8s-agent.values.yaml.tmpl diff --git a/pkg/core/templates/argocd-apps/sealed-secrets.values.yaml.tmpl b/pkg/core/templates/argocd-apps/sealed-secrets.values.yaml.tmpl new file mode 100644 index 0000000..99e0f29 --- /dev/null +++ b/pkg/core/templates/argocd-apps/sealed-secrets.values.yaml.tmpl @@ -0,0 +1,6 @@ +--- +{{- if and (.AWSConfig) (.AWSConfig.DisasterRecovery) }} +backup: + kube2iamRole: arn:aws:iam::{{ .AWSAccountID }}:role/sealed-secrets-backuper-{{ .ClusterConfig.Name }} + backupBucket: {{ .AWSConfig.DisasterRecovery.SealedSecretsBackupS3BucketName }} +{{- end }} diff --git a/cmd/bootstrap_cluster/templates/argocd-apps/templates/argo-cd.app.yaml.tmpl b/pkg/core/templates/argocd-apps/templates/argo-cd.app.yaml.tmpl similarity index 88% rename from cmd/bootstrap_cluster/templates/argocd-apps/templates/argo-cd.app.yaml.tmpl rename to pkg/core/templates/argocd-apps/templates/argo-cd.app.yaml.tmpl index 60b4115..277bbbc 100644 --- a/cmd/bootstrap_cluster/templates/argocd-apps/templates/argo-cd.app.yaml.tmpl +++ b/pkg/core/templates/argocd-apps/templates/argo-cd.app.yaml.tmpl @@ -17,7 +17,7 @@ spec: targetRevision: HEAD helm: valueFiles: - - $values/k8s/{{ .ClusterName }}/argocd-apps/argo-cd.values.yaml + - $values/k8s/{{ .Name }}/argocd-apps/argo-cd.values.yaml - repoURL: {{ .KubeaidConfigForkURL }} targetRevision: HEAD ref: values diff --git a/cmd/bootstrap_cluster/templates/argocd-apps/templates/capi-cluster.app.yaml.tmpl b/pkg/core/templates/argocd-apps/templates/capi-cluster.app.yaml.tmpl similarity index 86% rename from cmd/bootstrap_cluster/templates/argocd-apps/templates/capi-cluster.app.yaml.tmpl rename to pkg/core/templates/argocd-apps/templates/capi-cluster.app.yaml.tmpl index e7ba5a6..0d3ed42 100644 --- a/cmd/bootstrap_cluster/templates/argocd-apps/templates/capi-cluster.app.yaml.tmpl +++ b/pkg/core/templates/argocd-apps/templates/capi-cluster.app.yaml.tmpl @@ -14,7 +14,7 @@ spec: targetRevision: HEAD helm: valueFiles: - - $values/k8s/{{ .ClusterName }}/argocd-apps/capi-cluster.values.yaml + - $values/k8s/{{ .ClusterConfig.Name }}/argocd-apps/capi-cluster.values.yaml - repoURL: {{ .KubeaidConfigForkURL }} targetRevision: HEAD ref: values diff --git a/cmd/bootstrap_cluster/templates/argocd-apps/templates/ccm-aws.yaml.tmpl b/pkg/core/templates/argocd-apps/templates/ccm-aws.app.yaml.tmpl similarity index 86% rename from cmd/bootstrap_cluster/templates/argocd-apps/templates/ccm-aws.yaml.tmpl rename to pkg/core/templates/argocd-apps/templates/ccm-aws.app.yaml.tmpl index 72e5ac0..ee38071 100644 --- a/cmd/bootstrap_cluster/templates/argocd-apps/templates/ccm-aws.yaml.tmpl +++ b/pkg/core/templates/argocd-apps/templates/ccm-aws.app.yaml.tmpl @@ -15,7 +15,7 @@ spec: targetRevision: HEAD helm: valueFiles: - - $values/k8s/{{ .ClusterName }}/argocd-apps/ccm-aws.values.yaml + - $values/k8s/{{ .ClusterConfig.Name }}/argocd-apps/ccm-aws.values.yaml - repoURL: {{ .KubeaidConfigForkURL }} targetRevision: HEAD ref: values diff --git a/pkg/core/templates/argocd-apps/templates/ccm-hetzner.app.yaml.tmpl b/pkg/core/templates/argocd-apps/templates/ccm-hetzner.app.yaml.tmpl new file mode 100644 index 0000000..19697eb --- /dev/null +++ b/pkg/core/templates/argocd-apps/templates/ccm-hetzner.app.yaml.tmpl @@ -0,0 +1,26 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: ccm-hetzner + namespace: argo-cd + +spec: + destination: + namespace: kube-system + server: https://kubernetes.default.svc + project: default + sources: + - repoURL: {{ .KubeaidForkURL }} + path: argocd-helm-charts/ccm-hetzner + targetRevision: HEAD + helm: + valueFiles: + - $values/k8s/{{ .ClusterConfig.Name }}/argocd-apps/ccm-hetzner.values.yaml + - repoURL: {{ .KubeaidConfigForkURL }} + targetRevision: HEAD + ref: values + syncPolicy: + syncOptions: + - CreateNamespace=true + - ApplyOutOfSyncOnly=true + diff --git a/cmd/bootstrap_cluster/templates/argocd-apps/templates/cert-manager.app.yaml.tmpl b/pkg/core/templates/argocd-apps/templates/cert-manager.app.yaml.tmpl similarity index 86% rename from cmd/bootstrap_cluster/templates/argocd-apps/templates/cert-manager.app.yaml.tmpl rename to pkg/core/templates/argocd-apps/templates/cert-manager.app.yaml.tmpl index ddbcc25..d24245f 100644 --- a/cmd/bootstrap_cluster/templates/argocd-apps/templates/cert-manager.app.yaml.tmpl +++ b/pkg/core/templates/argocd-apps/templates/cert-manager.app.yaml.tmpl @@ -15,7 +15,7 @@ spec: targetRevision: HEAD helm: valueFiles: - - $values/k8s/{{ .ClusterName }}/argocd-apps/cert-manager.values.yaml + - $values/k8s/{{ .ClusterConfig.Name }}/argocd-apps/cert-manager.values.yaml - repoURL: {{ .KubeaidConfigForkURL }} targetRevision: HEAD ref: values diff --git a/cmd/bootstrap_cluster/templates/argocd-apps/templates/cluster-api.app.yaml.tmpl b/pkg/core/templates/argocd-apps/templates/cluster-api.app.yaml.tmpl similarity index 86% rename from cmd/bootstrap_cluster/templates/argocd-apps/templates/cluster-api.app.yaml.tmpl rename to pkg/core/templates/argocd-apps/templates/cluster-api.app.yaml.tmpl index 3efb8d9..9e577af 100644 --- a/cmd/bootstrap_cluster/templates/argocd-apps/templates/cluster-api.app.yaml.tmpl +++ b/pkg/core/templates/argocd-apps/templates/cluster-api.app.yaml.tmpl @@ -14,7 +14,7 @@ spec: targetRevision: HEAD helm: valueFiles: - - $values/k8s/{{ .ClusterName }}/argocd-apps/cluster-api.values.yaml + - $values/k8s/{{ .ClusterConfig.Name }}/argocd-apps/cluster-api.values.yaml - repoURL: {{ .KubeaidConfigForkURL }} targetRevision: HEAD ref: values diff --git a/cmd/bootstrap_cluster/templates/argocd-apps/templates/cluster-autoscaler.app.yaml.tmpl b/pkg/core/templates/argocd-apps/templates/cluster-autoscaler.app.yaml.tmpl similarity index 85% rename from cmd/bootstrap_cluster/templates/argocd-apps/templates/cluster-autoscaler.app.yaml.tmpl rename to pkg/core/templates/argocd-apps/templates/cluster-autoscaler.app.yaml.tmpl index 44a0558..ff08468 100644 --- a/cmd/bootstrap_cluster/templates/argocd-apps/templates/cluster-autoscaler.app.yaml.tmpl +++ b/pkg/core/templates/argocd-apps/templates/cluster-autoscaler.app.yaml.tmpl @@ -14,7 +14,7 @@ spec: targetRevision: HEAD helm: valueFiles: - - $values/k8s/{{ .ClusterName }}/argocd-apps/cluster-autoscaler.values.yaml + - $values/k8s/{{ .ClusterConfig.Name }}/argocd-apps/cluster-autoscaler.values.yaml - repoURL: {{ .KubeaidConfigForkURL }} targetRevision: HEAD ref: values diff --git a/pkg/core/templates/argocd-apps/templates/hetzner-robot.app.yaml.tmpl b/pkg/core/templates/argocd-apps/templates/hetzner-robot.app.yaml.tmpl new file mode 100644 index 0000000..efdfa20 --- /dev/null +++ b/pkg/core/templates/argocd-apps/templates/hetzner-robot.app.yaml.tmpl @@ -0,0 +1,26 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: hetzner-robot + namespace: argo-cd + +spec: + destination: + namespace: kube-system + server: https://kubernetes.default.svc + project: default + sources: + - repoURL: {{ .KubeaidForkURL }} + path: argocd-helm-charts/hetzner-robot + targetRevision: HEAD + helm: + valueFiles: + - $values/k8s/{{ .ClusterConfig.Name }}/argocd-apps/hetzner-robot.values.yaml + - repoURL: {{ .KubeaidConfigForkURL }} + targetRevision: HEAD + ref: values + syncPolicy: + syncOptions: + - CreateNamespace=true + - ApplyOutOfSyncOnly=true + diff --git a/pkg/core/templates/argocd-apps/templates/k8s-configs.app.yaml.tmpl b/pkg/core/templates/argocd-apps/templates/k8s-configs.app.yaml.tmpl new file mode 100644 index 0000000..2e13ea6 --- /dev/null +++ b/pkg/core/templates/argocd-apps/templates/k8s-configs.app.yaml.tmpl @@ -0,0 +1,16 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: k8s-configs + namespace: argo-cd +spec: + destination: + server: https://kubernetes.default.svc + namespace: kube-system + project: default + source: + repoURL: {{ .KubeaidConfigForkURL }} + path: k8s/{{ .ClusterConfig.Name }}/k8s-configs + targetRevision: HEAD + directory: + recurse: true diff --git a/cmd/bootstrap_cluster/templates/argocd-apps/templates/kube-prometheus.app.yaml.tmpl b/pkg/core/templates/argocd-apps/templates/kube-prometheus.app.yaml.tmpl similarity index 89% rename from cmd/bootstrap_cluster/templates/argocd-apps/templates/kube-prometheus.app.yaml.tmpl rename to pkg/core/templates/argocd-apps/templates/kube-prometheus.app.yaml.tmpl index bdd5974..2dc99a2 100644 --- a/cmd/bootstrap_cluster/templates/argocd-apps/templates/kube-prometheus.app.yaml.tmpl +++ b/pkg/core/templates/argocd-apps/templates/kube-prometheus.app.yaml.tmpl @@ -12,7 +12,7 @@ spec: namespace: monitoring project: default source: - path: k8s/{{ .ClusterName }}/kube-prometheus + path: k8s/{{ .ClusterConfig.Name }}/kube-prometheus repoURL: {{ .KubeaidConfigForkURL }} targetRevision: HEAD directory: diff --git a/cmd/bootstrap_cluster/templates/argocd-apps/templates/traefik.app.yaml.tmpl b/pkg/core/templates/argocd-apps/templates/kube2iam.app.yaml.tmpl similarity index 70% rename from cmd/bootstrap_cluster/templates/argocd-apps/templates/traefik.app.yaml.tmpl rename to pkg/core/templates/argocd-apps/templates/kube2iam.app.yaml.tmpl index 6517f30..54d1a66 100644 --- a/cmd/bootstrap_cluster/templates/argocd-apps/templates/traefik.app.yaml.tmpl +++ b/pkg/core/templates/argocd-apps/templates/kube2iam.app.yaml.tmpl @@ -1,26 +1,25 @@ apiVersion: argoproj.io/v1alpha1 kind: Application metadata: - name: traefik + name: kube2iam namespace: argo-cd finalizers: - resources-finalizer.argocd.argoproj.io - spec: destination: server: https://kubernetes.default.svc - namespace: traefik + namespace: aws project: default sources: - repoURL: {{ .KubeaidForkURL }} - path: argocd-helm-charts/traefik + path: argocd-helm-charts/kube2iam targetRevision: HEAD helm: valueFiles: - - $values/k8s/{{ .ClusterName }}/argocd-apps/traefik.values.yaml + - $values/k8s/{{ .ClusterConfig.Name }}/argocd-apps/kube2iam.values.yaml - repoURL: {{ .KubeaidConfigForkURL }} targetRevision: HEAD ref: values syncPolicy: syncOptions: - - CreateNamespace=true + - CreateNamespace=true diff --git a/cmd/bootstrap_cluster/templates/argocd-apps/templates/obmondo-k8s-agent.app.yaml.tmpl b/pkg/core/templates/argocd-apps/templates/obmondo-k8s-agent.app.yaml.tmpl similarity index 86% rename from cmd/bootstrap_cluster/templates/argocd-apps/templates/obmondo-k8s-agent.app.yaml.tmpl rename to pkg/core/templates/argocd-apps/templates/obmondo-k8s-agent.app.yaml.tmpl index cb08238..28fb145 100644 --- a/cmd/bootstrap_cluster/templates/argocd-apps/templates/obmondo-k8s-agent.app.yaml.tmpl +++ b/pkg/core/templates/argocd-apps/templates/obmondo-k8s-agent.app.yaml.tmpl @@ -17,7 +17,7 @@ spec: targetRevision: HEAD helm: valueFiles: - - $values/k8s/{{ .ClusterName }}/argocd-apps/obmondo-k8s-agent.values.yaml + - $values/k8s/{{ .ClusterConfig.Name }}/argocd-apps/obmondo-k8s-agent.values.yaml - repoURL: {{ .KubeaidConfigForkURL }} targetRevision: HEAD ref: values diff --git a/cmd/bootstrap_cluster/templates/argocd-apps/templates/root.yaml.tmpl b/pkg/core/templates/argocd-apps/templates/root.yaml.tmpl similarity index 88% rename from cmd/bootstrap_cluster/templates/argocd-apps/templates/root.yaml.tmpl rename to pkg/core/templates/argocd-apps/templates/root.yaml.tmpl index c322c26..d3090a8 100644 --- a/cmd/bootstrap_cluster/templates/argocd-apps/templates/root.yaml.tmpl +++ b/pkg/core/templates/argocd-apps/templates/root.yaml.tmpl @@ -11,7 +11,7 @@ spec: namespace: argo-cd project: default source: - path: k8s/{{ .ClusterName }}/argocd-apps + path: k8s/{{ .ClusterConfig.Name }}/argocd-apps repoURL: {{ .KubeaidConfigForkURL }} targetRevision: HEAD syncPolicy: diff --git a/cmd/bootstrap_cluster/templates/argocd-apps/templates/sealed-secrets.app.yaml.tmpl b/pkg/core/templates/argocd-apps/templates/sealed-secrets.app.yaml.tmpl similarity index 83% rename from cmd/bootstrap_cluster/templates/argocd-apps/templates/sealed-secrets.app.yaml.tmpl rename to pkg/core/templates/argocd-apps/templates/sealed-secrets.app.yaml.tmpl index 891fb9d..b7074ee 100644 --- a/cmd/bootstrap_cluster/templates/argocd-apps/templates/sealed-secrets.app.yaml.tmpl +++ b/pkg/core/templates/argocd-apps/templates/sealed-secrets.app.yaml.tmpl @@ -9,7 +9,7 @@ metadata: spec: destination: server: https://kubernetes.default.svc - namespace: kube-system + namespace: sealed-secrets project: default sources: - repoURL: {{ .KubeaidForkURL }} @@ -17,7 +17,7 @@ spec: targetRevision: HEAD helm: valueFiles: - - $values/k8s/{{ .ClusterName }}/argocd-apps/sealed-secrets.values.yaml + - $values/k8s/{{ .ClusterConfig.Name }}/argocd-apps/sealed-secrets.values.yaml - repoURL: {{ .KubeaidConfigForkURL }} targetRevision: HEAD ref: values diff --git a/cmd/bootstrap_cluster/templates/argocd-apps/templates/secrets.app.yaml.tmpl b/pkg/core/templates/argocd-apps/templates/secrets.app.yaml.tmpl similarity index 78% rename from cmd/bootstrap_cluster/templates/argocd-apps/templates/secrets.app.yaml.tmpl rename to pkg/core/templates/argocd-apps/templates/secrets.app.yaml.tmpl index f5973a5..3e0e8b2 100644 --- a/cmd/bootstrap_cluster/templates/argocd-apps/templates/secrets.app.yaml.tmpl +++ b/pkg/core/templates/argocd-apps/templates/secrets.app.yaml.tmpl @@ -7,10 +7,10 @@ metadata: spec: destination: server: https://kubernetes.default.svc - namespace: kube-system + namespace: sealed-secrets project: default source: - path: k8s/{{ .ClusterName }}/sealed-secrets + path: k8s/{{ .ClusterConfig.Name }}/sealed-secrets repoURL: {{ .KubeaidConfigForkURL }} targetRevision: HEAD directory: diff --git a/pkg/core/templates/argocd-apps/templates/velero.app.yaml.tmpl b/pkg/core/templates/argocd-apps/templates/velero.app.yaml.tmpl new file mode 100644 index 0000000..3c358bf --- /dev/null +++ b/pkg/core/templates/argocd-apps/templates/velero.app.yaml.tmpl @@ -0,0 +1,26 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: velero + namespace: argo-cd + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + destination: + server: https://kubernetes.default.svc + namespace: velero + project: default + sources: + - repoURL: {{ .KubeaidForkURL }} + path: argocd-helm-charts/velero + targetRevision: HEAD + helm: + valueFiles: + - $values/k8s/{{ .ClusterConfig.Name }}/argocd-apps/velero.values.yaml + - repoURL: {{ .KubeaidConfigForkURL }} + targetRevision: HEAD + ref: values + syncPolicy: + syncOptions: + - ApplyOutOfSyncOnly=true + - CreateNamespace=true diff --git a/pkg/core/templates/argocd-apps/velero.values.yaml.tmpl b/pkg/core/templates/argocd-apps/velero.values.yaml.tmpl new file mode 100644 index 0000000..0ad718c --- /dev/null +++ b/pkg/core/templates/argocd-apps/velero.values.yaml.tmpl @@ -0,0 +1,27 @@ +LoadCustomCRDs: true +velero: + configuration: + extraEnvVars: + AWS_CLUSTER_NAME: {{ .ClusterConfig.Name }} + backupStorageLocation: + - name: default + bucket: {{ .AWSConfig.DisasterRecovery.VeleroBackupsS3BucketName }} + config: + region: {{ .AWSConfig.Credentials.AWSRegion }} + provider: aws + volumeSnapshotLocation: + - name: default + provider: aws + config: + region: {{ .AWSConfig.Credentials.AWSRegion }} + features: EnableCSI + initContainers: + - name: velero-velero-plugin-for-aws + image: "velero/velero-plugin-for-aws:v1.7.1" + imagePullPolicy: IfNotPresent + volumeMounts: + - mountPath: /target + name: plugins + podAnnotations: + iam.amazonaws.com/role: arn:aws:iam::{{ .AWSAccountID }}:role/velero-{{ .ClusterConfig.Name }} + cluster-autoscaler.kubernetes.io/safe-to-evict: "true" diff --git a/cmd/bootstrap_cluster/templates/cluster-vars.jsonnet.tmpl b/pkg/core/templates/cluster-vars.jsonnet.tmpl similarity index 100% rename from cmd/bootstrap_cluster/templates/cluster-vars.jsonnet.tmpl rename to pkg/core/templates/cluster-vars.jsonnet.tmpl diff --git a/pkg/core/templates/k8s-configs/sealed-secrets.namespace.yaml.tmpl b/pkg/core/templates/k8s-configs/sealed-secrets.namespace.yaml.tmpl new file mode 100644 index 0000000..bbbfa22 --- /dev/null +++ b/pkg/core/templates/k8s-configs/sealed-secrets.namespace.yaml.tmpl @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: Namespace +metadata: + annotations: + iam.amazonaws.com/allowed-roles: | + ["arn:aws:iam::{{ .AWSAccountID }}:role/sealed-secrets-backuper-{{ .ClusterConfig.Name }}"] + name: sealed-secrets +spec: + finalizers: + - kubernetes diff --git a/pkg/core/templates/k8s-configs/velero.namespace.yaml.tmpl b/pkg/core/templates/k8s-configs/velero.namespace.yaml.tmpl new file mode 100644 index 0000000..9c74dc4 --- /dev/null +++ b/pkg/core/templates/k8s-configs/velero.namespace.yaml.tmpl @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: Namespace +metadata: + annotations: + iam.amazonaws.com/allowed-roles: | + ["arn:aws:iam::{{ .AWSAccountID }}:role/velero-{{ .ClusterConfig.Name }}"] + name: velero +spec: + finalizers: + - kubernetes diff --git a/cmd/bootstrap_cluster/templates/sealed-secrets/argo-cd/kubeaid-config.yaml.tmpl b/pkg/core/templates/sealed-secrets/argo-cd/kubeaid-config.yaml.tmpl similarity index 100% rename from cmd/bootstrap_cluster/templates/sealed-secrets/argo-cd/kubeaid-config.yaml.tmpl rename to pkg/core/templates/sealed-secrets/argo-cd/kubeaid-config.yaml.tmpl diff --git a/pkg/core/templates/sealed-secrets/capi-cluster/cloud-credentials.yaml.tmpl b/pkg/core/templates/sealed-secrets/capi-cluster/cloud-credentials.yaml.tmpl new file mode 100644 index 0000000..c0ac88e --- /dev/null +++ b/pkg/core/templates/sealed-secrets/capi-cluster/cloud-credentials.yaml.tmpl @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Secret +metadata: + name: cloud-credentials + namespace: {{ .CAPIClusterNamespace }} +stringData: + {{- if .AWSConfig }} + AWS_B64ENCODED_CREDENTIALS: {{ .AWSB64EncodedCredentials }} + {{- end }} + + {{- if .HetznerConfig }} + hcloud: "{{ .HetznerConfig.Credentials.HetznerAPIToken }}" + robot-user: "{{ .HetznerConfig.Credentials.HetznerRobotUser }}" + robot-password: "{{ .HetznerConfig.Credentials.HetznerRobotPassword }}" + {{- end }} diff --git a/pkg/core/templates/sealed-secrets/capi-cluster/hetzner-robot-ssh-keypair.yaml.tmpl b/pkg/core/templates/sealed-secrets/capi-cluster/hetzner-robot-ssh-keypair.yaml.tmpl new file mode 100644 index 0000000..8913aec --- /dev/null +++ b/pkg/core/templates/sealed-secrets/capi-cluster/hetzner-robot-ssh-keypair.yaml.tmpl @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Secret +metadata: + name: hetzner-robot-ssh-keys + namespace: {{ .CAPIClusterNamespace }} +stringData: + ssh-privatekey: | +{{ .HetznerConfig.RobotSSHKeyPair.PrivateKey | indent 4 }} + + ssh-publickey: | +{{ .HetznerConfig.RobotSSHKeyPair.PublicKey | indent 4 }} diff --git a/pkg/core/templates/sealed-secrets/kube-system/cloud-credentials.yaml.tmpl b/pkg/core/templates/sealed-secrets/kube-system/cloud-credentials.yaml.tmpl new file mode 100644 index 0000000..62ba832 --- /dev/null +++ b/pkg/core/templates/sealed-secrets/kube-system/cloud-credentials.yaml.tmpl @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: Secret +metadata: + name: cloud-credentials + namespace: kube-system +stringData: + hcloud: {{ .HetznerConfig.Credentials.HetznerAPIToken }} + robot-user: {{ .HetznerConfig.Credentials.HetznerRobotUser }} + robot-password: {{ .HetznerConfig.Credentials.HetznerRobotPassword }} diff --git a/scripts/install-prerequisites.sh b/scripts/install-prerequisites.sh index b60c491..541d961 100644 --- a/scripts/install-prerequisites.sh +++ b/scripts/install-prerequisites.sh @@ -2,55 +2,57 @@ set -o verbose set -o errexit -set -o nounset # Causes the shell to treat unset variables as errors and exit immediately +set -o nounset # Causes the shell to treat unset variables as errors and exit immediately. apt update -# Jsonnet and jq. -apt install -y jsonnet jq +# -------------------------- Required by KubeAid to build KubePrometheus -------------------------- -# Kubeseal. -KUBESEAL_VERSION="0.23.0" -curl -OL "https://github.com/bitnami-labs/sealed-secrets/releases/download/v${KUBESEAL_VERSION:?}/kubeseal-${KUBESEAL_VERSION:?}-linux-${CPU_ARCHITECTURE}.tar.gz" -tar -xvzf kubeseal-${KUBESEAL_VERSION:?}-linux-"${CPU_ARCHITECTURE}".tar.gz kubeseal -install -m 755 kubeseal /usr/local/bin/kubeseal +apt install -y jsonnet jq gnupg2 scdaemon curl -# gojsontoyaml. +# gojsontoyaml wget https://github.com/brancz/gojsontoyaml/releases/download/v0.1.0/gojsontoyaml_0.1.0_linux_"${CPU_ARCHITECTURE}".tar.gz tar -xvzf gojsontoyaml_0.1.0_linux_"${CPU_ARCHITECTURE}".tar.gz chmod +x gojsontoyaml mkdir -p /usr/local/bin mv ./gojsontoyaml /usr/local/bin -# K3d. +# ------------------------------ Required by KubeAid Bootstrap Script ----------------------------- + +# Kubeseal +KUBESEAL_VERSION="0.23.0" +curl -OL "https://github.com/bitnami-labs/sealed-secrets/releases/download/v${KUBESEAL_VERSION:?}/kubeseal-${KUBESEAL_VERSION:?}-linux-${CPU_ARCHITECTURE}.tar.gz" +tar -xvzf kubeseal-${KUBESEAL_VERSION:?}-linux-"${CPU_ARCHITECTURE}".tar.gz kubeseal +install -m 755 kubeseal /usr/local/bin/kubeseal + +# K3d apt install -y curl curl -s https://raw.githubusercontent.com/k3d-io/k3d/main/install.sh | bash -# Clusterawsadm. +# Clusterawsadm wget https://github.com/kubernetes-sigs/cluster-api-provider-aws/releases/download/v2.5.2/clusterawsadm_v2.5.2_linux_"${CPU_ARCHITECTURE}" mv clusterawsadm_v2.5.2_linux_"${CPU_ARCHITECTURE}" /usr/local/bin/clusterawsadm chmod +x /usr/local/bin/clusterawsadm -# Kubectl. +# Clusterctl +curl -L https://github.com/kubernetes-sigs/cluster-api/releases/download/v1.7.3/clusterctl-linux-"${CPU_ARCHITECTURE}" -o clusterctl +install -o root -g root -m 0755 clusterctl /usr/local/bin/clusterctl + +# ------------------------------------------- Utilities ------------------------------------------- + +# Kubectl curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/${CPU_ARCHITECTURE}/kubectl" chmod +x ./kubectl mv ./kubectl /usr/local/bin -# Helm. -curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 -chmod 700 get_helm.sh -./get_helm.sh - -# ArgoCD. -curl -sSL -o argocd-linux-"${CPU_ARCHITECTURE}" https://github.com/argoproj/argo-cd/releases/latest/download/argocd-linux-"${CPU_ARCHITECTURE}" -install -m 555 argocd-linux-"${CPU_ARCHITECTURE}" /usr/local/bin/argocd -rm argocd-linux-"${CPU_ARCHITECTURE}" - -# Clusterctl -curl -L https://github.com/kubernetes-sigs/cluster-api/releases/download/v1.7.3/clusterctl-linux-${CPU_ARCHITECTURE} -o clusterctl -install -o root -g root -m 0755 clusterctl /usr/local/bin/clusterctl - -# K3d +# K9s wget https://github.com/derailed/k9s/releases/download/v0.32.5/k9s_linux_"${CPU_ARCHITECTURE}".deb dpkg -i k9s_linux_"${CPU_ARCHITECTURE}".deb rm k9s_linux_"${CPU_ARCHITECTURE}".deb + +apt install -y vim + +# ------------------------------------------ Add SSH keys ----------------------------------------- +mkdir -p /root/.ssh +ssh-keyscan {github.com,gitlab.com} >>/root/.ssh/known_hosts +ssh-keyscan -p 2223 gitea.obmondo.com >>/root/.ssh/known_hosts diff --git a/utils/argo.go b/utils/argo.go new file mode 100644 index 0000000..0c0d0ce --- /dev/null +++ b/utils/argo.go @@ -0,0 +1,214 @@ +package utils + +import ( + "context" + "fmt" + "log/slog" + "path" + "strings" + "time" + + "github.com/Obmondo/kubeaid-bootstrap-script/constants" + "github.com/Obmondo/kubeaid-bootstrap-script/utils/assert" + "github.com/Obmondo/kubeaid-bootstrap-script/utils/logger" + "github.com/argoproj/argo-cd/v2/pkg/apiclient" + "github.com/argoproj/argo-cd/v2/pkg/apiclient/application" + "github.com/argoproj/argo-cd/v2/pkg/apiclient/session" + argoCDV1Aplha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" + "github.com/aws/aws-sdk-go-v2/aws" + coreV1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/tools/clientcmd" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// Installs ArgoCD Helm chart and creates the root ArgoCD App. +// Then creates and returns an ArgoCD Application client. +func InstallAndSetupArgoCD(ctx context.Context, clusterDir string, kubeClient client.Client) { + // Install ArgoCD Helm chart. + HelmInstall(ctx, &HelmInstallArgs{ + RepoName: "argo-cd", + RepoURL: "https://argoproj.github.io/argo-helm", + ChartName: "argo-cd", + Version: "7.7.0", + Namespace: "argo-cd", + ReleaseName: "argo-cd", + Values: "notification.enabled=false, dex.enabled=false", + }) + + // Create the root ArgoCD App. + slog.Info("Creating root ArgoCD app") + rootArgoCDAppPath := path.Join(clusterDir, "argocd-apps/templates/root.yaml") + + ExecuteCommandOrDie(fmt.Sprintf("kubectl apply -f %s", rootArgoCDAppPath)) + + createArgoCDApplicationClient(ctx, kubeClient) +} + +// Creates and returns an ArgoCD application client. +func createArgoCDApplicationClient(ctx context.Context, kubeClient client.Client) { + slog.InfoContext(ctx, "Creating ArgoCD application client") + + // Create ArgoCD client (without auth token). + argoCDClientOpts := &apiclient.ClientOptions{ + ServerName: "argocd-server", + + PortForward: true, + PortForwardNamespace: constants.NamespaceArgoCD, + KubeOverrides: &clientcmd.ConfigOverrides{ + Timeout: "10s", + }, + + Insecure: true, + HttpRetryMax: 20, + } + argoCDClient := apiclient.NewClientOrDie(argoCDClientOpts) + + // Create a session using that ArgoCD client. + argoCDClientSessionCloser, argoCDClientSession, err := argoCDClient.NewSessionClient() + assert.AssertErrNil(ctx, err, "Failed creating session using ArgoCD client") + defer argoCDClientSessionCloser.Close() + + // Retrieve ArgoCD admin password. + argoCDInitialAdminSecret := &coreV1.Secret{} + err = kubeClient.Get( + ctx, + types.NamespacedName{ + Namespace: constants.NamespaceArgoCD, + Name: "argocd-initial-admin-secret", + }, + argoCDInitialAdminSecret, + ) + assert.AssertErrNil(ctx, err, "Failed getting argocd-initial-admin-secret Secret") + argoCDAdminPassword := string(argoCDInitialAdminSecret.Data["password"]) + + // Retrieve ArgoCD auth token. + response, err := argoCDClientSession.Create(context.Background(), &session.SessionCreateRequest{ + Username: "admin", + Password: argoCDAdminPassword, + }) + assert.AssertErrNil(ctx, err, "Failed retrieving ArgoCD auth token") + + // Recreate ArgoCD client, with auth token. + argoCDClientOpts.AuthToken = response.Token + argoCDClient = apiclient.NewClientOrDie(argoCDClientOpts) + + // Create ArgoCD Application client. + constants.ArgoCDApplicationClientCloser, constants.ArgoCDApplicationClient = argoCDClient.NewApplicationClientOrDie() +} + +// Syncs the ArgoCD App (if not synced already). +// If the resources array is empty, then the whole ArgoCD App is synced. Otherwise, only the +// specified resources. +func SyncArgoCDApp(ctx context.Context, name string, resources []*argoCDV1Aplha1.SyncOperationResource) { + ctx = logger.AppendSlogAttributesToCtx(ctx, []slog.Attr{ + slog.String("app", name), + }) + + // Skip, if the ArgoCD App is already synced. + if isArgoCDAppSynced(ctx, name, resources) { + slog.InfoContext(ctx, "Skipped syncing ArgoCD application") + return + } + + // Sync the ArgoCD app. + + slog.InfoContext(ctx, "Syncing ArgoCD application") + + applicationSyncRequest := &application.ApplicationSyncRequest{ + Name: &name, + AppNamespace: aws.String(constants.NamespaceArgoCD), + RetryStrategy: &argoCDV1Aplha1.RetryStrategy{ + Limit: 3, + Backoff: &argoCDV1Aplha1.Backoff{ + Duration: "10s", + }, + }, + } + if len(resources) > 0 { + applicationSyncRequest.Resources = resources + } + + for { + _, err := constants.ArgoCDApplicationClient.Sync(ctx, applicationSyncRequest) + if err != nil { + if strings.Contains(err.Error(), "another operation is already in progress") { + slog.WarnContext(ctx, "ArgoCD App sync failed. Retrying after some time", logger.Error(err)) + time.Sleep(10 * time.Second) + continue + } + + assert.AssertErrNil(ctx, err, "Failed syncing ArgoCD application") + } + + switch name { + // Wait for the child ArgoCD Apps to be created. + case constants.ArgoCDAppRoot: + slog.InfoContext(ctx, "Sleeping for 10 seconds, waiting for the child ArgoCD Apps to be created") + time.Sleep(10 * time.Second) + return + + // Wait for the ArgoCD App to be synced. + default: + for { + if isArgoCDAppSynced(ctx, name, resources) { + return + } + slog.InfoContext(ctx, "Waiting for ArgoCD App to be synced") + time.Sleep(15 * time.Second) + } + } + } +} + +// Returns whether the given ArgoCD App is synced or not. +// If the resources array is empty, then checks whether the whole ArgoCD App is synced. Otherwise, +// only checks for the specified resources. +func isArgoCDAppSynced(ctx context.Context, name string, resources []*argoCDV1Aplha1.SyncOperationResource) bool { + // Get the ArgoCD App. + argoCDApp, err := constants.ArgoCDApplicationClient.Get(context.Background(), &application.ApplicationQuery{ + Name: &name, + AppNamespace: aws.String(constants.NamespaceArgoCD), + }) + assert.AssertErrNil(ctx, err, "Failed getting ArgoCD App") + + switch { + // Only check that the specified resources are synced. + case len(resources) > 0: + { + syncedResourcesMap := make(map[string]bool) + for _, resource := range argoCDApp.Status.Resources { + key := fmt.Sprintf("%s/%s/%s", resource.Group, resource.Kind, resource.Name) + syncedResourcesMap[key] = (resource.Status == argoCDV1Aplha1.SyncStatusCodeSynced) + } + + for _, resource := range resources { + key := fmt.Sprintf("%s/%s/%s", resource.Group, resource.Kind, resource.Name) + synced, exists := syncedResourcesMap[key] + if !exists || !synced { + return false + } + } + return true + } + + // Check that the whole ArgoCD App is synced. + default: + if name != constants.ArgoCDAppVelero { + return argoCDApp.Status.Sync.Status == argoCDV1Aplha1.SyncStatusCodeSynced + } + + // In case of Velero ArgoCD App, check that all the resources (except Schedules and Backups) + // are synced. + for _, resource := range argoCDApp.Status.Resources { + if resource.Kind == "Schedule" || resource.Kind == "Backup" { + continue + } + + if resource.Status != argoCDV1Aplha1.SyncStatusCodeSynced { + return false + } + } + return true + } +} diff --git a/utils/assert/assert.go b/utils/assert/assert.go new file mode 100644 index 0000000..5e87f42 --- /dev/null +++ b/utils/assert/assert.go @@ -0,0 +1,40 @@ +package assert + +import ( + "context" + "log/slog" + "os" + + "github.com/Obmondo/kubeaid-bootstrap-script/utils/logger" +) + +// Panics if the given error isn't nil. +func AssertErrNil(ctx context.Context, err error, customErrorMessage string, attributes ...any) { + if err == nil { + return + } + + attributes = append(attributes, logger.Error(err)) + slog.ErrorContext(ctx, customErrorMessage, attributes...) + os.Exit(1) +} + +// Panics if the given value isn't nil. +func AssertNil(ctx context.Context, value interface{}, errorMessage string) { + if value == nil { + return + } + + slog.ErrorContext(ctx, errorMessage) + os.Exit(1) +} + +// Panics if the given value is nil. +func AssertNotNil(ctx context.Context, value interface{}, errorMessage string) { + if value != nil { + return + } + + slog.ErrorContext(ctx, errorMessage) + os.Exit(1) +} diff --git a/utils/git.go b/utils/git.go index a1f251c..eb78193 100644 --- a/utils/git.go +++ b/utils/git.go @@ -1,13 +1,18 @@ package utils import ( + "context" + "errors" "fmt" "log" "log/slog" "os" + "strings" "time" - "github.com/Obmondo/kubeaid-bootstrap-script/constants" + "github.com/Obmondo/kubeaid-bootstrap-script/config" + "github.com/Obmondo/kubeaid-bootstrap-script/utils/assert" + "github.com/Obmondo/kubeaid-bootstrap-script/utils/logger" "github.com/go-git/go-git/v5" gitConfig "github.com/go-git/go-git/v5/config" "github.com/go-git/go-git/v5/plumbing" @@ -17,58 +22,87 @@ import ( "github.com/go-git/go-git/v5/plumbing/transport/ssh" ) -func GetGitAuthMethod() (authMethod transport.AuthMethod) { - if len(constants.ParsedConfig.Git.SSHPrivateKey) > 0 { - publicKeys, err := ssh.NewPublicKeysFromFile("git", constants.ParsedConfig.Git.SSHPrivateKey, constants.ParsedConfig.Git.Password) - if err != nil { - log.Fatalf("Failed generating SSH public key from SSH private key and password for git : %v", err) - } - authMethod = publicKeys - slog.Info("Using SSH private key and password for git authentication") - return - } +func GetGitAuthMethod(ctx context.Context) (authMethod transport.AuthMethod) { + slog.InfoContext(ctx, "Determining git auth method") - if len(constants.ParsedConfig.Git.Password) > 0 { + var err error + + switch { + // SSH private key and password. + case len(config.ParsedConfig.Git.SSHPrivateKey) > 0: + authMethod, err = ssh.NewPublicKeysFromFile("git", config.ParsedConfig.Git.SSHPrivateKey, config.ParsedConfig.Git.Password) + assert.AssertErrNil(ctx, err, "Failed generating SSH public key from SSH private key and password for git") + slog.Info("Using SSH private key and password") + + // Username and password. + case len(config.ParsedConfig.Git.Password) > 0: authMethod = &http.BasicAuth{ - Username: constants.ParsedConfig.Git.Username, - Password: constants.ParsedConfig.Git.Password, + Username: config.ParsedConfig.Git.Username, + Password: config.ParsedConfig.Git.Password, } - slog.Info("Using username and password for git authentication") - return - } + slog.Info("Using username and password") - sshAuth, err := ssh.NewSSHAgentAuth("git") - if err != nil { - log.Fatalf("SSH agent failed : %v", err) + // SSH agent. + default: + authMethod, err = ssh.NewSSHAgentAuth("git") + assert.AssertErrNil(ctx, err, "SSH agent failed") + slog.Info("Using SSH agent") } - authMethod = sshAuth - slog.Info("Using SSH agent for git authentication") return } -func GitCloneRepo(url, dir string, authMethod transport.AuthMethod) *git.Repository { +// Clones the given git repository into the given directory (only if the repo doesn't already exist +// in there). +// If the repo already exists, then it just does git pull. +func GitCloneRepo(ctx context.Context, url, dirPath string, authMethod transport.AuthMethod) *git.Repository { + ctx = logger.AppendSlogAttributesToCtx(ctx, []slog.Attr{ + slog.String("repo", url), slog.String("dir", dirPath), + }) + + // If the repo already exists. + if _, err := os.ReadDir(dirPath); err == nil { + repo, err := git.PlainOpen(dirPath) + assert.AssertErrNil(ctx, err, "Failed opening existing git repo") + + return repo + } + + // Clone git repo. + + slog.InfoContext(ctx, "Cloning repo") + opts := &git.CloneOptions{ Auth: authMethod, URL: url, } - if url == constants.ParsedConfig.Forks.KubeaidForkURL { + if url == config.ParsedConfig.Forks.KubeaidForkURL { opts.Depth = 1 } - repo, err := git.PlainClone(dir, false, opts) - if err != nil { - log.Fatalf("Failed git cloning repo %s in %s : %v", url, dir, err) - } - slog.Info("Cloned repo", slog.String("repo", url), slog.String("dir", dir)) + repo, err := git.PlainClone(dirPath, false, opts) + assert.AssertErrNil(ctx, err, "Failed cloning repo") return repo } -func GetDefaultBranchName(repo *git.Repository) string { - headRef, err := repo.Head() - if err != nil { - log.Fatal("Failed getting HEAD ref of kubeaid-config repo") +func GetDefaultBranchName(ctx context.Context, repo *git.Repository) string { + remote, err := repo.Remote("origin") + assert.AssertErrNil(ctx, err, "Failed getting repo 'origin' remote") + + refs, err := remote.List(&git.ListOptions{}) + assert.AssertErrNil(ctx, err, "Failed listing refs for 'origin' remote") + + for _, ref := range refs { + if ref.Name().String() == "HEAD" { + target := ref.Target().String() + + defaultBranchName := target[11:] // Remove "refs/heads/" prefix. + slog.InfoContext(ctx, "Detected default branch name", slog.String("branch", defaultBranchName)) + + return defaultBranchName + } } - return headRef.Name().Short() + + panic("Failed detecting default branch name") } func CreateAndCheckoutToBranch(repo *git.Repository, branch string, workTree *git.Worktree) { @@ -87,63 +121,71 @@ func CreateAndCheckoutToBranch(repo *git.Repository, branch string, workTree *gi slog.Info("Created and checked out to new branch", slog.String("branch", branch)) } -func AddCommitAndPushChanges(repo *git.Repository, workTree *git.Worktree, branch string, auth transport.AuthMethod, clusterName string, commitMessage string) plumbing.Hash { - if err := workTree.AddGlob(fmt.Sprintf("k8s/%s/*", clusterName)); err != nil { - log.Fatalf("Failed adding changes to git : %v", err) - } +func AddCommitAndPushChanges(ctx context.Context, repo *git.Repository, workTree *git.Worktree, branch string, auth transport.AuthMethod, clusterName string, commitMessage string) plumbing.Hash { + err := workTree.AddGlob(fmt.Sprintf("k8s/%s/*", config.ParsedConfig.Cluster.Name)) + assert.AssertErrNil(ctx, err, "Failed adding changes to git") status, err := workTree.Status() - if err != nil { - log.Fatalf("Failed determining git status : %v", err) - } + assert.AssertErrNil(ctx, err, "Failed determining git status") slog.Info("Determined git status", slog.Any("git-status", status)) commit, err := workTree.Commit(commitMessage, &git.CommitOptions{ Author: &object.Signature{ - Name: "KubeAid Installer", + Name: "KubeAid Bootstrap Script", Email: "info@obmondo.com", When: time.Now(), }, }) - if err != nil { - log.Fatalf("Failed creating git commit : %v", err) - } + assert.AssertErrNil(ctx, err, "Failed creating git commit") + commitObject, err := repo.CommitObject(commit) - if err != nil { - log.Fatalf("Failed getting commit object : %v", err) - } + assert.AssertErrNil(ctx, err, "Failed getting commit object") - if err = repo.Push(&git.PushOptions{ + err = repo.Push(&git.PushOptions{ Progress: os.Stdout, RemoteName: "origin", RefSpecs: []gitConfig.RefSpec{ gitConfig.RefSpec("refs/heads/" + branch + ":refs/heads/" + branch), }, Auth: auth, - }); err != nil { - log.Fatalf("git push failed : %v", err) - } + }) + assert.AssertErrNil(ctx, err, "Failed pushing commit to upstream") slog.Info("Added, committed and pushed changes", slog.String("commit-hash", commitObject.Hash.String())) + slog.Info("Create and merge PR please", slog.String("URL", getCreatePRURL(branch))) + return commitObject.Hash } -func WaitUntilPRMerged(repo *git.Repository, defaultBranchName string, commitHash plumbing.Hash, auth transport.AuthMethod, branchToBeMerged string) { +func getCreatePRURL(fromBranch string) string { + var ( + parts = strings.Split(config.ParsedConfig.Forks.KubeaidConfigForkURL, "/") + + repoOwner = parts[len(parts)-2] + repoName = strings.Split(parts[len(parts)-1], ".git")[0] + ) + + createPRURL := fmt.Sprintf("%s/compare/main...%s:%s:%s", + config.ParsedConfig.Forks.KubeaidConfigForkURL, repoOwner, repoName, fromBranch) + + return createPRURL +} + +func WaitUntilPRMerged(ctx context.Context, repo *git.Repository, defaultBranchName string, commitHash plumbing.Hash, auth transport.AuthMethod, branchToBeMerged string) { for { - slog.Info("Waiting for %s branch to be merged into the default branch %s. Sleeping for 10 seconds...\n", branchToBeMerged, defaultBranchName) + slog.Info("Waiting for PR to be merged. Sleeping for 10 seconds....", slog.String("from-branch", branchToBeMerged), slog.String("to-branch", defaultBranchName)) time.Sleep(10 * time.Second) - if err := repo.Fetch(&git.FetchOptions{ + err := repo.Fetch(&git.FetchOptions{ Auth: auth, RefSpecs: []gitConfig.RefSpec{"refs/*:refs/*"}, - }); err != nil && err != git.NoErrAlreadyUpToDate { - log.Fatalf("Failed determining whether branch is merged or not : %v", err) + }) + if !errors.Is(err, git.NoErrAlreadyUpToDate) { + assert.AssertErrNil(ctx, err, "Failed determining whether branch is merged or not") } defaultBranchRef, err := repo.Reference(plumbing.ReferenceName("refs/heads/"+defaultBranchName), true) - if err != nil { - log.Fatalf("Failed to get default branch ref of kubeaid-config repo : %v", err) - } + assert.AssertErrNil(ctx, err, "Failed to get default branch ref") if commitPresent := isCommitPresentInBranch(repo, commitHash, defaultBranchRef.Hash()); commitPresent { slog.Info("Detected branch merge") diff --git a/utils/helm.go b/utils/helm.go new file mode 100644 index 0000000..9473ffe --- /dev/null +++ b/utils/helm.go @@ -0,0 +1,119 @@ +package utils + +import ( + "context" + "log/slog" + "os" + "time" + + "github.com/Obmondo/kubeaid-bootstrap-script/utils/assert" + "github.com/Obmondo/kubeaid-bootstrap-script/utils/logger" + "github.com/avast/retry-go/v4" + "helm.sh/helm/v3/pkg/action" + "helm.sh/helm/v3/pkg/chart" + "helm.sh/helm/v3/pkg/chart/loader" + "helm.sh/helm/v3/pkg/cli" + "helm.sh/helm/v3/pkg/release" + "helm.sh/helm/v3/pkg/strvals" +) + +type HelmInstallArgs struct { + RepoURL, + RepoName, + ChartName, + Version, + ReleaseName, + Namespace string + Values string +} + +// Installs the given Helm chart (if not already deployed). +func HelmInstall(ctx context.Context, args *HelmInstallArgs) { + ctx = logger.AppendSlogAttributesToCtx(ctx, []slog.Attr{slog.String("chart", args.ChartName)}) + + settings := cli.New() + + actionConfig := &action.Configuration{} + err := actionConfig.Init(settings.RESTClientGetter(), settings.Namespace(), os.Getenv("HELM_DRIVER"), slog.Debug) + assert.AssertErrNil(ctx, err, "Failed initializing Helm action config") + + existingHelmRelease := findExistingHelmRelease(ctx, actionConfig, args) + + // CASE : Helm chart is already deployed. So we don't need to do anything. + if (existingHelmRelease != nil) && (existingHelmRelease.Info.Status == release.StatusDeployed) { + slog.InfoContext(ctx, "Skipped installing Helm chart, since it's already deployed") + return + } + + // CASE : Helm chart installation is stuck in pending-install state. So delete it first. Then + // we'll try to install it again. + if (existingHelmRelease != nil) && (existingHelmRelease.Info.Status == release.StatusPendingInstall) { + slog.InfoContext(ctx, "Uninstalling Helm chart, stuck in pending-install state") + + uninstallAction := action.NewUninstall(actionConfig) + uninstallAction.Timeout = 10 * time.Minute + uninstallAction.Wait = true + + _, err = uninstallAction.Run(args.ReleaseName) + assert.AssertErrNil(ctx, err, "Failed uninstalling Helm chart") + } + + // Install the Helm chart. + helmInstall(ctx, settings, actionConfig, args) +} + +// Looks whether a Helm release with the given name exists or not. +// If yes, then returns it. +func findExistingHelmRelease(ctx context.Context, actionConfig *action.Configuration, args *HelmInstallArgs) *release.Release { + listAction := action.NewList(actionConfig) + listAction.AllNamespaces = true + listAction.StateMask = action.ListAll + listAction.Filter = args.ReleaseName + + releases, err := listAction.Run() + assert.AssertErrNil(ctx, err, "Failed searching for existing Helm release") + + for _, release := range releases { + if (release.Name == args.ReleaseName) && (release.Namespace == args.Namespace) { + return release + } + } + return nil +} + +// Installs the given Helm chart. +func helmInstall(ctx context.Context, settings *cli.EnvSettings, actionConfig *action.Configuration, args *HelmInstallArgs) { + slog.InfoContext(ctx, "Installing Helm chart") + + installAction := action.NewInstall(actionConfig) + installAction.RepoURL = args.RepoURL + installAction.Version = args.Version + installAction.ReleaseName = args.ReleaseName + installAction.Namespace = args.Namespace + installAction.CreateNamespace = true + installAction.Timeout = 10 * time.Minute + installAction.Wait = true + + // Determine the path to the Helm chart. + chartPath, err := installAction.ChartPathOptions.LocateChart(args.ChartName, settings) + assert.AssertErrNil(ctx, err, "Failed locating chart path in Helm repo") + + /* + Load the Helm chart from that chart path. + We need to retry, since sometimes on the first try, we get this error : + + looks like args.RepoURL is not a valid chart repository or cannot be reached. + helm.sh/helm/v3/pkg/repo.FindChartInAuthAndTLSAndPassRepoURL + */ + chart, _ := retry.DoWithData(func() (*chart.Chart, error) { + return loader.Load(chartPath) + }) + + // Parse Helm chart values. + values, err := strvals.Parse(args.Values) + assert.AssertErrNil(ctx, err, "Failed parsing Helm values") + + // Install the Helm chart. + _, err = installAction.Run(chart, values) + assert.AssertErrNil(ctx, err, "Failed installing Helm chart") +} diff --git a/utils/k3d.go b/utils/k3d.go new file mode 100644 index 0000000..77b7ca8 --- /dev/null +++ b/utils/k3d.go @@ -0,0 +1,59 @@ +package utils + +import ( + "context" + "fmt" + "log/slog" + + "github.com/Obmondo/kubeaid-bootstrap-script/utils/assert" + "github.com/Obmondo/kubeaid-bootstrap-script/utils/logger" + k3dClient "github.com/k3d-io/k3d/v5/pkg/client" + "github.com/k3d-io/k3d/v5/pkg/runtimes" +) + +// Creates a K3D cluster with the given name (only if it doesn't already exist). +func CreateK3DCluster(ctx context.Context, name string) { + ctx = logger.AppendSlogAttributesToCtx(ctx, []slog.Attr{ + slog.String("cluster", name), + }) + + // Skip creating the cluster, if it already exists. + if doesK3dClusterExist(ctx, name) { + slog.InfoContext(ctx, "Skipped creating the K3d management cluster") + return + } + + slog.InfoContext(ctx, "Creating the K3d management cluster") + + // Create the cluster. + ExecuteCommandOrDie(fmt.Sprintf(` + k3d cluster create %s \ + --servers 1 --agents 3 \ + --image rancher/k3s:v1.31.0-k3s1 \ + --network k3d-%s \ + --wait + `, name, name)) + + // By default, the Kubernetes API server URL is like : https://0.0.0.0:5xxxx. 0.0.0.0 isn't + // resolvable from within the dev container. + // Since we are mounting the Docker socket to the dev container, it can resolve DNS names of + // Docker networks. So use the DNS name instead of 0.0.0.0. + // NOTE : Consider this situation : + // an existing K3D cluster may have wrong Kubernetes API server URL server. + ExecuteCommandOrDie(fmt.Sprintf(` + kubectl config set-cluster k3d-%s --server=https://k3d-%s-serverlb:6443 + `, name, name)) +} + +// Returns whether the given K3d cluster exists or not. +func doesK3dClusterExist(ctx context.Context, name string) bool { + clusters, err := k3dClient.ClusterList(ctx, runtimes.Docker) + assert.AssertErrNil(ctx, err, "Failed listing K3d clusters") + + for _, cluster := range clusters { + if cluster.Name == name { + return true + } + } + return false +} diff --git a/utils/kubernetes.go b/utils/kubernetes.go index 5c01890..8c5d2c5 100644 --- a/utils/kubernetes.go +++ b/utils/kubernetes.go @@ -1,89 +1,51 @@ package utils import ( + "context" "fmt" "log/slog" - "path" - "strings" + "os" "time" + "github.com/Obmondo/kubeaid-bootstrap-script/config" "github.com/Obmondo/kubeaid-bootstrap-script/constants" + "github.com/Obmondo/kubeaid-bootstrap-script/utils/assert" + "github.com/Obmondo/kubeaid-bootstrap-script/utils/logger" + coreV1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/client-go/tools/clientcmd" + kubeadmConstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" + clusterAPIV1Beta1 "sigs.k8s.io/cluster-api/api/v1beta1" + "sigs.k8s.io/controller-runtime/pkg/client" ) -// Installs Sealed Secrets in the underlying Kubernetes cluster. -func InstallSealedSecrets() { - ExecuteCommandOrDie(` - helm repo add sealed-secrets https://bitnami-labs.github.io/sealed-secrets - helm install sealed-secrets sealed-secrets/sealed-secrets --namespace kube-system \ - --set-string fullnameOverride=sealed-secrets-controller \ - --wait - `) -} - -/* -Does the following : +// Uses the kubeconfig file present at the given path, to create and return a Kubernetes Go client. +func CreateKubernetesClient(ctx context.Context, kubeconfigPath string) client.Client { + ctx = logger.AppendSlogAttributesToCtx(ctx, []slog.Attr{ + slog.String("kubeconfig", kubeconfigPath), + }) - (1) Install ArgoCD. + kubeconfig, err := clientcmd.BuildConfigFromFlags("", kubeconfigPath) + assert.AssertErrNil(ctx, err, "Failed building config from kubeconfig file") - (2) Then port forwards the ArgoCD server by spinning up a go-routine. - NOTE : Kills any existing kubectl process. - TODO : Just kill the port-forwarding on port 8080, instead of killing all kubectl processes. + scheme := runtime.NewScheme() - (3) Logs in to ArgoCD from ArgoCD CLI. + err = coreV1.AddToScheme(scheme) + assert.AssertErrNil(ctx, err, "Failed adding Core v1 scheme") - (4) Creates the root ArgoCD app. -*/ -func InstallAndSetupArgoCD(clusterDir string) { - // Install ArgoCD. - ExecuteCommandOrDie(` - helm repo add argo https://argoproj.github.io/argo-helm - helm install argo-cd argo/argo-cd --namespace argo-cd --create-namespace \ - --set notification.enabled=false --set dex.enabled=false \ - --wait`) - time.Sleep(time.Second * 20) - - // Port forward ArgoCD server. - go func() { - // Sometimes ArgoCD port forward may fail with this error : - // - // error copying from remote stream to local connection: readfrom tcp6 [::1]:8080->[::1]:34908: - // write tcp6 [::1]:8080->[::1]:34908: write: broken pipe - // - // In that case we want to re-establish the port-forwarding. - for { - output, _ := ExecuteCommand(` - pkill kubectl -9 - kubectl port-forward svc/argo-cd-argocd-server -n argo-cd 8080:443 - `) - if !strings.Contains(output, "broken pipe") { - break - } + err = clusterAPIV1Beta1.AddToScheme(scheme) + assert.AssertErrNil(ctx, err, "Failed adding ClusterAPI v1beta1 scheme") - slog.Info("Retrying port-forwarding ArgoCD server") - } - }() - slog.Info("Waiting for kubectl port-forward to be executed in the other go routine....") - time.Sleep(time.Second * 10) - - // Login to ArgoCD from ArgoCD CLI. - ExecuteCommandOrDie(` - ARGOCD_PASSWORD=$(kubectl -n argo-cd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d) - argocd login localhost:8080 --username admin --password $ARGOCD_PASSWORD --insecure`) - - // Create the root ArgoCD App. - slog.Info("Creating and syncing root ArgoCD app") - rootArgoCDAppPath := path.Join(clusterDir, "argocd-apps/templates/root.yaml") - ExecuteCommandOrDie(fmt.Sprintf("kubectl apply -f %s", rootArgoCDAppPath)) -} + kubeClient, err := client.New(kubeconfig, client.Options{ + Scheme: scheme, + }) + assert.AssertErrNil(ctx, err, "Failed creating kube client from kubeconfig") -// Takes the path to a Kubernetes Secret file. It then replaces the contents of that file by -// generating the corresponding Sealed Secret. -func GenerateSealedSecret(secretFilePath string) { - ExecuteCommandOrDie(fmt.Sprintf(` - kubeseal \ - --controller-name sealed-secrets-controller --controller-namespace kube-system \ - --secret-file %s --sealed-secret-file %s - `, secretFilePath, secretFilePath)) + return kubeClient } // Returns the namespace (capi-cluster / capi-cluster-) where the 'cloud-credentials' @@ -91,44 +53,152 @@ func GenerateSealedSecret(secretFilePath string) { // with the underlying cloud provider. func GetCapiClusterNamespace() string { capiClusterNamespace := "capi-cluster" - if len(constants.ParsedConfig.CustomerID) > 0 { - capiClusterNamespace = fmt.Sprintf("capi-cluster-%s", constants.ParsedConfig.CustomerID) + if len(config.ParsedConfig.CustomerID) > 0 { + capiClusterNamespace = fmt.Sprintf("capi-cluster-%s", config.ParsedConfig.CustomerID) } return capiClusterNamespace } -// Syncs the Infrastructure Provider component of the CAPI Cluster ArgoCD App and waits for the -// infrastructure specific CRDs to be installed and pod to be running. -func SyncInfrastructureProvider() { - // Determine the name of the Infrastructure Provider component. - var infrastructureProviderName string - switch { - case constants.ParsedConfig.Cloud.AWS != nil: - infrastructureProviderName = "aws" - - default: - Unreachable() +// Creates the given namespace (if it doesn't already exist). +func CreateNamespace(ctx context.Context, namespaceName string, kubeClient client.Client) { + namespace := &coreV1.Namespace{ + ObjectMeta: metaV1.ObjectMeta{ + Name: namespaceName, + }, } - if len(constants.ParsedConfig.CustomerID) > 0 { - infrastructureProviderName = fmt.Sprintf("aws-%s", constants.ParsedConfig.CustomerID) + + err := kubeClient.Create(ctx, namespace) + if errors.IsAlreadyExists(err) { + return } + assert.AssertErrNil(ctx, err, "Failed creating namespace", slog.String("namespace", namespaceName)) +} + +// Installs Sealed Secrets in the underlying Kubernetes cluster. +func InstallSealedSecrets(ctx context.Context) { + HelmInstall(ctx, &HelmInstallArgs{ + RepoName: "sealed-secrets", + RepoURL: "https://bitnami-labs.github.io/sealed-secrets", + ChartName: "sealed-secrets", + Version: "2.16.2", + Namespace: "sealed-secrets", + ReleaseName: "sealed-secrets", + Values: "fullnameOverride=sealed-secrets-controller", + }) +} + +// Takes the path to a Kubernetes Secret file. It replaces the contents of that file by generating +// the corresponding Sealed Secret. +func GenerateSealedSecret(ctx context.Context, secretFilePath string) { + ExecuteCommandOrDie(fmt.Sprintf(` + kubeseal \ + --controller-name sealed-secrets-controller --controller-namespace sealed-secrets \ + --secret-file %s --sealed-secret-file %s + `, secretFilePath, secretFilePath)) +} + +// Waits for the main cluster to be provisioned. +func WaitForMainClusterToBeProvisioned(ctx context.Context, kubeClient client.Client) { + wait.PollUntilContextCancel(ctx, time.Minute, false, func(ctx context.Context) (bool, error) { + slog.Info("Waiting for the main cluster to be provisioned") - ExecuteCommandOrDie(fmt.Sprintf( - "argocd app sync argo-cd/capi-cluster --resource operator.cluster.x-k8s.io:InfrastructureProvider:%s", - infrastructureProviderName, - )) - - capiClusterNamespace := GetCapiClusterNamespace() - // Wait for the infrastructure specific CRDs to be installed and pod to be running. - for { - if output := ExecuteCommandOrDie(fmt.Sprintf("kubectl get pods -n %s", capiClusterNamespace)); !strings.Contains(output, "No resources found") { - podStatus := ExecuteCommandOrDie(fmt.Sprintf("kubectl get pods -n %s -o jsonpath='{.items[0].status.phase}'", capiClusterNamespace)) - if podStatus == "Running" { - break + // Get the Cluster resource from the management cluster. + cluster := &clusterAPIV1Beta1.Cluster{} + if err := GetClusterResource(ctx, kubeClient, cluster); err != nil { + return false, err + } + + // Cluster phase should be 'Provisioned'. + if cluster.Status.Phase != string(clusterAPIV1Beta1.ClusterPhaseProvisioned) { + return false, nil + } + // + // Cluster status should be 'Ready'. + for _, condition := range cluster.Status.Conditions { + if condition.Type == clusterAPIV1Beta1.ReadyCondition && condition.Status == "True" { + return true, nil } } + return false, nil + }) +} + +// Queries the Cluster resource using the given kube-client. +func GetClusterResource(ctx context.Context, kubeClient client.Client, cluster *clusterAPIV1Beta1.Cluster) error { + var ( + name = config.ParsedConfig.Cluster.Name + namespace = GetCapiClusterNamespace() + ) + return kubeClient.Get( + ctx, + types.NamespacedName{ + Namespace: namespace, + Name: name, + }, + cluster, + ) +} + +// Waits for the main cluster to be ready to run our application workloads. +func WaitForMainClusterToBeReady(ctx context.Context, kubeClient client.Client) { + wait.PollUntilContextCancel(ctx, time.Minute, false, func(ctx context.Context) (bool, error) { + slog.Info("Waiting for the provisioned cluster's Kubernetes API server to be reachable and atleast 1 worker node to be initialized....") + + // List the nodes. + nodes := &coreV1.NodeList{} + if err := kubeClient.List(ctx, nodes); err != nil { + return false, err + } - slog.Info("Waiting for the capi-cluster pod to come up", slog.String("namespace", capiClusterNamespace)) - time.Sleep(5 * time.Second) + initializedWorkerNodeCount := 0 + for _, node := range nodes.Items { + if isControlPlaneNode(&node) { + continue + } + + isUninitialized := false + // + // Check whether the 'node.cluster.x-k8s.io/uninitialized' taint exists for the node or not. + // If yes, that means the node is still uninitialized. + for _, taint := range node.Spec.Taints { + if taint.Key == clusterAPIV1Beta1.NodeUninitializedTaint.Key { + isUninitialized = true + } + } + + if !isUninitialized { + initializedWorkerNodeCount++ + } + } + isClusterReady := (initializedWorkerNodeCount > 0) + return isClusterReady, nil + }) +} + +// Returns whether the given node object is part of the control plane or not. +func isControlPlaneNode(node *coreV1.Node) bool { + isControlPlaneNode := false + for key := range node.Labels { + if key == kubeadmConstants.LabelNodeRoleControlPlane { + isControlPlaneNode = true + } } + return isControlPlaneNode +} + +// Saves kubeconfig of the provisioned cluster locally. +func SaveKubeconfig(ctx context.Context, kubeClient client.Client) { + secret := &coreV1.Secret{} + err := kubeClient.Get(ctx, types.NamespacedName{ + Name: fmt.Sprintf("%s-kubeconfig", config.ParsedConfig.Cluster.Name), + Namespace: GetCapiClusterNamespace(), + }, secret) + assert.AssertErrNil(ctx, err, "Failed getting secret containing kubeconfig") + + kubeConfig := secret.Data["value"] + + err = os.WriteFile(constants.OutputPathProvisionedClusterKubeconfig, kubeConfig, 0644) + assert.AssertErrNil(ctx, err, "Failed saving kubeconfig to file") + + slog.InfoContext(ctx, "kubeconfig has been saved locally") } diff --git a/utils/logger/logger.go b/utils/logger/logger.go new file mode 100644 index 0000000..a553444 --- /dev/null +++ b/utils/logger/logger.go @@ -0,0 +1,32 @@ +package logger + +import ( + "log/slog" + "os" +) + +// Initializes the logger. +func InitLogger(isDebugModeEnabled bool) { + logLevel := slog.LevelInfo + if isDebugModeEnabled { + logLevel = slog.LevelDebug + } + + textHandler := slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{ + Level: logLevel, + + ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr { + if a.Key == slog.TimeKey { + a.Value = slog.StringValue(a.Value.Time().Format("15:04")) + } + return a + }, + }) + + logger := slog.New(withContextualSlogAttributesHandler(textHandler)) + slog.SetDefault(logger) +} + +func Error(err error) slog.Attr { + return slog.Any("error", err) +} diff --git a/utils/logger/with_contextual_attributes_handler.go b/utils/logger/with_contextual_attributes_handler.go new file mode 100644 index 0000000..99d5314 --- /dev/null +++ b/utils/logger/with_contextual_attributes_handler.go @@ -0,0 +1,47 @@ +package logger + +import ( + "context" + "log/slog" +) + +type SlogAttributesCtxKey struct{} + +var slogAttributesCtxKey = SlogAttributesCtxKey{} + +type ContextualSlogAttributesHandler struct { + slog.Handler +} + +// Retrieves slog attributes present in the context and appends them to the slog record (log event). +// The slog handler is then invoked with the modified record. +// We do this to print out the contextual slog attributes. +func (h *ContextualSlogAttributesHandler) Handle(ctx context.Context, record slog.Record) error { + if attributes, ok := ctx.Value(slogAttributesCtxKey).([]slog.Attr); ok { + for _, attribute := range attributes { + record.AddAttrs(attribute) + } + } + + return h.Handler.Handle(ctx, record) +} + +func withContextualSlogAttributesHandler(handler slog.Handler) *ContextualSlogAttributesHandler { + return &ContextualSlogAttributesHandler{ + handler, + } +} + +// Appends slog attributes to the given context. The modified context is then returned. +func AppendSlogAttributesToCtx(ctx context.Context, attributes []slog.Attr) context.Context { + if ctx == nil { + ctx = context.Background() + } + + existingSlogAttributes, ok := ctx.Value(slogAttributesCtxKey).([]slog.Attr) + if !ok { + existingSlogAttributes = []slog.Attr{} + } + existingSlogAttributes = append(existingSlogAttributes, attributes...) + return context.WithValue(ctx, slogAttributesCtxKey, existingSlogAttributes) +} diff --git a/utils/others.go b/utils/others.go index 0941a44..3bff74e 100644 --- a/utils/others.go +++ b/utils/others.go @@ -1,80 +1,86 @@ package utils import ( - "bytes" - "embed" + "context" "fmt" - "log" "log/slog" "os" "os/exec" + "path" "path/filepath" "strings" - "text/template" "time" + "github.com/Obmondo/kubeaid-bootstrap-script/config" "github.com/Obmondo/kubeaid-bootstrap-script/constants" - "github.com/go-sprout/sprout/sprigin" + "github.com/Obmondo/kubeaid-bootstrap-script/utils/assert" ) -func SetEnvs() { - os.Setenv("KUBECONFIG", constants.OutputPathManagementClusterKubeconfig) - - // Cloud provider specific environment variables. - switch { - case constants.ParsedConfig.Cloud.AWS != nil: - os.Setenv("AWS_REGION", constants.ParsedConfig.Cloud.AWS.Region) - os.Setenv("AWS_ACCESS_KEY_ID", constants.ParsedConfig.Cloud.AWS.AccessKey) - os.Setenv("AWS_SECRET_ACCESS_KEY", constants.ParsedConfig.Cloud.AWS.SecretKey) - os.Setenv("AWS_SESSION_TOKEN", constants.ParsedConfig.Cloud.AWS.SessionToken) - - awsB64EncodedCredentials := strings.TrimSpace( - strings.Split( - ExecuteCommandOrDie("clusterawsadm bootstrap credentials encode-as-profile"), - "WARNING: `encode-as-profile` should only be used for bootstrapping.", - )[1], - ) - os.Setenv(constants.EnvNameAWSB64EcodedCredentials, awsB64EncodedCredentials) - - default: - Unreachable() +// Creates a temp dir inside /tmp, where KubeAid Bootstrap Script will clone repos. +// Then sets the value of constants.TempDir as the temp dir path. +// If the temp dir already exists, then that gets reused. +func InitTempDir() { + namePrefix := "kubeaid-bootstrap-script-" + + // Check if a temp dir already exists for KubeAid Bootstrap Script. + // If yes, then reuse that. + filesAndFolders, err := os.ReadDir("/tmp") + if err != nil { + slog.Error("Failed listing files and folders in /tmp", slog.Any("error", err)) + os.Exit(1) } -} + for _, item := range filesAndFolders { + if item.IsDir() && strings.HasPrefix(item.Name(), namePrefix) { + path := "/tmp/" + item.Name() + slog.Info("Skipped creating temp dir, since it already exists", slog.String("path", path)) -func GetParentDirPath(filePath string) string { - splitPosition := strings.LastIndex(filePath, "/") - if splitPosition == -1 { - return "" + constants.TempDir = path + + return + } } - return filePath[:splitPosition] + + // Otherwise, create it. + + dirName := fmt.Sprintf("%s%d", namePrefix, time.Now().Unix()) + + path, err := os.MkdirTemp("/tmp", dirName) + assert.AssertErrNil(context.Background(), err, "Failed creating temp dir", slog.String("path", path)) + + slog.Info("Created temp dir", slog.String("path", path)) + + constants.TempDir = path } -func ParseAndExecuteTemplate(embeddedFS *embed.FS, fileName string, values any) []byte { - contentsAsBytes, err := embeddedFS.ReadFile(fileName) - if err != nil { - log.Fatalf("Failed getting template %s from embedded file-system : %v", fileName, err) +// Returns value of the given environment variable. +// Panics if the environment variable isn't found. +func GetEnv(name string) string { + value, found := os.LookupEnv(name) + if !found { + slog.Error("Env not found", slog.String("name", name)) + os.Exit(1) } - parsedTemplate, err := template.New(fileName).Funcs(sprigin.FuncMap()).Parse(string(contentsAsBytes)) - if err != nil { - log.Fatalf("Failed parsing template %s : %v", fileName, err) - } + return value +} - var executedTemplate bytes.Buffer - if err = parsedTemplate.Execute(&executedTemplate, values); err != nil { - log.Fatalf("Failed executing template %s : %v", fileName, err) +// Returns path to the parent dir of the given file. +func GetParentDirPath(filePath string) string { + splitPosition := strings.LastIndex(filePath, "/") + if splitPosition == -1 { + return "" } - return executedTemplate.Bytes() + return filePath[:splitPosition] } func executeCommand(command string, panicOnExecutionFailure bool) (string, error) { - cmd := exec.Command("bash", "-c", command) + slog.Info("Executing command", slog.String("command", command)) - slog.Info("Executing command", slog.String("command", cmd.String())) - output, err := cmd.CombinedOutput() - if err != nil && panicOnExecutionFailure { - log.Fatalf("Command execution failed : %s\n %v", string(output), err) + output, err := exec.Command("bash", "-c", command).CombinedOutput() + if panicOnExecutionFailure { + assert.AssertErrNil(context.Background(), err, "Command execution failed", slog.String("output", string(output))) } + slog.Debug("Command executed", slog.String("output", string(output))) return string(output), err } @@ -90,25 +96,23 @@ func ExecuteCommandOrDie(command string) string { return output } -func Unreachable() { panic("unreachable") } - // Creates intermediate directories which don't exist for the given file path. -func CreateIntermediateDirectories(filePath string) { +func CreateIntermediateDirsForFile(ctx context.Context, filePath string) { parentDir := filepath.Dir(filePath) - if err := os.MkdirAll(parentDir, os.ModePerm); err != nil { - log.Fatalf("Failed creating intermediate directories for file path %s : %v", filePath, err) - } + + err := os.MkdirAll(parentDir, os.ModePerm) + assert.AssertErrNil(ctx, err, "Failed creating intermediate directories for file", slog.String("path", filePath)) } -// Creates a temp dir inside /tmp, where KubeAid Bootstrap Script will clone repos. -// Then sets the value of constants.TempDir as the temp dir path. -func InitTempDir() { - name := fmt.Sprintf("kubeaid-bootstrap-script-%d", time.Now().Unix()) - path, err := os.MkdirTemp("/tmp", name) - if err != nil { - log.Fatalf("Failed creating temp dir : %v", err) - } - slog.Info("Created temp dir", slog.String("path", path)) +func GetClusterDir() string { + repoDir := path.Join(constants.TempDir, "kubeaid-config") - constants.TempDir = path + clusterDir := path.Join(repoDir, "k8s", config.ParsedConfig.Cluster.Name) + return clusterDir +} + +// Returns the path to the local temp directory, where contents of the given blob storage bucket +// will be / is downloaded. +func GetDirPathForDownloadedStorageBucketContents(bucketName string) string { + return path.Join(constants.TempDir, "buckets", bucketName) } diff --git a/utils/templates/templates.go b/utils/templates/templates.go new file mode 100644 index 0000000..ffb98b8 --- /dev/null +++ b/utils/templates/templates.go @@ -0,0 +1,30 @@ +package templates + +import ( + "bytes" + "context" + "embed" + "log/slog" + "text/template" + + "github.com/Obmondo/kubeaid-bootstrap-script/utils/assert" + "github.com/Obmondo/kubeaid-bootstrap-script/utils/logger" + "github.com/go-sprout/sprout/sprigin" +) + +func ParseAndExecuteTemplate(ctx context.Context, embeddedFS *embed.FS, templateFileName string, values any) []byte { + ctx = logger.AppendSlogAttributesToCtx(ctx, []slog.Attr{ + slog.String("template", templateFileName), + }) + + contentsAsBytes, err := embeddedFS.ReadFile(templateFileName) + assert.AssertErrNil(ctx, err, "Failed getting template from embedded file-system") + + parsedTemplate, err := template.New(templateFileName).Funcs(sprigin.FuncMap()).Parse(string(contentsAsBytes)) + assert.AssertErrNil(ctx, err, "Failed parsing template") + + var executedTemplate bytes.Buffer + err = parsedTemplate.Execute(&executedTemplate, values) + assert.AssertErrNil(ctx, err, "Failed executing template") + return executedTemplate.Bytes() +}