Skip to content

Commit

Permalink
Make colab runtime startable/stoppable (#12904) (#21207)
Browse files Browse the repository at this point in the history
[upstream:30fcb3c06ba3d5226289e15164fde21ae92b65a3]

Signed-off-by: Modular Magician <magic-modules@google.com>
  • Loading branch information
modular-magician authored Feb 3, 2025
1 parent 2f6d879 commit 6d156a5
Show file tree
Hide file tree
Showing 6 changed files with 477 additions and 65 deletions.
3 changes: 3 additions & 0 deletions .changelog/12904.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:enhancement
colab: made `google_colab_runtime` startable/stoppable.
```
166 changes: 102 additions & 64 deletions google/services/colab/resource_colab_runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,46 @@ import (
transport_tpg "github.com/hashicorp/terraform-provider-google/google/transport"
)

func ModifyColabRuntimeOperation(config *transport_tpg.Config, d *schema.ResourceData, project string, billingProject string, userAgent string, method string) (map[string]interface{}, error) {
url, err := tpgresource.ReplaceVars(d, config, "{{ColabBasePath}}projects/{{project}}/locations/{{location}}/notebookRuntimes/{{name}}:"+method)
if err != nil {
return nil, err
}

res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
Config: config,
Method: "POST",
Project: billingProject,
RawURL: url,
UserAgent: userAgent,
})
if err != nil {
return nil, fmt.Errorf("Unable to %q google_colab_runtime %q: %s", method, d.Id(), err)
}
return res, nil
}
func waitForColabOperation(config *transport_tpg.Config, d *schema.ResourceData, project string, billingProject string, userAgent string, response map[string]interface{}) error {
var opRes map[string]interface{}
err := ColabOperationWaitTimeWithResponse(
config, response, &opRes, project, "Waiting for Colab Runtime Operation", userAgent,
d.Timeout(schema.TimeoutUpdate))
if err != nil {
return err
}
return nil
}

func ModifyColabRuntime(config *transport_tpg.Config, d *schema.ResourceData, project string, billingProject string, userAgent string, method string) error {
dRes, err := ModifyColabRuntimeOperation(config, d, project, billingProject, userAgent, method)
if err != nil {
return err
}
if err := waitForColabOperation(config, d, project, billingProject, userAgent, dRes); err != nil {
return fmt.Errorf("Error with Colab runtime method: %s", err)
}
return nil
}

func ResourceColabRuntime() *schema.Resource {
return &schema.Resource{
Create: resourceColabRuntimeCreate,
Expand All @@ -56,26 +96,31 @@ func ResourceColabRuntime() *schema.Resource {
"display_name": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: `Required. The display name of the Runtime.`,
},
"location": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: `The location for the resource: https://cloud.google.com/colab/docs/locations`,
},
"runtime_user": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: `The user email of the NotebookRuntime.`,
},
"description": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Description: `The description of the Runtime.`,
},
"name": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Description: `The resource name of the Runtime`,
},
"notebook_runtime_template_ref": {
Expand All @@ -88,12 +133,24 @@ func ResourceColabRuntime() *schema.Resource {
"notebook_runtime_template": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
DiffSuppressFunc: tpgresource.ProjectNumberDiffSuppress,
Description: `The resource name of the NotebookRuntimeTemplate based on which a NotebookRuntime will be created.`,
},
},
},
},
"state": {
Type: schema.TypeString,
Computed: true,
Description: `Output only. The state of the runtime.`,
},
"desired_state": {
Type: schema.TypeString,
Optional: true,
Description: `Desired state of the Colab Runtime. Set this field to 'RUNNING' to start the runtime, and 'STOPPED' to stop it.`,
Default: "RUNNING",
},
"project": {
Type: schema.TypeString,
Optional: true,
Expand Down Expand Up @@ -194,6 +251,12 @@ func resourceColabRuntimeCreate(d *schema.ResourceData, meta interface{}) error
return fmt.Errorf("Error waiting to create Runtime: %s", err)
}

if p, ok := d.GetOk("desired_state"); ok && p.(string) == "STOPPED" {
if err := ModifyColabRuntime(config, d, project, billingProject, userAgent, "stop"); err != nil {
return err
}
}

