diff --git a/.github/workflows/golangci-lint.yaml b/.github/workflows/golangci-lint.yaml index aa840da..14808ef 100644 --- a/.github/workflows/golangci-lint.yaml +++ b/.github/workflows/golangci-lint.yaml @@ -1,8 +1,5 @@ name: golangci-lint on: - push: - branches: - - main pull_request: permissions: @@ -36,7 +33,7 @@ jobs: # Note: By default, the `.golangci.yml` file should be at the root of the repository. # The location of the configuration file can be changed by using `--config=` # args: --timeout=30m --config=/my/path/.golangci.yml --issues-exit-code=0 - args: --timeout=5m + args: --timeout=5m --out-format=github-actions # Optional: show only new issues if it's a pull request. The default value is `false`. # only-new-issues: true diff --git a/.golangci.yaml b/.golangci.yaml index bc3c98f..96f350d 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -79,7 +79,7 @@ linters: - godox # detects TODOs keywords # - goimports # in addition to fixing imports, goimports also formats your code in the same style as gofmt. Dissabled as can't work together with `gci` - gomnd # detects magic numbers - - gomoddirectives # manages the use of 'replace', 'retract', and 'excludes' directives in go.mod + # - gomoddirectives # manages the use of 'replace', 'retract', and 'excludes' directives in go.mod - nestif # reports deeply nested if statements - nilerr # finds the code that returns nil even if it checks that the error is not nil - nilnil # checks that there is no simultaneous return of nil error and an invalid value diff --git a/Dockerfile b/Dockerfile index 97b3f0b..e52494c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,6 +13,7 @@ RUN go mod download # Copy the go source COPY main.go main.go +COPY api/ api/ COPY controllers/ controllers/ COPY internal/ internal/ COPY pkg/ pkg/ diff --git a/PROJECT b/PROJECT index 4982caa..1e65d36 100644 --- a/PROJECT +++ b/PROJECT @@ -11,10 +11,9 @@ resources: - api: crdVersion: v1 namespaced: true - controller: true domain: kyma-project.io group: operator - kind: CompassManager + kind: CompassManagerMapping path: github.com/kyma-project/compass-manager/api/v1beta1 version: v1beta1 version: "3" diff --git a/api/v1beta1/compassmanagermapping_types.go b/api/v1beta1/compassmanagermapping_types.go new file mode 100644 index 0000000..ae3496d --- /dev/null +++ b/api/v1beta1/compassmanagermapping_types.go @@ -0,0 +1,36 @@ +package v1beta1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// CompassManagerMappingSpec defines the desired state of CompassManagerMapping +type CompassManagerMappingSpec struct{} + +// CompassManagerMappingStatus defines the observed state of CompassManagerMapping +type CompassManagerMappingStatus struct{} + +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status + +// CompassManagerMapping is the Schema for the compassmanagermappings API +type CompassManagerMapping struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec CompassManagerMappingSpec `json:"spec,omitempty"` + Status CompassManagerMappingStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// CompassManagerMappingList contains a list of CompassManagerMapping +type CompassManagerMappingList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []CompassManagerMapping `json:"items"` +} + +func init() { + SchemeBuilder.Register(&CompassManagerMapping{}, &CompassManagerMappingList{}) +} diff --git a/api/v1beta1/groupversion_info.go b/api/v1beta1/groupversion_info.go new file mode 100644 index 0000000..f2c7c80 --- /dev/null +++ b/api/v1beta1/groupversion_info.go @@ -0,0 +1,20 @@ +// Package v1beta1 contains API Schema definitions for the operator v1beta1 API group +// +kubebuilder:object:generate=true +// +groupName=operator.kyma-project.io +package v1beta1 + +import ( + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/scheme" +) + +var ( + // GroupVersion is group version used to register these objects + GroupVersion = schema.GroupVersion{Group: "operator.kyma-project.io", Version: "v1beta1"} //nolint:gochecknoglobals + + // SchemeBuilder is used to add go types to the GroupVersionKind scheme + SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} //nolint:gochecknoglobals + + // AddToScheme adds the types in this group-version to the given scheme. + AddToScheme = SchemeBuilder.AddToScheme //nolint:gochecknoglobals +) diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go new file mode 100644 index 0000000..971a85c --- /dev/null +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -0,0 +1,99 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +// Code generated by controller-gen. DO NOT EDIT. + +package v1beta1 + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CompassManagerMapping) DeepCopyInto(out *CompassManagerMapping) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + out.Status = in.Status +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CompassManagerMapping. +func (in *CompassManagerMapping) DeepCopy() *CompassManagerMapping { + if in == nil { + return nil + } + out := new(CompassManagerMapping) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *CompassManagerMapping) 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 *CompassManagerMappingList) DeepCopyInto(out *CompassManagerMappingList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]CompassManagerMapping, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CompassManagerMappingList. +func (in *CompassManagerMappingList) DeepCopy() *CompassManagerMappingList { + if in == nil { + return nil + } + out := new(CompassManagerMappingList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *CompassManagerMappingList) 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 *CompassManagerMappingSpec) DeepCopyInto(out *CompassManagerMappingSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CompassManagerMappingSpec. +func (in *CompassManagerMappingSpec) DeepCopy() *CompassManagerMappingSpec { + if in == nil { + return nil + } + out := new(CompassManagerMappingSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CompassManagerMappingStatus) DeepCopyInto(out *CompassManagerMappingStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CompassManagerMappingStatus. +func (in *CompassManagerMappingStatus) DeepCopy() *CompassManagerMappingStatus { + if in == nil { + return nil + } + out := new(CompassManagerMappingStatus) + in.DeepCopyInto(out) + return out +} diff --git a/config/crd/bases/operator.kyma-project.io_compassmanagermappings.yaml b/config/crd/bases/operator.kyma-project.io_compassmanagermappings.yaml new file mode 100644 index 0000000..ca1f0b9 --- /dev/null +++ b/config/crd/bases/operator.kyma-project.io_compassmanagermappings.yaml @@ -0,0 +1,47 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.11.2 + creationTimestamp: null + name: compassmanagermappings.operator.kyma-project.io +spec: + group: operator.kyma-project.io + names: + kind: CompassManagerMapping + listKind: CompassManagerMappingList + plural: compassmanagermappings + singular: compassmanagermapping + scope: Namespaced + versions: + - name: v1beta1 + schema: + openAPIV3Schema: + description: CompassManagerMapping is the Schema for the compassmanagermappings + 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: CompassManagerMappingSpec defines the desired state of CompassManagerMapping + type: object + status: + description: CompassManagerMappingStatus defines the observed state of + CompassManagerMapping + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml new file mode 100644 index 0000000..12b4b05 --- /dev/null +++ b/config/crd/kustomization.yaml @@ -0,0 +1,21 @@ +# This kustomization.yaml is not intended to be run by itself, +# since it depends on service name and namespace that are out of this kustomize package. +# It should be run by config/default +resources: +- bases/operator.kyma-project.io_compassmanagermappings.yaml +#+kubebuilder:scaffold:crdkustomizeresource + +patchesStrategicMerge: +# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix. +# patches here are for enabling the conversion webhook for each CRD +#- patches/webhook_in_compassmanagermappings.yaml +#+kubebuilder:scaffold:crdkustomizewebhookpatch + +# [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix. +# patches here are for enabling the CA injection for each CRD +#- patches/cainjection_in_compassmanagermappings.yaml +#+kubebuilder:scaffold:crdkustomizecainjectionpatch + +# the following config is for teaching kustomize how to do kustomization for CRDs. +configurations: +- kustomizeconfig.yaml diff --git a/config/crd/kustomizeconfig.yaml b/config/crd/kustomizeconfig.yaml new file mode 100644 index 0000000..ec5c150 --- /dev/null +++ b/config/crd/kustomizeconfig.yaml @@ -0,0 +1,19 @@ +# This file is for teaching kustomize how to substitute name and namespace reference in CRD +nameReference: +- kind: Service + version: v1 + fieldSpecs: + - kind: CustomResourceDefinition + version: v1 + group: apiextensions.k8s.io + path: spec/conversion/webhook/clientConfig/service/name + +namespace: +- kind: CustomResourceDefinition + version: v1 + group: apiextensions.k8s.io + path: spec/conversion/webhook/clientConfig/service/namespace + create: false + +varReference: +- path: metadata/annotations diff --git a/config/crd/patches/cainjection_in_compassmanagermappings.yaml b/config/crd/patches/cainjection_in_compassmanagermappings.yaml new file mode 100644 index 0000000..4a04653 --- /dev/null +++ b/config/crd/patches/cainjection_in_compassmanagermappings.yaml @@ -0,0 +1,7 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + name: compassmanagermappings.operator.kyma-project.io diff --git a/config/crd/patches/webhook_in_compassmanagermappings.yaml b/config/crd/patches/webhook_in_compassmanagermappings.yaml new file mode 100644 index 0000000..a352cf8 --- /dev/null +++ b/config/crd/patches/webhook_in_compassmanagermappings.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: compassmanagermappings.operator.kyma-project.io +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/config/rbac/compassmanagermapping_editor_role.yaml b/config/rbac/compassmanagermapping_editor_role.yaml new file mode 100644 index 0000000..6e8a856 --- /dev/null +++ b/config/rbac/compassmanagermapping_editor_role.yaml @@ -0,0 +1,31 @@ +# permissions for end users to edit compassmanagermappings. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: compassmanagermapping-editor-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: compass-manager + app.kubernetes.io/part-of: compass-manager + app.kubernetes.io/managed-by: kustomize + name: compassmanagermapping-editor-role +rules: +- apiGroups: + - operator.kyma-project.io + resources: + - compassmanagermappings + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - operator.kyma-project.io + resources: + - compassmanagermappings/status + verbs: + - get diff --git a/config/rbac/compassmanagermapping_viewer_role.yaml b/config/rbac/compassmanagermapping_viewer_role.yaml new file mode 100644 index 0000000..8caac98 --- /dev/null +++ b/config/rbac/compassmanagermapping_viewer_role.yaml @@ -0,0 +1,27 @@ +# permissions for end users to view compassmanagermappings. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: compassmanagermapping-viewer-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: compass-manager + app.kubernetes.io/part-of: compass-manager + app.kubernetes.io/managed-by: kustomize + name: compassmanagermapping-viewer-role +rules: +- apiGroups: + - operator.kyma-project.io + resources: + - compassmanagermappings + verbs: + - get + - list + - watch +- apiGroups: + - operator.kyma-project.io + resources: + - compassmanagermappings/status + verbs: + - get diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index ae99517..90f4605 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -21,6 +21,15 @@ rules: - get - list - watch +- apiGroups: + - operator.kyma-project.io + resources: + - compassmanagermappings + verbs: + - create + - delete + - get + - list - apiGroups: - operator.kyma-project.io resources: @@ -28,7 +37,6 @@ rules: verbs: - get - list - - update - watch - apiGroups: - operator.kyma-project.io diff --git a/config/samples/operator_v1beta1_compassmanagermapping.yaml b/config/samples/operator_v1beta1_compassmanagermapping.yaml new file mode 100644 index 0000000..0380dd2 --- /dev/null +++ b/config/samples/operator_v1beta1_compassmanagermapping.yaml @@ -0,0 +1,12 @@ +apiVersion: operator.kyma-project.io/v1beta1 +kind: CompassManagerMapping +metadata: + labels: + app.kubernetes.io/name: compassmanagermapping + app.kubernetes.io/instance: compassmanagermapping-sample + app.kubernetes.io/part-of: compass-manager + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/created-by: compass-manager + name: compassmanagermapping-sample +spec: + # TODO(user): Add fields here diff --git a/controllers/compassmanager_controller.go b/controllers/compassmanager_controller.go index c2094d4..39db60f 100644 --- a/controllers/compassmanager_controller.go +++ b/controllers/compassmanager_controller.go @@ -4,11 +4,14 @@ import ( "context" "time" - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/labels" - + "github.com/kyma-incubator/compass/components/director/pkg/graphql" + "github.com/kyma-project/compass-manager/api/v1beta1" kyma "github.com/kyma-project/lifecycle-manager/api/v1beta2" + "github.com/pkg/errors" log "github.com/sirupsen/logrus" + corev1 "k8s.io/api/core/v1" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" ctrl "sigs.k8s.io/controller-runtime" @@ -20,37 +23,45 @@ import ( ) const ( - KymaNameLabel = "operator.kyma-project.io/kyma-name" - CompassIDLabel = "operator.kyma-project.io/compass-id" - BrokerPlanIDLabel = "kyma-project.io/broker-plan-id" - BrokerPlanNameLabel = "kyma-project.io/broker-plan-name" - GlobalAccountIDLabel = "kyma-project.io/global-account-id" - BrokerInstanceIDLabel = "kyma-project.io/instance-id" - ShootNameLabel = "kyma-project.io/shoot-name" - SubaccountIDLabel = "kyma-project.io/subaccount-id" + KymaNameLabel = "operator.kyma-project.io/kyma-name" + BrokerPlanIDLabel = "kyma-project.io/broker-plan-id" + BrokerPlanNameLabel = "kyma-project.io/broker-plan-name" + GlobalAccountIDLabel = "kyma-project.io/global-account-id" + BrokerInstanceIDLabel = "kyma-project.io/instance-id" + ShootNameLabel = "kyma-project.io/shoot-name" + SubaccountIDLabel = "kyma-project.io/subaccount-id" + ComppassIDLabel = "kyma-project.io/compass-runtime-id" + ManagedByLabel = "operator.kyma-project.io/managed-by" + ApplicationConnectorModuleName = "application-connector-module" // KubeconfigKey is the name of the key in the secret storing cluster credentials. // The secret is created by KEB: https://github.com/kyma-project/control-plane/blob/main/components/kyma-environment-broker/internal/process/steps/lifecycle_manager_kubeconfig.go KubeconfigKey = "config" ) //+kubebuilder:rbac:groups=apiextensions.k8s.io,resources=customresourcedefinitions,verbs=get;list;watch -//+kubebuilder:rbac:groups=operator.kyma-project.io,resources=kymas,verbs=get;list;watch;update +//+kubebuilder:rbac:groups=operator.kyma-project.io,resources=kymas,verbs=get;list;watch +//+kubebuilder:rbac:groups=operator.kyma-project.io,resources=compassmanagermappings,verbs=create;get;list;delete //+kubebuilder:rbac:groups=operator.kyma-project.io,resources=kymas/status,verbs=get //+kubebuilder:rbac:groups=core,resources=secrets,verbs=get;list;watch //go:generate mockery --name=Configurator type Configurator interface { - // RegisterInCompass creates Runtime in the Compass system. It must be idempotent. - RegisterInCompass(compassRuntimeLabels map[string]interface{}) (string, error) - // ConfigureCompassRuntimeAgent creates a config map in the Runtime that is used by the Compass Runtime Agent. It must be idempotent. + // ConfigureCompassRuntimeAgent creates a secret in the Runtime that is used by the Compass Runtime Agent. It must be idempotent. ConfigureCompassRuntimeAgent(kubeconfig string, runtimeID string) error - // ConfigurationForRuntimeAgentExists checks if config map used by the Compass Runtime Agent is present in the Runtime - ConfigurationForRuntimeAgentExists(kubeconfig string) (bool, error) - // UpdateCompassRuntimeAgent updates the config map in the Runtime that is used by the Compass Runtime Agent + // UpdateCompassRuntimeAgent updates the secret in the Runtime that is used by the Compass Runtime Agent UpdateCompassRuntimeAgent(kubeconfig string) error } +//go:generate mockery --name=Registrator +type Registrator interface { + // RegisterInCompass creates Runtime in the Compass system. It must be idempotent. + RegisterInCompass(compassRuntimeLabels map[string]interface{}) (string, error) + // RefreshCompassToken gets new connection token for Compass requests + RefreshCompassToken(compassID, globalAccount string) (graphql.OneTimeTokenForRuntimeExt, error) +} + type Client interface { + Create(ctx context.Context, obj client.Object, opts ...client.CreateOption) error Get(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error Update(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error List(ctx context.Context, obj client.ObjectList, opts ...client.ListOption) error @@ -62,21 +73,22 @@ type CompassManagerReconciler struct { Scheme *runtime.Scheme Log *log.Logger Configurator Configurator + Registrator Registrator + requeueTime time.Duration } -func NewCompassManagerReconciler(mgr manager.Manager, log *log.Logger, c Configurator) *CompassManagerReconciler { +func NewCompassManagerReconciler(mgr manager.Manager, log *log.Logger, c Configurator, r Registrator, requeueTime time.Duration) *CompassManagerReconciler { return &CompassManagerReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), Log: log, Configurator: c, + Registrator: r, + requeueTime: requeueTime, } } -var requeueTime = time.Minute * 5 - -func (cm *CompassManagerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - +func (cm *CompassManagerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { //nolint:revive cm.Log.Infof("Reconciliation triggered for Kyma Resource %s", req.Name) kubeconfig, err := cm.getKubeconfig(req.Name) if err != nil { @@ -85,30 +97,48 @@ func (cm *CompassManagerReconciler) Reconcile(ctx context.Context, req ctrl.Requ if kubeconfig == "" { cm.Log.Infof("Kubeconfig for Kyma resource %s not available.", req.Name) - return ctrl.Result{RequeueAfter: requeueTime}, nil + return ctrl.Result{RequeueAfter: cm.requeueTime}, nil } kymaLabels, err := cm.getKymaLabels(req.NamespacedName) if err != nil { cm.Log.Warnf("Failed to obtain labels from Kyma resource %s: %v.", req.Name, err) - return ctrl.Result{RequeueAfter: requeueTime}, err + return ctrl.Result{RequeueAfter: cm.requeueTime}, err } - compassRuntimeID, err := cm.Configurator.RegisterInCompass(createCompassRuntimeLabels(kymaLabels)) + compassRuntimeID, err := cm.getRuntimeIDFromCompassMapping(req.Name, req.Namespace) if err != nil { - cm.Log.Warnf("Failed to register Runtime for Kyma resource %s: %v.", req.Name, err) - return ctrl.Result{RequeueAfter: requeueTime}, err + return ctrl.Result{RequeueAfter: cm.requeueTime}, err + } + + if compassRuntimeID == "" { + newCompassRuntimeID, regErr := cm.Registrator.RegisterInCompass(createCompassRuntimeLabels(kymaLabels)) + if regErr != nil { + cmerr := cm.upsertCompassMappingResource("", req.Namespace, kymaLabels) + if cmerr != nil { + return ctrl.Result{RequeueAfter: cm.requeueTime}, errors.Wrap(cmerr, "failed to create Compass Manager Mapping after failed attempt to register runtime") + } + + return ctrl.Result{RequeueAfter: cm.requeueTime}, err + } + + cmerr := cm.upsertCompassMappingResource(newCompassRuntimeID, req.Namespace, kymaLabels) + if cmerr != nil { + return ctrl.Result{RequeueAfter: cm.requeueTime}, errors.Wrap(cmerr, "failed to create Compass Manager Mapping after successful attempt to register runtime") + } + + compassRuntimeID = newCompassRuntimeID + cm.Log.Infof("Runtime %s registered for Kyma resource %s.", newCompassRuntimeID, req.Name) } - cm.Log.Infof("Runtime %s registered for Kyma resource %s.", compassRuntimeID, req.Name) err = cm.Configurator.ConfigureCompassRuntimeAgent(kubeconfig, compassRuntimeID) if err != nil { cm.Log.Warnf("Failed to configure Compass Runtime Agent for Kyma resource %s: %v.", req.Name, err) - return ctrl.Result{RequeueAfter: requeueTime}, err + return ctrl.Result{RequeueAfter: cm.requeueTime}, err } cm.Log.Infof("Compass Runtime Agent for Runtime %s configured.", compassRuntimeID) - return ctrl.Result{}, cm.markRuntimeRegistered(req.NamespacedName, compassRuntimeID) + return ctrl.Result{}, nil } func (cm *CompassManagerReconciler) getKubeconfig(kymaName string) (string, error) { @@ -145,30 +175,63 @@ func (cm *CompassManagerReconciler) getKymaLabels(objKey types.NamespacedName) ( if l == nil { l = make(map[string]string) } - if err != nil { - return nil, err - } return l, nil } -func (cm *CompassManagerReconciler) markRuntimeRegistered(objKey types.NamespacedName, compassID string) error { - instance := &kyma.Kyma{} +func (cm *CompassManagerReconciler) upsertCompassMappingResource(compassRuntimeID, namespace string, kymaLabels map[string]string) error { + kymaName := kymaLabels[KymaNameLabel] + compassMapping := &v1beta1.CompassManagerMapping{} + compassMapping.Name = kymaName + compassMapping.Namespace = namespace - err := cm.Client.Get(context.Background(), objKey, instance) + compassMappingLabels := make(map[string]string) + compassMappingLabels[KymaNameLabel] = kymaLabels[KymaNameLabel] + compassMappingLabels[ComppassIDLabel] = compassRuntimeID + compassMappingLabels[GlobalAccountIDLabel] = kymaLabels[GlobalAccountIDLabel] + compassMappingLabels[SubaccountIDLabel] = kymaLabels[SubaccountIDLabel] + compassMappingLabels[ManagedByLabel] = "compass-manager" + + compassMapping.SetLabels(compassMappingLabels) + + key := types.NamespacedName{ + Name: kymaName, + Namespace: namespace, + } + + existingMapping := v1beta1.CompassManagerMapping{} + // TODOs add retry for upsert logic + err := cm.Client.Get(context.TODO(), key, &existingMapping) if err != nil { - return err + if k8serrors.IsNotFound(err) { + return cm.Client.Create(context.Background(), compassMapping) + } } - l := instance.GetLabels() - if l == nil { - l = make(map[string]string) + existingMapping.SetLabels(compassMappingLabels) + return cm.Client.Update(context.TODO(), &existingMapping) +} + +func (cm *CompassManagerReconciler) getRuntimeIDFromCompassMapping(kymaName string, namespace string) (string, error) { + mappingList := &v1beta1.CompassManagerMappingList{} + labelSelector := labels.SelectorFromSet(map[string]string{ + KymaNameLabel: kymaName, + }) + + err := cm.Client.List(context.Background(), mappingList, &client.ListOptions{ + LabelSelector: labelSelector, + Namespace: namespace, + }) + + if err != nil { + return "", err } - l[CompassIDLabel] = compassID + if len(mappingList.Items) == 0 { + return "", nil + } - instance.SetLabels(l) - return cm.Client.Update(context.Background(), instance) + return mappingList.Items[0].GetLabels()[ComppassIDLabel], nil } // SetupWithManager sets up the controller with the Manager. @@ -203,22 +266,26 @@ func (cm *CompassManagerReconciler) SetupWithManager(mgr ctrl.Manager) error { } func (cm *CompassManagerReconciler) needsToBeReconciled(obj runtime.Object) bool { - kymaObj, ok := obj.(*kyma.Kyma) - if !ok { cm.Log.Error("Unexpected type detected. Object type is supposed to be of Kyma type.") return false } - _, labelFound := kymaObj.GetLabels()[CompassIDLabel] - return !labelFound + kymaModules := kymaObj.Spec.Modules + + for _, v := range kymaModules { + // Placeholder for App Conn module name, change if the name will be already known + if v.Name == ApplicationConnectorModuleName { + return true + } + } + + return false } func createCompassRuntimeLabels(kymaLabels map[string]string) map[string]interface{} { - runtimeLabels := make(map[string]interface{}) - runtimeLabels["director_connection_managed_by"] = "compass-manager" runtimeLabels["broker_instance_id"] = kymaLabels[BrokerInstanceIDLabel] runtimeLabels["gardenerClusterName"] = kymaLabels[ShootNameLabel] diff --git a/controllers/compassmanager_controller_test.go b/controllers/compassmanager_controller_test.go index 8dde0e1..5e571ce 100644 --- a/controllers/compassmanager_controller_test.go +++ b/controllers/compassmanager_controller_test.go @@ -4,10 +4,12 @@ import ( "context" "time" + "github.com/kyma-project/compass-manager/api/v1beta1" kyma "github.com/kyma-project/lifecycle-manager/api/v1beta2" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" ) @@ -32,11 +34,11 @@ var _ = Describe("Compass Manager controller", func() { Expect(k8sClient.Create(context.Background(), &secret)).To(Succeed()) By("Create Kyma Resource") - kyma := createKymaResource(kymaName) - Expect(k8sClient.Create(context.Background(), &kyma)).To(Succeed()) + kymaCR := createKymaResource(kymaName) + Expect(k8sClient.Create(context.Background(), &kymaCR)).To(Succeed()) Eventually(func() bool { - label, err := getKymaLabel(kyma.Name, "operator.kyma-project.io/compass-id", kymaCustomResourceNamespace) + label, err := getCompassMappingLabel(kymaCR.Name, ComppassIDLabel, kymaCustomResourceNamespace) return err == nil && label != "" }, clientTimeout, clientInterval).Should(BeTrue()) @@ -51,26 +53,61 @@ var _ = Describe("Compass Manager controller", func() { It("requeue the request if and succeeded when user add the secret", func() { By("Create Kyma Resource") - kyma := createKymaResource("empty-kubeconfig") - Expect(k8sClient.Create(context.Background(), &kyma)).To(Succeed()) + kymaCR := createKymaResource("empty-kubeconfig") + Expect(k8sClient.Create(context.Background(), &kymaCR)).To(Succeed()) Consistently(func() bool { - label, err := getKymaLabel(kyma.Name, "operator.kyma-project.io/compass-id", kymaCustomResourceNamespace) + label, err := getCompassMappingLabel(kymaCR.Name, ComppassIDLabel, kymaCustomResourceNamespace) - return err == nil && label == "" + return errors.IsNotFound(err) && label == "" }, clientTimeout, clientInterval).Should(BeTrue()) By("Create secret with credentials") - secret := createCredentialsSecret(kyma.Name, kymaCustomResourceNamespace) + secret := createCredentialsSecret(kymaCR.Name, kymaCustomResourceNamespace) Expect(k8sClient.Create(context.Background(), &secret)).To(Succeed()) Eventually(func() bool { - label, err := getKymaLabel(kyma.Name, "operator.kyma-project.io/compass-id", kymaCustomResourceNamespace) + label, err := getCompassMappingLabel(kymaCR.Name, ComppassIDLabel, kymaCustomResourceNamespace) return err == nil && label != "" }, clientTimeout, clientInterval).Should(BeTrue()) }) }) + + // Feature (refreshing token) is implemented but according to our discussions, it will be a part of another PR + + // Context("After successful runtime registration when user re-enable Application Connector module", func() { + // DescribeTable("the one-time token for Compass Runtime Agent should be refreshed", func(kymaName string) { + // By("Create secret with credentials") + // secret := createCredentialsSecret(kymaName, kymaCustomResourceNamespace) + // Expect(k8sClient.Create(context.Background(), &secret)).To(Succeed()) + // + // By("Create Kyma Resource") + // kymaCR := createKymaResource(kymaName) + // Expect(k8sClient.Create(context.Background(), &kymaCR)).To(Succeed()) + // + // Eventually(func() bool { + // label, err := getCompassMappingLabel(kymaCR.Name, ComppassIDLabel, kymaCustomResourceNamespace) + // + // return err == nil && label != "" + // }, clientTimeout, clientInterval).Should(BeTrue()) + // + // By("Disable the Application Connector module") + // modifiedKyma, err := modifyKymaModules(kymaCR.Name, kymaCustomResourceNamespace, nil) + // Expect(err).NotTo(HaveOccurred()) + // Expect(k8sClient.Update(context.Background(), modifiedKyma)).To(Succeed()) + // + // By("Re-enable the Application Connector module") + // kymaModules := make([]kyma.Module, 2) + // kymaModules[0].Name = ApplicationConnectorModuleName + // kymaModules[1].Name = "test-module" + // modifiedKyma, err = modifyKymaModules(kymaCR.Name, kymaCustomResourceNamespace, kymaModules) + // Expect(err).NotTo(HaveOccurred()) + // Expect(k8sClient.Update(context.Background(), modifiedKyma)).To(Succeed()) + // }, + // Entry("Token successfully refreshed", "refresh-token"), + // ) + // }) }) func createNamespace(name string) error { @@ -86,6 +123,10 @@ func createKymaResource(name string) kyma.Kyma { kymaCustomResourceLabels := make(map[string]string) kymaCustomResourceLabels[GlobalAccountIDLabel] = "globalAccount" kymaCustomResourceLabels[ShootNameLabel] = name + kymaCustomResourceLabels[KymaNameLabel] = name + + kymaModules := make([]kyma.Module, 1) + kymaModules[0].Name = ApplicationConnectorModuleName return kyma.Kyma{ TypeMeta: metav1.TypeMeta{ @@ -99,6 +140,7 @@ func createKymaResource(name string) kyma.Kyma { }, Spec: kyma.KymaSpec{ Channel: "regular", + Modules: kymaModules, }, } } @@ -118,8 +160,8 @@ func createCredentialsSecret(kymaName, namespace string) corev1.Secret { } } -func getKymaLabel(kymaName, labelName, namespace string) (string, error) { - var obj kyma.Kyma +func getCompassMappingLabel(kymaName, labelName, namespace string) (string, error) { + var obj v1beta1.CompassManagerMapping key := types.NamespacedName{Name: kymaName, Namespace: namespace} err := cm.Client.Get(context.Background(), key, &obj) @@ -130,3 +172,19 @@ func getKymaLabel(kymaName, labelName, namespace string) (string, error) { labels := obj.GetLabels() return labels[labelName], nil } + +// Feature (refreshing token) is implemented but according to our discussions, it will be a part of another PR + +// func modifyKymaModules(kymaName, kymaNamespace string, kymaModules []kyma.Module) (*kyma.Kyma, error) { +// var obj kyma.Kyma +// key := types.NamespacedName{Name: kymaName, Namespace: kymaNamespace} +// +// err := cm.Client.Get(context.Background(), key, &obj) +// if err != nil { +// return &kyma.Kyma{}, err +// } +// +// obj.Spec.Modules = kymaModules +// +// return &obj, nil +// } diff --git a/controllers/configurator.go b/controllers/configurator.go new file mode 100644 index 0000000..d4b6786 --- /dev/null +++ b/controllers/configurator.go @@ -0,0 +1,19 @@ +package controllers + +import "github.com/sirupsen/logrus" + +type RuntimeAgentConfigurator struct { + Log *logrus.Logger +} + +func NewRuntimeAgentConfigurator(log *logrus.Logger) *RuntimeAgentConfigurator { + return &RuntimeAgentConfigurator{Log: log} +} + +func (r *RuntimeAgentConfigurator) ConfigureCompassRuntimeAgent(kubeconfig string, runtimeID string) error { //nolint:revive + return nil +} + +func (r *RuntimeAgentConfigurator) UpdateCompassRuntimeAgent(kubeconfig string) error { //nolint:revive + return nil +} diff --git a/controllers/mocks/Configurator.go b/controllers/mocks/Configurator.go index 0694085..a3f4145 100644 --- a/controllers/mocks/Configurator.go +++ b/controllers/mocks/Configurator.go @@ -9,30 +9,6 @@ type Configurator struct { mock.Mock } -// ConfigurationForRuntimeAgentExists provides a mock function with given fields: kubeconfig -func (_m *Configurator) ConfigurationForRuntimeAgentExists(kubeconfig string) (bool, error) { - ret := _m.Called(kubeconfig) - - var r0 bool - var r1 error - if rf, ok := ret.Get(0).(func(string) (bool, error)); ok { - return rf(kubeconfig) - } - if rf, ok := ret.Get(0).(func(string) bool); ok { - r0 = rf(kubeconfig) - } else { - r0 = ret.Get(0).(bool) - } - - if rf, ok := ret.Get(1).(func(string) error); ok { - r1 = rf(kubeconfig) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - // ConfigureCompassRuntimeAgent provides a mock function with given fields: kubeconfig, runtimeID func (_m *Configurator) ConfigureCompassRuntimeAgent(kubeconfig string, runtimeID string) error { ret := _m.Called(kubeconfig, runtimeID) @@ -47,30 +23,6 @@ func (_m *Configurator) ConfigureCompassRuntimeAgent(kubeconfig string, runtimeI return r0 } -// RegisterInCompass provides a mock function with given fields: compassRuntimeLabels -func (_m *Configurator) RegisterInCompass(compassRuntimeLabels map[string]interface{}) (string, error) { - ret := _m.Called(compassRuntimeLabels) - - var r0 string - var r1 error - if rf, ok := ret.Get(0).(func(map[string]interface{}) (string, error)); ok { - return rf(compassRuntimeLabels) - } - if rf, ok := ret.Get(0).(func(map[string]interface{}) string); ok { - r0 = rf(compassRuntimeLabels) - } else { - r0 = ret.Get(0).(string) - } - - if rf, ok := ret.Get(1).(func(map[string]interface{}) error); ok { - r1 = rf(compassRuntimeLabels) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - // UpdateCompassRuntimeAgent provides a mock function with given fields: kubeconfig func (_m *Configurator) UpdateCompassRuntimeAgent(kubeconfig string) error { ret := _m.Called(kubeconfig) diff --git a/controllers/mocks/Registrator.go b/controllers/mocks/Registrator.go new file mode 100644 index 0000000..6340353 --- /dev/null +++ b/controllers/mocks/Registrator.go @@ -0,0 +1,76 @@ +// Code generated by mockery v2.22.1. DO NOT EDIT. + +package mocks + +import ( + graphql "github.com/kyma-incubator/compass/components/director/pkg/graphql" + mock "github.com/stretchr/testify/mock" +) + +// Registrator is an autogenerated mock type for the Registrator type +type Registrator struct { + mock.Mock +} + +// RefreshCompassToken provides a mock function with given fields: compassID, globalAccount +func (_m *Registrator) RefreshCompassToken(compassID string, globalAccount string) (graphql.OneTimeTokenForRuntimeExt, error) { + ret := _m.Called(compassID, globalAccount) + + var r0 graphql.OneTimeTokenForRuntimeExt + var r1 error + if rf, ok := ret.Get(0).(func(string, string) (graphql.OneTimeTokenForRuntimeExt, error)); ok { + return rf(compassID, globalAccount) + } + if rf, ok := ret.Get(0).(func(string, string) graphql.OneTimeTokenForRuntimeExt); ok { + r0 = rf(compassID, globalAccount) + } else { + r0 = ret.Get(0).(graphql.OneTimeTokenForRuntimeExt) + } + + if rf, ok := ret.Get(1).(func(string, string) error); ok { + r1 = rf(compassID, globalAccount) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RegisterInCompass provides a mock function with given fields: compassRuntimeLabels +func (_m *Registrator) RegisterInCompass(compassRuntimeLabels map[string]interface{}) (string, error) { + ret := _m.Called(compassRuntimeLabels) + + var r0 string + var r1 error + if rf, ok := ret.Get(0).(func(map[string]interface{}) (string, error)); ok { + return rf(compassRuntimeLabels) + } + if rf, ok := ret.Get(0).(func(map[string]interface{}) string); ok { + r0 = rf(compassRuntimeLabels) + } else { + r0 = ret.Get(0).(string) + } + + if rf, ok := ret.Get(1).(func(map[string]interface{}) error); ok { + r1 = rf(compassRuntimeLabels) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +type mockConstructorTestingTNewRegistrator interface { + mock.TestingT + Cleanup(func()) +} + +// NewRegistrator creates a new instance of Registrator. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewRegistrator(t mockConstructorTestingTNewRegistrator) *Registrator { + mock := &Registrator{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/controllers/registrator.go b/controllers/registrator.go index f6356cf..dc81efa 100644 --- a/controllers/registrator.go +++ b/controllers/registrator.go @@ -1,16 +1,22 @@ package controllers import ( + "math/rand" + "time" + + "github.com/kyma-incubator/compass/components/director/pkg/graphql" "github.com/kyma-project/compass-manager/internal/apperrors" "github.com/kyma-project/compass-manager/internal/director" "github.com/kyma-project/compass-manager/internal/util" "github.com/kyma-project/compass-manager/pkg/gqlschema" "github.com/sirupsen/logrus" - "math/rand" - "time" ) -const nameIDLen = 4 +const ( + nameIDLen = 4 + retryTime = 5 + attempts = 3 +) type CompassRegistrator struct { Client director.Client @@ -24,28 +30,14 @@ func NewCompassRegistator(directorClient director.Client, log *logrus.Logger) *C } } -func (r *CompassRegistrator) ConfigureCompassRuntimeAgent(kubeconfig string, runtimeID string) error { - return nil -} - -func (r *CompassRegistrator) ConfigurationForRuntimeAgentExists(kubeconfig string) (bool, error) { - return true, nil -} - -func (r *CompassRegistrator) UpdateCompassRuntimeAgent(kubeconfig string) error { - return nil -} - func (r *CompassRegistrator) RegisterInCompass(compassRuntimeLabels map[string]interface{}) (string, error) { - var runtimeID string - r.Log.Infof("Compass-Runtime-Labels: %s", compassRuntimeLabels) runtimeInput, err := createRuntimeInput(compassRuntimeLabels) if err != nil { return "", err } - err = util.RetryOnError(5*time.Second, 3, "Error while registering runtime in Director: %s", func() (err apperrors.AppError) { + err = util.RetryOnError(retryTime*time.Second, attempts, "Error while registering runtime in Director: %s", func() (err apperrors.AppError) { runtimeID, err = r.Client.CreateRuntime(runtimeInput, compassRuntimeLabels["global_account_id"].(string)) return }) @@ -57,8 +49,21 @@ func (r *CompassRegistrator) RegisterInCompass(compassRuntimeLabels map[string]i return runtimeID, nil } -func createRuntimeInput(compassRuntimeLabels map[string]interface{}) (*gqlschema.RuntimeInput, error) { +func (r *CompassRegistrator) RefreshCompassToken(compassID, globalAccount string) (graphql.OneTimeTokenForRuntimeExt, error) { + var token graphql.OneTimeTokenForRuntimeExt + err := util.RetryOnError(retryTime*time.Second, attempts, "Error while refreshing OneTime token in Director: %s", func() (err apperrors.AppError) { + token, err = r.Client.GetConnectionToken(compassID, globalAccount) + return + }) + if err != nil { + return graphql.OneTimeTokenForRuntimeExt{}, err + } + + return token, nil +} + +func createRuntimeInput(compassRuntimeLabels map[string]interface{}) (*gqlschema.RuntimeInput, error) { runtimeInput := &gqlschema.RuntimeInput{} runtimeInput.Name = compassRuntimeLabels["gardenerClusterName"].(string) + "-" + generateRandomText(nameIDLen) diff --git a/controllers/suite_test.go b/controllers/suite_test.go index 1242f13..6f025b7 100644 --- a/controllers/suite_test.go +++ b/controllers/suite_test.go @@ -7,34 +7,33 @@ import ( "testing" "time" + "github.com/kyma-project/compass-manager/api/v1beta1" "github.com/kyma-project/compass-manager/controllers/mocks" - kyma "github.com/kyma-project/lifecycle-manager/api/v1beta2" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/sirupsen/logrus" - ctrl "sigs.k8s.io/controller-runtime" - "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" + ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/envtest" logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/log/zap" - //+kubebuilder:scaffold:imports ) // These tests use Ginkgo (BDD-style Go testing framework). Refer to // http://onsi.github.io/ginkgo/ to learn more about Ginkgo. var ( - cfg *rest.Config - k8sClient client.Client - testEnv *envtest.Environment - cm *CompassManagerReconciler - mockConfigurator *mocks.Configurator - suiteCtx context.Context - cancelSuiteCtx context.CancelFunc + cfg *rest.Config //nolint:gochecknoglobals + k8sClient client.Client //nolint:gochecknoglobals + testEnv *envtest.Environment //nolint:gochecknoglobals + cm *CompassManagerReconciler //nolint:gochecknoglobals + mockConfigurator *mocks.Configurator //nolint:gochecknoglobals + mockRegistrator *mocks.Registrator //nolint:gochecknoglobals + suiteCtx context.Context //nolint:gochecknoglobals + cancelSuiteCtx context.CancelFunc //nolint:gochecknoglobals ) func TestAPIs(t *testing.T) { @@ -47,12 +46,12 @@ var _ = BeforeSuite(func() { By("bootstrapping test environment") testEnv = &envtest.Environment{ - CRDDirectoryPaths: []string{filepath.Join("..", "hack", "crd")}, + CRDDirectoryPaths: []string{filepath.Join("..", "hack", "crd"), filepath.Join("..", "config", "crd", "bases")}, ErrorIfCRDPathMissing: true, } var err error - requeueTime = time.Second * 10 + // cfg is defined in this file globally. cfg, err = testEnv.Start() Expect(err).NotTo(HaveOccurred()) @@ -60,6 +59,8 @@ var _ = BeforeSuite(func() { err = kyma.AddToScheme(scheme.Scheme) Expect(err).NotTo(HaveOccurred()) + err = v1beta1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) //+kubebuilder:scaffold:scheme @@ -70,9 +71,12 @@ var _ = BeforeSuite(func() { log.SetLevel(logrus.InfoLevel) mockConfigurator = &mocks.Configurator{} - prepareMockFunctions(mockConfigurator) + mockRegistrator = &mocks.Registrator{} + prepareMockFunctions(mockConfigurator, mockRegistrator) + + requeueTime := time.Second * 10 - cm = NewCompassManagerReconciler(k8sManager, log, mockConfigurator) + cm = NewCompassManagerReconciler(k8sManager, log, mockConfigurator, mockRegistrator, requeueTime) k8sClient = k8sManager.GetClient() err = cm.SetupWithManager(k8sManager) Expect(err).ToNot(HaveOccurred()) @@ -110,26 +114,38 @@ var _ = AfterSuite(func() { Expect(err).NotTo(HaveOccurred()) }) -func prepareMockFunctions(r *mocks.Configurator) { - +func prepareMockFunctions(c *mocks.Configurator, r *mocks.Registrator) { compassLabelsAllGood := createCompassRuntimeLabels(map[string]string{ShootNameLabel: "all-good", GlobalAccountIDLabel: "globalAccount"}) compassLabelsConfigureFails := createCompassRuntimeLabels(map[string]string{ShootNameLabel: "configure-fails", GlobalAccountIDLabel: "globalAccount"}) compassLabelsRegistrationFails := createCompassRuntimeLabels(map[string]string{ShootNameLabel: "registration-fails", GlobalAccountIDLabel: "globalAccount"}) compassLabelsEmptyKubeconfig := createCompassRuntimeLabels(map[string]string{ShootNameLabel: "empty-kubeconfig", GlobalAccountIDLabel: "globalAccount"}) + // Feature (refreshing token) is implemented but according to our discussions, it will be a part of another PR + // compassLabelsRefreshToken := createCompassRuntimeLabels(map[string]string{ShootNameLabel: "refresh-token", GlobalAccountIDLabel: "globalAccount"}) + // refreshedToken := graphql.OneTimeTokenForRuntimeExt{ + // OneTimeTokenForRuntime: graphql.OneTimeTokenForRuntime{}, + // Raw: "rawToken", + // RawEncoded: "rawEncodedToken", + //} + r.On("RegisterInCompass", compassLabelsAllGood).Return("id-all-good", nil) - r.On("ConfigureCompassRuntimeAgent", "kubeconfig-data-all-good", "id-all-good").Return(nil) + c.On("ConfigureCompassRuntimeAgent", "kubeconfig-data-all-good", "id-all-good").Return(nil) // The first call to ConfigureRuntimeAgent fails, but the second is successful r.On("RegisterInCompass", compassLabelsConfigureFails).Return("id-configure-fails", nil) - r.On("ConfigureCompassRuntimeAgent", "kubeconfig-data-configure-fails", "id-configure-fails").Return(errors.New("error during configuration of Compass Runtime Agent CR")).Once() - r.On("ConfigureCompassRuntimeAgent", "kubeconfig-data-configure-fails", "id-configure-fails").Return(nil).Once() + c.On("ConfigureCompassRuntimeAgent", "kubeconfig-data-configure-fails", "id-configure-fails").Return(errors.New("error during configuration of Compass Runtime Agent CR")).Once() + c.On("ConfigureCompassRuntimeAgent", "kubeconfig-data-configure-fails", "id-configure-fails").Return(nil).Once() // The first call to RegisterInCompass fails, but the second is successful. r.On("RegisterInCompass", compassLabelsRegistrationFails).Return("", errors.New("error during registration")).Once() r.On("RegisterInCompass", compassLabelsRegistrationFails).Return("registration-fails", nil).Once() - r.On("ConfigureCompassRuntimeAgent", "kubeconfig-data-registration-fails", "registration-fails").Return(nil) + c.On("ConfigureCompassRuntimeAgent", "kubeconfig-data-registration-fails", "registration-fails").Return(nil) r.On("RegisterInCompass", compassLabelsEmptyKubeconfig).Return("id-empty-kubeconfig", nil) - r.On("ConfigureCompassRuntimeAgent", "kubeconfig-data-empty-kubeconfig", "id-empty-kubeconfig").Return(nil) + c.On("ConfigureCompassRuntimeAgent", "kubeconfig-data-empty-kubeconfig", "id-empty-kubeconfig").Return(nil) + + // Feature (refreshing token) is implemented but according to our discussions, it will be a part of another PR + // r.On("RegisterInCompass", compassLabelsRefreshToken).Return("id-refresh-token", nil).Once() + // c.On("ConfigureCompassRuntimeAgent", "kubeconfig-data-refresh-token", "id-refresh-token").Return(nil).Once() + // r.On("RefreshCompassToken", "id-refresh-token", "globalAccount").Return(refreshedToken, nil).Once() } diff --git a/internal/apperrors/apperrors.go b/internal/apperrors/apperrors.go index 4e6a7d5..5c437c4 100644 --- a/internal/apperrors/apperrors.go +++ b/internal/apperrors/apperrors.go @@ -35,8 +35,8 @@ const ( ) const ( - Unknown CauseCode = 10 - TenantNotFound CauseCode = 11 + Unknown CauseCode = 10 + GlobalAccountNotFound CauseCode = 11 ) type AppError interface { @@ -83,8 +83,8 @@ func BadRequest(format string, a ...interface{}) AppError { return errorf(CodeBadRequest, Unknown, format, a...) } -func InvalidTenant(format string, a ...interface{}) AppError { - return errorf(CodeBadRequest, TenantNotFound, format, a...) +func InvalidGlobalAccount(format string, a ...interface{}) AppError { + return errorf(CodeBadRequest, GlobalAccountNotFound, format, a...) } func (ae appError) Append(additionalFormat string, a ...interface{}) AppError { diff --git a/internal/apperrors/apperrors_test.go b/internal/apperrors/apperrors_test.go index b66a925..5b06318 100644 --- a/internal/apperrors/apperrors_test.go +++ b/internal/apperrors/apperrors_test.go @@ -7,7 +7,6 @@ import ( ) func TestAppError(t *testing.T) { - t.Run("should create error with proper code", func(t *testing.T) { assert.Equal(t, CodeInternal, Internal("error").Code()) assert.Equal(t, CodeForbidden, Forbidden("error").Code()) @@ -24,38 +23,37 @@ func TestAppError(t *testing.T) { assert.Equal(t, "code: 1, error: bug", Internal("code: %d, error: %s", 1, "bug").Error()) assert.Equal(t, "code: 1, error: bug", Forbidden("code: %d, error: %s", 1, "bug").Error()) assert.Equal(t, "code: 1, error: bug", BadRequest("code: %d, error: %s", 1, "bug").Error()) - }) t.Run("should append apperrors without changing error code", func(t *testing.T) { - //given + // given createdInternalErr := Internal("Some Internal apperror, %s", "Some pkg err") createdForbiddenErr := Forbidden("Some Forbidden apperror, %s", "Some pkg err") createdBadRequestErr := BadRequest("Some BadRequest apperror, %s", "Some pkg err") - //when + // when appendedInternalErr := createdInternalErr.Append("Some additional message") appendedForbiddenErr := createdForbiddenErr.Append("Some additional message") appendedBadRequestErr := createdBadRequestErr.Append("Some additional message") - //then + // then assert.Equal(t, CodeInternal, appendedInternalErr.Code()) assert.Equal(t, CodeForbidden, appendedForbiddenErr.Code()) assert.Equal(t, CodeBadRequest, appendedBadRequestErr.Code()) }) t.Run("should append apperrors and chain messages correctly", func(t *testing.T) { - //given + // given createdInternalErr := Internal("Some Internal apperror, %s", "Some pkg err") createdForbiddenErr := Forbidden("Some Forbidden apperror, %s", "Some pkg err") createdBadRequestErr := BadRequest("Some BadRequest apperror, %s", "Some pkg err") - //when + // when appendedInternalErr := createdInternalErr.Append("Some additional message: %s", "error") appendedForbiddenErr := createdForbiddenErr.Append("Some additional message: %s", "error") appendedBadRequestErr := createdBadRequestErr.Append("Some additional message: %s", "error") - //then + // then assert.Equal(t, "Some additional message: error, Some Internal apperror, Some pkg err", appendedInternalErr.Error()) assert.Equal(t, "Some additional message: error, Some Forbidden apperror, Some pkg err", appendedForbiddenErr.Error()) assert.Equal(t, "Some additional message: error, Some BadRequest apperror, Some pkg err", appendedBadRequestErr.Error()) diff --git a/internal/apperrors/presenter.go b/internal/apperrors/presenter.go index ca772c0..694946f 100644 --- a/internal/apperrors/presenter.go +++ b/internal/apperrors/presenter.go @@ -14,7 +14,7 @@ type presenter struct { Logger *log.Logger } -func NewPresenter(logger *log.Logger) *presenter { +func NewPresenter(logger *log.Logger) *presenter { //nolint:revive return &presenter{Logger: logger} } diff --git a/internal/apperrors/presenter_test.go b/internal/apperrors/presenter_test.go index e359c72..0e2297e 100644 --- a/internal/apperrors/presenter_test.go +++ b/internal/apperrors/presenter_test.go @@ -7,24 +7,22 @@ import ( "testing" "github.com/kyma-project/compass-manager/internal/apperrors" - "github.com/stretchr/testify/require" - "github.com/sirupsen/logrus/hooks/test" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestPresenter_ErrorPresenter(t *testing.T) { - //given + // given errMsg := "testErr" log, hook := test.NewNullLogger() presenter := apperrors.NewPresenter(log) t.Run("Unknown error", func(t *testing.T) { - //when + // when err := presenter.Do(context.TODO(), errors.New(errMsg)) - //then + // then entry := hook.LastEntry() require.NotNil(t, entry) assert.Equal(t, fmt.Sprintf("Unknown error: %s\n", errMsg), entry.Message) @@ -35,13 +33,13 @@ func TestPresenter_ErrorPresenter(t *testing.T) { }) t.Run("Internal Error", func(t *testing.T) { - //given + // given customErr := apperrors.Internal(errMsg) - //when + // when err := presenter.Do(context.TODO(), customErr) - //then + // then entry := hook.LastEntry() require.NotNil(t, entry) assert.Equal(t, fmt.Sprintf("Internal Server Error: %s", errMsg), entry.Message) diff --git a/internal/director/directorclient.go b/internal/director/directorclient.go index ee60de2..21ea054 100644 --- a/internal/director/directorclient.go +++ b/internal/director/directorclient.go @@ -1,18 +1,18 @@ package director import ( + "errors" "fmt" - "github.com/kyma-project/compass-manager/pkg/gqlschema" directorApperrors "github.com/kyma-incubator/compass/components/director/pkg/apperrors" "github.com/kyma-incubator/compass/components/director/pkg/graphql" "github.com/kyma-incubator/compass/components/director/pkg/graphql/graphqlizer" - log "github.com/sirupsen/logrus" - "github.com/kyma-project/compass-manager/internal/apperrors" gql "github.com/kyma-project/compass-manager/internal/graphql" "github.com/kyma-project/compass-manager/internal/oauth" + "github.com/kyma-project/compass-manager/pkg/gqlschema" gcli "github.com/kyma-project/compass-manager/third_party/machinebox/graphql" + log "github.com/sirupsen/logrus" ) const ( @@ -22,13 +22,13 @@ const ( //go:generate mockery --name=Client type Client interface { - CreateRuntime(config *gqlschema.RuntimeInput, tenant string) (string, apperrors.AppError) - GetRuntime(id, tenant string) (graphql.RuntimeExt, apperrors.AppError) - GetConnectionToken(id, tenant string) (graphql.OneTimeTokenForRuntimeExt, apperrors.AppError) - //RuntimeExists(gardenerClusterName, tenant string) (bool, apperrors.AppError) - //UpdateRuntime(id string, config *graphql.RuntimeUpdateInput, tenant string) apperrors.AppError - //DeleteRuntime(id, tenant string) apperrors.AppError - //SetRuntimeStatusCondition(id string, statusCondition graphql.RuntimeStatusCondition, tenant string) apperrors.AppError + CreateRuntime(config *gqlschema.RuntimeInput, globalAccount string) (string, apperrors.AppError) + GetRuntime(compassID, globalAccount string) (graphql.RuntimeExt, apperrors.AppError) + GetConnectionToken(compassID, globalAccount string) (graphql.OneTimeTokenForRuntimeExt, apperrors.AppError) + // RuntimeExists(gardenerClusterName, tenant string) (bool, apperrors.AppError) + // UpdateRuntime(id string, config *graphql.RuntimeUpdateInput, tenant string) apperrors.AppError + // DeleteRuntime(id, tenant string) apperrors.AppError + // SetRuntimeStatusCondition(id string, statusCondition graphql.RuntimeStatusCondition, tenant string) apperrors.AppError } type directorClient struct { @@ -49,7 +49,7 @@ func NewDirectorClient(gqlClient gql.Client, oauthClient oauth.Client) Client { } } -func (cc *directorClient) CreateRuntime(config *gqlschema.RuntimeInput, tenant string) (string, apperrors.AppError) { +func (cc *directorClient) CreateRuntime(config *gqlschema.RuntimeInput, globalAccount string) (string, apperrors.AppError) { log.Infof("Registering Runtime on Director service") if config == nil { @@ -77,7 +77,7 @@ func (cc *directorClient) CreateRuntime(config *gqlschema.RuntimeInput, tenant s runtimeQuery := cc.queryProvider.createRuntimeMutation(runtimeInput) var response CreateRuntimeResponse - appErr := cc.executeDirectorGraphQLCall(runtimeQuery, tenant, &response) + appErr := cc.executeDirectorGraphQLCall(runtimeQuery, globalAccount, &response) if appErr != nil { return "", appErr.Append("Failed to register runtime in Director. Request failed") } @@ -87,46 +87,46 @@ func (cc *directorClient) CreateRuntime(config *gqlschema.RuntimeInput, tenant s return "", apperrors.Internal("Failed to register runtime in Director: Received nil response.").SetComponent(apperrors.ErrCompassDirector).SetReason(apperrors.ErrDirectorNilResponse) } - log.Infof("Successfully registered Runtime %s in Director for tenant %s", config.Name, tenant) + log.Infof("Successfully registered Runtime %s in Director for Global Account %s", config.Name, globalAccount) return response.Result.ID, nil } -func (cc *directorClient) GetRuntime(id, tenant string) (graphql.RuntimeExt, apperrors.AppError) { +func (cc *directorClient) GetRuntime(compassID, globalAccount string) (graphql.RuntimeExt, apperrors.AppError) { log.Infof("Getting Runtime from Director service") - runtimeQuery := cc.queryProvider.getRuntimeQuery(id) + runtimeQuery := cc.queryProvider.getRuntimeQuery(compassID) var response GetRuntimeResponse - err := cc.executeDirectorGraphQLCall(runtimeQuery, tenant, &response) + err := cc.executeDirectorGraphQLCall(runtimeQuery, globalAccount, &response) if err != nil { - return graphql.RuntimeExt{}, err.Append("Failed to get runtime %s from Director", id) + return graphql.RuntimeExt{}, err.Append("Failed to get runtime %s from Director", compassID) } if response.Result == nil { - return graphql.RuntimeExt{}, apperrors.Internal("Failed to get runtime %s from Director: received nil response.", id).SetComponent(apperrors.ErrCompassDirector).SetReason(apperrors.ErrDirectorNilResponse) + return graphql.RuntimeExt{}, apperrors.Internal("Failed to get runtime %s from Director: received nil response.", compassID).SetComponent(apperrors.ErrCompassDirector).SetReason(apperrors.ErrDirectorNilResponse) } - if response.Result.ID != id { - return graphql.RuntimeExt{}, apperrors.Internal("Failed to get runtime %s from Director: received unexpected RuntimeID", id).SetComponent(apperrors.ErrCompassDirector).SetReason(apperrors.ErrDirectorRuntimeIDMismatch) + if response.Result.ID != compassID { + return graphql.RuntimeExt{}, apperrors.Internal("Failed to get runtime %s from Director: received unexpected RuntimeID", compassID).SetComponent(apperrors.ErrCompassDirector).SetReason(apperrors.ErrDirectorRuntimeIDMismatch) } - log.Infof("Successfully got Runtime %s from Director for tenant %s", id, tenant) + log.Infof("Successfully got Runtime %s from Director for Global Account %s", compassID, globalAccount) return *response.Result, nil } -func (cc *directorClient) GetConnectionToken(id, tenant string) (graphql.OneTimeTokenForRuntimeExt, apperrors.AppError) { - runtimeQuery := cc.queryProvider.requestOneTimeTokenMutation(id) +func (cc *directorClient) GetConnectionToken(compassID, globalAccount string) (graphql.OneTimeTokenForRuntimeExt, apperrors.AppError) { + runtimeQuery := cc.queryProvider.requestOneTimeTokenMutation(compassID) var response OneTimeTokenResponse - err := cc.executeDirectorGraphQLCall(runtimeQuery, tenant, &response) + err := cc.executeDirectorGraphQLCall(runtimeQuery, globalAccount, &response) if err != nil { - return graphql.OneTimeTokenForRuntimeExt{}, err.Append("Failed to get OneTimeToken for Runtime %s in Director", id) + return graphql.OneTimeTokenForRuntimeExt{}, err.Append("Failed to get OneTimeToken for Runtime %s in Director", compassID) } if response.Result == nil { - return graphql.OneTimeTokenForRuntimeExt{}, apperrors.Internal("Failed to get OneTimeToken for Runtime %s in Director: received nil response.", id).SetComponent(apperrors.ErrCompassDirector).SetReason(apperrors.ErrDirectorNilResponse) + return graphql.OneTimeTokenForRuntimeExt{}, apperrors.Internal("Failed to get OneTimeToken for Runtime %s in Director: received nil response.", compassID).SetComponent(apperrors.ErrCompassDirector).SetReason(apperrors.ErrDirectorNilResponse) } - log.Infof("Received OneTimeToken for Runtime %s in Director for tenant %s", id, tenant) + log.Infof("Received OneTimeToken for Runtime %s in Director for Global Account %s", compassID, globalAccount) return *response.Result, nil } @@ -145,7 +145,7 @@ func (cc *directorClient) getToken() apperrors.AppError { return nil } -func (cc *directorClient) executeDirectorGraphQLCall(directorQuery string, tenant string, response interface{}) apperrors.AppError { +func (cc *directorClient) executeDirectorGraphQLCall(directorQuery string, globalAccount string, response interface{}) apperrors.AppError { if cc.token.EmptyOrExpired() { log.Infof("Refreshing token to access Director Service") if err := cc.getToken(); err != nil { @@ -155,10 +155,11 @@ func (cc *directorClient) executeDirectorGraphQLCall(directorQuery string, tenan req := gcli.NewRequest(directorQuery) req.Header.Set(AuthorizationHeader, fmt.Sprintf("Bearer %s", cc.token.AccessToken)) - req.Header.Set(TenantHeader, tenant) + req.Header.Set(TenantHeader, globalAccount) if err := cc.gqlClient.Do(req, response); err != nil { - if egErr, ok := err.(gcli.ExtendedError); ok { + var egErr gcli.ExtendedError + if errors.As(err, &egErr) { return mapDirectorErrorToProvisionerError(egErr).Append("Failed to execute GraphQL request to Director") } return apperrors.Internal("Failed to execute GraphQL request to Director: %v", err) @@ -189,7 +190,7 @@ func mapDirectorErrorToProvisionerError(egErr gcli.ExtendedError) apperrors.AppE directorApperrors.InvalidOperation: err = apperrors.BadRequest(egErr.Error()) case directorApperrors.TenantRequired, directorApperrors.TenantNotFound: - err = apperrors.InvalidTenant(egErr.Error()) + err = apperrors.InvalidGlobalAccount(egErr.Error()) default: err = apperrors.Internal("Did not recognize the error code from the error response. Original error: %v", egErr) } diff --git a/internal/director/directorclient_test.go b/internal/director/directorclient_test.go index 1fdf046..d1a2198 100644 --- a/internal/director/directorclient_test.go +++ b/internal/director/directorclient_test.go @@ -8,9 +8,6 @@ import ( directorApperrors "github.com/kyma-incubator/compass/components/director/pkg/apperrors" "github.com/kyma-incubator/compass/components/director/pkg/graphql" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/kyma-project/compass-manager/internal/apperrors" gql "github.com/kyma-project/compass-manager/internal/graphql" "github.com/kyma-project/compass-manager/internal/oauth" @@ -18,13 +15,15 @@ import ( "github.com/kyma-project/compass-manager/internal/util" "github.com/kyma-project/compass-manager/pkg/gqlschema" gcli "github.com/kyma-project/compass-manager/third_party/machinebox/graphql" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) const ( - runtimeTestingID = "test-runtime-ID-12345" - runtimeTestingName = "Runtime Test name" + compassTestingID = "test-runtime-ID-12345" + compassTestingName = "Runtime Test name" validTokenValue = "12345" - tenantValue = "3e64ebae-38b5-46a0-b1ed-9ccee153a0ae" + globalAccountValue = "3e64ebae-38b5-46a0-b1ed-9ccee153a0ae" oneTimeToken = "54321" connectorURL = "https://kyma.cx/connector/graphql" @@ -46,19 +45,19 @@ const ( ) var ( - futureExpirationTime = time.Now().Add(time.Duration(60) * time.Minute).Unix() - passedExpirationTime = time.Now().Add(time.Duration(60) * time.Minute * -1).Unix() + futureExpirationTime = time.Now().Add(time.Duration(60) * time.Minute).Unix() //nolint:gochecknoglobals + passedExpirationTime = time.Now().Add(time.Duration(60) * time.Minute * -1).Unix() //nolint:gochecknoglobals ) func TestDirectorClient_RuntimeRegistering(t *testing.T) { expectedRequest := gcli.NewRequest(expectedRegisterRuntimeQuery) expectedRequest.Header.Set(AuthorizationHeader, fmt.Sprintf("Bearer %s", validTokenValue)) - expectedRequest.Header.Set(TenantHeader, tenantValue) + expectedRequest.Header.Set(TenantHeader, globalAccountValue) inputDescription := "runtime description" runtimeInput := &gqlschema.RuntimeInput{ - Name: runtimeTestingName, + Name: compassTestingName, Description: &inputDescription, } @@ -66,12 +65,12 @@ func TestDirectorClient_RuntimeRegistering(t *testing.T) { // given responseDescription := "runtime description" expectedResponse := &graphql.Runtime{ - ID: runtimeTestingID, - Name: runtimeTestingName, + ID: compassTestingID, + Name: compassTestingName, Description: &responseDescription, } - expectedID := runtimeTestingID + expectedID := compassTestingID gqlClient := gql.NewQueryAssertClient(t, nil, []*gcli.Request{expectedRequest}, func(t *testing.T, r interface{}) { cfg, ok := r.(*CreateRuntimeResponse) @@ -91,7 +90,7 @@ func TestDirectorClient_RuntimeRegistering(t *testing.T) { configClient := NewDirectorClient(gqlClient, mockedOAuthClient) // when - receivedRuntimeID, err := configClient.CreateRuntime(runtimeInput, tenantValue) + receivedRuntimeID, err := configClient.CreateRuntime(runtimeInput, globalAccountValue) // then assert.NoError(t, err) @@ -111,7 +110,7 @@ func TestDirectorClient_RuntimeRegistering(t *testing.T) { configClient := NewDirectorClient(nil, mockedOAuthClient) // when - receivedRuntimeID, err := configClient.CreateRuntime(runtimeInput, tenantValue) + receivedRuntimeID, err := configClient.CreateRuntime(runtimeInput, globalAccountValue) // then assert.Error(t, err) @@ -131,7 +130,7 @@ func TestDirectorClient_RuntimeRegistering(t *testing.T) { configClient := NewDirectorClient(nil, mockedOAuthClient) // when - receivedRuntimeID, err := configClient.CreateRuntime(runtimeInput, tenantValue) + receivedRuntimeID, err := configClient.CreateRuntime(runtimeInput, globalAccountValue) // then assert.Error(t, err) @@ -146,7 +145,7 @@ func TestDirectorClient_RuntimeRegistering(t *testing.T) { configClient := NewDirectorClient(nil, mockedOAuthClient) // when - receivedRuntimeID, err := configClient.CreateRuntime(runtimeInput, tenantValue) + receivedRuntimeID, err := configClient.CreateRuntime(runtimeInput, globalAccountValue) // then assert.Error(t, err) @@ -174,7 +173,7 @@ func TestDirectorClient_RuntimeRegistering(t *testing.T) { configClient := NewDirectorClient(gqlClient, mockedOAuthClient) // when - receivedRuntimeID, err := configClient.CreateRuntime(runtimeInput, tenantValue) + receivedRuntimeID, err := configClient.CreateRuntime(runtimeInput, globalAccountValue) // then assert.Error(t, err) @@ -201,7 +200,7 @@ func TestDirectorClient_RuntimeRegistering(t *testing.T) { configClient := NewDirectorClient(gqlClient, mockedOAuthClient) // when - receivedRuntimeID, err := configClient.CreateRuntime(runtimeInput, tenantValue) + receivedRuntimeID, err := configClient.CreateRuntime(runtimeInput, globalAccountValue) // then assert.Error(t, err) @@ -212,7 +211,7 @@ func TestDirectorClient_RuntimeRegistering(t *testing.T) { func TestDirectorClient_GetConnectionToken(t *testing.T) { expectedRequest := gcli.NewRequest(expectedOneTimeTokenQuery) expectedRequest.Header.Set(AuthorizationHeader, fmt.Sprintf("Bearer %s", validTokenValue)) - expectedRequest.Header.Set(TenantHeader, tenantValue) + expectedRequest.Header.Set(TenantHeader, globalAccountValue) t.Run("Should return OneTimeToken when Oauth Token is valid", func(t *testing.T) { // given @@ -243,7 +242,7 @@ func TestDirectorClient_GetConnectionToken(t *testing.T) { configClient := NewDirectorClient(gqlClient, mockedOAuthClient) // when - receivedOneTimeToken, err := configClient.GetConnectionToken(runtimeTestingID, tenantValue) + receivedOneTimeToken, err := configClient.GetConnectionToken(compassTestingID, globalAccountValue) // then require.NoError(t, err) @@ -265,7 +264,7 @@ func TestDirectorClient_GetConnectionToken(t *testing.T) { configClient := NewDirectorClient(nil, mockedOAuthClient) // when - receivedOneTimeToken, err := configClient.GetConnectionToken(runtimeTestingID, tenantValue) + receivedOneTimeToken, err := configClient.GetConnectionToken(compassTestingID, globalAccountValue) // then require.Error(t, err) @@ -285,7 +284,7 @@ func TestDirectorClient_GetConnectionToken(t *testing.T) { configClient := NewDirectorClient(nil, mockedOAuthClient) // when - receivedOneTimeToken, err := configClient.GetConnectionToken(runtimeTestingID, tenantValue) + receivedOneTimeToken, err := configClient.GetConnectionToken(compassTestingID, globalAccountValue) // then require.Error(t, err) @@ -311,7 +310,7 @@ func TestDirectorClient_GetConnectionToken(t *testing.T) { configClient := NewDirectorClient(gqlClient, mockedOAuthClient) // when - receivedOneTimeToken, err := configClient.GetConnectionToken(runtimeTestingID, tenantValue) + receivedOneTimeToken, err := configClient.GetConnectionToken(compassTestingID, globalAccountValue) // then require.Error(t, err) @@ -322,14 +321,14 @@ func TestDirectorClient_GetConnectionToken(t *testing.T) { func TestDirectorClient_GetRuntime(t *testing.T) { expectedRequest := gcli.NewRequest(expectedGetRuntimeQuery) expectedRequest.Header.Set(AuthorizationHeader, fmt.Sprintf("Bearer %s", validTokenValue)) - expectedRequest.Header.Set(TenantHeader, tenantValue) + expectedRequest.Header.Set(TenantHeader, globalAccountValue) t.Run("should return Runtime", func(t *testing.T) { // given expectedResponse := &graphql.RuntimeExt{ Runtime: graphql.Runtime{ - ID: runtimeTestingID, - Name: runtimeTestingName, + ID: compassTestingID, + Name: compassTestingName, }, } @@ -351,7 +350,7 @@ func TestDirectorClient_GetRuntime(t *testing.T) { configClient := NewDirectorClient(gqlClient, mockedOAuthClient) // when - runtime, err := configClient.GetRuntime(runtimeTestingID, tenantValue) + runtime, err := configClient.GetRuntime(compassTestingID, globalAccountValue) // then require.NoError(t, err) @@ -372,7 +371,7 @@ func TestDirectorClient_GetRuntime(t *testing.T) { configClient := NewDirectorClient(nil, mockedOAuthClient) // when - runtime, err := configClient.GetRuntime(runtimeTestingID, tenantValue) + runtime, err := configClient.GetRuntime(compassTestingID, globalAccountValue) // then assert.Error(t, err) @@ -399,7 +398,7 @@ func TestDirectorClient_GetRuntime(t *testing.T) { configClient := NewDirectorClient(gqlClient, mockedOAuthClient) // when - runtime, err := configClient.GetRuntime(runtimeTestingID, tenantValue) + runtime, err := configClient.GetRuntime(compassTestingID, globalAccountValue) // then require.Error(t, err) @@ -426,7 +425,7 @@ func TestDirectorClient_GetRuntime(t *testing.T) { configClient := NewDirectorClient(gqlClient, mockedOAuthClient) // when - runtime, err := configClient.GetRuntime(runtimeTestingID, tenantValue) + runtime, err := configClient.GetRuntime(compassTestingID, globalAccountValue) // then require.Error(t, err) @@ -451,11 +450,11 @@ func TestDirectorClient_MapDirectorErrors(t *testing.T) { // given expectedRequest := gcli.NewRequest(expectedRegisterRuntimeQuery) expectedRequest.Header.Set(AuthorizationHeader, fmt.Sprintf("Bearer %s", validTokenValue)) - expectedRequest.Header.Set(TenantHeader, tenantValue) + expectedRequest.Header.Set(TenantHeader, globalAccountValue) inputDescription := "runtime description" runtimeInput := &gqlschema.RuntimeInput{ - Name: runtimeTestingName, + Name: compassTestingName, Description: &inputDescription, } @@ -524,14 +523,14 @@ func TestDirectorClient_MapDirectorErrors(t *testing.T) { "Should map Director Tenant Required Error to Provisioner Bad Request Error", map[string]interface{}{"error_code": float64(directorApperrors.TenantRequired)}, apperrors.CodeBadRequest, - apperrors.TenantNotFound, + apperrors.GlobalAccountNotFound, "Failed to register runtime in Director. Request failed, Failed to execute GraphQL request to Director, graphql: some error", }, { "Should map Director Tenant Not Found Error to Provisioner Bad Request Error", map[string]interface{}{"error_code": float64(directorApperrors.TenantNotFound)}, apperrors.CodeBadRequest, - apperrors.TenantNotFound, + apperrors.GlobalAccountNotFound, "Failed to register runtime in Director. Request failed, Failed to execute GraphQL request to Director, graphql: some error", }, { @@ -580,7 +579,7 @@ func TestDirectorClient_MapDirectorErrors(t *testing.T) { directorClient := NewDirectorClient(gqlClient, mockedOAuthClient) // when - _, err := directorClient.CreateRuntime(runtimeInput, tenantValue) + _, err := directorClient.CreateRuntime(runtimeInput, globalAccountValue) // then require.Error(t, err) diff --git a/internal/director/mocks/Client.go b/internal/director/mocks/Client.go index 839cdb3..adbbbf0 100644 --- a/internal/director/mocks/Client.go +++ b/internal/director/mocks/Client.go @@ -17,23 +17,23 @@ type Client struct { mock.Mock } -// CreateRuntime provides a mock function with given fields: config, tenant -func (_m *Client) CreateRuntime(config *gqlschema.RuntimeInput, tenant string) (string, apperrors.AppError) { - ret := _m.Called(config, tenant) +// CreateRuntime provides a mock function with given fields: config, globalAccount +func (_m *Client) CreateRuntime(config *gqlschema.RuntimeInput, globalAccount string) (string, apperrors.AppError) { + ret := _m.Called(config, globalAccount) var r0 string var r1 apperrors.AppError if rf, ok := ret.Get(0).(func(*gqlschema.RuntimeInput, string) (string, apperrors.AppError)); ok { - return rf(config, tenant) + return rf(config, globalAccount) } if rf, ok := ret.Get(0).(func(*gqlschema.RuntimeInput, string) string); ok { - r0 = rf(config, tenant) + r0 = rf(config, globalAccount) } else { r0 = ret.Get(0).(string) } if rf, ok := ret.Get(1).(func(*gqlschema.RuntimeInput, string) apperrors.AppError); ok { - r1 = rf(config, tenant) + r1 = rf(config, globalAccount) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(apperrors.AppError) @@ -43,23 +43,23 @@ func (_m *Client) CreateRuntime(config *gqlschema.RuntimeInput, tenant string) ( return r0, r1 } -// GetConnectionToken provides a mock function with given fields: id, tenant -func (_m *Client) GetConnectionToken(id string, tenant string) (graphql.OneTimeTokenForRuntimeExt, apperrors.AppError) { - ret := _m.Called(id, tenant) +// GetConnectionToken provides a mock function with given fields: compassID, globalAccount +func (_m *Client) GetConnectionToken(compassID string, globalAccount string) (graphql.OneTimeTokenForRuntimeExt, apperrors.AppError) { + ret := _m.Called(compassID, globalAccount) var r0 graphql.OneTimeTokenForRuntimeExt var r1 apperrors.AppError if rf, ok := ret.Get(0).(func(string, string) (graphql.OneTimeTokenForRuntimeExt, apperrors.AppError)); ok { - return rf(id, tenant) + return rf(compassID, globalAccount) } if rf, ok := ret.Get(0).(func(string, string) graphql.OneTimeTokenForRuntimeExt); ok { - r0 = rf(id, tenant) + r0 = rf(compassID, globalAccount) } else { r0 = ret.Get(0).(graphql.OneTimeTokenForRuntimeExt) } if rf, ok := ret.Get(1).(func(string, string) apperrors.AppError); ok { - r1 = rf(id, tenant) + r1 = rf(compassID, globalAccount) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(apperrors.AppError) @@ -69,23 +69,23 @@ func (_m *Client) GetConnectionToken(id string, tenant string) (graphql.OneTimeT return r0, r1 } -// GetRuntime provides a mock function with given fields: id, tenant -func (_m *Client) GetRuntime(id string, tenant string) (graphql.RuntimeExt, apperrors.AppError) { - ret := _m.Called(id, tenant) +// GetRuntime provides a mock function with given fields: compassID, globalAccount +func (_m *Client) GetRuntime(compassID string, globalAccount string) (graphql.RuntimeExt, apperrors.AppError) { + ret := _m.Called(compassID, globalAccount) var r0 graphql.RuntimeExt var r1 apperrors.AppError if rf, ok := ret.Get(0).(func(string, string) (graphql.RuntimeExt, apperrors.AppError)); ok { - return rf(id, tenant) + return rf(compassID, globalAccount) } if rf, ok := ret.Get(0).(func(string, string) graphql.RuntimeExt); ok { - r0 = rf(id, tenant) + r0 = rf(compassID, globalAccount) } else { r0 = ret.Get(0).(graphql.RuntimeExt) } if rf, ok := ret.Get(1).(func(string, string) apperrors.AppError); ok { - r1 = rf(id, tenant) + r1 = rf(compassID, globalAccount) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(apperrors.AppError) diff --git a/internal/director/query.go b/internal/director/query.go index c5723c9..60b2630 100644 --- a/internal/director/query.go +++ b/internal/director/query.go @@ -9,16 +9,16 @@ func (qp queryProvider) createRuntimeMutation(runtimeInput string) string { result: registerRuntime(in: %s) { id } }`, runtimeInput) } -func (qp queryProvider) getRuntimeQuery(runtimeID string) string { +func (qp queryProvider) getRuntimeQuery(compassID string) string { return fmt.Sprintf(`query { result: runtime(id: "%s") { id name description labels -}}`, runtimeID) +}}`, compassID) } -func (qp queryProvider) requestOneTimeTokenMutation(runtimeID string) string { +func (qp queryProvider) requestOneTimeTokenMutation(compassID string) string { return fmt.Sprintf(`mutation { result: requestOneTimeTokenForRuntime(id: "%s") { token connectorURL -}}`, runtimeID) +}}`, compassID) } diff --git a/internal/oauth/client.go b/internal/oauth/client.go index 2923e0c..68cbf0c 100644 --- a/internal/oauth/client.go +++ b/internal/oauth/client.go @@ -10,7 +10,6 @@ import ( "time" "github.com/kyma-project/compass-manager/internal/apperrors" - "github.com/kyma-project/compass-manager/internal/util" log "github.com/sirupsen/logrus" ) @@ -65,8 +64,8 @@ func (c *oauthClient) getAuthorizationToken(credentials credentials) (Token, app defer util.Close(response.Body) if response.StatusCode != http.StatusOK { - dump, err := httputil.DumpResponse(response, true) - if err != nil { + dump, dumpErr := httputil.DumpResponse(response, true) + if dumpErr != nil { dump = []byte("failed to dump response body") } return Token{}, apperrors.External("Get token call returned unexpected status: %s. Response dump: %s", response.Status, string(dump)).SetComponent(apperrors.ErrMpsOAuth2) diff --git a/internal/oauth/client_test.go b/internal/oauth/client_test.go index aecdf58..0aa743e 100644 --- a/internal/oauth/client_test.go +++ b/internal/oauth/client_test.go @@ -4,7 +4,7 @@ import ( "bytes" "context" "encoding/json" - "io/ioutil" + "io" "net/http" "testing" "time" @@ -24,8 +24,8 @@ const ( func TestOauthClient_GetAuthorizationToken(t *testing.T) { t.Run("Should return oauth token", func(t *testing.T) { - //given - credentials := credentials{ + // given + credentials := credentials{ //nolint:govet clientID: "12345", clientSecret: "some dark and scary secret", tokensEndpoint: "http://hydra:4445", @@ -46,7 +46,7 @@ func TestOauthClient_GetAuthorizationToken(t *testing.T) { return &http.Response{ StatusCode: http.StatusOK, - Body: ioutil.NopCloser(bytes.NewReader(jsonToken)), + Body: io.NopCloser(bytes.NewReader(jsonToken)), } } return &http.Response{ @@ -61,12 +61,12 @@ func TestOauthClient_GetAuthorizationToken(t *testing.T) { oauthClient := NewOauthClient(client, credentials.clientID, credentials.clientSecret, credentials.tokensEndpoint) - //when + // when responseToken, err := oauthClient.GetAuthorizationToken() require.NoError(t, err) token.Expiration += time.Now().Unix() - //then + // then assert.Equal(t, token.AccessToken, responseToken.AccessToken) assert.Equal(t, token.Expiration, responseToken.Expiration) }) @@ -85,7 +85,6 @@ func (f RoundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) { } func createFakeCredentialsSecret(t *testing.T, secrets core.SecretInterface, credentials credentials) { - secret := &v1.Secret{ ObjectMeta: meta.ObjectMeta{ Name: secretName, diff --git a/internal/oauth/types_test.go b/internal/oauth/types_test.go index e1f8b0f..e7aa0db 100644 --- a/internal/oauth/types_test.go +++ b/internal/oauth/types_test.go @@ -9,18 +9,18 @@ import ( func TestToken_EmptyOrExpired(t *testing.T) { t.Run("Should return true when token is empty", func(t *testing.T) { - //given + // given token := Token{} - //when + // when empty := token.EmptyOrExpired() - //then + // then assert.True(t, empty) }) t.Run("Should return true when expired", func(t *testing.T) { - //given + // given time2000 := time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC).Unix() token := Token{ @@ -28,15 +28,15 @@ func TestToken_EmptyOrExpired(t *testing.T) { Expiration: time2000, } - //when + // when expired := token.EmptyOrExpired() - //then + // then assert.True(t, expired) }) t.Run("Should return false when not empty or expired", func(t *testing.T) { - //given + // given time3000 := time.Date(3000, 1, 1, 0, 0, 0, 0, time.UTC).Unix() token := Token{ @@ -44,10 +44,10 @@ func TestToken_EmptyOrExpired(t *testing.T) { Expiration: time3000, } - //when + // when notExpired := token.EmptyOrExpired() - //then + // then assert.False(t, notExpired) }) } diff --git a/internal/util/errors.go b/internal/util/errors.go index a475772..1e2880b 100644 --- a/internal/util/errors.go +++ b/internal/util/errors.go @@ -1,10 +1,11 @@ package util import ( + "testing" + "github.com/kyma-project/compass-manager/internal/apperrors" "github.com/pkg/errors" "github.com/stretchr/testify/assert" - "testing" ) func CheckErrorType(t *testing.T, err error, errCode apperrors.ErrCode) { diff --git a/internal/util/retry.go b/internal/util/retry.go index 0dcfd88..76b5cc0 100644 --- a/internal/util/retry.go +++ b/internal/util/retry.go @@ -4,7 +4,6 @@ import ( "time" "github.com/kyma-project/compass-manager/internal/apperrors" - "github.com/sirupsen/logrus" ) diff --git a/internal/util/retry_test.go b/internal/util/retry_test.go index bee443a..dc63239 100644 --- a/internal/util/retry_test.go +++ b/internal/util/retry_test.go @@ -9,13 +9,13 @@ import ( func TestRetryOnError(t *testing.T) { t.Run("should retry function on error", func(t *testing.T) { - //given + // given tester := tester{errReturned: false} - //when + // when err := RetryOnError(1, 2, "function call returned error: %s", tester.testFunction) - //then + // then require.NoError(t, err) }) } diff --git a/main.go b/main.go index fa202ae..b0543ca 100644 --- a/main.go +++ b/main.go @@ -4,22 +4,23 @@ import ( "crypto/tls" "flag" "fmt" - "github.com/pkg/errors" - "github.com/vrischmann/envconfig" - "k8s.io/apimachinery/pkg/util/yaml" "log" "net/http" "os" "time" + "github.com/kyma-project/compass-manager/api/v1beta1" "github.com/kyma-project/compass-manager/controllers" "github.com/kyma-project/compass-manager/internal/director" "github.com/kyma-project/compass-manager/internal/graphql" "github.com/kyma-project/compass-manager/internal/oauth" kyma "github.com/kyma-project/lifecycle-manager/api/v1beta2" + "github.com/pkg/errors" "github.com/sirupsen/logrus" + "github.com/vrischmann/envconfig" "k8s.io/apimachinery/pkg/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/apimachinery/pkg/util/yaml" clientgoscheme "k8s.io/client-go/kubernetes/scheme" _ "k8s.io/client-go/plugin/pkg/client/auth" ctrl "sigs.k8s.io/controller-runtime" @@ -28,8 +29,8 @@ import ( ) var ( - scheme = runtime.NewScheme() - setupLog = ctrl.Log.WithName("setup") + scheme = runtime.NewScheme() //nolint:gochecknoglobals + setupLog = ctrl.Log.WithName("setup") //nolint:gochecknoglobals ) type config struct { @@ -58,11 +59,11 @@ type DirectorOAuth struct { func init() { utilruntime.Must(clientgoscheme.AddToScheme(scheme)) utilruntime.Must(kyma.AddToScheme(scheme)) + utilruntime.Must(v1beta1.AddToScheme(scheme)) //+kubebuilder:scaffold:scheme } func main() { - cfg := config{} err := envconfig.InitWithPrefix(&cfg, "APP") exitOnError(err, "Failed to load application config") @@ -86,7 +87,7 @@ func main() { mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ Scheme: scheme, MetricsBindAddress: metricsAddr, - Port: 9443, + Port: 9443, //nolint:gomnd HealthProbeBindAddress: probeAddr, LeaderElection: enableLeaderElection, LeaderElectionID: "2647ec81.kyma-project.io", @@ -106,8 +107,10 @@ func main() { } compassRegistrator := controllers.NewCompassRegistator(directorClient, log) + runtimeAgentConfigurator := controllers.NewRuntimeAgentConfigurator(log) + requeueTime := time.Minute * 5 //nolint:gomnd - compassManagerReconciler := controllers.NewCompassManagerReconciler(mgr, log, compassRegistrator) + compassManagerReconciler := controllers.NewCompassManagerReconciler(mgr, log, runtimeAgentConfigurator, compassRegistrator, requeueTime) if err = compassManagerReconciler.SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "CompassManager") os.Exit(1) @@ -153,7 +156,7 @@ func newHTTPClient(skipCertVerification bool) *http.Client { Transport: &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: skipCertVerification}, }, - Timeout: 30 * time.Second, + Timeout: 30 * time.Second, //nolint:gomnd } } diff --git a/pkg/gqlschema/labels.go b/pkg/gqlschema/labels.go index 5ea34e0..b63ee58 100644 --- a/pkg/gqlschema/labels.go +++ b/pkg/gqlschema/labels.go @@ -3,11 +3,9 @@ package gqlschema import ( "io" - log "github.com/sirupsen/logrus" - "github.com/kyma-incubator/compass/components/director/pkg/scalar" - "github.com/pkg/errors" + log "github.com/sirupsen/logrus" ) type Labels map[string]interface{} diff --git a/sec-scanners-config.yaml b/sec-scanners-config.yaml new file mode 100644 index 0000000..c1a78ba --- /dev/null +++ b/sec-scanners-config.yaml @@ -0,0 +1,8 @@ +module-name: compass-manager +protecode: + - europe-docker.pkg.dev/kyma-project/prod/compass-manager:v20230921-52c7b2bc +whitesource: + language: golang-mod + subprojects: false + exclude: + - "**/*_test.go" \ No newline at end of file