Skip to content

Commit c73845d

Browse files
committed
Add resource_sql_provision_script
1 parent f7de501 commit c73845d

File tree

7 files changed

+435
-1
lines changed

7 files changed

+435
-1
lines changed

mmv1/third_party/terraform/acctest/resource_inventory_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,7 @@ func TestValidateResourceMetadata(t *testing.T) {
279279
if r.ApiVersion == "" && !slices.Contains(ignoredResources, resourceName) {
280280
t.Errorf("%s: `api_version` is required and not set", r.FileName)
281281
}
282-
if r.ApiResourceTypeKind == "" {
282+
if r.ApiResourceTypeKind == "" && resourceName != "google_sql_provision_script" {
283283
t.Errorf("%s: `api_resource_type_kind` is required and not set", r.FileName)
284284
}
285285
}

mmv1/third_party/terraform/provider/provider_mmv1_resources.go.tmpl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,7 @@ var handwrittenResources = map[string]*schema.Resource{
450450
{{- end }}
451451
"google_service_networking_connection": servicenetworking.ResourceServiceNetworkingConnection(),
452452
"google_sql_database_instance": sql.ResourceSqlDatabaseInstance(),
453+
"google_sql_provision_script": sql.ResourceSqlProvisionScript(),
453454
"google_sql_ssl_cert": sql.ResourceSqlSslCert(),
454455
"google_sql_user": sql.ResourceSqlUser(),
455456
"google_organization_iam_custom_role": resourcemanager.ResourceGoogleOrganizationIamCustomRole(),
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
package sql
2+
3+
import (
4+
"fmt"
5+
"log"
6+
7+
"github.com/hashicorp/terraform-provider-google/google/tpgresource"
8+
transport_tpg "github.com/hashicorp/terraform-provider-google/google/transport"
9+
10+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff"
11+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
12+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
13+
sqladmin "google.golang.org/api/sqladmin/v1beta4"
14+
)
15+
16+
func ResourceSqlProvisionScript() *schema.Resource {
17+
return &schema.Resource{
18+
Create: resourceSqlProvisionScriptCreate,
19+
Read: resourceSqlProvisionScriptRead,
20+
Update: resourceSqlProvisionScriptUpdate,
21+
Delete: resourceSqlProvisionScriptDelete,
22+
CustomizeDiff: customdiff.All(
23+
tpgresource.DefaultProviderProject,
24+
),
25+
26+
SchemaVersion: 1,
27+
28+
Schema: map[string]*schema.Schema{
29+
"name": {
30+
Type: schema.TypeString,
31+
Required: true,
32+
Description: `The name of the provision script.`,
33+
},
34+
35+
"script": {
36+
Type: schema.TypeString,
37+
Required: true,
38+
ForceNew: true,
39+
Description: `The SQL script to provision database resources. Its execution time limit is 30 s.
40+
Changing this forces the script to be rerun. Make sure the script is idempotent.
41+
You can use statements like "create if not exists …" or
42+
"if not exists (select …) then … end if" to prevent existence-related errors. If it's not
43+
possible to make a statement idempotent, you can run it once and then remove it from this script.`,
44+
},
45+
46+
"instance": {
47+
Type: schema.TypeString,
48+
Required: true,
49+
ForceNew: true,
50+
Description: `The name of the Cloud SQL instance. Changing this forces the script to be run on the new instance.`,
51+
},
52+
53+
"database": {
54+
Type: schema.TypeString,
55+
Optional: true,
56+
ForceNew: true,
57+
Description: `The name of the database on which the SQL script is executed. This is required for Postgres
58+
instances. Changing this forces the script to be run using this database.`,
59+
},
60+
61+
"deletion_policy": {
62+
Type: schema.TypeString,
63+
Optional: true,
64+
Default: "ABANDON",
65+
Description: `The deletion policy for the resources created by the script. The default is "ABANDON".
66+
It must be "ABANDON" to allow Terraform to abandon the resources. If you want to delete resources, add statements
67+
in the script such as "drop … if exists".`,
68+
ValidateFunc: validation.StringInSlice([]string{"ABANDON"}, false),
69+
},
70+
},
71+
UseJSONNumber: true,
72+
}
73+
}
74+
75+
func resourceSqlProvisionScriptCreate(d *schema.ResourceData, meta interface{}) error {
76+
config := meta.(*transport_tpg.Config)
77+
userAgent, err := tpgresource.GenerateUserAgentString(d, config.UserAgent)
78+
if err != nil {
79+
return err
80+
}
81+
82+
project, err := tpgresource.GetProject(d, config)
83+
if err != nil {
84+
return err
85+
}
86+
87+
name := d.Get("name").(string)
88+
instance := d.Get("instance").(string)
89+
script := d.Get("script").(string)
90+
91+
var database string
92+
if db, ok := d.GetOk("database"); ok {
93+
database = db.(string)
94+
}
95+
96+
executeSqlPayload := &sqladmin.ExecuteSqlPayload{
97+
SqlStatement: script,
98+
Database: database,
99+
AutoIamAuthn: true,
100+
Application: "Terraform",
101+
}
102+
103+
transport_tpg.MutexStore.Lock(instanceMutexKey(project, instance))
104+
defer transport_tpg.MutexStore.Unlock(instanceMutexKey(project, instance))
105+
106+
var databaseInstance *sqladmin.DatabaseInstance
107+
err = transport_tpg.Retry(transport_tpg.RetryOptions{
108+
RetryFunc: func() (rerr error) {
109+
databaseInstance, rerr = config.NewSqlAdminClient(userAgent).Instances.Get(project, instance).Do()
110+
return rerr
111+
},
112+
Timeout: d.Timeout(schema.TimeoutRead),
113+
ErrorRetryPredicates: []transport_tpg.RetryErrorPredicateFunc{transport_tpg.IsSqlOperationInProgressError},
114+
})
115+
if err != nil {
116+
return transport_tpg.HandleNotFoundError(err, d, fmt.Sprintf("SQL Database Instance %q", d.Get("instance").(string)))
117+
}
118+
if databaseInstance.Settings.ActivationPolicy != "ALWAYS" {
119+
return fmt.Errorf("Error, failed to run script %s because instance %s is not up", name, instance)
120+
}
121+
122+
log.Printf("[INFO] executing script %s on database %s on instance %s", name, database, instance)
123+
124+
var resp *sqladmin.SqlInstancesExecuteSqlResponse
125+
resp, err = config.NewSqlAdminClient(userAgent).Instances.ExecuteSql(project, instance,
126+
executeSqlPayload).Do()
127+
128+
if err != nil {
129+
return fmt.Errorf("Error, failed to run script %s on instance %s: %v", name, instance, err)
130+
} else if resp.Status != nil && resp.Status.Code != 0 {
131+
return fmt.Errorf("Error, failed to run script %s on instance %s: %v", name, instance, resp.Status.Message)
132+
}
133+
log.Printf("[INFO] response from the execution of script %s on instance %s: %+v", name, instance, resp)
134+
135+
d.SetId(fmt.Sprintf("%s/%s", instance, name))
136+
return nil
137+
}
138+
139+
func resourceSqlProvisionScriptRead(d *schema.ResourceData, meta interface{}) error {
140+
return nil
141+
}
142+
143+
func resourceSqlProvisionScriptUpdate(d *schema.ResourceData, meta interface{}) error {
144+
name := d.Get("name").(string)
145+
instance := d.Get("instance").(string)
146+
d.SetId(fmt.Sprintf("%s/%s", instance, name))
147+
return nil
148+
}
149+
150+
func resourceSqlProvisionScriptDelete(d *schema.ResourceData, meta interface{}) error {
151+
return nil
152+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
resource: 'google_sql_provision_script'
2+
generation_type: 'handwritten'
3+
api_service_name: 'sqladmin.googleapis.com'
4+
api_version: 'v1beta4'
5+
#api_resource_type_kind: none. Not associated with any REST resource.
6+
fields:
7+
- field: 'name'
8+
- field: 'script'
9+
- field: 'instance'
10+
- field: 'database'
11+
- field: 'deletion_policy'
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
package sql_test
2+
3+
import (
4+
"fmt"
5+
"regexp"
6+
"strings"
7+
"testing"
8+
9+
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
10+
"github.com/hashicorp/terraform-provider-google/google/acctest"
11+
"github.com/hashicorp/terraform-provider-google/google/envvar"
12+
)
13+
14+
const (
15+
// Set to your account when manually running this test. Not needed in automated tests.
16+
manual_test_user = "abc@example.com"
17+
)
18+
19+
func TestAccSqlProvisionScriptMySql(t *testing.T) {
20+
t.Parallel()
21+
serviceAcct := envvar.GetTestServiceAccountFromEnv(t)
22+
t.Log("Test service account is", serviceAcct)
23+
24+
instance := fmt.Sprintf("tf-test-%d", acctest.RandInt(t))
25+
scriptName := fmt.Sprintf("tf-test-%d", acctest.RandInt(t))
26+
script := "CREATE USER IF NOT EXISTS 'user'@'%' IDENTIFIED BY RANDOM PASSWORD; GRANT SELECT ON *.* to 'user'@'%';"
27+
acctest.VcrTest(t, resource.TestCase{
28+
PreCheck: func() { acctest.AccTestPreCheck(t) },
29+
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
30+
CheckDestroy: testAccSqlUserDestroyProducer(t),
31+
Steps: []resource.TestStep{
32+
{
33+
Config: fmt.Sprintf(
34+
testGoogleSqlProvisionScript_mysql, instance, serviceAcct, manual_test_user, scriptName, script),
35+
Check: resource.ComposeTestCheckFunc(
36+
resource.TestCheckNoResourceAttr("google_sql_provision_script.script", "database"),
37+
resource.TestCheckResourceAttr("google_sql_provision_script.script", "deletion_policy", "ABANDON"),
38+
),
39+
},
40+
{
41+
Config: fmt.Sprintf(
42+
testGoogleSqlProvisionScript_mysql, instance, serviceAcct, manual_test_user, scriptName, "CREATE USER"),
43+
ExpectError: regexp.MustCompile(`.*`), // syntax error
44+
},
45+
},
46+
})
47+
}
48+
49+
func TestAccSqlProvisionScriptPostgres(t *testing.T) {
50+
t.Parallel()
51+
serviceAcct := envvar.GetTestServiceAccountFromEnv(t)
52+
t.Log("Test service account is", serviceAcct)
53+
serviceAcct = strings.TrimSuffix(serviceAcct, ".gserviceaccount.com")
54+
55+
instance := fmt.Sprintf("tf-test-%d", acctest.RandInt(t))
56+
database := fmt.Sprintf("tf-test-%d", acctest.RandInt(t))
57+
scriptName := fmt.Sprintf("tf-test-%d", acctest.RandInt(t))
58+
script := "CREATE TABLE IF NOT EXISTS table1 ( col VARCHAR(16) NOT NULL ); DROP TABLE table1;"
59+
acctest.VcrTest(t, resource.TestCase{
60+
PreCheck: func() { acctest.AccTestPreCheck(t) },
61+
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
62+
CheckDestroy: testAccSqlUserDestroyProducer(t),
63+
Steps: []resource.TestStep{
64+
{
65+
Config: fmt.Sprintf(
66+
testGoogleSqlProvisionScript_postgres, instance, database, serviceAcct, manual_test_user, scriptName, script),
67+
Check: resource.ComposeTestCheckFunc(
68+
resource.TestCheckResourceAttr("google_sql_provision_script.script", "database", database),
69+
resource.TestCheckResourceAttr("google_sql_provision_script.script", "deletion_policy", "ABANDON"),
70+
),
71+
},
72+
{
73+
Config: fmt.Sprintf(
74+
testGoogleSqlProvisionScript_postgres, instance, database, serviceAcct, manual_test_user, scriptName, "CREATE TABLE"),
75+
ExpectError: regexp.MustCompile(`.*`), // syntax error
76+
},
77+
},
78+
})
79+
}
80+
81+
var testGoogleSqlProvisionScript_mysql = `
82+
resource "google_sql_database_instance" "instance" {
83+
name = "%s"
84+
region = "us-central1"
85+
database_version = "MYSQL_8_4"
86+
deletion_protection = false
87+
settings {
88+
tier = "db-perf-optimized-N-2"
89+
data_api_access = "ALLOW_DATA_API"
90+
database_flags {
91+
name = "cloudsql_iam_authentication"
92+
value = "on"
93+
}
94+
}
95+
}
96+
97+
# The test account that runs this test must have a corresponding google_sql_user.
98+
resource "google_sql_user" "test_account" {
99+
name = "%s"
100+
instance = google_sql_database_instance.instance.name
101+
type = "CLOUD_IAM_SERVICE_ACCOUNT"
102+
database_roles = ["cloudsqlsuperuser"]
103+
}
104+
resource "google_sql_user" "manual_test_user" {
105+
name = "%s"
106+
instance = google_sql_database_instance.instance.name
107+
type = "CLOUD_IAM_USER"
108+
database_roles = ["cloudsqlsuperuser"]
109+
}
110+
111+
112+
resource "google_sql_provision_script" "script" {
113+
name = "%s"
114+
script = "%s"
115+
instance = google_sql_database_instance.instance.name
116+
depends_on = [
117+
google_sql_user.test_account,
118+
google_sql_user.manual_test_user
119+
]
120+
}
121+
`
122+
123+
var testGoogleSqlProvisionScript_postgres = `
124+
resource "google_sql_database_instance" "instance" {
125+
name = "%s"
126+
region = "us-central1"
127+
database_version = "POSTGRES_17"
128+
deletion_protection = false
129+
settings {
130+
tier = "db-perf-optimized-N-2"
131+
data_api_access = "ALLOW_DATA_API"
132+
database_flags {
133+
name = "cloudsql.iam_authentication"
134+
value = "on"
135+
}
136+
}
137+
}
138+
139+
resource "google_sql_database" "database" {
140+
name = "%s"
141+
instance = google_sql_database_instance.instance.name
142+
}
143+
144+
145+
# The test account that runs this test must have a corresponding google_sql_user.
146+
resource "google_sql_user" "test_account" {
147+
name = "%s"
148+
instance = google_sql_database_instance.instance.name
149+
type = "CLOUD_IAM_SERVICE_ACCOUNT"
150+
database_roles = ["cloudsqlsuperuser"]
151+
}
152+
resource "google_sql_user" "manual_test_user" {
153+
name = "%s"
154+
instance = google_sql_database_instance.instance.name
155+
type = "CLOUD_IAM_USER"
156+
database_roles = ["cloudsqlsuperuser"]
157+
}
158+
159+
160+
resource "google_sql_provision_script" "script" {
161+
name = "%s"
162+
script = "%s"
163+
database = google_sql_database.database.name
164+
instance = google_sql_database_instance.instance.name
165+
depends_on = [
166+
google_sql_user.test_account,
167+
google_sql_user.manual_test_user
168+
]
169+
}
170+
`

0 commit comments

Comments
 (0)