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

     1  package agentconfig
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"os"
     7  	"path/filepath"
     8  
     9  	"github.com/pkg/errors"
    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/types/agent"
    15  	"github.com/openshift/installer/pkg/types/agent/conversion"
    16  	"github.com/openshift/installer/pkg/validate"
    17  )
    18  
    19  var (
    20  	agentConfigFilename = "agent-config.yaml"
    21  )
    22  
    23  // AgentConfig reads the agent-config.yaml file.
    24  type AgentConfig struct {
    25  	File     *asset.File
    26  	Config   *agent.Config
    27  	Template string
    28  }
    29  
    30  var _ asset.WritableAsset = (*AgentConfig)(nil)
    31  
    32  // Name returns a human friendly name for the asset.
    33  func (*AgentConfig) Name() string {
    34  	return "Agent Config"
    35  }
    36  
    37  // Dependencies returns all of the dependencies directly needed to generate
    38  // the asset.
    39  func (*AgentConfig) Dependencies() []asset.Asset {
    40  	return []asset.Asset{}
    41  }
    42  
    43  // Generate generates the Agent Config manifest.
    44  func (a *AgentConfig) Generate(_ context.Context, dependencies asset.Parents) error {
    45  	// TODO: We are temporarily generating a template of the agent-config.yaml
    46  	// Change this when its interactive survey is implemented.
    47  	agentConfigTemplate := `#
    48  # Note: This is a sample AgentConfig file showing
    49  # which fields are available to aid you in creating your
    50  # own agent-config.yaml file.
    51  #
    52  apiVersion: v1beta1
    53  kind: AgentConfig
    54  metadata:
    55    name: example-agent-config
    56    namespace: cluster0
    57  # All fields are optional
    58  rendezvousIP: your-node0-ip
    59  bootArtifactsBaseURL: http://user-specified-infra.com
    60  additionalNTPSources:
    61  - 0.rhel.pool.ntp.org
    62  - 1.rhel.pool.ntp.org
    63  hosts:
    64  # If a host is listed, then at least one interface
    65  # needs to be specified.
    66  - hostname: change-to-hostname
    67    role: master
    68    # For more information about rootDeviceHints:
    69    # https://docs.openshift.com/container-platform/4.10/installing/installing_bare_metal_ipi/ipi-install-installation-workflow.html#root-device-hints_ipi-install-installation-workflow
    70    rootDeviceHints:
    71      deviceName: /dev/sda
    72    # interfaces are used to identify the host to apply this configuration to
    73    interfaces:
    74      - macAddress: 00:00:00:00:00:00
    75        name: host-network-interface-name
    76    # networkConfig contains the network configuration for the host in NMState format.
    77    # See https://nmstate.io/examples.html for examples.
    78    networkConfig:
    79      interfaces:
    80        - name: eth0
    81          type: ethernet
    82          state: up
    83          mac-address: 00:00:00:00:00:00
    84          ipv4:
    85            enabled: true
    86            address:
    87              - ip: 192.168.122.2
    88                prefix-length: 23
    89            dhcp: false
    90  `
    91  
    92  	a.Template = agentConfigTemplate
    93  
    94  	// Set the File field correctly with the generated agent config YAML content
    95  	a.File = &asset.File{
    96  		Filename: agentConfigFilename,
    97  		Data:     []byte(a.Template),
    98  	}
    99  
   100  	// TODO: template is not validated
   101  	return nil
   102  }
   103  
   104  // PersistToFile writes the agent-config.yaml file to the assets folder.
   105  func (a *AgentConfig) PersistToFile(directory string) error {
   106  	templatePath := filepath.Join(directory, agentConfigFilename)
   107  	templateByte := []byte(a.Template)
   108  
   109  	err := os.WriteFile(templatePath, templateByte, 0600)
   110  	if err != nil {
   111  		return err
   112  	}
   113  
   114  	return nil
   115  }
   116  
   117  // Files returns the files generated by the asset.
   118  func (a *AgentConfig) Files() []*asset.File {
   119  	if a.File != nil {
   120  		return []*asset.File{a.File}
   121  	}
   122  	return []*asset.File{}
   123  }
   124  
   125  // Load returns agent config asset from the disk.
   126  func (a *AgentConfig) Load(f asset.FileFetcher) (bool, error) {
   127  	file, err := f.FetchByName(agentConfigFilename)
   128  	if err != nil {
   129  		if os.IsNotExist(err) {
   130  			return false, nil
   131  		}
   132  		return false, errors.Wrap(err, fmt.Sprintf("failed to load %s file", agentConfigFilename))
   133  	}
   134  
   135  	config := &agent.Config{}
   136  	if err := yaml.UnmarshalStrict(file.Data, config); err != nil {
   137  		return false, errors.Wrapf(err, "failed to unmarshal %s", agentConfigFilename)
   138  	}
   139  
   140  	// Upconvert any deprecated fields
   141  	if err := conversion.ConvertAgentConfig(config); err != nil {
   142  		return false, err
   143  	}
   144  
   145  	a.File, a.Config = file, config
   146  
   147  	if err = a.finish(); err != nil {
   148  		return false, err
   149  	}
   150  
   151  	return true, nil
   152  }
   153  
   154  func (a *AgentConfig) finish() error {
   155  	if err := a.validateAgent().ToAggregate(); err != nil {
   156  		return errors.Wrapf(err, "invalid Agent Config configuration")
   157  	}
   158  
   159  	return nil
   160  }
   161  
   162  func (a *AgentConfig) validateAgent() field.ErrorList {
   163  	var allErrs field.ErrorList
   164  
   165  	if err := a.validateRendezvousIP(); err != nil {
   166  		allErrs = append(allErrs, err...)
   167  	}
   168  
   169  	if err := a.validateAdditionalNTPSources(field.NewPath("AdditionalNTPSources"), a.Config.AdditionalNTPSources); err != nil {
   170  		allErrs = append(allErrs, err...)
   171  	}
   172  
   173  	if err := a.validateBootArtifactsBaseURL(); err != nil {
   174  		allErrs = append(allErrs, err...)
   175  	}
   176  
   177  	return allErrs
   178  }
   179  
   180  func (a *AgentConfig) validateRendezvousIP() field.ErrorList {
   181  	var allErrs field.ErrorList
   182  
   183  	rendezvousIPPath := field.NewPath("rendezvousIP")
   184  
   185  	// empty rendezvous ip is fine
   186  	if a.Config.RendezvousIP == "" {
   187  		return nil
   188  	}
   189  
   190  	if err := validate.IP(a.Config.RendezvousIP); err != nil {
   191  		allErrs = append(allErrs, field.Invalid(rendezvousIPPath, a.Config.RendezvousIP, err.Error()))
   192  	}
   193  
   194  	return allErrs
   195  }
   196  
   197  func (a *AgentConfig) validateAdditionalNTPSources(additionalNTPSourcesPath *field.Path, sources []string) field.ErrorList {
   198  	var allErrs field.ErrorList
   199  
   200  	for i, source := range sources {
   201  		domainNameErr := validate.DomainName(source, true)
   202  		if domainNameErr != nil {
   203  			ipErr := validate.IP(source)
   204  			if ipErr != nil {
   205  				allErrs = append(allErrs, field.Invalid(additionalNTPSourcesPath.Index(i), source, "NTP source is not a valid domain name nor a valid IP"))
   206  			}
   207  		}
   208  	}
   209  
   210  	return allErrs
   211  }
   212  
   213  func (a *AgentConfig) validateBootArtifactsBaseURL() field.ErrorList {
   214  	var allErrs field.ErrorList
   215  
   216  	bootArtifactsBaseURL := field.NewPath("bootArtifactsBaseURL")
   217  
   218  	// empty bootArtifactsBaseURL is fine
   219  	if a.Config.BootArtifactsBaseURL == "" {
   220  		return nil
   221  	}
   222  
   223  	if err := validate.URI(a.Config.BootArtifactsBaseURL); err != nil {
   224  		allErrs = append(allErrs, field.Invalid(bootArtifactsBaseURL, a.Config.BootArtifactsBaseURL, err.Error()))
   225  	}
   226  
   227  	return allErrs
   228  }
   229  
   230  func unmarshalJSON(b []byte) []byte {
   231  	output, _ := yaml.JSONToYAML(b)
   232  	return output
   233  }