Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨Add templating interface to clusterctl #3115

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
5 changes: 5 additions & 0 deletions cmd/clusterctl/client/alias.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"sigs.k8s.io/cluster-api/cmd/clusterctl/client/cluster"
"sigs.k8s.io/cluster-api/cmd/clusterctl/client/config"
"sigs.k8s.io/cluster-api/cmd/clusterctl/client/repository"
yaml "sigs.k8s.io/cluster-api/cmd/clusterctl/client/yamlprocessor"
)

// Alias creates local aliases for types defined in the low-level libraries.
Expand All @@ -42,3 +43,7 @@ type UpgradePlan cluster.UpgradePlan

// Kubeconfig is a type that specifies inputs related to the actual kubeconfig.
type Kubeconfig cluster.Kubeconfig

// Processor defines the methods necessary for creating a specific yaml
// processor.
type Processor yaml.Processor
43 changes: 32 additions & 11 deletions cmd/clusterctl/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,21 @@ type clusterctlClient struct {
clusterClientFactory ClusterClientFactory
}

type RepositoryClientFactory func(config.Provider) (repository.Client, error)
type ClusterClientFactory func(Kubeconfig) (cluster.Client, error)
// RepositoryClientFactoryInput represents the inputs required by the
// RepositoryClientFactory
type RepositoryClientFactoryInput struct {
wfernandes marked this conversation as resolved.
Show resolved Hide resolved
provider Provider
processor Processor
}
type RepositoryClientFactory func(RepositoryClientFactoryInput) (repository.Client, error)

// ClusterClientFactoryInput reporesents the inputs required by the
// ClusterClientFactory
type ClusterClientFactoryInput struct {
kubeconfig Kubeconfig
processor Processor
}
type ClusterClientFactory func(ClusterClientFactoryInput) (cluster.Client, error)

// Ensure clusterctlClient implements Client.
var _ Client = &clusterctlClient{}
Expand Down Expand Up @@ -130,17 +143,25 @@ func newClusterctlClient(path string, options ...Option) (*clusterctlClient, err
return client, nil
}

