Skip to content

Commit 6015012

Browse files
Make notebooks wait for active state and enable stopping/starting ins… (#9971) (#17268)
* Make notebooks wait for active state and enable stopping/starting instances using the desired_state field * ignore update_time [upstream:18072d2b0665b0182e8bcd677fc1d0ca75693507] Signed-off-by: Modular Magician <[email protected]>
1 parent aae7853 commit 6015012

File tree

5 files changed

+172
-0
lines changed

5 files changed

+172
-0
lines changed

.changelog/9971.txt

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:enhancement
2+
made `google_notebooks_instance` wait for active state on creation and enable stopping/starting instances.
3+
```

google/services/notebooks/resource_notebooks_instance.go

+102
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
"time"
2828

2929
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff"
30+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
3031
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
3132

3233
"github.com/hashicorp/terraform-provider-google/google/tpgresource"
@@ -70,6 +71,55 @@ func NotebooksInstanceKmsDiffSuppress(_, old, new string, _ *schema.ResourceData
7071
return false
7172
}
7273

74+
// waitForNotebooksInstanceActive waits for an Notebook instance to become "ACTIVE"
75+
func waitForNotebooksInstanceActive(d *schema.ResourceData, config *transport_tpg.Config, timeout time.Duration) error {
76+
return resource.Retry(timeout, func() *resource.RetryError {
77+
if err := resourceNotebooksInstanceRead(d, config); err != nil {
78+
return resource.NonRetryableError(err)
79+
}
80+
81+
name := d.Get("name").(string)
82+
state := d.Get("state").(string)
83+
if state == "ACTIVE" {
84+
log.Printf("[DEBUG] Notebook Instance %q has state %q.", name, state)
85+
return nil
86+
} else {
87+
return resource.RetryableError(fmt.Errorf("Notebook Instance %q has state %q. Waiting for ACTIVE state", name, state))
88+
}
89+
90+
})
91+
}
92+
93+
func modifyNotebooksInstanceState(config *transport_tpg.Config, d *schema.ResourceData, project string, billingProject string, userAgent string, state string) (map[string]interface{}, error) {
94+
url, err := tpgresource.ReplaceVars(d, config, "{{NotebooksBasePath}}projects/{{project}}/locations/{{location}}/instances/{{name}}:"+state)
95+
if err != nil {
96+
return nil, err
97+
}
98+
99+
res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
100+
Config: config,
101+
Method: "POST",
102+
Project: billingProject,
103+
RawURL: url,
104+
UserAgent: userAgent,
105+
})
106+
if err != nil {
107+
return nil, fmt.Errorf("Unable to %q google_notebooks_instance %q: %s", state, d.Id(), err)
108+
}
109+
return res, nil
110+
}
111+
112+
func waitForNotebooksOperation(config *transport_tpg.Config, d *schema.ResourceData, project string, billingProject string, userAgent string, response map[string]interface{}) error {
113+
var opRes map[string]interface{}
114+
err := NotebooksOperationWaitTimeWithResponse(
115+
config, response, &opRes, project, "Modifying Notebook Instance state", userAgent,
116+
d.Timeout(schema.TimeoutUpdate))
117+
if err != nil {
118+
return err
119+
}
120+
return nil
121+
}
122+
73123
func ResourceNotebooksInstance() *schema.Resource {
74124
return &schema.Resource{
75125
Create: resourceNotebooksInstanceCreate,
@@ -496,6 +546,12 @@ the population of this value.`,
496546
Optional: true,
497547
Description: `Instance update time.`,
498548
},
549+
"desired_state": {
550+
Type: schema.TypeString,
551+
Optional: true,
552+
Default: "ACTIVE",
553+
Description: `Desired state of the Notebook Instance. Set this field to 'ACTIVE' to start the Instance, and 'STOPPED' to stop the Instance.`,
554+
},
499555
"project": {
500556
Type: schema.TypeString,
501557
Optional: true,
@@ -737,6 +793,20 @@ func resourceNotebooksInstanceCreate(d *schema.ResourceData, meta interface{}) e
737793
}
738794
d.SetId(id)
739795

