Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Alter agent install to detect installation from package #30289

Merged
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions Vagrantfile
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ TEST_BOXES = [
{:name => "amazon2", :box => "bento/amazonlinux-2", :platform => "centos"},

# Unsupported platforms
{:name => "opensuse153", :box => "bento/opensuse-leap-15.3", :platform => "opensuse"},
{:name => "sles12", :box => "elastic/sles-12-x86_64", :platform => "sles"},
Comment on lines +56 to +57
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

opensuse/sles12 added to help test suse

{:name => "solaris", :box => "https://s3.amazonaws.com/beats-files/vagrant/beats-solaris-11.2-virtualbox-2016-11-02_1603.box", :platform => "unix"},
{:name => "freebsd", :box => "bento/freebsd-13", :platform => "freebsd", :extras => "pkg install -y -q bash && chsh -s bash vagrant"},
{:name => "openbsd", :box => "generic/openbsd6", :platform => "openbsd", :extras => "sudo pkg_add go"},
Expand Down Expand Up @@ -103,14 +105,14 @@ Vagrant.configure("2") do |config|
end
end

# Freebsd
# Freebsd
if node[:platform] == "freebsd"
nodeconfig.vm.provision "shell", path: "dev-tools/vagrant_scripts/unixProvision.sh", args: "gvm amd64 freebsd #{GO_VERSION}", privileged: false
nodeconfig.vm.provision "shell", inline: "sudo mount -t linprocfs /dev/null /proc", privileged: false
end

# gvm install
if node[:platform] == "centos" or node[:platform] == "ubuntu" or node[:platform] == "debian" or node[:platorm] == "archlinux"
if node[:platform] == "centos" or node[:platform] == "ubuntu" or node[:platform] == "debian" or node[:platform] == "archlinux" or node[:platform] == "opensuse" or node[:platform] == "sles"
michel-laterman marked this conversation as resolved.
Show resolved Hide resolved
nodeconfig.vm.provision "shell", type: "shell", path: "dev-tools/vagrant_scripts/unixProvision.sh", args: "gvm amd64 linux #{GO_VERSION}", privileged: false
end

Expand Down
10 changes: 9 additions & 1 deletion dev-tools/vagrant_scripts/unixProvision.sh
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ function archProvision () {
pacman -Sy && pacman -S --noconfirm make gcc python python-pip git
}

function suseProvision() {
zypper refresh
zypper install -y make gcc git python3 python3-pip python3-virtualenv git rpm-devel
}

case "${PROVISION_TYPE}" in
"gvm")
gvmProvision $ARCH $OS $GO_VERSION
Expand All @@ -61,6 +66,9 @@ case "${PROVISION_TYPE}" in
"debian" | "ubuntu")
debProvision
;;
"opensuse" | "sles")
suseProvision
;;
*)
echo "No Extra provisioning steps for this platform"
esac
esac
1 change: 1 addition & 0 deletions x-pack/elastic-agent/CHANGELOG.next.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -159,3 +159,4 @@
- Discover changes in Kubernetes nodes metadata as soon as they happen. {pull}23139[23139]
- Add results of inspect output command into archive produced by diagnostics collect. {pull}29902[29902]
- Add support for loading input configuration from external configuration files in standalone mode. You can load inputs from YAML configuration files under the folder `{path.config}/inputs.d`. {pull}30087[30087]
- Install command will skip install/uninstall steps when installation via package is detected on Linux distros. {pull}30289[30289]
49 changes: 29 additions & 20 deletions x-pack/elastic-agent/pkg/agent/cmd/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ func installCmd(streams *cli.IOStreams, cmd *cobra.Command, args []string) error
return fmt.Errorf("already installed at: %s", paths.InstallPath)
}

if status == install.PackageInstall {
fmt.Fprintf(streams.Out, "Installed as a system package, installation will not be altered.\n")
}

