Skip to content

Commit c45c39e

Browse files
authored
Validate zero modules (#324)
* Validate zero modules Adds two private functions in 'internal/module_config': - func (cfg ModuleConfig) collectMissing() []string - func findMissing(reflect.Value, string, string, []string) These are unexported functions that aid in introspecting a datastructure to see if it has all its expected data. These functions are implicitly invoked via: - func LoadModuleConfig(string) (ModuleConfig, error) If there are errors then the program aborts with appropriate output. * Set description and author in example zero module * Remove unused code * Remove unnecessary newline in error log
1 parent 151cdf5 commit c45c39e

File tree

4 files changed

+108
-8
lines changed

4 files changed

+108
-8
lines changed

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ require (
1010
github.com/google/uuid v1.1.1
1111
github.com/hashicorp/go-getter v1.4.2-0.20200106182914-9813cbd4eb02
1212
github.com/hashicorp/terraform v0.12.26
13+
github.com/iancoleman/strcase v0.1.2
1314
github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a // indirect
1415
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 // indirect
1516
github.com/k0kubun/pp v3.0.1+incompatible

go.sum

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,8 @@ github.com/hashicorp/terraform-config-inspect v0.0.0-20191212124732-c6ae6269b9d7
204204
github.com/hashicorp/terraform-svchost v0.0.0-20191011084731-65d371908596/go.mod h1:kNDNcF7sN4DocDLBkQYz73HGKwN1ANB1blq4lIYLYvg=
205205
github.com/hashicorp/vault v0.10.4/go.mod h1:KfSyffbKxoVyspOdlaGVjIuwLobi07qD1bAbosPMpP0=
206206
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
207+
github.com/iancoleman/strcase v0.1.2 h1:gnomlvw9tnV3ITTAxzKSgTF+8kFWcU/f+TgttpXGz1U=
208+
github.com/iancoleman/strcase v0.1.2/go.mod h1:SK73tn/9oHe+/Y0h39VT4UCxmurVJkR5NA7kMEAOgSE=
207209
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
208210
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
209211
github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74=
@@ -227,6 +229,7 @@ github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALr
227229
github.com/keybase/go-crypto v0.0.0-20161004153544-93f5b35093ba/go.mod h1:ghbZscTyKdM07+Fw3KSi0hcJm+AlEUWj8QLlPtijN/M=
228230
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
229231
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
232+
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
230233
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
231234
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
232235
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=

internal/config/moduleconfig/module_config.go

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
11
package moduleconfig
22

33
import (
4+
"fmt"
45
"io/ioutil"
6+
"log"
7+
"reflect"
8+
"strings"
59

610
yaml "gopkg.in/yaml.v2"
11+
12+
"github.com/commitdev/zero/pkg/util/flog"
13+
"github.com/iancoleman/strcase"
714
)
815

916
type ModuleConfig struct {
@@ -48,16 +55,106 @@ type TemplateConfig struct {
4855
OutputDir string `yaml:"outputDir"`
4956
}
5057

58+
// A "nice" wrapper around findMissing()
59+
func (cfg ModuleConfig) collectMissing() []string {
60+
var missing []string
61+
findMissing(reflect.ValueOf(cfg), "", "", &missing)
62+
63+
return missing
64+
}
65+
5166
func LoadModuleConfig(filePath string) (ModuleConfig, error) {
5267
config := ModuleConfig{}
68+
5369
data, err := ioutil.ReadFile(filePath)
5470
if err != nil {
5571
return config, err
5672
}
73+
5774
err = yaml.Unmarshal(data, &config)
5875
if err != nil {
5976
return config, err
6077
}
6178

79+
missing := config.collectMissing()
80+
if len(missing) > 0 {
81+
flog.Errorf("%v is missing information", filePath)
82+
83+
for _, m := range missing {
84+
flog.Errorf("\t %v", m)
85+
}
86+
87+
log.Fatal("")
88+
}
89+
6290
return config, nil
6391
}
92+
93+
// Recurses through a datastructure to find any missing data.
94+
// This assumes several things:
95+
// 1. The structure matches that defined by ModuleConfig and its child datastructures.
96+
// 2. YAML struct field metadata is sufficient to define whether an attribute is missing or not.
97+
// That is, "yaml:foo,omitempty" tells us this is not a required field because we can omit it.
98+
// 3. Slices and arrays are assumed to be optional.
99+
//
100+
// As this function recurses through the datastructure, it builds up a string
101+
// path representing each node's path within the datastructure.
102+
// If the value of the current node is equal to the zero value for its datatype
103+
// and its struct field does *not* have a "omitempty" value, then we assume it
104+
// is missing and add it to the resultset.
105+
func findMissing(obj reflect.Value, path, metadata string, missing *[]string) {
106+
t := obj.Type()
107+
switch t.Kind() {
108+
case reflect.String:
109+
if obj.String() == "" && !strings.Contains(metadata, "omitempty") {
110+
*missing = append(*missing, path)
111+
}
112+
113+
case reflect.Slice, reflect.Array:
114+
for i := 0; i < obj.Len(); i++ {
115+
prefix := fmt.Sprintf("%v[%v]", path, i)
116+
findMissing(obj.Index(i), prefix, metadata, missing)
117+
}
118+
119+
case reflect.Struct:
120+
for i := 0; i < t.NumField(); i++ {
121+
fieldType := t.Field(i)
122+
fieldTags, _ := fieldType.Tag.Lookup("yaml")
123+
fieldVal := obj.Field(i)
124+
125+
tags := strings.Split(fieldTags, ",")
126+
127+
hasOmitEmpty := false
128+
// We have all metadata yaml tags, now let's remove the "omitempty" tag if
129+
// it is present.
130+
// Then if we have only one tag remaining, this must be the expected yaml
131+
// identifer.
132+
// Otherwise the name of the yaml identifier should match the struct
133+
// attribute name.
134+
for i := len(tags) - 1; i >= 0; i-- {
135+
tag := tags[i]
136+
if tag == "omitempty" {
137+
hasOmitEmpty = true
138+
tags = append(tags[:i], tags[i+1:]...)
139+
}
140+
}
141+
142+
yamlName := strcase.ToLowerCamel(fieldType.Name)
143+
if len(tags) == 1 && tags[0] != "" { // For some reason, empty tag lists are giving a count of 1.
144+
yamlName = tags[0]
145+
}
146+
147+
prefix := yamlName
148+
if path != "" {
149+
prefix = fmt.Sprintf("%v.%v", path, yamlName)
150+
}
151+
152+
zeroVal := reflect.Zero(fieldType.Type)
153+
if fieldVal == zeroVal && !hasOmitEmpty {
154+
*missing = append(*missing, prefix)
155+
}
156+
157+
findMissing(fieldVal, prefix, fieldTags, missing)
158+
}
159+
}
160+
}
Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: "CI templates"
2-
description: ""
3-
author: ""
2+
description: "CI description"
3+
author: "CI author"
44
icon: ""
55
thumbnail: ""
66

@@ -10,25 +10,24 @@ requiredCredentials:
1010
- github
1111

1212
# Template variables to populate, these could be overwritten by the file spefic frontmatter variables
13-
template:
14-
# strictMode: true # will only parse files that includes the .tmpl.* extension, otherwise it will copy file
13+
template:
14+
# strictMode: true # will only parse files that includes the .tmpl.* extension, otherwise it will copy file
1515
delimiters:
1616
- "<%"
1717
- "%>"
1818
inputDir: 'templates'
1919
outputDir: ".circleci"
2020

2121
# required context parameters: will throw a warning message at the end if any of the context parameters are not present
22-
# contextRequired:
22+
# contextRequired:
2323
# - cognitoPoolID
2424
# - cognitoClientID
2525

2626
# parameters required from user to populate the template params
27-
parameters:
27+
parameters:
2828
- field: platform
2929
label: CI Platform
3030
# default: github
31-
options:
31+
options:
3232
- github
3333
- circlci
34-

0 commit comments

Comments
 (0)