diff --git a/apply/apply.go b/apply/apply.go index 3a54a22a9..951739106 100644 --- a/apply/apply.go +++ b/apply/apply.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" "io" + "io/ioutil" "net/url" "os" "path/filepath" @@ -26,7 +27,14 @@ import ( const rootPath = "terraform" -func Apply(fs afero.Fs, conf *config.Config, tmp *templates.T) error { +func Apply(fs afero.Fs, conf *config.Config, tmp *templates.T, upgrade bool) error { + if !upgrade { + toolVersion, _ := util.VersionString() + versionChange, repoVersion, _ := checkToolVersions(fs, toolVersion) + if versionChange { + return fmt.Errorf("fogg version (%s) is different than version currently used to manage repo (%s). To upgrade add --upgrade.", toolVersion, repoVersion) + } + } p, err := plan.Eval(conf, false) if err != nil { return errors.Wrap(err, "unable to evaluate plan") @@ -56,6 +64,38 @@ func Apply(fs afero.Fs, conf *config.Config, tmp *templates.T) error { return errors.Wrap(e, "unable to apply modules") } +func checkToolVersions(fs afero.Fs, current string) (bool, string, error) { + f, e := fs.Open(".fogg-version") + if e != nil { + return false, "", errors.Wrap(e, "unable to open .fogg-version file") + } + reader := io.ReadCloser(f) + defer reader.Close() + + b, e := ioutil.ReadAll(reader) + if e != nil { + return false, "", errors.Wrap(e, "unable to read .fogg-version file") + } + repoVersion := string(b) + changed, e := versionIsChanged(repoVersion, current) + return changed, repoVersion, e +} + +func versionIsChanged(repo string, tool string) (bool, error) { + repoVersion, repoSha, repoDirty := util.ParseVersion(repo) + toolVersion, toolSha, toolDirty := util.ParseVersion(tool) + + if repoDirty || toolDirty { + return true, nil + } + + if (repoSha != "" || toolSha != "") && repoSha != toolSha { + return true, nil + } + + return toolVersion.NE(repoVersion), nil +} + func applyRepo(fs afero.Fs, p *plan.Plan, repoTemplates *packr.Box) error { e := applyTree(fs, repoTemplates, "", p) if e != nil { diff --git a/apply/apply_test.go b/apply/apply_test.go index d90840136..dc34f7b0f 100644 --- a/apply/apply_test.go +++ b/apply/apply_test.go @@ -158,7 +158,7 @@ func TestApplySmokeTest(t *testing.T) { c, e := config.ReadConfig(ioutil.NopCloser(strings.NewReader(json))) assert.Nil(t, e) - e = Apply(fs, c, templates.Templates) + e = Apply(fs, c, templates.Templates, false) assert.Nil(t, e) } @@ -274,6 +274,48 @@ func TestCalculateLocalPath(t *testing.T) { } } +var versionTests = []struct { + current string + tool string + result bool +}{ + {"0.0.0", "0.1.0", true}, + {"0.1.0", "0.0.0", true}, + {"0.1.0", "0.1.0", false}, + {"0.1.0", "0.1.0-abcdef", true}, + {"0.1.0", "0.1.0-abcdef-dirty", true}, + {"0.1.0-abcdef-dirty", "0.1.0", true}, + {"0.1.0-abc", "0.1.0-def", true}, + {"0.1.0-abc", "0.1.0-abc", false}, +} + +func TestCheckToolVersions(t *testing.T) { + a := assert.New(t) + + for _, tc := range versionTests { + t.Run("", func(t *testing.T) { + fs := afero.NewMemMapFs() + writeFile(fs, ".fogg-version", tc.current) + + v, _, e := checkToolVersions(fs, tc.tool) + a.NoError(e) + a.Equal(tc.result, v) + }) + } +} + +func TestVersionIsChanged(t *testing.T) { + a := assert.New(t) + + for _, test := range versionTests { + t.Run("", func(t *testing.T) { + b, e := versionIsChanged(test.current, test.tool) + a.NoError(e) + a.Equal(test.result, b) + }) + } +} + func readFile(fs afero.Fs, path string) (string, error) { f, e := fs.Open(path) if e != nil { diff --git a/cmd/apply.go b/cmd/apply.go index 0d1d7233e..4d68b4058 100644 --- a/cmd/apply.go +++ b/cmd/apply.go @@ -13,6 +13,7 @@ import ( func init() { applyCmd.Flags().StringP("config", "c", "fogg.json", "Use this to override the fogg config file.") applyCmd.Flags().BoolP("verbose", "v", false, "use this to turn on verbose output") + applyCmd.Flags().BoolP("upgrade", "u", false, "use this when running a new version of fogg") rootCmd.AddCommand(applyCmd) } @@ -47,6 +48,11 @@ var applyCmd = &cobra.Command{ log.Panic(e) } + upgrade, e := cmd.Flags().GetBool("upgrade") + if e != nil { + log.Panic(e) + } + // check that we are at root of initialized git repo openGitOrExit(pwd) @@ -55,7 +61,7 @@ var applyCmd = &cobra.Command{ exitOnConfigErrors(err) // apply - e = apply.Apply(fs, config, templates.Templates) + e = apply.Apply(fs, config, templates.Templates, upgrade) if e != nil { log.Panic(e) } diff --git a/util/version.go b/util/version.go index 6263166dd..6b62e670c 100644 --- a/util/version.go +++ b/util/version.go @@ -3,6 +3,7 @@ package util import ( "fmt" "strconv" + "strings" "github.com/blang/semver" "github.com/pkg/errors" @@ -39,6 +40,24 @@ func VersionCacheKey() string { return fmt.Sprintf("%d.%d.%d", v.Major, v.Minor, v.Patch) } +func ParseVersion(version string) (semver.Version, string, bool) { + var dirty bool + var sha string + v := version + if strings.HasSuffix(v, "-dirty") { + dirty = true + v = strings.TrimSuffix(v, "-dirty") + } + if strings.Contains(v, "-") { + tmp := strings.Split(v, "-") + v = tmp[0] + sha = tmp[1] + } + + semVersion, _ := semver.Parse(v) + return semVersion, sha, dirty +} + func versionString(version, sha string, release, dirty bool) string { if release { return version diff --git a/util/version_test.go b/util/version_test.go index e062dcef9..e9da83bd4 100644 --- a/util/version_test.go +++ b/util/version_test.go @@ -3,6 +3,7 @@ package util import ( "testing" + "github.com/blang/semver" "github.com/stretchr/testify/assert" ) @@ -17,3 +18,29 @@ func TestVersionString(t *testing.T) { assert.Equal(t, "0.1.0-abcdef-dirty", s) } + +func TestParse(t *testing.T) { + a := assert.New(t) + + testCases := []struct { + input string + version string + sha string + dirty bool + }{ + {"0.1.0", "0.1.0", "", false}, + {"0.1.0-abcdef", "0.1.0", "abcdef", false}, + {"0.1.0-abcdef-dirty", "0.1.0", "abcdef", true}, + } + for _, tc := range testCases { + t.Run("", func(t *testing.T) { + v, sha, dirty := ParseVersion(tc.input) + semVersion, e := semver.Parse(tc.version) + a.NoError(e) + a.Equal(semVersion, v) + a.Equal(tc.sha, sha) + a.Equal(tc.dirty, dirty) + }) + } + +}