796+
if err := waitForNotebooksInstanceActive(d, config, d.Timeout(schema.TimeoutCreate)-time.Minute); err != nil {
797+
return fmt.Errorf("Notebook instance %q did not reach ACTIVE state: %q", d.Get("name").(string), err)
798+
}
799+
800+
if p, ok := d.GetOk("desired_state"); ok && p.(string) == "STOPPED" {
801+
dRes, err := modifyNotebooksInstanceState(config, d, project, billingProject, userAgent, "stop")
802+
if err != nil {
803+
return err
804+
}
805+
if err := waitForNotebooksOperation(config, d, project, billingProject, userAgent, dRes); err != nil {
806+
return fmt.Errorf("Error stopping Notebook Instance: %s", err)
807+
}
808+
}
809+
740810
log.Printf("[DEBUG] Finished creating Instance %q: %#v", d.Id(), res)
741811

742812
return resourceNotebooksInstanceRead(d, meta)
@@ -778,6 +848,12 @@ func resourceNotebooksInstanceRead(d *schema.ResourceData, meta interface{}) err
778848
return transport_tpg.HandleNotFoundError(err, d, fmt.Sprintf("NotebooksInstance %q", d.Id()))
779849
}
780850

851+
// Explicitly set virtual fields to default values if unset
852+
if _, ok := d.GetOkExists("desired_state"); !ok {
853+
if err := d.Set("desired_state", "ACTIVE"); err != nil {
854+
return fmt.Errorf("Error setting desired_state: %s", err)
855+
}
856+
}
781857
if err := d.Set("project", project); err != nil {
782858
return fmt.Errorf("Error reading Instance: %s", err)
783859
}
@@ -972,6 +1048,27 @@ func resourceNotebooksInstanceUpdate(d *schema.ResourceData, meta interface{}) e
9721048

9731049
d.Partial(false)
9741050

1051+
name := d.Get("name").(string)
1052+
state := d.Get("state").(string)
1053+
desired_state := d.Get("desired_state").(string)
1054+
1055+
if state != desired_state {
1056+
verb := "start"
1057+
if desired_state == "STOPPED" {
1058+
verb = "stop"
1059+
}
1060+
pRes, err := modifyNotebooksInstanceState(config, d, project, billingProject, userAgent, verb)
1061+
if err != nil {
1062+
return err
1063+
}
1064+
1065+
if err := waitForNotebooksOperation(config, d, project, billingProject, userAgent, pRes); err != nil {
1066+
return fmt.Errorf("Error waiting to modify Notebook Instance state: %s", err)
1067+
}
1068+
1069+
} else {
1070+
log.Printf("[DEBUG] Notebook Instance %q has state %q.", name, state)
1071+
}
9751072
return resourceNotebooksInstanceRead(d, meta)
9761073
}
9771074

@@ -1045,6 +1142,11 @@ func resourceNotebooksInstanceImport(d *schema.ResourceData, meta interface{}) (
10451142
}
10461143
d.SetId(id)
10471144

1145+
// Explicitly set virtual fields to default values on import
1146+
if err := d.Set("desired_state", "ACTIVE"); err != nil {
1147+
return nil, fmt.Errorf("Error setting desired_state: %s", err)
1148+
}
1149+
10481150
return []*schema.ResourceData{d}, nil
10491151
}
10501152

google/services/notebooks/resource_notebooks_instance_generated_test.go

