From c2ca37579d1507d0f14c0fe6d755bc2a0bfb1e91 Mon Sep 17 00:00:00 2001 From: Ashlesha Atrey Date: Tue, 10 Sep 2019 09:48:35 -0700 Subject: [PATCH] Implement mixer reset command It reverts the mix to a previous version in case of a build failure or in case the user wants to roll back to a specific version. By default, the command will bring back the mix to the same state it have when it last build a successful mix. This value can be overridden if a `--to` flag is provided with a version number. The command will not be destructive unless a `--clean` flag is provided specified. If so, mixer will delete all files associated with versions that are bigger than the one provided. If not, only the values of the state files will change, but the files will be kept. Signed-off-by: Ashlesha Atrey --- Makefile | 3 +- bat/tests/reset-command/Makefile | 10 + bat/tests/reset-command/description.txt | 7 + bat/tests/reset-command/run.bats | 34 +++ builder/build_validate.go | 8 +- builder/format.go | 7 +- docs/mixer.1 | 14 ++ docs/mixer.1.rst | 10 + docs/mixer.reset.1 | 73 +++++++ docs/mixer.reset.1.rst | 56 +++++ mixer/cmd/reset.go | 274 ++++++++++++++++++++++++ 11 files changed, 488 insertions(+), 8 deletions(-) create mode 100644 bat/tests/reset-command/Makefile create mode 100644 bat/tests/reset-command/description.txt create mode 100644 bat/tests/reset-command/run.bats create mode 100644 docs/mixer.reset.1 create mode 100644 docs/mixer.reset.1.rst create mode 100644 mixer/cmd/reset.go diff --git a/Makefile b/Makefile index 552508532..1cfd1f5d6 100644 --- a/Makefile +++ b/Makefile @@ -78,7 +78,8 @@ MANPAGES = \ docs/mixer.init.1 \ docs/mixer.repo.1 \ docs/mixer.versions.1 \ - docs/mixin.1 + docs/mixin.1 \ + docs/mixer.reset.1 man: $(MANPAGES) diff --git a/bat/tests/reset-command/Makefile b/bat/tests/reset-command/Makefile new file mode 100644 index 000000000..3a1436da4 --- /dev/null +++ b/bat/tests/reset-command/Makefile @@ -0,0 +1,10 @@ +.PHONY: check clean + +check: + bats ./run.bats + +CLEANDIRS = ./update ./test-chroot ./logs ./.repos ./bundles ./update ./mix-bundles ./clr-bundles ./local-yum ./results ./repodata ./local-rpms ./upstream-bundles ./local-bundles +CLEANFILES = ./*.log ./run.bats.trs ./yum.conf.in ./builder.conf ./mixer.state ./.{c,m}* *.pem .yum-mix.conf mixversion upstreamurl upstreamversion mixbundles +clean: + sudo rm -rf $(CLEANDIRS) $(CLEANFILES) + diff --git a/bat/tests/reset-command/description.txt b/bat/tests/reset-command/description.txt new file mode 100644 index 000000000..3378a5b3f --- /dev/null +++ b/bat/tests/reset-command/description.txt @@ -0,0 +1,7 @@ +reset +==================== +This test attempts to create multiple mixes of different versions +in different format. It then tries to revert the mix to a previous stable +version. If clean flag is set, mixer will delete all files associated with +versions that are bigger than the one provided. + diff --git a/bat/tests/reset-command/run.bats b/bat/tests/reset-command/run.bats new file mode 100644 index 000000000..b77f7809f --- /dev/null +++ b/bat/tests/reset-command/run.bats @@ -0,0 +1,34 @@ +#!/usr/bin/env bats + +# shared test functions +load ../../lib/mixerlib + +setup() { + global_setup +} + +@test "reset" { + mixer-init-stripped-down latest 10 + sudo mixer build all --format 1 --native + mixer-versions-update 20 latest + sudo mixer build all --format 2 --native + mixer-versions-update 30 latest + sudo mixer build all --format 3 --native + #check LAST_VER and PREVIOUS_MIX_VERSION match + test $(< update/image/LAST_VER) -eq 30 + test $(sed -n 's/[ ]*PREVIOUS_MIX_VERSION[ ="]*\([0-9]\+\)[ "]*/\1/p' mixer.state) -eq 20 + sudo mixer reset --to 20 + #check LAST_VER and PREVIOUS_MIX_VERSION match + test $(< update/image/LAST_VER) -eq 20 + test $(sed -n 's/[ ]*PREVIOUS_MIX_VERSION[ ="]*\([0-9]\+\)[ "]*/\1/p' mixer.state) -eq 10 + test -d "./update/www/30" + test -d "./update/image/30" + sudo mixer reset --to 10 --clean + #check LAST_VER and PREVIOUS_MIX_VERSION match + test $(< update/image/LAST_VER) -eq 10 + test $(sed -n 's/[ ]*PREVIOUS_MIX_VERSION[ ="]*\([0-9]\+\)[ "]*/\1/p' mixer.state) -eq 0 + test ! -d "./update/www/20" + test ! -d "./update/image/30" + } +# vi: ft=sh ts=8 sw=2 sts=2 et tw=80 + diff --git a/builder/build_validate.go b/builder/build_validate.go index e06c1146f..c666586f7 100644 --- a/builder/build_validate.go +++ b/builder/build_validate.go @@ -272,7 +272,7 @@ func (b *Builder) mcaPkgInfo(manifests []*swupd.Manifest, version, downloadRetri repoURIs := make(map[string]string) // Download RPMs from correct upstream version - upstreamVer, err := b.getLocalUpstreamVersion(strconv.Itoa(version)) + upstreamVer, err := b.GetLocalUpstreamVersion(strconv.Itoa(version)) if err != nil { return nil, err } @@ -1126,7 +1126,7 @@ func checkMcaFbErrors(b *Builder, diffErrors []string, fromVer, toVer int) ([]st // isPlus10Version determines whether the version is the last version in a format. func (b *Builder) isPlus10Version(ver int) (bool, error) { verStr := strconv.Itoa(ver) - format, err := b.getFormatForVersion(verStr) + format, err := b.GetFormatForVersion(verStr) if err != nil { return false, err } @@ -1146,11 +1146,11 @@ func (b *Builder) isPlus10Version(ver int) (bool, error) { // checkFormatsMatch determines whether two versions are in the same format. func (b *Builder) checkFormatsMatch(fromVer, toVer int) (bool, error) { - fromFormat, err := b.getFormatForVersion(strconv.Itoa(fromVer)) + fromFormat, err := b.GetFormatForVersion(strconv.Itoa(fromVer)) if err != nil { return false, err } - toFormat, err := b.getFormatForVersion(strconv.Itoa(toVer)) + toFormat, err := b.GetFormatForVersion(strconv.Itoa(toVer)) if err != nil { return false, err } diff --git a/builder/format.go b/builder/format.go index 92d6ea330..7b46b29cf 100644 --- a/builder/format.go +++ b/builder/format.go @@ -72,8 +72,8 @@ func (b *Builder) getLastBuildUpstreamVersion() (string, error) { return strings.TrimSpace(string(lastVer)), nil } -// getFormatForVersion returns the format for the provided local version -func (b *Builder) getFormatForVersion(version string) (string, error) { +// GetFormatForVersion returns the format for the provided local version +func (b *Builder) GetFormatForVersion(version string) (string, error) { var format []byte var err error @@ -98,7 +98,8 @@ func (b *Builder) getLatestForFormat(format string) (string, error) { return strings.TrimSpace(string(lastVer)), nil } -func (b *Builder) getLocalUpstreamVersion(version string) (string, error) { +// GetLocalUpstreamVersion returns the upstream version for the provided version +func (b *Builder) GetLocalUpstreamVersion(version string) (string, error) { var localUpstreamVer []byte var err error diff --git a/docs/mixer.1 b/docs/mixer.1 index e4202ec57..bc2269254 100644 --- a/docs/mixer.1 +++ b/docs/mixer.1 @@ -149,6 +149,18 @@ upstream available. Also allows the user to update mix and upstream versions. See \fBmixer.versions\fP(1) for more details. .UNINDENT .UNINDENT +.sp +\fBreset\fP +.INDENT 0.0 +.INDENT 3.5 +Reverts a mix state to the end of the last build or to the end +of a given version build if one is provided. By default, the value +of PREVIOUS_MIX_VERSION in mixer.state will be used to define the +last build. This command can be used to roll back the mixer state +in case of a build failure or in case the user wants to roll back +to a previous version. See \fBmixer.reset\fP(1) for more details. +.UNINDENT +.UNINDENT .SH FILES .sp \fI/builder.conf\fP @@ -184,6 +196,8 @@ On success, 0 is returned. A non\-zero return code indicates a failure. .IP \(bu 2 \fBmixer.versions\fP(1) .IP \(bu 2 +\fBmixer.reset\fP(1) +.IP \(bu 2 \fBswupd\fP(1) .IP \(bu 2 \fBos\-format\fP(7) diff --git a/docs/mixer.1.rst b/docs/mixer.1.rst index 7ff5f669f..66d0a86a7 100644 --- a/docs/mixer.1.rst +++ b/docs/mixer.1.rst @@ -114,6 +114,15 @@ SUBCOMMANDS upstream available. Also allows the user to update mix and upstream versions. See ``mixer.versions``\(1) for more details. +``reset`` + + Reverts a mix state to the end of the last build or to the end + of a given version build if one is provided. By default, the value + of PREVIOUS_MIX_VERSION in mixer.state will be used to define the + last build. This command can be used to roll back the mixer state + in case of a build failure or in case the user wants to roll back + to a previous version. See ``mixer.reset``\(1) for more details. + FILES ===== @@ -142,6 +151,7 @@ SEE ALSO * ``mixer.init``\(1) * ``mixer.repo``\(1) * ``mixer.versions``\(1) +* ``mixer.reset``\(1) * ``swupd``\(1) * ``os-format``\(7) * https://github.com/clearlinux/mixer-tools diff --git a/docs/mixer.reset.1 b/docs/mixer.reset.1 new file mode 100644 index 000000000..decc2c636 --- /dev/null +++ b/docs/mixer.reset.1 @@ -0,0 +1,73 @@ +.\" Man page generated from reStructuredText. +. +.TH MIXER.RESET 1 "" "" "" +.SH NAME +mixer.reset \- Reset mixer to a given or previous version +. +.nr rst2man-indent-level 0 +. +.de1 rstReportMargin +\\$1 \\n[an-margin] +level \\n[rst2man-indent-level] +level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] +- +\\n[rst2man-indent0] +\\n[rst2man-indent1] +\\n[rst2man-indent2] +.. +.de1 INDENT +.\" .rstReportMargin pre: +. RS \\$1 +. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] +. nr rst2man-indent-level +1 +.\" .rstReportMargin post: +.. +.de UNINDENT +. RE +.\" indent \\n[an-margin] +.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] +.nr rst2man-indent-level -1 +.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] +.in \\n[rst2man-indent\\n[rst2man-indent-level]]u +.. +.SH SYNOPSIS +.sp +\fBmixer reset [flags]\fP +.SH DESCRIPTION +.sp +Reverts a mix state to the end of the last build or to the end +of a given version build if one is provided. By default, the value +of PREVIOUS_MIX_VERSION in mixer.state will be used to define the +last build. This command can be used to roll back the mixer state +in case of a build failure or in case the user wants to roll back +to a previous version. +.SH OPTIONS +.sp +In addition to the globally recognized \fBmixer\fP flags (see \fBmixer\fP(1) for +more details), the following options are recognized. +.INDENT 0.0 +.IP \(bu 2 +\fB\-\-to\fP +.sp +Reverts the mix to the version provided by the flag +.IP \(bu 2 +\fB\-\-clean\fP +.sp +Delete all files associated with versions that are bigger than the one provided. +.IP \(bu 2 +\fB\-h, \-\-help\fP +.sp +Display \fBreset\fP help information and exit. +.UNINDENT +.SH EXIT STATUS +.sp +On success, 0 is returned. A non\-zero return code indicates a failure. +.SS SEE ALSO +.INDENT 0.0 +.IP \(bu 2 +\fBmixer\fP(1) +.UNINDENT +.SH COPYRIGHT +(C) 2019 Intel Corporation, CC-BY-SA-3.0 +.\" Generated by docutils manpage writer. +. diff --git a/docs/mixer.reset.1.rst b/docs/mixer.reset.1.rst new file mode 100644 index 000000000..fc214a92d --- /dev/null +++ b/docs/mixer.reset.1.rst @@ -0,0 +1,56 @@ +=========== +mixer.reset +=========== + +------------------------------------------ +Reset mixer to a given or previous version +------------------------------------------ + +:Copyright: \(C) 2019 Intel Corporation, CC-BY-SA-3.0 +:Manual section: 1 + + +SYNOPSIS +======== + +``mixer reset [flags]`` + + +DESCRIPTION +=========== + +Reverts a mix state to the end of the last build or to the end +of a given version build if one is provided. By default, the value +of PREVIOUS_MIX_VERSION in mixer.state will be used to define the +last build. This command can be used to roll back the mixer state +in case of a build failure or in case the user wants to roll back +to a previous version. + +OPTIONS +======= + +In addition to the globally recognized ``mixer`` flags (see ``mixer``\(1) for +more details), the following options are recognized. + +- ``--to`` + + Reverts the mix to the version provided by the flag + +- ``--clean`` + + Delete all files associated with versions that are bigger than the one provided. + +- ``-h, --help`` + + Display ``reset`` help information and exit. + + +EXIT STATUS +=========== + +On success, 0 is returned. A non-zero return code indicates a failure. + +SEE ALSO +-------- + +* ``mixer``\(1) diff --git a/mixer/cmd/reset.go b/mixer/cmd/reset.go new file mode 100644 index 000000000..825af9af4 --- /dev/null +++ b/mixer/cmd/reset.go @@ -0,0 +1,274 @@ +// Copyright © 2019 Intel Corporation +// +// 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 ( + "fmt" + "io/ioutil" + "log" + "os" + "path/filepath" + "strconv" + "strings" + + "github.com/clearlinux/mixer-tools/swupd" + + "github.com/clearlinux/mixer-tools/builder" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var resetCmd = &cobra.Command{ + Use: "reset", + Short: "Revert mix to a previous version", + Long: `Revert the mix to a previous version. +Reverts a mix state to the end of the last build +or to the end of a given version build if one is provided. +By default, the value of PREVIOUS_MIX_VERSION +in mixer.state will be used to define the last build. +This command can be used to roll back the mixer state +in case of a build failure or in case the user +wants to roll back to a previous version.`, + RunE: runReset, +} + +type resetFlags struct { + toVersion int32 + clean bool +} + +var resetCmdFlags resetFlags + +func init() { + RootCmd.AddCommand(resetCmd) + + resetCmd.Flags().Int32Var(&resetCmdFlags.toVersion, "to", -1, "Reset to a specific mix version, default = PREVIOUS_MIX_VERSION") + resetCmd.Flags().BoolVar(&resetCmdFlags.clean, "clean", false, "Deletes all files with versions bigger than the one provided") +} + +func runReset(cmd *cobra.Command, args []string) error { + if err := checkRoot(); err != nil { + fail(err) + } + + b, err := builder.NewFromConfig(configFile) + if err != nil { + return err + } + + // if toVersion provided by the user, replace the mixer version + if resetCmdFlags.toVersion >= 0 { + b.MixVer = strconv.Itoa(int(resetCmdFlags.toVersion)) + b.MixVerUint32 = uint32(resetCmdFlags.toVersion) + } else { + // assuming mixer.state file has the correct info + b.MixVer = b.State.Mix.PreviousMixVer + b.MixVerUint32, err = parseUint32(b.State.Mix.PreviousMixVer) + if err != nil { + return err + } + fmt.Println("Reseting to default PREVIOUS_MIX_VERSION ", b.State.Mix.PreviousMixVer) + } + + if b.MixVer == "0" { + b.State.LoadDefaults(b.Config) + b.State.Mix.PreviousMixVer = "0" + if err := b.State.Save(); err != nil { + fail(err) + } + + if err := ioutil.WriteFile(filepath.Join(b.Config.Builder.VersionPath, b.MixVerFile), []byte("10"), 0644); err != nil { + return err + } + + if !resetCmdFlags.clean { + fmt.Println("Reset completed.") + return nil + } + err := os.RemoveAll(b.Config.Builder.ServerStateDir) + if err != nil { + log.Println(err) + return err + } + fmt.Println("Reset completed.") + return nil + } + + // find the new previous version by reading the Manifest.Mom file for the new current version + momFile := filepath.Join(b.Config.Builder.ServerStateDir, "www", b.MixVer, "Manifest.MoM") + mom, err := swupd.ParseManifestFile(momFile) + if err != nil { + return err + } + b.State.Mix.PreviousMixVer = strconv.Itoa(int(mom.Header.Previous)) + currentMixFormatInt := mom.Header.Format + + // Make sure FORMAT in mixer.state has the same value as update/www//format + var format string + if mom.Header.Previous != 0 { + format, err = b.GetFormatForVersion(b.State.Mix.PreviousMixVer) + if err != nil { + return err + } + } else { + format = strconv.Itoa(int(currentMixFormatInt)) + } + b.State.Mix.Format = strings.TrimSpace(format) + + // Change upstreamVersion file content to the new current version upstreamver + var lastStableMixUpstreamVersion string + lastStableMixUpstreamVersion, err = b.GetLocalUpstreamVersion(b.MixVer) + if err != nil { + return err + } + filename := filepath.Join(b.Config.Builder.ServerStateDir, "www", b.MixVer, "upstreamver") + if strings.TrimSpace(lastStableMixUpstreamVersion) != b.UpstreamVer { + // Set the upstream version to the previous format's latest version + b.UpstreamVer = strings.TrimSpace(lastStableMixUpstreamVersion) + b.UpstreamVerUint32, err = parseUint32(b.UpstreamVer) + if err != nil { + return errors.Wrapf(err, "Couldn't parse upstream version") + } + vFile := filepath.Join(b.Config.Builder.VersionPath, b.UpstreamVerFile) + if err = ioutil.WriteFile(vFile, []byte(b.UpstreamVer), 0644); err != nil { + return err + } + } + + // Change upstreamURL file content to the new current version upstreamurl + var lastStableMixUpstreamURL []byte + filename = filepath.Join(b.Config.Builder.ServerStateDir, "www", b.MixVer, "upstreamurl") + if lastStableMixUpstreamURL, err = ioutil.ReadFile(filename); err != nil { + return err + } + if strings.TrimSpace(string(lastStableMixUpstreamURL)) != b.UpstreamURL { + // Set the upstream version to the previous format's latest version + b.UpstreamURL = strings.TrimSpace(string(lastStableMixUpstreamURL)) + vFile := filepath.Join(b.Config.Builder.VersionPath, b.UpstreamURLFile) + if err = ioutil.WriteFile(vFile, []byte(b.UpstreamURL), 0644); err != nil { + return err + } + } + + // Change mixVersion to point to new mixer version + if err := ioutil.WriteFile(filepath.Join(b.Config.Builder.VersionPath, b.MixVerFile), []byte(b.MixVer), 0644); err != nil { + return err + } + + // Make sure update/image/LAST_VER points to new mixer version + if err = ioutil.WriteFile(filepath.Join(b.Config.Builder.ServerStateDir, "image", "LAST_VER"), []byte(b.MixVer), 0644); err != nil { + return fmt.Errorf("couldn't update LAST_VER file: %s", err) + } + + // Make sure update/image/format#/latest points to new mixer version + newFormat := "format" + strconv.Itoa(int(currentMixFormatInt)) + if err = ioutil.WriteFile(filepath.Join(b.Config.Builder.ServerStateDir, "/www/version/", newFormat, "latest"), []byte(b.MixVer), 0644); err != nil { + return fmt.Errorf("couldn't update latest file: %s", err) + } + //update the sig file for the latest + + // Make sure update/image/latest_version points to new mixer version + if err = ioutil.WriteFile(filepath.Join(b.Config.Builder.ServerStateDir, "/www/version/", "latest_version"), []byte(b.MixVer), 0644); err != nil { + return fmt.Errorf("couldn't update latest_version file: %s", err) + } + //update the sig file for the latest_version + + // update the mixer.state file + err = b.State.Save() + if err != nil { + return fmt.Errorf("couldn't update mixer.state file: %s", err) + } + + // if clean flag not set + if !resetCmdFlags.clean { + fmt.Println("Reset completed.") + return nil + } + + // Remove any folder inside update/image for versions above mixver + files, err := ioutil.ReadDir(b.Config.Builder.ServerStateDir + "/image") + if err != nil { + log.Println(err) + } + + for _, f := range files { + if f.Name() == "LAST_VER" { + continue + } + dirNameInt, err := parseUint32(f.Name()) + if err != nil { + log.Println(err) + continue + } + if dirNameInt > b.MixVerUint32 { + err := os.RemoveAll(b.Config.Builder.ServerStateDir + "/image/" + f.Name()) + if err != nil { + log.Println(err) + continue + } + } + } + + // Remove any folder inside update/www for versions above mixver + files, err = ioutil.ReadDir(b.Config.Builder.ServerStateDir + "/www") + if err != nil { + log.Println(err) + } + + for _, f := range files { + if f.Name() == "version" { + continue + } + dirNameInt, err := parseUint32(f.Name()) + if err != nil { + log.Println(err) + continue + } + if dirNameInt > b.MixVerUint32 { + err := os.RemoveAll(b.Config.Builder.ServerStateDir + "/www/" + f.Name()) + if err != nil { + log.Println(err) + } + } + } + + // Remove any folder inside update/www/version/ for version above mixver + files, err = ioutil.ReadDir(b.Config.Builder.ServerStateDir + "/www/version") + if err != nil { + log.Println(err) + } + // iterate over all the formats folder + for _, f := range files { + // read the dir name and extract the format + if f.IsDir() { + dirName := strings.SplitAfter(f.Name(), "format") + if len(dirName) >= 2 { + dirNameInt, err := parseUint32(dirName[1]) + if err != nil { + log.Println(err) + continue + } + if dirNameInt > b.MixVerUint32 { + err := os.RemoveAll(b.Config.Builder.ServerStateDir + "/www/version/" + f.Name()) + if err != nil { + log.Println(err) + } + } + } + } + } + fmt.Println("Reset completed.") + return nil +}