From 7587769d0b4ad55f843bbfa4bcf2ba2d4a12eb29 Mon Sep 17 00:00:00 2001 From: vaikas-google Date: Sun, 3 Apr 2016 15:32:23 +0300 Subject: [PATCH 1/2] Implement deployment describe --- cmd/helm/deployment.go | 21 +++++++++- cmd/helm/deployment_test.go | 19 +++++++++ pkg/client/deployments.go | 20 ++++++--- pkg/client/deployments_test.go | 76 ++++++++++++++++++++++++++++++++++ 4 files changed, 129 insertions(+), 7 deletions(-) diff --git a/cmd/helm/deployment.go b/cmd/helm/deployment.go index ac642402d..c17b68023 100644 --- a/cmd/helm/deployment.go +++ b/cmd/helm/deployment.go @@ -48,6 +48,13 @@ Status: {{.State.Status}} {{range .}} {{.}}{{end}} {{end}}` +const defaultShowResourceFormat = `Name: {{.Name}} +Type: {{.Type}} +Status: {{.State.Status}} +{{with .State.Errors}}Errors: +{{range .}} {{.}}{{end}} +{{end}}` + func init() { addCommands(deploymentCommands()) } @@ -145,13 +152,23 @@ func describeDeployment(c *cli.Context) error { return errTooManyArgs } name := args[0] - _, err := NewClient(c).DescribeDeployment(name) + manifest, err := NewClient(c).DescribeDeployment(name) if err != nil { return err } - format.Info("TO BE IMPLEMENTED") + if manifest.ExpandedConfig == nil { + return errors.New("No ExpandedConfig found for: " + name) + } + for _, resource := range manifest.ExpandedConfig.Resources { + tmpl := template.Must(template.New("showresource").Parse(defaultShowResourceFormat)) + err = tmpl.Execute(os.Stdout, resource) + if err != nil { + return err + } + + } return nil } diff --git a/cmd/helm/deployment_test.go b/cmd/helm/deployment_test.go index dd1f149cc..d82109f7a 100644 --- a/cmd/helm/deployment_test.go +++ b/cmd/helm/deployment_test.go @@ -54,6 +54,25 @@ func TestDeployment(t *testing.T) { []string{"guestbook.yaml"}, "guestbook.yaml\n", }, + { + []string{"deployment", "describe", "guestbook.yaml"}, + &common.Manifest{ + Deployment: "guestbook.yaml", + Name: "manifestxyz", + ExpandedConfig: &common.Configuration{ + Resources: []*common.Resource{ + {Name: "fe-rc", Type: "ReplicationController", State: &common.ResourceState{Status: common.Created}}, + {Name: "fe", Type: "Service", State: &common.ResourceState{Status: common.Created}}, + {Name: "be-rc", Type: "ReplicationController", State: &common.ResourceState{Status: common.Created}}, + {Name: "be", Type: "Service", State: &common.ResourceState{Status: common.Created}}, + }, + }, + }, + "Name: fe-rc\nType: ReplicationController\nStatus: Created\n" + + "Name: fe\nType: Service\nStatus: Created\n" + + "Name: be-rc\nType: ReplicationController\nStatus: Created\n" + + "Name: be\nType: Service\nStatus: Created\n", + }, } for _, tc := range deploymentTestCases { diff --git a/pkg/client/deployments.go b/pkg/client/deployments.go index a881f6ce1..b28c2d9eb 100644 --- a/pkg/client/deployments.go +++ b/pkg/client/deployments.go @@ -18,6 +18,7 @@ package client import ( "encoding/json" + "errors" "io/ioutil" "net/http" "os" @@ -100,11 +101,20 @@ func (c *Client) DeleteDeployment(name string) (*common.Deployment, error) { return deployment, err } -// DescribeDeployment describes the kubernetes resources of the supplied deployment -func (c *Client) DescribeDeployment(name string) (*common.Deployment, error) { - var deployment *common.Deployment - //TODO: implement - return deployment, nil +// DescribeDeployment describes the kubernetes resources of the supplied deployment by fetching the +// latest manifest. +func (c *Client) DescribeDeployment(name string) (*common.Manifest, error) { + var manifest *common.Manifest + deployment, err := c.GetDeployment(name) + if err != nil { + return nil, err + } + if deployment.LatestManifest == "" { + return nil, errors.New("Deployment: '" + name + "' has no manifest") + } + + _, err = c.Get(fancypath.Join("deployments", name, "manifests", deployment.LatestManifest), &manifest) + return manifest, err } // PostDeployment posts a deployment object to the manager service. diff --git a/pkg/client/deployments_test.go b/pkg/client/deployments_test.go index 6f8f0c04c..83b8ef4d3 100644 --- a/pkg/client/deployments_test.go +++ b/pkg/client/deployments_test.go @@ -19,6 +19,7 @@ package client import ( "fmt" "net/http" + "strings" "testing" "github.com/kubernetes/helm/pkg/common" @@ -64,6 +65,81 @@ func TestGetDeployment(t *testing.T) { } } +func TestDescribeDeployment(t *testing.T) { + fc := &fakeClient{ + handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if strings.Contains(r.URL.String(), "manifest") { + w.Write([]byte(`{"deployment":"guestbook.yaml","name":"manifest-1454962670728402229","expandedConfig":{"resources":[{"name":"fe-rc","type":"ReplicationController","state":{"status":"Created"}},{"name":"fe","type":"Service","state":{"status":"Created"}}]}}`)) + } else { + w.Write([]byte(`{"name":"guestbook.yaml","id":0,"createdAt":"2016-02-08T12:17:49.251658308-08:00","deployedAt":"2016-02-08T12:17:49.251658589-08:00","modifiedAt":"2016-02-08T12:17:51.177518098-08:00","deletedAt":"0001-01-01T00:00:00Z","state":{"status":"Deployed"},"latestManifest":"manifest-1454962670728402229"}`)) + } + + }), + } + defer fc.teardown() + + m, err := fc.setup().DescribeDeployment("guestbook.yaml") + if err != nil { + t.Fatal(err) + } + + if m.Deployment != "guestbook.yaml" { + t.Fatalf("expected deployment name 'guestbook.yaml', got '%s'", m.Name) + } + if m.Name != "manifest-1454962670728402229" { + t.Fatalf("expected manifest name 'manifest-1454962670728402229', got '%s'", m.Name) + } + if len(m.ExpandedConfig.Resources) != 2 { + t.Fatalf("expected two resources, got %d", len(m.ExpandedConfig.Resources)) + } + var foundFE = false + var foundFERC = false + for _, r := range m.ExpandedConfig.Resources { + if r.Name == "fe" { + foundFE = true + if r.Type != "Service" { + t.Fatalf("Incorrect type, expected 'Service' got '%s'", r.Type) + } + } + if r.Name == "fe-rc" { + foundFERC = true + if r.Type != "ReplicationController" { + t.Fatalf("Incorrect type, expected 'ReplicationController' got '%s'", r.Type) + } + } + if r.State.Status != common.Created { + t.Fatalf("Incorrect status, expected '%s' got '%s'", common.Created, r.State.Status) + } + } + if !foundFE { + t.Fatalf("didn't find 'fe' in resources") + } + if !foundFERC { + t.Fatalf("didn't find 'fe-rc' in resources") + } +} + +func TestDescribeDeploymentFailedDeployment(t *testing.T) { + var expectedError = "Deployment: 'guestbook.yaml' has no manifest" + fc := &fakeClient{ + handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(`{"name":"guestbook.yaml","createdAt":"2016-04-02T10:41:06.509049871-07:00","deployedAt":"0001-01-01T00:00:00Z","modifiedAt":"2016-04-02T10:41:06.509203582-07:00","deletedAt":"0001-01-01T00:00:00Z","state":{"status":"Failed","errors":["cannot expand configuration:No repository for url gs://kubernetes-charts-testing/redis-2.tgz\n\u0026{[0xc82014efc0]}\n"]},"latestManifest":""}`)) + }), + } + defer fc.teardown() + + m, err := fc.setup().DescribeDeployment("guestbook.yaml") + if err == nil { + t.Fatal("Did not get an error for missing manifest") + } + if err.Error() != expectedError { + t.Fatalf("Unexpected error message, wanted:\n%s\ngot:\n%s", expectedError, err.Error()) + } + if m != nil { + t.Fatal("Got back manifest but shouldn't have") + } +} + func TestPostDeployment(t *testing.T) { chartInvocation := &common.Resource{ Name: "foo", From 4edccdea5f7ff351f8372f66c0e9b44dcc06219d Mon Sep 17 00:00:00 2001 From: vaikas-google Date: Sun, 3 Apr 2016 19:56:16 +0300 Subject: [PATCH 2/2] fix the tests for the command --- cmd/helm/deployment_test.go | 66 ++++++++++++++++++++++--------------- 1 file changed, 40 insertions(+), 26 deletions(-) diff --git a/cmd/helm/deployment_test.go b/cmd/helm/deployment_test.go index d82109f7a..581cbc6aa 100644 --- a/cmd/helm/deployment_test.go +++ b/cmd/helm/deployment_test.go @@ -24,64 +24,78 @@ import ( "github.com/kubernetes/helm/pkg/common" ) +type pathAndResponse struct { + path string + resp interface{} +} + func TestDeployment(t *testing.T) { var deploymentTestCases = []struct { args []string - resp interface{} + resp []pathAndResponse expected string }{ { []string{"deployment", "show", "guestbook.yaml"}, - &common.Deployment{ + []pathAndResponse{{"/deployments/", &common.Deployment{ Name: "guestbook.yaml", State: &common.DeploymentState{Status: common.CreatedStatus}, - }, + }}}, "Name: guestbook.yaml\nStatus: Created\n", }, { []string{"deployment", "show", "guestbook.yaml"}, - &common.Deployment{ + []pathAndResponse{{"/deployments/", &common.Deployment{ Name: "guestbook.yaml", State: &common.DeploymentState{ Status: common.FailedStatus, Errors: []string{"error message"}, }, - }, + }}}, "Name: guestbook.yaml\nStatus: Failed\nErrors:\n error message\n", }, { []string{"deployment", "list"}, - []string{"guestbook.yaml"}, + []pathAndResponse{{"/deployments/", []string{"guestbook.yaml"}}}, "guestbook.yaml\n", }, { []string{"deployment", "describe", "guestbook.yaml"}, - &common.Manifest{ - Deployment: "guestbook.yaml", - Name: "manifestxyz", - ExpandedConfig: &common.Configuration{ - Resources: []*common.Resource{ - {Name: "fe-rc", Type: "ReplicationController", State: &common.ResourceState{Status: common.Created}}, - {Name: "fe", Type: "Service", State: &common.ResourceState{Status: common.Created}}, - {Name: "be-rc", Type: "ReplicationController", State: &common.ResourceState{Status: common.Created}}, - {Name: "be", Type: "Service", State: &common.ResourceState{Status: common.Created}}, + []pathAndResponse{{ + "/deployments/guestbook.yaml", + &common.Deployment{Name: "guestbook.yaml", + State: &common.DeploymentState{Status: common.CreatedStatus}, + LatestManifest: "manifestxyz", + }}, + {"/deployments/guestbook.yaml/manifests/manifestxyz", &common.Manifest{ + Deployment: "guestbook.yaml", + Name: "manifestxyz", + ExpandedConfig: &common.Configuration{ + Resources: []*common.Resource{ + {Name: "fe-rc", Type: "ReplicationController", State: &common.ResourceState{Status: common.Created}}, + {Name: "fe", Type: "Service", State: &common.ResourceState{Status: common.Created}}, + {Name: "be-rc", Type: "ReplicationController", State: &common.ResourceState{Status: common.Created}}, + {Name: "be", Type: "Service", State: &common.ResourceState{Status: common.Created}}, + }, }, - }, - }, - "Name: fe-rc\nType: ReplicationController\nStatus: Created\n" + - "Name: fe\nType: Service\nStatus: Created\n" + - "Name: be-rc\nType: ReplicationController\nStatus: Created\n" + - "Name: be\nType: Service\nStatus: Created\n", + }}}, + "Name: fe-rc\nType: ReplicationController\nStatus: Created\n" + + "Name: fe\nType: Service\nStatus: Created\n" + + "Name: be-rc\nType: ReplicationController\nStatus: Created\n" + + "Name: be\nType: Service\nStatus: Created\n", }, } for _, tc := range deploymentTestCases { th := testHelm(t) - th.mux.HandleFunc("/deployments/", func(w http.ResponseWriter, r *http.Request) { - data, err := json.Marshal(tc.resp) - th.must(err) - w.Write(data) - }) + for _, pathAndResponse := range tc.resp { + var response = pathAndResponse.resp + th.mux.HandleFunc(pathAndResponse.path, func(w http.ResponseWriter, r *http.Request) { + data, err := json.Marshal(response) + th.must(err) + w.Write(data) + }) + } th.run(tc.args...)