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

     1  package image
     2  
     3  import (
     4  	"compress/gzip"
     5  	"context"
     6  	"encoding/json"
     7  	"errors"
     8  	"fmt"
     9  	"io"
    10  	"os"
    11  	"path/filepath"
    12  	"regexp"
    13  
    14  	"github.com/coreos/stream-metadata-go/arch"
    15  	"github.com/sirupsen/logrus"
    16  
    17  	"github.com/openshift/assisted-image-service/pkg/isoeditor"
    18  	"github.com/openshift/installer/pkg/asset"
    19  	"github.com/openshift/installer/pkg/types"
    20  )
    21  
    22  // AgentPXEFiles is an asset that generates the bootable image used to install clusters.
    23  type AgentPXEFiles struct {
    24  	imageReader          isoeditor.ImageReader
    25  	cpuArch              string
    26  	tmpPath              string
    27  	bootArtifactsBaseURL string
    28  	kernelArgs           string
    29  }
    30  
    31  type coreOSKargs struct {
    32  	DefaultKernelArgs string `json:"default"`
    33  }
    34  
    35  var _ asset.WritableAsset = (*AgentPXEFiles)(nil)
    36  
    37  // Dependencies returns the assets on which the AgentPXEFiles asset depends.
    38  func (a *AgentPXEFiles) Dependencies() []asset.Asset {
    39  	return []asset.Asset{
    40  		&AgentArtifacts{},
    41  	}
    42  }
    43  
    44  // Generate generates the image files for PXE asset.
    45  func (a *AgentPXEFiles) Generate(_ context.Context, dependencies asset.Parents) error {
    46  	agentArtifacts := &AgentArtifacts{}
    47  	dependencies.Get(agentArtifacts)
    48  
    49  	a.tmpPath = agentArtifacts.TmpPath
    50  
    51  	ignitionContent := &isoeditor.IgnitionContent{Config: agentArtifacts.IgnitionByte}
    52  	initrdImgPath := filepath.Join(a.tmpPath, "images", "pxeboot", "initrd.img")
    53  	custom, err := isoeditor.NewInitRamFSStreamReader(initrdImgPath, ignitionContent)
    54  	if err != nil {
    55  		return err
    56  	}
    57  
    58  	a.imageReader = custom
    59  	a.cpuArch = agentArtifacts.CPUArch
    60  	a.bootArtifactsBaseURL = agentArtifacts.BootArtifactsBaseURL
    61  
    62  	kernelArgs, err := getKernelArgs(filepath.Join(a.tmpPath, "coreos", "kargs.json"))
    63  	if err != nil {
    64  		return err
    65  	}
    66  	a.kernelArgs = kernelArgs + string(agentArtifacts.Kargs)
    67  	return nil
    68  }
    69  
    70  // PersistToFile writes the PXE assets in the assets folder named pxe.
    71  func (a *AgentPXEFiles) PersistToFile(directory string) error {
    72  	var kernelFileType string
    73  	// If the imageReader is not set then it means that either one of the AgentPXEFiles
    74  	// dependencies or the asset itself failed for some reason
    75  	if a.imageReader == nil {
    76  		return errors.New("cannot generate PXE assets due to configuration errors")
    77  	}
    78  
    79  	defer a.imageReader.Close()
    80  	bootArtifactsFullPath := filepath.Join(directory, bootArtifactsPath)
    81  
    82  	err := createDir(bootArtifactsFullPath)
    83  	if err != nil {
    84  		return err
    85  	}
    86  
    87  	err = extractRootFS(bootArtifactsFullPath, a.tmpPath, a.cpuArch)
    88  	if err != nil {
    89  		return err
    90  	}
    91  
    92  	agentInitrdFile := filepath.Join(bootArtifactsFullPath, fmt.Sprintf("agent.%s-initrd.img", a.cpuArch))
    93  	err = copyfile(agentInitrdFile, a.imageReader)
    94  	if err != nil {
    95  		return err
    96  	}
    97  
    98  	switch a.cpuArch {
    99  	case arch.RpmArch(types.ArchitectureS390X):
   100  
   101  		kernelFileType = "kernel.img"
   102  
   103  		err = a.handleAdditionals390xArtifacts(bootArtifactsFullPath)
   104  		if err != nil {
   105  			return err
   106  		}
   107  	default:
   108  		kernelFileType = "vmlinuz"
   109  	}
   110  
   111  	agentVmlinuzFile := filepath.Join(bootArtifactsFullPath, fmt.Sprintf("agent.%s-%s", a.cpuArch, kernelFileType))
   112  	kernelReader, err := os.Open(filepath.Join(a.tmpPath, "images", "pxeboot", kernelFileType))
   113  	if err != nil {
   114  		return err
   115  	}
   116  	defer kernelReader.Close()
   117  
   118  	if a.cpuArch == arch.RpmArch(types.ArchitectureARM64) {
   119  		gzipReader, err := gzip.NewReader(kernelReader)
   120  		if err != nil {
   121  			panic(err)
   122  		}
   123  		defer gzipReader.Close()
   124  		err = copyfile(agentVmlinuzFile, gzipReader)
   125  		if err != nil {
   126  			return err
   127  		}
   128  	} else {
   129  		err = copyfile(agentVmlinuzFile, kernelReader)
   130  		if err != nil {
   131  			return err
   132  		}
   133  	}
   134  
   135  	if a.bootArtifactsBaseURL != "" {
   136  		err = a.createiPXEScript(bootArtifactsFullPath)
   137  		if err != nil {
   138  			return err
   139  		}
   140  	}
   141  
   142  	logrus.Infof("PXE boot artifacts created in: %s", bootArtifactsFullPath)
   143  	logrus.Infof("Kernel parameters for PXE boot: %s", a.kernelArgs)
   144  
   145  	return nil
   146  }
   147  
   148  // Name returns the human-friendly name of the asset.
   149  func (a *AgentPXEFiles) Name() string {
   150  	return "Agent Installer PXE Files"
   151  }
   152  
   153  // Load returns the PXE image from disk.
   154  func (a *AgentPXEFiles) Load(f asset.FileFetcher) (bool, error) {
   155  	// The PXE image will not be needed by another asset so load is noop.
   156  	// This is implemented because it is required by WritableAsset
   157  	return false, nil
   158  }
   159  
   160  // Files returns the files generated by the asset.
   161  func (a *AgentPXEFiles) Files() []*asset.File {
   162  	// Return empty array because File will never be loaded.
   163  	return []*asset.File{}
   164  }
   165  
   166  func copyfile(filepath string, src io.Reader) error {
   167  	output, err := os.Create(filepath)
   168  	if err != nil {
   169  		return err
   170  	}
   171  	defer output.Close()
   172  
   173  	_, err = io.Copy(output, src)
   174  	if err != nil {
   175  		return err
   176  	}
   177  
   178  	return nil
   179  }
   180  
   181  func (a *AgentPXEFiles) createiPXEScript(pxeAssetsFullPath string) error {
   182  	iPXEScriptTemplate := `#!ipxe
   183  initrd --name initrd %s/%s
   184  kernel %s/%s initrd=initrd coreos.live.rootfs_url=%s/%s %s
   185  boot
   186  `
   187  
   188  	iPXEScript := fmt.Sprintf(iPXEScriptTemplate, a.bootArtifactsBaseURL,
   189  		fmt.Sprintf("agent.%s-initrd.img", a.cpuArch), a.bootArtifactsBaseURL,
   190  		fmt.Sprintf("agent.%s-vmlinuz", a.cpuArch), a.bootArtifactsBaseURL,
   191  		fmt.Sprintf("agent.%s-rootfs.img", a.cpuArch), a.kernelArgs)
   192  
   193  	iPXEFile := fmt.Sprintf("agent.%s.ipxe", a.cpuArch)
   194  
   195  	err := os.WriteFile(filepath.Join(pxeAssetsFullPath, iPXEFile), []byte(iPXEScript), 0600)
   196  	if err != nil {
   197  		return err
   198  	}
   199  	logrus.Infof("Created iPXE script %s in %s directory", iPXEFile, pxeAssetsFullPath)
   200  
   201  	return nil
   202  }
   203  
   204  func getKernelArgs(filepath string) (string, error) {
   205  	kargs, err := os.Open(filepath)
   206  	if err != nil {
   207  		return "", err
   208  	}
   209  	defer kargs.Close()
   210  
   211  	data, err := io.ReadAll(kargs)
   212  	if err != nil {
   213  		return "", err
   214  	}
   215  
   216  	var args coreOSKargs
   217  	err = json.Unmarshal(data, &args)
   218  	if err != nil {
   219  		return "", err
   220  	}
   221  
   222  	// Remove the coreos.liveiso arg
   223  	liveISOArgMatch := regexp.MustCompile(`coreos\.liveiso=[^ ]+ ?`)
   224  	kernelArgs := liveISOArgMatch.ReplaceAllString(args.DefaultKernelArgs, "")
   225  	return kernelArgs, nil
   226  }
   227  
   228  func (a *AgentPXEFiles) handleAdditionals390xArtifacts(bootArtifactsFullPath string) error {
   229  	// initrd is already copied and file pointer is at EOF so move it to start again.
   230  	_, err := a.imageReader.Seek(0, io.SeekStart)
   231  	if err != nil {
   232  		return err
   233  	}
   234  
   235  	agentInitrdAddrFilePath := filepath.Join(a.tmpPath, "images", "initrd.addrsize")
   236  	addrsizeFile, err := isoeditor.NewInitrdAddrsizeReader(agentInitrdAddrFilePath, a.imageReader)
   237  	if err != nil {
   238  		return err
   239  	}
   240  
   241  	agentInitrdAddrFile := filepath.Join(bootArtifactsFullPath, fmt.Sprintf("agent.%s-initrd.addrsize", a.cpuArch))
   242  	err = copyfile(agentInitrdAddrFile, addrsizeFile)
   243  	if err != nil {
   244  		return err
   245  	}
   246  
   247  	agentINSFile := filepath.Join(bootArtifactsFullPath, fmt.Sprintf("agent.%s-generic.ins", a.cpuArch))
   248  	genericReader, err := os.Open(filepath.Join(a.tmpPath, "generic.ins"))
   249  	if err != nil {
   250  		return err
   251  	}
   252  	defer genericReader.Close()
   253  
   254  	err = copyfile(agentINSFile, genericReader)
   255  	if err != nil {
   256  		return err
   257  	}
   258  
   259  	return nil
   260  }