Browse code

make oc set image resolve image stream images and tags using --source flag

Michal Fojtik authored on 2016/09/20 17:56:25
Showing 12 changed files
... ...
@@ -12899,6 +12899,8 @@ _oc_set_image()
12899 12899
     local_nonpersistent_flags+=("--show-labels")
12900 12900
     flags+=("--sort-by=")
12901 12901
     local_nonpersistent_flags+=("--sort-by=")
12902
+    flags+=("--source=")
12903
+    local_nonpersistent_flags+=("--source=")
12902 12904
     flags+=("--template=")
12903 12905
     flags_with_completion+=("--template")
12904 12906
     flags_completion+=("_filedir")
... ...
@@ -17482,6 +17482,8 @@ _openshift_cli_set_image()
17482 17482
     local_nonpersistent_flags+=("--show-labels")
17483 17483
     flags+=("--sort-by=")
17484 17484
     local_nonpersistent_flags+=("--sort-by=")
17485
+    flags+=("--source=")
17486
+    local_nonpersistent_flags+=("--source=")
17485 17487
     flags+=("--template=")
17486 17488
     flags_with_completion+=("--template")
17487 17489
     flags_completion+=("_filedir")
... ...
@@ -13061,6 +13061,8 @@ _oc_set_image()
13061 13061
     local_nonpersistent_flags+=("--show-labels")
13062 13062
     flags+=("--sort-by=")
13063 13063
     local_nonpersistent_flags+=("--sort-by=")
13064
+    flags+=("--source=")
13065
+    local_nonpersistent_flags+=("--source=")
13064 13066
     flags+=("--template=")
13065 13067
     flags_with_completion+=("--template")
13066 13068
     flags_completion+=("_filedir")
... ...
@@ -17644,6 +17644,8 @@ _openshift_cli_set_image()
17644 17644
     local_nonpersistent_flags+=("--show-labels")
17645 17645
     flags+=("--sort-by=")
17646 17646
     local_nonpersistent_flags+=("--sort-by=")
17647
+    flags+=("--source=")
17648
+    local_nonpersistent_flags+=("--source=")
17647 17649
     flags+=("--template=")
17648 17650
     flags_with_completion+=("--template")
17649 17651
     flags_completion+=("_filedir")
... ...
@@ -69,6 +69,10 @@ Update existing container image(s) of resources.
69 69
     If non\-empty, sort list types using this field specification.  The field specification is expressed as a JSONPath expression (e.g. '{.metadata.name}'). The field in the API resource specified by this JSONPath expression must be an integer or a string.
70 70
 
71 71
 .PP
72
+\fB\-\-source\fP="istag"
73
+    The image source type; valid types are valid values are 'imagestreamtag', 'istag', 'imagestreamimage', 'isimage', and 'docker'
74
+
75
+.PP
72 76
 \fB\-\-template\fP=""
