diff --git a/cmd/clusterctl/pkg/client/repository/client.go b/cmd/clusterctl/pkg/client/repository/client.go new file mode 100644 index 000000000000..69bd8fda7fba --- /dev/null +++ b/cmd/clusterctl/pkg/client/repository/client.go @@ -0,0 +1,153 @@ +/* +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 repository + +import ( + "net/url" + + "github.com/pkg/errors" + "sigs.k8s.io/cluster-api/cmd/clusterctl/pkg/client/config" + "sigs.k8s.io/cluster-api/cmd/clusterctl/pkg/internal/test" +) + +// Client is used to interact with provider repositories. +// Provider repository are expected to contain two types of YAML files: +// - YAML files defining the provider components (CRD, Controller, RBAC etc.) +// - YAML files defining the cluster templates (Cluster, Machines) +type Client interface { + config.Provider + + // Components provide access to YAML file for creating provider components. + Components() ComponentsClient + + // Templates provide access to YAML file for generating workload cluster templates. + // Please note that templates are expected to exist for the infrastructure providers only. + Templates(version string) TemplatesClient +} + +// repositoryClient implements Client. +type repositoryClient struct { + config.Provider + configVariablesClient config.VariablesClient + repository Repository +} + +// ensure repositoryClient implements Client. +var _ Client = &repositoryClient{} + +func (c *repositoryClient) Components() ComponentsClient { + return newComponentsClient(c.Provider, c.repository, c.configVariablesClient) +} + +func (c *repositoryClient) Templates(version string) TemplatesClient { + return newTemplatesClient(c.Provider, version, c.repository, c.configVariablesClient) +} + +// NewOptions carries the options supported by New +type NewOptions struct { + injectRepository Repository +} + +// Option is a configuration option supplied to New +type Option func(*NewOptions) + +// InjectRepository allows to override the repository implementation to use; +// by default, the repository implementation to use is created according to the +// repository URL. +func InjectRepository(repository Repository) Option { + return func(c *NewOptions) { + c.injectRepository = repository + } +} + +// New returns a Client. +func New(provider config.Provider, configVariablesClient config.VariablesClient, options Options) (Client, error) { + return newRepositoryClient(provider, configVariablesClient, options) +} + +func newRepositoryClient(provider config.Provider, configVariablesClient config.VariablesClient, options Options) (*repositoryClient, error) { + repository := options.InjectRepository + if repository == nil { + r, err := repositoryFactory(provider, configVariablesClient) + if err != nil { + return nil, errors.Wrapf(err, "failed to get repository client for %q", provider.Name()) + } + repository = r + } + + return &repositoryClient{ + Provider: provider, + repository: repository, + configVariablesClient: configVariablesClient, + }, nil +} + +// Options allow to set Client options +type Options struct { + InjectRepository Repository +} + +// Repository defines the behavior of a repository implementation. +// clusterctl is designed to support different repository types; each repository implementation should consider +// the following capabilities and provide best support considering the underlying technology. +// 1. Versions awareness: repositories are expected to be aware of the provider version they are hosting, and +// possibly to host more than one version. +// 2. Kustomize awareness: even if it is recommended that provider expose “pre-compiled” YAML files, we want +// to allow usage of clusterctl reading from the “raw” /config directory generated by kubebuilder (developer friendly) +type Repository interface { + // DefaultVersion returns the default provider version returned by a repository. + // In case the repository URL points to latest, this method returns the current latest version; in other cases + // it returns the version of the provider hosted in the repository. + DefaultVersion() string + + // RootPath returns the path inside the repository where the YAML file for creating provider components and + // the YAML file for generating workload cluster templates are stored. + // This value is derived from the repository URL; all the paths returned by this interface should be relative to this path. + RootPath() string + + // ComponentsPath return the path (a folder name or file name) of the YAML file for creating provider components. + // This value is derived from the repository URL. + ComponentsPath() string + + // KustomizeDir returns the path to a folder containing the kustomization.yaml for creating the YAML file provider components. + // This value is derived from the repository URL, and it is used only when the YAML for provider components is spread + // across nested folders inside the repository like e.g. the /config folder generated by kubebuilder. + KustomizeDir() string + + // GetFiles returns files for a given provider version. + // If file is a path, the entire content of the path is returned. + GetFiles(version string, path string) (map[string][]byte, error) +} + +var _ Repository = &test.FakeRepository{} + +//repositoryFactory returns the repository implementation corresponding to the provider URL. +func repositoryFactory(providerConfig config.Provider, configVariablesClient config.VariablesClient) (Repository, error) { //nolint + // parse the repository url + rURL, err := url.Parse(providerConfig.URL()) + if err != nil { + return nil, errors.Errorf("failed to parse repository url %q", providerConfig.URL()) + } + + // if the url is a github repository + //TODO: implement in a follow up PR + + // if the url is a local repository + //TODO: implement in a follow up PR + + return nil, errors.Errorf("invalid provider url. there are no provider implementation for %q schema", rURL.Scheme) +} diff --git a/cmd/clusterctl/pkg/client/repository/components.go b/cmd/clusterctl/pkg/client/repository/components.go new file mode 100644 index 000000000000..001dc2ac914e --- /dev/null +++ b/cmd/clusterctl/pkg/client/repository/components.go @@ -0,0 +1,66 @@ +/* +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 repository + +import ( + "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/pkg/client/config" +) + +// Components wraps a YAML file that defines the provider components +// to be installed in a management cluster (CRD, Controller, RBAC etc.) +// It is important to notice that clusterctl applies a set of processing steps to the “raw” component read +// from the provider repositories: +// 1. In case the provider exposed a component YAML that is not “pre-compiled” (e.g. the /config dir), it uses +// kustomize to create a single component YAML file +// 2. Checks for all the variables in the component YAML file and replace with corresponding config values +// 3. Ensures all the provider components are deployed in the target namespace (apply only to namespaced objects) +// 4. Ensures all the ClusterRoleBinding which are referencing namespaced objects have the name prefixed with the namespace name +// 5. Set the watching namespace for the provider controller +// 6. Adds labels to all the components in order to allow easy identification of the provider objects +type Components interface { + // configuration of the provider the template belongs to. + config.Provider + + // Version of the provider. + Version() string + + // Variables required by the template. + // This value is derived by the component YAML. + Variables() []string + + // TargetNamespace where the provider components will be installed. + // By default this value is derived by the component YAML, but it is possible to override it + // during the creation of the Components object. + TargetNamespace() string + + // WatchingNamespace defines the namespace where the provider controller is is watching (empty means all namespaces). + // By default this value is derived by the component YAML, but it is possible to override it + // during the creation of the Components object. + WatchingNamespace() string + + // Metadata returns the clusterctl metadata object representing the provider that will be + // generated by this provider components. + Metadata() clusterctlv1.Provider + + // Yaml return the provider components in the form of a YAML file. + Yaml() ([]byte, error) + + // Objs return the provider components in the form of a list of Unstructured objects. + Objs() []unstructured.Unstructured +} diff --git a/cmd/clusterctl/pkg/client/repository/components_client.go b/cmd/clusterctl/pkg/client/repository/components_client.go new file mode 100644 index 000000000000..120c210a7fda --- /dev/null +++ b/cmd/clusterctl/pkg/client/repository/components_client.go @@ -0,0 +1,51 @@ +/* +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 repository + +import ( + "sigs.k8s.io/cluster-api/cmd/clusterctl/pkg/client/config" +) + +// ComponentsClient has methods to work with yaml file for generating provider components. +// Assets are yaml files to be used for deploying a provider into a management cluster. +type ComponentsClient interface { + Get(version, targetNamespace, watchingNamespace string) (Components, error) +} + +// componentsClient implements ComponentsClient. +type componentsClient struct { + provider config.Provider + repository Repository + configVariablesClient config.VariablesClient +} + +// ensure componentsClient implements ComponentsClient. +var _ ComponentsClient = &componentsClient{} + +// newComponentsClient returns a componentsClient. +func newComponentsClient(provider config.Provider, repository Repository, configVariablesClient config.VariablesClient) *componentsClient { + return &componentsClient{ + provider: provider, + repository: repository, + configVariablesClient: configVariablesClient, + } +} + +func (f *componentsClient) Get(version, targetNamespace, watchingNamespace string) (Components, error) { + //TODO: implement in a follow up PR + return nil, nil +} diff --git a/cmd/clusterctl/pkg/client/repository/template.go b/cmd/clusterctl/pkg/client/repository/template.go new file mode 100644 index 000000000000..7037f62506f8 --- /dev/null +++ b/cmd/clusterctl/pkg/client/repository/template.go @@ -0,0 +1,53 @@ +/* +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 repository + +import ( + "sigs.k8s.io/cluster-api/cmd/clusterctl/pkg/client/config" +) + +// Template wraps a YAML file that defines the cluster objects (Cluster, Machines etc.). +// It is important to notice that clusterctl applies a set of processing steps to the “raw” cluster template YAML read +// from the provider repositories: +// 1. Checks for all the variables in the cluster template YAML file and replace with corresponding config values +// 2. Process go templates contained in the cluster template YAML file +// 3. Ensure all the cluster objects are deployed in the target namespace +type Template interface { + // configuration of the provider the template belongs to. + config.Provider + + // Version of the provider the template belongs to. + Version() string + + // Flavor implemented by the template (empty means default flavor). + // A flavor is a variant of cluster template supported by the provider, like e.g. Prod, Test. + Flavor() string + + // Bootstrap provider used by the cluster template. + Bootstrap() string + + // Variables required by the template. + // This value is derived by the template YAML. + Variables() []string + + // TargetNamespace where the template objects will be installed. + // This value is inherited by the template options. + TargetNamespace() string + + // Yaml file defining all the cluster objects. + Yaml() []byte +} diff --git a/cmd/clusterctl/pkg/client/repository/templates_client.go b/cmd/clusterctl/pkg/client/repository/templates_client.go new file mode 100644 index 000000000000..6cf0d93d935a --- /dev/null +++ b/cmd/clusterctl/pkg/client/repository/templates_client.go @@ -0,0 +1,67 @@ +/* +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 repository + +import ( + "sigs.k8s.io/cluster-api/cmd/clusterctl/pkg/client/config" +) + +// TemplateOptions defines a set of well-know variables that all the cluster templates are expected to manage; +// this set of variables defines a simple, day1 experience that will be made accessible via flags in the clusterctl CLI. +// Please note that each provider/each template is allowed to add more variables, but additional variables are exposed +// only via environment variables or the clusterctl configuration file. +type TemplateOptions struct { + ClusterName string + Namespace string + KubernetesVersion string + ControlplaneCount int + WorkerCount int +} + +// TemplatesClient has methods to work with templatesClient hosted on a provider repository. +// Templates are yaml files to be used for creating a guest cluster. +type TemplatesClient interface { + Get(flavor, bootstrap string, options TemplateOptions) (Template, error) +} + +// templatesClient implements TemplatesClient. +type templatesClient struct { + provider config.Provider + version string + repository Repository + configVariablesClient config.VariablesClient +} + +// ensure templatesClient implements TemplatesClient. +var _ TemplatesClient = &templatesClient{} + +// newTemplatesClient returns a templatesClient. +func newTemplatesClient(provider config.Provider, version string, repository Repository, configVariablesClient config.VariablesClient) *templatesClient { + return &templatesClient{ + provider: provider, + version: version, + repository: repository, + configVariablesClient: configVariablesClient, + } +} + +// Get return the template for the flavor/bootstrap provider specified. +// In case the template does not exists, an error is returned. +func (f *templatesClient) Get(flavor, bootstrap string, options TemplateOptions) (Template, error) { + //TODO: implement in a follow up PR + return nil, nil +} diff --git a/cmd/clusterctl/pkg/internal/test/fake_repository.go b/cmd/clusterctl/pkg/internal/test/fake_repository.go new file mode 100644 index 000000000000..fe18e40bb48e --- /dev/null +++ b/cmd/clusterctl/pkg/internal/test/fake_repository.go @@ -0,0 +1,95 @@ +/* +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 test + +import ( + "fmt" + "strings" + + "github.com/pkg/errors" +) + +type FakeRepository struct { + defaultVersion string + rootPath string + componentsPath string + kustomizeDir string + versions map[string]bool + files map[string][]byte +} + +func (f *FakeRepository) DefaultVersion() string { + return f.defaultVersion +} + +func (f *FakeRepository) RootPath() string { + return f.rootPath +} + +func (f *FakeRepository) ComponentsPath() string { + return f.componentsPath +} + +func (f *FakeRepository) KustomizeDir() string { + return f.kustomizeDir +} + +func (f FakeRepository) GetFiles(version string, path string) (map[string][]byte, error) { + if _, ok := f.versions[version]; !ok { + return nil, errors.Errorf("unable to get files for version %s", version) + } + + ret := map[string][]byte{} + for p, c := range f.files { + if p == vpath(version, path) || + strings.HasPrefix(p, vpath(version, path)) { // this is a quick & dirty way for identifying files belonging to a path tree + + x := strings.TrimPrefix(p, fmt.Sprintf("%s/", version)) + ret[x] = c + } + } + return ret, nil +} + +func NewFakeRepository() *FakeRepository { + return &FakeRepository{ + versions: map[string]bool{}, + files: map[string][]byte{}, + } +} + +func (f *FakeRepository) WithPaths(rootPath, componentsPath, kustomizeDir string) *FakeRepository { + f.rootPath = rootPath + f.componentsPath = componentsPath + f.kustomizeDir = kustomizeDir + return f +} + +func (f *FakeRepository) WithDefaultVersion(version string) *FakeRepository { + f.defaultVersion = version + return f +} + +func (f *FakeRepository) WithFile(version, path string, content []byte) *FakeRepository { + f.versions[version] = true + f.files[vpath(version, path)] = content + return f +} + +func vpath(version string, path string) string { + return fmt.Sprintf("%s/%s", version, path) +} 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=