Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 71 additions & 7 deletions pkg/host/info.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ import (
"bufio"
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"os"
"regexp"
"strings"
Expand All @@ -31,9 +33,13 @@ const (
mountInfoLocation = "/proc/self/mountinfo"
osReleaseLocation = "/etc/os-release"

ecsMetadataEnvV4 = "ECS_CONTAINER_METADATA_URI_V4"

k8sKind = "kubepods"
docker = "docker"
containerd = "containerd"
ecsPrefix = "ecs" // AWS ECS Fargate
fargate = "fargate" // AWS EKS Fargate

numberOfKeysAndValues = 2
lengthOfContainerID = 64
Expand Down Expand Up @@ -112,7 +118,20 @@ func (i *Info) IsContainer() (bool, error) {
}
}

return containsContainerReference(i.selfCgroupLocation)
ref, err := containsContainerReference(i.selfCgroupLocation)
if ref {
return true, nil
}

if os.Getenv(ecsMetadataEnvV4) != "" {
return true, nil
}

if err != nil {
return false, err
}

return false, nil
}

// ResourceID returns a unique identifier for the resource.
Expand All @@ -121,7 +140,7 @@ func (i *Info) IsContainer() (bool, error) {
func (i *Info) ResourceID(ctx context.Context) (string, error) {
isContainer, _ := i.IsContainer()
if isContainer {
return i.containerID()
return i.containerID(ctx)
}

return i.hostID(ctx)
Expand All @@ -134,7 +153,7 @@ func (i *Info) ContainerInfo(ctx context.Context) (*v1.Resource_ContainerInfo, e
if err != nil {
return nil, err
}
containerId, err := i.containerID()
containerId, err := i.containerID(ctx)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -203,9 +222,26 @@ func (i *Info) releaseInfo(ctx context.Context, osReleaseLocation string) (*v1.R
}

// containerID returns the container ID of the current running environment.
func (i *Info) containerID() (string, error) {
containerID, err := containerIDFromMountInfo(i.mountInfoLocation)
return uuid.NewMD5(uuid.NameSpaceDNS, []byte(containerID)).String(), err
func (i *Info) containerID(ctx context.Context) (string, error) {
var errs error

// Try to get container ID from mount info first
if containerIDMount, err := containerIDFromMountInfo(i.mountInfoLocation); err == nil && containerIDMount != "" {
return uuid.NewMD5(uuid.NameSpaceDNS, []byte(containerIDMount)).String(), nil
} else if err != nil {
errs = errors.Join(errs, err)
}

// Try to get container ID from ECS metadata if available
if metadataURI := os.Getenv(ecsMetadataEnvV4); metadataURI != "" {
if cid, err := i.containerIDFromECS(ctx, metadataURI); err == nil && cid != "" {
return uuid.NewMD5(uuid.NameSpaceDNS, []byte(cid)).String(), nil
} else if err != nil {
errs = errors.Join(errs, err)
}
}

return "", errs
}

// containsContainerReference checks if the cgroup file contains references to container runtimes.
Expand All @@ -218,7 +254,8 @@ func containsContainerReference(cgroupFile string) (bool, error) {
scanner := bufio.NewScanner(bytes.NewReader(data))
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if strings.Contains(line, k8sKind) || strings.Contains(line, docker) || strings.Contains(line, containerd) {
if strings.Contains(line, k8sKind) || strings.Contains(line, docker) || strings.Contains(line, containerd) ||
strings.Contains(line, ecsPrefix) || strings.Contains(line, fargate) {
return true, nil
}
}
Expand Down Expand Up @@ -367,3 +404,30 @@ func mergeHostAndOsReleaseInfo(
Id: osReleaseInfo[id],
}
}

func (i *Info) containerIDFromECS(ctx context.Context, uri string) (string, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri, nil)
if err != nil {
return "", err
}

resp, err := http.DefaultClient.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("metadata endpoint %s returned status %d", uri, resp.StatusCode)
}

var metadata struct {
DockerId string `json:"DockerId"`
}

if err = json.NewDecoder(resp.Body).Decode(&metadata); err != nil {
return "", err
}

return metadata.DockerId, nil
}
55 changes: 55 additions & 0 deletions pkg/host/info_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ package host

import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"os"
"strings"
"testing"
Expand Down Expand Up @@ -633,3 +636,55 @@ func TestInfo_ParseOsReleaseFile(t *testing.T) {
})
}
}

func TestInfo_containerIDFromECS(t *testing.T) {
ctx := context.Background()

tests := []struct {
name string
handler http.HandlerFunc
wantID string
expectErr bool
}{
{
name: "Test 1: success - valid JSON",
handler: func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
fmt.Fprintln(w, `{"DockerId":"c1234567890"}`)
},
wantID: "c1234567890",
expectErr: false,
},
{
name: "Test 2: non-200 response",
handler: func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusInternalServerError)
},
expectErr: true,
},
{
name: "Test 3: invalid JSON",
handler: func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
fmt.Fprintln(w, `not-a-json`)
},
expectErr: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
srv := httptest.NewServer(tt.handler)
defer srv.Close()

info := NewInfo()
id, err := info.containerIDFromECS(ctx, srv.URL)
if tt.expectErr {
require.Error(t, err)
} else {
require.NoError(t, err)
assert.Equal(t, tt.wantID, id)
}
})
}
}
Loading