2021-06-25 14:06:31 -04:00
// Copyright (c) HashiCorp, Inc.
2020-09-08 13:27:51 -07:00
// SPDX-License-Identifier: BUSL-1.1
2023-02-02 17:15:17 -08:00
2020-05-04 11:23:35 -04:00
package main
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"path/filepath"
2020-08-07 12:55:02 -04:00
"sort"
"strings"
2020-05-04 11:23:35 -04:00
"text/template"
2020-08-07 12:55:02 -04:00
2022-09-08 13:19:21 -04:00
"github.com/hashicorp/go-secure-stdlib/strutil"
2020-08-07 12:55:02 -04:00
"github.com/iancoleman/strcase"
2020-05-04 11:23:35 -04:00
)
2021-06-22 22:26:26 -04:00
func getArgsAndPaths ( pluralResource , parentTypeName , action string ) ( colArg , colPath , resPath string ) {
2020-09-08 13:27:51 -07:00
strToReplace := "scope"
2021-06-22 22:26:26 -04:00
if parentTypeName != "" {
strToReplace = parentTypeName
2020-09-02 12:31:30 -07:00
}
2021-06-22 22:26:26 -04:00
2020-09-08 13:27:51 -07:00
colArg = fmt . Sprintf ( "%sId" , strcase . ToLowerCamel ( strings . ReplaceAll ( strToReplace , "-" , "_" )))
2021-06-22 22:26:26 -04:00
colPath = pluralResource
// append s at the end only if it isn't already present
if colPath [ len ( colPath ) - 1 ] != 's' {
colPath = fmt . Sprintf ( "%ss" , colPath )
}
2020-09-02 12:31:30 -07:00
if action != "" {
action = fmt . Sprintf ( ":%s" , action )
}
2022-04-27 08:13:46 -07:00
resPath = fmt . Sprintf ( "fmt.Sprintf(\"%s/%%s%s\", url.PathEscape(id))" , colPath , action )
2020-09-02 12:31:30 -07:00
return
}
2020-08-07 12:55:02 -04:00
type templateInput struct {
2020-09-08 13:27:51 -07:00
Name string
Package string
Fields [] fieldInfo
2021-06-22 22:26:26 -04:00
PluralResourceName string
2020-09-08 13:27:51 -07:00
CollectionFunctionArg string
ResourceFunctionArg string
CollectionPath string
ResourcePath string
ParentTypeName string
2021-06-25 14:06:31 -04:00
SliceSubtypes map [ string ] sliceSubtypeInfo
2021-09-28 09:40:12 -04:00
ExtraFields [] fieldInfo
2020-09-08 13:27:51 -07:00
VersionEnabled bool
2022-09-08 13:19:21 -04:00
CreateResponseTypes [] string
2023-05-24 15:05:28 -07:00
SkipListFiltering bool
2021-01-23 13:39:04 -05:00
RecursiveListing bool
2023-06-28 09:55:29 -07:00
Subtype string
2020-08-07 12:55:02 -04:00
}
2020-05-04 11:23:35 -04:00
2020-08-07 12:55:02 -04:00
func fillTemplates () {
optionsMap := map [ string ] map [ string ] fieldInfo {}
2021-01-23 13:39:04 -05:00
inputMap := map [ string ] * structInfo {}
2020-08-07 12:55:02 -04:00
for _ , in := range inputStructs {
2021-01-23 13:39:04 -05:00
inputMap [ in . generatedStructure . pkg ] = in
2020-05-04 11:23:35 -04:00
outBuf := new ( bytes . Buffer )
2020-08-07 12:55:02 -04:00
input := templateInput {
2020-09-15 01:08:21 -04:00
Name : in . generatedStructure . name ,
Package : in . generatedStructure . pkg ,
Fields : in . generatedStructure . fields ,
2021-06-22 22:26:26 -04:00
PluralResourceName : in . pluralResourceName ,
2020-09-15 01:08:21 -04:00
ParentTypeName : in . parentTypeName ,
2021-09-28 09:40:12 -04:00
ExtraFields : in . extraFields ,
2020-09-15 01:08:21 -04:00
VersionEnabled : in . versionEnabled ,
CreateResponseTypes : in . createResponseTypes ,
2023-05-24 15:05:28 -07:00
SkipListFiltering : in . skipListFiltering ,
2021-01-23 13:39:04 -05:00
RecursiveListing : in . recursiveListing ,
2023-06-28 09:55:29 -07:00
Subtype : in . subtype ,
2020-08-07 12:55:02 -04:00
}
2021-06-25 14:06:31 -04:00
if in . packageOverride != "" {
input . Package = in . packageOverride
}
if in . nameOverride != "" {
input . Name = in . nameOverride
}
2020-08-07 12:55:02 -04:00
2021-06-22 22:26:26 -04:00
if len ( in . pluralResourceName ) > 0 {
input . CollectionFunctionArg , input . CollectionPath , input . ResourcePath = getArgsAndPaths ( in . pluralResourceName , in . parentTypeName , "" )
2020-08-07 12:55:02 -04:00
}
2021-06-25 14:06:31 -04:00
for _ , override := range in . fieldOverrides {
for i , field := range in . generatedStructure . fields {
if field . Name == override . Name {
if override . FieldType != "" {
field . FieldType = override . FieldType
}
2022-09-26 15:27:28 +01:00
if len ( override . JsonTags ) != 0 {
field . JsonTags = override . JsonTags
}
2021-06-25 14:06:31 -04:00
in . generatedStructure . fields [ i ] = field
}
}
}
2020-08-07 12:55:02 -04:00
if err := structTemplate . Execute ( outBuf , input ); err != nil {
fmt . Printf ( "error executing struct template for resource %s: %v\n" , in . generatedStructure . name , err )
os . Exit ( 1 )
}
2021-05-17 15:36:45 -04:00
if len ( in . sliceSubtypes ) > 0 {
input . SliceSubtypes = in . sliceSubtypes
in . templates = append ( in . templates , sliceSubtypeTemplate )
2020-08-07 12:55:02 -04:00
}
for _ , t := range in . templates {
if err := t . Execute ( outBuf , input ); err != nil {
fmt . Printf ( "error executing function template for resource %s: %v\n" , in . generatedStructure . name , err )
os . Exit ( 1 )
}
}
// We want to generate options per-package, not per-struct, so we
// collate them all here for writing later. The map argument of the
// package map is to prevent duplicates since we may have multiple e.g.
// Name or Description fields.
2021-06-25 14:06:31 -04:00
if ! in . skipOptions {
2020-08-07 12:55:02 -04:00
pkgOptionMap := map [ string ] fieldInfo {}
for _ , val := range input . Fields {
if val . GenerateSdkOption {
2021-11-17 20:08:18 -05:00
val . SubtypeNames = append ( val . SubtypeNames , in . subtypeName )
2020-08-07 12:55:02 -04:00
pkgOptionMap [ val . Name ] = val
}
}
optionMap := optionsMap [ input . Package ]
if optionMap == nil {
optionMap = map [ string ] fieldInfo {}
}
for name , val := range pkgOptionMap {
2021-11-17 20:08:18 -05:00
val . SubtypeNames = append ( val . SubtypeNames , optionMap [ name ]. SubtypeNames ... )
2020-08-07 12:55:02 -04:00
optionMap [ name ] = val
}
optionsMap [ input . Package ] = optionMap
}
2021-09-28 09:40:12 -04:00
// Add in extra defined fields
if len ( in . extraFields ) > 0 {
2020-08-26 23:27:25 -04:00
optionMap := optionsMap [ input . Package ]
if optionMap == nil {
optionMap = map [ string ] fieldInfo {}
}
2021-09-28 09:40:12 -04:00
for _ , val := range in . extraFields {
2020-08-26 23:27:25 -04:00
optionMap [ val . Name ] = val
}
optionsMap [ input . Package ] = optionMap
}
2021-06-02 13:05:10 -05:00
// Override some defined options
if len ( in . fieldOverrides ) > 0 && optionsMap != nil {
for _ , override := range in . fieldOverrides {
inOpts := optionsMap [ input . Package ]
if inOpts != nil {
if override . SkipDefault {
fieldInfo := inOpts [ override . Name ]
fieldInfo . SkipDefault = true
inOpts [ override . Name ] = fieldInfo
}
}
}
}
2020-08-07 12:55:02 -04:00
outFile , err := filepath . Abs ( fmt . Sprintf ( "%s/%s" , os . Getenv ( "API_GEN_BASEPATH" ), in . outFile ))
if err != nil {
fmt . Printf ( "error opening file %q: %v\n" , in . outFile , err )
os . Exit ( 1 )
}
outDir := filepath . Dir ( outFile )
if _ , err := os . Stat ( outDir ); os . IsNotExist ( err ) {
_ = os . Mkdir ( outDir , os . ModePerm )
}
2021-01-07 10:02:32 -05:00
if err := ioutil . WriteFile ( outFile , outBuf . Bytes (), 0 o644 ); err != nil {
2020-08-07 12:55:02 -04:00
fmt . Printf ( "error writing file %q: %v\n" , outFile , err )
os . Exit ( 1 )
}
}
// Now reconstruct options per package and write them out
for pkg , options := range optionsMap {
outBuf := new ( bytes . Buffer )
var fieldNames [] string
for _ , v := range options {
fieldNames = append ( fieldNames , v . Name )
}
sort . Strings ( fieldNames )
var fields [] fieldInfo
for _ , v := range fieldNames {
fields = append ( fields , options [ v ])
}
input := templateInput {
2023-05-24 15:05:28 -07:00
Package : pkg ,
Fields : fields ,
SkipListFiltering : inputMap [ pkg ]. skipListFiltering ,
RecursiveListing : inputMap [ pkg ]. recursiveListing ,
VersionEnabled : inputMap [ pkg ]. versionEnabled ,
2020-08-07 12:55:02 -04:00
}
if err := optionTemplate . Execute ( outBuf , input ); err != nil {
fmt . Printf ( "error executing option template for package %s: %v\n" , pkg , err )
os . Exit ( 1 )
}
outFile , err := filepath . Abs ( fmt . Sprintf ( "%s/%s/%s" , os . Getenv ( "API_GEN_BASEPATH" ), pkg , "option.gen.go" ))
2020-05-04 11:23:35 -04:00
if err != nil {
2020-08-07 12:55:02 -04:00
fmt . Printf ( "error opening file %q: %v\n" , "option.gen.go" , err )
2020-05-04 11:23:35 -04:00
os . Exit ( 1 )
}
2020-08-07 12:55:02 -04:00
outDir := filepath . Dir ( outFile )
if _ , err := os . Stat ( outDir ); os . IsNotExist ( err ) {
_ = os . Mkdir ( outDir , os . ModePerm )
}
2021-01-07 10:02:32 -05:00
if err := ioutil . WriteFile ( outFile , outBuf . Bytes (), 0 o644 ); err != nil {
2020-05-04 11:23:35 -04:00
fmt . Printf ( "error writing file %q: %v\n" , outFile , err )
os . Exit ( 1 )
}
}
}
2020-09-08 13:27:51 -07:00
var listTemplate = template . Must ( template . New ( "" ). Funcs (
2020-09-02 12:31:30 -07:00
template . FuncMap {
"snakeCase" : snakeCase ,
},
). Parse ( `
2020-09-24 14:34:58 -07:00
func (c *Client) List(ctx context.Context, {{ .CollectionFunctionArg }} string, opt... Option) (* {{ .Name }} ListResult, error) {
2020-09-08 13:27:51 -07:00
if {{ .CollectionFunctionArg }} == "" {
2020-09-24 14:34:58 -07:00
return nil, fmt.Errorf("empty {{ .CollectionFunctionArg }} value passed into List request")
2020-09-02 12:31:30 -07:00
}
if c.client == nil {
2020-09-24 14:34:58 -07:00
return nil, fmt.Errorf("nil client")
2020-09-02 12:31:30 -07:00
}
opts, apiOpts := getOpts(opt...)
2020-09-08 13:27:51 -07:00
opts.queryMap[" {{ snakeCase .CollectionFunctionArg }} "] = {{ .CollectionFunctionArg }}
2020-09-02 12:31:30 -07:00
2020-09-08 13:27:51 -07:00
req, err := c.client.NewRequest(ctx, "GET", " {{ .CollectionPath }} ", nil, apiOpts...)
2020-09-02 12:31:30 -07:00
if err != nil {
2020-09-24 14:34:58 -07:00
return nil, fmt.Errorf("error creating List request: %w", err)
2020-09-02 12:31:30 -07:00
}
2021-01-23 13:39:04 -05:00
2020-09-02 12:31:30 -07:00
if len(opts.queryMap) > 0 {
q := url.Values{}
for k, v := range opts.queryMap {
q.Add(k, v)
}
req.URL.RawQuery = q.Encode()
}
resp, err := c.client.Do(req)
if err != nil {
2020-09-24 14:34:58 -07:00
return nil, fmt.Errorf("error performing client request during List call: %w", err)
2020-09-02 12:31:30 -07:00
}
2020-09-15 01:08:21 -04:00
target := new( {{ .Name }} ListResult)
2020-09-02 12:31:30 -07:00
apiErr, err := resp.Decode(target)
if err != nil {
2020-09-24 14:34:58 -07:00
return nil, fmt.Errorf("error decoding List response: %w", err)
2020-09-02 12:31:30 -07:00
}
if apiErr != nil {
2020-09-24 14:34:58 -07:00
return nil, apiErr
2020-09-02 12:31:30 -07:00
}
2020-12-07 14:25:16 -07:00
target.response = resp
2020-09-24 14:34:58 -07:00
return target, nil
2020-09-02 12:31:30 -07:00
}
` ))
2020-08-07 12:55:02 -04:00
var readTemplate = template . Must ( template . New ( "" ). Parse ( `
2021-06-22 22:26:26 -04:00
func (c *Client) Read(ctx context.Context, id string, opt... Option) (* {{ .Name }} ReadResult, error) {
if id == "" {
return nil, fmt.Errorf("empty id value passed into Read request")
2020-08-07 12:55:02 -04:00
}
if c.client == nil {
2020-09-24 14:34:58 -07:00
return nil, fmt.Errorf("nil client")
2020-08-07 12:55:02 -04:00
}
2020-08-26 23:27:25 -04:00
opts, apiOpts := getOpts(opt...)
2020-08-07 12:55:02 -04:00
req, err := c.client.NewRequest(ctx, "GET", {{ .ResourcePath }} , nil, apiOpts...)
if err != nil {
2020-09-24 14:34:58 -07:00
return nil, fmt.Errorf("error creating Read request: %w", err)
2020-08-07 12:55:02 -04:00
}
2020-08-26 23:27:25 -04:00
if len(opts.queryMap) > 0 {
q := url.Values{}
for k, v := range opts.queryMap {
q.Add(k, v)
}
req.URL.RawQuery = q.Encode()
}
2021-01-08 17:30:41 -05:00
resp, err := c.client.Do(req, apiOpts...)
2020-08-07 12:55:02 -04:00
if err != nil {
2020-09-24 14:34:58 -07:00
return nil, fmt.Errorf("error performing client request during Read call: %w", err)
2020-08-07 12:55:02 -04:00
}
2020-09-15 01:08:21 -04:00
target := new( {{ .Name }} ReadResult)
target.Item = new( {{ .Name }} )
apiErr, err := resp.Decode(target.Item)
2020-08-07 12:55:02 -04:00
if err != nil {
2020-09-24 14:34:58 -07:00
return nil, fmt.Errorf("error decoding Read response: %w", err)
2020-08-07 12:55:02 -04:00
}
2020-08-20 17:31:38 -04:00
if apiErr != nil {
2020-09-24 14:34:58 -07:00
return nil, apiErr
2020-08-20 17:31:38 -04:00
}
2020-12-07 14:25:16 -07:00
target.response = resp
2020-09-24 14:34:58 -07:00
return target, nil
2020-05-04 11:23:35 -04:00
}
2020-08-07 12:55:02 -04:00
` ))
2020-05-04 11:23:35 -04:00
2020-08-07 12:55:02 -04:00
var deleteTemplate = template . Must ( template . New ( "" ). Parse ( `
2021-06-22 22:26:26 -04:00
func (c *Client) Delete(ctx context.Context, id string, opt... Option) (* {{ .Name }} DeleteResult, error) {
if id == "" {
return nil, fmt.Errorf("empty id value passed into Delete request")
2020-08-07 12:55:02 -04:00
}
if c.client == nil {
2020-09-24 14:34:58 -07:00
return nil, fmt.Errorf("nil client")
2020-08-07 12:55:02 -04:00
}
2020-08-26 23:27:25 -04:00
opts, apiOpts := getOpts(opt...)
2020-08-07 12:55:02 -04:00
req, err := c.client.NewRequest(ctx, "DELETE", {{ .ResourcePath }} , nil, apiOpts...)
if err != nil {
2020-09-24 14:34:58 -07:00
return nil, fmt.Errorf("error creating Delete request: %w", err)
2020-08-07 12:55:02 -04:00
}
2020-08-26 23:27:25 -04:00
if len(opts.queryMap) > 0 {
q := url.Values{}
for k, v := range opts.queryMap {
q.Add(k, v)
}
req.URL.RawQuery = q.Encode()
}
2020-08-07 12:55:02 -04:00
resp, err := c.client.Do(req)
if err != nil {
2020-09-24 14:34:58 -07:00
return nil, fmt.Errorf("error performing client request during Delete call: %w", err)
2020-08-07 12:55:02 -04:00
}
2020-09-08 23:57:26 -04:00
apiErr, err := resp.Decode(nil)
2020-08-07 12:55:02 -04:00
if err != nil {
2020-09-24 14:34:58 -07:00
return nil, fmt.Errorf("error decoding Delete response: %w", err)
2020-08-07 12:55:02 -04:00
}
2020-08-20 17:31:38 -04:00
if apiErr != nil {
2020-09-24 14:34:58 -07:00
return nil, apiErr
2020-09-15 01:08:21 -04:00
}
target := & {{ .Name }} DeleteResult{
2020-12-07 14:25:16 -07:00
response: resp,
2020-08-20 17:31:38 -04:00
}
2020-09-24 14:34:58 -07:00
return target, nil
2020-08-07 12:55:02 -04:00
}
` ))
2022-06-09 14:30:33 -04:00
const createTemplateStr = `
2022-09-02 14:32:41 -04:00
func (c *Client) {{ funcName }} (ctx context.Context, {{ range extraRequiredParams }} {{ .Name }} {{ .Typ }} , {{ end }} {{ .CollectionFunctionArg }} string, opt... Option) (* {{ .Name }} CreateResult, error) {
2020-09-08 13:27:51 -07:00
if {{ .CollectionFunctionArg }} == "" {
2022-06-09 14:30:33 -04:00
return nil, fmt.Errorf("empty {{ .CollectionFunctionArg }} value passed into {{ funcName }} request")
2020-09-02 12:31:30 -07:00
}
2020-09-08 13:27:51 -07:00
2020-09-02 12:31:30 -07:00
opts, apiOpts := getOpts(opt...)
2020-09-08 13:27:51 -07:00
2020-09-02 12:31:30 -07:00
if c.client == nil {
2020-09-24 14:34:58 -07:00
return nil, fmt.Errorf("nil client")
2020-09-02 12:31:30 -07:00
}
2022-09-02 14:32:41 -04:00
{{ range extraRequiredParams }} if {{ .Name }} == "" {
2022-06-09 14:30:33 -04:00
return nil, fmt.Errorf("empty {{ .Name }} value passed into {{ funcName }} request")
2020-09-02 12:31:30 -07:00
} else {
2022-06-09 14:30:33 -04:00
opts.postMap[" {{ .PostType }} "] = {{ .Name }}
2020-09-02 12:31:30 -07:00
} {{ end }}
2020-09-08 13:27:51 -07:00
opts.postMap[" {{ snakeCase .CollectionFunctionArg }} "] = {{ .CollectionFunctionArg }}
2020-09-02 12:31:30 -07:00
2022-06-09 14:30:33 -04:00
req, err := c.client.NewRequest(ctx, "POST", " {{ .CollectionPath }}{{ apiAction }} ", opts.postMap, apiOpts...)
2020-09-02 12:31:30 -07:00
if err != nil {
2022-06-09 14:30:33 -04:00
return nil, fmt.Errorf("error creating {{ funcName }} request: %w", err)
2020-09-02 12:31:30 -07:00
}
{{ if ( eq .CollectionPath "\"scopes\"" ) }}
opts.queryMap["scope_id"] = scopeId
{{ end }}
if len(opts.queryMap) > 0 {
q := url.Values{}
for k, v := range opts.queryMap {
q.Add(k, v)
}
req.URL.RawQuery = q.Encode()
}
resp, err := c.client.Do(req)
if err != nil {
2022-06-09 14:30:33 -04:00
return nil, fmt.Errorf("error performing client request during {{ funcName }} call: %w", err)
2020-09-02 12:31:30 -07:00
}
2020-09-15 01:08:21 -04:00
target := new( {{ .Name }} CreateResult)
target.Item = new( {{ .Name }} )
apiErr, err := resp.Decode(target.Item)
2020-09-02 12:31:30 -07:00
if err != nil {
2022-06-09 14:30:33 -04:00
return nil, fmt.Errorf("error decoding {{ funcName }} response: %w", err)
2020-09-02 12:31:30 -07:00
}
if apiErr != nil {
2020-09-24 14:34:58 -07:00
return nil, apiErr
2020-09-02 12:31:30 -07:00
}
2020-12-07 14:25:16 -07:00
target.response = resp
2020-09-24 14:34:58 -07:00
return target, nil
2020-09-02 12:31:30 -07:00
}
2022-06-09 14:30:33 -04:00
`
var commonCreateTemplate = template . Must ( template . New ( "" ). Funcs (
template . FuncMap {
"snakeCase" : snakeCase ,
"funcName" : func () string {
return "Create"
},
"apiAction" : func () string {
return ""
},
2022-09-02 14:32:41 -04:00
"extraRequiredParams" : func () [] requiredParam {
return nil
},
2022-06-09 14:30:33 -04:00
},
). Parse ( createTemplateStr ))
2020-09-02 12:31:30 -07:00
2020-08-07 12:55:02 -04:00
var updateTemplate = template . Must ( template . New ( "" ). Parse ( `
2021-06-22 22:26:26 -04:00
func (c *Client) Update(ctx context.Context, id string, version uint32, opt... Option) (* {{ .Name }} UpdateResult, error) {
if id == "" {
return nil, fmt.Errorf("empty id value passed into Update request")
2020-09-08 13:27:51 -07:00
}
2020-08-07 12:55:02 -04:00
if c.client == nil {
2020-09-24 14:34:58 -07:00
return nil, fmt.Errorf("nil client")
2020-08-07 12:55:02 -04:00
}
opts, apiOpts := getOpts(opt...)
2020-05-04 11:23:35 -04:00
2020-08-07 12:55:02 -04:00
{{ if .VersionEnabled }}
if version == 0 {
if !opts.withAutomaticVersioning {
2020-09-24 14:34:58 -07:00
return nil, errors.New("zero version number passed into Update request and automatic versioning not specified")
2020-08-07 12:55:02 -04:00
}
2021-06-22 22:26:26 -04:00
existingTarget, existingErr := c.Read(ctx, id, append([]Option{WithSkipCurlOutput(true)}, opt...)...)
2020-08-07 12:55:02 -04:00
if existingErr != nil {
2020-09-24 14:34:58 -07:00
if api.AsServerError(existingErr) != nil {
return nil, fmt.Errorf("error from controller when performing initial check-and-set read: %w", existingErr)
}
return nil, fmt.Errorf("error performing initial check-and-set read: %w", existingErr)
2020-08-07 12:55:02 -04:00
}
if existingTarget == nil {
2020-09-24 14:34:58 -07:00
return nil, errors.New("nil resource response found when performing initial check-and-set read")
2020-09-15 01:08:21 -04:00
}
if existingTarget.Item == nil {
2020-09-24 14:34:58 -07:00
return nil, errors.New("nil resource found when performing initial check-and-set read")
2020-08-07 12:55:02 -04:00
}
2020-09-15 01:08:21 -04:00
version = existingTarget.Item.Version
2020-07-14 14:32:47 -07:00
}
2020-08-07 12:55:02 -04:00
{{ end }}
2020-08-26 23:27:25 -04:00
opts.postMap["version"] = version
2020-08-18 17:38:38 -07:00
2020-08-26 23:27:25 -04:00
req, err := c.client.NewRequest(ctx, "PATCH", {{ .ResourcePath }} , opts.postMap, apiOpts...)
2020-08-07 12:55:02 -04:00
if err != nil {
2020-09-24 14:34:58 -07:00
return nil, fmt.Errorf("error creating Update request: %w", err)
2020-08-07 12:55:02 -04:00
}
2020-08-26 23:27:25 -04:00
if len(opts.queryMap) > 0 {
q := url.Values{}
for k, v := range opts.queryMap {
q.Add(k, v)
}
req.URL.RawQuery = q.Encode()
}
2020-08-07 12:55:02 -04:00
resp, err := c.client.Do(req)
if err != nil {
2020-09-24 14:34:58 -07:00
return nil, fmt.Errorf("error performing client request during Update call: %w", err)
2020-08-07 12:55:02 -04:00
}
2020-09-15 01:08:21 -04:00
target := new( {{ .Name }} UpdateResult)
target.Item = new( {{ .Name }} )
apiErr, err := resp.Decode(target.Item)
2020-08-07 12:55:02 -04:00
if err != nil {
2020-09-24 14:34:58 -07:00
return nil, fmt.Errorf("error decoding Update response: %w", err)
2020-08-07 12:55:02 -04:00
}
2020-08-20 17:31:38 -04:00
if apiErr != nil {
2020-09-24 14:34:58 -07:00
return nil, apiErr
2020-08-20 17:31:38 -04:00
}
2020-12-07 14:25:16 -07:00
target.response = resp
2020-09-24 14:34:58 -07:00
return target, nil
2020-05-04 11:23:35 -04:00
}
2020-08-07 12:55:02 -04:00
` ))
2021-05-17 15:36:45 -04:00
var sliceSubtypeTemplate = template . Must ( template . New ( "" ). Funcs (
2020-08-07 12:55:02 -04:00
template . FuncMap {
"makeSlice" : makeSlice ,
"snakeCase" : snakeCase ,
"kebabCase" : kebabCase ,
"getPathWithAction" : getPathWithAction ,
},
). Parse ( `
{{ $input := . }}
{{ range $index , $op := makeSlice "Add" "Set" "Remove" }}
2021-05-17 15:36:45 -04:00
{{ range $key , $value := $input .SliceSubtypes }}
2020-08-07 12:55:02 -04:00
{{ $fullName := print $op $key }}
{{ $actionName := kebabCase $fullName }}
2021-06-22 22:26:26 -04:00
{{ $resPath := getPathWithAction $input .PluralResourceName $input .ParentTypeName $actionName }}
2021-06-25 14:06:31 -04:00
func (c *Client) {{ $fullName }} (ctx context.Context, id string, version uint32, {{ if ( not ( eq $value .VarName "" ) ) }}{{ $value .VarName }} {{ $value .SliceType }} , {{ end }} opt... Option) (* {{ $input .Name }} UpdateResult, error) {
2021-06-22 22:26:26 -04:00
if id == "" {
return nil, fmt.Errorf("empty id value passed into {{ $fullName }} request")
2020-09-02 12:31:30 -07:00
}
2021-06-25 14:06:31 -04:00
{{ if ( not ( eq $op "Set" ) ) }}
{{ if ( not ( eq $value .VarName "" ) ) }}
if len( {{ $value .VarName }} ) == 0 {
return nil, errors.New("empty {{ $value .VarName }} passed into {{ $fullName }} request")
}
{{ end }}
{{ end }}
2020-09-02 12:31:30 -07:00
if c.client == nil {
2020-09-24 14:34:58 -07:00
return nil, errors.New("nil client")
2020-09-02 12:31:30 -07:00
}
opts, apiOpts := getOpts(opt...)
{{ if $input .VersionEnabled }}
if version == 0 {
if !opts.withAutomaticVersioning {
2020-09-24 14:34:58 -07:00
return nil, errors.New("zero version number passed into {{ $fullName }} request")
2020-09-02 12:31:30 -07:00
}
2021-06-22 22:26:26 -04:00
existingTarget, existingErr := c.Read(ctx, id, append([]Option{WithSkipCurlOutput(true)}, opt...)...)
2020-09-02 12:31:30 -07:00
if existingErr != nil {
2020-09-24 14:34:58 -07:00
if api.AsServerError(existingErr) != nil {
return nil, fmt.Errorf("error from controller when performing initial check-and-set read: %w", existingErr)
}
return nil, fmt.Errorf("error performing initial check-and-set read: %w", existingErr)
2020-09-02 12:31:30 -07:00
}
if existingTarget == nil {
2020-09-24 14:34:58 -07:00
return nil, errors.New("nil resource response found when performing initial check-and-set read")
2020-09-15 01:08:21 -04:00
}
if existingTarget.Item == nil {
2020-09-24 14:34:58 -07:00
return nil, errors.New("nil resource found when performing initial check-and-set read")
2020-09-02 12:31:30 -07:00
}
2020-09-15 01:08:21 -04:00
version = existingTarget.Item.Version
2020-09-02 12:31:30 -07:00
}
{{ end }}
opts.postMap["version"] = version
2021-06-25 14:06:31 -04:00
{{ if ( not ( eq $value .VarName "" ) ) }}
opts.postMap[" {{ snakeCase $value .VarName }} "] = {{ $value .VarName }}
{{ end }}
2020-09-02 12:31:30 -07:00
req, err := c.client.NewRequest(ctx, "POST", {{ $resPath }} , opts.postMap, apiOpts...)
if err != nil {
2020-09-24 14:34:58 -07:00
return nil, fmt.Errorf("error creating {{ $fullName }} request: %w", err)
2020-09-02 12:31:30 -07:00
}
if len(opts.queryMap) > 0 {
q := url.Values{}
for k, v := range opts.queryMap {
q.Add(k, v)
}
req.URL.RawQuery = q.Encode()
}
resp, err := c.client.Do(req)
if err != nil {
2020-09-24 14:34:58 -07:00
return nil, fmt.Errorf("error performing client request during {{ $fullName }} call: %w", err)
2020-09-02 12:31:30 -07:00
}
2020-09-15 01:08:21 -04:00
target := new( {{ $input .Name }} UpdateResult)
target.Item = new( {{ $input .Name }} )
apiErr, err := resp.Decode(target.Item)
2020-09-02 12:31:30 -07:00
if err != nil {
2020-09-24 14:34:58 -07:00
return nil, fmt.Errorf("error decoding {{ $fullName }} response: %w", err)
2020-09-02 12:31:30 -07:00
}
if apiErr != nil {
2020-09-24 14:34:58 -07:00
return nil, apiErr
2020-09-02 12:31:30 -07:00
}
2020-12-07 14:25:16 -07:00
target.response = resp
2020-09-24 14:34:58 -07:00
return target, nil
2020-09-02 12:31:30 -07:00
}
{{ end }}
{{ end }}
` ))
2022-09-08 13:19:21 -04:00
func hasResponseType ( types [] string , typ string ) bool {
return strutil . StrListContains ( types , typ )
}
var structTemplate = template . Must ( template . New ( "" ). Funcs (
template . FuncMap {
"hasResponseType" : hasResponseType ,
2022-09-26 15:27:28 +01:00
"stringsjoin" : strings . Join ,
2022-09-08 13:19:21 -04:00
},
). Parse (
2020-08-07 12:55:02 -04:00
fmt . Sprint ( `// Code generated by "make api"; DO NOT EDIT.
2023-05-09 12:03:54 -04:00
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
2020-05-04 11:23:35 -04:00
package {{ .Package }}
import (
2020-08-07 12:55:02 -04:00
"context"
2020-05-04 11:23:35 -04:00
"fmt"
2020-08-07 12:55:02 -04:00
"time"
2020-05-04 11:23:35 -04:00
2020-08-07 12:55:02 -04:00
"github.com/kr/pretty"
2020-05-04 22:59:45 -04:00
2020-08-10 17:15:02 -04:00
"github.com/hashicorp/boundary/api"
"github.com/hashicorp/boundary/api/scopes"
2020-05-04 11:23:35 -04:00
)
2020-08-07 12:55:02 -04:00
type {{ .Name }} struct { {{ range .Fields }}
2022-09-26 15:27:28 +01:00
{{ .Name }} {{ .FieldType }} ` , "`json:\"{{ .ProtoName }}{{ if ( ne ( len ( .JsonTags ) ) 0 ) }},{{ stringsjoin .JsonTags \",\" }}{{ end }},omitempty\"`" , ` {{ end }}
2022-09-08 13:19:21 -04:00
{{ if ( not ( eq ( len ( .CreateResponseTypes ) ) 0 ) ) }}
2020-12-07 14:25:16 -07:00
response *api.Response
{{ else if ( eq .Name "Error" ) }}
response *Response
2020-09-15 01:08:21 -04:00
{{ end }}
}
2022-09-08 13:19:21 -04:00
{{ if ( hasResponseType .CreateResponseTypes "read" ) }}
2020-09-15 01:08:21 -04:00
type {{ .Name }} ReadResult struct {
Item * {{ .Name }}
2020-12-07 14:25:16 -07:00
response *api.Response
2020-09-15 01:08:21 -04:00
}
2022-07-21 15:21:31 -07:00
func (n {{ .Name }} ReadResult) GetItem() * {{ .Name }} {
2020-09-15 01:08:21 -04:00
return n.Item
}
2021-02-25 12:43:17 -05:00
func (n {{ .Name }} ReadResult) GetResponse() *api.Response {
return n.response
2020-09-15 01:08:21 -04:00
}
2022-09-08 13:19:21 -04:00
{{ end }}
{{ if ( hasResponseType .CreateResponseTypes "create" ) }} type {{ .Name }} CreateResult = {{ .Name }} ReadResult {{ end }}
{{ if ( hasResponseType .CreateResponseTypes "update" ) }} type {{ .Name }} UpdateResult = {{ .Name }} ReadResult {{ end }}
2020-09-15 01:08:21 -04:00
2022-09-08 13:19:21 -04:00
{{ if ( hasResponseType .CreateResponseTypes "delete" ) }}
2020-09-15 01:08:21 -04:00
type {{ .Name }} DeleteResult struct {
2020-12-07 14:25:16 -07:00
response *api.Response
2020-09-11 00:51:55 -04:00
}
2021-04-26 19:38:55 -05:00
// GetItem will always be nil for {{ .Name }} DeleteResult
func (n {{ .Name }} DeleteResult) GetItem() interface{} {
return nil
}
2021-02-25 12:43:17 -05:00
func (n {{ .Name }} DeleteResult) GetResponse() *api.Response {
return n.response
2020-08-07 12:55:02 -04:00
}
2022-09-08 13:19:21 -04:00
{{ end }}
{{ if ( hasResponseType .CreateResponseTypes "list" ) }}
2020-09-15 01:08:21 -04:00
type {{ .Name }} ListResult struct {
Items []* {{ .Name }}
2020-12-07 14:25:16 -07:00
response *api.Response
2020-09-15 01:08:21 -04:00
}
2022-07-21 15:21:31 -07:00
func (n {{ .Name }} ListResult) GetItems() []* {{ .Name }} {
2020-09-15 01:08:21 -04:00
return n.Items
}
2021-02-25 12:43:17 -05:00
func (n {{ .Name }} ListResult) GetResponse() *api.Response {
return n.response
2020-09-15 01:08:21 -04:00
}
{{ end }}
2020-08-07 12:55:02 -04:00
` )))
2020-05-04 22:59:45 -04:00
2020-08-07 12:55:02 -04:00
var clientTemplate = template . Must ( template . New ( "" ). Parse ( `
2020-09-08 13:27:51 -07:00
// Client is a client for this collection
2020-08-27 20:43:33 -04:00
type Client struct {
2020-08-07 12:55:02 -04:00
client *api.Client
2020-05-04 11:23:35 -04:00
}
2020-09-08 13:27:51 -07:00
// Creates a new client for this collection. The submitted API client is cloned;
// modifications to it after generating this client will not have effect. If you
// need to make changes to the underlying API client, use ApiClient() to access
// it.
2020-08-27 20:43:33 -04:00
func NewClient(c *api.Client) *Client {
2020-09-08 13:27:51 -07:00
return &Client{ client: c.Clone() }
}
// ApiClient returns the underlying API client
func (c *Client) ApiClient() *api.Client {
return c.client
2020-08-07 12:55:02 -04:00
}
` ))
2021-11-17 20:08:18 -05:00
var optionTemplate = template . Must ( template . New ( "" ). Funcs (
template . FuncMap {
"makeSlice" : makeSlice ,
"removeDups" : removeDups ,
},
2023-05-09 12:03:54 -04:00
). Parse ( `// Code generated by "make api"; DO NOT EDIT.
2023-05-09 11:12:03 -04:00
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
2020-08-07 12:55:02 -04:00
package {{ .Package }}
import (
2021-01-23 13:39:04 -05:00
"strconv"
2020-08-07 12:55:02 -04:00
2020-08-10 17:15:02 -04:00
"github.com/hashicorp/boundary/api"
2020-08-07 12:55:02 -04:00
)
// Option is a func that sets optional attributes for a call. This does not need
// to be used directly, but instead option arguments are built from the
// functions in this package. WithX options set a value to that given in the
// argument; DefaultX options indicate that the value should be set to its
// default. When an API call is made options are processed in ther order they
// appear in the function call, so for a given argument X, a succession of WithX
// or DefaultX calls will result in the last call taking effect.
type Option func(*options)
type options struct {
2020-08-26 23:27:25 -04:00
postMap map[string]interface{}
queryMap map[string]string
2020-08-07 12:55:02 -04:00
withAutomaticVersioning bool
2021-01-08 17:30:41 -05:00
withSkipCurlOutput bool
2021-02-22 13:31:17 -07:00
withFilter string
2021-01-23 13:39:04 -05:00
{{ if .RecursiveListing }} withRecursive bool {{ end }}
2020-08-07 12:55:02 -04:00
}
func getDefaultOptions() options {
return options{
2020-08-26 23:27:25 -04:00
postMap: make(map[string]interface{}),
queryMap: make(map[string]string),
2020-05-04 11:23:35 -04:00
}
2020-08-07 12:55:02 -04:00
}
func getOpts(opt ...Option) (options, []api.Option) {
opts := getDefaultOptions()
for _, o := range opt {
2021-04-08 20:54:32 -04:00
if o != nil {
o(&opts)
}
2020-05-04 11:23:35 -04:00
}
2020-08-07 12:55:02 -04:00
var apiOpts []api.Option
2021-01-08 17:30:41 -05:00
if opts.withSkipCurlOutput {
apiOpts = append(apiOpts, api.WithSkipCurlOutput(true))
2021-02-22 13:31:17 -07:00
}
if opts.withFilter != "" {
opts.queryMap["filter"] = opts.withFilter
} {{ if .RecursiveListing }}
2021-01-23 13:39:04 -05:00
if opts.withRecursive {
opts.queryMap["recursive"] = strconv.FormatBool(opts.withRecursive)
} {{ end }}
2020-08-07 12:55:02 -04:00
return opts, apiOpts
}
2023-05-09 11:12:03 -04:00
{{ if .VersionEnabled }}
2020-08-07 12:55:02 -04:00
// If set, and if the version is zero during an update, the API will perform a
// fetch to get the current version of the resource and populate it during the
// update call. This is convenient but opens up the possibility for subtle
// order-of-modification issues, so use carefully.
2020-09-08 13:27:51 -07:00
func WithAutomaticVersioning(enable bool) Option {
2020-08-07 12:55:02 -04:00
return func(o *options) {
2020-09-08 13:27:51 -07:00
o.withAutomaticVersioning = enable
2020-05-04 11:23:35 -04:00
}
2020-08-07 12:55:02 -04:00
}
2023-05-09 11:12:03 -04:00
{{ end }}
2021-01-08 17:30:41 -05:00
// WithSkipCurlOutput tells the API to not use the current call for cURL output.
// Useful for when we need to look up versions.
func WithSkipCurlOutput(skip bool) Option {
return func(o *options) {
o.withSkipCurlOutput = true
}
}
2021-02-22 13:31:17 -07:00
2023-05-24 15:05:28 -07:00
{{ if not .SkipListFiltering }}
2021-02-22 13:31:17 -07:00
// WithFilter tells the API to filter the items returned using the provided
// filter term. The filter should be in a format supported by
// hashicorp/go-bexpr.
func WithFilter(filter string) Option {
return func(o *options) {
o.withFilter = strings.TrimSpace(filter)
}
}
2023-05-24 15:05:28 -07:00
{{ end }}
2021-01-23 13:39:04 -05:00
{{ if .RecursiveListing }}
// WithRecursive tells the API to use recursion for listing operations on this
// resource
func WithRecursive(recurse bool) Option {
return func(o *options) {
o.withRecursive = true
}
}
{{ end }}
2021-11-17 20:08:18 -05:00
{{ range $fieldIndex , $field := .Fields }}
{{ $subtypes := ( removeDups $field .SubtypeNames ) }}
{{ if ( eq ( len ( $subtypes ) ) 0 ) }}
{{ $subtypes = ( makeSlice "" ) }}
{{ end }}
{{ range $subtypeIndex , $subtypeName := $subtypes }}
func With {{ $subtypeName }}{{ $field .Name }} (in {{ $field .Name }} {{ $field .FieldType }} ) Option {
return func(o *options) { {{ if ( not ( eq $subtypeName "" ) ) }}
2020-08-26 23:27:25 -04:00
raw, ok := o.postMap["attributes"]
2020-08-07 12:55:02 -04:00
if !ok {
raw = interface{}(map[string]interface{}{})
}
val := raw.(map[string]interface{})
2021-11-17 20:08:18 -05:00
val[" {{ $field .ProtoName }} "] = in {{ $field .Name }}
2020-08-26 23:27:25 -04:00
o.postMap["attributes"] = val
2021-11-17 20:08:18 -05:00
{{ else if $field .Query }}
o.queryMap[" {{ $field .ProtoName }} "] = fmt.Sprintf("%v", in {{ $field .Name }} )
2020-08-07 12:55:02 -04:00
{{ else }}
2021-11-17 20:08:18 -05:00
o.postMap[" {{ $field .ProtoName }} "] = in {{ $field .Name }}
2020-08-07 12:55:02 -04:00
{{ end }} }
}
2021-11-17 20:08:18 -05:00
{{ if ( not $field .SkipDefault ) }}
func Default {{ $subtypeName }}{{ $field .Name }} () Option {
return func(o *options) { {{ if ( not ( eq $subtypeName "" ) ) }}
2020-08-26 23:27:25 -04:00
raw, ok := o.postMap["attributes"]
2020-08-07 12:55:02 -04:00
if !ok {
raw = interface{}(map[string]interface{}{})
}
val := raw.(map[string]interface{})
2021-11-17 20:08:18 -05:00
val[" {{ $field .ProtoName }} "] = nil
2020-08-26 23:27:25 -04:00
o.postMap["attributes"] = val
2020-08-07 12:55:02 -04:00
{{ else }}
2021-11-17 20:08:18 -05:00
o.postMap[" {{ $field .ProtoName }} "] = nil
2020-08-07 12:55:02 -04:00
{{ end }} }
2020-05-04 11:23:35 -04:00
}
2020-08-07 12:55:02 -04:00
{{ end }}
2020-09-19 00:18:17 -04:00
{{ end }}
2021-11-17 20:08:18 -05:00
{{ end }}
2020-05-04 11:23:35 -04:00
` ))
2020-08-07 12:55:02 -04:00
2022-07-28 18:17:30 -04:00
var mapstructureConversionTemplate = template . Must ( template . New ( "" ). Funcs (
template . FuncMap {
"typeFromSubtype" : typeFromSubtype ,
"kebabCase" : kebabCase ,
},
). Parse ( `
func AttributesMapTo {{ .Name }} (in map[string]interface{}) (* {{ .Name }} , error) {
if in == nil {
return nil, fmt.Errorf("nil input map")
}
var out {{ .Name }}
dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
Result: &out,
TagName: "json",
})
if err != nil {
return nil, fmt.Errorf("error creating mapstructure decoder: %w", err)
}
if err := dec.Decode(in); err != nil {
return nil, fmt.Errorf("error decoding: %w", err)
}
return &out, nil
}
func (pt * {{ .ParentTypeName }} ) Get {{ .Name }} () (* {{ .Name }} , error) {
2023-06-28 09:55:29 -07:00
if pt.Type != " {{ typeFromSubtype .Subtype .Name .ParentTypeName "Attributes" }} " {
return nil, fmt.Errorf("asked to fetch %s-type attributes but {{ kebabCase .ParentTypeName }} is of type %s", " {{ typeFromSubtype .Subtype .Name .ParentTypeName "Attributes" }} ", pt.Type)
2022-07-28 18:17:30 -04:00
}
return AttributesMapTo {{ .Name }} (pt.Attributes)
}
` ))
2020-08-07 12:55:02 -04:00
func makeSlice ( strs ... string ) [] string {
return strs
}
func snakeCase ( in string ) string {
return strcase . ToSnake ( in )
}
func kebabCase ( in string ) string {
return strcase . ToKebab ( in )
}
2021-06-22 22:26:26 -04:00
func getPathWithAction ( plResName , parentTypeName , action string ) string {
_ , _ , resPath := getArgsAndPaths ( plResName , parentTypeName , action )
2020-09-02 12:31:30 -07:00
return resPath
}
2021-11-17 20:08:18 -05:00
func removeDups ( in [] string ) [] string {
if in == nil {
return nil
}
if len ( in ) == 0 {
return [] string {}
}
vals := make ( map [ string ] struct {}, len ( in ))
for _ , val := range in {
vals [ val ] = struct {}{}
}
ret := make ([] string , 0 , len ( vals ))
for val := range vals {
ret = append ( ret , val )
}
2022-03-04 11:44:41 -05:00
sort . Strings ( ret )
2021-11-17 20:08:18 -05:00
return ret
}
2022-07-28 18:17:30 -04:00
2023-06-28 09:55:29 -07:00
func typeFromSubtype ( subtype , in , parent , extraSuffix string ) string {
if subtype != "" {
return subtype
}
2022-07-28 18:17:30 -04:00
return strings . ToLower ( strings . TrimSuffix ( strings . TrimSuffix ( in , extraSuffix ), parent ))
}