From a6e20ae38f5354f85a87169e66b8984144b93d93 Mon Sep 17 00:00:00 2001 From: Art Berger Date: Thu, 10 Sep 2020 18:20:18 -0400 Subject: [PATCH 01/20] docs: Doc edits to readme for tasks and ref section Signed-off-by: Art Berger --- README.md | 494 +++++++++++++++++++++++++++++------------------------- 1 file changed, 269 insertions(+), 225 deletions(-) diff --git a/README.md b/README.md index 8ad32384..7d9a6710 100644 --- a/README.md +++ b/README.md @@ -6,295 +6,339 @@ -The IBM Cloud Operator provides a simple Kubernetes CRD-Based API to provision and bind -IBM public cloud services on your Kubernetes cluster. With this operator, you no longer need -out-of-band processes to consume IBM Cloud Services in your application; -you can simply provide service and binding custom resources as part of your Kubernetes -application templates and let the operator reconciliation logic ensure that the required -resources are automatically created and maintained. +With the IBM Cloud Operator, you can provision and bind [IBM public cloud services](https://cloud.ibm.com/catalog#services) to your Kubernetes cluster in a Kubernetes-native way. The IBM Cloud Operator is based on the [Kubernetes custom resource definition (CRD) API](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/) so that your applications can create, update, and delete IBM Cloud services from within the cluster by calling Kubnernetes APIs, instead of needing to use several IBM Cloud APIs in addition to configuring your app for Kubernetes. -For a detailed guide on how to use the IBM Cloud Operator, see [IBM Cloud Operator User Guide](docs/user-guide.md). - +For more informatino, see the [IBM Cloud Operator user guide](docs/user-guide.md). -## Features + -* **Service Provisioning** - supports provisioning for any service and plan available in the IBM Cloud catalog. +## Table of content +* [Features](#features) +* [Prerequisites](#prerequisites) +* [Setting up the operator](#setting-up-the-operator) + * [Using a service ID](#using-a-service-id) + * [Installing the operator for OpenShift clusters](#installing-the-operator-for-openshift-clusters) + * [Installing the operator for Kubernetes clusters](#installing-the-operator-for-kubernetes-clusters) + * [Uninstalling the operator](#uninstalling-the-operator) +* [Using the IBM Cloud Operator](#using-the-ibm-cloud-operator) +* [Using separate IBM Cloud accounts](#using-separate-ibm-cloud-accounts) +* [Using a management namespace](#using-a-management-namespace) +* [Reference documentation](#reference-documentation) + * [Service properties](#service-properties) + * [Binding properties](#binding-properties) + * [Account context in operator secret and configmap](#account-context-in-operator-secret-and-configmap) +* [Contributing](#contributing) +* [Troubleshooting](#troubleshooting) -* **Bindings Management** - automatically creates secrets with the credentials to bind to -any provisioned service. + -## Requirements +## Features -The operator can be installed on any Kubernetes cluster with version >= 1.11. -You need an [IBM Cloud account](https://cloud.ibm.com/registration) and the -[IBM Cloud CLI](https://cloud.ibm.com/docs/cli?topic=cloud-cli-getting-started). -You need also to have the [kubectl CLI](https://kubernetes.io/docs/tasks/tools/install-kubectl/) -already configured to access your cluster. Before installing the operator, you need to login to -your IBM cloud account with the IBM Cloud CLI: +* **Service Provisioning**: Create any service with any plan that is available in the [IBM Cloud catalog](https://cloud.ibm.com/catalog#services). -```bash -ibmcloud login -``` +* **Bindings Management**: Automatically creates Kubernetes secrets in your Kubernetes cluster with the credentials to bind to any provisioned service to the cluster. -and set a default target environment for your resources with the command: +[Back to top](#ibm-cloud-operator) -```bash -ibmcloud target --cf -g default -``` +## Prerequisites -This will use the IBM Cloud ResourceGroup `default`. To specify a different ResourceGroup, use the following command: -```bash -ibmcloud target -g -``` - -Notice that the `org` and `space` must be included, even if no Cloud Foundry services will be instantiated. +1. Have an [IBM Cloud account](https://cloud.ibm.com/registration). +2. Have a cluster that runs Kubernetes version 1.11 or later (OpenShift 3.11 or later). +3. Install the required command line tools. + * [IBM Cloud CLI](https://cloud.ibm.com/docs/cli?topic=cloud-cli-getting-started) (`ibmcloud`) + * [Kubernetes CLI](https://kubernetes.io/docs/tasks/tools/install-kubectl/) (`kubectl`) +4. Log in to your IBM Cloud account from the CLI. + ```bash + ibmcloud login + ``` +5. Target the appropriate environment (`--cf`) and resource group (`-g`). Note that you must still set the Cloud Foundry `org` and `space` environment, even if you do not create any Cloud Foundry services. + ```bash + ibmcloud target --cf -g + ``` +6. Set the Kubernetes context of your CLI to your cluster so that you can run `kubectl` commands. For example, if your cluster is in IBM Cloud Kubernetes Service, run the following command. + ```bash + ibmcloud ks cluster config -c + ``` -## Set up the operator + To check that your Kubernetes context is set to your cluster, run the following command. + ```bash + kubectl config current-context + ``` -To set up the operator after logging in with `ibmcloud`, run the below installer. +[Back to top](#ibm-cloud-operator) -By default, the script will create a new API key for use in the operator. To use a custom API key, set the `IBMCLOUD_API_KEY` environment variable to the key. +## Setting up the operator -#### Using a ServiceId +You can use an installation script to set up the IBM Cloud Operator. The installation script stores an API key in a Kubernetes secret in your cluster that can be accessed by the IBM Cloud Operator. Next, the script sets default values that are used to provision IBM Cloud services, like the resource group and region to provision the services in. Later, you can override any default value in the `Service` custom resource. Finally, the script deploys the operator in your cluster in the `ibmcloud-operators` namespace. -To instantiate services and bindings on behalf of a ServiceId, set the environment variable `IBMCLOUD_API_KEY` to the `api-key` of the ServiceId. This can be obtained via the IBM Cloud Console or [CLI](https://cloud.ibm.com/docs/iam?topic=iam-serviceids). Be sure to give proper access permissions to the ServiceId. +Prefer to create the secrets and defaults yourself? See the [IBM Cloud Operator installation guide](docs/install.md). -Next log into the IBM Cloud account that owns the ServiceId and follow the instructions above. - - +### Using a service ID -### Install +By default, the installation script creates an IBM Cloud API key that impersonates your user credentials, to use to set up the IBM Cloud Operator. However, you might want to create a service ID in IBM Cloud Identity and Access Managment (IAM). By using a service ID, you can control access for the IBM Cloud Operator without having the permissions tied to a particular user, such as if that user leaves the company. For more information, see the [IBM Cloud docs](https://cloud.ibm.com/docs/account?topic=account-serviceids). -To install the latest stable release of the operator, run the below script. +1. Create a service ID in IBM Cloud IAM. + ```bash + ibmcloud iam service-id-create serviceid-ico -d service-ID-for-ibm-cloud-operator + ``` +2. Assign the service ID access to the required permissions. + ```bash + ibmcloud iam service-policy-create serviceid-ico -r Administrator + ``` +3. Create an API key for the service ID. + ```bash + ibmcloud iam service-api-key-create apikey-ico serviceid-ico -d api-key-for-ibm-cloud-operator + ``` +4. Set the API key of the service ID as your CLI environment variable. Now, when you run the installation script, the script uses the service ID's API key. The following command is an example for macOS. + ```bash + setenv IBMCLOUD_API_KEY + ``` +5. Confirm that the API key environment variable is set in your CLI. + ```bash + echo $IBMCLOUD_API_KEY + ``` +6. Follow the [prerequisite steps](#prerequisites) to log in to the IBM Cloud account that owns the service ID. -To install the latest release of the operator, remove `-v 0.1.11`. NOTE: `v0.2.x` involved major changes and is undergoing further testing to ensure a smoother transition. +[Back to top](#ibm-cloud-operator) -```bash -curl -sL https://raw.githubusercontent.com/IBM/cloud-operators/master/hack/configure-operator.sh | bash -s -- -v 0.1.11 install -``` +### Installing the operator for OpenShift clusters -The above script stores an API key in a Kubernetes secret that can be accessed by the operator. -Next, it sets default values used in provisioning IBM Cloud Services, like the resource group and region. -You can override any default value in the `Service` custom resource. -Finally, the script deploys the operator in your cluster. +Before you begin, complete the [prerequisite steps](#prerequisites) to log in to IBM Cloud and your cluster, and optionally set up a [service ID API key](#using-a-service-id). -If you prefer to create the secret and the defaults manually, consult the [IBM Cloud Operator documentation](docs/install.md). +To install the latest release for OpenShift before install, run the following script: -To install a specific version of the operator, you can pass a semantic version: +* **Latest release**: + ```bash + curl -sL https://raw.githubusercontent.com/IBM/cloud-operators/master/hack/configure-operator.sh | bash + ``` -```bash -curl -sL https://raw.githubusercontent.com/IBM/cloud-operators/master/hack/configure-operator.sh | bash -s -- -v 0.0.0 install -``` +* **Specific release**: Replace `-v 0.0.0` with the specific version that you want to install. + ```bash + curl -sL https://raw.githubusercontent.com/IBM/cloud-operators/master/hack/configure-operator.sh | bash -s -- -v 0.0.0 store-creds + ``` -### Uninstall +[Back to top](#ibm-cloud-operator) -To remove the operator, run the following script: - -```bash -curl -sL https://raw.githubusercontent.com/IBM/cloud-operators/master/hack/configure-operator.sh | bash -s -- remove -``` - - - -### Configure the operator for OpenShift - -To configure the latest release for OpenShift before install, run the following script: - -```bash -curl -sL https://raw.githubusercontent.com/IBM/cloud-operators/master/hack/configure-operator.sh | bash -``` + -The above script stores an API key in a Kubernetes secret that can be accessed by the operator. -Next, it sets default values used in provisioning IBM Cloud Services, like the resource group and region. -You can override any default value in the `Service` custom resource. +### Installing the operator for Kubernetes clusters -If you prefer to create the secret and the defaults manually, consult the [IBM Cloud Operator documentation](docs/install.md). +Before you begin, complete the [prerequisite steps](#prerequisites) to log in to IBM Cloud and your cluster, and optionally set up a [service ID API key](#using-a-service-id). -To configure with a specific version of the operator, you can pass a semantic version: +* **Stable release**: To install the latest stable release of the operator, run the following script. + ```bash + curl -sL https://raw.githubusercontent.com/IBM/cloud-operators/master/hack/configure-operator.sh | bash -s -- -v 0.1.11 install + ``` -```bash -curl -sL https://raw.githubusercontent.com/IBM/cloud-operators/master/hack/configure-operator.sh | bash -s -- -v 0.0.0 store-creds -``` +* **Latest release**: The latest `v0.2.x` version involves major changes and is undergoing further testing to ensure a smoother transition. + ```bash + curl -sL https://raw.githubusercontent.com/IBM/cloud-operators/master/hack/configure-operator.sh |bash -s -- install + ``` -## Using the IBM Cloud Operator +* **Specific release**: Replace `-v 0.0.0` with the specific version that you want to install. + ```bash + curl -sL https://raw.githubusercontent.com/IBM/cloud-operators/master/hack/configure-operator.sh |bash -s -- -v 0.0.0 install + ``` -You can create an instance of an IBM public cloud service using the following custom resource: +[Back to top](#ibm-cloud-operator) -```yaml -apiVersion: ibmcloud.ibm.com/v1beta1 -kind: Service -metadata: - name: myservice -spec: - plan: - serviceClass: -``` +### Uninstalling the operator -to find the value for ``, you can list the names of all IBM public cloud -services with the command: +Before you begin, complete the [prerequisite steps](#prerequisites) to log in to IBM Cloud and your cluster. -```bash -ibmcloud catalog service-marketplace -``` - -once you find the `` name, you can list the available plans to select -a `` with the command: - -```bash -ibmcloud catalog service | grep plan -``` - -After creating a service, you can find its status with: +To remove the operator, run the following script: ```bash -kubectl get services.ibmcloud -NAME STATUS AGE -myservice Online 12s +curl -sL https://raw.githubusercontent.com/IBM/cloud-operators/master/hack/configure-operator.sh | bash -s -- remove ``` -You can bind to a service with name `myservice` using the following custom resource: - -```yaml -apiVersion: ibmcloud.ibm.com/v1beta1 -kind: Binding -metadata: - name: mybinding -spec: - serviceName: myservice -``` - -To find the status of your binding, you can run the command: - -```bash -kubectl get bindings.ibmcloud -NAME STATUS AGE -mybinding Online 25s -``` +[Back to top](#ibm-cloud-operator) -A `Binding` generates a secret with the same name as the binding resource and -contains service credentials that can be consumed by your application. + -```bash -kubectl get secrets -NAME TYPE DATA AGE -mybinding Opaque 6 102s -``` +## Using the IBM Cloud Operator -You can find [additional samples](config/samples), and more information on -[using the operator](docs/user-guide.md) in the operator documentation. +To use the IBM Cloud Operator, create a service instance and then bind the service to your cluster. For more information, see the [sample configuration files](config/samples) and [user guide](docs/user-guide.md). + +**Step 1: Creating a service instance** + +1. To create an instance of an IBM public cloud service, first create a `Service` custom resource file. For more options, see the [Service properties](#service-properties) reference doc. + * `` is the IBM Cloud service that you want to create. To list IBM Cloud services, run `ibmcloud catalog service-marketplace` and use the **Name** value from the output. + * `` is the plan for the IBM Cloud service that you want to create, such as `free` or `standard`. To list available plans, run `ibmcloud catalog service | grep plan`. + + ```yaml + apiVersion: ibmcloud.ibm.com/v1beta1 + kind: Service + metadata: + name: myservice + spec: + plan: + serviceClass: + ``` + +2. Create the service instance in your cluster. + ```bash + kubectl apply -f filepath/myservice.yaml + ``` +3. Check that your service status is **Online** in your cluster. + ```bash + kubectl get services.ibmcloud + NAME STATUS AGE + myservice Online 12s + ``` +4. Verify that your service instance is created in IBM Cloud. + ```bash + ibmcloud resource service-instances | grep myservice + ``` + +**Step 2: Binding the service instance** + +1. To bind your service to the cluster so that your apps can use the service, create a `Binding` custom resource, where the `serviceName` field is the name of the `Service` custom resource that you previously created. For more options, see [Binding properties](#binding-properties). + + ```yaml + apiVersion: ibmcloud.ibm.com/v1beta1 + kind: Binding + metadata: + name: mybinding + spec: + serviceName: myservice + ``` + +2. Create the binding in your cluster. + ```bash + kubectl apply -f filepath/mybinding.yaml + ``` +3. Check that your service status is **Online**. + ```bash + kubectl get bindings.ibmcloud + NAME STATUS AGE + mybinding Online 25s + ``` +4. Check that a secret of the same name as your binding is created. The secret contains the service credentials that apps in your cluster can use to access the service. + ```bash + kubectl get secrets + NAME TYPE DATA AGE + mybinding Opaque 6 102s + ``` + +[Back to top](#ibm-cloud-operator) +## Using separate IBM Cloud accounts + +You can provision IBM Cloud services in separate IBM Cloud accounts from the same cluster. To use separate accounts, update the secrets and configmap in the Kubernetes namespace where you want to create services and bindings. + +**Tip**: Just want to use a different account one time and don't want to manage a bunch of namespaces? You can also specify a different account in the individual [service configuration](#service-properties), by overriding the default [account context](#account-context-in-operator-secret-and-configmap). + +1. Get the IBM Cloud account details, including account ID, Cloud Foundry org and space, resource group, region, and API key credentials. +2. Edit or replace the `secret-ibm-cloud-operator` secret in the Kubernetes namespace that you want to use to create services in the account. +3. Edit or replace the `config-ibm-cloud-operator` configmap in the Kubernetes namespace that you want to use to create services in the account. +4. Optional: [Set up a management namespace](#setting-up-a-management-namespace) so that cluster users with access across namespaces cannot see the API keys for the different IBM Cloud accounts. + +## Setting up a management namespace + +By default, the API key credentials and other IBM Cloud account information are stored in a secret and a configmap within each namespace where you create IBM Cloud Operator service and binding custom resources. However, you might want to hide access to this information from cluster users in the namespace. For example, you might have multiple IBM Cloud accounts that you do not want cluster users in different namespaces to know about. + +1. Create a management namespace that is named `safe`. + ```bash + kubectl create namespace safe + ``` +2. In the namespace where the IBM Cloud Operator runs, create an `ibm-cloud-operator` configmap that points to the `safe` namespace. + ```bash + cat <-secret-ibm-cloud-operator + -config-ibm-cloud-operator + ``` + + For example, if you have a cluster with three namespaces `default`, `test`, and `prod`: + ``` + default-secret-ibm-cloud-operator + default-config-ibm-cloud-operator + test-secret-ibm-cloud-operator + test-config-ibm-cloud-operator + prod-secret-ibm-cloud-operator + prod-config-ibm-cloud-operator + ``` +4. Delete the `secret-ibm-cloud-operator` secrets and `config-ibm-cloud-operator` configmaps in the other Kubernetes namespaces. + +[Back to top](#ibm-cloud-operator) + +## Reference documentation + ### Service Properties -A `Service` includes the following properties: +A `Service` custom resource includes the properties in the following table. Each `parameter` value is treated as a raw value by the operator and passed as-is. For more information, see the [IBM Cloud Operator user guide](docs/user-guide.md). -| Field | Required | Type | Comments | +| Parameter | Required | Type | Comments | |:-----------------|:---------|:-----------|:-----------------------------------------------------------------------------------------------------------| -| serviceClass | Yes | `string` | The type of service being instantiated | -| plan | Yes | `string` | The plan to use for service instantiation, set to `Alias` for linking to an existing service instance | -| serviceClassType | CF only | `string` | Set to `CF` for Cloud Foundry services, omit otherwise | -| externalName | No | `string` | The name that appears for the instantiated service on the IBM Public Cloud Dashboard | -| parameters | No | `[]Param` | Parameters passed for service instantiation, the type can be anything (number, string, object, ...) | -| tags | No | `[]string` | Tags passed for service instantiation. These tags appear on the instance on the IBM Public Cloud Dashboard | -| context | No | `Context` | Used to override default account context (see below) | - -Each `parameter`'s value is treated as a raw value by the Operator and passed as-is. -The `plan` can be set to `Alias` to link to an existing service instance (see [IBM Cloud Operator User Guide](docs/user-guide.md) -for more details). - -_Notice that the `serviceClass`, `plan`, `serviceClassType`, and `externalName` fields are immutable. Immutability is not enforced -with an admission controller, so updates go through initially successfully. However, the controller intercepts such changes and -changes those fields back to their original values. So although it may seem that updates to those fields are accepted, they are -in fact reverted by the controller. On the other hand, `parameters` and `tags` are updatable._ - -The IBM Cloud Operator needs an account context, which indicates the `api-key` and the details of the IBM Public Cloud -account to be used for service instantiation. The `api-key` is contained in a Secret called `secret-ibm-cloud-operator` that is created -when the IBM Cloud Operator is installed. Details of the account (such as organization, space, resource group) are held in a -ConfigMap called `config-ibm-cloud-operator`. To find the secret and configmap the IBM Cloud Operator first looks at the namespace of the -resource being created, and if not found, in a management namespace (see below for more details on management namespaces). If there is no management namespace, then the operator looks for the secret and configmap in the `default` namespace. - - -The account information can be overriden by using -the `context` field in the service yaml, with the following substructure: - -| Field | Required | Type | -|:-----------------|:---------|:---------| -| org | No | `string` | -| space | No | `string` | -| region | No | `string` | -| resourceGroup | No | `string` | -| resourceGroupID | No | `string` | -| resourceLocation | No | `string` | - -To override any element, the user can simply indicate that element and omit the others. -If a `resourceGroup` is indicated, then the `resourceGroupID` must also be provided. This can be obtained with the -following command, and retrieving the field `ID`. +| serviceClass `*` | Yes | `string` | The IBM Cloud service that you want to create. To list IBM Cloud services, run `ibmcloud catalog service-marketplace` and use the **Name** value from the output.| +| plan `*` | Yes | `string` | The plan to use for the service instance, such as `free` or `standard`. To use an existing service instance, set to `Alias`. To list available plans, run `ibmcloud catalog service | grep plan`. | +| serviceClassType `*` | CF only | `string` | Set to `CF` for Cloud Foundry services. Otherwise, omit this field. | +| externalName `*` | No | `string` | The name for the service instance in IBM Cloud, such as in the console.| +| parameters | No | `[]Param` | Parameters that are passed in to create the service instance. These parameters vary by service, and can be anything, such as a number, string, or object. | +| tags | No | `[]string` | The IBM Cloud [tag](https://cloud.ibm.com/docs/account?topic=account-tag) to assign the service instance, to help organize your cloud resources such as in the IBM Cloud console. | +| context | No | `Context` | The IBM Cloud account context to use instead of the [default account context](#account-context-in-operator-secret-and-configmap).| -```bash -ibmcloud resource group -``` +**Note**: The `serviceClass`, `plan`, `serviceClassType`, and `externalName` parameters are immutable. After you set these parameters, you cannot later edit their values. If you do edit the values, the changes are overwritten back to the original values. -#### Using a Management Namespace +[Back to top](#ibm-cloud-operator) -Different Kubernetes namespaces can contain different secrets `secret-ibm-cloud-operator` and configmap `config-ibm-cloud-operator`, corresponding to different IBM Public Cloud accounts. So each namespace can be set up for a different account. +### Binding Properties -In some scenarios, however, there is a need for hiding the `api-keys` from users. In this case, a management namespace can be set up that contains all the secrets and configmaps corresponding to each namespace, with a naming convention. +A `Binding` custom resources includes the properties in the following table. For more information, see the [IBM Cloud Operator user guide](docs/user-guide.md). -To configure a management namespace named `safe`, there must be a configmap named `ibm-cloud-operator` created in the same namespace as the IBM Cloud Operator itself. This configmap indicates the name of the management namespace, in a field `namespace`. To create such a config map, execute the following: +| Field | Required | Type | Comments | +|:-----------------|:---------|:---------|:------------------------------------------------------------------------------------------------------| +| serviceName | Yes | `string` | The name of the `Service` resource that corresponds to the service instance on which to create credentials for the binding. | +| serviceNamespace | No | `string` | The namespace of the `Service` resource.| +| alias | No | `string` | The name of credentials, if this binding links to existing credentials.| +| secretName | No | `string` | The name of the `Secret` to be created. If you do not specify a value, the secret is given the same name as the binding.| +| role | No | `string` | The IBM Cloud IAM role to create the credentials to the service instance. Review the each service's documentation for a description of the roles. If you do not specify a role, the IAM `Manager` service access role is used. If the service does not support the `Manager` role, the first returned role from the service is used. | +| parameters | No | `[]Any` | Parameters that are passed in to create the create the service credentials. These parameters vary by service, and can be anything, such as an integer, string, or object. | -```bash -cat < - labels: - app.kubernetes.io/name: ibmcloud-operator -data: - namespace: safe -EOF -``` +[Back to top](#ibm-cloud-operator) -This configmap indicates to the operator where to find the management namespace, in this case `safe`. -Next the `safe` namespace needs to contain secrets and configmaps corresponding to each namespace that will contain services and bindings. The naming convention is as follows: +### Account context in operator secret and configmap -``` --secret-ibm-cloud-operator --config-ibm-cloud-operator -``` +The IBM Cloud Operator needs an account context, which indicates the API key and the details of the IBM Cloud account to be used for creating services. The API key is stored in a `secret-ibm-cloud-operator` secret that is created when the IBM Cloud Operator is installed. Account details such as the account ID, Cloud Foundry org and space, resource group, and region are stored in a `config-ibm-cloud-operator` configmap. -These can be created similary to what is done with `make install`. +When you create an IBM Cloud Operator service or binding resource, the operator checks the namespace that you create the resource in for the secret and configmap. If the the operator does not find the secret and configmap, it checks its own namespace for a configmap that points to a [management namespace](#setting-up-a-management-namespace). Then, the operator checks the management namespace for `-secret-ibm-cloud-operator` secrets and `-config-ibm-cloud-operator` configmaps. If no management namespace exists, the operator checks the `default` namespace for the `secret-ibm-cloud-operator` secret and `config-ibm-cloud-operator` configmap. -If we create a service or binding resource in a namespace `XYZ`, the IBM Cloud Operator first looks in the `XYZ` namespace to find `secret-ibm-cloud-operator` and `config-ibm-cloud-operator`, for account context. If they are missing in `XYZ`, it looks for the `ibm-cloud-operator` configmap in the namespace where the operator is installed, to see if there is a management namespace. If there is, it looks in the management namespace for the secret and configmap with the naming convention: -`XYZ-secret-ibm-cloud-operator` and `XYZ-config-ibm-cloud-operator`. If there is no management namespace, the operator looks in the `default` namespace for the secret and configmap (`secret-ibm-cloud-operator` and `config-ibm-cloud-operator`). +You can override the account context in the `Service` configuration file with the `context` field, as described in the following table. You might override the account context if you want to use a different IBM Cloud account to provision a service, but do not want to create separate secrets and configmaps for different namespaces. +| Field | Required | Type | Description | +|:-----------------|:---------|:---------|:------------| +| org | No | `string` | The Cloud Foundry org. To list orgs, run `ibmcloud account orgs`. | +| space | No | `string` | The Cloud Foundry space. To list spaces, run `ibmcloud account spaces`. | +| region | No | `string` | The IBM Cloud region. To list regions, run `ibmcloud regions`. | +| resourceGroup | No | `string` | The IBM Cloud resource group name. You must also include the `resourceGroupID`. To list resource groups, run `ibmcloud resource groups`. | +| resourceGroupID | No | `string` | The IBM Cloud resource group ID. You must also include the `resourceGroup`. To list resource groups, run `ibmcloud resource groups`. | +| resourceLocation | No | `string` | The location of the resource. | +[Back to top](#ibm-cloud-operator) -### Binding Properties +## Contributing to the project -A `Binding` includes the following properties: +See [Contributing to Cloud Operators](./CONTRIBUTING.md) -| Field | Required | Type | Comments | -|:-----------------|:---------|:---------|:------------------------------------------------------------------------------------------------------| -| serviceName | Yes | `string` | The name of the `Service` resource corresponding to the service instance on which to create credentials | -| serviceNamespace | No | `string` | The namespace of the `Service` resource | -| alias | No | `string` | The name of credentials, if this `Binding` is linking to existing credentials | -| secretName | No | `string` | The name of the `Secret` to be created | -| role | No | `string` | The role to be passed for credentials creation | -| parameters | No | `[]Any` | Parameters passed for credentials creation, the type could be anything (int, string, object, ...) | - -The `alias` field is used to link to an existing credentials (see [IBM Cloud Operator User Guide](docs/user-guide.md) -for more details). If the `secretName` is omitted, then the same name as the `Binding` itself is used. If the `role` is -omitted, then the operator sets role to `Manager`, if that is supported by the service (and if not, it picks the first role -listed by the service). Most services support the `Manager` role. - -## Learn more about how to contribute - -- [contributions](./CONTRIBUTING.md) +[Back to top](#ibm-cloud-operator) ## Troubleshooting -The [troubleshooting](docs/troubleshooting.md) section provides info on how -to debug your operator. +See the [Troubleshooting guide](docs/troubleshooting.md). + +[Back to top](#ibm-cloud-operator) From d94ca9f99165dc14bfeabf0192303c26689755dd Mon Sep 17 00:00:00 2001 From: Art Berger Date: Fri, 11 Sep 2020 16:01:37 -0400 Subject: [PATCH 02/20] docs: Add versions Signed-off-by: Art Berger --- README.md | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 7d9a6710..eaee3767 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ With the IBM Cloud Operator, you can provision and bind [IBM public cloud services](https://cloud.ibm.com/catalog#services) to your Kubernetes cluster in a Kubernetes-native way. The IBM Cloud Operator is based on the [Kubernetes custom resource definition (CRD) API](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/) so that your applications can create, update, and delete IBM Cloud services from within the cluster by calling Kubnernetes APIs, instead of needing to use several IBM Cloud APIs in addition to configuring your app for Kubernetes. -For more informatino, see the [IBM Cloud Operator user guide](docs/user-guide.md). +For more information, see the [IBM Cloud Operator user guide](docs/user-guide.md). @@ -27,6 +27,7 @@ For more informatino, see the [IBM Cloud Operator user guide](docs/user-guide.md * [Service properties](#service-properties) * [Binding properties](#binding-properties) * [Account context in operator secret and configmap](#account-context-in-operator-secret-and-configmap) + * [Versions](#versions) * [Contributing](#contributing) * [Troubleshooting](#troubleshooting) @@ -34,9 +35,9 @@ For more informatino, see the [IBM Cloud Operator user guide](docs/user-guide.md ## Features -* **Service Provisioning**: Create any service with any plan that is available in the [IBM Cloud catalog](https://cloud.ibm.com/catalog#services). +* **Service provisioning**: Create any service with any plan that is available in the [IBM Cloud catalog](https://cloud.ibm.com/catalog#services). -* **Bindings Management**: Automatically creates Kubernetes secrets in your Kubernetes cluster with the credentials to bind to any provisioned service to the cluster. +* **Bindings management**: Automatically create Kubernetes secrets in your Kubernetes cluster with the credentials to bind to any provisioned service to the cluster. [Back to top](#ibm-cloud-operator) @@ -125,12 +126,12 @@ To install the latest release for OpenShift before install, run the following sc Before you begin, complete the [prerequisite steps](#prerequisites) to log in to IBM Cloud and your cluster, and optionally set up a [service ID API key](#using-a-service-id). -* **Stable release**: To install the latest stable release of the operator, run the following script. +* **Stable release**: To install the latest stable release of the operator, run the following script. The stable release supports the `v1alpha1` Kubernetes API version. ```bash curl -sL https://raw.githubusercontent.com/IBM/cloud-operators/master/hack/configure-operator.sh | bash -s -- -v 0.1.11 install ``` -* **Latest release**: The latest `v0.2.x` version involves major changes and is undergoing further testing to ensure a smoother transition. +* **Latest release**: The latest `v0.2.x` version involves major changes and is undergoing further testing to ensure a smoother transition. The latest release supports the `v1alpha1` or `v1beta1` Kubernetes API versions. ```bash curl -sL https://raw.githubusercontent.com/IBM/cloud-operators/master/hack/configure-operator.sh |bash -s -- install ``` @@ -236,6 +237,8 @@ You can provision IBM Cloud services in separate IBM Cloud accounts from the sam 3. Edit or replace the `config-ibm-cloud-operator` configmap in the Kubernetes namespace that you want to use to create services in the account. 4. Optional: [Set up a management namespace](#setting-up-a-management-namespace) so that cluster users with access across namespaces cannot see the API keys for the different IBM Cloud accounts. +[Back to top](#ibm-cloud-operator) + ## Setting up a management namespace By default, the API key credentials and other IBM Cloud account information are stored in a secret and a configmap within each namespace where you create IBM Cloud Operator service and binding custom resources. However, you might want to hide access to this information from cluster users in the namespace. For example, you might have multiple IBM Cloud accounts that you do not want cluster users in different namespaces to know about. @@ -331,6 +334,17 @@ You can override the account context in the `Service` configuration file with th [Back to top](#ibm-cloud-operator) +### Versions + +Review the supported Kubernetes API versions for the following IBM Cloud Operator versions. + +| Operator version | Kubernetes API version | +| --- | --- | +| `v0.2.x` | `v1beta1` or `v1alpha1` | +| `v0.1.x` | `v1alpha` | + +[Back to top](#ibm-cloud-operator) + ## Contributing to the project See [Contributing to Cloud Operators](./CONTRIBUTING.md) From 256ccb6aa830c55e8ad7eda031b4fce2b5615c8e Mon Sep 17 00:00:00 2001 From: Art Berger Date: Fri, 11 Sep 2020 16:05:40 -0400 Subject: [PATCH 03/20] docs: add asterisk Signed-off-by: Art Berger --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index eaee3767..60999a48 100644 --- a/README.md +++ b/README.md @@ -296,7 +296,7 @@ A `Service` custom resource includes the properties in the following table. Each | tags | No | `[]string` | The IBM Cloud [tag](https://cloud.ibm.com/docs/account?topic=account-tag) to assign the service instance, to help organize your cloud resources such as in the IBM Cloud console. | | context | No | `Context` | The IBM Cloud account context to use instead of the [default account context](#account-context-in-operator-secret-and-configmap).| -**Note**: The `serviceClass`, `plan`, `serviceClassType`, and `externalName` parameters are immutable. After you set these parameters, you cannot later edit their values. If you do edit the values, the changes are overwritten back to the original values. +`*` **Note**: The `serviceClass`, `plan`, `serviceClassType`, and `externalName` parameters are immutable. After you set these parameters, you cannot later edit their values. If you do edit the values, the changes are overwritten back to the original values. [Back to top](#ibm-cloud-operator) From 8811224984f6950597b1bcd056468b527166bd23 Mon Sep 17 00:00:00 2001 From: Art Berger Date: Fri, 11 Sep 2020 16:07:14 -0400 Subject: [PATCH 04/20] docs: add tip about api version Signed-off-by: Art Berger --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 60999a48..40046f79 100644 --- a/README.md +++ b/README.md @@ -166,6 +166,7 @@ To use the IBM Cloud Operator, create a service instance and then bind the servi 1. To create an instance of an IBM public cloud service, first create a `Service` custom resource file. For more options, see the [Service properties](#service-properties) reference doc. * `` is the IBM Cloud service that you want to create. To list IBM Cloud services, run `ibmcloud catalog service-marketplace` and use the **Name** value from the output. * `` is the plan for the IBM Cloud service that you want to create, such as `free` or `standard`. To list available plans, run `ibmcloud catalog service | grep plan`. + * **Note**: Using operator `v0.1.x`? Change the `apiVersion` to `v1alpha1` when you create the YAML files. ```yaml apiVersion: ibmcloud.ibm.com/v1beta1 @@ -194,7 +195,7 @@ To use the IBM Cloud Operator, create a service instance and then bind the servi **Step 2: Binding the service instance** -1. To bind your service to the cluster so that your apps can use the service, create a `Binding` custom resource, where the `serviceName` field is the name of the `Service` custom resource that you previously created. For more options, see [Binding properties](#binding-properties). +1. To bind your service to the cluster so that your apps can use the service, create a `Binding` custom resource, where the `serviceName` field is the name of the `Service` custom resource that you previously created. For more options, see [Binding properties](#binding-properties). **Note**: Using operator `v0.1.x`? Change the `apiVersion` to `v1alpha1` when you create the YAML files. ```yaml apiVersion: ibmcloud.ibm.com/v1beta1 From 21b43fea257587e7c54ccd9a6983157fc0d6f271 Mon Sep 17 00:00:00 2001 From: Art Date: Mon, 14 Sep 2020 15:38:29 -0400 Subject: [PATCH 05/20] Doc edits for the contributors guide (#193) Signed-off-by: Art Berger --- CONTRIBUTING.md | 172 ++++++++++++++++++++++++++---------------------- 1 file changed, 92 insertions(+), 80 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 24f916bd..ec942594 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,108 +1,109 @@ # Contributing to Cloud Operators -This page contains information about reporting issues, how to suggest changes -as well as the guidelines we follow for how our documents are formatted. +You can report issues or open a pull request (PR) to suggest changes. If you open a PR, make sure to follow the Git commit and Golang code style guidelines. -## Table of Contents -* [Reporting an Issue](#reporting-an-issue) -* [Suggesting a Change](#suggesting-a-change) -* [Code Style](#code-style) +## Table of contents +* [Reporting an issue](#reporting-an-issue) +* [Suggesting a change](#suggesting-a-change) + * [Assigning and owning work](#assigning-and-owning-work) + * [Code style](#code-style) + * [Git commit guidelines](#git-commit-guidelines) + * [Documentation guidelines](#documentation-guidelines) -## Reporting an Issue +## Reporting an issue To report an issue, or to suggest an idea for a change that you haven't -had time to write-up yet, open an -[issue](https://github.com/IBM/cloud-operators/issues). It is best to check -our existing [issues](https://github.com/IBM/cloud-operators/issues) first -to see if a similar one has already been opened and discussed. +had time to write-up yet: +1. [Review existing issues](https://github.com/IBM/cloud-operators/issues) to see if a similar issue has been opened or discussed. +2. [Open an +issue](https://github.com/IBM/cloud-operators/issues/new). Be sure to include any helpful information, such as your Kubernetes environment details, error messages, or logs that you might have. -## Suggesting a Change +[Back to table of contents](#table-of-contents) -To suggest a change to this repository, submit a [pull -request](https://github.com/IBM/kui/pulls)(PR) with the complete -set of changes you'd like to see. See the -[Code Style](#code-style) section for -the guidelines we follow for how documents are formatted. +## Suggesting a change -Each PR must be signed per the following section. +To suggest a change to this repository, [submit a pull request](https://github.com/IBM/cloud-operators/pulls) with the complete set of changes that you want to suggest. Make sure your PR meets the following guidelines for code, Git commit messages and signing, and documentation. -### Assigning and Owning work +### Assigning and owning work -If you want to own and work on an issue, add a comment asking -about ownership. A maintainer will then add the Assigned label and modify -the first comment in the issue to include `Assigned to: @person` +If you want to own and work on an issue, add a comment asking about ownership. A maintainer then adds the **Assigned** label and modifies the first comment in the issue to include `Assigned to: @person`. -### Git Commit Guidelines +[Back to table of contents](#table-of-contents) + +### Code style + +This project is written in `Go` and follows the Golang community coding style. For guidelines, see the [Go code review style doc](https://github.com/golang/go/wiki/CodeReviewComments). + +[Back to table of contents](#table-of-contents) + +### Git commit guidelines #### Conventional Commits -This project uses [Conventional Commits](https://www.conventionalcommits.org) as a guide for commit messages. Please ensure that your commit message follows this structure: +This project uses [Conventional Commits](https://www.conventionalcommits.org) as a guide for commit messages. Make sure that your commit message follows this structure: ``` type(component?): message ``` -*type* is one of: feat, fix, docs, chore, style, refactor, perf, test +where -*component* optionally is the name of the module you are fixing. +* *type* is one of: feat, fix, docs, chore, style, refactor, perf, test +* *component* optionally is the name of the module you are fixing #### Sign your work -The sign-off is a simple line at the end of the explanation for the patch. Your -signature certifies that you wrote the patch or otherwise have the right to pass -it on as an open-source patch. The rules are pretty simple: if you can certify -the below (from [developercertificate.org](http://developercertificate.org/)): +The sign-off is a simple line at the end of the explanation for the patch. Your signature certifies that you wrote the patch or otherwise have the right to pass it on as an open-source patch. -``` -Developer Certificate of Origin -Version 1.1 - -Copyright (C) 2004, 2006 The Linux Foundation and its contributors. -1 Letterman Drive -Suite D4700 -San Francisco, CA, 94129 - -Everyone is permitted to copy and distribute verbatim copies of this -license document, but changing it is not allowed. - -Developer's Certificate of Origin 1.1 - -By making a contribution to this project, I certify that: - -(a) The contribution was created in whole or in part by me and I - have the right to submit it under the open source license - indicated in the file; or - -(b) The contribution is based upon previous work that, to the best - of my knowledge, is covered under an appropriate open source - license and I have the right under that license to submit that - work with modifications, whether created in whole or in part - by me, under the same open source license (unless I am - permitted to submit under a different license), as indicated - in the file; or - -(c) The contribution was provided directly to me by some other - person who certified (a), (b) or (c) and I have not modified - it. - -(d) I understand and agree that this project and the contribution - are public and that a record of the contribution (including all - personal information I submit with it, including my sign-off) is - maintained indefinitely and may be redistributed consistent with - this project or the open source license(s) involved. -``` +1. Review the following developer certificate of origin (DCO). Your signature in your commit certifies this statement. The DCO is taken from [developercertificate.org](http://developercertificate.org/). -Then you just add a line to every git commit message: + ``` + Developer Certificate of Origin + Version 1.1 - Signed-off-by: Joe Smith + Copyright (C) 2004, 2006 The Linux Foundation and its contributors. + 1 Letterman Drive + Suite D4700 + San Francisco, CA, 94129 + + Everyone is permitted to copy and distribute verbatim copies of this + license document, but changing it is not allowed. + + Developer's Certificate of Origin 1.1 -Use your real name (sorry, no pseudonyms or anonymous contributions.) + By making a contribution to this project, I certify that: -If you set your `user.name` and `user.email` git configs, you can sign your -commit automatically with `git commit -s`. + (a) The contribution was created in whole or in part by me and I + have the right to submit it under the open source license + indicated in the file; or -Note: If your git config information is set properly then viewing the - `git log` information for your commit will look something like this: + (b) The contribution is based upon previous work that, to the best + of my knowledge, is covered under an appropriate open source + license and I have the right under that license to submit that + work with modifications, whether created in whole or in part + by me, under the same open source license (unless I am + permitted to submit under a different license), as indicated + in the file; or + + (c) The contribution was provided directly to me by some other + person who certified (a), (b) or (c) and I have not modified + it. + + (d) I understand and agree that this project and the contribution + are public and that a record of the contribution (including all + personal information I submit with it, including my sign-off) is + maintained indefinitely and may be redistributed consistent with + this project or the open source license(s) involved. + ``` +2. Add the following line to every git commit message in your pull request. Use your real name (sorry, no pseudonyms or anonymous contributions). + ``` + Signed-off-by: Joe Smith + ``` + +**Tip**: If you set your `user.name` and `user.email` in your git config, you can sign your +commits automatically with `git commit -s`. (And don't forget your message! `git commit -s -m "type: message"`) + +Not sure if your git config is set? Run `git config --list` and review the `user.name` and `user.email` fields. Then, run `git log` for your commit and verify that the `Author` and `Signed-off-by` lines match. If the lines do not match, your PR is rejected by the automated DCO checker. ``` Author: Joe Smith @@ -113,10 +114,21 @@ Date: Thu Feb 2 11:41:15 2018 -0800 Signed-off-by: Joe Smith ``` -Notice the `Author` and `Signed-off-by` lines match. If they don't -your PR will be rejected by the automated DCO check. +[Back to table of contents](#table-of-contents) + +### Documentation guidelines + +For documentation within `Go` code, see the Golang community [guidelines](https://github.com/golang/go/wiki/CodeReviewComments#doc-comments) and [commentary](https://golang.org/doc/effective_go.html#commentary). + +For documentation pages, this project uses Markdown (`.md` files), and generally follows IBM Style. Some basics of IBM Style include: +* American English spelling. When in doubt, consult the Merriam Webster dictionary. +* Active voice and present tense. +* Sentence case (as opposed to Title Case). + +Example docs that you can contribute to: +* [`/docs` directory](./docs/): You can update an existing file, or add your own. Example files include installation and user guides. +* [`README.md`](./README.md): Try to keep the `README.md` file about top-level information that is general to most implementations, or that guides users to other content for more detailed information. Keep in mind that this file is reused in other components, such as the OperatorHub. +* [CONTRIBUTING.md](./CONTRIBUTING.md): Yes, you can even suggest a change to how you want to contribute. -## Code style +[Back to table of contents](#table-of-contents) -The coding style suggested by the Golang community is used in this project. -See the [style doc](https://github.com/golang/go/wiki/CodeReviewComments) for details. From 309f91f579874b0a85ce4b91ad0baded0267f222 Mon Sep 17 00:00:00 2001 From: John Starich Date: Mon, 14 Sep 2020 16:53:37 -0500 Subject: [PATCH 06/20] Add service controller unit tests (#195) * Add unit tests for service controller * Add more tests for CF services * Add "make test-fast" for quicker local testing Signed-off-by: John Starich Signed-off-by: Art Berger --- Makefile | 4 + controllers/service_controller.go | 41 +- controllers/service_controller_test.go | 2018 +++++++++++++++++ controllers/suite_test.go | 1 + .../ibmcloud/cfservice/service_instance.go | 8 +- .../ibmcloud/resource/service_instance.go | 6 +- main.go | 1 + 7 files changed, 2058 insertions(+), 21 deletions(-) diff --git a/Makefile b/Makefile index 4a3de9b6..b730afd3 100644 --- a/Makefile +++ b/Makefile @@ -65,6 +65,10 @@ cache/bin/kustomize: cache/bin curl -s "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" | bash [[ "$$(which kustomize)" =~ cache/bin/kustomize ]] +.PHONY: test-fast +test-fast: generate manifests kubebuilder + go test -short -coverprofile cover.out ./... + .PHONY: test test: generate manifests kubebuilder go test -race -coverprofile cover.out ./... diff --git a/controllers/service_controller.go b/controllers/service_controller.go index de43f944..0a878915 100644 --- a/controllers/service_controller.go +++ b/controllers/service_controller.go @@ -33,7 +33,6 @@ import ( ibmcloudv1beta1 "github.com/ibm/cloud-operators/api/v1beta1" "github.com/ibm/cloud-operators/internal/config" - "github.com/ibm/cloud-operators/internal/ibmcloud" "github.com/ibm/cloud-operators/internal/ibmcloud/cfservice" "github.com/ibm/cloud-operators/internal/ibmcloud/resource" ) @@ -64,6 +63,7 @@ type ServiceReconciler struct { DeleteCFServiceInstance cfservice.InstanceDeleter DeleteResourceServiceInstance resource.ServiceInstanceDeleter GetCFServiceInstance cfservice.InstanceGetter + GetIBMCloudInfo IBMCloudInfoGetter GetResourceServiceAliasInstance resource.ServiceAliasInstanceGetter GetResourceServiceInstanceState resource.ServiceInstanceStatusGetter UpdateResourceServiceInstance resource.ServiceInstanceUpdater @@ -121,7 +121,7 @@ func (r *ServiceReconciler) Reconcile(request ctrl.Request) (ctrl.Result, error) targetCRN string ) { - ibmCloudInfo, err := ibmcloud.GetInfo(logt, r.Client, instance) + ibmCloudInfo, err := r.GetIBMCloudInfo(logt, r.Client, instance) if err != nil { // If secrets have already been deleted and we are in a deletion flow, just delete the finalizers // to not prevent object from finalizing. This would cause orphaned service in IBM Cloud. @@ -130,11 +130,13 @@ func (r *ServiceReconciler) Reconcile(request ctrl.Request) (ctrl.Result, error) logt.Info("Cannot get IBMCloud related secrets and configmaps, just remove finalizers", "in deletion", err.Error()) instance.ObjectMeta.Finalizers = deleteServiceFinalizer(instance) if err := r.Update(ctx, instance); err != nil { - logt.Info("Error removing finalizers", "in deletion", err.Error()) + logt.Error(err, "Error removing finalizers in deletion") + // TODO(johnstarich): Shouldn't this be a failure so it can be requeued? + // Also, should the status be updated to include this failure message? } return ctrl.Result{}, nil } - logt.Info(err.Error()) + logt.Error(err, "Failed to get IBM Cloud info for service") return r.updateStatusError(instance, serviceStateFailed, err) } resourceContext = ibmCloudInfo.Context @@ -170,7 +172,8 @@ func (r *ServiceReconciler) Reconcile(request ctrl.Request) (ctrl.Result, error) if !containsServiceFinalizer(instance) { instance.ObjectMeta.Finalizers = append(instance.ObjectMeta.Finalizers, serviceFinalizer) if err := r.Update(ctx, instance); err != nil { - logt.Info("Error adding finalizer", instance.ObjectMeta.Name, err.Error()) + logt.Error(err, "Error adding finalizer", "service", instance.ObjectMeta.Name) + // TODO(johnstarich): Shouldn't this update the status with the failure message? return ctrl.Result{}, err } } @@ -179,14 +182,16 @@ func (r *ServiceReconciler) Reconcile(request ctrl.Request) (ctrl.Result, error) if containsServiceFinalizer(instance) { err := r.deleteService(session, logt, instance, serviceClassType) if err != nil { - logt.Info("Error deleting resource", instance.ObjectMeta.Name, err.Error()) + logt.Error(err, "Error deleting resource", "service", instance.ObjectMeta.Name) + // TODO(johnstarich): Shouldn't this return the error so it will be logged? return ctrl.Result{Requeue: true, RequeueAfter: time.Second * 10}, nil } // remove our finalizer from the list and update it. instance.ObjectMeta.Finalizers = deleteServiceFinalizer(instance) - if err := r.Update(ctx, instance); err != nil { - logt.Info("Error removing finalizers", "in deletion", err.Error()) + err = r.Update(ctx, instance) + if err != nil { + logt.Error(err, "Error removing finalizers") } return ctrl.Result{}, err } @@ -222,14 +227,14 @@ func (r *ServiceReconciler) Reconcile(request ctrl.Request) (ctrl.Result, error) externalName := getExternalName(instance) params, err := r.getParams(ctx, instance) if err != nil { - logt.Error(err, "Instance ", instance.ObjectMeta.Name, " has problems with its parameters") + logt.Error(err, "Instance has problems with its parameters", "service", instance.ObjectMeta.Name) return r.updateStatusError(instance, serviceStateFailed, err) } tags := getTags(instance) logt.Info("ServiceInstance ", "name", externalName, "tags", tags) if serviceClassType == "CF" { - logt.Info("ServiceInstance ", "is CF", instance.ObjectMeta.Name) + logt.Info("ServiceInstance is CF", "instance", instance.ObjectMeta.Name) if instance.Status.InstanceID == "" { // ServiceInstance has not been created on Bluemix // check if using the alias plan, in that case we need to use the existing instance if isAlias(instance) { @@ -243,7 +248,7 @@ func (r *ServiceReconciler) Reconcile(request ctrl.Request) (ctrl.Result, error) return r.updateStatus(session, logt, instance, resourceContext, instanceID, serviceStateOnline, serviceClassType) } // Service is not Alias - logt.Info("Creating ", instance.ObjectMeta.Name, instance.Spec.ServiceClass) + logt.Info("Creating", "instance", instance.ObjectMeta.Name, "service class", instance.Spec.ServiceClass) guid, state, err := r.CreateCFServiceInstance(session, externalName, servicePlanID, spaceID, params, tags) if err != nil { return r.updateStatusError(instance, serviceStateFailed, err) @@ -285,13 +290,13 @@ func (r *ServiceReconciler) Reconcile(request ctrl.Request) (ctrl.Result, error) if instance.Status.InstanceID == "" { // ServiceInstance has not been created on Bluemix // check if using the alias plan, in that case we need to use the existing instance if isAlias(instance) { + logt := logt.WithValues("Name", instance.ObjectMeta.Name) logt.Info("Using `Alias` plan, checking if instance exists") // check if there is an annotation for service ID instanceID := instance.ObjectMeta.GetAnnotations()[instanceIDKey] - logger := logt.WithValues("Name", instance.ObjectMeta.Name) - id, state, err := r.GetResourceServiceAliasInstance(session, instanceID, resourceGroupID, servicePlanID, externalName, logger) + id, state, err := r.GetResourceServiceAliasInstance(session, instanceID, resourceGroupID, servicePlanID, externalName, logt) if _, notFound := err.(resource.NotFoundError); notFound { return r.updateStatusError(instance, serviceStateFailed, errors.Wrapf(err, "no service instances with name %s found for alias plan", instance.ObjectMeta.Name)) } @@ -323,7 +328,7 @@ func (r *ServiceReconciler) Reconcile(request ctrl.Request) (ctrl.Result, error) if _, ok := err.(resource.NotFoundError); ok { // Need to recreate it! if !isAlias(instance) { logt.Info("Recreating ", instance.ObjectMeta.Name, instance.Spec.ServiceClass) - instance.Status.InstanceID = "IN PROGRESS" + instance.Status.InstanceID = inProgress if err := r.Status().Update(ctx, instance); err != nil { logt.Info("Error updating instanceID to be in progress", "Error", err.Error()) return ctrl.Result{}, err @@ -476,7 +481,7 @@ func (r *ServiceReconciler) getParams(ctx context.Context, instance *ibmcloudv1b // paramToJSON converts variable value to JSON value func (r *ServiceReconciler) paramToJSON(ctx context.Context, p ibmcloudv1beta1.Param, namespace string) (interface{}, error) { if p.Value != nil && p.ValueFrom != nil { - return nil, fmt.Errorf("value and ValueFrom properties are mutually exclusive (for %s variable)", p.Name) + return nil, fmt.Errorf("Value and ValueFrom properties are mutually exclusive (for %s variable)", p.Name) } valueFrom := p.ValueFrom @@ -496,18 +501,18 @@ func (r *ServiceReconciler) paramValueToJSON(ctx context.Context, valueFrom ibmc data, err := getKubeSecretValue(ctx, r, r.Log, valueFrom.SecretKeyRef.Name, valueFrom.SecretKeyRef.Key, true, namespace) if err != nil { // Recoverable - return nil, fmt.Errorf("missing secret %s", valueFrom.SecretKeyRef.Name) + return nil, fmt.Errorf("Missing secret %s", valueFrom.SecretKeyRef.Name) } return paramToJSONFromString(string(data)) } else if valueFrom.ConfigMapKeyRef != nil { data, err := getConfigMapValue(ctx, r, r.Log, valueFrom.ConfigMapKeyRef.Name, valueFrom.ConfigMapKeyRef.Key, true, namespace) if err != nil { // Recoverable - return nil, fmt.Errorf("missing configmap %s", valueFrom.ConfigMapKeyRef.Name) + return nil, fmt.Errorf("Missing configmap %s", valueFrom.ConfigMapKeyRef.Name) } return paramToJSONFromString(data) } - return nil, fmt.Errorf("missing secretKeyRef or configMapKeyRef") + return nil, fmt.Errorf("Missing secretKeyRef or configMapKeyRef") } func getTags(instance *ibmcloudv1beta1.Service) []string { diff --git a/controllers/service_controller_test.go b/controllers/service_controller_test.go index b84859e0..351b90df 100644 --- a/controllers/service_controller_test.go +++ b/controllers/service_controller_test.go @@ -2,21 +2,35 @@ package controllers import ( "context" + "encoding/json" "fmt" "path/filepath" "testing" + "time" "github.com/IBM-Cloud/bluemix-go/api/mccp/mccpv2" bxcontroller "github.com/IBM-Cloud/bluemix-go/api/resource/resourcev1/controller" "github.com/IBM-Cloud/bluemix-go/models" + "github.com/IBM-Cloud/bluemix-go/session" "github.com/go-logr/logr" "github.com/go-logr/zapr" ibmcloudv1beta1 "github.com/ibm/cloud-operators/api/v1beta1" + "github.com/ibm/cloud-operators/internal/config" "github.com/ibm/cloud-operators/internal/ibmcloud" + "github.com/ibm/cloud-operators/internal/ibmcloud/cfservice" + "github.com/ibm/cloud-operators/internal/ibmcloud/resource" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/zap/zaptest" + corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" + k8sErrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" ) func TestService(t *testing.T) { @@ -201,3 +215,2007 @@ func TestServiceV1Alpha1Compat(t *testing.T) { _, err = getServiceInstanceFromObj(logger, serviceCopy) assert.True(t, ibmcloud.IsNotFound(err), "Expect service to be deleted") } + +func TestServiceLoadServiceFailed(t *testing.T) { + t.Parallel() + const ( + serviceName = "myservice" + namespace = "mynamespace" + ) + + t.Run("not found error", func(t *testing.T) { + scheme := schemas(t) + objects := []runtime.Object{} + r := &ServiceReconciler{ + Client: fake.NewFakeClientWithScheme(scheme, objects...), + Log: testLogger(t), + Scheme: scheme, + } + + result, err := r.Reconcile(ctrl.Request{ + NamespacedName: types.NamespacedName{Name: serviceName, Namespace: namespace}, + }) + assert.Equal(t, ctrl.Result{}, result) + assert.NoError(t, err) + }) + + t.Run("other error", func(t *testing.T) { + scheme := runtime.NewScheme() + objects := []runtime.Object{} + r := &ServiceReconciler{ + Client: fake.NewFakeClientWithScheme(scheme, objects...), + Log: testLogger(t), + Scheme: scheme, + } + + result, err := r.Reconcile(ctrl.Request{ + NamespacedName: types.NamespacedName{Name: serviceName, Namespace: namespace}, + }) + assert.Equal(t, ctrl.Result{}, result) + assert.Error(t, err) + assert.False(t, k8sErrors.IsNotFound(err)) + }) +} + +func TestServiceSpecChangedAndUpdateFailed(t *testing.T) { + t.Parallel() + const ( + serviceName = "myservice" + namespace = "mynamespace" + ) + + scheme := schemas(t) + objects := []runtime.Object{ + &ibmcloudv1beta1.Service{ + ObjectMeta: metav1.ObjectMeta{Name: serviceName, Namespace: namespace}, + Status: ibmcloudv1beta1.ServiceStatus{ + Plan: "Lite", + }, + }, + } + r := &ServiceReconciler{ + Client: newMockClient( + fake.NewFakeClientWithScheme(scheme, objects...), + MockConfig{UpdateErr: fmt.Errorf("failed")}, + ), + Log: testLogger(t), + Scheme: scheme, + } + + result, err := r.Reconcile(ctrl.Request{ + NamespacedName: types.NamespacedName{Name: serviceName, Namespace: namespace}, + }) + assert.Equal(t, ctrl.Result{}, result) + assert.EqualError(t, err, "failed") +} + +func TestServiceGetIBMCloudInfoFailed(t *testing.T) { + t.Parallel() + const ( + serviceName = "myservice" + namespace = "mynamespace" + ) + + now := metav1Now(t) + scheme := schemas(t) + objects := []runtime.Object{ + &ibmcloudv1beta1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: serviceName, + Namespace: namespace, + DeletionTimestamp: now, + Finalizers: []string{serviceFinalizer}, + }, + Status: ibmcloudv1beta1.ServiceStatus{Plan: "Lite"}, + Spec: ibmcloudv1beta1.ServiceSpec{Plan: "Lite"}, + }, + } + + t.Run("not found error", func(t *testing.T) { + r := &ServiceReconciler{ + Client: newMockClient( + fake.NewFakeClientWithScheme(scheme, objects...), + MockConfig{UpdateErr: fmt.Errorf("failed")}, + ), + Log: testLogger(t), + Scheme: scheme, + + GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1beta1.Service) (*ibmcloud.Info, error) { + return nil, errors.NewNotFound(ctrl.GroupResource{Group: "ibmcloud.ibm.com", Resource: "secret"}, "secret-ibm-cloud-operator") + }, + } + + result, err := r.Reconcile(ctrl.Request{ + NamespacedName: types.NamespacedName{Name: serviceName, Namespace: namespace}, + }) + assert.Equal(t, ctrl.Result{}, result) + assert.NoError(t, err) + assert.Equal(t, &ibmcloudv1beta1.Service{ + TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + ObjectMeta: metav1.ObjectMeta{ + Name: serviceName, + Namespace: namespace, + DeletionTimestamp: now, + Finalizers: nil, // attempt to remove finalizers + }, + Status: ibmcloudv1beta1.ServiceStatus{ + //State: serviceStateFailed, // TODO this state should be set! + Plan: "Lite", + }, + Spec: ibmcloudv1beta1.ServiceSpec{Plan: "Lite"}, + }, r.Client.(MockClient).LastUpdate()) + assert.Equal(t, nil, r.Client.(MockClient).LastStatusUpdate()) + }) + + t.Run("other error", func(t *testing.T) { + fakeClient := newMockClient( + fake.NewFakeClientWithScheme(scheme, objects...), + MockConfig{}, + ) + r := &ServiceReconciler{ + Client: fakeClient, + Log: testLogger(t), + Scheme: scheme, + + GetIBMCloudInfo: func(logt logr.Logger, r client.Client, instance *ibmcloudv1beta1.Service) (*ibmcloud.Info, error) { + return nil, fmt.Errorf("failed") + }, + } + + result, err := r.Reconcile(ctrl.Request{ + NamespacedName: types.NamespacedName{Name: serviceName, Namespace: namespace}, + }) + assert.Equal(t, ctrl.Result{ + Requeue: true, + RequeueAfter: config.Get().SyncPeriod, + }, result) + assert.NoError(t, err) + assert.Equal(t, &ibmcloudv1beta1.Service{ + TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + ObjectMeta: metav1.ObjectMeta{ + Name: serviceName, + Namespace: namespace, + DeletionTimestamp: now, + Finalizers: []string{serviceFinalizer}, + }, + Status: ibmcloudv1beta1.ServiceStatus{ + State: serviceStateFailed, + Message: "failed", + Plan: "Lite", + }, + Spec: ibmcloudv1beta1.ServiceSpec{Plan: "Lite"}, + }, fakeClient.LastStatusUpdate()) + }) +} + +func TestServiceFirstStatusFailed(t *testing.T) { + t.Parallel() + const ( + serviceName = "myservice" + namespace = "mynamespace" + ) + + scheme := schemas(t) + objects := []runtime.Object{ + &ibmcloudv1beta1.Service{ + ObjectMeta: metav1.ObjectMeta{Name: serviceName, Namespace: namespace}, + Status: ibmcloudv1beta1.ServiceStatus{}, + }, + } + r := &ServiceReconciler{ + Client: newMockClient( + fake.NewFakeClientWithScheme(scheme, objects...), + MockConfig{StatusUpdateErr: fmt.Errorf("failed")}, + ), + Log: testLogger(t), + Scheme: scheme, + + GetIBMCloudInfo: func(logt logr.Logger, r client.Client, instance *ibmcloudv1beta1.Service) (*ibmcloud.Info, error) { + return &ibmcloud.Info{}, nil + }, + } + + result, err := r.Reconcile(ctrl.Request{ + NamespacedName: types.NamespacedName{Name: serviceName, Namespace: namespace}, + }) + assert.Equal(t, ctrl.Result{}, result) + assert.EqualError(t, err, "failed") +} + +func TestServiceEnsureFinalizerFailed(t *testing.T) { + t.Parallel() + const ( + serviceName = "myservice" + namespace = "mynamespace" + ) + + scheme := schemas(t) + objects := []runtime.Object{ + &ibmcloudv1beta1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: serviceName, + Namespace: namespace, + DeletionTimestamp: nil, // not deleting + Finalizers: nil, // AND missing finalizer + }, + Status: ibmcloudv1beta1.ServiceStatus{Plan: "Lite"}, + Spec: ibmcloudv1beta1.ServiceSpec{Plan: "Lite"}, + }, + } + var r *ServiceReconciler + r = &ServiceReconciler{ + Client: fake.NewFakeClientWithScheme(scheme, objects...), + Log: testLogger(t), + Scheme: scheme, + + GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1beta1.Service) (*ibmcloud.Info, error) { + r.Client = newMockClient( + fake.NewFakeClientWithScheme(scheme, objects...), + MockConfig{UpdateErr: fmt.Errorf("failed")}, + ) + return &ibmcloud.Info{}, nil + }, + } + + result, err := r.Reconcile(ctrl.Request{ + NamespacedName: types.NamespacedName{Name: serviceName, Namespace: namespace}, + }) + assert.Equal(t, ctrl.Result{}, result) + assert.EqualError(t, err, "failed") + assert.Equal(t, &ibmcloudv1beta1.Service{ + TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + ObjectMeta: metav1.ObjectMeta{ + Name: serviceName, + Namespace: namespace, + Finalizers: []string{serviceFinalizer}, + }, + Status: ibmcloudv1beta1.ServiceStatus{ + Plan: "Lite", + }, + Spec: ibmcloudv1beta1.ServiceSpec{Plan: "Lite"}, + }, r.Client.(MockClient).LastUpdate()) +} + +func TestServiceDeletingFailed(t *testing.T) { + t.Parallel() + const ( + serviceName = "myservice" + namespace = "mynamespace" + ) + + t.Run("service delete failed", func(t *testing.T) { + scheme := schemas(t) + now := metav1Now(t) + objects := []runtime.Object{ + &ibmcloudv1beta1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: serviceName, + Namespace: namespace, + DeletionTimestamp: now, + Finalizers: []string{serviceFinalizer}, + }, + Status: ibmcloudv1beta1.ServiceStatus{Plan: "Lite", InstanceID: "myinstanceid"}, + Spec: ibmcloudv1beta1.ServiceSpec{Plan: "Lite"}, + }, + } + + var r *ServiceReconciler + r = &ServiceReconciler{ + Client: fake.NewFakeClientWithScheme(scheme, objects...), + Log: testLogger(t), + Scheme: scheme, + + GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1beta1.Service) (*ibmcloud.Info, error) { + r.Client = newMockClient( + fake.NewFakeClientWithScheme(scheme, objects...), + MockConfig{}, + ) + return &ibmcloud.Info{}, nil + }, + DeleteResourceServiceInstance: func(session *session.Session, instanceID string, logt logr.Logger) error { + return fmt.Errorf("failed") + }, + } + + result, err := r.Reconcile(ctrl.Request{ + NamespacedName: types.NamespacedName{Name: serviceName, Namespace: namespace}, + }) + assert.Equal(t, ctrl.Result{ + Requeue: true, + RequeueAfter: 10 * time.Second, + }, result) + assert.NoError(t, err) + }) + + t.Run("update failed", func(t *testing.T) { + scheme := schemas(t) + now := metav1Now(t) + objects := []runtime.Object{ + &ibmcloudv1beta1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: serviceName, + Namespace: namespace, + DeletionTimestamp: now, + Finalizers: []string{serviceFinalizer}, + }, + Status: ibmcloudv1beta1.ServiceStatus{Plan: "Lite"}, + Spec: ibmcloudv1beta1.ServiceSpec{Plan: "Lite"}, + }, + } + + var r *ServiceReconciler + r = &ServiceReconciler{ + Client: fake.NewFakeClientWithScheme(scheme, objects...), + Log: testLogger(t), + Scheme: scheme, + + GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1beta1.Service) (*ibmcloud.Info, error) { + r.Client = newMockClient( + fake.NewFakeClientWithScheme(scheme, objects...), + MockConfig{UpdateErr: fmt.Errorf("failed")}, + ) + return &ibmcloud.Info{}, nil + }, + } + + result, err := r.Reconcile(ctrl.Request{ + NamespacedName: types.NamespacedName{Name: serviceName, Namespace: namespace}, + }) + assert.Equal(t, ctrl.Result{}, result) + assert.EqualError(t, err, "failed") + assert.Equal(t, &ibmcloudv1beta1.Service{ + TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + ObjectMeta: metav1.ObjectMeta{ + Name: serviceName, + Namespace: namespace, + DeletionTimestamp: now, + Finalizers: nil, // attempt to remove finalizers + }, + Status: ibmcloudv1beta1.ServiceStatus{ + Plan: "Lite", + }, + Spec: ibmcloudv1beta1.ServiceSpec{Plan: "Lite"}, + }, r.Client.(MockClient).LastUpdate()) + }) +} + +func TestServiceGetParamsFailed(t *testing.T) { + t.Parallel() + const ( + serviceName = "myservice" + namespace = "mynamespace" + ) + + scheme := schemas(t) + objects := []runtime.Object{ + &ibmcloudv1beta1.Service{ + ObjectMeta: metav1.ObjectMeta{Name: serviceName, Namespace: namespace}, + Status: ibmcloudv1beta1.ServiceStatus{ + Plan: "Lite", + Parameters: []ibmcloudv1beta1.Param{ + { + Name: "hello", + Value: &ibmcloudv1beta1.ParamValue{RawMessage: json.RawMessage(`"world"`)}, + ValueFrom: &ibmcloudv1beta1.ParamSource{}, + }, + }, + }, + Spec: ibmcloudv1beta1.ServiceSpec{ + Plan: "Lite", + Parameters: []ibmcloudv1beta1.Param{ + { + Name: "hello", + Value: &ibmcloudv1beta1.ParamValue{RawMessage: json.RawMessage(`"world"`)}, + ValueFrom: &ibmcloudv1beta1.ParamSource{}, + }, + }, + }, + }, + } + r := &ServiceReconciler{ + Client: newMockClient( + fake.NewFakeClientWithScheme(scheme, objects...), + MockConfig{}, + ), + Log: testLogger(t), + Scheme: scheme, + + GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1beta1.Service) (*ibmcloud.Info, error) { + return &ibmcloud.Info{}, nil + }, + } + + result, err := r.Reconcile(ctrl.Request{ + NamespacedName: types.NamespacedName{Name: serviceName, Namespace: namespace}, + }) + assert.Equal(t, ctrl.Result{ + Requeue: true, + RequeueAfter: config.Get().SyncPeriod, + }, result) + assert.NoError(t, err) + assert.Equal(t, &ibmcloudv1beta1.Service{ + TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + ObjectMeta: metav1.ObjectMeta{ + Name: serviceName, + Namespace: namespace, + Finalizers: []string{serviceFinalizer}, + }, + Status: ibmcloudv1beta1.ServiceStatus{ + State: serviceStateFailed, + Message: "Value and ValueFrom properties are mutually exclusive (for hello variable)", + Plan: "Lite", + Parameters: []ibmcloudv1beta1.Param{ + { + Name: "hello", + Value: &ibmcloudv1beta1.ParamValue{RawMessage: json.RawMessage(`"world"`)}, + ValueFrom: &ibmcloudv1beta1.ParamSource{}, + }, + }, + }, + Spec: ibmcloudv1beta1.ServiceSpec{ + Plan: "Lite", + Parameters: []ibmcloudv1beta1.Param{ + { + Name: "hello", + Value: &ibmcloudv1beta1.ParamValue{RawMessage: json.RawMessage(`"world"`)}, + ValueFrom: &ibmcloudv1beta1.ParamSource{}, + }, + }, + }, + }, r.Client.(MockClient).LastStatusUpdate()) +} + +func TestServiceEnsureCFServiceExists(t *testing.T) { + t.Parallel() + const ( + serviceName = "myservice" + namespace = "mynamespace" + ) + + t.Run("create - empty service ID", func(t *testing.T) { + scheme := schemas(t) + objects := []runtime.Object{ + &ibmcloudv1beta1.Service{ + ObjectMeta: metav1.ObjectMeta{Name: serviceName, Namespace: namespace}, + Status: ibmcloudv1beta1.ServiceStatus{Plan: "Lite", ServiceClass: "service-name"}, + Spec: ibmcloudv1beta1.ServiceSpec{Plan: "Lite", ServiceClass: "service-name"}, + }, + } + var createErr error + r := &ServiceReconciler{ + Client: newMockClient( + fake.NewFakeClientWithScheme(scheme, objects...), + MockConfig{}, + ), + Log: testLogger(t), + Scheme: scheme, + + GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1beta1.Service) (*ibmcloud.Info, error) { + return &ibmcloud.Info{ + ServiceClassType: "CF", + }, nil + }, + CreateCFServiceInstance: func(session *session.Session, externalName, planID, spaceID string, params map[string]interface{}, tags []string) (guid string, state string, err error) { + return "guid", "state", createErr + }, + } + + t.Run("success", func(t *testing.T) { + createErr = nil + result, err := r.Reconcile(ctrl.Request{ + NamespacedName: types.NamespacedName{Name: serviceName, Namespace: namespace}, + }) + assert.Equal(t, ctrl.Result{ + Requeue: true, + RequeueAfter: config.Get().SyncPeriod, + }, result) + assert.NoError(t, err) + assert.Equal(t, &ibmcloudv1beta1.Service{ + TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + ObjectMeta: metav1.ObjectMeta{ + Name: serviceName, + Namespace: namespace, + Finalizers: []string{serviceFinalizer}, + }, + Status: ibmcloudv1beta1.ServiceStatus{ + State: "state", + Message: "state", + Plan: "Lite", + InstanceID: "guid", + DashboardURL: "https://cloud.ibm.com/services/service-name/guid", + ServiceClass: "service-name", + }, + Spec: ibmcloudv1beta1.ServiceSpec{Plan: "Lite", ServiceClass: "service-name"}, + }, r.Client.(MockClient).LastStatusUpdate()) + }) + + t.Run("failed", func(t *testing.T) { + createErr = fmt.Errorf("failed") + result, err := r.Reconcile(ctrl.Request{ + NamespacedName: types.NamespacedName{Name: serviceName, Namespace: namespace}, + }) + assert.Equal(t, ctrl.Result{ + Requeue: true, + RequeueAfter: config.Get().SyncPeriod, + }, result) + assert.NoError(t, err) + assert.Equal(t, &ibmcloudv1beta1.Service{ + TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + ObjectMeta: metav1.ObjectMeta{ + Name: serviceName, + Namespace: namespace, + Finalizers: []string{serviceFinalizer}, + }, + Status: ibmcloudv1beta1.ServiceStatus{ + State: serviceStateFailed, + Message: "failed", + Plan: "Lite", + ServiceClass: "service-name", + }, + Spec: ibmcloudv1beta1.ServiceSpec{Plan: "Lite", ServiceClass: "service-name"}, + }, r.Client.(MockClient).LastStatusUpdate()) + }) + }) + + t.Run("create alias success", func(t *testing.T) { + scheme := schemas(t) + objects := []runtime.Object{ + &ibmcloudv1beta1.Service{ + ObjectMeta: metav1.ObjectMeta{Name: serviceName, Namespace: namespace}, + Status: ibmcloudv1beta1.ServiceStatus{ + Plan: aliasPlan, + ServiceClass: "service-name", + InstanceID: "guid", + }, + Spec: ibmcloudv1beta1.ServiceSpec{ + Plan: aliasPlan, + ServiceClass: "service-name", + }, + }, + } + r := &ServiceReconciler{ + Client: newMockClient( + fake.NewFakeClientWithScheme(scheme, objects...), + MockConfig{}, + ), + Log: testLogger(t), + Scheme: scheme, + + GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1beta1.Service) (*ibmcloud.Info, error) { + return &ibmcloud.Info{ + ServiceClassType: "CF", + }, nil + }, + CreateCFServiceInstance: func(session *session.Session, externalName, planID, spaceID string, params map[string]interface{}, tags []string) (guid string, state string, err error) { + return "", "", fmt.Errorf("failed") + }, + GetCFServiceInstance: func(session *session.Session, name string) (guid string, state string, err error) { + return "guid", "state", nil + }, + } + + result, err := r.Reconcile(ctrl.Request{ + NamespacedName: types.NamespacedName{Name: serviceName, Namespace: namespace}, + }) + assert.Equal(t, ctrl.Result{ + Requeue: true, + RequeueAfter: config.Get().SyncPeriod, + }, result) + assert.NoError(t, err) + assert.Equal(t, &ibmcloudv1beta1.Service{ + TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + ObjectMeta: metav1.ObjectMeta{ + Name: serviceName, + Namespace: namespace, + Finalizers: []string{serviceFinalizer}, + }, + Status: ibmcloudv1beta1.ServiceStatus{ + State: "state", // TODO(johnstarich) This isn't a known state, right? We should have predictable states here. + Message: "state", + DashboardURL: "https://cloud.ibm.com/services/service-name/guid", + Plan: aliasPlan, + ServiceClass: "service-name", + InstanceID: "guid", + }, + Spec: ibmcloudv1beta1.ServiceSpec{ + Plan: aliasPlan, + ServiceClass: "service-name", + }, + }, r.Client.(MockClient).LastStatusUpdate()) + }) + + t.Run("ensure alias - empty instance ID", func(t *testing.T) { + scheme := schemas(t) + objects := []runtime.Object{ + &ibmcloudv1beta1.Service{ + ObjectMeta: metav1.ObjectMeta{Name: serviceName, Namespace: namespace}, + Status: ibmcloudv1beta1.ServiceStatus{ + Plan: aliasPlan, + ServiceClass: "service-name", + InstanceID: "", // no instance ID set + }, + Spec: ibmcloudv1beta1.ServiceSpec{ + Plan: aliasPlan, + ServiceClass: "service-name", + }, + }, + } + var getInstanceErr error + r := &ServiceReconciler{ + Client: newMockClient( + fake.NewFakeClientWithScheme(scheme, objects...), + MockConfig{}, + ), + Log: testLogger(t), + Scheme: scheme, + + GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1beta1.Service) (*ibmcloud.Info, error) { + return &ibmcloud.Info{ + ServiceClassType: "CF", + }, nil + }, + GetCFServiceInstance: func(session *session.Session, name string) (guid string, state string, err error) { + return "guid", "state", getInstanceErr + }, + } + + t.Run("success", func(t *testing.T) { + getInstanceErr = nil + result, err := r.Reconcile(ctrl.Request{ + NamespacedName: types.NamespacedName{Name: serviceName, Namespace: namespace}, + }) + assert.Equal(t, ctrl.Result{ + Requeue: true, + RequeueAfter: config.Get().SyncPeriod, + }, result) + assert.NoError(t, err) + assert.Equal(t, &ibmcloudv1beta1.Service{ + TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + ObjectMeta: metav1.ObjectMeta{ + Name: serviceName, + Namespace: namespace, + Finalizers: []string{serviceFinalizer}, + }, + Status: ibmcloudv1beta1.ServiceStatus{ + State: serviceStateOnline, + Message: serviceStateOnline, + Plan: aliasPlan, + ServiceClass: "service-name", + InstanceID: "guid", + DashboardURL: "https://cloud.ibm.com/services/service-name/guid", + }, + Spec: ibmcloudv1beta1.ServiceSpec{ + Plan: aliasPlan, + ServiceClass: "service-name", + }, + }, r.Client.(MockClient).LastStatusUpdate()) + }) + + t.Run("failed", func(t *testing.T) { + getInstanceErr = fmt.Errorf("failed") + result, err := r.Reconcile(ctrl.Request{ + NamespacedName: types.NamespacedName{Name: serviceName, Namespace: namespace}, + }) + assert.Equal(t, ctrl.Result{ + Requeue: true, + RequeueAfter: config.Get().SyncPeriod, + }, result) + assert.NoError(t, err) + assert.Equal(t, &ibmcloudv1beta1.Service{ + TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + ObjectMeta: metav1.ObjectMeta{ + Name: serviceName, + Namespace: namespace, + Finalizers: []string{serviceFinalizer}, + }, + Status: ibmcloudv1beta1.ServiceStatus{ + State: serviceStateFailed, + Message: "failed", + Plan: aliasPlan, + ServiceClass: "service-name", + InstanceID: "", + }, + Spec: ibmcloudv1beta1.ServiceSpec{ + Plan: aliasPlan, + ServiceClass: "service-name", + }, + }, r.Client.(MockClient).LastStatusUpdate()) + }) + }) + + t.Run("get instance failed - not found", func(t *testing.T) { + scheme := schemas(t) + objects := []runtime.Object{ + &ibmcloudv1beta1.Service{ + ObjectMeta: metav1.ObjectMeta{Name: serviceName, Namespace: namespace}, + Status: ibmcloudv1beta1.ServiceStatus{ + Plan: "Lite", + ServiceClass: "service-name", + InstanceID: "guid", + }, + Spec: ibmcloudv1beta1.ServiceSpec{ + Plan: "Lite", + ServiceClass: "service-name", + }, + }, + } + r := &ServiceReconciler{ + Client: newMockClient( + fake.NewFakeClientWithScheme(scheme, objects...), + MockConfig{}, + ), + Log: testLogger(t), + Scheme: scheme, + + GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1beta1.Service) (*ibmcloud.Info, error) { + return &ibmcloud.Info{ + ServiceClassType: "CF", + }, nil + }, + CreateCFServiceInstance: func(session *session.Session, externalName, planID, spaceID string, params map[string]interface{}, tags []string) (guid string, state string, err error) { + return "guid", "state", nil + }, + GetCFServiceInstance: func(session *session.Session, name string) (guid string, state string, err error) { + return "", "", cfservice.NotFoundError{Err: fmt.Errorf("failed")} + }, + } + + result, err := r.Reconcile(ctrl.Request{ + NamespacedName: types.NamespacedName{Name: serviceName, Namespace: namespace}, + }) + assert.Equal(t, ctrl.Result{ + Requeue: true, + RequeueAfter: config.Get().SyncPeriod, + }, result) + assert.NoError(t, err) + assert.Equal(t, &ibmcloudv1beta1.Service{ + TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + ObjectMeta: metav1.ObjectMeta{ + Name: serviceName, + Namespace: namespace, + Finalizers: []string{serviceFinalizer}, + }, + Status: ibmcloudv1beta1.ServiceStatus{ + State: "state", + Message: "state", + Plan: "Lite", + InstanceID: "guid", + ServiceClass: "service-name", + DashboardURL: "https://cloud.ibm.com/services/service-name/guid", + }, + Spec: ibmcloudv1beta1.ServiceSpec{Plan: "Lite", ServiceClass: "service-name"}, + }, r.Client.(MockClient).LastStatusUpdate()) + }) + + t.Run("get instance failed - not found, create failed", func(t *testing.T) { + scheme := schemas(t) + objects := []runtime.Object{ + &ibmcloudv1beta1.Service{ + ObjectMeta: metav1.ObjectMeta{Name: serviceName, Namespace: namespace}, + Status: ibmcloudv1beta1.ServiceStatus{ + Plan: "Lite", + ServiceClass: "service-name", + InstanceID: "guid", + }, + Spec: ibmcloudv1beta1.ServiceSpec{ + Plan: "Lite", + ServiceClass: "service-name", + }, + }, + } + r := &ServiceReconciler{ + Client: newMockClient( + fake.NewFakeClientWithScheme(scheme, objects...), + MockConfig{}, + ), + Log: testLogger(t), + Scheme: scheme, + + GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1beta1.Service) (*ibmcloud.Info, error) { + return &ibmcloud.Info{ + ServiceClassType: "CF", + }, nil + }, + CreateCFServiceInstance: func(session *session.Session, externalName, planID, spaceID string, params map[string]interface{}, tags []string) (guid string, state string, err error) { + return "", "", fmt.Errorf("failed") + }, + GetCFServiceInstance: func(session *session.Session, name string) (guid string, state string, err error) { + return "", "", cfservice.NotFoundError{Err: fmt.Errorf("failed")} + }, + } + + result, err := r.Reconcile(ctrl.Request{ + NamespacedName: types.NamespacedName{Name: serviceName, Namespace: namespace}, + }) + assert.Equal(t, ctrl.Result{ + Requeue: true, + RequeueAfter: config.Get().SyncPeriod, + }, result) + assert.NoError(t, err) + assert.Equal(t, &ibmcloudv1beta1.Service{ + TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + ObjectMeta: metav1.ObjectMeta{ + Name: serviceName, + Namespace: namespace, + Finalizers: []string{serviceFinalizer}, + }, + Status: ibmcloudv1beta1.ServiceStatus{ + State: serviceStateFailed, + Message: "failed", + Plan: "Lite", + InstanceID: "guid", + ServiceClass: "service-name", + }, + Spec: ibmcloudv1beta1.ServiceSpec{Plan: "Lite", ServiceClass: "service-name"}, + }, r.Client.(MockClient).LastStatusUpdate()) + }) + + t.Run("get instance failed - other error", func(t *testing.T) { + scheme := schemas(t) + objects := []runtime.Object{ + &ibmcloudv1beta1.Service{ + ObjectMeta: metav1.ObjectMeta{Name: serviceName, Namespace: namespace}, + Status: ibmcloudv1beta1.ServiceStatus{ + Plan: "Lite", + ServiceClass: "service-name", + InstanceID: "guid", + }, + Spec: ibmcloudv1beta1.ServiceSpec{ + Plan: "Lite", + ServiceClass: "service-name", + }, + }, + } + r := &ServiceReconciler{ + Client: newMockClient( + fake.NewFakeClientWithScheme(scheme, objects...), + MockConfig{}, + ), + Log: testLogger(t), + Scheme: scheme, + + GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1beta1.Service) (*ibmcloud.Info, error) { + return &ibmcloud.Info{ + ServiceClassType: "CF", + }, nil + }, + GetCFServiceInstance: func(session *session.Session, name string) (guid string, state string, err error) { + return "", "", fmt.Errorf("failed") + }, + } + + result, err := r.Reconcile(ctrl.Request{ + NamespacedName: types.NamespacedName{Name: serviceName, Namespace: namespace}, + }) + assert.Equal(t, ctrl.Result{ + Requeue: true, + RequeueAfter: config.Get().SyncPeriod, + }, result) + assert.NoError(t, err) + assert.Equal(t, &ibmcloudv1beta1.Service{ + TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + ObjectMeta: metav1.ObjectMeta{ + Name: serviceName, + Namespace: namespace, + Finalizers: []string{serviceFinalizer}, + }, + Status: ibmcloudv1beta1.ServiceStatus{ + State: serviceStateFailed, + Message: "failed", + Plan: "Lite", + InstanceID: "guid", + ServiceClass: "service-name", + }, + Spec: ibmcloudv1beta1.ServiceSpec{Plan: "Lite", ServiceClass: "service-name"}, + }, r.Client.(MockClient).LastStatusUpdate()) + }) + + t.Run("ensure alias - instance does not exist", func(t *testing.T) { + scheme := schemas(t) + objects := []runtime.Object{ + &ibmcloudv1beta1.Service{ + ObjectMeta: metav1.ObjectMeta{Name: serviceName, Namespace: namespace}, + Status: ibmcloudv1beta1.ServiceStatus{ + Plan: aliasPlan, + ServiceClass: "service-name", + InstanceID: "some-instance-id", // instance ID set + }, + Spec: ibmcloudv1beta1.ServiceSpec{ + Plan: aliasPlan, + ServiceClass: "service-name", + }, + }, + } + r := &ServiceReconciler{ + Client: newMockClient( + fake.NewFakeClientWithScheme(scheme, objects...), + MockConfig{}, + ), + Log: testLogger(t), + Scheme: scheme, + + GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1beta1.Service) (*ibmcloud.Info, error) { + return &ibmcloud.Info{ + ServiceClassType: "CF", + }, nil + }, + GetCFServiceInstance: func(session *session.Session, name string) (guid string, state string, err error) { + return "", "", cfservice.NotFoundError{Err: fmt.Errorf("failed")} + }, + } + + result, err := r.Reconcile(ctrl.Request{ + NamespacedName: types.NamespacedName{Name: serviceName, Namespace: namespace}, + }) + assert.Equal(t, ctrl.Result{ + Requeue: true, + RequeueAfter: config.Get().SyncPeriod, + }, result) + assert.NoError(t, err) + assert.Equal(t, &ibmcloudv1beta1.Service{ + TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + ObjectMeta: metav1.ObjectMeta{ + Name: serviceName, + Namespace: namespace, + Finalizers: []string{serviceFinalizer}, + }, + Status: ibmcloudv1beta1.ServiceStatus{ + State: serviceStatePending, + Message: "failed", + Plan: aliasPlan, + ServiceClass: "service-name", + InstanceID: "", // instance ID should be deleted + }, + Spec: ibmcloudv1beta1.ServiceSpec{ + Plan: aliasPlan, + ServiceClass: "service-name", + }, + }, r.Client.(MockClient).LastStatusUpdate()) + }) +} + +func TestServiceEnsureResourceServiceInstance(t *testing.T) { + t.Parallel() + const ( + serviceName = "myservice" + namespace = "mynamespace" + ) + + t.Run("alias", func(t *testing.T) { + scheme := schemas(t) + objects := []runtime.Object{ + &ibmcloudv1beta1.Service{ + ObjectMeta: metav1.ObjectMeta{Name: serviceName, Namespace: namespace}, + Status: ibmcloudv1beta1.ServiceStatus{ + Plan: aliasPlan, + ServiceClass: "service-name", + }, + Spec: ibmcloudv1beta1.ServiceSpec{ + Plan: aliasPlan, + ServiceClass: "service-name", + }, + }, + } + + t.Run("success", func(t *testing.T) { + r := &ServiceReconciler{ + Client: newMockClient( + fake.NewFakeClientWithScheme(scheme, objects...), + MockConfig{}, + ), + Log: testLogger(t), + Scheme: scheme, + + GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1beta1.Service) (*ibmcloud.Info, error) { + return &ibmcloud.Info{}, nil + }, + GetResourceServiceAliasInstance: func(session *session.Session, instanceID, resourceGroupID, servicePlanID, externalName string, logt logr.Logger) (id string, state string, err error) { + return "guid", "state", nil + }, + } + result, err := r.Reconcile(ctrl.Request{ + NamespacedName: types.NamespacedName{Name: serviceName, Namespace: namespace}, + }) + + assert.Equal(t, ctrl.Result{ + Requeue: true, + RequeueAfter: config.Get().SyncPeriod, + }, result) + assert.NoError(t, err) + assert.Equal(t, &ibmcloudv1beta1.Service{ + TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + ObjectMeta: metav1.ObjectMeta{ + Name: serviceName, + Namespace: namespace, + Finalizers: []string{serviceFinalizer}, + }, + Status: ibmcloudv1beta1.ServiceStatus{ + State: "state", + Message: "state", + Plan: aliasPlan, + ServiceClass: "service-name", + InstanceID: "guid", + DashboardURL: "https://cloud.ibm.com/services/service-name/guid", + }, + Spec: ibmcloudv1beta1.ServiceSpec{ + Plan: aliasPlan, + ServiceClass: "service-name", + }, + }, r.Client.(MockClient).LastStatusUpdate()) + }) + + t.Run("not found", func(t *testing.T) { + r := &ServiceReconciler{ + Client: newMockClient( + fake.NewFakeClientWithScheme(scheme, objects...), + MockConfig{}, + ), + Log: testLogger(t), + Scheme: scheme, + + GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1beta1.Service) (*ibmcloud.Info, error) { + return &ibmcloud.Info{}, nil + }, + GetResourceServiceAliasInstance: func(session *session.Session, instanceID, resourceGroupID, servicePlanID, externalName string, logt logr.Logger) (id string, state string, err error) { + return "", "", resource.NotFoundError{Err: fmt.Errorf("failed")} + }, + } + result, err := r.Reconcile(ctrl.Request{ + NamespacedName: types.NamespacedName{Name: serviceName, Namespace: namespace}, + }) + + assert.Equal(t, ctrl.Result{ + Requeue: true, + RequeueAfter: config.Get().SyncPeriod, + }, result) + assert.NoError(t, err) + assert.Equal(t, &ibmcloudv1beta1.Service{ + TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + ObjectMeta: metav1.ObjectMeta{ + Name: serviceName, + Namespace: namespace, + Finalizers: []string{serviceFinalizer}, + }, + Status: ibmcloudv1beta1.ServiceStatus{ + State: serviceStateFailed, + Message: "no service instances with name myservice found for alias plan: failed", + Plan: aliasPlan, + ServiceClass: "service-name", + }, + Spec: ibmcloudv1beta1.ServiceSpec{ + Plan: aliasPlan, + ServiceClass: "service-name", + }, + }, r.Client.(MockClient).LastStatusUpdate()) + }) + + t.Run("other error", func(t *testing.T) { + r := &ServiceReconciler{ + Client: newMockClient( + fake.NewFakeClientWithScheme(scheme, objects...), + MockConfig{}, + ), + Log: testLogger(t), + Scheme: scheme, + + GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1beta1.Service) (*ibmcloud.Info, error) { + return &ibmcloud.Info{}, nil + }, + GetResourceServiceAliasInstance: func(session *session.Session, instanceID, resourceGroupID, servicePlanID, externalName string, logt logr.Logger) (id string, state string, err error) { + return "", "", fmt.Errorf("failed") + }, + } + result, err := r.Reconcile(ctrl.Request{ + NamespacedName: types.NamespacedName{Name: serviceName, Namespace: namespace}, + }) + + assert.Equal(t, ctrl.Result{ + Requeue: true, + RequeueAfter: config.Get().SyncPeriod, + }, result) + assert.NoError(t, err) + assert.Equal(t, &ibmcloudv1beta1.Service{ + TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + ObjectMeta: metav1.ObjectMeta{ + Name: serviceName, + Namespace: namespace, + Finalizers: []string{serviceFinalizer}, + }, + Status: ibmcloudv1beta1.ServiceStatus{ + State: serviceStateFailed, + Message: "failed to resolve Alias plan instance myservice: failed", + Plan: aliasPlan, + ServiceClass: "service-name", + }, + Spec: ibmcloudv1beta1.ServiceSpec{ + Plan: aliasPlan, + ServiceClass: "service-name", + }, + }, r.Client.(MockClient).LastStatusUpdate()) + }) + }) + + t.Run("non-alias", func(t *testing.T) { + scheme := schemas(t) + objects := []runtime.Object{ + &ibmcloudv1beta1.Service{ + ObjectMeta: metav1.ObjectMeta{Name: serviceName, Namespace: namespace}, + Status: ibmcloudv1beta1.ServiceStatus{ + Plan: "Lite", + ServiceClass: "service-name", + }, + Spec: ibmcloudv1beta1.ServiceSpec{ + Plan: "Lite", + ServiceClass: "service-name", + }, + }, + } + + t.Run("success", func(t *testing.T) { + r := &ServiceReconciler{ + Client: newMockClient( + fake.NewFakeClientWithScheme(scheme, objects...), + MockConfig{}, + ), + Log: testLogger(t), + Scheme: scheme, + + GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1beta1.Service) (*ibmcloud.Info, error) { + return &ibmcloud.Info{}, nil + }, + CreateResourceServiceInstance: func(session *session.Session, externalName, servicePlanID, resourceGroupID, targetCrn string, params map[string]interface{}, tags []string) (id string, state string, err error) { + return "id", "state", nil + }, + } + + result, err := r.Reconcile(ctrl.Request{ + NamespacedName: types.NamespacedName{Name: serviceName, Namespace: namespace}, + }) + assert.Equal(t, ctrl.Result{ + Requeue: true, + RequeueAfter: config.Get().SyncPeriod, + }, result) + assert.NoError(t, err) + assert.Equal(t, &ibmcloudv1beta1.Service{ + TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + ObjectMeta: metav1.ObjectMeta{ + Name: serviceName, + Namespace: namespace, + Finalizers: []string{serviceFinalizer}, + }, + Status: ibmcloudv1beta1.ServiceStatus{ + State: "state", + Message: "state", + Plan: "Lite", + ServiceClass: "service-name", + InstanceID: "id", + DashboardURL: "https://cloud.ibm.com/services/service-name/id", + }, + Spec: ibmcloudv1beta1.ServiceSpec{ + Plan: "Lite", + ServiceClass: "service-name", + }, + }, r.Client.(MockClient).LastStatusUpdate()) + }) + + t.Run("update status failed", func(t *testing.T) { + r := &ServiceReconciler{ + Client: newMockClient( + fake.NewFakeClientWithScheme(scheme, objects...), + MockConfig{StatusUpdateErr: fmt.Errorf("failed")}, + ), + Log: testLogger(t), + Scheme: scheme, + + GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1beta1.Service) (*ibmcloud.Info, error) { + return &ibmcloud.Info{}, nil + }, + } + + result, err := r.Reconcile(ctrl.Request{ + NamespacedName: types.NamespacedName{Name: serviceName, Namespace: namespace}, + }) + assert.Equal(t, ctrl.Result{}, result) + assert.EqualError(t, err, "failed") + assert.Equal(t, &ibmcloudv1beta1.Service{ + TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + ObjectMeta: metav1.ObjectMeta{ + Name: serviceName, + Namespace: namespace, + Finalizers: []string{serviceFinalizer}, + }, + Status: ibmcloudv1beta1.ServiceStatus{ + State: "", + Message: "", + Plan: "Lite", + ServiceClass: "service-name", + InstanceID: inProgress, + }, + Spec: ibmcloudv1beta1.ServiceSpec{ + Plan: "Lite", + ServiceClass: "service-name", + }, + }, r.Client.(MockClient).LastStatusUpdate()) + }) + + t.Run("create failed", func(t *testing.T) { + r := &ServiceReconciler{ + Client: newMockClient( + fake.NewFakeClientWithScheme(scheme, objects...), + MockConfig{}, + ), + Log: testLogger(t), + Scheme: scheme, + + GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1beta1.Service) (*ibmcloud.Info, error) { + return &ibmcloud.Info{}, nil + }, + CreateResourceServiceInstance: func(session *session.Session, externalName, servicePlanID, resourceGroupID, targetCrn string, params map[string]interface{}, tags []string) (id string, state string, err error) { + return "", "", fmt.Errorf("failed") + }, + } + + result, err := r.Reconcile(ctrl.Request{ + NamespacedName: types.NamespacedName{Name: serviceName, Namespace: namespace}, + }) + assert.Equal(t, ctrl.Result{ + Requeue: true, + RequeueAfter: config.Get().SyncPeriod, + }, result) + assert.NoError(t, err) + assert.Equal(t, &ibmcloudv1beta1.Service{ + TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + ObjectMeta: metav1.ObjectMeta{ + Name: serviceName, + Namespace: namespace, + Finalizers: []string{serviceFinalizer}, + }, + Status: ibmcloudv1beta1.ServiceStatus{ + State: serviceStateFailed, + Message: "failed", + Plan: "Lite", + ServiceClass: "service-name", + InstanceID: inProgress, + }, + Spec: ibmcloudv1beta1.ServiceSpec{ + Plan: "Lite", + ServiceClass: "service-name", + }, + }, r.Client.(MockClient).LastStatusUpdate()) + }) + }) +} + +func TestServiceVerifyExists(t *testing.T) { + t.Parallel() + const ( + namespace = "mynamespace" + serviceName = "myservice" + ) + scheme := schemas(t) + objects := []runtime.Object{ + &ibmcloudv1beta1.Service{ + ObjectMeta: metav1.ObjectMeta{Name: serviceName, Namespace: namespace}, + Status: ibmcloudv1beta1.ServiceStatus{ + Plan: "Lite", + ServiceClass: "service-name", + InstanceID: "myinstanceid", + }, + Spec: ibmcloudv1beta1.ServiceSpec{ + Plan: "Lite", + ServiceClass: "service-name", + }, + }, + } + aliasObjects := []runtime.Object{ + &ibmcloudv1beta1.Service{ + ObjectMeta: metav1.ObjectMeta{Name: serviceName, Namespace: namespace}, + Status: ibmcloudv1beta1.ServiceStatus{ + Plan: aliasPlan, + ServiceClass: "service-name", + InstanceID: "myinstanceid", + }, + Spec: ibmcloudv1beta1.ServiceSpec{ + Plan: aliasPlan, + ServiceClass: "service-name", + }, + }, + } + + t.Run("success", func(t *testing.T) { + r := &ServiceReconciler{ + Client: newMockClient( + fake.NewFakeClientWithScheme(scheme, objects...), + MockConfig{}, + ), + Log: testLogger(t), + Scheme: scheme, + + GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1beta1.Service) (*ibmcloud.Info, error) { + return &ibmcloud.Info{}, nil + }, + GetResourceServiceInstanceState: func(session *session.Session, resourceGroupID, servicePlanID, externalName, instanceID string) (state string, err error) { + return "state", nil + }, + } + + result, err := r.Reconcile(ctrl.Request{ + NamespacedName: types.NamespacedName{Name: serviceName, Namespace: namespace}, + }) + assert.Equal(t, ctrl.Result{ + Requeue: true, + RequeueAfter: config.Get().SyncPeriod, + }, result) + assert.NoError(t, err) + assert.Equal(t, &ibmcloudv1beta1.Service{ + TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + ObjectMeta: metav1.ObjectMeta{ + Name: serviceName, + Namespace: namespace, + Finalizers: []string{serviceFinalizer}, + }, + Status: ibmcloudv1beta1.ServiceStatus{ + State: "state", + Message: "state", + Plan: "Lite", + ServiceClass: "service-name", + InstanceID: "myinstanceid", + DashboardURL: "https://cloud.ibm.com/services/service-name/myinstanceid", + }, + Spec: ibmcloudv1beta1.ServiceSpec{ + Plan: "Lite", + ServiceClass: "service-name", + }, + }, r.Client.(MockClient).LastStatusUpdate()) + }) + + t.Run("not found non-alias - recreate service", func(t *testing.T) { + var createErr error + r := &ServiceReconciler{ + Client: newMockClient( + fake.NewFakeClientWithScheme(scheme, objects...), + MockConfig{}, + ), + Log: testLogger(t), + Scheme: scheme, + + GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1beta1.Service) (*ibmcloud.Info, error) { + return &ibmcloud.Info{}, nil + }, + GetResourceServiceInstanceState: func(session *session.Session, resourceGroupID, servicePlanID, externalName, instanceID string) (state string, err error) { + return "", resource.NotFoundError{Err: fmt.Errorf("failed")} + }, + CreateResourceServiceInstance: func(session *session.Session, externalName, servicePlanID, resourceGroupID, targetCrn string, params map[string]interface{}, tags []string) (id string, state string, err error) { + return "id", "state", createErr + }, + } + + t.Run("success", func(t *testing.T) { + createErr = nil + result, err := r.Reconcile(ctrl.Request{ + NamespacedName: types.NamespacedName{Name: serviceName, Namespace: namespace}, + }) + assert.Equal(t, ctrl.Result{ + Requeue: true, + RequeueAfter: config.Get().SyncPeriod, + }, result) + assert.NoError(t, err) + assert.Equal(t, &ibmcloudv1beta1.Service{ + TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + ObjectMeta: metav1.ObjectMeta{ + Name: serviceName, + Namespace: namespace, + Finalizers: []string{serviceFinalizer}, + }, + Status: ibmcloudv1beta1.ServiceStatus{ + State: "state", + Message: "state", + Plan: "Lite", + ServiceClass: "service-name", + InstanceID: "id", + DashboardURL: "https://cloud.ibm.com/services/service-name/id", + }, + Spec: ibmcloudv1beta1.ServiceSpec{ + Plan: "Lite", + ServiceClass: "service-name", + }, + }, r.Client.(MockClient).LastStatusUpdate()) + }) + + t.Run("create error", func(t *testing.T) { + createErr = fmt.Errorf("failed") + result, err := r.Reconcile(ctrl.Request{ + NamespacedName: types.NamespacedName{Name: serviceName, Namespace: namespace}, + }) + assert.Equal(t, ctrl.Result{ + Requeue: true, + RequeueAfter: config.Get().SyncPeriod, + }, result) + assert.NoError(t, err) + assert.Equal(t, &ibmcloudv1beta1.Service{ + TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + ObjectMeta: metav1.ObjectMeta{ + Name: serviceName, + Namespace: namespace, + Finalizers: []string{serviceFinalizer}, + }, + Status: ibmcloudv1beta1.ServiceStatus{ + State: serviceStateFailed, + Message: "failed", + Plan: "Lite", + ServiceClass: "service-name", + InstanceID: inProgress, + }, + Spec: ibmcloudv1beta1.ServiceSpec{ + Plan: "Lite", + ServiceClass: "service-name", + }, + }, r.Client.(MockClient).LastStatusUpdate()) + }) + }) + + t.Run("not found alias", func(t *testing.T) { + r := &ServiceReconciler{ + Client: newMockClient( + fake.NewFakeClientWithScheme(scheme, aliasObjects...), + MockConfig{}, + ), + Log: testLogger(t), + Scheme: scheme, + + GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1beta1.Service) (*ibmcloud.Info, error) { + return &ibmcloud.Info{}, nil + }, + GetResourceServiceInstanceState: func(session *session.Session, resourceGroupID, servicePlanID, externalName, instanceID string) (state string, err error) { + return "", resource.NotFoundError{Err: fmt.Errorf("failed")} + }, + } + + result, err := r.Reconcile(ctrl.Request{ + NamespacedName: types.NamespacedName{Name: serviceName, Namespace: namespace}, + }) + assert.Equal(t, ctrl.Result{ + Requeue: true, + RequeueAfter: config.Get().SyncPeriod, + }, result) + assert.NoError(t, err) + assert.Equal(t, &ibmcloudv1beta1.Service{ + TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + ObjectMeta: metav1.ObjectMeta{ + Name: serviceName, + Namespace: namespace, + Finalizers: []string{serviceFinalizer}, + }, + Status: ibmcloudv1beta1.ServiceStatus{ + State: serviceStatePending, + Message: "aliased service instance no longer exists", + Plan: aliasPlan, + ServiceClass: "service-name", + InstanceID: "", + }, + Spec: ibmcloudv1beta1.ServiceSpec{ + Plan: aliasPlan, + ServiceClass: "service-name", + }, + }, r.Client.(MockClient).LastStatusUpdate()) + }) + + t.Run("other error", func(t *testing.T) { + r := &ServiceReconciler{ + Client: newMockClient( + fake.NewFakeClientWithScheme(scheme, objects...), + MockConfig{}, + ), + Log: testLogger(t), + Scheme: scheme, + + GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1beta1.Service) (*ibmcloud.Info, error) { + return &ibmcloud.Info{}, nil + }, + GetResourceServiceInstanceState: func(session *session.Session, resourceGroupID, servicePlanID, externalName, instanceID string) (state string, err error) { + return "", fmt.Errorf("failed") + }, + } + + result, err := r.Reconcile(ctrl.Request{ + NamespacedName: types.NamespacedName{Name: serviceName, Namespace: namespace}, + }) + assert.Equal(t, ctrl.Result{ + Requeue: true, + RequeueAfter: config.Get().SyncPeriod, + }, result) + assert.NoError(t, err) + assert.Equal(t, &ibmcloudv1beta1.Service{ + TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + ObjectMeta: metav1.ObjectMeta{ + Name: serviceName, + Namespace: namespace, + Finalizers: []string{serviceFinalizer}, + }, + Status: ibmcloudv1beta1.ServiceStatus{ + State: serviceStatePending, + Message: "failed", + Plan: "Lite", + ServiceClass: "service-name", + InstanceID: "myinstanceid", + }, + Spec: ibmcloudv1beta1.ServiceSpec{ + Plan: "Lite", + ServiceClass: "service-name", + }, + }, r.Client.(MockClient).LastStatusUpdate()) + }) +} + +func TestServiceUpdateTagsOrParamsFailed(t *testing.T) { + t.Parallel() + const ( + namespace = "mynamespace" + serviceName = "myservice" + ) + scheme := schemas(t) + objects := []runtime.Object{ + &ibmcloudv1beta1.Service{ + ObjectMeta: metav1.ObjectMeta{Name: serviceName, Namespace: namespace}, + Status: ibmcloudv1beta1.ServiceStatus{ + Plan: "Lite", + ServiceClass: "service-name", + InstanceID: "myinstanceid", + }, + Spec: ibmcloudv1beta1.ServiceSpec{ + Plan: "Lite", + ServiceClass: "service-name", + Tags: []string{"somethingNew"}, + }, + }, + } + + r := &ServiceReconciler{ + Client: newMockClient( + fake.NewFakeClientWithScheme(scheme, objects...), + MockConfig{}, + ), + Log: testLogger(t), + Scheme: scheme, + + GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1beta1.Service) (*ibmcloud.Info, error) { + return &ibmcloud.Info{}, nil + }, + GetResourceServiceInstanceState: func(session *session.Session, resourceGroupID, servicePlanID, externalName, instanceID string) (state string, err error) { + return "state", nil + }, + UpdateResourceServiceInstance: func(session *session.Session, serviceInstanceID, externalName, servicePlanID string, params map[string]interface{}, tags []string) (state string, err error) { + return "", fmt.Errorf("failed") + }, + } + + result, err := r.Reconcile(ctrl.Request{ + NamespacedName: types.NamespacedName{Name: serviceName, Namespace: namespace}, + }) + assert.Equal(t, ctrl.Result{ + Requeue: true, + RequeueAfter: config.Get().SyncPeriod, + }, result) + assert.NoError(t, err) + assert.Equal(t, &ibmcloudv1beta1.Service{ + TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + ObjectMeta: metav1.ObjectMeta{ + Name: serviceName, + Namespace: namespace, + Finalizers: []string{serviceFinalizer}, + }, + Status: ibmcloudv1beta1.ServiceStatus{ + State: serviceStateFailed, + Message: "failed", + Plan: "Lite", + ServiceClass: "service-name", + InstanceID: "myinstanceid", + }, + Spec: ibmcloudv1beta1.ServiceSpec{ + Plan: "Lite", + ServiceClass: "service-name", + Tags: []string{"somethingNew"}, + }, + }, r.Client.(MockClient).LastStatusUpdate()) +} + +func TestSpecChanged(t *testing.T) { + t.Parallel() + const ( + something = "something" + somethingElse = "something else" + ) + for _, tc := range []struct { + description string + instance ibmcloudv1beta1.Service + expectChanged bool + }{ + { + description: "empty object", + instance: ibmcloudv1beta1.Service{}, + expectChanged: false, + }, + { + description: "missing status plan", + instance: ibmcloudv1beta1.Service{ + Spec: ibmcloudv1beta1.ServiceSpec{ExternalName: something}, + Status: ibmcloudv1beta1.ServiceStatus{ExternalName: something}, + }, + expectChanged: false, + }, + { + description: "mismatched external name", + instance: ibmcloudv1beta1.Service{ + Spec: ibmcloudv1beta1.ServiceSpec{ + Plan: something, + ExternalName: something, + }, + Status: ibmcloudv1beta1.ServiceStatus{ + Plan: something, + ExternalName: somethingElse, + }, + }, + expectChanged: true, + }, + { + description: "mismatched plan", + instance: ibmcloudv1beta1.Service{ + Spec: ibmcloudv1beta1.ServiceSpec{ + Plan: something, + }, + Status: ibmcloudv1beta1.ServiceStatus{ + Plan: somethingElse, + }, + }, + expectChanged: true, + }, + { + description: "mismatched service class", + instance: ibmcloudv1beta1.Service{ + Spec: ibmcloudv1beta1.ServiceSpec{ + Plan: something, + ServiceClass: something, + }, + Status: ibmcloudv1beta1.ServiceStatus{ + Plan: something, + ServiceClass: somethingElse, + }, + }, + expectChanged: true, + }, + { + description: "mismatched service class type", + instance: ibmcloudv1beta1.Service{ + Spec: ibmcloudv1beta1.ServiceSpec{ + Plan: something, + ServiceClassType: something, + }, + Status: ibmcloudv1beta1.ServiceStatus{ + Plan: something, + ServiceClassType: somethingElse, + }, + }, + expectChanged: true, + }, + { + description: "mismatched context", + instance: ibmcloudv1beta1.Service{ + Spec: ibmcloudv1beta1.ServiceSpec{ + Plan: something, + Context: ibmcloudv1beta1.ResourceContext{User: something}, + }, + Status: ibmcloudv1beta1.ServiceStatus{ + Plan: something, + Context: ibmcloudv1beta1.ResourceContext{User: somethingElse}, + }, + }, + expectChanged: true, + }, + { + description: "matching contexts", + instance: ibmcloudv1beta1.Service{ + Spec: ibmcloudv1beta1.ServiceSpec{ + Plan: something, + Context: ibmcloudv1beta1.ResourceContext{User: somethingElse}, + }, + Status: ibmcloudv1beta1.ServiceStatus{ + Plan: something, + Context: ibmcloudv1beta1.ResourceContext{User: somethingElse}, + }, + }, + expectChanged: false, + }, + } { + t.Run(tc.description, func(t *testing.T) { + assert.Equal(t, tc.expectChanged, specChanged(&tc.instance)) + }) + } +} + +func TestDeleteServiceFinalizer(t *testing.T) { + t.Parallel() + t.Run("no finalizer found", func(t *testing.T) { + finalizers := []string(nil) + assert.Equal(t, finalizers, deleteServiceFinalizer(&ibmcloudv1beta1.Service{ + ObjectMeta: metav1.ObjectMeta{Finalizers: finalizers}, + })) + }) + + t.Run("one other finalizer found", func(t *testing.T) { + finalizers := []string{"not-service-finalizer"} + assert.Equal(t, finalizers, deleteServiceFinalizer(&ibmcloudv1beta1.Service{ + ObjectMeta: metav1.ObjectMeta{Finalizers: finalizers}, + })) + }) + + t.Run("one finalizer found", func(t *testing.T) { + finalizers := []string{serviceFinalizer} + assert.Equal(t, []string(nil), deleteServiceFinalizer(&ibmcloudv1beta1.Service{ + ObjectMeta: metav1.ObjectMeta{Finalizers: finalizers}, + })) + }) + + t.Run("multiple finalizers found", func(t *testing.T) { + finalizers := []string{serviceFinalizer, serviceFinalizer} + assert.Equal(t, []string(nil), deleteServiceFinalizer(&ibmcloudv1beta1.Service{ + ObjectMeta: metav1.ObjectMeta{Finalizers: finalizers}, + })) + }) +} + +func TestServiceParamToJSON(t *testing.T) { + t.Parallel() + for _, tc := range []struct { + description string + param ibmcloudv1beta1.Param + expectJSON map[string]interface{} + expectErr string + }{ + { + description: "error: value and valueFrom both set", + param: ibmcloudv1beta1.Param{ + Name: "myvalue", + Value: &ibmcloudv1beta1.ParamValue{}, + ValueFrom: &ibmcloudv1beta1.ParamSource{}, + }, + expectErr: "Value and ValueFrom properties are mutually exclusive (for myvalue variable)", + }, + { + description: "empty valueFrom error", + param: ibmcloudv1beta1.Param{ + Name: "myvalue", + ValueFrom: &ibmcloudv1beta1.ParamSource{}, + }, + expectErr: "Missing secretKeyRef or configMapKeyRef", + }, + { + description: "empty value error", + param: ibmcloudv1beta1.Param{ + Name: "myvalue", + Value: &ibmcloudv1beta1.ParamValue{}, + }, + expectErr: "unexpected end of JSON input", + }, + { + description: "value happy path", + param: ibmcloudv1beta1.Param{ + Name: "myvalue", + Value: &ibmcloudv1beta1.ParamValue{ + RawMessage: json.RawMessage(`{"hello": true, "world": {"!": 1}}`), + }, + }, + expectJSON: map[string]interface{}{ + "hello": true, + "world": map[string]interface{}{ + "!": 1.0, + }, + }, + }, + { + description: "neither value nor valueFrom set", + param: ibmcloudv1beta1.Param{Name: "myvalue"}, + expectJSON: nil, + expectErr: "", + }, + } { + t.Run(tc.description, func(t *testing.T) { + r := &ServiceReconciler{} + j, err := r.paramToJSON(context.TODO(), tc.param, "someNamespace") + if tc.expectErr != "" { + assert.EqualError(t, err, tc.expectErr) + return + } + require.NoError(t, err) + if tc.expectJSON == nil { + assert.Nil(t, j) + } else { + assert.Equal(t, tc.expectJSON, j) + } + }) + } +} + +func TestServiceParamValueToJSON(t *testing.T) { + t.Parallel() + const ( + secretName = "secretName" + secretKey = "mykey" + secretValue = "myvalue" + configMapName = "configMapName" + configMapKey = "mykey" + configMapValue = "myvalue" + namespace = "mynamespace" + ) + + objects := []runtime.Object{ + &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{Name: secretName, Namespace: namespace}, + Data: map[string][]byte{ + secretKey: []byte(secretValue), + }, + }, + &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{Name: configMapName, Namespace: namespace}, + Data: map[string]string{ + configMapKey: configMapValue, + }, + }, + } + + for _, tc := range []struct { + description string + valueFrom ibmcloudv1beta1.ParamSource + expectJSON interface{} + expectErr string + }{ + { + description: "no value error", + expectErr: "Missing secretKeyRef or configMapKeyRef", + }, + { + description: "secret ref success", + valueFrom: ibmcloudv1beta1.ParamSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: secretName, + }, + Key: secretKey, + }, + }, + expectJSON: secretValue, + }, + { + description: "secret ref name failure", + valueFrom: ibmcloudv1beta1.ParamSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "wrong-secret-name", + }, + Key: secretKey, + }, + }, + expectErr: "Missing secret wrong-secret-name", + }, + { + description: "secret ref key failure", + valueFrom: ibmcloudv1beta1.ParamSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: secretName, + }, + Key: "wrong-key-name", + }, + }, + expectJSON: "", + }, + { + description: "configmap ref success", + valueFrom: ibmcloudv1beta1.ParamSource{ + ConfigMapKeyRef: &corev1.ConfigMapKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: configMapName, + }, + Key: configMapKey, + }, + }, + expectJSON: configMapValue, + }, + { + description: "configmap ref name failure", + valueFrom: ibmcloudv1beta1.ParamSource{ + ConfigMapKeyRef: &corev1.ConfigMapKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "wrong-configmap-name", + }, + Key: configMapKey, + }, + }, + expectErr: "Missing configmap wrong-configmap-name", + }, + { + description: "configmap ref key failure", + valueFrom: ibmcloudv1beta1.ParamSource{ + ConfigMapKeyRef: &corev1.ConfigMapKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: configMapName, + }, + Key: "wrong-key-name", + }, + }, + expectJSON: "", + }, + } { + t.Run(tc.description, func(t *testing.T) { + scheme := schemas(t) + r := &ServiceReconciler{ + Client: fake.NewFakeClientWithScheme(scheme, objects...), + Log: testLogger(t), + Scheme: scheme, + } + + j, err := r.paramValueToJSON(context.TODO(), tc.valueFrom, namespace) + if tc.expectErr != "" { + assert.EqualError(t, err, tc.expectErr) + return + } + require.NoError(t, err) + assert.Equal(t, tc.expectJSON, j) + }) + } +} + +func TestServiceUpdateStatusFailed(t *testing.T) { + t.Parallel() + const ( + namespace = "mynamespace" + serviceName = "myservice" + ) + scheme := schemas(t) + instance := &ibmcloudv1beta1.Service{ + ObjectMeta: metav1.ObjectMeta{Name: serviceName, Namespace: namespace}, + Status: ibmcloudv1beta1.ServiceStatus{ + Plan: "Lite", + ServiceClass: "service-name", + InstanceID: "myinstanceid", + }, + Spec: ibmcloudv1beta1.ServiceSpec{ + Plan: "Lite", + ServiceClass: "service-name", + }, + } + + r := &ServiceReconciler{ + Client: newMockClient( + fake.NewFakeClientWithScheme(scheme, instance), + MockConfig{StatusUpdateErr: fmt.Errorf("failed")}, + ), + Log: testLogger(t), + Scheme: scheme, + + DeleteResourceServiceInstance: func(session *session.Session, instanceID string, logt logr.Logger) error { + return fmt.Errorf("failed to delete") // only gets logged, no error handling + }, + } + + result, err := r.updateStatus(nil, r.Log, instance, ibmcloudv1beta1.ResourceContext{}, "myinstanceid", "state", "") + assert.Equal(t, ctrl.Result{}, result) + assert.EqualError(t, err, "failed") + assert.Equal(t, &ibmcloudv1beta1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: serviceName, + Namespace: namespace, + }, + Status: ibmcloudv1beta1.ServiceStatus{ + State: "state", + Message: "state", + Plan: "Lite", + ServiceClass: "service-name", + InstanceID: "myinstanceid", + DashboardURL: "https://cloud.ibm.com/services/service-name/myinstanceid", + }, + Spec: ibmcloudv1beta1.ServiceSpec{ + Plan: "Lite", + ServiceClass: "service-name", + }, + }, r.Client.(MockClient).LastStatusUpdate()) +} diff --git a/controllers/suite_test.go b/controllers/suite_test.go index 829a5958..ffb1dfd0 100644 --- a/controllers/suite_test.go +++ b/controllers/suite_test.go @@ -168,6 +168,7 @@ func mainSetup(ctx context.Context) error { CreateResourceServiceInstance: resource.CreateServiceInstance, DeleteResourceServiceInstance: resource.DeleteServiceInstance, GetCFServiceInstance: cfservice.GetInstance, + GetIBMCloudInfo: ibmcloud.GetInfo, GetResourceServiceAliasInstance: resource.GetServiceAliasInstance, GetResourceServiceInstanceState: resource.GetServiceInstanceState, UpdateResourceServiceInstance: resource.UpdateServiceInstance, diff --git a/internal/ibmcloud/cfservice/service_instance.go b/internal/ibmcloud/cfservice/service_instance.go index 12505a6b..e4710c7b 100644 --- a/internal/ibmcloud/cfservice/service_instance.go +++ b/internal/ibmcloud/cfservice/service_instance.go @@ -9,7 +9,11 @@ import ( ) type NotFoundError struct { - error + Err error +} + +func (e NotFoundError) Error() string { + return e.Err.Error() } type InstanceGetter func(session *session.Session, name string) (guid, state string, err error) @@ -24,7 +28,7 @@ func GetInstance(session *session.Session, name string) (guid, state string, err serviceInstance, err := bxClient.ServiceInstances().FindByName(name) if err != nil { if strings.Contains(err.Error(), "doesn't exist") { - err = NotFoundError{err} + err = NotFoundError{Err: err} } return "", "", err } diff --git a/internal/ibmcloud/resource/service_instance.go b/internal/ibmcloud/resource/service_instance.go index a98e822a..41bde3d3 100644 --- a/internal/ibmcloud/resource/service_instance.go +++ b/internal/ibmcloud/resource/service_instance.go @@ -13,7 +13,11 @@ import ( ) type NotFoundError struct { - error + Err error +} + +func (n NotFoundError) Error() string { + return n.Err.Error() } type ServiceInstanceCRNGetter func(session *session.Session, instanceID string) (instanceCRN crn.CRN, serviceID string, err error) diff --git a/main.go b/main.go index 9b3fab3a..3d4d714f 100644 --- a/main.go +++ b/main.go @@ -103,6 +103,7 @@ func main() { CreateResourceServiceInstance: resource.CreateServiceInstance, DeleteResourceServiceInstance: resource.DeleteServiceInstance, GetCFServiceInstance: cfservice.GetInstance, + GetIBMCloudInfo: ibmcloud.GetInfo, GetResourceServiceAliasInstance: resource.GetServiceAliasInstance, GetResourceServiceInstanceState: resource.GetServiceInstanceState, UpdateResourceServiceInstance: resource.UpdateServiceInstance, From 4b54795e2b5bb172b977406cde997c87c00d2199 Mon Sep 17 00:00:00 2001 From: John Starich Date: Mon, 14 Sep 2020 18:17:28 -0500 Subject: [PATCH 07/20] Add coverage badge to README (#196) Signed-off-by: John Starich Signed-off-by: Art Berger --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 40046f79..6f38c886 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ [![Build Status](https://travis-ci.com/IBM/cloud-operators.svg?branch=master)](https://travis-ci.com/IBM/cloud-operators) [![Go Report Card](https://goreportcard.com/badge/github.com/IBM/cloud-operators)](https://goreportcard.com/report/github.com/IBM/cloud-operators) +[![codecov.io](https://codecov.io/github/IBM/cloud-operators/coverage.svg?branch=master)](https://codecov.io/github/IBM/cloud-operators?branch=master) [![GoDoc](https://godoc.org/github.com/IBM/cloud-operators?status.svg)](https://godoc.org/github.com/IBM/cloud-operators) # IBM Cloud Operator From d87df034563c3a5f0c9fe889c4f9430d67628921 Mon Sep 17 00:00:00 2001 From: John Starich Date: Tue, 15 Sep 2020 20:00:46 -0500 Subject: [PATCH 08/20] Fix installer not handling semvers correctly, fix namespace output file (#197) * Validate semver format before running script Signed-off-by: John Starich * Fix semver install for v0.2 Signed-off-by: John Starich * Manually fix namespace name Signed-off-by: John Starich * Fix coverage flapping Signed-off-by: John Starich Signed-off-by: Art Berger --- config/manager/manager.yaml | 2 +- controllers/binding_controller_test.go | 43 ++++++++++ controllers/service_controller_test.go | 114 +++++++++++++++++++++++++ hack/configure-operator.sh | 21 ++++- 4 files changed, 178 insertions(+), 2 deletions(-) diff --git a/config/manager/manager.yaml b/config/manager/manager.yaml index b6c85a52..934a980f 100644 --- a/config/manager/manager.yaml +++ b/config/manager/manager.yaml @@ -3,7 +3,7 @@ kind: Namespace metadata: labels: control-plane: controller-manager - name: system + name: ibmcloud-operators-system --- apiVersion: apps/v1 kind: Deployment diff --git a/controllers/binding_controller_test.go b/controllers/binding_controller_test.go index 261214ff..5ad13a25 100644 --- a/controllers/binding_controller_test.go +++ b/controllers/binding_controller_test.go @@ -2250,3 +2250,46 @@ func TestBindingDeleteCredentials(t *testing.T) { }) } } + +func TestBindingUpdateStatusOnlineFailed(t *testing.T) { + t.Parallel() + scheme := schemas(t) + binding := &ibmcloudv1beta1.Binding{ + ObjectMeta: metav1.ObjectMeta{Name: "myservice", Namespace: "mynamespace"}, + Spec: ibmcloudv1beta1.BindingSpec{}, + } + service := &ibmcloudv1beta1.Service{ + ObjectMeta: metav1.ObjectMeta{Name: "myservice", Namespace: "mynamespace"}, + Spec: ibmcloudv1beta1.ServiceSpec{}, + } + + client := newMockClient( + fake.NewFakeClientWithScheme(scheme, binding, service), + MockConfig{StatusUpdateErr: fmt.Errorf("status failed")}, + ) + r := &BindingReconciler{ + Client: client, + Log: testLogger(t), + Scheme: scheme, + + DeleteResourceServiceKey: func(session *session.Session, keyID string) error { + return fmt.Errorf("failed") + }, + } + + result, err := r.updateStatusOnline(nil, binding, service, "") + assert.Equal(t, ctrl.Result{ + Requeue: true, + RequeueAfter: config.Get().SyncPeriod, + }, result) + assert.NoError(t, err) + assert.Equal(t, &ibmcloudv1beta1.Binding{ + ObjectMeta: metav1.ObjectMeta{Name: "myservice", Namespace: "mynamespace"}, + Status: ibmcloudv1beta1.BindingStatus{ + State: bindingStateOnline, + Message: bindingStateOnline, + SecretName: "myservice", + }, + Spec: ibmcloudv1beta1.BindingSpec{}, + }, client.LastStatusUpdate()) +} diff --git a/controllers/service_controller_test.go b/controllers/service_controller_test.go index 351b90df..bf4477c5 100644 --- a/controllers/service_controller_test.go +++ b/controllers/service_controller_test.go @@ -1654,6 +1654,49 @@ func TestServiceVerifyExists(t *testing.T) { }) }) + t.Run("not found non-alias - status update failed", func(t *testing.T) { + r := &ServiceReconciler{ + Client: newMockClient( + fake.NewFakeClientWithScheme(scheme, objects...), + MockConfig{StatusUpdateErr: fmt.Errorf("failed")}, + ), + Log: testLogger(t), + Scheme: scheme, + + GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1beta1.Service) (*ibmcloud.Info, error) { + return &ibmcloud.Info{}, nil + }, + GetResourceServiceInstanceState: func(session *session.Session, resourceGroupID, servicePlanID, externalName, instanceID string) (state string, err error) { + return "", resource.NotFoundError{Err: fmt.Errorf("some other error")} + }, + } + + result, err := r.Reconcile(ctrl.Request{ + NamespacedName: types.NamespacedName{Name: serviceName, Namespace: namespace}, + }) + assert.Equal(t, ctrl.Result{}, result) + assert.EqualError(t, err, "failed") + assert.Equal(t, &ibmcloudv1beta1.Service{ + TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + ObjectMeta: metav1.ObjectMeta{ + Name: serviceName, + Namespace: namespace, + Finalizers: []string{serviceFinalizer}, + }, + Status: ibmcloudv1beta1.ServiceStatus{ + State: "", + Message: "", + Plan: "Lite", + ServiceClass: "service-name", + InstanceID: inProgress, + }, + Spec: ibmcloudv1beta1.ServiceSpec{ + Plan: "Lite", + ServiceClass: "service-name", + }, + }, r.Client.(MockClient).LastStatusUpdate()) + }) + t.Run("not found alias", func(t *testing.T) { r := &ServiceReconciler{ Client: newMockClient( @@ -2219,3 +2262,74 @@ func TestServiceUpdateStatusFailed(t *testing.T) { }, }, r.Client.(MockClient).LastStatusUpdate()) } + +func TestServiceUpdateStatusError(t *testing.T) { + t.Parallel() + const ( + namespace = "mynamespace" + serviceName = "myservice" + ) + scheme := schemas(t) + instance := &ibmcloudv1beta1.Service{ + ObjectMeta: metav1.ObjectMeta{Name: serviceName, Namespace: namespace}, + Status: ibmcloudv1beta1.ServiceStatus{ + Plan: "Lite", + ServiceClass: "service-name", + InstanceID: "myinstanceid", + }, + Spec: ibmcloudv1beta1.ServiceSpec{ + Plan: "Lite", + ServiceClass: "service-name", + }, + } + + t.Run("no such host", func(t *testing.T) { + r := &ServiceReconciler{ + Client: newMockClient( + fake.NewFakeClientWithScheme(scheme, instance), + MockConfig{}, + ), + Log: testLogger(t), + Scheme: scheme, + } + + result, err := r.updateStatusError(instance, "state", fmt.Errorf("no such host")) + assert.Equal(t, ctrl.Result{ + Requeue: true, + RequeueAfter: 5 * time.Minute, + }, result) + assert.NoError(t, err) + }) + + t.Run("status update failed", func(t *testing.T) { + r := &ServiceReconciler{ + Client: newMockClient( + fake.NewFakeClientWithScheme(scheme, instance), + MockConfig{StatusUpdateErr: fmt.Errorf("failed")}, + ), + Log: testLogger(t), + Scheme: scheme, + } + + result, err := r.updateStatusError(instance, "state", fmt.Errorf("some error")) + assert.Equal(t, ctrl.Result{}, result) + assert.EqualError(t, err, "failed") + assert.Equal(t, &ibmcloudv1beta1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: serviceName, + Namespace: namespace, + }, + Status: ibmcloudv1beta1.ServiceStatus{ + State: "state", + Message: "some error", + Plan: "Lite", + ServiceClass: "service-name", + InstanceID: "myinstanceid", + }, + Spec: ibmcloudv1beta1.ServiceSpec{ + Plan: "Lite", + ServiceClass: "service-name", + }, + }, r.Client.(MockClient).LastStatusUpdate()) + }) +} diff --git a/hack/configure-operator.sh b/hack/configure-operator.sh index 640cac60..505a98c7 100755 --- a/hack/configure-operator.sh +++ b/hack/configure-operator.sh @@ -118,6 +118,16 @@ fetch_assets() { printf "$TMPDIR" } +# valid_semver returns 0 if $1 is a valid semver number. Only allows MAJOR.MINOR.PATCH format. +valid_semver() { + local version=$1 + local semver_pattern='^([0-9]+)\.([0-9]+)\.([0-9]+)$' + if [[ "$version" =~ $semver_pattern ]]; then + return 0 + fi + return 1 +} + # compare_semver prints 0 if the semver $1 is equal to $2, -1 for $1 < $2, and 1 for $1 > $2 compare_semver() { local semver_pattern='([0-9]+)\.([0-9]+)\.([0-9]+)' @@ -222,7 +232,11 @@ release_action() { local action=$1 local version=$2 - local release=$(curl -H 'Accept: application/vnd.github.v3+json' "https://api.github.com/repos/IBM/cloud-operators/releases/$version") + local download_version=$version + if [[ "$version" != latest ]]; then + download_version="tags/v${version}" + fi + local release=$(curl -H 'Accept: application/vnd.github.v3+json' "https://api.github.com/repos/IBM/cloud-operators/releases/$download_version") local urls=$(json_grep browser_download_url -1 <<<"$release") local file_urls=() while read -r url; do @@ -268,6 +282,11 @@ while getopts "hv:" opt; do exit 0 ;; v) + if ! valid_semver "$OPTARG"; then + error "Invalid semver: $OPTARG" + usage + exit 2 + fi VERSION=$OPTARG ;; esac From bca25d6aec89c9e4a09dd5d1be9f7091d60e9628 Mon Sep 17 00:00:00 2001 From: John Starich Date: Fri, 18 Sep 2020 00:04:17 -0500 Subject: [PATCH 09/20] Fix OLM data & formatting issues (#200) Fixes OLM metadata so it will install properly from Operator Hub for v0.2+. Adds make tasks to assists OLM testing. Bumps the memory limit to mitigate an OOMKilled issue when running in OpenShift 4.4 (i.e. lots of built-in secrets). Opened #199 to look into it further. Also update TokenController to skip processing any secret not using the well-known names. e.g. `secret-ibm-cloud-operator` `-secret-ibm-cloud-operator` Signed-off-by: Art Berger --- Makefile | 32 +++- .../crd/bases/ibmcloud.ibm.com_bindings.yaml | 44 +++--- .../crd/bases/ibmcloud.ibm.com_services.yaml | 58 +++---- config/manager/manager.yaml | 8 +- controllers/token_controller.go | 19 ++- controllers/token_controller_test.go | 46 +++--- internal/cmd/fixcrd/main.go | 143 ++++++++++++++++++ internal/cmd/genolm/crd.go | 2 + internal/cmd/genolm/deployments.go | 27 ++++ internal/cmd/genolm/main.go | 85 +++++++---- .../templates/clusterserviceversion.yaml | 6 +- 11 files changed, 371 insertions(+), 99 deletions(-) create mode 100644 internal/cmd/genolm/deployments.go diff --git a/Makefile b/Makefile index b730afd3..bc4b88ae 100644 --- a/Makefile +++ b/Makefile @@ -169,10 +169,40 @@ out: # Prepares Kubernetes yaml files for release. Useful for testing against your own cluster. .PHONY: release-prep -release-prep: kustomize out +release-prep: kustomize manifests out cd config/manager && kustomize edit set image controller=${IMG} kustomize build config/default --output out/ go run ./internal/cmd/genolm --version ${RELEASE_VERSION} .PHONY: release release: release-prep docker-push + +.PHONY: operator-courier +operator-courier: + @if ! which operator-courier; then \ + pip3 install operator-courier; \ + fi + +.PHONY: verify-operator-meta +verify-operator-meta: release-prep operator-courier + operator-courier verify --ui_validate_io out/ + +.PHONY: operator-push-test +operator-push-test: IMG = quay.io/${QUAY_NAMESPACE}/${QUAY_REPO}:${RELEASE_VERSION} +operator-push-test: verify-operator-meta docker-build + # Example values: + # + # QUAY_NAMESPACE=myuser + # QUAY_REPO=ibmcloud-operator-image + # QUAY_APP=ibmcloud-operator NOTE: Must have a repository AND a quay "application". They aren't the same thing. + # QUAY_USER=myuser+mybot NOTE: Bot users are best, so you can manage permissions better. + # QUAY_TOKEN=abcdef1234567 + @for v in "${QUAY_NAMESPACE}" "${QUAY_APP}" "${QUAY_REPO}" "${RELEASE_VERSION}" "${QUAY_USER}" "${QUAY_TOKEN}"; do \ + if [[ -z "$$v" ]]; then \ + echo 'Not all Quay variables set. See the make target for details.'; \ + exit 1; \ + fi; \ + done + docker login -u="${QUAY_USER}" -p="${QUAY_TOKEN}" quay.io + docker push "${IMG}" + operator-courier push ./out "${QUAY_NAMESPACE}" "${QUAY_APP}" "${RELEASE_VERSION}" "Basic $$(printf "${QUAY_USER}:${QUAY_TOKEN}" | base64)" diff --git a/config/crd/bases/ibmcloud.ibm.com_bindings.yaml b/config/crd/bases/ibmcloud.ibm.com_bindings.yaml index 1ac02d26..d6666f72 100644 --- a/config/crd/bases/ibmcloud.ibm.com_bindings.yaml +++ b/config/crd/bases/ibmcloud.ibm.com_bindings.yaml @@ -22,12 +22,13 @@ spec: scope: Namespaced subresources: status: {} - version: v1alpha1 + version: v1beta1 versions: - - name: v1alpha1 + - name: v1beta1 schema: openAPIV3Schema: - description: Binding is the Schema for the bindings API + description: Binding is an instance of a service binding resource on IBM Cloud. + A Binding creates a secret with the service instance credentials. properties: apiVersion: description: 'APIVersion defines the versioned schema of this representation @@ -45,8 +46,10 @@ spec: description: BindingSpec defines the desired state of Binding properties: alias: + description: Alias is the name for the credentials to be aliased type: string parameters: + description: Parameters pass configuration to the service during creation items: description: Param represents a key-value pair properties: @@ -106,12 +109,18 @@ spec: type: object type: array role: + description: Role is the role for the credentials type: string secretName: + description: SecretName is the name of the secret where credentials + will be stored type: string serviceName: + description: ServiceClass is the name of the service resource to bind type: string serviceNamespace: + description: ServiceNamespace is the namespace of the service resource + to bind type: string required: - serviceName @@ -123,24 +132,29 @@ spec: format: int64 type: integer instanceId: + description: InstanceID is the instance ID for the service type: string keyInstanceId: + description: KeyInstanceID is the key instance ID for the credentials type: string message: + description: Message is a detailed message on current status type: string secretName: + description: SecretName is the name of the generated secret with service + credentials type: string state: + description: State is a short name for the current status type: string type: object type: object served: true - storage: false - - name: v1beta1 + storage: true + - name: v1alpha1 schema: openAPIV3Schema: - description: Binding is an instance of a service binding resource on IBM Cloud. - A Binding creates a secret with the service instance credentials. + description: Binding is the Schema for the bindings API properties: apiVersion: description: 'APIVersion defines the versioned schema of this representation @@ -158,10 +172,8 @@ spec: description: BindingSpec defines the desired state of Binding properties: alias: - description: Alias is the name for the credentials to be aliased type: string parameters: - description: Parameters pass configuration to the service during creation items: description: Param represents a key-value pair properties: @@ -221,18 +233,12 @@ spec: type: object type: array role: - description: Role is the role for the credentials type: string secretName: - description: SecretName is the name of the secret where credentials - will be stored type: string serviceName: - description: ServiceClass is the name of the service resource to bind type: string serviceNamespace: - description: ServiceNamespace is the namespace of the service resource - to bind type: string required: - serviceName @@ -244,25 +250,19 @@ spec: format: int64 type: integer instanceId: - description: InstanceID is the instance ID for the service type: string keyInstanceId: - description: KeyInstanceID is the key instance ID for the credentials type: string message: - description: Message is a detailed message on current status type: string secretName: - description: SecretName is the name of the generated secret with service - credentials type: string state: - description: State is a short name for the current status type: string type: object type: object served: true - storage: true + storage: false status: acceptedNames: kind: "" diff --git a/config/crd/bases/ibmcloud.ibm.com_services.yaml b/config/crd/bases/ibmcloud.ibm.com_services.yaml index 4964f889..4245a2c3 100644 --- a/config/crd/bases/ibmcloud.ibm.com_services.yaml +++ b/config/crd/bases/ibmcloud.ibm.com_services.yaml @@ -22,12 +22,12 @@ spec: scope: Namespaced subresources: status: {} - version: v1alpha1 + version: v1beta1 versions: - - name: v1alpha1 + - name: v1beta1 schema: openAPIV3Schema: - description: Service is the Schema for the services API + description: Service is an instance of a Service resource on IBM Cloud. properties: apiVersion: description: 'APIVersion defines the versioned schema of this representation @@ -52,8 +52,6 @@ spec: type: string region: type: string - resourcegroup: - type: string resourcegroupid: type: string resourcelocation: @@ -64,8 +62,11 @@ spec: type: string type: object externalName: + description: ExternalName is the name for the service as it appears + on IBM Cloud type: string parameters: + description: Parameters pass configuration to the service during creation items: description: Param represents a key-value pair properties: @@ -125,10 +126,14 @@ spec: type: object type: array plan: + description: Plan for the service from the IBM Cloud Catalog type: string serviceClass: + description: ServiceClass is the name of the service from the IBM + Cloud Catalog type: string serviceClassType: + description: ServiceClassType is set to CF if the service is CloundFoundry type: string tags: items: @@ -149,8 +154,6 @@ spec: type: string region: type: string - resourcegroup: - type: string resourcegroupid: type: string resourcelocation: @@ -161,17 +164,23 @@ spec: type: string type: object dashboardURL: + description: DashboardURL is the dashboard URL for the service type: string externalName: + description: ExternalName is the name for the service as it appears + on IBM Cloud type: string generation: format: int64 type: integer instanceId: + description: InstanceID is the instance ID for the service type: string message: + description: Message is a detailed message on current status type: string parameters: + description: Parameters pass configuration to the service during creation items: description: Param represents a key-value pair properties: @@ -231,12 +240,17 @@ spec: type: object type: array plan: + description: Plan for the service from the IBM Cloud Catalog type: string serviceClass: + description: ServiceClass is the name of the service from the IBM + Cloud Catalog type: string serviceClassType: + description: ServiceClassType is set to CF if the service is CloundFoundry type: string state: + description: State is a short name for the current status type: string tags: items: @@ -249,11 +263,11 @@ spec: type: object type: object served: true - storage: false - - name: v1beta1 + storage: true + - name: v1alpha1 schema: openAPIV3Schema: - description: Service is an instance of a Service resource on IBM Cloud. + description: Service is the Schema for the services API properties: apiVersion: description: 'APIVersion defines the versioned schema of this representation @@ -278,6 +292,8 @@ spec: type: string region: type: string + resourcegroup: + type: string resourcegroupid: type: string resourcelocation: @@ -288,11 +304,8 @@ spec: type: string type: object externalName: - description: ExternalName is the name for the service as it appears - on IBM Cloud type: string parameters: - description: Parameters pass configuration to the service during creation items: description: Param represents a key-value pair properties: @@ -352,14 +365,10 @@ spec: type: object type: array plan: - description: Plan for the service from the IBM Cloud Catalog type: string serviceClass: - description: ServiceClass is the name of the service from the IBM - Cloud Catalog type: string serviceClassType: - description: ServiceClassType is set to CF if the service is CloundFoundry type: string tags: items: @@ -380,6 +389,8 @@ spec: type: string region: type: string + resourcegroup: + type: string resourcegroupid: type: string resourcelocation: @@ -390,23 +401,17 @@ spec: type: string type: object dashboardURL: - description: DashboardURL is the dashboard URL for the service type: string externalName: - description: ExternalName is the name for the service as it appears - on IBM Cloud type: string generation: format: int64 type: integer instanceId: - description: InstanceID is the instance ID for the service type: string message: - description: Message is a detailed message on current status type: string parameters: - description: Parameters pass configuration to the service during creation items: description: Param represents a key-value pair properties: @@ -466,17 +471,12 @@ spec: type: object type: array plan: - description: Plan for the service from the IBM Cloud Catalog type: string serviceClass: - description: ServiceClass is the name of the service from the IBM - Cloud Catalog type: string serviceClassType: - description: ServiceClassType is set to CF if the service is CloundFoundry type: string state: - description: State is a short name for the current status type: string tags: items: @@ -489,7 +489,7 @@ spec: type: object type: object served: true - storage: true + storage: false status: acceptedNames: kind: "" diff --git a/config/manager/manager.yaml b/config/manager/manager.yaml index 934a980f..3bddfb5e 100644 --- a/config/manager/manager.yaml +++ b/config/manager/manager.yaml @@ -27,12 +27,18 @@ spec: - /manager args: - --enable-leader-election + env: + - name: CONTROLLER_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace image: controller:latest name: manager resources: limits: cpu: 100m - memory: 30Mi + # TODO(johnstarich): Reduce back to 30Mi once this is resolved: https://github.com/IBM/cloud-operators/issues/199 + memory: 125Mi requests: cpu: 100m memory: 20Mi diff --git a/controllers/token_controller.go b/controllers/token_controller.go index 11bc0b29..e818434a 100644 --- a/controllers/token_controller.go +++ b/controllers/token_controller.go @@ -18,6 +18,7 @@ package controllers import ( "context" + "strings" "time" "github.com/go-logr/logr" @@ -28,6 +29,13 @@ import ( "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/event" + "sigs.k8s.io/controller-runtime/pkg/predicate" +) + +const ( + icoSecretName = "secret-ibm-cloud-operator" + icoTokensName = "secret-ibm-cloud-operator-tokens" ) // TokenReconciler reconciles a Token object @@ -90,7 +98,7 @@ func (r *TokenReconciler) Reconcile(request ctrl.Request) (ctrl.Result, error) { return ctrl.Result{}, err // requeue } - tokensRef := secret.Name + "-tokens" + tokensRef := strings.TrimSuffix(secret.Name, icoSecretName) + icoTokensName // need to trim suffix, since management namespace could be the prefix logt.Info("creating tokens secret", "name", tokensRef) tokens := &corev1.Secret{ @@ -118,5 +126,14 @@ func (r *TokenReconciler) Reconcile(request ctrl.Request) (ctrl.Result, error) { func (r *TokenReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). For(&corev1.Secret{}). + WithEventFilter(predicate.Funcs{ + CreateFunc: func(e event.CreateEvent) bool { return shouldProcessSecret(e.Meta) }, + DeleteFunc: func(e event.DeleteEvent) bool { return shouldProcessSecret(e.Meta) }, + UpdateFunc: func(e event.UpdateEvent) bool { return shouldProcessSecret(e.MetaNew) }, + }). Complete(r) } + +func shouldProcessSecret(meta metav1.Object) bool { + return meta.GetName() == icoSecretName || strings.HasSuffix(meta.GetName(), "-"+icoSecretName) +} diff --git a/controllers/token_controller_test.go b/controllers/token_controller_test.go index ba2e90b8..169fc931 100644 --- a/controllers/token_controller_test.go +++ b/controllers/token_controller_test.go @@ -31,7 +31,7 @@ func TestToken(t *testing.T) { // Create the secret object and expect the Reconcile const ( - secretName = "dummyapikey" + secretName = "secret-ibm-cloud-operator" secretAPIKey = "VExS246avaUT6MXZ56SH_I-AeWo_-JmW0u79Jd8LiBH" // nolint:gosec // Fake API key ) @@ -62,7 +62,7 @@ func TestToken(t *testing.T) { var secret corev1.Secret assert.Eventually(t, func() bool { - err := k8sClient.Get(context.TODO(), client.ObjectKey{Namespace: "default", Name: "dummyapikey-tokens"}, &secret) + err := k8sClient.Get(context.TODO(), client.ObjectKey{Namespace: "default", Name: "secret-ibm-cloud-operator-tokens"}, &secret) if err != nil { t.Log("Failed to get secret:", err) return false @@ -83,7 +83,7 @@ func TestTokenFailedAuth(t *testing.T) { scheme := schemas(t) objects := []runtime.Object{ &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{Name: "secret"}, + ObjectMeta: metav1.ObjectMeta{Name: "secret-ibm-cloud-operator"}, Data: map[string][]byte{ "api-key": []byte(`bogus key`), }, @@ -99,7 +99,7 @@ func TestTokenFailedAuth(t *testing.T) { } result, err := r.Reconcile(ctrl.Request{ - NamespacedName: types.NamespacedName{Name: "secret"}, + NamespacedName: types.NamespacedName{Name: "secret-ibm-cloud-operator"}, }) assert.EqualError(t, err, "failure") assert.Equal(t, ctrl.Result{}, result) @@ -117,7 +117,7 @@ func TestTokenFailedSecretLookup(t *testing.T) { t.Run("not found", func(t *testing.T) { result, err := r.Reconcile(ctrl.Request{ - NamespacedName: types.NamespacedName{Name: "secret"}, + NamespacedName: types.NamespacedName{Name: "secret-ibm-cloud-operator"}, }) assert.NoError(t, err, "Don't retry (return err) if secret no longer exists") assert.Equal(t, ctrl.Result{}, result) @@ -126,7 +126,7 @@ func TestTokenFailedSecretLookup(t *testing.T) { r.Client = fake.NewFakeClientWithScheme(runtime.NewScheme()) // fail to read the type Secret t.Run("failed to read secret", func(t *testing.T) { result, err := r.Reconcile(ctrl.Request{ - NamespacedName: types.NamespacedName{Name: "secret"}, + NamespacedName: types.NamespacedName{Name: "secret-ibm-cloud-operator"}, }) assert.Error(t, err) assert.False(t, k8sErrors.IsNotFound(err)) @@ -141,7 +141,7 @@ func TestTokenSecretIsDeleting(t *testing.T) { objects := []runtime.Object{ &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ - Name: "secret", + Name: "secret-ibm-cloud-operator", DeletionTimestamp: now, }, }, @@ -154,7 +154,7 @@ func TestTokenSecretIsDeleting(t *testing.T) { } result, err := r.Reconcile(ctrl.Request{ - NamespacedName: types.NamespacedName{Name: "secret"}, + NamespacedName: types.NamespacedName{Name: "secret-ibm-cloud-operator"}, }) assert.NoError(t, err, "Don't retry (return err) if secret is deleting") assert.Equal(t, ctrl.Result{}, result) @@ -165,7 +165,7 @@ func TestTokenAPIKeyIsMissing(t *testing.T) { scheme := schemas(t) objects := []runtime.Object{ &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{Name: "secret"}, + ObjectMeta: metav1.ObjectMeta{Name: "secret-ibm-cloud-operator"}, Data: nil, // no API key }, } @@ -177,7 +177,7 @@ func TestTokenAPIKeyIsMissing(t *testing.T) { } result, err := r.Reconcile(ctrl.Request{ - NamespacedName: types.NamespacedName{Name: "secret"}, + NamespacedName: types.NamespacedName{Name: "secret-ibm-cloud-operator"}, }) assert.NoError(t, err, "Don't retry (return err) if secret does not contain an api-key entry") assert.Equal(t, ctrl.Result{}, result) @@ -192,7 +192,7 @@ func TestTokenAuthInvalidConfig(t *testing.T) { ) objects := []runtime.Object{ &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{Name: "secret"}, + ObjectMeta: metav1.ObjectMeta{Name: "secret-ibm-cloud-operator"}, Data: map[string][]byte{ "api-key": []byte(apiKey), "region": []byte(region), @@ -211,7 +211,7 @@ func TestTokenAuthInvalidConfig(t *testing.T) { } result, err := r.Reconcile(ctrl.Request{ - NamespacedName: types.NamespacedName{Name: "secret"}, + NamespacedName: types.NamespacedName{Name: "secret-ibm-cloud-operator"}, }) assert.NoError(t, err, "Don't retry (return err) if secret region is invalid") assert.Equal(t, ctrl.Result{}, result) @@ -227,7 +227,7 @@ func TestTokenDeleteFailed(t *testing.T) { ) objects := []runtime.Object{ &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{Name: "secret"}, + ObjectMeta: metav1.ObjectMeta{Name: "secret-ibm-cloud-operator"}, Data: map[string][]byte{ "api-key": []byte(apiKey), "region": []byte(region), @@ -250,7 +250,7 @@ func TestTokenDeleteFailed(t *testing.T) { } result, err := r.Reconcile(ctrl.Request{ - NamespacedName: types.NamespacedName{Name: "secret"}, + NamespacedName: types.NamespacedName{Name: "secret-ibm-cloud-operator"}, }) assert.Error(t, err) assert.False(t, k8sErrors.IsNotFound(err)) @@ -266,14 +266,14 @@ func TestTokenRaceCreateFailed(t *testing.T) { accessToken = "some access token" ) tokensSecret := &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{Name: "secret-tokens"}, + ObjectMeta: metav1.ObjectMeta{Name: "secret-ibm-cloud-operator-tokens"}, Data: map[string][]byte{ "access_token": []byte("old " + accessToken), }, } objects := []runtime.Object{ &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{Name: "secret"}, + ObjectMeta: metav1.ObjectMeta{Name: "secret-ibm-cloud-operator"}, Data: map[string][]byte{ "api-key": []byte(apiKey), "region": []byte(region), @@ -312,7 +312,7 @@ func TestTokenRaceCreateFailed(t *testing.T) { var err error require.Eventually(t, func() bool { result, err = r.Reconcile(ctrl.Request{ - NamespacedName: types.NamespacedName{Name: "secret"}, + NamespacedName: types.NamespacedName{Name: "secret-ibm-cloud-operator"}, }) return err != nil }, 5*time.Second, 10*time.Millisecond) @@ -320,3 +320,15 @@ func TestTokenRaceCreateFailed(t *testing.T) { assert.True(t, k8sErrors.IsAlreadyExists(err)) assert.Equal(t, ctrl.Result{}, result) } + +func TestShouldProcessSecret(t *testing.T) { + t.Parallel() + + t.Run("normal secret", func(t *testing.T) { + assert.True(t, shouldProcessSecret(&metav1.ObjectMeta{Name: "secret-ibm-cloud-operator"})) + }) + + t.Run("management namespace secret", func(t *testing.T) { + assert.True(t, shouldProcessSecret(&metav1.ObjectMeta{Name: "mynamespace-secret-ibm-cloud-operator"})) + }) +} diff --git a/internal/cmd/fixcrd/main.go b/internal/cmd/fixcrd/main.go index bc837743..2c82b1e9 100644 --- a/internal/cmd/fixcrd/main.go +++ b/internal/cmd/fixcrd/main.go @@ -7,6 +7,9 @@ import ( "io/ioutil" "os" "reflect" + "sort" + "strings" + "unicode" "gopkg.in/yaml.v2" ) @@ -51,6 +54,9 @@ func mutateYaml(r io.Reader, w io.Writer) error { } removeValueType(&yamlData, false) + versions := getVersions(yamlData) + setVersion(&yamlData, versions[len(versions)-1], 0) + reorderVersions(&yamlData, versions, false) return yaml.NewEncoder(w).Encode(yamlData) } @@ -97,3 +103,140 @@ func ptrSetter(v interface{}) (value interface{}, setPtr func(interface{})) { val.Set(reflect.ValueOf(newValue)) } } + +func setVersion(v interface{}, version string, depth int) { + const maxDepth = 3 + if depth > maxDepth { + return + } + depth++ + + d, set := ptrSetter(v) + switch d := d.(type) { + case []interface{}: + newSlice := make([]interface{}, 0, len(d)) + for ix := range d { + item := d[ix] + setVersion(&item, version, depth) + newSlice = append(newSlice, item) + } + set(newSlice) + case yaml.MapSlice: + newSlice := make(yaml.MapSlice, 0, len(d)) + for ix := range d { + item := d[ix] + setVersion(&item, version, depth) + newSlice = append(newSlice, item) + } + set(newSlice) + case yaml.MapItem: + setVersion(&d.Value, version, depth) + if d.Key == "version" { + d.Value = version + } + set(d) + } +} + +func getVersions(v interface{}) []string { + versions := recursiveGetVersions(v, false) + // sort versions lowest to highest. i.e. v1alpha1, v1beta1, v1beta2, v1 + sort.Slice(versions, func(a, b int) bool { + strA := versions[a] + strB := versions[b] + // artificially sort 'v1' as greater than 'v1alpha1'. this works because '~' > 'a-zA-Z' + if isDigits(strings.TrimPrefix(strA, "v")) { + strA += "~" + } + if isDigits(strings.TrimPrefix(strB, "v")) { + strA += "~" + } + return strA < strB + }) + return versions +} + +func recursiveGetVersions(v interface{}, foundVersions bool) (versions []string) { + switch d := v.(type) { + case []interface{}: + for ix := range d { + item := d[ix] + versions = append(versions, recursiveGetVersions(item, foundVersions)...) + } + case yaml.MapSlice: + for ix := range d { + item := d[ix] + versions = append(versions, recursiveGetVersions(item, foundVersions)...) + } + case yaml.MapItem: + if d.Key == "versions" { + return recursiveGetVersions(d.Value, true) + } + if !foundVersions { + return append(versions, recursiveGetVersions(d.Value, foundVersions)...) + } + if d.Key != "name" { + return + } + return append(versions, d.Value.(string)) + } + return +} + +func isDigits(s string) bool { + for _, r := range s { + if !unicode.IsDigit(r) { + return false + } + } + return true +} + +// reorderVersions sorts the CRD versions from newest to oldest ('versions' is sorted oldest to newest so range over it backwards) +func reorderVersions(v interface{}, versions []string, foundVersions bool) { + d, set := ptrSetter(v) + switch d := d.(type) { + case []interface{}: + if foundVersions { + versionMap := make(map[string]interface{}) + for ix := range d { + item := d[ix] + for _, kv := range item.(yaml.MapSlice) { + if kv.Key == "name" { + versionMap[kv.Value.(string)] = &item + break + } + } + } + newSlice := make([]interface{}, 0, len(d)) + for ix := range versions { + fromEnd := versions[len(versions)-1-ix] + newSlice = append(newSlice, versionMap[fromEnd]) + } + set(newSlice) + return + } + + newSlice := make([]interface{}, 0, len(d)) + for ix := range d { + item := d[ix] + reorderVersions(&item, versions, foundVersions) + newSlice = append(newSlice, item) + } + set(newSlice) + case yaml.MapSlice: + newSlice := make(yaml.MapSlice, 0, len(d)) + for ix := range d { + item := d[ix] + reorderVersions(&item, versions, foundVersions) + newSlice = append(newSlice, item) + } + set(newSlice) + case yaml.MapItem: + if d.Key == "versions" { + foundVersions = true + } + reorderVersions(&d.Value, versions, foundVersions) + set(d) + } +} diff --git a/internal/cmd/genolm/crd.go b/internal/cmd/genolm/crd.go index a86484bb..d317d159 100644 --- a/internal/cmd/genolm/crd.go +++ b/internal/cmd/genolm/crd.go @@ -23,6 +23,7 @@ type CRD struct { SpecDescriptors []Descriptor `json:"specDescriptors,omitempty"` StatusDescriptors []Descriptor `json:"statusDescriptors,omitempty"` ActionDescriptors []Descriptor `json:"actionDescriptors,omitempty"` + Version string `json:"version"` } type TypeMeta struct { @@ -101,6 +102,7 @@ func NewCRD(src apiextensionsv1beta1.CustomResourceDefinition, ownedResources [] OwnedResources: ownedResources, SpecDescriptors: descriptorsFor("spec", latestVersion, xDescriptors), StatusDescriptors: descriptorsFor("status", latestVersion, xDescriptors), + Version: src.Spec.Version, } } diff --git a/internal/cmd/genolm/deployments.go b/internal/cmd/genolm/deployments.go new file mode 100644 index 00000000..4a84bfbc --- /dev/null +++ b/internal/cmd/genolm/deployments.go @@ -0,0 +1,27 @@ +package main + +import ( + "io/ioutil" + "path/filepath" + + "github.com/ghodss/yaml" + "github.com/pkg/errors" + appsv1 "k8s.io/api/apps/v1" +) + +type Deployment struct { + Name string `json:"name"` + Spec appsv1.DeploymentSpec `json:"spec"` +} + +func getDeployments(output string) ([]Deployment, error) { + var deployment appsv1.Deployment + deploymentBytes, err := ioutil.ReadFile(filepath.Join(output, "apps_v1_deployment_ibmcloud-operators-controller-manager.yaml")) + if err != nil { + return nil, errors.Wrap(err, "Error reading generated deployment file. Did kustomize run yet?") + } + err = yaml.Unmarshal(deploymentBytes, &deployment) + return []Deployment{ + {Name: deployment.Name, Spec: deployment.Spec}, + }, err +} diff --git a/internal/cmd/genolm/main.go b/internal/cmd/genolm/main.go index 5b41e8a0..1b49ad5e 100644 --- a/internal/cmd/genolm/main.go +++ b/internal/cmd/genolm/main.go @@ -17,7 +17,6 @@ import ( "github.com/blang/semver/v4" "github.com/ghodss/yaml" "github.com/pkg/errors" - appsv1 "k8s.io/api/apps/v1" rbacv1 "k8s.io/api/rbac/v1" "k8s.io/apimachinery/pkg/runtime" ) @@ -36,20 +35,22 @@ func main() { type Data struct { CRDs []CRD - DeploymentSpec appsv1.DeploymentSpec + Deployments []Deployment Examples []runtime.RawExtension Image string Maintainers []Maintainer Name string Now string - RBAC []roleRules + ClusterRoles []roleRules + Roles []roleRules README string ReplaceVersion string Version string } type roleRules struct { - Rules []rbacv1.PolicyRule `json:"rules"` + Rules []rbacv1.PolicyRule `json:"rules"` + ServiceAccountName string `json:"serviceAccountName"` } func run(output, repoRoot, versionStr string) error { @@ -96,19 +97,12 @@ func run(output, repoRoot, versionStr string) error { return err } - // DeploymentSpec - var deployment appsv1.Deployment - deploymentBytes, err := ioutil.ReadFile(filepath.Join(output, "apps_v1_deployment_ibmcloud-operators-controller-manager.yaml")) - if err != nil { - return errors.Wrap(err, "Error reading generated deployment file. Did kustomize run yet?") - } - err = yaml.Unmarshal(deploymentBytes, &deployment) + deployments, err := getDeployments(output) if err != nil { return err } - deploymentSpec := deployment.Spec - rbac, err := getRBAC(output) + clusterRoles, roles, err := getRBAC(output) if err != nil { return err } @@ -125,13 +119,14 @@ func run(output, repoRoot, versionStr string) error { data := Data{ CRDs: crds, - DeploymentSpec: deploymentSpec, + Deployments: deployments, Examples: samples, Image: "cloudoperators/ibmcloud-operator", Maintainers: maintainers, Name: "ibmcloud-operator", Now: time.Now().UTC().Format(time.RFC3339), - RBAC: []roleRules{rbac}, + ClusterRoles: []roleRules{clusterRoles}, + Roles: []roleRules{roles}, README: readme, ReplaceVersion: replaceVersion, Version: version.String(), @@ -195,28 +190,66 @@ func templateYAMLMarshal(v interface{}) (string, error) { return string(buf), err } -func getRBAC(output string) (roleRules, error) { - var rbac roleRules +func getRBAC(output string) (clusterRoles, roles roleRules, err error) { rbacFiles, err := filepath.Glob(filepath.Join(output, "rbac.*.yaml")) if err != nil { - return roleRules{}, err + return roleRules{}, roleRules{}, err } for _, path := range rbacFiles { buf, err := ioutil.ReadFile(path) if err != nil { - return roleRules{}, err + return roleRules{}, roleRules{}, err } - var role rbacv1.Role - err = yaml.Unmarshal(buf, &role) + var meta runtime.TypeMeta + err = yaml.Unmarshal(buf, &meta) if err != nil { - return roleRules{}, err + return roleRules{}, roleRules{}, err } - kind := role.GetObjectKind().GroupVersionKind().Kind - if kind == "ClusterRole" || kind == "Role" { - rbac.Rules = append(rbac.Rules, role.Rules...) + kind := meta.Kind + switch kind { + case "ClusterRole": + var role rbacv1.ClusterRole + err := yaml.Unmarshal(buf, &role) + if err != nil { + return roleRules{}, roleRules{}, err + } + clusterRoles.Rules = append(clusterRoles.Rules, role.Rules...) + case "Role": + var role rbacv1.Role + err := yaml.Unmarshal(buf, &role) + if err != nil { + return roleRules{}, roleRules{}, err + } + roles.Rules = append(roles.Rules, role.Rules...) + case "ClusterRoleBinding": + var binding rbacv1.RoleBinding + err := yaml.Unmarshal(buf, &binding) + if err != nil { + return roleRules{}, roleRules{}, err + } + for _, sub := range binding.Subjects { + if sub.Kind == "ServiceAccount" { + clusterRoles.ServiceAccountName = sub.Name + break + } + } + case "RoleBinding": + var binding rbacv1.ClusterRoleBinding + err := yaml.Unmarshal(buf, &binding) + if err != nil { + return roleRules{}, roleRules{}, err + } + for _, sub := range binding.Subjects { + if sub.Kind == "ServiceAccount" { + roles.ServiceAccountName = sub.Name + break + } + } + default: + panic("Unrecognized role type: " + kind) } } - return rbac, nil + return clusterRoles, roles, nil } func getSamples(repoRoot string) ([]runtime.RawExtension, error) { diff --git a/internal/cmd/genolm/templates/clusterserviceversion.yaml b/internal/cmd/genolm/templates/clusterserviceversion.yaml index 42ad4ba7..0352c11f 100644 --- a/internal/cmd/genolm/templates/clusterserviceversion.yaml +++ b/internal/cmd/genolm/templates/clusterserviceversion.yaml @@ -53,9 +53,11 @@ spec: strategy: deployment spec: clusterPermissions: - {{.RBAC | yaml | indent 6 | trimSpace}} + {{.ClusterRoles | yaml | indent 6 | trimSpace}} + permissions: + {{.Roles | yaml | indent 6 | trimSpace}} deployments: - - {{.DeploymentSpec | yaml | indent 8 | trimSpace}} + {{.Deployments | yaml | indent 6 | trimSpace}} customresourcedefinitions: owned: {{.CRDs | yaml | indent 6 | trimSpace}} From 0a92cb3f024f7a841d21bd593cc42a182dc3ba8c Mon Sep 17 00:00:00 2001 From: John Starich Date: Fri, 18 Sep 2020 00:23:51 -0500 Subject: [PATCH 10/20] break: Use consistent resource names for v1 (#201) * Update namespace to use singular form: "ibmcloud-operator" * Breaking change: Update to use the same naming scheme everywhere "ibmcloud-operator-*" Signed-off-by: Art Berger --- Makefile | 2 +- README.md | 148 +++++++++++++++++-------- config/default/kustomization.yaml | 4 +- config/manager/manager.yaml | 2 +- controllers/binding_controller_test.go | 2 +- controllers/service_controller_test.go | 2 +- controllers/suite_config_test.go | 6 +- controllers/token_controller.go | 4 +- controllers/token_controller_test.go | 38 +++---- docs/troubleshooting.md | 10 +- hack/configure-operator.sh | 5 +- internal/cmd/firstsetup/main.go | 11 +- internal/cmd/genolm/deployments.go | 2 +- internal/ibmcloud/ibmcloud.go | 10 +- 14 files changed, 150 insertions(+), 96 deletions(-) diff --git a/Makefile b/Makefile index bc4b88ae..92f46937 100644 --- a/Makefile +++ b/Makefile @@ -101,7 +101,7 @@ install: manifests kustomize .PHONY: uninstall uninstall: manifests kustomize kustomize build config/crd | kubectl delete -f - - kubectl delete secret/secret-ibm-cloud-operator configmap/config-ibm-cloud-operator + kubectl delete secret/ibmcloud-operator-secret configmap/ibmcloud-operator-defaults # Deploy controller in the configured Kubernetes cluster in ~/.kube/config .PHONY: deploy diff --git a/README.md b/README.md index 6f38c886..0f2e0b24 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ [![Build Status](https://travis-ci.com/IBM/cloud-operators.svg?branch=master)](https://travis-ci.com/IBM/cloud-operators) [![Go Report Card](https://goreportcard.com/badge/github.com/IBM/cloud-operators)](https://goreportcard.com/report/github.com/IBM/cloud-operators) [![codecov.io](https://codecov.io/github/IBM/cloud-operators/coverage.svg?branch=master)](https://codecov.io/github/IBM/cloud-operators?branch=master) +![Docker Pulls](https://img.shields.io/docker/pulls/cloudoperators/ibmcloud-operator) [![GoDoc](https://godoc.org/github.com/IBM/cloud-operators?status.svg)](https://godoc.org/github.com/IBM/cloud-operators) # IBM Cloud Operator @@ -9,8 +10,6 @@ With the IBM Cloud Operator, you can provision and bind [IBM public cloud services](https://cloud.ibm.com/catalog#services) to your Kubernetes cluster in a Kubernetes-native way. The IBM Cloud Operator is based on the [Kubernetes custom resource definition (CRD) API](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/) so that your applications can create, update, and delete IBM Cloud services from within the cluster by calling Kubnernetes APIs, instead of needing to use several IBM Cloud APIs in addition to configuring your app for Kubernetes. -For more information, see the [IBM Cloud Operator user guide](docs/user-guide.md). - ## Table of content @@ -22,6 +21,7 @@ For more information, see the [IBM Cloud Operator user guide](docs/user-guide.md * [Installing the operator for Kubernetes clusters](#installing-the-operator-for-kubernetes-clusters) * [Uninstalling the operator](#uninstalling-the-operator) * [Using the IBM Cloud Operator](#using-the-ibm-cloud-operator) +* [Upgrading the operator](#upgrading-the-operator) * [Using separate IBM Cloud accounts](#using-separate-ibm-cloud-accounts) * [Using a management namespace](#using-a-management-namespace) * [Reference documentation](#reference-documentation) @@ -40,7 +40,9 @@ For more information, see the [IBM Cloud Operator user guide](docs/user-guide.md * **Bindings management**: Automatically create Kubernetes secrets in your Kubernetes cluster with the credentials to bind to any provisioned service to the cluster. + [Back to top](#ibm-cloud-operator) + ## Prerequisites @@ -50,24 +52,35 @@ For more information, see the [IBM Cloud Operator user guide](docs/user-guide.md * [IBM Cloud CLI](https://cloud.ibm.com/docs/cli?topic=cloud-cli-getting-started) (`ibmcloud`) * [Kubernetes CLI](https://kubernetes.io/docs/tasks/tools/install-kubectl/) (`kubectl`) 4. Log in to your IBM Cloud account from the CLI. + ```bash ibmcloud login ``` + 5. Target the appropriate environment (`--cf`) and resource group (`-g`). Note that you must still set the Cloud Foundry `org` and `space` environment, even if you do not create any Cloud Foundry services. + ```bash ibmcloud target --cf -g ``` -6. Set the Kubernetes context of your CLI to your cluster so that you can run `kubectl` commands. For example, if your cluster is in IBM Cloud Kubernetes Service, run the following command. + +6. Set the Kubernetes context of your CLI to your cluster so that you can run `kubectl` commands. For example, if your cluster runs OpenShift, use the `oc login` command. + + + +If your cluster is in IBM Cloud Kubernetes Service, run the following command. + ```bash ibmcloud ks cluster config -c ``` To check that your Kubernetes context is set to your cluster, run the following command. + ```bash kubectl config current-context ``` [Back to top](#ibm-cloud-operator) + ## Setting up the operator @@ -80,72 +93,81 @@ Prefer to create the secrets and defaults yourself? See the [IBM Cloud Operator By default, the installation script creates an IBM Cloud API key that impersonates your user credentials, to use to set up the IBM Cloud Operator. However, you might want to create a service ID in IBM Cloud Identity and Access Managment (IAM). By using a service ID, you can control access for the IBM Cloud Operator without having the permissions tied to a particular user, such as if that user leaves the company. For more information, see the [IBM Cloud docs](https://cloud.ibm.com/docs/account?topic=account-serviceids). 1. Create a service ID in IBM Cloud IAM. + ```bash ibmcloud iam service-id-create serviceid-ico -d service-ID-for-ibm-cloud-operator ``` -2. Assign the service ID access to the required permissions. + +2. Assign the service ID access to the required permissions to work with the IBM Cloud services that you want the operator to manage. The required permissions vary with each IBM Cloud service. You can also scope an access policy to different regions or resource groups. For example, the following command grants the service ID the **Administrator** platform role in the default resource group in the US South (Dallas) region. For more information, see the [IBM Cloud docs](https://cloud.ibm.com/docs/account?topic=account-userroles). + ```bash - ibmcloud iam service-policy-create serviceid-ico -r Administrator + ibmcloud iam service-policy-create serviceid-ico --roles Administrator --resource-group-name default --region us-south ``` + 3. Create an API key for the service ID. + ```bash ibmcloud iam service-api-key-create apikey-ico serviceid-ico -d api-key-for-ibm-cloud-operator ``` + 4. Set the API key of the service ID as your CLI environment variable. Now, when you run the installation script, the script uses the service ID's API key. The following command is an example for macOS. + ```bash setenv IBMCLOUD_API_KEY ``` + 5. Confirm that the API key environment variable is set in your CLI. + ```bash echo $IBMCLOUD_API_KEY ``` -6. Follow the [prerequisite steps](#prerequisites) to log in to the IBM Cloud account that owns the service ID. +6. Follow the [prerequisite steps](README.md#prerequisites) to log in to the IBM Cloud account that owns the service ID. + + [Back to top](#ibm-cloud-operator) + ### Installing the operator for OpenShift clusters -Before you begin, complete the [prerequisite steps](#prerequisites) to log in to IBM Cloud and your cluster, and optionally set up a [service ID API key](#using-a-service-id). +Before you begin, complete the [prerequisite steps](README.md#prerequisites) to log in to IBM Cloud and your cluster, and optionally set up a [service ID API key](README.md#using-a-service-id). To install the latest release for OpenShift before install, run the following script: * **Latest release**: + ```bash curl -sL https://raw.githubusercontent.com/IBM/cloud-operators/master/hack/configure-operator.sh | bash ``` * **Specific release**: Replace `-v 0.0.0` with the specific version that you want to install. + ```bash curl -sL https://raw.githubusercontent.com/IBM/cloud-operators/master/hack/configure-operator.sh | bash -s -- -v 0.0.0 store-creds ``` -[Back to top](#ibm-cloud-operator) - +[Back to top](#ibm-cloud-operator) ### Installing the operator for Kubernetes clusters Before you begin, complete the [prerequisite steps](#prerequisites) to log in to IBM Cloud and your cluster, and optionally set up a [service ID API key](#using-a-service-id). -* **Stable release**: To install the latest stable release of the operator, run the following script. The stable release supports the `v1alpha1` Kubernetes API version. +* **Latest release**: To install the latest stable release of the operator, run the following script. + ```bash - curl -sL https://raw.githubusercontent.com/IBM/cloud-operators/master/hack/configure-operator.sh | bash -s -- -v 0.1.11 install - ``` - -* **Latest release**: The latest `v0.2.x` version involves major changes and is undergoing further testing to ensure a smoother transition. The latest release supports the `v1alpha1` or `v1beta1` Kubernetes API versions. - ```bash - curl -sL https://raw.githubusercontent.com/IBM/cloud-operators/master/hack/configure-operator.sh |bash -s -- install + curl -sL https://raw.githubusercontent.com/IBM/cloud-operators/master/hack/configure-operator.sh | bash -s -- install ``` * **Specific release**: Replace `-v 0.0.0` with the specific version that you want to install. + ```bash - curl -sL https://raw.githubusercontent.com/IBM/cloud-operators/master/hack/configure-operator.sh |bash -s -- -v 0.0.0 install + curl -sL https://raw.githubusercontent.com/IBM/cloud-operators/master/hack/configure-operator.sh | bash -s -- -v 0.0.0 install ``` [Back to top](#ibm-cloud-operator) ### Uninstalling the operator - Before you begin, complete the [prerequisite steps](#prerequisites) to log in to IBM Cloud and your cluster. To remove the operator, run the following script: @@ -160,17 +182,16 @@ curl -sL https://raw.githubusercontent.com/IBM/cloud-operators/master/hack/confi ## Using the IBM Cloud Operator -To use the IBM Cloud Operator, create a service instance and then bind the service to your cluster. For more information, see the [sample configuration files](config/samples) and [user guide](docs/user-guide.md). +To use the IBM Cloud Operator, create a service instance and then bind the service to your cluster. For more information, see the [sample configuration files](config/samples), [user guide](docs/user-guide.md), and [reference documentation](README.md#reference). **Step 1: Creating a service instance** -1. To create an instance of an IBM public cloud service, first create a `Service` custom resource file. For more options, see the [Service properties](#service-properties) reference doc. +1. To create an instance of an IBM public cloud service, first create a `Service` custom resource file. For more options, see the [Service properties](README.md#service-properties) reference doc. * `` is the IBM Cloud service that you want to create. To list IBM Cloud services, run `ibmcloud catalog service-marketplace` and use the **Name** value from the output. * `` is the plan for the IBM Cloud service that you want to create, such as `free` or `standard`. To list available plans, run `ibmcloud catalog service | grep plan`. - * **Note**: Using operator `v0.1.x`? Change the `apiVersion` to `v1alpha1` when you create the YAML files. ```yaml - apiVersion: ibmcloud.ibm.com/v1beta1 + apiVersion: ibmcloud.ibm.com/v1 kind: Service metadata: name: myservice @@ -180,26 +201,33 @@ To use the IBM Cloud Operator, create a service instance and then bind the servi ``` 2. Create the service instance in your cluster. + ```bash kubectl apply -f filepath/myservice.yaml ``` + 3. Check that your service status is **Online** in your cluster. + ```bash kubectl get services.ibmcloud NAME STATUS AGE myservice Online 12s ``` + 4. Verify that your service instance is created in IBM Cloud. + ```bash ibmcloud resource service-instances | grep myservice ``` +
+ **Step 2: Binding the service instance** -1. To bind your service to the cluster so that your apps can use the service, create a `Binding` custom resource, where the `serviceName` field is the name of the `Service` custom resource that you previously created. For more options, see [Binding properties](#binding-properties). **Note**: Using operator `v0.1.x`? Change the `apiVersion` to `v1alpha1` when you create the YAML files. +1. To bind your service to the cluster so that your apps can use the service, create a `Binding` custom resource, where the `serviceName` field is the name of the `Service` custom resource that you previously created. For more options, see [Binding properties](README.md#binding-properties). ```yaml - apiVersion: ibmcloud.ibm.com/v1beta1 + apiVersion: ibmcloud.ibm.com/v1 kind: Binding metadata: name: mybinding @@ -208,25 +236,46 @@ To use the IBM Cloud Operator, create a service instance and then bind the servi ``` 2. Create the binding in your cluster. + ```bash kubectl apply -f filepath/mybinding.yaml ``` + 3. Check that your service status is **Online**. + ```bash kubectl get bindings.ibmcloud NAME STATUS AGE mybinding Online 25s ``` + 4. Check that a secret of the same name as your binding is created. The secret contains the service credentials that apps in your cluster can use to access the service. + ```bash kubectl get secrets NAME TYPE DATA AGE mybinding Opaque 6 102s ``` + [Back to top](#ibm-cloud-operator) + + +## Upgrading the operator + +### Upgrading to version 0.3.0 or later +**IMPORTANT NOTICE:** v0.1 and v0.2 used a different naming scheme for secrets and configmaps. Before you update the IBM Cloud Operator, create secret and configmap resources with these names and copy the contents of your previous resources to the new resources. Then, the upgraded operator recognizes and continues to update the resources. + +| Previous names (< v0.3) | **Current names (v0.3 or later)** | Description | +|:---------------------------------------|:--------------------------------------------|:--------------------------------------------------------------------------------------------| +| secret-ibm-cloud-operator | **ibmcloud-operator-secret** | Secret with the API key, scoped to the namespace. | +| config-ibm-cloud-operator | **ibmcloud-operator-defaults** | ConfigMap with the default values for new resources. | +| ibm-cloud-operator | **ibmcloud-operator-config** | ConfigMap with the management namespace configuration. | +| ${namespace}-secret-ibm-cloud-operator | **${namespace}-ibmcloud-operator-secret** | Management namespace Secret with the API key for ${namespace}. | +| ${namespace}-config-ibm-cloud-operator | **${namespace}-ibmcloud-operator-defaults** | Management namespace ConfigMap with default values for new resources in ${namespace}. | +[Back to top](#ibm-cloud-operator) ## Using separate IBM Cloud accounts @@ -235,8 +284,8 @@ You can provision IBM Cloud services in separate IBM Cloud accounts from the sam **Tip**: Just want to use a different account one time and don't want to manage a bunch of namespaces? You can also specify a different account in the individual [service configuration](#service-properties), by overriding the default [account context](#account-context-in-operator-secret-and-configmap). 1. Get the IBM Cloud account details, including account ID, Cloud Foundry org and space, resource group, region, and API key credentials. -2. Edit or replace the `secret-ibm-cloud-operator` secret in the Kubernetes namespace that you want to use to create services in the account. -3. Edit or replace the `config-ibm-cloud-operator` configmap in the Kubernetes namespace that you want to use to create services in the account. +2. Edit or replace the `ibmcloud-operator-secret` secret in the Kubernetes namespace that you want to use to create services in the account. +3. Edit or replace the `ibmcloud-operator-defaults` configmap in the Kubernetes namespace that you want to use to create services in the account. 4. Optional: [Set up a management namespace](#setting-up-a-management-namespace) so that cluster users with access across namespaces cannot see the API keys for the different IBM Cloud accounts. [Back to top](#ibm-cloud-operator) @@ -246,16 +295,19 @@ You can provision IBM Cloud services in separate IBM Cloud accounts from the sam By default, the API key credentials and other IBM Cloud account information are stored in a secret and a configmap within each namespace where you create IBM Cloud Operator service and binding custom resources. However, you might want to hide access to this information from cluster users in the namespace. For example, you might have multiple IBM Cloud accounts that you do not want cluster users in different namespaces to know about. 1. Create a management namespace that is named `safe`. + ```bash kubectl create namespace safe ``` -2. In the namespace where the IBM Cloud Operator runs, create an `ibm-cloud-operator` configmap that points to the `safe` namespace. + +2. In the namespace where the IBM Cloud Operator runs, create an `ibmcloud-operator-config` configmap that points to the `safe` namespace. + ```bash cat <-secret-ibm-cloud-operator - -config-ibm-cloud-operator + -ibmcloud-operator-secret + -ibmcloud-operator-defaults ``` For example, if you have a cluster with three namespaces `default`, `test`, and `prod`: + ``` - default-secret-ibm-cloud-operator - default-config-ibm-cloud-operator - test-secret-ibm-cloud-operator - test-config-ibm-cloud-operator - prod-secret-ibm-cloud-operator - prod-config-ibm-cloud-operator + default-ibmcloud-operator-secret + default-ibmcloud-operator-defaults + test-ibmcloud-operator-secret + test-ibmcloud-operator-defaults + prod-ibmcloud-operator-secret + prod-ibmcloud-operator-defaults ``` -4. Delete the `secret-ibm-cloud-operator` secrets and `config-ibm-cloud-operator` configmaps in the other Kubernetes namespaces. + +4. Delete the `ibmcloud-operator-secret` secrets and `ibmcloud-operator-defaults` configmaps in the other Kubernetes namespaces. [Back to top](#ibm-cloud-operator) @@ -310,7 +366,7 @@ A `Binding` custom resources includes the properties in the following table. For |:-----------------|:---------|:---------|:------------------------------------------------------------------------------------------------------| | serviceName | Yes | `string` | The name of the `Service` resource that corresponds to the service instance on which to create credentials for the binding. | | serviceNamespace | No | `string` | The namespace of the `Service` resource.| -| alias | No | `string` | The name of credentials, if this binding links to existing credentials.| +| alias | No | `string` | The name of existing IBM Cloud credentials to link this binding to. This binding creates a secret for these credentials in the cluster namespace, but cannot modify the existing credentials in IBM Cloud.| | secretName | No | `string` | The name of the `Secret` to be created. If you do not specify a value, the secret is given the same name as the binding.| | role | No | `string` | The IBM Cloud IAM role to create the credentials to the service instance. Review the each service's documentation for a description of the roles. If you do not specify a role, the IAM `Manager` service access role is used. If the service does not support the `Manager` role, the first returned role from the service is used. | | parameters | No | `[]Any` | Parameters that are passed in to create the create the service credentials. These parameters vary by service, and can be anything, such as an integer, string, or object. | @@ -318,10 +374,9 @@ A `Binding` custom resources includes the properties in the following table. For [Back to top](#ibm-cloud-operator) ### Account context in operator secret and configmap +The IBM Cloud Operator needs an account context, which indicates the API key and the details of the IBM Cloud account to be used for creating services. The API key is stored in a `ibmcloud-operator-secret` secret that is created when the IBM Cloud Operator is installed. Account details such as the account ID, Cloud Foundry org and space, resource group, and region are stored in a `ibmcloud-operator-defaults` configmap. -The IBM Cloud Operator needs an account context, which indicates the API key and the details of the IBM Cloud account to be used for creating services. The API key is stored in a `secret-ibm-cloud-operator` secret that is created when the IBM Cloud Operator is installed. Account details such as the account ID, Cloud Foundry org and space, resource group, and region are stored in a `config-ibm-cloud-operator` configmap. - -When you create an IBM Cloud Operator service or binding resource, the operator checks the namespace that you create the resource in for the secret and configmap. If the the operator does not find the secret and configmap, it checks its own namespace for a configmap that points to a [management namespace](#setting-up-a-management-namespace). Then, the operator checks the management namespace for `-secret-ibm-cloud-operator` secrets and `-config-ibm-cloud-operator` configmaps. If no management namespace exists, the operator checks the `default` namespace for the `secret-ibm-cloud-operator` secret and `config-ibm-cloud-operator` configmap. +When you create an IBM Cloud Operator service or binding resource, the operator checks the namespace that you create the resource in for the secret and configmap. If the the operator does not find the secret and configmap, it checks its own namespace for a configmap that points to a [management namespace](#setting-up-a-management-namespace). Then, the operator checks the management namespace for `-ibmcloud-operator-secret` secrets and `-ibmcloud-operator-defaults` configmaps. If no management namespace exists, the operator checks the `default` namespace for the `ibmcloud-operator-secret` secret and `ibmcloud-operator-defaults` configmap. You can override the account context in the `Service` configuration file with the `context` field, as described in the following table. You might override the account context if you want to use a different IBM Cloud account to provision a service, but do not want to create separate secrets and configmaps for different namespaces. @@ -332,7 +387,7 @@ You can override the account context in the `Service` configuration file with th | region | No | `string` | The IBM Cloud region. To list regions, run `ibmcloud regions`. | | resourceGroup | No | `string` | The IBM Cloud resource group name. You must also include the `resourceGroupID`. To list resource groups, run `ibmcloud resource groups`. | | resourceGroupID | No | `string` | The IBM Cloud resource group ID. You must also include the `resourceGroup`. To list resource groups, run `ibmcloud resource groups`. | -| resourceLocation | No | `string` | The location of the resource. | +| resourceLocation | No | `string` | The location of the resource.| [Back to top](#ibm-cloud-operator) @@ -342,8 +397,9 @@ Review the supported Kubernetes API versions for the following IBM Cloud Operato | Operator version | Kubernetes API version | | --- | --- | -| `v0.2.x` | `v1beta1` or `v1alpha1` | -| `v0.1.x` | `v1alpha` | +| `v0.3` or later | `v1` | +| `v0.2` | `v1beta1` or `v1alpha1` | +| `v0.1` | `v1alpha` | [Back to top](#ibm-cloud-operator) @@ -357,4 +413,4 @@ See [Contributing to Cloud Operators](./CONTRIBUTING.md) See the [Troubleshooting guide](docs/troubleshooting.md). -[Back to top](#ibm-cloud-operator) +[Back to top](#ibm-cloud-operator) \ No newline at end of file diff --git a/config/default/kustomization.yaml b/config/default/kustomization.yaml index 77571280..4431a2dc 100644 --- a/config/default/kustomization.yaml +++ b/config/default/kustomization.yaml @@ -1,12 +1,12 @@ # Adds namespace to all resources. -namespace: ibmcloud-operators-system +namespace: ibmcloud-operator-system # Value of this field is prepended to the # names of all resources, e.g. a deployment named # "wordpress" becomes "alices-wordpress". # Note that it should also match with the prefix (text before '-') of the namespace # field above. -namePrefix: ibmcloud-operators- +namePrefix: ibmcloud-operator- # Labels to add to all resources and selectors. #commonLabels: diff --git a/config/manager/manager.yaml b/config/manager/manager.yaml index 3bddfb5e..36aa9309 100644 --- a/config/manager/manager.yaml +++ b/config/manager/manager.yaml @@ -3,7 +3,7 @@ kind: Namespace metadata: labels: control-plane: controller-manager - name: ibmcloud-operators-system + name: ibmcloud-operator-system --- apiVersion: apps/v1 kind: Deployment diff --git a/controllers/binding_controller_test.go b/controllers/binding_controller_test.go index 5ad13a25..a3495eab 100644 --- a/controllers/binding_controller_test.go +++ b/controllers/binding_controller_test.go @@ -514,7 +514,7 @@ func TestBindingGetIBMCloudInfoFailed(t *testing.T) { fake.NewFakeClientWithScheme(scheme, objects...), MockConfig{UpdateErr: fmt.Errorf("failed")}, ) - return nil, errors.NewNotFound(ctrl.GroupResource{Group: "ibmcloud.ibm.com", Resource: "secret"}, "secret-ibm-cloud-operator") + return nil, errors.NewNotFound(ctrl.GroupResource{Group: "ibmcloud.ibm.com", Resource: "secret"}, "ibmcloud-operator-secret") }, } diff --git a/controllers/service_controller_test.go b/controllers/service_controller_test.go index bf4477c5..45b2bd6d 100644 --- a/controllers/service_controller_test.go +++ b/controllers/service_controller_test.go @@ -321,7 +321,7 @@ func TestServiceGetIBMCloudInfoFailed(t *testing.T) { Scheme: scheme, GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1beta1.Service) (*ibmcloud.Info, error) { - return nil, errors.NewNotFound(ctrl.GroupResource{Group: "ibmcloud.ibm.com", Resource: "secret"}, "secret-ibm-cloud-operator") + return nil, errors.NewNotFound(ctrl.GroupResource{Group: "ibmcloud.ibm.com", Resource: "secret"}, "ibmcloud-operator-secret") }, } diff --git a/controllers/suite_config_test.go b/controllers/suite_config_test.go index 2180bfe9..79942fc0 100644 --- a/controllers/suite_config_test.go +++ b/controllers/suite_config_test.go @@ -49,7 +49,7 @@ func setupConfigs() error { err := k8sClient.Create(ctx, &v1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ - Name: "config-ibm-cloud-operator", + Name: "ibmcloud-operator-defaults", Namespace: testNamespace, }, Data: map[string]string{ @@ -66,7 +66,7 @@ func setupConfigs() error { err = k8sClient.Create(ctx, &v1.Secret{ ObjectMeta: metav1.ObjectMeta{ - Name: "secret-ibm-cloud-operator", + Name: "ibmcloud-operator-secret", Namespace: testNamespace, }, Data: map[string][]byte{ @@ -79,7 +79,7 @@ func setupConfigs() error { return k8sClient.Create(ctx, &v1.Secret{ ObjectMeta: metav1.ObjectMeta{ - Name: "secret-ibm-cloud-operator-tokens", + Name: "ibmcloud-operator-tokens", Namespace: testNamespace, }, Data: map[string][]byte{ diff --git a/controllers/token_controller.go b/controllers/token_controller.go index e818434a..3b50dca6 100644 --- a/controllers/token_controller.go +++ b/controllers/token_controller.go @@ -34,8 +34,8 @@ import ( ) const ( - icoSecretName = "secret-ibm-cloud-operator" - icoTokensName = "secret-ibm-cloud-operator-tokens" + icoSecretName = "ibmcloud-operator-secret" + icoTokensName = "ibmcloud-operator-tokens" ) // TokenReconciler reconciles a Token object diff --git a/controllers/token_controller_test.go b/controllers/token_controller_test.go index 169fc931..9caffccd 100644 --- a/controllers/token_controller_test.go +++ b/controllers/token_controller_test.go @@ -31,7 +31,7 @@ func TestToken(t *testing.T) { // Create the secret object and expect the Reconcile const ( - secretName = "secret-ibm-cloud-operator" + secretName = "ibmcloud-operator-secret" secretAPIKey = "VExS246avaUT6MXZ56SH_I-AeWo_-JmW0u79Jd8LiBH" // nolint:gosec // Fake API key ) @@ -62,7 +62,7 @@ func TestToken(t *testing.T) { var secret corev1.Secret assert.Eventually(t, func() bool { - err := k8sClient.Get(context.TODO(), client.ObjectKey{Namespace: "default", Name: "secret-ibm-cloud-operator-tokens"}, &secret) + err := k8sClient.Get(context.TODO(), client.ObjectKey{Namespace: "default", Name: "ibmcloud-operator-tokens"}, &secret) if err != nil { t.Log("Failed to get secret:", err) return false @@ -83,7 +83,7 @@ func TestTokenFailedAuth(t *testing.T) { scheme := schemas(t) objects := []runtime.Object{ &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{Name: "secret-ibm-cloud-operator"}, + ObjectMeta: metav1.ObjectMeta{Name: "ibmcloud-operator-secret"}, Data: map[string][]byte{ "api-key": []byte(`bogus key`), }, @@ -99,7 +99,7 @@ func TestTokenFailedAuth(t *testing.T) { } result, err := r.Reconcile(ctrl.Request{ - NamespacedName: types.NamespacedName{Name: "secret-ibm-cloud-operator"}, + NamespacedName: types.NamespacedName{Name: "ibmcloud-operator-secret"}, }) assert.EqualError(t, err, "failure") assert.Equal(t, ctrl.Result{}, result) @@ -117,7 +117,7 @@ func TestTokenFailedSecretLookup(t *testing.T) { t.Run("not found", func(t *testing.T) { result, err := r.Reconcile(ctrl.Request{ - NamespacedName: types.NamespacedName{Name: "secret-ibm-cloud-operator"}, + NamespacedName: types.NamespacedName{Name: "ibmcloud-operator-secret"}, }) assert.NoError(t, err, "Don't retry (return err) if secret no longer exists") assert.Equal(t, ctrl.Result{}, result) @@ -126,7 +126,7 @@ func TestTokenFailedSecretLookup(t *testing.T) { r.Client = fake.NewFakeClientWithScheme(runtime.NewScheme()) // fail to read the type Secret t.Run("failed to read secret", func(t *testing.T) { result, err := r.Reconcile(ctrl.Request{ - NamespacedName: types.NamespacedName{Name: "secret-ibm-cloud-operator"}, + NamespacedName: types.NamespacedName{Name: "ibmcloud-operator-secret"}, }) assert.Error(t, err) assert.False(t, k8sErrors.IsNotFound(err)) @@ -141,7 +141,7 @@ func TestTokenSecretIsDeleting(t *testing.T) { objects := []runtime.Object{ &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ - Name: "secret-ibm-cloud-operator", + Name: "ibmcloud-operator-secret", DeletionTimestamp: now, }, }, @@ -154,7 +154,7 @@ func TestTokenSecretIsDeleting(t *testing.T) { } result, err := r.Reconcile(ctrl.Request{ - NamespacedName: types.NamespacedName{Name: "secret-ibm-cloud-operator"}, + NamespacedName: types.NamespacedName{Name: "ibmcloud-operator-secret"}, }) assert.NoError(t, err, "Don't retry (return err) if secret is deleting") assert.Equal(t, ctrl.Result{}, result) @@ -165,7 +165,7 @@ func TestTokenAPIKeyIsMissing(t *testing.T) { scheme := schemas(t) objects := []runtime.Object{ &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{Name: "secret-ibm-cloud-operator"}, + ObjectMeta: metav1.ObjectMeta{Name: "ibmcloud-operator-secret"}, Data: nil, // no API key }, } @@ -177,7 +177,7 @@ func TestTokenAPIKeyIsMissing(t *testing.T) { } result, err := r.Reconcile(ctrl.Request{ - NamespacedName: types.NamespacedName{Name: "secret-ibm-cloud-operator"}, + NamespacedName: types.NamespacedName{Name: "ibmcloud-operator-secret"}, }) assert.NoError(t, err, "Don't retry (return err) if secret does not contain an api-key entry") assert.Equal(t, ctrl.Result{}, result) @@ -192,7 +192,7 @@ func TestTokenAuthInvalidConfig(t *testing.T) { ) objects := []runtime.Object{ &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{Name: "secret-ibm-cloud-operator"}, + ObjectMeta: metav1.ObjectMeta{Name: "ibmcloud-operator-secret"}, Data: map[string][]byte{ "api-key": []byte(apiKey), "region": []byte(region), @@ -211,7 +211,7 @@ func TestTokenAuthInvalidConfig(t *testing.T) { } result, err := r.Reconcile(ctrl.Request{ - NamespacedName: types.NamespacedName{Name: "secret-ibm-cloud-operator"}, + NamespacedName: types.NamespacedName{Name: "ibmcloud-operator-secret"}, }) assert.NoError(t, err, "Don't retry (return err) if secret region is invalid") assert.Equal(t, ctrl.Result{}, result) @@ -227,7 +227,7 @@ func TestTokenDeleteFailed(t *testing.T) { ) objects := []runtime.Object{ &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{Name: "secret-ibm-cloud-operator"}, + ObjectMeta: metav1.ObjectMeta{Name: "ibmcloud-operator-secret"}, Data: map[string][]byte{ "api-key": []byte(apiKey), "region": []byte(region), @@ -250,7 +250,7 @@ func TestTokenDeleteFailed(t *testing.T) { } result, err := r.Reconcile(ctrl.Request{ - NamespacedName: types.NamespacedName{Name: "secret-ibm-cloud-operator"}, + NamespacedName: types.NamespacedName{Name: "ibmcloud-operator-secret"}, }) assert.Error(t, err) assert.False(t, k8sErrors.IsNotFound(err)) @@ -266,14 +266,14 @@ func TestTokenRaceCreateFailed(t *testing.T) { accessToken = "some access token" ) tokensSecret := &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{Name: "secret-ibm-cloud-operator-tokens"}, + ObjectMeta: metav1.ObjectMeta{Name: "ibmcloud-operator-tokens"}, Data: map[string][]byte{ "access_token": []byte("old " + accessToken), }, } objects := []runtime.Object{ &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{Name: "secret-ibm-cloud-operator"}, + ObjectMeta: metav1.ObjectMeta{Name: "ibmcloud-operator-secret"}, Data: map[string][]byte{ "api-key": []byte(apiKey), "region": []byte(region), @@ -312,7 +312,7 @@ func TestTokenRaceCreateFailed(t *testing.T) { var err error require.Eventually(t, func() bool { result, err = r.Reconcile(ctrl.Request{ - NamespacedName: types.NamespacedName{Name: "secret-ibm-cloud-operator"}, + NamespacedName: types.NamespacedName{Name: "ibmcloud-operator-secret"}, }) return err != nil }, 5*time.Second, 10*time.Millisecond) @@ -325,10 +325,10 @@ func TestShouldProcessSecret(t *testing.T) { t.Parallel() t.Run("normal secret", func(t *testing.T) { - assert.True(t, shouldProcessSecret(&metav1.ObjectMeta{Name: "secret-ibm-cloud-operator"})) + assert.True(t, shouldProcessSecret(&metav1.ObjectMeta{Name: "ibmcloud-operator-secret"})) }) t.Run("management namespace secret", func(t *testing.T) { - assert.True(t, shouldProcessSecret(&metav1.ObjectMeta{Name: "mynamespace-secret-ibm-cloud-operator"})) + assert.True(t, shouldProcessSecret(&metav1.ObjectMeta{Name: "mynamespace-ibmcloud-operator-secret"})) }) } diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 708b6443..f83dd79a 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -5,20 +5,20 @@ To check if the operator is correctly started, type: ``` -kubectl get pod -l "app=ibmcloud-operator" -n ibmcloud-operators +kubectl get pods -n ibmcloud-operator-system ``` if the operator is running, you should get an output similar to the following: ``` NAME READY STATUS RESTARTS AGE -ibmcloud-operator-5885bd58c4-84q52 1/1 Running 0 7s +ibmcloud-operator-controller-manager-5885bd58c4-84q52 1/1 Running 0 7s ``` to check the operator logs, type: ``` -kubectl logs -n ibmcloud-operators $(kubectl get pod -l "app=ibmcloud-operator" -n ibmcloud-operators -o jsonpath='{.items[0].metadata.name}') +kubectl logs -n ibmcloud-operator-system $(kubectl get pods -n ibmcloud-operator-system -o jsonpath='{.items[0].metadata.name}') ``` ## Finding the current git revision for the operator @@ -26,5 +26,5 @@ kubectl logs -n ibmcloud-operators $(kubectl get pod -l "app=ibmcloud-operator" To find the current git revision for the operator, type: ``` -kubectl exec -n ibmcloud-operators $(kubectl get pod -l "app=ibmcloud-operator" -n ibmcloud-operators -o jsonpath='{.items[0].metadata.name}') -- cat git-rev -``` \ No newline at end of file +kubectl exec -n ibmcloud-operator-system $(kubectl get pods -n ibmcloud-operator-system -o jsonpath='{.items[0].metadata.name}') -- cat git-rev +``` diff --git a/hack/configure-operator.sh b/hack/configure-operator.sh index 505a98c7..2ac26824 100755 --- a/hack/configure-operator.sh +++ b/hack/configure-operator.sh @@ -187,9 +187,8 @@ store_creds() { apiVersion: v1 kind: Secret metadata: - name: secret-ibm-cloud-operator + name: ibmcloud-operator-secret labels: - seed.ibm.com/ibmcloud-token: "apikey" app.kubernetes.io/name: ibmcloud-operator namespace: default type: Opaque @@ -212,7 +211,7 @@ EOT apiVersion: v1 kind: ConfigMap metadata: - name: config-ibm-cloud-operator + name: ibmcloud-operator-defaults namespace: default labels: app.kubernetes.io/name: ibmcloud-operator diff --git a/internal/cmd/firstsetup/main.go b/internal/cmd/firstsetup/main.go index 1c0e09a5..18a03e23 100644 --- a/internal/cmd/firstsetup/main.go +++ b/internal/cmd/firstsetup/main.go @@ -27,8 +27,8 @@ func run() error { return err } - _, secretErr := k8sClient.CoreV1().Secrets(namespace).Get("secret-ibm-cloud-operator", metav1.GetOptions{}) - _, configMapErr := k8sClient.CoreV1().ConfigMaps(namespace).Get("config-ibm-cloud-operator", metav1.GetOptions{}) + _, secretErr := k8sClient.CoreV1().Secrets(namespace).Get("ibmcloud-operator-secret", metav1.GetOptions{}) + _, configMapErr := k8sClient.CoreV1().ConfigMaps(namespace).Get("ibmcloud-operator-defaults", metav1.GetOptions{}) if secretErr == nil && configMapErr == nil { fmt.Println("IBM Cloud Operators configmap and secret already set up. Skipping...") return nil @@ -38,11 +38,10 @@ func run() error { _, err = k8sClient.CoreV1().Secrets(namespace).Create(&v1.Secret{ ObjectMeta: metav1.ObjectMeta{ - Name: "secret-ibm-cloud-operator", + Name: "ibmcloud-operator-secret", Namespace: namespace, Labels: map[string]string{ - "seed.ibm.com/ibmcloud-token": "apikey", - "app.kubernetes.io/name": "ibmcloud-operator", + "app.kubernetes.io/name": "ibmcloud-operator", }, }, Data: map[string][]byte{ @@ -56,7 +55,7 @@ func run() error { _, err = k8sClient.CoreV1().ConfigMaps(namespace).Create(&v1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ - Name: "config-ibm-cloud-operator", + Name: "ibmcloud-operator-defaults", Namespace: namespace, Labels: map[string]string{ "app.kubernetes.io/name": "ibmcloud-operator", diff --git a/internal/cmd/genolm/deployments.go b/internal/cmd/genolm/deployments.go index 4a84bfbc..e0135ad1 100644 --- a/internal/cmd/genolm/deployments.go +++ b/internal/cmd/genolm/deployments.go @@ -16,7 +16,7 @@ type Deployment struct { func getDeployments(output string) ([]Deployment, error) { var deployment appsv1.Deployment - deploymentBytes, err := ioutil.ReadFile(filepath.Join(output, "apps_v1_deployment_ibmcloud-operators-controller-manager.yaml")) + deploymentBytes, err := ioutil.ReadFile(filepath.Join(output, "apps_v1_deployment_ibmcloud-operator-controller-manager.yaml")) if err != nil { return nil, errors.Wrap(err, "Error reading generated deployment file. Did kustomize run yet?") } diff --git a/internal/ibmcloud/ibmcloud.go b/internal/ibmcloud/ibmcloud.go index 86dc3e47..0dce16ef 100644 --- a/internal/ibmcloud/ibmcloud.go +++ b/internal/ibmcloud/ibmcloud.go @@ -26,10 +26,10 @@ import ( const ( aliasPlan = "alias" - seedInstall = "ibm-cloud-operator" - seedSecret = "secret-ibm-cloud-operator" - seedDefaults = "config-ibm-cloud-operator" - seedTokens = "secret-ibm-cloud-operator-tokens" + icoConfigMap = "ibmcloud-operator-config" + seedSecret = "ibmcloud-operator-secret" + seedDefaults = "ibmcloud-operator-defaults" + seedTokens = "ibmcloud-operator-tokens" ) // Info kept all the needed client API resource and instance Info @@ -349,7 +349,7 @@ func getIBMCloudContext(instance *ibmcloudv1beta1.Service, cm *v1.ConfigMap) ibm func getDefaultNamespace(r client.Client) (string, bool) { cm := &v1.ConfigMap{} - err := r.Get(context.Background(), types.NamespacedName{Namespace: config.Get().ControllerNamespace, Name: seedInstall}, cm) + err := r.Get(context.Background(), types.NamespacedName{Namespace: config.Get().ControllerNamespace, Name: icoConfigMap}, cm) if err != nil { return "default", false } From c7f0d971d958ffb0d869e738c9287c55dc0bb4d8 Mon Sep 17 00:00:00 2001 From: John Starich Date: Fri, 18 Sep 2020 18:44:11 -0500 Subject: [PATCH 11/20] Add new stable channel, new versions will update the stable channel (#203) * Add new stable channel. New versions will update the stable channel. This isn't perfect right now, but it will be accurate once we release v1.0.0. Signed-off-by: John Starich * Set default channel to stable Signed-off-by: John Starich * Order stable channel before alpha Signed-off-by: John Starich * Fix validation while including old alpha release Signed-off-by: John Starich Signed-off-by: Art Berger --- Makefile | 4 ++++ internal/cmd/genolm/templates/package.yaml | 5 ++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 92f46937..e795b0ff 100644 --- a/Makefile +++ b/Makefile @@ -185,6 +185,10 @@ operator-courier: .PHONY: verify-operator-meta verify-operator-meta: release-prep operator-courier + # Download the alpha channel's last release of CSV and CRDs so it passes validation + curl -sL https://github.com/IBM/cloud-operators/releases/download/v0.1.11/001_ibmcloud_v1alpha1_binding.yaml > out/0.1.11_ibmcloud_v1alpha1_binding.yaml + curl -sL https://github.com/IBM/cloud-operators/releases/download/v0.1.11/002_ibmcloud_v1alpha1_service.yaml > out/0.1.11_ibmcloud_v1alpha1_service.yaml + curl -sL https://github.com/IBM/cloud-operators/releases/download/v0.1.11/ibmcloud_operator.v0.1.11.clusterserviceversion.yaml > out/ibmcloud_operator.v0.1.11.clusterserviceversion.yaml operator-courier verify --ui_validate_io out/ .PHONY: operator-push-test diff --git a/internal/cmd/genolm/templates/package.yaml b/internal/cmd/genolm/templates/package.yaml index 28253704..20f6b741 100644 --- a/internal/cmd/genolm/templates/package.yaml +++ b/internal/cmd/genolm/templates/package.yaml @@ -1,4 +1,7 @@ packageName: {{.Name}} channels: - - name: alpha + - name: stable currentCSV: {{.Name}}.v{{.Version}} + - name: alpha + currentCSV: ibmcloud-operator.v0.1.11 +defaultChannel: stable From f681f62e05225d487dddfb6327b70039a7a92350 Mon Sep 17 00:00:00 2001 From: John Starich Date: Fri, 18 Sep 2020 19:01:19 -0500 Subject: [PATCH 12/20] Fix Markdown formatting for Operator Hub (#202) * Fix Markdown formatting for Operator Hub Signed-off-by: John Starich * Poke Travis Signed-off-by: John Starich Signed-off-by: Art Berger --- internal/cmd/genolm/templates/clusterserviceversion.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/cmd/genolm/templates/clusterserviceversion.yaml b/internal/cmd/genolm/templates/clusterserviceversion.yaml index 0352c11f..8c5da92c 100644 --- a/internal/cmd/genolm/templates/clusterserviceversion.yaml +++ b/internal/cmd/genolm/templates/clusterserviceversion.yaml @@ -20,7 +20,7 @@ spec: # image should be 175x175 - base64data: {{include "ibmcloud.png" . | base64}} mediatype: image/png - description: > + description: |- {{.README | indent 4 | trimSpace}} version: {{.Version}} replaces: {{.Name}}.v{{.ReplaceVersion}} From 3ff174d2ac3f42aa7a034a7423a90b8ea44e8b92 Mon Sep 17 00:00:00 2001 From: John Starich Date: Fri, 18 Sep 2020 20:28:37 -0500 Subject: [PATCH 13/20] Upgrade custom resource types to v1 (#204) Signed-off-by: Art Berger --- Makefile | 2 +- PROJECT | 6 + api/v1/binding_types.go | 98 +++ api/v1/groupversion_info.go | 36 ++ api/v1/param.go | 42 ++ api/v1/service_types.go | 144 +++++ api/v1/zz_generated.deepcopy.go | 345 +++++++++++ api/v1beta1/binding_types.go | 1 - api/v1beta1/service_types.go | 1 - .../crd/bases/ibmcloud.ibm.com_bindings.yaml | 131 +++- .../crd/bases/ibmcloud.ibm.com_services.yaml | 244 +++++++- config/samples/apiconnect.yaml | 2 +- config/samples/appid.yaml | 4 +- config/samples/cloudant-with-tags.yaml | 4 +- config/samples/cloudant.yaml | 8 +- config/samples/contdelivery.yaml | 2 +- config/samples/cos.yaml | 4 +- config/samples/db2.yaml | 4 +- config/samples/elastic.yaml | 4 +- config/samples/etcd.yaml | 4 +- config/samples/iot.yaml | 4 +- config/samples/keyprotect.yaml | 4 +- config/samples/knowledge.yaml | 2 +- config/samples/messagehub-binding-alias.yaml | 6 +- config/samples/messagehub-service-alias.yaml | 2 +- config/samples/messagehub.yaml | 4 +- config/samples/messagehubCF-named.yaml | 4 +- config/samples/messagehubCF.yaml | 4 +- config/samples/mq.yaml | 2 +- config/samples/param.yaml | 2 +- config/samples/personality.yaml | 4 +- config/samples/postgresql.yaml | 4 +- config/samples/sql-query.yaml | 2 +- config/samples/streams.yaml | 4 +- config/samples/sysdig.yaml | 4 +- config/samples/test.yaml | 6 +- config/samples/tone.yaml | 4 +- config/samples/trans.yaml | 4 +- config/samples/translator-alias.yaml | 2 +- .../samples/translator-binding-with-ns.yaml | 2 +- config/samples/translator-binding.yaml | 2 +- config/samples/translator-bindonly.yaml | 2 +- config/samples/translator-named.yaml | 2 +- config/samples/translator.yaml | 2 +- controllers/binding_controller.go | 52 +- controllers/binding_controller_test.go | 476 +++++++------- controllers/secret.go | 4 +- controllers/service_controller.go | 40 +- controllers/service_controller_test.go | 581 +++++++++--------- controllers/suite_config_test.go | 11 +- controllers/suite_test.go | 4 +- controllers/testdata/cos.yaml | 2 +- controllers/testdata/messagehub.yaml | 2 +- controllers/testdata/translator-2.yaml | 2 +- controllers/testdata/translator-alias.yaml | 2 +- controllers/testdata/translator-binding.yaml | 2 +- controllers/testdata/translator-v1beta1.yaml | 7 + .../testdata/translator-wrong-plan.yaml | 2 +- controllers/testdata/translator.yaml | 2 +- docs/user-guide.md | 20 +- internal/cmd/fixcrd/main.go | 2 +- internal/ibmcloud/ibmcloud.go | 24 +- main.go | 2 + 63 files changed, 1737 insertions(+), 664 deletions(-) create mode 100644 api/v1/binding_types.go create mode 100644 api/v1/groupversion_info.go create mode 100644 api/v1/param.go create mode 100644 api/v1/service_types.go create mode 100644 api/v1/zz_generated.deepcopy.go create mode 100644 controllers/testdata/translator-v1beta1.yaml diff --git a/Makefile b/Makefile index e795b0ff..00f59648 100644 --- a/Makefile +++ b/Makefile @@ -172,7 +172,7 @@ out: release-prep: kustomize manifests out cd config/manager && kustomize edit set image controller=${IMG} kustomize build config/default --output out/ - go run ./internal/cmd/genolm --version ${RELEASE_VERSION} + ulimit -n 1000 && go run ./internal/cmd/genolm --version ${RELEASE_VERSION} .PHONY: release release: release-prep docker-push diff --git a/PROJECT b/PROJECT index 4b498372..957e7068 100644 --- a/PROJECT +++ b/PROJECT @@ -13,4 +13,10 @@ resources: - group: ibmcloud kind: Service version: v1beta1 +- group: ibmcloud + kind: Binding + version: v1 +- group: ibmcloud + kind: Service + version: v1 version: "2" diff --git a/api/v1/binding_types.go b/api/v1/binding_types.go new file mode 100644 index 00000000..8b90380b --- /dev/null +++ b/api/v1/binding_types.go @@ -0,0 +1,98 @@ +/* + * Copyright 2020 IBM Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package v1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// BindingSpec defines the desired state of Binding +type BindingSpec struct { + // ServiceClass is the name of the service resource to bind + ServiceName string `json:"serviceName"` + // ServiceNamespace is the namespace of the service resource to bind + // +optional + ServiceNamespace string `json:"serviceNamespace,omitempty"` + // SecretName is the name of the secret where credentials will be stored + // +optional + SecretName string `json:"secretName,omitempty"` + // Role is the role for the credentials + // +optional + Role string `json:"role,omitempty"` + // Alias is the name for the credentials to be aliased + // +optional + Alias string `json:"alias,omitempty"` + // Parameters pass configuration to the service during creation + // +optional + Parameters []Param `json:"parameters,omitempty"` +} + +// BindingStatus defines the observed state of Binding +type BindingStatus struct { + // State is a short name for the current status + State string `json:"state,omitempty"` + // Message is a detailed message on current status + Message string `json:"message,omitempty"` + + // +optional + Generation int64 `json:"generation,omitempty"` + // InstanceID is the instance ID for the service + // +optional + InstanceID string `json:"instanceId,omitempty"` + // KeyInstanceID is the key instance ID for the credentials + // +optional + KeyInstanceID string `json:"keyInstanceId,omitempty"` + // SecretName is the name of the generated secret with service credentials + // +optional + SecretName string `json:"secretName,omitempty"` +} + +// +kubebuilder:object:root=true +// +kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.state" +// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" +// +kubebuilder:subresource:status +// +kubebuilder:storageversion + +// Binding is an instance of a service binding resource on IBM Cloud. A Binding creates a secret with the service instance credentials. +type Binding struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec BindingSpec `json:"spec,omitempty"` + Status BindingStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// BindingList contains a list of Binding +type BindingList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Binding `json:"items"` +} + +func init() { + SchemeBuilder.Register(&Binding{}, &BindingList{}) +} + +func (b *Binding) GetState() string { + return b.Status.State +} + +func (b *Binding) GetMessage() string { + return b.Status.Message +} diff --git a/api/v1/groupversion_info.go b/api/v1/groupversion_info.go new file mode 100644 index 00000000..8a421ef2 --- /dev/null +++ b/api/v1/groupversion_info.go @@ -0,0 +1,36 @@ +/* + * Copyright 2020 IBM Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Package v1 contains API Schema definitions for the ibmcloud v1 API group +// +kubebuilder:object:generate=true +// +groupName=ibmcloud.ibm.com +package v1 + +import ( + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/scheme" +) + +var ( + // GroupVersion is group version used to register these objects + GroupVersion = schema.GroupVersion{Group: "ibmcloud.ibm.com", Version: "v1"} + + // SchemeBuilder is used to add go types to the GroupVersionKind scheme + SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} + + // AddToScheme adds the types in this group-version to the given scheme. + AddToScheme = SchemeBuilder.AddToScheme +) diff --git a/api/v1/param.go b/api/v1/param.go new file mode 100644 index 00000000..f93b0a47 --- /dev/null +++ b/api/v1/param.go @@ -0,0 +1,42 @@ +package v1 + +import ( + "encoding/json" + + v1 "k8s.io/api/core/v1" +) + +// Param represents a key-value pair +type Param struct { + // Name representing the key. + Name string `json:"name"` + + // A parameter may have attributes (e.g. message hub topic might have partitions) + // +optional + Attributes map[string]ParamValue `json:"attributes,omitempty"` + + // Mutual exclusive: no more than one of the following may be specified. + + // Defaults to null. + // +optional + Value *ParamValue `json:"value,omitempty"` + + // Source for the value. Cannot be used if value is not empty. + // +optional + ValueFrom *ParamSource `json:"valueFrom,omitempty"` +} + +// ParamSource represents a source for the value of a Param. +type ParamSource struct { + // Selects a key of a ConfigMap. + // +optional + ConfigMapKeyRef *v1.ConfigMapKeySelector `json:"configMapKeyRef,omitempty"` + + // Selects a key of a secret in the resource namespace + // +optional + SecretKeyRef *v1.SecretKeySelector `json:"secretKeyRef,omitempty"` +} + +type ParamValue struct { + json.RawMessage `json:"-"` +} diff --git a/api/v1/service_types.go b/api/v1/service_types.go new file mode 100644 index 00000000..01c6d000 --- /dev/null +++ b/api/v1/service_types.go @@ -0,0 +1,144 @@ +/* + * Copyright 2020 IBM Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package v1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// ServiceSpec defines the desired state of Service +type ServiceSpec struct { + // ServiceClass is the name of the service from the IBM Cloud Catalog + ServiceClass string `json:"serviceClass"` + // Plan for the service from the IBM Cloud Catalog + Plan string `json:"plan"` + // ServiceClassType is set to CF if the service is CloundFoundry + // +optional + ServiceClassType string `json:"serviceClassType,omitempty"` + // ExternalName is the name for the service as it appears on IBM Cloud + // +optional + ExternalName string `json:"externalName,omitempty"` + // Parameters pass configuration to the service during creation + // +optional + Parameters []Param `json:"parameters,omitempty"` + // +optional + Tags []string `json:"tags,omitempty"` + // +optional + Context ResourceContext `json:"context,omitempty"` +} + +// ServiceStatus defines the observed state of Service +type ServiceStatus struct { + // State is a short name for the current status + State string `json:"state,omitempty"` + // Message is a detailed message on current status + Message string `json:"message,omitempty"` + + Generation int64 `json:"generation,omitempty"` + + // ServiceClass is the name of the service from the IBM Cloud Catalog + ServiceClass string `json:"serviceClass"` + // ServiceClassType is set to CF if the service is CloundFoundry + ServiceClassType string `json:"serviceClassType"` + // Plan for the service from the IBM Cloud Catalog + Plan string `json:"plan"` + // InstanceID is the instance ID for the service + // +optional + InstanceID string `json:"instanceId,omitempty"` + // ExternalName is the name for the service as it appears on IBM Cloud + // +optional + ExternalName string `json:"externalName,omitempty"` + // +optional + Context ResourceContext `json:"context,omitempty"` + // Parameters pass configuration to the service during creation + // +optional + Parameters []Param `json:"parameters,omitempty"` + // +optional + Tags []string `json:"tags,omitempty"` + // DashboardURL is the dashboard URL for the service + // +optional + DashboardURL string `json:"dashboardURL,omitempty"` +} + +// +kubebuilder:object:root=true +// +kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.state" +// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" +// +kubebuilder:subresource:status +// +kubebuilder:storageversion + +// Service is an instance of a Service resource on IBM Cloud. +type Service struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec ServiceSpec `json:"spec,omitempty"` + Status ServiceStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// ServiceList contains a list of Service +type ServiceList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Service `json:"items"` +} + +func init() { + SchemeBuilder.Register(&Service{}, &ServiceList{}) +} + +type ServiceContext struct { + // +optional + Org string `json:"org,omitempty"` + // +optional + Space string `json:"space,omitempty"` + // +optional + Region string `json:"region,omitempty"` + // +optional + ResourceGroup string `json:"resourcegroup,omitempty"` + // +optional + ResourceGroupID string `json:"resourcegroupid,omitempty"` + // +optional + ResourceLocation string `json:"resourcelocation,omitempty"` + // +optional + User string `json:"user,omitempty"` +} + +// ResourceContext defines the CloudFoundry context and resource group +type ResourceContext struct { + // +optional + Org string `json:"org,omitempty"` + // +optional + Space string `json:"space,omitempty"` + // +optional + Region string `json:"region,omitempty"` + // +optional + ResourceGroupID string `json:"resourcegroupid,omitempty"` + // +optional + ResourceLocation string `json:"resourcelocation,omitempty"` + // +optional + User string `json:"user,omitempty"` +} + +func (s *Service) GetState() string { + return s.Status.State +} + +func (s *Service) GetMessage() string { + return s.Status.Message +} diff --git a/api/v1/zz_generated.deepcopy.go b/api/v1/zz_generated.deepcopy.go new file mode 100644 index 00000000..3aef6f23 --- /dev/null +++ b/api/v1/zz_generated.deepcopy.go @@ -0,0 +1,345 @@ +// +build !ignore_autogenerated + +/* + * Copyright 2020 IBM Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Code generated by controller-gen. DO NOT EDIT. + +package v1 + +import ( + "encoding/json" + corev1 "k8s.io/api/core/v1" + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Binding) DeepCopyInto(out *Binding) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + out.Status = in.Status +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Binding. +func (in *Binding) DeepCopy() *Binding { + if in == nil { + return nil + } + out := new(Binding) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Binding) 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 *BindingList) DeepCopyInto(out *BindingList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Binding, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BindingList. +func (in *BindingList) DeepCopy() *BindingList { + if in == nil { + return nil + } + out := new(BindingList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *BindingList) 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 *BindingSpec) DeepCopyInto(out *BindingSpec) { + *out = *in + if in.Parameters != nil { + in, out := &in.Parameters, &out.Parameters + *out = make([]Param, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BindingSpec. +func (in *BindingSpec) DeepCopy() *BindingSpec { + if in == nil { + return nil + } + out := new(BindingSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BindingStatus) DeepCopyInto(out *BindingStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BindingStatus. +func (in *BindingStatus) DeepCopy() *BindingStatus { + if in == nil { + return nil + } + out := new(BindingStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Param) DeepCopyInto(out *Param) { + *out = *in + if in.Attributes != nil { + in, out := &in.Attributes, &out.Attributes + *out = make(map[string]ParamValue, len(*in)) + for key, val := range *in { + (*out)[key] = *val.DeepCopy() + } + } + if in.Value != nil { + in, out := &in.Value, &out.Value + *out = new(ParamValue) + (*in).DeepCopyInto(*out) + } + if in.ValueFrom != nil { + in, out := &in.ValueFrom, &out.ValueFrom + *out = new(ParamSource) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Param. +func (in *Param) DeepCopy() *Param { + if in == nil { + return nil + } + out := new(Param) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ParamSource) DeepCopyInto(out *ParamSource) { + *out = *in + if in.ConfigMapKeyRef != nil { + in, out := &in.ConfigMapKeyRef, &out.ConfigMapKeyRef + *out = new(corev1.ConfigMapKeySelector) + (*in).DeepCopyInto(*out) + } + if in.SecretKeyRef != nil { + in, out := &in.SecretKeyRef, &out.SecretKeyRef + *out = new(corev1.SecretKeySelector) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ParamSource. +func (in *ParamSource) DeepCopy() *ParamSource { + if in == nil { + return nil + } + out := new(ParamSource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ParamValue) DeepCopyInto(out *ParamValue) { + *out = *in + if in.RawMessage != nil { + in, out := &in.RawMessage, &out.RawMessage + *out = make(json.RawMessage, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ParamValue. +func (in *ParamValue) DeepCopy() *ParamValue { + if in == nil { + return nil + } + out := new(ParamValue) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ResourceContext) DeepCopyInto(out *ResourceContext) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceContext. +func (in *ResourceContext) DeepCopy() *ResourceContext { + if in == nil { + return nil + } + out := new(ResourceContext) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Service) DeepCopyInto(out *Service) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Service. +func (in *Service) DeepCopy() *Service { + if in == nil { + return nil + } + out := new(Service) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Service) 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 *ServiceContext) DeepCopyInto(out *ServiceContext) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceContext. +func (in *ServiceContext) DeepCopy() *ServiceContext { + if in == nil { + return nil + } + out := new(ServiceContext) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ServiceList) DeepCopyInto(out *ServiceList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Service, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceList. +func (in *ServiceList) DeepCopy() *ServiceList { + if in == nil { + return nil + } + out := new(ServiceList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ServiceList) 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 *ServiceSpec) DeepCopyInto(out *ServiceSpec) { + *out = *in + if in.Parameters != nil { + in, out := &in.Parameters, &out.Parameters + *out = make([]Param, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Tags != nil { + in, out := &in.Tags, &out.Tags + *out = make([]string, len(*in)) + copy(*out, *in) + } + out.Context = in.Context +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceSpec. +func (in *ServiceSpec) DeepCopy() *ServiceSpec { + if in == nil { + return nil + } + out := new(ServiceSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ServiceStatus) DeepCopyInto(out *ServiceStatus) { + *out = *in + out.Context = in.Context + if in.Parameters != nil { + in, out := &in.Parameters, &out.Parameters + *out = make([]Param, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Tags != nil { + in, out := &in.Tags, &out.Tags + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceStatus. +func (in *ServiceStatus) DeepCopy() *ServiceStatus { + if in == nil { + return nil + } + out := new(ServiceStatus) + in.DeepCopyInto(out) + return out +} diff --git a/api/v1beta1/binding_types.go b/api/v1beta1/binding_types.go index 08785577..674b81ce 100644 --- a/api/v1beta1/binding_types.go +++ b/api/v1beta1/binding_types.go @@ -65,7 +65,6 @@ type BindingStatus struct { // +kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.state" // +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" // +kubebuilder:subresource:status -// +kubebuilder:storageversion // Binding is an instance of a service binding resource on IBM Cloud. A Binding creates a secret with the service instance credentials. type Binding struct { diff --git a/api/v1beta1/service_types.go b/api/v1beta1/service_types.go index ec2041e3..7aae54aa 100644 --- a/api/v1beta1/service_types.go +++ b/api/v1beta1/service_types.go @@ -78,7 +78,6 @@ type ServiceStatus struct { // +kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.state" // +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" // +kubebuilder:subresource:status -// +kubebuilder:storageversion // Service is an instance of a Service resource on IBM Cloud. type Service struct { diff --git a/config/crd/bases/ibmcloud.ibm.com_bindings.yaml b/config/crd/bases/ibmcloud.ibm.com_bindings.yaml index d6666f72..42e4e580 100644 --- a/config/crd/bases/ibmcloud.ibm.com_bindings.yaml +++ b/config/crd/bases/ibmcloud.ibm.com_bindings.yaml @@ -22,9 +22,9 @@ spec: scope: Namespaced subresources: status: {} - version: v1beta1 + version: v1 versions: - - name: v1beta1 + - name: v1 schema: openAPIV3Schema: description: Binding is an instance of a service binding resource on IBM Cloud. @@ -151,6 +151,133 @@ spec: type: object served: true storage: true + - name: v1beta1 + schema: + openAPIV3Schema: + description: Binding is an instance of a service binding resource on IBM Cloud. + A Binding creates a secret with the service instance credentials. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: BindingSpec defines the desired state of Binding + properties: + alias: + description: Alias is the name for the credentials to be aliased + type: string + parameters: + description: Parameters pass configuration to the service during creation + items: + description: Param represents a key-value pair + properties: + attributes: + additionalProperties: + type: object + description: A parameter may have attributes (e.g. message hub + topic might have partitions) + type: object + name: + description: Name representing the key. + type: string + value: + description: Defaults to null. + valueFrom: + description: Source for the value. Cannot be used if value is + not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the ConfigMap or its key + must be defined + type: boolean + required: + - key + type: object + secretKeyRef: + description: Selects a key of a secret in the resource namespace + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the Secret or its key must + be defined + type: boolean + required: + - key + type: object + type: object + required: + - name + type: object + type: array + role: + description: Role is the role for the credentials + type: string + secretName: + description: SecretName is the name of the secret where credentials + will be stored + type: string + serviceName: + description: ServiceClass is the name of the service resource to bind + type: string + serviceNamespace: + description: ServiceNamespace is the namespace of the service resource + to bind + type: string + required: + - serviceName + type: object + status: + description: BindingStatus defines the observed state of Binding + properties: + generation: + format: int64 + type: integer + instanceId: + description: InstanceID is the instance ID for the service + type: string + keyInstanceId: + description: KeyInstanceID is the key instance ID for the credentials + type: string + message: + description: Message is a detailed message on current status + type: string + secretName: + description: SecretName is the name of the generated secret with service + credentials + type: string + state: + description: State is a short name for the current status + type: string + type: object + type: object + served: true + storage: false - name: v1alpha1 schema: openAPIV3Schema: diff --git a/config/crd/bases/ibmcloud.ibm.com_services.yaml b/config/crd/bases/ibmcloud.ibm.com_services.yaml index 4245a2c3..511b5654 100644 --- a/config/crd/bases/ibmcloud.ibm.com_services.yaml +++ b/config/crd/bases/ibmcloud.ibm.com_services.yaml @@ -22,9 +22,9 @@ spec: scope: Namespaced subresources: status: {} - version: v1beta1 + version: v1 versions: - - name: v1beta1 + - name: v1 schema: openAPIV3Schema: description: Service is an instance of a Service resource on IBM Cloud. @@ -264,6 +264,246 @@ spec: type: object served: true storage: true + - name: v1beta1 + schema: + openAPIV3Schema: + description: Service is an instance of a Service resource on IBM Cloud. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: ServiceSpec defines the desired state of Service + properties: + context: + description: ResourceContext defines the CloudFoundry context and + resource group + properties: + org: + type: string + region: + type: string + resourcegroupid: + type: string + resourcelocation: + type: string + space: + type: string + user: + type: string + type: object + externalName: + description: ExternalName is the name for the service as it appears + on IBM Cloud + type: string + parameters: + description: Parameters pass configuration to the service during creation + items: + description: Param represents a key-value pair + properties: + attributes: + additionalProperties: + type: object + description: A parameter may have attributes (e.g. message hub + topic might have partitions) + type: object + name: + description: Name representing the key. + type: string + value: + description: Defaults to null. + valueFrom: + description: Source for the value. Cannot be used if value is + not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the ConfigMap or its key + must be defined + type: boolean + required: + - key + type: object + secretKeyRef: + description: Selects a key of a secret in the resource namespace + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the Secret or its key must + be defined + type: boolean + required: + - key + type: object + type: object + required: + - name + type: object + type: array + plan: + description: Plan for the service from the IBM Cloud Catalog + type: string + serviceClass: + description: ServiceClass is the name of the service from the IBM + Cloud Catalog + type: string + serviceClassType: + description: ServiceClassType is set to CF if the service is CloundFoundry + type: string + tags: + items: + type: string + type: array + required: + - plan + - serviceClass + type: object + status: + description: ServiceStatus defines the observed state of Service + properties: + context: + description: ResourceContext defines the CloudFoundry context and + resource group + properties: + org: + type: string + region: + type: string + resourcegroupid: + type: string + resourcelocation: + type: string + space: + type: string + user: + type: string + type: object + dashboardURL: + description: DashboardURL is the dashboard URL for the service + type: string + externalName: + description: ExternalName is the name for the service as it appears + on IBM Cloud + type: string + generation: + format: int64 + type: integer + instanceId: + description: InstanceID is the instance ID for the service + type: string + message: + description: Message is a detailed message on current status + type: string + parameters: + description: Parameters pass configuration to the service during creation + items: + description: Param represents a key-value pair + properties: + attributes: + additionalProperties: + type: object + description: A parameter may have attributes (e.g. message hub + topic might have partitions) + type: object + name: + description: Name representing the key. + type: string + value: + description: Defaults to null. + valueFrom: + description: Source for the value. Cannot be used if value is + not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the ConfigMap or its key + must be defined + type: boolean + required: + - key + type: object + secretKeyRef: + description: Selects a key of a secret in the resource namespace + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the Secret or its key must + be defined + type: boolean + required: + - key + type: object + type: object + required: + - name + type: object + type: array + plan: + description: Plan for the service from the IBM Cloud Catalog + type: string + serviceClass: + description: ServiceClass is the name of the service from the IBM + Cloud Catalog + type: string + serviceClassType: + description: ServiceClassType is set to CF if the service is CloundFoundry + type: string + state: + description: State is a short name for the current status + type: string + tags: + items: + type: string + type: array + required: + - plan + - serviceClass + - serviceClassType + type: object + type: object + served: true + storage: false - name: v1alpha1 schema: openAPIV3Schema: diff --git a/config/samples/apiconnect.yaml b/config/samples/apiconnect.yaml index 9d996915..6db0d5fb 100644 --- a/config/samples/apiconnect.yaml +++ b/config/samples/apiconnect.yaml @@ -1,4 +1,4 @@ -apiVersion: ibmcloud.ibm.com/v1beta1 +apiVersion: ibmcloud.ibm.com/v1 kind: Service metadata: name: myapiconnect diff --git a/config/samples/appid.yaml b/config/samples/appid.yaml index 3e24814b..5fa0946a 100644 --- a/config/samples/appid.yaml +++ b/config/samples/appid.yaml @@ -1,4 +1,4 @@ -apiVersion: ibmcloud.ibm.com/v1beta1 +apiVersion: ibmcloud.ibm.com/v1 kind: Service metadata: name: myappid @@ -6,7 +6,7 @@ spec: plan: lite serviceClass: appid --- -apiVersion: ibmcloud.ibm.com/v1beta1 +apiVersion: ibmcloud.ibm.com/v1 kind: Binding metadata: name: binding-appid diff --git a/config/samples/cloudant-with-tags.yaml b/config/samples/cloudant-with-tags.yaml index 7c6c0a7f..fe4f9c5a 100644 --- a/config/samples/cloudant-with-tags.yaml +++ b/config/samples/cloudant-with-tags.yaml @@ -1,4 +1,4 @@ -apiVersion: ibmcloud.ibm.com/v1beta1 +apiVersion: ibmcloud.ibm.com/v1 kind: Service metadata: name: mycloudant @@ -11,7 +11,7 @@ spec: - choumi - zoumi --- -apiVersion: ibmcloud.ibm.com/v1beta1 +apiVersion: ibmcloud.ibm.com/v1 kind: Binding metadata: name: binding-cloudant diff --git a/config/samples/cloudant.yaml b/config/samples/cloudant.yaml index 7cb16c93..af9e9fbe 100644 --- a/config/samples/cloudant.yaml +++ b/config/samples/cloudant.yaml @@ -1,4 +1,4 @@ -apiVersion: ibmcloud.ibm.com/v1beta1 +apiVersion: ibmcloud.ibm.com/v1 kind: Service metadata: name: mycloudant @@ -6,16 +6,16 @@ spec: plan: lite serviceClass: cloudantnosqldb --- -apiVersion: ibmcloud.ibm.com/v1beta1 +apiVersion: ibmcloud.ibm.com/v1 kind: Binding metadata: name: binding-cloudant spec: serviceName: mycloudant --- -apiVersion: ibmcloud.ibm.com/v1beta1 +apiVersion: ibmcloud.ibm.com/v1 kind: Binding metadata: name: binding-cloudant1 spec: - serviceName: mycloudant \ No newline at end of file + serviceName: mycloudant diff --git a/config/samples/contdelivery.yaml b/config/samples/contdelivery.yaml index 096fae77..1fdd9b0e 100644 --- a/config/samples/contdelivery.yaml +++ b/config/samples/contdelivery.yaml @@ -1,4 +1,4 @@ -apiVersion: ibmcloud.ibm.com/v1beta1 +apiVersion: ibmcloud.ibm.com/v1 kind: Service metadata: name: mydelivery diff --git a/config/samples/cos.yaml b/config/samples/cos.yaml index 1ba223a8..009244f3 100644 --- a/config/samples/cos.yaml +++ b/config/samples/cos.yaml @@ -1,4 +1,4 @@ -apiVersion: ibmcloud.ibm.com/v1beta1 +apiVersion: ibmcloud.ibm.com/v1 kind: Service metadata: name: mycos @@ -8,7 +8,7 @@ spec: context: region: global --- -apiVersion: ibmcloud.ibm.com/v1beta1 +apiVersion: ibmcloud.ibm.com/v1 kind: Binding metadata: name: binding-mycos diff --git a/config/samples/db2.yaml b/config/samples/db2.yaml index 34c17a3a..7eab8a26 100644 --- a/config/samples/db2.yaml +++ b/config/samples/db2.yaml @@ -1,4 +1,4 @@ -apiVersion: ibmcloud.ibm.com/v1beta1 +apiVersion: ibmcloud.ibm.com/v1 kind: Service metadata: name: mydb2 @@ -6,7 +6,7 @@ spec: plan: free serviceClass: dashdb-for-transactions --- -apiVersion: ibmcloud.ibm.com/v1beta1 +apiVersion: ibmcloud.ibm.com/v1 kind: Binding metadata: name: binding-db2 diff --git a/config/samples/elastic.yaml b/config/samples/elastic.yaml index 2c8e6974..5d32b256 100644 --- a/config/samples/elastic.yaml +++ b/config/samples/elastic.yaml @@ -1,4 +1,4 @@ -apiVersion: ibmcloud.ibm.com/v1beta1 +apiVersion: ibmcloud.ibm.com/v1 kind: Service metadata: name: myes @@ -13,7 +13,7 @@ spec: tags: - booya --- -apiVersion: ibmcloud.ibm.com/v1beta1 +apiVersion: ibmcloud.ibm.com/v1 kind: Binding metadata: name: binding-myes diff --git a/config/samples/etcd.yaml b/config/samples/etcd.yaml index 2c1936fd..dd5feba0 100644 --- a/config/samples/etcd.yaml +++ b/config/samples/etcd.yaml @@ -1,4 +1,4 @@ -apiVersion: ibmcloud.ibm.com/v1beta1 +apiVersion: ibmcloud.ibm.com/v1 kind: Service metadata: name: myetcd @@ -6,7 +6,7 @@ spec: plan: standard serviceClass: databases-for-etcd --- -apiVersion: ibmcloud.ibm.com/v1beta1 +apiVersion: ibmcloud.ibm.com/v1 kind: Binding metadata: name: binding-etcd diff --git a/config/samples/iot.yaml b/config/samples/iot.yaml index db6cc959..65735659 100644 --- a/config/samples/iot.yaml +++ b/config/samples/iot.yaml @@ -1,4 +1,4 @@ -apiVersion: ibmcloud.ibm.com/v1beta1 +apiVersion: ibmcloud.ibm.com/v1 kind: Service metadata: name: myiot @@ -7,7 +7,7 @@ spec: serviceClass: iotf-service serviceClassType: CF --- -apiVersion: ibmcloud.ibm.com/v1beta1 +apiVersion: ibmcloud.ibm.com/v1 kind: Binding metadata: name: binding-iot diff --git a/config/samples/keyprotect.yaml b/config/samples/keyprotect.yaml index 0febdf57..a36bb617 100644 --- a/config/samples/keyprotect.yaml +++ b/config/samples/keyprotect.yaml @@ -1,4 +1,4 @@ -apiVersion: ibmcloud.ibm.com/v1beta1 +apiVersion: ibmcloud.ibm.com/v1 kind: Service metadata: name: mykeyprotect @@ -6,7 +6,7 @@ spec: plan: tiered-pricing serviceClass: kms --- -apiVersion: ibmcloud.ibm.com/v1beta1 +apiVersion: ibmcloud.ibm.com/v1 kind: Binding metadata: name: binding-keyprotect diff --git a/config/samples/knowledge.yaml b/config/samples/knowledge.yaml index dd2aa77a..dff65475 100644 --- a/config/samples/knowledge.yaml +++ b/config/samples/knowledge.yaml @@ -1,4 +1,4 @@ -apiVersion: ibmcloud.ibm.com/v1beta1 +apiVersion: ibmcloud.ibm.com/v1 kind: Service metadata: name: myknowledgestudio diff --git a/config/samples/messagehub-binding-alias.yaml b/config/samples/messagehub-binding-alias.yaml index e28051a0..b326cf35 100644 --- a/config/samples/messagehub-binding-alias.yaml +++ b/config/samples/messagehub-binding-alias.yaml @@ -1,4 +1,4 @@ -apiVersion: ibmcloud.ibm.com/v1beta1 +apiVersion: ibmcloud.ibm.com/v1 kind: Service metadata: name: mymessagehub @@ -6,14 +6,14 @@ spec: plan: standard serviceClass: messagehub --- -apiVersion: ibmcloud.ibm.com/v1beta1 +apiVersion: ibmcloud.ibm.com/v1 kind: Binding metadata: name: binding-messagehub spec: serviceName: mymessagehub --- -apiVersion: ibmcloud.ibm.com/v1beta1 +apiVersion: ibmcloud.ibm.com/v1 kind: Binding metadata: name: binding-messagehub-alias diff --git a/config/samples/messagehub-service-alias.yaml b/config/samples/messagehub-service-alias.yaml index 325ba83f..a3d05270 100644 --- a/config/samples/messagehub-service-alias.yaml +++ b/config/samples/messagehub-service-alias.yaml @@ -1,4 +1,4 @@ -apiVersion: ibmcloud.ibm.com/v1beta1 +apiVersion: ibmcloud.ibm.com/v1 kind: Service metadata: name: mymessagehub diff --git a/config/samples/messagehub.yaml b/config/samples/messagehub.yaml index 099e82e5..6e443863 100644 --- a/config/samples/messagehub.yaml +++ b/config/samples/messagehub.yaml @@ -1,4 +1,4 @@ -apiVersion: ibmcloud.ibm.com/v1beta1 +apiVersion: ibmcloud.ibm.com/v1 kind: Service metadata: name: mymessagehub @@ -6,7 +6,7 @@ spec: plan: standard serviceClass: messagehub --- -apiVersion: ibmcloud.ibm.com/v1beta1 +apiVersion: ibmcloud.ibm.com/v1 kind: Binding metadata: name: binding-messagehub diff --git a/config/samples/messagehubCF-named.yaml b/config/samples/messagehubCF-named.yaml index e2113381..56437e44 100644 --- a/config/samples/messagehubCF-named.yaml +++ b/config/samples/messagehubCF-named.yaml @@ -1,4 +1,4 @@ -apiVersion: ibmcloud.ibm.com/v1beta1 +apiVersion: ibmcloud.ibm.com/v1 kind: Service metadata: name: mymessagehubcf @@ -8,7 +8,7 @@ spec: serviceClassType: CF externalName: "MY MESSAGEHUB" --- -apiVersion: ibmcloud.ibm.com/v1beta1 +apiVersion: ibmcloud.ibm.com/v1 kind: Binding metadata: name: binding-messagehubcf diff --git a/config/samples/messagehubCF.yaml b/config/samples/messagehubCF.yaml index 162ec9e5..454ad253 100644 --- a/config/samples/messagehubCF.yaml +++ b/config/samples/messagehubCF.yaml @@ -1,4 +1,4 @@ -apiVersion: ibmcloud.ibm.com/v1beta1 +apiVersion: ibmcloud.ibm.com/v1 kind: Service metadata: name: mymessagehubcf @@ -7,7 +7,7 @@ spec: serviceClass: messagehub serviceClassType: CF --- -apiVersion: ibmcloud.ibm.com/v1beta1 +apiVersion: ibmcloud.ibm.com/v1 kind: Binding metadata: name: binding-messagehubcf diff --git a/config/samples/mq.yaml b/config/samples/mq.yaml index 2489f5e9..485eac5d 100644 --- a/config/samples/mq.yaml +++ b/config/samples/mq.yaml @@ -1,4 +1,4 @@ -apiVersion: ibmcloud.ibm.com/v1beta1 +apiVersion: ibmcloud.ibm.com/v1 kind: Service metadata: name: mymq diff --git a/config/samples/param.yaml b/config/samples/param.yaml index a7657cd4..57934b5f 100644 --- a/config/samples/param.yaml +++ b/config/samples/param.yaml @@ -1,4 +1,4 @@ -apiVersion: ibmcloud.ibm.com/v1beta1 +apiVersion: ibmcloud.ibm.com/v1 kind: Service metadata: name: myes4 diff --git a/config/samples/personality.yaml b/config/samples/personality.yaml index 1680235b..5f92d738 100644 --- a/config/samples/personality.yaml +++ b/config/samples/personality.yaml @@ -1,4 +1,4 @@ -apiVersion: ibmcloud.ibm.com/v1beta1 +apiVersion: ibmcloud.ibm.com/v1 kind: Service metadata: name: mypersonality @@ -6,7 +6,7 @@ spec: plan: lite serviceClass: personality-insights --- -apiVersion: ibmcloud.ibm.com/v1beta1 +apiVersion: ibmcloud.ibm.com/v1 kind: Binding metadata: name: binding-personality diff --git a/config/samples/postgresql.yaml b/config/samples/postgresql.yaml index b4a9681b..f1da649b 100644 --- a/config/samples/postgresql.yaml +++ b/config/samples/postgresql.yaml @@ -1,4 +1,4 @@ -apiVersion: ibmcloud.ibm.com/v1beta1 +apiVersion: ibmcloud.ibm.com/v1 kind: Service metadata: name: mypostgresql @@ -6,7 +6,7 @@ spec: plan: standard serviceClass: databases-for-postgresql --- -apiVersion: ibmcloud.ibm.com/v1beta1 +apiVersion: ibmcloud.ibm.com/v1 kind: Binding metadata: name: binding-postgresql diff --git a/config/samples/sql-query.yaml b/config/samples/sql-query.yaml index 10f45b2c..d394b2c2 100644 --- a/config/samples/sql-query.yaml +++ b/config/samples/sql-query.yaml @@ -1,4 +1,4 @@ -apiVersion: ibmcloud.ibm.com/v1beta1 +apiVersion: ibmcloud.ibm.com/v1 kind: Service metadata: name: mysqlquery diff --git a/config/samples/streams.yaml b/config/samples/streams.yaml index 98e662d3..dc3b064a 100644 --- a/config/samples/streams.yaml +++ b/config/samples/streams.yaml @@ -1,4 +1,4 @@ -apiVersion: ibmcloud.ibm.com/v1beta1 +apiVersion: ibmcloud.ibm.com/v1 kind: Service metadata: name: mystreams @@ -6,7 +6,7 @@ spec: plan: entry-container-lite serviceClass: streaming-analytics --- -apiVersion: ibmcloud.ibm.com/v1beta1 +apiVersion: ibmcloud.ibm.com/v1 kind: Binding metadata: name: binding-streams diff --git a/config/samples/sysdig.yaml b/config/samples/sysdig.yaml index 8b163176..f339dd8b 100644 --- a/config/samples/sysdig.yaml +++ b/config/samples/sysdig.yaml @@ -1,4 +1,4 @@ -apiVersion: ibmcloud.ibm.com/v1beta1 +apiVersion: ibmcloud.ibm.com/v1 kind: Service metadata: name: sysdiglite @@ -6,7 +6,7 @@ spec: plan: lite serviceClass: sysdig-monitor --- -apiVersion: ibmcloud.ibm.com/v1beta1 +apiVersion: ibmcloud.ibm.com/v1 kind: Binding metadata: name: binding-sysdiglite diff --git a/config/samples/test.yaml b/config/samples/test.yaml index 3813a77e..72b26c3c 100644 --- a/config/samples/test.yaml +++ b/config/samples/test.yaml @@ -1,4 +1,4 @@ -apiVersion: ibmcloud.ibm.com/v1beta1 +apiVersion: ibmcloud.ibm.com/v1 kind: Service metadata: name: my-cloudant @@ -6,14 +6,14 @@ spec: plan: standard serviceClass: cloudantnosqldb --- -apiVersion: ibmcloud.ibm.com/v1beta1 +apiVersion: ibmcloud.ibm.com/v1 kind: Binding metadata: name: my-binding-cloudant-1 spec: serviceName: my-cloudant --- -apiVersion: ibmcloud.ibm.com/v1beta1 +apiVersion: ibmcloud.ibm.com/v1 kind: Binding metadata: name: my-binding-cloudant-2 diff --git a/config/samples/tone.yaml b/config/samples/tone.yaml index 365b5340..de05d968 100644 --- a/config/samples/tone.yaml +++ b/config/samples/tone.yaml @@ -1,4 +1,4 @@ -apiVersion: ibmcloud.ibm.com/v1beta1 +apiVersion: ibmcloud.ibm.com/v1 kind: Service metadata: name: mytone @@ -6,7 +6,7 @@ spec: plan: lite serviceClass: tone-analyzer --- -apiVersion: ibmcloud.ibm.com/v1beta1 +apiVersion: ibmcloud.ibm.com/v1 kind: Binding metadata: name: binding-tone diff --git a/config/samples/trans.yaml b/config/samples/trans.yaml index f972af4e..08047e41 100644 --- a/config/samples/trans.yaml +++ b/config/samples/trans.yaml @@ -1,4 +1,4 @@ -apiVersion: ibmcloud.ibm.com/v1beta1 +apiVersion: ibmcloud.ibm.com/v1 kind: Service metadata: name: mytranslator @@ -8,7 +8,7 @@ spec: plan: lite serviceClass: language-translator --- -apiVersion: ibmcloud.ibm.com/v1beta1 +apiVersion: ibmcloud.ibm.com/v1 kind: Binding metadata: name: binding-translator diff --git a/config/samples/translator-alias.yaml b/config/samples/translator-alias.yaml index 09533bc4..3f5ae78e 100644 --- a/config/samples/translator-alias.yaml +++ b/config/samples/translator-alias.yaml @@ -1,4 +1,4 @@ -apiVersion: ibmcloud.ibm.com/v1beta1 +apiVersion: ibmcloud.ibm.com/v1 kind: Service metadata: name: mytranslator-alias1 diff --git a/config/samples/translator-binding-with-ns.yaml b/config/samples/translator-binding-with-ns.yaml index 1eb21051..00daee0f 100644 --- a/config/samples/translator-binding-with-ns.yaml +++ b/config/samples/translator-binding-with-ns.yaml @@ -1,4 +1,4 @@ -apiVersion: ibmcloud.ibm.com/v1beta1 +apiVersion: ibmcloud.ibm.com/v1 kind: Binding metadata: name: binding-translator diff --git a/config/samples/translator-binding.yaml b/config/samples/translator-binding.yaml index 1e5f7005..6953de39 100644 --- a/config/samples/translator-binding.yaml +++ b/config/samples/translator-binding.yaml @@ -1,4 +1,4 @@ -apiVersion: ibmcloud.ibm.com/v1beta1 +apiVersion: ibmcloud.ibm.com/v1 kind: Binding metadata: name: binding-translator diff --git a/config/samples/translator-bindonly.yaml b/config/samples/translator-bindonly.yaml index 6ad5ac63..37866053 100644 --- a/config/samples/translator-bindonly.yaml +++ b/config/samples/translator-bindonly.yaml @@ -1,4 +1,4 @@ -apiVersion: ibmcloud.ibm.com/v1beta1 +apiVersion: ibmcloud.ibm.com/v1 kind: Service metadata: name: mytranslator-alias diff --git a/config/samples/translator-named.yaml b/config/samples/translator-named.yaml index 8812296d..d8244c75 100644 --- a/config/samples/translator-named.yaml +++ b/config/samples/translator-named.yaml @@ -1,4 +1,4 @@ -apiVersion: ibmcloud.ibm.com/v1beta1 +apiVersion: ibmcloud.ibm.com/v1 kind: Service metadata: name: mytranslator-named diff --git a/config/samples/translator.yaml b/config/samples/translator.yaml index 398f495f..7f9ed497 100644 --- a/config/samples/translator.yaml +++ b/config/samples/translator.yaml @@ -1,4 +1,4 @@ -apiVersion: ibmcloud.ibm.com/v1beta1 +apiVersion: ibmcloud.ibm.com/v1 kind: Service metadata: name: mytranslator diff --git a/controllers/binding_controller.go b/controllers/binding_controller.go index 9cbe92ef..90dbe223 100644 --- a/controllers/binding_controller.go +++ b/controllers/binding_controller.go @@ -26,7 +26,7 @@ import ( "github.com/IBM-Cloud/bluemix-go/session" "github.com/go-logr/logr" - ibmcloudv1beta1 "github.com/ibm/cloud-operators/api/v1beta1" + ibmcloudv1 "github.com/ibm/cloud-operators/api/v1" "github.com/ibm/cloud-operators/internal/config" "github.com/ibm/cloud-operators/internal/ibmcloud" "github.com/ibm/cloud-operators/internal/ibmcloud/cfservice" @@ -79,11 +79,11 @@ type BindingReconciler struct { type ControllerReferenceSetter func(owner, controlled metav1.Object, scheme *runtime.Scheme) error -type IBMCloudInfoGetter func(logt logr.Logger, r client.Client, instance *ibmcloudv1beta1.Service) (*ibmcloud.Info, error) +type IBMCloudInfoGetter func(logt logr.Logger, r client.Client, instance *ibmcloudv1.Service) (*ibmcloud.Info, error) func (r *BindingReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). - For(&ibmcloudv1beta1.Binding{}). + For(&ibmcloudv1.Binding{}). Complete(r) } @@ -103,7 +103,7 @@ func (r *BindingReconciler) Reconcile(request ctrl.Request) (ctrl.Result, error) logt := r.Log.WithValues("binding", request.NamespacedName) // Fetch the Binding instance - instance := &ibmcloudv1beta1.Binding{} + instance := &ibmcloudv1.Binding{} err := r.Get(context.Background(), request.NamespacedName, instance) if err != nil { if errors.IsNotFound(err) { @@ -116,7 +116,7 @@ func (r *BindingReconciler) Reconcile(request ctrl.Request) (ctrl.Result, error) } // Set the Status field for the first time - if reflect.DeepEqual(instance.Status, ibmcloudv1beta1.BindingStatus{}) { + if reflect.DeepEqual(instance.Status, ibmcloudv1.BindingStatus{}) { instance.Status.State = bindingStatePending instance.Status.Message = "Processing Resource" if err := r.Status().Update(ctx, instance); err != nil { @@ -338,20 +338,20 @@ func (r *BindingReconciler) Reconcile(request ctrl.Request) (ctrl.Result, error) return r.updateStatusOnline(session, instance, serviceInstance, serviceClassType) } -func (r *BindingReconciler) getServiceInstance(instance *ibmcloudv1beta1.Binding) (*ibmcloudv1beta1.Service, error) { +func (r *BindingReconciler) getServiceInstance(instance *ibmcloudv1.Binding) (*ibmcloudv1.Service, error) { serviceNameSpace := instance.ObjectMeta.Namespace if instance.Spec.ServiceNamespace != "" { serviceNameSpace = instance.Spec.ServiceNamespace } - serviceInstance := &ibmcloudv1beta1.Service{} + serviceInstance := &ibmcloudv1.Service{} err := r.Get(context.Background(), types.NamespacedName{Name: instance.Spec.ServiceName, Namespace: serviceNameSpace}, serviceInstance) if err != nil { - return &ibmcloudv1beta1.Service{}, err + return &ibmcloudv1.Service{}, err } return serviceInstance, nil } -func (r *BindingReconciler) resetResource(instance *ibmcloudv1beta1.Binding) (ctrl.Result, error) { +func (r *BindingReconciler) resetResource(instance *ibmcloudv1.Binding) (ctrl.Result, error) { instance.Status.State = bindingStatePending instance.Status.Message = "Processing Resource" instance.Status.InstanceID = "" @@ -373,7 +373,7 @@ func (r *BindingReconciler) resetResource(instance *ibmcloudv1beta1.Binding) (ct return ctrl.Result{Requeue: true, RequeueAfter: config.Get().SyncPeriod}, nil } -func (r *BindingReconciler) updateStatusError(instance *ibmcloudv1beta1.Binding, state string, err error) (ctrl.Result, error) { +func (r *BindingReconciler) updateStatusError(instance *ibmcloudv1.Binding, state string, err error) (ctrl.Result, error) { message := err.Error() r.Log.Info(message) @@ -396,7 +396,7 @@ func (r *BindingReconciler) updateStatusError(instance *ibmcloudv1beta1.Binding, } // deleteCredentials also deletes the corresponding secret -func (r *BindingReconciler) deleteCredentials(session *session.Session, instance *ibmcloudv1beta1.Binding, serviceClassType string) error { +func (r *BindingReconciler) deleteCredentials(session *session.Session, instance *ibmcloudv1.Binding, serviceClassType string) error { r.Log.Info("Deleting", "credentials", instance.ObjectMeta.Name) if instance.Spec.Alias == "" { // Delete only if it not alias @@ -415,7 +415,7 @@ func (r *BindingReconciler) deleteCredentials(session *session.Session, instance return r.deleteSecret(instance) } -func (r *BindingReconciler) getAliasCredentials(logt logr.Logger, session *session.Session, instance *ibmcloudv1beta1.Binding, serviceClassType string) (string, map[string]interface{}, error) { +func (r *BindingReconciler) getAliasCredentials(logt logr.Logger, session *session.Session, instance *ibmcloudv1.Binding, serviceClassType string) (string, map[string]interface{}, error) { logt.Info("Getting", " alias credentials", instance.ObjectMeta.Name) name := instance.Spec.Alias @@ -445,7 +445,7 @@ func (r *BindingReconciler) getAliasCredentials(logt logr.Logger, session *sessi return guid, credentials, nil } -func (r *BindingReconciler) createCredentials(ctx context.Context, session *session.Session, instance *ibmcloudv1beta1.Binding, serviceClassType string) (string, map[string]interface{}, error) { +func (r *BindingReconciler) createCredentials(ctx context.Context, session *session.Session, instance *ibmcloudv1.Binding, serviceClassType string) (string, map[string]interface{}, error) { r.Log.Info("Creating", "credentials", instance.ObjectMeta.Name) parameters, err := r.getParams(ctx, instance) if err != nil { @@ -459,7 +459,7 @@ func (r *BindingReconciler) createCredentials(ctx context.Context, session *sess return r.getResourceServiceCredentials(session, instance, parameters) } -func (r *BindingReconciler) getResourceServiceCredentials(session *session.Session, instance *ibmcloudv1beta1.Binding, parameters map[string]interface{}) (string, map[string]interface{}, error) { +func (r *BindingReconciler) getResourceServiceCredentials(session *session.Session, instance *ibmcloudv1.Binding, parameters map[string]interface{}) (string, map[string]interface{}, error) { instanceCRN, serviceID, err := r.GetServiceInstanceCRN(session, instance.Status.InstanceID) if err != nil { return "", nil, err @@ -477,7 +477,7 @@ func (r *BindingReconciler) getResourceServiceCredentials(session *session.Sessi return r.CreateResourceServiceKey(session, instance.ObjectMeta.Name, instanceCRN, parameters) } -func (r *BindingReconciler) createSecret(instance *ibmcloudv1beta1.Binding, keyContents map[string]interface{}) error { +func (r *BindingReconciler) createSecret(instance *ibmcloudv1.Binding, keyContents map[string]interface{}) error { r.Log.Info("Creating ", "secret", instance.ObjectMeta.Name) datamap, err := processKey(keyContents) if err != nil { @@ -505,7 +505,7 @@ func (r *BindingReconciler) createSecret(instance *ibmcloudv1beta1.Binding, keyC return nil } -func (r *BindingReconciler) updateStatusOnline(session *session.Session, instance *ibmcloudv1beta1.Binding, serviceInstance *ibmcloudv1beta1.Service, serviceClassType string) (ctrl.Result, error) { +func (r *BindingReconciler) updateStatusOnline(session *session.Session, instance *ibmcloudv1.Binding, serviceInstance *ibmcloudv1.Service, serviceClassType string) (ctrl.Result, error) { instance.Status.State = bindingStateOnline instance.Status.Message = bindingStateOnline instance.Status.SecretName = getSecretName(instance) @@ -521,7 +521,7 @@ func (r *BindingReconciler) updateStatusOnline(session *session.Session, instanc return ctrl.Result{Requeue: true, RequeueAfter: config.Get().SyncPeriod}, nil } -func (r *BindingReconciler) getCredentials(logt logr.Logger, session *session.Session, instance *ibmcloudv1beta1.Binding, serviceClassType string) (string, map[string]interface{}, error) { +func (r *BindingReconciler) getCredentials(logt logr.Logger, session *session.Session, instance *ibmcloudv1.Binding, serviceClassType string) (string, map[string]interface{}, error) { logt.Info("Getting", "credentials", instance.ObjectMeta.Name) if serviceClassType == "CF" { // service type is CF @@ -537,7 +537,7 @@ func (r *BindingReconciler) getCredentials(logt logr.Logger, session *session.Se return "", nil, fmt.Errorf(notFound) } -func getSecretName(instance *ibmcloudv1beta1.Binding) string { +func getSecretName(instance *ibmcloudv1.Binding) string { secretName := instance.ObjectMeta.Name if instance.Spec.SecretName != "" { secretName = instance.Spec.SecretName @@ -553,7 +553,7 @@ func keyContentsChanged(keyContents map[string]interface{}, secret *corev1.Secre return !reflect.DeepEqual(newContent, secret.Data), nil } -func (r *BindingReconciler) deleteSecret(instance *ibmcloudv1beta1.Binding) error { +func (r *BindingReconciler) deleteSecret(instance *ibmcloudv1.Binding) error { r.Log.Info("Deleting ", "secret", instance.Status.SecretName) secret, err := getSecret(r, instance) if err != nil { @@ -568,12 +568,12 @@ func (r *BindingReconciler) deleteSecret(instance *ibmcloudv1beta1.Binding) erro return nil } -func (r *BindingReconciler) getCFCredentials(logt logr.Logger, session *session.Session, instance *ibmcloudv1beta1.Binding, name string) (string, map[string]interface{}, error) { +func (r *BindingReconciler) getCFCredentials(logt logr.Logger, session *session.Session, instance *ibmcloudv1.Binding, name string) (string, map[string]interface{}, error) { logt.Info("Getting", "CF credentials", name) return r.GetCFServiceKeyCredentials(session, instance.Status.InstanceID, name) } -func (r *BindingReconciler) getParams(ctx context.Context, instance *ibmcloudv1beta1.Binding) (map[string]interface{}, error) { +func (r *BindingReconciler) getParams(ctx context.Context, instance *ibmcloudv1.Binding) (map[string]interface{}, error) { params := make(map[string]interface{}) for _, p := range instance.Spec.Parameters { @@ -604,7 +604,7 @@ func processKey(keyContents map[string]interface{}) (map[string][]byte, error) { } // paramToJSON converts variable value to JSON value -func (r *BindingReconciler) paramToJSON(ctx context.Context, p ibmcloudv1beta1.Param, namespace string) (interface{}, error) { +func (r *BindingReconciler) paramToJSON(ctx context.Context, p ibmcloudv1.Param, namespace string) (interface{}, error) { if p.Value != nil && p.ValueFrom != nil { return nil, fmt.Errorf("Value and ValueFrom properties are mutually exclusive (for %s variable)", p.Name) } @@ -621,7 +621,7 @@ func (r *BindingReconciler) paramToJSON(ctx context.Context, p ibmcloudv1beta1.P } // paramValueToJSON takes a ParamSource and resolves its value -func (r *BindingReconciler) paramValueToJSON(ctx context.Context, valueFrom ibmcloudv1beta1.ParamSource, namespace string) (interface{}, error) { +func (r *BindingReconciler) paramValueToJSON(ctx context.Context, valueFrom ibmcloudv1.ParamSource, namespace string) (interface{}, error) { if valueFrom.SecretKeyRef != nil { data, err := getKubeSecretValue(ctx, r, r.Log, valueFrom.SecretKeyRef.Name, valueFrom.SecretKeyRef.Key, true, namespace) if err != nil { @@ -640,7 +640,7 @@ func (r *BindingReconciler) paramValueToJSON(ctx context.Context, valueFrom ibmc return nil, fmt.Errorf("Missing secretKeyRef or configMapKeyRef") } -func paramToJSONFromRaw(content *ibmcloudv1beta1.ParamValue) (interface{}, error) { +func paramToJSONFromRaw(content *ibmcloudv1.ParamValue) (interface{}, error) { var data interface{} if err := json.Unmarshal(content.RawMessage, &data); err != nil { @@ -670,7 +670,7 @@ func paramToJSONFromString(content string) (interface{}, error) { } // containsBindingFinalizer checks if the instance contains service finalizer -func containsBindingFinalizer(instance *ibmcloudv1beta1.Binding) bool { +func containsBindingFinalizer(instance *ibmcloudv1.Binding) bool { for _, finalizer := range instance.ObjectMeta.Finalizers { if strings.Contains(finalizer, bindingFinalizer) { return true @@ -680,7 +680,7 @@ func containsBindingFinalizer(instance *ibmcloudv1beta1.Binding) bool { } // deleteBindingFinalizer delete service finalizer -func deleteBindingFinalizer(instance *ibmcloudv1beta1.Binding) []string { +func deleteBindingFinalizer(instance *ibmcloudv1.Binding) []string { var result []string for _, finalizer := range instance.ObjectMeta.Finalizers { if finalizer == bindingFinalizer { diff --git a/controllers/binding_controller_test.go b/controllers/binding_controller_test.go index a3495eab..b31d7727 100644 --- a/controllers/binding_controller_test.go +++ b/controllers/binding_controller_test.go @@ -12,7 +12,7 @@ import ( "github.com/IBM-Cloud/bluemix-go/session" "github.com/ghodss/yaml" "github.com/go-logr/logr" - ibmcloudv1beta1 "github.com/ibm/cloud-operators/api/v1beta1" + ibmcloudv1 "github.com/ibm/cloud-operators/api/v1" "github.com/ibm/cloud-operators/internal/config" "github.com/ibm/cloud-operators/internal/ibmcloud" "github.com/stretchr/testify/assert" @@ -51,9 +51,9 @@ func TestBinding(t *testing.T) { bindingfile = "testdata/translator-binding.yaml" ) - var service ibmcloudv1beta1.Service + var service ibmcloudv1.Service mustLoadObject(t, servicefile, &service, &service.ObjectMeta) - var binding ibmcloudv1beta1.Binding + var binding ibmcloudv1.Binding mustLoadObject(t, bindingfile, &binding, &binding.ObjectMeta) ready := t.Run("should be ready", func(t *testing.T) { @@ -63,14 +63,14 @@ func TestBinding(t *testing.T) { require.NoError(t, err) // make sure service is online - require.Eventually(t, verifyStatus(ctx, t, service.ObjectMeta, new(ibmcloudv1beta1.Service), serviceStateOnline), defaultWait, defaultTick) + require.Eventually(t, verifyStatus(ctx, t, service.ObjectMeta, new(ibmcloudv1.Service), serviceStateOnline), defaultWait, defaultTick) // now test creation of binding err = k8sClient.Create(ctx, &binding) require.NoError(t, err) // check binding is online - require.Eventually(t, verifyStatus(ctx, t, binding.ObjectMeta, new(ibmcloudv1beta1.Binding), bindingStateOnline), defaultWait, defaultTick) + require.Eventually(t, verifyStatus(ctx, t, binding.ObjectMeta, new(ibmcloudv1.Binding), bindingStateOnline), defaultWait, defaultTick) // check secret is created err = getObject(ctx, binding.ObjectMeta, &corev1.Secret{}) @@ -96,7 +96,7 @@ func TestBinding(t *testing.T) { require.NoError(t, k8sClient.Delete(ctx, &service)) assert.Eventually(t, func() bool { - err := getObject(ctx, service.ObjectMeta, &ibmcloudv1beta1.Service{}) + err := getObject(ctx, service.ObjectMeta, &ibmcloudv1.Service{}) return errors.IsNotFound(err) }, defaultWait, defaultTick) }) @@ -134,9 +134,9 @@ func TestBindingFailInitialStatus(t *testing.T) { t.Parallel() scheme := schemas(t) objects := []runtime.Object{ - &ibmcloudv1beta1.Binding{ + &ibmcloudv1.Binding{ ObjectMeta: metav1.ObjectMeta{Name: "mybinding"}, - Status: ibmcloudv1beta1.BindingStatus{}, // empty + Status: ibmcloudv1.BindingStatus{}, // empty }, } client := fake.NewFakeClientWithScheme(scheme, objects...) @@ -161,18 +161,18 @@ func TestBindingFailGetServiceInstance(t *testing.T) { now := metav1Now(t) for _, tc := range []struct { description string - binding *ibmcloudv1beta1.Binding + binding *ibmcloudv1.Binding fakeClient *MockConfig - expectUpdate *ibmcloudv1beta1.Binding - expectStatusUpdate *ibmcloudv1beta1.Binding + expectUpdate *ibmcloudv1.Binding + expectStatusUpdate *ibmcloudv1.Binding expectResult ctrl.Result }{ { description: "no service instance", - binding: &ibmcloudv1beta1.Binding{ - TypeMeta: metav1.TypeMeta{Kind: "Binding", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + binding: &ibmcloudv1.Binding{ + TypeMeta: metav1.TypeMeta{Kind: "Binding", APIVersion: "ibmcloud.ibm.com/v1"}, ObjectMeta: metav1.ObjectMeta{Name: "mybinding"}, - Spec: ibmcloudv1beta1.BindingSpec{ + Spec: ibmcloudv1.BindingSpec{ ServiceName: "myservice", }, }, @@ -183,87 +183,87 @@ func TestBindingFailGetServiceInstance(t *testing.T) { }, { description: "binding is deleting", - binding: &ibmcloudv1beta1.Binding{ - TypeMeta: metav1.TypeMeta{Kind: "Binding", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + binding: &ibmcloudv1.Binding{ + TypeMeta: metav1.TypeMeta{Kind: "Binding", APIVersion: "ibmcloud.ibm.com/v1"}, ObjectMeta: metav1.ObjectMeta{ Name: "mybinding", DeletionTimestamp: now, Finalizers: []string{bindingFinalizer}, }, - Spec: ibmcloudv1beta1.BindingSpec{ + Spec: ibmcloudv1.BindingSpec{ ServiceName: "myservice", }, - Status: ibmcloudv1beta1.BindingStatus{State: bindingStateOnline}, + Status: ibmcloudv1.BindingStatus{State: bindingStateOnline}, }, fakeClient: &MockConfig{}, - expectUpdate: &ibmcloudv1beta1.Binding{ - TypeMeta: metav1.TypeMeta{Kind: "Binding", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + expectUpdate: &ibmcloudv1.Binding{ + TypeMeta: metav1.TypeMeta{Kind: "Binding", APIVersion: "ibmcloud.ibm.com/v1"}, ObjectMeta: metav1.ObjectMeta{ Name: "mybinding", DeletionTimestamp: now, }, - Spec: ibmcloudv1beta1.BindingSpec{ + Spec: ibmcloudv1.BindingSpec{ ServiceName: "myservice", }, - Status: ibmcloudv1beta1.BindingStatus{State: bindingStateOnline}, + Status: ibmcloudv1.BindingStatus{State: bindingStateOnline}, }, expectResult: ctrl.Result{}, }, { description: "binding is deleting but update fails", - binding: &ibmcloudv1beta1.Binding{ - TypeMeta: metav1.TypeMeta{Kind: "Binding", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + binding: &ibmcloudv1.Binding{ + TypeMeta: metav1.TypeMeta{Kind: "Binding", APIVersion: "ibmcloud.ibm.com/v1"}, ObjectMeta: metav1.ObjectMeta{ Name: "mybinding", DeletionTimestamp: now, Finalizers: []string{bindingFinalizer}, }, - Spec: ibmcloudv1beta1.BindingSpec{ + Spec: ibmcloudv1.BindingSpec{ ServiceName: "myservice", }, - Status: ibmcloudv1beta1.BindingStatus{State: bindingStateOnline}, + Status: ibmcloudv1.BindingStatus{State: bindingStateOnline}, }, fakeClient: &MockConfig{UpdateErr: fmt.Errorf("failed")}, - expectUpdate: &ibmcloudv1beta1.Binding{ - TypeMeta: metav1.TypeMeta{Kind: "Binding", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + expectUpdate: &ibmcloudv1.Binding{ + TypeMeta: metav1.TypeMeta{Kind: "Binding", APIVersion: "ibmcloud.ibm.com/v1"}, ObjectMeta: metav1.ObjectMeta{ Name: "mybinding", DeletionTimestamp: now, }, - Spec: ibmcloudv1beta1.BindingSpec{ + Spec: ibmcloudv1.BindingSpec{ ServiceName: "myservice", }, - Status: ibmcloudv1beta1.BindingStatus{State: bindingStateOnline}, + Status: ibmcloudv1.BindingStatus{State: bindingStateOnline}, }, expectResult: ctrl.Result{}, }, { description: "binding is deleting and status service instance is set", - binding: &ibmcloudv1beta1.Binding{ - TypeMeta: metav1.TypeMeta{Kind: "Binding", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + binding: &ibmcloudv1.Binding{ + TypeMeta: metav1.TypeMeta{Kind: "Binding", APIVersion: "ibmcloud.ibm.com/v1"}, ObjectMeta: metav1.ObjectMeta{ Name: "mybinding", Finalizers: []string{bindingFinalizer}, }, - Spec: ibmcloudv1beta1.BindingSpec{ + Spec: ibmcloudv1.BindingSpec{ ServiceName: "myservice", }, - Status: ibmcloudv1beta1.BindingStatus{ + Status: ibmcloudv1.BindingStatus{ State: bindingStateOnline, KeyInstanceID: "myinstance", }, }, fakeClient: &MockConfig{}, - expectStatusUpdate: &ibmcloudv1beta1.Binding{ - TypeMeta: metav1.TypeMeta{Kind: "Binding", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + expectStatusUpdate: &ibmcloudv1.Binding{ + TypeMeta: metav1.TypeMeta{Kind: "Binding", APIVersion: "ibmcloud.ibm.com/v1"}, ObjectMeta: metav1.ObjectMeta{ Name: "mybinding", Finalizers: []string{bindingFinalizer}, }, - Spec: ibmcloudv1beta1.BindingSpec{ + Spec: ibmcloudv1.BindingSpec{ ServiceName: "myservice", }, - Status: ibmcloudv1beta1.BindingStatus{ + Status: ibmcloudv1.BindingStatus{ State: bindingStatePending, Message: "Processing Resource", }, @@ -306,15 +306,15 @@ func TestBindingSetOwnerReferenceFailed(t *testing.T) { scheme := schemas(t) const namespace = "mynamespace" objects := []runtime.Object{ - &ibmcloudv1beta1.Binding{ - TypeMeta: metav1.TypeMeta{Kind: "Binding", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + &ibmcloudv1.Binding{ + TypeMeta: metav1.TypeMeta{Kind: "Binding", APIVersion: "ibmcloud.ibm.com/v1"}, ObjectMeta: metav1.ObjectMeta{Name: "mybinding", Namespace: namespace}, - Spec: ibmcloudv1beta1.BindingSpec{ + Spec: ibmcloudv1.BindingSpec{ ServiceName: "myservice", }, }, - &ibmcloudv1beta1.Service{ - TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + &ibmcloudv1.Service{ + TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1"}, ObjectMeta: metav1.ObjectMeta{Name: "myservice", Namespace: namespace}, }, } @@ -339,18 +339,18 @@ func TestBindingSetOwnerReferenceFailed(t *testing.T) { scheme := schemas(t) const namespace = "mynamespace" objects := []runtime.Object{ - &ibmcloudv1beta1.Binding{ - TypeMeta: metav1.TypeMeta{Kind: "Binding", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + &ibmcloudv1.Binding{ + TypeMeta: metav1.TypeMeta{Kind: "Binding", APIVersion: "ibmcloud.ibm.com/v1"}, ObjectMeta: metav1.ObjectMeta{Name: "mybinding", Namespace: namespace}, - Spec: ibmcloudv1beta1.BindingSpec{ + Spec: ibmcloudv1.BindingSpec{ ServiceName: "myservice", }, - Status: ibmcloudv1beta1.BindingStatus{ + Status: ibmcloudv1.BindingStatus{ State: bindingStateOnline, }, }, - &ibmcloudv1beta1.Service{ - TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + &ibmcloudv1.Service{ + TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1"}, ObjectMeta: metav1.ObjectMeta{Name: "myservice", Namespace: namespace}, }, } @@ -374,13 +374,13 @@ func TestBindingSetOwnerReferenceFailed(t *testing.T) { }) assert.Equal(t, ctrl.Result{}, result) assert.NoError(t, err) - assert.Equal(t, &ibmcloudv1beta1.Binding{ - TypeMeta: metav1.TypeMeta{Kind: "Binding", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + assert.Equal(t, &ibmcloudv1.Binding{ + TypeMeta: metav1.TypeMeta{Kind: "Binding", APIVersion: "ibmcloud.ibm.com/v1"}, ObjectMeta: metav1.ObjectMeta{Name: "mybinding", Namespace: namespace}, - Spec: ibmcloudv1beta1.BindingSpec{ + Spec: ibmcloudv1.BindingSpec{ ServiceName: "myservice", }, - Status: ibmcloudv1beta1.BindingStatus{ + Status: ibmcloudv1.BindingStatus{ State: bindingStateOnline, }, }, client.LastUpdate()) @@ -393,17 +393,17 @@ func TestBindingServiceIsNotReady(t *testing.T) { scheme := schemas(t) const namespace = "mynamespace" objects := []runtime.Object{ - &ibmcloudv1beta1.Binding{ - TypeMeta: metav1.TypeMeta{Kind: "Binding", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + &ibmcloudv1.Binding{ + TypeMeta: metav1.TypeMeta{Kind: "Binding", APIVersion: "ibmcloud.ibm.com/v1"}, ObjectMeta: metav1.ObjectMeta{Name: "mybinding", Namespace: namespace}, - Spec: ibmcloudv1beta1.BindingSpec{ + Spec: ibmcloudv1.BindingSpec{ ServiceName: "myservice", }, }, - &ibmcloudv1beta1.Service{ - TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + &ibmcloudv1.Service{ + TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1"}, ObjectMeta: metav1.ObjectMeta{Name: "myservice", Namespace: namespace}, - Status: ibmcloudv1beta1.ServiceStatus{ + Status: ibmcloudv1.ServiceStatus{ InstanceID: "", }, }, @@ -432,17 +432,17 @@ func TestBindingServiceIsNotReady(t *testing.T) { scheme := schemas(t) const namespace = "mynamespace" objects := []runtime.Object{ - &ibmcloudv1beta1.Binding{ - TypeMeta: metav1.TypeMeta{Kind: "Binding", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + &ibmcloudv1.Binding{ + TypeMeta: metav1.TypeMeta{Kind: "Binding", APIVersion: "ibmcloud.ibm.com/v1"}, ObjectMeta: metav1.ObjectMeta{Name: "mybinding", Namespace: namespace}, - Spec: ibmcloudv1beta1.BindingSpec{ + Spec: ibmcloudv1.BindingSpec{ ServiceName: "myservice", }, }, - &ibmcloudv1beta1.Service{ - TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + &ibmcloudv1.Service{ + TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1"}, ObjectMeta: metav1.ObjectMeta{Name: "myservice", Namespace: namespace}, - Status: ibmcloudv1beta1.ServiceStatus{ + Status: ibmcloudv1.ServiceStatus{ InstanceID: inProgress, }, }, @@ -479,21 +479,21 @@ func TestBindingGetIBMCloudInfoFailed(t *testing.T) { someInstanceID = "some-instance-id" ) objects := []runtime.Object{ - &ibmcloudv1beta1.Binding{ - TypeMeta: metav1.TypeMeta{Kind: "Binding", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + &ibmcloudv1.Binding{ + TypeMeta: metav1.TypeMeta{Kind: "Binding", APIVersion: "ibmcloud.ibm.com/v1"}, ObjectMeta: metav1.ObjectMeta{ Name: bindingName, Namespace: namespace, DeletionTimestamp: now, Finalizers: []string{bindingFinalizer}, }, - Spec: ibmcloudv1beta1.BindingSpec{ServiceName: serviceName}, - Status: ibmcloudv1beta1.BindingStatus{State: bindingStateFailed}, + Spec: ibmcloudv1.BindingSpec{ServiceName: serviceName}, + Status: ibmcloudv1.BindingStatus{State: bindingStateFailed}, }, - &ibmcloudv1beta1.Service{ - TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + &ibmcloudv1.Service{ + TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1"}, ObjectMeta: metav1.ObjectMeta{Name: serviceName, Namespace: namespace}, - Status: ibmcloudv1beta1.ServiceStatus{ + Status: ibmcloudv1.ServiceStatus{ InstanceID: someInstanceID, }, }, @@ -509,7 +509,7 @@ func TestBindingGetIBMCloudInfoFailed(t *testing.T) { SetControllerReference: func(owner, controlled metav1.Object, scheme *runtime.Scheme) error { return nil }, - GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1beta1.Service) (*ibmcloud.Info, error) { + GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1.Service) (*ibmcloud.Info, error) { r.Client = newMockClient( // swap out client so next update fails fake.NewFakeClientWithScheme(scheme, objects...), MockConfig{UpdateErr: fmt.Errorf("failed")}, @@ -523,16 +523,16 @@ func TestBindingGetIBMCloudInfoFailed(t *testing.T) { }) assert.Equal(t, ctrl.Result{}, result) assert.NoError(t, err) - assert.Equal(t, &ibmcloudv1beta1.Binding{ - TypeMeta: metav1.TypeMeta{Kind: "Binding", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + assert.Equal(t, &ibmcloudv1.Binding{ + TypeMeta: metav1.TypeMeta{Kind: "Binding", APIVersion: "ibmcloud.ibm.com/v1"}, ObjectMeta: metav1.ObjectMeta{ Name: bindingName, Namespace: namespace, DeletionTimestamp: now, Finalizers: nil, // attempt to remove finalizers }, - Spec: ibmcloudv1beta1.BindingSpec{ServiceName: serviceName}, - Status: ibmcloudv1beta1.BindingStatus{State: bindingStateFailed}, + Spec: ibmcloudv1.BindingSpec{ServiceName: serviceName}, + Status: ibmcloudv1.BindingStatus{State: bindingStateFailed}, }, r.Client.(MockClient).LastUpdate()) assert.Equal(t, nil, r.Client.(MockClient).LastStatusUpdate()) }) @@ -546,7 +546,7 @@ func TestBindingGetIBMCloudInfoFailed(t *testing.T) { SetControllerReference: func(owner, controlled metav1.Object, scheme *runtime.Scheme) error { return nil }, - GetIBMCloudInfo: func(logt logr.Logger, r client.Client, instance *ibmcloudv1beta1.Service) (*ibmcloud.Info, error) { + GetIBMCloudInfo: func(logt logr.Logger, r client.Client, instance *ibmcloudv1.Service) (*ibmcloud.Info, error) { return nil, fmt.Errorf("failed") }, } @@ -559,16 +559,16 @@ func TestBindingGetIBMCloudInfoFailed(t *testing.T) { RequeueAfter: config.Get().SyncPeriod, }, result) assert.NoError(t, err) - assert.Equal(t, &ibmcloudv1beta1.Binding{ - TypeMeta: metav1.TypeMeta{Kind: "Binding", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + assert.Equal(t, &ibmcloudv1.Binding{ + TypeMeta: metav1.TypeMeta{Kind: "Binding", APIVersion: "ibmcloud.ibm.com/v1"}, ObjectMeta: metav1.ObjectMeta{ Name: bindingName, Namespace: namespace, DeletionTimestamp: now, Finalizers: []string{bindingFinalizer}, }, - Spec: ibmcloudv1beta1.BindingSpec{ServiceName: serviceName}, - Status: ibmcloudv1beta1.BindingStatus{ + Spec: ibmcloudv1.BindingSpec{ServiceName: serviceName}, + Status: ibmcloudv1.BindingStatus{ State: bindingStatePending, Message: "failed", }, @@ -590,24 +590,24 @@ func TestBindingDeletesWithFinalizerFailed(t *testing.T) { someInstanceID = "some-instance-id" ) objects := []runtime.Object{ - &ibmcloudv1beta1.Binding{ - TypeMeta: metav1.TypeMeta{Kind: "Binding", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + &ibmcloudv1.Binding{ + TypeMeta: metav1.TypeMeta{Kind: "Binding", APIVersion: "ibmcloud.ibm.com/v1"}, ObjectMeta: metav1.ObjectMeta{ Name: bindingName, Namespace: namespace, DeletionTimestamp: now, Finalizers: []string{bindingFinalizer}, }, - Spec: ibmcloudv1beta1.BindingSpec{ + Spec: ibmcloudv1.BindingSpec{ ServiceName: serviceName, Alias: "some-binding-alias", // use alias plan to mock fewer dependencies during delete creds SecretName: secretName, }, }, - &ibmcloudv1beta1.Service{ - TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + &ibmcloudv1.Service{ + TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1"}, ObjectMeta: metav1.ObjectMeta{Name: serviceName, Namespace: namespace}, - Status: ibmcloudv1beta1.ServiceStatus{ + Status: ibmcloudv1.ServiceStatus{ InstanceID: someInstanceID, }, }, @@ -624,7 +624,7 @@ func TestBindingDeletesWithFinalizerFailed(t *testing.T) { Log: testLogger(t), Scheme: scheme, - GetIBMCloudInfo: func(logt logr.Logger, r client.Client, instance *ibmcloudv1beta1.Service) (*ibmcloud.Info, error) { + GetIBMCloudInfo: func(logt logr.Logger, r client.Client, instance *ibmcloudv1.Service) (*ibmcloud.Info, error) { return &ibmcloud.Info{}, nil }, SetControllerReference: func(owner, controlled metav1.Object, scheme *runtime.Scheme) error { @@ -656,25 +656,25 @@ func TestBindingDeletesWithFinalizerFailed(t *testing.T) { someInstanceID = "some-instance-id" ) objects := []runtime.Object{ - &ibmcloudv1beta1.Binding{ - TypeMeta: metav1.TypeMeta{Kind: "Binding", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + &ibmcloudv1.Binding{ + TypeMeta: metav1.TypeMeta{Kind: "Binding", APIVersion: "ibmcloud.ibm.com/v1"}, ObjectMeta: metav1.ObjectMeta{ Name: bindingName, Namespace: namespace, DeletionTimestamp: now, Finalizers: []string{bindingFinalizer}, }, - Spec: ibmcloudv1beta1.BindingSpec{ + Spec: ibmcloudv1.BindingSpec{ ServiceName: serviceName, Alias: "some-binding-alias", // use alias plan to mock fewer dependencies during delete creds SecretName: secretName, }, - Status: ibmcloudv1beta1.BindingStatus{State: bindingStatePending}, + Status: ibmcloudv1.BindingStatus{State: bindingStatePending}, }, - &ibmcloudv1beta1.Service{ - TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + &ibmcloudv1.Service{ + TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1"}, ObjectMeta: metav1.ObjectMeta{Name: serviceName, Namespace: namespace}, - Status: ibmcloudv1beta1.ServiceStatus{ + Status: ibmcloudv1.ServiceStatus{ InstanceID: someInstanceID, }, }, @@ -688,7 +688,7 @@ func TestBindingDeletesWithFinalizerFailed(t *testing.T) { Log: testLogger(t), Scheme: scheme, - GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1beta1.Service) (*ibmcloud.Info, error) { + GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1.Service) (*ibmcloud.Info, error) { r.Client = newMockClient( // swap out client so next update fails fake.NewFakeClientWithScheme(scheme, objects...), MockConfig{UpdateErr: fmt.Errorf("failed")}, @@ -705,8 +705,8 @@ func TestBindingDeletesWithFinalizerFailed(t *testing.T) { }) assert.Equal(t, ctrl.Result{}, result) assert.NoError(t, err) - assert.Equal(t, &ibmcloudv1beta1.Binding{ - TypeMeta: metav1.TypeMeta{Kind: "Binding", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + assert.Equal(t, &ibmcloudv1.Binding{ + TypeMeta: metav1.TypeMeta{Kind: "Binding", APIVersion: "ibmcloud.ibm.com/v1"}, ObjectMeta: metav1.ObjectMeta{ Name: bindingName, Namespace: namespace, @@ -714,12 +714,12 @@ func TestBindingDeletesWithFinalizerFailed(t *testing.T) { Finalizers: nil, // attempt to remove finalizers ResourceVersion: "1", }, - Spec: ibmcloudv1beta1.BindingSpec{ + Spec: ibmcloudv1.BindingSpec{ ServiceName: serviceName, Alias: "some-binding-alias", SecretName: secretName, }, - Status: ibmcloudv1beta1.BindingStatus{State: bindingStatePending}, + Status: ibmcloudv1.BindingStatus{State: bindingStatePending}, }, r.Client.(MockClient).LastUpdate()) }) } @@ -735,21 +735,21 @@ func TestBindingDeletesMissingFinalizerFailed(t *testing.T) { someInstanceID = "some-instance-id" ) objects := []runtime.Object{ - &ibmcloudv1beta1.Binding{ - TypeMeta: metav1.TypeMeta{Kind: "Binding", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + &ibmcloudv1.Binding{ + TypeMeta: metav1.TypeMeta{Kind: "Binding", APIVersion: "ibmcloud.ibm.com/v1"}, ObjectMeta: metav1.ObjectMeta{ Name: bindingName, Namespace: namespace, DeletionTimestamp: nil, // not deleting Finalizers: nil, // AND missing finalizer }, - Spec: ibmcloudv1beta1.BindingSpec{ServiceName: serviceName}, - Status: ibmcloudv1beta1.BindingStatus{State: bindingStatePending}, + Spec: ibmcloudv1.BindingSpec{ServiceName: serviceName}, + Status: ibmcloudv1.BindingStatus{State: bindingStatePending}, }, - &ibmcloudv1beta1.Service{ - TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + &ibmcloudv1.Service{ + TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1"}, ObjectMeta: metav1.ObjectMeta{Name: serviceName, Namespace: namespace}, - Status: ibmcloudv1beta1.ServiceStatus{ + Status: ibmcloudv1.ServiceStatus{ InstanceID: someInstanceID, }, }, @@ -760,7 +760,7 @@ func TestBindingDeletesMissingFinalizerFailed(t *testing.T) { Log: testLogger(t), Scheme: scheme, - GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1beta1.Service) (*ibmcloud.Info, error) { + GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1.Service) (*ibmcloud.Info, error) { r.Client = newMockClient( // swap out client so next update fails fake.NewFakeClientWithScheme(scheme, objects...), MockConfig{UpdateErr: fmt.Errorf("failed")}, @@ -777,16 +777,16 @@ func TestBindingDeletesMissingFinalizerFailed(t *testing.T) { }) assert.Equal(t, ctrl.Result{}, result) assert.NoError(t, err) - assert.Equal(t, &ibmcloudv1beta1.Binding{ - TypeMeta: metav1.TypeMeta{Kind: "Binding", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + assert.Equal(t, &ibmcloudv1.Binding{ + TypeMeta: metav1.TypeMeta{Kind: "Binding", APIVersion: "ibmcloud.ibm.com/v1"}, ObjectMeta: metav1.ObjectMeta{ Name: bindingName, Namespace: namespace, Finalizers: []string{bindingFinalizer}, // added a finalizer ResourceVersion: "1", }, - Spec: ibmcloudv1beta1.BindingSpec{ServiceName: serviceName}, - Status: ibmcloudv1beta1.BindingStatus{State: bindingStatePending}, + Spec: ibmcloudv1.BindingSpec{ServiceName: serviceName}, + Status: ibmcloudv1.BindingStatus{State: bindingStatePending}, }, r.Client.(MockClient).LastUpdate()) } @@ -801,28 +801,28 @@ func TestBindingDeleteMismatchedServiceIDsSecretFailed(t *testing.T) { someInstanceID = "some-instance-id" ) objects := []runtime.Object{ - &ibmcloudv1beta1.Binding{ - TypeMeta: metav1.TypeMeta{Kind: "Binding", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + &ibmcloudv1.Binding{ + TypeMeta: metav1.TypeMeta{Kind: "Binding", APIVersion: "ibmcloud.ibm.com/v1"}, ObjectMeta: metav1.ObjectMeta{ Name: bindingName, Namespace: namespace, Finalizers: []string{bindingFinalizer}, }, - Spec: ibmcloudv1beta1.BindingSpec{ + Spec: ibmcloudv1.BindingSpec{ ServiceName: serviceName, Alias: "some-binding-alias", // use alias plan to mock fewer dependencies during delete creds SecretName: secretName, }, - Status: ibmcloudv1beta1.BindingStatus{ + Status: ibmcloudv1.BindingStatus{ State: bindingStatePending, InstanceID: "a-deleted-instance-id", SecretName: secretName, }, }, - &ibmcloudv1beta1.Service{ - TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + &ibmcloudv1.Service{ + TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1"}, ObjectMeta: metav1.ObjectMeta{Name: serviceName, Namespace: namespace}, - Status: ibmcloudv1beta1.ServiceStatus{ + Status: ibmcloudv1.ServiceStatus{ InstanceID: someInstanceID, }, }, @@ -836,7 +836,7 @@ func TestBindingDeleteMismatchedServiceIDsSecretFailed(t *testing.T) { Log: testLogger(t), Scheme: scheme, - GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1beta1.Service) (*ibmcloud.Info, error) { + GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1.Service) (*ibmcloud.Info, error) { r.Client = newMockClient( // swap out client so next delete fails fake.NewFakeClientWithScheme(scheme, objects...), MockConfig{DeleteErr: fmt.Errorf("failed")}, @@ -856,20 +856,20 @@ func TestBindingDeleteMismatchedServiceIDsSecretFailed(t *testing.T) { RequeueAfter: config.Get().SyncPeriod, }, result) assert.NoError(t, err) - assert.Equal(t, &ibmcloudv1beta1.Binding{ - TypeMeta: metav1.TypeMeta{Kind: "Binding", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + assert.Equal(t, &ibmcloudv1.Binding{ + TypeMeta: metav1.TypeMeta{Kind: "Binding", APIVersion: "ibmcloud.ibm.com/v1"}, ObjectMeta: metav1.ObjectMeta{ Name: bindingName, Namespace: namespace, Finalizers: []string{bindingFinalizer}, ResourceVersion: "1", }, - Spec: ibmcloudv1beta1.BindingSpec{ + Spec: ibmcloudv1.BindingSpec{ ServiceName: serviceName, Alias: "some-binding-alias", SecretName: secretName, }, - Status: ibmcloudv1beta1.BindingStatus{ + Status: ibmcloudv1.BindingStatus{ State: bindingStateFailed, // should move to failed state Message: "failed", InstanceID: "a-deleted-instance-id", @@ -895,44 +895,44 @@ func TestBindingSetKeyInstanceFailed(t *testing.T) { someInstanceID = "some-instance-id" ) objects := []runtime.Object{ - &ibmcloudv1beta1.Binding{ - TypeMeta: metav1.TypeMeta{Kind: "Binding", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + &ibmcloudv1.Binding{ + TypeMeta: metav1.TypeMeta{Kind: "Binding", APIVersion: "ibmcloud.ibm.com/v1"}, ObjectMeta: metav1.ObjectMeta{ Name: bindingName, Namespace: namespace, Finalizers: []string{bindingFinalizer}, }, - Spec: ibmcloudv1beta1.BindingSpec{ + Spec: ibmcloudv1.BindingSpec{ ServiceName: serviceName, SecretName: secretName, }, - Status: ibmcloudv1beta1.BindingStatus{ + Status: ibmcloudv1.BindingStatus{ State: bindingStatePending, InstanceID: someInstanceID, SecretName: secretName, }, }, - &ibmcloudv1beta1.Binding{ - TypeMeta: metav1.TypeMeta{Kind: "Binding", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + &ibmcloudv1.Binding{ + TypeMeta: metav1.TypeMeta{Kind: "Binding", APIVersion: "ibmcloud.ibm.com/v1"}, ObjectMeta: metav1.ObjectMeta{ Name: aliasTargetName, Namespace: namespace, Finalizers: []string{bindingFinalizer}, }, - Spec: ibmcloudv1beta1.BindingSpec{ + Spec: ibmcloudv1.BindingSpec{ ServiceName: serviceName, SecretName: secretName, }, - Status: ibmcloudv1beta1.BindingStatus{ + Status: ibmcloudv1.BindingStatus{ State: bindingStatePending, InstanceID: someInstanceID, SecretName: secretName, }, }, - &ibmcloudv1beta1.Service{ - TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + &ibmcloudv1.Service{ + TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1"}, ObjectMeta: metav1.ObjectMeta{Name: serviceName, Namespace: namespace}, - Status: ibmcloudv1beta1.ServiceStatus{ + Status: ibmcloudv1.ServiceStatus{ InstanceID: someInstanceID, }, }, @@ -1024,7 +1024,7 @@ func TestBindingSetKeyInstanceFailed(t *testing.T) { t.Run(tc.description, func(t *testing.T) { var testObjects []runtime.Object for _, obj := range objects { - if binding, ok := obj.(*ibmcloudv1beta1.Binding); ok && binding.Name != aliasTargetName { + if binding, ok := obj.(*ibmcloudv1.Binding); ok && binding.Name != aliasTargetName { binding = binding.DeepCopy() if tc.instanceIDKey { binding.Annotations = map[string]string{idkey: someInstanceID} @@ -1045,7 +1045,7 @@ func TestBindingSetKeyInstanceFailed(t *testing.T) { Log: testLogger(t), Scheme: scheme, - GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1beta1.Service) (*ibmcloud.Info, error) { + GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1.Service) (*ibmcloud.Info, error) { return &ibmcloud.Info{}, nil }, SetControllerReference: func(owner, controlled metav1.Object, scheme *runtime.Scheme) error { @@ -1075,8 +1075,8 @@ func TestBindingSetKeyInstanceFailed(t *testing.T) { assert.NoError(t, err) update := r.Client.(MockClient).LastStatusUpdate() - require.IsType(t, &ibmcloudv1beta1.Binding{}, update) - status := update.(*ibmcloudv1beta1.Binding).Status + require.IsType(t, &ibmcloudv1.Binding{}, update) + status := update.(*ibmcloudv1.Binding).Status assert.Equal(t, tc.expectState, status.State) assert.Equal(t, tc.expectMessage, status.Message) }) @@ -1095,28 +1095,28 @@ func TestBindingEnsureCredentialsFailed(t *testing.T) { someKeyInstanceID = "some-key-instance-id" ) objects := []runtime.Object{ - &ibmcloudv1beta1.Binding{ - TypeMeta: metav1.TypeMeta{Kind: "Binding", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + &ibmcloudv1.Binding{ + TypeMeta: metav1.TypeMeta{Kind: "Binding", APIVersion: "ibmcloud.ibm.com/v1"}, ObjectMeta: metav1.ObjectMeta{ Name: bindingName, Namespace: namespace, Finalizers: []string{bindingFinalizer}, }, - Spec: ibmcloudv1beta1.BindingSpec{ + Spec: ibmcloudv1.BindingSpec{ ServiceName: serviceName, SecretName: secretName, }, - Status: ibmcloudv1beta1.BindingStatus{ + Status: ibmcloudv1.BindingStatus{ State: bindingStatePending, InstanceID: someInstanceID, SecretName: secretName, KeyInstanceID: someKeyInstanceID, }, }, - &ibmcloudv1beta1.Service{ - TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + &ibmcloudv1.Service{ + TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1"}, ObjectMeta: metav1.ObjectMeta{Name: serviceName, Namespace: namespace}, - Status: ibmcloudv1beta1.ServiceStatus{ + Status: ibmcloudv1.ServiceStatus{ InstanceID: someInstanceID, }, }, @@ -1133,7 +1133,7 @@ func TestBindingEnsureCredentialsFailed(t *testing.T) { Log: testLogger(t), Scheme: scheme, - GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1beta1.Service) (*ibmcloud.Info, error) { + GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1.Service) (*ibmcloud.Info, error) { return &ibmcloud.Info{}, nil }, SetControllerReference: func(owner, controlled metav1.Object, scheme *runtime.Scheme) error { @@ -1165,8 +1165,8 @@ func TestBindingEnsureCredentialsFailed(t *testing.T) { }, result) assert.NoError(t, err) update := r.Client.(MockClient).LastStatusUpdate() - require.IsType(t, &ibmcloudv1beta1.Binding{}, update) - status := update.(*ibmcloudv1beta1.Binding).Status + require.IsType(t, &ibmcloudv1.Binding{}, update) + status := update.(*ibmcloudv1.Binding).Status assert.Equal(t, bindingStateFailed, status.State) assert.Equal(t, "failed", status.Message) } @@ -1183,48 +1183,48 @@ func TestBindingEnsureAliasCredentialsFailed(t *testing.T) { someKeyInstanceID = "some-key-instance-id" ) objects := []runtime.Object{ - &ibmcloudv1beta1.Binding{ - TypeMeta: metav1.TypeMeta{Kind: "Binding", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + &ibmcloudv1.Binding{ + TypeMeta: metav1.TypeMeta{Kind: "Binding", APIVersion: "ibmcloud.ibm.com/v1"}, ObjectMeta: metav1.ObjectMeta{ Name: bindingName, Namespace: namespace, Finalizers: []string{bindingFinalizer}, Annotations: map[string]string{idkey: someInstanceID}, }, - Spec: ibmcloudv1beta1.BindingSpec{ + Spec: ibmcloudv1.BindingSpec{ ServiceName: serviceName, SecretName: secretName, Alias: aliasTargetName, }, - Status: ibmcloudv1beta1.BindingStatus{ + Status: ibmcloudv1.BindingStatus{ State: bindingStatePending, InstanceID: someInstanceID, SecretName: secretName, KeyInstanceID: someKeyInstanceID, }, }, - &ibmcloudv1beta1.Binding{ - TypeMeta: metav1.TypeMeta{Kind: "Binding", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + &ibmcloudv1.Binding{ + TypeMeta: metav1.TypeMeta{Kind: "Binding", APIVersion: "ibmcloud.ibm.com/v1"}, ObjectMeta: metav1.ObjectMeta{ Name: aliasTargetName, Namespace: namespace, Finalizers: []string{bindingFinalizer}, }, - Spec: ibmcloudv1beta1.BindingSpec{ + Spec: ibmcloudv1.BindingSpec{ ServiceName: serviceName, SecretName: secretName, }, - Status: ibmcloudv1beta1.BindingStatus{ + Status: ibmcloudv1.BindingStatus{ State: bindingStatePending, InstanceID: someInstanceID, SecretName: secretName, KeyInstanceID: someKeyInstanceID, }, }, - &ibmcloudv1beta1.Service{ - TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + &ibmcloudv1.Service{ + TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1"}, ObjectMeta: metav1.ObjectMeta{Name: serviceName, Namespace: namespace}, - Status: ibmcloudv1beta1.ServiceStatus{ + Status: ibmcloudv1.ServiceStatus{ InstanceID: someInstanceID, }, }, @@ -1243,7 +1243,7 @@ func TestBindingEnsureAliasCredentialsFailed(t *testing.T) { Log: testLogger(t), Scheme: scheme, - GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1beta1.Service) (*ibmcloud.Info, error) { + GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1.Service) (*ibmcloud.Info, error) { return &ibmcloud.Info{}, nil }, SetControllerReference: func(owner, controlled metav1.Object, scheme *runtime.Scheme) error { @@ -1263,8 +1263,8 @@ func TestBindingEnsureAliasCredentialsFailed(t *testing.T) { }, result) assert.NoError(t, err) update := r.Client.(MockClient).LastStatusUpdate() - require.IsType(t, &ibmcloudv1beta1.Binding{}, update) - status := update.(*ibmcloudv1beta1.Binding).Status + require.IsType(t, &ibmcloudv1.Binding{}, update) + status := update.(*ibmcloudv1.Binding).Status assert.Equal(t, bindingStatePending, status.State) assert.Equal(t, "Processing Resource", status.Message) }) @@ -1279,7 +1279,7 @@ func TestBindingEnsureAliasCredentialsFailed(t *testing.T) { Log: testLogger(t), Scheme: scheme, - GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1beta1.Service) (*ibmcloud.Info, error) { + GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1.Service) (*ibmcloud.Info, error) { return &ibmcloud.Info{}, nil }, SetControllerReference: func(owner, controlled metav1.Object, scheme *runtime.Scheme) error { @@ -1311,8 +1311,8 @@ func TestBindingEnsureAliasCredentialsFailed(t *testing.T) { }, result) assert.NoError(t, err) update := r.Client.(MockClient).LastStatusUpdate() - require.IsType(t, &ibmcloudv1beta1.Binding{}, update) - status := update.(*ibmcloudv1beta1.Binding).Status + require.IsType(t, &ibmcloudv1.Binding{}, update) + status := update.(*ibmcloudv1.Binding).Status assert.Equal(t, bindingStateFailed, status.State) assert.Equal(t, "failed", status.Message) }) @@ -1330,28 +1330,28 @@ func TestBindingEnsureSecretFailed(t *testing.T) { someKeyInstanceID = "some-key-instance-id" ) objects := []runtime.Object{ - &ibmcloudv1beta1.Binding{ - TypeMeta: metav1.TypeMeta{Kind: "Binding", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + &ibmcloudv1.Binding{ + TypeMeta: metav1.TypeMeta{Kind: "Binding", APIVersion: "ibmcloud.ibm.com/v1"}, ObjectMeta: metav1.ObjectMeta{ Name: bindingName, Namespace: namespace, Finalizers: []string{bindingFinalizer}, }, - Spec: ibmcloudv1beta1.BindingSpec{ + Spec: ibmcloudv1.BindingSpec{ ServiceName: serviceName, SecretName: secretName, }, - Status: ibmcloudv1beta1.BindingStatus{ + Status: ibmcloudv1.BindingStatus{ State: bindingStatePending, InstanceID: someInstanceID, SecretName: secretName, KeyInstanceID: someKeyInstanceID, }, }, - &ibmcloudv1beta1.Service{ - TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + &ibmcloudv1.Service{ + TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1"}, ObjectMeta: metav1.ObjectMeta{Name: serviceName, Namespace: namespace}, - Status: ibmcloudv1beta1.ServiceStatus{ + Status: ibmcloudv1.ServiceStatus{ InstanceID: someInstanceID, }, }, @@ -1366,7 +1366,7 @@ func TestBindingEnsureSecretFailed(t *testing.T) { Log: testLogger(t), Scheme: scheme, - GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1beta1.Service) (*ibmcloud.Info, error) { + GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1.Service) (*ibmcloud.Info, error) { return &ibmcloud.Info{}, nil }, SetControllerReference: func(owner, controlled metav1.Object, scheme *runtime.Scheme) error { @@ -1399,8 +1399,8 @@ func TestBindingEnsureSecretFailed(t *testing.T) { }, r.Client.(MockClient).LastCreate()) update := r.Client.(MockClient).LastStatusUpdate() - require.IsType(t, &ibmcloudv1beta1.Binding{}, update) - status := update.(*ibmcloudv1beta1.Binding).Status + require.IsType(t, &ibmcloudv1.Binding{}, update) + status := update.(*ibmcloudv1.Binding).Status assert.Equal(t, bindingStateOnline, status.State) assert.Equal(t, bindingStateOnline, status.Message) }) @@ -1414,7 +1414,7 @@ func TestBindingEnsureSecretFailed(t *testing.T) { Log: testLogger(t), Scheme: scheme, - GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1beta1.Service) (*ibmcloud.Info, error) { + GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1.Service) (*ibmcloud.Info, error) { return &ibmcloud.Info{}, nil }, SetControllerReference: func(owner, controlled metav1.Object, scheme *runtime.Scheme) error { @@ -1447,8 +1447,8 @@ func TestBindingEnsureSecretFailed(t *testing.T) { }, r.Client.(MockClient).LastCreate()) update := r.Client.(MockClient).LastStatusUpdate() - require.IsType(t, &ibmcloudv1beta1.Binding{}, update) - status := update.(*ibmcloudv1beta1.Binding).Status + require.IsType(t, &ibmcloudv1.Binding{}, update) + status := update.(*ibmcloudv1.Binding).Status assert.Equal(t, bindingStateFailed, status.State) assert.Equal(t, "failed", status.Message) }) @@ -1466,28 +1466,28 @@ func TestBindingEnsureKeyContentsFailed(t *testing.T) { someKeyInstanceID = "some-key-instance-id" ) objects := []runtime.Object{ - &ibmcloudv1beta1.Binding{ - TypeMeta: metav1.TypeMeta{Kind: "Binding", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + &ibmcloudv1.Binding{ + TypeMeta: metav1.TypeMeta{Kind: "Binding", APIVersion: "ibmcloud.ibm.com/v1"}, ObjectMeta: metav1.ObjectMeta{ Name: bindingName, Namespace: namespace, Finalizers: []string{bindingFinalizer}, }, - Spec: ibmcloudv1beta1.BindingSpec{ + Spec: ibmcloudv1.BindingSpec{ ServiceName: serviceName, SecretName: secretName, }, - Status: ibmcloudv1beta1.BindingStatus{ + Status: ibmcloudv1.BindingStatus{ State: bindingStatePending, InstanceID: someInstanceID, SecretName: secretName, KeyInstanceID: someKeyInstanceID, }, }, - &ibmcloudv1beta1.Service{ - TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + &ibmcloudv1.Service{ + TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1"}, ObjectMeta: metav1.ObjectMeta{Name: serviceName, Namespace: namespace}, - Status: ibmcloudv1beta1.ServiceStatus{ + Status: ibmcloudv1.ServiceStatus{ InstanceID: someInstanceID, }, }, @@ -1520,7 +1520,7 @@ func TestBindingEnsureKeyContentsFailed(t *testing.T) { Log: testLogger(t), Scheme: scheme, - GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1beta1.Service) (*ibmcloud.Info, error) { + GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1.Service) (*ibmcloud.Info, error) { return &ibmcloud.Info{}, nil }, SetControllerReference: func(owner, controlled metav1.Object, scheme *runtime.Scheme) error { @@ -1556,8 +1556,8 @@ func TestBindingEnsureKeyContentsFailed(t *testing.T) { }, r.Client.(MockClient).LastCreate()) update := r.Client.(MockClient).LastStatusUpdate() - require.IsType(t, &ibmcloudv1beta1.Binding{}, update) - status := update.(*ibmcloudv1beta1.Binding).Status + require.IsType(t, &ibmcloudv1.Binding{}, update) + status := update.(*ibmcloudv1.Binding).Status assert.Equal(t, bindingStateOnline, status.State) assert.Equal(t, bindingStateOnline, status.Message) }) @@ -1589,7 +1589,7 @@ func TestBindingEnsureKeyContentsFailed(t *testing.T) { Log: testLogger(t), Scheme: scheme, - GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1beta1.Service) (*ibmcloud.Info, error) { + GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1.Service) (*ibmcloud.Info, error) { return &ibmcloud.Info{}, nil }, SetControllerReference: func(owner, controlled metav1.Object, scheme *runtime.Scheme) error { @@ -1612,8 +1612,8 @@ func TestBindingEnsureKeyContentsFailed(t *testing.T) { assert.Nil(t, r.Client.(MockClient).LastCreate()) update := r.Client.(MockClient).LastStatusUpdate() - require.IsType(t, &ibmcloudv1beta1.Binding{}, update) - status := update.(*ibmcloudv1beta1.Binding).Status + require.IsType(t, &ibmcloudv1.Binding{}, update) + status := update.(*ibmcloudv1.Binding).Status assert.Equal(t, bindingStateOnline, status.State) assert.Equal(t, bindingStateOnline, status.Message) }) @@ -1636,7 +1636,7 @@ func TestBindingEnsureKeyContentsFailed(t *testing.T) { Log: testLogger(t), Scheme: scheme, - GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1beta1.Service) (*ibmcloud.Info, error) { + GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1.Service) (*ibmcloud.Info, error) { return &ibmcloud.Info{}, nil }, SetControllerReference: func(owner, controlled metav1.Object, scheme *runtime.Scheme) error { @@ -1662,8 +1662,8 @@ func TestBindingEnsureKeyContentsFailed(t *testing.T) { }, r.Client.(MockClient).LastDelete()) update := r.Client.(MockClient).LastStatusUpdate() - require.IsType(t, &ibmcloudv1beta1.Binding{}, update) - status := update.(*ibmcloudv1beta1.Binding).Status + require.IsType(t, &ibmcloudv1.Binding{}, update) + status := update.(*ibmcloudv1.Binding).Status assert.Equal(t, bindingStateFailed, status.State) assert.Equal(t, "failed", status.Message) }) @@ -1686,7 +1686,7 @@ func TestBindingEnsureKeyContentsFailed(t *testing.T) { Log: testLogger(t), Scheme: scheme, - GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1beta1.Service) (*ibmcloud.Info, error) { + GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1.Service) (*ibmcloud.Info, error) { return &ibmcloud.Info{}, nil }, SetControllerReference: func(owner, controlled metav1.Object, scheme *runtime.Scheme) error { @@ -1722,8 +1722,8 @@ func TestBindingEnsureKeyContentsFailed(t *testing.T) { }, r.Client.(MockClient).LastCreate()) update := r.Client.(MockClient).LastStatusUpdate() - require.IsType(t, &ibmcloudv1beta1.Binding{}, update) - status := update.(*ibmcloudv1beta1.Binding).Status + require.IsType(t, &ibmcloudv1.Binding{}, update) + status := update.(*ibmcloudv1.Binding).Status assert.Equal(t, bindingStateFailed, status.State) assert.Equal(t, "failed", status.Message) }) @@ -1736,9 +1736,9 @@ func TestBindingResetResource(t *testing.T) { secretName = "mysecret" namespace = "mynamespace" ) - binding := &ibmcloudv1beta1.Binding{ + binding := &ibmcloudv1.Binding{ ObjectMeta: metav1.ObjectMeta{Name: "mybinding", Namespace: namespace}, - Spec: ibmcloudv1beta1.BindingSpec{ + Spec: ibmcloudv1.BindingSpec{ SecretName: secretName, }, } @@ -1864,8 +1864,8 @@ func TestBindingUpdateStatusError(t *testing.T) { } { t.Run(tc.description, func(t *testing.T) { scheme := schemas(t) - binding := &ibmcloudv1beta1.Binding{ - Status: ibmcloudv1beta1.BindingStatus{ + binding := &ibmcloudv1.Binding{ + Status: ibmcloudv1.BindingStatus{ State: tc.initialState, Message: tc.initialMessage, }, @@ -1885,8 +1885,8 @@ func TestBindingUpdateStatusError(t *testing.T) { assert.NoError(t, err) var expectBinding runtime.Object if tc.expectState != "" { - expectBinding = &ibmcloudv1beta1.Binding{ - Status: ibmcloudv1beta1.BindingStatus{ + expectBinding = &ibmcloudv1.Binding{ + Status: ibmcloudv1.BindingStatus{ State: tc.expectState, Message: tc.expectMessage, }, @@ -1901,40 +1901,40 @@ func TestBindingParamToJSON(t *testing.T) { t.Parallel() for _, tc := range []struct { description string - param ibmcloudv1beta1.Param + param ibmcloudv1.Param expectJSON map[string]interface{} expectErr string }{ { description: "error: value and valueFrom both set", - param: ibmcloudv1beta1.Param{ + param: ibmcloudv1.Param{ Name: "myvalue", - Value: &ibmcloudv1beta1.ParamValue{}, - ValueFrom: &ibmcloudv1beta1.ParamSource{}, + Value: &ibmcloudv1.ParamValue{}, + ValueFrom: &ibmcloudv1.ParamSource{}, }, expectErr: "Value and ValueFrom properties are mutually exclusive (for myvalue variable)", }, { description: "empty valueFrom error", - param: ibmcloudv1beta1.Param{ + param: ibmcloudv1.Param{ Name: "myvalue", - ValueFrom: &ibmcloudv1beta1.ParamSource{}, + ValueFrom: &ibmcloudv1.ParamSource{}, }, expectErr: "Missing secretKeyRef or configMapKeyRef", }, { description: "empty value error", - param: ibmcloudv1beta1.Param{ + param: ibmcloudv1.Param{ Name: "myvalue", - Value: &ibmcloudv1beta1.ParamValue{}, + Value: &ibmcloudv1.ParamValue{}, }, expectErr: "unexpected end of JSON input", }, { description: "value happy path", - param: ibmcloudv1beta1.Param{ + param: ibmcloudv1.Param{ Name: "myvalue", - Value: &ibmcloudv1beta1.ParamValue{ + Value: &ibmcloudv1.ParamValue{ RawMessage: json.RawMessage(`{"hello": true, "world": {"!": 1}}`), }, }, @@ -1947,7 +1947,7 @@ func TestBindingParamToJSON(t *testing.T) { }, { description: "neither value nor valueFrom set", - param: ibmcloudv1beta1.Param{Name: "myvalue"}, + param: ibmcloudv1.Param{Name: "myvalue"}, expectJSON: nil, expectErr: "", }, @@ -1998,7 +1998,7 @@ func TestBindingParamValueToJSON(t *testing.T) { for _, tc := range []struct { description string - valueFrom ibmcloudv1beta1.ParamSource + valueFrom ibmcloudv1.ParamSource expectJSON interface{} expectErr string }{ @@ -2008,7 +2008,7 @@ func TestBindingParamValueToJSON(t *testing.T) { }, { description: "secret ref success", - valueFrom: ibmcloudv1beta1.ParamSource{ + valueFrom: ibmcloudv1.ParamSource{ SecretKeyRef: &corev1.SecretKeySelector{ LocalObjectReference: corev1.LocalObjectReference{ Name: secretName, @@ -2020,7 +2020,7 @@ func TestBindingParamValueToJSON(t *testing.T) { }, { description: "secret ref name failure", - valueFrom: ibmcloudv1beta1.ParamSource{ + valueFrom: ibmcloudv1.ParamSource{ SecretKeyRef: &corev1.SecretKeySelector{ LocalObjectReference: corev1.LocalObjectReference{ Name: "wrong-secret-name", @@ -2032,7 +2032,7 @@ func TestBindingParamValueToJSON(t *testing.T) { }, { description: "secret ref key failure", - valueFrom: ibmcloudv1beta1.ParamSource{ + valueFrom: ibmcloudv1.ParamSource{ SecretKeyRef: &corev1.SecretKeySelector{ LocalObjectReference: corev1.LocalObjectReference{ Name: secretName, @@ -2044,7 +2044,7 @@ func TestBindingParamValueToJSON(t *testing.T) { }, { description: "configmap ref success", - valueFrom: ibmcloudv1beta1.ParamSource{ + valueFrom: ibmcloudv1.ParamSource{ ConfigMapKeyRef: &corev1.ConfigMapKeySelector{ LocalObjectReference: corev1.LocalObjectReference{ Name: configMapName, @@ -2056,7 +2056,7 @@ func TestBindingParamValueToJSON(t *testing.T) { }, { description: "configmap ref name failure", - valueFrom: ibmcloudv1beta1.ParamSource{ + valueFrom: ibmcloudv1.ParamSource{ ConfigMapKeyRef: &corev1.ConfigMapKeySelector{ LocalObjectReference: corev1.LocalObjectReference{ Name: "wrong-configmap-name", @@ -2068,7 +2068,7 @@ func TestBindingParamValueToJSON(t *testing.T) { }, { description: "configmap ref key failure", - valueFrom: ibmcloudv1beta1.ParamSource{ + valueFrom: ibmcloudv1.ParamSource{ ConfigMapKeyRef: &corev1.ConfigMapKeySelector{ LocalObjectReference: corev1.LocalObjectReference{ Name: configMapName, @@ -2136,28 +2136,28 @@ func TestDeleteBindingFinalizer(t *testing.T) { t.Parallel() t.Run("no finalizer found", func(t *testing.T) { finalizers := []string(nil) - assert.Equal(t, finalizers, deleteBindingFinalizer(&ibmcloudv1beta1.Binding{ + assert.Equal(t, finalizers, deleteBindingFinalizer(&ibmcloudv1.Binding{ ObjectMeta: metav1.ObjectMeta{Finalizers: finalizers}, })) }) t.Run("one other finalizer found", func(t *testing.T) { finalizers := []string{"not-binding-finalizer"} - assert.Equal(t, finalizers, deleteBindingFinalizer(&ibmcloudv1beta1.Binding{ + assert.Equal(t, finalizers, deleteBindingFinalizer(&ibmcloudv1.Binding{ ObjectMeta: metav1.ObjectMeta{Finalizers: finalizers}, })) }) t.Run("one finalizer found", func(t *testing.T) { finalizers := []string{bindingFinalizer} - assert.Equal(t, []string(nil), deleteBindingFinalizer(&ibmcloudv1beta1.Binding{ + assert.Equal(t, []string(nil), deleteBindingFinalizer(&ibmcloudv1.Binding{ ObjectMeta: metav1.ObjectMeta{Finalizers: finalizers}, })) }) t.Run("multiple finalizers found", func(t *testing.T) { finalizers := []string{bindingFinalizer, bindingFinalizer} - assert.Equal(t, []string(nil), deleteBindingFinalizer(&ibmcloudv1beta1.Binding{ + assert.Equal(t, []string(nil), deleteBindingFinalizer(&ibmcloudv1.Binding{ ObjectMeta: metav1.ObjectMeta{Finalizers: finalizers}, })) }) @@ -2166,9 +2166,9 @@ func TestDeleteBindingFinalizer(t *testing.T) { func TestBindingDeleteCredentials(t *testing.T) { t.Parallel() scheme := schemas(t) - binding := &ibmcloudv1beta1.Binding{ + binding := &ibmcloudv1.Binding{ ObjectMeta: metav1.ObjectMeta{Name: "myservice", Namespace: "mynamespace"}, - Spec: ibmcloudv1beta1.BindingSpec{ + Spec: ibmcloudv1.BindingSpec{ Alias: "", // not an alias, so should delete IBM Cloud resources }, } @@ -2178,9 +2178,9 @@ func TestBindingDeleteCredentials(t *testing.T) { } objects := []runtime.Object{ binding, - &ibmcloudv1beta1.Service{ + &ibmcloudv1.Service{ ObjectMeta: metav1.ObjectMeta{Name: "myservice", Namespace: "mynamespace"}, - Spec: ibmcloudv1beta1.ServiceSpec{}, + Spec: ibmcloudv1.ServiceSpec{}, }, secret, } @@ -2254,13 +2254,13 @@ func TestBindingDeleteCredentials(t *testing.T) { func TestBindingUpdateStatusOnlineFailed(t *testing.T) { t.Parallel() scheme := schemas(t) - binding := &ibmcloudv1beta1.Binding{ + binding := &ibmcloudv1.Binding{ ObjectMeta: metav1.ObjectMeta{Name: "myservice", Namespace: "mynamespace"}, - Spec: ibmcloudv1beta1.BindingSpec{}, + Spec: ibmcloudv1.BindingSpec{}, } - service := &ibmcloudv1beta1.Service{ + service := &ibmcloudv1.Service{ ObjectMeta: metav1.ObjectMeta{Name: "myservice", Namespace: "mynamespace"}, - Spec: ibmcloudv1beta1.ServiceSpec{}, + Spec: ibmcloudv1.ServiceSpec{}, } client := newMockClient( @@ -2283,13 +2283,13 @@ func TestBindingUpdateStatusOnlineFailed(t *testing.T) { RequeueAfter: config.Get().SyncPeriod, }, result) assert.NoError(t, err) - assert.Equal(t, &ibmcloudv1beta1.Binding{ + assert.Equal(t, &ibmcloudv1.Binding{ ObjectMeta: metav1.ObjectMeta{Name: "myservice", Namespace: "mynamespace"}, - Status: ibmcloudv1beta1.BindingStatus{ + Status: ibmcloudv1.BindingStatus{ State: bindingStateOnline, Message: bindingStateOnline, SecretName: "myservice", }, - Spec: ibmcloudv1beta1.BindingSpec{}, + Spec: ibmcloudv1.BindingSpec{}, }, client.LastStatusUpdate()) } diff --git a/controllers/secret.go b/controllers/secret.go index c7957777..1d2cb0fe 100644 --- a/controllers/secret.go +++ b/controllers/secret.go @@ -5,14 +5,14 @@ import ( "fmt" "github.com/go-logr/logr" - ibmcloudv1beta1 "github.com/ibm/cloud-operators/api/v1beta1" + ibmcloudv1 "github.com/ibm/cloud-operators/api/v1" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" ) // getSecret takes a name and namespace for a Binding and returns the corresponding secret -func getSecret(r client.Client, binding *ibmcloudv1beta1.Binding) (*v1.Secret, error) { +func getSecret(r client.Client, binding *ibmcloudv1.Binding) (*v1.Secret, error) { secretName := binding.Name if binding.Spec.SecretName != "" { secretName = binding.Spec.SecretName diff --git a/controllers/service_controller.go b/controllers/service_controller.go index 0a878915..a1d231ea 100644 --- a/controllers/service_controller.go +++ b/controllers/service_controller.go @@ -31,7 +31,7 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" - ibmcloudv1beta1 "github.com/ibm/cloud-operators/api/v1beta1" + ibmcloudv1 "github.com/ibm/cloud-operators/api/v1" "github.com/ibm/cloud-operators/internal/config" "github.com/ibm/cloud-operators/internal/ibmcloud/cfservice" "github.com/ibm/cloud-operators/internal/ibmcloud/resource" @@ -71,7 +71,7 @@ type ServiceReconciler struct { func (r *ServiceReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). - For(&ibmcloudv1beta1.Service{}). + For(&ibmcloudv1.Service{}). Complete(r) } @@ -86,7 +86,7 @@ func (r *ServiceReconciler) Reconcile(request ctrl.Request) (ctrl.Result, error) logt := r.Log.WithValues("service", request.NamespacedName) // Fetch the Service instance - instance := &ibmcloudv1beta1.Service{} + instance := &ibmcloudv1.Service{} err := r.Get(ctx, request.NamespacedName, instance) if err != nil { if k8sErrors.IsNotFound(err) { @@ -112,7 +112,7 @@ func (r *ServiceReconciler) Reconcile(request ctrl.Request) (ctrl.Result, error) } var ( - resourceContext ibmcloudv1beta1.ResourceContext + resourceContext ibmcloudv1.ResourceContext session *session.Session resourceGroupID, serviceClassType, @@ -156,7 +156,7 @@ func (r *ServiceReconciler) Reconcile(request ctrl.Request) (ctrl.Result, error) } // Set the Status field for the first time - if reflect.DeepEqual(instance.Status, ibmcloudv1beta1.ServiceStatus{}) { + if reflect.DeepEqual(instance.Status, ibmcloudv1.ServiceStatus{}) { instance.Status.State = serviceStatePending instance.Status.Message = "Processing Resource" //setStatusFieldsFromSpec(instance, ibmCloudInfo) @@ -362,8 +362,8 @@ func (r *ServiceReconciler) Reconcile(request ctrl.Request) (ctrl.Result, error) return r.updateStatus(session, logt, instance, resourceContext, instance.Status.InstanceID, state, serviceClassType) } -func specChanged(instance *ibmcloudv1beta1.Service) bool { - if reflect.DeepEqual(instance.Status, ibmcloudv1beta1.ServiceStatus{}) { // Object does not have a status field yet +func specChanged(instance *ibmcloudv1.Service) bool { + if reflect.DeepEqual(instance.Status, ibmcloudv1.ServiceStatus{}) { // Object does not have a status field yet return false } // If the Plan has not been set, then there is no need to test is spec has changed, Object has not been fully initialized yet @@ -391,7 +391,7 @@ func specChanged(instance *ibmcloudv1beta1.Service) bool { } // containsServiceFinalizer checks if the instance contains service finalizer -func containsServiceFinalizer(instance *ibmcloudv1beta1.Service) bool { +func containsServiceFinalizer(instance *ibmcloudv1.Service) bool { for _, finalizer := range instance.ObjectMeta.Finalizers { if strings.Contains(finalizer, serviceFinalizer) { return true @@ -401,7 +401,7 @@ func containsServiceFinalizer(instance *ibmcloudv1beta1.Service) bool { } // deleteServiceFinalizer delete service finalizer -func deleteServiceFinalizer(instance *ibmcloudv1beta1.Service) []string { +func deleteServiceFinalizer(instance *ibmcloudv1.Service) []string { var result []string for _, finalizer := range instance.ObjectMeta.Finalizers { if finalizer == serviceFinalizer { @@ -412,7 +412,7 @@ func deleteServiceFinalizer(instance *ibmcloudv1beta1.Service) []string { return result } -func (r *ServiceReconciler) updateStatusError(instance *ibmcloudv1beta1.Service, state string, err error) (ctrl.Result, error) { +func (r *ServiceReconciler) updateStatusError(instance *ibmcloudv1.Service, state string, err error) (ctrl.Result, error) { logt := r.Log.WithValues("namespacedname", instance.Namespace+"/"+instance.Name) message := err.Error() logt.Error(err, "Updating status with error") @@ -433,7 +433,7 @@ func (r *ServiceReconciler) updateStatusError(instance *ibmcloudv1beta1.Service, return ctrl.Result{Requeue: true, RequeueAfter: config.Get().SyncPeriod}, nil } -func (r *ServiceReconciler) deleteService(session *session.Session, logt logr.Logger, instance *ibmcloudv1beta1.Service, serviceClassType string) error { +func (r *ServiceReconciler) deleteService(session *session.Session, logt logr.Logger, instance *ibmcloudv1.Service, serviceClassType string) error { if isAlias(instance) { logt.Info("Aliased service will not be deleted", "Name", instance.Name) return nil @@ -458,14 +458,14 @@ func (r *ServiceReconciler) deleteService(session *session.Session, logt logr.Lo return nil } -func getExternalName(instance *ibmcloudv1beta1.Service) string { +func getExternalName(instance *ibmcloudv1.Service) string { if instance.Spec.ExternalName != "" { return instance.Spec.ExternalName } return instance.Name } -func (r *ServiceReconciler) getParams(ctx context.Context, instance *ibmcloudv1beta1.Service) (map[string]interface{}, error) { +func (r *ServiceReconciler) getParams(ctx context.Context, instance *ibmcloudv1.Service) (map[string]interface{}, error) { params := make(map[string]interface{}) for _, p := range instance.Spec.Parameters { @@ -479,7 +479,7 @@ func (r *ServiceReconciler) getParams(ctx context.Context, instance *ibmcloudv1b } // paramToJSON converts variable value to JSON value -func (r *ServiceReconciler) paramToJSON(ctx context.Context, p ibmcloudv1beta1.Param, namespace string) (interface{}, error) { +func (r *ServiceReconciler) paramToJSON(ctx context.Context, p ibmcloudv1.Param, namespace string) (interface{}, error) { if p.Value != nil && p.ValueFrom != nil { return nil, fmt.Errorf("Value and ValueFrom properties are mutually exclusive (for %s variable)", p.Name) } @@ -496,7 +496,7 @@ func (r *ServiceReconciler) paramToJSON(ctx context.Context, p ibmcloudv1beta1.P } // paramValueToJSON takes a ParamSource and resolves its value -func (r *ServiceReconciler) paramValueToJSON(ctx context.Context, valueFrom ibmcloudv1beta1.ParamSource, namespace string) (interface{}, error) { +func (r *ServiceReconciler) paramValueToJSON(ctx context.Context, valueFrom ibmcloudv1.ParamSource, namespace string) (interface{}, error) { if valueFrom.SecretKeyRef != nil { data, err := getKubeSecretValue(ctx, r, r.Log, valueFrom.SecretKeyRef.Name, valueFrom.SecretKeyRef.Key, true, namespace) if err != nil { @@ -515,15 +515,15 @@ func (r *ServiceReconciler) paramValueToJSON(ctx context.Context, valueFrom ibmc return nil, fmt.Errorf("Missing secretKeyRef or configMapKeyRef") } -func getTags(instance *ibmcloudv1beta1.Service) []string { +func getTags(instance *ibmcloudv1.Service) []string { return instance.Spec.Tags } -func isAlias(instance *ibmcloudv1beta1.Service) bool { +func isAlias(instance *ibmcloudv1.Service) bool { return strings.ToLower(instance.Spec.Plan) == aliasPlan } -func (r *ServiceReconciler) updateStatus(session *session.Session, logt logr.Logger, instance *ibmcloudv1beta1.Service, resourceContext ibmcloudv1beta1.ResourceContext, instanceID, instanceState, serviceClassType string) (ctrl.Result, error) { +func (r *ServiceReconciler) updateStatus(session *session.Session, logt logr.Logger, instance *ibmcloudv1.Service, resourceContext ibmcloudv1.ResourceContext, instanceID, instanceState, serviceClassType string) (ctrl.Result, error) { r.Log.Info("the instance state", "is:", instanceState) state := getState(instanceState) if instance.Status.State != state || instance.Status.InstanceID != instanceID || tagsOrParamsChanged(instance) { @@ -552,7 +552,7 @@ func getState(serviceInstanceState string) string { return serviceInstanceState } -func setStatusFieldsFromSpec(instance *ibmcloudv1beta1.Service, resourceContext ibmcloudv1beta1.ResourceContext) { +func setStatusFieldsFromSpec(instance *ibmcloudv1.Service, resourceContext ibmcloudv1.ResourceContext) { instance.Status.Plan = instance.Spec.Plan instance.Status.ExternalName = instance.Spec.ExternalName instance.Status.ServiceClass = instance.Spec.ServiceClass @@ -563,7 +563,7 @@ func setStatusFieldsFromSpec(instance *ibmcloudv1beta1.Service, resourceContext instance.Spec.Context = resourceContext } -func tagsOrParamsChanged(instance *ibmcloudv1beta1.Service) bool { +func tagsOrParamsChanged(instance *ibmcloudv1.Service) bool { return !reflect.DeepEqual(instance.Spec.Parameters, instance.Status.Parameters) || !reflect.DeepEqual(instance.Spec.Tags, instance.Status.Tags) } diff --git a/controllers/service_controller_test.go b/controllers/service_controller_test.go index 45b2bd6d..bf6b82ac 100644 --- a/controllers/service_controller_test.go +++ b/controllers/service_controller_test.go @@ -14,7 +14,7 @@ import ( "github.com/IBM-Cloud/bluemix-go/session" "github.com/go-logr/logr" "github.com/go-logr/zapr" - ibmcloudv1beta1 "github.com/ibm/cloud-operators/api/v1beta1" + ibmcloudv1 "github.com/ibm/cloud-operators/api/v1" "github.com/ibm/cloud-operators/internal/config" "github.com/ibm/cloud-operators/internal/ibmcloud" "github.com/ibm/cloud-operators/internal/ibmcloud/cfservice" @@ -24,7 +24,6 @@ import ( "go.uber.org/zap/zaptest" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" - k8sErrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" @@ -46,7 +45,7 @@ func TestService(t *testing.T) { //"geoCF.yaml", } { t.Run(specfile, func(t *testing.T) { - service := new(ibmcloudv1beta1.Service) + service := new(ibmcloudv1.Service) mustLoadObject(t, filepath.Join("testdata", specfile), service, &service.ObjectMeta) ctx := context.TODO() logger := zapr.NewLogger(zaptest.NewLogger(t)) @@ -61,7 +60,7 @@ func TestService(t *testing.T) { assert.Equal(t, service.ObjectMeta.Name, bxsvc.Name) // test delete - serviceCopy := service.DeepCopyObject().(*ibmcloudv1beta1.Service) + serviceCopy := service.DeepCopyObject().(*ibmcloudv1.Service) require.NoError(t, k8sClient.Delete(ctx, service)) require.Eventually(t, func() bool { err := getObject(ctx, service.ObjectMeta, service) @@ -77,7 +76,7 @@ func TestService(t *testing.T) { assert.Equal(t, service.ObjectMeta.Name, bxsvc.Name) // test delete - serviceCopy := service.DeepCopyObject().(*ibmcloudv1beta1.Service) + serviceCopy := service.DeepCopyObject().(*ibmcloudv1.Service) require.NoError(t, k8sClient.Delete(ctx, service)) require.Eventually(t, func() bool { err := getObject(ctx, service.ObjectMeta, service) @@ -100,7 +99,7 @@ func TestService(t *testing.T) { aliasfile = "testdata/translator-alias.yaml" ) - service, alias := new(ibmcloudv1beta1.Service), new(ibmcloudv1beta1.Service) + service, alias := new(ibmcloudv1.Service), new(ibmcloudv1.Service) mustLoadObject(t, specfile, service, &service.ObjectMeta) mustLoadObject(t, aliasfile, alias, &alias.ObjectMeta) logger := zapr.NewLogger(zaptest.NewLogger(t)) @@ -113,7 +112,7 @@ func TestService(t *testing.T) { require.Eventually(t, verifyStatus(ctx, t, alias.ObjectMeta, alias, serviceStateOnline), defaultWait*2, defaultTick) // test delete - serviceCopy := service.DeepCopyObject().(*ibmcloudv1beta1.Service) + serviceCopy := service.DeepCopyObject().(*ibmcloudv1.Service) require.NoError(t, k8sClient.Delete(ctx, service)) require.Eventually(t, func() bool { err := getObject(ctx, service.ObjectMeta, service) @@ -134,7 +133,7 @@ func TestService(t *testing.T) { const ( specfile = "testdata/translator-wrong-plan.yaml" ) - service := new(ibmcloudv1beta1.Service) + service := new(ibmcloudv1.Service) mustLoadObject(t, specfile, service, &service.ObjectMeta) ctx := context.TODO() @@ -144,7 +143,7 @@ func TestService(t *testing.T) { } // getServiceInstanceFromObjCF from bx given context and resource in a CF context -func getServiceInstanceFromObjCF(logt logr.Logger, service *ibmcloudv1beta1.Service) (*mccpv2.ServiceInstance, error) { +func getServiceInstanceFromObjCF(logt logr.Logger, service *ibmcloudv1.Service) (*mccpv2.ServiceInstance, error) { externalName := getExternalName(service) ibmCloudInfo, err := ibmcloud.GetInfo(logt, k8sClient, service) @@ -157,7 +156,7 @@ func getServiceInstanceFromObjCF(logt logr.Logger, service *ibmcloudv1beta1.Serv } // getServiceInstanceFromObj from bx given context and resource -func getServiceInstanceFromObj(logt logr.Logger, service *ibmcloudv1beta1.Service) (models.ServiceInstance, error) { +func getServiceInstanceFromObj(logt logr.Logger, service *ibmcloudv1.Service) (models.ServiceInstance, error) { externalName := getExternalName(service) ibmCloudInfo, err := ibmcloud.GetInfo(logt, k8sClient, service) @@ -191,7 +190,7 @@ func TestServiceV1Alpha1Compat(t *testing.T) { t.SkipNow() } - service := new(ibmcloudv1beta1.Service) + service := new(ibmcloudv1.Service) mustLoadObject(t, filepath.Join("testdata", "translator-v1alpha1.yaml"), service, &service.ObjectMeta) ctx := context.TODO() logger := zapr.NewLogger(zaptest.NewLogger(t)) @@ -205,7 +204,37 @@ func TestServiceV1Alpha1Compat(t *testing.T) { assert.Equal(t, service.ObjectMeta.Name, bxsvc.Name) // test delete - serviceCopy := service.DeepCopyObject().(*ibmcloudv1beta1.Service) + serviceCopy := service.DeepCopyObject().(*ibmcloudv1.Service) + require.NoError(t, k8sClient.Delete(ctx, service)) + require.Eventually(t, func() bool { + err := getObject(ctx, service.ObjectMeta, service) + return errors.IsNotFound(err) + }, defaultWait, defaultTick) + + _, err = getServiceInstanceFromObj(logger, serviceCopy) + assert.True(t, ibmcloud.IsNotFound(err), "Expect service to be deleted") +} + +func TestServiceV1Beta1Compat(t *testing.T) { + if testing.Short() { + t.SkipNow() + } + + service := new(ibmcloudv1.Service) + mustLoadObject(t, filepath.Join("testdata", "translator-v1beta1.yaml"), service, &service.ObjectMeta) + ctx := context.TODO() + logger := zapr.NewLogger(zaptest.NewLogger(t)) + + require.NoError(t, k8sClient.Create(ctx, service)) + require.Eventually(t, verifyStatus(ctx, t, service.ObjectMeta, service, serviceStateOnline), defaultWait, defaultTick) + + // get instance directly from bx to make sure is there + bxsvc, err := getServiceInstanceFromObj(logger, service) + require.NoError(t, err) + assert.Equal(t, service.ObjectMeta.Name, bxsvc.Name) + + // test delete + serviceCopy := service.DeepCopyObject().(*ibmcloudv1.Service) require.NoError(t, k8sClient.Delete(ctx, service)) require.Eventually(t, func() bool { err := getObject(ctx, service.ObjectMeta, service) @@ -253,7 +282,7 @@ func TestServiceLoadServiceFailed(t *testing.T) { }) assert.Equal(t, ctrl.Result{}, result) assert.Error(t, err) - assert.False(t, k8sErrors.IsNotFound(err)) + assert.False(t, errors.IsNotFound(err)) }) } @@ -266,9 +295,9 @@ func TestServiceSpecChangedAndUpdateFailed(t *testing.T) { scheme := schemas(t) objects := []runtime.Object{ - &ibmcloudv1beta1.Service{ + &ibmcloudv1.Service{ ObjectMeta: metav1.ObjectMeta{Name: serviceName, Namespace: namespace}, - Status: ibmcloudv1beta1.ServiceStatus{ + Status: ibmcloudv1.ServiceStatus{ Plan: "Lite", }, }, @@ -299,15 +328,15 @@ func TestServiceGetIBMCloudInfoFailed(t *testing.T) { now := metav1Now(t) scheme := schemas(t) objects := []runtime.Object{ - &ibmcloudv1beta1.Service{ + &ibmcloudv1.Service{ ObjectMeta: metav1.ObjectMeta{ Name: serviceName, Namespace: namespace, DeletionTimestamp: now, Finalizers: []string{serviceFinalizer}, }, - Status: ibmcloudv1beta1.ServiceStatus{Plan: "Lite"}, - Spec: ibmcloudv1beta1.ServiceSpec{Plan: "Lite"}, + Status: ibmcloudv1.ServiceStatus{Plan: "Lite"}, + Spec: ibmcloudv1.ServiceSpec{Plan: "Lite"}, }, } @@ -320,7 +349,7 @@ func TestServiceGetIBMCloudInfoFailed(t *testing.T) { Log: testLogger(t), Scheme: scheme, - GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1beta1.Service) (*ibmcloud.Info, error) { + GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1.Service) (*ibmcloud.Info, error) { return nil, errors.NewNotFound(ctrl.GroupResource{Group: "ibmcloud.ibm.com", Resource: "secret"}, "ibmcloud-operator-secret") }, } @@ -330,19 +359,19 @@ func TestServiceGetIBMCloudInfoFailed(t *testing.T) { }) assert.Equal(t, ctrl.Result{}, result) assert.NoError(t, err) - assert.Equal(t, &ibmcloudv1beta1.Service{ - TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + assert.Equal(t, &ibmcloudv1.Service{ + TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1"}, ObjectMeta: metav1.ObjectMeta{ Name: serviceName, Namespace: namespace, DeletionTimestamp: now, Finalizers: nil, // attempt to remove finalizers }, - Status: ibmcloudv1beta1.ServiceStatus{ + Status: ibmcloudv1.ServiceStatus{ //State: serviceStateFailed, // TODO this state should be set! Plan: "Lite", }, - Spec: ibmcloudv1beta1.ServiceSpec{Plan: "Lite"}, + Spec: ibmcloudv1.ServiceSpec{Plan: "Lite"}, }, r.Client.(MockClient).LastUpdate()) assert.Equal(t, nil, r.Client.(MockClient).LastStatusUpdate()) }) @@ -357,7 +386,7 @@ func TestServiceGetIBMCloudInfoFailed(t *testing.T) { Log: testLogger(t), Scheme: scheme, - GetIBMCloudInfo: func(logt logr.Logger, r client.Client, instance *ibmcloudv1beta1.Service) (*ibmcloud.Info, error) { + GetIBMCloudInfo: func(logt logr.Logger, r client.Client, instance *ibmcloudv1.Service) (*ibmcloud.Info, error) { return nil, fmt.Errorf("failed") }, } @@ -370,20 +399,20 @@ func TestServiceGetIBMCloudInfoFailed(t *testing.T) { RequeueAfter: config.Get().SyncPeriod, }, result) assert.NoError(t, err) - assert.Equal(t, &ibmcloudv1beta1.Service{ - TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + assert.Equal(t, &ibmcloudv1.Service{ + TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1"}, ObjectMeta: metav1.ObjectMeta{ Name: serviceName, Namespace: namespace, DeletionTimestamp: now, Finalizers: []string{serviceFinalizer}, }, - Status: ibmcloudv1beta1.ServiceStatus{ + Status: ibmcloudv1.ServiceStatus{ State: serviceStateFailed, Message: "failed", Plan: "Lite", }, - Spec: ibmcloudv1beta1.ServiceSpec{Plan: "Lite"}, + Spec: ibmcloudv1.ServiceSpec{Plan: "Lite"}, }, fakeClient.LastStatusUpdate()) }) } @@ -397,9 +426,9 @@ func TestServiceFirstStatusFailed(t *testing.T) { scheme := schemas(t) objects := []runtime.Object{ - &ibmcloudv1beta1.Service{ + &ibmcloudv1.Service{ ObjectMeta: metav1.ObjectMeta{Name: serviceName, Namespace: namespace}, - Status: ibmcloudv1beta1.ServiceStatus{}, + Status: ibmcloudv1.ServiceStatus{}, }, } r := &ServiceReconciler{ @@ -410,7 +439,7 @@ func TestServiceFirstStatusFailed(t *testing.T) { Log: testLogger(t), Scheme: scheme, - GetIBMCloudInfo: func(logt logr.Logger, r client.Client, instance *ibmcloudv1beta1.Service) (*ibmcloud.Info, error) { + GetIBMCloudInfo: func(logt logr.Logger, r client.Client, instance *ibmcloudv1.Service) (*ibmcloud.Info, error) { return &ibmcloud.Info{}, nil }, } @@ -431,15 +460,15 @@ func TestServiceEnsureFinalizerFailed(t *testing.T) { scheme := schemas(t) objects := []runtime.Object{ - &ibmcloudv1beta1.Service{ + &ibmcloudv1.Service{ ObjectMeta: metav1.ObjectMeta{ Name: serviceName, Namespace: namespace, DeletionTimestamp: nil, // not deleting Finalizers: nil, // AND missing finalizer }, - Status: ibmcloudv1beta1.ServiceStatus{Plan: "Lite"}, - Spec: ibmcloudv1beta1.ServiceSpec{Plan: "Lite"}, + Status: ibmcloudv1.ServiceStatus{Plan: "Lite"}, + Spec: ibmcloudv1.ServiceSpec{Plan: "Lite"}, }, } var r *ServiceReconciler @@ -448,7 +477,7 @@ func TestServiceEnsureFinalizerFailed(t *testing.T) { Log: testLogger(t), Scheme: scheme, - GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1beta1.Service) (*ibmcloud.Info, error) { + GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1.Service) (*ibmcloud.Info, error) { r.Client = newMockClient( fake.NewFakeClientWithScheme(scheme, objects...), MockConfig{UpdateErr: fmt.Errorf("failed")}, @@ -462,17 +491,17 @@ func TestServiceEnsureFinalizerFailed(t *testing.T) { }) assert.Equal(t, ctrl.Result{}, result) assert.EqualError(t, err, "failed") - assert.Equal(t, &ibmcloudv1beta1.Service{ - TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + assert.Equal(t, &ibmcloudv1.Service{ + TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1"}, ObjectMeta: metav1.ObjectMeta{ Name: serviceName, Namespace: namespace, Finalizers: []string{serviceFinalizer}, }, - Status: ibmcloudv1beta1.ServiceStatus{ + Status: ibmcloudv1.ServiceStatus{ Plan: "Lite", }, - Spec: ibmcloudv1beta1.ServiceSpec{Plan: "Lite"}, + Spec: ibmcloudv1.ServiceSpec{Plan: "Lite"}, }, r.Client.(MockClient).LastUpdate()) } @@ -487,15 +516,15 @@ func TestServiceDeletingFailed(t *testing.T) { scheme := schemas(t) now := metav1Now(t) objects := []runtime.Object{ - &ibmcloudv1beta1.Service{ + &ibmcloudv1.Service{ ObjectMeta: metav1.ObjectMeta{ Name: serviceName, Namespace: namespace, DeletionTimestamp: now, Finalizers: []string{serviceFinalizer}, }, - Status: ibmcloudv1beta1.ServiceStatus{Plan: "Lite", InstanceID: "myinstanceid"}, - Spec: ibmcloudv1beta1.ServiceSpec{Plan: "Lite"}, + Status: ibmcloudv1.ServiceStatus{Plan: "Lite", InstanceID: "myinstanceid"}, + Spec: ibmcloudv1.ServiceSpec{Plan: "Lite"}, }, } @@ -505,7 +534,7 @@ func TestServiceDeletingFailed(t *testing.T) { Log: testLogger(t), Scheme: scheme, - GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1beta1.Service) (*ibmcloud.Info, error) { + GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1.Service) (*ibmcloud.Info, error) { r.Client = newMockClient( fake.NewFakeClientWithScheme(scheme, objects...), MockConfig{}, @@ -531,15 +560,15 @@ func TestServiceDeletingFailed(t *testing.T) { scheme := schemas(t) now := metav1Now(t) objects := []runtime.Object{ - &ibmcloudv1beta1.Service{ + &ibmcloudv1.Service{ ObjectMeta: metav1.ObjectMeta{ Name: serviceName, Namespace: namespace, DeletionTimestamp: now, Finalizers: []string{serviceFinalizer}, }, - Status: ibmcloudv1beta1.ServiceStatus{Plan: "Lite"}, - Spec: ibmcloudv1beta1.ServiceSpec{Plan: "Lite"}, + Status: ibmcloudv1.ServiceStatus{Plan: "Lite"}, + Spec: ibmcloudv1.ServiceSpec{Plan: "Lite"}, }, } @@ -549,7 +578,7 @@ func TestServiceDeletingFailed(t *testing.T) { Log: testLogger(t), Scheme: scheme, - GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1beta1.Service) (*ibmcloud.Info, error) { + GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1.Service) (*ibmcloud.Info, error) { r.Client = newMockClient( fake.NewFakeClientWithScheme(scheme, objects...), MockConfig{UpdateErr: fmt.Errorf("failed")}, @@ -563,18 +592,18 @@ func TestServiceDeletingFailed(t *testing.T) { }) assert.Equal(t, ctrl.Result{}, result) assert.EqualError(t, err, "failed") - assert.Equal(t, &ibmcloudv1beta1.Service{ - TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + assert.Equal(t, &ibmcloudv1.Service{ + TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1"}, ObjectMeta: metav1.ObjectMeta{ Name: serviceName, Namespace: namespace, DeletionTimestamp: now, Finalizers: nil, // attempt to remove finalizers }, - Status: ibmcloudv1beta1.ServiceStatus{ + Status: ibmcloudv1.ServiceStatus{ Plan: "Lite", }, - Spec: ibmcloudv1beta1.ServiceSpec{Plan: "Lite"}, + Spec: ibmcloudv1.ServiceSpec{Plan: "Lite"}, }, r.Client.(MockClient).LastUpdate()) }) } @@ -588,25 +617,25 @@ func TestServiceGetParamsFailed(t *testing.T) { scheme := schemas(t) objects := []runtime.Object{ - &ibmcloudv1beta1.Service{ + &ibmcloudv1.Service{ ObjectMeta: metav1.ObjectMeta{Name: serviceName, Namespace: namespace}, - Status: ibmcloudv1beta1.ServiceStatus{ + Status: ibmcloudv1.ServiceStatus{ Plan: "Lite", - Parameters: []ibmcloudv1beta1.Param{ + Parameters: []ibmcloudv1.Param{ { Name: "hello", - Value: &ibmcloudv1beta1.ParamValue{RawMessage: json.RawMessage(`"world"`)}, - ValueFrom: &ibmcloudv1beta1.ParamSource{}, + Value: &ibmcloudv1.ParamValue{RawMessage: json.RawMessage(`"world"`)}, + ValueFrom: &ibmcloudv1.ParamSource{}, }, }, }, - Spec: ibmcloudv1beta1.ServiceSpec{ + Spec: ibmcloudv1.ServiceSpec{ Plan: "Lite", - Parameters: []ibmcloudv1beta1.Param{ + Parameters: []ibmcloudv1.Param{ { Name: "hello", - Value: &ibmcloudv1beta1.ParamValue{RawMessage: json.RawMessage(`"world"`)}, - ValueFrom: &ibmcloudv1beta1.ParamSource{}, + Value: &ibmcloudv1.ParamValue{RawMessage: json.RawMessage(`"world"`)}, + ValueFrom: &ibmcloudv1.ParamSource{}, }, }, }, @@ -620,7 +649,7 @@ func TestServiceGetParamsFailed(t *testing.T) { Log: testLogger(t), Scheme: scheme, - GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1beta1.Service) (*ibmcloud.Info, error) { + GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1.Service) (*ibmcloud.Info, error) { return &ibmcloud.Info{}, nil }, } @@ -633,32 +662,32 @@ func TestServiceGetParamsFailed(t *testing.T) { RequeueAfter: config.Get().SyncPeriod, }, result) assert.NoError(t, err) - assert.Equal(t, &ibmcloudv1beta1.Service{ - TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + assert.Equal(t, &ibmcloudv1.Service{ + TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1"}, ObjectMeta: metav1.ObjectMeta{ Name: serviceName, Namespace: namespace, Finalizers: []string{serviceFinalizer}, }, - Status: ibmcloudv1beta1.ServiceStatus{ + Status: ibmcloudv1.ServiceStatus{ State: serviceStateFailed, Message: "Value and ValueFrom properties are mutually exclusive (for hello variable)", Plan: "Lite", - Parameters: []ibmcloudv1beta1.Param{ + Parameters: []ibmcloudv1.Param{ { Name: "hello", - Value: &ibmcloudv1beta1.ParamValue{RawMessage: json.RawMessage(`"world"`)}, - ValueFrom: &ibmcloudv1beta1.ParamSource{}, + Value: &ibmcloudv1.ParamValue{RawMessage: json.RawMessage(`"world"`)}, + ValueFrom: &ibmcloudv1.ParamSource{}, }, }, }, - Spec: ibmcloudv1beta1.ServiceSpec{ + Spec: ibmcloudv1.ServiceSpec{ Plan: "Lite", - Parameters: []ibmcloudv1beta1.Param{ + Parameters: []ibmcloudv1.Param{ { Name: "hello", - Value: &ibmcloudv1beta1.ParamValue{RawMessage: json.RawMessage(`"world"`)}, - ValueFrom: &ibmcloudv1beta1.ParamSource{}, + Value: &ibmcloudv1.ParamValue{RawMessage: json.RawMessage(`"world"`)}, + ValueFrom: &ibmcloudv1.ParamSource{}, }, }, }, @@ -675,10 +704,10 @@ func TestServiceEnsureCFServiceExists(t *testing.T) { t.Run("create - empty service ID", func(t *testing.T) { scheme := schemas(t) objects := []runtime.Object{ - &ibmcloudv1beta1.Service{ + &ibmcloudv1.Service{ ObjectMeta: metav1.ObjectMeta{Name: serviceName, Namespace: namespace}, - Status: ibmcloudv1beta1.ServiceStatus{Plan: "Lite", ServiceClass: "service-name"}, - Spec: ibmcloudv1beta1.ServiceSpec{Plan: "Lite", ServiceClass: "service-name"}, + Status: ibmcloudv1.ServiceStatus{Plan: "Lite", ServiceClass: "service-name"}, + Spec: ibmcloudv1.ServiceSpec{Plan: "Lite", ServiceClass: "service-name"}, }, } var createErr error @@ -690,7 +719,7 @@ func TestServiceEnsureCFServiceExists(t *testing.T) { Log: testLogger(t), Scheme: scheme, - GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1beta1.Service) (*ibmcloud.Info, error) { + GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1.Service) (*ibmcloud.Info, error) { return &ibmcloud.Info{ ServiceClassType: "CF", }, nil @@ -710,14 +739,14 @@ func TestServiceEnsureCFServiceExists(t *testing.T) { RequeueAfter: config.Get().SyncPeriod, }, result) assert.NoError(t, err) - assert.Equal(t, &ibmcloudv1beta1.Service{ - TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + assert.Equal(t, &ibmcloudv1.Service{ + TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1"}, ObjectMeta: metav1.ObjectMeta{ Name: serviceName, Namespace: namespace, Finalizers: []string{serviceFinalizer}, }, - Status: ibmcloudv1beta1.ServiceStatus{ + Status: ibmcloudv1.ServiceStatus{ State: "state", Message: "state", Plan: "Lite", @@ -725,7 +754,7 @@ func TestServiceEnsureCFServiceExists(t *testing.T) { DashboardURL: "https://cloud.ibm.com/services/service-name/guid", ServiceClass: "service-name", }, - Spec: ibmcloudv1beta1.ServiceSpec{Plan: "Lite", ServiceClass: "service-name"}, + Spec: ibmcloudv1.ServiceSpec{Plan: "Lite", ServiceClass: "service-name"}, }, r.Client.(MockClient).LastStatusUpdate()) }) @@ -739,20 +768,20 @@ func TestServiceEnsureCFServiceExists(t *testing.T) { RequeueAfter: config.Get().SyncPeriod, }, result) assert.NoError(t, err) - assert.Equal(t, &ibmcloudv1beta1.Service{ - TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + assert.Equal(t, &ibmcloudv1.Service{ + TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1"}, ObjectMeta: metav1.ObjectMeta{ Name: serviceName, Namespace: namespace, Finalizers: []string{serviceFinalizer}, }, - Status: ibmcloudv1beta1.ServiceStatus{ + Status: ibmcloudv1.ServiceStatus{ State: serviceStateFailed, Message: "failed", Plan: "Lite", ServiceClass: "service-name", }, - Spec: ibmcloudv1beta1.ServiceSpec{Plan: "Lite", ServiceClass: "service-name"}, + Spec: ibmcloudv1.ServiceSpec{Plan: "Lite", ServiceClass: "service-name"}, }, r.Client.(MockClient).LastStatusUpdate()) }) }) @@ -760,14 +789,14 @@ func TestServiceEnsureCFServiceExists(t *testing.T) { t.Run("create alias success", func(t *testing.T) { scheme := schemas(t) objects := []runtime.Object{ - &ibmcloudv1beta1.Service{ + &ibmcloudv1.Service{ ObjectMeta: metav1.ObjectMeta{Name: serviceName, Namespace: namespace}, - Status: ibmcloudv1beta1.ServiceStatus{ + Status: ibmcloudv1.ServiceStatus{ Plan: aliasPlan, ServiceClass: "service-name", InstanceID: "guid", }, - Spec: ibmcloudv1beta1.ServiceSpec{ + Spec: ibmcloudv1.ServiceSpec{ Plan: aliasPlan, ServiceClass: "service-name", }, @@ -781,7 +810,7 @@ func TestServiceEnsureCFServiceExists(t *testing.T) { Log: testLogger(t), Scheme: scheme, - GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1beta1.Service) (*ibmcloud.Info, error) { + GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1.Service) (*ibmcloud.Info, error) { return &ibmcloud.Info{ ServiceClassType: "CF", }, nil @@ -802,14 +831,14 @@ func TestServiceEnsureCFServiceExists(t *testing.T) { RequeueAfter: config.Get().SyncPeriod, }, result) assert.NoError(t, err) - assert.Equal(t, &ibmcloudv1beta1.Service{ - TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + assert.Equal(t, &ibmcloudv1.Service{ + TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1"}, ObjectMeta: metav1.ObjectMeta{ Name: serviceName, Namespace: namespace, Finalizers: []string{serviceFinalizer}, }, - Status: ibmcloudv1beta1.ServiceStatus{ + Status: ibmcloudv1.ServiceStatus{ State: "state", // TODO(johnstarich) This isn't a known state, right? We should have predictable states here. Message: "state", DashboardURL: "https://cloud.ibm.com/services/service-name/guid", @@ -817,7 +846,7 @@ func TestServiceEnsureCFServiceExists(t *testing.T) { ServiceClass: "service-name", InstanceID: "guid", }, - Spec: ibmcloudv1beta1.ServiceSpec{ + Spec: ibmcloudv1.ServiceSpec{ Plan: aliasPlan, ServiceClass: "service-name", }, @@ -827,14 +856,14 @@ func TestServiceEnsureCFServiceExists(t *testing.T) { t.Run("ensure alias - empty instance ID", func(t *testing.T) { scheme := schemas(t) objects := []runtime.Object{ - &ibmcloudv1beta1.Service{ + &ibmcloudv1.Service{ ObjectMeta: metav1.ObjectMeta{Name: serviceName, Namespace: namespace}, - Status: ibmcloudv1beta1.ServiceStatus{ + Status: ibmcloudv1.ServiceStatus{ Plan: aliasPlan, ServiceClass: "service-name", InstanceID: "", // no instance ID set }, - Spec: ibmcloudv1beta1.ServiceSpec{ + Spec: ibmcloudv1.ServiceSpec{ Plan: aliasPlan, ServiceClass: "service-name", }, @@ -849,7 +878,7 @@ func TestServiceEnsureCFServiceExists(t *testing.T) { Log: testLogger(t), Scheme: scheme, - GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1beta1.Service) (*ibmcloud.Info, error) { + GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1.Service) (*ibmcloud.Info, error) { return &ibmcloud.Info{ ServiceClassType: "CF", }, nil @@ -869,14 +898,14 @@ func TestServiceEnsureCFServiceExists(t *testing.T) { RequeueAfter: config.Get().SyncPeriod, }, result) assert.NoError(t, err) - assert.Equal(t, &ibmcloudv1beta1.Service{ - TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + assert.Equal(t, &ibmcloudv1.Service{ + TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1"}, ObjectMeta: metav1.ObjectMeta{ Name: serviceName, Namespace: namespace, Finalizers: []string{serviceFinalizer}, }, - Status: ibmcloudv1beta1.ServiceStatus{ + Status: ibmcloudv1.ServiceStatus{ State: serviceStateOnline, Message: serviceStateOnline, Plan: aliasPlan, @@ -884,7 +913,7 @@ func TestServiceEnsureCFServiceExists(t *testing.T) { InstanceID: "guid", DashboardURL: "https://cloud.ibm.com/services/service-name/guid", }, - Spec: ibmcloudv1beta1.ServiceSpec{ + Spec: ibmcloudv1.ServiceSpec{ Plan: aliasPlan, ServiceClass: "service-name", }, @@ -901,21 +930,21 @@ func TestServiceEnsureCFServiceExists(t *testing.T) { RequeueAfter: config.Get().SyncPeriod, }, result) assert.NoError(t, err) - assert.Equal(t, &ibmcloudv1beta1.Service{ - TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + assert.Equal(t, &ibmcloudv1.Service{ + TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1"}, ObjectMeta: metav1.ObjectMeta{ Name: serviceName, Namespace: namespace, Finalizers: []string{serviceFinalizer}, }, - Status: ibmcloudv1beta1.ServiceStatus{ + Status: ibmcloudv1.ServiceStatus{ State: serviceStateFailed, Message: "failed", Plan: aliasPlan, ServiceClass: "service-name", InstanceID: "", }, - Spec: ibmcloudv1beta1.ServiceSpec{ + Spec: ibmcloudv1.ServiceSpec{ Plan: aliasPlan, ServiceClass: "service-name", }, @@ -926,14 +955,14 @@ func TestServiceEnsureCFServiceExists(t *testing.T) { t.Run("get instance failed - not found", func(t *testing.T) { scheme := schemas(t) objects := []runtime.Object{ - &ibmcloudv1beta1.Service{ + &ibmcloudv1.Service{ ObjectMeta: metav1.ObjectMeta{Name: serviceName, Namespace: namespace}, - Status: ibmcloudv1beta1.ServiceStatus{ + Status: ibmcloudv1.ServiceStatus{ Plan: "Lite", ServiceClass: "service-name", InstanceID: "guid", }, - Spec: ibmcloudv1beta1.ServiceSpec{ + Spec: ibmcloudv1.ServiceSpec{ Plan: "Lite", ServiceClass: "service-name", }, @@ -947,7 +976,7 @@ func TestServiceEnsureCFServiceExists(t *testing.T) { Log: testLogger(t), Scheme: scheme, - GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1beta1.Service) (*ibmcloud.Info, error) { + GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1.Service) (*ibmcloud.Info, error) { return &ibmcloud.Info{ ServiceClassType: "CF", }, nil @@ -968,14 +997,14 @@ func TestServiceEnsureCFServiceExists(t *testing.T) { RequeueAfter: config.Get().SyncPeriod, }, result) assert.NoError(t, err) - assert.Equal(t, &ibmcloudv1beta1.Service{ - TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + assert.Equal(t, &ibmcloudv1.Service{ + TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1"}, ObjectMeta: metav1.ObjectMeta{ Name: serviceName, Namespace: namespace, Finalizers: []string{serviceFinalizer}, }, - Status: ibmcloudv1beta1.ServiceStatus{ + Status: ibmcloudv1.ServiceStatus{ State: "state", Message: "state", Plan: "Lite", @@ -983,21 +1012,21 @@ func TestServiceEnsureCFServiceExists(t *testing.T) { ServiceClass: "service-name", DashboardURL: "https://cloud.ibm.com/services/service-name/guid", }, - Spec: ibmcloudv1beta1.ServiceSpec{Plan: "Lite", ServiceClass: "service-name"}, + Spec: ibmcloudv1.ServiceSpec{Plan: "Lite", ServiceClass: "service-name"}, }, r.Client.(MockClient).LastStatusUpdate()) }) t.Run("get instance failed - not found, create failed", func(t *testing.T) { scheme := schemas(t) objects := []runtime.Object{ - &ibmcloudv1beta1.Service{ + &ibmcloudv1.Service{ ObjectMeta: metav1.ObjectMeta{Name: serviceName, Namespace: namespace}, - Status: ibmcloudv1beta1.ServiceStatus{ + Status: ibmcloudv1.ServiceStatus{ Plan: "Lite", ServiceClass: "service-name", InstanceID: "guid", }, - Spec: ibmcloudv1beta1.ServiceSpec{ + Spec: ibmcloudv1.ServiceSpec{ Plan: "Lite", ServiceClass: "service-name", }, @@ -1011,7 +1040,7 @@ func TestServiceEnsureCFServiceExists(t *testing.T) { Log: testLogger(t), Scheme: scheme, - GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1beta1.Service) (*ibmcloud.Info, error) { + GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1.Service) (*ibmcloud.Info, error) { return &ibmcloud.Info{ ServiceClassType: "CF", }, nil @@ -1032,35 +1061,35 @@ func TestServiceEnsureCFServiceExists(t *testing.T) { RequeueAfter: config.Get().SyncPeriod, }, result) assert.NoError(t, err) - assert.Equal(t, &ibmcloudv1beta1.Service{ - TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + assert.Equal(t, &ibmcloudv1.Service{ + TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1"}, ObjectMeta: metav1.ObjectMeta{ Name: serviceName, Namespace: namespace, Finalizers: []string{serviceFinalizer}, }, - Status: ibmcloudv1beta1.ServiceStatus{ + Status: ibmcloudv1.ServiceStatus{ State: serviceStateFailed, Message: "failed", Plan: "Lite", InstanceID: "guid", ServiceClass: "service-name", }, - Spec: ibmcloudv1beta1.ServiceSpec{Plan: "Lite", ServiceClass: "service-name"}, + Spec: ibmcloudv1.ServiceSpec{Plan: "Lite", ServiceClass: "service-name"}, }, r.Client.(MockClient).LastStatusUpdate()) }) t.Run("get instance failed - other error", func(t *testing.T) { scheme := schemas(t) objects := []runtime.Object{ - &ibmcloudv1beta1.Service{ + &ibmcloudv1.Service{ ObjectMeta: metav1.ObjectMeta{Name: serviceName, Namespace: namespace}, - Status: ibmcloudv1beta1.ServiceStatus{ + Status: ibmcloudv1.ServiceStatus{ Plan: "Lite", ServiceClass: "service-name", InstanceID: "guid", }, - Spec: ibmcloudv1beta1.ServiceSpec{ + Spec: ibmcloudv1.ServiceSpec{ Plan: "Lite", ServiceClass: "service-name", }, @@ -1074,7 +1103,7 @@ func TestServiceEnsureCFServiceExists(t *testing.T) { Log: testLogger(t), Scheme: scheme, - GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1beta1.Service) (*ibmcloud.Info, error) { + GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1.Service) (*ibmcloud.Info, error) { return &ibmcloud.Info{ ServiceClassType: "CF", }, nil @@ -1092,35 +1121,35 @@ func TestServiceEnsureCFServiceExists(t *testing.T) { RequeueAfter: config.Get().SyncPeriod, }, result) assert.NoError(t, err) - assert.Equal(t, &ibmcloudv1beta1.Service{ - TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + assert.Equal(t, &ibmcloudv1.Service{ + TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1"}, ObjectMeta: metav1.ObjectMeta{ Name: serviceName, Namespace: namespace, Finalizers: []string{serviceFinalizer}, }, - Status: ibmcloudv1beta1.ServiceStatus{ + Status: ibmcloudv1.ServiceStatus{ State: serviceStateFailed, Message: "failed", Plan: "Lite", InstanceID: "guid", ServiceClass: "service-name", }, - Spec: ibmcloudv1beta1.ServiceSpec{Plan: "Lite", ServiceClass: "service-name"}, + Spec: ibmcloudv1.ServiceSpec{Plan: "Lite", ServiceClass: "service-name"}, }, r.Client.(MockClient).LastStatusUpdate()) }) t.Run("ensure alias - instance does not exist", func(t *testing.T) { scheme := schemas(t) objects := []runtime.Object{ - &ibmcloudv1beta1.Service{ + &ibmcloudv1.Service{ ObjectMeta: metav1.ObjectMeta{Name: serviceName, Namespace: namespace}, - Status: ibmcloudv1beta1.ServiceStatus{ + Status: ibmcloudv1.ServiceStatus{ Plan: aliasPlan, ServiceClass: "service-name", InstanceID: "some-instance-id", // instance ID set }, - Spec: ibmcloudv1beta1.ServiceSpec{ + Spec: ibmcloudv1.ServiceSpec{ Plan: aliasPlan, ServiceClass: "service-name", }, @@ -1134,7 +1163,7 @@ func TestServiceEnsureCFServiceExists(t *testing.T) { Log: testLogger(t), Scheme: scheme, - GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1beta1.Service) (*ibmcloud.Info, error) { + GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1.Service) (*ibmcloud.Info, error) { return &ibmcloud.Info{ ServiceClassType: "CF", }, nil @@ -1152,21 +1181,21 @@ func TestServiceEnsureCFServiceExists(t *testing.T) { RequeueAfter: config.Get().SyncPeriod, }, result) assert.NoError(t, err) - assert.Equal(t, &ibmcloudv1beta1.Service{ - TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + assert.Equal(t, &ibmcloudv1.Service{ + TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1"}, ObjectMeta: metav1.ObjectMeta{ Name: serviceName, Namespace: namespace, Finalizers: []string{serviceFinalizer}, }, - Status: ibmcloudv1beta1.ServiceStatus{ + Status: ibmcloudv1.ServiceStatus{ State: serviceStatePending, Message: "failed", Plan: aliasPlan, ServiceClass: "service-name", InstanceID: "", // instance ID should be deleted }, - Spec: ibmcloudv1beta1.ServiceSpec{ + Spec: ibmcloudv1.ServiceSpec{ Plan: aliasPlan, ServiceClass: "service-name", }, @@ -1184,13 +1213,13 @@ func TestServiceEnsureResourceServiceInstance(t *testing.T) { t.Run("alias", func(t *testing.T) { scheme := schemas(t) objects := []runtime.Object{ - &ibmcloudv1beta1.Service{ + &ibmcloudv1.Service{ ObjectMeta: metav1.ObjectMeta{Name: serviceName, Namespace: namespace}, - Status: ibmcloudv1beta1.ServiceStatus{ + Status: ibmcloudv1.ServiceStatus{ Plan: aliasPlan, ServiceClass: "service-name", }, - Spec: ibmcloudv1beta1.ServiceSpec{ + Spec: ibmcloudv1.ServiceSpec{ Plan: aliasPlan, ServiceClass: "service-name", }, @@ -1206,7 +1235,7 @@ func TestServiceEnsureResourceServiceInstance(t *testing.T) { Log: testLogger(t), Scheme: scheme, - GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1beta1.Service) (*ibmcloud.Info, error) { + GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1.Service) (*ibmcloud.Info, error) { return &ibmcloud.Info{}, nil }, GetResourceServiceAliasInstance: func(session *session.Session, instanceID, resourceGroupID, servicePlanID, externalName string, logt logr.Logger) (id string, state string, err error) { @@ -1222,14 +1251,14 @@ func TestServiceEnsureResourceServiceInstance(t *testing.T) { RequeueAfter: config.Get().SyncPeriod, }, result) assert.NoError(t, err) - assert.Equal(t, &ibmcloudv1beta1.Service{ - TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + assert.Equal(t, &ibmcloudv1.Service{ + TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1"}, ObjectMeta: metav1.ObjectMeta{ Name: serviceName, Namespace: namespace, Finalizers: []string{serviceFinalizer}, }, - Status: ibmcloudv1beta1.ServiceStatus{ + Status: ibmcloudv1.ServiceStatus{ State: "state", Message: "state", Plan: aliasPlan, @@ -1237,7 +1266,7 @@ func TestServiceEnsureResourceServiceInstance(t *testing.T) { InstanceID: "guid", DashboardURL: "https://cloud.ibm.com/services/service-name/guid", }, - Spec: ibmcloudv1beta1.ServiceSpec{ + Spec: ibmcloudv1.ServiceSpec{ Plan: aliasPlan, ServiceClass: "service-name", }, @@ -1253,7 +1282,7 @@ func TestServiceEnsureResourceServiceInstance(t *testing.T) { Log: testLogger(t), Scheme: scheme, - GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1beta1.Service) (*ibmcloud.Info, error) { + GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1.Service) (*ibmcloud.Info, error) { return &ibmcloud.Info{}, nil }, GetResourceServiceAliasInstance: func(session *session.Session, instanceID, resourceGroupID, servicePlanID, externalName string, logt logr.Logger) (id string, state string, err error) { @@ -1269,20 +1298,20 @@ func TestServiceEnsureResourceServiceInstance(t *testing.T) { RequeueAfter: config.Get().SyncPeriod, }, result) assert.NoError(t, err) - assert.Equal(t, &ibmcloudv1beta1.Service{ - TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + assert.Equal(t, &ibmcloudv1.Service{ + TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1"}, ObjectMeta: metav1.ObjectMeta{ Name: serviceName, Namespace: namespace, Finalizers: []string{serviceFinalizer}, }, - Status: ibmcloudv1beta1.ServiceStatus{ + Status: ibmcloudv1.ServiceStatus{ State: serviceStateFailed, Message: "no service instances with name myservice found for alias plan: failed", Plan: aliasPlan, ServiceClass: "service-name", }, - Spec: ibmcloudv1beta1.ServiceSpec{ + Spec: ibmcloudv1.ServiceSpec{ Plan: aliasPlan, ServiceClass: "service-name", }, @@ -1298,7 +1327,7 @@ func TestServiceEnsureResourceServiceInstance(t *testing.T) { Log: testLogger(t), Scheme: scheme, - GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1beta1.Service) (*ibmcloud.Info, error) { + GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1.Service) (*ibmcloud.Info, error) { return &ibmcloud.Info{}, nil }, GetResourceServiceAliasInstance: func(session *session.Session, instanceID, resourceGroupID, servicePlanID, externalName string, logt logr.Logger) (id string, state string, err error) { @@ -1314,20 +1343,20 @@ func TestServiceEnsureResourceServiceInstance(t *testing.T) { RequeueAfter: config.Get().SyncPeriod, }, result) assert.NoError(t, err) - assert.Equal(t, &ibmcloudv1beta1.Service{ - TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + assert.Equal(t, &ibmcloudv1.Service{ + TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1"}, ObjectMeta: metav1.ObjectMeta{ Name: serviceName, Namespace: namespace, Finalizers: []string{serviceFinalizer}, }, - Status: ibmcloudv1beta1.ServiceStatus{ + Status: ibmcloudv1.ServiceStatus{ State: serviceStateFailed, Message: "failed to resolve Alias plan instance myservice: failed", Plan: aliasPlan, ServiceClass: "service-name", }, - Spec: ibmcloudv1beta1.ServiceSpec{ + Spec: ibmcloudv1.ServiceSpec{ Plan: aliasPlan, ServiceClass: "service-name", }, @@ -1338,13 +1367,13 @@ func TestServiceEnsureResourceServiceInstance(t *testing.T) { t.Run("non-alias", func(t *testing.T) { scheme := schemas(t) objects := []runtime.Object{ - &ibmcloudv1beta1.Service{ + &ibmcloudv1.Service{ ObjectMeta: metav1.ObjectMeta{Name: serviceName, Namespace: namespace}, - Status: ibmcloudv1beta1.ServiceStatus{ + Status: ibmcloudv1.ServiceStatus{ Plan: "Lite", ServiceClass: "service-name", }, - Spec: ibmcloudv1beta1.ServiceSpec{ + Spec: ibmcloudv1.ServiceSpec{ Plan: "Lite", ServiceClass: "service-name", }, @@ -1360,7 +1389,7 @@ func TestServiceEnsureResourceServiceInstance(t *testing.T) { Log: testLogger(t), Scheme: scheme, - GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1beta1.Service) (*ibmcloud.Info, error) { + GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1.Service) (*ibmcloud.Info, error) { return &ibmcloud.Info{}, nil }, CreateResourceServiceInstance: func(session *session.Session, externalName, servicePlanID, resourceGroupID, targetCrn string, params map[string]interface{}, tags []string) (id string, state string, err error) { @@ -1376,14 +1405,14 @@ func TestServiceEnsureResourceServiceInstance(t *testing.T) { RequeueAfter: config.Get().SyncPeriod, }, result) assert.NoError(t, err) - assert.Equal(t, &ibmcloudv1beta1.Service{ - TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + assert.Equal(t, &ibmcloudv1.Service{ + TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1"}, ObjectMeta: metav1.ObjectMeta{ Name: serviceName, Namespace: namespace, Finalizers: []string{serviceFinalizer}, }, - Status: ibmcloudv1beta1.ServiceStatus{ + Status: ibmcloudv1.ServiceStatus{ State: "state", Message: "state", Plan: "Lite", @@ -1391,7 +1420,7 @@ func TestServiceEnsureResourceServiceInstance(t *testing.T) { InstanceID: "id", DashboardURL: "https://cloud.ibm.com/services/service-name/id", }, - Spec: ibmcloudv1beta1.ServiceSpec{ + Spec: ibmcloudv1.ServiceSpec{ Plan: "Lite", ServiceClass: "service-name", }, @@ -1407,7 +1436,7 @@ func TestServiceEnsureResourceServiceInstance(t *testing.T) { Log: testLogger(t), Scheme: scheme, - GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1beta1.Service) (*ibmcloud.Info, error) { + GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1.Service) (*ibmcloud.Info, error) { return &ibmcloud.Info{}, nil }, } @@ -1417,21 +1446,21 @@ func TestServiceEnsureResourceServiceInstance(t *testing.T) { }) assert.Equal(t, ctrl.Result{}, result) assert.EqualError(t, err, "failed") - assert.Equal(t, &ibmcloudv1beta1.Service{ - TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + assert.Equal(t, &ibmcloudv1.Service{ + TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1"}, ObjectMeta: metav1.ObjectMeta{ Name: serviceName, Namespace: namespace, Finalizers: []string{serviceFinalizer}, }, - Status: ibmcloudv1beta1.ServiceStatus{ + Status: ibmcloudv1.ServiceStatus{ State: "", Message: "", Plan: "Lite", ServiceClass: "service-name", InstanceID: inProgress, }, - Spec: ibmcloudv1beta1.ServiceSpec{ + Spec: ibmcloudv1.ServiceSpec{ Plan: "Lite", ServiceClass: "service-name", }, @@ -1447,7 +1476,7 @@ func TestServiceEnsureResourceServiceInstance(t *testing.T) { Log: testLogger(t), Scheme: scheme, - GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1beta1.Service) (*ibmcloud.Info, error) { + GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1.Service) (*ibmcloud.Info, error) { return &ibmcloud.Info{}, nil }, CreateResourceServiceInstance: func(session *session.Session, externalName, servicePlanID, resourceGroupID, targetCrn string, params map[string]interface{}, tags []string) (id string, state string, err error) { @@ -1463,21 +1492,21 @@ func TestServiceEnsureResourceServiceInstance(t *testing.T) { RequeueAfter: config.Get().SyncPeriod, }, result) assert.NoError(t, err) - assert.Equal(t, &ibmcloudv1beta1.Service{ - TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + assert.Equal(t, &ibmcloudv1.Service{ + TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1"}, ObjectMeta: metav1.ObjectMeta{ Name: serviceName, Namespace: namespace, Finalizers: []string{serviceFinalizer}, }, - Status: ibmcloudv1beta1.ServiceStatus{ + Status: ibmcloudv1.ServiceStatus{ State: serviceStateFailed, Message: "failed", Plan: "Lite", ServiceClass: "service-name", InstanceID: inProgress, }, - Spec: ibmcloudv1beta1.ServiceSpec{ + Spec: ibmcloudv1.ServiceSpec{ Plan: "Lite", ServiceClass: "service-name", }, @@ -1494,28 +1523,28 @@ func TestServiceVerifyExists(t *testing.T) { ) scheme := schemas(t) objects := []runtime.Object{ - &ibmcloudv1beta1.Service{ + &ibmcloudv1.Service{ ObjectMeta: metav1.ObjectMeta{Name: serviceName, Namespace: namespace}, - Status: ibmcloudv1beta1.ServiceStatus{ + Status: ibmcloudv1.ServiceStatus{ Plan: "Lite", ServiceClass: "service-name", InstanceID: "myinstanceid", }, - Spec: ibmcloudv1beta1.ServiceSpec{ + Spec: ibmcloudv1.ServiceSpec{ Plan: "Lite", ServiceClass: "service-name", }, }, } aliasObjects := []runtime.Object{ - &ibmcloudv1beta1.Service{ + &ibmcloudv1.Service{ ObjectMeta: metav1.ObjectMeta{Name: serviceName, Namespace: namespace}, - Status: ibmcloudv1beta1.ServiceStatus{ + Status: ibmcloudv1.ServiceStatus{ Plan: aliasPlan, ServiceClass: "service-name", InstanceID: "myinstanceid", }, - Spec: ibmcloudv1beta1.ServiceSpec{ + Spec: ibmcloudv1.ServiceSpec{ Plan: aliasPlan, ServiceClass: "service-name", }, @@ -1531,7 +1560,7 @@ func TestServiceVerifyExists(t *testing.T) { Log: testLogger(t), Scheme: scheme, - GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1beta1.Service) (*ibmcloud.Info, error) { + GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1.Service) (*ibmcloud.Info, error) { return &ibmcloud.Info{}, nil }, GetResourceServiceInstanceState: func(session *session.Session, resourceGroupID, servicePlanID, externalName, instanceID string) (state string, err error) { @@ -1547,14 +1576,14 @@ func TestServiceVerifyExists(t *testing.T) { RequeueAfter: config.Get().SyncPeriod, }, result) assert.NoError(t, err) - assert.Equal(t, &ibmcloudv1beta1.Service{ - TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + assert.Equal(t, &ibmcloudv1.Service{ + TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1"}, ObjectMeta: metav1.ObjectMeta{ Name: serviceName, Namespace: namespace, Finalizers: []string{serviceFinalizer}, }, - Status: ibmcloudv1beta1.ServiceStatus{ + Status: ibmcloudv1.ServiceStatus{ State: "state", Message: "state", Plan: "Lite", @@ -1562,7 +1591,7 @@ func TestServiceVerifyExists(t *testing.T) { InstanceID: "myinstanceid", DashboardURL: "https://cloud.ibm.com/services/service-name/myinstanceid", }, - Spec: ibmcloudv1beta1.ServiceSpec{ + Spec: ibmcloudv1.ServiceSpec{ Plan: "Lite", ServiceClass: "service-name", }, @@ -1579,7 +1608,7 @@ func TestServiceVerifyExists(t *testing.T) { Log: testLogger(t), Scheme: scheme, - GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1beta1.Service) (*ibmcloud.Info, error) { + GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1.Service) (*ibmcloud.Info, error) { return &ibmcloud.Info{}, nil }, GetResourceServiceInstanceState: func(session *session.Session, resourceGroupID, servicePlanID, externalName, instanceID string) (state string, err error) { @@ -1600,14 +1629,14 @@ func TestServiceVerifyExists(t *testing.T) { RequeueAfter: config.Get().SyncPeriod, }, result) assert.NoError(t, err) - assert.Equal(t, &ibmcloudv1beta1.Service{ - TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + assert.Equal(t, &ibmcloudv1.Service{ + TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1"}, ObjectMeta: metav1.ObjectMeta{ Name: serviceName, Namespace: namespace, Finalizers: []string{serviceFinalizer}, }, - Status: ibmcloudv1beta1.ServiceStatus{ + Status: ibmcloudv1.ServiceStatus{ State: "state", Message: "state", Plan: "Lite", @@ -1615,7 +1644,7 @@ func TestServiceVerifyExists(t *testing.T) { InstanceID: "id", DashboardURL: "https://cloud.ibm.com/services/service-name/id", }, - Spec: ibmcloudv1beta1.ServiceSpec{ + Spec: ibmcloudv1.ServiceSpec{ Plan: "Lite", ServiceClass: "service-name", }, @@ -1632,21 +1661,21 @@ func TestServiceVerifyExists(t *testing.T) { RequeueAfter: config.Get().SyncPeriod, }, result) assert.NoError(t, err) - assert.Equal(t, &ibmcloudv1beta1.Service{ - TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + assert.Equal(t, &ibmcloudv1.Service{ + TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1"}, ObjectMeta: metav1.ObjectMeta{ Name: serviceName, Namespace: namespace, Finalizers: []string{serviceFinalizer}, }, - Status: ibmcloudv1beta1.ServiceStatus{ + Status: ibmcloudv1.ServiceStatus{ State: serviceStateFailed, Message: "failed", Plan: "Lite", ServiceClass: "service-name", InstanceID: inProgress, }, - Spec: ibmcloudv1beta1.ServiceSpec{ + Spec: ibmcloudv1.ServiceSpec{ Plan: "Lite", ServiceClass: "service-name", }, @@ -1663,7 +1692,7 @@ func TestServiceVerifyExists(t *testing.T) { Log: testLogger(t), Scheme: scheme, - GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1beta1.Service) (*ibmcloud.Info, error) { + GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1.Service) (*ibmcloud.Info, error) { return &ibmcloud.Info{}, nil }, GetResourceServiceInstanceState: func(session *session.Session, resourceGroupID, servicePlanID, externalName, instanceID string) (state string, err error) { @@ -1676,21 +1705,21 @@ func TestServiceVerifyExists(t *testing.T) { }) assert.Equal(t, ctrl.Result{}, result) assert.EqualError(t, err, "failed") - assert.Equal(t, &ibmcloudv1beta1.Service{ - TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + assert.Equal(t, &ibmcloudv1.Service{ + TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1"}, ObjectMeta: metav1.ObjectMeta{ Name: serviceName, Namespace: namespace, Finalizers: []string{serviceFinalizer}, }, - Status: ibmcloudv1beta1.ServiceStatus{ + Status: ibmcloudv1.ServiceStatus{ State: "", Message: "", Plan: "Lite", ServiceClass: "service-name", InstanceID: inProgress, }, - Spec: ibmcloudv1beta1.ServiceSpec{ + Spec: ibmcloudv1.ServiceSpec{ Plan: "Lite", ServiceClass: "service-name", }, @@ -1706,7 +1735,7 @@ func TestServiceVerifyExists(t *testing.T) { Log: testLogger(t), Scheme: scheme, - GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1beta1.Service) (*ibmcloud.Info, error) { + GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1.Service) (*ibmcloud.Info, error) { return &ibmcloud.Info{}, nil }, GetResourceServiceInstanceState: func(session *session.Session, resourceGroupID, servicePlanID, externalName, instanceID string) (state string, err error) { @@ -1722,21 +1751,21 @@ func TestServiceVerifyExists(t *testing.T) { RequeueAfter: config.Get().SyncPeriod, }, result) assert.NoError(t, err) - assert.Equal(t, &ibmcloudv1beta1.Service{ - TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + assert.Equal(t, &ibmcloudv1.Service{ + TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1"}, ObjectMeta: metav1.ObjectMeta{ Name: serviceName, Namespace: namespace, Finalizers: []string{serviceFinalizer}, }, - Status: ibmcloudv1beta1.ServiceStatus{ + Status: ibmcloudv1.ServiceStatus{ State: serviceStatePending, Message: "aliased service instance no longer exists", Plan: aliasPlan, ServiceClass: "service-name", InstanceID: "", }, - Spec: ibmcloudv1beta1.ServiceSpec{ + Spec: ibmcloudv1.ServiceSpec{ Plan: aliasPlan, ServiceClass: "service-name", }, @@ -1752,7 +1781,7 @@ func TestServiceVerifyExists(t *testing.T) { Log: testLogger(t), Scheme: scheme, - GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1beta1.Service) (*ibmcloud.Info, error) { + GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1.Service) (*ibmcloud.Info, error) { return &ibmcloud.Info{}, nil }, GetResourceServiceInstanceState: func(session *session.Session, resourceGroupID, servicePlanID, externalName, instanceID string) (state string, err error) { @@ -1768,21 +1797,21 @@ func TestServiceVerifyExists(t *testing.T) { RequeueAfter: config.Get().SyncPeriod, }, result) assert.NoError(t, err) - assert.Equal(t, &ibmcloudv1beta1.Service{ - TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + assert.Equal(t, &ibmcloudv1.Service{ + TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1"}, ObjectMeta: metav1.ObjectMeta{ Name: serviceName, Namespace: namespace, Finalizers: []string{serviceFinalizer}, }, - Status: ibmcloudv1beta1.ServiceStatus{ + Status: ibmcloudv1.ServiceStatus{ State: serviceStatePending, Message: "failed", Plan: "Lite", ServiceClass: "service-name", InstanceID: "myinstanceid", }, - Spec: ibmcloudv1beta1.ServiceSpec{ + Spec: ibmcloudv1.ServiceSpec{ Plan: "Lite", ServiceClass: "service-name", }, @@ -1798,14 +1827,14 @@ func TestServiceUpdateTagsOrParamsFailed(t *testing.T) { ) scheme := schemas(t) objects := []runtime.Object{ - &ibmcloudv1beta1.Service{ + &ibmcloudv1.Service{ ObjectMeta: metav1.ObjectMeta{Name: serviceName, Namespace: namespace}, - Status: ibmcloudv1beta1.ServiceStatus{ + Status: ibmcloudv1.ServiceStatus{ Plan: "Lite", ServiceClass: "service-name", InstanceID: "myinstanceid", }, - Spec: ibmcloudv1beta1.ServiceSpec{ + Spec: ibmcloudv1.ServiceSpec{ Plan: "Lite", ServiceClass: "service-name", Tags: []string{"somethingNew"}, @@ -1821,7 +1850,7 @@ func TestServiceUpdateTagsOrParamsFailed(t *testing.T) { Log: testLogger(t), Scheme: scheme, - GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1beta1.Service) (*ibmcloud.Info, error) { + GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1.Service) (*ibmcloud.Info, error) { return &ibmcloud.Info{}, nil }, GetResourceServiceInstanceState: func(session *session.Session, resourceGroupID, servicePlanID, externalName, instanceID string) (state string, err error) { @@ -1840,21 +1869,21 @@ func TestServiceUpdateTagsOrParamsFailed(t *testing.T) { RequeueAfter: config.Get().SyncPeriod, }, result) assert.NoError(t, err) - assert.Equal(t, &ibmcloudv1beta1.Service{ - TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1beta1"}, + assert.Equal(t, &ibmcloudv1.Service{ + TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1"}, ObjectMeta: metav1.ObjectMeta{ Name: serviceName, Namespace: namespace, Finalizers: []string{serviceFinalizer}, }, - Status: ibmcloudv1beta1.ServiceStatus{ + Status: ibmcloudv1.ServiceStatus{ State: serviceStateFailed, Message: "failed", Plan: "Lite", ServiceClass: "service-name", InstanceID: "myinstanceid", }, - Spec: ibmcloudv1beta1.ServiceSpec{ + Spec: ibmcloudv1.ServiceSpec{ Plan: "Lite", ServiceClass: "service-name", Tags: []string{"somethingNew"}, @@ -1870,30 +1899,30 @@ func TestSpecChanged(t *testing.T) { ) for _, tc := range []struct { description string - instance ibmcloudv1beta1.Service + instance ibmcloudv1.Service expectChanged bool }{ { description: "empty object", - instance: ibmcloudv1beta1.Service{}, + instance: ibmcloudv1.Service{}, expectChanged: false, }, { description: "missing status plan", - instance: ibmcloudv1beta1.Service{ - Spec: ibmcloudv1beta1.ServiceSpec{ExternalName: something}, - Status: ibmcloudv1beta1.ServiceStatus{ExternalName: something}, + instance: ibmcloudv1.Service{ + Spec: ibmcloudv1.ServiceSpec{ExternalName: something}, + Status: ibmcloudv1.ServiceStatus{ExternalName: something}, }, expectChanged: false, }, { description: "mismatched external name", - instance: ibmcloudv1beta1.Service{ - Spec: ibmcloudv1beta1.ServiceSpec{ + instance: ibmcloudv1.Service{ + Spec: ibmcloudv1.ServiceSpec{ Plan: something, ExternalName: something, }, - Status: ibmcloudv1beta1.ServiceStatus{ + Status: ibmcloudv1.ServiceStatus{ Plan: something, ExternalName: somethingElse, }, @@ -1902,11 +1931,11 @@ func TestSpecChanged(t *testing.T) { }, { description: "mismatched plan", - instance: ibmcloudv1beta1.Service{ - Spec: ibmcloudv1beta1.ServiceSpec{ + instance: ibmcloudv1.Service{ + Spec: ibmcloudv1.ServiceSpec{ Plan: something, }, - Status: ibmcloudv1beta1.ServiceStatus{ + Status: ibmcloudv1.ServiceStatus{ Plan: somethingElse, }, }, @@ -1914,12 +1943,12 @@ func TestSpecChanged(t *testing.T) { }, { description: "mismatched service class", - instance: ibmcloudv1beta1.Service{ - Spec: ibmcloudv1beta1.ServiceSpec{ + instance: ibmcloudv1.Service{ + Spec: ibmcloudv1.ServiceSpec{ Plan: something, ServiceClass: something, }, - Status: ibmcloudv1beta1.ServiceStatus{ + Status: ibmcloudv1.ServiceStatus{ Plan: something, ServiceClass: somethingElse, }, @@ -1928,12 +1957,12 @@ func TestSpecChanged(t *testing.T) { }, { description: "mismatched service class type", - instance: ibmcloudv1beta1.Service{ - Spec: ibmcloudv1beta1.ServiceSpec{ + instance: ibmcloudv1.Service{ + Spec: ibmcloudv1.ServiceSpec{ Plan: something, ServiceClassType: something, }, - Status: ibmcloudv1beta1.ServiceStatus{ + Status: ibmcloudv1.ServiceStatus{ Plan: something, ServiceClassType: somethingElse, }, @@ -1942,28 +1971,28 @@ func TestSpecChanged(t *testing.T) { }, { description: "mismatched context", - instance: ibmcloudv1beta1.Service{ - Spec: ibmcloudv1beta1.ServiceSpec{ + instance: ibmcloudv1.Service{ + Spec: ibmcloudv1.ServiceSpec{ Plan: something, - Context: ibmcloudv1beta1.ResourceContext{User: something}, + Context: ibmcloudv1.ResourceContext{User: something}, }, - Status: ibmcloudv1beta1.ServiceStatus{ + Status: ibmcloudv1.ServiceStatus{ Plan: something, - Context: ibmcloudv1beta1.ResourceContext{User: somethingElse}, + Context: ibmcloudv1.ResourceContext{User: somethingElse}, }, }, expectChanged: true, }, { description: "matching contexts", - instance: ibmcloudv1beta1.Service{ - Spec: ibmcloudv1beta1.ServiceSpec{ + instance: ibmcloudv1.Service{ + Spec: ibmcloudv1.ServiceSpec{ Plan: something, - Context: ibmcloudv1beta1.ResourceContext{User: somethingElse}, + Context: ibmcloudv1.ResourceContext{User: somethingElse}, }, - Status: ibmcloudv1beta1.ServiceStatus{ + Status: ibmcloudv1.ServiceStatus{ Plan: something, - Context: ibmcloudv1beta1.ResourceContext{User: somethingElse}, + Context: ibmcloudv1.ResourceContext{User: somethingElse}, }, }, expectChanged: false, @@ -1979,28 +2008,28 @@ func TestDeleteServiceFinalizer(t *testing.T) { t.Parallel() t.Run("no finalizer found", func(t *testing.T) { finalizers := []string(nil) - assert.Equal(t, finalizers, deleteServiceFinalizer(&ibmcloudv1beta1.Service{ + assert.Equal(t, finalizers, deleteServiceFinalizer(&ibmcloudv1.Service{ ObjectMeta: metav1.ObjectMeta{Finalizers: finalizers}, })) }) t.Run("one other finalizer found", func(t *testing.T) { finalizers := []string{"not-service-finalizer"} - assert.Equal(t, finalizers, deleteServiceFinalizer(&ibmcloudv1beta1.Service{ + assert.Equal(t, finalizers, deleteServiceFinalizer(&ibmcloudv1.Service{ ObjectMeta: metav1.ObjectMeta{Finalizers: finalizers}, })) }) t.Run("one finalizer found", func(t *testing.T) { finalizers := []string{serviceFinalizer} - assert.Equal(t, []string(nil), deleteServiceFinalizer(&ibmcloudv1beta1.Service{ + assert.Equal(t, []string(nil), deleteServiceFinalizer(&ibmcloudv1.Service{ ObjectMeta: metav1.ObjectMeta{Finalizers: finalizers}, })) }) t.Run("multiple finalizers found", func(t *testing.T) { finalizers := []string{serviceFinalizer, serviceFinalizer} - assert.Equal(t, []string(nil), deleteServiceFinalizer(&ibmcloudv1beta1.Service{ + assert.Equal(t, []string(nil), deleteServiceFinalizer(&ibmcloudv1.Service{ ObjectMeta: metav1.ObjectMeta{Finalizers: finalizers}, })) }) @@ -2010,40 +2039,40 @@ func TestServiceParamToJSON(t *testing.T) { t.Parallel() for _, tc := range []struct { description string - param ibmcloudv1beta1.Param + param ibmcloudv1.Param expectJSON map[string]interface{} expectErr string }{ { description: "error: value and valueFrom both set", - param: ibmcloudv1beta1.Param{ + param: ibmcloudv1.Param{ Name: "myvalue", - Value: &ibmcloudv1beta1.ParamValue{}, - ValueFrom: &ibmcloudv1beta1.ParamSource{}, + Value: &ibmcloudv1.ParamValue{}, + ValueFrom: &ibmcloudv1.ParamSource{}, }, expectErr: "Value and ValueFrom properties are mutually exclusive (for myvalue variable)", }, { description: "empty valueFrom error", - param: ibmcloudv1beta1.Param{ + param: ibmcloudv1.Param{ Name: "myvalue", - ValueFrom: &ibmcloudv1beta1.ParamSource{}, + ValueFrom: &ibmcloudv1.ParamSource{}, }, expectErr: "Missing secretKeyRef or configMapKeyRef", }, { description: "empty value error", - param: ibmcloudv1beta1.Param{ + param: ibmcloudv1.Param{ Name: "myvalue", - Value: &ibmcloudv1beta1.ParamValue{}, + Value: &ibmcloudv1.ParamValue{}, }, expectErr: "unexpected end of JSON input", }, { description: "value happy path", - param: ibmcloudv1beta1.Param{ + param: ibmcloudv1.Param{ Name: "myvalue", - Value: &ibmcloudv1beta1.ParamValue{ + Value: &ibmcloudv1.ParamValue{ RawMessage: json.RawMessage(`{"hello": true, "world": {"!": 1}}`), }, }, @@ -2056,7 +2085,7 @@ func TestServiceParamToJSON(t *testing.T) { }, { description: "neither value nor valueFrom set", - param: ibmcloudv1beta1.Param{Name: "myvalue"}, + param: ibmcloudv1.Param{Name: "myvalue"}, expectJSON: nil, expectErr: "", }, @@ -2107,7 +2136,7 @@ func TestServiceParamValueToJSON(t *testing.T) { for _, tc := range []struct { description string - valueFrom ibmcloudv1beta1.ParamSource + valueFrom ibmcloudv1.ParamSource expectJSON interface{} expectErr string }{ @@ -2117,7 +2146,7 @@ func TestServiceParamValueToJSON(t *testing.T) { }, { description: "secret ref success", - valueFrom: ibmcloudv1beta1.ParamSource{ + valueFrom: ibmcloudv1.ParamSource{ SecretKeyRef: &corev1.SecretKeySelector{ LocalObjectReference: corev1.LocalObjectReference{ Name: secretName, @@ -2129,7 +2158,7 @@ func TestServiceParamValueToJSON(t *testing.T) { }, { description: "secret ref name failure", - valueFrom: ibmcloudv1beta1.ParamSource{ + valueFrom: ibmcloudv1.ParamSource{ SecretKeyRef: &corev1.SecretKeySelector{ LocalObjectReference: corev1.LocalObjectReference{ Name: "wrong-secret-name", @@ -2141,7 +2170,7 @@ func TestServiceParamValueToJSON(t *testing.T) { }, { description: "secret ref key failure", - valueFrom: ibmcloudv1beta1.ParamSource{ + valueFrom: ibmcloudv1.ParamSource{ SecretKeyRef: &corev1.SecretKeySelector{ LocalObjectReference: corev1.LocalObjectReference{ Name: secretName, @@ -2153,7 +2182,7 @@ func TestServiceParamValueToJSON(t *testing.T) { }, { description: "configmap ref success", - valueFrom: ibmcloudv1beta1.ParamSource{ + valueFrom: ibmcloudv1.ParamSource{ ConfigMapKeyRef: &corev1.ConfigMapKeySelector{ LocalObjectReference: corev1.LocalObjectReference{ Name: configMapName, @@ -2165,7 +2194,7 @@ func TestServiceParamValueToJSON(t *testing.T) { }, { description: "configmap ref name failure", - valueFrom: ibmcloudv1beta1.ParamSource{ + valueFrom: ibmcloudv1.ParamSource{ ConfigMapKeyRef: &corev1.ConfigMapKeySelector{ LocalObjectReference: corev1.LocalObjectReference{ Name: "wrong-configmap-name", @@ -2177,7 +2206,7 @@ func TestServiceParamValueToJSON(t *testing.T) { }, { description: "configmap ref key failure", - valueFrom: ibmcloudv1beta1.ParamSource{ + valueFrom: ibmcloudv1.ParamSource{ ConfigMapKeyRef: &corev1.ConfigMapKeySelector{ LocalObjectReference: corev1.LocalObjectReference{ Name: configMapName, @@ -2214,14 +2243,14 @@ func TestServiceUpdateStatusFailed(t *testing.T) { serviceName = "myservice" ) scheme := schemas(t) - instance := &ibmcloudv1beta1.Service{ + instance := &ibmcloudv1.Service{ ObjectMeta: metav1.ObjectMeta{Name: serviceName, Namespace: namespace}, - Status: ibmcloudv1beta1.ServiceStatus{ + Status: ibmcloudv1.ServiceStatus{ Plan: "Lite", ServiceClass: "service-name", InstanceID: "myinstanceid", }, - Spec: ibmcloudv1beta1.ServiceSpec{ + Spec: ibmcloudv1.ServiceSpec{ Plan: "Lite", ServiceClass: "service-name", }, @@ -2240,15 +2269,15 @@ func TestServiceUpdateStatusFailed(t *testing.T) { }, } - result, err := r.updateStatus(nil, r.Log, instance, ibmcloudv1beta1.ResourceContext{}, "myinstanceid", "state", "") + result, err := r.updateStatus(nil, r.Log, instance, ibmcloudv1.ResourceContext{}, "myinstanceid", "state", "") assert.Equal(t, ctrl.Result{}, result) assert.EqualError(t, err, "failed") - assert.Equal(t, &ibmcloudv1beta1.Service{ + assert.Equal(t, &ibmcloudv1.Service{ ObjectMeta: metav1.ObjectMeta{ Name: serviceName, Namespace: namespace, }, - Status: ibmcloudv1beta1.ServiceStatus{ + Status: ibmcloudv1.ServiceStatus{ State: "state", Message: "state", Plan: "Lite", @@ -2256,7 +2285,7 @@ func TestServiceUpdateStatusFailed(t *testing.T) { InstanceID: "myinstanceid", DashboardURL: "https://cloud.ibm.com/services/service-name/myinstanceid", }, - Spec: ibmcloudv1beta1.ServiceSpec{ + Spec: ibmcloudv1.ServiceSpec{ Plan: "Lite", ServiceClass: "service-name", }, @@ -2270,14 +2299,14 @@ func TestServiceUpdateStatusError(t *testing.T) { serviceName = "myservice" ) scheme := schemas(t) - instance := &ibmcloudv1beta1.Service{ + instance := &ibmcloudv1.Service{ ObjectMeta: metav1.ObjectMeta{Name: serviceName, Namespace: namespace}, - Status: ibmcloudv1beta1.ServiceStatus{ + Status: ibmcloudv1.ServiceStatus{ Plan: "Lite", ServiceClass: "service-name", InstanceID: "myinstanceid", }, - Spec: ibmcloudv1beta1.ServiceSpec{ + Spec: ibmcloudv1.ServiceSpec{ Plan: "Lite", ServiceClass: "service-name", }, @@ -2314,19 +2343,19 @@ func TestServiceUpdateStatusError(t *testing.T) { result, err := r.updateStatusError(instance, "state", fmt.Errorf("some error")) assert.Equal(t, ctrl.Result{}, result) assert.EqualError(t, err, "failed") - assert.Equal(t, &ibmcloudv1beta1.Service{ + assert.Equal(t, &ibmcloudv1.Service{ ObjectMeta: metav1.ObjectMeta{ Name: serviceName, Namespace: namespace, }, - Status: ibmcloudv1beta1.ServiceStatus{ + Status: ibmcloudv1.ServiceStatus{ State: "state", Message: "some error", Plan: "Lite", ServiceClass: "service-name", InstanceID: "myinstanceid", }, - Spec: ibmcloudv1beta1.ServiceSpec{ + Spec: ibmcloudv1.ServiceSpec{ Plan: "Lite", ServiceClass: "service-name", }, diff --git a/controllers/suite_config_test.go b/controllers/suite_config_test.go index 79942fc0..26d0a33b 100644 --- a/controllers/suite_config_test.go +++ b/controllers/suite_config_test.go @@ -15,14 +15,13 @@ import ( "github.com/ghodss/yaml" "github.com/go-logr/logr" "github.com/go-logr/zapr" - ibmcloudv1beta1 "github.com/ibm/cloud-operators/api/v1beta1" + ibmcloudv1 "github.com/ibm/cloud-operators/api/v1" "github.com/ibm/cloud-operators/internal/config" "github.com/pkg/errors" "github.com/stretchr/testify/require" "go.uber.org/zap" "go.uber.org/zap/zaptest" corev1 "k8s.io/api/core/v1" - v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" ) @@ -47,7 +46,7 @@ func setup() error { func setupConfigs() error { ctx := context.Background() - err := k8sClient.Create(ctx, &v1.ConfigMap{ + err := k8sClient.Create(ctx, &corev1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ Name: "ibmcloud-operator-defaults", Namespace: testNamespace, @@ -64,7 +63,7 @@ func setupConfigs() error { return err } - err = k8sClient.Create(ctx, &v1.Secret{ + err = k8sClient.Create(ctx, &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: "ibmcloud-operator-secret", Namespace: testNamespace, @@ -77,7 +76,7 @@ func setupConfigs() error { return err } - return k8sClient.Create(ctx, &v1.Secret{ + return k8sClient.Create(ctx, &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: "ibmcloud-operator-tokens", Namespace: testNamespace, @@ -159,7 +158,7 @@ func getAuthTokens(sess *session.Session) (uaaAccessToken, uaaRefreshToken strin } func schemas(t *testing.T) *runtime.Scheme { - scheme, err := ibmcloudv1beta1.SchemeBuilder.Build() + scheme, err := ibmcloudv1.SchemeBuilder.Build() require.NoError(t, err) require.NoError(t, corev1.SchemeBuilder.AddToScheme(scheme)) return scheme diff --git a/controllers/suite_test.go b/controllers/suite_test.go index ffb1dfd0..84c3b10c 100644 --- a/controllers/suite_test.go +++ b/controllers/suite_test.go @@ -46,7 +46,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/envtest" runtimeZap "sigs.k8s.io/controller-runtime/pkg/log/zap" - ibmcloudv1beta1 "github.com/ibm/cloud-operators/api/v1beta1" + ibmcloudv1 "github.com/ibm/cloud-operators/api/v1" // +kubebuilder:scaffold:imports ) @@ -128,7 +128,7 @@ func mainSetup(ctx context.Context) error { return err } - err = ibmcloudv1beta1.AddToScheme(scheme.Scheme) + err = ibmcloudv1.AddToScheme(scheme.Scheme) if err != nil { return err } diff --git a/controllers/testdata/cos.yaml b/controllers/testdata/cos.yaml index bd9b06db..b11a5e11 100644 --- a/controllers/testdata/cos.yaml +++ b/controllers/testdata/cos.yaml @@ -1,4 +1,4 @@ -apiVersion: ibmcloud.ibm.com/v1beta1 +apiVersion: ibmcloud.ibm.com/v1 kind: Service metadata: name: mycos diff --git a/controllers/testdata/messagehub.yaml b/controllers/testdata/messagehub.yaml index 1d721456..e17352b6 100644 --- a/controllers/testdata/messagehub.yaml +++ b/controllers/testdata/messagehub.yaml @@ -1,4 +1,4 @@ -apiVersion: ibmcloud.ibm.com/v1beta1 +apiVersion: ibmcloud.ibm.com/v1 kind: Service metadata: name: mymessagehub diff --git a/controllers/testdata/translator-2.yaml b/controllers/testdata/translator-2.yaml index a11cc864..cd6c8365 100644 --- a/controllers/testdata/translator-2.yaml +++ b/controllers/testdata/translator-2.yaml @@ -1,4 +1,4 @@ -apiVersion: ibmcloud.ibm.com/v1beta1 +apiVersion: ibmcloud.ibm.com/v1 kind: Service metadata: name: test-translator-2 diff --git a/controllers/testdata/translator-alias.yaml b/controllers/testdata/translator-alias.yaml index f2ceb76c..00f46b1e 100644 --- a/controllers/testdata/translator-alias.yaml +++ b/controllers/testdata/translator-alias.yaml @@ -1,4 +1,4 @@ -apiVersion: ibmcloud.ibm.com/v1beta1 +apiVersion: ibmcloud.ibm.com/v1 kind: Service metadata: name: mytranslator diff --git a/controllers/testdata/translator-binding.yaml b/controllers/testdata/translator-binding.yaml index 48570dc8..680686da 100644 --- a/controllers/testdata/translator-binding.yaml +++ b/controllers/testdata/translator-binding.yaml @@ -1,4 +1,4 @@ -apiVersion: ibmcloud.ibm.com/v1beta1 +apiVersion: ibmcloud.ibm.com/v1 kind: Binding metadata: name: binding-translator diff --git a/controllers/testdata/translator-v1beta1.yaml b/controllers/testdata/translator-v1beta1.yaml new file mode 100644 index 00000000..84b2f33d --- /dev/null +++ b/controllers/testdata/translator-v1beta1.yaml @@ -0,0 +1,7 @@ +apiVersion: ibmcloud.ibm.com/v1beta1 +kind: Service +metadata: + name: test-translator-1 +spec: + plan: standard + serviceClass: language-translator diff --git a/controllers/testdata/translator-wrong-plan.yaml b/controllers/testdata/translator-wrong-plan.yaml index 59f4eca9..68958670 100644 --- a/controllers/testdata/translator-wrong-plan.yaml +++ b/controllers/testdata/translator-wrong-plan.yaml @@ -1,4 +1,4 @@ -apiVersion: ibmcloud.ibm.com/v1beta1 +apiVersion: ibmcloud.ibm.com/v1 kind: Service metadata: name: test-translator-2 diff --git a/controllers/testdata/translator.yaml b/controllers/testdata/translator.yaml index 84b2f33d..d4f26a6a 100644 --- a/controllers/testdata/translator.yaml +++ b/controllers/testdata/translator.yaml @@ -1,4 +1,4 @@ -apiVersion: ibmcloud.ibm.com/v1beta1 +apiVersion: ibmcloud.ibm.com/v1 kind: Service metadata: name: test-translator-1 diff --git a/docs/user-guide.md b/docs/user-guide.md index f38a0ef0..9154ca35 100644 --- a/docs/user-guide.md +++ b/docs/user-guide.md @@ -10,7 +10,7 @@ IBM Cloud public instances. To create an instance of an IBM public cloud service, create the following custom resource, `service.yaml`: ```yaml -apiVersion: ibmcloud.ibm.com/v1alpha1 +apiVersion: ibmcloud.ibm.com/v1 kind: Service metadata: name: myservice @@ -58,7 +58,7 @@ Failed: No deployment found for service plan at location . Vali Here's an example of how to set the region to `global`: ```yaml -apiVersion: ibmcloud.ibm.com/v1alpha1 +apiVersion: ibmcloud.ibm.com/v1 kind: Service metadata: name: mycos @@ -96,7 +96,7 @@ instance and the reserved plan name `Alias`. For example, if the service `mytran use the following custom resource to link it: ```yaml -apiVersion: ibmcloud.ibm.com/v1alpha1 +apiVersion: ibmcloud.ibm.com/v1 kind: Service metadata: name: mytranslator @@ -111,7 +111,7 @@ The following would also work: ```yaml -apiVersion: ibmcloud.ibm.com/v1alpha1 +apiVersion: ibmcloud.ibm.com/v1 kind: Service metadata: name: mytranslator-alias @@ -178,7 +178,7 @@ Let's assume you want to use the first instance; then, simply copy the ID value `ibmcloud.ibm.com/instanceId` annotation. The resource definition for this example is then: ```yaml -apiVersion: ibmcloud.ibm.com/v1alpha1 +apiVersion: ibmcloud.ibm.com/v1 kind: Service metadata: name: mytranslator @@ -217,7 +217,7 @@ then deleting the resource will not delete the service instance. You can bind to a service with name `myservice` using the following custom resource: ```yaml -apiVersion: ibmcloud.ibm.com/v1alpha1 +apiVersion: ibmcloud.ibm.com/v1 kind: Binding metadata: name: mybinding @@ -248,7 +248,7 @@ In this case, the binding needs to be deleted manually and will not be deleted w Bindings can also be created by specifying Roles and ServiceIds. The following example shows a binding yaml with a specific Role: ```yaml -apiVersion: ibmcloud.ibm.com/v1alpha1 +apiVersion: ibmcloud.ibm.com/v1 kind: Binding metadata: name: binding-myes @@ -262,7 +262,7 @@ The strings allowed for Roles depend on the service for which credentials are be ServiceIds can be specified by passing a parameter in the binding yaml: ```yaml -apiVersion: ibmcloud.ibm.com/v1alpha1 +apiVersion: ibmcloud.ibm.com/v1 kind: Binding metadata: name: binding-translator @@ -286,8 +286,8 @@ ibmcloud iam service-id When many bindings are needed on the same service, it is possible to link to the same set of credentials on the service instance, instead of creating new ones. -``` -apiVersion: ibmcloud.ibm.com/v1alpha1 +```yaml +apiVersion: ibmcloud.ibm.com/v1 kind: Binding metadata: name: binding-translator-alias diff --git a/internal/cmd/fixcrd/main.go b/internal/cmd/fixcrd/main.go index 2c82b1e9..4e05b064 100644 --- a/internal/cmd/fixcrd/main.go +++ b/internal/cmd/fixcrd/main.go @@ -149,7 +149,7 @@ func getVersions(v interface{}) []string { strA += "~" } if isDigits(strings.TrimPrefix(strB, "v")) { - strA += "~" + strB += "~" } return strA < strB }) diff --git a/internal/ibmcloud/ibmcloud.go b/internal/ibmcloud/ibmcloud.go index 0dce16ef..1c954144 100644 --- a/internal/ibmcloud/ibmcloud.go +++ b/internal/ibmcloud/ibmcloud.go @@ -16,7 +16,7 @@ import ( "github.com/IBM-Cloud/bluemix-go/models" "github.com/IBM-Cloud/bluemix-go/session" "github.com/go-logr/logr" - ibmcloudv1beta1 "github.com/ibm/cloud-operators/api/v1beta1" + ibmcloudv1 "github.com/ibm/cloud-operators/api/v1" "github.com/ibm/cloud-operators/internal/config" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" @@ -55,11 +55,11 @@ type Info struct { ServicePlanID string TargetCrn string Token string - Context ibmcloudv1beta1.ResourceContext + Context ibmcloudv1.ResourceContext } // GetInfo initializes sessions and sets up a struct to faciliate making calls to bx -func GetInfo(logt logr.Logger, r client.Client, instance *ibmcloudv1beta1.Service) (*Info, error) { +func GetInfo(logt logr.Logger, r client.Client, instance *ibmcloudv1.Service) (*Info, error) { bxConfig, err := getBxConfig(logt, r, instance) if err != nil { return nil, err @@ -73,7 +73,7 @@ func GetInfo(logt logr.Logger, r client.Client, instance *ibmcloudv1beta1.Servic return getInfoHelper(logt, r, &bxConfig, ibmCloudContext, instance) } -func getInfoHelper(logt logr.Logger, r client.Client, config *bluemix.Config, nctx ibmcloudv1beta1.ResourceContext, instance *ibmcloudv1beta1.Service) (*Info, error) { +func getInfoHelper(logt logr.Logger, r client.Client, config *bluemix.Config, nctx ibmcloudv1.ResourceContext, instance *ibmcloudv1.Service) (*Info, error) { servicename := instance.Spec.ServiceClass servicetype := instance.Spec.ServiceClassType serviceplan := instance.Spec.Plan @@ -229,7 +229,7 @@ func getInfoHelper(logt logr.Logger, r client.Client, config *bluemix.Config, nc }, nil } -func getBxConfig(logt logr.Logger, r client.Client, instance *ibmcloudv1beta1.Service) (bluemix.Config, error) { +func getBxConfig(logt logr.Logger, r client.Client, instance *ibmcloudv1.Service) (bluemix.Config, error) { secretName := seedSecret secretNameSpace := instance.ObjectMeta.Namespace @@ -258,9 +258,9 @@ func getBxConfig(logt logr.Logger, r client.Client, instance *ibmcloudv1beta1.Se return c, nil } -func getIBMCloudDefaultContext(logt logr.Logger, r client.Client, instance *ibmcloudv1beta1.Service) (ibmcloudv1beta1.ResourceContext, error) { +func getIBMCloudDefaultContext(logt logr.Logger, r client.Client, instance *ibmcloudv1.Service) (ibmcloudv1.ResourceContext, error) { // If the object already has the context set in its Status, then we don't read from the configmap - if !reflect.DeepEqual(instance.Status.Context, ibmcloudv1beta1.ResourceContext{}) { + if !reflect.DeepEqual(instance.Status.Context, ibmcloudv1.ResourceContext{}) { return instance.Status.Context, nil } @@ -271,14 +271,14 @@ func getIBMCloudDefaultContext(logt logr.Logger, r client.Client, instance *ibmc err := getConfigOrSecret(logt, r, cmNameSpace, cmName, cm) if err != nil { logt.Info("Unable to get IBM Cloud Operator configmap in namespace", cmNameSpace, err) - return ibmcloudv1beta1.ResourceContext{}, err + return ibmcloudv1.ResourceContext{}, err } ibmCloudContext := getIBMCloudContext(instance, cm) return ibmCloudContext, nil } -func getIamToken(logt logr.Logger, r client.Client, instance *ibmcloudv1beta1.Service) (string, string, string, string, error) { +func getIamToken(logt logr.Logger, r client.Client, instance *ibmcloudv1.Service) (string, string, string, string, error) { secretName := seedTokens secretNameSpace := instance.ObjectMeta.Namespace @@ -318,9 +318,9 @@ func getConfigOrSecret(logt logr.Logger, r client.Client, instanceNamespace stri return nil } -func getIBMCloudContext(instance *ibmcloudv1beta1.Service, cm *v1.ConfigMap) ibmcloudv1beta1.ResourceContext { - if (ibmcloudv1beta1.ResourceContext{}) == instance.Spec.Context { - newContext := ibmcloudv1beta1.ResourceContext{ +func getIBMCloudContext(instance *ibmcloudv1.Service, cm *v1.ConfigMap) ibmcloudv1.ResourceContext { + if (ibmcloudv1.ResourceContext{}) == instance.Spec.Context { + newContext := ibmcloudv1.ResourceContext{ Org: cm.Data["org"], Space: cm.Data["space"], Region: cm.Data["region"], diff --git a/main.go b/main.go index 3d4d714f..bd0afb96 100644 --- a/main.go +++ b/main.go @@ -34,6 +34,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/log/zap" + ibmcloudv1 "github.com/ibm/cloud-operators/api/v1" ibmcloudv1alpha1 "github.com/ibm/cloud-operators/api/v1alpha1" ibmcloudv1beta1 "github.com/ibm/cloud-operators/api/v1beta1" // +kubebuilder:scaffold:imports @@ -49,6 +50,7 @@ func init() { _ = ibmcloudv1alpha1.AddToScheme(scheme) _ = ibmcloudv1beta1.AddToScheme(scheme) + _ = ibmcloudv1.AddToScheme(scheme) // +kubebuilder:scaffold:scheme } From 6e798b8b1613572e4e74c5639993a3207b6569a1 Mon Sep 17 00:00:00 2001 From: John Starich Date: Mon, 21 Sep 2020 18:50:00 -0500 Subject: [PATCH 14/20] Set up both tests and main.go in one place + add tests to check missing deps (#208) Signed-off-by: Art Berger --- controllers/manager_setup.go | 80 +++++++++++++++++++++++++++++++ controllers/manager_setup_test.go | 51 ++++++++++++++++++++ controllers/suite_test.go | 57 +++------------------- main.go | 53 +------------------- 4 files changed, 139 insertions(+), 102 deletions(-) create mode 100644 controllers/manager_setup.go create mode 100644 controllers/manager_setup_test.go diff --git a/controllers/manager_setup.go b/controllers/manager_setup.go new file mode 100644 index 00000000..5fb93e79 --- /dev/null +++ b/controllers/manager_setup.go @@ -0,0 +1,80 @@ +package controllers + +import ( + "net/http" + + "github.com/ibm/cloud-operators/internal/ibmcloud" + "github.com/ibm/cloud-operators/internal/ibmcloud/auth" + "github.com/ibm/cloud-operators/internal/ibmcloud/cfservice" + "github.com/ibm/cloud-operators/internal/ibmcloud/iam" + "github.com/ibm/cloud-operators/internal/ibmcloud/resource" + "github.com/pkg/errors" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" +) + +// Controllers passes back references to set up controllers for test mocking purposes +type Controllers struct { + *BindingReconciler + *ServiceReconciler + *TokenReconciler +} + +func SetUpControllers(mgr ctrl.Manager) (*Controllers, error) { + c := setUpControllerDependencies(mgr) + if err := c.BindingReconciler.SetupWithManager(mgr); err != nil { + return nil, errors.Wrap(err, "Unable to setup binding controller") + } + if err := c.ServiceReconciler.SetupWithManager(mgr); err != nil { + return nil, errors.Wrap(err, "Unable to setup service controller") + } + if err := c.TokenReconciler.SetupWithManager(mgr); err != nil { + return nil, errors.Wrap(err, "Unable to setup token controller") + } + + // +kubebuilder:scaffold:builder + return c, nil +} + +func setUpControllerDependencies(mgr ctrl.Manager) *Controllers { + return &Controllers{ + BindingReconciler: &BindingReconciler{ + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controllers").WithName("Binding"), + Scheme: mgr.GetScheme(), + + CreateCFServiceKey: cfservice.CreateKey, + CreateResourceServiceKey: resource.CreateKey, + DeleteCFServiceKey: cfservice.DeleteKey, + DeleteResourceServiceKey: resource.DeleteKey, + GetCFServiceKeyCredentials: cfservice.GetKey, + GetIBMCloudInfo: ibmcloud.GetInfo, + GetResourceServiceKey: resource.GetKey, + GetServiceInstanceCRN: resource.GetServiceInstanceCRN, + GetServiceName: resource.GetServiceName, + GetServiceRoleCRN: iam.GetServiceRoleCRN, + SetControllerReference: controllerutil.SetControllerReference, + }, + ServiceReconciler: &ServiceReconciler{ + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controllers").WithName("Service"), + Scheme: mgr.GetScheme(), + + CreateCFServiceInstance: cfservice.CreateInstance, + CreateResourceServiceInstance: resource.CreateServiceInstance, + DeleteCFServiceInstance: cfservice.DeleteInstance, + DeleteResourceServiceInstance: resource.DeleteServiceInstance, + GetCFServiceInstance: cfservice.GetInstance, + GetIBMCloudInfo: ibmcloud.GetInfo, + GetResourceServiceAliasInstance: resource.GetServiceAliasInstance, + GetResourceServiceInstanceState: resource.GetServiceInstanceState, + UpdateResourceServiceInstance: resource.UpdateServiceInstance, + }, + TokenReconciler: &TokenReconciler{ + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controllers").WithName("Token"), + Scheme: mgr.GetScheme(), + Authenticate: auth.New(http.DefaultClient), + }, + } +} diff --git a/controllers/manager_setup_test.go b/controllers/manager_setup_test.go new file mode 100644 index 00000000..fa73ce18 --- /dev/null +++ b/controllers/manager_setup_test.go @@ -0,0 +1,51 @@ +package controllers + +import ( + "reflect" + "testing" + "unicode" +) + +func TestSetUpControllers(t *testing.T) { + if testing.Short() { + t.SkipNow() + } + + c := setUpControllerDependencies(k8sManager) + + assertNoNilFields(t, c) +} + +func assertNoNilFields(t *testing.T, v interface{}) { + assertNoNilFieldsReflect(t, reflect.ValueOf(v), "") + if t.Failed() { + t.Log("See the above failures for fields that must be filled in, inside controllers/manager_setup.go") + } +} + +func assertNoNilFieldsReflect(t *testing.T, v reflect.Value, name string) { + t.Helper() + switch v.Kind() { + case reflect.Ptr: + assertNoNilFieldsReflect(t, v.Elem(), name) + case reflect.Struct: + for i := 0; i < v.NumField(); i++ { + field := v.Field(i) + typeField := v.Type().Field(i) + if len(typeField.Name) > 0 && unicode.IsUpper([]rune(typeField.Name)[0]) { + assertNoNilFieldsReflect(t, field, joinFields(name, typeField.Name)) + } + } + default: + if v.IsZero() { + t.Errorf("Field %q is not set up properly for controllers", name) + } + } +} + +func joinFields(a, b string) string { + if a == "" { + return b + } + return a + "." + b +} diff --git a/controllers/suite_test.go b/controllers/suite_test.go index 84c3b10c..c9c5ac37 100644 --- a/controllers/suite_test.go +++ b/controllers/suite_test.go @@ -27,12 +27,7 @@ import ( "testing" "time" - "github.com/ibm/cloud-operators/internal/ibmcloud" "github.com/ibm/cloud-operators/internal/ibmcloud/auth" - "github.com/ibm/cloud-operators/internal/ibmcloud/cfservice" - "github.com/ibm/cloud-operators/internal/ibmcloud/iam" - "github.com/ibm/cloud-operators/internal/ibmcloud/resource" - "github.com/pkg/errors" "go.uber.org/zap" "go.uber.org/zap/zapcore" v1 "k8s.io/api/core/v1" @@ -42,7 +37,6 @@ import ( "k8s.io/client-go/rest" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/envtest" runtimeZap "sigs.k8s.io/controller-runtime/pkg/log/zap" @@ -140,56 +134,17 @@ func mainSetup(ctx context.Context) error { return err } - if err = (&BindingReconciler{ - Client: k8sManager.GetClient(), - Log: ctrl.Log.WithName("controllers").WithName("Binding"), - Scheme: k8sManager.GetScheme(), - - CreateCFServiceKey: cfservice.CreateKey, - CreateResourceServiceKey: resource.CreateKey, - DeleteCFServiceKey: cfservice.DeleteKey, - DeleteResourceServiceKey: resource.DeleteKey, - GetCFServiceKeyCredentials: cfservice.GetKey, - GetIBMCloudInfo: ibmcloud.GetInfo, - GetResourceServiceKey: resource.GetKey, - GetServiceInstanceCRN: resource.GetServiceInstanceCRN, - GetServiceName: resource.GetServiceName, - GetServiceRoleCRN: iam.GetServiceRoleCRN, - SetControllerReference: controllerutil.SetControllerReference, - }).SetupWithManager(k8sManager); err != nil { - return errors.Wrap(err, "Failed to set up binding controller") - } - if err = (&ServiceReconciler{ - Client: k8sManager.GetClient(), - Log: ctrl.Log.WithName("controllers").WithName("Service"), - Scheme: k8sManager.GetScheme(), - - CreateCFServiceInstance: cfservice.CreateInstance, - CreateResourceServiceInstance: resource.CreateServiceInstance, - DeleteResourceServiceInstance: resource.DeleteServiceInstance, - GetCFServiceInstance: cfservice.GetInstance, - GetIBMCloudInfo: ibmcloud.GetInfo, - GetResourceServiceAliasInstance: resource.GetServiceAliasInstance, - GetResourceServiceInstanceState: resource.GetServiceInstanceState, - UpdateResourceServiceInstance: resource.UpdateServiceInstance, - }).SetupWithManager(k8sManager); err != nil { - return errors.Wrap(err, "Failed to set up service controller") - } - tokenReconciler := &TokenReconciler{ - Client: k8sManager.GetClient(), - Log: ctrl.Log.WithName("controllers").WithName("Token"), - Scheme: k8sManager.GetScheme(), - Authenticate: auth.New(http.DefaultClient), + c, err := SetUpControllers(k8sManager) + if err != nil { + return err } + setTokenHTTPClient = func(tb testing.TB, authenticator auth.Authenticator) { - tokenReconciler.Authenticate = authenticator + c.TokenReconciler.Authenticate = authenticator tb.Cleanup(func() { - tokenReconciler.Authenticate = auth.New(http.DefaultClient) + c.TokenReconciler.Authenticate = auth.New(http.DefaultClient) }) } - if err = tokenReconciler.SetupWithManager(k8sManager); err != nil { - return errors.Wrap(err, "Failed to set up token controller") - } go func() { err = k8sManager.Start(ctx.Done()) diff --git a/main.go b/main.go index bd0afb96..5a9c27d6 100644 --- a/main.go +++ b/main.go @@ -18,20 +18,13 @@ package main import ( "flag" - "net/http" "os" "github.com/ibm/cloud-operators/controllers" - "github.com/ibm/cloud-operators/internal/ibmcloud" - "github.com/ibm/cloud-operators/internal/ibmcloud/auth" - "github.com/ibm/cloud-operators/internal/ibmcloud/cfservice" - "github.com/ibm/cloud-operators/internal/ibmcloud/iam" - "github.com/ibm/cloud-operators/internal/ibmcloud/resource" "k8s.io/apimachinery/pkg/runtime" clientgoscheme "k8s.io/client-go/kubernetes/scheme" _ "k8s.io/client-go/plugin/pkg/client/auth/oidc" ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/log/zap" ibmcloudv1 "github.com/ibm/cloud-operators/api/v1" @@ -77,52 +70,10 @@ func main() { os.Exit(1) } - if err = (&controllers.BindingReconciler{ - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controllers").WithName("Binding"), - Scheme: mgr.GetScheme(), - - CreateCFServiceKey: cfservice.CreateKey, - CreateResourceServiceKey: resource.CreateKey, - DeleteCFServiceKey: cfservice.DeleteKey, - DeleteResourceServiceKey: resource.DeleteKey, - GetCFServiceKeyCredentials: cfservice.GetKey, - GetIBMCloudInfo: ibmcloud.GetInfo, - GetResourceServiceKey: resource.GetKey, - GetServiceName: resource.GetServiceName, - GetServiceRoleCRN: iam.GetServiceRoleCRN, - SetControllerReference: controllerutil.SetControllerReference, - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", "Binding") - os.Exit(1) - } - if err = (&controllers.ServiceReconciler{ - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controllers").WithName("Service"), - Scheme: mgr.GetScheme(), - - CreateCFServiceInstance: cfservice.CreateInstance, - CreateResourceServiceInstance: resource.CreateServiceInstance, - DeleteResourceServiceInstance: resource.DeleteServiceInstance, - GetCFServiceInstance: cfservice.GetInstance, - GetIBMCloudInfo: ibmcloud.GetInfo, - GetResourceServiceAliasInstance: resource.GetServiceAliasInstance, - GetResourceServiceInstanceState: resource.GetServiceInstanceState, - UpdateResourceServiceInstance: resource.UpdateServiceInstance, - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", "Service") - os.Exit(1) - } - if err = (&controllers.TokenReconciler{ - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controllers").WithName("Token"), - Scheme: mgr.GetScheme(), - Authenticate: auth.New(http.DefaultClient), - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", "Token") + if _, err := controllers.SetUpControllers(mgr); err != nil { + setupLog.Error(err, "Unable to set up controllers") os.Exit(1) } - // +kubebuilder:scaffold:builder setupLog.Info("starting manager") if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { From cecd83a21240e5207b4204e4ec5eef78b04dd896 Mon Sep 17 00:00:00 2001 From: John Starich Date: Mon, 21 Sep 2020 20:39:24 -0500 Subject: [PATCH 15/20] Fix default namespace fallback (#209) * Add more logging * Fix bug: was calling wrong IsNotFound func * Remove confusing IsNotFound func from ibmcloud package Signed-off-by: Art Berger --- controllers/service_controller_test.go | 14 +++++++++----- internal/ibmcloud/errors.go | 5 ----- internal/ibmcloud/ibmcloud.go | 11 +++++++---- main.go | 5 +++-- 4 files changed, 19 insertions(+), 16 deletions(-) delete mode 100644 internal/ibmcloud/errors.go diff --git a/controllers/service_controller_test.go b/controllers/service_controller_test.go index bf6b82ac..6588a02e 100644 --- a/controllers/service_controller_test.go +++ b/controllers/service_controller_test.go @@ -32,6 +32,10 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client/fake" ) +var ( + errNotFoundTest = fmt.Errorf("not found") +) + func TestService(t *testing.T) { if testing.Short() { t.SkipNow() @@ -84,7 +88,7 @@ func TestService(t *testing.T) { }, defaultWait, defaultTick) _, err = getServiceInstanceFromObj(logger, serviceCopy) - assert.True(t, ibmcloud.IsNotFound(err), "Expect service to be deleted") + assert.Equal(t, errNotFoundTest, err, "Expect service to be deleted") } }) } @@ -126,7 +130,7 @@ func TestService(t *testing.T) { }, defaultWait, defaultTick) _, err := getServiceInstanceFromObj(logger, serviceCopy) - assert.True(t, ibmcloud.IsNotFound(err)) + assert.Equal(t, errNotFoundTest, err) }) t.Run("should fail", func(t *testing.T) { @@ -182,7 +186,7 @@ func getServiceInstanceFromObj(logt logr.Logger, service *ibmcloudv1.Service) (m return instance, nil } } - return models.ServiceInstance{}, fmt.Errorf("not found") + return models.ServiceInstance{}, errNotFoundTest } func TestServiceV1Alpha1Compat(t *testing.T) { @@ -212,7 +216,7 @@ func TestServiceV1Alpha1Compat(t *testing.T) { }, defaultWait, defaultTick) _, err = getServiceInstanceFromObj(logger, serviceCopy) - assert.True(t, ibmcloud.IsNotFound(err), "Expect service to be deleted") + assert.Equal(t, errNotFoundTest, err, "Expect service to be deleted") } func TestServiceV1Beta1Compat(t *testing.T) { @@ -242,7 +246,7 @@ func TestServiceV1Beta1Compat(t *testing.T) { }, defaultWait, defaultTick) _, err = getServiceInstanceFromObj(logger, serviceCopy) - assert.True(t, ibmcloud.IsNotFound(err), "Expect service to be deleted") + assert.Equal(t, errNotFoundTest, err, "Expect service to be deleted") } func TestServiceLoadServiceFailed(t *testing.T) { diff --git a/internal/ibmcloud/errors.go b/internal/ibmcloud/errors.go deleted file mode 100644 index 30b0aeb1..00000000 --- a/internal/ibmcloud/errors.go +++ /dev/null @@ -1,5 +0,0 @@ -package ibmcloud - -func IsNotFound(err error) bool { - return err != nil && err.Error() == "not found" -} diff --git a/internal/ibmcloud/ibmcloud.go b/internal/ibmcloud/ibmcloud.go index 1c954144..ddb078fd 100644 --- a/internal/ibmcloud/ibmcloud.go +++ b/internal/ibmcloud/ibmcloud.go @@ -19,6 +19,7 @@ import ( ibmcloudv1 "github.com/ibm/cloud-operators/api/v1" "github.com/ibm/cloud-operators/internal/config" v1 "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" @@ -293,7 +294,7 @@ func getIamToken(logt logr.Logger, r client.Client, instance *ibmcloudv1.Service } func getConfigOrSecret(logt logr.Logger, r client.Client, instanceNamespace string, objName string, obj runtime.Object) error { - defaultNamespace, isManagement := getDefaultNamespace(r) + defaultNamespace, isManagement := getDefaultNamespace(logt, r) if isManagement { objName = instanceNamespace + "-" + objName err := r.Get(context.TODO(), types.NamespacedName{Name: objName, Namespace: defaultNamespace}, obj) @@ -305,14 +306,15 @@ func getConfigOrSecret(logt logr.Logger, r client.Client, instanceNamespace stri } err := r.Get(context.TODO(), types.NamespacedName{Name: objName, Namespace: instanceNamespace}, obj) if err != nil { - if IsNotFound(err) { + if errors.IsNotFound(err) { err = r.Get(context.TODO(), types.NamespacedName{Name: objName, Namespace: defaultNamespace}, obj) if err != nil { - logt.Info("Unable to find secret or config in namespace", objName, defaultNamespace) + logt.Info("Unable to find secret or config in same namespace or default namespace", "secret or config", objName, "namespace", instanceNamespace, "default namespace", defaultNamespace) return err } return nil } + logt.Error(err, "Unknown error getting config or secret", "name", objName, "namespace", instanceNamespace) return err } return nil @@ -347,7 +349,7 @@ func getIBMCloudContext(instance *ibmcloudv1.Service, cm *v1.ConfigMap) ibmcloud return instance.Spec.Context } -func getDefaultNamespace(r client.Client) (string, bool) { +func getDefaultNamespace(logt logr.Logger, r client.Client) (string, bool) { cm := &v1.ConfigMap{} err := r.Get(context.Background(), types.NamespacedName{Namespace: config.Get().ControllerNamespace, Name: icoConfigMap}, cm) if err != nil { @@ -355,5 +357,6 @@ func getDefaultNamespace(r client.Client) (string, bool) { } // There exists an ico-management configmap in the controller namespace + logt.Info("Found ibmcloud-operator-config in own namespace, using that namespace for configuration", "own namespace", config.Get().ControllerNamespace, "management namespace", cm.Data["namespace"]) return cm.Data["namespace"], true } diff --git a/main.go b/main.go index 5a9c27d6..a3846a45 100644 --- a/main.go +++ b/main.go @@ -21,11 +21,12 @@ import ( "os" "github.com/ibm/cloud-operators/controllers" + "go.uber.org/zap" "k8s.io/apimachinery/pkg/runtime" clientgoscheme "k8s.io/client-go/kubernetes/scheme" _ "k8s.io/client-go/plugin/pkg/client/auth/oidc" ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/log/zap" + zapLog "sigs.k8s.io/controller-runtime/pkg/log/zap" ibmcloudv1 "github.com/ibm/cloud-operators/api/v1" ibmcloudv1alpha1 "github.com/ibm/cloud-operators/api/v1alpha1" @@ -56,7 +57,7 @@ func main() { "Enabling this will ensure there is only one active controller manager.") flag.Parse() - ctrl.SetLogger(zap.New(zap.UseDevMode(true))) + ctrl.SetLogger(zapLog.New(zapLog.UseDevMode(true), zapLog.RawZapOpts(zap.AddCaller()))) mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ Scheme: scheme, From a10f63e849d4fe1cddae1767bf5fdc342d1e6999 Mon Sep 17 00:00:00 2001 From: John Starich Date: Mon, 21 Sep 2020 21:36:58 -0500 Subject: [PATCH 16/20] Add back max concurrent reconciles environment variable (#210) * MaxConcurrentReconciles can be set through env var * Combine max reconciles into main Config struct, surface it in deployment.yaml Co-authored-by: qibobo Signed-off-by: Art Berger --- config/manager/manager.yaml | 2 ++ controllers/binding_controller.go | 4 +++- controllers/manager_setup.go | 12 +++++++++--- controllers/service_controller.go | 4 +++- controllers/token_controller.go | 4 +++- internal/config/config.go | 20 +++++++++++--------- 6 files changed, 31 insertions(+), 15 deletions(-) diff --git a/config/manager/manager.yaml b/config/manager/manager.yaml index 36aa9309..70b05722 100644 --- a/config/manager/manager.yaml +++ b/config/manager/manager.yaml @@ -32,6 +32,8 @@ spec: valueFrom: fieldRef: fieldPath: metadata.namespace + - name: MAX_CONCURRENT_RECONCILES + value: 1 image: controller:latest name: manager resources: diff --git a/controllers/binding_controller.go b/controllers/binding_controller.go index 90dbe223..5c3ba454 100644 --- a/controllers/binding_controller.go +++ b/controllers/binding_controller.go @@ -39,6 +39,7 @@ import ( "k8s.io/apimachinery/pkg/types" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" ) const ( @@ -81,8 +82,9 @@ type ControllerReferenceSetter func(owner, controlled metav1.Object, scheme *run type IBMCloudInfoGetter func(logt logr.Logger, r client.Client, instance *ibmcloudv1.Service) (*ibmcloud.Info, error) -func (r *BindingReconciler) SetupWithManager(mgr ctrl.Manager) error { +func (r *BindingReconciler) SetupWithManager(mgr ctrl.Manager, options controller.Options) error { return ctrl.NewControllerManagedBy(mgr). + WithOptions(options). For(&ibmcloudv1.Binding{}). Complete(r) } diff --git a/controllers/manager_setup.go b/controllers/manager_setup.go index 5fb93e79..7a78add7 100644 --- a/controllers/manager_setup.go +++ b/controllers/manager_setup.go @@ -3,6 +3,7 @@ package controllers import ( "net/http" + "github.com/ibm/cloud-operators/internal/config" "github.com/ibm/cloud-operators/internal/ibmcloud" "github.com/ibm/cloud-operators/internal/ibmcloud/auth" "github.com/ibm/cloud-operators/internal/ibmcloud/cfservice" @@ -10,6 +11,7 @@ import ( "github.com/ibm/cloud-operators/internal/ibmcloud/resource" "github.com/pkg/errors" ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/controller" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" ) @@ -22,13 +24,17 @@ type Controllers struct { func SetUpControllers(mgr ctrl.Manager) (*Controllers, error) { c := setUpControllerDependencies(mgr) - if err := c.BindingReconciler.SetupWithManager(mgr); err != nil { + + options := controller.Options{ + MaxConcurrentReconciles: config.Get().MaxConcurrentReconciles, + } + if err := c.BindingReconciler.SetupWithManager(mgr, options); err != nil { return nil, errors.Wrap(err, "Unable to setup binding controller") } - if err := c.ServiceReconciler.SetupWithManager(mgr); err != nil { + if err := c.ServiceReconciler.SetupWithManager(mgr, options); err != nil { return nil, errors.Wrap(err, "Unable to setup service controller") } - if err := c.TokenReconciler.SetupWithManager(mgr); err != nil { + if err := c.TokenReconciler.SetupWithManager(mgr, options); err != nil { return nil, errors.Wrap(err, "Unable to setup token controller") } diff --git a/controllers/service_controller.go b/controllers/service_controller.go index a1d231ea..2e5c00c5 100644 --- a/controllers/service_controller.go +++ b/controllers/service_controller.go @@ -30,6 +30,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" ibmcloudv1 "github.com/ibm/cloud-operators/api/v1" "github.com/ibm/cloud-operators/internal/config" @@ -69,8 +70,9 @@ type ServiceReconciler struct { UpdateResourceServiceInstance resource.ServiceInstanceUpdater } -func (r *ServiceReconciler) SetupWithManager(mgr ctrl.Manager) error { +func (r *ServiceReconciler) SetupWithManager(mgr ctrl.Manager, options controller.Options) error { return ctrl.NewControllerManagedBy(mgr). + WithOptions(options). For(&ibmcloudv1.Service{}). Complete(r) } diff --git a/controllers/token_controller.go b/controllers/token_controller.go index 3b50dca6..fb6a35ea 100644 --- a/controllers/token_controller.go +++ b/controllers/token_controller.go @@ -29,6 +29,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" "sigs.k8s.io/controller-runtime/pkg/event" "sigs.k8s.io/controller-runtime/pkg/predicate" ) @@ -123,8 +124,9 @@ func (r *TokenReconciler) Reconcile(request ctrl.Request) (ctrl.Result, error) { return ctrl.Result{RequeueAfter: 10 * time.Minute}, nil } -func (r *TokenReconciler) SetupWithManager(mgr ctrl.Manager) error { +func (r *TokenReconciler) SetupWithManager(mgr ctrl.Manager, options controller.Options) error { return ctrl.NewControllerManagedBy(mgr). + WithOptions(options). For(&corev1.Secret{}). WithEventFilter(predicate.Funcs{ CreateFunc: func(e event.CreateEvent) bool { return shouldProcessSecret(e.Meta) }, diff --git a/internal/config/config.go b/internal/config/config.go index ee5bce41..138b2a52 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -13,20 +13,22 @@ var ( ) type Config struct { - APIKey string `envconfig:"bluemix_api_key"` - AccountID string `envconfig:"bluemix_account_id"` - ControllerNamespace string `envconfig:"controller_namespace"` - Org string `envconfig:"bluemix_org"` - Region string `envconfig:"bluemix_region"` - ResourceGroupName string `envconfig:"bluemix_resource_group"` - Space string `envconfig:"bluemix_space"` - SyncPeriod time.Duration `envconfig:"sync_period"` + APIKey string `envconfig:"bluemix_api_key"` + AccountID string `envconfig:"bluemix_account_id"` + ControllerNamespace string `envconfig:"controller_namespace"` + MaxConcurrentReconciles int `envconfig:"max_concurrent_reconciles"` + Org string `envconfig:"bluemix_org"` + Region string `envconfig:"bluemix_region"` + ResourceGroupName string `envconfig:"bluemix_resource_group"` + Space string `envconfig:"bluemix_space"` + SyncPeriod time.Duration `envconfig:"sync_period"` } func Get() Config { loadOnce.Do(func() { config = Config{ // default values - SyncPeriod: 150 * time.Second, + MaxConcurrentReconciles: 1, + SyncPeriod: 150 * time.Second, } envconfig.MustProcess("", &config) }) From a55a57d1a9b6cf1c766b540176368b72bd18b094 Mon Sep 17 00:00:00 2001 From: John Starich Date: Mon, 21 Sep 2020 22:29:07 -0500 Subject: [PATCH 17/20] Add tests to ensure an alias service will never issue an API call to create (#211) Signed-off-by: John Starich Signed-off-by: Art Berger --- controllers/service_controller_test.go | 58 ++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/controllers/service_controller_test.go b/controllers/service_controller_test.go index 6588a02e..9a28e256 100644 --- a/controllers/service_controller_test.go +++ b/controllers/service_controller_test.go @@ -1292,6 +1292,9 @@ func TestServiceEnsureResourceServiceInstance(t *testing.T) { GetResourceServiceAliasInstance: func(session *session.Session, instanceID, resourceGroupID, servicePlanID, externalName string, logt logr.Logger) (id string, state string, err error) { return "", "", resource.NotFoundError{Err: fmt.Errorf("failed")} }, + CreateResourceServiceInstance: func(session *session.Session, externalName, servicePlanID, resourceGroupID, targetCrn string, params map[string]interface{}, tags []string) (id, state string, err error) { + panic("Must not re-create an alias service") // https://github.com/IBM/cloud-operators/issues/71 + }, } result, err := r.Reconcile(ctrl.Request{ NamespacedName: types.NamespacedName{Name: serviceName, Namespace: namespace}, @@ -1337,6 +1340,9 @@ func TestServiceEnsureResourceServiceInstance(t *testing.T) { GetResourceServiceAliasInstance: func(session *session.Session, instanceID, resourceGroupID, servicePlanID, externalName string, logt logr.Logger) (id string, state string, err error) { return "", "", fmt.Errorf("failed") }, + CreateResourceServiceInstance: func(session *session.Session, externalName, servicePlanID, resourceGroupID, targetCrn string, params map[string]interface{}, tags []string) (id, state string, err error) { + panic("Must not re-create an alias service") // https://github.com/IBM/cloud-operators/issues/71 + }, } result, err := r.Reconcile(ctrl.Request{ NamespacedName: types.NamespacedName{Name: serviceName, Namespace: namespace}, @@ -1745,6 +1751,9 @@ func TestServiceVerifyExists(t *testing.T) { GetResourceServiceInstanceState: func(session *session.Session, resourceGroupID, servicePlanID, externalName, instanceID string) (state string, err error) { return "", resource.NotFoundError{Err: fmt.Errorf("failed")} }, + CreateResourceServiceInstance: func(session *session.Session, externalName, servicePlanID, resourceGroupID, targetCrn string, params map[string]interface{}, tags []string) (id, state string, err error) { + panic("Must not re-create an alias service") // https://github.com/IBM/cloud-operators/issues/71 + }, } result, err := r.Reconcile(ctrl.Request{ @@ -1776,6 +1785,55 @@ func TestServiceVerifyExists(t *testing.T) { }, r.Client.(MockClient).LastStatusUpdate()) }) + t.Run("other error alias", func(t *testing.T) { + r := &ServiceReconciler{ + Client: newMockClient( + fake.NewFakeClientWithScheme(scheme, aliasObjects...), + MockConfig{}, + ), + Log: testLogger(t), + Scheme: scheme, + + GetIBMCloudInfo: func(logt logr.Logger, _ client.Client, instance *ibmcloudv1.Service) (*ibmcloud.Info, error) { + return &ibmcloud.Info{}, nil + }, + GetResourceServiceInstanceState: func(session *session.Session, resourceGroupID, servicePlanID, externalName, instanceID string) (state string, err error) { + return "", fmt.Errorf("failed") + }, + CreateResourceServiceInstance: func(session *session.Session, externalName, servicePlanID, resourceGroupID, targetCrn string, params map[string]interface{}, tags []string) (id, state string, err error) { + panic("Must not re-create an alias service") // https://github.com/IBM/cloud-operators/issues/71 + }, + } + + result, err := r.Reconcile(ctrl.Request{ + NamespacedName: types.NamespacedName{Name: serviceName, Namespace: namespace}, + }) + assert.Equal(t, ctrl.Result{ + Requeue: true, + RequeueAfter: config.Get().SyncPeriod, + }, result) + assert.NoError(t, err) + assert.Equal(t, &ibmcloudv1.Service{ + TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "ibmcloud.ibm.com/v1"}, + ObjectMeta: metav1.ObjectMeta{ + Name: serviceName, + Namespace: namespace, + Finalizers: []string{serviceFinalizer}, + }, + Status: ibmcloudv1.ServiceStatus{ + State: serviceStatePending, + Message: "failed", + Plan: aliasPlan, + ServiceClass: "service-name", + InstanceID: "myinstanceid", + }, + Spec: ibmcloudv1.ServiceSpec{ + Plan: aliasPlan, + ServiceClass: "service-name", + }, + }, r.Client.(MockClient).LastStatusUpdate()) + }) + t.Run("other error", func(t *testing.T) { r := &ServiceReconciler{ Client: newMockClient( From cb6608de4e93d05d4bbfe98aafa6463a4abaaa92 Mon Sep 17 00:00:00 2001 From: John Starich Date: Tue, 22 Sep 2020 15:46:05 -0500 Subject: [PATCH 18/20] Add kubeval for validation of kube yaml in PRs (#214) Signed-off-by: Art Berger --- .travis.yml | 4 ++-- Makefile | 23 ++++++++++++++++++++++- config/manager/kustomization.yaml | 2 +- config/manager/manager.yaml | 2 +- internal/cmd/genolm/maintainer.go | 2 +- 5 files changed, 27 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6bbb3350..4ab0a17c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,8 +15,8 @@ jobs: if: (branch = master AND type = push) OR type = pull_request # Don't run int tests too much on the same account in parallel, risks service name conflict failures - name: Test End-to-end script: make test-e2e - - name: Docker Build - script: make docker-build + - name: Release Validation + script: make validate-release RELEASE_VERSION=0.3.0 # Fake version, just used for quick validation - name: Release stage: release script: make -e RELEASE_VERSION="${TRAVIS_TAG/v}" release diff --git a/Makefile b/Makefile index 00f59648..c469e478 100644 --- a/Makefile +++ b/Makefile @@ -3,6 +3,8 @@ export KUBEBUILDER_ASSETS = ${PWD}/cache/kubebuilder_${KUBEBUILDER_VERSION}/bin CONTROLLER_GEN_VERSION = 0.2.5 CONTROLLER_GEN=${PWD}/cache/controller-gen_${CONTROLLER_GEN_VERSION}/controller-gen LINT_VERSION = 1.28.3 +KUBEVAL_VERSION= 0.15.0 +KUBEVAL_KUBE_VERSION=1.18.1 # Set PATH to pick up cached tools. The additional 'sed' is required for cross-platform support of quoting the args to 'env' SHELL := /usr/bin/env PATH=$(shell echo ${PWD}/cache/bin:${KUBEBUILDER_ASSETS}:${PATH} | sed 's/ /\\ /g') bash @@ -62,7 +64,12 @@ cache/bin/kustomize: cache/bin @rm -f cache/bin/kustomize cd cache/bin && \ set -o pipefail && \ - curl -s "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" | bash + for (( i = 0; i < 5; i++ )); do \ + curl -s "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" | bash; \ + if [[ "$$(which kustomize)" =~ cache/bin/kustomize ]]; then \ + break; \ + fi \ + done [[ "$$(which kustomize)" =~ cache/bin/kustomize ]] .PHONY: test-fast @@ -177,6 +184,12 @@ release-prep: kustomize manifests out .PHONY: release release: release-prep docker-push +# Validates release artifacts. +# TODO add validation for operator-courier. Currently hitting WAY too many issues with Travis CI and Python deps. +.PHONY: validate-release +validate-release: kubeval release-prep docker-build + kubeval -d out --kubernetes-version "${KUBEVAL_KUBE_VERSION}" --ignored-filename-patterns package.yaml --ignore-missing-schemas + .PHONY: operator-courier operator-courier: @if ! which operator-courier; then \ @@ -189,6 +202,7 @@ verify-operator-meta: release-prep operator-courier curl -sL https://github.com/IBM/cloud-operators/releases/download/v0.1.11/001_ibmcloud_v1alpha1_binding.yaml > out/0.1.11_ibmcloud_v1alpha1_binding.yaml curl -sL https://github.com/IBM/cloud-operators/releases/download/v0.1.11/002_ibmcloud_v1alpha1_service.yaml > out/0.1.11_ibmcloud_v1alpha1_service.yaml curl -sL https://github.com/IBM/cloud-operators/releases/download/v0.1.11/ibmcloud_operator.v0.1.11.clusterserviceversion.yaml > out/ibmcloud_operator.v0.1.11.clusterserviceversion.yaml + ls out operator-courier verify --ui_validate_io out/ .PHONY: operator-push-test @@ -210,3 +224,10 @@ operator-push-test: verify-operator-meta docker-build docker login -u="${QUAY_USER}" -p="${QUAY_TOKEN}" quay.io docker push "${IMG}" operator-courier push ./out "${QUAY_NAMESPACE}" "${QUAY_APP}" "${RELEASE_VERSION}" "Basic $$(printf "${QUAY_USER}:${QUAY_TOKEN}" | base64)" + +.PHONY: kubeval +kubeval: cache/bin + @if [[ ! -f cache/bin/kubeval ]]; then \ + set -ex -o pipefail; \ + curl -sL https://github.com/instrumenta/kubeval/releases/download/${KUBEVAL_VERSION}/kubeval-$$(uname)-amd64.tar.gz | tar -xz -C cache/bin; \ + fi diff --git a/config/manager/kustomization.yaml b/config/manager/kustomization.yaml index 7e787cb9..a5eab3e0 100644 --- a/config/manager/kustomization.yaml +++ b/config/manager/kustomization.yaml @@ -5,4 +5,4 @@ kind: Kustomization images: - name: controller newName: cloudoperators/ibmcloud-operator - newTag: 0.2.1 + newTag: 0.2.0 diff --git a/config/manager/manager.yaml b/config/manager/manager.yaml index 70b05722..2bb8b033 100644 --- a/config/manager/manager.yaml +++ b/config/manager/manager.yaml @@ -33,7 +33,7 @@ spec: fieldRef: fieldPath: metadata.namespace - name: MAX_CONCURRENT_RECONCILES - value: 1 + value: "1" image: controller:latest name: manager resources: diff --git a/internal/cmd/genolm/maintainer.go b/internal/cmd/genolm/maintainer.go index db630048..f0ca0782 100644 --- a/internal/cmd/genolm/maintainer.go +++ b/internal/cmd/genolm/maintainer.go @@ -40,7 +40,7 @@ func getMaintainers(repoRoot string) ([]Maintainer, error) { } var commits []*object.Commit - const maxCommits = 200 + const maxCommits = 100 for i := 0; i < maxCommits; i++ { commit, err := commitIter.Next() if err == io.EOF { From b108fac6f2254b9517f4c35c57a463aec241ce16 Mon Sep 17 00:00:00 2001 From: John Starich Date: Tue, 22 Sep 2020 17:25:55 -0500 Subject: [PATCH 19/20] Bump memory limit again for higher # of secrets in OpenShift (#216) Signed-off-by: John Starich Signed-off-by: Art Berger --- config/manager/manager.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/manager/manager.yaml b/config/manager/manager.yaml index 2bb8b033..5561e302 100644 --- a/config/manager/manager.yaml +++ b/config/manager/manager.yaml @@ -40,7 +40,7 @@ spec: limits: cpu: 100m # TODO(johnstarich): Reduce back to 30Mi once this is resolved: https://github.com/IBM/cloud-operators/issues/199 - memory: 125Mi + memory: 175Mi requests: cpu: 100m memory: 20Mi From 3a28a4803f778750006e3da4afaec6a95d6bdebf Mon Sep 17 00:00:00 2001 From: Art Berger Date: Wed, 23 Sep 2020 14:11:27 -0400 Subject: [PATCH 20/20] docs: moving upgrade section back up Signed-off-by: Art Berger --- README.md | 39 ++++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 0f2e0b24..09176bc7 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ With the IBM Cloud Operator, you can provision and bind [IBM public cloud servic ## Table of content * [Features](#features) +* [Upgrading the operator](#upgrading-the-operator) * [Prerequisites](#prerequisites) * [Setting up the operator](#setting-up-the-operator) * [Using a service ID](#using-a-service-id) @@ -21,7 +22,6 @@ With the IBM Cloud Operator, you can provision and bind [IBM public cloud servic * [Installing the operator for Kubernetes clusters](#installing-the-operator-for-kubernetes-clusters) * [Uninstalling the operator](#uninstalling-the-operator) * [Using the IBM Cloud Operator](#using-the-ibm-cloud-operator) -* [Upgrading the operator](#upgrading-the-operator) * [Using separate IBM Cloud accounts](#using-separate-ibm-cloud-accounts) * [Using a management namespace](#using-a-management-namespace) * [Reference documentation](#reference-documentation) @@ -44,6 +44,24 @@ With the IBM Cloud Operator, you can provision and bind [IBM public cloud servic [Back to top](#ibm-cloud-operator) +## Upgrading the operator +To upgrade, you can reinstall the operator through the OperatorHub or the `curl` [installation command](README.md#setting-up-the-operator). + +### Upgrading to version 0.3.0 or later +**IMPORTANT NOTICE:** v0.1 and v0.2 used a different naming scheme for secrets and configmaps. Before you update the IBM Cloud Operator, create secret and configmap resources with these names and copy the contents of your previous resources to the new resources. Then, the upgraded operator recognizes and continues to update the resources. + +| Previous names (< v0.3) | **Current names (v0.3 or later)** | Description | +|:---------------------------------------|:--------------------------------------------|:--------------------------------------------------------------------------------------------| +| secret-ibm-cloud-operator | **ibmcloud-operator-secret** | Secret with the API key, scoped to the namespace. | +| config-ibm-cloud-operator | **ibmcloud-operator-defaults** | ConfigMap with the default values for new resources. | +| ibm-cloud-operator | **ibmcloud-operator-config** | ConfigMap with the management namespace configuration. | +| ${namespace}-secret-ibm-cloud-operator | **${namespace}-ibmcloud-operator-secret** | Management namespace Secret with the API key for ${namespace}. | +| ${namespace}-config-ibm-cloud-operator | **${namespace}-ibmcloud-operator-defaults** | Management namespace ConfigMap with default values for new resources in ${namespace}. | + + +[Back to top](#ibm-cloud-operator) + + ## Prerequisites 1. Have an [IBM Cloud account](https://cloud.ibm.com/registration). @@ -132,7 +150,7 @@ By default, the installation script creates an IBM Cloud API key that impersonat Before you begin, complete the [prerequisite steps](README.md#prerequisites) to log in to IBM Cloud and your cluster, and optionally set up a [service ID API key](README.md#using-a-service-id). -To install the latest release for OpenShift before install, run the following script: +To configure the latest release for OpenShift before installing via the OperatorHub, run the following script: * **Latest release**: @@ -257,23 +275,6 @@ To use the IBM Cloud Operator, create a service instance and then bind the servi mybinding Opaque 6 102s ``` - -[Back to top](#ibm-cloud-operator) - - -## Upgrading the operator - -### Upgrading to version 0.3.0 or later -**IMPORTANT NOTICE:** v0.1 and v0.2 used a different naming scheme for secrets and configmaps. Before you update the IBM Cloud Operator, create secret and configmap resources with these names and copy the contents of your previous resources to the new resources. Then, the upgraded operator recognizes and continues to update the resources. - -| Previous names (< v0.3) | **Current names (v0.3 or later)** | Description | -|:---------------------------------------|:--------------------------------------------|:--------------------------------------------------------------------------------------------| -| secret-ibm-cloud-operator | **ibmcloud-operator-secret** | Secret with the API key, scoped to the namespace. | -| config-ibm-cloud-operator | **ibmcloud-operator-defaults** | ConfigMap with the default values for new resources. | -| ibm-cloud-operator | **ibmcloud-operator-config** | ConfigMap with the management namespace configuration. | -| ${namespace}-secret-ibm-cloud-operator | **${namespace}-ibmcloud-operator-secret** | Management namespace Secret with the API key for ${namespace}. | -| ${namespace}-config-ibm-cloud-operator | **${namespace}-ibmcloud-operator-defaults** | Management namespace ConfigMap with default values for new resources in ${namespace}. | - [Back to top](#ibm-cloud-operator)