github.com/openshift/installer@v1.4.17/pkg/asset/imagebased/configimage/imagebased_config.go (about) 1 package configimage 2 3 import ( 4 "context" 5 "fmt" 6 "os" 7 "path/filepath" 8 9 "github.com/sirupsen/logrus" 10 "k8s.io/apimachinery/pkg/util/validation/field" 11 "sigs.k8s.io/yaml" 12 13 "github.com/openshift/installer/pkg/asset" 14 "github.com/openshift/installer/pkg/asset/agent/manifests/staticnetworkconfig" 15 "github.com/openshift/installer/pkg/types/imagebased" 16 "github.com/openshift/installer/pkg/validate" 17 ) 18 19 const ( 20 imageBasedConfigFilename = "image-based-config.yaml" 21 ) 22 23 // ImageBasedConfig reads the image-based-config.yaml file. 24 type ImageBasedConfig struct { 25 File *asset.File 26 Config *imagebased.Config 27 Template string 28 } 29 30 var _ asset.WritableAsset = (*ImageBasedConfig)(nil) 31 32 // Name returns a human friendly name for the asset. 33 func (*ImageBasedConfig) Name() string { 34 return "Image-based Config ISO configuration" 35 } 36 37 // Dependencies returns all of the dependencies directly needed to generate 38 // the asset. 39 func (*ImageBasedConfig) Dependencies() []asset.Asset { 40 return []asset.Asset{} 41 } 42 43 // Generate generates the Image-based Config manifest. 44 func (i *ImageBasedConfig) Generate(_ context.Context, dependencies asset.Parents) error { 45 imageBasedConfigTemplate := `# 46 # Note: This is a sample ImageBasedConfig file showing 47 # which fields are available to aid you in creating your 48 # own image-based-config.yaml file. 49 # 50 apiVersion: v1beta1 51 kind: ImageBasedConfig 52 metadata: 53 name: example-image-based-config 54 additionalNTPSources: 55 - 0.rhel.pool.ntp.org 56 - 1.rhel.pool.ntp.org 57 hostname: change-to-hostname 58 releaseRegistry: quay.io 59 # networkConfig contains the network configuration for the host in NMState format. 60 # See https://nmstate.io/examples.html for examples. 61 networkConfig: 62 interfaces: 63 - name: eth0 64 type: ethernet 65 state: up 66 mac-address: 00:00:00:00:00:00 67 ipv4: 68 enabled: true 69 address: 70 - ip: 192.168.122.2 71 prefix-length: 23 72 dhcp: false 73 ` 74 75 i.Template = imageBasedConfigTemplate 76 77 // Set the File field correctly with the generated image-based install config YAML content 78 i.File = &asset.File{ 79 Filename: imageBasedConfigFilename, 80 Data: []byte(i.Template), 81 } 82 83 return nil 84 } 85 86 // PersistToFile writes the image-based-config.yaml file to the assets folder. 87 func (i *ImageBasedConfig) PersistToFile(directory string) error { 88 if i.File == nil { 89 return nil 90 } 91 92 configPath := filepath.Join(directory, imageBasedConfigFilename) 93 err := os.WriteFile(configPath, i.File.Data, 0o600) 94 if err != nil { 95 return err 96 } 97 98 return nil 99 } 100 101 // Files returns the files generated by the asset. 102 func (i *ImageBasedConfig) Files() []*asset.File { 103 if i.File != nil { 104 return []*asset.File{i.File} 105 } 106 return []*asset.File{} 107 } 108 109 // Load returns the image-based config asset from the disk. 110 func (i *ImageBasedConfig) Load(f asset.FileFetcher) (bool, error) { 111 file, err := f.FetchByName(imageBasedConfigFilename) 112 if err != nil { 113 if os.IsNotExist(err) { 114 return false, nil 115 } 116 return false, fmt.Errorf("failed to load %s file: %w", imageBasedConfigFilename, err) 117 } 118 119 config := &imagebased.Config{} 120 if err := yaml.UnmarshalStrict(file.Data, config); err != nil { 121 return false, fmt.Errorf("failed to unmarshal %s: %w", imageBasedConfigFilename, err) 122 } 123 124 i.File, i.Config = file, config 125 126 if err = i.finish(); err != nil { 127 return false, err 128 } 129 130 return true, nil 131 } 132 133 func (i *ImageBasedConfig) finish() error { 134 if err := i.validateImageBasedConfig().ToAggregate(); err != nil { 135 return fmt.Errorf("invalid Image-based Config configuration: %w", err) 136 } 137 return nil 138 } 139 140 func (i *ImageBasedConfig) validateImageBasedConfig() field.ErrorList { 141 var allErrs field.ErrorList 142 143 if err := i.validateAdditionalNTPSources(); err != nil { 144 allErrs = append(allErrs, err...) 145 } 146 147 if err := i.validateNetworkConfig(); err != nil { 148 allErrs = append(allErrs, err...) 149 } 150 151 return allErrs 152 } 153 154 func (i *ImageBasedConfig) validateNetworkConfig() field.ErrorList { 155 var allErrs field.ErrorList 156 157 // empty NetworkConfig is fine 158 if i.Config.NetworkConfig.String() == "" { 159 return nil 160 } 161 162 networkConfig := field.NewPath("networkConfig") 163 164 staticNetworkConfigGenerator := staticnetworkconfig.New(logrus.StandardLogger(), staticnetworkconfig.Config{MaxConcurrentGenerations: 2}) 165 166 // Validate the network config using nmstatectl 167 if err := staticNetworkConfigGenerator.ValidateNMStateYaml(context.Background(), i.Config.NetworkConfig.String()); err != nil { 168 allErrs = append(allErrs, field.Invalid(networkConfig, i.Config.NetworkConfig, err.Error())) 169 } 170 171 return allErrs 172 } 173 174 func (i *ImageBasedConfig) validateAdditionalNTPSources() field.ErrorList { 175 var allErrs field.ErrorList 176 177 additionalNTPSourcesPath := field.NewPath("additionalNTPSources") 178 179 for i, source := range i.Config.AdditionalNTPSources { 180 if domainNameErr := validate.DomainName(source, true); domainNameErr != nil { 181 if ipErr := validate.IP(source); ipErr != nil { 182 allErrs = append(allErrs, field.Invalid(additionalNTPSourcesPath.Index(i), source, "NTP source is not a valid domain name nor a valid IP")) 183 } 184 } 185 } 186 187 return allErrs 188 }