Skip to content

Commit

Permalink
Adds image util to parse repo, image and tags from container images
Browse files Browse the repository at this point in the history
  • Loading branch information
gab-satchi committed Mar 31, 2020
1 parent ecff70a commit 24b30f3
Show file tree
Hide file tree
Showing 4 changed files with 211 additions and 18 deletions.
30 changes: 15 additions & 15 deletions controlplane/kubeadm/internal/workload_cluster_coredns.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,7 @@ package internal
import (
"context"
"fmt"

"github.com/coredns/corefile-migration/migration"
"github.com/docker/distribution/reference"
"github.com/pkg/errors"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
Expand All @@ -30,6 +28,7 @@ import (
kubeadmv1 "sigs.k8s.io/cluster-api/bootstrap/kubeadm/types/v1beta1"
controlplanev1 "sigs.k8s.io/cluster-api/controlplane/kubeadm/api/v1alpha3"
"sigs.k8s.io/cluster-api/util"
"sigs.k8s.io/cluster-api/util/image"
"sigs.k8s.io/cluster-api/util/patch"
ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"
)
Expand Down Expand Up @@ -142,31 +141,32 @@ func (w *Workload) getCoreDNSInfo(ctx context.Context, clusterConfig *kubeadmv1.
return nil, errors.Errorf("failed to update coredns deployment: deployment spec has no %q container", coreDNSKey)
}

// Parse container image.
parsedImage, err := reference.ParseNormalizedNamed(container.Image)
r, err := image.GetRepository(container.Image)
if err != nil {
return nil, errors.Wrapf(err, "unable to parse %q deployment image", container.Image)
return nil, errors.Wrapf(err, "unable to parse repository from %q deployment image", container.Image)
}

// Handle imageRepository.
toImageRepository := fmt.Sprintf("%s/%s", reference.Domain(parsedImage), reference.Path(parsedImage))
imageName, err := image.GetImage(container.Image)
if err != nil {
return nil, errors.Wrapf(err, "unable to parse image name from %q deployment image", container.Image)
}
toImageRepository := fmt.Sprintf("%s/%s", r, imageName)
if clusterConfig.ImageRepository != "" {
toImageRepository = fmt.Sprintf("%s/%s", clusterConfig.ImageRepository, reference.Path(parsedImage))
toImageRepository = fmt.Sprintf("%s/%s", clusterConfig.ImageRepository, imageName)
}
if clusterConfig.DNS.ImageRepository != "" {
toImageRepository = fmt.Sprintf("%s/%s", clusterConfig.DNS.ImageRepository, reference.Path(parsedImage))
toImageRepository = fmt.Sprintf("%s/%s", clusterConfig.DNS.ImageRepository, imageName)
}

// Handle imageTag.
imageRefTag, ok := parsedImage.(reference.Tagged)
if !ok {
imageRefTag, err := image.GetTag(container.Image)
if err != nil {
return nil, errors.Errorf("failed to update coredns deployment: does not have a valid image tag: %q", container.Image)
}
currentMajorMinorPatch, err := extractImageVersion(imageRefTag.Tag())
currentMajorMinorPatch, err := extractImageVersion(imageRefTag)
if err != nil {
return nil, err
}
toImageTag := imageRefTag.Tag()
toImageTag := imageRefTag
if clusterConfig.DNS.ImageTag != "" {
toImageTag = clusterConfig.DNS.ImageTag
}
Expand All @@ -180,7 +180,7 @@ func (w *Workload) getCoreDNSInfo(ctx context.Context, clusterConfig *kubeadmv1.
Deployment: deployment,
CurrentMajorMinorPatch: currentMajorMinorPatch,
TargetMajorMinorPatch: targetMajorMinorPatch,
FromImageTag: imageRefTag.Tag(),
FromImageTag: imageRefTag,
ToImageTag: toImageTag,
FromImage: container.Image,
ToImage: fmt.Sprintf("%s:%s", toImageRepository, toImageTag),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ func TestUpdateCoreDNSCorefile(t *testing.T) {

func TestGetCoreDNSInfo(t *testing.T) {
t.Run("get coredns info", func(t *testing.T) {
expectedImage := "k8s.gcr.io/coredns:1.6.2"
expectedImage := "k8s.gcr.io/some-folder/coredns:1.6.2"
depl := &appsv1.Deployment{
TypeMeta: v1.TypeMeta{
Kind: "Deployment",
Expand Down Expand Up @@ -345,14 +345,14 @@ func TestGetCoreDNSInfo(t *testing.T) {
name: "uses global config ImageRepository if DNS ImageRepository is not set",
objs: []runtime.Object{depl, cm},
clusterConfig: &kubeadmv1.ClusterConfiguration{
ImageRepository: "globalRepo",
ImageRepository: "globalRepo/sub-path",
DNS: kubeadmv1.DNS{
ImageMeta: kubeadmv1.ImageMeta{
ImageTag: "1.7.2-foobar.1",
},
},
},
toImage: "globalRepo/coredns:1.7.2-foobar.1",
toImage: "globalRepo/sub-path/coredns:1.7.2-foobar.1",
},
{
name: "uses DNS ImageRepository config if both global and DNS-level are set",
Expand Down
56 changes: 56 additions & 0 deletions util/image/image.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
Copyright 2020 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package image

import (
"errors"
"strings"
)

// GetRepository returns the repository path given a full container path
func GetRepository(path string) (string, error) {
imageNameIndex := strings.LastIndex(path, "/")
if imageNameIndex == -1 {
return "", errors.New("unable to parse repository")
}
return path[:imageNameIndex], nil
}

// GetImage returns the image given a full container path
func GetImage(path string) (string, error) {
imageNameIndex := strings.LastIndex(path, "/")
if imageNameIndex == -1 {
return "", errors.New("unable to parse image")
}
remainder := path[imageNameIndex+1:]
if tagIndex := strings.IndexRune(remainder, ':'); tagIndex > -1 {
return remainder[:tagIndex], nil
}
return remainder, nil
}

// GetTag returns the image tag given a full container path
func GetTag(path string) (string, error) {
i := strings.IndexRune(path, ':')
if i == -1 {
return "", errors.New("unable to parse tag")
}
if digestIndex := strings.IndexRune(path, '@'); digestIndex > -1 {
return path[i+1 : digestIndex], nil
}
return path[i+1:], nil
}
137 changes: 137 additions & 0 deletions util/image/image_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
/*
Copyright 2020 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package image

import (
. "github.com/onsi/gomega"
"testing"
)

func TestGetRepository(t *testing.T) {
g := NewWithT(t)

var testcases = []struct {
name string
path string
repositoryName string
expectError bool
}{
{
name: "should return repository name",
path: "k8s.gcr.io/coredns:1.6.6",
repositoryName: "k8s.gcr.io",
},
{
name: "should return repository name with subpaths",
path: "k8s.gcr.io/org/coredns:1.6.6",
repositoryName: "k8s.gcr.io/org",
},
{
name: "should return repository name when tag is missing",
path: "k8s.gcr.io/coredns",
repositoryName: "k8s.gcr.io",
},
{
name: "should return repository with port",
path: "k8s.gcr.io:1234/coredns",
repositoryName: "k8s.gcr.io:1234",
},
{
name: "should error when image missing",
path: "k8s.gcr.io",
expectError: true,
},
}

for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
out, err := GetRepository(tc.path)
g.Expect(err != nil).To(Equal(tc.expectError))
g.Expect(out).To(Equal(tc.repositoryName))
})
}
}

func TestGetImage(t *testing.T) {
g := NewWithT(t)

var testcases = []struct {
name string
path string
image string
expectError bool
}{
{
name: "should return image name",
path: "k8s.gcr.io/coredns:1.6.6",
image: "coredns",
},
{
name: "should return image name when tag is missing",
path: "k8s.gcr.io/org/coredns",
image: "coredns",
},
{
name: "should error when repository is missing",
path: "coredns:1.6.6",
expectError: true,
},
}

for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
out, err := GetImage(tc.path)
g.Expect(err != nil).To(Equal(tc.expectError))
g.Expect(out).To(Equal(tc.image))
})
}
}

func TestGetTag(t *testing.T) {
g := NewWithT(t)

var testcases = []struct {
name string
path string
tag string
expectError bool
}{
{
name: "should return tag",
path: "k8s.gcr.io/coredns:v1.6.6+build1",
tag: "v1.6.6+build1",
},
{
name: "should return tag when digest is present",
path: "k8s.gcr.io/org/coredns:1.6.6@some-digest",
tag: "1.6.6",
},
{
name: "should error when tag separator ':' is missing",
path: "malformed1.6.6",
expectError: true,
},
}

for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
out, err := GetTag(tc.path)
g.Expect(err != nil).To(Equal(tc.expectError))
g.Expect(out).To(Equal(tc.tag))
})
}
}

0 comments on commit 24b30f3

Please sign in to comment.