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  }