From f0eef01f7ff1357b464872c6dd789f267c8dd218 Mon Sep 17 00:00:00 2001 From: Aaron Kirkbride Date: Fri, 8 Jun 2018 15:09:59 +0100 Subject: [PATCH 1/2] Create ListImagesWithOptions in v10 --- api/api.go | 10 +++----- api/v10/api.go | 25 ++++++++++++++++++ api/v6/api.go | 6 +---- cmd/fluxctl/list_images_cmd.go | 2 +- daemon/daemon.go | 14 +++++++--- daemon/daemon_test.go | 28 ++++++++++---------- http/client/client.go | 11 ++++++-- http/daemon/server.go | 12 +++++---- http/routes.go | 15 ++++++----- http/transport.go | 1 + remote/logging.go | 14 ++++++++-- remote/metrics.go | 15 +++++++++-- remote/mock.go | 15 ++++++++--- remote/rpc/baseclient.go | 7 ++++- remote/rpc/clientV10.go | 47 ++++++++++++++++++++++++++++++++++ remote/rpc/clientV6.go | 9 ++++++- remote/rpc/clientV7.go | 2 +- remote/rpc/clientV8.go | 2 +- remote/rpc/rpc_test.go | 2 +- remote/rpc/server.go | 16 +++++++++++- 20 files changed, 197 insertions(+), 56 deletions(-) create mode 100644 api/v10/api.go create mode 100644 remote/rpc/clientV10.go diff --git a/api/api.go b/api/api.go index b3282dbe9..c90ba502c 100644 --- a/api/api.go +++ b/api/api.go @@ -1,19 +1,17 @@ package api -import ( - "github.com/weaveworks/flux/api/v9" -) +import "github.com/weaveworks/flux/api/v10" // Server defines the minimal interface a Flux must satisfy to adequately serve a // connecting fluxctl. This interface specifically does not facilitate connecting // to Weave Cloud. type Server interface { - v9.Server + v10.Server } // UpstreamServer is the interface a Flux must satisfy in order to communicate with // Weave Cloud. type UpstreamServer interface { - v9.Server - v9.Upstream + v10.Server + v10.Upstream } diff --git a/api/v10/api.go b/api/v10/api.go new file mode 100644 index 000000000..02d9c0ae9 --- /dev/null +++ b/api/v10/api.go @@ -0,0 +1,25 @@ +// This package defines the types for Flux API version 9. +package v10 + +import ( + "context" + + "github.com/weaveworks/flux/api/v6" + "github.com/weaveworks/flux/api/v9" + "github.com/weaveworks/flux/update" +) + +type ListImagesOptions struct { + Spec update.ResourceSpec + OverrideContainerFields []string +} + +type Server interface { + v6.NotDeprecated + + ListImagesWithOptions(ctx context.Context, opts ListImagesOptions) ([]v6.ImageStatus, error) +} + +type Upstream interface { + v9.Upstream +} diff --git a/api/v6/api.go b/api/v6/api.go index 37749f67e..42f4ad78d 100644 --- a/api/v6/api.go +++ b/api/v6/api.go @@ -76,17 +76,13 @@ type Deprecated interface { SyncNotify(context.Context) error } -type ListImagesOptions struct { - OverrideContainerFields []string -} - type NotDeprecated interface { // from v5 Export(context.Context) ([]byte, error) // v6 ListServices(ctx context.Context, namespace string) ([]ControllerStatus, error) - ListImages(ctx context.Context, spec update.ResourceSpec, opts ListImagesOptions) ([]ImageStatus, error) + ListImages(ctx context.Context, spec update.ResourceSpec) ([]ImageStatus, error) UpdateManifests(context.Context, update.Spec) (job.ID, error) SyncStatus(ctx context.Context, ref string) ([]string, error) JobStatus(context.Context, job.ID) (job.Status, error) diff --git a/cmd/fluxctl/list_images_cmd.go b/cmd/fluxctl/list_images_cmd.go index 138fff09e..e9bb7a115 100644 --- a/cmd/fluxctl/list_images_cmd.go +++ b/cmd/fluxctl/list_images_cmd.go @@ -67,7 +67,7 @@ func (opts *controllerShowOpts) RunE(cmd *cobra.Command, args []string) error { ctx := context.Background() - controllers, err := opts.API.ListImages(ctx, resourceSpec, v6.ListImagesOptions{}) + controllers, err := opts.API.ListImages(ctx, resourceSpec) if err != nil { return err } diff --git a/daemon/daemon.go b/daemon/daemon.go index 33188baec..06b250d31 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -12,6 +12,7 @@ import ( "github.com/weaveworks/flux" "github.com/weaveworks/flux/api" + "github.com/weaveworks/flux/api/v10" "github.com/weaveworks/flux/api/v6" "github.com/weaveworks/flux/api/v9" "github.com/weaveworks/flux/cluster" @@ -142,14 +143,19 @@ func (cs clusterContainers) Containers(i int) []resource.Container { return cs[i].ContainersOrNil() } -// List the images available for set of services -func (d *Daemon) ListImages(ctx context.Context, spec update.ResourceSpec, opts v6.ListImagesOptions) ([]v6.ImageStatus, error) { +// ListImages - deprecated from v10, lists the images available for set of services +func (d *Daemon) ListImages(ctx context.Context, spec update.ResourceSpec) ([]v6.ImageStatus, error) { + return d.ListImagesWithOptions(ctx, v10.ListImagesOptions{Spec: spec}) +} + +// ListImagesWithOptions lists the images available for set of services +func (d *Daemon) ListImagesWithOptions(ctx context.Context, opts v10.ListImagesOptions) ([]v6.ImageStatus, error) { var services []cluster.Controller var err error - if spec == update.ResourceSpecAll { + if opts.Spec == update.ResourceSpecAll { services, err = d.Cluster.AllControllers("") } else { - id, err := spec.AsID() + id, err := opts.Spec.AsID() if err != nil { return nil, errors.Wrap(err, "treating service spec as ID") } diff --git a/daemon/daemon_test.go b/daemon/daemon_test.go index f8f9baa2e..4590edd05 100644 --- a/daemon/daemon_test.go +++ b/daemon/daemon_test.go @@ -15,6 +15,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/weaveworks/flux" + "github.com/weaveworks/flux/api/v10" "github.com/weaveworks/flux/api/v6" "github.com/weaveworks/flux/api/v9" "github.com/weaveworks/flux/cluster" @@ -135,7 +136,7 @@ func TestDaemon_ListServices(t *testing.T) { } // When I call list images for a service, it should return images -func TestDaemon_ListImages(t *testing.T) { +func TestDaemon_ListImagesWithOptions(t *testing.T) { d, start, clean, _, _ := mockDaemon(t) start() defer clean() @@ -160,8 +161,7 @@ func TestDaemon_ListImages(t *testing.T) { tests := []struct { name string - spec update.ResourceSpec - opts v6.ListImagesOptions + opts v10.ListImagesOptions expectedImages []v6.ImageStatus expectedNumImages int @@ -169,8 +169,7 @@ func TestDaemon_ListImages(t *testing.T) { }{ { name: "All services", - spec: specAll, - opts: v6.ListImagesOptions{}, + opts: v10.ListImagesOptions{Spec: specAll}, expectedImages: []v6.ImageStatus{ { ID: svcID, @@ -212,8 +211,7 @@ func TestDaemon_ListImages(t *testing.T) { }, { name: "Specific service", - spec: update.ResourceSpec(svc), - opts: v6.ListImagesOptions{}, + opts: v10.ListImagesOptions{Spec: update.ResourceSpec(svc)}, expectedImages: []v6.ImageStatus{ { ID: svcID, @@ -238,8 +236,10 @@ func TestDaemon_ListImages(t *testing.T) { }, { name: "Override container field selection", - spec: specAll, - opts: v6.ListImagesOptions{OverrideContainerFields: []string{"Name", "Current", "NewAvailableImagesCount"}}, + opts: v10.ListImagesOptions{ + Spec: specAll, + OverrideContainerFields: []string{"Name", "Current", "NewAvailableImagesCount"}, + }, expectedImages: []v6.ImageStatus{ { ID: svcID, @@ -265,9 +265,11 @@ func TestDaemon_ListImages(t *testing.T) { shouldError: false, }, { - name: "Override container field selection with invalid field", - spec: specAll, - opts: v6.ListImagesOptions{OverrideContainerFields: []string{"InvalidField"}}, + name: "Override container field selection with invalid field", + opts: v10.ListImagesOptions{ + Spec: specAll, + OverrideContainerFields: []string{"InvalidField"}, + }, expectedImages: nil, shouldError: true, }, @@ -275,7 +277,7 @@ func TestDaemon_ListImages(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - is, err := d.ListImages(ctx, tt.spec, tt.opts) + is, err := d.ListImagesWithOptions(ctx, tt.opts) assert.Equal(t, tt.shouldError, err != nil) // Clear CreatedAt fields for testing diff --git a/http/client/client.go b/http/client/client.go index a4acaea0b..61566bc25 100644 --- a/http/client/client.go +++ b/http/client/client.go @@ -13,6 +13,7 @@ import ( "github.com/pkg/errors" "github.com/weaveworks/flux/api" + "github.com/weaveworks/flux/api/v10" "github.com/weaveworks/flux/api/v6" fluxerr "github.com/weaveworks/flux/errors" "github.com/weaveworks/flux/event" @@ -57,9 +58,15 @@ func (c *Client) ListServices(ctx context.Context, namespace string) ([]v6.Contr return res, err } -func (c *Client) ListImages(ctx context.Context, s update.ResourceSpec, opts v6.ListImagesOptions) ([]v6.ImageStatus, error) { +func (c *Client) ListImages(ctx context.Context, s update.ResourceSpec) ([]v6.ImageStatus, error) { var res []v6.ImageStatus - err := c.Get(ctx, &res, transport.ListImages, "service", string(s), "containerFields", strings.Join(opts.OverrideContainerFields, ",")) + err := c.Get(ctx, &res, transport.ListImages, "service", string(s)) + return res, err +} + +func (c *Client) ListImagesWithOptions(ctx context.Context, opts v10.ListImagesOptions) ([]v6.ImageStatus, error) { + var res []v6.ImageStatus + err := c.Get(ctx, &res, transport.ListImagesWithOptions, "service", string(opts.Spec), "containerFields", strings.Join(opts.OverrideContainerFields, ",")) return res, err } diff --git a/http/daemon/server.go b/http/daemon/server.go index 7380272ae..57013946f 100644 --- a/http/daemon/server.go +++ b/http/daemon/server.go @@ -12,7 +12,7 @@ import ( "github.com/weaveworks/flux" "github.com/weaveworks/flux/api" - "github.com/weaveworks/flux/api/v6" + "github.com/weaveworks/flux/api/v10" transport "github.com/weaveworks/flux/http" "github.com/weaveworks/flux/job" fluxmetrics "github.com/weaveworks/flux/metrics" @@ -48,7 +48,8 @@ func NewRouter() *mux.Router { func NewHandler(s api.Server, r *mux.Router) http.Handler { handle := HTTPServer{s} r.Get(transport.ListServices).HandlerFunc(handle.ListServices) - r.Get(transport.ListImages).HandlerFunc(handle.ListImages) + r.Get(transport.ListImages).HandlerFunc(handle.ListImagesWithOptions) + r.Get(transport.ListImagesWithOptions).HandlerFunc(handle.ListImagesWithOptions) r.Get(transport.UpdateManifests).HandlerFunc(handle.UpdateManifests) r.Get(transport.JobStatus).HandlerFunc(handle.JobStatus) r.Get(transport.SyncStatus).HandlerFunc(handle.SyncStatus) @@ -92,7 +93,8 @@ func (s HTTPServer) SyncStatus(w http.ResponseWriter, r *http.Request) { transport.JSONResponse(w, r, commits) } -func (s HTTPServer) ListImages(w http.ResponseWriter, r *http.Request) { +func (s HTTPServer) ListImagesWithOptions(w http.ResponseWriter, r *http.Request) { + var opts v10.ListImagesOptions queryValues := r.URL.Query() // service - Select services to update. @@ -105,15 +107,15 @@ func (s HTTPServer) ListImages(w http.ResponseWriter, r *http.Request) { transport.WriteError(w, r, http.StatusBadRequest, errors.Wrapf(err, "parsing service spec %q", service)) return } + opts.Spec = spec // containerFields - Override which fields to return in the container struct. - var opts v6.ListImagesOptions containerFields := queryValues.Get("containerFields") if containerFields != "" { opts.OverrideContainerFields = strings.Split(containerFields, ",") } - d, err := s.server.ListImages(r.Context(), spec, opts) + d, err := s.server.ListImagesWithOptions(r.Context(), opts) if err != nil { transport.ErrorResponse(w, r, err) return diff --git a/http/routes.go b/http/routes.go index 6b67908d3..25c6853e7 100644 --- a/http/routes.go +++ b/http/routes.go @@ -1,13 +1,14 @@ package http const ( - ListServices = "ListServices" - ListImages = "ListImages" - UpdateManifests = "UpdateManifests" - JobStatus = "JobStatus" - SyncStatus = "SyncStatus" - Export = "Export" - GitRepoConfig = "GitRepoConfig" + ListServices = "ListServices" + ListImages = "ListImages" + ListImagesWithOptions = "ListImagesWithOptions" + UpdateManifests = "UpdateManifests" + JobStatus = "JobStatus" + SyncStatus = "SyncStatus" + Export = "Export" + GitRepoConfig = "GitRepoConfig" UpdateImages = "UpdateImages" UpdatePolicies = "UpdatePolicies" diff --git a/http/transport.go b/http/transport.go index 74d019519..89d7bf25b 100644 --- a/http/transport.go +++ b/http/transport.go @@ -31,6 +31,7 @@ func NewAPIRouter() *mux.Router { r.NewRoute().Name(ListServices).Methods("GET").Path("/v6/services") r.NewRoute().Name(ListImages).Methods("GET").Path("/v6/images") + r.NewRoute().Name(ListImagesWithOptions).Methods("GET").Path("/v10/images") r.NewRoute().Name(UpdateManifests).Methods("POST").Path("/v9/update-manifests") r.NewRoute().Name(JobStatus).Methods("GET").Path("/v6/jobs").Queries("id", "{id}") diff --git a/remote/logging.go b/remote/logging.go index 86226bdaa..bc63fe455 100644 --- a/remote/logging.go +++ b/remote/logging.go @@ -6,6 +6,7 @@ import ( "github.com/go-kit/kit/log" "github.com/weaveworks/flux/api" + "github.com/weaveworks/flux/api/v10" "github.com/weaveworks/flux/api/v6" "github.com/weaveworks/flux/api/v9" "github.com/weaveworks/flux/job" @@ -43,13 +44,22 @@ func (p *ErrorLoggingServer) ListServices(ctx context.Context, maybeNamespace st return p.server.ListServices(ctx, maybeNamespace) } -func (p *ErrorLoggingServer) ListImages(ctx context.Context, spec update.ResourceSpec, opts v6.ListImagesOptions) (_ []v6.ImageStatus, err error) { +func (p *ErrorLoggingServer) ListImages(ctx context.Context, spec update.ResourceSpec) (_ []v6.ImageStatus, err error) { defer func() { if err != nil { p.logger.Log("method", "ListImages", "error", err) } }() - return p.server.ListImages(ctx, spec, opts) + return p.server.ListImages(ctx, spec) +} + +func (p *ErrorLoggingServer) ListImagesWithOptions(ctx context.Context, opts v10.ListImagesOptions) (_ []v6.ImageStatus, err error) { + defer func() { + if err != nil { + p.logger.Log("method", "ListImages", "error", err) + } + }() + return p.server.ListImagesWithOptions(ctx, opts) } func (p *ErrorLoggingServer) JobStatus(ctx context.Context, jobID job.ID) (_ job.Status, err error) { diff --git a/remote/metrics.go b/remote/metrics.go index 50a98282a..3a468e75f 100644 --- a/remote/metrics.go +++ b/remote/metrics.go @@ -9,6 +9,7 @@ import ( stdprometheus "github.com/prometheus/client_golang/prometheus" "github.com/weaveworks/flux/api" + "github.com/weaveworks/flux/api/v10" "github.com/weaveworks/flux/api/v6" "github.com/weaveworks/flux/api/v9" "github.com/weaveworks/flux/job" @@ -56,14 +57,24 @@ func (i *instrumentedServer) ListServices(ctx context.Context, namespace string) return i.s.ListServices(ctx, namespace) } -func (i *instrumentedServer) ListImages(ctx context.Context, spec update.ResourceSpec, opts v6.ListImagesOptions) (_ []v6.ImageStatus, err error) { +func (i *instrumentedServer) ListImages(ctx context.Context, spec update.ResourceSpec) (_ []v6.ImageStatus, err error) { defer func(begin time.Time) { requestDuration.With( fluxmetrics.LabelMethod, "ListImages", fluxmetrics.LabelSuccess, fmt.Sprint(err == nil), ).Observe(time.Since(begin).Seconds()) }(time.Now()) - return i.s.ListImages(ctx, spec, opts) + return i.s.ListImages(ctx, spec) +} + +func (i *instrumentedServer) ListImagesWithOptions(ctx context.Context, opts v10.ListImagesOptions) (_ []v6.ImageStatus, err error) { + defer func(begin time.Time) { + requestDuration.With( + fluxmetrics.LabelMethod, "ListImagesWithOptions", + fluxmetrics.LabelSuccess, fmt.Sprint(err == nil), + ).Observe(time.Since(begin).Seconds()) + }(time.Now()) + return i.s.ListImagesWithOptions(ctx, opts) } func (i *instrumentedServer) UpdateManifests(ctx context.Context, spec update.Spec) (_ job.ID, err error) { diff --git a/remote/mock.go b/remote/mock.go index e23a41540..9266b51fd 100644 --- a/remote/mock.go +++ b/remote/mock.go @@ -10,6 +10,7 @@ import ( "github.com/weaveworks/flux" "github.com/weaveworks/flux/api" + "github.com/weaveworks/flux/api/v10" "github.com/weaveworks/flux/api/v6" "github.com/weaveworks/flux/api/v9" "github.com/weaveworks/flux/guid" @@ -65,7 +66,11 @@ func (p *MockServer) ListServices(ctx context.Context, ns string) ([]v6.Controll return p.ListServicesAnswer, p.ListServicesError } -func (p *MockServer) ListImages(context.Context, update.ResourceSpec, v6.ListImagesOptions) ([]v6.ImageStatus, error) { +func (p *MockServer) ListImages(context.Context, update.ResourceSpec) ([]v6.ImageStatus, error) { + return p.ListImagesAnswer, p.ListImagesError +} + +func (p *MockServer) ListImagesWithOptions(context.Context, v10.ListImagesOptions) ([]v6.ImageStatus, error) { return p.ListImagesAnswer, p.ListImagesError } @@ -194,7 +199,9 @@ func ServerTestBattery(t *testing.T, wrap func(mock api.UpstreamServer) api.Upst t.Error("expected error from ListServices, got nil") } - ims, err := client.ListImages(ctx, update.ResourceSpecAll, v6.ListImagesOptions{}) + ims, err := client.ListImagesWithOptions(ctx, v10.ListImagesOptions{ + Spec: update.ResourceSpecAll, + }) if err != nil { t.Error(err) } @@ -202,7 +209,9 @@ func ServerTestBattery(t *testing.T, wrap func(mock api.UpstreamServer) api.Upst t.Error(fmt.Errorf("expected:\n%#v\ngot:\n%#v", mock.ListImagesAnswer, ims)) } mock.ListImagesError = fmt.Errorf("list images error") - if _, err = client.ListImages(ctx, update.ResourceSpecAll, v6.ListImagesOptions{}); err == nil { + if _, err = client.ListImagesWithOptions(ctx, v10.ListImagesOptions{ + Spec: update.ResourceSpecAll, + }); err == nil { t.Error("expected error from ListImages, got nil") } diff --git a/remote/rpc/baseclient.go b/remote/rpc/baseclient.go index 5375d1526..9c40762a5 100644 --- a/remote/rpc/baseclient.go +++ b/remote/rpc/baseclient.go @@ -6,6 +6,7 @@ import ( "github.com/pkg/errors" "github.com/weaveworks/flux/api" + "github.com/weaveworks/flux/api/v10" "github.com/weaveworks/flux/api/v6" "github.com/weaveworks/flux/api/v9" "github.com/weaveworks/flux/job" @@ -33,10 +34,14 @@ func (bc baseClient) ListServices(context.Context, string) ([]v6.ControllerStatu return nil, remote.UpgradeNeededError(errors.New("ListServices method not implemented")) } -func (bc baseClient) ListImages(context.Context, update.ResourceSpec, v6.ListImagesOptions) ([]v6.ImageStatus, error) { +func (bc baseClient) ListImages(context.Context, update.ResourceSpec) ([]v6.ImageStatus, error) { return nil, remote.UpgradeNeededError(errors.New("ListImages method not implemented")) } +func (bc baseClient) ListImagesWithOptions(context.Context, v10.ListImagesOptions) ([]v6.ImageStatus, error) { + return nil, remote.UpgradeNeededError(errors.New("ListImagesWithOptions method not implemented")) +} + func (bc baseClient) UpdateManifests(context.Context, update.Spec) (job.ID, error) { var id job.ID return id, remote.UpgradeNeededError(errors.New("UpdateManifests method not implemented")) diff --git a/remote/rpc/clientV10.go b/remote/rpc/clientV10.go new file mode 100644 index 000000000..c30b20b54 --- /dev/null +++ b/remote/rpc/clientV10.go @@ -0,0 +1,47 @@ +package rpc + +import ( + "context" + "io" + "net/rpc" + + "github.com/weaveworks/flux/api/v10" + "github.com/weaveworks/flux/api/v6" + "github.com/weaveworks/flux/remote" +) + +// RPCClientV10 is the rpc-backed implementation of a server, for +// talking to remote daemons. Version 8 has the same methods, but +// supports a different set of resource kinds to earlier versions. +type RPCClientV10 struct { + *RPCClientV9 +} + +type clientV10 interface { + v10.Server + v10.Upstream +} + +var _ clientV10 = &RPCClientV10{} + +// NewClientV10 creates a new rpc-backed implementation of the server. +func NewClientV10(conn io.ReadWriteCloser) *RPCClientV10 { + return &RPCClientV10{NewClientV9(conn)} +} + +func (p *RPCClientV10) ListImagesWithOptions(ctx context.Context, opts v10.ListImagesOptions) ([]v6.ImageStatus, error) { + var resp ListImagesResponse + if err := requireServiceSpecKinds(opts.Spec, supportedKindsV8); err != nil { + return resp.Result, remote.UnsupportedResourceKind(err) + } + + err := p.client.Call("RPCServer.ListImagesWithOptions", opts, &resp) + if err != nil { + if _, ok := err.(rpc.ServerError); !ok && err != nil { + err = remote.FatalError{err} + } + } else if resp.ApplicationError != nil { + err = resp.ApplicationError + } + return resp.Result, err +} diff --git a/remote/rpc/clientV6.go b/remote/rpc/clientV6.go index d7206af69..905df0feb 100644 --- a/remote/rpc/clientV6.go +++ b/remote/rpc/clientV6.go @@ -6,6 +6,8 @@ import ( "net/rpc" "net/rpc/jsonrpc" + "github.com/weaveworks/flux/api/v10" + "github.com/weaveworks/flux/api/v6" fluxerr "github.com/weaveworks/flux/errors" "github.com/weaveworks/flux/job" @@ -99,7 +101,7 @@ func (p *RPCClientV6) ListServices(ctx context.Context, namespace string) ([]v6. return services, err } -func (p *RPCClientV6) ListImages(ctx context.Context, spec update.ResourceSpec, opts v6.ListImagesOptions) ([]v6.ImageStatus, error) { +func (p *RPCClientV6) ListImages(ctx context.Context, spec update.ResourceSpec) ([]v6.ImageStatus, error) { var images []v6.ImageStatus if err := requireServiceSpecKinds(spec, supportedKindsV6); err != nil { return images, remote.UpgradeNeededError(err) @@ -115,6 +117,11 @@ func (p *RPCClientV6) ListImages(ctx context.Context, spec update.ResourceSpec, return images, err } +func (p *RPCClientV6) ListImagesWithOptions(ctx context.Context, opts v10.ListImagesOptions) ([]v6.ImageStatus, error) { + // TODO: Backfill list image summary fields from v10 + return p.ListImages(ctx, opts.Spec) +} + func (p *RPCClientV6) UpdateManifests(ctx context.Context, u update.Spec) (job.ID, error) { var result job.ID if err := requireSpecKinds(u, supportedKindsV6); err != nil { diff --git a/remote/rpc/clientV7.go b/remote/rpc/clientV7.go index 074b5f46d..b87a6c65d 100644 --- a/remote/rpc/clientV7.go +++ b/remote/rpc/clientV7.go @@ -62,7 +62,7 @@ func (p *RPCClientV7) ListServices(ctx context.Context, namespace string) ([]v6. return resp.Result, err } -func (p *RPCClientV7) ListImages(ctx context.Context, spec update.ResourceSpec, opts v6.ListImagesOptions) ([]v6.ImageStatus, error) { +func (p *RPCClientV7) ListImages(ctx context.Context, spec update.ResourceSpec) ([]v6.ImageStatus, error) { var resp ListImagesResponse if err := requireServiceSpecKinds(spec, supportedKindsV7); err != nil { return resp.Result, remote.UpgradeNeededError(err) diff --git a/remote/rpc/clientV8.go b/remote/rpc/clientV8.go index 76a5422db..a4933a01c 100644 --- a/remote/rpc/clientV8.go +++ b/remote/rpc/clientV8.go @@ -32,7 +32,7 @@ func NewClientV8(conn io.ReadWriteCloser) *RPCClientV8 { return &RPCClientV8{NewClientV7(conn)} } -func (p *RPCClientV8) ListImages(ctx context.Context, spec update.ResourceSpec, opts v6.ListImagesOptions) ([]v6.ImageStatus, error) { +func (p *RPCClientV8) ListImages(ctx context.Context, spec update.ResourceSpec) ([]v6.ImageStatus, error) { var resp ListImagesResponse if err := requireServiceSpecKinds(spec, supportedKindsV8); err != nil { return resp.Result, remote.UnsupportedResourceKind(err) diff --git a/remote/rpc/rpc_test.go b/remote/rpc/rpc_test.go index 7890811c0..e1dd08d88 100644 --- a/remote/rpc/rpc_test.go +++ b/remote/rpc/rpc_test.go @@ -31,7 +31,7 @@ func TestRPC(t *testing.T) { t.Fatal(err) } go server.ServeConn(serverConn) - return NewClientV9(clientConn) + return NewClientV10(clientConn) } remote.ServerTestBattery(t, wrap) } diff --git a/remote/rpc/server.go b/remote/rpc/server.go index c12905d96..4b9f9e5b9 100644 --- a/remote/rpc/server.go +++ b/remote/rpc/server.go @@ -6,6 +6,8 @@ import ( "net/rpc" "net/rpc/jsonrpc" + "github.com/weaveworks/flux/api/v10" + "github.com/pkg/errors" "github.com/weaveworks/flux/api" @@ -89,7 +91,19 @@ type ListImagesResponse struct { } func (p *RPCServer) ListImages(spec update.ResourceSpec, resp *ListImagesResponse) error { - v, err := p.s.ListImages(context.Background(), spec, v6.ListImagesOptions{}) + v, err := p.s.ListImages(context.Background(), spec) + resp.Result = v + if err != nil { + if err, ok := errors.Cause(err).(*fluxerr.Error); ok { + resp.ApplicationError = err + return nil + } + } + return err +} + +func (p *RPCServer) ListImagesWithOptions(opts v10.ListImagesOptions, resp *ListImagesResponse) error { + v, err := p.s.ListImagesWithOptions(context.Background(), opts) resp.Result = v if err != nil { if err, ok := errors.Cause(err).(*fluxerr.Error); ok { From b5c3b30bcb904c00d34120c0761d45d1eefb51db Mon Sep 17 00:00:00 2001 From: Aaron Kirkbride Date: Fri, 8 Jun 2018 16:33:41 +0100 Subject: [PATCH 2/2] Polyfill container fields filter from v10 to v6 --- api/v10/api.go | 2 +- api/v6/api.go | 17 ----- api/v6/container.go | 115 ++++++++++++++++++++++++++++++++++ api/v6/container_test.go | 130 +++++++++++++++++++++++++++++++++++++++ daemon/daemon.go | 69 ++------------------- daemon/daemon_test.go | 1 - daemon/images.go | 15 +---- daemon/images_test.go | 58 ----------------- policy/policy.go | 11 ++++ policy/policy_test.go | 52 ++++++++++++++++ remote/logging.go | 2 +- remote/rpc/clientV10.go | 4 +- remote/rpc/clientV6.go | 41 +++++++++++- 13 files changed, 356 insertions(+), 161 deletions(-) create mode 100644 api/v6/container.go create mode 100644 api/v6/container_test.go delete mode 100644 daemon/images_test.go diff --git a/api/v10/api.go b/api/v10/api.go index 02d9c0ae9..1819d273a 100644 --- a/api/v10/api.go +++ b/api/v10/api.go @@ -1,4 +1,4 @@ -// This package defines the types for Flux API version 9. +// This package defines the types for Flux API version 10. package v10 import ( diff --git a/api/v6/api.go b/api/v6/api.go index 42f4ad78d..1a9349b8b 100644 --- a/api/v6/api.go +++ b/api/v6/api.go @@ -5,7 +5,6 @@ import ( "github.com/weaveworks/flux" "github.com/weaveworks/flux/git" - "github.com/weaveworks/flux/image" "github.com/weaveworks/flux/job" "github.com/weaveworks/flux/ssh" "github.com/weaveworks/flux/update" @@ -42,22 +41,6 @@ type ControllerStatus struct { Policies map[string]string } -type Container struct { - Name string `json:",omitempty"` - Current image.Info `json:",omitempty"` - LatestFiltered image.Info `json:",omitempty"` - - // All available images (ignoring tag filters) - Available []image.Info `json:",omitempty"` - AvailableError string `json:",omitempty"` - AvailableImagesCount int `json:",omitempty"` - NewAvailableImagesCount int `json:",omitempty"` - - // Filtered available images (matching tag filters) - FilteredImagesCount int `json:",omitempty"` - NewFilteredImagesCount int `json:",omitempty"` -} - // --- config types type GitRemoteConfig struct { diff --git a/api/v6/container.go b/api/v6/container.go new file mode 100644 index 000000000..b09bdda8c --- /dev/null +++ b/api/v6/container.go @@ -0,0 +1,115 @@ +package v6 + +import ( + "github.com/pkg/errors" + "github.com/weaveworks/flux/image" + "github.com/weaveworks/flux/registry" + "github.com/weaveworks/flux/update" +) + +// Container describes an individual container including current image info and +// available images. +type Container struct { + Name string `json:",omitempty"` + Current image.Info `json:",omitempty"` + LatestFiltered image.Info `json:",omitempty"` + + // All available images (ignoring tag filters) + Available []image.Info `json:",omitempty"` + AvailableError string `json:",omitempty"` + AvailableImagesCount int `json:",omitempty"` + NewAvailableImagesCount int `json:",omitempty"` + + // Filtered available images (matching tag filters) + FilteredImagesCount int `json:",omitempty"` + NewFilteredImagesCount int `json:",omitempty"` +} + +// NewContainer creates a Container given a list of images and the current image +func NewContainer(name string, images update.ImageInfos, currentImage image.Info, tagPattern string, fields []string) (Container, error) { + // All images + imagesCount := len(images) + imagesErr := "" + if images == nil { + imagesErr = registry.ErrNoImageData.Error() + } + var newImages []image.Info + for _, img := range images { + if img.CreatedAt.After(currentImage.CreatedAt) { + newImages = append(newImages, img) + } + } + newImagesCount := len(newImages) + + // Filtered images + filteredImages := images.Filter(tagPattern) + filteredImagesCount := len(filteredImages) + var newFilteredImages []image.Info + for _, img := range filteredImages { + if img.CreatedAt.After(currentImage.CreatedAt) { + newFilteredImages = append(newFilteredImages, img) + } + } + newFilteredImagesCount := len(newFilteredImages) + latestFiltered, _ := filteredImages.Latest() + + container := Container{ + Name: name, + Current: currentImage, + LatestFiltered: latestFiltered, + + Available: images, + AvailableError: imagesErr, + AvailableImagesCount: imagesCount, + NewAvailableImagesCount: newImagesCount, + FilteredImagesCount: filteredImagesCount, + NewFilteredImagesCount: newFilteredImagesCount, + } + return filterContainerFields(container, fields) +} + +// filterContainerFields returns a new container with only the fields specified. If not fields are specified, +// a list of default fields is used. +func filterContainerFields(container Container, fields []string) (Container, error) { + // Default fields + if len(fields) == 0 { + fields = []string{ + "Name", + "Current", + "LatestFiltered", + "Available", + "AvailableError", + "AvailableImagesCount", + "NewAvailableImagesCount", + "FilteredImagesCount", + "NewFilteredImagesCount", + } + } + + var c Container + for _, field := range fields { + switch field { + case "Name": + c.Name = container.Name + case "Current": + c.Current = container.Current + case "LatestFiltered": + c.LatestFiltered = container.LatestFiltered + case "Available": + c.Available = container.Available + case "AvailableError": + c.AvailableError = container.AvailableError + case "AvailableImagesCount": + c.AvailableImagesCount = container.AvailableImagesCount + case "NewAvailableImagesCount": + c.NewAvailableImagesCount = container.NewAvailableImagesCount + case "FilteredImagesCount": + c.FilteredImagesCount = container.FilteredImagesCount + case "NewFilteredImagesCount": + c.NewFilteredImagesCount = container.NewFilteredImagesCount + default: + return c, errors.Errorf("%s is an invalid field", field) + } + } + return c, nil +} diff --git a/api/v6/container_test.go b/api/v6/container_test.go new file mode 100644 index 000000000..57ebaaa5d --- /dev/null +++ b/api/v6/container_test.go @@ -0,0 +1,130 @@ +package v6 + +import ( + "reflect" + "testing" + + "github.com/weaveworks/flux/image" + "github.com/weaveworks/flux/update" +) + +func TestNewContainer(t *testing.T) { + + testImage := image.Info{ImageID: "test"} + + type args struct { + name string + images update.ImageInfos + currentImage image.Info + tagPattern string + fields []string + } + tests := []struct { + name string + args args + want Container + wantErr bool + }{ + { + name: "Simple", + args: args{ + name: "container1", + images: update.ImageInfos{testImage}, + currentImage: testImage, + tagPattern: "*", + }, + want: Container{ + Name: "container1", + Current: testImage, + LatestFiltered: testImage, + Available: []image.Info{testImage}, + AvailableImagesCount: 1, + NewAvailableImagesCount: 0, + FilteredImagesCount: 1, + NewFilteredImagesCount: 0, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := NewContainer(tt.args.name, tt.args.images, tt.args.currentImage, tt.args.tagPattern, tt.args.fields) + if (err != nil) != tt.wantErr { + t.Errorf("NewContainer() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("NewContainer() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestFilterContainerFields(t *testing.T) { + testContainer := Container{ + Name: "test", + Current: image.Info{ImageID: "123"}, + LatestFiltered: image.Info{ImageID: "123"}, + Available: []image.Info{{ImageID: "123"}}, + AvailableError: "test", + AvailableImagesCount: 1, + NewAvailableImagesCount: 2, + FilteredImagesCount: 3, + NewFilteredImagesCount: 4, + } + + type args struct { + container Container + fields []string + } + tests := []struct { + name string + args args + want Container + wantErr bool + }{ + { + name: "Default fields", + args: args{ + container: testContainer, + }, + want: testContainer, + wantErr: false, + }, + { + name: "Filter", + args: args{ + container: testContainer, + fields: []string{"Name", "Available", "NewAvailableImagesCount", "NewFilteredImagesCount"}, + }, + want: Container{ + Name: "test", + Available: []image.Info{{ImageID: "123"}}, + NewAvailableImagesCount: 2, + NewFilteredImagesCount: 4, + }, + wantErr: false, + }, + { + name: "Invalid field", + args: args{ + container: testContainer, + fields: []string{"Invalid"}, + }, + want: Container{}, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := filterContainerFields(tt.args.container, tt.args.fields) + if (err != nil) != tt.wantErr { + t.Errorf("FilterContainerFields() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("FilterContainerFields() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/daemon/daemon.go b/daemon/daemon.go index 06b250d31..82fdacfcf 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -572,77 +572,16 @@ func containers2containers(cs []resource.Container) []v6.Container { } func getServiceContainers(service cluster.Controller, imageRepos update.ImageRepos, policyResourceMap policy.ResourceMap, fields []string) (res []v6.Container, err error) { - if len(fields) == 0 { - fields = []string{ - "Name", - "Current", - "LatestFiltered", - "Available", - "AvailableError", - "AvailableImagesCount", - "NewAvailableImagesCount", - "FilteredImagesCount", - "NewFilteredImagesCount", - } - } - for _, c := range service.ContainersOrNil() { - var container v6.Container - imageRepo := c.Image.Name - tagPattern := getTagPattern(policyResourceMap, service.ID, c.Name) + tagPattern := policy.GetTagPattern(policyResourceMap, service.ID, c.Name) images := imageRepos.GetRepoImages(imageRepo) currentImage := images.FindWithRef(c.Image) - // All images - imagesCount := len(images) - imagesErr := "" - if images == nil { - imagesErr = registry.ErrNoImageData.Error() - } - var newImages []image.Info - for _, img := range images { - if img.CreatedAt.After(currentImage.CreatedAt) { - newImages = append(newImages, img) - } - } - newImagesCount := len(newImages) - - // Filtered images - filteredImages := images.Filter(tagPattern) - filteredImagesCount := len(filteredImages) - var newFilteredImages []image.Info - for _, img := range filteredImages { - if img.CreatedAt.After(currentImage.CreatedAt) { - newFilteredImages = append(newFilteredImages, img) - } - } - newFilteredImagesCount := len(newFilteredImages) - - for _, field := range fields { - switch field { - case "Name": - container.Name = c.Name - case "Current": - container.Current = currentImage - case "LatestFiltered": - container.LatestFiltered, _ = filteredImages.Latest() - case "Available": - container.Available = images - case "AvailableError": - container.AvailableError = imagesErr - case "AvailableImagesCount": - container.AvailableImagesCount = imagesCount - case "NewAvailableImagesCount": - container.NewAvailableImagesCount = newImagesCount - case "FilteredImagesCount": - container.FilteredImagesCount = filteredImagesCount - case "NewFilteredImagesCount": - container.NewFilteredImagesCount = newFilteredImagesCount - default: - return nil, errors.Errorf("%s is an invalid field", field) - } + container, err := v6.NewContainer(c.Name, images, currentImage, tagPattern, fields) + if err != nil { + return res, err } res = append(res, container) } diff --git a/daemon/daemon_test.go b/daemon/daemon_test.go index 4590edd05..5341a9ee8 100644 --- a/daemon/daemon_test.go +++ b/daemon/daemon_test.go @@ -13,7 +13,6 @@ import ( "github.com/go-kit/kit/log" "github.com/stretchr/testify/assert" - "github.com/weaveworks/flux" "github.com/weaveworks/flux/api/v10" "github.com/weaveworks/flux/api/v6" diff --git a/daemon/images.go b/daemon/images.go index eb933b5c4..e72aa6f63 100644 --- a/daemon/images.go +++ b/daemon/images.go @@ -2,12 +2,10 @@ package daemon import ( "context" - "strings" "github.com/go-kit/kit/log" "github.com/pkg/errors" - "github.com/weaveworks/flux" "github.com/weaveworks/flux/git" "github.com/weaveworks/flux/policy" "github.com/weaveworks/flux/update" @@ -51,7 +49,7 @@ func (d *Daemon) pollForNewImages(logger log.Logger) { continue } - pattern := getTagPattern(candidateServicesPolicyMap, service.ID, container.Name) + pattern := policy.GetTagPattern(candidateServicesPolicyMap, service.ID, container.Name) repo := currentImageID.Name logger.Log("repo", repo, "pattern", pattern) @@ -74,17 +72,6 @@ func (d *Daemon) pollForNewImages(logger log.Logger) { } } -func getTagPattern(services policy.ResourceMap, service flux.ResourceID, container string) string { - if services == nil { - return "*" - } - policies := services[service] - if pattern, ok := policies.Get(policy.TagPrefix(container)); ok { - return strings.TrimPrefix(pattern, "glob:") - } - return "*" -} - // getUnlockedAutomatedServicesPolicyMap returns a resource policy map for all unlocked automated services func (d *Daemon) getUnlockedAutomatedServicesPolicyMap(ctx context.Context) (policy.ResourceMap, error) { var services policy.ResourceMap diff --git a/daemon/images_test.go b/daemon/images_test.go deleted file mode 100644 index cc2da131c..000000000 --- a/daemon/images_test.go +++ /dev/null @@ -1,58 +0,0 @@ -package daemon - -import ( - "fmt" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/weaveworks/flux" - "github.com/weaveworks/flux/policy" -) - -func Test_getTagPattern(t *testing.T) { - resourceID, err := flux.ParseResourceID("default:deployment/helloworld") - assert.NoError(t, err) - container := "helloContainer" - - type args struct { - services policy.ResourceMap - service flux.ResourceID - container string - } - tests := []struct { - name string - args args - want string - }{ - { - name: "Nil policies", - args: args{services: nil}, - want: "*", - }, - { - name: "No match", - args: args{services: policy.ResourceMap{}}, - want: "*", - }, - { - name: "Match", - args: args{ - services: policy.ResourceMap{ - resourceID: policy.Set{ - policy.Policy(fmt.Sprintf("tag.%s", container)): "glob:master-*", - }, - }, - service: resourceID, - container: container, - }, - want: "master-*", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := getTagPattern(tt.args.services, tt.args.service, tt.args.container); got != tt.want { - t.Errorf("getTagPattern() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/policy/policy.go b/policy/policy.go index 856fd1b02..105294fb7 100644 --- a/policy/policy.go +++ b/policy/policy.go @@ -36,6 +36,17 @@ func Tag(policy Policy) bool { return strings.HasPrefix(string(policy), "tag.") } +func GetTagPattern(services ResourceMap, service flux.ResourceID, container string) string { + if services == nil { + return "*" + } + policies := services[service] + if pattern, ok := policies.Get(TagPrefix(container)); ok { + return strings.TrimPrefix(pattern, "glob:") + } + return "*" +} + type Updates map[flux.ResourceID]Update type Update struct { diff --git a/policy/policy_test.go b/policy/policy_test.go index 914cd6c95..ac8e2863a 100644 --- a/policy/policy_test.go +++ b/policy/policy_test.go @@ -2,8 +2,12 @@ package policy import ( "encoding/json" + "fmt" "reflect" "testing" + + "github.com/stretchr/testify/assert" + "github.com/weaveworks/flux" ) func TestJSON(t *testing.T) { @@ -45,3 +49,51 @@ func TestJSON(t *testing.T) { t.Errorf("Parsing equivalent list did not preserve policy. Expected:\n%#v\nGot:\n%#v\n", policy, policy2) } } + +func Test_GetTagPattern(t *testing.T) { + resourceID, err := flux.ParseResourceID("default:deployment/helloworld") + assert.NoError(t, err) + container := "helloContainer" + + type args struct { + services ResourceMap + service flux.ResourceID + container string + } + tests := []struct { + name string + args args + want string + }{ + { + name: "Nil policies", + args: args{services: nil}, + want: "*", + }, + { + name: "No match", + args: args{services: ResourceMap{}}, + want: "*", + }, + { + name: "Match", + args: args{ + services: ResourceMap{ + resourceID: Set{ + Policy(fmt.Sprintf("tag.%s", container)): "glob:master-*", + }, + }, + service: resourceID, + container: container, + }, + want: "master-*", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := GetTagPattern(tt.args.services, tt.args.service, tt.args.container); got != tt.want { + t.Errorf("GetTagPattern() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/remote/logging.go b/remote/logging.go index bc63fe455..ef64c4166 100644 --- a/remote/logging.go +++ b/remote/logging.go @@ -56,7 +56,7 @@ func (p *ErrorLoggingServer) ListImages(ctx context.Context, spec update.Resourc func (p *ErrorLoggingServer) ListImagesWithOptions(ctx context.Context, opts v10.ListImagesOptions) (_ []v6.ImageStatus, err error) { defer func() { if err != nil { - p.logger.Log("method", "ListImages", "error", err) + p.logger.Log("method", "ListImagesWithOptions", "error", err) } }() return p.server.ListImagesWithOptions(ctx, opts) diff --git a/remote/rpc/clientV10.go b/remote/rpc/clientV10.go index c30b20b54..76bafc626 100644 --- a/remote/rpc/clientV10.go +++ b/remote/rpc/clientV10.go @@ -11,8 +11,8 @@ import ( ) // RPCClientV10 is the rpc-backed implementation of a server, for -// talking to remote daemons. Version 8 has the same methods, but -// supports a different set of resource kinds to earlier versions. +// talking to remote daemons. This version introduces methods which accept an +// options struct as the first argument. e.g. ListImagesWithOptions type RPCClientV10 struct { *RPCClientV9 } diff --git a/remote/rpc/clientV6.go b/remote/rpc/clientV6.go index 905df0feb..2846c052c 100644 --- a/remote/rpc/clientV6.go +++ b/remote/rpc/clientV6.go @@ -7,6 +7,7 @@ import ( "net/rpc/jsonrpc" "github.com/weaveworks/flux/api/v10" + "github.com/weaveworks/flux/policy" "github.com/weaveworks/flux/api/v6" fluxerr "github.com/weaveworks/flux/errors" @@ -118,8 +119,44 @@ func (p *RPCClientV6) ListImages(ctx context.Context, spec update.ResourceSpec) } func (p *RPCClientV6) ListImagesWithOptions(ctx context.Context, opts v10.ListImagesOptions) ([]v6.ImageStatus, error) { - // TODO: Backfill list image summary fields from v10 - return p.ListImages(ctx, opts.Spec) + images, err := p.ListImages(ctx, opts.Spec) + if err != nil { + return images, err + } + + var ns string + if opts.Spec != update.ResourceSpecAll { + resourceID, err := opts.Spec.AsID() + if err != nil { + return images, err + } + ns, _, _ = resourceID.Components() + } + services, err := p.ListServices(ctx, ns) + + policyMap := make(policy.ResourceMap) + for _, service := range services { + var s policy.Set + for k, v := range service.Policies { + s[policy.Policy(k)] = v + } + policyMap[service.ID] = s + } + + // Polyfill container fields from v10 + for i, image := range images { + for j, container := range image.Containers { + tagPattern := policy.GetTagPattern(policyMap, image.ID, container.Name) + // Create a new container using the same function used in v10 + newContainer, err := v6.NewContainer(container.Name, container.Available, container.Current, tagPattern, opts.OverrideContainerFields) + if err != nil { + return images, err + } + images[i].Containers[j] = newContainer + } + } + + return images, nil } func (p *RPCClientV6) UpdateManifests(ctx context.Context, u update.Spec) (job.ID, error) {