diff --git a/.scripts/packages.list b/.scripts/packages.list index 124a356d..d3aeb400 100644 --- a/.scripts/packages.list +++ b/.scripts/packages.list @@ -47,6 +47,7 @@ L + netutil * + rand * + req * + secstr +L - setup * - signal * + sliceutil * + sortutil diff --git a/CHANGELOG.md b/CHANGELOG.md index dbadec37..9bc89d7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ ## Changelog +### [13.6.0](https://kaos.sh/ek/13.6.0) + +- `[setup]` Added package to install/uninstall application as a service +- `[support/deps]` Improved collecting and filtering dependencies info +- `[support/kernel]` Added simple globs support for parameter names + ### [13.5.1](https://kaos.sh/ek/13.5.1) - `[mathutil]` Added method `FromPerc` diff --git a/README.md b/README.md index dd2a547d..3c022c8a 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@
-Auxiliary packages for Go 1.22+. +Auxiliary packages for [Go 1.22+](https://github.com/essentialkaos/.github/blob/master/GO-VERSION-SUPPORT.md). ### Platform support diff --git a/go.mod b/go.mod index e2271e62..6a3c76dd 100644 --- a/go.mod +++ b/go.mod @@ -1,17 +1,17 @@ module github.com/essentialkaos/ek/v13 -go 1.21 +go 1.22.8 require ( github.com/essentialkaos/check v1.4.0 - github.com/essentialkaos/depsy v1.3.0 - github.com/essentialkaos/go-linenoise/v3 v3.6.0 - golang.org/x/crypto v0.27.0 - golang.org/x/sys v0.25.0 + github.com/essentialkaos/depsy v1.3.1 + github.com/essentialkaos/go-linenoise/v3 v3.6.1 + golang.org/x/crypto v0.28.0 + golang.org/x/sys v0.26.0 ) require ( github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect - github.com/rogpeppe/go-internal v1.11.0 // indirect + github.com/rogpeppe/go-internal v1.13.1 // indirect ) diff --git a/go.sum b/go.sum index cc6de943..ab452beb 100644 --- a/go.sum +++ b/go.sum @@ -1,19 +1,19 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/essentialkaos/check v1.4.0 h1:kWdFxu9odCxUqo1NNFNJmguGrDHgwi3A8daXX1nkuKk= github.com/essentialkaos/check v1.4.0/go.mod h1:LMKPZ2H+9PXe7Y2gEoKyVAwUqXVgx7KtgibfsHJPus0= -github.com/essentialkaos/depsy v1.3.0 h1:CN7bRgBU2jGTHSkg/Sh38eDUn7cvmaTp2sxFt2HpFeU= -github.com/essentialkaos/depsy v1.3.0/go.mod h1:kpiTAV17dyByVnrbNaMcZt2jRwvuXClUYOzpyJQwtG8= -github.com/essentialkaos/go-linenoise/v3 v3.6.0 h1:deLcrodtLIkcHjNyW/MoQpjznXPVqvwlspxk7s/5YeY= -github.com/essentialkaos/go-linenoise/v3 v3.6.0/go.mod h1:Fi6kLdZdURkXHpRkIiX2nFGORNv81CXTZ2Mn72i/cn0= +github.com/essentialkaos/depsy v1.3.1 h1:00k9QcMsdPM4IzDaEFHsTHBD/zoM0oxtB5+dMUwbQa8= +github.com/essentialkaos/depsy v1.3.1/go.mod h1:B5+7Jhv2a2RacOAxIKU2OeJp9QfZjwIpEEPI5X7auWM= +github.com/essentialkaos/go-linenoise/v3 v3.6.1 h1:VzjakaWNAPfattl/HSIJveWYpfrxAYHzUl8u6DdOjtY= +github.com/essentialkaos/go-linenoise/v3 v3.6.1/go.mod h1:hpxke5G2eXvFhVnDAiGU9ArH3MTDGGc13afObIbuaJQ= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= -github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= -golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= diff --git a/req/req.go b/req/req.go index 3cc49b6a..0a8aa775 100644 --- a/req/req.go +++ b/req/req.go @@ -177,7 +177,7 @@ const ( ) // USER_AGENT is default user agent -const USER_AGENT = "go-ek-req" +const USER_AGENT = "ek.go" // ////////////////////////////////////////////////////////////////////////////////// // @@ -319,7 +319,7 @@ func (e *Engine) Init() *Engine { } if e.UserAgent == "" { - e.SetUserAgent(USER_AGENT, "10") + e.SetUserAgent(USER_AGENT, "13") } e.dialTimeout = 0 diff --git a/setup/setup.go b/setup/setup.go new file mode 100644 index 00000000..24706394 --- /dev/null +++ b/setup/setup.go @@ -0,0 +1,450 @@ +//go:build linux +// +build linux + +// Package setup provides methods to install/unistall application as a service on the +// system +package setup + +// ////////////////////////////////////////////////////////////////////////////////// // +// // +// Copyright (c) 2024 ESSENTIAL KAOS // +// Apache License, Version 2.0 // +// // +// ////////////////////////////////////////////////////////////////////////////////// // + +import ( + "bytes" + "fmt" + "os" + "os/exec" + "strings" + + "github.com/essentialkaos/ek/v13/fsutil" + "github.com/essentialkaos/ek/v13/hash" + "github.com/essentialkaos/ek/v13/path" + "github.com/essentialkaos/ek/v13/strutil" + "github.com/essentialkaos/ek/v13/system" +) + +// ////////////////////////////////////////////////////////////////////////////////// // + +// App contains basic application configuration +type App struct { + Name string // Application name + Options []string // List of options + DocsURL string // Documentation URL + User string // Service user + Identifier string // Syslog identifier + WorkingDir string // Working dir + + StopSignal string // Stop signal + ReloadSignal string // Reload signal + + WithLog bool // Create directory for logs + WithoutPrivateTemp bool // Disable private temp + + Configs []Config // Configuration files +} + +// Config contains configuration file data +// +// Note that all configurations are stored in /etc +type Config struct { + Name string // File name + Data []byte // Data + Mode os.FileMode // File mode +} + +// ////////////////////////////////////////////////////////////////////////////////// // + +// binaryInfo contains basic info about binary +type binaryInfo struct { + File string // Path to binary file + Name string // Binary file name +} + +// ////////////////////////////////////////////////////////////////////////////////// // + +// serviceDir is path to directory with system service files +var serviceDir = "/usr/lib/systemd/system" + +// binaryDir is path to directory with binaries +var binaryDir = "/usr/bin" + +// logDir is path to directory with logs +var logDir = "/var/log" + +// configDir is path to directory with configuration files +var configDir = "/etc" + +// unitComment is comment for service unit +var unitComment = "# Unit generated by ek.go/setup" + +// checksum is current binary checksum +var checksum string + +// ////////////////////////////////////////////////////////////////////////////////// // + +// Install installs or reinstalls application on the system +func (app App) Install() error { + bin := getBinaryInfo() + err := checkForInstall(app, bin) + + if err != nil { + return err + } + + err = installFiles(app, bin) + + if err != nil { + return err + } + + exec.Command("systemctl", "daemon-reload").Run() + + return nil +} + +// Uninstall uninstall unistalls application from the system +func (app App) Uninstall(full bool) error { + bin := getBinaryInfo() + err := checkForUninstall(app, bin) + + if err != nil { + return err + } + + err = uninstallFiles(app, bin, full) + + if err != nil { + return err + } + + exec.Command("systemctl", "daemon-reload").Run() + + return nil +} + +// ////////////////////////////////////////////////////////////////////////////////// // + +// BinPath returns binary installation path +func (b *binaryInfo) BinInstallPath() string { + return path.Join(binaryDir, b.Name) +} + +// LogDir returns logs directory path +func (b *binaryInfo) LogDir() string { + return path.Join(logDir, b.Name) +} + +// ServiceUnitPath returns service unit path +func (b *binaryInfo) ServiceUnitPath() string { + return path.Join(serviceDir, b.Name+".service") +} + +// IsBinInstalled returns true if current binary already installed +func (b *binaryInfo) IsBinInstalled() bool { + return fsutil.IsExist(b.BinInstallPath()) && + b.Checksum() == hash.FileHash(b.BinInstallPath()) +} + +// IsServiceInstalled returns true if service unit already installed +func (b *binaryInfo) IsServiceInstalled() bool { + return fsutil.IsExist(b.ServiceUnitPath()) +} + +// Checksum returns checksum of current binary +func (b *binaryInfo) Checksum() string { + if checksum != "" { + return checksum + } + + checksum = hash.FileHash(b.File) + + return checksum +} + +// ////////////////////////////////////////////////////////////////////////////////// // + +// getBinary returns basic info about current binary +func getBinaryInfo() binaryInfo { + bin, _ := os.Executable() + binFile := path.Clean(bin) + binName := path.Base(binFile) + + return binaryInfo{File: binFile, Name: binName} +} + +// checkForInstall checks if app can be installed +func checkForInstall(app App, bin binaryInfo) error { + if len(app.Configs) != 0 { + for _, c := range app.Configs { + if strings.Contains(c.Name, "/") { + return fmt.Errorf("Configuration file name %q is invalid", c.Name) + } + } + } + + user, err := system.CurrentUser() + + if err != nil { + return fmt.Errorf("Can't get info about current user: %w", err) + } + + if !user.IsRoot() { + return fmt.Errorf("You must have superuser (root) privileges to install app as a service") + } + + if bin.IsServiceInstalled() { + isGenerated, err := isGeneratedUnit(bin.ServiceUnitPath()) + + if err != nil { + return fmt.Errorf("Can't check systemd unit: %w", err) + } + + if !isGenerated { + return fmt.Errorf("Can't replace systemd unit, it wasn't created by app") + } + } + + return nil +} + +// checkForUninstall checks if app can be uninstalled +func checkForUninstall(app App, bin binaryInfo) error { + if len(app.Configs) != 0 { + for _, c := range app.Configs { + if strings.Contains(c.Name, "/") { + return fmt.Errorf("Configuration file name %q is invalid", c.Name) + } + } + } + + if !bin.IsBinInstalled() { + return fmt.Errorf("Binary is not installed (new binary?)") + } + + if bin.File != bin.BinInstallPath() { + return fmt.Errorf("You must use installed binary to uninstall service from the system") + } + + user, err := system.CurrentUser() + + if err != nil { + return fmt.Errorf("Can't get info about current user: %w", err) + } + + if !user.IsRoot() { + return fmt.Errorf("You must have superuser (root) privileges to uninstall this app") + } + + if bin.IsServiceInstalled() { + isGenerated, err := isGeneratedUnit(bin.ServiceUnitPath()) + + if err != nil { + return fmt.Errorf("Can't check systemd unit: %w", err) + } + + if !isGenerated { + return fmt.Errorf("Can't uninstall systemd unit, it wasn't created by app") + } + } + + return nil +} + +// installFiles installs binary and systemd unit file +func installFiles(app App, bin binaryInfo) error { + err := installConfigurationFiles(app) + + if err != nil { + return err + } + + if !bin.IsBinInstalled() { + err := fsutil.CopyFile(bin.File, bin.BinInstallPath(), 0755) + + if err != nil { + return fmt.Errorf("Can't copy binary to %s: %w", binaryDir, err) + } + } + + err = createServiceFile(app, bin) + + if err != nil { + return fmt.Errorf("Can't install systemd service: %w", err) + } + + if app.WithLog && !fsutil.IsExist(bin.LogDir()) { + err := os.Mkdir(bin.LogDir(), 0755) + + if err != nil { + return fmt.Errorf("Can't create directory for logs (%s): %w", bin.LogDir(), err) + } + } + + return nil +} + +// uninstallFiles removes binary and systemd unit file +func uninstallFiles(app App, bin binaryInfo, full bool) error { + if app.WithLog && full { + os.RemoveAll(bin.LogDir()) + } + + err := os.Remove(bin.ServiceUnitPath()) + + if err != nil { + return fmt.Errorf("Can't remove systemd unit: %w", err) + } + + err = os.Remove(bin.BinInstallPath()) + + if err != nil { + return fmt.Errorf("Can't remove binary: %w", err) + } + + if full && len(app.Configs) > 0 { + return uninstallConfigurationFiles(app) + } + + return nil +} + +// installConfigurationFiles creates all configuration files +func installConfigurationFiles(app App) error { + if len(app.Configs) == 0 { + return nil + } + + for _, c := range app.Configs { + filePath := path.Clean(path.Join(configDir, c.Name)) + + // Don't rewrite files + if len(c.Data) == 0 || fsutil.IsExist(filePath) { + continue + } + + fileMode := c.Mode + + if fileMode == 0 { + fileMode = 0640 + } + + err := os.WriteFile(filePath, c.Data, fileMode) + + if err != nil { + return fmt.Errorf("Can't create configuration file %q: %w", filePath, err) + } + } + + return nil +} + +// uninstallConfigurationFiles removes all configuration files +func uninstallConfigurationFiles(app App) error { + if len(app.Configs) == 0 { + return nil + } + + for _, c := range app.Configs { + filePath := path.Clean(path.Join(configDir, c.Name)) + + if !fsutil.IsExist(filePath) { + continue + } + + err := os.Remove(filePath) + + if err != nil { + return fmt.Errorf("Can't delete configuration file %q: %w", filePath, err) + } + } + + return nil +} + +// createServiceFile creates systemd service file +func createServiceFile(app App, bin binaryInfo) error { + serviceUnitData := generateServiceUnit(app, bin) + + err := os.WriteFile(bin.ServiceUnitPath(), serviceUnitData, 0644) + + if err != nil { + return fmt.Errorf("Can't create service file (%s): %w", bin.ServiceUnitPath(), err) + } + + return nil +} + +// generateServiceUnit generates systemd service file +func generateServiceUnit(app App, bin binaryInfo) []byte { + var buf bytes.Buffer + + buf.WriteString(fmt.Sprintf( + "%s (%s)\n\n", + unitComment, + strutil.Head(bin.Checksum(), 7), + )) + + buf.WriteString("[Unit]\n") + buf.WriteString(fmt.Sprintf("Description=%s\n", strutil.Q(app.Name, bin.Name))) + + if app.DocsURL != "" { + buf.WriteString(fmt.Sprintf("Documentation=%s\n", app.DocsURL)) + } + + buf.WriteString("After=network-online.target remote-fs.target nss-lookup.target\n") + buf.WriteString("Wants=network-online.target\n\n") + buf.WriteString("[Service]\n") + buf.WriteString("Type=simple\n") + + if app.User != "" { + buf.WriteString(fmt.Sprintf("User=%s\n", app.User)) + } + + if len(app.Options) > 0 { + buf.WriteString(fmt.Sprintf( + "ExecStart=%s %s\n", path.Join(binaryDir, bin.Name), strings.Join(app.Options, " "), + )) + } else { + buf.WriteString(fmt.Sprintf("ExecStart=%s\n", path.Join(binaryDir, bin.Name))) + } + + if app.ReloadSignal != "" { + buf.WriteString(fmt.Sprintf( + "ExecReload=/bin/kill -s %s $MAINPID\n", app.ReloadSignal, + )) + } + + if app.StopSignal != "" { + buf.WriteString(fmt.Sprintf( + "ExecStop=/bin/kill -s %s $MAINPID\n", app.StopSignal, + )) + } + + if app.WithLog { + buf.WriteString(fmt.Sprintf( + "StandardError=file:%s/startup.log\n", bin.LogDir(), + )) + } + + if !app.WithoutPrivateTemp { + buf.WriteString("PrivateTmp=true\n") + } + + buf.WriteString("\n[Install]\nWantedBy=multi-user.target\n\n") + + return buf.Bytes() +} + +// isGeneratedUnit returns true if given systemd unit generated by this package +func isGeneratedUnit(file string) (bool, error) { + unitData, err := os.ReadFile(file) + + if err != nil { + return false, err + } + + return bytes.HasPrefix(unitData, []byte(unitComment)), nil +} diff --git a/setup/setup_stub.go b/setup/setup_stub.go new file mode 100644 index 00000000..c0122d77 --- /dev/null +++ b/setup/setup_stub.go @@ -0,0 +1,56 @@ +//go:build !linux +// +build !linux + +// Package setup provides methods to install/unistall application as a service on the +// system +package setup + +// ////////////////////////////////////////////////////////////////////////////////// // +// // +// Copyright (c) 2024 ESSENTIAL KAOS // +// Apache License, Version 2.0 // +// // +// ////////////////////////////////////////////////////////////////////////////////// // + +import "os" + +// ////////////////////////////////////////////////////////////////////////////////// // + +// ❗ App contains basic application configuration +type App struct { + Name string // Application name + Options []string // List of options + DocsURL string // Documentation URL + User string // Service user + Identifier string // Syslog identifier + WorkingDir string // Working dir + + StopSignal string // Stop signal + ReloadSignal string // Reload signal + + WithLog bool // Create directory for logs + WithoutPrivateTemp bool // Disable private temp + + Configs []Config // Configuration files +} + +// ❗ Config contains configuration file data +// +// Note that all configurations are stored in /etc +type Config struct { + Name string // File name + Data []byte // Data + Mode os.FileMode // File mode +} + +// ////////////////////////////////////////////////////////////////////////////////// // + +// ❗ Install installs or reinstalls application on the system +func (app App) Install() error { + panic("UNSUPPORTED") +} + +// ❗ Uninstall uninstall unistalls application from the system +func (app App) Uninstall(full bool) error { + panic("UNSUPPORTED") +} diff --git a/setup/setup_test.go b/setup/setup_test.go new file mode 100644 index 00000000..94e2a0dc --- /dev/null +++ b/setup/setup_test.go @@ -0,0 +1,173 @@ +package setup + +// ////////////////////////////////////////////////////////////////////////////////// // +// // +// Copyright (c) 2024 ESSENTIAL KAOS // +// Apache License, Version 2.0 // +// // +// ////////////////////////////////////////////////////////////////////////////////// // + +import ( + "errors" + "testing" + + . "github.com/essentialkaos/check" +) + +// ////////////////////////////////////////////////////////////////////////////////// // + +func Test(t *testing.T) { TestingT(t) } + +type SetupSuite struct{} + +// ////////////////////////////////////////////////////////////////////////////////// // + +var _ = Suite(&SetupSuite{}) + +// ////////////////////////////////////////////////////////////////////////////////// // + +func (s *SetupSuite) TestInstall(c *C) { + serviceDir = c.MkDir() + binaryDir = c.MkDir() + logDir = c.MkDir() + configDir = c.MkDir() + + app, bin := s.generateApp(), s.generateBin() + + err := installFiles(app, bin) + c.Assert(err, IsNil) + + isGen, _ := isGeneratedUnit(bin.ServiceUnitPath()) + c.Assert(isGen, Equals, true) + _, err = isGeneratedUnit("/_unknown_") + c.Assert(err, NotNil) + + app = s.generateApp() + app.Options = nil + + err = installFiles(app, bin) + c.Assert(err, IsNil) + + c.Assert(bin.IsBinInstalled(), Equals, true) + c.Assert(bin.IsServiceInstalled(), Equals, true) +} + +func (s *SetupSuite) TestInstallErrors(c *C) { + serviceDir = c.MkDir() + binaryDir = c.MkDir() + + app, bin := s.generateApp(), s.generateBin() + + logDir = "/_unknown_" + err := installFiles(app, bin) + c.Assert(err, NotNil) + + serviceDir = "/_unknown_" + err = installFiles(app, bin) + c.Assert(err, NotNil) + + binaryDir = "/_unknown_" + err = installFiles(app, bin) + c.Assert(err, NotNil) + + app.Configs = []Config{ + {"abcd/test1.knf", []byte("[main]\n test: 1\n\n"), 0}, + } + + err = installFiles(app, bin) + c.Assert(err, NotNil) +} + +func (s *SetupSuite) TestUninstall(c *C) { + serviceDir = c.MkDir() + binaryDir = c.MkDir() + logDir = c.MkDir() + configDir = c.MkDir() + + app, bin := s.generateApp(), s.generateBin() + + err := installFiles(app, bin) + c.Assert(err, IsNil) + + app.Configs = append(app.Configs, Config{Name: "testX.knf"}) + + err = uninstallFiles(app, bin, true) + c.Assert(err, IsNil) + + app.Configs = nil + + err = installFiles(app, bin) + c.Assert(err, IsNil) + + err = uninstallFiles(app, bin, true) + c.Assert(err, IsNil) + + c.Assert(uninstallConfigurationFiles(app), IsNil) +} + +func (s *SetupSuite) TestUninstallErrors(c *C) { + serviceDir = c.MkDir() + binaryDir = c.MkDir() + logDir = c.MkDir() + + app, bin := s.generateApp(), s.generateBin() + + err := installFiles(app, bin) + c.Assert(err, IsNil) + + binaryDir = "/_unknown_" + err = uninstallFiles(app, bin, true) + c.Assert(err, NotNil) + + serviceDir = "/_unknown_" + err = uninstallFiles(app, bin, true) + c.Assert(err, NotNil) +} + +func (s *SetupSuite) TestAux(c *C) { + bin := getBinaryInfo() + + app, bin := s.generateApp(), s.generateBin() + + app.Install() + app.Uninstall(false) + + c.Assert(bin.File, Not(Equals), "") + c.Assert(bin.Name, Not(Equals), "") + + c.Assert(checkForInstall(app, bin), NotNil) + c.Assert(checkForUninstall(app, bin), NotNil) + + app = s.generateApp() + app.Configs = []Config{ + {"abcd/test1.knf", []byte("[main]\n test: 1\n\n"), 0}, + } + + c.Assert(checkForInstall(app, bin), DeepEquals, errors.New("Configuration file name \"abcd/test1.knf\" is invalid")) + c.Assert(checkForUninstall(app, bin), DeepEquals, errors.New("Configuration file name \"abcd/test1.knf\" is invalid")) +} + +// ////////////////////////////////////////////////////////////////////////////////// // + +func (s *SetupSuite) generateApp() App { + return App{ + Name: "Test", + Options: []string{"--config", "/etc/test.knf"}, + DocsURL: "https://domain.com", + User: "nobody", + Identifier: "TEST", + WorkingDir: "/srv/test", + StopSignal: "TERM", + ReloadSignal: "HUP", + WithLog: true, + WithoutPrivateTemp: false, + Configs: []Config{ + {"test1.knf", []byte("[main]\n test: 1\n\n"), 0}, + {"test2.knf", []byte("[main]\n test: 2\n\n"), 0644}, + }, + } +} + +func (s *SetupSuite) generateBin() binaryInfo { + return binaryInfo{File: "/usr/bin/echo", Name: "test1"} +} diff --git a/support/deps/deps.go b/support/deps/deps.go index 3b10c0b0..dbdae68a 100644 --- a/support/deps/deps.go +++ b/support/deps/deps.go @@ -9,6 +9,9 @@ package deps // ////////////////////////////////////////////////////////////////////////////////// // import ( + "runtime/debug" + "strings" + "github.com/essentialkaos/ek/v13/support" "github.com/essentialkaos/depsy" @@ -17,16 +20,58 @@ import ( // ////////////////////////////////////////////////////////////////////////////////// // // Extract extracts dependencies info from gomod data -func Extract(gomod []byte) []support.Dep { +func Extract(gomod []byte, withIndirect ...bool) []support.Dep { + if len(withIndirect) > 0 && withIndirect[0] { + return filterDeps(depsy.Extract(gomod, true)) + } + + return filterDeps(depsy.Extract(gomod, false)) +} + +// ////////////////////////////////////////////////////////////////////////////////// // + +// filterDeps filters dependencies from gomod using information from bundled build info +func filterDeps(deps depsy.Dependencies) []support.Dep { var result []support.Dep - for _, dep := range depsy.Extract(gomod, false) { - result = append(result, support.Dep{ + buildInfo, _ := debug.ReadBuildInfo() + + for _, dep := range deps { + depInfo := support.Dep{ Version: dep.Version, Path: dep.PrettyPath(), Extra: dep.Extra, - }) + } + + if buildInfo != nil { + hasDep, version := hasBuiltDep(dep, buildInfo) + + if !hasDep { + continue + } + + if version != "" && strings.Contains(version, "(") { + depInfo.Version = strings.Trim(version, "()") + } + } + + result = append(result, depInfo) } return result } + +// hasBuiltDep checks if given dependency is present in build info +func hasBuiltDep(dep depsy.Dependency, buildInfo *debug.BuildInfo) (bool, string) { + for _, bDep := range buildInfo.Deps { + if bDep.Path == dep.Path { + if bDep.Replace != nil { + return true, bDep.Replace.Version + } + + return true, "" + } + } + + return false, "" +} diff --git a/support/kernel/kernel.go b/support/kernel/kernel.go index d7f7b2f8..df673f72 100644 --- a/support/kernel/kernel.go +++ b/support/kernel/kernel.go @@ -32,9 +32,18 @@ func Collect(params ...string) []support.KernelParam { var result []support.KernelParam for _, param := range params { + isGlob := strings.HasSuffix(param, "*") + param = strings.TrimRight(param, "*") + for k, v := range kernelParams { - if !strings.HasPrefix(k, param) { - continue + if isGlob { + if !strings.HasPrefix(k, param) { + continue + } + } else { + if k != param { + continue + } } value := strings.ReplaceAll(v, "\t", " ") diff --git a/support/support.go b/support/support.go index 62cf369d..72a7b1dc 100644 --- a/support/support.go +++ b/support/support.go @@ -32,7 +32,7 @@ Example of collecting maximum information about the application and system: WithNetwork(network.Collect("https://cloudflare.com/cdn-cgi/trace")). WithFS(fs.Collect()). WithResources(resources.Collect()). - WithKernel(kernel.Collect()). + WithKernel(kernel.Collect("vm.nr_hugepages*", "vm.swappiness")). Print() Also, you can't encode data to JSON/GOB and send it to your server instead of printing @@ -49,7 +49,7 @@ it to the console. WithNetwork(network.Collect("https://cloudflare.com/cdn-cgi/trace")). WithFS(fs.Collect()). WithResources(resources.Collect()). - WithKernel(kernel.Collect()) + WithKernel(kernel.Collect("vm.nr_hugepages*", "vm.swappiness")) b, _ := json.Marshal(info) diff --git a/usage/update/github.go b/usage/update/github.go index 3748a4e8..89d4a183 100644 --- a/usage/update/github.go +++ b/usage/update/github.go @@ -50,24 +50,23 @@ func getLatestGitHubRelease(app, version, repository string) *githubRelease { engine.SetDialTimeout(3) engine.SetRequestTimeout(3) - engine.SetUserAgent(app, version, "GoEK.v13") - response, err := engine.Get(req.Request{ + resp, err := engine.Get(req.Request{ URL: githubAPI + "/repos/" + repository + "/releases/latest", Headers: req.Headers{"X-GitHub-Api-Version": "2022-11-28"}, AutoDiscard: true, }) - if err != nil || response.StatusCode != 200 { + if err != nil || resp.StatusCode != 200 { return nil } - if response.Header.Get("X-RateLimit-Remaining") == "0" { + if resp.Header.Get("X-RateLimit-Remaining") == "0" { return nil } release := &githubRelease{} - err = response.JSON(release) + err = resp.JSON(release) if err != nil { return nil diff --git a/usage/update/gitlab.go b/usage/update/gitlab.go index 47018730..c83784d7 100644 --- a/usage/update/gitlab.go +++ b/usage/update/gitlab.go @@ -50,27 +50,26 @@ func getLatestGitLabRelease(app, version, repository string) *gitlabRelease { engine.SetDialTimeout(3) engine.SetRequestTimeout(3) - engine.SetUserAgent(app, version, "GoEK.v13") if strings.Contains(repository, "/") { repository = strings.ReplaceAll(repository, "/", "%2F") } - response, err := engine.Get(req.Request{ + resp, err := engine.Get(req.Request{ URL: gitlabAPI + "/projects/" + repository + "/releases/permalink/latest", AutoDiscard: true, }) - if err != nil || response.StatusCode != 200 { + if err != nil || resp.StatusCode != 200 { return nil } - if response.Header.Get("RateLimit-Remaining") == "0" { + if resp.Header.Get("RateLimit-Remaining") == "0" { return nil } release := &gitlabRelease{} - err = response.JSON(release) + err = resp.JSON(release) if err != nil { return nil diff --git a/usage/update/update_test.go b/usage/update/update_test.go index 30c73cd4..fc17fcad 100644 --- a/usage/update/update_test.go +++ b/usage/update/update_test.go @@ -39,8 +39,6 @@ func (s *UpdateSuite) SetUpSuite(c *C) { s.url = "http://127.0.0.1:" + s.port go runHTTPServer(s, c) - - time.Sleep(time.Second) } func (s *UpdateSuite) TearDownSuite(c *C) { @@ -166,7 +164,7 @@ func runHTTPServer(s *UpdateSuite, c *C) { server.Handler.(*http.ServeMux).HandleFunc("/github/repos/essentialkaos/limited/releases/latest", githubLimitedHandler) server.Handler.(*http.ServeMux).HandleFunc("/github/repos/essentialkaos/garbage/releases/latest", githubWrongFormatHandler) - server.Handler.(*http.ServeMux).HandleFunc("/gitlab/projects/essentialkaos/project/releases/permalink/latest", gitlabInfoHandler) + server.Handler.(*http.ServeMux).HandleFunc("/gitlab/projects/essentialkaos%2Fproject/releases/permalink/latest", gitlabInfoHandler) server.Handler.(*http.ServeMux).HandleFunc("/gitlab/projects/essentialkaos/unknown/releases/permalink/latest", gitlabNotFoundHandler) server.Handler.(*http.ServeMux).HandleFunc("/gitlab/projects/essentialkaos/limited/releases/permalink/latest", gitlabLimitedHandler) server.Handler.(*http.ServeMux).HandleFunc("/gitlab/projects/essentialkaos/garbage/releases/permalink/latest", gitlabWrongFormatHandler) diff --git a/version.go b/version.go index 9efe2a5a..14137bf4 100644 --- a/version.go +++ b/version.go @@ -8,4 +8,4 @@ package ek // ////////////////////////////////////////////////////////////////////////////////// // // VERSION is current ek package version -const VERSION = "13.5.1" +const VERSION = "13.6.0"