-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: Itxaka <[email protected]>
- Loading branch information
Showing
4 changed files
with
286 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,164 @@ | ||
package plugins | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"io/fs" | ||
"os" | ||
"strings" | ||
|
||
"github.com/joho/godotenv" | ||
"github.com/mudler/yip/pkg/logger" | ||
"github.com/mudler/yip/pkg/schema" | ||
"github.com/twpayne/go-vfs/v4" | ||
) | ||
|
||
type Installer string | ||
|
||
const ( | ||
APTInstaller Installer = "apt-get" | ||
DNFInstaller Installer = "dnf" | ||
PacmanInstaller Installer = "pacman" | ||
SUSEInstaller Installer = "zypper" | ||
AlpineInstaller Installer = "apk" | ||
UnknownInstaller Installer = "unknown" | ||
) | ||
|
||
func (d Installer) String() string { | ||
return string(d) | ||
} | ||
|
||
type Distro string | ||
|
||
const ( | ||
Debian Distro = "debian" | ||
Ubuntu Distro = "ubuntu" | ||
RedHat Distro = "redhat" | ||
CentOS Distro = "centos" | ||
RockyLinux Distro = "rocky" | ||
AlmaLinux Distro = "almalinux" | ||
Fedora Distro = "fedora" | ||
Arch Distro = "arch" | ||
Alpine Distro = "alpine" | ||
OpenSUSELeap Distro = "opensuse-leap" | ||
OpenSUSETumbleweed Distro = "opensuse-tumbleweed" | ||
) | ||
|
||
// Packages runs the package manager to try to install/remove/refresh packages | ||
// It will try to identify the package manager based on the distro | ||
// If it can't identify the package manager, it will return an error | ||
// Order is Refresh -> Install -> Remove | ||
func Packages(l logger.Interface, s schema.Stage, fs vfs.FS, console Console) error { | ||
// Don't do anything if empty | ||
if len(s.Packages.Remove) == 0 && len(s.Packages.Install) == 0 && s.Packages.Refresh == false { | ||
return nil | ||
} | ||
|
||
var installArgs, updateArgs, removeArgs []string | ||
|
||
cmd := identifyInstaller(fs) | ||
|
||
switch cmd { | ||
case APTInstaller: | ||
// Needed so it doesn't ask for user input | ||
_ = os.Setenv("DEBIAN_FRONTEND", "noninteractive") | ||
defer func() { | ||
_ = os.Unsetenv("DEBIAN_FRONTEND") | ||
}() | ||
updateArgs = []string{"-y", "update"} | ||
installArgs = []string{"-y", "--no-install-recommends", "install"} | ||
removeArgs = []string{"-y", "remove"} | ||
case AlpineInstaller: | ||
updateArgs = []string{"update"} | ||
installArgs = []string{"add", "--no-cache"} | ||
removeArgs = []string{"del", "--no-cache"} | ||
case DNFInstaller, SUSEInstaller: | ||
updateArgs = []string{"-y", "update"} | ||
installArgs = []string{"-y", "install"} | ||
removeArgs = []string{"-y", "remove"} | ||
case PacmanInstaller: | ||
updateArgs = []string{"-Sy"} | ||
installArgs = []string{"-S", "--noconfirm"} | ||
removeArgs = []string{"-R", "--noconfirm"} | ||
default: | ||
l.Errorf("Unknown installer") | ||
return errors.New("unknown package manager") | ||
} | ||
// Run update | ||
if s.Packages.Refresh { | ||
l.Debugf("Running update") | ||
out, err := console.Run(templateSysData(l, strings.Join(append([]string{cmd.String()}, updateArgs...), " "))) | ||
if err != nil { | ||
return err | ||
} | ||
if strings.TrimSpace(out) != "" { | ||
l.Debug(fmt.Sprintf("Command output: %s", out)) | ||
} else { | ||
l.Debugf("Empty command output") | ||
} | ||
} | ||
|
||
if s.Packages.Install != nil { | ||
// Run install | ||
installArgs = append(installArgs, s.Packages.Install...) | ||
l.Debugf("Running install") | ||
out, err := console.Run(templateSysData(l, strings.Join(append([]string{cmd.String()}, installArgs...), " "))) | ||
if err != nil { | ||
return err | ||
} | ||
if strings.TrimSpace(out) != "" { | ||
l.Debug(fmt.Sprintf("Command output: %s", out)) | ||
} else { | ||
l.Debugf("Empty command output") | ||
} | ||
} | ||
|
||
if s.Packages.Remove != nil { | ||
// Run remove | ||
removeArgs = append(removeArgs, s.Packages.Remove...) | ||
l.Debugf("Running remove") | ||
out, err := console.Run(templateSysData(l, strings.Join(append([]string{cmd.String()}, removeArgs...), " "))) | ||
if err != nil { | ||
return err | ||
} | ||
if strings.TrimSpace(out) != "" { | ||
l.Debug(fmt.Sprintf("Command output: %s", out)) | ||
} else { | ||
l.Debugf("Empty command output") | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// identifyInstaller returns the package manager based on the distro | ||
func identifyInstaller(fsys vfs.FS) Installer { | ||
file, err := fsys.Open("/etc/os-release") | ||
if err != nil { | ||
return UnknownInstaller | ||
} | ||
defer func(file fs.File) { | ||
err := file.Close() | ||
if err != nil { | ||
|
||
} | ||
}(file) | ||
val, err := godotenv.Parse(file) | ||
if err != nil { | ||
return UnknownInstaller | ||
} | ||
switch Distro(val["ID"]) { | ||
case Debian, Ubuntu: | ||
return APTInstaller | ||
case Fedora, RockyLinux, AlmaLinux, RedHat, CentOS: | ||
return DNFInstaller | ||
case Arch: | ||
return PacmanInstaller | ||
case Alpine: | ||
return AlpineInstaller | ||
case OpenSUSELeap, OpenSUSETumbleweed: | ||
return SUSEInstaller | ||
default: | ||
return UnknownInstaller | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
package plugins | ||
|
||
import ( | ||
"github.com/mudler/yip/pkg/schema" | ||
consoletests "github.com/mudler/yip/tests/console" | ||
. "github.com/onsi/ginkgo/v2" | ||
. "github.com/onsi/gomega" | ||
"github.com/sanity-io/litter" | ||
"github.com/sirupsen/logrus" | ||
"github.com/twpayne/go-vfs/v4/vfst" | ||
"io" | ||
) | ||
|
||
var _ = Describe("Commands", func() { | ||
Context("parsing yip file", func() { | ||
testConsole := consoletests.TestConsole{} | ||
l := logrus.New() | ||
l.SetOutput(io.Discard) | ||
|
||
BeforeEach(func() { | ||
consoletests.Reset() | ||
}) | ||
It("execute proper install commands", func() { | ||
fs, cleanup, err := vfst.NewTestFS(map[string]interface{}{}) | ||
Expect(err).Should(BeNil()) | ||
defer cleanup() | ||
Expect(fs.Mkdir("/etc", 0755)).ToNot(HaveOccurred()) | ||
Expect(fs.WriteFile("/etc/os-release", []byte("ID=debian\nVERSION=10\n"), 0644)).ToNot(HaveOccurred()) | ||
|
||
err = Packages(l, schema.Stage{ | ||
Packages: schema.Packages{ | ||
Install: []string{"foo", "bar"}, | ||
Remove: []string{"baz", "qux"}, | ||
Refresh: true, | ||
}, | ||
}, fs, testConsole) | ||
Expect(err).ShouldNot(HaveOccurred()) | ||
Expect(consoletests.Commands).Should(Equal([]string{"apt-get -y update", "apt-get -y --no-install-recommends install foo bar", "apt-get -y remove baz qux"})) | ||
}) | ||
It("execute proper install commands for different OS", func() { | ||
fs, cleanup, err := vfst.NewTestFS(map[string]interface{}{}) | ||
Expect(err).Should(BeNil()) | ||
defer cleanup() | ||
Expect(fs.Mkdir("/etc", 0755)).ToNot(HaveOccurred()) | ||
stage := schema.Stage{ | ||
Packages: schema.Packages{ | ||
Install: []string{"foo", "bar"}, | ||
Remove: []string{"baz", "qux"}, | ||
Refresh: true, | ||
}, | ||
} | ||
type test struct { | ||
osRelease string | ||
expected []string | ||
} | ||
tests := []test{ | ||
{ | ||
osRelease: "ID=debian\nVERSION=10\n", | ||
expected: []string{"apt-get -y update", "apt-get -y --no-install-recommends install foo bar", "apt-get -y remove baz qux"}, | ||
}, | ||
{ | ||
osRelease: "ID=debian\nVERSION=11\n", | ||
expected: []string{"apt-get -y update", "apt-get -y --no-install-recommends install foo bar", "apt-get -y remove baz qux"}, | ||
}, | ||
{ | ||
osRelease: "ID=ubuntu\nVERSION=20.04\n", | ||
expected: []string{"apt-get -y update", "apt-get -y --no-install-recommends install foo bar", "apt-get -y remove baz qux"}, | ||
}, | ||
{ | ||
osRelease: "ID=centos\nVERSION=8\n", | ||
expected: []string{"dnf -y update", "dnf -y install foo bar", "dnf -y remove baz qux"}, | ||
}, | ||
{ | ||
osRelease: "ID=fedora\nVERSION=34\n", | ||
expected: []string{"dnf -y update", "dnf -y install foo bar", "dnf -y remove baz qux"}, | ||
}, | ||
{ | ||
osRelease: "ID=alpine\nVERSION=3.14\n", | ||
expected: []string{"apk update", "apk add --no-cache foo bar", "apk del --no-cache baz qux"}, | ||
}, | ||
{ | ||
osRelease: "ID=opensuse-leap\nVERSION=15.3\n", | ||
expected: []string{"zypper -y update", "zypper -y install foo bar", "zypper -y remove baz qux"}, | ||
}, | ||
{ | ||
osRelease: "ID=arch\nVERSION=rolling\n", | ||
expected: []string{"pacman -Sy", "pacman -S --noconfirm foo bar", "pacman -R --noconfirm baz qux"}, | ||
}, | ||
} | ||
|
||
for _, t := range tests { | ||
Expect(fs.WriteFile("/etc/os-release", []byte(t.osRelease), 0644)).ToNot(HaveOccurred()) | ||
err = Packages(l, stage, fs, testConsole) | ||
Expect(err).ShouldNot(HaveOccurred(), t.osRelease) | ||
Expect(consoletests.Commands).Should(Equal(t.expected), litter.Sdump(t.osRelease)) | ||
consoletests.Reset() | ||
} | ||
}) | ||
It("fails if it cant identify the systems package manager", func() { | ||
fs, cleanup, err := vfst.NewTestFS(map[string]interface{}{}) | ||
Expect(err).Should(BeNil()) | ||
defer cleanup() | ||
err = Packages(l, schema.Stage{ | ||
Packages: schema.Packages{ | ||
Install: []string{"foo", "bar"}, | ||
Remove: []string{"baz", "qux"}, | ||
Refresh: true, | ||
}, | ||
}, fs, testConsole) | ||
Expect(err).To(HaveOccurred()) | ||
Expect(err.Error()).To(ContainSubstring("unknown package manager")) | ||
}) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters