github.com/openshift/installer@v1.4.17/pkg/asset/agent/joiner/clusterinfo.go (about)

     1  package joiner
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"strings"
     8  
     9  	igntypes "github.com/coreos/ignition/v2/config/v3_2/types"
    10  	"github.com/coreos/stream-metadata-go/arch"
    11  	"github.com/coreos/stream-metadata-go/stream"
    12  	"github.com/sirupsen/logrus"
    13  	corev1 "k8s.io/api/core/v1"
    14  	"k8s.io/apimachinery/pkg/api/errors"
    15  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    16  	"k8s.io/apimachinery/pkg/util/validation/field"
    17  	"k8s.io/client-go/kubernetes"
    18  	"k8s.io/client-go/rest"
    19  	"k8s.io/client-go/tools/clientcmd"
    20  	"sigs.k8s.io/yaml"
    21  
    22  	configv1 "github.com/openshift/api/config/v1"
    23  	hiveext "github.com/openshift/assisted-service/api/hiveextension/v1beta1"
    24  	"github.com/openshift/assisted-service/models"
    25  	configclient "github.com/openshift/client-go/config/clientset/versioned"
    26  	"github.com/openshift/installer/pkg/asset"
    27  	"github.com/openshift/installer/pkg/asset/agent"
    28  	"github.com/openshift/installer/pkg/asset/agent/workflow"
    29  	"github.com/openshift/installer/pkg/types"
    30  	"github.com/openshift/installer/pkg/types/aws"
    31  	"github.com/openshift/installer/pkg/types/azure"
    32  	"github.com/openshift/installer/pkg/types/baremetal"
    33  	"github.com/openshift/installer/pkg/types/external"
    34  	"github.com/openshift/installer/pkg/types/gcp"
    35  	"github.com/openshift/installer/pkg/types/ibmcloud"
    36  	"github.com/openshift/installer/pkg/types/none"
    37  	"github.com/openshift/installer/pkg/types/nutanix"
    38  	"github.com/openshift/installer/pkg/types/openstack"
    39  	"github.com/openshift/installer/pkg/types/ovirt"
    40  	"github.com/openshift/installer/pkg/types/powervs"
    41  	"github.com/openshift/installer/pkg/types/vsphere"
    42  )
    43  
    44  // ClusterInfo it's an asset used to retrieve config info
    45  // from an already existing cluster. A number of different resources
    46  // are inspected to extract the required configuration.
    47  type ClusterInfo struct {
    48  	Client          kubernetes.Interface
    49  	OpenshiftClient configclient.Interface
    50  
    51  	ClusterID                     string
    52  	ClusterName                   string
    53  	Version                       string
    54  	ReleaseImage                  string
    55  	APIDNSName                    string
    56  	PullSecret                    string
    57  	Namespace                     string
    58  	UserCaBundle                  string
    59  	Proxy                         *types.Proxy
    60  	Architecture                  string
    61  	ImageDigestSources            []types.ImageDigestSource
    62  	DeprecatedImageContentSources []types.ImageContentSource
    63  	PlatformType                  hiveext.PlatformType
    64  	SSHKey                        string
    65  	OSImage                       *stream.Stream
    66  	OSImageLocation               string
    67  	IgnitionEndpointWorker        *models.IgnitionEndpoint
    68  	FIPS                          bool
    69  	Nodes                         *corev1.NodeList
    70  }
    71  
    72  var _ asset.WritableAsset = (*ClusterInfo)(nil)
    73  
    74  // Name returns the human-friendly name of the asset.
    75  func (ci *ClusterInfo) Name() string {
    76  	return "Agent Installer ClusterInfo"
    77  }
    78  
    79  // Dependencies returns all of the dependencies directly needed to generate
    80  // the asset.
    81  func (*ClusterInfo) Dependencies() []asset.Asset {
    82  	return []asset.Asset{
    83  		&workflow.AgentWorkflow{},
    84  		&AddNodesConfig{},
    85  	}
    86  }
    87  
    88  // Generate generates the ClusterInfo.
    89  func (ci *ClusterInfo) Generate(_ context.Context, dependencies asset.Parents) error {
    90  	agentWorkflow := &workflow.AgentWorkflow{}
    91  	addNodesConfig := &AddNodesConfig{}
    92  	dependencies.Get(agentWorkflow, addNodesConfig)
    93  
    94  	if agentWorkflow.Workflow != workflow.AgentWorkflowTypeAddNodes {
    95  		return nil
    96  	}
    97  
    98  	err := ci.initClients(addNodesConfig.Params.Kubeconfig)
    99  	if err != nil {
   100  		return err
   101  	}
   102  	err = ci.retrieveClusterData()
   103  	if err != nil {
   104  		return err
   105  	}
   106  	err = ci.retrieveProxy()
   107  	if err != nil {
   108  		return err
   109  	}
   110  
   111  	err = ci.retrievePullSecret()
   112  	if err != nil {
   113  		return err
   114  	}
   115  	err = ci.retrieveUserTrustBundle()
   116  	if err != nil {
   117  		return err
   118  	}
   119  
   120  	err = ci.retrieveArchitecture(addNodesConfig)
   121  	if err != nil {
   122  		return err
   123  	}
   124  
   125  	err = ci.retrieveInstallConfigData(addNodesConfig)
   126  	if err != nil {
   127  		return err
   128  	}
   129  	err = ci.retrieveOsImage()
   130  	if err != nil {
   131  		return err
   132  	}
   133  	err = ci.retrieveIgnitionEndpointWorker()
   134  	if err != nil {
   135  		return err
   136  	}
   137  
   138  	ci.Namespace = "cluster0"
   139  
   140  	return ci.validate().ToAggregate()
   141  }
   142  
   143  func (ci *ClusterInfo) initClients(kubeconfig string) error {
   144  	if ci.Client != nil && ci.OpenshiftClient != nil {
   145  		return nil
   146  	}
   147  
   148  	var err error
   149  	var config *rest.Config
   150  	if kubeconfig != "" {
   151  		config, err = clientcmd.BuildConfigFromFlags("", kubeconfig)
   152  	} else {
   153  		config, err = rest.InClusterConfig()
   154  	}
   155  	if err != nil {
   156  		return err
   157  	}
   158  
   159  	openshiftClient, err := configclient.NewForConfig(config)
   160  	if err != nil {
   161  		return err
   162  	}
   163  	ci.OpenshiftClient = openshiftClient
   164  
   165  	k8sclientset, err := kubernetes.NewForConfig(config)
   166  	if err != nil {
   167  		return err
   168  	}
   169  	ci.Client = k8sclientset
   170  
   171  	return err
   172  }
   173  
   174  func (ci *ClusterInfo) retrieveClusterData() error {
   175  	cv, err := ci.OpenshiftClient.ConfigV1().ClusterVersions().Get(context.Background(), "version", metav1.GetOptions{})
   176  	if err != nil {
   177  		return err
   178  	}
   179  	ci.ClusterID = string(cv.Spec.ClusterID)
   180  	ci.ReleaseImage = cv.Status.History[0].Image
   181  	ci.Version = cv.Status.History[0].Version
   182  
   183  	return nil
   184  }
   185  
   186  func (ci *ClusterInfo) retrieveProxy() error {
   187  	proxy, err := ci.OpenshiftClient.ConfigV1().Proxies().Get(context.Background(), "cluster", metav1.GetOptions{})
   188  	if err != nil {
   189  		return err
   190  	}
   191  	ci.Proxy = &types.Proxy{
   192  		HTTPProxy:  proxy.Spec.HTTPProxy,
   193  		HTTPSProxy: proxy.Spec.HTTPSProxy,
   194  		NoProxy:    proxy.Spec.NoProxy,
   195  	}
   196  
   197  	return nil
   198  }
   199  
   200  func (ci *ClusterInfo) retrievePullSecret() error {
   201  	pullSecret, err := ci.Client.CoreV1().Secrets("openshift-config").Get(context.Background(), "pull-secret", metav1.GetOptions{})
   202  	if err != nil {
   203  		return err
   204  	}
   205  	ci.PullSecret = string(pullSecret.Data[".dockerconfigjson"])
   206  
   207  	return nil
   208  }
   209  
   210  func (ci *ClusterInfo) retrieveUserTrustBundle() error {
   211  	userCaBundle, err := ci.Client.CoreV1().ConfigMaps("openshift-config").Get(context.Background(), "user-ca-bundle", metav1.GetOptions{})
   212  	if err != nil {
   213  		if errors.IsNotFound(err) {
   214  			return nil
   215  		}
   216  		return err
   217  	}
   218  	ci.UserCaBundle = userCaBundle.Data["ca-bundle.crt"]
   219  
   220  	return nil
   221  }
   222  
   223  func (ci *ClusterInfo) retrieveArchitecture(addNodesConfig *AddNodesConfig) error {
   224  	nodes, err := ci.Client.CoreV1().Nodes().List(context.Background(), metav1.ListOptions{})
   225  	if err != nil {
   226  		return err
   227  	}
   228  	ci.Nodes = nodes
   229  
   230  	if addNodesConfig.Config.CPUArchitecture != "" {
   231  		logrus.Infof("CPU architecture set to: %v", addNodesConfig.Config.CPUArchitecture)
   232  		ci.Architecture = addNodesConfig.Config.CPUArchitecture
   233  		return nil
   234  	}
   235  
   236  	for _, n := range ci.Nodes.Items {
   237  		if _, found := n.GetLabels()["node-role.kubernetes.io/master"]; found {
   238  			ci.Architecture = n.Status.NodeInfo.Architecture
   239  			return nil
   240  		}
   241  	}
   242  
   243  	return fmt.Errorf("unable to determine target cluster architecture")
   244  }
   245  
   246  func (ci *ClusterInfo) retrieveInstallConfigData(addNodesConfig *AddNodesConfig) error {
   247  	clusterConfig, err := ci.Client.CoreV1().ConfigMaps("kube-system").Get(context.Background(), "cluster-config-v1", metav1.GetOptions{})
   248  	if err != nil {
   249  		if errors.IsNotFound(err) {
   250  			return nil
   251  		}
   252  		return err
   253  	}
   254  	data, ok := clusterConfig.Data["install-config"]
   255  	if !ok {
   256  		return fmt.Errorf("cannot find install-config data")
   257  	}
   258  
   259  	installConfig := types.InstallConfig{}
   260  	if err = yaml.Unmarshal([]byte(data), &installConfig); err != nil {
   261  		return err
   262  	}
   263  
   264  	ci.ImageDigestSources = installConfig.ImageDigestSources
   265  	ci.DeprecatedImageContentSources = installConfig.DeprecatedImageContentSources
   266  	ci.PlatformType = agent.HivePlatformType(installConfig.Platform)
   267  	ci.SSHKey = installConfig.SSHKey
   268  	ci.ClusterName = installConfig.ObjectMeta.Name
   269  	ci.APIDNSName = fmt.Sprintf("api.%s.%s", ci.ClusterName, installConfig.BaseDomain)
   270  	ci.FIPS = installConfig.FIPS
   271  
   272  	if addNodesConfig.Config.SSHKey != "" {
   273  		ci.SSHKey = addNodesConfig.Config.SSHKey
   274  	}
   275  
   276  	return nil
   277  }
   278  
   279  func (ci *ClusterInfo) retrieveOsImage() error {
   280  	clusterConfig, err := ci.Client.CoreV1().ConfigMaps("openshift-machine-config-operator").Get(context.Background(), "coreos-bootimages", metav1.GetOptions{})
   281  	if err != nil {
   282  		if errors.IsNotFound(err) {
   283  			return nil
   284  		}
   285  		return err
   286  	}
   287  	data, ok := clusterConfig.Data["stream"]
   288  	if !ok {
   289  		return fmt.Errorf("cannot find stream data")
   290  	}
   291  
   292  	var st stream.Stream
   293  	if err := json.Unmarshal([]byte(data), &st); err != nil {
   294  		return fmt.Errorf("failed to parse CoreOS stream metadata: %w", err)
   295  	}
   296  	ci.OSImage = &st
   297  
   298  	clusterArch := arch.RpmArch(ci.Architecture)
   299  	streamArch, err := st.GetArchitecture(clusterArch)
   300  	if err != nil {
   301  		return err
   302  	}
   303  	metal, ok := streamArch.Artifacts["metal"]
   304  	if !ok {
   305  		return fmt.Errorf("stream data not found for 'metal' artifact")
   306  	}
   307  	format, ok := metal.Formats["iso"]
   308  	if !ok {
   309  		return fmt.Errorf("no ISO found to download for %s", clusterArch)
   310  	}
   311  	ci.OSImageLocation = format.Disk.Location
   312  
   313  	return nil
   314  }
   315  
   316  // This method retrieves, if present, the secured ignition endpoint - along with its ca certificate.
   317  // These information will be used to configure subsequently the imported Assisted Service cluster,
   318  // so that the secure port (22623) could be used by the nodes to fetch the worker ignition.
   319  func (ci *ClusterInfo) retrieveIgnitionEndpointWorker() error {
   320  	workerUserDataManaged, err := ci.Client.CoreV1().Secrets("openshift-machine-api").Get(context.Background(), "worker-user-data-managed", metav1.GetOptions{})
   321  	if err != nil {
   322  		if errors.IsNotFound(err) {
   323  			return nil
   324  		}
   325  		return err
   326  	}
   327  	userData := workerUserDataManaged.Data["userData"]
   328  
   329  	config := &igntypes.Config{}
   330  	err = json.Unmarshal(userData, config)
   331  	if err != nil {
   332  		return err
   333  	}
   334  
   335  	// Check if there is at least a CA certificate in the ignition
   336  	if len(config.Ignition.Security.TLS.CertificateAuthorities) == 0 {
   337  		return nil
   338  	}
   339  
   340  	// Get the first source and ca certificate (and strip the base64 data header)
   341  	ignEndpointURL := config.Ignition.Config.Merge[0].Source
   342  	caCertSource := *config.Ignition.Security.TLS.CertificateAuthorities[0].Source
   343  
   344  	hdrIndex := strings.Index(caCertSource, ",")
   345  	if hdrIndex == -1 {
   346  		return fmt.Errorf("error while parsing ignition endpoints ca certificate, invalid data url format")
   347  	}
   348  	caCert := caCertSource[hdrIndex+1:]
   349  
   350  	ci.IgnitionEndpointWorker = &models.IgnitionEndpoint{
   351  		URL:           ignEndpointURL,
   352  		CaCertificate: &caCert,
   353  	}
   354  
   355  	return nil
   356  }
   357  
   358  // Files returns the files generated by the asset.
   359  func (*ClusterInfo) Files() []*asset.File {
   360  	return []*asset.File{}
   361  }
   362  
   363  // Load returns agent config asset from the disk.
   364  func (*ClusterInfo) Load(f asset.FileFetcher) (bool, error) {
   365  	return false, nil
   366  }
   367  
   368  func (ci *ClusterInfo) validate() field.ErrorList {
   369  	var allErrs field.ErrorList
   370  
   371  	if err := ci.validateSupportedPlatforms(); err != nil {
   372  		allErrs = append(allErrs, err...)
   373  	}
   374  
   375  	return allErrs
   376  }
   377  
   378  func (ci *ClusterInfo) validateSupportedPlatforms() field.ErrorList {
   379  	var allErrs field.ErrorList
   380  
   381  	infra, err := ci.OpenshiftClient.ConfigV1().Infrastructures().Get(context.Background(), "cluster", metav1.GetOptions{})
   382  	if err != nil {
   383  		return append(allErrs, field.InternalError(nil, err))
   384  	}
   385  	platform, err := ci.toTypesPlatform(infra.Spec.PlatformSpec.Type)
   386  	if err != nil {
   387  		return append(allErrs, field.InternalError(nil, err))
   388  	}
   389  	return agent.ValidateSupportedPlatforms(platform, ci.Architecture)
   390  }
   391  
   392  func (ci *ClusterInfo) toTypesPlatform(platformType configv1.PlatformType) (types.Platform, error) {
   393  	platform := types.Platform{}
   394  
   395  	switch platformType {
   396  	case configv1.AWSPlatformType:
   397  		platform.AWS = &aws.Platform{}
   398  	case configv1.AzurePlatformType:
   399  		platform.Azure = &azure.Platform{}
   400  	case configv1.BareMetalPlatformType:
   401  		platform.BareMetal = &baremetal.Platform{}
   402  	case configv1.GCPPlatformType:
   403  		platform.GCP = &gcp.Platform{}
   404  	case configv1.OpenStackPlatformType:
   405  		platform.OpenStack = &openstack.Platform{}
   406  	case configv1.NonePlatformType:
   407  		platform.None = &none.Platform{}
   408  	case configv1.VSpherePlatformType:
   409  		platform.VSphere = &vsphere.Platform{}
   410  	case configv1.OvirtPlatformType:
   411  		platform.Ovirt = &ovirt.Platform{}
   412  	case configv1.IBMCloudPlatformType:
   413  		platform.IBMCloud = &ibmcloud.Platform{}
   414  	case configv1.PowerVSPlatformType:
   415  		platform.PowerVS = &powervs.Platform{}
   416  	case configv1.NutanixPlatformType:
   417  		platform.Nutanix = &nutanix.Platform{}
   418  	case configv1.ExternalPlatformType:
   419  		platform.External = &external.Platform{}
   420  	default:
   421  		return platform, fmt.Errorf("unable to convert platform type %v", platformType)
   422  	}
   423  
   424  	return platform, nil
   425  }