From 1a2054d7f6b62de018af61e570fdfc613e1754b5 Mon Sep 17 00:00:00 2001 From: fabriziopandini Date: Fri, 13 Dec 2019 09:36:40 +0100 Subject: [PATCH] clusterctlv2 add implementation for clusterctl config providers --- cmd/clusterctl/cmd/config_providers.go | 22 +++++ cmd/clusterctl/pkg/client/alias.go | 27 ++++++ cmd/clusterctl/pkg/client/client.go | 74 +++++++++++++++ cmd/clusterctl/pkg/client/client_test.go | 109 +++++++++++++++++++++++ cmd/clusterctl/pkg/client/config.go | 32 +++++++ cmd/clusterctl/pkg/client/config_test.go | 87 ++++++++++++++++++ test/infrastructure/docker/go.sum | 1 + 7 files changed, 352 insertions(+) create mode 100644 cmd/clusterctl/pkg/client/alias.go create mode 100644 cmd/clusterctl/pkg/client/client.go create mode 100644 cmd/clusterctl/pkg/client/client_test.go create mode 100644 cmd/clusterctl/pkg/client/config.go create mode 100644 cmd/clusterctl/pkg/client/config_test.go diff --git a/cmd/clusterctl/cmd/config_providers.go b/cmd/clusterctl/cmd/config_providers.go index 95c44468469f..387095b4c61e 100644 --- a/cmd/clusterctl/cmd/config_providers.go +++ b/cmd/clusterctl/cmd/config_providers.go @@ -17,8 +17,13 @@ limitations under the License. package cmd import ( + "fmt" + "os" + "text/tabwriter" + "github.com/pkg/errors" "github.com/spf13/cobra" + "sigs.k8s.io/cluster-api/cmd/clusterctl/pkg/client" ) type configProvidersOptions struct { @@ -80,6 +85,23 @@ func init() { } func runGetRepositories() error { + c, err := client.New(cfgFile) + if err != nil { + return err + } + + repositoryList, err := c.GetProvidersConfig() + if err != nil { + return err + } + + w := tabwriter.NewWriter(os.Stdout, 10, 4, 3, ' ', 0) + fmt.Fprintln(w, "NAME\tTYPE\tURL") + for _, r := range repositoryList { + fmt.Fprintf(w, "%s\t%s\t%s\n", r.Name(), r.Type(), r.URL()) + } + w.Flush() + return nil } diff --git a/cmd/clusterctl/pkg/client/alias.go b/cmd/clusterctl/pkg/client/alias.go new file mode 100644 index 000000000000..b848b5754e12 --- /dev/null +++ b/cmd/clusterctl/pkg/client/alias.go @@ -0,0 +1,27 @@ +/* +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 client + +import ( + "sigs.k8s.io/cluster-api/cmd/clusterctl/pkg/client/config" +) + +// Alias creates local alias for types defined in the low-level library. +// By using local alias, ensures that the users of the API will be forced to import the clusterctl high-level library only. + +// Provider defines a provider configuration. +type Provider config.Provider diff --git a/cmd/clusterctl/pkg/client/client.go b/cmd/clusterctl/pkg/client/client.go new file mode 100644 index 000000000000..809748b63c09 --- /dev/null +++ b/cmd/clusterctl/pkg/client/client.go @@ -0,0 +1,74 @@ +/* +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 client + +import ( + "sigs.k8s.io/cluster-api/cmd/clusterctl/pkg/client/config" +) + +// Client is exposes the clusterctl high-level client library +type Client interface { + GetProvidersConfig() ([]Provider, error) +} + +// clusterctlClient implements Client. +type clusterctlClient struct { + configClient config.Client +} + +// ensure clusterctlClient implements Client. +var _ Client = &clusterctlClient{} + +// NewOptions carries the options supported by New +type NewOptions struct { + injectConfig config.Client +} + +// Option is a configuration option supplied to New +type Option func(*NewOptions) + +// InjectConfig implements a New Option that allows to override the default configuration client used by clusterctl. +func InjectConfig(config config.Client) Option { + return func(c *NewOptions) { + c.injectConfig = config + } +} + +// New returns a configClient. +func New(path string, options ...Option) (Client, error) { + return newClusterctlClient(path, options...) +} + +func newClusterctlClient(path string, options ...Option) (*clusterctlClient, error) { + cfg := &NewOptions{} + for _, o := range options { + o(cfg) + } + + configClient := cfg.injectConfig + if configClient == nil { + c, err := config.New(path) + if err != nil { + return nil, err + } + configClient = c + } + + return &clusterctlClient{ + configClient: configClient, + }, nil +} diff --git a/cmd/clusterctl/pkg/client/client_test.go b/cmd/clusterctl/pkg/client/client_test.go new file mode 100644 index 000000000000..1bfc859dd85a --- /dev/null +++ b/cmd/clusterctl/pkg/client/client_test.go @@ -0,0 +1,109 @@ +/* +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 client + +import ( + "testing" + + clusterctlv1 "sigs.k8s.io/cluster-api/cmd/clusterctl/api/v1alpha3" + "sigs.k8s.io/cluster-api/cmd/clusterctl/pkg/client/config" + "sigs.k8s.io/cluster-api/cmd/clusterctl/pkg/internal/test" +) + +// dummy test to document fakeClient usage +func TestNewFakeClient(t *testing.T) { + // create a fake config with a provider named P1 and a variable named var + repository1Config := config.NewProvider("p1", "url", clusterctlv1.CoreProviderType) + + config1 := newFakeConfig(). + WithVar("var", "value"). + WithProvider(repository1Config) + + // create a new fakeClient using the fake config + newFakeClient(config1) +} + +type fakeClient struct { + configClient config.Client + internalclient *clusterctlClient +} + +var _ Client = &fakeClient{} + +func (f fakeClient) GetProvidersConfig() ([]Provider, error) { + return f.internalclient.GetProvidersConfig() +} + +// newFakeClient return a fake implementation of the client for high-level clusterctl library, based on th given config. +func newFakeClient(configClient config.Client) *fakeClient { + + fake := &fakeClient{} + + fake.configClient = configClient + if fake.configClient == nil { + fake.configClient = newFakeConfig() + } + + fake.internalclient, _ = newClusterctlClient("fake-config", + InjectConfig(fake.configClient), + ) + + return fake +} + +// newFakeConfig return a fake implementation of the client for low-level config library. +// The implementation uses a FakeReader that stores configuration settings in a config map; you can use +// the WithVar or WithProvider methods to set the config map values. +func newFakeConfig() *fakeConfigClient { + fakeReader := test.NewFakeReader() + + client, _ := config.New("fake-config", config.InjectReader(fakeReader)) + + return &fakeConfigClient{ + fakeReader: fakeReader, + internalclient: client, + } +} + +type fakeConfigClient struct { + fakeReader *test.FakeReader + internalclient config.Client +} + +var _ config.Client = &fakeConfigClient{} + +func (f fakeConfigClient) Providers() config.ProvidersClient { + return f.internalclient.Providers() +} + +func (f fakeConfigClient) Variables() config.VariablesClient { + return f.internalclient.Variables() +} + +func (f *fakeConfigClient) WithVar(key, value string) *fakeConfigClient { + f.fakeReader.WithVar(key, value) + return f +} + +func (f *fakeConfigClient) WithProvider(provider config.Provider) *fakeConfigClient { + f.fakeReader.WithProvider(provider.Name(), provider.Type(), provider.URL()) + return f +} + +var ( + bootstrapProviderConfig = config.NewProvider("bootstrap", "url", clusterctlv1.BootstrapProviderType) +) diff --git a/cmd/clusterctl/pkg/client/config.go b/cmd/clusterctl/pkg/client/config.go new file mode 100644 index 000000000000..7ddd4f113af7 --- /dev/null +++ b/cmd/clusterctl/pkg/client/config.go @@ -0,0 +1,32 @@ +/* +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 client + +func (c *clusterctlClient) GetProvidersConfig() ([]Provider, error) { + r, err := c.configClient.Providers().List() + if err != nil { + return nil, err + } + + // Provider is an alias for config.Provider; this makes the conversion + rr := make([]Provider, len(r)) + for i, provider := range r { + rr[i] = provider + } + + return rr, nil +} diff --git a/cmd/clusterctl/pkg/client/config_test.go b/cmd/clusterctl/pkg/client/config_test.go new file mode 100644 index 000000000000..93b07aeaa18d --- /dev/null +++ b/cmd/clusterctl/pkg/client/config_test.go @@ -0,0 +1,87 @@ +/* +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 client + +import ( + "testing" + + "sigs.k8s.io/cluster-api/cmd/clusterctl/pkg/client/config" +) + +func Test_clusterctlClient_GetProvidersConfig(t *testing.T) { + type field struct { + client Client + } + tests := []struct { + name string + field field + wantProviders []string + wantErr bool + }{ + { + name: "Returns default providers", + field: field{ + client: newFakeClient(newFakeConfig()), + }, + wantProviders: []string{ + "aws", + config.ClusterAPIName, + "docker", + "kubeadm", + "vsphere", + }, + wantErr: false, + }, + { + name: "Returns default providers and custom providers if defined", + field: field{ + client: newFakeClient(newFakeConfig().WithProvider(bootstrapProviderConfig)), + }, + wantProviders: []string{ + "aws", + bootstrapProviderConfig.Name(), + config.ClusterAPIName, + "docker", + "kubeadm", + "vsphere", + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.field.client.GetProvidersConfig() + if (err != nil) != tt.wantErr { + t.Errorf("GetProvidersConfig() error = %v, wantErr %v", err, tt.wantErr) + return + } + + if len(got) != len(tt.wantProviders) { + t.Errorf("Init() got = %v items, want %v items", len(got), len(tt.wantProviders)) + return + } + + for i, g := range got { + w := tt.wantProviders[i] + + if g.Name() != w { + t.Errorf("GetProvidersConfig(), Item[%d].Name() got = %v, want = %v ", i, g.Name(), w) + } + } + }) + } +} diff --git a/test/infrastructure/docker/go.sum b/test/infrastructure/docker/go.sum index 56403d88851a..232a608a505f 100644 --- a/test/infrastructure/docker/go.sum +++ b/test/infrastructure/docker/go.sum @@ -307,6 +307,7 @@ go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/ go.uber.org/zap v0.0.0-20180814183419-67bc79d13d15/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.9.1 h1:XCJQEf3W6eZaVwhRBof6ImoYGJSITeKWsyeh3HFu/5o= go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181025213731-e84da0312774/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=