Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 119 additions & 0 deletions mmv1/products/saasservicemgmt/Saas.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,24 @@ examples:
- name: saas_runtime_saas_basic
primary_resource_id: "example"
min_version: 'beta'
ignore_read_extra:
- blueprint_repo
- conditions
- state
- update_time
- etag
- application_template.0.sync_operation
vars:
saas_name: test-saas
test_env_vars:
project: 'PROJECT_NAME'
bootstrap_iam:
- member: "serviceAccount:service-{project_number}@gcp-sa-saasservicemgmt.iam.gserviceaccount.com"
role: "roles/saasservicemgmt.serviceAgent"
autogen_async: false
autogen_status: U2Fhcw==
custom_code:
custom_delete: 'templates/terraform/custom_delete/saas_runtime_saas_delete.go.tmpl'
parameters:
- name: location
type: String
Expand All @@ -56,10 +67,108 @@ properties:
They are not queryable and should be preserved when modifying objects.

More info: https://kubernetes.io/docs/user-guide/annotations
- name: applicationTemplate
type: NestedObject
description: |-
CompositeRef represents a reference to a composite resource.
properties:
- name: applicationTemplate
type: String
required: true
description: Reference to the ApplicationTemplate resource.
- name: revision
type: String
description: |-
Revision of the ApplicationTemplate to use.
Changes to revision will trigger manual resynchronization.
If empty, ApplicationTemplate will be ignored.
- name: syncOperation
type: String
description: |-
Reference to on-going AppTemplate import and replication operation (i.e.
the operation_id for the long-running operation).
This field is opaque for external usage.
output: true
- name: blueprintRepo
type: String
description: |-
Name of repository in Artifact Registry for system-generated Blueprints,
eg. Blueprints of imported ApplicationTemplates.
output: true
- name: conditions
type: Array
description: |-
A set of conditions which indicate the various conditions this resource can
have.
output: true
Comment thread
neil-dey marked this conversation as resolved.
item_type:
type: NestedObject
properties:
- name: lastTransitionTime
type: String
description: Last time the condition transited from one status to another.
output: true
- name: message
type: String
description: Human readable message indicating details about the last transition.
output: true
- name: reason
type: String
description: Brief reason for the condition's last transition.
output: true
- name: status
type: String
description: |-
Status of the condition.
Possible values:
STATUS_UNKNOWN
STATUS_TRUE
STATUS_FALSE
output: true
- name: type
type: String
description: |-
Type of the condition.
Possible values:
TYPE_READY
TYPE_SYNCHRONIZED
output: true
- name: createTime
type: String
description: The timestamp when the resource was created.
output: true
- name: error
type: NestedObject
output: true
Comment thread
neil-dey marked this conversation as resolved.
description: |-
The `Status` type defines a logical error model that is suitable for
different programming environments, including REST APIs and RPC APIs. It is
used by [gRPC](https://github.com/grpc). Each `Status` message contains
three pieces of data: error code, error message, and error details.

You can find out more about this error model and how to work with it in the
[API Design Guide](https://cloud.google.com/apis/design/errors).
properties:
- name: code
type: Integer
description: The status code, which should be an enum value of google.rpc.Code.
output: true
- name: details
type: Array
description: |-
A list of messages that carry the error details. There is a common set of
message types for APIs to use.
item_type:
type: NestedObject
properties: []
output: true
- name: message
type: String
description: |-
A developer-facing error message, which should be in English. Any
user-facing error message should be localized and sent in the
google.rpc.Status.details field, or localized by the client.
output: true
- name: etag
type: String
description: |-
Expand Down Expand Up @@ -91,6 +200,16 @@ properties:

"projects/{project}/locations/{location}/saas/{saas}"
output: true
- name: state
type: String
description: |-
State of the Saas.
It is always in STATE_ACTIVE state if the application_template is empty.
Possible values:
STATE_ACTIVE
STATE_RUNNING
STATE_FAILED
output: true
- name: uid
type: String
description: |-
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
billingProject := ""

project, err := tpgresource.GetProject(d, config)
if err != nil {
return fmt.Errorf("Error fetching project for Saas: %s", err)
}
billingProject = project
url, err := tpgresource.ReplaceVars(d, config, fmt.Sprintf("%s%s", transport_tpg.BaseUrl(Product, config), "projects/{{"{{"}}project{{"}}"}}/locations/{{"{{"}}location{{"}}"}}/saas/{{"{{"}}saas_id{{"}}"}}"))
if err != nil {
return err
}

// err == nil indicates that the billing_project value was found
if bp, err := tpgresource.GetBillingProject(d, config); err == nil {
billingProject = bp
}

headers := make(http.Header)

// 1. Wait for the resource to transition out of STATE_RUNNING before deleting
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How long does this reasonably take? Should we set limits here on how long to wait on this?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current implementation uses the default timeout of 20 minutes, which we believe is reasonable. Furthermore, leaving the default lets the user configure it if needed; if we set a hardcoded timeout, users would lose the ability to configure a custom value. https://developer.hashicorp.com/terraform/plugin/sdkv2/resources/retries-and-customizable-timeouts

log.Printf("[DEBUG] Waiting for Saas %q to transition out of STATE_RUNNING", d.Id())
err = retry.RetryContext(context.Background(), d.Timeout(schema.TimeoutDelete), func() *retry.RetryError {
res, readErr := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
Config: config,
Method: "GET",
Project: billingProject,
RawURL: url,
UserAgent: userAgent,
Headers: headers,
})
if readErr != nil {
if transport_tpg.IsGoogleApiErrorWithCode(readErr, 404) {
return nil // already gone
}
return retry.NonRetryableError(readErr)
}

state, ok := res["state"].(string)
if !ok {
return retry.RetryableError(fmt.Errorf("Saas %q state is not yet populated", d.Id()))
}
if state == "STATE_RUNNING" {
return retry.RetryableError(fmt.Errorf("Saas %q is still in STATE_RUNNING state", d.Id()))
}

return nil
})
if err != nil {
return fmt.Errorf("Error waiting for Saas %q to be ready for deletion: %s", d.Id(), err)
}