log.Printf("[DEBUG] Finished creating Runtime %q: %#v", d.Id(), res)

return resourceColabRuntimeRead(d, meta)
Expand Down Expand Up @@ -237,6 +300,12 @@ func resourceColabRuntimeRead(d *schema.ResourceData, meta interface{}) error {
return transport_tpg.HandleNotFoundError(err, d, fmt.Sprintf("ColabRuntime %q", d.Id()))
}

// Explicitly set virtual fields to default values if unset
if _, ok := d.GetOkExists("desired_state"); !ok {
if err := d.Set("desired_state", "RUNNING"); err != nil {
return fmt.Errorf("Error setting desired_state: %s", err)
}
}
if err := d.Set("project", project); err != nil {
return fmt.Errorf("Error reading Runtime: %s", err)
}
Expand All @@ -253,95 +322,55 @@ func resourceColabRuntimeRead(d *schema.ResourceData, meta interface{}) error {
if err := d.Set("description", flattenColabRuntimeDescription(res["description"], d, config)); err != nil {
return fmt.Errorf("Error reading Runtime: %s", err)
}
if err := d.Set("state", flattenColabRuntimeState(res["state"], d, config)); err != nil {
return fmt.Errorf("Error reading Runtime: %s", err)
}

return nil
}

func resourceColabRuntimeUpdate(d *schema.ResourceData, meta interface{}) error {
config := meta.(*transport_tpg.Config)
userAgent, err := tpgresource.GenerateUserAgentString(d, config.UserAgent)
if err != nil {
return err
}

billingProject := ""
name := d.Get("name").(string)
state := d.Get("state").(string)
desired_state := d.Get("desired_state").(string)

project, err := tpgresource.GetProject(d, config)
if err != nil {
return fmt.Errorf("Error fetching project for Runtime: %s", err)
}
billingProject = project

obj := make(map[string]interface{})
notebookRuntimeTemplateRefProp, err := expandColabRuntimeNotebookRuntimeTemplateRef(d.Get("notebook_runtime_template_ref"), d, config)
if err != nil {
return err
} else if v, ok := d.GetOkExists("notebook_runtime_template_ref"); !tpgresource.IsEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, notebookRuntimeTemplateRefProp)) {
obj["notebookRuntimeTemplateRef"] = notebookRuntimeTemplateRefProp
}
runtimeUserProp, err := expandColabRuntimeRuntimeUser(d.Get("runtime_user"), d, config)
if err != nil {
return err
} else if v, ok := d.GetOkExists("runtime_user"); !tpgresource.IsEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, runtimeUserProp)) {
obj["runtimeUser"] = runtimeUserProp
}
displayNameProp, err := expandColabRuntimeDisplayName(d.Get("display_name"), d, config)
if err != nil {
return err
} else if v, ok := d.GetOkExists("display_name"); !tpgresource.IsEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, displayNameProp)) {
obj["displayName"] = displayNameProp
}
descriptionProp, err := expandColabRuntimeDescription(d.Get("description"), d, config)
if err != nil {
return err
} else if v, ok := d.GetOkExists("description"); !tpgresource.IsEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, descriptionProp)) {
obj["description"] = descriptionProp
}

obj, err = resourceColabRuntimeEncoder(d, meta, obj)
if err != nil {
return err
}

url, err := tpgresource.ReplaceVars(d, config, "{{ColabBasePath}}projects/{{project}}/locations/{{location}}/notebookRuntimes/{{name}}")
userAgent, err := tpgresource.GenerateUserAgentString(d, config.UserAgent)
if err != nil {
return err
}

log.Printf("[DEBUG] Updating Runtime %q: %#v", d.Id(), obj)
headers := make(http.Header)

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

