Skip to content

Commit 8fb6adb

Browse files
authored
Version requirements (#357)
* modules can declare zero version requirements
1 parent 40cc108 commit 8fb6adb

File tree

12 files changed

+182
-18
lines changed

12 files changed

+182
-18
lines changed

.goreleaser.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ builds:
66
- env:
77
- CGO_ENABLED=0
88
ldflags:
9-
- -X github.com/commitdev/zero/cmd.appVersion={{.Version}} -X github.com/commitdev/zero/cmd.appBuild={{.ShortCommit}}
9+
- -X github.com/commitdev/zero/version.AppVersion={{.Version}} -X github.com/commitdev/zero/version.AppBuild={{.ShortCommit}}
1010
archives:
1111
- replacements:
1212
darwin: Darwin

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
VERSION = 0.0.1
22
BUILD ?=$(shell git rev-parse --short HEAD)
33
PKG ?=github.com/commitdev/zero
4-
BUILD_ARGS=-v -trimpath -ldflags=all="-X ${PKG}/cmd.appVersion=${VERSION} -X ${PKG}/cmd.appBuild=${BUILD}"
4+
BUILD_ARGS=-v -trimpath -ldflags=all="-X ${PKG}/version.AppVersion=${VERSION} -X ${PKG}/version.AppBuild=${BUILD}"
55

66
deps:
77
go mod download

cmd/version.go

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,10 @@ package cmd
33
import (
44
"fmt"
55

6+
"github.com/commitdev/zero/version"
67
"github.com/spf13/cobra"
78
)
89

9-
var (
10-
appVersion = "SNAPSHOT"
11-
appBuild = "SNAPSHOT"
12-
)
13-
1410
func init() {
1511
rootCmd.AddCommand(versionCmd)
1612
}
@@ -19,7 +15,7 @@ var versionCmd = &cobra.Command{
1915
Use: "version",
2016
Short: "Print the version number of zero",
2117
Run: func(cmd *cobra.Command, args []string) {
22-
fmt.Printf("version: %v\n", appVersion)
23-
fmt.Printf("build: %v\n", appBuild)
18+
fmt.Printf("version: %v\n", version.AppVersion)
19+
fmt.Printf("build: %v\n", version.AppBuild)
2420
},
2521
}

docs/module-definition.md

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@
22
This file is the definition of a Zero module. It contains a list of all the required parameters to be able to prompt a user for choices during `zero init`, information about how to template the contents of the module during `zero create`, and the information needed for the module to run (`zero apply`).
33
It also declares the module's dependencies to determine the order of execution in relation to other modules.
44

5-
| Parameters | type | Description |
6-
|---------------|-----------------|--------------------------------------------------|
7-
| `name` | string | Name of module |
8-
| `description` | string | Description of the module |
9-
| `template` | template | default settings for templating out the module |
10-
| `author` | string | Author of the module |
11-
| `icon` | string | Path to logo image |
12-
| `parameters` | list(Parameter) | Parameters to prompt users |
5+
| Parameters | type | Description |
6+
|---------------|--------------------|--------------------------------------------------|
7+
| `name` | string | Name of module |
8+
| `description` | string | Description of the module |
9+
| `template` | template | default settings for templating out the module |
10+
| `author` | string | Author of the module |
11+
| `icon` | string | Path to logo image |
12+
| `parameters` | list(Parameter) | Parameters to prompt users |
13+
| `zeroVersion` | string([go-semver])| Zero versions its compatible with |
1314

1415

1516
### Template
@@ -77,3 +78,5 @@ For example if a user decide to not use circleCI, condition can be used to skip
7778
| `type` | enum(string) | Currently supports [`regex`] |
7879
| `value` | string | Regular expression string |
7980
| `errorMessage` | string | Error message when validation fails |
81+
82+
[go-semver]: https://github.com/hashicorp/go-version/blob/master/README.md

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ require (
99
github.com/google/go-cmp v0.3.1
1010
github.com/google/uuid v1.1.1
1111
github.com/hashicorp/go-getter v1.4.2-0.20200106182914-9813cbd4eb02
12+
github.com/hashicorp/go-version v1.2.1
1213
github.com/hashicorp/terraform v0.12.26
1314
github.com/iancoleman/strcase v0.1.2
1415
github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a // indirect

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,8 @@ github.com/hashicorp/go-version v1.1.0 h1:bPIoEKD27tNdebFGGxxYwcL4nepeY4j1QP23PF
186186
github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
187187
github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E=
188188
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
189+
github.com/hashicorp/go-version v1.2.1 h1:zEfKbn2+PDgroKdiOzqiE8rsmLqU2uwi5PB5pBJ3TkI=
190+
github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
189191
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
190192
github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
191193
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=

internal/config/moduleconfig/module_config.go

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
11
package moduleconfig
22

33
import (
4+
"errors"
45
"fmt"
56
"io/ioutil"
67
"log"
78
"reflect"
89
"strings"
910

11+
goVerson "github.com/hashicorp/go-version"
1012
yaml "gopkg.in/yaml.v2"
1113

1214
"github.com/commitdev/zero/internal/config/projectconfig"
15+
"github.com/commitdev/zero/internal/constants"
1316
"github.com/commitdev/zero/pkg/util/flog"
17+
"github.com/commitdev/zero/version"
1418
"github.com/iancoleman/strcase"
1519
)
1620

@@ -20,11 +24,39 @@ type ModuleConfig struct {
2024
Author string
2125
DependsOn []string `yaml:"dependsOn,omitempty"`
2226
TemplateConfig `yaml:"template"`
23-
RequiredCredentials []string `yaml:"requiredCredentials"`
27+
RequiredCredentials []string `yaml:"requiredCredentials"`
28+
ZeroVersion VersionConstraints `yaml:"zeroVersion,omitempty"`
2429
Parameters []Parameter
2530
Conditions []Condition `yaml:"conditions,omitempty"`
2631
}
2732

33+
func checkVersionAgainstConstrains(vc VersionConstraints, versionString string) bool {
34+
v, err := goVerson.NewVersion(versionString)
35+
if err != nil {
36+
return false
37+
}
38+
39+
return vc.Check(v)
40+
}
41+
42+
// ValidateZeroVersion receives a module config, and returns whether the running zero's binary
43+
// is compatible with the module
44+
func ValidateZeroVersion(mc ModuleConfig) bool {
45+
if mc.ZeroVersion.String() == "" {
46+
return true
47+
}
48+
49+
zeroVersion := version.AppVersion
50+
flog.Debugf("Checking Zero version (%s) against %s", zeroVersion, mc.ZeroVersion)
51+
52+
// Unreleased versions or test runs, defaults to SNAPSHOT when not declared
53+
if zeroVersion == "SNAPSHOT" {
54+
return true
55+
}
56+
57+
return checkVersionAgainstConstrains(mc.ZeroVersion, zeroVersion)
58+
}
59+
2860
type Parameter struct {
2961
Field string
3062
Label string `yaml:"label,omitempty"`
@@ -60,6 +92,10 @@ type TemplateConfig struct {
6092
OutputDir string `yaml:"outputDir"`
6193
}
6294

95+
type VersionConstraints struct {
96+
goVerson.Constraints
97+
}
98+
6399
// A "nice" wrapper around findMissing()
64100
func (cfg ModuleConfig) collectMissing() []string {
65101
var missing []string
@@ -107,6 +143,13 @@ func LoadModuleConfig(filePath string) (ModuleConfig, error) {
107143
log.Fatal("")
108144
}
109145

146+
if !ValidateZeroVersion(config) {
147+
constraint := config.ZeroVersion.Constraints.String()
148+
errTpl := `Module(%s) requires Zero to be version %s. Your current Zero version is: %s
149+
Please update your Zero version to %s.
150+
Please check %s for available releases.`
151+
return config, errors.New(fmt.Sprintf(errTpl, config.Name, constraint, version.AppVersion, constraint, constants.ZeroReleaseURL))
152+
}
110153
return config, nil
111154
}
112155

@@ -214,3 +257,23 @@ func SummarizeConditions(module ModuleConfig) []projectconfig.Condition {
214257
}
215258
return moduleConditions
216259
}
260+
261+
// UnmarshalYAML Parses a version constraint string into go-version constraint during yaml parsing
262+
func (semVer *VersionConstraints) UnmarshalYAML(unmarshal func(interface{}) error) error {
263+
var versionString string
264+
err := unmarshal(&versionString)
265+
if err != nil {
266+
return err
267+
}
268+
if versionString != "" {
269+
constraints, constErr := goVerson.NewConstraint(versionString)
270+
// If an invalid constraint is declared in a module
271+
// instead of erroring out we just print a warning message
272+
if constErr != nil {
273+
flog.Warnf("Zero version constraint invalid format: %s", constErr)
274+
}
275+
276+
*semVer = VersionConstraints{constraints}
277+
}
278+
return nil
279+
}

internal/constants/constants.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,5 @@ const (
1313
MaxPnameLength = 16
1414
RegexValidation = "regex"
1515
FunctionValidation = "function"
16+
ZeroReleaseURL = "https://github.com/commitdev/zero/releases"
1617
)

internal/module/module_test.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@ package module_test
22

33
import (
44
"errors"
5+
"fmt"
56
"testing"
67

78
"github.com/commitdev/zero/internal/config/moduleconfig"
89
"github.com/stretchr/testify/assert"
910

1011
"github.com/commitdev/zero/internal/module"
12+
"github.com/commitdev/zero/version"
1113
)
1214

1315
func TestGetSourceDir(t *testing.T) {
@@ -33,6 +35,7 @@ func TestParseModuleConfig(t *testing.T) {
3335

3436
t.Run("Loading module from source", func(t *testing.T) {
3537
mod, _ = module.ParseModuleConfig(testModuleSource)
38+
moduleconfig.ValidateZeroVersion(mod)
3639

3740
assert.Equal(t, "CI templates", mod.Name)
3841
})
@@ -86,6 +89,74 @@ func TestParseModuleConfig(t *testing.T) {
8689
assert.Equal(t, []string{"<%", "%>"}, mod.TemplateConfig.Delimiters)
8790
})
8891

92+
t.Run("Parsing zero version constraints", func(t *testing.T) {
93+
moduleConstraints := mod.ZeroVersion.Constraints.String()
94+
assert.Equal(t, ">= 3.0.0, < 4.0.0", moduleConstraints)
95+
})
96+
97+
t.Run("Should Fail against old zero version", func(t *testing.T) {
98+
moduleConstraints := mod.ZeroVersion.Constraints.String()
99+
100+
// Mocking zero's version, testing against ">= 3.0.0, <= 4.0.0"
101+
originalVersion := version.AppVersion
102+
version.AppVersion = "2.0.0"
103+
defer func() { version.AppVersion = originalVersion }()
104+
// end of mock
105+
106+
isValid := moduleconfig.ValidateZeroVersion(mod)
107+
assert.Equal(t, false, isValid, fmt.Sprintf("Version should satisfy %s", moduleConstraints))
108+
})
109+
110+
t.Run("Should Fail against too new zero version", func(t *testing.T) {
111+
moduleConstraints := mod.ZeroVersion.Constraints.String()
112+
113+
// Mocking zero's version, testing against ">= 3.0.0, <= 4.0.0"
114+
originalVersion := version.AppVersion
115+
version.AppVersion = "4.0.0"
116+
defer func() { version.AppVersion = originalVersion }()
117+
// end of mock
118+
119+
isValid := moduleconfig.ValidateZeroVersion(mod)
120+
assert.Equal(t, false, isValid, fmt.Sprintf("Version should satisfy %s", moduleConstraints))
121+
})
122+
123+
t.Run("Should validate against valid versions", func(t *testing.T) {
124+
moduleConstraints := mod.ZeroVersion.Constraints.String()
125+
126+
// Mocking zero's version, testing against ">= 3.0.0, <= 4.0.0"
127+
const newZeroVersion = "3.0.5"
128+
originalVersion := version.AppVersion
129+
version.AppVersion = newZeroVersion
130+
defer func() { version.AppVersion = originalVersion }()
131+
// end of mock
132+
133+
isValid := moduleconfig.ValidateZeroVersion(mod)
134+
assert.Equal(t, true, isValid, fmt.Sprintf("Version should satisfy %s", moduleConstraints))
135+
})
136+
137+
t.Run("default to SNAPSHOT version passes tests", func(t *testing.T) {
138+
assert.Equal(t, "SNAPSHOT", version.AppVersion)
139+
isValid := moduleconfig.ValidateZeroVersion(mod)
140+
assert.Equal(t, true, isValid, "default test run should pass version constraint")
141+
})
142+
143+
}
144+
145+
func TestModuleWithNoVersionConstraint(t *testing.T) {
146+
testModuleSource := "../../tests/test_data/modules/no-version-constraint"
147+
var mod moduleconfig.ModuleConfig
148+
var err error
149+
150+
t.Run("Parsing Module with no version constraint", func(t *testing.T) {
151+
mod, err = module.ParseModuleConfig(testModuleSource)
152+
assert.Equal(t, "", mod.ZeroVersion.String())
153+
assert.Nil(t, err)
154+
})
155+
156+
t.Run("Should pass Validation if constraint not specified", func(t *testing.T) {
157+
isValid := moduleconfig.ValidateZeroVersion(mod)
158+
assert.Equal(t, true, isValid, "Module with no constraint should pass version validation")
159+
})
89160
}
90161

91162
func findParameter(params []moduleconfig.Parameter, field string) (moduleconfig.Parameter, error) {

tests/test_data/modules/ci/zero-module.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ description: "CI description"
33
author: "CI author"
44
icon: ""
55
thumbnail: ""
6+
zeroVersion: ">= 3.0.0, < 4.0.0"
67

78
requiredCredentials:
89
- aws

0 commit comments

Comments
 (0)