diff options
Diffstat (limited to 'ops/besadii')
-rw-r--r-- | ops/besadii/main.go | 184 |
1 files changed, 117 insertions, 67 deletions
diff --git a/ops/besadii/main.go b/ops/besadii/main.go index 86356a073fac..5ffe1beeda98 100644 --- a/ops/besadii/main.go +++ b/ops/besadii/main.go @@ -25,26 +25,25 @@ import ( "os" "path" "regexp" + "strconv" "strings" ) -var branchRegexp = regexp.MustCompile(`^refs/heads/(.*)$`) -var metaRegexp = regexp.MustCompile(`^refs/changes/\d{0,2}/(\d+)/meta$`) -var patchsetRegexp = regexp.MustCompile(`^refs/changes/\d{0,2}/(\d+)/(\d+)$`) +// Regular expression to extract change ID out of a URL +var changeIdRegexp = regexp.MustCompile(`^.*/(\d+)$`) // buildTrigger represents the information passed to besadii when it // is invoked as a Gerrit hook. // // https://gerrit.googlesource.com/plugins/hooks/+/HEAD/src/main/resources/Documentation/hooks.md type buildTrigger struct { - project string - ref string - commit string - submitter string - email string - - changeId *string - patchset *string + project string + ref string + commit string + owner string + + changeId string + patchset string } type Author struct { @@ -118,9 +117,11 @@ func triggerBuild(log *syslog.Writer, token string, trigger *buildTrigger) error // // This information is later used by besadii when invoked by Gerrit // to communicate the build status back to Gerrit. - if trigger.changeId != nil && trigger.patchset != nil { - env["GERRIT_CHANGE_ID"] = *trigger.changeId - env["GERRIT_PATCHSET"] = *trigger.patchset + canonBuild := true + if trigger.changeId != "" && trigger.patchset != "" { + env["GERRIT_CHANGE_ID"] = trigger.changeId + env["GERRIT_PATCHSET"] = trigger.patchset + canonBuild = false } build := Build{ @@ -128,8 +129,7 @@ func triggerBuild(log *syslog.Writer, token string, trigger *buildTrigger) error Branch: trigger.ref, Env: env, Author: Author{ - Name: trigger.submitter, - Email: trigger.email, + Name: trigger.owner, }, } @@ -168,9 +168,14 @@ func triggerBuild(log *syslog.Writer, token string, trigger *buildTrigger) error fmt.Fprintf(log, "triggered build for ref %q at commit %q: %s", trigger.ref, trigger.commit, buildResp.WebUrl) + // For builds of canon there is nothing else to do + if canonBuild { + return nil + } + // Report the status back to the Gerrit CL so that users can click // through to the running build. - msg := fmt.Sprintf("Started build for patchset #%s of cl/%s: %s", *trigger.patchset, *trigger.changeId, buildResp.WebUrl) + msg := fmt.Sprintf("Started build for patchset #%s of cl/%s: %s", trigger.patchset, trigger.changeId, buildResp.WebUrl) review := reviewInput{ Message: msg, OmitDuplicateComments: true, @@ -179,7 +184,7 @@ func triggerBuild(log *syslog.Writer, token string, trigger *buildTrigger) error // Do not update the attention set for this comment. IgnoreDefaultAttentionSetRules: true, } - updateGerrit(review, *trigger.changeId, *trigger.patchset) + updateGerrit(review, trigger.changeId, trigger.patchset) return nil } @@ -199,69 +204,96 @@ func triggerIndexUpdate(token string) error { return err } -func buildTriggerFromFlags() (*buildTrigger, error) { +// Gerrit passes more flags than we want, but Rob Pike decided[0] in +// 2013 that the Go art project will not allow users to ignore flags +// because he "doesn't like it". This function allows users to ignore +// flags. +// +// [0]: https://github.com/golang/go/issues/6112#issuecomment-66083768 +func ignoreFlags(ignore []string) { + for _, f := range ignore { + flag.String(f, "", "flag to ignore") + } +} + +// Extract the buildtrigger struct out of the flags passed to besadii +// when invoked as Gerrit's 'patchset-created' hook. This hook is used +// for triggering CI on in-progress CLs. +func buildTriggerFromPatchsetCreated() (*buildTrigger, error) { + // Information that needs to be returned var trigger buildTrigger + // Information that is only needed for parsing + var targetBranch, changeUrl string + flag.StringVar(&trigger.project, "project", "", "Gerrit project") - flag.StringVar(&trigger.commit, "newrev", "", "new revision") - flag.StringVar(&trigger.email, "submitter", "", "Submitter email") - flag.StringVar(&trigger.submitter, "submitter-username", "", "Submitter username") - flag.StringVar(&trigger.ref, "refname", "", "updated reference name") - - // Gerrit passes more flags than we want, but Rob Pike decided[0] in - // 2013 that the Go art project will not allow users to ignore flags - // because he "doesn't like it". The following code ignores the - // flags. - // - // [0]: https://github.com/golang/go/issues/6112#issuecomment-66083768 - var _old string - flag.StringVar(&_old, "oldrev", "", "") + flag.StringVar(&trigger.commit, "commit", "", "commit hash") + flag.StringVar(&trigger.owner, "change-owner", "", "change owner") + flag.StringVar(&trigger.patchset, "patchset", "", "patchset ID") - flag.Parse() + flag.StringVar(&targetBranch, "branch", "", "CL target branch") + flag.StringVar(&changeUrl, "change-url", "", "HTTPS URL of change") - if trigger.project == "" || trigger.ref == "" || trigger.commit == "" || trigger.submitter == "" { - // If we get here, the user is probably being a dummy and invoking - // this manually - but incorrectly. - return nil, fmt.Errorf("'ref-updated' hook invoked without required arguments") - } + // patchset-created also passes various flags which we don't need. + ignoreFlags([]string{"kind", "topic", "change", "uploader", "uploader-username", "change-owner-username"}) + + flag.Parse() - if trigger.project != "depot" || metaRegexp.MatchString(trigger.ref) { - // this is not an error, but also not something we handle. + // If the patchset is not for depot@canon then we can ignore it. It + // might be some other kind of change (refs/meta/config or + // Gerrit-internal), but it is not an error. + if trigger.project != "depot" || targetBranch != "canon" { return nil, nil } - if branchRegexp.MatchString(trigger.ref) { - // these refs don't need special handling, just move on - return &trigger, nil - } + // Change ID is not directly passed in the numeric format, so we + // need to extract it out of the URL + matches := changeIdRegexp.FindStringSubmatch(changeUrl) + trigger.changeId = matches[1] - if matches := patchsetRegexp.FindStringSubmatch(trigger.ref); matches != nil { - trigger.changeId = &matches[1] - trigger.patchset = &matches[2] - return &trigger, nil - } + // Construct the CL ref from which the build should happen. + changeId, _ := strconv.Atoi(trigger.changeId) + trigger.ref = fmt.Sprintf( + "refs/changes/%02d/%s/%s", + changeId%100, trigger.changeId, trigger.patchset, + ) - return nil, fmt.Errorf("besadii does not support updates for this type of ref (%q)", trigger.ref) + return &trigger, nil } -func refUpdatedMain() { - // Logging happens in syslog for Gerrit hooks because we don't want - // the hook output to be intermingled with Gerrit's own output - // stream - log, err := syslog.New(syslog.LOG_INFO|syslog.LOG_USER, "besadii") - if err != nil { - fmt.Fprintf(os.Stderr, "failed to open syslog: %s\n", err) - os.Exit(1) - } +// Extract the buildtrigger struct out of the flags passed to besadii +// when invoked as Gerrit's 'change-merged' hook. This hook is used +// for triggering canon builds after change submission. +func buildTriggerFromChangeMerged() *buildTrigger { + // Information that needs to be returned + var trigger buildTrigger - trigger, err := buildTriggerFromFlags() - if err != nil { - log.Err(fmt.Sprintf("failed to parse ref update: %s", err)) - os.Exit(1) + // Information that is only needed for parsing + var targetBranch string + + flag.StringVar(&trigger.project, "project", "", "Gerrit project") + flag.StringVar(&trigger.commit, "commit", "", "Commit hash") + flag.StringVar(&trigger.owner, "change-owner", "", "Change owner") + flag.StringVar(&targetBranch, "branch", "", "CL target branch") + + // Ignore extra flags passed by change-merged + ignoreFlags([]string{"change", "topic", "change-url", "submitter", "submitter-username", "newrev", "change-owner-username"}) + + flag.Parse() + + // Skip builds for anything other than depot@canon + if trigger.project != "depot" || targetBranch != "canon" { + return nil } - if trigger == nil { // the project was not 'depot' - log.Err("build triggers are only supported for the 'depot' project") + trigger.ref = "refs/heads/canon" + + return &trigger +} + +func gerritHookMain(log *syslog.Writer, trigger *buildTrigger) { + if trigger == nil { + // The hook was not for something we care about. os.Exit(0) } @@ -347,10 +379,28 @@ func postCommandMain() { } func main() { + // Logging happens in syslog because it's almost impossible to get + // output out of Gerrit hooks otherwise. + log, err := syslog.New(syslog.LOG_INFO|syslog.LOG_USER, "besadii") + if err != nil { + fmt.Fprintf(os.Stderr, "failed to open syslog: %s\n", err) + os.Exit(1) + } + + log.Info(fmt.Sprintf("besadii called with arguments: %v", os.Args)) + bin := path.Base(os.Args[0]) - if bin == "ref-updated" { - refUpdatedMain() + if bin == "patchset-created" { + trigger, err := buildTriggerFromPatchsetCreated() + if err != nil { + log.Crit("failed to parse 'patchset-created' invocation from args") + os.Exit(1) + } + gerritHookMain(log, trigger) + } else if bin == "change-merged" { + trigger := buildTriggerFromChangeMerged() + gerritHookMain(log, trigger) } else if bin == "post-command" { postCommandMain() } else { |