sigs.k8s.io/cluster-api@v1.7.1/cmd/clusterctl/client/repository/client.go (about) 1 /* 2 Copyright 2019 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package repository 18 19 import ( 20 "context" 21 "net/url" 22 "strings" 23 24 "github.com/pkg/errors" 25 26 "sigs.k8s.io/cluster-api/cmd/clusterctl/client/config" 27 yaml "sigs.k8s.io/cluster-api/cmd/clusterctl/client/yamlprocessor" 28 ) 29 30 // Client is used to interact with provider repositories. 31 // Provider repository are expected to contain two types of YAML files: 32 // - YAML files defining the provider components (CRD, Controller, RBAC etc.) 33 // - YAML files defining the cluster templates (Cluster, Machines). 34 type Client interface { 35 config.Provider 36 37 // DefaultVersion returns the default provider version returned by a repository. 38 // In case the repository URL points to latest, this method returns the current latest version; in other cases 39 // it returns the version of the provider hosted in the repository. 40 DefaultVersion() string 41 42 // GetVersions return the list of versions that are available in a provider repository 43 GetVersions(ctx context.Context) ([]string, error) 44 45 // Components provide access to YAML file for creating provider components. 46 Components() ComponentsClient 47 48 // Templates provide access to YAML file for generating workload cluster templates. 49 // Please note that templates are expected to exist for the infrastructure providers only. 50 Templates(version string) TemplateClient 51 52 // ClusterClasses provide access to YAML file for the ClusterClasses available 53 // for the provider. 54 ClusterClasses(version string) ClusterClassClient 55 56 // Metadata provide access to YAML with the provider's metadata. 57 Metadata(version string) MetadataClient 58 } 59 60 // repositoryClient implements Client. 61 type repositoryClient struct { 62 config.Provider 63 configClient config.Client 64 repository Repository 65 processor yaml.Processor 66 } 67 68 // ensure repositoryClient implements Client. 69 var _ Client = &repositoryClient{} 70 71 func (c *repositoryClient) DefaultVersion() string { 72 return c.repository.DefaultVersion() 73 } 74 75 func (c *repositoryClient) GetVersions(ctx context.Context) ([]string, error) { 76 return c.repository.GetVersions(ctx) 77 } 78 79 func (c *repositoryClient) Components() ComponentsClient { 80 return newComponentsClient(c.Provider, c.repository, c.configClient) 81 } 82 83 func (c *repositoryClient) Templates(version string) TemplateClient { 84 return newTemplateClient(TemplateClientInput{version, c.Provider, c.repository, c.configClient.Variables(), c.processor}) 85 } 86 87 func (c *repositoryClient) ClusterClasses(version string) ClusterClassClient { 88 return newClusterClassClient(ClusterClassClientInput{version, c.Provider, c.repository, c.configClient.Variables(), c.processor}) 89 } 90 91 func (c *repositoryClient) Metadata(version string) MetadataClient { 92 return newMetadataClient(c.Provider, version, c.repository, c.configClient.Variables()) 93 } 94 95 // Option is a configuration option supplied to New. 96 type Option func(*repositoryClient) 97 98 // InjectRepository allows to override the repository implementation to use; 99 // by default, the repository implementation to use is created according to the 100 // repository URL. 101 func InjectRepository(repository Repository) Option { 102 return func(c *repositoryClient) { 103 c.repository = repository 104 } 105 } 106 107 // InjectYamlProcessor allows you to override the yaml processor that the 108 // repository client uses. By default, the SimpleProcessor is used. This is 109 // true even if a nil processor is injected. 110 func InjectYamlProcessor(p yaml.Processor) Option { 111 return func(c *repositoryClient) { 112 if p != nil { 113 c.processor = p 114 } 115 } 116 } 117 118 // New returns a Client. 119 func New(ctx context.Context, provider config.Provider, configClient config.Client, options ...Option) (Client, error) { 120 return newRepositoryClient(ctx, provider, configClient, options...) 121 } 122 123 func newRepositoryClient(ctx context.Context, provider config.Provider, configClient config.Client, options ...Option) (*repositoryClient, error) { 124 client := &repositoryClient{ 125 Provider: provider, 126 configClient: configClient, 127 processor: yaml.NewSimpleProcessor(), 128 } 129 for _, o := range options { 130 o(client) 131 } 132 133 // if there is an injected repository, use it, otherwise use a default one 134 if client.repository == nil { 135 r, err := repositoryFactory(ctx, provider, configClient.Variables()) 136 if err != nil { 137 return nil, errors.Wrapf(err, "failed to get repository client for the %s with name %s", provider.Type(), provider.Name()) 138 } 139 client.repository = r 140 } 141 142 return client, nil 143 } 144 145 // Repository defines the behavior of a repository implementation. 146 // clusterctl is designed to support different repository types; each repository implementation should be aware of 147 // the provider version they are hosting, and possibly to host more than one version. 148 type Repository interface { 149 // DefaultVersion returns the default provider version returned by a repository. 150 // In case the repository URL points to latest, this method returns the current latest version; in other cases 151 // it returns the version of the provider hosted in the repository. 152 DefaultVersion() string 153 154 // RootPath returns the path inside the repository where the YAML file for creating provider components and 155 // the YAML file for generating workload cluster templates are stored. 156 // This value is derived from the repository URL; all the paths returned by this interface should be relative to this path. 157 RootPath() string 158 159 // ComponentsPath return the path (a folder name or file name) of the YAML file for creating provider components. 160 // This value is derived from the repository URL. 161 ComponentsPath() string 162 163 // GetFile return a file for a given provider version. 164 GetFile(ctx context.Context, version string, path string) ([]byte, error) 165 166 // GetVersions return the list of versions that are available in a provider repository 167 GetVersions(ctx context.Context) ([]string, error) 168 } 169 170 // repositoryFactory returns the repository implementation corresponding to the provider URL. 171 func repositoryFactory(ctx context.Context, providerConfig config.Provider, configVariablesClient config.VariablesClient) (Repository, error) { 172 // parse the repository url 173 rURL, err := url.Parse(providerConfig.URL()) 174 if err != nil { 175 return nil, errors.Errorf("failed to parse repository url %q", providerConfig.URL()) 176 } 177 178 if rURL.Scheme == httpsScheme { 179 // if the url is a GitHub repository 180 if rURL.Host == githubDomain { 181 repo, err := NewGitHubRepository(ctx, providerConfig, configVariablesClient) 182 if err != nil { 183 return nil, errors.Wrap(err, "error creating the GitHub repository client") 184 } 185 return repo, err 186 } 187 188 // if the url is a GitLab repository 189 if strings.HasPrefix(rURL.Host, gitlabHostPrefix) && strings.HasPrefix(rURL.RawPath, gitlabPackagesAPIPrefix) { 190 repo, err := NewGitLabRepository(providerConfig, configVariablesClient) 191 if err != nil { 192 return nil, errors.Wrap(err, "error creating the GitLab repository client") 193 } 194 return repo, err 195 } 196 197 return nil, errors.Errorf("invalid provider url. Only GitHub and GitLab are supported for %q schema", rURL.Scheme) 198 } 199 200 // if the url is a local filesystem repository 201 if rURL.Scheme == "file" || rURL.Scheme == "" { 202 repo, err := newLocalRepository(ctx, providerConfig, configVariablesClient) 203 if err != nil { 204 return nil, errors.Wrap(err, "error creating the local filesystem repository client") 205 } 206 return repo, err 207 } 208 209 return nil, errors.Errorf("invalid provider url. there are no provider implementation for %q schema", rURL.Scheme) 210 }