From 7004afa85e57cf4b264c1c3081310100d18dd797 Mon Sep 17 00:00:00 2001 From: Oscar Rodriguez Date: Thu, 25 Apr 2024 08:41:49 -0600 Subject: [PATCH] slimming down the operator --- Makefile | 26 +- details/operator-sdk/PROJECT => PROJECT | 2 +- api/v1/astraconnector_types.go | 78 + .../v1/astraconnector_validator.go | 0 .../v1/astraconnector_validator_test.go | 3 +- .../api => api}/v1/astraconnector_webhook.go | 0 api/v1/astraneptune_types.go | 75 + .../api => api}/v1/groupversion_info.go | 0 .../api => api}/v1/zz_generated.deepcopy.go | 131 +- app/deployer/connector/astra_connect.go | 664 +++--- .../connector/astra_connect_natless.go | 116 +- app/deployer/connector/astra_connect_test.go | 2 +- app/deployer/connector/nats.go | 342 --- app/deployer/connector/nats_test.go | 183 -- app/deployer/connector/natssync_client.go | 269 --- .../connector/natssync_client_test.go | 198 -- app/deployer/factory.go | 4 - app/deployer/model/deployer.go | 24 +- app/deployer/neptune/neptuneV2.go | 91 +- app/deployer/neptune/neptune_test.go | 2 +- app/register/register.go | 1142 ---------- app/register/register_test.go | 1910 ----------------- asup.yaml | 13 + common/neptune-version.go | 3 + common/neptune-version.txt | 1 + common/neptune_manager_tag.txt | 2 +- .../certmanager/certificate.yaml | 0 .../certmanager/kustomization.yaml | 0 .../certmanager/kustomizeconfig.yaml | 0 .../astra.netapp.io_astraconnectors.yaml | 102 + .../bases/astra.netapp.io_astraneptunes.yaml | 105 + .../config => config}/crd/kustomization.yaml | 1 + .../crd/kustomizeconfig.yaml | 0 .../cainjection_in_astrainstallers.yaml | 0 .../patches/webhook_in_astrainstallers.yaml | 0 .../default/kustomization.yaml | 0 .../default/manager_auth_proxy_patch.yaml | 0 .../default/manager_config_patch.yaml | 0 .../default/manager_webhook_patch.yaml | 0 .../default/webhookcainjection_patch.yaml | 0 .../manager/controller_manager_config.yaml | 0 .../manager/kustomization.yaml | 0 .../config => config}/manager/manager.yaml | 0 ...astra-installer.clusterserviceversion.yaml | 0 .../manifests/kustomization.yaml | 0 .../prometheus/kustomization.yaml | 0 .../config => config}/prometheus/monitor.yaml | 0 .../rbac/astrainstaller_editor_role.yaml | 0 .../rbac/astrainstaller_viewer_role.yaml | 0 .../rbac/auth_proxy_client_clusterrole.yaml | 0 .../rbac/auth_proxy_role.yaml | 0 .../rbac/auth_proxy_role_binding.yaml | 0 .../rbac/auth_proxy_service.yaml | 0 .../config => config}/rbac/kustomization.yaml | 0 .../rbac/leader_election_role.yaml | 0 .../rbac/leader_election_role_binding.yaml | 0 .../config => config}/rbac/role.yaml | 26 + .../config => config}/rbac/role_binding.yaml | 0 .../rbac/service_account.yaml | 0 config/samples/astra_v1_astraconnector.yaml | 32 + .../samples/kustomization.yaml | 0 .../config => config}/samples/neptune.yaml | 0 config/samples/temp.yaml | 21 + .../scorecard/bases/config.yaml | 0 .../scorecard/kustomization.yaml | 0 .../scorecard/patches/basic.config.yaml | 0 .../scorecard/patches/olm.config.yaml | 0 .../webhook/kustomization.yaml | 0 .../webhook/kustomizeconfig.yaml | 0 .../config => config}/webhook/manifests.yaml | 0 .../config => config}/webhook/service.yaml | 0 .../controllers => controllers}/.DS_Store | Bin controllers/astraconnector_controller.go | 235 ++ .../astraconnector_controller_delete_test.go | 0 .../astraconnector_controller_test.go | 3 +- controllers/astraneptune_controller.go | 229 ++ .../controllers => controllers}/deployer.go | 82 +- controllers/natless_connector.go | 46 + controllers/neptune.go | 56 + .../status_strings.go | 2 +- .../controllers => controllers}/suite_test.go | 3 +- .../yaml/neptune_crds.yaml | 0 details/k8s/cluster_type_checker.go | 217 -- details/k8s/cluster_type_checker_test.go | 106 - .../api/v1/astraconnector_types.go | 143 -- .../astra.netapp.io_astraconnectors.yaml | 273 --- .../samples/astra_v1_astraconnector.yaml | 14 - .../controllers/astraconnector_controller.go | 590 ----- details/operator-sdk/controllers/connector.go | 203 -- details/operator-sdk/controllers/constants.go | 129 -- .../controllers/natless_connector.go | 53 - details/operator-sdk/controllers/neptune.go | 31 - .../hack => hack}/boilerplate.go.txt | 0 {details/k8s => k8s}/k8s_util.go | 18 +- {details/k8s => k8s}/k8s_util_test.go | 2 +- {details/k8s => k8s}/precheck/k8s_crd.go | 0 {details/k8s => k8s}/precheck/k8s_version.go | 0 .../k8s => k8s}/precheck/k8s_version_test.go | 3 +- {details/k8s => k8s}/precheck/precheck.go | 3 +- main.go | 33 +- mocks/ClusterRegisterUtil.go | 538 ----- mocks/Deployer.go | 2 +- mocks/getK8sResources.go | 2 +- scripts/.DS_Store | Bin 0 -> 6148 bytes scripts/Astra_Connector_ACP_CR.yaml | 24 + scripts/create-image-tar.sh | 4 +- test/test-util/test_util.go | 7 +- util/go_util_test.go | 2 +- 108 files changed, 1673 insertions(+), 6948 deletions(-) rename details/operator-sdk/PROJECT => PROJECT (88%) create mode 100644 api/v1/astraconnector_types.go rename {details/operator-sdk/api => api}/v1/astraconnector_validator.go (100%) rename {details/operator-sdk/api => api}/v1/astraconnector_validator_test.go (93%) rename {details/operator-sdk/api => api}/v1/astraconnector_webhook.go (100%) create mode 100644 api/v1/astraneptune_types.go rename {details/operator-sdk/api => api}/v1/groupversion_info.go (100%) rename {details/operator-sdk/api => api}/v1/zz_generated.deepcopy.go (73%) delete mode 100644 app/deployer/connector/nats.go delete mode 100644 app/deployer/connector/nats_test.go delete mode 100644 app/deployer/connector/natssync_client.go delete mode 100644 app/deployer/connector/natssync_client_test.go delete mode 100644 app/register/register.go delete mode 100644 app/register/register_test.go create mode 100644 asup.yaml create mode 100644 common/neptune-version.go create mode 100644 common/neptune-version.txt rename {details/operator-sdk/config => config}/certmanager/certificate.yaml (100%) rename {details/operator-sdk/config => config}/certmanager/kustomization.yaml (100%) rename {details/operator-sdk/config => config}/certmanager/kustomizeconfig.yaml (100%) create mode 100644 config/crd/bases/astra.netapp.io_astraconnectors.yaml create mode 100644 config/crd/bases/astra.netapp.io_astraneptunes.yaml rename {details/operator-sdk/config => config}/crd/kustomization.yaml (95%) rename {details/operator-sdk/config => config}/crd/kustomizeconfig.yaml (100%) rename {details/operator-sdk/config => config}/crd/patches/cainjection_in_astrainstallers.yaml (100%) rename {details/operator-sdk/config => config}/crd/patches/webhook_in_astrainstallers.yaml (100%) rename {details/operator-sdk/config => config}/default/kustomization.yaml (100%) rename {details/operator-sdk/config => config}/default/manager_auth_proxy_patch.yaml (100%) rename {details/operator-sdk/config => config}/default/manager_config_patch.yaml (100%) rename {details/operator-sdk/config => config}/default/manager_webhook_patch.yaml (100%) rename {details/operator-sdk/config => config}/default/webhookcainjection_patch.yaml (100%) rename {details/operator-sdk/config => config}/manager/controller_manager_config.yaml (100%) rename {details/operator-sdk/config => config}/manager/kustomization.yaml (100%) rename {details/operator-sdk/config => config}/manager/manager.yaml (100%) rename {details/operator-sdk/config => config}/manifests/bases/astra-installer.clusterserviceversion.yaml (100%) rename {details/operator-sdk/config => config}/manifests/kustomization.yaml (100%) rename {details/operator-sdk/config => config}/prometheus/kustomization.yaml (100%) rename {details/operator-sdk/config => config}/prometheus/monitor.yaml (100%) rename {details/operator-sdk/config => config}/rbac/astrainstaller_editor_role.yaml (100%) rename {details/operator-sdk/config => config}/rbac/astrainstaller_viewer_role.yaml (100%) rename {details/operator-sdk/config => config}/rbac/auth_proxy_client_clusterrole.yaml (100%) rename {details/operator-sdk/config => config}/rbac/auth_proxy_role.yaml (100%) rename {details/operator-sdk/config => config}/rbac/auth_proxy_role_binding.yaml (100%) rename {details/operator-sdk/config => config}/rbac/auth_proxy_service.yaml (100%) rename {details/operator-sdk/config => config}/rbac/kustomization.yaml (100%) rename {details/operator-sdk/config => config}/rbac/leader_election_role.yaml (100%) rename {details/operator-sdk/config => config}/rbac/leader_election_role_binding.yaml (100%) rename {details/operator-sdk/config => config}/rbac/role.yaml (82%) rename {details/operator-sdk/config => config}/rbac/role_binding.yaml (100%) rename {details/operator-sdk/config => config}/rbac/service_account.yaml (100%) create mode 100644 config/samples/astra_v1_astraconnector.yaml rename {details/operator-sdk/config => config}/samples/kustomization.yaml (100%) rename {details/operator-sdk/config => config}/samples/neptune.yaml (100%) create mode 100644 config/samples/temp.yaml rename {details/operator-sdk/config => config}/scorecard/bases/config.yaml (100%) rename {details/operator-sdk/config => config}/scorecard/kustomization.yaml (100%) rename {details/operator-sdk/config => config}/scorecard/patches/basic.config.yaml (100%) rename {details/operator-sdk/config => config}/scorecard/patches/olm.config.yaml (100%) rename {details/operator-sdk/config => config}/webhook/kustomization.yaml (100%) rename {details/operator-sdk/config => config}/webhook/kustomizeconfig.yaml (100%) rename {details/operator-sdk/config => config}/webhook/manifests.yaml (100%) rename {details/operator-sdk/config => config}/webhook/service.yaml (100%) rename {details/operator-sdk/controllers => controllers}/.DS_Store (100%) create mode 100644 controllers/astraconnector_controller.go rename {details/operator-sdk/controllers => controllers}/astraconnector_controller_delete_test.go (100%) rename {details/operator-sdk/controllers => controllers}/astraconnector_controller_test.go (98%) create mode 100644 controllers/astraneptune_controller.go rename {details/operator-sdk/controllers => controllers}/deployer.go (60%) create mode 100644 controllers/natless_connector.go create mode 100644 controllers/neptune.go rename {details/operator-sdk/controllers => controllers}/status_strings.go (97%) rename {details/operator-sdk/controllers => controllers}/suite_test.go (95%) rename {details/operator-sdk/controllers => controllers}/yaml/neptune_crds.yaml (100%) delete mode 100644 details/k8s/cluster_type_checker.go delete mode 100644 details/k8s/cluster_type_checker_test.go delete mode 100644 details/operator-sdk/api/v1/astraconnector_types.go delete mode 100644 details/operator-sdk/config/crd/bases/astra.netapp.io_astraconnectors.yaml delete mode 100644 details/operator-sdk/config/samples/astra_v1_astraconnector.yaml delete mode 100644 details/operator-sdk/controllers/astraconnector_controller.go delete mode 100644 details/operator-sdk/controllers/connector.go delete mode 100644 details/operator-sdk/controllers/constants.go delete mode 100644 details/operator-sdk/controllers/natless_connector.go delete mode 100644 details/operator-sdk/controllers/neptune.go rename {details/operator-sdk/hack => hack}/boilerplate.go.txt (100%) rename {details/k8s => k8s}/k8s_util.go (82%) rename {details/k8s => k8s}/k8s_util_test.go (98%) rename {details/k8s => k8s}/precheck/k8s_crd.go (100%) rename {details/k8s => k8s}/precheck/k8s_version.go (100%) rename {details/k8s => k8s}/precheck/k8s_version_test.go (94%) rename {details/k8s => k8s}/precheck/precheck.go (90%) delete mode 100644 mocks/ClusterRegisterUtil.go create mode 100644 scripts/.DS_Store create mode 100644 scripts/Astra_Connector_ACP_CR.yaml diff --git a/Makefile b/Makefile index 0453fb47..c096d02a 100644 --- a/Makefile +++ b/Makefile @@ -84,10 +84,10 @@ help: ## Display this help. ##@ Development manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. - cd details/operator-sdk && $(CONTROLLER_GEN) rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases + $(CONTROLLER_GEN) rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. - cd details/operator-sdk && $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." + $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." fmt: ## Run go fmt against code. go fmt ./... @@ -119,17 +119,17 @@ docker-buildx: test ## Build and push docker image for the manager for cross-pla ##@ Deployment install: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config. - cd details/operator-sdk && $(KUSTOMIZE) build config/crd | kubectl apply -f - + $(KUSTOMIZE) build config/crd | kubectl apply -f - uninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config. - cd details/operator-sdk && $(KUSTOMIZE) build config/crd | kubectl delete -f - + $(KUSTOMIZE) build config/crd | kubectl delete -f - deploy: manifests kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config. - cd details/operator-sdk/config/manager && $(KUSTOMIZE) edit set image controller=${IMG} - cd details/operator-sdk && $(KUSTOMIZE) build config/default | kubectl apply -f - + cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG} + $(KUSTOMIZE) build config/default | kubectl apply -f - undeploy: ## Undeploy controller from the K8s cluster specified in ~/.kube/config. - cd details/operator-sdk && $(KUSTOMIZE) build config/default | kubectl delete -f - + $(KUSTOMIZE) build config/default | kubectl delete -f - CONTROLLER_GEN = $(shell pwd)/bin/controller-gen @@ -170,8 +170,8 @@ endef .PHONY: bundle bundle: manifests kustomize ## Generate bundle manifests and metadata, then validate generated files. operator-sdk generate kustomize manifests -q - cd details/operator-sdk/config/manager && $(KUSTOMIZE) edit set image controller=$(IMG) - cd details/operator-sdk && $(KUSTOMIZE) build config/manifests | operator-sdk generate bundle -q --overwrite --version $(VERSION) $(BUNDLE_METADATA_OPTS) + cd config/manager && $(KUSTOMIZE) edit set image controller=$(IMG) + $(KUSTOMIZE) build config/manifests | operator-sdk generate bundle -q --overwrite --version $(VERSION) $(BUNDLE_METADATA_OPTS) operator-sdk bundle validate ./bundle .PHONY: opm @@ -225,12 +225,12 @@ image-tar: release: kustomize rm -rf build mkdir build - cd details/operator-sdk/config/manager && $(KUSTOMIZE) edit set image controller=$(IMG) - cd details/operator-sdk && $(KUSTOMIZE) build config/default > $(BUILD_DIR)/only_astraconnector_operator.yaml - cat $(MAKEFILE_DIR)/details/operator-sdk/config/samples/neptune.yaml > $(BUILD_DIR)/astraconnector_operator.yaml + cd config/manager && $(KUSTOMIZE) edit set image controller=$(IMG) + $(KUSTOMIZE) build config/default > $(BUILD_DIR)/only_astraconnector_operator.yaml + cat $(MAKEFILE_DIR)/config/samples/neptune.yaml > $(BUILD_DIR)/astraconnector_operator.yaml echo "---" >> $(BUILD_DIR)/astraconnector_operator.yaml cat $(BUILD_DIR)/only_astraconnector_operator.yaml >> $(BUILD_DIR)/astraconnector_operator.yaml - cp $(MAKEFILE_DIR)/details/operator-sdk/config/samples/astra_v1_astraconnector.yaml $(BUILD_DIR)/astra_v1_astraconnector.yaml + cp $(MAKEFILE_DIR)/config/samples/astra_v1_astraconnector.yaml $(BUILD_DIR)/astra_v1_astraconnector.yaml .PHONY: generate-mocks generate-mocks: install-mockery diff --git a/details/operator-sdk/PROJECT b/PROJECT similarity index 88% rename from details/operator-sdk/PROJECT rename to PROJECT index 7ba9e467..10795713 100644 --- a/details/operator-sdk/PROJECT +++ b/PROJECT @@ -18,7 +18,7 @@ resources: domain: netapp.io group: astra kind: AstraConnector - path: github.com/NetApp-Polaris/astra-connector-operator/details/operator-sdk/api/v1 + path: github.com/NetApp-Polaris/astra-connector-operator/api/v1 version: v1 webhooks: defaulting: true diff --git a/api/v1/astraconnector_types.go b/api/v1/astraconnector_types.go new file mode 100644 index 00000000..d18c1239 --- /dev/null +++ b/api/v1/astraconnector_types.go @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2024. NetApp, Inc. All Rights Reserved. + */ + +package v1 + +import ( + metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// AstraConnectorSpec defines the desired state of AstraConnector +type AstraConnectorSpec struct { + AccountId string `json:"accountId"` + ApiTokenSecretRef string `json:"apiTokenSecretRef,omitempty"` + AstraControlUrl string `json:"astraControlUrl,omitempty"` + // +kubebuilder:validation:Optional + CloudId string `json:"cloudId"` + // +kubebuilder:validation:Optional + ClusterId string `json:"clusterId"` + // +kubebuilder:validation:Optional + SkipTLSValidation bool `json:"skipTLSValidation,omitempty"` + + Image string `json:"image,omitempty"` + // +kubebuilder:validation:Optional + HostAliasIP string `json:"hostAliasIP,omitempty"` + // +kubebuilder:default:=1 + Replicas int32 `json:"replicas,omitempty"` + + ImageRegistry ImageRegistry `json:"imageRegistry,omitempty"` + + // SkipPreCheck determines if you want to skip pre-checks and go ahead with the installation. + // +kubebuilder:default:=false + SkipPreCheck bool `json:"skipPreCheck"` + + // Labels any additional labels wanted to be added to resources + Labels map[string]string `json:"labels"` +} + +// AstraConnectorStatus defines the observed state of AstraConnector +type AstraConnectorStatus struct { + Version string `json:"version"` + Status string `json:"status"` +} + +// +kubebuilder:validation:Optional + +type ImageRegistry struct { + Name string `json:"name,omitempty"` + Secret string `json:"secret,omitempty"` +} + +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status +//+kubebuilder:printcolumn:name="AstraConnectorVersion",type=string,JSONPath=`.status.version` +//+kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.status` + +// AstraConnector is the Schema for the astraconnectors API +// +kubebuilder:subresource:status +type AstraConnector struct { + metaV1.TypeMeta `json:",inline"` + metaV1.ObjectMeta `json:"metadata,omitempty"` + + Spec AstraConnectorSpec `json:"spec,omitempty"` + Status AstraConnectorStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// AstraConnectorList contains a list of AstraConnector +type AstraConnectorList struct { + metaV1.TypeMeta `json:",inline"` + metaV1.ListMeta `json:"metadata,omitempty"` + Items []AstraConnector `json:"items"` +} + +func init() { + SchemeBuilder.Register(&AstraConnector{}, &AstraConnectorList{}) +} diff --git a/details/operator-sdk/api/v1/astraconnector_validator.go b/api/v1/astraconnector_validator.go similarity index 100% rename from details/operator-sdk/api/v1/astraconnector_validator.go rename to api/v1/astraconnector_validator.go diff --git a/details/operator-sdk/api/v1/astraconnector_validator_test.go b/api/v1/astraconnector_validator_test.go similarity index 93% rename from details/operator-sdk/api/v1/astraconnector_validator_test.go rename to api/v1/astraconnector_validator_test.go index 66c17a7b..3b70e567 100644 --- a/details/operator-sdk/api/v1/astraconnector_validator_test.go +++ b/api/v1/astraconnector_validator_test.go @@ -5,11 +5,10 @@ package v1_test import ( + "github.com/NetApp-Polaris/astra-connector-operator/api/v1" "testing" "github.com/stretchr/testify/assert" - - v1 "github.com/NetApp-Polaris/astra-connector-operator/details/operator-sdk/api/v1" ) func TestAstraConnector_ValidateCreateAstraConnector(t *testing.T) { diff --git a/details/operator-sdk/api/v1/astraconnector_webhook.go b/api/v1/astraconnector_webhook.go similarity index 100% rename from details/operator-sdk/api/v1/astraconnector_webhook.go rename to api/v1/astraconnector_webhook.go diff --git a/api/v1/astraneptune_types.go b/api/v1/astraneptune_types.go new file mode 100644 index 00000000..e922fcc1 --- /dev/null +++ b/api/v1/astraneptune_types.go @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2024. NetApp, Inc. All Rights Reserved. + */ + +package v1 + +import ( + metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// AutoSupport defines how the customer interacts with NetApp ActiveIQ. +type AutoSupport struct { + // Enrolled determines if you want to send anonymous data to NetApp for support purposes. + // +kubebuilder:default:=true + Enrolled bool `json:"enrolled"` + + // URL determines where the anonymous data will be sent + // +kubebuilder:default:="https://stagesupport.netapp.com/put/AsupPut" + URL string `json:"url,omitempty"` +} + +// AstraNeptuneSpec defines the desired state of AstraNeptune +type AstraNeptuneSpec struct { + ImageRegistry ImageRegistry `json:"imageRegistry,omitempty"` + Image string `json:"image,omitempty"` + + // AutoSupport indicates willingness to participate in NetApp's proactive support application, NetApp Active IQ. + // An internet connection is required (port 442) and all support data is anonymized. + // The default election is true and indicates support data will be sent to NetApp. + // An empty or blank election is the same as a default election. + // Air gapped installations should enter false. + // +kubebuilder:default={"enrolled":true, "url":"https://stagesupport.netapp.com/put/AsupPut"} + AutoSupport AutoSupport `json:"autoSupport"` + + // SkipPreCheck determines if you want to skip pre-checks and go ahead with the installation. + // +kubebuilder:default:=false + SkipPreCheck bool `json:"skipPreCheck"` + + // Labels any additional labels wanted to be added to resources + Labels map[string]string `json:"labels"` +} + +// AstraNeptuneStatus defines the observed state of AstraNeptune +type AstraNeptuneStatus struct { + Version string `json:"version"` + Status string `json:"status"` +} + +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status +//+kubebuilder:printcolumn:name="AstraNeptuneVersion",type=string,JSONPath=`.status.version` +//+kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.status` + +// AstraNeptune is the Schema for the astraneptunes API +// +kubebuilder:subresource:status +type AstraNeptune struct { + metaV1.TypeMeta `json:",inline"` + metaV1.ObjectMeta `json:"metadata,omitempty"` + + Spec AstraNeptuneSpec `json:"spec,omitempty"` + Status AstraNeptuneStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// AstraNeptuneList contains a list of AstraNeptune +type AstraNeptuneList struct { + metaV1.TypeMeta `json:",inline"` + metaV1.ListMeta `json:"metadata,omitempty"` + Items []AstraConnector `json:"items"` +} + +func init() { + SchemeBuilder.Register(&AstraNeptune{}, &AstraNeptuneList{}) +} diff --git a/details/operator-sdk/api/v1/groupversion_info.go b/api/v1/groupversion_info.go similarity index 100% rename from details/operator-sdk/api/v1/groupversion_info.go rename to api/v1/groupversion_info.go diff --git a/details/operator-sdk/api/v1/zz_generated.deepcopy.go b/api/v1/zz_generated.deepcopy.go similarity index 73% rename from details/operator-sdk/api/v1/zz_generated.deepcopy.go rename to api/v1/zz_generated.deepcopy.go index eb5efbd2..d463bfd8 100644 --- a/details/operator-sdk/api/v1/zz_generated.deepcopy.go +++ b/api/v1/zz_generated.deepcopy.go @@ -13,43 +13,13 @@ import ( "k8s.io/apimachinery/pkg/runtime" ) -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Astra) DeepCopyInto(out *Astra) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Astra. -func (in *Astra) DeepCopy() *Astra { - if in == nil { - return nil - } - out := new(Astra) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *AstraConnect) DeepCopyInto(out *AstraConnect) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AstraConnect. -func (in *AstraConnect) DeepCopy() *AstraConnect { - if in == nil { - return nil - } - out := new(AstraConnect) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *AstraConnector) DeepCopyInto(out *AstraConnector) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) in.Spec.DeepCopyInto(&out.Spec) - in.Status.DeepCopyInto(&out.Status) + out.Status = in.Status } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AstraConnector. @@ -105,13 +75,7 @@ func (in *AstraConnectorList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *AstraConnectorSpec) DeepCopyInto(out *AstraConnectorSpec) { *out = *in - out.Astra = in.Astra - out.NatsSyncClient = in.NatsSyncClient - out.Nats = in.Nats - out.AstraConnect = in.AstraConnect - out.Neptune = in.Neptune out.ImageRegistry = in.ImageRegistry - out.AutoSupport = in.AutoSupport if in.Labels != nil { in, out := &in.Labels, &out.Labels *out = make(map[string]string, len(*in)) @@ -134,13 +98,6 @@ func (in *AstraConnectorSpec) DeepCopy() *AstraConnectorSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *AstraConnectorStatus) DeepCopyInto(out *AstraConnectorStatus) { *out = *in - if in.Nodes != nil { - in, out := &in.Nodes, &out.Nodes - *out = make([]string, len(*in)) - copy(*out, *in) - } - out.NatsSyncClient = in.NatsSyncClient - in.ObservedSpec.DeepCopyInto(&out.ObservedSpec) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AstraConnectorStatus. @@ -154,91 +111,129 @@ func (in *AstraConnectorStatus) DeepCopy() *AstraConnectorStatus { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *AutoSupport) DeepCopyInto(out *AutoSupport) { +func (in *AstraNeptune) DeepCopyInto(out *AstraNeptune) { *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + out.Status = in.Status } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AutoSupport. -func (in *AutoSupport) DeepCopy() *AutoSupport { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AstraNeptune. +func (in *AstraNeptune) DeepCopy() *AstraNeptune { if in == nil { return nil } - out := new(AutoSupport) + out := new(AstraNeptune) in.DeepCopyInto(out) return out } +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *AstraNeptune) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ImageRegistry) DeepCopyInto(out *ImageRegistry) { +func (in *AstraNeptuneList) DeepCopyInto(out *AstraNeptuneList) { *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]AstraConnector, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ImageRegistry. -func (in *ImageRegistry) DeepCopy() *ImageRegistry { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AstraNeptuneList. +func (in *AstraNeptuneList) DeepCopy() *AstraNeptuneList { if in == nil { return nil } - out := new(ImageRegistry) + out := new(AstraNeptuneList) in.DeepCopyInto(out) return out } +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *AstraNeptuneList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Nats) DeepCopyInto(out *Nats) { +func (in *AstraNeptuneSpec) DeepCopyInto(out *AstraNeptuneSpec) { *out = *in + out.ImageRegistry = in.ImageRegistry + out.AutoSupport = in.AutoSupport + if in.Labels != nil { + in, out := &in.Labels, &out.Labels + *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 Nats. -func (in *Nats) DeepCopy() *Nats { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AstraNeptuneSpec. +func (in *AstraNeptuneSpec) DeepCopy() *AstraNeptuneSpec { if in == nil { return nil } - out := new(Nats) + out := new(AstraNeptuneSpec) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *NatsSyncClient) DeepCopyInto(out *NatsSyncClient) { +func (in *AstraNeptuneStatus) DeepCopyInto(out *AstraNeptuneStatus) { *out = *in } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NatsSyncClient. -func (in *NatsSyncClient) DeepCopy() *NatsSyncClient { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AstraNeptuneStatus. +func (in *AstraNeptuneStatus) DeepCopy() *AstraNeptuneStatus { if in == nil { return nil } - out := new(NatsSyncClient) + out := new(AstraNeptuneStatus) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *NatsSyncClientStatus) DeepCopyInto(out *NatsSyncClientStatus) { +func (in *AutoSupport) DeepCopyInto(out *AutoSupport) { *out = *in } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NatsSyncClientStatus. -func (in *NatsSyncClientStatus) DeepCopy() *NatsSyncClientStatus { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AutoSupport. +func (in *AutoSupport) DeepCopy() *AutoSupport { if in == nil { return nil } - out := new(NatsSyncClientStatus) + out := new(AutoSupport) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Neptune) DeepCopyInto(out *Neptune) { +func (in *ImageRegistry) DeepCopyInto(out *ImageRegistry) { *out = *in } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Neptune. -func (in *Neptune) DeepCopy() *Neptune { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ImageRegistry. +func (in *ImageRegistry) DeepCopy() *ImageRegistry { if in == nil { return nil } - out := new(Neptune) + out := new(ImageRegistry) in.DeepCopyInto(out) return out } diff --git a/app/deployer/connector/astra_connect.go b/app/deployer/connector/astra_connect.go index 046771ee..eaf2c4d2 100644 --- a/app/deployer/connector/astra_connect.go +++ b/app/deployer/connector/astra_connect.go @@ -1,338 +1,336 @@ /* - * Copyright (c) 2023. NetApp, Inc. All Rights Reserved. +* Copyright (c) 2023. NetApp, Inc. All Rights Reserved. */ package connector -import ( - "context" - "fmt" - "strconv" - - "maps" - - "k8s.io/apimachinery/pkg/api/resource" - - appsv1 "k8s.io/api/apps/v1" - corev1 "k8s.io/api/core/v1" - rbacv1 "k8s.io/api/rbac/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - ctrllog "sigs.k8s.io/controller-runtime/pkg/log" - - "github.com/NetApp-Polaris/astra-connector-operator/app/conf" - "github.com/NetApp-Polaris/astra-connector-operator/app/deployer/model" - "github.com/NetApp-Polaris/astra-connector-operator/common" - v1 "github.com/NetApp-Polaris/astra-connector-operator/details/operator-sdk/api/v1" -) - -type AstraConnectDeployer struct{} - -func NewAstraConnectorDeployer() model.Deployer { - return &AstraConnectDeployer{} -} - -// GetDeploymentObjects returns a Astra Connect Deployment object -func (d *AstraConnectDeployer) GetDeploymentObjects(m *v1.AstraConnector, ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) { - log := ctrllog.FromContext(ctx) - ls := LabelsForAstraConnectClient(common.AstraConnectName, m.Spec.Labels) - - var imageRegistry string - var containerImage string - var connectorImage string - if m.Spec.ImageRegistry.Name != "" { - imageRegistry = m.Spec.ImageRegistry.Name - } else { - imageRegistry = common.DefaultImageRegistry - } - - if m.Spec.AstraConnect.Image != "" { - containerImage = m.Spec.AstraConnect.Image - } else { - containerImage = common.ConnectorImageTag - } - - connectorImage = fmt.Sprintf("%s/astra-connector:%s", imageRegistry, containerImage) - log.Info("Using AstraConnector image", "image", connectorImage) - - ref := &corev1.ConfigMapKeySelector{LocalObjectReference: corev1.LocalObjectReference{Name: common.AstraConnectName}, Key: "nats_url"} - - dep := &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: common.AstraConnectName, - Namespace: m.Namespace, - Annotations: map[string]string{ - "container.seccomp.security.alpha.kubernetes.io/pod": "runtime/default", - }, - }, - Spec: appsv1.DeploymentSpec{ - // TODO remove option to set replica count in CRD. This should always only-ever be 1 - Replicas: &m.Spec.AstraConnect.Replicas, - Selector: &metav1.LabelSelector{ - MatchLabels: ls, - }, - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Labels: ls, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{{ - Image: connectorImage, - Name: common.AstraConnectName, - Env: []corev1.EnvVar{ - { - Name: "NATS_SERVER_URL", - ValueFrom: &corev1.EnvVarSource{ConfigMapKeyRef: ref}, - }, - { - Name: "LOG_LEVEL", - Value: "trace", - }, - { - Name: "POD_NAME", - ValueFrom: &corev1.EnvVarSource{ - FieldRef: &corev1.ObjectFieldSelector{ - APIVersion: "v1", - FieldPath: "metadata.name", - }, - }, - }, - { - Name: "NAMESPACE", - ValueFrom: &corev1.EnvVarSource{ - FieldRef: &corev1.ObjectFieldSelector{ - APIVersion: "v1", - FieldPath: "metadata.namespace", - }, - }, - }, - }, - Resources: corev1.ResourceRequirements{ - Limits: corev1.ResourceList{ - corev1.ResourceCPU: resource.MustParse("0.1"), - }, - }, - SecurityContext: conf.GetSecurityContext(), - }}, - ServiceAccountName: common.AstraConnectName, - }, - }, - }, - } - - if m.Spec.ImageRegistry.Secret != "" { - dep.Spec.Template.Spec.ImagePullSecrets = []corev1.LocalObjectReference{ - { - Name: m.Spec.ImageRegistry.Secret, - }, - } - } - return []client.Object{dep}, model.NonMutateFn, nil -} - -// GetServiceObjects returns an Astra-Connect Service object -func (d *AstraConnectDeployer) GetServiceObjects(m *v1.AstraConnector, ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) { - return nil, model.NonMutateFn, nil -} - -// LabelsForAstraConnectClient returns the labels for selecting the AstraConnectClient -func LabelsForAstraConnectClient(name string, mLabels map[string]string) map[string]string { - labels := map[string]string{"type": name, "role": name} - maps.Copy(labels, mLabels) - return labels -} - -// GetConfigMapObjects returns a ConfigMap object for Astra Connect -func (d *AstraConnectDeployer) GetConfigMapObjects(m *v1.AstraConnector, ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) { - configMap := &corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: m.Namespace, - Name: common.AstraConnectName, - }, - Data: map[string]string{ - "nats_url": GetNatsURL(m), - "skip_tls_validation": strconv.FormatBool(m.Spec.Astra.SkipTLSValidation), - }, - } - return []client.Object{configMap}, model.NonMutateFn, nil -} - -// GetServiceAccountObjects returns a ServiceAccount object for Astra Connect -func (d *AstraConnectDeployer) GetServiceAccountObjects(m *v1.AstraConnector, ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) { - sa := &corev1.ServiceAccount{ - ObjectMeta: metav1.ObjectMeta{ - Name: common.AstraConnectName, - Namespace: m.Namespace, - }, - } - return []client.Object{sa}, model.NonMutateFn, nil -} - -func (d *AstraConnectDeployer) GetClusterRoleObjects(m *v1.AstraConnector, ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) { - clusterRole := &rbacv1.ClusterRole{ - ObjectMeta: metav1.ObjectMeta{ - Name: common.AstraConnectName, - }, - Rules: []rbacv1.PolicyRule{ - { - APIGroups: []string{""}, - Resources: []string{"namespaces", "persistentvolumes", "nodes", "pods", "services"}, - Verbs: []string{"watch", "list", "get"}, - }, - { - APIGroups: []string{"storage.k8s.io"}, - Resources: []string{"storageclasses"}, - Verbs: []string{"update", "watch", "list", "get"}, - }, - { - APIGroups: []string{"storage.k8s.io"}, - Resources: []string{"csidrivers"}, - Verbs: []string{"watch", "list", "get"}, - }, - { - APIGroups: []string{"snapshot.storage.k8s.io"}, - Resources: []string{"volumesnapshotclasses"}, - Verbs: []string{"watch", "list", "get"}, - }, - { - APIGroups: []string{"trident.netapp.io"}, - Resources: []string{"tridentversions", "tridentorchestrators"}, - Verbs: []string{"watch", "list", "get"}, - }, - { - APIGroups: []string{"astra.netapp.io"}, - Resources: []string{ - "applications", - "appmirrorrelationships", - "appmirrorupdates", - "appvaults", - "autosupportbundles", - "backups", - "backupinplacerestores", - "backuprestores", - "exechooks", - "exechooksruns", - "pvccopies", - "pvcerases", - "resourcebackups", - "resourcedeletes", - "resourcerestores", - "resourcesummaryuploads", - "resticvolumebackups", - "resticvolumerestores", - "schedules", - "shutdownsnapshots", - "snapshots", - "snapshotinplacerestores", - "snapshotrestores", - "astraconnectors", - }, - Verbs: []string{"watch", "list", "get"}, - }, - { - APIGroups: []string{"security.openshift.io"}, - Resources: []string{"securitycontextconstraints"}, - Verbs: []string{"use"}, - }, - }, - } - return []client.Object{clusterRole}, model.NonMutateFn, nil -} - -func (d *AstraConnectDeployer) GetClusterRoleBindingObjects(m *v1.AstraConnector, ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) { - clusterRoleBinding := &rbacv1.ClusterRoleBinding{ - ObjectMeta: metav1.ObjectMeta{ - Name: common.AstraConnectName, - }, - Subjects: []rbacv1.Subject{ - { - Kind: "ServiceAccount", - Name: common.AstraConnectName, - Namespace: m.Namespace, - }, - }, - RoleRef: rbacv1.RoleRef{ - Kind: "ClusterRole", - Name: common.AstraConnectName, - APIGroup: "rbac.authorization.k8s.io", - }, - } - return []client.Object{clusterRoleBinding}, model.NonMutateFn, nil -} - -// GetRoleObjects returns a ConfigMapRole object for Astra Connect -func (d *AstraConnectDeployer) GetRoleObjects(m *v1.AstraConnector, ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) { - role := &rbacv1.Role{ - ObjectMeta: metav1.ObjectMeta{ - Name: common.AstraConnectName, - Namespace: m.Namespace, - }, - Rules: []rbacv1.PolicyRule{ - { - APIGroups: []string{""}, - Resources: []string{"secrets"}, - Verbs: []string{"create", "update", "list", "get"}, - }, - { - APIGroups: []string{"astra.netapp.io"}, - Resources: []string{ - "applications", - "appmirrorrelationships", - "appmirrorupdates", - "appvaults", - "autosupportbundles", - "backups", - "backupinplacerestores", - "backuprestores", - "exechooks", - "exechooksruns", - "pvccopies", - "pvcerases", - "resourcebackups", - "resourcedeletes", - "resourcerestores", - "resourcesummaryuploads", - "resticvolumebackups", - "resticvolumerestores", - "schedules", - "shutdownsnapshots", - "snapshots", - "snapshotinplacerestores", - "snapshotrestores", - "astraconnectors", - }, - Verbs: []string{"create", "update", "delete"}, - }, - }, - } - return []client.Object{role}, model.NonMutateFn, nil -} - -// GetRoleBindingObjects returns a ConfigMapRoleBinding object -func (d *AstraConnectDeployer) GetRoleBindingObjects(m *v1.AstraConnector, ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) { - roleBinding := &rbacv1.RoleBinding{ - ObjectMeta: metav1.ObjectMeta{ - Name: common.AstraConnectName, - Namespace: m.Namespace, - }, - Subjects: []rbacv1.Subject{ - { - Kind: "ServiceAccount", - Name: common.AstraConnectName, - Namespace: m.Namespace, - }, - }, - RoleRef: rbacv1.RoleRef{ - Kind: "Role", - Name: common.AstraConnectName, - APIGroup: "rbac.authorization.k8s.io", - }, - } - return []client.Object{roleBinding}, model.NonMutateFn, nil -} - -// NIL RESOURCES BELOW -func (d *AstraConnectDeployer) GetStatefulSetObjects(m *v1.AstraConnector, ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) { - return nil, model.NonMutateFn, nil -} +//import ( +// "context" +// "fmt" +// "github.com/NetApp-Polaris/astra-connector-operator/api/v1" +// "maps" +// +// "k8s.io/apimachinery/pkg/api/resource" +// +// appsv1 "k8s.io/api/apps/v1" +// corev1 "k8s.io/api/core/v1" +// rbacv1 "k8s.io/api/rbac/v1" +// metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +// "sigs.k8s.io/controller-runtime/pkg/client" +// "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" +// ctrllog "sigs.k8s.io/controller-runtime/pkg/log" +// +// "github.com/NetApp-Polaris/astra-connector-operator/app/conf" +// "github.com/NetApp-Polaris/astra-connector-operator/app/deployer/model" +// "github.com/NetApp-Polaris/astra-connector-operator/common" +//) +// +//type AstraConnectDeployer struct{} +// +//func NewAstraConnectorDeployer() model.Deployer { +// return &AstraConnectDeployer{} +//} +// +//// GetDeploymentObjects returns a Astra Connect Deployment object +//func (d *AstraConnectDeployer) GetDeploymentObjects(m *v1.AstraConnector, ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) { +// log := ctrllog.FromContext(ctx) +// ls := LabelsForAstraConnectClient(common.AstraConnectName, m.Spec.Labels) +// +// var imageRegistry string +// var containerImage string +// var connectorImage string +// if m.Spec.ImageRegistry.Name != "" { +// imageRegistry = m.Spec.ImageRegistry.Name +// } else { +// imageRegistry = common.DefaultImageRegistry +// } +// +// if m.Spec.Image != "" { +// containerImage = m.Spec.Image +// } else { +// containerImage = common.ConnectorImageTag +// } +// +// connectorImage = fmt.Sprintf("%s/astra-connector:%s", imageRegistry, containerImage) +// log.Info("Using AstraConnector image", "image", connectorImage) +// +// ref := &corev1.ConfigMapKeySelector{LocalObjectReference: corev1.LocalObjectReference{Name: common.AstraConnectName}, Key: "nats_url"} +// +// dep := &appsv1.Deployment{ +// ObjectMeta: metav1.ObjectMeta{ +// Name: common.AstraConnectName, +// Namespace: m.Namespace, +// Annotations: map[string]string{ +// "container.seccomp.security.alpha.kubernetes.io/pod": "runtime/default", +// }, +// }, +// Spec: appsv1.DeploymentSpec{ +// // TODO remove option to set replica count in CRD. This should always only-ever be 1 +// Replicas: &m.Spec.Replicas, +// Selector: &metav1.LabelSelector{ +// MatchLabels: ls, +// }, +// Template: corev1.PodTemplateSpec{ +// ObjectMeta: metav1.ObjectMeta{ +// Labels: ls, +// }, +// Spec: corev1.PodSpec{ +// Containers: []corev1.Container{{ +// Image: connectorImage, +// Name: common.AstraConnectName, +// Env: []corev1.EnvVar{ +// { +// Name: "NATS_SERVER_URL", +// ValueFrom: &corev1.EnvVarSource{ConfigMapKeyRef: ref}, +// }, +// { +// Name: "LOG_LEVEL", +// Value: "trace", +// }, +// { +// Name: "POD_NAME", +// ValueFrom: &corev1.EnvVarSource{ +// FieldRef: &corev1.ObjectFieldSelector{ +// APIVersion: "v1", +// FieldPath: "metadata.name", +// }, +// }, +// }, +// { +// Name: "NAMESPACE", +// ValueFrom: &corev1.EnvVarSource{ +// FieldRef: &corev1.ObjectFieldSelector{ +// APIVersion: "v1", +// FieldPath: "metadata.namespace", +// }, +// }, +// }, +// }, +// Resources: corev1.ResourceRequirements{ +// Limits: corev1.ResourceList{ +// corev1.ResourceCPU: resource.MustParse("0.1"), +// }, +// }, +// SecurityContext: conf.GetSecurityContext(), +// }}, +// ServiceAccountName: common.AstraConnectName, +// }, +// }, +// }, +// } +// +// if m.Spec.ImageRegistry.Secret != "" { +// dep.Spec.Template.Spec.ImagePullSecrets = []corev1.LocalObjectReference{ +// { +// Name: m.Spec.ImageRegistry.Secret, +// }, +// } +// } +// return []client.Object{dep}, model.NonMutateFn, nil +//} +// +//// GetServiceObjects returns an Astra-Connect Service object +//func (d *AstraConnectDeployer) GetServiceObjects(m *v1.AstraConnector, ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) { +// return nil, model.NonMutateFn, nil +//} +// +//// LabelsForAstraConnectClient returns the labels for selecting the AstraConnectClient +//func LabelsForAstraConnectClient(name string, mLabels map[string]string) map[string]string { +// labels := map[string]string{"type": name, "role": name} +// maps.Copy(labels, mLabels) +// return labels +//} +// +//// GetConfigMapObjects returns a ConfigMap object for Astra Connect +//func (d *AstraConnectDeployer) GetConfigMapObjects(m *v1.AstraConnector, ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) { +// configMap := &corev1.ConfigMap{ +// ObjectMeta: metav1.ObjectMeta{ +// Namespace: m.Namespace, +// Name: common.AstraConnectName, +// }, +// //Data: map[string]string{ +// // "nats_url": GetNatsURL(m), +// // "skip_tls_validation": strconv.FormatBool(m.Spec.Astra.SkipTLSValidation), +// //}, +// } +// return []client.Object{configMap}, model.NonMutateFn, nil +//} +// +//// GetServiceAccountObjects returns a ServiceAccount object for Astra Connect +//func (d *AstraConnectDeployer) GetServiceAccountObjects(m *v1.AstraConnector, ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) { +// sa := &corev1.ServiceAccount{ +// ObjectMeta: metav1.ObjectMeta{ +// Name: common.AstraConnectName, +// Namespace: m.Namespace, +// }, +// } +// return []client.Object{sa}, model.NonMutateFn, nil +//} +// +//func (d *AstraConnectDeployer) GetClusterRoleObjects(m *v1.AstraConnector, ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) { +// clusterRole := &rbacv1.ClusterRole{ +// ObjectMeta: metav1.ObjectMeta{ +// Name: common.AstraConnectName, +// }, +// Rules: []rbacv1.PolicyRule{ +// { +// APIGroups: []string{""}, +// Resources: []string{"namespaces", "persistentvolumes", "nodes", "pods", "services"}, +// Verbs: []string{"watch", "list", "get"}, +// }, +// { +// APIGroups: []string{"storage.k8s.io"}, +// Resources: []string{"storageclasses"}, +// Verbs: []string{"update", "watch", "list", "get"}, +// }, +// { +// APIGroups: []string{"storage.k8s.io"}, +// Resources: []string{"csidrivers"}, +// Verbs: []string{"watch", "list", "get"}, +// }, +// { +// APIGroups: []string{"snapshot.storage.k8s.io"}, +// Resources: []string{"volumesnapshotclasses"}, +// Verbs: []string{"watch", "list", "get"}, +// }, +// { +// APIGroups: []string{"trident.netapp.io"}, +// Resources: []string{"tridentversions", "tridentorchestrators"}, +// Verbs: []string{"watch", "list", "get"}, +// }, +// { +// APIGroups: []string{"astra.netapp.io"}, +// Resources: []string{ +// "applications", +// "appmirrorrelationships", +// "appmirrorupdates", +// "appvaults", +// "autosupportbundles", +// "backups", +// "backupinplacerestores", +// "backuprestores", +// "exechooks", +// "exechooksruns", +// "pvccopies", +// "pvcerases", +// "resourcebackups", +// "resourcedeletes", +// "resourcerestores", +// "resourcesummaryuploads", +// "resticvolumebackups", +// "resticvolumerestores", +// "schedules", +// "shutdownsnapshots", +// "snapshots", +// "snapshotinplacerestores", +// "snapshotrestores", +// "astraconnectors", +// }, +// Verbs: []string{"watch", "list", "get"}, +// }, +// { +// APIGroups: []string{"security.openshift.io"}, +// Resources: []string{"securitycontextconstraints"}, +// Verbs: []string{"use"}, +// }, +// }, +// } +// return []client.Object{clusterRole}, model.NonMutateFn, nil +//} +// +//func (d *AstraConnectDeployer) GetClusterRoleBindingObjects(m *v1.AstraConnector, ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) { +// clusterRoleBinding := &rbacv1.ClusterRoleBinding{ +// ObjectMeta: metav1.ObjectMeta{ +// Name: common.AstraConnectName, +// }, +// Subjects: []rbacv1.Subject{ +// { +// Kind: "ServiceAccount", +// Name: common.AstraConnectName, +// Namespace: m.Namespace, +// }, +// }, +// RoleRef: rbacv1.RoleRef{ +// Kind: "ClusterRole", +// Name: common.AstraConnectName, +// APIGroup: "rbac.authorization.k8s.io", +// }, +// } +// return []client.Object{clusterRoleBinding}, model.NonMutateFn, nil +//} +// +//// GetRoleObjects returns a ConfigMapRole object for Astra Connect +//func (d *AstraConnectDeployer) GetRoleObjects(m *v1.AstraConnector, ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) { +// role := &rbacv1.Role{ +// ObjectMeta: metav1.ObjectMeta{ +// Name: common.AstraConnectName, +// Namespace: m.Namespace, +// }, +// Rules: []rbacv1.PolicyRule{ +// { +// APIGroups: []string{""}, +// Resources: []string{"secrets"}, +// Verbs: []string{"create", "update", "list", "get"}, +// }, +// { +// APIGroups: []string{"astra.netapp.io"}, +// Resources: []string{ +// "applications", +// "appmirrorrelationships", +// "appmirrorupdates", +// "appvaults", +// "autosupportbundles", +// "backups", +// "backupinplacerestores", +// "backuprestores", +// "exechooks", +// "exechooksruns", +// "pvccopies", +// "pvcerases", +// "resourcebackups", +// "resourcedeletes", +// "resourcerestores", +// "resourcesummaryuploads", +// "resticvolumebackups", +// "resticvolumerestores", +// "schedules", +// "shutdownsnapshots", +// "snapshots", +// "snapshotinplacerestores", +// "snapshotrestores", +// "astraconnectors", +// }, +// Verbs: []string{"create", "update", "delete"}, +// }, +// }, +// } +// return []client.Object{role}, model.NonMutateFn, nil +//} +// +//// GetRoleBindingObjects returns a ConfigMapRoleBinding object +//func (d *AstraConnectDeployer) GetRoleBindingObjects(m *v1.AstraConnector, ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) { +// roleBinding := &rbacv1.RoleBinding{ +// ObjectMeta: metav1.ObjectMeta{ +// Name: common.AstraConnectName, +// Namespace: m.Namespace, +// }, +// Subjects: []rbacv1.Subject{ +// { +// Kind: "ServiceAccount", +// Name: common.AstraConnectName, +// Namespace: m.Namespace, +// }, +// }, +// RoleRef: rbacv1.RoleRef{ +// Kind: "Role", +// Name: common.AstraConnectName, +// APIGroup: "rbac.authorization.k8s.io", +// }, +// } +// return []client.Object{roleBinding}, model.NonMutateFn, nil +//} +// +//// NIL RESOURCES BELOW +//func (d *AstraConnectDeployer) GetStatefulSetObjects(m *v1.AstraConnector, ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) { +// return nil, model.NonMutateFn, nil +//} diff --git a/app/deployer/connector/astra_connect_natless.go b/app/deployer/connector/astra_connect_natless.go index e926411c..eaf30dc8 100644 --- a/app/deployer/connector/astra_connect_natless.go +++ b/app/deployer/connector/astra_connect_natless.go @@ -7,6 +7,9 @@ package connector import ( "context" "fmt" + "github.com/NetApp-Polaris/astra-connector-operator/api/v1" + "maps" + "reflect" "strconv" "k8s.io/apimachinery/pkg/api/resource" @@ -22,31 +25,65 @@ import ( "github.com/NetApp-Polaris/astra-connector-operator/app/conf" "github.com/NetApp-Polaris/astra-connector-operator/app/deployer/model" "github.com/NetApp-Polaris/astra-connector-operator/common" - v1 "github.com/NetApp-Polaris/astra-connector-operator/details/operator-sdk/api/v1" ) -type AstraConnectNatlessDeployer struct{} +type AstraConnectNatlessDeployer struct { + connectorCR v1.AstraConnector +} + +func NewAstraConnectorNatlessDeployer(cr v1.AstraConnector) model.Deployer { + return &AstraConnectNatlessDeployer{connectorCR: cr} +} + +func (d *AstraConnectNatlessDeployer) UpdateStatus(ctx context.Context, status string, statusWriter client.StatusWriter) error { + d.connectorCR.Status.Status = status + err := statusWriter.Update(ctx, &d.connectorCR) + if err != nil { + return err + } -func NewAstraConnectorNatlessDeployer() model.Deployer { - return &AstraConnectNatlessDeployer{} + return nil +} + +func (d *AstraConnectNatlessDeployer) IsSpecModified(ctx context.Context, k8sClient client.Client) bool { + log := ctrllog.FromContext(ctx) + // Fetch the AstraConnector instance + controllerKey := client.ObjectKeyFromObject(&d.connectorCR) + updatedAstraConnector := &v1.AstraConnector{} + err := k8sClient.Get(ctx, controllerKey, updatedAstraConnector) + if err != nil { + log.Info("AstraConnector resource not found. Ignoring since object must be deleted") + return true + } + + if updatedAstraConnector.GetDeletionTimestamp() != nil { + log.Info("AstraConnector marked for deletion, reconciler requeue") + return true + } + + if !reflect.DeepEqual(updatedAstraConnector.Spec, d.connectorCR.Spec) { + log.Info("AstraConnector spec change, reconciler requeue") + return true + } + return false } // GetDeploymentObjects returns a Astra Connect Deployment object -func (d *AstraConnectNatlessDeployer) GetDeploymentObjects(m *v1.AstraConnector, ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) { +func (d *AstraConnectNatlessDeployer) GetDeploymentObjects(ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) { log := ctrllog.FromContext(ctx) - ls := LabelsForAstraConnectClient(common.AstraConnectName, m.Spec.Labels) + ls := LabelsForAstraConnectClient(common.AstraConnectName, d.connectorCR.Spec.Labels) var imageRegistry string var containerImage string var connectorImage string - if m.Spec.ImageRegistry.Name != "" { - imageRegistry = m.Spec.ImageRegistry.Name + if d.connectorCR.Spec.ImageRegistry.Name != "" { + imageRegistry = d.connectorCR.Spec.ImageRegistry.Name } else { imageRegistry = common.DefaultImageRegistry } - if m.Spec.AstraConnect.Image != "" { - containerImage = m.Spec.AstraConnect.Image + if d.connectorCR.Spec.Image != "" { + containerImage = d.connectorCR.Spec.Image } else { containerImage = common.ConnectorImageTag } @@ -57,14 +94,14 @@ func (d *AstraConnectNatlessDeployer) GetDeploymentObjects(m *v1.AstraConnector, dep := &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{ Name: common.AstraConnectName, - Namespace: m.Namespace, + Namespace: d.connectorCR.Namespace, Annotations: map[string]string{ "container.seccomp.security.alpha.kubernetes.io/pod": "runtime/default", }, }, Spec: appsv1.DeploymentSpec{ // TODO remove option to set replica count in CRD. This should always only-ever be 1 - Replicas: &m.Spec.AstraConnect.Replicas, + Replicas: &d.connectorCR.Spec.Replicas, Selector: &metav1.LabelSelector{ MatchLabels: ls, }, @@ -87,31 +124,31 @@ func (d *AstraConnectNatlessDeployer) GetDeploymentObjects(m *v1.AstraConnector, }, { Name: "API_TOKEN_SECRET_REF", - Value: m.Spec.Astra.TokenRef, + Value: d.connectorCR.Spec.ApiTokenSecretRef, }, { Name: "ASTRA_CONTROL_URL", - Value: m.Spec.NatsSyncClient.CloudBridgeURL, + Value: d.connectorCR.Spec.AstraControlUrl, }, { Name: "ACCOUNT_ID", - Value: m.Spec.Astra.AccountId, + Value: d.connectorCR.Spec.AccountId, }, { Name: "CLOUD_ID", - Value: m.Spec.Astra.CloudId, + Value: d.connectorCR.Spec.CloudId, }, { Name: "CLUSTER_ID", - Value: m.Spec.Astra.ClusterId, + Value: d.connectorCR.Spec.ClusterId, }, { Name: "HOST_ALIAS_IP", - Value: m.Spec.NatsSyncClient.HostAliasIP, + Value: d.connectorCR.Spec.HostAliasIP, }, { Name: "SKIP_TLS_VALIDATION", - Value: strconv.FormatBool(m.Spec.Astra.SkipTLSValidation), + Value: strconv.FormatBool(d.connectorCR.Spec.SkipTLSValidation), }, { Name: "MEMORY_RESOURCE_LIMIT", @@ -136,10 +173,10 @@ func (d *AstraConnectNatlessDeployer) GetDeploymentObjects(m *v1.AstraConnector, }, } - if m.Spec.ImageRegistry.Secret != "" { + if d.connectorCR.Spec.ImageRegistry.Secret != "" { dep.Spec.Template.Spec.ImagePullSecrets = []corev1.LocalObjectReference{ { - Name: m.Spec.ImageRegistry.Secret, + Name: d.connectorCR.Spec.ImageRegistry.Secret, }, } } @@ -147,37 +184,37 @@ func (d *AstraConnectNatlessDeployer) GetDeploymentObjects(m *v1.AstraConnector, } // GetServiceObjects returns an Astra-Connect Service object -func (d *AstraConnectNatlessDeployer) GetServiceObjects(m *v1.AstraConnector, ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) { +func (d *AstraConnectNatlessDeployer) GetServiceObjects(ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) { return nil, model.NonMutateFn, nil } // GetConfigMapObjects returns a ConfigMap object for Astra Connect -func (d *AstraConnectNatlessDeployer) GetConfigMapObjects(m *v1.AstraConnector, ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) { +func (d *AstraConnectNatlessDeployer) GetConfigMapObjects(ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) { configMap := &corev1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ - Namespace: m.Namespace, + Namespace: d.connectorCR.Namespace, Name: common.AstraConnectName, }, Data: map[string]string{ //"nats_url": GetNatsURL(m), - "skip_tls_validation": strconv.FormatBool(m.Spec.Astra.SkipTLSValidation), + "skip_tls_validation": strconv.FormatBool(d.connectorCR.Spec.SkipTLSValidation), }, } return []client.Object{configMap}, model.NonMutateFn, nil } // GetServiceAccountObjects returns a ServiceAccount object for Astra Connect -func (d *AstraConnectNatlessDeployer) GetServiceAccountObjects(m *v1.AstraConnector, ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) { +func (d *AstraConnectNatlessDeployer) GetServiceAccountObjects(ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) { sa := &corev1.ServiceAccount{ ObjectMeta: metav1.ObjectMeta{ Name: common.AstraConnectName, - Namespace: m.Namespace, + Namespace: d.connectorCR.Namespace, }, } return []client.Object{sa}, model.NonMutateFn, nil } -func (d *AstraConnectNatlessDeployer) GetClusterRoleObjects(m *v1.AstraConnector, ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) { +func (d *AstraConnectNatlessDeployer) GetClusterRoleObjects(ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) { clusterRole := &rbacv1.ClusterRole{ ObjectMeta: metav1.ObjectMeta{ Name: common.AstraConnectName, @@ -249,7 +286,7 @@ func (d *AstraConnectNatlessDeployer) GetClusterRoleObjects(m *v1.AstraConnector return []client.Object{clusterRole}, model.NonMutateFn, nil } -func (d *AstraConnectNatlessDeployer) GetClusterRoleBindingObjects(m *v1.AstraConnector, ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) { +func (d *AstraConnectNatlessDeployer) GetClusterRoleBindingObjects(ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) { clusterRoleBinding := &rbacv1.ClusterRoleBinding{ ObjectMeta: metav1.ObjectMeta{ Name: common.AstraConnectName, @@ -258,7 +295,7 @@ func (d *AstraConnectNatlessDeployer) GetClusterRoleBindingObjects(m *v1.AstraCo { Kind: "ServiceAccount", Name: common.AstraConnectName, - Namespace: m.Namespace, + Namespace: d.connectorCR.Namespace, }, }, RoleRef: rbacv1.RoleRef{ @@ -271,11 +308,11 @@ func (d *AstraConnectNatlessDeployer) GetClusterRoleBindingObjects(m *v1.AstraCo } // GetRoleObjects returns a ConfigMapRole object for Astra Connect -func (d *AstraConnectNatlessDeployer) GetRoleObjects(m *v1.AstraConnector, ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) { +func (d *AstraConnectNatlessDeployer) GetRoleObjects(ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) { role := &rbacv1.Role{ ObjectMeta: metav1.ObjectMeta{ Name: common.AstraConnectName, - Namespace: m.Namespace, + Namespace: d.connectorCR.Namespace, }, Rules: []rbacv1.PolicyRule{ { @@ -320,17 +357,17 @@ func (d *AstraConnectNatlessDeployer) GetRoleObjects(m *v1.AstraConnector, ctx c } // GetRoleBindingObjects returns a ConfigMapRoleBinding object -func (d *AstraConnectNatlessDeployer) GetRoleBindingObjects(m *v1.AstraConnector, ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) { +func (d *AstraConnectNatlessDeployer) GetRoleBindingObjects(ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) { roleBinding := &rbacv1.RoleBinding{ ObjectMeta: metav1.ObjectMeta{ Name: common.AstraConnectName, - Namespace: m.Namespace, + Namespace: d.connectorCR.Namespace, }, Subjects: []rbacv1.Subject{ { Kind: "ServiceAccount", Name: common.AstraConnectName, - Namespace: m.Namespace, + Namespace: d.connectorCR.Namespace, }, }, RoleRef: rbacv1.RoleRef{ @@ -343,6 +380,13 @@ func (d *AstraConnectNatlessDeployer) GetRoleBindingObjects(m *v1.AstraConnector } // NIL RESOURCES BELOW -func (d *AstraConnectNatlessDeployer) GetStatefulSetObjects(m *v1.AstraConnector, ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) { +func (d *AstraConnectNatlessDeployer) GetStatefulSetObjects(ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) { return nil, model.NonMutateFn, nil } + +// LabelsForAstraConnectClient returns the labels for selecting the AstraConnectClient +func LabelsForAstraConnectClient(name string, mLabels map[string]string) map[string]string { + labels := map[string]string{"type": name, "role": name} + maps.Copy(labels, mLabels) + return labels +} diff --git a/app/deployer/connector/astra_connect_test.go b/app/deployer/connector/astra_connect_test.go index af837f80..9218a026 100644 --- a/app/deployer/connector/astra_connect_test.go +++ b/app/deployer/connector/astra_connect_test.go @@ -2,6 +2,7 @@ package connector_test import ( "context" + "github.com/NetApp-Polaris/astra-connector-operator/api/v1" "testing" appsv1 "k8s.io/api/apps/v1" @@ -10,7 +11,6 @@ import ( "github.com/NetApp-Polaris/astra-connector-operator/app/deployer/connector" "github.com/NetApp-Polaris/astra-connector-operator/common" - v1 "github.com/NetApp-Polaris/astra-connector-operator/details/operator-sdk/api/v1" "github.com/stretchr/testify/assert" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) diff --git a/app/deployer/connector/nats.go b/app/deployer/connector/nats.go deleted file mode 100644 index 05b24d1a..00000000 --- a/app/deployer/connector/nats.go +++ /dev/null @@ -1,342 +0,0 @@ -/* - * Copyright (c) 2022. NetApp, Inc. All Rights Reserved. - */ - -package connector - -import ( - "context" - "fmt" - "maps" - "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - "strings" - - "sigs.k8s.io/controller-runtime/pkg/client" - - "github.com/NetApp-Polaris/astra-connector-operator/app/conf" - "github.com/NetApp-Polaris/astra-connector-operator/app/deployer/model" - - "github.com/NetApp-Polaris/astra-connector-operator/common" - - appsv1 "k8s.io/api/apps/v1" - corev1 "k8s.io/api/core/v1" - rbacv1 "k8s.io/api/rbac/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - ctrllog "sigs.k8s.io/controller-runtime/pkg/log" - - v1 "github.com/NetApp-Polaris/astra-connector-operator/details/operator-sdk/api/v1" -) - -type NatsDeployer struct{} - -func NewNatsDeployer() model.Deployer { - return &NatsDeployer{} -} - -// GetStatefulSetObjects returns a NATS Statefulset object -func (n *NatsDeployer) GetStatefulSetObjects(m *v1.AstraConnector, ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) { - log := ctrllog.FromContext(ctx) - ls := labelsForNats(common.NatsName, m.Spec.Labels) - - var replicas int32 - if m.Spec.Nats.Replicas > 2 { - replicas = m.Spec.Nats.Replicas - } else { - log.Info("Defaulting the Nats replica size", "size", common.NatsDefaultReplicas) - replicas = common.NatsDefaultReplicas - } - - var natsImage string - var imageRegistry string - var containerImage string - if m.Spec.ImageRegistry.Name != "" { - imageRegistry = m.Spec.ImageRegistry.Name - } else { - imageRegistry = common.DefaultImageRegistry - } - - if m.Spec.Nats.Image != "" { - containerImage = m.Spec.Nats.Image - } else { - containerImage = common.NatsDefaultImage - } - - natsImage = fmt.Sprintf("%s/%s", imageRegistry, containerImage) - log.Info("Using nats image", "image", natsImage) - - dep := &appsv1.StatefulSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: common.NatsName, - Namespace: m.Namespace, - Annotations: map[string]string{ - "container.seccomp.security.alpha.kubernetes.io/pod": "runtime/default", - }, - }, - Spec: appsv1.StatefulSetSpec{ - ServiceName: common.NatsClusterServiceName, - Replicas: &replicas, - Selector: &metav1.LabelSelector{ - MatchLabels: ls, - }, - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Labels: ls, - }, - Spec: corev1.PodSpec{ - ServiceAccountName: common.NatsServiceAccountName, - Containers: []corev1.Container{{ - Image: natsImage, - Name: common.NatsName, - Ports: []corev1.ContainerPort{ - { - Name: "client", - ContainerPort: common.NatsClientPort, - }, - { - Name: "cluster", - ContainerPort: common.NatsClusterPort, - }, - { - Name: "monitor", - ContainerPort: common.NatsMonitorPort, - }, - { - Name: "metrics", - ContainerPort: common.NatsMetricsPort, - }, - }, - Command: []string{"nats-server", "--config", "/etc/nats-config/nats.conf"}, - Env: []corev1.EnvVar{ - { - Name: "CLUSTER_ADVERTISE", - Value: fmt.Sprintf("%s.nats.%s.svc", common.NatsName, m.Namespace), - }, - { - Name: "POD_NAME", - Value: common.NatsName, - }, { - Name: "POD_NAMESPACE", - Value: m.Namespace, - }, - }, - VolumeMounts: []corev1.VolumeMount{ - { - Name: common.NatsVolumeName, - MountPath: "/etc/nats-config", - }, - { - Name: "pid", - MountPath: "/var/run/nats", - }, - }, - SecurityContext: conf.GetSecurityContext(), - }}, - Volumes: []corev1.Volume{ - { - Name: common.NatsVolumeName, - VolumeSource: corev1.VolumeSource{ - ConfigMap: &corev1.ConfigMapVolumeSource{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: common.NatsConfigMapName, - }, - }, - }, - }, - { - Name: "pid", - VolumeSource: corev1.VolumeSource{ - EmptyDir: &corev1.EmptyDirVolumeSource{ - Medium: "", - }, - }, - }, - }, - }, - }, - }, - } - if m.Spec.ImageRegistry.Secret != "" { - dep.Spec.Template.Spec.ImagePullSecrets = []corev1.LocalObjectReference{ - { - Name: m.Spec.ImageRegistry.Secret, - }, - } - } - return []client.Object{dep}, model.NonMutateFn, nil -} - -// GetConfigMapObjects returns a ConfigMap object for nats -func (n *NatsDeployer) GetConfigMapObjects(m *v1.AstraConnector, ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) { - log := ctrllog.FromContext(ctx) - - routes := make([]string, 0) - var index int32 - index = 0 - var replicas int32 - - // Setting the replicas to 1, things dont work with multiple replicas on GKE - // Uncomment once issue is fixed. - //if m.Spec.Nats.Replicas > 2 { - // replicas = m.Spec.Nats.Replicas - //} else { - // log.Info("Defaulting the Nats replica size", "size", common.NatsDefaultReplicas) - // replicas = common.NatsDefaultReplicas - //} - - log.Info("Defaulting the Nats replica size", "size", common.NatsDefaultReplicas) - replicas = common.NatsDefaultReplicas - - for index < replicas { - rt := fmt.Sprintf("\n nats://nats-%d.nats-cluster:%d", index, common.NatsClusterPort) - routes = append(routes, rt) - index += 1 - } - routes[len(routes)-1] += "\n " - routeConfig := strings.Join(routes, "") - - natsConf := "pid_file: \"/var/run/nats/nats.pid\"\nhttp: %d\nmax_payload: %d\n\ncluster {\n port: %d\n routes [%s]\n\n cluster_advertise: $CLUSTER_ADVERTISE\n connect_retries: 30\n}\n" - configMap := &corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: m.Namespace, - Name: common.NatsConfigMapName, - }, - Data: map[string]string{ - "nats.conf": fmt.Sprintf(natsConf, common.NatsMonitorPort, common.NatsMaxPayload, common.NatsClusterPort, routeConfig), - }, - } - return []client.Object{configMap}, model.NonMutateFn, nil -} - -// GetServiceAccountObjects returns a ServiceAccount object for nats -func (n *NatsDeployer) GetServiceAccountObjects(m *v1.AstraConnector, ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) { - sa := &corev1.ServiceAccount{ - ObjectMeta: metav1.ObjectMeta{ - Name: common.NatsServiceAccountName, - Namespace: m.Namespace, - Labels: labelsForNats(common.NatsName, m.Spec.Labels), - }, - } - return []client.Object{sa}, model.NonMutateFn, nil -} - -// GetServiceObjects returns a Service object for nats -func (n *NatsDeployer) GetServiceObjects(m *v1.AstraConnector, ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) { - ls := labelsForNats(common.NatsName, m.Spec.Labels) - var services []client.Object - - natsService := &corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: common.NatsName, - Namespace: m.Namespace, - Labels: ls, - }, - Spec: corev1.ServiceSpec{ - Type: corev1.ServiceTypeClusterIP, - Ports: []corev1.ServicePort{ - { - Name: common.NatsName, - Port: common.NatsClientPort, - }, - }, - Selector: ls, - }, - } - natsClusterService := &corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: common.NatsClusterServiceName, - Namespace: m.Namespace, - Labels: ls, - }, - Spec: corev1.ServiceSpec{ - ClusterIP: "", - Ports: []corev1.ServicePort{ - { - Name: "client", - Port: common.NatsClientPort, - }, - { - Name: "cluster", - Port: common.NatsClusterPort, - }, - { - Name: "monitor", - Port: common.NatsMonitorPort, - }, - { - Name: "metrics", - Port: common.NatsMetricsPort, - }, - { - Name: "gateways", - Port: common.NatsGatewaysPort, - }, - }, - Selector: ls, - }, - } - services = append(services, natsService, natsClusterService) - return services, model.NonMutateFn, nil -} - -// labelsForNats returns the labels for selecting the nats resources -func labelsForNats(name string, mLabels map[string]string) map[string]string { - labels := map[string]string{"app": name} - maps.Copy(labels, mLabels) - return labels -} - -func (n *NatsDeployer) GetDeploymentObjects(m *v1.AstraConnector, ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) { - return nil, model.NonMutateFn, nil -} - -func (n *NatsDeployer) GetRoleObjects(m *v1.AstraConnector, ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) { - role := &rbacv1.Role{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: m.Namespace, - Name: common.NatsRoleName, - }, - Rules: []rbacv1.PolicyRule{ - { - APIGroups: []string{"security.openshift.io"}, - Resources: []string{"securitycontextconstraints"}, - Verbs: []string{"use"}, - }, - }, - } - return []client.Object{role}, model.NonMutateFn, nil -} - -func (n *NatsDeployer) GetRoleBindingObjects(m *v1.AstraConnector, ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) { - roleBinding := &rbacv1.RoleBinding{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: m.Namespace, - Name: common.NatsRoleBindingName, - }, - Subjects: []rbacv1.Subject{ - { - Kind: "ServiceAccount", - Name: common.NatsServiceAccountName, - }, - }, - RoleRef: rbacv1.RoleRef{ - Kind: "Role", - Name: common.NatsRoleName, - APIGroup: "rbac.authorization.k8s.io", - }, - } - return []client.Object{roleBinding}, model.NonMutateFn, nil -} - -func (n *NatsDeployer) GetClusterRoleObjects(m *v1.AstraConnector, ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) { - return nil, model.NonMutateFn, nil -} - -func (n *NatsDeployer) GetClusterRoleBindingObjects(m *v1.AstraConnector, ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) { - return nil, model.NonMutateFn, nil -} - -// GetNatsURL returns the nats URL -func GetNatsURL(m *v1.AstraConnector) string { - natsURL := fmt.Sprintf("nats://%s.%s:%d", common.NatsName, m.Namespace, common.NatsClientPort) - return natsURL -} diff --git a/app/deployer/connector/nats_test.go b/app/deployer/connector/nats_test.go deleted file mode 100644 index b1c7a93d..00000000 --- a/app/deployer/connector/nats_test.go +++ /dev/null @@ -1,183 +0,0 @@ -package connector_test - -import ( - "context" - "testing" - - "github.com/NetApp-Polaris/astra-connector-operator/app/deployer/connector" - "github.com/NetApp-Polaris/astra-connector-operator/common" - v1 "github.com/NetApp-Polaris/astra-connector-operator/details/operator-sdk/api/v1" - "github.com/stretchr/testify/assert" - appsv1 "k8s.io/api/apps/v1" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -func TestNatsGetStatefulSetObjects(t *testing.T) { - deployer := connector.NewNatsDeployer() - ctx := context.Background() - - m := &v1.AstraConnector{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-connector", - Namespace: "test-namespace", - }, - Spec: v1.AstraConnectorSpec{ - - Nats: v1.Nats{ - Replicas: 3, - Image: "test-image", - }, - - ImageRegistry: v1.ImageRegistry{ - Name: "test-registry", - Secret: "test-secret", - }, - }, - } - - objects, _, err := deployer.GetStatefulSetObjects(m, ctx) - assert.NoError(t, err) - assert.Equal(t, 1, len(objects)) - - statefulSet, ok := objects[0].(*appsv1.StatefulSet) - assert.True(t, ok) - - assert.Equal(t, common.NatsName, statefulSet.Name) - assert.Equal(t, m.Namespace, statefulSet.Namespace) - assert.Equal(t, int32(3), *statefulSet.Spec.Replicas) - assert.Equal(t, "test-registry/test-image", statefulSet.Spec.Template.Spec.Containers[0].Image) - assert.Equal(t, []corev1.LocalObjectReference{{Name: "test-secret"}}, statefulSet.Spec.Template.Spec.ImagePullSecrets) -} - -func TestNatsGetStatefulSetObjectsUseDefaults(t *testing.T) { - deployer := connector.NewNatsDeployer() - ctx := context.Background() - - m := &v1.AstraConnector{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-connector", - Namespace: "test-namespace", - }, - Spec: v1.AstraConnectorSpec{ - - Nats: v1.Nats{ - Replicas: -2, - }, - }, - } - - objects, _, err := deployer.GetStatefulSetObjects(m, ctx) - assert.NoError(t, err) - assert.Equal(t, 1, len(objects)) - - statefulSet, ok := objects[0].(*appsv1.StatefulSet) - assert.True(t, ok) - - assert.Equal(t, common.NatsName, statefulSet.Name) - assert.Equal(t, m.Namespace, statefulSet.Namespace) - assert.Equal(t, int32(1), *statefulSet.Spec.Replicas) - assert.Equal(t, "netappdownloads.jfrog.io/docker-astra-control-staging/arch30/neptune/nats:2.10.1-alpine3.18", statefulSet.Spec.Template.Spec.Containers[0].Image) - assert.Nil(t, statefulSet.Spec.Template.Spec.ImagePullSecrets) -} - -func TestNatsGetConfigMapObjects(t *testing.T) { - deployer := connector.NewNatsDeployer() - ctx := context.Background() - - m := DummyAstraConnector() - - objects, _, err := deployer.GetConfigMapObjects(&m, ctx) - assert.NoError(t, err) - assert.Equal(t, 1, len(objects)) - - configMap, ok := objects[0].(*corev1.ConfigMap) - assert.True(t, ok) - - // Todo Add assertions for the expected values in the ConfigMap object - assert.Equal(t, common.NatsConfigMapName, configMap.Name) - assert.Equal(t, m.Namespace, configMap.Namespace) - data := map[string]string{"nats.conf": "pid_file: \"/var/run/nats/nats.pid\"\nhttp: 8222\nmax_payload: 8388608\n\ncluster {\n port: 6222\n routes [\n nats://nats-0.nats-cluster:6222\n ]\n\n cluster_advertise: $CLUSTER_ADVERTISE\n connect_retries: 30\n}\n"} - assert.Equal(t, data, configMap.Data) -} - -func TestNatsGetServiceAccountObjects(t *testing.T) { - deployer := connector.NewNatsDeployer() - ctx := context.Background() - - m := DummyAstraConnector() - - objects, _, err := deployer.GetServiceAccountObjects(&m, ctx) - assert.NoError(t, err) - assert.Equal(t, 1, len(objects)) - - serviceAccount, ok := objects[0].(*corev1.ServiceAccount) - assert.True(t, ok) - - assert.Equal(t, common.NatsServiceAccountName, serviceAccount.Name) - assert.Equal(t, m.Namespace, serviceAccount.Namespace) - assert.Equal(t, map[string]string{"Label1": "Value1", "app": "nats"}, serviceAccount.Labels) -} - -func TestNatsGetServiceObjects(t *testing.T) { - deployer := connector.NewNatsDeployer() - ctx := context.Background() - - m := DummyAstraConnector() - - objects, _, err := deployer.GetServiceObjects(&m, ctx) - assert.NoError(t, err) - assert.Equal(t, 2, len(objects)) - - // test the first service which is natsService - service, ok := objects[0].(*corev1.Service) - assert.True(t, ok) - - assert.Equal(t, common.NatsName, service.Name) - assert.Equal(t, m.Namespace, service.Namespace) - assert.Equal(t, map[string]string{"Label1": "Value1", "app": "nats"}, service.Labels) - assert.Equal(t, corev1.ServiceTypeClusterIP, service.Spec.Type) - - // now test the second service nats-cluster - service, ok = objects[1].(*corev1.Service) - assert.True(t, ok) - - assert.Equal(t, common.NatsClusterServiceName, service.Name) - assert.Equal(t, m.Namespace, service.Namespace) - assert.Equal(t, map[string]string{"Label1": "Value1", "app": "nats"}, service.Labels) - -} - -// Below are all the nil objects - -func TestNatsK8sObjectsNotCreated(t *testing.T) { - deployer := connector.NewNatsDeployer() - ctx := context.Background() - - m := DummyAstraConnector() - - objects, fn, err := deployer.GetDeploymentObjects(&m, ctx) - assert.Nil(t, objects) - assert.NotNil(t, fn) - assert.Nil(t, err) - - objects, fn, err = deployer.GetRoleObjects(&m, ctx) - assert.NotNil(t, objects) - assert.NotNil(t, fn) - assert.Nil(t, err) - - objects, fn, err = deployer.GetRoleBindingObjects(&m, ctx) - assert.NotNil(t, objects) - assert.NotNil(t, fn) - assert.Nil(t, err) - - objects, fn, err = deployer.GetClusterRoleObjects(&m, ctx) - assert.Nil(t, objects) - assert.NotNil(t, fn) - assert.Nil(t, err) - - objects, fn, err = deployer.GetClusterRoleBindingObjects(&m, ctx) - assert.Nil(t, objects) - assert.NotNil(t, fn) - assert.Nil(t, err) -} diff --git a/app/deployer/connector/natssync_client.go b/app/deployer/connector/natssync_client.go deleted file mode 100644 index ba4764ae..00000000 --- a/app/deployer/connector/natssync_client.go +++ /dev/null @@ -1,269 +0,0 @@ -/* - * Copyright (c) 2022. NetApp, Inc. All Rights Reserved. - */ - -package connector - -import ( - "context" - "errors" - "fmt" - "maps" - "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - "strconv" - "strings" - - appsv1 "k8s.io/api/apps/v1" - corev1 "k8s.io/api/core/v1" - rbacv1 "k8s.io/api/rbac/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "sigs.k8s.io/controller-runtime/pkg/client" - ctrllog "sigs.k8s.io/controller-runtime/pkg/log" - - "github.com/NetApp-Polaris/astra-connector-operator/app/conf" - "github.com/NetApp-Polaris/astra-connector-operator/app/deployer/model" - "github.com/NetApp-Polaris/astra-connector-operator/app/register" - "github.com/NetApp-Polaris/astra-connector-operator/common" - v1 "github.com/NetApp-Polaris/astra-connector-operator/details/operator-sdk/api/v1" -) - -type NatsSyncClientDeployer struct{} - -func NewNatsSyncClientDeployer() model.Deployer { - return &NatsSyncClientDeployer{} -} - -// GetDeploymentObjects returns a NatsSyncClient Deployment object -func (d *NatsSyncClientDeployer) GetDeploymentObjects(m *v1.AstraConnector, ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) { - log := ctrllog.FromContext(ctx) - ls := LabelsForNatsSyncClient(common.NatsSyncClientName, m.Spec.Labels) - - var imageRegistry string - var containerImage string - var natsSyncClientImage string - if m.Spec.ImageRegistry.Name != "" { - imageRegistry = m.Spec.ImageRegistry.Name - } else { - imageRegistry = common.DefaultImageRegistry - } - - if m.Spec.NatsSyncClient.Image != "" { - containerImage = m.Spec.NatsSyncClient.Image - } else { - containerImage = common.NatsSyncClientDefaultImage - } - - natsSyncClientImage = fmt.Sprintf("%s/%s", imageRegistry, containerImage) - log.Info("Using NatsSyncClient image", "image", natsSyncClientImage) - natsSyncCloudBridgeURL := register.GetAstraHostURL(m) - keyStoreURLSplit := strings.Split(common.NatsSyncClientKeystoreUrl, "://") - if len(keyStoreURLSplit) < 2 { - return nil, nil, errors.New("invalid keyStoreURLSplit provided, format - configmap:///configmap-data") - } - - dep := &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: common.NatsSyncClientName, - Namespace: m.Namespace, - Annotations: map[string]string{ - "container.seccomp.security.alpha.kubernetes.io/pod": "runtime/default", - }, - }, - Spec: appsv1.DeploymentSpec{ - Replicas: &m.Spec.NatsSyncClient.Replicas, - Selector: &metav1.LabelSelector{ - MatchLabels: ls, - }, - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Labels: ls, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{{ - Image: natsSyncClientImage, - Name: common.NatsSyncClientName, - Env: []corev1.EnvVar{ - { - Name: "NATS_SERVER_URL", - Value: GetNatsURL(m), - }, - { - Name: "CLOUD_BRIDGE_URL", - Value: natsSyncCloudBridgeURL, - }, - { - Name: "CONFIGMAP_NAME", - Value: common.NatsSyncClientConfigMapName, - }, - { - Name: "POD_NAMESPACE", - Value: m.Namespace, - }, - { - Name: "KEYSTORE_URL", - Value: common.NatsSyncClientKeystoreUrl, - }, - { - Name: "SKIP_TLS_VALIDATION", - Value: strconv.FormatBool(m.Spec.Astra.SkipTLSValidation), - }, - }, - VolumeMounts: []corev1.VolumeMount{ - { - Name: common.NatsSyncClientConfigMapVolumeName, - MountPath: keyStoreURLSplit[1], - }, - }, - SecurityContext: conf.GetSecurityContext(), - }}, - ServiceAccountName: common.NatsSyncClientConfigMapServiceAccountName, - Volumes: []corev1.Volume{ - { - Name: common.NatsSyncClientConfigMapVolumeName, - VolumeSource: corev1.VolumeSource{ - ConfigMap: &corev1.ConfigMapVolumeSource{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: common.NatsSyncClientConfigMapName, - }, - }, - }, - }, - }, - }, - }, - }, - } - - if m.Spec.NatsSyncClient.HostAliasIP != "" { - hostNamesSplit := strings.Split(natsSyncCloudBridgeURL, "://") - if len(hostNamesSplit) < 2 { - return nil, nil, errors.New("invalid hostname provided, hostname format - https://hostname") - } - dep.Spec.Template.Spec.HostAliases = []corev1.HostAlias{ - { - IP: m.Spec.NatsSyncClient.HostAliasIP, - Hostnames: []string{hostNamesSplit[1]}, - }, - } - } - if m.Spec.ImageRegistry.Secret != "" { - dep.Spec.Template.Spec.ImagePullSecrets = []corev1.LocalObjectReference{ - { - Name: m.Spec.ImageRegistry.Secret, - }, - } - } - return []client.Object{dep}, model.NonMutateFn, nil -} - -// GetServiceObjects returns a NatsSyncClient Service object -func (d *NatsSyncClientDeployer) GetServiceObjects(m *v1.AstraConnector, ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) { - service := &corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: common.NatsSyncClientName, - Namespace: m.Namespace, - Labels: map[string]string{ - "app": common.NatsSyncClientName, - }, - }, - Spec: corev1.ServiceSpec{ - Type: corev1.ServiceTypeClusterIP, - Ports: []corev1.ServicePort{ - { - Port: common.NatsSyncClientPort, - Protocol: common.NatsSyncClientProtocol, - }, - }, - Selector: map[string]string{ - "app": common.NatsSyncClientName, - }, - }, - } - return []client.Object{service}, model.NonMutateFn, nil -} - -// LabelsForNatsSyncClient returns the labels for selecting the NatsSyncClient -func LabelsForNatsSyncClient(name string, mLabels map[string]string) map[string]string { - labels := map[string]string{"app": name} - maps.Copy(labels, mLabels) - return labels -} - -// GetConfigMapObjects returns a ConfigMap object for NatsSyncClient -func (d *NatsSyncClientDeployer) GetConfigMapObjects(m *v1.AstraConnector, ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) { - configMap := &corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: m.Namespace, - Name: common.NatsSyncClientConfigMapName, - }, - } - return []client.Object{configMap}, model.NonMutateFn, nil -} - -// GetRoleObjects returns a ConfigMapRole object for NatsSyncClient -func (d *NatsSyncClientDeployer) GetRoleObjects(m *v1.AstraConnector, ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) { - configMapRole := &rbacv1.Role{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: m.Namespace, - Name: common.NatsSyncClientConfigMapRoleName, - }, - Rules: []rbacv1.PolicyRule{ - { - APIGroups: []string{""}, - Resources: []string{"configmaps"}, - Verbs: []string{"get", "list", "patch"}, - }, - { - APIGroups: []string{"security.openshift.io"}, - Resources: []string{"securitycontextconstraints"}, - Verbs: []string{"use"}, - }, - }, - } - return []client.Object{configMapRole}, model.NonMutateFn, nil -} - -// GetRoleBindingObjects returns a NatsSyncClient ConfigMapRoleBinding object -func (d *NatsSyncClientDeployer) GetRoleBindingObjects(m *v1.AstraConnector, ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) { - configMapRoleBinding := &rbacv1.RoleBinding{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: m.Namespace, - Name: common.NatsSyncClientConfigMapRoleBindingName, - }, - Subjects: []rbacv1.Subject{ - { - Kind: "ServiceAccount", - Name: common.NatsSyncClientConfigMapServiceAccountName, - }, - }, - RoleRef: rbacv1.RoleRef{ - Kind: "Role", - Name: common.NatsSyncClientConfigMapRoleName, - APIGroup: "rbac.authorization.k8s.io", - }, - } - return []client.Object{configMapRoleBinding}, model.NonMutateFn, nil -} - -// GetServiceAccountObjects returns a ServiceAccount object for NatsSyncClient -func (d *NatsSyncClientDeployer) GetServiceAccountObjects(m *v1.AstraConnector, ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) { - sa := &corev1.ServiceAccount{ - ObjectMeta: metav1.ObjectMeta{ - Name: common.NatsSyncClientConfigMapServiceAccountName, - Namespace: m.Namespace, - }, - } - return []client.Object{sa}, model.NonMutateFn, nil -} - -func (d *NatsSyncClientDeployer) GetStatefulSetObjects(m *v1.AstraConnector, ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) { - return nil, model.NonMutateFn, nil -} - -func (d *NatsSyncClientDeployer) GetClusterRoleObjects(m *v1.AstraConnector, ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) { - return nil, model.NonMutateFn, nil -} - -func (d *NatsSyncClientDeployer) GetClusterRoleBindingObjects(m *v1.AstraConnector, ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) { - return nil, model.NonMutateFn, nil -} diff --git a/app/deployer/connector/natssync_client_test.go b/app/deployer/connector/natssync_client_test.go deleted file mode 100644 index 3e52f647..00000000 --- a/app/deployer/connector/natssync_client_test.go +++ /dev/null @@ -1,198 +0,0 @@ -package connector_test - -import ( - "context" - "testing" - - "github.com/NetApp-Polaris/astra-connector-operator/app/deployer/connector" - "github.com/NetApp-Polaris/astra-connector-operator/common" - v1 "github.com/NetApp-Polaris/astra-connector-operator/details/operator-sdk/api/v1" - "github.com/stretchr/testify/assert" - appsv1 "k8s.io/api/apps/v1" - corev1 "k8s.io/api/core/v1" - rbacv1 "k8s.io/api/rbac/v1" -) - -func TestNatsSyncGetDeploymentObjects(t *testing.T) { - mockAstraConnector := &v1.AstraConnector{ - Spec: v1.AstraConnectorSpec{ - ImageRegistry: v1.ImageRegistry{ - Name: "test-registry", - Secret: "test-secret", - }, - NatsSyncClient: v1.NatsSyncClient{ - Image: "test-image", - Replicas: 2, - HostAliasIP: "192.168.1.1", - }, - Astra: v1.Astra{ - SkipTLSValidation: true, - }, - }, - } - - deployer := connector.NewNatsSyncClientDeployer() - - objects, _, err := deployer.GetDeploymentObjects(mockAstraConnector, context.Background()) - assert.NoError(t, err) - assert.Len(t, objects, 1) - - deployment, ok := objects[0].(*appsv1.Deployment) - assert.True(t, ok) - - assert.Equal(t, int32(2), *deployment.Spec.Replicas) - assert.Equal(t, "test-registry/test-image", deployment.Spec.Template.Spec.Containers[0].Image) - assert.Equal(t, "192.168.1.1", deployment.Spec.Template.Spec.HostAliases[0].IP) - assert.Equal(t, "test-secret", deployment.Spec.Template.Spec.ImagePullSecrets[0].Name) - assert.Equal(t, 1, len(deployment.Spec.Template.Spec.Containers)) - assert.Equal(t, "natssync-client", deployment.Spec.Template.Spec.Containers[0].Name) - -} - -func TestNatsSyncGetDeploymentObjectsDefault(t *testing.T) { - mockAstraConnector := &v1.AstraConnector{ - Spec: v1.AstraConnectorSpec{ - NatsSyncClient: v1.NatsSyncClient{ - HostAliasIP: "192.168.1.1", - Replicas: 1, - }, - Astra: v1.Astra{ - SkipTLSValidation: false, - }, - }, - } - deployer := connector.NewNatsSyncClientDeployer() - objects, _, err := deployer.GetDeploymentObjects(mockAstraConnector, context.Background()) - assert.NoError(t, err) - assert.Len(t, objects, 1) - - deployment, ok := objects[0].(*appsv1.Deployment) - assert.True(t, ok) - - assert.Equal(t, int32(1), *deployment.Spec.Replicas) - assert.Equal(t, "netappdownloads.jfrog.io/docker-astra-control-staging/arch30/neptune/natssync-client:2.2.202402012115", deployment.Spec.Template.Spec.Containers[0].Image) - assert.Equal(t, "192.168.1.1", deployment.Spec.Template.Spec.HostAliases[0].IP) - assert.Nil(t, deployment.Spec.Template.Spec.ImagePullSecrets) - // TODO add more checks -} - -func TestGetServiceObjects(t *testing.T) { - mockAstraConnector := &v1.AstraConnector{ - Spec: v1.AstraConnectorSpec{ - ImageRegistry: v1.ImageRegistry{ - Name: "test-registry", - Secret: "test-secret", - }, - NatsSyncClient: v1.NatsSyncClient{ - Image: "test-image", - Replicas: 2, - HostAliasIP: "192.168.1.1", - }, - Astra: v1.Astra{ - SkipTLSValidation: true, - }, - }, - } - - deployer := connector.NewNatsSyncClientDeployer() - objects, _, err := deployer.GetServiceObjects(mockAstraConnector, context.Background()) - assert.NoError(t, err) - assert.Len(t, objects, 1) - - service, ok := objects[0].(*corev1.Service) - assert.True(t, ok) - - assert.Equal(t, corev1.ServiceTypeClusterIP, service.Spec.Type) - assert.Equal(t, int32(8080), service.Spec.Ports[0].Port) - assert.Equal(t, corev1.Protocol("TCP"), service.Spec.Ports[0].Protocol) - assert.Equal(t, common.NatsSyncClientName, service.Spec.Selector["app"]) -} - -func TestNatsSyncGetConfigMapObjects(t *testing.T) { - mockAstraConnector := DummyAstraConnector() - deployer := connector.NewNatsSyncClientDeployer() - - objects, _, err := deployer.GetConfigMapObjects(&mockAstraConnector, context.Background()) - assert.NoError(t, err) - assert.Len(t, objects, 1) - - configMap, ok := objects[0].(*corev1.ConfigMap) - assert.True(t, ok) - - assert.Equal(t, common.NatsSyncClientConfigMapName, configMap.Name) -} - -func TestNatsSyncGetRoleObjects(t *testing.T) { - mockAstraConnector := DummyAstraConnector() - deployer := connector.NewNatsSyncClientDeployer() - - objects, _, err := deployer.GetRoleObjects(&mockAstraConnector, context.Background()) - assert.NoError(t, err) - assert.Len(t, objects, 1) - - role, ok := objects[0].(*rbacv1.Role) - assert.True(t, ok) - - assert.Equal(t, common.NatsSyncClientConfigMapRoleName, role.Name) - assert.Len(t, role.Rules, 2) - assert.Equal(t, []string{""}, role.Rules[0].APIGroups) - assert.Equal(t, []string{"configmaps"}, role.Rules[0].Resources) - assert.Equal(t, []string{"get", "list", "patch"}, role.Rules[0].Verbs) -} - -func TestGetRoleBindingObjects(t *testing.T) { - mockAstraConnector := DummyAstraConnector() - deployer := connector.NewNatsSyncClientDeployer() - objects, _, err := deployer.GetRoleBindingObjects(&mockAstraConnector, context.Background()) - assert.NoError(t, err) - - assert.Len(t, objects, 1) - - roleBinding, ok := objects[0].(*rbacv1.RoleBinding) - assert.True(t, ok) - - assert.Equal(t, common.NatsSyncClientConfigMapRoleBindingName, roleBinding.Name) - assert.Len(t, roleBinding.Subjects, 1) - assert.Equal(t, "ServiceAccount", roleBinding.Subjects[0].Kind) - assert.Equal(t, common.NatsSyncClientConfigMapServiceAccountName, roleBinding.Subjects[0].Name) - assert.Equal(t, "Role", roleBinding.RoleRef.Kind) - assert.Equal(t, common.NatsSyncClientConfigMapRoleName, roleBinding.RoleRef.Name) - assert.Equal(t, "rbac.authorization.k8s.io", roleBinding.RoleRef.APIGroup) -} - -func TestNatsSyncGetServiceAccountObjects(t *testing.T) { - // Create a mock AstraConnector object - m := DummyAstraConnector() - deployer := connector.NewNatsSyncClientDeployer() - objects, _, err := deployer.GetServiceAccountObjects(&m, context.Background()) - assert.NoError(t, err) - - assert.Len(t, objects, 1) - serviceAccount, ok := objects[0].(*corev1.ServiceAccount) - assert.True(t, ok) - - assert.Equal(t, common.NatsSyncClientConfigMapServiceAccountName, serviceAccount.Name) -} - -// Below are all the nil objects -func TestNatsSyncK8sObjectsNotCreated(t *testing.T) { - deployer := connector.NewNatsSyncClientDeployer() - ctx := context.Background() - - m := DummyAstraConnector() - - objects, fn, err := deployer.GetStatefulSetObjects(&m, ctx) - assert.Nil(t, objects) - assert.NotNil(t, fn) - assert.Nil(t, err) - - objects, fn, err = deployer.GetClusterRoleObjects(&m, ctx) - assert.Nil(t, objects) - assert.NotNil(t, fn) - assert.Nil(t, err) - - objects, fn, err = deployer.GetClusterRoleBindingObjects(&m, ctx) - assert.Nil(t, objects) - assert.NotNil(t, fn) - assert.Nil(t, err) -} diff --git a/app/deployer/factory.go b/app/deployer/factory.go index 2b9cde76..23abc01b 100644 --- a/app/deployer/factory.go +++ b/app/deployer/factory.go @@ -19,10 +19,6 @@ func Factory( deploymentName string, ) (model.Deployer, error) { switch deploymentName { - case common.NatsName: - return connector.NewNatsDeployer(), nil - case common.NatsSyncClientName: - return connector.NewNatsSyncClientDeployer(), nil case common.AstraConnectName: return connector.NewAstraConnectorDeployer(), nil case common.NeptuneName: diff --git a/app/deployer/model/deployer.go b/app/deployer/model/deployer.go index dfca775b..610b2adc 100644 --- a/app/deployer/model/deployer.go +++ b/app/deployer/model/deployer.go @@ -8,20 +8,22 @@ import ( "context" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - - v1 "github.com/NetApp-Polaris/astra-connector-operator/details/operator-sdk/api/v1" ) type Deployer interface { - GetDeploymentObjects(m *v1.AstraConnector, ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) - GetStatefulSetObjects(m *v1.AstraConnector, ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) - GetServiceObjects(m *v1.AstraConnector, ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) - GetConfigMapObjects(m *v1.AstraConnector, ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) - GetServiceAccountObjects(m *v1.AstraConnector, ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) - GetRoleObjects(m *v1.AstraConnector, ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) - GetClusterRoleObjects(m *v1.AstraConnector, ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) - GetRoleBindingObjects(m *v1.AstraConnector, ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) - GetClusterRoleBindingObjects(m *v1.AstraConnector, ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) + GetDeploymentObjects(ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) + GetStatefulSetObjects(ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) + GetServiceObjects(ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) + GetConfigMapObjects(ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) + GetServiceAccountObjects(ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) + GetRoleObjects(ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) + GetClusterRoleObjects(ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) + GetRoleBindingObjects(ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) + GetClusterRoleBindingObjects(ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) + + // UpdateStatus of CR + UpdateStatus(ctx context.Context, status string, statusWriter client.StatusWriter) error + IsSpecModified(ctx context.Context, k8sClient client.Client) bool } // Define the MutateFn function diff --git a/app/deployer/neptune/neptuneV2.go b/app/deployer/neptune/neptuneV2.go index d9bc04c3..ba8741ab 100644 --- a/app/deployer/neptune/neptuneV2.go +++ b/app/deployer/neptune/neptuneV2.go @@ -9,8 +9,10 @@ import ( _ "embed" "encoding/json" "fmt" + "github.com/NetApp-Polaris/astra-connector-operator/api/v1" "log" "maps" + "reflect" "strings" appsv1 "k8s.io/api/apps/v1" @@ -26,30 +28,63 @@ import ( "github.com/NetApp-Polaris/astra-connector-operator/app/conf" "github.com/NetApp-Polaris/astra-connector-operator/app/deployer/model" "github.com/NetApp-Polaris/astra-connector-operator/common" - v1 "github.com/NetApp-Polaris/astra-connector-operator/details/operator-sdk/api/v1" ) -type NeptuneClientDeployerV2 struct{} +type NeptuneClientDeployerV2 struct { + neptuneCR *v1.AstraNeptune +} + +func NewNeptuneClientDeployerV2(neptune *v1.AstraNeptune) model.Deployer { + return &NeptuneClientDeployerV2{neptuneCR: neptune} +} + +func (n *NeptuneClientDeployerV2) UpdateStatus(ctx context.Context, status string, statusWriter client.StatusWriter) error { + n.neptuneCR.Status.Status = status + err := statusWriter.Update(ctx, n.neptuneCR) + if err != nil { + return err + } + return nil +} -func NewNeptuneClientDeployerV2() model.Deployer { - return &NeptuneClientDeployerV2{} +func (n *NeptuneClientDeployerV2) IsSpecModified(ctx context.Context, k8sClient client.Client) bool { + log := ctrllog.FromContext(ctx) + // Fetch the AstraNeptune instance + controllerKey := client.ObjectKeyFromObject(n.neptuneCR) + updatedAstraNeptune := &v1.AstraNeptune{} + err := k8sClient.Get(ctx, controllerKey, updatedAstraNeptune) + if err != nil { + log.Info("AstraNeptune resource not found. Ignoring since object must be deleted") + return true + } + + if updatedAstraNeptune.GetDeletionTimestamp() != nil { + log.Info("AstraNeptune marked for deletion, reconciler requeue") + return true + } + + if !reflect.DeepEqual(updatedAstraNeptune.Spec, n.neptuneCR.Spec) { + log.Info("AstraNeptune spec change, reconciler requeue") + return true + } + return false } -func (n NeptuneClientDeployerV2) GetDeploymentObjects(m *v1.AstraConnector, ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) { +func (n *NeptuneClientDeployerV2) GetDeploymentObjects(ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) { var deps []client.Object log := ctrllog.FromContext(ctx) var imageRegistry string var containerImage string var neptuneImage string - if m.Spec.ImageRegistry.Name != "" { - imageRegistry = m.Spec.ImageRegistry.Name + if n.neptuneCR.Spec.ImageRegistry.Name != "" { + imageRegistry = n.neptuneCR.Spec.ImageRegistry.Name } else { imageRegistry = common.DefaultImageRegistry } - if m.Spec.Neptune.Image != "" { - containerImage = m.Spec.Neptune.Image + if n.neptuneCR.Spec.Image != "" { + containerImage = n.neptuneCR.Spec.Image } else { containerImage = common.NeptuneImageTag } @@ -68,19 +103,19 @@ func (n NeptuneClientDeployerV2) GetDeploymentObjects(m *v1.AstraConnector, ctx "control-plane": "controller-manager", } // add any labels user wants to use or override - maps.Copy(deploymentLabels, m.Spec.Labels) + maps.Copy(deploymentLabels, n.neptuneCR.Spec.Labels) podLabels := map[string]string{ "control-plane": "controller-manager", "app": "controller.neptune.netapp.io", } - maps.Copy(podLabels, m.Spec.Labels) + maps.Copy(podLabels, n.neptuneCR.Spec.Labels) neptuneReplicas := int32(common.NeptuneReplicas) deployment := &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{ Name: common.NeptuneName, - Namespace: m.Namespace, + Namespace: n.neptuneCR.Namespace, Labels: deploymentLabels, Annotations: map[string]string{ "container.seccomp.security.alpha.kubernetes.io/pod": "runtime/default", @@ -172,7 +207,11 @@ func (n NeptuneClientDeployerV2) GetDeploymentObjects(m *v1.AstraConnector, ctx "/manager", }, Image: neptuneImage, - Env: getNeptuneEnvVars(imageRegistry, containerImage, m.Spec.ImageRegistry.Secret, m.Spec.AutoSupport.URL, m.Spec.Labels), + Env: getNeptuneEnvVars(imageRegistry, + containerImage, + n.neptuneCR.Spec.ImageRegistry.Secret, + n.neptuneCR.Spec.AutoSupport.URL, + n.neptuneCR.Spec.Labels), LivenessProbe: &corev1.Probe{ ProbeHandler: corev1.ProbeHandler{ HTTPGet: &corev1.HTTPGetAction{ @@ -218,10 +257,10 @@ func (n NeptuneClientDeployerV2) GetDeploymentObjects(m *v1.AstraConnector, ctx }, } - if m.Spec.ImageRegistry.Secret != "" { + if n.neptuneCR.Spec.ImageRegistry.Secret != "" { deployment.Spec.Template.Spec.ImagePullSecrets = []corev1.LocalObjectReference{ { - Name: m.Spec.ImageRegistry.Secret, + Name: n.neptuneCR.Spec.ImageRegistry.Secret, }, } } @@ -236,7 +275,7 @@ func (n NeptuneClientDeployerV2) GetDeploymentObjects(m *v1.AstraConnector, ctx if container.Name == "manager" { for j, envVar := range container.Env { if envVar.Name == "NEPTUNE_AUTOSUPPORT_URL" { - containers[i].Env[j].Value = m.Spec.AutoSupport.URL + containers[i].Env[j].Value = n.neptuneCR.Spec.AutoSupport.URL } } } @@ -320,17 +359,17 @@ func getNeptuneEnvVars(imageRegistry, containerImage, pullSecret, asupUrl string return envVars } -func (n NeptuneClientDeployerV2) GetStatefulSetObjects(m *v1.AstraConnector, ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) { +func (n *NeptuneClientDeployerV2) GetStatefulSetObjects(ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) { return nil, model.NonMutateFn, nil } -func (n NeptuneClientDeployerV2) GetServiceObjects(m *v1.AstraConnector, ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) { +func (n *NeptuneClientDeployerV2) GetServiceObjects(ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) { var services []client.Object service := &corev1.Service{ ObjectMeta: metav1.ObjectMeta{ Name: "neptune-controller-manager-metrics-service", - Namespace: m.Namespace, + Namespace: n.neptuneCR.Namespace, Labels: map[string]string{ "app.kubernetes.io/component": "kube-rbac-proxy", "app.kubernetes.io/created-by": "neptune", @@ -362,32 +401,32 @@ func (n NeptuneClientDeployerV2) GetServiceObjects(m *v1.AstraConnector, ctx con return services, model.NonMutateFn, nil } -func (n NeptuneClientDeployerV2) GetConfigMapObjects(m *v1.AstraConnector, ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) { +func (n *NeptuneClientDeployerV2) GetConfigMapObjects(ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) { return nil, model.NonMutateFn, nil } -func (n NeptuneClientDeployerV2) GetServiceAccountObjects(m *v1.AstraConnector, ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) { +func (n *NeptuneClientDeployerV2) GetServiceAccountObjects(ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) { sa := &corev1.ServiceAccount{ ObjectMeta: metav1.ObjectMeta{ Name: common.NeptuneName, - Namespace: m.Namespace, + Namespace: n.neptuneCR.Namespace, }, } return []client.Object{sa}, model.NonMutateFn, nil } -func (n NeptuneClientDeployerV2) GetRoleObjects(m *v1.AstraConnector, ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) { +func (n *NeptuneClientDeployerV2) GetRoleObjects(ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) { return nil, model.NonMutateFn, nil } -func (n NeptuneClientDeployerV2) GetClusterRoleObjects(m *v1.AstraConnector, ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) { +func (n *NeptuneClientDeployerV2) GetClusterRoleObjects(ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) { return nil, model.NonMutateFn, nil } -func (n NeptuneClientDeployerV2) GetRoleBindingObjects(m *v1.AstraConnector, ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) { +func (n *NeptuneClientDeployerV2) GetRoleBindingObjects(ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) { return nil, model.NonMutateFn, nil } -func (n NeptuneClientDeployerV2) GetClusterRoleBindingObjects(m *v1.AstraConnector, ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) { +func (n *NeptuneClientDeployerV2) GetClusterRoleBindingObjects(ctx context.Context) ([]client.Object, controllerutil.MutateFn, error) { return nil, model.NonMutateFn, nil } diff --git a/app/deployer/neptune/neptune_test.go b/app/deployer/neptune/neptune_test.go index 052f8a66..3268e3be 100644 --- a/app/deployer/neptune/neptune_test.go +++ b/app/deployer/neptune/neptune_test.go @@ -2,6 +2,7 @@ package neptune_test import ( "context" + "github.com/NetApp-Polaris/astra-connector-operator/api/v1" "github.com/NetApp-Polaris/astra-connector-operator/common" corev1 "k8s.io/api/core/v1" "testing" @@ -12,7 +13,6 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/NetApp-Polaris/astra-connector-operator/app/deployer/neptune" - v1 "github.com/NetApp-Polaris/astra-connector-operator/details/operator-sdk/api/v1" ) func createNeptuneDeployerV2() (neptune.NeptuneClientDeployerV2, *v1.AstraConnector, context.Context) { // Create a new NeptuneClientDeployer instance diff --git a/app/register/register.go b/app/register/register.go deleted file mode 100644 index b75d1896..00000000 --- a/app/register/register.go +++ /dev/null @@ -1,1142 +0,0 @@ -/* - * Copyright (c) 2024. NetApp, Inc. All Rights Reserved. - */ - -package register - -import ( - "bytes" - "context" - "crypto/tls" - "encoding/base64" - "encoding/json" - "fmt" - "io" - "math" - "net" - "net/http" - "strconv" - "strings" - "time" - - "github.com/go-logr/logr" - "github.com/pkg/errors" - coreV1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/client" - - "github.com/NetApp-Polaris/astra-connector-operator/common" - "github.com/NetApp-Polaris/astra-connector-operator/details/k8s" - v1 "github.com/NetApp-Polaris/astra-connector-operator/details/operator-sdk/api/v1" -) - -const ( - errorRetrySleep = time.Second * 3 - clusterUnManagedState = "unmanaged" - clusterManagedState = "managed" - getClusterPollCount = 5 - connectorInstalled = "installed" - connectorInstallPending = "pending" -) - -// HTTPClient interface used for request and to facilitate testing -type HTTPClient interface { - Do(req *http.Request) (*http.Response, error) -} - -// HeaderMap User specific details required for the http header -type HeaderMap struct { - AccountId string - Authorization string -} - -// DoRequest Makes http request with the given parameters -func DoRequest(ctx context.Context, client HTTPClient, method, url string, bodyBytes []byte, headerMap HeaderMap, log logr.Logger, retryCount ...int) (*http.Response, error, context.CancelFunc) { - // Default retry count - retries := 1 - if len(retryCount) > 0 { - retries = retryCount[0] - } - - var httpResponse *http.Response - var err error - var cancel context.CancelFunc - - for i := 0; i < retries; i++ { - - sleepTimeout := time.Duration(math.Pow(2, float64(i))) * time.Second - log.Info(fmt.Sprintf("Retry %d, waiting for %v before next retry\n", i, sleepTimeout)) - - // Child context that can't exceed a deadline specified - var childCtx context.Context - childCtx, cancel = context.WithTimeout(ctx, 3*time.Minute) - - req, _ := http.NewRequestWithContext(childCtx, method, url, bytes.NewReader(bodyBytes)) - - req.Header.Add("Content-Type", "application/json") - - if headerMap.Authorization != "" { - req.Header.Add("authorization", headerMap.Authorization) - } - - httpResponse, err = client.Do(req) - if err == nil && httpResponse.StatusCode >= 200 && httpResponse.StatusCode < 300 { - log.Info("Request successful") - break - } - - if err != nil { - log.Info(fmt.Sprintf("Request failed with error: %v\n", err)) - } else { - log.Info(fmt.Sprintf("Request failed with error: %v\n", httpResponse.Status)) - } - - // If the request failed or the server returned a non-2xx status code, wait before retrying - time.Sleep(sleepTimeout) - } - - return httpResponse, err, cancel -} - -type ClusterRegisterUtil interface { - GetConnectorIDFromConfigMap(cmData map[string]string) (string, error) - GetNatsSyncClientRegistrationURL() string - GetNatsSyncClientUnregisterURL() string - RegisterNatsSyncClient() (string, string, error) - UnRegisterNatsSyncClient() error - GetAPITokenFromSecret(secretName string) (string, string, error) - RegisterClusterWithAstra(astraConnectorId, clusterId string) (string, string, error) - CloudExists(astraHost, cloudID, apiToken string) bool - ListClouds(astraHost, apiToken string) (*http.Response, error) - GetCloudId(astraHost, cloudType, apiToken string, retryTimeout ...time.Duration) (string, string, error) - CreateCloud(astraHost, cloudType, apiToken string) (string, string, error) - GetOrCreateCloud(astraHost, cloudType, apiToken string) (string, string, error) - GetClusters(astraHost, cloudId, apiToken string) (GetClustersResponse, string, error) - GetCluster(astraHost, cloudId, clusterId, apiToken string) (Cluster, string, error) - CreateCluster(astraHost, cloudId, astraConnectorId, apiToken string) (ClusterInfo, string, error) - UpdateCluster(astraHost, cloudId, clusterId, astraConnectorId, apiToken string) (string, error) - CreateOrUpdateCluster(astraHost, cloudId, clusterId, astraConnectorId, connectorInstall, clustersMethod, apiToken string) (ClusterInfo, string, error) - CreateManagedCluster(astraHost, cloudId, clusterID, connectorInstall, apiToken string) (string, error) - UpdateManagedCluster(astraHost, clusterId, astraConnectorId, connectorInstall, apiToken string) (string, error) - CreateOrUpdateManagedCluster(astraHost, cloudId, clusterId, astraConnectorId, managedClustersMethod, apiToken string) (ClusterInfo, string, error) - ValidateAndGetCluster(astraHost, cloudId, apiToken, clusterId string) (ClusterInfo, string, error) - UnmanageCluster(clusterID string) error -} - -type clusterRegisterUtil struct { - AstraConnector *v1.AstraConnector - Client HTTPClient - K8sClient client.Client - K8sUtil k8s.K8sUtilInterface - Ctx context.Context - Log logr.Logger -} - -func NewClusterRegisterUtil(astraConnector *v1.AstraConnector, client HTTPClient, k8sClient client.Client, k8sUtil k8s.K8sUtilInterface, log logr.Logger, ctx context.Context) ClusterRegisterUtil { - return &clusterRegisterUtil{ - AstraConnector: astraConnector, - Client: client, - K8sClient: k8sClient, - K8sUtil: k8sUtil, - Log: log, - Ctx: ctx, - } -} - -// ****************************** -// FUNCTIONS TO REGISTER NATS -// ****************************** - -type AstraConnector struct { - Id string `json:"locationID"` -} - -// GetConnectorIDFromConfigMap Returns already registered ConnectorId -func (c clusterRegisterUtil) GetConnectorIDFromConfigMap(cmData map[string]string) (string, error) { - var serviceKeyDataString string - var serviceKeyData map[string]interface{} - for key := range cmData { - if key == "cloud-master_locationData.json" { - continue - } - serviceKeyDataString = cmData[key] - if err := json.Unmarshal([]byte(serviceKeyDataString), &serviceKeyData); err != nil { - return "", err - } - } - return serviceKeyData["locationID"].(string), nil -} - -// GetNatsSyncClientRegistrationURL Returns NatsSyncClient Registration URL -func (c clusterRegisterUtil) GetNatsSyncClientRegistrationURL() string { - natsSyncClientURL := fmt.Sprintf("http://%s.%s:%d/bridge-client/1", common.NatsSyncClientName, c.AstraConnector.Namespace, common.NatsSyncClientPort) - natsSyncClientRegisterURL := fmt.Sprintf("%s/register", natsSyncClientURL) - return natsSyncClientRegisterURL -} - -// GetNatsSyncClientUnregisterURL returns NatsSyncClient Unregister URL -func (c clusterRegisterUtil) GetNatsSyncClientUnregisterURL() string { - natsSyncClientURL := fmt.Sprintf("http://%s.%s:%d/bridge-client/1", common.NatsSyncClientName, c.AstraConnector.Namespace, common.NatsSyncClientPort) - natsSyncClientRegisterURL := fmt.Sprintf("%s/unregister", natsSyncClientURL) - return natsSyncClientRegisterURL -} - -// generateAuthPayload Returns the payload for authentication -func (c clusterRegisterUtil) generateAuthPayload() ([]byte, string, error) { - apiToken, errorReason, err := c.GetAPITokenFromSecret(c.AstraConnector.Spec.Astra.TokenRef) - if err != nil { - return nil, errorReason, err - } - - authPayload, err := json.Marshal(map[string]string{ - "userToken": apiToken, - "accountId": c.AstraConnector.Spec.Astra.AccountId, - }) - - if err != nil { - return nil, "Failed to marshal auth payload", err - } - - reqBodyBytes, err := json.Marshal(map[string]string{"authToken": base64.StdEncoding.EncodeToString(authPayload)}) - if err != nil { - return nil, "Failed to marshal auth token", err - } - - return reqBodyBytes, "", nil -} - -// UnRegisterNatsSyncClient Unregisters NatsSyncClient -func (c clusterRegisterUtil) UnRegisterNatsSyncClient() error { - natsSyncClientUnregisterURL := c.GetNatsSyncClientUnregisterURL() - reqBodyBytes, _, err := c.generateAuthPayload() - if err != nil { - return err - } - - response, err, cancel := DoRequest(c.Ctx, c.Client, http.MethodPost, natsSyncClientUnregisterURL, reqBodyBytes, HeaderMap{}, c.Log) - defer cancel() - - if err != nil { - if response != nil { - return errors.New(CreateErrorMsg("UnRegisterNatsSyncClient", "make POST call", natsSyncClientUnregisterURL, response.Status, "", err)) - } - return errors.New(CreateErrorMsg("UnRegisterNatsSyncClient", "make POST call", natsSyncClientUnregisterURL, "", "", err)) - } - - if response.StatusCode != http.StatusNoContent { - bodyBytes, err := io.ReadAll(response.Body) - if err != nil { - return errors.New(CreateErrorMsg("UnRegisterNatsSyncClient", "read response", natsSyncClientUnregisterURL, response.Status, "", err)) - } - return errors.New(CreateErrorMsg("UnRegisterNatsSyncClient", "make POST call", natsSyncClientUnregisterURL, response.Status, string(bodyBytes), errors.New("Unexpected unregistration status"))) - } - - return nil -} - -// RegisterNatsSyncClient Registers NatsSyncClient with NatsSyncServer -func (c clusterRegisterUtil) RegisterNatsSyncClient() (string, string, error) { - natsSyncClientRegisterURL := c.GetNatsSyncClientRegistrationURL() - reqBodyBytes, errorReason, err := c.generateAuthPayload() - if err != nil { - return "", errorReason, err - } - - response, err, cancel := DoRequest(c.Ctx, c.Client, http.MethodPost, natsSyncClientRegisterURL, reqBodyBytes, HeaderMap{}, c.Log, 3) - defer cancel() - if err != nil { - if response != nil { - return "", CreateErrorMsg("RegisterNatsSyncClient", "make POST call", natsSyncClientRegisterURL, response.Status, "", err), err - } - return "", CreateErrorMsg("RegisterNatsSyncClient", "make POST call", natsSyncClientRegisterURL, "", "", err), err - } - - c.Log.Info(fmt.Sprintf("response %v, %v, %v", response.Body, response.Status, response.StatusCode)) - - if response.StatusCode != http.StatusCreated { - bodyBytes, err := io.ReadAll(response.Body) - if err != nil { - return "", CreateErrorMsg("RegisterNatsSyncClient", "read response from POST call", natsSyncClientRegisterURL, response.Status, "", err), err - } - errorMsg := CreateErrorMsg("RegisterNatsSyncClient", "make POST call", natsSyncClientRegisterURL, response.Status, string(bodyBytes), errors.New("Unexpected registration status")) - return "", errorMsg, errors.New(errorMsg) - } - - astraConnector := &AstraConnector{} - err = json.NewDecoder(response.Body).Decode(astraConnector) - if err != nil { - return "", CreateErrorMsg("RegisterNatsSyncClient", "decode response", natsSyncClientRegisterURL, response.Status, "", err), err - } - - return astraConnector.Id, "", nil -} - -// ************************************************ -// FUNCTIONS TO REGISTER CLUSTER WITH ASTRA -// ************************************************ - -func GetAstraHostURL(astraConnector *v1.AstraConnector) string { - var astraHost string - if astraConnector.Spec.NatsSyncClient.CloudBridgeURL != "" { - astraHost = astraConnector.Spec.NatsSyncClient.CloudBridgeURL - astraHost = strings.TrimSuffix(astraHost, "/") - } else { - astraHost = common.NatsSyncClientDefaultCloudBridgeURL - } - - return astraHost -} - -func (c clusterRegisterUtil) getAstraHostFromURL(astraHostURL string) (string, error) { - cloudBridgeURLSplit := strings.Split(astraHostURL, "://") - if len(cloudBridgeURLSplit) != 2 { - errStr := fmt.Sprintf("invalid cloudBridgeURL provided: %s, format - https://hostname", astraHostURL) - return "", errors.New(errStr) - } - return cloudBridgeURLSplit[1], nil -} - -func (c clusterRegisterUtil) logHttpError(response *http.Response) { - bodyBytes, err := io.ReadAll(response.Body) - if err != nil { - c.Log.Error(err, "Error reading response body") - } else { - c.Log.Info("Received unexpected status", "responseBody", string(bodyBytes), "status", response.Status) - err = response.Body.Close() - if err != nil { - c.Log.Error(err, "Error closing the response body") - } - } -} - -func (c clusterRegisterUtil) readResponseBody(response *http.Response) ([]byte, error) { - bodyBytes, err := io.ReadAll(response.Body) - if err != nil { - return nil, err - } - return bodyBytes, nil -} - -func (c clusterRegisterUtil) setHttpClient(disableTls bool, astraHost string) error { - if disableTls { - http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true} - c.Log.WithValues("disableTls", disableTls).Info("TLS Validation Disabled! Not for use in production!") - } - - if c.AstraConnector.Spec.NatsSyncClient.HostAliasIP != "" { - c.Log.WithValues("HostAliasIP", c.AstraConnector.Spec.NatsSyncClient.HostAliasIP).Info("Using the HostAlias IP") - cloudBridgeHost, err := c.getAstraHostFromURL(astraHost) - if err != nil { - return err - } - - dialer := &net.Dialer{ - Timeout: 30 * time.Second, - KeepAlive: 30 * time.Second, - } - - http.DefaultTransport.(*http.Transport).DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) { - if addr == cloudBridgeHost+":443" { - addr = c.AstraConnector.Spec.NatsSyncClient.HostAliasIP + ":443" - } - if addr == cloudBridgeHost+":80" { - addr = c.AstraConnector.Spec.NatsSyncClient.HostAliasIP + ":80" - } - return dialer.DialContext(ctx, network, addr) - } - } - - c.Client = &http.Client{} - return nil -} - -func (c clusterRegisterUtil) CloudExists(astraHost, cloudID, apiToken string) bool { - url := fmt.Sprintf("%s/accounts/%s/topology/v1/clouds/%s", astraHost, c.AstraConnector.Spec.Astra.AccountId, cloudID) - - headerMap := HeaderMap{Authorization: fmt.Sprintf("Bearer %s", apiToken)} - response, err, cancel := DoRequest(c.Ctx, c.Client, http.MethodGet, url, nil, headerMap, c.Log) - defer cancel() - - if err != nil { - c.Log.Error(err, "Error getting Cloud: "+cloudID) - return false - } - - if response.StatusCode == http.StatusNotFound { - c.Log.Info("Cloud Not Found: " + cloudID) - return false - } - - if response.StatusCode != http.StatusOK { - msg := fmt.Sprintf("Get Clouds call returned with status: %s", response.Status) - c.Log.Error(errors.New("Invalid Status"), msg) - return false - } - - c.Log.Info("Cloud Found: " + cloudID) - return true -} - -func (c clusterRegisterUtil) ListClouds(astraHost, apiToken string) (*http.Response, error) { - url := fmt.Sprintf("%s/accounts/%s/topology/v1/clouds", astraHost, c.AstraConnector.Spec.Astra.AccountId) - - c.Log.Info("Getting clouds") - headerMap := HeaderMap{Authorization: fmt.Sprintf("Bearer %s", apiToken)} - response, err, cancel := DoRequest(c.Ctx, c.Client, http.MethodGet, url, nil, headerMap, c.Log) - defer cancel() - - if err != nil { - return nil, err - } - - return response, nil -} - -func (c clusterRegisterUtil) GetCloudId(astraHost, cloudType, apiToken string, retryTimeout ...time.Duration) (string, string, error) { - // TODO: This function assumes that only ONE cloud instance of a given cloud type would be present in the persistence. - // TODO: If we ever choose to support multiple cloud instances of type "private" this function wouldn't support that and an enhancement would be needed. - - success := false - var response *http.Response - timeout := time.Second * 30 - if len(retryTimeout) > 0 { - timeout = retryTimeout[0] - } - timeExpire := time.Now().Add(timeout) - - for time.Now().Before(timeExpire) { - var err error - response, err = c.ListClouds(astraHost, apiToken) - if err != nil { - c.Log.Error(err, "Error listing clouds") - time.Sleep(errorRetrySleep) - continue - } - - if response.StatusCode == 200 { - success = true - break - } - - c.logHttpError(response) - _ = response.Body.Close() - time.Sleep(errorRetrySleep) - } - - if !success { - return "", "Failed to Get Clouds", fmt.Errorf("timed out querying Astra API") - } - - defer func(Body io.ReadCloser) { - _ = Body.Close() - }(response.Body) - - type respData struct { - Items []struct { - CloudType string `json:"cloudType"` - Id string `json:"id"` - } `json:"items"` - } - - bodyBytes, err := c.readResponseBody(response) - if err != nil { - return "", "Failed to read response from Get Clouds", err - } - resp := respData{} - err = json.Unmarshal(bodyBytes, &resp) - if err != nil { - return "", "Failed to unmarshal response from Get Clouds", err - } - - var cloudId string - for _, cloudInfo := range resp.Items { - if cloudInfo.CloudType == cloudType { - cloudId = cloudInfo.Id - break - } - } - - return cloudId, "", nil -} - -func (c clusterRegisterUtil) CreateCloud(astraHost, cloudType, apiToken string) (string, string, error) { - url := fmt.Sprintf("%s/accounts/%s/topology/v1/clouds", astraHost, c.AstraConnector.Spec.Astra.AccountId) - payLoad := map[string]string{ - "type": "application/astra-cloud", - "version": "1.0", - "name": common.AstraPrivateCloudName, - "cloudType": cloudType, - } - - reqBodyBytes, err := json.Marshal(payLoad) - if err != nil { - return "", fmt.Sprintf("Failed to marshal request body payload for POST %v", url), err - } - - c.Log.WithValues("cloudType", cloudType).Info("Creating cloud") - headerMap := HeaderMap{Authorization: fmt.Sprintf("Bearer %s", apiToken)} - response, err, cancel := DoRequest(c.Ctx, c.Client, http.MethodPost, url, reqBodyBytes, headerMap, c.Log) - defer cancel() - - if err != nil { - if response != nil { - return "", CreateErrorMsg("CreateCloud", "make POST call", url, response.Status, "", err), err - } - return "", CreateErrorMsg("CreateCloud", "make POST call", url, "", "", err), err - } - - type CloudResp struct { - ID string `json:"id"` - Name string `json:"name"` - } - - respBody, err := c.readResponseBody(response) - if err != nil { - return "", CreateErrorMsg("CreateCloud", "read response from POST call", url, response.Status, "", err), err - } - - cloudResp := &CloudResp{} - err = json.Unmarshal(respBody, &cloudResp) - if err != nil { - return "", CreateErrorMsg("CreateCloud", "unmarshal response from POST call", url, response.Status, string(respBody), err), err - } - - if cloudResp.ID == "" { - c.Log.WithValues("response", string(respBody)).Error(errors.New("got empty cloud id"), "invalid response") - } - - return cloudResp.ID, "", nil -} - -func (c clusterRegisterUtil) GetOrCreateCloud(astraHost, cloudType, apiToken string) (string, string, error) { - // If a cloudId is specified in the CR Spec, validate its existence. - // If the provided cloudId is valid, return the same. - // If it is not a valid cloudId i.e., provided cloudId doesn't exist in the DB, return an error - cloudId := c.AstraConnector.Spec.Astra.CloudId - if cloudId != "" { - c.Log.WithValues("cloudID", cloudId).Info("Validating the provided CloudId") - if !c.CloudExists(astraHost, cloudId, apiToken) { - errMsg := fmt.Sprintf("Invalid CloudId %v provided in the Spec", cloudId) - return "", errMsg, errors.New(errMsg) - } - - c.Log.WithValues("cloudID", cloudId).Info("CloudId exists in the system") - return cloudId, "", nil - } - - // When a cloudId is not specified in the CR Spec, check if a cloud of type "private" - // exists in the system. If it exists, return the CloudId of the "private" cloud. - // Otherwise, proceed to create a cloud of type "private" and the return the CloudId - // of the newly created cloud. - c.Log.WithValues("cloudType", cloudType).Info("Fetching Cloud Id") - - cloudId, errorReason, err := c.GetCloudId(astraHost, cloudType, apiToken) - if err != nil { - c.Log.Error(err, "Error fetching cloud ID") - return "", errorReason, err - } - - if cloudId == "" { - c.Log.Info("Cloud doesn't seem to exist, creating the cloud", "cloudType", cloudType) - cloudId, errorReason, err = c.CreateCloud(astraHost, cloudType, apiToken) - if err != nil { - c.Log.Error(err, "Failed to create cloud", "cloudType", cloudType) - return "", errorReason, err - } - if cloudId == "" { - return "", "Got empty Cloud Id from POST call to clouds", fmt.Errorf("could not create cloud of type %s", cloudType) - } - } - - c.Log.WithValues("cloudID", cloudId).Info("Found/Created Cloud") - - return cloudId, "", nil -} - -type Cluster struct { - Type string `json:"type,omitempty"` - Version string `json:"version,omitempty"` - ID string `json:"id,omitempty"` - Name string `json:"name,omitempty"` - ManagedState string `json:"managedState,omitempty"` - ClusterType string `json:"clusterType,omitempty"` - CloudID string `json:"cloudID,omitempty"` - PrivateRouteID string `json:"privateRouteID,omitempty"` - ConnectorCapabilities []string `json:"connectorCapabilities,omitempty"` - ConnectorInstall string `json:"connectorInstall,omitempty"` - TridentManagedStateDesired string `json:"tridentManagedStateDesired,omitempty"` - ApiServiceID string `json:"apiServiceID,omitempty"` -} - -type GetClustersResponse struct { - Items []Cluster `json:"items"` -} - -type ClusterInfo struct { - ID string - Name string - ManagedState string - ConnectorInstall string -} - -// GetClusters Returns a list of existing clusters -func (c clusterRegisterUtil) GetClusters(astraHost, cloudId, apiToken string) (GetClustersResponse, string, error) { - url := fmt.Sprintf("%s/accounts/%s/topology/v1/clouds/%s/clusters", astraHost, c.AstraConnector.Spec.Astra.AccountId, cloudId) - var clustersRespJson GetClustersResponse - - c.Log.Info("Getting Clusters") - - headerMap := HeaderMap{Authorization: fmt.Sprintf("Bearer %s", apiToken)} - response, err, cancel := DoRequest(c.Ctx, c.Client, http.MethodGet, url, nil, headerMap, c.Log) - defer cancel() - - if err != nil { - if response != nil { - return clustersRespJson, CreateErrorMsg("GetClusters", "make GET call", url, response.Status, "", err), err - } - return clustersRespJson, CreateErrorMsg("GetClusters", "make GET call", url, "", "", err), err - } - - if response.StatusCode != http.StatusOK { - errorMsg := CreateErrorMsg("GetClusters", "make GET call", url, response.Status, "", err) - return clustersRespJson, errorMsg, errors.New(errorMsg) - } - - respBody, err := io.ReadAll(response.Body) - if err != nil { - return clustersRespJson, CreateErrorMsg("GetClusters", "read response from GET call", url, response.Status, string(respBody), err), err - } - - err = json.Unmarshal(respBody, &clustersRespJson) - if err != nil { - return clustersRespJson, CreateErrorMsg("GetClusters", "unmarshal response from GET call", url, response.Status, string(respBody), err), err - } - - return clustersRespJson, "", nil -} - -// pollForClusterToBeInDesiredState Polls until a given cluster is in desired state (or until timeout) -func (c clusterRegisterUtil) pollForClusterToBeInDesiredState(astraHost, cloudId, clusterId, desiredState, apiToken string) error { - for i := 1; i <= getClusterPollCount; i++ { - time.Sleep(15 * time.Second) - getCluster, errorMsg, getClusterErr := c.GetCluster(astraHost, cloudId, clusterId, apiToken) - - if getClusterErr != nil { - return errors.New(errorMsg) - } - - if getCluster.ManagedState == desiredState { - return nil - } - } - return errors.New(CreateErrorMsg("pollForClusterToBeInDesiredState", "check cluster state", astraHost, "", "", errors.New("cluster state not changed to desired state: "+clusterId))) -} - -// GetCluster Returns the details of the given clusterID (if it exists) -func (c clusterRegisterUtil) GetCluster(astraHost, cloudId, clusterId, apiToken string) (Cluster, string, error) { - url := fmt.Sprintf("%s/accounts/%s/topology/v1/clouds/%s/clusters/%s", astraHost, c.AstraConnector.Spec.Astra.AccountId, cloudId, clusterId) - var clustersRespJson Cluster - - headerMap := HeaderMap{Authorization: fmt.Sprintf("Bearer %s", apiToken)} - response, err, cancel := DoRequest(c.Ctx, c.Client, http.MethodGet, url, nil, headerMap, c.Log) - defer cancel() - - if err != nil { - if response != nil { - return Cluster{}, CreateErrorMsg("GetCluster", "make GET call", url, response.Status, "", err), err - } - return Cluster{}, CreateErrorMsg("GetCluster", "make GET call", url, "", "", err), err - } - - if response.StatusCode != http.StatusOK { - errorMsg := CreateErrorMsg("GetCluster", "make GET call", url, response.Status, "", nil) - return clustersRespJson, errorMsg, errors.New(errorMsg) - } - - respBody, err := io.ReadAll(response.Body) - if err != nil { - return clustersRespJson, CreateErrorMsg("GetCluster", "read response from GET call", url, response.Status, string(respBody), err), err - } - - err = json.Unmarshal(respBody, &clustersRespJson) - if err != nil { - return Cluster{}, CreateErrorMsg("GetCluster", "unmarshal response from GET call", url, response.Status, string(respBody), err), err - } - - return clustersRespJson, "", nil -} - -// CreateCluster Creates a cluster with the provided details -func (c clusterRegisterUtil) CreateCluster(astraHost, cloudId, astraConnectorId, apiToken string) (ClusterInfo, string, error) { - url := fmt.Sprintf("%s/accounts/%s/topology/v1/clouds/%s/clusters", astraHost, c.AstraConnector.Spec.Astra.AccountId, cloudId) - var clustersRespJson Cluster - - clusterTypeChecker := k8s.ClusterTypeChecker{K8sUtil: c.K8sUtil, Log: c.Log} - clusterType := clusterTypeChecker.DetermineClusterType() - - clustersBody := Cluster{ - Type: "application/astra-cluster", - Version: common.AstraClustersAPIVersion, - Name: c.AstraConnector.Spec.Astra.ClusterName, - ConnectorCapabilities: common.GetConnectorCapabilities(), - PrivateRouteID: astraConnectorId, - ConnectorInstall: connectorInstallPending, - ClusterType: clusterType, - } - - clustersBodyJson, _ := json.Marshal(clustersBody) - headerMap := HeaderMap{Authorization: fmt.Sprintf("Bearer %s", apiToken)} - clustersResp, err, cancel := DoRequest(c.Ctx, c.Client, http.MethodPost, url, clustersBodyJson, headerMap, c.Log, 3) - defer cancel() - - if err != nil { - errorMsg := "" - if clustersResp != nil { - errorMsg = CreateErrorMsg("CreateCluster", "make POST call", url, clustersResp.Status, "", err) - } else { - errorMsg = CreateErrorMsg("CreateCluster", "make POST call", url, "", "", err) - } - - return ClusterInfo{}, errorMsg, fmt.Errorf("%s: %w", errorMsg, err) - } - - respBody, err := io.ReadAll(clustersResp.Body) - if err != nil { - errorMsg := CreateErrorMsg("CreateCluster", "read response from POST call", url, clustersResp.Status, "", err) - return ClusterInfo{}, errorMsg, fmt.Errorf("%s", errorMsg) - } - - if clustersResp.StatusCode != http.StatusCreated { - errorMsg := CreateErrorMsg("CreateCluster", "make POST call", url, clustersResp.Status, string(respBody), nil) - return ClusterInfo{}, errorMsg, fmt.Errorf("%s", errorMsg) - } - - err = json.Unmarshal(respBody, &clustersRespJson) - if err != nil { - errorMsg := CreateErrorMsg("CreateCluster", "unmarshal response from POST call", url, clustersResp.Status, "", err) - return ClusterInfo{}, errorMsg, fmt.Errorf("%s: %w", errorMsg, err) - } - - if clustersRespJson.ID == "" { - errorMsg := CreateErrorMsg("CreateCluster", "get clusterId in response from POST call", url, clustersResp.Status, string(respBody), nil) - return ClusterInfo{}, errorMsg, fmt.Errorf("%s", errorMsg) - } - - if clustersRespJson.ManagedState == clusterUnManagedState { - c.Log.Info("Cluster added to Astra", "clusterId", clustersRespJson.ID) - return ClusterInfo{ID: clustersRespJson.ID, ManagedState: clustersRespJson.ManagedState, Name: clustersRespJson.Name}, "", nil - } - - err = c.pollForClusterToBeInDesiredState(astraHost, cloudId, clustersRespJson.ID, clusterUnManagedState, apiToken) - if err == nil { - c.Log.Info("Cluster added to Astra", "clusterId", clustersRespJson.ID) - return ClusterInfo{ID: clustersRespJson.ID, ManagedState: clustersRespJson.ManagedState, Name: clustersRespJson.Name}, "", nil - } - - return ClusterInfo{}, "Cluster State not changed to desired state", errors.New("cluster state not changed to desired state") -} - -// UpdateCluster Updates an existing cluster with the provided details -func (c clusterRegisterUtil) UpdateCluster(astraHost, cloudId, clusterId, astraConnectorId, apiToken string) (string, error) { - url := fmt.Sprintf("%s/accounts/%s/topology/v1/clouds/%s/clusters/%s", astraHost, c.AstraConnector.Spec.Astra.AccountId, cloudId, clusterId) - - clusterTypeChecker := &k8s.ClusterTypeChecker{K8sUtil: c.K8sUtil, Log: c.Log} - clusterType := clusterTypeChecker.DetermineClusterType() - - clustersBody := Cluster{ - Type: "application/astra-cluster", - Version: common.AstraClustersAPIVersion, - Name: c.AstraConnector.Spec.Astra.ClusterName, - ConnectorCapabilities: common.GetConnectorCapabilities(), - PrivateRouteID: astraConnectorId, - ClusterType: clusterType, - } - - clustersBodyJson, _ := json.Marshal(clustersBody) - headerMap := HeaderMap{Authorization: fmt.Sprintf("Bearer %s", apiToken)} - response, err, cancel := DoRequest(c.Ctx, c.Client, http.MethodPut, url, clustersBodyJson, headerMap, c.Log, 3) - defer cancel() - - if err != nil { - if response != nil { - return CreateErrorMsg("UpdateCluster", "make PUT call", url, response.Status, "", err), err - } - return CreateErrorMsg("UpdateCluster", "make PUT call", url, "", "", err), err - } - - if response.StatusCode > http.StatusNoContent { - errorMsg := CreateErrorMsg("UpdateCluster", "make PUT call", url, response.Status, "", nil) - return errorMsg, errors.New(errorMsg) - } - - c.Log.WithValues("clusterId", clusterId).Info("Cluster updated") - return "", nil -} - -func (c clusterRegisterUtil) CreateOrUpdateCluster(astraHost, cloudId, clusterId, astraConnectorId, connectorInstall, clustersMethod, apiToken string) (ClusterInfo, string, error) { - if clustersMethod == http.MethodPut { - c.Log.WithValues("clusterId", clusterId).Info("Updating cluster") - - errorReason, err := c.UpdateCluster(astraHost, cloudId, clusterId, astraConnectorId, apiToken) - if err != nil { - return ClusterInfo{}, errorReason, errors.Wrap(err, "error updating cluster") - } - - return ClusterInfo{ID: clusterId, ConnectorInstall: connectorInstall}, "", nil - } - - if clustersMethod == http.MethodPost { - c.Log.Info("Creating Cluster") - - clusterInfo, errorReason, err := c.CreateCluster(astraHost, cloudId, astraConnectorId, apiToken) - if err != nil { - return ClusterInfo{}, errorReason, errors.Wrap(err, "error creating cluster") - } - - return clusterInfo, "", nil - } - - c.Log.Info("Create/Update cluster not required!") - return ClusterInfo{ID: clusterId}, "", nil -} - -// UpdateManagedCluster Updates the persisted record of the given managed cluster -func (c clusterRegisterUtil) UpdateManagedCluster(astraHost, clusterId, astraConnectorId, connectorInstall, apiToken string) (string, error) { - url := fmt.Sprintf("%s/accounts/%s/topology/v1/managedClusters/%s", astraHost, c.AstraConnector.Spec.Astra.AccountId, clusterId) - - manageClustersBody := Cluster{ - Type: "application/astra-managedCluster", - Version: common.AstraManagedClustersAPIVersion, - ConnectorCapabilities: common.GetConnectorCapabilities(), - PrivateRouteID: astraConnectorId, - ConnectorInstall: connectorInstall, - } - manageClustersBodyJson, _ := json.Marshal(manageClustersBody) - - headerMap := HeaderMap{Authorization: fmt.Sprintf("Bearer %s", apiToken)} - response, err, cancel := DoRequest(c.Ctx, c.Client, http.MethodPut, url, manageClustersBodyJson, headerMap, c.Log, 3) - defer cancel() - - if err != nil { - if response != nil { - return CreateErrorMsg("UpdateManagedCluster", "make PUT call", url, response.Status, "", err), err - } - return CreateErrorMsg("UpdateManagedCluster", "make PUT call", url, "", "", err), err - } - - if response.StatusCode > http.StatusNoContent { - errorMsg := CreateErrorMsg("UpdateManagedCluster", "make PUT call", url, response.Status, "", errors.New("update managed cluster failed with: "+strconv.Itoa(response.StatusCode))) - return errorMsg, errors.New(errorMsg) - } - - c.Log.WithValues("clusterId", clusterId).Info("Managed Cluster updated") - return "", nil -} - -// CreateManagedCluster Transitions a cluster from unmanaged state to managed state -func (c clusterRegisterUtil) CreateManagedCluster(astraHost, cloudId, clusterID, connectorInstall, apiToken string) (string, error) { - url := fmt.Sprintf("%s/accounts/%s/topology/v1/managedClusters", astraHost, c.AstraConnector.Spec.Astra.AccountId) - var manageClustersRespJson Cluster - - manageClustersBody := Cluster{ - Type: "application/astra-managedCluster", - Version: common.AstraManagedClustersAPIVersion, - ID: clusterID, - TridentManagedStateDesired: clusterManagedState, - ConnectorInstall: connectorInstall, - } - manageClustersBodyJson, _ := json.Marshal(manageClustersBody) - - headerMap := HeaderMap{Authorization: fmt.Sprintf("Bearer %s", apiToken)} - response, err, cancel := DoRequest(c.Ctx, c.Client, http.MethodPost, url, manageClustersBodyJson, headerMap, c.Log, 3) - defer cancel() - - if err != nil { - if response != nil { - return CreateErrorMsg("CreateManagedCluster", "make POST call", url, response.Status, "", err), err - } - return CreateErrorMsg("CreateManagedCluster", "make POST call", url, "", "", err), err - } - - if response.StatusCode != http.StatusCreated { - errorMsg := CreateErrorMsg("CreateManagedCluster", "make POST call", url, response.Status, "", errors.New("manage cluster failed with: "+strconv.Itoa(response.StatusCode))) - return errorMsg, errors.New(errorMsg) - } - - respBody, err := io.ReadAll(response.Body) - if err != nil { - return CreateErrorMsg("CreateManagedCluster", "read response from POST call", url, response.Status, "", err), err - } - - err = json.Unmarshal(respBody, &manageClustersRespJson) - if err != nil { - return CreateErrorMsg("CreateManagedCluster", "unmarshal response from POST call", url, response.Status, string(respBody), err), err - } - - err = c.pollForClusterToBeInDesiredState(astraHost, cloudId, clusterID, clusterManagedState, apiToken) - if err == nil { - return "", nil - } - - return "Cluster State not changed to managed", errors.New(CreateErrorMsg("CreateManagedCluster", "check cluster state", astraHost, "", "", errors.New("cluster state not changed to managed"))) -} - -func (c clusterRegisterUtil) CreateOrUpdateManagedCluster(astraHost, cloudId, clusterId, astraConnectorId, managedClustersMethod, apiToken string) (ClusterInfo, string, error) { - if managedClustersMethod == http.MethodPut { - c.Log.Info("Updating Managed Cluster") - - errorReason, err := c.UpdateManagedCluster(astraHost, clusterId, astraConnectorId, connectorInstalled, apiToken) - if err != nil { - return ClusterInfo{ID: clusterId}, errorReason, errors.Wrap(err, "error updating managed cluster") - } - - return ClusterInfo{ID: clusterId, ManagedState: clusterManagedState}, "", nil - } - - if managedClustersMethod == http.MethodPost { - c.Log.Info("Creating Managed Cluster") - - // Note: we no longer set storageClass for arch3.0 clusters - errorReason, err := c.CreateManagedCluster(astraHost, cloudId, clusterId, connectorInstalled, apiToken) - if err != nil { - return ClusterInfo{ID: clusterId}, errorReason, errors.Wrap(err, "error creating managed cluster") - } - - return ClusterInfo{ID: clusterId, ManagedState: clusterManagedState}, "", nil - } - - c.Log.Info("Create/Update managed cluster not required!") - return ClusterInfo{ID: clusterId}, "", nil -} - -func (c clusterRegisterUtil) ValidateAndGetCluster(astraHost, cloudId, apiToken, clusterId string) (ClusterInfo, string, error) { - // If a clusterId is known (from CR Spec or CR Status), validate its existence. - // If the provided clusterId exists in the DB, return the details of that cluster, otherwise return an error - - if clusterId != "" { - c.Log.WithValues("cloudID", cloudId, "clusterID", clusterId).Info("Validating the provided ClusterId") - getClusterResp, errorReason, err := c.GetCluster(astraHost, cloudId, clusterId, apiToken) - if err != nil { - return ClusterInfo{}, errorReason, errors.Wrap(err, "error on get cluster") - } - - if getClusterResp.ID == "" { - errMsg := fmt.Sprintf("Invalid ClusterId %v provided in the Spec", clusterId) - return ClusterInfo{}, errMsg, errors.New(errMsg) - } - - c.Log.WithValues("cloudID", cloudId, "clusterID", clusterId).Info("ClusterId exists in the system") - return ClusterInfo{ID: clusterId, Name: getClusterResp.Name, ManagedState: getClusterResp.ManagedState, ConnectorInstall: getClusterResp.ConnectorInstall}, "", nil - } - - // Check whether a cluster exists with a matching "apiServiceID" - // Get all clusters and validate whether any of the response matches with the current cluster's "ServiceUUID" - k8sService := &coreV1.Service{} - err := c.K8sClient.Get(c.Ctx, types.NamespacedName{Name: "kubernetes", Namespace: "default"}, k8sService) - if err != nil { - errMsg := "Failed to get kubernetes service from default namespace" - c.Log.Error(err, errMsg) - return ClusterInfo{}, errMsg, err - } - k8sServiceUUID := string(k8sService.ObjectMeta.UID) - c.Log.Info(fmt.Sprintf("Kubernetes service UUID is %s", k8sServiceUUID)) - - // Check whether a cluster exists with the above "k8sServiceUUID" as "apiServiceID" - getClustersResp, errorReason, err := c.GetClusters(astraHost, cloudId, apiToken) - if err != nil { - return ClusterInfo{}, errorReason, errors.Wrap(err, "error on get clusters") - } - - c.Log.WithValues("cloudID", cloudId).Info("Checking existing records for current cluster's record") - for _, value := range getClustersResp.Items { - // We want to allow dual management of a cluster across different architectures to support migration from v2 to v3. Only reuse existing clusterID if v3 i.e. ConnectorInstall is true - if value.ApiServiceID == k8sServiceUUID && value.ConnectorInstall == "installed" { - c.Log.WithValues("ClusterId", value.ID, "Name", value.Name, "ManagedState", value.ManagedState).Info("Cluster Info found in the existing records") - return ClusterInfo{ID: value.ID, Name: value.Name, ManagedState: value.ManagedState}, "", nil - } - } - - // This is the case for creation of cluster with POST calls to /clusters and /managedClusters - c.Log.WithValues("cloudID", cloudId).Info("ClusterId not specified in CR Spec and an existing cluster doesn't exist in the system") - return ClusterInfo{}, "", nil -} - -// UnmanageCluster unmanages and removes a cluster from Astra. -// It accomplishes this by sending two DELETE requests to the Astra platform cluster: -// one to unmanage the cluster and another to remove the cluster record. -func (c clusterRegisterUtil) UnmanageCluster(clusterID string) error { - astraHost := GetAstraHostURL(c.AstraConnector) - c.Log.WithValues("URL", astraHost).Info("Astra Host Info") - - apiToken, _, err := c.GetAPITokenFromSecret(c.AstraConnector.Spec.Astra.TokenRef) - if err != nil { - c.Log.Error(err, "Failed to get API token") - return err - } - - cloudId, _, err := c.GetCloudId(astraHost, common.AstraPrivateCloudType, apiToken) - if err != nil { - c.Log.Error(err, "Failed to get Cloud ID") - return err - } - - headerMap := HeaderMap{Authorization: fmt.Sprintf("Bearer %s", apiToken)} - - // Un-managing cluster - url := fmt.Sprintf("%s/accounts/%s/topology/v1/managedClusters/%s", astraHost, c.AstraConnector.Spec.Astra.AccountId, clusterID) - resp, err, cancel := DoRequest(c.Ctx, c.Client, http.MethodDelete, url, nil, headerMap, c.Log) - if err != nil { - c.Log.Error(err, "Failed to unmanage cluster") - return errors.New(CreateErrorMsg("UnmanageCluster", "make DELETE call", url, "", "", err)) - } - if resp != nil { - defer cancel() - if resp.StatusCode != http.StatusNoContent { - c.Log.Error(err, "Failed to unmanage cluster, received non-OK response") - return errors.New(CreateErrorMsg("UnmanageCluster", "make DELETE call", url, resp.Status, "", err)) - } - } - - // Removing cluster - url = fmt.Sprintf("%s/accounts/%s/topology/v1/clouds/%s/clusters/%s", astraHost, c.AstraConnector.Spec.Astra.AccountId, cloudId, clusterID) - resp, err, cancel = DoRequest(c.Ctx, c.Client, http.MethodDelete, url, nil, headerMap, c.Log) - defer cancel() - - if err != nil { - c.Log.Error(err, "Failed to remove cluster") - return errors.New(CreateErrorMsg("UnmanageCluster", "make DELETE call", url, "", "", err)) - } - - if resp != nil { - defer cancel() - if resp.StatusCode != http.StatusNoContent { - c.Log.Error(err, "Failed to remove cluster, received non-OK response") - return errors.New(CreateErrorMsg("UnmanageCluster", "make DELETE call", url, resp.Status, "", err)) - } - } - - return nil -} - -// GetAPITokenFromSecret Gets Secret provided in the ACC Spec and returns api token string of the data in secret -func (c clusterRegisterUtil) GetAPITokenFromSecret(secretName string) (string, string, error) { - secret := &coreV1.Secret{} - - err := c.K8sClient.Get(c.Ctx, types.NamespacedName{Name: secretName, Namespace: c.AstraConnector.Namespace}, secret) - if err != nil { - c.Log.WithValues("namespace", c.AstraConnector.Namespace, "secret", secretName).Error(err, "failed to get kubernetes secret") - return "", fmt.Sprintf("Failed to get secret %s", secretName), err - } - - // Extract the value of the 'apiToken' key from the secret - apiToken, ok := secret.Data["apiToken"] - if !ok { - c.Log.WithValues("namespace", c.AstraConnector.Namespace, "secret", secretName).Error(err, "failed to extract apiToken key from secret") - return "", fmt.Sprintf("Failed to extract 'apiToken' key from secret %s", secretName), errors.New("failed to extract apiToken key from secret") - } - - // Convert the value to a string - apiTokenStr := string(apiToken) - return apiTokenStr, "", nil -} - -// RegisterClusterWithAstra Registers/Adds the cluster to Astra -func (c clusterRegisterUtil) RegisterClusterWithAstra(astraConnectorId string, clusterId string) (string, string, error) { - astraHost := GetAstraHostURL(c.AstraConnector) - c.Log.WithValues("URL", astraHost).Info("Astra Host Info") - - err := c.setHttpClient(c.AstraConnector.Spec.Astra.SkipTLSValidation, astraHost) - if err != nil { - return "", "Failed to set TLS Config", err - } - - // Extract the apiToken from the secret provided in the CR Spec via "tokenRef" field - // This is needed to make calls to the Astra - apiToken, errorReason, err := c.GetAPITokenFromSecret(c.AstraConnector.Spec.Astra.TokenRef) - if err != nil { - return "", errorReason, err - } - - // 1. Checks the existence of cloud in the system with the cloudId (if it was specified in the CR Spec) - // If the CloudId was specified and the cloud exists in the system, the same cloudId is returned. - // If the CloudId was specified and the cloud doesn't exist in the system, an error is returned. - // 2. If the CloudId was not specified in the CR Spec, checks whether a cloud of type "private" - // exists in the system, if so returns the cloudId of the "private" cloud. Otherwise, a new cloud of - // type "private" is created and the cloudId is returned. - cloudId, errorReason, err := c.GetOrCreateCloud(astraHost, common.AstraPrivateCloudType, apiToken) - if err != nil { - return "", errorReason, err - } - - // 1. Checks the existence of cluster in the system with the clusterId (if it was specified in the CR Spec) - // If the ClusterId was specified and the cluster exists in the system, details related to that cluster are returned. - // If the ClusterId was specified and the cluster doesn't exist in the system, an error is returned. - // 2. If the ClusterId was not specified in the CR Spec, checks the existence of a cluster in the system (happens on reinstall) - // with "K8s Service UUID" of the current cluster as "ApiServiceID" field value. If there exists such a record, - // details related to that cluster will be returned. Otherwise, empty cluster details will be returned - clusterInfo, errorReason, err := c.ValidateAndGetCluster(astraHost, cloudId, apiToken, clusterId) - if err != nil { - return "", errorReason, err - } - - var clustersMethod, managedClustersMethod string - if clusterInfo.ID != "" { - // clusterInfo.ID != "" ====> - // 1. ClusterId specified in the CR Status or CR Spec AND it is present in the system - // OR - // 2. A cluster record with matching "apiServiceID" is present in the system (happens on re-install) - c.Log.WithValues( - "cloudID", cloudId, - "clusterID", clusterInfo.ID, - "clusterManagedState", clusterInfo.ManagedState, - "connectorInstall", clusterInfo.ConnectorInstall, - ).Info("Cluster exists in the system, updating the existing cluster") - - if clusterInfo.ManagedState == clusterUnManagedState { - clustersMethod = http.MethodPut // PUT /clusters to update the record - managedClustersMethod = http.MethodPost // POST /managedClusters to create a new managed record - } else { - clustersMethod = "" // no call on /clusters - managedClustersMethod = http.MethodPut // PUT /managedClusters to update the record - } - } else { - // Case where clusterId was not specified in the CR Spec - // and a cluster with matching "apiServiceID" was not found - c.Log.Info("Cluster doesn't exist in the system, creating a new cluster and managing it") - clustersMethod = http.MethodPost - managedClustersMethod = http.MethodPost - } - - // Adding or Updating a Cluster based on the status from above - clusterInfo, errorReason, err = c.CreateOrUpdateCluster(astraHost, cloudId, clusterInfo.ID, astraConnectorId, clusterInfo.ConnectorInstall, clustersMethod, apiToken) - if err != nil { - return "", errorReason, err - } - - // Adding or Updating Managed Cluster based on the status from above - clusterInfo, errorReason, err = c.CreateOrUpdateManagedCluster(astraHost, cloudId, clusterInfo.ID, astraConnectorId, managedClustersMethod, apiToken) - if err != nil { - return clusterInfo.ID, errorReason, err - } - - c.Log.WithValues("clusterId", clusterInfo.ID, "clusterName", clusterInfo.Name).Info("Cluster managed by Astra!!!!") - return clusterInfo.ID, "", nil -} - -// CreateErrorMsg creates a standardized error message for HTTP requests. -// This should be used in all cases that we want to format an error message for CR status updates. -func CreateErrorMsg(functionName, action, url, status, responseBody string, err error) string { - errMessage := "" - if err != nil { - errMessage = fmt.Sprintf(": %s", err.Error()) - } - - respBodyMessage := "" - if responseBody != "" { - respBodyMessage = fmt.Sprintf("; Response Body: %s", responseBody) - } - - errorMsg := fmt.Sprintf("%s: Failed to %s to %v with status %s%s%s", functionName, action, url, status, errMessage, respBodyMessage) - - return errorMsg -} diff --git a/app/register/register_test.go b/app/register/register_test.go deleted file mode 100644 index bc3ab8c4..00000000 --- a/app/register/register_test.go +++ /dev/null @@ -1,1910 +0,0 @@ -/* - * Copyright (c) 2024. NetApp, Inc. All Rights Reserved. - */ - -package register_test - -import ( - "bytes" - "context" - "errors" - "io" - "k8s.io/client-go/kubernetes/fake" - "net/http" - "testing" - "time" - - "github.com/google/uuid" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - coreV1 "k8s.io/api/core/v1" - metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "sigs.k8s.io/controller-runtime/pkg/client" - - "github.com/NetApp-Polaris/astra-connector-operator/app/register" - v1 "github.com/NetApp-Polaris/astra-connector-operator/details/operator-sdk/api/v1" - "github.com/NetApp-Polaris/astra-connector-operator/mocks" - testutil "github.com/NetApp-Polaris/astra-connector-operator/test/test-util" -) - -const ( - testNamespace = "test-namespace" - testCloudId = "9876" - testClusterId = "1234" - testURL = "test_url" - testIP = "test_ip" -) - -var ctx = context.Background() - -type mockRead struct { - mock.Mock -} - -var mockHttpRes400 = &http.Response{ - StatusCode: 400, - Body: io.NopCloser(bytes.NewReader([]byte(`errorBody`))), - Status: "Mock Error", -} - -var mockHttpRes401 = &http.Response{ - StatusCode: 400, - Body: io.NopCloser(bytes.NewReader([]byte(`errorBody`))), - Status: "Mock Error", -} - -func (m *mockRead) Read(in []byte) (n int, err error) { - return m.Called(in).Int(0), m.Called(in).Error(1) -} - -func (m *mockRead) Close() error { - return m.Called().Error(0) -} - -func setupTokenSecret(secretName string, k8sClient client.Client) { - secretObj := &coreV1.Secret{ - ObjectMeta: metaV1.ObjectMeta{ - Name: secretName, - Namespace: testNamespace, - }, - Data: map[string][]byte{ - "apiToken": []byte("auth-token"), - }, - } - - _ = k8sClient.Create(ctx, secretObj) -} - -type AstraConnectorInput struct { - createTokenSecret bool - cloudId bool - clusterId bool - invalidHostDetails bool -} - -func createClusterRegister(astraConnectorInput AstraConnectorInput) (register.ClusterRegisterUtil, *mocks.HTTPClient, string, client.Client) { - log := testutil.CreateLoggerForTesting() - mockHttpClient := &mocks.HTTPClient{} - fakeClient := testutil.CreateFakeClient() - k8sUtil := &mocks.K8sUtilInterface{} - k8sUtil.On("RESTGet", mock.Anything).Return(nil, errors.New("test")) - k8sUtil.On("VersionGet").Return("1.0.0", nil) - k8sUtil.On("K8sClientset").Return(fake.NewSimpleClientset()) - apiTokenSecret := "astra-token" - - astraConnector := &v1.AstraConnector{ - ObjectMeta: metaV1.ObjectMeta{ - Name: "test-astra-connector", - Namespace: testNamespace, - }, - Spec: v1.AstraConnectorSpec{ - Astra: v1.Astra{ - TokenRef: apiTokenSecret, - }, - ImageRegistry: v1.ImageRegistry{ - Name: "test-registry", - Secret: "test-secret", - }, - AutoSupport: v1.AutoSupport{ - Enrolled: true, - URL: "https://my-asup", - }, - AstraConnect: v1.AstraConnect{ - Image: "test-image", - Replicas: 2, - }, - }, - } - - if astraConnectorInput.createTokenSecret { - apiTokenSecret = uuid.New().String() - setupTokenSecret(apiTokenSecret, fakeClient) - astraConnector.Spec.Astra.TokenRef = apiTokenSecret - } - - if astraConnectorInput.cloudId { - astraConnector.Spec.Astra.CloudId = testCloudId - } - - if astraConnectorInput.clusterId { - astraConnector.Spec.Astra.ClusterId = testClusterId - } - - if astraConnectorInput.invalidHostDetails { - astraConnector.Spec.NatsSyncClient.CloudBridgeURL = testURL - astraConnector.Spec.NatsSyncClient.HostAliasIP = testIP - } - - clusterRegisterUtil := register.NewClusterRegisterUtil(astraConnector, mockHttpClient, fakeClient, k8sUtil, log, context.Background()) - return clusterRegisterUtil, mockHttpClient, apiTokenSecret, fakeClient -} - -// Tests - -func TestGetConnectorIDFromConfigMap(t *testing.T) { - t.Run("TestGetConnectorIDFromConfigMap__ReturnsConnectorID", func(t *testing.T) { - cmData := map[string]string{ - "cloud-master_locationData.json": "", - "validKey": `{"locationID":"testConnectorID"}`, - } - - clusterRegisterUtil, _, _, _ := createClusterRegister(AstraConnectorInput{}) - id, err := clusterRegisterUtil.GetConnectorIDFromConfigMap(cmData) - - assert.NoError(t, err) - assert.Equal(t, "testConnectorID", id) - }) - - t.Run("TestGetConnectorIDFromConfigMap__UnmarshallError", func(t *testing.T) { - cmData := map[string]string{ - "invalidKey": `{"name":"Jane","age":25`, - } - - clusterRegisterUtil, _, _, _ := createClusterRegister(AstraConnectorInput{}) - id, err := clusterRegisterUtil.GetConnectorIDFromConfigMap(cmData) - - assert.Equal(t, "", id) - assert.EqualError(t, err, "unexpected end of JSON input") - }) -} - -func TestGetNatsSyncClientRegistrationURL(t *testing.T) { - t.Run("TestGetNatsSyncClientRegistrationURL__ReturnsValidURL", func(t *testing.T) { - clusterRegisterUtil, _, _, _ := createClusterRegister(AstraConnectorInput{}) - url := clusterRegisterUtil.GetNatsSyncClientRegistrationURL() - - expectedURL := "http://natssync-client.test-namespace:8080/bridge-client/1/register" - assert.Equal(t, expectedURL, url) - }) -} - -func TestGetNatsSyncClientUnregisterURL(t *testing.T) { - t.Run("TestGetNatsSyncClientUnregisterURL__ReturnsValidURL", func(t *testing.T) { - clusterRegisterUtil, _, _, _ := createClusterRegister(AstraConnectorInput{}) - url := clusterRegisterUtil.GetNatsSyncClientUnregisterURL() - - expectedURL := "http://natssync-client.test-namespace:8080/bridge-client/1/unregister" - assert.Equal(t, expectedURL, url) - }) -} - -func TestUnRegisterNatsSyncClient(t *testing.T) { - t.Run("TestUnRegisterNatsSyncClient__InvalidAuthPayloadReturnsError", func(t *testing.T) { - clusterRegisterUtil, _, _, _ := createClusterRegister(AstraConnectorInput{}) - err := clusterRegisterUtil.UnRegisterNatsSyncClient() - - assert.EqualError(t, err, "secrets \"astra-token\" not found") - }) - - t.Run("TestUnRegisterNatsSyncClient__HTTPPostRequestFailsReturnError", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, _ := createClusterRegister(AstraConnectorInput{createTokenSecret: true}) - - errorText := "error on post request" - mockHttpClient.On("Do", mock.Anything).Return(mockHttpRes400, errors.New(errorText)) - - err := clusterRegisterUtil.UnRegisterNatsSyncClient() - assert.EqualError(t, err, "UnRegisterNatsSyncClient: Failed to make POST call to http://natssync-client.test-namespace:8080/bridge-client/1/unregister with status Mock Error: error on post request") - }) - - t.Run("TestUnRegisterNatsSyncClient__HTTPPostRequestInvalidStatusReturnError", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, _ := createClusterRegister(AstraConnectorInput{createTokenSecret: true}) - - mockHttpClient.On("Do", mock.Anything).Return(mockHttpRes400, nil).Times(3) - - err := clusterRegisterUtil.UnRegisterNatsSyncClient() - assert.ErrorContains(t, err, "Unexpected unregistration status") - }) - - t.Run("TestUnRegisterNatsSyncClient__ReadResponseBodyErrorReturnError", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, _ := createClusterRegister(AstraConnectorInput{createTokenSecret: true}) - - mockRead := mockRead{} - mockRead.On("Read", mock.Anything).Return(0, errors.New("error reading")) - mockRead.On("Close").Return(errors.New("error closing")) - - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 400, - Body: &mockRead, - Status: "Mock Error", - }, nil).Times(3) - - err := clusterRegisterUtil.UnRegisterNatsSyncClient() - assert.EqualError(t, err, "UnRegisterNatsSyncClient: Failed to read response to http://natssync-client.test-namespace:8080/bridge-client/1/unregister with status Mock Error: error reading") - }) - - t.Run("TestUnRegisterNatsSyncClient__OnSuccessReturnNil", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, _ := createClusterRegister(AstraConnectorInput{createTokenSecret: true}) - - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 204, - Body: nil, - }, nil).Once() - - err := clusterRegisterUtil.UnRegisterNatsSyncClient() - assert.Nil(t, err) - }) -} - -func TestRegisterNatsSyncClient(t *testing.T) { - t.Run("TestRegisterNatsSyncClient__InvalidAuthPayloadReturnsError", func(t *testing.T) { - clusterRegisterUtil, _, _, _ := createClusterRegister(AstraConnectorInput{}) - connectorId, errorReason, err := clusterRegisterUtil.RegisterNatsSyncClient() - - assert.Equal(t, "", connectorId) - assert.Equal(t, errorReason, "Failed to get secret astra-token") - assert.EqualError(t, err, "secrets \"astra-token\" not found") - }) - - t.Run("TestRegisterNatsSyncClient__HTTPPostRequestFailsReturnError", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, _ := createClusterRegister(AstraConnectorInput{createTokenSecret: true}) - - errorText := "error on post request create" - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{}, errors.New(errorText)) - - connectorId, errorReason, err := clusterRegisterUtil.RegisterNatsSyncClient() - - assert.Equal(t, "", connectorId) - assert.Contains(t, errorReason, "Failed to make POST call to") - assert.EqualError(t, err, errorText) - }) - - t.Run("TestUnRegisterNatsSyncClient__HTTPPostRequestInvalidStatusReturnError", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, _ := createClusterRegister(AstraConnectorInput{createTokenSecret: true}) - - mockHttpClient.On("Do", mock.Anything).Return(mockHttpRes400, nil).Times(3) - - connectorId, errorReason, err := clusterRegisterUtil.RegisterNatsSyncClient() - - assert.Equal(t, "", connectorId) - assert.Contains(t, errorReason, "Failed to make POST call") - assert.ErrorContains(t, err, "Unexpected registration status") - }) - - t.Run("TestUnRegisterNatsSyncClient__ReadResponseBodyErrorReturnError", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, _ := createClusterRegister(AstraConnectorInput{createTokenSecret: true}) - - mockRead := mockRead{} - mockRead.On("Read", mock.Anything).Return(0, errors.New("error reading")) - mockRead.On("Close").Return(errors.New("error closing")) - - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 400, - Body: &mockRead, - Status: "Mock Error", - }, nil).Times(3) - - connectorId, errorReason, err := clusterRegisterUtil.RegisterNatsSyncClient() - - assert.Equal(t, "", connectorId) - assert.Contains(t, errorReason, "Failed to read response from POST call to") - assert.EqualError(t, err, "error reading") - }) - - t.Run("TestUnRegisterNatsSyncClient__OnSuccessReturnConnectorId", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, _ := createClusterRegister(AstraConnectorInput{createTokenSecret: true}) - - ret := io.NopCloser(bytes.NewReader([]byte(`{"locationID":"test-connectorID"}`))) - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 201, - Body: ret, - }, nil).Once() - - connectorId, errorReason, err := clusterRegisterUtil.RegisterNatsSyncClient() - - assert.Equal(t, "test-connectorID", connectorId) - assert.Equal(t, "", errorReason) - assert.Nil(t, err) - }) - - t.Run("TestUnRegisterNatsSyncClient__OnSuccessButInvalidJSONBodyReturnError", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, _ := createClusterRegister(AstraConnectorInput{createTokenSecret: true}) - - ret := io.NopCloser(bytes.NewReader([]byte(`"locationID":"test-connectorID"}`))) - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 201, - Body: ret, - Status: "201 Success", - }, nil).Once() - - connectorId, errorReason, err := clusterRegisterUtil.RegisterNatsSyncClient() - - assert.Equal(t, "", connectorId) - assert.Contains(t, errorReason, "Failed to decode response") - assert.NotNil(t, err) - }) -} - -func TestGetAstraHostURL(t *testing.T) { - t.Run("TestGetAstraHostURLTrailingSlash", func(t *testing.T) { - astraConnector := &v1.AstraConnector{ - ObjectMeta: metaV1.ObjectMeta{ - Name: "test-astra-connector", - Namespace: testNamespace, - }, - Spec: v1.AstraConnectorSpec{ - NatsSyncClient: v1.NatsSyncClient{ - CloudBridgeURL: "https://netapp.astra.io/", - }, - }, - } - - host := register.GetAstraHostURL(astraConnector) - assert.Equal(t, host, "https://netapp.astra.io") - - }) -} - -func TestCloudExists(t *testing.T) { - host, cloudId, apiToken := "test_host", "test_cloudId", "test_apiToken" - - t.Run("TestCloudExists__HTTPGetRequestFailsReturnFalse", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, _ := createClusterRegister(AstraConnectorInput{}) - - errorText := "error on get request" - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{}, errors.New(errorText)) - - cloudExists := clusterRegisterUtil.CloudExists(host, cloudId, apiToken) - assert.Equal(t, false, cloudExists) - }) - - t.Run("TestCloudExists__HTTPStatusNotFoundCodeReturnFalse", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, _ := createClusterRegister(AstraConnectorInput{}) - - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 404, - Body: nil, - }, nil).Once() - - cloudExists := clusterRegisterUtil.CloudExists(host, cloudId, apiToken) - assert.Equal(t, false, cloudExists) - }) - - t.Run("TestCloudExists__HTTPStatusInvalidStatusCodeReturnFalse", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, _ := createClusterRegister(AstraConnectorInput{}) - - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 202, - Body: nil, - }, nil).Once() - - cloudExists := clusterRegisterUtil.CloudExists(host, cloudId, apiToken) - assert.Equal(t, false, cloudExists) - }) - - t.Run("TestCloudExists__HTTPStatusValidStatusCodeReturnTrue", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, _ := createClusterRegister(AstraConnectorInput{}) - - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 200, - Body: nil, - }, nil).Once() - - cloudExists := clusterRegisterUtil.CloudExists(host, cloudId, apiToken) - assert.Equal(t, true, cloudExists) - }) -} - -func TestListClouds(t *testing.T) { - host, apiToken := "test_host", "test_apiToken" - - t.Run("TestListClouds__HTTPGetRequestFailsReturnError", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, _ := createClusterRegister(AstraConnectorInput{}) - - errorText := "error on get request" - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{}, errors.New(errorText)) - - cloudsResp, err := clusterRegisterUtil.ListClouds(host, apiToken) - assert.Nil(t, cloudsResp) - assert.EqualError(t, err, "error on get request") - }) - - t.Run("TestListClouds__ReturnCloudsResponse", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, _ := createClusterRegister(AstraConnectorInput{}) - - ret := io.NopCloser(bytes.NewReader([]byte(`{"items":[{"id":"1234","name":"cloud1"}, {"id":"5678","name":"cloud2"}]}`))) - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 200, - Body: ret, - }, nil).Once() - - cloudsResp, err := clusterRegisterUtil.ListClouds(host, apiToken) - assert.NotNil(t, cloudsResp) - assert.Nil(t, err) - }) -} - -func TestGetCloudId(t *testing.T) { - host, cloudType, apiToken := "test_host", "private", "test_apiToken" - - t.Run("TestGetCloudId__ListCloudsFailReturnError", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, _ := createClusterRegister(AstraConnectorInput{}) - - errorText := "error on get request" - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{}, errors.New(errorText)) - - cloudId, errorReason, err := clusterRegisterUtil.GetCloudId(host, cloudType, apiToken, 3*time.Second) - - assert.Equal(t, "", cloudId) - assert.Equal(t, "Failed to Get Clouds", errorReason) - assert.EqualError(t, err, "timed out querying Astra API") - }) - - t.Run("TestGetCloudId__ListCloudsInvalidStatusCodeReturnError", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, _ := createClusterRegister(AstraConnectorInput{}) - - ret := io.NopCloser(bytes.NewReader([]byte(`{"items":[{"id":"1234","name":"cloud1", "cloudType":"private"}, {"id":"5678","name":"cloud2","cloudType":"not-private"}]}`))) - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 401, - Body: ret, - }, nil) - - cloudId, errorReason, err := clusterRegisterUtil.GetCloudId(host, cloudType, apiToken, 3*time.Second) - - assert.Equal(t, "", cloudId) - assert.Equal(t, "Failed to Get Clouds", errorReason) - assert.EqualError(t, err, "timed out querying Astra API") - }) - - t.Run("TestGetCloudId__ReadResponseBodyErrorReturnError", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, _ := createClusterRegister(AstraConnectorInput{}) - - mockRead := mockRead{} - mockRead.On("Read", mock.Anything).Return(0, errors.New("error reading")) - mockRead.On("Close").Return(errors.New("error closing")) - - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 200, - Body: &mockRead, - }, nil).Once() - - cloudId, errorReason, err := clusterRegisterUtil.GetCloudId(host, cloudType, apiToken) - - assert.Equal(t, "", cloudId) - assert.Equal(t, "Failed to read response from Get Clouds", errorReason) - assert.EqualError(t, err, "error reading") - }) - - t.Run("TestGetCloudId__UnmarshalBodyErrorReturnError", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, _ := createClusterRegister(AstraConnectorInput{}) - - ret := io.NopCloser(bytes.NewReader([]byte(`items:{"Name":"Joe","Body":"Hello","Time":1294706395881547069`))) - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 200, - Body: ret, - }, nil).Once() - - cloudId, errorReason, err := clusterRegisterUtil.GetCloudId(host, cloudType, apiToken) - - assert.Equal(t, "", cloudId) - assert.Equal(t, "Failed to unmarshal response from Get Clouds", errorReason) - assert.EqualError(t, err, "invalid character 'i' looking for beginning of value") - }) - - t.Run("TestGetCloudId__ReturnEmptyCloudIdWhenNoPrivateCloudType", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, _ := createClusterRegister(AstraConnectorInput{}) - - ret := io.NopCloser(bytes.NewReader([]byte(`{"items":[{"id":"1234","name":"cloud1", "cloudType":"not-private"}, {"id":"5678","name":"cloud2","cloudType":"not-private"}]}`))) - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 200, - Body: ret, - }, nil).Once() - - cloudId, errorReason, err := clusterRegisterUtil.GetCloudId(host, cloudType, apiToken) - - assert.Equal(t, "", cloudId) - assert.Equal(t, "", errorReason) - assert.Nil(t, err) - }) - - t.Run("TestGetCloudId__ReturnCloudIdOfTypePrivate", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, _ := createClusterRegister(AstraConnectorInput{}) - - ret := io.NopCloser(bytes.NewReader([]byte(`{"items":[{"id":"1234","name":"cloud1", "cloudType":"private"}, {"id":"5678","name":"cloud2","cloudType":"not-private"}]}`))) - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 200, - Body: ret, - }, nil).Once() - - cloudId, errorReason, err := clusterRegisterUtil.GetCloudId(host, cloudType, apiToken) - - assert.Equal(t, "1234", cloudId) - assert.Equal(t, "", errorReason) - assert.Nil(t, err) - }) -} - -func TestCreateCloud(t *testing.T) { - host, cloudType, apiToken := "test_host", "private", "test_apiToken" - - t.Run("TestCreateCloud__HTTPPostRequestFailsReturnError", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, _ := createClusterRegister(AstraConnectorInput{}) - - errorText := "error on post request create" - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{}, errors.New(errorText)) - - cloudId, errorReason, err := clusterRegisterUtil.CreateCloud(host, cloudType, apiToken) - - assert.Equal(t, "", cloudId) - assert.Contains(t, errorReason, "Failed to make POST call to") - assert.EqualError(t, err, "error on post request create") - }) - - t.Run("TestCreateCloud__ReadResponseBodyErrorReturnError", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, _ := createClusterRegister(AstraConnectorInput{}) - - mockRead := mockRead{} - mockRead.On("Read", mock.Anything).Return(0, errors.New("error reading")) - mockRead.On("Close").Return(errors.New("error closing")) - - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 201, - Body: &mockRead, - Status: "201 Success", - }, nil).Once() - - cloudId, errorReason, err := clusterRegisterUtil.CreateCloud(host, cloudType, apiToken) - - assert.Equal(t, "", cloudId) - assert.Contains(t, errorReason, "Failed to read response from POST call to") - assert.EqualError(t, err, "error reading") - }) - - t.Run("TestCreateCloud__UnmarshalBodyErrorReturnError", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, _ := createClusterRegister(AstraConnectorInput{}) - - ret := io.NopCloser(bytes.NewReader([]byte(`items:{"Name":"Joe","Body":"Hello","Time":1294706395881547069`))) - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 201, - Body: ret, - }, nil).Once() - - cloudId, errorReason, err := clusterRegisterUtil.CreateCloud(host, cloudType, apiToken) - - assert.Equal(t, "", cloudId) - assert.Contains(t, errorReason, "Failed to unmarshal response from POST call to") - assert.ErrorContains(t, err, "invalid character 'i' looking for beginning of value") - }) - - t.Run("TestCreateCloud__GotEmptyCloudIDReturnError", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, _ := createClusterRegister(AstraConnectorInput{}) - - ret := io.NopCloser(bytes.NewReader([]byte(`{"id":"","name":""}`))) - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 201, - Body: ret, - }, nil).Once() - - cloudId, errorReason, err := clusterRegisterUtil.CreateCloud(host, cloudType, apiToken) - - assert.Equal(t, "", cloudId) - assert.Equal(t, "", errorReason) - assert.Nil(t, err) - }) - - t.Run("TestCreateCloud__CloudCreatedReturnCloudId", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, _ := createClusterRegister(AstraConnectorInput{}) - - ret := io.NopCloser(bytes.NewReader([]byte(`{"id":"1234","name":"test-cloud"}`))) - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 201, - Body: ret, - }, nil).Once() - - cloudId, errorReason, err := clusterRegisterUtil.CreateCloud(host, cloudType, apiToken) - - assert.Equal(t, "1234", cloudId) - assert.Equal(t, "", errorReason) - assert.Nil(t, err) - }) -} - -func TestGetOrCreateCloud(t *testing.T) { - host, cloudType, apiToken := "test_host", "private", "test_apiToken" - - t.Run("TestGetOrCreateCloud__InvalidCloudIdProvidedInTheSpecReturnError", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, _ := createClusterRegister(AstraConnectorInput{cloudId: true}) - - errorText := "error on get request" - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{}, errors.New(errorText)) - - cloudId, errorReason, err := clusterRegisterUtil.GetOrCreateCloud(host, cloudType, apiToken) - - assert.Equal(t, "", cloudId) - assert.Equal(t, "Invalid CloudId 9876 provided in the Spec", errorReason) - assert.EqualError(t, err, "Invalid CloudId 9876 provided in the Spec") - }) - - t.Run("TestGetOrCreateCloud__ValidCloudIdProvidedInTheSpecReturnCloudId", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, _ := createClusterRegister(AstraConnectorInput{cloudId: true}) - - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 200, - Body: nil, - }, nil).Once() - - cloudId, errorReason, err := clusterRegisterUtil.GetOrCreateCloud(host, cloudType, apiToken) - - assert.Equal(t, "9876", cloudId) - assert.Equal(t, "", errorReason) - assert.Nil(t, err) - }) - - t.Run("TestGetOrCreateCloud__GetCloudIdReturnsErrorReturnError", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, _ := createClusterRegister(AstraConnectorInput{}) - - ret := io.NopCloser(bytes.NewReader([]byte(`items:{"Name":"Joe","Body":"Hello","Time":1294706395881547069`))) - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 200, - Body: ret, - }, nil).Once() - - cloudId, errorReason, err := clusterRegisterUtil.GetOrCreateCloud(host, cloudType, apiToken) - - assert.Equal(t, "", cloudId) - assert.Equal(t, "Failed to unmarshal response from Get Clouds", errorReason) - assert.EqualError(t, err, "invalid character 'i' looking for beginning of value") - }) - - t.Run("TestGetOrCreateCloud__GetCloudIdReturnsCloudReturnCloudId", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, _ := createClusterRegister(AstraConnectorInput{}) - - ret := io.NopCloser(bytes.NewReader([]byte(`{"items":[{"id":"1234","name":"cloud1", "cloudType":"private"}, {"id":"5678","name":"cloud2","cloudType":"not-private"}]}`))) - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 200, - Body: ret, - }, nil).Once() - - cloudId, errorReason, err := clusterRegisterUtil.GetOrCreateCloud(host, cloudType, apiToken) - - assert.Equal(t, "1234", cloudId) - assert.Equal(t, "", errorReason) - assert.Nil(t, err) - }) - - t.Run("TestGetOrCreateCloud__CreateCloudReturnsEmptyCloudIdReturnError", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, _ := createClusterRegister(AstraConnectorInput{}) - - ret := io.NopCloser(bytes.NewReader([]byte(`{"items":[{"id":"1234","name":"cloud1", "cloudType":"not-private"}, {"id":"5678","name":"cloud2","cloudType":"not-private"}]}`))) - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 200, - Body: ret, - }, nil).Once() - - errorText := "error on post request create" - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{}, errors.New(errorText)) - - cloudId, errorReason, err := clusterRegisterUtil.GetOrCreateCloud(host, cloudType, apiToken) - - assert.Equal(t, "", cloudId) - assert.Contains(t, errorReason, "Failed to make POST call to") - assert.EqualError(t, err, "error on post request create") - }) - - t.Run("TestGetOrCreateCloud__CreateCloudReturnsErrorReturnError", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, _ := createClusterRegister(AstraConnectorInput{}) - - ret := io.NopCloser(bytes.NewReader([]byte(`{"items":[{"id":"1234","name":"cloud1", "cloudType":"not-private"}, {"id":"5678","name":"cloud2","cloudType":"not-private"}]}`))) - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 200, - Body: ret, - }, nil).Once() - - ret = io.NopCloser(bytes.NewReader([]byte(`{"id":"","name":""}`))) - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 201, - Body: ret, - }, nil).Once() - - cloudId, errorReason, err := clusterRegisterUtil.GetOrCreateCloud(host, cloudType, apiToken) - - assert.Equal(t, "", cloudId) - assert.Equal(t, "Got empty Cloud Id from POST call to clouds", errorReason) - assert.EqualError(t, err, "could not create cloud of type private") - }) - - t.Run("TestGetOrCreateCloud__CloudCreatedReturnCloudId", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, _ := createClusterRegister(AstraConnectorInput{}) - - ret := io.NopCloser(bytes.NewReader([]byte(`{"items":[{"id":"1234","name":"cloud1", "cloudType":"not-private"}, {"id":"5678","name":"cloud2","cloudType":"not-private"}]}`))) - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 200, - Body: ret, - }, nil).Once() - - ret = io.NopCloser(bytes.NewReader([]byte(`{"id":"1234","name":"test-cloud"}`))) - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 201, - Body: ret, - }, nil).Once() - - cloudId, errorReason, err := clusterRegisterUtil.GetOrCreateCloud(host, cloudType, apiToken) - - assert.Equal(t, "1234", cloudId) - assert.Equal(t, "", errorReason) - assert.Nil(t, err) - }) -} - -func TestGetClusters(t *testing.T) { - host, cloudId, apiToken := "test_host", "test_cloudId", "test_apiToken" - - t.Run("TestGetClusters__HTTPGetRequestFailsReturnError", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, _ := createClusterRegister(AstraConnectorInput{}) - - errorText := "error on get request" - mockHttpClient.On("Do", mock.Anything).Return(mockHttpRes401, errors.New(errorText)) - - clusters, errorReason, err := clusterRegisterUtil.GetClusters(host, cloudId, apiToken) - - assert.Equal(t, 0, len(clusters.Items)) - assert.Contains(t, errorReason, "Failed to make GET call to") - assert.EqualError(t, err, "error on get request") - }) - - t.Run("TestGetClusters__HTTPGetRequestInvalidStatusCodeReturnError", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, _ := createClusterRegister(AstraConnectorInput{}) - - mockHttpClient.On("Do", mock.Anything).Return(mockHttpRes401, nil).Once() - - clusters, errorReason, err := clusterRegisterUtil.GetClusters(host, cloudId, apiToken) - - assert.Equal(t, 0, len(clusters.Items)) - assert.Contains(t, errorReason, "Failed to make GET call") - assert.EqualError(t, err, "GetClusters: Failed to make GET call to test_host/accounts//topology/v1/clouds/test_cloudId/clusters with status Mock Error") - }) - - t.Run("TestGetClusters__ReadResponseBodyErrorReturnError", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, _ := createClusterRegister(AstraConnectorInput{}) - - mockRead := mockRead{} - mockRead.On("Read", mock.Anything).Return(0, errors.New("error reading")) - mockRead.On("Close").Return(errors.New("error closing")) - - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 200, - Body: &mockRead, - }, nil).Once() - - clusters, errorReason, err := clusterRegisterUtil.GetClusters(host, cloudId, apiToken) - - assert.Equal(t, 0, len(clusters.Items)) - assert.Contains(t, errorReason, "Failed to read response from GET call to") - assert.EqualError(t, err, "error reading") - }) - - t.Run("TestGetClusters__UnmarshalBodyErrorReturnError", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, _ := createClusterRegister(AstraConnectorInput{}) - - ret := io.NopCloser(bytes.NewReader([]byte(`items:{"Name":"Joe","Body":"Hello","Time":1294706395881547069`))) - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 200, - Body: ret, - }, nil).Once() - - clusters, errorReason, err := clusterRegisterUtil.GetClusters(host, cloudId, apiToken) - - assert.Equal(t, 0, len(clusters.Items)) - assert.Contains(t, errorReason, "Failed to unmarshal response from GET call to") - assert.EqualError(t, err, "invalid character 'i' looking for beginning of value") - }) - - t.Run("TestGetClusters__ReturnClusterResponse", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, _ := createClusterRegister(AstraConnectorInput{}) - - ret := io.NopCloser(bytes.NewReader([]byte(`{"items":[{"id":"1234","name":"cluster1"}, {"id":"5678","name":"cluster2"}]}`))) - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 200, - Body: ret, - }, nil).Once() - - clusters, errorReason, err := clusterRegisterUtil.GetClusters(host, cloudId, apiToken) - - assert.Equal(t, 2, len(clusters.Items)) - assert.Equal(t, "", errorReason) - assert.Nil(t, err) - }) -} - -func TestGetCluster(t *testing.T) { - host, cloudId, clusterId, apiToken := "test_host", "test_cloudId", "test_clusterId", "test_apiToken" - - t.Run("TestGetCluster__HTTPGetRequestFailsReturnError", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, _ := createClusterRegister(AstraConnectorInput{}) - - errorText := "error on get request" - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{}, errors.New(errorText)) - - cluster, errorReason, err := clusterRegisterUtil.GetCluster(host, cloudId, clusterId, apiToken) - - assert.Equal(t, "", cluster.ID) - assert.Equal(t, "", cluster.Name) - assert.Contains(t, errorReason, "Failed to make GET call to") - assert.EqualError(t, err, "error on get request") - }) - - t.Run("TestGetCluster__HTTPGetRequestInvalidStatusCodeReturnError", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, _ := createClusterRegister(AstraConnectorInput{}) - - mockHttpClient.On("Do", mock.Anything).Return(mockHttpRes401, nil).Once() - - cluster, errorReason, err := clusterRegisterUtil.GetCluster(host, cloudId, clusterId, apiToken) - - assert.Equal(t, "", cluster.ID) - assert.Equal(t, "", cluster.Name) - assert.Contains(t, errorReason, "Failed to make GET call") - assert.EqualError(t, err, "GetCluster: Failed to make GET call to test_host/accounts//topology/v1/clouds/test_cloudId/clusters/test_clusterId with status Mock Error") - }) - - t.Run("TestGetCluster__ReadResponseBodyErrorReturnError", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, _ := createClusterRegister(AstraConnectorInput{}) - - mockRead := mockRead{} - mockRead.On("Read", mock.Anything).Return(0, errors.New("error reading")) - mockRead.On("Close").Return(errors.New("error closing")) - - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 200, - Body: &mockRead, - }, nil).Once() - - cluster, errorReason, err := clusterRegisterUtil.GetCluster(host, cloudId, clusterId, apiToken) - - assert.Equal(t, "", cluster.ID) - assert.Equal(t, "", cluster.Name) - assert.Contains(t, errorReason, "Failed to read response from GET call to") - assert.EqualError(t, err, "error reading") - }) - - t.Run("TestGetCluster__UnmarshalBodyErrorReturnError", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, _ := createClusterRegister(AstraConnectorInput{}) - - ret := io.NopCloser(bytes.NewReader([]byte(`items:{"Name":"Joe","Body":"Hello","Time":1294706395881547069`))) - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 200, - Body: ret, - }, nil).Once() - - cluster, errorReason, err := clusterRegisterUtil.GetCluster(host, cloudId, clusterId, apiToken) - - assert.Equal(t, "", cluster.ID) - assert.Equal(t, "", cluster.Name) - assert.Contains(t, errorReason, "Failed to unmarshal response from GET call to") - assert.EqualError(t, err, "invalid character 'i' looking for beginning of value") - }) - - t.Run("TestGetCluster__ReturnClusterResponse", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, _ := createClusterRegister(AstraConnectorInput{}) - - ret := io.NopCloser(bytes.NewReader([]byte(`{"id":"1234","name":"this is a cluster"}`))) - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 200, - Body: ret, - }, nil).Once() - - cluster, errorReason, err := clusterRegisterUtil.GetCluster(host, cloudId, clusterId, apiToken) - - assert.Equal(t, "1234", cluster.ID) - assert.Equal(t, "this is a cluster", cluster.Name) - assert.Equal(t, "", errorReason) - assert.Nil(t, err) - }) -} - -func TestCreateCluster(t *testing.T) { - host, cloudId, connectorId, apiToken := "test_host", "test_cloudId", "test_connectorId", "test_apiToken" - - t.Run("TestCreateCluster__HTTPPostRequestFailsReturnError", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, _ := createClusterRegister(AstraConnectorInput{}) - - errorText := "error on post request create" - mockHttpClient.On("Do", mock.Anything).Return( - &http.Response{ - Status: "Mock Error", - }, - errors.New(errorText), - ) - - clusterInfo, errorReason, err := clusterRegisterUtil.CreateCluster(host, cloudId, connectorId, apiToken) - - assert.Equal(t, "", clusterInfo.ID) - assert.Equal(t, "", clusterInfo.Name) - assert.Contains(t, errorReason, "CreateCluster: Failed to make POST call to") - assert.EqualError(t, err, "CreateCluster: Failed to make POST call to test_host/accounts//topology/v1/clouds/test_cloudId/clusters with status Mock Error: error on post request create: error on post request create") - }) - - t.Run("TestCreateCluster__ReadResponseBodyErrorReturnError", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, _ := createClusterRegister(AstraConnectorInput{}) - - mockRead := mockRead{} - mockRead.On("Read", mock.Anything).Return(0, errors.New("error reading")) - mockRead.On("Close").Return(errors.New("error closing")) - - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 400, - Body: &mockRead, - Status: "Mock Error", - }, nil).Times(3) - - clusterInfo, errorReason, err := clusterRegisterUtil.CreateCluster(host, cloudId, connectorId, apiToken) - - assert.Equal(t, "", clusterInfo.ID) - assert.Equal(t, "", clusterInfo.Name) - assert.Contains(t, errorReason, "CreateCluster: Failed to read response from POST call to") - assert.EqualError(t, err, "CreateCluster: Failed to read response from POST call to test_host/accounts//topology/v1/clouds/test_cloudId/clusters with status Mock Error: error reading") - }) - - t.Run("TestCreateCluster__HTTPPostRequestInvalidStatusCodeReturnError", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, _ := createClusterRegister(AstraConnectorInput{}) - - ret := io.NopCloser(bytes.NewReader([]byte(`items:{"Name":"Joe","Body":"Hello","Time":1294706395881547069}`))) - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 400, - Body: ret, - Status: "Mock Error", - }, nil).Times(3) - - clusterInfo, errorReason, err := clusterRegisterUtil.CreateCluster(host, cloudId, connectorId, apiToken) - - assert.Equal(t, "", clusterInfo.ID) - assert.Equal(t, "", clusterInfo.Name) - assert.Contains(t, errorReason, "CreateCluster: Failed to make POST call to") - assert.EqualError(t, err, "CreateCluster: Failed to make POST call to test_host/accounts//topology/v1/clouds/test_cloudId/clusters with status Mock Error; Response Body: items:{\"Name\":\"Joe\",\"Body\":\"Hello\",\"Time\":1294706395881547069}") - }) - - t.Run("TestCreateCluster__UnmarshalBodyErrorReturnError", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, _ := createClusterRegister(AstraConnectorInput{}) - - ret := io.NopCloser(bytes.NewReader([]byte(`items:{"Name":"Joe","Body":"Hello","Time":1294706395881547069`))) - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 201, - Body: ret, - }, nil).Times(3) - - clusterInfo, errorReason, err := clusterRegisterUtil.CreateCluster(host, cloudId, connectorId, apiToken) - - assert.Equal(t, "", clusterInfo.ID) - assert.Equal(t, "", clusterInfo.Name) - assert.Contains(t, errorReason, "CreateCluster: Failed to unmarshal response from POST call to") - assert.EqualError(t, err, "CreateCluster: Failed to unmarshal response from POST call to test_host/accounts//topology/v1/clouds/test_cloudId/clusters with status : invalid character 'i' looking for beginning of value: invalid character 'i' looking for beginning of value") - }) - - t.Run("TestCreateCluster__GotEmptyClusterIDReturnError", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, _ := createClusterRegister(AstraConnectorInput{}) - - ret := io.NopCloser(bytes.NewReader([]byte(`{"id":"","name":"","managedState":""}`))) - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 201, - Body: ret, - Status: "201", - }, nil).Times(3) - - clusterInfo, errorReason, err := clusterRegisterUtil.CreateCluster(host, cloudId, connectorId, apiToken) - - assert.Equal(t, "", clusterInfo.ID) - assert.Equal(t, "", clusterInfo.Name) - assert.Contains(t, errorReason, "CreateCluster: Failed to get clusterId in response from POST call") - assert.EqualError(t, err, "CreateCluster: Failed to get clusterId in response from POST call to test_host/accounts//topology/v1/clouds/test_cloudId/clusters with status 201; Response Body: {\"id\":\"\",\"name\":\"\",\"managedState\":\"\"}") - }) - - t.Run("TestCreateCluster__ClusterAddedReturnClusterInfo", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, _ := createClusterRegister(AstraConnectorInput{}) - - ret := io.NopCloser(bytes.NewReader([]byte(`{"id":"1234","name":"test-cluster","managedState":"unmanaged"}`))) - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 201, - Body: ret, - }, nil).Once() - - clusterInfo, errorReason, err := clusterRegisterUtil.CreateCluster(host, cloudId, connectorId, apiToken) - - assert.Equal(t, "1234", clusterInfo.ID) - assert.Equal(t, "test-cluster", clusterInfo.Name) - assert.Equal(t, "unmanaged", clusterInfo.ManagedState) - assert.Equal(t, "", errorReason) - assert.Nil(t, err) - }) -} - -func TestUpdateCluster(t *testing.T) { - host, cloudId, clusterId, connectorId, apiToken := "test_host", "test_cloudId", "test_clusterId", "test_connectorId", "test_apiToken" - - t.Run("TestUpdateCluster__HTTPPutRequestFailsReturnError", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, _ := createClusterRegister(AstraConnectorInput{}) - - errorText := "error on put request update" - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{}, errors.New(errorText)) - - errorReason, err := clusterRegisterUtil.UpdateCluster(host, cloudId, clusterId, connectorId, apiToken) - assert.Contains(t, errorReason, "Failed to make PUT call to") - assert.EqualError(t, err, "error on put request update") - }) - - t.Run("TestUpdateCluster__HTTPPutRequestInvalidStatusCodeReturnError", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, _ := createClusterRegister(AstraConnectorInput{}) - - mockHttpClient.On("Do", mock.Anything).Return(mockHttpRes400, nil).Times(3) - - errorReason, err := clusterRegisterUtil.UpdateCluster(host, cloudId, clusterId, connectorId, apiToken) - assert.Contains(t, errorReason, "Failed to make PUT call to") - assert.EqualError(t, err, "UpdateCluster: Failed to make PUT call to test_host/accounts//topology/v1/clouds/test_cloudId/clusters/test_clusterId with status Mock Error") - }) - - t.Run("TestUpdateCluster__ClusterUpdatedReturnNil", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, _ := createClusterRegister(AstraConnectorInput{}) - - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 201, - Body: nil, - }, nil).Once() - - errorReason, err := clusterRegisterUtil.UpdateCluster(host, cloudId, clusterId, connectorId, apiToken) - assert.Equal(t, "", errorReason) - assert.Nil(t, err) - }) -} - -func TestCreateOrUpdateCluster(t *testing.T) { - host, cloudId, clusterId, connectorId, apiToken, connectorInstall := "test_host", "test_cloudId", "test_clusterId", "test_connectorId", "test_apiToken", "pending" - - t.Run("TestCreateOrUpdateCluster__ReturnsErrorWhenCreateClusterFails", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, _ := createClusterRegister(AstraConnectorInput{}) - - errorText := "this is an error" - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{}, errors.New(errorText)) - - clusterInfo, errorReason, err := clusterRegisterUtil.CreateOrUpdateCluster(host, cloudId, clusterId, connectorId, connectorInstall, http.MethodPost, apiToken) - - assert.Equal(t, "", clusterInfo.ID) - assert.Equal(t, "", clusterInfo.Name) - assert.Contains(t, errorReason, "CreateCluster: Failed to make POST call to") - assert.EqualError(t, err, "error creating cluster: CreateCluster: Failed to make POST call to test_host/accounts//topology/v1/clouds/test_cloudId/clusters with status : this is an error: this is an error") - }) - - t.Run("TestCreateOrUpdateCluster__ReturnsClusterInfoWhenClusterGetsCreated", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, _ := createClusterRegister(AstraConnectorInput{}) - - ret := io.NopCloser(bytes.NewReader([]byte(`{"id":"1234","name":"test-cluster","managedState":"unmanaged"}`))) - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 201, - Body: ret, - }, nil).Once() - - clusterInfo, errorReason, err := clusterRegisterUtil.CreateOrUpdateCluster(host, cloudId, clusterId, connectorId, connectorInstall, http.MethodPost, apiToken) - - assert.Equal(t, "1234", clusterInfo.ID) - assert.Equal(t, "test-cluster", clusterInfo.Name) - assert.Equal(t, "unmanaged", clusterInfo.ManagedState) - assert.Equal(t, "", errorReason) - assert.Nil(t, err) - }) - - t.Run("TestCreateOrUpdateCluster__ReturnsErrorWhenUpdateClusterFails", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, _ := createClusterRegister(AstraConnectorInput{}) - - errorText := "this is an error" - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{}, errors.New(errorText)) - - clusterInfo, errorReason, err := clusterRegisterUtil.CreateOrUpdateCluster(host, cloudId, clusterId, connectorId, connectorInstall, http.MethodPut, apiToken) - - assert.Equal(t, "", clusterInfo.ID) - assert.Equal(t, "", clusterInfo.Name) - assert.Contains(t, errorReason, "Failed to make PUT call to") - assert.EqualError(t, err, "error updating cluster: this is an error") - }) - - t.Run("TestCreateOrUpdateCluster__ReturnsClusterInfoWhenClusterGetsUpdated", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, _ := createClusterRegister(AstraConnectorInput{}) - - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 201, - Body: nil, - }, nil).Once() - - clusterInfo, errorReason, err := clusterRegisterUtil.CreateOrUpdateCluster(host, cloudId, clusterId, connectorId, connectorInstall, http.MethodPut, apiToken) - - assert.Equal(t, "test_clusterId", clusterInfo.ID) - assert.Equal(t, "", clusterInfo.Name) - assert.Equal(t, "", clusterInfo.ManagedState) - assert.Equal(t, "", errorReason) - assert.Nil(t, err) - }) -} - -func TestUpdateManagedCluster(t *testing.T) { - host, clusterId, connectorId, apiToken, connectorInstall := "test_host", "test_clusterId", "test_connectorId", "test_apiToken", "installed" - - t.Run("TestUpdateManagedCluster__HTTPPutRequestFailsReturnError", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, _ := createClusterRegister(AstraConnectorInput{}) - - errorText := "error on put request update" - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{}, errors.New(errorText)) - - errorReason, err := clusterRegisterUtil.UpdateManagedCluster(host, clusterId, connectorId, connectorInstall, apiToken) - assert.Contains(t, errorReason, "Failed to make PUT call to") - assert.EqualError(t, err, "error on put request update") - }) - - t.Run("TestUpdateManagedCluster__HTTPPutRequestInvalidStatusCodeReturnError", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, _ := createClusterRegister(AstraConnectorInput{}) - - mockHttpClient.On("Do", mock.Anything).Return(mockHttpRes400, nil).Times(3) - - errorReason, err := clusterRegisterUtil.UpdateManagedCluster(host, clusterId, connectorId, connectorInstall, apiToken) - assert.Contains(t, errorReason, "Failed to make PUT call") - assert.EqualError(t, err, "UpdateManagedCluster: Failed to make PUT call to test_host/accounts//topology/v1/managedClusters/test_clusterId with status Mock Error: update managed cluster failed with: 400") - }) - - t.Run("TestUpdateManagedCluster__ClusterUpdatedReturnNil", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, _ := createClusterRegister(AstraConnectorInput{}) - - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 201, - Body: nil, - }, nil).Once() - - errorReason, err := clusterRegisterUtil.UpdateManagedCluster(host, clusterId, connectorId, connectorInstall, apiToken) - assert.Equal(t, "", errorReason) - assert.Nil(t, err) - }) -} - -func TestCreateManagedCluster(t *testing.T) { - host, cloudId, clusterId, apiToken, connectorInstalled := "test_host", "test_cloudId", "test_clusterId", "test_apiToken", "installed" - - t.Run("TestCreateManagedCluster__HTTPPostRequestFailsReturnError", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, _ := createClusterRegister(AstraConnectorInput{}) - - errorText := "error on post request create" - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{}, errors.New(errorText)) - - errorReason, err := clusterRegisterUtil.CreateManagedCluster(host, cloudId, clusterId, connectorInstalled, apiToken) - assert.Contains(t, errorReason, "Failed to make POST call to") - assert.EqualError(t, err, "error on post request create") - }) - - t.Run("TestCreateManagedCluster__HTTPPostRequestInvalidStatusCodeReturnError", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, _ := createClusterRegister(AstraConnectorInput{}) - - mockHttpClient.On("Do", mock.Anything).Return(mockHttpRes400, nil).Times(3) - - errorReason, err := clusterRegisterUtil.CreateManagedCluster(host, cloudId, clusterId, connectorInstalled, apiToken) - assert.Contains(t, errorReason, "Failed to make POST call") - assert.EqualError(t, err, "CreateManagedCluster: Failed to make POST call to test_host/accounts//topology/v1/managedClusters with status Mock Error: manage cluster failed with: 400") - }) - - t.Run("TestCreateManagedCluster__ReadResponseBodyErrorReturnError", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, _ := createClusterRegister(AstraConnectorInput{}) - - mockRead := mockRead{} - mockRead.On("Read", mock.Anything).Return(0, errors.New("error reading")) - mockRead.On("Close").Return(errors.New("error closing")) - - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 201, - Body: &mockRead, - }, nil).Once() - - errorReason, err := clusterRegisterUtil.CreateManagedCluster(host, cloudId, clusterId, connectorInstalled, apiToken) - assert.Contains(t, errorReason, "Failed to read response from POST call to") - assert.EqualError(t, err, "error reading") - }) - - t.Run("TestCreateManagedCluster__UnmarshalBodyErrorReturnError", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, _ := createClusterRegister(AstraConnectorInput{}) - - ret := io.NopCloser(bytes.NewReader([]byte(`items:{"Name":"Joe","Body":"Hello","Time":1294706395881547069`))) - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 201, - Body: ret, - }, nil).Once() - - errorReason, err := clusterRegisterUtil.CreateManagedCluster(host, cloudId, clusterId, connectorInstalled, apiToken) - assert.Contains(t, errorReason, "Failed to unmarshal response from POST call to") - assert.ErrorContains(t, err, "invalid character 'i' looking for beginning of value") - }) - - t.Run("TestCreateManagedCluster__ClusterManagedReturnNil", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, _ := createClusterRegister(AstraConnectorInput{}) - - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 201, - Body: io.NopCloser(bytes.NewReader([]byte(`{"id":"1234","name":"test-cluster","managedState":"managed"}`))), - }, nil).Once() - - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 200, - Body: io.NopCloser(bytes.NewReader([]byte(`{"id":"1234","name":"test-cluster","managedState":"managed"}`))), - }, nil).Once() - - errorReason, err := clusterRegisterUtil.CreateManagedCluster(host, cloudId, clusterId, connectorInstalled, apiToken) - assert.Equal(t, "", errorReason) - assert.Nil(t, err) - }) -} - -func TestCreateOrUpdateManagedCluster(t *testing.T) { - host, cloudId, clusterId, connectorId, apiToken := "test_host", "test_cloudId", "test_clusterId", "test_connectorId", "test_apiToken" - - t.Run("TestCreateOrUpdateManagedCluster__ReturnsErrorWhenUpdateManagedClusterFails", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, _ := createClusterRegister(AstraConnectorInput{}) - - errorText := "this is an error" - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{}, errors.New(errorText)) - - clusterInfo, errorReason, err := clusterRegisterUtil.CreateOrUpdateManagedCluster(host, cloudId, clusterId, connectorId, http.MethodPut, apiToken) - - assert.Equal(t, clusterId, clusterInfo.ID) - assert.Equal(t, "", clusterInfo.Name) - assert.Contains(t, errorReason, "Failed to make PUT call to") - assert.EqualError(t, err, "error updating managed cluster: this is an error") - }) - - t.Run("TestCreateOrUpdateManagedCluster__ReturnsClusterInfoWhenManagedClusterGetsUpdated", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, _ := createClusterRegister(AstraConnectorInput{}) - - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 201, - Body: nil, - }, nil).Once() - - clusterInfo, errorReason, err := clusterRegisterUtil.CreateOrUpdateManagedCluster(host, cloudId, clusterId, connectorId, http.MethodPut, apiToken) - - assert.Equal(t, clusterId, clusterInfo.ID) - assert.Equal(t, "", clusterInfo.Name) - assert.Equal(t, "managed", clusterInfo.ManagedState) - assert.Equal(t, "", errorReason) - assert.Nil(t, err) - }) - - t.Run("TestCreateOrUpdateManagedCluster__ReturnsErrorWhenCreateManagedClusterFails", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, _ := createClusterRegister(AstraConnectorInput{}) - - errorText := "this is an error" - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{}, errors.New(errorText)) - - clusterInfo, errorReason, err := clusterRegisterUtil.CreateOrUpdateManagedCluster(host, cloudId, clusterId, connectorId, http.MethodPost, apiToken) - - assert.Equal(t, clusterId, clusterInfo.ID) - assert.Equal(t, "", clusterInfo.Name) - assert.Contains(t, errorReason, "Failed to make POST call to") - assert.EqualError(t, err, "error creating managed cluster: this is an error") - }) - - t.Run("TestCreateOrUpdateManagedCluster__ReturnsClusterInfoWhenClusterGetsManaged", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, _ := createClusterRegister(AstraConnectorInput{}) - - ret := io.NopCloser(bytes.NewReader([]byte(`{"id":"test_cluster","name":"test-cluster","managedState":"managed"}`))) - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 201, - Body: ret, - }, nil).Once() - - ret = io.NopCloser(bytes.NewReader([]byte(`{"id":"test_cluster","name":"test-cluster","managedState":"managed"}`))) - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 200, - Body: ret, - }, nil).Once() - - clusterInfo, errorReason, err := clusterRegisterUtil.CreateOrUpdateManagedCluster(host, cloudId, clusterId, connectorId, http.MethodPost, apiToken) - - assert.Equal(t, clusterId, clusterInfo.ID) - assert.Equal(t, "", clusterInfo.Name) - assert.Equal(t, "managed", clusterInfo.ManagedState) - assert.Equal(t, "", errorReason) - assert.Nil(t, err) - }) -} - -func TestValidateAndGetCluster(t *testing.T) { - host, cloudId, apiToken := "test_host", "test_cloudId", "test_apiToken" - - t.Run("TestValidateAndGetCluster__GetClusterReturnsErrorReturnError", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, _ := createClusterRegister(AstraConnectorInput{cloudId: true, clusterId: true}) - - errorText := "error on get request" - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{}, errors.New(errorText)) - - clusterInfo, errorReason, err := clusterRegisterUtil.ValidateAndGetCluster(host, cloudId, apiToken, "1234") - - assert.Equal(t, "", clusterInfo.ID) - assert.Equal(t, "", clusterInfo.Name) - assert.Contains(t, errorReason, "Failed to make GET call to") - assert.EqualError(t, err, "error on get cluster: error on get request") - }) - - t.Run("TestValidateAndGetCluster__GetClusterReturnsEmptyClusterInfoReturnError", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, _ := createClusterRegister(AstraConnectorInput{cloudId: true, clusterId: true}) - - ret := io.NopCloser(bytes.NewReader([]byte(`{"id":"","name":"this is a cluster"}`))) - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 200, - Body: ret, - }, nil).Once() - - clusterInfo, errorReason, err := clusterRegisterUtil.ValidateAndGetCluster(host, cloudId, apiToken, "1234") - - assert.Equal(t, "", clusterInfo.ID) - assert.Equal(t, "", clusterInfo.Name) - assert.Equal(t, "Invalid ClusterId 1234 provided in the Spec", errorReason) - assert.EqualError(t, err, "Invalid ClusterId 1234 provided in the Spec") - }) - - t.Run("TestValidateAndGetCluster__ValidClusterIdProvidedInTheSpecReturnClusterInfo", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, _ := createClusterRegister(AstraConnectorInput{cloudId: true, clusterId: true}) - - ret := io.NopCloser(bytes.NewReader([]byte(`{"id":"1234","name":"this is a cluster"}`))) - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 200, - Body: ret, - }, nil).Once() - - clusterInfo, errorReason, err := clusterRegisterUtil.ValidateAndGetCluster(host, cloudId, apiToken, "1234") - - assert.Equal(t, "1234", clusterInfo.ID) - assert.Equal(t, "this is a cluster", clusterInfo.Name) - assert.Equal(t, "", errorReason) - assert.Nil(t, err) - }) - - t.Run("TestValidateAndGetCluster__GetDefaultServiceFailsReturnError", func(t *testing.T) { - clusterRegisterUtil, _, _, _ := createClusterRegister(AstraConnectorInput{}) - - clusterInfo, errorReason, err := clusterRegisterUtil.ValidateAndGetCluster(host, cloudId, apiToken, "") - - assert.Equal(t, "", clusterInfo.ID) - assert.Equal(t, "", clusterInfo.Name) - assert.Equal(t, "Failed to get kubernetes service from default namespace", errorReason) - assert.EqualError(t, err, "services \"kubernetes\" not found") - }) - - t.Run("TestValidateAndGetCluster__GetClustersFailReturnError", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, fakeClient := createClusterRegister(AstraConnectorInput{}) - - service := &coreV1.Service{ - ObjectMeta: metaV1.ObjectMeta{ - Name: "kubernetes", - Namespace: "default", - UID: "svc-uid", - }, - } - - // creating secret - err := fakeClient.Create(ctx, service) - assert.NoError(t, err) - - errorText := "error on get request" - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{}, errors.New(errorText)) - - clusterInfo, errorReason, err := clusterRegisterUtil.ValidateAndGetCluster(host, cloudId, apiToken, "") - - assert.Equal(t, "", clusterInfo.ID) - assert.Equal(t, "", clusterInfo.Name) - assert.Contains(t, errorReason, "Failed to make GET call to") - assert.EqualError(t, err, "error on get clusters: error on get request") - }) - - t.Run("TestValidateAndGetCluster__ClusterWithMatchingUUIDFoundReturnClusterInfo", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, fakeClient := createClusterRegister(AstraConnectorInput{}) - - service := &coreV1.Service{ - ObjectMeta: metaV1.ObjectMeta{ - Name: "kubernetes", - Namespace: "default", - UID: "svc-uid", - }, - } - - // creating secret - err := fakeClient.Create(ctx, service) - assert.NoError(t, err) - - ret := io.NopCloser(bytes.NewReader([]byte(`{"items":[{"id":"1234","name":"cluster1", "apiServiceID":"svc-uid", "connectorInstall":"installed"}, {"id":"5678","name":"cluster2"}]}`))) - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 200, - Body: ret, - }, nil).Once() - - clusterInfo, errorReason, err := clusterRegisterUtil.ValidateAndGetCluster(host, cloudId, apiToken, "") - - assert.Equal(t, "1234", clusterInfo.ID) - assert.Equal(t, "cluster1", clusterInfo.Name) - assert.Equal(t, "", errorReason) - assert.Nil(t, err) - }) - - t.Run("TestValidateAndGetCluster__ClusterWithMatchingUUIDNotFoundReturnEmptyClusterInfo", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, fakeClient := createClusterRegister(AstraConnectorInput{}) - - service := &coreV1.Service{ - ObjectMeta: metaV1.ObjectMeta{ - Name: "kubernetes", - Namespace: "default", - UID: "svc-uid", - }, - } - - // creating secret - err := fakeClient.Create(ctx, service) - assert.NoError(t, err) - - ret := io.NopCloser(bytes.NewReader([]byte(`{"items":[{"id":"1234","name":"cluster1", "apiServiceID":"svc-uid11"}, {"id":"5678","name":"cluster2"}]}`))) - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 200, - Body: ret, - }, nil).Once() - - clusterInfo, errorReason, err := clusterRegisterUtil.ValidateAndGetCluster(host, cloudId, apiToken, "") - - assert.Equal(t, "", clusterInfo.ID) - assert.Equal(t, "", clusterInfo.Name) - assert.Equal(t, "", errorReason) - assert.Nil(t, err) - }) -} - -func TestUnmanageCluster(t *testing.T) { - t.Run("TestUnManageCluster_GetApiTokenFromSecretError", func(t *testing.T) { - clusterRegisterUtil, _, _, _ := createClusterRegister(AstraConnectorInput{}) - - err := clusterRegisterUtil.UnmanageCluster("1234") - - assert.Error(t, err) - assert.EqualError(t, err, "secrets \"astra-token\" not found") - }) - - t.Run("TestUnManageCluster_GetCloudIdError", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, _ := createClusterRegister(AstraConnectorInput{createTokenSecret: true}) - - errorText := "error on get request" - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{}, errors.New(errorText)) - - err := clusterRegisterUtil.UnmanageCluster("1234") - - assert.Error(t, err) - assert.EqualError(t, err, "timed out querying Astra API") - }) - - t.Run("TestUnManageCluster_UnmanageRequestError", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, _ := createClusterRegister(AstraConnectorInput{createTokenSecret: true}) - - ret := io.NopCloser(bytes.NewReader([]byte(`{"items":[{"id":"1234","name":"cloud1", "cloudType":"private"}, {"id":"5678","name":"cloud2","cloudType":"not-private"}]}`))) - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 200, - Body: ret, - }, nil).Once() - - errorText := "error on DELETE request" - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{}, errors.New(errorText)).Once() - - err := clusterRegisterUtil.UnmanageCluster("1234") - - assert.Error(t, err) - assert.EqualError(t, err, "UnmanageCluster: Failed to make DELETE call to https://astra.netapp.io/accounts//topology/v1/managedClusters/1234 with status : error on DELETE request") - }) - - t.Run("TestUnManageCluster_UnmanageRequestNonOkStatus", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, _ := createClusterRegister(AstraConnectorInput{createTokenSecret: true}) - - ret := io.NopCloser(bytes.NewReader([]byte(`{"items":[{"id":"1234","name":"cloud1", "cloudType":"private"}, {"id":"5678","name":"cloud2","cloudType":"not-private"}]}`))) - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 200, - Body: ret, - }, nil).Once() - - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 400, - }, nil).Once() - - err := clusterRegisterUtil.UnmanageCluster("1234") - - assert.Error(t, err) - assert.EqualError(t, err, "UnmanageCluster: Failed to make DELETE call to https://astra.netapp.io/accounts//topology/v1/managedClusters/1234 with status ") - }) - - t.Run("TestUnManageCluster_RemoveRequestError", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, _ := createClusterRegister(AstraConnectorInput{createTokenSecret: true}) - - ret := io.NopCloser(bytes.NewReader([]byte(`{"items":[{"id":"1234","name":"cloud1", "cloudType":"private"}, {"id":"5678","name":"cloud2","cloudType":"not-private"}]}`))) - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 200, - Body: ret, - }, nil).Once() - - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 204, - }, nil).Once() - - errorText := "error on DELETE request" - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{}, errors.New(errorText)).Once() - - err := clusterRegisterUtil.UnmanageCluster("1234") - - assert.Error(t, err) - assert.EqualError(t, err, "UnmanageCluster: Failed to make DELETE call to https://astra.netapp.io/accounts//topology/v1/clouds/1234/clusters/1234 with status : error on DELETE request") - }) - - t.Run("TestUnManageCluster_RemoveRequestNonOkStatus", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, _ := createClusterRegister(AstraConnectorInput{createTokenSecret: true}) - - ret := io.NopCloser(bytes.NewReader([]byte(`{"items":[{"id":"1234","name":"cloud1", "cloudType":"private"}, {"id":"5678","name":"cloud2","cloudType":"not-private"}]}`))) - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 200, - Body: ret, - }, nil).Once() - - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 204, - }, nil).Once() - - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 400, - }, nil).Once() - - err := clusterRegisterUtil.UnmanageCluster("1234") - - assert.Error(t, err) - assert.EqualError(t, err, "UnmanageCluster: Failed to make DELETE call to https://astra.netapp.io/accounts//topology/v1/clouds/1234/clusters/1234 with status ") - }) - - t.Run("TestUnManageCluster_Success", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, _ := createClusterRegister(AstraConnectorInput{createTokenSecret: true}) - - ret := io.NopCloser(bytes.NewReader([]byte(`{"items":[{"id":"1234","name":"cloud1", "cloudType":"private"}, {"id":"5678","name":"cloud2","cloudType":"not-private"}]}`))) - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 200, - Body: ret, - }, nil).Once() - - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 204, - }, nil).Once() - - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 204, - }, nil).Once() - - err := clusterRegisterUtil.UnmanageCluster("1234") - - assert.NoError(t, err) - }) -} - -func TestGetAPITokenFromSecret(t *testing.T) { - t.Run("GetAPITokenFromSecret__SecretNotPresentReturnsError", func(t *testing.T) { - clusterRegisterUtil, _, _, _ := createClusterRegister(AstraConnectorInput{}) - apiToken, errorReason, err := clusterRegisterUtil.GetAPITokenFromSecret("astra-token") - - assert.Equal(t, apiToken, "") - assert.Equal(t, "Failed to get secret astra-token", errorReason) - assert.EqualError(t, err, "secrets \"astra-token\" not found") - }) - - t.Run("GetAPITokenFromSecret__SecretInvalidReturnsError", func(t *testing.T) { - clusterRegisterUtil, _, apiTokenSecret, fakeClient := createClusterRegister(AstraConnectorInput{}) - - secret := &coreV1.Secret{ - ObjectMeta: metaV1.ObjectMeta{ - Name: apiTokenSecret, - Namespace: testNamespace, - }, - Data: map[string][]byte{ - "api-token": []byte("auth-token"), - }, - } - - // creating secret - err := fakeClient.Create(ctx, secret) - assert.NoError(t, err) - - apiToken, errorReason, err := clusterRegisterUtil.GetAPITokenFromSecret(apiTokenSecret) - - assert.Equal(t, apiToken, "") - assert.Equal(t, "Failed to extract 'apiToken' key from secret astra-token", errorReason) - assert.EqualError(t, err, "failed to extract apiToken key from secret") - }) - - t.Run("GetAPITokenFromSecret__ReturnsApiToken", func(t *testing.T) { - clusterRegisterUtil, _, apiTokenSecret, _ := createClusterRegister(AstraConnectorInput{createTokenSecret: true}) - - apiToken, errorReason, err := clusterRegisterUtil.GetAPITokenFromSecret(apiTokenSecret) - assert.Equal(t, apiToken, "auth-token") - assert.Equal(t, "", errorReason) - assert.NoError(t, err) - }) -} - -func TestRegisterClusterWithAstra(t *testing.T) { - connectorId := "test_connectorId" - - t.Run("TestRegisterClusterWithAstra__SetHttpClientFailsReturnError", func(t *testing.T) { - clusterRegisterUtil, _, _, _ := createClusterRegister(AstraConnectorInput{invalidHostDetails: true}) - - _, errorReason, err := clusterRegisterUtil.RegisterClusterWithAstra(connectorId, "") - assert.Equal(t, "Failed to set TLS Config", errorReason) - assert.EqualError(t, err, "invalid cloudBridgeURL provided: test_url, format - https://hostname") - }) - - t.Run("TestRegisterClusterWithAstra__GetAPITokenFromSecretFailsReturnError", func(t *testing.T) { - clusterRegisterUtil, _, _, _ := createClusterRegister(AstraConnectorInput{}) - - _, errorReason, err := clusterRegisterUtil.RegisterClusterWithAstra(connectorId, "") - assert.Equal(t, "Failed to get secret astra-token", errorReason) - assert.EqualError(t, err, "secrets \"astra-token\" not found") - }) - - t.Run("TestRegisterClusterWithAstra__GetOrCreateCloudFailsReturnError", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, _ := createClusterRegister(AstraConnectorInput{createTokenSecret: true, cloudId: true}) - - // For GetOrCreateCloud call - errorText := "error on get or create cloud" - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{}, errors.New(errorText)) - - _, errorReason, err := clusterRegisterUtil.RegisterClusterWithAstra(connectorId, "") - assert.Equal(t, "Invalid CloudId 9876 provided in the Spec", errorReason) - assert.EqualError(t, err, "Invalid CloudId 9876 provided in the Spec") - }) - - t.Run("TestRegisterClusterWithAstra__ValidateAndGetClusterFailsReturnError", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, _ := createClusterRegister(AstraConnectorInput{createTokenSecret: true, cloudId: true}) - - // For GetOrCreateCloud call - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 200, - Body: nil, - }, nil).Once() - - // For ValidateAndGetCluster call - errorText := "error on validate and get cluster" - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{}, errors.New(errorText)) - - _, errorReason, err := clusterRegisterUtil.RegisterClusterWithAstra(connectorId, "") - assert.Equal(t, "Failed to get kubernetes service from default namespace", errorReason) - assert.EqualError(t, err, "services \"kubernetes\" not found") - }) - - t.Run("TestRegisterClusterWithAstra__CreateOrUpdateClusterFailsReturnError", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, fakeClient := createClusterRegister(AstraConnectorInput{createTokenSecret: true, cloudId: true}) - - // For GetOrCreateCloud call - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 200, - Body: nil, - }, nil).Once() - - // For ValidateAndGetCluster call - service := &coreV1.Service{ - ObjectMeta: metaV1.ObjectMeta{ - Name: "kubernetes", - Namespace: "default", - UID: "svc-uid", - }, - } - - err := fakeClient.Create(ctx, service) - assert.NoError(t, err) - - ret := io.NopCloser(bytes.NewReader([]byte(`{"items":[{"id":"1234","name":"cluster1", "apiServiceID":"svc-uid", "managedState":"unmanaged", "connectorInstall":"installed"}, {"id":"5678","name":"cluster2"}]}`))) - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 200, - Body: ret, - }, nil).Once() - - // For CreateOrUpdateCluster call - errorText := "error on create or update cluster" - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{}, errors.New(errorText)) - - _, errorReason, err := clusterRegisterUtil.RegisterClusterWithAstra(connectorId, "") - assert.Equal(t, "UpdateCluster: Failed to make PUT call to https://astra.netapp.io/accounts//topology/v1/clouds/9876/clusters/1234 with status : error on create or update cluster", errorReason) - assert.EqualError(t, err, "error updating cluster: error on create or update cluster") - }) - - t.Run("TestRegisterClusterWithAstra__CreateOrUpdateManagedClusterFailsReturnError", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, fakeClient := createClusterRegister(AstraConnectorInput{createTokenSecret: true, cloudId: true}) - - // For GetOrCreateCloud call - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 200, - Body: nil, - }, nil).Once() - - // For ValidateAndGetCluster call - service := &coreV1.Service{ - ObjectMeta: metaV1.ObjectMeta{ - Name: "kubernetes", - Namespace: "default", - UID: "svc-uid", - }, - } - - err := fakeClient.Create(ctx, service) - assert.NoError(t, err) - - ret := io.NopCloser(bytes.NewReader([]byte(`{"items":[{"id":"1234","name":"cluster1", "apiServiceID":"svc-uid", "managedState":"unmanaged", "connectorInstall":"installed"}, {"id":"5678","name":"cluster2"}]}`))) - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 200, - Body: ret, - }, nil).Once() - - // For CreateOrUpdateCluster call - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 201, - Body: nil, - }, nil).Once() - - errorText := "this is an error" - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{}, errors.New(errorText)) - - _, errorReason, err := clusterRegisterUtil.RegisterClusterWithAstra(connectorId, "") - assert.Equal(t, "CreateManagedCluster: Failed to make POST call to https://astra.netapp.io/accounts//topology/v1/managedClusters with status : this is an error", errorReason) - assert.EqualError(t, err, "error creating managed cluster: this is an error") - }) - - t.Run("TestRegisterClusterWithAstra__EverythingWorksReturnNil", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, fakeClient := createClusterRegister(AstraConnectorInput{createTokenSecret: true, cloudId: true}) - - // For GetOrCreateCloud call - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 200, - Body: nil, - }, nil).Once() - - // For ValidateAndGetCluster call - service := &coreV1.Service{ - ObjectMeta: metaV1.ObjectMeta{ - Name: "kubernetes", - Namespace: "default", - UID: "svc-uid", - }, - } - - err := fakeClient.Create(ctx, service) - assert.NoError(t, err) - - ret := io.NopCloser(bytes.NewReader([]byte(`{"items":[{"id":"1234","name":"cluster1", "apiServiceID":"svc-uid", "managedState":"unmanaged", "connectorInstall":"installed"}, {"id":"5678","name":"cluster2"}]}`))) - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 200, - Body: ret, - }, nil).Once() - - // For CreateOrUpdateCluster call - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 201, - Body: nil, - }, nil).Once() - - ret = io.NopCloser(bytes.NewReader([]byte(`{"id":"test_cluster","name":"test-cluster","managedState":"managed"}`))) - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 201, - Body: ret, - }, nil).Once() - - // For poll call - ret = io.NopCloser(bytes.NewReader([]byte(`{"id":"test_cluster","name":"test-cluster","managedState":"managed"}`))) - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 200, - Body: ret, - }, nil).Once() - - _, errorReason, err := clusterRegisterUtil.RegisterClusterWithAstra(connectorId, "") - assert.Equal(t, "", errorReason) - assert.Nil(t, err) - }) - - t.Run("TestRegisterClusterWithAstra__EverythingWorksWhenExistingClusterInManagedStateReturnNil", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, fakeClient := createClusterRegister(AstraConnectorInput{createTokenSecret: true, cloudId: true}) - - // For GetOrCreateCloud call - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 200, - Body: nil, - }, nil).Once() - - // For ValidateAndGetCluster call - service := &coreV1.Service{ - ObjectMeta: metaV1.ObjectMeta{ - Name: "kubernetes", - Namespace: "default", - UID: "svc-uid", - }, - } - - err := fakeClient.Create(ctx, service) - assert.NoError(t, err) - - ret := io.NopCloser(bytes.NewReader([]byte(`{"items":[{"id":"1234","name":"cluster1", "apiServiceID":"svc-uid", "managedState":"managed", "connectorInstall":"installed"}, {"id":"5678","name":"cluster2"}]}`))) - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 200, - Body: ret, - }, nil).Once() - - // For CreateOrUpdateCluster call, no call will be made - - // For CreateOrUpdateManagedCluster call - ret = io.NopCloser(bytes.NewReader([]byte(`{"items":[{"id":"1234","name":"test_sc1234"}, {"id":"5678","name":"test-sc1","isDefault":"true"}]}`))) - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 200, - Body: ret, - }, nil).Once() - - ret = io.NopCloser(bytes.NewReader([]byte(`{"id":"test_cluster","name":"test-cluster","managedState":"managed"}`))) - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 201, - Body: ret, - }, nil).Once() - - _, errorReason, err := clusterRegisterUtil.RegisterClusterWithAstra(connectorId, "") - assert.Equal(t, "", errorReason) - assert.Nil(t, err) - }) - - t.Run("TestRegisterClusterWithAstra__EverythingWorksWhenNoExistingClusterReturnNil", func(t *testing.T) { - clusterRegisterUtil, mockHttpClient, _, fakeClient := createClusterRegister(AstraConnectorInput{createTokenSecret: true, cloudId: true}) - - // For GetOrCreateCloud call - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 200, - Body: nil, - }, nil).Once() - - // For ValidateAndGetCluster call - service := &coreV1.Service{ - ObjectMeta: metaV1.ObjectMeta{ - Name: "kubernetes", - Namespace: "default", - UID: "svc-uid", - }, - } - - err := fakeClient.Create(ctx, service) - assert.NoError(t, err) - - ret := io.NopCloser(bytes.NewReader([]byte(`{"items":[{"id":"1234","name":"cluster1", "apiServiceID":"svc-uid12", "managedState":"managed"}, {"id":"5678","name":"cluster2"}]}`))) - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 200, - Body: ret, - }, nil).Once() - - // For CreateOrUpdateCluster call, post call will be made - ret = io.NopCloser(bytes.NewReader([]byte(`{"id":"1234","name":"test-cluster","managedState":"unmanaged"}`))) - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 201, - Body: ret, - }, nil).Once() - - ret = io.NopCloser(bytes.NewReader([]byte(`{"id":"test_cluster","name":"test-cluster","managedState":"managed"}`))) - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 201, - Body: ret, - }, nil).Once() - - ret = io.NopCloser(bytes.NewReader([]byte(`{"id":"test_cluster","name":"test-cluster","managedState":"managed"}`))) - mockHttpClient.On("Do", mock.Anything).Return(&http.Response{ - StatusCode: 200, - Body: ret, - }, nil).Once() - - _, errorReason, err := clusterRegisterUtil.RegisterClusterWithAstra(connectorId, "") - assert.Equal(t, "", errorReason) - assert.Nil(t, err) - }) -} diff --git a/asup.yaml b/asup.yaml new file mode 100644 index 00000000..2398cf1b --- /dev/null +++ b/asup.yaml @@ -0,0 +1,13 @@ +apiVersion: management.astra.netapp.io/v1 +kind: AutoSupportBundleSchedule +metadata: + name: autosupportbundleschedule-123 + namespace: neptune-system +spec: + enabled: false + + + + + autoSupport: + enrolled: false diff --git a/common/neptune-version.go b/common/neptune-version.go new file mode 100644 index 00000000..56c4188d --- /dev/null +++ b/common/neptune-version.go @@ -0,0 +1,3 @@ +package common + +const NeptuneDefaultImage2 = "controller:73846b5" diff --git a/common/neptune-version.txt b/common/neptune-version.txt new file mode 100644 index 00000000..1892a242 --- /dev/null +++ b/common/neptune-version.txt @@ -0,0 +1 @@ +73846b5 \ No newline at end of file diff --git a/common/neptune_manager_tag.txt b/common/neptune_manager_tag.txt index 296663c6..369a372c 100644 --- a/common/neptune_manager_tag.txt +++ b/common/neptune_manager_tag.txt @@ -1 +1 @@ -5d887e4 +02665bc diff --git a/details/operator-sdk/config/certmanager/certificate.yaml b/config/certmanager/certificate.yaml similarity index 100% rename from details/operator-sdk/config/certmanager/certificate.yaml rename to config/certmanager/certificate.yaml diff --git a/details/operator-sdk/config/certmanager/kustomization.yaml b/config/certmanager/kustomization.yaml similarity index 100% rename from details/operator-sdk/config/certmanager/kustomization.yaml rename to config/certmanager/kustomization.yaml diff --git a/details/operator-sdk/config/certmanager/kustomizeconfig.yaml b/config/certmanager/kustomizeconfig.yaml similarity index 100% rename from details/operator-sdk/config/certmanager/kustomizeconfig.yaml rename to config/certmanager/kustomizeconfig.yaml diff --git a/config/crd/bases/astra.netapp.io_astraconnectors.yaml b/config/crd/bases/astra.netapp.io_astraconnectors.yaml new file mode 100644 index 00000000..59d6b9f0 --- /dev/null +++ b/config/crd/bases/astra.netapp.io_astraconnectors.yaml @@ -0,0 +1,102 @@ + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.7.0 + creationTimestamp: null + name: astraconnectors.astra.netapp.io +spec: + group: astra.netapp.io + names: + kind: AstraConnector + listKind: AstraConnectorList + plural: astraconnectors + singular: astraconnector + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.version + name: AstraConnectorVersion + type: string + - jsonPath: .status.status + name: Status + type: string + name: v1 + schema: + openAPIV3Schema: + description: AstraConnector is the Schema for the astraconnectors API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: AstraConnectorSpec defines the desired state of AstraConnector + properties: + accountId: + type: string + apiTokenSecretRef: + type: string + astraControlUrl: + type: string + cloudId: + type: string + clusterId: + type: string + hostAliasIP: + type: string + image: + type: string + imageRegistry: + properties: + name: + type: string + secret: + type: string + type: object + labels: + additionalProperties: + type: string + description: Labels any additional labels wanted to be added to resources + type: object + replicas: + default: 1 + format: int32 + type: integer + skipPreCheck: + default: false + description: SkipPreCheck determines if you want to skip pre-checks + and go ahead with the installation. + type: boolean + skipTLSValidation: + type: boolean + type: object + status: + description: AstraConnectorStatus defines the observed state of AstraConnector + properties: + status: + type: string + version: + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/config/crd/bases/astra.netapp.io_astraneptunes.yaml b/config/crd/bases/astra.netapp.io_astraneptunes.yaml new file mode 100644 index 00000000..96edf160 --- /dev/null +++ b/config/crd/bases/astra.netapp.io_astraneptunes.yaml @@ -0,0 +1,105 @@ + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.7.0 + creationTimestamp: null + name: astraneptunes.astra.netapp.io +spec: + group: astra.netapp.io + names: + kind: AstraNeptune + listKind: AstraNeptuneList + plural: astraneptunes + singular: astraneptune + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.version + name: AstraNeptuneVersion + type: string + - jsonPath: .status.status + name: Status + type: string + name: v1 + schema: + openAPIV3Schema: + description: AstraNeptune is the Schema for the astraneptunes API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: AstraNeptuneSpec defines the desired state of AstraNeptune + properties: + autoSupport: + default: + enrolled: true + url: https://stagesupport.netapp.com/put/AsupPut + description: AutoSupport indicates willingness to participate in NetApp's + proactive support application, NetApp Active IQ. An internet connection + is required (port 442) and all support data is anonymized. The default + election is true and indicates support data will be sent to NetApp. + An empty or blank election is the same as a default election. Air + gapped installations should enter false. + properties: + enrolled: + default: true + description: Enrolled determines if you want to send anonymous + data to NetApp for support purposes. + type: boolean + url: + default: https://stagesupport.netapp.com/put/AsupPut + description: URL determines where the anonymous data will be sent + type: string + type: object + image: + type: string + imageRegistry: + properties: + name: + type: string + secret: + type: string + type: object + labels: + additionalProperties: + type: string + description: Labels any additional labels wanted to be added to resources + type: object + skipPreCheck: + default: false + description: SkipPreCheck determines if you want to skip pre-checks + and go ahead with the installation. + type: boolean + type: object + status: + description: AstraNeptuneStatus defines the observed state of AstraNeptune + properties: + status: + type: string + version: + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/details/operator-sdk/config/crd/kustomization.yaml b/config/crd/kustomization.yaml similarity index 95% rename from details/operator-sdk/config/crd/kustomization.yaml rename to config/crd/kustomization.yaml index a2be3a4e..da9a1f24 100644 --- a/details/operator-sdk/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -3,6 +3,7 @@ # It should be run by config/default resources: - bases/astra.netapp.io_astraconnectors.yaml +- bases/astra.netapp.io_astraneptunes.yaml #+kubebuilder:scaffold:crdkustomizeresource patchesStrategicMerge: diff --git a/details/operator-sdk/config/crd/kustomizeconfig.yaml b/config/crd/kustomizeconfig.yaml similarity index 100% rename from details/operator-sdk/config/crd/kustomizeconfig.yaml rename to config/crd/kustomizeconfig.yaml diff --git a/details/operator-sdk/config/crd/patches/cainjection_in_astrainstallers.yaml b/config/crd/patches/cainjection_in_astrainstallers.yaml similarity index 100% rename from details/operator-sdk/config/crd/patches/cainjection_in_astrainstallers.yaml rename to config/crd/patches/cainjection_in_astrainstallers.yaml diff --git a/details/operator-sdk/config/crd/patches/webhook_in_astrainstallers.yaml b/config/crd/patches/webhook_in_astrainstallers.yaml similarity index 100% rename from details/operator-sdk/config/crd/patches/webhook_in_astrainstallers.yaml rename to config/crd/patches/webhook_in_astrainstallers.yaml diff --git a/details/operator-sdk/config/default/kustomization.yaml b/config/default/kustomization.yaml similarity index 100% rename from details/operator-sdk/config/default/kustomization.yaml rename to config/default/kustomization.yaml diff --git a/details/operator-sdk/config/default/manager_auth_proxy_patch.yaml b/config/default/manager_auth_proxy_patch.yaml similarity index 100% rename from details/operator-sdk/config/default/manager_auth_proxy_patch.yaml rename to config/default/manager_auth_proxy_patch.yaml diff --git a/details/operator-sdk/config/default/manager_config_patch.yaml b/config/default/manager_config_patch.yaml similarity index 100% rename from details/operator-sdk/config/default/manager_config_patch.yaml rename to config/default/manager_config_patch.yaml diff --git a/details/operator-sdk/config/default/manager_webhook_patch.yaml b/config/default/manager_webhook_patch.yaml similarity index 100% rename from details/operator-sdk/config/default/manager_webhook_patch.yaml rename to config/default/manager_webhook_patch.yaml diff --git a/details/operator-sdk/config/default/webhookcainjection_patch.yaml b/config/default/webhookcainjection_patch.yaml similarity index 100% rename from details/operator-sdk/config/default/webhookcainjection_patch.yaml rename to config/default/webhookcainjection_patch.yaml diff --git a/details/operator-sdk/config/manager/controller_manager_config.yaml b/config/manager/controller_manager_config.yaml similarity index 100% rename from details/operator-sdk/config/manager/controller_manager_config.yaml rename to config/manager/controller_manager_config.yaml diff --git a/details/operator-sdk/config/manager/kustomization.yaml b/config/manager/kustomization.yaml similarity index 100% rename from details/operator-sdk/config/manager/kustomization.yaml rename to config/manager/kustomization.yaml diff --git a/details/operator-sdk/config/manager/manager.yaml b/config/manager/manager.yaml similarity index 100% rename from details/operator-sdk/config/manager/manager.yaml rename to config/manager/manager.yaml diff --git a/details/operator-sdk/config/manifests/bases/astra-installer.clusterserviceversion.yaml b/config/manifests/bases/astra-installer.clusterserviceversion.yaml similarity index 100% rename from details/operator-sdk/config/manifests/bases/astra-installer.clusterserviceversion.yaml rename to config/manifests/bases/astra-installer.clusterserviceversion.yaml diff --git a/details/operator-sdk/config/manifests/kustomization.yaml b/config/manifests/kustomization.yaml similarity index 100% rename from details/operator-sdk/config/manifests/kustomization.yaml rename to config/manifests/kustomization.yaml diff --git a/details/operator-sdk/config/prometheus/kustomization.yaml b/config/prometheus/kustomization.yaml similarity index 100% rename from details/operator-sdk/config/prometheus/kustomization.yaml rename to config/prometheus/kustomization.yaml diff --git a/details/operator-sdk/config/prometheus/monitor.yaml b/config/prometheus/monitor.yaml similarity index 100% rename from details/operator-sdk/config/prometheus/monitor.yaml rename to config/prometheus/monitor.yaml diff --git a/details/operator-sdk/config/rbac/astrainstaller_editor_role.yaml b/config/rbac/astrainstaller_editor_role.yaml similarity index 100% rename from details/operator-sdk/config/rbac/astrainstaller_editor_role.yaml rename to config/rbac/astrainstaller_editor_role.yaml diff --git a/details/operator-sdk/config/rbac/astrainstaller_viewer_role.yaml b/config/rbac/astrainstaller_viewer_role.yaml similarity index 100% rename from details/operator-sdk/config/rbac/astrainstaller_viewer_role.yaml rename to config/rbac/astrainstaller_viewer_role.yaml diff --git a/details/operator-sdk/config/rbac/auth_proxy_client_clusterrole.yaml b/config/rbac/auth_proxy_client_clusterrole.yaml similarity index 100% rename from details/operator-sdk/config/rbac/auth_proxy_client_clusterrole.yaml rename to config/rbac/auth_proxy_client_clusterrole.yaml diff --git a/details/operator-sdk/config/rbac/auth_proxy_role.yaml b/config/rbac/auth_proxy_role.yaml similarity index 100% rename from details/operator-sdk/config/rbac/auth_proxy_role.yaml rename to config/rbac/auth_proxy_role.yaml diff --git a/details/operator-sdk/config/rbac/auth_proxy_role_binding.yaml b/config/rbac/auth_proxy_role_binding.yaml similarity index 100% rename from details/operator-sdk/config/rbac/auth_proxy_role_binding.yaml rename to config/rbac/auth_proxy_role_binding.yaml diff --git a/details/operator-sdk/config/rbac/auth_proxy_service.yaml b/config/rbac/auth_proxy_service.yaml similarity index 100% rename from details/operator-sdk/config/rbac/auth_proxy_service.yaml rename to config/rbac/auth_proxy_service.yaml diff --git a/details/operator-sdk/config/rbac/kustomization.yaml b/config/rbac/kustomization.yaml similarity index 100% rename from details/operator-sdk/config/rbac/kustomization.yaml rename to config/rbac/kustomization.yaml diff --git a/details/operator-sdk/config/rbac/leader_election_role.yaml b/config/rbac/leader_election_role.yaml similarity index 100% rename from details/operator-sdk/config/rbac/leader_election_role.yaml rename to config/rbac/leader_election_role.yaml diff --git a/details/operator-sdk/config/rbac/leader_election_role_binding.yaml b/config/rbac/leader_election_role_binding.yaml similarity index 100% rename from details/operator-sdk/config/rbac/leader_election_role_binding.yaml rename to config/rbac/leader_election_role_binding.yaml diff --git a/details/operator-sdk/config/rbac/role.yaml b/config/rbac/role.yaml similarity index 82% rename from details/operator-sdk/config/rbac/role.yaml rename to config/rbac/role.yaml index d78ada4c..d7138009 100644 --- a/details/operator-sdk/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -105,3 +105,29 @@ rules: - get - patch - update +- apiGroups: + - astra.netapp.io + resources: + - astraneptunes + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - astra.netapp.io + resources: + - astraneptunes/finalizers + verbs: + - update +- apiGroups: + - astra.netapp.io + resources: + - astraneptunes/status + verbs: + - get + - patch + - update diff --git a/details/operator-sdk/config/rbac/role_binding.yaml b/config/rbac/role_binding.yaml similarity index 100% rename from details/operator-sdk/config/rbac/role_binding.yaml rename to config/rbac/role_binding.yaml diff --git a/details/operator-sdk/config/rbac/service_account.yaml b/config/rbac/service_account.yaml similarity index 100% rename from details/operator-sdk/config/rbac/service_account.yaml rename to config/rbac/service_account.yaml diff --git a/config/samples/astra_v1_astraconnector.yaml b/config/samples/astra_v1_astraconnector.yaml new file mode 100644 index 00000000..1f373228 --- /dev/null +++ b/config/samples/astra_v1_astraconnector.yaml @@ -0,0 +1,32 @@ +apiVersion: astra.netapp.io/v1 +kind: AstraConnector +metadata: + name: astra-connector + namespace: astra-connector +spec: + accountId: Astra Account ID from the API Access page in Astra UI + apiTokenSecretRef: Secret reference to API Token from the API Access page in Astra UI + astraControlUrl: https://integration.astra.netapp.io + hostAliasIP: 10.193.60.80 + skipTLSValidation: true + cloudId: ID of the cloud + clusterId: ID of cluster + imageRegistry: + name: netappdownloads.jfrog.io/docker-astra-control-staging/arch30/neptune + secret: regcred + +#--- +#apiVersion: astra.netapp.io/v1 +#kind: AstraNeptune +#metadata: +# name: astra-connector +# namespace: astra-connector +#spec: +# astra: +# tokenRef: Secret reference to API Token from the API Access page in Astra UI +# accountId: Astra Account ID from the API Access page in Astra UI +# skipTLSValidation: true +# clusterName: Name of your cluster +# natsSyncClient: +# cloudBridgeURL: https://integration.astra.netapp.io +# hostAliasIP: 10.193.60.80 \ No newline at end of file diff --git a/details/operator-sdk/config/samples/kustomization.yaml b/config/samples/kustomization.yaml similarity index 100% rename from details/operator-sdk/config/samples/kustomization.yaml rename to config/samples/kustomization.yaml diff --git a/details/operator-sdk/config/samples/neptune.yaml b/config/samples/neptune.yaml similarity index 100% rename from details/operator-sdk/config/samples/neptune.yaml rename to config/samples/neptune.yaml diff --git a/config/samples/temp.yaml b/config/samples/temp.yaml new file mode 100644 index 00000000..4a44ddfc --- /dev/null +++ b/config/samples/temp.yaml @@ -0,0 +1,21 @@ +apiVersion: v1 +kind: Service +metadata: + labels: + app.kubernetes.io/component: kube-rbac-proxy + app.kubernetes.io/created-by: neptune + app.kubernetes.io/instance: controller-manager-metrics-service + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: service + app.kubernetes.io/part-of: neptune + control-plane: controller-manager + name: neptune-controller-manager-metrics-service + namespace: astra-connector +spec: + ports: + - name: https + port: 8443 + protocol: TCP + targetPort: https + selector: + control-plane: controller-manager diff --git a/details/operator-sdk/config/scorecard/bases/config.yaml b/config/scorecard/bases/config.yaml similarity index 100% rename from details/operator-sdk/config/scorecard/bases/config.yaml rename to config/scorecard/bases/config.yaml diff --git a/details/operator-sdk/config/scorecard/kustomization.yaml b/config/scorecard/kustomization.yaml similarity index 100% rename from details/operator-sdk/config/scorecard/kustomization.yaml rename to config/scorecard/kustomization.yaml diff --git a/details/operator-sdk/config/scorecard/patches/basic.config.yaml b/config/scorecard/patches/basic.config.yaml similarity index 100% rename from details/operator-sdk/config/scorecard/patches/basic.config.yaml rename to config/scorecard/patches/basic.config.yaml diff --git a/details/operator-sdk/config/scorecard/patches/olm.config.yaml b/config/scorecard/patches/olm.config.yaml similarity index 100% rename from details/operator-sdk/config/scorecard/patches/olm.config.yaml rename to config/scorecard/patches/olm.config.yaml diff --git a/details/operator-sdk/config/webhook/kustomization.yaml b/config/webhook/kustomization.yaml similarity index 100% rename from details/operator-sdk/config/webhook/kustomization.yaml rename to config/webhook/kustomization.yaml diff --git a/details/operator-sdk/config/webhook/kustomizeconfig.yaml b/config/webhook/kustomizeconfig.yaml similarity index 100% rename from details/operator-sdk/config/webhook/kustomizeconfig.yaml rename to config/webhook/kustomizeconfig.yaml diff --git a/details/operator-sdk/config/webhook/manifests.yaml b/config/webhook/manifests.yaml similarity index 100% rename from details/operator-sdk/config/webhook/manifests.yaml rename to config/webhook/manifests.yaml diff --git a/details/operator-sdk/config/webhook/service.yaml b/config/webhook/service.yaml similarity index 100% rename from details/operator-sdk/config/webhook/service.yaml rename to config/webhook/service.yaml diff --git a/details/operator-sdk/controllers/.DS_Store b/controllers/.DS_Store similarity index 100% rename from details/operator-sdk/controllers/.DS_Store rename to controllers/.DS_Store diff --git a/controllers/astraconnector_controller.go b/controllers/astraconnector_controller.go new file mode 100644 index 00000000..2255db50 --- /dev/null +++ b/controllers/astraconnector_controller.go @@ -0,0 +1,235 @@ +/* + * Copyright (c) 2024. NetApp, Inc. All Rights Reserved. + */ + +package controllers + +import ( + "context" + "fmt" + "github.com/NetApp-Polaris/astra-connector-operator/api/v1" + "github.com/NetApp-Polaris/astra-connector-operator/k8s" + "github.com/NetApp-Polaris/astra-connector-operator/k8s/precheck" + "github.com/go-logr/logr" + "github.com/pkg/errors" + "sigs.k8s.io/controller-runtime/pkg/builder" + "strings" + + corev1 "k8s.io/api/core/v1" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/validation/field" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + ctrllog "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/predicate" + + "github.com/NetApp-Polaris/astra-connector-operator/app/conf" +) + +// AstraConnectorController reconciles a AstraConnector object +type AstraConnectorController struct { + client.Client + Scheme *runtime.Scheme +} + +// +kubebuilder:rbac:groups=astra.netapp.io,resources=astraconnectors,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=astra.netapp.io,resources=astraconnectors/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=astra.netapp.io,resources=astraconnectors/finalizers,verbs=update +// +kubebuilder:rbac:groups=*,resources=*,verbs=* +// +kubebuilder:rbac:groups="";apiextensions.k8s.io;apps;autoscaling;batch;crd.projectcalico.org;extensions;networking.k8s.io;policy;rbac.authorization.k8s.io;security.openshift.io;snapshot.storage.k8s.io;storage.k8s.io;trident.netapp.io,resources=configmaps;cronjobs;customresourcedefinitions;daemonsets;deployments;horizontalpodautoscalers;ingresses;jobs;namespaces;networkpolicies;persistentvolumeclaims;poddisruptionbudgets;pods;podtemplates;podsecuritypolicies;replicasets;replicationcontrollers;replicationcontrollers/scale;rolebindings;roles;secrets;serviceaccounts;services;statefulsets;storageclasses;csidrivers;csinodes;securitycontextconstraints;tridentmirrorrelationships;tridentsnapshotinfos;tridentvolumes;volumesnapshots;volumesnapshotcontents;tridentversions;tridentbackends;tridentnodes,verbs=get;list;watch;delete;use;create;update;patch +// +kubebuilder:rbac:urls=/metrics,verbs=get;list;watch + +func (r *AstraConnectorController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + log := ctrllog.FromContext(ctx) + + // Fetch the AstraConnector instance + astraConnector := &v1.AstraConnector{} + err := r.Get(ctx, req.NamespacedName, astraConnector) + 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 + log.Info("AstraConnector resource not found. Ignoring since object must be deleted") + return ctrl.Result{}, nil + } + // Error reading the object - requeue the request. + log.Error(err, FailedAstraConnectorGet) + _ = r.updateAstraConnectorStatus(ctx, astraConnector, FailedAstraConnectorGet) + // Do not requeue + return ctrl.Result{}, err + } + + // Validate AstraConnector CR for any errors + err = r.validateAstraConnector(*astraConnector, log) + if err != nil { + // Error validating the connector object. Do not requeue and update the connector status. + log.Error(err, FailedAstraConnectorValidation) + _ = r.updateAstraConnectorStatus(ctx, astraConnector, FailedAstraConnectorValidation) + // Do not requeue. This is a user input error + return ctrl.Result{}, err + } + + // name of our custom finalizer + finalizerName := "netapp.io/finalizer" + // examine DeletionTimestamp to determine if object is under deletion + if astraConnector.ObjectMeta.DeletionTimestamp.IsZero() { + // The object is not being deleted, so if it does not have our finalizer, + // then lets add the finalizer and update the object. This is equivalent + // registering our finalizer. + if !controllerutil.ContainsFinalizer(astraConnector, finalizerName) { + log.Info("Adding finalizer to AstraConnector instance", "finalizerName", finalizerName) + controllerutil.AddFinalizer(astraConnector, finalizerName) + if err := r.Update(ctx, astraConnector); err != nil { + _ = r.updateAstraConnectorStatus(ctx, astraConnector, FailedFinalizerAdd) + return ctrl.Result{}, err + } + } + } else { + // The object is being deleted + if controllerutil.ContainsFinalizer(astraConnector, finalizerName) { + // Update status message to indicate that CR delete is in progress + //natsSyncClientStatus.Status = DeleteInProgress + //_ = r.updateAstraConnectorStatus(ctx, astraConnector, natsSyncClientStatus) + + // delete any cluster scoped resources created by the operator + r.deleteConnectorClusterScopedResources(ctx, astraConnector) + + // remove our finalizer from the list and update it. + controllerutil.RemoveFinalizer(astraConnector, finalizerName) + if err := r.Update(ctx, astraConnector); err != nil { + _ = r.updateAstraConnectorStatus(ctx, astraConnector, FailedFinalizerRemove) + // Do not requeue. Item is being deleted + return ctrl.Result{}, err + } + + // Update status message to indicate that CR delete is in finished + _ = r.updateAstraConnectorStatus(ctx, astraConnector, DeletionComplete) + } + + // Stop reconciliation as the item is being deleted + // Do not requeue + return ctrl.Result{}, nil + } + + if !astraConnector.Spec.SkipPreCheck { + k8sUtil := k8s.NewK8sUtil(r.Client, log) + preCheckClient := precheck.NewPrecheckClient(log, k8sUtil) + errList := preCheckClient.Run() + + if errList != nil { + errString := "" + for i, err := range errList { + if i > 0 { + errString = errString + ", " + } + + log.Error(err, "Pre-check Error") + errString = errString + err.Error() + } + _ = r.updateAstraConnectorStatus(ctx, astraConnector, errString) + // Do not requeue. Item is being deleted + return ctrl.Result{}, errors.New(errString) + } + } + + // deploy Neptune + //if conf.Config.FeatureFlags().DeployNeptune() { + // log.Info("Initiating Neptune deployment") + // neptuneResult, err := r.deployNeptune(ctx, astraConnector, &natsSyncClientStatus) + // if err != nil { + // // Note: Returning nil in error since we want to wait for the requeue to happen + // // non nil errors triggers the requeue right away + // log.Error(err, "Error deploying Neptune, requeueing after delay", "delay", conf.Config.ErrorTimeout()) + // return neptuneResult, nil + // } + //} + + if conf.Config.FeatureFlags().DeployNatsConnector() { + log.Info("Initiating Connector deployment") + var connectorResults ctrl.Result + var deployError error + + connectorResults, deployError = r.deployNatlessConnector(ctx, astraConnector) + if deployError != nil { + // Note: Returning nil in error since we want to wait for the requeue to happen + // non nil errors triggers the requeue right away + log.Error(err, "Error deploying NatsConnector, requeueing after delay", "delay", conf.Config.ErrorTimeout()) + return connectorResults, nil + } + } + + return ctrl.Result{}, nil + +} + +// removeString removes a string from a slice of strings. +func removeString(slice []string, s string) []string { + for i, v := range slice { + if v == s { + return append(slice[:i], slice[i+1:]...) + } + } + return slice +} + +func (r *AstraConnectorController) updateAstraConnectorStatus( + ctx context.Context, + astraConnector *v1.AstraConnector, + status string) error { + // Update the astraConnector status with the pod names + // List the pods for this astraConnector's deployment + //log := ctrllog.FromContext(ctx) + + astraConnector.Status.Status = status + + // Update the status + err := r.Status().Update(ctx, astraConnector) + if err != nil { + return err + } + + return nil +} + +// getPodNames returns the pod names of the array of pods passed in +func getPodNames(pods []corev1.Pod) []string { + var podNames []string + for _, pod := range pods { + podNames = append(podNames, pod.Name) + } + return podNames +} + +// SetupWithManager sets up the controller with the Manager. +func (r *AstraConnectorController) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&v1.AstraConnector{}, builder.WithPredicates(predicate.GenerationChangedPredicate{})). + WithOptions(controller.Options{MaxConcurrentReconciles: 1}). + WithEventFilter(predicate.GenerationChangedPredicate{}). // Avoid reconcile for status updates + Complete(r) +} + +func (r *AstraConnectorController) validateAstraConnector(connector v1.AstraConnector, logger logr.Logger) error { + var validateErrors field.ErrorList + + logger.V(3).Info("Validating Create AstraConnector") + validateErrors = connector.ValidateCreateAstraConnector() + + var fieldErrors []string + for _, v := range validateErrors { + if v == nil { + continue + } + fieldErrors = append(fieldErrors, fmt.Sprintf("'%s' %s", v.Field, v.Detail)) + } + + if len(fieldErrors) == 0 { + return nil + } + + return errors.New(fmt.Sprintf("Errors while validating AstraConnector CR: %s", strings.Join(fieldErrors, "; "))) +} diff --git a/details/operator-sdk/controllers/astraconnector_controller_delete_test.go b/controllers/astraconnector_controller_delete_test.go similarity index 100% rename from details/operator-sdk/controllers/astraconnector_controller_delete_test.go rename to controllers/astraconnector_controller_delete_test.go diff --git a/details/operator-sdk/controllers/astraconnector_controller_test.go b/controllers/astraconnector_controller_test.go similarity index 98% rename from details/operator-sdk/controllers/astraconnector_controller_test.go rename to controllers/astraconnector_controller_test.go index 78dbd77e..7932d6a8 100644 --- a/details/operator-sdk/controllers/astraconnector_controller_test.go +++ b/controllers/astraconnector_controller_test.go @@ -6,6 +6,7 @@ package controllers import ( "context" + "github.com/NetApp-Polaris/astra-connector-operator/api/v1" "testing" "time" @@ -20,8 +21,6 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - - v1 "github.com/NetApp-Polaris/astra-connector-operator/details/operator-sdk/api/v1" ) var _ = Describe("Astraconnector controller", func() { diff --git a/controllers/astraneptune_controller.go b/controllers/astraneptune_controller.go new file mode 100644 index 00000000..78ece1e3 --- /dev/null +++ b/controllers/astraneptune_controller.go @@ -0,0 +1,229 @@ +/* + * Copyright (c) 2024. NetApp, Inc. All Rights Reserved. + */ + +package controllers + +import ( + "context" + "fmt" + "github.com/NetApp-Polaris/astra-connector-operator/api/v1" + "github.com/NetApp-Polaris/astra-connector-operator/k8s" + "github.com/NetApp-Polaris/astra-connector-operator/k8s/precheck" + "github.com/pkg/errors" + "sigs.k8s.io/controller-runtime/pkg/builder" + + k8serrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + ctrllog "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/predicate" + + "github.com/NetApp-Polaris/astra-connector-operator/app/conf" +) + +// AstraNeptuneController reconciles a AstraConnector object +type AstraNeptuneController struct { + client.Client + Scheme *runtime.Scheme +} + +// +kubebuilder:rbac:groups=astra.netapp.io,resources=astraneptunes,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=astra.netapp.io,resources=astraneptunes/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=astra.netapp.io,resources=astraneptunes/finalizers,verbs=update +// +kubebuilder:rbac:groups=*,resources=*,verbs=* +// +kubebuilder:rbac:groups="";apiextensions.k8s.io;apps;autoscaling;batch;crd.projectcalico.org;extensions;networking.k8s.io;policy;rbac.authorization.k8s.io;security.openshift.io;snapshot.storage.k8s.io;storage.k8s.io;trident.netapp.io,resources=configmaps;cronjobs;customresourcedefinitions;daemonsets;deployments;horizontalpodautoscalers;ingresses;jobs;namespaces;networkpolicies;persistentvolumeclaims;poddisruptionbudgets;pods;podtemplates;podsecuritypolicies;replicasets;replicationcontrollers;replicationcontrollers/scale;rolebindings;roles;secrets;serviceaccounts;services;statefulsets;storageclasses;csidrivers;csinodes;securitycontextconstraints;tridentmirrorrelationships;tridentsnapshotinfos;tridentvolumes;volumesnapshots;volumesnapshotcontents;tridentversions;tridentbackends;tridentnodes,verbs=get;list;watch;delete;use;create;update;patch +// +kubebuilder:rbac:urls=/metrics,verbs=get;list;watch + +func (r *AstraNeptuneController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + log := ctrllog.FromContext(ctx) + + // Fetch the AstraConnector instance + astraNeptune := &v1.AstraNeptune{} + err := r.Get(ctx, req.NamespacedName, astraNeptune) + 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 + log.Info("AstraConnector resource not found. Ignoring since object must be deleted") + return ctrl.Result{}, nil + } + // Error reading the object - requeue the request. + log.Error(err, FailedAstraConnectorGet) + _ = r.updateAstraNeptuneStatus(ctx, astraNeptune, FailedAstraConnectorGet) + // Do not requeue + return ctrl.Result{}, err + } + // todo write valdidator for this - Oscar + //// Validate AstraConnector CR for any errors + //err = r.validateAstraNeptune(*astraNeptune, log) + //if err != nil { + // // Error validating the connector object. Do not requeue and update the connector status. + // log.Error(err, FailedAstraConnectorValidation) + // _ = r.updateAstraConnectorStatus(ctx, astraNeptune, FailedAstraConnectorValidation) + // // Do not requeue. This is a user input error + // return ctrl.Result{}, err + //} + + // name of our custom finalizer + finalizerName := "netapp.io/finalizer" + // examine DeletionTimestamp to determine if object is under deletion + if astraNeptune.ObjectMeta.DeletionTimestamp.IsZero() { + // The object is not being deleted, so if it does not have our finalizer, + // then lets add the finalizer and update the object. This is equivalent + // registering our finalizer. + if !controllerutil.ContainsFinalizer(astraNeptune, finalizerName) { + log.Info("Adding finalizer to AstraConnector instance", "finalizerName", finalizerName) + controllerutil.AddFinalizer(astraNeptune, finalizerName) + if err := r.Update(ctx, astraNeptune); err != nil { + _ = r.updateAstraNeptuneStatus(ctx, astraNeptune, FailedFinalizerAdd) + return ctrl.Result{}, err + } + } + } else { + // The object is being deleted + if controllerutil.ContainsFinalizer(astraNeptune, finalizerName) { + // Update status message to indicate that CR delete is in progress + //natsSyncClientStatus.Status = DeleteInProgress + //_ = r.updateAstraConnectorStatus (ctx, astraConnector, natsSyncClientStatus) + + // delete any cluster scoped resources created by the operator + r.deleteNeptuneClusterScopedResources(ctx, astraNeptune) + + // remove our finalizer from the list and update it. + controllerutil.RemoveFinalizer(astraNeptune, finalizerName) + if err := r.Update(ctx, astraNeptune); err != nil { + _ = r.updateAstraNeptuneStatus(ctx, astraNeptune, FailedFinalizerRemove) + // Do not requeue. Item is being deleted + return ctrl.Result{}, err + } + + // Update status message to indicate that CR delete is in finished + _ = r.updateAstraNeptuneStatus(ctx, astraNeptune, DeletionComplete) + } + + // Stop reconciliation as the item is being deleted + // Do not requeue + return ctrl.Result{}, nil + } + + if !astraNeptune.Spec.SkipPreCheck { + k8sUtil := k8s.NewK8sUtil(r.Client, log) + preCheckClient := precheck.NewPrecheckClient(log, k8sUtil) + errList := preCheckClient.Run() + + if errList != nil { + errString := "" + for i, err := range errList { + if i > 0 { + errString = errString + ", " + } + + log.Error(err, "Pre-check Error") + errString = errString + err.Error() + } + _ = r.updateAstraNeptuneStatus(ctx, astraNeptune, errString) + // Do not requeue. Item is being deleted + return ctrl.Result{}, errors.New(errString) + } + } + + // deploy Neptune + if conf.Config.FeatureFlags().DeployNeptune() { + log.Info("Initiating Neptune deployment") + neptuneResult, err := r.deployNeptune(ctx, astraNeptune) + if err != nil { + // Note: Returning nil in error since we want to wait for the requeue to happen + // non nil errors triggers the requeue right away + log.Error(err, "Error deploying Neptune, requeueing after delay", "delay", conf.Config.ErrorTimeout()) + return neptuneResult, nil + } + } + + return ctrl.Result{}, nil + +} + +func (r *AstraNeptuneController) updateAstraNeptuneStatus( + ctx context.Context, + astraNeptune *v1.AstraNeptune, + status string) error { + // Update the astraConnector status with the pod names + // List the pods for this astraConnector's deployment + //log := ctrllog.FromContext(ctx) + + astraNeptune.Status.Status = status + + // Update the status + err := r.Status().Update(ctx, astraNeptune) + if err != nil { + return err + } + + return nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *AstraNeptuneController) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&v1.AstraNeptune{}, builder.WithPredicates(predicate.GenerationChangedPredicate{})). + WithOptions(controller.Options{MaxConcurrentReconciles: 1}). + WithEventFilter(predicate.GenerationChangedPredicate{}). // Avoid reconcile for status updates + Complete(r) +} +func (r *AstraNeptuneController) createASUPCR(ctx context.Context, astraNeptune *v1.AstraNeptune, astraClusterID string) error { + log := ctrllog.FromContext(ctx) + k8sUtil := k8s.NewK8sUtil(r.Client, log) + + cr := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "astra.netapp.io/v1", + "kind": "AutoSupportBundleSchedule", + "metadata": map[string]interface{}{ + "name": "asupbundleschedule-" + astraClusterID, + "namespace": astraNeptune.Namespace, + }, + "spec": map[string]interface{}{ + "enabled": astraNeptune.Spec.AutoSupport.Enrolled, + }, + }, + } + // Define the MutateFn function + mutateFn := func() error { + cr.Object["spec"].(map[string]interface{})["enabled"] = astraNeptune.Spec.AutoSupport.Enrolled + return nil + } + result, err := k8sUtil.CreateOrUpdateResource(ctx, cr, astraNeptune, mutateFn) + if err != nil { + return err + } + + log.Info(fmt.Sprintf("Successfully %s AutoSupportBundleSchedule", result)) + return nil +} + +//func (r *AstraNeptuneController) validateAstraConnector(connector v1.AstraConnector, logger logr.Logger) error { +// var validateErrors field.ErrorList +// +// logger.V(3).Info("Validating Create AstraConnector") +// validateErrors = connector.ValidateCreateAstraConnector() +// +// var fieldErrors []string +// for _, v := range validateErrors { +// if v == nil { +// continue +// } +// fieldErrors = append(fieldErrors, fmt.Sprintf("'%s' %s", v.Field, v.Detail)) +// } +// +// if len(fieldErrors) == 0 { +// return nil +// } +// +// return errors.New(fmt.Sprintf("Errors while validating AstraConnector CR: %s", strings.Join(fieldErrors, "; "))) +//} diff --git a/details/operator-sdk/controllers/deployer.go b/controllers/deployer.go similarity index 60% rename from details/operator-sdk/controllers/deployer.go rename to controllers/deployer.go index 24a4fdcb..630a1bfd 100644 --- a/details/operator-sdk/controllers/deployer.go +++ b/controllers/deployer.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "github.com/NetApp-Polaris/astra-connector-operator/app/conf" + "github.com/NetApp-Polaris/astra-connector-operator/k8s" "github.com/go-logr/logr" appsv1 "k8s.io/api/apps/v1" "reflect" @@ -15,12 +16,10 @@ import ( ctrllog "sigs.k8s.io/controller-runtime/pkg/log" "github.com/NetApp-Polaris/astra-connector-operator/app/deployer/model" - "github.com/NetApp-Polaris/astra-connector-operator/details/k8s" - installer "github.com/NetApp-Polaris/astra-connector-operator/details/operator-sdk/api/v1" ) // getK8sResources of function type -type getK8sResources func(model.Deployer, *installer.AstraConnector, context.Context) ([]client.Object, controllerutil.MutateFn, error) +type getK8sResources func(model.Deployer, context.Context) ([]client.Object, controllerutil.MutateFn, error) type createResourceParams struct { getResource getK8sResources @@ -29,8 +28,6 @@ type createResourceParams struct { clusterScope bool } -const () - // ResourcesToDeploy This is a list and order matters since things will be created in the order specified var resources = []createResourceParams{ {createMessage: CreateConfigMap, errorMessage: ErrorCreateConfigMaps, getResource: model.Deployer.GetConfigMapObjects, clusterScope: false}, @@ -44,13 +41,23 @@ var resources = []createResourceParams{ {createMessage: CreateDeployment, errorMessage: ErrorCreateDeployments, getResource: model.Deployer.GetDeploymentObjects, clusterScope: false}, } -func (r *AstraConnectorController) deployResources(ctx context.Context, deployer model.Deployer, astraConnector *installer.AstraConnector, natsSyncClientStatus *installer.NatsSyncClientStatus) error { +type K8sDeployer struct { + k8sUtil k8s.K8sUtil + client client.Client + owner client.Object + deployable model.Deployer +} + +func NewK8sDeployer(k8s k8s.K8sUtil, client client.Client, owner client.Object, deployable model.Deployer) K8sDeployer { + return K8sDeployer{k8sUtil: k8s, client: client, owner: owner, deployable: deployable} +} + +func (r *K8sDeployer) deployResources(ctx context.Context) error { log := ctrllog.FromContext(ctx) - k8sUtil := k8s.NewK8sUtil(r.Client, r.Clientset, log) for _, funcList := range resources { - resourceList, mutateFunc, err := funcList.getResource(deployer, astraConnector, ctx) + resourceList, mutateFunc, err := funcList.getResource(r.deployable, ctx) if err != nil { return errors.Wrapf(err, "Unable to get resource") } @@ -61,20 +68,17 @@ func (r *AstraConnectorController) deployResources(ctx context.Context, deployer for _, kubeObject := range resourceList { key := client.ObjectKeyFromObject(kubeObject) statusMsg := fmt.Sprintf(funcList.createMessage, key.Namespace, key.Name) - // todo we need to use event and not use status because that causes reconciles - log.Info(statusMsg) - //natsSyncClientStatus.Status = statusMsg - //_ = r.updateAstraConnectorStatus(ctx, astraConnector, *natsSyncClientStatus) + _ = r.deployable.UpdateStatus(ctx, statusMsg, r.client.Status()) - result, err := k8sUtil.CreateOrUpdateResource(ctx, kubeObject, astraConnector, mutateFunc) + result, err := r.k8sUtil.CreateOrUpdateResource(ctx, kubeObject, r.owner, mutateFunc) if err != nil { - return r.formatError(ctx, astraConnector, log, funcList.errorMessage, key.Namespace, key.Name, err, natsSyncClientStatus) + return r.formatError(ctx, log, funcList.errorMessage, key.Namespace, key.Name, err) } else { waitCtx, cancel := context.WithCancel(ctx) defer cancel() - err = r.waitForResourceReady(waitCtx, kubeObject, astraConnector) + err = r.waitForResourceReady(waitCtx, kubeObject) if err != nil { - return r.formatError(ctx, astraConnector, log, funcList.errorMessage, key.Namespace, key.Name, err, natsSyncClientStatus) + return r.formatError(ctx, log, funcList.errorMessage, key.Namespace, key.Name, err) } log.Info(fmt.Sprintf("Successfully %s resources", result)) } @@ -84,9 +88,8 @@ func (r *AstraConnectorController) deployResources(ctx context.Context, deployer return nil } -func (r *AstraConnectorController) deleteClusterScopedResources(ctx context.Context, deployer model.Deployer, astraConnector *installer.AstraConnector) { +func (r *K8sDeployer) deleteClusterScopedResources(ctx context.Context) { log := ctrllog.FromContext(ctx) - k8sUtil := k8s.NewK8sUtil(r.Client, r.Clientset, log) for _, funcList := range resources { if !funcList.clusterScope { @@ -94,7 +97,7 @@ func (r *AstraConnectorController) deleteClusterScopedResources(ctx context.Cont continue } - resourceList, _, _ := funcList.getResource(deployer, astraConnector, ctx) + resourceList, _, _ := funcList.getResource(r.deployable, ctx) if resourceList == nil { continue } @@ -104,7 +107,7 @@ func (r *AstraConnectorController) deleteClusterScopedResources(ctx context.Cont objectKind := reflect.TypeOf(kubeObject).String() log.WithValues("name", key.Name, "kind", objectKind).Info("Deleting resource") - err := k8sUtil.DeleteResource(ctx, kubeObject) + err := r.k8sUtil.DeleteResource(ctx, kubeObject) if err != nil { log.WithValues("name", key.Name, "kind", objectKind).Error(err, "error deleting resource") return @@ -114,11 +117,10 @@ func (r *AstraConnectorController) deleteClusterScopedResources(ctx context.Cont } } -func (r *AstraConnectorController) waitForResourceReady(ctx context.Context, kubeObject client.Object, astraConnector *installer.AstraConnector) error { +func (r *K8sDeployer) waitForResourceReady(ctx context.Context, kubeObject client.Object) error { log := ctrllog.FromContext(ctx) timeout := time.After(conf.Config.WaitDurationForResource()) // default is 2 mins ticker := time.NewTicker(3 * time.Second) - originalSpec := astraConnector.Spec for { select { @@ -129,11 +131,11 @@ func (r *AstraConnectorController) waitForResourceReady(ctx context.Context, kub case <-ticker.C: // First lets make sure controller isn't modified, this check allow us // to respond faster when there is a cr spec change or deletion - if r.isControllerModified(ctx, astraConnector, originalSpec) { + if r.deployable.IsSpecModified(ctx, r.client) { return fmt.Errorf("controller updated, requeue to handle changes") } - err := r.Client.Get(ctx, client.ObjectKeyFromObject(kubeObject), kubeObject) + err := r.client.Get(ctx, client.ObjectKeyFromObject(kubeObject), kubeObject) if err != nil { log.Error(err, "Error getting resource", "namespace", kubeObject.GetNamespace(), "name", kubeObject.GetName()) continue @@ -157,36 +159,10 @@ func (r *AstraConnectorController) waitForResourceReady(ctx context.Context, kub } } -func (r *AstraConnectorController) formatError(ctx context.Context, astraConnector *installer.AstraConnector, - log logr.Logger, errorMessage, namespace, name string, err error, - natsSyncClientStatus *installer.NatsSyncClientStatus) error { +func (r *K8sDeployer) formatError(ctx context.Context, + log logr.Logger, errorMessage, namespace, name string, err error) error { statusMsg := fmt.Sprintf(errorMessage, namespace, name) - natsSyncClientStatus.Status = statusMsg - _ = r.updateAstraConnectorStatus(ctx, astraConnector, *natsSyncClientStatus) + _ = r.deployable.UpdateStatus(ctx, statusMsg, r.client.Status()) log.Error(err, statusMsg) return errors.Wrapf(err, statusMsg) } - -func (r *AstraConnectorController) isControllerModified(ctx context.Context, - astraConnector *installer.AstraConnector, originalSpec installer.AstraConnectorSpec) bool { - log := ctrllog.FromContext(ctx) - // Fetch the AstraConnector instance - controllerKey := client.ObjectKeyFromObject(astraConnector) - updatedAstraConnector := &installer.AstraConnector{} - err := r.Get(ctx, controllerKey, updatedAstraConnector) - if err != nil { - log.Info("AstraConnector resource not found. Ignoring since object must be deleted") - return true - } - - if updatedAstraConnector.GetDeletionTimestamp() != nil { - log.Info("AstraConnector marked for deletion, reconciler requeue") - return true - } - - if !reflect.DeepEqual(updatedAstraConnector.Spec, originalSpec) { - log.Info("AstraConnector spec change, reconciler requeue") - return true - } - return false -} diff --git a/controllers/natless_connector.go b/controllers/natless_connector.go new file mode 100644 index 00000000..76407c3e --- /dev/null +++ b/controllers/natless_connector.go @@ -0,0 +1,46 @@ +package controllers + +import ( + "context" + "github.com/NetApp-Polaris/astra-connector-operator/api/v1" + "github.com/NetApp-Polaris/astra-connector-operator/k8s" + "time" + + ctrl "sigs.k8s.io/controller-runtime" + ctrllog "sigs.k8s.io/controller-runtime/pkg/log" + + "github.com/NetApp-Polaris/astra-connector-operator/app/conf" + "github.com/NetApp-Polaris/astra-connector-operator/app/deployer/connector" +) + +func (r *AstraConnectorController) deployNatlessConnector(ctx context.Context, + astraConnector *v1.AstraConnector) (ctrl.Result, error) { + log := ctrllog.FromContext(ctx) + + // let's deploy Astra Connector without Nats + connectorDeployable := connector.NewAstraConnectorNatlessDeployer(*astraConnector) + deployerCtrl := NewK8sDeployer(k8s.K8sUtil{Client: r.Client, Log: ctrl.Log}, + r.Client, + astraConnector, + connectorDeployable) + err := deployerCtrl.deployResources(ctx) + if err != nil { + // Failed deploying we want status to reflect that for at least 30 seconds before it's requeued so + // anyone watching can be informed + log.V(3).Info("Requeue after 30 seconds, so that status reflects error") + return ctrl.Result{RequeueAfter: time.Minute * conf.Config.ErrorTimeout()}, err + } + + _ = r.updateAstraConnectorStatus(ctx, astraConnector, DeployedComponents) + // No need to requeue due to success + return ctrl.Result{}, nil +} + +func (r *AstraConnectorController) deleteConnectorClusterScopedResources(ctx context.Context, astraConnector *v1.AstraConnector) { + connectorDeployable := connector.NewAstraConnectorNatlessDeployer(*astraConnector) + deployerCtrl := NewK8sDeployer(k8s.K8sUtil{Client: r.Client, Log: ctrl.Log}, + r.Client, + astraConnector, + connectorDeployable) + deployerCtrl.deleteClusterScopedResources(ctx) +} diff --git a/controllers/neptune.go b/controllers/neptune.go new file mode 100644 index 00000000..9a232a73 --- /dev/null +++ b/controllers/neptune.go @@ -0,0 +1,56 @@ +package controllers + +import ( + "context" + "github.com/NetApp-Polaris/astra-connector-operator/api/v1" + "github.com/NetApp-Polaris/astra-connector-operator/k8s" + "time" + + ctrl "sigs.k8s.io/controller-runtime" + + "github.com/NetApp-Polaris/astra-connector-operator/app/conf" + "github.com/NetApp-Polaris/astra-connector-operator/app/deployer/neptune" +) + +func (r *AstraNeptuneController) deployNeptune(ctx context.Context, + astraNeptune *v1.AstraNeptune) (ctrl.Result, error) { + + // TODO CRD will be installed as part of our crd install + // check if they are installed if not error here or maybe a pre-check + + // Deploy Neptune + neptuneDeployable := neptune.NewNeptuneClientDeployerV2(astraNeptune) + + deployerCtrl := NewK8sDeployer(k8s.K8sUtil{Client: r.Client, Log: ctrl.Log}, + r.Client, + astraNeptune, + neptuneDeployable) + err := deployerCtrl.deployResources(ctx) + if err != nil { + // Failed deploying we want status to reflect that for at least 30 seconds before it's requeued so + // anyone watching can be informed + return ctrl.Result{RequeueAfter: time.Minute * conf.Config.ErrorTimeout()}, err + } + + // if we are registered and have a clusterid let's set up the asup cr + err = r.createASUPCR(ctx, astraNeptune, "1") + if err != nil { + ctrl.Log.Error(err, FailedASUPCreation) + _ = r.updateAstraNeptuneStatus(ctx, astraNeptune, FailedASUPCreation) + return ctrl.Result{RequeueAfter: time.Minute * conf.Config.ErrorTimeout()}, err + } + _ = r.updateAstraNeptuneStatus(ctx, astraNeptune, DeployedComponents) + // No need to requeue due to success + return ctrl.Result{}, nil +} + +func (r *AstraNeptuneController) deleteNeptuneClusterScopedResources(ctx context.Context, astraNeptune *v1.AstraNeptune) { + neptuneDeployable := neptune.NewNeptuneClientDeployerV2(astraNeptune) + + deployerCtrl := NewK8sDeployer(k8s.K8sUtil{Client: r.Client, Log: ctrl.Log}, + r.Client, + astraNeptune, + neptuneDeployable) + + deployerCtrl.deleteClusterScopedResources(ctx) +} diff --git a/details/operator-sdk/controllers/status_strings.go b/controllers/status_strings.go similarity index 97% rename from details/operator-sdk/controllers/status_strings.go rename to controllers/status_strings.go index 2809a559..56dc1b65 100644 --- a/details/operator-sdk/controllers/status_strings.go +++ b/controllers/status_strings.go @@ -38,7 +38,7 @@ const ( FailedUnRegisterNSClient = "Failed to unregister natsSyncClient" FailedASUPCreation = "Failed to create ASUP CR" - DeployedComponents = "Deployed all the connector components" + DeployedComponents = "Deployed" RegisteredWithAstra = "Registered with Astra" FailedConnectorIDAdd = "Failed to add cluster to Astra" diff --git a/details/operator-sdk/controllers/suite_test.go b/controllers/suite_test.go similarity index 95% rename from details/operator-sdk/controllers/suite_test.go rename to controllers/suite_test.go index bf476266..f7ff0c26 100644 --- a/details/operator-sdk/controllers/suite_test.go +++ b/controllers/suite_test.go @@ -5,6 +5,7 @@ package controllers import ( + "github.com/NetApp-Polaris/astra-connector-operator/api/v1" "path/filepath" "testing" @@ -17,8 +18,6 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - - v1 "github.com/NetApp-Polaris/astra-connector-operator/details/operator-sdk/api/v1" //+kubebuilder:scaffold:imports ) diff --git a/details/operator-sdk/controllers/yaml/neptune_crds.yaml b/controllers/yaml/neptune_crds.yaml similarity index 100% rename from details/operator-sdk/controllers/yaml/neptune_crds.yaml rename to controllers/yaml/neptune_crds.yaml diff --git a/details/k8s/cluster_type_checker.go b/details/k8s/cluster_type_checker.go deleted file mode 100644 index 3dab69a2..00000000 --- a/details/k8s/cluster_type_checker.go +++ /dev/null @@ -1,217 +0,0 @@ -package k8s - -import ( - "context" - "encoding/json" - "fmt" - "strings" - - "github.com/go-logr/logr" - "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -const ( - FlavorAKS = "aks" - FlavorGKE = "gke" - FlavorEKS = "eks" - FlavorKubernetes = "kubernetes" - FlavorOpenShift = "openshift" - FlavorRKE = "rke" - FlavorRKE2 = "rke2" - FlavorTanzu = "tanzu" - FlavorAnthos = "anthos" - - openShiftApiServerName = "openshift-apiserver" -) - -type ClusterTypeCheckerInterface interface { - DetermineClusterType() string -} - -type ClusterTypeChecker struct { - K8sUtil K8sUtilInterface - Log logr.Logger -} - -func NewClusterTypeChecker(k8sUtil K8sUtilInterface, log logr.Logger) ClusterTypeCheckerInterface { - return &ClusterTypeChecker{K8sUtil: k8sUtil, Log: log} -} - -func (c *ClusterTypeChecker) DetermineClusterType() string { - if c.isAnthosFlavor() { - return FlavorAnthos - } - - if c.isOpenshiftFlavor() { - return FlavorOpenShift - } - - // RKE2 check must come before the RKE check - if c.isRKE2Flavor() { - return FlavorRKE2 - } - - if c.isRKEFlavor() { - return FlavorRKE - } - - if c.isTanzuFlavor() { - return FlavorTanzu - } - - if c.isGKEFlavor() { - return FlavorGKE - } - - if c.isAKSFlavor() { - return FlavorAKS - } - - if c.isEKSFlavor() { - return FlavorEKS - } - - return FlavorKubernetes -} - -// isOpenshiftFlavor - tries to hit an api registered by the openshift operator -// https://confluence.ngage.netapp.com/display/POLARIS/OpenShift+Questions -func (c *ClusterTypeChecker) isOpenshiftFlavor() bool { - - _, err := c.getOpenshiftVersion() - if err != nil { - if errors.IsNotFound(err) { - return false - } - c.Log.Error(err, "error requesting for openshift version endpoint") - return false - } - - return true -} - -func (c *ClusterTypeChecker) getOpenshiftVersion() (string, error) { - // struct modeling only required information present in openshift API response - var openshiftVersionSchema struct { - Status struct { - Versions []struct { - Name string - Version string - } - } - } - - versionBytes, err := c.K8sUtil.RESTGet("apis/config.openshift.io/v1/clusteroperators/openshift-apiserver") - if err != nil { - return "", err - } - - if err := json.Unmarshal(versionBytes, &openshiftVersionSchema); err != nil { - return "", fmt.Errorf("failed to unmarshal version bytes: %v", err) - } - - for _, versionObject := range openshiftVersionSchema.Status.Versions { - if versionObject.Name == openShiftApiServerName { - return versionObject.Version, nil - } - } - - return "", fmt.Errorf("failed to find version object for '%v'", openShiftApiServerName) -} - -// isRKEFlavor - checks for the presence of the rancher API -func (c *ClusterTypeChecker) isRKEFlavor() bool { - // Querying the base URL will return a list of supported API versions. If we wanted to we could - // parse the response, extract the latest version and use it to query cluster CRs to ensure one - // actually exists, but for now the presence of the API is sufficient for determining it is Rancher - _, err := c.K8sUtil.RESTGet("apis/management.cattle.io") - - if err != nil { - if errors.IsNotFound(err) { - return false - } - c.Log.Error(err, "error querying the rancher API") - return false - } - return true -} - -// isRKE2Flavor - checks for the presence of the RKE2 base: k3s API -func (c *ClusterTypeChecker) isRKE2Flavor() bool { - _, err := c.K8sUtil.RESTGet("apis/k3s.cattle.io") - - if err != nil { - if errors.IsNotFound(err) { - return false - } - c.Log.Error(err, "error querying the RKE2 API") - return false - } - return true -} - -// isTanzuFlavor - checks for the presence of the tanzu API -func (c *ClusterTypeChecker) isTanzuFlavor() bool { - _, err := c.K8sUtil.RESTGet("apis/core.antrea.tanzu.vmware.com") - - if err != nil { - if errors.IsNotFound(err) { - return false - } - c.Log.Error(err, "error querying the TKG API") - return false - } - return true -} - -// isAKSFlavor - checks cluster roles for AKS service resource -// https://docs.microsoft.com/en-us/azure/aks/concepts-identity#clusterrolebinding -func (c *ClusterTypeChecker) isAKSFlavor() bool { - const aksService = "aks-service" - aksRoleBinding, err := c.K8sUtil.K8sClientset().RbacV1().ClusterRoles().Get(context.Background(), aksService, metav1.GetOptions{}) - - if err != nil { - if errors.IsNotFound(err) { - return false - } - c.Log.Error(err, "Unable to get AKS cluster role. Assuming not AKS") - return false - } - - return aksRoleBinding != nil -} - -// isGKEFlavor - checks for 'gke' in the client git version -// i.e. "gitVersion": "v1.20.6-gke.1000", -func (c *ClusterTypeChecker) isGKEFlavor() bool { - version, err := c.K8sUtil.VersionGet() - if err != nil { - return false - } - - return strings.Contains(strings.ToLower(version), FlavorGKE) -} - -func (c *ClusterTypeChecker) isEKSFlavor() bool { - version, err := c.K8sUtil.VersionGet() - if err != nil { - return false - } - - return strings.Contains(strings.ToLower(version), FlavorEKS) -} - -// isAnthosFlavor - checks for the presence of the Anthos API -func (c *ClusterTypeChecker) isAnthosFlavor() bool { - _, err := c.K8sUtil.RESTGet("apis/anthos.gke.io") - - if err != nil { - if errors.IsNotFound(err) { - return false - } - c.Log.Error(err, "error querying the Anthos API") - return false - } - return true -} diff --git a/details/k8s/cluster_type_checker_test.go b/details/k8s/cluster_type_checker_test.go deleted file mode 100644 index 7576d102..00000000 --- a/details/k8s/cluster_type_checker_test.go +++ /dev/null @@ -1,106 +0,0 @@ -package k8s_test - -import ( - "errors" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - v1 "k8s.io/api/rbac/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/kubernetes/fake" - - "github.com/NetApp-Polaris/astra-connector-operator/details/k8s" - "github.com/NetApp-Polaris/astra-connector-operator/mocks" - testutil "github.com/NetApp-Polaris/astra-connector-operator/test/test-util" -) - -func createHandler(t *testing.T) (k8s.ClusterTypeCheckerInterface, *mocks.K8sUtilInterface, kubernetes.Interface) { - k8sUtil := new(mocks.K8sUtilInterface) - mockInterface := fake.NewSimpleClientset() - log := testutil.CreateLoggerForTesting(t) - k8sUtil.On("K8sClientset").Return(mockInterface) - clusterTypeChecker := k8s.NewClusterTypeChecker(k8sUtil, log) - return clusterTypeChecker, k8sUtil, mockInterface -} - -func TestDetermineClusterType(t *testing.T) { - t.Run("AKS", func(t *testing.T) { - clusterTypeChecker, k8sUtil, k8sInterface := createHandler(t) - k8sUtil.On("RESTGet", mock.Anything).Return(nil, errors.New("testing")) - k8sUtil.On("VersionGet").Return("1.1", nil) - clusterRole := &v1.ClusterRole{ - ObjectMeta: metav1.ObjectMeta{ - Name: "aks-service", - }, - } - - _, err := k8sInterface.RbacV1().ClusterRoles().Create(ctx, clusterRole, metav1.CreateOptions{}) - assert.NoError(t, err) - - assert.Equal(t, k8s.FlavorAKS, clusterTypeChecker.DetermineClusterType()) - }) - - t.Run("Anthos", func(t *testing.T) { - clusterTypeChecker, k8sUtil, _ := createHandler(t) - k8sUtil.On("RESTGet", "apis/anthos.gke.io").Return(make([]byte, 0), nil) - k8sUtil.On("VersionGet").Return("1.1", nil) - - assert.Equal(t, k8s.FlavorAnthos, clusterTypeChecker.DetermineClusterType()) - }) - - t.Run("RKE", func(t *testing.T) { - clusterTypeChecker, k8sUtil, _ := createHandler(t) - k8sUtil.On("RESTGet", "apis/management.cattle.io").Return(make([]byte, 0), nil) - k8sUtil.On("RESTGet", mock.Anything).Return(nil, errors.New("testing")) - k8sUtil.On("VersionGet").Return("1.1", nil) - - assert.Equal(t, k8s.FlavorRKE, clusterTypeChecker.DetermineClusterType()) - }) - - t.Run("RKE2", func(t *testing.T) { - clusterTypeChecker, k8sUtil, _ := createHandler(t) - k8sUtil.On("RESTGet", "apis/k3s.cattle.io").Return(make([]byte, 0), nil) - k8sUtil.On("RESTGet", mock.Anything).Return(nil, errors.New("testing")) - k8sUtil.On("VersionGet").Return("1.1", nil) - - assert.Equal(t, k8s.FlavorRKE2, clusterTypeChecker.DetermineClusterType()) - }) - - t.Run("Tanzu", func(t *testing.T) { - clusterTypeChecker, k8sUtil, _ := createHandler(t) - k8sUtil.On("RESTGet", "apis/core.antrea.tanzu.vmware.com").Return(make([]byte, 0), nil) - k8sUtil.On("RESTGet", mock.Anything).Return(nil, errors.New("testing")) - k8sUtil.On("VersionGet").Return("1.1", nil) - - assert.Equal(t, k8s.FlavorTanzu, clusterTypeChecker.DetermineClusterType()) - }) - - t.Run("GKE", func(t *testing.T) { - clusterTypeChecker, k8sUtil, _ := createHandler(t) - k8sUtil.On("RESTGet", mock.Anything).Return(nil, errors.New("testing")) - k8sUtil.On("VersionGet").Return("1.1-gke", nil) - - assert.Equal(t, k8s.FlavorGKE, clusterTypeChecker.DetermineClusterType()) - }) - - t.Run("EKS", func(t *testing.T) { - clusterTypeChecker, k8sUtil, _ := createHandler(t) - k8sUtil.On("RESTGet", mock.Anything).Return(nil, errors.New("testing")) - k8sUtil.On("VersionGet").Return("v1.28.5-eks-5e0fdde", nil) - - assert.Equal(t, k8s.FlavorEKS, clusterTypeChecker.DetermineClusterType()) - }) - - t.Run("openshift", func(t *testing.T) { - clusterTypeChecker, k8sUtil, _ := createHandler(t) - versionBytes := []byte(`{"Status": {"Versions": [{"Name": "openshift-apiserver", "Version": "1.0.0"}]}}`) - - k8sUtil.On("RESTGet", "apis/config.openshift.io/v1/clusteroperators/openshift-apiserver").Return(versionBytes, nil) - k8sUtil.On("RESTGet", mock.Anything).Return(nil, errors.New("testing")) - k8sUtil.On("VersionGet").Return("1.1", nil) - - assert.Equal(t, k8s.FlavorOpenShift, clusterTypeChecker.DetermineClusterType()) - }) -} diff --git a/details/operator-sdk/api/v1/astraconnector_types.go b/details/operator-sdk/api/v1/astraconnector_types.go deleted file mode 100644 index 8e49380f..00000000 --- a/details/operator-sdk/api/v1/astraconnector_types.go +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright (c) 2024. NetApp, Inc. All Rights Reserved. - */ - -package v1 - -import ( - metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -type Astra struct { - AccountId string `json:"accountId"` - // +kubebuilder:validation:Optional - CloudId string `json:"cloudId"` - // +kubebuilder:validation:Optional - ClusterId string `json:"clusterId"` - ClusterName string `json:"clusterName,omitempty"` - // +kubebuilder:validation:Optional - SkipTLSValidation bool `json:"skipTLSValidation,omitempty"` - TokenRef string `json:"tokenRef,omitempty"` - Unregister bool `json:"unregister,omitempty"` -} - -// AutoSupport defines how the customer interacts with NetApp ActiveIQ. -type AutoSupport struct { - // Enrolled determines if you want to send anonymous data to NetApp for support purposes. - // +kubebuilder:default:=true - Enrolled bool `json:"enrolled"` - - // URL determines where the anonymous data will be sent - // +kubebuilder:default:="https://stagesupport.netapp.com/put/AsupPut" - URL string `json:"url,omitempty"` -} - -type NatsSyncClient struct { - CloudBridgeURL string `json:"cloudBridgeURL,omitempty"` - // +kubebuilder:validation:Optional - Image string `json:"image,omitempty"` - // +kubebuilder:validation:Optional - HostAliasIP string `json:"hostAliasIP,omitempty"` - // +kubebuilder:default:=1 - Replicas int32 `json:"replicas,omitempty"` -} - -// +kubebuilder:validation:Optional - -type Nats struct { - Image string `json:"image,omitempty"` - // +kubebuilder:default:=1 - Replicas int32 `json:"replicas,omitempty"` -} - -// +kubebuilder:validation:Optional - -type AstraConnect struct { - Image string `json:"image,omitempty"` - // +kubebuilder:default:=1 - Replicas int32 `json:"replicas,omitempty"` -} - -// +kubebuilder:validation:Optional - -type Neptune struct { - Image string `json:"image,omitempty"` -} - -// AstraConnectorSpec defines the desired state of AstraConnector -type AstraConnectorSpec struct { - Astra Astra `json:"astra"` - NatsSyncClient NatsSyncClient `json:"natsSyncClient,omitempty"` - Nats Nats `json:"nats,omitempty"` - AstraConnect AstraConnect `json:"astraConnect,omitempty"` - Neptune Neptune `json:"neptune"` - ImageRegistry ImageRegistry `json:"imageRegistry,omitempty"` - - // AutoSupport indicates willingness to participate in NetApp's proactive support application, NetApp Active IQ. - // An internet connection is required (port 442) and all support data is anonymized. - // The default election is true and indicates support data will be sent to NetApp. - // An empty or blank election is the same as a default election. - // Air gapped installations should enter false. - // +kubebuilder:default={"enrolled":true, "url":"https://stagesupport.netapp.com/put/AsupPut"} - AutoSupport AutoSupport `json:"autoSupport"` - - // SkipPreCheck determines if you want to skip pre-checks and go ahead with the installation. - // +kubebuilder:default:=false - SkipPreCheck bool `json:"skipPreCheck"` - - // Labels any additional labels wanted to be added to resources - Labels map[string]string `json:"labels"` -} - -// AstraConnectorStatus defines the observed state of AstraConnector -type AstraConnectorStatus struct { - Nodes []string `json:"nodes"` - NatsSyncClient NatsSyncClientStatus `json:"natsSyncClient"` - // ObservedSpec is the last observed Connector custom resource spec - ObservedSpec AstraConnectorSpec `json:"observedSpec,omitempty"` -} - -// NatsSyncClientStatus defines the observed state of NatsSyncClient -type NatsSyncClientStatus struct { - Registered string `json:"registered"` //todo cluster vs connector registered - AstraClusterId string `json:"astraClusterID,omitempty"` - AstraConnectorID string `json:"astraConnectorID"` - Status string `json:"status"` -} - -// +kubebuilder:validation:Optional - -type ImageRegistry struct { - Name string `json:"name,omitempty"` - Secret string `json:"secret,omitempty"` -} - -//+kubebuilder:object:root=true -//+kubebuilder:subresource:status -//+kubebuilder:printcolumn:name="Registered",type=string,JSONPath=`.status.natsSyncClient.registered` -//+kubebuilder:printcolumn:name="AstraClusterID",type=string,JSONPath=`.status.natsSyncClient.astraClusterID` -//+kubebuilder:printcolumn:name="AstraConnectorID",type=string,JSONPath=`.status.natsSyncClient.astraConnectorID` -//+kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.natsSyncClient.status` - -// AstraConnector is the Schema for the astraconnectors API -// +kubebuilder:subresource:status -type AstraConnector struct { - metaV1.TypeMeta `json:",inline"` - metaV1.ObjectMeta `json:"metadata,omitempty"` - - Spec AstraConnectorSpec `json:"spec,omitempty"` - Status AstraConnectorStatus `json:"status,omitempty"` -} - -//+kubebuilder:object:root=true - -// AstraConnectorList contains a list of AstraConnector -type AstraConnectorList struct { - metaV1.TypeMeta `json:",inline"` - metaV1.ListMeta `json:"metadata,omitempty"` - Items []AstraConnector `json:"items"` -} - -func init() { - SchemeBuilder.Register(&AstraConnector{}, &AstraConnectorList{}) -} diff --git a/details/operator-sdk/config/crd/bases/astra.netapp.io_astraconnectors.yaml b/details/operator-sdk/config/crd/bases/astra.netapp.io_astraconnectors.yaml deleted file mode 100644 index 53aa0ec9..00000000 --- a/details/operator-sdk/config/crd/bases/astra.netapp.io_astraconnectors.yaml +++ /dev/null @@ -1,273 +0,0 @@ - ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.7.0 - creationTimestamp: null - name: astraconnectors.astra.netapp.io -spec: - group: astra.netapp.io - names: - kind: AstraConnector - listKind: AstraConnectorList - plural: astraconnectors - singular: astraconnector - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .status.natsSyncClient.registered - name: Registered - type: string - - jsonPath: .status.natsSyncClient.astraClusterID - name: AstraClusterID - type: string - - jsonPath: .status.natsSyncClient.astraConnectorID - name: AstraConnectorID - type: string - - jsonPath: .status.natsSyncClient.status - name: Status - type: string - name: v1 - schema: - openAPIV3Schema: - description: AstraConnector is the Schema for the astraconnectors API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: AstraConnectorSpec defines the desired state of AstraConnector - properties: - astra: - properties: - accountId: - type: string - cloudId: - type: string - clusterId: - type: string - clusterName: - type: string - skipTLSValidation: - type: boolean - tokenRef: - type: string - unregister: - type: boolean - type: object - astraConnect: - properties: - image: - type: string - replicas: - default: 1 - format: int32 - type: integer - type: object - autoSupport: - default: - enrolled: true - url: https://stagesupport.netapp.com/put/AsupPut - description: AutoSupport indicates willingness to participate in NetApp's - proactive support application, NetApp Active IQ. An internet connection - is required (port 442) and all support data is anonymized. The default - election is true and indicates support data will be sent to NetApp. - An empty or blank election is the same as a default election. Air - gapped installations should enter false. - properties: - enrolled: - default: true - description: Enrolled determines if you want to send anonymous - data to NetApp for support purposes. - type: boolean - url: - default: https://stagesupport.netapp.com/put/AsupPut - description: URL determines where the anonymous data will be sent - type: string - type: object - imageRegistry: - properties: - name: - type: string - secret: - type: string - type: object - labels: - additionalProperties: - type: string - description: Labels any additional labels wanted to be added to resources - type: object - nats: - properties: - image: - type: string - replicas: - default: 1 - format: int32 - type: integer - type: object - natsSyncClient: - properties: - cloudBridgeURL: - type: string - hostAliasIP: - type: string - image: - type: string - replicas: - default: 1 - format: int32 - type: integer - type: object - neptune: - properties: - image: - type: string - type: object - skipPreCheck: - default: false - description: SkipPreCheck determines if you want to skip pre-checks - and go ahead with the installation. - type: boolean - type: object - status: - description: AstraConnectorStatus defines the observed state of AstraConnector - properties: - natsSyncClient: - description: NatsSyncClientStatus defines the observed state of NatsSyncClient - properties: - astraClusterID: - type: string - astraConnectorID: - type: string - registered: - type: string - status: - type: string - type: object - nodes: - items: - type: string - type: array - observedSpec: - description: ObservedSpec is the last observed Connector custom resource - spec - properties: - astra: - properties: - accountId: - type: string - cloudId: - type: string - clusterId: - type: string - clusterName: - type: string - skipTLSValidation: - type: boolean - tokenRef: - type: string - unregister: - type: boolean - type: object - astraConnect: - properties: - image: - type: string - replicas: - default: 1 - format: int32 - type: integer - type: object - autoSupport: - default: - enrolled: true - url: https://stagesupport.netapp.com/put/AsupPut - description: AutoSupport indicates willingness to participate - in NetApp's proactive support application, NetApp Active IQ. - An internet connection is required (port 442) and all support - data is anonymized. The default election is true and indicates - support data will be sent to NetApp. An empty or blank election - is the same as a default election. Air gapped installations - should enter false. - properties: - enrolled: - default: true - description: Enrolled determines if you want to send anonymous - data to NetApp for support purposes. - type: boolean - url: - default: https://stagesupport.netapp.com/put/AsupPut - description: URL determines where the anonymous data will - be sent - type: string - type: object - imageRegistry: - properties: - name: - type: string - secret: - type: string - type: object - labels: - additionalProperties: - type: string - description: Labels any additional labels wanted to be added to - resources - type: object - nats: - properties: - image: - type: string - replicas: - default: 1 - format: int32 - type: integer - type: object - natsSyncClient: - properties: - cloudBridgeURL: - type: string - hostAliasIP: - type: string - image: - type: string - replicas: - default: 1 - format: int32 - type: integer - type: object - neptune: - properties: - image: - type: string - type: object - skipPreCheck: - default: false - description: SkipPreCheck determines if you want to skip pre-checks - and go ahead with the installation. - type: boolean - type: object - type: object - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/details/operator-sdk/config/samples/astra_v1_astraconnector.yaml b/details/operator-sdk/config/samples/astra_v1_astraconnector.yaml deleted file mode 100644 index f46e6dc7..00000000 --- a/details/operator-sdk/config/samples/astra_v1_astraconnector.yaml +++ /dev/null @@ -1,14 +0,0 @@ -apiVersion: astra.netapp.io/v1 -kind: AstraConnector -metadata: - name: astra-connector - namespace: astra-connector -spec: - astra: - tokenRef: Secret reference to API Token from the API Access page in Astra UI - accountId: Astra Account ID from the API Access page in Astra UI - skipTLSValidation: true - clusterName: Name of your cluster - natsSyncClient: - cloudBridgeURL: https://integration.astra.netapp.io - hostAliasIP: 10.193.60.80 diff --git a/details/operator-sdk/controllers/astraconnector_controller.go b/details/operator-sdk/controllers/astraconnector_controller.go deleted file mode 100644 index 08a85376..00000000 --- a/details/operator-sdk/controllers/astraconnector_controller.go +++ /dev/null @@ -1,590 +0,0 @@ -/* - * Copyright (c) 2024. NetApp, Inc. All Rights Reserved. - */ - -package controllers - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "reflect" - "strings" - "time" - - "github.com/NetApp-Polaris/astra-connector-operator/common" - - "github.com/go-logr/logr" - "github.com/pkg/errors" - - appsv1 "k8s.io/api/apps/v1" - corev1 "k8s.io/api/core/v1" - rbacv1 "k8s.io/api/rbac/v1" - apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/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" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/validation/field" - "k8s.io/apimachinery/pkg/util/wait" - "k8s.io/client-go/dynamic" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/util/retry" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/controller" - "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - ctrllog "sigs.k8s.io/controller-runtime/pkg/log" - "sigs.k8s.io/controller-runtime/pkg/predicate" - - "github.com/NetApp-Polaris/astra-connector-operator/app/conf" - "github.com/NetApp-Polaris/astra-connector-operator/app/register" - "github.com/NetApp-Polaris/astra-connector-operator/details/k8s" - "github.com/NetApp-Polaris/astra-connector-operator/details/k8s/precheck" - v1 "github.com/NetApp-Polaris/astra-connector-operator/details/operator-sdk/api/v1" -) - -// AstraConnectorController reconciles a AstraConnector object -type AstraConnectorController struct { - client.Client - *kubernetes.Clientset - Scheme *runtime.Scheme - DynamicClient dynamic.Interface -} - -// +kubebuilder:rbac:groups=astra.netapp.io,resources=astraconnectors,verbs=get;list;watch;create;update;patch;delete -// +kubebuilder:rbac:groups=astra.netapp.io,resources=astraconnectors/status,verbs=get;update;patch -// +kubebuilder:rbac:groups=astra.netapp.io,resources=astraconnectors/finalizers,verbs=update -// +kubebuilder:rbac:groups=*,resources=*,verbs=* -// +kubebuilder:rbac:groups="";apiextensions.k8s.io;apps;autoscaling;batch;crd.projectcalico.org;extensions;networking.k8s.io;policy;rbac.authorization.k8s.io;security.openshift.io;snapshot.storage.k8s.io;storage.k8s.io;trident.netapp.io,resources=configmaps;cronjobs;customresourcedefinitions;daemonsets;deployments;horizontalpodautoscalers;ingresses;jobs;namespaces;networkpolicies;persistentvolumeclaims;poddisruptionbudgets;pods;podtemplates;podsecuritypolicies;replicasets;replicationcontrollers;replicationcontrollers/scale;rolebindings;roles;secrets;serviceaccounts;services;statefulsets;storageclasses;csidrivers;csinodes;securitycontextconstraints;tridentmirrorrelationships;tridentsnapshotinfos;tridentvolumes;volumesnapshots;volumesnapshotcontents;tridentversions;tridentbackends;tridentnodes,verbs=get;list;watch;delete;use;create;update;patch -// +kubebuilder:rbac:urls=/metrics,verbs=get;list;watch - -func (r *AstraConnectorController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - log := ctrllog.FromContext(ctx) - - // Fetch the AstraConnector instance - astraConnector := &v1.AstraConnector{} - err := r.Get(ctx, req.NamespacedName, astraConnector) - 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 - log.Info("AstraConnector resource not found. Ignoring since object must be deleted") - return ctrl.Result{}, nil - } - // Error reading the object - requeue the request. - log.Error(err, FailedAstraConnectorGet) - _ = r.updateAstraConnectorStatus(ctx, astraConnector, v1.NatsSyncClientStatus{ - Status: FailedAstraConnectorGet, - Registered: "false", - }) - // Do not requeue - return ctrl.Result{}, err - } - - natsSyncClientStatus := astraConnector.Status.NatsSyncClient - natsSyncClientStatus.AstraClusterId = astraConnector.Status.NatsSyncClient.AstraClusterId - - if natsSyncClientStatus.Registered == "" { - natsSyncClientStatus.Registered = "false" - } - - // Validate AstraConnector CR for any errors - err = r.validateAstraConnector(*astraConnector, log) - if err != nil { - // Error validating the connector object. Do not requeue and update the connector status. - log.Error(err, FailedAstraConnectorValidation) - natsSyncClientStatus.Status = fmt.Sprintf("%s; %s", FailedAstraConnectorValidation, err.Error()) - _ = r.updateAstraConnectorStatus(ctx, astraConnector, natsSyncClientStatus) - // Do not requeue. This is a user input error - return ctrl.Result{}, err - } - - // name of our custom finalizer - finalizerName := "netapp.io/finalizer" - // examine DeletionTimestamp to determine if object is under deletion - if astraConnector.ObjectMeta.DeletionTimestamp.IsZero() { - // The object is not being deleted, so if it does not have our finalizer, - // then lets add the finalizer and update the object. This is equivalent - // registering our finalizer. - if !controllerutil.ContainsFinalizer(astraConnector, finalizerName) { - log.Info("Adding finalizer to AstraConnector instance", "finalizerName", finalizerName) - controllerutil.AddFinalizer(astraConnector, finalizerName) - if err := r.Update(ctx, astraConnector); err != nil { - natsSyncClientStatus.Status = FailedFinalizerAdd - _ = r.updateAstraConnectorStatus(ctx, astraConnector, natsSyncClientStatus) - return ctrl.Result{}, err - } - } - } else { - // The object is being deleted - if controllerutil.ContainsFinalizer(astraConnector, finalizerName) { - // Update status message to indicate that CR delete is in progress - //natsSyncClientStatus.Status = DeleteInProgress - //_ = r.updateAstraConnectorStatus(ctx, astraConnector, natsSyncClientStatus) - - k8sUtil := k8s.NewK8sUtil(r.Client, r.Clientset, log) - registerUtil := register.NewClusterRegisterUtil(astraConnector, &http.Client{}, r.Client, k8sUtil, log, context.Background()) - - if astraConnector.Status.NatsSyncClient.Registered == "true" { - log.Info("Removing cluster from Astra upon CRD delete") - err = registerUtil.UnmanageCluster(natsSyncClientStatus.AstraClusterId) - if err != nil { - log.Error(err, "Failed to unmanage cluster, ignoring...") - } - } - - log.Info("Unregistering natsSyncClient upon CRD delete") - err = registerUtil.UnRegisterNatsSyncClient() - if err != nil { - log.Error(err, FailedUnRegisterNSClient+", ignoring...") - } else { - natsSyncClientStatus.Status = "Unregistered" - log.Info("Unregistered natsSyncClient upon CRD delete") - } - - // delete any cluster scoped resources created by the operator - r.deleteConnectorClusterScopedResources(ctx, astraConnector) - - // delete any Neptune resources from the namespace AstraConnector is installed - if err := r.deleteNeptuneResources(ctx, astraConnector.GetNamespace()); err != nil { - log.Error(err, "unable to remove neptune resources") - // Requeue in order to try again to remove resources - return ctrl.Result{Requeue: true}, err - } - - // remove our finalizer from the list and update it. - controllerutil.RemoveFinalizer(astraConnector, finalizerName) - if err := r.Update(ctx, astraConnector); err != nil { - natsSyncClientStatus.Status = FailedFinalizerRemove - _ = r.updateAstraConnectorStatus(ctx, astraConnector, natsSyncClientStatus) - // Do not requeue. Item is being deleted - return ctrl.Result{}, err - } - - // Update status message to indicate that CR delete is in finished - natsSyncClientStatus.Status = DeletionComplete - _ = r.updateAstraConnectorStatus(ctx, astraConnector, natsSyncClientStatus) - } - - // Stop reconciliation as the item is being deleted - // Do not requeue - return ctrl.Result{}, nil - } - - if r.needsReconcile(ctx, *astraConnector, log) { - log.Info("Actual state does not match desired state", "registered", astraConnector.Status.NatsSyncClient.Registered, "desiredSpec", astraConnector.Spec, "observedSpec", astraConnector.Status.ObservedSpec) - if !astraConnector.Spec.SkipPreCheck { - k8sUtil := k8s.NewK8sUtil(r.Client, r.Clientset, log) - preCheckClient := precheck.NewPrecheckClient(log, k8sUtil) - errList := preCheckClient.Run() - - if errList != nil { - errString := "" - for i, err := range errList { - if i > 0 { - errString = errString + ", " - } - - log.Error(err, "Pre-check Error") - errString = errString + err.Error() - } - errString = "Pre-check errors: " + errString - natsSyncClientStatus.Status = errString - _ = r.updateAstraConnectorStatus(ctx, astraConnector, natsSyncClientStatus) - // Do not requeue. Item is being deleted - return ctrl.Result{}, errors.New(errString) - } - } - - // deploy Neptune - if conf.Config.FeatureFlags().DeployNeptune() { - log.Info("Initiating Neptune deployment") - neptuneResult, err := r.deployNeptune(ctx, astraConnector, &natsSyncClientStatus) - if err != nil { - // Note: Returning nil in error since we want to wait for the requeue to happen - // non nil errors triggers the requeue right away - log.Error(err, "Error deploying Neptune, requeueing after delay", "delay", conf.Config.ErrorTimeout()) - return neptuneResult, nil - } - } - - if conf.Config.FeatureFlags().DeployNatsConnector() { - log.Info("Initiating Connector deployment") - var connectorResults ctrl.Result - var deployError error - - if conf.Config.FeatureFlags().NatLess() { - connectorResults, deployError = r.deployNatlessConnector(ctx, astraConnector, &natsSyncClientStatus) - } else { - connectorResults, deployError = r.deployConnector(ctx, astraConnector, &natsSyncClientStatus) - } - if deployError != nil { - // Note: Returning nil in error since we want to wait for the requeue to happen - // non nil errors triggers the requeue right away - log.Error(err, "Error deploying NatsConnector, requeueing after delay", "delay", conf.Config.ErrorTimeout()) - return connectorResults, nil - } - } - - if natsSyncClientStatus.AstraClusterId != "" { - log.Info(fmt.Sprintf("Updating CR status, clusterID: '%s'", natsSyncClientStatus.AstraClusterId)) - } - - _ = r.updateAstraConnectorStatus(ctx, astraConnector, natsSyncClientStatus) - err := r.waitForStatusUpdate(astraConnector, log) - if err != nil { - log.Error(err, "Failed to update status, ignoring since this will be fixed on a future reconcile.") - } - - return ctrl.Result{}, nil - - } else { - log.Info("Actual state matches desired state", "registered", astraConnector.Status.NatsSyncClient.Registered, "desiredSpec", astraConnector.Spec) - return ctrl.Result{}, nil - } -} - -func (r *AstraConnectorController) deleteNeptuneResources(ctx context.Context, namespace string) error { - log := ctrllog.FromContext(ctx) - log.Info("deleting neptune resources") - - // Get a list of all installed CRDs - crdList := &apiextensionsv1.CustomResourceDefinitionList{} - if err := r.Client.List(ctx, crdList); err != nil { - log.Error(err, "Unable to list CRDs") - return err - } - - // Originally, we were listing the CRDs available on the cluster, and then iterating - // through them. However, there are certain implications to deleting one resource before - // deleting another. Eg. - deleting an AppVault CR before deleting a ResourceBackup CR - // will block the ResourceBackup's finalizer clean-up from running b/c it must be - // removing some data from the bucket. - // - // That being the case, we can tailor our deletion steps to delete the resources - // in some logical ordering instead of whatever order is returned to us from the k8s api. - for _, gvr := range neptuneGVRs { - // Check to see if there are any resources to begin with. We may not need to enter the loop below. - if !r.gvrContainsResources(ctx, gvr, namespace) { - log.Info("List returned no resources", "GVR", gvr) - continue - } - - // Delete all resources of this CRD kind in the namespace AstraConnector is installed - if err := r.DynamicClient.Resource(gvr).Namespace(namespace).DeleteCollection(ctx, metav1.DeleteOptions{}, metav1.ListOptions{}); err != nil { - log.Error(err, "Unable to delete resources", "Resource", gvr.Resource) - return err - } - - timeout := time.After(120 * time.Second) - ticker := time.NewTicker(5 * time.Second) - - // Start a loop that periodically checks to see if the resources for a particular - // resource are properly cleaned up as to not leave dangling resources behind. - func(gvr schema.GroupVersionResource) { - for { - select { - case <-timeout: - log.Info("cleaning up resources for GVR timed out", "GVR", gvr) - if err := r.removeResourcesFinalizer(gvr, namespace); err != nil { - log.Error(err, "unable to remove finalizers", "GVR", gvr) - } - - // Keep trying to delete the resources for this GVR - if err := r.DynamicClient.Resource(gvr).Namespace(namespace).DeleteCollection(ctx, metav1.DeleteOptions{}, metav1.ListOptions{}); err != nil { - log.Error(err, "Unable to delete resources", "Resource", gvr.Resource) - } - return - case <-ticker.C: - if !r.gvrContainsResources(ctx, gvr, namespace) { - log.Info("Deleted all resources", "GVR", gvr) - ticker.Stop() - return - } - - // Keep trying to delete the resources for this GVR - if err := r.DynamicClient.Resource(gvr).Namespace(namespace).DeleteCollection(ctx, metav1.DeleteOptions{}, metav1.ListOptions{}); err != nil { - log.Error(err, "Unable to delete resources", "Resource", gvr.Resource) - return - } - log.Info("Resource not cleaned up yet", "GVR", gvr) - } - } - }(gvr) - } - log.Info("Cleaned up neptune resources") - return nil -} - -// removeResourcesFinalizer removes the finalizer from neptune CRs as a last-ditch effort to remove resources. -func (r *AstraConnectorController) removeResourcesFinalizer(gvr schema.GroupVersionResource, namespace string) error { - resourceList, err := r.DynamicClient.Resource(gvr).Namespace(namespace).List(context.Background(), metav1.ListOptions{}) - if err != nil { - return err - } - - // Iterate over the resources - for _, resource := range resourceList.Items { - // Get the finalizers - finalizers, found, err := unstructured.NestedStringSlice(resource.Object, "metadata", "finalizers") - if err != nil { - return err - } - - if found { - finalizers = removeString(finalizers, "astra.netapp.io/finalizer") - - // Update the finalizers in the resource - if err := unstructured.SetNestedStringSlice(resource.Object, finalizers, "metadata", "finalizers"); err != nil { - return err - } - - _, err = r.DynamicClient.Resource(gvr).Namespace(namespace).Update(context.Background(), &resource, metav1.UpdateOptions{}) - if err != nil { - return err - } - } - } - return err -} - -// gvrContainsResources takes in a GVR and a namespace to check if at least one resource for that GVR in the -// namespace exists. -func (r *AstraConnectorController) gvrContainsResources(ctx context.Context, gvr schema.GroupVersionResource, namespace string) bool { - log := ctrllog.FromContext(ctx) - - // We only need to check if at least one resource still exists for this CRD - resourceList, err := r.DynamicClient.Resource(gvr).Namespace(namespace).List(ctx, metav1.ListOptions{Limit: 1}) - if err != nil { - log.Error(err, "Unable to list resources", "Resource", gvr.Resource) - return false - } - if len(resourceList.Items) == 0 { - log.Info("Deleted all resources", "GVR", gvr) - return false - } - return true -} - -// removeString removes a string from a slice of strings. -func removeString(slice []string, s string) []string { - for i, v := range slice { - if v == s { - return append(slice[:i], slice[i+1:]...) - } - } - return slice -} - -func (r *AstraConnectorController) updateAstraConnectorStatus( - ctx context.Context, - astraConnector *v1.AstraConnector, - natsSyncClientStatus v1.NatsSyncClientStatus, - updateObservedSpec ...bool) error { - // Update the astraConnector status with the pod names - // List the pods for this astraConnector's deployment - log := ctrllog.FromContext(ctx) - podList := &corev1.PodList{} - listOpts := []client.ListOption{ - client.InNamespace(astraConnector.Namespace), - } - if err := r.List(ctx, podList, listOpts...); err != nil { - log.Error(err, "Failed to list pods", "Namespace", astraConnector.Namespace) - return err - } - podNames := getPodNames(podList.Items) - - // due to conflicts with network or changing object we need to retry on conflict - return retry.RetryOnConflict(retry.DefaultRetry, func() error { - // Get the current status of the resource - current := astraConnector.DeepCopy() - err := r.Get(ctx, types.NamespacedName{Name: astraConnector.Name, Namespace: astraConnector.Namespace}, current) - if err != nil { - return err - } - - // Update status.Nodes if needed - if !reflect.DeepEqual(podNames, astraConnector.Status.Nodes) { - astraConnector.Status.Nodes = podNames - } - - // FIXME Status should never be nil - if astraConnector.Status.Nodes == nil { - astraConnector.Status.Nodes = []string{""} - } - - if !reflect.DeepEqual(natsSyncClientStatus, astraConnector.Status.NatsSyncClient) { - astraConnector.Status.NatsSyncClient = natsSyncClientStatus - } - - if len(updateObservedSpec) > 0 && updateObservedSpec[0] { - astraConnector.Status.ObservedSpec = astraConnector.Spec - } else { - astraConnector.Status.ObservedSpec = current.Status.ObservedSpec - } - - // Update the status - err = r.Status().Update(ctx, astraConnector) - if err != nil { - return err - } - - return nil - }) -} - -// getPodNames returns the pod names of the array of pods passed in -func getPodNames(pods []corev1.Pod) []string { - var podNames []string - for _, pod := range pods { - podNames = append(podNames, pod.Name) - } - return podNames -} - -// SetupWithManager sets up the controller with the Manager. -func (r *AstraConnectorController) SetupWithManager(mgr ctrl.Manager) error { - return ctrl.NewControllerManagedBy(mgr). - For(&v1.AstraConnector{}). - Owns(&appsv1.Deployment{}). - Owns(&appsv1.StatefulSet{}). - Owns(&corev1.Service{}). - Owns(&corev1.ServiceAccount{}). - Owns(&corev1.ConfigMap{}). - Owns(&rbacv1.Role{}). - Owns(&rbacv1.RoleBinding{}). - WithOptions(controller.Options{MaxConcurrentReconciles: 1}). - WithEventFilter(predicate.GenerationChangedPredicate{}). // Avoid reconcile for status updates - Complete(r) -} - -func (r *AstraConnectorController) validateAstraConnector(connector v1.AstraConnector, logger logr.Logger) error { - var validateErrors field.ErrorList - - logger.V(3).Info("Validating Create AstraConnector") - validateErrors = connector.ValidateCreateAstraConnector() - - var fieldErrors []string - for _, v := range validateErrors { - if v == nil { - continue - } - fieldErrors = append(fieldErrors, fmt.Sprintf("'%s' %s", v.Field, v.Detail)) - } - - if len(fieldErrors) == 0 { - return nil - } - - return errors.New(fmt.Sprintf("Errors while validating AstraConnector CR: %s", strings.Join(fieldErrors, "; "))) -} - -func (r *AstraConnectorController) needsReconcile(ctx context.Context, connector v1.AstraConnector, log logr.Logger) bool { - // Ensure that the cluster has registered successfully - if connector.Status.NatsSyncClient.Registered != "true" { - return true - } - // Ensure that the CR spec has not changed between reconciles - if !reflect.DeepEqual(connector.Status.ObservedSpec, connector.Spec) { - return true - } - - // Check if the number of replicas for each component matches the desired number of replicas - natsSyncDeployment := &appsv1.Deployment{} - err := r.Get(ctx, types.NamespacedName{Name: common.NatsSyncClientName, Namespace: connector.Namespace}, natsSyncDeployment) - if err != nil { - return true - } - if *natsSyncDeployment.Spec.Replicas != connector.Spec.NatsSyncClient.Replicas { - log.Info("Number of NatsSyncClient replicas does not match", "Expected", connector.Spec.NatsSyncClient.Replicas, "Actual", *natsSyncDeployment.Spec.Replicas) - return true - } - - natsDeployment := &appsv1.StatefulSet{} - err = r.Get(ctx, types.NamespacedName{Name: common.NatsName, Namespace: connector.Namespace}, natsDeployment) - if err != nil { - return true - } - // This is currently configured to only ever have one replica due to an issue with - // deploying on GKE. See app/deployer/connector/nats.go for more info. - if *natsDeployment.Spec.Replicas != common.NatsDefaultReplicas { - log.Info("Number of Nats replicas does not match", "Expected", connector.Spec.Nats.Replicas, "Actual", *natsDeployment.Spec.Replicas) - return true - } - - astraConnectDeployment := &appsv1.Deployment{} - err = r.Get(ctx, types.NamespacedName{Name: common.AstraConnectName, Namespace: connector.Namespace}, astraConnectDeployment) - if err != nil { - log.Info("Number of AstraConnector replicas does not match", "Expected", connector.Spec.AstraConnect.Replicas, "Actual", *astraConnectDeployment.Spec.Replicas) - return true - } - if *astraConnectDeployment.Spec.Replicas != connector.Spec.AstraConnect.Replicas { - return true - } - - neptuneDeployment := &appsv1.Deployment{} - err = r.Get(ctx, types.NamespacedName{Name: common.AstraConnectName, Namespace: connector.Namespace}, neptuneDeployment) - if err != nil { - return true - } - if *neptuneDeployment.Spec.Replicas != common.NeptuneReplicas { - log.Info("Number of Neptune replicas does not match", "Expected", connector.Spec.AstraConnect.Replicas, "Actual", *astraConnectDeployment.Spec.Replicas) - return true - } - - return false -} - -// waitForStatusUpdate waits for the status of an AstraConnector to be updated in Kubernetes. -// Status().Update() function returns even though the update is still in progress on k8s. -// Polling to make sure this function exits only after status subresource update is reflected in k8s. -func (r *AstraConnectorController) waitForStatusUpdate(astraConnector *v1.AstraConnector, log logr.Logger) error { - interval := 2 * time.Second - timeout := 15 * time.Second - - ctx, cancel := context.WithTimeout(context.Background(), timeout) - defer cancel() - - // Start polling process - err := errors.Wrap(wait.PollUntilContextTimeout(ctx, interval, timeout, true, - func(ctx context.Context) (bool, error) { - current := &v1.AstraConnector{} - err := r.Get(ctx, types.NamespacedName{Name: astraConnector.Name, Namespace: astraConnector.Namespace}, current) - if err != nil { - log.Error(err, "Failed to get the current status of the AstraConnector. Retrying...") - return false, nil - } - - astraConnectorStatusJson, err := json.Marshal(astraConnector.Status) - if err != nil { - log.Error(err, "Failed to marshal astraConnector.Status to JSON") - return false, nil - } - - currentStatusJson, err := json.Marshal(current.Status) - if err != nil { - log.Error(err, "Failed to marshal current.Status to JSON") - return false, nil - } - - // If the status has not been updated yet, log the current and expected statuses and continue polling. - if string(astraConnectorStatusJson) != string(currentStatusJson) { - log.Info("AstraConnector instance status subresource update is in progress... retrying", - "Expected status", astraConnector.Status, "Actual status", current.Status) - return false, nil - } - - // Otherwise stop polling - return true, nil - }), fmt.Sprintf("AstraConnector status is not updated even after %s", timeout)) - - if err == nil { - log.Info("AstraConnector status reflected in k8s") - } - return err -} diff --git a/details/operator-sdk/controllers/connector.go b/details/operator-sdk/controllers/connector.go deleted file mode 100644 index 91ef5d2e..00000000 --- a/details/operator-sdk/controllers/connector.go +++ /dev/null @@ -1,203 +0,0 @@ -package controllers - -import ( - "context" - "fmt" - "net/http" - "strings" - "time" - - "github.com/NetApp-Polaris/astra-connector-operator/details/k8s" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/types" - ctrl "sigs.k8s.io/controller-runtime" - ctrllog "sigs.k8s.io/controller-runtime/pkg/log" - - "github.com/NetApp-Polaris/astra-connector-operator/app/conf" - "github.com/NetApp-Polaris/astra-connector-operator/app/deployer/connector" - "github.com/NetApp-Polaris/astra-connector-operator/app/deployer/model" - "github.com/NetApp-Polaris/astra-connector-operator/app/register" - "github.com/NetApp-Polaris/astra-connector-operator/common" - v1 "github.com/NetApp-Polaris/astra-connector-operator/details/operator-sdk/api/v1" -) - -func (r *AstraConnectorController) deployConnector(ctx context.Context, - astraConnector *v1.AstraConnector, natsSyncClientStatus *v1.NatsSyncClientStatus) (ctrl.Result, error) { - log := ctrllog.FromContext(ctx) - - // let's deploy Nats, NatsSyncClient and Astra Connector in that order - connectorDeployers := getDeployers() - for _, deployer := range connectorDeployers { - err := r.deployResources(ctx, deployer, astraConnector, natsSyncClientStatus) - if err != nil { - // Failed deploying we want status to reflect that for at least 30 seconds before it's requeued so - // anyone watching can be informed - log.V(3).Info("Requeue after 30 seconds, so that status reflects error") - return ctrl.Result{RequeueAfter: time.Minute * conf.Config.ErrorTimeout()}, err - } - } - - k8sUtil := k8s.NewK8sUtil(r.Client, r.Clientset, log) - - // Let's register the cluster now - registerUtil := register.NewClusterRegisterUtil(astraConnector, &http.Client{}, r.Client, k8sUtil, log, context.Background()) - registered := false - log.Info("Checking for natsSyncClient configmap") - foundCM := &corev1.ConfigMap{} - astraConnectorID := "" - err := r.Get(ctx, types.NamespacedName{Name: common.NatsSyncClientConfigMapName, Namespace: astraConnector.Namespace}, foundCM) - if err != nil { - return ctrl.Result{RequeueAfter: time.Minute * conf.Config.ErrorTimeout()}, err - } - if len(foundCM.Data) != 0 { - registered = true - astraConnectorID, err = registerUtil.GetConnectorIDFromConfigMap(foundCM.Data) - if err != nil { - log.Error(err, FailedLocationIDGet) - natsSyncClientStatus.Status = FailedLocationIDGet - _ = r.updateAstraConnectorStatus(ctx, astraConnector, *natsSyncClientStatus) - return ctrl.Result{RequeueAfter: time.Minute * conf.Config.ErrorTimeout()}, err - } - if astraConnectorID == "" { - log.Error(err, EmptyLocationIDGet) - natsSyncClientStatus.Status = EmptyLocationIDGet - _ = r.updateAstraConnectorStatus(ctx, astraConnector, *natsSyncClientStatus) - return ctrl.Result{RequeueAfter: time.Minute * conf.Config.ErrorTimeout()}, err - } - } - - // RegisterClient - if !astraConnector.Spec.Astra.Unregister { - // Check the feature flag first - if conf.Config.FeatureFlags().SkipAstraRegistration() { - log.Info("Skipping Nats and Astra registration, feature flag set to not register") - natsSyncClientStatus.Status = DeployedComponents - _ = r.updateAstraConnectorStatus(ctx, astraConnector, *natsSyncClientStatus) - return ctrl.Result{RequeueAfter: time.Minute * conf.Config.ErrorTimeout()}, nil - } - - if registered { - log.Info("natsSyncClient already registered", "astraConnectorID", astraConnectorID) - } else { - log.Info("Registering natsSyncClient") - var errorReason string - astraConnectorID, errorReason, err = registerUtil.RegisterNatsSyncClient() - if err != nil { - log.Error(err, FailedRegisterNSClient) - natsSyncClientStatus.Status = errorReason - _ = r.updateAstraConnectorStatus(ctx, astraConnector, *natsSyncClientStatus) - return ctrl.Result{RequeueAfter: time.Minute * conf.Config.ErrorTimeout()}, err - } - - log.Info("natsSyncClient ConnectorID", "astraConnectorID", astraConnectorID) - } - natsSyncClientStatus.AstraConnectorID = astraConnectorID - natsSyncClientStatus.Status = RegisterNSClient - - if astraConnector.Spec.Astra.TokenRef == "" || astraConnector.Spec.Astra.AccountId == "" || astraConnector.Spec.Astra.ClusterName == "" { - log.Info("Skipping cluster registration with Astra, incomplete Astra details provided TokenRef/AccountId/ClusterName") - } else { - log.Info("Registering cluster with Astra") - - // Check if there is a cluster ID from: - // 1. CR Status. If it is in hear it means we have already been through this loop once and know what the ID is - // 2. Check the CR Spec. If it is in here, use it. It will be validated later. - // 3. If the clusterID is in neither of the above, leave it "" and the operator will create one and populate the status - // 4. Save the clusterID to the CR Status - var clusterId string - if strings.TrimSpace(natsSyncClientStatus.AstraClusterId) != "" { - clusterId = natsSyncClientStatus.AstraClusterId - log.WithValues("clusterID", clusterId).Info("using clusterID from CR Status") - } else { - clusterId = astraConnector.Spec.Astra.ClusterId - } - - var errorReason string - natsSyncClientStatus.AstraClusterId, errorReason, err = registerUtil.RegisterClusterWithAstra(astraConnectorID, clusterId) - if err != nil { - log.Error(err, FailedConnectorIDAdd) - natsSyncClientStatus.Status = errorReason - _ = r.updateAstraConnectorStatus(ctx, astraConnector, *natsSyncClientStatus) - return ctrl.Result{RequeueAfter: time.Minute * conf.Config.ErrorTimeout()}, err - } - log.Info("Registered cluster with Astra") - } - natsSyncClientStatus.Registered = "true" - natsSyncClientStatus.Status = RegisteredWithAstra - } else { - if registered { - log.Info("Unregistering natsSyncClient") - err = registerUtil.UnRegisterNatsSyncClient() - if err != nil { - log.Error(err, FailedUnRegisterNSClient) - natsSyncClientStatus.Status = FailedUnRegisterNSClient - _ = r.updateAstraConnectorStatus(ctx, astraConnector, *natsSyncClientStatus) - return ctrl.Result{RequeueAfter: time.Minute * conf.Config.ErrorTimeout()}, err - } - log.Info(UnregisterNSClient) - } else { - log.Info("Already unregistered with Astra") - } - natsSyncClientStatus.Registered = "false" - natsSyncClientStatus.AstraConnectorID = "" - natsSyncClientStatus.Status = UnregisterNSClient - } - - // if we are registered and have a clusterid let's set up the asup cr - if natsSyncClientStatus.Registered == "true" && natsSyncClientStatus.AstraClusterId != "" { - err = r.createASUPCR(ctx, astraConnector, natsSyncClientStatus.AstraClusterId) - if err != nil { - log.Error(err, FailedASUPCreation) - natsSyncClientStatus.Status = FailedASUPCreation - _ = r.updateAstraConnectorStatus(ctx, astraConnector, *natsSyncClientStatus) - return ctrl.Result{RequeueAfter: time.Minute * conf.Config.ErrorTimeout()}, err - } - } - - // No need to requeue due to success - return ctrl.Result{}, nil -} - -func getDeployers() []model.Deployer { - return []model.Deployer{connector.NewNatsDeployer(), connector.NewNatsSyncClientDeployer(), connector.NewAstraConnectorDeployer()} -} - -func (r *AstraConnectorController) deleteConnectorClusterScopedResources(ctx context.Context, astraConnector *v1.AstraConnector) { - connectorDeployers := getDeployers() - for _, deployer := range connectorDeployers { - r.deleteClusterScopedResources(ctx, deployer, astraConnector) - } -} - -func (r *AstraConnectorController) createASUPCR(ctx context.Context, astraConnector *v1.AstraConnector, astraClusterID string) error { - log := ctrllog.FromContext(ctx) - k8sUtil := k8s.NewK8sUtil(r.Client, r.Clientset, log) - - cr := &unstructured.Unstructured{ - Object: map[string]interface{}{ - "apiVersion": "astra.netapp.io/v1", - "kind": "AutoSupportBundleSchedule", - "metadata": map[string]interface{}{ - "name": "asupbundleschedule-" + astraClusterID, - "namespace": astraConnector.Namespace, - }, - "spec": map[string]interface{}{ - "enabled": astraConnector.Spec.AutoSupport.Enrolled, - }, - }, - } - // Define the MutateFn function - mutateFn := func() error { - cr.Object["spec"].(map[string]interface{})["enabled"] = astraConnector.Spec.AutoSupport.Enrolled - return nil - } - result, err := k8sUtil.CreateOrUpdateResource(ctx, cr, astraConnector, mutateFn) - if err != nil { - return err - } - - log.Info(fmt.Sprintf("Successfully %s AutoSupportBundleSchedule", result)) - return nil -} diff --git a/details/operator-sdk/controllers/constants.go b/details/operator-sdk/controllers/constants.go deleted file mode 100644 index 0f29d0b1..00000000 --- a/details/operator-sdk/controllers/constants.go +++ /dev/null @@ -1,129 +0,0 @@ -package controllers - -import ( - "k8s.io/apimachinery/pkg/runtime/schema" -) - -// TODO: need to manually update this list if changes are made -var neptuneGVRs = []schema.GroupVersionResource{ - { - Group: "astra.netapp.io", - Version: "v1", - Resource: "backupinplacerestores", - }, - { - Group: "astra.netapp.io", - Version: "v1", - Resource: "backuprestores", - }, - { - Group: "astra.netapp.io", - Version: "v1", - Resource: "appmirrorrelationships", - }, - { - Group: "astra.netapp.io", - Version: "v1", - Resource: "snapshotinplacerestores", - }, - { - Group: "astra.netapp.io", - Version: "v1", - Resource: "snapshotrestores", - }, - { - Group: "astra.netapp.io", - Version: "v1", - Resource: "backups", - }, - { - Group: "astra.netapp.io", - Version: "v1", - Resource: "snapshots", - }, - { - Group: "astra.netapp.io", - Version: "v1", - Resource: "applications", - }, - { - Group: "astra.netapp.io", - Version: "v1", - Resource: "appmirrorupdates", - }, - { - Group: "astra.netapp.io", - Version: "v1", - Resource: "autosupportbundles", - }, - { - Group: "astra.netapp.io", - Version: "v1", - Resource: "autosupportbundleschedules", - }, - { - Group: "astra.netapp.io", - Version: "v1", - Resource: "exechooks", - }, - { - Group: "astra.netapp.io", - Version: "v1", - Resource: "exechooksruns", - }, - { - Group: "astra.netapp.io", - Version: "v1", - Resource: "pvccopies", - }, - { - Group: "astra.netapp.io", - Version: "v1", - Resource: "pvcerases", - }, - { - Group: "astra.netapp.io", - Version: "v1", - Resource: "resourcedeletes", - }, - { - Group: "astra.netapp.io", - Version: "v1", - Resource: "resourcerestores", - }, - { - Group: "astra.netapp.io", - Version: "v1", - Resource: "resourcesummaryuploads", - }, - { - Group: "astra.netapp.io", - Version: "v1", - Resource: "resticvolumebackups", - }, - { - Group: "astra.netapp.io", - Version: "v1", - Resource: "resticvolumerestores", - }, - { - Group: "astra.netapp.io", - Version: "v1", - Resource: "schedules", - }, - { - Group: "astra.netapp.io", - Version: "v1", - Resource: "resourcebackups", - }, - { - Group: "astra.netapp.io", - Version: "v1", - Resource: "shutdownsnapshots", - }, - { - Group: "astra.netapp.io", - Version: "v1", - Resource: "appvaults", - }, -} diff --git a/details/operator-sdk/controllers/natless_connector.go b/details/operator-sdk/controllers/natless_connector.go deleted file mode 100644 index 4e438bd4..00000000 --- a/details/operator-sdk/controllers/natless_connector.go +++ /dev/null @@ -1,53 +0,0 @@ -package controllers - -import ( - "context" - "time" - - ctrl "sigs.k8s.io/controller-runtime" - ctrllog "sigs.k8s.io/controller-runtime/pkg/log" - - "github.com/NetApp-Polaris/astra-connector-operator/app/conf" - "github.com/NetApp-Polaris/astra-connector-operator/app/deployer/connector" - "github.com/NetApp-Polaris/astra-connector-operator/app/deployer/model" - v1 "github.com/NetApp-Polaris/astra-connector-operator/details/operator-sdk/api/v1" -) - -func (r *AstraConnectorController) deployNatlessConnector(ctx context.Context, - astraConnector *v1.AstraConnector, natsSyncClientStatus *v1.NatsSyncClientStatus) (ctrl.Result, error) { - log := ctrllog.FromContext(ctx) - - // let's deploy Astra Connector without Nats - connectorDeployers := getNatlessDeployers() - for _, deployer := range connectorDeployers { - err := r.deployResources(ctx, deployer, astraConnector, natsSyncClientStatus) - if err != nil { - // Failed deploying we want status to reflect that for at least 30 seconds before it's requeued so - // anyone watching can be informed - log.V(3).Info("Requeue after 30 seconds, so that status reflects error") - return ctrl.Result{RequeueAfter: time.Minute * conf.Config.ErrorTimeout()}, err - } - } - - // if we are registered and have a clusterid let's set up the asup cr - err := r.createASUPCR(ctx, astraConnector, "1") - if err != nil { - log.Error(err, FailedASUPCreation) - natsSyncClientStatus.Status = FailedASUPCreation - _ = r.updateAstraConnectorStatus(ctx, astraConnector, *natsSyncClientStatus) - return ctrl.Result{RequeueAfter: time.Minute * conf.Config.ErrorTimeout()}, err - } - - natsSyncClientStatus.Registered = "true" - natsSyncClientStatus.AstraConnectorID = "n/a" - natsSyncClientStatus.AstraClusterId = "n/a" - natsSyncClientStatus.Status = RegisteredWithAstra - _ = r.updateAstraConnectorStatus(ctx, astraConnector, *natsSyncClientStatus) - - // No need to requeue due to success - return ctrl.Result{}, nil -} - -func getNatlessDeployers() []model.Deployer { - return []model.Deployer{connector.NewAstraConnectorNatlessDeployer()} -} diff --git a/details/operator-sdk/controllers/neptune.go b/details/operator-sdk/controllers/neptune.go deleted file mode 100644 index d4cb1da8..00000000 --- a/details/operator-sdk/controllers/neptune.go +++ /dev/null @@ -1,31 +0,0 @@ -package controllers - -import ( - "context" - "time" - - ctrl "sigs.k8s.io/controller-runtime" - - "github.com/NetApp-Polaris/astra-connector-operator/app/conf" - "github.com/NetApp-Polaris/astra-connector-operator/app/deployer/neptune" - v1 "github.com/NetApp-Polaris/astra-connector-operator/details/operator-sdk/api/v1" -) - -func (r *AstraConnectorController) deployNeptune(ctx context.Context, - astraConnector *v1.AstraConnector, natsSyncClientStatus *v1.NatsSyncClientStatus) (ctrl.Result, error) { - - // TODO CRD will be installed as part of our crd install - // check if they are installed if not error here or maybe a pre-check - - // Deploy Neptune - neptuneDeployer := neptune.NewNeptuneClientDeployerV2() - err := r.deployResources(ctx, neptuneDeployer, astraConnector, natsSyncClientStatus) - if err != nil { - // Failed deploying we want status to reflect that for at least 30 seconds before it's requeued so - // anyone watching can be informed - return ctrl.Result{RequeueAfter: time.Minute * conf.Config.ErrorTimeout()}, err - } - - // No need to requeue due to success - return ctrl.Result{}, nil -} diff --git a/details/operator-sdk/hack/boilerplate.go.txt b/hack/boilerplate.go.txt similarity index 100% rename from details/operator-sdk/hack/boilerplate.go.txt rename to hack/boilerplate.go.txt diff --git a/details/k8s/k8s_util.go b/k8s/k8s_util.go similarity index 82% rename from details/k8s/k8s_util.go rename to k8s/k8s_util.go index ffc4a308..750dc8bd 100644 --- a/details/k8s/k8s_util.go +++ b/k8s/k8s_util.go @@ -13,7 +13,6 @@ import ( apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/discovery" - "k8s.io/client-go/kubernetes" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" @@ -23,7 +22,6 @@ import ( type K8sUtil struct { client.Client - kubernetes.Interface Log logr.Logger } @@ -32,12 +30,10 @@ type K8sUtilInterface interface { DeleteResource(context.Context, client.Object) error VersionGet() (string, error) IsCRDInstalled(string) bool - RESTGet(string) ([]byte, error) - K8sClientset() kubernetes.Interface } -func NewK8sUtil(c client.Client, i kubernetes.Interface, log logr.Logger) K8sUtilInterface { - return &K8sUtil{Client: c, Interface: i, Log: log} +func NewK8sUtil(c client.Client, log logr.Logger) K8sUtilInterface { + return &K8sUtil{Client: c, Log: log} } // CreateOrUpdateResource creates a role, provided a namespace and name @@ -105,13 +101,3 @@ func (r *K8sUtil) IsCRDInstalled(crdName string) bool { } } - -// RESTGet Makes a GET request on the K8s Rest Client and returns the raw byte array -func (r *K8sUtil) RESTGet(path string) ([]byte, error) { - return r.Interface.Discovery().RESTClient().Get().AbsPath(path).DoRaw(context.TODO()) -} - -// K8sClientset Returns the k8s Clientset -func (r *K8sUtil) K8sClientset() kubernetes.Interface { - return r.Interface -} diff --git a/details/k8s/k8s_util_test.go b/k8s/k8s_util_test.go similarity index 98% rename from details/k8s/k8s_util_test.go rename to k8s/k8s_util_test.go index 9d3095ef..c74661c1 100644 --- a/details/k8s/k8s_util_test.go +++ b/k8s/k8s_util_test.go @@ -7,6 +7,7 @@ package k8s_test import ( "context" "github.com/NetApp-Polaris/astra-connector-operator/app/deployer/model" + "github.com/NetApp-Polaris/astra-connector-operator/k8s" "testing" "github.com/stretchr/testify/assert" @@ -16,7 +17,6 @@ import ( "k8s.io/client-go/kubernetes/fake" "sigs.k8s.io/controller-runtime/pkg/client" - "github.com/NetApp-Polaris/astra-connector-operator/details/k8s" testutil "github.com/NetApp-Polaris/astra-connector-operator/test/test-util" ) diff --git a/details/k8s/precheck/k8s_crd.go b/k8s/precheck/k8s_crd.go similarity index 100% rename from details/k8s/precheck/k8s_crd.go rename to k8s/precheck/k8s_crd.go diff --git a/details/k8s/precheck/k8s_version.go b/k8s/precheck/k8s_version.go similarity index 100% rename from details/k8s/precheck/k8s_version.go rename to k8s/precheck/k8s_version.go diff --git a/details/k8s/precheck/k8s_version_test.go b/k8s/precheck/k8s_version_test.go similarity index 94% rename from details/k8s/precheck/k8s_version_test.go rename to k8s/precheck/k8s_version_test.go index 1a76af3b..9b936712 100644 --- a/details/k8s/precheck/k8s_version_test.go +++ b/k8s/precheck/k8s_version_test.go @@ -1,12 +1,11 @@ package precheck_test import ( + "github.com/NetApp-Polaris/astra-connector-operator/k8s/precheck" "github.com/NetApp-Polaris/astra-connector-operator/mocks" testutil "github.com/NetApp-Polaris/astra-connector-operator/test/test-util" "github.com/stretchr/testify/assert" "testing" - - "github.com/NetApp-Polaris/astra-connector-operator/details/k8s/precheck" ) func TestIsSupported(t *testing.T) { diff --git a/details/k8s/precheck/precheck.go b/k8s/precheck/precheck.go similarity index 90% rename from details/k8s/precheck/precheck.go rename to k8s/precheck/precheck.go index a2974337..337a6354 100644 --- a/details/k8s/precheck/precheck.go +++ b/k8s/precheck/precheck.go @@ -3,9 +3,8 @@ package precheck import ( + "github.com/NetApp-Polaris/astra-connector-operator/k8s" "github.com/go-logr/logr" - - "github.com/NetApp-Polaris/astra-connector-operator/details/k8s" ) type SetWarning func(message string) error diff --git a/main.go b/main.go index cff2d5ef..a6ec93db 100644 --- a/main.go +++ b/main.go @@ -7,12 +7,12 @@ package main import ( "flag" "fmt" - "k8s.io/client-go/kubernetes" + astrav1 "github.com/NetApp-Polaris/astra-connector-operator/api/v1" + "github.com/NetApp-Polaris/astra-connector-operator/controllers" "os" // 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/dynamic" _ "k8s.io/client-go/plugin/pkg/client/auth" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" @@ -24,8 +24,6 @@ import ( "sigs.k8s.io/controller-runtime/pkg/log/zap" "github.com/NetApp-Polaris/astra-connector-operator/app/conf" - astrav1 "github.com/NetApp-Polaris/astra-connector-operator/details/operator-sdk/api/v1" - "github.com/NetApp-Polaris/astra-connector-operator/details/operator-sdk/controllers" //+kubebuilder:scaffold:imports ) @@ -77,28 +75,19 @@ func main() { os.Exit(1) } - // Create the dynamic client - dynamicClient, err := dynamic.NewForConfig(ctrl.GetConfigOrDie()) - if err != nil { - setupLog.Error(err, "unable to create dynamic client") - os.Exit(1) - } - - // create config and clientset - config := mgr.GetConfig() - clientset, err := kubernetes.NewForConfig(config) - if err != nil { - setupLog.Error(err, "unable to create clientset") + if err = (&controllers.AstraConnectorController{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "AstraConnector") os.Exit(1) } - if err = (&controllers.AstraConnectorController{ - Client: mgr.GetClient(), - Clientset: clientset, - Scheme: mgr.GetScheme(), - DynamicClient: dynamicClient, + if err = (&controllers.AstraNeptuneController{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", "AstraConnector") + setupLog.Error(err, "unable to create controller", "controller", "AstraNeptune") os.Exit(1) } diff --git a/mocks/ClusterRegisterUtil.go b/mocks/ClusterRegisterUtil.go deleted file mode 100644 index c48aa9bc..00000000 --- a/mocks/ClusterRegisterUtil.go +++ /dev/null @@ -1,538 +0,0 @@ -// Code generated by mockery v2.19.0. DO NOT EDIT. - -package mocks - -import ( - http "net/http" - - register "github.com/NetApp-Polaris/astra-connector-operator/app/register" - mock "github.com/stretchr/testify/mock" - - time "time" -) - -// ClusterRegisterUtil is an autogenerated mock type for the ClusterRegisterUtil type -type ClusterRegisterUtil struct { - mock.Mock -} - -// CloudExists provides a mock function with given fields: astraHost, cloudID, apiToken -func (_m *ClusterRegisterUtil) CloudExists(astraHost string, cloudID string, apiToken string) bool { - ret := _m.Called(astraHost, cloudID, apiToken) - - var r0 bool - if rf, ok := ret.Get(0).(func(string, string, string) bool); ok { - r0 = rf(astraHost, cloudID, apiToken) - } else { - r0 = ret.Get(0).(bool) - } - - return r0 -} - -// CreateCloud provides a mock function with given fields: astraHost, cloudType, apiToken -func (_m *ClusterRegisterUtil) CreateCloud(astraHost string, cloudType string, apiToken string) (string, string, error) { - ret := _m.Called(astraHost, cloudType, apiToken) - - var r0 string - if rf, ok := ret.Get(0).(func(string, string, string) string); ok { - r0 = rf(astraHost, cloudType, apiToken) - } else { - r0 = ret.Get(0).(string) - } - - var r1 string - if rf, ok := ret.Get(1).(func(string, string, string) string); ok { - r1 = rf(astraHost, cloudType, apiToken) - } else { - r1 = ret.Get(1).(string) - } - - var r2 error - if rf, ok := ret.Get(2).(func(string, string, string) error); ok { - r2 = rf(astraHost, cloudType, apiToken) - } else { - r2 = ret.Error(2) - } - - return r0, r1, r2 -} - -// CreateCluster provides a mock function with given fields: astraHost, cloudId, astraConnectorId, apiToken -func (_m *ClusterRegisterUtil) CreateCluster(astraHost string, cloudId string, astraConnectorId string, apiToken string) (register.ClusterInfo, string, error) { - ret := _m.Called(astraHost, cloudId, astraConnectorId, apiToken) - - var r0 register.ClusterInfo - if rf, ok := ret.Get(0).(func(string, string, string, string) register.ClusterInfo); ok { - r0 = rf(astraHost, cloudId, astraConnectorId, apiToken) - } else { - r0 = ret.Get(0).(register.ClusterInfo) - } - - var r1 string - if rf, ok := ret.Get(1).(func(string, string, string, string) string); ok { - r1 = rf(astraHost, cloudId, astraConnectorId, apiToken) - } else { - r1 = ret.Get(1).(string) - } - - var r2 error - if rf, ok := ret.Get(2).(func(string, string, string, string) error); ok { - r2 = rf(astraHost, cloudId, astraConnectorId, apiToken) - } else { - r2 = ret.Error(2) - } - - return r0, r1, r2 -} - -// CreateManagedCluster provides a mock function with given fields: astraHost, cloudId, clusterID, connectorInstall, apiToken -func (_m *ClusterRegisterUtil) CreateManagedCluster(astraHost string, cloudId string, clusterID string, connectorInstall string, apiToken string) (string, error) { - ret := _m.Called(astraHost, cloudId, clusterID, connectorInstall, apiToken) - - var r0 string - if rf, ok := ret.Get(0).(func(string, string, string, string, string) string); ok { - r0 = rf(astraHost, cloudId, clusterID, connectorInstall, apiToken) - } else { - r0 = ret.Get(0).(string) - } - - var r1 error - if rf, ok := ret.Get(1).(func(string, string, string, string, string) error); ok { - r1 = rf(astraHost, cloudId, clusterID, connectorInstall, apiToken) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// CreateOrUpdateCluster provides a mock function with given fields: astraHost, cloudId, clusterId, astraConnectorId, connectorInstall, clustersMethod, apiToken -func (_m *ClusterRegisterUtil) CreateOrUpdateCluster(astraHost string, cloudId string, clusterId string, astraConnectorId string, connectorInstall string, clustersMethod string, apiToken string) (register.ClusterInfo, string, error) { - ret := _m.Called(astraHost, cloudId, clusterId, astraConnectorId, connectorInstall, clustersMethod, apiToken) - - var r0 register.ClusterInfo - if rf, ok := ret.Get(0).(func(string, string, string, string, string, string, string) register.ClusterInfo); ok { - r0 = rf(astraHost, cloudId, clusterId, astraConnectorId, connectorInstall, clustersMethod, apiToken) - } else { - r0 = ret.Get(0).(register.ClusterInfo) - } - - var r1 string - if rf, ok := ret.Get(1).(func(string, string, string, string, string, string, string) string); ok { - r1 = rf(astraHost, cloudId, clusterId, astraConnectorId, connectorInstall, clustersMethod, apiToken) - } else { - r1 = ret.Get(1).(string) - } - - var r2 error - if rf, ok := ret.Get(2).(func(string, string, string, string, string, string, string) error); ok { - r2 = rf(astraHost, cloudId, clusterId, astraConnectorId, connectorInstall, clustersMethod, apiToken) - } else { - r2 = ret.Error(2) - } - - return r0, r1, r2 -} - -// CreateOrUpdateManagedCluster provides a mock function with given fields: astraHost, cloudId, clusterId, astraConnectorId, managedClustersMethod, apiToken -func (_m *ClusterRegisterUtil) CreateOrUpdateManagedCluster(astraHost string, cloudId string, clusterId string, astraConnectorId string, managedClustersMethod string, apiToken string) (register.ClusterInfo, string, error) { - ret := _m.Called(astraHost, cloudId, clusterId, astraConnectorId, managedClustersMethod, apiToken) - - var r0 register.ClusterInfo - if rf, ok := ret.Get(0).(func(string, string, string, string, string, string) register.ClusterInfo); ok { - r0 = rf(astraHost, cloudId, clusterId, astraConnectorId, managedClustersMethod, apiToken) - } else { - r0 = ret.Get(0).(register.ClusterInfo) - } - - var r1 string - if rf, ok := ret.Get(1).(func(string, string, string, string, string, string) string); ok { - r1 = rf(astraHost, cloudId, clusterId, astraConnectorId, managedClustersMethod, apiToken) - } else { - r1 = ret.Get(1).(string) - } - - var r2 error - if rf, ok := ret.Get(2).(func(string, string, string, string, string, string) error); ok { - r2 = rf(astraHost, cloudId, clusterId, astraConnectorId, managedClustersMethod, apiToken) - } else { - r2 = ret.Error(2) - } - - return r0, r1, r2 -} - -// GetAPITokenFromSecret provides a mock function with given fields: secretName -func (_m *ClusterRegisterUtil) GetAPITokenFromSecret(secretName string) (string, string, error) { - ret := _m.Called(secretName) - - var r0 string - if rf, ok := ret.Get(0).(func(string) string); ok { - r0 = rf(secretName) - } else { - r0 = ret.Get(0).(string) - } - - var r1 string - if rf, ok := ret.Get(1).(func(string) string); ok { - r1 = rf(secretName) - } else { - r1 = ret.Get(1).(string) - } - - var r2 error - if rf, ok := ret.Get(2).(func(string) error); ok { - r2 = rf(secretName) - } else { - r2 = ret.Error(2) - } - - return r0, r1, r2 -} - -// GetCloudId provides a mock function with given fields: astraHost, cloudType, apiToken, retryTimeout -func (_m *ClusterRegisterUtil) GetCloudId(astraHost string, cloudType string, apiToken string, retryTimeout ...time.Duration) (string, string, error) { - _va := make([]interface{}, len(retryTimeout)) - for _i := range retryTimeout { - _va[_i] = retryTimeout[_i] - } - var _ca []interface{} - _ca = append(_ca, astraHost, cloudType, apiToken) - _ca = append(_ca, _va...) - ret := _m.Called(_ca...) - - var r0 string - if rf, ok := ret.Get(0).(func(string, string, string, ...time.Duration) string); ok { - r0 = rf(astraHost, cloudType, apiToken, retryTimeout...) - } else { - r0 = ret.Get(0).(string) - } - - var r1 string - if rf, ok := ret.Get(1).(func(string, string, string, ...time.Duration) string); ok { - r1 = rf(astraHost, cloudType, apiToken, retryTimeout...) - } else { - r1 = ret.Get(1).(string) - } - - var r2 error - if rf, ok := ret.Get(2).(func(string, string, string, ...time.Duration) error); ok { - r2 = rf(astraHost, cloudType, apiToken, retryTimeout...) - } else { - r2 = ret.Error(2) - } - - return r0, r1, r2 -} - -// GetCluster provides a mock function with given fields: astraHost, cloudId, clusterId, apiToken -func (_m *ClusterRegisterUtil) GetCluster(astraHost string, cloudId string, clusterId string, apiToken string) (register.Cluster, string, error) { - ret := _m.Called(astraHost, cloudId, clusterId, apiToken) - - var r0 register.Cluster - if rf, ok := ret.Get(0).(func(string, string, string, string) register.Cluster); ok { - r0 = rf(astraHost, cloudId, clusterId, apiToken) - } else { - r0 = ret.Get(0).(register.Cluster) - } - - var r1 string - if rf, ok := ret.Get(1).(func(string, string, string, string) string); ok { - r1 = rf(astraHost, cloudId, clusterId, apiToken) - } else { - r1 = ret.Get(1).(string) - } - - var r2 error - if rf, ok := ret.Get(2).(func(string, string, string, string) error); ok { - r2 = rf(astraHost, cloudId, clusterId, apiToken) - } else { - r2 = ret.Error(2) - } - - return r0, r1, r2 -} - -// GetClusters provides a mock function with given fields: astraHost, cloudId, apiToken -func (_m *ClusterRegisterUtil) GetClusters(astraHost string, cloudId string, apiToken string) (register.GetClustersResponse, string, error) { - ret := _m.Called(astraHost, cloudId, apiToken) - - var r0 register.GetClustersResponse - if rf, ok := ret.Get(0).(func(string, string, string) register.GetClustersResponse); ok { - r0 = rf(astraHost, cloudId, apiToken) - } else { - r0 = ret.Get(0).(register.GetClustersResponse) - } - - var r1 string - if rf, ok := ret.Get(1).(func(string, string, string) string); ok { - r1 = rf(astraHost, cloudId, apiToken) - } else { - r1 = ret.Get(1).(string) - } - - var r2 error - if rf, ok := ret.Get(2).(func(string, string, string) error); ok { - r2 = rf(astraHost, cloudId, apiToken) - } else { - r2 = ret.Error(2) - } - - return r0, r1, r2 -} - -// GetConnectorIDFromConfigMap provides a mock function with given fields: cmData -func (_m *ClusterRegisterUtil) GetConnectorIDFromConfigMap(cmData map[string]string) (string, error) { - ret := _m.Called(cmData) - - var r0 string - if rf, ok := ret.Get(0).(func(map[string]string) string); ok { - r0 = rf(cmData) - } else { - r0 = ret.Get(0).(string) - } - - var r1 error - if rf, ok := ret.Get(1).(func(map[string]string) error); ok { - r1 = rf(cmData) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// GetNatsSyncClientRegistrationURL provides a mock function with given fields: -func (_m *ClusterRegisterUtil) GetNatsSyncClientRegistrationURL() string { - ret := _m.Called() - - var r0 string - if rf, ok := ret.Get(0).(func() string); ok { - r0 = rf() - } else { - r0 = ret.Get(0).(string) - } - - return r0 -} - -// GetNatsSyncClientUnregisterURL provides a mock function with given fields: -func (_m *ClusterRegisterUtil) GetNatsSyncClientUnregisterURL() string { - ret := _m.Called() - - var r0 string - if rf, ok := ret.Get(0).(func() string); ok { - r0 = rf() - } else { - r0 = ret.Get(0).(string) - } - - return r0 -} - -// GetOrCreateCloud provides a mock function with given fields: astraHost, cloudType, apiToken -func (_m *ClusterRegisterUtil) GetOrCreateCloud(astraHost string, cloudType string, apiToken string) (string, string, error) { - ret := _m.Called(astraHost, cloudType, apiToken) - - var r0 string - if rf, ok := ret.Get(0).(func(string, string, string) string); ok { - r0 = rf(astraHost, cloudType, apiToken) - } else { - r0 = ret.Get(0).(string) - } - - var r1 string - if rf, ok := ret.Get(1).(func(string, string, string) string); ok { - r1 = rf(astraHost, cloudType, apiToken) - } else { - r1 = ret.Get(1).(string) - } - - var r2 error - if rf, ok := ret.Get(2).(func(string, string, string) error); ok { - r2 = rf(astraHost, cloudType, apiToken) - } else { - r2 = ret.Error(2) - } - - return r0, r1, r2 -} - -// ListClouds provides a mock function with given fields: astraHost, apiToken -func (_m *ClusterRegisterUtil) ListClouds(astraHost string, apiToken string) (*http.Response, error) { - ret := _m.Called(astraHost, apiToken) - - var r0 *http.Response - if rf, ok := ret.Get(0).(func(string, string) *http.Response); ok { - r0 = rf(astraHost, apiToken) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*http.Response) - } - } - - var r1 error - if rf, ok := ret.Get(1).(func(string, string) error); ok { - r1 = rf(astraHost, apiToken) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// RegisterClusterWithAstra provides a mock function with given fields: astraConnectorId, clusterId -func (_m *ClusterRegisterUtil) RegisterClusterWithAstra(astraConnectorId string, clusterId string) (string, string, error) { - ret := _m.Called(astraConnectorId, clusterId) - - var r0 string - if rf, ok := ret.Get(0).(func(string, string) string); ok { - r0 = rf(astraConnectorId, clusterId) - } else { - r0 = ret.Get(0).(string) - } - - var r1 string - if rf, ok := ret.Get(1).(func(string, string) string); ok { - r1 = rf(astraConnectorId, clusterId) - } else { - r1 = ret.Get(1).(string) - } - - var r2 error - if rf, ok := ret.Get(2).(func(string, string) error); ok { - r2 = rf(astraConnectorId, clusterId) - } else { - r2 = ret.Error(2) - } - - return r0, r1, r2 -} - -// RegisterNatsSyncClient provides a mock function with given fields: -func (_m *ClusterRegisterUtil) RegisterNatsSyncClient() (string, string, error) { - ret := _m.Called() - - var r0 string - if rf, ok := ret.Get(0).(func() string); ok { - r0 = rf() - } else { - r0 = ret.Get(0).(string) - } - - var r1 string - if rf, ok := ret.Get(1).(func() string); ok { - r1 = rf() - } else { - r1 = ret.Get(1).(string) - } - - var r2 error - if rf, ok := ret.Get(2).(func() error); ok { - r2 = rf() - } else { - r2 = ret.Error(2) - } - - return r0, r1, r2 -} - -// UnRegisterNatsSyncClient provides a mock function with given fields: -func (_m *ClusterRegisterUtil) UnRegisterNatsSyncClient() error { - ret := _m.Called() - - var r0 error - if rf, ok := ret.Get(0).(func() error); ok { - r0 = rf() - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// UpdateCluster provides a mock function with given fields: astraHost, cloudId, clusterId, astraConnectorId, apiToken -func (_m *ClusterRegisterUtil) UpdateCluster(astraHost string, cloudId string, clusterId string, astraConnectorId string, apiToken string) (string, error) { - ret := _m.Called(astraHost, cloudId, clusterId, astraConnectorId, apiToken) - - var r0 string - if rf, ok := ret.Get(0).(func(string, string, string, string, string) string); ok { - r0 = rf(astraHost, cloudId, clusterId, astraConnectorId, apiToken) - } else { - r0 = ret.Get(0).(string) - } - - var r1 error - if rf, ok := ret.Get(1).(func(string, string, string, string, string) error); ok { - r1 = rf(astraHost, cloudId, clusterId, astraConnectorId, apiToken) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// UpdateManagedCluster provides a mock function with given fields: astraHost, clusterId, astraConnectorId, connectorInstall, apiToken -func (_m *ClusterRegisterUtil) UpdateManagedCluster(astraHost string, clusterId string, astraConnectorId string, connectorInstall string, apiToken string) (string, error) { - ret := _m.Called(astraHost, clusterId, astraConnectorId, connectorInstall, apiToken) - - var r0 string - if rf, ok := ret.Get(0).(func(string, string, string, string, string) string); ok { - r0 = rf(astraHost, clusterId, astraConnectorId, connectorInstall, apiToken) - } else { - r0 = ret.Get(0).(string) - } - - var r1 error - if rf, ok := ret.Get(1).(func(string, string, string, string, string) error); ok { - r1 = rf(astraHost, clusterId, astraConnectorId, connectorInstall, apiToken) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// ValidateAndGetCluster provides a mock function with given fields: astraHost, cloudId, apiToken, clusterId -func (_m *ClusterRegisterUtil) ValidateAndGetCluster(astraHost string, cloudId string, apiToken string, clusterId string) (register.ClusterInfo, string, error) { - ret := _m.Called(astraHost, cloudId, apiToken, clusterId) - - var r0 register.ClusterInfo - if rf, ok := ret.Get(0).(func(string, string, string, string) register.ClusterInfo); ok { - r0 = rf(astraHost, cloudId, apiToken, clusterId) - } else { - r0 = ret.Get(0).(register.ClusterInfo) - } - - var r1 string - if rf, ok := ret.Get(1).(func(string, string, string, string) string); ok { - r1 = rf(astraHost, cloudId, apiToken, clusterId) - } else { - r1 = ret.Get(1).(string) - } - - var r2 error - if rf, ok := ret.Get(2).(func(string, string, string, string) error); ok { - r2 = rf(astraHost, cloudId, apiToken, clusterId) - } else { - r2 = ret.Error(2) - } - - return r0, r1, r2 -} - -type mockConstructorTestingTNewClusterRegisterUtil interface { - mock.TestingT - Cleanup(func()) -} - -// NewClusterRegisterUtil creates a new instance of ClusterRegisterUtil. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewClusterRegisterUtil(t mockConstructorTestingTNewClusterRegisterUtil) *ClusterRegisterUtil { - mock := &ClusterRegisterUtil{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/mocks/Deployer.go b/mocks/Deployer.go index b2f7ae18..d6b458a1 100644 --- a/mocks/Deployer.go +++ b/mocks/Deployer.go @@ -11,7 +11,7 @@ import ( mock "github.com/stretchr/testify/mock" - v1 "github.com/NetApp-Polaris/astra-connector-operator/details/operator-sdk/api/v1" + v1 "github.com/NetApp-Polaris/astra-connector-operator/api/v1" ) // Deployer is an autogenerated mock type for the Deployer type diff --git a/mocks/getK8sResources.go b/mocks/getK8sResources.go index 903a6bd2..653648ed 100644 --- a/mocks/getK8sResources.go +++ b/mocks/getK8sResources.go @@ -13,7 +13,7 @@ import ( model "github.com/NetApp-Polaris/astra-connector-operator/app/deployer/model" - v1 "github.com/NetApp-Polaris/astra-connector-operator/details/operator-sdk/api/v1" + v1 "github.com/NetApp-Polaris/astra-connector-operator/api/v1" ) // getK8sResources is an autogenerated mock type for the getK8sResources type diff --git a/scripts/.DS_Store b/scripts/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6 GIT binary patch literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0