From f368036dddea81b16cdeddc088f2be2023eb311c Mon Sep 17 00:00:00 2001 From: Guillaume Tardif <guillaume.tardif@gmail.com> Date: Thu, 4 Feb 2021 16:57:06 +0100 Subject: [PATCH] Add compose ps listing pod statuses (retrieve pods from compose project label) This does not yet retrieve port sharing data (need to reconcile ports with kube services) Signed-off-by: Guillaume Tardif <guillaume.tardif@gmail.com> --- kube/client/client.go | 38 ++++++++++++++++++++++++++ kube/client/client_test.go | 56 ++++++++++++++++++++++++++++++++++++++ kube/compose.go | 5 +++- kube/e2e/compose_test.go | 13 +++++++++ 4 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 kube/client/client_test.go diff --git a/kube/client/client.go b/kube/client/client.go index da97872ab..1cdf8821b 100644 --- a/kube/client/client.go +++ b/kube/client/client.go @@ -19,14 +19,24 @@ package client import ( + "context" + "encoding/json" + "fmt" + + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/cli-runtime/pkg/genericclioptions" "k8s.io/client-go/kubernetes" + + "github.com/docker/compose-cli/api/compose" ) +// KubeClient API to access kube objects type KubeClient struct { client *kubernetes.Clientset } +// NewKubeClient new kubernetes client func NewKubeClient(config genericclioptions.RESTClientGetter) (*KubeClient, error) { restConfig, err := config.ToRESTConfig() if err != nil { @@ -41,3 +51,31 @@ func NewKubeClient(config genericclioptions.RESTClientGetter) (*KubeClient, erro client: clientset, }, nil } + +// GetContainers get containers for a given compose project +func (kc KubeClient) GetContainers(ctx context.Context, projectName string, all bool) ([]compose.ContainerSummary, error) { + pods, err := kc.client.CoreV1().Pods("").List(ctx, metav1.ListOptions{LabelSelector: fmt.Sprintf("%s=%s", compose.ProjectTag, projectName)}) + + if err != nil { + return nil, err + } + json, _ := json.MarshalIndent(pods, "", " ") + fmt.Println(string(json)) + fmt.Printf("containers: %d\n", len(pods.Items)) + result := []compose.ContainerSummary{} + for _, pod := range pods.Items { + result = append(result, podToContainerSummary(pod)) + } + + return result, nil +} + +func podToContainerSummary(pod v1.Pod) compose.ContainerSummary { + return compose.ContainerSummary{ + ID: pod.GetObjectMeta().GetName(), + Name: pod.GetObjectMeta().GetName(), + Service: pod.GetObjectMeta().GetLabels()[compose.ServiceTag], + State: string(pod.Status.Phase), + Project: pod.GetObjectMeta().GetLabels()[compose.ProjectTag], + } +} diff --git a/kube/client/client_test.go b/kube/client/client_test.go new file mode 100644 index 000000000..bd063dfa0 --- /dev/null +++ b/kube/client/client_test.go @@ -0,0 +1,56 @@ +// +build kube + +/* + Copyright 2020 Docker Compose CLI authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package client + +import ( + "testing" + + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "gotest.tools/v3/assert" + + "github.com/docker/compose-cli/api/compose" +) + +func TestPodToContainerSummary(t *testing.T) { + pod := v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "c1-123", + Labels: map[string]string{ + compose.ProjectTag: "myproject", + compose.ServiceTag: "service1", + }, + }, + Status: v1.PodStatus{ + Phase: "Running", + }, + } + + container := podToContainerSummary(pod) + + expected := compose.ContainerSummary{ + ID: "c1-123", + Name: "c1-123", + Project: "myproject", + Service: "service1", + State: "Running", + } + assert.DeepEqual(t, container, expected) +} diff --git a/kube/compose.go b/kube/compose.go index 27266da77..1d80aca52 100644 --- a/kube/compose.go +++ b/kube/compose.go @@ -54,6 +54,9 @@ func NewComposeService(ctx context.Context) (compose.Service, error) { return nil, err } actions, err := helm.NewActions(config) + if err != nil { + return nil, err + } apiClient, err := client.NewKubeClient(config) if err != nil { return nil, err @@ -156,7 +159,7 @@ func (s *composeService) Logs(ctx context.Context, projectName string, consumer // Ps executes the equivalent to a `compose ps` func (s *composeService) Ps(ctx context.Context, projectName string, options compose.PsOptions) ([]compose.ContainerSummary, error) { - return nil, errdefs.ErrNotImplemented + return s.client.GetContainers(ctx, projectName, options.All) } // Convert translate compose model into backend's native format diff --git a/kube/e2e/compose_test.go b/kube/e2e/compose_test.go index 0d14c8f7e..0813f5a3a 100644 --- a/kube/e2e/compose_test.go +++ b/kube/e2e/compose_test.go @@ -24,6 +24,7 @@ import ( "testing" "time" + testify "github.com/stretchr/testify/assert" "gotest.tools/v3/assert" "gotest.tools/v3/icmd" @@ -78,6 +79,18 @@ func TestComposeUp(t *testing.T) { res.Assert(t, icmd.Expected{Out: `[{"Name":"compose-kube-demo","Status":"deployed"}]`}) }) + t.Run("compose ps", func(t *testing.T) { + getServiceRegx := func(project string, service string) string { + // match output with random hash / spaces like: + // myproject-db-698f4dd798-jd9gw db Running + return fmt.Sprintf("%s-%s-.*\\s+%s\\s+Pending\\s+", project, service, service) + } + res := c.RunDockerCmd("compose", "ps", "-p", projectName) + testify.Regexp(t, getServiceRegx(projectName, "db"), res.Stdout()) + testify.Regexp(t, getServiceRegx(projectName, "words"), res.Stdout()) + testify.Regexp(t, getServiceRegx(projectName, "web"), res.Stdout()) + }) + t.Run("check running project", func(t *testing.T) { // Docker Desktop kube cluster automatically exposes ports on the host, this is not the case with kind on Desktop, //we need to connect to the clusterIP, from the kind container