| // 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") |
| } |
| } |
| } |