diff --git a/.golangci.yml b/.golangci.yml index 051ce4c..8f0af98 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,2 +1,40 @@ run: - timeout: 10m \ No newline at end of file + timeout: 10m + deadline: 2m + skip-dirs: + - scripts + +linter-settings: + govet: + check-shadowing: true + gocyclo: + min-complexity: 12.0 + maligned: + suggest-new: true + goconst: + min-len: 3.0 + min-occurrences: 3.0 + misspell: + locale: US + +linters: + enable-all: true + disable: + - tparallel + - paralleltest + - exhaustivestruct + +issues: + exclude-rules: + - path: _test\.go + text: "err113: do not define dynamic errors, use wrapped static errors instead" + - path: _test\.go + text: "error returned from external package is unwrapped" + - path: provider_test.go + text: "testAccProvider[s]* is a global variable" + - path: provider_test.go + text: "don't use `init` function" + - path: resource_sendgrid_template_version.go + text: "Function 'resourceSendgridTemplateVersion' is too long" + - path: resource_sendgrid_api_key.go + text: "Consider preallocating `scopes`" diff --git a/go.mod b/go.mod index fc2735b..5ee0550 100644 --- a/go.mod +++ b/go.mod @@ -1,9 +1,9 @@ module github.com/trois-six/terraform-provider-sendgrid -go 1.14 +go 1.15 require ( - github.com/hashicorp/terraform-plugin-sdk/v2 v2.4.0 + github.com/hashicorp/terraform-plugin-sdk/v2 v2.4.4 github.com/sendgrid/rest v2.6.2+incompatible - github.com/sendgrid/sendgrid-go v3.7.2+incompatible + github.com/sendgrid/sendgrid-go v3.8.0+incompatible ) diff --git a/go.sum b/go.sum index 09eec0c..d3fa7f0 100644 --- a/go.sum +++ b/go.sum @@ -201,14 +201,14 @@ github.com/hashicorp/hcl/v2 v2.3.0 h1:iRly8YaMwTBAKhn1Ybk7VSdzbnopghktCD031P8ggU github.com/hashicorp/hcl/v2 v2.3.0/go.mod h1:d+FwDBbOLvpAM3Z6J7gPj/VoAGkNe/gm352ZhjJ/Zv8= github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= -github.com/hashicorp/terraform-exec v0.12.0 h1:Tb1VC2gqArl9EJziJjoazep2MyxMk00tnNKV/rgMba0= -github.com/hashicorp/terraform-exec v0.12.0/go.mod h1:SGhto91bVRlgXQWcJ5znSz+29UZIa8kpBbkGwQ+g9E8= +github.com/hashicorp/terraform-exec v0.13.0 h1:1Pth+pdWJAufJuWWjaVOVNEkoRTOjGn3hQpAqj4aPdg= +github.com/hashicorp/terraform-exec v0.13.0/go.mod h1:SGhto91bVRlgXQWcJ5znSz+29UZIa8kpBbkGwQ+g9E8= github.com/hashicorp/terraform-json v0.8.0 h1:XObQ3PgqU52YLQKEaJ08QtUshAfN3yu4u8ebSW0vztc= github.com/hashicorp/terraform-json v0.8.0/go.mod h1:3defM4kkMfttwiE7VakJDwCd4R+umhSQnvJwORXbprE= -github.com/hashicorp/terraform-plugin-go v0.1.0 h1:kyXZ0nkHxiRev/q18N40IbRRk4AV0zE/MDJkDM3u8dY= -github.com/hashicorp/terraform-plugin-go v0.1.0/go.mod h1:10V6F3taeDWVAoLlkmArKttR3IULlRWFAGtQIQTIDr4= -github.com/hashicorp/terraform-plugin-sdk/v2 v2.4.0 h1:2c+vG46celrDCsfYEIzaXxvBaAXCqlVG77LwtFz8cfs= -github.com/hashicorp/terraform-plugin-sdk/v2 v2.4.0/go.mod h1:JBItawj+j8Ssla5Ib6BC/W9VQkOucBfnX7VRtyx1vw8= +github.com/hashicorp/terraform-plugin-go v0.2.1 h1:EW/R8bB2Zbkjmugzsy1d27yS8/0454b3MtYHkzOknqA= +github.com/hashicorp/terraform-plugin-go v0.2.1/go.mod h1:10V6F3taeDWVAoLlkmArKttR3IULlRWFAGtQIQTIDr4= +github.com/hashicorp/terraform-plugin-sdk/v2 v2.4.4 h1:6k0WcxFgVqF/GUFHPvAH8FIrCkoA1RInXzSxhkKamPg= +github.com/hashicorp/terraform-plugin-sdk/v2 v2.4.4/go.mod h1:z+cMZ0iswzZOahBJ3XmNWgWkVnAd2bl8g+FhyyuPDH4= github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M= github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d h1:kJCB4vdITiW1eC1vq2e6IsrXKrZit1bv/TDYFGMp4BQ= @@ -292,8 +292,8 @@ github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1: github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/sendgrid/rest v2.6.2+incompatible h1:zGMNhccsPkIc8SvU9x+qdDz2qhFoGUPGGC4mMvTondA= github.com/sendgrid/rest v2.6.2+incompatible/go.mod h1:kXX7q3jZtJXK5c5qK83bSGMdV6tsOE70KbHoqJls4lE= -github.com/sendgrid/sendgrid-go v3.7.2+incompatible h1:ePQr9ns8so+28whk+gLKRYiyI5IiCESkDIqy7cjiwLg= -github.com/sendgrid/sendgrid-go v3.7.2+incompatible/go.mod h1:QRQt+LX/NmgVEvmdRw0VT/QgUn499+iza2FnDca9fg8= +github.com/sendgrid/sendgrid-go v3.8.0+incompatible h1:7yoUFMwT+jDI2ArBpC6zvtuQj1RUyYfCDl7zZea3XV4= +github.com/sendgrid/sendgrid-go v3.8.0+incompatible/go.mod h1:QRQt+LX/NmgVEvmdRw0VT/QgUn499+iza2FnDca9fg8= github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= diff --git a/main.go b/main.go index 9067b8b..1638998 100644 --- a/main.go +++ b/main.go @@ -1,16 +1,12 @@ package main import ( - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/plugin" - "github.com/trois-six/terraform-provider-sendgrid/sendgrid" ) func main() { plugin.Serve(&plugin.ServeOpts{ - ProviderFunc: func() *schema.Provider { - return sendgrid.Provider() - }, + ProviderFunc: sendgrid.Provider, }) } diff --git a/scripts/main.go b/scripts/main.go index 357c1f9..d7d85a3 100644 --- a/scripts/main.go +++ b/scripts/main.go @@ -47,7 +47,7 @@ func main() { genIdx(fpath) } -// genIdx generating index for resource +// genIdx generating index for resource. func genIdx(fpath string) { type Index struct { Name string @@ -63,15 +63,18 @@ func genIdx(fpath string) { fname := "provider.go" log.Printf("[START]get description from file: %s\n", fname) + description, err := getFileDescription(fmt.Sprintf("%s/%s", fpath, fname)) if err != nil { log.Printf("[SKIP!]get description failed, skip: %s", err) + return } description = strings.TrimSpace(description) if description == "" { log.Printf("[SKIP!]description empty, skip: %s\n", fname) + return } @@ -80,20 +83,25 @@ func genIdx(fpath string) { resources = strings.TrimSpace(description[pos+16:]) } else { log.Printf("[SKIP!]resource list missing, skip: %s\n", fname) + return } index := Index{} + for _, v := range strings.Split(resources, "\n") { vv := strings.TrimSpace(v) if vv == "" { continue } + if strings.HasPrefix(v, " ") { if index.Name == "" { log.Printf("[FAIL!]no resource name found: %s", v) + return } + index.Resources = append(index.Resources, []string{vv, vv[len(providerName)+1:]}) } else { if index.Name != "" { @@ -136,9 +144,11 @@ func genIdx(fpath string) { } fname = fmt.Sprintf("%s/index.md", docRoot) + fd, err := os.OpenFile(fname, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) if err != nil { log.Printf("[FAIL!]open file %s failed: %s", fname, err) + return } @@ -151,24 +161,27 @@ func genIdx(fpath string) { idxTPL, err := ioutil.ReadFile(indexFile) if err != nil { log.Printf("[FAIL!]open file %s failed: %s", indexFile, err) + return } t := template.Must(template.New("t").Parse(string(idxTPL))) + err = t.Execute(fd, data) if err != nil { log.Printf("[FAIL!]write file %s failed: %s", fname, err) + return } log.Printf("[SUCC.]write doc to file success: %s", fname) } -// genDoc generating doc for resource +// genDoc generating doc for resource. func genDoc(dtype, dtypeFolder, fpath, name string, resource *schema.Resource) { data := map[string]string{ "name": name, - "dtype": strings.Replace(dtype, "_", "", -1), + "dtype": strings.ReplaceAll(dtype, "_", ""), "dtype_folder": dtypeFolder, "resource": name[len(providerName)+1:], "example": "", @@ -183,12 +196,14 @@ func genDoc(dtype, dtypeFolder, fpath, name string, resource *schema.Resource) { description, err := getFileDescription(fmt.Sprintf("%s/%s", fpath, fname)) if err != nil { log.Printf("[SKIP!]get description failed, skip: %s", err) + return } description = strings.TrimSpace(description) if description == "" { log.Printf("[SKIP!]description empty, skip: %s\n", fname) + return } @@ -204,10 +219,12 @@ func genDoc(dtype, dtypeFolder, fpath, name string, resource *schema.Resource) { description = strings.TrimSpace(description[:pos]) } else { log.Printf("[SKIP!]example usage missing, skip: %s\n", fname) + return } data["description"] = description + pos = strings.Index(description, "\n\n") if pos != -1 { data["description_short"] = strings.TrimSpace(description[:pos]) @@ -221,21 +238,30 @@ func genDoc(dtype, dtypeFolder, fpath, name string, resource *schema.Resource) { subStruct := []string{} var keys []string + for k := range resource.Schema { keys = append(keys, k) } + sort.Strings(keys) + for _, k := range keys { v := resource.Schema[k] if v.Description == "" { continue } + if v.Required { opt := "Required" if v.ForceNew { opt += ", ForceNew" } - requiredArgs = append(requiredArgs, fmt.Sprintf("* `%s` - (%s) %s", k, opt, v.Description)) + + requiredArgs = append( + requiredArgs, + fmt.Sprintf("* `%s` - (%s) %s", k, opt, v.Description), + ) + subStruct = append(subStruct, getSubStruct(0, k, v)...) } else if v.Optional { opt := "Optional" @@ -258,15 +284,19 @@ func genDoc(dtype, dtypeFolder, fpath, name string, resource *schema.Resource) { requiredArgs = append(requiredArgs, optionalArgs...) data["arguments"] = strings.Join(requiredArgs, "\n") + if len(subStruct) > 0 { data["arguments"] += "\n" + strings.Join(subStruct, "\n") } + data["attributes"] = strings.Join(attributes, "\n") fname = fmt.Sprintf("%s/%s/%s.md", docRoot, dtypeFolder, data["resource"]) fd, err := os.OpenFile(fname, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) + if err != nil { log.Printf("[FAIL!]open file %s failed: %s", fname, err) + return } @@ -279,20 +309,23 @@ func genDoc(dtype, dtypeFolder, fpath, name string, resource *schema.Resource) { docTPL, err := ioutil.ReadFile(docFile) if err != nil { log.Printf("[FAIL!]open file %s failed: %s", docFile, err) + return } t := template.Must(template.New("t").Parse(string(docTPL))) + err = t.Execute(fd, data) if err != nil { log.Printf("[FAIL!]write file %s failed: %s", fname, err) + return } log.Printf("[SUCC.]write doc to file success: %s", fname) } -// getAttributes get attributes from schema +// getAttributes get attributes from schema. func getAttributes(step int, k string, v *schema.Schema) []string { attributes := []string{} ident := strings.Repeat(" ", step*2) @@ -304,18 +337,26 @@ func getAttributes(step int, k string, v *schema.Schema) []string { if v.Computed { if _, ok := v.Elem.(*schema.Resource); ok { listAttributes := []string{} + for kk, vv := range v.Elem.(*schema.Resource).Schema { attrs := getAttributes(step+1, kk, vv) if len(attrs) > 0 { listAttributes = append(listAttributes, attrs...) } } + slistAttributes := "" + sort.Strings(listAttributes) + if len(listAttributes) > 0 { slistAttributes = "\n" + strings.Join(listAttributes, "\n") } - attributes = append(attributes, fmt.Sprintf("%s* `%s` - %s%s", ident, k, v.Description, slistAttributes)) + + attributes = append( + attributes, + fmt.Sprintf("%s* `%s` - %s%s", ident, k, v.Description, slistAttributes), + ) } else { attributes = append(attributes, fmt.Sprintf("%s* `%s` - %s", ident, k, v.Description)) } @@ -324,7 +365,7 @@ func getAttributes(step int, k string, v *schema.Schema) []string { return attributes } -// getFileDescription get description from go file +// getFileDescription get description from go file. func getFileDescription(fname string) (string, error) { fset := token.NewFileSet() @@ -336,7 +377,7 @@ func getFileDescription(fname string) (string, error) { return parsedAst.Doc.Text(), nil } -// getSubStruct get sub structure from go file +// getSubStruct get sub structure from go file. func getSubStruct(step int, k string, v *schema.Schema) []string { subStructs := []string{} @@ -346,7 +387,10 @@ func getSubStruct(step int, k string, v *schema.Schema) []string { if v.Type == schema.TypeMap || v.Type == schema.TypeList || v.Type == schema.TypeSet { if _, ok := v.Elem.(*schema.Resource); ok { - subStructs = append(subStructs, fmt.Sprintf("\nThe `%s` object supports the following:\n", k)) + subStructs = append( + subStructs, + fmt.Sprintf("\nThe `%s` object supports the following:\n", k), + ) requiredArgs := []string{} optionalArgs := []string{} attributes := []string{} @@ -355,18 +399,25 @@ func getSubStruct(step int, k string, v *schema.Schema) []string { for kk := range v.Elem.(*schema.Resource).Schema { keys = append(keys, kk) } + sort.Strings(keys) + for _, kk := range keys { vv := v.Elem.(*schema.Resource).Schema[kk] if vv.Description == "" { vv.Description = "************************* Please input Description for Schema ************************* " } + if vv.Required { opt := "Required" if vv.ForceNew { opt += ", ForceNew" } - requiredArgs = append(requiredArgs, fmt.Sprintf("* `%s` - (%s) %s", kk, opt, vv.Description)) + + requiredArgs = append( + requiredArgs, + fmt.Sprintf("* `%s` - (%s) %s", kk, opt, vv.Description), + ) } else if vv.Optional { opt := "Optional" if vv.ForceNew { @@ -380,10 +431,13 @@ func getSubStruct(step int, k string, v *schema.Schema) []string { } } } + sort.Strings(requiredArgs) subStructs = append(subStructs, requiredArgs...) + sort.Strings(optionalArgs) subStructs = append(subStructs, optionalArgs...) + sort.Strings(attributes) subStructs = append(subStructs, attributes...) @@ -393,5 +447,6 @@ func getSubStruct(step int, k string, v *schema.Schema) []string { } } } + return subStructs } diff --git a/sdk/api_key.go b/sdk/api_key.go index b066c1d..6b4b36a 100644 --- a/sdk/api_key.go +++ b/sdk/api_key.go @@ -15,75 +15,76 @@ type APIKey struct { func parseAPIKey(respBody string) (*APIKey, error) { var body APIKey - err := json.Unmarshal([]byte(respBody), &body) - if err != nil { - return nil, err + if err := json.Unmarshal([]byte(respBody), &body); err != nil { + return nil, fmt.Errorf("failed parsing API key: %w", err) } + return &body, nil } // CreateAPIKey creates an APIKey and returns it. func (c *Client) CreateAPIKey(name string, scopes []string) (*APIKey, error) { if name == "" { - return nil, fmt.Errorf("[CreateAPIKey] a name is required") + return nil, ErrNameRequired } + respBody, _, err := c.Post("POST", "/api_keys", APIKey{ Name: name, Scopes: scopes, }) if err != nil { - return nil, err + return nil, fmt.Errorf("failed creating API key: %w", err) } + return parseAPIKey(respBody) } // ReadAPIKey retreives an APIKey and returns it. func (c *Client) ReadAPIKey(id string) (*APIKey, error) { if id == "" { - return nil, fmt.Errorf("[ReadAPIKey] an ID is required") + return nil, ErrAPIKeyIDRequired } + respBody, _, err := c.Get("GET", "/api_keys/"+id) if err != nil { - return nil, err + return nil, fmt.Errorf("failed reading API key: %w", err) } + return parseAPIKey(respBody) } // UpdateAPIKey edits an APIKey and returns it. func (c *Client) UpdateAPIKey(id, name string, scopes []string) (*APIKey, error) { if id == "" { - return nil, fmt.Errorf("[UpdateAPIKey] an ID is required") + return nil, ErrAPIKeyIDRequired } + t := APIKey{} if name != "" { t.Name = name } + if len(scopes) > 0 { t.Scopes = scopes } + respBody, _, err := c.Post("PUT", "/api_keys/"+id, t) if err != nil { - return nil, err + return nil, fmt.Errorf("failed updating API key: %w", err) } + return parseAPIKey(respBody) } // DeleteAPIKey deletes an APIKey. func (c *Client) DeleteAPIKey(id string) (bool, error) { if id == "" { - return false, fmt.Errorf("[DeleteAPIKey] an ID is required") + return false, ErrAPIKeyIDRequired } - responseBody, statusCode, err := c.Get("DELETE", "/api_keys/"+id) - if statusCode > 299 && statusCode != 404 { - return false, &RequestError{ - StatusCode: statusCode, - Err: fmt.Errorf("[DeleteAPIKey] error deleting api key, status: %d, response: %s", statusCode, responseBody), - } + if _, statusCode, err := c.Get("DELETE", "/api_keys/"+id); statusCode > 299 || err != nil { + return false, fmt.Errorf("failed deleting API key: %w", err) } - if err != nil { - return false, err - } return true, nil } diff --git a/sdk/client.go b/sdk/client.go index 2046464..c1aff28 100644 --- a/sdk/client.go +++ b/sdk/client.go @@ -20,6 +20,7 @@ func NewClient(apiKey, host, onBehalfOf string) *Client { if host == "" { host = "https://api.sendgrid.com/v3" } + return &Client{ apiKey: apiKey, host: host, @@ -29,12 +30,14 @@ func NewClient(apiKey, host, onBehalfOf string) *Client { func bodyToJSON(body interface{}) ([]byte, error) { if body == nil { - return nil, fmt.Errorf("[bodyToJSON] body must no be nil") + return nil, ErrBodyNotNil } + jsonBody, err := json.Marshal(body) if err != nil { - return nil, fmt.Errorf("[bodyToJSON] body could not be jsonified") + return nil, fmt.Errorf("body could not be jsonified: %w", err) } + return jsonBody, nil } @@ -46,31 +49,40 @@ func (c *Client) Get(method rest.Method, endpoint string) (string, int, error) { } else { req = sendgrid.GetRequest(c.apiKey, endpoint, c.host) } + req.Method = method + resp, err := sendgrid.API(req) if err != nil { - return "", resp.StatusCode, fmt.Errorf("[Get] %s", err) + return "", resp.StatusCode, fmt.Errorf("failed getting resource: %w", err) } + return resp.Body, resp.StatusCode, nil } // Post posts a resource to Sendgrid. func (c *Client) Post(method rest.Method, endpoint string, body interface{}) (string, int, error) { var err error + var req rest.Request + if c.OnBehalfOf != "" { req = sendgrid.GetRequestSubuser(c.apiKey, endpoint, c.host, c.OnBehalfOf) } else { req = sendgrid.GetRequest(c.apiKey, endpoint, c.host) } + req.Method = method + req.Body, err = bodyToJSON(body) if err != nil { - return "", 0, err + return "", 0, fmt.Errorf("failed preparing request body: %w", err) } + resp, err := sendgrid.API(req) if err != nil { - return "", resp.StatusCode, fmt.Errorf("[Post] %s", err) + return "", resp.StatusCode, fmt.Errorf("failed posting resource: %w", err) } + return resp.Body, resp.StatusCode, nil } diff --git a/sdk/errors.go b/sdk/errors.go new file mode 100644 index 0000000..320a688 --- /dev/null +++ b/sdk/errors.go @@ -0,0 +1,18 @@ +package sendgrid + +import "errors" + +var ( + ErrNameRequired = errors.New("a name is required") + ErrAPIKeyIDRequired = errors.New("an API Key ID is required") + ErrBodyNotNil = errors.New("body must not be nil") + ErrUsernameRequired = errors.New("a username is required") + ErrEmailRequired = errors.New("an email is required") + ErrPasswordRequired = errors.New("a password is required") + ErrIPRequired = errors.New("at least one ip address is required") + ErrTemplateIDRequired = errors.New("a template ID is required") + ErrTemplateNameRequired = errors.New("a template name is required") + ErrTemplateVersionIDRequired = errors.New("a template version ID is required") + ErrTemplateVersionNameRequired = errors.New("a template version name is required") + ErrTemplateVersionSubjectRequired = errors.New("a template version subject is required") +) diff --git a/sdk/subuser.go b/sdk/subuser.go index f898df7..8896519 100644 --- a/sdk/subuser.go +++ b/sdk/subuser.go @@ -3,7 +3,7 @@ package sendgrid import ( "encoding/json" "fmt" - "log" + "net/http" "net/url" ) @@ -13,12 +13,11 @@ type creditAllocation struct { type RequestError struct { StatusCode int - - Err error + Err error } -// Subuser is a Sendgrid Subuser. -type Subuser struct { +// SubUser is a Sendgrid SubUser. +type SubUser struct { ID int `json:"id,omitempty"` UserID int `json:"user_id,omitempty"` UserName string `json:"username,omitempty"` @@ -31,140 +30,127 @@ type Subuser struct { CreditAllocation creditAllocation `json:"credit_allocation,omitempty"` } -type subuserError struct { +type subUserError struct { Field string `json:"field,omitempty"` Message string `json:"message,omitempty"` } -type subuserErrors struct { - Errors []subuserError `json:"errors,omitempty"` -} -func parseSubuser(respBody string) (*Subuser, error) { - var body Subuser - err := json.Unmarshal([]byte(respBody), &body) - if err != nil { - return nil, err - } - return &body, nil +type subUserErrors struct { + Errors []subUserError `json:"errors,omitempty"` } -func parseSubusers(respBody string) (*Subuser, error) { - var body []Subuser - log.Printf("[DEBUG] Parsing sub users, response body: %s", respBody) - err := json.Unmarshal([]byte(respBody), &body) - if err != nil { - log.Printf("[ERROR] Couldn't parse sub users") - return nil, err +func parseSubUsers(respBody string) ([]SubUser, RequestError) { + var body []SubUser + if err := json.Unmarshal([]byte(respBody), &body); err != nil { + return nil, RequestError{ + StatusCode: http.StatusInternalServerError, + Err: fmt.Errorf("failed parsing subUsers: %w", err), + } } - return &body[0], nil + + return body, RequestError{StatusCode: http.StatusOK, Err: nil} } // CreateSubuser creates a subuser and returns it. -func (c *Client) CreateSubuser(username, email, password string, ips []string) (*Subuser, *RequestError) { +func (c *Client) CreateSubuser( + username, email, password string, + ips []string, +) (*SubUser, RequestError) { if username == "" { - return nil, GenericError(fmt.Errorf("[CreateSubuser] a username is required")) + return nil, RequestError{StatusCode: http.StatusNotAcceptable, Err: ErrUsernameRequired} } + if email == "" { - return nil, GenericError(fmt.Errorf("[CreateSubuser] an email is required")) + return nil, RequestError{StatusCode: http.StatusNotAcceptable, Err: ErrEmailRequired} } + if password == "" { - return nil, GenericError(fmt.Errorf("[CreateSubuser] a password is required")) + return nil, RequestError{StatusCode: http.StatusNotAcceptable, Err: ErrPasswordRequired} } + if len(ips) < 1 { - return nil, GenericError(fmt.Errorf("[CreateSubuser] at least one ip address is required")) + return nil, RequestError{StatusCode: http.StatusNotAcceptable, Err: ErrIPRequired} } - respBody, statusCode, err := c.Post("POST", "/subusers", Subuser{ + + respBody, statusCode, err := c.Post("POST", "/subusers", SubUser{ UserName: username, Email: email, Password: password, IPs: ips, }) - - log.Printf("[DEBUG] [CreateSubuser] status: %d, body: %s", statusCode, respBody) - - if err != nil { - return nil, &RequestError{ + if err != nil || statusCode >= 300 { + return nil, RequestError{ StatusCode: statusCode, - Err: err, + Err: fmt.Errorf("failed creating subUser: %w", err), } } - if statusCode >= 300 { - return nil, &RequestError{ - StatusCode: statusCode, - Err: fmt.Errorf("[CreateSubuser] error returned from API, status: %d, body: %s", statusCode, respBody), - } - } + subUsers, requestErr := parseSubUsers(respBody) - subuser, err := parseSubuser(respBody) - return subuser, GenericError(err) + return &subUsers[0], requestErr } // ReadSubuser retreives a subuser and returns it. -func (c *Client) ReadSubuser(username string) (*Subuser, error) { +func (c *Client) ReadSubUser(username string) (*SubUser, RequestError) { if username == "" { - return nil, fmt.Errorf("[ReadSubuser] a username is required") + return nil, RequestError{StatusCode: http.StatusNotAcceptable, Err: ErrUsernameRequired} } endpoint := "/subusers?username=" + url.QueryEscape(username) - log.Printf("[DEBUG] Searching for user at: %s", endpoint) - - respBody, _, err := c.Get("GET", endpoint) + respBody, statusCode, err := c.Get("GET", endpoint) if err != nil { - return nil, err + return nil, RequestError{ + StatusCode: statusCode, + Err: fmt.Errorf("failed reading subUser: %w", err), + } } - return parseSubusers(respBody) + + subUsers, requestErr := parseSubUsers(respBody) + + return &subUsers[0], requestErr } // UpdateSubuser enables/disables a subuser. -func (c *Client) UpdateSubuser(username string, disabled bool) (bool, error) { +func (c *Client) UpdateSubuser(username string, disabled bool) (bool, RequestError) { if username == "" { - return false, fmt.Errorf("[UpdateSubuser] a name is required") + return false, RequestError{StatusCode: http.StatusNotAcceptable, Err: ErrUsernameRequired} } - respBody, _, err := c.Post("PATCH", "/subusers/"+username, Subuser{ + + respBody, statusCode, err := c.Post("PATCH", "/subusers/"+username, SubUser{ Disabled: disabled, }) if err != nil { - return false, err - } - var body subuserErrors - err = json.Unmarshal([]byte(respBody), &body) - if err != nil { - return false, err + return false, RequestError{ + StatusCode: statusCode, + Err: fmt.Errorf("failed updating subUser: %w", err), + } } - return len(body.Errors) == 0, nil -} - -func (r *RequestError) Error() string { - return fmt.Sprintf("status: %d, err: %s", r.StatusCode, r.Err) -} -func GenericError(error error) *RequestError { - return &RequestError{ - StatusCode: 500, - Err: error, + var body subUserErrors + if err = json.Unmarshal([]byte(respBody), &body); err != nil { + return false, RequestError{ + StatusCode: http.StatusInternalServerError, + Err: fmt.Errorf("failed updating subUser: %w", err), + } } + + return len(body.Errors) == 0, RequestError{StatusCode: http.StatusOK, Err: nil} } // DeleteSubuser deletes a subuser. -func (c *Client) DeleteSubuser(username string) (bool, *RequestError) { +func (c *Client) DeleteSubuser(username string) (bool, RequestError) { if username == "" { - return false, GenericError(fmt.Errorf("[DeleteSubuser] a username is required")) - } - responseBody, statusCode, err := c.Get("DELETE", "/subusers/"+username) - if err != nil { - return false, GenericError(err) + return false, RequestError{StatusCode: http.StatusNotAcceptable, Err: ErrUsernameRequired} } - if statusCode > 299 && statusCode != 404 { - return false, &RequestError{ - StatusCode: statusCode, - Err: fmt.Errorf("[DeleteSubuser] error deleting user: %s", username), + if _, statusCode, err := c.Get("DELETE", "/subusers/"+username); statusCode > 299 || + err != nil { + return false, RequestError{ + StatusCode: http.StatusInternalServerError, + Err: fmt.Errorf("failed deleting subUser: %w", err), } } - log.Printf("[DEBUG] [DeleteSubuser] status code: %d, responseBody: %s", statusCode, responseBody) - - return true, nil + return true, RequestError{StatusCode: http.StatusOK, Err: nil} } diff --git a/sdk/template.go b/sdk/template.go index 51f985f..935f32b 100644 --- a/sdk/template.go +++ b/sdk/template.go @@ -17,68 +17,81 @@ type Template struct { func parseTemplate(respBody string) (*Template, error) { var body Template + err := json.Unmarshal([]byte(respBody), &body) if err != nil { - return nil, err + return nil, fmt.Errorf("failed parsing template: %w", err) } + return &body, nil } // CreateTemplate creates a transactional template and returns it. func (c *Client) CreateTemplate(name, generation string) (*Template, error) { if name == "" { - return nil, fmt.Errorf("[CreateTemplate] a name is required") + return nil, ErrTemplateNameRequired } + if generation == "" { generation = "dynamic" } + respBody, _, err := c.Post("POST", "/templates", Template{ Name: name, Generation: generation, }) if err != nil { - return nil, err + return nil, fmt.Errorf("failed creating template: %w", err) } + return parseTemplate(respBody) } // ReadTemplate retreives a transactional template and returns it. func (c *Client) ReadTemplate(id string) (*Template, error) { if id == "" { - return nil, fmt.Errorf("[ReadTemplate] an ID is required") + return nil, ErrTemplateIDRequired } + respBody, _, err := c.Get("GET", "/templates/"+id) if err != nil { - return nil, err + return nil, fmt.Errorf("failed reading template: %w", err) } + return parseTemplate(respBody) } -// UpdateTemplate edits a transactional template and returns it. We can't change the "generation" of a transactional template. +// UpdateTemplate edits a transactional template and returns it. +// We can't change the "generation" of a transactional template. func (c *Client) UpdateTemplate(id, name string) (*Template, error) { if id == "" { - return nil, fmt.Errorf("[UpdateTemplate] an ID is required") + return nil, ErrTemplateIDRequired } + if name == "" { - return nil, fmt.Errorf("[UpdateTemplate] a name is required") + return nil, ErrTemplateNameRequired } + respBody, _, err := c.Post("PATCH", "/templates/"+id, Template{ Name: name, - }) + }, + ) if err != nil { - return nil, err + return nil, fmt.Errorf("failed updating template: %w", err) } + return parseTemplate(respBody) } // DeleteTemplate deletes a transactional template. func (c *Client) DeleteTemplate(id string) (bool, error) { if id == "" { - return false, fmt.Errorf("[DeleteTemplate] an ID is required") + return false, ErrTemplateIDRequired } - _, _, err := c.Get("DELETE", "/templates/"+id) - if err != nil { - return false, err + + if _, statusCode, err := c.Get("DELETE", "/templates/"+id); statusCode > 299 || err != nil { + return false, fmt.Errorf("failed deleting template: %w", err) } + return true, nil } diff --git a/sdk/template_version.go b/sdk/template_version.go index c419831..d1d1937 100644 --- a/sdk/template_version.go +++ b/sdk/template_version.go @@ -24,66 +24,83 @@ type TemplateVersion struct { func parseTemplateVersion(respBody string) (*TemplateVersion, error) { var body TemplateVersion + err := json.Unmarshal([]byte(respBody), &body) if err != nil { - return nil, err + return nil, fmt.Errorf("failed parsing template version: %w", err) } + return &body, nil } // CreateTemplateVersion creates a new version of a transactional template and returns it. func (c *Client) CreateTemplateVersion(t TemplateVersion) (*TemplateVersion, error) { if t.TemplateID == "" { - return nil, fmt.Errorf("[CreateTemplateVersion] a template ID is required") + return nil, ErrTemplateVersionIDRequired } + if t.Name == "" { - return nil, fmt.Errorf("[CreateTemplateVersion] a template Name is required") + return nil, ErrTemplateVersionNameRequired } + if t.Subject == "" { - return nil, fmt.Errorf("[CreateTemplateVersion] a template Subject is required") + return nil, ErrTemplateVersionSubjectRequired } + respBody, _, err := c.Post("POST", "/templates/"+t.TemplateID+"/versions", t) if err != nil { - return nil, err + return nil, fmt.Errorf("failed creating template version: %w", err) } + return parseTemplateVersion(respBody) } // ReadTemplateVersion retreives a version of a transactional template and returns it. func (c *Client) ReadTemplateVersion(templateID, id string) (*TemplateVersion, error) { if templateID == "" { - return nil, fmt.Errorf("[ReadTemplateVersion] a template ID is required") + return nil, ErrTemplateVersionIDRequired } + if id == "" { - return nil, fmt.Errorf("[ReadTemplateVersion] a version ID is required") + return nil, ErrTemplateIDRequired } + respBody, _, err := c.Get("GET", "/templates/"+templateID+"/versions/"+id) if err != nil { - return nil, err + return nil, fmt.Errorf("failed reading template version: %w", err) } + return parseTemplateVersion(respBody) } // UpdateTemplateVersion edits a version of a transactional template and returns it. func (c *Client) UpdateTemplateVersion(t TemplateVersion) (*TemplateVersion, error) { if t.ID == "" { - return nil, fmt.Errorf("[UpdateTemplateVersion] a template ID is required") + return nil, ErrTemplateVersionIDRequired } + if t.TemplateID == "" { - return nil, fmt.Errorf("[UpdateTemplateVersion] a template ID is required") + return nil, ErrTemplateIDRequired } + respBody, _, err := c.Post("PATCH", "/templates/"+t.TemplateID+"/versions/"+t.ID, t) if err != nil { - return nil, err + return nil, fmt.Errorf("failed updating template version: %w", err) } + return parseTemplateVersion(respBody) } // DeleteTemplateVersion deletes a version of a transactional template. func (c *Client) DeleteTemplateVersion(templateID, id string) (bool, error) { - _, _, err := c.Get("DELETE", "/templates/"+templateID+"/versions/"+id) - if err != nil { - return false, err + if templateID == "" { + return false, ErrTemplateVersionIDRequired + } + + if _, statusCode, err := c.Get("DELETE", "/templates/"+templateID+"/versions/"+id); statusCode > 299 || + err != nil { + return false, fmt.Errorf("failed deleting template version: %w", err) } + return true, nil } diff --git a/sendgrid/errors.go b/sendgrid/errors.go new file mode 100644 index 0000000..b8b72b3 --- /dev/null +++ b/sendgrid/errors.go @@ -0,0 +1,8 @@ +package sendgrid + +import "errors" + +var ( + ErrCreateRateLimit = errors.New("expected instance to be created but we were rate limited") + ErrInvalidImportFormat = errors.New("invalid import. Supported import format: {{templateID}}/{{templateVersionID}}") +) diff --git a/sendgrid/provider.go b/sendgrid/provider.go index 898d765..562407a 100644 --- a/sendgrid/provider.go +++ b/sendgrid/provider.go @@ -21,7 +21,7 @@ import ( sendgrid "github.com/trois-six/terraform-provider-sendgrid/sdk" ) -// Provider terraform.ResourceProvider +// Provider terraform.ResourceProvider. func Provider() *schema.Provider { return &schema.Provider{ Schema: map[string]*schema.Schema{ @@ -62,8 +62,11 @@ func providerConfigure(_ context.Context, d *schema.ResourceData) (interface{}, diags = append(diags, diag.Diagnostic{ Severity: diag.Error, Summary: "Sendgrid API key wasn't provided", - Detail: "Unable to retrieve the API key, either from the configuration of the provider, nor the env variable SENDGRID_API_KEY", + Detail: `Unable to retrieve the API key, + either from the configuration of the provider, + nor the env variable SENDGRID_API_KEY`, }) + return nil, diags } diff --git a/sendgrid/provider_test.go b/sendgrid/provider_test.go index 9c34209..bc8e7b0 100644 --- a/sendgrid/provider_test.go +++ b/sendgrid/provider_test.go @@ -1,33 +1,37 @@ -package sendgrid +package sendgrid_test import ( "os" "testing" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/trois-six/terraform-provider-sendgrid/sendgrid" ) var testAccProviders map[string]*schema.Provider + var testAccProvider *schema.Provider func init() { - testAccProvider = Provider() + testAccProvider = sendgrid.Provider() testAccProviders = map[string]*schema.Provider{ "sendgrid": testAccProvider, } } func TestProvider(t *testing.T) { - if err := Provider().InternalValidate(); err != nil { + if err := sendgrid.Provider().InternalValidate(); err != nil { t.Fatalf("err: %s", err) } } func TestProvider_impl(t *testing.T) { - var _ *schema.Provider = Provider() + var _ *schema.Provider = sendgrid.Provider() } func testAccPreCheck(t *testing.T) { + t.Helper() + if err := os.Getenv("SENDGRID_API_KEY"); err == "" { t.Fatal("SENDGRID_API_KEY must be set for acceptance tests") } diff --git a/sendgrid/resource_sendgrid_api_key.go b/sendgrid/resource_sendgrid_api_key.go index bdc46b0..c2f41ac 100644 --- a/sendgrid/resource_sendgrid_api_key.go +++ b/sendgrid/resource_sendgrid_api_key.go @@ -70,16 +70,17 @@ func scopeInScopes(scopes []string, scope string) bool { return true } } + return false } func resourceSendgridAPIKeyCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - c := m.(*sendgrid.Client) + var scopes []string + c := m.(*sendgrid.Client) name := d.Get("name").(string) - subUserOnBehalfOf := d.Get("sub_user_on_behalf_of").(string) - c.OnBehalfOf = subUserOnBehalfOf - var scopes []string + c.OnBehalfOf = d.Get("sub_user_on_behalf_of").(string) + for _, scope := range d.Get("scopes").(*schema.Set).List() { scopes = append(scopes, scope.(string)) } @@ -107,8 +108,7 @@ func resourceSendgridAPIKeyCreate(ctx context.Context, d *schema.ResourceData, m func resourceSendgridAPIKeyRead(_ context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { c := m.(*sendgrid.Client) - subUserOnBehalfOf := d.Get("sub_user_on_behalf_of").(string) - c.OnBehalfOf = subUserOnBehalfOf + c.OnBehalfOf = d.Get("sub_user_on_behalf_of").(string) apiKey, err := c.ReadAPIKey(d.Id()) if err != nil { @@ -134,8 +134,7 @@ func hasDiff(o, n interface{}) bool { func resourceSendgridAPIKeyUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { c := m.(*sendgrid.Client) - subUserOnBehalfOf := d.Get("sub_user_on_behalf_of").(string) - c.OnBehalfOf = subUserOnBehalfOf + c.OnBehalfOf = d.Get("sub_user_on_behalf_of").(string) a := sendgrid.APIKey{ ID: d.Id(), @@ -151,11 +150,11 @@ func resourceSendgridAPIKeyUpdate(ctx context.Context, d *schema.ResourceData, m for _, scope := range d.Get("scopes").(*schema.Set).List() { scopes = append(scopes, scope.(string)) } + a.Scopes = scopes } - _, err := c.UpdateAPIKey(d.Id(), a.Name, a.Scopes) - if err != nil { + if _, err := c.UpdateAPIKey(d.Id(), a.Name, a.Scopes); err != nil { return diag.FromErr(err) } @@ -165,8 +164,7 @@ func resourceSendgridAPIKeyUpdate(ctx context.Context, d *schema.ResourceData, m func resourceSendgridAPIKeyDelete(_ context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { c := m.(*sendgrid.Client) - subUserOnBehalfOf := d.Get("sub_user_on_behalf_of").(string) - c.OnBehalfOf = subUserOnBehalfOf + c.OnBehalfOf = d.Get("sub_user_on_behalf_of").(string) _, err := c.DeleteAPIKey(d.Id()) if err != nil { diff --git a/sendgrid/resource_sendgrid_api_key_test.go b/sendgrid/resource_sendgrid_api_key_test.go index 46fe749..40f09a6 100644 --- a/sendgrid/resource_sendgrid_api_key_test.go +++ b/sendgrid/resource_sendgrid_api_key_test.go @@ -1,4 +1,4 @@ -package sendgrid +package sendgrid_test import ( "fmt" diff --git a/sendgrid/resource_sendgrid_subuser.go b/sendgrid/resource_sendgrid_subuser.go index e41cedd..7618b7b 100644 --- a/sendgrid/resource_sendgrid_subuser.go +++ b/sendgrid/resource_sendgrid_subuser.go @@ -22,6 +22,8 @@ package sendgrid import ( "context" "fmt" + "net/http" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -100,7 +102,11 @@ func resourceSendgridSubuserCreate(ctx context.Context, d *schema.ResourceData, ips = append(ips, ip.(string)) } - if err := resource.Retry(d.Timeout(schema.TimeoutCreate), retrySubUserCreateClient(c, username, email, password, ips)); err != nil { + if err := resource.RetryContext( + ctx, + d.Timeout(schema.TimeoutCreate), + retrySubUserCreateClient(c, username, email, password, ips), + ); err != nil { return diag.FromErr(err) } @@ -113,16 +119,22 @@ func resourceSendgridSubuserCreate(ctx context.Context, d *schema.ResourceData, return resourceSendgridSubuserRead(ctx, d, m) } -func retrySubUserCreateClient(c *sendgrid.Client, username string, email string, password string, ips []string) func() *resource.RetryError { +func retrySubUserCreateClient( + c *sendgrid.Client, + username string, + email string, + password string, + ips []string, +) func() *resource.RetryError { return func() *resource.RetryError { - _, err := c.CreateSubuser(username, email, password, ips) + _, requestErr := c.CreateSubuser(username, email, password, ips) - if err != nil && err.StatusCode == 429 { - return resource.RetryableError(fmt.Errorf("expected instance to be created but we were rate limited")) + if requestErr.Err != nil && requestErr.StatusCode == http.StatusTooManyRequests { + return resource.RetryableError(ErrCreateRateLimit) } - if err != nil && err.Err != nil { - return resource.NonRetryableError(err) + if requestErr.Err != nil { + return resource.NonRetryableError(requestErr.Err) } return nil @@ -132,9 +144,9 @@ func retrySubUserCreateClient(c *sendgrid.Client, username string, email string, func resourceSendgridSubuserRead(_ context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { c := m.(*sendgrid.Client) - Subuser, err := c.ReadSubuser(d.Id()) - if err != nil { - return diag.FromErr(err) + Subuser, requestErr := c.ReadSubUser(d.Id()) + if requestErr.Err != nil { + return diag.FromErr(requestErr.Err) } //nolint:errcheck @@ -151,9 +163,9 @@ func resourceSendgridSubuserUpdate(ctx context.Context, d *schema.ResourceData, c := m.(*sendgrid.Client) if d.HasChange("disabled") { - _, err := c.UpdateSubuser(d.Id(), d.Get("disabled").(bool)) - if err != nil { - return diag.FromErr(err) + _, requestErr := c.UpdateSubuser(d.Id(), d.Get("disabled").(bool)) + if requestErr.Err != nil { + return diag.FromErr(requestErr.Err) } } @@ -162,24 +174,27 @@ func resourceSendgridSubuserUpdate(ctx context.Context, d *schema.ResourceData, func retrySubUserDeleteClient(c *sendgrid.Client, username string) func() *resource.RetryError { return func() *resource.RetryError { - _, err := c.DeleteSubuser(username) + _, requestErr := c.DeleteSubuser(username) - if err != nil && err.StatusCode == 429 { - return resource.RetryableError(fmt.Errorf("expected instance to be deleted but we were rate limited")) + if requestErr.Err != nil && requestErr.StatusCode == http.StatusTooManyRequests { + return resource.RetryableError(ErrCreateRateLimit) } - if err != nil { - return resource.NonRetryableError(fmt.Errorf("error creating subuser: %s", err)) + if requestErr.Err != nil { + return resource.NonRetryableError( + fmt.Errorf("error creating subuser: %w", requestErr.Err), + ) } return nil } } -func resourceSendgridSubuserDelete(_ context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { +func resourceSendgridSubuserDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { c := m.(*sendgrid.Client) - if err := resource.Retry(d.Timeout(schema.TimeoutCreate), retrySubUserDeleteClient(c, d.Id())); err != nil { + if err := resource.RetryContext(ctx, d.Timeout(schema.TimeoutCreate), retrySubUserDeleteClient( + c, d.Id())); err != nil { return diag.FromErr(err) } diff --git a/sendgrid/resource_sendgrid_subuser_test.go b/sendgrid/resource_sendgrid_subuser_test.go index e393e39..fe471fe 100644 --- a/sendgrid/resource_sendgrid_subuser_test.go +++ b/sendgrid/resource_sendgrid_subuser_test.go @@ -1,4 +1,4 @@ -package sendgrid +package sendgrid_test import ( "fmt" @@ -41,9 +41,9 @@ func testAccCheckSendgridSubuserDestroy(s *terraform.State) error { SubuserName := rs.Primary.ID - _, err := c.DeleteSubuser(SubuserName) - if err != nil { - return err + _, requestErr := c.DeleteSubuser(SubuserName) + if requestErr.Err != nil { + return requestErr.Err } } @@ -70,7 +70,7 @@ func testAccCheckSendgridSubuserExists(n string) resource.TestCheckFunc { } if rs.Primary.ID == "" { - return fmt.Errorf("No SubuserName set") + return fmt.Errorf("No SubUserName set") } return nil diff --git a/sendgrid/resource_sendgrid_template_test.go b/sendgrid/resource_sendgrid_template_test.go index bd71479..a7d9c21 100644 --- a/sendgrid/resource_sendgrid_template_test.go +++ b/sendgrid/resource_sendgrid_template_test.go @@ -1,4 +1,4 @@ -package sendgrid +package sendgrid_test import ( "fmt" diff --git a/sendgrid/resource_sendgrid_template_version.go b/sendgrid/resource_sendgrid_template_version.go index 4cd2f64..5f4e945 100644 --- a/sendgrid/resource_sendgrid_template_version.go +++ b/sendgrid/resource_sendgrid_template_version.go @@ -26,7 +26,6 @@ package sendgrid import ( "context" - "fmt" "reflect" "strings" @@ -36,6 +35,8 @@ import ( sendgrid "github.com/trois-six/terraform-provider-sendgrid/sdk" ) +const ImportSplitParts = 2 + func resourceSendgridTemplateVersion() *schema.Resource { return &schema.Resource{ CreateContext: resourceSendgridTemplateVersionCreate, @@ -63,9 +64,11 @@ func resourceSendgridTemplateVersion() *schema.Resource { Computed: true, }, "active": { - Type: schema.TypeInt, - Description: "Set the version as the active version associated with the template. Only one version of a template can be active. The first version created for a template will automatically be set to Active. Allowed values: 0, 1.", - Optional: true, + Type: schema.TypeInt, + Description: `Set the version as the active version associated with the template. + Only one version of a template can be active. + The first version created for a template will automatically be set to Active. Allowed values: 0, 1.`, + Optional: true, }, "name": { Type: schema.TypeString, @@ -83,10 +86,11 @@ func resourceSendgridTemplateVersion() *schema.Resource { Computed: true, }, "generate_plain_content": { - Type: schema.TypeBool, - Description: "If true (default), plain_content is always generated from html_content. If false, plain_content is not altered.", - Optional: true, - Default: true, + Type: schema.TypeBool, + Description: `If true (default), plain_content is always generated from html_content. + If false, plain_content is not altered.`, + Optional: true, + Default: true, }, "subject": { Type: schema.TypeString, @@ -101,9 +105,10 @@ func resourceSendgridTemplateVersion() *schema.Resource { ValidateFunc: validation.StringInSlice([]string{"code", "design"}, false), }, "test_data": { - Type: schema.TypeString, - Description: "For dynamic templates only, the mock json data that will be used for template preview and test sends.", - Optional: true, + Type: schema.TypeString, + Description: `For dynamic templates only, + the mock json data that will be used for template preview and test sends.`, + Optional: true, }, }, } @@ -165,7 +170,11 @@ func resourceSendgridTemplateVersionRead(_ context.Context, d *schema.ResourceDa return nil } -func resourceSendgridTemplateVersionUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { +func resourceSendgridTemplateVersionUpdate( + ctx context.Context, + d *schema.ResourceData, + m interface{}, +) diag.Diagnostics { c := m.(*sendgrid.Client) baseTemplateVersion := sendgrid.TemplateVersion{ @@ -177,21 +186,27 @@ func resourceSendgridTemplateVersionUpdate(ctx context.Context, d *schema.Resour if d.HasChange("active") { templateVersion.Active = d.Get("active").(int) } + if d.HasChange("name") { templateVersion.Name = d.Get("name").(string) } + if d.HasChange("html_content") { templateVersion.HTMLContent = d.Get("html_content").(string) } + if d.HasChange("generate_plain_content") { templateVersion.GeneratePlainContent = d.Get("generate_plain_content").(bool) } + if d.HasChange("subject") { templateVersion.Subject = d.Get("subject").(string) } + if d.HasChange("editor") { templateVersion.Editor = d.Get("editor").(string) } + if d.HasChange("test_data") { templateVersion.TestData = d.Get("test_data").(string) } @@ -200,8 +215,7 @@ func resourceSendgridTemplateVersionUpdate(ctx context.Context, d *schema.Resour return nil } - _, err := c.UpdateTemplateVersion(templateVersion) - if err != nil { + if _, err := c.UpdateTemplateVersion(templateVersion); err != nil { return diag.FromErr(err) } @@ -219,10 +233,14 @@ func resourceSendgridTemplateVersionDelete(_ context.Context, d *schema.Resource return nil } -func resourceSendgridTemplateVersionImport(ctx context.Context, d *schema.ResourceData, _ interface{}) ([]*schema.ResourceData, error) { +func resourceSendgridTemplateVersionImport( + ctx context.Context, + d *schema.ResourceData, + _ interface{}, +) ([]*schema.ResourceData, error) { parts := strings.Split(d.Id(), "/") - if len(parts) != 2 { - return nil, fmt.Errorf("Invalid import. Supported import format: {{templateID}}/{{templateVersionID}}") + if len(parts) != ImportSplitParts { + return nil, ErrInvalidImportFormat } //nolint:errcheck diff --git a/sendgrid/resource_sendgrid_template_version_test.go b/sendgrid/resource_sendgrid_template_version_test.go index 9569a8d..be5f222 100644 --- a/sendgrid/resource_sendgrid_template_version_test.go +++ b/sendgrid/resource_sendgrid_template_version_test.go @@ -1,4 +1,4 @@ -package sendgrid +package sendgrid_test import ( "fmt" @@ -21,7 +21,11 @@ func TestAccSendgridTemplateVersionBasic(t *testing.T) { CheckDestroy: testAccCheckSendgridTemplateVersionDestroy, Steps: []resource.TestStep{ { - Config: testAccCheckSendgridTemplateVersionConfigBasic(templateName, templateVersionName, subject), + Config: testAccCheckSendgridTemplateVersionConfigBasic( + templateName, + templateVersionName, + subject, + ), Check: resource.ComposeTestCheckFunc( testAccCheckSendgridTemplateVersionExists("sendgrid_template_version.new"), ), @@ -50,7 +54,9 @@ func testAccCheckSendgridTemplateVersionDestroy(s *terraform.State) error { return nil } -func testAccCheckSendgridTemplateVersionConfigBasic(templateName, templateVersionName, subject string) string { +func testAccCheckSendgridTemplateVersionConfigBasic( + templateName, templateVersionName, subject string, +) string { return fmt.Sprintf(` resource "sendgrid_template" "template" { name = %s