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 }