Skip to content

Commit

Permalink
clusterctl: support relative path and HOME/CLUSTERCTL_REPO_PATH env vars
Browse files Browse the repository at this point in the history
  • Loading branch information
sbueringer committed Oct 5, 2022
1 parent c6cbae8 commit 93e11b7
Show file tree
Hide file tree
Showing 7 changed files with 94 additions and 25 deletions.
27 changes: 21 additions & 6 deletions cmd/clusterctl/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
}
Expand All @@ -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.
Expand All @@ -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
}
}
4 changes: 3 additions & 1 deletion cmd/clusterctl/client/cluster/cert_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ type CertManagerClient interface {
type certManagerClient struct {
configClient config.Client
repositoryClientFactory RepositoryClientFactory
repoPath string
proxy Proxy
pollImmediateWaiter PollImmediateWaiter
}
Expand All @@ -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,
}
Expand Down
10 changes: 9 additions & 1 deletion cmd/clusterctl/client/cluster/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ type clusterClient struct {
kubeconfig Kubeconfig
proxy Proxy
repositoryClientFactory RepositoryClientFactory
repoPath string
pollImmediateWaiter PollImmediateWaiter
processor yaml.Processor
}
Expand All @@ -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 {
Expand Down Expand Up @@ -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) {
Expand Down
35 changes: 29 additions & 6 deletions cmd/clusterctl/client/config/cert_manager_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ limitations under the License.
package config

import (
"net/url"
"os"
"path/filepath"
"strings"
"time"

"github.com/pkg/errors"
Expand Down Expand Up @@ -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,
}
}

Expand All @@ -66,7 +72,7 @@ type configCertManager struct {
}

func (p *certManagerClient) Get() (CertManager, error) {
url := CertManagerDefaultURL
certManagerURL := CertManagerDefaultURL
version := CertManagerDefaultVersion
timeout := CertManagerDefaultTimeout.String()

Expand All @@ -75,14 +81,31 @@ 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
}
if userCertManager.Timeout != "" {
timeout = userCertManager.Timeout
}

return NewCertManager(url, version, timeout), nil
return NewCertManager(certManagerURL, version, timeout), nil
}
15 changes: 10 additions & 5 deletions cmd/clusterctl/client/config/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ limitations under the License.
package config

import (
"path"

"github.com/pkg/errors"
)

Expand All @@ -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 {
Expand Down Expand Up @@ -79,16 +82,18 @@ 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)
}

// 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")
}
}
Expand Down
13 changes: 10 additions & 3 deletions cmd/clusterctl/client/repository/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ type repositoryClient struct {
configClient config.Client
repository Repository
processor yaml.Processor
path string
}

// ensure repositoryClient implements Client.
Expand Down Expand Up @@ -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...)
Expand All @@ -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())
}
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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")
}
Expand Down
15 changes: 12 additions & 3 deletions cmd/clusterctl/client/repository/repository_local.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
Expand All @@ -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
Expand Down

0 comments on commit 93e11b7

Please sign in to comment.