diff --git a/Makefile b/Makefile index 669470a19..6a53de613 100644 --- a/Makefile +++ b/Makefile @@ -103,7 +103,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..2ca098e5b --- /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 + mixer build all --format 1 --native + mixer-versions-update 20 latest + mixer build all --format 2 --native + mixer-versions-update 30 latest + 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 + 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 20 + test -d "./update/www/30" + test -d "./update/image/30" + 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 10 + 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/docs/mixer.1 b/docs/mixer.1 index 8a113c081..d99b0eb55 100644 --- a/docs/mixer.1 +++ b/docs/mixer.1 @@ -50,7 +50,10 @@ configure remote RPM repositories. The output of \fBmixer\fP is a set of manifests readable by \fBswupd\fP as well as all the OS content \fBswupd\fP needs to perform its update operations. The OS content includes all the files in an update as well as zero\- and delta\-packs for -improved update performance. See \fBswupd\fP(1) for more details. +improved update performance. The content that \fBmixer\fP produces is tied to a +specific format so that \fBswupd\fP is guaranteed to understand it if the client +is using the right version of \fBswupd\fP\&. See \fBswupd\fP(1) and \fBos\-format\fP(7) +for more details. .SH OPTIONS .sp The following options are applicable to most subcommands, and can be @@ -102,9 +105,8 @@ itself. See \fBmixer.build\fP(1) for more details. .INDENT 0.0 .INDENT 3.5 Perform various configuration actions on local and upstream bundles. The -user can add or remove bundles from their mix, edit local and upstream -bundles, create new bundle definitions, or validate local bundle definition -files. See \fBmixer.bundle\fP(1) for more details. +user can add or remove bundles from their mix, create new bundle definitions, +or validate local bundle definition files. See \fBmixer.bundle\fP(1) for more details. .UNINDENT .UNINDENT .sp @@ -151,6 +153,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 @@ -186,8 +200,12 @@ 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) +.IP \(bu 2 \fI\%https://github.com/clearlinux/mixer\-tools\fP .IP \(bu 2 \fI\%https://github.com/clearlinux/swupd\-client\fP diff --git a/docs/mixer.1.rst b/docs/mixer.1.rst index 3799d86e6..a0b37844b 100644 --- a/docs/mixer.1.rst +++ b/docs/mixer.1.rst @@ -118,6 +118,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 ===== @@ -146,6 +155,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..6d803e617 --- /dev/null +++ b/mixer/cmd/reset.go @@ -0,0 +1,211 @@ +// 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/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 { + b, err := builder.NewFromConfig(configFile) + if err != nil { + return err + } + var stateMixPreviousMixVersionUint32 uint32 + + // if toVersion provided by the user, replace the default previous version + if resetCmdFlags.toVersion >= 0 { + b.State.Mix.PreviousMixVer = strconv.Itoa(int(resetCmdFlags.toVersion)) + stateMixPreviousMixVersionUint32 = uint32(resetCmdFlags.toVersion) + } else { + // assuming mixer.state file has the correct info + stateMixPreviousMixVersionUint32, err = parseUint32(b.State.Mix.PreviousMixVer) + if err != nil { + return err + } + } + + // Make sure FORMAT in mixer.state has the same value as update/www//format + var format []byte + filename := filepath.Join(b.Config.Builder.ServerStateDir, "www", b.State.Mix.PreviousMixVer, "format") + if format, err = ioutil.ReadFile(filename); err != nil { + return err + } + b.State.Mix.Format = strings.TrimSpace(string(format)) + stateMixPreviousMixFormatUint32, err := parseUint32(b.State.Mix.Format) + if err != nil { + return err + } + + // Change upstreamVersion file content to the previous version + var lastStableMixUpstreamVersion []byte + filename = filepath.Join(b.Config.Builder.ServerStateDir, "www", b.State.Mix.PreviousMixVer, "upstreamver") + if lastStableMixUpstreamVersion, err = ioutil.ReadFile(filename); err != nil { + return err + } + if strings.TrimSpace(string(lastStableMixUpstreamVersion)) != b.UpstreamVer { + // Set the upstream version to the previous format's latest version + b.UpstreamVer = strings.TrimSpace(string(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 mixVersion to point to PreviousMixVer + if err := ioutil.WriteFile(filepath.Join(b.Config.Builder.VersionPath, b.MixVerFile), []byte(b.State.Mix.PreviousMixVer), 0644); err != nil { + return err + } + b.MixVer = b.State.Mix.PreviousMixVer + b.MixVerUint32, err = parseUint32(b.MixVer) + if err != nil { + return errors.Wrapf(err, "Couldn't parse mixer version") + } + + // Make sure update/image/LAST_VER points to PreviousMixVer + if err = ioutil.WriteFile(filepath.Join(b.Config.Builder.ServerStateDir, "image", "LAST_VER"), []byte(b.State.Mix.PreviousMixVer), 0644); err != nil { + return fmt.Errorf("couldn't update LAST_VER file: %s", err) + } + + // 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 prevMixVer + 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 > stateMixPreviousMixVersionUint32 { + 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 prevMixVer + 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 > stateMixPreviousMixVersionUint32 { + 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 prevMixVer + 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 > stateMixPreviousMixFormatUint32 { + err := os.RemoveAll(b.Config.Builder.ServerStateDir + "/www/version/" + f.Name()) + if err != nil { + log.Println(err) + } + } + } + } + } + fmt.Println("Reset completed.") + return nil +}