Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add spinnaker_pipeline_template_v2 resource #13

Merged
merged 2 commits into from
Jun 1, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions docs/resources/pipeline_template_v2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "spinnaker_pipeline_template_v2 Resource - terraform-provider-spinnaker"
subcategory: ""
description: |-
Provides a V2 pipeline template. See https://spinnaker.io/reference/pipeline/templates/ for more details.
---

# spinnaker_pipeline_template_v2 (Resource)

Provides a V2 pipeline template. See https://spinnaker.io/reference/pipeline/templates/ for more details.



<!-- schema generated by tfplugindocs -->
## Schema

### Required

- **template** (String) JSON schema of the V2 pipeline template.
- **template_id** (String) ID of the template.

### Optional

- **id** (String) The ID of this resource.

### Read-Only

- **reference** (String) The URL for referencing the template in a pipeline instance.


3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@ module github.com/Bonial-International-GmbH/terraform-provider-spinnaker
go 1.16

require (
github.com/antihax/optional v1.0.0
github.com/cenkalti/backoff/v4 v4.1.0
github.com/ghodss/yaml v1.0.0
github.com/hashicorp/go-multierror v1.0.0
github.com/hashicorp/terraform-plugin-sdk v1.17.2
github.com/mitchellh/mapstructure v1.4.1
github.com/spinnaker/spin v1.22.0
github.com/stretchr/testify v1.7.0
)
68 changes: 65 additions & 3 deletions spinnaker/api/errors/errors.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
package errors

