Browse code

Properly version Docker image metadata sent to the API

Create a new internal object image/api.DockerImage which has two
versions, "1.0" and "pre012" to match the docker client and docker
registry API versioning. Allow the version of that API to be
passed when submitting an image - if not passed, assume that the
object is "1.0" (which is our legacy behavior). Clients should
set "apiVersion" inside the "DockerImageMetadata" field for maximum
forward compatibility to "1.0".

TODO: Upstream should allow the ability to decode and default a
kind and apiVersion for maximum flexibility.

Clayton Coleman authored on 2015/01/19 10:59:31
Showing 16 changed files
... ...
@@ -19,6 +19,7 @@ import (
19 19
 	_ "github.com/openshift/origin/pkg/api/latest"
20 20
 	"github.com/openshift/origin/pkg/api/v1beta1"
21 21
 	config "github.com/openshift/origin/pkg/config/api"
22
+	image "github.com/openshift/origin/pkg/image/api"
22 23
 	template "github.com/openshift/origin/pkg/template/api"
23 24
 )
24 25
 
... ...
@@ -78,6 +79,14 @@ var apiObjectFuzzer = fuzz.New().NilChance(.5).NumElements(1, 1).Funcs(
78 78
 		// TODO: replace with structured type definition
79 79
 		j.Items = []runtime.RawExtension{}
80 80
 	},
