diff --git a/.golangci.yml b/.golangci.yml index 12526b66..7506f368 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -4,7 +4,6 @@ linters: disable: - exportloopref # WARN The linter 'exportloopref' is deprecated (since v1.60.2) due to: Since Go1.22 (loopvar) this linter is no longer relevant. Replaced by copyloopvar. - err113 # WARN [lintersdb] The name "goerr113" is deprecated. The linter has been renamed to: err113 - - gomnd # WARN The linter 'gomnd' is deprecated (since v1.58.0) due to: The linter has been renamed. Replaced by mnd - tagliatelle - wsl - nlreturn @@ -13,6 +12,7 @@ linters: - varnamelen - ireturn - depguard + - tagalign # TODO - dupl - wrapcheck @@ -23,6 +23,5 @@ linters: - gocognit - gocyclo - exhaustruct - - execinquery - dupword - musttag diff --git a/cmd/gen-jsonschema/main.go b/cmd/gen-jsonschema/main.go new file mode 100644 index 00000000..ec788ede --- /dev/null +++ b/cmd/gen-jsonschema/main.go @@ -0,0 +1,44 @@ +package main + +import ( + "encoding/json" + "fmt" + "log" + "os" + "strings" + + "github.com/invopop/jsonschema" + "github.com/suzuki-shunsuke/tfcmt/v4/pkg/config" +) + +func main() { + if err := core(); err != nil { + log.Fatal(err) + } +} + +func core() error { + if err := gen(&config.Config{}, "json-schema/tfcmt.json"); err != nil { + return err + } + return nil +} + +func gen(input interface{}, p string) error { + f, err := os.Create(p) + if err != nil { + return fmt.Errorf("create a file %s: %w", p, err) + } + defer f.Close() + encoder := json.NewEncoder(f) + encoder.SetIndent("", " ") + s := jsonschema.Reflect(input) + b, err := json.MarshalIndent(s, "", " ") + if err != nil { + return fmt.Errorf("mashal schema as JSON: %w", err) + } + if err := os.WriteFile(p, []byte(strings.ReplaceAll(string(b), "http://json-schema.org", "https://json-schema.org")+"\n"), 0o644); err != nil { //nolint:gosec,mnd + return fmt.Errorf("write JSON Schema to %s: %w", p, err) + } + return nil +} diff --git a/go.mod b/go.mod index 14c613a6..e68a80cd 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/Masterminds/sprig/v3 v3.3.0 github.com/google/go-cmp v0.6.0 github.com/google/go-github/v68 v68.0.0 + github.com/invopop/jsonschema v0.12.0 github.com/mattn/go-colorable v0.1.13 github.com/shurcooL/githubv4 v0.0.0-20240429030203-be2daab69064 github.com/sirupsen/logrus v1.9.3 @@ -22,10 +23,13 @@ require ( dario.cat/mergo v1.0.1 // indirect github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver/v3 v3.3.0 // indirect + github.com/bahlo/generic-list-go v0.2.0 // indirect + github.com/buger/jsonparser v1.1.1 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect github.com/google/go-querystring v1.1.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/huandu/xstrings v1.5.0 // indirect + github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-isatty v0.0.16 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect @@ -33,6 +37,7 @@ require ( github.com/shopspring/decimal v1.4.0 // indirect github.com/shurcooL/graphql v0.0.0-20200928012149-18c5c3165e3a // indirect github.com/spf13/cast v1.7.0 // indirect + github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect golang.org/x/crypto v0.26.0 // indirect golang.org/x/net v0.22.0 // indirect diff --git a/go.sum b/go.sum index 6f1c729e..6d53e300 100644 --- a/go.sum +++ b/go.sum @@ -6,6 +6,10 @@ github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+ github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs= github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0= +github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= +github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= +github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= +github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc= github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -24,10 +28,15 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/invopop/jsonschema v0.12.0 h1:6ovsNSuvn9wEQVOyc72aycBMVQFKz7cPdMJn10CvzRI= +github.com/invopop/jsonschema v0.12.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= @@ -53,8 +62,9 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/suzuki-shunsuke/github-comment-metadata v0.1.0 h1:89uGvBINoWZ4p2dGj7S695bm/L1H175eMp/D47ltSPs= github.com/suzuki-shunsuke/github-comment-metadata v0.1.0/go.mod h1:GNDhEmWAJ6Bbk9rIds0mAMF4noyPV3EqwqLetnEoNLg= github.com/suzuki-shunsuke/go-ci-env/v3 v3.1.0 h1:WHVT7TZTVITrKiIvW7i5+4vr8ebxD6oXGRffixIXNJU= @@ -65,6 +75,8 @@ github.com/suzuki-shunsuke/logrus-error v0.1.4 h1:nWo98uba1fANHdZ9Y5pJ2RKs/PpVjr github.com/suzuki-shunsuke/logrus-error v0.1.4/go.mod h1:WsVvvw6SKSt08/fB2qbnsKIMJA4K1MYCUprqsBJbMiM= github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w= github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ= +github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc= +github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= diff --git a/json-schema/tfcmt.json b/json-schema/tfcmt.json new file mode 100644 index 00000000..4464fadb --- /dev/null +++ b/json-schema/tfcmt.json @@ -0,0 +1,187 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/suzuki-shunsuke/tfcmt/v4/pkg/config/config", + "$ref": "#/$defs/Config", + "$defs": { + "Apply": { + "properties": { + "template": { + "type": "string" + }, + "when_parse_error": { + "$ref": "#/$defs/WhenParseError" + } + }, + "additionalProperties": false, + "type": "object" + }, + "Config": { + "properties": { + "terraform": { + "$ref": "#/$defs/Terraform" + }, + "embedded_var_names": { + "items": { + "type": "string" + }, + "type": "array" + }, + "templates": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "log": { + "$ref": "#/$defs/Log" + }, + "ghe_base_url": { + "type": "string" + }, + "ghe_graphql_endpoint": { + "type": "string" + }, + "plan_patch": { + "type": "boolean" + }, + "repo_owner": { + "type": "string" + }, + "repo_name": { + "type": "string" + } + }, + "additionalProperties": false, + "type": "object" + }, + "Log": { + "properties": { + "level": { + "type": "string" + } + }, + "additionalProperties": false, + "type": "object" + }, + "Plan": { + "properties": { + "template": { + "type": "string" + }, + "when_add_or_update_only": { + "$ref": "#/$defs/WhenAddOrUpdateOnly" + }, + "when_destroy": { + "$ref": "#/$defs/WhenDestroy" + }, + "when_no_changes": { + "$ref": "#/$defs/WhenNoChanges" + }, + "when_plan_error": { + "$ref": "#/$defs/WhenPlanError" + }, + "when_parse_error": { + "$ref": "#/$defs/WhenParseError" + }, + "disable_label": { + "type": "boolean" + }, + "ignore_warning": { + "type": "boolean" + } + }, + "additionalProperties": false, + "type": "object" + }, + "Terraform": { + "properties": { + "plan": { + "$ref": "#/$defs/Plan" + }, + "apply": { + "$ref": "#/$defs/Apply" + }, + "use_raw_output": { + "type": "boolean" + } + }, + "additionalProperties": false, + "type": "object" + }, + "WhenAddOrUpdateOnly": { + "properties": { + "label": { + "type": "string" + }, + "label_color": { + "type": "string" + }, + "disable_label": { + "type": "boolean" + } + }, + "additionalProperties": false, + "type": "object" + }, + "WhenDestroy": { + "properties": { + "label": { + "type": "string" + }, + "label_color": { + "type": "string" + }, + "disable_label": { + "type": "boolean" + } + }, + "additionalProperties": false, + "type": "object" + }, + "WhenNoChanges": { + "properties": { + "label": { + "type": "string" + }, + "label_color": { + "type": "string" + }, + "disable_label": { + "type": "boolean" + }, + "DisableComment": { + "type": "boolean" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "DisableComment" + ] + }, + "WhenParseError": { + "properties": { + "template": { + "type": "string" + } + }, + "additionalProperties": false, + "type": "object" + }, + "WhenPlanError": { + "properties": { + "label": { + "type": "string" + }, + "label_color": { + "type": "string" + }, + "disable_label": { + "type": "boolean" + } + }, + "additionalProperties": false, + "type": "object" + } + } +} diff --git a/pkg/config/config.go b/pkg/config/config.go index f066a347..ad16eb03 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -12,19 +12,19 @@ import ( // Config is for tfcmt config structure type Config struct { - CI CI `yaml:"-"` - Terraform Terraform - Vars map[string]string `yaml:"-"` - EmbeddedVarNames []string `yaml:"embedded_var_names"` - Templates map[string]string - Log Log - GHEBaseURL string `yaml:"ghe_base_url"` - GHEGraphQLEndpoint string `yaml:"ghe_graphql_endpoint"` - PlanPatch bool `yaml:"plan_patch"` - RepoOwner string `yaml:"repo_owner"` - RepoName string `yaml:"repo_name"` - Output string `yaml:"-"` - Masks []*Mask `yaml:"-"` + CI CI `json:"-" yaml:"-"` + Terraform Terraform `json:"terraform,omitempty"` + Vars map[string]string `json:"-" yaml:"-"` + EmbeddedVarNames []string `json:"embedded_var_names,omitempty" yaml:"embedded_var_names"` + Templates map[string]string `json:"templates,omitempty"` + Log Log `json:"log,omitempty"` + GHEBaseURL string `json:"ghe_base_url,omitempty" yaml:"ghe_base_url"` + GHEGraphQLEndpoint string `json:"ghe_graphql_endpoint,omitempty" yaml:"ghe_graphql_endpoint"` + PlanPatch bool `json:"plan_patch,omitempty" yaml:"plan_patch"` + RepoOwner string `json:"repo_owner,omitempty" yaml:"repo_owner"` + RepoName string `json:"repo_name,omitempty" yaml:"repo_name"` + Output string `json:"-" yaml:"-"` + Masks []*Mask `json:"-" yaml:"-"` } type Mask struct { @@ -43,67 +43,67 @@ type CI struct { } type Log struct { - Level string + Level string `json:"level,omitempty"` // Format string } // Terraform represents terraform configurations type Terraform struct { - Plan Plan - Apply Apply - UseRawOutput bool `yaml:"use_raw_output"` + Plan Plan `json:"plan,omitempty"` + Apply Apply `json:"apply,omitempty"` + UseRawOutput bool `json:"use_raw_output,omitempty" yaml:"use_raw_output"` } // Plan is a terraform plan config type Plan struct { - Template string - WhenAddOrUpdateOnly WhenAddOrUpdateOnly `yaml:"when_add_or_update_only"` - WhenDestroy WhenDestroy `yaml:"when_destroy"` - WhenNoChanges WhenNoChanges `yaml:"when_no_changes"` - WhenPlanError WhenPlanError `yaml:"when_plan_error"` - WhenParseError WhenParseError `yaml:"when_parse_error"` - DisableLabel bool `yaml:"disable_label"` - IgnoreWarning bool `yaml:"ignore_warning"` + Template string `json:"template,omitempty"` + WhenAddOrUpdateOnly WhenAddOrUpdateOnly `json:"when_add_or_update_only,omitempty" yaml:"when_add_or_update_only"` + WhenDestroy WhenDestroy `json:"when_destroy,omitempty" yaml:"when_destroy"` + WhenNoChanges WhenNoChanges `json:"when_no_changes,omitempty" yaml:"when_no_changes"` + WhenPlanError WhenPlanError `json:"when_plan_error,omitempty" yaml:"when_plan_error"` + WhenParseError WhenParseError `json:"when_parse_error,omitempty" yaml:"when_parse_error"` + DisableLabel bool `json:"disable_label,omitempty" yaml:"disable_label"` + IgnoreWarning bool `json:"ignore_warning,omitempty" yaml:"ignore_warning"` } // WhenAddOrUpdateOnly is a configuration to notify the plan result contains new or updated in place resources type WhenAddOrUpdateOnly struct { - Label string - Color string `yaml:"label_color"` - DisableLabel bool `yaml:"disable_label"` + Label string `json:"label,omitempty"` + Color string `json:"label_color,omitempty" yaml:"label_color"` + DisableLabel bool `json:"disable_label,omitempty" yaml:"disable_label"` } // WhenDestroy is a configuration to notify the plan result contains destroy operation type WhenDestroy struct { - Label string - Color string `yaml:"label_color"` - DisableLabel bool `yaml:"disable_label"` + Label string `json:"label,omitempty"` + Color string `json:"label_color,omitempty" yaml:"label_color"` + DisableLabel bool `json:"disable_label,omitempty" yaml:"disable_label"` } // WhenNoChanges is a configuration to add a label when the plan result contains no change type WhenNoChanges struct { - Label string - Color string `yaml:"label_color"` - DisableLabel bool `yaml:"disable_label"` + Label string `json:"label,omitempty"` + Color string `json:"label_color,omitempty" yaml:"label_color"` + DisableLabel bool `json:"disable_label,omitempty" yaml:"disable_label"` DisableComment bool `yaml:"disable_comment"` } // WhenPlanError is a configuration to notify the plan result returns an error type WhenPlanError struct { - Label string - Color string `yaml:"label_color"` - DisableLabel bool `yaml:"disable_label"` + Label string `json:"label,omitempty"` + Color string `json:"label_color,omitempty" yaml:"label_color"` + DisableLabel bool `json:"disable_label,omitempty" yaml:"disable_label"` } // WhenParseError is a configuration to notify the plan result returns an error type WhenParseError struct { - Template string + Template string `json:"template,omitempty"` } // Apply is a terraform apply config type Apply struct { - Template string - WhenParseError WhenParseError `yaml:"when_parse_error"` + Template string `json:"template,omitempty"` + WhenParseError WhenParseError `json:"when_parse_error,omitempty" yaml:"when_parse_error"` } // LoadFile binds the config file to Config structure