diff --git a/Gopkg.lock b/Gopkg.lock index c488f6ea5..99c6130d5 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -730,6 +730,7 @@ packages = [ ".", "api", + "api/v10", "api/v6", "api/v9", "cluster", @@ -754,7 +755,7 @@ "ssh", "update" ] - revision = "1fc5294eb70e0353b8c7b0140fc15feb7560f52b" + revision = "03d1eeddc40e5798c94764b4c334325149e00052" [[projects]] branch = "master" @@ -991,6 +992,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "1768b90ca328e133c8bd0884bc4fcb8d802bad3a1b1916c40456eb35074ee545" + inputs-digest = "24b5ba3fac514c1cf1ab22be88ba72982c6210c96e97950b9cec1c8cbd11e0fd" solver-name = "gps-cdcl" solver-version = 1 diff --git a/flux-api/bus/nats/bus.go b/flux-api/bus/nats/bus.go index 46622fce8..514f3b830 100644 --- a/flux-api/bus/nats/bus.go +++ b/flux-api/bus/nats/bus.go @@ -9,6 +9,7 @@ import ( "github.com/nats-io/go-nats" "github.com/weaveworks/flux/api" + "github.com/weaveworks/flux/api/v10" "github.com/weaveworks/flux/api/v6" "github.com/weaveworks/flux/api/v9" fluxerr "github.com/weaveworks/flux/errors" @@ -29,17 +30,18 @@ const ( presenceTick = 50 * time.Millisecond encoderType = nats.JSON_ENCODER - methodKick = ".Platform.Kick" - methodPing = ".Platform.Ping" - methodVersion = ".Platform.Version" - methodExport = ".Platform.Export" - methodListServices = ".Platform.ListServices" - methodListImages = ".Platform.ListImages" - methodJobStatus = ".Platform.JobStatus" - methodSyncStatus = ".Platform.SyncStatus" - methodUpdateManifests = ".Platform.UpdateManifests" - methodGitRepoConfig = ".Platform.GitRepoConfig" - methodNotifyChange = ".Platform.NotifyChange" + methodKick = ".Platform.Kick" + methodPing = ".Platform.Ping" + methodVersion = ".Platform.Version" + methodExport = ".Platform.Export" + methodListServices = ".Platform.ListServices" + methodListImages = ".Platform.ListImages" + methodListImagesWithOptions = ".Platform.ListImagesWithOptions" + methodJobStatus = ".Platform.JobStatus" + methodSyncStatus = ".Platform.SyncStatus" + methodUpdateManifests = ".Platform.UpdateManifests" + methodGitRepoConfig = ".Platform.GitRepoConfig" + methodNotifyChange = ".Platform.NotifyChange" ) var ( @@ -153,6 +155,12 @@ type ListImagesResponse struct { ErrorResponse `json:",omitempty"` } +// ListImagesWithOptionsResponse is the ListImagesWithOptions response. +type ListImagesWithOptionsResponse struct { + Result []v6.ImageStatus + ErrorResponse `json:",omitempty"` +} + // UpdateManifestsResponse is the UpdateManifests response. type UpdateManifestsResponse struct { Result job.ID @@ -265,6 +273,16 @@ func (r *natsPlatform) ListImages(ctx context.Context, spec update.ResourceSpec) return response.Result, extractError(response.ErrorResponse) } +func (r *natsPlatform) ListImagesWithOptions(ctx context.Context, opts v10.ListImagesOptions) ([]v6.ImageStatus, error) { + var response ListImagesWithOptionsResponse + ctx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + if err := r.conn.RequestWithContext(ctx, r.instance+methodListImagesWithOptions, opts, &response); err != nil { + return response.Result, remote.UnavailableError(err) + } + return response.Result, extractError(response.ErrorResponse) +} + func (r *natsPlatform) UpdateManifests(ctx context.Context, u update.Spec) (job.ID, error) { var response UpdateManifestsResponse ctx, cancel := context.WithTimeout(ctx, timeout) @@ -403,6 +421,8 @@ func (n *NATS) processRequest(ctx context.Context, request *nats.Msg, instID ser err = n.processListServices(ctx, request, platform) case strings.HasSuffix(request.Subject, methodListImages): err = n.processListImages(ctx, request, platform) + case strings.HasSuffix(request.Subject, methodListImagesWithOptions): + err = n.processListImagesWithOptions(ctx, request, platform) case strings.HasSuffix(request.Subject, methodUpdateManifests): err = n.processUpdateManifests(ctx, request, platform) case strings.HasSuffix(request.Subject, methodJobStatus): @@ -495,6 +515,19 @@ func (n *NATS) processListImages(ctx context.Context, request *nats.Msg, platfor return err } +func (n *NATS) processListImagesWithOptions(ctx context.Context, request *nats.Msg, platform api.UpstreamServer) error { + var ( + req v10.ListImagesOptions + res []v6.ImageStatus + ) + err := encoder.Decode(request.Subject, request.Data, &req) + if err == nil { + res, err = platform.ListImagesWithOptions(ctx, req) + } + n.enc.Publish(request.Reply, ListImagesWithOptionsResponse{res, makeErrorResponse(err)}) + return err +} + func (n *NATS) processUpdateManifests(ctx context.Context, request *nats.Msg, platform api.UpstreamServer) error { var ( req update.Spec diff --git a/flux-api/http/server.go b/flux-api/http/server.go index aa8ae50b0..047da576a 100644 --- a/flux-api/http/server.go +++ b/flux-api/http/server.go @@ -21,6 +21,7 @@ import ( "github.com/weaveworks/flux" fluxapi "github.com/weaveworks/flux/api" + "github.com/weaveworks/flux/api/v10" "github.com/weaveworks/flux/api/v9" fluxerr "github.com/weaveworks/flux/errors" "github.com/weaveworks/flux/event" @@ -106,6 +107,8 @@ func (s Server) MakeHandler(r *mux.Router) http.Handler { transport.SyncStatus: s.syncStatus, transport.JobStatus: s.jobStatus, transport.GitRepoConfig: s.gitRepoConfig, + // flux/api/ServerV10 + transport.ListImagesWithOptions: s.listImagesWithOptions, // fluxctl legacy routes transport.UpdateImages: s.updateImages, transport.UpdatePolicies: s.updatePolicies, @@ -151,7 +154,9 @@ func (s Server) listServices(w http.ResponseWriter, r *http.Request) { func (s Server) listImages(w http.ResponseWriter, r *http.Request) { ctx := getRequestContext(r) - service := mux.Vars(r)["service"] + queryValues := r.URL.Query() + service := queryValues.Get("service") + spec, err := update.ParseResourceSpec(service) if err != nil { transport.WriteError(w, r, http.StatusBadRequest, errors.Wrapf(err, "parsing service spec %q", service)) @@ -167,6 +172,38 @@ func (s Server) listImages(w http.ResponseWriter, r *http.Request) { transport.JSONResponse(w, r, d) } +func (s Server) listImagesWithOptions(w http.ResponseWriter, r *http.Request) { + var opts v10.ListImagesOptions + ctx := getRequestContext(r) + queryValues := r.URL.Query() + + // service - Select services to update. + service := queryValues.Get("service") + if service == "" { + service = string(update.ResourceSpecAll) + } + spec, err := update.ParseResourceSpec(service) + if err != nil { + 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. + containerFields := queryValues.Get("containerFields") + if containerFields != "" { + opts.OverrideContainerFields = strings.Split(containerFields, ",") + } + + d, err := s.daemonProxy.ListImagesWithOptions(ctx, opts) + if err != nil { + transport.ErrorResponse(w, r, err) + return + } + + transport.JSONResponse(w, r, d) +} + func (s Server) updateManifests(w http.ResponseWriter, r *http.Request) { ctx := getRequestContext(r) diff --git a/flux-api/main_test.go b/flux-api/main_test.go index 1d994f032..3c60dca13 100644 --- a/flux-api/main_test.go +++ b/flux-api/main_test.go @@ -12,6 +12,8 @@ import ( "testing" "time" + "github.com/weaveworks/flux/api/v10" + "github.com/go-kit/kit/log" "github.com/gorilla/mux" @@ -193,20 +195,6 @@ func TestFluxsvc_ListServices(t *testing.T) { if svcs[0].ID.String() != helloWorldSvc && svcs[1].ID.String() != helloWorldSvc { t.Errorf("Expected one of the services to be %q", helloWorldSvc) } - - // Test that `namespace` argument is mandatory - u, err := transport.MakeURL(ts.URL, router, "ListServices") - if err != nil { - t.Fatal(err) - } - resp, err := http.Get(u.String()) - if err != nil { - t.Fatal(err) - } - defer resp.Body.Close() - if resp.StatusCode != http.StatusNotFound { - t.Fatalf("Request should result in 404, but got: %q", resp.Status) - } } // Note that this test will reach out to docker hub to check the images @@ -240,19 +228,38 @@ func TestFluxsvc_ListImages(t *testing.T) { if len(imgs[0].Containers) == 0 { t.Error("Expected >1 containers") } +} + +// Note that this test will reach out to docker hub to check the images +// associated with alpine +func TestFluxsvc_ListImagesWithOptions(t *testing.T) { + setup(t) + defer teardown() - // Test that `service` argument is mandatory - u, err := transport.MakeURL(ts.URL, router, "ListImages") + ctx := context.Background() + + // Test ListImagesWithOptions + imgs, err := apiClient.ListImagesWithOptions(ctx, v10.ListImagesOptions{Spec: update.ResourceSpecAll}) if err != nil { t.Fatal(err) } - resp, err := http.Get(u.String()) + if len(imgs) != 2 { + t.Error("Expected there two sets of images") + } + if len(imgs[0].Containers) == 0 && len(imgs[1].Containers) == 0 { + t.Error("Should have been lots of containers") + } + + // Test ListImagesWithOptions for specific service + imgs, err = apiClient.ListImagesWithOptions(ctx, v10.ListImagesOptions{Spec: helloWorldSvc}) if err != nil { t.Fatal(err) } - defer resp.Body.Close() - if resp.StatusCode != http.StatusNotFound { - t.Fatalf("Request should result in 404, but got: %s", resp.Status) + if len(imgs) != 2 { + t.Error("Expected two sets of images") + } + if len(imgs[0].Containers) == 0 { + t.Error("Expected >1 containers") } } diff --git a/flux-api/server/server.go b/flux-api/server/server.go index d53e1a2d4..25aef2413 100644 --- a/flux-api/server/server.go +++ b/flux-api/server/server.go @@ -6,6 +6,8 @@ import ( "sync/atomic" "time" + "github.com/weaveworks/flux/api/v10" + "github.com/go-kit/kit/log" "github.com/pkg/errors" @@ -179,6 +181,20 @@ func (s *Server) ListImages(ctx context.Context, spec update.ResourceSpec) (res return inst.Platform.ListImages(ctx, spec) } +// ListImagesWithOptions calls ListImagesWithOptions on the given instance. +func (s *Server) ListImagesWithOptions(ctx context.Context, opts v10.ListImagesOptions) (res []v6.ImageStatus, err error) { + instID, err := getInstanceID(ctx) + if err != nil { + return res, err + } + + inst, err := s.instancer.Get(instID) + if err != nil { + return nil, errors.Wrapf(err, "getting instance "+string(instID)) + } + return inst.Platform.ListImagesWithOptions(ctx, opts) +} + // UpdateImages updates images on the given instance. func (s *Server) UpdateImages(ctx context.Context, spec update.ReleaseSpec, cause update.Cause) (job.ID, error) { instID, err := getInstanceID(ctx) diff --git a/vendor/github.com/weaveworks/flux/api/api.go b/vendor/github.com/weaveworks/flux/api/api.go index b3282dbe9..c90ba502c 100644 --- a/vendor/github.com/weaveworks/flux/api/api.go +++ b/vendor/github.com/weaveworks/flux/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/vendor/github.com/weaveworks/flux/api/v10/api.go b/vendor/github.com/weaveworks/flux/api/v10/api.go new file mode 100644 index 000000000..1819d273a --- /dev/null +++ b/vendor/github.com/weaveworks/flux/api/v10/api.go @@ -0,0 +1,25 @@ +// This package defines the types for Flux API version 10. +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/vendor/github.com/weaveworks/flux/api/v6/api.go b/vendor/github.com/weaveworks/flux/api/v6/api.go index 9befabd71..1a9349b8b 100644 --- a/vendor/github.com/weaveworks/flux/api/v6/api.go +++ b/vendor/github.com/weaveworks/flux/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" @@ -35,19 +34,13 @@ type ControllerStatus struct { Containers []Container ReadOnly ReadOnlyReason Status string + Labels map[string]string Automated bool Locked bool Ignore bool Policies map[string]string } -type Container struct { - Name string - Current image.Info - Available []image.Info - AvailableError string `json:",omitempty"` -} - // --- config types type GitRemoteConfig struct { @@ -72,7 +65,7 @@ type NotDeprecated interface { // v6 ListServices(ctx context.Context, namespace string) ([]ControllerStatus, error) - ListImages(context.Context, update.ResourceSpec) ([]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/vendor/github.com/weaveworks/flux/api/v6/container.go b/vendor/github.com/weaveworks/flux/api/v6/container.go new file mode 100644 index 000000000..b09bdda8c --- /dev/null +++ b/vendor/github.com/weaveworks/flux/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/vendor/github.com/weaveworks/flux/cluster/cluster.go b/vendor/github.com/weaveworks/flux/cluster/cluster.go index 154e5cc9a..0a3952df0 100644 --- a/vendor/github.com/weaveworks/flux/cluster/cluster.go +++ b/vendor/github.com/weaveworks/flux/cluster/cluster.go @@ -34,6 +34,7 @@ type Controller struct { // control of the platform. In the case of Kubernetes, we simply // omit these controllers; but this may not always be the case. IsSystem bool + Labels map[string]string Containers ContainersOrExcuse } diff --git a/vendor/github.com/weaveworks/flux/cluster/manifests.go b/vendor/github.com/weaveworks/flux/cluster/manifests.go index 643bd0852..5e95e75af 100644 --- a/vendor/github.com/weaveworks/flux/cluster/manifests.go +++ b/vendor/github.com/weaveworks/flux/cluster/manifests.go @@ -18,9 +18,8 @@ type Manifests interface { // which services. // FIXME(michael): remove when redundant FindDefinedServices(path string) (map[flux.ResourceID][]string, error) - // Update the definitions in a manifests bytes according to the - // spec given. - UpdateDefinition(def []byte, container string, newImageID image.Ref) ([]byte, error) + // Update the image in a manifest's bytes to that given + UpdateImage(def []byte, resourceID flux.ResourceID, container string, newImageID image.Ref) ([]byte, error) // Load all the resource manifests under the path given. `baseDir` // is used to relativise the paths, which are supplied as absolute // paths to directories or files; at least one path must be @@ -29,7 +28,7 @@ type Manifests interface { // Parse the manifests given in an exported blob ParseManifests([]byte) (map[string]resource.Resource, error) // UpdatePolicies modifies a manifest to apply the policy update specified - UpdatePolicies([]byte, policy.Update) ([]byte, error) + UpdatePolicies([]byte, flux.ResourceID, policy.Update) ([]byte, error) // ServicesWithPolicies returns all services with their associated policies ServicesWithPolicies(path string) (policy.ResourceMap, error) } diff --git a/vendor/github.com/weaveworks/flux/cluster/mock.go b/vendor/github.com/weaveworks/flux/cluster/mock.go index 5f14f16ea..2bbd7a5e2 100644 --- a/vendor/github.com/weaveworks/flux/cluster/mock.go +++ b/vendor/github.com/weaveworks/flux/cluster/mock.go @@ -17,11 +17,11 @@ type Mock struct { SyncFunc func(SyncDef) error PublicSSHKeyFunc func(regenerate bool) (ssh.PublicKey, error) FindDefinedServicesFunc func(path string) (map[flux.ResourceID][]string, error) - UpdateDefinitionFunc func(def []byte, container string, newImageID image.Ref) ([]byte, error) + UpdateImageFunc func(def []byte, id flux.ResourceID, container string, newImageID image.Ref) ([]byte, error) LoadManifestsFunc func(base, first string, rest ...string) (map[string]resource.Resource, error) ParseManifestsFunc func([]byte) (map[string]resource.Resource, error) UpdateManifestFunc func(path, resourceID string, f func(def []byte) ([]byte, error)) error - UpdatePoliciesFunc func([]byte, policy.Update) ([]byte, error) + UpdatePoliciesFunc func([]byte, flux.ResourceID, policy.Update) ([]byte, error) ServicesWithPoliciesFunc func(path string) (policy.ResourceMap, error) } @@ -53,8 +53,8 @@ func (m *Mock) FindDefinedServices(path string) (map[flux.ResourceID][]string, e return m.FindDefinedServicesFunc(path) } -func (m *Mock) UpdateDefinition(def []byte, container string, newImageID image.Ref) ([]byte, error) { - return m.UpdateDefinitionFunc(def, container, newImageID) +func (m *Mock) UpdateImage(def []byte, id flux.ResourceID, container string, newImageID image.Ref) ([]byte, error) { + return m.UpdateImageFunc(def, id, container, newImageID) } func (m *Mock) LoadManifests(base, first string, rest ...string) (map[string]resource.Resource, error) { @@ -69,8 +69,8 @@ func (m *Mock) UpdateManifest(path string, resourceID string, f func(def []byte) return m.UpdateManifestFunc(path, resourceID, f) } -func (m *Mock) UpdatePolicies(def []byte, p policy.Update) ([]byte, error) { - return m.UpdatePoliciesFunc(def, p) +func (m *Mock) UpdatePolicies(def []byte, id flux.ResourceID, p policy.Update) ([]byte, error) { + return m.UpdatePoliciesFunc(def, id, p) } func (m *Mock) ServicesWithPolicies(path string) (policy.ResourceMap, error) { diff --git a/vendor/github.com/weaveworks/flux/git/repo.go b/vendor/github.com/weaveworks/flux/git/repo.go index ed03e7c31..456a82539 100644 --- a/vendor/github.com/weaveworks/flux/git/repo.go +++ b/vendor/github.com/weaveworks/flux/git/repo.go @@ -128,6 +128,14 @@ func (r *Repo) Notify() { } } +// refreshed indicates that the repo has successfully fetched from upstream. +func (r *Repo) refreshed() { + select { + case r.C <- struct{}{}: + default: + } +} + // errorIfNotReady returns the appropriate error if the repo is not // ready, and `nil` otherwise. func (r *Repo) errorIfNotReady() error { @@ -222,6 +230,9 @@ func (r *Repo) Start(shutdown <-chan struct{}, done *sync.WaitGroup) error { cancel() if err == nil { r.setStatus(RepoReady, nil) + // Treat every transition to ready as a refresh, so + // that any listeners can respond in the same way. + r.refreshed() continue // with new status, skipping timer } r.setStatus(RepoCloned, err) @@ -257,10 +268,7 @@ func (r *Repo) Refresh(ctx context.Context) error { if err := r.fetch(ctx); err != nil { return err } - select { - case r.C <- struct{}{}: - default: - } + r.refreshed() return nil } diff --git a/vendor/github.com/weaveworks/flux/http/client/client.go b/vendor/github.com/weaveworks/flux/http/client/client.go index ccafac535..61566bc25 100644 --- a/vendor/github.com/weaveworks/flux/http/client/client.go +++ b/vendor/github.com/weaveworks/flux/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" @@ -63,6 +64,12 @@ func (c *Client) ListImages(ctx context.Context, s update.ResourceSpec) ([]v6.Im 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 +} + func (c *Client) JobStatus(ctx context.Context, jobID job.ID) (job.Status, error) { var res job.Status err := c.Get(ctx, &res, transport.JobStatus, "id", string(jobID)) @@ -210,7 +217,7 @@ func (c *Client) executeRequest(req *http.Request) (*http.Response, error) { if err := json.Unmarshal(body, &niceError); err != nil { return resp, errors.Wrap(err, "decoding response body of error") } - // just in case it's JSON but not one of our own errors + // just in case it's JSON but not one of our own errors if niceError.Err != nil { return resp, &niceError } diff --git a/vendor/github.com/weaveworks/flux/http/daemon/server.go b/vendor/github.com/weaveworks/flux/http/daemon/server.go index 5f22231e3..57013946f 100644 --- a/vendor/github.com/weaveworks/flux/http/daemon/server.go +++ b/vendor/github.com/weaveworks/flux/http/daemon/server.go @@ -3,6 +3,7 @@ package daemon import ( "encoding/json" "net/http" + "strings" "github.com/gorilla/mux" "github.com/pkg/errors" @@ -11,6 +12,7 @@ import ( "github.com/weaveworks/flux" "github.com/weaveworks/flux/api" + "github.com/weaveworks/flux/api/v10" transport "github.com/weaveworks/flux/http" "github.com/weaveworks/flux/job" fluxmetrics "github.com/weaveworks/flux/metrics" @@ -46,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) @@ -90,15 +93,29 @@ 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) { - service := mux.Vars(r)["service"] +func (s HTTPServer) ListImagesWithOptions(w http.ResponseWriter, r *http.Request) { + var opts v10.ListImagesOptions + queryValues := r.URL.Query() + + // service - Select services to update. + service := queryValues.Get("service") + if service == "" { + service = string(update.ResourceSpecAll) + } spec, err := update.ParseResourceSpec(service) if err != nil { 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. + containerFields := queryValues.Get("containerFields") + if containerFields != "" { + opts.OverrideContainerFields = strings.Split(containerFields, ",") + } - d, err := s.server.ListImages(r.Context(), spec) + d, err := s.server.ListImagesWithOptions(r.Context(), opts) if err != nil { transport.ErrorResponse(w, r, err) return @@ -122,7 +139,7 @@ func (s HTTPServer) UpdateManifests(w http.ResponseWriter, r *http.Request) { } func (s HTTPServer) ListServices(w http.ResponseWriter, r *http.Request) { - namespace := mux.Vars(r)["namespace"] + namespace := r.URL.Query().Get("namespace") res, err := s.server.ListServices(r.Context(), namespace) if err != nil { transport.ErrorResponse(w, r, err) diff --git a/vendor/github.com/weaveworks/flux/http/daemon/upstream.go b/vendor/github.com/weaveworks/flux/http/daemon/upstream.go index 3f59b352e..7e2f0f827 100644 --- a/vendor/github.com/weaveworks/flux/http/daemon/upstream.go +++ b/vendor/github.com/weaveworks/flux/http/daemon/upstream.go @@ -163,7 +163,7 @@ func (a *Upstream) connect() error { // _server_. rpcserver, err := rpc.NewServer(a.server) if err != nil { - return errors.Wrap(err, "initializing rpc client") + return errors.Wrap(err, "initializing rpc server") } rpcserver.ServeConn(ws) a.logger.Log("disconnected", true) @@ -175,7 +175,6 @@ func (a *Upstream) setConnectionDuration(duration float64) { } func (a *Upstream) LogEvent(event event.Event) error { - // Instance ID is set via token here, so we can leave it blank. return a.apiClient.LogEvent(context.TODO(), event) } diff --git a/vendor/github.com/weaveworks/flux/http/routes.go b/vendor/github.com/weaveworks/flux/http/routes.go index 6b67908d3..25c6853e7 100644 --- a/vendor/github.com/weaveworks/flux/http/routes.go +++ b/vendor/github.com/weaveworks/flux/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/vendor/github.com/weaveworks/flux/http/transport.go b/vendor/github.com/weaveworks/flux/http/transport.go index 7b9d764c9..89d7bf25b 100644 --- a/vendor/github.com/weaveworks/flux/http/transport.go +++ b/vendor/github.com/weaveworks/flux/http/transport.go @@ -29,8 +29,9 @@ func DeprecateVersions(r *mux.Router, versions ...string) { func NewAPIRouter() *mux.Router { r := mux.NewRouter() - r.NewRoute().Name(ListServices).Methods("GET").Path("/v6/services").Queries("namespace", "{namespace}") // optional namespace! - r.NewRoute().Name(ListImages).Methods("GET").Path("/v6/images").Queries("service", "{service}") + 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/vendor/github.com/weaveworks/flux/image/image.go b/vendor/github.com/weaveworks/flux/image/image.go index ac283165a..fbb6e179d 100644 --- a/vendor/github.com/weaveworks/flux/image/image.go +++ b/vendor/github.com/weaveworks/flux/image/image.go @@ -225,16 +225,16 @@ func (i Ref) WithNewTag(t string) Ref { // from its registry. type Info struct { // the reference to this image; probably a tagged image name - ID Ref + ID Ref `json:",omitempty"` // the digest we got when fetching the metadata, which will be // different each time a manifest is uploaded for the reference - Digest string + Digest string `json:",omitempty"` // an identifier for the *image* this reference points to; this // will be the same for references that point at the same image // (but does not necessarily equal Docker's image ID) - ImageID string + ImageID string `json:",omitempty"` // the time at which the image pointed at was created - CreatedAt time.Time + CreatedAt time.Time `json:",omitempty"` } // MarshalJSON returns the Info value in JSON (as bytes). It is diff --git a/vendor/github.com/weaveworks/flux/policy/policy.go b/vendor/github.com/weaveworks/flux/policy/policy.go index a54bb1a21..105294fb7 100644 --- a/vendor/github.com/weaveworks/flux/policy/policy.go +++ b/vendor/github.com/weaveworks/flux/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 { @@ -105,6 +116,16 @@ func (s Set) Get(p Policy) (string, bool) { return v, ok } +func (s Set) Without(omit Policy) Set { + newMap := Set{} + for p, v := range s { + if p != omit { + newMap[p] = v + } + } + return newMap +} + func (s Set) ToStringMap() map[string]string { m := map[string]string{} for p, v := range s { diff --git a/vendor/github.com/weaveworks/flux/registry/credentials.go b/vendor/github.com/weaveworks/flux/registry/credentials.go index a44e60eb8..36ef0697b 100644 --- a/vendor/github.com/weaveworks/flux/registry/credentials.go +++ b/vendor/github.com/weaveworks/flux/registry/credentials.go @@ -4,6 +4,7 @@ import ( "encoding/base64" "encoding/json" "fmt" + "io/ioutil" "net/url" "strings" @@ -96,7 +97,30 @@ func ParseCredentials(from string, b []byte) (Credentials, error) { return Credentials{m: m}, nil } -// For yields an authenticator for a specific host. +func ImageCredsWithDefaults(lookup func() ImageCreds, configPath string) (func() ImageCreds, error) { + var defaults Credentials + bs, err := ioutil.ReadFile(configPath) + if err == nil { + defaults, err = ParseCredentials(configPath, bs) + } + if err != nil { + return nil, err + } + return func() ImageCreds { + imageCreds := lookup() + for k, v := range imageCreds { + newCreds := NoCredentials() + newCreds.Merge(defaults) + newCreds.Merge(v) + imageCreds[k] = newCreds + } + return imageCreds + }, nil +} + +// --- + +// credsFor yields an authenticator for a specific host. func (cs Credentials) credsFor(host string) creds { if cred, found := cs.m[host]; found { return cred diff --git a/vendor/github.com/weaveworks/flux/registry/monitoring.go b/vendor/github.com/weaveworks/flux/registry/monitoring.go index 2d6be8662..fcdef5444 100644 --- a/vendor/github.com/weaveworks/flux/registry/monitoring.go +++ b/vendor/github.com/weaveworks/flux/registry/monitoring.go @@ -46,9 +46,9 @@ func NewInstrumentedRegistry(next Registry) Registry { } } -func (m *instrumentedRegistry) GetRepository(id image.Name) (res []image.Info, err error) { +func (m *instrumentedRegistry) GetSortedRepositoryImages(id image.Name) (res []image.Info, err error) { start := time.Now() - res, err = m.next.GetRepository(id) + res, err = m.next.GetSortedRepositoryImages(id) registryDuration.With( fluxmetrics.LabelSuccess, strconv.FormatBool(err == nil), ).Observe(time.Since(start).Seconds()) diff --git a/vendor/github.com/weaveworks/flux/registry/registry.go b/vendor/github.com/weaveworks/flux/registry/registry.go index f0768671f..5794ee503 100644 --- a/vendor/github.com/weaveworks/flux/registry/registry.go +++ b/vendor/github.com/weaveworks/flux/registry/registry.go @@ -12,7 +12,7 @@ var ( // Registry is a store of image metadata. type Registry interface { - GetRepository(image.Name) ([]image.Info, error) + GetSortedRepositoryImages(image.Name) ([]image.Info, error) GetImage(image.Ref) (image.Info, error) } diff --git a/vendor/github.com/weaveworks/flux/remote/logging.go b/vendor/github.com/weaveworks/flux/remote/logging.go index b2deed8a6..ef64c4166 100644 --- a/vendor/github.com/weaveworks/flux/remote/logging.go +++ b/vendor/github.com/weaveworks/flux/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" @@ -52,6 +53,15 @@ func (p *ErrorLoggingServer) ListImages(ctx context.Context, spec update.Resourc 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", "ListImagesWithOptions", "error", err) + } + }() + return p.server.ListImagesWithOptions(ctx, opts) +} + func (p *ErrorLoggingServer) JobStatus(ctx context.Context, jobID job.ID) (_ job.Status, err error) { defer func() { if err != nil { diff --git a/vendor/github.com/weaveworks/flux/remote/metrics.go b/vendor/github.com/weaveworks/flux/remote/metrics.go index 2938f188e..3a468e75f 100644 --- a/vendor/github.com/weaveworks/flux/remote/metrics.go +++ b/vendor/github.com/weaveworks/flux/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" @@ -66,6 +67,16 @@ func (i *instrumentedServer) ListImages(ctx context.Context, spec update.Resourc 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) { defer func(begin time.Time) { requestDuration.With( diff --git a/vendor/github.com/weaveworks/flux/remote/mock.go b/vendor/github.com/weaveworks/flux/remote/mock.go index 0b5fac128..9266b51fd 100644 --- a/vendor/github.com/weaveworks/flux/remote/mock.go +++ b/vendor/github.com/weaveworks/flux/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" @@ -69,6 +70,10 @@ func (p *MockServer) ListImages(context.Context, update.ResourceSpec) ([]v6.Imag return p.ListImagesAnswer, p.ListImagesError } +func (p *MockServer) ListImagesWithOptions(context.Context, v10.ListImagesOptions) ([]v6.ImageStatus, error) { + return p.ListImagesAnswer, p.ListImagesError +} + func (p *MockServer) UpdateManifests(ctx context.Context, s update.Spec) (job.ID, error) { if p.UpdateManifestsArgTest != nil { if err := p.UpdateManifestsArgTest(s); err != nil { @@ -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) + 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); 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/vendor/github.com/weaveworks/flux/remote/rpc/baseclient.go b/vendor/github.com/weaveworks/flux/remote/rpc/baseclient.go index 9b7dfa4b4..9c40762a5 100644 --- a/vendor/github.com/weaveworks/flux/remote/rpc/baseclient.go +++ b/vendor/github.com/weaveworks/flux/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" @@ -37,6 +38,10 @@ func (bc baseClient) ListImages(context.Context, update.ResourceSpec) ([]v6.Imag 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/vendor/github.com/weaveworks/flux/remote/rpc/clientV10.go b/vendor/github.com/weaveworks/flux/remote/rpc/clientV10.go new file mode 100644 index 000000000..76bafc626 --- /dev/null +++ b/vendor/github.com/weaveworks/flux/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. This version introduces methods which accept an +// options struct as the first argument. e.g. ListImagesWithOptions +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/vendor/github.com/weaveworks/flux/remote/rpc/clientV6.go b/vendor/github.com/weaveworks/flux/remote/rpc/clientV6.go index 1cbfb6b85..2846c052c 100644 --- a/vendor/github.com/weaveworks/flux/remote/rpc/clientV6.go +++ b/vendor/github.com/weaveworks/flux/remote/rpc/clientV6.go @@ -6,6 +6,9 @@ import ( "net/rpc" "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" "github.com/weaveworks/flux/job" @@ -115,6 +118,47 @@ 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) { + 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) { var result job.ID if err := requireSpecKinds(u, supportedKindsV6); err != nil { diff --git a/vendor/github.com/weaveworks/flux/remote/rpc/clientV8.go b/vendor/github.com/weaveworks/flux/remote/rpc/clientV8.go index dddb3eba8..a4933a01c 100644 --- a/vendor/github.com/weaveworks/flux/remote/rpc/clientV8.go +++ b/vendor/github.com/weaveworks/flux/remote/rpc/clientV8.go @@ -25,7 +25,7 @@ type clientV8 interface { var _ clientV8 = &RPCClientV8{} -var supportedKindsV8 = []string{"deployment", "daemonset", "statefulset", "cronjob"} +var supportedKindsV8 = []string{"deployment", "daemonset", "statefulset", "cronjob", "fluxhelmrelease"} // NewClient creates a new rpc-backed implementation of the server. func NewClientV8(conn io.ReadWriteCloser) *RPCClientV8 { diff --git a/vendor/github.com/weaveworks/flux/remote/rpc/server.go b/vendor/github.com/weaveworks/flux/remote/rpc/server.go index ebe2ba531..4b9f9e5b9 100644 --- a/vendor/github.com/weaveworks/flux/remote/rpc/server.go +++ b/vendor/github.com/weaveworks/flux/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" @@ -100,6 +102,18 @@ func (p *RPCServer) ListImages(spec update.ResourceSpec, resp *ListImagesRespons 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 { + resp.ApplicationError = err + return nil + } + } + return err +} + type UpdateManifestsResponse struct { Result job.ID ApplicationError *fluxerr.Error diff --git a/vendor/github.com/weaveworks/flux/resource/resource.go b/vendor/github.com/weaveworks/flux/resource/resource.go index 686009a0c..8dfb672cf 100644 --- a/vendor/github.com/weaveworks/flux/resource/resource.go +++ b/vendor/github.com/weaveworks/flux/resource/resource.go @@ -22,4 +22,8 @@ type Container struct { type Workload interface { Resource Containers() []Container + // SetContainerImage mutates this workload so that the container + // named has the image given. This is not expected to have an + // effect on any underlying file or cluster resource. + SetContainerImage(container string, ref image.Ref) error } diff --git a/vendor/github.com/weaveworks/flux/update/automated.go b/vendor/github.com/weaveworks/flux/update/automated.go index 43cffa099..c2d8f74ea 100644 --- a/vendor/github.com/weaveworks/flux/update/automated.go +++ b/vendor/github.com/weaveworks/flux/update/automated.go @@ -108,7 +108,7 @@ func (a *Automated) calculateImageUpdates(rc ReleaseContext, candidates []*Contr // resource (e.g., to avoid canonicalising it) newImageID := currentImageID.WithNewTag(change.ImageID.Tag) var err error - u.ManifestBytes, err = rc.Manifests().UpdateDefinition(u.ManifestBytes, container.Name, newImageID) + u.ManifestBytes, err = rc.Manifests().UpdateImage(u.ManifestBytes, u.ResourceID, container.Name, newImageID) if err != nil { return nil, err } diff --git a/vendor/github.com/weaveworks/flux/update/images.go b/vendor/github.com/weaveworks/flux/update/images.go index 740e16d20..42e870a64 100644 --- a/vendor/github.com/weaveworks/flux/update/images.go +++ b/vendor/github.com/weaveworks/flux/update/images.go @@ -14,47 +14,67 @@ import ( "github.com/weaveworks/flux/resource" ) -type infoMap map[image.CanonicalName][]image.Info +type imageReposMap map[image.CanonicalName]ImageInfos -type ImageMap struct { - images infoMap +// ImageRepos contains a map of image repositories to their images +type ImageRepos struct { + imageRepos imageReposMap } -// LatestImage returns the latest releasable image for a repository -// for which the tag matches a given pattern. A releasable image is -// one that is not tagged "latest". (Assumes the available images are -// in descending order of latestness.) If no such image exists, -// returns a zero value and `false`, and the caller can decide whether -// that's an error or not. -func (m ImageMap) LatestImage(repo image.Name, tagGlob string) (image.Info, bool) { - for _, available := range m.images[repo.CanonicalName()] { - tag := available.ID.Tag +// GetRepoImages returns image.Info entries for all the images in the +// named image repository. +func (r ImageRepos) GetRepoImages(repo image.Name) ImageInfos { + if canon, ok := r.imageRepos[repo.CanonicalName()]; ok { + infos := make([]image.Info, len(canon)) + for i := range canon { + infos[i] = canon[i] + infos[i].ID = repo.ToRef(infos[i].ID.Tag) + } + return infos + } + return nil +} + +// ImageInfos is a list of image.Info which can be filtered. +type ImageInfos []image.Info + +// Filter returns only the images which match the tagGlob. +func (ii ImageInfos) Filter(tagGlob string) ImageInfos { + var filtered ImageInfos + for _, i := range ii { + tag := i.ID.Tag // Ignore latest if and only if it's not what the user wants. if !strings.EqualFold(tagGlob, "latest") && strings.EqualFold(tag, "latest") { continue } if glob.Glob(tagGlob, tag) { var im image.Info - im = available - im.ID = repo.ToRef(tag) - return im, true + im = i + filtered = append(filtered, im) } } + return filtered +} + +// Latest returns the latest image from ImageInfos. If no such image exists, +// returns a zero value and `false`, and the caller can decide whether +// that's an error or not. +func (ii ImageInfos) Latest() (image.Info, bool) { + if len(ii) > 0 { + return ii[0], true + } return image.Info{}, false } -// Available returns image.Info entries for all the images in the -// named image repository. -func (m ImageMap) Available(repo image.Name) []image.Info { - if canon, ok := m.images[repo.CanonicalName()]; ok { - infos := make([]image.Info, len(canon)) - for i := range canon { - infos[i] = canon[i] - infos[i].ID = repo.ToRef(infos[i].ID.Tag) +// FindWithRef returns image.Info given an image ref. If the image cannot be +// found, it returns the image.Info with the ID provided. +func (ii ImageInfos) FindWithRef(ref image.Ref) image.Info { + for _, img := range ii { + if img.ID == ref { + return img } - return infos } - return nil + return image.Info{ID: ref} } // containers represents a collection of things that have containers @@ -73,54 +93,55 @@ func (cs controllerContainers) Containers(i int) []resource.Container { return cs[i].Controller.ContainersOrNil() } -// collectUpdateImages is a convenient shim to -// `CollectAvailableImages`. -func collectUpdateImages(registry registry.Registry, updateable []*ControllerUpdate, logger log.Logger) (ImageMap, error) { - return CollectAvailableImages(registry, controllerContainers(updateable), logger) +// fetchUpdatableImageRepos is a convenient shim to +// `FetchImageRepos`. +func fetchUpdatableImageRepos(registry registry.Registry, updateable []*ControllerUpdate, logger log.Logger) (ImageRepos, error) { + return FetchImageRepos(registry, controllerContainers(updateable), logger) } -// CollectAvailableImages finds all the known image metadata for +// FetchImageRepos finds all the known image metadata for // containers in the controllers given. -func CollectAvailableImages(reg registry.Registry, cs containers, logger log.Logger) (ImageMap, error) { - images := infoMap{} +func FetchImageRepos(reg registry.Registry, cs containers, logger log.Logger) (ImageRepos, error) { + imageRepos := imageReposMap{} for i := 0; i < cs.Len(); i++ { for _, container := range cs.Containers(i) { - images[container.Image.CanonicalName()] = nil + imageRepos[container.Image.CanonicalName()] = nil } } - for name := range images { - imageRepo, err := reg.GetRepository(name.Name) + for repo := range imageRepos { + sortedRepoImages, err := reg.GetSortedRepositoryImages(repo.Name) if err != nil { // Not an error if missing. Use empty images. if !fluxerr.IsMissing(err) { - logger.Log("err", errors.Wrapf(err, "fetching image metadata for %s", name)) + logger.Log("err", errors.Wrapf(err, "fetching image metadata for %s", repo)) continue } } - images[name] = imageRepo + imageRepos[repo] = sortedRepoImages } - return ImageMap{images}, nil + return ImageRepos{imageRepos}, nil } -// Create a map of images. It will check that each image exists. -func exactImages(reg registry.Registry, images []image.Ref) (ImageMap, error) { - m := infoMap{} +// Create a map of image repos to images. It will check that each image exists. +func exactImageRepos(reg registry.Registry, images []image.Ref) (ImageRepos, error) { + m := imageReposMap{} for _, id := range images { // We must check that the exact images requested actually exist. Otherwise we risk pushing invalid images to git. exist, err := imageExists(reg, id) if err != nil { - return ImageMap{}, errors.Wrap(image.ErrInvalidImageID, err.Error()) + return ImageRepos{}, errors.Wrap(image.ErrInvalidImageID, err.Error()) } if !exist { - return ImageMap{}, errors.Wrap(image.ErrInvalidImageID, fmt.Sprintf("image %q does not exist", id)) + return ImageRepos{}, errors.Wrap(image.ErrInvalidImageID, fmt.Sprintf("image %q does not exist", id)) } m[id.CanonicalName()] = []image.Info{{ID: id}} } - return ImageMap{m}, nil + return ImageRepos{m}, nil } // Checks whether the given image exists in the repository. -// Return true if exist, false otherwise +// Return true if exist, false otherwise. +// FIXME(michael): never returns an error; should it? func imageExists(reg registry.Registry, imageID image.Ref) (bool, error) { _, err := reg.GetImage(imageID) if err != nil { diff --git a/vendor/github.com/weaveworks/flux/update/release.go b/vendor/github.com/weaveworks/flux/update/release.go index 4dae03ef3..df52bc574 100644 --- a/vendor/github.com/weaveworks/flux/update/release.go +++ b/vendor/github.com/weaveworks/flux/update/release.go @@ -188,20 +188,20 @@ func (s ReleaseSpec) markSkipped(results Result) { // if not, it indicates there's likely some problem with the running // system vs the definitions given in the repo.) func (s ReleaseSpec) calculateImageUpdates(rc ReleaseContext, candidates []*ControllerUpdate, results Result, logger log.Logger) ([]*ControllerUpdate, error) { - // Compile an `ImageMap` of all relevant images - var images ImageMap + // Compile an `ImageRepos` of all relevant images + var imageRepos ImageRepos var singleRepo image.CanonicalName var err error switch s.ImageSpec { case ImageSpecLatest: - images, err = collectUpdateImages(rc.Registry(), candidates, logger) + imageRepos, err = fetchUpdatableImageRepos(rc.Registry(), candidates, logger) default: var ref image.Ref ref, err = s.ImageSpec.AsRef() if err == nil { singleRepo = ref.CanonicalName() - images, err = exactImages(rc.Registry(), []image.Ref{ref}) + imageRepos, err = exactImageRepos(rc.Registry(), []image.Ref{ref}) } } @@ -231,7 +231,8 @@ func (s ReleaseSpec) calculateImageUpdates(rc ReleaseContext, candidates []*Cont for _, container := range containers { currentImageID := container.Image - latestImage, ok := images.LatestImage(currentImageID.Name, "*") + filteredImages := imageRepos.GetRepoImages(currentImageID.Name).Filter("*") + latestImage, ok := filteredImages.Latest() if !ok { if currentImageID.CanonicalName() != singleRepo { ignoredOrSkipped = ReleaseStatusIgnored @@ -251,7 +252,7 @@ func (s ReleaseSpec) calculateImageUpdates(rc ReleaseContext, candidates []*Cont // canonical form. newImageID := currentImageID.WithNewTag(latestImage.ID.Tag) - u.ManifestBytes, err = rc.Manifests().UpdateDefinition(u.ManifestBytes, container.Name, newImageID) + u.ManifestBytes, err = rc.Manifests().UpdateImage(u.ManifestBytes, u.ResourceID, container.Name, newImageID) if err != nil { return nil, err } diff --git a/vendor/github.com/weaveworks/flux/update/spec.go b/vendor/github.com/weaveworks/flux/update/spec.go index 71324b8ba..0a22317f1 100644 --- a/vendor/github.com/weaveworks/flux/update/spec.go +++ b/vendor/github.com/weaveworks/flux/update/spec.go @@ -11,6 +11,7 @@ const ( Images = "image" Policy = "policy" Auto = "auto" + Sync = "sync" ) // How did this update get triggered? @@ -58,6 +59,11 @@ func (spec *Spec) UnmarshalJSON(in []byte) error { return err } spec.Spec = update + case Sync: + var update ManualSync + if err := json.Unmarshal(wire.SpecBytes, &update); err != nil { + } + spec.Spec = update default: return errors.New("unknown spec type: " + wire.Type) } diff --git a/vendor/github.com/weaveworks/flux/update/sync.go b/vendor/github.com/weaveworks/flux/update/sync.go new file mode 100644 index 000000000..aea289e76 --- /dev/null +++ b/vendor/github.com/weaveworks/flux/update/sync.go @@ -0,0 +1,4 @@ +package update + +type ManualSync struct { +}