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 +}