81
+	func(j *image.Image, c fuzz.Continue) {
82
+		c.Fuzz(&j.ObjectMeta)
83
+		c.Fuzz(&j.DockerImageMetadata)
84
+		j.DockerImageMetadata.APIVersion = ""
85
+		j.DockerImageMetadata.Kind = ""
86
+		j.DockerImageMetadataVersion = []string{"pre012", "1.0"}[c.Rand.Intn(2)]
87
+		j.DockerImageReference = c.RandString()
88
+	},
81 89
 	func(j *config.Config, c fuzz.Continue) {
82 90
 		c.Fuzz(&j.ObjectMeta)
83 91
 		// TODO: replace with structured type definition
... ...
@@ -127,12 +136,12 @@ var apiObjectFuzzer = fuzz.New().NilChance(.5).NumElements(1, 1).Funcs(
127 127
 func runTest(t *testing.T, codec runtime.Codec, source runtime.Object) {
128 128
 	name := reflect.TypeOf(source).Elem().Name()
129 129
 	apiObjectFuzzer.Fuzz(source)
130
-	j, err := meta.Accessor(source)
131
-	if err != nil {
132
-		t.Fatalf("Unexpected error %v for %#v", err, source)
130
+	if j, err := meta.TypeAccessor(source); err == nil {
131
+		j.SetKind("")
132
+		j.SetAPIVersion("")
133
+	} else {
134
+		t.Logf("Unable to set apiversion/kind to empty on %v", reflect.TypeOf(source))
133 135
 	}
134
-	j.SetKind("")
135
-	j.SetAPIVersion("")
136 136
 
137 137
 	data, err := codec.Encode(source)
138 138
 	if err != nil {
... ...
@@ -161,6 +170,10 @@ func runTest(t *testing.T, codec runtime.Codec, source runtime.Object) {
161 161
 	}
162 162
 }
163 163
 
164
+var skipStandardVersions = map[string][]string{
165
+	"DockerImage": {"pre012", "1.0"},
166
+}
167
+
164 168
 func TestTypes(t *testing.T) {
165 169
 	for kind, reflectType := range api.Scheme.KnownTypes("") {
166 170
 		if !strings.Contains(reflectType.PkgPath(), "/origin/") {
... ...
@@ -174,8 +187,10 @@ func TestTypes(t *testing.T) {
174 174
 				t.Errorf("Couldn't make a %v? %v", kind, err)
175 175
 				continue
176 176
 			}
177
-			if _, err := meta.Accessor(item); err != nil {
178
-				t.Logf("%s is not a ObjectMeta and cannot be round tripped: %v", kind, err)
177
+			if versions, ok := skipStandardVersions[kind]; ok {
178
+				for _, v := range versions {
179
+					runTest(t, runtime.CodecFor(api.Scheme, v), item)
180
+				}
179 181
 				continue
180 182
 			}
181 183
 			runTest(t, v1beta1.Codec, item)
182 184
new file mode 100644
... ...
@@ -0,0 +1,55 @@
0
+package api
1
+
2
+import (
3
+	"github.com/fsouza/go-dockerclient"
4
+
5
+	kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
6
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/conversion"
7
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
8
+)
9
+
10
+func init() {
11
+	err := kapi.Scheme.AddConversionFuncs(
12
+		// Convert docker client object to internal object
13
+		func(in *docker.Image, out *DockerImage, s conversion.Scope) error {
14
+			if err := s.Convert(in.Config, &out.Config, conversion.AllowDifferentFieldTypeNames); err != nil {
15
+				return err
16
+			}
17
+			if err := s.Convert(&in.ContainerConfig, &out.ContainerConfig, conversion.AllowDifferentFieldTypeNames); err != nil {
18
+				return err
19
+			}
20
+			out.ID = in.ID
21
+			out.Parent = in.Parent
22
+			out.Comment = in.Comment
23
+			out.Created = util.NewTime(in.Created)
24
+			out.Container = in.Container
25
+			out.DockerVersion = in.DockerVersion
26
+			out.Author = in.Author
27
+			out.Architecture = in.Architecture
28
+			out.Size = in.Size
29
+			return nil
30
+		},
31
+		func(in *DockerImage, out *docker.Image, s conversion.Scope) error {
32
+			if err := s.Convert(&in.Config, &out.Config, conversion.AllowDifferentFieldTypeNames); err != nil {
33
+				return err
34
+			}
35
+			if err := s.Convert(&in.ContainerConfig, &out.ContainerConfig, conversion.AllowDifferentFieldTypeNames); err != nil {
36
+				return err
37
+			}
38
+			out.ID = in.ID
39
+			out.Parent = in.Parent
40
+			out.Comment = in.Comment
41
+			out.Created = in.Created.Time
42
+			out.Container = in.Container
43
+			out.DockerVersion = in.DockerVersion
44
+			out.Author = in.Author
45
+			out.Architecture = in.Architecture
46
+			out.Size = in.Size
47
+			return nil
48
+		},
49
+	)
50
+	if err != nil {
51
+		// If one of the conversion functions is malformed, detect it immediately.
52
+		panic(err)
53
+	}
54
+}
0 55
new file mode 100644
... ...
@@ -0,0 +1,52 @@
0
+package docker10
1
+
2
+import (
3
+	kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
4
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
5
+)
6
+
7
+// Image is the type representing a docker image and its various properties when
8
+// retrieved from the Docker client API.
9
+type DockerImage struct {
10
+	kapi.TypeMeta `json:",inline" yaml:",inline"`
11
+
12
+	ID              string       `json:"Id" yaml:"Id"`
13
+	Parent          string       `json:"Parent,omitempty" yaml:"Parent,omitempty"`
14
+	Comment         string       `json:"Comment,omitempty" yaml:"Comment,omitempty"`
15
+	Created         util.Time    `json:"Created,omitempty" yaml:"Created,omitempty"`
16
+	Container       string       `json:"Container,omitempty" yaml:"Container,omitempty"`
17
+	ContainerConfig DockerConfig `json:"ContainerConfig,omitempty" yaml:"ContainerConfig,omitempty"`
18
+	DockerVersion   string       `json:"DockerVersion,omitempty" yaml:"DockerVersion,omitempty"`
19
+	Author          string       `json:"Author,omitempty" yaml:"Author,omitempty"`
20
+	Config          DockerConfig `json:"Config,omitempty" yaml:"Config,omitempty"`
21
+	Architecture    string       `json:"Architecture,omitempty" yaml:"Architecture,omitempty"`
22
+	Size            int64        `json:"Size,omitempty" yaml:"Size,omitempty"`
23
+}
24
+
25
+// DockerConfig is the list of configuration options used when creating a container.
26
+type DockerConfig struct {
27
+	Hostname        string              `json:"Hostname,omitempty" yaml:"Hostname,omitempty"`
28
+	Domainname      string              `json:"Domainname,omitempty" yaml:"Domainname,omitempty"`
29
+	User            string              `json:"User,omitempty" yaml:"User,omitempty"`
30
+	Memory          int64               `json:"Memory,omitempty" yaml:"Memory,omitempty"`
31
+	MemorySwap      int64               `json:"MemorySwap,omitempty" yaml:"MemorySwap,omitempty"`
32
+	CPUShares       int64               `json:"CpuShares,omitempty" yaml:"CpuShares,omitempty"`
33
+	CPUSet          string              `json:"Cpuset,omitempty" yaml:"Cpuset,omitempty"`
34
+	AttachStdin     bool                `json:"AttachStdin,omitempty" yaml:"AttachStdin,omitempty"`
35
+	AttachStdout    bool                `json:"AttachStdout,omitempty" yaml:"AttachStdout,omitempty"`
36
+	AttachStderr    bool                `json:"AttachStderr,omitempty" yaml:"AttachStderr,omitempty"`
37
+	PortSpecs       []string            `json:"PortSpecs,omitempty" yaml:"PortSpecs,omitempty"`
38
+	ExposedPorts    map[string]struct{} `json:"ExposedPorts,omitempty" yaml:"ExposedPorts,omitempty"`
39
+	Tty             bool                `json:"Tty,omitempty" yaml:"Tty,omitempty"`
40
+	OpenStdin       bool                `json:"OpenStdin,omitempty" yaml:"OpenStdin,omitempty"`
41
+	StdinOnce       bool                `json:"StdinOnce,omitempty" yaml:"StdinOnce,omitempty"`
42
+	Env             []string            `json:"Env,omitempty" yaml:"Env,omitempty"`
43
+	Cmd             []string            `json:"Cmd,omitempty" yaml:"Cmd,omitempty"`
44
+	DNS             []string            `json:"Dns,omitempty" yaml:"Dns,omitempty"` // For Docker API v1.9 and below only
45
+	Image           string              `json:"Image,omitempty" yaml:"Image,omitempty"`
46
+	Volumes         map[string]struct{} `json:"Volumes,omitempty" yaml:"Volumes,omitempty"`
47
+	VolumesFrom     string              `json:"VolumesFrom,omitempty" yaml:"VolumesFrom,omitempty"`
48
+	WorkingDir      string              `json:"WorkingDir,omitempty" yaml:"WorkingDir,omitempty"`
49
+	Entrypoint      []string            `json:"Entrypoint,omitempty" yaml:"Entrypoint,omitempty"`
50
+	NetworkDisabled bool                `json:"NetworkDisabled,omitempty" yaml:"NetworkDisabled,omitempty"`
51
+}
0 52
new file mode 100644
... ...
@@ -0,0 +1,13 @@
0
+package docker10
1
+
2
+import (
3
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
4
+)
5
+
6
+func init() {
7
+	api.Scheme.AddKnownTypes("1.0",
8
+		&DockerImage{},
9
+	)
10
+}
11
+
12
+func (*DockerImage) IsAnAPIObject() {}
0 13
new file mode 100644
... ...
@@ -0,0 +1,57 @@
0
+package dockerpre012
1
+
2
+import (
3
+	"github.com/fsouza/go-dockerclient"
4
+
5
+	kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
6
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/conversion"
7
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
8
+
9
+	newer "github.com/openshift/origin/pkg/image/api"
10
+)
11
+
12
+func init() {
13
+	err := kapi.Scheme.AddConversionFuncs(
14
+		// Convert docker client object to internal object, but only when this package is included
15
+		func(in *docker.ImagePre012, out *newer.DockerImage, s conversion.Scope) error {
16
+			if err := s.Convert(in.Config, &out.Config, conversion.AllowDifferentFieldTypeNames); err != nil {
17
+				return err
18
+			}
19
+			if err := s.Convert(&in.ContainerConfig, &out.ContainerConfig, conversion.AllowDifferentFieldTypeNames); err != nil {
20
+				return err
21
+			}
22
+			out.ID = in.ID
23
+			out.Parent = in.Parent
24
+			out.Comment = in.Comment
25
+			out.Created = util.NewTime(in.Created)
26
+			out.Container = in.Container
27
+			out.DockerVersion = in.DockerVersion
28
+			out.Author = in.Author
29
+			out.Architecture = in.Architecture
30
+			out.Size = in.Size
31
+			return nil
32
+		},
33
+		func(in *newer.DockerImage, out *docker.ImagePre012, s conversion.Scope) error {
34
+			if err := s.Convert(&in.Config, &out.Config, conversion.AllowDifferentFieldTypeNames); err != nil {
35
+				return err
36
+			}
37
+			if err := s.Convert(&in.ContainerConfig, &out.ContainerConfig, conversion.AllowDifferentFieldTypeNames); err != nil {
38
+				return err
39
+			}
40
+			out.ID = in.ID
41
+			out.Parent = in.Parent
42
+			out.Comment = in.Comment
43
+			out.Created = in.Created.Time
44
+			out.Container = in.Container
45
+			out.DockerVersion = in.DockerVersion
46
+			out.Author = in.Author
47
+			out.Architecture = in.Architecture
48
+			out.Size = in.Size
49
+			return nil
50
+		},
51
+	)
52
+	if err != nil {
53
+		// If one of the conversion functions is malformed, detect it immediately.
54
+		panic(err)
55
+	}
56
+}
0 57
new file mode 100644
... ...
@@ -0,0 +1,52 @@
0
+package dockerpre012
1
+
2
+import (
3
+	kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
4
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
5
+)
6
+
7
+// DockerImage is for earlier versions of the Docker API (pre-012 to be specific). It is also the
8
+// version of metadata that the Docker registry uses to persist metadata.
9
+type DockerImage struct {
10
+	kapi.TypeMeta `json:",inline" yaml:",inline"`
11
+
12
+	ID              string       `json:"id"`
13
+	Parent          string       `json:"parent,omitempty"`
14
+	Comment         string       `json:"comment,omitempty"`
15
+	Created         util.Time    `json:"created"`
16
+	Container       string       `json:"container,omitempty"`
17
+	ContainerConfig DockerConfig `json:"container_config,omitempty"`
18
+	DockerVersion   string       `json:"docker_version,omitempty"`
19
+	Author          string       `json:"author,omitempty"`
20
+	Config          DockerConfig `json:"config,omitempty"`
21
+	Architecture    string       `json:"architecture,omitempty"`
22
+	Size            int64        `json:"size,omitempty"`
23
+}
24
+
25
+// DockerConfig is the list of configuration options used when creating a container.
26
+type DockerConfig struct {
27
+	Hostname        string              `json:"Hostname,omitempty" yaml:"Hostname,omitempty"`
28
+	Domainname      string              `json:"Domainname,omitempty" yaml:"Domainname,omitempty"`
29
+	User            string              `json:"User,omitempty" yaml:"User,omitempty"`
30
+	Memory          int64               `json:"Memory,omitempty" yaml:"Memory,omitempty"`
31
+	MemorySwap      int64               `json:"MemorySwap,omitempty" yaml:"MemorySwap,omitempty"`
32
+	CPUShares       int64               `json:"CpuShares,omitempty" yaml:"CpuShares,omitempty"`
33
+	CPUSet          string              `json:"Cpuset,omitempty" yaml:"Cpuset,omitempty"`
34
+	AttachStdin     bool                `json:"AttachStdin,omitempty" yaml:"AttachStdin,omitempty"`
35
+	AttachStdout    bool                `json:"AttachStdout,omitempty" yaml:"AttachStdout,omitempty"`
36
+	AttachStderr    bool                `json:"AttachStderr,omitempty" yaml:"AttachStderr,omitempty"`
37
+	PortSpecs       []string            `json:"PortSpecs,omitempty" yaml:"PortSpecs,omitempty"`
38
+	ExposedPorts    map[string]struct{} `json:"ExposedPorts,omitempty" yaml:"ExposedPorts,omitempty"`
39
+	Tty             bool                `json:"Tty,omitempty" yaml:"Tty,omitempty"`
40
+	OpenStdin       bool                `json:"OpenStdin,omitempty" yaml:"OpenStdin,omitempty"`
41
+	StdinOnce       bool                `json:"StdinOnce,omitempty" yaml:"StdinOnce,omitempty"`
42
+	Env             []string            `json:"Env,omitempty" yaml:"Env,omitempty"`
43
+	Cmd             []string            `json:"Cmd,omitempty" yaml:"Cmd,omitempty"`
44
+	DNS             []string            `json:"Dns,omitempty" yaml:"Dns,omitempty"` // For Docker API v1.9 and below only
45
+	Image           string              `json:"Image,omitempty" yaml:"Image,omitempty"`
46
+	Volumes         map[string]struct{} `json:"Volumes,omitempty" yaml:"Volumes,omitempty"`
47
+	VolumesFrom     string              `json:"VolumesFrom,omitempty" yaml:"VolumesFrom,omitempty"`
48
+	WorkingDir      string              `json:"WorkingDir,omitempty" yaml:"WorkingDir,omitempty"`
49
+	Entrypoint      []string            `json:"Entrypoint,omitempty" yaml:"Entrypoint,omitempty"`
50
+	NetworkDisabled bool                `json:"NetworkDisabled,omitempty" yaml:"NetworkDisabled,omitempty"`
51
+}
0 52
new file mode 100644
... ...
@@ -0,0 +1,13 @@
0
+package dockerpre012
1
+
2
+import (
3
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
4
+)
5
+
6
+func init() {
7
+	api.Scheme.AddKnownTypes("pre012",
8
+		&DockerImage{},
9
+	)
10
+}
11
+
12
+func (*DockerImage) IsAnAPIObject() {}
0 13
new file mode 100644
... ...
@@ -0,0 +1,52 @@
0
+package api
1
+
2
+import (
3
+	kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
4
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
5
+)
6
+
7
+// Image is the type representing a docker image and its various properties when
8
+// retrieved from the Docker client API.
9
+type DockerImage struct {
10
+	kapi.TypeMeta `json:",inline" yaml:",inline"`
11
+
12
+	ID              string       `json:"Id" yaml:"Id"`
13
+	Parent          string       `json:"Parent,omitempty" yaml:"Parent,omitempty"`
14
+	Comment         string       `json:"Comment,omitempty" yaml:"Comment,omitempty"`
15
+	Created         util.Time    `json:"Created,omitempty" yaml:"Created,omitempty"`
16
+	Container       string       `json:"Container,omitempty" yaml:"Container,omitempty"`
17
+	ContainerConfig DockerConfig `json:"ContainerConfig,omitempty" yaml:"ContainerConfig,omitempty"`
18
+	DockerVersion   string       `json:"DockerVersion,omitempty" yaml:"DockerVersion,omitempty"`
19
+	Author          string       `json:"Author,omitempty" yaml:"Author,omitempty"`
20
+	Config          DockerConfig `json:"Config,omitempty" yaml:"Config,omitempty"`
21
+	Architecture    string       `json:"Architecture,omitempty" yaml:"Architecture,omitempty"`
22
+	Size            int64        `json:"Size,omitempty" yaml:"Size,omitempty"`
23
+}
24
+
25
+// DockerConfig is the list of configuration options used when creating a container.
26
+type DockerConfig struct {
27
+	Hostname        string              `json:"Hostname,omitempty" yaml:"Hostname,omitempty"`
28
+	Domainname      string              `json:"Domainname,omitempty" yaml:"Domainname,omitempty"`
29
+	User            string              `json:"User,omitempty" yaml:"User,omitempty"`
30
+	Memory          int64               `json:"Memory,omitempty" yaml:"Memory,omitempty"`
31
+	MemorySwap      int64               `json:"MemorySwap,omitempty" yaml:"MemorySwap,omitempty"`
32
+	CPUShares       int64               `json:"CpuShares,omitempty" yaml:"CpuShares,omitempty"`
33
+	CPUSet          string              `json:"Cpuset,omitempty" yaml:"Cpuset,omitempty"`
34
+	AttachStdin     bool                `json:"AttachStdin,omitempty" yaml:"AttachStdin,omitempty"`
35
+	AttachStdout    bool                `json:"AttachStdout,omitempty" yaml:"AttachStdout,omitempty"`
36
+	AttachStderr    bool                `json:"AttachStderr,omitempty" yaml:"AttachStderr,omitempty"`
37
+	PortSpecs       []string            `json:"PortSpecs,omitempty" yaml:"PortSpecs,omitempty"`
38
+	ExposedPorts    map[string]struct{} `json:"ExposedPorts,omitempty" yaml:"ExposedPorts,omitempty"`
39
+	Tty             bool                `json:"Tty,omitempty" yaml:"Tty,omitempty"`
40
+	OpenStdin       bool                `json:"OpenStdin,omitempty" yaml:"OpenStdin,omitempty"`
41
+	StdinOnce       bool                `json:"StdinOnce,omitempty" yaml:"StdinOnce,omitempty"`
42
+	Env             []string            `json:"Env,omitempty" yaml:"Env,omitempty"`
43
+	Cmd             []string            `json:"Cmd,omitempty" yaml:"Cmd,omitempty"`
44
+	DNS             []string            `json:"Dns,omitempty" yaml:"Dns,omitempty"` // For Docker API v1.9 and below only
45
+	Image           string              `json:"Image,omitempty" yaml:"Image,omitempty"`
46
+	Volumes         map[string]struct{} `json:"Volumes,omitempty" yaml:"Volumes,omitempty"`
47
+	VolumesFrom     string              `json:"VolumesFrom,omitempty" yaml:"VolumesFrom,omitempty"`
48
+	WorkingDir      string              `json:"WorkingDir,omitempty" yaml:"WorkingDir,omitempty"`
49
+	Entrypoint      []string            `json:"Entrypoint,omitempty" yaml:"Entrypoint,omitempty"`
50
+	NetworkDisabled bool                `json:"NetworkDisabled,omitempty" yaml:"NetworkDisabled,omitempty"`
51
+}
... ...
@@ -11,6 +11,7 @@ func init() {
11 11
 		&ImageRepository{},
12 12
 		&ImageRepositoryList{},
13 13
 		&ImageRepositoryMapping{},
14
+		&DockerImage{},
14 15
 	)
15 16
 }
16 17
 
... ...
@@ -19,3 +20,4 @@ func (*ImageList) IsAnAPIObject()              {}
19 19
 func (*ImageRepository) IsAnAPIObject()        {}
20 20
 func (*ImageRepositoryList) IsAnAPIObject()    {}
21 21
 func (*ImageRepositoryMapping) IsAnAPIObject() {}
22
+func (*DockerImage) IsAnAPIObject()            {}
... ...
@@ -2,7 +2,6 @@ package api
2 2
 
3 3
 import (
4 4
 	kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
5
-	"github.com/fsouza/go-dockerclient"
6 5
 )
7 6
 
8 7
 // ImageList is a list of Image objects.
... ...
@@ -21,7 +20,9 @@ type Image struct {
21 21
 	// The string that can be used to pull this image.
22 22
 	DockerImageReference string `json:"dockerImageReference,omitempty" yaml:"dockerImageReference,omitempty"`
23 23
 	// Metadata about this image
24
-	DockerImageMetadata docker.Image `json:"dockerImageMetadata,omitempty" yaml:"dockerImageMetadata,omitempty"`
24
+	DockerImageMetadata DockerImage `json:"dockerImageMetadata,omitempty" yaml:"dockerImageMetadata,omitempty"`
25
+	// This attribute conveys the version of docker metadata the JSON should be stored in, which if empty defaults to "1.0"
26
+	DockerImageMetadataVersion string `json:"dockerImageMetadataVersion,omitempty" yaml:"dockerImageMetadata,omitempty"`
25 27
 }
26 28
 
27 29
 // ImageRepositoryList is a list of ImageRepository objects.
28 30
new file mode 100644
... ...
@@ -0,0 +1,66 @@
0
+package v1beta1
1
+
2
+import (
3
+	kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
4
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/conversion"
5
+
6
+	newer "github.com/openshift/origin/pkg/image/api"
7
+)
8
+
9
+func init() {
10
+	err := kapi.Scheme.AddConversionFuncs(
11
+		// The docker metadat must be cast to a version
12
+		func(in *newer.Image, out *Image, s conversion.Scope) error {
13
+			if err := s.Convert(&in.ObjectMeta, &out.ObjectMeta, 0); err != nil {
14
+				return err
15
+			}
16
+
17
+			out.DockerImageReference = in.DockerImageReference
18
+
19
+			version := in.DockerImageMetadataVersion
20
+			if len(version) == 0 {
21
+				version = "1.0"
22
+			}
23
+			data, err := kapi.Scheme.EncodeToVersion(&in.DockerImageMetadata, version)
24
+			if err != nil {
25
+				return err
26
+			}
27
+			out.DockerImageMetadata.RawJSON = data
28
+			out.DockerImageMetadataVersion = version
29
+
30
+			return nil
31
+		},
32
+		func(in *Image, out *newer.Image, s conversion.Scope) error {
33
+			if err := s.Convert(&in.ObjectMeta, &out.ObjectMeta, 0); err != nil {
34
+				return err
35
+			}
36
+
37
+			out.DockerImageReference = in.DockerImageReference
38
+
39
+			version := in.DockerImageMetadataVersion
40
+			if len(version) == 0 {
41
+				version = "1.0"
42
+			}
43
+			if len(in.DockerImageMetadata.RawJSON) > 0 {
44
+				// TODO: add a way to default the expected kind and version of an object if not set
45
+				obj, err := kapi.Scheme.New(version, "DockerImage")
46
+				if err != nil {
47
+					return err
48
+				}
49
+				if err := kapi.Scheme.DecodeInto(in.DockerImageMetadata.RawJSON, obj); err != nil {
50
+					return err
51
+				}
52
+				if err := s.Convert(obj, &out.DockerImageMetadata, 0); err != nil {
53
+					return err
54
+				}
55
+			}
56
+			out.DockerImageMetadataVersion = version
57
+
58
+			return nil
59
+		},
60
+	)
61
+	if err != nil {
62
+		// If one of the conversion functions is malformed, detect it immediately.
63
+		panic(err)
64
+	}
65
+}
0 66
new file mode 100644
... ...
@@ -0,0 +1,88 @@
0
+package v1beta1_test
1
+
2
+import (
3
+	"reflect"
4
+	"testing"
5
+
6
+	kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
7
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
8
+	"github.com/fsouza/go-dockerclient"
9
+
10
+	_ "github.com/openshift/origin/pkg/api/latest"
11
+	newer "github.com/openshift/origin/pkg/image/api"
12
+)
13
+
14
+var Convert = kapi.Scheme.Convert
15
+
16
+func TestRoundTripVersionedObject(t *testing.T) {
17
+	d := &newer.DockerImage{
18
+		Config: newer.DockerConfig{
19
+			Env: []string{"A=1", "B=2"},
20
+		},
21
+	}
22
+	i := &newer.Image{
23
+		ObjectMeta: kapi.ObjectMeta{Name: "foo"},
24
+
25
+		DockerImageMetadata:  *d,
26
+		DockerImageReference: "foo/bar/baz",
27
+	}
28
+
29
+	data, err := kapi.Scheme.EncodeToVersion(i, "v1beta1")
30
+	if err != nil {
31
+		t.Fatalf("unexpected error: %v", err)
32
+	}
33
+
34
+	obj, err := kapi.Scheme.Decode(data)
35
+	if err != nil {
36
+		t.Fatalf("unexpected error: %v", err)
37
+	}
38
+	image := obj.(*newer.Image)
39
+	if image.DockerImageMetadataVersion != "1.0" {
40
+		t.Errorf("did not default to correct metadata version: %#v", image)
41
+	}
42
+	image.DockerImageMetadataVersion = ""
43
+	if !reflect.DeepEqual(i, image) {
44
+		t.Errorf("unable to round trip object: %s", util.ObjectDiff(i, image))
45
+	}
46
+}
47
+
48
+// This tests that JSON generated by an older version of v1beta1 still correctly parses and versions
49
+func TestDecodeExistingAPIObjects(t *testing.T) {
50
+	obj, err := kapi.Scheme.Decode([]byte(`{
51
+		"kind":"Image",
52
+		"apiVersion":"v1beta1",
53
+		"metadata":{
54
+			"name":"foo"
55
+		},
56
+		"dockerImageReference":"foo/bar/baz",
57
+		"dockerImageMetadata":{
58
+			"Id":"0001",
59
+			"Config":{
60
+				"Env":["A=1","B=2"]
61
+				}
62
+			}
63
+		}`))
64
+	if err != nil {
65
+		t.Fatalf("unexpected error: %v", err)
66
+	}
67
+	image := obj.(*newer.Image)
68
+	if image.Name != "foo" || image.DockerImageReference != "foo/bar/baz" || image.DockerImageMetadata.ID != "0001" || image.DockerImageMetadata.Config.Env[0] != "A=1" {
69
+		t.Errorf("unexpected object: %#v", image)
70
+	}
71
+}
72
+
73
+func TestDecodeDockerRegistryJSON(t *testing.T) {
74
+	oldImage := docker.ImagePre012{
75
+		ID: "something",
76
+		Config: &docker.Config{
77
+			Env: []string{"A=1", "B=2"},
78
+		},
79
+	}
80
+	newImage := newer.DockerImage{}
81
+	if err := kapi.Scheme.Convert(&oldImage, &newImage); err != nil {
82
+		t.Fatalf("unexpected error: %v", err)
83
+	}
84
+	if newImage.ID != "something" || newImage.Config.Env[0] != "A=1" {
85
+		t.Errorf("unexpected object: %#v", newImage)
86
+	}
87
+}
... ...
@@ -2,6 +2,9 @@ package v1beta1
2 2
 
3 3
 import (
4 4
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
5
+
6
+	_ "github.com/openshift/origin/pkg/image/api/docker10"
7
+	_ "github.com/openshift/origin/pkg/image/api/dockerpre012"
5 8
 )
6 9
 
7 10
 func init() {
... ...
@@ -2,7 +2,7 @@ package v1beta1
2 2
 
3 3
 import (
4 4
 	kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta3"
5
-	"github.com/fsouza/go-dockerclient"
5
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
6 6
 )
7 7
 
8 8
 // ImageList is a list of Image objects.
... ...
@@ -21,7 +21,9 @@ type Image struct {
21 21
 	// The string that can be used to pull this image.
22 22
 	DockerImageReference string `json:"dockerImageReference,omitempty" yaml:"dockerImageReference,omitempty"`
23 23
 	// Metadata about this image
24
-	DockerImageMetadata docker.Image `json:"dockerImageMetadata,omitempty" yaml:"dockerImageMetadata,omitempty"`
24
+	DockerImageMetadata runtime.RawExtension `json:"dockerImageMetadata,omitempty" yaml:"dockerImageMetadata,omitempty"`
25
+	// This attribute conveys the version of the object, which if empty defaults to "1.0"
26
+	DockerImageMetadataVersion string `json:"dockerImageMetadataVersion,omitempty" yaml:"dockerImageMetadata,omitempty"`
25 27
 }
26 28
 
27 29
 // ImageRepositoryList is a list of ImageRepository objects.
... ...
@@ -12,9 +12,9 @@ import (
12 12
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
13 13
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
14 14
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/tools"
15
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
15 16
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
16 17
 	"github.com/coreos/go-etcd/etcd"
17
-	"github.com/fsouza/go-dockerclient"
18 18
 
19 19
 	"github.com/openshift/origin/pkg/api/latest"
20 20
 	"github.com/openshift/origin/pkg/image/api"
... ...
@@ -216,7 +216,7 @@ func TestEtcdCreateImage(t *testing.T) {
216 216
 			Name: "foo",
217 217
 		},
218 218
 		DockerImageReference: "openshift/ruby-19-centos",
219
-		DockerImageMetadata: docker.Image{
219
+		DockerImageMetadata: api.DockerImage{
220 220
 			ID: "abc123",
221 221
 		},
222 222
 	})
... ...
@@ -343,9 +343,9 @@ func TestEtcdWatchImagesOK(t *testing.T) {
343 343
 		{
344 344
 			labels.Everything(),
345 345
 			[]*api.Image{
346
-				{ObjectMeta: kapi.ObjectMeta{Name: "a"}, DockerImageMetadata: docker.Image{Created: time.Date(2000, 1, 1, 1, 1, 1, 0, time.UTC)}},
347
-				{ObjectMeta: kapi.ObjectMeta{Name: "b"}, DockerImageMetadata: docker.Image{Created: time.Date(2000, 1, 1, 1, 1, 1, 0, time.UTC)}},
348
-				{ObjectMeta: kapi.ObjectMeta{Name: "c"}, DockerImageMetadata: docker.Image{Created: time.Date(2000, 1, 1, 1, 1, 1, 0, time.UTC)}},
346
+				{ObjectMeta: kapi.ObjectMeta{Name: "a"}, DockerImageMetadata: api.DockerImage{}},
347
+				{ObjectMeta: kapi.ObjectMeta{Name: "b"}, DockerImageMetadata: api.DockerImage{}},
348
+				{ObjectMeta: kapi.ObjectMeta{Name: "c"}, DockerImageMetadata: api.DockerImage{}},
349 349
 			},
350 350
 			[]bool{
351 351
 				true,
... ...
@@ -356,9 +356,9 @@ func TestEtcdWatchImagesOK(t *testing.T) {
356 356
 		{
357 357
 			labels.SelectorFromSet(labels.Set{"color": "blue"}),
358 358
 			[]*api.Image{
359
-				{ObjectMeta: kapi.ObjectMeta{Name: "a", Labels: map[string]string{"color": "blue"}}, DockerImageMetadata: docker.Image{Created: time.Date(2000, 1, 1, 1, 1, 1, 0, time.UTC)}},
360
-				{ObjectMeta: kapi.ObjectMeta{Name: "b", Labels: map[string]string{"color": "green"}}, DockerImageMetadata: docker.Image{Created: time.Date(2000, 1, 1, 1, 1, 1, 0, time.UTC)}},
361
-				{ObjectMeta: kapi.ObjectMeta{Name: "c", Labels: map[string]string{"color": "blue"}}, DockerImageMetadata: docker.Image{Created: time.Date(2000, 1, 1, 1, 1, 1, 0, time.UTC)}},
359
+				{ObjectMeta: kapi.ObjectMeta{Name: "a", Labels: map[string]string{"color": "blue"}}, DockerImageMetadata: api.DockerImage{}},
360
+				{ObjectMeta: kapi.ObjectMeta{Name: "b", Labels: map[string]string{"color": "green"}}, DockerImageMetadata: api.DockerImage{}},
361
+				{ObjectMeta: kapi.ObjectMeta{Name: "c", Labels: map[string]string{"color": "blue"}}, DockerImageMetadata: api.DockerImage{}},
362 362
 			},
363 363
 			[]bool{
364 364
 				true,
... ...
@@ -394,8 +394,9 @@ func TestEtcdWatchImagesOK(t *testing.T) {
394 394
 				if e, a := watch.Added, event.Type; e != a {
395 395
 					t.Errorf("Expected %v, got %v", e, a)
396 396
 				}
397
+				image.DockerImageMetadataVersion = "1.0"
397 398
 				if e, a := image, event.Object; !reflect.DeepEqual(e, a) {
398
-					t.Errorf("Expected %v, got %v", e, a)
399
+					t.Errorf("Objects did not match: %s", util.ObjectDiff(e, a))
399 400
 				}
400 401
 			case <-time.After(50 * time.Millisecond):
401 402
 				if tt.expected[testIndex] {
... ...
@@ -11,7 +11,6 @@ import (
11 11
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
12 12
 	kclient "github.com/GoogleCloudPlatform/kubernetes/pkg/client"
13 13
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
14
-	"github.com/fsouza/go-dockerclient"
15 14
 
16 15
 	"github.com/openshift/origin/pkg/image/api"
17 16
 	"github.com/openshift/origin/pkg/image/registry/test"
... ...
@@ -174,11 +173,11 @@ func TestCreateImageRepositoryMapping(t *testing.T) {
174 174
 				Name: "imageID1",
175 175
 			},
176 176
 			DockerImageReference: "localhost:5000/someproject/somerepo:imageID1",
177
-			DockerImageMetadata: docker.Image{
178
-				Config: &docker.Config{
177
+			DockerImageMetadata: api.DockerImage{
178
+				Config: api.DockerConfig{
179 179
 					Cmd:          []string{"ls", "/"},
180 180
 					Env:          []string{"a=1"},
181
-					ExposedPorts: map[docker.Port]struct{}{"1234/tcp": {}},
181
+					ExposedPorts: map[string]struct{}{"1234/tcp": {}},
182 182
 					Memory:       1234,
183 183
 					CPUShares:    99,
184 184
 					WorkingDir:   "/workingDir",
... ...
@@ -240,11 +239,11 @@ func TestCreateImageRepositoryConflictingNamespace(t *testing.T) {
240 240
 				Name: "imageID1",
241 241
 			},
242 242
 			DockerImageReference: "localhost:5000/someproject/somerepo:imageID1",
243
-			DockerImageMetadata: docker.Image{
244
-				Config: &docker.Config{
243
+			DockerImageMetadata: api.DockerImage{
244
+				Config: api.DockerConfig{
245 245
 					Cmd:          []string{"ls", "/"},
246 246
 					Env:          []string{"a=1"},
247
-					ExposedPorts: map[docker.Port]struct{}{"1234/tcp": {}},
247
+					ExposedPorts: map[string]struct{}{"1234/tcp": {}},
248 248
 					Memory:       1234,
249 249
 					CPUShares:    99,
250 250
 					WorkingDir:   "/workingDir",