import "regexp"
import (
"errors"
"fmt"
"net/http"
"regexp"

var (
pipelineAlreadyExistsRegexp = regexp.MustCompile(`.*A pipeline with name .* already exists.*`)
gateapi "github.com/spinnaker/spin/gateapi"
)

var pipelineAlreadyExistsRegexp = regexp.MustCompile(`.*A pipeline with name .* already exists.*`)

// IsPipelineAlreadyExists returns true if the error indicates that a pipeline
// already exists.
func IsPipelineAlreadyExists(err error) bool {
Expand All @@ -15,3 +20,60 @@ func IsPipelineAlreadyExists(err error) bool {

return pipelineAlreadyExistsRegexp.MatchString(err.Error())
}

// IsNotFound returns true if err resembles an HTTP NotFound error.
func IsNotFound(err error) bool {
return HasCode(http.StatusNotFound, err)
}

// HasCode returns true if err resembles an HTTP error with status code.
func HasCode(code int, err error) bool {
var respErr *ResponseError

if errors.As(err, &respErr) {
return respErr.Code() == code
}

return false
}

// ResponseError wraps a (potentially nil) *http.Response and an error.
type ResponseError struct {
resp *http.Response
err error
}

// NewResponseError creates a new *ResponseError.
func NewResponseError(resp *http.Response, err error) *ResponseError {
return &ResponseError{
resp: resp,
err: err,
}
}

// Code returns the HTTP status code if the error includes an *http.Response.
// Otherwise returns 0.
func (e *ResponseError) Code() int {
if e.resp != nil {
return e.resp.StatusCode
}

return 0
}

// Error implements the error interface.
func (e *ResponseError) Error() string {
if e.resp == nil {
return e.err.Error()
}

code := e.Code()

var gateErr gateapi.GenericSwaggerError

if errors.As(e.err, &gateErr) {
return fmt.Sprintf("%v, Code: %d, Body: %s", gateErr, code, string(gateErr.Body()))
}

return fmt.Sprintf("%v, Code: %d", e.err, code)
}
27 changes: 27 additions & 0 deletions spinnaker/api/errors/errors_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package errors

import (
"errors"
"net/http"
"testing"

"github.com/stretchr/testify/require"
)

func TestResponseError(t *testing.T) {
err := errors.New("the-error")
notFoundResp := &http.Response{StatusCode: http.StatusNotFound}

require.EqualError(t, NewResponseError(nil, err), "the-error")
require.EqualError(t, NewResponseError(notFoundResp, err), "the-error, Code: 404")
}

func TestIsNotFound(t *testing.T) {
err := errors.New("the-error")
respErr := NewResponseError(nil, err)
notFoundErr := NewResponseError(&http.Response{StatusCode: http.StatusNotFound}, err)

require.False(t, IsNotFound(err))
require.False(t, IsNotFound(respErr))
require.True(t, IsNotFound(notFoundErr))
}
31 changes: 31 additions & 0 deletions spinnaker/api/models.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package api

// PipelineTemplateV2 defines the schema of the pipeline template JSON.
type PipelineTemplateV2 struct {
ID string `json:"id,omitempty"`
Metadata PipelineTemplateV2Metadata `json:"metadata"`
Pipeline interface{} `json:"pipeline"`
pjungermann marked this conversation as resolved.
Show resolved Hide resolved
Protect *bool `json:"protect,omitempty"`
Schema string `json:"schema"`
Variables []PipelineTemplateV2Variable `json:"variables,omitempty"`
}

type PipelineTemplateV2Metadata struct {
Name string `json:"name"`
Description string `json:"description"`
Owner *string `json:"owner,omitempty"`
Scopes []string `json:"scopes,omitempty"`
}

type PipelineTemplateV2Variable struct {
Name string `json:"name"`
Description *string `json:"description,omitempty"`
DefaultValue interface{} `json:"defaultValue,omitempty"`
Type string `json:"type"`
}

type PipelineTemplateV2Version struct {
ID string `json:"id"`
Digest string `json:"digest"`
Tag string `json:"tag"`
}
File renamed without changes.
97 changes: 97 additions & 0 deletions spinnaker/api/pipeline_template_v2.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package api

import (
"net/http"

"github.com/Bonial-International-GmbH/terraform-provider-spinnaker/spinnaker/api/errors"
"github.com/antihax/optional"
"github.com/mitchellh/mapstructure"
gate "github.com/spinnaker/spin/cmd/gateclient"
gateapi "github.com/spinnaker/spin/gateapi"
)

// CreatePipelineTemplateV2 creates a pipeline template.
func CreatePipelineTemplateV2(client *gate.GatewayClient, template *PipelineTemplateV2) error {
_, resp, err := retry(func() (map[string]interface{}, *http.Response, error) {
return client.V2PipelineTemplatesControllerApi.CreateUsingPOST1(client.Context, template, nil)
})
if err != nil || (resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated) {
return errors.NewResponseError(resp, err)
}

return nil
}

// GetPipelineTemplateV2 fetches the pipeline template with templateID.
func GetPipelineTemplateV2(client *gate.GatewayClient, templateID string) (*PipelineTemplateV2, error) {
payload, resp, err := retry(func() (map[string]interface{}, *http.Response, error) {
return client.V2PipelineTemplatesControllerApi.GetUsingGET2(client.Context, templateID, nil)
})
if err != nil || resp.StatusCode != http.StatusOK {
return nil, errors.NewResponseError(resp, err)
}

var template PipelineTemplateV2

if err := mapstructure.Decode(payload, &template); err != nil {
return nil, err
}

return &template, nil
}

// DeletePipelineTemplateV2 deletes the pipeline template with templateID.
// Either digest or tag can be set on a delete request, but not both.
func DeletePipelineTemplateV2(client *gate.GatewayClient, templateID, tag, digest string) error {
opts := &gateapi.V2PipelineTemplatesControllerApiDeleteUsingDELETE1Opts{}
if digest != "" {
opts.Digest = optional.NewString(digest)
} else if tag != "" {
opts.Tag = optional.NewString(tag)
}

_, resp, err := retry(func() (map[string]interface{}, *http.Response, error) {
return client.V2PipelineTemplatesControllerApi.DeleteUsingDELETE1(client.Context, templateID, opts)
})
if err != nil || (resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusNoContent) {
return errors.NewResponseError(resp, err)
}

return nil
}

// UpdatePipelineTemplateV2 updates the pipeline template with templateID with
// the data in template.
func UpdatePipelineTemplateV2(client *gate.GatewayClient, template *PipelineTemplateV2) error {
_, resp, err := retry(func() (map[string]interface{}, *http.Response, error) {
return client.V2PipelineTemplatesControllerApi.UpdateUsingPOST1(client.Context, template.ID, template, nil)
})
if err != nil || (resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated) {
return errors.NewResponseError(resp, err)
}

return nil
}

// ListPipelineTemplateV2Versions lists versions of all available pipeline
// templates. The resulting map is keyed by template ID.
func ListPipelineTemplateV2Versions(client *gate.GatewayClient) (map[string][]*PipelineTemplateV2Version, error) {
var payload interface{}

_, resp, err := retry(func() (map[string]interface{}, *http.Response, error) {
v, resp, err := client.V2PipelineTemplatesControllerApi.ListVersionsUsingGET(client.Context, nil)
payload = v
return nil, resp, err
})
if err != nil || resp.StatusCode != http.StatusOK {
return nil, errors.NewResponseError(resp, err)
}

var versionMap map[string][]*PipelineTemplateV2Version

if err = mapstructure.Decode(payload, &versionMap); err != nil {
return nil, err
}

return versionMap, nil
}
1 change: 1 addition & 0 deletions spinnaker/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ func Provider() *schema.Provider {
"spinnaker_pipeline": resourcePipeline(),
"spinnaker_pipeline_template": resourcePipelineTemplate(),
"spinnaker_pipeline_template_config": resourcePipelineTemplateConfig(),
"spinnaker_pipeline_template_v2": resourcePipelineTemplateV2(),
},
DataSourcesMap: map[string]*schema.Resource{
"spinnaker_pipeline": datasourcePipeline(),
Expand Down
Loading