Skip to content

Commit 2481d95

Browse files
authored
fix: improve pipeline error handling (#14)
This adjusts the pipelines API to make use of the `ResponseError` implementation added in #13. Furthermore it fixes a bug where non-existent pipelines produce errors although terraform should just recreate them.
1 parent c20e8f8 commit 2481d95

File tree

3 files changed

+81
-106
lines changed

3 files changed

+81
-106
lines changed

spinnaker/api/pipeline.go

Lines changed: 13 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
package api
22

33
import (
4-
"fmt"
5-
"log"
64
"net/http"
75

6+
"github.com/Bonial-International-GmbH/terraform-provider-spinnaker/spinnaker/api/errors"
87
"github.com/mitchellh/mapstructure"
98
gate "github.com/spinnaker/spin/cmd/gateclient"
109
)
@@ -15,62 +14,38 @@ func CreatePipeline(client *gate.GatewayClient, pipeline interface{}) error {
1514

1615
return nil, resp, err
1716
})
18-
if err != nil {
19-
return err
20-
}
21-
22-
if resp.StatusCode != http.StatusOK {
23-
return fmt.Errorf("Encountered an error saving pipeline, status code: %d\n", resp.StatusCode)
17+
if err != nil || (resp.StatusCode != http.StatusOK) {
18+
return errors.NewResponseError(resp, err)
2419
}
2520

2621
return nil
2722
}
2823

