New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
cmd/krel/cmd/ff: Initial commit of Go-based branchff tool #869
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") | ||
|
||
go_library( | ||
name = "go_default_library", | ||
srcs = ["main.go"], | ||
importpath = "k8s.io/release/cmd/krel", | ||
visibility = ["//visibility:private"], | ||
deps = ["//cmd/krel/cmd:go_default_library"], | ||
) | ||
|
||
go_binary( | ||
name = "krel", | ||
embed = [":go_default_library"], | ||
visibility = ["//visibility:public"], | ||
) | ||
|
||
filegroup( | ||
name = "package-srcs", | ||
srcs = glob(["**"]), | ||
tags = ["automanaged"], | ||
visibility = ["//visibility:private"], | ||
) | ||
|
||
filegroup( | ||
name = "all-srcs", | ||
srcs = [ | ||
":package-srcs", | ||
"//cmd/krel/cmd:all-srcs", | ||
], | ||
tags = ["automanaged"], | ||
visibility = ["//visibility:public"], | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
load("@io_bazel_rules_go//go:def.bzl", "go_library") | ||
|
||
go_library( | ||
name = "go_default_library", | ||
srcs = [ | ||
"ff.go", | ||
"root.go", | ||
], | ||
importpath = "k8s.io/release/cmd/krel/cmd", | ||
visibility = ["//visibility:public"], | ||
deps = [ | ||
"//pkg/util:go_default_library", | ||
"@com_github_spf13_cobra//:go_default_library", | ||
"@in_gopkg_src_d_go_git_v4//:go_default_library", | ||
"@in_gopkg_src_d_go_git_v4//plumbing:go_default_library", | ||
], | ||
) | ||
|
||
filegroup( | ||
name = "package-srcs", | ||
srcs = glob(["**"]), | ||
tags = ["automanaged"], | ||
visibility = ["//visibility:private"], | ||
) | ||
|
||
filegroup( | ||
name = "all-srcs", | ||
srcs = [":package-srcs"], | ||
tags = ["automanaged"], | ||
visibility = ["//visibility:public"], | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,316 @@ | ||
/* | ||
Copyright 2019 The Kubernetes Authors. | ||
|
||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
|
||
http://www.apache.org/licenses/LICENSE-2.0 | ||
|
||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
|
||
package cmd | ||
|
||
import ( | ||
"bytes" | ||
"fmt" | ||
"io/ioutil" | ||
"log" | ||
"os" | ||
"os/exec" | ||
"path/filepath" | ||
"strings" | ||
|
||
"github.com/spf13/cobra" | ||
"gopkg.in/src-d/go-git.v4" | ||
"gopkg.in/src-d/go-git.v4/plumbing" | ||
|
||
"k8s.io/release/pkg/util" | ||
) | ||
|
||
var cfgFile string | ||
|
||
const defaultMasterRef string = "HEAD" | ||
|
||
type Options struct { | ||
branch string | ||
masterRef string | ||
org string | ||
nomock bool | ||
cleanup bool | ||
} | ||
|
||
var opts = &Options{} | ||
|
||
// ffCmd represents the base command when called without any subcommands | ||
var ffCmd = &cobra.Command{ | ||
Use: "ff --branch <release-branch> [--ref <master-ref>] [--nomock] [--cleanup]", | ||
Short: "ff fast forwards a Kubernetes release branch", | ||
Long: `ff fast forwards a branch to a specified master object reference | ||
(defaults to HEAD), and then prepares the branch as a Kubernetes release branch: | ||
|
||
- Run hack/update-all.sh to ensure compliance of generated files`, | ||
Example: "krel ff --branch release-1.16 39d0135e --ref HEAD --cleanup", | ||
RunE: func(cmd *cobra.Command, args []string) error { | ||
if err := runFf(opts); err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
}, | ||
} | ||
|
||
func init() { | ||
cobra.OnInitialize(initConfig) | ||
|
||
ffCmd.PersistentFlags().StringVar(&opts.branch, "branch", "", "branch") | ||
ffCmd.PersistentFlags().StringVar(&opts.masterRef, "ref", defaultMasterRef, "ref on master") | ||
ffCmd.PersistentFlags().StringVar(&opts.org, "org", util.DefaultGithubOrg, "org to run tool against") | ||
|
||
rootCmd.AddCommand(ffCmd) | ||
} | ||
|
||
func runFf(opts *Options) error { | ||
// TODO: Add usage/help | ||
// TODO: Check prerequisites (git, jq, go, make) | ||
// TODO: Set positional args | ||
// TODO: Fail on empty branch | ||
// TODO: Fail on GITHUB_TOKEN not set | ||
|
||
branch := opts.branch | ||
masterRef := opts.masterRef | ||
remote := util.DefaultRemote | ||
remoteMaster := fmt.Sprintf("%s/%s", remote, "master") | ||
|
||
log.Printf("Preparing to fast-forward master@%s onto the %s branch...\n", masterRef, branch) | ||
|
||
nomock := opts.nomock | ||
dryRunFlag := "--dry-run" | ||
if nomock { | ||
// TODO: Set this to empty string once we're ready to turn on the tool | ||
log.Println("Running in no-mock mode!") | ||
dryRunFlag = "--dry-run" | ||
} | ||
|
||
isReleaseBranch := util.IsReleaseBranch(branch) | ||
if !isReleaseBranch { | ||
log.Fatalf("%s is not a release branch\n", branch) | ||
} | ||
|
||
branchExists, err := util.BranchExists(branch) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we return either a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Still returning both, but the error is handled now. |
||
if err != nil { | ||
return err | ||
} | ||
if !branchExists { | ||
log.Fatalf("the %s branch does not exist\n", branch) | ||
} | ||
|
||
baseDir, err := ioutil.TempDir("", "ff-") | ||
if err != nil { | ||
return err | ||
} | ||
|
||
cleanup := opts.cleanup | ||
if cleanup { | ||
defer cleanupTmpDir(baseDir) | ||
} | ||
|
||
workingDir := filepath.Join(baseDir, branch) | ||
log.Printf("%s", workingDir) | ||
|
||
os.Setenv("GOPATH", workingDir) | ||
log.Printf("GOPATH: %s", os.Getenv("GOPATH")) | ||
|
||
gitRoot := fmt.Sprintf("%s/src/k8s.io/kubernetes", workingDir) | ||
|
||
// TODO: nomock? | ||
if nomock { | ||
log.Printf("nomock mode (from within ff)\n") | ||
} | ||
|
||
// TODO: If workingDir exists, prompt user to delete | ||
// TODO: Tweak file permissions (dir + user rwx) | ||
workingDirErr := os.MkdirAll(workingDir, os.ModePerm) | ||
if workingDirErr != nil { | ||
return err | ||
} | ||
|
||
// TODO: Remove once SyncRepo works | ||
gitRoot = "/tmp/ff-465649664/release-1.16/src/k8s.io/kubernetes" | ||
|
||
syncErr := util.SyncRepo(util.KubernetesGitHubAuthURL, gitRoot) | ||
if syncErr != nil { | ||
return syncErr | ||
} | ||
|
||
chdirErr := os.Chdir(gitRoot) | ||
if chdirErr != nil { | ||
return chdirErr | ||
} | ||
|
||
repo, repoErr := git.PlainOpen(gitRoot) | ||
if repoErr != nil { | ||
return repoErr | ||
} | ||
|
||
mergeBase, err := util.GetMergeBase("master", branch, repo) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// TODO: Rewrite using go-git | ||
comparedCommits := []string{mergeBase, remoteMaster} | ||
var tags []string | ||
for _, commit := range comparedCommits { | ||
cmd := exec.Command("git", "describe", "--abbrev=0", "--tags", commit) | ||
cmd.Stdin = strings.NewReader("some input") | ||
var out bytes.Buffer | ||
cmd.Stdout = &out | ||
err = cmd.Run() | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
log.Printf("in all caps: %q\n", out.String()) | ||
tags = append(tags, strings.TrimSuffix(out.String(), "\n")) | ||
} | ||
|
||
// TODO: This should return an error if it fails | ||
// TODO: Provide more information to debug here | ||
if tags[0] != tags[1] { | ||
log.Printf("%s did not match %s", tags[0], tags[1]) | ||
} | ||
|
||
// TODO: Rewrite using go-git | ||
// --dry-run appears to be unsupported for git push, so we shell out here. | ||
checkPushCmd := exec.Command("git", "push", "-q", "--dry-run", util.KubernetesGitHubAuthURL) | ||
util.Run(checkPushCmd) | ||
|
||
w, err := repo.Worktree() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// ... checking out to commit | ||
//Info("git checkout %s", commit) | ||
remoteHash, err := repo.ResolveRevision(plumbing.Revision(fmt.Sprintf("%s/%s", remote, branch))) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
err = w.Checkout(&git.CheckoutOptions{ | ||
Hash: plumbing.NewHash(remoteHash.String()), | ||
Branch: plumbing.NewBranchReferenceName(branch), | ||
Create: true, | ||
}) | ||
|
||
if err != nil { | ||
return err | ||
} | ||
|
||
// TODO: Merge and update | ||
mergeCmd := exec.Command("git", "merge", "-X", "ours", remoteMaster) | ||
util.Run(mergeCmd) | ||
|
||
// TODO: Check for deleted files | ||
// TODO: Do we need hack/install-etcd.sh | ||
installEtcd := exec.Command("hack/install-etcd.sh") | ||
util.Run(installEtcd) | ||
|
||
currentPath := os.Getenv("PATH") | ||
etcdDir := filepath.Join(gitRoot, "third_party/etcd") | ||
newPath := fmt.Sprintf("%s:%s", etcdDir, currentPath) | ||
os.Setenv("PATH", newPath) | ||
log.Printf("PATH has been set to %s", os.Getenv("PATH")) | ||
|
||
// TODO: Running update scripts fails with go1.13 | ||
log.Printf("Running hack/update* scripts...") | ||
updateScripts := []string{ | ||
"update-openapi-spec", | ||
} | ||
/* | ||
for _, script := range updateScripts { | ||
scriptPath := fmt.Sprintf("hack/%s.sh", script) | ||
if _, err := os.Stat(scriptPath); os.IsNotExist(err) { | ||
log.Printf("The update script (%s) does not exist. Skipping...", scriptPath) | ||
continue | ||
} | ||
|
||
scriptCmd := exec.Command(scriptPath) | ||
log.Printf("Running %s...", scriptPath) | ||
util.Run(scriptCmd) | ||
} | ||
*/ | ||
|
||
status, err := w.Status() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if !status.IsClean() { | ||
log.Printf("Commit changes:") | ||
// TODO: Rewrite using go-git | ||
gitAdd := exec.Command("git", "add", "-A") | ||
util.Run(gitAdd) | ||
|
||
// TODO: Rewrite using go-git | ||
gitCommit := exec.Command("git", "commit", "-am", fmt.Sprintf("Results of running update scripts: %s", strings.Join(updateScripts, ","))) | ||
util.Run(gitCommit) | ||
} | ||
|
||
releaseRefName := remoteHash.String() | ||
releaseRev, err := util.RevParseShort(releaseRefName, gitRoot) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
headRev, err := util.RevParseShort("HEAD", gitRoot) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
log.Printf("%s", prepushMessage(gitRoot, remote, branch, util.KubernetesGitHubURL, releaseRev, headRev)) | ||
|
||
_, pushUpstream, err := util.Ask("Are you ready to push the local branch fast-forward changes upstream? Please only answer after you have validated the changes.", "yes", 3) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if pushUpstream { | ||
log.Printf("Pushing %s %s branch upstream: ", dryRunFlag, branch) | ||
//git push $DRYRUN_FLAG origin $RELEASE_BRANCH:$RELEASE_BRANCH | ||
// TODO: Need to handle https and ssh auth sanely | ||
gitPushCmd := exec.Command("git", "push", dryRunFlag, remote, branch) //fmt.Sprintf("%s:%s", branch, branch)) | ||
util.Run(gitPushCmd) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func prepushMessage(gitRoot, remote, branch, githubURL, releaseRev, headRev string) string { | ||
message := `Go look around in %s to make sure things look okay before pushing... | ||
|
||
Check for files left uncommitted using: | ||
|
||
git status -s | ||
|
||
Validate the fast-forward commit using: | ||
|
||
git show | ||
|
||
Validate the changes pulled in from master using: | ||
|
||
git log %s/%s..HEAD | ||
|
||
Once the branch fast-forward is complete, the diff will be available after push at: | ||
|
||
%s/compare/%s...%s" | ||
|
||
` | ||
|
||
return message | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I’m wondering if we need bazel at all if we stick to go modules and a recent version (>1.12).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@saschagrunert -- Though I grumble on occasion about bazel, we get the advantage of leveraging the verify tests from k/repo-infra.
Updating & verifying all things is now simple:
./hack/update-all.sh
./hack/verify-all.sh
See here: #889, #890
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It, being the verify, does not seem to work for me: #908
It might well be an issue on my side, could someone maybe check if that actually works for you?