From a2ed15ffb26c8da2be2beac719e89f2126c77666 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Mon, 30 Oct 2017 08:58:26 +0100 Subject: [PATCH] cmd: crio-release: initial commit Signed-off-by: Antonio Murdaca --- .gitignore | 1 + Makefile | 9 +- cmd/crio-release/build.go | 43 +++++++++ cmd/crio-release/main.go | 115 ++++++++++++++++++++++++ cmd/crio-release/template.go | 35 ++++++++ cmd/crio-release/util.go | 165 +++++++++++++++++++++++++++++++++++ releases/v1.0.1.toml | 38 ++++++++ 7 files changed, 403 insertions(+), 3 deletions(-) create mode 100644 cmd/crio-release/build.go create mode 100644 cmd/crio-release/main.go create mode 100644 cmd/crio-release/template.go create mode 100644 cmd/crio-release/util.go create mode 100644 releases/v1.0.1.toml diff --git a/.gitignore b/.gitignore index f9c8e7d9..c1c7f393 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ /docs/*.[158] /docs/*.[158].gz /kpod +/crio-release /crioctl /crio /crio.conf diff --git a/Makefile b/Makefile index 27bd3725..7e2b837d 100644 --- a/Makefile +++ b/Makefile @@ -45,7 +45,7 @@ help: @echo "Usage: make " @echo @echo " * 'install' - Install binaries to system locations" - @echo " * 'binaries' - Build crio, conmon and crioctl" + @echo " * 'binaries' - Build crio, conmon, crio-release and crioctl" @echo " * 'integration' - Execute integration tests" @echo " * 'clean' - Clean artifacts" @echo " * 'lint' - Execute the source code linter" @@ -86,6 +86,9 @@ crio: .gopathok $(shell hack/find-godeps.sh $(GOPKGDIR) cmd/crio $(PROJECT)) crioctl: .gopathok $(shell hack/find-godeps.sh $(GOPKGDIR) cmd/crioctl $(PROJECT)) $(GO) build $(LDFLAGS) -tags "$(BUILDTAGS)" -o $@ $(PROJECT)/cmd/crioctl +crio-release: .gopathok + $(GO) build -o $@ $(PROJECT)/cmd/crio-release + kpod: .gopathok $(shell hack/find-godeps.sh $(GOPKGDIR) cmd/kpod $(PROJECT)) $(GO) build $(LDFLAGS) -tags "$(BUILDTAGS)" -o $@ $(PROJECT)/cmd/kpod @@ -101,7 +104,7 @@ endif rm -fr test/testdata/redis-image find . -name \*~ -delete find . -name \#\* -delete - rm -f crioctl crio kpod + rm -f crioctl crio kpod crio-release make -C conmon clean make -C pause clean rm -f test/bin2img/bin2img @@ -123,7 +126,7 @@ testunit: localintegration: clean binaries ./test/test_runner.sh ${TESTFLAGS} -binaries: crio crioctl kpod conmon pause test/bin2img/bin2img test/copyimg/copyimg test/checkseccomp/checkseccomp +binaries: crio crio-release crioctl kpod conmon pause test/bin2img/bin2img test/copyimg/copyimg test/checkseccomp/checkseccomp MANPAGES_MD := $(wildcard docs/*.md) MANPAGES := $(MANPAGES_MD:%.md=%) diff --git a/cmd/crio-release/build.go b/cmd/crio-release/build.go new file mode 100644 index 00000000..e4129417 --- /dev/null +++ b/cmd/crio-release/build.go @@ -0,0 +1,43 @@ +package main + +import ( + "crypto/sha256" + "encoding/hex" + "fmt" + "io" + "os" + "os/exec" + "runtime" +) + +func build() error { + out, err := exec.Command("make").CombinedOutput() + if err != nil { + return fmt.Errorf("%s: %s", err, out) + } + return nil +} + +const tarFormat = "cri-o-%s.%s-%s.tar.gz" + +func tarRelease(tag string) (string, error) { + path := fmt.Sprintf(tarFormat, tag, runtime.GOOS, runtime.GOARCH) + out, err := exec.Command("tar", "-zcf", path, "bin/").CombinedOutput() + if err != nil { + return "", fmt.Errorf("%s: %s", err, out) + } + return path, nil +} + +func hash(path string) (string, error) { + f, err := os.Open(path) + if err != nil { + return "", err + } + defer f.Close() + s := sha256.New() + if _, err := io.Copy(s, f); err != nil { + return "", err + } + return hex.EncodeToString(s.Sum(nil)), nil +} diff --git a/cmd/crio-release/main.go b/cmd/crio-release/main.go new file mode 100644 index 00000000..eca4f76a --- /dev/null +++ b/cmd/crio-release/main.go @@ -0,0 +1,115 @@ +package main + +import ( + "fmt" + "os" + "text/template" + + "github.com/sirupsen/logrus" + "github.com/urfave/cli" +) + +const vendorConf = "vendor.conf" + +type note struct { + Title string `toml:"title"` + Description string `toml:"description"` +} + +type change struct { + Commit string `toml:"commit"` + Description string `toml:"description"` +} + +type dependency struct { + Name string + Commit string + Previous string +} + +type download struct { + Filename string + Hash string +} + +type release struct { + Commit string `toml:"commit"` + Previous string `toml:"previous"` + PreRelease bool `toml:"pre_release"` + Preface string `toml:"preface"` + Notes map[string]note `toml:"notes"` + BreakingChanges map[string]change `toml:"breaking"` + // generated fields + Changes []change + Contributors []string + Dependencies []dependency + Version string + Downloads []download +} + +func main() { + app := cli.NewApp() + app.Name = "crio-release" + app.Description = `release tooling for CRI-O. + +This tool should be ran from the root of the CRI-O repository for a new release. +` + app.Flags = []cli.Flag{ + cli.BoolFlag{ + Name: "dry,n", + Usage: "run the release tooling as a dry run to print the release notes to stdout", + }, + } + app.Action = func(context *cli.Context) error { + logrus.Info("Welcome to the CRI-O release tool...") + var ( + path = context.Args().First() + tag = parseTag(path) + ) + r, err := loadRelease(path) + if err != nil { + return err + } + previous, err := getPreviousDeps(r.Previous) + if err != nil { + return err + } + changes, err := changelog(r.Previous, r.Commit) + if err != nil { + return err + } + logrus.Infof("creating new release %s with %d new changes...", tag, len(changes)) + rd, err := fileFromRev(r.Commit, vendorConf) + if err != nil { + return err + } + deps, err := parseDependencies(rd) + if err != nil { + return err + } + updatedDeps := updatedDeps(previous, deps) + contributors, err := getContributors(r.Previous, r.Commit) + if err != nil { + return err + } + // update the release fields with generated data + r.Contributors = contributors + r.Dependencies = updatedDeps + r.Changes = changes + r.Version = tag + + if context.Bool("dry") { + t, err := template.New("release-notes").Parse(releaseNotes) + if err != nil { + return err + } + return t.Execute(os.Stdout, r) + } + logrus.Info("release complete!") + return nil + } + if err := app.Run(os.Args); err != nil { + fmt.Fprint(os.Stderr, err) + os.Exit(1) + } +} diff --git a/cmd/crio-release/template.go b/cmd/crio-release/template.go new file mode 100644 index 00000000..cae3a971 --- /dev/null +++ b/cmd/crio-release/template.go @@ -0,0 +1,35 @@ +package main + +const releaseNotes = `Welcome to the release of CRI-O {{.Version}}! +{{if .PreRelease}} +*This is a pre-release of CRI-O* +{{- end}} + +{{.Preface}} + +Please try out the release binaries and report any issues at +https://github.com/kubernetes-incubator/cri-o/issues. + +{{range $note := .Notes}} +### {{$note.Title}} + +{{$note.Description}} +{{- end}} + +### Contributors +{{range $contributor := .Contributors}} +* {{$contributor}} +{{- end}} + +### Changes +{{range $change := .Changes}} +* {{$change.Commit}} {{$change.Description}} +{{- end}} + +### Dependency Changes + +Previous release can be found at [{{.Previous}}](https://github.com/kubernetes-incubator/cri-o/releases/tag/{{.Previous}}) +{{range $dep := .Dependencies}} +* {{$dep.Previous}} -> {{$dep.Commit}} **{{$dep.Name}}** +{{- end}} +` diff --git a/cmd/crio-release/util.go b/cmd/crio-release/util.go new file mode 100644 index 00000000..e041822b --- /dev/null +++ b/cmd/crio-release/util.go @@ -0,0 +1,165 @@ +package main + +import ( + "bufio" + "bytes" + "errors" + "fmt" + "io" + "os" + "os/exec" + "path/filepath" + "sort" + "strings" + + "github.com/BurntSushi/toml" +) + +func loadRelease(path string) (*release, error) { + var r release + if _, err := toml.DecodeFile(path, &r); err != nil { + if os.IsNotExist(err) { + return nil, errors.New("please specify the release file as the first argument") + } + return nil, err + } + return &r, nil +} + +func parseTag(path string) string { + return strings.TrimSuffix(filepath.Base(path), ".toml") +} + +func parseDependencies(r io.Reader) ([]dependency, error) { + var deps []dependency + s := bufio.NewScanner(r) + for s.Scan() { + ln := strings.TrimSpace(s.Text()) + if strings.HasPrefix(ln, "#") || ln == "" { + continue + } + cidx := strings.Index(ln, "#") + if cidx > 0 { + ln = ln[:cidx] + } + ln = strings.TrimSpace(ln) + parts := strings.Fields(ln) + if len(parts) != 2 && len(parts) != 3 { + return nil, fmt.Errorf("invalid config format: %s", ln) + } + deps = append(deps, dependency{ + Name: parts[0], + Commit: parts[1], + }) + } + if err := s.Err(); err != nil { + return nil, err + } + return deps, nil +} + +func getPreviousDeps(previous string) ([]dependency, error) { + r, err := fileFromRev(previous, vendorConf) + if err != nil { + return nil, err + } + return parseDependencies(r) +} + +func changelog(previous, commit string) ([]change, error) { + raw, err := getChangelog(previous, commit) + if err != nil { + return nil, err + } + return parseChangelog(raw) +} + +func getChangelog(previous, commit string) ([]byte, error) { + return git("log", "--oneline", fmt.Sprintf("%s..%s", previous, commit)) +} + +func parseChangelog(changelog []byte) ([]change, error) { + var ( + changes []change + s = bufio.NewScanner(bytes.NewReader(changelog)) + ) + for s.Scan() { + fields := strings.Fields(s.Text()) + changes = append(changes, change{ + Commit: fields[0], + Description: strings.Join(fields[1:], " "), + }) + } + if err := s.Err(); err != nil { + return nil, err + } + return changes, nil +} + +func fileFromRev(rev, file string) (io.Reader, error) { + p, err := git("show", fmt.Sprintf("%s:%s", rev, file)) + if err != nil { + return nil, err + } + + return bytes.NewReader(p), nil +} + +func git(args ...string) ([]byte, error) { + o, err := exec.Command("git", args...).CombinedOutput() + if err != nil { + return nil, fmt.Errorf("%s: %s", err, o) + } + return o, nil +} + +func updatedDeps(previous, deps []dependency) []dependency { + var updated []dependency + pm, cm := toDepMap(previous), toDepMap(deps) + for name, c := range cm { + d, ok := pm[name] + if !ok { + // it is a new dep and should be noted + updated = append(updated, c) + continue + } + // it exists, see if its updated + if d.Commit != c.Commit { + // set the previous commit + c.Previous = d.Commit + updated = append(updated, c) + } + } + return updated +} + +func toDepMap(deps []dependency) map[string]dependency { + out := make(map[string]dependency) + for _, d := range deps { + out[d.Name] = d + } + return out +} + +func getContributors(previous, commit string) ([]string, error) { + raw, err := git("log", "--format=%aN", fmt.Sprintf("%s..%s", previous, commit)) + if err != nil { + return nil, err + } + var ( + set = make(map[string]struct{}) + s = bufio.NewScanner(bytes.NewReader(raw)) + out []string + ) + for s.Scan() { + set[s.Text()] = struct{}{} + } + if err := s.Err(); err != nil { + return nil, err + } + for name := range set { + out = append(out, name) + } + sort.Strings(out) + return out, nil +} diff --git a/releases/v1.0.1.toml b/releases/v1.0.1.toml new file mode 100644 index 00000000..0852b7e8 --- /dev/null +++ b/releases/v1.0.1.toml @@ -0,0 +1,38 @@ +# commit to be tagged for new release +commit = "HEAD" + +# previous release +previous = "v1.0.0" + +pre_release = false + +preface = """\ +This releases is the first patch release in the v1.0.0 release. It includes +bugfixes and performance fixes. No new features have been added.""" + +# notable prs to include in the release notes, 1234 is the pr number +#[notes] + #[notes.gc] + #title = "Garbage Collection" + #description = """\ +#Full garbage collection support for cleaning up content, snapshots and metadata +#for containerd resources. The implementation is based on well-known concurrent +#mark and sweep and the approach allows for extensibility in collection +#policies. + +#The GC is triggered `Container.Delete and Image.Delete. Note that images may +#need to be re-pulled for proper support.""" + + #[notes.migrations] + #title = "Migrations" + #description = """\ +#A lightweight migration framework is now part of containerd. This allows us to +#safely evolve the schema between releases with the groundwork laid early. + +#As part of this release, a migration will be run to add back references in +#support of garbage collection.""" + +#[breaking] + #[breaking.metrics] + #pr = 1235 + #description = """ """