2924
func GetPipeline(client *gate.GatewayClient, applicationName, pipelineName string, dest interface{}) (map[string]interface{}, error) {
30-
jsonMap, resp, err := retry(func() (map[string]interface{}, *http.Response, error) {
25+
payload, resp, err := retry(func() (map[string]interface{}, *http.Response, error) {
3126
return client.ApplicationControllerApi.GetPipelineConfigUsingGET(
3227
client.Context,
3328
applicationName,
3429
pipelineName,
3530
)
3631
})
37-
if err != nil {
38-
if resp != nil && resp.StatusCode == http.StatusNotFound {
39-
return jsonMap, fmt.Errorf("%s", ErrCodeNoSuchEntityException)
40-
}
41-
return jsonMap, fmt.Errorf("Encountered an error getting pipeline %s. Error: %s\n",
42-
pipelineName,
43-
err.Error())
44-
}
45-
46-
if resp.StatusCode != http.StatusOK {
47-
return jsonMap, fmt.Errorf("Encountered an error getting pipeline in pipeline %s with name %s, status code: %d\n",
48-
applicationName,
49-
pipelineName,
50-
resp.StatusCode)
32+
if err != nil || resp.StatusCode != http.StatusOK {
33+
return nil, errors.NewResponseError(resp, err)
5134
}
5235

53-
if jsonMap == nil {
54-
return jsonMap, fmt.Errorf(ErrCodeNoSuchEntityException)
36+
if err := mapstructure.Decode(payload, dest); err != nil {
37+
return nil, err
5538
}
5639

57-
if err := mapstructure.Decode(jsonMap, dest); err != nil {
58-
return jsonMap, err
59-
}
60-
61-
return jsonMap, nil
40+
return payload, nil
6241
}
6342

6443
func UpdatePipeline(client *gate.GatewayClient, pipelineID string, pipeline interface{}) error {
6544
_, resp, err := retry(func() (map[string]interface{}, *http.Response, error) {
6645
return client.PipelineControllerApi.UpdatePipelineUsingPUT(client.Context, pipelineID, pipeline)
6746
})
68-
if err != nil {
69-
return err
70-
}
71-
72-
if resp.StatusCode != http.StatusOK {
73-
return fmt.Errorf("Encountered an error saving pipeline, status code: %d\n", resp.StatusCode)
47+
if err != nil || resp.StatusCode != http.StatusOK {
48+
return errors.NewResponseError(resp, err)
7449
}
7550

7651
return nil
@@ -85,14 +60,10 @@ func DeletePipeline(client *gate.GatewayClient, applicationName, pipelineName st
8560
)
8661
return nil, resp, err
8762
})
88-
if err != nil {
89-
return err
63+
if err != nil || resp.StatusCode != http.StatusOK {
64+
return errors.NewResponseError(resp, err)
9065
}
9166

92-
if resp.StatusCode != http.StatusOK {
93-
return fmt.Errorf("Encountered an error deleting pipeline, status code: %d\n", resp.StatusCode)
94-
}
95-
log.Printf("deleted pipeline %v for application %v", pipelineName, applicationName)
9667
return nil
9768
}
9869

spinnaker/resource_pipeline.go

Lines changed: 64 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import (
66
"strings"
77

88
"github.com/Bonial-International-GmbH/terraform-provider-spinnaker/spinnaker/api"
9-
"github.com/Bonial-International-GmbH/terraform-provider-spinnaker/spinnaker/api/errors"
9+
apierrors "github.com/Bonial-International-GmbH/terraform-provider-spinnaker/spinnaker/api/errors"
1010
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
1111
)
1212

@@ -57,11 +57,9 @@ func resourcePipelineCreate(data *schema.ResourceData, meta interface{}) error {
5757

5858
applicationName := data.Get("application").(string)
5959
pipelineName := data.Get("name").(string)
60-
rawPipeline := []byte(data.Get("pipeline").(string))
60+
rawPipeline := data.Get("pipeline").(string)
6161

62-
var pipeline map[string]interface{}
63-
64-
err = json.Unmarshal(rawPipeline, &pipeline)
62+
pipeline, err := parsePipeline(rawPipeline)
6563
if err != nil {
6664
return err
6765
}
@@ -71,12 +69,13 @@ func resourcePipelineCreate(data *schema.ResourceData, meta interface{}) error {
7169
delete(pipeline, "id")
7270

7371
err = api.CreatePipeline(client, pipeline)
74-
if errors.IsPipelineAlreadyExists(err) {
72+
if apierrors.IsPipelineAlreadyExists(err) {
7573
err = api.RecreatePipeline(client, applicationName, pipelineName, pipeline)
7674
}
7775

7876
if err != nil {
79-
return err
77+
return fmt.Errorf("failed to create pipeline %q for application %q: %w",
78+
pipelineName, applicationName, err)
8079
}
8180

8281
return resourcePipelineRead(data, meta)
@@ -94,24 +93,23 @@ func resourcePipelineRead(data *schema.ResourceData, meta interface{}) error {
9493
pipelineName := data.Get("name").(string)
9594

9695
var p pipelineRead
97-
jsonMap, err := api.GetPipeline(client, applicationName, pipelineName, &p)
98-
if err != nil {
99-
return err
96+
97+
pipeline, err := api.GetPipeline(client, applicationName, pipelineName, &p)
98+
if apierrors.IsNotFound(err) {
99+
data.SetId("")
100+
return nil
101+
} else if err != nil {
102+
return fmt.Errorf("failed to fetch pipeline %q for application %q: %w",
103+
pipelineName, applicationName, err)
100104
}
101105

102-
pipeline, err := editAndEncodePipeline(jsonMap)
106+
encodedPipeline, err := editAndEncodePipeline(pipeline)
103107
if err != nil {
104108
return err
105109
}
106-
err = data.Set("pipeline", pipeline)
107-
if err != nil {
108-
return fmt.Errorf("Could not set pipeline for pipeline %s: %s", pipelineName, err)
109-
}
110110

111-
err = data.Set("pipeline_id", p.ID)
112-
if err != nil {
113-
return fmt.Errorf("Could not set pipeline_id for pipeline %s: %s", pipelineName, err)
114-
}
111+
data.Set("pipeline", encodedPipeline)
112+
data.Set("pipeline_id", p.ID)
115113
data.SetId(p.ID)
116114

117115
return nil
@@ -127,33 +125,28 @@ func resourcePipelineUpdate(data *schema.ResourceData, meta interface{}) error {
127125

128126
applicationName := data.Get("application").(string)
129127
pipelineName := data.Get("name").(string)
130-
rawPipeline := []byte(data.Get("pipeline").(string))
131-
132-
pipelineID, ok := data.GetOk("pipeline_id")
133-
if !ok {
134-
return fmt.Errorf("No pipeline_id found to pipeline in %s with name %s", applicationName, pipelineName)
135-
}
128+
pipelineID := data.Get("pipeline_id").(string)
129+
rawPipeline := data.Get("pipeline").(string)
136130

137-
var pipeline map[string]interface{}
138-
139-
err = json.Unmarshal(rawPipeline, &pipeline)
131+
pipeline, err := parsePipeline(rawPipeline)
140132
if err != nil {
141-
return fmt.Errorf("could not unmarshal pipeline")
133+
return err
142134
}
143135

144136
pipeline["application"] = applicationName
145137
pipeline["name"] = pipelineName
146-
pipeline["id"] = pipelineID.(string)
138+
pipeline["id"] = pipelineID
147139

148-
err = api.UpdatePipeline(client, pipelineID.(string), pipeline)
149-
if errors.IsPipelineAlreadyExists(err) {
140+
err = api.UpdatePipeline(client, pipelineID, pipeline)
141+
if apierrors.IsPipelineAlreadyExists(err) {
150142
// Although it seems odd, this error can happen here due to the hideous
151143
// spinnaker API. We handle it by just recreating the pipeline.
152144
err = api.RecreatePipeline(client, applicationName, pipelineName, pipeline)
153145
}
154146

155147
if err != nil {
156-
return err
148+
return fmt.Errorf("failed to update pipeline %q for application %q: %w",
149+
pipelineName, applicationName, err)
157150
}
158151

159152
return resourcePipelineRead(data, meta)
@@ -170,8 +163,10 @@ func resourcePipelineDelete(data *schema.ResourceData, meta interface{}) error {
170163
applicationName := data.Get("application").(string)
171164
pipelineName := data.Get("name").(string)
172165

173-
if err := api.DeletePipeline(client, applicationName, pipelineName); err != nil {
174-
return err
166+
err = api.DeletePipeline(client, applicationName, pipelineName)
167+
if err != nil && !apierrors.IsNotFound(err) {
168+
return fmt.Errorf("failed to delete pipeline %q for application %q: %w",
169+
pipelineName, applicationName, err)
175170
}
176171

177172
return nil
@@ -189,12 +184,16 @@ func resourcePipelineExists(data *schema.ResourceData, meta interface{}) (bool,
189184
pipelineName := data.Get("name").(string)
190185

191186
var p pipelineRead
192-
if _, err := api.GetPipeline(client, applicationName, pipelineName, &p); err != nil && !strings.Contains(err.Error(), "EOF") {
193-
return false, err
194-
}
195187

196-
if p.Name == "" {
197-
return false, nil
188+
_, err = api.GetPipeline(client, applicationName, pipelineName, &p)
189+
if err != nil {
190+
// Weird error case: sometimes spinnaker returns an EOF error for non-existing pipelines.
191+
if apierrors.IsNotFound(err) || strings.Contains(err.Error(), "EOF") {
192+
return false, nil
193+
}
194+
195+
return false, fmt.Errorf("failed to fetch pipeline %q for application %q: %w",
196+
pipelineName, applicationName, err)
198197
}
199198

200199
return true, nil
@@ -217,37 +216,39 @@ func pipelineDiffSuppressFunc(k, old, new string, d *schema.ResourceData) bool {
217216
return editedOld == editedNew
218217
}
219218

220-
func decodeEditAndEncodePipeline(pipeline string) (encodedPipeline string, err error) {
221-
// Decode the pipeline into a map we can edit
222-
pipelineBytes := []byte(pipeline)
223-
var pipelineMapGeneric interface{}
224-
err = json.Unmarshal(pipelineBytes, &pipelineMapGeneric)
225-
if err != nil {
226-
return
219+
func parsePipeline(rawPipeline string) (map[string]interface{}, error) {
220+
var pipeline map[string]interface{}
221+
222+
if err := json.Unmarshal([]byte(rawPipeline), &pipeline); err != nil {
223+
return nil, fmt.Errorf("invalid pipeline json: %w", err)
227224
}
228225

229-
pipelineMap := pipelineMapGeneric.(map[string]interface{})
226+
return pipeline, nil
227+
}
230228

231-
return editAndEncodePipeline(pipelineMap)
229+
func decodeEditAndEncodePipeline(rawPipeline string) (string, error) {
230+
pipeline, err := parsePipeline(rawPipeline)
231+
if err != nil {
232+
return "", err
233+
}
234+
235+
return editAndEncodePipeline(pipeline)
232236
}
233237

234-
func editAndEncodePipeline(pipelineMap map[string]interface{}) (encodedPipeline string, err error) {
238+
func editAndEncodePipeline(pipeline map[string]interface{}) (string, error) {
235239
// Remove the keys we know are problematic because they are managed
236240
// by spinnaker or are handled by other schema attributes.
237-
delete(pipelineMap, "application")
238-
delete(pipelineMap, "lastModifiedBy")
239-
delete(pipelineMap, "id")
240-
delete(pipelineMap, "index")
241-
delete(pipelineMap, "name")
242-
delete(pipelineMap, "updateTs")
243-
244-
// Encode the pipeline into a single string
245-
// This will sort all keys, etc.
246-
editedPipelineBytes, err := json.Marshal(pipelineMap)
241+
delete(pipeline, "application")
242+
delete(pipeline, "lastModifiedBy")
243+
delete(pipeline, "id")
244+
delete(pipeline, "index")
245+
delete(pipeline, "name")
246+
delete(pipeline, "updateTs")
247+
248+
encoded, err := json.Marshal(pipeline)
247249
if err != nil {
248-
return
250+
return "", fmt.Errorf("failed to marshal pipeline: %w", err)
249251
}
250252

251-
encodedPipeline = string(editedPipelineBytes)
252-
return
253+
return string(encoded), nil
253254
}

spinnaker/resource_pipeline_template_v2.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,10 @@ func resourcePipelineTemplateV2Read(data *schema.ResourceData, meta interface{})
8787
templateID := data.Get("template_id").(string)
8888

8989
template, err := api.GetPipelineTemplateV2(client, templateID)
90-
if err != nil {
90+
if apierrors.IsNotFound(err) {
91+
data.SetId("")
92+
return nil
93+
} else if err != nil {
9194
return fmt.Errorf("failed to fetch pipeline template %q: %w", templateID, err)
9295
}
9396

0 commit comments

Comments
 (0)