From 089d432fe7d0b0635200bba85ba7cdcd0c3d8c6c Mon Sep 17 00:00:00 2001 From: Modular Magician Date: Mon, 24 Feb 2020 20:42:51 +0000 Subject: [PATCH] Adding support for google game services (#3160) Signed-off-by: Modular Magician --- .changelog/3160.txt | 15 + google-beta/config.go | 3 + google-beta/game_services_operation.go | 71 ++ google-beta/provider.go | 18 +- ...ource_game_services_game_server_cluster.go | 487 ++++++++++++ ...rvices_game_server_cluster_sweeper_test.go | 119 +++ ...source_game_services_game_server_config.go | 703 ++++++++++++++++++ ...vices_game_server_config_generated_test.go | 108 +++ ...ervices_game_server_config_sweeper_test.go | 119 +++ ...ce_game_services_game_server_deployment.go | 328 ++++++++ ...s_game_server_deployment_generated_test.go | 80 ++ ...services_game_server_deployment_rollout.go | 385 ++++++++++ ...erver_deployment_rollout_generated_test.go | 103 +++ ..._server_deployment_rollout_sweeper_test.go | 119 +++ ...ces_game_server_deployment_sweeper_test.go | 119 +++ google-beta/resource_game_services_realm.go | 370 +++++++++ ...urce_game_services_realm_generated_test.go | 83 +++ ...source_game_services_realm_sweeper_test.go | 119 +++ ...services_game_server_cluster.html.markdown | 138 ++++ ..._services_game_server_config.html.markdown | 227 ++++++ ...vices_game_server_deployment.html.markdown | 120 +++ ...me_server_deployment_rollout.html.markdown | 159 ++++ .../docs/r/game_services_realm.html.markdown | 130 ++++ 23 files changed, 4121 insertions(+), 2 deletions(-) create mode 100644 .changelog/3160.txt create mode 100644 google-beta/game_services_operation.go create mode 100644 google-beta/resource_game_services_game_server_cluster.go create mode 100644 google-beta/resource_game_services_game_server_cluster_sweeper_test.go create mode 100644 google-beta/resource_game_services_game_server_config.go create mode 100644 google-beta/resource_game_services_game_server_config_generated_test.go create mode 100644 google-beta/resource_game_services_game_server_config_sweeper_test.go create mode 100644 google-beta/resource_game_services_game_server_deployment.go create mode 100644 google-beta/resource_game_services_game_server_deployment_generated_test.go create mode 100644 google-beta/resource_game_services_game_server_deployment_rollout.go create mode 100644 google-beta/resource_game_services_game_server_deployment_rollout_generated_test.go create mode 100644 google-beta/resource_game_services_game_server_deployment_rollout_sweeper_test.go create mode 100644 google-beta/resource_game_services_game_server_deployment_sweeper_test.go create mode 100644 google-beta/resource_game_services_realm.go create mode 100644 google-beta/resource_game_services_realm_generated_test.go create mode 100644 google-beta/resource_game_services_realm_sweeper_test.go create mode 100644 website/docs/r/game_services_game_server_cluster.html.markdown create mode 100644 website/docs/r/game_services_game_server_config.html.markdown create mode 100644 website/docs/r/game_services_game_server_deployment.html.markdown create mode 100644 website/docs/r/game_services_game_server_deployment_rollout.html.markdown create mode 100644 website/docs/r/game_services_realm.html.markdown diff --git a/.changelog/3160.txt b/.changelog/3160.txt new file mode 100644 index 0000000000..53ead71fb3 --- /dev/null +++ b/.changelog/3160.txt @@ -0,0 +1,15 @@ +```release-note:new-resource +gameservices: Added new resource `google_game_services_realm` +``` +```release-note:new-resource +gameservices: Added new resource `google_game_services_game_server_cluster` +``` +```release-note:new-resource +gameservices: Added new resource `google_game_services_game_server_config` +``` +```release-note:new-resource +gameservices: Added new resource `google_game_services_game_server_deployment` +``` +```release-note:new-resource +gameservices: Added new resource `google_game_services_game_server_deployment_rollout` +``` diff --git a/google-beta/config.go b/google-beta/config.go index 9193decdfb..d5c1e3575f 100644 --- a/google-beta/config.go +++ b/google-beta/config.go @@ -96,6 +96,7 @@ type Config struct { DNSBasePath string FilestoreBasePath string FirestoreBasePath string + GameServicesBasePath string HealthcareBasePath string IapBasePath string IdentityPlatformBasePath string @@ -240,6 +241,7 @@ var DialogflowDefaultBasePath = "https://dialogflow.googleapis.com/v2/" var DNSDefaultBasePath = "https://www.googleapis.com/dns/v1beta2/" var FilestoreDefaultBasePath = "https://file.googleapis.com/v1/" var FirestoreDefaultBasePath = "https://firestore.googleapis.com/v1/" +var GameServicesDefaultBasePath = "https://gameservices.googleapis.com/v1beta/" var HealthcareDefaultBasePath = "https://healthcare.googleapis.com/v1beta1/" var IapDefaultBasePath = "https://iap.googleapis.com/v1/" var IdentityPlatformDefaultBasePath = "https://identitytoolkit.googleapis.com/v2/" @@ -743,6 +745,7 @@ func ConfigureBasePaths(c *Config) { c.DNSBasePath = DNSDefaultBasePath c.FilestoreBasePath = FilestoreDefaultBasePath c.FirestoreBasePath = FirestoreDefaultBasePath + c.GameServicesBasePath = GameServicesDefaultBasePath c.HealthcareBasePath = HealthcareDefaultBasePath c.IapBasePath = IapDefaultBasePath c.IdentityPlatformBasePath = IdentityPlatformDefaultBasePath diff --git a/google-beta/game_services_operation.go b/google-beta/game_services_operation.go new file mode 100644 index 0000000000..3660526ee1 --- /dev/null +++ b/google-beta/game_services_operation.go @@ -0,0 +1,71 @@ +// ---------------------------------------------------------------------------- +// +// *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +// +// ---------------------------------------------------------------------------- +// +// This file is automatically generated by Magic Modules and manual +// changes will be clobbered when the file is regenerated. +// +// Please read more about how to change this file in +// .github/CONTRIBUTING.md. +// +// ---------------------------------------------------------------------------- +package google + +import ( + "encoding/json" + "fmt" +) + +type GameServicesOperationWaiter struct { + Config *Config + Project string + CommonOperationWaiter +} + +func (w *GameServicesOperationWaiter) QueryOp() (interface{}, error) { + if w == nil { + return nil, fmt.Errorf("Cannot query operation, it's unset or nil.") + } + // Returns the proper get. + url := fmt.Sprintf("https://gameservices.googleapis.com/v1beta/%s", w.CommonOperationWaiter.Op.Name) + return sendRequest(w.Config, "GET", w.Project, url, nil) +} + +func createGameServicesWaiter(config *Config, op map[string]interface{}, project, activity string) (*GameServicesOperationWaiter, error) { + if val, ok := op["name"]; !ok || val == "" { + // This was a synchronous call - there is no operation to wait for. + return nil, nil + } + w := &GameServicesOperationWaiter{ + Config: config, + Project: project, + } + if err := w.CommonOperationWaiter.SetOp(op); err != nil { + return nil, err + } + return w, nil +} + +// nolint: deadcode,unused +func gameServicesOperationWaitTimeWithResponse(config *Config, op map[string]interface{}, response *map[string]interface{}, project, activity string, timeoutMinutes int) error { + w, err := createGameServicesWaiter(config, op, project, activity) + if err != nil || w == nil { + // If w is nil, the op was synchronous. + return err + } + if err := OperationWait(w, activity, timeoutMinutes); err != nil { + return err + } + return json.Unmarshal([]byte(w.CommonOperationWaiter.Op.Response), response) +} + +func gameServicesOperationWaitTime(config *Config, op map[string]interface{}, project, activity string, timeoutMinutes int) error { + w, err := createGameServicesWaiter(config, op, project, activity) + if err != nil || w == nil { + // If w is nil, the op was synchronous. + return err + } + return OperationWait(w, activity, timeoutMinutes) +} diff --git a/google-beta/provider.go b/google-beta/provider.go index 2698dee473..bbadb67fed 100644 --- a/google-beta/provider.go +++ b/google-beta/provider.go @@ -285,6 +285,14 @@ func Provider() terraform.ResourceProvider { "GOOGLE_FIRESTORE_CUSTOM_ENDPOINT", }, FirestoreDefaultBasePath), }, + "game_services_custom_endpoint": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validateCustomEndpoint, + DefaultFunc: schema.MultiEnvDefaultFunc([]string{ + "GOOGLE_GAME_SERVICES_CUSTOM_ENDPOINT", + }, GameServicesDefaultBasePath), + }, "healthcare_custom_endpoint": { Type: schema.TypeString, Optional: true, @@ -556,9 +564,9 @@ func Provider() terraform.ResourceProvider { return provider } -// Generated resources: 120 +// Generated resources: 125 // Generated IAM resources: 54 -// Total generated resources: 174 +// Total generated resources: 179 func ResourceMap() map[string]*schema.Resource { resourceMap, _ := ResourceMapWithErrors() return resourceMap @@ -664,6 +672,11 @@ func ResourceMapWithErrors() (map[string]*schema.Resource, error) { "google_dns_policy": resourceDNSPolicy(), "google_filestore_instance": resourceFilestoreInstance(), "google_firestore_index": resourceFirestoreIndex(), + "google_game_services_realm": resourceGameServicesRealm(), + "google_game_services_game_server_cluster": resourceGameServicesGameServerCluster(), + "google_game_services_game_server_deployment": resourceGameServicesGameServerDeployment(), + "google_game_services_game_server_config": resourceGameServicesGameServerConfig(), + "google_game_services_game_server_deployment_rollout": resourceGameServicesGameServerDeploymentRollout(), "google_healthcare_dataset": resourceHealthcareDataset(), "google_healthcare_dicom_store": resourceHealthcareDicomStore(), "google_healthcare_fhir_store": resourceHealthcareFhirStore(), @@ -924,6 +937,7 @@ func providerConfigure(d *schema.ResourceData, p *schema.Provider, terraformVers config.DNSBasePath = d.Get("dns_custom_endpoint").(string) config.FilestoreBasePath = d.Get("filestore_custom_endpoint").(string) config.FirestoreBasePath = d.Get("firestore_custom_endpoint").(string) + config.GameServicesBasePath = d.Get("game_services_custom_endpoint").(string) config.HealthcareBasePath = d.Get("healthcare_custom_endpoint").(string) config.IapBasePath = d.Get("iap_custom_endpoint").(string) config.IdentityPlatformBasePath = d.Get("identity_platform_custom_endpoint").(string) diff --git a/google-beta/resource_game_services_game_server_cluster.go b/google-beta/resource_game_services_game_server_cluster.go new file mode 100644 index 0000000000..f921ed48e5 --- /dev/null +++ b/google-beta/resource_game_services_game_server_cluster.go @@ -0,0 +1,487 @@ +// ---------------------------------------------------------------------------- +// +// *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +// +// ---------------------------------------------------------------------------- +// +// This file is automatically generated by Magic Modules and manual +// changes will be clobbered when the file is regenerated. +// +// Please read more about how to change this file in +// .github/CONTRIBUTING.md. +// +// ---------------------------------------------------------------------------- + +package google + +import ( + "fmt" + "log" + "reflect" + "strings" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" +) + +func suppressSuffixDiff(_, old, new string, _ *schema.ResourceData) bool { + if strings.HasSuffix(old, new) { + log.Printf("[INFO] suppressing diff as %s is the same as the full path of %s", new, old) + return true + } + + return false +} + +func resourceGameServicesGameServerCluster() *schema.Resource { + return &schema.Resource{ + Create: resourceGameServicesGameServerClusterCreate, + Read: resourceGameServicesGameServerClusterRead, + Update: resourceGameServicesGameServerClusterUpdate, + Delete: resourceGameServicesGameServerClusterDelete, + + Importer: &schema.ResourceImporter{ + State: resourceGameServicesGameServerClusterImport, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(4 * time.Minute), + Update: schema.DefaultTimeout(4 * time.Minute), + Delete: schema.DefaultTimeout(4 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "cluster_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: `Required. The resource name of the game server cluster`, + }, + "connection_info": { + Type: schema.TypeList, + Required: true, + ForceNew: true, + Description: `Game server cluster connection information. This information is used to +manage game server clusters.`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "gke_cluster_reference": { + Type: schema.TypeList, + Required: true, + ForceNew: true, + Description: `Reference of the GKE cluster where the game servers are installed.`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "cluster": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + DiffSuppressFunc: suppressSuffixDiff, + Description: `The full or partial name of a GKE cluster, using one of the following +forms: + +* 'projects/{project_id}/locations/{location}/clusters/{cluster_id}' +* 'locations/{location}/clusters/{cluster_id}' +* '{cluster_id}' + +If project and location are not specified, the project and location of the +GameServerCluster resource are used to generate the full name of the +GKE cluster.`, + }, + }, + }, + }, + "namespace": { + Type: schema.TypeString, + Required: true, + Description: `Namespace designated on the game server cluster where the game server +instances will be created. The namespace existence will be validated +during creation.`, + }, + }, + }, + }, + "realm_id": { + Type: schema.TypeString, + Required: true, + DiffSuppressFunc: compareSelfLinkOrResourceName, + Description: `The realm id of the game server realm.`, + }, + "description": { + Type: schema.TypeString, + Optional: true, + Description: `Human readable description of the cluster.`, + }, + "labels": { + Type: schema.TypeMap, + Optional: true, + Description: `The labels associated with this game server cluster. Each label is a +key-value pair.`, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "location": { + Type: schema.TypeString, + Optional: true, + Description: `Location of the Cluster.`, + Default: "global", + }, + "name": { + Type: schema.TypeString, + Computed: true, + Description: `The resource id of the game server cluster, eg: + +'projects/{project_id}/locations/{location}/realms/{realm_id}/gameServerClusters/{cluster_id}'. +For example, + +'projects/my-project/locations/{location}/realms/zanzibar/gameServerClusters/my-onprem-cluster'.`, + }, + "project": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + }, + } +} + +func resourceGameServicesGameServerClusterCreate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + obj := make(map[string]interface{}) + labelsProp, err := expandGameServicesGameServerClusterLabels(d.Get("labels"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("labels"); !isEmptyValue(reflect.ValueOf(labelsProp)) && (ok || !reflect.DeepEqual(v, labelsProp)) { + obj["labels"] = labelsProp + } + connectionInfoProp, err := expandGameServicesGameServerClusterConnectionInfo(d.Get("connection_info"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("connection_info"); !isEmptyValue(reflect.ValueOf(connectionInfoProp)) && (ok || !reflect.DeepEqual(v, connectionInfoProp)) { + obj["connectionInfo"] = connectionInfoProp + } + descriptionProp, err := expandGameServicesGameServerClusterDescription(d.Get("description"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("description"); !isEmptyValue(reflect.ValueOf(descriptionProp)) && (ok || !reflect.DeepEqual(v, descriptionProp)) { + obj["description"] = descriptionProp + } + + url, err := replaceVars(d, config, "{{GameServicesBasePath}}projects/{{project}}/locations/{{location}}/realms/{{realm_id}}/gameServerClusters?gameServerClusterId={{cluster_id}}") + if err != nil { + return err + } + + log.Printf("[DEBUG] Creating new GameServerCluster: %#v", obj) + project, err := getProject(d, config) + if err != nil { + return err + } + res, err := sendRequestWithTimeout(config, "POST", project, url, obj, d.Timeout(schema.TimeoutCreate)) + if err != nil { + return fmt.Errorf("Error creating GameServerCluster: %s", err) + } + + // Store the ID now + id, err := replaceVars(d, config, "projects/{{project}}/locations/{{location}}/realms/{{realm_id}}/gameServerClusters/{{cluster_id}}") + if err != nil { + return fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + // Use the resource in the operation response to populate + // identity fields and d.Id() before read + var opRes map[string]interface{} + err = gameServicesOperationWaitTimeWithResponse( + config, res, &opRes, project, "Creating GameServerCluster", + int(d.Timeout(schema.TimeoutCreate).Minutes())) + if err != nil { + // The resource didn't actually create + d.SetId("") + return fmt.Errorf("Error waiting to create GameServerCluster: %s", err) + } + + if err := d.Set("name", flattenGameServicesGameServerClusterName(opRes["name"], d, config)); err != nil { + return err + } + + // This may have caused the ID to update - update it if so. + id, err = replaceVars(d, config, "projects/{{project}}/locations/{{location}}/realms/{{realm_id}}/gameServerClusters/{{cluster_id}}") + if err != nil { + return fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + log.Printf("[DEBUG] Finished creating GameServerCluster %q: %#v", d.Id(), res) + + return resourceGameServicesGameServerClusterRead(d, meta) +} + +func resourceGameServicesGameServerClusterRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + url, err := replaceVars(d, config, "{{GameServicesBasePath}}projects/{{project}}/locations/{{location}}/realms/{{realm_id}}/gameServerClusters/{{cluster_id}}") + if err != nil { + return err + } + + project, err := getProject(d, config) + if err != nil { + return err + } + res, err := sendRequest(config, "GET", project, url, nil) + if err != nil { + return handleNotFoundError(err, d, fmt.Sprintf("GameServicesGameServerCluster %q", d.Id())) + } + + if err := d.Set("project", project); err != nil { + return fmt.Errorf("Error reading GameServerCluster: %s", err) + } + + if err := d.Set("name", flattenGameServicesGameServerClusterName(res["name"], d, config)); err != nil { + return fmt.Errorf("Error reading GameServerCluster: %s", err) + } + if err := d.Set("labels", flattenGameServicesGameServerClusterLabels(res["labels"], d, config)); err != nil { + return fmt.Errorf("Error reading GameServerCluster: %s", err) + } + if err := d.Set("connection_info", flattenGameServicesGameServerClusterConnectionInfo(res["connectionInfo"], d, config)); err != nil { + return fmt.Errorf("Error reading GameServerCluster: %s", err) + } + if err := d.Set("description", flattenGameServicesGameServerClusterDescription(res["description"], d, config)); err != nil { + return fmt.Errorf("Error reading GameServerCluster: %s", err) + } + + return nil +} + +func resourceGameServicesGameServerClusterUpdate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + project, err := getProject(d, config) + if err != nil { + return err + } + + obj := make(map[string]interface{}) + labelsProp, err := expandGameServicesGameServerClusterLabels(d.Get("labels"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("labels"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, labelsProp)) { + obj["labels"] = labelsProp + } + descriptionProp, err := expandGameServicesGameServerClusterDescription(d.Get("description"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("description"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, descriptionProp)) { + obj["description"] = descriptionProp + } + + url, err := replaceVars(d, config, "{{GameServicesBasePath}}projects/{{project}}/locations/{{location}}/realms/{{realm_id}}/gameServerClusters/{{cluster_id}}") + if err != nil { + return err + } + + log.Printf("[DEBUG] Updating GameServerCluster %q: %#v", d.Id(), obj) + updateMask := []string{} + + if d.HasChange("labels") { + updateMask = append(updateMask, "labels") + } + + if d.HasChange("description") { + updateMask = append(updateMask, "description") + } + // updateMask is a URL parameter but not present in the schema, so replaceVars + // won't set it + url, err = addQueryParams(url, map[string]string{"updateMask": strings.Join(updateMask, ",")}) + if err != nil { + return err + } + res, err := sendRequestWithTimeout(config, "PATCH", project, url, obj, d.Timeout(schema.TimeoutUpdate)) + + if err != nil { + return fmt.Errorf("Error updating GameServerCluster %q: %s", d.Id(), err) + } + + err = gameServicesOperationWaitTime( + config, res, project, "Updating GameServerCluster", + int(d.Timeout(schema.TimeoutUpdate).Minutes())) + + if err != nil { + return err + } + + return resourceGameServicesGameServerClusterRead(d, meta) +} + +func resourceGameServicesGameServerClusterDelete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + project, err := getProject(d, config) + if err != nil { + return err + } + + url, err := replaceVars(d, config, "{{GameServicesBasePath}}projects/{{project}}/locations/{{location}}/realms/{{realm_id}}/gameServerClusters/{{cluster_id}}") + if err != nil { + return err + } + + var obj map[string]interface{} + log.Printf("[DEBUG] Deleting GameServerCluster %q", d.Id()) + + res, err := sendRequestWithTimeout(config, "DELETE", project, url, obj, d.Timeout(schema.TimeoutDelete)) + if err != nil { + return handleNotFoundError(err, d, "GameServerCluster") + } + + err = gameServicesOperationWaitTime( + config, res, project, "Deleting GameServerCluster", + int(d.Timeout(schema.TimeoutDelete).Minutes())) + + if err != nil { + return err + } + + log.Printf("[DEBUG] Finished deleting GameServerCluster %q: %#v", d.Id(), res) + return nil +} + +func resourceGameServicesGameServerClusterImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + config := meta.(*Config) + if err := parseImportId([]string{ + "projects/(?P[^/]+)/locations/(?P[^/]+)/realms/(?P[^/]+)/gameServerClusters/(?P[^/]+)", + "(?P[^/]+)/(?P[^/]+)/(?P[^/]+)/(?P[^/]+)", + "(?P[^/]+)/(?P[^/]+)/(?P[^/]+)", + }, d, config); err != nil { + return nil, err + } + + // Replace import id for the resource id + id, err := replaceVars(d, config, "projects/{{project}}/locations/{{location}}/realms/{{realm_id}}/gameServerClusters/{{cluster_id}}") + if err != nil { + return nil, fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + return []*schema.ResourceData{d}, nil +} + +func flattenGameServicesGameServerClusterName(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenGameServicesGameServerClusterLabels(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenGameServicesGameServerClusterConnectionInfo(v interface{}, d *schema.ResourceData, config *Config) interface{} { + if v == nil { + return nil + } + original := v.(map[string]interface{}) + if len(original) == 0 { + return nil + } + transformed := make(map[string]interface{}) + transformed["gke_cluster_reference"] = + flattenGameServicesGameServerClusterConnectionInfoGkeClusterReference(original["gkeClusterReference"], d, config) + transformed["namespace"] = + flattenGameServicesGameServerClusterConnectionInfoNamespace(original["namespace"], d, config) + return []interface{}{transformed} +} +func flattenGameServicesGameServerClusterConnectionInfoGkeClusterReference(v interface{}, d *schema.ResourceData, config *Config) interface{} { + if v == nil { + return nil + } + original := v.(map[string]interface{}) + if len(original) == 0 { + return nil + } + transformed := make(map[string]interface{}) + transformed["cluster"] = + flattenGameServicesGameServerClusterConnectionInfoGkeClusterReferenceCluster(original["cluster"], d, config) + return []interface{}{transformed} +} +func flattenGameServicesGameServerClusterConnectionInfoGkeClusterReferenceCluster(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenGameServicesGameServerClusterConnectionInfoNamespace(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenGameServicesGameServerClusterDescription(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func expandGameServicesGameServerClusterLabels(v interface{}, d TerraformResourceData, config *Config) (map[string]string, error) { + if v == nil { + return map[string]string{}, nil + } + m := make(map[string]string) + for k, val := range v.(map[string]interface{}) { + m[k] = val.(string) + } + return m, nil +} + +func expandGameServicesGameServerClusterConnectionInfo(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + l := v.([]interface{}) + if len(l) == 0 || l[0] == nil { + return nil, nil + } + raw := l[0] + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) + + transformedGkeClusterReference, err := expandGameServicesGameServerClusterConnectionInfoGkeClusterReference(original["gke_cluster_reference"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedGkeClusterReference); val.IsValid() && !isEmptyValue(val) { + transformed["gkeClusterReference"] = transformedGkeClusterReference + } + + transformedNamespace, err := expandGameServicesGameServerClusterConnectionInfoNamespace(original["namespace"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedNamespace); val.IsValid() && !isEmptyValue(val) { + transformed["namespace"] = transformedNamespace + } + + return transformed, nil +} + +func expandGameServicesGameServerClusterConnectionInfoGkeClusterReference(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + l := v.([]interface{}) + if len(l) == 0 || l[0] == nil { + return nil, nil + } + raw := l[0] + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) + + transformedCluster, err := expandGameServicesGameServerClusterConnectionInfoGkeClusterReferenceCluster(original["cluster"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedCluster); val.IsValid() && !isEmptyValue(val) { + transformed["cluster"] = transformedCluster + } + + return transformed, nil +} + +func expandGameServicesGameServerClusterConnectionInfoGkeClusterReferenceCluster(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandGameServicesGameServerClusterConnectionInfoNamespace(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandGameServicesGameServerClusterDescription(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} diff --git a/google-beta/resource_game_services_game_server_cluster_sweeper_test.go b/google-beta/resource_game_services_game_server_cluster_sweeper_test.go new file mode 100644 index 0000000000..08b71d0d5e --- /dev/null +++ b/google-beta/resource_game_services_game_server_cluster_sweeper_test.go @@ -0,0 +1,119 @@ +// ---------------------------------------------------------------------------- +// +// *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +// +// ---------------------------------------------------------------------------- +// +// This file is automatically generated by Magic Modules and manual +// changes will be clobbered when the file is regenerated. +// +// Please read more about how to change this file in +// .github/CONTRIBUTING.md. +// +// ---------------------------------------------------------------------------- + +package google + +import ( + "context" + "log" + "strings" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" +) + +func init() { + resource.AddTestSweepers("GameServicesGameServerCluster", &resource.Sweeper{ + Name: "GameServicesGameServerCluster", + F: testSweepGameServicesGameServerCluster, + }) +} + +// At the time of writing, the CI only passes us-central1 as the region +func testSweepGameServicesGameServerCluster(region string) error { + resourceName := "GameServicesGameServerCluster" + log.Printf("[INFO][SWEEPER_LOG] Starting sweeper for %s", resourceName) + + config, err := sharedConfigForRegion(region) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] error getting shared config for region: %s", err) + return err + } + + err = config.LoadAndValidate(context.Background()) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] error loading: %s", err) + return err + } + + // Setup variables to replace in list template + d := &ResourceDataMock{ + FieldsInSchema: map[string]interface{}{ + "project": config.Project, + "region": region, + "location": region, + "zone": "-", + }, + } + + listTemplate := strings.Split("https://gameservices.googleapis.com/v1beta/projects/{{project}}/locations/{{location}}/realms/{{realm_id}}/gameServerClusters", "?")[0] + listUrl, err := replaceVars(d, config, listTemplate) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] error preparing sweeper list url: %s", err) + return nil + } + + res, err := sendRequest(config, "GET", config.Project, listUrl, nil) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] Error in response from request %s: %s", listUrl, err) + return nil + } + + resourceList, ok := res["gameServerClusters"] + if !ok { + log.Printf("[INFO][SWEEPER_LOG] Nothing found in response.") + return nil + } + + rl := resourceList.([]interface{}) + + log.Printf("[INFO][SWEEPER_LOG] Found %d items in %s list response.", len(rl), resourceName) + // items who don't match the tf-test prefix + nonPrefixCount := 0 + for _, ri := range rl { + obj := ri.(map[string]interface{}) + if obj["name"] == nil { + log.Printf("[INFO][SWEEPER_LOG] %s resource name was nil", resourceName) + return nil + } + + name := GetResourceNameFromSelfLink(obj["name"].(string)) + // Only sweep resources with the test prefix + if !strings.HasPrefix(name, "tf-test") { + nonPrefixCount++ + continue + } + + deleteTemplate := "https://gameservices.googleapis.com/v1beta/projects/{{project}}/locations/{{location}}/realms/{{realm_id}}/gameServerClusters/{{cluster_id}}" + deleteUrl, err := replaceVars(d, config, deleteTemplate) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] error preparing delete url: %s", err) + return nil + } + deleteUrl = deleteUrl + name + + // Don't wait on operations as we may have a lot to delete + _, err = sendRequest(config, "DELETE", config.Project, deleteUrl, nil) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] Error deleting for url %s : %s", deleteUrl, err) + } else { + log.Printf("[INFO][SWEEPER_LOG] Sent delete request for %s resource: %s", resourceName, name) + } + } + + if nonPrefixCount > 0 { + log.Printf("[INFO][SWEEPER_LOG] %d items without tf_test prefix remain.", nonPrefixCount) + } + + return nil +} diff --git a/google-beta/resource_game_services_game_server_config.go b/google-beta/resource_game_services_game_server_config.go new file mode 100644 index 0000000000..6569db5995 --- /dev/null +++ b/google-beta/resource_game_services_game_server_config.go @@ -0,0 +1,703 @@ +// ---------------------------------------------------------------------------- +// +// *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +// +// ---------------------------------------------------------------------------- +// +// This file is automatically generated by Magic Modules and manual +// changes will be clobbered when the file is regenerated. +// +// Please read more about how to change this file in +// .github/CONTRIBUTING.md. +// +// ---------------------------------------------------------------------------- + +package google + +import ( + "fmt" + "log" + "reflect" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" +) + +func resourceGameServicesGameServerConfig() *schema.Resource { + return &schema.Resource{ + Create: resourceGameServicesGameServerConfigCreate, + Read: resourceGameServicesGameServerConfigRead, + Delete: resourceGameServicesGameServerConfigDelete, + + Importer: &schema.ResourceImporter{ + State: resourceGameServicesGameServerConfigImport, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(4 * time.Minute), + Delete: schema.DefaultTimeout(4 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "config_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: `A unique id for the deployment config.`, + }, + "deployment_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + DiffSuppressFunc: compareSelfLinkOrResourceName, + Description: `A unique id for the deployment.`, + }, + "fleet_configs": { + Type: schema.TypeList, + Required: true, + ForceNew: true, + Description: `The fleet config contains list of fleet specs. In the Single Cloud, there +will be only one.`, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "fleet_spec": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: `The fleet spec, which is sent to Agones to configure fleet. +The spec can be passed as inline json but it is recommended to use a file reference +instead. File references can contain the json or yaml format of the fleet spec. Eg: + +* fleet_spec = jsonencode(yamldecode(file("fleet_configs.yaml"))) +* fleet_spec = file("fleet_configs.json") + +The format of the spec can be found : +'https://agones.dev/site/docs/reference/fleet/'.`, + }, + "name": { + Type: schema.TypeString, + Computed: true, + Optional: true, + ForceNew: true, + Description: `The name of the FleetConfig.`, + }, + }, + }, + }, + "description": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: `The description of the game server config.`, + }, + "labels": { + Type: schema.TypeMap, + Optional: true, + ForceNew: true, + Description: `The labels associated with this game server config. Each label is a +key-value pair.`, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "location": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: `Location of the Deployment.`, + Default: "global", + }, + "scaling_configs": { + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Description: `Optional. This contains the autoscaling settings.`, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "fleet_autoscaler_spec": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: `Fleet autoscaler spec, which is sent to Agones. +Example spec can be found : +https://agones.dev/site/docs/reference/fleetautoscaler/`, + }, + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: `The name of the ScalingConfig`, + }, + "schedules": { + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Description: `The schedules to which this scaling config applies.`, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "cron_job_duration": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: `The duration for the cron job event. The duration of the event is effective +after the cron job's start time. + +A duration in seconds with up to nine fractional digits, terminated by 's'. Example: "3.5s".`, + }, + "cron_spec": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: `The cron definition of the scheduled event. See +https://en.wikipedia.org/wiki/Cron. Cron spec specifies the local time as +defined by the realm.`, + }, + "end_time": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: `The end time of the event. + +A timestamp in RFC3339 UTC "Zulu" format, accurate to nanoseconds. Example: "2014-10-02T15:01:23.045123456Z".`, + }, + "start_time": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: `The start time of the event. + +A timestamp in RFC3339 UTC "Zulu" format, accurate to nanoseconds. Example: "2014-10-02T15:01:23.045123456Z".`, + }, + }, + }, + }, + "selectors": { + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Description: `Labels used to identify the clusters to which this scaling config +applies. A cluster is subject to this scaling config if its labels match +any of the selector entries.`, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "labels": { + Type: schema.TypeMap, + Optional: true, + ForceNew: true, + Description: `Set of labels to group by.`, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + }, + }, + }, + }, + }, + "name": { + Type: schema.TypeString, + Computed: true, + Description: `The resource name of the game server config, in the form: + +'projects/{project_id}/locations/{location}/gameServerDeployments/{deployment_id}/configs/{config_id}'.`, + }, + "project": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + }, + } +} + +func resourceGameServicesGameServerConfigCreate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + obj := make(map[string]interface{}) + descriptionProp, err := expandGameServicesGameServerConfigDescription(d.Get("description"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("description"); !isEmptyValue(reflect.ValueOf(descriptionProp)) && (ok || !reflect.DeepEqual(v, descriptionProp)) { + obj["description"] = descriptionProp + } + labelsProp, err := expandGameServicesGameServerConfigLabels(d.Get("labels"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("labels"); !isEmptyValue(reflect.ValueOf(labelsProp)) && (ok || !reflect.DeepEqual(v, labelsProp)) { + obj["labels"] = labelsProp + } + fleetConfigsProp, err := expandGameServicesGameServerConfigFleetConfigs(d.Get("fleet_configs"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("fleet_configs"); !isEmptyValue(reflect.ValueOf(fleetConfigsProp)) && (ok || !reflect.DeepEqual(v, fleetConfigsProp)) { + obj["fleetConfigs"] = fleetConfigsProp + } + scalingConfigsProp, err := expandGameServicesGameServerConfigScalingConfigs(d.Get("scaling_configs"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("scaling_configs"); !isEmptyValue(reflect.ValueOf(scalingConfigsProp)) && (ok || !reflect.DeepEqual(v, scalingConfigsProp)) { + obj["scalingConfigs"] = scalingConfigsProp + } + + url, err := replaceVars(d, config, "{{GameServicesBasePath}}projects/{{project}}/locations/{{location}}/gameServerDeployments/{{deployment_id}}/configs?configId={{config_id}}") + if err != nil { + return err + } + + log.Printf("[DEBUG] Creating new GameServerConfig: %#v", obj) + project, err := getProject(d, config) + if err != nil { + return err + } + res, err := sendRequestWithTimeout(config, "POST", project, url, obj, d.Timeout(schema.TimeoutCreate)) + if err != nil { + return fmt.Errorf("Error creating GameServerConfig: %s", err) + } + + // Store the ID now + id, err := replaceVars(d, config, "projects/{{project}}/locations/{{location}}/gameServerDeployments/{{deployment_id}}/configs/{{config_id}}") + if err != nil { + return fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + // Use the resource in the operation response to populate + // identity fields and d.Id() before read + var opRes map[string]interface{} + err = gameServicesOperationWaitTimeWithResponse( + config, res, &opRes, project, "Creating GameServerConfig", + int(d.Timeout(schema.TimeoutCreate).Minutes())) + if err != nil { + // The resource didn't actually create + d.SetId("") + return fmt.Errorf("Error waiting to create GameServerConfig: %s", err) + } + + if err := d.Set("name", flattenGameServicesGameServerConfigName(opRes["name"], d, config)); err != nil { + return err + } + + // This may have caused the ID to update - update it if so. + id, err = replaceVars(d, config, "projects/{{project}}/locations/{{location}}/gameServerDeployments/{{deployment_id}}/configs/{{config_id}}") + if err != nil { + return fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + log.Printf("[DEBUG] Finished creating GameServerConfig %q: %#v", d.Id(), res) + + return resourceGameServicesGameServerConfigRead(d, meta) +} + +func resourceGameServicesGameServerConfigRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + url, err := replaceVars(d, config, "{{GameServicesBasePath}}projects/{{project}}/locations/{{location}}/gameServerDeployments/{{deployment_id}}/configs/{{config_id}}") + if err != nil { + return err + } + + project, err := getProject(d, config) + if err != nil { + return err + } + res, err := sendRequest(config, "GET", project, url, nil) + if err != nil { + return handleNotFoundError(err, d, fmt.Sprintf("GameServicesGameServerConfig %q", d.Id())) + } + + if err := d.Set("project", project); err != nil { + return fmt.Errorf("Error reading GameServerConfig: %s", err) + } + + if err := d.Set("name", flattenGameServicesGameServerConfigName(res["name"], d, config)); err != nil { + return fmt.Errorf("Error reading GameServerConfig: %s", err) + } + if err := d.Set("description", flattenGameServicesGameServerConfigDescription(res["description"], d, config)); err != nil { + return fmt.Errorf("Error reading GameServerConfig: %s", err) + } + if err := d.Set("labels", flattenGameServicesGameServerConfigLabels(res["labels"], d, config)); err != nil { + return fmt.Errorf("Error reading GameServerConfig: %s", err) + } + if err := d.Set("fleet_configs", flattenGameServicesGameServerConfigFleetConfigs(res["fleetConfigs"], d, config)); err != nil { + return fmt.Errorf("Error reading GameServerConfig: %s", err) + } + if err := d.Set("scaling_configs", flattenGameServicesGameServerConfigScalingConfigs(res["scalingConfigs"], d, config)); err != nil { + return fmt.Errorf("Error reading GameServerConfig: %s", err) + } + + return nil +} + +func resourceGameServicesGameServerConfigDelete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + project, err := getProject(d, config) + if err != nil { + return err + } + + url, err := replaceVars(d, config, "{{GameServicesBasePath}}projects/{{project}}/locations/{{location}}/gameServerDeployments/{{deployment_id}}/configs/{{config_id}}") + if err != nil { + return err + } + + var obj map[string]interface{} + log.Printf("[DEBUG] Deleting GameServerConfig %q", d.Id()) + + res, err := sendRequestWithTimeout(config, "DELETE", project, url, obj, d.Timeout(schema.TimeoutDelete)) + if err != nil { + return handleNotFoundError(err, d, "GameServerConfig") + } + + err = gameServicesOperationWaitTime( + config, res, project, "Deleting GameServerConfig", + int(d.Timeout(schema.TimeoutDelete).Minutes())) + + if err != nil { + return err + } + + log.Printf("[DEBUG] Finished deleting GameServerConfig %q: %#v", d.Id(), res) + return nil +} + +func resourceGameServicesGameServerConfigImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + config := meta.(*Config) + if err := parseImportId([]string{ + "projects/(?P[^/]+)/locations/(?P[^/]+)/gameServerDeployments/(?P[^/]+)/configs/(?P[^/]+)", + "(?P[^/]+)/(?P[^/]+)/(?P[^/]+)/(?P[^/]+)", + "(?P[^/]+)/(?P[^/]+)/(?P[^/]+)", + }, d, config); err != nil { + return nil, err + } + + // Replace import id for the resource id + id, err := replaceVars(d, config, "projects/{{project}}/locations/{{location}}/gameServerDeployments/{{deployment_id}}/configs/{{config_id}}") + if err != nil { + return nil, fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + return []*schema.ResourceData{d}, nil +} + +func flattenGameServicesGameServerConfigName(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenGameServicesGameServerConfigDescription(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenGameServicesGameServerConfigLabels(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenGameServicesGameServerConfigFleetConfigs(v interface{}, d *schema.ResourceData, config *Config) interface{} { + if v == nil { + return v + } + l := v.([]interface{}) + transformed := make([]interface{}, 0, len(l)) + for _, raw := range l { + original := raw.(map[string]interface{}) + if len(original) < 1 { + // Do not include empty json objects coming back from the api + continue + } + transformed = append(transformed, map[string]interface{}{ + "fleet_spec": flattenGameServicesGameServerConfigFleetConfigsFleetSpec(original["fleetSpec"], d, config), + "name": flattenGameServicesGameServerConfigFleetConfigsName(original["name"], d, config), + }) + } + return transformed +} +func flattenGameServicesGameServerConfigFleetConfigsFleetSpec(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenGameServicesGameServerConfigFleetConfigsName(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenGameServicesGameServerConfigScalingConfigs(v interface{}, d *schema.ResourceData, config *Config) interface{} { + if v == nil { + return v + } + l := v.([]interface{}) + transformed := make([]interface{}, 0, len(l)) + for _, raw := range l { + original := raw.(map[string]interface{}) + if len(original) < 1 { + // Do not include empty json objects coming back from the api + continue + } + transformed = append(transformed, map[string]interface{}{ + "name": flattenGameServicesGameServerConfigScalingConfigsName(original["name"], d, config), + "fleet_autoscaler_spec": flattenGameServicesGameServerConfigScalingConfigsFleetAutoscalerSpec(original["fleetAutoscalerSpec"], d, config), + "selectors": flattenGameServicesGameServerConfigScalingConfigsSelectors(original["selectors"], d, config), + "schedules": flattenGameServicesGameServerConfigScalingConfigsSchedules(original["schedules"], d, config), + }) + } + return transformed +} +func flattenGameServicesGameServerConfigScalingConfigsName(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenGameServicesGameServerConfigScalingConfigsFleetAutoscalerSpec(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenGameServicesGameServerConfigScalingConfigsSelectors(v interface{}, d *schema.ResourceData, config *Config) interface{} { + if v == nil { + return v + } + l := v.([]interface{}) + transformed := make([]interface{}, 0, len(l)) + for _, raw := range l { + original := raw.(map[string]interface{}) + if len(original) < 1 { + // Do not include empty json objects coming back from the api + continue + } + transformed = append(transformed, map[string]interface{}{ + "labels": flattenGameServicesGameServerConfigScalingConfigsSelectorsLabels(original["labels"], d, config), + }) + } + return transformed +} +func flattenGameServicesGameServerConfigScalingConfigsSelectorsLabels(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenGameServicesGameServerConfigScalingConfigsSchedules(v interface{}, d *schema.ResourceData, config *Config) interface{} { + if v == nil { + return v + } + l := v.([]interface{}) + transformed := make([]interface{}, 0, len(l)) + for _, raw := range l { + original := raw.(map[string]interface{}) + if len(original) < 1 { + // Do not include empty json objects coming back from the api + continue + } + transformed = append(transformed, map[string]interface{}{ + "start_time": flattenGameServicesGameServerConfigScalingConfigsSchedulesStartTime(original["startTime"], d, config), + "end_time": flattenGameServicesGameServerConfigScalingConfigsSchedulesEndTime(original["endTime"], d, config), + "cron_job_duration": flattenGameServicesGameServerConfigScalingConfigsSchedulesCronJobDuration(original["cronJobDuration"], d, config), + "cron_spec": flattenGameServicesGameServerConfigScalingConfigsSchedulesCronSpec(original["cronSpec"], d, config), + }) + } + return transformed +} +func flattenGameServicesGameServerConfigScalingConfigsSchedulesStartTime(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenGameServicesGameServerConfigScalingConfigsSchedulesEndTime(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenGameServicesGameServerConfigScalingConfigsSchedulesCronJobDuration(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenGameServicesGameServerConfigScalingConfigsSchedulesCronSpec(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func expandGameServicesGameServerConfigDescription(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandGameServicesGameServerConfigLabels(v interface{}, d TerraformResourceData, config *Config) (map[string]string, error) { + if v == nil { + return map[string]string{}, nil + } + m := make(map[string]string) + for k, val := range v.(map[string]interface{}) { + m[k] = val.(string) + } + return m, nil +} + +func expandGameServicesGameServerConfigFleetConfigs(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + l := v.([]interface{}) + req := make([]interface{}, 0, len(l)) + for _, raw := range l { + if raw == nil { + continue + } + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) + + transformedFleetSpec, err := expandGameServicesGameServerConfigFleetConfigsFleetSpec(original["fleet_spec"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedFleetSpec); val.IsValid() && !isEmptyValue(val) { + transformed["fleetSpec"] = transformedFleetSpec + } + + transformedName, err := expandGameServicesGameServerConfigFleetConfigsName(original["name"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedName); val.IsValid() && !isEmptyValue(val) { + transformed["name"] = transformedName + } + + req = append(req, transformed) + } + return req, nil +} + +func expandGameServicesGameServerConfigFleetConfigsFleetSpec(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandGameServicesGameServerConfigFleetConfigsName(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandGameServicesGameServerConfigScalingConfigs(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + l := v.([]interface{}) + req := make([]interface{}, 0, len(l)) + for _, raw := range l { + if raw == nil { + continue + } + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) + + transformedName, err := expandGameServicesGameServerConfigScalingConfigsName(original["name"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedName); val.IsValid() && !isEmptyValue(val) { + transformed["name"] = transformedName + } + + transformedFleetAutoscalerSpec, err := expandGameServicesGameServerConfigScalingConfigsFleetAutoscalerSpec(original["fleet_autoscaler_spec"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedFleetAutoscalerSpec); val.IsValid() && !isEmptyValue(val) { + transformed["fleetAutoscalerSpec"] = transformedFleetAutoscalerSpec + } + + transformedSelectors, err := expandGameServicesGameServerConfigScalingConfigsSelectors(original["selectors"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedSelectors); val.IsValid() && !isEmptyValue(val) { + transformed["selectors"] = transformedSelectors + } + + transformedSchedules, err := expandGameServicesGameServerConfigScalingConfigsSchedules(original["schedules"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedSchedules); val.IsValid() && !isEmptyValue(val) { + transformed["schedules"] = transformedSchedules + } + + req = append(req, transformed) + } + return req, nil +} + +func expandGameServicesGameServerConfigScalingConfigsName(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandGameServicesGameServerConfigScalingConfigsFleetAutoscalerSpec(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandGameServicesGameServerConfigScalingConfigsSelectors(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + l := v.([]interface{}) + req := make([]interface{}, 0, len(l)) + for _, raw := range l { + if raw == nil { + continue + } + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) + + transformedLabels, err := expandGameServicesGameServerConfigScalingConfigsSelectorsLabels(original["labels"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedLabels); val.IsValid() && !isEmptyValue(val) { + transformed["labels"] = transformedLabels + } + + req = append(req, transformed) + } + return req, nil +} + +func expandGameServicesGameServerConfigScalingConfigsSelectorsLabels(v interface{}, d TerraformResourceData, config *Config) (map[string]string, error) { + if v == nil { + return map[string]string{}, nil + } + m := make(map[string]string) + for k, val := range v.(map[string]interface{}) { + m[k] = val.(string) + } + return m, nil +} + +func expandGameServicesGameServerConfigScalingConfigsSchedules(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + l := v.([]interface{}) + req := make([]interface{}, 0, len(l)) + for _, raw := range l { + if raw == nil { + continue + } + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) + + transformedStartTime, err := expandGameServicesGameServerConfigScalingConfigsSchedulesStartTime(original["start_time"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedStartTime); val.IsValid() && !isEmptyValue(val) { + transformed["startTime"] = transformedStartTime + } + + transformedEndTime, err := expandGameServicesGameServerConfigScalingConfigsSchedulesEndTime(original["end_time"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedEndTime); val.IsValid() && !isEmptyValue(val) { + transformed["endTime"] = transformedEndTime + } + + transformedCronJobDuration, err := expandGameServicesGameServerConfigScalingConfigsSchedulesCronJobDuration(original["cron_job_duration"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedCronJobDuration); val.IsValid() && !isEmptyValue(val) { + transformed["cronJobDuration"] = transformedCronJobDuration + } + + transformedCronSpec, err := expandGameServicesGameServerConfigScalingConfigsSchedulesCronSpec(original["cron_spec"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedCronSpec); val.IsValid() && !isEmptyValue(val) { + transformed["cronSpec"] = transformedCronSpec + } + + req = append(req, transformed) + } + return req, nil +} + +func expandGameServicesGameServerConfigScalingConfigsSchedulesStartTime(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandGameServicesGameServerConfigScalingConfigsSchedulesEndTime(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandGameServicesGameServerConfigScalingConfigsSchedulesCronJobDuration(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandGameServicesGameServerConfigScalingConfigsSchedulesCronSpec(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} diff --git a/google-beta/resource_game_services_game_server_config_generated_test.go b/google-beta/resource_game_services_game_server_config_generated_test.go new file mode 100644 index 0000000000..d10e49edde --- /dev/null +++ b/google-beta/resource_game_services_game_server_config_generated_test.go @@ -0,0 +1,108 @@ +// ---------------------------------------------------------------------------- +// +// *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +// +// ---------------------------------------------------------------------------- +// +// This file is automatically generated by Magic Modules and manual +// changes will be clobbered when the file is regenerated. +// +// Please read more about how to change this file in +// .github/CONTRIBUTING.md. +// +// ---------------------------------------------------------------------------- + +package google + +import ( + "fmt" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" +) + +func TestAccGameServicesGameServerConfig_gameServiceConfigBasicExample(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "random_suffix": acctest.RandString(10), + } + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProvidersOiCS, + CheckDestroy: testAccCheckGameServicesGameServerConfigDestroy, + Steps: []resource.TestStep{ + { + Config: testAccGameServicesGameServerConfig_gameServiceConfigBasicExample(context), + }, + }, + }) +} + +func testAccGameServicesGameServerConfig_gameServiceConfigBasicExample(context map[string]interface{}) string { + return Nprintf(` +resource "google_game_services_game_server_deployment" "default" { + provider = google-private + + deployment_id = "tf-test-tf-test-deployment%{random_suffix}" + description = "a deployment description" +} + +resource "google_game_services_game_server_config" "default" { + provider = google-private + + config_id = "tf-test-tf-test-config%{random_suffix}" + deployment_id = google_game_services_game_server_deployment.default.deployment_id + description = "a config description" + + fleet_configs { + name = "something-unique" + fleet_spec = jsonencode({ "replicas" : 1, "scheduling" : "Packed", "template" : { "metadata" : { "name" : "tf-test-game-server-template" }, "spec" : { "template" : { "spec" : { "containers" : [{ "name" : "simple-udp-server", "image" : "gcr.io/agones-images/udp-server:0.14" }] } } } } }) + } + + scaling_configs { + name = "scaling-config-name" + fleet_autoscaler_spec = jsonencode({"spec":{"fleetName":"simple-udp","policy":{"type":"Webhook","webhook":{"service":{"name":"autoscaler-webhook-service","namespace":"default","path":"scale"}}}}}) + selectors { + labels = { + "one" : "two" + } + } + + schedules { + cron_job_duration = "3.500s" + cron_spec = "0 0 * * 0" + } + } +} +`, context) +} + +func testAccCheckGameServicesGameServerConfigDestroy(s *terraform.State) error { + for name, rs := range s.RootModule().Resources { + if rs.Type != "google_game_services_game_server_config" { + continue + } + if strings.HasPrefix(name, "data.") { + continue + } + + config := testAccProvider.Meta().(*Config) + + url, err := replaceVarsForTest(config, rs, "{{GameServicesBasePath}}projects/{{project}}/locations/{{location}}/gameServerDeployments/{{deployment_id}}/configs/{{config_id}}") + if err != nil { + return err + } + + _, err = sendRequest(config, "GET", "", url, nil) + if err == nil { + return fmt.Errorf("GameServicesGameServerConfig still exists at %s", url) + } + } + + return nil +} diff --git a/google-beta/resource_game_services_game_server_config_sweeper_test.go b/google-beta/resource_game_services_game_server_config_sweeper_test.go new file mode 100644 index 0000000000..1539920582 --- /dev/null +++ b/google-beta/resource_game_services_game_server_config_sweeper_test.go @@ -0,0 +1,119 @@ +// ---------------------------------------------------------------------------- +// +// *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +// +// ---------------------------------------------------------------------------- +// +// This file is automatically generated by Magic Modules and manual +// changes will be clobbered when the file is regenerated. +// +// Please read more about how to change this file in +// .github/CONTRIBUTING.md. +// +// ---------------------------------------------------------------------------- + +package google + +import ( + "context" + "log" + "strings" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" +) + +func init() { + resource.AddTestSweepers("GameServicesGameServerConfig", &resource.Sweeper{ + Name: "GameServicesGameServerConfig", + F: testSweepGameServicesGameServerConfig, + }) +} + +// At the time of writing, the CI only passes us-central1 as the region +func testSweepGameServicesGameServerConfig(region string) error { + resourceName := "GameServicesGameServerConfig" + log.Printf("[INFO][SWEEPER_LOG] Starting sweeper for %s", resourceName) + + config, err := sharedConfigForRegion(region) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] error getting shared config for region: %s", err) + return err + } + + err = config.LoadAndValidate(context.Background()) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] error loading: %s", err) + return err + } + + // Setup variables to replace in list template + d := &ResourceDataMock{ + FieldsInSchema: map[string]interface{}{ + "project": config.Project, + "region": region, + "location": region, + "zone": "-", + }, + } + + listTemplate := strings.Split("https://gameservices.googleapis.com/v1beta/projects/{{project}}/locations/{{location}}/gameServerDeployments/{{deployment_id}}/configs", "?")[0] + listUrl, err := replaceVars(d, config, listTemplate) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] error preparing sweeper list url: %s", err) + return nil + } + + res, err := sendRequest(config, "GET", config.Project, listUrl, nil) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] Error in response from request %s: %s", listUrl, err) + return nil + } + + resourceList, ok := res["gameServerConfigs"] + if !ok { + log.Printf("[INFO][SWEEPER_LOG] Nothing found in response.") + return nil + } + + rl := resourceList.([]interface{}) + + log.Printf("[INFO][SWEEPER_LOG] Found %d items in %s list response.", len(rl), resourceName) + // items who don't match the tf-test prefix + nonPrefixCount := 0 + for _, ri := range rl { + obj := ri.(map[string]interface{}) + if obj["name"] == nil { + log.Printf("[INFO][SWEEPER_LOG] %s resource name was nil", resourceName) + return nil + } + + name := GetResourceNameFromSelfLink(obj["name"].(string)) + // Only sweep resources with the test prefix + if !strings.HasPrefix(name, "tf-test") { + nonPrefixCount++ + continue + } + + deleteTemplate := "https://gameservices.googleapis.com/v1beta/projects/{{project}}/locations/{{location}}/gameServerDeployments/{{deployment_id}}/configs/{{config_id}}" + deleteUrl, err := replaceVars(d, config, deleteTemplate) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] error preparing delete url: %s", err) + return nil + } + deleteUrl = deleteUrl + name + + // Don't wait on operations as we may have a lot to delete + _, err = sendRequest(config, "DELETE", config.Project, deleteUrl, nil) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] Error deleting for url %s : %s", deleteUrl, err) + } else { + log.Printf("[INFO][SWEEPER_LOG] Sent delete request for %s resource: %s", resourceName, name) + } + } + + if nonPrefixCount > 0 { + log.Printf("[INFO][SWEEPER_LOG] %d items without tf_test prefix remain.", nonPrefixCount) + } + + return nil +} diff --git a/google-beta/resource_game_services_game_server_deployment.go b/google-beta/resource_game_services_game_server_deployment.go new file mode 100644 index 0000000000..20bddbb80e --- /dev/null +++ b/google-beta/resource_game_services_game_server_deployment.go @@ -0,0 +1,328 @@ +// ---------------------------------------------------------------------------- +// +// *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +// +// ---------------------------------------------------------------------------- +// +// This file is automatically generated by Magic Modules and manual +// changes will be clobbered when the file is regenerated. +// +// Please read more about how to change this file in +// .github/CONTRIBUTING.md. +// +// ---------------------------------------------------------------------------- + +package google + +import ( + "fmt" + "log" + "reflect" + "strings" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" +) + +func resourceGameServicesGameServerDeployment() *schema.Resource { + return &schema.Resource{ + Create: resourceGameServicesGameServerDeploymentCreate, + Read: resourceGameServicesGameServerDeploymentRead, + Update: resourceGameServicesGameServerDeploymentUpdate, + Delete: resourceGameServicesGameServerDeploymentDelete, + + Importer: &schema.ResourceImporter{ + State: resourceGameServicesGameServerDeploymentImport, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(4 * time.Minute), + Update: schema.DefaultTimeout(4 * time.Minute), + Delete: schema.DefaultTimeout(4 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "deployment_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: `A unique id for the deployment.`, + }, + "description": { + Type: schema.TypeString, + Optional: true, + Description: `Human readable description of the game server deployment.`, + }, + "labels": { + Type: schema.TypeMap, + Optional: true, + Description: `The labels associated with this game server deployment. Each label is a +key-value pair.`, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "location": { + Type: schema.TypeString, + Optional: true, + Description: `Location of the Deployment.`, + Default: "global", + }, + "name": { + Type: schema.TypeString, + Computed: true, + Description: `The resource id of the game server deployment, eg: + +'projects/{project_id}/locations/{location}/gameServerDeployments/{deployment_id}'. +For example, + +'projects/my-project/locations/{location}/gameServerDeployments/my-deployment'.`, + }, + "project": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + }, + } +} + +func resourceGameServicesGameServerDeploymentCreate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + obj := make(map[string]interface{}) + descriptionProp, err := expandGameServicesGameServerDeploymentDescription(d.Get("description"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("description"); !isEmptyValue(reflect.ValueOf(descriptionProp)) && (ok || !reflect.DeepEqual(v, descriptionProp)) { + obj["description"] = descriptionProp + } + labelsProp, err := expandGameServicesGameServerDeploymentLabels(d.Get("labels"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("labels"); !isEmptyValue(reflect.ValueOf(labelsProp)) && (ok || !reflect.DeepEqual(v, labelsProp)) { + obj["labels"] = labelsProp + } + + url, err := replaceVars(d, config, "{{GameServicesBasePath}}projects/{{project}}/locations/{{location}}/gameServerDeployments?deploymentId={{deployment_id}}") + if err != nil { + return err + } + + log.Printf("[DEBUG] Creating new GameServerDeployment: %#v", obj) + project, err := getProject(d, config) + if err != nil { + return err + } + res, err := sendRequestWithTimeout(config, "POST", project, url, obj, d.Timeout(schema.TimeoutCreate)) + if err != nil { + return fmt.Errorf("Error creating GameServerDeployment: %s", err) + } + + // Store the ID now + id, err := replaceVars(d, config, "projects/{{project}}/locations/{{location}}/gameServerDeployments/{{deployment_id}}") + if err != nil { + return fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + // Use the resource in the operation response to populate + // identity fields and d.Id() before read + var opRes map[string]interface{} + err = gameServicesOperationWaitTimeWithResponse( + config, res, &opRes, project, "Creating GameServerDeployment", + int(d.Timeout(schema.TimeoutCreate).Minutes())) + if err != nil { + // The resource didn't actually create + d.SetId("") + return fmt.Errorf("Error waiting to create GameServerDeployment: %s", err) + } + + if err := d.Set("name", flattenGameServicesGameServerDeploymentName(opRes["name"], d, config)); err != nil { + return err + } + + // This may have caused the ID to update - update it if so. + id, err = replaceVars(d, config, "projects/{{project}}/locations/{{location}}/gameServerDeployments/{{deployment_id}}") + if err != nil { + return fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + log.Printf("[DEBUG] Finished creating GameServerDeployment %q: %#v", d.Id(), res) + + return resourceGameServicesGameServerDeploymentRead(d, meta) +} + +func resourceGameServicesGameServerDeploymentRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + url, err := replaceVars(d, config, "{{GameServicesBasePath}}projects/{{project}}/locations/{{location}}/gameServerDeployments/{{deployment_id}}") + if err != nil { + return err + } + + project, err := getProject(d, config) + if err != nil { + return err + } + res, err := sendRequest(config, "GET", project, url, nil) + if err != nil { + return handleNotFoundError(err, d, fmt.Sprintf("GameServicesGameServerDeployment %q", d.Id())) + } + + if err := d.Set("project", project); err != nil { + return fmt.Errorf("Error reading GameServerDeployment: %s", err) + } + + if err := d.Set("name", flattenGameServicesGameServerDeploymentName(res["name"], d, config)); err != nil { + return fmt.Errorf("Error reading GameServerDeployment: %s", err) + } + if err := d.Set("description", flattenGameServicesGameServerDeploymentDescription(res["description"], d, config)); err != nil { + return fmt.Errorf("Error reading GameServerDeployment: %s", err) + } + if err := d.Set("labels", flattenGameServicesGameServerDeploymentLabels(res["labels"], d, config)); err != nil { + return fmt.Errorf("Error reading GameServerDeployment: %s", err) + } + + return nil +} + +func resourceGameServicesGameServerDeploymentUpdate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + project, err := getProject(d, config) + if err != nil { + return err + } + + obj := make(map[string]interface{}) + descriptionProp, err := expandGameServicesGameServerDeploymentDescription(d.Get("description"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("description"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, descriptionProp)) { + obj["description"] = descriptionProp + } + labelsProp, err := expandGameServicesGameServerDeploymentLabels(d.Get("labels"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("labels"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, labelsProp)) { + obj["labels"] = labelsProp + } + + url, err := replaceVars(d, config, "{{GameServicesBasePath}}projects/{{project}}/locations/{{location}}/gameServerDeployments/{{deployment_id}}") + if err != nil { + return err + } + + log.Printf("[DEBUG] Updating GameServerDeployment %q: %#v", d.Id(), obj) + updateMask := []string{} + + if d.HasChange("description") { + updateMask = append(updateMask, "description") + } + + if d.HasChange("labels") { + updateMask = append(updateMask, "labels") + } + // updateMask is a URL parameter but not present in the schema, so replaceVars + // won't set it + url, err = addQueryParams(url, map[string]string{"updateMask": strings.Join(updateMask, ",")}) + if err != nil { + return err + } + res, err := sendRequestWithTimeout(config, "PATCH", project, url, obj, d.Timeout(schema.TimeoutUpdate)) + + if err != nil { + return fmt.Errorf("Error updating GameServerDeployment %q: %s", d.Id(), err) + } + + err = gameServicesOperationWaitTime( + config, res, project, "Updating GameServerDeployment", + int(d.Timeout(schema.TimeoutUpdate).Minutes())) + + if err != nil { + return err + } + + return resourceGameServicesGameServerDeploymentRead(d, meta) +} + +func resourceGameServicesGameServerDeploymentDelete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + project, err := getProject(d, config) + if err != nil { + return err + } + + url, err := replaceVars(d, config, "{{GameServicesBasePath}}projects/{{project}}/locations/{{location}}/gameServerDeployments/{{deployment_id}}") + if err != nil { + return err + } + + var obj map[string]interface{} + log.Printf("[DEBUG] Deleting GameServerDeployment %q", d.Id()) + + res, err := sendRequestWithTimeout(config, "DELETE", project, url, obj, d.Timeout(schema.TimeoutDelete)) + if err != nil { + return handleNotFoundError(err, d, "GameServerDeployment") + } + + err = gameServicesOperationWaitTime( + config, res, project, "Deleting GameServerDeployment", + int(d.Timeout(schema.TimeoutDelete).Minutes())) + + if err != nil { + return err + } + + log.Printf("[DEBUG] Finished deleting GameServerDeployment %q: %#v", d.Id(), res) + return nil +} + +func resourceGameServicesGameServerDeploymentImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + config := meta.(*Config) + if err := parseImportId([]string{ + "projects/(?P[^/]+)/locations/(?P[^/]+)/gameServerDeployments/(?P[^/]+)", + "(?P[^/]+)/(?P[^/]+)/(?P[^/]+)", + "(?P[^/]+)/(?P[^/]+)", + }, d, config); err != nil { + return nil, err + } + + // Replace import id for the resource id + id, err := replaceVars(d, config, "projects/{{project}}/locations/{{location}}/gameServerDeployments/{{deployment_id}}") + if err != nil { + return nil, fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + return []*schema.ResourceData{d}, nil +} + +func flattenGameServicesGameServerDeploymentName(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenGameServicesGameServerDeploymentDescription(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenGameServicesGameServerDeploymentLabels(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func expandGameServicesGameServerDeploymentDescription(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandGameServicesGameServerDeploymentLabels(v interface{}, d TerraformResourceData, config *Config) (map[string]string, error) { + if v == nil { + return map[string]string{}, nil + } + m := make(map[string]string) + for k, val := range v.(map[string]interface{}) { + m[k] = val.(string) + } + return m, nil +} diff --git a/google-beta/resource_game_services_game_server_deployment_generated_test.go b/google-beta/resource_game_services_game_server_deployment_generated_test.go new file mode 100644 index 0000000000..5ebeb2ad74 --- /dev/null +++ b/google-beta/resource_game_services_game_server_deployment_generated_test.go @@ -0,0 +1,80 @@ +// ---------------------------------------------------------------------------- +// +// *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +// +// ---------------------------------------------------------------------------- +// +// This file is automatically generated by Magic Modules and manual +// changes will be clobbered when the file is regenerated. +// +// Please read more about how to change this file in +// .github/CONTRIBUTING.md. +// +// ---------------------------------------------------------------------------- + +package google + +import ( + "fmt" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" +) + +func TestAccGameServicesGameServerDeployment_gameServiceDeploymentBasicExample(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "random_suffix": acctest.RandString(10), + } + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProvidersOiCS, + CheckDestroy: testAccCheckGameServicesGameServerDeploymentDestroy, + Steps: []resource.TestStep{ + { + Config: testAccGameServicesGameServerDeployment_gameServiceDeploymentBasicExample(context), + }, + }, + }) +} + +func testAccGameServicesGameServerDeployment_gameServiceDeploymentBasicExample(context map[string]interface{}) string { + return Nprintf(` +resource "google_game_services_game_server_deployment" "default" { + provider = google-private + + deployment_id = "tf-test-tf-test-deployment%{random_suffix}" + description = "a deployment description" +} +`, context) +} + +func testAccCheckGameServicesGameServerDeploymentDestroy(s *terraform.State) error { + for name, rs := range s.RootModule().Resources { + if rs.Type != "google_game_services_game_server_deployment" { + continue + } + if strings.HasPrefix(name, "data.") { + continue + } + + config := testAccProvider.Meta().(*Config) + + url, err := replaceVarsForTest(config, rs, "{{GameServicesBasePath}}projects/{{project}}/locations/{{location}}/gameServerDeployments/{{deployment_id}}") + if err != nil { + return err + } + + _, err = sendRequest(config, "GET", "", url, nil) + if err == nil { + return fmt.Errorf("GameServicesGameServerDeployment still exists at %s", url) + } + } + + return nil +} diff --git a/google-beta/resource_game_services_game_server_deployment_rollout.go b/google-beta/resource_game_services_game_server_deployment_rollout.go new file mode 100644 index 0000000000..109fecc01b --- /dev/null +++ b/google-beta/resource_game_services_game_server_deployment_rollout.go @@ -0,0 +1,385 @@ +// ---------------------------------------------------------------------------- +// +// *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +// +// ---------------------------------------------------------------------------- +// +// This file is automatically generated by Magic Modules and manual +// changes will be clobbered when the file is regenerated. +// +// Please read more about how to change this file in +// .github/CONTRIBUTING.md. +// +// ---------------------------------------------------------------------------- + +package google + +import ( + "fmt" + "log" + "reflect" + "strings" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" +) + +func resourceGameServicesGameServerDeploymentRollout() *schema.Resource { + return &schema.Resource{ + Create: resourceGameServicesGameServerDeploymentRolloutCreate, + Read: resourceGameServicesGameServerDeploymentRolloutRead, + Update: resourceGameServicesGameServerDeploymentRolloutUpdate, + Delete: resourceGameServicesGameServerDeploymentRolloutDelete, + + Importer: &schema.ResourceImporter{ + State: resourceGameServicesGameServerDeploymentRolloutImport, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(4 * time.Minute), + Update: schema.DefaultTimeout(4 * time.Minute), + Delete: schema.DefaultTimeout(4 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "default_game_server_config": { + Type: schema.TypeString, + Required: true, + Description: `This field points to the game server config that is +applied by default to all realms and clusters. For example, + +'projects/my-project/locations/global/gameServerDeployments/my-game/configs/my-config'.`, + }, + "deployment_id": { + Type: schema.TypeString, + Required: true, + DiffSuppressFunc: compareSelfLinkOrResourceName, + Description: `The deployment to rollout the new config to. Only 1 rollout must be associated with each deployment.`, + }, + "game_server_config_overrides": { + Type: schema.TypeList, + Optional: true, + Description: `The game_server_config_overrides contains the per game server config +overrides. The overrides are processed in the order they are listed. As +soon as a match is found for a cluster, the rest of the list is not +processed.`, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "config_version": { + Type: schema.TypeString, + Optional: true, + Description: `Version of the configuration.`, + }, + "realms_selector": { + Type: schema.TypeList, + Optional: true, + Description: `Selection by realms.`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "realms": { + Type: schema.TypeList, + Optional: true, + Description: `List of realms to match against.`, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + }, + }, + }, + }, + }, + }, + "name": { + Type: schema.TypeString, + Computed: true, + Description: `The resource id of the game server deployment + +eg: 'projects/my-project/locations/global/gameServerDeployments/my-deployment/rollout'.`, + }, + "project": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + }, + } +} + +func resourceGameServicesGameServerDeploymentRolloutCreate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + // Store the ID now + id, err := replaceVars(d, config, "projects/{{project}}/locations/global/gameServerDeployments/{{deployment_id}}/rollout") + if err != nil { + return fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + log.Printf("[DEBUG] Creating GameServerDeploymentRollout %q: ", d.Id()) + + err = resourceGameServicesGameServerDeploymentRolloutUpdate(d, meta) + if err != nil { + d.SetId("") + return fmt.Errorf("Error trying to create GameServerDeploymentRollout: %s", err) + } + + return nil +} + +func resourceGameServicesGameServerDeploymentRolloutRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + url, err := replaceVars(d, config, "{{GameServicesBasePath}}projects/{{project}}/locations/global/gameServerDeployments/{{deployment_id}}/rollout") + if err != nil { + return err + } + + project, err := getProject(d, config) + if err != nil { + return err + } + res, err := sendRequest(config, "GET", project, url, nil) + if err != nil { + return handleNotFoundError(err, d, fmt.Sprintf("GameServicesGameServerDeploymentRollout %q", d.Id())) + } + + if err := d.Set("project", project); err != nil { + return fmt.Errorf("Error reading GameServerDeploymentRollout: %s", err) + } + + if err := d.Set("name", flattenGameServicesGameServerDeploymentRolloutName(res["name"], d, config)); err != nil { + return fmt.Errorf("Error reading GameServerDeploymentRollout: %s", err) + } + if err := d.Set("default_game_server_config", flattenGameServicesGameServerDeploymentRolloutDefaultGameServerConfig(res["defaultGameServerConfig"], d, config)); err != nil { + return fmt.Errorf("Error reading GameServerDeploymentRollout: %s", err) + } + if err := d.Set("game_server_config_overrides", flattenGameServicesGameServerDeploymentRolloutGameServerConfigOverrides(res["gameServerConfigOverrides"], d, config)); err != nil { + return fmt.Errorf("Error reading GameServerDeploymentRollout: %s", err) + } + + return nil +} + +func resourceGameServicesGameServerDeploymentRolloutUpdate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + project, err := getProject(d, config) + if err != nil { + return err + } + + obj := make(map[string]interface{}) + defaultGameServerConfigProp, err := expandGameServicesGameServerDeploymentRolloutDefaultGameServerConfig(d.Get("default_game_server_config"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("default_game_server_config"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, defaultGameServerConfigProp)) { + obj["defaultGameServerConfig"] = defaultGameServerConfigProp + } + gameServerConfigOverridesProp, err := expandGameServicesGameServerDeploymentRolloutGameServerConfigOverrides(d.Get("game_server_config_overrides"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("game_server_config_overrides"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, gameServerConfigOverridesProp)) { + obj["gameServerConfigOverrides"] = gameServerConfigOverridesProp + } + + url, err := replaceVars(d, config, "{{GameServicesBasePath}}projects/{{project}}/locations/global/gameServerDeployments/{{deployment_id}}/rollout") + if err != nil { + return err + } + + log.Printf("[DEBUG] Updating GameServerDeploymentRollout %q: %#v", d.Id(), obj) + updateMask := []string{} + + if d.HasChange("default_game_server_config") { + updateMask = append(updateMask, "defaultGameServerConfig") + } + + if d.HasChange("game_server_config_overrides") { + updateMask = append(updateMask, "gameServerConfigOverrides") + } + // updateMask is a URL parameter but not present in the schema, so replaceVars + // won't set it + url, err = addQueryParams(url, map[string]string{"updateMask": strings.Join(updateMask, ",")}) + if err != nil { + return err + } + res, err := sendRequestWithTimeout(config, "PATCH", project, url, obj, d.Timeout(schema.TimeoutUpdate)) + + if err != nil { + return fmt.Errorf("Error updating GameServerDeploymentRollout %q: %s", d.Id(), err) + } + + err = gameServicesOperationWaitTime( + config, res, project, "Updating GameServerDeploymentRollout", + int(d.Timeout(schema.TimeoutUpdate).Minutes())) + + if err != nil { + return err + } + + return resourceGameServicesGameServerDeploymentRolloutRead(d, meta) +} + +func resourceGameServicesGameServerDeploymentRolloutDelete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + project, err := getProject(d, config) + if err != nil { + return err + } + + url, err := replaceVars(d, config, "{{GameServicesBasePath}}projects/{{project}}/locations/global/gameServerDeployments/{{deployment_id}}/rollout?updateMask=defaultGameServerConfig") + if err != nil { + return err + } + + var obj map[string]interface{} + log.Printf("[DEBUG] Deleting GameServerDeploymentRollout %q", d.Id()) + + res, err := sendRequestWithTimeout(config, "PATCH", project, url, obj, d.Timeout(schema.TimeoutDelete)) + if err != nil { + return handleNotFoundError(err, d, "GameServerDeploymentRollout") + } + + err = gameServicesOperationWaitTime( + config, res, project, "Deleting GameServerDeploymentRollout", + int(d.Timeout(schema.TimeoutDelete).Minutes())) + + if err != nil { + return err + } + + log.Printf("[DEBUG] Finished deleting GameServerDeploymentRollout %q: %#v", d.Id(), res) + return nil +} + +func resourceGameServicesGameServerDeploymentRolloutImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + config := meta.(*Config) + if err := parseImportId([]string{ + "projects/(?P[^/]+)/locations/global/gameServerDeployments/(?P[^/]+)/rollout/(?P[^/]+)", + "(?P[^/]+)/(?P[^/]+)/(?P[^/]+)", + "(?P[^/]+)/(?P[^/]+)", + }, d, config); err != nil { + return nil, err + } + + // Replace import id for the resource id + id, err := replaceVars(d, config, "projects/{{project}}/locations/global/gameServerDeployments/{{deployment_id}}/rollout") + if err != nil { + return nil, fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + return []*schema.ResourceData{d}, nil +} + +func flattenGameServicesGameServerDeploymentRolloutName(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenGameServicesGameServerDeploymentRolloutDefaultGameServerConfig(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenGameServicesGameServerDeploymentRolloutGameServerConfigOverrides(v interface{}, d *schema.ResourceData, config *Config) interface{} { + if v == nil { + return v + } + l := v.([]interface{}) + transformed := make([]interface{}, 0, len(l)) + for _, raw := range l { + original := raw.(map[string]interface{}) + if len(original) < 1 { + // Do not include empty json objects coming back from the api + continue + } + transformed = append(transformed, map[string]interface{}{ + "realms_selector": flattenGameServicesGameServerDeploymentRolloutGameServerConfigOverridesRealmsSelector(original["realmsSelector"], d, config), + "config_version": flattenGameServicesGameServerDeploymentRolloutGameServerConfigOverridesConfigVersion(original["configVersion"], d, config), + }) + } + return transformed +} +func flattenGameServicesGameServerDeploymentRolloutGameServerConfigOverridesRealmsSelector(v interface{}, d *schema.ResourceData, config *Config) interface{} { + if v == nil { + return nil + } + original := v.(map[string]interface{}) + if len(original) == 0 { + return nil + } + transformed := make(map[string]interface{}) + transformed["realms"] = + flattenGameServicesGameServerDeploymentRolloutGameServerConfigOverridesRealmsSelectorRealms(original["realms"], d, config) + return []interface{}{transformed} +} +func flattenGameServicesGameServerDeploymentRolloutGameServerConfigOverridesRealmsSelectorRealms(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenGameServicesGameServerDeploymentRolloutGameServerConfigOverridesConfigVersion(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func expandGameServicesGameServerDeploymentRolloutDefaultGameServerConfig(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandGameServicesGameServerDeploymentRolloutGameServerConfigOverrides(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + l := v.([]interface{}) + req := make([]interface{}, 0, len(l)) + for _, raw := range l { + if raw == nil { + continue + } + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) + + transformedRealmsSelector, err := expandGameServicesGameServerDeploymentRolloutGameServerConfigOverridesRealmsSelector(original["realms_selector"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedRealmsSelector); val.IsValid() && !isEmptyValue(val) { + transformed["realmsSelector"] = transformedRealmsSelector + } + + transformedConfigVersion, err := expandGameServicesGameServerDeploymentRolloutGameServerConfigOverridesConfigVersion(original["config_version"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedConfigVersion); val.IsValid() && !isEmptyValue(val) { + transformed["configVersion"] = transformedConfigVersion + } + + req = append(req, transformed) + } + return req, nil +} + +func expandGameServicesGameServerDeploymentRolloutGameServerConfigOverridesRealmsSelector(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + l := v.([]interface{}) + if len(l) == 0 || l[0] == nil { + return nil, nil + } + raw := l[0] + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) + + transformedRealms, err := expandGameServicesGameServerDeploymentRolloutGameServerConfigOverridesRealmsSelectorRealms(original["realms"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedRealms); val.IsValid() && !isEmptyValue(val) { + transformed["realms"] = transformedRealms + } + + return transformed, nil +} + +func expandGameServicesGameServerDeploymentRolloutGameServerConfigOverridesRealmsSelectorRealms(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandGameServicesGameServerDeploymentRolloutGameServerConfigOverridesConfigVersion(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} diff --git a/google-beta/resource_game_services_game_server_deployment_rollout_generated_test.go b/google-beta/resource_game_services_game_server_deployment_rollout_generated_test.go new file mode 100644 index 0000000000..3a44d152ce --- /dev/null +++ b/google-beta/resource_game_services_game_server_deployment_rollout_generated_test.go @@ -0,0 +1,103 @@ +// ---------------------------------------------------------------------------- +// +// *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +// +// ---------------------------------------------------------------------------- +// +// This file is automatically generated by Magic Modules and manual +// changes will be clobbered when the file is regenerated. +// +// Please read more about how to change this file in +// .github/CONTRIBUTING.md. +// +// ---------------------------------------------------------------------------- + +package google + +import ( + "fmt" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" +) + +func TestAccGameServicesGameServerDeploymentRollout_gameServiceDeploymentRolloutBasicExample(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "random_suffix": acctest.RandString(10), + } + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProvidersOiCS, + CheckDestroy: testAccCheckGameServicesGameServerDeploymentRolloutDestroy, + Steps: []resource.TestStep{ + { + Config: testAccGameServicesGameServerDeploymentRollout_gameServiceDeploymentRolloutBasicExample(context), + }, + }, + }) +} + +func testAccGameServicesGameServerDeploymentRollout_gameServiceDeploymentRolloutBasicExample(context map[string]interface{}) string { + return Nprintf(` +resource "google_game_services_game_server_deployment" "default" { + provider = google-private + + deployment_id = "tf-test-tf-test-deployment%{random_suffix}" + description = "a deployment description" +} + +resource "google_game_services_game_server_config" "default" { + provider = google-private + + config_id = "tf-test-tf-test-config%{random_suffix}" + deployment_id = google_game_services_game_server_deployment.default.deployment_id + description = "a config description" + + fleet_configs { + name = "some-non-guid" + fleet_spec = jsonencode({ "replicas" : 1, "scheduling" : "Packed", "template" : { "metadata" : { "name" : "tf-test-game-server-template" }, "spec" : { "template" : { "spec" : { "containers" : [{ "name" : "simple-udp-server", "image" : "gcr.io/agones-images/udp-server:0.14" }] } } } } }) + + // Alternate usage: + // fleet_spec = file(fleet_configs.json) + } +} + +resource "google_game_services_game_server_deployment_rollout" "default" { + provider = google-private + + deployment_id = google_game_services_game_server_deployment.default.deployment_id + default_game_server_config = google_game_services_game_server_config.default.name +} +`, context) +} + +func testAccCheckGameServicesGameServerDeploymentRolloutDestroy(s *terraform.State) error { + for name, rs := range s.RootModule().Resources { + if rs.Type != "google_game_services_game_server_deployment_rollout" { + continue + } + if strings.HasPrefix(name, "data.") { + continue + } + + config := testAccProvider.Meta().(*Config) + + url, err := replaceVarsForTest(config, rs, "{{GameServicesBasePath}}projects/{{project}}/locations/global/gameServerDeployments/{{deployment_id}}/rollout") + if err != nil { + return err + } + + _, err = sendRequest(config, "GET", "", url, nil) + if err == nil { + return fmt.Errorf("GameServicesGameServerDeploymentRollout still exists at %s", url) + } + } + + return nil +} diff --git a/google-beta/resource_game_services_game_server_deployment_rollout_sweeper_test.go b/google-beta/resource_game_services_game_server_deployment_rollout_sweeper_test.go new file mode 100644 index 0000000000..cec448d465 --- /dev/null +++ b/google-beta/resource_game_services_game_server_deployment_rollout_sweeper_test.go @@ -0,0 +1,119 @@ +// ---------------------------------------------------------------------------- +// +// *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +// +// ---------------------------------------------------------------------------- +// +// This file is automatically generated by Magic Modules and manual +// changes will be clobbered when the file is regenerated. +// +// Please read more about how to change this file in +// .github/CONTRIBUTING.md. +// +// ---------------------------------------------------------------------------- + +package google + +import ( + "context" + "log" + "strings" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" +) + +func init() { + resource.AddTestSweepers("GameServicesGameServerDeploymentRollout", &resource.Sweeper{ + Name: "GameServicesGameServerDeploymentRollout", + F: testSweepGameServicesGameServerDeploymentRollout, + }) +} + +// At the time of writing, the CI only passes us-central1 as the region +func testSweepGameServicesGameServerDeploymentRollout(region string) error { + resourceName := "GameServicesGameServerDeploymentRollout" + log.Printf("[INFO][SWEEPER_LOG] Starting sweeper for %s", resourceName) + + config, err := sharedConfigForRegion(region) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] error getting shared config for region: %s", err) + return err + } + + err = config.LoadAndValidate(context.Background()) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] error loading: %s", err) + return err + } + + // Setup variables to replace in list template + d := &ResourceDataMock{ + FieldsInSchema: map[string]interface{}{ + "project": config.Project, + "region": region, + "location": region, + "zone": "-", + }, + } + + listTemplate := strings.Split("https://gameservices.googleapis.com/v1beta/projects/{{project}}/locations/global/gameServerDeployments/{{deployment_id}}/rollout", "?")[0] + listUrl, err := replaceVars(d, config, listTemplate) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] error preparing sweeper list url: %s", err) + return nil + } + + res, err := sendRequest(config, "GET", config.Project, listUrl, nil) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] Error in response from request %s: %s", listUrl, err) + return nil + } + + resourceList, ok := res["gameServerDeploymentRollouts"] + if !ok { + log.Printf("[INFO][SWEEPER_LOG] Nothing found in response.") + return nil + } + + rl := resourceList.([]interface{}) + + log.Printf("[INFO][SWEEPER_LOG] Found %d items in %s list response.", len(rl), resourceName) + // items who don't match the tf-test prefix + nonPrefixCount := 0 + for _, ri := range rl { + obj := ri.(map[string]interface{}) + if obj["name"] == nil { + log.Printf("[INFO][SWEEPER_LOG] %s resource name was nil", resourceName) + return nil + } + + name := GetResourceNameFromSelfLink(obj["name"].(string)) + // Only sweep resources with the test prefix + if !strings.HasPrefix(name, "tf-test") { + nonPrefixCount++ + continue + } + + deleteTemplate := "https://gameservices.googleapis.com/v1beta/projects/{{project}}/locations/global/gameServerDeployments/{{deployment_id}}/rollout?updateMask=defaultGameServerConfig" + deleteUrl, err := replaceVars(d, config, deleteTemplate) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] error preparing delete url: %s", err) + return nil + } + deleteUrl = deleteUrl + name + + // Don't wait on operations as we may have a lot to delete + _, err = sendRequest(config, "DELETE", config.Project, deleteUrl, nil) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] Error deleting for url %s : %s", deleteUrl, err) + } else { + log.Printf("[INFO][SWEEPER_LOG] Sent delete request for %s resource: %s", resourceName, name) + } + } + + if nonPrefixCount > 0 { + log.Printf("[INFO][SWEEPER_LOG] %d items without tf_test prefix remain.", nonPrefixCount) + } + + return nil +} diff --git a/google-beta/resource_game_services_game_server_deployment_sweeper_test.go b/google-beta/resource_game_services_game_server_deployment_sweeper_test.go new file mode 100644 index 0000000000..c169785993 --- /dev/null +++ b/google-beta/resource_game_services_game_server_deployment_sweeper_test.go @@ -0,0 +1,119 @@ +// ---------------------------------------------------------------------------- +// +// *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +// +// ---------------------------------------------------------------------------- +// +// This file is automatically generated by Magic Modules and manual +// changes will be clobbered when the file is regenerated. +// +// Please read more about how to change this file in +// .github/CONTRIBUTING.md. +// +// ---------------------------------------------------------------------------- + +package google + +import ( + "context" + "log" + "strings" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" +) + +func init() { + resource.AddTestSweepers("GameServicesGameServerDeployment", &resource.Sweeper{ + Name: "GameServicesGameServerDeployment", + F: testSweepGameServicesGameServerDeployment, + }) +} + +// At the time of writing, the CI only passes us-central1 as the region +func testSweepGameServicesGameServerDeployment(region string) error { + resourceName := "GameServicesGameServerDeployment" + log.Printf("[INFO][SWEEPER_LOG] Starting sweeper for %s", resourceName) + + config, err := sharedConfigForRegion(region) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] error getting shared config for region: %s", err) + return err + } + + err = config.LoadAndValidate(context.Background()) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] error loading: %s", err) + return err + } + + // Setup variables to replace in list template + d := &ResourceDataMock{ + FieldsInSchema: map[string]interface{}{ + "project": config.Project, + "region": region, + "location": region, + "zone": "-", + }, + } + + listTemplate := strings.Split("https://gameservices.googleapis.com/v1beta/projects/{{project}}/locations/{{location}}/gameServerDeployments", "?")[0] + listUrl, err := replaceVars(d, config, listTemplate) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] error preparing sweeper list url: %s", err) + return nil + } + + res, err := sendRequest(config, "GET", config.Project, listUrl, nil) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] Error in response from request %s: %s", listUrl, err) + return nil + } + + resourceList, ok := res["gameServerDeployments"] + if !ok { + log.Printf("[INFO][SWEEPER_LOG] Nothing found in response.") + return nil + } + + rl := resourceList.([]interface{}) + + log.Printf("[INFO][SWEEPER_LOG] Found %d items in %s list response.", len(rl), resourceName) + // items who don't match the tf-test prefix + nonPrefixCount := 0 + for _, ri := range rl { + obj := ri.(map[string]interface{}) + if obj["name"] == nil { + log.Printf("[INFO][SWEEPER_LOG] %s resource name was nil", resourceName) + return nil + } + + name := GetResourceNameFromSelfLink(obj["name"].(string)) + // Only sweep resources with the test prefix + if !strings.HasPrefix(name, "tf-test") { + nonPrefixCount++ + continue + } + + deleteTemplate := "https://gameservices.googleapis.com/v1beta/projects/{{project}}/locations/{{location}}/gameServerDeployments/{{deployment_id}}" + deleteUrl, err := replaceVars(d, config, deleteTemplate) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] error preparing delete url: %s", err) + return nil + } + deleteUrl = deleteUrl + name + + // Don't wait on operations as we may have a lot to delete + _, err = sendRequest(config, "DELETE", config.Project, deleteUrl, nil) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] Error deleting for url %s : %s", deleteUrl, err) + } else { + log.Printf("[INFO][SWEEPER_LOG] Sent delete request for %s resource: %s", resourceName, name) + } + } + + if nonPrefixCount > 0 { + log.Printf("[INFO][SWEEPER_LOG] %d items without tf_test prefix remain.", nonPrefixCount) + } + + return nil +} diff --git a/google-beta/resource_game_services_realm.go b/google-beta/resource_game_services_realm.go new file mode 100644 index 0000000000..eccb219b7b --- /dev/null +++ b/google-beta/resource_game_services_realm.go @@ -0,0 +1,370 @@ +// ---------------------------------------------------------------------------- +// +// *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +// +// ---------------------------------------------------------------------------- +// +// This file is automatically generated by Magic Modules and manual +// changes will be clobbered when the file is regenerated. +// +// Please read more about how to change this file in +// .github/CONTRIBUTING.md. +// +// ---------------------------------------------------------------------------- + +package google + +import ( + "fmt" + "log" + "reflect" + "strings" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" +) + +func resourceGameServicesRealm() *schema.Resource { + return &schema.Resource{ + Create: resourceGameServicesRealmCreate, + Read: resourceGameServicesRealmRead, + Update: resourceGameServicesRealmUpdate, + Delete: resourceGameServicesRealmDelete, + + Importer: &schema.ResourceImporter{ + State: resourceGameServicesRealmImport, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(4 * time.Minute), + Update: schema.DefaultTimeout(4 * time.Minute), + Delete: schema.DefaultTimeout(4 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "realm_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: `GCP region of the Realm.`, + }, + "time_zone": { + Type: schema.TypeString, + Required: true, + Description: `Required. Time zone where all realm-specific policies are evaluated. The value of +this field must be from the IANA time zone database: +https://www.iana.org/time-zones.`, + }, + "description": { + Type: schema.TypeString, + Optional: true, + Description: `Human readable description of the realm.`, + }, + "labels": { + Type: schema.TypeMap, + Optional: true, + Description: `The labels associated with this realm. Each label is a key-value pair.`, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "location": { + Type: schema.TypeString, + Optional: true, + Description: `Location of the Realm.`, + Default: "global", + }, + "etag": { + Type: schema.TypeString, + Computed: true, + Description: `ETag of the resource.`, + }, + "name": { + Type: schema.TypeString, + Computed: true, + Description: `The resource id of the realm, of the form: +'projects/{project_id}/locations/{location}/realms/{realm_id}'. For +example, 'projects/my-project/locations/{location}/realms/my-realm'.`, + }, + "project": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + }, + } +} + +func resourceGameServicesRealmCreate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + obj := make(map[string]interface{}) + labelsProp, err := expandGameServicesRealmLabels(d.Get("labels"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("labels"); !isEmptyValue(reflect.ValueOf(labelsProp)) && (ok || !reflect.DeepEqual(v, labelsProp)) { + obj["labels"] = labelsProp + } + timeZoneProp, err := expandGameServicesRealmTimeZone(d.Get("time_zone"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("time_zone"); !isEmptyValue(reflect.ValueOf(timeZoneProp)) && (ok || !reflect.DeepEqual(v, timeZoneProp)) { + obj["timeZone"] = timeZoneProp + } + descriptionProp, err := expandGameServicesRealmDescription(d.Get("description"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("description"); !isEmptyValue(reflect.ValueOf(descriptionProp)) && (ok || !reflect.DeepEqual(v, descriptionProp)) { + obj["description"] = descriptionProp + } + + url, err := replaceVars(d, config, "{{GameServicesBasePath}}projects/{{project}}/locations/{{location}}/realms?realmId={{realm_id}}") + if err != nil { + return err + } + + log.Printf("[DEBUG] Creating new Realm: %#v", obj) + project, err := getProject(d, config) + if err != nil { + return err + } + res, err := sendRequestWithTimeout(config, "POST", project, url, obj, d.Timeout(schema.TimeoutCreate)) + if err != nil { + return fmt.Errorf("Error creating Realm: %s", err) + } + + // Store the ID now + id, err := replaceVars(d, config, "projects/{{project}}/locations/{{location}}/realms/{{realm_id}}") + if err != nil { + return fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + // Use the resource in the operation response to populate + // identity fields and d.Id() before read + var opRes map[string]interface{} + err = gameServicesOperationWaitTimeWithResponse( + config, res, &opRes, project, "Creating Realm", + int(d.Timeout(schema.TimeoutCreate).Minutes())) + if err != nil { + // The resource didn't actually create + d.SetId("") + return fmt.Errorf("Error waiting to create Realm: %s", err) + } + + if err := d.Set("name", flattenGameServicesRealmName(opRes["name"], d, config)); err != nil { + return err + } + + // This may have caused the ID to update - update it if so. + id, err = replaceVars(d, config, "projects/{{project}}/locations/{{location}}/realms/{{realm_id}}") + if err != nil { + return fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + log.Printf("[DEBUG] Finished creating Realm %q: %#v", d.Id(), res) + + return resourceGameServicesRealmRead(d, meta) +} + +func resourceGameServicesRealmRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + url, err := replaceVars(d, config, "{{GameServicesBasePath}}projects/{{project}}/locations/{{location}}/realms/{{realm_id}}") + if err != nil { + return err + } + + project, err := getProject(d, config) + if err != nil { + return err + } + res, err := sendRequest(config, "GET", project, url, nil) + if err != nil { + return handleNotFoundError(err, d, fmt.Sprintf("GameServicesRealm %q", d.Id())) + } + + if err := d.Set("project", project); err != nil { + return fmt.Errorf("Error reading Realm: %s", err) + } + + if err := d.Set("name", flattenGameServicesRealmName(res["name"], d, config)); err != nil { + return fmt.Errorf("Error reading Realm: %s", err) + } + if err := d.Set("labels", flattenGameServicesRealmLabels(res["labels"], d, config)); err != nil { + return fmt.Errorf("Error reading Realm: %s", err) + } + if err := d.Set("time_zone", flattenGameServicesRealmTimeZone(res["timeZone"], d, config)); err != nil { + return fmt.Errorf("Error reading Realm: %s", err) + } + if err := d.Set("etag", flattenGameServicesRealmEtag(res["etag"], d, config)); err != nil { + return fmt.Errorf("Error reading Realm: %s", err) + } + if err := d.Set("description", flattenGameServicesRealmDescription(res["description"], d, config)); err != nil { + return fmt.Errorf("Error reading Realm: %s", err) + } + + return nil +} + +func resourceGameServicesRealmUpdate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + project, err := getProject(d, config) + if err != nil { + return err + } + + obj := make(map[string]interface{}) + labelsProp, err := expandGameServicesRealmLabels(d.Get("labels"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("labels"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, labelsProp)) { + obj["labels"] = labelsProp + } + timeZoneProp, err := expandGameServicesRealmTimeZone(d.Get("time_zone"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("time_zone"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, timeZoneProp)) { + obj["timeZone"] = timeZoneProp + } + descriptionProp, err := expandGameServicesRealmDescription(d.Get("description"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("description"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, descriptionProp)) { + obj["description"] = descriptionProp + } + + url, err := replaceVars(d, config, "{{GameServicesBasePath}}projects/{{project}}/locations/{{location}}/realms/{{realm_id}}") + if err != nil { + return err + } + + log.Printf("[DEBUG] Updating Realm %q: %#v", d.Id(), obj) + updateMask := []string{} + + if d.HasChange("labels") { + updateMask = append(updateMask, "labels") + } + + if d.HasChange("time_zone") { + updateMask = append(updateMask, "timeZone") + } + + if d.HasChange("description") { + updateMask = append(updateMask, "description") + } + // updateMask is a URL parameter but not present in the schema, so replaceVars + // won't set it + url, err = addQueryParams(url, map[string]string{"updateMask": strings.Join(updateMask, ",")}) + if err != nil { + return err + } + res, err := sendRequestWithTimeout(config, "PATCH", project, url, obj, d.Timeout(schema.TimeoutUpdate)) + + if err != nil { + return fmt.Errorf("Error updating Realm %q: %s", d.Id(), err) + } + + err = gameServicesOperationWaitTime( + config, res, project, "Updating Realm", + int(d.Timeout(schema.TimeoutUpdate).Minutes())) + + if err != nil { + return err + } + + return resourceGameServicesRealmRead(d, meta) +} + +func resourceGameServicesRealmDelete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + project, err := getProject(d, config) + if err != nil { + return err + } + + url, err := replaceVars(d, config, "{{GameServicesBasePath}}projects/{{project}}/locations/{{location}}/realms/{{realm_id}}") + if err != nil { + return err + } + + var obj map[string]interface{} + log.Printf("[DEBUG] Deleting Realm %q", d.Id()) + + res, err := sendRequestWithTimeout(config, "DELETE", project, url, obj, d.Timeout(schema.TimeoutDelete)) + if err != nil { + return handleNotFoundError(err, d, "Realm") + } + + err = gameServicesOperationWaitTime( + config, res, project, "Deleting Realm", + int(d.Timeout(schema.TimeoutDelete).Minutes())) + + if err != nil { + return err + } + + log.Printf("[DEBUG] Finished deleting Realm %q: %#v", d.Id(), res) + return nil +} + +func resourceGameServicesRealmImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + config := meta.(*Config) + if err := parseImportId([]string{ + "projects/(?P[^/]+)/locations/(?P[^/]+)/realms//(?P[^/]+)", + "(?P[^/]+)/(?P[^/]+)/(?P[^/]+)", + "(?P[^/]+)/(?P[^/]+)", + }, d, config); err != nil { + return nil, err + } + + // Replace import id for the resource id + id, err := replaceVars(d, config, "projects/{{project}}/locations/{{location}}/realms/{{realm_id}}") + if err != nil { + return nil, fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + return []*schema.ResourceData{d}, nil +} + +func flattenGameServicesRealmName(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenGameServicesRealmLabels(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenGameServicesRealmTimeZone(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenGameServicesRealmEtag(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenGameServicesRealmDescription(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func expandGameServicesRealmLabels(v interface{}, d TerraformResourceData, config *Config) (map[string]string, error) { + if v == nil { + return map[string]string{}, nil + } + m := make(map[string]string) + for k, val := range v.(map[string]interface{}) { + m[k] = val.(string) + } + return m, nil +} + +func expandGameServicesRealmTimeZone(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandGameServicesRealmDescription(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} diff --git a/google-beta/resource_game_services_realm_generated_test.go b/google-beta/resource_game_services_realm_generated_test.go new file mode 100644 index 0000000000..aca15c1bd6 --- /dev/null +++ b/google-beta/resource_game_services_realm_generated_test.go @@ -0,0 +1,83 @@ +// ---------------------------------------------------------------------------- +// +// *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +// +// ---------------------------------------------------------------------------- +// +// This file is automatically generated by Magic Modules and manual +// changes will be clobbered when the file is regenerated. +// +// Please read more about how to change this file in +// .github/CONTRIBUTING.md. +// +// ---------------------------------------------------------------------------- + +package google + +import ( + "fmt" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" +) + +func TestAccGameServicesRealm_gameServiceRealmBasicExample(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "random_suffix": acctest.RandString(10), + } + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProvidersOiCS, + CheckDestroy: testAccCheckGameServicesRealmDestroy, + Steps: []resource.TestStep{ + { + Config: testAccGameServicesRealm_gameServiceRealmBasicExample(context), + }, + }, + }) +} + +func testAccGameServicesRealm_gameServiceRealmBasicExample(context map[string]interface{}) string { + return Nprintf(` +resource "google_game_services_realm" "default" { + provider = google-private + + realm_id = "tf-test-tf-test-realm%{random_suffix}" + time_zone = "EST" + location = "global" + + description = "one of the nine" +} +`, context) +} + +func testAccCheckGameServicesRealmDestroy(s *terraform.State) error { + for name, rs := range s.RootModule().Resources { + if rs.Type != "google_game_services_realm" { + continue + } + if strings.HasPrefix(name, "data.") { + continue + } + + config := testAccProvider.Meta().(*Config) + + url, err := replaceVarsForTest(config, rs, "{{GameServicesBasePath}}projects/{{project}}/locations/{{location}}/realms/{{realm_id}}") + if err != nil { + return err + } + + _, err = sendRequest(config, "GET", "", url, nil) + if err == nil { + return fmt.Errorf("GameServicesRealm still exists at %s", url) + } + } + + return nil +} diff --git a/google-beta/resource_game_services_realm_sweeper_test.go b/google-beta/resource_game_services_realm_sweeper_test.go new file mode 100644 index 0000000000..05806dad84 --- /dev/null +++ b/google-beta/resource_game_services_realm_sweeper_test.go @@ -0,0 +1,119 @@ +// ---------------------------------------------------------------------------- +// +// *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +// +// ---------------------------------------------------------------------------- +// +// This file is automatically generated by Magic Modules and manual +// changes will be clobbered when the file is regenerated. +// +// Please read more about how to change this file in +// .github/CONTRIBUTING.md. +// +// ---------------------------------------------------------------------------- + +package google + +import ( + "context" + "log" + "strings" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" +) + +func init() { + resource.AddTestSweepers("GameServicesRealm", &resource.Sweeper{ + Name: "GameServicesRealm", + F: testSweepGameServicesRealm, + }) +} + +// At the time of writing, the CI only passes us-central1 as the region +func testSweepGameServicesRealm(region string) error { + resourceName := "GameServicesRealm" + log.Printf("[INFO][SWEEPER_LOG] Starting sweeper for %s", resourceName) + + config, err := sharedConfigForRegion(region) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] error getting shared config for region: %s", err) + return err + } + + err = config.LoadAndValidate(context.Background()) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] error loading: %s", err) + return err + } + + // Setup variables to replace in list template + d := &ResourceDataMock{ + FieldsInSchema: map[string]interface{}{ + "project": config.Project, + "region": region, + "location": region, + "zone": "-", + }, + } + + listTemplate := strings.Split("https://gameservices.googleapis.com/v1beta/projects/{{project}}/locations/{{location}}/realms/", "?")[0] + listUrl, err := replaceVars(d, config, listTemplate) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] error preparing sweeper list url: %s", err) + return nil + } + + res, err := sendRequest(config, "GET", config.Project, listUrl, nil) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] Error in response from request %s: %s", listUrl, err) + return nil + } + + resourceList, ok := res["realms"] + if !ok { + log.Printf("[INFO][SWEEPER_LOG] Nothing found in response.") + return nil + } + + rl := resourceList.([]interface{}) + + log.Printf("[INFO][SWEEPER_LOG] Found %d items in %s list response.", len(rl), resourceName) + // items who don't match the tf-test prefix + nonPrefixCount := 0 + for _, ri := range rl { + obj := ri.(map[string]interface{}) + if obj["name"] == nil { + log.Printf("[INFO][SWEEPER_LOG] %s resource name was nil", resourceName) + return nil + } + + name := GetResourceNameFromSelfLink(obj["name"].(string)) + // Only sweep resources with the test prefix + if !strings.HasPrefix(name, "tf-test") { + nonPrefixCount++ + continue + } + + deleteTemplate := "https://gameservices.googleapis.com/v1beta/projects/{{project}}/locations/{{location}}/realms/{{realm_id}}" + deleteUrl, err := replaceVars(d, config, deleteTemplate) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] error preparing delete url: %s", err) + return nil + } + deleteUrl = deleteUrl + name + + // Don't wait on operations as we may have a lot to delete + _, err = sendRequest(config, "DELETE", config.Project, deleteUrl, nil) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] Error deleting for url %s : %s", deleteUrl, err) + } else { + log.Printf("[INFO][SWEEPER_LOG] Sent delete request for %s resource: %s", resourceName, name) + } + } + + if nonPrefixCount > 0 { + log.Printf("[INFO][SWEEPER_LOG] %d items without tf_test prefix remain.", nonPrefixCount) + } + + return nil +} diff --git a/website/docs/r/game_services_game_server_cluster.html.markdown b/website/docs/r/game_services_game_server_cluster.html.markdown new file mode 100644 index 0000000000..f6b15760a2 --- /dev/null +++ b/website/docs/r/game_services_game_server_cluster.html.markdown @@ -0,0 +1,138 @@ +--- +# ---------------------------------------------------------------------------- +# +# *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +# +# ---------------------------------------------------------------------------- +# +# This file is automatically generated by Magic Modules and manual +# changes will be clobbered when the file is regenerated. +# +# Please read more about how to change this file in +# .github/CONTRIBUTING.md. +# +# ---------------------------------------------------------------------------- +subcategory: "Google Game Services" +layout: "google" +page_title: "Google: google_game_services_game_server_cluster" +sidebar_current: "docs-google-game-services-game-server-cluster" +description: |- + A game server cluster resource. +--- + +# google\_game\_services\_game\_server\_cluster + +A game server cluster resource. + +~> **Warning:** This resource is in beta, and should be used with the terraform-provider-google-beta provider. +See [Provider Versions](https://terraform.io/docs/providers/google/guides/provider_versions.html) for more details on beta resources. + +To get more information about GameServerCluster, see: + +* [API documentation](https://cloud.google.com/game-servers/docs/reference/rest/v1beta/projects.locations.realms.gameServerClusters) +* How-to Guides + * [Official Documentation](https://cloud.google.com/game-servers/docs) + +## Argument Reference + +The following arguments are supported: + + +* `cluster_id` - + (Required) + Required. The resource name of the game server cluster + +* `realm_id` - + (Required) + The realm id of the game server realm. + +* `connection_info` - + (Required) + Game server cluster connection information. This information is used to + manage game server clusters. Structure is documented below. + + +The `connection_info` block supports: + +* `gke_cluster_reference` - + (Required) + Reference of the GKE cluster where the game servers are installed. Structure is documented below. + +* `namespace` - + (Required) + Namespace designated on the game server cluster where the game server + instances will be created. The namespace existence will be validated + during creation. + + +The `gke_cluster_reference` block supports: + +* `cluster` - + (Required) + The full or partial name of a GKE cluster, using one of the following + forms: + * `projects/{project_id}/locations/{location}/clusters/{cluster_id}` + * `locations/{location}/clusters/{cluster_id}` + * `{cluster_id}` + If project and location are not specified, the project and location of the + GameServerCluster resource are used to generate the full name of the + GKE cluster. + +- - - + + +* `location` - + (Optional) + Location of the Cluster. + +* `labels` - + (Optional) + The labels associated with this game server cluster. Each label is a + key-value pair. + +* `description` - + (Optional) + Human readable description of the cluster. + +* `project` - (Optional) The ID of the project in which the resource belongs. + If it is not provided, the provider project is used. + + +## Attributes Reference + +In addition to the arguments listed above, the following computed attributes are exported: + +* `id` - an identifier for the resource with format `projects/{{project}}/locations/{{location}}/realms/{{realm_id}}/gameServerClusters/{{cluster_id}}` + +* `name` - + The resource id of the game server cluster, eg: + `projects/{project_id}/locations/{location}/realms/{realm_id}/gameServerClusters/{cluster_id}`. + For example, + `projects/my-project/locations/{location}/realms/zanzibar/gameServerClusters/my-onprem-cluster`. + + +## Timeouts + +This resource provides the following +[Timeouts](/docs/configuration/resources.html#timeouts) configuration options: + +- `create` - Default is 4 minutes. +- `update` - Default is 4 minutes. +- `delete` - Default is 4 minutes. + +## Import + +GameServerCluster can be imported using any of these accepted formats: + +``` +$ terraform import -provider=google-beta google_game_services_game_server_cluster.default projects/{{project}}/locations/{{location}}/realms/{{realm_id}}/gameServerClusters/{{name}} +$ terraform import -provider=google-beta google_game_services_game_server_cluster.default {{project}}/{{location}}/{{realm_id}}/{{name}} +$ terraform import -provider=google-beta google_game_services_game_server_cluster.default {{location}}/{{realm_id}}/{{name}} +``` + +-> If you're importing a resource with beta features, make sure to include `-provider=google-beta` +as an argument so that Terraform uses the correct provider to import your resource. + +## User Project Overrides + +This resource supports [User Project Overrides](https://www.terraform.io/docs/providers/google/guides/provider_reference.html#user_project_override). diff --git a/website/docs/r/game_services_game_server_config.html.markdown b/website/docs/r/game_services_game_server_config.html.markdown new file mode 100644 index 0000000000..0ff881bf29 --- /dev/null +++ b/website/docs/r/game_services_game_server_config.html.markdown @@ -0,0 +1,227 @@ +--- +# ---------------------------------------------------------------------------- +# +# *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +# +# ---------------------------------------------------------------------------- +# +# This file is automatically generated by Magic Modules and manual +# changes will be clobbered when the file is regenerated. +# +# Please read more about how to change this file in +# .github/CONTRIBUTING.md. +# +# ---------------------------------------------------------------------------- +subcategory: "Google Game Services" +layout: "google" +page_title: "Google: google_game_services_game_server_config" +sidebar_current: "docs-google-game-services-game-server-config" +description: |- + A game server config resource. +--- + +# google\_game\_services\_game\_server\_config + +A game server config resource. Configs are global and immutable. + +~> **Warning:** This resource is in beta, and should be used with the terraform-provider-google-beta provider. +See [Provider Versions](https://terraform.io/docs/providers/google/guides/provider_versions.html) for more details on beta resources. + +To get more information about GameServerConfig, see: + +* [API documentation](https://cloud.google.com/game-servers/docs/reference/rest/v1beta/projects.locations.gameServerDeployments.configs) +* How-to Guides + * [Official Documentation](https://cloud.google.com/game-servers/docs) + + +## Example Usage - Game Service Config Basic + + +```hcl +resource "google_game_services_game_server_deployment" "default" { + provider = google-private + + deployment_id = "tf-test-deployment" + description = "a deployment description" +} + +resource "google_game_services_game_server_config" "default" { + provider = google-private + + config_id = "tf-test-config" + deployment_id = google_game_services_game_server_deployment.default.deployment_id + description = "a config description" + + fleet_configs { + name = "something-unique" + fleet_spec = jsonencode({ "replicas" : 1, "scheduling" : "Packed", "template" : { "metadata" : { "name" : "tf-test-game-server-template" }, "spec" : { "template" : { "spec" : { "containers" : [{ "name" : "simple-udp-server", "image" : "gcr.io/agones-images/udp-server:0.14" }] } } } } }) + } + + scaling_configs { + name = "scaling-config-name" + fleet_autoscaler_spec = jsonencode({"spec":{"fleetName":"simple-udp","policy":{"type":"Webhook","webhook":{"service":{"name":"autoscaler-webhook-service","namespace":"default","path":"scale"}}}}}) + selectors { + labels = { + "one" : "two" + } + } + + schedules { + cron_job_duration = "3.500s" + cron_spec = "0 0 * * 0" + } + } +} +``` + +## Argument Reference + +The following arguments are supported: + + +* `config_id` - + (Required) + A unique id for the deployment config. + +* `deployment_id` - + (Required) + A unique id for the deployment. + +* `fleet_configs` - + (Required) + The fleet config contains list of fleet specs. In the Single Cloud, there + will be only one. Structure is documented below. + + +The `fleet_configs` block supports: + +* `fleet_spec` - + (Required) + The fleet spec, which is sent to Agones to configure fleet. + The spec can be passed as inline json but it is recommended to use a file reference + instead. File references can contain the json or yaml format of the fleet spec. Eg: + * fleet_spec = jsonencode(yamldecode(file("fleet_configs.yaml"))) + * fleet_spec = file("fleet_configs.json") + The format of the spec can be found : + `https://agones.dev/site/docs/reference/fleet/`. + +* `name` - + (Required) + The name of the FleetConfig. + +- - - + + +* `location` - + (Optional) + Location of the Deployment. + +* `description` - + (Optional) + The description of the game server config. + +* `labels` - + (Optional) + The labels associated with this game server config. Each label is a + key-value pair. + +* `scaling_configs` - + (Optional) + Optional. This contains the autoscaling settings. Structure is documented below. + +* `project` - (Optional) The ID of the project in which the resource belongs. + If it is not provided, the provider project is used. + + +The `scaling_configs` block supports: + +* `name` - + (Required) + The name of the ScalingConfig + +* `fleet_autoscaler_spec` - + (Required) + Fleet autoscaler spec, which is sent to Agones. + Example spec can be found : + https://agones.dev/site/docs/reference/fleetautoscaler/ + +* `selectors` - + (Optional) + Labels used to identify the clusters to which this scaling config + applies. A cluster is subject to this scaling config if its labels match + any of the selector entries. Structure is documented below. + +* `schedules` - + (Optional) + The schedules to which this scaling config applies. Structure is documented below. + + +The `selectors` block supports: + +* `labels` - + (Optional) + Set of labels to group by. + +The `schedules` block supports: + +* `start_time` - + (Optional) + The start time of the event. + A timestamp in RFC3339 UTC "Zulu" format, accurate to nanoseconds. Example: "2014-10-02T15:01:23.045123456Z". + +* `end_time` - + (Optional) + The end time of the event. + A timestamp in RFC3339 UTC "Zulu" format, accurate to nanoseconds. Example: "2014-10-02T15:01:23.045123456Z". + +* `cron_job_duration` - + (Optional) + The duration for the cron job event. The duration of the event is effective + after the cron job's start time. + A duration in seconds with up to nine fractional digits, terminated by 's'. Example: "3.5s". + +* `cron_spec` - + (Optional) + The cron definition of the scheduled event. See + https://en.wikipedia.org/wiki/Cron. Cron spec specifies the local time as + defined by the realm. + +## Attributes Reference + +In addition to the arguments listed above, the following computed attributes are exported: + +* `id` - an identifier for the resource with format `projects/{{project}}/locations/{{location}}/gameServerDeployments/{{deployment_id}}/configs/{{config_id}}` + +* `name` - + The resource name of the game server config, in the form: + `projects/{project_id}/locations/{location}/gameServerDeployments/{deployment_id}/configs/{config_id}`. + + +## Timeouts + +This resource provides the following +[Timeouts](/docs/configuration/resources.html#timeouts) configuration options: + +- `create` - Default is 4 minutes. +- `delete` - Default is 4 minutes. + +## Import + +GameServerConfig can be imported using any of these accepted formats: + +``` +$ terraform import -provider=google-beta google_game_services_game_server_config.default projects/{{project}}/locations/{{location}}/gameServerDeployments/{{deployment_id}}/configs/{{name}} +$ terraform import -provider=google-beta google_game_services_game_server_config.default {{project}}/{{location}}/{{deployment_id}}/{{name}} +$ terraform import -provider=google-beta google_game_services_game_server_config.default {{location}}/{{deployment_id}}/{{name}} +``` + +-> If you're importing a resource with beta features, make sure to include `-provider=google-beta` +as an argument so that Terraform uses the correct provider to import your resource. + +## User Project Overrides + +This resource supports [User Project Overrides](https://www.terraform.io/docs/providers/google/guides/provider_reference.html#user_project_override). diff --git a/website/docs/r/game_services_game_server_deployment.html.markdown b/website/docs/r/game_services_game_server_deployment.html.markdown new file mode 100644 index 0000000000..0d0d505fda --- /dev/null +++ b/website/docs/r/game_services_game_server_deployment.html.markdown @@ -0,0 +1,120 @@ +--- +# ---------------------------------------------------------------------------- +# +# *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +# +# ---------------------------------------------------------------------------- +# +# This file is automatically generated by Magic Modules and manual +# changes will be clobbered when the file is regenerated. +# +# Please read more about how to change this file in +# .github/CONTRIBUTING.md. +# +# ---------------------------------------------------------------------------- +subcategory: "Google Game Services" +layout: "google" +page_title: "Google: google_game_services_game_server_deployment" +sidebar_current: "docs-google-game-services-game-server-deployment" +description: |- + A game server deployment resource. +--- + +# google\_game\_services\_game\_server\_deployment + +A game server deployment resource. + +~> **Warning:** This resource is in beta, and should be used with the terraform-provider-google-beta provider. +See [Provider Versions](https://terraform.io/docs/providers/google/guides/provider_versions.html) for more details on beta resources. + +To get more information about GameServerDeployment, see: + +* [API documentation](https://cloud.google.com/game-servers/docs/reference/rest/v1beta/projects.locations.gameServerDeployments) +* How-to Guides + * [Official Documentation](https://cloud.google.com/game-servers/docs) + + +## Example Usage - Game Service Deployment Basic + + +```hcl +resource "google_game_services_game_server_deployment" "default" { + provider = google-private + + deployment_id = "tf-test-deployment" + description = "a deployment description" +} +``` + +## Argument Reference + +The following arguments are supported: + + +* `deployment_id` - + (Required) + A unique id for the deployment. + + +- - - + + +* `description` - + (Optional) + Human readable description of the game server deployment. + +* `location` - + (Optional) + Location of the Deployment. + +* `labels` - + (Optional) + The labels associated with this game server deployment. Each label is a + key-value pair. + +* `project` - (Optional) The ID of the project in which the resource belongs. + If it is not provided, the provider project is used. + + +## Attributes Reference + +In addition to the arguments listed above, the following computed attributes are exported: + +* `id` - an identifier for the resource with format `projects/{{project}}/locations/{{location}}/gameServerDeployments/{{deployment_id}}` + +* `name` - + The resource id of the game server deployment, eg: + `projects/{project_id}/locations/{location}/gameServerDeployments/{deployment_id}`. + For example, + `projects/my-project/locations/{location}/gameServerDeployments/my-deployment`. + + +## Timeouts + +This resource provides the following +[Timeouts](/docs/configuration/resources.html#timeouts) configuration options: + +- `create` - Default is 4 minutes. +- `update` - Default is 4 minutes. +- `delete` - Default is 4 minutes. + +## Import + +GameServerDeployment can be imported using any of these accepted formats: + +``` +$ terraform import -provider=google-beta google_game_services_game_server_deployment.default projects/{{project}}/locations/{{location}}/gameServerDeployments/{{name}} +$ terraform import -provider=google-beta google_game_services_game_server_deployment.default {{project}}/{{location}}/{{name}} +$ terraform import -provider=google-beta google_game_services_game_server_deployment.default {{location}}/{{name}} +``` + +-> If you're importing a resource with beta features, make sure to include `-provider=google-beta` +as an argument so that Terraform uses the correct provider to import your resource. + +## User Project Overrides + +This resource supports [User Project Overrides](https://www.terraform.io/docs/providers/google/guides/provider_reference.html#user_project_override). diff --git a/website/docs/r/game_services_game_server_deployment_rollout.html.markdown b/website/docs/r/game_services_game_server_deployment_rollout.html.markdown new file mode 100644 index 0000000000..f694711cb4 --- /dev/null +++ b/website/docs/r/game_services_game_server_deployment_rollout.html.markdown @@ -0,0 +1,159 @@ +--- +# ---------------------------------------------------------------------------- +# +# *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +# +# ---------------------------------------------------------------------------- +# +# This file is automatically generated by Magic Modules and manual +# changes will be clobbered when the file is regenerated. +# +# Please read more about how to change this file in +# .github/CONTRIBUTING.md. +# +# ---------------------------------------------------------------------------- +subcategory: "Google Game Services" +layout: "google" +page_title: "Google: google_game_services_game_server_deployment_rollout" +sidebar_current: "docs-google-game-services-game-server-deployment-rollout" +description: |- + This represents the rollout state. +--- + +# google\_game\_services\_game\_server\_deployment\_rollout + +This represents the rollout state. This is part of the game server +deployment. + +~> **Warning:** This resource is in beta, and should be used with the terraform-provider-google-beta provider. +See [Provider Versions](https://terraform.io/docs/providers/google/guides/provider_versions.html) for more details on beta resources. + +To get more information about GameServerDeploymentRollout, see: + +* [API documentation](https://cloud.google.com/game-servers/docs/reference/rest/v1beta/GameServerDeploymentRollout) +* How-to Guides + * [Official Documentation](https://cloud.google.com/game-servers/docs) + + +## Example Usage - Game Service Deployment Rollout Basic + + +```hcl +resource "google_game_services_game_server_deployment" "default" { + provider = google-private + + deployment_id = "tf-test-deployment" + description = "a deployment description" +} + +resource "google_game_services_game_server_config" "default" { + provider = google-private + + config_id = "tf-test-config" + deployment_id = google_game_services_game_server_deployment.default.deployment_id + description = "a config description" + + fleet_configs { + name = "some-non-guid" + fleet_spec = jsonencode({ "replicas" : 1, "scheduling" : "Packed", "template" : { "metadata" : { "name" : "tf-test-game-server-template" }, "spec" : { "template" : { "spec" : { "containers" : [{ "name" : "simple-udp-server", "image" : "gcr.io/agones-images/udp-server:0.14" }] } } } } }) + + // Alternate usage: + // fleet_spec = file(fleet_configs.json) + } +} + +resource "google_game_services_game_server_deployment_rollout" "default" { + provider = google-private + + deployment_id = google_game_services_game_server_deployment.default.deployment_id + default_game_server_config = google_game_services_game_server_config.default.name +} +``` + +## Argument Reference + +The following arguments are supported: + + +* `deployment_id` - + (Required) + The deployment to rollout the new config to. Only 1 rollout must be associated with each deployment. + +* `default_game_server_config` - + (Required) + This field points to the game server config that is + applied by default to all realms and clusters. For example, + `projects/my-project/locations/global/gameServerDeployments/my-game/configs/my-config`. + + +- - - + + +* `game_server_config_overrides` - + (Optional) + The game_server_config_overrides contains the per game server config + overrides. The overrides are processed in the order they are listed. As + soon as a match is found for a cluster, the rest of the list is not + processed. Structure is documented below. + +* `project` - (Optional) The ID of the project in which the resource belongs. + If it is not provided, the provider project is used. + + +The `game_server_config_overrides` block supports: + +* `realms_selector` - + (Optional) + Selection by realms. Structure is documented below. + +* `config_version` - + (Optional) + Version of the configuration. + + +The `realms_selector` block supports: + +* `realms` - + (Optional) + List of realms to match against. + +## Attributes Reference + +In addition to the arguments listed above, the following computed attributes are exported: + +* `id` - an identifier for the resource with format `projects/{{project}}/locations/global/gameServerDeployments/{{deployment_id}}/rollout` + +* `name` - + The resource id of the game server deployment + eg: `projects/my-project/locations/global/gameServerDeployments/my-deployment/rollout`. + + +## Timeouts + +This resource provides the following +[Timeouts](/docs/configuration/resources.html#timeouts) configuration options: + +- `create` - Default is 4 minutes. +- `update` - Default is 4 minutes. +- `delete` - Default is 4 minutes. + +## Import + +GameServerDeploymentRollout can be imported using any of these accepted formats: + +``` +$ terraform import -provider=google-beta google_game_services_game_server_deployment_rollout.default projects/{{project}}/locations/global/gameServerDeployments/{{deployment_id}}/rollout/{{name}} +$ terraform import -provider=google-beta google_game_services_game_server_deployment_rollout.default {{project}}/{{deployment_id}}/{{name}} +$ terraform import -provider=google-beta google_game_services_game_server_deployment_rollout.default {{deployment_id}}/{{name}} +``` + +-> If you're importing a resource with beta features, make sure to include `-provider=google-beta` +as an argument so that Terraform uses the correct provider to import your resource. + +## User Project Overrides + +This resource supports [User Project Overrides](https://www.terraform.io/docs/providers/google/guides/provider_reference.html#user_project_override). diff --git a/website/docs/r/game_services_realm.html.markdown b/website/docs/r/game_services_realm.html.markdown new file mode 100644 index 0000000000..209d6f7f8d --- /dev/null +++ b/website/docs/r/game_services_realm.html.markdown @@ -0,0 +1,130 @@ +--- +# ---------------------------------------------------------------------------- +# +# *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +# +# ---------------------------------------------------------------------------- +# +# This file is automatically generated by Magic Modules and manual +# changes will be clobbered when the file is regenerated. +# +# Please read more about how to change this file in +# .github/CONTRIBUTING.md. +# +# ---------------------------------------------------------------------------- +subcategory: "Google Game Services" +layout: "google" +page_title: "Google: google_game_services_realm" +sidebar_current: "docs-google-game-services-realm" +description: |- + A Realm resource. +--- + +# google\_game\_services\_realm + +A Realm resource. + +~> **Warning:** This resource is in beta, and should be used with the terraform-provider-google-beta provider. +See [Provider Versions](https://terraform.io/docs/providers/google/guides/provider_versions.html) for more details on beta resources. + +To get more information about Realm, see: + +* [API documentation](https://cloud.google.com/game-servers/docs/reference/rest/v1beta/projects.locations.realms) +* How-to Guides + * [Official Documentation](https://cloud.google.com/game-servers/docs) + + +## Example Usage - Game Service Realm Basic + + +```hcl +resource "google_game_services_realm" "default" { + provider = google-private + + realm_id = "tf-test-realm" + time_zone = "EST" + location = "global" + + description = "one of the nine" +} +``` + +## Argument Reference + +The following arguments are supported: + + +* `time_zone` - + (Required) + Required. Time zone where all realm-specific policies are evaluated. The value of + this field must be from the IANA time zone database: + https://www.iana.org/time-zones. + +* `realm_id` - + (Required) + GCP region of the Realm. + + +- - - + + +* `labels` - + (Optional) + The labels associated with this realm. Each label is a key-value pair. + +* `description` - + (Optional) + Human readable description of the realm. + +* `location` - + (Optional) + Location of the Realm. + +* `project` - (Optional) The ID of the project in which the resource belongs. + If it is not provided, the provider project is used. + + +## Attributes Reference + +In addition to the arguments listed above, the following computed attributes are exported: + +* `id` - an identifier for the resource with format `projects/{{project}}/locations/{{location}}/realms/{{realm_id}}` + +* `name` - + The resource id of the realm, of the form: + `projects/{project_id}/locations/{location}/realms/{realm_id}`. For + example, `projects/my-project/locations/{location}/realms/my-realm`. + +* `etag` - + ETag of the resource. + + +## Timeouts + +This resource provides the following +[Timeouts](/docs/configuration/resources.html#timeouts) configuration options: + +- `create` - Default is 4 minutes. +- `update` - Default is 4 minutes. +- `delete` - Default is 4 minutes. + +## Import + +Realm can be imported using any of these accepted formats: + +``` +$ terraform import -provider=google-beta google_game_services_realm.default projects/{{project}}/locations/{{location}}/realms//{{name}} +$ terraform import -provider=google-beta google_game_services_realm.default {{project}}/{{location}}/{{name}} +$ terraform import -provider=google-beta google_game_services_realm.default {{location}}/{{name}} +``` + +-> If you're importing a resource with beta features, make sure to include `-provider=google-beta` +as an argument so that Terraform uses the correct provider to import your resource. + +## User Project Overrides + +This resource supports [User Project Overrides](https://www.terraform.io/docs/providers/google/guides/provider_reference.html#user_project_override).