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 }