diff --git a/cmd/fluxd/main.go b/cmd/fluxd/main.go index bfd681531..54729f2a4 100644 --- a/cmd/fluxd/main.go +++ b/cmd/fluxd/main.go @@ -46,8 +46,9 @@ const ( // There are running systems that assume these defaults (by not // supplying a value for one or both). Don't change them. - defaultGitSyncTag = "flux-sync" - defaultGitNotesRef = "flux" + defaultGitSyncTag = "flux-sync" + defaultGitNotesRef = "flux" + defaultGitSkipMessage = "\n\n[ci skip]" ) func optionalVar(fs *pflag.FlagSet, value ssh.OptionalValue, name, usage string) ssh.OptionalValue { @@ -79,8 +80,10 @@ func main() { gitSetAuthor = fs.Bool("git-set-author", false, "If set, the author of git commits will reflect the user who initiated the commit and will differ from the git committer.") gitLabel = fs.String("git-label", "", "label to keep track of sync progress; overrides both --git-sync-tag and --git-notes-ref") // Old git config; still used if --git-label is not supplied, but --git-label is preferred. - gitSyncTag = fs.String("git-sync-tag", defaultGitSyncTag, "tag to use to mark sync progress for this cluster") - gitNotesRef = fs.String("git-notes-ref", defaultGitNotesRef, "ref to use for keeping commit annotations in git notes") + gitSyncTag = fs.String("git-sync-tag", defaultGitSyncTag, "tag to use to mark sync progress for this cluster") + gitNotesRef = fs.String("git-notes-ref", defaultGitNotesRef, "ref to use for keeping commit annotations in git notes") + gitSkip = fs.Bool("git-ci-skip", false, `append "[ci skip]" to commit messages so that CI will skip builds`) + gitSkipMessage = fs.String("git-ci-skip-message", "", "additional text for commit messages, useful for skipping builds in CI. Use this to supply specific text, or set --git-ci-skip") gitPollInterval = fs.Duration("git-poll-interval", 5*time.Minute, "period at which to poll git repo for new commits") // registry @@ -146,6 +149,10 @@ func main() { } } + if *gitSkipMessage == "" && *gitSkip { + *gitSkipMessage = defaultGitSkipMessage + } + if len(*gitPath) > 0 && (*gitPath)[0] == '/' { logger.Log("err", "git subdirectory (--git-path) should not have leading forward slash") os.Exit(1) @@ -320,13 +327,14 @@ func main() { gitRemote := git.Remote{URL: *gitURL} gitConfig := git.Config{ - Path: *gitPath, - Branch: *gitBranch, - SyncTag: *gitSyncTag, - NotesRef: *gitNotesRef, - UserName: *gitUser, - UserEmail: *gitEmail, - SetAuthor: *gitSetAuthor, + Path: *gitPath, + Branch: *gitBranch, + SyncTag: *gitSyncTag, + NotesRef: *gitNotesRef, + UserName: *gitUser, + UserEmail: *gitEmail, + SetAuthor: *gitSetAuthor, + SkipMessage: *gitSkipMessage, } repo := git.NewRepo(gitRemote) diff --git a/daemon/daemon.go b/daemon/daemon.go index e42b88bf6..c952c0790 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -297,7 +297,7 @@ func (d *Daemon) updatePolicy(spec update.Spec, updates policy.Updates) DaemonJo if d.GitConfig.SetAuthor { commitAuthor = spec.Cause.User } - commitAction := &git.CommitAction{Author: commitAuthor, Message: policyCommitMessage(updates, spec.Cause)} + commitAction := git.CommitAction{Author: commitAuthor, Message: policyCommitMessage(updates, spec.Cause)} if err := working.CommitAndPush(ctx, commitAction, &git.Note{JobID: jobID, Spec: spec}); err != nil { // On the chance pushing failed because it was not // possible to fast-forward, ask for a sync so the @@ -336,7 +336,7 @@ func (d *Daemon) release(spec update.Spec, c release.Changes) DaemonJobFunc { if d.GitConfig.SetAuthor { commitAuthor = spec.Cause.User } - commitAction := &git.CommitAction{Author: commitAuthor, Message: commitMsg} + commitAction := git.CommitAction{Author: commitAuthor, Message: commitMsg} if err := working.CommitAndPush(ctx, commitAction, &git.Note{JobID: jobID, Spec: spec, Result: result}); err != nil { // On the chance pushing failed because it was not // possible to fast-forward, ask the repo to fetch diff --git a/daemon/loop_test.go b/daemon/loop_test.go index d6b551926..f9cf1c51c 100644 --- a/daemon/loop_test.go +++ b/daemon/loop_test.go @@ -240,7 +240,7 @@ func TestDoSync_WithNewCommit(t *testing.T) { return err } - commitAction := &git.CommitAction{Author: "", Message: "test commit"} + commitAction := git.CommitAction{Author: "", Message: "test commit"} err = checkout.CommitAndPush(ctx, commitAction, nil) if err != nil { return err diff --git a/git/gittest/repo.go b/git/gittest/repo.go index 2d60a5d78..1bc6c7b2b 100644 --- a/git/gittest/repo.go +++ b/git/gittest/repo.go @@ -59,33 +59,43 @@ func Repo(t *testing.T) (*git.Repo, func()) { }), cleanup } -func Checkout(t *testing.T) (*git.Checkout, func()) { +// CheckoutWithConfig makes a standard repo, clones it, and returns +// the clone, the original repo, and a cleanup function. +func CheckoutWithConfig(t *testing.T, config git.Config) (*git.Checkout, *git.Repo, func()) { repo, cleanup := Repo(t) shutdown, wg := make(chan struct{}), &sync.WaitGroup{} wg.Add(1) go repo.Start(shutdown, wg) WaitForRepoReady(repo, t) - config := git.Config{ - Branch: "master", - UserName: "example", - UserEmail: "example@example.com", - SyncTag: "flux-test", - NotesRef: "fluxtest", - } co, err := repo.Clone(context.Background(), config) if err != nil { close(shutdown) cleanup() t.Fatal(err) } - return co, func() { + return co, repo, func() { close(shutdown) co.Clean() cleanup() } } +var TestConfig git.Config = git.Config{ + Branch: "master", + UserName: "example", + UserEmail: "example@example.com", + SyncTag: "flux-test", + NotesRef: "fluxtest", +} + +// Checkout makes a standard repo, clones it, and returns the clone +// with a cleanup function. +func Checkout(t *testing.T) (*git.Checkout, func()) { + checkout, _, cleanup := CheckoutWithConfig(t, TestConfig) + return checkout, cleanup +} + func execCommand(cmd string, args ...string) error { c := exec.Command(cmd, args...) c.Stderr = ioutil.Discard diff --git a/git/gittest/repo_test.go b/git/gittest/repo_test.go index aa05ee724..378ebaa5b 100644 --- a/git/gittest/repo_test.go +++ b/git/gittest/repo_test.go @@ -6,6 +6,7 @@ import ( "reflect" "sync" "testing" + "time" "context" @@ -16,6 +17,53 @@ import ( "github.com/weaveworks/flux/update" ) +func TestCommit(t *testing.T) { + config := TestConfig + config.SkipMessage = " **SKIP**" + checkout, repo, cleanup := CheckoutWithConfig(t, config) + defer cleanup() + + for file, _ := range testfiles.Files { + path := filepath.Join(checkout.ManifestDir(), file) + if err := ioutil.WriteFile(path, []byte("FIRST CHANGE"), 0666); err != nil { + t.Fatal(err) + } + break + } + + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + commitAction := git.CommitAction{Message: "Changed file"} + if err := checkout.CommitAndPush(ctx, commitAction, nil); err != nil { + t.Fatal(err) + } + + err := repo.Refresh(ctx) + if err != nil { + t.Error(err) + } + + commits, err := repo.CommitsBefore(ctx, "HEAD", "") + + if err != nil { + t.Fatal(err) + } + if len(commits) < 1 { + t.Fatal("expected at least one commit") + } + if msg := commits[0].Message; msg != commitAction.Message+config.SkipMessage { + t.Errorf(`expected commit message to be: + +%s + + but it was + +%s +`, commitAction.Message+config.SkipMessage, msg) + } +} + func TestCheckout(t *testing.T) { repo, cleanup := Repo(t) defer cleanup() @@ -64,7 +112,7 @@ func TestCheckout(t *testing.T) { changedFile = file break } - commitAction := &git.CommitAction{Author: "", Message: "Changed file"} + commitAction := git.CommitAction{Author: "", Message: "Changed file"} if err := checkout.CommitAndPush(ctx, commitAction, nil); err != nil { t.Fatal(err) } @@ -88,7 +136,7 @@ func TestCheckout(t *testing.T) { }, }, } - commitAction = &git.CommitAction{Author: "", Message: "Changed file again"} + commitAction = git.CommitAction{Author: "", Message: "Changed file again"} if err := checkout.CommitAndPush(ctx, commitAction, &expectedNote); err != nil { t.Fatal(err) } diff --git a/git/operations.go b/git/operations.go index 9c239dc4c..204a2b9b9 100644 --- a/git/operations.go +++ b/git/operations.go @@ -68,7 +68,7 @@ func checkPush(ctx context.Context, workingDir, upstream string) error { return execGitCmd(ctx, workingDir, nil, "push", "--delete", upstream, "tag", CheckPushTag) } -func commit(ctx context.Context, workingDir string, commitAction *CommitAction) error { +func commit(ctx context.Context, workingDir string, commitAction CommitAction) error { commitAuthor := commitAction.Author if commitAuthor != "" { if err := execGitCmd(ctx, diff --git a/git/working.go b/git/working.go index b8c286023..0ae412f2b 100644 --- a/git/working.go +++ b/git/working.go @@ -9,13 +9,14 @@ import ( // Config holds some values we use when working in the working clone of // a repo. type Config struct { - Branch string // branch we're syncing to - Path string // path within the repo containing files we care about - SyncTag string - NotesRef string - UserName string - UserEmail string - SetAuthor bool + Branch string // branch we're syncing to + Path string // path within the repo containing files we care about + SyncTag string + NotesRef string + UserName string + UserEmail string + SetAuthor bool + SkipMessage string } // Checkout is a local working clone of the remote repo. It is @@ -96,10 +97,13 @@ func (c *Checkout) ManifestDir() string { // CommitAndPush commits changes made in this checkout, along with any // extra data as a note, and pushes the commit and note to the remote repo. -func (c *Checkout) CommitAndPush(ctx context.Context, commitAction *CommitAction, note *Note) error { +func (c *Checkout) CommitAndPush(ctx context.Context, commitAction CommitAction, note *Note) error { if !check(ctx, c.dir, c.config.Path) { return ErrNoChanges } + + commitAction.Message += c.config.SkipMessage + if err := commit(ctx, c.dir, commitAction); err != nil { return err } diff --git a/site/daemon.md b/site/daemon.md index bb2c3cdab..6bf8b7fd5 100644 --- a/site/daemon.md +++ b/site/daemon.md @@ -45,6 +45,8 @@ fluxd requires setup and offers customization though a multitude of flags. |**Git repo & key etc.** | || |--git-url | | URL of git repo with Kubernetes manifests; e.g., `git@github.com:weaveworks/flux-example`| |--git-branch | `master` | branch of git repo to use for Kubernetes manifests| +|--git-ci-skip | false | when set, fluxd will append `\n\n[ci skip]` to its commit messages | +|--git-ci-skip-message | `""` | if provided, fluxd will append this to commit messages (overrides --git-ci-skip`) | |--git-path | | path within git repo to locate Kubernetes manifests (relative path)| |--git-user | `Weave Flux` | username to use as git committer| |--git-email | `support@weave.works` | email to use as git committer| diff --git a/sync/sync_test.go b/sync/sync_test.go index 37eb99bcc..b33cfa914 100644 --- a/sync/sync_test.go +++ b/sync/sync_test.go @@ -48,7 +48,7 @@ func TestSync(t *testing.T) { if err := execCommand("rm", res[0]); err != nil { t.Fatal(err) } - commitAction := &git.CommitAction{Author: "", Message: "deleted " + res[0]} + commitAction := git.CommitAction{Author: "", Message: "deleted " + res[0]} if err := checkout.CommitAndPush(context.Background(), commitAction, nil); err != nil { t.Fatal(err) }