|  | // Copyright (c) HashiCorp, Inc. | 
|  | // SPDX-License-Identifier: MPL-2.0 | 
|  | package compute | 
|  |  | 
|  | import ( | 
|  | "bytes" | 
|  | "context" | 
|  | "encoding/json" | 
|  | "errors" | 
|  | "fmt" | 
|  | "io" | 
|  | "log" | 
|  | "time" | 
|  |  | 
|  | "github.com/hashicorp/terraform-provider-google-beta/google-beta/tpgresource" | 
|  | transport_tpg "github.com/hashicorp/terraform-provider-google-beta/google-beta/transport" | 
|  |  | 
|  | compute "google.golang.org/api/compute/v0.beta" | 
|  | ) | 
|  |  | 
|  | type ComputeOperationWaiter struct { | 
|  | Service *compute.Service | 
|  | Op      *compute.Operation | 
|  | Context context.Context | 
|  | Project string | 
|  | Parent  string | 
|  | } | 
|  |  | 
|  | func (w *ComputeOperationWaiter) State() string { | 
|  | if w == nil || w.Op == nil { | 
|  | return "<nil>" | 
|  | } | 
|  |  | 
|  | return w.Op.Status | 
|  | } | 
|  |  | 
|  | func (w *ComputeOperationWaiter) Error() error { | 
|  | if w != nil && w.Op != nil && w.Op.Error != nil { | 
|  | return ComputeOperationError(*w.Op.Error) | 
|  | } | 
|  | return nil | 
|  | } | 
|  |  | 
|  | func (w *ComputeOperationWaiter) IsRetryable(err error) bool { | 
|  | if oe, ok := err.(ComputeOperationError); ok { | 
|  | for _, e := range oe.Errors { | 
|  | if e.Code == "RESOURCE_NOT_READY" { | 
|  | return true | 
|  | } | 
|  | } | 
|  | } | 
|  | return false | 
|  | } | 
|  |  | 
|  | func (w *ComputeOperationWaiter) SetOp(op interface{}) error { | 
|  | var ok bool | 
|  | w.Op, ok = op.(*compute.Operation) | 
|  | if !ok { | 
|  | return fmt.Errorf("Unable to set operation. Bad type!") | 
|  | } | 
|  | return nil | 
|  | } | 
|  |  | 
|  | func (w *ComputeOperationWaiter) QueryOp() (interface{}, error) { | 
|  | if w == nil || w.Op == nil { | 
|  | return nil, fmt.Errorf("Cannot query operation, it's unset or nil.") | 
|  | } | 
|  | if w.Context != nil { | 
|  | select { | 
|  | case <-w.Context.Done(): | 
|  | log.Println("[WARN] request has been cancelled early") | 
|  | return w.Op, errors.New("unable to finish polling, context has been cancelled") | 
|  | default: | 
|  | // default must be here to keep the previous case from blocking | 
|  | } | 
|  | } | 
|  | if w.Op.Zone != "" { | 
|  | zone := tpgresource.GetResourceNameFromSelfLink(w.Op.Zone) | 
|  | return w.Service.ZoneOperations.Get(w.Project, zone, w.Op.Name).Do() | 
|  | } else if w.Op.Region != "" { | 
|  | region := tpgresource.GetResourceNameFromSelfLink(w.Op.Region) | 
|  | return w.Service.RegionOperations.Get(w.Project, region, w.Op.Name).Do() | 
|  | } else if w.Parent != "" { | 
|  | return w.Service.GlobalOrganizationOperations.Get(w.Op.Name).ParentId(w.Parent).Do() | 
|  | } | 
|  | return w.Service.GlobalOperations.Get(w.Project, w.Op.Name).Do() | 
|  | } | 
|  |  | 
|  | func (w *ComputeOperationWaiter) OpName() string { | 
|  | if w == nil || w.Op == nil { | 
|  | return "<nil> Compute Op" | 
|  | } | 
|  |  | 
|  | return w.Op.Name | 
|  | } | 
|  |  | 
|  | func (w *ComputeOperationWaiter) PendingStates() []string { | 
|  | return []string{"PENDING", "RUNNING"} | 
|  | } | 
|  |  | 
|  | func (w *ComputeOperationWaiter) TargetStates() []string { | 
|  | return []string{"DONE"} | 
|  | } | 
|  |  | 
|  | func ComputeOperationWaitTime(config *transport_tpg.Config, res interface{}, project, activity, userAgent string, timeout time.Duration) error { | 
|  | op := &compute.Operation{} | 
|  | err := tpgresource.Convert(res, op) | 
|  | if err != nil { | 
|  | return err | 
|  | } | 
|  |  | 
|  | w := &ComputeOperationWaiter{ | 
|  | Service: config.NewComputeClient(userAgent), | 
|  | Context: config.Context, | 
|  | Op:      op, | 
|  | Project: project, | 
|  | } | 
|  |  | 
|  | if err := w.SetOp(op); err != nil { | 
|  | return err | 
|  | } | 
|  | return tpgresource.OperationWait(w, activity, timeout, config.PollInterval) | 
|  | } | 
|  |  | 
|  | func ComputeOrgOperationWaitTimeWithResponse(config *transport_tpg.Config, res interface{}, response *map[string]interface{}, parent, activity, userAgent string, timeout time.Duration) error { | 
|  | op := &compute.Operation{} | 
|  | err := tpgresource.Convert(res, op) | 
|  | if err != nil { | 
|  | return err | 
|  | } | 
|  |  | 
|  | w := &ComputeOperationWaiter{ | 
|  | Service: config.NewComputeClient(userAgent), | 
|  | Op:      op, | 
|  | Parent:  parent, | 
|  | } | 
|  |  | 
|  | if err := w.SetOp(op); err != nil { | 
|  | return err | 
|  | } | 
|  | if err := tpgresource.OperationWait(w, activity, timeout, config.PollInterval); err != nil { | 
|  | return err | 
|  | } | 
|  | e, err := json.Marshal(w.Op) | 
|  | if err != nil { | 
|  | return err | 
|  | } | 
|  | return json.Unmarshal(e, response) | 
|  | } | 
|  |  | 
|  | // ComputeOperationError wraps compute.OperationError and implements the | 
|  | // error interface so it can be returned. | 
|  | type ComputeOperationError compute.OperationError | 
|  |  | 
|  | func (e ComputeOperationError) Error() string { | 
|  | buf := bytes.NewBuffer(nil) | 
|  | for _, err := range e.Errors { | 
|  | writeOperationError(buf, err) | 
|  | } | 
|  |  | 
|  | return buf.String() | 
|  | } | 
|  |  | 
|  | const errMsgSep = "\n\n" | 
|  |  | 
|  | func writeOperationError(w io.StringWriter, opError *compute.OperationErrorErrors) { | 
|  | w.WriteString(opError.Message + "\n") | 
|  |  | 
|  | var lm *compute.LocalizedMessage | 
|  | var link *compute.HelpLink | 
|  |  | 
|  | for _, ed := range opError.ErrorDetails { | 
|  | if opError.Code == "QUOTA_EXCEEDED" && ed.QuotaInfo != nil { | 
|  | w.WriteString("\tmetric name = " + ed.QuotaInfo.MetricName + "\n") | 
|  | w.WriteString("\tlimit name = " + ed.QuotaInfo.LimitName + "\n") | 
|  | if ed.QuotaInfo.Limit != 0 { | 
|  | w.WriteString("\tlimit = " + fmt.Sprint(ed.QuotaInfo.Limit) + "\n") | 
|  | } | 
|  | if ed.QuotaInfo.FutureLimit != 0 { | 
|  | w.WriteString("\tfuture limit = " + fmt.Sprint(ed.QuotaInfo.FutureLimit) + "\n") | 
|  | w.WriteString("\trollout status = in progress\n") | 
|  | } | 
|  | if ed.QuotaInfo.Dimensions != nil { | 
|  | w.WriteString("\tdimensions = " + fmt.Sprint(ed.QuotaInfo.Dimensions) + "\n") | 
|  | } | 
|  | break | 
|  | } | 
|  | if lm == nil && ed.LocalizedMessage != nil { | 
|  | lm = ed.LocalizedMessage | 
|  | } | 
|  |  | 
|  | if link == nil && ed.Help != nil && len(ed.Help.Links) > 0 { | 
|  | link = ed.Help.Links[0] | 
|  | } | 
|  |  | 
|  | if lm != nil && link != nil { | 
|  | break | 
|  | } | 
|  | } | 
|  |  | 
|  | if lm != nil && lm.Message != "" { | 
|  | w.WriteString(errMsgSep) | 
|  | w.WriteString(lm.Message + "\n") | 
|  | } | 
|  |  | 
|  | if link != nil { | 
|  | w.WriteString(errMsgSep) | 
|  |  | 
|  | if link.Description != "" { | 
|  | w.WriteString(link.Description + "\n") | 
|  | } | 
|  |  | 
|  | if link.Url != "" { | 
|  | w.WriteString(link.Url + "\n") | 
|  | } | 
|  | } | 
|  | } |