+41
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,46 @@ resource "google_notebooks_instance" "instance" {
7070
`, context)
7171
}
7272

73+
func TestAccNotebooksInstance_notebookInstanceBasicStoppedExample(t *testing.T) {
74+
t.Parallel()
75+
76+
context := map[string]interface{}{
77+
"random_suffix": acctest.RandString(t, 10),
78+
}
79+
80+
acctest.VcrTest(t, resource.TestCase{
81+
PreCheck: func() { acctest.AccTestPreCheck(t) },
82+
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
83+
CheckDestroy: testAccCheckNotebooksInstanceDestroyProducer(t),
84+
Steps: []resource.TestStep{
85+
{
86+
Config: testAccNotebooksInstance_notebookInstanceBasicStoppedExample(context),
87+
},
88+
{
89+
ResourceName: "google_notebooks_instance.instance",
90+
ImportState: true,
91+
ImportStateVerify: true,
92+
ImportStateVerifyIgnore: []string{"name", "instance_owners", "boot_disk_type", "boot_disk_size_gb", "data_disk_type", "data_disk_size_gb", "no_remove_data_disk", "metadata", "vm_image", "container_image", "location", "desired_state", "labels", "terraform_labels"},
93+
},
94+
},
95+
})
96+
}
97+
98+
func testAccNotebooksInstance_notebookInstanceBasicStoppedExample(context map[string]interface{}) string {
99+
return acctest.Nprintf(`
100+
resource "google_notebooks_instance" "instance" {
101+
name = "tf-test-notebooks-instance%{random_suffix}"
102+
location = "us-west1-a"
103+
machine_type = "e2-medium"
104+
vm_image {
105+
project = "deeplearning-platform-release"
106+
image_family = "tf-latest-cpu"
107+
}
108+
desired_state = "STOPPED"
109+
}
110+
`, context)
111+
}
112+
73113
func TestAccNotebooksInstance_notebookInstanceBasicContainerExample(t *testing.T) {
74114
t.Parallel()
75115

@@ -225,6 +265,7 @@ resource "google_notebooks_instance" "instance" {
225265
]
226266
disk_encryption = "CMEK"
227267
kms_key = "%{key_name}"
268+
desired_state = "ACTIVE"
228269
}
229270
230271
data "google_compute_network" "my_network" {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
package notebooks_test

website/docs/r/notebooks_instance.html.markdown

+23
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,26 @@ resource "google_notebooks_instance" "instance" {
5353
}
5454
}
5555
```
56+
<div class = "oics-button" style="float: right; margin: 0 0 -15px">
57+
<a href="https://console.cloud.google.com/cloudshell/open?cloudshell_git_repo=https%3A%2F%2Fgithub.jpy.wang%2Fterraform-google-modules%2Fdocs-examples.git&cloudshell_working_dir=notebook_instance_basic_stopped&cloudshell_image=gcr.io%2Fcloudshell-images%2Fcloudshell%3Alatest&open_in_editor=main.tf&cloudshell_print=.%2Fmotd&cloudshell_tutorial=.%2Ftutorial.md" target="_blank">
58+
<img alt="Open in Cloud Shell" src="//gstatic.com/cloudssh/images/open-btn.svg" style="max-height: 44px; margin: 32px auto; max-width: 100%;">
59+
</a>
60+
</div>
61+
## Example Usage - Notebook Instance Basic Stopped
62+
63+
64+
```hcl
65+
resource "google_notebooks_instance" "instance" {
66+
name = "notebooks-instance"
67+
location = "us-west1-a"
68+
machine_type = "e2-medium"
69+
vm_image {
70+
project = "deeplearning-platform-release"
71+
image_family = "tf-latest-cpu"
72+
}
73+
desired_state = "STOPPED"
74+
}
75+
```
5676
<div class = "oics-button" style="float: right; margin: 0 0 -15px">
5777
<a href="https://console.cloud.google.com/cloudshell/open?cloudshell_git_repo=https%3A%2F%2Fgithub.jpy.wang%2Fterraform-google-modules%2Fdocs-examples.git&cloudshell_working_dir=notebook_instance_basic_container&cloudshell_image=gcr.io%2Fcloudshell-images%2Fcloudshell%3Alatest&open_in_editor=main.tf&cloudshell_print=.%2Fmotd&cloudshell_tutorial=.%2Ftutorial.md" target="_blank">
5878
<img alt="Open in Cloud Shell" src="//gstatic.com/cloudssh/images/open-btn.svg" style="max-height: 44px; margin: 32px auto; max-width: 100%;">
@@ -143,6 +163,7 @@ resource "google_notebooks_instance" "instance" {
143163
]
144164
disk_encryption = "CMEK"
145165
kms_key = "my-crypto-key"
166+
desired_state = "ACTIVE"
146167
}
147168
148169
data "google_compute_network" "my_network" {
@@ -324,6 +345,8 @@ The following arguments are supported:
324345
* `project` - (Optional) The ID of the project in which the resource belongs.
325346
If it is not provided, the provider project is used.
326347

348+
* `desired_state` - (Optional) Desired state of the Notebook Instance. Set this field to `ACTIVE` to start the Instance, and `STOPPED` to stop the Instance.
349+
327350

328351
<a name="nested_accelerator_config"></a>The `accelerator_config` block supports:
329352

0 commit comments

Comments
 (0)