// 2. Trigger the DELETE request
log.Printf("[DEBUG] Deleting Saas %q", d.Id())
res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
Config: config,
Method: "DELETE",
Project: billingProject,
RawURL: url,
UserAgent: userAgent,
Timeout: d.Timeout(schema.TimeoutDelete),
Headers: headers,
})
if err != nil {
return transport_tpg.HandleNotFoundError(err, d, "Saas")
}

log.Printf("[DEBUG] Finished trigger for deleting Saas %q: %#v", d.Id(), res)

// 3. Wait for the Saas resource to be fully deleted from GCP (eventual consistency)
err = retry.RetryContext(context.Background(), d.Timeout(schema.TimeoutDelete), func() *retry.RetryError {
_, readErr := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
Config: config,
Method: "GET",
Project: billingProject,
RawURL: url,
UserAgent: userAgent,
Headers: headers,
})
if readErr != nil {
if transport_tpg.IsGoogleApiErrorWithCode(readErr, 404) {
return nil
}
return retry.NonRetryableError(readErr)
}
return retry.RetryableError(fmt.Errorf("Saas %q still exists", d.Id()))
})
if err != nil {
return fmt.Errorf("Error waiting for Saas %q to be fully deleted: %s", d.Id(), err)
}

return nil
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,9 @@ resource "google_saas_runtime_saas" "{{$.PrimaryResourceId}}" {
locations {
name = "europe-west1"
}

application_template {
application_template = "projects/%{project}/locations/global/applicationTemplates/my-template"
revision = "r1"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/hashicorp/terraform-plugin-testing/plancheck"

"github.com/hashicorp/terraform-provider-google/google/acctest"
"github.com/hashicorp/terraform-provider-google/google/envvar"
_ "github.com/hashicorp/terraform-provider-google/google/services/saasruntime"
"github.com/hashicorp/terraform-provider-google/google/services/resourcemanager"
)
Expand All @@ -24,6 +25,7 @@ func TestAccSaasRuntimeSaas_update(t *testing.T) {

context := map[string]interface{}{
"random_suffix": acctest.RandString(t, 10),
"project": envvar.GetTestProjectFromEnv(),
}

acctest.VcrTest(t, resource.TestCase{
Expand All @@ -37,7 +39,7 @@ func TestAccSaasRuntimeSaas_update(t *testing.T) {
ResourceName: "google_saas_runtime_saas.example",
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"annotations", "labels", "location", "saas_id", "terraform_labels"},
ImportStateVerifyIgnore: []string{"annotations", "labels", "location", "saas_id", "terraform_labels", "blueprint_repo", "conditions", "state", "update_time", "etag"},
},
{
Config: testAccSaasRuntimeSaas_update(context),
Expand All @@ -51,7 +53,7 @@ func TestAccSaasRuntimeSaas_update(t *testing.T) {
ResourceName: "google_saas_runtime_saas.example",
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"annotations", "labels", "location", "saas_id", "terraform_labels"},
ImportStateVerifyIgnore: []string{"annotations", "labels", "location", "saas_id", "terraform_labels", "blueprint_repo", "conditions", "state", "update_time", "etag"},
},
},
})
Expand All @@ -70,6 +72,10 @@ resource "google_saas_runtime_saas" "example" {
locations {
name = "europe-west1"
}

application_template {
application_template = "projects/%{project}/locations/global/applicationTemplates/my-template"
}
}
`, context)
}
Expand All @@ -95,6 +101,11 @@ resource "google_saas_runtime_saas" "example" {
annotations = {
"annotation-one": "bar"
}

application_template {
application_template = "projects/%{project}/locations/global/applicationTemplates/my-template"
revision = "r2"
}
}
`, context)
}
Expand Down
Loading