From d8f0c975a02c2165c3719eba5d8c4c495db49107 Mon Sep 17 00:00:00 2001 From: Andrew Kroh Date: Thu, 29 Aug 2024 13:27:11 -0400 Subject: [PATCH 1/3] stack status cmd - add columns for image build date and VCS reference MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When dealing with snapshot builds that are constantly updated, it's difficult to know what build of a service is really running. When viewing historic build logs from CI this information is important to know. This adds information about the service to the output of `elastic-package stack status`. The data is read from http://label-schema.org/rc1/ labels. It uses the org.label-schema.build-date and org.label-schema.vcs-ref labels. ``` BEFORE: ╭──────────────────┬─────────────────┬─────────────────────╮ │ SERVICE │ VERSION │ STATUS │ ├──────────────────┼─────────────────┼─────────────────────┤ │ elastic-agent │ 8.16.0-SNAPSHOT │ running (unhealthy) │ │ elasticsearch │ 8.16.0-SNAPSHOT │ running (healthy) │ │ fleet-server │ 8.16.0-SNAPSHOT │ running (healthy) │ │ kibana │ 8.16.0-SNAPSHOT │ running (healthy) │ │ package-registry │ latest │ running (healthy) │ ╰──────────────────┴─────────────────┴─────────────────────╯ ``` ``` AFTER: ╭──────────────────┬─────────────────┬─────────────────────┬───────────────────┬────────────╮ │ SERVICE │ VERSION │ STATUS │ IMAGE BUILD DATE │ VCS REF │ ├──────────────────┼─────────────────┼─────────────────────┼───────────────────┼────────────┤ │ elastic-agent │ 8.16.0-SNAPSHOT │ running (unhealthy) │ 2024-08-22T02:44Z │ b96a4ca8fa │ │ elasticsearch │ 8.16.0-SNAPSHOT │ running (healthy) │ 2024-08-22T13:26Z │ 1362d56865 │ │ fleet-server │ 8.16.0-SNAPSHOT │ running (healthy) │ 2024-08-22T02:44Z │ b96a4ca8fa │ │ kibana │ 8.16.0-SNAPSHOT │ running (healthy) │ 2024-08-22T11:09Z │ cdcdfddd3f │ │ package-registry │ latest │ running (healthy) │ │ │ ╰──────────────────┴─────────────────┴─────────────────────┴───────────────────┴────────────╯ ``` --- cmd/stack.go | 22 ++++++++++++++++++++-- internal/docker/docker.go | 4 ++++ internal/stack/compose.go | 2 ++ internal/stack/compose_test.go | 3 +++ 4 files changed, 29 insertions(+), 2 deletions(-) diff --git a/cmd/stack.go b/cmd/stack.go index 63899ba2eb..91a6502a1d 100644 --- a/cmd/stack.go +++ b/cmd/stack.go @@ -7,6 +7,7 @@ package cmd import ( "fmt" "strings" + "time" "github.com/jedib0t/go-pretty/table" @@ -350,11 +351,28 @@ func printStatus(cmd *cobra.Command, servicesStatus []stack.ServiceStatus) { return } t := table.NewWriter() - t.AppendHeader(table.Row{"Service", "Version", "Status"}) + t.AppendHeader(table.Row{"Service", "Version", "Status", "Image Build Date", "VCS Ref"}) for _, service := range servicesStatus { - t.AppendRow(table.Row{service.Name, service.Version, service.Status}) + t.AppendRow(table.Row{service.Name, service.Version, service.Status, formatTime(service.Labels.BuildDate), truncate(service.Labels.VCSRef, 10)}) } t.SetStyle(table.StyleRounded) cmd.Println(t.Render()) } + +// formatTime returns the given RFC3339 time formated as 2006-01-02T15:04Z. +// If the value is not in RFC3339 format, then it is returned as-is. +func formatTime(maybeRFC3339Time string) string { + if t, err := time.Parse(time.RFC3339, maybeRFC3339Time); err == nil { + return t.UTC().Format("2006-01-02T15:04Z") + } + return maybeRFC3339Time +} + +// truncate truncates text if it is longer than maxLength. +func truncate(text string, maxLength int) string { + if len(text) > maxLength { + return text[:maxLength] + } + return text +} diff --git a/internal/docker/docker.go b/internal/docker/docker.go index fedfe3a151..4f4f9d5537 100644 --- a/internal/docker/docker.go +++ b/internal/docker/docker.go @@ -49,6 +49,10 @@ type ConfigLabels struct { ComposeProject string `json:"com.docker.compose.project"` ComposeService string `json:"com.docker.compose.service"` ComposeVersion string `json:"com.docker.compose.version"` + + // http://label-schema.org/rc1/ Labels + BuildDate string `json:"org.label-schema.build-date,omitempty"` // This label contains the Date/Time the image was built. The value SHOULD be formatted according to RFC 3339. + VCSRef string `json:"org.label-schema.vcs-ref,omitempty"` // Identifier for the version of the source code from which this image was built. For example if the version control system is git this is the SHA. } // String function dumps string representation of the container description. diff --git a/internal/stack/compose.go b/internal/stack/compose.go index 483e638e80..a7270183b1 100644 --- a/internal/stack/compose.go +++ b/internal/stack/compose.go @@ -18,6 +18,7 @@ type ServiceStatus struct { Name string Status string Version string + Labels *docker.ConfigLabels // Container labels. } const readyServicesSuffix = "is_ready" @@ -214,6 +215,7 @@ func newServiceStatus(description *docker.ContainerDescription) (*ServiceStatus, Name: description.Config.Labels.ComposeService, Status: description.State.Status, Version: getVersionFromDockerImage(description.Config.Image), + Labels: &description.Config.Labels, } if description.State.Status == "running" { healthStatus := "unknown health" diff --git a/internal/stack/compose_test.go b/internal/stack/compose_test.go index 96be46942c..496ce48ecd 100644 --- a/internal/stack/compose_test.go +++ b/internal/stack/compose_test.go @@ -78,6 +78,7 @@ func TestNewServiceStatus(t *testing.T) { Name: "myservice", Status: "running (healthy)", Version: "1.42.0", + Labels: &docker.ConfigLabels{ComposeService: "myservice"}, }, }, { @@ -112,6 +113,7 @@ func TestNewServiceStatus(t *testing.T) { Name: "myservice", Status: "exited (128)", Version: "1.42.0", + Labels: &docker.ConfigLabels{ComposeService: "myservice"}, }, }, { @@ -155,6 +157,7 @@ func TestNewServiceStatus(t *testing.T) { Name: "myservice", Status: "running (starting)", Version: "1.42.0", + Labels: &docker.ConfigLabels{ComposeService: "myservice"}, }, }, } From ff8b012604b6f18036d65e1b7b970eb9e017ed77 Mon Sep 17 00:00:00 2001 From: Andrew Kroh Date: Thu, 29 Aug 2024 15:04:06 -0400 Subject: [PATCH 2/3] make test-stack-command pass --- scripts/test-stack-command.sh | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/scripts/test-stack-command.sh b/scripts/test-stack-command.sh index b057ae16bb..5e8b632837 100755 --- a/scripts/test-stack-command.sh +++ b/scripts/test-stack-command.sh @@ -32,7 +32,12 @@ default_version() { clean_status_output() { local output_file="$1" - cat "${output_file}" | grep "│" | tr -d ' ' + # This removes the 'IMAGE BUILD DATE" and 'VCS REF' columns and + # removes the whitespace between columns. + grep "│" "${output_file}" \ + | sed 's/│/|/g' \ + | cut -d '|' -f 1-4,7- \ + | tr -d ' ' } trap cleanup EXIT @@ -90,20 +95,20 @@ curl --cacert "${ELASTIC_PACKAGE_CA_CERT}" -f "${ELASTIC_PACKAGE_KIBANA_HOST}/lo # Check status with running services cat < "${OUTPUT_PATH_STATUS}/expected_running.txt" Status of Elastic stack services: -╭──────────────────┬─────────┬───────────────────╮ -│ SERVICE │ VERSION │ STATUS │ -├──────────────────┼─────────┼───────────────────┤ -│ elastic-agent │ ${EXPECTED_VERSION} │ running (healthy) │ -│ elasticsearch │ ${EXPECTED_VERSION} │ running (healthy) │ -│ fleet-server │ ${EXPECTED_VERSION} │ running (healthy) │ -│ kibana │ ${EXPECTED_VERSION} │ running (healthy) │ -│ package-registry │ latest │ running (healthy) │ -╰──────────────────┴─────────┴───────────────────╯ +╭──────────────────┬─────────────────────┬───────────────────┬───────────────────┬────────────╮ +│ SERVICE │ VERSION │ STATUS │ IMAGE BUILD DATE │ VCS REF │ +├──────────────────┼─────────────────────┼───────────────────┼───────────────────┼────────────┤ +│ elastic-agent │ ${EXPECTED_VERSION} │ running (healthy) │ 2024-08-22T02:44Z │ b96a4ca8fa │ +│ elasticsearch │ ${EXPECTED_VERSION} │ running (healthy) │ 2024-08-22T13:26Z │ 1362d56865 │ +│ fleet-server │ ${EXPECTED_VERSION} │ running (healthy) │ 2024-08-22T02:44Z │ b96a4ca8fa │ +│ kibana │ ${EXPECTED_VERSION} │ running (healthy) │ 2024-08-22T11:09Z │ cdcdfddd3f │ +│ package-registry │ latest │ running (healthy) │ │ │ +╰──────────────────┴─────────────────────┴───────────────────┴───────────────────┴────────────╯ EOF elastic-package stack status -v 2> "${OUTPUT_PATH_STATUS}/running.txt" -# Remove spaces to avoid issues with spaces between columns +# Remove dates, commit IDs, and spaces to avoid issues. clean_status_output "${OUTPUT_PATH_STATUS}/expected_running.txt" > "${OUTPUT_PATH_STATUS}/expected_no_spaces.txt" clean_status_output "${OUTPUT_PATH_STATUS}/running.txt" > "${OUTPUT_PATH_STATUS}/running_no_spaces.txt" From d3eaa744ceb44eefed107513c3a77e38d55e96fe Mon Sep 17 00:00:00 2001 From: Andrew Kroh Date: Wed, 12 Nov 2025 08:31:04 -0500 Subject: [PATCH 3/3] Fix nil pointer dereference for serverless environments When creating a serverless project, there are no local containers for elasticsearch, kibana and fleet. For that scenario, the service.Labels field is nil. This commit adds a nil check before accessing service.Labels.BuildDate and service.Labels.VCSRef to prevent a panic in serverless environments. When Labels is nil, empty strings are used for both the build date and VCS ref columns. Fixes the panic: ``` panic: runtime error: invalid memory address or nil pointer dereference [signal SIGSEGV: segmentation violation code=0x1 addr=0x30 pc=0x3516bd3] goroutine 1 [running]: github.com/elastic/elastic-package/cmd.printStatus(0xc000546c08, {0xc0008fc460, 0x4, 0x0?}) /home/mariorodriguez/Coding/work/elastic-package-stack-status-vcs-ref/cmd/stack.go:357 +0x233 ``` --- cmd/stack.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/cmd/stack.go b/cmd/stack.go index e64cf3f695..db67328579 100644 --- a/cmd/stack.go +++ b/cmd/stack.go @@ -370,7 +370,12 @@ func printStatus(cmd *cobra.Command, servicesStatus []stack.ServiceStatus) { ) table.Header("Service", "Version", "Status", "Image Build Date", "VCS Ref") for _, service := range servicesStatus { - table.Append(service.Name, service.Version, service.Status, formatTime(service.Labels.BuildDate), truncate(service.Labels.VCSRef, 10)) + var buildDate, vcsRef string + if service.Labels != nil { + buildDate = formatTime(service.Labels.BuildDate) + vcsRef = truncate(service.Labels.VCSRef, 10) + } + table.Append(service.Name, service.Version, service.Status, buildDate, vcsRef) } table.Render() }