diff --git a/.dockerignore b/.dockerignore index c33e1a84..94c43417 100644 --- a/.dockerignore +++ b/.dockerignore @@ -11,6 +11,7 @@ !api/ !controllers/ !logger/ +!pkg/ !version/ # Other files not to ignore diff --git a/.gitignore b/.gitignore index 465f85cb..f934c432 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,9 @@ target/ .idea/ instana-agent.yaml +# Local development demonstration file should be ignored as it is not relevant and might be accidentally added +instana_v1_instanaagent_demo.yaml + # Eclipse https://github.com/github/gitignore/blob/master/Global/Eclipse.gitignore *.pydevproject diff --git a/.golangci.yml b/.golangci.yml index c321489d..558036c0 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -5,18 +5,15 @@ linters-settings: linters: disable-all: true enable: - - deadcode - - goimports - - gosimple - - govet - - ineffassign - - staticcheck - - structcheck - - typecheck - - unused - - varcheck - - misspell + - goimports + - gosimple + - govet + - ineffassign + - unused + - misspell + - exhaustive + - errcheck service: - golangci-lint-version: 1.31.x # use the fixed version to not introduce new linters unexpectedly + golangci-lint-version: 1.56.2 # use the fixed version to not introduce new linters unexpectedly diff --git a/Dockerfile b/Dockerfile index 8c89da01..c37e57b3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -25,6 +25,7 @@ COPY main.go main.go COPY api/ api/ COPY controllers/ controllers/ COPY version/ version/ +COPY pkg/ pkg/ # Build, injecting VERSION and GIT_COMMIT directly in the code RUN export ARCH=$(case "${TARGETPLATFORM}" in 'linux/amd64') echo 'amd64' ;; 'linux/arm64') echo 'arm64' ;; 'linux/s390x') echo 's390x' ;; 'linux/ppc64le') echo 'ppc64le' ;; esac) \ diff --git a/MAINTAINERS.md b/MAINTAINERS.md index cd7f9a42..368822b9 100644 --- a/MAINTAINERS.md +++ b/MAINTAINERS.md @@ -1,5 +1,5 @@ # MAINTAINERS -Felix Marx - @FelixMarxIBM -Henning Treu - @htreu -Torsten Kohn - @tkohn +- Konrad Ohms - @konrad-ohms +- Milica Cvrkota - @Milica-Cvrkota-IBM +- Fredrik Gundersen diff --git a/Makefile b/Makefile index 7e8ccc71..98d7691f 100644 --- a/Makefile +++ b/Makefile @@ -118,6 +118,9 @@ deploy: manifests kustomize ## Deploy controller in the configured Kubernetes cl cd config/manager && $(KUSTOMIZE) edit set image instana/instana-agent-operator=${IMG} $(KUSTOMIZE) build config/default | kubectl apply -f - +scale-to-zero: ## Scales the operator to zero in the cluster to allow local testing against a cluster + kubectl -n instana-agent scale --replicas=0 deployment.apps/instana-agent-operator && sleep 5 && kubectl get all -n instana-agent + deploy-minikube: manifests kustomize ## Convenience target to push the docker image to a local running Minikube cluster and deploy the Operator there. (eval $$(minikube docker-env) && docker rmi ${IMG} || true) docker save ${IMG} | (eval $$(minikube docker-env) && docker load) @@ -217,3 +220,62 @@ bundle-build: ## Build the bundle image for OLM. controller-yaml: manifests kustomize ## Output the YAML for deployment, so it can be packaged with the release. Use `make --silent` to suppress other output. cd config/manager && $(KUSTOMIZE) edit set image "instana/instana-agent-operator=$(IMG)" $(KUSTOMIZE) build config/default + +get-mockgen: + # commit 2c718f249a424ac6ce6e2afa28c3c17f95c51241 introduces --write_command_comment flag to enable us to keep the mocks without additional command comments + which mockgen >> /dev/null 2>&1 || go install go.uber.org/mock/mockgen@2c718f249a424ac6ce6e2afa28c3c17f95c51241 + +MOCKGEN_ARGS = --write_package_comment=false --write_source_comment=false --write_command_comment=false --copyright_file=hack/boilerplate.go.txt +gen-mocks: get-mockgen + mockgen --source ${GOPATH}/pkg/mod/sigs.k8s.io/controller-runtime@v0.17.2/pkg/client/interfaces.go --destination ./pkg/k8s/client/k8s_client_mock_test.go --package client ${MOCKGEN_ARGS} + + mockgen --source ./pkg/hash/hash.go --destination ./pkg/k8s/object/builders/agent/daemonset/hash_mock_test.go --package daemonset ${MOCKGEN_ARGS} + mockgen --source ./pkg/k8s/object/transformations/pod_selector.go --destination ./pkg/k8s/object/builders/agent/daemonset/pod_selector_mock_test.go --package daemonset ${MOCKGEN_ARGS} + mockgen --source ./pkg/k8s/object/builders/common/ports/ports.go --destination ./pkg/k8s/object/builders/agent/daemonset/ports_mock_test.go --package daemonset ${MOCKGEN_ARGS} + mockgen --source ./pkg/k8s/object/builders/common/env/env_builder.go --destination ./pkg/k8s/object/builders/agent/daemonset/env_builder_mock_test.go --package daemonset ${MOCKGEN_ARGS} + mockgen --source ./pkg/k8s/object/builders/common/volume/volume_builder.go --destination ./pkg/k8s/object/builders/agent/daemonset/volume_builder_mock_test.go --package daemonset ${MOCKGEN_ARGS} + mockgen --source ./pkg/k8s/operator/status/status.go --destination ./pkg/k8s/object/builders/agent/daemonset/status_mock_test.go --package daemonset ${MOCKGEN_ARGS} + + mockgen --source ./pkg/k8s/object/builders/common/helpers/helpers.go --destination ./pkg/k8s/object/builders/common/env/helpers_mock_test.go --package env ${MOCKGEN_ARGS} + + mockgen --source ./pkg/k8s/object/builders/common/helpers/helpers.go --destination ./pkg/k8s/object/builders/common/volume/helpers_mock_test.go --package volume ${MOCKGEN_ARGS} + + mockgen --source ./pkg/k8s/client/client.go --destination ./pkg/k8s/operator/operator_utils/instana_agent_client_mock_test.go --package operator_utils ${MOCKGEN_ARGS} + mockgen --source ./pkg/k8s/object/builders/common/builder/builder.go --destination ./pkg/k8s/operator/operator_utils/builder_mock_test.go --package operator_utils ${MOCKGEN_ARGS} + mockgen --source ./pkg/k8s/operator/lifecycle/lifecycle.go --destination ./pkg/k8s/operator/operator_utils/lifecycle_mock_test.go --package operator_utils ${MOCKGEN_ARGS} + + mockgen --source ./pkg/k8s/object/builders/common/helpers/helpers.go --destination ./pkg/k8s/object/builders/k8s-sensor/rbac/helpers_mock_test.go --package rbac ${MOCKGEN_ARGS} + + mockgen --source ./pkg/k8s/object/builders/common/helpers/helpers.go --destination ./pkg/k8s/object/builders/k8s-sensor/serviceaccount/helpers_mock_test.go --package serviceaccount ${MOCKGEN_ARGS} + + mockgen --source ./pkg/k8s/object/builders/common/helpers/helpers.go --destination ./pkg/k8s/object/builders/agent/serviceaccount/helpers_mock_test.go --package serviceaccount ${MOCKGEN_ARGS} + + mockgen --source ./pkg/k8s/object/builders/common/helpers/helpers.go --destination ./pkg/k8s/object/builders/k8s-sensor/configmap/helpers_mock_test.go --package configmap ${MOCKGEN_ARGS} + + mockgen --source ./pkg/k8s/object/builders/common/ports/ports.go --destination ./pkg/k8s/object/builders/common/ports/ports_mock_test.go --package ports ${MOCKGEN_ARGS} + mockgen --source ./pkg/k8s/object/builders/common/helpers/agent_interfaces.go --destination ./pkg/k8s/object/builders/common/ports/agent_interfaces_mock_test.go --package ports ${MOCKGEN_ARGS} + + mockgen --source ./pkg/k8s/object/builders/common/helpers/helpers.go --destination ./pkg/k8s/object/builders/agent/headless-service/helpers_mock_test.go --package headless_service ${MOCKGEN_ARGS} + mockgen --source ./pkg/k8s/object/transformations/pod_selector.go --destination ./pkg/k8s/object/builders/agent/headless-service/pod_selector_mock_test.go --package headless_service ${MOCKGEN_ARGS} + mockgen --source ./pkg/k8s/object/builders/common/ports/ports.go --destination ./pkg/k8s/object/builders/agent/headless-service/ports_mock_test.go --package headless_service ${MOCKGEN_ARGS} + + mockgen --source ./pkg/k8s/object/transformations/pod_selector.go --destination ./pkg/k8s/object/builders/agent/service/pod_selector_mock_test.go --package service ${MOCKGEN_ARGS} + mockgen --source ./pkg/k8s/object/builders/common/ports/ports.go --destination ./pkg/k8s/object/builders/agent/service/ports_mock_test.go --package service ${MOCKGEN_ARGS} + mockgen --source ./pkg/k8s/object/builders/common/helpers/agent_interfaces.go --destination ./pkg/k8s/object/builders/agent/service/agent_interfaces_mock_test.go --package service ${MOCKGEN_ARGS} + + mockgen --source ./pkg/k8s/object/transformations/transformations.go --destination ./pkg/k8s/object/builders/common/builder/transformations_mock_test.go --package builder ${MOCKGEN_ARGS} + mockgen --source ./pkg/k8s/object/builders/common/builder/builder.go --destination ./pkg/k8s/object/builders/common/builder/builder_mock_test.go --package builder ${MOCKGEN_ARGS} + + mockgen --source ./pkg/k8s/object/builders/common/helpers/helpers.go --destination ./pkg/k8s/object/builders/agent/secrets/tls-secret/helpers_mock_test.go --package tls_secret ${MOCKGEN_ARGS} + + mockgen --source ./pkg/k8s/object/builders/common/helpers/helpers.go --destination ./pkg/k8s/object/builders/agent/secrets/containers-instana-io-secret/helpers_mock_test.go --package containers_instana_io_secret ${MOCKGEN_ARGS} + mockgen --source ./pkg/k8s/object/builders/agent/secrets/containers-instana-io-secret/docker_config_json.go --destination ./pkg/k8s/object/builders/agent/secrets/containers-instana-io-secret/docker_config_json_mock_test.go --package containers_instana_io_secret ${MOCKGEN_ARGS} + + mockgen --source ./pkg/k8s/operator/status/status.go --destination ./pkg/k8s/object/builders/agent/configmap/status_mock_test.go --package configmap ${MOCKGEN_ARGS} + + mockgen --source ./pkg/k8s/object/builders/common/helpers/helpers.go --destination ./pkg/k8s/object/builders/k8s-sensor/poddisruptionbudget/helpers_mock_test.go --package poddisruptionbudget ${MOCKGEN_ARGS} + mockgen --source ./pkg/k8s/object/transformations/pod_selector.go --destination ./pkg/k8s/object/builders/k8s-sensor/poddisruptionbudget/pod_selector_mock_test.go --package poddisruptionbudget ${MOCKGEN_ARGS} + + + + diff --git a/README.md b/README.md index 3358e296..3e546ea1 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ## Introduction -Instana is an [APM solution](https://www.instana.com/product-overview/) built for microservices that enables IT Ops to build applications faster and deliver higher quality services by automating monitoring, tracing and root cause analysis. The solution is optimized for [Kubernetes](https://www.instana.com/automatic-kubernetes-monitoring/) and [OpenShift](https://www.instana.com/blog/automatic-root-cause-analysis-for-openshift-applications/). +Instana is an [APM solution](https://www.ibm.com/products/instana) built for microservices that enables IT Ops to build applications faster and deliver higher quality services by automating monitoring, tracing and root cause analysis. The solution is optimized for [Kubernetes](https://www.ibm.com/products/instana/kubernetes-monitoring) and [OpenShift](https://www.ibm.com/products/instana/supported-technologies/openshift-monitoring). ## Instana Agent Operator @@ -25,25 +25,87 @@ Please see the guidelines in [CONTRIBUTING.md](CONTRIBUTING.md). ## Local Development -Developing (and running) the Operator is possible in two easy ways: -- Running as Go application outside the Cluster -- Running as Deployment inside the Cluster +Prerequisites: -Both are described below. +* [Make](https://www.gnu.org/software/make/) ([Makefile](Makefile) used as a utility CMD ) +* [Go](https://go.dev) (for the supported version, see the [go.mod](go.mod)-file) +* [Kubernetes](http://kubernetes.io) +* [Minikube](https://minikube.sigs.k8s.io/docs/) +* [Operator SDK](https://sdk.operatorframework.io/docs/installation/#install-from-homebrew-macos) +* Something like [Docker](https://www.docker.com/) or [Podman](https://podman.io/) +* Instana Agent key -### Running Go Operator locally against a (Minikube) cluster +Developing (and running) the Operator is easiest in two ways: -1. Create a copy of the file `config/samples/instana_v1_instanaagent.yaml`, for the below steps we're assuming `config/samples/instana_v1_instanaagent_demo.yaml` -2. In this file, put correct values for e.g. the Agent `key`, `endpointHost` and `endpointPort`. -3. Install the CRD: `make install`. -4. Run the Go application, either from your IDE, or from command-line: `make run`. -5. Deploy the custom resource earlier created using `kubectl apply -f config/samples/instana_v1_instanaagent_demo.yaml` +### **Option 1:** Running Go Operator locally against a **Minikube** cluster + +1. Start minikube ([minikube docs](https://minikube.sigs.k8s.io/docs/start/)) + > [!NOTE] + When minikube runs on docker (at least on `RHEL 8`), there are network issues for pods reaching the internet. This causes connection issues for the agent and will prevent auto-updates or connections to the backend. To avoid this, use kvm2 driver instead: `minikube start --driver=kvm2`. If one is using podman, don't forget to create the minikube with the podman driver: `minikube start --driver=podman`. More info and options can be found in Minikube documentation about [podman](https://minikube.sigs.k8s.io/docs/drivers/podman/) + + ```shell + minikube start + ``` + +2. Install the CRD by running `make install` at the root of the repository + + ```shell + # Install command in root of the repository (installs custom resource to k8s) + make install + # List CRD to verify it appears in the list + kubectl get crd + ``` + +3. Create `instana-agent` namespace on the cluster: + + ```shell + kubectl apply -f config/samples/instana_agent_namespace.yaml + # List namespaces to verify it appears in the list + kubectl get ns -n instana-agent + ``` + +4. Run the `instana-agent-operator` Go application, either from your IDE, or from command-line: `make run`. + + ```shell + # Starts the operator using make with additional fmt vet gen functionality + make run + ``` + +5. **Duplicate** agent [sample file](config/samples/instana_v1_instanaagent.yaml) in `config/samples/instana_v1_instanaagent.yaml` + > [!NOTE] + for this demonstration the duplicate will be named as `instana_v1_instanaagent_demo.yaml` + + ```shell + # Copy/Duplicate the sample file with a "demo" suffix + cp config/samples/instana_v1_instanaagent.yaml config/samples/instana_v1_instanaagent_demo.yaml + ``` + +6. Change the placeholder values in the [**duplicated file**](config/samples/instana_v1_instanaagent_demo.yaml) to your preferred values e.g. the Agent `key`, `endpointHost` and `endpointPort` + > [!TIP] + In the configuration, there is a field `spec.zone.name`. Changing this to something more identifiable and personalised will help you find your infrastructure easier in the frontend-client. +7. Deploy the custom resource earlier created using + + ```shell + kubectl apply -f config/samples/instana_v1_instanaagent_demo.yaml + ``` + + Verify that the operator reacted to the application of the yaml file by looking into the logs of the running operator +8. Depending on your local configurations, the environment should appear **IBM Instana infrastructure-page**. Standard minikube configuration should appear there as `minikube`. To stop, take the following actions: -- `kubectl delete -f config/samples/instana_v1_instanaagent_demo.yaml` -- `make uninstall` -### Running Deployment inside the cluster + ```shell + # Remove the instance from your kubernetes instance + kubectl delete -f config/samples/instana_v1_instanaagent_demo.yaml + # Final cleanup e.g `kubectl delete -k config/crd` + make uninstall + # Will stop the service + minikube start + # Will reset the whole set-up + minikube delete + ``` + +### **Option 2:** Running Deployment inside the cluster The Instana Agent Operator can be developed and tested easily against a local Minikube cluster or any other configured Kubernetes cluster. Therefore, follow the below steps: @@ -58,6 +120,5 @@ Kubernetes cluster. Therefore, follow the below steps: Now you should have a successful running Operator. To remove the Operator again, run: -- `kubectl delete -f config/samples/instana_v1_instanaagent_demo.yaml` -- `make undeploy`. - +* `kubectl delete -f config/samples/instana_v1_instanaagent_demo.yaml` +* `make undeploy`. diff --git a/TODO.md b/TODO.md new file mode 100644 index 00000000..c5ff111a --- /dev/null +++ b/TODO.md @@ -0,0 +1,186 @@ +## Next Steps + +### ~~Fix K8Sensor Deployment And~~ Test Seamless Upgrade from deprecated k8s sensors + +~~Deployment of K8Sensor is currently broken, fix this and then~~ run tests to ensure upgrade from a configuration +using the deprecated kubernetes sensor is seamless. To test this you should install the agent using the old (2.x) +version of the operator that uses helm and configure the agent to use the deprecated k8s sensor. Then upgrade the +operator to the new (3.x) version and ensure that the upgrade works seamlessly and that the new k8sensor is deployed. +Then check the Instana backend to ensure that k8s data collection has continued to function as expected through the +transition. + +### Misconfiguration Errors + +If the user misconfigures the agent then the attempt to create or update the Agent CR should be rejected. This can be +achieved using one of or a combination of the following methods. + +#### CR Validation + +[Validation rules](https://kubernetes.io/blog/2022/09/23/crd-validation-rules-beta/) and schema-based +[generation](https://book.kubebuilder.io/reference/markers/crd.html), +[validation](https://book.kubebuilder.io/reference/markers/crd-validation.html), and +[processing](https://book.kubebuilder.io/reference/markers/crd-processing.html) rules can be used to verify validity of +user-provided configuration and provide useful feedback for troubleshooting. + +#### Webhook Validation + +[Defaulting and Validation Webhooks](https://book.kubebuilder.io/cronjob-tutorial/webhook-implementation) could be used +for more advanced validation and to ensure defaulted values will appear on the CR present on the cluster without the +need for updates to the CR by the controller that could cause performance issues if another controller is managing the +agent CR. + +#### Validation Admission Policy + +Beginning in k8s v1.26 (alpha) or v1.28 (beta) a +[ValidationAdmissionPolicy](https://kubernetes.io/docs/reference/access-authn-authz/validating-admission-policy/) may +also be used to configure validation rules using [Common Expression Language (CEL)](https://github.com/google/cel-spec). + +### Testing + +#### Unit Tests + +Unit test coverage exists now for most of the repo, but I recommend adding unit test coverage for +[status.go](./pkg/k8s/operator/status/status.go) and [event-filter.go](./controllers/event_filter.go). Unit tests should +also be added for the [k8sensor deployment builder](./pkg/k8s/object/builders/k8s-sensor/deployment/deployment.go). I +recommend implementing mostly whitebox testing. This should be very similar to the tests that already exist +[for the agent daemonset](./pkg/k8s/object/builders/agent/daemonset/daemonset_test.go). You will need to use the +[mock-gen](https://github.com/golang/mock) tool to generate mocks for some of the interfaces used by the deployment +builder. You will then need to add the relevant mock-gen commands to the Makefile, so they can be easily regenerated +in the future if needed. For an example of this, please see the mockgen commands for the daemonset tests +[in the Makefile](./Makefile#L227). + +#### Behavioral Tests + +Some behavioral tests exist at [controllers/suite_test.go](./controllers/suite_test.go). These will run a mock k8s +server and test the agent controller's behavior to ensure that the lifecycle of owned resources are managed as expected +during create, update, and delete of the Agent CR. Additional tests can be added here as needed or desired. + +#### End to End Tests + +The end to end tests that currently run in the helm and webhook builds will need to be setup to run against PRs for this +repo to ensure that the end-to-end functionality of the agent works as expected when changes are made to the operator +logic. + +### Chart Update + +The Helm chart should be updated to wrap the operator and an instance of the CR built by directly using toYaml on the +Values.yaml file to construct the spec. This should end up looking something like this: + +```yaml +apiVersion: instana.io/v1 +kind: InstanaAgent +metadata: + name: "{{ .Release.Name }}" + namespace: "{{ .Release.Namespace }}" +spec: + {{- toYaml .Values | nindent 2}} +``` + +But you may need to modify it a bit to get the formatting exactly right. The new Helm chart should include only this +and the CRD, plus the resources needed to deploy the operator, which can be generated by running +`kustomize build config/default` and then replacing the namespace in each namespaced resource with +`{{ .Release.Namespace }}` to ensure that all resources will be deployed into the desired namespace. Helm itself will +ensure that the CRD will be installed before the InstanaAgent CR. See +[here](https://helm.sh/docs/chart_best_practices/custom_resource_definitions/) for more information. + +#### Chart Update Automation + +Some automation to make updates to the chart based on changes to the CRD or +operator deployment may be desirable if these things are expected to change often. + +## Future Considerations + +### Manifest Build Automation + +It may be useful to add logic to the PR builds that will automatically regenerate manifests and bundle YAMLs and +commit the changes to the source branch. Additionally, if you find that you are frequently making changes to operator +deployment files or sample CRs that would affect the Helm chart you may want to consider creating some automation that +will automatically push changes to the chart when relevant files in the operator repo have changed. + +### Code Linting + +Settings for [static code linting](.golangci.yml) should be reviewed and updated to suit the teams preferences. A basic +set of rules is currently enabled and will run during PR builds for this repo. + +### Error Wrapping + +Custom error types should be created with relevant messages to wrap errors that are being passed up the stack with +relevant information for debugging. + +### Sensitive Data + +Currently sensitive data (agent key, download, key, certificates, etc.) can be configured directly with the Agent CR. +This is considered bad-practice and can be a security risk in some cases; however, it may be alright to keep as a means +to deploy agents easily in development environments. Customers should be advised to place sensitive data directly into +secrets and reference the secrets from the agent spec. + +### Configure Exponential Backoff + +Rate-limiting [for the controller](https://danielmangum.com/posts/controller-runtime-client-go-rate-limiting/) should +be configured to prevent potential performance issues in cases where the cluster is inaccessible or the agent otherwise +cannot be deployed for some reason. + +### Automatic Tolerations + +Options could be added to the CR-spec to enable agents to run on master nodes by automatically setting the appropriate +tolerations for node taints. + +### Automatic Zones + +If desired an option could be added to automatically assign zone names to agent instances based on the value of the +`topology.kubernetes.io/zone` label on the node on which they are running. + +### Logging + +It may be worth considering the use of different default logging settings to improve readability +(e.g. --zap-time-encoding=rfc3339 --zap-encoder=console). + +### .spec.agent.configuration_yaml + +This could potentially be deprecated and replaced by a field using the `json.RawMessage` type, which would enable the +configuration yaml to be configured using native yaml within the CR rather than as an embedded string. + +### Probes + +The agent should have a readiness probe in addition to its liveness probe. The k8s_sensor should also have liveness and +readiness probes. A startup probe can also be added to the agent now that it is supported in all stable versions of k8s. +This will allow for faster readiness and recovery from soft-locks (if they occur) since it will allow the +initialDelaySeconds to be reduced on the liveness probe. The agent and k8sensor may also want to create dedicated +readiness endpoints to allow their actual availability to be reflected more accurately in the CR status. In the +agent's case, the availability of independent servers running on different ports may need to be considered when +deciding whether to do this since traffic directed at k8s services will not be forwarded to pods that are marked as +unready. + +### PVs For Package Downloads + +Optional Persistent volumes could potentially be used to cache dynamically downloaded updates and packages in between +agent restarts. + +### Runtime Status + +Runtime status information from the agent could be scraped and incorporated into the status tracked by the CR if this +is deemed useful. + +### Ephemeral Storage Requests/Limits + +Requests and limits for +[ephemeral storage](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#setting-requests-and-limits-for-local-ephemeral-storage) +can be set to ensure that agent pods are assigned to nodes containing appropriate storage space for dynamic agent +package downloads or to ensure agents do not exceed some limit of storage use on the host node. + +### Certificate Generation + +If desired, certificates could be automatically generated and configured when appropriate if cert-manager or +OpenShift's certificate generation is available. + +### Network Policies + +[Network policies](https://kubernetes.io/docs/concepts/services-networking/network-policies/) can be used to restrict +inbound traffic on ports that the agent or k8s_sensor do not use as a security measure. (May not work on agent itself +due to `hostNetwork: true). + +### Auto-Reload on Agent-Key or Download-Key Change + +Currently, the agent-key and download-key are read by the agent via environment variable set via referencing a key in +one or more k8s secrets. It would be beneficial to watch the secret(s) and trigger a restart of the agent daemsonset if +a change is detected in the secret(s). \ No newline at end of file diff --git a/api/v1/inline_types.go b/api/v1/inline_types.go index dd85b093..3317c46a 100644 --- a/api/v1/inline_types.go +++ b/api/v1/inline_types.go @@ -5,8 +5,15 @@ package v1 import ( - appV1 "k8s.io/api/apps/v1" - coreV1 "k8s.io/api/core/v1" + "fmt" + "strconv" + + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + + "github.com/instana/instana-agent-operator/pkg/map_defaulter" + "github.com/instana/instana-agent-operator/pkg/pointer" ) type AgentMode string @@ -25,12 +32,20 @@ type Name struct { type Create struct { // +kubebuilder:validation:Optional - Create bool `json:"create,omitempty"` + Create *bool `json:"create,omitempty"` } type Enabled struct { // +kubebuilder:validation:Optional - Enabled bool `json:"enabled,omitempty"` + Enabled *bool `json:"enabled,omitempty" yaml:"enabled,omitempty"` +} + +func (e Enabled) String() string { + if e.Enabled == nil { + return "nil" + } else { + return strconv.FormatBool(*e.Enabled) + } } // BaseAgentSpec defines the desired state info related to the running Agent @@ -89,11 +104,11 @@ type BaseAgentSpec struct { // Override the container image used for the Instana Agent pods. // +kubebuilder:validation:Optional - ImageSpec `json:"image,omitempty"` + ExtendedImageSpec `json:"image,omitempty"` // Control how to update the Agent DaemonSet // +kubebuilder:validation:Optional - UpdateStrategy appV1.DaemonSetUpdateStrategy `json:"updateStrategy,omitempty"` + UpdateStrategy appsv1.DaemonSetUpdateStrategy `json:"updateStrategy,omitempty"` // Override Agent Pod specific settings such as annotations, labels and resources. // +kubebuilder:validation:Optional @@ -127,9 +142,6 @@ type BaseAgentSpec struct { // Supply Agent configuration e.g. for configuring certain Sensors. // +kubebuilder:validation:Optional ConfigurationYaml string `json:"configuration_yaml,omitempty"` - // Mount in a ConfigMap with Agent configuration. Alternative to the `configuration_yaml` field. - // +kubebuilder:validation:Optional - Configuration ConfigurationSpec `json:"configuration,omitempty"` // RedactKubernetesSecrets sets the INSTANA_KUBERNETES_REDACT_SECRETS environment variable. // +kubebuilder:validation:Optional @@ -143,9 +155,26 @@ type BaseAgentSpec struct { // Alternative to `Host` for referencing a different Maven repo. // +kubebuilder:validation:Optional MvnRepoUrl string `json:"instanaMvnRepoUrl,omitempty"` - // Custom agent charts url. + // Sets the INSTANA_MVN_REPOSITORY_FEATURES_PATH environment variable + // +kubebuilder:validation:Optional + MvnRepoFeaturesPath string `json:"instanaMvnRepoFeaturesPath,omitempty"` + // Sets the INSTANA_MVN_REPOSITORY_SHARED_PATH environment variable // +kubebuilder:validation:Optional - ChartsUrl string `json:"charts_url,omitempty"` + MvnRepoSharedPath string `json:"instanaMvnRepoSharedPath,omitempty"` +} + +type ResourceRequirements corev1.ResourceRequirements + +func (r ResourceRequirements) GetOrDefault() corev1.ResourceRequirements { + requestsDefaulter := map_defaulter.NewMapDefaulter((*map[corev1.ResourceName]resource.Quantity)(&r.Requests)) + requestsDefaulter.SetIfEmpty(corev1.ResourceMemory, resource.MustParse("512Mi")) + requestsDefaulter.SetIfEmpty(corev1.ResourceCPU, resource.MustParse("0.5")) + + limitsDefaulter := map_defaulter.NewMapDefaulter((*map[corev1.ResourceName]resource.Quantity)(&r.Limits)) + limitsDefaulter.SetIfEmpty(corev1.ResourceMemory, resource.MustParse("768Mi")) + limitsDefaulter.SetIfEmpty(corev1.ResourceCPU, resource.MustParse("1.5")) + + return corev1.ResourceRequirements(r) } type AgentPodSpec struct { @@ -159,12 +188,12 @@ type AgentPodSpec struct { // agent.pod.tolerations are tolerations to influence agent pod assignment. // +kubebuilder:validation:Optional - Tolerations []coreV1.Toleration `json:"tolerations,omitempty"` + Tolerations []corev1.Toleration `json:"tolerations,omitempty"` // agent.pod.affinity are affinities to influence agent pod assignment. // https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ // +kubebuilder:validation:Optional - Affinity coreV1.Affinity `json:"affinity,omitempty"` + Affinity corev1.Affinity `json:"affinity,omitempty"` // agent.pod.priorityClassName is the name of an existing PriorityClass that should be set on the agent pods // https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/ @@ -172,7 +201,9 @@ type AgentPodSpec struct { PriorityClassName string `json:"priorityClassName,omitempty"` // Override Agent resource requirements to e.g. give the Agent container more memory. - coreV1.ResourceRequirements `json:",inline"` + ResourceRequirements `json:",inline"` + + NodeSelector map[string]string `json:"nodeSelector,omitempty"` } type TlsSpec struct { @@ -181,10 +212,10 @@ type TlsSpec struct { SecretName string `json:"secretName,omitempty"` // certificate (together with key) is the alternative to an existing Secret. Must be base64 encoded. // +kubebuilder:validation:Optional - Certificate string `json:"certificate,omitempty"` + Certificate []byte `json:"certificate,omitempty"` // key (together with certificate) is the alternative to an existing Secret. Must be base64 encoded. // +kubebuilder:validation:Optional - Key string `json:"key,omitempty"` + Key []byte `json:"key,omitempty"` } type ImageSpec struct { @@ -203,16 +234,28 @@ type ImageSpec struct { // PullPolicy specifies when to pull the image container. // +kubebuilder:validation:Optional - PullPolicy string `json:"pullPolicy,omitempty"` + PullPolicy corev1.PullPolicy `json:"pullPolicy,omitempty"` +} + +type ExtendedImageSpec struct { + // +kubebuilder:validation:Required + ImageSpec `json:",inline"` // PullSecrets allows you to override the default pull secret that is created when `agent.image.name` starts with // "containers.instana.io". Setting `agent.image.pullSecrets` prevents the creation of the default "containers-instana-io" secret. // +kubebuilder:validation:Optional - PullSecrets []PullSecretSpec `json:"pullSecrets,omitempty"` + PullSecrets []corev1.LocalObjectReference `json:"pullSecrets,omitempty"` } -type PullSecretSpec struct { - Name `json:",inline"` +func (i ImageSpec) Image() string { + switch { + case i.Digest != "": + return fmt.Sprintf("%s@%s", i.Name, i.Digest) + case i.Tag != "": + return fmt.Sprintf("%s:%s", i.Name, i.Tag) + default: + return i.Name + } } type HostSpec struct { @@ -220,15 +263,6 @@ type HostSpec struct { Repository string `json:"repository,omitempty"` } -type ConfigurationSpec struct { - // When setting this to true, the Helm chart will automatically look up the entries - // of the default instana-agent ConfigMap, and mount as agent configuration files - // under /opt/instana/agent/etc/instana all entries with keys that match the - // 'configuration-*.yaml' scheme - // +kubebuilder:validation:Optional - AutoMountConfigEntries bool `json:"autoMountConfigEntries,omitempty"` -} - type Prometheus struct { // +kubebuilder:validation:Optional RemoteWrite Enabled `json:"remoteWrite,omitempty"` @@ -249,6 +283,8 @@ type ServiceAccountSpec struct { // Name of the ServiceAccount. If not set and `create` is true, a name is generated using the fullname template. Name `json:",inline"` + + Annotations map[string]string `json:"annotations,omitempty"` } type PodSecurityPolicySpec struct { @@ -270,32 +306,87 @@ type K8sSpec struct { DeploymentSpec KubernetesDeploymentSpec `json:"deployment,omitempty"` // +kubebuilder:validation:Optional ImageSpec ImageSpec `json:"image,omitempty"` + // Toggles the PDB for the K8s Sensor + // +kubebuilder:validation:Optional + PodDisruptionBudget Enabled `json:"podDisruptionBudget,omitempty"` +} + +type KubernetesPodSpec struct { + ResourceRequirements `json:",inline"` + + // +kubebuilder:validation:Optional + NodeSelector map[string]string `json:"nodeSelector,omitempty"` + + // +kubebuilder:validation:Optional + PriorityClassName string `json:"priorityClassName,omitempty"` + + // agent.pod.tolerations are tolerations to influence agent pod assignment. + // +kubebuilder:validation:Optional + Tolerations []corev1.Toleration `json:"tolerations,omitempty"` + + // agent.pod.affinity are affinities to influence agent pod assignment. + // https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ + // +kubebuilder:validation:Optional + Affinity corev1.Affinity `json:"affinity,omitempty"` } type KubernetesDeploymentSpec struct { // Specify if separate deployment of the Kubernetes Sensor should be enabled. Enabled `json:",inline"` - // Specify the number of replicas for the Kubernetes Sensor. - // +kubebuilder:validation:Optional - Replicas int `json:"replicas,omitempty"` - // The minimum number of seconds for which a newly created Pod should be ready without any of its containers crashing, for it to be considered available // +kubebuilder:validation:Optional MinReadySeconds int `json:"minReadySeconds,omitempty"` + // Specify the number of replicas for the Kubernetes Sensor. + // +kubebuilder:validation:Optional + Replicas int `json:"replicas,omitempty"` + // Override pod resource requirements for the Kubernetes Sensor pods. // +kubebuilder:validation:Optional - Pod coreV1.ResourceRequirements `json:"pod,omitempty"` + Pod KubernetesPodSpec `json:"pod,omitempty"` } type OpenTelemetry struct { // Deprecated setting for backwards compatibility - Enabled `json:",inline"` + Enabled `json:",inline" yaml:",inline"` // +kubebuilder:validation:Optional - GRPC Enabled `json:"grpc,omitempty"` + GRPC *Enabled `json:"grpc,omitempty" yaml:"grpc,omitempty"` // +kubebuilder:validation:Optional - HTTP Enabled `json:"http,omitempty"` + HTTP *Enabled `json:"http,omitempty" yaml:"http,omitempty"` +} + +func (otlp OpenTelemetry) GrpcIsEnabled() bool { + switch otlp.GRPC { + case nil: + return pointer.DerefOrEmpty(otlp.Enabled.Enabled) + default: + return pointer.DerefOrDefault(otlp.GRPC.Enabled, true) + } +} + +func (otlp OpenTelemetry) HttpIsEnabled() bool { + switch otlp.HTTP { + case nil: + return false + default: + return pointer.DerefOrDefault(otlp.HTTP.Enabled, true) + } +} + +func (otlp OpenTelemetry) IsEnabled() bool { + return otlp.GrpcIsEnabled() || otlp.HttpIsEnabled() +} + +type Zone struct { + // +kubebuilder:validation:Optional + Name `json:",inline"` + // +kubebuilder:validation:Optional + Tolerations []corev1.Toleration `json:"tolerations,omitempty"` + // +kubebuilder:validation:Optional + Affinity corev1.Affinity `json:"affinity,omitempty"` + // +kubebuilder:validation:Optional + Mode AgentMode `json:"mode,omitempty"` } diff --git a/api/v1/inline_types_test.go b/api/v1/inline_types_test.go new file mode 100644 index 00000000..de8a21d3 --- /dev/null +++ b/api/v1/inline_types_test.go @@ -0,0 +1,257 @@ +package v1 + +import ( + "encoding/json" + "fmt" + "testing" + + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + + "github.com/instana/instana-agent-operator/pkg/map_defaulter" + "github.com/instana/instana-agent-operator/pkg/optional" + "github.com/instana/instana-agent-operator/pkg/or_die" + "github.com/instana/instana-agent-operator/pkg/pointer" +) + +func TestImageSpec_Image(t *testing.T) { + for _, test := range []struct { + name string + ImageSpec + expected string + }{ + { + name: "with_digest", + ImageSpec: ImageSpec{ + Name: "icr.io/instana/instana-agent-operator", + Digest: "sha256:61417f330b2eb7eff88ccb9812921b65a31bf350fe9efdcb6663a29759c47fe4", + }, + expected: "icr.io/instana/instana-agent-operator@sha256:61417f330b2eb7eff88ccb9812921b65a31bf350fe9efdcb6663a29759c47fe4", + }, + { + name: "with_digest_and_tag", + ImageSpec: ImageSpec{ + Name: "icr.io/instana/instana-agent-operator", + Digest: "sha256:61417f330b2eb7eff88ccb9812921b65a31bf350fe9efdcb6663a29759c47fe4", + Tag: "2.0.10", + }, + expected: "icr.io/instana/instana-agent-operator@sha256:61417f330b2eb7eff88ccb9812921b65a31bf350fe9efdcb6663a29759c47fe4", + }, + { + name: "with_tag", + ImageSpec: ImageSpec{ + Name: "icr.io/instana/instana-agent-operator", + Tag: "2.0.10", + }, + expected: "icr.io/instana/instana-agent-operator:2.0.10", + }, + { + name: "with_name_only", + ImageSpec: ImageSpec{ + Name: "icr.io/instana/instana-agent-operator:2.0.10", + }, + expected: "icr.io/instana/instana-agent-operator:2.0.10", + }, + } { + t.Run( + test.name, func(t *testing.T) { + assertions := require.New(t) + + actual := test.ImageSpec.Image() + + assertions.Equal(test.expected, actual) + }, + ) + } +} + +var ( + allPossibleEnabled = []Enabled{ + {}, + { + Enabled: pointer.To(true), + }, + { + Enabled: pointer.To(false), + }, + } + allPossibleEnabledPtr = []*Enabled{ + nil, + {}, + { + Enabled: pointer.To(true), + }, + { + Enabled: pointer.To(false), + }, + } +) + +func jsonStringOrDie(obj interface{}) string { + return string( + or_die.New[[]byte]().ResultOrDie( + func() ([]byte, error) { + return json.Marshal(obj) + }, + ), + ) +} + +func testForOtlp(t *testing.T, otlp *OpenTelemetry, getExpected func(otlp *OpenTelemetry) bool, getActual func() bool) { + t.Run( + jsonStringOrDie(otlp), func(t *testing.T) { + assertions := require.New(t) + + expected := getExpected(otlp) + actual := getActual() + + assertions.Equal(expected, actual) + }, + ) +} + +func grpcIsEnabled_expected(otlp *OpenTelemetry) bool { + switch grpc := otlp.GRPC; grpc { + case nil: + switch enabled := otlp.Enabled.Enabled; enabled { + case nil: + return false + default: + return *enabled + } + default: + switch enabled := grpc.Enabled; enabled { + case nil: + return true + default: + return *enabled + } + } +} + +func TestOpenTelemetry_GrpcIsEnabled(t *testing.T) { + for _, enabled := range allPossibleEnabled { + for _, grpc := range allPossibleEnabledPtr { + otlp := &OpenTelemetry{ + Enabled: enabled, + GRPC: grpc, + } + testForOtlp(t, otlp, grpcIsEnabled_expected, otlp.GrpcIsEnabled) + } + } +} + +func httpIsEnabled_expected(otlp *OpenTelemetry) bool { + switch http := otlp.HTTP; http { + case nil: + return false + default: + switch enabled := http.Enabled; enabled { + case nil: + return true + default: + return *enabled + } + } +} + +func TestOpenTelemetry_HttpIsEnabled(t *testing.T) { + for _, http := range allPossibleEnabledPtr { + otlp := &OpenTelemetry{ + HTTP: http, + } + testForOtlp(t, otlp, httpIsEnabled_expected, otlp.HttpIsEnabled) + } +} + +func isEnabled_expected(otlp *OpenTelemetry) bool { + return grpcIsEnabled_expected(otlp) || httpIsEnabled_expected(otlp) +} + +func TestOpenTelemetry_IsEnabled(t *testing.T) { + for _, enabled := range allPossibleEnabled { + for _, grpc := range allPossibleEnabledPtr { + for _, http := range allPossibleEnabledPtr { + otlp := &OpenTelemetry{ + Enabled: enabled, + GRPC: grpc, + HTTP: http, + } + testForOtlp(t, otlp, isEnabled_expected, otlp.IsEnabled) + } + } + } +} + +func TestDaemonSetBuilder_getResourceRequirements(t *testing.T) { + metaAssertions := require.New(t) + + type testParams struct { + providedMemRequest string + providedCpuRequest string + providedMemLimit string + providedCpuLimit string + + expectedMemRequest string + expectedCpuRequest string + expectedMemLimit string + expectedCpuLimit string + } + + tests := make([]testParams, 0, 16) + for _, providedMemRequest := range []string{"", "123Mi"} { + for _, providedCpuRequest := range []string{"", "1.2"} { + for _, providedMemLimit := range []string{"", "456Mi"} { + for _, providedCpuLimit := range []string{"", "4.5"} { + tests = append( + tests, testParams{ + expectedMemRequest: optional.Of(providedMemRequest).GetOrDefault("512Mi"), + expectedCpuRequest: optional.Of(providedCpuRequest).GetOrDefault("0.5"), + expectedMemLimit: optional.Of(providedMemLimit).GetOrDefault("768Mi"), + expectedCpuLimit: optional.Of(providedCpuLimit).GetOrDefault("1.5"), + + providedMemRequest: providedMemRequest, + providedCpuRequest: providedCpuRequest, + providedMemLimit: providedMemLimit, + providedCpuLimit: providedCpuLimit, + }, + ) + } + } + } + } + + metaAssertions.Len(tests, 16) + + for _, test := range tests { + t.Run( + fmt.Sprintf("%+v", test), func(t *testing.T) { + assertions := require.New(t) + + provided := ResourceRequirements{} + + setIfNotEmpty := func(providedVal string, key corev1.ResourceName, resourceList *corev1.ResourceList) { + if providedVal != "" { + map_defaulter.NewMapDefaulter((*map[corev1.ResourceName]resource.Quantity)(resourceList)).SetIfEmpty( + key, + resource.MustParse(providedVal), + ) + } + } + + setIfNotEmpty(test.providedMemLimit, corev1.ResourceMemory, &provided.Limits) + setIfNotEmpty(test.providedCpuLimit, corev1.ResourceCPU, &provided.Limits) + setIfNotEmpty(test.providedMemRequest, corev1.ResourceMemory, &provided.Requests) + setIfNotEmpty(test.providedCpuRequest, corev1.ResourceCPU, &provided.Requests) + + actual := provided.GetOrDefault() + + assertions.Equal(resource.MustParse(test.expectedMemLimit), actual.Limits[corev1.ResourceMemory]) + assertions.Equal(resource.MustParse(test.expectedCpuLimit), actual.Limits[corev1.ResourceCPU]) + assertions.Equal(resource.MustParse(test.expectedMemRequest), actual.Requests[corev1.ResourceMemory]) + assertions.Equal(resource.MustParse(test.expectedCpuRequest), actual.Requests[corev1.ResourceCPU]) + }, + ) + } +} diff --git a/api/v1/instanaagent_types.go b/api/v1/instanaagent_types.go index 0fdf0f0f..c5bf5826 100644 --- a/api/v1/instanaagent_types.go +++ b/api/v1/instanaagent_types.go @@ -6,11 +6,17 @@ package v1 import ( - "k8s.io/apimachinery/pkg/api/resource" + "github.com/Masterminds/semver/v3" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/intstr" + + "github.com/instana/instana-agent-operator/pkg/optional" + "github.com/instana/instana-agent-operator/pkg/pointer" ) -//+k8s:openapi-gen=true +// +k8s:openapi-gen=true // InstanaAgentSpec defines the desired state of the Instana Agent type InstanaAgentSpec struct { @@ -26,9 +32,9 @@ type InstanaAgentSpec struct { Zone Name `json:"zone,omitempty"` // Set to `True` to indicate the Operator is being deployed in a OpenShift cluster. Provides a hint so that RBAC etc is - // configured correctly. + // configured correctly. Will attempt to auto-detect if unset. // +kubebuilder:validation:Optional - OpenShift bool `json:"openshift,omitempty"` + OpenShift *bool `json:"openshift,omitempty"` // Specifies whether RBAC resources should be created. // +kubebuilder:validation:Optional @@ -70,41 +76,12 @@ type InstanaAgentSpec struct { // +kubebuilder:validation:Optional PinnedChartVersion string `json:"pinnedChartVersion,omitempty"` - // - // OLD v1beta1 spec which, by including temporarily, we can provide backwards compatibility for the v1beta1 spec as served - // by the Java Operator. Prevents having to modify the CR outside the Operator. - // - - ConfigurationFiles map[string]string `json:"config.files,omitempty"` - AgentZoneName string `json:"agent.zone.name,omitempty"` - AgentKey string `json:"agent.key,omitempty"` - AgentEndpointHost string `json:"agent.endpoint.host,omitempty"` - AgentEndpointPort uint16 `json:"agent.endpoint.port,omitempty"` - AgentClusterRoleName string `json:"agent.clusterRoleName,omitempty"` - AgentClusterRoleBindingName string `json:"agent.clusterRoleBindingName,omitempty"` - AgentServiceAccountName string `json:"agent.serviceAccountName,omitempty"` - AgentSecretName string `json:"agent.secretName,omitempty"` - AgentDaemonSetName string `json:"agent.daemonSetName,omitempty"` - AgentConfigMapName string `json:"agent.configMapName,omitempty"` - AgentRbacCreate bool `json:"agent.rbac.create,omitempty"` - AgentImageName string `json:"agent.image,omitempty"` - AgentImagePullPolicy string `json:"agent.imagePullPolicy,omitempty"` - AgentCpuReq resource.Quantity `json:"agent.cpuReq,omitempty"` - AgentCpuLim resource.Quantity `json:"agent.cpuLimit,omitempty"` - AgentMemReq resource.Quantity `json:"agent.memReq,omitempty"` - AgentMemLim resource.Quantity `json:"agent.memLimit,omitempty"` - AgentDownloadKey string `json:"agent.downloadKey,omitempty"` - AgentRepository string `json:"agent.host.repository,omitempty"` - AgentTlsSecretName string `json:"agent.tls.secretName,omitempty"` - AgentTlsCertificate string `json:"agent.tls.certificate,omitempty"` - AgentTlsKey string `json:"agent.tls.key,omitempty"` - OpenTelemetryEnabled bool `json:"opentelemetry.enabled,omitempty"` - ClusterName string `json:"cluster.name,omitempty"` - AgentEnv map[string]string `json:"agent.env,omitempty"` - // END of OLD spec + // Zones can be used to specify agents in multiple zones split across different nodes in the cluster + // +kubebuilder:validation:Optional + Zones []Zone `json:"zones,omitempty"` } -//+k8s:openapi-gen=true +// +k8s:openapi-gen=true // ResourceInfo holds Name and UID to given object type ResourceInfo struct { @@ -124,10 +101,12 @@ const ( OperatorStateFailed AgentOperatorState = "Failed" ) -//+k8s:openapi-gen=true +// +k8s:openapi-gen=true // InstanaAgentStatus defines the observed state of InstanaAgent -type InstanaAgentStatus struct { + +// Deprecated: DeprecatedInstanaAgentStatus are the previous status fields that will be used to ensure backwards compatibility with any automation that may exist +type DeprecatedInstanaAgentStatus struct { Status AgentOperatorState `json:"status,omitempty"` Reason string `json:"reason,omitempty"` LastUpdate metav1.Time `json:"lastUpdate,omitempty"` @@ -139,12 +118,31 @@ type InstanaAgentStatus struct { LeadingAgentPod map[string]ResourceInfo `json:"leadingAgentPod,omitempty"` } -//+kubebuilder:object:root=true -//+k8s:openapi-gen=true -//+kubebuilder:subresource:status -//+kubebuilder:resource:path=agents,singular=agent,shortName=ia,scope=Namespaced,categories=monitoring;openshift-optional -//+kubebuilder:storageversion -//+operator-sdk:csv:customresourcedefinitions:displayName="Instana Agent", resources={{DaemonSet,v1,instana-agent},{Pod,v1,instana-agent},{Secret,v1,instana-agent}} +// +kubebuilder:validation:Type=string +// +kubebuilder:validation:Pattern=`^v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$` + +type SemanticVersion struct { + semver.Version `json:"-"` +} + +type InstanaAgentStatus struct { + DeprecatedInstanaAgentStatus `json:",inline"` + // +patchMergeKey=type + // +patchStrategy=merge + // +listType=map + // +listMapKey=type + Conditions []metav1.Condition `json:"conditions,omitempty"` + // +kubebuilder:validation:Minimum=0 + ObservedGeneration *int64 `json:"observedGeneration,omitempty"` + OperatorVersion *SemanticVersion `json:"operatorVersion,omitempty"` +} + +// +kubebuilder:object:root=true +// +k8s:openapi-gen=true +// +kubebuilder:subresource:status +// +kubebuilder:resource:path=agents,singular=agent,shortName=ia,scope=Namespaced,categories=monitoring;openshift-optional +// +kubebuilder:storageversion +// +operator-sdk:csv:customresourcedefinitions:displayName="Instana Agent", resources={{DaemonSet,v1,instana-agent},{Pod,v1,instana-agent},{Secret,v1,instana-agent}} // InstanaAgent is the Schema for the agents API type InstanaAgent struct { @@ -155,7 +153,25 @@ type InstanaAgent struct { Status InstanaAgentStatus `json:"status,omitempty"` } -//+kubebuilder:object:root=true +func (in *InstanaAgent) Default() { + optional.ValueOrDefault(&in.Spec.Agent.EndpointHost, "ingress-red-saas.instana.io") + optional.ValueOrDefault(&in.Spec.Agent.EndpointPort, "443") + optional.ValueOrDefault(&in.Spec.Agent.ImageSpec.Name, "icr.io/instana/agent") + optional.ValueOrDefault(&in.Spec.Agent.ImageSpec.Tag, "latest") + optional.ValueOrDefault(&in.Spec.Agent.ImageSpec.PullPolicy, corev1.PullAlways) + optional.ValueOrDefault(&in.Spec.Agent.UpdateStrategy.Type, appsv1.RollingUpdateDaemonSetStrategyType) + optional.ValueOrDefault(&in.Spec.Agent.UpdateStrategy.RollingUpdate, &appsv1.RollingUpdateDaemonSet{}) + optional.ValueOrDefault(&in.Spec.Agent.UpdateStrategy.RollingUpdate.MaxUnavailable, pointer.To(intstr.FromInt(1))) + optional.ValueOrDefault(&in.Spec.Rbac.Create, pointer.To(true)) + optional.ValueOrDefault(&in.Spec.Service.Create, pointer.To(true)) + optional.ValueOrDefault(&in.Spec.ServiceAccountSpec.Create.Create, pointer.To(true)) + optional.ValueOrDefault(&in.Spec.K8sSensor.ImageSpec.Name, "icr.io/instana/k8sensor") + optional.ValueOrDefault(&in.Spec.K8sSensor.ImageSpec.Tag, "latest") + optional.ValueOrDefault(&in.Spec.K8sSensor.ImageSpec.PullPolicy, corev1.PullAlways) + optional.ValueOrDefault(&in.Spec.K8sSensor.DeploymentSpec.Replicas, 3) +} + +// +kubebuilder:object:root=true // InstanaAgentList contains a list of InstanaAgent type InstanaAgentList struct { diff --git a/api/v1/instanaagent_types_test.go b/api/v1/instanaagent_types_test.go new file mode 100644 index 00000000..7b5c4740 --- /dev/null +++ b/api/v1/instanaagent_types_test.go @@ -0,0 +1,126 @@ +package v1 + +import ( + "testing" + + "github.com/stretchr/testify/require" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/util/intstr" + + "github.com/instana/instana-agent-operator/pkg/pointer" +) + +func TestInstanaAgent_Default(t *testing.T) { + withOverrides := InstanaAgentSpec{ + Agent: BaseAgentSpec{ + EndpointHost: "abc", + EndpointPort: "123", + ExtendedImageSpec: ExtendedImageSpec{ + ImageSpec: ImageSpec{ + Name: "icr.io/instana/asdf", + Tag: "1.1", + PullPolicy: corev1.PullIfNotPresent, + }, + }, + UpdateStrategy: appsv1.DaemonSetUpdateStrategy{ + Type: appsv1.OnDeleteDaemonSetStrategyType, + RollingUpdate: &appsv1.RollingUpdateDaemonSet{ + MaxUnavailable: pointer.To(intstr.FromInt(2)), + }, + }, + }, + Rbac: Create{ + Create: pointer.To(false), + }, + Service: Create{ + Create: pointer.To(false), + }, + ServiceAccountSpec: ServiceAccountSpec{ + Create: Create{ + Create: pointer.To(false), + }, + }, + K8sSensor: K8sSpec{ + ImageSpec: ImageSpec{ + Name: "icr.io/instana/qwerty", + Tag: "2.2", + PullPolicy: corev1.PullNever, + }, + DeploymentSpec: KubernetesDeploymentSpec{ + Replicas: 2, + }, + }, + } + + tests := []struct { + name string + spec *InstanaAgentSpec + expected *InstanaAgentSpec + }{ + { + name: "no_user_overrides", + spec: &InstanaAgentSpec{}, + expected: &InstanaAgentSpec{ + Agent: BaseAgentSpec{ + EndpointHost: "ingress-red-saas.instana.io", + EndpointPort: "443", + ExtendedImageSpec: ExtendedImageSpec{ + ImageSpec: ImageSpec{ + Name: "icr.io/instana/agent", + Tag: "latest", + PullPolicy: corev1.PullAlways, + }, + }, + UpdateStrategy: appsv1.DaemonSetUpdateStrategy{ + Type: appsv1.RollingUpdateDaemonSetStrategyType, + RollingUpdate: &appsv1.RollingUpdateDaemonSet{ + MaxUnavailable: pointer.To(intstr.FromInt(1)), + }, + }, + }, + Rbac: Create{ + Create: pointer.To(true), + }, + Service: Create{ + Create: pointer.To(true), + }, + ServiceAccountSpec: ServiceAccountSpec{ + Create: Create{ + Create: pointer.To(true), + }, + }, + K8sSensor: K8sSpec{ + ImageSpec: ImageSpec{ + Name: "icr.io/instana/k8sensor", + Tag: "latest", + PullPolicy: corev1.PullAlways, + }, + DeploymentSpec: KubernetesDeploymentSpec{ + Replicas: 3, + }, + }, + }, + }, + { + name: "all_overrides", + spec: withOverrides.DeepCopy(), + expected: withOverrides.DeepCopy(), + }, + } + for _, tt := range tests { + t.Run( + tt.name, func(t *testing.T) { + assertions := require.New(t) + + agent := &InstanaAgent{ + Spec: *tt.spec, + } + + agent.Default() + + assertions.Equal(&InstanaAgent{Spec: *tt.expected}, agent) + }, + ) + } +} diff --git a/api/v1/zz_generated.deepcopy.go b/api/v1/zz_generated.deepcopy.go index 0fc79d99..79f1fb78 100644 --- a/api/v1/zz_generated.deepcopy.go +++ b/api/v1/zz_generated.deepcopy.go @@ -23,6 +23,7 @@ package v1 import ( corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" ) @@ -52,6 +53,13 @@ func (in *AgentPodSpec) DeepCopyInto(out *AgentPodSpec) { } in.Affinity.DeepCopyInto(&out.Affinity) in.ResourceRequirements.DeepCopyInto(&out.ResourceRequirements) + if in.NodeSelector != nil { + in, out := &in.NodeSelector, &out.NodeSelector + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AgentPodSpec. @@ -87,8 +95,8 @@ func (in *BaseAgentSpec) DeepCopyInto(out *BaseAgentSpec) { *out = make([]BackendSpec, len(*in)) copy(*out, *in) } - out.TlsSpec = in.TlsSpec - in.ImageSpec.DeepCopyInto(&out.ImageSpec) + in.TlsSpec.DeepCopyInto(&out.TlsSpec) + in.ExtendedImageSpec.DeepCopyInto(&out.ExtendedImageSpec) in.UpdateStrategy.DeepCopyInto(&out.UpdateStrategy) in.Pod.DeepCopyInto(&out.Pod) if in.Env != nil { @@ -98,7 +106,6 @@ func (in *BaseAgentSpec) DeepCopyInto(out *BaseAgentSpec) { (*out)[key] = val } } - out.Configuration = in.Configuration out.Host = in.Host } @@ -113,31 +120,46 @@ func (in *BaseAgentSpec) DeepCopy() *BaseAgentSpec { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ConfigurationSpec) DeepCopyInto(out *ConfigurationSpec) { +func (in *Create) DeepCopyInto(out *Create) { *out = *in + if in.Create != nil { + in, out := &in.Create, &out.Create + *out = new(bool) + **out = **in + } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConfigurationSpec. -func (in *ConfigurationSpec) DeepCopy() *ConfigurationSpec { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Create. +func (in *Create) DeepCopy() *Create { if in == nil { return nil } - out := new(ConfigurationSpec) + out := new(Create) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Create) DeepCopyInto(out *Create) { +func (in *DeprecatedInstanaAgentStatus) DeepCopyInto(out *DeprecatedInstanaAgentStatus) { *out = *in + in.LastUpdate.DeepCopyInto(&out.LastUpdate) + out.ConfigMap = in.ConfigMap + out.DaemonSet = in.DaemonSet + if in.LeadingAgentPod != nil { + in, out := &in.LeadingAgentPod, &out.LeadingAgentPod + *out = make(map[string]ResourceInfo, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Create. -func (in *Create) DeepCopy() *Create { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DeprecatedInstanaAgentStatus. +func (in *DeprecatedInstanaAgentStatus) DeepCopy() *DeprecatedInstanaAgentStatus { if in == nil { return nil } - out := new(Create) + out := new(DeprecatedInstanaAgentStatus) in.DeepCopyInto(out) return out } @@ -145,6 +167,11 @@ func (in *Create) DeepCopy() *Create { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Enabled) DeepCopyInto(out *Enabled) { *out = *in + if in.Enabled != nil { + in, out := &in.Enabled, &out.Enabled + *out = new(bool) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Enabled. @@ -157,6 +184,27 @@ func (in *Enabled) DeepCopy() *Enabled { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ExtendedImageSpec) DeepCopyInto(out *ExtendedImageSpec) { + *out = *in + out.ImageSpec = in.ImageSpec + if in.PullSecrets != nil { + in, out := &in.PullSecrets, &out.PullSecrets + *out = make([]corev1.LocalObjectReference, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExtendedImageSpec. +func (in *ExtendedImageSpec) DeepCopy() *ExtendedImageSpec { + if in == nil { + return nil + } + out := new(ExtendedImageSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *HostSpec) DeepCopyInto(out *HostSpec) { *out = *in @@ -175,11 +223,6 @@ func (in *HostSpec) DeepCopy() *HostSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ImageSpec) DeepCopyInto(out *ImageSpec) { *out = *in - if in.PullSecrets != nil { - in, out := &in.PullSecrets, &out.PullSecrets - *out = make([]PullSecretSpec, len(*in)) - copy(*out, *in) - } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ImageSpec. @@ -257,30 +300,24 @@ func (in *InstanaAgentSpec) DeepCopyInto(out *InstanaAgentSpec) { in.Agent.DeepCopyInto(&out.Agent) out.Cluster = in.Cluster out.Zone = in.Zone - out.Rbac = in.Rbac - out.Service = in.Service - out.OpenTelemetry = in.OpenTelemetry - out.Prometheus = in.Prometheus - out.ServiceAccountSpec = in.ServiceAccountSpec - out.PodSecurityPolicySpec = in.PodSecurityPolicySpec + if in.OpenShift != nil { + in, out := &in.OpenShift, &out.OpenShift + *out = new(bool) + **out = **in + } + in.Rbac.DeepCopyInto(&out.Rbac) + in.Service.DeepCopyInto(&out.Service) + in.OpenTelemetry.DeepCopyInto(&out.OpenTelemetry) + in.Prometheus.DeepCopyInto(&out.Prometheus) + in.ServiceAccountSpec.DeepCopyInto(&out.ServiceAccountSpec) + in.PodSecurityPolicySpec.DeepCopyInto(&out.PodSecurityPolicySpec) in.KubernetesSpec.DeepCopyInto(&out.KubernetesSpec) in.K8sSensor.DeepCopyInto(&out.K8sSensor) - if in.ConfigurationFiles != nil { - in, out := &in.ConfigurationFiles, &out.ConfigurationFiles - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } - out.AgentCpuReq = in.AgentCpuReq.DeepCopy() - out.AgentCpuLim = in.AgentCpuLim.DeepCopy() - out.AgentMemReq = in.AgentMemReq.DeepCopy() - out.AgentMemLim = in.AgentMemLim.DeepCopy() - if in.AgentEnv != nil { - in, out := &in.AgentEnv, &out.AgentEnv - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val + if in.Zones != nil { + in, out := &in.Zones, &out.Zones + *out = make([]Zone, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) } } } @@ -298,16 +335,24 @@ func (in *InstanaAgentSpec) DeepCopy() *InstanaAgentSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *InstanaAgentStatus) DeepCopyInto(out *InstanaAgentStatus) { *out = *in - in.LastUpdate.DeepCopyInto(&out.LastUpdate) - out.ConfigMap = in.ConfigMap - out.DaemonSet = in.DaemonSet - if in.LeadingAgentPod != nil { - in, out := &in.LeadingAgentPod, &out.LeadingAgentPod - *out = make(map[string]ResourceInfo, len(*in)) - for key, val := range *in { - (*out)[key] = val + in.DeprecatedInstanaAgentStatus.DeepCopyInto(&out.DeprecatedInstanaAgentStatus) + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]metav1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.ObservedGeneration != nil { + in, out := &in.ObservedGeneration, &out.ObservedGeneration + *out = new(int64) + **out = **in + } + if in.OperatorVersion != nil { + in, out := &in.OperatorVersion, &out.OperatorVersion + *out = new(SemanticVersion) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InstanaAgentStatus. @@ -324,7 +369,8 @@ func (in *InstanaAgentStatus) DeepCopy() *InstanaAgentStatus { func (in *K8sSpec) DeepCopyInto(out *K8sSpec) { *out = *in in.DeploymentSpec.DeepCopyInto(&out.DeploymentSpec) - in.ImageSpec.DeepCopyInto(&out.ImageSpec) + out.ImageSpec = in.ImageSpec + in.PodDisruptionBudget.DeepCopyInto(&out.PodDisruptionBudget) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new K8sSpec. @@ -340,7 +386,7 @@ func (in *K8sSpec) DeepCopy() *K8sSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *KubernetesDeploymentSpec) DeepCopyInto(out *KubernetesDeploymentSpec) { *out = *in - out.Enabled = in.Enabled + in.Enabled.DeepCopyInto(&out.Enabled) in.Pod.DeepCopyInto(&out.Pod) } @@ -354,6 +400,37 @@ func (in *KubernetesDeploymentSpec) DeepCopy() *KubernetesDeploymentSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *KubernetesPodSpec) DeepCopyInto(out *KubernetesPodSpec) { + *out = *in + in.ResourceRequirements.DeepCopyInto(&out.ResourceRequirements) + if in.NodeSelector != nil { + in, out := &in.NodeSelector, &out.NodeSelector + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.Tolerations != nil { + in, out := &in.Tolerations, &out.Tolerations + *out = make([]corev1.Toleration, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + in.Affinity.DeepCopyInto(&out.Affinity) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubernetesPodSpec. +func (in *KubernetesPodSpec) DeepCopy() *KubernetesPodSpec { + if in == nil { + return nil + } + out := new(KubernetesPodSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *KubernetesSpec) DeepCopyInto(out *KubernetesSpec) { *out = *in @@ -388,9 +465,17 @@ func (in *Name) DeepCopy() *Name { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *OpenTelemetry) DeepCopyInto(out *OpenTelemetry) { *out = *in - out.Enabled = in.Enabled - out.GRPC = in.GRPC - out.HTTP = in.HTTP + in.Enabled.DeepCopyInto(&out.Enabled) + if in.GRPC != nil { + in, out := &in.GRPC, &out.GRPC + *out = new(Enabled) + (*in).DeepCopyInto(*out) + } + if in.HTTP != nil { + in, out := &in.HTTP, &out.HTTP + *out = new(Enabled) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OpenTelemetry. @@ -406,7 +491,7 @@ func (in *OpenTelemetry) DeepCopy() *OpenTelemetry { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PodSecurityPolicySpec) DeepCopyInto(out *PodSecurityPolicySpec) { *out = *in - out.Enabled = in.Enabled + in.Enabled.DeepCopyInto(&out.Enabled) out.Name = in.Name } @@ -423,7 +508,7 @@ func (in *PodSecurityPolicySpec) DeepCopy() *PodSecurityPolicySpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Prometheus) DeepCopyInto(out *Prometheus) { *out = *in - out.RemoteWrite = in.RemoteWrite + in.RemoteWrite.DeepCopyInto(&out.RemoteWrite) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Prometheus. @@ -437,32 +522,66 @@ func (in *Prometheus) DeepCopy() *Prometheus { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PullSecretSpec) DeepCopyInto(out *PullSecretSpec) { +func (in *ResourceInfo) DeepCopyInto(out *ResourceInfo) { *out = *in - out.Name = in.Name } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PullSecretSpec. -func (in *PullSecretSpec) DeepCopy() *PullSecretSpec { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceInfo. +func (in *ResourceInfo) DeepCopy() *ResourceInfo { if in == nil { return nil } - out := new(PullSecretSpec) + out := new(ResourceInfo) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ResourceInfo) DeepCopyInto(out *ResourceInfo) { +func (in *ResourceRequirements) DeepCopyInto(out *ResourceRequirements) { *out = *in + if in.Limits != nil { + in, out := &in.Limits, &out.Limits + *out = make(corev1.ResourceList, len(*in)) + for key, val := range *in { + (*out)[key] = val.DeepCopy() + } + } + if in.Requests != nil { + in, out := &in.Requests, &out.Requests + *out = make(corev1.ResourceList, len(*in)) + for key, val := range *in { + (*out)[key] = val.DeepCopy() + } + } + if in.Claims != nil { + in, out := &in.Claims, &out.Claims + *out = make([]corev1.ResourceClaim, len(*in)) + copy(*out, *in) + } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceInfo. -func (in *ResourceInfo) DeepCopy() *ResourceInfo { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceRequirements. +func (in *ResourceRequirements) DeepCopy() *ResourceRequirements { if in == nil { return nil } - out := new(ResourceInfo) + out := new(ResourceRequirements) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SemanticVersion) DeepCopyInto(out *SemanticVersion) { + *out = *in + out.Version = in.Version +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SemanticVersion. +func (in *SemanticVersion) DeepCopy() *SemanticVersion { + if in == nil { + return nil + } + out := new(SemanticVersion) in.DeepCopyInto(out) return out } @@ -470,8 +589,15 @@ func (in *ResourceInfo) DeepCopy() *ResourceInfo { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ServiceAccountSpec) DeepCopyInto(out *ServiceAccountSpec) { *out = *in - out.Create = in.Create + in.Create.DeepCopyInto(&out.Create) out.Name = in.Name + if in.Annotations != nil { + in, out := &in.Annotations, &out.Annotations + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceAccountSpec. @@ -487,6 +613,16 @@ func (in *ServiceAccountSpec) DeepCopy() *ServiceAccountSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TlsSpec) DeepCopyInto(out *TlsSpec) { *out = *in + if in.Certificate != nil { + in, out := &in.Certificate, &out.Certificate + *out = make([]byte, len(*in)) + copy(*out, *in) + } + if in.Key != nil { + in, out := &in.Key, &out.Key + *out = make([]byte, len(*in)) + copy(*out, *in) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TlsSpec. @@ -498,3 +634,27 @@ func (in *TlsSpec) DeepCopy() *TlsSpec { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Zone) DeepCopyInto(out *Zone) { + *out = *in + out.Name = in.Name + if in.Tolerations != nil { + in, out := &in.Tolerations, &out.Tolerations + *out = make([]corev1.Toleration, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + in.Affinity.DeepCopyInto(&out.Affinity) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Zone. +func (in *Zone) DeepCopy() *Zone { + if in == nil { + return nil + } + out := new(Zone) + in.DeepCopyInto(out) + return out +} diff --git a/ci/scripts/end-to-end-test.sh b/ci/scripts/end-to-end-test.sh index 5cc2880a..08257e44 100755 --- a/ci/scripts/end-to-end-test.sh +++ b/ci/scripts/end-to-end-test.sh @@ -7,10 +7,12 @@ set -e set -o pipefail +set -x POD_WAIT_TIME_OUT=120 # s Pod-check max waiting time POD_WAIT_INTERVAL=5 # s Pod-check interval time OPERATOR_LOG_LINE='Agent installed/upgraded successfully' +OPERATOR_LOG_LINE_NEW='successfully finished reconcile on agent CR' # Wait for a pod to be running # It uses the global variables: @@ -22,6 +24,7 @@ function wait_for_running_pod() { namespace="instana-agent" label=${1} deployment=${2} + pods_are_running=false status=$(kubectl get pod -n "${namespace}" -l="${label}" -o go-template='{{ range .items }}{{ println .status.phase }}{{ end }}' | uniq) echo "The status of pods from deployment ${deployment} in namespace ${namespace} is: \"$status\"" @@ -29,6 +32,7 @@ function wait_for_running_pod() { if [[ "${#status[@]}" -eq "1" && "${status[0]}" == "Running" ]]; then echo "The status of pods from deployment ${deployment} in namespace ${namespace} is: \"$status\". Ending waiting loop here." + pods_are_running=true break fi status=$(kubectl get pod -n "${namespace}" -o go-template='{{ range .items }}{{ println .status.phase }}{{ end }}'| uniq) @@ -36,7 +40,7 @@ function wait_for_running_pod() { ((timeout+=POD_WAIT_INTERVAL)) sleep $POD_WAIT_INTERVAL done - if [[ "${timeout}" -gt "${POD_WAIT_TIME_OUT}" ]]; then + if [[ "${pods_are_running}" == "false" ]]; then echo "${namespace} failed to initialize. Exceeded timeout of ${POD_WAIT_TIME_OUT} s. Exit here" exit 1 @@ -49,15 +53,20 @@ function wait_for_successfull_agent_installation() { local timeout=0 local namespace="instana-agent" local label="app.kubernetes.io/name=instana-agent-operator" + local agent_found=false #Workaround as grep will return -1 if the line is not found. #With pipefail enabled, this would fail the script if the if statement omitted. if ! crd_installed_successfully=$(kubectl logs -l=${label} -n ${namespace} --tail=-1 | grep "${OPERATOR_LOG_LINE}"); then - crd_installed_successfully="" + # Try to fetch the new log line if the old one is not there + if ! crd_installed_successfully=$(kubectl logs -l=${label} -n ${namespace} --tail=-1 | grep "${OPERATOR_LOG_LINE_NEW}"); then + crd_installed_successfully="" + fi fi while [[ "${timeout}" -le "${POD_WAIT_TIME_OUT}" ]]; do if [[ -n "${crd_installed_successfully}" ]]; then echo "The agent has been installed/upgraded successfully. Ending waiting loop here." + agent_found=true break fi ((timeout+=POD_WAIT_INTERVAL)) @@ -65,10 +74,13 @@ function wait_for_successfull_agent_installation() { #Workaround as grep will return -1 if the line is not found. #With pipefail enabled, this would fail the script if the if statement omitted. if ! crd_installed_successfully=$(kubectl logs -l=${label} -n ${namespace} --tail=-1 | grep "${OPERATOR_LOG_LINE}"); then - crd_installed_successfully="" + # Try to fetch the new log line if the old one is not there + if ! crd_installed_successfully=$(kubectl logs -l=${label} -n ${namespace} --tail=-1 | grep "${OPERATOR_LOG_LINE_NEW}"); then + crd_installed_successfully="" + fi fi done - if [[ "${timeout}" -gt "${POD_WAIT_TIME_OUT}" ]]; then + if [[ "${agent_found}" == "false" ]]; then echo "Agent failed to be installed/upgraded successfully. Exceeded timeout of ${POD_WAIT_TIME_OUT} s. Exit here" exit 1 fi diff --git a/config/manager/kustomization.yaml b/config/manager/kustomization.yaml index 0851b026..12de93c3 100644 --- a/config/manager/kustomization.yaml +++ b/config/manager/kustomization.yaml @@ -13,4 +13,4 @@ kind: Kustomization images: - name: instana/instana-agent-operator newName: icr.io/instana/instana-agent-operator - newTag: latest + newTag: snapshot diff --git a/config/manager/manager.yaml b/config/manager/manager.yaml index 4ce21b7a..0f750725 100644 --- a/config/manager/manager.yaml +++ b/config/manager/manager.yaml @@ -68,5 +68,5 @@ spec: requests: cpu: 200m memory: 200Mi - serviceAccountName: controller-manager + serviceAccountName: instana-agent-operator terminationGracePeriodSeconds: 10 diff --git a/config/manifests/bases/instana-agent-operator.clusterserviceversion.yaml b/config/manifests/bases/instana-agent-operator.clusterserviceversion.yaml index 964358fd..f1c96a7a 100644 --- a/config/manifests/bases/instana-agent-operator.clusterserviceversion.yaml +++ b/config/manifests/bases/instana-agent-operator.clusterserviceversion.yaml @@ -290,9 +290,9 @@ spec: - get - list - watch - serviceAccountName: controller-manager + serviceAccountName: instana-agent-operator deployments: - - name: controller-manager + - name: instana-agent-operator spec: replicas: 1 selector: @@ -321,11 +321,11 @@ spec: fieldPath: metadata.annotations['olm.targetNamespaces'] image: icr.io/instana/instana-agent-operator:0.0.0 imagePullPolicy: Always - name: controller-manager + name: instana-agent-operator ports: - containerPort: 8080 resources: {} - serviceAccountName: controller-manager + serviceAccountName: instana-agent-operator strategy: deployment installModes: - supported: true diff --git a/config/rbac/leader_election_role_binding.yaml b/config/rbac/leader_election_role_binding.yaml index 2ed5ccbb..6b36c0f1 100644 --- a/config/rbac/leader_election_role_binding.yaml +++ b/config/rbac/leader_election_role_binding.yaml @@ -8,5 +8,5 @@ roleRef: name: leader-election-role subjects: - kind: ServiceAccount - name: controller-manager + name: instana-agent-operator namespace: system diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index e67889ad..ecf4ba8a 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -1,198 +1,176 @@ +--- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: - creationTimestamp: null name: manager-role - namespace: instana-agent rules: +- nonResourceURLs: + - /healthz + - /version + verbs: + - get - apiGroups: - - instana.io + - apiextensions.k8s.io resources: - - agents + - customresourcedefinitions verbs: - - create - - delete - get - list - - patch - - update - watch - apiGroups: - - instana.io + - apps resources: - - agents/finalizers + - daemonsets + - deployments verbs: + - create + - delete + - get + - list + - patch - update + - watch - apiGroups: - - instana.io + - apps resources: - - agents/status + - daemonsets + - deployments + - replicasets + - statefulsets verbs: - get - - patch - - update + - list + - watch - apiGroups: - - '' - - 'extensions' - - 'apps' - - 'rbac.authorization.k8s.io' - resources: - - 'serviceaccounts' - - 'clusterroles' - - 'clusterrolebindings' - - 'secrets' - - 'configmaps' - - 'daemonsets' - - 'namespaces' - - 'services' - - 'deployments' - - 'nodes/stats' - - 'nodes/metrics' + - apps.openshift.io + resources: + - deploymentconfigs verbs: - - create - - delete - get - list - - patch - - update - watch -# ------------------------------------------------------------------------- -# The following need to be watched, but not created: -# * Pods are created by the daemon set. -# * The custom resource definition is created by the user. -# ------------------------------------------------------------------------- -- apiGroups: - - '' - - 'apiextensions.k8s.io' - resources: - - 'pods' - - 'customresourcedefinitions' - verbs: - - 'get' - - 'list' - - 'watch' -# ------------------------------------------------------------------------- -# Events are just created but not watched. It's a fire-and-forget operation. -# ------------------------------------------------------------------------- -- apiGroups: - - '' - resources: - - 'events' - verbs: - - 'create' -# ------------------------------------------------------------------------- -# Below are the permissions are for the agent. -# The operator needs these permissions to create the agent's cluster role. -# ------------------------------------------------------------------------- -- nonResourceURLs: - - '/version' - - '/healthz' - - "/metrics" - - "/stats/summary" - - "/metrics/cadvisor" - verbs: - - 'get' - apiGroups: - - '' + - autoscaling resources: - - 'componentstatuses' - - 'endpoints' - - 'events' - - 'namespaces' - - 'nodes' - - 'pods' - - 'replicationcontrollers' - - 'resourcequotas' - - 'services' - - 'persistentvolumes' - - 'persistentvolumeclaims' + - horizontalpodautoscalers verbs: - - 'get' - - 'list' - - 'watch' + - get + - list + - watch - apiGroups: - - '' + - batch resources: - - 'endpoints' + - cronjobs + - jobs verbs: - - 'create' - - 'update' - - 'patch' + - get + - list + - watch - apiGroups: - - 'apps' + - "" resources: - - 'deployments' - - 'replicasets' - - 'daemonsets' - - 'statefulsets' + - configmaps + - endpoints + - events + - namespaces + - nodes + - persistentvolumeclaims + - persistentvolumes + - pods + - pods/log + - replicationcontrollers + - resourcequotas + - services verbs: - - 'get' - - 'list' - - 'watch' + - get + - list + - watch - apiGroups: - - 'batch' + - "" resources: - - 'jobs' - - 'cronjobs' + - configmaps + - secrets + - serviceaccounts + - services verbs: - - 'get' - - 'list' - - 'watch' + - create + - delete + - get + - list + - patch + - update + - watch - apiGroups: - - 'extensions' + - extensions resources: - - 'deployments' - - 'ingresses' - - 'replicasets' + - deployments + - ingresses + - replicasets verbs: - - 'get' - - 'list' - - 'watch' + - get + - list + - watch - apiGroups: - - 'networking.k8s.io' + - instana.io resources: - - 'ingresses' + - agents verbs: - - 'get' - - 'list' - - 'watch' + - create + - delete + - get + - list + - patch + - update + - watch - apiGroups: - - 'apps.openshift.io' + - instana.io resources: - - 'deploymentconfigs' + - agents/finalizers verbs: - - 'get' - - 'list' - - 'watch' + - update - apiGroups: - - 'security.openshift.io' - resourceNames: - - 'privileged' + - instana.io resources: - - 'securitycontextconstraints' + - agents/status verbs: - - 'use' + - get + - patch + - update - apiGroups: - - autoscaling/v1 + - networking.k8s.io resources: - - horizontalpodautoscalers + - ingresses verbs: - get - list - watch - apiGroups: - - autoscaling/v2 + - policy + resourceNames: + - instana-agent-k8sensor resources: - - horizontalpodautoscalers + - podsecuritypolicies verbs: - - get - - list - - watch + - use - apiGroups: - - "" + - rbac.authorization.k8s.io resources: - - pods/log + - clusterrolebindings + - clusterroles verbs: + - bind + - create + - delete - get - list + - patch + - update - watch +- apiGroups: + - security.openshift.io + resourceNames: + - privileged + resources: + - securitycontextconstraints + verbs: + - use diff --git a/config/rbac/role_binding.yaml b/config/rbac/role_binding.yaml index 2070ede4..c329870d 100644 --- a/config/rbac/role_binding.yaml +++ b/config/rbac/role_binding.yaml @@ -8,5 +8,5 @@ roleRef: name: manager-role subjects: - kind: ServiceAccount - name: controller-manager + name: instana-agent-operator namespace: system diff --git a/config/rbac/service_account.yaml b/config/rbac/service_account.yaml index 7cd6025b..f346bf7d 100644 --- a/config/rbac/service_account.yaml +++ b/config/rbac/service_account.yaml @@ -1,5 +1,5 @@ apiVersion: v1 kind: ServiceAccount metadata: - name: controller-manager + name: instana-agent-operator namespace: system diff --git a/config/samples/instana_agent_namespace.yaml b/config/samples/instana_agent_namespace.yaml new file mode 100644 index 00000000..bd06c096 --- /dev/null +++ b/config/samples/instana_agent_namespace.yaml @@ -0,0 +1,6 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: instana-agent + labels: + name: instana-agent diff --git a/config/samples/instana_v1_extended_instanaagent.yaml b/config/samples/instana_v1_extended_instanaagent.yaml index d52bd817..db67ca52 100644 --- a/config/samples/instana_v1_extended_instanaagent.yaml +++ b/config/samples/instana_v1_extended_instanaagent.yaml @@ -31,13 +31,13 @@ spec: # TLS for end-to-end encryption between Instana agent and clients accessing the agent. # The Instana agent does not yet allow enforcing TLS encryption. # TLS is only enabled on a connection when requested by the client. - tls: + # tls: # In order to enable TLS, a secret of type kubernetes.io/tls must be specified. # secretName is the name of the secret that has the relevant files. - # secretName: null + # secretName: "" # Otherwise, the certificate and the private key must be provided as base64 encoded. - # certificate: null - # key: null + # certificate: "" + # key: "" image: # agent.image.name is the name of the container image of the Instana agent. @@ -77,7 +77,7 @@ spec: # agent.pod.priorityClassName is the name of an existing PriorityClass that should be set on the agent pods # https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/ - priorityClassName: null + # priorityClassName: "" # agent.pod.requests and agent.pod.limits adjusts the resource assignments for the DaemonSet agent # regardless of the kubernetes.deployment.enabled setting @@ -93,16 +93,13 @@ spec: cpu: "1.5" # agent.host.repository sets a host path to be mounted as the agent maven repository (for debugging or development purposes) - host: - repository: null + # host: + # repository: "" configuration_yaml: | # You can leave this empty, or use this to configure your instana agent. # See https://ibm.biz/monitoring-k8s - # Custom agent charts URL that can be used in air gapped envs - charts_url: some-link - # openshift specifies whether the cluster role should include openshift permissions and other tweaks to the YAML. # The chart will try to auto-detect if the cluster is OpenShift, so you will likely not even need to set this explicitly. # openshift: true @@ -159,10 +156,10 @@ spec: # k8s_sensor.deployment.pod.requests.memory is the requested memory allocation in MiB for the agent pods. memory: 128Mi # k8s_sensor.deployment.pod.requests.cpu are the requested CPU units allocation for the agent pods. - cpu: 10m + cpu: 120m limits: # k8s_sensor.deployment.pod.limits.memory set the memory allocation limits in MiB for the agent pods. - memory: 1536Mi + memory: 2048Mi # k8s_sensor.deployment.pod.limits.cpu sets the CPU units allocation limits for the agent pods. cpu: 500m affinity: diff --git a/config/samples/instana_v1_instanaagent.yaml b/config/samples/instana_v1_instanaagent.yaml index 31668d1f..91ead844 100644 --- a/config/samples/instana_v1_instanaagent.yaml +++ b/config/samples/instana_v1_instanaagent.yaml @@ -7,7 +7,7 @@ spec: zone: name: edited-zone # (optional) name of the zone of the host cluster: - name: my-cluster + name: my-cluster agent: key: replace-key # replace with your Instana agent key endpointHost: ingress-red-saas.instana.io @@ -15,4 +15,4 @@ spec: env: {} configuration_yaml: | # You can leave this empty, or use this to configure your instana agent. - # See https://ibm.biz/monitoring-k8s + # See https://github.com/instana/instana-agent-operator/blob/main/config/samples/instana_v1_extended_instanaagent.yaml for the extended version. diff --git a/controllers/agent.go b/controllers/agent.go new file mode 100644 index 00000000..c84d1942 --- /dev/null +++ b/controllers/agent.go @@ -0,0 +1,59 @@ +package controllers + +import ( + "context" + "errors" + + k8serrors "k8s.io/apimachinery/pkg/api/errors" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + logf "sigs.k8s.io/controller-runtime/pkg/log" + + instanav1 "github.com/instana/instana-agent-operator/api/v1" + instanaclient "github.com/instana/instana-agent-operator/pkg/k8s/client" +) + +func (r *InstanaAgentReconciler) getAgent(ctx context.Context, req ctrl.Request) ( + *instanav1.InstanaAgent, + reconcileReturn, +) { + var agent instanav1.InstanaAgent + + log := logf.FromContext(ctx) + + switch err := r.client.Get(ctx, req.NamespacedName, &agent); { + case k8serrors.IsNotFound(err): + log.V(10).Info("attempted to reconcile agent CR that could not be found") + return nil, reconcileSuccess(ctrl.Result{}) + case !errors.Is(err, nil): + log.Error(err, "failed to retrieve info about agent CR") + return nil, reconcileFailure(err) + default: + log.V(1).Info("successfully retrieved agent CR info") + return &agent, reconcileContinue() + } +} + +func (r *InstanaAgentReconciler) updateAgent( + ctx context.Context, + agentOld *instanav1.InstanaAgent, + agentNew *instanav1.InstanaAgent, +) reconcileReturn { + log := r.loggerFor(ctx, agentNew) + + switch err := r.client.Patch( + ctx, + agentNew, + client.MergeFrom(agentOld), + client.FieldOwner(instanaclient.FieldOwnerName), + ); errors.Is(err, nil) { + case true: + log.V(1).Info("successfully applied updates to agent CR") + return reconcileSuccess(ctrl.Result{Requeue: true}) + default: + if !k8serrors.IsNotFound(err) { + log.Error(err, "failed to apply updates to agent CR") + } + return reconcileFailure(err) + } +} diff --git a/controllers/apply.go b/controllers/apply.go new file mode 100644 index 00000000..977ebd7e --- /dev/null +++ b/controllers/apply.go @@ -0,0 +1,82 @@ +package controllers + +import ( + "context" + + instanav1 "github.com/instana/instana-agent-operator/api/v1" + agentconfigmap "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/agent/configmap" + agentdaemonset "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/agent/daemonset" + headlessservice "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/agent/headless-service" + containersinstanaiosecret "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/agent/secrets/containers-instana-io-secret" + keyssecret "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/agent/secrets/keys-secret" + tlssecret "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/agent/secrets/tls-secret" + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/agent/service" + agentserviceaccount "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/agent/serviceaccount" + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/builder" + k8ssensorconfigmap "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/k8s-sensor/configmap" + k8ssensordeployment "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/k8s-sensor/deployment" + k8ssensorpoddisruptionbudget "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/k8s-sensor/poddisruptionbudget" + k8ssensorrbac "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/k8s-sensor/rbac" + k8ssensorserviceaccount "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/k8s-sensor/serviceaccount" + "github.com/instana/instana-agent-operator/pkg/k8s/operator/operator_utils" + "github.com/instana/instana-agent-operator/pkg/k8s/operator/status" +) + +func getDaemonSetBuilders( + agent *instanav1.InstanaAgent, + isOpenShift bool, + statusManager status.AgentStatusManager, +) []builder.ObjectBuilder { + if len(agent.Spec.Zones) == 0 { + return []builder.ObjectBuilder{agentdaemonset.NewDaemonSetBuilder(agent, isOpenShift, statusManager)} + } + + builders := make([]builder.ObjectBuilder, 0, len(agent.Spec.Zones)) + + for _, zone := range agent.Spec.Zones { + builders = append( + builders, + agentdaemonset.NewDaemonSetBuilderWithZoneInfo(agent, isOpenShift, statusManager, &zone), + ) + } + + return builders +} + +func (r *InstanaAgentReconciler) applyResources( + ctx context.Context, + agent *instanav1.InstanaAgent, + isOpenShift bool, + operatorUtils operator_utils.OperatorUtils, + statusManager status.AgentStatusManager, +) reconcileReturn { + log := r.loggerFor(ctx, agent) + log.V(1).Info("applying Kubernetes resources for agent") + + builders := append( + getDaemonSetBuilders(agent, isOpenShift, statusManager), + agentconfigmap.NewConfigMapBuilder(agent, statusManager), + headlessservice.NewHeadlessServiceBuilder(agent), + containersinstanaiosecret.NewSecretBuilder(agent), + keyssecret.NewSecretBuilder(agent), + tlssecret.NewSecretBuilder(agent), + service.NewServiceBuilder(agent), + agentserviceaccount.NewServiceAccountBuilder(agent), + k8ssensorconfigmap.NewConfigMapBuilder(agent), + k8ssensordeployment.NewDeploymentBuilder(agent, isOpenShift, statusManager), + k8ssensorpoddisruptionbudget.NewPodDisruptionBudgetBuilder(agent), + k8ssensorrbac.NewClusterRoleBuilder(agent), + k8ssensorrbac.NewClusterRoleBindingBuilder(agent), + k8ssensorserviceaccount.NewServiceAccountBuilder(agent), + ) + + switch res := operatorUtils.ApplyAll(builders...); res.IsSuccess() { + case true: + log.V(1).Info("successfully applied kubernetes resources for agent") + return reconcileContinue() + default: + _, err := res.Get() + log.Error(err, "failed to apply kubernetes resources for agent") + return reconcileFailure(err) + } +} diff --git a/controllers/cleanup.go b/controllers/cleanup.go new file mode 100644 index 00000000..0e0c89b6 --- /dev/null +++ b/controllers/cleanup.go @@ -0,0 +1,76 @@ +package controllers + +import ( + "context" + "errors" + + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + + instanav1 "github.com/instana/instana-agent-operator/api/v1" + "github.com/instana/instana-agent-operator/controllers/reconciliation/helm" + "github.com/instana/instana-agent-operator/pkg/k8s/operator/operator_utils" +) + +func (r *InstanaAgentReconciler) cleanupHelmChart( + ctx context.Context, + agentOld *instanav1.InstanaAgent, +) reconcileReturn { + agentNew := agentOld.DeepCopy() + log := r.loggerFor(ctx, agentNew) + + if !controllerutil.RemoveFinalizer(agentNew, finalizerV1) { + log.V(2).Info("no deprecated Helm chart installation detected based on finalizers present on agent CR") + return reconcileContinue() + } else if err := helm.NewHelmReconciliation(r.scheme, log).Delete(); !errors.Is(err, nil) { + log.Error(err, "failed to uninstall deprecated Helm installation of Instana agent") + return reconcileFailure(err) + } else { + log.V(1).Info("successfully uninstalled deprecated Helm installation of Instana agent") + return r.updateAgent(ctx, agentOld, agentNew) + } +} + +func (r *InstanaAgentReconciler) cleanupDependents( + ctx context.Context, + agentOld *instanav1.InstanaAgent, + operatorUtils operator_utils.OperatorUtils, +) reconcileReturn { + agentNew := agentOld.DeepCopy() + log := r.loggerFor(ctx, agentNew) + + if !controllerutil.RemoveFinalizer(agentNew, finalizerV3) { + log.V(2).Info("agent finalizer not present, so no further cleanup is needed") + return reconcileContinue() + } else if deleteRes := operatorUtils.DeleteAll(); deleteRes.IsFailure() { + _, err := deleteRes.Get() + log.Error(err, "failed to cleanup agent dependents during uninstall") + return reconcileFailure(err) + } else { + log.V(1).Info("successfully cleaned up agent dependents during uninstall") + return r.updateAgent(ctx, agentOld, agentNew) + } +} + +func (r *InstanaAgentReconciler) handleDeletion( + ctx context.Context, + agent *instanav1.InstanaAgent, + operatorUtils operator_utils.OperatorUtils, +) reconcileReturn { + log := r.loggerFor(ctx, agent) + + if agent.DeletionTimestamp == nil { + log.V(2).Info("agent is not under deletion") + return reconcileContinue() + } else if cleanupChartRes := r.cleanupHelmChart(ctx, agent); cleanupChartRes.suppliesReconcileResult() { + return cleanupChartRes + } else if cleanupDependentsRes := r.cleanupDependents( + ctx, + agent, + operatorUtils, + ); cleanupDependentsRes.suppliesReconcileResult() { + return cleanupDependentsRes + } else { + return reconcileSuccess(ctrl.Result{}) + } +} diff --git a/controllers/event_filter.go b/controllers/event_filter.go new file mode 100644 index 00000000..04cdeaf7 --- /dev/null +++ b/controllers/event_filter.go @@ -0,0 +1,73 @@ +package controllers + +import ( + "time" + + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/event" + "sigs.k8s.io/controller-runtime/pkg/predicate" + + instanav1 "github.com/instana/instana-agent-operator/api/v1" + "github.com/instana/instana-agent-operator/pkg/collections/list" + instanaclient "github.com/instana/instana-agent-operator/pkg/k8s/client" +) + +func wasModifiedByOther(objectNew client.Object, objectOld client.Object) bool { + var lastModifiedBySelf time.Time + + for _, mfe := range objectNew.GetManagedFields() { + if mfe.Manager == instanaclient.FieldOwnerName { + if mfe.Time == nil { + continue + } + lastModifiedBySelf = mfe.Time.Time + break + } + } + + if lastModifiedBySelf.IsZero() { + return true + } + + for _, mfe := range objectNew.GetManagedFields() { + if mfe.Manager == instanaclient.FieldOwnerName { + continue + } else if mfe.Time == nil && !list.NewDeepContainsElementChecker(objectOld.GetManagedFields()).Contains(mfe) { + return true + } else if lastModifiedBySelf.Before(mfe.Time.Time) { + return true + } + } + + return false +} + +// Create generic filter for all events, that removes some chattiness mainly when only the Status field has been updated. +func filterPredicate() predicate.Predicate { + return predicate.Funcs{ + CreateFunc: func(createEvent event.CreateEvent) bool { + switch createEvent.Object.(type) { + case *instanav1.InstanaAgent: + return true + default: + return false + } + }, + UpdateFunc: func(e event.UpdateEvent) bool { + switch e.ObjectOld.(type) { + case *instanav1.InstanaAgent: + return e.ObjectOld.GetGeneration() != e.ObjectNew.GetGeneration() + default: + return wasModifiedByOther(e.ObjectNew, e.ObjectOld) + } + }, + DeleteFunc: func(e event.DeleteEvent) bool { + switch e.Object.(type) { + case *instanav1.InstanaAgent: + return !e.DeleteStateUnknown + default: + return true + } + }, + } +} diff --git a/controllers/finalizer.go b/controllers/finalizer.go new file mode 100644 index 00000000..8be95bcd --- /dev/null +++ b/controllers/finalizer.go @@ -0,0 +1,23 @@ +package controllers + +import ( + "context" + + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + + v1 "github.com/instana/instana-agent-operator/api/v1" +) + +func (r *InstanaAgentReconciler) addOrUpdateFinalizers(ctx context.Context, agentOld *v1.InstanaAgent) reconcileReturn { + agentNew := agentOld.DeepCopy() + + switch removeHelmChartRes := r.cleanupHelmChart(ctx, agentNew); { + case removeHelmChartRes.suppliesReconcileResult(): + return removeHelmChartRes + case controllerutil.AddFinalizer(agentNew, finalizerV3): + r.loggerFor(ctx, agentNew).V(1).Info("adding agent finalizer to agent CR") + return r.updateAgent(ctx, agentOld, agentNew) + default: + return reconcileContinue() + } +} diff --git a/controllers/instanaagent_controller.go b/controllers/instanaagent_controller.go index 3c63ae4a..3503d56d 100644 --- a/controllers/instanaagent_controller.go +++ b/controllers/instanaagent_controller.go @@ -1,493 +1,166 @@ /* - * (c) Copyright IBM Corp. 2021 - * (c) Copyright Instana Inc. 2021 + * (c) Copyright IBM Corp. 2021, 2024 + * (c) Copyright Instana Inc. 2021, 2024 */ package controllers import ( "context" - "fmt" - "math" - "time" - - "k8s.io/client-go/tools/record" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - v1 "k8s.io/api/rbac/v1" - - "github.com/blang/semver" - - "github.com/google/go-cmp/cmp" - - instanaV1 "github.com/instana/instana-agent-operator/api/v1" - - k8sErrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/client-go/rest" - - logf "sigs.k8s.io/controller-runtime/pkg/log" - "sigs.k8s.io/controller-runtime/pkg/manager" "github.com/go-logr/logr" - "github.com/instana/instana-agent-operator/controllers/leaderelection" - "github.com/instana/instana-agent-operator/controllers/reconciliation" - - appV1 "k8s.io/api/apps/v1" - coreV1 "k8s.io/api/core/v1" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" "k8s.io/apimachinery/pkg/runtime" - + "k8s.io/client-go/tools/record" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/event" - "sigs.k8s.io/controller-runtime/pkg/predicate" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/manager" - "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + instanav1 "github.com/instana/instana-agent-operator/api/v1" + instanaclient "github.com/instana/instana-agent-operator/pkg/k8s/client" + "github.com/instana/instana-agent-operator/pkg/k8s/operator/operator_utils" + "github.com/instana/instana-agent-operator/pkg/k8s/operator/status" + "github.com/instana/instana-agent-operator/pkg/multierror" + "github.com/instana/instana-agent-operator/pkg/recovery" ) const ( - instanaAgentFinalizer = "agent.instana.io/finalizer" - crExpectedName = "instana-agent" - crExpectedNamespace = "instana-agent" -) - -var ( - minimumSupportedChartVersion, _ = semver.Parse("1.2.28") + finalizerV1 = "agent.instana.io/finalizer" + finalizerV3 = "v3.agent.instana.io/finalizer" ) // Add will create a new Instana Agent Controller and add this to the Manager for reconciling func Add(mgr manager.Manager) error { - return add(mgr, NewInstanaAgentReconciler( - mgr.GetClient(), - mgr.GetAPIReader(), - mgr.GetEventRecorderFor("agent.controller"), - mgr.GetScheme(), - mgr.GetConfig(), - logf.Log.WithName("agent.controller"))) + return add( + mgr, NewInstanaAgentReconciler( + mgr.GetClient(), + mgr.GetScheme(), + mgr.GetEventRecorderFor("agent-controller"), + ), + ) } // add sets up the controller with the Manager. func add(mgr ctrl.Manager, r *InstanaAgentReconciler) error { return ctrl.NewControllerManagedBy(mgr). - For(&instanaV1.InstanaAgent{}). - Owns(&appV1.DaemonSet{}). - Owns(&coreV1.Pod{}). - Owns(&coreV1.ConfigMap{}). - Owns(&coreV1.Secret{}). - Owns(&coreV1.ServiceAccount{}). - Owns(&coreV1.Service{}). - Owns(&v1.ClusterRole{}). - Owns(&v1.ClusterRoleBinding{}). + For(&instanav1.InstanaAgent{}). + Owns(&appsv1.DaemonSet{}). + Owns(&appsv1.Deployment{}). + Owns(&corev1.ConfigMap{}). + Owns(&corev1.Secret{}). + Owns(&corev1.ServiceAccount{}). + Owns(&corev1.Service{}). + Owns(&rbacv1.ClusterRole{}). + Owns(&rbacv1.ClusterRoleBinding{}). WithEventFilter(filterPredicate()). Complete(r) } -// Create generic filter for all events, that removes some chattiness mainly when only the Status field has been updated. -func filterPredicate() predicate.Predicate { - return predicate.Funcs{ - UpdateFunc: func(e event.UpdateEvent) bool { - // Ignore updates to CR status in which case metadata.Generation does not change. - return e.ObjectOld.GetGeneration() != e.ObjectNew.GetGeneration() - }, - DeleteFunc: func(e event.DeleteEvent) bool { - // Evaluates to false if the object has been confirmed deleted. - return !e.DeleteStateUnknown - }, - } -} - // NewInstanaAgentReconciler initializes a new InstanaAgentReconciler instance -func NewInstanaAgentReconciler(client client.Client, apiReader client.Reader, recorder record.EventRecorder, scheme *runtime.Scheme, config *rest.Config, log logr.Logger) *InstanaAgentReconciler { +func NewInstanaAgentReconciler( + client client.Client, + scheme *runtime.Scheme, + recorder record.EventRecorder, +) *InstanaAgentReconciler { return &InstanaAgentReconciler{ - client: client, - apiReader: apiReader, - recorder: recorder, - scheme: scheme, - config: config, - log: log, - agentReconciliation: reconciliation.New(scheme, log, crExpectedName, crExpectedNamespace), - crAppName: crExpectedName, - crAppNamespace: crExpectedNamespace, + client: instanaclient.NewClient(client), + recorder: recorder, + scheme: scheme, } } type InstanaAgentReconciler struct { - client client.Client - apiReader client.Reader - recorder record.EventRecorder - scheme *runtime.Scheme - config *rest.Config - log logr.Logger - agentReconciliation reconciliation.Reconciliation - crAppName string - crAppNamespace string - // Uninitialized variables in NewInstanaAgentReconciler - leaderElector *leaderelection.LeaderElector -} - -// +kubebuilder:rbac:groups=agents.instana.io,resources=instanaagent,verbs=get;list;watch;create;update;patch;delete -// +kubebuilder:rbac:groups=apps,resources=daemonsets,verbs=get;list;watch;create;update;patch;delete -// +kubebuilder:rbac:groups=core,resources=pods;secrets;configmaps;services;serviceaccounts,verbs=get;list;watch;create;update;patch;delete -// +kubebuilder:rbac:groups=agents.instana.io,resources=instanaagent/status,verbs=get;update;patch -// +kubebuilder:rbac:groups=agents.instana.io,resources=instanaagent/finalizers,verbs=update -func (r *InstanaAgentReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - log := r.log.WithValues("namespace", req.Namespace, "name", req.Name) - log.Info("Reconciling Instana Agent") - - crdInstance, err := r.fetchAgentCrdInstance(ctx, req) - if err != nil { - if k8sErrors.IsNotFound(err) { - // Request object not found, could have been deleted after reconcile request. - // Owned objects are automatically garbage collected. For additional cleanup logic use finalizers. - // Return and don't requeue - r.log.Info("Instana Agent CRD instance not found, please install the InstanaAgent CustomResource") - return ctrl.Result{}, nil - } else { - r.log.Error(err, "Failed to get Instana Agent CustomResource or invalid") - return ctrl.Result{}, err - } - } - - isInstanaAgentDeleted := crdInstance.GetDeletionTimestamp() != nil - if isInstanaAgentDeleted { - r.log.Info("Instana Agent Operator CustomResource is deleted. Cleanup Agent.") - - if controllerutil.ContainsFinalizer(crdInstance, instanaAgentFinalizer) { - // This is a kind of work-around. Normally should just directly execute the clean-up logic. But when the user removes - // the entire instana-agent Namespace the Operator runtime will get deleted before having a chance to clean up. - // Try to detect this and remove the Finalizer. Otherwise the user needs to manually remove the Finalizer to get - // all garbage collected. - // The proper way of cleaning up would be: - // 1) remove the Operator Custom Resource - // 2) remove everything else - if instanaNamespace, err := r.fetchInstanaNamespace(ctx); err == nil && instanaNamespace.GetDeletionTimestamp() != nil { - r.log.Info("Seems like the Instana namespace got deleted. Skip running the finalizer logic and try to remove finalizer.\n" + - " Please delete the Instana Agent Operator CustomResource _first_!") - } else { - r.log.V(1).Info("Running the finalizer...") - if err := r.finalizeAgent(req, crdInstance); err != nil { - return r.handleError(ctx, req, err, crdInstance) - } - } - - controllerutil.RemoveFinalizer(crdInstance, instanaAgentFinalizer) - if err := r.client.Update(ctx, crdInstance); err != nil { - return r.handleError(ctx, req, err, crdInstance) - } - r.log.V(1).Info("Removed Finalizer from Instana Agent Operator CustomResource") - } - return ctrl.Result{}, nil - } - - // Validate the Custom Resource object (configuration) before we're taking any other actions - r.log.V(1).Info("Validating the CRD") - if err := r.validateAgentCrd(crdInstance); err != nil { - r.log.Error(err, "Unrecoverable error validating the Instana Agent CRD for deployment") - return r.handleError(ctx, req, err, crdInstance) - } - - if !crdInstance.Status.OldVersionsUpdated { - // If something got deleted, give the Operator another reconcile loop to clean up before continuing. So return immediately - if deleted, err := r.purgeOldResources(ctx); err != nil { - return r.handleError(ctx, req, err, crdInstance) - } else if deleted { - return ctrl.Result{RequeueAfter: time.Second * 1}, nil - } - - if err := r.upsertCrdStatusFields(ctx, req, instanaV1.OperatorStateUpdating, func(status *instanaV1.InstanaAgentStatus) instanaV1.InstanaAgentStatus { - status.OldVersionsUpdated = true - return *status - }); err != nil { - if k8sErrors.IsConflict(err) { - // do manual retry without error - r.log.V(1).Info("CRD update conflict, rescheduling...") - return ctrl.Result{RequeueAfter: time.Second * 1}, nil - } - r.log.Error(err, "Failed to update Instana Agent CRD Status field - old versions purged") - return ctrl.Result{}, err - } - } - - // - // Potential Old Operator resources removed, start installation of (new) Operator - // - - r.log.V(1).Info("Injecting finalizer into CRD, for cleanup when CRD gets removed") - if err := r.injectFinalizer(ctx, req, crdInstance); err != nil { - if k8sErrors.IsConflict(err) { - // do manual retry without error - r.log.V(1).Info("CRD update conflict, rescheduling...") - return ctrl.Result{RequeueAfter: time.Second * 1}, nil - } - r.log.Error(err, "Failure adding finalizer into CRD") - return r.handleError(ctx, req, err, crdInstance) - } - - // First try to start Leader Election Coordination so to return error if we cannot get it started - if crdInstance.Spec.K8sSensor.DeploymentSpec.Enabled.Enabled { - if r.leaderElector != nil { - r.leaderElector.CancelLeaderElection() - } - } else if r.leaderElector == nil || !r.leaderElector.IsLeaderElectionScheduled() { - if r.leaderElector != nil { - // As we'll replace the Leader Elector instance make sure to properly clean up old one - r.leaderElector.CancelLeaderElection() - } - - r.leaderElector = leaderelection.NewLeaderElection(r.client, req.NamespacedName) - if err := r.leaderElector.StartCoordination(r.crAppNamespace); err != nil { - r.log.Error(err, "Failure starting Leader Election Coordination") - return r.handleError(ctx, req, err, crdInstance) - } - } - - if err := r.agentReconciliation.CreateOrUpdate(req, crdInstance); err != nil { - return r.handleError(ctx, req, err, crdInstance) - } - r.log.Info("Agent installed/upgraded successfully") - - if err := r.updateStatusFieldsAndFireEvent(ctx, req, crdInstance); err != nil { - if k8sErrors.IsConflict(err) { - // do manual retry without error - r.log.V(1).Info("CRD update conflict, rescheduling...") - return ctrl.Result{RequeueAfter: time.Second * 1}, nil - } - r.log.Error(err, "Failed to update Instana Agent CRD Status field - resource references") - return ctrl.Result{}, err - } - - return ctrl.Result{}, nil + client instanaclient.InstanaAgentClient + recorder record.EventRecorder + scheme *runtime.Scheme } -// handleError will set the Operator Status to a Failure state with a description of the error, and generate an Event for this -// error. Furthermore, it will increase the retry interval as otherwise the reconcile loop might get repeated in fast succession -// without chance of resolving the issue. Kubernetes will back-off by itself, but initially with shorter intervals and might not -// correctly work with different errors. -func (r *InstanaAgentReconciler) handleError(ctx context.Context, req ctrl.Request, originalErr error, crdInstance *instanaV1.InstanaAgent) (ctrl.Result, error) { - var retryInterval time.Duration - lastUpdate := crdInstance.Status.LastUpdate.Time - lastStatus := crdInstance.Status.Status - if lastUpdate.IsZero() || lastStatus != instanaV1.OperatorStateFailed { - // If not already in a 'failure' state, retry after one second. - retryInterval = time.Second - } else { - // Already in failure state, base the retry interval on the previous update interval - retryInterval = time.Since(lastUpdate).Round(time.Second) +func (r *InstanaAgentReconciler) reconcile( + ctx context.Context, + req ctrl.Request, + statusManager status.AgentStatusManager, +) reconcileReturn { + agent, getAgentRes := r.getAgent(ctx, req) + if getAgentRes.suppliesReconcileResult() { + return getAgentRes } - r.recorder.Event(crdInstance, "Warning", "ProcessingError", originalErr.Error()) + statusManager.SetAgentOld(agent) - if err := r.upsertCrdStatusFields(ctx, req, instanaV1.OperatorStateFailed, func(status *instanaV1.InstanaAgentStatus) instanaV1.InstanaAgentStatus { - status.Reason = originalErr.Error() - return *status - }); err != nil { - // Cannot update status, so retry immediately. - r.log.Error(err, fmt.Sprintf("unable to update status after handling error: %v", originalErr)) - return ctrl.Result{RequeueAfter: time.Second * 1}, nil - } + log := r.loggerFor(ctx, agent) + ctx = logr.NewContext(ctx, log) + log.Info("reconciling Agent CR") - return ctrl.Result{ - // Keep doubling the interval with a max of 6 hours - RequeueAfter: time.Duration(math.Min(float64(retryInterval.Nanoseconds()*2), float64(time.Hour.Nanoseconds()*6))), - Requeue: true, - }, nil -} + agent.Default() + operatorUtils := operator_utils.NewOperatorUtils(ctx, r.client, agent) -func (r *InstanaAgentReconciler) finalizeAgent(req ctrl.Request, crdInstance *instanaV1.InstanaAgent) error { - if err := r.agentReconciliation.Delete(req, crdInstance); err != nil { - return err - } - if r.leaderElector != nil { - r.leaderElector.CancelLeaderElection() + if handleDeletionRes := r.handleDeletion(ctx, agent, operatorUtils); handleDeletionRes.suppliesReconcileResult() { + return handleDeletionRes } - r.log.Info("Successfully finalized instana agent") - return nil -} -func (r *InstanaAgentReconciler) injectFinalizer(ctx context.Context, req ctrl.Request, crdInstance *instanaV1.InstanaAgent) error { - if !controllerutil.ContainsFinalizer(crdInstance, instanaAgentFinalizer) { - // Pull the CR object again, so we're sure to have the latest version including changes - if err := r.client.Get(ctx, req.NamespacedName, crdInstance); err != nil { - return err - } - - controllerutil.AddFinalizer(crdInstance, instanaAgentFinalizer) - return r.client.Update(ctx, crdInstance) + if addFinalizerRes := r.addOrUpdateFinalizers(ctx, agent); addFinalizerRes.suppliesReconcileResult() { + return addFinalizerRes } - return nil -} -func (r *InstanaAgentReconciler) fetchAgentCrdInstance(ctx context.Context, req ctrl.Request) (*instanaV1.InstanaAgent, error) { - crdInstance := &instanaV1.InstanaAgent{} - if err := r.client.Get(ctx, req.NamespacedName, crdInstance); err != nil { - return nil, err + isOpenShift, isOpenShiftRes := r.isOpenShift(ctx, operatorUtils) + if isOpenShiftRes.suppliesReconcileResult() { + return isOpenShiftRes } - // Verify if the CR has the expected Name / Namespace set. At a later time we could really make this configurable and install - // our Agent in the given Namespace. For now, we only support the fixed value. - if crExpectedName != crdInstance.Name || crExpectedNamespace != crdInstance.Namespace { - err := fmt.Errorf("Instana Agent CustomResource Name (%v) or Namespace (%v) don't match currently mandatory Name='%v' and Namespace='%v'. Please adjust the CustomResource", - crdInstance.Name, crdInstance.Namespace, crExpectedName, crExpectedNamespace) - return nil, err + if applyResourcesRes := r.applyResources( + ctx, + agent, + isOpenShift, + operatorUtils, + statusManager, + ); applyResourcesRes.suppliesReconcileResult() { + return applyResourcesRes } - r.log.V(1).Info(fmt.Sprintf("Found Instana CustomResource: %v", crdInstance)) - return crdInstance, nil -} - -// fetchInstanaNamespace will get the Namespace instance for ourselves -func (r *InstanaAgentReconciler) fetchInstanaNamespace(ctx context.Context) (*coreV1.Namespace, error) { - instanaNamespace := &coreV1.Namespace{} - if err := r.client.Get(ctx, client.ObjectKey{ - Namespace: "", - Name: crExpectedNamespace, - }, instanaNamespace); err != nil { - return nil, err - } + log.Info("successfully finished reconcile on agent CR") - r.log.V(1).Info(fmt.Sprintf("Found Instana-Agent Namespace: %v", instanaNamespace)) - return instanaNamespace, nil + return reconcileSuccess(ctrl.Result{}) } -// validateAgentCrd does some basic validation as otherwise Helm may not deploy the Agent DaemonSet but silently skip it if -// certain fields are omitted. -func (r *InstanaAgentReconciler) validateAgentCrd(crd *instanaV1.InstanaAgent) error { - if len(crd.Spec.Agent.EndpointHost) == 0 || len(crd.Spec.Agent.EndpointPort) == 0 || crd.Spec.Agent.EndpointPort == "0" { - r.log.Info(` -############################################################################## -#### ERROR: You did not specify a correct Endpoint (host and/or port) #### -############################################################################## -`) - return fmt.Errorf("CRD Agent Spec should contain valid EndpointHost and EndpointPort") - } - - if len(crd.Spec.Cluster.Name) == 0 && len(crd.Spec.Zone.Name) == 0 { - r.log.Info(` -############################################################################## -#### ERROR: You did not specify a zone or name for this cluster. #### -############################################################################## -`) - return fmt.Errorf("CRD Agent Spec should contain either Zone or Cluster name") - } - - if len(crd.Spec.Agent.Key) == 0 && len(crd.Spec.Agent.KeysSecret) == 0 { - r.log.Info(` -############################################################################## -#### ERROR: You did not specify your secret agent key. #### -############################################################################## -`) - return fmt.Errorf("CRD Agent Spec should contain either Key or KeySecret") - } - - if len(crd.Spec.PinnedChartVersion) > 0 { - if pinnedChartVersion, err := semver.Parse(crd.Spec.PinnedChartVersion); err != nil { - r.log.Info(` -############################################################################## -#### ERROR: Invalid Helm Chart version pinned, cannot be parsed. #### -############################################################################## -`) - return fmt.Errorf("Helm Chart version pinned but invalid format") - - } else if pinnedChartVersion.Compare(minimumSupportedChartVersion) < 0 { - r.log.Info(fmt.Sprintf(` -############################################################################## -#### ERROR: Invalid Helm Chart version pinned. #### -#### Pick a version %v or higher. #### -############################################################################## -`, minimumSupportedChartVersion)) - return fmt.Errorf("Helm Chart version pinned but does not meet minimum supported version") +// +kubebuilder:rbac:groups=instana.io,resources=agents,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=apps,resources=daemonsets;deployments,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=core,resources=secrets;configmaps;services;serviceaccounts,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=clusterroles;clusterrolebindings,verbs=get;list;watch;create;update;patch;delete;bind +// +kubebuilder:rbac:groups=apiextensions.k8s.io,resources=customresourcedefinitions,verbs=get;list;watch +// +kubebuilder:rbac:groups=instana.io,resources=agents/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=instana.io,resources=agents/finalizers,verbs=update + +// adding role property required to manage instana-agent-k8sensor ClusterRole +// +kubebuilder:rbac:urls=/version;/healthz,verbs=get +// +kubebuilder:rbac:groups=extensions,resources=deployments;replicasets;ingresses,verbs=get;list;watch +// +kubebuilder:rbac:groups=core,resources=configmaps;events;services;endpoints;namespaces;nodes;pods;pods/log;replicationcontrollers;resourcequotas;persistentvolumes;persistentvolumeclaims,verbs=get;list;watch +// +kubebuilder:rbac:groups=apps,resources=daemonsets;deployments;replicasets;statefulsets,verbs=get;list;watch +// +kubebuilder:rbac:groups=batch,resources=cronjobs;jobs,verbs=get;list;watch +// +kubebuilder:rbac:groups=networking.k8s.io,resources=ingresses,verbs=get;list;watch +// +kubebuilder:rbac:groups=autoscaling,resources=horizontalpodautoscalers,verbs=get;list;watch +// +kubebuilder:rbac:groups=apps.openshift.io,resources=deploymentconfigs,verbs=get;list;watch +// +kubebuilder:rbac:groups=security.openshift.io,resourceNames=privileged,resources=securitycontextconstraints,verbs=use +// +kubebuilder:rbac:groups=policy,resourceNames=instana-agent-k8sensor,resources=podsecuritypolicies,verbs=use + +func (r *InstanaAgentReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( + res ctrl.Result, + reconcileErr error, +) { + defer recovery.Catch(&reconcileErr) + + logger := logf.FromContext(ctx).WithName("agent-controller") + ctx = logf.IntoContext(ctx, logger) + + statusManager := status.NewAgentStatusManager(r.client, r.recorder) + defer func() { + if err := statusManager.UpdateAgentStatus(ctx, reconcileErr); err != nil { + errBuilder := multierror.NewMultiErrorBuilder(reconcileErr, err) + reconcileErr = errBuilder.Build() } - } - - return nil -} - -func (r *InstanaAgentReconciler) purgeOldResources(ctx context.Context) (bool, error) { - r.log.V(1).Info("Checking for old Agent Operator installations and purging / upgrading them") - - if deleted, err := r.getAndDeleteOldOperator(ctx); err != nil { - r.log.Error(err, "Unrecoverable error removing the old Operator Deployment spec. Cannot continue Agent installation") - return false, err - } else if deleted { - return true, nil - } - - if deleted, err := r.getAndDeleteOldOperatorResources(ctx); err != nil { - r.log.Error(err, "Unrecoverable error updating old resources for Helm-based installation. Cannot continue Agent installation") - return false, err - } else if deleted { - return true, nil - } - - return false, nil -} - -func (r *InstanaAgentReconciler) upsertCrdStatusFields(ctx context.Context, req ctrl.Request, resultState instanaV1.AgentOperatorState, statusFn func(status *instanaV1.InstanaAgentStatus) instanaV1.InstanaAgentStatus) error { - // Pull the CR object again, so we're sure to have the latest version including changes - crdInstance := &instanaV1.InstanaAgent{} - if err := r.client.Get(ctx, req.NamespacedName, crdInstance); err != nil { - return err - } - - // Always update timestamp and state. Do this before executing statusFn so if _must_, it can be overwritten - crdInstance.Status.Status = resultState - crdInstance.Status.Reason = "" - crdInstance.Status.LastUpdate = metav1.Now() - - crdInstance.Status = statusFn(&crdInstance.Status) - - return r.client.Status().Update(ctx, crdInstance) -} - -func (r *InstanaAgentReconciler) updateStatusFieldsAndFireEvent(ctx context.Context, req ctrl.Request, crdInstance *instanaV1.InstanaAgent) error { - r.log.V(1).Info("Updating Agent CRD Status field with references to DaemonSet and ConfigMap") - - configMaps := &coreV1.ConfigMapList{} - if err := r.client.List(ctx, configMaps, client.InNamespace(r.crAppNamespace)); err != nil { - r.log.Error(err, "Failed getting ConfigMap to update Instana Agent CRD Status field") - return err - } - var configMapResource instanaV1.ResourceInfo - for _, val := range configMaps.Items { - if val.Name == "instana-agent" { - configMapResource = instanaV1.ResourceInfo{ - Name: val.Name, - UID: string(val.UID), - } - } - } - - daemonSets := &appV1.DaemonSetList{} - if err := r.client.List(ctx, daemonSets, client.InNamespace(r.crAppNamespace)); err != nil { - r.log.Error(err, "Failed getting DaemonSet to update Instana Agent CRD Status field") - return err - } - var daemonSetResource instanaV1.ResourceInfo - for _, val := range daemonSets.Items { - if val.Name == "instana-agent" { - daemonSetResource = instanaV1.ResourceInfo{ - Name: val.Name, - UID: string(val.UID), - } - } - } - - if !cmp.Equal(configMapResource, crdInstance.Status.ConfigMap) || - !cmp.Equal(daemonSetResource, crdInstance.Status.DaemonSet) { - - r.recorder.Event(crdInstance, "Normal", "CreatedUpdated", "The Agent deployment was correctly created / updated") - - state := instanaV1.OperatorStateRunning - if (instanaV1.ResourceInfo{}) == configMapResource || (instanaV1.ResourceInfo{}) == daemonSetResource { - state = instanaV1.OperatorStateUpdating - } - - return r.upsertCrdStatusFields(ctx, req, state, func(status *instanaV1.InstanaAgentStatus) instanaV1.InstanaAgentStatus { - status.ConfigMap = configMapResource - status.DaemonSet = daemonSetResource - return *status - }) - } + }() - return nil + return r.reconcile(ctx, req, statusManager).reconcileResult() } diff --git a/controllers/instanaagent_controller_test.go b/controllers/instanaagent_controller_test.go deleted file mode 100644 index 81741b3a..00000000 --- a/controllers/instanaagent_controller_test.go +++ /dev/null @@ -1,163 +0,0 @@ -/* - * (c) Copyright IBM Corp. 2021 - * (c) Copyright Instana Inc. 2021 - */ - -package controllers - -import ( - "context" - "time" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - - agentv1 "github.com/instana/instana-agent-operator/api/v1" -) - -var _ = Describe("Instana-Agent controller", func() { - - // Define utility constants for object names and testing timeouts/durations and intervals. - const ( - InstanaAgentName = "instana-agent" - - timeout = time.Second * 5 - duration = time.Second * 10 - interval = time.Millisecond * 250 - ) - - ns := SetupTest(context.TODO()) - - Context("When creating an InstanaAgent CustomResource", func() { - It("Should not create any Agent resources", func() { - By("By validating and exiting early") - ctx := context.Background() - instanaAgent := &agentv1.InstanaAgent{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "instana.io/v1", - Kind: "InstanaAgent", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: InstanaAgentName, - Namespace: ns.Name, - }, - Spec: agentv1.InstanaAgentSpec{ - Zone: agentv1.Name{ - Name: "Maaskantje", - }, - Cluster: agentv1.Name{ - Name: "Maaskantje", - }, - Agent: agentv1.BaseAgentSpec{ - EndpointHost: "instana.rocks", - EndpointPort: "443", - }, - }, - } - Expect(k8sClient.Create(ctx, instanaAgent)).Should(Succeed()) - - agentOperatorLookupKey := types.NamespacedName{Name: InstanaAgentName, Namespace: ns.Name} - createdAgentOperator := &agentv1.InstanaAgent{} - - // We'll need to retry getting this newly created InstanaAgent, given that creation may not immediately happen. - Eventually(func() bool { - err := k8sClient.Get(ctx, agentOperatorLookupKey, createdAgentOperator) - return err == nil - }, timeout, interval).Should(BeTrue()) - // Let's make sure our Schedule string value was properly converted/handled. - Expect(createdAgentOperator.Spec.Cluster.Name).Should(Equal("Maaskantje")) - - By("By checking the Status is not set because the CustomResource is invalid") - Consistently(func() (bool, error) { - err := k8sClient.Get(ctx, agentOperatorLookupKey, createdAgentOperator) - if err != nil { - return false, err - } - return createdAgentOperator.Status.OldVersionsUpdated, nil - }, duration, interval).Should(Equal(false)) - }) - }) - - // TODO These tests need to be fixed, at least two known issues: - // - Operator always expects a fixed namespace (instana-agent). But cluster status cannot be cleaned between tests and - // deleting namespaces is not possible. So either everything needs to run in a single test or make the Operator work with - // variable namespaces - // - Helm errors with "Kubernetes cluster unreachable" - // - //Context("When creating InstanaAgent CustomResource that is valid", func() { - // It("Should create Agent resources", func() { - // By("By valid CRD creation") - // ctx := context.Background() - // instanaAgent := &agentv1.InstanaAgent{ - // TypeMeta: metav1.TypeMeta{ - // APIVersion: "instana.io/v1", - // Kind: "InstanaAgent", - // }, - // ObjectMeta: metav1.ObjectMeta{ - // Name: InstanaAgentName, - // Namespace: ns.Name, - // }, - // Spec: agentv1.InstanaAgentSpec{ - // Zone: agentv1.Name{ - // Name: "Maaskantje", - // }, - // Cluster: agentv1.Name{ - // Name: "Maaskantje", - // }, - // Agent: agentv1.BaseAgentSpec{ - // Key: "foobar", - // EndpointHost: "instana.rocks", - // EndpointPort: "443", - // }, - // }, - // } - // Expect(k8sClient.Create(ctx, instanaAgent)).Should(Succeed()) - // - // agentOperatorLookupKey := types.NamespacedName{Name: InstanaAgentName, Namespace: ns.Name} - // createdAgentOperator := &agentv1.InstanaAgent{} - // - // // We'll need to retry getting this newly created InstanaAgent, given that creation may not immediately happen. - // Eventually(func() bool { - // err := k8sClient.Get(ctx, agentOperatorLookupKey, createdAgentOperator) - // return err == nil - // }, timeout, interval).Should(BeTrue()) - // // Let's make sure our Schedule string value was properly converted/handled. - // Expect(createdAgentOperator.Spec.Agent.Key).Should(Equal("foobar")) - // - // By("By checking the Status updated") - // Eventually(func() (bool, error) { - // err := k8sClient.Get(ctx, agentOperatorLookupKey, createdAgentOperator) - // if err != nil { - // return false, err - // } - // return createdAgentOperator.Status.OldVersionsUpdated, nil - // }, timeout, interval).Should(BeTrue()) - // - // By("By checking a DaemonSet was created") - // agentDaemonSetKey := types.NamespacedName{Name: "instana-agent", Namespace: ns.Name} - // agentDaemonSet := &appsv1.DaemonSet{} - // Eventually(func() bool { - // err := k8sClient.Get(ctx, agentDaemonSetKey, agentDaemonSet) - // return err == nil - // }, timeout, interval).Should(BeTrue()) - // // Let's make sure our Schedule string value was properly converted/handled. - // Expect(len(agentDaemonSet.Spec.Template.Spec.Containers)).Should(Equal(1)) - // Expect(agentDaemonSet.Spec.Template.Spec.Containers[0].Env).Should(ContainElement(v1.EnvVar{ - // Name: "INSTANA_KUBERNETES_CLUSTER_NAME", - // Value: "Maaskantje", - // })) - // - // Consistently(func() (string, error) { - // err := k8sClient.Get(ctx, agentOperatorLookupKey, createdAgentOperator) - // if err != nil { - // return "", err - // } - // return createdAgentOperator.Status.DaemonSet.UID, nil - // }, duration, interval).Should(MatchRegexp(".+")) - // - // }) - //}) - -}) diff --git a/controllers/leaderelection/agent_leaderelection.go b/controllers/leaderelection/agent_leaderelection.go deleted file mode 100644 index 1e0db0b3..00000000 --- a/controllers/leaderelection/agent_leaderelection.go +++ /dev/null @@ -1,332 +0,0 @@ -/* - * (c) Copyright IBM Corp. 2021 - * (c) Copyright Instana Inc. 2021 - */ - -package leaderelection - -import ( - "context" - "errors" - "fmt" - "math/rand" - "sync" - "time" - - "k8s.io/apimachinery/pkg/types" - - instanaV1 "github.com/instana/instana-agent-operator/api/v1" - - logf "sigs.k8s.io/controller-runtime/pkg/log" - - "github.com/google/go-cmp/cmp/cmpopts" - - "github.com/google/go-cmp/cmp" - - "github.com/go-logr/logr" - "github.com/instana/instana-agent-operator/controllers/leaderelection/coordination_api" - "github.com/procyon-projects/chrono" - coreV1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/labels" - "sigs.k8s.io/controller-runtime/pkg/client" -) - -func NewLeaderElection(client client.Client, namespacedName types.NamespacedName) *LeaderElector { - return &LeaderElector{ - client: client, - log: logf.Log.WithName("agent.leaderelector"), - coordinationApi: coordination_api.New(), - leaderElectionTaskScheduler: chrono.NewDefaultTaskScheduler(), - namespacedName: namespacedName, - } -} - -type LeaderElector struct { - client client.Client - log logr.Logger - coordinationApi coordination_api.PodCoordinationApi - leaderElectionTaskScheduler chrono.TaskScheduler - namespacedName types.NamespacedName - // Uninitialized fields from NewLeaderElection - leaderElectionTask chrono.ScheduledTask -} - -/* -StartCoordination starts scheduling of leader elector coordination between Instana Agents. - -Agent Coordination works by requesting the "resources" for which an Agent would be leader. This could be one or more. -Different Agents might request similar or different "resources". - -The Operator should, for every requested resource, appoint one specific Agent pod as leader by calling the -/coordination/assigned endpoint. -*/ -func (l *LeaderElector) StartCoordination(agentNameSpace string) error { - if l.IsLeaderElectionScheduled() { - return errors.New("leader election coordination task has already been scheduled") - } - - if task, err := l.leaderElectionTaskScheduler.ScheduleWithFixedDelay(func(ctx context.Context) { - fetchPodsCtx, cancelFunc := context.WithTimeout(ctx, 5*time.Second) - defer cancelFunc() - - activePods, err := l.fetchPods(fetchPodsCtx, agentNameSpace) - if err != nil { - l.log.Error(err, "Unable to fetch agent pods for doing election") - return - } - - assignCtx, cancelFunc := context.WithTimeout(ctx, 20*time.Second) - defer cancelFunc() - leaders := l.pollAgentsAndAssignLeaders(assignCtx, activePods) - - if leaders != nil { - l.updateLeaderStatusInCustomResource(leaders, activePods) - } - - }, 10*time.Second); err != nil { - return fmt.Errorf("failure scheduling leader elector coordination task: %w", err) - } else { - l.leaderElectionTask = task - l.log.Info("Leader Election task has been scheduled successfully.") - } - - return nil -} - -func (l *LeaderElector) IsLeaderElectionScheduled() bool { - return l.leaderElectionTask != nil && !l.leaderElectionTask.IsCancelled() -} - -func (l *LeaderElector) CancelLeaderElection() { - if l.leaderElectionTask != nil && !l.leaderElectionTask.IsCancelled() { - l.leaderElectionTask.Cancel() - } - if !l.leaderElectionTaskScheduler.IsShutdown() { - l.leaderElectionTaskScheduler.Shutdown() - } -} - -func (l *LeaderElector) fetchPods(ctx context.Context, agentNameSpace string) (map[string]coreV1.Pod, error) { - podList := &coreV1.PodList{} - activePods := make(map[string]coreV1.Pod) - lbs := map[string]string{ - "app.kubernetes.io/name": l.namespacedName.Namespace, - } - labelSelector := labels.SelectorFromSet(lbs) - listOps := &client.ListOptions{Namespace: agentNameSpace, LabelSelector: labelSelector} - if err := l.client.List(ctx, podList, listOps); err != nil { - return nil, err - } - - for _, pod := range podList.Items { - if pod.Status.Phase == coreV1.PodRunning { - activePods[string(pod.GetObjectMeta().GetUID())] = pod - } - } - return activePods, nil -} - -// pollAgentsAndAssignLeaders will first get all "requested resources" from every Agent Pod. It will then calculate new -// assignments, prioritizing any Pod that already holds that assignment. -// The function is executed in a loop, so that should assignments fail for any Pod, determining assignments starts over from scratch. -func (l *LeaderElector) pollAgentsAndAssignLeaders(ctx context.Context, pods map[string]coreV1.Pod) map[string][]string { -outer: - for { - // Safeguard to prevent infinite loop should we fail to assign all pods - if len(pods) == 0 { - return nil - } - - leadershipStatus := l.pollLeadershipStatus(ctx, pods) - // if leadershipStatus.Status is empty list that means we were not able to get leadership status for any pod - // so we can't proceed with leadership assigning process and we should return - if len(leadershipStatus.Status) == 0 { - return nil - } - - desiredPodWithAssignments := l.calculateDesiredAssignments(leadershipStatus) - - // As there can be multiple pods with different resource assignments, have to try Pod assignment one by one. - // If one fails, the pod needs to be removed and assignments re-calculated - for pod, assignments := range desiredPodWithAssignments { - if result := l.assign(ctx, pods, leadershipStatus, pod, assignments); !result { - // Failure for assigning to one of the pods, need to re-evaluate and re-calculate coordination - delete(pods, pod) - continue outer - } - } - - // Because of a bug in the Agent code, it could happen we have Pods with _no_ requests but _with_ assignments, clean - // these up although we're not interested failures as the Pod might get restarted - for _, pod := range leadershipStatus.getPodsWithAssignmentsNoRequests() { - c := pods[pod] - l.log.Info(fmt.Sprintf("Pod with UID %v has assignments but no requests. Resetting.", c.GetObjectMeta().GetName())) - l.assign(ctx, pods, leadershipStatus, pod, []string{}) - } - - // All assignments finished correctly, so exit - return desiredPodWithAssignments - } -} - -func (l *LeaderElector) assign(ctx context.Context, activePods map[string]coreV1.Pod, leadershipStatus *LeadershipStatus, desiredPod string, assignments []string) bool { - less := func(a, b string) bool { return a < b } - if !cmp.Equal(assignments, leadershipStatus.getAssignmentsForPod(desiredPod), cmpopts.SortSlices(less)) { - // Only need to update if desired assignments are not yet equal to actual assignments - pod := activePods[desiredPod] - if err := l.coordinationApi.Assign(ctx, pod, assignments); err != nil { - l.log.Error(err, fmt.Sprintf("Failed to assign leadership %v to pod: %v", assignments, pod.GetObjectMeta().GetName())) - return false - } - l.log.Info(fmt.Sprintf("Assigned leadership of %v to pod: %v", assignments, pod.GetObjectMeta().GetName())) - } - return true -} - -func (l *LeaderElector) pollLeadershipStatus(ctx context.Context, pods map[string]coreV1.Pod) *LeadershipStatus { - resourcesMutex := sync.Mutex{} - resourcesByPod := make(map[string]*coordination_api.CoordinationRecord) - - group := sync.WaitGroup{} - - for uid, pod := range pods { - - group.Add(1) - go func(uid string, pod coreV1.Pod) { - defer group.Done() - - coordinationRecord, err := l.coordinationApi.PollPod(ctx, pod) - if err != nil { - // Logging on Info level because could just happen that Pod is not ready - l.log.Info(fmt.Sprintf("Unable to poll coordination status for Pod %v: %v", pod.GetObjectMeta().GetName(), err)) - } else { - l.log.V(1).Info(fmt.Sprintf("Coordination status was successfully polled for Pod %v", pod.GetObjectMeta().GetName())) - resourcesMutex.Lock() - resourcesByPod[uid] = coordinationRecord - resourcesMutex.Unlock() - } - }(uid, pod) - } - - group.Wait() - return &LeadershipStatus{Status: resourcesByPod} -} - -func (l *LeaderElector) calculateDesiredAssignments(leadershipStatus *LeadershipStatus) map[string][]string { - desiredPodWithAssignments := make(map[string][]string) - - // For every requested resource, determine if there is a 'leader' already, if not, pick a random Pod from the eligible ones - for resource, podsList := range leadershipStatus.getRequestedResourcesWithPods() { - var desiredPod string - var ok bool - - if desiredPod, ok = leadershipStatus.getCurrentLeaderPodForResource(resource); !ok { - // We can be certain len(podsList) > 0 and so invocation rand.Intn() is safe - desiredPod = podsList[rand.Intn(len(podsList))] - } - - if assignments, contains := desiredPodWithAssignments[desiredPod]; contains { - desiredPodWithAssignments[desiredPod] = append(assignments, resource) - } else { - desiredPodWithAssignments[desiredPod] = []string{resource} - } - } - - return desiredPodWithAssignments -} - -func (l *LeaderElector) updateLeaderStatusInCustomResource(leaders map[string][]string, pods map[string]coreV1.Pod) { - // Don't do any error handling. Updating the Status field is not critical ATM and will be retried each cycle - - // Convert from map pods -> statuses to status -> ResourceInfo(pod) - leadershipStatus := make(map[string]instanaV1.ResourceInfo, len(leaders)) - for podUID, leaderTypes := range leaders { - for _, leaderType := range leaderTypes { - - var podName string - if pod, ok := pods[podUID]; ok { - podName = pod.Name - } else { - podName = "" - } - - leadershipStatus[leaderType] = instanaV1.ResourceInfo{ - Name: podName, - UID: podUID, - } - } - } - - crdInstance := &instanaV1.InstanaAgent{} - if err := l.client.Get(context.Background(), l.namespacedName, crdInstance); err != nil { - l.log.Error(err, "Failure querying InstanaAgent CR to update LeaderShip Status field") - return - } - - // Only update the CR if anything actually changed - less := func(a, b string) bool { return a < b } - if crdInstance.Status.LeadingAgentPod == nil || !cmp.Equal(leadershipStatus, crdInstance.Status.LeadingAgentPod, cmpopts.SortSlices(less)) { - - crdInstance.Status.LeadingAgentPod = leadershipStatus - if err := l.client.Status().Update(context.Background(), crdInstance); err != nil { - l.log.Error(err, "Failed updating CR with LeaderShip Status") - } - } -} - -type LeadershipStatus struct { - Status map[string]*coordination_api.CoordinationRecord -} - -func (s *LeadershipStatus) getRequestedResourcesWithPods() map[string][]string { - requestedResourcesByPods := make(map[string][]string) - - // From a Map with podUid -> [] requested resources, transform to a map of 'requested resource' -> [] podUids - for podUid, coordinationRecord := range s.Status { - if len(coordinationRecord.Requested) > 0 { - for _, resource := range coordinationRecord.Requested { - if elem, contains := requestedResourcesByPods[resource]; contains { - requestedResourcesByPods[resource] = append(elem, podUid) - } else { - requestedResourcesByPods[resource] = []string{podUid} - } - } - } - } - - return requestedResourcesByPods -} - -func (s *LeadershipStatus) getCurrentLeaderPodForResource(resource string) (string, bool) { - for podUid, coordinationRecord := range s.Status { - if len(coordinationRecord.Assigned) > 0 { - for _, assignedResource := range coordinationRecord.Assigned { - if resource == assignedResource { - return podUid, true - } - } - } - } - return "", false -} - -func (s *LeadershipStatus) getAssignmentsForPod(podUid string) []string { - if coordinationRecord, ok := s.Status[podUid]; ok { - return coordinationRecord.Assigned - } else { - return nil - } -} - -func (s *LeadershipStatus) getPodsWithAssignmentsNoRequests() []string { - podsWithoutRequests := make([]string, 0, len(s.Status)) - - // From a Map with podUid -> [] requested resources, transform to a map of 'requested resource' -> [] podUids - for podUid, coordinationRecord := range s.Status { - if len(coordinationRecord.Assigned) > 0 && len(coordinationRecord.Requested) == 0 { - podsWithoutRequests = append(podsWithoutRequests, podUid) - } - } - - return podsWithoutRequests -} diff --git a/controllers/leaderelection/coordination_api/http_client.go b/controllers/leaderelection/coordination_api/http_client.go deleted file mode 100644 index db0808ce..00000000 --- a/controllers/leaderelection/coordination_api/http_client.go +++ /dev/null @@ -1,90 +0,0 @@ -/* - * (c) Copyright IBM Corp. 2021 - * (c) Copyright Instana Inc. 2021 - */ - -package coordination_api - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "io" - "net/http" - - coreV1 "k8s.io/api/core/v1" -) - -type podCoordinationHttpClient struct { -} - -func (c *podCoordinationHttpClient) Assign(ctx context.Context, pod coreV1.Pod, assignment []string) error { - url := c.getBaseUrl(pod) + "/assigned" - - body, err := json.Marshal(assignment) - if err != nil { - return fmt.Errorf("error marshaling assignment list for %v: %w", pod.GetObjectMeta().GetName(), err) - } - - request, err := http.NewRequest(http.MethodPut, url, bytes.NewReader(body)) - if err != nil { - return fmt.Errorf("invalid Http request for assigning leadership to %v: %w", pod.GetObjectMeta().GetName(), err) - } - request.Header.Add("content-type", "application/json") - - resp, err := http.DefaultClient.Do(request.WithContext(ctx)) - if err != nil { - return fmt.Errorf("unsuccessful request assigning leadership to %v: %w", pod.GetObjectMeta().GetName(), err) - } - - defer func(Body io.ReadCloser) { - _ = Body.Close() - }(resp.Body) - - if resp.StatusCode >= http.StatusBadRequest { - return fmt.Errorf("leadership assignment request to %v resulted in HTTP error response %v", - pod.GetObjectMeta().GetName(), resp.Status) - } - - return nil -} - -func (c *podCoordinationHttpClient) PollPod(ctx context.Context, pod coreV1.Pod) (*CoordinationRecord, error) { - coordinationRecord := &CoordinationRecord{} - url := c.getBaseUrl(pod) - - request, err := http.NewRequest(http.MethodGet, url, nil) - if err != nil { - return nil, fmt.Errorf("invalid Http request for querying leadership to %v: %w", pod.GetObjectMeta().GetName(), err) - } - - resp, err := http.DefaultClient.Do(request.WithContext(ctx)) - if err != nil { - return nil, fmt.Errorf("unsuccessful request polling %v: %w", pod.GetObjectMeta().GetName(), err) - } - - defer func(Body io.ReadCloser) { - _ = Body.Close() - }(resp.Body) - - if resp.StatusCode >= http.StatusBadRequest { - return nil, fmt.Errorf("request polling %v resulted in HTTP error response %v", - pod.GetObjectMeta().GetName(), resp.Status) - } - - body, err := io.ReadAll(resp.Body) - if err != nil { - return nil, fmt.Errorf("error reading /coordination response of %v: %w", pod.GetObjectMeta().GetName(), err) - } - - if err := json.Unmarshal(body, coordinationRecord); err != nil { - return nil, fmt.Errorf("error unmarshalling response of %v: %w", pod.GetObjectMeta().GetName(), err) - } - - return coordinationRecord, nil -} - -func (c *podCoordinationHttpClient) getBaseUrl(pod coreV1.Pod) string { - return fmt.Sprintf("http://%v:%d/coordination", pod.Status.HostIP, AgentPort) -} diff --git a/controllers/leaderelection/coordination_api/pod_coordination_api.go b/controllers/leaderelection/coordination_api/pod_coordination_api.go deleted file mode 100644 index cafebef9..00000000 --- a/controllers/leaderelection/coordination_api/pod_coordination_api.go +++ /dev/null @@ -1,28 +0,0 @@ -/* - * (c) Copyright IBM Corp. 2021 - * (c) Copyright Instana Inc. 2021 - */ - -package coordination_api - -import ( - "context" - - coreV1 "k8s.io/api/core/v1" -) - -const AgentPort = 42699 - -type PodCoordinationApi interface { - Assign(ctx context.Context, pod coreV1.Pod, assignment []string) error - PollPod(ctx context.Context, pod coreV1.Pod) (*CoordinationRecord, error) -} - -type CoordinationRecord struct { - Requested []string `json:"requested,omitempty"` - Assigned []string `json:"assigned,omitempty"` -} - -func New() PodCoordinationApi { - return &podCoordinationHttpClient{} -} diff --git a/controllers/old_operator_support.go b/controllers/old_operator_support.go deleted file mode 100644 index 14d91c31..00000000 --- a/controllers/old_operator_support.go +++ /dev/null @@ -1,152 +0,0 @@ -/* - * (c) Copyright IBM Corp. 2021 - * (c) Copyright Instana Inc. 2021 - */ - -package controllers - -import ( - "context" - "fmt" - - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/selection" - - appV1 "k8s.io/api/apps/v1" - coreV1 "k8s.io/api/core/v1" - rbacv1 "k8s.io/api/rbac/v1" - k8sErrors "k8s.io/apimachinery/pkg/api/errors" - "sigs.k8s.io/controller-runtime/pkg/client" -) - -const ( - ManagedByLabelKey = "app.kubernetes.io/managed-by" -) - -func (r *InstanaAgentReconciler) getAndDeleteOldOperator(ctx context.Context) (bool, error) { - oldOperatorDeployment := &appV1.Deployment{} - if err := r.client.Get(ctx, client.ObjectKey{ - Namespace: "instana-agent", - Name: "instana-agent-operator", - }, oldOperatorDeployment); err != nil { - if k8sErrors.IsNotFound(err) { - r.log.V(1).Info("No old Operator Deployment found, not necessary to delete") - return false, nil - } else { - r.log.Error(err, "Failure looking for old Operator Deployment") - return false, err - } - } - - r.log.V(1).Info(fmt.Sprintf("Found old Operator Deployment and will try to delete: %v", oldOperatorDeployment)) - if err := r.client.Delete(ctx, oldOperatorDeployment); err != nil { - r.log.Error(err, "Failure deleting old Operator Deployment") - return false, err - } - - return true, nil -} - -type ObjectListItemsConversion struct { - list client.ObjectList -} - -func (c *ObjectListItemsConversion) getClientObjectItems() []client.Object { - var convertedItems []client.Object - - switch objType := c.list.(type) { - case *rbacv1.ClusterRoleBindingList: - convertedItems = make([]client.Object, len(objType.Items)) - for i := range objType.Items { - convertedItems[i] = &objType.Items[i] - } - case *rbacv1.ClusterRoleList: - convertedItems = make([]client.Object, len(objType.Items)) - for i := range objType.Items { - convertedItems[i] = &objType.Items[i] - } - case *coreV1.ServiceAccountList: - convertedItems = make([]client.Object, len(objType.Items)) - for i := range objType.Items { - convertedItems[i] = &objType.Items[i] - } - case *coreV1.ConfigMapList: - convertedItems = make([]client.Object, len(objType.Items)) - for i := range objType.Items { - convertedItems[i] = &objType.Items[i] - } - case *coreV1.SecretList: - convertedItems = make([]client.Object, len(objType.Items)) - for i := range objType.Items { - convertedItems[i] = &objType.Items[i] - } - case *appV1.DaemonSetList: - convertedItems = make([]client.Object, len(objType.Items)) - for i := range objType.Items { - convertedItems[i] = &objType.Items[i] - } - } - - return convertedItems -} - -// getAndDeleteOldOperatorResources removes all resources that might exist and belong to the 'old' (Java) operator. -// Initially tried adding labels and annotations so Helm could inherit these resources, but that mostly led to deployment issues. -// So instead just completely removing so Helm can do a 'clean' install. Downside is a short interruption of the Agent, but if -// the DaemonSet was not exactly the same, chances are very high it would get re-deployed anyhow by Helm. -func (r *InstanaAgentReconciler) getAndDeleteOldOperatorResources(ctx context.Context) (bool, error) { - // List of object types that need possible deletion. - // Make sure to add any type also to the ObjectListItemsConversion.getClientObjectItems method - toDeleteResourceTypes := []ObjectListItemsConversion{ - {list: &rbacv1.ClusterRoleBindingList{}}, - {list: &rbacv1.ClusterRoleList{}}, - {list: &coreV1.ServiceAccountList{}}, - {list: &coreV1.ConfigMapList{}}, - {list: &coreV1.SecretList{}}, - {list: &appV1.DaemonSetList{}}, - } - - // Init and add to selector. - selector := labels.NewSelector() - managedByReq, _ := labels.NewRequirement(ManagedByLabelKey, selection.Equals, []string{"instana-agent-operator"}) - selector = selector.Add(*managedByReq) - - hasDeletedResources := false - for _, toBeDeletedType := range toDeleteResourceTypes { - if deleted, err := r.deleteResourcesOfType(ctx, toBeDeletedType, selector); err != nil { - return false, err - } else if deleted { - hasDeletedResources = true - } - } - - return hasDeletedResources, nil -} - -func (r *InstanaAgentReconciler) deleteResourcesOfType(ctx context.Context, toBeDeletedType ObjectListItemsConversion, selector labels.Selector) (bool, error) { - - if err := r.client.List(ctx, toBeDeletedType.list, client.MatchingLabelsSelector{Selector: selector}); err != nil { - r.log.Error(err, fmt.Sprintf("Failure listing: %v", toBeDeletedType)) - return false, err - } - - resourceInstances := toBeDeletedType.getClientObjectItems() - - for _, resource := range resourceInstances { - if err := r.deleteResource(ctx, resource); err != nil { - return false, err - } - } - - return len(resourceInstances) > 0, nil -} - -func (r *InstanaAgentReconciler) deleteResource(ctx context.Context, item client.Object) error { - r.log.V(1).Info(fmt.Sprintf("Found existing resource managed by old Operator, trying to delete: %v", item)) - // client.DeleteAllOf doesn't work for all types of resources, so just delete them one by one. - if err := r.client.Delete(ctx, item); err != nil { - r.log.Error(err, fmt.Sprintf("Failure deleting existing resource: %v", item)) - return err - } - return nil -} diff --git a/controllers/reconcile_result.go b/controllers/reconcile_result.go new file mode 100644 index 00000000..3282e07a --- /dev/null +++ b/controllers/reconcile_result.go @@ -0,0 +1,32 @@ +package controllers + +import ( + ctrl "sigs.k8s.io/controller-runtime" + + "github.com/instana/instana-agent-operator/pkg/optional" + "github.com/instana/instana-agent-operator/pkg/result" +) + +type reconcileReturn struct { + res optional.Optional[result.Result[ctrl.Result]] +} + +func (r reconcileReturn) suppliesReconcileResult() bool { + return r.res.IsPresent() +} + +func (r reconcileReturn) reconcileResult() (ctrl.Result, error) { + return r.res.Get().Get() +} + +func reconcileSuccess(res ctrl.Result) reconcileReturn { + return reconcileReturn{optional.Of(result.OfSuccess(res))} +} + +func reconcileFailure(err error) reconcileReturn { + return reconcileReturn{optional.Of(result.OfFailure[ctrl.Result](err))} +} + +func reconcileContinue() reconcileReturn { + return reconcileReturn{optional.Empty[result.Result[ctrl.Result]]()} +} diff --git a/controllers/reconciliation/helm/agent_reconciliation.go b/controllers/reconciliation/helm/agent_reconciliation.go index b8642659..fae3be90 100644 --- a/controllers/reconciliation/helm/agent_reconciliation.go +++ b/controllers/reconciliation/helm/agent_reconciliation.go @@ -9,47 +9,28 @@ import ( "fmt" "os" - "sigs.k8s.io/yaml" - "github.com/go-logr/logr" - instanaV1 "github.com/instana/instana-agent-operator/api/v1" "github.com/pkg/errors" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/client-go/discovery" - "helm.sh/helm/v3/pkg/action" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chart/loader" - "helm.sh/helm/v3/pkg/chartutil" "helm.sh/helm/v3/pkg/cli" - "helm.sh/helm/v3/pkg/getter" - "helm.sh/helm/v3/pkg/repo" "helm.sh/helm/v3/pkg/storage/driver" - ctrl "sigs.k8s.io/controller-runtime" -) - -const ( - agentChartName = "instana-agent" -) - -var ( - settings *cli.EnvSettings - helmRepo string + "k8s.io/apimachinery/pkg/runtime" ) -func init() { - settings = cli.New() - helmRepo = "https://agents.instana.io/helm" - settings.RepositoryConfig = helmRepo +type DeprecatedInternalChartUninstaller interface { + Delete() error } -func NewHelmReconciliation(scheme *runtime.Scheme, log logr.Logger, crAppName string, crAppNamespace string) *HelmReconciliation { - h := &HelmReconciliation{ +func NewHelmReconciliation( + scheme *runtime.Scheme, + log logr.Logger, +) DeprecatedInternalChartUninstaller { + h := &helmReconciliation{ scheme: scheme, log: log.WithName("helm-reconcile"), - crAppName: crAppName, - crAppNamespace: crAppNamespace, + crAppName: "instana-agent", + crAppNamespace: "instana-agent", } if err := h.initHelmConfig(); err != nil { // This is a highly unlikely edge-case (the action.Configuration.Init(...) itself only panics) from which we can't @@ -62,20 +43,26 @@ func NewHelmReconciliation(scheme *runtime.Scheme, log logr.Logger, crAppName st } // initHelmConfig initializes some general setup, extracted from the 'constructor' because it might error -func (h *HelmReconciliation) initHelmConfig() error { +func (h *helmReconciliation) initHelmConfig() error { + settings := cli.New() h.helmCfg = new(action.Configuration) - if err := h.helmCfg.Init(settings.RESTClientGetter(), settings.Namespace(), os.Getenv("HELM_DRIVER"), h.debugLog); err != nil { + if err := h.helmCfg.Init( + settings.RESTClientGetter(), + settings.Namespace(), + os.Getenv("HELM_DRIVER"), + h.debugLog, + ); err != nil { return fmt.Errorf("failure initializing Helm configuration: %w", err) } return nil } // debugLog provides a logging function so that the Helm driver will send all output via our logging pipeline -func (h *HelmReconciliation) debugLog(format string, v ...interface{}) { +func (h *helmReconciliation) debugLog(format string, v ...interface{}) { h.log.WithName("helm").V(1).Info(fmt.Sprintf(format, v...)) } -type HelmReconciliation struct { +type helmReconciliation struct { helmCfg *action.Configuration scheme *runtime.Scheme log logr.Logger @@ -83,7 +70,7 @@ type HelmReconciliation struct { crAppNamespace string } -func (h *HelmReconciliation) Delete(_ ctrl.Request, _ *instanaV1.InstanaAgent) error { +func (h *helmReconciliation) Delete() error { uninstallAction := action.NewUninstall(h.helmCfg) if _, err := uninstallAction.Run(h.crAppName); err != nil { // If there was an error because already uninstalled, ignore it @@ -98,184 +85,3 @@ func (h *HelmReconciliation) Delete(_ ctrl.Request, _ *instanaV1.InstanaAgent) e h.log.Info("Release Instana Agent (Charts) uninstalled") return nil } - -func (h *HelmReconciliation) CreateOrUpdate(_ ctrl.Request, crdInstance *instanaV1.InstanaAgent) error { - // Prepare CRD for Helm Chart installation, converting to YAML (key-value map) - versionSet, err := h.getApiVersions() - if err != nil { - return err - } - if crdInstance.Spec.OpenShift || versionSet.Has("apps.openshift.io/v1") { - crdInstance.Spec.OpenShift = true - } else { - crdInstance.Spec.OpenShift = false - } - - yamlMap, err := h.mapCRDToYaml(crdInstance) - if err != nil { - return err - } - - if agentConfig, ok := yamlMap["agent"]; ok { - if agentConfigMap, mapFound := agentConfig.(map[string]interface{}); mapFound { - if helmUrl, urlFound := agentConfigMap["charts_url"]; urlFound { - h.log.Info(fmt.Sprintf("Custom charts url found. Setting it to %s", helmUrl)) - helmRepo = fmt.Sprintf("%v", helmUrl) - settings.RepositoryConfig = helmRepo - } - } - } - - // Find out if there's an Agent chart already installed or need a fresh install - histClient := action.NewHistory(h.helmCfg) - histClient.Max = 1 - if _, err := histClient.Run(h.crAppName); err == driver.ErrReleaseNotFound { - h.log.Info("Instana Agent Chart Release does not exist. Installing it now.") - - installAction := action.NewInstall(h.helmCfg) - installAction.CreateNamespace = true // Namespace should already be there (same as Operator), but won't hurt - installAction.Namespace = h.crAppNamespace - installAction.ReleaseName = h.crAppName - installAction.RepoURL = helmRepo - installAction.PostRenderer = NewAgentChartPostRenderer(h, crdInstance) - installAction.Version = fixChartVersion(crdInstance.Spec.PinnedChartVersion, installAction.Devel) - - agentChart, err := h.loadAndValidateChart(installAction.ChartPathOptions) - if err != nil { - h.log.Error(err, "Failure loading or validating Instana Agent Helm Chart, cannot proceed installation") - return errors.Wrap(err, "loading Instana Agent Helm Chart failed") - } - - _, err = installAction.Run(agentChart, yamlMap) - if err != nil { - h.log.Error(err, "Failure installing Instana Agent Helm Chart, cannot proceed installation") - return errors.Wrap(err, "installation Instana Agent failed") - } - - } else if err == nil { - h.log.Info("Found existing Instana Agent Chart Release. Upgrade existing installation.") - - upgradeAction := action.NewUpgrade(h.helmCfg) - upgradeAction.Namespace = h.crAppNamespace - upgradeAction.RepoURL = helmRepo - upgradeAction.MaxHistory = 1 - upgradeAction.PostRenderer = NewAgentChartPostRenderer(h, crdInstance) - upgradeAction.Version = fixChartVersion(crdInstance.Spec.PinnedChartVersion, upgradeAction.Devel) - - agentChart, err := h.loadAndValidateChart(upgradeAction.ChartPathOptions) - if err != nil { - h.log.Error(err, "Failure loading or validating Instana Agent Helm Chart, cannot proceed installation") - return errors.Wrap(err, "loading Instana Agent Helm Chart failed") - } - - _, err = upgradeAction.Run(h.crAppName, agentChart, yamlMap) - if err != nil { - h.log.Error(err, "Failure installing Instana Agent Helm Chart, cannot proceed installation") - return errors.Wrap(err, "installation Instana Agent failed") - } - - } else { - h.log.Error(err, "Unexpected error trying to fetch Instana Agent Chart install history") - return err - } - - h.log.Info("Done installing / upgrading Instana Agent Helm Chart") - return nil -} - -func (h *HelmReconciliation) mapCRDToYaml(crdInstance *instanaV1.InstanaAgent) (map[string]interface{}, error) { - specYaml, err := yaml.Marshal(crdInstance.Spec) - if err != nil { - return nil, errors.Wrapf(err, "failed marshaling to yaml") - } - yamlMap := map[string]interface{}{} - if err := yaml.Unmarshal(specYaml, &yamlMap); err != nil { - return nil, err - } - return yamlMap, nil -} - -func fixChartVersion(version string, devel bool) string { - if (len(version) == 0 || version == "") && devel { - return ">0.0.0-0" - } else { - return version - } -} - -func (h *HelmReconciliation) loadAndValidateChart(chartOptions action.ChartPathOptions) (*chart.Chart, error) { - chartPath, err := chartOptions.LocateChart(agentChartName, settings) - if err != nil { - // The Chart might have never been downloaded or got removed. Update the repo and fetch Chart locally - if err := h.repoUpdate(); err != nil { - return nil, err - } - } - - agentChart, err := loader.Load(chartPath) - if err != nil { - return nil, err - } - - if agentChart.Metadata.Deprecated { - h.log.Info("NOTE! This chart is deprecated!") - } - - if err := h.checkIfInstallable(agentChart); err != nil { - return nil, err - } - - // NOTE, the 'original' code from the cmd/helm/install.go package contained code to also check and update Chart Dependencies. - // Our Agent Chart does not have any dependencies on other Charts, so for simplicity this code is not needed and omitted for now. - - return agentChart, nil -} - -func (h *HelmReconciliation) repoUpdate() error { - entry := &repo.Entry{Name: agentChartName, URL: helmRepo} - r, err := repo.NewChartRepository(entry, getter.All(settings)) - if err != nil { - return err - } - if _, err := r.DownloadIndexFile(); err != nil { - h.log.Info(fmt.Sprintf("...Unable to get an update from the %q chart repository (%s):\n\t%s\n", r.Config.Name, r.Config.URL, err)) - } else { - h.log.Info(fmt.Sprintf("...Successfully got an update from the %q chart repository\n", r.Config.Name)) - } - return nil -} - -func (h *HelmReconciliation) checkIfInstallable(ch *chart.Chart) error { - switch ch.Metadata.Type { - case "", "application": - return nil - } - return errors.Errorf("%s charts are not installable", ch.Metadata.Type) -} - -func (h *HelmReconciliation) getApiVersions() (*chartutil.VersionSet, error) { - if h.helmCfg.Capabilities != nil { - return &h.helmCfg.Capabilities.APIVersions, nil - } - dc, err := h.helmCfg.RESTClientGetter.ToDiscoveryClient() - if err != nil { - return nil, errors.Wrap(err, "could not get Kubernetes discovery client") - } - // force a discovery cache invalidation to always fetch the latest server version/capabilities. - dc.Invalidate() - // Issue #6361: - // Client-Go emits an error when an API service is registered but unimplemented. - // We trap that error here and print a warning. But since the discovery client continues - // building the API object, it is correctly populated with all valid APIs. - // See https://github.com/kubernetes/kubernetes/issues/72051#issuecomment-521157642 - apiVersions, err := action.GetVersionSet(dc) - if err != nil { - if discovery.IsGroupDiscoveryFailedError(err) { - h.log.Error(err, "WARNING: The Kubernetes server has an orphaned API service. Server reports: %s") - h.log.Info("WARNING: To fix this, kubectl delete apiservice ") - } else { - return nil, errors.Wrap(err, "could not get apiVersions from Kubernetes") - } - } - return &apiVersions, err -} diff --git a/controllers/reconciliation/helm/post_renderer.go b/controllers/reconciliation/helm/post_renderer.go deleted file mode 100644 index 45577304..00000000 --- a/controllers/reconciliation/helm/post_renderer.go +++ /dev/null @@ -1,139 +0,0 @@ -/* - * (c) Copyright IBM Corp. 2021 - * (c) Copyright Instana Inc. 2021 - */ - -package helm - -import ( - "bytes" - - "github.com/go-logr/logr" - instanaV1 "github.com/instana/instana-agent-operator/api/v1" - "helm.sh/helm/v3/pkg/action" - - appV1 "k8s.io/api/apps/v1" - v1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/cli-runtime/pkg/resource" - "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - "sigs.k8s.io/yaml" -) - -func NewAgentChartPostRenderer(h *HelmReconciliation, crdInstance *instanaV1.InstanaAgent) *AgentChartPostRenderer { - return &AgentChartPostRenderer{ - scheme: h.scheme, - helmCfg: h.helmCfg, - crdInstance: crdInstance, - log: h.log.WithName("postrenderer"), - } -} - -type AgentChartPostRenderer struct { - scheme *runtime.Scheme - crdInstance *instanaV1.InstanaAgent - helmCfg *action.Configuration - log logr.Logger -} - -func (p *AgentChartPostRenderer) Run(in *bytes.Buffer) (*bytes.Buffer, error) { - resourceList, err := p.helmCfg.KubeClient.Build(in, false) - if err != nil { - return nil, err - } - - out := bytes.Buffer{} - if err := resourceList.Visit(func(r *resource.Info, incomingErr error) error { - // For any incoming (parsing) errors, return with error to stop processing - if incomingErr != nil { - return incomingErr - } - - // Make sure we have Unstructured content (wrapped key-value map) to work with for the necessary modifications - var modifiedResource *unstructured.Unstructured - if objMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(r.Object); err == nil { - modifiedResource = &unstructured.Unstructured{Object: objMap} - } else { - return err - } - - if r.ObjectName() == "daemonsets/instana-agent" { - var err error // Shadow outer "err" because cannot use re-declaration - if modifiedResource, err = p.adjustDaemonsetRemoveLeaderElection(modifiedResource); err != nil { - return err - } - p.log.V(1).Info("Removing leader-elector sidecar from DaemonSet was successful") - } - - if !(r.ObjectName() == "clusterroles/instana-agent" || r.ObjectName() == "clusterrolebindings/instana-agent" || r.ObjectName() == "clusterroles/k8sensor" || r.ObjectName() == "clusterrolebindings/k8sensor") { - if err := controllerutil.SetControllerReference(p.crdInstance, modifiedResource, p.scheme); err != nil { - return err - } - p.log.V(1).Info("Setting controller reference for Object was successful", "ObjectName", r.ObjectName()) - } - - if err := p.writeToOutBuffer(modifiedResource, &out); err != nil { - return err - } - return nil - - }); err != nil { - return nil, err - } - - return &out, nil -} - -func (p *AgentChartPostRenderer) adjustDaemonsetRemoveLeaderElection(unstr *unstructured.Unstructured) (*unstructured.Unstructured, error) { - // Convert to a structured Go object which is easier to modify in this case - var ds = &appV1.DaemonSet{} - if err := runtime.DefaultUnstructuredConverter.FromUnstructured(unstr.Object, ds); err != nil { - return nil, err - } - - containerList := ds.Spec.Template.Spec.Containers - containerList = p.removeLeaderElectorContainer(containerList) - for i := range containerList { - ds.Spec.Template.Spec.Containers[i].Env = p.replaceLeaderElectorEnvVar(ds.Spec.Template.Spec.Containers[i].Env) - } - ds.Spec.Template.Spec.Containers = containerList - - if objMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(ds); err == nil { - return &unstructured.Unstructured{Object: objMap}, nil - } else { - return nil, err - } -} - -func (p *AgentChartPostRenderer) replaceLeaderElectorEnvVar(envList []v1.EnvVar) []v1.EnvVar { - for i, envVar := range envList { - if envVar.Name == "INSTANA_AGENT_LEADER_ELECTOR_PORT" { - envList = append(envList[:i], envList[i+1:]...) - } - } - envList = append(envList, v1.EnvVar{Name: "INSTANA_OPERATOR_MANAGED", Value: "true"}) - return envList -} - -func (p *AgentChartPostRenderer) removeLeaderElectorContainer(containerList []v1.Container) []v1.Container { - for i, container := range containerList { - if container.Name == "leader-elector" { - p.log.V(1).Info("Found leader-elector sidecar container", "container", container) - // Assume only a single 'leader elector' container so return immediately with the updated slice - return append(containerList[:i], containerList[i+1:]...) - } - } - return containerList -} - -func (p *AgentChartPostRenderer) writeToOutBuffer(modifiedResource *unstructured.Unstructured, out *bytes.Buffer) error { - outData, err := yaml.Marshal(modifiedResource) - if err != nil { - return err - } - if _, err := out.WriteString("---\n" + string(outData)); err != nil { - return err - } - return nil -} diff --git a/controllers/reconciliation/reconciliation.go b/controllers/reconciliation/reconciliation.go deleted file mode 100644 index f78c0af3..00000000 --- a/controllers/reconciliation/reconciliation.go +++ /dev/null @@ -1,25 +0,0 @@ -/* - * (c) Copyright IBM Corp. 2021 - * (c) Copyright Instana Inc. 2021 - */ - -package reconciliation - -import ( - "github.com/go-logr/logr" - instanaV1 "github.com/instana/instana-agent-operator/api/v1" - "github.com/instana/instana-agent-operator/controllers/reconciliation/helm" - "k8s.io/apimachinery/pkg/runtime" - ctrl "sigs.k8s.io/controller-runtime" -) - -type Reconciliation interface { - // CreateOrUpdate creates a new Agent installation or updates to the latest defined configuration - CreateOrUpdate(req ctrl.Request, crdInstance *instanaV1.InstanaAgent) error - // Delete removes the Agent installation from the cluster - Delete(req ctrl.Request, crdInstance *instanaV1.InstanaAgent) error -} - -func New(scheme *runtime.Scheme, log logr.Logger, crAppName string, crAppNamespace string) Reconciliation { - return helm.NewHelmReconciliation(scheme, log, crAppName, crAppNamespace) -} diff --git a/controllers/suite_test.go b/controllers/suite_test.go index b012819b..b493f5cd 100644 --- a/controllers/suite_test.go +++ b/controllers/suite_test.go @@ -9,11 +9,10 @@ import ( "context" "path/filepath" "testing" + "time" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - v1 "k8s.io/api/core/v1" - + "k8s.io/apimachinery/pkg/runtime/schema" ctrl "sigs.k8s.io/controller-runtime" . "github.com/onsi/ginkgo" @@ -26,105 +25,419 @@ import ( "sigs.k8s.io/controller-runtime/pkg/log/zap" instanav1 "github.com/instana/instana-agent-operator/api/v1" - //+kubebuilder:scaffold:imports + "github.com/instana/instana-agent-operator/pkg/collections/list" + instanaclient "github.com/instana/instana-agent-operator/pkg/k8s/client" + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/helpers" + "github.com/instana/instana-agent-operator/pkg/pointer" + // +kubebuilder:scaffold:imports ) -// These tests use Ginkgo (BDD-style Go testing framework). Refer to -// http://onsi.github.io/ginkgo/ to learn more about Ginkgo. - const ( - InstanaAgentNamespace = "instana-agent" + instanaAgentName = "instana-agent" + instanaAgentNamespace = "default" +) + +var agent = &instanav1.InstanaAgent{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "instana.io/v1", + Kind: "InstanaAgent", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: instanaAgentName, + Namespace: instanaAgentNamespace, + Finalizers: []string{"test"}, + }, + Spec: instanav1.InstanaAgentSpec{ + Zone: instanav1.Name{Name: "test"}, + Cluster: instanav1.Name{Name: "test"}, + Agent: instanav1.BaseAgentSpec{ + Key: "test", + EndpointHost: "ingress-red-saas.instana.io", + EndpointPort: "443", + }, + K8sSensor: instanav1.K8sSpec{ + PodDisruptionBudget: instanav1.Enabled{Enabled: pointer.To(true)}, + }, + }, +} + +type object struct { + gvk schema.GroupVersionKind + key client.ObjectKey +} + +// agent resources +var ( + agentConfigMap = object{ + gvk: schema.GroupVersionKind{ + Version: "v1", + Kind: "ConfigMap", + }, + key: client.ObjectKey{ + Name: instanaAgentName, + Namespace: instanaAgentNamespace, + }, + } + agentDaemonset = object{ + gvk: schema.GroupVersionKind{ + Group: "apps", + Version: "v1", + Kind: "DaemonSet", + }, + key: client.ObjectKey{ + Name: instanaAgentName, + Namespace: instanaAgentNamespace, + }, + } + agentHeadlessService = object{ + gvk: schema.GroupVersionKind{ + Version: "v1", + Kind: "Service", + }, + key: client.ObjectKey{ + Name: instanaAgentName + "-headless", + Namespace: instanaAgentNamespace, + }, + } + agentService = object{ + gvk: schema.GroupVersionKind{ + Version: "v1", + Kind: "Service", + }, + key: client.ObjectKey{ + Name: instanaAgentName, + Namespace: instanaAgentNamespace, + }, + } + agentKeysSecret = object{ + gvk: schema.GroupVersionKind{ + Version: "v1", + Kind: "Secret", + }, + key: client.ObjectKey{ + Name: instanaAgentName, + Namespace: instanaAgentNamespace, + }, + } + agentContainerSecret = object{ + gvk: schema.GroupVersionKind{ + Version: "v1", + Kind: "Secret", + }, + key: client.ObjectKey{ + Name: instanaAgentName + "-containers-instana-io", + Namespace: instanaAgentNamespace, + }, + } + agentServiceAccount = object{ + gvk: schema.GroupVersionKind{ + Version: "v1", + Kind: "ServiceAccount", + }, + key: client.ObjectKey{ + Name: instanaAgentName, + Namespace: instanaAgentNamespace, + }, + } ) +// k8sensor resources +var ( + k8SensorConfigMap = object{ + gvk: schema.GroupVersionKind{ + Version: "v1", + Kind: "ConfigMap", + }, + key: client.ObjectKey{ + Name: instanaAgentName + "-k8sensor", + Namespace: instanaAgentNamespace, + }, + } + k8SensorDeployment = object{ + gvk: schema.GroupVersionKind{ + Group: "apps", + Version: "v1", + Kind: "Deployment", + }, + key: client.ObjectKey{ + Name: instanaAgentName + "-k8sensor", + Namespace: instanaAgentNamespace, + }, + } + k8SensorPdb = object{ + gvk: schema.GroupVersionKind{ + Group: "policy", + Version: "v1", + Kind: "PodDisruptionBudget", + }, + key: client.ObjectKey{ + Name: instanaAgentName + "-k8sensor", + Namespace: instanaAgentNamespace, + }, + } + k8SensorClusterRole = object{ + gvk: schema.GroupVersionKind{ + Group: "rbac.authorization.k8s.io", + Version: "v1", + Kind: "ClusterRole", + }, + key: client.ObjectKey{ + Name: instanaAgentName + "-k8sensor", + Namespace: instanaAgentNamespace, + }, + } + k8SensorClusterRoleBinding = object{ + gvk: schema.GroupVersionKind{ + Group: "rbac.authorization.k8s.io", + Version: "v1", + Kind: "ClusterRoleBinding", + }, + key: client.ObjectKey{ + Name: instanaAgentName + "-k8sensor", + Namespace: instanaAgentNamespace, + }, + } + k8SensorServiceAccount = object{ + gvk: schema.GroupVersionKind{ + Version: "v1", + Kind: "ServiceAccount", + }, + key: client.ObjectKey{ + Name: instanaAgentName + "-k8sensor", + Namespace: instanaAgentNamespace, + }, + } +) + +// These tests use Ginkgo (BDD-style Go testing framework). Refer to +// http://onsi.github.io/ginkgo/ to learn more about Ginkgo. + var cfg *rest.Config var k8sClient client.Client +var instanaClient instanaclient.InstanaAgentClient var testEnv *envtest.Environment var mgrCancel context.CancelFunc +var ctx context.Context +var cancel context.CancelFunc func TestAPIs(t *testing.T) { RegisterFailHandler(Fail) - RunSpecs(t, - "Controller Suite") + RunSpecs( + t, + "Controller Suite", + ) } -var _ = BeforeSuite(func() { - logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) +var _ = BeforeSuite( + func() { + ctx, cancel = context.WithCancel(context.Background()) - By("bootstrapping test environment") - testEnv = &envtest.Environment{ - CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")}, - ErrorIfCRDPathMissing: true, - CRDInstallOptions: envtest.CRDInstallOptions{CleanUpAfterUse: true}, - } + logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) - var err error - cfg, err = testEnv.Start() - Expect(err).NotTo(HaveOccurred()) - Expect(cfg).NotTo(BeNil()) - - err = instanav1.AddToScheme(scheme.Scheme) - Expect(err).NotTo(HaveOccurred()) + err := instanav1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) - //+kubebuilder:scaffold:scheme + By("bootstrapping test environment") + testEnv = &envtest.Environment{ + CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")}, + ErrorIfCRDPathMissing: true, + CRDInstallOptions: envtest.CRDInstallOptions{CleanUpAfterUse: true}, + Scheme: scheme.Scheme, + } - // "Live" client to interact directly with the Kubernetes test cluster. The Manager client does caching but for verification - // we want to read directly what got created. - k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) - Expect(err).NotTo(HaveOccurred()) - Expect(k8sClient).NotTo(BeNil()) + cfg, err = testEnv.Start() + Expect(err).NotTo(HaveOccurred()) + Expect(cfg).NotTo(BeNil()) - // Set up the Manager as we'd do in the main.go, but disable some (unneeded) config and use the above cluster configuration - k8sManager, err := ctrl.NewManager(cfg, ctrl.Options{ - Namespace: "instana-agent", - Scheme: scheme.Scheme, - }) - Expect(err).ToNot(HaveOccurred()) + // +kubebuilder:scaffold:scheme - // Create the Reconciler / Controller and register with the Manager (just like in the main.go) - err = Add(k8sManager) - Expect(err).ToNot(HaveOccurred()) + // Set up the Manager as we'd do in the main.go, but disable some (unneeded) config and use the above cluster configuration + k8sManager, err := ctrl.NewManager( + cfg, ctrl.Options{ + Scheme: scheme.Scheme, + }, + ) + Expect(err).ToNot(HaveOccurred()) - var mgrCtx context.Context - mgrCtx, mgrCancel = context.WithCancel(ctrl.SetupSignalHandler()) + k8sClient = k8sManager.GetClient() + instanaClient = instanaclient.NewClient(k8sClient) - go func() { - err = k8sManager.Start(mgrCtx) + // Create the Reconciler / Controller and register with the Manager (just like in the main.go) + err = Add(k8sManager) Expect(err).ToNot(HaveOccurred()) - }() -}, 60) + var mgrCtx context.Context + mgrCtx, mgrCancel = context.WithCancel(ctrl.SetupSignalHandler()) -var _ = AfterSuite(func() { - By("tearing down the test environment") - if mgrCancel != nil { - mgrCancel() - } - err := testEnv.Stop() - Expect(err).NotTo(HaveOccurred()) -}) - -// SetupTest will set up a testing environment. -// This includes: -// * creating a Namespace to be used during the test -// * cleanup the Namespace after the test -// Call this function at the start of each of your tests. -func SetupTest(ctx context.Context) *v1.Namespace { - ns := &v1.Namespace{} - - BeforeEach(func() { - *ns = v1.Namespace{ - ObjectMeta: metav1.ObjectMeta{Name: InstanaAgentNamespace}, + err = Add(k8sManager) + Expect(err).NotTo(HaveOccurred()) + + go func() { + err = k8sManager.Start(mgrCtx) + Expect(err).ToNot(HaveOccurred()) + }() + + }, 60, +) + +var _ = AfterSuite( + func() { + By("tearing down the test environment") + if mgrCancel != nil { + mgrCancel() } + err := testEnv.Stop() + Expect(err).NotTo(HaveOccurred()) - err := k8sClient.Create(ctx, ns) - Expect(err).NotTo(HaveOccurred(), "failed to create \"instana-agent\" test namespace") - }) + cancel() + }, +) + +func exist(obj object) bool { + res, err := instanaClient.Exists(ctx, obj.gvk, obj.key).Get() + return res && err == nil +} - AfterEach(func() { - err := k8sClient.Delete(ctx, ns) - Expect(err).NotTo(HaveOccurred(), "failed to delete \"instana-agent\" test namespace") - }) +func allExist(o ...object) func() bool { + return func() bool { + objects := list.NewConditions(o) - return ns + return objects.All(exist) + } +} + +func doNotExist(obj object) bool { + res, err := instanaClient.Exists(ctx, obj.gvk, obj.key).Get() + return !res && err == nil +} + +func noneExist(o ...object) func() bool { + return func() bool { + objects := list.NewConditions(o) + + return objects.All(doNotExist) + } } + +func failTest(err error) { + Expect(err).NotTo(HaveOccurred()) +} + +var _ = Describe( + "An InstanaAgent CR", func() { + When( + "the CR is created", func() { + Specify( + "using the k8s client", func() { + instanaClient.Apply(ctx, agent).OnFailure(failTest) + }, + ) + Specify( + "the controller should create all of the expected resources", func() { + Eventually( + allExist( + agentConfigMap, + agentDaemonset, + agentHeadlessService, + agentService, + agentServiceAccount, + agentKeysSecret, + k8SensorConfigMap, + k8SensorDeployment, + k8SensorServiceAccount, + k8SensorClusterRole, + k8SensorClusterRoleBinding, + k8SensorPdb, + ), + ). + Within(10 * time.Second). + ProbeEvery(time.Second). + Should(BeTrue()) + }, + ) + }, + ) + When( + "the CR is updated", func() { + Specify( + "using the k8s client", func() { + agentNew := agent.DeepCopy() + + agentNew.Spec.K8sSensor.PodDisruptionBudget.Enabled = pointer.To(false) + agentNew.Spec.Agent.KeysSecret = "test" + agentNew.Spec.Agent.ImageSpec.Name = helpers.ContainersInstanaIORegistry + "/instana-agent" + + err := k8sClient.Patch(ctx, agentNew, client.MergeFrom(agent)) + Expect(err).NotTo(HaveOccurred()) + }, + ) + Specify( + "the controller should update the resources", func() { + Eventually( + allExist( + agentConfigMap, + agentDaemonset, + agentHeadlessService, + agentService, + agentServiceAccount, + agentContainerSecret, + k8SensorConfigMap, + k8SensorDeployment, + k8SensorServiceAccount, + k8SensorClusterRole, + k8SensorClusterRoleBinding, + ), + ). + Within(10 * time.Second). + ProbeEvery(time.Second). + Should(BeTrue()) + Eventually( + noneExist( + agentKeysSecret, + k8SensorPdb, + ), + ). + Within(10 * time.Second). + ProbeEvery(time.Second). + Should(BeTrue()) + }, + ) + }, + ) + When( + "the CR is deleted", func() { + Specify( + "using the k8s client", func() { + err := k8sClient.Delete(ctx, agent) + Expect(err).NotTo(HaveOccurred()) + }, + ) + Specify( + "the controller should delete all of the expected resources", func() { + Eventually( + noneExist( + agentConfigMap, + agentDaemonset, + agentHeadlessService, + agentService, + agentServiceAccount, + agentKeysSecret, + agentContainerSecret, + k8SensorConfigMap, + k8SensorDeployment, + k8SensorServiceAccount, + k8SensorClusterRole, + k8SensorClusterRoleBinding, + k8SensorPdb, + ), + ). + Within(10 * time.Second). + ProbeEvery(time.Second). + Should(BeTrue()) + }, + ) + }, + ) + }, +) diff --git a/controllers/util.go b/controllers/util.go new file mode 100644 index 00000000..38a593dd --- /dev/null +++ b/controllers/util.go @@ -0,0 +1,39 @@ +package controllers + +import ( + "context" + + "github.com/go-logr/logr" + logf "sigs.k8s.io/controller-runtime/pkg/log" + + instanav1 "github.com/instana/instana-agent-operator/api/v1" + "github.com/instana/instana-agent-operator/pkg/k8s/operator/operator_utils" +) + +func (r *InstanaAgentReconciler) isOpenShift(ctx context.Context, operatorUtils operator_utils.OperatorUtils) ( + bool, + reconcileReturn, +) { + log := logf.FromContext(ctx) + + isOpenShiftRes := operatorUtils.ClusterIsOpenShift() + answer, err := isOpenShiftRes.Get() + + switch isOpenShiftRes.IsSuccess() { + case true: + log.V(1).Info("successfully detected whether cluster is OpenShift", "IsOpenShift", answer) + return answer, reconcileContinue() + default: + log.Error(err, "failed to determine if cluster is OpenShift") + return false, reconcileFailure(err) + } +} + +func (r *InstanaAgentReconciler) loggerFor(ctx context.Context, agent *instanav1.InstanaAgent) logr.Logger { + return logf.FromContext(ctx).WithValues( + "Generation", + agent.Generation, + "UID", + agent.UID, + ) +} diff --git a/go.mod b/go.mod index 822ffd05..c4a59095 100644 --- a/go.mod +++ b/go.mod @@ -3,157 +3,159 @@ module github.com/instana/instana-agent-operator go 1.22 require ( - github.com/blang/semver v3.5.1+incompatible - github.com/go-logr/logr v0.4.0 - github.com/google/go-cmp v0.5.5 - github.com/onsi/ginkgo v1.16.4 - github.com/onsi/gomega v1.13.0 + github.com/Masterminds/goutils v1.1.1 + github.com/Masterminds/semver/v3 v3.2.1 + github.com/go-logr/logr v1.4.1 + github.com/onsi/ginkgo v1.16.5 + github.com/onsi/gomega v1.31.1 github.com/pkg/errors v0.9.1 - github.com/procyon-projects/chrono v1.0.0 - helm.sh/helm/v3 v3.7.0 - k8s.io/api v0.22.1 - k8s.io/apimachinery v0.22.1 - k8s.io/cli-runtime v0.22.1 - k8s.io/client-go v0.22.1 - sigs.k8s.io/controller-runtime v0.9.2 - sigs.k8s.io/yaml v1.2.0 + github.com/stretchr/testify v1.8.4 + go.uber.org/mock v0.4.0 + golang.org/x/net v0.21.0 + gopkg.in/yaml.v3 v3.0.1 + helm.sh/helm/v3 v3.14.2 + k8s.io/api v0.29.2 + k8s.io/apimachinery v0.29.2 + k8s.io/client-go v0.29.2 + sigs.k8s.io/controller-runtime v0.17.2 ) require ( - cloud.google.com/go v0.81.0 // indirect + github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect - github.com/Azure/go-autorest v14.2.0+incompatible // indirect - github.com/Azure/go-autorest/autorest v0.11.18 // indirect - github.com/Azure/go-autorest/autorest/adal v0.9.13 // indirect - github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect - github.com/Azure/go-autorest/logger v0.2.1 // indirect - github.com/Azure/go-autorest/tracing v0.6.0 // indirect - github.com/BurntSushi/toml v0.3.1 // indirect - github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd // indirect - github.com/Masterminds/goutils v1.1.1 // indirect - github.com/Masterminds/semver/v3 v3.1.1 // indirect - github.com/Masterminds/sprig/v3 v3.2.2 // indirect - github.com/Masterminds/squirrel v1.5.0 // indirect - github.com/Microsoft/go-winio v0.4.17 // indirect - github.com/Microsoft/hcsshim v0.8.24 // indirect - github.com/PuerkitoBio/purell v1.1.1 // indirect - github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect + github.com/BurntSushi/toml v1.3.2 // indirect + github.com/MakeNowJust/heredoc v1.0.0 // indirect + github.com/Masterminds/sprig/v3 v3.2.3 // indirect + github.com/Masterminds/squirrel v1.5.4 // indirect + github.com/Microsoft/hcsshim v0.11.4 // indirect github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 // indirect github.com/beorn7/perks v1.0.1 // indirect - github.com/cespare/xxhash/v2 v2.1.1 // indirect - github.com/containerd/containerd v1.5.14 // indirect - github.com/containerd/continuity v0.1.0 // indirect - github.com/cyphar/filepath-securejoin v0.2.2 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/chai2010/gettext-go v1.0.2 // indirect + github.com/containerd/containerd v1.7.11 // indirect + github.com/containerd/log v0.1.0 // indirect + github.com/cyphar/filepath-securejoin v0.2.4 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/docker/cli v20.10.7+incompatible // indirect - github.com/docker/distribution v2.7.1+incompatible // indirect - github.com/docker/docker v17.12.0-ce-rc1.0.20200618181300-9dc6525e6118+incompatible // indirect - github.com/docker/docker-credential-helpers v0.6.3 // indirect + github.com/docker/cli v24.0.6+incompatible // indirect + github.com/docker/distribution v2.8.2+incompatible // indirect + github.com/docker/docker v24.0.7+incompatible // indirect + github.com/docker/docker-credential-helpers v0.7.0 // indirect github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-metrics v0.0.1 // indirect - github.com/docker/go-units v0.4.0 // indirect - github.com/evanphx/json-patch v4.11.0+incompatible // indirect + github.com/docker/go-units v0.5.0 // indirect + github.com/emicklei/go-restful/v3 v3.11.3 // indirect + github.com/evanphx/json-patch v5.7.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/color v1.7.0 // indirect - github.com/form3tech-oss/jwt-go v3.2.3+incompatible // indirect - github.com/fsnotify/fsnotify v1.4.9 // indirect - github.com/go-errors/errors v1.0.1 // indirect - github.com/go-logr/zapr v0.4.0 // indirect - github.com/go-openapi/jsonpointer v0.19.5 // indirect - github.com/go-openapi/jsonreference v0.19.5 // indirect - github.com/go-openapi/swag v0.19.14 // indirect + github.com/fatih/color v1.13.0 // indirect + github.com/felixge/httpsnoop v1.0.3 // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/go-errors/errors v1.4.2 // indirect + github.com/go-gorp/gorp/v3 v3.1.0 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-logr/zapr v1.3.0 // indirect + github.com/go-openapi/jsonpointer v0.20.2 // indirect + github.com/go-openapi/jsonreference v0.20.4 // indirect + github.com/go-openapi/swag v0.22.9 // indirect github.com/gobwas/glob v0.2.3 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/golang/protobuf v1.5.2 // indirect + github.com/golang/protobuf v1.5.3 // indirect github.com/google/btree v1.0.1 // indirect - github.com/google/gofuzz v1.1.0 // indirect + github.com/google/gnostic-models v0.6.8 // indirect + github.com/google/go-cmp v0.6.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.2.0 // indirect - github.com/googleapis/gnostic v0.5.5 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/gorilla/mux v1.8.0 // indirect + github.com/gorilla/websocket v1.5.0 // indirect github.com/gosuri/uitable v0.0.4 // indirect github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect - github.com/huandu/xstrings v1.3.2 // indirect - github.com/imdario/mergo v0.3.12 // indirect - github.com/inconshreveable/mousetrap v1.0.0 // indirect - github.com/jmoiron/sqlx v1.3.1 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/huandu/xstrings v1.4.0 // indirect + github.com/imdario/mergo v0.3.16 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jmoiron/sqlx v1.3.5 // indirect github.com/josharian/intern v1.0.0 // indirect - github.com/json-iterator/go v1.1.11 // indirect - github.com/klauspost/compress v1.11.13 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/compress v1.16.0 // indirect github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect - github.com/lib/pq v1.10.0 // indirect + github.com/lib/pq v1.10.9 // indirect github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect - github.com/mailru/easyjson v0.7.6 // indirect - github.com/mattn/go-colorable v0.0.9 // indirect - github.com/mattn/go-isatty v0.0.4 // 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.17 // indirect github.com/mattn/go-runewidth v0.0.9 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect - github.com/mitchellh/copystructure v1.1.1 // indirect - github.com/mitchellh/go-wordwrap v1.0.0 // indirect - github.com/mitchellh/reflectwalk v1.0.1 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/go-wordwrap v1.0.1 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/moby/locker v1.0.1 // indirect github.com/moby/spdystream v0.2.0 // indirect - github.com/moby/term v0.0.0-20210610120745-9d4ed1856297 // 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.1 // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect github.com/morikuni/aec v1.0.0 // indirect - github.com/nxadm/tail v1.4.8 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect + github.com/nxadm/tail v1.4.11 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/opencontainers/image-spec v1.0.2 // indirect - github.com/opencontainers/runc v1.0.2 // indirect + github.com/opencontainers/image-spec v1.1.0-rc5 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_golang v1.11.0 // indirect - github.com/prometheus/client_model v0.2.0 // indirect - github.com/prometheus/common v0.26.0 // indirect - github.com/prometheus/procfs v0.6.0 // indirect - github.com/rubenv/sql-migrate v0.0.0-20210614095031-55d5740dbbcc // indirect - github.com/russross/blackfriday v1.5.2 // indirect - github.com/shopspring/decimal v1.2.0 // indirect - github.com/sirupsen/logrus v1.8.1 // indirect - github.com/spf13/cast v1.3.1 // indirect - github.com/spf13/cobra v1.2.1 // indirect + github.com/prometheus/client_golang v1.19.0 // indirect + github.com/prometheus/client_model v0.6.0 // indirect + github.com/prometheus/common v0.49.0 // indirect + github.com/prometheus/procfs v0.12.0 // indirect + github.com/rubenv/sql-migrate v1.5.2 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/shopspring/decimal v1.3.1 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + github.com/spf13/cast v1.5.0 // indirect + github.com/spf13/cobra v1.8.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/stretchr/testify v1.7.0 // indirect - github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect + github.com/stretchr/objx v0.5.2 // 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 v0.0.0-20181112141820-a009c3971eca // indirect - go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect - go.uber.org/atomic v1.7.0 // indirect - go.uber.org/multierr v1.6.0 // indirect - go.uber.org/zap v1.17.0 // indirect - golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a // indirect - golang.org/x/net v0.0.0-20210520170846-37e1c6afe023 // indirect - golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602 // indirect - golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect - golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect - golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d // indirect - golang.org/x/text v0.3.6 // indirect - golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect - golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect - gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect - google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c // indirect - google.golang.org/grpc v1.38.0 // indirect - google.golang.org/protobuf v1.27.1 // indirect - gopkg.in/gorp.v1 v1.7.2 // indirect + github.com/xlab/treeprint v1.2.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0 // indirect + go.opentelemetry.io/otel v1.19.0 // indirect + go.opentelemetry.io/otel/metric v1.19.0 // indirect + go.opentelemetry.io/otel/trace v1.19.0 // indirect + go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.26.0 // indirect + golang.org/x/crypto v0.19.0 // indirect + golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect + golang.org/x/oauth2 v0.17.0 // indirect + golang.org/x/sync v0.6.0 // indirect + golang.org/x/sys v0.17.0 // indirect + golang.org/x/term v0.17.0 // indirect + golang.org/x/text v0.14.0 // indirect + golang.org/x/time v0.5.0 // indirect + gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect + google.golang.org/appengine v1.6.8 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect + google.golang.org/grpc v1.58.3 // indirect + google.golang.org/protobuf v1.32.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect - k8s.io/apiextensions-apiserver v0.22.1 // indirect - k8s.io/apiserver v0.22.1 // indirect - k8s.io/component-base v0.22.1 // indirect - k8s.io/klog/v2 v2.9.0 // indirect - k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e // indirect - k8s.io/kubectl v0.22.1 // indirect - k8s.io/utils v0.0.0-20210707171843-4b05e18ac7d9 // indirect - oras.land/oras-go v0.4.0 // indirect - sigs.k8s.io/kustomize/api v0.8.11 // indirect - sigs.k8s.io/kustomize/kyaml v0.11.0 // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.1.2 // indirect + k8s.io/apiextensions-apiserver v0.29.2 // indirect + k8s.io/apiserver v0.29.2 // indirect + k8s.io/cli-runtime v0.29.0 // indirect + k8s.io/component-base v0.29.2 // indirect + k8s.io/klog/v2 v2.120.1 // indirect + k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect + k8s.io/kubectl v0.29.0 // indirect + k8s.io/utils v0.0.0-20240102154912-e7106e64919e // indirect + oras.land/oras-go v1.2.4 // indirect + sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect + sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3 // indirect + sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3 // 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 93ce8b16..04fe9fab 100644 --- a/go.sum +++ b/go.sum @@ -1,461 +1,160 @@ -bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= -cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= -cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= -cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= -cloud.google.com/go v0.81.0 h1:at8Tk2zUz63cLPR0JPWm5vp77pEZmzxEQBEfRKn1VV8= -cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= -github.com/Azure/go-ansiterm v0.0.0-20210608223527-2377c96fe795/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +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-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/Azure/go-autorest v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= -github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= -github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= -github.com/Azure/go-autorest/autorest v0.11.1/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw= -github.com/Azure/go-autorest/autorest v0.11.12/go.mod h1:eipySxLmqSyC5s5k1CLupqet0PSENBEDP93LQ9a8QYw= -github.com/Azure/go-autorest/autorest v0.11.18 h1:90Y4srNYrwOtAgVo3ndrQkTYn6kf1Eg/AjTFJ8Is2aM= -github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= -github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg= -github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= -github.com/Azure/go-autorest/autorest/adal v0.9.13 h1:Mp5hbtOePIzM8pJVRa3YLrWWmZtoxRXqUEzCfJt3+/Q= -github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= -github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= -github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= -github.com/Azure/go-autorest/autorest/mocks v0.4.0/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= -github.com/Azure/go-autorest/autorest/mocks v0.4.1 h1:K0laFcLE6VLTOwNgSxaGbUcLPuGXlNkbVvq4cW4nIHk= -github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= -github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= -github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg= -github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= -github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= -github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= -github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/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.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= -github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd h1:sjQovDkwrZp8u+gxLtPgKGjk5hCxuy2hrRejBTA9xFU= -github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E= -github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +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 v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= -github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= -github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= -github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= -github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8= -github.com/Masterminds/sprig/v3 v3.2.2/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk= -github.com/Masterminds/squirrel v1.5.0 h1:JukIZisrUXadA9pl3rMkjhiamxiB0cXiu+HGp/Y8cY8= -github.com/Masterminds/squirrel v1.5.0/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= -github.com/Masterminds/vcs v1.13.1/go.mod h1:N09YCmOQr6RLxC6UNHzuVwAdodYbbnycGHSmwVJjcKA= -github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= -github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= -github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= -github.com/Microsoft/go-winio v0.4.16-0.20201130162521-d1ffc52c7331/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= -github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= -github.com/Microsoft/go-winio v0.4.17-0.20210211115548-6eac466e5fa3/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= -github.com/Microsoft/go-winio v0.4.17-0.20210324224401-5516f17a5958/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= -github.com/Microsoft/go-winio v0.4.17 h1:iT12IBVClFevaf8PuVyi3UmZOVh4OqnaLxDTW2O6j3w= -github.com/Microsoft/go-winio v0.4.17/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= -github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= -github.com/Microsoft/hcsshim v0.8.7-0.20190325164909-8abdbb8205e4/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= -github.com/Microsoft/hcsshim v0.8.7/go.mod h1:OHd7sQqRFrYd3RmSgbgji+ctCwkbq2wbEYNSzOYtcBQ= -github.com/Microsoft/hcsshim v0.8.9/go.mod h1:5692vkUqntj1idxauYlpoINNKeqCiG6Sg38RRsjT5y8= -github.com/Microsoft/hcsshim v0.8.14/go.mod h1:NtVKoYxQuTLx6gEq0L96c9Ju4JbRJ4nY2ow3VK6a9Lg= -github.com/Microsoft/hcsshim v0.8.15/go.mod h1:x38A4YbHbdxJtc0sF6oIz+RG0npwSCAvn69iY6URG00= -github.com/Microsoft/hcsshim v0.8.16/go.mod h1:o5/SZqmR7x9JNKsW3pu+nqHm0MF8vbA+VxGOoXdC600= -github.com/Microsoft/hcsshim v0.8.18/go.mod h1:+w2gRZ5ReXQhFOrvSQeNfhrYB/dg3oDwTOcER2fw4I4= -github.com/Microsoft/hcsshim v0.8.24 h1:jP+GMeRXIR1sH1kG4lJr9ShmSjVrua5jmFZDtfYGkn4= -github.com/Microsoft/hcsshim v0.8.24/go.mod h1:4zegtUJth7lAvFyc6cH2gGQ5B3OFQim01nnU2M8jKDg= -github.com/Microsoft/hcsshim/test v0.0.0-20201218223536-d3e5debf77da/go.mod h1:5hlzMzRKMLyo42nCZ9oml8AdTlq/0cvIaBv6tK1RehU= -github.com/Microsoft/hcsshim/test v0.0.0-20210227013316-43a75bb4edd3/go.mod h1:mw7qgWloBUl75W/gVH3cQszUg1+gUITj7D6NY7ywVnY= -github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= -github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= -github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= -github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= +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= +github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA= +github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= +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.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= +github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= 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/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= -github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae/go.mod h1:CgnQgUtFrFz9mxFNtED3jI5tLDjKlOM+oUF/sTk6ps0= -github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= -github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= -github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= -github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +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-20200428143746-21a406dcc535 h1:4daAzAu0S6Vi7/lbWECcX0j45yZReDZ56BQsrVBOEEY= github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= -github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= -github.com/aws/aws-sdk-go v1.34.9/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= -github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= -github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/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/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= -github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= -github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= -github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= -github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= -github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= -github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= -github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= -github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= 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/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd h1:rFt+Y/IK1aEZkEHchZRSq9OQbsSzIT/OrI8YFFmRIng= github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b h1:otBG+dV+YK+Soembjv71DPz3uX/V/6MMlSyD9JBQ6kQ= github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50= github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 h1:nvj0OLI3YqYXer/kZD8Ri1aaunCxIEsOst1BVJswV0o= github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= -github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= -github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5/go.mod h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw= -github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw= -github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/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/cilium/ebpf v0.0.0-20200110133405-4032b1d8aae3/go.mod h1:MA5e5Lr8slmEg9bt0VpxxWqJlO4iwu3FBdHUzV7wQVg= -github.com/cilium/ebpf v0.0.0-20200702112145-1c8d4c9ef775/go.mod h1:7cR51M8ViRLIdUjrmSXlK9pkrsDlLHbO8jiB8X8JnOc= -github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs= -github.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= -github.com/cilium/ebpf v0.6.2/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= -github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo= -github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA= -github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI= -github.com/containerd/aufs v0.0.0-20200908144142-dab0cbea06f4/go.mod h1:nukgQABAEopAHvB6j7cnP5zJ+/3aVcE7hCYqvIwAHyE= -github.com/containerd/aufs v0.0.0-20201003224125-76a6863f2989/go.mod h1:AkGGQs9NM2vtYHaUen+NljV0/baGCAPELGm2q9ZXpWU= -github.com/containerd/aufs v0.0.0-20210316121734-20793ff83c97/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU= -github.com/containerd/aufs v1.0.0/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU= -github.com/containerd/btrfs v0.0.0-20201111183144-404b9149801e/go.mod h1:jg2QkJcsabfHugurUvvPhS3E08Oxiuh5W/g1ybB4e0E= -github.com/containerd/btrfs v0.0.0-20210316141732-918d888fb676/go.mod h1:zMcX3qkXTAi9GI50+0HOeuV8LU2ryCE/V2vG/ZBiTss= -github.com/containerd/btrfs v1.0.0/go.mod h1:zMcX3qkXTAi9GI50+0HOeuV8LU2ryCE/V2vG/ZBiTss= -github.com/containerd/cgroups v0.0.0-20190717030353-c4b9ac5c7601/go.mod h1:X9rLEHIqSf/wfK8NsPqxJmeZgW4pcfzdXITDrUSJ6uI= -github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko= -github.com/containerd/cgroups v0.0.0-20200531161412-0dbf7f05ba59/go.mod h1:pA0z1pT8KYB3TCXK/ocprsh7MAkoW8bZVzPdih9snmM= -github.com/containerd/cgroups v0.0.0-20200710171044-318312a37340/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo= -github.com/containerd/cgroups v0.0.0-20200824123100-0b889c03f102/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo= -github.com/containerd/cgroups v0.0.0-20210114181951-8a68de567b68/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE= -github.com/containerd/cgroups v1.0.1/go.mod h1:0SJrPIenamHDcZhEcJMNBB85rHcUsw4f25ZfBiPYRkU= -github.com/containerd/cgroups v1.0.3 h1:ADZftAkglvCiD44c77s5YmMqaP2pzVCFZvBmAlBdAP4= -github.com/containerd/cgroups v1.0.3/go.mod h1:/ofk34relqNjSGyqPrmEULrO4Sc8LJhvJmWbUCUKqj8= -github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= -github.com/containerd/console v0.0.0-20181022165439-0650fd9eeb50/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= -github.com/containerd/console v0.0.0-20191206165004-02ecf6a7291e/go.mod h1:8Pf4gM6VEbTNRIT26AyyU7hxdQU3MvAvxVI0sc00XBE= -github.com/containerd/console v1.0.1/go.mod h1:XUsP6YE/mKtz6bxc+I8UiKKTP04qjQL4qcS3XoQ5xkw= -github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ= -github.com/containerd/containerd v1.2.10/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.3.0-beta.2.0.20190828155532-0293cbd26c69/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.3.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.3.1-0.20191213020239-082f7e3aed57/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.4.0-beta.2.0.20200729163537-40b22ef07410/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.4.1/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.4.3/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.4.9/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.5.0-beta.1/go.mod h1:5HfvG1V2FsKesEGQ17k5/T7V960Tmcumvqn8Mc+pCYQ= -github.com/containerd/containerd v1.5.0-beta.3/go.mod h1:/wr9AVtEM7x9c+n0+stptlo/uBBoBORwEx6ardVcmKU= -github.com/containerd/containerd v1.5.0-beta.4/go.mod h1:GmdgZd2zA2GYIBZ0w09ZvgqEq8EfBp/m3lcVZIvPHhI= -github.com/containerd/containerd v1.5.0-rc.0/go.mod h1:V/IXoMqNGgBlabz3tHD2TWDoTJseu1FGOKuoA4nNb2s= -github.com/containerd/containerd v1.5.1/go.mod h1:0DOxVqwDy2iZvrZp2JUx/E+hS0UNTVn7dJnIOwtYR4g= -github.com/containerd/containerd v1.5.2/go.mod h1:0DOxVqwDy2iZvrZp2JUx/E+hS0UNTVn7dJnIOwtYR4g= -github.com/containerd/containerd v1.5.4/go.mod h1:sx18RgvW6ABJ4iYUw7Q5x7bgFOAB9B6G7+yO0XBc4zw= -github.com/containerd/containerd v1.5.14 h1:cHUbxL/N5eip6fAD09PR90UkpDR9QR2ETPA4frx8fGE= -github.com/containerd/containerd v1.5.14/go.mod h1:aYaCLQkjLNcWede2Cpa/q42RWFWXv9rPlmlUbRqxB3U= -github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= -github.com/containerd/continuity v0.0.0-20190815185530-f2a389ac0a02/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= -github.com/containerd/continuity v0.0.0-20191127005431-f65d91d395eb/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= -github.com/containerd/continuity v0.0.0-20200710164510-efbc4488d8fe/go.mod h1:cECdGN1O8G9bgKTlLhuPJimka6Xb/Gg7vYzCTNVxhvo= -github.com/containerd/continuity v0.0.0-20201208142359-180525291bb7/go.mod h1:kR3BEg7bDFaEddKm54WSmrol1fKWDU1nKYkgrcgZT7Y= -github.com/containerd/continuity v0.0.0-20210208174643-50096c924a4e/go.mod h1:EXlVlkqNba9rJe3j7w3Xa924itAMLgZH4UD/Q4PExuQ= -github.com/containerd/continuity v0.1.0 h1:UFRRY5JemiAhPZrr/uE0n8fMTLcZsUvySPr1+D7pgr8= -github.com/containerd/continuity v0.1.0/go.mod h1:ICJu0PwR54nI0yPEnJ6jcS+J7CZAUXrLh8lPo2knzsM= -github.com/containerd/fifo v0.0.0-20180307165137-3d5202aec260/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= -github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= -github.com/containerd/fifo v0.0.0-20200410184934-f15a3290365b/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0= -github.com/containerd/fifo v0.0.0-20201026212402-0724c46b320c/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0= -github.com/containerd/fifo v0.0.0-20210316144830-115abcc95a1d/go.mod h1:ocF/ME1SX5b1AOlWi9r677YJmCPSwwWnQ9O123vzpE4= -github.com/containerd/fifo v1.0.0/go.mod h1:ocF/ME1SX5b1AOlWi9r677YJmCPSwwWnQ9O123vzpE4= -github.com/containerd/go-cni v1.0.1/go.mod h1:+vUpYxKvAF72G9i1WoDOiPGRtQpqsNW/ZHtSlv++smU= -github.com/containerd/go-cni v1.0.2/go.mod h1:nrNABBHzu0ZwCug9Ije8hL2xBCYh/pjfMb1aZGrrohk= -github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= -github.com/containerd/go-runc v0.0.0-20190911050354-e029b79d8cda/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= -github.com/containerd/go-runc v0.0.0-20200220073739-7016d3ce2328/go.mod h1:PpyHrqVs8FTi9vpyHwPwiNEGaACDxT/N/pLcvMSRA9g= -github.com/containerd/go-runc v0.0.0-20201020171139-16b287bc67d0/go.mod h1:cNU0ZbCgCQVZK4lgG3P+9tn9/PaJNmoDXPpoJhDR+Ok= -github.com/containerd/go-runc v1.0.0/go.mod h1:cNU0ZbCgCQVZK4lgG3P+9tn9/PaJNmoDXPpoJhDR+Ok= -github.com/containerd/imgcrypt v1.0.1/go.mod h1:mdd8cEPW7TPgNG4FpuP3sGBiQ7Yi/zak9TYCG3juvb0= -github.com/containerd/imgcrypt v1.0.4-0.20210301171431-0ae5c75f59ba/go.mod h1:6TNsg0ctmizkrOgXRNQjAPFWpMYRWuiB6dSF4Pfa5SA= -github.com/containerd/imgcrypt v1.1.1-0.20210312161619-7ed62a527887/go.mod h1:5AZJNI6sLHJljKuI9IHnw1pWqo/F0nGDOuR9zgTs7ow= -github.com/containerd/imgcrypt v1.1.1/go.mod h1:xpLnwiQmEUJPvQoAapeb2SNCxz7Xr6PJrXQb0Dpc4ms= -github.com/containerd/nri v0.0.0-20201007170849-eb1350a75164/go.mod h1:+2wGSDGFYfE5+So4M5syatU0N0f0LbWpuqyMi4/BE8c= -github.com/containerd/nri v0.0.0-20210316161719-dbaa18c31c14/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY= -github.com/containerd/nri v0.1.0/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY= -github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= -github.com/containerd/ttrpc v0.0.0-20190828172938-92c8520ef9f8/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= -github.com/containerd/ttrpc v0.0.0-20191028202541-4f1b8fe65a5c/go.mod h1:LPm1u0xBw8r8NOKoOdNMeVHSawSsltak+Ihv+etqsE8= -github.com/containerd/ttrpc v1.0.1/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y= -github.com/containerd/ttrpc v1.0.2/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y= -github.com/containerd/ttrpc v1.1.0/go.mod h1:XX4ZTnoOId4HklF4edwc4DcqskFZuvXB1Evzy5KFQpQ= -github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc= -github.com/containerd/typeurl v0.0.0-20190911142611-5eb25027c9fd/go.mod h1:GeKYzf2pQcqv7tJ0AoCuuhtnqhva5LNU3U+OyKxxJpk= -github.com/containerd/typeurl v1.0.1/go.mod h1:TB1hUtrpaiO88KEK56ijojHS1+NeF0izUACaJW2mdXg= -github.com/containerd/typeurl v1.0.2/go.mod h1:9trJWW2sRlGub4wZJRTW83VtbOLS6hwcDZXTn6oPz9s= -github.com/containerd/zfs v0.0.0-20200918131355-0a33824f23a2/go.mod h1:8IgZOBdv8fAgXddBT4dBXJPtxyRsejFIpXoklgxgEjw= -github.com/containerd/zfs v0.0.0-20210301145711-11e8f1707f62/go.mod h1:A9zfAbMlQwE+/is6hi0Xw8ktpL+6glmqZYtevJgaB8Y= -github.com/containerd/zfs v0.0.0-20210315114300-dde8f0fda960/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= -github.com/containerd/zfs v0.0.0-20210324211415-d5c4544f0433/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= -github.com/containerd/zfs v1.0.0/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= -github.com/containernetworking/cni v0.7.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= -github.com/containernetworking/cni v0.8.0/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= -github.com/containernetworking/cni v0.8.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= -github.com/containernetworking/plugins v0.8.6/go.mod h1:qnw5mN19D8fIwkqW7oHHYDHVlzhJpcY6TQxn/fUyDDM= -github.com/containernetworking/plugins v0.9.1/go.mod h1:xP/idU2ldlzN6m4p5LmGiwRDjeJr6FLK6vuiUwoH7P8= -github.com/containers/ocicrypt v1.0.1/go.mod h1:MeJDzk1RJHv89LjsH0Sp5KTY3ZYkjXO/C+bKAeWFIrc= -github.com/containers/ocicrypt v1.1.0/go.mod h1:b8AOe0YR67uU8OqfVNcznfFpAzu3rdgUV4GP9qXPfu4= -github.com/containers/ocicrypt v1.1.1/go.mod h1:Dm55fwWm1YZAjYRaJ94z2mfZikIyIN4B0oB3dj3jFxY= -github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= -github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-iptables v0.4.5/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= -github.com/coreos/go-iptables v0.5.0/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= -github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= -github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd v0.0.0-20161114122254-48702e0da86b/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= -github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= -github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw= -github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/cyphar/filepath-securejoin v0.2.2 h1:jCwT2GTP+PY5nBz3c/YL5PAIbusElVrPujOBSCj8xRg= -github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= -github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ= -github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s= -github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8= -github.com/d2g/hardwareaddr v0.0.0-20190221164911-e7d9fbe030e4/go.mod h1:bMl4RjIciD2oAxI7DmWRx6gbeqrkoLqv3MV0vzNad+I= +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.11 h1:lfGKw3eU35sjV0aG2eYZTiwFEY1pCzxdzicHP3SZILw= +github.com/containerd/containerd v1.7.11/go.mod h1:5UluHxHTX2rdvYuZ5OJTC5m/KJNs0Zs9wVoJm9zf5ZE= +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/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= +github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= +github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= +github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= +github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/daviddengcn/go-colortext v0.0.0-20160507010035-511bcaf42ccd/go.mod h1:dv4zxwHi5C/8AeI+4gX4dCWOIvNi7I6JCSX0HvlKPgE= -github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= -github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0= -github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= -github.com/distribution/distribution/v3 v3.0.0-20210804104954-38ab4c606ee3 h1:rEK0juuU5idazw//KzUcL3yYwUU3DIe2OnfJwjDBqno= -github.com/distribution/distribution/v3 v3.0.0-20210804104954-38ab4c606ee3/go.mod h1:gt38b7cvVKazi5XkHvINNytZXgTEntyhtyM3HQz46Nk= -github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= -github.com/docker/cli v20.10.7+incompatible h1:pv/3NqibQKphWZiAskMzdz8w0PRbtTaEB+f6NwdU7Is= -github.com/docker/cli v20.10.7+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY= -github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug= -github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v17.12.0-ce-rc1.0.20200618181300-9dc6525e6118+incompatible h1:iWPIG7pWIsCwT6ZtHnTUpoVMnete7O/pzd9HFE3+tn8= -github.com/docker/docker v17.12.0-ce-rc1.0.20200618181300-9dc6525e6118+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker-credential-helpers v0.6.3 h1:zI2p9+1NQYdnG6sMU26EX4aVGlqbInSQxQXLvzJ4RPQ= -github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y= +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/docker/cli v24.0.6+incompatible h1:fF+XCQCgJjjQNIMjzaSmiKJSCcfcXb3TWTcc7GAneOY= +github.com/docker/cli v24.0.6+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= +github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v24.0.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig1seQen0cKYlM= +github.com/docker/docker v24.0.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A= +github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= -github.com/docker/go-events v0.0.0-20170721190031-9461782956ad/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= 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.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= -github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 h1:ZClxb8laGDf5arXfYcAtECDFgAgHklGI8CxgjHnXKJ4= github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= -github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= -github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= -github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153 h1:yUdfgN0XgIJw7foRItutHYUIhlcKzcSf5vDpdhQAKTc= -github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= -github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= -github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/emicklei/go-restful/v3 v3.11.3 h1:yagOQz/38xJmcNeZJtrUcKjkHRltIaIFXKWeG1SkWGE= +github.com/emicklei/go-restful/v3 v3.11.3/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= -github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= -github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evanphx/json-patch v4.11.0+incompatible h1:glyUF9yIYtMHzn8xaKw5rMhdWcwsYV8dZHIq5567/xs= -github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v5.7.0+incompatible h1:vgGkfT/9f8zE6tvSCe74nfpAVDQ2tG6yudJd8LBksgI= +github.com/evanphx/json-patch v5.7.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/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= -github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= -github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= -github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= -github.com/form3tech-oss/jwt-go v3.2.3+incompatible h1:7ZaBxOI7TMoYBfyA3cQHErNNyAWIKUMIwqxEtgHOs5c= -github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= -github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= +github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/foxcpp/go-mockdns v1.0.0 h1:7jBqxd3WDWwi/6WhDvacvH1XsN3rOLXyHM1uhvIx6FI= +github.com/foxcpp/go-mockdns v1.0.0/go.mod h1:lgRN6+KxQBawyIghpnl5CezHFGS9VLzvtVlwxvzXTQ4= +github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= +github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA= -github.com/fvbommel/sortorder v1.0.1/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0= -github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7 h1:LofdAjjjqCSXMwLGgOgnE+rdPuvX9DxCqaHwKy7i/ko= -github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= -github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= -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/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= -github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +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/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-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-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/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.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= -github.com/go-logr/logr v0.4.0 h1:K7/B1jt6fIBQVd4Owv2MqGQClcgf0R266+7C/QjRcLc= -github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= -github.com/go-logr/zapr v0.4.0 h1:uc1uML3hRYL9/ZZPdgHS/n8Nzo+eaYL/Efxkkamf7OM= -github.com/go-logr/zapr v0.4.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk= -github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= -github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= -github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= -github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= -github.com/go-openapi/jsonreference v0.19.5 h1:1WJP/wi4OjB4iV8KVbH73rQaoialJrqv8gitZLxGLtM= -github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= -github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= -github.com/go-openapi/spec v0.19.5/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk= -github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5Fng= -github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= -github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= -github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-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.20.2 h1:mQc3nmndL8ZBzStEo3JYF8wzmeWffDH4VbXz58sAx6Q= +github.com/go-openapi/jsonpointer v0.20.2/go.mod h1:bHen+N0u1KEO3YlmqOjTT9Adn1RfD91Ar825/PuiRVs= +github.com/go-openapi/jsonreference v0.20.4 h1:bKlDxQxQJgwpUSgOENiMPzCTBVuc7vTdXSSgNeAhojU= +github.com/go-openapi/jsonreference v0.20.4/go.mod h1:5pZJyJP2MnYCpoeoMAql78cCHauHj0V9Lhc506VOpw4= +github.com/go-openapi/swag v0.22.9 h1:XX2DssF+mQKM2DHsbgZK74y/zj4mo9I99+89xUmuZCE= +github.com/go-openapi/swag v0.22.9/go.mod h1:3/OXnFfnMAwBD099SwYRk7GD3xOrr1iL7d/XNLXVVwE= +github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= +github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= -github.com/gobuffalo/logger v1.0.3 h1:YaXOTHNPCvkqqA7w05A4v0k2tCdpr+sgFlgINbQ6gqc= -github.com/gobuffalo/logger v1.0.3/go.mod h1:SoeejUwldiS7ZsyCBphOGURmWdwUFXs0J7TCjEhjKxM= -github.com/gobuffalo/packd v1.0.0 h1:6ERZvJHfe24rfFmA9OaoKBdC7+c9sydrytMg8SdFGBM= -github.com/gobuffalo/packd v1.0.0/go.mod h1:6VTc4htmJRFB7u1m/4LeMTWjFoYrUiBkU9Fdec9hrhI= -github.com/gobuffalo/packr/v2 v2.8.1 h1:tkQpju6i3EtMXJ9uoF5GT6kB+LMTimDWD8Xvbz6zDVA= -github.com/gobuffalo/packr/v2 v2.8.1/go.mod h1:c/PLlOuTU+p3SybaJATW3H6lX/iK7xEz5OeMf+NnJpg= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +github.com/gobuffalo/logger v1.0.6 h1:nnZNpxYo0zx+Aj9RfMPBm+x9zAU2OayFh/xrAWi34HU= +github.com/gobuffalo/logger v1.0.6/go.mod h1:J31TBEHR1QLV2683OXTAItYIg8pv2JMHnF/quuAbMjs= +github.com/gobuffalo/packd v1.0.1 h1:U2wXfRr4E9DH8IdsDLlRFwTZTK7hLfq9qT/QHXGVe/0= +github.com/gobuffalo/packd v1.0.1/go.mod h1:PP2POP3p3RXGz7Jh6eYEf93S7vA2za6xM7QT85L4+VY= +github.com/gobuffalo/packr/v2 v2.8.3 h1:xE1yzvnO56cUC0sTpKR3DIbxZgB54AftTFMhB2XEWlY= +github.com/gobuffalo/packr/v2 v2.8.3/go.mod h1:0SahksCVcx4IMnigTjiFuyldmTrdTctXsOdiU5KwbKc= 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/godbus/dbus v0.0.0-20151105175453-c7fdd8b5cd55/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= -github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= -github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= -github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/godror/godror v0.24.2/go.mod h1:wZv/9vPiUib6tkoDl+AZ/QLf5YZgMravZ7jxH2eQWAE= -github.com/gofrs/flock v0.8.0/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= -github.com/gogo/googleapis v1.2.0/go.mod h1:Njal3psf3qN6dwBtQfUmBZh2ybovJ0tlu3o/AC7HYjU= -github.com/gogo/googleapis v1.4.0/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= -github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= -github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= -github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/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-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= @@ -463,163 +162,82 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= -github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golangplus/testing v0.0.0-20180327235837-af21d9c3145e/go.mod h1:0AA//k/eakGydO4jKRoRL2j92ZKSzTgj9tclaCrvXHk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/gomodule/redigo v1.8.2 h1:H5XSIre1MB5NbPYFp+i1NBbb5qN1W8Y8YAQoAYbkm8k= github.com/gomodule/redigo v1.8.2/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0= -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.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= +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.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +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/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= -github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 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.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= -github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= -github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= -github.com/googleapis/gnostic v0.5.5 h1:9fHAtK0uDfpveeqqo1hkEZJcFvYXAiCN3UutL8F9xHw= -github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= +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/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= -github.com/gorilla/mux v1.7.2/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 v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/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 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= -github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= -github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= -github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I= -github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= -github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= -github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= -github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= -github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +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-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/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/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= -github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= -github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= -github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= -github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw= -github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/imdario/mergo v0.3.10/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU= +github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= -github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= -github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= -github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA= -github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= -github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= -github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= -github.com/jmoiron/sqlx v1.3.1 h1:aLN7YINNZ7cYOPK3QC83dbM6KT0NMqVMw961TqrejlE= -github.com/jmoiron/sqlx v1.3.1/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ= -github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= -github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= +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.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= +github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= 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/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= 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.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ= -github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= -github.com/karrick/godirwalk v1.15.8 h1:7+rWAZPn9zuRxaIqqT8Ohs2Q2Ac0msBqwRdxNCr2VVs= -github.com/karrick/godirwalk v1.15.8/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk= -github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= -github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/karrick/godirwalk v1.16.1 h1:DynhcF+bztK8gooS0+NDJFrdNZjJ3gzVzC545UNA9iw= +github.com/karrick/godirwalk v1.16.1/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk= 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.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= -github.com/klauspost/compress v1.11.13 h1:eSvu8Tmq6j2psUJqJrLcWH6K3w5Dwc+qipbaA6eVEN4= -github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4= +github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= 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/kortschak/utter v1.0.1/go.mod h1:vSmSjbyrlKjjsL71193LmzBOKgwePk9DH6uFaWHIInc= -github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= 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.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw= @@ -627,792 +245,305 @@ github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtB 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/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.10.0 h1:Zx5DJFEYQXio93kgXnQ09fXNiUKsqv4OUEu2UtGcB1E= -github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +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/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= -github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= -github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= -github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= -github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/markbates/errx v1.1.0 h1:QDFeR+UP95dO12JgW+tgi2UVfo0V8YBHiUIOaeBPiEI= github.com/markbates/errx v1.1.0/go.mod h1:PLa46Oex9KNbVDZhKel8v1OT7hD5JZ2eI7AHhA0wswc= github.com/markbates/oncer v1.0.0 h1:E83IaVAHygyndzPimgUYJjbshhDTALZyXxvk9FOlQRY= github.com/markbates/oncer v1.0.0/go.mod h1:Z59JA581E9GP6w96jai+TGqafHPW+cPfRxz2aSZ0mcI= github.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI= github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= -github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho= -github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4= -github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs= -github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-oci8 v0.1.1/go.mod h1:wjDx6Xm9q7dFtHJvIlrI99JytznLw5wQ4R+9mNXJwGI= -github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= +github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= -github.com/mattn/go-shellwords v1.0.11/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= -github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI= +github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= -github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= -github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= -github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4= -github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= -github.com/mitchellh/cli v1.1.2/go.mod h1:6iaV0fGdElS6dPBx0EApTxHrcWvmJphyh2n8YBLPPZ4= +github.com/miekg/dns v1.1.25 h1:dFwPR6SfLtrSwgDcIq2bcU/gVutB4sNApq2HBdqcakg= +github.com/miekg/dns v1.1.25/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= -github.com/mitchellh/copystructure v1.1.1 h1:Bp6x9R1Wn16SIz3OfeDr0b7RnCG2OB66Y7PQyC/cvq4= -github.com/mitchellh/copystructure v1.1.1/go.mod h1:EBArHfARyrSWO/+Wyr9zwEkc6XMFB9XyNgFNmRkZZU4= -github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4= -github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= -github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= -github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= -github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A= +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-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= +github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/mitchellh/reflectwalk v1.0.1 h1:FVzMWA5RllMAKIdUSC8mdWo3XtwoecrH79BY70sEEpE= -github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= 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/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8= github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= -github.com/moby/sys/mountinfo v0.4.0/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= -github.com/moby/sys/mountinfo v0.4.1 h1:1O+1cHA1aujwEwwVMa2Xm2l+gIpUHyd3+D+d7LZh1kM= -github.com/moby/sys/mountinfo v0.4.1/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= -github.com/moby/sys/symlink v0.1.0/go.mod h1:GGDODQmbFOjFsXvfLVn3+ZRxkch54RkSiGqsZeMYowQ= -github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo= -github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc= -github.com/moby/term v0.0.0-20210610120745-9d4ed1856297 h1:yH0SvLzcbZxcJXho2yh7CqdENGMQe73Cw3woZBpPli0= -github.com/moby/term v0.0.0-20210610120745-9d4ed1856297/go.mod h1:vgPCkQMyxTZ7IDy8SXRufE172gr8+K/JE/7hHFxHW3A= +github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78= +github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI= +github.com/moby/term v0.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 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= 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/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/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= -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/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/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/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM= -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/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= -github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= -github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA= -github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= -github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY= +github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc= 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.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= -github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E= -github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= -github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= -github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= -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/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.15.0 h1:79HwNRBAZHOEwrczrgSOPy+eFTTlIGELKy5as+ClttY= +github.com/onsi/ginkgo/v2 v2.15.0/go.mod h1:HlxMHtYF57y6Dpf+mc5529KKmSq9h2FpCF+/ZkwUxKM= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= -github.com/onsi/gomega v1.13.0 h1:7lLHu94wT9Ij0o6EWWclhu0aOh32VxhkwEJvzuWPeak= -github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY= -github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= -github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= -github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= -github.com/opencontainers/go-digest v1.0.0-rc1.0.20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/onsi/gomega v1.31.1 h1:KYppCUK+bUgAZwHOu7EXVBKyQA6ILvOESHkn/tgoqvo= +github.com/onsi/gomega v1.31.1/go.mod h1:y40C95dwAD1Nz36SsEnxvfFe8FFfNxzI5eJ0EYGyAy0= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.0.0/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= -github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= -github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= -github.com/opencontainers/runc v1.0.0-rc8.0.20190926000215-3e425f80a8c9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= -github.com/opencontainers/runc v1.0.0-rc9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= -github.com/opencontainers/runc v1.0.0-rc93/go.mod h1:3NOsor4w32B2tC0Zbl8Knk4Wg84SM2ImC1fxBuqJ/H0= -github.com/opencontainers/runc v1.0.2 h1:opHZMaswlyxz1OuGpBE53Dwe4/xF7EZTY0A2L/FpCOg= -github.com/opencontainers/runc v1.0.2/go.mod h1:aTaHFFwQXuA71CiyxOdFFIorAoemI04suvGRQFzWTD0= -github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/runtime-spec v1.0.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/runtime-spec v1.0.2-0.20190207185410-29686dbc5559/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/runtime-spec v1.0.3-0.20200929063507-e6143ca7d51d/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs= -github.com/opencontainers/selinux v1.6.0/go.mod h1:VVGKuOLlE7v4PJyT6h7mNWvq1rzqiriPsEqVhc+svHE= -github.com/opencontainers/selinux v1.8.0/go.mod h1:RScLhm78qiWa2gbVCcGkC7tCGdgk3ogry1nUQF8Evvo= -github.com/opencontainers/selinux v1.8.2/go.mod h1:MUIHuUEvKB1wtJjQdOyYRgOnLD2xAPP8dBsCoU0KuF8= -github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= -github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= -github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/opencontainers/image-spec v1.1.0-rc5 h1:Ygwkfw9bpDvs+c9E34SdgGOj41dX/cbdlwvlWt0pnFI= +github.com/opencontainers/image-spec v1.1.0-rc5/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8= 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-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc= -github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE= +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/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= -github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= -github.com/procyon-projects/chrono v1.0.0 h1:edOeKOW2eGP8j3gI6wQWzyoUM0njjuxOlbtpguIkXqQ= -github.com/procyon-projects/chrono v1.0.0/go.mod h1:RwQ27W7hRaq+QUWN2yXU3BDG2FUyEQiKds8/M1FI5C8= -github.com/prometheus/client_golang v0.0.0-20180209125602-c332b6f63c06/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +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.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= -github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= -github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ= -github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= -github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU= +github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k= 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.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= -github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/common v0.0.0-20180110214958-89604d197083/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/client_model v0.6.0 h1:k1v3CzpSRUTrKMppY35TLwPvxHqBu0bYgxZzqGIgaos= +github.com/prometheus/client_model v0.6.0/go.mod h1:NTQHnmxFpouOD0DpvP4XujX3CdOAGQPoaGhyTchlyt8= 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.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= -github.com/prometheus/common v0.26.0 h1:iMAkS2TDoNWnKM+Kopnx/8tnEStIfpYA0ur0xQzzhMQ= -github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= -github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/common v0.49.0 h1:ToNTdK4zSnPVJmh698mGFkDor9wBI/iGaJy5dbH1EgI= +github.com/prometheus/common v0.49.0/go.mod h1:Kxm+EULxRbUkjGU6WFsQqo3ORzB4tyKvlWFOE9mB2sE= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.0-20190522114515-bc1a522cf7b1/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= -github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= -github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= -github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4= -github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= -github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= -github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.5.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rubenv/sql-migrate v0.0.0-20210614095031-55d5740dbbcc h1:BD7uZqkN8CpjJtN/tScAKiccBikU4dlqe/gNrkRaPY4= -github.com/rubenv/sql-migrate v0.0.0-20210614095031-55d5740dbbcc/go.mod h1:HFLT6i9iR4QBOF5rdCyjddC9t59ArqWJV2xx+jwcCMo= -github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= -github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= -github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= -github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= -github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= -github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= +github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= +github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/rubenv/sql-migrate v1.5.2 h1:bMDqOnrJVV/6JQgQ/MxOpU+AdO8uzYYA/TxFUBzFtS0= +github.com/rubenv/sql-migrate v1.5.2/go.mod h1:H38GW8Vqf8F0Su5XignRyaRcbXbJunSWxs+kmzlg0Is= +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/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= -github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= -github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= +github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= +github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= -github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= -github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= -github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= -github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= -github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -github.com/spf13/cobra v0.0.6/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= -github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= -github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= -github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= -github.com/spf13/cobra v1.2.1 h1:+KmjbUw1hriSNMF55oPrkZcb27aECyrj8V2ytv7kWDw= -github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= -github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= -github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= +github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= +github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= +github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= -github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= -github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= -github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8= -github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= -github.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 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 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= -github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= -github.com/stretchr/testify v0.0.0-20180303142811-b89eecf5ca5d/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +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 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= -github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= -github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= -github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= -github.com/tchap/go-patricia v2.2.6+incompatible/go.mod h1:bmLyhP68RS6kStMGxByiQ23RP/odRBOTVjwp2cDyi6I= -github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= -github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= -github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= -github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= -github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= -github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= -github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI= -github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= -github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= -github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= -github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI= -github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/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 v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= -github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= -github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca h1:1CFlNzQhALwjS9mBAUkycX616GzgsuYUOCHA5+HSlXI= -github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= -github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +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.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 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= -github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs= -github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= -go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= -go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= -go.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489/go.mod h1:yVHk9ub3CSBatqGNg7GRmsnfLWtoW60w4eDYfh7vHDg= -go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= -go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= -go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= -go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0= -go.etcd.io/etcd/pkg/v3 v3.5.0/go.mod h1:UzJGatBQ1lXChBkQF0AuAtkRQMYnHubxAEYIrC3MSsE= -go.etcd.io/etcd/raft/v3 v3.5.0/go.mod h1:UFOHSIvO/nKwd4lhkwabrTD3cqW5yVyYYf/KlD00Szc= -go.etcd.io/etcd/server/v3 v3.5.0/go.mod h1:3Ah5ruV+M+7RZr0+Y/5mNLwC+eQlni+mQmOVdCRJoS4= -go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= -go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= -go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= -go.opentelemetry.io/contrib v0.20.0/go.mod h1:G/EtFaa6qaN7+LxqfIAT3GiZa7Wv5DTBUzl5H4LY0Kc= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.20.0/go.mod h1:oVGt1LRbBOBq1A5BQLlUg9UaU/54aiHw8cgjV3aWZ/E= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.20.0/go.mod h1:2AboqHi0CiIZU0qwhtUfCYD1GeUzvvIXWNkhDt7ZMG4= -go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo= -go.opentelemetry.io/otel/exporters/otlp v0.20.0/go.mod h1:YIieizyaN77rtLJra0buKiNBOm9XQfkPEKBeuhoMwAM= -go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU= -go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw= -go.opentelemetry.io/otel/sdk v0.20.0/go.mod h1:g/IcepuwNsoiX5Byy2nNV0ySUF1em498m7hBWC279Yc= -go.opentelemetry.io/otel/sdk/export/metric v0.20.0/go.mod h1:h7RBNMsDJ5pmI1zExLi+bJK+Dr8NQCh0qGhm1KDnNlE= -go.opentelemetry.io/otel/sdk/metric v0.20.0/go.mod h1:knxiS8Xd4E/N+ZqKmUPf3gTTZ4/0TjTXukfxjzSTpHE= -go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw= -go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 h1:+FNtrFTmVw0YZGpBGX56XDee331t6JAXeK2bcyhLOOc= -go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5/go.mod h1:nmDLcffg48OtT/PSW0Hg7FvpRQsQh5OSqIylirxKC7o= -go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= -go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/goleak v1.1.10 h1:z+mqJhf6ss6BSfSM671tgKyZBFPTTJM+HLxnhPC3wu0= -go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= -go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= -go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= -go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.17.0 h1:MTjgFu6ZLKvY6Pvaqk97GlxNBuMpV4Hy/3P6tRGlI2U= -go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= -golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +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/net/http/otelhttp v0.45.0 h1:x8Z78aZx8cOF0+Kkazoc7lwUNMGy0LrzEMxTm4BbTxg= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0/go.mod h1:62CPTSry9QZtOaSsE3tOzhx6LzDhHnXJ6xHeMNNiM6Q= +go.opentelemetry.io/otel v1.19.0 h1:MuS/TNf4/j4IXsZuJegVzI1cwut7Qc00344rgH7p8bs= +go.opentelemetry.io/otel v1.19.0/go.mod h1:i0QyjOq3UPoTzff0PJB2N66fb4S0+rSbSB15/oyH9fY= +go.opentelemetry.io/otel/metric v1.19.0 h1:aTzpGtV0ar9wlV4Sna9sdJyII5jTVJEvKETPiOKwvpE= +go.opentelemetry.io/otel/metric v1.19.0/go.mod h1:L5rUsV9kM1IxCj1MmSdS+JQAcVm319EUrDVLrt7jqt8= +go.opentelemetry.io/otel/trace v1.19.0 h1:DFVQmlVbfVeOuBRrwdtaehRrWiL1JoVs9CPIQ1Dzxpg= +go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo= +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/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= +go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= +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.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= +go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a h1:kr2P4QFmQr29mSLA43kwrOcgcReGTfbE9N577tCTuBc= -golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= +golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ= +golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= -golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.1-0.20200828183125-ce943fd02449/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8= +golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190619014844-b5b0513f8c1b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210224082022-3d97a244fca7/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= -golang.org/x/net v0.0.0-20210520170846-37e1c6afe023 h1:ADo5wSpq2gqaCGQWzk7S5vd//0iyyLeAratkEoG5dLE= -golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602 h1:0Ja1LBD+yisY6RWM/BH7TJVXWsSjs2VwBSmvSX4HdBc= -golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.17.0 h1:6m3ZPmLEFdVxKKWnKq4VqZ60gutO35zm+zrAHVmHyDQ= +golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-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-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190514135907-3a4b5fb9f71f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190522044717-8097e1b27ff5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190812073006-9eafafc0a87e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191002063906-3421d5a6bb1c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200120151820-655fe14d7479/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200817155316-9781c653f443/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200916030750-2334cc1a136f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200922070232-aee5d888a860/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201117170446-d9b008d0a637/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201202213521-69691e467435/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/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-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d h1:SZxvLBoTP5yHO3Frd4z4vrF+DBX9vMVanchswa69toE= -golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +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.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= +golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20210611083556-38a9dc6acbc6/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac h1:7zkz7BUtwNFFqcowJ+RIgu2MaV/MapERkDIy+mwPyjs= -golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200308013534-11ec41452d41/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= -golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.1.2 h1:kRBLX7v7Af8W7Gdbbc908OJcdgtK8bOz9Uaj8/F1ACA= -golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ= +golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gomodules.xyz/jsonpatch/v2 v2.2.0 h1:4pT439QV83L+G9FkcCriY6EkpcK6r6bK+A5FBUMI7qY= -gomodules.xyz/jsonpatch/v2 v2.2.0/go.mod h1:WXp+iVDkoLQqPudfQ9GBlwB2eZ5DKOnjQZCYdOS8GPY= -google.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= -google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= -google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= -google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= -google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= -google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= +gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= +gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/cloud v0.0.0-20151119220103-975617b05ea8/go.mod h1:0H1ncTHf11KCFhTc/+EFRbzSCOZx+VUbRMk55Yv5MYk= +google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= +google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190522204451-c2c4e71fbf69/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200117163144-32f20d992d24/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= -google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c h1:wtujag7C+4D6KMoulW9YauvK2lgdvCMS260jsqqBXr0= -google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d h1:uvYuEyMHKNt+lT4K3bN6fGswmK8qSvcreM3BwjDh+y4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= -google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= -google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.38.0 h1:/9BgsAsa5nWe26HqOlvlgJnqBuktYOLCgjCPqsa56W0= -google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= +google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= 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= @@ -1421,152 +552,69 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= -google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= +google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= +google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20141024133853-64131543e789/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/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/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= -gopkg.in/gorp.v1 v1.7.2 h1:j3DWlAyGVv8whO7AcIWznQ2Yj7yJkn34B8s63GViAAw= -gopkg.in/gorp.v1 v1.7.2/go.mod h1:Wo3h+DBQZIxATwftsglhdD/62zRFPhGhTiu5jUJmCaw= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= -gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= -gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= -gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= -gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= 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/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= -gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= -gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= -gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= -gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= -helm.sh/helm/v3 v3.7.0 h1:jRZCCdrOEfJI1LgStRAbmDJQkAwZkFy6gr4OlwrE2Ro= -helm.sh/helm/v3 v3.7.0/go.mod h1:DajHtQTe8KrjNmvy5gxWkosFKaADrS3uRS5EkDtsmI4= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o= +gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g= +helm.sh/helm/v3 v3.14.2 h1:V71fv+NGZv0icBlr+in1MJXuUIHCiPG1hW9gEBISTIA= +helm.sh/helm/v3 v3.14.2/go.mod h1:2itvvDv2WSZXTllknfQo6j7u3VVgMAvm8POCDgYH424= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -k8s.io/api v0.20.1/go.mod h1:KqwcCVogGxQY3nBlRpwt+wpAMF/KjaCc7RpywacvqUo= -k8s.io/api v0.20.4/go.mod h1:++lNL1AJMkDymriNniQsWRkMDzRaX2Y/POTUi8yvqYQ= -k8s.io/api v0.20.6/go.mod h1:X9e8Qag6JV/bL5G6bU8sdVRltWKmdHsFUGS3eVndqE8= -k8s.io/api v0.21.2/go.mod h1:Lv6UGJZ1rlMI1qusN8ruAp9PUBFyBwpEHAdG24vIsiU= -k8s.io/api v0.22.1 h1:ISu3tD/jRhYfSW8jI/Q1e+lRxkR7w9UwQEZ7FgslrwY= -k8s.io/api v0.22.1/go.mod h1:bh13rkTp3F1XEaLGykbyRD2QaTTzPm0e/BMd8ptFONY= -k8s.io/apiextensions-apiserver v0.21.2/go.mod h1:+Axoz5/l3AYpGLlhJDfcVQzCerVYq3K3CvDMvw6X1RA= -k8s.io/apiextensions-apiserver v0.22.1 h1:YSJYzlFNFSfUle+yeEXX0lSQyLEoxoPJySRupepb0gE= -k8s.io/apiextensions-apiserver v0.22.1/go.mod h1:HeGmorjtRmRLE+Q8dJu6AYRoZccvCMsghwS8XTUYb2c= -k8s.io/apimachinery v0.20.1/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= -k8s.io/apimachinery v0.20.4/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= -k8s.io/apimachinery v0.20.6/go.mod h1:ejZXtW1Ra6V1O5H8xPBGz+T3+4gfkTCeExAHKU57MAc= -k8s.io/apimachinery v0.21.2/go.mod h1:CdTY8fU/BlvAbJ2z/8kBwimGki5Zp8/fbVuLY8gJumM= -k8s.io/apimachinery v0.22.1 h1:DTARnyzmdHMz7bFWFDDm22AM4pLWTQECMpRTFu2d2OM= -k8s.io/apimachinery v0.22.1/go.mod h1:O3oNtNadZdeOMxHFVxOreoznohCpy0z6mocxbZr7oJ0= -k8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU= -k8s.io/apiserver v0.20.4/go.mod h1:Mc80thBKOyy7tbvFtB4kJv1kbdD0eIH8k8vianJcbFM= -k8s.io/apiserver v0.20.6/go.mod h1:QIJXNt6i6JB+0YQRNcS0hdRHJlMhflFmsBDeSgT1r8Q= -k8s.io/apiserver v0.21.2/go.mod h1:lN4yBoGyiNT7SC1dmNk0ue6a5Wi6O3SWOIw91TsucQw= -k8s.io/apiserver v0.22.1 h1:Ul9Iv8OMB2s45h2tl5XWPpAZo1VPIJ/6N+MESeed7L8= -k8s.io/apiserver v0.22.1/go.mod h1:2mcM6dzSt+XndzVQJX21Gx0/Klo7Aen7i0Ai6tIa400= -k8s.io/cli-runtime v0.22.1 h1:WIueieKvT+IiSVSFosRLI6rkM0tyBGEGH1WUEztVjho= -k8s.io/cli-runtime v0.22.1/go.mod h1:YqwGrlXeEk15Yn3em2xzr435UGwbrCw5x+COQoTYfoo= -k8s.io/client-go v0.20.1/go.mod h1:/zcHdt1TeWSd5HoUe6elJmHSQ6uLLgp4bIJHVEuy+/Y= -k8s.io/client-go v0.20.4/go.mod h1:LiMv25ND1gLUdBeYxBIwKpkSC5IsozMMmOOeSJboP+k= -k8s.io/client-go v0.20.6/go.mod h1:nNQMnOvEUEsOzRRFIIkdmYOjAZrC8bgq0ExboWSU1I0= -k8s.io/client-go v0.21.2/go.mod h1:HdJ9iknWpbl3vMGtib6T2PyI/VYxiZfq936WNVHBRrA= -k8s.io/client-go v0.22.1 h1:jW0ZSHi8wW260FvcXHkIa0NLxFBQszTlhiAVsU5mopw= -k8s.io/client-go v0.22.1/go.mod h1:BquC5A4UOo4qVDUtoc04/+Nxp1MeHcVc1HJm1KmG8kk= -k8s.io/code-generator v0.21.2/go.mod h1:8mXJDCB7HcRo1xiEQstcguZkbxZaqeUOrO9SsicWs3U= -k8s.io/code-generator v0.22.1/go.mod h1:eV77Y09IopzeXOJzndrDyCI88UBok2h6WxAlBwpxa+o= -k8s.io/component-base v0.20.1/go.mod h1:guxkoJnNoh8LNrbtiQOlyp2Y2XFCZQmrcg2n/DeYNLk= -k8s.io/component-base v0.20.4/go.mod h1:t4p9EdiagbVCJKrQ1RsA5/V4rFQNDfRlevJajlGwgjI= -k8s.io/component-base v0.20.6/go.mod h1:6f1MPBAeI+mvuts3sIdtpjljHWBQ2cIy38oBIWMYnrM= -k8s.io/component-base v0.21.2/go.mod h1:9lvmIThzdlrJj5Hp8Z/TOgIkdfsNARQ1pT+3PByuiuc= -k8s.io/component-base v0.22.1 h1:SFqIXsEN3v3Kkr1bS6rstrs1wd45StJqbtgbQ4nRQdo= -k8s.io/component-base v0.22.1/go.mod h1:0D+Bl8rrnsPN9v0dyYvkqFfBeAd4u7n77ze+p8CMiPo= -k8s.io/component-helpers v0.22.1/go.mod h1:QvBcDbX+qU5I2tMZABBF5fRwAlQwiv771IGBHK9WYh4= -k8s.io/cri-api v0.17.3/go.mod h1:X1sbHmuXhwaHs9xxYffLqJogVsnI+f6cPRcgPel7ywM= -k8s.io/cri-api v0.20.1/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= -k8s.io/cri-api v0.20.4/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= -k8s.io/cri-api v0.20.6/go.mod h1:ew44AjNXwyn1s0U4xCKGodU7J1HzBeZ1MpGrpa5r8Yc= -k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= -k8s.io/gengo v0.0.0-20201214224949-b6c5ce23f027/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= -k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= -k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= -k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= -k8s.io/klog/v2 v2.8.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= -k8s.io/klog/v2 v2.9.0 h1:D7HV+n1V57XeZ0m6tdRkfknthUaM06VFbWldOFh8kzM= -k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= -k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM= -k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7/go.mod h1:wXW5VT87nVfh/iLV8FpR2uDvrFyomxbtb1KivDbvPTE= -k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e h1:KLHHjkdQFomZy8+06csTWZ0m1343QqxZhR2LJ1OxCYM= -k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= -k8s.io/kubectl v0.22.1 h1:kpXO+ajPNTzAVLDM9pAzCsWH9MtCMr92zpcvXMt7P6E= -k8s.io/kubectl v0.22.1/go.mod h1:mjAOgEbMNMtZWxnfM6jd+nPjPsaoLqO5xanc78WcSbw= -k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= -k8s.io/metrics v0.22.1/go.mod h1:i/ZNap89UkV1gLa26dn7fhKAdheJaKy+moOqJbiif7E= -k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20210527160623-6fdb442a123b/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20210707171843-4b05e18ac7d9 h1:imL9YgXQ9p7xmPzHFm/vVd/cF78jad+n4wK1ABwYtMM= -k8s.io/utils v0.0.0-20210707171843-4b05e18ac7d9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -oras.land/oras-go v0.4.0 h1:u6+7D+raZDYHwlz/uOwNANiRmyYDSSMW7A9E1xXycUQ= -oras.land/oras-go v0.4.0/go.mod h1:VJcU+VE4rkclUbum5C0O7deEZbBYnsnpbGSACwTjOcg= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/letsencrypt v0.0.3 h1:H7xDfhkaFFSYEJlKeq38RwX2jYcnTeHuDQyT+mMNMwM= -rsc.io/letsencrypt v0.0.3/go.mod h1:buyQKZ6IXrRnB7TdkHP0RyEybLx18HHyOSoTyoOLqNY= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.14/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.15/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.19/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.22/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= -sigs.k8s.io/controller-runtime v0.9.2 h1:MnCAsopQno6+hI9SgJHKddzXpmv2wtouZz6931Eax+Q= -sigs.k8s.io/controller-runtime v0.9.2/go.mod h1:TxzMCHyEUpaeuOiZx/bIdc2T81vfs/aKdvJt9wuu0zk= -sigs.k8s.io/kustomize/api v0.8.11 h1:LzQzlq6Z023b+mBtc6v72N2mSHYmN8x7ssgbf/hv0H8= -sigs.k8s.io/kustomize/api v0.8.11/go.mod h1:a77Ls36JdfCWojpUqR6m60pdGY1AYFix4AH83nJtY1g= -sigs.k8s.io/kustomize/cmd/config v0.9.13/go.mod h1:7547FLF8W/lTaDf0BDqFTbZxM9zqwEJqCKN9sSR0xSs= -sigs.k8s.io/kustomize/kustomize/v4 v4.2.0/go.mod h1:MOkR6fmhwG7hEDRXBYELTi5GSFcLwfqwzTRHW3kv5go= -sigs.k8s.io/kustomize/kyaml v0.11.0 h1:9KhiCPKaVyuPcgOLJXkvytOvjMJLoxpjodiycb4gHsA= -sigs.k8s.io/kustomize/kyaml v0.11.0/go.mod h1:GNMwjim4Ypgp/MueD3zXHLRJEjz7RvtPae0AwlvEMFM= -sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= -sigs.k8s.io/structured-merge-diff/v4 v4.0.3/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= -sigs.k8s.io/structured-merge-diff/v4 v4.1.0/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= -sigs.k8s.io/structured-merge-diff/v4 v4.1.2 h1:Hr/htKFmJEbtMgS/UD0N+gtgctAqz81t3nu+sPzynno= -sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= -sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= -sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= -sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= +k8s.io/api v0.29.2 h1:hBC7B9+MU+ptchxEqTNW2DkUosJpp1P+Wn6YncZ474A= +k8s.io/api v0.29.2/go.mod h1:sdIaaKuU7P44aoyyLlikSLayT6Vb7bvJNCX105xZXY0= +k8s.io/apiextensions-apiserver v0.29.2 h1:UK3xB5lOWSnhaCk0RFZ0LUacPZz9RY4wi/yt2Iu+btg= +k8s.io/apiextensions-apiserver v0.29.2/go.mod h1:aLfYjpA5p3OwtqNXQFkhJ56TB+spV8Gc4wfMhUA3/b8= +k8s.io/apimachinery v0.29.2 h1:EWGpfJ856oj11C52NRCHuU7rFDwxev48z+6DSlGNsV8= +k8s.io/apimachinery v0.29.2/go.mod h1:6HVkd1FwxIagpYrHSwJlQqZI3G9LfYWRPAkUvLnXTKU= +k8s.io/apiserver v0.29.2 h1:+Z9S0dSNr+CjnVXQePG8TcBWHr3Q7BmAr7NraHvsMiQ= +k8s.io/apiserver v0.29.2/go.mod h1:B0LieKVoyU7ykQvPFm7XSdIHaCHSzCzQWPFa5bqbeMQ= +k8s.io/cli-runtime v0.29.0 h1:q2kC3cex4rOBLfPOnMSzV2BIrrQlx97gxHJs21KxKS4= +k8s.io/cli-runtime v0.29.0/go.mod h1:VKudXp3X7wR45L+nER85YUzOQIru28HQpXr0mTdeCrk= +k8s.io/client-go v0.29.2 h1:FEg85el1TeZp+/vYJM7hkDlSTFZ+c5nnK44DJ4FyoRg= +k8s.io/client-go v0.29.2/go.mod h1:knlvFZE58VpqbQpJNbCbctTVXcd35mMyAAwBdpt4jrA= +k8s.io/component-base v0.29.2 h1:lpiLyuvPA9yV1aQwGLENYyK7n/8t6l3nn3zAtFTJYe8= +k8s.io/component-base v0.29.2/go.mod h1:BfB3SLrefbZXiBfbM+2H1dlat21Uewg/5qtKOl8degM= +k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= +k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= +k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= +k8s.io/kubectl v0.29.0 h1:Oqi48gXjikDhrBF67AYuZRTcJV4lg2l42GmvsP7FmYI= +k8s.io/kubectl v0.29.0/go.mod h1:0jMjGWIcMIQzmUaMgAzhSELv5WtHo2a8pq67DtviAJs= +k8s.io/utils v0.0.0-20240102154912-e7106e64919e h1:eQ/4ljkx21sObifjzXwlPKpdGLrCfRziVtos3ofG/sQ= +k8s.io/utils v0.0.0-20240102154912-e7106e64919e/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +oras.land/oras-go v1.2.4 h1:djpBY2/2Cs1PV87GSJlxv4voajVOMZxqqtq9AB8YNvY= +oras.land/oras-go v1.2.4/go.mod h1:DYcGfb3YF1nKjcezfX2SNlDAeQFKSXmf+qrFmrh4324= +sigs.k8s.io/controller-runtime v0.17.2 h1:FwHwD1CTUemg0pW2otk7/U5/i5m2ymzvOXdbeGOUvw0= +sigs.k8s.io/controller-runtime v0.17.2/go.mod h1:+MngTvIQQQhfXtwfdGw/UOQ/aIaqsYywfCINOtwMO/s= +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.13.5-0.20230601165947-6ce0bf390ce3 h1:XX3Ajgzov2RKUdc5jW3t5jwY7Bo7dcRm+tFxT+NfgY0= +sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3/go.mod h1:9n16EZKMhXBNSiUC5kSdFQJkdH3zbxS/JoO619G1VAY= +sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3 h1:W6cLQc5pnqM7vh3b7HvGNfXrJ/xL6BDMS0v1V/HHg5U= +sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3/go.mod h1:JWP1Fj0VWGHyw3YUPjXSQnRnrwezrZSrApfX5S0nIag= +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.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/main.go b/main.go index cef4eba2..1389601c 100644 --- a/main.go +++ b/main.go @@ -12,25 +12,20 @@ import ( "runtime" "strconv" - "github.com/instana/instana-agent-operator/controllers" - - logf "sigs.k8s.io/controller-runtime/pkg/log" - k8sruntime "k8s.io/apimachinery/pkg/runtime" - - agentoperatorv1 "github.com/instana/instana-agent-operator/api/v1" - "github.com/instana/instana-agent-operator/version" - - // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) - // to ensure that exec-entrypoint and run can make use of them. - _ "k8s.io/client-go/plugin/pkg/client/auth" - utilruntime "k8s.io/apimachinery/pkg/util/runtime" clientgoscheme "k8s.io/client-go/kubernetes/scheme" + _ "k8s.io/client-go/plugin/pkg/client/auth" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/healthz" + logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/log/zap" - //+kubebuilder:scaffold:imports + metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" + + agentoperatorv1 "github.com/instana/instana-agent-operator/api/v1" + "github.com/instana/instana-agent-operator/controllers" + "github.com/instana/instana-agent-operator/version" + // +kubebuilder:scaffold:imports ) var ( @@ -53,9 +48,11 @@ func main() { flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") // By default disable leader-election and assume single instance gets installed. Via parameters (--leader-elect) it will be // enabled from the Operator Deployment spec. - flag.BoolVar(&enableLeaderElection, "leader-elect", false, + flag.BoolVar( + &enableLeaderElection, "leader-elect", false, "Enable leader election for controller manager. "+ - "Enabling this will ensure there is only one active controller manager.") + "Enabling this will ensure there is only one active controller manager.", + ) // When running in debug-mode, include some more logging etc debugMode, _ := strconv.ParseBool(os.Getenv("DEBUG_MODE")) @@ -71,15 +68,17 @@ func main() { printVersion() - mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ - Namespace: "instana-agent", - Scheme: scheme, - MetricsBindAddress: metricsAddr, - Port: 9443, - HealthProbeBindAddress: probeAddr, - LeaderElection: enableLeaderElection, - LeaderElectionID: "819a9291.instana.io", - }) + mgr, err := ctrl.NewManager( + ctrl.GetConfigOrDie(), ctrl.Options{ + Metrics: metricsserver.Options{ + BindAddress: metricsAddr, + }, + Scheme: scheme, + HealthProbeBindAddress: probeAddr, + LeaderElection: enableLeaderElection, + LeaderElectionID: "819a9291.instana.io", + }, + ) if err != nil { log.Error(err, "Unable to start manager") os.Exit(1) diff --git a/pkg/collections/list/conditions.go b/pkg/collections/list/conditions.go new file mode 100644 index 00000000..89a42fa6 --- /dev/null +++ b/pkg/collections/list/conditions.go @@ -0,0 +1,34 @@ +package list + +type Conditions[T any] interface { + All(condition func(item T) bool) bool + Any(condition func(item T) bool) bool +} + +type conditions[T any] struct { + list []T +} + +func (c *conditions[T]) All(condition func(item T) bool) bool { + for _, item := range c.list { + if !condition(item) { + return false + } + } + return true +} + +func (c *conditions[T]) Any(condition func(item T) bool) bool { + for _, item := range c.list { + if condition(item) { + return true + } + } + return false +} + +func NewConditions[T any](list []T) Conditions[T] { + return &conditions[T]{ + list: list, + } +} diff --git a/pkg/collections/list/conditions_test.go b/pkg/collections/list/conditions_test.go new file mode 100644 index 00000000..7fcdf700 --- /dev/null +++ b/pkg/collections/list/conditions_test.go @@ -0,0 +1,55 @@ +package list + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestConditions(t *testing.T) { + condition := func(item bool) bool { + return item + } + + type conditionTest struct { + name string + list []bool + expectedAny bool + expectedAll bool + } + + tests := make([]conditionTest, 0, 8) + + for _, first := range []bool{true, false} { + for _, second := range []bool{true, false} { + for _, third := range []bool{true, false} { + list := []bool{first, second, third} + tests = append( + tests, conditionTest{ + name: fmt.Sprintf("%v", list), + list: list, + expectedAny: first || second || third, + expectedAll: first && second && third, + }, + ) + } + } + } + + for _, test := range tests { + t.Run( + test.name, func(t *testing.T) { + assertions := require.New(t) + + c := NewConditions(test.list) + + actualAny := c.Any(condition) + assertions.Equal(test.expectedAny, actualAny) + + actualAll := c.All(condition) + assertions.Equal(test.expectedAll, actualAll) + }, + ) + } +} diff --git a/pkg/collections/list/contains.go b/pkg/collections/list/contains.go new file mode 100644 index 00000000..3e8ed69f --- /dev/null +++ b/pkg/collections/list/contains.go @@ -0,0 +1,52 @@ +package list + +import "reflect" + +func toSet[T comparable](list []T) map[T]bool { + res := make(map[T]bool, len(list)) + + for _, item := range list { + res[item] = true + } + + return res +} + +type ContainsElementChecker[T any] interface { + Contains(v T) bool +} + +type containsElementChecker[T comparable] struct { + set map[T]bool +} + +func (c *containsElementChecker[T]) Contains(expected T) bool { + _, res := c.set[expected] + return res +} + +func NewContainsElementChecker[T comparable](in []T) ContainsElementChecker[T] { + return &containsElementChecker[T]{ + set: toSet(in), + } +} + +type deepContainsElementChecker[T any] struct { + list []T +} + +func (d *deepContainsElementChecker[T]) Contains(expected T) bool { + for _, item := range d.list { + if reflect.DeepEqual(expected, item) { + return true + } + } + + return false +} + +func NewDeepContainsElementChecker[T any](in []T) ContainsElementChecker[T] { + return &deepContainsElementChecker[T]{ + list: in, + } +} diff --git a/pkg/collections/list/contains_test.go b/pkg/collections/list/contains_test.go new file mode 100644 index 00000000..527ecbcc --- /dev/null +++ b/pkg/collections/list/contains_test.go @@ -0,0 +1,64 @@ +package list + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func Test_toSet(t *testing.T) { + assertions := require.New(t) + + expected := map[int]bool{ + 1: true, + 2: true, + 3: true, + } + + in := []int{1, 2, 2, 3, 3, 3} + actual := toSet(in) + + assertions.Equal(expected, actual) +} + +func TestContainsElementChecker_Contains(t *testing.T) { + for _, test := range []struct { + name string + list []int + testElement int + expected bool + }{ + { + name: "contains", + list: []int{1, 2, 3, 4}, + testElement: 2, + expected: true, + }, + { + name: "not_contains", + list: []int{4, 5, 6, 7}, + testElement: 8, + expected: false, + }, + { + name: "not_contains_because_nil", + list: nil, + testElement: 8, + expected: false, + }, + } { + t.Run( + test.name, func(t *testing.T) { + assertions := require.New(t) + + cec := NewContainsElementChecker[int](test.list) + + assertions.Equal(test.expected, cec.Contains(test.testElement)) + + dec := NewDeepContainsElementChecker(test.list) + + assertions.Equal(test.expected, dec.Contains(test.testElement)) + }, + ) + } +} diff --git a/pkg/collections/list/diff.go b/pkg/collections/list/diff.go new file mode 100644 index 00000000..fe92cefd --- /dev/null +++ b/pkg/collections/list/diff.go @@ -0,0 +1,35 @@ +package list + +type Diff[T any] interface { + Diff(old []T, new []T) []T +} + +type diff[T any] struct { + newContainsElementChecker func(in []T) ContainsElementChecker[T] +} + +func (d *diff[T]) Diff(old []T, new []T) []T { + newSet := d.newContainsElementChecker(new) + + res := make([]T, 0, len(old)) + + for _, item := range old { + if !newSet.Contains(item) { + res = append(res, item) + } + } + + return res +} + +func NewDiff[T comparable]() Diff[T] { + return &diff[T]{ + newContainsElementChecker: NewContainsElementChecker[T], + } +} + +func NewDeepDiff[T any]() Diff[T] { + return &diff[T]{ + newContainsElementChecker: NewDeepContainsElementChecker[T], + } +} diff --git a/pkg/collections/list/diff_test.go b/pkg/collections/list/diff_test.go new file mode 100644 index 00000000..0b13adcf --- /dev/null +++ b/pkg/collections/list/diff_test.go @@ -0,0 +1,61 @@ +package list + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestDiff_Diff(t *testing.T) { + a := []int{0, 1, 2, 3, 4, 5, 6} + b := []int{3, 4, 5, 6, 7, 8, 9} + + df := NewDiff[int]() + ddf := NewDeepDiff[int]() + + for _, test := range []struct { + name string + + old []int + new []int + + expected []int + }{ + { + name: "a-b", + old: a, + new: b, + expected: []int{0, 1, 2}, + }, + { + name: "b-a", + old: b, + new: a, + expected: []int{7, 8, 9}, + }, + { + name: "a-empty", + old: a, + new: nil, + expected: a, + }, + { + name: "empty-a", + old: nil, + new: a, + expected: []int{}, + }, + } { + t.Run( + test.name, func(t *testing.T) { + assertions := require.New(t) + + actual := df.Diff(test.old, test.new) + assertions.ElementsMatch(test.expected, actual) + + deepActual := ddf.Diff(test.old, test.new) + assertions.ElementsMatch(test.expected, deepActual) + }, + ) + } +} diff --git a/pkg/collections/list/transformers.go b/pkg/collections/list/transformers.go new file mode 100644 index 00000000..fee7c528 --- /dev/null +++ b/pkg/collections/list/transformers.go @@ -0,0 +1,39 @@ +package list + +type ListFilter[T any] interface { + Filter(in []T, shouldBeIncluded func(val T) bool) []T +} + +type ListMapTo[T any, S any] interface { + MapTo(in []T, mapItemTo func(val T) S) []S +} + +type transformer[T any, S any] struct{} + +func NewListFilter[T any]() ListFilter[T] { + return &transformer[T, any]{} +} + +func NewListMapTo[T any, S any]() ListMapTo[T, S] { + return &transformer[T, S]{} +} + +func (t *transformer[T, S]) Filter(in []T, shouldBeIncluded func(val T) bool) []T { + res := make([]T, 0, len(in)) + for _, v := range in { + v := v + if shouldBeIncluded(v) { + res = append(res, v) + } + } + return res +} + +func (t *transformer[T, S]) MapTo(in []T, mapItemTo func(val T) S) []S { + res := make([]S, 0, len(in)) + for _, v := range in { + v := v + res = append(res, mapItemTo(v)) + } + return res +} diff --git a/pkg/collections/list/transformers_test.go b/pkg/collections/list/transformers_test.go new file mode 100644 index 00000000..68c8dc4b --- /dev/null +++ b/pkg/collections/list/transformers_test.go @@ -0,0 +1,46 @@ +package list + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestFilter(t *testing.T) { + mpr := NewListFilter[bool]() + + in := []bool{true, true, false, true, false, false, true} + out := mpr.Filter( + in, func(val bool) bool { + return val + }, + ) + + assertions := require.New(t) + + assertions.Len(out, 4) + + for _, v := range out { + assertions.True(v) + } +} + +func TestMapTo(t *testing.T) { + mpr := NewListMapTo[bool, int]() + + in := []bool{true, true, false, true, false, false, true} + out := mpr.MapTo( + in, func(v bool) int { + switch v { + case true: + return 1 + default: + return 0 + } + }, + ) + + assertions := require.New(t) + + assertions.Equal([]int{1, 1, 0, 1, 0, 0, 1}, out) +} diff --git a/pkg/collections/map/copy.go b/pkg/collections/map/copy.go new file mode 100644 index 00000000..dcc0b74d --- /dev/null +++ b/pkg/collections/map/copy.go @@ -0,0 +1,25 @@ +package _map + +type Copier[K comparable, V any] interface { + Copy() map[K]V +} + +type copier[K comparable, V any] struct { + m map[K]V +} + +func (c *copier[K, V]) Copy() map[K]V { + res := make(map[K]V, len(c.m)) + + for k, v := range c.m { + res[k] = v + } + + return res +} + +func NewCopier[K comparable, V any](m map[K]V) Copier[K, V] { + return &copier[K, V]{ + m: m, + } +} diff --git a/pkg/collections/map/copy_test.go b/pkg/collections/map/copy_test.go new file mode 100644 index 00000000..59c3ce30 --- /dev/null +++ b/pkg/collections/map/copy_test.go @@ -0,0 +1,25 @@ +package _map + +import ( + "testing" + + "github.com/stretchr/testify/require" + "k8s.io/apimachinery/pkg/util/rand" +) + +func TestCopier_Copy(t *testing.T) { + assertions := require.New(t) + + expected := map[string]string{ + rand.String(10): rand.String(10), + rand.String(10): rand.String(10), + rand.String(10): rand.String(10), + } + + c := NewCopier(expected) + + actual := c.Copy() + + assertions.NotSame(expected, actual) + assertions.Equal(expected, actual) +} diff --git a/pkg/collections/map/map.go b/pkg/collections/map/map.go new file mode 100644 index 00000000..52408fee --- /dev/null +++ b/pkg/collections/map/map.go @@ -0,0 +1,19 @@ +package _map + +type MapConverter[K comparable, V any, O any] interface { + ToList(in map[K]V, mapItemTo func(key K, val V) O) []O +} + +type mapConverter[K comparable, V any, O any] struct{} + +func NewMapConverter[K comparable, V any, O any]() MapConverter[K, V, O] { + return &mapConverter[K, V, O]{} +} + +func (m *mapConverter[K, V, O]) ToList(in map[K]V, mapItemTo func(key K, val V) O) []O { + res := make([]O, 0, len(in)) + for k, v := range in { + res = append(res, mapItemTo(k, v)) + } + return res +} diff --git a/pkg/collections/map/map_test.go b/pkg/collections/map/map_test.go new file mode 100644 index 00000000..6ab9244d --- /dev/null +++ b/pkg/collections/map/map_test.go @@ -0,0 +1,26 @@ +package _map + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestMapConverter_ToList(t *testing.T) { + assertions := require.New(t) + + in := map[string]string{ + "foo": "bar", + "hello": "world", + "something": "else", + } + + actual := NewMapConverter[string, string, string]().ToList( + in, func(key string, val string) string { + return fmt.Sprintf("%s: %s", key, val) + }, + ) + + assertions.ElementsMatch([]string{"foo: bar", "hello: world", "something: else"}, actual) +} diff --git a/pkg/env/env.go b/pkg/env/env.go new file mode 100644 index 00000000..ed43a11b --- /dev/null +++ b/pkg/env/env.go @@ -0,0 +1,15 @@ +package env + +import ( + "os" + + "github.com/instana/instana-agent-operator/pkg/optional" +) + +var ( + operatorVersion = optional.Of(os.Getenv("OPERATOR_VERSION")).GetOrDefault("v0.0.1-dev") +) + +func GetOperatorVersion() string { + return operatorVersion +} diff --git a/pkg/hash/hash.go b/pkg/hash/hash.go new file mode 100644 index 00000000..7fec67c2 --- /dev/null +++ b/pkg/hash/hash.go @@ -0,0 +1,33 @@ +package hash + +import ( + "crypto/md5" + "encoding/base64" + "encoding/json" + + "github.com/instana/instana-agent-operator/pkg/or_die" +) + +type hasher struct { + or_die.OrDie[[]byte] +} + +func (h *hasher) HashJsonOrDie(obj interface{}) string { + jsonStr := h.ResultOrDie( + func() ([]byte, error) { + return json.Marshal(obj) + }, + ) + hash := md5.Sum(jsonStr) + return base64.StdEncoding.EncodeToString(hash[:]) +} + +type JsonHasher interface { + HashJsonOrDie(obj interface{}) string +} + +func NewJsonHasher() JsonHasher { + return &hasher{ + OrDie: or_die.New[[]byte](), + } +} diff --git a/pkg/hash/hash_test.go b/pkg/hash/hash_test.go new file mode 100644 index 00000000..3ceebdb4 --- /dev/null +++ b/pkg/hash/hash_test.go @@ -0,0 +1,41 @@ +package hash + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +type TestStruct struct { + Field1 string `json:"field1"` + Field2 int `json:"field2"` +} + +func TestHasher(t *testing.T) { + + h := NewJsonHasher() + + t.Run( + "ValidInput", func(t *testing.T) { + assertions := require.New(t) + testStruct := &TestStruct{ + Field1: "test1", + Field2: 42, + } + const expectedHash = "+wF/Y9d+dlvLIQSi8zR0IA==" + resultHash := h.HashJsonOrDie(testStruct) + assertions.Equal(expectedHash, resultHash) + }, + ) + + t.Run( + "InvalidInput", func(t *testing.T) { + assertions := require.New(t) + assertions.PanicsWithError( + "json: unsupported type: func()", func() { + h.HashJsonOrDie(func() {}) + }, + ) + }, + ) +} diff --git a/pkg/json_or_die/json.go b/pkg/json_or_die/json.go new file mode 100644 index 00000000..99d0c84b --- /dev/null +++ b/pkg/json_or_die/json.go @@ -0,0 +1,65 @@ +package json_or_die + +import ( + "encoding/json" + + "github.com/instana/instana-agent-operator/pkg/or_die" +) + +type JsonOrDieMarshaler[T any] interface { + MarshalOrDie(obj T) []byte + UnMarshalOrDie(raw []byte) T +} + +type jsonOrDie[T any] struct { + or_die.OrDie[[]byte] + newEmptyObject func() T +} + +func (j *jsonOrDie[T]) MarshalOrDie(obj T) []byte { + return j.ResultOrDie( + func() ([]byte, error) { + return json.Marshal(obj) + }, + ) +} + +func (j *jsonOrDie[T]) UnMarshalOrDie(raw []byte) T { + obj := j.newEmptyObject() + + j.ResultOrDie( + func() ([]byte, error) { + return nil, json.Unmarshal(raw, &obj) + }, + ) + + return obj +} + +func NewJsonOrDie[T any]() JsonOrDieMarshaler[*T] { + return &jsonOrDie[*T]{ + OrDie: or_die.New[[]byte](), + newEmptyObject: func() *T { + var obj T + return &obj + }, + } +} + +func NewJsonOrDieMap[K comparable, V any]() JsonOrDieMarshaler[map[K]V] { + return &jsonOrDie[map[K]V]{ + OrDie: or_die.New[[]byte](), + newEmptyObject: func() map[K]V { + return make(map[K]V, 0) + }, + } +} + +func NewJsonOrDieArray[T any]() JsonOrDieMarshaler[[]T] { + return &jsonOrDie[[]T]{ + OrDie: or_die.New[[]byte](), + newEmptyObject: func() []T { + return make([]T, 0) + }, + } +} diff --git a/pkg/json_or_die/json_test.go b/pkg/json_or_die/json_test.go new file mode 100644 index 00000000..3bf0fa9b --- /dev/null +++ b/pkg/json_or_die/json_test.go @@ -0,0 +1,57 @@ +package json_or_die + +import ( + "testing" + + "github.com/stretchr/testify/require" + + instanav1 "github.com/instana/instana-agent-operator/api/v1" +) + +func testJsonOrDie[T any](t *testing.T, name string, createFunction func() JsonOrDieMarshaler[T]) { + t.Run( + name, func(t *testing.T) { + for _, test := range []struct { + name string + execute func(assertions *require.Assertions, j JsonOrDieMarshaler[T]) + }{ + { + name: "should_panic_on_bad_json", + execute: func(assertions *require.Assertions, j JsonOrDieMarshaler[T]) { + assertions.Panics( + func() { + j.UnMarshalOrDie([]byte("{")) + }, + ) + }, + }, + { + name: "round_trip", + execute: func(assertions *require.Assertions, j JsonOrDieMarshaler[T]) { + expected := j.(*jsonOrDie[T]).newEmptyObject() + + marshaled := j.MarshalOrDie(expected) + actual := j.UnMarshalOrDie(marshaled) + + assertions.Equal(expected, actual) + }, + }, + } { + t.Run( + test.name, func(t *testing.T) { + assertions := require.New(t) + j := createFunction() + test.execute(assertions, j) + }, + ) + } + }, + ) +} + +func TestJsonOrDie(t *testing.T) { + testJsonOrDie(t, "struct", NewJsonOrDie[instanav1.InstanaAgent]) + testJsonOrDie(t, "map", NewJsonOrDieMap[string, any]) + testJsonOrDie(t, "literal", NewJsonOrDie[string]) + testJsonOrDie(t, "array", NewJsonOrDieArray[string]) +} diff --git a/pkg/k8s/client/client.go b/pkg/k8s/client/client.go new file mode 100644 index 00000000..f06e2035 --- /dev/null +++ b/pkg/k8s/client/client.go @@ -0,0 +1,193 @@ +package client + +import ( + "context" + "errors" + "time" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + logf "sigs.k8s.io/controller-runtime/pkg/log" + + "github.com/instana/instana-agent-operator/pkg/collections/list" + "github.com/instana/instana-agent-operator/pkg/multierror" + "github.com/instana/instana-agent-operator/pkg/result" + + k8sclient "sigs.k8s.io/controller-runtime/pkg/client" +) + +const ( + FieldOwnerName = "instana-agent-operator" +) + +// ObjectResult alias is needed to workaround issues in mockgen +type ObjectResult = result.Result[k8sclient.Object] + +// MultiObjectResult alias is needed to workaround issues in mockgen +type MultiObjectResult = result.Result[[]k8sclient.Object] + +// BoolResult alias is needed to workaround issues in mockgen +type BoolResult = result.Result[bool] + +type InstanaAgentClient interface { + k8sclient.Client + Apply(ctx context.Context, obj k8sclient.Object, opts ...k8sclient.PatchOption) ObjectResult + GetAsResult( + ctx context.Context, key k8sclient.ObjectKey, obj k8sclient.Object, opts ...k8sclient.GetOption, + ) ObjectResult + Exists(ctx context.Context, gvk schema.GroupVersionKind, key k8sclient.ObjectKey) BoolResult + DeleteAllInTimeLimit( + ctx context.Context, + objects []k8sclient.Object, + timeout time.Duration, + waitTime time.Duration, + opts ...k8sclient.DeleteOption, + ) MultiObjectResult +} + +type instanaAgentClient struct { + k8sclient.Client +} + +func (c *instanaAgentClient) objectsExist( + ctx context.Context, + objects []k8sclient.Object, +) []BoolResult { + res := make([]BoolResult, 0, len(objects)) + + for _, obj := range objects { + objExistsRes := c.Exists(ctx, obj.GetObjectKind().GroupVersionKind(), k8sclient.ObjectKeyFromObject(obj)). + OnFailure( + func(err error) { + log := logf.FromContext(ctx) + log.Error(err, "failed to verify if resource has finished terminating", "Resource", obj) + }, + ) + res = append(res, objExistsRes) + } + + return res +} + +func doNotExist(res BoolResult) bool { + return res.IsSuccess() && !res.ToOptional().Get() +} + +func (c *instanaAgentClient) verifyDeletionStep( + ctx context.Context, + objects []k8sclient.Object, + waitTime time.Duration, +) error { + objectsExistResults := c.objectsExist(ctx, objects) + + switch list.NewConditions(objectsExistResults).All(doNotExist) { + case true: + return nil + default: + time.Sleep(waitTime) + return c.verifyDeletion(ctx, objects, waitTime) + } +} + +func (c *instanaAgentClient) verifyDeletion( + ctx context.Context, + objects []k8sclient.Object, + waitTime time.Duration, +) error { + select { + case <-ctx.Done(): + return ctx.Err() + default: + return c.verifyDeletionStep(ctx, objects, waitTime) + } +} + +func (c *instanaAgentClient) deleteAll( + ctx context.Context, + objects []k8sclient.Object, + opts ...k8sclient.DeleteOption, +) error { + errBuilder := multierror.NewMultiErrorBuilder() + + for _, obj := range objects { + err := c.Delete(ctx, obj, opts...) + errBuilder.Add(k8sclient.IgnoreNotFound(err)) + } + + return errBuilder.Build() +} + +func (c *instanaAgentClient) deleteAllInTimeLimit( + ctx context.Context, + objects []k8sclient.Object, + timeout time.Duration, + waitTime time.Duration, + opts ...k8sclient.DeleteOption, +) error { + ctx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + + switch err := c.deleteAll(ctx, objects, opts...); errors.Is(err, nil) { + case true: + return c.verifyDeletion(ctx, objects, waitTime) + default: + return err + } +} + +func (c *instanaAgentClient) DeleteAllInTimeLimit( + ctx context.Context, + objects []k8sclient.Object, + timeout time.Duration, + waitTime time.Duration, + opts ...k8sclient.DeleteOption, +) MultiObjectResult { + return result.Of(objects, c.deleteAllInTimeLimit(ctx, objects, timeout, waitTime, opts...)) +} + +func wasRetrieved(_ k8sclient.Object) result.Result[bool] { + return result.OfSuccess(true) +} + +func ifNotFound(err error) (bool, error) { + return false, k8sclient.IgnoreNotFound(err) +} + +func (c *instanaAgentClient) Exists( + ctx context.Context, + gvk schema.GroupVersionKind, + key k8sclient.ObjectKey, +) BoolResult { + obj := &unstructured.Unstructured{} + obj.SetGroupVersionKind(gvk) + + res := c.GetAsResult(ctx, key, obj) + + return result.Map(res, wasRetrieved).Recover(ifNotFound) +} + +func (c *instanaAgentClient) Apply( + ctx context.Context, obj k8sclient.Object, opts ...k8sclient.PatchOption, +) result.Result[k8sclient.Object] { + obj.SetManagedFields(nil) + return result.Of( + obj, c.Patch( + ctx, + obj, + k8sclient.Apply, + append(opts, k8sclient.ForceOwnership, k8sclient.FieldOwner(FieldOwnerName))..., + ), + ) +} + +func (c *instanaAgentClient) GetAsResult( + ctx context.Context, key k8sclient.ObjectKey, obj k8sclient.Object, opts ...k8sclient.GetOption, +) result.Result[k8sclient.Object] { + return result.Of(obj, c.Client.Get(ctx, key, obj, opts...)) +} + +func NewClient(k8sClient k8sclient.Client) InstanaAgentClient { + return &instanaAgentClient{ + Client: k8sClient, + } +} diff --git a/pkg/k8s/client/client_test.go b/pkg/k8s/client/client_test.go new file mode 100644 index 00000000..fbc030c1 --- /dev/null +++ b/pkg/k8s/client/client_test.go @@ -0,0 +1,162 @@ +/* +(c) Copyright IBM Corp. 2024 +(c) Copyright Instana Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package client + +import ( + "context" + "errors" + "testing" + + "github.com/stretchr/testify/require" + gomock "go.uber.org/mock/gomock" + corev1 "k8s.io/api/core/v1" + k8sErrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + k8sclient "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/instana/instana-agent-operator/pkg/result" +) + +func TestInstanaAgentClient_Apply(t *testing.T) { + ctrl := gomock.NewController(t) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + cm := corev1.ConfigMap{} + opts := []k8sclient.PatchOption{k8sclient.DryRunAll} + expectedErr := errors.New("awojsgeoisegoijsdg") + + mockK8sClient := NewMockClient(ctrl) + mockK8sClient.EXPECT().Patch( + gomock.Eq(ctx), + gomock.Eq(&cm), + gomock.Eq(k8sclient.Apply), + gomock.Eq(append(opts, k8sclient.ForceOwnership, k8sclient.FieldOwner("instana-agent-operator"))), + ).Times(1).Return(expectedErr) + + client := instanaAgentClient{ + Client: mockK8sClient, + } + + actualVal, actualErr := client.Apply(ctx, &cm, opts...).Get() + + assertions := require.New(t) + + assertions.Same(&cm, actualVal) + assertions.Equal(expectedErr, actualErr) +} + +func TestInstanaAgentClient_GetAsResult(t *testing.T) { + assertions := require.New(t) + ctrl := gomock.NewController(t) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + key := types.NamespacedName{ + Namespace: "adsf", + Name: "rasdgf", + } + obj := &unstructured.Unstructured{} + opts := &k8sclient.GetOptions{ + Raw: &metav1.GetOptions{ + TypeMeta: metav1.TypeMeta{ + Kind: "reoisoijd", + APIVersion: "erifoijsd", + }, + ResourceVersion: "adsfadsf", + }, + } + + mockK8sClient := NewMockClient(ctrl) + mockK8sClient.EXPECT().Get( + gomock.Eq(ctx), + gomock.Eq(key), + gomock.Eq(obj), + gomock.Eq(opts), + gomock.Eq(opts), + ).Return(errors.New("foo")) + + client := instanaAgentClient{ + Client: mockK8sClient, + } + + actual := client.GetAsResult(ctx, key, obj, opts, opts) + assertions.Equal(result.Of[k8sclient.Object](obj, errors.New("foo")), actual) +} + +func TestInstanaAgentClient_Exists(t *testing.T) { + for _, test := range []struct { + name string + errOfGet error + expected result.Result[bool] + }{ + { + name: "crd_exists", + errOfGet: nil, + expected: result.OfSuccess(true), + }, + { + name: "crd_does_not_exist", + errOfGet: k8sErrors.NewNotFound( + schema.GroupResource{ + Group: "apiextensions.k8s.io", + Resource: "customresourcedefinitions", + }, "some-resource", + ), + expected: result.OfSuccess(false), + }, + { + name: "error_getting_crd", + errOfGet: errors.New("qwerty"), + expected: result.OfFailure[bool](errors.New("qwerty")), + }, + } { + t.Run( + test.name, func(t *testing.T) { + assertions := require.New(t) + ctrl := gomock.NewController(t) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + key := types.NamespacedName{ + Name: "some-resource", + } + gvk := schema.GroupVersionKind{ + Group: "somegroup", + Version: "v1beta1", + Kind: "SomeKind", + } + + obj := &unstructured.Unstructured{} + obj.SetGroupVersionKind(gvk) + + k8sClient := NewMockClient(ctrl) + k8sClient.EXPECT().Get(gomock.Eq(ctx), gomock.Eq(key), gomock.Eq(obj)).Return(test.errOfGet) + + instanaClient := NewClient(k8sClient) + + actual := instanaClient.Exists(ctx, gvk, key) + assertions.Equal(test.expected, actual) + }, + ) + } +} diff --git a/pkg/k8s/client/k8s_client_mock_test.go b/pkg/k8s/client/k8s_client_mock_test.go new file mode 100644 index 00000000..5b660443 --- /dev/null +++ b/pkg/k8s/client/k8s_client_mock_test.go @@ -0,0 +1,1155 @@ +// /* +// (c) Copyright IBM Corp. 2024 +// (c) Copyright Instana Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// */ +// + +// Code generated by MockGen. DO NOT EDIT. +package client + +import ( + context "context" + reflect "reflect" + + gomock "go.uber.org/mock/gomock" + meta "k8s.io/apimachinery/pkg/api/meta" + runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + client "sigs.k8s.io/controller-runtime/pkg/client" +) + +// MockPatch is a mock of Patch interface. +type MockPatch struct { + ctrl *gomock.Controller + recorder *MockPatchMockRecorder +} + +// MockPatchMockRecorder is the mock recorder for MockPatch. +type MockPatchMockRecorder struct { + mock *MockPatch +} + +// NewMockPatch creates a new mock instance. +func NewMockPatch(ctrl *gomock.Controller) *MockPatch { + mock := &MockPatch{ctrl: ctrl} + mock.recorder = &MockPatchMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockPatch) EXPECT() *MockPatchMockRecorder { + return m.recorder +} + +// ISGOMOCK indicates that this struct is a gomock mock. +func (m *MockPatch) ISGOMOCK() struct{} { + return struct{}{} +} + +// Data mocks base method. +func (m *MockPatch) Data(obj client.Object) ([]byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Data", obj) + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Data indicates an expected call of Data. +func (mr *MockPatchMockRecorder) Data(obj any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Data", reflect.TypeOf((*MockPatch)(nil).Data), obj) +} + +// Type mocks base method. +func (m *MockPatch) Type() types.PatchType { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Type") + ret0, _ := ret[0].(types.PatchType) + return ret0 +} + +// Type indicates an expected call of Type. +func (mr *MockPatchMockRecorder) Type() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Type", reflect.TypeOf((*MockPatch)(nil).Type)) +} + +// MockReader is a mock of Reader interface. +type MockReader struct { + ctrl *gomock.Controller + recorder *MockReaderMockRecorder +} + +// MockReaderMockRecorder is the mock recorder for MockReader. +type MockReaderMockRecorder struct { + mock *MockReader +} + +// NewMockReader creates a new mock instance. +func NewMockReader(ctrl *gomock.Controller) *MockReader { + mock := &MockReader{ctrl: ctrl} + mock.recorder = &MockReaderMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockReader) EXPECT() *MockReaderMockRecorder { + return m.recorder +} + +// ISGOMOCK indicates that this struct is a gomock mock. +func (m *MockReader) ISGOMOCK() struct{} { + return struct{}{} +} + +// Get mocks base method. +func (m *MockReader) Get(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error { + m.ctrl.T.Helper() + varargs := []any{ctx, key, obj} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Get", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// Get indicates an expected call of Get. +func (mr *MockReaderMockRecorder) Get(ctx, key, obj any, opts ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, key, obj}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockReader)(nil).Get), varargs...) +} + +// List mocks base method. +func (m *MockReader) List(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error { + m.ctrl.T.Helper() + varargs := []any{ctx, list} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "List", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// List indicates an expected call of List. +func (mr *MockReaderMockRecorder) List(ctx, list any, opts ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, list}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockReader)(nil).List), varargs...) +} + +// MockWriter is a mock of Writer interface. +type MockWriter struct { + ctrl *gomock.Controller + recorder *MockWriterMockRecorder +} + +// MockWriterMockRecorder is the mock recorder for MockWriter. +type MockWriterMockRecorder struct { + mock *MockWriter +} + +// NewMockWriter creates a new mock instance. +func NewMockWriter(ctrl *gomock.Controller) *MockWriter { + mock := &MockWriter{ctrl: ctrl} + mock.recorder = &MockWriterMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockWriter) EXPECT() *MockWriterMockRecorder { + return m.recorder +} + +// ISGOMOCK indicates that this struct is a gomock mock. +func (m *MockWriter) ISGOMOCK() struct{} { + return struct{}{} +} + +// Create mocks base method. +func (m *MockWriter) Create(ctx context.Context, obj client.Object, opts ...client.CreateOption) error { + m.ctrl.T.Helper() + varargs := []any{ctx, obj} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Create", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// Create indicates an expected call of Create. +func (mr *MockWriterMockRecorder) Create(ctx, obj any, opts ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, obj}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockWriter)(nil).Create), varargs...) +} + +// Delete mocks base method. +func (m *MockWriter) Delete(ctx context.Context, obj client.Object, opts ...client.DeleteOption) error { + m.ctrl.T.Helper() + varargs := []any{ctx, obj} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Delete", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// Delete indicates an expected call of Delete. +func (mr *MockWriterMockRecorder) Delete(ctx, obj any, opts ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, obj}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockWriter)(nil).Delete), varargs...) +} + +// DeleteAllOf mocks base method. +func (m *MockWriter) DeleteAllOf(ctx context.Context, obj client.Object, opts ...client.DeleteAllOfOption) error { + m.ctrl.T.Helper() + varargs := []any{ctx, obj} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "DeleteAllOf", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteAllOf indicates an expected call of DeleteAllOf. +func (mr *MockWriterMockRecorder) DeleteAllOf(ctx, obj any, opts ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, obj}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteAllOf", reflect.TypeOf((*MockWriter)(nil).DeleteAllOf), varargs...) +} + +// Patch mocks base method. +func (m *MockWriter) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.PatchOption) error { + m.ctrl.T.Helper() + varargs := []any{ctx, obj, patch} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Patch", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// Patch indicates an expected call of Patch. +func (mr *MockWriterMockRecorder) Patch(ctx, obj, patch any, opts ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, obj, patch}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Patch", reflect.TypeOf((*MockWriter)(nil).Patch), varargs...) +} + +// Update mocks base method. +func (m *MockWriter) Update(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + m.ctrl.T.Helper() + varargs := []any{ctx, obj} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Update", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// Update indicates an expected call of Update. +func (mr *MockWriterMockRecorder) Update(ctx, obj any, opts ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, obj}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockWriter)(nil).Update), varargs...) +} + +// MockStatusClient is a mock of StatusClient interface. +type MockStatusClient struct { + ctrl *gomock.Controller + recorder *MockStatusClientMockRecorder +} + +// MockStatusClientMockRecorder is the mock recorder for MockStatusClient. +type MockStatusClientMockRecorder struct { + mock *MockStatusClient +} + +// NewMockStatusClient creates a new mock instance. +func NewMockStatusClient(ctrl *gomock.Controller) *MockStatusClient { + mock := &MockStatusClient{ctrl: ctrl} + mock.recorder = &MockStatusClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockStatusClient) EXPECT() *MockStatusClientMockRecorder { + return m.recorder +} + +// ISGOMOCK indicates that this struct is a gomock mock. +func (m *MockStatusClient) ISGOMOCK() struct{} { + return struct{}{} +} + +// Status mocks base method. +func (m *MockStatusClient) Status() client.SubResourceWriter { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Status") + ret0, _ := ret[0].(client.SubResourceWriter) + return ret0 +} + +// Status indicates an expected call of Status. +func (mr *MockStatusClientMockRecorder) Status() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Status", reflect.TypeOf((*MockStatusClient)(nil).Status)) +} + +// MockSubResourceClientConstructor is a mock of SubResourceClientConstructor interface. +type MockSubResourceClientConstructor struct { + ctrl *gomock.Controller + recorder *MockSubResourceClientConstructorMockRecorder +} + +// MockSubResourceClientConstructorMockRecorder is the mock recorder for MockSubResourceClientConstructor. +type MockSubResourceClientConstructorMockRecorder struct { + mock *MockSubResourceClientConstructor +} + +// NewMockSubResourceClientConstructor creates a new mock instance. +func NewMockSubResourceClientConstructor(ctrl *gomock.Controller) *MockSubResourceClientConstructor { + mock := &MockSubResourceClientConstructor{ctrl: ctrl} + mock.recorder = &MockSubResourceClientConstructorMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockSubResourceClientConstructor) EXPECT() *MockSubResourceClientConstructorMockRecorder { + return m.recorder +} + +// ISGOMOCK indicates that this struct is a gomock mock. +func (m *MockSubResourceClientConstructor) ISGOMOCK() struct{} { + return struct{}{} +} + +// SubResource mocks base method. +func (m *MockSubResourceClientConstructor) SubResource(subResource string) client.SubResourceClient { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SubResource", subResource) + ret0, _ := ret[0].(client.SubResourceClient) + return ret0 +} + +// SubResource indicates an expected call of SubResource. +func (mr *MockSubResourceClientConstructorMockRecorder) SubResource(subResource any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SubResource", reflect.TypeOf((*MockSubResourceClientConstructor)(nil).SubResource), subResource) +} + +// MockSubResourceReader is a mock of SubResourceReader interface. +type MockSubResourceReader struct { + ctrl *gomock.Controller + recorder *MockSubResourceReaderMockRecorder +} + +// MockSubResourceReaderMockRecorder is the mock recorder for MockSubResourceReader. +type MockSubResourceReaderMockRecorder struct { + mock *MockSubResourceReader +} + +// NewMockSubResourceReader creates a new mock instance. +func NewMockSubResourceReader(ctrl *gomock.Controller) *MockSubResourceReader { + mock := &MockSubResourceReader{ctrl: ctrl} + mock.recorder = &MockSubResourceReaderMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockSubResourceReader) EXPECT() *MockSubResourceReaderMockRecorder { + return m.recorder +} + +// ISGOMOCK indicates that this struct is a gomock mock. +func (m *MockSubResourceReader) ISGOMOCK() struct{} { + return struct{}{} +} + +// Get mocks base method. +func (m *MockSubResourceReader) Get(ctx context.Context, obj, subResource client.Object, opts ...client.SubResourceGetOption) error { + m.ctrl.T.Helper() + varargs := []any{ctx, obj, subResource} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Get", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// Get indicates an expected call of Get. +func (mr *MockSubResourceReaderMockRecorder) Get(ctx, obj, subResource any, opts ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, obj, subResource}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockSubResourceReader)(nil).Get), varargs...) +} + +// MockSubResourceWriter is a mock of SubResourceWriter interface. +type MockSubResourceWriter struct { + ctrl *gomock.Controller + recorder *MockSubResourceWriterMockRecorder +} + +// MockSubResourceWriterMockRecorder is the mock recorder for MockSubResourceWriter. +type MockSubResourceWriterMockRecorder struct { + mock *MockSubResourceWriter +} + +// NewMockSubResourceWriter creates a new mock instance. +func NewMockSubResourceWriter(ctrl *gomock.Controller) *MockSubResourceWriter { + mock := &MockSubResourceWriter{ctrl: ctrl} + mock.recorder = &MockSubResourceWriterMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockSubResourceWriter) EXPECT() *MockSubResourceWriterMockRecorder { + return m.recorder +} + +// ISGOMOCK indicates that this struct is a gomock mock. +func (m *MockSubResourceWriter) ISGOMOCK() struct{} { + return struct{}{} +} + +// Create mocks base method. +func (m *MockSubResourceWriter) Create(ctx context.Context, obj, subResource client.Object, opts ...client.SubResourceCreateOption) error { + m.ctrl.T.Helper() + varargs := []any{ctx, obj, subResource} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Create", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// Create indicates an expected call of Create. +func (mr *MockSubResourceWriterMockRecorder) Create(ctx, obj, subResource any, opts ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, obj, subResource}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockSubResourceWriter)(nil).Create), varargs...) +} + +// Patch mocks base method. +func (m *MockSubResourceWriter) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.SubResourcePatchOption) error { + m.ctrl.T.Helper() + varargs := []any{ctx, obj, patch} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Patch", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// Patch indicates an expected call of Patch. +func (mr *MockSubResourceWriterMockRecorder) Patch(ctx, obj, patch any, opts ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, obj, patch}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Patch", reflect.TypeOf((*MockSubResourceWriter)(nil).Patch), varargs...) +} + +// Update mocks base method. +func (m *MockSubResourceWriter) Update(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error { + m.ctrl.T.Helper() + varargs := []any{ctx, obj} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Update", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// Update indicates an expected call of Update. +func (mr *MockSubResourceWriterMockRecorder) Update(ctx, obj any, opts ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, obj}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockSubResourceWriter)(nil).Update), varargs...) +} + +// MockSubResourceClient is a mock of SubResourceClient interface. +type MockSubResourceClient struct { + ctrl *gomock.Controller + recorder *MockSubResourceClientMockRecorder +} + +// MockSubResourceClientMockRecorder is the mock recorder for MockSubResourceClient. +type MockSubResourceClientMockRecorder struct { + mock *MockSubResourceClient +} + +// NewMockSubResourceClient creates a new mock instance. +func NewMockSubResourceClient(ctrl *gomock.Controller) *MockSubResourceClient { + mock := &MockSubResourceClient{ctrl: ctrl} + mock.recorder = &MockSubResourceClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockSubResourceClient) EXPECT() *MockSubResourceClientMockRecorder { + return m.recorder +} + +// ISGOMOCK indicates that this struct is a gomock mock. +func (m *MockSubResourceClient) ISGOMOCK() struct{} { + return struct{}{} +} + +// Create mocks base method. +func (m *MockSubResourceClient) Create(ctx context.Context, obj, subResource client.Object, opts ...client.SubResourceCreateOption) error { + m.ctrl.T.Helper() + varargs := []any{ctx, obj, subResource} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Create", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// Create indicates an expected call of Create. +func (mr *MockSubResourceClientMockRecorder) Create(ctx, obj, subResource any, opts ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, obj, subResource}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockSubResourceClient)(nil).Create), varargs...) +} + +// Get mocks base method. +func (m *MockSubResourceClient) Get(ctx context.Context, obj, subResource client.Object, opts ...client.SubResourceGetOption) error { + m.ctrl.T.Helper() + varargs := []any{ctx, obj, subResource} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Get", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// Get indicates an expected call of Get. +func (mr *MockSubResourceClientMockRecorder) Get(ctx, obj, subResource any, opts ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, obj, subResource}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockSubResourceClient)(nil).Get), varargs...) +} + +// Patch mocks base method. +func (m *MockSubResourceClient) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.SubResourcePatchOption) error { + m.ctrl.T.Helper() + varargs := []any{ctx, obj, patch} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Patch", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// Patch indicates an expected call of Patch. +func (mr *MockSubResourceClientMockRecorder) Patch(ctx, obj, patch any, opts ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, obj, patch}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Patch", reflect.TypeOf((*MockSubResourceClient)(nil).Patch), varargs...) +} + +// Update mocks base method. +func (m *MockSubResourceClient) Update(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error { + m.ctrl.T.Helper() + varargs := []any{ctx, obj} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Update", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// Update indicates an expected call of Update. +func (mr *MockSubResourceClientMockRecorder) Update(ctx, obj any, opts ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, obj}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockSubResourceClient)(nil).Update), varargs...) +} + +// MockClient is a mock of Client interface. +type MockClient struct { + ctrl *gomock.Controller + recorder *MockClientMockRecorder +} + +// MockClientMockRecorder is the mock recorder for MockClient. +type MockClientMockRecorder struct { + mock *MockClient +} + +// NewMockClient creates a new mock instance. +func NewMockClient(ctrl *gomock.Controller) *MockClient { + mock := &MockClient{ctrl: ctrl} + mock.recorder = &MockClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockClient) EXPECT() *MockClientMockRecorder { + return m.recorder +} + +// ISGOMOCK indicates that this struct is a gomock mock. +func (m *MockClient) ISGOMOCK() struct{} { + return struct{}{} +} + +// Create mocks base method. +func (m *MockClient) Create(ctx context.Context, obj client.Object, opts ...client.CreateOption) error { + m.ctrl.T.Helper() + varargs := []any{ctx, obj} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Create", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// Create indicates an expected call of Create. +func (mr *MockClientMockRecorder) Create(ctx, obj any, opts ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, obj}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockClient)(nil).Create), varargs...) +} + +// Delete mocks base method. +func (m *MockClient) Delete(ctx context.Context, obj client.Object, opts ...client.DeleteOption) error { + m.ctrl.T.Helper() + varargs := []any{ctx, obj} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Delete", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// Delete indicates an expected call of Delete. +func (mr *MockClientMockRecorder) Delete(ctx, obj any, opts ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, obj}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockClient)(nil).Delete), varargs...) +} + +// DeleteAllOf mocks base method. +func (m *MockClient) DeleteAllOf(ctx context.Context, obj client.Object, opts ...client.DeleteAllOfOption) error { + m.ctrl.T.Helper() + varargs := []any{ctx, obj} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "DeleteAllOf", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteAllOf indicates an expected call of DeleteAllOf. +func (mr *MockClientMockRecorder) DeleteAllOf(ctx, obj any, opts ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, obj}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteAllOf", reflect.TypeOf((*MockClient)(nil).DeleteAllOf), varargs...) +} + +// Get mocks base method. +func (m *MockClient) Get(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error { + m.ctrl.T.Helper() + varargs := []any{ctx, key, obj} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Get", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// Get indicates an expected call of Get. +func (mr *MockClientMockRecorder) Get(ctx, key, obj any, opts ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, key, obj}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockClient)(nil).Get), varargs...) +} + +// GroupVersionKindFor mocks base method. +func (m *MockClient) GroupVersionKindFor(obj runtime.Object) (schema.GroupVersionKind, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GroupVersionKindFor", obj) + ret0, _ := ret[0].(schema.GroupVersionKind) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GroupVersionKindFor indicates an expected call of GroupVersionKindFor. +func (mr *MockClientMockRecorder) GroupVersionKindFor(obj any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GroupVersionKindFor", reflect.TypeOf((*MockClient)(nil).GroupVersionKindFor), obj) +} + +// IsObjectNamespaced mocks base method. +func (m *MockClient) IsObjectNamespaced(obj runtime.Object) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "IsObjectNamespaced", obj) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// IsObjectNamespaced indicates an expected call of IsObjectNamespaced. +func (mr *MockClientMockRecorder) IsObjectNamespaced(obj any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsObjectNamespaced", reflect.TypeOf((*MockClient)(nil).IsObjectNamespaced), obj) +} + +// List mocks base method. +func (m *MockClient) List(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error { + m.ctrl.T.Helper() + varargs := []any{ctx, list} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "List", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// List indicates an expected call of List. +func (mr *MockClientMockRecorder) List(ctx, list any, opts ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, list}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockClient)(nil).List), varargs...) +} + +// Patch mocks base method. +func (m *MockClient) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.PatchOption) error { + m.ctrl.T.Helper() + varargs := []any{ctx, obj, patch} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Patch", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// Patch indicates an expected call of Patch. +func (mr *MockClientMockRecorder) Patch(ctx, obj, patch any, opts ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, obj, patch}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Patch", reflect.TypeOf((*MockClient)(nil).Patch), varargs...) +} + +// RESTMapper mocks base method. +func (m *MockClient) RESTMapper() meta.RESTMapper { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RESTMapper") + ret0, _ := ret[0].(meta.RESTMapper) + return ret0 +} + +// RESTMapper indicates an expected call of RESTMapper. +func (mr *MockClientMockRecorder) RESTMapper() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RESTMapper", reflect.TypeOf((*MockClient)(nil).RESTMapper)) +} + +// Scheme mocks base method. +func (m *MockClient) Scheme() *runtime.Scheme { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Scheme") + ret0, _ := ret[0].(*runtime.Scheme) + return ret0 +} + +// Scheme indicates an expected call of Scheme. +func (mr *MockClientMockRecorder) Scheme() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Scheme", reflect.TypeOf((*MockClient)(nil).Scheme)) +} + +// Status mocks base method. +func (m *MockClient) Status() client.SubResourceWriter { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Status") + ret0, _ := ret[0].(client.SubResourceWriter) + return ret0 +} + +// Status indicates an expected call of Status. +func (mr *MockClientMockRecorder) Status() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Status", reflect.TypeOf((*MockClient)(nil).Status)) +} + +// SubResource mocks base method. +func (m *MockClient) SubResource(subResource string) client.SubResourceClient { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SubResource", subResource) + ret0, _ := ret[0].(client.SubResourceClient) + return ret0 +} + +// SubResource indicates an expected call of SubResource. +func (mr *MockClientMockRecorder) SubResource(subResource any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SubResource", reflect.TypeOf((*MockClient)(nil).SubResource), subResource) +} + +// Update mocks base method. +func (m *MockClient) Update(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + m.ctrl.T.Helper() + varargs := []any{ctx, obj} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Update", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// Update indicates an expected call of Update. +func (mr *MockClientMockRecorder) Update(ctx, obj any, opts ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, obj}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockClient)(nil).Update), varargs...) +} + +// MockWithWatch is a mock of WithWatch interface. +type MockWithWatch struct { + ctrl *gomock.Controller + recorder *MockWithWatchMockRecorder +} + +// MockWithWatchMockRecorder is the mock recorder for MockWithWatch. +type MockWithWatchMockRecorder struct { + mock *MockWithWatch +} + +// NewMockWithWatch creates a new mock instance. +func NewMockWithWatch(ctrl *gomock.Controller) *MockWithWatch { + mock := &MockWithWatch{ctrl: ctrl} + mock.recorder = &MockWithWatchMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockWithWatch) EXPECT() *MockWithWatchMockRecorder { + return m.recorder +} + +// ISGOMOCK indicates that this struct is a gomock mock. +func (m *MockWithWatch) ISGOMOCK() struct{} { + return struct{}{} +} + +// Create mocks base method. +func (m *MockWithWatch) Create(ctx context.Context, obj client.Object, opts ...client.CreateOption) error { + m.ctrl.T.Helper() + varargs := []any{ctx, obj} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Create", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// Create indicates an expected call of Create. +func (mr *MockWithWatchMockRecorder) Create(ctx, obj any, opts ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, obj}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockWithWatch)(nil).Create), varargs...) +} + +// Delete mocks base method. +func (m *MockWithWatch) Delete(ctx context.Context, obj client.Object, opts ...client.DeleteOption) error { + m.ctrl.T.Helper() + varargs := []any{ctx, obj} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Delete", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// Delete indicates an expected call of Delete. +func (mr *MockWithWatchMockRecorder) Delete(ctx, obj any, opts ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, obj}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockWithWatch)(nil).Delete), varargs...) +} + +// DeleteAllOf mocks base method. +func (m *MockWithWatch) DeleteAllOf(ctx context.Context, obj client.Object, opts ...client.DeleteAllOfOption) error { + m.ctrl.T.Helper() + varargs := []any{ctx, obj} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "DeleteAllOf", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteAllOf indicates an expected call of DeleteAllOf. +func (mr *MockWithWatchMockRecorder) DeleteAllOf(ctx, obj any, opts ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, obj}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteAllOf", reflect.TypeOf((*MockWithWatch)(nil).DeleteAllOf), varargs...) +} + +// Get mocks base method. +func (m *MockWithWatch) Get(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error { + m.ctrl.T.Helper() + varargs := []any{ctx, key, obj} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Get", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// Get indicates an expected call of Get. +func (mr *MockWithWatchMockRecorder) Get(ctx, key, obj any, opts ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, key, obj}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockWithWatch)(nil).Get), varargs...) +} + +// GroupVersionKindFor mocks base method. +func (m *MockWithWatch) GroupVersionKindFor(obj runtime.Object) (schema.GroupVersionKind, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GroupVersionKindFor", obj) + ret0, _ := ret[0].(schema.GroupVersionKind) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GroupVersionKindFor indicates an expected call of GroupVersionKindFor. +func (mr *MockWithWatchMockRecorder) GroupVersionKindFor(obj any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GroupVersionKindFor", reflect.TypeOf((*MockWithWatch)(nil).GroupVersionKindFor), obj) +} + +// IsObjectNamespaced mocks base method. +func (m *MockWithWatch) IsObjectNamespaced(obj runtime.Object) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "IsObjectNamespaced", obj) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// IsObjectNamespaced indicates an expected call of IsObjectNamespaced. +func (mr *MockWithWatchMockRecorder) IsObjectNamespaced(obj any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsObjectNamespaced", reflect.TypeOf((*MockWithWatch)(nil).IsObjectNamespaced), obj) +} + +// List mocks base method. +func (m *MockWithWatch) List(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error { + m.ctrl.T.Helper() + varargs := []any{ctx, list} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "List", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// List indicates an expected call of List. +func (mr *MockWithWatchMockRecorder) List(ctx, list any, opts ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, list}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockWithWatch)(nil).List), varargs...) +} + +// Patch mocks base method. +func (m *MockWithWatch) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.PatchOption) error { + m.ctrl.T.Helper() + varargs := []any{ctx, obj, patch} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Patch", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// Patch indicates an expected call of Patch. +func (mr *MockWithWatchMockRecorder) Patch(ctx, obj, patch any, opts ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, obj, patch}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Patch", reflect.TypeOf((*MockWithWatch)(nil).Patch), varargs...) +} + +// RESTMapper mocks base method. +func (m *MockWithWatch) RESTMapper() meta.RESTMapper { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RESTMapper") + ret0, _ := ret[0].(meta.RESTMapper) + return ret0 +} + +// RESTMapper indicates an expected call of RESTMapper. +func (mr *MockWithWatchMockRecorder) RESTMapper() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RESTMapper", reflect.TypeOf((*MockWithWatch)(nil).RESTMapper)) +} + +// Scheme mocks base method. +func (m *MockWithWatch) Scheme() *runtime.Scheme { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Scheme") + ret0, _ := ret[0].(*runtime.Scheme) + return ret0 +} + +// Scheme indicates an expected call of Scheme. +func (mr *MockWithWatchMockRecorder) Scheme() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Scheme", reflect.TypeOf((*MockWithWatch)(nil).Scheme)) +} + +// Status mocks base method. +func (m *MockWithWatch) Status() client.SubResourceWriter { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Status") + ret0, _ := ret[0].(client.SubResourceWriter) + return ret0 +} + +// Status indicates an expected call of Status. +func (mr *MockWithWatchMockRecorder) Status() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Status", reflect.TypeOf((*MockWithWatch)(nil).Status)) +} + +// SubResource mocks base method. +func (m *MockWithWatch) SubResource(subResource string) client.SubResourceClient { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SubResource", subResource) + ret0, _ := ret[0].(client.SubResourceClient) + return ret0 +} + +// SubResource indicates an expected call of SubResource. +func (mr *MockWithWatchMockRecorder) SubResource(subResource any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SubResource", reflect.TypeOf((*MockWithWatch)(nil).SubResource), subResource) +} + +// Update mocks base method. +func (m *MockWithWatch) Update(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + m.ctrl.T.Helper() + varargs := []any{ctx, obj} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Update", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// Update indicates an expected call of Update. +func (mr *MockWithWatchMockRecorder) Update(ctx, obj any, opts ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, obj}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockWithWatch)(nil).Update), varargs...) +} + +// Watch mocks base method. +func (m *MockWithWatch) Watch(ctx context.Context, obj client.ObjectList, opts ...client.ListOption) (watch.Interface, error) { + m.ctrl.T.Helper() + varargs := []any{ctx, obj} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Watch", varargs...) + ret0, _ := ret[0].(watch.Interface) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Watch indicates an expected call of Watch. +func (mr *MockWithWatchMockRecorder) Watch(ctx, obj any, opts ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, obj}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Watch", reflect.TypeOf((*MockWithWatch)(nil).Watch), varargs...) +} + +// MockFieldIndexer is a mock of FieldIndexer interface. +type MockFieldIndexer struct { + ctrl *gomock.Controller + recorder *MockFieldIndexerMockRecorder +} + +// MockFieldIndexerMockRecorder is the mock recorder for MockFieldIndexer. +type MockFieldIndexerMockRecorder struct { + mock *MockFieldIndexer +} + +// NewMockFieldIndexer creates a new mock instance. +func NewMockFieldIndexer(ctrl *gomock.Controller) *MockFieldIndexer { + mock := &MockFieldIndexer{ctrl: ctrl} + mock.recorder = &MockFieldIndexerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockFieldIndexer) EXPECT() *MockFieldIndexerMockRecorder { + return m.recorder +} + +// ISGOMOCK indicates that this struct is a gomock mock. +func (m *MockFieldIndexer) ISGOMOCK() struct{} { + return struct{}{} +} + +// IndexField mocks base method. +func (m *MockFieldIndexer) IndexField(ctx context.Context, obj client.Object, field string, extractValue client.IndexerFunc) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "IndexField", ctx, obj, field, extractValue) + ret0, _ := ret[0].(error) + return ret0 +} + +// IndexField indicates an expected call of IndexField. +func (mr *MockFieldIndexerMockRecorder) IndexField(ctx, obj, field, extractValue any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IndexField", reflect.TypeOf((*MockFieldIndexer)(nil).IndexField), ctx, obj, field, extractValue) +} diff --git a/pkg/k8s/object/builders/agent/configmap/configmap.go b/pkg/k8s/object/builders/agent/configmap/configmap.go new file mode 100644 index 00000000..7adb91db --- /dev/null +++ b/pkg/k8s/object/builders/agent/configmap/configmap.go @@ -0,0 +1,167 @@ +package configmap + +import ( + "strconv" + "strings" + + "gopkg.in/yaml.v3" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + instanav1 "github.com/instana/instana-agent-operator/api/v1" + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/builder" + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/constants" + "github.com/instana/instana-agent-operator/pkg/k8s/operator/status" + "github.com/instana/instana-agent-operator/pkg/optional" + "github.com/instana/instana-agent-operator/pkg/or_die" + "github.com/instana/instana-agent-operator/pkg/pointer" +) + +type configMapBuilder struct { + *instanav1.InstanaAgent + statusManager status.AgentStatusManager +} + +func (c *configMapBuilder) ComponentName() string { + return constants.ComponentInstanaAgent +} + +func (c *configMapBuilder) IsNamespaced() bool { + return true +} + +func yamlOrDie(obj any) string { + return string( + or_die.New[[]byte](). + ResultOrDie( + func() ([]byte, error) { + return yaml.Marshal(obj) + }, + ), + ) +} + +func keyEqualsValue(key string, value string) string { + return key + "=" + value +} + +func (c *configMapBuilder) getData() map[string]string { + res := make(map[string]string) + + optional.Of(c.Spec.Cluster.Name).IfPresent( + func(clusterName string) { + res["cluster_name"] = clusterName + }, + ) + + optional.Of(c.Spec.Agent.ConfigurationYaml).IfPresent( + func(configYaml string) { + res["configuration.yaml"] = configYaml + }, + ) + + if otlp := c.Spec.OpenTelemetry; otlp.IsEnabled() { + otlpPluginSettings := map[string]instanav1.OpenTelemetry{"com.instana.plugin.opentelemetry": otlp} + res["configuration-opentelemetry.yaml"] = yamlOrDie(&otlpPluginSettings) + } + + if pointer.DerefOrEmpty(c.Spec.Prometheus.RemoteWrite.Enabled) { + res["configuration-prometheus-remote-write.yaml"] = yamlOrDie( + map[string]any{ + "com.instana.plugin.prometheus": map[string]any{ + "remote_write": map[string]any{ + "enabled": true, + }, + }, + }, + ) + } + + // Deprecated since k8s sensor deployment will always be enabled now, + // can remove once deprecated sensor is removed from agent + + res["configuration-disable-kubernetes-sensor.yaml"] = yamlOrDie( + map[string]any{ + "com.instana.plugin.kubernetes": map[string]any{ + "enabled": false, + }, + }, + ) + + for i, backend := range c.Spec.Agent.AdditionalBackends { + lines := make([]string, 0, 10) + lines = append( + lines, + keyEqualsValue("host", backend.EndpointHost), + keyEqualsValue("port", optional.Of(backend.EndpointPort).GetOrDefault("443")), + keyEqualsValue("key", backend.Key), + keyEqualsValue("protocol", "HTTP/2"), + ) + + optional.Of(c.Spec.Agent.ProxyHost).IfPresent( + func(proxyHost string) { + lines = append( + lines, + keyEqualsValue("proxy.type", "HTTP"), + keyEqualsValue("proxy.host", proxyHost), + keyEqualsValue( + "proxy.port", optional.Of(c.Spec.Agent.ProxyPort).GetOrDefault("80"), + ), + ) + }, + ) + + optional.Of(c.Spec.Agent.ProxyUser).IfPresent( + func(proxyUser string) { + lines = append(lines, keyEqualsValue("proxy.user", proxyUser)) + }, + ) + + optional.Of(c.Spec.Agent.ProxyPassword).IfPresent( + func(proxyPassword string) { + lines = append(lines, keyEqualsValue("proxy.password", proxyPassword)) + }, + ) + + if c.Spec.Agent.ProxyUseDNS { + lines = append(lines, keyEqualsValue("proxyUseDNS", "true")) + } + + res["additional-backend-"+strconv.Itoa(i+2)] = strings.Join(lines, "\n") + } + + return res + +} + +func (c *configMapBuilder) Build() (res optional.Optional[client.Object]) { + defer func() { + res.IfPresent( + func(cm client.Object) { + c.statusManager.SetAgentConfigMap(client.ObjectKeyFromObject(cm)) + }, + ) + }() + + return optional.Of[client.Object]( + &corev1.ConfigMap{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "ConfigMap", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: c.Name, + Namespace: c.Namespace, + }, + Data: c.getData(), + }, + ) +} + +func NewConfigMapBuilder(agent *instanav1.InstanaAgent, statusManager status.AgentStatusManager) builder.ObjectBuilder { + return &configMapBuilder{ + InstanaAgent: agent, + statusManager: statusManager, + } +} diff --git a/pkg/k8s/object/builders/agent/configmap/configmap_test.go b/pkg/k8s/object/builders/agent/configmap/configmap_test.go new file mode 100644 index 00000000..9e5ed709 --- /dev/null +++ b/pkg/k8s/object/builders/agent/configmap/configmap_test.go @@ -0,0 +1,108 @@ +/* +(c) Copyright IBM Corp. 2024 +(c) Copyright Instana Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package configmap + +import ( + "testing" + + "github.com/stretchr/testify/require" + gomock "go.uber.org/mock/gomock" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + instanav1 "github.com/instana/instana-agent-operator/api/v1" + "github.com/instana/instana-agent-operator/pkg/optional" + "github.com/instana/instana-agent-operator/pkg/pointer" +) + +func TestAgentConfigMapBuilder_Build(t *testing.T) { + assertions := require.New(t) + ctrl := gomock.NewController(t) + + agentCm := &instanav1.InstanaAgent{ + ObjectMeta: metav1.ObjectMeta{ + Name: "llsdfoije", + Namespace: "glkdsoijeijsd", + }, + Spec: instanav1.InstanaAgentSpec{ + Cluster: instanav1.Name{ + Name: "eoisdgoijds", + }, + Agent: instanav1.BaseAgentSpec{ + ConfigurationYaml: "riosoidoijdsg", + ProxyHost: "weoisdoijsdg", + ProxyPort: "lksdlkjsdglkjsd", + ProxyUser: "peoijsadglkj", + ProxyPassword: "relksdlkj", + ProxyUseDNS: true, + AdditionalBackends: []instanav1.BackendSpec{ + { + EndpointHost: "eoijsdlkjf", + EndpointPort: "goieoijsdofj", + Key: "eoisdljsdlkfj", + }, + { + EndpointHost: "glknsdlknmdsflk", + EndpointPort: "lgslkjsdfoieoiljsdf", + Key: "sdlkjsadofjpoej", + }, + }, + }, + OpenTelemetry: instanav1.OpenTelemetry{ + GRPC: &instanav1.Enabled{}, + }, + Prometheus: instanav1.Prometheus{ + RemoteWrite: instanav1.Enabled{ + Enabled: pointer.To(true), + }, + }, + }, + } + + statusManager := NewMockAgentStatusManager(ctrl) + statusManager.EXPECT().SetAgentConfigMap(gomock.Eq(client.ObjectKeyFromObject(agentCm))) + + builder := NewConfigMapBuilder(agentCm, statusManager) + + actual := builder.Build() + + expected := optional.Of[client.Object]( + &v1.ConfigMap{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "ConfigMap", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "llsdfoije", + Namespace: "glkdsoijeijsd", + }, + Data: map[string]string{ + "cluster_name": "eoisdgoijds", + "configuration.yaml": "riosoidoijdsg", + "configuration-opentelemetry.yaml": "com.instana.plugin.opentelemetry:\n grpc: {}\n", + "configuration-prometheus-remote-write.yaml": "com.instana.plugin.prometheus:\n remote_write:\n enabled: true\n", + "configuration-disable-kubernetes-sensor.yaml": "com.instana.plugin.kubernetes:\n enabled: false\n", + "additional-backend-2": "host=eoijsdlkjf\nport=goieoijsdofj\nkey=eoisdljsdlkfj\nprotocol=HTTP/2\nproxy.type=HTTP\nproxy.host=weoisdoijsdg\nproxy.port=lksdlkjsdglkjsd\nproxy.user=peoijsadglkj\nproxy.password=relksdlkj\nproxyUseDNS=true", + "additional-backend-3": "host=glknsdlknmdsflk\nport=lgslkjsdfoieoiljsdf\nkey=sdlkjsadofjpoej\nprotocol=HTTP/2\nproxy.type=HTTP\nproxy.host=weoisdoijsdg\nproxy.port=lksdlkjsdglkjsd\nproxy.user=peoijsadglkj\nproxy.password=relksdlkj\nproxyUseDNS=true", + }, + }, + ) + + assertions.Equal(expected, actual) +} diff --git a/pkg/k8s/object/builders/agent/configmap/status_mock_test.go b/pkg/k8s/object/builders/agent/configmap/status_mock_test.go new file mode 100644 index 00000000..7f28d5d0 --- /dev/null +++ b/pkg/k8s/object/builders/agent/configmap/status_mock_test.go @@ -0,0 +1,119 @@ +// /* +// (c) Copyright IBM Corp. 2024 +// (c) Copyright Instana Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// */ +// + +// Code generated by MockGen. DO NOT EDIT. +package configmap + +import ( + context "context" + reflect "reflect" + + v1 "github.com/instana/instana-agent-operator/api/v1" + gomock "go.uber.org/mock/gomock" + client "sigs.k8s.io/controller-runtime/pkg/client" +) + +// MockAgentStatusManager is a mock of AgentStatusManager interface. +type MockAgentStatusManager struct { + ctrl *gomock.Controller + recorder *MockAgentStatusManagerMockRecorder +} + +// MockAgentStatusManagerMockRecorder is the mock recorder for MockAgentStatusManager. +type MockAgentStatusManagerMockRecorder struct { + mock *MockAgentStatusManager +} + +// NewMockAgentStatusManager creates a new mock instance. +func NewMockAgentStatusManager(ctrl *gomock.Controller) *MockAgentStatusManager { + mock := &MockAgentStatusManager{ctrl: ctrl} + mock.recorder = &MockAgentStatusManagerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockAgentStatusManager) EXPECT() *MockAgentStatusManagerMockRecorder { + return m.recorder +} + +// ISGOMOCK indicates that this struct is a gomock mock. +func (m *MockAgentStatusManager) ISGOMOCK() struct{} { + return struct{}{} +} + +// AddAgentDaemonset mocks base method. +func (m *MockAgentStatusManager) AddAgentDaemonset(agentDaemonset client.ObjectKey) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "AddAgentDaemonset", agentDaemonset) +} + +// AddAgentDaemonset indicates an expected call of AddAgentDaemonset. +func (mr *MockAgentStatusManagerMockRecorder) AddAgentDaemonset(agentDaemonset any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddAgentDaemonset", reflect.TypeOf((*MockAgentStatusManager)(nil).AddAgentDaemonset), agentDaemonset) +} + +// SetAgentConfigMap mocks base method. +func (m *MockAgentStatusManager) SetAgentConfigMap(agentConfigMap client.ObjectKey) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetAgentConfigMap", agentConfigMap) +} + +// SetAgentConfigMap indicates an expected call of SetAgentConfigMap. +func (mr *MockAgentStatusManagerMockRecorder) SetAgentConfigMap(agentConfigMap any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetAgentConfigMap", reflect.TypeOf((*MockAgentStatusManager)(nil).SetAgentConfigMap), agentConfigMap) +} + +// SetAgentOld mocks base method. +func (m *MockAgentStatusManager) SetAgentOld(agent *v1.InstanaAgent) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetAgentOld", agent) +} + +// SetAgentOld indicates an expected call of SetAgentOld. +func (mr *MockAgentStatusManagerMockRecorder) SetAgentOld(agent any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetAgentOld", reflect.TypeOf((*MockAgentStatusManager)(nil).SetAgentOld), agent) +} + +// SetK8sSensorDeployment mocks base method. +func (m *MockAgentStatusManager) SetK8sSensorDeployment(k8sSensorDeployment client.ObjectKey) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetK8sSensorDeployment", k8sSensorDeployment) +} + +// SetK8sSensorDeployment indicates an expected call of SetK8sSensorDeployment. +func (mr *MockAgentStatusManagerMockRecorder) SetK8sSensorDeployment(k8sSensorDeployment any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetK8sSensorDeployment", reflect.TypeOf((*MockAgentStatusManager)(nil).SetK8sSensorDeployment), k8sSensorDeployment) +} + +// UpdateAgentStatus mocks base method. +func (m *MockAgentStatusManager) UpdateAgentStatus(ctx context.Context, reconcileErr error) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateAgentStatus", ctx, reconcileErr) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateAgentStatus indicates an expected call of UpdateAgentStatus. +func (mr *MockAgentStatusManagerMockRecorder) UpdateAgentStatus(ctx, reconcileErr any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateAgentStatus", reflect.TypeOf((*MockAgentStatusManager)(nil).UpdateAgentStatus), ctx, reconcileErr) +} diff --git a/pkg/k8s/object/builders/agent/daemonset/daemonset.go b/pkg/k8s/object/builders/agent/daemonset/daemonset.go new file mode 100644 index 00000000..e2377e4e --- /dev/null +++ b/pkg/k8s/object/builders/agent/daemonset/daemonset.go @@ -0,0 +1,273 @@ +/* +(c) Copyright IBM Corp. 2024 +(c) Copyright Instana Inc. 2024 +*/ + +package daemonset + +import ( + "fmt" + + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/intstr" + "sigs.k8s.io/controller-runtime/pkg/client" + + instanav1 "github.com/instana/instana-agent-operator/api/v1" + "github.com/instana/instana-agent-operator/pkg/hash" + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/builder" + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/constants" + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/env" + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/helpers" + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/ports" + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/volume" + "github.com/instana/instana-agent-operator/pkg/k8s/object/transformations" + "github.com/instana/instana-agent-operator/pkg/k8s/operator/status" + "github.com/instana/instana-agent-operator/pkg/optional" + "github.com/instana/instana-agent-operator/pkg/pointer" +) + +const ( + componentName = constants.ComponentInstanaAgent +) + +type daemonSetBuilder struct { + *instanav1.InstanaAgent + statusManager status.AgentStatusManager + + transformations.PodSelectorLabelGenerator + hash.JsonHasher + helpers.Helpers + ports.PortsBuilder + env.EnvBuilder + volume.VolumeBuilder + + zone *instanav1.Zone +} + +func (d *daemonSetBuilder) ComponentName() string { + return componentName +} + +func (d *daemonSetBuilder) IsNamespaced() bool { + return true +} + +func (d *daemonSetBuilder) getPodTemplateLabels() map[string]string { + podLabels := optional.Of(d.InstanaAgent.Spec.Agent.Pod.Labels).GetOrDefault(map[string]string{}) + podLabels[constants.LabelAgentMode] = string(optional.Of(d.InstanaAgent.Spec.Agent.Mode).GetOrDefault(instanav1.APM)) + + return d.GetPodLabels(podLabels) +} + +func (d *daemonSetBuilder) getEnvVars() []corev1.EnvVar { + return d.EnvBuilder.Build( + env.AgentModeEnv, + env.ZoneNameEnv, + env.ClusterNameEnv, + env.AgentEndpointEnv, + env.AgentEndpointPortEnv, + env.MavenRepoURLEnv, + env.MavenRepoFeaturesPath, + env.MavenRepoSharedPath, + env.ProxyHostEnv, + env.ProxyPortEnv, + env.ProxyProtocolEnv, + env.ProxyUserEnv, + env.ProxyPasswordEnv, + env.ProxyUseDNSEnv, + env.ListenAddressEnv, + env.RedactK8sSecretsEnv, + env.ConfigPathEnv, + env.InstanaAgentKeyEnv, + env.DownloadKeyEnv, + env.InstanaAgentPodNameEnv, + env.PodIPEnv, + env.K8sServiceDomainEnv, + ) +} + +func (d *daemonSetBuilder) getContainerPorts() []corev1.ContainerPort { + return d.GetContainerPorts( + ports.AgentAPIsPort, + ports.AgentSocketPort, + ports.OpenTelemetryLegacyPort, + ports.OpenTelemetryGRPCPort, + ports.OpenTelemetryHTTPPort, + ) +} + +func (d *daemonSetBuilder) getVolumes() ([]corev1.Volume, []corev1.VolumeMount) { + return d.VolumeBuilder.Build( + volume.DevVolume, + volume.RunVolume, + volume.VarRunVolume, + volume.VarRunKuboVolume, + volume.VarRunContainerdVolume, + volume.VarContainerdConfigVolume, + volume.SysVolume, + volume.VarLogVolume, + volume.VarLibVolume, + volume.VarDataVolume, + volume.MachineIdVolume, + volume.ConfigVolume, + volume.TlsVolume, + volume.RepoVolume, + ) +} + +func (d *daemonSetBuilder) getName() string { + switch d.zone { + case nil: + return d.InstanaAgent.Name + default: + return fmt.Sprintf("%s-%s", d.InstanaAgent.Name, d.zone.Name.Name) + } +} + +func (d *daemonSetBuilder) getNonStandardLabels() map[string]string { + switch d.zone { + case nil: + return nil + default: + return map[string]string{ + transformations.ZoneLabel: d.zone.Name.Name, + } + } +} + +func (d *daemonSetBuilder) getAffinity() *corev1.Affinity { + switch d.zone { + case nil: + return &d.InstanaAgent.Spec.Agent.Pod.Affinity + default: + return &d.zone.Affinity + } +} + +func (d *daemonSetBuilder) getTolerations() []corev1.Toleration { + switch d.zone { + case nil: + return d.InstanaAgent.Spec.Agent.Pod.Tolerations + default: + return d.zone.Tolerations + } +} + +func (d *daemonSetBuilder) build() *appsv1.DaemonSet { + volumes, volumeMounts := d.getVolumes() + + return &appsv1.DaemonSet{ + TypeMeta: metav1.TypeMeta{ + Kind: "DaemonSet", + APIVersion: "apps/v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: d.getName(), + Namespace: d.Namespace, + Labels: d.getNonStandardLabels(), + }, + Spec: appsv1.DaemonSetSpec{ + MinReadySeconds: int32(d.Spec.Agent.MinReadySeconds), + Selector: &metav1.LabelSelector{ + MatchLabels: d.GetPodSelectorLabels(), + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: d.getPodTemplateLabels(), + Annotations: d.InstanaAgent.Spec.Agent.Pod.Annotations, + }, + Spec: corev1.PodSpec{ + Volumes: volumes, + ServiceAccountName: d.ServiceAccountName(), + NodeSelector: d.Spec.Agent.Pod.NodeSelector, + HostNetwork: true, + HostPID: true, + PriorityClassName: d.Spec.Agent.Pod.PriorityClassName, + DNSPolicy: corev1.DNSClusterFirstWithHostNet, + ImagePullSecrets: d.ImagePullSecrets(), + Containers: []corev1.Container{ + { + Name: "instana-agent", + Image: d.Spec.Agent.Image(), + ImagePullPolicy: d.Spec.Agent.PullPolicy, + VolumeMounts: volumeMounts, + Env: d.getEnvVars(), + SecurityContext: &corev1.SecurityContext{ + Privileged: pointer.To(true), + }, + LivenessProbe: &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + HTTPGet: &corev1.HTTPGetAction{ + Host: "127.0.0.1", + Path: "/status", + Port: intstr.FromString(string(ports.AgentAPIsPort)), + }, + }, + InitialDelaySeconds: 600, + TimeoutSeconds: 5, + PeriodSeconds: 10, + FailureThreshold: 3, + }, + Resources: d.Spec.Agent.Pod.ResourceRequirements.GetOrDefault(), + Ports: d.getContainerPorts(), + }, + }, + Tolerations: d.getTolerations(), + Affinity: d.getAffinity(), + }, + }, + UpdateStrategy: d.InstanaAgent.Spec.Agent.UpdateStrategy, + }, + } +} + +func (d *daemonSetBuilder) Build() (res optional.Optional[client.Object]) { + defer func() { + res.IfPresent( + func(ds client.Object) { + d.statusManager.AddAgentDaemonset(client.ObjectKeyFromObject(ds)) + }, + ) + }() + + switch { + case d.Spec.Agent.Key == "" && d.Spec.Agent.KeysSecret == "": + fallthrough + case d.zone == nil && d.Spec.Zone.Name == "" && d.Spec.Cluster.Name == "": + fallthrough + case d.zone != nil && d.Spec.Cluster.Name == "": + return optional.Empty[client.Object]() + default: + return optional.Of[client.Object](d.build()) + } +} + +func NewDaemonSetBuilder( + agent *instanav1.InstanaAgent, + isOpenshift bool, + statusManager status.AgentStatusManager, +) builder.ObjectBuilder { + return NewDaemonSetBuilderWithZoneInfo(agent, isOpenshift, statusManager, nil) +} + +func NewDaemonSetBuilderWithZoneInfo( + agent *instanav1.InstanaAgent, + isOpenshift bool, + statusManager status.AgentStatusManager, + zone *instanav1.Zone, +) builder.ObjectBuilder { + return &daemonSetBuilder{ + InstanaAgent: agent, + statusManager: statusManager, + + PodSelectorLabelGenerator: transformations.PodSelectorLabelsWithZoneInfo(agent, componentName, zone), + JsonHasher: hash.NewJsonHasher(), + Helpers: helpers.NewHelpers(agent), + PortsBuilder: ports.NewPortsBuilder(agent), + EnvBuilder: env.NewEnvBuilderWithZoneInfo(agent, zone), + VolumeBuilder: volume.NewVolumeBuilder(agent, isOpenshift), + zone: zone, + } +} diff --git a/pkg/k8s/object/builders/agent/daemonset/daemonset_test.go b/pkg/k8s/object/builders/agent/daemonset/daemonset_test.go new file mode 100644 index 00000000..58016398 --- /dev/null +++ b/pkg/k8s/object/builders/agent/daemonset/daemonset_test.go @@ -0,0 +1,405 @@ +/* +(c) Copyright IBM Corp. 2024 +(c) Copyright Instana Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package daemonset + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + gomock "go.uber.org/mock/gomock" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/rand" + + instanav1 "github.com/instana/instana-agent-operator/api/v1" + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/constants" + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/env" + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/ports" + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/volume" + "github.com/instana/instana-agent-operator/pkg/k8s/object/transformations" +) + +func TestDaemonSetBuilder_getPodTemplateLabels(t *testing.T) { + for _, test := range []struct { + name string + getPodLabelsInput map[string]string + agentSpec instanav1.InstanaAgentSpec + }{ + { + name: "agent_mode_unset", + getPodLabelsInput: map[string]string{ + "instana/agent-mode": string(instanav1.APM), + }, + agentSpec: instanav1.InstanaAgentSpec{}, + }, + { + name: "agent_mode_set_by_user", + getPodLabelsInput: map[string]string{ + "instana/agent-mode": string(instanav1.KUBERNETES), + }, + agentSpec: instanav1.InstanaAgentSpec{ + Agent: instanav1.BaseAgentSpec{ + Mode: instanav1.KUBERNETES, + }, + }, + }, + { + name: "agent_mode_unset_with_user_given_pod_labels", + getPodLabelsInput: map[string]string{ + "asdfasdf": "eoisdgoinv", + "reoirionv": "98458hgoisjdf", + "instana/agent-mode": string(instanav1.APM), + }, + agentSpec: instanav1.InstanaAgentSpec{ + Agent: instanav1.BaseAgentSpec{ + Pod: instanav1.AgentPodSpec{ + Labels: map[string]string{ + "asdfasdf": "eoisdgoinv", + "reoirionv": "98458hgoisjdf", + }, + }, + }, + }, + }, + { + name: "agent_mode_set_by_user_with_user_given_pod_labels", + getPodLabelsInput: map[string]string{ + "asdfasdf": "eoisdgoinv", + "reoirionv": "98458hgoisjdf", + "instana/agent-mode": string(instanav1.KUBERNETES), + }, + agentSpec: instanav1.InstanaAgentSpec{ + Agent: instanav1.BaseAgentSpec{ + Mode: instanav1.KUBERNETES, + Pod: instanav1.AgentPodSpec{ + Labels: map[string]string{ + "asdfasdf": "eoisdgoinv", + "reoirionv": "98458hgoisjdf", + }, + }, + }, + }, + }, + } { + t.Run( + test.name, func(t *testing.T) { + assertions := require.New(t) + ctrl := gomock.NewController(t) + + expected := map[string]string{ + "adsf": "eroinsvd", + "osdgoiego": "rwuriunsv", + "e8uriunv": "rrudsiu", + } + + podSelector := NewMockPodSelectorLabelGenerator(ctrl) + podSelector.EXPECT().GetPodLabels(gomock.Eq(test.getPodLabelsInput)).Return(expected) + + d := &daemonSetBuilder{ + InstanaAgent: &instanav1.InstanaAgent{ + Spec: test.agentSpec, + }, + PodSelectorLabelGenerator: podSelector, + } + + actual := d.getPodTemplateLabels() + + assertions.Equal(expected, actual) + }, + ) + } +} + +func TestDaemonSetBuilder_getEnvVars(t *testing.T) { + assertions := require.New(t) + ctrl := gomock.NewController(t) + + expected := []corev1.EnvVar{ + { + Name: "foo", + Value: "bar", + }, + { + Name: "hello", + Value: "world", + }, + } + + envBuilder := NewMockEnvBuilder(ctrl) + envBuilder.EXPECT().Build( + env.AgentModeEnv, + env.ZoneNameEnv, + env.ClusterNameEnv, + env.AgentEndpointEnv, + env.AgentEndpointPortEnv, + env.MavenRepoURLEnv, + env.MavenRepoFeaturesPath, + env.MavenRepoSharedPath, + env.ProxyHostEnv, + env.ProxyPortEnv, + env.ProxyProtocolEnv, + env.ProxyUserEnv, + env.ProxyPasswordEnv, + env.ProxyUseDNSEnv, + env.ListenAddressEnv, + env.RedactK8sSecretsEnv, + env.ConfigPathEnv, + env.InstanaAgentKeyEnv, + env.DownloadKeyEnv, + env.InstanaAgentPodNameEnv, + env.PodIPEnv, + env.K8sServiceDomainEnv, + ). + Return(expected) + + db := &daemonSetBuilder{ + EnvBuilder: envBuilder, + } + + actual := db.getEnvVars() + + assertions.Equal(expected, actual) +} + +func TestDaemonSetBuilder_getContainerPorts(t *testing.T) { + assertions := require.New(t) + ctrl := gomock.NewController(t) + + expected := []corev1.ContainerPort{ + { + Name: "something", + ContainerPort: 12345, + }, + } + + portsBuilder := NewMockPortsBuilder(ctrl) + portsBuilder.EXPECT().GetContainerPorts( + ports.AgentAPIsPort, + ports.AgentSocketPort, + ports.OpenTelemetryLegacyPort, + ports.OpenTelemetryGRPCPort, + ports.OpenTelemetryHTTPPort, + ).Return(expected) + + db := &daemonSetBuilder{ + PortsBuilder: portsBuilder, + } + + actual := db.getContainerPorts() + + assertions.Equal(expected, actual) +} + +func TestDaemonSetBuilder_getVolumes(t *testing.T) { + assertions := require.New(t) + ctrl := gomock.NewController(t) + + expectedVolumes := []corev1.Volume{{Name: rand.String(10)}} + expectedVolumeMounts := []corev1.VolumeMount{{Name: rand.String(10)}} + + volumeBuilder := NewMockVolumeBuilder(ctrl) + volumeBuilder.EXPECT().Build( + gomock.Eq(volume.DevVolume), + gomock.Eq(volume.RunVolume), + gomock.Eq(volume.VarRunVolume), + gomock.Eq(volume.VarRunKuboVolume), + gomock.Eq(volume.VarRunContainerdVolume), + gomock.Eq(volume.VarContainerdConfigVolume), + gomock.Eq(volume.SysVolume), + gomock.Eq(volume.VarLogVolume), + gomock.Eq(volume.VarLibVolume), + gomock.Eq(volume.VarDataVolume), + gomock.Eq(volume.MachineIdVolume), + gomock.Eq(volume.ConfigVolume), + gomock.Eq(volume.TlsVolume), + gomock.Eq(volume.RepoVolume), + ).Return(expectedVolumes, expectedVolumeMounts) + + db := &daemonSetBuilder{ + VolumeBuilder: volumeBuilder, + } + + actualVolumes, actualVolumeMounts := db.getVolumes() + + assertions.Equal(expectedVolumes, actualVolumes) + assertions.Equal(expectedVolumeMounts, actualVolumeMounts) +} + +func TestDaemonSetBuilder_IsNamespaced_ComponentName(t *testing.T) { + assertions := assert.New(t) + + dsBuilder := NewDaemonSetBuilder(nil, false, nil) + + assertions.True(dsBuilder.IsNamespaced()) + assertions.Equal(constants.ComponentInstanaAgent, dsBuilder.ComponentName()) +} + +func TestZoning(t *testing.T) { + agentName := rand.String(10) + zoneName := rand.String(10) + + for _, test := range []struct { + name string + expectedName string + hasZoneSet bool + expectedNonStandardLabels map[string]string + expectedAffinity *corev1.Affinity + expectedTolerations []corev1.Toleration + }{ + { + name: "no_zone_set", + expectedName: agentName, + hasZoneSet: false, + expectedNonStandardLabels: nil, + expectedTolerations: []corev1.Toleration{{Key: agentName}}, + }, + { + name: "with_zone_set", + expectedName: agentName + "-" + zoneName, + hasZoneSet: true, + expectedNonStandardLabels: map[string]string{ + transformations.ZoneLabel: zoneName, + }, + expectedTolerations: []corev1.Toleration{{Key: zoneName}}, + }, + } { + t.Run( + test.name, func(t *testing.T) { + assertions := require.New(t) + + agent := &instanav1.InstanaAgent{ + ObjectMeta: metav1.ObjectMeta{ + Name: agentName, + }, + Spec: instanav1.InstanaAgentSpec{ + Agent: instanav1.BaseAgentSpec{ + Pod: instanav1.AgentPodSpec{ + Affinity: corev1.Affinity{}, + Tolerations: []corev1.Toleration{ + { + Key: agentName, + }, + }, + }, + }, + }, + } + zone := &instanav1.Zone{ + Name: instanav1.Name{ + Name: zoneName, + }, + Affinity: corev1.Affinity{}, + Tolerations: []corev1.Toleration{ + { + Key: zoneName, + }, + }, + } + + dsBuilder := &daemonSetBuilder{ + InstanaAgent: agent, + } + + if test.hasZoneSet { + dsBuilder.zone = zone + } + + t.Run( + "getName", func(t *testing.T) { + actualName := dsBuilder.getName() + assertions.Equal(test.expectedName, actualName) + }, + ) + + t.Run( + "getNonStandardLabels", func(t *testing.T) { + actualNonStandardLabels := dsBuilder.getNonStandardLabels() + assertions.Equal(test.expectedNonStandardLabels, actualNonStandardLabels) + }, + ) + + t.Run( + "getAffinity", func(t *testing.T) { + assertions.NotSame(&zone.Affinity, &agent.Spec.Agent.Pod.Affinity) + + expectedAffinity := func() *corev1.Affinity { + switch test.hasZoneSet { + case true: + return &zone.Affinity + default: + return &agent.Spec.Agent.Pod.Affinity + } + }() + + actualAffinity := dsBuilder.getAffinity() + assertions.Same(expectedAffinity, actualAffinity) + }, + ) + + t.Run( + "getTolerations", func(t *testing.T) { + actualTolerations := dsBuilder.getTolerations() + assertions.Equal(test.expectedTolerations, actualTolerations) + }, + ) + }, + ) + } +} + +func TestDaemonSetBuilder_Build(t *testing.T) { + for _, test := range []struct { + name string + agent *instanav1.InstanaAgent + expectPresent bool + }{ + { + name: "should_be_not_present", + agent: &instanav1.InstanaAgent{}, + expectPresent: false, + }, + { + name: "should_be_present", + agent: &instanav1.InstanaAgent{ + Spec: instanav1.InstanaAgentSpec{ + Agent: instanav1.BaseAgentSpec{Key: "key"}, + Cluster: instanav1.Name{Name: "cluster"}, + }, + }, + expectPresent: true, + }, + } { + t.Run( + test.name, func(t *testing.T) { + assertions := assert.New(t) + ctrl := gomock.NewController(t) + + status := NewMockAgentStatusManager(ctrl) + if test.expectPresent { + status.EXPECT().AddAgentDaemonset(gomock.Any()) + } + + dsBuilder := NewDaemonSetBuilder(test.agent, false, status) + + result := dsBuilder.Build() + assertions.Equal(test.expectPresent, result.IsPresent()) + }, + ) + } +} diff --git a/pkg/k8s/object/builders/agent/daemonset/env_builder_mock_test.go b/pkg/k8s/object/builders/agent/daemonset/env_builder_mock_test.go new file mode 100644 index 00000000..31ba64a8 --- /dev/null +++ b/pkg/k8s/object/builders/agent/daemonset/env_builder_mock_test.go @@ -0,0 +1,74 @@ +// /* +// (c) Copyright IBM Corp. 2024 +// (c) Copyright Instana Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// */ +// + +// Code generated by MockGen. DO NOT EDIT. +package daemonset + +import ( + reflect "reflect" + + env "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/env" + gomock "go.uber.org/mock/gomock" + v1 "k8s.io/api/core/v1" +) + +// MockEnvBuilder is a mock of EnvBuilder interface. +type MockEnvBuilder struct { + ctrl *gomock.Controller + recorder *MockEnvBuilderMockRecorder +} + +// MockEnvBuilderMockRecorder is the mock recorder for MockEnvBuilder. +type MockEnvBuilderMockRecorder struct { + mock *MockEnvBuilder +} + +// NewMockEnvBuilder creates a new mock instance. +func NewMockEnvBuilder(ctrl *gomock.Controller) *MockEnvBuilder { + mock := &MockEnvBuilder{ctrl: ctrl} + mock.recorder = &MockEnvBuilderMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockEnvBuilder) EXPECT() *MockEnvBuilderMockRecorder { + return m.recorder +} + +// ISGOMOCK indicates that this struct is a gomock mock. +func (m *MockEnvBuilder) ISGOMOCK() struct{} { + return struct{}{} +} + +// Build mocks base method. +func (m *MockEnvBuilder) Build(envVars ...env.EnvVar) []v1.EnvVar { + m.ctrl.T.Helper() + varargs := []any{} + for _, a := range envVars { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Build", varargs...) + ret0, _ := ret[0].([]v1.EnvVar) + return ret0 +} + +// Build indicates an expected call of Build. +func (mr *MockEnvBuilderMockRecorder) Build(envVars ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Build", reflect.TypeOf((*MockEnvBuilder)(nil).Build), envVars...) +} diff --git a/pkg/k8s/object/builders/agent/daemonset/hash_mock_test.go b/pkg/k8s/object/builders/agent/daemonset/hash_mock_test.go new file mode 100644 index 00000000..499adeab --- /dev/null +++ b/pkg/k8s/object/builders/agent/daemonset/hash_mock_test.go @@ -0,0 +1,68 @@ +// /* +// (c) Copyright IBM Corp. 2024 +// (c) Copyright Instana Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// */ +// + +// Code generated by MockGen. DO NOT EDIT. +package daemonset + +import ( + reflect "reflect" + + gomock "go.uber.org/mock/gomock" +) + +// MockJsonHasher is a mock of JsonHasher interface. +type MockJsonHasher struct { + ctrl *gomock.Controller + recorder *MockJsonHasherMockRecorder +} + +// MockJsonHasherMockRecorder is the mock recorder for MockJsonHasher. +type MockJsonHasherMockRecorder struct { + mock *MockJsonHasher +} + +// NewMockJsonHasher creates a new mock instance. +func NewMockJsonHasher(ctrl *gomock.Controller) *MockJsonHasher { + mock := &MockJsonHasher{ctrl: ctrl} + mock.recorder = &MockJsonHasherMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockJsonHasher) EXPECT() *MockJsonHasherMockRecorder { + return m.recorder +} + +// ISGOMOCK indicates that this struct is a gomock mock. +func (m *MockJsonHasher) ISGOMOCK() struct{} { + return struct{}{} +} + +// HashJsonOrDie mocks base method. +func (m *MockJsonHasher) HashJsonOrDie(obj any) string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "HashJsonOrDie", obj) + ret0, _ := ret[0].(string) + return ret0 +} + +// HashJsonOrDie indicates an expected call of HashJsonOrDie. +func (mr *MockJsonHasherMockRecorder) HashJsonOrDie(obj any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HashJsonOrDie", reflect.TypeOf((*MockJsonHasher)(nil).HashJsonOrDie), obj) +} diff --git a/pkg/k8s/object/builders/agent/daemonset/pod_selector_mock_test.go b/pkg/k8s/object/builders/agent/daemonset/pod_selector_mock_test.go new file mode 100644 index 00000000..18993b03 --- /dev/null +++ b/pkg/k8s/object/builders/agent/daemonset/pod_selector_mock_test.go @@ -0,0 +1,82 @@ +// /* +// (c) Copyright IBM Corp. 2024 +// (c) Copyright Instana Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// */ +// + +// Code generated by MockGen. DO NOT EDIT. +package daemonset + +import ( + reflect "reflect" + + gomock "go.uber.org/mock/gomock" +) + +// MockPodSelectorLabelGenerator is a mock of PodSelectorLabelGenerator interface. +type MockPodSelectorLabelGenerator struct { + ctrl *gomock.Controller + recorder *MockPodSelectorLabelGeneratorMockRecorder +} + +// MockPodSelectorLabelGeneratorMockRecorder is the mock recorder for MockPodSelectorLabelGenerator. +type MockPodSelectorLabelGeneratorMockRecorder struct { + mock *MockPodSelectorLabelGenerator +} + +// NewMockPodSelectorLabelGenerator creates a new mock instance. +func NewMockPodSelectorLabelGenerator(ctrl *gomock.Controller) *MockPodSelectorLabelGenerator { + mock := &MockPodSelectorLabelGenerator{ctrl: ctrl} + mock.recorder = &MockPodSelectorLabelGeneratorMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockPodSelectorLabelGenerator) EXPECT() *MockPodSelectorLabelGeneratorMockRecorder { + return m.recorder +} + +// ISGOMOCK indicates that this struct is a gomock mock. +func (m *MockPodSelectorLabelGenerator) ISGOMOCK() struct{} { + return struct{}{} +} + +// GetPodLabels mocks base method. +func (m *MockPodSelectorLabelGenerator) GetPodLabels(userLabels map[string]string) map[string]string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetPodLabels", userLabels) + ret0, _ := ret[0].(map[string]string) + return ret0 +} + +// GetPodLabels indicates an expected call of GetPodLabels. +func (mr *MockPodSelectorLabelGeneratorMockRecorder) GetPodLabels(userLabels any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPodLabels", reflect.TypeOf((*MockPodSelectorLabelGenerator)(nil).GetPodLabels), userLabels) +} + +// GetPodSelectorLabels mocks base method. +func (m *MockPodSelectorLabelGenerator) GetPodSelectorLabels() map[string]string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetPodSelectorLabels") + ret0, _ := ret[0].(map[string]string) + return ret0 +} + +// GetPodSelectorLabels indicates an expected call of GetPodSelectorLabels. +func (mr *MockPodSelectorLabelGeneratorMockRecorder) GetPodSelectorLabels() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPodSelectorLabels", reflect.TypeOf((*MockPodSelectorLabelGenerator)(nil).GetPodSelectorLabels)) +} diff --git a/pkg/k8s/object/builders/agent/daemonset/ports_mock_test.go b/pkg/k8s/object/builders/agent/daemonset/ports_mock_test.go new file mode 100644 index 00000000..d8ecdfeb --- /dev/null +++ b/pkg/k8s/object/builders/agent/daemonset/ports_mock_test.go @@ -0,0 +1,163 @@ +// /* +// (c) Copyright IBM Corp. 2024 +// (c) Copyright Instana Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// */ +// + +// Code generated by MockGen. DO NOT EDIT. +package daemonset + +import ( + reflect "reflect" + + helpers "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/helpers" + ports "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/ports" + gomock "go.uber.org/mock/gomock" + v1 "k8s.io/api/core/v1" +) + +// MockPort is a mock of Port interface. +type MockPort struct { + ctrl *gomock.Controller + recorder *MockPortMockRecorder +} + +// MockPortMockRecorder is the mock recorder for MockPort. +type MockPortMockRecorder struct { + mock *MockPort +} + +// NewMockPort creates a new mock instance. +func NewMockPort(ctrl *gomock.Controller) *MockPort { + mock := &MockPort{ctrl: ctrl} + mock.recorder = &MockPortMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockPort) EXPECT() *MockPortMockRecorder { + return m.recorder +} + +// ISGOMOCK indicates that this struct is a gomock mock. +func (m *MockPort) ISGOMOCK() struct{} { + return struct{}{} +} + +// String mocks base method. +func (m *MockPort) String() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "String") + ret0, _ := ret[0].(string) + return ret0 +} + +// String indicates an expected call of String. +func (mr *MockPortMockRecorder) String() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "String", reflect.TypeOf((*MockPort)(nil).String)) +} + +// isEnabled mocks base method. +func (m *MockPort) isEnabled(openTelemetrySettings helpers.OpenTelemetrySettings) bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "isEnabled", openTelemetrySettings) + ret0, _ := ret[0].(bool) + return ret0 +} + +// isEnabled indicates an expected call of isEnabled. +func (mr *MockPortMockRecorder) isEnabled(openTelemetrySettings any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "isEnabled", reflect.TypeOf((*MockPort)(nil).isEnabled), openTelemetrySettings) +} + +// portNumber mocks base method. +func (m *MockPort) portNumber() int32 { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "portNumber") + ret0, _ := ret[0].(int32) + return ret0 +} + +// portNumber indicates an expected call of portNumber. +func (mr *MockPortMockRecorder) portNumber() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "portNumber", reflect.TypeOf((*MockPort)(nil).portNumber)) +} + +// MockPortsBuilder is a mock of PortsBuilder interface. +type MockPortsBuilder struct { + ctrl *gomock.Controller + recorder *MockPortsBuilderMockRecorder +} + +// MockPortsBuilderMockRecorder is the mock recorder for MockPortsBuilder. +type MockPortsBuilderMockRecorder struct { + mock *MockPortsBuilder +} + +// NewMockPortsBuilder creates a new mock instance. +func NewMockPortsBuilder(ctrl *gomock.Controller) *MockPortsBuilder { + mock := &MockPortsBuilder{ctrl: ctrl} + mock.recorder = &MockPortsBuilderMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockPortsBuilder) EXPECT() *MockPortsBuilderMockRecorder { + return m.recorder +} + +// ISGOMOCK indicates that this struct is a gomock mock. +func (m *MockPortsBuilder) ISGOMOCK() struct{} { + return struct{}{} +} + +// GetContainerPorts mocks base method. +func (m *MockPortsBuilder) GetContainerPorts(ports ...ports.Port) []v1.ContainerPort { + m.ctrl.T.Helper() + varargs := []any{} + for _, a := range ports { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "GetContainerPorts", varargs...) + ret0, _ := ret[0].([]v1.ContainerPort) + return ret0 +} + +// GetContainerPorts indicates an expected call of GetContainerPorts. +func (mr *MockPortsBuilderMockRecorder) GetContainerPorts(ports ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetContainerPorts", reflect.TypeOf((*MockPortsBuilder)(nil).GetContainerPorts), ports...) +} + +// GetServicePorts mocks base method. +func (m *MockPortsBuilder) GetServicePorts(ports ...ports.Port) []v1.ServicePort { + m.ctrl.T.Helper() + varargs := []any{} + for _, a := range ports { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "GetServicePorts", varargs...) + ret0, _ := ret[0].([]v1.ServicePort) + return ret0 +} + +// GetServicePorts indicates an expected call of GetServicePorts. +func (mr *MockPortsBuilderMockRecorder) GetServicePorts(ports ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetServicePorts", reflect.TypeOf((*MockPortsBuilder)(nil).GetServicePorts), ports...) +} diff --git a/pkg/k8s/object/builders/agent/daemonset/status_mock_test.go b/pkg/k8s/object/builders/agent/daemonset/status_mock_test.go new file mode 100644 index 00000000..6bb0ecb4 --- /dev/null +++ b/pkg/k8s/object/builders/agent/daemonset/status_mock_test.go @@ -0,0 +1,119 @@ +// /* +// (c) Copyright IBM Corp. 2024 +// (c) Copyright Instana Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// */ +// + +// Code generated by MockGen. DO NOT EDIT. +package daemonset + +import ( + context "context" + reflect "reflect" + + v1 "github.com/instana/instana-agent-operator/api/v1" + gomock "go.uber.org/mock/gomock" + client "sigs.k8s.io/controller-runtime/pkg/client" +) + +// MockAgentStatusManager is a mock of AgentStatusManager interface. +type MockAgentStatusManager struct { + ctrl *gomock.Controller + recorder *MockAgentStatusManagerMockRecorder +} + +// MockAgentStatusManagerMockRecorder is the mock recorder for MockAgentStatusManager. +type MockAgentStatusManagerMockRecorder struct { + mock *MockAgentStatusManager +} + +// NewMockAgentStatusManager creates a new mock instance. +func NewMockAgentStatusManager(ctrl *gomock.Controller) *MockAgentStatusManager { + mock := &MockAgentStatusManager{ctrl: ctrl} + mock.recorder = &MockAgentStatusManagerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockAgentStatusManager) EXPECT() *MockAgentStatusManagerMockRecorder { + return m.recorder +} + +// ISGOMOCK indicates that this struct is a gomock mock. +func (m *MockAgentStatusManager) ISGOMOCK() struct{} { + return struct{}{} +} + +// AddAgentDaemonset mocks base method. +func (m *MockAgentStatusManager) AddAgentDaemonset(agentDaemonset client.ObjectKey) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "AddAgentDaemonset", agentDaemonset) +} + +// AddAgentDaemonset indicates an expected call of AddAgentDaemonset. +func (mr *MockAgentStatusManagerMockRecorder) AddAgentDaemonset(agentDaemonset any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddAgentDaemonset", reflect.TypeOf((*MockAgentStatusManager)(nil).AddAgentDaemonset), agentDaemonset) +} + +// SetAgentConfigMap mocks base method. +func (m *MockAgentStatusManager) SetAgentConfigMap(agentConfigMap client.ObjectKey) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetAgentConfigMap", agentConfigMap) +} + +// SetAgentConfigMap indicates an expected call of SetAgentConfigMap. +func (mr *MockAgentStatusManagerMockRecorder) SetAgentConfigMap(agentConfigMap any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetAgentConfigMap", reflect.TypeOf((*MockAgentStatusManager)(nil).SetAgentConfigMap), agentConfigMap) +} + +// SetAgentOld mocks base method. +func (m *MockAgentStatusManager) SetAgentOld(agent *v1.InstanaAgent) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetAgentOld", agent) +} + +// SetAgentOld indicates an expected call of SetAgentOld. +func (mr *MockAgentStatusManagerMockRecorder) SetAgentOld(agent any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetAgentOld", reflect.TypeOf((*MockAgentStatusManager)(nil).SetAgentOld), agent) +} + +// SetK8sSensorDeployment mocks base method. +func (m *MockAgentStatusManager) SetK8sSensorDeployment(k8sSensorDeployment client.ObjectKey) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetK8sSensorDeployment", k8sSensorDeployment) +} + +// SetK8sSensorDeployment indicates an expected call of SetK8sSensorDeployment. +func (mr *MockAgentStatusManagerMockRecorder) SetK8sSensorDeployment(k8sSensorDeployment any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetK8sSensorDeployment", reflect.TypeOf((*MockAgentStatusManager)(nil).SetK8sSensorDeployment), k8sSensorDeployment) +} + +// UpdateAgentStatus mocks base method. +func (m *MockAgentStatusManager) UpdateAgentStatus(ctx context.Context, reconcileErr error) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateAgentStatus", ctx, reconcileErr) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateAgentStatus indicates an expected call of UpdateAgentStatus. +func (mr *MockAgentStatusManagerMockRecorder) UpdateAgentStatus(ctx, reconcileErr any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateAgentStatus", reflect.TypeOf((*MockAgentStatusManager)(nil).UpdateAgentStatus), ctx, reconcileErr) +} diff --git a/pkg/k8s/object/builders/agent/daemonset/volume_builder_mock_test.go b/pkg/k8s/object/builders/agent/daemonset/volume_builder_mock_test.go new file mode 100644 index 00000000..b20015b4 --- /dev/null +++ b/pkg/k8s/object/builders/agent/daemonset/volume_builder_mock_test.go @@ -0,0 +1,75 @@ +// /* +// (c) Copyright IBM Corp. 2024 +// (c) Copyright Instana Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// */ +// + +// Code generated by MockGen. DO NOT EDIT. +package daemonset + +import ( + reflect "reflect" + + volume "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/volume" + gomock "go.uber.org/mock/gomock" + v1 "k8s.io/api/core/v1" +) + +// MockVolumeBuilder is a mock of VolumeBuilder interface. +type MockVolumeBuilder struct { + ctrl *gomock.Controller + recorder *MockVolumeBuilderMockRecorder +} + +// MockVolumeBuilderMockRecorder is the mock recorder for MockVolumeBuilder. +type MockVolumeBuilderMockRecorder struct { + mock *MockVolumeBuilder +} + +// NewMockVolumeBuilder creates a new mock instance. +func NewMockVolumeBuilder(ctrl *gomock.Controller) *MockVolumeBuilder { + mock := &MockVolumeBuilder{ctrl: ctrl} + mock.recorder = &MockVolumeBuilderMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockVolumeBuilder) EXPECT() *MockVolumeBuilderMockRecorder { + return m.recorder +} + +// ISGOMOCK indicates that this struct is a gomock mock. +func (m *MockVolumeBuilder) ISGOMOCK() struct{} { + return struct{}{} +} + +// Build mocks base method. +func (m *MockVolumeBuilder) Build(volumes ...volume.Volume) ([]v1.Volume, []v1.VolumeMount) { + m.ctrl.T.Helper() + varargs := []any{} + for _, a := range volumes { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Build", varargs...) + ret0, _ := ret[0].([]v1.Volume) + ret1, _ := ret[1].([]v1.VolumeMount) + return ret0, ret1 +} + +// Build indicates an expected call of Build. +func (mr *MockVolumeBuilderMockRecorder) Build(volumes ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Build", reflect.TypeOf((*MockVolumeBuilder)(nil).Build), volumes...) +} diff --git a/pkg/k8s/object/builders/agent/headless-service/headless-service.go b/pkg/k8s/object/builders/agent/headless-service/headless-service.go new file mode 100644 index 00000000..a43d58b1 --- /dev/null +++ b/pkg/k8s/object/builders/agent/headless-service/headless-service.go @@ -0,0 +1,70 @@ +package headless_service + +import ( + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + instanav1 "github.com/instana/instana-agent-operator/api/v1" + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/builder" + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/constants" + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/helpers" + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/ports" + "github.com/instana/instana-agent-operator/pkg/k8s/object/transformations" + "github.com/instana/instana-agent-operator/pkg/optional" +) + +const ( + componentName = constants.ComponentInstanaAgent +) + +type headlessServiceBuilder struct { + *instanav1.InstanaAgent + + helpers.Helpers + transformations.PodSelectorLabelGenerator + ports.PortsBuilder +} + +func (h *headlessServiceBuilder) Build() builder.OptionalObject { + return optional.Of[client.Object]( + &corev1.Service{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Service", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: h.HeadlessServiceName(), + Namespace: h.Namespace, + }, + Spec: corev1.ServiceSpec{ + ClusterIP: corev1.ClusterIPNone, + Selector: h.GetPodSelectorLabels(), + Ports: h.GetServicePorts( + ports.AgentAPIsPort, + ports.OpenTelemetryLegacyPort, + ports.OpenTelemetryGRPCPort, + ports.OpenTelemetryHTTPPort, + ), + }, + }, + ) +} + +func (h *headlessServiceBuilder) ComponentName() string { + return componentName +} + +func (h *headlessServiceBuilder) IsNamespaced() bool { + return true +} + +func NewHeadlessServiceBuilder(agent *instanav1.InstanaAgent) builder.ObjectBuilder { + return &headlessServiceBuilder{ + InstanaAgent: agent, + + Helpers: helpers.NewHelpers(agent), + PodSelectorLabelGenerator: transformations.PodSelectorLabels(agent, componentName), + PortsBuilder: ports.NewPortsBuilder(agent), + } +} diff --git a/pkg/k8s/object/builders/agent/headless-service/headless-service_test.go b/pkg/k8s/object/builders/agent/headless-service/headless-service_test.go new file mode 100644 index 00000000..79a4f37f --- /dev/null +++ b/pkg/k8s/object/builders/agent/headless-service/headless-service_test.go @@ -0,0 +1,120 @@ +/* +(c) Copyright IBM Corp. 2024 +(c) Copyright Instana Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package headless_service + +import ( + "testing" + + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + instanav1 "github.com/instana/instana-agent-operator/api/v1" + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/constants" + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/helpers" + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/ports" + "github.com/instana/instana-agent-operator/pkg/k8s/object/transformations" + "github.com/instana/instana-agent-operator/pkg/optional" +) + +func TestHeadlessServiceBuilder_IsNamespaced(t *testing.T) { + assertions := require.New(t) + assertions.True(NewHeadlessServiceBuilder(nil).IsNamespaced()) +} + +func TestHeadlessServiceBuilder_ComponentName(t *testing.T) { + assertions := require.New(t) + assertions.Equal(constants.ComponentInstanaAgent, NewHeadlessServiceBuilder(nil).ComponentName()) +} + +func TestHeadlessServiceBuilder_Build(t *testing.T) { + assertions := require.New(t) + ctrl := gomock.NewController(t) + + agent := &instanav1.InstanaAgent{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "agent-namespace", + }, + } + + hlprs := NewMockHelpers(ctrl) + hlprs.EXPECT().HeadlessServiceName().Return("headless-service-name") + + podSelectorLabelGenerator := NewMockPodSelectorLabelGenerator(ctrl) + podSelectorLabelGenerator.EXPECT().GetPodSelectorLabels().Return(map[string]string{"foo": "bar", "hello": "world"}) + + portsBuilder := NewMockPortsBuilder(ctrl) + portsBuilder.EXPECT().GetServicePorts( + ports.AgentAPIsPort, + ports.OpenTelemetryLegacyPort, + ports.OpenTelemetryGRPCPort, + ports.OpenTelemetryHTTPPort, + ). + Return( + []corev1.ServicePort{ + { + Name: "headless-service-port", + }, + }, + ) + + expected := optional.Of[client.Object]( + &corev1.Service{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Service", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "headless-service-name", + Namespace: "agent-namespace", + }, + Spec: corev1.ServiceSpec{ + ClusterIP: corev1.ClusterIPNone, + Selector: map[string]string{"foo": "bar", "hello": "world"}, + Ports: []corev1.ServicePort{{Name: "headless-service-port"}}, + }, + }, + ) + + actual := (&headlessServiceBuilder{ + InstanaAgent: agent, + Helpers: hlprs, + PodSelectorLabelGenerator: podSelectorLabelGenerator, + PortsBuilder: portsBuilder, + }).Build() + + assertions.Equal(expected, actual) +} + +func TestNewHeadlessServiceBuilder(t *testing.T) { + assertions := require.New(t) + + agent := &instanav1.InstanaAgent{ObjectMeta: metav1.ObjectMeta{Name: "some-agent"}} + + expected := &headlessServiceBuilder{ + InstanaAgent: agent, + Helpers: helpers.NewHelpers(agent), + PodSelectorLabelGenerator: transformations.PodSelectorLabels(agent, constants.ComponentInstanaAgent), + PortsBuilder: ports.NewPortsBuilder(agent), + } + + actual := NewHeadlessServiceBuilder(agent) + + assertions.Equal(expected, actual) +} diff --git a/pkg/k8s/object/builders/agent/headless-service/helpers_mock_test.go b/pkg/k8s/object/builders/agent/headless-service/helpers_mock_test.go new file mode 100644 index 00000000..ea21fdba --- /dev/null +++ b/pkg/k8s/object/builders/agent/headless-service/helpers_mock_test.go @@ -0,0 +1,181 @@ +// /* +// (c) Copyright IBM Corp. 2024 +// (c) Copyright Instana Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// */ +// + +// Code generated by MockGen. DO NOT EDIT. +package headless_service + +import ( + reflect "reflect" + + gomock "go.uber.org/mock/gomock" + v1 "k8s.io/api/core/v1" +) + +// MockHelpers is a mock of Helpers interface. +type MockHelpers struct { + ctrl *gomock.Controller + recorder *MockHelpersMockRecorder +} + +// MockHelpersMockRecorder is the mock recorder for MockHelpers. +type MockHelpersMockRecorder struct { + mock *MockHelpers +} + +// NewMockHelpers creates a new mock instance. +func NewMockHelpers(ctrl *gomock.Controller) *MockHelpers { + mock := &MockHelpers{ctrl: ctrl} + mock.recorder = &MockHelpersMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockHelpers) EXPECT() *MockHelpersMockRecorder { + return m.recorder +} + +// ISGOMOCK indicates that this struct is a gomock mock. +func (m *MockHelpers) ISGOMOCK() struct{} { + return struct{}{} +} + +// ContainersSecretName mocks base method. +func (m *MockHelpers) ContainersSecretName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ContainersSecretName") + ret0, _ := ret[0].(string) + return ret0 +} + +// ContainersSecretName indicates an expected call of ContainersSecretName. +func (mr *MockHelpersMockRecorder) ContainersSecretName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ContainersSecretName", reflect.TypeOf((*MockHelpers)(nil).ContainersSecretName)) +} + +// HeadlessServiceName mocks base method. +func (m *MockHelpers) HeadlessServiceName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "HeadlessServiceName") + ret0, _ := ret[0].(string) + return ret0 +} + +// HeadlessServiceName indicates an expected call of HeadlessServiceName. +func (mr *MockHelpersMockRecorder) HeadlessServiceName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HeadlessServiceName", reflect.TypeOf((*MockHelpers)(nil).HeadlessServiceName)) +} + +// ImagePullSecrets mocks base method. +func (m *MockHelpers) ImagePullSecrets() []v1.LocalObjectReference { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ImagePullSecrets") + ret0, _ := ret[0].([]v1.LocalObjectReference) + return ret0 +} + +// ImagePullSecrets indicates an expected call of ImagePullSecrets. +func (mr *MockHelpersMockRecorder) ImagePullSecrets() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ImagePullSecrets", reflect.TypeOf((*MockHelpers)(nil).ImagePullSecrets)) +} + +// K8sSensorResourcesName mocks base method. +func (m *MockHelpers) K8sSensorResourcesName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "K8sSensorResourcesName") + ret0, _ := ret[0].(string) + return ret0 +} + +// K8sSensorResourcesName indicates an expected call of K8sSensorResourcesName. +func (mr *MockHelpersMockRecorder) K8sSensorResourcesName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "K8sSensorResourcesName", reflect.TypeOf((*MockHelpers)(nil).K8sSensorResourcesName)) +} + +// KeysSecretName mocks base method. +func (m *MockHelpers) KeysSecretName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "KeysSecretName") + ret0, _ := ret[0].(string) + return ret0 +} + +// KeysSecretName indicates an expected call of KeysSecretName. +func (mr *MockHelpersMockRecorder) KeysSecretName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "KeysSecretName", reflect.TypeOf((*MockHelpers)(nil).KeysSecretName)) +} + +// ServiceAccountName mocks base method. +func (m *MockHelpers) ServiceAccountName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ServiceAccountName") + ret0, _ := ret[0].(string) + return ret0 +} + +// ServiceAccountName indicates an expected call of ServiceAccountName. +func (mr *MockHelpersMockRecorder) ServiceAccountName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ServiceAccountName", reflect.TypeOf((*MockHelpers)(nil).ServiceAccountName)) +} + +// TLSIsEnabled mocks base method. +func (m *MockHelpers) TLSIsEnabled() bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "TLSIsEnabled") + ret0, _ := ret[0].(bool) + return ret0 +} + +// TLSIsEnabled indicates an expected call of TLSIsEnabled. +func (mr *MockHelpersMockRecorder) TLSIsEnabled() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TLSIsEnabled", reflect.TypeOf((*MockHelpers)(nil).TLSIsEnabled)) +} + +// TLSSecretName mocks base method. +func (m *MockHelpers) TLSSecretName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "TLSSecretName") + ret0, _ := ret[0].(string) + return ret0 +} + +// TLSSecretName indicates an expected call of TLSSecretName. +func (mr *MockHelpersMockRecorder) TLSSecretName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TLSSecretName", reflect.TypeOf((*MockHelpers)(nil).TLSSecretName)) +} + +// UseContainersSecret mocks base method. +func (m *MockHelpers) UseContainersSecret() bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UseContainersSecret") + ret0, _ := ret[0].(bool) + return ret0 +} + +// UseContainersSecret indicates an expected call of UseContainersSecret. +func (mr *MockHelpersMockRecorder) UseContainersSecret() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UseContainersSecret", reflect.TypeOf((*MockHelpers)(nil).UseContainersSecret)) +} diff --git a/pkg/k8s/object/builders/agent/headless-service/pod_selector_mock_test.go b/pkg/k8s/object/builders/agent/headless-service/pod_selector_mock_test.go new file mode 100644 index 00000000..ca0fa9de --- /dev/null +++ b/pkg/k8s/object/builders/agent/headless-service/pod_selector_mock_test.go @@ -0,0 +1,82 @@ +// /* +// (c) Copyright IBM Corp. 2024 +// (c) Copyright Instana Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// */ +// + +// Code generated by MockGen. DO NOT EDIT. +package headless_service + +import ( + reflect "reflect" + + gomock "go.uber.org/mock/gomock" +) + +// MockPodSelectorLabelGenerator is a mock of PodSelectorLabelGenerator interface. +type MockPodSelectorLabelGenerator struct { + ctrl *gomock.Controller + recorder *MockPodSelectorLabelGeneratorMockRecorder +} + +// MockPodSelectorLabelGeneratorMockRecorder is the mock recorder for MockPodSelectorLabelGenerator. +type MockPodSelectorLabelGeneratorMockRecorder struct { + mock *MockPodSelectorLabelGenerator +} + +// NewMockPodSelectorLabelGenerator creates a new mock instance. +func NewMockPodSelectorLabelGenerator(ctrl *gomock.Controller) *MockPodSelectorLabelGenerator { + mock := &MockPodSelectorLabelGenerator{ctrl: ctrl} + mock.recorder = &MockPodSelectorLabelGeneratorMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockPodSelectorLabelGenerator) EXPECT() *MockPodSelectorLabelGeneratorMockRecorder { + return m.recorder +} + +// ISGOMOCK indicates that this struct is a gomock mock. +func (m *MockPodSelectorLabelGenerator) ISGOMOCK() struct{} { + return struct{}{} +} + +// GetPodLabels mocks base method. +func (m *MockPodSelectorLabelGenerator) GetPodLabels(userLabels map[string]string) map[string]string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetPodLabels", userLabels) + ret0, _ := ret[0].(map[string]string) + return ret0 +} + +// GetPodLabels indicates an expected call of GetPodLabels. +func (mr *MockPodSelectorLabelGeneratorMockRecorder) GetPodLabels(userLabels any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPodLabels", reflect.TypeOf((*MockPodSelectorLabelGenerator)(nil).GetPodLabels), userLabels) +} + +// GetPodSelectorLabels mocks base method. +func (m *MockPodSelectorLabelGenerator) GetPodSelectorLabels() map[string]string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetPodSelectorLabels") + ret0, _ := ret[0].(map[string]string) + return ret0 +} + +// GetPodSelectorLabels indicates an expected call of GetPodSelectorLabels. +func (mr *MockPodSelectorLabelGeneratorMockRecorder) GetPodSelectorLabels() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPodSelectorLabels", reflect.TypeOf((*MockPodSelectorLabelGenerator)(nil).GetPodSelectorLabels)) +} diff --git a/pkg/k8s/object/builders/agent/headless-service/ports_mock_test.go b/pkg/k8s/object/builders/agent/headless-service/ports_mock_test.go new file mode 100644 index 00000000..d379dac9 --- /dev/null +++ b/pkg/k8s/object/builders/agent/headless-service/ports_mock_test.go @@ -0,0 +1,163 @@ +// /* +// (c) Copyright IBM Corp. 2024 +// (c) Copyright Instana Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// */ +// + +// Code generated by MockGen. DO NOT EDIT. +package headless_service + +import ( + reflect "reflect" + + helpers "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/helpers" + ports "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/ports" + gomock "go.uber.org/mock/gomock" + v1 "k8s.io/api/core/v1" +) + +// MockPort is a mock of Port interface. +type MockPort struct { + ctrl *gomock.Controller + recorder *MockPortMockRecorder +} + +// MockPortMockRecorder is the mock recorder for MockPort. +type MockPortMockRecorder struct { + mock *MockPort +} + +// NewMockPort creates a new mock instance. +func NewMockPort(ctrl *gomock.Controller) *MockPort { + mock := &MockPort{ctrl: ctrl} + mock.recorder = &MockPortMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockPort) EXPECT() *MockPortMockRecorder { + return m.recorder +} + +// ISGOMOCK indicates that this struct is a gomock mock. +func (m *MockPort) ISGOMOCK() struct{} { + return struct{}{} +} + +// String mocks base method. +func (m *MockPort) String() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "String") + ret0, _ := ret[0].(string) + return ret0 +} + +// String indicates an expected call of String. +func (mr *MockPortMockRecorder) String() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "String", reflect.TypeOf((*MockPort)(nil).String)) +} + +// isEnabled mocks base method. +func (m *MockPort) isEnabled(openTelemetrySettings helpers.OpenTelemetrySettings) bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "isEnabled", openTelemetrySettings) + ret0, _ := ret[0].(bool) + return ret0 +} + +// isEnabled indicates an expected call of isEnabled. +func (mr *MockPortMockRecorder) isEnabled(openTelemetrySettings any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "isEnabled", reflect.TypeOf((*MockPort)(nil).isEnabled), openTelemetrySettings) +} + +// portNumber mocks base method. +func (m *MockPort) portNumber() int32 { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "portNumber") + ret0, _ := ret[0].(int32) + return ret0 +} + +// portNumber indicates an expected call of portNumber. +func (mr *MockPortMockRecorder) portNumber() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "portNumber", reflect.TypeOf((*MockPort)(nil).portNumber)) +} + +// MockPortsBuilder is a mock of PortsBuilder interface. +type MockPortsBuilder struct { + ctrl *gomock.Controller + recorder *MockPortsBuilderMockRecorder +} + +// MockPortsBuilderMockRecorder is the mock recorder for MockPortsBuilder. +type MockPortsBuilderMockRecorder struct { + mock *MockPortsBuilder +} + +// NewMockPortsBuilder creates a new mock instance. +func NewMockPortsBuilder(ctrl *gomock.Controller) *MockPortsBuilder { + mock := &MockPortsBuilder{ctrl: ctrl} + mock.recorder = &MockPortsBuilderMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockPortsBuilder) EXPECT() *MockPortsBuilderMockRecorder { + return m.recorder +} + +// ISGOMOCK indicates that this struct is a gomock mock. +func (m *MockPortsBuilder) ISGOMOCK() struct{} { + return struct{}{} +} + +// GetContainerPorts mocks base method. +func (m *MockPortsBuilder) GetContainerPorts(ports ...ports.Port) []v1.ContainerPort { + m.ctrl.T.Helper() + varargs := []any{} + for _, a := range ports { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "GetContainerPorts", varargs...) + ret0, _ := ret[0].([]v1.ContainerPort) + return ret0 +} + +// GetContainerPorts indicates an expected call of GetContainerPorts. +func (mr *MockPortsBuilderMockRecorder) GetContainerPorts(ports ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetContainerPorts", reflect.TypeOf((*MockPortsBuilder)(nil).GetContainerPorts), ports...) +} + +// GetServicePorts mocks base method. +func (m *MockPortsBuilder) GetServicePorts(ports ...ports.Port) []v1.ServicePort { + m.ctrl.T.Helper() + varargs := []any{} + for _, a := range ports { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "GetServicePorts", varargs...) + ret0, _ := ret[0].([]v1.ServicePort) + return ret0 +} + +// GetServicePorts indicates an expected call of GetServicePorts. +func (mr *MockPortsBuilderMockRecorder) GetServicePorts(ports ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetServicePorts", reflect.TypeOf((*MockPortsBuilder)(nil).GetServicePorts), ports...) +} diff --git a/pkg/k8s/object/builders/agent/secrets/containers-instana-io-secret/docker_config_json.go b/pkg/k8s/object/builders/agent/secrets/containers-instana-io-secret/docker_config_json.go new file mode 100644 index 00000000..dd9b3eb2 --- /dev/null +++ b/pkg/k8s/object/builders/agent/secrets/containers-instana-io-secret/docker_config_json.go @@ -0,0 +1,15 @@ +package containers_instana_io_secret + +type DockerConfigAuth struct { + Auth []byte `json:"auth"` +} + +type DockerConfigJson struct { + Auths map[string]DockerConfigAuth `json:"auths"` +} + +// Defining this so mock-gen will work +type dockerConfigMarshaler interface { + MarshalOrDie(obj *DockerConfigJson) []byte + UnMarshalOrDie(raw []byte) *DockerConfigJson +} diff --git a/pkg/k8s/object/builders/agent/secrets/containers-instana-io-secret/docker_config_json_mock_test.go b/pkg/k8s/object/builders/agent/secrets/containers-instana-io-secret/docker_config_json_mock_test.go new file mode 100644 index 00000000..8d7e68d7 --- /dev/null +++ b/pkg/k8s/object/builders/agent/secrets/containers-instana-io-secret/docker_config_json_mock_test.go @@ -0,0 +1,82 @@ +// /* +// (c) Copyright IBM Corp. 2024 +// (c) Copyright Instana Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// */ +// + +// Code generated by MockGen. DO NOT EDIT. +package containers_instana_io_secret + +import ( + reflect "reflect" + + gomock "go.uber.org/mock/gomock" +) + +// MockdockerConfigMarshaler is a mock of dockerConfigMarshaler interface. +type MockdockerConfigMarshaler struct { + ctrl *gomock.Controller + recorder *MockdockerConfigMarshalerMockRecorder +} + +// MockdockerConfigMarshalerMockRecorder is the mock recorder for MockdockerConfigMarshaler. +type MockdockerConfigMarshalerMockRecorder struct { + mock *MockdockerConfigMarshaler +} + +// NewMockdockerConfigMarshaler creates a new mock instance. +func NewMockdockerConfigMarshaler(ctrl *gomock.Controller) *MockdockerConfigMarshaler { + mock := &MockdockerConfigMarshaler{ctrl: ctrl} + mock.recorder = &MockdockerConfigMarshalerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockdockerConfigMarshaler) EXPECT() *MockdockerConfigMarshalerMockRecorder { + return m.recorder +} + +// ISGOMOCK indicates that this struct is a gomock mock. +func (m *MockdockerConfigMarshaler) ISGOMOCK() struct{} { + return struct{}{} +} + +// MarshalOrDie mocks base method. +func (m *MockdockerConfigMarshaler) MarshalOrDie(obj *DockerConfigJson) []byte { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MarshalOrDie", obj) + ret0, _ := ret[0].([]byte) + return ret0 +} + +// MarshalOrDie indicates an expected call of MarshalOrDie. +func (mr *MockdockerConfigMarshalerMockRecorder) MarshalOrDie(obj any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MarshalOrDie", reflect.TypeOf((*MockdockerConfigMarshaler)(nil).MarshalOrDie), obj) +} + +// UnMarshalOrDie mocks base method. +func (m *MockdockerConfigMarshaler) UnMarshalOrDie(raw []byte) *DockerConfigJson { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UnMarshalOrDie", raw) + ret0, _ := ret[0].(*DockerConfigJson) + return ret0 +} + +// UnMarshalOrDie indicates an expected call of UnMarshalOrDie. +func (mr *MockdockerConfigMarshalerMockRecorder) UnMarshalOrDie(raw any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UnMarshalOrDie", reflect.TypeOf((*MockdockerConfigMarshaler)(nil).UnMarshalOrDie), raw) +} diff --git a/pkg/k8s/object/builders/agent/secrets/containers-instana-io-secret/helpers_mock_test.go b/pkg/k8s/object/builders/agent/secrets/containers-instana-io-secret/helpers_mock_test.go new file mode 100644 index 00000000..fd672f13 --- /dev/null +++ b/pkg/k8s/object/builders/agent/secrets/containers-instana-io-secret/helpers_mock_test.go @@ -0,0 +1,181 @@ +// /* +// (c) Copyright IBM Corp. 2024 +// (c) Copyright Instana Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// */ +// + +// Code generated by MockGen. DO NOT EDIT. +package containers_instana_io_secret + +import ( + reflect "reflect" + + gomock "go.uber.org/mock/gomock" + v1 "k8s.io/api/core/v1" +) + +// MockHelpers is a mock of Helpers interface. +type MockHelpers struct { + ctrl *gomock.Controller + recorder *MockHelpersMockRecorder +} + +// MockHelpersMockRecorder is the mock recorder for MockHelpers. +type MockHelpersMockRecorder struct { + mock *MockHelpers +} + +// NewMockHelpers creates a new mock instance. +func NewMockHelpers(ctrl *gomock.Controller) *MockHelpers { + mock := &MockHelpers{ctrl: ctrl} + mock.recorder = &MockHelpersMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockHelpers) EXPECT() *MockHelpersMockRecorder { + return m.recorder +} + +// ISGOMOCK indicates that this struct is a gomock mock. +func (m *MockHelpers) ISGOMOCK() struct{} { + return struct{}{} +} + +// ContainersSecretName mocks base method. +func (m *MockHelpers) ContainersSecretName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ContainersSecretName") + ret0, _ := ret[0].(string) + return ret0 +} + +// ContainersSecretName indicates an expected call of ContainersSecretName. +func (mr *MockHelpersMockRecorder) ContainersSecretName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ContainersSecretName", reflect.TypeOf((*MockHelpers)(nil).ContainersSecretName)) +} + +// HeadlessServiceName mocks base method. +func (m *MockHelpers) HeadlessServiceName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "HeadlessServiceName") + ret0, _ := ret[0].(string) + return ret0 +} + +// HeadlessServiceName indicates an expected call of HeadlessServiceName. +func (mr *MockHelpersMockRecorder) HeadlessServiceName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HeadlessServiceName", reflect.TypeOf((*MockHelpers)(nil).HeadlessServiceName)) +} + +// ImagePullSecrets mocks base method. +func (m *MockHelpers) ImagePullSecrets() []v1.LocalObjectReference { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ImagePullSecrets") + ret0, _ := ret[0].([]v1.LocalObjectReference) + return ret0 +} + +// ImagePullSecrets indicates an expected call of ImagePullSecrets. +func (mr *MockHelpersMockRecorder) ImagePullSecrets() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ImagePullSecrets", reflect.TypeOf((*MockHelpers)(nil).ImagePullSecrets)) +} + +// K8sSensorResourcesName mocks base method. +func (m *MockHelpers) K8sSensorResourcesName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "K8sSensorResourcesName") + ret0, _ := ret[0].(string) + return ret0 +} + +// K8sSensorResourcesName indicates an expected call of K8sSensorResourcesName. +func (mr *MockHelpersMockRecorder) K8sSensorResourcesName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "K8sSensorResourcesName", reflect.TypeOf((*MockHelpers)(nil).K8sSensorResourcesName)) +} + +// KeysSecretName mocks base method. +func (m *MockHelpers) KeysSecretName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "KeysSecretName") + ret0, _ := ret[0].(string) + return ret0 +} + +// KeysSecretName indicates an expected call of KeysSecretName. +func (mr *MockHelpersMockRecorder) KeysSecretName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "KeysSecretName", reflect.TypeOf((*MockHelpers)(nil).KeysSecretName)) +} + +// ServiceAccountName mocks base method. +func (m *MockHelpers) ServiceAccountName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ServiceAccountName") + ret0, _ := ret[0].(string) + return ret0 +} + +// ServiceAccountName indicates an expected call of ServiceAccountName. +func (mr *MockHelpersMockRecorder) ServiceAccountName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ServiceAccountName", reflect.TypeOf((*MockHelpers)(nil).ServiceAccountName)) +} + +// TLSIsEnabled mocks base method. +func (m *MockHelpers) TLSIsEnabled() bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "TLSIsEnabled") + ret0, _ := ret[0].(bool) + return ret0 +} + +// TLSIsEnabled indicates an expected call of TLSIsEnabled. +func (mr *MockHelpersMockRecorder) TLSIsEnabled() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TLSIsEnabled", reflect.TypeOf((*MockHelpers)(nil).TLSIsEnabled)) +} + +// TLSSecretName mocks base method. +func (m *MockHelpers) TLSSecretName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "TLSSecretName") + ret0, _ := ret[0].(string) + return ret0 +} + +// TLSSecretName indicates an expected call of TLSSecretName. +func (mr *MockHelpersMockRecorder) TLSSecretName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TLSSecretName", reflect.TypeOf((*MockHelpers)(nil).TLSSecretName)) +} + +// UseContainersSecret mocks base method. +func (m *MockHelpers) UseContainersSecret() bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UseContainersSecret") + ret0, _ := ret[0].(bool) + return ret0 +} + +// UseContainersSecret indicates an expected call of UseContainersSecret. +func (mr *MockHelpersMockRecorder) UseContainersSecret() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UseContainersSecret", reflect.TypeOf((*MockHelpers)(nil).UseContainersSecret)) +} diff --git a/pkg/k8s/object/builders/agent/secrets/containers-instana-io-secret/secret.go b/pkg/k8s/object/builders/agent/secrets/containers-instana-io-secret/secret.go new file mode 100644 index 00000000..b37f9690 --- /dev/null +++ b/pkg/k8s/object/builders/agent/secrets/containers-instana-io-secret/secret.go @@ -0,0 +1,81 @@ +package containers_instana_io_secret + +import ( + "fmt" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + instanav1 "github.com/instana/instana-agent-operator/api/v1" + "github.com/instana/instana-agent-operator/pkg/json_or_die" + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/builder" + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/constants" + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/helpers" + "github.com/instana/instana-agent-operator/pkg/optional" +) + +type secretBuilder struct { + *instanav1.InstanaAgent + + helpers.Helpers + dockerConfigMarshaler +} + +func (s *secretBuilder) IsNamespaced() bool { + return true +} + +func (s *secretBuilder) ComponentName() string { + return constants.ComponentInstanaAgent +} + +func (s *secretBuilder) buildDockerConfigJson() []byte { + password := optional.Of(s.Spec.Agent.DownloadKey).GetOrDefault(s.Spec.Agent.Key) + auth := fmt.Sprintf("_:%s", password) + + json := DockerConfigJson{ + Auths: map[string]DockerConfigAuth{ + helpers.ContainersInstanaIORegistry: { + Auth: []byte(auth), + }, + }, + } + + return s.MarshalOrDie(&json) +} + +func (s *secretBuilder) build() *corev1.Secret { + return &corev1.Secret{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Secret", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: s.ContainersSecretName(), + Namespace: s.Namespace, + }, + Data: map[string][]byte{ + corev1.DockerConfigJsonKey: s.buildDockerConfigJson(), + }, + Type: corev1.SecretTypeDockerConfigJson, + } +} + +func (s *secretBuilder) Build() optional.Optional[client.Object] { + switch s.UseContainersSecret() { + case true: + return optional.Of[client.Object](s.build()) + default: + return optional.Empty[client.Object]() + } +} + +func NewSecretBuilder(agent *instanav1.InstanaAgent) builder.ObjectBuilder { + return &secretBuilder{ + InstanaAgent: agent, + + Helpers: helpers.NewHelpers(agent), + dockerConfigMarshaler: json_or_die.NewJsonOrDie[DockerConfigJson](), + } +} diff --git a/pkg/k8s/object/builders/agent/secrets/containers-instana-io-secret/secret_test.go b/pkg/k8s/object/builders/agent/secrets/containers-instana-io-secret/secret_test.go new file mode 100644 index 00000000..7b7d7230 --- /dev/null +++ b/pkg/k8s/object/builders/agent/secrets/containers-instana-io-secret/secret_test.go @@ -0,0 +1,150 @@ +/* +(c) Copyright IBM Corp. 2024 +(c) Copyright Instana Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package containers_instana_io_secret + +import ( + "testing" + + "github.com/stretchr/testify/require" + gomock "go.uber.org/mock/gomock" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/rand" + "sigs.k8s.io/controller-runtime/pkg/client" + + instanav1 "github.com/instana/instana-agent-operator/api/v1" + "github.com/instana/instana-agent-operator/pkg/optional" +) + +func TestSecretBuilder_IsNamespaced_ComponentName(t *testing.T) { + assertions := require.New(t) + + s := NewSecretBuilder(&instanav1.InstanaAgent{}) + + assertions.True(s.IsNamespaced()) + assertions.Equal("instana-agent", s.ComponentName()) +} + +func randString() string { + return rand.String(rand.IntnRange(1, 15)) +} + +func dockerConfigJsonForMarshal(password string) *DockerConfigJson { + return &DockerConfigJson{ + Auths: map[string]DockerConfigAuth{ + "containers.instana.io": { + Auth: []byte("_:" + password), + }, + }, + } +} + +func TestSecretBuilder_Build(t *testing.T) { + randomNamespace := randString() + randomAgentKey := randString() + randomDownloadKey := randString() + randomContainerSecretName := randString() + randomMarshalResult := []byte(randString()) + + expectedResult := optional.Of[client.Object]( + &corev1.Secret{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Secret", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: randomContainerSecretName, + Namespace: randomNamespace, + }, + Data: map[string][]byte{ + corev1.DockerConfigJsonKey: randomMarshalResult, + }, + Type: corev1.SecretTypeDockerConfigJson, + }, + ) + + for _, test := range []struct { + name string + useContainerSecret bool + expectedPassword string + agentKey string + downloadKey string + expected optional.Optional[client.Object] + }{ + { + name: "should_be_empty", + useContainerSecret: false, + expected: optional.Empty[client.Object](), + }, + { + name: "download_key_is_specified", + useContainerSecret: true, + expectedPassword: randomDownloadKey, + agentKey: randomAgentKey, + downloadKey: randomDownloadKey, + expected: expectedResult, + }, + { + name: "download_key_is_not_specified", + useContainerSecret: true, + expectedPassword: randomAgentKey, + agentKey: randomAgentKey, + expected: expectedResult, + }, + } { + t.Run( + test.name, func(t *testing.T) { + assertions := require.New(t) + ctrl := gomock.NewController(t) + + agent := &instanav1.InstanaAgent{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: randomNamespace, + }, + Spec: instanav1.InstanaAgentSpec{ + Agent: instanav1.BaseAgentSpec{ + Key: test.agentKey, + DownloadKey: test.downloadKey, + }, + }, + } + + helpers := NewMockHelpers(ctrl) + helpers.EXPECT().UseContainersSecret().Return(test.useContainerSecret) + if test.useContainerSecret { + helpers.EXPECT().ContainersSecretName().Return(randomContainerSecretName) + } + + marshaler := NewMockdockerConfigMarshaler(ctrl) + if test.useContainerSecret { + marshaler.EXPECT().MarshalOrDie(dockerConfigJsonForMarshal(test.expectedPassword)).Return(randomMarshalResult) + } + + sb := &secretBuilder{ + InstanaAgent: agent, + Helpers: helpers, + dockerConfigMarshaler: marshaler, + } + + actual := sb.Build() + + assertions.Equal(test.expected, actual) + }, + ) + } +} diff --git a/pkg/k8s/object/builders/agent/secrets/keys-secret/secret.go b/pkg/k8s/object/builders/agent/secrets/keys-secret/secret.go new file mode 100644 index 00000000..4bafe412 --- /dev/null +++ b/pkg/k8s/object/builders/agent/secrets/keys-secret/secret.go @@ -0,0 +1,72 @@ +package keys_secret + +import ( + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + instanav1 "github.com/instana/instana-agent-operator/api/v1" + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/builder" + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/constants" + "github.com/instana/instana-agent-operator/pkg/optional" +) + +type secretBuilder struct { + *instanav1.InstanaAgent +} + +func (s *secretBuilder) IsNamespaced() bool { + return true +} + +func (s *secretBuilder) ComponentName() string { + return constants.ComponentInstanaAgent +} + +func (s *secretBuilder) getData() map[string][]byte { + data := make(map[string][]byte, 2) + + optional.Of(s.Spec.Agent.Key).IfPresent( + func(key string) { + data[constants.AgentKey] = []byte(key) + }, + ) + + optional.Of(s.Spec.Agent.DownloadKey).IfPresent( + func(downloadKey string) { + data[constants.DownloadKey] = []byte(downloadKey) + }, + ) + + return data +} + +func (s *secretBuilder) build() *corev1.Secret { + return &corev1.Secret{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Secret", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: s.Name, + Namespace: s.Namespace, + }, + Data: s.getData(), + Type: corev1.SecretTypeOpaque, + } +} + +func (s *secretBuilder) Build() optional.Optional[client.Object] { + switch s.Spec.Agent.KeysSecret { + case "": + return optional.Of[client.Object](s.build()) + default: + return optional.Empty[client.Object]() + } +} + +func NewSecretBuilder(agent *instanav1.InstanaAgent) builder.ObjectBuilder { + return &secretBuilder{ + InstanaAgent: agent, + } +} diff --git a/pkg/k8s/object/builders/agent/secrets/keys-secret/secret_test.go b/pkg/k8s/object/builders/agent/secrets/keys-secret/secret_test.go new file mode 100644 index 00000000..e7f5bb47 --- /dev/null +++ b/pkg/k8s/object/builders/agent/secrets/keys-secret/secret_test.go @@ -0,0 +1,104 @@ +package keys_secret + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/rand" + "sigs.k8s.io/controller-runtime/pkg/client" + + instanav1 "github.com/instana/instana-agent-operator/api/v1" + "github.com/instana/instana-agent-operator/pkg/optional" +) + +func TestSecretBuilder_IsNamespaced_ComponentName(t *testing.T) { + assertions := require.New(t) + + s := NewSecretBuilder(&instanav1.InstanaAgent{}) + + assertions.True(s.IsNamespaced()) + assertions.Equal("instana-agent", s.ComponentName()) +} + +func randString() string { + return rand.String(rand.IntnRange(1, 15)) +} + +func emptyOrRandomString() []string { + return []string{"", randString()} +} + +func TestSecretBuilder_Build(t *testing.T) { + for _, keysSecret := range emptyOrRandomString() { + for _, key := range emptyOrRandomString() { + for _, downloadKey := range emptyOrRandomString() { + t.Run( + fmt.Sprintf( + "keysSecretIsEmpty:%v_keyIsEmpty:%v_downloadKeyIsEmpty:%v", + len(keysSecret) == 0, + len(key) == 0, + len(downloadKey) == 0, + ), func(t *testing.T) { + assertions := require.New(t) + + name := randString() + namespace := randString() + + agent := instanav1.InstanaAgent{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Spec: instanav1.InstanaAgentSpec{ + Agent: instanav1.BaseAgentSpec{ + KeysSecret: keysSecret, + Key: key, + DownloadKey: downloadKey, + }, + }, + } + + sb := NewSecretBuilder(&agent) + + actual := sb.Build() + + switch keysSecret { + case "": + data := make(map[string][]byte, 2) + + if len(key) > 0 { + data["key"] = []byte(key) + } + + if len(downloadKey) > 0 { + data["downloadKey"] = []byte(downloadKey) + } + + expected := optional.Of[client.Object]( + &corev1.Secret{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Secret", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Data: data, + Type: corev1.SecretTypeOpaque, + }, + ) + + assertions.Equal(expected, actual) + default: + assertions.Empty(actual) + } + }, + ) + } + } + } +} diff --git a/pkg/k8s/object/builders/agent/secrets/tls-secret/helpers_mock_test.go b/pkg/k8s/object/builders/agent/secrets/tls-secret/helpers_mock_test.go new file mode 100644 index 00000000..0e8c9fd0 --- /dev/null +++ b/pkg/k8s/object/builders/agent/secrets/tls-secret/helpers_mock_test.go @@ -0,0 +1,181 @@ +// /* +// (c) Copyright IBM Corp. 2024 +// (c) Copyright Instana Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// */ +// + +// Code generated by MockGen. DO NOT EDIT. +package tls_secret + +import ( + reflect "reflect" + + gomock "go.uber.org/mock/gomock" + v1 "k8s.io/api/core/v1" +) + +// MockHelpers is a mock of Helpers interface. +type MockHelpers struct { + ctrl *gomock.Controller + recorder *MockHelpersMockRecorder +} + +// MockHelpersMockRecorder is the mock recorder for MockHelpers. +type MockHelpersMockRecorder struct { + mock *MockHelpers +} + +// NewMockHelpers creates a new mock instance. +func NewMockHelpers(ctrl *gomock.Controller) *MockHelpers { + mock := &MockHelpers{ctrl: ctrl} + mock.recorder = &MockHelpersMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockHelpers) EXPECT() *MockHelpersMockRecorder { + return m.recorder +} + +// ISGOMOCK indicates that this struct is a gomock mock. +func (m *MockHelpers) ISGOMOCK() struct{} { + return struct{}{} +} + +// ContainersSecretName mocks base method. +func (m *MockHelpers) ContainersSecretName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ContainersSecretName") + ret0, _ := ret[0].(string) + return ret0 +} + +// ContainersSecretName indicates an expected call of ContainersSecretName. +func (mr *MockHelpersMockRecorder) ContainersSecretName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ContainersSecretName", reflect.TypeOf((*MockHelpers)(nil).ContainersSecretName)) +} + +// HeadlessServiceName mocks base method. +func (m *MockHelpers) HeadlessServiceName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "HeadlessServiceName") + ret0, _ := ret[0].(string) + return ret0 +} + +// HeadlessServiceName indicates an expected call of HeadlessServiceName. +func (mr *MockHelpersMockRecorder) HeadlessServiceName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HeadlessServiceName", reflect.TypeOf((*MockHelpers)(nil).HeadlessServiceName)) +} + +// ImagePullSecrets mocks base method. +func (m *MockHelpers) ImagePullSecrets() []v1.LocalObjectReference { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ImagePullSecrets") + ret0, _ := ret[0].([]v1.LocalObjectReference) + return ret0 +} + +// ImagePullSecrets indicates an expected call of ImagePullSecrets. +func (mr *MockHelpersMockRecorder) ImagePullSecrets() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ImagePullSecrets", reflect.TypeOf((*MockHelpers)(nil).ImagePullSecrets)) +} + +// K8sSensorResourcesName mocks base method. +func (m *MockHelpers) K8sSensorResourcesName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "K8sSensorResourcesName") + ret0, _ := ret[0].(string) + return ret0 +} + +// K8sSensorResourcesName indicates an expected call of K8sSensorResourcesName. +func (mr *MockHelpersMockRecorder) K8sSensorResourcesName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "K8sSensorResourcesName", reflect.TypeOf((*MockHelpers)(nil).K8sSensorResourcesName)) +} + +// KeysSecretName mocks base method. +func (m *MockHelpers) KeysSecretName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "KeysSecretName") + ret0, _ := ret[0].(string) + return ret0 +} + +// KeysSecretName indicates an expected call of KeysSecretName. +func (mr *MockHelpersMockRecorder) KeysSecretName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "KeysSecretName", reflect.TypeOf((*MockHelpers)(nil).KeysSecretName)) +} + +// ServiceAccountName mocks base method. +func (m *MockHelpers) ServiceAccountName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ServiceAccountName") + ret0, _ := ret[0].(string) + return ret0 +} + +// ServiceAccountName indicates an expected call of ServiceAccountName. +func (mr *MockHelpersMockRecorder) ServiceAccountName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ServiceAccountName", reflect.TypeOf((*MockHelpers)(nil).ServiceAccountName)) +} + +// TLSIsEnabled mocks base method. +func (m *MockHelpers) TLSIsEnabled() bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "TLSIsEnabled") + ret0, _ := ret[0].(bool) + return ret0 +} + +// TLSIsEnabled indicates an expected call of TLSIsEnabled. +func (mr *MockHelpersMockRecorder) TLSIsEnabled() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TLSIsEnabled", reflect.TypeOf((*MockHelpers)(nil).TLSIsEnabled)) +} + +// TLSSecretName mocks base method. +func (m *MockHelpers) TLSSecretName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "TLSSecretName") + ret0, _ := ret[0].(string) + return ret0 +} + +// TLSSecretName indicates an expected call of TLSSecretName. +func (mr *MockHelpersMockRecorder) TLSSecretName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TLSSecretName", reflect.TypeOf((*MockHelpers)(nil).TLSSecretName)) +} + +// UseContainersSecret mocks base method. +func (m *MockHelpers) UseContainersSecret() bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UseContainersSecret") + ret0, _ := ret[0].(bool) + return ret0 +} + +// UseContainersSecret indicates an expected call of UseContainersSecret. +func (mr *MockHelpersMockRecorder) UseContainersSecret() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UseContainersSecret", reflect.TypeOf((*MockHelpers)(nil).UseContainersSecret)) +} diff --git a/pkg/k8s/object/builders/agent/secrets/tls-secret/secret.go b/pkg/k8s/object/builders/agent/secrets/tls-secret/secret.go new file mode 100644 index 00000000..5b07dd86 --- /dev/null +++ b/pkg/k8s/object/builders/agent/secrets/tls-secret/secret.go @@ -0,0 +1,62 @@ +package tls_secret + +import ( + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + instanav1 "github.com/instana/instana-agent-operator/api/v1" + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/builder" + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/constants" + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/helpers" + "github.com/instana/instana-agent-operator/pkg/optional" +) + +type secretBuilder struct { + *instanav1.InstanaAgent + + helpers.Helpers +} + +func (s *secretBuilder) IsNamespaced() bool { + return true +} + +func (s *secretBuilder) ComponentName() string { + return constants.ComponentInstanaAgent +} + +func (s *secretBuilder) build() *corev1.Secret { + return &corev1.Secret{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Secret", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: s.TLSSecretName(), + Namespace: s.Namespace, + }, + Data: map[string][]byte{ + corev1.TLSCertKey: s.Spec.Agent.TlsSpec.Certificate, + corev1.TLSPrivateKeyKey: s.Spec.Agent.TlsSpec.Key, + }, + Type: corev1.SecretTypeTLS, + } +} + +func (s *secretBuilder) Build() optional.Optional[client.Object] { + switch tls := s.Spec.Agent.TlsSpec; tls.SecretName == "" && len(tls.Key) > 0 && len(tls.Certificate) > 0 { + case true: + return optional.Of[client.Object](s.build()) + default: + return optional.Empty[client.Object]() + } +} + +func NewSecretBuilder(agent *instanav1.InstanaAgent) builder.ObjectBuilder { + return &secretBuilder{ + InstanaAgent: agent, + + Helpers: helpers.NewHelpers(agent), + } +} diff --git a/pkg/k8s/object/builders/agent/secrets/tls-secret/secret_test.go b/pkg/k8s/object/builders/agent/secrets/tls-secret/secret_test.go new file mode 100644 index 00000000..2fb041c1 --- /dev/null +++ b/pkg/k8s/object/builders/agent/secrets/tls-secret/secret_test.go @@ -0,0 +1,127 @@ +/* +(c) Copyright IBM Corp. 2024 +(c) Copyright Instana Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package tls_secret + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" + gomock "go.uber.org/mock/gomock" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/rand" + "sigs.k8s.io/controller-runtime/pkg/client" + + instanav1 "github.com/instana/instana-agent-operator/api/v1" + "github.com/instana/instana-agent-operator/pkg/optional" +) + +func TestSecretBuilder_IsNamespaced_ComponentName(t *testing.T) { + assertions := require.New(t) + + s := NewSecretBuilder(&instanav1.InstanaAgent{}) + + assertions.True(s.IsNamespaced()) + assertions.Equal("instana-agent", s.ComponentName()) +} + +func TestSecretBuilder_Build(t *testing.T) { + for _, secretName := range []string{"", rand.String(rand.IntnRange(1, 15))} { + for _, key := range [][]byte{nil, []byte(rand.String(rand.IntnRange(1, 15)))} { + for _, cert := range [][]byte{nil, []byte(rand.String(rand.IntnRange(1, 15)))} { + t.Run( + fmt.Sprintf( + "%+v", struct { + secretNameIsEmpty bool + keyIsEmpty bool + certIsEmpty bool + }{ + secretNameIsEmpty: len(secretName) == 0, + keyIsEmpty: len(key) == 0, + certIsEmpty: len(cert) == 0, + }, + ), func(t *testing.T) { + assertions := require.New(t) + ctrl := gomock.NewController(t) + + namespace := rand.String(rand.IntnRange(1, 15)) + + agent := &instanav1.InstanaAgent{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + }, + Spec: instanav1.InstanaAgentSpec{ + Agent: instanav1.BaseAgentSpec{ + TlsSpec: instanav1.TlsSpec{ + SecretName: secretName, + Key: key, + Certificate: cert, + }, + }, + }, + } + + helpers := NewMockHelpers(ctrl) + + sb := &secretBuilder{ + InstanaAgent: agent, + Helpers: helpers, + } + + switch { + case secretName != "": + fallthrough + case len(key) == 0: + fallthrough + case len(cert) == 0: + actual := sb.Build() + assertions.Empty(actual) + default: + tlsSecretName := rand.String(rand.IntnRange(1, 15)) + + helpers.EXPECT().TLSSecretName().Return(tlsSecretName) + + expected := optional.Of[client.Object]( + &corev1.Secret{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Secret", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: tlsSecretName, + Namespace: namespace, + }, + Data: map[string][]byte{ + corev1.TLSPrivateKeyKey: key, + corev1.TLSCertKey: cert, + }, + Type: corev1.SecretTypeTLS, + }, + ) + + actual := sb.Build() + + assertions.Equal(expected, actual) + } + }, + ) + } + } + } +} diff --git a/pkg/k8s/object/builders/agent/service/agent_interfaces_mock_test.go b/pkg/k8s/object/builders/agent/service/agent_interfaces_mock_test.go new file mode 100644 index 00000000..4dc6e388 --- /dev/null +++ b/pkg/k8s/object/builders/agent/service/agent_interfaces_mock_test.go @@ -0,0 +1,96 @@ +// /* +// (c) Copyright IBM Corp. 2024 +// (c) Copyright Instana Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// */ +// + +// Code generated by MockGen. DO NOT EDIT. +package service + +import ( + reflect "reflect" + + gomock "go.uber.org/mock/gomock" +) + +// MockOpenTelemetrySettings is a mock of OpenTelemetrySettings interface. +type MockOpenTelemetrySettings struct { + ctrl *gomock.Controller + recorder *MockOpenTelemetrySettingsMockRecorder +} + +// MockOpenTelemetrySettingsMockRecorder is the mock recorder for MockOpenTelemetrySettings. +type MockOpenTelemetrySettingsMockRecorder struct { + mock *MockOpenTelemetrySettings +} + +// NewMockOpenTelemetrySettings creates a new mock instance. +func NewMockOpenTelemetrySettings(ctrl *gomock.Controller) *MockOpenTelemetrySettings { + mock := &MockOpenTelemetrySettings{ctrl: ctrl} + mock.recorder = &MockOpenTelemetrySettingsMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockOpenTelemetrySettings) EXPECT() *MockOpenTelemetrySettingsMockRecorder { + return m.recorder +} + +// ISGOMOCK indicates that this struct is a gomock mock. +func (m *MockOpenTelemetrySettings) ISGOMOCK() struct{} { + return struct{}{} +} + +// GrpcIsEnabled mocks base method. +func (m *MockOpenTelemetrySettings) GrpcIsEnabled() bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GrpcIsEnabled") + ret0, _ := ret[0].(bool) + return ret0 +} + +// GrpcIsEnabled indicates an expected call of GrpcIsEnabled. +func (mr *MockOpenTelemetrySettingsMockRecorder) GrpcIsEnabled() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GrpcIsEnabled", reflect.TypeOf((*MockOpenTelemetrySettings)(nil).GrpcIsEnabled)) +} + +// HttpIsEnabled mocks base method. +func (m *MockOpenTelemetrySettings) HttpIsEnabled() bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "HttpIsEnabled") + ret0, _ := ret[0].(bool) + return ret0 +} + +// HttpIsEnabled indicates an expected call of HttpIsEnabled. +func (mr *MockOpenTelemetrySettingsMockRecorder) HttpIsEnabled() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HttpIsEnabled", reflect.TypeOf((*MockOpenTelemetrySettings)(nil).HttpIsEnabled)) +} + +// IsEnabled mocks base method. +func (m *MockOpenTelemetrySettings) IsEnabled() bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "IsEnabled") + ret0, _ := ret[0].(bool) + return ret0 +} + +// IsEnabled indicates an expected call of IsEnabled. +func (mr *MockOpenTelemetrySettingsMockRecorder) IsEnabled() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsEnabled", reflect.TypeOf((*MockOpenTelemetrySettings)(nil).IsEnabled)) +} diff --git a/pkg/k8s/object/builders/agent/service/pod_selector_mock_test.go b/pkg/k8s/object/builders/agent/service/pod_selector_mock_test.go new file mode 100644 index 00000000..1a6dbb9a --- /dev/null +++ b/pkg/k8s/object/builders/agent/service/pod_selector_mock_test.go @@ -0,0 +1,82 @@ +// /* +// (c) Copyright IBM Corp. 2024 +// (c) Copyright Instana Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// */ +// + +// Code generated by MockGen. DO NOT EDIT. +package service + +import ( + reflect "reflect" + + gomock "go.uber.org/mock/gomock" +) + +// MockPodSelectorLabelGenerator is a mock of PodSelectorLabelGenerator interface. +type MockPodSelectorLabelGenerator struct { + ctrl *gomock.Controller + recorder *MockPodSelectorLabelGeneratorMockRecorder +} + +// MockPodSelectorLabelGeneratorMockRecorder is the mock recorder for MockPodSelectorLabelGenerator. +type MockPodSelectorLabelGeneratorMockRecorder struct { + mock *MockPodSelectorLabelGenerator +} + +// NewMockPodSelectorLabelGenerator creates a new mock instance. +func NewMockPodSelectorLabelGenerator(ctrl *gomock.Controller) *MockPodSelectorLabelGenerator { + mock := &MockPodSelectorLabelGenerator{ctrl: ctrl} + mock.recorder = &MockPodSelectorLabelGeneratorMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockPodSelectorLabelGenerator) EXPECT() *MockPodSelectorLabelGeneratorMockRecorder { + return m.recorder +} + +// ISGOMOCK indicates that this struct is a gomock mock. +func (m *MockPodSelectorLabelGenerator) ISGOMOCK() struct{} { + return struct{}{} +} + +// GetPodLabels mocks base method. +func (m *MockPodSelectorLabelGenerator) GetPodLabels(userLabels map[string]string) map[string]string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetPodLabels", userLabels) + ret0, _ := ret[0].(map[string]string) + return ret0 +} + +// GetPodLabels indicates an expected call of GetPodLabels. +func (mr *MockPodSelectorLabelGeneratorMockRecorder) GetPodLabels(userLabels any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPodLabels", reflect.TypeOf((*MockPodSelectorLabelGenerator)(nil).GetPodLabels), userLabels) +} + +// GetPodSelectorLabels mocks base method. +func (m *MockPodSelectorLabelGenerator) GetPodSelectorLabels() map[string]string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetPodSelectorLabels") + ret0, _ := ret[0].(map[string]string) + return ret0 +} + +// GetPodSelectorLabels indicates an expected call of GetPodSelectorLabels. +func (mr *MockPodSelectorLabelGeneratorMockRecorder) GetPodSelectorLabels() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPodSelectorLabels", reflect.TypeOf((*MockPodSelectorLabelGenerator)(nil).GetPodSelectorLabels)) +} diff --git a/pkg/k8s/object/builders/agent/service/ports_mock_test.go b/pkg/k8s/object/builders/agent/service/ports_mock_test.go new file mode 100644 index 00000000..f6428a02 --- /dev/null +++ b/pkg/k8s/object/builders/agent/service/ports_mock_test.go @@ -0,0 +1,163 @@ +// /* +// (c) Copyright IBM Corp. 2024 +// (c) Copyright Instana Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// */ +// + +// Code generated by MockGen. DO NOT EDIT. +package service + +import ( + reflect "reflect" + + helpers "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/helpers" + ports "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/ports" + gomock "go.uber.org/mock/gomock" + v1 "k8s.io/api/core/v1" +) + +// MockPort is a mock of Port interface. +type MockPort struct { + ctrl *gomock.Controller + recorder *MockPortMockRecorder +} + +// MockPortMockRecorder is the mock recorder for MockPort. +type MockPortMockRecorder struct { + mock *MockPort +} + +// NewMockPort creates a new mock instance. +func NewMockPort(ctrl *gomock.Controller) *MockPort { + mock := &MockPort{ctrl: ctrl} + mock.recorder = &MockPortMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockPort) EXPECT() *MockPortMockRecorder { + return m.recorder +} + +// ISGOMOCK indicates that this struct is a gomock mock. +func (m *MockPort) ISGOMOCK() struct{} { + return struct{}{} +} + +// String mocks base method. +func (m *MockPort) String() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "String") + ret0, _ := ret[0].(string) + return ret0 +} + +// String indicates an expected call of String. +func (mr *MockPortMockRecorder) String() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "String", reflect.TypeOf((*MockPort)(nil).String)) +} + +// isEnabled mocks base method. +func (m *MockPort) isEnabled(openTelemetrySettings helpers.OpenTelemetrySettings) bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "isEnabled", openTelemetrySettings) + ret0, _ := ret[0].(bool) + return ret0 +} + +// isEnabled indicates an expected call of isEnabled. +func (mr *MockPortMockRecorder) isEnabled(openTelemetrySettings any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "isEnabled", reflect.TypeOf((*MockPort)(nil).isEnabled), openTelemetrySettings) +} + +// portNumber mocks base method. +func (m *MockPort) portNumber() int32 { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "portNumber") + ret0, _ := ret[0].(int32) + return ret0 +} + +// portNumber indicates an expected call of portNumber. +func (mr *MockPortMockRecorder) portNumber() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "portNumber", reflect.TypeOf((*MockPort)(nil).portNumber)) +} + +// MockPortsBuilder is a mock of PortsBuilder interface. +type MockPortsBuilder struct { + ctrl *gomock.Controller + recorder *MockPortsBuilderMockRecorder +} + +// MockPortsBuilderMockRecorder is the mock recorder for MockPortsBuilder. +type MockPortsBuilderMockRecorder struct { + mock *MockPortsBuilder +} + +// NewMockPortsBuilder creates a new mock instance. +func NewMockPortsBuilder(ctrl *gomock.Controller) *MockPortsBuilder { + mock := &MockPortsBuilder{ctrl: ctrl} + mock.recorder = &MockPortsBuilderMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockPortsBuilder) EXPECT() *MockPortsBuilderMockRecorder { + return m.recorder +} + +// ISGOMOCK indicates that this struct is a gomock mock. +func (m *MockPortsBuilder) ISGOMOCK() struct{} { + return struct{}{} +} + +// GetContainerPorts mocks base method. +func (m *MockPortsBuilder) GetContainerPorts(ports ...ports.Port) []v1.ContainerPort { + m.ctrl.T.Helper() + varargs := []any{} + for _, a := range ports { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "GetContainerPorts", varargs...) + ret0, _ := ret[0].([]v1.ContainerPort) + return ret0 +} + +// GetContainerPorts indicates an expected call of GetContainerPorts. +func (mr *MockPortsBuilderMockRecorder) GetContainerPorts(ports ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetContainerPorts", reflect.TypeOf((*MockPortsBuilder)(nil).GetContainerPorts), ports...) +} + +// GetServicePorts mocks base method. +func (m *MockPortsBuilder) GetServicePorts(ports ...ports.Port) []v1.ServicePort { + m.ctrl.T.Helper() + varargs := []any{} + for _, a := range ports { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "GetServicePorts", varargs...) + ret0, _ := ret[0].([]v1.ServicePort) + return ret0 +} + +// GetServicePorts indicates an expected call of GetServicePorts. +func (mr *MockPortsBuilderMockRecorder) GetServicePorts(ports ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetServicePorts", reflect.TypeOf((*MockPortsBuilder)(nil).GetServicePorts), ports...) +} diff --git a/pkg/k8s/object/builders/agent/service/service.go b/pkg/k8s/object/builders/agent/service/service.go new file mode 100644 index 00000000..586c8914 --- /dev/null +++ b/pkg/k8s/object/builders/agent/service/service.go @@ -0,0 +1,82 @@ +package service + +import ( + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + instanav1 "github.com/instana/instana-agent-operator/api/v1" + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/builder" + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/constants" + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/helpers" + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/ports" + "github.com/instana/instana-agent-operator/pkg/k8s/object/transformations" + "github.com/instana/instana-agent-operator/pkg/optional" + "github.com/instana/instana-agent-operator/pkg/pointer" +) + +const ( + componentName = constants.ComponentInstanaAgent +) + +type serviceBuilder struct { + *instanav1.InstanaAgent + + transformations.PodSelectorLabelGenerator + ports.PortsBuilder + helpers.OpenTelemetrySettings +} + +func (s *serviceBuilder) ComponentName() string { + return componentName +} + +func (s *serviceBuilder) IsNamespaced() bool { + return true +} + +func (s *serviceBuilder) build() *corev1.Service { + return &corev1.Service{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Service", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: s.Name, + Namespace: s.Namespace, + }, + Spec: corev1.ServiceSpec{ + Selector: s.GetPodSelectorLabels(), + Ports: s.GetServicePorts( + ports.AgentAPIsPort, + ports.OpenTelemetryLegacyPort, + ports.OpenTelemetryGRPCPort, + ports.OpenTelemetryHTTPPort, + ), + InternalTrafficPolicy: pointer.To(corev1.ServiceInternalTrafficPolicyLocal), + }, + } +} + +func (s *serviceBuilder) Build() optional.Optional[client.Object] { + switch { + case pointer.DerefOrEmpty(s.Spec.Service.Create): + fallthrough + case pointer.DerefOrEmpty(s.Spec.Prometheus.RemoteWrite.Enabled): + fallthrough + case s.OpenTelemetrySettings.IsEnabled(): + return optional.Of[client.Object](s.build()) + default: + return optional.Empty[client.Object]() + } +} + +func NewServiceBuilder(agent *instanav1.InstanaAgent) builder.ObjectBuilder { + return &serviceBuilder{ + InstanaAgent: agent, + + PodSelectorLabelGenerator: transformations.PodSelectorLabels(agent, componentName), + PortsBuilder: ports.NewPortsBuilder(agent), + OpenTelemetrySettings: agent.Spec.OpenTelemetry, + } +} diff --git a/pkg/k8s/object/builders/agent/service/service_test.go b/pkg/k8s/object/builders/agent/service/service_test.go new file mode 100644 index 00000000..6fb59476 --- /dev/null +++ b/pkg/k8s/object/builders/agent/service/service_test.go @@ -0,0 +1,156 @@ +/* +(c) Copyright IBM Corp. 2024 +(c) Copyright Instana Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package service + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" + gomock "go.uber.org/mock/gomock" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/rand" + "sigs.k8s.io/controller-runtime/pkg/client" + + instanav1 "github.com/instana/instana-agent-operator/api/v1" + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/constants" + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/ports" + "github.com/instana/instana-agent-operator/pkg/optional" + "github.com/instana/instana-agent-operator/pkg/pointer" +) + +func TestServiceBuilder_ComponentName_IsNamespaced(t *testing.T) { + assertions := require.New(t) + + sb := NewServiceBuilder(&instanav1.InstanaAgent{}) + + assertions.True(sb.IsNamespaced()) + assertions.Equal(constants.ComponentInstanaAgent, sb.ComponentName()) +} + +func TestServiceBuilder_Build(t *testing.T) { + for _, serviceCreate := range []*bool{nil, pointer.To(true), pointer.To(false)} { + for _, remoteWriteEnabled := range []instanav1.Enabled{ + {Enabled: pointer.To(true)}, + {Enabled: pointer.To(false)}, + {Enabled: nil}, + } { + for _, otlpIsEnabled := range []bool{true, false} { + t.Run( + fmt.Sprintf( + "service.create=%v,prometheus.remoteWrite.enabled=%v,otlpEnabled=%v", + serviceCreate, + remoteWriteEnabled, + otlpIsEnabled, + ), + func(t *testing.T) { + assertions := require.New(t) + ctrl := gomock.NewController(t) + + name := rand.String(10) + namespace := rand.String(10) + + agent := instanav1.InstanaAgent{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Spec: instanav1.InstanaAgentSpec{ + Service: instanav1.Create{Create: serviceCreate}, + Prometheus: instanav1.Prometheus{ + RemoteWrite: remoteWriteEnabled, + }, + }, + } + + otlpSettings := NewMockOpenTelemetrySettings(ctrl) + if !pointer.DerefOrEmpty(serviceCreate) && (remoteWriteEnabled.Enabled == nil || !*remoteWriteEnabled.Enabled) { + otlpSettings.EXPECT().IsEnabled().Return(otlpIsEnabled) + } + + podSelectorLabelGenerator := NewMockPodSelectorLabelGenerator(ctrl) + + portsBuilder := NewMockPortsBuilder(ctrl) + + sb := &serviceBuilder{ + InstanaAgent: &agent, + + PodSelectorLabelGenerator: podSelectorLabelGenerator, + PortsBuilder: portsBuilder, + OpenTelemetrySettings: otlpSettings, + } + + if pointer.DerefOrEmpty(serviceCreate) || (remoteWriteEnabled.Enabled != nil && *remoteWriteEnabled.Enabled) || otlpIsEnabled { + expectedSelectorLabels := map[string]string{ + rand.String(rand.IntnRange(1, 15)): rand.String(rand.IntnRange(1, 15)), + rand.String(rand.IntnRange(1, 15)): rand.String(rand.IntnRange(1, 15)), + rand.String(rand.IntnRange(1, 15)): rand.String(rand.IntnRange(1, 15)), + } + podSelectorLabelGenerator.EXPECT().GetPodSelectorLabels().Return(expectedSelectorLabels) + + expectedServicePorts := []corev1.ServicePort{ + { + Name: rand.String(rand.IntnRange(1, 15)), + }, + { + Name: rand.String(rand.IntnRange(1, 15)), + }, + { + Name: rand.String(rand.IntnRange(1, 15)), + }, + } + portsBuilder.EXPECT().GetServicePorts( + ports.AgentAPIsPort, + ports.OpenTelemetryLegacyPort, + ports.OpenTelemetryGRPCPort, + ports.OpenTelemetryHTTPPort, + ).Return(expectedServicePorts) + + expected := optional.Of[client.Object]( + &corev1.Service{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Service", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Spec: corev1.ServiceSpec{ + Selector: expectedSelectorLabels, + Ports: expectedServicePorts, + InternalTrafficPolicy: pointer.To(corev1.ServiceInternalTrafficPolicyLocal), + }, + }, + ) + + actual := sb.Build() + + assertions.Equal(expected, actual) + } else { + res := sb.Build() + + assertions.Empty(res) + } + }, + ) + } + } + } +} diff --git a/pkg/k8s/object/builders/agent/serviceaccount/helpers_mock_test.go b/pkg/k8s/object/builders/agent/serviceaccount/helpers_mock_test.go new file mode 100644 index 00000000..9c076305 --- /dev/null +++ b/pkg/k8s/object/builders/agent/serviceaccount/helpers_mock_test.go @@ -0,0 +1,181 @@ +// /* +// (c) Copyright IBM Corp. 2024 +// (c) Copyright Instana Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// */ +// + +// Code generated by MockGen. DO NOT EDIT. +package serviceaccount + +import ( + reflect "reflect" + + gomock "go.uber.org/mock/gomock" + v1 "k8s.io/api/core/v1" +) + +// MockHelpers is a mock of Helpers interface. +type MockHelpers struct { + ctrl *gomock.Controller + recorder *MockHelpersMockRecorder +} + +// MockHelpersMockRecorder is the mock recorder for MockHelpers. +type MockHelpersMockRecorder struct { + mock *MockHelpers +} + +// NewMockHelpers creates a new mock instance. +func NewMockHelpers(ctrl *gomock.Controller) *MockHelpers { + mock := &MockHelpers{ctrl: ctrl} + mock.recorder = &MockHelpersMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockHelpers) EXPECT() *MockHelpersMockRecorder { + return m.recorder +} + +// ISGOMOCK indicates that this struct is a gomock mock. +func (m *MockHelpers) ISGOMOCK() struct{} { + return struct{}{} +} + +// ContainersSecretName mocks base method. +func (m *MockHelpers) ContainersSecretName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ContainersSecretName") + ret0, _ := ret[0].(string) + return ret0 +} + +// ContainersSecretName indicates an expected call of ContainersSecretName. +func (mr *MockHelpersMockRecorder) ContainersSecretName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ContainersSecretName", reflect.TypeOf((*MockHelpers)(nil).ContainersSecretName)) +} + +// HeadlessServiceName mocks base method. +func (m *MockHelpers) HeadlessServiceName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "HeadlessServiceName") + ret0, _ := ret[0].(string) + return ret0 +} + +// HeadlessServiceName indicates an expected call of HeadlessServiceName. +func (mr *MockHelpersMockRecorder) HeadlessServiceName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HeadlessServiceName", reflect.TypeOf((*MockHelpers)(nil).HeadlessServiceName)) +} + +// ImagePullSecrets mocks base method. +func (m *MockHelpers) ImagePullSecrets() []v1.LocalObjectReference { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ImagePullSecrets") + ret0, _ := ret[0].([]v1.LocalObjectReference) + return ret0 +} + +// ImagePullSecrets indicates an expected call of ImagePullSecrets. +func (mr *MockHelpersMockRecorder) ImagePullSecrets() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ImagePullSecrets", reflect.TypeOf((*MockHelpers)(nil).ImagePullSecrets)) +} + +// K8sSensorResourcesName mocks base method. +func (m *MockHelpers) K8sSensorResourcesName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "K8sSensorResourcesName") + ret0, _ := ret[0].(string) + return ret0 +} + +// K8sSensorResourcesName indicates an expected call of K8sSensorResourcesName. +func (mr *MockHelpersMockRecorder) K8sSensorResourcesName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "K8sSensorResourcesName", reflect.TypeOf((*MockHelpers)(nil).K8sSensorResourcesName)) +} + +// KeysSecretName mocks base method. +func (m *MockHelpers) KeysSecretName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "KeysSecretName") + ret0, _ := ret[0].(string) + return ret0 +} + +// KeysSecretName indicates an expected call of KeysSecretName. +func (mr *MockHelpersMockRecorder) KeysSecretName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "KeysSecretName", reflect.TypeOf((*MockHelpers)(nil).KeysSecretName)) +} + +// ServiceAccountName mocks base method. +func (m *MockHelpers) ServiceAccountName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ServiceAccountName") + ret0, _ := ret[0].(string) + return ret0 +} + +// ServiceAccountName indicates an expected call of ServiceAccountName. +func (mr *MockHelpersMockRecorder) ServiceAccountName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ServiceAccountName", reflect.TypeOf((*MockHelpers)(nil).ServiceAccountName)) +} + +// TLSIsEnabled mocks base method. +func (m *MockHelpers) TLSIsEnabled() bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "TLSIsEnabled") + ret0, _ := ret[0].(bool) + return ret0 +} + +// TLSIsEnabled indicates an expected call of TLSIsEnabled. +func (mr *MockHelpersMockRecorder) TLSIsEnabled() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TLSIsEnabled", reflect.TypeOf((*MockHelpers)(nil).TLSIsEnabled)) +} + +// TLSSecretName mocks base method. +func (m *MockHelpers) TLSSecretName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "TLSSecretName") + ret0, _ := ret[0].(string) + return ret0 +} + +// TLSSecretName indicates an expected call of TLSSecretName. +func (mr *MockHelpersMockRecorder) TLSSecretName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TLSSecretName", reflect.TypeOf((*MockHelpers)(nil).TLSSecretName)) +} + +// UseContainersSecret mocks base method. +func (m *MockHelpers) UseContainersSecret() bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UseContainersSecret") + ret0, _ := ret[0].(bool) + return ret0 +} + +// UseContainersSecret indicates an expected call of UseContainersSecret. +func (mr *MockHelpersMockRecorder) UseContainersSecret() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UseContainersSecret", reflect.TypeOf((*MockHelpers)(nil).UseContainersSecret)) +} diff --git a/pkg/k8s/object/builders/agent/serviceaccount/serviceaccount.go b/pkg/k8s/object/builders/agent/serviceaccount/serviceaccount.go new file mode 100644 index 00000000..10757159 --- /dev/null +++ b/pkg/k8s/object/builders/agent/serviceaccount/serviceaccount.go @@ -0,0 +1,56 @@ +package serviceaccount + +import ( + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + instanav1 "github.com/instana/instana-agent-operator/api/v1" + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/builder" + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/constants" + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/helpers" + "github.com/instana/instana-agent-operator/pkg/optional" + "github.com/instana/instana-agent-operator/pkg/pointer" +) + +type serviceAccountBuilder struct { + *instanav1.InstanaAgent + helpers.Helpers +} + +func (s *serviceAccountBuilder) IsNamespaced() bool { + return true +} + +func (s *serviceAccountBuilder) ComponentName() string { + return constants.ComponentInstanaAgent +} + +func (s *serviceAccountBuilder) build() *corev1.ServiceAccount { + return &corev1.ServiceAccount{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "ServiceAccount", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: s.ServiceAccountName(), + Namespace: s.Namespace, + Annotations: s.Spec.ServiceAccountSpec.Annotations, + }, + } +} + +func (s *serviceAccountBuilder) Build() optional.Optional[client.Object] { + if pointer.DerefOrEmpty(s.Spec.ServiceAccountSpec.Create.Create) { + return optional.Of[client.Object](s.build()) + } else { + return optional.Empty[client.Object]() + } +} + +func NewServiceAccountBuilder(agent *instanav1.InstanaAgent) builder.ObjectBuilder { + return &serviceAccountBuilder{ + InstanaAgent: agent, + Helpers: helpers.NewHelpers(agent), + } +} diff --git a/pkg/k8s/object/builders/agent/serviceaccount/serviceaccount_test.go b/pkg/k8s/object/builders/agent/serviceaccount/serviceaccount_test.go new file mode 100644 index 00000000..8f0a6772 --- /dev/null +++ b/pkg/k8s/object/builders/agent/serviceaccount/serviceaccount_test.go @@ -0,0 +1,123 @@ +/* +(c) Copyright IBM Corp. 2024 +(c) Copyright Instana Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package serviceaccount + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" + gomock "go.uber.org/mock/gomock" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/rand" + "sigs.k8s.io/controller-runtime/pkg/client" + + instanav1 "github.com/instana/instana-agent-operator/api/v1" + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/constants" + "github.com/instana/instana-agent-operator/pkg/optional" + "github.com/instana/instana-agent-operator/pkg/pointer" +) + +func TestServiceAccountBuilder_IsNamespaced_ComponentName(t *testing.T) { + assertions := require.New(t) + + sb := NewServiceAccountBuilder(nil) + + assertions.True(sb.IsNamespaced()) + assertions.Equal(constants.ComponentInstanaAgent, sb.ComponentName()) +} + +func TestServiceAccountBuilder_Build(t *testing.T) { + for _, test := range []struct { + createServiceAccount *bool + }{ + { + createServiceAccount: pointer.To(true), + }, + { + createServiceAccount: pointer.To(false), + }, + { + createServiceAccount: nil, + }, + } { + t.Run( + fmt.Sprintf("%+v", test), func(t *testing.T) { + assertions := require.New(t) + ctrl := gomock.NewController(t) + + serviceAccountName := rand.String(10) + namespace := rand.String(10) + + const annotationKeyName = "instana.io/example" + annotationKeyValue := rand.String(10) + + agent := &instanav1.InstanaAgent{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + }, + Spec: instanav1.InstanaAgentSpec{ + ServiceAccountSpec: instanav1.ServiceAccountSpec{ + Create: instanav1.Create{ + Create: test.createServiceAccount, + }, + Annotations: map[string]string{ + annotationKeyName: annotationKeyValue, + }, + }, + }, + } + + expected := optional.Of[client.Object]( + &corev1.ServiceAccount{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "ServiceAccount", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: serviceAccountName, + Namespace: namespace, + Annotations: map[string]string{ + annotationKeyName: annotationKeyValue, + }, + }, + }, + ) + + helpers := NewMockHelpers(ctrl) + if pointer.DerefOrEmpty(test.createServiceAccount) { + helpers.EXPECT().ServiceAccountName().Return(serviceAccountName) + } + + sb := &serviceAccountBuilder{ + InstanaAgent: agent, + Helpers: helpers, + } + + actual := sb.Build() + + if pointer.DerefOrEmpty(test.createServiceAccount) { + assertions.Equal(expected, actual) + } else { + assertions.Empty(actual) + } + }, + ) + } +} diff --git a/pkg/k8s/object/builders/common/builder/builder.go b/pkg/k8s/object/builders/common/builder/builder.go new file mode 100644 index 00000000..7e9026df --- /dev/null +++ b/pkg/k8s/object/builders/common/builder/builder.go @@ -0,0 +1,46 @@ +package builder + +import ( + "sigs.k8s.io/controller-runtime/pkg/client" + + instanav1 "github.com/instana/instana-agent-operator/api/v1" + "github.com/instana/instana-agent-operator/pkg/k8s/object/transformations" + "github.com/instana/instana-agent-operator/pkg/optional" +) + +type OptionalObject = optional.Optional[client.Object] + +type ObjectBuilder interface { + Build() OptionalObject + ComponentName() string + IsNamespaced() bool +} + +type BuilderTransformer interface { + Apply(bldr ObjectBuilder) OptionalObject +} + +type builderTransformer struct { + transformations.Transformations +} + +func (b *builderTransformer) Apply(builder ObjectBuilder) optional.Optional[client.Object] { + switch opt := builder.Build(); opt.IsPresent() { + case true: + obj := opt.Get() + b.AddCommonLabels(obj, builder.ComponentName()) + if builder.IsNamespaced() { + b.AddOwnerReference(obj) + } + return optional.Of(obj) + default: + return opt + } + +} + +func NewBuilderTransformer(agent *instanav1.InstanaAgent) BuilderTransformer { + return &builderTransformer{ + Transformations: transformations.NewTransformations(agent), + } +} diff --git a/pkg/k8s/object/builders/common/builder/builder_mock_test.go b/pkg/k8s/object/builders/common/builder/builder_mock_test.go new file mode 100644 index 00000000..6053d4a4 --- /dev/null +++ b/pkg/k8s/object/builders/common/builder/builder_mock_test.go @@ -0,0 +1,138 @@ +// /* +// (c) Copyright IBM Corp. 2024 +// (c) Copyright Instana Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// */ +// + +// Code generated by MockGen. DO NOT EDIT. +package builder + +import ( + reflect "reflect" + + gomock "go.uber.org/mock/gomock" +) + +// MockObjectBuilder is a mock of ObjectBuilder interface. +type MockObjectBuilder struct { + ctrl *gomock.Controller + recorder *MockObjectBuilderMockRecorder +} + +// MockObjectBuilderMockRecorder is the mock recorder for MockObjectBuilder. +type MockObjectBuilderMockRecorder struct { + mock *MockObjectBuilder +} + +// NewMockObjectBuilder creates a new mock instance. +func NewMockObjectBuilder(ctrl *gomock.Controller) *MockObjectBuilder { + mock := &MockObjectBuilder{ctrl: ctrl} + mock.recorder = &MockObjectBuilderMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockObjectBuilder) EXPECT() *MockObjectBuilderMockRecorder { + return m.recorder +} + +// ISGOMOCK indicates that this struct is a gomock mock. +func (m *MockObjectBuilder) ISGOMOCK() struct{} { + return struct{}{} +} + +// Build mocks base method. +func (m *MockObjectBuilder) Build() OptionalObject { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Build") + ret0, _ := ret[0].(OptionalObject) + return ret0 +} + +// Build indicates an expected call of Build. +func (mr *MockObjectBuilderMockRecorder) Build() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Build", reflect.TypeOf((*MockObjectBuilder)(nil).Build)) +} + +// ComponentName mocks base method. +func (m *MockObjectBuilder) ComponentName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ComponentName") + ret0, _ := ret[0].(string) + return ret0 +} + +// ComponentName indicates an expected call of ComponentName. +func (mr *MockObjectBuilderMockRecorder) ComponentName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ComponentName", reflect.TypeOf((*MockObjectBuilder)(nil).ComponentName)) +} + +// IsNamespaced mocks base method. +func (m *MockObjectBuilder) IsNamespaced() bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "IsNamespaced") + ret0, _ := ret[0].(bool) + return ret0 +} + +// IsNamespaced indicates an expected call of IsNamespaced. +func (mr *MockObjectBuilderMockRecorder) IsNamespaced() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsNamespaced", reflect.TypeOf((*MockObjectBuilder)(nil).IsNamespaced)) +} + +// MockBuilderTransformer is a mock of BuilderTransformer interface. +type MockBuilderTransformer struct { + ctrl *gomock.Controller + recorder *MockBuilderTransformerMockRecorder +} + +// MockBuilderTransformerMockRecorder is the mock recorder for MockBuilderTransformer. +type MockBuilderTransformerMockRecorder struct { + mock *MockBuilderTransformer +} + +// NewMockBuilderTransformer creates a new mock instance. +func NewMockBuilderTransformer(ctrl *gomock.Controller) *MockBuilderTransformer { + mock := &MockBuilderTransformer{ctrl: ctrl} + mock.recorder = &MockBuilderTransformerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockBuilderTransformer) EXPECT() *MockBuilderTransformerMockRecorder { + return m.recorder +} + +// ISGOMOCK indicates that this struct is a gomock mock. +func (m *MockBuilderTransformer) ISGOMOCK() struct{} { + return struct{}{} +} + +// Apply mocks base method. +func (m *MockBuilderTransformer) Apply(bldr ObjectBuilder) OptionalObject { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Apply", bldr) + ret0, _ := ret[0].(OptionalObject) + return ret0 +} + +// Apply indicates an expected call of Apply. +func (mr *MockBuilderTransformerMockRecorder) Apply(bldr any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Apply", reflect.TypeOf((*MockBuilderTransformer)(nil).Apply), bldr) +} diff --git a/pkg/k8s/object/builders/common/builder/builder_test.go b/pkg/k8s/object/builders/common/builder/builder_test.go new file mode 100644 index 00000000..06c4ee69 --- /dev/null +++ b/pkg/k8s/object/builders/common/builder/builder_test.go @@ -0,0 +1,107 @@ +/* +(c) Copyright IBM Corp. 2024 +(c) Copyright Instana Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package builder + +import ( + "testing" + + "github.com/Masterminds/goutils" + "github.com/stretchr/testify/require" + gomock "go.uber.org/mock/gomock" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/instana/instana-agent-operator/pkg/optional" +) + +func newDummyObject() optional.Optional[client.Object] { + return optional.Of[client.Object](&unstructured.Unstructured{}) +} + +func TestBuilderTransformer_Apply(t *testing.T) { + for _, test := range []struct { + name string + expected func(builder *MockObjectBuilder, transformations *MockTransformations) optional.Optional[client.Object] + }{ + { + name: "empty_object", + expected: func( + builder *MockObjectBuilder, + transformations *MockTransformations, + ) optional.Optional[client.Object] { + builder.EXPECT().Build().Return(optional.Empty[client.Object]()) + + return optional.Empty[client.Object]() + }, + }, + { + name: "non_namespaced", + expected: func( + builder *MockObjectBuilder, + transformations *MockTransformations, + ) optional.Optional[client.Object] { + componentName, _ := goutils.RandomAlphabetic(10) + + builder.EXPECT().Build().Return(newDummyObject()) + builder.EXPECT().ComponentName().Return(componentName) + builder.EXPECT().IsNamespaced().Return(false) + + transformations.EXPECT().AddCommonLabels(gomock.Eq(newDummyObject().Get()), gomock.Eq(componentName)) + + return newDummyObject() + }, + }, + { + name: "namespaced", + expected: func( + builder *MockObjectBuilder, + transformations *MockTransformations, + ) optional.Optional[client.Object] { + componentName, _ := goutils.RandomAlphabetic(10) + + builder.EXPECT().Build().Return(newDummyObject()) + builder.EXPECT().ComponentName().Return(componentName) + builder.EXPECT().IsNamespaced().Return(true) + + transformations.EXPECT().AddCommonLabels(gomock.Eq(newDummyObject().Get()), gomock.Eq(componentName)) + transformations.EXPECT().AddOwnerReference(gomock.Eq(newDummyObject().Get())) + + return newDummyObject() + }, + }, + } { + t.Run( + test.name, func(t *testing.T) { + assertions := require.New(t) + ctrl := gomock.NewController(t) + + builder := NewMockObjectBuilder(ctrl) + transformations := NewMockTransformations(ctrl) + + expected := test.expected(builder, transformations) + + bt := &builderTransformer{ + Transformations: transformations, + } + + actual := bt.Apply(builder) + assertions.Equal(expected, actual) + }, + ) + } +} diff --git a/pkg/k8s/object/builders/common/builder/transformations_mock_test.go b/pkg/k8s/object/builders/common/builder/transformations_mock_test.go new file mode 100644 index 00000000..836dfbcd --- /dev/null +++ b/pkg/k8s/object/builders/common/builder/transformations_mock_test.go @@ -0,0 +1,94 @@ +// /* +// (c) Copyright IBM Corp. 2024 +// (c) Copyright Instana Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// */ +// + +// Code generated by MockGen. DO NOT EDIT. +package builder + +import ( + reflect "reflect" + + gomock "go.uber.org/mock/gomock" + labels "k8s.io/apimachinery/pkg/labels" + client "sigs.k8s.io/controller-runtime/pkg/client" +) + +// MockTransformations is a mock of Transformations interface. +type MockTransformations struct { + ctrl *gomock.Controller + recorder *MockTransformationsMockRecorder +} + +// MockTransformationsMockRecorder is the mock recorder for MockTransformations. +type MockTransformationsMockRecorder struct { + mock *MockTransformations +} + +// NewMockTransformations creates a new mock instance. +func NewMockTransformations(ctrl *gomock.Controller) *MockTransformations { + mock := &MockTransformations{ctrl: ctrl} + mock.recorder = &MockTransformationsMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockTransformations) EXPECT() *MockTransformationsMockRecorder { + return m.recorder +} + +// ISGOMOCK indicates that this struct is a gomock mock. +func (m *MockTransformations) ISGOMOCK() struct{} { + return struct{}{} +} + +// AddCommonLabels mocks base method. +func (m *MockTransformations) AddCommonLabels(obj client.Object, component string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "AddCommonLabels", obj, component) +} + +// AddCommonLabels indicates an expected call of AddCommonLabels. +func (mr *MockTransformationsMockRecorder) AddCommonLabels(obj, component any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddCommonLabels", reflect.TypeOf((*MockTransformations)(nil).AddCommonLabels), obj, component) +} + +// AddOwnerReference mocks base method. +func (m *MockTransformations) AddOwnerReference(obj client.Object) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "AddOwnerReference", obj) +} + +// AddOwnerReference indicates an expected call of AddOwnerReference. +func (mr *MockTransformationsMockRecorder) AddOwnerReference(obj any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddOwnerReference", reflect.TypeOf((*MockTransformations)(nil).AddOwnerReference), obj) +} + +// PreviousGenerationsSelector mocks base method. +func (m *MockTransformations) PreviousGenerationsSelector() labels.Selector { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PreviousGenerationsSelector") + ret0, _ := ret[0].(labels.Selector) + return ret0 +} + +// PreviousGenerationsSelector indicates an expected call of PreviousGenerationsSelector. +func (mr *MockTransformationsMockRecorder) PreviousGenerationsSelector() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PreviousGenerationsSelector", reflect.TypeOf((*MockTransformations)(nil).PreviousGenerationsSelector)) +} diff --git a/pkg/k8s/object/builders/common/constants/constants.go b/pkg/k8s/object/builders/common/constants/constants.go new file mode 100644 index 00000000..426d88fe --- /dev/null +++ b/pkg/k8s/object/builders/common/constants/constants.go @@ -0,0 +1,21 @@ +package constants + +// components +const ( + ComponentInstanaAgent = "instana-agent" + ComponentK8Sensor = "k8sensor" +) + +// labels + +const ( + LabelAgentMode = "instana/agent-mode" +) + +// keys + +const ( + AgentKey = "key" + DownloadKey = "downloadKey" + BackendKey = "backend" +) diff --git a/pkg/k8s/object/builders/common/env/env.go b/pkg/k8s/object/builders/common/env/env.go new file mode 100644 index 00000000..da93dd09 --- /dev/null +++ b/pkg/k8s/object/builders/common/env/env.go @@ -0,0 +1,20 @@ +package env + +import ( + "fmt" + + corev1 "k8s.io/api/core/v1" + + "github.com/instana/instana-agent-operator/pkg/optional" +) + +func fromCRField[T any](name string, val T) optional.Optional[corev1.EnvVar] { + return optional.Map( + optional.Of(val), func(v T) corev1.EnvVar { + return corev1.EnvVar{ + Name: name, + Value: fmt.Sprintf("%v", v), + } + }, + ) +} diff --git a/pkg/k8s/object/builders/common/env/env_builder.go b/pkg/k8s/object/builders/common/env/env_builder.go new file mode 100644 index 00000000..904d9267 --- /dev/null +++ b/pkg/k8s/object/builders/common/env/env_builder.go @@ -0,0 +1,153 @@ +package env + +import ( + "errors" + + corev1 "k8s.io/api/core/v1" + + instanav1 "github.com/instana/instana-agent-operator/api/v1" + "github.com/instana/instana-agent-operator/pkg/collections/list" + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/helpers" + "github.com/instana/instana-agent-operator/pkg/optional" +) + +type EnvVar int + +const ( + AgentModeEnv EnvVar = iota + ZoneNameEnv + ClusterNameEnv + AgentEndpointEnv + AgentEndpointPortEnv + MavenRepoURLEnv + MavenRepoFeaturesPath + MavenRepoSharedPath + ProxyHostEnv + ProxyPortEnv + ProxyProtocolEnv + ProxyUserEnv + ProxyPasswordEnv + ProxyUseDNSEnv + ListenAddressEnv + RedactK8sSecretsEnv + AgentZoneEnv + HTTPSProxyEnv + BackendURLEnv + NoProxyEnv + ConfigPathEnv + BackendEnv + InstanaAgentKeyEnv + AgentKeyEnv + DownloadKeyEnv + InstanaAgentPodNameEnv + PodNameEnv + PodIPEnv + PodUIDEnv + PodNamespaceEnv + K8sServiceDomainEnv +) + +type EnvBuilder interface { + Build(envVars ...EnvVar) []corev1.EnvVar +} + +type envBuilder struct { + agent *instanav1.InstanaAgent + zone *instanav1.Zone + helpers.Helpers +} + +// Mapping between EnvVar constants and the functions that build them must be included here +func (e *envBuilder) getBuilder(envVar EnvVar) func() optional.Optional[corev1.EnvVar] { + switch envVar { + case AgentModeEnv: + return e.agentModeEnv + case ZoneNameEnv: + return e.zoneNameEnv + case ClusterNameEnv: + return e.clusterNameEnv + case AgentEndpointEnv: + return e.agentEndpointEnv + case AgentEndpointPortEnv: + return e.agentEndpointPortEnv + case MavenRepoURLEnv: + return e.mavenRepoURLEnv + case MavenRepoFeaturesPath: + return e.mavenRepoFeaturesPath + case MavenRepoSharedPath: + return e.mavenRepoSharedPath + case ProxyHostEnv: + return e.proxyHostEnv + case ProxyPortEnv: + return e.proxyPortEnv + case ProxyProtocolEnv: + return e.proxyProtocolEnv + case ProxyUserEnv: + return e.proxyUserEnv + case ProxyPasswordEnv: + return e.proxyPasswordEnv + case ProxyUseDNSEnv: + return e.proxyUseDNSEnv + case ListenAddressEnv: + return e.listenAddressEnv + case RedactK8sSecretsEnv: + return e.redactK8sSecretsEnv + case AgentZoneEnv: + return e.agentZoneEnv + case HTTPSProxyEnv: + return e.httpsProxyEnv + case BackendURLEnv: + return e.backendURLEnv + case NoProxyEnv: + return e.noProxyEnv + case ConfigPathEnv: + return e.configPathEnv + case BackendEnv: + return e.backendEnv + case InstanaAgentKeyEnv: + return e.instanaAgentKeyEnv + case AgentKeyEnv: + return e.agentKeyEnv + case DownloadKeyEnv: + return e.downloadKeyEnv + case InstanaAgentPodNameEnv: + return e.instanaAgentPodNameEnv + case PodNameEnv: + return e.podNameEnv + case PodIPEnv: + return e.podIPEnv + case PodUIDEnv: + return e.podUIDEnv + case PodNamespaceEnv: + return e.podNamespaceEnv + case K8sServiceDomainEnv: + return e.k8sServiceDomainEnv + default: + panic(errors.New("unknown environment variable requested")) + } +} + +func (e *envBuilder) Build(envVars ...EnvVar) []corev1.EnvVar { + userProvided := e.userProvidedEnv() + + builtFromSpec := list.NewListMapTo[EnvVar, optional.Optional[corev1.EnvVar]]().MapTo( + envVars, + func(envVar EnvVar) optional.Optional[corev1.EnvVar] { + return e.getBuilder(envVar)() + }, + ) + + return optional.NewNonEmptyOptionalMapper[corev1.EnvVar]().AllNonEmpty(append(userProvided, builtFromSpec...)) +} + +func NewEnvBuilder(agent *instanav1.InstanaAgent) EnvBuilder { + return NewEnvBuilderWithZoneInfo(agent, nil) +} + +func NewEnvBuilderWithZoneInfo(agent *instanav1.InstanaAgent, zone *instanav1.Zone) EnvBuilder { + return &envBuilder{ + agent: agent, + zone: zone, + Helpers: helpers.NewHelpers(agent), + } +} diff --git a/pkg/k8s/object/builders/common/env/env_builder_test.go b/pkg/k8s/object/builders/common/env/env_builder_test.go new file mode 100644 index 00000000..fbe624f0 --- /dev/null +++ b/pkg/k8s/object/builders/common/env/env_builder_test.go @@ -0,0 +1,118 @@ +package env + +import ( + "reflect" + "testing" + + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + + instanav1 "github.com/instana/instana-agent-operator/api/v1" + "github.com/instana/instana-agent-operator/pkg/collections/list" +) + +func rangeUntil(n int) []int { + res := make([]int, 0, n) + + for i := 0; i < n; i++ { + res = append(res, i) + } + + return res +} + +func assertAllElementsUnique[T comparable](assertions *require.Assertions, list []T) { + m := make(map[T]bool, len(list)) + + for _, element := range list { + m[element] = true + } + + assertions.Equal(len(list), len(m)) +} + +func TestEnvBuilder_getBuilder(t *testing.T) { + const numDefinedEnvVars = 31 + + t.Run( + "each_defined_var_has_unique_function", func(t *testing.T) { + assertions := require.New(t) + + eb := &envBuilder{} + + allBuilders := list.NewListMapTo[int, uintptr]().MapTo( + rangeUntil(numDefinedEnvVars), + func(envVar int) uintptr { + method := eb.getBuilder(EnvVar(envVar)) + + return reflect.ValueOf(method).Pointer() + }, + ) + + assertions.Len(allBuilders, numDefinedEnvVars) + assertAllElementsUnique(assertions, allBuilders) + }, + ) + + t.Run( + "panics_above_defined_limit", func(t *testing.T) { + assertions := require.New(t) + + eb := &envBuilder{} + + assertions.PanicsWithError( + "unknown environment variable requested", func() { + eb.getBuilder(numDefinedEnvVars) + }, + ) + }, + ) +} + +func TestEnvBuilder_Build(t *testing.T) { + assertions := require.New(t) + + expected := []corev1.EnvVar{ + { + Name: "MY_VAR1", + Value: "MY_VAL1", + }, + { + Name: "MY_VAR2", + Value: "MY_VAL2", + }, + { + Name: "INSTANA_AGENT_POD_NAME", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + FieldPath: "metadata.name", + }, + }, + }, + { + Name: "POD_IP", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + FieldPath: "status.podIP", + }, + }, + }, + } + + eb := NewEnvBuilder( + &instanav1.InstanaAgent{ + Spec: instanav1.InstanaAgentSpec{ + Agent: instanav1.BaseAgentSpec{ + Env: map[string]string{ + "MY_VAR1": "MY_VAL1", + "MY_VAR2": "MY_VAL2", + }, + }, + }, + }, + ) + + actual := eb.Build(InstanaAgentPodNameEnv, PodIPEnv) + + assertions.ElementsMatch(expected, actual) +} diff --git a/pkg/k8s/object/builders/common/env/env_test.go b/pkg/k8s/object/builders/common/env/env_test.go new file mode 100644 index 00000000..9e1e6e68 --- /dev/null +++ b/pkg/k8s/object/builders/common/env/env_test.go @@ -0,0 +1,37 @@ +package env + +import ( + "testing" + + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + + "github.com/instana/instana-agent-operator/pkg/optional" +) + +func Test_fromCRField(t *testing.T) { + t.Run( + "when_empty", func(t *testing.T) { + assertions := require.New(t) + actual := fromCRField("MY_ENV_FIELD_1", "") + + assertions.Empty(actual) + }, + ) + t.Run( + "with_value", func(t *testing.T) { + assertions := require.New(t) + actual := fromCRField("MY_ENV_FIELD_1", "ewoihsdoighds") + + assertions.Equal( + optional.Of( + corev1.EnvVar{ + Name: "MY_ENV_FIELD_1", + Value: "ewoihsdoighds", + }, + ), + actual, + ) + }, + ) +} diff --git a/pkg/k8s/object/builders/common/env/helpers_mock_test.go b/pkg/k8s/object/builders/common/env/helpers_mock_test.go new file mode 100644 index 00000000..d4621889 --- /dev/null +++ b/pkg/k8s/object/builders/common/env/helpers_mock_test.go @@ -0,0 +1,181 @@ +// /* +// (c) Copyright IBM Corp. 2024 +// (c) Copyright Instana Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// */ +// + +// Code generated by MockGen. DO NOT EDIT. +package env + +import ( + reflect "reflect" + + gomock "go.uber.org/mock/gomock" + v1 "k8s.io/api/core/v1" +) + +// MockHelpers is a mock of Helpers interface. +type MockHelpers struct { + ctrl *gomock.Controller + recorder *MockHelpersMockRecorder +} + +// MockHelpersMockRecorder is the mock recorder for MockHelpers. +type MockHelpersMockRecorder struct { + mock *MockHelpers +} + +// NewMockHelpers creates a new mock instance. +func NewMockHelpers(ctrl *gomock.Controller) *MockHelpers { + mock := &MockHelpers{ctrl: ctrl} + mock.recorder = &MockHelpersMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockHelpers) EXPECT() *MockHelpersMockRecorder { + return m.recorder +} + +// ISGOMOCK indicates that this struct is a gomock mock. +func (m *MockHelpers) ISGOMOCK() struct{} { + return struct{}{} +} + +// ContainersSecretName mocks base method. +func (m *MockHelpers) ContainersSecretName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ContainersSecretName") + ret0, _ := ret[0].(string) + return ret0 +} + +// ContainersSecretName indicates an expected call of ContainersSecretName. +func (mr *MockHelpersMockRecorder) ContainersSecretName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ContainersSecretName", reflect.TypeOf((*MockHelpers)(nil).ContainersSecretName)) +} + +// HeadlessServiceName mocks base method. +func (m *MockHelpers) HeadlessServiceName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "HeadlessServiceName") + ret0, _ := ret[0].(string) + return ret0 +} + +// HeadlessServiceName indicates an expected call of HeadlessServiceName. +func (mr *MockHelpersMockRecorder) HeadlessServiceName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HeadlessServiceName", reflect.TypeOf((*MockHelpers)(nil).HeadlessServiceName)) +} + +// ImagePullSecrets mocks base method. +func (m *MockHelpers) ImagePullSecrets() []v1.LocalObjectReference { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ImagePullSecrets") + ret0, _ := ret[0].([]v1.LocalObjectReference) + return ret0 +} + +// ImagePullSecrets indicates an expected call of ImagePullSecrets. +func (mr *MockHelpersMockRecorder) ImagePullSecrets() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ImagePullSecrets", reflect.TypeOf((*MockHelpers)(nil).ImagePullSecrets)) +} + +// K8sSensorResourcesName mocks base method. +func (m *MockHelpers) K8sSensorResourcesName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "K8sSensorResourcesName") + ret0, _ := ret[0].(string) + return ret0 +} + +// K8sSensorResourcesName indicates an expected call of K8sSensorResourcesName. +func (mr *MockHelpersMockRecorder) K8sSensorResourcesName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "K8sSensorResourcesName", reflect.TypeOf((*MockHelpers)(nil).K8sSensorResourcesName)) +} + +// KeysSecretName mocks base method. +func (m *MockHelpers) KeysSecretName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "KeysSecretName") + ret0, _ := ret[0].(string) + return ret0 +} + +// KeysSecretName indicates an expected call of KeysSecretName. +func (mr *MockHelpersMockRecorder) KeysSecretName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "KeysSecretName", reflect.TypeOf((*MockHelpers)(nil).KeysSecretName)) +} + +// ServiceAccountName mocks base method. +func (m *MockHelpers) ServiceAccountName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ServiceAccountName") + ret0, _ := ret[0].(string) + return ret0 +} + +// ServiceAccountName indicates an expected call of ServiceAccountName. +func (mr *MockHelpersMockRecorder) ServiceAccountName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ServiceAccountName", reflect.TypeOf((*MockHelpers)(nil).ServiceAccountName)) +} + +// TLSIsEnabled mocks base method. +func (m *MockHelpers) TLSIsEnabled() bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "TLSIsEnabled") + ret0, _ := ret[0].(bool) + return ret0 +} + +// TLSIsEnabled indicates an expected call of TLSIsEnabled. +func (mr *MockHelpersMockRecorder) TLSIsEnabled() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TLSIsEnabled", reflect.TypeOf((*MockHelpers)(nil).TLSIsEnabled)) +} + +// TLSSecretName mocks base method. +func (m *MockHelpers) TLSSecretName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "TLSSecretName") + ret0, _ := ret[0].(string) + return ret0 +} + +// TLSSecretName indicates an expected call of TLSSecretName. +func (mr *MockHelpersMockRecorder) TLSSecretName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TLSSecretName", reflect.TypeOf((*MockHelpers)(nil).TLSSecretName)) +} + +// UseContainersSecret mocks base method. +func (m *MockHelpers) UseContainersSecret() bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UseContainersSecret") + ret0, _ := ret[0].(bool) + return ret0 +} + +// UseContainersSecret indicates an expected call of UseContainersSecret. +func (mr *MockHelpersMockRecorder) UseContainersSecret() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UseContainersSecret", reflect.TypeOf((*MockHelpers)(nil).UseContainersSecret)) +} diff --git a/pkg/k8s/object/builders/common/env/vars.go b/pkg/k8s/object/builders/common/env/vars.go new file mode 100644 index 00000000..59ebb377 --- /dev/null +++ b/pkg/k8s/object/builders/common/env/vars.go @@ -0,0 +1,325 @@ +package env + +import ( + "fmt" + "strings" + + corev1 "k8s.io/api/core/v1" + + _map "github.com/instana/instana-agent-operator/pkg/collections/map" + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/constants" + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/volume" + "github.com/instana/instana-agent-operator/pkg/optional" + "github.com/instana/instana-agent-operator/pkg/pointer" +) + +// Directly From CR + +func (e *envBuilder) agentModeEnv() optional.Optional[corev1.EnvVar] { + const envVarName = "INSTANA_AGENT_MODE" + if e.zone != nil { + return optional.Of( + corev1.EnvVar{ + Name: envVarName, + Value: string(e.zone.Mode), + }, + ) + } + return fromCRField(envVarName, e.agent.Spec.Agent.Mode) +} + +func (e *envBuilder) zoneNameEnv() optional.Optional[corev1.EnvVar] { + const envVarName = "INSTANA_ZONE" + if e.zone != nil { + return optional.Of( + corev1.EnvVar{ + Name: envVarName, + Value: e.zone.Name.Name, + }, + ) + } + return fromCRField(envVarName, e.agent.Spec.Zone.Name) +} + +func (e *envBuilder) clusterNameEnv() optional.Optional[corev1.EnvVar] { + return fromCRField("INSTANA_KUBERNETES_CLUSTER_NAME", e.agent.Spec.Cluster.Name) +} + +func (e *envBuilder) agentEndpointEnv() optional.Optional[corev1.EnvVar] { + return fromCRField("INSTANA_AGENT_ENDPOINT", e.agent.Spec.Agent.EndpointHost) +} + +func (e *envBuilder) agentEndpointPortEnv() optional.Optional[corev1.EnvVar] { + return fromCRField("INSTANA_AGENT_ENDPOINT_PORT", e.agent.Spec.Agent.EndpointPort) +} + +func (e *envBuilder) mavenRepoURLEnv() optional.Optional[corev1.EnvVar] { + return fromCRField("INSTANA_MVN_REPOSITORY_URL", e.agent.Spec.Agent.MvnRepoUrl) +} + +func (e *envBuilder) mavenRepoFeaturesPath() optional.Optional[corev1.EnvVar] { + return fromCRField("INSTANA_MVN_REPOSITORY_FEATURES_PATH", e.agent.Spec.Agent.MvnRepoFeaturesPath) +} + +func (e *envBuilder) mavenRepoSharedPath() optional.Optional[corev1.EnvVar] { + return fromCRField("INSTANA_MVN_REPOSITORY_SHARED_PATH", e.agent.Spec.Agent.MvnRepoSharedPath) +} + +func (e *envBuilder) proxyHostEnv() optional.Optional[corev1.EnvVar] { + return fromCRField("INSTANA_AGENT_PROXY_HOST", e.agent.Spec.Agent.ProxyHost) +} + +func (e *envBuilder) proxyPortEnv() optional.Optional[corev1.EnvVar] { + return fromCRField("INSTANA_AGENT_PROXY_PORT", e.agent.Spec.Agent.ProxyPort) +} + +func (e *envBuilder) proxyProtocolEnv() optional.Optional[corev1.EnvVar] { + return fromCRField("INSTANA_AGENT_PROXY_PROTOCOL", e.agent.Spec.Agent.ProxyProtocol) +} + +func (e *envBuilder) proxyUserEnv() optional.Optional[corev1.EnvVar] { + return fromCRField("INSTANA_AGENT_PROXY_USER", e.agent.Spec.Agent.ProxyUser) +} + +func (e *envBuilder) proxyPasswordEnv() optional.Optional[corev1.EnvVar] { + return fromCRField("INSTANA_AGENT_PROXY_PASSWORD", e.agent.Spec.Agent.ProxyPassword) +} + +func (e *envBuilder) proxyUseDNSEnv() optional.Optional[corev1.EnvVar] { + return fromCRField("INSTANA_AGENT_PROXY_USE_DNS", e.agent.Spec.Agent.ProxyUseDNS) +} + +func (e *envBuilder) listenAddressEnv() optional.Optional[corev1.EnvVar] { + return fromCRField("INSTANA_AGENT_HTTP_LISTEN", e.agent.Spec.Agent.ListenAddress) +} + +func (e *envBuilder) redactK8sSecretsEnv() optional.Optional[corev1.EnvVar] { + return fromCRField("INSTANA_KUBERNETES_REDACT_SECRETS", e.agent.Spec.Agent.RedactKubernetesSecrets) +} + +func (e *envBuilder) agentZoneEnv() optional.Optional[corev1.EnvVar] { + return optional.Of( + corev1.EnvVar{ + Name: "AGENT_ZONE", + Value: optional.Of(e.agent.Spec.Cluster.Name).GetOrDefault(e.agent.Spec.Zone.Name), + }, + ) +} + +func (e *envBuilder) proxyUserPass() string { + return optional.Map[string, string]( + optional.Of(e.agent.Spec.Agent.ProxyUser), + func(proxyUser string) string { + return optional.Map[string, string]( + optional.Of(e.agent.Spec.Agent.ProxyPassword), + func(proxyPassword string) string { + return fmt.Sprintf("%s:%s@", proxyUser, proxyPassword) + }, + ).Get() + }, + ).Get() +} + +func (e *envBuilder) httpsProxyEnv() optional.Optional[corev1.EnvVar] { + return optional.Map[string, corev1.EnvVar]( + optional.Of(e.agent.Spec.Agent.ProxyHost), + func(proxyHost string) corev1.EnvVar { + return corev1.EnvVar{ + Name: "HTTPS_PROXY", + Value: fmt.Sprintf( + "http://%s%s:%s", + e.proxyUserPass(), + proxyHost, + optional.Of(e.agent.Spec.Agent.ProxyPort).GetOrDefault("80"), + ), + } + }, + ) +} + +// Static + +func (e *envBuilder) backendURLEnv() optional.Optional[corev1.EnvVar] { + return optional.Of( + corev1.EnvVar{ + Name: "BACKEND_URL", + Value: "https://$(BACKEND)", + }, + ) +} + +func (e *envBuilder) noProxyEnv() optional.Optional[corev1.EnvVar] { + return optional.Map[string, corev1.EnvVar]( + optional.Of(e.agent.Spec.Agent.ProxyHost), + func(proxyHost string) corev1.EnvVar { + return corev1.EnvVar{ + Name: "NO_PROXY", + Value: "kubernetes.default.svc", + } + }, + ) +} + +func (e *envBuilder) configPathEnv() optional.Optional[corev1.EnvVar] { + return optional.Of( + corev1.EnvVar{ + Name: "CONFIG_PATH", + Value: volume.InstanaConfigDirectory, + }, + ) +} + +// From a ConfigMap + +func (e *envBuilder) backendEnv() optional.Optional[corev1.EnvVar] { + return optional.Of( + corev1.EnvVar{ + Name: "BACKEND", + ValueFrom: &corev1.EnvVarSource{ + ConfigMapKeyRef: &corev1.ConfigMapKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: e.K8sSensorResourcesName(), + }, + Key: constants.BackendKey, + }, + }, + }, + ) +} + +// From a Secret + +func (e *envBuilder) agentKeyHelper(name string) optional.Optional[corev1.EnvVar] { + return optional.Of( + corev1.EnvVar{ + Name: name, + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: e.KeysSecretName(), + }, + Key: constants.AgentKey, + }, + }, + }, + ) +} + +func (e *envBuilder) instanaAgentKeyEnv() optional.Optional[corev1.EnvVar] { + return e.agentKeyHelper("INSTANA_AGENT_KEY") +} + +func (e *envBuilder) agentKeyEnv() optional.Optional[corev1.EnvVar] { + return e.agentKeyHelper("AGENT_KEY") +} + +func (e *envBuilder) downloadKeyEnv() optional.Optional[corev1.EnvVar] { + return optional.Of( + corev1.EnvVar{ + Name: "INSTANA_DOWNLOAD_KEY", + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: e.KeysSecretName(), + }, + Key: constants.DownloadKey, + Optional: pointer.To(true), + }, + }, + }, + ) +} + +// From Pod Reference + +func (e *envBuilder) instanaAgentPodNameEnv() optional.Optional[corev1.EnvVar] { + return optional.Of( + corev1.EnvVar{ + Name: "INSTANA_AGENT_POD_NAME", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + FieldPath: "metadata.name", + }, + }, + }, + ) +} + +func (e *envBuilder) podNameEnv() optional.Optional[corev1.EnvVar] { + return optional.Of( + corev1.EnvVar{ + Name: "POD_NAME", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + FieldPath: "metadata.name", + }, + }, + }, + ) +} + +func (e *envBuilder) podIPEnv() optional.Optional[corev1.EnvVar] { + return optional.Of( + corev1.EnvVar{ + Name: "POD_IP", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + FieldPath: "status.podIP", + }, + }, + }, + ) +} + +func (e *envBuilder) podUIDEnv() optional.Optional[corev1.EnvVar] { + return optional.Of( + corev1.EnvVar{ + Name: "POD_UID", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + FieldPath: "metadata.uid", + }, + }, + }, + ) +} + +func (e *envBuilder) podNamespaceEnv() optional.Optional[corev1.EnvVar] { + return optional.Of( + corev1.EnvVar{ + Name: "POD_NAMESPACE", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + FieldPath: "metadata.namespace", + }, + }, + }, + ) +} + +// Referencing Another Object Created by the Operator + +func (e *envBuilder) k8sServiceDomainEnv() optional.Optional[corev1.EnvVar] { + return optional.Of( + corev1.EnvVar{ + Name: "K8S_SERVICE_DOMAIN", + Value: strings.Join([]string{e.HeadlessServiceName(), e.agent.Namespace, "svc"}, "."), + }, + ) +} + +// From user-provided in CR + +func (e *envBuilder) userProvidedEnv() []optional.Optional[corev1.EnvVar] { + return _map.NewMapConverter[string, string, optional.Optional[corev1.EnvVar]](). + ToList( + e.agent.Spec.Agent.Env, func(name string, value string) optional.Optional[corev1.EnvVar] { + return optional.Of( + corev1.EnvVar{ + Name: name, + Value: value, + }, + ) + }, + ) +} diff --git a/pkg/k8s/object/builders/common/env/vars_test.go b/pkg/k8s/object/builders/common/env/vars_test.go new file mode 100644 index 00000000..02578773 --- /dev/null +++ b/pkg/k8s/object/builders/common/env/vars_test.go @@ -0,0 +1,927 @@ +/* +(c) Copyright IBM Corp. 2024 +(c) Copyright Instana Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package env + +import ( + "strconv" + "testing" + + "github.com/stretchr/testify/require" + gomock "go.uber.org/mock/gomock" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/rand" + + instanav1 "github.com/instana/instana-agent-operator/api/v1" + "github.com/instana/instana-agent-operator/pkg/collections/list" + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/constants" + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/volume" + "github.com/instana/instana-agent-operator/pkg/optional" + "github.com/instana/instana-agent-operator/pkg/pointer" +) + +func randString() string { + return rand.String(rand.IntnRange(1, 15)) +} + +type varMethodTest struct { + name string + getMethod func(builder *envBuilder) func() optional.Optional[corev1.EnvVar] + agent *instanav1.InstanaAgent + helpersExpect func(hlprs *MockHelpers) + expected optional.Optional[corev1.EnvVar] +} + +func testVarMethod(t *testing.T, tests []varMethodTest) { + for _, test := range tests { + t.Run( + test.name, func(t *testing.T) { + assertions := require.New(t) + ctrl := gomock.NewController(t) + + hlprs := NewMockHelpers(ctrl) + if helpersExpect := test.helpersExpect; helpersExpect != nil { + helpersExpect(hlprs) + } + + builder := &envBuilder{ + agent: test.agent, + Helpers: hlprs, + } + method := test.getMethod(builder) + actual := method() + + assertions.Equal(test.expected, actual) + }, + ) + } +} + +type crFieldVarTest struct { + t *testing.T + getMethod func(builder *envBuilder) func() optional.Optional[corev1.EnvVar] + expectedName string + expectedValue string + agentSpec instanav1.InstanaAgentSpec +} + +func testFromCRField(test *crFieldVarTest) { + testVarMethod( + test.t, []varMethodTest{ + { + name: "not_provided", + getMethod: test.getMethod, + agent: &instanav1.InstanaAgent{}, + expected: optional.Empty[corev1.EnvVar](), + }, + { + name: "provided", + getMethod: test.getMethod, + agent: &instanav1.InstanaAgent{Spec: test.agentSpec}, + expected: optional.Of( + corev1.EnvVar{ + Name: test.expectedName, + Value: test.expectedValue, + }, + ), + }, + }, + ) +} + +func TestEnvBuilder_agentModeEnv(t *testing.T) { + const expectedValue = instanav1.KUBERNETES + + testFromCRField( + &crFieldVarTest{ + t: t, + getMethod: func(builder *envBuilder) func() optional.Optional[corev1.EnvVar] { + return builder.agentModeEnv + }, + expectedName: "INSTANA_AGENT_MODE", + expectedValue: string(expectedValue), + agentSpec: instanav1.InstanaAgentSpec{ + Agent: instanav1.BaseAgentSpec{ + Mode: expectedValue, + }, + }, + }, + ) +} + +func TestEnvBuilder_zoneNameEnv(t *testing.T) { + const expectedValue = "some-zone" + + testFromCRField( + &crFieldVarTest{ + t: t, + getMethod: func(builder *envBuilder) func() optional.Optional[corev1.EnvVar] { + return builder.zoneNameEnv + }, + expectedName: "INSTANA_ZONE", + expectedValue: expectedValue, + agentSpec: instanav1.InstanaAgentSpec{ + Zone: instanav1.Name{ + Name: expectedValue, + }, + }, + }, + ) +} + +func TestEnvBuilder_clusterNameEnv(t *testing.T) { + const expectedValue = "some-cluster" + + testFromCRField( + &crFieldVarTest{ + t: t, + getMethod: func(builder *envBuilder) func() optional.Optional[corev1.EnvVar] { + return builder.clusterNameEnv + }, + expectedName: "INSTANA_KUBERNETES_CLUSTER_NAME", + expectedValue: expectedValue, + agentSpec: instanav1.InstanaAgentSpec{ + Cluster: instanav1.Name{ + Name: expectedValue, + }, + }, + }, + ) +} + +func TestEnvBuilder_agentEndpointEnv(t *testing.T) { + const expectedValue = "some-agent-endpoint" + + testFromCRField( + &crFieldVarTest{ + t: t, + getMethod: func(builder *envBuilder) func() optional.Optional[corev1.EnvVar] { + return builder.agentEndpointEnv + }, + expectedName: "INSTANA_AGENT_ENDPOINT", + expectedValue: expectedValue, + agentSpec: instanav1.InstanaAgentSpec{ + Agent: instanav1.BaseAgentSpec{ + EndpointHost: expectedValue, + }, + }, + }, + ) +} + +func TestEnvBuilder_agentEndpointPortEnv(t *testing.T) { + const expectedValue = "12345" + + testFromCRField( + &crFieldVarTest{ + t: t, + getMethod: func(builder *envBuilder) func() optional.Optional[corev1.EnvVar] { + return builder.agentEndpointPortEnv + }, + expectedName: "INSTANA_AGENT_ENDPOINT_PORT", + expectedValue: expectedValue, + agentSpec: instanav1.InstanaAgentSpec{ + Agent: instanav1.BaseAgentSpec{ + EndpointPort: expectedValue, + }, + }, + }, + ) +} + +func TestEnvBuilder_mavenRepoURLEnv(t *testing.T) { + const expectedValue = "https://repo.maven.apache.org/maven2" + + testFromCRField( + &crFieldVarTest{ + t: t, + getMethod: func(builder *envBuilder) func() optional.Optional[corev1.EnvVar] { + return builder.mavenRepoURLEnv + }, + expectedName: "INSTANA_MVN_REPOSITORY_URL", + expectedValue: expectedValue, + agentSpec: instanav1.InstanaAgentSpec{ + Agent: instanav1.BaseAgentSpec{ + MvnRepoUrl: expectedValue, + }, + }, + }, + ) +} + +func TestEnvBuilder_mavenRepoFeaturesPath(t *testing.T) { + const expectedValue = "/I/am/a/path" + + testFromCRField( + &crFieldVarTest{ + t: t, + getMethod: func(builder *envBuilder) func() optional.Optional[corev1.EnvVar] { + return builder.mavenRepoFeaturesPath + }, + expectedName: "INSTANA_MVN_REPOSITORY_FEATURES_PATH", + expectedValue: expectedValue, + agentSpec: instanav1.InstanaAgentSpec{ + Agent: instanav1.BaseAgentSpec{ + MvnRepoFeaturesPath: expectedValue, + }, + }, + }, + ) +} + +func TestEnvBuilder_mavenRepoSharedPath(t *testing.T) { + const expectedValue = "/I/am/a/path" + + testFromCRField( + &crFieldVarTest{ + t: t, + getMethod: func(builder *envBuilder) func() optional.Optional[corev1.EnvVar] { + return builder.mavenRepoSharedPath + }, + expectedName: "INSTANA_MVN_REPOSITORY_SHARED_PATH", + expectedValue: expectedValue, + agentSpec: instanav1.InstanaAgentSpec{ + Agent: instanav1.BaseAgentSpec{ + MvnRepoSharedPath: expectedValue, + }, + }, + }, + ) +} + +func TestEnvBuilder_proxyHostEnv(t *testing.T) { + const expectedValue = "some-proxy-host" + + testFromCRField( + &crFieldVarTest{ + t: t, + getMethod: func(builder *envBuilder) func() optional.Optional[corev1.EnvVar] { + return builder.proxyHostEnv + }, + expectedName: "INSTANA_AGENT_PROXY_HOST", + expectedValue: expectedValue, + agentSpec: instanav1.InstanaAgentSpec{ + Agent: instanav1.BaseAgentSpec{ + ProxyHost: expectedValue, + }, + }, + }, + ) +} + +func TestEnvBuilder_proxyPortEnv(t *testing.T) { + const expectedValue = "8888" + + testFromCRField( + &crFieldVarTest{ + t: t, + getMethod: func(builder *envBuilder) func() optional.Optional[corev1.EnvVar] { + return builder.proxyPortEnv + }, + expectedName: "INSTANA_AGENT_PROXY_PORT", + expectedValue: expectedValue, + agentSpec: instanav1.InstanaAgentSpec{ + Agent: instanav1.BaseAgentSpec{ + ProxyPort: expectedValue, + }, + }, + }, + ) +} + +func TestEnvBuilder_proxyProtocolEnv(t *testing.T) { + const expectedValue = "http" + + testFromCRField( + &crFieldVarTest{ + t: t, + getMethod: func(builder *envBuilder) func() optional.Optional[corev1.EnvVar] { + return builder.proxyProtocolEnv + }, + expectedName: "INSTANA_AGENT_PROXY_PROTOCOL", + expectedValue: expectedValue, + agentSpec: instanav1.InstanaAgentSpec{ + Agent: instanav1.BaseAgentSpec{ + ProxyProtocol: expectedValue, + }, + }, + }, + ) +} + +func TestEnvBuilder_proxyUserEnv(t *testing.T) { + const expectedValue = "some-proxy-user" + + testFromCRField( + &crFieldVarTest{ + t: t, + getMethod: func(builder *envBuilder) func() optional.Optional[corev1.EnvVar] { + return builder.proxyUserEnv + }, + expectedName: "INSTANA_AGENT_PROXY_USER", + expectedValue: expectedValue, + agentSpec: instanav1.InstanaAgentSpec{ + Agent: instanav1.BaseAgentSpec{ + ProxyUser: expectedValue, + }, + }, + }, + ) +} + +func TestEnvBuilder_proxyPasswordEnv(t *testing.T) { + const expectedValue = "some-proxy-password" + + testFromCRField( + &crFieldVarTest{ + t: t, + getMethod: func(builder *envBuilder) func() optional.Optional[corev1.EnvVar] { + return builder.proxyPasswordEnv + }, + expectedName: "INSTANA_AGENT_PROXY_PASSWORD", + expectedValue: expectedValue, + agentSpec: instanav1.InstanaAgentSpec{ + Agent: instanav1.BaseAgentSpec{ + ProxyPassword: expectedValue, + }, + }, + }, + ) +} + +func TestEnvBuilder_proxyUseDNSEnv(t *testing.T) { + const expectedValue = true + + testFromCRField( + &crFieldVarTest{ + t: t, + getMethod: func(builder *envBuilder) func() optional.Optional[corev1.EnvVar] { + return builder.proxyUseDNSEnv + }, + expectedName: "INSTANA_AGENT_PROXY_USE_DNS", + expectedValue: strconv.FormatBool(expectedValue), + agentSpec: instanav1.InstanaAgentSpec{ + Agent: instanav1.BaseAgentSpec{ + ProxyUseDNS: expectedValue, + }, + }, + }, + ) +} + +func TestEnvBuilder_listenAddressEnv(t *testing.T) { + const expectedValue = "0.0.0.0:42699" + + testFromCRField( + &crFieldVarTest{ + t: t, + getMethod: func(builder *envBuilder) func() optional.Optional[corev1.EnvVar] { + return builder.listenAddressEnv + }, + expectedName: "INSTANA_AGENT_HTTP_LISTEN", + expectedValue: expectedValue, + agentSpec: instanav1.InstanaAgentSpec{ + Agent: instanav1.BaseAgentSpec{ + ListenAddress: expectedValue, + }, + }, + }, + ) +} + +func TestEnvBuilder_redactK8sSecretsEnv(t *testing.T) { + const expectedValue = "true" + + testFromCRField( + &crFieldVarTest{ + t: t, + getMethod: func(builder *envBuilder) func() optional.Optional[corev1.EnvVar] { + return builder.redactK8sSecretsEnv + }, + expectedName: "INSTANA_KUBERNETES_REDACT_SECRETS", + expectedValue: expectedValue, + agentSpec: instanav1.InstanaAgentSpec{ + Agent: instanav1.BaseAgentSpec{ + RedactKubernetesSecrets: expectedValue, + }, + }, + }, + ) +} + +type fromSecretTest struct { + t *testing.T + expectedSecretName string + expected optional.Optional[corev1.EnvVar] + getMethod func(builder *envBuilder) func() optional.Optional[corev1.EnvVar] +} + +func testFromSecretMethod(test *fromSecretTest) { + testVarMethod( + test.t, []varMethodTest{ + { + name: "build", + getMethod: test.getMethod, + agent: nil, + helpersExpect: func(hlprs *MockHelpers) { + hlprs.EXPECT().KeysSecretName().Return(test.expectedSecretName) + }, + expected: test.expected, + }, + }, + ) +} + +func TestEnvBuilder_agentZone(t *testing.T) { + clusterName := randString() + zoneName := randString() + + agentZoneMethod := func(builder *envBuilder) func() optional.Optional[corev1.EnvVar] { + return builder.agentZoneEnv + } + + testVarMethod( + t, []varMethodTest{ + { + name: "clusterName_is_set", + getMethod: agentZoneMethod, + helpersExpect: func(hlprs *MockHelpers) {}, + agent: &instanav1.InstanaAgent{ + Spec: instanav1.InstanaAgentSpec{ + Cluster: instanav1.Name{ + Name: clusterName, + }, + Zone: instanav1.Name{ + Name: zoneName, + }, + }, + }, + expected: optional.Of( + corev1.EnvVar{ + Name: "AGENT_ZONE", + Value: clusterName, + }, + ), + }, + + { + name: "clusterName_is_not_set", + getMethod: agentZoneMethod, + helpersExpect: func(hlprs *MockHelpers) {}, + agent: &instanav1.InstanaAgent{ + Spec: instanav1.InstanaAgentSpec{ + Zone: instanav1.Name{ + Name: zoneName, + }, + }, + }, + expected: optional.Of( + corev1.EnvVar{ + Name: "AGENT_ZONE", + Value: zoneName, + }, + ), + }, + }, + ) +} + +func TestEnvBuilder_httpsProxyEnv(t *testing.T) { + proxyHost := randString() + proxyPort := randString() + proxyUser := randString() + proxyPass := randString() + + for _, test := range []struct { + name string + agentSpec instanav1.InstanaAgentSpec + expected string + }{ + { + name: "host_only", + agentSpec: instanav1.InstanaAgentSpec{ + Agent: instanav1.BaseAgentSpec{ + ProxyHost: proxyHost, + }, + }, + expected: "http://" + proxyHost + ":80", + }, + { + name: "no_credentials", + agentSpec: instanav1.InstanaAgentSpec{ + Agent: instanav1.BaseAgentSpec{ + ProxyHost: proxyHost, + ProxyPort: proxyPort, + }, + }, + expected: "http://" + proxyHost + ":" + proxyPort, + }, + { + name: "no_password", + agentSpec: instanav1.InstanaAgentSpec{ + Agent: instanav1.BaseAgentSpec{ + ProxyHost: proxyHost, + ProxyPort: proxyPort, + ProxyUser: proxyUser, + }, + }, + expected: "http://" + proxyHost + ":" + proxyPort, + }, + { + name: "with_credentials", + agentSpec: instanav1.InstanaAgentSpec{ + Agent: instanav1.BaseAgentSpec{ + ProxyHost: proxyHost, + ProxyPort: proxyPort, + ProxyUser: proxyUser, + ProxyPassword: proxyPass, + }, + }, + expected: "http://" + proxyUser + ":" + proxyPass + "@" + proxyHost + ":" + proxyPort, + }, + } { + t.Run( + test.name, func(t *testing.T) { + testFromCRField( + &crFieldVarTest{ + t: t, + getMethod: func(builder *envBuilder) func() optional.Optional[corev1.EnvVar] { + return builder.httpsProxyEnv + }, + expectedName: "HTTPS_PROXY", + expectedValue: test.expected, + agentSpec: test.agentSpec, + }, + ) + }, + ) + } +} + +func TestEnvBuilder_backendURLEnv(t *testing.T) { + testLiteralAlways( + &literalAlwaysTest{ + t: t, + getMethod: func(builder *envBuilder) func() optional.Optional[corev1.EnvVar] { + return builder.backendURLEnv + }, + expected: optional.Of( + corev1.EnvVar{ + Name: "BACKEND_URL", + Value: "https://$(BACKEND)", + }, + ), + }, + ) +} + +func TestEnvBuilder_noProxyEnv(t *testing.T) { + testFromCRField( + &crFieldVarTest{ + t: t, + getMethod: func(builder *envBuilder) func() optional.Optional[corev1.EnvVar] { + return builder.noProxyEnv + }, + expectedName: "NO_PROXY", + expectedValue: "kubernetes.default.svc", + agentSpec: instanav1.InstanaAgentSpec{ + Agent: instanav1.BaseAgentSpec{ + ProxyHost: randString(), + }, + }, + }, + ) +} + +func TestEnvBuilder_configPathEnv(t *testing.T) { + testLiteralAlways( + &literalAlwaysTest{ + t: t, + getMethod: func(builder *envBuilder) func() optional.Optional[corev1.EnvVar] { + return builder.configPathEnv + }, + expected: optional.Of( + corev1.EnvVar{ + Name: "CONFIG_PATH", + Value: volume.InstanaConfigDirectory, + }, + ), + }, + ) +} + +func TestEnvBuilder_backendEnv(t *testing.T) { + k8sSensorResourceName := randString() + + testVarMethod( + t, []varMethodTest{ + { + name: "from_configMap", + getMethod: func(builder *envBuilder) func() optional.Optional[corev1.EnvVar] { + return builder.backendEnv + }, + agent: &instanav1.InstanaAgent{}, + helpersExpect: func(hlprs *MockHelpers) { + hlprs.EXPECT().K8sSensorResourcesName().Return(k8sSensorResourceName) + }, + expected: optional.Of( + corev1.EnvVar{ + Name: "BACKEND", + ValueFrom: &corev1.EnvVarSource{ + ConfigMapKeyRef: &corev1.ConfigMapKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: k8sSensorResourceName, + }, + Key: constants.BackendKey, + }, + }, + }, + ), + }, + }, + ) +} + +func agentKeyTestHelper( + t *testing.T, + name string, + getMethod func(builder *envBuilder) func() optional.Optional[corev1.EnvVar], +) { + const expectedSecretName = "agent-key-secret" + + testFromSecretMethod( + &fromSecretTest{ + t: t, + expectedSecretName: expectedSecretName, + expected: optional.Of( + corev1.EnvVar{ + Name: name, + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: expectedSecretName, + }, + Key: "key", + }, + }, + }, + ), + getMethod: getMethod, + }, + ) +} + +func TestEnvBuilder_instanaAgentKeyEnv(t *testing.T) { + agentKeyTestHelper( + t, + "INSTANA_AGENT_KEY", + func(builder *envBuilder) func() optional.Optional[corev1.EnvVar] { + return builder.instanaAgentKeyEnv + }, + ) +} + +func TestEnvBuilder_agentKeyEnv(t *testing.T) { + agentKeyTestHelper( + t, + "AGENT_KEY", + func(builder *envBuilder) func() optional.Optional[corev1.EnvVar] { + return builder.agentKeyEnv + }, + ) +} + +func TestEnvBuilder_downloadKeyEnv(t *testing.T) { + const expectedSecretName = "download-key-secret" + + testFromSecretMethod( + &fromSecretTest{ + t: t, + expectedSecretName: expectedSecretName, + expected: optional.Of( + corev1.EnvVar{ + Name: "INSTANA_DOWNLOAD_KEY", + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: expectedSecretName, + }, + Key: "downloadKey", + Optional: pointer.To(true), + }, + }, + }, + ), + getMethod: func(builder *envBuilder) func() optional.Optional[corev1.EnvVar] { + return builder.downloadKeyEnv + }, + }, + ) +} + +type literalAlwaysTest struct { + t *testing.T + getMethod func(builder *envBuilder) func() optional.Optional[corev1.EnvVar] + expected optional.Optional[corev1.EnvVar] +} + +func testLiteralAlways(test *literalAlwaysTest) { + testVarMethod( + test.t, []varMethodTest{ + { + name: "build", + getMethod: test.getMethod, + expected: test.expected, + }, + }, + ) +} + +func TestEnvBuilder_instanaAgentPodNameEnv(t *testing.T) { + testLiteralAlways( + &literalAlwaysTest{ + t: t, + getMethod: func(builder *envBuilder) func() optional.Optional[corev1.EnvVar] { + return builder.instanaAgentPodNameEnv + }, + expected: optional.Of( + corev1.EnvVar{ + Name: "INSTANA_AGENT_POD_NAME", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + FieldPath: "metadata.name", + }, + }, + }, + ), + }, + ) +} + +func TestEnvBuilder_podNameEnv(t *testing.T) { + testLiteralAlways( + &literalAlwaysTest{ + t: t, + getMethod: func(builder *envBuilder) func() optional.Optional[corev1.EnvVar] { + return builder.podNameEnv + }, + expected: optional.Of( + corev1.EnvVar{ + Name: "POD_NAME", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + FieldPath: "metadata.name", + }, + }, + }, + ), + }, + ) +} + +func TestEnvBuilder_podIPEnv(t *testing.T) { + testLiteralAlways( + &literalAlwaysTest{ + t: t, + getMethod: func(builder *envBuilder) func() optional.Optional[corev1.EnvVar] { + return builder.podIPEnv + }, + expected: optional.Of( + corev1.EnvVar{ + Name: "POD_IP", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + FieldPath: "status.podIP", + }, + }, + }, + ), + }, + ) +} + +func TestEnvBuilder_podUIDEnv(t *testing.T) { + testLiteralAlways( + &literalAlwaysTest{ + t: t, + getMethod: func(builder *envBuilder) func() optional.Optional[corev1.EnvVar] { + return builder.podUIDEnv + }, + expected: optional.Of( + corev1.EnvVar{ + Name: "POD_UID", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + FieldPath: "metadata.uid", + }, + }, + }, + ), + }, + ) +} + +func TestEnvBuilder_podNamespaceEnv(t *testing.T) { + testLiteralAlways( + &literalAlwaysTest{ + t: t, + getMethod: func(builder *envBuilder) func() optional.Optional[corev1.EnvVar] { + return builder.podNamespaceEnv + }, + expected: optional.Of( + corev1.EnvVar{ + Name: "POD_NAMESPACE", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + FieldPath: "metadata.namespace", + }, + }, + }, + ), + }, + ) +} + +func TestK8sServiceDomainEnv(t *testing.T) { + testVarMethod( + t, []varMethodTest{ + { + name: "build", + getMethod: func(builder *envBuilder) func() optional.Optional[corev1.EnvVar] { + return builder.k8sServiceDomainEnv + }, + agent: &instanav1.InstanaAgent{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "goijesdlkvlk", + }, + }, + helpersExpect: func(hlprs *MockHelpers) { + hlprs.EXPECT().HeadlessServiceName().Return("roidilmsdgo") + }, + expected: optional.Of( + corev1.EnvVar{ + Name: "K8S_SERVICE_DOMAIN", + Value: "roidilmsdgo.goijesdlkvlk.svc", + }, + ), + }, + }, + ) +} + +func TestUserProvidedEnv(t *testing.T) { + assertions := require.New(t) + + builder := &envBuilder{ + agent: &instanav1.InstanaAgent{ + Spec: instanav1.InstanaAgentSpec{ + Agent: instanav1.BaseAgentSpec{ + Env: map[string]string{ + "foo": "bar", + "hello": "world", + "oijrgoij": "45ioiojdij", + }, + }, + }, + }, + } + + expected := []corev1.EnvVar{ + { + Name: "foo", + Value: "bar", + }, + { + Name: "hello", + Value: "world", + }, + { + Name: "oijrgoij", + Value: "45ioiojdij", + }, + } + + opts := builder.userProvidedEnv() + actual := list.NewListMapTo[optional.Optional[corev1.EnvVar], corev1.EnvVar]().MapTo( + opts, func(builder optional.Optional[corev1.EnvVar]) corev1.EnvVar { + return builder.Get() + }, + ) + + assertions.ElementsMatch(expected, actual) +} diff --git a/pkg/k8s/object/builders/common/helpers/agent_interfaces.go b/pkg/k8s/object/builders/common/helpers/agent_interfaces.go new file mode 100644 index 00000000..ffac9400 --- /dev/null +++ b/pkg/k8s/object/builders/common/helpers/agent_interfaces.go @@ -0,0 +1,7 @@ +package helpers + +type OpenTelemetrySettings interface { + GrpcIsEnabled() bool + HttpIsEnabled() bool + IsEnabled() bool +} diff --git a/pkg/k8s/object/builders/common/helpers/helpers.go b/pkg/k8s/object/builders/common/helpers/helpers.go new file mode 100644 index 00000000..be8be64c --- /dev/null +++ b/pkg/k8s/object/builders/common/helpers/helpers.go @@ -0,0 +1,98 @@ +package helpers + +import ( + "strings" + + corev1 "k8s.io/api/core/v1" + + instanav1 "github.com/instana/instana-agent-operator/api/v1" + "github.com/instana/instana-agent-operator/pkg/optional" + "github.com/instana/instana-agent-operator/pkg/pointer" +) + +const ( + ContainersInstanaIORegistry = "containers.instana.io" +) + +type helpers struct { + *instanav1.InstanaAgent +} + +type Helpers interface { + ServiceAccountName() string + KeysSecretName() string + TLSIsEnabled() bool + TLSSecretName() string + HeadlessServiceName() string + K8sSensorResourcesName() string + ContainersSecretName() string + UseContainersSecret() bool + ImagePullSecrets() []corev1.LocalObjectReference +} + +func (h *helpers) serviceAccountNameDefault() string { + switch pointer.DerefOrEmpty(h.Spec.ServiceAccountSpec.Create.Create) { + case true: + return h.Name + default: + return "default" + } +} + +func (h *helpers) ServiceAccountName() string { + return optional.Of(h.Spec.ServiceAccountSpec.Name.Name).GetOrDefault(h.serviceAccountNameDefault()) +} + +func (h *helpers) KeysSecretName() string { + return optional.Of(h.Spec.Agent.KeysSecret).GetOrDefault(h.Name) +} + +func (h *helpers) TLSIsEnabled() bool { + return h.Spec.Agent.TlsSpec.SecretName != "" || (len(h.Spec.Agent.TlsSpec.Certificate) > 0 && len(h.Spec.Agent.TlsSpec.Key) > 0) +} + +func (h *helpers) TLSSecretName() string { + return optional.Of(h.Spec.Agent.TlsSpec.SecretName).GetOrDefault(h.Name + "-tls") +} + +func (h *helpers) HeadlessServiceName() string { + return h.Name + "-headless" +} + +func (h *helpers) K8sSensorResourcesName() string { + return h.Name + "-k8sensor" +} + +func (h *helpers) ContainersSecretName() string { + return h.Name + "-containers-instana-io" +} + +func (h *helpers) UseContainersSecret() bool { + // Explicitly using e.PullSecrets != nil instead of len(e.PullSecrets) == 0 since the original chart specified that + // auto-generated secret shouldn't be used if the user explicitly provided an empty list of pull secrets + // (original logic was to only use the generated secret if the registry matches AND the pullSecrets field was + // omitted by the user). I don't understand why anyone would want this, but the original chart had comments + // specifically mentioning that this was the desired behavior, so keeping it until someone says otherwise. + return h.Spec.Agent.PullSecrets == nil && strings.HasPrefix( + h.Spec.Agent.ImageSpec.Name, + ContainersInstanaIORegistry, + ) +} + +func (h *helpers) ImagePullSecrets() []corev1.LocalObjectReference { + if h.UseContainersSecret() { + return []corev1.LocalObjectReference{ + { + Name: h.ContainersSecretName(), + }, + } + } else { + return h.Spec.Agent.ExtendedImageSpec.PullSecrets + } +} + +func NewHelpers(agent *instanav1.InstanaAgent) Helpers { + return &helpers{ + InstanaAgent: agent, + } +} diff --git a/pkg/k8s/object/builders/common/helpers/helpers_test.go b/pkg/k8s/object/builders/common/helpers/helpers_test.go new file mode 100644 index 00000000..54e023dd --- /dev/null +++ b/pkg/k8s/object/builders/common/helpers/helpers_test.go @@ -0,0 +1,404 @@ +package helpers + +import ( + "testing" + + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/rand" + + instanav1 "github.com/instana/instana-agent-operator/api/v1" + "github.com/instana/instana-agent-operator/pkg/pointer" +) + +func TestHelpers_ServiceAccountName(t *testing.T) { + for _, tt := range []struct { + name string + agent *instanav1.InstanaAgent + want string + }{ + { + name: "ServiceAccount name is set in spec", + agent: &instanav1.InstanaAgent{ + Spec: instanav1.InstanaAgentSpec{ + ServiceAccountSpec: instanav1.ServiceAccountSpec{ + Name: instanav1.Name{ + Name: "0wegoijsdgo", + }, + }, + }, + }, + want: "0wegoijsdgo", + }, + { + name: "ServiceAccount name is set in spec and create is true", + agent: &instanav1.InstanaAgent{ + Spec: instanav1.InstanaAgentSpec{ + ServiceAccountSpec: instanav1.ServiceAccountSpec{ + Name: instanav1.Name{ + Name: "erhpoijsg94", + }, + Create: instanav1.Create{ + Create: pointer.To(true), + }, + }, + }, + }, + want: "erhpoijsg94", + }, + { + name: "ServiceAccount create is true in spec", + agent: &instanav1.InstanaAgent{ + ObjectMeta: metav1.ObjectMeta{ + Name: "-94jsdogijoijwgt", + }, + Spec: instanav1.InstanaAgentSpec{ + ServiceAccountSpec: instanav1.ServiceAccountSpec{ + Create: instanav1.Create{ + Create: pointer.To(true), + }, + }, + }, + }, + want: "-94jsdogijoijwgt", + }, + { + name: "ServiceAccount create is false in spec", + agent: &instanav1.InstanaAgent{ + ObjectMeta: metav1.ObjectMeta{ + Name: "-94jsdogijoijwgt", + }, + Spec: instanav1.InstanaAgentSpec{ + ServiceAccountSpec: instanav1.ServiceAccountSpec{ + Create: instanav1.Create{ + Create: pointer.To(false), + }, + }, + }, + }, + want: "default", + }, + { + name: "No ServiceAccount options specified", + agent: &instanav1.InstanaAgent{}, + want: "default", + }, + } { + t.Run( + tt.name, func(t *testing.T) { + assertions := require.New(t) + h := NewHelpers(tt.agent) + assertions.Equal(tt.want, h.ServiceAccountName()) + }, + ) + } +} + +func TestHelpers_KeysSecretName(t *testing.T) { + for _, tc := range []struct { + name string + expected string + agent instanav1.InstanaAgent + }{ + { + name: "keys_secret_not_provided_by_user", + expected: "peposdgokds", + agent: instanav1.InstanaAgent{ + ObjectMeta: metav1.ObjectMeta{ + Name: "peposdgokds", + }, + }, + }, + { + name: "keys_secret_is_provided_by_user", + expected: "riuoidfoisd", + agent: instanav1.InstanaAgent{ + Spec: instanav1.InstanaAgentSpec{ + Agent: instanav1.BaseAgentSpec{ + KeysSecret: "riuoidfoisd", + }, + }, + }, + }, + } { + t.Run( + tc.name, func(t *testing.T) { + assertions := require.New(t) + + h := NewHelpers(&tc.agent) + + actual := h.KeysSecretName() + + assertions.Equal(tc.expected, actual) + }, + ) + } +} + +func TestHelpers_TLSIsEnabled(t *testing.T) { + for _, test := range []struct { + name string + secretName string + certificate string + key string + expected bool + }{ + { + name: "all_empty", + }, + { + name: "secret_name_filled", + secretName: "adsfasg", + expected: true, + }, + { + name: "secret_name_and_key_filled", + secretName: "adsfasg", + expected: true, + key: "rgiosdoig", + }, + { + name: "secret_name_and_cert_filled", + secretName: "adsfasg", + expected: true, + certificate: "asoijegpoijsd", + }, + { + name: "secret_name_cert_and_key_filled", + secretName: "adsfasg", + expected: true, + certificate: "groijoijds", + key: "rwihjsdoijdsj", + }, + { + name: "cert_filled", + certificate: "woisoijdsjdsg", + }, + { + name: "key_filled", + key: "soihsoigjsdg", + }, + { + name: "key_and_cert_filled", + key: "rwoihsdohjd", + certificate: "ojoijsdoijoijdsf", + expected: true, + }, + } { + t.Run( + test.name, func(t *testing.T) { + assertions := require.New(t) + + h := NewHelpers( + &instanav1.InstanaAgent{ + Spec: instanav1.InstanaAgentSpec{ + Agent: instanav1.BaseAgentSpec{ + TlsSpec: instanav1.TlsSpec{ + SecretName: test.secretName, + Certificate: []byte(test.certificate), + Key: []byte(test.key), + }, + }, + }, + }, + ) + assertions.Equal(test.expected, h.TLSIsEnabled()) + }, + ) + } +} + +func TestHelpers_TLSSecretName(t *testing.T) { + for _, tc := range []struct { + name string + agent *instanav1.InstanaAgent + wantSecret string + }{ + { + name: "secret_name_set_explicitly", + agent: &instanav1.InstanaAgent{ + ObjectMeta: metav1.ObjectMeta{ + Name: "oioijsdjdsf", + }, + Spec: instanav1.InstanaAgentSpec{ + Agent: instanav1.BaseAgentSpec{ + TlsSpec: instanav1.TlsSpec{ + SecretName: "prpojdg", + }, + }, + }, + }, + wantSecret: "prpojdg", + }, + { + name: "secret_name_not_set_explicitly", + agent: &instanav1.InstanaAgent{ + ObjectMeta: metav1.ObjectMeta{ + Name: "risoijsdgljs", + }, + }, + wantSecret: "risoijsdgljs-tls", + }, + } { + t.Run( + tc.name, func(t *testing.T) { + assertions := require.New(t) + + h := NewHelpers(tc.agent) + + gotSecret := h.TLSSecretName() + + assertions.Equal(tc.wantSecret, gotSecret) + }, + ) + } +} + +func TestHelpers_HeadlessServiceName(t *testing.T) { + assertions := require.New(t) + + h := NewHelpers( + &instanav1.InstanaAgent{ + ObjectMeta: metav1.ObjectMeta{ + Name: "rhjaoijdsoijoidsf", + }, + }, + ) + assertions.Equal("rhjaoijdsoijoidsf-headless", h.HeadlessServiceName()) +} + +func TestHelpers_K8sSensorResourcesName(t *testing.T) { + assertions := require.New(t) + + h := NewHelpers( + &instanav1.InstanaAgent{ + ObjectMeta: metav1.ObjectMeta{ + Name: "rhjaoijdsoijoidsf", + }, + }, + ) + assertions.Equal("rhjaoijdsoijoidsf-k8sensor", h.K8sSensorResourcesName()) +} + +func TestHelpers_ContainersSecretName(t *testing.T) { + assertions := require.New(t) + + agentName := rand.String(rand.IntnRange(1, 15)) + + h := NewHelpers( + &instanav1.InstanaAgent{ + ObjectMeta: metav1.ObjectMeta{ + Name: agentName, + }, + }, + ) + + assertions.Equal(agentName+"-containers-instana-io", h.ContainersSecretName()) +} + +func TestHelpers_UseContainersSecret(t *testing.T) { + for _, test := range []struct { + name string + userProvidedPullSecrets []corev1.LocalObjectReference + imageName string + expected bool + }{ + { + name: "nil_secrets_random_image", + userProvidedPullSecrets: nil, + imageName: rand.String(rand.IntnRange(1, 15)), + expected: false, + }, + { + name: "empty_secrets_random_image", + userProvidedPullSecrets: []corev1.LocalObjectReference{}, + imageName: rand.String(rand.IntnRange(1, 15)), + expected: false, + }, + { + name: "non_empty_secrets_random_image", + userProvidedPullSecrets: []corev1.LocalObjectReference{ + { + Name: rand.String(rand.IntnRange(1, 15)), + }, + { + Name: rand.String(rand.IntnRange(1, 15)), + }, + }, + imageName: rand.String(rand.IntnRange(1, 15)), + expected: false, + }, + { + name: "nil_secrets_image_has_prefix", + userProvidedPullSecrets: nil, + imageName: "containers.instana.io/" + rand.String(rand.IntnRange(1, 15)), + expected: true, + }, + { + name: "empty_secrets_image_has_prefix", + userProvidedPullSecrets: []corev1.LocalObjectReference{}, + imageName: "containers.instana.io/" + rand.String(rand.IntnRange(1, 15)), + expected: false, + }, + { + name: "non_empty_secrets_image_has_prefix", + userProvidedPullSecrets: []corev1.LocalObjectReference{ + { + Name: rand.String(rand.IntnRange(1, 15)), + }, + { + Name: rand.String(rand.IntnRange(1, 15)), + }, + }, + imageName: "containers.instana.io/" + rand.String(rand.IntnRange(1, 15)), + expected: false, + }, + } { + t.Run( + test.name, func(t *testing.T) { + assertions := require.New(t) + + h := NewHelpers( + &instanav1.InstanaAgent{ + ObjectMeta: metav1.ObjectMeta{ + Name: rand.String(rand.IntnRange(1, 15)), + }, + Spec: instanav1.InstanaAgentSpec{ + Agent: instanav1.BaseAgentSpec{ + ExtendedImageSpec: instanav1.ExtendedImageSpec{ + PullSecrets: test.userProvidedPullSecrets, + ImageSpec: instanav1.ImageSpec{ + Name: test.imageName, + }, + }, + }, + }, + }, + ) + + actual := h.UseContainersSecret() + + assertions.Equal(test.expected, actual) + + expectedSecrets := func() []corev1.LocalObjectReference { + if test.expected { + containersSecretName := h.ContainersSecretName() + + return []corev1.LocalObjectReference{ + { + Name: containersSecretName, + }, + } + } else { + return test.userProvidedPullSecrets + } + }() + + actualSecrets := h.ImagePullSecrets() + + assertions.Equal(expectedSecrets, actualSecrets) + }, + ) + } +} diff --git a/pkg/k8s/object/builders/common/ports/agent_interfaces_mock_test.go b/pkg/k8s/object/builders/common/ports/agent_interfaces_mock_test.go new file mode 100644 index 00000000..d1b57a0b --- /dev/null +++ b/pkg/k8s/object/builders/common/ports/agent_interfaces_mock_test.go @@ -0,0 +1,96 @@ +// /* +// (c) Copyright IBM Corp. 2024 +// (c) Copyright Instana Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// */ +// + +// Code generated by MockGen. DO NOT EDIT. +package ports + +import ( + reflect "reflect" + + gomock "go.uber.org/mock/gomock" +) + +// MockOpenTelemetrySettings is a mock of OpenTelemetrySettings interface. +type MockOpenTelemetrySettings struct { + ctrl *gomock.Controller + recorder *MockOpenTelemetrySettingsMockRecorder +} + +// MockOpenTelemetrySettingsMockRecorder is the mock recorder for MockOpenTelemetrySettings. +type MockOpenTelemetrySettingsMockRecorder struct { + mock *MockOpenTelemetrySettings +} + +// NewMockOpenTelemetrySettings creates a new mock instance. +func NewMockOpenTelemetrySettings(ctrl *gomock.Controller) *MockOpenTelemetrySettings { + mock := &MockOpenTelemetrySettings{ctrl: ctrl} + mock.recorder = &MockOpenTelemetrySettingsMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockOpenTelemetrySettings) EXPECT() *MockOpenTelemetrySettingsMockRecorder { + return m.recorder +} + +// ISGOMOCK indicates that this struct is a gomock mock. +func (m *MockOpenTelemetrySettings) ISGOMOCK() struct{} { + return struct{}{} +} + +// GrpcIsEnabled mocks base method. +func (m *MockOpenTelemetrySettings) GrpcIsEnabled() bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GrpcIsEnabled") + ret0, _ := ret[0].(bool) + return ret0 +} + +// GrpcIsEnabled indicates an expected call of GrpcIsEnabled. +func (mr *MockOpenTelemetrySettingsMockRecorder) GrpcIsEnabled() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GrpcIsEnabled", reflect.TypeOf((*MockOpenTelemetrySettings)(nil).GrpcIsEnabled)) +} + +// HttpIsEnabled mocks base method. +func (m *MockOpenTelemetrySettings) HttpIsEnabled() bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "HttpIsEnabled") + ret0, _ := ret[0].(bool) + return ret0 +} + +// HttpIsEnabled indicates an expected call of HttpIsEnabled. +func (mr *MockOpenTelemetrySettingsMockRecorder) HttpIsEnabled() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HttpIsEnabled", reflect.TypeOf((*MockOpenTelemetrySettings)(nil).HttpIsEnabled)) +} + +// IsEnabled mocks base method. +func (m *MockOpenTelemetrySettings) IsEnabled() bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "IsEnabled") + ret0, _ := ret[0].(bool) + return ret0 +} + +// IsEnabled indicates an expected call of IsEnabled. +func (mr *MockOpenTelemetrySettingsMockRecorder) IsEnabled() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsEnabled", reflect.TypeOf((*MockOpenTelemetrySettings)(nil).IsEnabled)) +} diff --git a/pkg/k8s/object/builders/common/ports/ports.go b/pkg/k8s/object/builders/common/ports/ports.go new file mode 100644 index 00000000..39605d85 --- /dev/null +++ b/pkg/k8s/object/builders/common/ports/ports.go @@ -0,0 +1,114 @@ +package ports + +import ( + "errors" + "fmt" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/util/intstr" + + instanav1 "github.com/instana/instana-agent-operator/api/v1" + "github.com/instana/instana-agent-operator/pkg/collections/list" + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/helpers" +) + +type Port interface { + fmt.Stringer + + portNumber() int32 + isEnabled(openTelemetrySettings helpers.OpenTelemetrySettings) bool +} + +type InstanaAgentPort string + +const ( + AgentAPIsPort InstanaAgentPort = "agent-apis" + AgentSocketPort InstanaAgentPort = "agent-socket" + OpenTelemetryLegacyPort InstanaAgentPort = "otlp-legacy" + OpenTelemetryGRPCPort InstanaAgentPort = "otlp-grpc" + OpenTelemetryHTTPPort InstanaAgentPort = "otlp-http" +) + +func (p InstanaAgentPort) String() string { + return string(p) +} + +func (p InstanaAgentPort) portNumber() int32 { + switch p { + case AgentAPIsPort: + return 42699 + case AgentSocketPort: + return 42666 + case OpenTelemetryLegacyPort: + return 55680 + case OpenTelemetryGRPCPort: + return 4317 + case OpenTelemetryHTTPPort: + return 4318 + default: + panic(errors.New("unknown port requested")) + } +} + +func (p InstanaAgentPort) isEnabled(openTelemetrySettings helpers.OpenTelemetrySettings) bool { + switch p { + case OpenTelemetryLegacyPort: + fallthrough + case OpenTelemetryGRPCPort: + return openTelemetrySettings.GrpcIsEnabled() + case OpenTelemetryHTTPPort: + return openTelemetrySettings.HttpIsEnabled() + case AgentAPIsPort: + fallthrough + case AgentSocketPort: + fallthrough + default: + return true + } +} + +func toServicePort(port Port) corev1.ServicePort { + return corev1.ServicePort{ + Name: port.String(), + Protocol: corev1.ProtocolTCP, + Port: port.portNumber(), + TargetPort: intstr.FromString(port.String()), + } +} + +func toContainerPort(port Port) corev1.ContainerPort { + return corev1.ContainerPort{ + Name: port.String(), + ContainerPort: port.portNumber(), + Protocol: corev1.ProtocolTCP, + } +} + +type PortsBuilder interface { + GetServicePorts(ports ...Port) []corev1.ServicePort + GetContainerPorts(ports ...Port) []corev1.ContainerPort +} + +type portsBuilder struct { + *instanav1.InstanaAgent +} + +func (p *portsBuilder) GetServicePorts(ports ...Port) []corev1.ServicePort { + enabledPorts := list.NewListFilter[Port]().Filter( + ports, func(port Port) bool { + return port.isEnabled(p.Spec.OpenTelemetry) + }, + ) + + return list.NewListMapTo[Port, corev1.ServicePort]().MapTo(enabledPorts, toServicePort) +} + +func (p *portsBuilder) GetContainerPorts(ports ...Port) []corev1.ContainerPort { + return list.NewListMapTo[Port, corev1.ContainerPort]().MapTo(ports, toContainerPort) +} + +func NewPortsBuilder(agent *instanav1.InstanaAgent) PortsBuilder { + return &portsBuilder{ + InstanaAgent: agent, + } +} diff --git a/pkg/k8s/object/builders/common/ports/ports_mock_test.go b/pkg/k8s/object/builders/common/ports/ports_mock_test.go new file mode 100644 index 00000000..f1fa0827 --- /dev/null +++ b/pkg/k8s/object/builders/common/ports/ports_mock_test.go @@ -0,0 +1,162 @@ +// /* +// (c) Copyright IBM Corp. 2024 +// (c) Copyright Instana Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// */ +// + +// Code generated by MockGen. DO NOT EDIT. +package ports + +import ( + reflect "reflect" + + helpers "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/helpers" + gomock "go.uber.org/mock/gomock" + v1 "k8s.io/api/core/v1" +) + +// MockPort is a mock of Port interface. +type MockPort struct { + ctrl *gomock.Controller + recorder *MockPortMockRecorder +} + +// MockPortMockRecorder is the mock recorder for MockPort. +type MockPortMockRecorder struct { + mock *MockPort +} + +// NewMockPort creates a new mock instance. +func NewMockPort(ctrl *gomock.Controller) *MockPort { + mock := &MockPort{ctrl: ctrl} + mock.recorder = &MockPortMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockPort) EXPECT() *MockPortMockRecorder { + return m.recorder +} + +// ISGOMOCK indicates that this struct is a gomock mock. +func (m *MockPort) ISGOMOCK() struct{} { + return struct{}{} +} + +// String mocks base method. +func (m *MockPort) String() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "String") + ret0, _ := ret[0].(string) + return ret0 +} + +// String indicates an expected call of String. +func (mr *MockPortMockRecorder) String() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "String", reflect.TypeOf((*MockPort)(nil).String)) +} + +// isEnabled mocks base method. +func (m *MockPort) isEnabled(openTelemetrySettings helpers.OpenTelemetrySettings) bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "isEnabled", openTelemetrySettings) + ret0, _ := ret[0].(bool) + return ret0 +} + +// isEnabled indicates an expected call of isEnabled. +func (mr *MockPortMockRecorder) isEnabled(openTelemetrySettings any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "isEnabled", reflect.TypeOf((*MockPort)(nil).isEnabled), openTelemetrySettings) +} + +// portNumber mocks base method. +func (m *MockPort) portNumber() int32 { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "portNumber") + ret0, _ := ret[0].(int32) + return ret0 +} + +// portNumber indicates an expected call of portNumber. +func (mr *MockPortMockRecorder) portNumber() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "portNumber", reflect.TypeOf((*MockPort)(nil).portNumber)) +} + +// MockPortsBuilder is a mock of PortsBuilder interface. +type MockPortsBuilder struct { + ctrl *gomock.Controller + recorder *MockPortsBuilderMockRecorder +} + +// MockPortsBuilderMockRecorder is the mock recorder for MockPortsBuilder. +type MockPortsBuilderMockRecorder struct { + mock *MockPortsBuilder +} + +// NewMockPortsBuilder creates a new mock instance. +func NewMockPortsBuilder(ctrl *gomock.Controller) *MockPortsBuilder { + mock := &MockPortsBuilder{ctrl: ctrl} + mock.recorder = &MockPortsBuilderMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockPortsBuilder) EXPECT() *MockPortsBuilderMockRecorder { + return m.recorder +} + +// ISGOMOCK indicates that this struct is a gomock mock. +func (m *MockPortsBuilder) ISGOMOCK() struct{} { + return struct{}{} +} + +// GetContainerPorts mocks base method. +func (m *MockPortsBuilder) GetContainerPorts(ports ...Port) []v1.ContainerPort { + m.ctrl.T.Helper() + varargs := []any{} + for _, a := range ports { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "GetContainerPorts", varargs...) + ret0, _ := ret[0].([]v1.ContainerPort) + return ret0 +} + +// GetContainerPorts indicates an expected call of GetContainerPorts. +func (mr *MockPortsBuilderMockRecorder) GetContainerPorts(ports ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetContainerPorts", reflect.TypeOf((*MockPortsBuilder)(nil).GetContainerPorts), ports...) +} + +// GetServicePorts mocks base method. +func (m *MockPortsBuilder) GetServicePorts(ports ...Port) []v1.ServicePort { + m.ctrl.T.Helper() + varargs := []any{} + for _, a := range ports { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "GetServicePorts", varargs...) + ret0, _ := ret[0].([]v1.ServicePort) + return ret0 +} + +// GetServicePorts indicates an expected call of GetServicePorts. +func (mr *MockPortsBuilderMockRecorder) GetServicePorts(ports ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetServicePorts", reflect.TypeOf((*MockPortsBuilder)(nil).GetServicePorts), ports...) +} diff --git a/pkg/k8s/object/builders/common/ports/ports_test.go b/pkg/k8s/object/builders/common/ports/ports_test.go new file mode 100644 index 00000000..1fee60ad --- /dev/null +++ b/pkg/k8s/object/builders/common/ports/ports_test.go @@ -0,0 +1,281 @@ +/* +(c) Copyright IBM Corp. 2024 +(c) Copyright Instana Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package ports + +import ( + "testing" + + "github.com/stretchr/testify/require" + gomock "go.uber.org/mock/gomock" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/intstr" + + instanav1 "github.com/instana/instana-agent-operator/api/v1" +) + +func TestPortMappings(t *testing.T) { + for _, test := range []struct { + name string + port InstanaAgentPort + otlpSettingsConditions func(openTelemetrySettings *MockOpenTelemetrySettings) + expectedPortNumber int32 + expectEnabled bool + expectPanic bool + }{ + { + name: string(AgentAPIsPort), + port: AgentAPIsPort, + otlpSettingsConditions: func(openTelemetrySettings *MockOpenTelemetrySettings) {}, + expectedPortNumber: 42699, + expectEnabled: true, + }, + + { + name: string(AgentSocketPort), + port: AgentSocketPort, + otlpSettingsConditions: func(openTelemetrySettings *MockOpenTelemetrySettings) {}, + expectedPortNumber: 42666, + expectEnabled: true, + }, + + { + name: string(OpenTelemetryLegacyPort) + "_not_enabled", + port: OpenTelemetryLegacyPort, + otlpSettingsConditions: func(openTelemetrySettings *MockOpenTelemetrySettings) { + openTelemetrySettings.EXPECT().GrpcIsEnabled().Return(false) + }, + expectedPortNumber: 55680, + expectEnabled: false, + }, + { + name: string(OpenTelemetryLegacyPort) + "_enabled", + port: OpenTelemetryLegacyPort, + otlpSettingsConditions: func(openTelemetrySettings *MockOpenTelemetrySettings) { + openTelemetrySettings.EXPECT().GrpcIsEnabled().Return(true) + }, + expectedPortNumber: 55680, + expectEnabled: true, + }, + + { + name: string(OpenTelemetryGRPCPort) + "_not_enabled", + port: OpenTelemetryGRPCPort, + otlpSettingsConditions: func(openTelemetrySettings *MockOpenTelemetrySettings) { + openTelemetrySettings.EXPECT().GrpcIsEnabled().Return(false) + }, + expectedPortNumber: 4317, + expectEnabled: false, + }, + { + name: string(OpenTelemetryGRPCPort) + "_enabled", + port: OpenTelemetryGRPCPort, + otlpSettingsConditions: func(openTelemetrySettings *MockOpenTelemetrySettings) { + openTelemetrySettings.EXPECT().GrpcIsEnabled().Return(true) + }, + expectedPortNumber: 4317, + expectEnabled: true, + }, + + { + name: string(OpenTelemetryHTTPPort) + "_not_enabled", + port: OpenTelemetryHTTPPort, + otlpSettingsConditions: func(openTelemetrySettings *MockOpenTelemetrySettings) { + openTelemetrySettings.EXPECT().HttpIsEnabled().Return(false) + }, expectedPortNumber: 4318, + expectEnabled: false, + }, + { + name: string(OpenTelemetryHTTPPort) + "_enabled", + port: OpenTelemetryHTTPPort, + otlpSettingsConditions: func(openTelemetrySettings *MockOpenTelemetrySettings) { + openTelemetrySettings.EXPECT().HttpIsEnabled().Return(true) + }, + expectedPortNumber: 4318, + expectEnabled: true, + }, + { + name: "unknown_port", + port: InstanaAgentPort("unknown"), + otlpSettingsConditions: func(openTelemetrySettings *MockOpenTelemetrySettings) {}, + expectEnabled: true, + expectPanic: true, + }, + } { + t.Run( + test.name, func(t *testing.T) { + assertions := require.New(t) + ctrl := gomock.NewController(t) + + openTelemetrySettings := NewMockOpenTelemetrySettings(ctrl) + test.otlpSettingsConditions(openTelemetrySettings) + + assertions.Equal(string(test.port), test.port.String()) + + assertions.Equal(test.expectEnabled, test.port.isEnabled(openTelemetrySettings)) + + if test.expectPanic { + assertions.PanicsWithError( + "unknown port requested", func() { + test.port.portNumber() + }, + ) + } else { + assertions.Equal(test.expectedPortNumber, test.port.portNumber()) + } + }, + ) + } +} + +func Test_toServicePort(t *testing.T) { + assertions := require.New(t) + ctrl := gomock.NewController(t) + + p := NewMockPort(ctrl) + p.EXPECT().String().Return("roijdoijsglkjsdf").Times(2) + p.EXPECT().portNumber().Return(int32(98798)) + + expected := corev1.ServicePort{ + Name: "roijdoijsglkjsdf", + Protocol: corev1.ProtocolTCP, + Port: 98798, + TargetPort: intstr.FromString("roijdoijsglkjsdf"), + } + + actual := toServicePort(p) + + assertions.Equal(expected, actual) +} + +func Test_toContainerPort(t *testing.T) { + assertions := require.New(t) + ctrl := gomock.NewController(t) + + p := NewMockPort(ctrl) + p.EXPECT().String().Return("roijdoijsglkjsdf") + p.EXPECT().portNumber().Return(int32(98798)) + + expected := corev1.ContainerPort{ + Name: "roijdoijsglkjsdf", + ContainerPort: 98798, + Protocol: corev1.ProtocolTCP, + } + + actual := toContainerPort(p) + + assertions.Equal(expected, actual) +} + +func TestPortsBuilder_GetServicePorts(t *testing.T) { + assertions := require.New(t) + ctrl := gomock.NewController(t) + + otlp := instanav1.OpenTelemetry{} + + agent := &instanav1.InstanaAgent{ + ObjectMeta: metav1.ObjectMeta{ + Name: "eoidoijdsg", + }, + Spec: instanav1.InstanaAgentSpec{ + OpenTelemetry: otlp, + }, + } + + p1 := NewMockPort(ctrl) + p1.EXPECT().isEnabled(gomock.Eq(otlp)).Return(true) + p1.EXPECT().String().Return("p1").Times(2) + p1.EXPECT().portNumber().Return(int32(1)) + + p2 := NewMockPort(ctrl) + p2.EXPECT().isEnabled(gomock.Eq(otlp)).Return(false) + + p3 := NewMockPort(ctrl) + p3.EXPECT().isEnabled(gomock.Eq(otlp)).Return(true) + p3.EXPECT().String().Return("p3").Times(2) + p3.EXPECT().portNumber().Return(int32(3)) + + expected := []corev1.ServicePort{ + { + Name: "p1", + Port: 1, + TargetPort: intstr.FromString("p1"), + Protocol: corev1.ProtocolTCP, + }, + { + Name: "p3", + Port: 3, + TargetPort: intstr.FromString("p3"), + Protocol: corev1.ProtocolTCP, + }, + } + + pb := NewPortsBuilder(agent) + actual := pb.GetServicePorts(p1, p2, p3) + + assertions.Equal(expected, actual) +} + +func TestPortsBuilder_GetContainerPorts(t *testing.T) { + assertions := require.New(t) + ctrl := gomock.NewController(t) + + p1 := NewMockPort(ctrl) + p1.EXPECT().String().Return("p1") + p1.EXPECT().portNumber().Return(int32(1)) + + p2 := NewMockPort(ctrl) + p2.EXPECT().String().Return("p2") + p2.EXPECT().portNumber().Return(int32(2)) + + p3 := NewMockPort(ctrl) + p3.EXPECT().String().Return("p3") + p3.EXPECT().portNumber().Return(int32(3)) + + expected := []corev1.ContainerPort{ + { + Name: "p1", + ContainerPort: 1, + Protocol: corev1.ProtocolTCP, + }, + { + Name: "p2", + ContainerPort: 2, + Protocol: corev1.ProtocolTCP, + }, + { + Name: "p3", + ContainerPort: 3, + Protocol: corev1.ProtocolTCP, + }, + } + + actual := NewPortsBuilder(&instanav1.InstanaAgent{}).GetContainerPorts(p1, p2, p3) + + assertions.Equal(expected, actual) +} + +func TestNewPortsBuilder(t *testing.T) { + assertions := require.New(t) + + agent := &instanav1.InstanaAgent{} + + pb := NewPortsBuilder(agent).(*portsBuilder) + + assertions.Same(agent, pb.InstanaAgent) +} diff --git a/pkg/k8s/object/builders/common/volume/helpers_mock_test.go b/pkg/k8s/object/builders/common/volume/helpers_mock_test.go new file mode 100644 index 00000000..c86d936a --- /dev/null +++ b/pkg/k8s/object/builders/common/volume/helpers_mock_test.go @@ -0,0 +1,181 @@ +// /* +// (c) Copyright IBM Corp. 2024 +// (c) Copyright Instana Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// */ +// + +// Code generated by MockGen. DO NOT EDIT. +package volume + +import ( + reflect "reflect" + + gomock "go.uber.org/mock/gomock" + v1 "k8s.io/api/core/v1" +) + +// MockHelpers is a mock of Helpers interface. +type MockHelpers struct { + ctrl *gomock.Controller + recorder *MockHelpersMockRecorder +} + +// MockHelpersMockRecorder is the mock recorder for MockHelpers. +type MockHelpersMockRecorder struct { + mock *MockHelpers +} + +// NewMockHelpers creates a new mock instance. +func NewMockHelpers(ctrl *gomock.Controller) *MockHelpers { + mock := &MockHelpers{ctrl: ctrl} + mock.recorder = &MockHelpersMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockHelpers) EXPECT() *MockHelpersMockRecorder { + return m.recorder +} + +// ISGOMOCK indicates that this struct is a gomock mock. +func (m *MockHelpers) ISGOMOCK() struct{} { + return struct{}{} +} + +// ContainersSecretName mocks base method. +func (m *MockHelpers) ContainersSecretName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ContainersSecretName") + ret0, _ := ret[0].(string) + return ret0 +} + +// ContainersSecretName indicates an expected call of ContainersSecretName. +func (mr *MockHelpersMockRecorder) ContainersSecretName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ContainersSecretName", reflect.TypeOf((*MockHelpers)(nil).ContainersSecretName)) +} + +// HeadlessServiceName mocks base method. +func (m *MockHelpers) HeadlessServiceName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "HeadlessServiceName") + ret0, _ := ret[0].(string) + return ret0 +} + +// HeadlessServiceName indicates an expected call of HeadlessServiceName. +func (mr *MockHelpersMockRecorder) HeadlessServiceName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HeadlessServiceName", reflect.TypeOf((*MockHelpers)(nil).HeadlessServiceName)) +} + +// ImagePullSecrets mocks base method. +func (m *MockHelpers) ImagePullSecrets() []v1.LocalObjectReference { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ImagePullSecrets") + ret0, _ := ret[0].([]v1.LocalObjectReference) + return ret0 +} + +// ImagePullSecrets indicates an expected call of ImagePullSecrets. +func (mr *MockHelpersMockRecorder) ImagePullSecrets() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ImagePullSecrets", reflect.TypeOf((*MockHelpers)(nil).ImagePullSecrets)) +} + +// K8sSensorResourcesName mocks base method. +func (m *MockHelpers) K8sSensorResourcesName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "K8sSensorResourcesName") + ret0, _ := ret[0].(string) + return ret0 +} + +// K8sSensorResourcesName indicates an expected call of K8sSensorResourcesName. +func (mr *MockHelpersMockRecorder) K8sSensorResourcesName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "K8sSensorResourcesName", reflect.TypeOf((*MockHelpers)(nil).K8sSensorResourcesName)) +} + +// KeysSecretName mocks base method. +func (m *MockHelpers) KeysSecretName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "KeysSecretName") + ret0, _ := ret[0].(string) + return ret0 +} + +// KeysSecretName indicates an expected call of KeysSecretName. +func (mr *MockHelpersMockRecorder) KeysSecretName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "KeysSecretName", reflect.TypeOf((*MockHelpers)(nil).KeysSecretName)) +} + +// ServiceAccountName mocks base method. +func (m *MockHelpers) ServiceAccountName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ServiceAccountName") + ret0, _ := ret[0].(string) + return ret0 +} + +// ServiceAccountName indicates an expected call of ServiceAccountName. +func (mr *MockHelpersMockRecorder) ServiceAccountName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ServiceAccountName", reflect.TypeOf((*MockHelpers)(nil).ServiceAccountName)) +} + +// TLSIsEnabled mocks base method. +func (m *MockHelpers) TLSIsEnabled() bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "TLSIsEnabled") + ret0, _ := ret[0].(bool) + return ret0 +} + +// TLSIsEnabled indicates an expected call of TLSIsEnabled. +func (mr *MockHelpersMockRecorder) TLSIsEnabled() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TLSIsEnabled", reflect.TypeOf((*MockHelpers)(nil).TLSIsEnabled)) +} + +// TLSSecretName mocks base method. +func (m *MockHelpers) TLSSecretName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "TLSSecretName") + ret0, _ := ret[0].(string) + return ret0 +} + +// TLSSecretName indicates an expected call of TLSSecretName. +func (mr *MockHelpersMockRecorder) TLSSecretName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TLSSecretName", reflect.TypeOf((*MockHelpers)(nil).TLSSecretName)) +} + +// UseContainersSecret mocks base method. +func (m *MockHelpers) UseContainersSecret() bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UseContainersSecret") + ret0, _ := ret[0].(bool) + return ret0 +} + +// UseContainersSecret indicates an expected call of UseContainersSecret. +func (mr *MockHelpersMockRecorder) UseContainersSecret() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UseContainersSecret", reflect.TypeOf((*MockHelpers)(nil).UseContainersSecret)) +} diff --git a/pkg/k8s/object/builders/common/volume/volume.go b/pkg/k8s/object/builders/common/volume/volume.go new file mode 100644 index 00000000..dbbd1fd8 --- /dev/null +++ b/pkg/k8s/object/builders/common/volume/volume.go @@ -0,0 +1,244 @@ +package volume + +import ( + corev1 "k8s.io/api/core/v1" + + "github.com/instana/instana-agent-operator/pkg/optional" + "github.com/instana/instana-agent-operator/pkg/pointer" +) + +const ( + InstanaConfigDirectory = "/opt/instana/agent/etc/instana-config-yml" +) + +type hostVolumeWithMountParams struct { + name string + path string + *corev1.MountPropagationMode +} + +type VolumeWithMount struct { + Volume corev1.Volume + VolumeMount corev1.VolumeMount +} + +func hostVolumeWithMount(params *hostVolumeWithMountParams) VolumeWithMount { + return VolumeWithMount{ + Volume: corev1.Volume{ + Name: params.name, + VolumeSource: corev1.VolumeSource{ + HostPath: &corev1.HostPathVolumeSource{ + Path: params.path, + }, + }, + }, + VolumeMount: corev1.VolumeMount{ + Name: params.name, + MountPath: params.path, + MountPropagation: params.MountPropagationMode, + }, + } +} + +func hostVolumeWithMountLiteral(params *hostVolumeWithMountParams) optional.Optional[VolumeWithMount] { + return optional.Of(hostVolumeWithMount(params)) +} + +func (v *volumeBuilder) devVolume() optional.Optional[VolumeWithMount] { + return hostVolumeWithMountLiteral( + &hostVolumeWithMountParams{ + name: "dev", + path: "/dev", + MountPropagationMode: pointer.To(corev1.MountPropagationHostToContainer), + }, + ) +} + +func (v *volumeBuilder) runVolume() optional.Optional[VolumeWithMount] { + return hostVolumeWithMountLiteral( + &hostVolumeWithMountParams{ + name: "run", + path: "/run", + MountPropagationMode: pointer.To(corev1.MountPropagationHostToContainer), + }, + ) +} + +func (v *volumeBuilder) varRunVolume() optional.Optional[VolumeWithMount] { + return hostVolumeWithMountLiteral( + &hostVolumeWithMountParams{ + name: "var-run", + path: "/var/run", + MountPropagationMode: pointer.To(corev1.MountPropagationHostToContainer), + }, + ) +} + +func hostVolumeWithMountLiteralWhenCondition( + condition bool, + params *hostVolumeWithMountParams, +) optional.Optional[VolumeWithMount] { + switch condition { + case true: + return hostVolumeWithMountLiteral(params) + default: + return optional.Empty[VolumeWithMount]() + } +} + +func (v *volumeBuilder) varRunKuboVolume() optional.Optional[VolumeWithMount] { + return hostVolumeWithMountLiteralWhenCondition( + v.isNotOpenShift, + &hostVolumeWithMountParams{ + name: "var-run-kubo", + path: "/var/vcap/sys/run/docker", + MountPropagationMode: pointer.To(corev1.MountPropagationHostToContainer), + }, + ) +} + +func (v *volumeBuilder) varRunContainerdVolume() optional.Optional[VolumeWithMount] { + return hostVolumeWithMountLiteralWhenCondition( + v.isNotOpenShift, + &hostVolumeWithMountParams{ + name: "var-run-containerd", + path: "/var/vcap/sys/run/containerd", + MountPropagationMode: pointer.To(corev1.MountPropagationHostToContainer), + }, + ) +} + +func (v *volumeBuilder) varContainerdConfigVolume() optional.Optional[VolumeWithMount] { + return hostVolumeWithMountLiteralWhenCondition( + v.isNotOpenShift, + &hostVolumeWithMountParams{ + name: "var-containerd-config", + path: "/var/vcap/jobs/containerd/config", + MountPropagationMode: pointer.To(corev1.MountPropagationHostToContainer), + }, + ) +} + +func (v *volumeBuilder) sysVolume() optional.Optional[VolumeWithMount] { + return hostVolumeWithMountLiteral( + &hostVolumeWithMountParams{ + name: "sys", + path: "/sys", + MountPropagationMode: pointer.To(corev1.MountPropagationHostToContainer), + }, + ) +} + +func (v *volumeBuilder) varLogVolume() optional.Optional[VolumeWithMount] { + return hostVolumeWithMountLiteral( + &hostVolumeWithMountParams{ + name: "var-log", + path: "/var/log", + MountPropagationMode: pointer.To(corev1.MountPropagationHostToContainer), + }, + ) +} + +func (v *volumeBuilder) varLibVolume() optional.Optional[VolumeWithMount] { + return hostVolumeWithMountLiteral( + &hostVolumeWithMountParams{ + name: "var-lib", + path: "/var/lib", + MountPropagationMode: pointer.To(corev1.MountPropagationHostToContainer), + }, + ) +} + +func (v *volumeBuilder) varDataVolume() optional.Optional[VolumeWithMount] { + return hostVolumeWithMountLiteral( + &hostVolumeWithMountParams{ + name: "var-data", + path: "/var/data", + MountPropagationMode: pointer.To(corev1.MountPropagationHostToContainer), + }, + ) +} + +func (v *volumeBuilder) machineIdVolume() optional.Optional[VolumeWithMount] { + return hostVolumeWithMountLiteral( + &hostVolumeWithMountParams{ + name: "machine-id", + path: "/etc/machine-id", + }, + ) +} + +func (v *volumeBuilder) configVolume() optional.Optional[VolumeWithMount] { + const volumeName = "config" + + return optional.Of[VolumeWithMount]( + VolumeWithMount{ + Volume: corev1.Volume{ + Name: volumeName, + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: v.Name, + }, + }, + }, + }, + VolumeMount: corev1.VolumeMount{ + Name: volumeName, + MountPath: InstanaConfigDirectory, + }, + }, + ) +} + +func (v *volumeBuilder) tlsVolume() optional.Optional[VolumeWithMount] { + const volumeName = "instana-agent-tls" + + switch v.TLSIsEnabled() { + case true: + return optional.Of( + VolumeWithMount{ + Volume: corev1.Volume{ + Name: volumeName, + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: v.TLSSecretName(), + DefaultMode: pointer.To[int32](0440), + }, + }, + }, + VolumeMount: corev1.VolumeMount{ + Name: volumeName, + MountPath: "/opt/instana/agent/etc/certs", + ReadOnly: true, + }, + }, + ) + default: + return optional.Empty[VolumeWithMount]() + } +} + +func (v *volumeBuilder) repoVolume() optional.Optional[VolumeWithMount] { + const volumeName = "repo" + + return optional.Map[string, VolumeWithMount]( + optional.Of(v.Spec.Agent.Host.Repository), + func(path string) VolumeWithMount { + return VolumeWithMount{ + Volume: corev1.Volume{ + Name: volumeName, + VolumeSource: corev1.VolumeSource{ + HostPath: &corev1.HostPathVolumeSource{ + Path: path, + }, + }, + }, + VolumeMount: corev1.VolumeMount{ + Name: volumeName, + MountPath: "/opt/instana/agent/data/repo", + }, + } + }, + ) +} diff --git a/pkg/k8s/object/builders/common/volume/volume_builder.go b/pkg/k8s/object/builders/common/volume/volume_builder.go new file mode 100644 index 00000000..12bda0ea --- /dev/null +++ b/pkg/k8s/object/builders/common/volume/volume_builder.go @@ -0,0 +1,111 @@ +package volume + +import ( + "errors" + + corev1 "k8s.io/api/core/v1" + + instanav1 "github.com/instana/instana-agent-operator/api/v1" + "github.com/instana/instana-agent-operator/pkg/collections/list" + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/helpers" + "github.com/instana/instana-agent-operator/pkg/optional" +) + +type Volume int + +const ( + DevVolume Volume = iota + RunVolume + VarRunVolume + VarRunKuboVolume + VarRunContainerdVolume + VarContainerdConfigVolume + SysVolume + VarLogVolume + VarLibVolume + VarDataVolume + MachineIdVolume + ConfigVolume + TlsVolume + RepoVolume +) + +type VolumeBuilder interface { + Build(volumes ...Volume) ([]corev1.Volume, []corev1.VolumeMount) +} + +type volumeBuilder struct { + *instanav1.InstanaAgent + helpers.Helpers + isNotOpenShift bool +} + +func (v *volumeBuilder) getBuilder(volume Volume) func() optional.Optional[VolumeWithMount] { + switch volume { + case DevVolume: + return v.devVolume + case RunVolume: + return v.runVolume + case VarRunVolume: + return v.varRunVolume + case VarRunKuboVolume: + return v.varRunKuboVolume + case VarRunContainerdVolume: + return v.varRunContainerdVolume + case VarContainerdConfigVolume: + return v.varContainerdConfigVolume + case SysVolume: + return v.sysVolume + case VarLogVolume: + return v.varLogVolume + case VarLibVolume: + return v.varLibVolume + case VarDataVolume: + return v.varDataVolume + case MachineIdVolume: + return v.machineIdVolume + case ConfigVolume: + return v.configVolume + case TlsVolume: + return v.tlsVolume + case RepoVolume: + return v.repoVolume + default: + panic(errors.New("unknown volume requested")) + } +} + +func (v *volumeBuilder) Build(volumes ...Volume) ([]corev1.Volume, []corev1.VolumeMount) { + volumeOptionals := list.NewListMapTo[Volume, optional.Optional[VolumeWithMount]]().MapTo( + volumes, + func(volume Volume) optional.Optional[VolumeWithMount] { + return v.getBuilder(volume)() + }, + ) + + volumesWithMounts := optional.NewNonEmptyOptionalMapper[VolumeWithMount]().AllNonEmpty(volumeOptionals) + + volumeSpecs := list.NewListMapTo[VolumeWithMount, corev1.Volume]().MapTo( + volumesWithMounts, + func(val VolumeWithMount) corev1.Volume { + return val.Volume + }, + ) + + volumeMounts := list.NewListMapTo[VolumeWithMount, corev1.VolumeMount]().MapTo( + volumesWithMounts, + func(val VolumeWithMount) corev1.VolumeMount { + return val.VolumeMount + }, + ) + + return volumeSpecs, volumeMounts +} + +func NewVolumeBuilder(agent *instanav1.InstanaAgent, isOpenShift bool) VolumeBuilder { + return &volumeBuilder{ + InstanaAgent: agent, + Helpers: helpers.NewHelpers(agent), + isNotOpenShift: !isOpenShift, + } +} diff --git a/pkg/k8s/object/builders/common/volume/volume_builder_test.go b/pkg/k8s/object/builders/common/volume/volume_builder_test.go new file mode 100644 index 00000000..31052b2f --- /dev/null +++ b/pkg/k8s/object/builders/common/volume/volume_builder_test.go @@ -0,0 +1,101 @@ +package volume + +import ( + "reflect" + "testing" + + "github.com/stretchr/testify/require" + + instanav1 "github.com/instana/instana-agent-operator/api/v1" + "github.com/instana/instana-agent-operator/pkg/collections/list" +) + +const numDefinedVolumes = 14 + +func rangeUntil(n int) []Volume { + res := make([]Volume, 0, n) + + for i := 0; i < n; i++ { + res = append(res, Volume(i)) + } + + return res +} + +func assertAllElementsUnique[T comparable](assertions *require.Assertions, list []T) { + m := make(map[T]bool, len(list)) + + for _, element := range list { + m[element] = true + } + + assertions.Equal(len(list), len(m)) +} + +func TestVolumeBuilder_getBuilder(t *testing.T) { + t.Run( + "each_defined_var_has_unique_function", func(t *testing.T) { + assertions := require.New(t) + + vb := &volumeBuilder{} + + allBuilders := list.NewListMapTo[Volume, uintptr]().MapTo( + rangeUntil(numDefinedVolumes), + func(volume Volume) uintptr { + method := vb.getBuilder(volume) + + return reflect.ValueOf(method).Pointer() + }, + ) + + assertions.Len(allBuilders, numDefinedVolumes) + assertAllElementsUnique(assertions, allBuilders) + }, + ) + + t.Run( + "panics_above_defined_limit", func(t *testing.T) { + assertions := require.New(t) + + vb := &volumeBuilder{} + + assertions.PanicsWithError( + "unknown volume requested", func() { + vb.getBuilder(numDefinedVolumes) + }, + ) + }, + ) +} + +func TestVolumeBuilder_Build(t *testing.T) { + for _, test := range []struct { + name string + isOpenShift bool + expectedNumVolumes int + }{ + { + name: "isOpenShift", + isOpenShift: true, + expectedNumVolumes: 9, + }, + { + name: "isNotOpenShift", + isOpenShift: false, + expectedNumVolumes: 12, + }, + } { + t.Run( + test.name, func(t *testing.T) { + assertions := require.New(t) + + vb := NewVolumeBuilder(&instanav1.InstanaAgent{}, test.isOpenShift) + + actualvolumes, actualVolumeMounts := vb.Build(rangeUntil(numDefinedVolumes)...) + + assertions.Len(actualvolumes, test.expectedNumVolumes) + assertions.Len(actualVolumeMounts, test.expectedNumVolumes) + }, + ) + } +} diff --git a/pkg/k8s/object/builders/common/volume/volume_test.go b/pkg/k8s/object/builders/common/volume/volume_test.go new file mode 100644 index 00000000..8e61c55c --- /dev/null +++ b/pkg/k8s/object/builders/common/volume/volume_test.go @@ -0,0 +1,368 @@ +/* +(c) Copyright IBM Corp. 2024 +(c) Copyright Instana Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package volume + +import ( + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/rand" + + instanav1 "github.com/instana/instana-agent-operator/api/v1" + + gomock "go.uber.org/mock/gomock" + + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + + "github.com/instana/instana-agent-operator/pkg/optional" + "github.com/instana/instana-agent-operator/pkg/pointer" +) + +func testHostLiteralVolume( + t *testing.T, + expected *hostVolumeWithMountParams, + volume Volume, +) { + assertions := require.New(t) + + v := &volumeBuilder{ + isNotOpenShift: true, + } + + VolumeWithMountOpt := v.getBuilder(volume)() + + assertions.Equal( + corev1.Volume{ + Name: expected.name, + VolumeSource: corev1.VolumeSource{ + HostPath: &corev1.HostPathVolumeSource{ + Path: expected.path, + }, + }, + }, + VolumeWithMountOpt.Get().Volume, + ) + assertions.Equal( + corev1.VolumeMount{ + Name: expected.name, + MountPath: expected.path, + MountPropagation: expected.MountPropagationMode, + }, + VolumeWithMountOpt.Get().VolumeMount, + ) +} + +func TestDevVolume(t *testing.T) { + testHostLiteralVolume( + t, + &hostVolumeWithMountParams{ + name: "dev", + path: "/dev", + MountPropagationMode: pointer.To(corev1.MountPropagationHostToContainer), + }, + DevVolume, + ) +} + +func TestRunVolume(t *testing.T) { + testHostLiteralVolume( + t, + &hostVolumeWithMountParams{ + name: "run", + path: "/run", + MountPropagationMode: pointer.To(corev1.MountPropagationHostToContainer), + }, + RunVolume, + ) +} + +func TestVarRunVolume(t *testing.T) { + testHostLiteralVolume( + t, + &hostVolumeWithMountParams{ + name: "var-run", + path: "/var/run", + MountPropagationMode: pointer.To(corev1.MountPropagationHostToContainer), + }, + VarRunVolume, + ) +} + +func testHostLiteralOnlyWhenCondition( + t *testing.T, + expected *hostVolumeWithMountParams, + volume Volume, +) { + t.Run( + "not_condition", func(t *testing.T) { + assertions := require.New(t) + + v := &volumeBuilder{} + + assertions.Empty(v.getBuilder(volume)()) + }, + ) + t.Run( + "is_condition", func(t *testing.T) { + testHostLiteralVolume( + t, expected, volume, + ) + }, + ) +} + +func TestVarRunKuboVolume(t *testing.T) { + testHostLiteralOnlyWhenCondition( + t, + &hostVolumeWithMountParams{ + name: "var-run-kubo", + path: "/var/vcap/sys/run/docker", + MountPropagationMode: pointer.To(corev1.MountPropagationHostToContainer), + }, + VarRunKuboVolume, + ) +} + +func TestVarRunContainerdVolume(t *testing.T) { + testHostLiteralOnlyWhenCondition( + t, + &hostVolumeWithMountParams{ + name: "var-run-containerd", + path: "/var/vcap/sys/run/containerd", + MountPropagationMode: pointer.To(corev1.MountPropagationHostToContainer), + }, + VarRunContainerdVolume, + ) +} + +func TestVarContainerdConfigVolume(t *testing.T) { + testHostLiteralOnlyWhenCondition( + t, + &hostVolumeWithMountParams{ + name: "var-containerd-config", + path: "/var/vcap/jobs/containerd/config", + MountPropagationMode: pointer.To(corev1.MountPropagationHostToContainer), + }, + VarContainerdConfigVolume, + ) +} + +func TestSysVolume(t *testing.T) { + testHostLiteralVolume( + t, + &hostVolumeWithMountParams{ + name: "sys", + path: "/sys", + MountPropagationMode: pointer.To(corev1.MountPropagationHostToContainer), + }, + SysVolume, + ) +} + +func TestVarLogVolume(t *testing.T) { + testHostLiteralVolume( + t, + &hostVolumeWithMountParams{ + name: "var-log", + path: "/var/log", + MountPropagationMode: pointer.To(corev1.MountPropagationHostToContainer), + }, + VarLogVolume, + ) +} + +func TestVarLibVolume(t *testing.T) { + testHostLiteralVolume( + t, + &hostVolumeWithMountParams{ + name: "var-lib", + path: "/var/lib", + MountPropagationMode: pointer.To(corev1.MountPropagationHostToContainer), + }, + VarLibVolume, + ) +} + +func TestVarDataVolume(t *testing.T) { + testHostLiteralVolume( + t, + &hostVolumeWithMountParams{ + name: "var-data", + path: "/var/data", + MountPropagationMode: pointer.To(corev1.MountPropagationHostToContainer), + }, + VarDataVolume, + ) +} + +func TestMachineIdVolume(t *testing.T) { + testHostLiteralVolume( + t, + &hostVolumeWithMountParams{ + name: "machine-id", + path: "/etc/machine-id", + MountPropagationMode: nil, + }, + MachineIdVolume, + ) +} + +func TestConfigVolume(t *testing.T) { + assertions := require.New(t) + + agentName := rand.String(10) + + expectedVolume := []corev1.Volume{ + { + Name: "config", + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: agentName, + }, + }, + }, + }, + } + expectedVolumeMount := []corev1.VolumeMount{ + { + Name: "config", + MountPath: "/opt/instana/agent/etc/instana-config-yml", + }, + } + + v := NewVolumeBuilder( + &instanav1.InstanaAgent{ + ObjectMeta: metav1.ObjectMeta{ + Name: agentName, + }, + }, false, + ) + + actualVolume, actualVolumeMount := v.Build(ConfigVolume) + + assertions.Equal(expectedVolume, actualVolume) + assertions.Equal(expectedVolumeMount, actualVolumeMount) +} + +func TestTlsVolume(t *testing.T) { + t.Run( + "tls_not_enabled", func(t *testing.T) { + assertions := require.New(t) + ctrl := gomock.NewController(t) + + helpers := NewMockHelpers(ctrl) + helpers.EXPECT().TLSIsEnabled().Return(false) + + v := &volumeBuilder{ + Helpers: helpers, + } + + assertions.Empty(v.tlsVolume()) + }, + ) + t.Run( + "tls_is_enabled", func(t *testing.T) { + assertions := require.New(t) + ctrl := gomock.NewController(t) + + helpers := NewMockHelpers(ctrl) + helpers.EXPECT().TLSIsEnabled().Return(true) + helpers.EXPECT().TLSSecretName().Return("goisoijsoigjsd") + + v := &volumeBuilder{ + Helpers: helpers, + } + + assertions.Equal( + optional.Of( + VolumeWithMount{ + Volume: corev1.Volume{ + Name: "instana-agent-tls", + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: "goisoijsoigjsd", + DefaultMode: pointer.To[int32](0440), + }, + }, + }, + VolumeMount: corev1.VolumeMount{ + Name: "instana-agent-tls", + MountPath: "/opt/instana/agent/etc/certs", + ReadOnly: true, + }, + }, + ), + v.tlsVolume(), + ) + }, + ) +} + +func TestRepoVolume(t *testing.T) { + t.Run( + "host_repo_not_set", func(t *testing.T) { + assertions := require.New(t) + + v := &volumeBuilder{ + InstanaAgent: &instanav1.InstanaAgent{}, + } + + assertions.Empty(v.repoVolume()) + }, + ) + t.Run( + "host_repo_is_set", func(t *testing.T) { + assertions := require.New(t) + + v := &volumeBuilder{ + InstanaAgent: &instanav1.InstanaAgent{ + Spec: instanav1.InstanaAgentSpec{ + Agent: instanav1.BaseAgentSpec{ + Host: instanav1.HostSpec{ + Repository: "eiosoijdsgih", + }, + }, + }, + }, + } + + actual := v.repoVolume() + assertions.Equal( + optional.Of( + VolumeWithMount{ + Volume: corev1.Volume{ + Name: "repo", + VolumeSource: corev1.VolumeSource{ + HostPath: &corev1.HostPathVolumeSource{ + Path: "eiosoijdsgih", + }, + }, + }, + VolumeMount: corev1.VolumeMount{ + Name: "repo", + MountPath: "/opt/instana/agent/data/repo", + }, + }, + ), + actual, + ) + }, + ) +} diff --git a/pkg/k8s/object/builders/k8s-sensor/configmap/configmap.go b/pkg/k8s/object/builders/k8s-sensor/configmap/configmap.go new file mode 100644 index 00000000..4c552718 --- /dev/null +++ b/pkg/k8s/object/builders/k8s-sensor/configmap/configmap.go @@ -0,0 +1,53 @@ +package configmap + +import ( + "fmt" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + instanav1 "github.com/instana/instana-agent-operator/api/v1" + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/builder" + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/constants" + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/helpers" + "github.com/instana/instana-agent-operator/pkg/optional" +) + +type configMapBuilder struct { + *instanav1.InstanaAgent + helpers.Helpers +} + +func (c *configMapBuilder) IsNamespaced() bool { + return true +} + +func (c *configMapBuilder) ComponentName() string { + return constants.ComponentK8Sensor +} + +func (c *configMapBuilder) Build() optional.Optional[client.Object] { + return optional.Of[client.Object]( + &corev1.ConfigMap{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "ConfigMap", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: c.K8sSensorResourcesName(), + Namespace: c.Namespace, + }, + Data: map[string]string{ + constants.BackendKey: fmt.Sprintf("%s:%s", c.Spec.Agent.EndpointHost, c.Spec.Agent.EndpointPort), + }, + }, + ) +} + +func NewConfigMapBuilder(agent *instanav1.InstanaAgent) builder.ObjectBuilder { + return &configMapBuilder{ + InstanaAgent: agent, + Helpers: helpers.NewHelpers(agent), + } +} diff --git a/pkg/k8s/object/builders/k8s-sensor/configmap/configmap_test.go b/pkg/k8s/object/builders/k8s-sensor/configmap/configmap_test.go new file mode 100644 index 00000000..7532aa7c --- /dev/null +++ b/pkg/k8s/object/builders/k8s-sensor/configmap/configmap_test.go @@ -0,0 +1,94 @@ +/* +(c) Copyright IBM Corp. 2024 +(c) Copyright Instana Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package configmap + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" + gomock "go.uber.org/mock/gomock" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/rand" + "sigs.k8s.io/controller-runtime/pkg/client" + + instanav1 "github.com/instana/instana-agent-operator/api/v1" + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/constants" + "github.com/instana/instana-agent-operator/pkg/optional" +) + +func TestConfigMapBuilder_IsNamespaced_ComponentName(t *testing.T) { + assertions := require.New(t) + + cmb := NewConfigMapBuilder(nil) + + assertions.True(cmb.IsNamespaced()) + assertions.Equal(constants.ComponentK8Sensor, cmb.ComponentName()) +} + +func TestConfigMapBuilder_Build(t *testing.T) { + assertions := require.New(t) + ctrl := gomock.NewController(t) + + sensorResourcesName := rand.String(10) + namespace := rand.String(10) + + endpointHost := rand.String(10) + endpointPort := rand.String(10) + + agent := &instanav1.InstanaAgent{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + }, + Spec: instanav1.InstanaAgentSpec{ + Agent: instanav1.BaseAgentSpec{ + EndpointHost: endpointHost, + EndpointPort: endpointPort, + }, + }, + } + + expected := optional.Of[client.Object]( + &corev1.ConfigMap{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "ConfigMap", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: sensorResourcesName, + Namespace: namespace, + }, + Data: map[string]string{ + "backend": fmt.Sprintf("%s:%s", endpointHost, endpointPort), + }, + }, + ) + + helpers := NewMockHelpers(ctrl) + helpers.EXPECT().K8sSensorResourcesName().Return(sensorResourcesName) + + cmb := &configMapBuilder{ + InstanaAgent: agent, + Helpers: helpers, + } + + actual := cmb.Build() + + assertions.Equal(expected, actual) +} diff --git a/pkg/k8s/object/builders/k8s-sensor/configmap/helpers_mock_test.go b/pkg/k8s/object/builders/k8s-sensor/configmap/helpers_mock_test.go new file mode 100644 index 00000000..67ad53b6 --- /dev/null +++ b/pkg/k8s/object/builders/k8s-sensor/configmap/helpers_mock_test.go @@ -0,0 +1,181 @@ +// /* +// (c) Copyright IBM Corp. 2024 +// (c) Copyright Instana Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// */ +// + +// Code generated by MockGen. DO NOT EDIT. +package configmap + +import ( + reflect "reflect" + + gomock "go.uber.org/mock/gomock" + v1 "k8s.io/api/core/v1" +) + +// MockHelpers is a mock of Helpers interface. +type MockHelpers struct { + ctrl *gomock.Controller + recorder *MockHelpersMockRecorder +} + +// MockHelpersMockRecorder is the mock recorder for MockHelpers. +type MockHelpersMockRecorder struct { + mock *MockHelpers +} + +// NewMockHelpers creates a new mock instance. +func NewMockHelpers(ctrl *gomock.Controller) *MockHelpers { + mock := &MockHelpers{ctrl: ctrl} + mock.recorder = &MockHelpersMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockHelpers) EXPECT() *MockHelpersMockRecorder { + return m.recorder +} + +// ISGOMOCK indicates that this struct is a gomock mock. +func (m *MockHelpers) ISGOMOCK() struct{} { + return struct{}{} +} + +// ContainersSecretName mocks base method. +func (m *MockHelpers) ContainersSecretName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ContainersSecretName") + ret0, _ := ret[0].(string) + return ret0 +} + +// ContainersSecretName indicates an expected call of ContainersSecretName. +func (mr *MockHelpersMockRecorder) ContainersSecretName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ContainersSecretName", reflect.TypeOf((*MockHelpers)(nil).ContainersSecretName)) +} + +// HeadlessServiceName mocks base method. +func (m *MockHelpers) HeadlessServiceName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "HeadlessServiceName") + ret0, _ := ret[0].(string) + return ret0 +} + +// HeadlessServiceName indicates an expected call of HeadlessServiceName. +func (mr *MockHelpersMockRecorder) HeadlessServiceName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HeadlessServiceName", reflect.TypeOf((*MockHelpers)(nil).HeadlessServiceName)) +} + +// ImagePullSecrets mocks base method. +func (m *MockHelpers) ImagePullSecrets() []v1.LocalObjectReference { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ImagePullSecrets") + ret0, _ := ret[0].([]v1.LocalObjectReference) + return ret0 +} + +// ImagePullSecrets indicates an expected call of ImagePullSecrets. +func (mr *MockHelpersMockRecorder) ImagePullSecrets() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ImagePullSecrets", reflect.TypeOf((*MockHelpers)(nil).ImagePullSecrets)) +} + +// K8sSensorResourcesName mocks base method. +func (m *MockHelpers) K8sSensorResourcesName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "K8sSensorResourcesName") + ret0, _ := ret[0].(string) + return ret0 +} + +// K8sSensorResourcesName indicates an expected call of K8sSensorResourcesName. +func (mr *MockHelpersMockRecorder) K8sSensorResourcesName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "K8sSensorResourcesName", reflect.TypeOf((*MockHelpers)(nil).K8sSensorResourcesName)) +} + +// KeysSecretName mocks base method. +func (m *MockHelpers) KeysSecretName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "KeysSecretName") + ret0, _ := ret[0].(string) + return ret0 +} + +// KeysSecretName indicates an expected call of KeysSecretName. +func (mr *MockHelpersMockRecorder) KeysSecretName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "KeysSecretName", reflect.TypeOf((*MockHelpers)(nil).KeysSecretName)) +} + +// ServiceAccountName mocks base method. +func (m *MockHelpers) ServiceAccountName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ServiceAccountName") + ret0, _ := ret[0].(string) + return ret0 +} + +// ServiceAccountName indicates an expected call of ServiceAccountName. +func (mr *MockHelpersMockRecorder) ServiceAccountName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ServiceAccountName", reflect.TypeOf((*MockHelpers)(nil).ServiceAccountName)) +} + +// TLSIsEnabled mocks base method. +func (m *MockHelpers) TLSIsEnabled() bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "TLSIsEnabled") + ret0, _ := ret[0].(bool) + return ret0 +} + +// TLSIsEnabled indicates an expected call of TLSIsEnabled. +func (mr *MockHelpersMockRecorder) TLSIsEnabled() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TLSIsEnabled", reflect.TypeOf((*MockHelpers)(nil).TLSIsEnabled)) +} + +// TLSSecretName mocks base method. +func (m *MockHelpers) TLSSecretName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "TLSSecretName") + ret0, _ := ret[0].(string) + return ret0 +} + +// TLSSecretName indicates an expected call of TLSSecretName. +func (mr *MockHelpersMockRecorder) TLSSecretName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TLSSecretName", reflect.TypeOf((*MockHelpers)(nil).TLSSecretName)) +} + +// UseContainersSecret mocks base method. +func (m *MockHelpers) UseContainersSecret() bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UseContainersSecret") + ret0, _ := ret[0].(bool) + return ret0 +} + +// UseContainersSecret indicates an expected call of UseContainersSecret. +func (mr *MockHelpersMockRecorder) UseContainersSecret() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UseContainersSecret", reflect.TypeOf((*MockHelpers)(nil).UseContainersSecret)) +} diff --git a/pkg/k8s/object/builders/k8s-sensor/deployment/deployment.go b/pkg/k8s/object/builders/k8s-sensor/deployment/deployment.go new file mode 100644 index 00000000..adc223ea --- /dev/null +++ b/pkg/k8s/object/builders/k8s-sensor/deployment/deployment.go @@ -0,0 +1,187 @@ +/* +(c) Copyright IBM Corp. 2024 +(c) Copyright Instana Inc. 2024 +*/ + +package deployment + +import ( + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + instanav1 "github.com/instana/instana-agent-operator/api/v1" + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/builder" + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/constants" + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/env" + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/helpers" + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/ports" + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/volume" + "github.com/instana/instana-agent-operator/pkg/k8s/object/transformations" + "github.com/instana/instana-agent-operator/pkg/k8s/operator/status" + "github.com/instana/instana-agent-operator/pkg/map_defaulter" + "github.com/instana/instana-agent-operator/pkg/optional" + "github.com/instana/instana-agent-operator/pkg/pointer" +) + +const componentName = constants.ComponentK8Sensor + +type deploymentBuilder struct { + *instanav1.InstanaAgent + statusManager status.AgentStatusManager + + helpers.Helpers + transformations.PodSelectorLabelGenerator + env.EnvBuilder + volume.VolumeBuilder + ports.PortsBuilder +} + +func (d *deploymentBuilder) IsNamespaced() bool { + return true +} + +func (d *deploymentBuilder) ComponentName() string { + return componentName +} + +func (d *deploymentBuilder) getPodTemplateLabels() map[string]string { + podLabels := optional.Of(d.Spec.Agent.Pod.Labels).GetOrDefault(make(map[string]string, 3)) + podLabels[constants.LabelAgentMode] = string(instanav1.KUBERNETES) + return d.GetPodLabels(podLabels) +} + +func (d *deploymentBuilder) getEnvVars() []corev1.EnvVar { + return d.EnvBuilder.Build( + env.AgentKeyEnv, + env.BackendEnv, + env.BackendURLEnv, + env.AgentZoneEnv, + env.PodUIDEnv, + env.PodNamespaceEnv, + env.PodNameEnv, + env.PodIPEnv, + env.HTTPSProxyEnv, + env.NoProxyEnv, + env.RedactK8sSecretsEnv, + env.ConfigPathEnv, + ) +} + +func (d *deploymentBuilder) getVolumes() ([]corev1.Volume, []corev1.VolumeMount) { + return d.VolumeBuilder.Build(volume.ConfigVolume) +} + +// K8Sensor relies on this label for internal sharding logic for some reason, if you remove it the k8sensor will break +func addAppLabel(labels map[string]string) map[string]string { + labelsDefaulter := map_defaulter.NewMapDefaulter(&labels) + labelsDefaulter.SetIfEmpty("app", "k8sensor") + return labels +} + +func (d *deploymentBuilder) build() *appsv1.Deployment { + volumes, mounts := d.getVolumes() + + return &appsv1.Deployment{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "apps/v1", + Kind: "Deployment", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: d.K8sSensorResourcesName(), + Namespace: d.Namespace, + Labels: addAppLabel(nil), + }, + Spec: appsv1.DeploymentSpec{ + Replicas: pointer.To(int32(d.Spec.K8sSensor.DeploymentSpec.Replicas)), + MinReadySeconds: int32(d.Spec.K8sSensor.DeploymentSpec.MinReadySeconds), + Selector: &metav1.LabelSelector{ + MatchLabels: addAppLabel(d.GetPodSelectorLabels()), + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: addAppLabel(d.getPodTemplateLabels()), + Annotations: d.Spec.Agent.Pod.Annotations, + }, + Spec: corev1.PodSpec{ + ServiceAccountName: d.K8sSensorResourcesName(), + NodeSelector: d.Spec.K8sSensor.DeploymentSpec.Pod.NodeSelector, + PriorityClassName: d.Spec.K8sSensor.DeploymentSpec.Pod.PriorityClassName, + ImagePullSecrets: d.ImagePullSecrets(), + Containers: []corev1.Container{ + { + Name: "instana-agent", + Image: d.Spec.K8sSensor.ImageSpec.Image(), + ImagePullPolicy: d.Spec.K8sSensor.ImageSpec.PullPolicy, + Env: d.getEnvVars(), + VolumeMounts: mounts, + Resources: d.Spec.K8sSensor.DeploymentSpec.Pod.ResourceRequirements.GetOrDefault(), + Ports: d.PortsBuilder.GetContainerPorts(ports.AgentAPIsPort), + }, + }, + Volumes: volumes, + Tolerations: d.Spec.K8sSensor.DeploymentSpec.Pod.Tolerations, + Affinity: pointer.To( + optional.Of(d.Spec.K8sSensor.DeploymentSpec.Pod.Affinity).GetOrDefault( + corev1.Affinity{ + PodAntiAffinity: &corev1.PodAntiAffinity{ + PreferredDuringSchedulingIgnoredDuringExecution: []corev1.WeightedPodAffinityTerm{ + { + Weight: 100, + PodAffinityTerm: corev1.PodAffinityTerm{ + LabelSelector: &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{ + { + Key: constants.LabelAgentMode, + Operator: metav1.LabelSelectorOpIn, + Values: []string{string(instanav1.KUBERNETES)}, + }, + }, + }, + TopologyKey: corev1.LabelHostname, + }, + }, + }, + }, + }, + ), + ), + }, + }, + }, + } +} + +func (d *deploymentBuilder) Build() (res optional.Optional[client.Object]) { + defer func() { + res.IfPresent( + func(dpl client.Object) { + d.statusManager.SetK8sSensorDeployment(client.ObjectKeyFromObject(dpl)) + }, + ) + }() + + switch (d.Spec.Agent.Key == "" && d.Spec.Agent.KeysSecret == "") || (d.Spec.Zone.Name == "" && d.Spec.Cluster.Name == "") { + case true: + return optional.Empty[client.Object]() + default: + return optional.Of[client.Object](d.build()) + } +} + +func NewDeploymentBuilder( + agent *instanav1.InstanaAgent, + isOpenShift bool, + statusManager status.AgentStatusManager, +) builder.ObjectBuilder { + return &deploymentBuilder{ + InstanaAgent: agent, + statusManager: statusManager, + Helpers: helpers.NewHelpers(agent), + PodSelectorLabelGenerator: transformations.PodSelectorLabels(agent, componentName), + EnvBuilder: env.NewEnvBuilder(agent), + VolumeBuilder: volume.NewVolumeBuilder(agent, isOpenShift), + PortsBuilder: ports.NewPortsBuilder(agent), + } +} diff --git a/pkg/k8s/object/builders/k8s-sensor/poddisruptionbudget/helpers_mock_test.go b/pkg/k8s/object/builders/k8s-sensor/poddisruptionbudget/helpers_mock_test.go new file mode 100644 index 00000000..1f94961f --- /dev/null +++ b/pkg/k8s/object/builders/k8s-sensor/poddisruptionbudget/helpers_mock_test.go @@ -0,0 +1,181 @@ +// /* +// (c) Copyright IBM Corp. 2024 +// (c) Copyright Instana Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// */ +// + +// Code generated by MockGen. DO NOT EDIT. +package poddisruptionbudget + +import ( + reflect "reflect" + + gomock "go.uber.org/mock/gomock" + v1 "k8s.io/api/core/v1" +) + +// MockHelpers is a mock of Helpers interface. +type MockHelpers struct { + ctrl *gomock.Controller + recorder *MockHelpersMockRecorder +} + +// MockHelpersMockRecorder is the mock recorder for MockHelpers. +type MockHelpersMockRecorder struct { + mock *MockHelpers +} + +// NewMockHelpers creates a new mock instance. +func NewMockHelpers(ctrl *gomock.Controller) *MockHelpers { + mock := &MockHelpers{ctrl: ctrl} + mock.recorder = &MockHelpersMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockHelpers) EXPECT() *MockHelpersMockRecorder { + return m.recorder +} + +// ISGOMOCK indicates that this struct is a gomock mock. +func (m *MockHelpers) ISGOMOCK() struct{} { + return struct{}{} +} + +// ContainersSecretName mocks base method. +func (m *MockHelpers) ContainersSecretName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ContainersSecretName") + ret0, _ := ret[0].(string) + return ret0 +} + +// ContainersSecretName indicates an expected call of ContainersSecretName. +func (mr *MockHelpersMockRecorder) ContainersSecretName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ContainersSecretName", reflect.TypeOf((*MockHelpers)(nil).ContainersSecretName)) +} + +// HeadlessServiceName mocks base method. +func (m *MockHelpers) HeadlessServiceName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "HeadlessServiceName") + ret0, _ := ret[0].(string) + return ret0 +} + +// HeadlessServiceName indicates an expected call of HeadlessServiceName. +func (mr *MockHelpersMockRecorder) HeadlessServiceName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HeadlessServiceName", reflect.TypeOf((*MockHelpers)(nil).HeadlessServiceName)) +} + +// ImagePullSecrets mocks base method. +func (m *MockHelpers) ImagePullSecrets() []v1.LocalObjectReference { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ImagePullSecrets") + ret0, _ := ret[0].([]v1.LocalObjectReference) + return ret0 +} + +// ImagePullSecrets indicates an expected call of ImagePullSecrets. +func (mr *MockHelpersMockRecorder) ImagePullSecrets() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ImagePullSecrets", reflect.TypeOf((*MockHelpers)(nil).ImagePullSecrets)) +} + +// K8sSensorResourcesName mocks base method. +func (m *MockHelpers) K8sSensorResourcesName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "K8sSensorResourcesName") + ret0, _ := ret[0].(string) + return ret0 +} + +// K8sSensorResourcesName indicates an expected call of K8sSensorResourcesName. +func (mr *MockHelpersMockRecorder) K8sSensorResourcesName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "K8sSensorResourcesName", reflect.TypeOf((*MockHelpers)(nil).K8sSensorResourcesName)) +} + +// KeysSecretName mocks base method. +func (m *MockHelpers) KeysSecretName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "KeysSecretName") + ret0, _ := ret[0].(string) + return ret0 +} + +// KeysSecretName indicates an expected call of KeysSecretName. +func (mr *MockHelpersMockRecorder) KeysSecretName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "KeysSecretName", reflect.TypeOf((*MockHelpers)(nil).KeysSecretName)) +} + +// ServiceAccountName mocks base method. +func (m *MockHelpers) ServiceAccountName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ServiceAccountName") + ret0, _ := ret[0].(string) + return ret0 +} + +// ServiceAccountName indicates an expected call of ServiceAccountName. +func (mr *MockHelpersMockRecorder) ServiceAccountName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ServiceAccountName", reflect.TypeOf((*MockHelpers)(nil).ServiceAccountName)) +} + +// TLSIsEnabled mocks base method. +func (m *MockHelpers) TLSIsEnabled() bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "TLSIsEnabled") + ret0, _ := ret[0].(bool) + return ret0 +} + +// TLSIsEnabled indicates an expected call of TLSIsEnabled. +func (mr *MockHelpersMockRecorder) TLSIsEnabled() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TLSIsEnabled", reflect.TypeOf((*MockHelpers)(nil).TLSIsEnabled)) +} + +// TLSSecretName mocks base method. +func (m *MockHelpers) TLSSecretName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "TLSSecretName") + ret0, _ := ret[0].(string) + return ret0 +} + +// TLSSecretName indicates an expected call of TLSSecretName. +func (mr *MockHelpersMockRecorder) TLSSecretName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TLSSecretName", reflect.TypeOf((*MockHelpers)(nil).TLSSecretName)) +} + +// UseContainersSecret mocks base method. +func (m *MockHelpers) UseContainersSecret() bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UseContainersSecret") + ret0, _ := ret[0].(bool) + return ret0 +} + +// UseContainersSecret indicates an expected call of UseContainersSecret. +func (mr *MockHelpersMockRecorder) UseContainersSecret() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UseContainersSecret", reflect.TypeOf((*MockHelpers)(nil).UseContainersSecret)) +} diff --git a/pkg/k8s/object/builders/k8s-sensor/poddisruptionbudget/pod_selector_mock_test.go b/pkg/k8s/object/builders/k8s-sensor/poddisruptionbudget/pod_selector_mock_test.go new file mode 100644 index 00000000..b2f21e9b --- /dev/null +++ b/pkg/k8s/object/builders/k8s-sensor/poddisruptionbudget/pod_selector_mock_test.go @@ -0,0 +1,82 @@ +// /* +// (c) Copyright IBM Corp. 2024 +// (c) Copyright Instana Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// */ +// + +// Code generated by MockGen. DO NOT EDIT. +package poddisruptionbudget + +import ( + reflect "reflect" + + gomock "go.uber.org/mock/gomock" +) + +// MockPodSelectorLabelGenerator is a mock of PodSelectorLabelGenerator interface. +type MockPodSelectorLabelGenerator struct { + ctrl *gomock.Controller + recorder *MockPodSelectorLabelGeneratorMockRecorder +} + +// MockPodSelectorLabelGeneratorMockRecorder is the mock recorder for MockPodSelectorLabelGenerator. +type MockPodSelectorLabelGeneratorMockRecorder struct { + mock *MockPodSelectorLabelGenerator +} + +// NewMockPodSelectorLabelGenerator creates a new mock instance. +func NewMockPodSelectorLabelGenerator(ctrl *gomock.Controller) *MockPodSelectorLabelGenerator { + mock := &MockPodSelectorLabelGenerator{ctrl: ctrl} + mock.recorder = &MockPodSelectorLabelGeneratorMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockPodSelectorLabelGenerator) EXPECT() *MockPodSelectorLabelGeneratorMockRecorder { + return m.recorder +} + +// ISGOMOCK indicates that this struct is a gomock mock. +func (m *MockPodSelectorLabelGenerator) ISGOMOCK() struct{} { + return struct{}{} +} + +// GetPodLabels mocks base method. +func (m *MockPodSelectorLabelGenerator) GetPodLabels(userLabels map[string]string) map[string]string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetPodLabels", userLabels) + ret0, _ := ret[0].(map[string]string) + return ret0 +} + +// GetPodLabels indicates an expected call of GetPodLabels. +func (mr *MockPodSelectorLabelGeneratorMockRecorder) GetPodLabels(userLabels any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPodLabels", reflect.TypeOf((*MockPodSelectorLabelGenerator)(nil).GetPodLabels), userLabels) +} + +// GetPodSelectorLabels mocks base method. +func (m *MockPodSelectorLabelGenerator) GetPodSelectorLabels() map[string]string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetPodSelectorLabels") + ret0, _ := ret[0].(map[string]string) + return ret0 +} + +// GetPodSelectorLabels indicates an expected call of GetPodSelectorLabels. +func (mr *MockPodSelectorLabelGeneratorMockRecorder) GetPodSelectorLabels() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPodSelectorLabels", reflect.TypeOf((*MockPodSelectorLabelGenerator)(nil).GetPodSelectorLabels)) +} diff --git a/pkg/k8s/object/builders/k8s-sensor/poddisruptionbudget/poddisruptionbudget.go b/pkg/k8s/object/builders/k8s-sensor/poddisruptionbudget/poddisruptionbudget.go new file mode 100644 index 00000000..fc6ebea7 --- /dev/null +++ b/pkg/k8s/object/builders/k8s-sensor/poddisruptionbudget/poddisruptionbudget.go @@ -0,0 +1,68 @@ +package poddisruptionbudget + +import ( + v1 "k8s.io/api/policy/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/intstr" + "sigs.k8s.io/controller-runtime/pkg/client" + + instanav1 "github.com/instana/instana-agent-operator/api/v1" + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/builder" + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/constants" + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/helpers" + "github.com/instana/instana-agent-operator/pkg/k8s/object/transformations" + "github.com/instana/instana-agent-operator/pkg/optional" + "github.com/instana/instana-agent-operator/pkg/pointer" +) + +const componentName = constants.ComponentK8Sensor + +type podDisruptionBudgetBuilder struct { + *instanav1.InstanaAgent + + helpers.Helpers + transformations.PodSelectorLabelGenerator +} + +func (p *podDisruptionBudgetBuilder) build() *v1.PodDisruptionBudget { + return &v1.PodDisruptionBudget{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "policy/v1", + Kind: "PodDisruptionBudget", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: p.K8sSensorResourcesName(), + Namespace: p.Namespace, + }, + Spec: v1.PodDisruptionBudgetSpec{ + Selector: &metav1.LabelSelector{ + MatchLabels: p.GetPodSelectorLabels(), + }, + MinAvailable: pointer.To(intstr.FromInt32(int32(p.Spec.K8sSensor.DeploymentSpec.Replicas) - 1)), + }, + } +} + +func (p *podDisruptionBudgetBuilder) Build() builder.OptionalObject { + if pointer.DerefOrEmpty(p.Spec.K8sSensor.PodDisruptionBudget.Enabled) && p.Spec.K8sSensor.DeploymentSpec.Replicas > 1 { + return optional.Of[client.Object](p.build()) + } else { + return optional.Empty[client.Object]() + } +} + +func (p *podDisruptionBudgetBuilder) ComponentName() string { + return componentName +} + +func (p *podDisruptionBudgetBuilder) IsNamespaced() bool { + return true +} + +func NewPodDisruptionBudgetBuilder(agent *instanav1.InstanaAgent) builder.ObjectBuilder { + return &podDisruptionBudgetBuilder{ + InstanaAgent: agent, + Helpers: helpers.NewHelpers(agent), + PodSelectorLabelGenerator: transformations.PodSelectorLabels(agent, componentName), + } +} diff --git a/pkg/k8s/object/builders/k8s-sensor/poddisruptionbudget/poddisruptionbudget_test.go b/pkg/k8s/object/builders/k8s-sensor/poddisruptionbudget/poddisruptionbudget_test.go new file mode 100644 index 00000000..b91ca9de --- /dev/null +++ b/pkg/k8s/object/builders/k8s-sensor/poddisruptionbudget/poddisruptionbudget_test.go @@ -0,0 +1,160 @@ +/* +(c) Copyright IBM Corp. 2024 +(c) Copyright Instana Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package poddisruptionbudget + +import ( + "testing" + + "github.com/stretchr/testify/assert" + gomock "go.uber.org/mock/gomock" + policyv1 "k8s.io/api/policy/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/intstr" + "k8s.io/apimachinery/pkg/util/rand" + "sigs.k8s.io/controller-runtime/pkg/client" + + instanav1 "github.com/instana/instana-agent-operator/api/v1" + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/constants" + "github.com/instana/instana-agent-operator/pkg/optional" + "github.com/instana/instana-agent-operator/pkg/pointer" +) + +func TestPodDisruptionBudgetBuilder_IsNamespaced_ComponentName(t *testing.T) { + assertions := assert.New(t) + + pdbBuilder := NewPodDisruptionBudgetBuilder(nil) + + assertions.True(pdbBuilder.IsNamespaced()) + assertions.Equal(constants.ComponentK8Sensor, pdbBuilder.ComponentName()) +} + +func TestPodDisruptionBudgetBuilder_Build(t *testing.T) { + agentName := rand.String(10) + agentNamespace := rand.String(10) + expectedPdbName := rand.String(10) + expectedMatchLabels := map[string]string{ + rand.String(10): rand.String(10), + rand.String(10): rand.String(10), + rand.String(10): rand.String(10), + } + numReplicas := rand.Int63nRange(2, 1000) + + for _, test := range []struct { + name string + agent *instanav1.InstanaAgent + expected optional.Optional[client.Object] + }{ + { + name: "pdb_enablement_unset", + agent: &instanav1.InstanaAgent{}, + expected: optional.Empty[client.Object](), + }, + { + name: "pdb_explicitly_disabled", + agent: &instanav1.InstanaAgent{ + Spec: instanav1.InstanaAgentSpec{ + K8sSensor: instanav1.K8sSpec{ + PodDisruptionBudget: instanav1.Enabled{Enabled: pointer.To(false)}, + }, + }, + }, + expected: optional.Empty[client.Object](), + }, + { + name: "pdb_enabled_but_replicas_is_zero", + agent: &instanav1.InstanaAgent{ + Spec: instanav1.InstanaAgentSpec{ + K8sSensor: instanav1.K8sSpec{ + PodDisruptionBudget: instanav1.Enabled{Enabled: pointer.To(true)}, + }, + }, + }, + expected: optional.Empty[client.Object](), + }, + { + name: "pdb_enabled_but_replicas_is_one", + agent: &instanav1.InstanaAgent{ + Spec: instanav1.InstanaAgentSpec{ + K8sSensor: instanav1.K8sSpec{ + PodDisruptionBudget: instanav1.Enabled{Enabled: pointer.To(true)}, + DeploymentSpec: instanav1.KubernetesDeploymentSpec{Replicas: 1}, + }, + }, + }, + expected: optional.Empty[client.Object](), + }, + { + name: "pdb_enabled_and_replicas_at_greater_than_one", + agent: &instanav1.InstanaAgent{ + ObjectMeta: metav1.ObjectMeta{ + Name: agentName, + Namespace: agentNamespace, + }, + Spec: instanav1.InstanaAgentSpec{ + K8sSensor: instanav1.K8sSpec{ + DeploymentSpec: instanav1.KubernetesDeploymentSpec{Replicas: int(numReplicas)}, + PodDisruptionBudget: instanav1.Enabled{Enabled: pointer.To(true)}, + }, + }, + }, + expected: optional.Of[client.Object]( + &policyv1.PodDisruptionBudget{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "policy/v1", + Kind: "PodDisruptionBudget", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: expectedPdbName, + Namespace: agentNamespace, + }, + Spec: policyv1.PodDisruptionBudgetSpec{ + Selector: &metav1.LabelSelector{ + MatchLabels: expectedMatchLabels, + }, + MinAvailable: pointer.To(intstr.FromInt32(int32(numReplicas) - 1)), + }, + }, + ), + }, + } { + t.Run( + test.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + + helpers := NewMockHelpers(ctrl) + podSelectorGen := NewMockPodSelectorLabelGenerator(ctrl) + + pdbBuilder := &podDisruptionBudgetBuilder{ + InstanaAgent: test.agent, + Helpers: helpers, + PodSelectorLabelGenerator: podSelectorGen, + } + + test.expected.IfPresent( + func(_ client.Object) { + helpers.EXPECT().K8sSensorResourcesName().Return(expectedPdbName) + podSelectorGen.EXPECT().GetPodSelectorLabels().Return(expectedMatchLabels) + }, + ) + + actual := pdbBuilder.Build() + assert.Equal(t, test.expected, actual) + }, + ) + } +} diff --git a/pkg/k8s/object/builders/k8s-sensor/rbac/clusterrole.go b/pkg/k8s/object/builders/k8s-sensor/rbac/clusterrole.go new file mode 100644 index 00000000..93e463f1 --- /dev/null +++ b/pkg/k8s/object/builders/k8s-sensor/rbac/clusterrole.go @@ -0,0 +1,119 @@ +package rbac + +import ( + rbacv1 "k8s.io/api/rbac/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + instanav1 "github.com/instana/instana-agent-operator/api/v1" + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/builder" + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/constants" + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/helpers" + "github.com/instana/instana-agent-operator/pkg/optional" +) + +func readerVerbs() []string { + return []string{"get", "list", "watch"} +} + +type clusterRoleBuilder struct { + *instanav1.InstanaAgent + helpers.Helpers +} + +func (c *clusterRoleBuilder) ComponentName() string { + return constants.ComponentK8Sensor +} + +func (c *clusterRoleBuilder) IsNamespaced() bool { + return false +} + +func (c *clusterRoleBuilder) Build() optional.Optional[client.Object] { + return optional.Of[client.Object]( + &rbacv1.ClusterRole{ + TypeMeta: metav1.TypeMeta{ + APIVersion: rbacApiVersion, + Kind: roleKind, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: c.K8sSensorResourcesName(), + }, + Rules: []rbacv1.PolicyRule{ + { + NonResourceURLs: []string{"/version", "/healthz"}, + Verbs: []string{"get"}, + APIGroups: []string{}, + Resources: []string{}, + }, + { + APIGroups: []string{"extensions"}, + Resources: []string{"deployments", "replicasets", "ingresses"}, + Verbs: readerVerbs(), + }, + { + APIGroups: []string{""}, + Resources: []string{ + "configmaps", + "events", + "services", + "endpoints", + "namespaces", + "nodes", + "pods", + "pods/log", + "replicationcontrollers", + "resourcequotas", + "persistentvolumes", + "persistentvolumeclaims", + }, + Verbs: readerVerbs(), + }, + { + APIGroups: []string{"apps"}, + Resources: []string{"daemonsets", "deployments", "replicasets", "statefulsets"}, + Verbs: readerVerbs(), + }, + { + APIGroups: []string{"batch"}, + Resources: []string{"cronjobs", "jobs"}, + Verbs: readerVerbs(), + }, + { + APIGroups: []string{"networking.k8s.io"}, + Resources: []string{"ingresses"}, + Verbs: readerVerbs(), + }, + { + APIGroups: []string{"autoscaling"}, + Resources: []string{"horizontalpodautoscalers"}, + Verbs: readerVerbs(), + }, + { + APIGroups: []string{"apps.openshift.io"}, + Resources: []string{"deploymentconfigs"}, + Verbs: readerVerbs(), + }, + { + APIGroups: []string{"security.openshift.io"}, + ResourceNames: []string{"privileged"}, + Resources: []string{"securitycontextconstraints"}, + Verbs: []string{"use"}, + }, + { + APIGroups: []string{"policy"}, + ResourceNames: []string{c.K8sSensorResourcesName()}, + Resources: []string{"podsecuritypolicies"}, + Verbs: []string{"use"}, + }, + }, + }, + ) +} + +func NewClusterRoleBuilder(agent *instanav1.InstanaAgent) builder.ObjectBuilder { + return &clusterRoleBuilder{ + InstanaAgent: agent, + Helpers: helpers.NewHelpers(agent), + } +} diff --git a/pkg/k8s/object/builders/k8s-sensor/rbac/clusterrole_test.go b/pkg/k8s/object/builders/k8s-sensor/rbac/clusterrole_test.go new file mode 100644 index 00000000..964d02b9 --- /dev/null +++ b/pkg/k8s/object/builders/k8s-sensor/rbac/clusterrole_test.go @@ -0,0 +1,139 @@ +/* +(c) Copyright IBM Corp. 2024 +(c) Copyright Instana Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package rbac + +import ( + "testing" + + "github.com/stretchr/testify/require" + gomock "go.uber.org/mock/gomock" + rbacv1 "k8s.io/api/rbac/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/rand" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/constants" + "github.com/instana/instana-agent-operator/pkg/optional" +) + +func TestClusterRoleBuilder_IsNamespaced_ComponentName(t *testing.T) { + assertions := require.New(t) + + cb := NewClusterRoleBuilder(nil) + + assertions.False(cb.IsNamespaced()) + assertions.Equal(constants.ComponentK8Sensor, cb.ComponentName()) +} + +func TestClusterRoleBuilder_Build(t *testing.T) { + assertions := require.New(t) + ctrl := gomock.NewController(t) + + sensorResourcesName := rand.String(10) + + expected := optional.Of[client.Object]( + &rbacv1.ClusterRole{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "rbac.authorization.k8s.io/v1", + Kind: "ClusterRole", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: sensorResourcesName, + }, + Rules: []rbacv1.PolicyRule{ + { + NonResourceURLs: []string{"/version", "/healthz"}, + Verbs: []string{"get"}, + APIGroups: []string{}, + Resources: []string{}, + }, + { + APIGroups: []string{"extensions"}, + Resources: []string{"deployments", "replicasets", "ingresses"}, + Verbs: []string{"get", "list", "watch"}, + }, + { + APIGroups: []string{""}, + Resources: []string{ + "configmaps", + "events", + "services", + "endpoints", + "namespaces", + "nodes", + "pods", + "pods/log", + "replicationcontrollers", + "resourcequotas", + "persistentvolumes", + "persistentvolumeclaims", + }, + Verbs: []string{"get", "list", "watch"}, + }, + { + APIGroups: []string{"apps"}, + Resources: []string{"daemonsets", "deployments", "replicasets", "statefulsets"}, + Verbs: []string{"get", "list", "watch"}, + }, + { + APIGroups: []string{"batch"}, + Resources: []string{"cronjobs", "jobs"}, + Verbs: []string{"get", "list", "watch"}, + }, + { + APIGroups: []string{"networking.k8s.io"}, + Resources: []string{"ingresses"}, + Verbs: []string{"get", "list", "watch"}, + }, + { + APIGroups: []string{"autoscaling"}, + Resources: []string{"horizontalpodautoscalers"}, + Verbs: []string{"get", "list", "watch"}, + }, + { + APIGroups: []string{"apps.openshift.io"}, + Resources: []string{"deploymentconfigs"}, + Verbs: []string{"get", "list", "watch"}, + }, + { + APIGroups: []string{"security.openshift.io"}, + ResourceNames: []string{"privileged"}, + Resources: []string{"securitycontextconstraints"}, + Verbs: []string{"use"}, + }, + { + APIGroups: []string{"policy"}, + ResourceNames: []string{sensorResourcesName}, + Resources: []string{"podsecuritypolicies"}, + Verbs: []string{"use"}, + }, + }, + }, + ) + + helpers := NewMockHelpers(ctrl) + helpers.EXPECT().K8sSensorResourcesName().Return(sensorResourcesName).Times(2) + + cb := &clusterRoleBuilder{ + Helpers: helpers, + } + + actual := cb.Build() + + assertions.Equal(expected, actual) +} diff --git a/pkg/k8s/object/builders/k8s-sensor/rbac/clusterrolebinding.go b/pkg/k8s/object/builders/k8s-sensor/rbac/clusterrolebinding.go new file mode 100644 index 00000000..7ecc55a1 --- /dev/null +++ b/pkg/k8s/object/builders/k8s-sensor/rbac/clusterrolebinding.go @@ -0,0 +1,59 @@ +package rbac + +import ( + rbacv1 "k8s.io/api/rbac/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + instanav1 "github.com/instana/instana-agent-operator/api/v1" + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/builder" + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/constants" + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/helpers" + "github.com/instana/instana-agent-operator/pkg/optional" +) + +type clusterRoleBindingBuilder struct { + *instanav1.InstanaAgent + helpers.Helpers +} + +func (c *clusterRoleBindingBuilder) IsNamespaced() bool { + return false +} + +func (c *clusterRoleBindingBuilder) ComponentName() string { + return constants.ComponentK8Sensor +} + +func (c *clusterRoleBindingBuilder) Build() optional.Optional[client.Object] { + return optional.Of[client.Object]( + &rbacv1.ClusterRoleBinding{ + TypeMeta: metav1.TypeMeta{ + APIVersion: rbacApiVersion, + Kind: "ClusterRoleBinding", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: c.K8sSensorResourcesName(), + }, + RoleRef: rbacv1.RoleRef{ + APIGroup: rbacApiGroup, + Kind: roleKind, + Name: c.K8sSensorResourcesName(), + }, + Subjects: []rbacv1.Subject{ + { + Kind: subjectKind, + Name: c.K8sSensorResourcesName(), + Namespace: c.Namespace, + }, + }, + }, + ) +} + +func NewClusterRoleBindingBuilder(agent *instanav1.InstanaAgent) builder.ObjectBuilder { + return &clusterRoleBindingBuilder{ + InstanaAgent: agent, + Helpers: helpers.NewHelpers(agent), + } +} diff --git a/pkg/k8s/object/builders/k8s-sensor/rbac/clusterrolebinding_test.go b/pkg/k8s/object/builders/k8s-sensor/rbac/clusterrolebinding_test.go new file mode 100644 index 00000000..76303da8 --- /dev/null +++ b/pkg/k8s/object/builders/k8s-sensor/rbac/clusterrolebinding_test.go @@ -0,0 +1,92 @@ +/* +(c) Copyright IBM Corp. 2024 +(c) Copyright Instana Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package rbac + +import ( + "testing" + + "github.com/stretchr/testify/require" + gomock "go.uber.org/mock/gomock" + rbacv1 "k8s.io/api/rbac/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/rand" + "sigs.k8s.io/controller-runtime/pkg/client" + + instanav1 "github.com/instana/instana-agent-operator/api/v1" + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/constants" + "github.com/instana/instana-agent-operator/pkg/optional" +) + +func TestClusterRoleBindingBuilder_IsNamespaced_ComponentName(t *testing.T) { + assertions := require.New(t) + + crb := NewClusterRoleBindingBuilder(nil) + + assertions.False(crb.IsNamespaced()) + assertions.Equal(constants.ComponentK8Sensor, crb.ComponentName()) +} + +func TestClusterRoleBindingBuilder_Build(t *testing.T) { + assertions := require.New(t) + ctrl := gomock.NewController(t) + + sensorResourcesName := rand.String(10) + namespace := rand.String(10) + + agent := &instanav1.InstanaAgent{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + }, + } + + expected := optional.Of[client.Object]( + &rbacv1.ClusterRoleBinding{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "rbac.authorization.k8s.io/v1", + Kind: "ClusterRoleBinding", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: sensorResourcesName, + }, + RoleRef: rbacv1.RoleRef{ + APIGroup: "rbac.authorization.k8s.io", + Kind: "ClusterRole", + Name: sensorResourcesName, + }, + Subjects: []rbacv1.Subject{ + { + Kind: "ServiceAccount", + Name: sensorResourcesName, + Namespace: namespace, + }, + }, + }, + ) + + helpers := NewMockHelpers(ctrl) + helpers.EXPECT().K8sSensorResourcesName().Return(sensorResourcesName).Times(3) + + crb := &clusterRoleBindingBuilder{ + InstanaAgent: agent, + Helpers: helpers, + } + + actual := crb.Build() + + assertions.Equal(expected, actual) +} diff --git a/pkg/k8s/object/builders/k8s-sensor/rbac/constants.go b/pkg/k8s/object/builders/k8s-sensor/rbac/constants.go new file mode 100644 index 00000000..27cb7893 --- /dev/null +++ b/pkg/k8s/object/builders/k8s-sensor/rbac/constants.go @@ -0,0 +1,8 @@ +package rbac + +const ( + rbacApiGroup = "rbac.authorization.k8s.io" + rbacApiVersion = rbacApiGroup + "/v1" + roleKind = "ClusterRole" + subjectKind = "ServiceAccount" +) diff --git a/pkg/k8s/object/builders/k8s-sensor/rbac/helpers_mock_test.go b/pkg/k8s/object/builders/k8s-sensor/rbac/helpers_mock_test.go new file mode 100644 index 00000000..d511db07 --- /dev/null +++ b/pkg/k8s/object/builders/k8s-sensor/rbac/helpers_mock_test.go @@ -0,0 +1,181 @@ +// /* +// (c) Copyright IBM Corp. 2024 +// (c) Copyright Instana Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// */ +// + +// Code generated by MockGen. DO NOT EDIT. +package rbac + +import ( + reflect "reflect" + + gomock "go.uber.org/mock/gomock" + v1 "k8s.io/api/core/v1" +) + +// MockHelpers is a mock of Helpers interface. +type MockHelpers struct { + ctrl *gomock.Controller + recorder *MockHelpersMockRecorder +} + +// MockHelpersMockRecorder is the mock recorder for MockHelpers. +type MockHelpersMockRecorder struct { + mock *MockHelpers +} + +// NewMockHelpers creates a new mock instance. +func NewMockHelpers(ctrl *gomock.Controller) *MockHelpers { + mock := &MockHelpers{ctrl: ctrl} + mock.recorder = &MockHelpersMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockHelpers) EXPECT() *MockHelpersMockRecorder { + return m.recorder +} + +// ISGOMOCK indicates that this struct is a gomock mock. +func (m *MockHelpers) ISGOMOCK() struct{} { + return struct{}{} +} + +// ContainersSecretName mocks base method. +func (m *MockHelpers) ContainersSecretName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ContainersSecretName") + ret0, _ := ret[0].(string) + return ret0 +} + +// ContainersSecretName indicates an expected call of ContainersSecretName. +func (mr *MockHelpersMockRecorder) ContainersSecretName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ContainersSecretName", reflect.TypeOf((*MockHelpers)(nil).ContainersSecretName)) +} + +// HeadlessServiceName mocks base method. +func (m *MockHelpers) HeadlessServiceName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "HeadlessServiceName") + ret0, _ := ret[0].(string) + return ret0 +} + +// HeadlessServiceName indicates an expected call of HeadlessServiceName. +func (mr *MockHelpersMockRecorder) HeadlessServiceName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HeadlessServiceName", reflect.TypeOf((*MockHelpers)(nil).HeadlessServiceName)) +} + +// ImagePullSecrets mocks base method. +func (m *MockHelpers) ImagePullSecrets() []v1.LocalObjectReference { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ImagePullSecrets") + ret0, _ := ret[0].([]v1.LocalObjectReference) + return ret0 +} + +// ImagePullSecrets indicates an expected call of ImagePullSecrets. +func (mr *MockHelpersMockRecorder) ImagePullSecrets() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ImagePullSecrets", reflect.TypeOf((*MockHelpers)(nil).ImagePullSecrets)) +} + +// K8sSensorResourcesName mocks base method. +func (m *MockHelpers) K8sSensorResourcesName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "K8sSensorResourcesName") + ret0, _ := ret[0].(string) + return ret0 +} + +// K8sSensorResourcesName indicates an expected call of K8sSensorResourcesName. +func (mr *MockHelpersMockRecorder) K8sSensorResourcesName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "K8sSensorResourcesName", reflect.TypeOf((*MockHelpers)(nil).K8sSensorResourcesName)) +} + +// KeysSecretName mocks base method. +func (m *MockHelpers) KeysSecretName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "KeysSecretName") + ret0, _ := ret[0].(string) + return ret0 +} + +// KeysSecretName indicates an expected call of KeysSecretName. +func (mr *MockHelpersMockRecorder) KeysSecretName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "KeysSecretName", reflect.TypeOf((*MockHelpers)(nil).KeysSecretName)) +} + +// ServiceAccountName mocks base method. +func (m *MockHelpers) ServiceAccountName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ServiceAccountName") + ret0, _ := ret[0].(string) + return ret0 +} + +// ServiceAccountName indicates an expected call of ServiceAccountName. +func (mr *MockHelpersMockRecorder) ServiceAccountName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ServiceAccountName", reflect.TypeOf((*MockHelpers)(nil).ServiceAccountName)) +} + +// TLSIsEnabled mocks base method. +func (m *MockHelpers) TLSIsEnabled() bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "TLSIsEnabled") + ret0, _ := ret[0].(bool) + return ret0 +} + +// TLSIsEnabled indicates an expected call of TLSIsEnabled. +func (mr *MockHelpersMockRecorder) TLSIsEnabled() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TLSIsEnabled", reflect.TypeOf((*MockHelpers)(nil).TLSIsEnabled)) +} + +// TLSSecretName mocks base method. +func (m *MockHelpers) TLSSecretName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "TLSSecretName") + ret0, _ := ret[0].(string) + return ret0 +} + +// TLSSecretName indicates an expected call of TLSSecretName. +func (mr *MockHelpersMockRecorder) TLSSecretName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TLSSecretName", reflect.TypeOf((*MockHelpers)(nil).TLSSecretName)) +} + +// UseContainersSecret mocks base method. +func (m *MockHelpers) UseContainersSecret() bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UseContainersSecret") + ret0, _ := ret[0].(bool) + return ret0 +} + +// UseContainersSecret indicates an expected call of UseContainersSecret. +func (mr *MockHelpersMockRecorder) UseContainersSecret() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UseContainersSecret", reflect.TypeOf((*MockHelpers)(nil).UseContainersSecret)) +} diff --git a/pkg/k8s/object/builders/k8s-sensor/serviceaccount/helpers_mock_test.go b/pkg/k8s/object/builders/k8s-sensor/serviceaccount/helpers_mock_test.go new file mode 100644 index 00000000..9c076305 --- /dev/null +++ b/pkg/k8s/object/builders/k8s-sensor/serviceaccount/helpers_mock_test.go @@ -0,0 +1,181 @@ +// /* +// (c) Copyright IBM Corp. 2024 +// (c) Copyright Instana Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// */ +// + +// Code generated by MockGen. DO NOT EDIT. +package serviceaccount + +import ( + reflect "reflect" + + gomock "go.uber.org/mock/gomock" + v1 "k8s.io/api/core/v1" +) + +// MockHelpers is a mock of Helpers interface. +type MockHelpers struct { + ctrl *gomock.Controller + recorder *MockHelpersMockRecorder +} + +// MockHelpersMockRecorder is the mock recorder for MockHelpers. +type MockHelpersMockRecorder struct { + mock *MockHelpers +} + +// NewMockHelpers creates a new mock instance. +func NewMockHelpers(ctrl *gomock.Controller) *MockHelpers { + mock := &MockHelpers{ctrl: ctrl} + mock.recorder = &MockHelpersMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockHelpers) EXPECT() *MockHelpersMockRecorder { + return m.recorder +} + +// ISGOMOCK indicates that this struct is a gomock mock. +func (m *MockHelpers) ISGOMOCK() struct{} { + return struct{}{} +} + +// ContainersSecretName mocks base method. +func (m *MockHelpers) ContainersSecretName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ContainersSecretName") + ret0, _ := ret[0].(string) + return ret0 +} + +// ContainersSecretName indicates an expected call of ContainersSecretName. +func (mr *MockHelpersMockRecorder) ContainersSecretName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ContainersSecretName", reflect.TypeOf((*MockHelpers)(nil).ContainersSecretName)) +} + +// HeadlessServiceName mocks base method. +func (m *MockHelpers) HeadlessServiceName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "HeadlessServiceName") + ret0, _ := ret[0].(string) + return ret0 +} + +// HeadlessServiceName indicates an expected call of HeadlessServiceName. +func (mr *MockHelpersMockRecorder) HeadlessServiceName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HeadlessServiceName", reflect.TypeOf((*MockHelpers)(nil).HeadlessServiceName)) +} + +// ImagePullSecrets mocks base method. +func (m *MockHelpers) ImagePullSecrets() []v1.LocalObjectReference { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ImagePullSecrets") + ret0, _ := ret[0].([]v1.LocalObjectReference) + return ret0 +} + +// ImagePullSecrets indicates an expected call of ImagePullSecrets. +func (mr *MockHelpersMockRecorder) ImagePullSecrets() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ImagePullSecrets", reflect.TypeOf((*MockHelpers)(nil).ImagePullSecrets)) +} + +// K8sSensorResourcesName mocks base method. +func (m *MockHelpers) K8sSensorResourcesName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "K8sSensorResourcesName") + ret0, _ := ret[0].(string) + return ret0 +} + +// K8sSensorResourcesName indicates an expected call of K8sSensorResourcesName. +func (mr *MockHelpersMockRecorder) K8sSensorResourcesName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "K8sSensorResourcesName", reflect.TypeOf((*MockHelpers)(nil).K8sSensorResourcesName)) +} + +// KeysSecretName mocks base method. +func (m *MockHelpers) KeysSecretName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "KeysSecretName") + ret0, _ := ret[0].(string) + return ret0 +} + +// KeysSecretName indicates an expected call of KeysSecretName. +func (mr *MockHelpersMockRecorder) KeysSecretName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "KeysSecretName", reflect.TypeOf((*MockHelpers)(nil).KeysSecretName)) +} + +// ServiceAccountName mocks base method. +func (m *MockHelpers) ServiceAccountName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ServiceAccountName") + ret0, _ := ret[0].(string) + return ret0 +} + +// ServiceAccountName indicates an expected call of ServiceAccountName. +func (mr *MockHelpersMockRecorder) ServiceAccountName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ServiceAccountName", reflect.TypeOf((*MockHelpers)(nil).ServiceAccountName)) +} + +// TLSIsEnabled mocks base method. +func (m *MockHelpers) TLSIsEnabled() bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "TLSIsEnabled") + ret0, _ := ret[0].(bool) + return ret0 +} + +// TLSIsEnabled indicates an expected call of TLSIsEnabled. +func (mr *MockHelpersMockRecorder) TLSIsEnabled() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TLSIsEnabled", reflect.TypeOf((*MockHelpers)(nil).TLSIsEnabled)) +} + +// TLSSecretName mocks base method. +func (m *MockHelpers) TLSSecretName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "TLSSecretName") + ret0, _ := ret[0].(string) + return ret0 +} + +// TLSSecretName indicates an expected call of TLSSecretName. +func (mr *MockHelpersMockRecorder) TLSSecretName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TLSSecretName", reflect.TypeOf((*MockHelpers)(nil).TLSSecretName)) +} + +// UseContainersSecret mocks base method. +func (m *MockHelpers) UseContainersSecret() bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UseContainersSecret") + ret0, _ := ret[0].(bool) + return ret0 +} + +// UseContainersSecret indicates an expected call of UseContainersSecret. +func (mr *MockHelpersMockRecorder) UseContainersSecret() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UseContainersSecret", reflect.TypeOf((*MockHelpers)(nil).UseContainersSecret)) +} diff --git a/pkg/k8s/object/builders/k8s-sensor/serviceaccount/serviceaccount.go b/pkg/k8s/object/builders/k8s-sensor/serviceaccount/serviceaccount.go new file mode 100644 index 00000000..09350c32 --- /dev/null +++ b/pkg/k8s/object/builders/k8s-sensor/serviceaccount/serviceaccount.go @@ -0,0 +1,48 @@ +package serviceaccount + +import ( + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + instanav1 "github.com/instana/instana-agent-operator/api/v1" + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/builder" + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/constants" + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/helpers" + "github.com/instana/instana-agent-operator/pkg/optional" +) + +type serviceAccountBuilder struct { + *instanav1.InstanaAgent + helpers.Helpers +} + +func (s *serviceAccountBuilder) IsNamespaced() bool { + return true +} + +func (s *serviceAccountBuilder) ComponentName() string { + return constants.ComponentK8Sensor +} + +func (s *serviceAccountBuilder) Build() optional.Optional[client.Object] { + return optional.Of[client.Object]( + &corev1.ServiceAccount{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "ServiceAccount", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: s.K8sSensorResourcesName(), + Namespace: s.Namespace, + }, + }, + ) +} + +func NewServiceAccountBuilder(agent *instanav1.InstanaAgent) builder.ObjectBuilder { + return &serviceAccountBuilder{ + InstanaAgent: agent, + Helpers: helpers.NewHelpers(agent), + } +} diff --git a/pkg/k8s/object/builders/k8s-sensor/serviceaccount/serviceaccount_test.go b/pkg/k8s/object/builders/k8s-sensor/serviceaccount/serviceaccount_test.go new file mode 100644 index 00000000..8c2a90da --- /dev/null +++ b/pkg/k8s/object/builders/k8s-sensor/serviceaccount/serviceaccount_test.go @@ -0,0 +1,81 @@ +/* +(c) Copyright IBM Corp. 2024 +(c) Copyright Instana Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package serviceaccount + +import ( + "testing" + + "github.com/stretchr/testify/require" + gomock "go.uber.org/mock/gomock" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/rand" + "sigs.k8s.io/controller-runtime/pkg/client" + + instanav1 "github.com/instana/instana-agent-operator/api/v1" + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/constants" + "github.com/instana/instana-agent-operator/pkg/optional" +) + +func TestServiceAccountBuilder_IsNamespaced_ComponentName(t *testing.T) { + assertions := require.New(t) + + sb := NewServiceAccountBuilder(nil) + + assertions.True(sb.IsNamespaced()) + assertions.Equal(constants.ComponentK8Sensor, sb.ComponentName()) +} + +func TestServiceAccountBuilder_Build(t *testing.T) { + assertions := require.New(t) + ctrl := gomock.NewController(t) + + sensorResourcesName := rand.String(10) + namespace := rand.String(10) + + agent := &instanav1.InstanaAgent{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + }, + } + + expected := optional.Of[client.Object]( + &corev1.ServiceAccount{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "ServiceAccount", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: sensorResourcesName, + Namespace: namespace, + }, + }, + ) + + helpers := NewMockHelpers(ctrl) + helpers.EXPECT().K8sSensorResourcesName().Return(sensorResourcesName) + + sb := &serviceAccountBuilder{ + InstanaAgent: agent, + Helpers: helpers, + } + + actual := sb.Build() + + assertions.Equal(expected, actual) +} diff --git a/pkg/k8s/object/transformations/pod_selector.go b/pkg/k8s/object/transformations/pod_selector.go new file mode 100644 index 00000000..4592a42d --- /dev/null +++ b/pkg/k8s/object/transformations/pod_selector.go @@ -0,0 +1,68 @@ +package transformations + +import ( + instanav1 "github.com/instana/instana-agent-operator/api/v1" + _map "github.com/instana/instana-agent-operator/pkg/collections/map" + "github.com/instana/instana-agent-operator/pkg/optional" +) + +const ( + ZoneLabel = "io.instana/zone" +) + +type PodSelectorLabelGenerator interface { + GetPodSelectorLabels() map[string]string + GetPodLabels(userLabels map[string]string) map[string]string +} + +type podSelectorLabelGenerator struct { + *instanav1.InstanaAgent + zone *instanav1.Zone + component string +} + +func (p *podSelectorLabelGenerator) GetPodLabels(userLabels map[string]string) map[string]string { + podLabels := optional.Of(_map.NewCopier(userLabels).Copy()).GetOrDefault(make(map[string]string, 6)) + + podLabels[NameLabel] = name + podLabels[InstanceLabel] = p.Name + podLabels[ComponentLabel] = p.component + podLabels[PartOfLabel] = partOf + podLabels[ManagedByLabel] = managedBy + + if p.zone != nil { + podLabels[ZoneLabel] = p.zone.Name.Name + } + + return podLabels +} + +func (p *podSelectorLabelGenerator) GetPodSelectorLabels() map[string]string { + labels := map[string]string{ + NameLabel: name, + InstanceLabel: p.Name, + ComponentLabel: p.component, + } + + if p.zone != nil { + labels[ZoneLabel] = p.zone.Name.Name + } + + return labels +} + +func PodSelectorLabels(agent *instanav1.InstanaAgent, component string) PodSelectorLabelGenerator { + return PodSelectorLabelsWithZoneInfo(agent, component, nil) +} + +func PodSelectorLabelsWithZoneInfo( + agent *instanav1.InstanaAgent, + component string, + zone *instanav1.Zone, +) PodSelectorLabelGenerator { + return &podSelectorLabelGenerator{ + InstanaAgent: agent, + component: component, + zone: zone, + } +} diff --git a/pkg/k8s/object/transformations/pod_selector_test.go b/pkg/k8s/object/transformations/pod_selector_test.go new file mode 100644 index 00000000..d51c7570 --- /dev/null +++ b/pkg/k8s/object/transformations/pod_selector_test.go @@ -0,0 +1,116 @@ +package transformations + +import ( + "testing" + + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/rand" + + instanav1 "github.com/instana/instana-agent-operator/api/v1" +) + +func assertSubset(assertions *require.Assertions, s map[string]string, subset map[string]string) { + for k, v := range subset { + assertions.Equal(v, s[k]) + } +} + +func Test_podSelectorLabelGenerator_GetPodLabels(t *testing.T) { + for _, test := range []struct { + name string + userLabels map[string]string + otherAssertions func(assertions *require.Assertions, userLabels map[string]string, actual map[string]string) + }{ + { + name: "no_user_labels", + otherAssertions: func( + assertions *require.Assertions, + userLabels map[string]string, + actual map[string]string, + ) { + }, + }, + { + name: "contains_all_user_labels", + userLabels: map[string]string{ + rand.String(10): rand.String(10), + rand.String(10): rand.String(10), + rand.String(10): rand.String(10), + }, + otherAssertions: func( + assertions *require.Assertions, + userLabels map[string]string, + actual map[string]string, + ) { + assertSubset(assertions, actual, userLabels) + assertions.Len(actual, len(userLabels)+5) + }, + }, + { + name: "overwrites_default_labels", + userLabels: map[string]string{ + "app.kubernetes.io/name": "abd", + "app.kubernetes.io/instance": "def", + "app.kubernetes.io/component": "ghi", + "app.kubernetes.io/part-of": "jkl", + "app.kubernetes.io/managed-by": "mno", + }, + otherAssertions: func( + assertions *require.Assertions, + userLabels map[string]string, + actual map[string]string, + ) { + assertions.Len(actual, 5) + }, + }, + } { + t.Run( + test.name, func(t *testing.T) { + asssertions := require.New(t) + + agentName := rand.String(10) + component := rand.String(10) + + agent := &instanav1.InstanaAgent{ObjectMeta: metav1.ObjectMeta{Name: agentName}} + + p := PodSelectorLabels(agent, component) + + actual := p.GetPodLabels(test.userLabels) + + assertSubset( + asssertions, actual, map[string]string{ + "app.kubernetes.io/name": "instana-agent", + "app.kubernetes.io/instance": agentName, + "app.kubernetes.io/component": component, + "app.kubernetes.io/part-of": "instana", + "app.kubernetes.io/managed-by": "instana-agent-operator", + }, + ) + asssertions.NotSame(test.userLabels, actual) + + test.otherAssertions(asssertions, test.userLabels, actual) + }, + ) + } +} + +func Test_podSelectorLabelGenerator_GetPodSelectorLabels(t *testing.T) { + assertions := require.New(t) + + agentName := rand.String(10) + component := rand.String(10) + + agent := &instanav1.InstanaAgent{ObjectMeta: metav1.ObjectMeta{Name: agentName}} + + p := PodSelectorLabels(agent, component) + + actual := p.GetPodSelectorLabels() + assertions.Equal( + map[string]string{ + "app.kubernetes.io/name": "instana-agent", + "app.kubernetes.io/instance": agentName, + "app.kubernetes.io/component": component, + }, actual, + ) +} diff --git a/pkg/k8s/object/transformations/transformations.go b/pkg/k8s/object/transformations/transformations.go new file mode 100644 index 00000000..37a9aa98 --- /dev/null +++ b/pkg/k8s/object/transformations/transformations.go @@ -0,0 +1,122 @@ +package transformations + +import ( + "strconv" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "sigs.k8s.io/controller-runtime/pkg/client" + + instanav1 "github.com/instana/instana-agent-operator/api/v1" + "github.com/instana/instana-agent-operator/pkg/env" + "github.com/instana/instana-agent-operator/pkg/optional" + "github.com/instana/instana-agent-operator/pkg/or_die" + "github.com/instana/instana-agent-operator/pkg/pointer" +) + +// labels +const ( + NameLabel = "app.kubernetes.io/name" + InstanceLabel = "app.kubernetes.io/instance" + VersionLabel = "app.kubernetes.io/version" + ComponentLabel = "app.kubernetes.io/component" + PartOfLabel = "app.kubernetes.io/part-of" + ManagedByLabel = "app.kubernetes.io/managed-by" + GenerationLabel = "agent.instana.io/generation" +) + +const ( + name = "instana-agent" + partOf = "instana" + managedBy = "instana-agent-operator" +) + +var ( + version = env.GetOperatorVersion() +) + +func GetVersion() string { + return version +} + +type Transformations interface { + AddCommonLabels(obj client.Object, component string) + AddOwnerReference(obj client.Object) + PreviousGenerationsSelector() labels.Selector +} + +type transformations struct { + metav1.OwnerReference + generation string +} + +func (t *transformations) AddCommonLabels(obj client.Object, component string) { + objLabels := optional.Of(obj.GetLabels()).GetOrDefault(make(map[string]string, 7)) + + objLabels[NameLabel] = name + objLabels[InstanceLabel] = t.Name + objLabels[VersionLabel] = version + objLabels[ComponentLabel] = component + objLabels[PartOfLabel] = partOf + objLabels[ManagedByLabel] = managedBy + objLabels[GenerationLabel] = t.generation + + obj.SetLabels(objLabels) +} + +func (t *transformations) PreviousGenerationsSelector() labels.Selector { + return or_die.New[labels.Selector]().ResultOrDie( + func() (labels.Selector, error) { + return metav1.LabelSelectorAsSelector( + &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{ + { + Key: NameLabel, + Operator: metav1.LabelSelectorOpIn, + Values: []string{name}, + }, + { + Key: InstanceLabel, + Operator: metav1.LabelSelectorOpIn, + Values: []string{t.Name}, + }, + { + Key: GenerationLabel, + Operator: metav1.LabelSelectorOpNotIn, + Values: []string{t.generation}, + }, + }, + }, + ) + }, + ) +} + +func (t *transformations) AddOwnerReference(obj client.Object) { + for _, preExisting := range obj.GetOwnerReferences() { + if preExisting.UID == t.OwnerReference.UID { + return + } + } + + obj.SetOwnerReferences( + append( + obj.GetOwnerReferences(), + t.OwnerReference, + ), + ) +} + +func NewTransformations(agent *instanav1.InstanaAgent) Transformations { + return &transformations{ + OwnerReference: metav1.OwnerReference{ + APIVersion: agent.APIVersion, + Kind: agent.Kind, + Name: agent.Name, + UID: agent.UID, + Controller: pointer.To(true), + BlockOwnerDeletion: pointer.To(true), + }, + generation: strconv.Itoa(int(agent.Generation)), + } +} diff --git a/pkg/k8s/object/transformations/transformations_test.go b/pkg/k8s/object/transformations/transformations_test.go new file mode 100644 index 00000000..3004aeb1 --- /dev/null +++ b/pkg/k8s/object/transformations/transformations_test.go @@ -0,0 +1,212 @@ +package transformations + +import ( + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + instanav1 "github.com/instana/instana-agent-operator/api/v1" + "github.com/instana/instana-agent-operator/pkg/pointer" + + "github.com/stretchr/testify/require" + v1 "k8s.io/api/core/v1" +) + +func TestTransformations_AddCommonLabels(t *testing.T) { + for _, tc := range []struct { + name string + initialLabels map[string]string + instanaAgent instanav1.InstanaAgent + componentLabel string + versionEnv string + expectedLabels map[string]string + }{ + { + name: "with_empty_labels_initially", + initialLabels: nil, + instanaAgent: instanav1.InstanaAgent{ + ObjectMeta: metav1.ObjectMeta{ + Name: "asdf", + Generation: 4, + }, + }, + componentLabel: "eoisdijsdf", + versionEnv: "v0.0.1", + expectedLabels: map[string]string{ + "app.kubernetes.io/name": "instana-agent", + "app.kubernetes.io/component": "eoisdijsdf", + "app.kubernetes.io/instance": "asdf", + "app.kubernetes.io/version": "v0.0.1", + "app.kubernetes.io/part-of": "instana", + "app.kubernetes.io/managed-by": "instana-agent-operator", + "agent.instana.io/generation": "4", + }, + }, + { + name: "with_initial_labels", + initialLabels: map[string]string{ + "foo": "bar", + "hello": "world", + }, + instanaAgent: instanav1.InstanaAgent{ + ObjectMeta: metav1.ObjectMeta{ + Name: "yrsthsdht", + Generation: 3, + }, + }, + componentLabel: "roisoijdsf", + versionEnv: "v0.0.2", + expectedLabels: map[string]string{ + "foo": "bar", + "hello": "world", + "app.kubernetes.io/name": "instana-agent", + "app.kubernetes.io/component": "roisoijdsf", + "app.kubernetes.io/instance": "yrsthsdht", + "app.kubernetes.io/version": "v0.0.2", + "app.kubernetes.io/part-of": "instana", + "app.kubernetes.io/managed-by": "instana-agent-operator", + "agent.instana.io/generation": "3", + }, + }, + } { + t.Run( + tc.name, func(t *testing.T) { + assertions := require.New(t) + + version = tc.versionEnv + + obj := v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Labels: tc.initialLabels, + }, + } + + NewTransformations(&tc.instanaAgent).AddCommonLabels(&obj, tc.componentLabel) + + assertions.Equal(tc.expectedLabels, obj.GetLabels()) + }, + ) + } +} + +func TestTransformations_AddOwnerReference(t *testing.T) { + for _, tc := range []struct { + name string + configMap v1.ConfigMap + expectedRefs []metav1.OwnerReference + }{ + { + name: "with_no_previous_references", + configMap: v1.ConfigMap{}, + expectedRefs: []metav1.OwnerReference{ + { + APIVersion: "instana.io/v1", + Kind: "InstanaAgent", + Name: "instana-agent", + UID: "iowegihsdgoijwefoih", + Controller: pointer.To(true), + BlockOwnerDeletion: pointer.To(true), + }, + }, + }, + { + name: "with_previous_references", + configMap: v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: "adsf", + Kind: "pojg", + Name: "ojregoi", + UID: "owjgepos", + Controller: pointer.To(false), + BlockOwnerDeletion: pointer.To(false), + }, + }, + }, + }, + expectedRefs: []metav1.OwnerReference{ + { + APIVersion: "adsf", + Kind: "pojg", + Name: "ojregoi", + UID: "owjgepos", + Controller: pointer.To(false), + BlockOwnerDeletion: pointer.To(false), + }, + { + APIVersion: "instana.io/v1", + Kind: "InstanaAgent", + Name: "instana-agent", + UID: "iowegihsdgoijwefoih", + Controller: pointer.To(true), + BlockOwnerDeletion: pointer.To(true), + }, + }, + }, + { + name: "with_duplicate_ref", + configMap: v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: "adsf", + Kind: "pojg", + Name: "ojregoi", + UID: "owjgepos", + Controller: pointer.To(false), + BlockOwnerDeletion: pointer.To(false), + }, + { + APIVersion: "instana.io/v1", + Kind: "InstanaAgent", + Name: "instana-agent", + UID: "iowegihsdgoijwefoih", + Controller: pointer.To(true), + BlockOwnerDeletion: pointer.To(true), + }, + }, + }, + }, + expectedRefs: []metav1.OwnerReference{ + { + APIVersion: "adsf", + Kind: "pojg", + Name: "ojregoi", + UID: "owjgepos", + Controller: pointer.To(false), + BlockOwnerDeletion: pointer.To(false), + }, + { + APIVersion: "instana.io/v1", + Kind: "InstanaAgent", + Name: "instana-agent", + UID: "iowegihsdgoijwefoih", + Controller: pointer.To(true), + BlockOwnerDeletion: pointer.To(true), + }, + }, + }, + } { + t.Run( + tc.name, func(t *testing.T) { + assertions := require.New(t) + + agent := instanav1.InstanaAgent{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "instana.io/v1", + Kind: "InstanaAgent", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "instana-agent", + UID: "iowegihsdgoijwefoih", + }, + } + + NewTransformations(&agent).AddOwnerReference(&tc.configMap) + + assertions.Equal(tc.expectedRefs, tc.configMap.OwnerReferences) + }, + ) + } +} diff --git a/pkg/k8s/operator/lifecycle/lifecycle.go b/pkg/k8s/operator/lifecycle/lifecycle.go new file mode 100644 index 00000000..f93f64ef --- /dev/null +++ b/pkg/k8s/operator/lifecycle/lifecycle.go @@ -0,0 +1,236 @@ +package lifecycle + +import ( + "context" + "fmt" + "time" + + corev1 "k8s.io/api/core/v1" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + + instanav1 "github.com/instana/instana-agent-operator/api/v1" + "github.com/instana/instana-agent-operator/pkg/collections/list" + "github.com/instana/instana-agent-operator/pkg/json_or_die" + instanaclient "github.com/instana/instana-agent-operator/pkg/k8s/client" + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/constants" + "github.com/instana/instana-agent-operator/pkg/k8s/object/transformations" + "github.com/instana/instana-agent-operator/pkg/multierror" + "github.com/instana/instana-agent-operator/pkg/result" +) + +type DependentLifecycleManager interface { + UpdateDependentLifecycleInfo(currentGenerationDependents []client.Object) instanaclient.MultiObjectResult + DeleteOrphanedDependents(currentGenerationDependents []client.Object) instanaclient.MultiObjectResult + DeleteAllDependents() instanaclient.MultiObjectResult +} + +type dependentLifecycleManager struct { + ctx context.Context + agent client.Object + + instanaclient.InstanaAgentClient + objectStrip + json_or_die.JsonOrDieMarshaler[[]unstructured.Unstructured] + transformations.Transformations +} + +func (d *dependentLifecycleManager) getCmName() string { + return d.agent.GetName() + "-dependents" +} + +func (d *dependentLifecycleManager) toStripped(objects []client.Object) []unstructured.Unstructured { + return list.NewListMapTo[client.Object, unstructured.Unstructured]().MapTo( + objects, + d.stripObject, + ) +} + +func (d *dependentLifecycleManager) marshalDependents(currentGenerationDependents []client.Object) []byte { + return d.MarshalOrDie(d.toStripped(currentGenerationDependents)) +} + +func (d *dependentLifecycleManager) getCurrentGenKey() string { + return fmt.Sprintf("%s_%d", transformations.GetVersion(), d.agent.GetGeneration()) +} + +func (d *dependentLifecycleManager) initializeIfNotFound(err error) (corev1.ConfigMap, error) { + switch k8serrors.IsNotFound(err) { + case true: + return corev1.ConfigMap{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "ConfigMap", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: d.getCmName(), + Namespace: d.agent.GetNamespace(), + }, + }, nil + default: + return corev1.ConfigMap{}, err + } +} + +func initializeDataIfNil(res corev1.ConfigMap) result.Result[corev1.ConfigMap] { + if res.Data == nil { + res.Data = make(map[string]string, 1) + } + return result.OfSuccess(res) +} + +func (d *dependentLifecycleManager) getOrInitializeLifecycleCm() result.Result[corev1.ConfigMap] { + lifecycleCm := d.getLifecycleCm().Recover(d.initializeIfNotFound) + + return result.Map(lifecycleCm, initializeDataIfNil) +} + +func toDependents(currentGenerationDependents []client.Object) func(_ client.Object) result.Result[[]client.Object] { + return func(_ client.Object) result.Result[[]client.Object] { + return result.OfSuccess(currentGenerationDependents) + } +} + +func (d *dependentLifecycleManager) updateDependentLifecycleInfo( + lifecycleCm *corev1.ConfigMap, + currentGenerationDependents []client.Object, +) instanaclient.MultiObjectResult { + currentGenKey := d.getCurrentGenKey() + + // Ensures that a lifecycle comparison will be performed even if neither the generation nor the operator version + // have been updated, should only be necessary for the sake of testing during development + if existingVersion, isPresent := lifecycleCm.Data[currentGenKey]; isPresent { + lifecycleCm.Data[currentGenKey+"-dirty"] = existingVersion + } + + lifecycleCm.Data[currentGenKey] = string(d.marshalDependents(currentGenerationDependents)) + + d.AddCommonLabels(lifecycleCm, constants.ComponentInstanaAgent) + d.AddOwnerReference(lifecycleCm) + + applyRes := d.Apply(d.ctx, lifecycleCm) + + return result.Map(applyRes, toDependents(currentGenerationDependents)) +} + +func (d *dependentLifecycleManager) andUpdateUsing(currentGenerationDependents []client.Object) func(lifecycleCm corev1.ConfigMap) result.Result[[]client.Object] { + return func(lifecycleCm corev1.ConfigMap) result.Result[[]client.Object] { + return d.updateDependentLifecycleInfo(&lifecycleCm, currentGenerationDependents) + } +} + +func (d *dependentLifecycleManager) UpdateDependentLifecycleInfo(currentGenerationDependents []client.Object) instanaclient.MultiObjectResult { + return result.Map(d.getOrInitializeLifecycleCm(), d.andUpdateUsing(currentGenerationDependents)) +} + +func (d *dependentLifecycleManager) getLifecycleCm() result.Result[corev1.ConfigMap] { + lifecycleCm := corev1.ConfigMap{} + + return result.Of( + lifecycleCm, + d.Get(d.ctx, types.NamespacedName{Name: d.getCmName(), Namespace: d.agent.GetNamespace()}, &lifecycleCm), + ) +} + +func (d *dependentLifecycleManager) unmarshalGenerationAsUnstructured( + lifecycleCm *corev1.ConfigMap, + key string, +) func() (res []unstructured.Unstructured, err error) { + return func() (res []unstructured.Unstructured, err error) { + return d.JsonOrDieMarshaler.UnMarshalOrDie([]byte(lifecycleCm.Data[key])), nil + } +} + +func emptyUnstructuredList() []unstructured.Unstructured { + return make([]unstructured.Unstructured, 0) +} + +func (d *dependentLifecycleManager) getGeneration( + lifecycleCm *corev1.ConfigMap, + key string, +) []unstructured.Unstructured { + jsonRes := result.OfInlineCatchingPanic(d.unmarshalGenerationAsUnstructured(lifecycleCm, key)) + + return jsonRes.ToOptional().GetOrElse(emptyUnstructuredList) +} + +func asClientObject(val unstructured.Unstructured) client.Object { + return &val +} + +func (d *dependentLifecycleManager) deleteAll(toDelete []unstructured.Unstructured) result.Result[[]client.Object] { + toDeleteCasted := list.NewListMapTo[unstructured.Unstructured, client.Object]().MapTo(toDelete, asClientObject) + + return d.DeleteAllInTimeLimit(d.ctx, toDeleteCasted, 30*time.Second, 5*time.Second) +} + +func (d *dependentLifecycleManager) removeKeyFromLifecycleCm( + lifecycleCm *corev1.ConfigMap, + key string, +) func(_ []client.Object) { + return func(_ []client.Object) { + if key != d.getCurrentGenKey() { + delete(lifecycleCm.Data, key) + } + } +} + +func (d *dependentLifecycleManager) deleteOrphanedDependents( + lifecycleCm *corev1.ConfigMap, + currentGenerationDependents []client.Object, +) result.Result[[]client.Object] { + errBuilder := multierror.NewMultiErrorBuilder() + + currentGeneration := d.toStripped(currentGenerationDependents) + + for key := range lifecycleCm.Data { + olderGeneration := d.getGeneration(lifecycleCm, key) + deprecatedDependents := list.NewDeepDiff[unstructured.Unstructured]().Diff( + olderGeneration, + currentGeneration, + ) + d.deleteAll(deprecatedDependents).OnSuccess( + d.removeKeyFromLifecycleCm( + lifecycleCm, + key, + ), + ).OnFailure(errBuilder.AddSingle) + } + + d.Apply(d.ctx, lifecycleCm).OnFailure(errBuilder.AddSingle) + + return result.Of(currentGenerationDependents, errBuilder.Build()) +} + +func (d *dependentLifecycleManager) andDeleteOrphanedDependentsUsing(currentGenerationDependents []client.Object) func(lifecycleCm corev1.ConfigMap) result.Result[[]client.Object] { + return func(lifecycleCm corev1.ConfigMap) result.Result[[]client.Object] { + return d.deleteOrphanedDependents(&lifecycleCm, currentGenerationDependents) + } +} + +func (d *dependentLifecycleManager) DeleteOrphanedDependents(currentGenerationDependents []client.Object) instanaclient.MultiObjectResult { + return result.Map(d.getLifecycleCm(), d.andDeleteOrphanedDependentsUsing(currentGenerationDependents)) +} + +func (d *dependentLifecycleManager) DeleteAllDependents() instanaclient.MultiObjectResult { + return d.DeleteOrphanedDependents(nil) +} + +func NewDependentLifecycleManager( + ctx context.Context, + agent *instanav1.InstanaAgent, + instanaClient instanaclient.InstanaAgentClient, +) DependentLifecycleManager { + return &dependentLifecycleManager{ + ctx: ctx, + agent: agent, + + InstanaAgentClient: instanaClient, + objectStrip: &strip{}, + JsonOrDieMarshaler: json_or_die.NewJsonOrDieArray[unstructured.Unstructured](), + Transformations: transformations.NewTransformations(agent), + } +} diff --git a/pkg/k8s/operator/lifecycle/strip.go b/pkg/k8s/operator/lifecycle/strip.go new file mode 100644 index 00000000..9d74d3e4 --- /dev/null +++ b/pkg/k8s/operator/lifecycle/strip.go @@ -0,0 +1,22 @@ +package lifecycle + +import ( + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +type objectStrip interface { + stripObject(original client.Object) unstructured.Unstructured +} + +type strip struct{} + +func (s *strip) stripObject(original client.Object) unstructured.Unstructured { + res := unstructured.Unstructured{} + + res.SetGroupVersionKind(original.GetObjectKind().GroupVersionKind()) + res.SetName(original.GetName()) + res.SetNamespace(original.GetNamespace()) + + return res +} diff --git a/pkg/k8s/operator/lifecycle/strip_test.go b/pkg/k8s/operator/lifecycle/strip_test.go new file mode 100644 index 00000000..d1e3b453 --- /dev/null +++ b/pkg/k8s/operator/lifecycle/strip_test.go @@ -0,0 +1,31 @@ +package lifecycle + +import ( + "testing" + + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func Test_strip_stripObject(t *testing.T) { + assertions := require.New(t) + + original := &corev1.ConfigMap{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "ConfigMap", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "cm-name", + Namespace: "cm-ns", + }, + } + + actual := (&strip{}).stripObject(original) + + assertions.Equal("cm-name", actual.GetName()) + assertions.Equal("cm-ns", actual.GetNamespace()) + assertions.Equal("v1", actual.GetAPIVersion()) + assertions.Equal("ConfigMap", actual.GetKind()) +} diff --git a/pkg/k8s/operator/operator_utils/builder_mock_test.go b/pkg/k8s/operator/operator_utils/builder_mock_test.go new file mode 100644 index 00000000..54b76f75 --- /dev/null +++ b/pkg/k8s/operator/operator_utils/builder_mock_test.go @@ -0,0 +1,139 @@ +// /* +// (c) Copyright IBM Corp. 2024 +// (c) Copyright Instana Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// */ +// + +// Code generated by MockGen. DO NOT EDIT. +package operator_utils + +import ( + reflect "reflect" + + builder "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/builder" + gomock "go.uber.org/mock/gomock" +) + +// MockObjectBuilder is a mock of ObjectBuilder interface. +type MockObjectBuilder struct { + ctrl *gomock.Controller + recorder *MockObjectBuilderMockRecorder +} + +// MockObjectBuilderMockRecorder is the mock recorder for MockObjectBuilder. +type MockObjectBuilderMockRecorder struct { + mock *MockObjectBuilder +} + +// NewMockObjectBuilder creates a new mock instance. +func NewMockObjectBuilder(ctrl *gomock.Controller) *MockObjectBuilder { + mock := &MockObjectBuilder{ctrl: ctrl} + mock.recorder = &MockObjectBuilderMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockObjectBuilder) EXPECT() *MockObjectBuilderMockRecorder { + return m.recorder +} + +// ISGOMOCK indicates that this struct is a gomock mock. +func (m *MockObjectBuilder) ISGOMOCK() struct{} { + return struct{}{} +} + +// Build mocks base method. +func (m *MockObjectBuilder) Build() builder.OptionalObject { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Build") + ret0, _ := ret[0].(builder.OptionalObject) + return ret0 +} + +// Build indicates an expected call of Build. +func (mr *MockObjectBuilderMockRecorder) Build() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Build", reflect.TypeOf((*MockObjectBuilder)(nil).Build)) +} + +// ComponentName mocks base method. +func (m *MockObjectBuilder) ComponentName() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ComponentName") + ret0, _ := ret[0].(string) + return ret0 +} + +// ComponentName indicates an expected call of ComponentName. +func (mr *MockObjectBuilderMockRecorder) ComponentName() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ComponentName", reflect.TypeOf((*MockObjectBuilder)(nil).ComponentName)) +} + +// IsNamespaced mocks base method. +func (m *MockObjectBuilder) IsNamespaced() bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "IsNamespaced") + ret0, _ := ret[0].(bool) + return ret0 +} + +// IsNamespaced indicates an expected call of IsNamespaced. +func (mr *MockObjectBuilderMockRecorder) IsNamespaced() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsNamespaced", reflect.TypeOf((*MockObjectBuilder)(nil).IsNamespaced)) +} + +// MockBuilderTransformer is a mock of BuilderTransformer interface. +type MockBuilderTransformer struct { + ctrl *gomock.Controller + recorder *MockBuilderTransformerMockRecorder +} + +// MockBuilderTransformerMockRecorder is the mock recorder for MockBuilderTransformer. +type MockBuilderTransformerMockRecorder struct { + mock *MockBuilderTransformer +} + +// NewMockBuilderTransformer creates a new mock instance. +func NewMockBuilderTransformer(ctrl *gomock.Controller) *MockBuilderTransformer { + mock := &MockBuilderTransformer{ctrl: ctrl} + mock.recorder = &MockBuilderTransformerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockBuilderTransformer) EXPECT() *MockBuilderTransformerMockRecorder { + return m.recorder +} + +// ISGOMOCK indicates that this struct is a gomock mock. +func (m *MockBuilderTransformer) ISGOMOCK() struct{} { + return struct{}{} +} + +// Apply mocks base method. +func (m *MockBuilderTransformer) Apply(bldr builder.ObjectBuilder) builder.OptionalObject { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Apply", bldr) + ret0, _ := ret[0].(builder.OptionalObject) + return ret0 +} + +// Apply indicates an expected call of Apply. +func (mr *MockBuilderTransformerMockRecorder) Apply(bldr any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Apply", reflect.TypeOf((*MockBuilderTransformer)(nil).Apply), bldr) +} diff --git a/pkg/k8s/operator/operator_utils/instana_agent_client_mock_test.go b/pkg/k8s/operator/operator_utils/instana_agent_client_mock_test.go new file mode 100644 index 00000000..d7e809a7 --- /dev/null +++ b/pkg/k8s/operator/operator_utils/instana_agent_client_mock_test.go @@ -0,0 +1,351 @@ +// /* +// (c) Copyright IBM Corp. 2024 +// (c) Copyright Instana Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// */ +// + +// Code generated by MockGen. DO NOT EDIT. +package operator_utils + +import ( + context "context" + reflect "reflect" + time "time" + + client "github.com/instana/instana-agent-operator/pkg/k8s/client" + gomock "go.uber.org/mock/gomock" + meta "k8s.io/apimachinery/pkg/api/meta" + runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" + client0 "sigs.k8s.io/controller-runtime/pkg/client" +) + +// MockInstanaAgentClient is a mock of InstanaAgentClient interface. +type MockInstanaAgentClient struct { + ctrl *gomock.Controller + recorder *MockInstanaAgentClientMockRecorder +} + +// MockInstanaAgentClientMockRecorder is the mock recorder for MockInstanaAgentClient. +type MockInstanaAgentClientMockRecorder struct { + mock *MockInstanaAgentClient +} + +// NewMockInstanaAgentClient creates a new mock instance. +func NewMockInstanaAgentClient(ctrl *gomock.Controller) *MockInstanaAgentClient { + mock := &MockInstanaAgentClient{ctrl: ctrl} + mock.recorder = &MockInstanaAgentClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockInstanaAgentClient) EXPECT() *MockInstanaAgentClientMockRecorder { + return m.recorder +} + +// ISGOMOCK indicates that this struct is a gomock mock. +func (m *MockInstanaAgentClient) ISGOMOCK() struct{} { + return struct{}{} +} + +// Apply mocks base method. +func (m *MockInstanaAgentClient) Apply(ctx context.Context, obj client0.Object, opts ...client0.PatchOption) client.ObjectResult { + m.ctrl.T.Helper() + varargs := []any{ctx, obj} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Apply", varargs...) + ret0, _ := ret[0].(client.ObjectResult) + return ret0 +} + +// Apply indicates an expected call of Apply. +func (mr *MockInstanaAgentClientMockRecorder) Apply(ctx, obj any, opts ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, obj}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Apply", reflect.TypeOf((*MockInstanaAgentClient)(nil).Apply), varargs...) +} + +// Create mocks base method. +func (m *MockInstanaAgentClient) Create(ctx context.Context, obj client0.Object, opts ...client0.CreateOption) error { + m.ctrl.T.Helper() + varargs := []any{ctx, obj} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Create", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// Create indicates an expected call of Create. +func (mr *MockInstanaAgentClientMockRecorder) Create(ctx, obj any, opts ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, obj}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockInstanaAgentClient)(nil).Create), varargs...) +} + +// Delete mocks base method. +func (m *MockInstanaAgentClient) Delete(ctx context.Context, obj client0.Object, opts ...client0.DeleteOption) error { + m.ctrl.T.Helper() + varargs := []any{ctx, obj} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Delete", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// Delete indicates an expected call of Delete. +func (mr *MockInstanaAgentClientMockRecorder) Delete(ctx, obj any, opts ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, obj}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockInstanaAgentClient)(nil).Delete), varargs...) +} + +// DeleteAllInTimeLimit mocks base method. +func (m *MockInstanaAgentClient) DeleteAllInTimeLimit(ctx context.Context, objects []client0.Object, timeout, waitTime time.Duration, opts ...client0.DeleteOption) client.MultiObjectResult { + m.ctrl.T.Helper() + varargs := []any{ctx, objects, timeout, waitTime} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "DeleteAllInTimeLimit", varargs...) + ret0, _ := ret[0].(client.MultiObjectResult) + return ret0 +} + +// DeleteAllInTimeLimit indicates an expected call of DeleteAllInTimeLimit. +func (mr *MockInstanaAgentClientMockRecorder) DeleteAllInTimeLimit(ctx, objects, timeout, waitTime any, opts ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, objects, timeout, waitTime}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteAllInTimeLimit", reflect.TypeOf((*MockInstanaAgentClient)(nil).DeleteAllInTimeLimit), varargs...) +} + +// DeleteAllOf mocks base method. +func (m *MockInstanaAgentClient) DeleteAllOf(ctx context.Context, obj client0.Object, opts ...client0.DeleteAllOfOption) error { + m.ctrl.T.Helper() + varargs := []any{ctx, obj} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "DeleteAllOf", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteAllOf indicates an expected call of DeleteAllOf. +func (mr *MockInstanaAgentClientMockRecorder) DeleteAllOf(ctx, obj any, opts ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, obj}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteAllOf", reflect.TypeOf((*MockInstanaAgentClient)(nil).DeleteAllOf), varargs...) +} + +// Exists mocks base method. +func (m *MockInstanaAgentClient) Exists(ctx context.Context, gvk schema.GroupVersionKind, key client0.ObjectKey) client.BoolResult { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Exists", ctx, gvk, key) + ret0, _ := ret[0].(client.BoolResult) + return ret0 +} + +// Exists indicates an expected call of Exists. +func (mr *MockInstanaAgentClientMockRecorder) Exists(ctx, gvk, key any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Exists", reflect.TypeOf((*MockInstanaAgentClient)(nil).Exists), ctx, gvk, key) +} + +// Get mocks base method. +func (m *MockInstanaAgentClient) Get(ctx context.Context, key client0.ObjectKey, obj client0.Object, opts ...client0.GetOption) error { + m.ctrl.T.Helper() + varargs := []any{ctx, key, obj} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Get", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// Get indicates an expected call of Get. +func (mr *MockInstanaAgentClientMockRecorder) Get(ctx, key, obj any, opts ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, key, obj}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockInstanaAgentClient)(nil).Get), varargs...) +} + +// GetAsResult mocks base method. +func (m *MockInstanaAgentClient) GetAsResult(ctx context.Context, key client0.ObjectKey, obj client0.Object, opts ...client0.GetOption) client.ObjectResult { + m.ctrl.T.Helper() + varargs := []any{ctx, key, obj} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "GetAsResult", varargs...) + ret0, _ := ret[0].(client.ObjectResult) + return ret0 +} + +// GetAsResult indicates an expected call of GetAsResult. +func (mr *MockInstanaAgentClientMockRecorder) GetAsResult(ctx, key, obj any, opts ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, key, obj}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAsResult", reflect.TypeOf((*MockInstanaAgentClient)(nil).GetAsResult), varargs...) +} + +// GroupVersionKindFor mocks base method. +func (m *MockInstanaAgentClient) GroupVersionKindFor(obj runtime.Object) (schema.GroupVersionKind, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GroupVersionKindFor", obj) + ret0, _ := ret[0].(schema.GroupVersionKind) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GroupVersionKindFor indicates an expected call of GroupVersionKindFor. +func (mr *MockInstanaAgentClientMockRecorder) GroupVersionKindFor(obj any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GroupVersionKindFor", reflect.TypeOf((*MockInstanaAgentClient)(nil).GroupVersionKindFor), obj) +} + +// IsObjectNamespaced mocks base method. +func (m *MockInstanaAgentClient) IsObjectNamespaced(obj runtime.Object) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "IsObjectNamespaced", obj) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// IsObjectNamespaced indicates an expected call of IsObjectNamespaced. +func (mr *MockInstanaAgentClientMockRecorder) IsObjectNamespaced(obj any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsObjectNamespaced", reflect.TypeOf((*MockInstanaAgentClient)(nil).IsObjectNamespaced), obj) +} + +// List mocks base method. +func (m *MockInstanaAgentClient) List(ctx context.Context, list client0.ObjectList, opts ...client0.ListOption) error { + m.ctrl.T.Helper() + varargs := []any{ctx, list} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "List", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// List indicates an expected call of List. +func (mr *MockInstanaAgentClientMockRecorder) List(ctx, list any, opts ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, list}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockInstanaAgentClient)(nil).List), varargs...) +} + +// Patch mocks base method. +func (m *MockInstanaAgentClient) Patch(ctx context.Context, obj client0.Object, patch client0.Patch, opts ...client0.PatchOption) error { + m.ctrl.T.Helper() + varargs := []any{ctx, obj, patch} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Patch", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// Patch indicates an expected call of Patch. +func (mr *MockInstanaAgentClientMockRecorder) Patch(ctx, obj, patch any, opts ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, obj, patch}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Patch", reflect.TypeOf((*MockInstanaAgentClient)(nil).Patch), varargs...) +} + +// RESTMapper mocks base method. +func (m *MockInstanaAgentClient) RESTMapper() meta.RESTMapper { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RESTMapper") + ret0, _ := ret[0].(meta.RESTMapper) + return ret0 +} + +// RESTMapper indicates an expected call of RESTMapper. +func (mr *MockInstanaAgentClientMockRecorder) RESTMapper() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RESTMapper", reflect.TypeOf((*MockInstanaAgentClient)(nil).RESTMapper)) +} + +// Scheme mocks base method. +func (m *MockInstanaAgentClient) Scheme() *runtime.Scheme { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Scheme") + ret0, _ := ret[0].(*runtime.Scheme) + return ret0 +} + +// Scheme indicates an expected call of Scheme. +func (mr *MockInstanaAgentClientMockRecorder) Scheme() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Scheme", reflect.TypeOf((*MockInstanaAgentClient)(nil).Scheme)) +} + +// Status mocks base method. +func (m *MockInstanaAgentClient) Status() client0.SubResourceWriter { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Status") + ret0, _ := ret[0].(client0.SubResourceWriter) + return ret0 +} + +// Status indicates an expected call of Status. +func (mr *MockInstanaAgentClientMockRecorder) Status() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Status", reflect.TypeOf((*MockInstanaAgentClient)(nil).Status)) +} + +// SubResource mocks base method. +func (m *MockInstanaAgentClient) SubResource(subResource string) client0.SubResourceClient { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SubResource", subResource) + ret0, _ := ret[0].(client0.SubResourceClient) + return ret0 +} + +// SubResource indicates an expected call of SubResource. +func (mr *MockInstanaAgentClientMockRecorder) SubResource(subResource any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SubResource", reflect.TypeOf((*MockInstanaAgentClient)(nil).SubResource), subResource) +} + +// Update mocks base method. +func (m *MockInstanaAgentClient) Update(ctx context.Context, obj client0.Object, opts ...client0.UpdateOption) error { + m.ctrl.T.Helper() + varargs := []any{ctx, obj} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Update", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// Update indicates an expected call of Update. +func (mr *MockInstanaAgentClientMockRecorder) Update(ctx, obj any, opts ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, obj}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockInstanaAgentClient)(nil).Update), varargs...) +} diff --git a/pkg/k8s/operator/operator_utils/lifecycle_mock_test.go b/pkg/k8s/operator/operator_utils/lifecycle_mock_test.go new file mode 100644 index 00000000..c9841a1e --- /dev/null +++ b/pkg/k8s/operator/operator_utils/lifecycle_mock_test.go @@ -0,0 +1,98 @@ +// /* +// (c) Copyright IBM Corp. 2024 +// (c) Copyright Instana Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// */ +// + +// Code generated by MockGen. DO NOT EDIT. +package operator_utils + +import ( + reflect "reflect" + + client "github.com/instana/instana-agent-operator/pkg/k8s/client" + gomock "go.uber.org/mock/gomock" + client0 "sigs.k8s.io/controller-runtime/pkg/client" +) + +// MockDependentLifecycleManager is a mock of DependentLifecycleManager interface. +type MockDependentLifecycleManager struct { + ctrl *gomock.Controller + recorder *MockDependentLifecycleManagerMockRecorder +} + +// MockDependentLifecycleManagerMockRecorder is the mock recorder for MockDependentLifecycleManager. +type MockDependentLifecycleManagerMockRecorder struct { + mock *MockDependentLifecycleManager +} + +// NewMockDependentLifecycleManager creates a new mock instance. +func NewMockDependentLifecycleManager(ctrl *gomock.Controller) *MockDependentLifecycleManager { + mock := &MockDependentLifecycleManager{ctrl: ctrl} + mock.recorder = &MockDependentLifecycleManagerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockDependentLifecycleManager) EXPECT() *MockDependentLifecycleManagerMockRecorder { + return m.recorder +} + +// ISGOMOCK indicates that this struct is a gomock mock. +func (m *MockDependentLifecycleManager) ISGOMOCK() struct{} { + return struct{}{} +} + +// DeleteAllDependents mocks base method. +func (m *MockDependentLifecycleManager) DeleteAllDependents() client.MultiObjectResult { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteAllDependents") + ret0, _ := ret[0].(client.MultiObjectResult) + return ret0 +} + +// DeleteAllDependents indicates an expected call of DeleteAllDependents. +func (mr *MockDependentLifecycleManagerMockRecorder) DeleteAllDependents() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteAllDependents", reflect.TypeOf((*MockDependentLifecycleManager)(nil).DeleteAllDependents)) +} + +// DeleteOrphanedDependents mocks base method. +func (m *MockDependentLifecycleManager) DeleteOrphanedDependents(currentGenerationDependents []client0.Object) client.MultiObjectResult { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteOrphanedDependents", currentGenerationDependents) + ret0, _ := ret[0].(client.MultiObjectResult) + return ret0 +} + +// DeleteOrphanedDependents indicates an expected call of DeleteOrphanedDependents. +func (mr *MockDependentLifecycleManagerMockRecorder) DeleteOrphanedDependents(currentGenerationDependents any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteOrphanedDependents", reflect.TypeOf((*MockDependentLifecycleManager)(nil).DeleteOrphanedDependents), currentGenerationDependents) +} + +// UpdateDependentLifecycleInfo mocks base method. +func (m *MockDependentLifecycleManager) UpdateDependentLifecycleInfo(currentGenerationDependents []client0.Object) client.MultiObjectResult { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateDependentLifecycleInfo", currentGenerationDependents) + ret0, _ := ret[0].(client.MultiObjectResult) + return ret0 +} + +// UpdateDependentLifecycleInfo indicates an expected call of UpdateDependentLifecycleInfo. +func (mr *MockDependentLifecycleManagerMockRecorder) UpdateDependentLifecycleInfo(currentGenerationDependents any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateDependentLifecycleInfo", reflect.TypeOf((*MockDependentLifecycleManager)(nil).UpdateDependentLifecycleInfo), currentGenerationDependents) +} diff --git a/pkg/k8s/operator/operator_utils/operator_utils.go b/pkg/k8s/operator/operator_utils/operator_utils.go new file mode 100644 index 00000000..459b3bf7 --- /dev/null +++ b/pkg/k8s/operator/operator_utils/operator_utils.go @@ -0,0 +1,113 @@ +package operator_utils + +import ( + "golang.org/x/net/context" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + k8sclient "sigs.k8s.io/controller-runtime/pkg/client" + + instanav1 "github.com/instana/instana-agent-operator/api/v1" + "github.com/instana/instana-agent-operator/pkg/collections/list" + "github.com/instana/instana-agent-operator/pkg/k8s/client" + "github.com/instana/instana-agent-operator/pkg/k8s/object/builders/common/builder" + "github.com/instana/instana-agent-operator/pkg/k8s/operator/lifecycle" + "github.com/instana/instana-agent-operator/pkg/multierror" + "github.com/instana/instana-agent-operator/pkg/optional" + "github.com/instana/instana-agent-operator/pkg/result" +) + +type OperatorUtils interface { + ClusterIsOpenShift() result.Result[bool] + ApplyAll(builders ...builder.ObjectBuilder) result.Result[[]k8sclient.Object] + DeleteAll() result.Result[[]k8sclient.Object] +} + +type operatorUtils struct { + ctx context.Context + client.InstanaAgentClient + *instanav1.InstanaAgent + builderTransformer builder.BuilderTransformer + lifecycle.DependentLifecycleManager +} + +func NewOperatorUtils( + ctx context.Context, client client.InstanaAgentClient, agent *instanav1.InstanaAgent, +) OperatorUtils { + return &operatorUtils{ + ctx: ctx, + InstanaAgentClient: client, + InstanaAgent: agent, + builderTransformer: builder.NewBuilderTransformer(agent), + DependentLifecycleManager: lifecycle.NewDependentLifecycleManager(ctx, agent, client), + } +} + +func (o *operatorUtils) crdIsInstalled(name string) result.Result[bool] { + return o.Exists( + o.ctx, schema.GroupVersionKind{ + Group: "apiextensions.k8s.io", + Version: "v1", + Kind: "CustomResourceDefinition", + }, types.NamespacedName{Name: name}, + ) + +} + +func (o *operatorUtils) ClusterIsOpenShift() result.Result[bool] { + switch userProvided := o.Spec.OpenShift; userProvided { + case nil: + return o.crdIsInstalled("clusteroperators.config.openshift.io") + default: + return result.OfSuccess(*userProvided) + } +} + +func (o *operatorUtils) applyAllWithOpts( + objects []k8sclient.Object, opts ...k8sclient.PatchOption, +) result.Result[[]k8sclient.Object] { + errBuilder := multierror.NewMultiErrorBuilder() + + for _, obj := range objects { + o.Apply(o.ctx, obj, opts...).OnFailure(errBuilder.AddSingle) + } + + return result.Of(objects, errBuilder.Build()) +} + +func (o *operatorUtils) applyAll(objects []k8sclient.Object) result.Result[[]k8sclient.Object] { + return o.applyAllWithOpts(objects) +} + +func (o *operatorUtils) applyAllDryRun(objects []k8sclient.Object) result.Result[[]k8sclient.Object] { + return o.applyAllWithOpts(objects, k8sclient.DryRunAll) +} + +func (o *operatorUtils) buildObjects(builders ...builder.ObjectBuilder) []k8sclient.Object { + optionals := list.NewListMapTo[builder.ObjectBuilder, optional.Optional[k8sclient.Object]]().MapTo( + builders, + o.builderTransformer.Apply, + ) + return optional.NewNonEmptyOptionalMapper[k8sclient.Object]().AllNonEmpty(optionals) +} + +func (o *operatorUtils) ApplyAll(builders ...builder.ObjectBuilder) result.Result[[]k8sclient.Object] { + if dryRunRes := o.applyAllDryRun(o.buildObjects(builders...)); dryRunRes.IsFailure() { + return dryRunRes + } + + objects := o.buildObjects(builders...) + + if updateLifecycleCmRes := o.UpdateDependentLifecycleInfo(objects); updateLifecycleCmRes.IsFailure() { + return updateLifecycleCmRes + } + + if applyRes := o.applyAll(objects); applyRes.IsFailure() { + return applyRes + } + + return o.DeleteOrphanedDependents(objects) +} + +func (o *operatorUtils) DeleteAll() result.Result[[]k8sclient.Object] { + return o.DeleteAllDependents() +} diff --git a/pkg/k8s/operator/operator_utils/operator_utils_test.go b/pkg/k8s/operator/operator_utils/operator_utils_test.go new file mode 100644 index 00000000..b2e4c956 --- /dev/null +++ b/pkg/k8s/operator/operator_utils/operator_utils_test.go @@ -0,0 +1,133 @@ +/* +(c) Copyright IBM Corp. 2024 +(c) Copyright Instana Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package operator_utils + +import ( + "context" + "errors" + "reflect" + "testing" + + "github.com/stretchr/testify/require" + gomock "go.uber.org/mock/gomock" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + + instanav1 "github.com/instana/instana-agent-operator/api/v1" + "github.com/instana/instana-agent-operator/pkg/pointer" + "github.com/instana/instana-agent-operator/pkg/result" +) + +func testOperatorUtils_crdIsInstalled(t *testing.T, name string, methodName string) { + for _, test := range []struct { + name string + expected result.Result[bool] + }{ + { + name: "crd_exists", + expected: result.OfSuccess(true), + }, + { + name: "crd_does_not_exist", + expected: result.OfSuccess(false), + }, + { + name: "error_getting_crd", + expected: result.OfFailure[bool](errors.New("qwerty")), + }, + } { + t.Run( + test.name, func(t *testing.T) { + assertions := require.New(t) + ctrl := gomock.NewController(t) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + gvk := schema.GroupVersionKind{ + Group: "apiextensions.k8s.io", + Version: "v1", + Kind: "CustomResourceDefinition", + } + key := types.NamespacedName{ + Name: name, + } + + instanaClient := NewMockInstanaAgentClient(ctrl) + instanaClient.EXPECT().Exists(gomock.Eq(ctx), gomock.Eq(gvk), gomock.Eq(key)).Return(test.expected) + + ot := NewOperatorUtils(ctx, instanaClient, &instanav1.InstanaAgent{}) + + actual := reflect.ValueOf(ot).MethodByName(methodName).Call([]reflect.Value{})[0].Interface().(result.Result[bool]) + assertions.Equal(test.expected, actual) + }, + ) + } +} + +func TestOperatorUtils_ClusterIsOpenShift(t *testing.T) { + t.Run( + "user_provided", func(t *testing.T) { + for _, test := range []struct { + name string + userProvidedVal bool + expected result.Result[bool] + }{ + { + name: "user_specifies_true", + userProvidedVal: true, + expected: result.OfSuccess(true), + }, + { + name: "user_specifies_false", + userProvidedVal: false, + expected: result.OfSuccess(false), + }, + } { + t.Run( + test.name, func(t *testing.T) { + assertions := require.New(t) + ctrl := gomock.NewController(t) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + instanaClient := NewMockInstanaAgentClient(ctrl) + + ot := NewOperatorUtils( + ctx, instanaClient, &instanav1.InstanaAgent{ + Spec: instanav1.InstanaAgentSpec{ + OpenShift: pointer.To(test.userProvidedVal), + }, + }, + ) + + actual := ot.ClusterIsOpenShift() + assertions.Equal(test.expected, actual) + }, + ) + } + }, + ) + + t.Run( + "auto_detect", func(t *testing.T) { + testOperatorUtils_crdIsInstalled(t, "clusteroperators.config.openshift.io", "ClusterIsOpenShift") + }, + ) +} diff --git a/pkg/k8s/operator/status/status.go b/pkg/k8s/operator/status/status.go new file mode 100644 index 00000000..d3efd364 --- /dev/null +++ b/pkg/k8s/operator/status/status.go @@ -0,0 +1,463 @@ +package status + +import ( + "context" + "fmt" + "time" + + "github.com/Masterminds/semver/v3" + "github.com/go-logr/logr" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/tools/record" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" + + instanav1 "github.com/instana/instana-agent-operator/api/v1" + "github.com/instana/instana-agent-operator/pkg/collections/list" + "github.com/instana/instana-agent-operator/pkg/env" + instanaclient "github.com/instana/instana-agent-operator/pkg/k8s/client" + "github.com/instana/instana-agent-operator/pkg/multierror" + "github.com/instana/instana-agent-operator/pkg/optional" + "github.com/instana/instana-agent-operator/pkg/pointer" + "github.com/instana/instana-agent-operator/pkg/recovery" + "github.com/instana/instana-agent-operator/pkg/result" +) + +// Conditions +const ( + ConditionTypeReconcileSucceeded = "ReconcileSucceeded" + ConditionTypeAllAgentsAvailable = "AllAgentsAvailable" + CondtionTypeAllK8sSensorsAvailable = "AllK8sSensorsAvailable" +) + +func NewAgentStatusManager(k8sClient client.Client, eventRecorder record.EventRecorder) AgentStatusManager { + return &agentStatusManager{ + k8sClient: instanaclient.NewClient(k8sClient), + eventRecorder: eventRecorder, + agentDaemonsets: make([]client.ObjectKey, 0, 1), + } +} + +type AgentStatusManager interface { + AddAgentDaemonset(agentDaemonset client.ObjectKey) + SetAgentOld(agent *instanav1.InstanaAgent) + SetK8sSensorDeployment(k8sSensorDeployment client.ObjectKey) + SetAgentConfigMap(agentConfigMap client.ObjectKey) + UpdateAgentStatus(ctx context.Context, reconcileErr error) error +} + +type agentStatusManager struct { + k8sClient instanaclient.InstanaAgentClient + eventRecorder record.EventRecorder + + agentOld *instanav1.InstanaAgent + + agentDaemonsets []client.ObjectKey + k8sSensorDeployment client.ObjectKey + agentConfigMap client.ObjectKey +} + +func (a *agentStatusManager) SetAgentOld(agent *instanav1.InstanaAgent) { + a.agentOld = agent.DeepCopy() +} + +func (a *agentStatusManager) AddAgentDaemonset(agentDaemonset client.ObjectKey) { + if !list.NewContainsElementChecker(a.agentDaemonsets).Contains(agentDaemonset) { + a.agentDaemonsets = append(a.agentDaemonsets, agentDaemonset) + } +} + +func (a *agentStatusManager) SetK8sSensorDeployment(k8sSensorDeployment client.ObjectKey) { + a.k8sSensorDeployment = k8sSensorDeployment +} + +func (a *agentStatusManager) SetAgentConfigMap(agentConfigMap client.ObjectKey) { + a.agentConfigMap = agentConfigMap +} + +func getAgentPhase(reconcileErr error) instanav1.AgentOperatorState { + switch reconcileErr { + case nil: + return instanav1.OperatorStateRunning + default: + return instanav1.OperatorStateFailed + } +} + +func getReason(reconcileErr error) string { + switch reconcileErr { + case nil: + return "" + default: + return reconcileErr.Error() + } +} + +func toResourceInfo(obj client.Object) result.Result[instanav1.ResourceInfo] { + return result.OfSuccess( + instanav1.ResourceInfo{ + Name: obj.GetName(), + UID: string(obj.GetUID()), + }, + ) +} + +func (a *agentStatusManager) getDaemonSet(ctx context.Context) result.Result[instanav1.ResourceInfo] { + if len(a.agentDaemonsets) != 1 { + return result.OfSuccess(instanav1.ResourceInfo{}) + } + + ds := a.k8sClient.GetAsResult(ctx, a.agentDaemonsets[0], &appsv1.DaemonSet{}) + + return result.Map(ds, toResourceInfo) +} + +func (a *agentStatusManager) getConfigMap(ctx context.Context) result.Result[instanav1.ResourceInfo] { + cm := a.k8sClient.GetAsResult(ctx, a.agentConfigMap, &corev1.ConfigMap{}) + + return result.Map(cm, toResourceInfo) +} + +func truncateMessage(message string) string { + const limit = 32768 + + if len(message) <= limit { + return message + } else { + return message[:limit] + } +} + +func eventTypeFromCondition(condition metav1.Condition) string { + //nolint:exhaustive + switch condition.Status { + case metav1.ConditionTrue: + return corev1.EventTypeNormal + default: + return corev1.EventTypeWarning + } +} + +func (a *agentStatusManager) setConditionAndFireEvent(agentNew *instanav1.InstanaAgent, condition metav1.Condition) { + meta.SetStatusCondition(&agentNew.Status.Conditions, condition) + a.eventRecorder.Event(agentNew, eventTypeFromCondition(condition), condition.Reason, condition.Message) +} + +func (a *agentStatusManager) getReconcileSucceededCondition(reconcileErr error) metav1.Condition { + res := metav1.Condition{ + Type: ConditionTypeReconcileSucceeded, + Status: "", + ObservedGeneration: a.agentOld.GetGeneration(), + Reason: "", + Message: "", + } + + switch reconcileErr { + case nil: + res.Status = metav1.ConditionTrue + res.Reason = "ReconcileSucceeded" + res.Message = "most recent reconcile of agent CR completed without issue" + default: + res.Status = metav1.ConditionFalse + res.Reason = "ReconcileFailed" + // TODO: Error wrapping where propagating to add relevant info + res.Message = truncateMessage(reconcileErr.Error()) + } + + return res +} + +func daemonsetIsAvailable(ds appsv1.DaemonSet) bool { + switch status := ds.Status; { + case optional.Of(status).IsNotPresent(): + return false + case ds.Generation != status.ObservedGeneration: + return false + case status.NumberMisscheduled != 0: + return false + case status.DesiredNumberScheduled != status.NumberAvailable: + return false + case status.DesiredNumberScheduled != status.UpdatedNumberScheduled: + return false + default: + return true + } +} + +func (a *agentStatusManager) getAllAgentsAvailableCondition(ctx context.Context) result.Result[metav1.Condition] { + condition := metav1.Condition{ + Type: ConditionTypeAllAgentsAvailable, + Status: "", + ObservedGeneration: a.agentOld.GetGeneration(), + Reason: "", + Message: "", + } + + dameonsets := make([]appsv1.DaemonSet, 0, len(a.agentDaemonsets)) + + for _, key := range a.agentDaemonsets { + var ds appsv1.DaemonSet + switch res := a.k8sClient.GetAsResult(ctx, key, &ds); { + case res.IsSuccess(): + dameonsets = append(dameonsets, ds) + case res.IsFailure(): + _, err := res.Get() + + condition.Status = metav1.ConditionUnknown + condition.Reason = "AgentDaemonsetInfoUnavailable" + //goland:noinspection GoNilness + msg := fmt.Sprintf( + "failed to retrieve status of Agent Daemonset: %s due to error: %s", + key.Name, + err.Error(), + ) + truncatedMsg := truncateMessage(msg) + condition.Message = truncatedMsg + + return result.Of(condition, err) + } + } + + // TODO: Implement a robust readiness endpoint in the agent for the readiness probe? + switch list.NewConditions(dameonsets).All(daemonsetIsAvailable) { + case true: + condition.Status = metav1.ConditionTrue + condition.Reason = "AllDesiredAgentsAvailable" + condition.Message = "All desired Instana Agents are available and using up-to-date configuration" + default: + condition.Status = metav1.ConditionFalse + condition.Reason = "NotAllDesiredAgentsAvailable" + condition.Message = "Not all desired Instana agents are available or some Agents are not using up-to-date configuration" + } + + return result.OfSuccess(condition) +} + +type deploymentConditionsMap map[appsv1.DeploymentConditionType]appsv1.DeploymentCondition + +func deploymentConditionsAsMap(conditions []appsv1.DeploymentCondition) deploymentConditionsMap { + res := make(deploymentConditionsMap, len(conditions)) + + for _, condition := range conditions { + res[condition.Type] = condition + } + + return res +} + +func deploymentHasMinimumAvailability(conditions deploymentConditionsMap) bool { + switch condition, isPresent := conditions[appsv1.DeploymentAvailable]; isPresent { + case true: + return condition.Status == corev1.ConditionTrue + default: + return false + } +} + +func deploymentIsComplete(conditions deploymentConditionsMap) bool { + switch condition, isPresent := conditions[appsv1.DeploymentProgressing]; isPresent { + case true: + return condition.Status == corev1.ConditionTrue && condition.Reason == "NewReplicaSetAvailable" + default: + return false + } +} + +func deploymentHasReplicaFailures(conditions deploymentConditionsMap) bool { + switch condition, isPresent := conditions[appsv1.DeploymentReplicaFailure]; isPresent { + case true: + return condition.Status == corev1.ConditionTrue + default: + return false + } +} + +func deploymentIsAvailableAndComplete(dpl appsv1.Deployment) bool { + conditions := deploymentConditionsAsMap(dpl.Status.Conditions) + + switch { + case dpl.Status.ObservedGeneration != dpl.Generation: + return false + case !deploymentHasMinimumAvailability(conditions): + return false + case !deploymentIsComplete(conditions): + return false + case deploymentHasReplicaFailures(conditions): + return false + default: + return true + } +} + +func (a *agentStatusManager) updateWasPerformed() bool { + switch operatorVersion, _ := semver.NewVersion(env.GetOperatorVersion()); { + case a.agentOld.Status.ObservedGeneration == nil: + return false + case *a.agentOld.Status.ObservedGeneration != a.agentOld.Generation: + return true + case a.agentOld.Status.OperatorVersion == nil: + return false + case operatorVersion == nil: + return true + case !a.agentOld.Status.OperatorVersion.Version.Equal(operatorVersion): + return true + default: + return false + } +} + +func (a *agentStatusManager) getAllK8sSensorsAvailableCondition(ctx context.Context) result.Result[metav1.Condition] { + condition := metav1.Condition{ + Type: CondtionTypeAllK8sSensorsAvailable, + Status: "", + ObservedGeneration: a.agentOld.GetGeneration(), + Reason: "", + Message: "", + } + + var deployment appsv1.Deployment + + if res := a.k8sClient.GetAsResult(ctx, a.k8sSensorDeployment, &deployment); res.IsFailure() { + _, err := res.Get() + + condition.Status = metav1.ConditionUnknown + condition.Reason = "K8sSensorDeploymentInfoUnavailable" + //goland:noinspection GoNilness + msg := fmt.Sprintf( + "failed to retrieve status of K8sSensor Deployment: %s due to error: %s", + a.k8sSensorDeployment.Name, + err.Error(), + ) + truncatedMsg := truncateMessage(msg) + condition.Message = truncatedMsg + + return result.Of(condition, err) + } + + switch deploymentIsAvailableAndComplete(deployment) { + case true: + condition.Status = metav1.ConditionTrue + condition.Reason = "AllDesiredK8sSensorsAvailable" + condition.Message = "All desired K8sSensors are available and using up-to-date configuration" + default: + condition.Status = metav1.ConditionFalse + condition.Reason = "NotAllDesiredK8sSensorsAvailable" + condition.Message = "Not all desired K8sSensors are available or some K8sSensors are not using up-to-date configuration" + } + + return result.OfSuccess(condition) +} + +func setStatusDotDaemonset(agentNew *instanav1.InstanaAgent) func(ds instanav1.ResourceInfo) { + return func(ds instanav1.ResourceInfo) { + agentNew.Status.DaemonSet = ds + } +} + +func setStatusDotConfigMap(agentNew *instanav1.InstanaAgent) func(cm instanav1.ResourceInfo) { + return func(cm instanav1.ResourceInfo) { + agentNew.Status.ConfigMap = cm + } +} + +func setStatusDotOperatorVersion(agentNew *instanav1.InstanaAgent) func(version *semver.Version) { + return func(version *semver.Version) { + agentNew.Status.OperatorVersion = &instanav1.SemanticVersion{Version: *version} + } +} + +func logOperatorVersionParseFailure(logger logr.Logger) func(err error) { + return func(err error) { + logger.Error( + err, + "operator version is not a valid semantic version", + "OperatorVersion", + env.GetOperatorVersion(), + ) + } +} + +func (a *agentStatusManager) agentWithUpdatedStatus( + ctx context.Context, + reconcileErr error, +) result.Result[*instanav1.InstanaAgent] { + errBuilder := multierror.NewMultiErrorBuilder() + + agentNew := a.agentOld.DeepCopy() + logger := log.FromContext(ctx).WithName("agent-status-manager") + + // Handle Deprecated Status Fields + + agentNew.Status.Status = getAgentPhase(reconcileErr) + agentNew.Status.Reason = getReason(reconcileErr) + agentNew.Status.LastUpdate = metav1.Time{Time: time.Now()} + + a.getDaemonSet(ctx). + OnSuccess(setStatusDotDaemonset(agentNew)). + OnFailure(errBuilder.AddSingle) + + a.getConfigMap(ctx). + OnSuccess(setStatusDotConfigMap(agentNew)). + OnFailure(errBuilder.AddSingle) + + if a.updateWasPerformed() { + agentNew.Status.OldVersionsUpdated = true + } + + // Handle New Status Fields + + agentNew.Status.ObservedGeneration = pointer.To(a.agentOld.GetGeneration()) + + result.Of(semver.NewVersion(env.GetOperatorVersion())). + OnSuccess(setStatusDotOperatorVersion(agentNew)). + OnFailure(logOperatorVersionParseFailure(logger)) + + // Handle Conditions + agentNew.Status.Conditions = optional.Of(agentNew.Status.Conditions).GetOrDefault(make([]metav1.Condition, 0, 3)) + + reconcileSucceededCondition := a.getReconcileSucceededCondition(reconcileErr) + a.setConditionAndFireEvent(agentNew, reconcileSucceededCondition) + + allAgentsAvailableCondition, _ := + a.getAllAgentsAvailableCondition(ctx). + OnFailure(errBuilder.AddSingle). + Get() + a.setConditionAndFireEvent(agentNew, allAgentsAvailableCondition) + + allK8sSensorsAvailableCondition, _ := + a.getAllK8sSensorsAvailableCondition(ctx). + OnFailure(errBuilder.AddSingle). + Get() + a.setConditionAndFireEvent(agentNew, allK8sSensorsAvailableCondition) + + return result.Of(agentNew, errBuilder.Build()) +} + +func (a *agentStatusManager) UpdateAgentStatus(ctx context.Context, reconcileErr error) (finalErr error) { + defer recovery.Catch(&finalErr) + + if a.agentOld == nil { + return nil + } + + errBuilder := multierror.NewMultiErrorBuilder() + + agentNew, _ := + a.agentWithUpdatedStatus(ctx, reconcileErr). + OnFailure(errBuilder.AddSingle). + Get() + + if err := a.k8sClient.Status().Patch( + ctx, + agentNew, + client.MergeFrom(a.agentOld), + client.FieldOwner(instanaclient.FieldOwnerName), + ); err != nil { + errBuilder.AddSingle(err) + } + + return errBuilder.Build() +} diff --git a/pkg/map_defaulter/map_defaulter.go b/pkg/map_defaulter/map_defaulter.go new file mode 100644 index 00000000..7ad4c04f --- /dev/null +++ b/pkg/map_defaulter/map_defaulter.go @@ -0,0 +1,23 @@ +package map_defaulter + +import "github.com/instana/instana-agent-operator/pkg/optional" + +type MapDefaulter[K comparable, V any] interface { + SetIfEmpty(key K, def V) +} + +type mapDefaulter[K comparable, V any] struct { + ptrToMap *map[K]V +} + +func NewMapDefaulter[K comparable, V any](ptrToMap *map[K]V) MapDefaulter[K, V] { + *ptrToMap = optional.Of(*ptrToMap).GetOrDefault(make(map[K]V)) + + return &mapDefaulter[K, V]{ + ptrToMap: ptrToMap, + } +} + +func (m *mapDefaulter[K, V]) SetIfEmpty(key K, def V) { + (*m.ptrToMap)[key] = optional.Of((*m.ptrToMap)[key]).GetOrDefault(def) +} diff --git a/pkg/map_defaulter/map_defaulter_test.go b/pkg/map_defaulter/map_defaulter_test.go new file mode 100644 index 00000000..d25e68cf --- /dev/null +++ b/pkg/map_defaulter/map_defaulter_test.go @@ -0,0 +1,43 @@ +package map_defaulter + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestMapDefaulter_SetIfEmpty(t *testing.T) { + for _, tc := range []struct { + name string + input map[string]interface{} + key string + value interface{} + expected interface{} + }{ + { + name: "sets_default", + input: nil, + key: "hello", + value: "world", + expected: "world", + }, + { + name: "already_set", + input: map[string]interface{}{"hello": "goodbye"}, + key: "hello", + value: "world", + expected: "goodbye", + }, + } { + t.Run( + tc.name, func(t *testing.T) { + assertions := require.New(t) + + m := tc.input + + NewMapDefaulter(&m).SetIfEmpty(tc.key, tc.value) + assertions.Equal(tc.expected, m[tc.key]) + }, + ) + } +} diff --git a/pkg/multierror/multierror.go b/pkg/multierror/multierror.go new file mode 100644 index 00000000..035f5257 --- /dev/null +++ b/pkg/multierror/multierror.go @@ -0,0 +1,82 @@ +package multierror + +import ( + "errors" + "fmt" + "strings" + + "github.com/instana/instana-agent-operator/pkg/collections/list" +) + +type multiError struct { + error +} + +func (m multiError) Unwrap() error { + return m.error +} + +func AsMultiError(err error) bool { + return errors.As(err, &multiError{}) +} + +type MultiErrorBuilder interface { + Build() error + Add(errs ...error) + AddSingle(err error) + All() []error + AllNonNil() []error +} + +type multiErrorBuilder struct { + errs []error +} + +func NewMultiErrorBuilder(errs ...error) MultiErrorBuilder { + return &multiErrorBuilder{ + errs: errs, + } +} + +func (m *multiErrorBuilder) Build() error { + errs := m.AllNonNil() + + switch len(errs) { + case 0: + return nil + case 1: + return multiError{ + error: errs[0], + } + default: + errMsgFmt := "Multiple Errors:\n" + strings.Repeat("%w\n", len(errs)) + errsAsAny := list.NewListMapTo[error, any]().MapTo( + errs, func(err error) any { + return err + }, + ) + return multiError{ + error: fmt.Errorf(errMsgFmt, errsAsAny...), + } + } +} + +func (m *multiErrorBuilder) Add(errs ...error) { + m.errs = append(m.errs, errs...) +} + +func (m *multiErrorBuilder) AddSingle(err error) { + m.Add(err) +} + +func (m *multiErrorBuilder) All() []error { + return m.errs +} + +func (m *multiErrorBuilder) AllNonNil() []error { + return list.NewListFilter[error]().Filter( + m.errs, func(err error) bool { + return !errors.Is(err, nil) + }, + ) +} diff --git a/pkg/multierror/multierror_test.go b/pkg/multierror/multierror_test.go new file mode 100644 index 00000000..e6b820f5 --- /dev/null +++ b/pkg/multierror/multierror_test.go @@ -0,0 +1,151 @@ +package multierror + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/require" +) + +func UnwrapAll(err error) []error { + u, ok := err.(interface { + Unwrap() []error + }) + if !ok { + return nil + } + return u.Unwrap() +} + +func TestMultiError(t *testing.T) { + meTarget := multiError{} + seTarget := errors.New("") + + t.Run( + "empty_should_be_nil", func(t *testing.T) { + assertions := require.New(t) + + me := NewMultiErrorBuilder() + assertions.ErrorIs(me.Build(), nil) + + me.Add(errors.New("")) + assertions.NotNil(me.Build()) + }, + ) + t.Run( + "all_nil_should_be_nil", func(t *testing.T) { + assertions := require.New(t) + + me := NewMultiErrorBuilder(nil, nil) + assertions.ErrorIs(me.Build(), nil) + + me.Add(errors.New("")) + assertions.NotNil(me.Build()) + }, + ) + t.Run( + "is_all_constituent_errors", func(t *testing.T) { + assertions := require.New(t) + + expected1 := errors.New("1") + expected2 := errors.New("2") + + me := NewMultiErrorBuilder(nil, expected1, nil) + + assertions.Equal([]error{nil, expected1, nil}, me.All()) + assertions.Equal([]error{expected1}, me.AllNonNil()) + assertions.ErrorIs(me.Build(), expected1) + + me.Add(expected2) + + assertions.Equal([]error{nil, expected1, nil, expected2}, me.All()) + assertions.Equal([]error{expected1, expected2}, me.AllNonNil()) + actual := me.Build() + assertions.ErrorIs(actual, expected1) + assertions.ErrorIs(actual, expected2) + }, + ) + t.Run( + "combine_and_add", func(t *testing.T) { + assertions := require.New(t) + + me := NewMultiErrorBuilder( + errors.New("1"), + nil, + errors.New("2"), + errors.New("3"), + ) + assertions.Equal( + []error{ + errors.New("1"), + nil, + errors.New("2"), + errors.New("3"), + }, + me.All(), + ) + assertions.Equal( + []error{ + errors.New("1"), + errors.New("2"), + errors.New("3"), + }, + me.AllNonNil(), + ) + assertions.Equal( + []error{ + errors.New("1"), + errors.New("2"), + errors.New("3"), + }, + UnwrapAll(me.Build().(multiError).Unwrap()), + ) + assertions.NotNil(me.Build()) + assertions.ErrorAs(me.Build(), &meTarget) + assertions.True(AsMultiError(me.Build())) + assertions.ErrorAs(me.Build(), &seTarget) + + me.Add( + errors.New("4"), + nil, + errors.New("5"), + ) + assertions.Equal( + []error{ + errors.New("1"), + nil, + errors.New("2"), + errors.New("3"), + errors.New("4"), + nil, + errors.New("5"), + }, + me.All(), + ) + assertions.Equal( + []error{ + errors.New("1"), + errors.New("2"), + errors.New("3"), + errors.New("4"), + errors.New("5"), + }, + me.AllNonNil(), + ) + assertions.Equal( + []error{ + errors.New("1"), + errors.New("2"), + errors.New("3"), + errors.New("4"), + errors.New("5"), + }, + UnwrapAll(me.Build().(multiError).Unwrap()), + ) + assertions.NotNil(me.Build()) + assertions.ErrorAs(me.Build(), &meTarget) + assertions.True(AsMultiError(me.Build())) + assertions.ErrorAs(me.Build(), &seTarget) + }, + ) +} diff --git a/pkg/optional/mapper.go b/pkg/optional/mapper.go new file mode 100644 index 00000000..f94a8524 --- /dev/null +++ b/pkg/optional/mapper.go @@ -0,0 +1,33 @@ +package optional + +import "github.com/instana/instana-agent-operator/pkg/collections/list" + +type NonEmptyOptionalMapper[T any] interface { + AllNonEmpty(in []Optional[T]) []T +} + +type nonEmptyOptionalMapper[T any] struct { + list.ListFilter[Optional[T]] + list.ListMapTo[Optional[T], T] +} + +func NewNonEmptyOptionalMapper[T any]() NonEmptyOptionalMapper[T] { + return &nonEmptyOptionalMapper[T]{ + ListFilter: list.NewListFilter[Optional[T]](), + ListMapTo: list.NewListMapTo[Optional[T], T](), + } +} + +func (o *nonEmptyOptionalMapper[T]) AllNonEmpty(in []Optional[T]) []T { + withoutEmpties := o.Filter( + in, func(val Optional[T]) bool { + return !val.IsNotPresent() + }, + ) + + return o.MapTo( + withoutEmpties, func(val Optional[T]) T { + return val.Get() + }, + ) +} diff --git a/pkg/optional/mapper_test.go b/pkg/optional/mapper_test.go new file mode 100644 index 00000000..ec273526 --- /dev/null +++ b/pkg/optional/mapper_test.go @@ -0,0 +1,29 @@ +package optional + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestAllNonEmpty(t *testing.T) { + mpr := NewNonEmptyOptionalMapper[int]() + + in := []Optional[int]{ + Empty[int](), + Of(1), + Of(0), + Of(2), + Of(3), + Empty[int](), + Empty[int](), + Empty[int](), + Of(4), + Of(5), + } + out := mpr.AllNonEmpty(in) + + assertions := require.New(t) + + assertions.Equal([]int{1, 2, 3, 4, 5}, out) +} diff --git a/pkg/optional/optional.go b/pkg/optional/optional.go new file mode 100644 index 00000000..c1bad9b8 --- /dev/null +++ b/pkg/optional/optional.go @@ -0,0 +1,71 @@ +package optional + +import "reflect" + +type optional[T any] struct { + val T +} + +func (o *optional[T]) IsNotPresent() bool { + valueOf := reflect.ValueOf(o.val) + return !valueOf.IsValid() || valueOf.IsZero() +} + +func (o *optional[T]) IsPresent() bool { + return !o.IsNotPresent() +} + +func (o *optional[T]) Get() T { + return o.val +} + +func (o *optional[T]) GetOrDefault(val T) T { + return o.GetOrElse( + func() T { + return val + }, + ) +} + +func (o *optional[T]) GetOrElse(f func() T) T { + switch o.IsNotPresent() { + case true: + return f() + default: + return o.val + } +} + +func (o *optional[T]) IfPresent(do func(val T)) { + if o.IsPresent() { + do(o.Get()) + } +} + +type Optional[T any] interface { + IsNotPresent() bool + IsPresent() bool + Get() T + GetOrDefault(val T) T + GetOrElse(func() T) T + IfPresent(func(T)) +} + +func Empty[T any]() Optional[T] { + return &optional[T]{} +} + +func Of[T any](val T) Optional[T] { + return &optional[T]{ + val: val, + } +} + +func Map[T any, U any](in Optional[T], transform func(in T) U) Optional[U] { + switch in.IsNotPresent() { + case true: + return Empty[U]() + default: + return Of(transform(in.Get())) + } +} diff --git a/pkg/optional/optional_test.go b/pkg/optional/optional_test.go new file mode 100644 index 00000000..9ea56ddb --- /dev/null +++ b/pkg/optional/optional_test.go @@ -0,0 +1,190 @@ +package optional + +import ( + "testing" + + "github.com/instana/instana-agent-operator/pkg/pointer" + + "github.com/stretchr/testify/require" +) + +func assertIsEmpty[T any](t *testing.T, opt Optional[T]) { + assertions := require.New(t) + + assertions.True(opt.IsNotPresent()) + assertions.False(opt.IsPresent()) + assertions.Zero(opt.Get()) +} + +func assertIsNotEmpty[T any](t *testing.T, opt Optional[T]) { + assertions := require.New(t) + + assertions.False(opt.IsNotPresent()) + assertions.True(opt.IsPresent()) + assertions.NotZero(opt.Get()) +} + +func TestOptional_IsEmpty(t *testing.T) { + for _, test := range []struct { + name string + f func(t *testing.T) + }{ + { + name: "empty_created", + f: func(t *testing.T) { + assertIsEmpty(t, Empty[any]()) + }, + }, + { + name: "nil_provided", + f: func(t *testing.T) { + assertIsEmpty(t, Of[any](nil)) + }, + }, + { + name: "non_zero_pointer_to_zero_val", + f: func(t *testing.T) { + assertIsNotEmpty[*bool](t, Of[*bool](pointer.To(false))) + }, + }, + { + name: "non_zero_explicit", + f: func(t *testing.T) { + assertIsNotEmpty[bool](t, Of[bool](true)) + }, + }, + { + name: "zero_explicit", + f: func(t *testing.T) { + assertIsEmpty[bool](t, Of[bool](false)) + }, + }, + } { + t.Run(test.name, test.f) + } +} + +func TestOptional_GetOrElse(t *testing.T) { + for _, tc := range []struct { + name string + input Optional[string] + expected string + }{ + { + name: "nil_given", + input: Empty[string](), + expected: "proijrognasoieojsg", + }, + { + name: "non_nil_given", + input: Of("opasegoihsegoihsg"), + expected: "opasegoihsegoihsg", + }, + } { + t.Run( + tc.name, func(t *testing.T) { + assertions := require.New(t) + + actual := tc.input.GetOrElse( + func() string { + return "proijrognasoieojsg" + }, + ) + + assertions.Equal(tc.expected, actual) + }, + ) + } +} + +func TestOptional_GetOrDefault(t *testing.T) { + for _, tc := range []struct { + name string + input Optional[string] + expected string + }{ + { + name: "nil_given", + input: Empty[string](), + expected: "proijrognasoieojsg", + }, + { + name: "non_nil_given", + input: Of("opasegoihsegoihsg"), + expected: "opasegoihsegoihsg", + }, + } { + t.Run( + tc.name, func(t *testing.T) { + assertions := require.New(t) + + actual := tc.input.GetOrDefault("proijrognasoieojsg") + + assertions.Equal(tc.expected, actual) + }, + ) + } +} + +func TestMap(t *testing.T) { + for _, tc := range []struct { + name string + in Optional[string] + want Optional[*string] + }{ + { + name: "when_empty", + in: Empty[string](), + want: Empty[*string](), + }, + { + name: "when_not_empty", + in: Of[string]("oiw4eoijsoidjdsgf"), + want: Of[*string](pointer.To("oiw4eoijsoidjdsgf")), + }, + } { + t.Run( + tc.name, func(t *testing.T) { + assertions := require.New(t) + + actual := Map[string, *string]( + tc.in, func(in string) *string { + return &in + }, + ) + + assertions.Equal(tc.want, actual) + }, + ) + } +} + +func TestIfPresent(t *testing.T) { + t.Run( + "not_present", func(t *testing.T) { + assertions := require.New(t) + + o := Of("") + o.IfPresent( + func(_ string) { + assertions.Fail("this function should not run if optional is empty") + }, + ) + }, + ) + t.Run( + "is_present", func(t *testing.T) { + assertions := require.New(t) + + actual := 0 + + o := Of(5) + o.IfPresent( + func(i int) { + actual = i + }, + ) + assertions.Equal(5, actual) + }, + ) +} diff --git a/pkg/optional/value_or_default.go b/pkg/optional/value_or_default.go new file mode 100644 index 00000000..4ac216f9 --- /dev/null +++ b/pkg/optional/value_or_default.go @@ -0,0 +1,5 @@ +package optional + +func ValueOrDefault[T any](val *T, def T) { + *val = Of(*val).GetOrDefault(def) +} diff --git a/pkg/or_die/or_die.go b/pkg/or_die/or_die.go new file mode 100644 index 00000000..4d6b2ac6 --- /dev/null +++ b/pkg/or_die/or_die.go @@ -0,0 +1,20 @@ +package or_die + +type orDie[T any] struct{} + +func (o *orDie[T]) ResultOrDie(resultAndError func() (T, error)) T { + switch res, err := resultAndError(); err == nil { + case true: + return res + default: + panic(err) + } +} + +type OrDie[T any] interface { + ResultOrDie(resultAndError func() (T, error)) T +} + +func New[T any]() OrDie[T] { + return &orDie[T]{} +} diff --git a/pkg/or_die/or_die_test.go b/pkg/or_die/or_die_test.go new file mode 100644 index 00000000..89aa723e --- /dev/null +++ b/pkg/or_die/or_die_test.go @@ -0,0 +1,40 @@ +package or_die + +import ( + "testing" + + "github.com/pkg/errors" + "github.com/stretchr/testify/require" +) + +func TestOrPanic(t *testing.T) { + p := New[string]() + t.Run( + "no error", func(t *testing.T) { + const expected = "oiwegoihsdoi" + assertions := require.New(t) + actual := p.ResultOrDie( + func() (string, error) { + return expected, nil + }, + ) + assertions.Equal(expected, actual) + }, + ) + t.Run( + "with error", func(t *testing.T) { + const expected = "woiegoisoishdg" + assertions := require.New(t) + + assertions.PanicsWithError( + expected, func() { + p.ResultOrDie( + func() (string, error) { + return "oisjeoijsdoigj", errors.New(expected) + }, + ) + }, + ) + }, + ) +} diff --git a/pkg/pointer/pointer.go b/pkg/pointer/pointer.go new file mode 100644 index 00000000..2185c9e0 --- /dev/null +++ b/pkg/pointer/pointer.go @@ -0,0 +1,31 @@ +package pointer + +func To[T any](in T) *T { + return &in +} + +func DerefOrEmpty[T any](in *T) T { + return DerefOrElse( + in, func() T { + var zero T + return zero + }, + ) +} + +func DerefOrDefault[T any](in *T, def T) T { + return DerefOrElse( + in, func() T { + return def + }, + ) +} + +func DerefOrElse[T any](in *T, do func() T) T { + switch in { + case nil: + return do() + default: + return *in + } +} diff --git a/pkg/pointer/pointer_test.go b/pkg/pointer/pointer_test.go new file mode 100644 index 00000000..79541431 --- /dev/null +++ b/pkg/pointer/pointer_test.go @@ -0,0 +1,104 @@ +package pointer + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestTo(t *testing.T) { + assertions := require.New(t) + + actual := *To(5) + + assertions.Equal(5, actual) +} + +func TestDerefOrEmpty(t *testing.T) { + for _, tc := range []struct { + name string + input *int + output int + }{ + { + name: "non_nil_pointer_given", + input: To(10), + output: 10, + }, + { + name: "nil_pointer_given", + input: nil, + output: 0, + }, + } { + t.Run( + tc.name, func(t *testing.T) { + assertions := require.New(t) + + actual := DerefOrEmpty(tc.input) + + assertions.Equal(tc.output, actual) + }, + ) + } +} + +func TestDerefOrDefault(t *testing.T) { + for _, tt := range []struct { + name string + input *int + defVal int + want int + }{ + { + name: "non_nil_pointer_given", + input: To(5), + defVal: 10, + want: 5, + }, + { + name: "nil_pointer_given", + input: nil, + defVal: 10, + want: 10, + }, + } { + t.Run( + tt.name, func(t *testing.T) { + assertions := require.New(t) + actual := DerefOrDefault(tt.input, tt.defVal) + assertions.Equal(tt.want, actual) + }, + ) + } +} + +func TestDerefOrElse(t *testing.T) { + for _, tc := range []struct { + name string + pointer *int + defaultValue func() int + expectedValue int + }{ + { + name: "non_nil_pointer_given", + pointer: To(5), + defaultValue: func() int { return 10 }, + expectedValue: 5, + }, + { + name: "nil_pointer_given", + pointer: nil, + defaultValue: func() int { return 10 }, + expectedValue: 10, + }, + } { + t.Run( + tc.name, func(t *testing.T) { + assertions := require.New(t) + actual := DerefOrElse(tc.pointer, tc.defaultValue) + assertions.Equal(tc.expectedValue, actual) + }, + ) + } +} diff --git a/pkg/recovery/recovery.go b/pkg/recovery/recovery.go new file mode 100644 index 00000000..2cea77cb --- /dev/null +++ b/pkg/recovery/recovery.go @@ -0,0 +1,42 @@ +package recovery + +import ( + "errors" + "fmt" + "runtime/debug" + + "github.com/instana/instana-agent-operator/pkg/multierror" +) + +type caughtPanic struct { + error +} + +func (c caughtPanic) Unwrap() error { + return c.error +} + +func AsCaughtPanic(err error) bool { + return errors.As(err, &caughtPanic{}) +} + +func Catch(err *error) { + if p := recover(); p != nil { + errBuilder := multierror.NewMultiErrorBuilder(*err) + + verb := func() string { + switch p.(type) { + case error: + return "%w" + default: + return "%v" + } + }() + + errBuilder.AddSingle(fmt.Errorf("caught panic: "+verb+"\n%s", p, debug.Stack())) + + *err = caughtPanic{ + error: errBuilder.Build(), + } + } +} diff --git a/pkg/recovery/recovery_test.go b/pkg/recovery/recovery_test.go new file mode 100644 index 00000000..20c92bd2 --- /dev/null +++ b/pkg/recovery/recovery_test.go @@ -0,0 +1,73 @@ +package recovery + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestCatch(t *testing.T) { + t.Run( + "catch_error", func(t *testing.T) { + assertions := require.New(t) + + expected := errors.New("hello") + + actual := func() (err error) { + defer Catch(&err) + + panic(expected) + }() + + assertions.ErrorAs(actual, &caughtPanic{}) + assertions.True(AsCaughtPanic(actual)) + assertions.ErrorIs(actual, expected) + + t.Log(actual.Error()) + }, + ) + t.Run( + "catch_other", func(t *testing.T) { + assertions := require.New(t) + + expected := "hello" + + actual := func() (err error) { + defer Catch(&err) + + panic(expected) + }() + + assertions.ErrorAs(actual, &caughtPanic{}) + assertions.True(AsCaughtPanic(actual)) + + t.Log(actual.Error()) + }, + ) + t.Run( + "catch_from_defer", func(t *testing.T) { + assertions := require.New(t) + + expectedReturn := errors.New("I will be returned") + expectedPanic := errors.New("I will be thrown in panic") + + actual := func() (err error) { + defer Catch(&err) + + defer func() { + panic(expectedPanic) + }() + + return expectedReturn + }() + + assertions.ErrorAs(actual, &caughtPanic{}) + assertions.True(AsCaughtPanic(actual)) + assertions.ErrorIs(actual, expectedReturn) + assertions.ErrorIs(actual, expectedPanic) + + t.Log(actual.Error()) + }, + ) +} diff --git a/pkg/result/result.go b/pkg/result/result.go new file mode 100644 index 00000000..c3dcde03 --- /dev/null +++ b/pkg/result/result.go @@ -0,0 +1,127 @@ +package result + +import ( + "errors" + + "github.com/instana/instana-agent-operator/pkg/optional" + "github.com/instana/instana-agent-operator/pkg/recovery" +) + +type Result[T any] interface { + IsSuccess() bool + IsFailure() bool + Get() (T, error) + ToOptional() optional.Optional[T] + OnSuccess(func(T)) Result[T] + OnFailure(func(error)) Result[T] + Recover(func(error) (T, error)) Result[T] + RecoverCatching(func(error) (T, error)) Result[T] +} + +type result[T any] struct { + res T + err error +} + +func (r *result[T]) IsSuccess() bool { + return errors.Is(r.err, nil) +} + +func (r *result[T]) IsFailure() bool { + return !r.IsSuccess() +} + +func (r *result[T]) Get() (T, error) { + return r.res, r.err +} + +func (r *result[T]) ToOptional() optional.Optional[T] { + switch r.IsSuccess() { + case true: + return optional.Of(r.res) + default: + return optional.Empty[T]() + } +} + +func (r *result[T]) OnSuccess(do func(res T)) Result[T] { + if r.IsSuccess() { + do(r.res) + } + return r +} + +func (r *result[T]) OnFailure(do func(err error)) Result[T] { + if r.IsFailure() { + do(r.err) + } + return r +} + +func (r *result[T]) recover(getRecoveredResult func() Result[T]) Result[T] { + switch r.IsFailure() { + case true: + return getRecoveredResult() + default: + return r + } +} + +func (r *result[T]) Recover(do func(err error) (T, error)) Result[T] { + return r.recover( + func() Result[T] { + return Of(do(r.err)) + }, + ) +} + +func (r *result[T]) RecoverCatching(do func(err error) (T, error)) Result[T] { + return r.recover( + func() Result[T] { + return OfInlineCatchingPanic( + func() (res T, err error) { + return do(r.err) + }, + ) + }, + ) +} + +func Of[T any](res T, err error) Result[T] { + return &result[T]{ + res: res, + err: err, + } +} + +func OfInline[T any](do func() (res T, err error)) Result[T] { + return Of(do()) +} + +func OfInlineCatchingPanic[T any](do func() (res T, err error)) Result[T] { + return OfInline[T]( + func() (res T, err error) { + defer recovery.Catch(&err) + return do() + }, + ) +} + +func OfSuccess[T any](res T) Result[T] { + return Of(res, nil) +} + +func OfFailure[T any](err error) Result[T] { + return &result[T]{ + err: err, + } +} + +func Map[I any, O any](original Result[I], mapper func(res I) Result[O]) Result[O] { + switch res, err := original.Get(); original.IsSuccess() { + case true: + return mapper(res) + default: + return OfFailure[O](err) + } +} diff --git a/pkg/result/result_test.go b/pkg/result/result_test.go new file mode 100644 index 00000000..7121441d --- /dev/null +++ b/pkg/result/result_test.go @@ -0,0 +1,434 @@ +package result + +import ( + "errors" + "strconv" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestOf(t *testing.T) { + assertions := require.New(t) + + rslt := Of("hello", errors.New("world")) + actualVal, actualErr := rslt.Get() + + assertions.Equal("hello", actualVal) + assertions.Equal(errors.New("world"), actualErr) +} + +func TestOfInline(t *testing.T) { + assertions := require.New(t) + + rslt := OfInline( + func() (res string, err error) { + return "hello", errors.New("world") + }, + ) + actualVal, actualErr := rslt.Get() + + assertions.Equal("hello", actualVal) + assertions.Equal(errors.New("world"), actualErr) +} + +func TestOfInlineCatchingPanic(t *testing.T) { + expectedErr := errors.New("err") + + for _, test := range []struct { + name string + do func() (res string, err error) + expectedVal string + expectedErr error + }{ + { + name: "no_panic", + do: func() (res string, err error) { + return "val", expectedErr + }, + expectedVal: "val", + expectedErr: expectedErr, + }, + { + name: "with_panic", + do: func() (res string, err error) { + panic(expectedErr) + }, + expectedVal: "", + expectedErr: expectedErr, + }, + } { + t.Run( + test.name, func(t *testing.T) { + assertions := require.New(t) + + rslt := OfInlineCatchingPanic(test.do) + actualVal, actualErr := rslt.Get() + + assertions.Equal(test.expectedVal, actualVal) + assertions.ErrorIs(actualErr, test.expectedErr) + }, + ) + } +} + +func TestOfSuccess(t *testing.T) { + assertions := require.New(t) + + rslt := OfSuccess("hello") + actualVal, actualErr := rslt.Get() + + assertions.Equal("hello", actualVal) + assertions.Nil(actualErr) +} + +func TestOfFailure(t *testing.T) { + assertions := require.New(t) + + rslt := OfFailure[string](errors.New("world")) + actualVal, actualErr := rslt.Get() + + assertions.Empty(actualVal) + assertions.Equal(errors.New("world"), actualErr) +} + +func TestResult_IsSuccess_IsFailure(t *testing.T) { + type testCase struct { + name string + input string + err error + want bool + } + + testCases := []testCase{ + { + name: "empty_val_nil_error", + input: "", + err: nil, + want: true, + }, + { + name: "empty_val_non_nil_error", + input: "", + err: errors.New("asdf"), + want: false, + }, + { + name: "non_empty_val_nil_error", + input: "jljsd;lfjads", + err: nil, + want: true, + }, + { + name: "non_empty_val_non_nil_error", + input: "kjasldjldsaf", + err: errors.New(";ljkasldjf;lkdsf"), + want: false, + }, + } + + for _, tc := range testCases { + t.Run( + tc.name, func(t *testing.T) { + assertions := require.New(t) + + rslt := Of(tc.input, tc.err) + assertions.Equal(tc.want, rslt.IsSuccess()) + assertions.Equal(tc.want, !rslt.IsFailure()) + }, + ) + } +} + +func TestResult_ToOptional(t *testing.T) { + for _, test := range []struct { + name string + inputVal string + inputErr error + expectEmpty bool + }{ + { + name: "empty_val_nil_error", + inputVal: "", + inputErr: nil, + expectEmpty: true, + }, + { + name: "empty_val_non_nil_error", + inputVal: "", + inputErr: errors.New(""), + expectEmpty: true, + }, + { + name: "non_empty_val_nil_error", + inputVal: "abcd", + inputErr: nil, + expectEmpty: false, + }, + { + name: "non_empty_val_non_nil_error", + inputVal: "abcd", + inputErr: errors.New(""), + expectEmpty: true, + }, + } { + t.Run( + test.name, func(t *testing.T) { + assertions := require.New(t) + + rslt := Of(test.inputVal, test.inputErr) + assertions.Equal(test.expectEmpty, rslt.ToOptional().IsNotPresent()) + }, + ) + } +} + +func TestResult_OnSuccess(t *testing.T) { + for _, tc := range []struct { + name string + result Result[int] + expect int + }{ + { + name: "with_nil_error", + result: Of(5, nil), + expect: 5, + }, + { + name: "with_non_nil_error", + result: Of(5, errors.New("asdfds")), + expect: 0, + }, + } { + t.Run( + tc.name, func(t *testing.T) { + assertions := require.New(t) + + actual := 0 + + rsltCopy := tc.result.OnSuccess( + func(i int) { + actual = i + }, + ) + + assertions.Same(tc.result, rsltCopy) + assertions.Equal(tc.expect, actual) + }, + ) + } +} + +func TestResult_OnFailure(t *testing.T) { + for _, tc := range []struct { + name string + input Result[int] + expected int + }{ + { + name: "with_nil_error", + input: Of(5, nil), + expected: 0, + }, + { + name: "with_non_nil_error", + input: Of(5, errors.New("7")), + expected: 7, + }, + } { + t.Run( + tc.name, func(t *testing.T) { + assertions := require.New(t) + + actual := 0 + + rsltCopy := tc.input.OnFailure( + func(err error) { + i, _ := strconv.Atoi(err.Error()) + actual = i + }, + ) + assertions.Same(tc.input, rsltCopy) + assertions.Equal(tc.expected, actual) + }, + ) + } +} + +func TestResult_Recover(t *testing.T) { + for _, tc := range []struct { + name string + inputValue string + inputError error + expectedValue string + expectedError error + recoverFunc func(error) (string, error) + expectedSame bool + }{ + { + name: "with_nil_error", + inputValue: "hello", + inputError: nil, + expectedValue: "hello", + expectedError: nil, + recoverFunc: func(err error) (string, error) { + return err.Error(), nil + }, + expectedSame: true, + }, + { + name: "with_non_nil_error", + inputValue: "hello", + inputError: errors.New("world"), + expectedValue: "world", + expectedError: nil, + recoverFunc: func(err error) (string, error) { + return err.Error(), nil + }, + expectedSame: false, + }, + } { + t.Run( + tc.name, func(t *testing.T) { + assertions := require.New(t) + + rslt := Of(tc.inputValue, tc.inputError) + rsltCopy := rslt.Recover(tc.recoverFunc) + + if tc.expectedSame { + assertions.Same(rslt, rsltCopy) + } else { + assertions.NotSame(rslt, rsltCopy) + } + + actualVal, actualErr := rsltCopy.Get() + assertions.Equal(tc.expectedValue, actualVal) + assertions.Equal(tc.expectedError, actualErr) + }, + ) + } +} + +func TestResult_RecoverCatching(t *testing.T) { + for _, tc := range []struct { + name string + inputValue string + inputError error + expectedValue string + expectedError error + getRsltCopy func(rslt Result[string]) Result[string] + expectedSame bool + }{ + { + name: "with_nil_error", + inputValue: "hello", + inputError: nil, + expectedValue: "hello", + expectedError: nil, + getRsltCopy: func(rslt Result[string]) Result[string] { + return rslt.RecoverCatching( + func(err error) (string, error) { + return err.Error(), nil + }, + ) + }, + expectedSame: true, + }, + { + name: "with_non_nil_error", + inputValue: "hello", + inputError: errors.New("world"), + expectedValue: "world", + expectedError: nil, + getRsltCopy: func(rslt Result[string]) Result[string] { + return rslt.RecoverCatching( + func(err error) (string, error) { + return err.Error(), nil + }, + ) + }, + expectedSame: false, + }, + { + name: "with_non_nil_error_then_throw", + inputValue: "hello", + inputError: errors.New("world"), + expectedValue: "world_goodbye", + expectedError: nil, + getRsltCopy: func(rslt Result[string]) Result[string] { + return rslt. + RecoverCatching( + func(err error) (string, error) { + panic(errors.New(err.Error() + "_goodbye")) + }, + ). + Recover( + func(err error) (string, error) { + return errors.Unwrap(errors.Unwrap(errors.Unwrap(err))).Error(), nil + }, + ) + }, + expectedSame: false, + }, + } { + t.Run( + tc.name, func(t *testing.T) { + assertions := require.New(t) + + rslt := Of(tc.inputValue, tc.inputError) + rsltCopy := tc.getRsltCopy(rslt) + + if tc.expectedSame { + assertions.Same(rslt, rsltCopy) + } else { + assertions.NotSame(rslt, rsltCopy) + } + + actualVal, actualErr := rsltCopy.Get() + assertions.Equal(tc.expectedValue, actualVal) + assertions.Equal(tc.expectedError, actualErr) + }, + ) + } +} + +func TestMap(t *testing.T) { + for _, tc := range []struct { + name string + input string + inputErr error + expectedVal int + expectedErr error + }{ + { + name: "with_nil_error", + input: "9", + inputErr: nil, + expectedVal: 9, + expectedErr: nil, + }, + { + name: "with_non_nil_error", + input: "9", + inputErr: errors.New("qwerty"), + expectedVal: 0, + expectedErr: errors.New("qwerty"), + }, + } { + t.Run( + tc.name, func(t *testing.T) { + assertions := require.New(t) + + rslt := Of(tc.input, tc.inputErr) + mappedResult := Map[string, int]( + rslt, func(str string) Result[int] { + return Of(strconv.Atoi(str)) + }, + ) + actualVal, actualErr := mappedResult.Get() + assertions.Equal(tc.expectedVal, actualVal) + assertions.Equal(tc.expectedErr, actualErr) + }, + ) + } +}