Implement render method for clusterctl executor

Change-Id: If3d66baa8eec27f51705c813f17854ad7ef23a26
Signed-off-by: Ruslan Aliev <raliev@mirantis.com>
This commit is contained in:
Ruslan Aliev 2021-01-31 14:51:24 -06:00
parent cc25bcf52e
commit 0f44ad9a7c
4 changed files with 123 additions and 16 deletions

View File

@ -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

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}