diff --git a/mmv1/third_party/terraform/resources/resource_apigee_flowhook.go b/mmv1/third_party/terraform/resources/resource_apigee_flowhook.go new file mode 100644 index 000000000000..689e000210d3 --- /dev/null +++ b/mmv1/third_party/terraform/resources/resource_apigee_flowhook.go @@ -0,0 +1,240 @@ +package google + +import ( + "fmt" + "log" + "reflect" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func ResourceApigeeFlowhook() *schema.Resource { + return &schema.Resource{ + Create: resourceApigeeFlowhookCreate, + Read: resourceApigeeFlowhookRead, + Delete: resourceApigeeFlowhookDelete, + + Importer: &schema.ResourceImporter{ + State: resourceApigeeFlowhookImport, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(20 * time.Minute), + Delete: schema.DefaultTimeout(20 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "description": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: `Description of the flow hook.`, + }, + "environment": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: `The resource ID of the environment.`, + }, + "flow_hook_point": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: `Where in the API call flow the flow hook is invoked. Must be one of PreProxyFlowHook, PostProxyFlowHook, PreTargetFlowHook, or PostTargetFlowHook.`, + }, + "org_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: `The Apigee Organization associated with the environment`, + }, + "sharedflow": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: `Id of the Sharedflow attaching to a flowhook point.`, + }, + "continue_on_error": { + Type: schema.TypeBool, + ForceNew: true, + Optional: true, + Default: true, + Description: `Flag that specifies whether execution should continue if the flow hook throws an exception. Set to true to continue execution. Set to false to stop execution if the flow hook throws an exception. Defaults to true.`, + }, + }, + UseJSONNumber: true, + } +} + +func resourceApigeeFlowhookCreate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + userAgent, err := generateUserAgentString(d, config.UserAgent) + if err != nil { + return err + } + + obj := make(map[string]interface{}) + descriptionProp, err := expandApigeeFlowhookDescription(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 + } + sharedflowProp, err := expandApigeeFlowhookSharedflow(d.Get("sharedflow"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("sharedflow"); !isEmptyValue(reflect.ValueOf(sharedflowProp)) && (ok || !reflect.DeepEqual(v, sharedflowProp)) { + obj["sharedFlow"] = sharedflowProp + } + continue_on_errorProp, err := expandApigeeFlowhookContinueOnError(d.Get("continue_on_error"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("continue_on_error"); !isEmptyValue(reflect.ValueOf(continue_on_errorProp)) && (ok || !reflect.DeepEqual(v, continue_on_errorProp)) { + obj["continueOnError"] = continue_on_errorProp + } + + url, err := replaceVars(d, config, "{{ApigeeBasePath}}organizations/{{org_id}}/environments/{{environment}}/flowhooks/{{flow_hook_point}}") + if err != nil { + return err + } + + log.Printf("[DEBUG] Creating new Flowhook: %#v", obj) + billingProject := "" + + // err == nil indicates that the billing_project value was found + if bp, err := getBillingProject(d, config); err == nil { + billingProject = bp + } + + res, err := SendRequestWithTimeout(config, "PUT", billingProject, url, userAgent, obj, d.Timeout(schema.TimeoutCreate)) + if err != nil { + return fmt.Errorf("Error creating Flowhook: %s", err) + } + + // Store the ID now + id, err := replaceVars(d, config, "organizations/{{org_id}}/environments/{{environment}}/flowhooks/{{flow_hook_point}}") + if err != nil { + return fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + log.Printf("[DEBUG] Finished creating Flowhook %q: %#v", d.Id(), res) + + return resourceApigeeFlowhookRead(d, meta) +} + +func resourceApigeeFlowhookRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + userAgent, err := generateUserAgentString(d, config.UserAgent) + if err != nil { + return err + } + + url, err := replaceVars(d, config, "{{ApigeeBasePath}}organizations/{{org_id}}/environments/{{environment}}/flowhooks/{{flow_hook_point}}") + if err != nil { + return err + } + + billingProject := "" + + // err == nil indicates that the billing_project value was found + if bp, err := getBillingProject(d, config); err == nil { + billingProject = bp + } + + res, err := SendRequest(config, "GET", billingProject, url, userAgent, nil) + if err != nil { + return handleNotFoundError(err, d, fmt.Sprintf("ApigeeFlowhook %q", d.Id())) + } + if res["sharedFlow"] == nil || res["sharedFlow"].(string) == "" { + //if response does not contain shared_flow field, then nothing is attached to this flowhook, we treat this "binding" resource non-existent + d.SetId("") + return nil + } + if err := d.Set("description", flattenApigeeFlowhookDescription(res["description"], d, config)); err != nil { + return fmt.Errorf("Error reading Flowhook: %s", err) + } + if err := d.Set("sharedflow", flattenApigeeFlowhookSharedflow(res["sharedFlow"], d, config)); err != nil { + return fmt.Errorf("Error reading Flowhook: %s", err) + } + if err := d.Set("continue_on_error", flattenApigeeFlowhookContinueOnError(res["continueOnError"], d, config)); err != nil { + return fmt.Errorf("Error reading Flowhook: %s", err) + } + + return nil +} + +func resourceApigeeFlowhookDelete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + userAgent, err := generateUserAgentString(d, config.UserAgent) + if err != nil { + return err + } + + billingProject := "" + + url, err := replaceVars(d, config, "{{ApigeeBasePath}}organizations/{{org_id}}/environments/{{environment}}/flowhooks/{{flow_hook_point}}") + if err != nil { + return err + } + + var obj map[string]interface{} + log.Printf("[DEBUG] Deleting Flowhook %q", d.Id()) + + // err == nil indicates that the billing_project value was found + if bp, err := getBillingProject(d, config); err == nil { + billingProject = bp + } + + res, err := SendRequestWithTimeout(config, "DELETE", billingProject, url, userAgent, obj, d.Timeout(schema.TimeoutDelete)) + if err != nil { + return handleNotFoundError(err, d, "Flowhook") + } + + log.Printf("[DEBUG] Finished deleting Flowhook %q: %#v", d.Id(), res) + return nil +} + +func resourceApigeeFlowhookImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + config := meta.(*Config) + if err := parseImportId([]string{ + "organizations/(?P[^/]+)/environments/(?P[^/]+)/flowhooks/(?P[^/]+)", + "(?P[^/]+)/(?P[^/]+)/(?P[^/]+)", + }, d, config); err != nil { + return nil, err + } + + // Replace import id for the resource id + id, err := replaceVars(d, config, "organizations/{{org_id}}/environments/{{environment}}/flowhooks/{{flow_hook_point}}") + if err != nil { + return nil, fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + return []*schema.ResourceData{d}, nil +} + +func flattenApigeeFlowhookDescription(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenApigeeFlowhookSharedflow(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenApigeeFlowhookContinueOnError(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func expandApigeeFlowhookDescription(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandApigeeFlowhookSharedflow(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandApigeeFlowhookContinueOnError(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} diff --git a/mmv1/third_party/terraform/resources/resource_apigee_sharedflow.go b/mmv1/third_party/terraform/resources/resource_apigee_sharedflow.go new file mode 100644 index 000000000000..e0c136552d0f --- /dev/null +++ b/mmv1/third_party/terraform/resources/resource_apigee_sharedflow.go @@ -0,0 +1,460 @@ +// ---------------------------------------------------------------------------- +// +// This file is partially automatically generated by Magic Modules and with manual +// changes to resourceApigeeSharedFlowCreate +// +// ---------------------------------------------------------------------------- + +package google + +import ( + "context" + "encoding/json" + "fmt" + "io" + "log" + "net/http" + "os" + "time" + + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "google.golang.org/api/googleapi" +) + +func ResourceApigeeSharedFlow() *schema.Resource { + return &schema.Resource{ + Create: resourceApigeeSharedFlowCreate, + Read: resourceApigeeSharedFlowRead, + Update: resourceApigeeSharedFlowUpdate, + Delete: resourceApigeeSharedFlowDelete, + + Importer: &schema.ResourceImporter{ + State: resourceApigeeSharedFlowImport, + }, + + CustomizeDiff: customdiff.All( + /* + If any of the config_bundle, detect_md5hash or md5hash is changed, + then an update is expected, so we tell Terraform core to expect update on meta_data, + latest_revision_id and revision + */ + + customdiff.ComputedIf("meta_data", apigeeSharedflowDetectBundleUpdate), + customdiff.ComputedIf("latest_revision_id", apigeeSharedflowDetectBundleUpdate), + customdiff.ComputedIf("revision", apigeeSharedflowDetectBundleUpdate), + ), + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(20 * time.Minute), + Update: schema.DefaultTimeout(20 * time.Minute), + Delete: schema.DefaultTimeout(20 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: `The ID of the shared flow.`, + }, + "org_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: `The Apigee Organization associated with the Apigee instance, +in the format 'organizations/{{org_name}}'.`, + }, + "latest_revision_id": { + Type: schema.TypeString, + Computed: true, + Description: `The id of the most recently created revision for this shared flow.`, + }, + "meta_data": { + Type: schema.TypeList, + Computed: true, + Description: `Metadata describing the shared flow.`, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "created_at": { + Type: schema.TypeString, + Computed: true, + Description: `Time at which the API proxy was created, in milliseconds since epoch.`, + }, + "last_modified_at": { + Type: schema.TypeString, + Computed: true, + Description: `Time at which the API proxy was most recently modified, in milliseconds since epoch.`, + }, + "sub_type": { + Type: schema.TypeString, + Computed: true, + Description: `The type of entity described`, + }, + }, + }, + }, + "revision": { + Type: schema.TypeList, + Computed: true, + Description: `A list of revisions of this shared flow.`, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "config_bundle": { + Type: schema.TypeString, + Required: true, + Description: `A path to the config bundle zip you want to upload. Must be defined if content is not.`, + }, + "md5hash": { + Type: schema.TypeString, + Computed: true, + Description: `Base 64 MD5 hash of the uploaded config bundle.`, + }, + "detect_md5hash": { + Type: schema.TypeString, + Optional: true, + Default: "Different Hash", + Description: `A hash of local config bundle in string, user needs to use a Terraform Hash function of their choice. A change in hash will trigger an update.`, + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + localMd5Hash := "" + if config_bundle, ok := d.GetOkExists("config_bundle"); ok { + localMd5Hash = getFileMd5Hash(config_bundle.(string)) + } + if localMd5Hash == "" { + return false + } + + // `old` is the md5 hash we speculated from server responses, + // when apply responded with succeed, hash is set to the hash of uploaded bundle + if old != localMd5Hash { + return false + } + + return true + }, + }, + }, + UseJSONNumber: true, + } +} + +func resourceApigeeSharedFlowCreate(d *schema.ResourceData, meta interface{}) error { + ctx := context.TODO() + tflog.Info(ctx, "resourceApigeeSharedFlowCreate") + log.Printf("[DEBUG] resourceApigeeSharedFlowCreate") + + log.Printf("[DEBUG] resourceApigeeSharedFlowCreate, name= %s", d.Get("name").(string)) + log.Printf("[DEBUG] resourceApigeeSharedFlowCreate, org_id=, %s", d.Get("org_id").(string)) + log.Printf("[DEBUG] resourceApigeeSharedFlowCreate, config_bundle=, %s", d.Get("config_bundle").(string)) + + config := meta.(*Config) + userAgent, err := generateUserAgentString(d, config.UserAgent) + if err != nil { + return err + } + + var file *os.File + var localMd5Hash string + if configBundlePath, ok := d.GetOk("config_bundle"); ok { + var err error + file, err = os.Open(configBundlePath.(string)) + if err != nil { + return err + } + localMd5Hash = getFileMd5Hash(configBundlePath.(string)) + } else { + return fmt.Errorf("Error, \"config_bundle\" must be specified") + } + + url, err := replaceVars(d, config, "{{ApigeeBasePath}}organizations/{{org_id}}/sharedflows?name={{name}}&action=import") + if err != nil { + return err + } + billingProject := "" + + // err == nil indicates that the billing_project value was found + if bp, err := getBillingProject(d, config); err == nil { + billingProject = bp + } + + log.Printf("[DEBUG] resourceApigeeSharedFlowCreate, url=, %s", url) + res, err := sendRequestRawBodyWithTimeout(config, "POST", billingProject, url, userAgent, file, "application/octet-stream", d.Timeout(schema.TimeoutCreate)) + + log.Printf("[DEBUG] sendRequestRawBodyWithTimeout Done") + if err != nil { + return fmt.Errorf("Error creating SharedFlow: %s", err) + } + + // Store the ID now + id, err := replaceVars(d, config, "organizations/{{org_id}}/sharedflows/{{name}}") + if err != nil { + return fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + log.Printf("[DEBUG] create d.SetId done, id = %s", id) + + log.Printf("[DEBUG] Finished creating SharedFlow %q: %#v", d.Id(), res) + + if resourceApigeeSharedFlowRead(d, meta) != nil { + return fmt.Errorf("Error reading SharedFlow at end of Create: %s", err) + } + d.Set("md5hash", localMd5Hash) + d.Set("detect_md5hash", localMd5Hash) + return nil +} + +func resourceApigeeSharedFlowUpdate(d *schema.ResourceData, meta interface{}) error { + //For how sharedflow api is implemented, just treat an update as create, when the name is same, it will create a new revision + return resourceApigeeSharedFlowCreate(d, meta) +} + +func resourceApigeeSharedFlowRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + userAgent, err := generateUserAgentString(d, config.UserAgent) + if err != nil { + return err + } + + url, err := replaceVars(d, config, "{{ApigeeBasePath}}organizations/{{org_id}}/sharedflows/{{name}}") + if err != nil { + return err + } + log.Printf("[DEBUG] sharedflow read url is: %s", url) + + billingProject := "" + + // err == nil indicates that the billing_project value was found + if bp, err := getBillingProject(d, config); err == nil { + billingProject = bp + } + log.Printf("[DEBUG] resourceApigeeSharedFlowRead sendRequest") + log.Printf("[DEBUG] resourceApigeeSharedFlowRead, url=, %s", url) + res, err := SendRequest(config, "GET", billingProject, url, userAgent, nil) + if err != nil { + return handleNotFoundError(err, d, fmt.Sprintf("ApigeeSharedFlow %q", d.Id())) + } + log.Printf("[DEBUG] resourceApigeeSharedFlowRead sendRequest completed") + previousLastModifiedAt := getApigeeSharedFlowLastModifiedAt(d) + if err := d.Set("meta_data", flattenApigeeSharedFlowMetaData(res["metaData"], d, config)); err != nil { + return fmt.Errorf("Error reading SharedFlow: %s", err) + } + currentLastModifiedAt := getApigeeSharedFlowLastModifiedAt(d) + if err := d.Set("name", flattenApigeeSharedFlowName(res["name"], d, config)); err != nil { + return fmt.Errorf("Error reading SharedFlow: %s", err) + } + if err := d.Set("revision", flattenApigeeSharedFlowRevision(res["revision"], d, config)); err != nil { + return fmt.Errorf("Error reading SharedFlow: %s", err) + } + if err := d.Set("latest_revision_id", flattenApigeeSharedFlowLatestRevisionId(res["latestRevisionId"], d, config)); err != nil { + return fmt.Errorf("Error reading SharedFlow: %s", err) + } + + //setting hash to suggest update + if previousLastModifiedAt != currentLastModifiedAt { + d.Set("md5hash", "UNKNOWN") + d.Set("detect_md5hash", "UNKNOWN") + } + return nil +} + +func getApigeeSharedFlowLastModifiedAt(d *schema.ResourceData) string { + + metaDataRaw := d.Get("meta_data").([]interface{}) + if len(metaDataRaw) != 1 { + //in Terraform Schema, a nest in object is implemented as an array of length one, even if it's technically an object + return "UNKNOWN" + } + metaData := metaDataRaw[0].(map[string]interface{}) + if metaData == nil { + return "UNKNOWN" + } + lastModifiedAt := metaData["last_modified_at"].(string) + if lastModifiedAt == "" { + return "UNKNOWN" + } + return lastModifiedAt +} + +func resourceApigeeSharedFlowDelete(d *schema.ResourceData, meta interface{}) error { + log.Printf("[DEBUG] resourceApigeeSharedFlowDelete") + config := meta.(*Config) + userAgent, err := generateUserAgentString(d, config.UserAgent) + if err != nil { + return err + } + + billingProject := "" + + url, err := replaceVars(d, config, "{{ApigeeBasePath}}organizations/{{org_id}}/sharedflows/{{name}}") + if err != nil { + return err + } + + var obj map[string]interface{} + log.Printf("[DEBUG] Deleting SharedFlow %q", d.Id()) + + // err == nil indicates that the billing_project value was found + if bp, err := getBillingProject(d, config); err == nil { + billingProject = bp + } + + res, err := SendRequestWithTimeout(config, "DELETE", billingProject, url, userAgent, obj, d.Timeout(schema.TimeoutDelete)) + if err != nil { + return handleNotFoundError(err, d, "SharedFlow") + } + + log.Printf("[DEBUG] Finished deleting SharedFlow %q: %#v", d.Id(), res) + return nil +} + +func resourceApigeeSharedFlowImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + config := meta.(*Config) + if err := parseImportId([]string{ + "organizations/(?P[^/]+)/sharedflows/(?P[^/]+)", + "(?P[^/]+)/(?P[^/]+)", + }, d, config); err != nil { + return nil, err + } + + // Replace import id for the resource id + id, err := replaceVars(d, config, "organizations/{{org_id}}/sharedflows/{{name}}") + + if err != nil { + return nil, fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + log.Printf("[DEBUG] resourceApigeeSharedFlowImport, id= %s", id) + + return []*schema.ResourceData{d}, nil +} + +func flattenApigeeSharedFlowMetaData(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["created_at"] = + flattenApigeeSharedFlowMetaDataCreatedAt(original["createdAt"], d, config) + transformed["last_modified_at"] = + flattenApigeeSharedFlowMetaDataLastModifiedAt(original["lastModifiedAt"], d, config) + transformed["sub_type"] = + flattenApigeeSharedFlowMetaDataSubType(original["subType"], d, config) + return []interface{}{transformed} +} +func flattenApigeeSharedFlowMetaDataCreatedAt(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenApigeeSharedFlowMetaDataLastModifiedAt(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenApigeeSharedFlowMetaDataSubType(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenApigeeSharedFlowName(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenApigeeSharedFlowRevision(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenApigeeSharedFlowLatestRevisionId(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func expandApigeeSharedFlowName(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +// sendRequestRawBodyWithTimeout is derived from sendRequestWithTimeout with direct pass through of request body +func sendRequestRawBodyWithTimeout(config *Config, method, project, rawurl, userAgent string, body io.Reader, contentType string, timeout time.Duration, errorRetryPredicates ...RetryErrorPredicateFunc) (map[string]interface{}, error) { + log.Printf("[DEBUG] sendRequestRawBodyWithTimeout start") + reqHeaders := make(http.Header) + reqHeaders.Set("User-Agent", userAgent) + reqHeaders.Set("Content-Type", contentType) + + if config.UserProjectOverride && project != "" { + // Pass the project into this fn instead of parsing it from the URL because + // both project names and URLs can have colons in them. + reqHeaders.Set("X-Goog-User-Project", project) + } + + if timeout == 0 { + timeout = time.Duration(1) * time.Minute + } + + var res *http.Response + + log.Printf("[DEBUG] sendRequestRawBodyWithTimeout sending request") + + err := RetryTimeDuration( + func() error { + req, err := http.NewRequest(method, rawurl, body) + if err != nil { + return err + } + + req.Header = reqHeaders + res, err = config.Client.Do(req) + if err != nil { + return err + } + + if err := googleapi.CheckResponse(res); err != nil { + googleapi.CloseBody(res) + return err + } + + return nil + }, + timeout, + errorRetryPredicates..., + ) + if err != nil { + return nil, err + } + + if res == nil { + return nil, fmt.Errorf("Unable to parse server response. This is most likely a terraform problem, please file a bug at https://github.com/hashicorp/terraform-provider-google/issues.") + } + + // The defer call must be made outside of the retryFunc otherwise it's closed too soon. + defer googleapi.CloseBody(res) + + // 204 responses will have no body, so we're going to error with "EOF" if we + // try to parse it. Instead, we can just return nil. + if res.StatusCode == 204 { + return nil, nil + } + result := make(map[string]interface{}) + if err := json.NewDecoder(res.Body).Decode(&result); err != nil { + return nil, err + } + log.Printf("[DEBUG] sendRequestRawBodyWithTimeout returning") + return result, nil +} + +func apigeeSharedflowDetectBundleUpdate(_ context.Context, diff *schema.ResourceDiff, v interface{}) bool { + tmp, _ := diff.GetChange("detect_md5hash") + oldBundleHash := tmp.(string) + currentBundleHash := "" + if config_bundle, ok := diff.GetOkExists("config_bundle"); ok { + currentBundleHash = getFileMd5Hash(config_bundle.(string)) + } + log.Printf("[DEBUG] apigeeSharedflowDetectUpdate detect_md5hash: %s -> %s", oldBundleHash, currentBundleHash) + + if oldBundleHash != currentBundleHash { + return true + } + return diff.HasChange("config_bundle") || diff.HasChange("md5hash") +} diff --git a/mmv1/third_party/terraform/resources/resource_apigee_sharedflow_deployment.go b/mmv1/third_party/terraform/resources/resource_apigee_sharedflow_deployment.go new file mode 100644 index 000000000000..6c1124637260 --- /dev/null +++ b/mmv1/third_party/terraform/resources/resource_apigee_sharedflow_deployment.go @@ -0,0 +1,198 @@ +package google + +import ( + "fmt" + "log" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func ResourceApigeeSharedFlowDeployment() *schema.Resource { + return &schema.Resource{ + Create: resourceApigeeSharedflowDeploymentCreate, + Read: resourceApigeeSharedflowDeploymentRead, + Delete: resourceApigeeSharedflowDeploymentDelete, + + Importer: &schema.ResourceImporter{ + State: resourceApigeeSharedflowDeploymentImport, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(20 * time.Minute), + Update: schema.DefaultTimeout(20 * time.Minute), + Delete: schema.DefaultTimeout(20 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "environment": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: `The resource ID of the environment.`, + }, + "org_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: `The Apigee Organization associated with the Apigee instance`, + }, + "revision": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: `Revision of the Sharedflow to be deployed.`, + }, + "service_account": { + Type: schema.TypeString, + ForceNew: true, + Optional: true, + Description: `The service account represents the identity of the deployed proxy, and determines what permissions it has. The format must be {ACCOUNT_ID}@{PROJECT}.iam.gserviceaccount.com.`, + }, + "sharedflow_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: `Id of the Sharedflow to be deployed.`, + }, + }, + UseJSONNumber: true, + } +} + +func resourceApigeeSharedflowDeploymentCreate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + userAgent, err := generateUserAgentString(d, config.UserAgent) + if err != nil { + return err + } + + url, err := replaceVars(d, config, "{{ApigeeBasePath}}organizations/{{org_id}}/environments/{{environment}}/sharedflows/{{sharedflow_id}}/revisions/{{revision}}/deployments?override=true&serviceAccount={{service_account}}") + if err != nil { + return err + } + + log.Printf("[DEBUG] Creating new SharedflowDeployment at %s", url) + billingProject := "" + + // err == nil indicates that the billing_project value was found + if bp, err := getBillingProject(d, config); err == nil { + billingProject = bp + } + + res, err := SendRequestWithTimeout(config, "POST", billingProject, url, userAgent, nil, d.Timeout(schema.TimeoutCreate)) + if err != nil { + return fmt.Errorf("Error creating SharedflowDeployment: %s", err) + } + + // Store the ID now + id, err := replaceVars(d, config, "organizations/{{org_id}}/environments/{{environment}}/sharedflows/{{sharedflow_id}}/revisions/{{revision}}/deployments") + if err != nil { + return fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + log.Printf("[DEBUG] Finished creating SharedflowDeployment %q: %#v", d.Id(), res) + + return resourceApigeeSharedflowDeploymentRead(d, meta) +} + +func resourceApigeeSharedflowDeploymentRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + userAgent, err := generateUserAgentString(d, config.UserAgent) + if err != nil { + return err + } + + url, err := replaceVars(d, config, "{{ApigeeBasePath}}organizations/{{org_id}}/environments/{{environment}}/sharedflows/{{sharedflow_id}}/revisions/{{revision}}/deployments") + if err != nil { + return err + } + + billingProject := "" + + // err == nil indicates that the billing_project value was found + if bp, err := getBillingProject(d, config); err == nil { + billingProject = bp + } + + log.Printf("[DEBUG] Reading SharedflowDeployment at %s", url) + + res, err := SendRequest(config, "GET", billingProject, url, userAgent, nil) + if err != nil { + return handleNotFoundError(err, d, fmt.Sprintf("ApigeeSharedflowDeployment %q", d.Id())) + } + log.Printf("[DEBUG] ApigeeSharedflowDeployment deployStartTime %s", res["deployStartTime"]) + + return nil +} + +func resourceApigeeSharedflowDeploymentDelete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + userAgent, err := generateUserAgentString(d, config.UserAgent) + if err != nil { + return err + } + + billingProject := "" + + url, err := replaceVars(d, config, "{{ApigeeBasePath}}organizations/{{org_id}}/environments/{{environment}}/sharedflows/{{sharedflow_id}}/revisions/{{revision}}/deployments") + if err != nil { + return err + } + + var obj map[string]interface{} + log.Printf("[DEBUG] Deleting SharedflowDeployment %q", d.Id()) + + // err == nil indicates that the billing_project value was found + if bp, err := getBillingProject(d, config); err == nil { + billingProject = bp + } + + res, err := SendRequestWithTimeout(config, "DELETE", billingProject, url, userAgent, obj, d.Timeout(schema.TimeoutDelete)) + if err != nil { + return handleNotFoundError(err, d, "SharedflowDeployment") + } + + log.Printf("[DEBUG] Finished deleting SharedflowDeployment %q: %#v", d.Id(), res) + return nil +} + +func resourceApigeeSharedflowDeploymentImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + config := meta.(*Config) + if err := parseImportId([]string{ + "organizations/(?P[^/]+)/environments/(?P[^/]+)/sharedflows/(?P[^/]+)/revisions/(?P[^/]+)", + "(?P[^/]+)/(?P[^/]+)/(?P[^/]+)/(?P[^/]+)", + }, d, config); err != nil { + return nil, err + } + + // Replace import id for the resource id + id, err := replaceVars(d, config, "organizations/{{org_id}}/environments/{{environment}}/sharedflows/{{sharedflow_id}}/revisions/{{revision}}/deployments") + if err != nil { + return nil, fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + return []*schema.ResourceData{d}, nil +} + +func flattenApigeeSharedflowDeploymentOrgId(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenApigeeSharedflowDeploymentEnvironment(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenApigeeSharedflowDeploymentSharedflowId(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenApigeeSharedflowDeploymentRevision(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenApigeeSharedflowDeploymentServiceAccount(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} diff --git a/mmv1/third_party/terraform/tests/resource_apigee_flowhook_test.go b/mmv1/third_party/terraform/tests/resource_apigee_flowhook_test.go new file mode 100644 index 000000000000..93ab7c2f7437 --- /dev/null +++ b/mmv1/third_party/terraform/tests/resource_apigee_flowhook_test.go @@ -0,0 +1,161 @@ +package google + +import ( + "fmt" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +func TestAccApigeeFlowhook_apigeeFlowhookTestExample(t *testing.T) { + SkipIfVcr(t) + t.Parallel() + + context := map[string]interface{}{ + "org_id": GetTestOrgFromEnv(t), + "billing_account": GetTestBillingAccountFromEnv(t), + "random_suffix": RandString(t, 10), + } + + VcrTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: TestAccProviders, + CheckDestroy: testAccCheckApigeeFlowhookDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccApigeeFlowhook_apigeeFlowhookTestExample(context), + }, + { + ResourceName: "google_apigee_flowhook.flowhook_test", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{}, + }, + }, + }) +} + +func testAccApigeeFlowhook_apigeeFlowhookTestExample(context map[string]interface{}) string { + return Nprintf(` +resource "google_project" "project" { + project_id = "tf-test%{random_suffix}" + name = "tf-test%{random_suffix}" + org_id = "%{org_id}" + billing_account = "%{billing_account}" +} + +resource "google_project_service" "apigee" { + project = google_project.project.project_id + service = "apigee.googleapis.com" +} + +resource "google_project_service" "servicenetworking" { + project = google_project.project.project_id + service = "servicenetworking.googleapis.com" + depends_on = [google_project_service.apigee] +} + +resource "google_project_service" "compute" { + project = google_project.project.project_id + service = "compute.googleapis.com" + depends_on = [google_project_service.servicenetworking] +} + +resource "google_compute_network" "apigee_network" { + name = "apigee-network" + project = google_project.project.project_id + depends_on = [google_project_service.compute] +} + +resource "google_compute_global_address" "apigee_range" { + name = "apigee-range" + purpose = "VPC_PEERING" + address_type = "INTERNAL" + prefix_length = 16 + network = google_compute_network.apigee_network.id + project = google_project.project.project_id +} + +resource "google_service_networking_connection" "apigee_vpc_connection" { + network = google_compute_network.apigee_network.id + service = "servicenetworking.googleapis.com" + reserved_peering_ranges = [google_compute_global_address.apigee_range.name] + depends_on = [google_project_service.servicenetworking] +} + +resource "google_apigee_organization" "apigee_org" { + analytics_region = "us-central1" + project_id = google_project.project.project_id + authorized_network = google_compute_network.apigee_network.id + depends_on = [ + google_service_networking_connection.apigee_vpc_connection, + google_project_service.apigee, + ] +} + +resource "google_apigee_environment" "apigee_environment" { + org_id = google_apigee_organization.apigee_org.id + name = "tf-test%{random_suffix}" + description = "Apigee Environment" + display_name = "environment-1" +} + +resource "google_apigee_sharedflow" "test_apigee_sharedflow" { + name = "tf-test-apigee-sharedflow" + org_id = google_project.project.project_id + config_bundle = "./test-fixtures/apigee/apigee_sharedflow_bundle.zip" + depends_on = [google_apigee_organization.apigee_org] +} + +resource "google_apigee_sharedflow_deployment" "sharedflow_deployment_test" { + environment = google_apigee_environment.apigee_environment.name + org_id = google_apigee_sharedflow.test_apigee_sharedflow.org_id + revision = google_apigee_sharedflow.test_apigee_sharedflow.revision[length(google_apigee_sharedflow.test_apigee_sharedflow.revision)-1] + sharedflow_id = google_apigee_sharedflow.test_apigee_sharedflow.name +} + +resource "google_apigee_flowhook" "flowhook_test" { + environment = google_apigee_sharedflow_deployment.sharedflow_deployment_test.environment + org_id = google_apigee_sharedflow.test_apigee_sharedflow.org_id + flow_hook_point = "PreProxyFlowHook" + sharedflow = google_apigee_sharedflow.test_apigee_sharedflow.name + description = "test flowhook" + continue_on_error = true + } +`, context) +} + +func testAccCheckApigeeFlowhookDestroyProducer(t *testing.T) func(s *terraform.State) error { + return func(s *terraform.State) error { + for name, rs := range s.RootModule().Resources { + if rs.Type != "google_apigee_flowhook" { + continue + } + if strings.HasPrefix(name, "data.") { + continue + } + + config := GoogleProviderConfig(t) + + url, err := replaceVarsForTest(config, rs, "{{ApigeeBasePath}}organizations/{{org_id}}/environments/{{environment}}/flowhooks/{{flow_hook_point}}") + if err != nil { + return err + } + + billingProject := "" + + if config.BillingProject != "" { + billingProject = config.BillingProject + } + res, err := SendRequest(config, "GET", billingProject, url, config.UserAgent, nil) + // Flowhooks always exist, we treat the binding as a removable resource, thus we check if the sharedFlow field to detect sharedflow attachment + if err == nil && res != nil && res["sharedFlow"] != nil { + return fmt.Errorf("Flowhook still has an attachment at %s", url) + } + } + + return nil + } +} diff --git a/mmv1/third_party/terraform/tests/resource_apigee_sharedflow_deployment_test.go b/mmv1/third_party/terraform/tests/resource_apigee_sharedflow_deployment_test.go new file mode 100644 index 000000000000..57f456e93dc0 --- /dev/null +++ b/mmv1/third_party/terraform/tests/resource_apigee_sharedflow_deployment_test.go @@ -0,0 +1,151 @@ +package google + +import ( + "fmt" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +func TestAccApigeeSharedflowDeployment_apigeeSharedflowDeploymentTestExample(t *testing.T) { + SkipIfVcr(t) + t.Parallel() + + context := map[string]interface{}{ + "org_id": GetTestOrgFromEnv(t), + "billing_account": GetTestBillingAccountFromEnv(t), + "random_suffix": RandString(t, 10), + } + + VcrTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: TestAccProviders, + CheckDestroy: testAccCheckApigeeSharedflowDeploymentDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccApigeeSharedflowDeployment_apigeeSharedflowDeploymentTestExample(context), + }, + { + ResourceName: "google_apigee_sharedflow_deployment.sharedflow_deployment_test", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{}, + }, + }, + }) +} + +func testAccApigeeSharedflowDeployment_apigeeSharedflowDeploymentTestExample(context map[string]interface{}) string { + return Nprintf(` +resource "google_project" "project" { + project_id = "tf-test%{random_suffix}" + name = "tf-test%{random_suffix}" + org_id = "%{org_id}" + billing_account = "%{billing_account}" +} + +resource "google_project_service" "apigee" { + project = google_project.project.project_id + service = "apigee.googleapis.com" +} + +resource "google_project_service" "servicenetworking" { + project = google_project.project.project_id + service = "servicenetworking.googleapis.com" + depends_on = [google_project_service.apigee] +} + +resource "google_project_service" "compute" { + project = google_project.project.project_id + service = "compute.googleapis.com" + depends_on = [google_project_service.servicenetworking] +} + +resource "google_compute_network" "apigee_network" { + name = "apigee-network" + project = google_project.project.project_id + depends_on = [google_project_service.compute] +} + +resource "google_compute_global_address" "apigee_range" { + name = "apigee-range" + purpose = "VPC_PEERING" + address_type = "INTERNAL" + prefix_length = 16 + network = google_compute_network.apigee_network.id + project = google_project.project.project_id +} + +resource "google_service_networking_connection" "apigee_vpc_connection" { + network = google_compute_network.apigee_network.id + service = "servicenetworking.googleapis.com" + reserved_peering_ranges = [google_compute_global_address.apigee_range.name] + depends_on = [google_project_service.servicenetworking] +} + +resource "google_apigee_organization" "apigee_org" { + analytics_region = "us-central1" + project_id = google_project.project.project_id + authorized_network = google_compute_network.apigee_network.id + depends_on = [ + google_service_networking_connection.apigee_vpc_connection, + google_project_service.apigee, + ] +} + +resource "google_apigee_environment" "apigee_environment" { + org_id = google_apigee_organization.apigee_org.id + name = "tf-test%{random_suffix}" + description = "Apigee Environment" + display_name = "environment-1" +} + +resource "google_apigee_sharedflow" "test_apigee_sharedflow" { + name = "tf-test-apigee-sharedflow" + org_id = google_project.project.project_id + config_bundle = "./test-fixtures/apigee/apigee_sharedflow_bundle.zip" + depends_on = [google_apigee_organization.apigee_org] +} + +resource "google_apigee_sharedflow_deployment" "sharedflow_deployment_test" { + environment = google_apigee_environment.apigee_environment.name + org_id = google_apigee_sharedflow.test_apigee_sharedflow.org_id + revision = google_apigee_sharedflow.test_apigee_sharedflow.revision[length(google_apigee_sharedflow.test_apigee_sharedflow.revision)-1] + sharedflow_id = google_apigee_sharedflow.test_apigee_sharedflow.name +} +`, context) +} + +func testAccCheckApigeeSharedflowDeploymentDestroyProducer(t *testing.T) func(s *terraform.State) error { + return func(s *terraform.State) error { + for name, rs := range s.RootModule().Resources { + if rs.Type != "google_apigee_sharedflow_deployment" { + continue + } + if strings.HasPrefix(name, "data.") { + continue + } + + config := GoogleProviderConfig(t) + + url, err := replaceVarsForTest(config, rs, "{{ApigeeBasePath}}organizations/{{org_id}}/environments/{{environment}}/sharedflows/{{sharedflow_id}}/revisions/{{revision}}/deployments") + if err != nil { + return err + } + + billingProject := "" + + if config.BillingProject != "" { + billingProject = config.BillingProject + } + _, err = SendRequest(config, "GET", billingProject, url, config.UserAgent, nil) + if err == nil { + return fmt.Errorf("ApigeeSharedFlow still exists at %s", url) + } + } + + return nil + } +} diff --git a/mmv1/third_party/terraform/tests/resource_apigee_sharedflow_sweeper_test.go b/mmv1/third_party/terraform/tests/resource_apigee_sharedflow_sweeper_test.go new file mode 100644 index 000000000000..9cd60acc221b --- /dev/null +++ b/mmv1/third_party/terraform/tests/resource_apigee_sharedflow_sweeper_test.go @@ -0,0 +1,128 @@ +// ---------------------------------------------------------------------------- +// +// *** AUTO GENERATED CODE *** Type: MMv1 *** +// +// ---------------------------------------------------------------------------- +// +// 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" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func init() { + resource.AddTestSweepers("ApigeeSharedFlow", &resource.Sweeper{ + Name: "ApigeeSharedFlow", + F: testSweepApigeeSharedFlow, + }) +} + +// At the time of writing, the CI only passes us-central1 as the region +func testSweepApigeeSharedFlow(region string) error { + resourceName := "ApigeeSharedFlow" + 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 + } + + t := &testing.T{} + billingId := GetTestBillingAccountFromEnv(t) + + // Setup variables to replace in list template + d := &ResourceDataMock{ + FieldsInSchema: map[string]interface{}{ + "project": config.Project, + "region": region, + "location": region, + "zone": "-", + "billing_account": billingId, + }, + } + + listTemplate := strings.Split("https://apigee.googleapis.com/v1/organizations/{{org_id}}/sharedflows/{{name}}", "?")[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, config.UserAgent, nil) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] Error in response from request %s: %s", listUrl, err) + return nil + } + + resourceList, ok := res["sharedFlows"] + 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) + // Keep count of items that aren't sweepable for logging. + nonPrefixCount := 0 + for _, ri := range rl { + obj := ri.(map[string]interface{}) + var name string + // Id detected in the delete URL, attempt to use id. + if obj["id"] != nil { + name = GetResourceNameFromSelfLink(obj["id"].(string)) + } else if obj["name"] != nil { + name = GetResourceNameFromSelfLink(obj["name"].(string)) + } else { + log.Printf("[INFO][SWEEPER_LOG] %s resource name and id were nil", resourceName) + return nil + } + // Skip resources that shouldn't be sweeped + if !IsSweepableTestResource(name) { + nonPrefixCount++ + continue + } + + deleteTemplate := "https://apigee.googleapis.com/v1/organizations/{{org_id}}/sharedflows/{{name}}" + 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, config.UserAgent, 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 were non-sweepable and skipped.", nonPrefixCount) + } + + return nil +} diff --git a/mmv1/third_party/terraform/tests/resource_apigee_sharedflow_test.go b/mmv1/third_party/terraform/tests/resource_apigee_sharedflow_test.go new file mode 100644 index 000000000000..bfd31ebbfb47 --- /dev/null +++ b/mmv1/third_party/terraform/tests/resource_apigee_sharedflow_test.go @@ -0,0 +1,216 @@ +package google + +import ( + "fmt" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +func TestAccApigeeSharedFlow_apigeeSharedflowTestExample(t *testing.T) { + SkipIfVcr(t) + t.Parallel() + + fmt.Printf("from t: org_id %s", GetTestOrgFromEnv(t)) + + context := map[string]interface{}{ + "org_id": GetTestOrgFromEnv(t), + "billing_account": GetTestBillingAccountFromEnv(t), + "random_suffix": RandString(t, 10), + } + + VcrTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: TestAccProviders, + CheckDestroy: testAccCheckApigeeSharedFlowDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccApigeeSharedFlow_apigeeSharedflowTestExample(context), + }, + { + ResourceName: "google_apigee_sharedflow.test_apigee_sharedflow", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"config_bundle", "detect_md5hash", "md5hash"}, + }, + { + Config: testAccApigeeSharedFlow_apigeeSharedflowTestExampleUpdate(context), + }, + { + ResourceName: "google_apigee_sharedflow.test_apigee_sharedflow", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"config_bundle", "detect_md5hash", "md5hash"}, + }, + }, + }) +} + +func testAccApigeeSharedFlow_apigeeSharedflowTestExample(context map[string]interface{}) string { + return Nprintf(` +resource "google_project" "project" { + project_id = "tf-test%{random_suffix}" + name = "tf-test%{random_suffix}" + org_id = "%{org_id}" + billing_account = "%{billing_account}" +} + +resource "google_project_service" "apigee" { + project = google_project.project.project_id + service = "apigee.googleapis.com" +} + +resource "google_project_service" "servicenetworking" { + project = google_project.project.project_id + service = "servicenetworking.googleapis.com" + depends_on = [google_project_service.apigee] +} + +resource "google_project_service" "compute" { + project = google_project.project.project_id + service = "compute.googleapis.com" + depends_on = [google_project_service.servicenetworking] +} + +resource "google_compute_network" "apigee_network" { + name = "apigee-network" + project = google_project.project.project_id + depends_on = [google_project_service.compute] +} + +resource "google_compute_global_address" "apigee_range" { + name = "apigee-range" + purpose = "VPC_PEERING" + address_type = "INTERNAL" + prefix_length = 16 + network = google_compute_network.apigee_network.id + project = google_project.project.project_id +} + +resource "google_service_networking_connection" "apigee_vpc_connection" { + network = google_compute_network.apigee_network.id + service = "servicenetworking.googleapis.com" + reserved_peering_ranges = [google_compute_global_address.apigee_range.name] + depends_on = [google_project_service.servicenetworking] +} + +resource "google_apigee_organization" "apigee_org" { + analytics_region = "us-central1" + project_id = google_project.project.project_id + authorized_network = google_compute_network.apigee_network.id + depends_on = [ + google_service_networking_connection.apigee_vpc_connection, + google_project_service.apigee, + ] +} + +resource "google_apigee_sharedflow" "test_apigee_sharedflow" { + name = "tf-test-apigee-sharedflow" + org_id = google_project.project.project_id + config_bundle = "./test-fixtures/apigee/apigee_sharedflow_bundle.zip" + depends_on = [google_apigee_organization.apigee_org] +} +`, context) +} + +func testAccCheckApigeeSharedFlowDestroyProducer(t *testing.T) func(s *terraform.State) error { + return func(s *terraform.State) error { + for name, rs := range s.RootModule().Resources { + if rs.Type != "google_apigee_shared_flow" { + continue + } + if strings.HasPrefix(name, "data.") { + continue + } + + config := GoogleProviderConfig(t) + + url, err := replaceVarsForTest(config, rs, "{{ApigeeBasePath}}organizations/{{org_id}}/sharedflows/{{name}}") + if err != nil { + return err + } + + billingProject := "" + + if config.BillingProject != "" { + billingProject = config.BillingProject + } + fmt.Printf("testAccCheckApigeeSharedFlowDestroyProducer, url %s", url) + _, err = SendRequest(config, "GET", billingProject, url, config.UserAgent, nil) + if err == nil { + return fmt.Errorf("ApigeeSharedFlow still exists at %s", url) + } + } + + return nil + } +} + +func testAccApigeeSharedFlow_apigeeSharedflowTestExampleUpdate(context map[string]interface{}) string { + return Nprintf(` +resource "google_project" "project" { + project_id = "tf-test%{random_suffix}" + name = "tf-test%{random_suffix}" + org_id = "%{org_id}" + billing_account = "%{billing_account}" +} + +resource "google_project_service" "apigee" { + project = google_project.project.project_id + service = "apigee.googleapis.com" +} + +resource "google_project_service" "servicenetworking" { + project = google_project.project.project_id + service = "servicenetworking.googleapis.com" + depends_on = [google_project_service.apigee] +} + +resource "google_project_service" "compute" { + project = google_project.project.project_id + service = "compute.googleapis.com" + depends_on = [google_project_service.servicenetworking] +} + +resource "google_compute_network" "apigee_network" { + name = "apigee-network" + project = google_project.project.project_id + depends_on = [google_project_service.compute] +} + +resource "google_compute_global_address" "apigee_range" { + name = "apigee-range" + purpose = "VPC_PEERING" + address_type = "INTERNAL" + prefix_length = 16 + network = google_compute_network.apigee_network.id + project = google_project.project.project_id +} + +resource "google_service_networking_connection" "apigee_vpc_connection" { + network = google_compute_network.apigee_network.id + service = "servicenetworking.googleapis.com" + reserved_peering_ranges = [google_compute_global_address.apigee_range.name] + depends_on = [google_project_service.servicenetworking] +} + +resource "google_apigee_organization" "apigee_org" { + analytics_region = "us-central1" + project_id = google_project.project.project_id + authorized_network = google_compute_network.apigee_network.id + depends_on = [ + google_service_networking_connection.apigee_vpc_connection, + google_project_service.apigee, + ] +} + +resource "google_apigee_sharedflow" "test_apigee_sharedflow" { + name = "tf-test-apigee-sharedflow" + org_id = google_project.project.project_id + config_bundle = "./test-fixtures/apigee/apigee_sharedflow_bundle2.zip" + depends_on = [google_apigee_organization.apigee_org] +} +`, context) +} diff --git a/mmv1/third_party/terraform/utils/provider.go.erb b/mmv1/third_party/terraform/utils/provider.go.erb index bd155a519289..8c6765833f41 100644 --- a/mmv1/third_party/terraform/utils/provider.go.erb +++ b/mmv1/third_party/terraform/utils/provider.go.erb @@ -409,6 +409,9 @@ end # products.each do map[string]*schema.Resource{ // ####### START handwritten resources ########### "google_app_engine_application": ResourceAppEngineApplication(), + "google_apigee_sharedflow": ResourceApigeeSharedFlow(), + "google_apigee_sharedflow_deployment": ResourceApigeeSharedFlowDeployment(), + "google_apigee_flowhook": ResourceApigeeFlowhook(), "google_bigquery_table": ResourceBigQueryTable(), "google_bigtable_gc_policy": ResourceBigtableGCPolicy(), "google_bigtable_instance": ResourceBigtableInstance(), @@ -698,4 +701,4 @@ func validateCredentials(v interface{}, k string) (warnings []string, errors []e } return -} +} \ No newline at end of file diff --git a/mmv1/third_party/terraform/utils/test-fixtures/apigee/apigee_sharedflow_bundle.zip b/mmv1/third_party/terraform/utils/test-fixtures/apigee/apigee_sharedflow_bundle.zip new file mode 100644 index 000000000000..52f1c94091bb Binary files /dev/null and b/mmv1/third_party/terraform/utils/test-fixtures/apigee/apigee_sharedflow_bundle.zip differ diff --git a/mmv1/third_party/terraform/utils/test-fixtures/apigee/apigee_sharedflow_bundle2.zip b/mmv1/third_party/terraform/utils/test-fixtures/apigee/apigee_sharedflow_bundle2.zip new file mode 100644 index 000000000000..6e15c1114033 Binary files /dev/null and b/mmv1/third_party/terraform/utils/test-fixtures/apigee/apigee_sharedflow_bundle2.zip differ diff --git a/mmv1/third_party/terraform/website/docs/r/apigee_flowhook.html.markdown b/mmv1/third_party/terraform/website/docs/r/apigee_flowhook.html.markdown new file mode 100644 index 000000000000..380a65ed82fe --- /dev/null +++ b/mmv1/third_party/terraform/website/docs/r/apigee_flowhook.html.markdown @@ -0,0 +1,71 @@ +--- +subcategory: "Apigee" +description: |- + Represents a sharedflow attachment to a flowhook point. +--- + +# google\_apigee\_flowhook + +Represents a sharedflow attachment to a flowhook point. + + +To get more information about Flowhook, see: + +* [API documentation](https://cloud.google.com/apigee/docs/reference/apis/apigee/rest/v1/organizations.environments.flowhooks#FlowHook) +* How-to Guides + * [organizations.environments.flowhooks](https://cloud.google.com/apigee/docs/reference/apis/apigee/rest/v1/organizations.environments.flowhooks#FlowHook) + +## Argument Reference + +The following arguments are supported: + + +* `org_id` - + (Required) + The Apigee Organization associated with the environment + +* `environment` - + (Required) + The resource ID of the environment. + +* `flow_hook_point` - + (Required) + Where in the API call flow the flow hook is invoked. Must be one of PreProxyFlowHook, PostProxyFlowHook, PreTargetFlowHook, or PostTargetFlowHook. + +* `description` - + (Optional) + Description of the flow hook. + +* `sharedflow` - + (Required) + Id of the Sharedflow attaching to a flowhook point. + +* `continue_on_error` - + (Optional) + Flag that specifies whether execution should continue if the flow hook throws an exception. Set to true to continue execution. Set to false to stop execution if the flow hook throws an exception. Defaults to true. + + +## Attributes Reference + +In addition to the arguments listed above, the following computed attributes are exported: + +* `id` - an identifier for the resource with format `organizations/{{org_id}}/environments/{{environment}}/flowhooks/{{flow_hook_point}}` + + +## Timeouts + +This resource provides the following +[Timeouts](https://developer.hashicorp.com/terraform/plugin/sdkv2/resources/retries-and-customizable-timeouts) configuration options: + +- `create` - Default is 20 minutes. +- `delete` - Default is 20 minutes. + +## Import + + +Flowhook can be imported using any of these accepted formats: + +``` +$ terraform import google_apigee_flowhook.default organizations/{{org_id}}/environments/{{environment}}/flowhooks/{{flow_hook_point}} +$ terraform import google_apigee_flowhook.default {{org_id}}/{{environment}}/{{flow_hook_point}} +``` diff --git a/mmv1/third_party/terraform/website/docs/r/apigee_sharedflow.html.markdown b/mmv1/third_party/terraform/website/docs/r/apigee_sharedflow.html.markdown new file mode 100644 index 000000000000..9da48d0f89ed --- /dev/null +++ b/mmv1/third_party/terraform/website/docs/r/apigee_sharedflow.html.markdown @@ -0,0 +1,105 @@ +--- +# ---------------------------------------------------------------------------- +# +# *** AUTO GENERATED CODE *** Type: MMv1 *** +# +# ---------------------------------------------------------------------------- +# +# 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: "Apigee" +page_title: "Google: google_apigee_shared_flow" +description: |- + You can combine policies and resources into a shared flow that you can consume from multiple API proxies, and even from other shared flows. +--- + +# google\_apigee\_shared\_flow + +You can combine policies and resources into a shared flow that you can consume from multiple API proxies, and even from other shared flows. Although it's like a proxy, a shared flow has no endpoint. It can be used only from an API proxy or shared flow that's in the same organization as the shared flow itself. + + +To get more information about SharedFlow, see: + +* [API documentation](https://cloud.google.com/apigee/docs/reference/apis/apigee/rest/v1/organizations.sharedflows) +* How-to Guides + * [Sharedflows](https://cloud.google.com/apigee/docs/resources) + + +## Argument Reference + +The following arguments are supported: + + +* `name` - + (Required) + The ID of the shared flow. + +* `org_id` - + (Required) + The Apigee Organization associated with the Apigee instance, + in the format `organizations/{{org_name}}`. + + +- - - + + + +## Attributes Reference + +In addition to the arguments listed above, the following computed attributes are exported: + +* `id` - an identifier for the resource with format `organizations/{{org_id}}/sharedflows/{{name}}` + +* `meta_data` - + Metadata describing the shared flow. + Structure is [documented below](#nested_meta_data). + +* `revision` - + A list of revisions of this shared flow. + +* `latest_revision_id` - + The id of the most recently created revision for this shared flow. + +* `md5hash` - + (Computed) Base 64 MD5 hash of the uploaded data. It is speculative as remote does not return hash of the bundle. Remote changes are detected using returned last_modified timestamp. + +* `detect_md5hash` - + (Optional) Detect changes to local config bundle file or changes made outside of Terraform. MD5 hash of the data, encoded using base64. Hash is automatically computed without need for user input. + + +The `meta_data` block contains: + +* `created_at` - + (Optional) + Time at which the API proxy was created, in milliseconds since epoch. + +* `last_modified_at` - + (Optional) + Time at which the API proxy was most recently modified, in milliseconds since epoch. + +* `sub_type` - + (Optional) + The type of entity described + +## Timeouts + +This resource provides the following +[Timeouts](/docs/configuration/resources.html#timeouts) configuration options: + +- `create` - Default is 20 minutes. +- `delete` - Default is 20 minutes. + +## Import + + +SharedFlow can be imported using any of these accepted formats: + +``` +$ terraform import google_apigee_shared_flow.default {{org_id}}/sharedflows/{{name}} +$ terraform import google_apigee_sharedflow.default {{org_id}}/{{name}} +``` diff --git a/mmv1/third_party/terraform/website/docs/r/apigee_sharedflow_deployment.html.markdown b/mmv1/third_party/terraform/website/docs/r/apigee_sharedflow_deployment.html.markdown new file mode 100644 index 000000000000..db00fac58d83 --- /dev/null +++ b/mmv1/third_party/terraform/website/docs/r/apigee_sharedflow_deployment.html.markdown @@ -0,0 +1,85 @@ +--- +# ---------------------------------------------------------------------------- +# +# *** AUTO GENERATED CODE *** Type: MMv1 *** +# +# ---------------------------------------------------------------------------- +# +# 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: "Apigee" +description: |- + Deploys a revision of a sharedflow. +--- + +# google\_apigee\_sharedflow\_deployment + +Deploys a revision of a sharedflow. + + +To get more information about SharedflowDeployment, see: + +* [API documentation](https://cloud.google.com/apigee/docs/reference/apis/apigee/rest/v1/organizations.environments.sharedflows.revisions.deployments) +* How-to Guides + * [sharedflows.revisions.deployments](https://cloud.google.com/apigee/docs/reference/apis/apigee/rest/v1/organizations.environments.sharedflows.revisions.deployments) + +## Argument Reference + +The following arguments are supported: + + +* `org_id` - + (Required) + The Apigee Organization associated with the Sharedflow + +* `environment` - + (Required) + The resource ID of the environment. + +* `sharedflow_id` - + (Required) + Id of the Sharedflow to be deployed. + +* `revision` - + (Required) + Revision of the Sharedflow to be deployed. + + +- - - + + +* `service_account` - + (Optional) + The service account represents the identity of the deployed proxy, and determines what permissions it has. The format must be {ACCOUNT_ID}@{PROJECT}.iam.gserviceaccount.com. + + +## Attributes Reference + +In addition to the arguments listed above, the following computed attributes are exported: + +* `id` - an identifier for the resource with format `organizations/{{org_id}}/environments/{{environment}}/sharedflows/{{sharedflow_id}}/revisions/{{revision}}/deployments` + + +## Timeouts + +This resource provides the following +[Timeouts](https://developer.hashicorp.com/terraform/plugin/sdkv2/resources/retries-and-customizable-timeouts) configuration options: + +- `create` - Default is 20 minutes. +- `update` - Default is 20 minutes. +- `delete` - Default is 20 minutes. + +## Import + + +SharedflowDeployment can be imported using any of these accepted formats: + +``` +$ terraform import google_apigee_sharedflow_deployment.default organizations/{{org_id}}/environments/{{environment}}/sharedflows/{{sharedflow_id}}/revisions/{{revision}}/deployments/{{name}} +$ terraform import google_apigee_sharedflow_deployment.default {{org_id}}/{{environment}}/{{sharedflow_id}}/{{revision}}/{{name}} +```