Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions cli/command/image/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -412,9 +412,9 @@ func runBuild(dockerCli command.Cli, options buildOptions) error {
defer response.Body.Close()

imageID := ""
aux := func(auxJSON *json.RawMessage) {
aux := func(m jsonmessage.JSONMessage) {
var result types.BuildResult
if err := json.Unmarshal(*auxJSON, &result); err != nil {
if err := json.Unmarshal(*m.Aux, &result); err != nil {
fmt.Fprintf(dockerCli.Err(), "Failed to parse aux message: %s", err)
} else {
imageID = result.ID
Expand Down
6 changes: 3 additions & 3 deletions cli/command/image/trust.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,15 @@ func PushTrustedReference(streams command.Streams, repoInfo *registry.Repository
// Count the times of calling for handleTarget,
// if it is called more that once, that should be considered an error in a trusted push.
cnt := 0
handleTarget := func(aux *json.RawMessage) {
handleTarget := func(m jsonmessage.JSONMessage) {
cnt++
if cnt > 1 {
// handleTarget should only be called one. This will be treated as an error.
// handleTarget should only be called once. This will be treated as an error.
return
}

var pushResult types.PushResult
err := json.Unmarshal(*aux, &pushResult)
err := json.Unmarshal(*m.Aux, &pushResult)
if err == nil && pushResult.Tag != "" {
if dgst, err := digest.Parse(pushResult.Digest); err == nil {
h, err := hex.DecodeString(dgst.Hex())
Expand Down
76 changes: 67 additions & 9 deletions cli/command/stack/kubernetes/list.go
Original file line number Diff line number Diff line change
@@ -1,22 +1,27 @@
package kubernetes

import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"

"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/formatter"
"github.com/docker/cli/cli/command/stack/options"
"github.com/pkg/errors"
core_v1 "k8s.io/api/core/v1"
apierrs "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// GetStacks lists the kubernetes stacks
func GetStacks(dockerCli command.Cli, opts options.List, kopts Options) ([]*formatter.Stack, error) {
kubeCli, err := WrapCli(dockerCli, kopts)
if err != nil {
return nil, err
}
func GetStacks(kubeCli *KubeCli, opts options.List) ([]*formatter.Stack, error) {
if opts.AllNamespaces || len(opts.Namespaces) == 0 {
return getStacks(kubeCli, opts)
return getStacksWithAllNamespaces(kubeCli, opts)
}
return getStacksWithNamespaces(kubeCli, opts)
return getStacksWithNamespaces(kubeCli, opts, removeDuplicates(opts.Namespaces))
}

func getStacks(kubeCli *KubeCli, opts options.List) ([]*formatter.Stack, error) {
Expand Down Expand Up @@ -44,9 +49,62 @@ func getStacks(kubeCli *KubeCli, opts options.List) ([]*formatter.Stack, error)
return formattedStacks, nil
}

func getStacksWithNamespaces(kubeCli *KubeCli, opts options.List) ([]*formatter.Stack, error) {
func getStacksWithAllNamespaces(kubeCli *KubeCli, opts options.List) ([]*formatter.Stack, error) {
stacks, err := getStacks(kubeCli, opts)
if !apierrs.IsForbidden(err) {
return stacks, err
}
namespaces, err2 := getUserVisibleNamespaces(*kubeCli)
if err2 != nil {
return nil, errors.Wrap(err2, "failed to query user visible namespaces")
}
if namespaces == nil {
// UCP API not present, fall back to Kubernetes error
return nil, err
}
opts.AllNamespaces = false
return getStacksWithNamespaces(kubeCli, opts, namespaces)
}

func getUserVisibleNamespaces(dockerCli command.Cli) ([]string, error) {
host := dockerCli.Client().DaemonHost()
endpoint, err := url.Parse(host)
if err != nil {
return nil, err
}
endpoint.Scheme = "https"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This part is a bit sad as this is something we already do in the moby/moby client package (setting the scheme to https when tls is configured)..

endpoint.Path = "/kubernetesNamespaces"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UCP does not have a versioned API at present. If we're going to rely on UCP endpoints, we need to be careful about making sure that this stays backwards-compatible.

Is this UX a requirement? Kubernetes does not have any other operations where you can view a list of only the resources that you have access to.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, this is really fragile and ad hoc, however this is indeed a UX requirement and Kubernetes does not provide this feature…
If you have any suggestion on how to better manage this I'll be happy to update the code.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UCP does not have a versioned API at present. If we're going to rely on UCP endpoints, we need to be careful about making sure that this stays backwards-compatible.

@wsong yes, that's the job of UCP to be backwards-compatible on that endpoint (or providing a versionned one)…

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All right, let's stick with this approach for now, but we'll have to be careful to test this with different combinations of CLI version + UCP version.

resp, err := dockerCli.Client().HTTPClient().Get(endpoint.String())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wondering if there are other things that should be set on the request (content-type JSON, custom headers that are configured in the client-configuration, etc);

func (cli *Client) addHeaders(req *http.Request, headers headers) *http.Request {
// Add CLI Config's HTTP Headers BEFORE we set the Docker headers
// then the user can't change OUR headers
for k, v := range cli.customHTTPHeaders {
if versions.LessThan(cli.version, "1.25") && k == "User-Agent" {
continue
}
req.Header.Set(k, v)
}
if headers != nil {
for k, v := range headers {
req.Header[k] = v
}
}
return req
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know, however this would not be an issue were the Client performing the request itself.
moby/moby#37071 has been merged in moby, but maybe it's not to late to bring that up?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, it's a bit of an odd situation; the /kubernetesNamespaces endpoint feels like an add-hoc/temporary solution. Some thoughts on that;

  • It's undocumented, and not sure how well-defined the endpoint is
  • the Engine remote API may perform validations, e.g.
    • correct request-type, respond different based on "accept" headers (plain-text/JSON)
    • version negotiation (not present in the UCP API) (this could be a problem if we used the docker/client/request functionality, as it may be done automatically - need to check)

So, should we export the docker/client/request client.get(), client.head() and so on functions? I don't know.

On the "plus" side; when using the docker/client/request functionality, we would get some things for free (improved/consistent errors if TLS fails, automatic handling of invalid JSON responses, etc.)

I think in an ideal situation, given that UCP defines its own API (which is the Engine remote-API + Additional endpoints/extensions) there would be an official UCP client that we could use.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do agree with @thaJeztah that it is really an odd situation : we end up hacking the docker/docker client around for something that isn't even docker api related.

As discussed a bit offline earlier, a cleaner way (way cleaner) would have been to have a kubernetes api extension (same as the current "compose on kubernetes" one) :

  • we wouldn't need to hack docker/docker client around, we would just consume the go code from the extension (again, same way as "compose on kubernetes")
  • it would be "automatically" versionned (because of k8s way of doing things)
  • it wouldn't be a direct dependency on UCP as it could be easily deployed on other cluster in the future if we wanted

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Being new to the org I cannot estimate whether this is a realistic solution given the time constraint, @chris-crone any opinion?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@thaJeztah there's an issue against the relevant repo to ensure that this endpoint is properly tested and that its behaviour isn't modified.

@vdemeester I agree that those solutions would be cleaner but given the timeline for this; they're not feasible. Since this is a fallback we can replace it or demote it in the fallback list at a later date once we've had time to investigate the cleaner options.

if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, errors.Wrapf(err, "received %d status and unable to read response", resp.StatusCode)
}
switch resp.StatusCode {
case http.StatusOK:
nms := &core_v1.NamespaceList{}
if err := json.Unmarshal(body, nms); err != nil {
return nil, errors.Wrapf(err, "unmarshal failed: %s", string(body))
}
namespaces := make([]string, len(nms.Items))
for i, namespace := range nms.Items {
namespaces[i] = namespace.Name
}
return namespaces, nil
case http.StatusNotFound:
// UCP API not present
return nil, nil
default:
return nil, fmt.Errorf("received %d status while retrieving namespaces: %s", resp.StatusCode, string(body))
}
}

func getStacksWithNamespaces(kubeCli *KubeCli, opts options.List, namespaces []string) ([]*formatter.Stack, error) {
stacks := []*formatter.Stack{}
for _, namespace := range removeDuplicates(opts.Namespaces) {
for _, namespace := range namespaces {
kubeCli.kubeNamespace = namespace
ss, err := getStacks(kubeCli, opts)
if err != nil {
Expand Down
6 changes: 5 additions & 1 deletion cli/command/stack/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,11 @@ func runList(cmd *cobra.Command, dockerCli command.Cli, opts options.List) error
stacks = append(stacks, ss...)
}
if dockerCli.ClientInfo().HasKubernetes() {
ss, err := kubernetes.GetStacks(dockerCli, opts, kubernetes.NewOptions(cmd.Flags()))
kubeCli, err := kubernetes.WrapCli(dockerCli, kubernetes.NewOptions(cmd.Flags()))
if err != nil {
return err
}
ss, err := kubernetes.GetStacks(kubeCli, opts)
if err != nil {
return err
}
Expand Down
10 changes: 5 additions & 5 deletions cli/command/volume/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ import (

type fakeClient struct {
client.Client
volumeCreateFunc func(volumetypes.VolumesCreateBody) (types.Volume, error)
volumeCreateFunc func(volumetypes.VolumeCreateBody) (types.Volume, error)
volumeInspectFunc func(volumeID string) (types.Volume, error)
volumeListFunc func(filter filters.Args) (volumetypes.VolumesListOKBody, error)
volumeListFunc func(filter filters.Args) (volumetypes.VolumeListOKBody, error)
volumeRemoveFunc func(volumeID string, force bool) error
volumePruneFunc func(filter filters.Args) (types.VolumesPruneReport, error)
}

func (c *fakeClient) VolumeCreate(ctx context.Context, options volumetypes.VolumesCreateBody) (types.Volume, error) {
func (c *fakeClient) VolumeCreate(ctx context.Context, options volumetypes.VolumeCreateBody) (types.Volume, error) {
if c.volumeCreateFunc != nil {
return c.volumeCreateFunc(options)
}
Expand All @@ -32,11 +32,11 @@ func (c *fakeClient) VolumeInspect(ctx context.Context, volumeID string) (types.
return types.Volume{}, nil
}

func (c *fakeClient) VolumeList(ctx context.Context, filter filters.Args) (volumetypes.VolumesListOKBody, error) {
func (c *fakeClient) VolumeList(ctx context.Context, filter filters.Args) (volumetypes.VolumeListOKBody, error) {
if c.volumeListFunc != nil {
return c.volumeListFunc(filter)
}
return volumetypes.VolumesListOKBody{}, nil
return volumetypes.VolumeListOKBody{}, nil
}

func (c *fakeClient) VolumesPrune(ctx context.Context, filter filters.Args) (types.VolumesPruneReport, error) {
Expand Down
2 changes: 1 addition & 1 deletion cli/command/volume/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ func newCreateCommand(dockerCli command.Cli) *cobra.Command {
func runCreate(dockerCli command.Cli, options createOptions) error {
client := dockerCli.Client()

volReq := volumetypes.VolumesCreateBody{
volReq := volumetypes.VolumeCreateBody{
Driver: options.driver,
DriverOpts: options.driverOpts.GetAll(),
Name: options.name,
Expand Down
8 changes: 4 additions & 4 deletions cli/command/volume/create_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ func TestVolumeCreateErrors(t *testing.T) {
testCases := []struct {
args []string
flags map[string]string
volumeCreateFunc func(volumetypes.VolumesCreateBody) (types.Volume, error)
volumeCreateFunc func(volumetypes.VolumeCreateBody) (types.Volume, error)
expectedError string
}{
{
Expand All @@ -33,7 +33,7 @@ func TestVolumeCreateErrors(t *testing.T) {
expectedError: "requires at most 1 argument",
},
{
volumeCreateFunc: func(createBody volumetypes.VolumesCreateBody) (types.Volume, error) {
volumeCreateFunc: func(createBody volumetypes.VolumeCreateBody) (types.Volume, error) {
return types.Volume{}, errors.Errorf("error creating volume")
},
expectedError: "error creating volume",
Expand All @@ -57,7 +57,7 @@ func TestVolumeCreateErrors(t *testing.T) {
func TestVolumeCreateWithName(t *testing.T) {
name := "foo"
cli := test.NewFakeCli(&fakeClient{
volumeCreateFunc: func(body volumetypes.VolumesCreateBody) (types.Volume, error) {
volumeCreateFunc: func(body volumetypes.VolumeCreateBody) (types.Volume, error) {
if body.Name != name {
return types.Volume{}, errors.Errorf("expected name %q, got %q", name, body.Name)
}
Expand Down Expand Up @@ -96,7 +96,7 @@ func TestVolumeCreateWithFlags(t *testing.T) {
name := "banana"

cli := test.NewFakeCli(&fakeClient{
volumeCreateFunc: func(body volumetypes.VolumesCreateBody) (types.Volume, error) {
volumeCreateFunc: func(body volumetypes.VolumeCreateBody) (types.Volume, error) {
if body.Name != "" {
return types.Volume{}, errors.Errorf("expected empty name, got %q", body.Name)
}
Expand Down
18 changes: 9 additions & 9 deletions cli/command/volume/list_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,16 @@ func TestVolumeListErrors(t *testing.T) {
testCases := []struct {
args []string
flags map[string]string
volumeListFunc func(filter filters.Args) (volumetypes.VolumesListOKBody, error)
volumeListFunc func(filter filters.Args) (volumetypes.VolumeListOKBody, error)
expectedError string
}{
{
args: []string{"foo"},
expectedError: "accepts no argument",
},
{
volumeListFunc: func(filter filters.Args) (volumetypes.VolumesListOKBody, error) {
return volumetypes.VolumesListOKBody{}, errors.Errorf("error listing volumes")
volumeListFunc: func(filter filters.Args) (volumetypes.VolumeListOKBody, error) {
return volumetypes.VolumeListOKBody{}, errors.Errorf("error listing volumes")
},
expectedError: "error listing volumes",
},
Expand All @@ -51,8 +51,8 @@ func TestVolumeListErrors(t *testing.T) {

func TestVolumeListWithoutFormat(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
volumeListFunc: func(filter filters.Args) (volumetypes.VolumesListOKBody, error) {
return volumetypes.VolumesListOKBody{
volumeListFunc: func(filter filters.Args) (volumetypes.VolumeListOKBody, error) {
return volumetypes.VolumeListOKBody{
Volumes: []*types.Volume{
Volume(),
Volume(VolumeName("foo"), VolumeDriver("bar")),
Expand All @@ -70,8 +70,8 @@ func TestVolumeListWithoutFormat(t *testing.T) {

func TestVolumeListWithConfigFormat(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
volumeListFunc: func(filter filters.Args) (volumetypes.VolumesListOKBody, error) {
return volumetypes.VolumesListOKBody{
volumeListFunc: func(filter filters.Args) (volumetypes.VolumeListOKBody, error) {
return volumetypes.VolumeListOKBody{
Volumes: []*types.Volume{
Volume(),
Volume(VolumeName("foo"), VolumeDriver("bar")),
Expand All @@ -92,8 +92,8 @@ func TestVolumeListWithConfigFormat(t *testing.T) {

func TestVolumeListWithFormat(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
volumeListFunc: func(filter filters.Args) (volumetypes.VolumesListOKBody, error) {
return volumetypes.VolumesListOKBody{
volumeListFunc: func(filter filters.Args) (volumetypes.VolumeListOKBody, error) {
return volumetypes.VolumeListOKBody{
Volumes: []*types.Volume{
Volume(),
Volume(VolumeName("foo"), VolumeDriver("bar")),
Expand Down
2 changes: 1 addition & 1 deletion cli/registry/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ func (c *client) getRepositoryForReference(ctx context.Context, ref reference.Na
if err != nil {
return nil, errors.Wrapf(err, "failed to parse repo name from %s", ref)
}
return distributionclient.NewRepository(ctx, repoName, repoEndpoint.BaseURL(), httpTransport)
return distributionclient.NewRepository(repoName, repoEndpoint.BaseURL(), httpTransport)
}

func (c *client) getHTTPTransportForRepoEndpoint(ctx context.Context, repoEndpoint repositoryEndpoint) (http.RoundTripper, error) {
Expand Down
15 changes: 11 additions & 4 deletions vendor.conf
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
github.com/agl/ed25519 d2b94fd789ea21d12fac1a4443dd3a3f79cda72c
github.com/Azure/go-ansiterm d6e3b3328b783f23731bc4d058875b0371ff8109
github.com/beorn7/perks 4c0e84591b9aa9e6dcfdf3e020114cd81f89d5f9
github.com/containerd/continuity d8fb8589b0e8e85b8c8bbaa8840226d0dfeb7371
github.com/coreos/etcd v3.2.1
github.com/cpuguy83/go-md2man v1.0.8
github.com/davecgh/go-spew 346938d642f2ec3594ed81d874461961cd0faa76
github.com/docker/distribution edc3ab29cdff8694dd6feb85cfeb4b5f1b38ed9c
github.com/docker/docker ed7b6428c133e7c59404251a09b7d6b02fa83cc2
github.com/docker/distribution 83389a148052d74ac602f5f1d62f86ff2f3c4aa5
github.com/docker/docker d37f5c6bdf788a6cb82c07fb707e31a240eff5f9
github.com/docker/docker-credential-helpers 3c90bd29a46b943b2a9842987b58fb91a7c1819b
# the docker/go package contains a customized version of canonical/json
# and is used by Notary. The package is periodically rebased on current Go versions.
github.com/docker/go d30aec9fd63c35133f8f79c3412ad91a3b08be06
github.com/docker/go-connections 7beb39f0b969b075d1325fecb092faf27fd357b6
github.com/docker/go-events 9461782956ad83b30282bf90e31fa6a70c255ba9
github.com/docker/go-metrics d466d4f6fd960e01820085bd7e1a24426ee7ef18
github.com/docker/go-units 9e638d38cf6977a37a8ea0078f3ee75a7cdb2dd1
github.com/docker/swarmkit 49a9d7f6ba3c1925262641e694c18eb43575f74b
github.com/docker/swarmkit bd69f6e8e301645afd344913fa1ede53a0a111fb
github.com/emicklei/go-restful ff4f55a206334ef123e4f79bbf348980da81ca46
github.com/emicklei/go-restful-swagger12 dcef7f55730566d41eae5db10e7d6981829720f6
github.com/flynn-archive/go-shlex 3f9db97f856818214da2e1057f8ad84803971cff
Expand Down Expand Up @@ -41,6 +43,7 @@ github.com/juju/ratelimit 5b9ff866471762aa2ab2dced63c9fb6f53921342
github.com/json-iterator/go 6240e1e7983a85228f7fd9c3e1b6932d46ec58e2
github.com/mailru/easyjson d5b7844b561a7bc640052f1b935f7b800330d7e0
github.com/mattn/go-shellwords v1.0.3
github.com/matttproud/golang_protobuf_extensions v1.0.0
github.com/Microsoft/go-winio v0.4.6
github.com/miekg/pkcs11 5f6e0d0dad6f472df908c8e968a98ef00c9224bb
github.com/mitchellh/mapstructure f3009df150dadf309fdee4a54ed65c124afad715
Expand All @@ -52,6 +55,10 @@ github.com/opencontainers/runc 4fc53a81fb7c994640722ac585fa9ca548971871
github.com/peterbourgon/diskv 5f041e8faa004a95c88a202771f4cc3e991971e6
github.com/pkg/errors 839d9e913e063e28dfd0e6c7b7512793e0a48be9
github.com/pmezard/go-difflib v1.0.0
github.com/prometheus/client_golang 52437c81da6b127a9925d17eb3a382a2e5fd395e
github.com/prometheus/client_model fa8ad6fec33561be4280a8f0514318c79d7f6cb6
github.com/prometheus/common ebdfc6da46522d58825777cf1f90490a5b1ef1d8
github.com/prometheus/procfs abf152e5f3e97f2fafac028d2cc06c1feb87ffa5
github.com/PuerkitoBio/purell 8a290539e2e8629dbc4e6bad948158f790ec31f4
github.com/PuerkitoBio/urlesc 5bd2802263f21d8788851d5305584c82a5c75d7e
github.com/russross/blackfriday 1d6b8e9301e720b08a8938b8c25c018285885438
Expand All @@ -65,7 +72,7 @@ github.com/xeipuuv/gojsonpointer e0fe6f68307607d540ed8eac07a342c33fa1b54a
github.com/xeipuuv/gojsonreference e02fc20de94c78484cd5ffb007f8af96be030a45
github.com/xeipuuv/gojsonschema 93e72a773fade158921402d6a24c819b48aba29d
golang.org/x/crypto 558b6879de74bc843225cde5686419267ff707ca
golang.org/x/net a8b9294777976932365dabb6640cf1468d95c70f
golang.org/x/net 5561cd9b4330353950f399814f427425c0a26fd2
golang.org/x/sync fd80eb99c8f653c847d294a001bdf2a3a6f768f5
golang.org/x/sys 37707fdb30a5b38865cfb95e5aab41707daec7fd
golang.org/x/text f72d8390a633d5dfb0cc84043294db9f6c935756
Expand Down
20 changes: 20 additions & 0 deletions vendor/github.com/beorn7/perks/LICENSE

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

31 changes: 31 additions & 0 deletions vendor/github.com/beorn7/perks/README.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading