diff --git a/docs/contributor/05-10-e2e_tests.md b/docs/contributor/05-10-e2e_tests.md index a224382985..bfdaecf9bc 100644 --- a/docs/contributor/05-10-e2e_tests.md +++ b/docs/contributor/05-10-e2e_tests.md @@ -2,12 +2,14 @@ ## Overview -The end-to-end (E2E) tests cover Kyma Environment Broker (KEB) and SAP BTP, Kyma runtime. -There are three tests: +The following end-to-end (E2E) tests cover Kyma Environment Broker (KEB) and SAP BTP, Kyma runtime: * `skr-tests` for testing the following operations on different cloud service providers: Kyma provisioning, BTP Manager Secret reconciliation, updating OIDC, updating machine type, and Kyma runtime deprovisioning -* `skr-aws-upgrade-integration` for checking Kyma runtime provisioning, upgrading, and deprovisioning * `keb-endpoints-test` for checking if `kyma-environment-broker` endpoints require authorization +* `skr-aws-networking` for checking if provisioning a Kyma runtime with custom networking parameters works as expected +* `skr-trial-suspension-dev` for testing the following operations: Kyma provisioning, Kyma suspension, and Kyma runtime deprovisioning +* `skr-aws-binding` for testing the following operations: Kyma provisioning, fetching Kyma Binding, using Kyma Binding, deleting Kyma Binding, and Kyma runtime deprovisioning +* `provisioning-service-aws-stage` for checking if Cloud Management Service Provisioning API works as expected ## E2E SKR Tests @@ -61,68 +63,69 @@ In this mode, the test executes the following steps: make skr SKIP_PROVISIONING=true ``` -## E2E SKR AWS Upgrade Integration Test +## KEB Endpoints Test ### Usage The test executes the following steps: - -1. Provisions a Kyma runtime cluster. -2. Runs a Kyma runtime upgrade. -3. Deprovisions the Kyma runtime instance and cleans up the resources. +1. Calls KEB endpoints without an authorization token. +2. Checks whether the call was rejected. ### Test Execution -1. Before you run the test, prepare the `.env` file based on this [`.env.template`](/testing/e2e/skr/skr-aws-upgrade-integration/.env.template). +1. Before you run the test, prepare the `.env` file based on this [`.env.template`](/testing/e2e/skr/keb-endpoints-test/.env.template). 2. To set up the environment variables in your system, run: ```bash export $(xargs < .env) ``` -3. Run the test scenario: - +3. Run the test scenario. + ```bash - make skr-aws-upgrade-integration + make keb-endpoints ``` -## KEB Endpoints Test +## Networking Parameter Tests ### Usage The test executes the following steps: -1. Calls KEB endpoints without an authorization token. + +1. Calls KEB endpoints with invalid networking parameters. 2. Checks whether the call was rejected. +3. Provisions a cluster with custom networking parameters. +4. Deprovisions the cluster. ### Test Execution -1. Before you run the test, prepare the `.env` file based on this [`.env.template`](/testing/e2e/skr/keb-endpoints-test/.env.template). +1. Before you run the test, prepare the `.env` file based on this [`.env.template`](/testing/e2e/skr/skr-networking-test/.env.template). 2. To set up the environment variables in your system, run: ```bash export $(xargs < .env) ``` -3. Run the test scenario. - +3. Run the test scenario: + ```bash - make keb-endpoints + make skr-networking ``` -## Networking Parameter Tests +## E2E SKR Suspension Test ### Usage The test executes the following steps: -1. Calls KEB endpoints with invalid networking parameters. -2. Checks whether the call was rejected. -3. Provisions a cluster with custom networking parameters. -4. Deprovisions the cluster. +1. Provisions a Kyma runtime cluster. +2. Waits until Trial Cleanup CronJob triggers suspension. +3. Waits until suspension succeeds. +4. Deprovisions the Kyma runtime instance and cleans up the resources. ### Test Execution -1. Before you run the test, prepare the `.env` file based on this [`.env.template`](/testing/e2e/skr/skr-networking-test/.env.template). +1. Before you run the test, prepare the `.env` file based on this [`.env.template`](/testing/e2e/skr/skr-test/.env.template). 2. To set up the environment variables in your system, run: ```bash @@ -132,7 +135,7 @@ The test executes the following steps: 3. Run the test scenario: ```bash - make skr-networking-test + make skr-trial-suspension ``` ## Binding Tests @@ -142,13 +145,16 @@ The test executes the following steps: The test executes the following steps: 1. Provisions a Kyma runtime cluster. -2. Creates a binding using Kubernetes TokenRequest and saves the returned kubeconfig. +2. Creates a Kyma Binding and saves the returned kubeconfig. 3. Initializes a Kubernetes client with the returned kubeconfig. -4. Tries to fetch a Secret using the binding from Kubernetes TokenRequest. -5. Creates a binding using Gardener and saves the returned kubeconfig. -6. Initializes a Kubernetes client with the returned kubeconfig. -7. Tries to fetch a Secret using the binding from Gardener. -8. Deprovisions the Kyma runtime instance and cleans up the resources. +4. Fetches the `sap-btp-manager` Secret using the Kyma Binding. +5. Fetches the created Kyma Binding. +6. Deletes the created Kyma Binding. +7. Tries to fetch the `sap-btp-manager` Secret using the deleted Kyma Binding. +8. Tries to create a Kyma Binding using invalid parameters. +9. Tests response status codes. +10. Tries to create more than 10 Kyma Bindings. +11. Deprovisions the Kyma runtime instance and cleans up the resources. ### Test Execution @@ -162,19 +168,52 @@ The test executes the following steps: 3. Run the test scenario: ```bash - make skr-binding-test + make skr-binding + ``` + +## Provisioning Service Tests + +### Usage + +The test executes the following steps: + +1. Sends a call to Provisioning API to provision a Kyma runtime. The test waits until the environment is created. +2. Creates a Kyma Binding. +3. Fetches the `sap-btp-manager` Secret using the kubeconfig from the created Kyma Binding. +4. Fetches the created Kyma Biding. +5. Deletes the created Kyma Binding. +6. Tries to fetch the `sap-btp-manager` Secret using the invalidated kubeconfig. +7. Tries to fetch the deleted Kyma Binding. +8. Sends a call to Provisioning API to deprovision the Kyma runtime. The test waits until the environment is deleted. + +### Test Execution + +1. Before you run the test, prepare the `.env` file based on this [`.env.template`](/testing/e2e/skr/provisioning-service-test/.env.template). +2. To set up the environment variables in your system, run: + + ```bash + export $(xargs < .env) + ``` + +3. Run the test scenario: + + ```bash + make provisioning-service ``` ## CI Pipelines -The tests are run once per day at 01:05 by the given ProwJobs: +The tests are run daily. +* `keb-endpoints-test` - KEB endpoints test +* `skr-aws-integration-dev` - SKR test +* `skr-aws-binding` - Kyma Bindings test +* `skr-aws-networking` - networking parameters test * `skr-azure-integration-dev` - SKR test * `skr-azure-lite-integration-dev` - SKR test -* `skr-trial-integration-dev` - SKR test -* `skr-preview-dev` - SKR test * `skr-free-aws-integration-dev` - SKR test -* `skr-aws-integration-dev` - SKR test -* `skr-aws-upgrade-integration-dev` - SKR AWS upgrade integration test -* `keb-endpoints-test` - KEB endpoints test -* `skr-networking-test` - networking parameters test +* `skr-preview-dev` - SKR test +* `skr-sap-converged-cloud-integration-dev` - SKR test +* `skr-trial-integration-dev` - SKR test +* `skr-trial-suspension-dev` - SKR suspension test +* `provisioning-service-aws-stage` - Provisioning API test diff --git a/testing/e2e/skr/Makefile b/testing/e2e/skr/Makefile index 769a83b714..8653b13918 100644 --- a/testing/e2e/skr/Makefile +++ b/testing/e2e/skr/Makefile @@ -46,4 +46,8 @@ skr-binding: curl -fLSs -o /usr/local/bin/kcp https://storage.googleapis.com/kyma-development-artifacts/kcp/master/kcp-linux chmod +x /usr/local/bin/kcp npm install - npm run skr-binding-test \ No newline at end of file + npm run skr-binding-test + +.PHONY: provisioning-service +provisioning-service: + cd provisioning-service-test/cmd && go test -timeout 130m diff --git a/testing/e2e/skr/provisioning-service-test/.env.template b/testing/e2e/skr/provisioning-service-test/.env.template new file mode 100644 index 0000000000..e8ac6a5ba9 --- /dev/null +++ b/testing/e2e/skr/provisioning-service-test/.env.template @@ -0,0 +1,9 @@ +APP_PROVISIONING_URL= +APP_PROVISIONING_CLIENT_ID= +APP_PROVISIONING_CLIENT_SECRET= +APP_PROVISIONING_UAA_URL= +APP_PROVISIONING_PLAN_NAME= +APP_PROVISIONING_PLAN_ID= +APP_PROVISIONING_USER= +APP_PROVISIONING_INSTANCE_NAME= +APP_PROVISIONING_REGION= \ No newline at end of file diff --git a/testing/e2e/skr/provisioning-service-test/cmd/provisioning_service_test.go b/testing/e2e/skr/provisioning-service-test/cmd/provisioning_service_test.go new file mode 100644 index 0000000000..592b0cc139 --- /dev/null +++ b/testing/e2e/skr/provisioning-service-test/cmd/provisioning_service_test.go @@ -0,0 +1,62 @@ +package cmd + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestProvisioningService(t *testing.T) { + suite := NewProvisioningSuite(t) + + suite.logger.Info("Creating a new environment") + environment, err := suite.provisioningClient.CreateEnvironment() + require.NoError(t, err) + + err = suite.provisioningClient.AwaitEnvironmentCreated(environment.ID) + assert.NoError(t, err) + suite.logger.Info("Environment created successfully", "environmentID", environment.ID) + + suite.logger.Info("Creating a new binding") + createdBinding, err := suite.provisioningClient.CreateBinding(environment.ID) + assert.NoError(t, err) + assert.NotEmpty(t, createdBinding.Credentials.Kubeconfig) + + if len(createdBinding.Credentials.Kubeconfig) != 0 { + suite.logger.Info("Creating a new K8s client set") + clientset, err := suite.K8sClientSetForKubeconfig(createdBinding.Credentials.Kubeconfig) + assert.NoError(t, err) + + suite.logger.Info("Fetching a secret", "Secret namespace", secretNamespace, "Secret name", secretName) + _, err = clientset.CoreV1().Secrets(secretNamespace).Get(context.TODO(), secretName, metav1.GetOptions{}) + assert.NoError(t, err) + + suite.logger.Info("Fetching a binding", "Binding ID", createdBinding.ID) + fetchedBinding, err := suite.provisioningClient.GetBinding(environment.ID, createdBinding.ID) + assert.NoError(t, err) + assert.Equal(t, createdBinding.Credentials.Kubeconfig, fetchedBinding.Credentials.Kubeconfig) + + suite.logger.Info("Deleting a binding", "Binding ID", createdBinding.ID) + err = suite.provisioningClient.DeleteBinding(environment.ID, createdBinding.ID) + assert.NoError(t, err) + + suite.logger.Info("Trying to fetch a secret using invalidated kubeconfig", "Secret namespace", secretNamespace, "Secret name", secretName) + _, err = clientset.CoreV1().Secrets(secretNamespace).Get(context.TODO(), secretName, metav1.GetOptions{}) + assert.Error(t, err) + + suite.logger.Info("Trying to fetch a deleted binding", "Binding ID", createdBinding.ID) + _, err = suite.provisioningClient.GetBinding(environment.ID, createdBinding.ID) + assert.EqualError(t, err, "unexpected status code 404: body is empty") + } + + suite.logger.Info("Deleting the environment", "environmentID", environment.ID) + _, err = suite.provisioningClient.DeleteEnvironment(environment.ID) + require.NoError(t, err) + + err = suite.provisioningClient.AwaitEnvironmentDeleted(environment.ID) + assert.NoError(t, err) + suite.logger.Info("Environment deleted successfully", "environmentID", environment.ID) +} diff --git a/testing/e2e/skr/provisioning-service-test/cmd/suite_test.go b/testing/e2e/skr/provisioning-service-test/cmd/suite_test.go new file mode 100644 index 0000000000..08e5675e9f --- /dev/null +++ b/testing/e2e/skr/provisioning-service-test/cmd/suite_test.go @@ -0,0 +1,66 @@ +package cmd + +import ( + "context" + "log/slog" + "os" + "testing" + + "github.com/kyma-project/kyma-environment-broker/testing/e2e/skr/provisioning-service-test/internal" + + "github.com/stretchr/testify/require" + "github.com/vrischmann/envconfig" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/clientcmd" +) + +const ( + secretNamespace = "kyma-system" + secretName = "sap-btp-manager" +) + +type Config struct { + Provisioning internal.ProvisioningConfig +} + +type ProvisioningSuite struct { + t *testing.T + logger *slog.Logger + + provisioningClient *internal.ProvisioningClient +} + +func NewProvisioningSuite(t *testing.T) *ProvisioningSuite { + logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo})) + + ctx := context.Background() + + var cfg Config + err := envconfig.InitWithPrefix(&cfg, "APP") + require.NoError(t, err) + + logger.Info("Creating a new provisioning client") + provisioningClient := internal.NewProvisioningClient(cfg.Provisioning, logger, ctx, 60) + err = provisioningClient.GetAccessToken() + require.NoError(t, err) + + return &ProvisioningSuite{ + t: t, + logger: logger, + provisioningClient: provisioningClient, + } +} + +func (p *ProvisioningSuite) K8sClientSetForKubeconfig(kubeconfig string) (kubernetes.Interface, error) { + config, err := clientcmd.RESTConfigFromKubeConfig([]byte(kubeconfig)) + if err != nil { + return nil, err + } + + clientset, err := kubernetes.NewForConfig(config) + if err != nil { + return nil, err + } + + return clientset, nil +} diff --git a/testing/e2e/skr/provisioning-service-test/go.mod b/testing/e2e/skr/provisioning-service-test/go.mod new file mode 100644 index 0000000000..1d2b195418 --- /dev/null +++ b/testing/e2e/skr/provisioning-service-test/go.mod @@ -0,0 +1,53 @@ +module github.com/kyma-project/kyma-environment-broker/testing/e2e/skr/provisioning-service-test + +go 1.23.1 + +require ( + github.com/stretchr/testify v1.9.0 + github.com/vrischmann/envconfig v1.3.0 + k8s.io/apimachinery v0.31.1 + k8s.io/client-go v0.31.1 +) + +require ( + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/emicklei/go-restful/v3 v3.11.0 // indirect + github.com/fxamacker/cbor/v2 v2.7.0 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-openapi/jsonpointer v0.19.6 // indirect + github.com/go-openapi/jsonreference v0.20.2 // indirect + github.com/go-openapi/swag v0.22.4 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/google/gnostic-models v0.6.8 // indirect + github.com/google/go-cmp v0.6.0 // indirect + github.com/google/gofuzz v1.2.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/imdario/mergo v0.3.6 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/x448/float16 v0.8.4 // indirect + golang.org/x/net v0.26.0 // indirect + golang.org/x/oauth2 v0.21.0 // indirect + golang.org/x/sys v0.21.0 // indirect + golang.org/x/term v0.21.0 // indirect + golang.org/x/text v0.16.0 // indirect + golang.org/x/time v0.3.0 // indirect + google.golang.org/protobuf v1.34.2 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/api v0.31.1 // indirect + k8s.io/klog/v2 v2.130.1 // indirect + k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect + k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect + sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect + sigs.k8s.io/yaml v1.4.0 // indirect +) diff --git a/testing/e2e/skr/provisioning-service-test/go.sum b/testing/e2e/skr/provisioning-service-test/go.sum new file mode 100644 index 0000000000..cc5525dd90 --- /dev/null +++ b/testing/e2e/skr/provisioning-service-test/go.sum @@ -0,0 +1,160 @@ +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= +github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= +github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= +github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= +github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= +github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af h1:kmjWCqn2qkEml422C2Rrd27c3VGxi6a/6HNq8QmHRKM= +github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= +github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA= +github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To= +github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw= +github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/vrischmann/envconfig v1.3.0 h1:4XIvQTXznxmWMnjouj0ST5lFo/WAYf5Exgl3x82crEk= +github.com/vrischmann/envconfig v1.3.0/go.mod h1:bbvxFYJdRSpXrhS63mBFtKJzkDiNkyArOLXtY6q0kuI= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= +golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= +golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA= +golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +k8s.io/api v0.31.1 h1:Xe1hX/fPW3PXYYv8BlozYqw63ytA92snr96zMW9gWTU= +k8s.io/api v0.31.1/go.mod h1:sbN1g6eY6XVLeqNsZGLnI5FwVseTrZX7Fv3O26rhAaI= +k8s.io/apimachinery v0.31.1 h1:mhcUBbj7KUjaVhyXILglcVjuS4nYXiwC+KKFBgIVy7U= +k8s.io/apimachinery v0.31.1/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= +k8s.io/client-go v0.31.1 h1:f0ugtWSbWpxHR7sjVpQwuvw9a3ZKLXX0u0itkFXufb0= +k8s.io/client-go v0.31.1/go.mod h1:sKI8871MJN2OyeqRlmA4W4KM9KBdBUpDLu/43eGemCg= +k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= +k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= +k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= +k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A= +k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= +sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/testing/e2e/skr/provisioning-service-test/internal/model.go b/testing/e2e/skr/provisioning-service-test/internal/model.go new file mode 100644 index 0000000000..9df3857db3 --- /dev/null +++ b/testing/e2e/skr/provisioning-service-test/internal/model.go @@ -0,0 +1,65 @@ +package internal + +type AccessToken struct { + Token string `json:"access_token"` +} + +type ErrorResponse struct { + Error Error `json:"error"` +} + +type Error struct { + Message string `json:"message"` +} + +type CreateEnvironmentRequest struct { + EnvironmentType string `json:"environmentType"` + ServiceName string `json:"serviceName"` + PlanName string `json:"planName"` + User string `json:"user"` + Parameters EnvironmentParameters `json:"parameters"` +} + +type EnvironmentParameters struct { + Name string `json:"name"` + Region string `json:"region"` +} + +type CreatedEnvironmentResponse struct { + ID string `json:"id"` +} + +type State string + +const ( + CREATING State = "CREATING" + UPDATING State = "UPDATING" + DELETING State = "DELETING" + OK State = "OK" + CREATION_FAILED State = "CREATION_FAILED" + DELETION_FAILED State = "DELETION_FAILED" + UPDATE_FAILED State = "UPDATE_FAILED" +) + +type EnvironmentResponse struct { + ID string `json:"id"` + State State `json:"state"` +} + +type CreateBindingRequest struct { + ServiceInstanceID string `json:"serviceInstanceId"` + PlanID string `json:"planId"` +} + +type CreatedBindingResponse struct { + ID string + Credentials Credentials `json:"credentials"` +} + +type GetBindingResponse struct { + Credentials Credentials `json:"credentials"` +} + +type Credentials struct { + Kubeconfig string `json:"kubeconfig"` +} diff --git a/testing/e2e/skr/provisioning-service-test/internal/provisioning_client.go b/testing/e2e/skr/provisioning-service-test/internal/provisioning_client.go new file mode 100644 index 0000000000..a916e2fa98 --- /dev/null +++ b/testing/e2e/skr/provisioning-service-test/internal/provisioning_client.go @@ -0,0 +1,310 @@ +package internal + +import ( + "bytes" + "context" + "encoding/base64" + "encoding/json" + "fmt" + "io" + "log/slog" + "net/http" + "net/url" + "time" + + "k8s.io/apimachinery/pkg/util/wait" +) + +const ( + interval = time.Minute + provisioningTimeout = 30 * time.Minute + deprovisioningTimeout = 90 * time.Minute + environmentType = "kyma" + serviceName = "kymaruntime" + accessTokenPath = "/oauth/token" + environmentsPath = "/provisioning/v1/environments" + bindingsPath = "/bindings" +) + +type ProvisioningConfig struct { + URL string + ClientID string + ClientSecret string + UAA_URL string + PlanName string + PlanID string + User string + InstanceName string + Region string +} + +type ProvisioningClient struct { + cfg ProvisioningConfig + logger *slog.Logger + cli *http.Client + ctx context.Context + accessToken string +} + +func NewProvisioningClient(cfg ProvisioningConfig, logger *slog.Logger, ctx context.Context, timeoutSeconds time.Duration) *ProvisioningClient { + cli := &http.Client{Timeout: timeoutSeconds * time.Second} + return &ProvisioningClient{ + cfg: cfg, + logger: logger, + cli: cli, + ctx: ctx, + } +} + +func (p *ProvisioningClient) CreateEnvironment() (CreatedEnvironmentResponse, error) { + requestBody := CreateEnvironmentRequest{ + EnvironmentType: environmentType, + PlanName: p.cfg.PlanName, + ServiceName: serviceName, + User: p.cfg.User, + Parameters: EnvironmentParameters{ + Name: p.cfg.InstanceName, + Region: p.cfg.Region, + }, + } + + requestBodyBytes, err := json.Marshal(requestBody) + if err != nil { + return CreatedEnvironmentResponse{}, fmt.Errorf("failed to marshal request body: %v", err) + } + + resp, err := p.sendRequest(http.MethodPost, p.environmentsPath(), http.StatusAccepted, bytes.NewBuffer(requestBodyBytes)) + if err != nil { + return CreatedEnvironmentResponse{}, err + } + + var environment CreatedEnvironmentResponse + err = p.unmarshallResponse(resp, &environment) + if err != nil { + return CreatedEnvironmentResponse{}, err + } + + return environment, nil +} + +func (p *ProvisioningClient) GetEnvironment(environmentID string) (EnvironmentResponse, error) { + resp, err := p.sendRequest(http.MethodGet, p.environmentsWithIDPath(environmentID), http.StatusOK, nil) + if err != nil { + return EnvironmentResponse{}, err + } + + var environment EnvironmentResponse + err = p.unmarshallResponse(resp, &environment) + if err != nil { + return EnvironmentResponse{}, err + } + + return environment, nil +} + +func (p *ProvisioningClient) DeleteEnvironment(environmentID string) (EnvironmentResponse, error) { + resp, err := p.sendRequest(http.MethodDelete, p.environmentsWithIDPath(environmentID), http.StatusAccepted, nil) + if err != nil { + return EnvironmentResponse{}, err + } + + var environment EnvironmentResponse + err = p.unmarshallResponse(resp, &environment) + if err != nil { + return EnvironmentResponse{}, err + } + + return environment, nil +} + +func (p *ProvisioningClient) CreateBinding(environmentID string) (CreatedBindingResponse, error) { + requestBody := CreateBindingRequest{ + ServiceInstanceID: environmentID, + PlanID: p.cfg.PlanID, + } + + requestBodyBytes, err := json.Marshal(requestBody) + if err != nil { + return CreatedBindingResponse{}, fmt.Errorf("failed to marshal request body: %v", err) + } + + resp, err := p.sendRequest(http.MethodPut, p.bindingsPath(environmentID), http.StatusAccepted, bytes.NewBuffer(requestBodyBytes)) + if err != nil { + return CreatedBindingResponse{}, err + } + + var binding CreatedBindingResponse + err = p.unmarshallResponse(resp, &binding) + if err != nil { + return CreatedBindingResponse{}, err + } + binding.ID = resp.Header.Get("location") + + return binding, nil +} + +func (p *ProvisioningClient) GetBinding(environmentID, bindingID string) (GetBindingResponse, error) { + resp, err := p.sendRequest(http.MethodGet, p.bindingsWithIDPath(environmentID, bindingID), http.StatusOK, nil) + if err != nil { + return GetBindingResponse{}, err + } + + var binding GetBindingResponse + err = p.unmarshallResponse(resp, &binding) + if err != nil { + return GetBindingResponse{}, err + } + + return binding, nil +} + +func (p *ProvisioningClient) DeleteBinding(environmentID, bindingID string) error { + if _, err := p.sendRequest(http.MethodDelete, p.bindingsWithIDPath(environmentID, bindingID), http.StatusOK, nil); err != nil { + return err + } + + return nil +} + +func (p *ProvisioningClient) GetAccessToken() error { + data := url.Values{} + data.Set("grant_type", "client_credentials") + + clientCredentials := fmt.Sprintf("%s:%s", p.cfg.ClientID, p.cfg.ClientSecret) + encodedCredentials := base64.StdEncoding.EncodeToString([]byte(clientCredentials)) + + req, err := http.NewRequest(http.MethodPost, p.accessTokenPath(), bytes.NewBufferString(data.Encode())) + if err != nil { + return fmt.Errorf("failed to create HTTP request: %v", err) + } + + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + req.Header.Set("Authorization", "Basic "+encodedCredentials) + + resp, err := p.cli.Do(req) + if err != nil { + return fmt.Errorf("failed to send request: %v", err) + } + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("unexpected status code: %d", resp.StatusCode) + } + + var accessToken AccessToken + err = p.unmarshallResponse(resp, &accessToken) + if err != nil { + return err + } + + p.accessToken = accessToken.Token + + return nil +} + +func (p *ProvisioningClient) AwaitEnvironmentCreated(environmentID string) error { + err := wait.PollUntilContextTimeout(p.ctx, interval, provisioningTimeout, true, func(ctx context.Context) (bool, error) { + environment, err := p.GetEnvironment(environmentID) + if err != nil { + p.logger.Warn(fmt.Sprintf("error getting environment: %v", err)) + return false, nil + } + p.logger.Info("Received environment state", "environmentID", environmentID, "state", environment.State) + if environment.State == OK { + return true, nil + } + return false, nil + }) + + if err != nil { + return fmt.Errorf("failed to wait for environment creation: %v", err) + } + + return nil +} + +func (p *ProvisioningClient) AwaitEnvironmentDeleted(environmentID string) error { + err := wait.PollUntilContextTimeout(p.ctx, interval, deprovisioningTimeout, true, func(ctx context.Context) (bool, error) { + environment, err := p.GetEnvironment(environmentID) + if err != nil { + if err.Error() == "unexpected status code 404: Environment instance not found" { + return true, nil + } + p.logger.Warn(fmt.Sprintf("error getting environment: %v", err)) + return false, nil + } + p.logger.Info("Received environment state", "environmentID", environmentID, "state", environment.State) + return false, nil + }) + + if err != nil { + return fmt.Errorf("failed to wait for environment deletion: %v", err) + } + + return nil +} + +func (p *ProvisioningClient) sendRequest(method string, url string, expectedStatus int, body io.Reader) (*http.Response, error) { + req, err := http.NewRequest(method, url, body) + if err != nil { + return nil, fmt.Errorf("failed to create HTTP request: %v", err) + } + + req.Header.Set("Authorization", "Bearer "+p.accessToken) + if method == http.MethodPost || method == http.MethodPatch || method == http.MethodPut { + req.Header.Add("Content-Type", "application/json") + } + + resp, err := p.cli.Do(req) + if err != nil { + return nil, fmt.Errorf("failed to send request: %v", err) + } + + if resp.StatusCode != expectedStatus { + var errorResponse ErrorResponse + err = p.unmarshallResponse(resp, &errorResponse) + if err != nil { + return nil, fmt.Errorf("unexpected status code %d: %v", resp.StatusCode, err) + } + return nil, fmt.Errorf("unexpected status code %d: %v", resp.StatusCode, errorResponse.Error.Message) + } + + return resp, nil +} + +func (p *ProvisioningClient) unmarshallResponse(resp *http.Response, output any) error { + defer resp.Body.Close() + body, err := io.ReadAll(resp.Body) + if err != nil { + return fmt.Errorf("failed to read response body: %v", err) + } + + if len(body) == 0 { + return fmt.Errorf("body is empty") + } + + if err := json.Unmarshal(body, output); err != nil { + return fmt.Errorf("failed to unmarshal response: %v", err) + } + + return nil +} + +func (p *ProvisioningClient) environmentsPath() string { + return fmt.Sprintf("%s%s", p.cfg.URL, environmentsPath) +} + +func (p *ProvisioningClient) environmentsWithIDPath(environmentID string) string { + return fmt.Sprintf("%s/%s", p.environmentsPath(), environmentID) +} + +func (p *ProvisioningClient) bindingsPath(environmentID string) string { + return fmt.Sprintf("%s%s", p.environmentsWithIDPath(environmentID), bindingsPath) +} + +func (p *ProvisioningClient) bindingsWithIDPath(environmentID string, bindingID string) string { + return fmt.Sprintf("%s/%s", p.bindingsPath(environmentID), bindingID) +} + +func (p *ProvisioningClient) accessTokenPath() string { + return fmt.Sprintf("%s%s", p.cfg.UAA_URL, accessTokenPath) +}