res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
Config: config,
Method: "PUT",
Project: billingProject,
RawURL: url,
UserAgent: userAgent,
Body: obj,
Timeout: d.Timeout(schema.TimeoutUpdate),
Headers: headers,
})
if desired_state != "" && state != desired_state {
var verb string

if err != nil {
return fmt.Errorf("Error updating Runtime %q: %s", d.Id(), err)
} else {
log.Printf("[DEBUG] Finished updating Runtime %q: %#v", d.Id(), res)
}
switch desired_state {
case "STOPPED":
verb = "stop"
case "RUNNING":
verb = "start"
default:
return fmt.Errorf("desired_state has to be RUNNING or STOPPED")
}

err = ColabOperationWaitTime(
config, res, project, "Updating Runtime", userAgent,
d.Timeout(schema.TimeoutUpdate))
if err := ModifyColabRuntime(config, d, project, billingProject, userAgent, verb); err != nil {
return err
}

if err != nil {
return err
} else {
log.Printf("[DEBUG] Colab runtime %q has state %q.", name, state)
}

return resourceColabRuntimeRead(d, meta)
return nil
}

func resourceColabRuntimeDelete(d *schema.ResourceData, meta interface{}) error {
Expand Down Expand Up @@ -417,6 +446,11 @@ func resourceColabRuntimeImport(d *schema.ResourceData, meta interface{}) ([]*sc
}
d.SetId(id)

// Explicitly set virtual fields to default values on import
if err := d.Set("desired_state", "RUNNING"); err != nil {
return nil, fmt.Errorf("Error setting desired_state: %s", err)
}

return []*schema.ResourceData{d}, nil
}

Expand Down Expand Up @@ -449,6 +483,10 @@ func flattenColabRuntimeDescription(v interface{}, d *schema.ResourceData, confi
return v
}

func flattenColabRuntimeState(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} {
return v
}

func expandColabRuntimeNotebookRuntimeTemplateRef(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) {
l := v.([]interface{})
if len(l) == 0 || l[0] == nil {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@ api_version: 'v1'
api_resource_type_kind: 'NotebookRuntime'
fields:
- field: 'description'
- field: 'desired_state'
provider_only: true
- field: 'display_name'
- field: 'location'
provider_only: true
- field: 'name'
provider_only: true
- field: 'notebook_runtime_template_ref.notebook_runtime_template'
- field: 'runtime_user'
- field: 'state'
65 changes: 64 additions & 1 deletion google/services/colab/resource_colab_runtime_generated_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,67 @@ resource "google_colab_runtime" "runtime" {
`, context)
}

func TestAccColabRuntime_colabRuntimeStoppedExample(t *testing.T) {
t.Parallel()

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

acctest.VcrTest(t, resource.TestCase{
PreCheck: func() { acctest.AccTestPreCheck(t) },
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
CheckDestroy: testAccCheckColabRuntimeDestroyProducer(t),
Steps: []resource.TestStep{
{
Config: testAccColabRuntime_colabRuntimeStoppedExample(context),
},
{
ResourceName: "google_colab_runtime.runtime",
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"desired_state", "location", "name"},
},
},
})
}

func testAccColabRuntime_colabRuntimeStoppedExample(context map[string]interface{}) string {
return acctest.Nprintf(`
resource "google_colab_runtime_template" "my_template" {
name = "tf-test-colab-runtime%{random_suffix}"
display_name = "Runtime template basic"
location = "us-central1"
machine_spec {
machine_type = "e2-standard-4"
}
network_spec {
enable_internet_access = true
}
}
resource "google_colab_runtime" "runtime" {
name = "tf-test-colab-runtime%{random_suffix}"
location = "us-central1"
notebook_runtime_template_ref {
notebook_runtime_template = google_colab_runtime_template.my_template.id
}
desired_state = "STOPPED"
display_name = "Runtime stopped"
runtime_user = "gterraformtestuser@gmail.com"
depends_on = [
google_colab_runtime_template.my_template,
]
}
`, context)
}

func TestAccColabRuntime_colabRuntimeFullExample(t *testing.T) {
t.Parallel()

Expand All @@ -109,7 +170,7 @@ func TestAccColabRuntime_colabRuntimeFullExample(t *testing.T) {
ResourceName: "google_colab_runtime.runtime",
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"location", "name"},
ImportStateVerifyIgnore: []string{"desired_state", "location", "name"},
},
},
})
Expand Down Expand Up @@ -172,6 +233,8 @@ resource "google_colab_runtime" "runtime" {
runtime_user = "gterraformtestuser@gmail.com"
description = "Full runtime"
desired_state = "ACTIVE"
depends_on = [
google_colab_runtime_template.my_template
]
Expand Down
Loading

0 comments on commit 6d156a5

Please sign in to comment.