Skip to content

Commit

Permalink
Merge pull request #2558 from fabriziopandini/clusterctl-cert-manager…
Browse files Browse the repository at this point in the history
…-image-overrides

✨clusterctl: allow cert-manager image overrides
  • Loading branch information
k8s-ci-robot authored Mar 6, 2020
2 parents 158e998 + f07371d commit b51a61e
Show file tree
Hide file tree
Showing 13 changed files with 517 additions and 20 deletions.
4 changes: 4 additions & 0 deletions cmd/clusterctl/client/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,10 @@ func (f fakeConfigClient) Variables() config.VariablesClient {
return f.internalclient.Variables()
}

func (f fakeConfigClient) ImageMeta() config.ImageMetaClient {
return f.internalclient.ImageMeta()
}

func (f *fakeConfigClient) WithVar(key, value string) *fakeConfigClient {
f.fakeReader.WithVar(key, value)
return f
Expand Down
52 changes: 35 additions & 17 deletions cmd/clusterctl/client/cluster/cert_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ import (
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
clusterctlv1 "sigs.k8s.io/cluster-api/cmd/clusterctl/api/v1alpha3"
"sigs.k8s.io/cluster-api/cmd/clusterctl/config"
"sigs.k8s.io/cluster-api/cmd/clusterctl/client/config"
manifests "sigs.k8s.io/cluster-api/cmd/clusterctl/config"
"sigs.k8s.io/cluster-api/cmd/clusterctl/internal/util"
logf "sigs.k8s.io/cluster-api/cmd/clusterctl/log"
"sigs.k8s.io/controller-runtime/pkg/client"
Expand All @@ -37,6 +38,8 @@ const (

retryCreateCertManagerObject = 3
retryIntervalCreateCertManagerObject = 1 * time.Second

certManagerImageComponent = "cert-manager"
)

// CertManagerClient has methods to work with cert-manager components in the cluster.
Expand All @@ -51,6 +54,7 @@ type CertManagerClient interface {

// certManagerClient implements CertManagerClient .
type certManagerClient struct {
configClient config.Client
proxy Proxy
pollImmediateWaiter PollImmediateWaiter
}
Expand All @@ -59,8 +63,9 @@ type certManagerClient struct {
var _ CertManagerClient = &certManagerClient{}

// newCertMangerClient returns a certManagerClient.
func newCertMangerClient(proxy Proxy, pollImmediateWaiter PollImmediateWaiter) *certManagerClient {
func newCertMangerClient(configClient config.Client, proxy Proxy, pollImmediateWaiter PollImmediateWaiter) *certManagerClient {
return &certManagerClient{
configClient: configClient,
proxy: proxy,
pollImmediateWaiter: pollImmediateWaiter,
}
Expand All @@ -76,14 +81,10 @@ func (cm *certManagerClient) Images() ([]string, error) {
return []string{}, nil
}

yaml, err := config.Asset(embeddedCertManagerManifestPath)
// Gets the cert-manager objects from the embedded assets.
objs, err := cm.getManifestObjs()
if err != nil {
return nil, err
}

objs, err := util.ToUnstructured(yaml)
if err != nil {
return nil, errors.Wrap(err, "failed to parse yaml for cert-manager manifest")
return []string{}, nil
}

images, err := util.InspectImages(objs)
Expand Down Expand Up @@ -112,15 +113,10 @@ func (cm *certManagerClient) EnsureWebhook() error {
// Otherwise install cert-manager
log.Info("Installing cert-manager")

// Gets the cert-manager manifest from the embedded assets and apply it.
yaml, err := config.Asset(embeddedCertManagerManifestPath)
// Gets the cert-manager objects from the embedded assets.
objs, err := cm.getManifestObjs()
if err != nil {
return err
}

objs, err := util.ToUnstructured(yaml)
if err != nil {
return errors.Wrap(err, "failed to parse yaml for cert-manager manifest")
return nil
}

// installs the web-hook
Expand Down Expand Up @@ -163,6 +159,28 @@ func (cm *certManagerClient) EnsureWebhook() error {
return nil
}

// getManifestObjs gets the cert-manager manifest, convert to unstructured objects, and fix images
func (cm *certManagerClient) getManifestObjs() ([]unstructured.Unstructured, error) {
yaml, err := manifests.Asset(embeddedCertManagerManifestPath)
if err != nil {
return nil, err
}

objs, err := util.ToUnstructured(yaml)
if err != nil {
return nil, errors.Wrap(err, "failed to parse yaml for cert-manager manifest")
}

objs, err = util.FixImages(objs, func(image string) (string, error) {
return cm.configClient.ImageMeta().AlterImage(certManagerImageComponent, image)
})
if err != nil {
return nil, errors.Wrap(err, "failed to apply image override to the cert-manager manifest")
}

return objs, nil
}

func (cm *certManagerClient) createObj(o unstructured.Unstructured) error {
c, err := cm.proxy.NewClient()
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion cmd/clusterctl/client/cluster/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ func (c *clusterClient) Proxy() Proxy {
}

func (c *clusterClient) CertManager() CertManagerClient {
return newCertMangerClient(c.proxy, c.pollImmediateWaiter)
return newCertMangerClient(c.configClient, c.proxy, c.pollImmediateWaiter)
}

func (c *clusterClient) ProviderComponents() ComponentsClient {
Expand Down
10 changes: 9 additions & 1 deletion cmd/clusterctl/client/config/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,19 @@ import (
)

// Client is used to interact with the clusterctl configurations.
// Clusterctl v2 handles two types of configs:
// Clusterctl v2 handles the following configurations:
// 1. The configuration of the providers (name, type and URL of the provider repository)
// 2. Variables used when installing providers/creating clusters. Variables can be read from the environment or from the config file
// 3. The configuration about image overrides
type Client interface {
// Providers provide access to provider configurations.
Providers() ProvidersClient

// Variables provide access to environment variables and/or variables defined in the clusterctl configuration file.
Variables() VariablesClient

// ImageMeta provide access to to image meta configurations.
ImageMeta() ImageMetaClient
}

// configClient implements Client.
Expand All @@ -49,6 +53,10 @@ func (c *configClient) Variables() VariablesClient {
return newVariablesClient(c.reader)
}

func (c *configClient) ImageMeta() ImageMetaClient {
return newImageMetaClient(c.reader)
}

// Option is a configuration option supplied to New
type Option func(*configClient)

Expand Down
171 changes: 171 additions & 0 deletions cmd/clusterctl/client/config/imagemeta_client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
/*
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 config

import (
"fmt"
"strings"

"github.com/docker/distribution/reference"
"github.com/pkg/errors"
)

const (
imagesConfigKey = "images"
allImageConfig = "all"
)

// ImageMetaClient has methods to work with image meta configurations.
type ImageMetaClient interface {
// AlterImage alters an image name according to the current image override configurations.
AlterImage(component, image string) (string, error)
}

// imageMetaClient implements ImageMetaClient.
type imageMetaClient struct {
reader Reader
imageMetaCache map[string]*imageMeta
}

// ensure imageMetaClient implements ImageMetaClient.
var _ ImageMetaClient = &imageMetaClient{}

func newImageMetaClient(reader Reader) *imageMetaClient {
return &imageMetaClient{
reader: reader,
imageMetaCache: map[string]*imageMeta{},
}
}

func (p *imageMetaClient) AlterImage(component, image string) (string, error) {
// Gets the image meta that applies to the selected component; if none, returns early
meta, err := p.getImageMetaByComponent(component)
if err != nil {
return "", err
}
if meta == nil {
return image, nil
}

// Apply the image meta to image name
return meta.ApplyToImage(image)
}

// getImageMetaByComponent returns the image meta that applies to the selected component
func (p *imageMetaClient) getImageMetaByComponent(component string) (*imageMeta, error) {
// if the image meta for the component is already known, return it
if im, ok := p.imageMetaCache[component]; ok {
return im, nil
}

// Otherwise read the image override configurations.
var meta map[string]imageMeta
if err := p.reader.UnmarshalKey(imagesConfigKey, &meta); err != nil {
return nil, errors.Wrap(err, "failed to unmarshal image override configurations")
}

// If there are not image override configurations, return.
if meta == nil {
p.imageMetaCache[component] = nil
return nil, nil
}

// Gets the image configuration and to the specific component, and returns the union of the two.
m := &imageMeta{}
if allMeta, ok := meta[allImageConfig]; ok {
m.Union(&allMeta)
}
if componentMeta, ok := meta[component]; ok {
m.Union(&componentMeta)
}

p.imageMetaCache[component] = m
return m, nil
}

// imageMeta allows to define transformations to apply to the image contained in the YAML manifests.
type imageMeta struct {
// repository sets the container registry to pull images from.
Repository string `json:"repository,omitempty"`

// Tag allows to specify a tag for the images.
Tag string `json:"tag,omitempty"`
}

// Union allows to merge two imageMeta transformation; in case both the imageMeta defines new values for the same field,
// the other transformation takes precedence on the existing one.
func (i *imageMeta) Union(other *imageMeta) {
if other.Repository != "" {
i.Repository = other.Repository
}
if other.Tag != "" {
i.Tag = other.Tag
}
}

// ApplyToImage changes an image name applying the transformations defined in the current imageMeta.
func (i *imageMeta) ApplyToImage(image string) (string, error) {
// Splits the image name into its own composing parts
ref, err := reference.ParseNormalizedNamed(image)
if err != nil {
return "", err
}

// apply transformations
if i.Repository != "" {
// store tag & digest for rebuilding the image name
tagged, hasTag := ref.(reference.Tagged)
digested, hasDigest := ref.(reference.Digested)

// detect the image name, dropping host and path if any
name := ref.Name()
imageNameIndex := strings.LastIndex(name, "/")
if imageNameIndex != -1 {
name = strings.TrimPrefix(name[imageNameIndex+1:], "/")
}

// parse the new image resulting by concatenating the new repository and the image name
ref, err = reference.ParseNormalizedNamed(fmt.Sprintf("%s/%s", strings.TrimSuffix(i.Repository, "/"), name))
if err != nil {
return "", err
}

// applies back tag & digest
if hasTag {
ref, err = reference.WithTag(ref, tagged.Tag())
if err != nil {
return "", err
}
}

if hasDigest {
ref, err = reference.WithDigest(ref, digested.Digest())
if err != nil {
return "", err
}
}
}
if i.Tag != "" {
ref, err = reference.WithTag(reference.TrimNamed(ref), i.Tag)
if err != nil {
return "", err
}
}

// returns the resulting image name
return ref.String(), nil
}
Loading

0 comments on commit b51a61e

Please sign in to comment.