1package tfe 2 3import ( 4 "context" 5 "errors" 6 "fmt" 7 "io" 8 "net/url" 9 "time" 10) 11 12// Compile-time proof of interface implementation. 13var _ Applies = (*applies)(nil) 14 15// Applies describes all the apply related methods that the Terraform 16// Enterprise API supports. 17// 18// TFE API docs: https://www.terraform.io/docs/enterprise/api/apply.html 19type Applies interface { 20 // Read an apply by its ID. 21 Read(ctx context.Context, applyID string) (*Apply, error) 22 23 // Logs retrieves the logs of an apply. 24 Logs(ctx context.Context, applyID string) (io.Reader, error) 25} 26 27// applies implements Applys. 28type applies struct { 29 client *Client 30} 31 32// ApplyStatus represents an apply state. 33type ApplyStatus string 34 35//List all available apply statuses. 36const ( 37 ApplyCanceled ApplyStatus = "canceled" 38 ApplyCreated ApplyStatus = "created" 39 ApplyErrored ApplyStatus = "errored" 40 ApplyFinished ApplyStatus = "finished" 41 ApplyMFAWaiting ApplyStatus = "mfa_waiting" 42 ApplyPending ApplyStatus = "pending" 43 ApplyQueued ApplyStatus = "queued" 44 ApplyRunning ApplyStatus = "running" 45 ApplyUnreachable ApplyStatus = "unreachable" 46) 47 48// Apply represents a Terraform Enterprise apply. 49type Apply struct { 50 ID string `jsonapi:"primary,applies"` 51 LogReadURL string `jsonapi:"attr,log-read-url"` 52 ResourceAdditions int `jsonapi:"attr,resource-additions"` 53 ResourceChanges int `jsonapi:"attr,resource-changes"` 54 ResourceDestructions int `jsonapi:"attr,resource-destructions"` 55 Status ApplyStatus `jsonapi:"attr,status"` 56 StatusTimestamps *ApplyStatusTimestamps `jsonapi:"attr,status-timestamps"` 57} 58 59// ApplyStatusTimestamps holds the timestamps for individual apply statuses. 60type ApplyStatusTimestamps struct { 61 CanceledAt time.Time `json:"canceled-at"` 62 ErroredAt time.Time `json:"errored-at"` 63 FinishedAt time.Time `json:"finished-at"` 64 ForceCanceledAt time.Time `json:"force-canceled-at"` 65 QueuedAt time.Time `json:"queued-at"` 66 StartedAt time.Time `json:"started-at"` 67} 68 69// Read an apply by its ID. 70func (s *applies) Read(ctx context.Context, applyID string) (*Apply, error) { 71 if !validStringID(&applyID) { 72 return nil, errors.New("invalid value for apply ID") 73 } 74 75 u := fmt.Sprintf("applies/%s", url.QueryEscape(applyID)) 76 req, err := s.client.newRequest("GET", u, nil) 77 if err != nil { 78 return nil, err 79 } 80 81 a := &Apply{} 82 err = s.client.do(ctx, req, a) 83 if err != nil { 84 return nil, err 85 } 86 87 return a, nil 88} 89 90// Logs retrieves the logs of an apply. 91func (s *applies) Logs(ctx context.Context, applyID string) (io.Reader, error) { 92 if !validStringID(&applyID) { 93 return nil, errors.New("invalid value for apply ID") 94 } 95 96 // Get the apply to make sure it exists. 97 a, err := s.Read(ctx, applyID) 98 if err != nil { 99 return nil, err 100 } 101 102 // Return an error if the log URL is empty. 103 if a.LogReadURL == "" { 104 return nil, fmt.Errorf("apply %s does not have a log URL", applyID) 105 } 106 107 u, err := url.Parse(a.LogReadURL) 108 if err != nil { 109 return nil, fmt.Errorf("invalid log URL: %v", err) 110 } 111 112 done := func() (bool, error) { 113 a, err := s.Read(ctx, a.ID) 114 if err != nil { 115 return false, err 116 } 117 118 switch a.Status { 119 case ApplyCanceled, ApplyErrored, ApplyFinished, ApplyUnreachable: 120 return true, nil 121 default: 122 return false, nil 123 } 124 } 125 126 return &LogReader{ 127 client: s.client, 128 ctx: ctx, 129 done: done, 130 logURL: u, 131 }, nil 132} 133