diff --git a/cmd/clusterctl/client/client.go b/cmd/clusterctl/client/client.go index f7eb88272e12..af54214260f5 100644 --- a/cmd/clusterctl/client/client.go +++ b/cmd/clusterctl/client/client.go @@ -17,6 +17,8 @@ limitations under the License. package client import ( + "path" + clusterctlv1 "sigs.k8s.io/cluster-api/cmd/clusterctl/api/v1alpha3" "sigs.k8s.io/cluster-api/cmd/clusterctl/client/alpha" "sigs.k8s.io/cluster-api/cmd/clusterctl/client/cluster" @@ -168,16 +170,19 @@ func New(path string, options ...Option) (Client, error) { return newClusterctlClient(path, options...) } -func newClusterctlClient(path string, options ...Option) (*clusterctlClient, error) { +func newClusterctlClient(configPath string, options ...Option) (*clusterctlClient, error) { client := &clusterctlClient{} for _, o := range options { o(client) } + // Use directory of the clusterctl configuration file as repository path. + repoPath := path.Dir(configPath) + // if there is an injected config, use it, otherwise use the default one // provided by the config low level library. if client.configClient == nil { - c, err := config.New(path) + c, err := config.New(configPath) if err != nil { return nil, err } @@ -186,12 +191,12 @@ func newClusterctlClient(path string, options ...Option) (*clusterctlClient, err // if there is an injected RepositoryFactory, use it, otherwise use a default one. if client.repositoryClientFactory == nil { - client.repositoryClientFactory = defaultRepositoryFactory(client.configClient) + client.repositoryClientFactory = defaultRepositoryFactory(client.configClient, repoPath) } // if there is an injected ClusterFactory, use it, otherwise use a default one. if client.clusterClientFactory == nil { - client.clusterClientFactory = defaultClusterFactory(client.configClient) + client.clusterClientFactory = defaultClusterFactory(client.configClient, repoPath) } // if there is an injected alphaClient, use it, otherwise use a default one. @@ -204,24 +209,34 @@ func newClusterctlClient(path string, options ...Option) (*clusterctlClient, err } // defaultRepositoryFactory is a RepositoryClientFactory func the uses the default client provided by the repository low level library. -func defaultRepositoryFactory(configClient config.Client) RepositoryClientFactory { +func defaultRepositoryFactory(configClient config.Client, repoPath string) RepositoryClientFactory { + rp := repoPath return func(input RepositoryClientFactoryInput) (repository.Client, error) { return repository.New( input.Provider, configClient, repository.InjectYamlProcessor(input.Processor), + repository.Path(rp), ) } } // defaultClusterFactory is a ClusterClientFactory func the uses the default client provided by the cluster low level library. -func defaultClusterFactory(configClient config.Client) ClusterClientFactory { +func defaultClusterFactory(configClient config.Client, repoPath string) 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.InjectRepoPath(repoPath), cluster.InjectYamlProcessor(input.Processor), + cluster.InjectRepositoryFactory(func(provider config.Provider, configClient config.Client, options ...repository.Option) (repository.Client, error) { + options = append(options, + repository.InjectYamlProcessor(input.Processor), + repository.Path(repoPath), + ) + return repository.New(provider, configClient, options...) + }), ), nil } } diff --git a/cmd/clusterctl/client/cluster/cert_manager.go b/cmd/clusterctl/client/cluster/cert_manager.go index 886281d45962..767593ab528d 100644 --- a/cmd/clusterctl/client/cluster/cert_manager.go +++ b/cmd/clusterctl/client/cluster/cert_manager.go @@ -84,6 +84,7 @@ type CertManagerClient interface { type certManagerClient struct { configClient config.Client repositoryClientFactory RepositoryClientFactory + repoPath string proxy Proxy pollImmediateWaiter PollImmediateWaiter } @@ -92,10 +93,11 @@ type certManagerClient struct { var _ CertManagerClient = &certManagerClient{} // newCertManagerClient returns a certManagerClient. -func newCertManagerClient(configClient config.Client, repositoryClientFactory RepositoryClientFactory, proxy Proxy, pollImmediateWaiter PollImmediateWaiter) *certManagerClient { +func newCertManagerClient(configClient config.Client, repositoryClientFactory RepositoryClientFactory, repoPath string, proxy Proxy, pollImmediateWaiter PollImmediateWaiter) *certManagerClient { return &certManagerClient{ configClient: configClient, repositoryClientFactory: repositoryClientFactory, + repoPath: repoPath, proxy: proxy, pollImmediateWaiter: pollImmediateWaiter, } diff --git a/cmd/clusterctl/client/cluster/client.go b/cmd/clusterctl/client/cluster/client.go index bd34d912581b..ef85a8d518d9 100644 --- a/cmd/clusterctl/client/cluster/client.go +++ b/cmd/clusterctl/client/cluster/client.go @@ -97,6 +97,7 @@ type clusterClient struct { kubeconfig Kubeconfig proxy Proxy repositoryClientFactory RepositoryClientFactory + repoPath string pollImmediateWaiter PollImmediateWaiter processor yaml.Processor } @@ -116,7 +117,7 @@ func (c *clusterClient) Proxy() Proxy { } func (c *clusterClient) CertManager() CertManagerClient { - return newCertManagerClient(c.configClient, c.repositoryClientFactory, c.proxy, c.pollImmediateWaiter) + return newCertManagerClient(c.configClient, c.repositoryClientFactory, c.repoPath, c.proxy, c.pollImmediateWaiter) } func (c *clusterClient) ProviderComponents() ComponentsClient { @@ -169,6 +170,13 @@ func InjectRepositoryFactory(factory RepositoryClientFactory) Option { } } +// InjectRepoPath allows to set the repo path. +func InjectRepoPath(repoPath string) Option { + return func(c *clusterClient) { + c.repoPath = repoPath + } +} + // InjectPollImmediateWaiter allows to override the default PollImmediateWaiter used by clusterctl. func InjectPollImmediateWaiter(pollImmediateWaiter PollImmediateWaiter) Option { return func(c *clusterClient) { diff --git a/cmd/clusterctl/client/config/cert_manager_client.go b/cmd/clusterctl/client/config/cert_manager_client.go index a232829409f7..9cb0b6229963 100644 --- a/cmd/clusterctl/client/config/cert_manager_client.go +++ b/cmd/clusterctl/client/config/cert_manager_client.go @@ -17,6 +17,10 @@ limitations under the License. package config import ( + "net/url" + "os" + "path/filepath" + "strings" "time" "github.com/pkg/errors" @@ -46,15 +50,17 @@ type CertManagerClient interface { // certManagerClient implements CertManagerClient. type certManagerClient struct { - reader Reader + reader Reader + repoPath string } // ensure certManagerClient implements CertManagerClient. var _ CertManagerClient = &certManagerClient{} -func newCertManagerClient(reader Reader) *certManagerClient { +func newCertManagerClient(reader Reader, repoPath string) *certManagerClient { return &certManagerClient{ - reader: reader, + reader: reader, + repoPath: repoPath, } } @@ -66,7 +72,7 @@ type configCertManager struct { } func (p *certManagerClient) Get() (CertManager, error) { - url := CertManagerDefaultURL + certManagerURL := CertManagerDefaultURL version := CertManagerDefaultVersion timeout := CertManagerDefaultTimeout.String() @@ -75,8 +81,25 @@ func (p *certManagerClient) Get() (CertManager, error) { return nil, errors.Wrap(err, "failed to unmarshal certManager from the clusterctl configuration file") } if userCertManager.URL != "" { - url = userCertManager.URL + certManagerURL = userCertManager.URL } + + if value := os.Getenv("HOME"); value != "" { + certManagerURL = strings.ReplaceAll(certManagerURL, "${HOME}", value) + } + if value := os.Getenv("CLUSTERCTL_REPO_PATH"); value != "" { + certManagerURL = strings.ReplaceAll(certManagerURL, "${CLUSTERCTL_REPO_PATH}", value) + } + + parsedURL, err := url.Parse(certManagerURL) + if err != nil { + return nil, errors.Wrap(err, "invalid url") + } + if !filepath.IsAbs(parsedURL.Path) { + parsedURL.Path = filepath.Join(p.repoPath, parsedURL.Path) + } + certManagerURL = parsedURL.String() + if userCertManager.Version != "" { version = userCertManager.Version } @@ -84,5 +107,5 @@ func (p *certManagerClient) Get() (CertManager, error) { timeout = userCertManager.Timeout } - return NewCertManager(url, version, timeout), nil + return NewCertManager(certManagerURL, version, timeout), nil } diff --git a/cmd/clusterctl/client/config/client.go b/cmd/clusterctl/client/config/client.go index a25faf9944b1..ce11700e7517 100644 --- a/cmd/clusterctl/client/config/client.go +++ b/cmd/clusterctl/client/config/client.go @@ -17,6 +17,8 @@ limitations under the License. package config import ( + "path" + "github.com/pkg/errors" ) @@ -42,14 +44,15 @@ type Client interface { // configClient implements Client. type configClient struct { - reader Reader + reader Reader + repoPath string } // ensure configClient implements Client. var _ Client = &configClient{} func (c *configClient) CertManager() CertManagerClient { - return newCertManagerClient(c.reader) + return newCertManagerClient(c.reader, c.repoPath) } func (c *configClient) Providers() ProvidersClient { @@ -79,8 +82,10 @@ func New(path string, options ...Option) (Client, error) { return newConfigClient(path, options...) } -func newConfigClient(path string, options ...Option) (*configClient, error) { - client := &configClient{} +func newConfigClient(configPath string, options ...Option) (*configClient, error) { + client := &configClient{ + repoPath: path.Dir(configPath), + } for _, o := range options { o(client) } @@ -88,7 +93,7 @@ func newConfigClient(path string, options ...Option) (*configClient, error) { // if there is an injected reader, use it, otherwise use a default one if client.reader == nil { client.reader = newViperReader() - if err := client.reader.Init(path); err != nil { + if err := client.reader.Init(configPath); err != nil { return nil, errors.Wrap(err, "failed to initialize the configuration reader") } } diff --git a/cmd/clusterctl/client/repository/client.go b/cmd/clusterctl/client/repository/client.go index 915936880ec0..ab606764b3f4 100644 --- a/cmd/clusterctl/client/repository/client.go +++ b/cmd/clusterctl/client/repository/client.go @@ -62,6 +62,7 @@ type repositoryClient struct { configClient config.Client repository Repository processor yaml.Processor + path string } // ensure repositoryClient implements Client. @@ -114,6 +115,12 @@ func InjectYamlProcessor(p yaml.Processor) Option { } } +func Path(path string) Option { + return func(c *repositoryClient) { + c.path = path + } +} + // New returns a Client. func New(provider config.Provider, configClient config.Client, options ...Option) (Client, error) { return newRepositoryClient(provider, configClient, options...) @@ -131,7 +138,7 @@ func newRepositoryClient(provider config.Provider, configClient config.Client, o // if there is an injected repository, use it, otherwise use a default one if client.repository == nil { - r, err := repositoryFactory(provider, configClient.Variables()) + r, err := repositoryFactory(provider, configClient.Variables(), client.path) if err != nil { return nil, errors.Wrapf(err, "failed to get repository client for the %s with name %s", provider.Type(), provider.Name()) } @@ -167,7 +174,7 @@ type Repository interface { } // repositoryFactory returns the repository implementation corresponding to the provider URL. -func repositoryFactory(providerConfig config.Provider, configVariablesClient config.VariablesClient) (Repository, error) { +func repositoryFactory(providerConfig config.Provider, configVariablesClient config.VariablesClient, repoPath string) (Repository, error) { // parse the repository url rURL, err := url.Parse(providerConfig.URL()) if err != nil { @@ -198,7 +205,7 @@ func repositoryFactory(providerConfig config.Provider, configVariablesClient con // if the url is a local filesystem repository if rURL.Scheme == "file" || rURL.Scheme == "" { - repo, err := newLocalRepository(providerConfig, configVariablesClient) + repo, err := newLocalRepository(providerConfig, configVariablesClient, repoPath) if err != nil { return nil, errors.Wrap(err, "error creating the local filesystem repository client") } diff --git a/cmd/clusterctl/client/repository/repository_local.go b/cmd/clusterctl/client/repository/repository_local.go index 541f5b98aa53..9f7d182ff18b 100644 --- a/cmd/clusterctl/client/repository/repository_local.go +++ b/cmd/clusterctl/client/repository/repository_local.go @@ -135,8 +135,17 @@ func (r *localRepository) GetVersions() ([]string, error) { } // newLocalRepository returns a new localRepository. -func newLocalRepository(providerConfig config.Provider, configVariablesClient config.VariablesClient) (*localRepository, error) { - url, err := url.Parse(providerConfig.URL()) +func newLocalRepository(providerConfig config.Provider, configVariablesClient config.VariablesClient, repoPath string) (*localRepository, error) { + providerURL := providerConfig.URL() + + if value := os.Getenv("HOME"); value != "" { + providerURL = strings.ReplaceAll(providerURL, "${HOME}", value) + } + if value := os.Getenv("CLUSTERCTL_REPO_PATH"); value != "" { + providerURL = strings.ReplaceAll(providerURL, "${CLUSTERCTL_REPO_PATH}", value) + } + + url, err := url.Parse(providerURL) if err != nil { return nil, errors.Wrap(err, "invalid url") } @@ -152,7 +161,7 @@ func newLocalRepository(providerConfig config.Provider, configVariablesClient co path = filepath.FromSlash(path) } if !filepath.IsAbs(path) { - return nil, errors.Errorf("invalid path: path %q must be an absolute path", providerConfig.URL()) + path = filepath.Join(repoPath, path) } // Extracts provider-name, version, componentsPath from the url