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

     1  package manifests
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"os"
     8  	"path/filepath"
     9  
    10  	"github.com/pkg/errors"
    11  	corev1 "k8s.io/api/core/v1"
    12  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    13  	"k8s.io/apimachinery/pkg/util/validation/field"
    14  	"sigs.k8s.io/yaml"
    15  
    16  	"github.com/openshift/installer/pkg/asset"
    17  	"github.com/openshift/installer/pkg/asset/agent"
    18  	"github.com/openshift/installer/pkg/asset/agent/joiner"
    19  	"github.com/openshift/installer/pkg/asset/agent/workflow"
    20  	"github.com/openshift/installer/pkg/validate"
    21  )
    22  
    23  const (
    24  	pullSecretKey       = ".dockerconfigjson" //nolint:gosec // not a secret despite the word
    25  	agentPullSecretName = "pull-secret"
    26  )
    27  
    28  var agentPullSecretFilename = filepath.Join(clusterManifestDir, fmt.Sprintf("%s.yaml", agentPullSecretName))
    29  
    30  // AgentPullSecret generates the pull-secret file used by the agent installer.
    31  type AgentPullSecret struct {
    32  	File   *asset.File
    33  	Config *corev1.Secret
    34  }
    35  
    36  var _ asset.WritableAsset = (*AgentPullSecret)(nil)
    37  
    38  // Name returns a human friendly name for the asset.
    39  func (*AgentPullSecret) Name() string {
    40  	return "Agent PullSecret"
    41  }
    42  
    43  // Dependencies returns all of the dependencies directly needed to generate
    44  // the asset.
    45  func (*AgentPullSecret) Dependencies() []asset.Asset {
    46  	return []asset.Asset{
    47  		&workflow.AgentWorkflow{},
    48  		&joiner.ClusterInfo{},
    49  		&agent.OptionalInstallConfig{},
    50  	}
    51  }
    52  
    53  // Generate generates the AgentPullSecret manifest.
    54  func (a *AgentPullSecret) Generate(_ context.Context, dependencies asset.Parents) error {
    55  	agentWorkflow := &workflow.AgentWorkflow{}
    56  	installConfig := &agent.OptionalInstallConfig{}
    57  	clusterInfo := &joiner.ClusterInfo{}
    58  	dependencies.Get(agentWorkflow, installConfig, clusterInfo)
    59  
    60  	switch agentWorkflow.Workflow {
    61  	case workflow.AgentWorkflowTypeInstall:
    62  		if installConfig.Config != nil {
    63  			a.generateSecret(installConfig.ClusterName(), installConfig.ClusterNamespace(), installConfig.Config.PullSecret)
    64  		}
    65  
    66  	case workflow.AgentWorkflowTypeAddNodes:
    67  		a.generateSecret(clusterInfo.ClusterName, clusterInfo.Namespace, clusterInfo.PullSecret)
    68  
    69  	default:
    70  		return fmt.Errorf("AgentWorkflowType value not supported: %s", agentWorkflow.Workflow)
    71  	}
    72  
    73  	return a.finish()
    74  }
    75  
    76  func (a *AgentPullSecret) generateSecret(name, namespace, pullSecret string) {
    77  	secret := &corev1.Secret{
    78  		TypeMeta: metav1.TypeMeta{
    79  			APIVersion: "v1",
    80  			Kind:       "Secret",
    81  		},
    82  		ObjectMeta: metav1.ObjectMeta{
    83  			Name:      getPullSecretName(name),
    84  			Namespace: namespace,
    85  		},
    86  		StringData: map[string]string{
    87  			pullSecretKey: pullSecret,
    88  		},
    89  	}
    90  	a.Config = secret
    91  }
    92  
    93  // Files returns the files generated by the asset.
    94  func (a *AgentPullSecret) Files() []*asset.File {
    95  	if a.File != nil {
    96  		return []*asset.File{a.File}
    97  	}
    98  	return []*asset.File{}
    99  }
   100  
   101  // Load returns the asset from disk.
   102  func (a *AgentPullSecret) Load(f asset.FileFetcher) (bool, error) {
   103  	file, err := f.FetchByName(agentPullSecretFilename)
   104  	if err != nil {
   105  		if os.IsNotExist(err) {
   106  			return false, nil
   107  		}
   108  		return false, errors.Wrap(err, fmt.Sprintf("failed to load %s file", agentPullSecretFilename))
   109  	}
   110  
   111  	config := &corev1.Secret{}
   112  	if err := yaml.UnmarshalStrict(file.Data, config); err != nil {
   113  		return false, errors.Wrapf(err, "failed to unmarshal %s", agentPullSecretFilename)
   114  	}
   115  
   116  	a.Config = config
   117  	if err = a.finish(); err != nil {
   118  		return false, err
   119  	}
   120  
   121  	return true, nil
   122  }
   123  
   124  func (a *AgentPullSecret) finish() error {
   125  	if a.Config == nil {
   126  		return errors.New("missing configuration or manifest file")
   127  	}
   128  
   129  	if err := a.validatePullSecret().ToAggregate(); err != nil {
   130  		return errors.Wrapf(err, "invalid PullSecret configuration")
   131  	}
   132  
   133  	// Normalise the JSON formatting so that we can redact the file reliably
   134  	normal, err := normalizeDockerConfig(a.Config.StringData[pullSecretKey])
   135  	if err != nil {
   136  		return err
   137  	}
   138  	a.Config.StringData[pullSecretKey] = normal
   139  
   140  	secretData, err := yaml.Marshal(a.Config)
   141  	if err != nil {
   142  		return errors.Wrap(err, "failed to marshal agent secret")
   143  	}
   144  	a.File = &asset.File{
   145  		Filename: agentPullSecretFilename,
   146  		Data:     secretData,
   147  	}
   148  	return nil
   149  }
   150  
   151  func normalizeDockerConfig(stringData string) (string, error) {
   152  	var data map[string]interface{}
   153  	if err := json.Unmarshal([]byte(stringData), &data); err != nil {
   154  		return stringData, err
   155  	}
   156  	normal, err := json.MarshalIndent(data, "", "  ")
   157  	if err == nil {
   158  		stringData = string(normal)
   159  	}
   160  	return stringData, err
   161  }
   162  
   163  func (a *AgentPullSecret) validatePullSecret() field.ErrorList {
   164  	if err := a.validateSecretIsNotEmpty(); err != nil {
   165  		return err
   166  	}
   167  
   168  	fieldPath := field.NewPath("StringData")
   169  	dockerConfig := a.Config.StringData[pullSecretKey]
   170  	if err := validate.ImagePullSecret(dockerConfig); err != nil {
   171  		return field.ErrorList{field.Invalid(fieldPath, dockerConfig, err.Error())}
   172  	}
   173  
   174  	return field.ErrorList{}
   175  }
   176  
   177  func (a *AgentPullSecret) validateSecretIsNotEmpty() field.ErrorList {
   178  	var allErrs field.ErrorList
   179  
   180  	fieldPath := field.NewPath("StringData")
   181  
   182  	if len(a.Config.StringData) == 0 {
   183  		allErrs = append(allErrs, field.Required(fieldPath, "the pull secret is empty"))
   184  		return allErrs
   185  	}
   186  
   187  	pullSecret, ok := a.Config.StringData[pullSecretKey]
   188  	if !ok {
   189  		allErrs = append(allErrs, field.Required(fieldPath, "the pull secret key '.dockerconfigjson' is not defined"))
   190  		return allErrs
   191  	}
   192  
   193  	if pullSecret == "" {
   194  		allErrs = append(allErrs, field.Required(fieldPath, "the pull secret does not contain any data"))
   195  		return allErrs
   196  	}
   197  
   198  	return allErrs
   199  }
   200  
   201  // GetPullSecretData returns the content of the pull secret.
   202  func (a *AgentPullSecret) GetPullSecretData() string {
   203  	return a.Config.StringData[".dockerconfigjson"]
   204  }