-
Notifications
You must be signed in to change notification settings - Fork 102
/
plan.go
165 lines (138 loc) · 4.33 KB
/
plan.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package tfe
import (
"bytes"
"context"
"fmt"
"io"
"net/url"
"time"
)
// Compile-time proof of interface implementation.
var _ Plans = (*plans)(nil)
// Plans describes all the plan related methods that the Terraform Enterprise
// API supports.
//
// TFE API docs: https://developer.hashicorp.com/terraform/cloud-docs/api-docs/plans
type Plans interface {
// Read a plan by its ID.
Read(ctx context.Context, planID string) (*Plan, error)
// Logs retrieves the logs of a plan.
Logs(ctx context.Context, planID string) (io.Reader, error)
// Retrieve the JSON execution plan
ReadJSONOutput(ctx context.Context, planID string) ([]byte, error)
}
// plans implements Plans.
type plans struct {
client *Client
}
// PlanStatus represents a plan state.
type PlanStatus string
// List all available plan statuses.
const (
PlanCanceled PlanStatus = "canceled"
PlanCreated PlanStatus = "created"
PlanErrored PlanStatus = "errored"
PlanFinished PlanStatus = "finished"
PlanMFAWaiting PlanStatus = "mfa_waiting"
PlanPending PlanStatus = "pending"
PlanQueued PlanStatus = "queued"
PlanRunning PlanStatus = "running"
PlanUnreachable PlanStatus = "unreachable"
)
// Plan represents a Terraform Enterprise plan.
type Plan struct {
ID string `jsonapi:"primary,plans"`
HasChanges bool `jsonapi:"attr,has-changes"`
GeneratedConfiguration bool `jsonapi:"attr,generated-configuration"`
LogReadURL string `jsonapi:"attr,log-read-url"`
ResourceAdditions int `jsonapi:"attr,resource-additions"`
ResourceChanges int `jsonapi:"attr,resource-changes"`
ResourceDestructions int `jsonapi:"attr,resource-destructions"`
ResourceImports int `jsonapi:"attr,resource-imports"`
Status PlanStatus `jsonapi:"attr,status"`
StatusTimestamps *PlanStatusTimestamps `jsonapi:"attr,status-timestamps"`
// Relations
Exports []*PlanExport `jsonapi:"relation,exports"`
}
// PlanStatusTimestamps holds the timestamps for individual plan statuses.
type PlanStatusTimestamps struct {
CanceledAt time.Time `jsonapi:"attr,canceled-at,rfc3339"`
ErroredAt time.Time `jsonapi:"attr,errored-at,rfc3339"`
FinishedAt time.Time `jsonapi:"attr,finished-at,rfc3339"`
ForceCanceledAt time.Time `jsonapi:"attr,force-canceled-at,rfc3339"`
QueuedAt time.Time `jsonapi:"attr,queued-at,rfc3339"`
StartedAt time.Time `jsonapi:"attr,started-at,rfc3339"`
}
// Read a plan by its ID.
func (s *plans) Read(ctx context.Context, planID string) (*Plan, error) {
if !validStringID(&planID) {
return nil, ErrInvalidPlanID
}
u := fmt.Sprintf("plans/%s", url.PathEscape(planID))
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, err
}
p := &Plan{}
err = req.Do(ctx, p)
if err != nil {
return nil, err
}
return p, nil
}
// Logs retrieves the logs of a plan.
func (s *plans) Logs(ctx context.Context, planID string) (io.Reader, error) {
if !validStringID(&planID) {
return nil, ErrInvalidPlanID
}
// Get the plan to make sure it exists.
p, err := s.Read(ctx, planID)
if err != nil {
return nil, err
}
// Return an error if the log URL is empty.
if p.LogReadURL == "" {
return nil, fmt.Errorf("plan %s does not have a log URL", planID)
}
u, err := url.Parse(p.LogReadURL)
if err != nil {
return nil, fmt.Errorf("invalid log URL: %w", err)
}
done := func() (bool, error) {
p, err := s.Read(ctx, p.ID)
if err != nil {
return false, err
}
switch p.Status {
case PlanCanceled, PlanErrored, PlanFinished, PlanUnreachable:
return true, nil
default:
return false, nil
}
}
return &LogReader{
client: s.client,
ctx: ctx,
done: done,
logURL: u,
}, nil
}
// Retrieve the JSON execution plan
func (s *plans) ReadJSONOutput(ctx context.Context, planID string) ([]byte, error) {
if !validStringID(&planID) {
return nil, ErrInvalidPlanID
}
u := fmt.Sprintf("plans/%s/json-output", url.PathEscape(planID))
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, err
}
var buf bytes.Buffer
err = req.Do(ctx, &buf)
if err != nil {
return nil, err
}
return buf.Bytes(), nil
}