Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
ee8f8d2
Set initial stack tatus command
mrodm Jul 29, 2022
859da25
Removed health and exit code columns
mrodm Jul 29, 2022
8832b95
Fix lint errors
mrodm Jul 29, 2022
725c734
Add license header
mrodm Jul 29, 2022
be66f84
Manage use case with stack down - no containers
mrodm Aug 9, 2022
33184f5
Use labels instead of regex to retrieve the service name
mrodm Aug 9, 2022
6f03031
Add tests for getting version from docker image
mrodm Aug 9, 2022
234a44e
fix test
mrodm Aug 9, 2022
da64d9e
fix test
mrodm Aug 9, 2022
91e61d1
Add tests for newServiceStatus
mrodm Aug 9, 2022
e6da583
review error messages
mrodm Aug 10, 2022
1cb6419
Add exit code
mrodm Aug 10, 2022
feabe05
update test
mrodm Aug 10, 2022
16203f2
List all services filtering out is_ready ones
mrodm Aug 10, 2022
74ecca5
Move filtering to stack package
mrodm Aug 10, 2022
a62a691
Removed unused version option
mrodm Aug 11, 2022
b50c9f1
Add tests for stack status command
mrodm Aug 11, 2022
d4e2e72
Add verbose
mrodm Aug 11, 2022
3145344
Add stack version parameter to get STACK_VERSION_VARIANT
mrodm Aug 11, 2022
ba14df8
Add subfolder per version to keep all files
mrodm Aug 11, 2022
48d5a25
Update artifact rules
mrodm Aug 11, 2022
9455dce
Remove unused field
mrodm Aug 11, 2022
d6e7281
review logger messages
mrodm Aug 11, 2022
b01c18e
Remove ID field - not used
mrodm Aug 11, 2022
5ade04f
Use docker ps to get containers
mrodm Aug 11, 2022
47809d6
Sort services status array
mrodm Aug 11, 2022
46a380d
Removed --version from test stack script
mrodm Aug 11, 2022
0f77b1d
Set key and value as parameters in ContainerIDsWithLabel
mrodm Aug 12, 2022
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
8 changes: 4 additions & 4 deletions .ci/Jenkinsfile
Original file line number Diff line number Diff line change
Expand Up @@ -97,10 +97,10 @@ pipeline {
dir("${BASE_DIR}") {
script {
def basicTasks = [
'stack-command-default': generateTestCommandStage(command: 'test-stack-command-default', artifacts: ['build/elastic-stack-dump/stack/*/logs/*.log', 'build/elastic-stack-dump/stack/*/logs/fleet-server-internal/*']),
'stack-command-oldest': generateTestCommandStage(command: 'test-stack-command-oldest', artifacts: ['build/elastic-stack-dump/stack/*/logs/*.log', 'build/elastic-stack-dump/stack/*/logs/fleet-server-internal/*']),
'stack-command-7x': generateTestCommandStage(command: 'test-stack-command-7x', artifacts: ['build/elastic-stack-dump/stack/*/logs/*.log', 'build/elastic-stack-dump/stack/*/logs/fleet-server-internal/*']),
'stack-command-8x': generateTestCommandStage(command: 'test-stack-command-8x', artifacts: ['build/elastic-stack-dump/stack/*/logs/*.log', 'build/elastic-stack-dump/stack/*/logs/fleet-server-internal/*']),
'stack-command-default': generateTestCommandStage(command: 'test-stack-command-default', artifacts: ['build/elastic-stack-dump/stack/*/logs/*.log', 'build/elastic-stack-dump/stack/*/logs/fleet-server-internal/*', 'build/elastic-stack-status/*/*']),
'stack-command-oldest': generateTestCommandStage(command: 'test-stack-command-oldest', artifacts: ['build/elastic-stack-dump/stack/*/logs/*.log', 'build/elastic-stack-dump/stack/*/logs/fleet-server-internal/*', 'build/elastic-stack-status/*/*']),
'stack-command-7x': generateTestCommandStage(command: 'test-stack-command-7x', artifacts: ['build/elastic-stack-dump/stack/*/logs/*.log', 'build/elastic-stack-dump/stack/*/logs/fleet-server-internal/*', 'build/elastic-stack-status/*/*']),
'stack-command-8x': generateTestCommandStage(command: 'test-stack-command-8x', artifacts: ['build/elastic-stack-dump/stack/*/logs/*.log', 'build/elastic-stack-dump/stack/*/logs/fleet-server-internal/*', 'build/elastic-stack-status/*/*']),
'check-packages-with-kind': generateTestCommandStage(command: 'test-check-packages-with-kind', artifacts: ['build/test-results/*.xml', 'build/kubectl-dump.txt', 'build/elastic-stack-dump/check-*/logs/*.log', 'build/elastic-stack-dump/check-*/logs/fleet-server-internal/*'], junitArtifacts: true, publishCoverage: true),
'check-packages-other': generateTestCommandStage(command: 'test-check-packages-other', artifacts: ['build/test-results/*.xml', 'build/elastic-stack-dump/check-*/logs/*.log', 'build/elastic-stack-dump/check-*/logs/fleet-server-internal/*'], junitArtifacts: true, publishCoverage: true),
'check-packages-with-custom-agent': generateTestCommandStage(command: 'test-check-packages-with-custom-agent', artifacts: ['build/test-results/*.xml', 'build/elastic-stack-dump/check-*/logs/*.log', 'build/elastic-stack-dump/check-*/logs/fleet-server-internal/*'], junitArtifacts: true, publishCoverage: true),
Expand Down
34 changes: 33 additions & 1 deletion cmd/stack.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"fmt"
"strings"

"github.com/jedib0t/go-pretty/table"
"github.com/pkg/errors"
"github.com/spf13/cobra"

Expand Down Expand Up @@ -246,6 +247,21 @@ func setupStackCommand() *cobraext.Command {
}
dumpCommand.Flags().StringP(cobraext.StackDumpOutputFlagName, "", "elastic-stack-dump", cobraext.StackDumpOutputFlagDescription)

statusCommand := &cobra.Command{
Use: "status",
Short: "Show status of the stack services",
RunE: func(cmd *cobra.Command, args []string) error {
servicesStatus, err := stack.Status()
if err != nil {
return errors.Wrap(err, "failed getting stack status")
}

cmd.Println("Status of Elastic stack services:")
printStatus(cmd, servicesStatus)
return nil
},
}

cmd := &cobra.Command{
Use: "stack",
Short: "Manage the Elastic stack",
Expand All @@ -257,7 +273,8 @@ func setupStackCommand() *cobraext.Command {
downCommand,
updateCommand,
shellInitCommand,
dumpCommand)
dumpCommand,
statusCommand)

return cobraext.NewCommand(cmd, cobraext.ContextGlobal)
}
Expand Down Expand Up @@ -300,3 +317,18 @@ func printInitConfig(cmd *cobra.Command, profile *profile.Profile) error {
cmd.Printf("Password: %s\n", initConfig.ElasticsearchPassword)
return nil
}

func printStatus(cmd *cobra.Command, servicesStatus []stack.ServiceStatus) {
if len(servicesStatus) == 0 {
cmd.Printf(" - No service running\n")
return
}
t := table.NewWriter()
t.AppendHeader(table.Row{"Service", "Version", "Status"})

for _, service := range servicesStatus {
t.AppendRow(table.Row{service.Name, service.Version, service.Status})
}
t.SetStyle(table.StyleRounded)
cmd.Println(t.Render())
}
1 change: 1 addition & 0 deletions internal/compose/compose.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ type Project struct {
type Config struct {
Services map[string]service
}

type service struct {
Ports []portMapping
Environment map[string]string
Expand Down
20 changes: 20 additions & 0 deletions internal/docker/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ type NetworkDescription struct {

// ContainerDescription describes the Docker container.
type ContainerDescription struct {
Config struct {
Image string
Labels map[string]string
}
ID string
State struct {
Status string
Expand Down Expand Up @@ -86,6 +90,22 @@ func ContainerID(containerName string) (string, error) {
return containerIDs[0], nil
}

// ContainerIDsWithLabel function returns all the container IDs filtering per label.
func ContainerIDsWithLabel(key, value string) ([]string, error) {
label := fmt.Sprintf("%s=%s", key, value)
cmd := exec.Command("docker", "ps", "-a", "--filter", "label="+label, "--format", "{{.ID}}")
errOutput := new(bytes.Buffer)
cmd.Stderr = errOutput

logger.Debugf("output command: %s", cmd)
output, err := cmd.Output()
if err != nil {
return []string{}, errors.Wrapf(err, "error getting containers with label \"%s\" (stderr=%q)", label, errOutput.String())
}
containerIDs := strings.Fields(string(output))
return containerIDs, nil
}

// InspectNetwork function returns the network description for the selected network.
func InspectNetwork(network string) ([]NetworkDescription, error) {
cmd := exec.Command("docker", "network", "inspect", network)
Expand Down
73 changes: 72 additions & 1 deletion internal/stack/compose.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,32 @@ package stack

import (
"fmt"
"strings"

"github.com/pkg/errors"

"github.com/elastic/elastic-package/internal/compose"
"github.com/elastic/elastic-package/internal/docker"
"github.com/elastic/elastic-package/internal/install"
"github.com/elastic/elastic-package/internal/logger"
"github.com/elastic/elastic-package/internal/profile"
)

type ServiceStatus struct {
Name string
Status string
Version string
}

const readyServicesSuffix = "is_ready"

const (
// serviceLabelDockerCompose is the label with the service name created by docker-compose
serviceLabelDockerCompose = "com.docker.compose.service"
// projectLabelDockerCompose is the label with the project name created by docker-compose
projectLabelDockerCompose = "com.docker.compose.project"
)

type envBuilder struct {
vars []string
}
Expand Down Expand Up @@ -162,7 +180,60 @@ func withIsReadyServices(services []string) []string {

var allServices []string
for _, aService := range services {
allServices = append(allServices, aService, fmt.Sprintf("%s_is_ready", aService))
allServices = append(allServices, aService, fmt.Sprintf("%s_%s", aService, readyServicesSuffix))
}
return allServices
}

func dockerComposeStatus() ([]ServiceStatus, error) {
var services []ServiceStatus
// query directly to docker to avoid load environment variables (e.g. STACK_VERSION_VARIANT) and profiles
containerIDs, err := docker.ContainerIDsWithLabel(projectLabelDockerCompose, DockerComposeProjectName)
if err != nil {
return nil, err
}

if len(containerIDs) == 0 {
return services, nil
}

containerDescriptions, err := docker.InspectContainers(containerIDs...)
if err != nil {
return nil, err
}

for _, containerDescription := range containerDescriptions {
service, err := newServiceStatus(&containerDescription)
if err != nil {
return nil, err
}
logger.Debugf("Adding Service: \"%v\"", service.Name)
services = append(services, *service)
}

return services, nil
}

func newServiceStatus(description *docker.ContainerDescription) (*ServiceStatus, error) {
service := ServiceStatus{
Name: description.Config.Labels[serviceLabelDockerCompose],
Status: description.State.Status,
Version: getVersionFromDockerImage(description.Config.Image),
}
if description.State.Status == "running" {
service.Status = fmt.Sprintf("%v (%v)", service.Status, description.State.Health.Status)
}
if description.State.Status == "exited" {
service.Status = fmt.Sprintf("%v (%v)", service.Status, description.State.ExitCode)
}

return &service, nil
}

func getVersionFromDockerImage(dockerImage string) string {
fields := strings.Split(dockerImage, ":")
if len(fields) == 2 {
return fields[1]
}
return "latest"
}
169 changes: 169 additions & 0 deletions internal/stack/compose_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
// 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 stack

import (
"testing"
"time"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/elastic/elastic-package/internal/docker"
)

func TestGetVersionFromDockerImage(t *testing.T) {
cases := []struct {
dockerImage string
expected string
}{
{"docker.test/test:1.42.0", "1.42.0"},
{"docker.test/test", "latest"},
}

for _, c := range cases {
t.Run(c.dockerImage, func(t *testing.T) {
version := getVersionFromDockerImage(c.dockerImage)
assert.Equal(t, c.expected, version)
})
}
}

func TestNewServiceStatus(t *testing.T) {
cases := []struct {
name string
description docker.ContainerDescription
expected ServiceStatus
}{
{
name: "commonService",
description: docker.ContainerDescription{
Config: struct {
Image string
Labels map[string]string
}{
Image: "docker.test:1.42.0",
Labels: map[string]string{"com.docker.compose.service": "myservice", "foo": "bar"},
},
ID: "123456789ab",
State: struct {
Status string
ExitCode int
Health *struct {
Status string
Log []struct {
Start time.Time
ExitCode int
Output string
}
}
}{
Status: "running",
ExitCode: 0,
Health: &struct {
Status string
Log []struct {
Start time.Time
ExitCode int
Output string
}
}{
Status: "healthy",
},
},
},
expected: ServiceStatus{
Name: "myservice",
Status: "running (healthy)",
Version: "1.42.0",
},
},
{
name: "exitedService",
description: docker.ContainerDescription{
Config: struct {
Image string
Labels map[string]string
}{
Image: "docker.test:1.42.0",
Labels: map[string]string{"com.docker.compose.service": "myservice", "foo": "bar"},
},
ID: "123456789ab",
State: struct {
Status string
ExitCode int
Health *struct {
Status string
Log []struct {
Start time.Time
ExitCode int
Output string
}
}
}{
Status: "exited",
ExitCode: 128,
Health: nil,
},
},
expected: ServiceStatus{
Name: "myservice",
Status: "exited (128)",
Version: "1.42.0",
},
},
{
name: "startingService",
description: docker.ContainerDescription{
Config: struct {
Image string
Labels map[string]string
}{
Image: "docker.test:1.42.0",
Labels: map[string]string{"com.docker.compose.service": "myservice", "foo": "bar"},
},
ID: "123456789ab",
State: struct {
Status string
ExitCode int
Health *struct {
Status string
Log []struct {
Start time.Time
ExitCode int
Output string
}
}
}{
Status: "running",
ExitCode: 0,
Health: &struct {
Status string
Log []struct {
Start time.Time
ExitCode int
Output string
}
}{
Status: "starting",
},
},
},
expected: ServiceStatus{
Name: "myservice",
Status: "running (starting)",
Version: "1.42.0",
},
},
}

for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
serviceStatus, err := newServiceStatus(&c.description)
require.NoError(t, err)
assert.Equal(t, &c.expected, serviceStatus)
})
}
}
Loading