// check the lock to ensure that elastic-agent is not already running in this directory
locker := filelock.NewAppLocker(paths.Data(), paths.AgentLockFileName)
if err := locker.TryLock(); err != nil {
Expand All @@ -80,7 +84,7 @@ func installCmd(streams *cli.IOStreams, cmd *cobra.Command, args []string) error
return fmt.Errorf("installation was cancelled by the user")
}
}
} else {
} else if status != install.PackageInstall {
if !force {
confirm, err := cli.Confirm(fmt.Sprintf("Elastic Agent will be installed at %s and will run as a service. Do you want to continue?", paths.InstallPath), true)
if err != nil {
Expand Down Expand Up @@ -141,30 +145,33 @@ func installCmd(streams *cli.IOStreams, cmd *cobra.Command, args []string) error
}
}
}
cfgFile := paths.ConfigFile()
err = install.Install(cfgFile)
if err != nil {
return err
}

defer func() {
if err != nil {
install.Uninstall(cfgFile)
}
}()

if !delayEnroll {
err = install.StartService()
cfgFile := paths.ConfigFile()
if status != install.PackageInstall {
err = install.Install(cfgFile) // can't run install for package installed agent, but must stop the running service and re-enroll, then start the service?
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure if we need to do the commented steps, currently the enroll command will be ran with the --from-install flag, but I don't know if that's correct

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure either, what is the behavior in the OS?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do not need to stop the running service, it should just enroll and then trigger a restart through the Elastic Agent control protocol. It should not need to be restarted through the service manager of the host.

if err != nil {
fmt.Fprintf(streams.Out, "Installation failed to start Elastic Agent service.\n")
return err
}

defer func() {
if err != nil {
install.StopService()
install.Uninstall(cfgFile)
}
}()

if !delayEnroll {
err = install.StartService()
if err != nil {
fmt.Fprintf(streams.Out, "Installation failed to start Elastic Agent service.\n")
return err
}

defer func() {
if err != nil {
install.StopService()
}
}()
}
}

if enroll {
Expand All @@ -180,10 +187,12 @@ func installCmd(streams *cli.IOStreams, cmd *cobra.Command, args []string) error
}
err = enrollCmd.Wait()
if err != nil {
install.Uninstall(cfgFile)
exitErr, ok := err.(*exec.ExitError)
if ok {
return fmt.Errorf("enroll command failed with exit code: %d", exitErr.ExitCode())
if status != install.PackageInstall {
install.Uninstall(cfgFile)
exitErr, ok := err.(*exec.ExitError)
if ok {
return fmt.Errorf("enroll command failed with exit code: %d", exitErr.ExitCode())
}
}
return fmt.Errorf("enroll command failed for unknown reason: %s", err)
}
Expand Down
51 changes: 51 additions & 0 deletions x-pack/elastic-agent/pkg/agent/install/install_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,59 @@

package install

import (
"os"
"os/exec"
"runtime"
"strings"

"github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/application/paths"
)

// postInstall performs post installation for unix-based systems.
func postInstall() error {
// do nothing
return nil
}

func checkPackageInstall() bool {
if runtime.GOOS != "linux" {
return false
}

// NOTE searching for english words might not be a great idea as far as portability goes.
// list all installed packages then search for paths.BinaryName?
// dpkg is strange as the remove and purge processes leads to the package bing isted after a remove, but not after a purge
Comment on lines +29 to +31
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would like feedback for this suggestion

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is expected because remove does not remove modified configuration files, those will remain. Only remove with purge completely removes all data on the system related to a package.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sadly yes, this is expected.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'll keep the comments here as the extra documentation can help if anyone else needs to work on this


// check debian based systems (or systems that use dpkg)
// If the package has been installed, the status starts with "install"
// If the package has been removed (but not pruged) status starts with "deinstall"
// If purged or never installed, rc is 1
if _, err := os.Stat("/etc/dpkg"); err == nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you thing about doing a which dpkg-query instead? Only thing would be is which always present on a system? I believe so.

out, err := exec.Command("dpkg-query", "-W", "-f", "${Status}", paths.BinaryName).Output()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be better to use the package name explicitly instead of the binary name. Even if they are the same at the moment use a specific constant for that would be better, that way if it ever changes.

Could you also use -s which will only show the status?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The output of -s includes more information than what we need:

vagrant@ubuntu-focal:~$ dpkg-query -s elastic-agent
Package: elastic-agent
Status: install ok installed
Priority: extra
Section: default
Installed-Size: 427208
Maintainer: <@ace0c0ea54fb>
Architecture: amd64
Version: 8.2.0
Conffiles:
 /etc/elastic-agent/.elastic-agent.active.commit 75bbbe44ef52356bb5e85a458166b1b6
 /etc/elastic-agent/elastic-agent.reference.yml 23aa69e9e641a43d7daaa0975edf0ec6
 /etc/elastic-agent/elastic-agent.yml f169e731b8e4c8a571dca0107954534d
 /etc/init.d/elastic-agent 79b301b56cc62629b9d276d6ae24ca2f
Description: Agent manages other beats based on configuration provided.
License: Elastic-License
Vendor: Elastic
Homepage: https://www.elastic.co/beats/elastic-agent

where the -W -f '${Status}' flags will give just that Status line

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@blakerouse, do you know where the package name is defined?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe it is using the name of the beat for the package name, so the way you have it works. I am just suggesting we use a different constant, with the same value. Just to future proof it that maybe one day they could be different.

Its more of a nitpick and if you prefer they way you have it, that is okay as well.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One question, here, I see we are interacting directly with the CLI, is there any C-Libary of even go package that we can use to abstract the package manager?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not finding a c or go library that can query dpkg/rpm for installed packages

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After a bit of digging we can find that the package name is defined by our Mage files as the BeatServiceName, this defaults to the same as BeatName for almost all cases (exception being heartbeat).

BeatName = EnvOr("BEAT_NAME", filepath.Base(CWD()))
BeatServiceName = EnvOr("BEAT_SERVICE_NAME", BeatName)

The value is set to the directory name we run mage out of (i.e., x-pack/elastic-agent). So I don't think it's a good idea to introduce a new variable for package name within the current codebase as it won't be picked up by our tooling.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sound good

if err != nil {
return false
}
if strings.HasPrefix(string(out), "deinstall") {
return false
}
return true
}

// check rhel and sles based systems (or systems that use rpm)
// if package has been installed query retuns with a list of associated files.
// otherwise if uninstalled, or has never been installled status ends with "not installed"
if _, err := os.Stat("/etc/rpm"); err == nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about which rpm here as well?

out, err := exec.Command("rpm", "-q", paths.BinaryName, "--state").Output()
if err != nil {
return false
}
if strings.HasSuffix(string(out), "not installed") {
return false
}
return true

}

return false
}
6 changes: 6 additions & 0 deletions x-pack/elastic-agent/pkg/agent/install/install_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,9 @@ func postInstall() error {

return nil
}

// checkPackageInstall is used for unix based systems to see if the Elastic-Agent was installed through a package manager.
// returns false
func checkPackageInstall() bool {
return false
}
10 changes: 9 additions & 1 deletion x-pack/elastic-agent/pkg/agent/install/installed.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,24 @@ type StatusType int
const (
// NotInstalled returned when Elastic Agent is not installed.
NotInstalled StatusType = iota
// Installed returned when Elastic Agent is installed currectly.
// Installed returned when Elastic Agent is installed correctly.
Installed
// Broken returned when Elastic Agent is installed but broken.
Broken
// PackageInstall returned when the Elastic agent has been installed through a package manager (deb/rpm)
PackageInstall
)

// Status returns the installation status of Agent.
func Status() (StatusType, string) {
expected := filepath.Join(paths.InstallPath, paths.BinaryName)
status, reason := checkService()
if checkPackageInstall() {
if status == Installed {
return PackageInstall, "service running"
}
return PackageInstall, "service not running"
}
_, err := os.Stat(expected)
if os.IsNotExist(err) {
if status == Installed {
Expand Down