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 }