diff --git a/pkg/clusterctl/client/client.go b/pkg/clusterctl/client/client.go index 3dc8c62c0..ed937db59 100644 --- a/pkg/clusterctl/client/client.go +++ b/pkg/clusterctl/client/client.go @@ -15,8 +15,11 @@ package client import ( + "sigs.k8s.io/cluster-api/cmd/clusterctl/api/v1alpha3" clusterctlclient "sigs.k8s.io/cluster-api/cmd/clusterctl/client" clusterctlconfig "sigs.k8s.io/cluster-api/cmd/clusterctl/client/config" + "sigs.k8s.io/cluster-api/cmd/clusterctl/client/repository" + "sigs.k8s.io/cluster-api/cmd/clusterctl/client/yamlprocessor" clog "sigs.k8s.io/cluster-api/cmd/clusterctl/log" airshipv1 "opendev.org/airship/airshipctl/pkg/api/v1alpha1" @@ -26,11 +29,23 @@ import ( var _ Interface = &Client{} +const ( + // BootstrapProviderType is a local copy of appropriate type from cluster-api + BootstrapProviderType = v1alpha3.BootstrapProviderType + // CoreProviderType is a local copy of appropriate type from cluster-api + CoreProviderType = v1alpha3.CoreProviderType + // ControlPlaneProviderType is a local copy of appropriate type from cluster-api + ControlPlaneProviderType = v1alpha3.ControlPlaneProviderType + // InfrastructureProviderType is a local copy of appropriate type from cluster-api + InfrastructureProviderType = v1alpha3.InfrastructureProviderType +) + // Interface is abstraction to Clusterctl type Interface interface { Init(kubeconfigPath, kubeconfigContext string) error Move(fromKubeconfigPath, fromKubeconfigContext, toKubeconfigPath, toKubeconfigContext, namespace string) error GetKubeconfig(options *GetKubeconfigOptions) (string, error) + Render(options RenderOptions) ([]byte, error) } // Client Implements interface to Clusterctl @@ -38,6 +53,14 @@ type Client struct { clusterctlClient clusterctlclient.Client initOptions clusterctlclient.InitOptions moveOptions clusterctlclient.MoveOptions + repoFactory RepositoryFactory +} + +// RenderOptions is used to get providers from RepoFactory for Render method +type RenderOptions struct { + ProviderName string + ProviderVersion string + ProviderType string } // GetKubeconfigOptions carries all the options to retrieve kubeconfig from parent cluster @@ -69,11 +92,11 @@ func NewClient(root string, debug bool, options *airshipv1.Clusterctl) (Interfac ControlPlaneProviders: initOptions.ControlPlaneProviders, } } - cclient, err := newClusterctlClient(root, options) + cclient, rf, err := newClusterctlClient(root, options) if err != nil { return nil, err } - return &Client{clusterctlClient: cclient, initOptions: cio}, nil + return &Client{clusterctlClient: cclient, initOptions: cio, repoFactory: rf}, nil } // Init implements interface to Clusterctl @@ -90,10 +113,6 @@ func (c *Client) Init(kubeconfigPath, kubeconfigContext string) error { // newConfig returns clusterctl config client func newConfig(options *airshipv1.Clusterctl, root string) (clusterctlconfig.Client, error) { for _, provider := range options.Providers { - // this is a workaround as clusterctl validates if URL is empty, even though it is not - // used anywhere outside repository factory which we override - // TODO (kkalynovskyi) we need to create issue for this in clusterctl, and remove URL - // validation and move it to be an error during repository interface initialization if !provider.IsClusterctlRepository { provider.URL = root } @@ -105,11 +124,13 @@ func newConfig(options *airshipv1.Clusterctl, root string) (clusterctlconfig.Cli return clusterctlconfig.New("", clusterctlconfig.InjectReader(reader)) } -func newClusterctlClient(root string, options *airshipv1.Clusterctl) (clusterctlclient.Client, error) { +func newClusterctlClient(root string, options *airshipv1.Clusterctl) (clusterctlclient.Client, + RepositoryFactory, error) { cconf, err := newConfig(options, root) if err != nil { - return nil, err + return nil, RepositoryFactory{}, err } + rf := RepositoryFactory{ Options: options, ConfigClient: cconf, @@ -120,7 +141,32 @@ func newClusterctlClient(root string, options *airshipv1.Clusterctl) (clusterctl orf := clusterctlclient.InjectRepositoryFactory(rf.ClientRepositoryFactory()) // options cluster client factory occf := clusterctlclient.InjectClusterClientFactory(rf.ClusterClientFactory()) - return clusterctlclient.New("", ocf, orf, occf) + client, err := clusterctlclient.New("", ocf, orf, occf) + return client, rf, err +} + +// Render returns requested components as yaml +func (c *Client) Render(renderOptions RenderOptions) ([]byte, error) { + provider, err := c.repoFactory.ConfigClient.Providers().Get(renderOptions.ProviderName, + v1alpha3.ProviderType(renderOptions.ProviderType)) + if err != nil { + return nil, err + } + + crf := c.repoFactory.ClientRepositoryFactory() + repoClient, err := crf(clusterctlclient.RepositoryClientFactoryInput{ + Provider: provider, + Processor: yamlprocessor.NewSimpleProcessor(), + }) + if err != nil { + return nil, err + } + + components, err := repoClient.Components().Get(repository.ComponentsOptions{Version: renderOptions.ProviderVersion}) + if err != nil { + return nil, err + } + return components.Yaml() } // GetKubeconfig is a wrapper for related cluster-api function diff --git a/pkg/phase/executors/clusterctl.go b/pkg/phase/executors/clusterctl.go index daf5993e9..c4f6d1261 100755 --- a/pkg/phase/executors/clusterctl.go +++ b/pkg/phase/executors/clusterctl.go @@ -15,11 +15,14 @@ package executors import ( + "bytes" "io" + "strings" airshipv1 "opendev.org/airship/airshipctl/pkg/api/v1alpha1" "opendev.org/airship/airshipctl/pkg/cluster/clustermap" "opendev.org/airship/airshipctl/pkg/clusterctl/client" + "opendev.org/airship/airshipctl/pkg/document" "opendev.org/airship/airshipctl/pkg/errors" "opendev.org/airship/airshipctl/pkg/events" "opendev.org/airship/airshipctl/pkg/k8s/kubeconfig" @@ -162,8 +165,44 @@ func (c *ClusterctlExecutor) Validate() error { } // Render executor documents -func (c *ClusterctlExecutor) Render(w io.Writer, _ ifc.RenderOptions) error { - // will be implemented later - _, err := w.Write([]byte{}) - return err +func (c *ClusterctlExecutor) Render(w io.Writer, ro ifc.RenderOptions) error { + dataAll := bytes.NewBuffer([]byte{}) + typeMap := map[string][]string{ + string(client.BootstrapProviderType): c.options.InitOptions.BootstrapProviders, + string(client.ControlPlaneProviderType): c.options.InitOptions.ControlPlaneProviders, + string(client.InfrastructureProviderType): c.options.InitOptions.InfrastructureProviders, + string(client.CoreProviderType): (map[bool][]string{true: {c.options.InitOptions.CoreProvider}, + false: {}})[c.options.InitOptions.CoreProvider != ""], + } + for prvType, prvList := range typeMap { + for _, prv := range prvList { + res := strings.Split(prv, ":") + if len(res) != 2 { + return ErrUnableParseProvider{ + Provider: prv, + ProviderType: prvType, + } + } + data, err := c.Interface.Render(client.RenderOptions{ + ProviderName: res[0], + ProviderVersion: res[1], + ProviderType: prvType, + }) + if err != nil { + return err + } + dataAll.Write(data) + dataAll.Write([]byte("\n---\n")) + } + } + + bundle, err := document.NewBundleFromBytes(dataAll.Bytes()) + if err != nil { + return err + } + filtered, err := bundle.SelectBundle(ro.FilterSelector) + if err != nil { + return err + } + return filtered.Write(w) } diff --git a/pkg/phase/executors/clusterctl_test.go b/pkg/phase/executors/clusterctl_test.go index b6758ecd6..70b136b0a 100755 --- a/pkg/phase/executors/clusterctl_test.go +++ b/pkg/phase/executors/clusterctl_test.go @@ -44,7 +44,7 @@ metadata: name: clusterctl-v1 action: %s init-options: - core-provider: "cluster-api:v0.3.3" + core-provider: "cluster-api:v0.3.2" kubeConfigRef: apiVersion: airshipit.org/v1alpha1 kind: KubeConfig @@ -53,7 +53,19 @@ providers: - name: "cluster-api" type: "CoreProvider" versions: - v0.3.3: manifests/function/capi/v0.3.3` + v0.3.2: functions/capi/infrastructure/v0.3.2` + + renderedDocs = `--- +apiVersion: v1 +kind: Namespace +metadata: + labels: + cluster.x-k8s.io/provider: cluster-api + clusterctl.cluster.x-k8s.io: "" + control-plane: controller-manager + name: version-two +... +` ) func TestNewExecutor(t *testing.T) { @@ -202,6 +214,6 @@ func TestExecutorRender(t *testing.T) { require.NoError(t, err) actualOut := &bytes.Buffer{} actualErr := executor.Render(actualOut, ifc.RenderOptions{}) - assert.Len(t, actualOut.Bytes(), 0) + assert.Equal(t, renderedDocs, actualOut.String()) assert.NoError(t, actualErr) } diff --git a/pkg/phase/executors/errors.go b/pkg/phase/executors/errors.go index c92d8b26f..6058c924d 100755 --- a/pkg/phase/executors/errors.go +++ b/pkg/phase/executors/errors.go @@ -47,3 +47,13 @@ type ErrUnknownExecutorName struct { func (e ErrUnknownExecutorName) Error() string { return fmt.Sprintf("unknown executor name '%s'", e.ExecutorName) } + +// ErrUnableParseProvider is returned when it's impossible to parse provider's name and version +type ErrUnableParseProvider struct { + Provider string + ProviderType string +} + +func (e ErrUnableParseProvider) Error() string { + return fmt.Sprintf("unable to parse name and version of '%s' type, '%s' provider", e.ProviderType, e.Provider) +}