// defaultClusterFactory is a ClusterClientFactory func the uses the default client provided by the cluster low level library.
func defaultClusterFactory(configClient config.Client) func(kubeconfig Kubeconfig) (cluster.Client, error) {
return func(kubeconfig Kubeconfig) (cluster.Client, error) {
// Kubeconfig is a type alias to cluster.Kubeconfig
return cluster.New(cluster.Kubeconfig(kubeconfig), configClient), nil
// defaultRepositoryFactory is a RepositoryClientFactory func the uses the default client provided by the repository low level library.
func defaultRepositoryFactory(configClient config.Client) RepositoryClientFactory {
return func(input RepositoryClientFactoryInput) (repository.Client, error) {
return repository.New(
input.provider,
configClient,
repository.InjectYamlProcessor(input.processor),
)
}
}

// defaultRepositoryFactory is a RepositoryClientFactory func the uses the default client provided by the repository low level library.
func defaultRepositoryFactory(configClient config.Client) func(providerConfig config.Provider) (repository.Client, error) {
return func(providerConfig config.Provider) (repository.Client, error) {
return repository.New(providerConfig, configClient)
// defaultClusterFactory is a ClusterClientFactory func the uses the default client provided by the cluster low level library.
func defaultClusterFactory(configClient config.Client) ClusterClientFactory {
return func(input ClusterClientFactoryInput) (cluster.Client, error) {
return cluster.New(
// Kubeconfig is a type alias to cluster.Kubeconfig
cluster.Kubeconfig(input.kubeconfig),
configClient,
cluster.InjectYamlProcessor(input.processor),
), nil
}
}
39 changes: 30 additions & 9 deletions cmd/clusterctl/client/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
"sigs.k8s.io/cluster-api/cmd/clusterctl/client/cluster"
"sigs.k8s.io/cluster-api/cmd/clusterctl/client/config"
"sigs.k8s.io/cluster-api/cmd/clusterctl/client/repository"
yaml "sigs.k8s.io/cluster-api/cmd/clusterctl/client/yamlprocessor"
"sigs.k8s.io/cluster-api/cmd/clusterctl/internal/scheme"
"sigs.k8s.io/cluster-api/cmd/clusterctl/internal/test"
)
Expand Down Expand Up @@ -118,23 +119,23 @@ func newFakeClient(configClient config.Client) *fakeClient {
fake.configClient = newFakeConfig()
}

var clusterClientFactory = func(i Kubeconfig) (cluster.Client, error) {
var clusterClientFactory = func(i ClusterClientFactoryInput) (cluster.Client, error) {
// converting the client.Kubeconfig to cluster.Kubeconfig alias
k := cluster.Kubeconfig(i)
k := cluster.Kubeconfig(i.kubeconfig)
if _, ok := fake.clusters[k]; !ok {
return nil, errors.Errorf("Cluster for kubeconfig %q and/or context %q does not exists.", i.Path, i.Context)
return nil, errors.Errorf("Cluster for kubeconfig %q and/or context %q does not exist.", i.kubeconfig.Path, i.kubeconfig.Context)
}
return fake.clusters[k], nil
}

fake.internalClient, _ = newClusterctlClient("fake-config",
InjectConfig(fake.configClient),
InjectClusterClientFactory(clusterClientFactory),
InjectRepositoryFactory(func(provider config.Provider) (repository.Client, error) {
if _, ok := fake.repositories[provider.ManifestLabel()]; !ok {
return nil, errors.Errorf("Repository for kubeconfig %q does not exists.", provider.ManifestLabel())
InjectRepositoryFactory(func(input RepositoryClientFactoryInput) (repository.Client, error) {
if _, ok := fake.repositories[input.provider.ManifestLabel()]; !ok {
return nil, errors.Errorf("Repository for kubeconfig %q does not exist.", input.provider.ManifestLabel())
}
return fake.repositories[provider.ManifestLabel()], nil
return fake.repositories[input.provider.ManifestLabel()], nil
}),
)

Expand Down Expand Up @@ -324,13 +325,15 @@ func newFakeRepository(provider config.Provider, configClient config.Client) *fa
Provider: provider,
configClient: configClient,
fakeRepository: fakeRepository,
processor: yaml.NewSimpleProcessor(),
}
}

type fakeRepositoryClient struct {
config.Provider
configClient config.Client
fakeRepository *test.FakeRepository
processor yaml.Processor
}

var _ repository.Client = &fakeRepositoryClient{}
Expand All @@ -349,6 +352,7 @@ func (f fakeRepositoryClient) Components() repository.ComponentsClient {
provider: f.Provider,
fakeRepository: f.fakeRepository,
configClient: f.configClient,
processor: f.processor,
}
}

Expand All @@ -358,6 +362,7 @@ func (f fakeRepositoryClient) Templates(version string) repository.TemplateClien
version: version,
fakeRepository: f.fakeRepository,
configVariablesClient: f.configClient.Variables(),
processor: f.processor,
}
}

Expand Down Expand Up @@ -399,6 +404,7 @@ type fakeTemplateClient struct {
version string
fakeRepository *test.FakeRepository
configVariablesClient config.VariablesClient
processor yaml.Processor
}

func (f *fakeTemplateClient) Get(flavor, targetNamespace string, listVariablesOnly bool) (repository.Template, error) {
Expand All @@ -412,7 +418,13 @@ func (f *fakeTemplateClient) Get(flavor, targetNamespace string, listVariablesOn
if err != nil {
return nil, err
}
return repository.NewTemplate(content, f.configVariablesClient, targetNamespace, listVariablesOnly)
return repository.NewTemplate(repository.TemplateInput{
RawArtifact: content,
ConfigVariablesClient: f.configVariablesClient,
Processor: f.processor,
TargetNamespace: targetNamespace,
ListVariablesOnly: listVariablesOnly,
})
}

// fakeMetadataClient provides a super simple MetadataClient (e.g. without support for local overrides/embedded metadata)
Expand Down Expand Up @@ -441,6 +453,7 @@ type fakeComponentClient struct {
provider config.Provider
fakeRepository *test.FakeRepository
configClient config.Client
processor yaml.Processor
}

func (f *fakeComponentClient) Get(options repository.ComponentsOptions) (repository.Components, error) {
Expand All @@ -454,5 +467,13 @@ func (f *fakeComponentClient) Get(options repository.ComponentsOptions) (reposit
return nil, err
}

return repository.NewComponents(f.provider, f.configClient, content, options)
return repository.NewComponents(
repository.ComponentsInput{
Provider: f.provider,
ConfigClient: f.configClient,
Processor: f.processor,
RawYaml: content,
Options: options,
},
)
}
16 changes: 15 additions & 1 deletion cmd/clusterctl/client/cluster/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"k8s.io/client-go/rest"
"sigs.k8s.io/cluster-api/cmd/clusterctl/client/config"
"sigs.k8s.io/cluster-api/cmd/clusterctl/client/repository"
yaml "sigs.k8s.io/cluster-api/cmd/clusterctl/client/yamlprocessor"
logf "sigs.k8s.io/cluster-api/cmd/clusterctl/log"
"sigs.k8s.io/controller-runtime/pkg/client"
)
Expand Down Expand Up @@ -97,6 +98,7 @@ type clusterClient struct {
proxy Proxy
repositoryClientFactory RepositoryClientFactory
pollImmediateWaiter PollImmediateWaiter
processor yaml.Processor
}

type RepositoryClientFactory func(provider config.Provider, configClient config.Client, options ...repository.Option) (repository.Client, error)
Expand Down Expand Up @@ -137,7 +139,7 @@ func (c *clusterClient) ProviderUpgrader() ProviderUpgrader {
}

func (c *clusterClient) Template() TemplateClient {
return newTemplateClient(c.proxy, c.configClient)
return newTemplateClient(TemplateClientInput{c.proxy, c.configClient, c.processor})
}

// Option is a configuration option supplied to New
Expand Down Expand Up @@ -165,6 +167,17 @@ func InjectPollImmediateWaiter(pollImmediateWaiter PollImmediateWaiter) Option {
}
}

// InjectYamlProcessor allows you to override the yaml processor that the
// cluster client uses. By default, the SimpleProcessor is used. This is
// true even if a nil processor is injected.
func InjectYamlProcessor(p yaml.Processor) Option {
return func(c *clusterClient) {
if p != nil {
c.processor = p
}
}
}

// New returns a cluster.Client.
func New(kubeconfig Kubeconfig, configClient config.Client, options ...Option) Client {
return newClusterClient(kubeconfig, configClient, options...)
Expand All @@ -174,6 +187,7 @@ func newClusterClient(kubeconfig Kubeconfig, configClient config.Client, options
client := &clusterClient{
configClient: configClient,
kubeconfig: kubeconfig,
processor: yaml.NewSimpleProcessor(),
}
for _, o := range options {
o(client)
Expand Down
71 changes: 71 additions & 0 deletions cmd/clusterctl/client/cluster/client_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
Copyright 2019 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 cluster

import (
"testing"

. "github.com/onsi/gomega"
yaml "sigs.k8s.io/cluster-api/cmd/clusterctl/client/yamlprocessor"
"sigs.k8s.io/cluster-api/cmd/clusterctl/internal/test"
)

func Test_newClusterClient_YamlProcessor(t *testing.T) {

tests := []struct {
name string
opts []Option
assert func(*WithT, yaml.Processor)
}{
{
name: "it creates a cluster client with simple yaml processor by default",
assert: func(g *WithT, p yaml.Processor) {
_, ok := (p).(*yaml.SimpleProcessor)
g.Expect(ok).To(BeTrue())
},
},
{
name: "it creates a cluster client with specified yaml processor",
opts: []Option{InjectYamlProcessor(test.NewFakeProcessor())},
assert: func(g *WithT, p yaml.Processor) {
_, ok := (p).(*yaml.SimpleProcessor)
g.Expect(ok).To(BeFalse())
_, ok = (p).(*test.FakeProcessor)
g.Expect(ok).To(BeTrue())
},
},
{
name: "it creates a cluster client with simple yaml processor even if injected with nil processor",
opts: []Option{InjectYamlProcessor(nil)},
assert: func(g *WithT, p yaml.Processor) {
g.Expect(p).ToNot(BeNil())
_, ok := (p).(*yaml.SimpleProcessor)
g.Expect(ok).To(BeTrue())
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
g := NewWithT(t)

client := newClusterClient(Kubeconfig{}, &fakeConfigClient{}, tt.opts...)
g.Expect(client).ToNot(BeNil())
tt.assert(g, client.processor)
})
}
}
31 changes: 26 additions & 5 deletions cmd/clusterctl/client/cluster/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (
corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/cluster-api/cmd/clusterctl/client/config"
"sigs.k8s.io/cluster-api/cmd/clusterctl/client/repository"
yaml "sigs.k8s.io/cluster-api/cmd/clusterctl/client/yamlprocessor"
"sigs.k8s.io/controller-runtime/pkg/client"
)

Expand All @@ -48,17 +49,25 @@ type templateClient struct {
proxy Proxy
configClient config.Client
gitHubClientFactory func(configVariablesClient config.VariablesClient) (*github.Client, error)
processor yaml.Processor
}

// ensure templateClient implements TemplateClient.
var _ TemplateClient = &templateClient{}

type TemplateClientInput struct {
proxy Proxy
configClient config.Client
processor yaml.Processor
}

// newTemplateClient returns a templateClient.
func newTemplateClient(proxy Proxy, configClient config.Client) *templateClient {
func newTemplateClient(input TemplateClientInput) *templateClient {
return &templateClient{
proxy: proxy,
configClient: configClient,
proxy: input.proxy,
configClient: input.configClient,
gitHubClientFactory: getGitHubClient,
processor: input.processor,
}
}

Expand Down Expand Up @@ -90,7 +99,13 @@ func (t *templateClient) GetFromConfigMap(configMapNamespace, configMapName, con
return nil, errors.Errorf("the ConfigMap %s/%s does not have the %q data key", configMapNamespace, configMapName, configMapDataKey)
}

return repository.NewTemplate([]byte(data), t.configClient.Variables(), targetNamespace, listVariablesOnly)
return repository.NewTemplate(repository.TemplateInput{
RawArtifact: []byte(data),
ConfigVariablesClient: t.configClient.Variables(),
Processor: t.processor,
TargetNamespace: targetNamespace,
ListVariablesOnly: listVariablesOnly,
})
}

func (t *templateClient) GetFromURL(templateURL, targetNamespace string, listVariablesOnly bool) (repository.Template, error) {
Expand All @@ -103,7 +118,13 @@ func (t *templateClient) GetFromURL(templateURL, targetNamespace string, listVar
return nil, errors.Wrapf(err, "invalid GetFromURL operation")
}

return repository.NewTemplate(content, t.configClient.Variables(), targetNamespace, listVariablesOnly)
return repository.NewTemplate(repository.TemplateInput{
RawArtifact: content,
ConfigVariablesClient: t.configClient.Variables(),
Processor: t.processor,
TargetNamespace: targetNamespace,
ListVariablesOnly: listVariablesOnly,
})
}

func (t *templateClient) getURLContent(templateURL string) ([]byte, error) {
Expand Down
Loading