73 77
     Template string or path to template file to use when \-o=go\-template, \-o=go\-template\-file. The template format is golang templates [
74 78
 \[la]http://golang.org/pkg/text/template/#pkg-overview\[ra]].
... ...
@@ -69,6 +69,10 @@ Update existing container image(s) of resources.
69 69
     If non\-empty, sort list types using this field specification.  The field specification is expressed as a JSONPath expression (e.g. '{.metadata.name}'). The field in the API resource specified by this JSONPath expression must be an integer or a string.
70 70
 
71 71
 .PP
72
+\fB\-\-source\fP="istag"
73
+    The image source type; valid types are valid values are 'imagestreamtag', 'istag', 'imagestreamimage', 'isimage', and 'docker'
74
+
75
+.PP
72 76
 \fB\-\-template\fP=""
73 77
     Template string or path to template file to use when \-o=go\-template, \-o=go\-template\-file. The template format is golang templates [
74 78
 \[la]http://golang.org/pkg/text/template/#pkg-overview\[ra]].
... ...
@@ -83,5 +83,9 @@ func NewCmdImage(fullName string, f *clientcmd.Factory, out io.Writer) *cobra.Co
83 83
 	cmd := set.NewCmdImage(f.Factory, out)
84 84
 	cmd.Long = setImageLong
85 85
 	cmd.Example = fmt.Sprintf(setImageExample, fullName)
86
+
87
+	flags := cmd.Flags()
88
+	f.ImageResolutionOptions.Bind(flags)
89
+
86 90
 	return cmd
87 91
 }
... ...
@@ -53,6 +53,7 @@ import (
53 53
 	deploycmd "github.com/openshift/origin/pkg/deploy/cmd"
54 54
 	deployutil "github.com/openshift/origin/pkg/deploy/util"
55 55
 	imageapi "github.com/openshift/origin/pkg/image/api"
56
+	imageutil "github.com/openshift/origin/pkg/image/util"
56 57
 	routegen "github.com/openshift/origin/pkg/route/generator"
57 58
 	userapi "github.com/openshift/origin/pkg/user/api"
58 59
 	authenticationreaper "github.com/openshift/origin/pkg/user/reaper"
... ...
@@ -167,6 +168,8 @@ type Factory struct {
167 167
 	*cmdutil.Factory
168 168
 	OpenShiftClientConfig kclientcmd.ClientConfig
169 169
 	clients               *clientCache
170
+
171
+	ImageResolutionOptions FlagBinder
170 172
 }
171 173
 
172 174
 func DefaultGenerators(cmdName string) map[string]kubectl.Generator {
... ...
@@ -193,9 +196,10 @@ func NewFactory(clientConfig kclientcmd.ClientConfig) *Factory {
193 193
 	}
194 194
 
195 195
 	w := &Factory{
196
-		Factory:               cmdutil.NewFactory(clientConfig),
197
-		OpenShiftClientConfig: clientConfig,
198
-		clients:               clients,
196
+		Factory:                cmdutil.NewFactory(clientConfig),
197
+		OpenShiftClientConfig:  clientConfig,
198
+		clients:                clients,
199
+		ImageResolutionOptions: &imageResolutionOptions{},
199 200
 	}
200 201
 
201 202
 	w.Object = func(bool) (meta.RESTMapper, runtime.ObjectTyper) {
... ...
@@ -599,6 +603,22 @@ func NewFactory(clientConfig kclientcmd.ClientConfig) *Factory {
599 599
 			return kResumeObjectFunc(object)
600 600
 		}
601 601
 	}
602
+	kResolveImageFunc := w.Factory.ResolveImage
603
+	w.Factory.ResolveImage = func(image string) (string, error) {
604
+		options := w.ImageResolutionOptions.(*imageResolutionOptions)
605
+		if imageutil.IsDocker(options.Source) {
606
+			return kResolveImageFunc(image)
607
+		}
608
+		oc, _, err := w.Clients()
609
+		if err != nil {
610
+			return "", err
611
+		}
612
+		namespace, _, err := w.DefaultNamespace()
613
+		if err != nil {
614
+			return "", err
615
+		}
616
+		return imageutil.ResolveImagePullSpec(oc, oc, options.Source, image, namespace)
617
+	}
602 618
 	kHistoryViewerFunc := w.Factory.HistoryViewer
603 619
 	w.Factory.HistoryViewer = func(mapping *meta.RESTMapping) (kubectl.HistoryViewer, error) {
604 620
 		switch mapping.GroupVersionKind.GroupKind() {
... ...
@@ -627,6 +647,34 @@ func NewFactory(clientConfig kclientcmd.ClientConfig) *Factory {
627 627
 	return w
628 628
 }
629 629
 
630
+// FlagBinder represents an interface that allows to bind extra flags into commands.
631
+type FlagBinder interface {
632
+	// Bound returns true if the flag is already bound to a command.
633
+	Bound() bool
634
+	// Bind allows to bind an extra flag to a command
635
+	Bind(*pflag.FlagSet)
636
+}
637
+
638
+// ImageResolutionOptions provides the "--source" flag to commands that deal with images
639
+// and need to provide extra capabilities for working with ImageStreamTags and
640
+// ImageStreamImages.
641
+type imageResolutionOptions struct {
642
+	bound  bool
643
+	Source string
644
+}
645
+
646
+func (o *imageResolutionOptions) Bound() bool {
647
+	return o.bound
648
+}
649
+
650
+func (o *imageResolutionOptions) Bind(f *pflag.FlagSet) {
651
+	if o.Bound() {
652
+		return
653
+	}
654
+	f.StringVarP(&o.Source, "source", "", "istag", "The image source type; valid types are valid values are 'imagestreamtag', 'istag', 'imagestreamimage', 'isimage', and 'docker'")
655
+	o.bound = true
656
+}
657
+
630 658
 // useDiscoveryRESTMapper checks the server version to see if its recent enough to have
631 659
 // enough discovery information avaiable to reliably build a RESTMapper.  If not, use the
632 660
 // hardcoded mapper in this client (legacy behavior)
633 661
new file mode 100644
... ...
@@ -0,0 +1,82 @@
0
+package util
1
+
2
+import (
3
+	"fmt"
4
+	"strings"
5
+
6
+	"github.com/openshift/origin/pkg/client"
7
+	imageapi "github.com/openshift/origin/pkg/image/api"
8
+)
9
+
10
+// ResolveImagePullSpec resolves the provided source which can be "docker", "istag" or
11
+// "isimage" and returns the full Docker pull spec.
12
+func ResolveImagePullSpec(images client.ImageStreamImagesNamespacer, tags client.ImageStreamTagsNamespacer, source, name, defaultNamespace string) (string, error) {
13
+	// for Docker source, just passtrough the image name
14
+	if IsDocker(source) {
15
+		return name, nil
16
+	}
17
+	// parse the namespace from the provided image
18
+	namespace, image := splitNamespaceAndImage(name)
19
+	if len(namespace) == 0 {
20
+		namespace = defaultNamespace
21
+	}
22
+
23
+	dockerImageReference := ""
24
+
25
+	if IsImageStreamTag(source) {
26
+		name, tag, ok := imageapi.SplitImageStreamTag(image)
27
+		if !ok {
28
+			return "", fmt.Errorf("invalid image stream tag %q, must be of the form [NAMESPACE/]NAME:TAG", name)
29
+		}
30
+		if resolved, err := tags.ImageStreamTags(namespace).Get(name, tag); err != nil {
31
+			return "", fmt.Errorf("failed to get image stream tag %q: %v", name, err)
32
+		} else {
33
+			dockerImageReference = resolved.Image.DockerImageReference
34
+		}
35
+	}
36
+
37
+	if IsImageStreamImage(source) {
38
+		name, digest, ok := imageapi.SplitImageStreamImage(image)
39
+		if !ok {
40
+			return "", fmt.Errorf("invalid image stream image %q, must be of the form [NAMESPACE/]NAME@DIGEST", name)
41
+		}
42
+		if resolved, err := images.ImageStreamImages(namespace).Get(name, digest); err != nil {
43
+			return "", fmt.Errorf("failed to get image stream image %q: %v", name, err)
44
+		} else {
45
+			dockerImageReference = resolved.Image.DockerImageReference
46
+		}
47
+	}
48
+
49
+	if len(dockerImageReference) == 0 {
50
+		return "", fmt.Errorf("unable to resolve %s %q", source, name)
51
+	}
52
+
53
+	reference, err := imageapi.ParseDockerImageReference(dockerImageReference)
54
+	if err != nil {
55
+		return "", err
56
+	}
57
+	return reference.String(), nil
58
+}
59
+
60
+func IsDocker(source string) bool {
61
+	return source == "docker"
62
+}
63
+
64
+func IsImageStreamTag(source string) bool {
65
+	return source == "istag" || source == "imagestreamtag"
66
+}
67
+
68
+func IsImageStreamImage(source string) bool {
69
+	return source == "isimage" || source == "imagestreamimage"
70
+}
71
+
72
+func splitNamespaceAndImage(name string) (string, string) {
73
+	namespace := ""
74
+	imageName := ""
75
+	if parts := strings.Split(name, "/"); len(parts) == 2 {
76
+		namespace, imageName = parts[0], parts[1]
77
+	} else if len(parts) == 1 {
78
+		imageName = parts[0]
79
+	}
80
+	return namespace, imageName
81
+}
0 82
new file mode 100644
... ...
@@ -0,0 +1,99 @@
0
+package util
1
+
2
+import (
3
+	"testing"
4
+
5
+	kapi "k8s.io/kubernetes/pkg/api"
6
+
7
+	"github.com/openshift/origin/pkg/client/testclient"
8
+	imageapi "github.com/openshift/origin/pkg/image/api"
9
+)
10
+
11
+func image(pullSpec string) *imageapi.Image {
12
+	return &imageapi.Image{
13
+		ObjectMeta:           kapi.ObjectMeta{Name: "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"},
14
+		DockerImageReference: pullSpec,
15
+	}
16
+}
17
+
18
+func isimage(name, pullSpec string) *imageapi.ImageStreamImage {
19
+	i := image(pullSpec)
20
+	return &imageapi.ImageStreamImage{
21
+		ObjectMeta: kapi.ObjectMeta{Name: name, Namespace: "default"},
22
+		Image:      *i,
23
+	}
24
+}
25
+
26
+func istag(name, pullSpec string) *imageapi.ImageStreamTag {
27
+	i := image(pullSpec)
28
+	return &imageapi.ImageStreamTag{
29
+		ObjectMeta: kapi.ObjectMeta{Name: name, Namespace: "default"},
30
+		Image:      *i,
31
+	}
32
+}
33
+
34
+func TestResolveImagePullSpec(t *testing.T) {
35
+	testCases := []struct {
36
+		client    *testclient.Fake
37
+		source    string
38
+		input     string
39
+		expect    string
40
+		expectErr bool
41
+	}{
42
+		{
43
+			client: testclient.NewSimpleFake(isimage("test@sha256:foo", "registry.url/image/test:latest")),
44
+			source: "isimage",
45
+			input:  "test@sha256:foo",
46
+			expect: "registry.url/image/test:latest",
47
+		},
48
+		{
49
+			client: testclient.NewSimpleFake(istag("test:1.1", "registry.url/image/test:latest")),
50
+			source: "istag",
51
+			input:  "test:1.1",
52
+			expect: "registry.url/image/test:latest",
53
+		},
54
+		{
55
+			client: testclient.NewSimpleFake(istag("test:1.1", "registry.url/image/test:latest")),
56
+			source: "istag",
57
+			input:  "user/test:1.1",
58
+			expect: "registry.url/image/test:latest",
59
+		},
60
+		{
61
+			client: testclient.NewSimpleFake(),
62
+			source: "docker",
63
+			input:  "test:latest",
64
+			expect: "test:latest",
65
+		},
66
+		{
67
+			client:    testclient.NewSimpleFake(),
68
+			source:    "istag",
69
+			input:     "test:1.2",
70
+			expectErr: true,
71
+		},
72
+		{
73
+			client:    testclient.NewSimpleFake(),
74
+			source:    "istag",
75
+			input:     "test:1.2",
76
+			expectErr: true,
77
+		},
78
+		{
79
+			client:    testclient.NewSimpleFake(),
80
+			source:    "unknown",
81
+			input:     "",
82
+			expectErr: true,
83
+		},
84
+	}
85
+
86
+	for i, test := range testCases {
87
+		t.Logf("[%d] trying to resolve %q %s and expecting %q (expectErr=%t)", i, test.source, test.input, test.expect, test.expectErr)
88
+		result, err := ResolveImagePullSpec(test.client, test.client, test.source, test.input, "default")
89
+		if err != nil && !test.expectErr {
90
+			t.Errorf("[%d] unexpected error: %v", i, err)
91
+		} else if err == nil && test.expectErr {
92
+			t.Errorf("[%d] expected error but got none and result %q", i, result)
93
+		}
94
+		if test.expect != result {
95
+			t.Errorf("[%d] expected %q, but got %q", i, test.expect, result)
96
+		}
97
+	}
98
+}
... ...
@@ -134,7 +134,7 @@ os::test::junit::declare_suite_end
134 134
 
135 135
 os::test::junit::declare_suite_start "cmd/deployments/setimage"
136 136
 os::cmd::expect_success 'oc create -f test/integration/testdata/test-deployment-config.yaml'
137
-os::cmd::expect_success 'oc set image dc/test-deployment-config ruby-helloworld=myshinynewimage'
137
+os::cmd::expect_success 'oc set image dc/test-deployment-config ruby-helloworld=myshinynewimage --source=docker'
138 138
 os::cmd::expect_success_and_text "oc get dc/test-deployment-config -o jsonpath='{.spec.template.spec.containers[0].image}'" "myshinynewimage"
139 139
 os::cmd::expect_success 'oc delete dc/test-deployment-config'
140 140
 echo "set image: ok"
141 141
new file mode 100755
... ...
@@ -0,0 +1,43 @@
0
+#!/bin/bash
1
+source "$(dirname "${BASH_SOURCE}")/../../hack/lib/init.sh"
2
+trap os::test::junit::reconcile_output EXIT
3
+
4
+# Cleanup cluster resources created by this test
5
+(
6
+  set +e
7
+  oc delete all,templates --all
8
+  exit 0
9
+) &>/dev/null
10
+
11
+
12
+os::test::junit::declare_suite_start "cmd/oc/set/image"
13
+os::cmd::expect_success 'oc create -f test/integration/testdata/test-deployment-config.yaml'
14
+os::cmd::expect_success 'oc create -f examples/hello-openshift/hello-pod.json'
15
+
16
+os::cmd::expect_success 'oc set image dc/test-deployment-config ruby-helloworld=openshift/ruby:2.3'
17
+os::cmd::expect_success_and_text "oc get dc/test-deployment-config -o jsonpath='{.spec.template.spec.containers[0].image}'" 'centos/ruby-23-centos7@sha256'
18
+
19
+os::cmd::expect_success 'oc set image dc/test-deployment-config ruby-helloworld=openshift/ruby:2.0'
20
+os::cmd::expect_success_and_text "oc get dc/test-deployment-config -o jsonpath='{.spec.template.spec.containers[0].image}'" 'openshift/ruby-20-centos7@sha256'
21
+
22
+os::cmd::expect_success 'oc set image dc/test-deployment-config ruby-helloworld=openshift/ruby:2.0'
23
+os::cmd::expect_success_and_text "oc get dc/test-deployment-config -o jsonpath='{.spec.template.spec.containers[0].image}'" 'openshift/ruby-20-centos7@sha256'
24
+
25
+os::cmd::expect_failure 'oc set image dc/test-deployment-config ruby-helloworld=openshift/ruby:XYZ'
26
+os::cmd::expect_failure 'oc set image dc/test-deployment-config ruby-helloworld=openshift/ruby:XYZ --source=isimage'
27
+
28
+os::cmd::expect_success 'oc set image dc/test-deployment-config ruby-helloworld=nginx --source=docker'
29
+os::cmd::expect_success_and_text "oc get dc/test-deployment-config -o jsonpath='{.spec.template.spec.containers[0].image}'" 'nginx'
30
+
31
+os::cmd::expect_success 'oc set image pod/hello-openshift hello-openshift=nginx --source=docker'
32
+os::cmd::expect_success_and_text "oc get pod/hello-openshift -o jsonpath='{.spec.containers[0].image}'" 'nginx'
33
+
34
+os::cmd::expect_success 'oc set image pod/hello-openshift hello-openshift=nginx:1.9.1 --source=docker'
35
+os::cmd::expect_success_and_text "oc get pod/hello-openshift -o jsonpath='{.spec.containers[0].image}'" 'nginx:1.9.1'
36
+
37
+os::cmd::expect_success 'oc set image pods,dc *=openshift/ruby:2.3 --all'
38
+os::cmd::expect_success_and_text "oc get pod/hello-openshift -o jsonpath='{.spec.containers[0].image}'" 'centos/ruby-23-centos7@sha256'
39
+os::cmd::expect_success_and_text "oc get dc/test-deployment-config -o jsonpath='{.spec.template.spec.containers[0].image}'" 'centos/ruby-23-centos7@sha256'
40
+
41
+echo "set-image: ok"
42
+os::test::junit::declare_suite_end