1818package systemtest
1919
2020import (
21+ "archive/tar"
22+ "bytes"
2123 "context"
2224 "encoding/json"
2325 "errors"
@@ -29,8 +31,8 @@ import (
2931 "net/url"
3032 "os"
3133 "os/exec"
32- "path"
3334 "path/filepath"
35+ "runtime"
3436 "strings"
3537 "time"
3638
@@ -42,6 +44,7 @@ import (
4244 "github.com/testcontainers/testcontainers-go/wait"
4345 "golang.org/x/sync/errgroup"
4446
47+ "github.com/elastic/apm-server/systemtest/apmservertest"
4548 "github.com/elastic/apm-server/systemtest/estest"
4649 "github.com/elastic/go-elasticsearch/v7"
4750)
@@ -281,6 +284,11 @@ func NewUnstartedElasticAgentContainer() (*ElasticAgentContainer, error) {
281284 Scheme : "https" ,
282285 Host : net .JoinHostPort (fleetServerIPAddress , fleetServerPort ),
283286 }
287+ containerCACertPath := "/etc/pki/tls/certs/fleet-ca.pem"
288+ hostCACertPath , err := filepath .Abs ("../testing/docker/fleet-server/ca.pem" )
289+ if err != nil {
290+ return nil , err
291+ }
284292
285293 // Use the same stack version as used for fleet-server.
286294 agentImageVersion := fleetServerContainer .Image [strings .LastIndex (fleetServerContainer .Image , ":" )+ 1 :]
@@ -292,43 +300,34 @@ func NewUnstartedElasticAgentContainer() (*ElasticAgentContainer, error) {
292300 if err != nil {
293301 return nil , err
294302 }
295- agentVCSRef := agentImageDetails .Config .Labels ["org.label-schema.vcs-ref" ]
296- agentDataHashDir := path .Join ("/usr/share/elastic-agent/data" , "elastic-agent-" + agentVCSRef [:6 ])
297- agentInstallDir := path .Join (agentDataHashDir , "install" )
303+ stackVersion := agentImageDetails .Config .Labels ["org.label-schema.version" ]
304+
305+ // Build a custom elastic-agent image with a locally built apm-server binary injected.
306+ agentImage , err = buildElasticAgentImage (context .Background (), docker , stackVersion , agentImageVersion )
307+ if err != nil {
308+ return nil , err
309+ }
298310
299311 req := testcontainers.ContainerRequest {
300312 Image : agentImage ,
301313 AutoRemove : true ,
302314 Networks : networks ,
315+ BindMounts : map [string ]string {hostCACertPath : containerCACertPath },
303316 Env : map [string ]string {
304- // NOTE(axw) because we bind-mount the apm-server artifacts in, they end up owned by the
305- // current user rather than root. Disable Beats's strict permission checks to avoid resulting
306- // complaints, as they're irrelevant to these system tests.
307- "BEAT_STRICT_PERMS" : "false" ,
317+ "FLEET_URL" : fleetServerURL .String (),
318+ "FLEET_CA" : containerCACertPath ,
308319 },
309320 }
310321 return & ElasticAgentContainer {
311- request : req ,
312- installDir : agentInstallDir ,
313- fleetServerURL : fleetServerURL .String (),
314- StackVersion : agentImageVersion ,
315- BindMountInstall : make (map [string ]string ),
322+ request : req ,
323+ StackVersion : agentImageVersion ,
316324 }, nil
317325}
318326
319327// ElasticAgentContainer represents an ephemeral Elastic Agent container.
320328type ElasticAgentContainer struct {
321- container testcontainers.Container
322- request testcontainers.ContainerRequest
323- fleetServerURL string
324-
325- // installDir holds the location of the "install" directory inside
326- // the Elastic Agent container.
327- //
328- // This will be set when the ElasticAgentContainer object is created,
329- // and can be used to anticipate the location into which artifacts
330- // can be bind-mounted.
331- installDir string
329+ container testcontainers.Container
330+ request testcontainers.ContainerRequest
332331
333332 // StackVersion holds the stack version of the container image,
334333 // e.g. 8.0.0-SNAPSHOT.
@@ -344,11 +343,6 @@ type ElasticAgentContainer struct {
344343 // by exposed port. This will be populated by Start.
345344 Addrs map [string ]string
346345
347- // BindMountInstall holds a map of files to bind mount into the
348- // container, mapping from the host location to target paths relative
349- // to the install directory in the container.
350- BindMountInstall map [string ]string
351-
352346 // FleetEnrollmentToken holds an optional Fleet enrollment token to
353347 // use for enrolling the agent with Fleet. The agent will only enroll
354348 // if this is specified.
@@ -366,27 +360,12 @@ func (c *ElasticAgentContainer) Start() error {
366360 defer cancel ()
367361
368362 // Update request from user-definable fields.
369- c .request .Env ["FLEET_URL" ] = c .fleetServerURL
370363 if c .FleetEnrollmentToken != "" {
371364 c .request .Env ["FLEET_ENROLL" ] = "1"
372365 c .request .Env ["FLEET_ENROLLMENT_TOKEN" ] = c .FleetEnrollmentToken
373366 }
374-
375367 c .request .ExposedPorts = c .ExposedPorts
376368 c .request .WaitingFor = c .WaitingFor
377- c .request .BindMounts = map [string ]string {}
378- for source , target := range c .BindMountInstall {
379- c .request .BindMounts [source ] = path .Join (c .installDir , target )
380- }
381-
382- // Inject CA certificate for verifying fleet-server.
383- containerCACertPath := "/etc/pki/tls/certs/fleet-ca.pem"
384- hostCACertPath , err := filepath .Abs ("../testing/docker/fleet-server/ca.pem" )
385- if err != nil {
386- return err
387- }
388- c .request .BindMounts [hostCACertPath ] = containerCACertPath
389- c .request .Env ["FLEET_CA" ] = containerCACertPath
390369
391370 container , err := testcontainers .GenericContainer (ctx , testcontainers.GenericContainerRequest {
392371 ContainerRequest : c .request ,
@@ -457,3 +436,91 @@ func matchFleetServerAPIStatusHealthy(r io.Reader) bool {
457436 }
458437 return status .Status == "HEALTHY"
459438}
439+
440+ // buildElasticAgentImage builds a Docker image from the published image with a locally built apm-server injected.
441+ func buildElasticAgentImage (ctx context.Context , docker * client.Client , stackVersion , imageVersion string ) (string , error ) {
442+ imageName := fmt .Sprintf ("elastic-agent-systemtest:%s" , imageVersion )
443+ log .Printf ("Building image %s..." , imageName )
444+
445+ // Build apm-server, and copy it into the elastic-agent container's "install" directory.
446+ // This bypasses downloading the artifact.
447+ arch := runtime .GOARCH
448+ if arch == "amd64" {
449+ arch = "x86_64"
450+ }
451+ apmServerInstallDir := fmt .Sprintf ("./state/data/install/apm-server-%s-linux-%s" , stackVersion , arch )
452+ apmServerBinary , err := apmservertest .BuildServerBinary ("linux" )
453+ if err != nil {
454+ return "" , err
455+ }
456+
457+ // Binaries to copy from disk into the build context.
458+ binaries := map [string ]string {
459+ "apm-server" : apmServerBinary ,
460+ }
461+
462+ // Generate Dockerfile contents.
463+ var dockerfile bytes.Buffer
464+ fmt .Fprintf (& dockerfile , "FROM docker.elastic.co/beats/elastic-agent:%s\n " , imageVersion )
465+ fmt .Fprintf (& dockerfile , "COPY --chown=elastic-agent:elastic-agent apm-server apm-server.yml %s/\n " , apmServerInstallDir )
466+
467+ // Files to generate in the build context.
468+ generatedFiles := map [string ][]byte {
469+ "Dockerfile" : dockerfile .Bytes (),
470+ "apm-server.yml" : []byte ("" ),
471+ }
472+
473+ var buildContext bytes.Buffer
474+ tarw := tar .NewWriter (& buildContext )
475+ for name , path := range binaries {
476+ f , err := os .Open (path )
477+ if err != nil {
478+ return "" , err
479+ }
480+ defer f .Close ()
481+ info , err := f .Stat ()
482+ if err != nil {
483+ return "" , err
484+ }
485+ if err := tarw .WriteHeader (& tar.Header {
486+ Name : name ,
487+ Size : info .Size (),
488+ Mode : 0755 ,
489+ Uname : "elastic-agent" ,
490+ Gname : "elastic-agent" ,
491+ }); err != nil {
492+ return "" , err
493+ }
494+ if _ , err := io .Copy (tarw , f ); err != nil {
495+ return "" , err
496+ }
497+ }
498+ for name , content := range generatedFiles {
499+ if err := tarw .WriteHeader (& tar.Header {
500+ Name : name ,
501+ Size : int64 (len (content )),
502+ Mode : 0644 ,
503+ Uname : "elastic-agent" ,
504+ Gname : "elastic-agent" ,
505+ }); err != nil {
506+ return "" , err
507+ }
508+ if _ , err := tarw .Write (content ); err != nil {
509+ return "" , err
510+ }
511+ }
512+ if err := tarw .Close (); err != nil {
513+ return "" , err
514+ }
515+
516+ resp , err := docker .ImageBuild (ctx , & buildContext , types.ImageBuildOptions {Tags : []string {imageName }})
517+ if err != nil {
518+ return "" , err
519+ }
520+ defer resp .Body .Close ()
521+ if _ , err := io .Copy (ioutil .Discard , resp .Body ); err != nil {
522+ return "" , err
523+ }
524+ log .Printf ("Built image %s" , imageName )
525+ return imageName , nil
526+ }
0 commit comments