diff --git a/Makefile b/Makefile index a275c5a49..a63a7c01a 100644 --- a/Makefile +++ b/Makefile @@ -3,8 +3,8 @@ IMAGE_ORG=integreatly IMAGE_NAME=cloud-resource-operator MANIFEST_NAME=cloud-resources NAMESPACE=cloud-resource-operator -PREV_VERSION=0.4.0 -VERSION=0.5.0 +PREV_VERSION=0.5.0 +VERSION=0.6.0 COMPILE_TARGET=./tmp/_output/bin/$(IMAGE_NAME) OPERATOR_SDK_VERSION=0.12.0 @@ -73,6 +73,8 @@ cluster/prepare: oc create -f ./deploy/crds/integreatly_v1alpha1_smtpcredentialset_crd.yaml -n $(NAMESPACE) oc create -f ./deploy/crds/integreatly_v1alpha1_redis_crd.yaml -n $(NAMESPACE) oc create -f ./deploy/crds/integreatly_v1alpha1_postgres_crd.yaml -n $(NAMESPACE) + oc create -f ./deploy/crds/integreatly_v1alpha1_redissnapshot_crd.yaml -n $(NAMESPACE) + oc create -f ./deploy/crds/integreatly_v1alpha1_postgressnapshot_crd.yaml -n $(NAMESPACE) oc create -f ./deploy/service_account.yaml -n $(NAMESPACE) oc create -f ./deploy/role.yaml -n $(NAMESPACE) oc create -f ./deploy/role_binding.yaml -n $(NAMESPACE) @@ -117,6 +119,8 @@ cluster/clean: oc delete -f ./deploy/crds/integreatly_v1alpha1_smtpcredentialset_crd.yaml -n $(NAMESPACE) oc delete -f ./deploy/crds/integreatly_v1alpha1_redis_crd.yaml -n $(NAMESPACE) oc delete -f ./deploy/crds/integreatly_v1alpha1_postgres_crd.yaml -n $(NAMESPACE) + oc delete -f ./deploy/crds/integreatly_v1alpha1_redissnapshot_crd.yaml -n $(NAMESPACE) + oc delete -f ./deploy/crds/integreatly_v1alpha1_postgressnapshot_crd.yaml -n $(NAMESPACE) oc delete -f ./deploy/service_account.yaml -n $(NAMESPACE) oc delete -f ./deploy/role.yaml -n $(NAMESPACE) oc delete -f ./deploy/role_binding.yaml -n $(NAMESPACE) diff --git a/README.md b/README.md index 44109660b..24be3f54a 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,20 @@ Currently AWS resources are deployed into a separate Virtual Private Cloud (VPC) The two VPCs should now be able to communicate with each other. +## Snapshots +The cloud resource operator supports the taking of arbitrary snapshots in the AWS provider for both `Postgres` and `Redis`. To take a snapshot you must create a `RedisSnapshot` or `PostgresSnapshot` resource, which should reference the `Redis` or `Postgres` resource you wish to create a snapshot of. The snapshot resource must also exist in the same namespace. +``` +apiVersion: integreatly.org/v1alpha1 +kind: RedisSnapshot +metadata: + name: my-redis-snapshot +spec: + # The redis resource name for the snapshot you want to take + resourceName: my-redis-resource + +``` +*Note* You may experience some downtime in the resource during the creation of the Snapshot + ## Skip Create The cloud resource operator continuously reconciles using the strat-config as a source of truth for the current state of the provisioned resources. Should these resources alter from the expected the state the operator will update the resources to match the expected state. diff --git a/deploy/crds/integreatly_v1alpha1_postgressnapshot_cr.yaml b/deploy/crds/integreatly_v1alpha1_postgressnapshot_cr.yaml new file mode 100644 index 000000000..43722de85 --- /dev/null +++ b/deploy/crds/integreatly_v1alpha1_postgressnapshot_cr.yaml @@ -0,0 +1,7 @@ +apiVersion: integreatly.org/v1alpha1 +kind: PostgresSnapshot +metadata: + name: example-postgressnapshot +spec: + # The postgres resource name for the snapshot you want to take + resourceName: REPLACE_ME diff --git a/deploy/crds/integreatly_v1alpha1_postgressnapshot_crd.yaml b/deploy/crds/integreatly_v1alpha1_postgressnapshot_crd.yaml new file mode 100644 index 000000000..9ce40a01d --- /dev/null +++ b/deploy/crds/integreatly_v1alpha1_postgressnapshot_crd.yaml @@ -0,0 +1,52 @@ +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: postgressnapshots.integreatly.org +spec: + group: integreatly.org + names: + kind: PostgresSnapshot + listKind: PostgresSnapshotList + plural: postgressnapshots + singular: postgressnapshot + scope: Namespaced + subresources: + status: {} + validation: + openAPIV3Schema: + 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/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/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + properties: + resourceName: + description: 'INSERT ADDITIONAL SPEC FIELDS - desired state of cluster + Important: Run "operator-sdk generate k8s" to regenerate code after + modifying this file Add custom validation using kubebuilder tags: + https://book-v1.book.kubebuilder.io/beyond_basics/generating_crd.html' + type: string + required: + - resourceName + type: object + status: + properties: + message: + type: string + phase: + type: string + type: object + version: v1alpha1 + versions: + - name: v1alpha1 + served: true + storage: true diff --git a/deploy/crds/integreatly_v1alpha1_redissnapshot_cr.yaml b/deploy/crds/integreatly_v1alpha1_redissnapshot_cr.yaml new file mode 100644 index 000000000..a411edab4 --- /dev/null +++ b/deploy/crds/integreatly_v1alpha1_redissnapshot_cr.yaml @@ -0,0 +1,7 @@ +apiVersion: integreatly.org/v1alpha1 +kind: RedisSnapshot +metadata: + name: example-redissnapshot +spec: + # The redis resource name for the snapshot you want to take + resourceName: REPLACE_ME diff --git a/deploy/crds/integreatly_v1alpha1_redissnapshot_crd.yaml b/deploy/crds/integreatly_v1alpha1_redissnapshot_crd.yaml new file mode 100644 index 000000000..b51196925 --- /dev/null +++ b/deploy/crds/integreatly_v1alpha1_redissnapshot_crd.yaml @@ -0,0 +1,52 @@ +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: redissnapshots.integreatly.org +spec: + group: integreatly.org + names: + kind: RedisSnapshot + listKind: RedisSnapshotList + plural: redissnapshots + singular: redissnapshot + scope: Namespaced + subresources: + status: {} + validation: + openAPIV3Schema: + 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/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/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + properties: + resourceName: + description: 'INSERT ADDITIONAL SPEC FIELDS - desired state of cluster + Important: Run "operator-sdk generate k8s" to regenerate code after + modifying this file Add custom validation using kubebuilder tags: + https://book-v1.book.kubebuilder.io/beyond_basics/generating_crd.html' + type: string + required: + - resourceName + type: object + status: + properties: + message: + type: string + phase: + type: string + type: object + version: v1alpha1 + versions: + - name: v1alpha1 + served: true + storage: true diff --git a/deploy/olm-catalog/cloud-resources/0.6.0/cloud-resources.v0.6.0.clusterserviceversion.yaml b/deploy/olm-catalog/cloud-resources/0.6.0/cloud-resources.v0.6.0.clusterserviceversion.yaml new file mode 100644 index 000000000..d757651aa --- /dev/null +++ b/deploy/olm-catalog/cloud-resources/0.6.0/cloud-resources.v0.6.0.clusterserviceversion.yaml @@ -0,0 +1,356 @@ +apiVersion: operators.coreos.com/v1alpha1 +kind: ClusterServiceVersion +metadata: + annotations: + alm-examples: |- + [ + { + "apiVersion": "integreatly.org/v1alpha1", + "kind": "Postgres", + "metadata": { + "labels": { + "productName": "productName" + }, + "name": "example-postgres" + }, + "spec": { + "secretRef": { + "name": "example-postgres-sec" + }, + "tier": "development", + "type": "REPLACE_ME" + } + }, + { + "apiVersion": "integreatly.org/v1alpha1", + "kind": "PostgresSnapshot", + "metadata": { + "name": "example-postgressnapshot" + }, + "spec": { + "resourceName": "REPLACE_ME" + } + }, + { + "apiVersion": "integreatly.org/v1alpha1", + "kind": "Redis", + "metadata": { + "labels": { + "productName": "productName" + }, + "name": "example-redis" + }, + "spec": { + "secretRef": { + "name": "example-redis-sec" + }, + "tier": "development", + "type": "REPLACE_ME" + } + }, + { + "apiVersion": "integreatly.org/v1alpha1", + "kind": "RedisSnapshot", + "metadata": { + "name": "example-redissnapshot" + }, + "spec": { + "resourceName": "REPLACE_ME" + } + }, + { + "apiVersion": "integreatly.org/v1alpha1", + "kind": "SMTPCredentialSet", + "metadata": { + "labels": { + "productName": "productName" + }, + "name": "example-smtpcredentialset" + }, + "spec": { + "secretRef": { + "name": "example-smtpcredentialset-sec" + }, + "tier": "development", + "type": "REPLACE_ME" + } + }, + { + "apiVersion": "integreatly.org/v1alpha1", + "kind": "BlobStorage", + "metadata": { + "labels": { + "productName": "ProductName" + }, + "name": "example-blobstorage" + }, + "spec": { + "secretRef": { + "name": "example-blobstorage-sec" + }, + "tier": "development", + "type": "REPLACE_ME" + } + } + ] + capabilities: Basic Install + categories: Integration & Delivery + certified: "false" + containerImage: quay.io/integreatly/cloud-resource-operator:0.5.0 + createdAt: "2019-10-07 12:34:56" + description: Operator to provision cloud provider resources in an abstracted manner + support: Integreatly + name: cloud-resources.v0.6.0 + namespace: placeholder +spec: + apiservicedefinitions: {} + customresourcedefinitions: + owned: + - description: Represents an instance of object or blob storage in a provider + displayName: Blob Storage + kind: BlobStorage + name: blobstorages.integreatly.org + specDescriptors: + - description: The type of the resource to deploy. Defaults are 'managed' and + 'workshop'. + displayName: Tier + path: tier + - description: The tier of the resource to deploy. Defaults are 'production' + and 'development'. + displayName: Type + path: type + - description: The secret which the resource information should be outputted + to + displayName: Secret Reference + path: secretRef + version: v1alpha1 + - description: Represents an instance of a Postgres in a provider + displayName: Postgres + kind: Postgres + name: postgres.integreatly.org + specDescriptors: + - description: The type of the resource to deploy. Defaults are 'managed' and + 'workshop'. + displayName: Tier + path: tier + - description: The tier of the resource to deploy. Defaults are 'production' + and 'development'. + displayName: Type + path: type + - description: The secret which the resource information should be outputted + to + displayName: Secret Reference + path: secretRef + version: v1alpha1 + - kind: PostgresSnapshot + name: postgressnapshots.integreatly.org + version: v1alpha1 + - description: Represents an instance of a Redis cluster in a provider + displayName: Redis + kind: Redis + name: redis.integreatly.org + specDescriptors: + - description: The type of the resource to deploy. Defaults are 'managed' and + 'workshop'. + displayName: Tier + path: tier + - description: The tier of the resource to deploy. Defaults are 'production' + and 'development'. + displayName: Type + path: type + - description: The secret which the resource information should be outputted + to + displayName: Secret Reference + path: secretRef + version: v1alpha1 + - kind: RedisSnapshot + name: redissnapshots.integreatly.org + version: v1alpha1 + - description: Represents an instance of SMTP credentials in a provider + displayName: SMTP Credentials + kind: SMTPCredentialSet + name: smtpcredentialsets.integreatly.org + specDescriptors: + - description: The type of the resource to deploy. Defaults are 'managed' and + 'workshop'. + displayName: Tier + path: tier + - description: The tier of the resource to deploy. Defaults are 'production' + and 'development'. + displayName: Type + path: type + - description: The secret which the resource information should be outputted + to + displayName: Secret Reference + path: secretRef + version: v1alpha1 + description: Provision and manage in-cluster and cloud provider resources (Blob + Storage, Postgres, Redis, SMTP Details) + displayName: Cloud Resource Operator + icon: + - base64data: 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 + mediatype: image/png + install: + spec: + clusterPermissions: + - rules: + - apiGroups: + - config.openshift.io + resources: + - infrastructures + verbs: + - get + - list + - watch + - apiGroups: + - "" + resources: + - persistentvolumes + - configmaps + verbs: + - '*' + serviceAccountName: cloud-resource-operator + deployments: + - name: cloud-resource-operator + spec: + replicas: 1 + selector: + matchLabels: + name: cloud-resource-operator + strategy: {} + template: + metadata: + labels: + name: cloud-resource-operator + spec: + containers: + - command: + - cloud-resource-operator + env: + - name: WATCH_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.annotations['olm.targetNamespaces'] + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: OPERATOR_NAME + value: cloud-resource-operator + - name: TAG_KEY_PREFIX + value: integreatly.org/ + image: quay.io/integreatly/cloud-resource-operator:0.6.0 + imagePullPolicy: Always + name: cloud-resource-operator + resources: {} + serviceAccountName: cloud-resource-operator + permissions: + - rules: + - apiGroups: + - "" + resources: + - pods + - pods/exec + - services + - services/finalizers + - endpoints + - persistentvolumeclaims + - events + - configmaps + - secrets + verbs: + - '*' + - apiGroups: + - apps + resources: + - deployments + - daemonsets + - replicasets + - statefulsets + verbs: + - '*' + - apiGroups: + - monitoring.coreos.com + resources: + - servicemonitors + verbs: + - get + - create + - apiGroups: + - apps + resourceNames: + - cloud-resource-operator + resources: + - deployments/finalizers + verbs: + - update + - apiGroups: + - "" + resources: + - pods + verbs: + - get + - apiGroups: + - apps + resources: + - replicasets + verbs: + - get + - apiGroups: + - integreatly + resources: + - '*' + verbs: + - '*' + - apiGroups: + - integreatly.org + resources: + - '*' + - smtpcredentialset + - redis + - postgres + - redissnapshots + - postgressnapshots + verbs: + - '*' + - apiGroups: + - config.openshift.io + resources: + - '*' + - infrastructures + - schedulers + - featuregates + - networks + - ingresses + - clusteroperators + - authentications + - builds + verbs: + - '*' + - apiGroups: + - cloudcredential.openshift.io + resources: + - credentialsrequests + verbs: + - '*' + serviceAccountName: cloud-resource-operator + strategy: deployment + installModes: + - supported: true + type: OwnNamespace + - supported: true + type: SingleNamespace + - supported: false + type: MultiNamespace + - supported: true + type: AllNamespaces + keywords: + - integreatly + maintainers: + - email: integreatly-support@redhat.com + name: Integreatly + maturity: alpha + provider: + name: Integreatly + replaces: cloud-resources.v0.5.0 + version: 0.6.0 diff --git a/deploy/olm-catalog/cloud-resources/0.6.0/integreatly_v1alpha1_blobstorage_crd.yaml b/deploy/olm-catalog/cloud-resources/0.6.0/integreatly_v1alpha1_blobstorage_crd.yaml new file mode 100644 index 000000000..8e08dcbb3 --- /dev/null +++ b/deploy/olm-catalog/cloud-resources/0.6.0/integreatly_v1alpha1_blobstorage_crd.yaml @@ -0,0 +1,76 @@ +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: blobstorages.integreatly.org +spec: + group: integreatly.org + names: + kind: BlobStorage + listKind: BlobStorageList + plural: blobstorages + singular: blobstorage + scope: Namespaced + subresources: + status: {} + validation: + openAPIV3Schema: + 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/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/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + properties: + secretRef: + properties: + name: + type: string + namespace: + type: string + required: + - name + type: object + skipCreate: + type: boolean + tier: + type: string + type: + type: string + required: + - type + - tier + - secretRef + type: object + status: + properties: + message: + type: string + phase: + type: string + provider: + type: string + secretRef: + properties: + name: + type: string + namespace: + type: string + required: + - name + type: object + strategy: + type: string + type: object + version: v1alpha1 + versions: + - name: v1alpha1 + served: true + storage: true diff --git a/deploy/olm-catalog/cloud-resources/0.6.0/integreatly_v1alpha1_postgres_crd.yaml b/deploy/olm-catalog/cloud-resources/0.6.0/integreatly_v1alpha1_postgres_crd.yaml new file mode 100644 index 000000000..ea52d65a9 --- /dev/null +++ b/deploy/olm-catalog/cloud-resources/0.6.0/integreatly_v1alpha1_postgres_crd.yaml @@ -0,0 +1,76 @@ +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: postgres.integreatly.org +spec: + group: integreatly.org + names: + kind: Postgres + listKind: PostgresList + plural: postgres + singular: postgres + scope: Namespaced + subresources: + status: {} + validation: + openAPIV3Schema: + 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/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/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + properties: + secretRef: + properties: + name: + type: string + namespace: + type: string + required: + - name + type: object + skipCreate: + type: boolean + tier: + type: string + type: + type: string + required: + - type + - tier + - secretRef + type: object + status: + properties: + message: + type: string + phase: + type: string + provider: + type: string + secretRef: + properties: + name: + type: string + namespace: + type: string + required: + - name + type: object + strategy: + type: string + type: object + version: v1alpha1 + versions: + - name: v1alpha1 + served: true + storage: true diff --git a/deploy/olm-catalog/cloud-resources/0.6.0/integreatly_v1alpha1_postgressnapshot_crd.yaml b/deploy/olm-catalog/cloud-resources/0.6.0/integreatly_v1alpha1_postgressnapshot_crd.yaml new file mode 100644 index 000000000..9ce40a01d --- /dev/null +++ b/deploy/olm-catalog/cloud-resources/0.6.0/integreatly_v1alpha1_postgressnapshot_crd.yaml @@ -0,0 +1,52 @@ +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: postgressnapshots.integreatly.org +spec: + group: integreatly.org + names: + kind: PostgresSnapshot + listKind: PostgresSnapshotList + plural: postgressnapshots + singular: postgressnapshot + scope: Namespaced + subresources: + status: {} + validation: + openAPIV3Schema: + 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/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/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + properties: + resourceName: + description: 'INSERT ADDITIONAL SPEC FIELDS - desired state of cluster + Important: Run "operator-sdk generate k8s" to regenerate code after + modifying this file Add custom validation using kubebuilder tags: + https://book-v1.book.kubebuilder.io/beyond_basics/generating_crd.html' + type: string + required: + - resourceName + type: object + status: + properties: + message: + type: string + phase: + type: string + type: object + version: v1alpha1 + versions: + - name: v1alpha1 + served: true + storage: true diff --git a/deploy/olm-catalog/cloud-resources/0.6.0/integreatly_v1alpha1_redis_crd.yaml b/deploy/olm-catalog/cloud-resources/0.6.0/integreatly_v1alpha1_redis_crd.yaml new file mode 100644 index 000000000..8edf44989 --- /dev/null +++ b/deploy/olm-catalog/cloud-resources/0.6.0/integreatly_v1alpha1_redis_crd.yaml @@ -0,0 +1,76 @@ +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: redis.integreatly.org +spec: + group: integreatly.org + names: + kind: Redis + listKind: RedisList + plural: redis + singular: redis + scope: Namespaced + subresources: + status: {} + validation: + openAPIV3Schema: + 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/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/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + properties: + secretRef: + properties: + name: + type: string + namespace: + type: string + required: + - name + type: object + skipCreate: + type: boolean + tier: + type: string + type: + type: string + required: + - type + - tier + - secretRef + type: object + status: + properties: + message: + type: string + phase: + type: string + provider: + type: string + secretRef: + properties: + name: + type: string + namespace: + type: string + required: + - name + type: object + strategy: + type: string + type: object + version: v1alpha1 + versions: + - name: v1alpha1 + served: true + storage: true diff --git a/deploy/olm-catalog/cloud-resources/0.6.0/integreatly_v1alpha1_redissnapshot_crd.yaml b/deploy/olm-catalog/cloud-resources/0.6.0/integreatly_v1alpha1_redissnapshot_crd.yaml new file mode 100644 index 000000000..b51196925 --- /dev/null +++ b/deploy/olm-catalog/cloud-resources/0.6.0/integreatly_v1alpha1_redissnapshot_crd.yaml @@ -0,0 +1,52 @@ +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: redissnapshots.integreatly.org +spec: + group: integreatly.org + names: + kind: RedisSnapshot + listKind: RedisSnapshotList + plural: redissnapshots + singular: redissnapshot + scope: Namespaced + subresources: + status: {} + validation: + openAPIV3Schema: + 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/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/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + properties: + resourceName: + description: 'INSERT ADDITIONAL SPEC FIELDS - desired state of cluster + Important: Run "operator-sdk generate k8s" to regenerate code after + modifying this file Add custom validation using kubebuilder tags: + https://book-v1.book.kubebuilder.io/beyond_basics/generating_crd.html' + type: string + required: + - resourceName + type: object + status: + properties: + message: + type: string + phase: + type: string + type: object + version: v1alpha1 + versions: + - name: v1alpha1 + served: true + storage: true diff --git a/deploy/olm-catalog/cloud-resources/0.6.0/integreatly_v1alpha1_smtpcredentialset_crd.yaml b/deploy/olm-catalog/cloud-resources/0.6.0/integreatly_v1alpha1_smtpcredentialset_crd.yaml new file mode 100644 index 000000000..a3ea1387b --- /dev/null +++ b/deploy/olm-catalog/cloud-resources/0.6.0/integreatly_v1alpha1_smtpcredentialset_crd.yaml @@ -0,0 +1,76 @@ +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: smtpcredentialsets.integreatly.org +spec: + group: integreatly.org + names: + kind: SMTPCredentialSet + listKind: SMTPCredentialSetList + plural: smtpcredentialsets + singular: smtpcredentialset + scope: Namespaced + subresources: + status: {} + validation: + openAPIV3Schema: + 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/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/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + properties: + secretRef: + properties: + name: + type: string + namespace: + type: string + required: + - name + type: object + skipCreate: + type: boolean + tier: + type: string + type: + type: string + required: + - type + - tier + - secretRef + type: object + status: + properties: + message: + type: string + phase: + type: string + provider: + type: string + secretRef: + properties: + name: + type: string + namespace: + type: string + required: + - name + type: object + strategy: + type: string + type: object + version: v1alpha1 + versions: + - name: v1alpha1 + served: true + storage: true diff --git a/deploy/olm-catalog/cloud-resources/cloud-resources.package.yaml b/deploy/olm-catalog/cloud-resources/cloud-resources.package.yaml index 1916d1ece..d087199bc 100644 --- a/deploy/olm-catalog/cloud-resources/cloud-resources.package.yaml +++ b/deploy/olm-catalog/cloud-resources/cloud-resources.package.yaml @@ -1,5 +1,5 @@ channels: -- currentCSV: cloud-resources.v0.5.0 +- currentCSV: cloud-resources.v0.6.0 name: integreatly defaultChannel: integreatly packageName: cloud-resources diff --git a/deploy/operator.yaml b/deploy/operator.yaml index aa45a3c54..023e01d47 100644 --- a/deploy/operator.yaml +++ b/deploy/operator.yaml @@ -15,7 +15,7 @@ spec: serviceAccountName: cloud-resource-operator containers: - name: cloud-resource-operator - image: quay.io/integreatly/cloud-resource-operator:0.5.0 + image: quay.io/integreatly/cloud-resource-operator:0.6.0 command: - cloud-resource-operator imagePullPolicy: Always diff --git a/deploy/role.yaml b/deploy/role.yaml index 868feb99e..63f84656a 100644 --- a/deploy/role.yaml +++ b/deploy/role.yaml @@ -67,12 +67,12 @@ rules: - smtpcredentialset - redis - postgres + - redissnapshots + - postgressnapshots verbs: - '*' - apiGroups: - config.openshift.io - verbs: - - '*' resources: - '*' - infrastructures @@ -83,9 +83,11 @@ rules: - clusteroperators - authentications - builds + verbs: + - '*' - apiGroups: - cloudcredential.openshift.io + resources: + - credentialsrequests verbs: - '*' - resources: - - credentialsrequests \ No newline at end of file diff --git a/go.mod b/go.mod index 10cfb97e2..0cf74e23f 100644 --- a/go.mod +++ b/go.mod @@ -26,7 +26,7 @@ require ( github.com/stretchr/testify v1.4.0 // indirect golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392 // indirect golang.org/x/net v0.0.0-20191003171128-d98b1b443823 // indirect - golang.org/x/sys v0.0.0-20200107162124-548cf772de50 // indirect + golang.org/x/sys v0.0.0-20200113162924-86b910548bc1 // indirect google.golang.org/appengine v1.6.2 // indirect gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect k8s.io/api v0.0.0 diff --git a/go.sum b/go.sum index 228877efa..121ea7fa0 100644 --- a/go.sum +++ b/go.sum @@ -655,8 +655,8 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190922100055-0a153f010e69 h1:rOhMmluY6kLMhdnrivzec6lLgaVbMHMn2ISQXJeJ5EM= golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200107162124-548cf772de50 h1:YvQ10rzcqWXLlJZ3XCUoO25savxmscf4+SC+ZqiCHhA= -golang.org/x/sys v0.0.0-20200107162124-548cf772de50/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1 h1:gZpLHxUX5BdYLA08Lj4YCJNN/jk7KtquiArPoeX0WvA= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= diff --git a/pkg/apis/integreatly/v1alpha1/postgressnapshot_types.go b/pkg/apis/integreatly/v1alpha1/postgressnapshot_types.go new file mode 100644 index 000000000..78d4e7609 --- /dev/null +++ b/pkg/apis/integreatly/v1alpha1/postgressnapshot_types.go @@ -0,0 +1,48 @@ +package v1alpha1 + +import ( + "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1/types" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! +// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. + +// PostgresSnapshotSpec defines the desired state of PostgresSnapshot +// +k8s:openapi-gen=true +type PostgresSnapshotSpec struct { + // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster + // Important: Run "operator-sdk generate k8s" to regenerate code after modifying this file + // Add custom validation using kubebuilder tags: https://book-v1.book.kubebuilder.io/beyond_basics/generating_crd.html + ResourceName string `json:"resourceName"` +} + +// PostgresSnapshotStatus defines the observed state of PostgresSnapshot +// +k8s:openapi-gen=true +type PostgresSnapshotStatus types.ResourceTypeSnapshotStatus + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// PostgresSnapshot is the Schema for the postgressnapshots API +// +k8s:openapi-gen=true +// +kubebuilder:subresource:status +type PostgresSnapshot struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec PostgresSnapshotSpec `json:"spec,omitempty"` + Status PostgresSnapshotStatus `json:"status,omitempty"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// PostgresSnapshotList contains a list of PostgresSnapshot +type PostgresSnapshotList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []PostgresSnapshot `json:"items"` +} + +func init() { + SchemeBuilder.Register(&PostgresSnapshot{}, &PostgresSnapshotList{}) +} diff --git a/pkg/apis/integreatly/v1alpha1/redissnapshot_types.go b/pkg/apis/integreatly/v1alpha1/redissnapshot_types.go new file mode 100644 index 000000000..9dd3cf2f8 --- /dev/null +++ b/pkg/apis/integreatly/v1alpha1/redissnapshot_types.go @@ -0,0 +1,45 @@ +package v1alpha1 + +import ( + "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1/types" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// RedisSnapshotSpec defines the desired state of RedisSnapshot +// +k8s:openapi-gen=true +type RedisSnapshotSpec struct { + // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster + // Important: Run "operator-sdk generate k8s" to regenerate code after modifying this file + // Add custom validation using kubebuilder tags: https://book-v1.book.kubebuilder.io/beyond_basics/generating_crd.html + ResourceName string `json:"resourceName"` +} + +// RedisSnapshotStatus defines the observed state of RedisSnapshot +// +k8s:openapi-gen=true +type RedisSnapshotStatus types.ResourceTypeSnapshotStatus + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// RedisSnapshot is the Schema for the redissnapshots API +// +k8s:openapi-gen=true +// +kubebuilder:subresource:status +type RedisSnapshot struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec RedisSnapshotSpec `json:"spec,omitempty"` + Status RedisSnapshotStatus `json:"status,omitempty"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// RedisSnapshotList contains a list of RedisSnapshot +type RedisSnapshotList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []RedisSnapshot `json:"items"` +} + +func init() { + SchemeBuilder.Register(&RedisSnapshot{}, &RedisSnapshotList{}) +} diff --git a/pkg/apis/integreatly/v1alpha1/types/types.go b/pkg/apis/integreatly/v1alpha1/types/types.go index e19341915..b4fe5b447 100644 --- a/pkg/apis/integreatly/v1alpha1/types/types.go +++ b/pkg/apis/integreatly/v1alpha1/types/types.go @@ -51,3 +51,11 @@ type ResourceTypeStatus struct { Phase StatusPhase `json:"phase,omitempty"` Message StatusMessage `json:"message,omitempty"` } + +// ResourceTypeSnapshotStatus Represents the basic status information provided by snapshot controller +// +k8s:openapi-gen=true +type ResourceTypeSnapshotStatus struct { + SnapshotID string `json:"snapshotId,omitempty"` + Phase StatusPhase `json:"phase,omitempty"` + Message StatusMessage `json:"message,omitempty"` +} diff --git a/pkg/apis/integreatly/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/integreatly/v1alpha1/zz_generated.deepcopy.go index 34e7a20c9..d48a5dff7 100644 --- a/pkg/apis/integreatly/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/integreatly/v1alpha1/zz_generated.deepcopy.go @@ -173,6 +173,99 @@ func (in *PostgresList) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PostgresSnapshot) DeepCopyInto(out *PostgresSnapshot) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + out.Status = in.Status + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PostgresSnapshot. +func (in *PostgresSnapshot) DeepCopy() *PostgresSnapshot { + if in == nil { + return nil + } + out := new(PostgresSnapshot) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *PostgresSnapshot) 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 *PostgresSnapshotList) DeepCopyInto(out *PostgresSnapshotList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]PostgresSnapshot, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PostgresSnapshotList. +func (in *PostgresSnapshotList) DeepCopy() *PostgresSnapshotList { + if in == nil { + return nil + } + out := new(PostgresSnapshotList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *PostgresSnapshotList) 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 *PostgresSnapshotSpec) DeepCopyInto(out *PostgresSnapshotSpec) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PostgresSnapshotSpec. +func (in *PostgresSnapshotSpec) DeepCopy() *PostgresSnapshotSpec { + if in == nil { + return nil + } + out := new(PostgresSnapshotSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PostgresSnapshotStatus) DeepCopyInto(out *PostgresSnapshotStatus) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PostgresSnapshotStatus. +func (in *PostgresSnapshotStatus) DeepCopy() *PostgresSnapshotStatus { + if in == nil { + return nil + } + out := new(PostgresSnapshotStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PostgresSpec) DeepCopyInto(out *PostgresSpec) { *out = *in @@ -276,6 +369,99 @@ func (in *RedisList) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RedisSnapshot) DeepCopyInto(out *RedisSnapshot) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + out.Status = in.Status + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RedisSnapshot. +func (in *RedisSnapshot) DeepCopy() *RedisSnapshot { + if in == nil { + return nil + } + out := new(RedisSnapshot) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *RedisSnapshot) 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 *RedisSnapshotList) DeepCopyInto(out *RedisSnapshotList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]RedisSnapshot, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RedisSnapshotList. +func (in *RedisSnapshotList) DeepCopy() *RedisSnapshotList { + if in == nil { + return nil + } + out := new(RedisSnapshotList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *RedisSnapshotList) 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 *RedisSnapshotSpec) DeepCopyInto(out *RedisSnapshotSpec) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RedisSnapshotSpec. +func (in *RedisSnapshotSpec) DeepCopy() *RedisSnapshotSpec { + if in == nil { + return nil + } + out := new(RedisSnapshotSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RedisSnapshotStatus) DeepCopyInto(out *RedisSnapshotStatus) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RedisSnapshotStatus. +func (in *RedisSnapshotStatus) DeepCopy() *RedisSnapshotStatus { + if in == nil { + return nil + } + out := new(RedisSnapshotStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RedisSpec) DeepCopyInto(out *RedisSpec) { *out = *in diff --git a/pkg/apis/integreatly/v1alpha1/zz_generated.openapi.go b/pkg/apis/integreatly/v1alpha1/zz_generated.openapi.go index e6303fc88..78248352b 100644 --- a/pkg/apis/integreatly/v1alpha1/zz_generated.openapi.go +++ b/pkg/apis/integreatly/v1alpha1/zz_generated.openapi.go @@ -11,18 +11,24 @@ import ( func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition { return map[string]common.OpenAPIDefinition{ - "./pkg/apis/integreatly/v1alpha1.BlobStorage": schema_pkg_apis_integreatly_v1alpha1_BlobStorage(ref), - "./pkg/apis/integreatly/v1alpha1.BlobStorageSpec": schema_pkg_apis_integreatly_v1alpha1_BlobStorageSpec(ref), - "./pkg/apis/integreatly/v1alpha1.BlobStorageStatus": schema_pkg_apis_integreatly_v1alpha1_BlobStorageStatus(ref), - "./pkg/apis/integreatly/v1alpha1.Postgres": schema_pkg_apis_integreatly_v1alpha1_Postgres(ref), - "./pkg/apis/integreatly/v1alpha1.PostgresSpec": schema_pkg_apis_integreatly_v1alpha1_PostgresSpec(ref), - "./pkg/apis/integreatly/v1alpha1.PostgresStatus": schema_pkg_apis_integreatly_v1alpha1_PostgresStatus(ref), - "./pkg/apis/integreatly/v1alpha1.Redis": schema_pkg_apis_integreatly_v1alpha1_Redis(ref), - "./pkg/apis/integreatly/v1alpha1.RedisSpec": schema_pkg_apis_integreatly_v1alpha1_RedisSpec(ref), - "./pkg/apis/integreatly/v1alpha1.RedisStatus": schema_pkg_apis_integreatly_v1alpha1_RedisStatus(ref), - "./pkg/apis/integreatly/v1alpha1.SMTPCredentialSet": schema_pkg_apis_integreatly_v1alpha1_SMTPCredentialSet(ref), - "./pkg/apis/integreatly/v1alpha1.SMTPCredentialSetSpec": schema_pkg_apis_integreatly_v1alpha1_SMTPCredentialSetSpec(ref), - "./pkg/apis/integreatly/v1alpha1.SMTPCredentialSetStatus": schema_pkg_apis_integreatly_v1alpha1_SMTPCredentialSetStatus(ref), + "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.BlobStorage": schema_pkg_apis_integreatly_v1alpha1_BlobStorage(ref), + "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.BlobStorageSpec": schema_pkg_apis_integreatly_v1alpha1_BlobStorageSpec(ref), + "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.BlobStorageStatus": schema_pkg_apis_integreatly_v1alpha1_BlobStorageStatus(ref), + "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.Postgres": schema_pkg_apis_integreatly_v1alpha1_Postgres(ref), + "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.PostgresSnapshot": schema_pkg_apis_integreatly_v1alpha1_PostgresSnapshot(ref), + "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.PostgresSnapshotSpec": schema_pkg_apis_integreatly_v1alpha1_PostgresSnapshotSpec(ref), + "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.PostgresSnapshotStatus": schema_pkg_apis_integreatly_v1alpha1_PostgresSnapshotStatus(ref), + "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.PostgresSpec": schema_pkg_apis_integreatly_v1alpha1_PostgresSpec(ref), + "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.PostgresStatus": schema_pkg_apis_integreatly_v1alpha1_PostgresStatus(ref), + "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.Redis": schema_pkg_apis_integreatly_v1alpha1_Redis(ref), + "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.RedisSnapshot": schema_pkg_apis_integreatly_v1alpha1_RedisSnapshot(ref), + "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.RedisSnapshotSpec": schema_pkg_apis_integreatly_v1alpha1_RedisSnapshotSpec(ref), + "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.RedisSnapshotStatus": schema_pkg_apis_integreatly_v1alpha1_RedisSnapshotStatus(ref), + "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.RedisSpec": schema_pkg_apis_integreatly_v1alpha1_RedisSpec(ref), + "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.RedisStatus": schema_pkg_apis_integreatly_v1alpha1_RedisStatus(ref), + "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.SMTPCredentialSet": schema_pkg_apis_integreatly_v1alpha1_SMTPCredentialSet(ref), + "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.SMTPCredentialSetSpec": schema_pkg_apis_integreatly_v1alpha1_SMTPCredentialSetSpec(ref), + "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.SMTPCredentialSetStatus": schema_pkg_apis_integreatly_v1alpha1_SMTPCredentialSetStatus(ref), } } @@ -53,19 +59,19 @@ func schema_pkg_apis_integreatly_v1alpha1_BlobStorage(ref common.ReferenceCallba }, "spec": { SchemaProps: spec.SchemaProps{ - Ref: ref("./pkg/apis/integreatly/v1alpha1.BlobStorageSpec"), + Ref: ref("github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.BlobStorageSpec"), }, }, "status": { SchemaProps: spec.SchemaProps{ - Ref: ref("./pkg/apis/integreatly/v1alpha1.BlobStorageStatus"), + Ref: ref("github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.BlobStorageStatus"), }, }, }, }, }, Dependencies: []string{ - "./pkg/apis/integreatly/v1alpha1.BlobStorageSpec", "./pkg/apis/integreatly/v1alpha1.BlobStorageStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.BlobStorageSpec", "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.BlobStorageStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, } } @@ -177,19 +183,108 @@ func schema_pkg_apis_integreatly_v1alpha1_Postgres(ref common.ReferenceCallback) }, "spec": { SchemaProps: spec.SchemaProps{ - Ref: ref("./pkg/apis/integreatly/v1alpha1.PostgresSpec"), + Ref: ref("github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.PostgresSpec"), }, }, "status": { SchemaProps: spec.SchemaProps{ - Ref: ref("./pkg/apis/integreatly/v1alpha1.PostgresStatus"), + Ref: ref("github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.PostgresStatus"), }, }, }, }, }, Dependencies: []string{ - "./pkg/apis/integreatly/v1alpha1.PostgresSpec", "./pkg/apis/integreatly/v1alpha1.PostgresStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.PostgresSpec", "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.PostgresStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + +func schema_pkg_apis_integreatly_v1alpha1_PostgresSnapshot(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "PostgresSnapshot is the Schema for the postgressnapshots API", + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + 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/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + 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/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + }, + }, + "spec": { + SchemaProps: spec.SchemaProps{ + Ref: ref("github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.PostgresSnapshotSpec"), + }, + }, + "status": { + SchemaProps: spec.SchemaProps{ + Ref: ref("github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.PostgresSnapshotStatus"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.PostgresSnapshotSpec", "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.PostgresSnapshotStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + +func schema_pkg_apis_integreatly_v1alpha1_PostgresSnapshotSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "PostgresSnapshotSpec defines the desired state of PostgresSnapshot", + Properties: map[string]spec.Schema{ + "resourceName": { + SchemaProps: spec.SchemaProps{ + Description: "INSERT ADDITIONAL SPEC FIELDS - desired state of cluster Important: Run \"operator-sdk generate k8s\" to regenerate code after modifying this file Add custom validation using kubebuilder tags: https://book-v1.book.kubebuilder.io/beyond_basics/generating_crd.html", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"resourceName"}, + }, + }, + Dependencies: []string{}, + } +} + +func schema_pkg_apis_integreatly_v1alpha1_PostgresSnapshotStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "PostgresSnapshotStatus defines the observed state of PostgresSnapshot", + Properties: map[string]spec.Schema{ + "phase": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + "message": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + Dependencies: []string{}, } } @@ -301,19 +396,108 @@ func schema_pkg_apis_integreatly_v1alpha1_Redis(ref common.ReferenceCallback) co }, "spec": { SchemaProps: spec.SchemaProps{ - Ref: ref("./pkg/apis/integreatly/v1alpha1.RedisSpec"), + Ref: ref("github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.RedisSpec"), }, }, "status": { SchemaProps: spec.SchemaProps{ - Ref: ref("./pkg/apis/integreatly/v1alpha1.RedisStatus"), + Ref: ref("github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.RedisStatus"), }, }, }, }, }, Dependencies: []string{ - "./pkg/apis/integreatly/v1alpha1.RedisSpec", "./pkg/apis/integreatly/v1alpha1.RedisStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.RedisSpec", "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.RedisStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + +func schema_pkg_apis_integreatly_v1alpha1_RedisSnapshot(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "RedisSnapshot is the Schema for the redissnapshots API", + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + 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/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + 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/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + }, + }, + "spec": { + SchemaProps: spec.SchemaProps{ + Ref: ref("github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.RedisSnapshotSpec"), + }, + }, + "status": { + SchemaProps: spec.SchemaProps{ + Ref: ref("github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.RedisSnapshotStatus"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.RedisSnapshotSpec", "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.RedisSnapshotStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + +func schema_pkg_apis_integreatly_v1alpha1_RedisSnapshotSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "RedisSnapshotSpec defines the desired state of RedisSnapshot", + Properties: map[string]spec.Schema{ + "resourceName": { + SchemaProps: spec.SchemaProps{ + Description: "INSERT ADDITIONAL SPEC FIELDS - desired state of cluster Important: Run \"operator-sdk generate k8s\" to regenerate code after modifying this file Add custom validation using kubebuilder tags: https://book-v1.book.kubebuilder.io/beyond_basics/generating_crd.html", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"resourceName"}, + }, + }, + Dependencies: []string{}, + } +} + +func schema_pkg_apis_integreatly_v1alpha1_RedisSnapshotStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "RedisSnapshotStatus defines the observed state of RedisSnapshot", + Properties: map[string]spec.Schema{ + "phase": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + "message": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + Dependencies: []string{}, } } @@ -425,19 +609,19 @@ func schema_pkg_apis_integreatly_v1alpha1_SMTPCredentialSet(ref common.Reference }, "spec": { SchemaProps: spec.SchemaProps{ - Ref: ref("./pkg/apis/integreatly/v1alpha1.SMTPCredentialSetSpec"), + Ref: ref("github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.SMTPCredentialSetSpec"), }, }, "status": { SchemaProps: spec.SchemaProps{ - Ref: ref("./pkg/apis/integreatly/v1alpha1.SMTPCredentialSetStatus"), + Ref: ref("github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.SMTPCredentialSetStatus"), }, }, }, }, }, Dependencies: []string{ - "./pkg/apis/integreatly/v1alpha1.SMTPCredentialSetSpec", "./pkg/apis/integreatly/v1alpha1.SMTPCredentialSetStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.SMTPCredentialSetSpec", "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1.SMTPCredentialSetStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, } } diff --git a/pkg/controller/add_postgressnapshot.go b/pkg/controller/add_postgressnapshot.go new file mode 100644 index 000000000..41c23bc35 --- /dev/null +++ b/pkg/controller/add_postgressnapshot.go @@ -0,0 +1,10 @@ +package controller + +import ( + "github.com/integr8ly/cloud-resource-operator/pkg/controller/postgressnapshot" +) + +func init() { + // AddToManagerFuncs is a list of functions to create controllers and add them to a manager. + AddToManagerFuncs = append(AddToManagerFuncs, postgressnapshot.Add) +} diff --git a/pkg/controller/add_redissnapshot.go b/pkg/controller/add_redissnapshot.go new file mode 100644 index 000000000..1ffe4dd0b --- /dev/null +++ b/pkg/controller/add_redissnapshot.go @@ -0,0 +1,10 @@ +package controller + +import ( + "github.com/integr8ly/cloud-resource-operator/pkg/controller/redissnapshot" +) + +func init() { + // AddToManagerFuncs is a list of functions to create controllers and add them to a manager. + AddToManagerFuncs = append(AddToManagerFuncs, redissnapshot.Add) +} diff --git a/pkg/controller/postgressnapshot/postgressnapshot_controller.go b/pkg/controller/postgressnapshot/postgressnapshot_controller.go new file mode 100644 index 000000000..d724e3b4e --- /dev/null +++ b/pkg/controller/postgressnapshot/postgressnapshot_controller.go @@ -0,0 +1,233 @@ +package postgressnapshot + +import ( + "context" + "fmt" + "github.com/aws/aws-sdk-go/service/rds/rdsiface" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/credentials" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/rds" + + "github.com/integr8ly/cloud-resource-operator/pkg/providers" + + integreatlyv1alpha1 "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1" + croType "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1/types" + croAws "github.com/integr8ly/cloud-resource-operator/pkg/providers/aws" + "github.com/integr8ly/cloud-resource-operator/pkg/resources" + errorUtil "github.com/pkg/errors" + "github.com/sirupsen/logrus" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/manager" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + "sigs.k8s.io/controller-runtime/pkg/source" +) + +// Add creates a new PostgresSnapshot Controller and adds it to the Manager. The Manager will set fields on the Controller +// and Start it when the Manager is Started. +func Add(mgr manager.Manager) error { + return add(mgr, newReconciler(mgr)) +} + +// newReconciler returns a new reconcile.Reconciler +func newReconciler(mgr manager.Manager) reconcile.Reconciler { + logger := logrus.WithFields(logrus.Fields{"controller": "controller_postgres_snapshot"}) + return &ReconcilePostgresSnapshot{ + client: mgr.GetClient(), + scheme: mgr.GetScheme(), + logger: logger, + ConfigManager: croAws.NewDefaultConfigMapConfigManager(mgr.GetClient()), + CredentialManager: croAws.NewCredentialMinterCredentialManager(mgr.GetClient()), + } +} + +// add adds a new Controller to mgr with r as the reconcile.Reconciler +func add(mgr manager.Manager, r reconcile.Reconciler) error { + // Create a new controller + c, err := controller.New("postgressnapshot-controller", mgr, controller.Options{Reconciler: r}) + if err != nil { + return err + } + + // Watch for changes to primary resource PostgresSnapshot + err = c.Watch(&source.Kind{Type: &integreatlyv1alpha1.PostgresSnapshot{}}, &handler.EnqueueRequestForObject{}) + if err != nil { + return err + } + + // Watch for changes to secondary resource Pods and requeue the owner PostgresSnapshot + err = c.Watch(&source.Kind{Type: &corev1.Pod{}}, &handler.EnqueueRequestForOwner{ + IsController: true, + OwnerType: &integreatlyv1alpha1.PostgresSnapshot{}, + }) + if err != nil { + return err + } + + return nil +} + +// blank assignment to verify that ReconcilePostgresSnapshot implements reconcile.Reconciler +var _ reconcile.Reconciler = &ReconcilePostgresSnapshot{} + +// ReconcilePostgresSnapshot reconciles a PostgresSnapshot object +type ReconcilePostgresSnapshot struct { + // This client, initialized using mgr.Client() above, is a split client + // that reads objects from the cache and writes to the apiserver + client client.Client + scheme *runtime.Scheme + logger *logrus.Entry + ConfigManager croAws.ConfigManager + CredentialManager croAws.CredentialManager +} + +// Reconcile reads that state of the cluster for a PostgresSnapshot object and makes changes based on the state read +// and what is in the PostgresSnapshot.Spec +func (r *ReconcilePostgresSnapshot) Reconcile(request reconcile.Request) (reconcile.Result, error) { + r.logger.Info("reconciling postgres snapshot") + ctx := context.TODO() + + // Fetch the PostgresSnapshot instance + instance := &integreatlyv1alpha1.PostgresSnapshot{} + err := r.client.Get(ctx, request.NamespacedName, instance) + if err != nil { + if errors.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 + return reconcile.Result{}, nil + } + // Error reading the object - requeue the request. + return reconcile.Result{}, err + } + + // check status, if complete return + if instance.Status.Phase == croType.PhaseComplete { + r.logger.Infof("skipping creation of snapshot for %s as phase is complete", instance.Name) + return reconcile.Result{Requeue: true, RequeueAfter: resources.SuccessReconcileTime}, nil + } + + // get postgres cr + postgresCr := &integreatlyv1alpha1.Postgres{} + err = r.client.Get(ctx, types.NamespacedName{Name: instance.Spec.ResourceName, Namespace: instance.Namespace}, postgresCr) + if err != nil { + errMsg := fmt.Sprintf("failed to get postgres resource: %s", err.Error()) + if updateErr := resources.UpdateSnapshotPhase(ctx, r.client, instance, croType.PhaseFailed, croType.StatusMessage(errMsg)); updateErr != nil { + return reconcile.Result{Requeue: true, RequeueAfter: resources.ErrorReconcileTime}, updateErr + } + return reconcile.Result{Requeue: true, RequeueAfter: resources.ErrorReconcileTime}, errorUtil.New(errMsg) + } + + // check postgres deployment strategy is aws + if postgresCr.Status.Strategy != providers.AWSDeploymentStrategy { + errMsg := fmt.Sprintf("the resource %s uses an unsupported provider strategy %s, only resources using the aws provider are valid", instance.Spec.ResourceName, postgresCr.Status.Strategy) + if updateErr := resources.UpdateSnapshotPhase(ctx, r.client, instance, croType.PhaseFailed, croType.StatusMessage(errMsg)); updateErr != nil { + return reconcile.Result{Requeue: true, RequeueAfter: resources.ErrorReconcileTime}, updateErr + } + return reconcile.Result{Requeue: true, RequeueAfter: resources.ErrorReconcileTime}, errorUtil.New(errMsg) + } + + // get resource region + stratCfg, err := r.ConfigManager.ReadStorageStrategy(ctx, providers.PostgresResourceType, postgresCr.Spec.Tier) + if err != nil { + if updateErr := resources.UpdateSnapshotPhase(ctx, r.client, instance, croType.PhaseFailed, croType.StatusMessage(err.Error())); updateErr != nil { + return reconcile.Result{Requeue: true, RequeueAfter: resources.ErrorReconcileTime}, updateErr + } + return reconcile.Result{Requeue: true, RequeueAfter: resources.ErrorReconcileTime}, err + } + if stratCfg.Region == "" { + stratCfg.Region = croAws.DefaultRegion + } + + // create the credentials to be used by the aws resource providers, not to be used by end-user + providerCreds, err := r.CredentialManager.ReconcileProviderCredentials(ctx, postgresCr.Namespace) + if err != nil { + errMsg := "failed to reconcile rds credentials" + if updateErr := resources.UpdateSnapshotPhase(ctx, r.client, instance, croType.PhaseFailed, croType.StatusMessage(errMsg)); updateErr != nil { + return reconcile.Result{Requeue: true, RequeueAfter: resources.ErrorReconcileTime}, updateErr + } + return reconcile.Result{Requeue: true, RequeueAfter: resources.ErrorReconcileTime}, errorUtil.Wrap(err, errMsg) + } + + // setup aws rds session + rdsSvc := rds.New(session.Must(session.NewSession(&aws.Config{ + Region: aws.String(stratCfg.Region), + Credentials: credentials.NewStaticCredentials(providerCreds.AccessKeyID, providerCreds.SecretAccessKey, ""), + }))) + + // create the snapshot and return the phase + phase, msg, err := r.createSnapshot(ctx, rdsSvc, instance, postgresCr) + if updateErr := resources.UpdateSnapshotPhase(ctx, r.client, instance, phase, msg); updateErr != nil { + return reconcile.Result{Requeue: true, RequeueAfter: resources.ErrorReconcileTime}, updateErr + } + if err != nil { + return reconcile.Result{Requeue: true, RequeueAfter: resources.ErrorReconcileTime}, err + } + return reconcile.Result{Requeue: true, RequeueAfter: resources.SuccessReconcileTime}, nil +} + +func (r *ReconcilePostgresSnapshot) createSnapshot(ctx context.Context, rdsSvc rdsiface.RDSAPI, snapshot *integreatlyv1alpha1.PostgresSnapshot, postgres *integreatlyv1alpha1.Postgres) (croType.StatusPhase, croType.StatusMessage, error) { + // generate snapshot name + snapshotName, err := croAws.BuildTimestampedInfraNameFromObjectCreation(ctx, r.client, snapshot.ObjectMeta, croAws.DefaultAwsIdentifierLength) + if err != nil { + errMsg := "failed to generate snapshot name" + return croType.PhaseFailed, croType.StatusMessage(errMsg), errorUtil.Wrap(err, errMsg) + } + + // update cr with snapshot name + snapshot.Status.SnapshotID = snapshotName + if err = r.client.Status().Update(ctx, snapshot); err != nil { + errMsg := fmt.Sprintf("failed to update instance %s in namespace %s", snapshot.Name, snapshot.Namespace) + return croType.PhaseFailed, croType.StatusMessage(errMsg), errorUtil.Wrap(err, errMsg) + } + + // get instance name + instanceName, err := croAws.BuildInfraNameFromObject(ctx, r.client, postgres.ObjectMeta, croAws.DefaultAwsIdentifierLength) + if err != nil { + errMsg := "failed to get cluster name" + return croType.PhaseFailed, croType.StatusMessage(errMsg), errorUtil.Wrap(err, errMsg) + } + + // check snapshot exists + listOutput, err := rdsSvc.DescribeDBSnapshots(&rds.DescribeDBSnapshotsInput{ + DBSnapshotIdentifier: aws.String(snapshotName), + }) + var foundSnapshot *rds.DBSnapshot + for _, c := range listOutput.DBSnapshots { + if *c.DBSnapshotIdentifier == snapshotName { + foundSnapshot = c + break + } + } + + // create snapshot of the rds instance + if foundSnapshot == nil { + r.logger.Info("creating rds snapshot") + _, err = rdsSvc.CreateDBSnapshot(&rds.CreateDBSnapshotInput{ + DBInstanceIdentifier: aws.String(instanceName), + DBSnapshotIdentifier: aws.String(snapshotName), + }) + if err != nil { + errMsg := "error creating rds snapshot" + return croType.PhaseFailed, croType.StatusMessage(fmt.Sprintf("error creating rds snapshot %s", errMsg)), errorUtil.Wrap(err, errMsg) + } + return croType.PhaseInProgress, "snapshot started", nil + } + + // if snapshot status complete update status + if *foundSnapshot.Status == "available" { + return croType.PhaseComplete, "snapshot created", nil + } + + // creation in progress + msg := fmt.Sprintf("current snapshot status : %s", *foundSnapshot.Status) + r.logger.Info(msg) + return croType.PhaseInProgress, croType.StatusMessage(msg), nil +} diff --git a/pkg/controller/postgressnapshot/postgressnapshot_controller_test.go b/pkg/controller/postgressnapshot/postgressnapshot_controller_test.go new file mode 100644 index 000000000..1a6786fef --- /dev/null +++ b/pkg/controller/postgressnapshot/postgressnapshot_controller_test.go @@ -0,0 +1,211 @@ +package postgressnapshot + +import ( + "context" + "testing" + + v12 "github.com/openshift/api/config/v1" + controllerruntime "sigs.k8s.io/controller-runtime" + + "github.com/integr8ly/cloud-resource-operator/pkg/apis" + v1 "k8s.io/api/core/v1" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/rds" + + "github.com/aws/aws-sdk-go/service/rds/rdsiface" + integreatlyv1alpha1 "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1" + "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1/types" + croAws "github.com/integr8ly/cloud-resource-operator/pkg/providers/aws" + "github.com/sirupsen/logrus" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" +) + +var testLogger = logrus.WithFields(logrus.Fields{"testing": "true"}) + +type mockRdsClient struct { + rdsiface.RDSAPI + wantErrList bool + wantErrCreate bool + wantErrDelete bool + dbSnapshots []*rds.DBSnapshot + dbSnapshot *rds.DBSnapshot +} + +func buildTestScheme() (*runtime.Scheme, error) { + scheme := runtime.NewScheme() + err := v1.AddToScheme(scheme) + err = apis.AddToScheme(scheme) + if err != nil { + return nil, err + } + return scheme, nil +} + +func buildPostgresSnapshot() *integreatlyv1alpha1.PostgresSnapshot { + return &integreatlyv1alpha1.PostgresSnapshot{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "test", + }, + } +} + +func buildPostgres() *integreatlyv1alpha1.Postgres { + return &integreatlyv1alpha1.Postgres{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "test", + }, + } +} + +func buildTestInfrastructure() *v12.Infrastructure { + return &v12.Infrastructure{ + ObjectMeta: controllerruntime.ObjectMeta{ + Name: "cluster", + }, + Status: v12.InfrastructureStatus{ + InfrastructureName: "test", + }, + } +} + +func buildSnapshot() *rds.DBSnapshot { + return &rds.DBSnapshot{ + DBInstanceIdentifier: aws.String("rds-db"), + } +} + +func buildSnapshots(snapshotName string, snapshotStatus string) []*rds.DBSnapshot { + return []*rds.DBSnapshot{ + { + DBSnapshotIdentifier: aws.String(snapshotName), + Status: aws.String(snapshotStatus), + }, + } +} + +func (m *mockRdsClient) DescribeDBSnapshots(*rds.DescribeDBSnapshotsInput) (*rds.DescribeDBSnapshotsOutput, error) { + return &rds.DescribeDBSnapshotsOutput{ + DBSnapshots: m.dbSnapshots, + }, nil +} + +func (m *mockRdsClient) CreateDBSnapshot(*rds.CreateDBSnapshotInput) (*rds.CreateDBSnapshotOutput, error) { + return &rds.CreateDBSnapshotOutput{ + DBSnapshot: m.dbSnapshot, + }, nil +} + +func TestReconcilePostgresSnapshot_createSnapshot(t *testing.T) { + ctx := context.TODO() + scheme, err := buildTestScheme() + if err != nil { + logrus.Fatal(err) + t.Fatal("failed to build scheme", err) + } + snapshotName, err := croAws.BuildTimestampedInfraNameFromObjectCreation(ctx, fake.NewFakeClientWithScheme(scheme, buildTestInfrastructure(), buildPostgresSnapshot()), buildPostgresSnapshot().ObjectMeta, croAws.DefaultAwsIdentifierLength) + if err != nil { + logrus.Fatal(err) + t.Fatal("failed to build scheme", err) + } + type fields struct { + client client.Client + scheme *runtime.Scheme + logger *logrus.Entry + ConfigManager croAws.ConfigManager + CredentialManager croAws.CredentialManager + } + type args struct { + ctx context.Context + rdsSvc rdsiface.RDSAPI + snapshot *integreatlyv1alpha1.PostgresSnapshot + postgres *integreatlyv1alpha1.Postgres + } + tests := []struct { + name string + fields fields + args args + want types.StatusPhase + wantErr bool + }{ + { + name: "test successful snapshot started", + args: args{ + ctx: ctx, + rdsSvc: &mockRdsClient{dbSnapshot: buildSnapshot()}, + snapshot: buildPostgresSnapshot(), + postgres: buildPostgres(), + }, + fields: fields{ + client: fake.NewFakeClientWithScheme(scheme, buildTestInfrastructure(), buildPostgresSnapshot()), + scheme: scheme, + logger: testLogger, + CredentialManager: nil, + ConfigManager: nil, + }, + want: types.PhaseInProgress, + wantErr: false, + }, + { + name: "test successful snapshot created", + args: args{ + ctx: ctx, + rdsSvc: &mockRdsClient{dbSnapshot: buildSnapshot(), dbSnapshots: buildSnapshots(snapshotName, "available")}, + snapshot: buildPostgresSnapshot(), + postgres: buildPostgres(), + }, + fields: fields{ + client: fake.NewFakeClientWithScheme(scheme, buildTestInfrastructure(), buildPostgresSnapshot()), + scheme: scheme, + logger: testLogger, + ConfigManager: nil, + CredentialManager: nil, + }, + want: types.PhaseComplete, + wantErr: false, + }, + { + name: "test successful snapshot in progress", + args: args{ + ctx: ctx, + rdsSvc: &mockRdsClient{dbSnapshot: buildSnapshot(), dbSnapshots: buildSnapshots(snapshotName, "creating")}, + snapshot: buildPostgresSnapshot(), + postgres: buildPostgres(), + }, + fields: fields{ + client: fake.NewFakeClientWithScheme(scheme, buildTestInfrastructure(), buildPostgresSnapshot()), + scheme: scheme, + logger: testLogger, + ConfigManager: nil, + CredentialManager: nil, + }, + want: types.PhaseInProgress, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := &ReconcilePostgresSnapshot{ + client: tt.fields.client, + scheme: tt.fields.scheme, + logger: tt.fields.logger, + ConfigManager: tt.fields.ConfigManager, + CredentialManager: tt.fields.CredentialManager, + } + got, _, err := r.createSnapshot(tt.args.ctx, tt.args.rdsSvc, tt.args.snapshot, tt.args.postgres) + if (err != nil) != tt.wantErr { + t.Errorf("createSnapshot() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("createSnapshot() got = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/controller/redissnapshot/redissnapshot_controller.go b/pkg/controller/redissnapshot/redissnapshot_controller.go new file mode 100644 index 000000000..c2e9885ba --- /dev/null +++ b/pkg/controller/redissnapshot/redissnapshot_controller.go @@ -0,0 +1,257 @@ +package redissnapshot + +import ( + "context" + "fmt" + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/credentials" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/elasticache" + "github.com/aws/aws-sdk-go/service/elasticache/elasticacheiface" + croType "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1/types" + "github.com/integr8ly/cloud-resource-operator/pkg/providers" + croAws "github.com/integr8ly/cloud-resource-operator/pkg/providers/aws" + "github.com/integr8ly/cloud-resource-operator/pkg/resources" + "github.com/sirupsen/logrus" + "k8s.io/apimachinery/pkg/types" + + integreatlyv1alpha1 "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1" + errorUtil "github.com/pkg/errors" + corev1 "k8s.io/api/core/v1" + + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/manager" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + "sigs.k8s.io/controller-runtime/pkg/source" +) + +// Add creates a new RedisSnapshot Controller and adds it to the Manager. The Manager will set fields on the Controller +// and Start it when the Manager is Started. +func Add(mgr manager.Manager) error { + return add(mgr, newReconciler(mgr)) +} + +// newReconciler returns a new reconcile.Reconciler +func newReconciler(mgr manager.Manager) reconcile.Reconciler { + logger := logrus.WithFields(logrus.Fields{"controller": "controller_redis_snapshot"}) + return &ReconcileRedisSnapshot{ + client: mgr.GetClient(), + scheme: mgr.GetScheme(), + logger: logger, + ConfigManager: croAws.NewDefaultConfigMapConfigManager(mgr.GetClient()), + CredentialManager: croAws.NewCredentialMinterCredentialManager(mgr.GetClient()), + } +} + +// add adds a new Controller to mgr with r as the reconcile.Reconciler +func add(mgr manager.Manager, r reconcile.Reconciler) error { + // Create a new controller + c, err := controller.New("redissnapshot-controller", mgr, controller.Options{Reconciler: r}) + if err != nil { + return err + } + + // Watch for changes to primary resource RedisSnapshot + err = c.Watch(&source.Kind{Type: &integreatlyv1alpha1.RedisSnapshot{}}, &handler.EnqueueRequestForObject{}) + if err != nil { + return err + } + + // Watch for changes to secondary resource Pods and requeue the owner RedisSnapshot + err = c.Watch(&source.Kind{Type: &corev1.Pod{}}, &handler.EnqueueRequestForOwner{ + IsController: true, + OwnerType: &integreatlyv1alpha1.RedisSnapshot{}, + }) + if err != nil { + return err + } + + return nil +} + +// blank assignment to verify that ReconcileRedisSnapshot implements reconcile.Reconciler +var _ reconcile.Reconciler = &ReconcileRedisSnapshot{} + +// ReconcileRedisSnapshot reconciles a RedisSnapshot object +type ReconcileRedisSnapshot struct { + // This client, initialized using mgr.Client() above, is a split client + // that reads objects from the cache and writes to the apiserver + client client.Client + scheme *runtime.Scheme + logger *logrus.Entry + ConfigManager croAws.ConfigManager + CredentialManager croAws.CredentialManager +} + +// Reconcile reads that state of the cluster for a RedisSnapshot object and makes changes based on the state read +// and what is in the RedisSnapshot.Spec +// The Controller will requeue the Request to be processed again if the returned error is non-nil or +// Result.Requeue is true, otherwise upon completion it will remove the work from the queue. +func (r *ReconcileRedisSnapshot) Reconcile(request reconcile.Request) (reconcile.Result, error) { + r.logger.Info("reconciling redis snapshot") + ctx := context.TODO() + + // Fetch the RedisSnapshot instance + instance := &integreatlyv1alpha1.RedisSnapshot{} + err := r.client.Get(ctx, request.NamespacedName, instance) + if err != nil { + if errors.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 + return reconcile.Result{}, nil + } + // Error reading the object - requeue the request. + return reconcile.Result{}, err + } + + // check status, if complete return + if instance.Status.Phase == croType.PhaseComplete { + r.logger.Infof("skipping creation of snapshot for %s as phase is complete", instance.Name) + return reconcile.Result{Requeue: true, RequeueAfter: resources.SuccessReconcileTime}, nil + } + + // get redis cr + redisCr := &integreatlyv1alpha1.Redis{} + err = r.client.Get(ctx, types.NamespacedName{Name: instance.Spec.ResourceName, Namespace: instance.Namespace}, redisCr) + if err != nil { + errMsg := fmt.Sprintf("failed to get redis cr : %s", err.Error()) + if updateErr := resources.UpdateSnapshotPhase(ctx, r.client, instance, croType.PhaseFailed, croType.StatusMessage(errMsg)); updateErr != nil { + return reconcile.Result{Requeue: true, RequeueAfter: resources.ErrorReconcileTime}, updateErr + } + return reconcile.Result{Requeue: true, RequeueAfter: resources.ErrorReconcileTime}, errorUtil.New(errMsg) + } + + // check redis cr deployment type is aws + if redisCr.Status.Strategy != providers.AWSDeploymentStrategy { + errMsg := fmt.Sprintf("the resource %s uses an unsupported provider strategy %s, only resources using the aws provider are valid", instance.Spec.ResourceName, redisCr.Status.Strategy) + if updateErr := resources.UpdateSnapshotPhase(ctx, r.client, instance, croType.PhaseFailed, croType.StatusMessage(errMsg)); updateErr != nil { + return reconcile.Result{Requeue: true, RequeueAfter: resources.ErrorReconcileTime}, updateErr + } + return reconcile.Result{Requeue: true, RequeueAfter: resources.ErrorReconcileTime}, errorUtil.New(errMsg) + } + + // get resource region + stratCfg, err := r.ConfigManager.ReadStorageStrategy(ctx, providers.RedisResourceType, redisCr.Spec.Tier) + if err != nil { + if updateErr := resources.UpdateSnapshotPhase(ctx, r.client, instance, croType.PhaseFailed, croType.StatusMessage(err.Error())); updateErr != nil { + return reconcile.Result{Requeue: true, RequeueAfter: resources.ErrorReconcileTime}, updateErr + } + return reconcile.Result{Requeue: true, RequeueAfter: resources.ErrorReconcileTime}, err + } + if stratCfg.Region == "" { + stratCfg.Region = croAws.DefaultRegion + } + + // create the credentials to be used by the aws resource providers, not to be used by end-user + providerCreds, err := r.CredentialManager.ReconcileProviderCredentials(ctx, redisCr.Namespace) + if err != nil { + errMsg := "failed to reconcile elasticache credentials" + if updateErr := resources.UpdateSnapshotPhase(ctx, r.client, instance, croType.PhaseFailed, croType.StatusMessage(errMsg)); updateErr != nil { + return reconcile.Result{Requeue: true, RequeueAfter: resources.ErrorReconcileTime}, updateErr + } + return reconcile.Result{Requeue: true, RequeueAfter: resources.ErrorReconcileTime}, errorUtil.Wrap(err, errMsg) + } + + // setup aws elasticache cluster sdk session + cacheSvc := elasticache.New(session.Must(session.NewSession(&aws.Config{ + Region: aws.String(stratCfg.Region), + Credentials: credentials.NewStaticCredentials(providerCreds.AccessKeyID, providerCreds.SecretAccessKey, ""), + }))) + + // create snapshot of primary node + phase, msg, err := r.createSnapshot(ctx, cacheSvc, instance, redisCr) + if updateErr := resources.UpdateSnapshotPhase(ctx, r.client, instance, phase, msg); updateErr != nil { + return reconcile.Result{Requeue: true, RequeueAfter: resources.ErrorReconcileTime}, updateErr + } + if err != nil { + return reconcile.Result{Requeue: true, RequeueAfter: resources.ErrorReconcileTime}, err + } + + return reconcile.Result{Requeue: true, RequeueAfter: resources.SuccessReconcileTime}, nil +} + +func (r *ReconcileRedisSnapshot) createSnapshot(ctx context.Context, cacheSvc elasticacheiface.ElastiCacheAPI, snapshot *integreatlyv1alpha1.RedisSnapshot, redis *integreatlyv1alpha1.Redis) (croType.StatusPhase, croType.StatusMessage, error) { + // generate snapshot name + snapshotName, err := croAws.BuildTimestampedInfraNameFromObjectCreation(ctx, r.client, snapshot.ObjectMeta, croAws.DefaultAwsIdentifierLength) + if err != nil { + errMsg := "failed to generate snapshot name" + return croType.PhaseFailed, croType.StatusMessage(errMsg), errorUtil.Wrap(err, errMsg) + } + + // update cr with snapshot name + snapshot.Status.SnapshotID = snapshotName + if err = r.client.Status().Update(ctx, snapshot); err != nil { + errMsg := fmt.Sprintf("failed to update instance %s in namespace %s", snapshot.Name, snapshot.Namespace) + return croType.PhaseFailed, croType.StatusMessage(errMsg), errorUtil.Wrap(err, errMsg) + } + + // generate cluster name + clusterName, err := croAws.BuildInfraNameFromObject(ctx, r.client, redis.ObjectMeta, croAws.DefaultAwsIdentifierLength) + if err != nil { + errMsg := "failed to get cluster name" + return croType.PhaseFailed, croType.StatusMessage(errMsg), errorUtil.Wrap(err, errMsg) + } + + // check snapshot exists + listOutput, err := cacheSvc.DescribeSnapshots(&elasticache.DescribeSnapshotsInput{ + SnapshotName: aws.String(snapshotName), + }) + var foundSnapshot *elasticache.Snapshot + for _, c := range listOutput.Snapshots { + if *c.SnapshotName == snapshotName { + foundSnapshot = c + break + } + } + + // get replication group + cacheOutput, err := cacheSvc.DescribeReplicationGroups(&elasticache.DescribeReplicationGroupsInput{ + ReplicationGroupId: aws.String(clusterName), + }) + + if cacheOutput == nil { + return croType.PhaseFailed, "snapshot failed, no replication group found", nil + } + + // ensure replication group is available + if *cacheOutput.ReplicationGroups[0].Status != "available" { + errMsg := fmt.Sprintf("current replication group status is %s", *cacheOutput.ReplicationGroups[0].Status) + return croType.PhaseFailed, croType.StatusMessage(errMsg), errorUtil.Wrap(err, errMsg) + } + + // find primary cache node + cacheName := "" + for _, i := range cacheOutput.ReplicationGroups[0].NodeGroups[0].NodeGroupMembers { + if *i.CurrentRole == "primary" { + cacheName = *i.CacheClusterId + break + } + } + + // create snapshot of primary cache node + if foundSnapshot == nil { + r.logger.Info("creating elasticache snapshot") + if _, err = cacheSvc.CreateSnapshot(&elasticache.CreateSnapshotInput{ + CacheClusterId: aws.String(cacheName), + SnapshotName: aws.String(snapshotName), + }); err != nil { + errMsg := fmt.Sprintf("error creating elasticache snapshot %s", err) + return croType.PhaseFailed, croType.StatusMessage(errMsg), errorUtil.Wrap(err, errMsg) + } + return croType.PhaseInProgress, "snapshot started", nil + } + + // if snapshot status complete update status + if *foundSnapshot.SnapshotStatus == "available" { + return croType.PhaseComplete, "snapshot created", nil + } + + msg := fmt.Sprintf("current snapshot status : %s", *foundSnapshot.SnapshotStatus) + r.logger.Info(msg) + return croType.PhaseInProgress, croType.StatusMessage(msg), nil +} diff --git a/pkg/controller/redissnapshot/redissnapshot_controller_test.go b/pkg/controller/redissnapshot/redissnapshot_controller_test.go new file mode 100644 index 000000000..c5284d32f --- /dev/null +++ b/pkg/controller/redissnapshot/redissnapshot_controller_test.go @@ -0,0 +1,240 @@ +package redissnapshot + +import ( + "context" + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/elasticache" + "github.com/aws/aws-sdk-go/service/elasticache/elasticacheiface" + "github.com/integr8ly/cloud-resource-operator/pkg/apis" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + + v12 "github.com/openshift/api/config/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + integreatlyv1alpha1 "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1" + "github.com/integr8ly/cloud-resource-operator/pkg/apis/integreatly/v1alpha1/types" + croAws "github.com/integr8ly/cloud-resource-operator/pkg/providers/aws" + "github.com/sirupsen/logrus" + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime" + controllerruntime "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "testing" +) + +var testLogger = logrus.WithFields(logrus.Fields{"testing": "true"}) + +type mockElasticacheClient struct { + elasticacheiface.ElastiCacheAPI + wantErrList bool + wantErrCreate bool + wantErrDelete bool + wantEmpty bool + repGroups []*elasticache.ReplicationGroup + rep *elasticache.ReplicationGroup + snapshots []*elasticache.Snapshot + nodeSnapshot *elasticache.Snapshot +} + +func buildTestScheme() (*runtime.Scheme, error) { + scheme := runtime.NewScheme() + err := v1.AddToScheme(scheme) + err = apis.AddToScheme(scheme) + if err != nil { + return nil, err + } + return scheme, nil +} + +func buildTestInfrastructure() *v12.Infrastructure { + return &v12.Infrastructure{ + ObjectMeta: controllerruntime.ObjectMeta{ + Name: "cluster", + }, + Status: v12.InfrastructureStatus{ + InfrastructureName: "test", + }, + } +} + +func buildSnapshot() *integreatlyv1alpha1.RedisSnapshot { + return &integreatlyv1alpha1.RedisSnapshot{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "test", + }, + } +} + +func buildRedisCR() *integreatlyv1alpha1.Redis { + return &integreatlyv1alpha1.Redis{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "test", + }, + } +} + +func buildAvailableReplicationGroup() *elasticache.ReplicationGroup { + return &elasticache.ReplicationGroup{ + ReplicationGroupId: aws.String("test"), + Status: aws.String("available"), + NodeGroups: []*elasticache.NodeGroup{ + { + NodeGroupId: aws.String("test"), + NodeGroupMembers: []*elasticache.NodeGroupMember{ + { + CacheClusterId: aws.String("test"), + CurrentRole: aws.String("primary"), + }, + }, + }, + }, + } +} + +func buildReplicationGroups() []*elasticache.ReplicationGroup { + var groups []*elasticache.ReplicationGroup + groups = append(groups, buildAvailableReplicationGroup()) + return groups +} + +func buildSnapshots(snapshotName string, snapshotStatus string) []*elasticache.Snapshot { + return []*elasticache.Snapshot{ + { + SnapshotName: aws.String(snapshotName), + SnapshotStatus: aws.String(snapshotStatus), + }, + } +} + +func (m *mockElasticacheClient) DescribeSnapshots(*elasticache.DescribeSnapshotsInput) (*elasticache.DescribeSnapshotsOutput, error) { + return &elasticache.DescribeSnapshotsOutput{ + Snapshots: m.snapshots, + }, nil +} + +func (m *mockElasticacheClient) DescribeReplicationGroups(*elasticache.DescribeReplicationGroupsInput) (*elasticache.DescribeReplicationGroupsOutput, error) { + return &elasticache.DescribeReplicationGroupsOutput{ + ReplicationGroups: m.repGroups, + }, nil +} + +func (m *mockElasticacheClient) CreateSnapshot(*elasticache.CreateSnapshotInput) (*elasticache.CreateSnapshotOutput, error) { + return &elasticache.CreateSnapshotOutput{}, nil +} + +func TestReconcileRedisSnapshot_createSnapshot(t *testing.T) { + ctx := context.TODO() + scheme, err := buildTestScheme() + if err != nil { + logrus.Fatal(err) + t.Fatal("failed to build scheme", err) + } + snapshotName, err := croAws.BuildTimestampedInfraNameFromObjectCreation(ctx, fake.NewFakeClientWithScheme(scheme, buildTestInfrastructure(), buildSnapshot()), buildSnapshot().ObjectMeta, croAws.DefaultAwsIdentifierLength) + if err != nil { + logrus.Fatal(err) + t.Fatal("failed to build snapshot name", err) + } + type fields struct { + client client.Client + scheme *runtime.Scheme + logger *logrus.Entry + ConfigManager croAws.ConfigManager + CredentialManager croAws.CredentialManager + } + type args struct { + ctx context.Context + cacheSvc elasticacheiface.ElastiCacheAPI + snapshot *integreatlyv1alpha1.RedisSnapshot + redis *integreatlyv1alpha1.Redis + } + tests := []struct { + name string + fields fields + args args + want types.StatusPhase + want1 types.StatusMessage + wantErr bool + }{ + { + name: "test successful snapshot started", + args: args{ + ctx: ctx, + cacheSvc: &mockElasticacheClient{repGroups: buildReplicationGroups()}, + snapshot: buildSnapshot(), + redis: buildRedisCR(), + }, + fields: fields{ + client: fake.NewFakeClientWithScheme(scheme, buildTestInfrastructure(), buildSnapshot()), + scheme: scheme, + logger: testLogger, + ConfigManager: nil, + CredentialManager: nil, + }, + want: types.PhaseInProgress, + want1: "snapshot started", + wantErr: false, + }, + { + name: "test successful snapshot created", + args: args{ + ctx: ctx, + cacheSvc: &mockElasticacheClient{repGroups: buildReplicationGroups(), snapshots: buildSnapshots(snapshotName, "available")}, + snapshot: buildSnapshot(), + redis: buildRedisCR(), + }, + fields: fields{ + client: fake.NewFakeClientWithScheme(scheme, buildTestInfrastructure(), buildSnapshot()), + scheme: scheme, + logger: testLogger, + ConfigManager: nil, + CredentialManager: nil, + }, + want: types.PhaseComplete, + want1: "snapshot created", + wantErr: false, + }, + { + name: "test creating snapshot in progress", + args: args{ + ctx: ctx, + cacheSvc: &mockElasticacheClient{repGroups: buildReplicationGroups(), snapshots: buildSnapshots(snapshotName, "creating")}, + snapshot: buildSnapshot(), + redis: buildRedisCR(), + }, + fields: fields{ + client: fake.NewFakeClientWithScheme(scheme, buildTestInfrastructure(), buildSnapshot()), + scheme: scheme, + logger: testLogger, + ConfigManager: nil, + CredentialManager: nil, + }, + want: types.PhaseInProgress, + want1: "current snapshot status : creating", + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := &ReconcileRedisSnapshot{ + client: tt.fields.client, + scheme: tt.fields.scheme, + logger: tt.fields.logger, + ConfigManager: tt.fields.ConfigManager, + CredentialManager: tt.fields.CredentialManager, + } + got, got1, err := r.createSnapshot(tt.args.ctx, tt.args.cacheSvc, tt.args.snapshot, tt.args.redis) + if (err != nil) != tt.wantErr { + t.Errorf("createSnapshot() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("createSnapshot() got = %v, want %v", got, tt.want) + } + if got1 != tt.want1 { + t.Errorf("createSnapshot() got1 = %v, want %v", got1, tt.want1) + } + }) + } +} diff --git a/pkg/providers/aws/config.go b/pkg/providers/aws/config.go index 8f98ab715..9bf72eda7 100644 --- a/pkg/providers/aws/config.go +++ b/pkg/providers/aws/config.go @@ -4,9 +4,10 @@ import ( "context" "encoding/json" "fmt" - "github.com/operator-framework/operator-sdk/pkg/k8sutil" "time" + "github.com/operator-framework/operator-sdk/pkg/k8sutil" + "github.com/integr8ly/cloud-resource-operator/pkg/resources" controllerruntime "sigs.k8s.io/controller-runtime" @@ -135,7 +136,7 @@ func (m *ConfigMapConfigManager) buildDefaultConfigMap() *v1.ConfigMap { } } -func buildInfraNameFromObject(ctx context.Context, c client.Client, om controllerruntime.ObjectMeta, n int) (string, error) { +func BuildInfraNameFromObject(ctx context.Context, c client.Client, om controllerruntime.ObjectMeta, n int) (string, error) { clusterId, err := resources.GetClusterId(ctx, c) if err != nil { return "", errorUtil.Wrap(err, "failed to retrieve cluster identifier") @@ -151,3 +152,11 @@ func buildTimestampedInfraNameFromObject(ctx context.Context, c client.Client, o curTime := time.Now().Unix() return resources.ShortenString(fmt.Sprintf("%s-%s-%s-%d", clusterId, om.Namespace, om.Name, curTime), n), nil } + +func BuildTimestampedInfraNameFromObjectCreation(ctx context.Context, c client.Client, om controllerruntime.ObjectMeta, n int) (string, error) { + clusterId, err := resources.GetClusterId(ctx, c) + if err != nil { + return "", errorUtil.Wrap(err, "failed to retrieve timestamped cluster identifier") + } + return resources.ShortenString(fmt.Sprintf("%s-%s-%s-%s", clusterId, om.Namespace, om.Name, om.GetObjectMeta().GetCreationTimestamp()), n), nil +} diff --git a/pkg/providers/aws/credentials.go b/pkg/providers/aws/credentials.go index fcebb2ce7..f110dcea8 100644 --- a/pkg/providers/aws/credentials.go +++ b/pkg/providers/aws/credentials.go @@ -45,12 +45,15 @@ var ( "elasticache:DescribeReplicationGroups", "elasticache:AddTagsToResource", "elasticache:DescribeSnapshots", + "elasticache:CreateSnapshot", + "elasticache:DescribeCacheClusters", "rds:DescribeDBInstances", "rds:CreateDBInstance", "rds:DeleteDBInstance", "rds:ModifyDBInstance", "rds:AddTagsToResource", "rds:DescribeDBSnapshots", + "rds:CreateDBSnapshot", "sts:GetCallerIdentity", }, Resource: "*", diff --git a/pkg/providers/aws/provider_blobstorage.go b/pkg/providers/aws/provider_blobstorage.go index 4f7d9090c..149425346 100644 --- a/pkg/providers/aws/provider_blobstorage.go +++ b/pkg/providers/aws/provider_blobstorage.go @@ -438,7 +438,7 @@ func (p *BlobStorageProvider) buildS3BucketConfig(ctx context.Context, bs *v1alp // cluster infra info p.Logger.Info("getting cluster id from infrastructure for bucket naming") - bucketName, err := buildInfraNameFromObject(ctx, p.Client, bs.ObjectMeta, defaultAwsBucketNameLength) + bucketName, err := BuildInfraNameFromObject(ctx, p.Client, bs.ObjectMeta, defaultAwsBucketNameLength) if err != nil { return nil, nil, nil, errorUtil.Wrapf(err, fmt.Sprintf("failed to retrieve aws s3 bucket config for blob storage instance %s", bs.Name)) } diff --git a/pkg/providers/aws/provider_postgres.go b/pkg/providers/aws/provider_postgres.go index ca38f1775..aa11a7f2a 100644 --- a/pkg/providers/aws/provider_postgres.go +++ b/pkg/providers/aws/provider_postgres.go @@ -36,7 +36,7 @@ import ( const ( postgresProviderName = "aws-rds" - defaultAwsIdentifierLength = 40 + DefaultAwsIdentifierLength = 40 // default create options defaultAwsPostgresDeletionProtection = true defaultAwsPostgresPort = 5432 @@ -503,7 +503,7 @@ func (p *AWSPostgresProvider) buildRDSCreateStrategy(ctx context.Context, pg *v1 rdsCreateConfig.EngineVersion = aws.String(defaultAwsEngineVersion) } } - instanceName, err := buildInfraNameFromObject(ctx, p.Client, pg.ObjectMeta, defaultAwsIdentifierLength) + instanceName, err := BuildInfraNameFromObject(ctx, p.Client, pg.ObjectMeta, DefaultAwsIdentifierLength) if err != nil { return errorUtil.Wrapf(err, "failed to retrieve rds config") } @@ -516,7 +516,7 @@ func (p *AWSPostgresProvider) buildRDSCreateStrategy(ctx context.Context, pg *v1 // verify postgres delete config func (p *AWSPostgresProvider) buildRDSDeleteConfig(ctx context.Context, pg *v1alpha1.Postgres, rdsCreateConfig *rds.CreateDBInstanceInput, rdsDeleteConfig *rds.DeleteDBInstanceInput) error { - instanceIdentifier, err := buildInfraNameFromObject(ctx, p.Client, pg.ObjectMeta, defaultAwsIdentifierLength) + instanceIdentifier, err := BuildInfraNameFromObject(ctx, p.Client, pg.ObjectMeta, DefaultAwsIdentifierLength) if err != nil { return errorUtil.Wrapf(err, "failed to retrieve rds config") } @@ -532,7 +532,7 @@ func (p *AWSPostgresProvider) buildRDSDeleteConfig(ctx context.Context, pg *v1al if rdsDeleteConfig.SkipFinalSnapshot == nil { rdsDeleteConfig.SkipFinalSnapshot = aws.Bool(defaultAwsSkipFinalSnapshot) } - snapshotIdentifier, err := buildTimestampedInfraNameFromObject(ctx, p.Client, pg.ObjectMeta, defaultAwsIdentifierLength) + snapshotIdentifier, err := buildTimestampedInfraNameFromObject(ctx, p.Client, pg.ObjectMeta, DefaultAwsIdentifierLength) if err != nil { return errorUtil.Wrap(err, "failed to retrieve timestamped rds config") } diff --git a/pkg/providers/aws/provider_postgres_test.go b/pkg/providers/aws/provider_postgres_test.go index 5c772c9eb..0c0f480af 100644 --- a/pkg/providers/aws/provider_postgres_test.go +++ b/pkg/providers/aws/provider_postgres_test.go @@ -2,7 +2,6 @@ package aws import ( "context" - "fmt" "github.com/integr8ly/cloud-resource-operator/pkg/providers" "reflect" "testing" @@ -265,8 +264,6 @@ func TestAWSPostgresProvider_createPostgresInstance(t *testing.T) { t.Errorf("createRDSInstance() error = %v, wantErr %v", err, tt.wantErr) return } - fmt.Println(got) - fmt.Println(tt.want) if tt.want != nil && !reflect.DeepEqual(got, tt.want) { t.Errorf("createRDSInstance() got = %+v, want %v", got.DeploymentDetails, tt.want) } diff --git a/pkg/providers/aws/provider_redis.go b/pkg/providers/aws/provider_redis.go index 906b1f49e..e047f24b7 100644 --- a/pkg/providers/aws/provider_redis.go +++ b/pkg/providers/aws/provider_redis.go @@ -164,6 +164,10 @@ func (p *AWSRedisProvider) createElasticacheCluster(ctx context.Context, r *v1al // add tags to cache nodes cacheInstance := *foundCache.NodeGroups[0] + if *cacheInstance.Status != "available" { + return nil, croType.StatusMessage(fmt.Sprintf("cache node status not available, current status: %s", *foundCache.Status)), nil + } + for _, cache := range cacheInstance.NodeGroupMembers { msg, err := p.TagElasticacheNode(ctx, cacheSvc, stsSvc, r, *stratCfg, cache) if err != nil { @@ -184,6 +188,21 @@ func (p *AWSRedisProvider) createElasticacheCluster(ctx context.Context, r *v1al func (p *AWSRedisProvider) TagElasticacheNode(ctx context.Context, cacheSvc elasticacheiface.ElastiCacheAPI, stsSvc stsiface.STSAPI, r *v1alpha1.Redis, stratCfg StrategyConfig, cache *elasticache.NodeGroupMember) (types.StatusMessage, error) { logrus.Info("creating or updating tags on elasticache nodes and snapshots") + // check the node to make sure it is available before applying the tag + // this is needed as the cluster may be available while a node is not + cacheClusterOutput, err := cacheSvc.DescribeCacheClusters(&elasticache.DescribeCacheClustersInput{ + CacheClusterId: cache.CacheClusterId, + }) + if err != nil { + errMsg := "failed to get cache cluster output" + return types.StatusMessage(errMsg), errorUtil.Wrap(err, errMsg) + } + clusterStatus := *cacheClusterOutput.CacheClusters[0].CacheClusterStatus + if clusterStatus != "available" { + errMsg := fmt.Sprintf("%s status is %s, skipping adding tags", *cache.CacheClusterId, clusterStatus) + return types.StatusMessage(errMsg), errorUtil.Wrap(err, errMsg) + } + // get account identity identityInput := &sts.GetCallerIdentityInput{} id, err := stsSvc.GetCallerIdentity(identityInput) @@ -257,7 +276,7 @@ func (p *AWSRedisProvider) TagElasticacheNode(ctx context.Context, cacheSvc elas } _, err = cacheSvc.AddTagsToResource(snapshotInput) if err != nil { - msg := "Failed to add tags to AWS Elasticache Snapshot:" + msg := "failed to add tags to aws elasticache snapshot" return types.StatusMessage(msg), err } } @@ -421,7 +440,7 @@ func (p *AWSRedisProvider) buildElasticacheCreateStrategy(ctx context.Context, r if elasticacheConfig.SnapshotRetentionLimit == nil { elasticacheConfig.SnapshotRetentionLimit = aws.Int64(defaultSnapshotRetention) } - cacheName, err := buildInfraNameFromObject(ctx, p.Client, r.ObjectMeta, defaultAwsIdentifierLength) + cacheName, err := BuildInfraNameFromObject(ctx, p.Client, r.ObjectMeta, DefaultAwsIdentifierLength) if err != nil { return errorUtil.Wrapf(err, "failed to retrieve elasticache config") } @@ -433,7 +452,7 @@ func (p *AWSRedisProvider) buildElasticacheCreateStrategy(ctx context.Context, r // buildElasticacheDeleteConfig checks redis config, if none exists sets values to defaults func (p *AWSRedisProvider) buildElasticacheDeleteConfig(ctx context.Context, r v1alpha1.Redis, elasticacheCreateConfig *elasticache.CreateReplicationGroupInput, elasticacheDeleteConfig *elasticache.DeleteReplicationGroupInput) error { - cacheName, err := buildInfraNameFromObject(ctx, p.Client, r.ObjectMeta, defaultAwsIdentifierLength) + cacheName, err := BuildInfraNameFromObject(ctx, p.Client, r.ObjectMeta, DefaultAwsIdentifierLength) if err != nil { return errorUtil.Wrapf(err, "failed to retrieve elasticache config") } @@ -446,7 +465,7 @@ func (p *AWSRedisProvider) buildElasticacheDeleteConfig(ctx context.Context, r v if elasticacheDeleteConfig.RetainPrimaryCluster == nil { elasticacheDeleteConfig.RetainPrimaryCluster = aws.Bool(false) } - snapshotIdentifier, err := buildTimestampedInfraNameFromObject(ctx, p.Client, r.ObjectMeta, defaultAwsIdentifierLength) + snapshotIdentifier, err := buildTimestampedInfraNameFromObject(ctx, p.Client, r.ObjectMeta, DefaultAwsIdentifierLength) if err != nil { return errorUtil.Wrapf(err, "failed to retrieve rds config") } diff --git a/pkg/providers/aws/provider_redis_test.go b/pkg/providers/aws/provider_redis_test.go index 94c1d356a..a4162a85e 100644 --- a/pkg/providers/aws/provider_redis_test.go +++ b/pkg/providers/aws/provider_redis_test.go @@ -80,6 +80,20 @@ func (m *mockElasticacheClient) DescribeSnapshots(*elasticache.DescribeSnapshots return &elasticache.DescribeSnapshotsOutput{}, nil } +// mock elasticache DescribeReplicationGroups output +func (m *mockElasticacheClient) DescribeCacheClusters(*elasticache.DescribeCacheClustersInput) (*elasticache.DescribeCacheClustersOutput, error) { + if m.wantEmpty { + return &elasticache.DescribeCacheClustersOutput{}, nil + } + return &elasticache.DescribeCacheClustersOutput{ + CacheClusters: []*elasticache.CacheCluster{ + { + CacheClusterStatus: aws.String("available"), + }, + }, + }, nil +} + // mock sts get caller identity func (m *mockStsClient) GetCallerIdentity(*sts.GetCallerIdentityInput) (*sts.GetCallerIdentityOutput, error) { return &sts.GetCallerIdentityOutput{ @@ -120,6 +134,7 @@ func buildReplicationGroupReady() []*elasticache.ReplicationGroup { Address: testAddress, Port: testPort, }, + Status: aws.String("available"), }, }, }, diff --git a/pkg/providers/aws/provider_smtpcredentialset.go b/pkg/providers/aws/provider_smtpcredentialset.go index 538f39032..2a426c1ab 100644 --- a/pkg/providers/aws/provider_smtpcredentialset.go +++ b/pkg/providers/aws/provider_smtpcredentialset.go @@ -117,7 +117,7 @@ func (p *SMTPCredentialProvider) CreateSMTPCredentials(ctx context.Context, smtp // create smtp credentials from generated iam role p.Logger.Info("creating iam role required to send mail through aws ses") - credSecName, err := buildInfraNameFromObject(ctx, p.Client, smtpCreds.ObjectMeta, 40) + credSecName, err := BuildInfraNameFromObject(ctx, p.Client, smtpCreds.ObjectMeta, 40) if err != nil { msg := "failed to generate smtp credentials secret name" return nil, croType.StatusMessage(msg), errorUtil.Wrap(err, msg) diff --git a/pkg/resources/config.go b/pkg/resources/config.go index cf7db61e9..53124dfbe 100644 --- a/pkg/resources/config.go +++ b/pkg/resources/config.go @@ -14,6 +14,8 @@ import ( const ( EnvForceReconcileTimeout = "ENV_FORCE_RECONCILE_TIMEOUT" DefaultTagKeyPrefix = "integreatly.org/" + ErrorReconcileTime = time.Second * 30 + SuccessReconcileTime = time.Second * 60 ) // returns envar for reconcile time else returns default time diff --git a/pkg/resources/phase.go b/pkg/resources/phase.go index a606e81c7..048fc7c57 100644 --- a/pkg/resources/phase.go +++ b/pkg/resources/phase.go @@ -30,3 +30,22 @@ func UpdatePhase(ctx context.Context, client client.Client, inst runtime.Object, } return nil } + +func UpdateSnapshotPhase(ctx context.Context, client client.Client, inst runtime.Object, phase croType.StatusPhase, msg croType.StatusMessage) error { + if msg == croType.StatusEmpty { + return nil + } + rts := &croType.ResourceTypeSnapshotStatus{} + if err := runtime.Field(reflect.ValueOf(inst).Elem(), "Status", rts); err != nil { + return errorUtil.Wrap(err, "failed to retrieve status block from object") + } + rts.Message = msg + rts.Phase = phase + if err := runtime.SetField(*rts, reflect.ValueOf(inst).Elem(), "Status"); err != nil { + return errorUtil.Wrap(err, "failed to set status block of object") + } + if err := client.Status().Update(ctx, inst); err != nil { + return errorUtil.Wrap(err, "failed to update resource status phase and message") + } + return nil +} diff --git a/vendor/modules.txt b/vendor/modules.txt index a4dcd7968..30b917aa3 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -235,7 +235,7 @@ golang.org/x/oauth2/google golang.org/x/oauth2/internal golang.org/x/oauth2/jws golang.org/x/oauth2/jwt -# golang.org/x/sys v0.0.0-20200107162124-548cf772de50 +# golang.org/x/sys v0.0.0-20200113162924-86b910548bc1 golang.org/x/sys/unix golang.org/x/sys/windows # golang.org/x/text v0.3.2 diff --git a/version/version.go b/version/version.go index 0ede0cbf7..1c3757b9f 100644 --- a/version/version.go +++ b/version/version.go @@ -1,5 +1,5 @@ package version var ( - Version = "0.5.0" + Version = "0.6.0" )