diff --git a/e2e/_suites/fleet/fleet.go b/e2e/_suites/fleet/fleet.go index 895ec3e399..22e75dceed 100644 --- a/e2e/_suites/fleet/fleet.go +++ b/e2e/_suites/fleet/fleet.go @@ -37,10 +37,8 @@ type FleetTestSuite struct { CurrentToken string // current enrollment token CurrentTokenID string // current enrollment tokenID ElasticAgentStopped bool // will be used to signal when the agent process can be called again in the tear-down stage - Hostname string // the hostname of the container Image string // base image used to install the agent InstallerType string - Installers map[string]installer.ElasticAgentInstaller Integration kibana.IntegrationPackage // the installed integration Policy kibana.Policy PolicyUpdatedAt string // the moment the policy was updated @@ -58,50 +56,43 @@ func (fts *FleetTestSuite) afterScenario() { defer func() { deployedAgentsCount = 0 }() serviceName := common.ElasticAgentServiceName + agentService := deploy.NewServiceRequest(serviceName) if !fts.StandAlone { - agentInstaller := fts.getInstaller() + agentInstaller, _ := installer.Attach(fts.deployer, agentService, fts.InstallerType) if log.IsLevelEnabled(log.DebugLevel) { - err := agentInstaller.PrintLogsFn(fts.Hostname) + err := agentInstaller.Logs() if err != nil { - log.WithFields(log.Fields{ - "containerName": fts.Hostname, - "error": err, - }).Warn("Could not get agent logs in the container") + log.WithField("error", err).Warn("Could not get agent logs in the container") } } // only call it when the elastic-agent is present if !fts.ElasticAgentStopped { - err := agentInstaller.UninstallFn() + err := agentInstaller.Uninstall() if err != nil { log.Warnf("Could not uninstall the agent after the scenario: %v", err) } } } else if log.IsLevelEnabled(log.DebugLevel) { - _ = fts.getContainerLogs() + _ = fts.deployer.Logs(agentService) } err := fts.unenrollHostname() if err != nil { + manifest, _ := fts.deployer.Inspect(agentService) log.WithFields(log.Fields{ "err": err, - "hostname": fts.Hostname, + "hostname": manifest.Hostname, }).Warn("The agentIDs for the hostname could not be unenrolled") } developerMode := shell.GetEnvBool("DEVELOPER_MODE") if !developerMode { - image := "" - if !fts.StandAlone { - agentInstaller := fts.getInstaller() - image = agentInstaller.Image - } - _ = fts.deployer.Remove( []deploy.ServiceRequest{ deploy.NewServiceRequest(common.FleetProfileName), - deploy.NewServiceRequest(serviceName).WithFlavour(image), + deploy.NewServiceRequest(serviceName), }, common.ProfileEnv) } else { @@ -122,7 +113,6 @@ func (fts *FleetTestSuite) afterScenario() { fts.CurrentTokenID = "" fts.CurrentToken = "" fts.Image = "" - fts.Hostname = "" fts.StandAlone = false } @@ -230,11 +220,7 @@ func (fts *FleetTestSuite) anAgentIsDeployedToFleetWithInstallerAndFleetServer(i fts.Image = image fts.InstallerType = installerType - agentInstaller := fts.getInstaller() - - containerName := fts.getContainerName(agentInstaller) // name of the container - - // enroll the agent with a new token + // Grab a new enrollment key for new agent enrollmentKey, err := fts.kibanaClient.CreateEnrollmentAPIKey(fts.Policy) if err != nil { return err @@ -242,86 +228,46 @@ func (fts *FleetTestSuite) anAgentIsDeployedToFleetWithInstallerAndFleetServer(i fts.CurrentToken = enrollmentKey.APIKey fts.CurrentTokenID = enrollmentKey.ID - var fleetConfig *kibana.FleetConfig - fleetConfig, err = deployAgentToFleet(agentInstaller, fts.deployer, containerName, fts.CurrentToken) + agentService := deploy.NewServiceRequest(common.ElasticAgentServiceName).WithFlavour(image).WithScale(deployedAgentsCount) + services := []deploy.ServiceRequest{ + deploy.NewServiceRequest(common.FleetProfileName), + agentService, + } + err = fts.deployer.Add(services, common.ProfileEnv) if err != nil { return err } - // the installation process for TAR includes the enrollment - if agentInstaller.InstallerType != "tar" { - err = agentInstaller.EnrollFn(fleetConfig) - if err != nil { - return err - } - } - - // get container hostname once - hostname, err := deploy.GetContainerHostname(containerName) + agentInstaller, _ := installer.Attach(fts.deployer, agentService, installerType) + err = deployAgentToFleet(agentInstaller, fts.CurrentToken) if err != nil { return err } - fts.Hostname = hostname - return err } -// getContainerName returns the current container name for the service: -// we are using the Docker client instead of docker-compose because it does not support -// returning the output of a command: it simply returns error level -func (fts *FleetTestSuite) getContainerName(i installer.ElasticAgentInstaller) string { - return fmt.Sprintf("%s_%s_%d", i.Profile, common.ElasticAgentServiceName, deployedAgentsCount) -} - -// getServiceName returns the current service name, the one defined at the docker compose -func (fts *FleetTestSuite) getServiceName(i installer.ElasticAgentInstaller) string { - return i.Image -} - -func (fts *FleetTestSuite) getInstaller() installer.ElasticAgentInstaller { - key := fmt.Sprintf("%s-%s-%s-%d", fts.Image, fts.InstallerType, fts.Version, deployedAgentsCount) - // check if the agent is already cached - if i, exists := fts.Installers[key]; exists { - return i - } - - // setting current index for the installer - agentInstaller := installer.GetElasticAgentInstaller(fts.Image, fts.InstallerType, fts.Version, deployedAgentsCount) - - // cache the new installer - fts.Installers[key] = agentInstaller - - return agentInstaller -} - func (fts *FleetTestSuite) processStateChangedOnTheHost(process string, state string) error { - profile := common.FleetProfileName - - agentInstaller := fts.getInstaller() - - serviceName := agentInstaller.Service // name of the service - - profileService := deploy.NewServiceRequest(profile) - imageService := deploy.NewServiceRequest(common.ElasticAgentServiceName).WithFlavour(agentInstaller.Image) - + agentService := deploy.NewServiceRequest(common.ElasticAgentServiceName) + agentInstaller, _ := installer.Attach(fts.deployer, agentService, fts.InstallerType) if state == "started" { - return installer.SystemctlRun(profileService, imageService, serviceName, "start") + err := agentInstaller.Start() + return err } else if state == "restarted" { - err := installer.SystemctlRun(profileService, imageService, serviceName, "stop") + err := agentInstaller.Stop() if err != nil { return err } utils.Sleep(time.Duration(utils.TimeoutFactor) * 10 * time.Second) - err = installer.SystemctlRun(profileService, imageService, serviceName, "start") + err = agentInstaller.Start() if err != nil { return err } return nil } else if state == "uninstalled" { - err := agentInstaller.UninstallFn() + err := agentInstaller.Uninstall() if err != nil { return err } @@ -337,25 +283,25 @@ func (fts *FleetTestSuite) processStateChangedOnTheHost(process string, state st } log.WithFields(log.Fields{ - "service": serviceName, + "service": agentService.Name, "process": process, }).Trace("Stopping process on the service") - err := installer.SystemctlRun(profileService, imageService, serviceName, "stop") + err := agentInstaller.Stop() if err != nil { log.WithFields(log.Fields{ "action": state, "error": err, - "service": serviceName, + "service": agentService.Name, "process": process, }).Error("Could not stop process on the host") return err } - containerName := fts.getContainerName(agentInstaller) + manifest, _ := fts.deployer.Inspect(agentService) - return CheckProcessState(fts.deployer, containerName, process, "stopped", 1, utils.TimeoutFactor) + return CheckProcessState(fts.deployer, manifest.Name, process, "stopped", 0, utils.TimeoutFactor) } func (fts *FleetTestSuite) setup() error { @@ -370,7 +316,9 @@ func (fts *FleetTestSuite) setup() error { } func (fts *FleetTestSuite) theAgentIsListedInFleetWithStatus(desiredStatus string) error { - return theAgentIsListedInFleetWithStatus(desiredStatus, fts.Hostname) + agentService := deploy.NewServiceRequest(common.ElasticAgentServiceName) + manifest, _ := fts.deployer.Inspect(agentService) + return theAgentIsListedInFleetWithStatus(desiredStatus, manifest.Hostname) } func theAgentIsListedInFleetWithStatus(desiredStatus string, hostname string) error { @@ -448,51 +396,46 @@ func theAgentIsListedInFleetWithStatus(desiredStatus string, hostname string) er } func (fts *FleetTestSuite) theFileSystemAgentFolderIsEmpty() error { - agentInstaller := fts.getInstaller() + agentService := deploy.NewServiceRequest(common.ElasticAgentServiceName) + agentInstaller, _ := installer.Attach(fts.deployer, agentService, fts.InstallerType) - containerName := fts.getContainerName(agentInstaller) + pkgManifest, _ := agentInstaller.Inspect() + cmd := []string{ + "ls", "-l", pkgManifest.WorkDir, + } - content, err := agentInstaller.ListElasticAgentWorkingDirContent(containerName) + content, err := agentInstaller.Exec(cmd) if err != nil { + if content == "" || strings.Contains(content, "No such file or directory") { + return nil + } return err } - if content == "" || strings.Contains(content, "No such file or directory") { - return nil - } + log.WithFields(log.Fields{ + "installer": agentInstaller, + "workingDir": pkgManifest.WorkDir, + "content": content, + }).Debug("Agent working dir content") return fmt.Errorf("The file system directory is not empty") } func (fts *FleetTestSuite) theHostIsRestarted() error { - agentInstaller := fts.getInstaller() - - containerName := fts.getContainerName(agentInstaller) - _, err := shell.Execute(context.Background(), ".", "docker", "stop", containerName) + agentService := deploy.NewServiceRequest(common.ElasticAgentServiceName) + err := fts.deployer.Stop(agentService) if err != nil { - log.WithFields(log.Fields{ - "containerName": containerName, - "image": agentInstaller.Image, - "service": agentInstaller.Service, - }).Error("Could not stop the service") + log.WithField("err", err).Error("Could not stop the service") } utils.Sleep(time.Duration(utils.TimeoutFactor) * 10 * time.Second) - _, err = shell.Execute(context.Background(), ".", "docker", "start", containerName) + err = fts.deployer.Start(agentService) if err != nil { - log.WithFields(log.Fields{ - "containerName": containerName, - "image": agentInstaller.Image, - "service": agentInstaller.Service, - }).Error("Could not start the service") + log.WithField("err", err).Error("Could not start the service") } - log.WithFields(log.Fields{ - "containerName": containerName, - "image": agentInstaller.Image, - "service": agentInstaller.Service, - }).Debug("The service has been restarted") + log.Debug("The elastic-agent service has been restarted") return nil } @@ -563,18 +506,10 @@ func (fts *FleetTestSuite) theAgentIsUnenrolled() error { func (fts *FleetTestSuite) theAgentIsReenrolledOnTheHost() error { log.Trace("Re-enrolling the agent on the host with same token") - agentInstaller := fts.getInstaller() + agentService := deploy.NewServiceRequest(common.ElasticAgentServiceName) + agentInstaller, _ := installer.Attach(fts.deployer, agentService, fts.InstallerType) - // a re-enroll does need to bootstrap the Fleet Server again - // during an unenroll the fleet server exits as there is no longer - // and agent id associated with the enrollment. When fleet server - // restarts it needs a new agent to associate with the boostrap - cfg, err := kibana.NewFleetConfig(fts.CurrentToken) - if err != nil { - return err - } - - err = agentInstaller.EnrollFn(cfg) + err := agentInstaller.Enroll(fts.CurrentToken) if err != nil { return err } @@ -650,13 +585,15 @@ func (fts *FleetTestSuite) theHostNameIsNotShownInTheAdminViewInTheSecurityApp() exp := utils.GetExponentialBackOff(maxTimeout) agentListedInSecurityFn := func() error { - host, err := fts.kibanaClient.IsAgentListedInSecurityApp(fts.Hostname) + agentService := deploy.NewServiceRequest(common.ElasticAgentServiceName) + manifest, _ := fts.deployer.Inspect(agentService) + host, err := fts.kibanaClient.IsAgentListedInSecurityApp(manifest.Hostname) if err != nil { log.WithFields(log.Fields{ "elapsedTime": exp.GetElapsedTime(), "err": err, "host": host, - "hostname": fts.Hostname, + "hostname": manifest.Hostname, "retry": retryCount, }).Warn("We could not check the agent in the Administration view in the Security App yet") @@ -667,7 +604,7 @@ func (fts *FleetTestSuite) theHostNameIsNotShownInTheAdminViewInTheSecurityApp() log.WithFields(log.Fields{ "elapsedTime": exp.GetElapsedTime(), - "hostname": fts.Hostname, + "hostname": manifest.Hostname, "retries": retryCount, }).Info("The Agent is not listed in the Administration view in the Security App") return nil @@ -690,13 +627,15 @@ func (fts *FleetTestSuite) theHostNameIsShownInTheAdminViewInTheSecurityApp(stat exp := utils.GetExponentialBackOff(maxTimeout) agentListedInSecurityFn := func() error { - matches, err := fts.kibanaClient.IsAgentListedInSecurityAppWithStatus(fts.Hostname, status) + agentService := deploy.NewServiceRequest(common.ElasticAgentServiceName) + manifest, _ := fts.deployer.Inspect(agentService) + matches, err := fts.kibanaClient.IsAgentListedInSecurityAppWithStatus(manifest.Hostname, status) if err != nil || !matches { log.WithFields(log.Fields{ "elapsedTime": exp.GetElapsedTime(), "desiredStatus": status, "err": err, - "hostname": fts.Hostname, + "hostname": manifest.Hostname, "matches": matches, "retry": retryCount, }).Warn("The agent is not listed in the Administration view in the Security App in the desired status yet") @@ -709,7 +648,7 @@ func (fts *FleetTestSuite) theHostNameIsShownInTheAdminViewInTheSecurityApp(stat log.WithFields(log.Fields{ "elapsedTime": exp.GetElapsedTime(), "desiredStatus": status, - "hostname": fts.Hostname, + "hostname": manifest.Hostname, "matches": matches, "retries": retryCount, }).Info("The Agent is listed in the Administration view in the Security App in the desired status") @@ -734,7 +673,9 @@ func (fts *FleetTestSuite) anIntegrationIsSuccessfullyDeployedWithAgentAndInstal } func (fts *FleetTestSuite) thePolicyResponseWillBeShownInTheSecurityApp() error { - agentID, err := fts.kibanaClient.GetAgentIDByHostname(fts.Hostname) + agentService := deploy.NewServiceRequest(common.ElasticAgentServiceName) + manifest, _ := fts.deployer.Inspect(agentService) + agentID, err := fts.kibanaClient.GetAgentIDByHostname(manifest.Hostname) if err != nil { return err } @@ -828,7 +769,9 @@ func (fts *FleetTestSuite) thePolicyIsUpdatedToHaveMode(name string, mode string } func (fts *FleetTestSuite) thePolicyWillReflectTheChangeInTheSecurityApp() error { - agentID, err := fts.kibanaClient.GetAgentIDByHostname(fts.Hostname) + agentService := deploy.NewServiceRequest(common.ElasticAgentServiceName) + manifest, _ := fts.deployer.Inspect(agentService) + agentID, err := fts.kibanaClient.GetAgentIDByHostname(manifest.Hostname) if err != nil { return err } @@ -898,35 +841,28 @@ func (fts *FleetTestSuite) anAttemptToEnrollANewAgentFails() error { // increase the number of agents deployedAgentsCount++ - agentInstaller := fts.getInstaller() - - containerName := fts.getContainerName(agentInstaller) // name of the new container - - fleetConfig, err := deployAgentToFleet(agentInstaller, fts.deployer, containerName, fts.CurrentToken) - - // the installation process for TAR includes the enrollment - if agentInstaller.InstallerType != "tar" { - if err != nil { - return err - } + agentService := deploy.NewServiceRequest(common.ElasticAgentServiceName).WithFlavour(fts.Image).WithScale(deployedAgentsCount) + services := []deploy.ServiceRequest{ + deploy.NewServiceRequest(common.FleetProfileName), + agentService, + } - err = agentInstaller.EnrollFn(fleetConfig) - if err == nil { - err = fmt.Errorf("The agent was enrolled although the token was previously revoked") + err := fts.deployer.Add(services, common.ProfileEnv) + if err != nil { + return err + } - log.WithFields(log.Fields{ - "tokenID": fts.CurrentTokenID, - "error": err, - }).Error(err.Error()) + agentInstaller, _ := installer.Attach(fts.deployer, agentService, fts.InstallerType) + err = deployAgentToFleet(agentInstaller, fts.CurrentToken) - return err - } + if err == nil { + err = fmt.Errorf("The agent was enrolled although the token was previously revoked") log.WithFields(log.Fields{ - "err": err, - "token": fts.CurrentToken, - }).Debug("As expected, it's not possible to enroll an agent with a revoked token") - return nil + "tokenID": fts.CurrentTokenID, + "error": err, + }).Error(err.Error()) + return err } // checking the error message produced by the install command in TAR installer @@ -939,12 +875,14 @@ func (fts *FleetTestSuite) anAttemptToEnrollANewAgentFails() error { return nil } - return err + return nil } // unenrollHostname deletes the statuses for an existing agent, filtering by hostname func (fts *FleetTestSuite) unenrollHostname() error { - log.Tracef("Un-enrolling all agentIDs for %s", fts.Hostname) + agentService := deploy.NewServiceRequest(common.ElasticAgentServiceName) + manifest, _ := fts.deployer.Inspect(agentService) + log.Tracef("Un-enrolling all agentIDs for %s", manifest.Hostname) agents, err := fts.kibanaClient.ListAgents() if err != nil { @@ -952,9 +890,9 @@ func (fts *FleetTestSuite) unenrollHostname() error { } for _, agent := range agents { - if agent.LocalMetadata.Host.HostName == fts.Hostname { + if agent.LocalMetadata.Host.HostName == manifest.Hostname { log.WithFields(log.Fields{ - "hostname": fts.Hostname, + "hostname": manifest.Hostname, }).Debug("Un-enrolling agent in Fleet") err := fts.kibanaClient.UnEnrollAgent(agent.LocalMetadata.Host.HostName) @@ -1041,56 +979,23 @@ func (fts *FleetTestSuite) checkDataStream() error { return err } -func deployAgentToFleet(agentInstaller installer.ElasticAgentInstaller, deployer deploy.Deployment, containerName string, token string) (*kibana.FleetConfig, error) { - profile := agentInstaller.Profile // name of the runtime dependencies compose file - service := agentInstaller.Service // name of the service - serviceTag := agentInstaller.Tag // docker tag of the service - - envVarsPrefix := strings.ReplaceAll(service, "-", "_") - - // let's start with Centos 7 - common.ProfileEnv[envVarsPrefix+"Tag"] = serviceTag - // we are setting the container name because Centos service could be reused by any other test suite - common.ProfileEnv[envVarsPrefix+"ContainerName"] = containerName - - agentService := deploy.NewServiceRequest(common.ElasticAgentServiceName).WithFlavour(agentInstaller.Image).WithScale(deployedAgentsCount) - - services := []deploy.ServiceRequest{deploy.NewServiceRequest(profile), agentService} - - err := deployer.Add(services, common.ProfileEnv) - if err != nil { - log.WithFields(log.Fields{ - "service": service, - "tag": serviceTag, - }).Error("Could not run the target box") - return nil, err - } - - isTar := (agentInstaller.InstallerType == "tar") - targetFile := "/" - - // copy downloaded agent to the root dir of the container - err = deploy.CopyFileToContainer(context.Background(), containerName, agentInstaller.BinaryPath, targetFile, isTar) +func deployAgentToFleet(agentInstaller deploy.ServiceOperator, token string) error { + err := agentInstaller.Preinstall() if err != nil { - return nil, err + return err } - err = agentInstaller.PreInstallFn() + err = agentInstaller.Install() if err != nil { - return nil, err - } - - cfg, cfgError := kibana.NewFleetConfig(token) - if cfgError != nil { - return nil, cfgError + return err } - err = agentInstaller.InstallFn(cfg) + err = agentInstaller.Enroll(token) if err != nil { - return nil, err + return err } - return cfg, agentInstaller.PostInstallFn() + return agentInstaller.Postinstall() } func inputs(integration string) []kibana.Input { @@ -1135,32 +1040,3 @@ func inputs(integration string) []kibana.Input { } return []kibana.Input{} } - -func (fts *FleetTestSuite) getContainerLogs() error { - serviceManager := deploy.NewServiceManager() - - image := "" - if !fts.StandAlone { - agentInstaller := fts.getInstaller() - image = agentInstaller.Image - } - - profile := deploy.NewServiceRequest(common.FleetProfileName) - serviceName := common.ElasticAgentServiceName - - services := []deploy.ServiceRequest{ - profile, // profile name - deploy.NewServiceRequest(serviceName).WithFlavour(image), // agent service - } - err := serviceManager.RunCommand(profile, services, []string{"logs", serviceName}, common.ProfileEnv) - if err != nil { - log.WithFields(log.Fields{ - "error": err, - "service": serviceName, - }).Error("Could not retrieve Elastic Agent logs") - - return err - } - - return nil -} diff --git a/e2e/_suites/fleet/ingest_manager_test.go b/e2e/_suites/fleet/ingest_manager_test.go index e9df20c969..6a1a011029 100644 --- a/e2e/_suites/fleet/ingest_manager_test.go +++ b/e2e/_suites/fleet/ingest_manager_test.go @@ -13,10 +13,8 @@ import ( "github.com/elastic/e2e-testing/cli/config" "github.com/elastic/e2e-testing/internal/common" "github.com/elastic/e2e-testing/internal/deploy" - "github.com/elastic/e2e-testing/internal/installer" "github.com/elastic/e2e-testing/internal/kibana" "github.com/elastic/e2e-testing/internal/shell" - "github.com/elastic/e2e-testing/internal/utils" log "github.com/sirupsen/logrus" ) @@ -32,31 +30,17 @@ func setUpSuite() { } common.Provider = shell.GetEnv("PROVIDER", common.Provider) - developerMode := shell.GetEnvBool("DEVELOPER_MODE") - if developerMode { + common.DeveloperMode = shell.GetEnvBool("DEVELOPER_MODE") + if common.DeveloperMode { log.Info("Running in Developer mode 💻: runtime dependencies between different test runs will be reused to speed up dev cycle") } common.InitVersions() - common.KibanaVersion = shell.GetEnv("KIBANA_VERSION", "") - if common.KibanaVersion == "" { - // we want to deploy a released version for Kibana - // if not set, let's use stackVersion - common.KibanaVersion, err = utils.GetElasticArtifactVersion(common.StackVersion) - if err != nil { - log.WithFields(log.Fields{ - "error": err, - "version": common.KibanaVersion, - }).Fatal("Failed to get kibana version, aborting") - } - } - imts = IngestManagerTestSuite{ Fleet: &FleetTestSuite{ kibanaClient: kibanaClient, deployer: deploy.New(common.Provider), - Installers: map[string]installer.ElasticAgentInstaller{}, // do not pre-initialise the map }, } } @@ -79,8 +63,6 @@ func InitializeIngestManagerTestScenario(ctx *godog.ScenarioContext) { } func InitializeIngestManagerTestSuite(ctx *godog.TestSuiteContext) { - developerMode := shell.GetEnvBool("DEVELOPER_MODE") - ctx.BeforeSuite(func() { setUpSuite() @@ -120,30 +102,10 @@ func InitializeIngestManagerTestSuite(ctx *godog.TestSuiteContext) { }) ctx.AfterSuite(func() { - if !developerMode { + if !common.DeveloperMode { log.Debug("Destroying Fleet runtime dependencies") deployer := deploy.New(common.Provider) deployer.Destroy() } - - installers := imts.Fleet.Installers - for k, v := range installers { - agentPath := v.BinaryPath - if _, err := os.Stat(agentPath); err == nil { - err = os.Remove(agentPath) - if err != nil { - log.WithFields(log.Fields{ - "err": err, - "installer": k, - "path": agentPath, - }).Warn("Elastic Agent binary could not be removed.") - } else { - log.WithFields(log.Fields{ - "installer": k, - "path": agentPath, - }).Debug("Elastic Agent binary was removed.") - } - } - } }) } diff --git a/e2e/_suites/fleet/stand-alone.go b/e2e/_suites/fleet/stand-alone.go index b60d0ff10d..8a44b69eb9 100644 --- a/e2e/_suites/fleet/stand-alone.go +++ b/e2e/_suites/fleet/stand-alone.go @@ -51,7 +51,10 @@ func (fts *FleetTestSuite) thereIsNewDataInTheIndexFromAgent() error { maxTimeout := time.Duration(utils.TimeoutFactor) * time.Minute * 2 minimumHitsCount := 50 - result, err := searchAgentData(fts.Hostname, fts.RuntimeDependenciesStartDate, minimumHitsCount, maxTimeout) + agentService := deploy.NewServiceRequest(common.ElasticAgentServiceName).WithFlavour(fts.Image) + + manifest, _ := fts.deployer.Inspect(agentService) + result, err := searchAgentData(manifest.Hostname, fts.RuntimeDependenciesStartDate, minimumHitsCount, maxTimeout) if err != nil { return err } @@ -62,11 +65,8 @@ func (fts *FleetTestSuite) thereIsNewDataInTheIndexFromAgent() error { } func (fts *FleetTestSuite) theDockerContainerIsStopped(serviceName string) error { - services := []deploy.ServiceRequest{ - deploy.NewServiceRequest(common.FleetProfileName), - deploy.NewServiceRequest(serviceName), - } - err := fts.deployer.Remove(services, common.ProfileEnv) + agentService := deploy.NewServiceRequest(serviceName) + err := fts.deployer.Stop(agentService) if err != nil { return err } @@ -79,7 +79,9 @@ func (fts *FleetTestSuite) thereIsNoNewDataInTheIndexAfterAgentShutsDown() error maxTimeout := time.Duration(30) * time.Second minimumHitsCount := 1 - result, err := searchAgentData(fts.Hostname, fts.AgentStoppedDate, minimumHitsCount, maxTimeout) + agentService := deploy.NewServiceRequest(common.ElasticAgentServiceName) + manifest, _ := fts.deployer.Inspect(agentService) + result, err := searchAgentData(manifest.Hostname, fts.AgentStoppedDate, minimumHitsCount, maxTimeout) if err != nil { if strings.Contains(err.Error(), "type:index_not_found_exception") { return err @@ -102,14 +104,14 @@ func (fts *FleetTestSuite) startStandAloneAgent(image string, flavour string, en useCISnapshots := shell.GetEnvBool("BEATS_USE_CI_SNAPSHOTS") beatsLocalPath := shell.GetEnv("BEATS_LOCAL_PATH", "") + if useCISnapshots || beatsLocalPath != "" { // load the docker images that were already: // a. downloaded from the GCP bucket // b. fetched from the local beats binaries - dockerInstaller := installer.GetElasticAgentInstaller("docker", image, common.BeatVersion, deployedAgentsCount) - - dockerInstaller.PreInstallFn() - + agentService := deploy.NewServiceRequest(common.ElasticAgentServiceName) + dockerInstaller, _ := installer.Attach(fts.deployer, agentService, "docker") + dockerInstaller.Preinstall() dockerImageTag += "-amd64" } @@ -140,14 +142,7 @@ func (fts *FleetTestSuite) startStandAloneAgent(image string, flavour string, en return err } - // get container hostname once - hostname, err := deploy.GetContainerHostname(containerName) - if err != nil { - return err - } - fts.Image = image - fts.Hostname = hostname err = fts.installTestTools(containerName) if err != nil { @@ -208,7 +203,7 @@ func (fts *FleetTestSuite) installTestTools(containerName string) error { "containerName": containerName, }).Trace("Installing test tools ") - _, err := deploy.ExecCommandIntoContainer(context.Background(), deploy.NewServiceRequest(containerName), "root", cmd) + _, err := deploy.ExecCommandIntoContainer(context.Background(), containerName, "root", cmd) if err != nil { log.WithFields(log.Fields{ "command": cmd, diff --git a/e2e/_suites/fleet/world.go b/e2e/_suites/fleet/world.go index 4e02804303..a8ab28faf6 100644 --- a/e2e/_suites/fleet/world.go +++ b/e2e/_suites/fleet/world.go @@ -23,7 +23,11 @@ type IngestManagerTestSuite struct { } func (imts *IngestManagerTestSuite) processStateOnTheHost(process string, state string) error { - return imts.thereAreInstancesOfTheProcessInTheState("1", process, state) + ocurrences := "1" + if state == "uninstalled" || state == "stopped" { + ocurrences = "0" + } + return imts.thereAreInstancesOfTheProcessInTheState(ocurrences, process, state) } func (imts *IngestManagerTestSuite) thereAreInstancesOfTheProcessInTheState(ocurrences string, process string, state string) error { @@ -34,8 +38,9 @@ func (imts *IngestManagerTestSuite) thereAreInstancesOfTheProcessInTheState(ocur if imts.Fleet.StandAlone { containerName = fmt.Sprintf("%s_%s_%d", profile, common.ElasticAgentServiceName, 1) } else { - agentInstaller := imts.Fleet.getInstaller() - containerName = imts.Fleet.getContainerName(agentInstaller) + agentService := deploy.NewServiceRequest(common.ElasticAgentServiceName) + manifest, _ := imts.Fleet.deployer.Inspect(agentService) + containerName = manifest.Name } count, err := strconv.Atoi(ocurrences) @@ -100,6 +105,22 @@ func waitForProcess(deployer deploy.Deployment, service string, process string, cmds := []string{"pgrep", "-d", ",", process} output, err := deployer.ExecIn(serviceRequest, cmds) if err != nil { + + if !mustBePresent && ocurrences == 0 { + log.WithFields(log.Fields{ + "cmds": cmds, + "desiredState": desiredState, + "elapsedTime": exp.GetElapsedTime(), + "error": err, + "service": service, + "mustBePresent": mustBePresent, + "ocurrences": ocurrences, + "process": process, + "retry": retryCount, + }).Warn("Process is not present and number of occurences is 0") + return nil + } + log.WithFields(log.Fields{ "cmds": cmds, "desiredState": desiredState, diff --git a/internal/common/defaults.go b/internal/common/defaults.go index 54ec149a6a..3d177aa1c1 100644 --- a/internal/common/defaults.go +++ b/internal/common/defaults.go @@ -28,6 +28,10 @@ const FleetProfileName = "fleet" // FleetServerAgentServiceName the name of the service for the Elastic Agent const FleetServerAgentServiceName = "fleet-server" +// AgentStaleVersion is the version of the agent to use as a base during upgrade +// It can be overriden by ELASTIC_AGENT_STALE_VERSION env var. Using latest GA as a default. +var AgentStaleVersion = "7.13-SNAPSHOT" + // BeatVersionBase is the base version of the Beat to use var BeatVersionBase = "7.13.0-SNAPSHOT" @@ -35,13 +39,8 @@ var BeatVersionBase = "7.13.0-SNAPSHOT" // It can be overriden by BEAT_VERSION env var var BeatVersion = BeatVersionBase -// AgentStaleVersion is the version of the agent to use as a base during upgrade -// It can be overriden by ELASTIC_AGENT_STALE_VERSION env var. Using latest GA as a default. -var AgentStaleVersion = "7.12-SNAPSHOT" - -// StackVersion is the version of the stack to use -// It can be overriden by STACK_VERSION env var -var StackVersion = BeatVersionBase +// DeveloperMode if enabled will keep deployments around after test runs +var DeveloperMode = false // KibanaVersion is the version of kibana to use // It can be override by KIBANA_VERSION @@ -54,6 +53,10 @@ var ProfileEnv map[string]string // Provider is the deployment provider used, currently docker is supported var Provider = "docker" +// StackVersion is the version of the stack to use +// It can be overriden by STACK_VERSION env var +var StackVersion = BeatVersionBase + // InitVersions initialise default versions. We do not want to do it in the init phase // supporting lazy-loading the versions when needed. Basically, the CLI part does not // need to load them @@ -91,4 +94,24 @@ func InitVersions() { }).Fatal("Failed to get stack version, aborting") } StackVersion = v + + KibanaVersion = shell.GetEnv("KIBANA_VERSION", "") + if KibanaVersion == "" { + // we want to deploy a released version for Kibana + // if not set, let's use StackVersion + KibanaVersion, err = utils.GetElasticArtifactVersion(StackVersion) + if err != nil { + log.WithFields(log.Fields{ + "error": err, + "version": KibanaVersion, + }).Fatal("Failed to get kibana version, aborting") + } + } + + log.WithFields(log.Fields{ + "BeatVersionBase": BeatVersionBase, + "BeatVersion": BeatVersion, + "StackVersion": StackVersion, + "KibanaVersion": KibanaVersion, + }).Trace("Initial artifact versions defined") } diff --git a/internal/deploy/base.go b/internal/deploy/base.go index 4500c6ee2c..a7eea272be 100644 --- a/internal/deploy/base.go +++ b/internal/deploy/base.go @@ -12,11 +12,37 @@ import ( // required for testing type Deployment interface { Add(services []ServiceRequest, env map[string]string) error // adds a service to deployment + AddFiles(service ServiceRequest, files []string) error // adds files to a service Bootstrap(waitCB func() error) error // will bootstrap or reuse existing cluster if kubernetes is selected Destroy() error // Teardown deployment ExecIn(service ServiceRequest, cmd []string) (string, error) // Execute arbitrary commands in service Inspect(service ServiceRequest) (*ServiceManifest, error) // inspects service + Logs(service ServiceRequest) error // prints logs of deployed service Remove(services []ServiceRequest, env map[string]string) error // Removes services from deployment + Start(service ServiceRequest) error // Starts a service or container depending on Deployment + Stop(service ServiceRequest) error // Stop a service or container depending on deployment +} + +// ServiceOperator represents the operations that can be performed by a service +type ServiceOperator interface { + AddFiles(files []string) error // adds files to service environment + Enroll(token string) error // handle any enrollment/registering of service + Exec(args []string) (string, error) // exec arbitrary commands in service environment + Inspect() (ServiceOperatorManifest, error) // returns manifest for package + Install() error + InstallCerts() error + Logs() error + Postinstall() error + Preinstall() error + Start() error // will start a service + Stop() error // will stop a service + Uninstall() error +} + +// ServiceOperatorManifest is state information for each service operator +type ServiceOperatorManifest struct { + CommitFile string + WorkDir string } // ServiceManifest information about a service in a deployment @@ -24,7 +50,9 @@ type ServiceManifest struct { ID string Name string Connection string // a string representing how to connect to service + Alias string // container network aliases Hostname string + Platform string // running in linux, macos, windows } // ServiceRequest represents the service to be created using the provider diff --git a/internal/deploy/docker.go b/internal/deploy/docker.go index 27faf4860b..455fa81be0 100644 --- a/internal/deploy/docker.go +++ b/internal/deploy/docker.go @@ -6,9 +6,11 @@ package deploy import ( "context" + "path/filepath" "strings" "github.com/elastic/e2e-testing/internal/common" + "github.com/elastic/e2e-testing/internal/shell" "github.com/elastic/e2e-testing/internal/utils" log "github.com/sirupsen/logrus" ) @@ -58,6 +60,23 @@ func (c *dockerDeploymentManifest) Bootstrap(waitCB func() error) error { return nil } +// AddFiles - add files to service +func (c *dockerDeploymentManifest) AddFiles(service ServiceRequest, files []string) error { + container, _ := c.Inspect(service) + for _, file := range files { + isTar := true + fileExt := filepath.Ext(file) + if fileExt == ".rpm" || fileExt == ".deb" { + isTar = false + } + err := CopyFileToContainer(c.Context, container.Name, file, "/", isTar) + if err != nil { + log.WithField("error", err).Fatal("Unable to copy file to service") + } + } + return nil +} + // Destroy teardown docker environment func (c *dockerDeploymentManifest) Destroy() error { serviceManager := NewServiceManager() @@ -73,7 +92,12 @@ func (c *dockerDeploymentManifest) Destroy() error { // ExecIn execute command in service func (c *dockerDeploymentManifest) ExecIn(service ServiceRequest, cmd []string) (string, error) { - output, err := ExecCommandIntoContainer(c.Context, service, "root", cmd) + inspect, _ := c.Inspect(service) + args := []string{"exec", "-u", "root", "-i", inspect.Name} + for _, cmdArg := range cmd { + args = append(args, cmdArg) + } + output, err := shell.Execute(c.Context, ".", "docker", args...) if err != nil { return "", err } @@ -86,17 +110,54 @@ func (c *dockerDeploymentManifest) Inspect(service ServiceRequest) (*ServiceMani if err != nil { return &ServiceManifest{}, err } + return &ServiceManifest{ ID: inspect.ID, Name: strings.TrimPrefix(inspect.Name, "/"), Connection: service.Name, - Hostname: inspect.NetworkSettings.Networks["fleet_default"].Aliases[0], + Alias: inspect.NetworkSettings.Networks["fleet_default"].Aliases[0], + Hostname: inspect.Config.Hostname, + Platform: inspect.Platform, }, nil } +// Logs print logs of service +func (c *dockerDeploymentManifest) Logs(service ServiceRequest) error { + manifest, _ := c.Inspect(service) + _, err := shell.Execute(c.Context, ".", "docker", "logs", manifest.Name) + if err != nil { + log.WithFields(log.Fields{ + "error": err, + "service": service.Name, + }).Error("Could not retrieve Elastic Agent logs") + + return err + } + return nil +} + // Remove remove services from deployment func (c *dockerDeploymentManifest) Remove(services []ServiceRequest, env map[string]string) error { - serviceManager := NewServiceManager() + for _, service := range services[1:] { + manifest, _ := c.Inspect(service) + _, err := shell.Execute(c.Context, ".", "docker", "rm", "-fv", manifest.Name) + if err != nil { + return err + } + } + return nil +} + +// Start a container +func (c *dockerDeploymentManifest) Start(service ServiceRequest) error { + manifest, _ := c.Inspect(service) + _, err := shell.Execute(c.Context, ".", "docker", "start", manifest.Name) + return err +} - return serviceManager.RemoveServicesFromCompose(c.Context, services[0], services[1:], env) +// Stop a container +func (c *dockerDeploymentManifest) Stop(service ServiceRequest) error { + manifest, _ := c.Inspect(service) + _, err := shell.Execute(c.Context, ".", "docker", "stop", manifest.Name) + return err } diff --git a/internal/deploy/docker_client.go b/internal/deploy/docker_client.go index f6a6b4f7b4..8d0e14c589 100644 --- a/internal/deploy/docker_client.go +++ b/internal/deploy/docker_client.go @@ -135,18 +135,18 @@ func CopyFileToContainer(ctx context.Context, containerName string, srcPath stri } // ExecCommandIntoContainer executes a command, as a user, into a container -func ExecCommandIntoContainer(ctx context.Context, container ServiceRequest, user string, cmd []string) (string, error) { +func ExecCommandIntoContainer(ctx context.Context, container string, user string, cmd []string) (string, error) { return ExecCommandIntoContainerWithEnv(ctx, container, user, cmd, []string{}) } // ExecCommandIntoContainerWithEnv executes a command, as a user, with env, into a container -func ExecCommandIntoContainerWithEnv(ctx context.Context, container ServiceRequest, user string, cmd []string, env []string) (string, error) { +func ExecCommandIntoContainerWithEnv(ctx context.Context, container string, user string, cmd []string, env []string) (string, error) { dockerClient := getDockerClient() detach := false tty := false - containerName := container.Name + containerName := container log.WithFields(log.Fields{ "container": containerName, @@ -192,6 +192,8 @@ func ExecCommandIntoContainerWithEnv(ctx context.Context, container ServiceReque Detach: detach, Tty: tty, }) + defer resp.Close() + if err != nil { log.WithFields(log.Fields{ "container": containerName, @@ -203,7 +205,6 @@ func ExecCommandIntoContainerWithEnv(ctx context.Context, container ServiceReque }).Error("Could not execute command in container") return "", err } - defer resp.Close() // see https://stackoverflow.com/a/57132902 var execRes execResult @@ -268,7 +269,7 @@ func GetContainerHostname(containerName string) (string, error) { "containerName": containerName, }).Trace("Retrieving container name from the Docker client") - hostname, err := ExecCommandIntoContainer(context.Background(), NewServiceRequest(containerName), "root", []string{"cat", "/etc/hostname"}) + hostname, err := ExecCommandIntoContainer(context.Background(), containerName, "root", []string{"cat", "/etc/hostname"}) if err != nil { log.WithFields(log.Fields{ "containerName": containerName, diff --git a/internal/deploy/docker_client_test.go b/internal/deploy/docker_client_test.go index f44d420244..7c912f7634 100644 --- a/internal/deploy/docker_client_test.go +++ b/internal/deploy/docker_client_test.go @@ -40,7 +40,7 @@ func Test_CopyFile(t *testing.T) { err = CopyFileToContainer(ctx, containerName, src, target, false) assert.Nil(t, err) - output, err := ExecCommandIntoContainer(ctx, NewServiceRequest(containerName), "root", []string{"cat", "/tmp/dockerCopy.txt"}) + output, err := ExecCommandIntoContainer(ctx, containerName, "root", []string{"cat", "/tmp/dockerCopy.txt"}) assert.Nil(t, err) assert.True(t, strings.HasSuffix(output, "OK!"), "File contains the 'OK!' string") }) @@ -76,7 +76,7 @@ func Test_CopyFile(t *testing.T) { err = CopyFileToContainer(ctx, containerName, src, target, true) assert.Nil(t, err) - output, err := ExecCommandIntoContainer(ctx, NewServiceRequest(containerName), "root", []string{"ls", "/project/txtr/kermit.jpg"}) + output, err := ExecCommandIntoContainer(ctx, containerName, "root", []string{"ls", "/project/txtr/kermit.jpg"}) assert.Nil(t, err) assert.True(t, strings.Contains(output, "/project/txtr/kermit.jpg"), "File '/project/txtr/kermit.jpg' should be present") }) diff --git a/internal/deploy/kubernetes.go b/internal/deploy/kubernetes.go index 2b13196712..51b1bfd885 100644 --- a/internal/deploy/kubernetes.go +++ b/internal/deploy/kubernetes.go @@ -12,6 +12,7 @@ import ( "github.com/elastic/e2e-testing/internal/kubernetes" "github.com/pkg/errors" + log "github.com/sirupsen/logrus" ) var cluster kubernetes.Cluster @@ -39,6 +40,20 @@ func (c *kubernetesDeploymentManifest) Add(services []ServiceRequest, env map[st return nil } +// AddFiles - add files to deployment service +func (c *kubernetesDeploymentManifest) AddFiles(service ServiceRequest, files []string) error { + container, _ := c.Inspect(service) + kubectl = cluster.Kubectl().WithNamespace(c.Context, "default") + + for _, file := range files { + _, err := kubectl.Run(c.Context, "cp", file, fmt.Sprintf("deployment/%s:.", container.Name)) + if err != nil { + log.WithField("error", err).Fatal("Unable to copy file to service") + } + } + return nil +} + // Bootstrap sets up environment with kind func (c *kubernetesDeploymentManifest) Bootstrap(waitCB func() error) error { err := cluster.Initialize(c.Context, "../../../cli/config/kubernetes/kind.yaml") @@ -102,9 +117,26 @@ func (c *kubernetesDeploymentManifest) Inspect(service ServiceRequest) (*Service Name: strings.TrimPrefix(inspect.Metadata.Name, "/"), Connection: service.Name, Hostname: service.Name, + Alias: service.Name, + Platform: "linux", }, nil } +// Logs print logs of service +func (c *kubernetesDeploymentManifest) Logs(service ServiceRequest) error { + kubectl = cluster.Kubectl().WithNamespace(c.Context, "default") + _, err := kubectl.Run(c.Context, "logs", "deployment/"+service.Name) + if err != nil { + log.WithFields(log.Fields{ + "error": err, + "service": service.Name, + }).Error("Could not retrieve Elastic Agent logs") + + return err + } + return nil +} + // Remove remove services from deployment func (c *kubernetesDeploymentManifest) Remove(services []ServiceRequest, env map[string]string) error { kubectl = cluster.Kubectl().WithNamespace(c.Context, "default") @@ -117,3 +149,13 @@ func (c *kubernetesDeploymentManifest) Remove(services []ServiceRequest, env map } return nil } + +// Start a container +func (c *kubernetesDeploymentManifest) Start(service ServiceRequest) error { + return nil +} + +// Stop a container +func (c *kubernetesDeploymentManifest) Stop(service ServiceRequest) error { + return nil +} diff --git a/internal/installer/base.go b/internal/installer/base.go index 8312d205f3..92e33f47ea 100644 --- a/internal/installer/base.go +++ b/internal/installer/base.go @@ -6,21 +6,38 @@ package installer import ( "context" - "fmt" "strings" - "github.com/elastic/e2e-testing/internal/common" "github.com/elastic/e2e-testing/internal/deploy" log "github.com/sirupsen/logrus" ) -// Package represents the operations that can be performed by an installer package type -type Package interface { - Install(containerName string, token string) error - PrintLogs(containerName string) error - Postinstall() error - Preinstall() error - Uninstall() error +// Attach will attach a installer to a deployment allowing +// the installation of a package to be transparently configured no matter the backend +func Attach(deploy deploy.Deployment, service deploy.ServiceRequest, installType string) (deploy.ServiceOperator, error) { + log.WithFields(log.Fields{ + "service": service, + "installType": installType, + }).Trace("Attaching service for configuration") + + if strings.EqualFold(service.Name, "elastic-agent") { + switch installType { + case "tar": + install := AttachElasticAgentTARPackage(deploy, service) + return install, nil + case "rpm": + install := AttachElasticAgentRPMPackage(deploy, service) + return install, nil + case "deb": + install := AttachElasticAgentDEBPackage(deploy, service) + return install, nil + case "docker": + install := AttachElasticAgentDockerPackage(deploy, service) + return install, nil + } + } + + return nil, nil } // BasePackage holds references to basic state for all installers @@ -33,82 +50,6 @@ type BasePackage struct { service string } -// extractPackage depends on the underlying OS, so 'cmds' must contain the specific instructions for the OS -func (i *BasePackage) extractPackage(cmds []string) error { - sm := deploy.NewServiceManager() - imageService := deploy.NewServiceRequest(common.ElasticAgentServiceName).WithFlavour(i.image) - - err := sm.ExecCommandInService( - deploy.NewServiceRequest(i.profile), imageService, i.service, cmds, common.ProfileEnv, false) - if err != nil { - log.WithFields(log.Fields{ - "command": cmds, - "error": err, - "image": i.image, - "service": i.service, - }).Error("Could not extract agent package in the box") - - return err - } - - return nil -} - -// Postinstall executes operations after installing a DEB package -func (i *BasePackage) Postinstall() error { - profileService := deploy.NewServiceRequest(i.profile) - imageService := deploy.NewServiceRequest(common.ElasticAgentServiceName).WithFlavour(i.image) - - err := SystemctlRun(profileService, imageService, i.service, "enable") - if err != nil { - return err - } - return SystemctlRun(profileService, imageService, i.service, "start") -} - -// PrintLogs prints logs for the agent -func (i *BasePackage) PrintLogs(containerName string) error { - err := i.resolveLogFile(containerName) - if err != nil { - return fmt.Errorf("Could not resolve log file: %v", err) - } - - cmd := []string{ - "cat", i.logFile, - } - - sm := deploy.NewServiceManager() - imageService := deploy.NewServiceRequest(common.ElasticAgentServiceName).WithFlavour(i.image) - - err = sm.ExecCommandInService( - deploy.NewServiceRequest(i.profile), imageService, i.service, cmd, common.ProfileEnv, false) - if err != nil { - return err - } - - return nil -} - -// resolveLogFile retrieves the full path of the log file in the underlying Docker container -// calculating the hash commit if necessary -func (i *BasePackage) resolveLogFile(containerName string) error { - if strings.Contains(i.logFile, "%s") { - hash, err := getElasticAgentHash(containerName, i.commitFile) - if err != nil { - log.WithFields(log.Fields{ - "containerName": containerName, - "error": err, - }).Error("Could not get agent hash in the container") - - return err - } - - i.logFile = fmt.Sprintf(i.logFile, hash) - } - - return nil -} - // getElasticAgentHash uses Elastic Agent's home dir to read the file with agent's build hash // it will return the first six characters of the hash (short hash) func getElasticAgentHash(containerName string, commitFile string) (string, error) { @@ -116,7 +57,7 @@ func getElasticAgentHash(containerName string, commitFile string) (string, error "cat", commitFile, } - fullHash, err := deploy.ExecCommandIntoContainer(context.Background(), deploy.NewServiceRequest(containerName), "root", cmd) + fullHash, err := deploy.ExecCommandIntoContainer(context.Background(), containerName, "root", cmd) if err != nil { return "", err } @@ -133,25 +74,3 @@ func getElasticAgentHash(containerName string, commitFile string) (string, error return shortHash, nil } - -// SystemctlRun runs systemctl in profile or service -func SystemctlRun(profile deploy.ServiceRequest, image deploy.ServiceRequest, service string, command string) error { - cmd := []string{"systemctl", command, common.ElasticAgentProcessName} - sm := deploy.NewServiceManager() - err := sm.ExecCommandInService(profile, image, service, cmd, common.ProfileEnv, false) - if err != nil { - log.WithFields(log.Fields{ - "command": cmd, - "error": err, - "service": service, - }).Errorf("Could not %s the service", command) - - return err - } - - log.WithFields(log.Fields{ - "command": cmd, - "service": service, - }).Trace("Systemctl executed") - return nil -} diff --git a/internal/installer/deb.go b/internal/installer/deb.go deleted file mode 100644 index f6ac27ad51..0000000000 --- a/internal/installer/deb.go +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one -// or more contributor license agreements. Licensed under the Elastic License; -// you may not use this file except in compliance with the Elastic License. - -package installer - -import ( - "github.com/elastic/e2e-testing/internal/common" - "github.com/elastic/e2e-testing/internal/deploy" - "github.com/elastic/e2e-testing/internal/kibana" - "github.com/elastic/e2e-testing/internal/utils" - log "github.com/sirupsen/logrus" -) - -// DEBPackage implements operations for a DEB installer -type DEBPackage struct { - BasePackage -} - -// NewDEBPackage creates an instance for the DEB installer -func NewDEBPackage(binaryName string, profile string, image string, service string, commitFile string, logFile string) *DEBPackage { - return &DEBPackage{ - BasePackage: BasePackage{ - binaryName: binaryName, - commitFile: commitFile, - image: image, - profile: profile, - service: service, - }, - } -} - -// Install installs a DEB package -func (i *DEBPackage) Install(cfg *kibana.FleetConfig) error { - return i.extractPackage([]string{"apt", "install", "/" + i.binaryName, "-y"}) -} - -// Preinstall executes operations before installing a DEB package -func (i *DEBPackage) Preinstall() error { - log.Trace("No preinstall commands for DEB packages") - return nil -} - -// Uninstall uninstalls a DEB package -func (i *DEBPackage) Uninstall() error { - log.Trace("No uninstall commands for DEB packages") - return nil -} - -// newDebianInstaller returns an instance of the Debian installer for a specific version -func newDebianInstaller(image string, tag string, version string) (ElasticAgentInstaller, error) { - service := common.ElasticAgentServiceName - profile := common.FleetProfileName - - // extract the agent in the box, as it's mounted as a volume - artifact := "elastic-agent" - os := "linux" - arch := "amd64" - extension := "deb" - - binaryName := utils.BuildArtifactName(artifact, version, common.BeatVersionBase, os, arch, extension, false) - binaryPath, err := downloadAgentBinary(binaryName, artifact, version) - if err != nil { - log.WithFields(log.Fields{ - "artifact": artifact, - "version": version, - "os": os, - "arch": arch, - "extension": extension, - "error": err, - }).Error("Could not download the binary for the agent") - return ElasticAgentInstaller{}, err - } - - profileService := deploy.NewServiceRequest(profile) - imageService := deploy.NewServiceRequest(service).WithFlavour("debian") - - enrollFn := func(cfg *kibana.FleetConfig) error { - return runElasticAgentCommandEnv(profileService, imageService, service, common.ElasticAgentProcessName, "enroll", cfg.Flags(), map[string]string{}) - } - - workingDir := "/var/lib/elastic-agent" - binDir := workingDir + "/data/elastic-agent-%s/" - - commitFile := "/etc/elastic-agent/.elastic-agent.active.commit" - - logsDir := binDir + "logs/" - logFileName := "elastic-agent-json.log" - logFile := logsDir + "/" + logFileName - - installerPackage := NewDEBPackage(binaryName, profile, image, service, commitFile, logFile) - - return ElasticAgentInstaller{ - artifactArch: arch, - artifactExtension: extension, - artifactName: artifact, - artifactOS: os, - artifactVersion: version, - BinaryPath: binaryPath, - EnrollFn: enrollFn, - Image: image, - InstallFn: installerPackage.Install, - InstallerType: "deb", - Name: binaryName, - PostInstallFn: installerPackage.Postinstall, - PreInstallFn: installerPackage.Preinstall, - PrintLogsFn: installerPackage.PrintLogs, - processName: common.ElasticAgentProcessName, - Profile: profile, - Service: service, - Tag: tag, - UninstallFn: installerPackage.Uninstall, - workingDir: workingDir, - }, nil -} diff --git a/internal/installer/docker.go b/internal/installer/docker.go deleted file mode 100644 index 713fdfd531..0000000000 --- a/internal/installer/docker.go +++ /dev/null @@ -1,176 +0,0 @@ -// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one -// or more contributor license agreements. Licensed under the Elastic License; -// you may not use this file except in compliance with the Elastic License. - -package installer - -import ( - "github.com/elastic/e2e-testing/internal/common" - "github.com/elastic/e2e-testing/internal/deploy" - "github.com/elastic/e2e-testing/internal/kibana" - "github.com/elastic/e2e-testing/internal/utils" - log "github.com/sirupsen/logrus" -) - -// DockerPackage implements operations for a DEB installer -type DockerPackage struct { - BasePackage - installerPath string - ubi8 bool - // optional fields - arch string - artifact string - originalVersion string - OS string - version string -} - -// NewDockerPackage creates an instance for the Docker installer -func NewDockerPackage(binaryName string, profile string, image string, service string, installerPath string, ubi8 bool, commitFile string, logFile string) *DockerPackage { - return &DockerPackage{ - BasePackage: BasePackage{ - binaryName: binaryName, - commitFile: commitFile, - image: image, - logFile: logFile, - profile: profile, - service: service, - }, - installerPath: installerPath, - ubi8: ubi8, - } -} - -// Install installs a Docker package -func (i *DockerPackage) Install(cfg *kibana.FleetConfig) error { - log.Trace("No install commands for Docker packages") - return nil -} - -// Preinstall executes operations before installing a Docker package -func (i *DockerPackage) Preinstall() error { - err := deploy.LoadImage(i.installerPath) - if err != nil { - return err - } - - // we need to tag the loaded image because its tag relates to the target branch - return deploy.TagImage( - "docker.elastic.co/beats/"+i.artifact+":"+common.BeatVersionBase, - "docker.elastic.co/observability-ci/"+i.artifact+":"+i.originalVersion+"-amd64", - ) -} - -// Postinstall executes operations after installing a Docker package -func (i *DockerPackage) Postinstall() error { - log.Trace("No postinstall commands for Docker packages") - return nil -} - -// Uninstall uninstalls a Docker package -func (i *DockerPackage) Uninstall() error { - log.Trace("No uninstall commands for Docker packages") - return nil -} - -// WithArch sets the architecture -func (i *DockerPackage) WithArch(arch string) *DockerPackage { - i.arch = arch - return i -} - -// WithArtifact sets the artifact -func (i *DockerPackage) WithArtifact(artifact string) *DockerPackage { - i.artifact = artifact - return i -} - -// WithOS sets the OS -func (i *DockerPackage) WithOS(OS string) *DockerPackage { - i.OS = OS - return i -} - -// WithVersion sets the version -func (i *DockerPackage) WithVersion(version string) *DockerPackage { - i.version = utils.CheckPRVersion(version, common.BeatVersionBase) // sanitize version - i.originalVersion = version - return i -} - -// newDockerInstaller returns an instance of the Docker installer -func newDockerInstaller(ubi8 bool, version string) (ElasticAgentInstaller, error) { - image := common.ElasticAgentServiceName - service := image - profile := common.FleetProfileName - - // extract the agent in the box, as it's mounted as a volume - artifact := "elastic-agent" - - artifactName := artifact - if ubi8 { - artifactName = "elastic-agent-ubi8" - image = "elastic-agent-ubi8" - } - - os := "linux" - arch := "amd64" - extension := "tar.gz" - - binaryName := utils.BuildArtifactName(artifactName, version, common.BeatVersionBase, os, arch, extension, true) - binaryPath, err := downloadAgentBinary(binaryName, artifact, version) - if err != nil { - log.WithFields(log.Fields{ - "artifact": artifact, - "version": version, - "os": os, - "arch": arch, - "extension": extension, - "error": err, - }).Error("Could not download the binary for the agent") - return ElasticAgentInstaller{}, err - } - - homeDir := "/usr/share/elastic-agent" - workingDir := homeDir - binDir := homeDir + "/data/elastic-agent-%s/" - - commitFile := homeDir + ".elastic-agent.active.commit" - - logsDir := binDir + "logs/" - logFileName := "elastic-agent-json.log" - logFile := logsDir + "/" + logFileName - - enrollFn := func(cfg *kibana.FleetConfig) error { - return nil - } - - installerPackage := NewDockerPackage(binaryName, profile, artifactName, service, binaryPath, ubi8, commitFile, logFile). - WithArch(arch). - WithArtifact(artifactName). - WithOS(os). - WithVersion(version) - - return ElasticAgentInstaller{ - artifactArch: arch, - artifactExtension: extension, - artifactName: artifact, - artifactOS: os, - artifactVersion: version, - BinaryPath: binaryPath, - EnrollFn: enrollFn, - Image: image, - InstallFn: installerPackage.Install, - InstallerType: "docker", - Name: binaryName, - PostInstallFn: installerPackage.Postinstall, - PreInstallFn: installerPackage.Preinstall, - PrintLogsFn: installerPackage.PrintLogs, - processName: common.ElasticAgentProcessName, - Profile: profile, - Service: service, - Tag: version, - UninstallFn: installerPackage.Uninstall, - workingDir: workingDir, - }, nil -} diff --git a/internal/installer/elasticagent.go b/internal/installer/elasticagent.go index d6a1831e56..62a035b239 100644 --- a/internal/installer/elasticagent.go +++ b/internal/installer/elasticagent.go @@ -5,87 +5,10 @@ package installer import ( - "context" - "fmt" - "github.com/elastic/e2e-testing/internal/common" - "github.com/elastic/e2e-testing/internal/deploy" - "github.com/elastic/e2e-testing/internal/kibana" "github.com/elastic/e2e-testing/internal/utils" - log "github.com/sirupsen/logrus" ) -// ElasticAgentInstaller represents how to install an agent, depending of the box type -type ElasticAgentInstaller struct { - artifactArch string // architecture of the artifact - artifactExtension string // extension of the artifact - artifactName string // name of the artifact - artifactOS string // OS of the artifact - artifactVersion string // version of the artifact - BinaryPath string // the local path where the agent for the binary is located - EnrollFn func(cfg *kibana.FleetConfig) error - Image string // docker image - InstallerType string - InstallFn func(cfg *kibana.FleetConfig) error - Name string // the name for the binary - processName string // name of the elastic-agent process - Profile string // parent docker-compose file - PostInstallFn func() error - PreInstallFn func() error - PrintLogsFn func(containerName string) error - Service string // name of the service - Tag string // docker tag - UninstallFn func() error - workingDir string // location of the application -} - -// ListElasticAgentWorkingDirContent list Elastic Agent's working dir content -func (i *ElasticAgentInstaller) ListElasticAgentWorkingDirContent(containerName string) (string, error) { - cmd := []string{ - "ls", "-l", i.workingDir, - } - - content, err := deploy.ExecCommandIntoContainer(context.Background(), deploy.NewServiceRequest(containerName), "root", cmd) - if err != nil { - return "", err - } - - log.WithFields(log.Fields{ - "workingDir": i.workingDir, - "containerName": containerName, - "content": content, - }).Debug("Agent working dir content") - - return content, nil -} - -// runElasticAgentCommandEnv runs a command for the elastic-agent -func runElasticAgentCommandEnv(profile deploy.ServiceRequest, image deploy.ServiceRequest, service string, process string, command string, arguments []string, env map[string]string) error { - cmds := []string{ - "timeout", fmt.Sprintf("%dm", utils.TimeoutFactor), process, command, - } - cmds = append(cmds, arguments...) - - for k, v := range env { - common.ProfileEnv[k] = v - } - - sm := deploy.NewServiceManager() - err := sm.ExecCommandInService(profile, image, service, cmds, common.ProfileEnv, false) - if err != nil { - log.WithFields(log.Fields{ - "command": cmds, - "profile": profile, - "service": service, - "error": err, - }).Error("Could not run agent command in the box") - - return err - } - - return nil -} - // downloadAgentBinary it downloads the binary and stores the location of the downloaded file // into the installer struct, to be used else where // If the environment variable ELASTIC_AGENT_DOWNLOAD_URL exists, then the artifact to be downloaded will @@ -102,43 +25,3 @@ func downloadAgentBinary(artifactName string, artifact string, version string) ( return imagePath, nil } - -// GetElasticAgentInstaller returns an installer from a docker image -func GetElasticAgentInstaller(image string, installerType string, version string, index int) ElasticAgentInstaller { - log.WithFields(log.Fields{ - "image": image, - "installer": installerType, - "version": version, - }).Debug("Configuring installer for the agent") - - var installer ElasticAgentInstaller - var err error - if "centos" == image && "tar" == installerType { - installer, err = newTarInstaller("centos", "latest", version, index) - } else if "centos" == image && "rpm" == installerType { - installer, err = newCentosInstaller("centos", "latest", version) - } else if "debian" == image && "tar" == installerType { - installer, err = newTarInstaller("debian", "stretch", version, index) - } else if "debian" == image && "deb" == installerType { - installer, err = newDebianInstaller("debian", "stretch", version) - } else if "docker" == image && "default" == installerType { - installer, err = newDockerInstaller(false, version) - } else if "docker" == image && "ubi8" == installerType { - installer, err = newDockerInstaller(true, version) - } else { - log.WithFields(log.Fields{ - "image": image, - "installer": installerType, - }).Fatal("Sorry, we currently do not support this installer") - return ElasticAgentInstaller{} - } - - if err != nil { - log.WithFields(log.Fields{ - "error": err, - "image": image, - "installer": installerType, - }).Fatal("Sorry, we could not download the installer") - } - return installer -} diff --git a/internal/installer/elasticagent_deb.go b/internal/installer/elasticagent_deb.go new file mode 100644 index 0000000000..165daf8586 --- /dev/null +++ b/internal/installer/elasticagent_deb.go @@ -0,0 +1,162 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package installer + +import ( + "fmt" + + "github.com/elastic/e2e-testing/internal/common" + "github.com/elastic/e2e-testing/internal/deploy" + "github.com/elastic/e2e-testing/internal/kibana" + "github.com/elastic/e2e-testing/internal/utils" + log "github.com/sirupsen/logrus" +) + +// elasticAgentDEBPackage implements operations for a DEB installer +type elasticAgentDEBPackage struct { + service deploy.ServiceRequest + deploy deploy.Deployment +} + +// AttachElasticAgentDEBPackage creates an instance for the DEB installer +func AttachElasticAgentDEBPackage(deploy deploy.Deployment, service deploy.ServiceRequest) deploy.ServiceOperator { + return &elasticAgentDEBPackage{ + service: service, + deploy: deploy, + } +} + +// AddFiles will add files into the service environment, default destination is / +func (i *elasticAgentDEBPackage) AddFiles(files []string) error { + return i.deploy.AddFiles(i.service, files) +} + +// Inspect returns info on package +func (i *elasticAgentDEBPackage) Inspect() (deploy.ServiceOperatorManifest, error) { + return deploy.ServiceOperatorManifest{ + WorkDir: "/var/lib/elastic-agent", + CommitFile: "/etc/elastic-agent/.elastic-agent.active.commit", + }, nil +} + +// Install installs a DEB package +func (i *elasticAgentDEBPackage) Install() error { + log.Trace("No additional install commands for DEB") + return nil +} + +// Exec will execute a command within the service environment +func (i *elasticAgentDEBPackage) Exec(args []string) (string, error) { + output, err := i.deploy.ExecIn(i.service, args) + return output, err +} + +// Enroll will enroll the agent into fleet +func (i *elasticAgentDEBPackage) Enroll(token string) error { + + cfg, _ := kibana.NewFleetConfig(token) + args := []string{"elastic-agent", "enroll"} + for _, arg := range cfg.Flags() { + args = append(args, arg) + } + + output, err := i.Exec(args) + log.Trace(output) + if err != nil { + return fmt.Errorf("Failed to install the agent with subcommand: %v", err) + } + return nil +} + +// InstallCerts installs the certificates for a DEB package, using the right OS package manager +func (i *elasticAgentDEBPackage) InstallCerts() error { + cmds := [][]string{ + {"apt-get", "update"}, + {"apt", "install", "ca-certificates", "-y"}, + {"update-ca-certificates", "-f"}, + } + for _, cmd := range cmds { + if _, err := i.Exec(cmd); err != nil { + return err + } + } + return nil +} + +// Logs prints logs of service +func (i *elasticAgentDEBPackage) Logs() error { + return i.deploy.Logs(i.service) +} + +// Postinstall executes operations after installing a DEB package +func (i *elasticAgentDEBPackage) Postinstall() error { + _, err := i.Exec([]string{"systemctl", "restart", "elastic-agent"}) + if err != nil { + return err + } + return nil +} + +// Preinstall executes operations before installing a DEB package +func (i *elasticAgentDEBPackage) Preinstall() error { + artifact := "elastic-agent" + os := "linux" + arch := "amd64" + extension := "deb" + + binaryName := utils.BuildArtifactName(artifact, common.BeatVersion, common.BeatVersionBase, os, arch, extension, false) + binaryPath, err := utils.FetchBeatsBinary(binaryName, artifact, common.BeatVersion, common.BeatVersionBase, utils.TimeoutFactor, true) + if err != nil { + log.WithFields(log.Fields{ + "artifact": artifact, + "version": common.BeatVersion, + "os": os, + "arch": arch, + "extension": extension, + "error": err, + }).Error("Could not download the binary for the agent") + return err + } + + err = i.AddFiles([]string{binaryPath}) + if err != nil { + return err + } + + _, err = i.Exec([]string{"apt", "install", "/" + binaryName, "-y"}) + if err != nil { + return err + } + + return nil +} + +// Start will start a service +func (i *elasticAgentDEBPackage) Start() error { + _, err := i.Exec([]string{"systemctl", "start", "elastic-agent"}) + if err != nil { + return err + } + return nil +} + +// Stop will start a service +func (i *elasticAgentDEBPackage) Stop() error { + _, err := i.Exec([]string{"systemctl", "stop", "elastic-agent"}) + if err != nil { + return err + } + return nil +} + +// Uninstall uninstalls a DEB package +func (i *elasticAgentDEBPackage) Uninstall() error { + args := []string{"elastic-agent", "uninstall", "-f"} + _, err := i.Exec(args) + if err != nil { + return fmt.Errorf("Failed to uninstall the agent with subcommand: %v", err) + } + return nil +} diff --git a/internal/installer/elasticagent_docker.go b/internal/installer/elasticagent_docker.go new file mode 100644 index 0000000000..b08626b1a1 --- /dev/null +++ b/internal/installer/elasticagent_docker.go @@ -0,0 +1,133 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package installer + +import ( + "fmt" + + "github.com/elastic/e2e-testing/internal/common" + "github.com/elastic/e2e-testing/internal/deploy" + "github.com/elastic/e2e-testing/internal/utils" + log "github.com/sirupsen/logrus" +) + +// elasticAgentDockerPackage implements operations for a docker installer +type elasticAgentDockerPackage struct { + service deploy.ServiceRequest + deploy deploy.Deployment +} + +// AttachElasticAgentDockerPackage creates an instance for the docker installer +func AttachElasticAgentDockerPackage(deploy deploy.Deployment, service deploy.ServiceRequest) deploy.ServiceOperator { + return &elasticAgentDockerPackage{ + service: service, + deploy: deploy, + } +} + +// AddFiles will add files into the service environment, default destination is / +func (i *elasticAgentDockerPackage) AddFiles(files []string) error { + return i.deploy.AddFiles(i.service, files) +} + +// Inspect returns info on package +func (i *elasticAgentDockerPackage) Inspect() (deploy.ServiceOperatorManifest, error) { + return deploy.ServiceOperatorManifest{ + WorkDir: "/usr/share/elastic-agent", + CommitFile: "/usr/share/elastic-agent/.elastic-agent.active.commit", + }, nil +} + +// Install installs a package +func (i *elasticAgentDockerPackage) Install() error { + return nil +} + +// Exec will execute a command within the service environment +func (i *elasticAgentDockerPackage) Exec(args []string) (string, error) { + output, err := i.deploy.ExecIn(i.service, args) + return output, err +} + +// Enroll will enroll the agent into fleet +func (i *elasticAgentDockerPackage) Enroll(token string) error { + return nil +} + +// InstallCerts installs the certificates for a package, using the right OS package manager +func (i *elasticAgentDockerPackage) InstallCerts() error { + return nil +} + +// Logs prints logs of service +func (i *elasticAgentDockerPackage) Logs() error { + return i.deploy.Logs(i.service) +} + +// Postinstall executes operations after installing a package +func (i *elasticAgentDockerPackage) Postinstall() error { + return nil +} + +// Preinstall executes operations before installing a package +func (i *elasticAgentDockerPackage) Preinstall() error { + artifact := "elastic-agent" + os := "linux" + arch := "x86_64" + extension := "tar.gz" + + binaryName := utils.BuildArtifactName(artifact, common.BeatVersion, common.BeatVersionBase, os, arch, extension, false) + binaryPath, err := utils.FetchBeatsBinary(binaryName, artifact, common.BeatVersion, common.BeatVersionBase, utils.TimeoutFactor, true) + if err != nil { + log.WithFields(log.Fields{ + "artifact": artifact, + "version": common.BeatVersion, + "os": os, + "arch": arch, + "extension": extension, + "error": err, + }).Error("Could not download the binary for the agent") + return err + } + + err = deploy.LoadImage(binaryPath) + if err != nil { + return err + } + + // we need to tag the loaded image because its tag relates to the target branch + return deploy.TagImage( + fmt.Sprintf("docker.elastic.co/beats/%s:%s", artifact, common.BeatVersionBase), + fmt.Sprintf("docker.elastic.co/observability-ci/%s:%s-amd64", artifact, common.BeatVersion), + ) +} + +// Start will start a service +func (i *elasticAgentDockerPackage) Start() error { + _, err := i.Exec([]string{"systemctl", "start", "elastic-agent"}) + if err != nil { + return err + } + return nil +} + +// Stop will start a service +func (i *elasticAgentDockerPackage) Stop() error { + _, err := i.Exec([]string{"systemctl", "stop", "elastic-agent"}) + if err != nil { + return err + } + return nil +} + +// Uninstall uninstalls a TAR package +func (i *elasticAgentDockerPackage) Uninstall() error { + args := []string{"elastic-agent", "uninstall", "-f"} + _, err := i.Exec(args) + if err != nil { + return fmt.Errorf("Failed to uninstall the agent with subcommand: %v", err) + } + return nil +} diff --git a/internal/installer/elasticagent_rpm.go b/internal/installer/elasticagent_rpm.go new file mode 100644 index 0000000000..1e450bfbc2 --- /dev/null +++ b/internal/installer/elasticagent_rpm.go @@ -0,0 +1,163 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package installer + +import ( + "fmt" + + "github.com/elastic/e2e-testing/internal/common" + "github.com/elastic/e2e-testing/internal/deploy" + "github.com/elastic/e2e-testing/internal/kibana" + "github.com/elastic/e2e-testing/internal/utils" + log "github.com/sirupsen/logrus" +) + +// elasticAgentRPMPackage implements operations for a RPM installer +type elasticAgentRPMPackage struct { + service deploy.ServiceRequest + deploy deploy.Deployment +} + +// AttachElasticAgentRPMPackage creates an instance for the RPM installer +func AttachElasticAgentRPMPackage(deploy deploy.Deployment, service deploy.ServiceRequest) deploy.ServiceOperator { + return &elasticAgentRPMPackage{ + service: service, + deploy: deploy, + } +} + +// AddFiles will add files into the service environment, default destination is / +func (i *elasticAgentRPMPackage) AddFiles(files []string) error { + return i.deploy.AddFiles(i.service, files) +} + +// Inspect returns info on package +func (i *elasticAgentRPMPackage) Inspect() (deploy.ServiceOperatorManifest, error) { + return deploy.ServiceOperatorManifest{ + WorkDir: "/var/lib/elastic-agent", + CommitFile: "/etc/elastic-agent/.elastic-agent.active.commit", + }, nil +} + +// Install installs a RPM package +func (i *elasticAgentRPMPackage) Install() error { + log.Trace("No additional install commands for RPM") + return nil +} + +// Exec will execute a command within the service environment +func (i *elasticAgentRPMPackage) Exec(args []string) (string, error) { + output, err := i.deploy.ExecIn(i.service, args) + return output, err +} + +// Enroll will enroll the agent into fleet +func (i *elasticAgentRPMPackage) Enroll(token string) error { + + cfg, _ := kibana.NewFleetConfig(token) + args := []string{"elastic-agent", "enroll"} + for _, arg := range cfg.Flags() { + args = append(args, arg) + } + + output, err := i.Exec(args) + log.Trace(output) + if err != nil { + return fmt.Errorf("Failed to install the agent with subcommand: %v", err) + } + return nil +} + +// InstallCerts installs the certificates for a RPM package, using the right OS package manager +func (i *elasticAgentRPMPackage) InstallCerts() error { + cmds := [][]string{ + {"yum", "check-update"}, + {"yum", "install", "ca-certificates", "-y"}, + {"update-ca-trust", "force-enable"}, + {"update-ca-trust", "extract"}, + } + for _, cmd := range cmds { + if _, err := i.Exec(cmd); err != nil { + return err + } + } + return nil +} + +// Logs prints logs of service +func (i *elasticAgentRPMPackage) Logs() error { + return i.deploy.Logs(i.service) +} + +// Postinstall executes operations after installing a RPM package +func (i *elasticAgentRPMPackage) Postinstall() error { + _, err := i.Exec([]string{"systemctl", "restart", "elastic-agent"}) + if err != nil { + return err + } + return nil +} + +// Preinstall executes operations before installing a RPM package +func (i *elasticAgentRPMPackage) Preinstall() error { + artifact := "elastic-agent" + os := "linux" + arch := "x86_64" + extension := "rpm" + + binaryName := utils.BuildArtifactName(artifact, common.BeatVersion, common.BeatVersionBase, os, arch, extension, false) + binaryPath, err := utils.FetchBeatsBinary(binaryName, artifact, common.BeatVersion, common.BeatVersionBase, utils.TimeoutFactor, true) + if err != nil { + log.WithFields(log.Fields{ + "artifact": artifact, + "version": common.BeatVersion, + "os": os, + "arch": arch, + "extension": extension, + "error": err, + }).Error("Could not download the binary for the agent") + return err + } + + err = i.AddFiles([]string{binaryPath}) + if err != nil { + return err + } + + _, err = i.Exec([]string{"yum", "localinstall", "/" + binaryName, "-y"}) + if err != nil { + return err + } + + return nil +} + +// Start will start a service +func (i *elasticAgentRPMPackage) Start() error { + _, err := i.Exec([]string{"systemctl", "start", "elastic-agent"}) + if err != nil { + return err + } + return nil +} + +// Stop will start a service +func (i *elasticAgentRPMPackage) Stop() error { + _, err := i.Exec([]string{"systemctl", "stop", "elastic-agent"}) + if err != nil { + return err + } + return nil +} + +// Uninstall uninstalls a RPM package +func (i *elasticAgentRPMPackage) Uninstall() error { + args := []string{"elastic-agent", "uninstall", "-f"} + _, err := i.Exec(args) + if err != nil { + return fmt.Errorf("Failed to uninstall the agent with subcommand: %v", err) + } + return nil +} diff --git a/internal/installer/elasticagent_tar.go b/internal/installer/elasticagent_tar.go new file mode 100644 index 0000000000..87234bb255 --- /dev/null +++ b/internal/installer/elasticagent_tar.go @@ -0,0 +1,144 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package installer + +import ( + "fmt" + + "github.com/elastic/e2e-testing/internal/common" + "github.com/elastic/e2e-testing/internal/deploy" + "github.com/elastic/e2e-testing/internal/kibana" + "github.com/elastic/e2e-testing/internal/utils" + log "github.com/sirupsen/logrus" +) + +// elasticAgentTARPackage implements operations for a RPM installer +type elasticAgentTARPackage struct { + service deploy.ServiceRequest + deploy deploy.Deployment +} + +// AttachElasticAgentTARPackage creates an instance for the RPM installer +func AttachElasticAgentTARPackage(deploy deploy.Deployment, service deploy.ServiceRequest) deploy.ServiceOperator { + return &elasticAgentTARPackage{ + service: service, + deploy: deploy, + } +} + +// AddFiles will add files into the service environment, default destination is / +func (i *elasticAgentTARPackage) AddFiles(files []string) error { + return i.deploy.AddFiles(i.service, files) +} + +// Inspect returns info on package +func (i *elasticAgentTARPackage) Inspect() (deploy.ServiceOperatorManifest, error) { + return deploy.ServiceOperatorManifest{ + WorkDir: "/opt/Elastic/Agent", + CommitFile: "/elastic-agent/.elastic-agent.active.commit", + }, nil +} + +// Install installs a TAR package +func (i *elasticAgentTARPackage) Install() error { + log.Trace("No TAR install instructions") + return nil +} + +// Exec will execute a command within the service environment +func (i *elasticAgentTARPackage) Exec(args []string) (string, error) { + output, err := i.deploy.ExecIn(i.service, args) + return output, err +} + +// Enroll will enroll the agent into fleet +func (i *elasticAgentTARPackage) Enroll(token string) error { + + cfg, _ := kibana.NewFleetConfig(token) + args := []string{"/elastic-agent/elastic-agent", "install"} + for _, arg := range cfg.Flags() { + args = append(args, arg) + } + + _, err := i.Exec(args) + if err != nil { + return fmt.Errorf("Failed to install the agent with subcommand: %v", err) + } + return nil +} + +// InstallCerts installs the certificates for a TAR package, using the right OS package manager +func (i *elasticAgentTARPackage) InstallCerts() error { + return nil +} + +// Logs prints logs of service +func (i *elasticAgentTARPackage) Logs() error { + return i.deploy.Logs(i.service) +} + +// Postinstall executes operations after installing a TAR package +func (i *elasticAgentTARPackage) Postinstall() error { + return nil +} + +// Preinstall executes operations before installing a TAR package +func (i *elasticAgentTARPackage) Preinstall() error { + artifact := "elastic-agent" + os := "linux" + arch := "x86_64" + extension := "tar.gz" + + binaryName := utils.BuildArtifactName(artifact, common.BeatVersion, common.BeatVersionBase, os, arch, extension, false) + binaryPath, err := utils.FetchBeatsBinary(binaryName, artifact, common.BeatVersion, common.BeatVersionBase, utils.TimeoutFactor, true) + if err != nil { + log.WithFields(log.Fields{ + "artifact": artifact, + "version": common.BeatVersion, + "os": os, + "arch": arch, + "extension": extension, + "error": err, + }).Error("Could not download the binary for the agent") + return err + } + + err = i.AddFiles([]string{binaryPath}) + if err != nil { + return err + } + + output, _ := i.Exec([]string{"mv", fmt.Sprintf("/%s-%s-%s-%s", artifact, common.BeatVersion, os, arch), "/elastic-agent"}) + log.WithField("output", output).Trace("Moved elastic-agent") + return nil +} + +// Start will start a service +func (i *elasticAgentTARPackage) Start() error { + _, err := i.Exec([]string{"systemctl", "start", "elastic-agent"}) + if err != nil { + return err + } + return nil +} + +// Stop will start a service +func (i *elasticAgentTARPackage) Stop() error { + _, err := i.Exec([]string{"systemctl", "stop", "elastic-agent"}) + if err != nil { + return err + } + return nil +} + +// Uninstall uninstalls a TAR package +func (i *elasticAgentTARPackage) Uninstall() error { + args := []string{"elastic-agent", "uninstall", "-f"} + _, err := i.Exec(args) + if err != nil { + return fmt.Errorf("Failed to uninstall the agent with subcommand: %v", err) + } + return nil +} diff --git a/internal/installer/rpm.go b/internal/installer/rpm.go deleted file mode 100644 index 2a31a8bbdb..0000000000 --- a/internal/installer/rpm.go +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one -// or more contributor license agreements. Licensed under the Elastic License; -// you may not use this file except in compliance with the Elastic License. - -package installer - -import ( - "github.com/elastic/e2e-testing/internal/common" - "github.com/elastic/e2e-testing/internal/deploy" - "github.com/elastic/e2e-testing/internal/kibana" - "github.com/elastic/e2e-testing/internal/utils" - log "github.com/sirupsen/logrus" -) - -// RPMPackage implements operations for a RPM installer -type RPMPackage struct { - BasePackage -} - -// NewRPMPackage creates an instance for the RPM installer -func NewRPMPackage(binaryName string, profile string, image string, service string, commitFile string, logFile string) *RPMPackage { - return &RPMPackage{ - BasePackage: BasePackage{ - binaryName: binaryName, - commitFile: commitFile, - image: image, - logFile: logFile, - profile: profile, - service: service, - }, - } -} - -// Install installs a RPM package -func (i *RPMPackage) Install(cfg *kibana.FleetConfig) error { - return i.extractPackage([]string{"yum", "localinstall", "/" + i.binaryName, "-y"}) -} - -// Preinstall executes operations before installing a RPM package -func (i *RPMPackage) Preinstall() error { - log.Trace("No preinstall commands for RPM packages") - return nil -} - -// Uninstall uninstalls a RPM package -func (i *RPMPackage) Uninstall() error { - log.Trace("No uninstall commands for RPM packages") - return nil -} - -// newCentosInstaller returns an instance of the Centos installer for a specific version -func newCentosInstaller(image string, tag string, version string) (ElasticAgentInstaller, error) { - service := common.ElasticAgentServiceName - profile := common.FleetProfileName - - // extract the agent in the box, as it's mounted as a volume - artifact := "elastic-agent" - os := "linux" - arch := "x86_64" - extension := "rpm" - - binaryName := utils.BuildArtifactName(artifact, version, common.BeatVersionBase, os, arch, extension, false) - binaryPath, err := downloadAgentBinary(binaryName, artifact, version) - if err != nil { - log.WithFields(log.Fields{ - "artifact": artifact, - "version": version, - "os": os, - "arch": arch, - "extension": extension, - "error": err, - }).Error("Could not download the binary for the agent") - return ElasticAgentInstaller{}, err - } - - profileService := deploy.NewServiceRequest(profile) - imageService := deploy.NewServiceRequest(service).WithFlavour("centos") - - enrollFn := func(cfg *kibana.FleetConfig) error { - return runElasticAgentCommandEnv(profileService, imageService, service, common.ElasticAgentProcessName, "enroll", cfg.Flags(), map[string]string{}) - } - - workingDir := "/var/lib/elastic-agent" - binDir := workingDir + "/data/elastic-agent-%s/" - - commitFile := "/etc/elastic-agent/.elastic-agent.active.commit" - - logsDir := binDir + "logs/" - logFileName := "elastic-agent-json.log" - logFile := logsDir + "/" + logFileName - - installerPackage := NewRPMPackage(binaryName, profile, image, service, commitFile, logFile) - - return ElasticAgentInstaller{ - artifactArch: arch, - artifactExtension: extension, - artifactName: artifact, - artifactOS: os, - artifactVersion: version, - BinaryPath: binaryPath, - EnrollFn: enrollFn, - Image: image, - InstallFn: installerPackage.Install, - InstallerType: "rpm", - Name: binaryName, - PostInstallFn: installerPackage.Postinstall, - PreInstallFn: installerPackage.Preinstall, - PrintLogsFn: installerPackage.PrintLogs, - processName: common.ElasticAgentProcessName, - Profile: profile, - Service: service, - Tag: tag, - UninstallFn: installerPackage.Uninstall, - workingDir: workingDir, - }, nil -} diff --git a/internal/installer/tar.go b/internal/installer/tar.go deleted file mode 100644 index c2b2468487..0000000000 --- a/internal/installer/tar.go +++ /dev/null @@ -1,213 +0,0 @@ -// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one -// or more contributor license agreements. Licensed under the Elastic License; -// you may not use this file except in compliance with the Elastic License. - -package installer - -import ( - "fmt" - - "github.com/elastic/e2e-testing/internal/common" - "github.com/elastic/e2e-testing/internal/deploy" - "github.com/elastic/e2e-testing/internal/kibana" - "github.com/elastic/e2e-testing/internal/utils" - log "github.com/sirupsen/logrus" -) - -// TARPackage implements operations for a RPM installer -type TARPackage struct { - BasePackage - // optional fields - arch string - artifact string - index int - OS string - OSFlavour string // at this moment, centos or debian - version string -} - -// NewTARPackage creates an instance for the RPM installer -func NewTARPackage(binaryName string, profile string, image string, service string, commitFile string, logFile string) *TARPackage { - return &TARPackage{ - BasePackage: BasePackage{ - binaryName: binaryName, - commitFile: commitFile, - image: image, - logFile: logFile, - profile: profile, - service: service, - }, - } -} - -// Install installs a TAR package -func (i *TARPackage) Install(cfg *kibana.FleetConfig) error { - // install the elastic-agent to /usr/bin/elastic-agent using command - binary := fmt.Sprintf("/elastic-agent/%s", i.artifact) - args := cfg.Flags() - - profileService := deploy.NewServiceRequest(i.profile) - imageService := deploy.NewServiceRequest(common.ElasticAgentServiceName).WithFlavour(i.OSFlavour).WithScale(i.index) - - err := runElasticAgentCommandEnv(profileService, imageService, i.service, binary, "install", args, map[string]string{}) - if err != nil { - return fmt.Errorf("Failed to install the agent with subcommand: %v", err) - } - - return nil -} - -// Postinstall executes operations after installing a TAR package -func (i *TARPackage) Postinstall() error { - log.Trace("No postinstall commands for TAR installer") - return nil -} - -// Preinstall executes operations before installing a TAR package -func (i *TARPackage) Preinstall() error { - // simplify layout - cmds := [][]string{ - {"rm", "-fr", "/elastic-agent"}, - {"mv", fmt.Sprintf("/%s-%s-%s-%s", i.artifact, i.version, i.OS, i.arch), "/elastic-agent"}, - } - - profileService := deploy.NewServiceRequest(i.profile) - imageService := deploy.NewServiceRequest(common.ElasticAgentServiceName).WithFlavour(i.OSFlavour).WithScale(i.index) - - for _, cmd := range cmds { - sm := deploy.NewServiceManager() - err := sm.ExecCommandInService(profileService, imageService, i.service, cmd, common.ProfileEnv, false) - if err != nil { - log.WithFields(log.Fields{ - "command": cmd, - "error": err, - "image": i.image, - "service": i.service, - "version": i.version, - }).Error("Could not extract agent package in the box") - - return err - } - } - - return nil -} - -// Uninstall uninstalls a TAR package -func (i *TARPackage) Uninstall() error { - args := []string{"-f"} - - profileService := deploy.NewServiceRequest(i.profile) - imageService := deploy.NewServiceRequest(common.ElasticAgentServiceName).WithFlavour(i.OSFlavour).WithScale(i.index) - - return runElasticAgentCommandEnv(profileService, imageService, i.service, common.ElasticAgentProcessName, "uninstall", args, map[string]string{}) -} - -// WithArch sets the architecture -func (i *TARPackage) WithArch(arch string) *TARPackage { - i.arch = arch - return i -} - -// WithArtifact sets the artifact -func (i *TARPackage) WithArtifact(artifact string) *TARPackage { - i.artifact = artifact - return i -} - -// WithIndex sets the index of the agent -func (i *TARPackage) WithIndex(index int) *TARPackage { - i.index = index - return i -} - -// WithOS sets the OS -func (i *TARPackage) WithOS(OS string) *TARPackage { - i.OS = OS - return i -} - -// WithOSFlavour sets the OS flavour, at this moment centos or debian -func (i *TARPackage) WithOSFlavour(OSFlavour string) *TARPackage { - i.OSFlavour = OSFlavour - return i -} - -// WithVersion sets the version -func (i *TARPackage) WithVersion(version string) *TARPackage { - i.version = version - return i -} - -// newTarInstaller returns an instance of the Debian installer for a specific version -func newTarInstaller(image string, tag string, version string, index int) (ElasticAgentInstaller, error) { - service := common.ElasticAgentServiceName - profile := common.FleetProfileName - - // extract the agent in the box, as it's mounted as a volume - artifact := "elastic-agent" - os := "linux" - arch := "x86_64" - extension := "tar.gz" - - binaryName := utils.BuildArtifactName(artifact, version, common.BeatVersionBase, os, arch, extension, false) - binaryPath, err := downloadAgentBinary(binaryName, artifact, version) - if err != nil { - log.WithFields(log.Fields{ - "artifact": artifact, - "version": version, - "os": os, - "arch": arch, - "extension": extension, - "error": err, - }).Error("Could not download the binary for the agent") - return ElasticAgentInstaller{}, err - } - - workingDir := "/opt/Elastic/Agent" - - commitFile := "/elastic-agent/.elastic-agent.active.commit" - - logsDir := workingDir + "/data/elastic-agent-%s/logs/" - logFileName := "elastic-agent-json.log" - logFile := logsDir + "/" + logFileName - - profileService := deploy.NewServiceRequest(profile) - imageService := deploy.NewServiceRequest(service).WithFlavour(image) - - enrollFn := func(cfg *kibana.FleetConfig) error { - return runElasticAgentCommandEnv(profileService, imageService, service, common.ElasticAgentProcessName, "enroll", cfg.Flags(), map[string]string{}) - } - - // - installerPackage := NewTARPackage(binaryName, profile, image, service, commitFile, logFile). - WithArch(arch). - WithArtifact(artifact). - WithIndex(index). - WithOS(os). - WithOSFlavour(image). - WithVersion(utils.CheckPRVersion(version, common.BeatVersionBase)) // sanitize version - - return ElasticAgentInstaller{ - artifactArch: arch, - artifactExtension: extension, - artifactName: artifact, - artifactOS: os, - artifactVersion: version, - BinaryPath: binaryPath, - EnrollFn: enrollFn, - Image: image, - InstallFn: installerPackage.Install, - InstallerType: "tar", - Name: binaryName, - PostInstallFn: installerPackage.Postinstall, - PreInstallFn: installerPackage.Preinstall, - PrintLogsFn: installerPackage.PrintLogs, - processName: common.ElasticAgentProcessName, - Profile: profile, - Service: service, - Tag: tag, - UninstallFn: installerPackage.Uninstall, - workingDir: workingDir, - }, nil -} diff --git a/internal/kibana/fleet.go b/internal/kibana/fleet.go index 64ec8dbbea..2e5299da25 100644 --- a/internal/kibana/fleet.go +++ b/internal/kibana/fleet.go @@ -46,17 +46,6 @@ func NewFleetConfig(token string) (*FleetConfig, error) { // Flags bootstrap flags for fleet server func (cfg FleetConfig) Flags() []string { - /* - // agent using an already bootstrapped fleet-server - fleetServerHost := "https://hostname_of_the_bootstrapped_fleet_server:8220" - return []string{ - "-e", "-v", "--force", "--insecure", - // ensure the enrollment belongs to the default policy - "--enrollment-token=" + cfg.EnrollmentToken, - "--url", fleetServerHost, - } - */ - flags := []string{ "-e", "-v", "--force", "--insecure", "--enrollment-token=" + cfg.EnrollmentToken, "--url", fmt.Sprintf("http://%s:%d", cfg.FleetServerURI, cfg.FleetServerPort),