44package agent
55
66import (
7+ "archive/tar"
78 "archive/zip"
89 "bytes"
910 "encoding/base64"
@@ -79,36 +80,101 @@ func (t *TemplateGenerator) getLinuxNodeCustomDataJSONObject(config *datamodel.N
7980 return fmt .Sprintf ("{\" customData\" : \" %s\" }" , str )
8081}
8182
82- func toButaneFile (file cloudInitWriteFile ) (* base0_5.File , error ) {
83- newfile := base0_5.File {}
84- newfile .Path = file .Path
85- newfile .User .Name = & file .Owner
86- newfile .Overwrite = to .BoolPtr (true )
87- mode , e := strconv .ParseInt (file .Permissions , 8 , 32 )
88- if e != nil {
89- return nil , fmt .Errorf ("failed to parse file mode: %w" , e )
83+ const (
84+ ignitionFilesTarPath = "/var/lib/ignition/ignition-files.tar"
85+ ignitionBootcmdScriptPath = "/etc/ignition-bootcmds.sh"
86+ ignitionTarUnitName = "ignition-file-extract.service"
87+ )
88+
89+ type ignitionTarEntry struct {
90+ path string
91+ mode int64
92+ owner string
93+ contents []byte
94+ }
95+
96+ // buildIgnitionTarEntries converts cloud-init write_files and bootcmds into tar entries.
97+ func buildIgnitionTarEntries (customData cloudInit ) ([]ignitionTarEntry , error ) {
98+ entries := make ([]ignitionTarEntry , 0 , len (customData .WriteFiles )+ 1 )
99+
100+ for _ , file := range customData .WriteFiles {
101+ mode , err := strconv .ParseInt (file .Permissions , 8 , 32 )
102+ if err != nil {
103+ return nil , fmt .Errorf ("failed to parse file mode: %w" , err )
104+ }
105+
106+ var contents []byte
107+ switch {
108+ case file .Content == "" || file .Encoding == "" :
109+ contents = []byte (file .Content )
110+ case file .Encoding == "gzip" :
111+ decoded , err := getGzipDecodedValue ([]byte (file .Content ))
112+ if err != nil {
113+ return nil , fmt .Errorf ("failed to decode gzip content for %s: %w" , file .Path , err )
114+ }
115+ contents = decoded
116+ case file .Encoding == "base64" :
117+ decoded , err := base64 .StdEncoding .DecodeString (file .Content )
118+ if err != nil {
119+ return nil , fmt .Errorf ("failed to decode base64 content: %w" , err )
120+ }
121+ contents = decoded
122+ default :
123+ return nil , fmt .Errorf ("unsupported encoding %s for %s" , file .Encoding , file .Path )
124+ }
125+
126+ entry := ignitionTarEntry {
127+ path : file .Path ,
128+ mode : mode ,
129+ owner : file .Owner ,
130+ contents : contents ,
131+ }
132+ entries = append (entries , entry )
133+ }
134+
135+ if len (customData .BootCommands ) > 0 {
136+ contents := strings .Join (append ([]string {"#!/bin/sh" }, customData .BootCommands ... ), "\n " )
137+ entries = append (entries , ignitionTarEntry {
138+ path : ignitionBootcmdScriptPath ,
139+ mode : 0o755 ,
140+ owner : "root" ,
141+ contents : []byte (contents ),
142+ })
90143 }
91- newfile .Mode = to .IntPtr (int (mode ))
92- switch file .Encoding {
93- case "gzip" :
94- newfile .Contents .Inline = & file .Content
95- // This is hit for AKSCustomCloud file
96- if file .Content != "" {
97- newfile .Contents .Compression = & file .Encoding
144+
145+ return entries , nil
146+ }
147+
148+ // buildIgnitionTarball builds a tar archive with relative paths for extraction at root.
149+ func buildIgnitionTarball (entries []ignitionTarEntry ) ([]byte , error ) {
150+ buf := new (bytes.Buffer )
151+ tarWriter := tar .NewWriter (buf )
152+
153+ for _ , entry := range entries {
154+ path := strings .TrimLeft (entry .path , "/" )
155+ header := & tar.Header {
156+ Name : path ,
157+ Mode : entry .mode ,
158+ Size : int64 (len (entry .contents )),
159+ Uid : 0 ,
160+ Gid : 0 ,
161+ }
162+ if entry .owner != "" && entry .owner != "root" {
163+ header .Uname = entry .owner
98164 }
99- case "base64" :
100- inline , e := base64 .StdEncoding .DecodeString (file .Content )
101- if e != nil {
102- return nil , fmt .Errorf ("failed to decode base64 content: %w" , e )
165+ if err := tarWriter .WriteHeader (header ); err != nil {
166+ return nil , fmt .Errorf ("failed to write tar header for %s: %w" , entry .path , err )
103167 }
104- newfile .Contents .Inline = to .StringPtr (string (inline ))
105- newfile .Contents .Compression = nil
106- case "" :
107- newfile .Contents .Inline = to .StringPtr (file .Content )
108- default :
109- return nil , fmt .Errorf ("unsupported encoding: %s" , file .Encoding )
168+ if _ , err := tarWriter .Write (entry .contents ); err != nil {
169+ return nil , fmt .Errorf ("failed to write tar contents for %s: %w" , entry .path , err )
170+ }
171+ }
172+
173+ if err := tarWriter .Close (); err != nil {
174+ return nil , fmt .Errorf ("failed to close tar writer: %w" , err )
110175 }
111- return & newfile , nil
176+
177+ return buf .Bytes (), nil
112178}
113179
114180func cloudInitToButane (customData cloudInit ) flatcar1_1.Config {
@@ -121,28 +187,31 @@ func cloudInitToButane(customData cloudInit) flatcar1_1.Config {
121187 panic (fmt .Errorf ("failed to unmarshal butane config: %w" , e ))
122188 }
123189
124- newfiles := make ([]base0_5.File , 0 )
125- var newfile * base0_5.File
126- for _ , file := range customData .WriteFiles {
127- newfile , e = toButaneFile (file )
128- if e != nil {
129- panic (fmt .Errorf ("failed to convert cloudInit file to butane file: %w" , e ))
130- }
131- newfiles = append (newfiles , * newfile )
190+ entries , err := buildIgnitionTarEntries (customData )
191+ if err != nil {
192+ panic (fmt .Errorf ("failed to convert cloudInit data to tar entries: %w" , err ))
132193 }
133- if len (customData .BootCommands ) > 0 {
134- var contents = strings .Join (append ([]string {"#!/bin/sh" }, customData .BootCommands ... ), "\n " )
135- newfiles = append (newfiles , base0_5.File {
136- Path : "/etc/ignition-bootcmds.sh" ,
137- User : base0_5.NodeUser {Name : to .StringPtr ("root" )},
138- Group : base0_5.NodeGroup {Name : to .StringPtr ("root" )},
139- Mode : to .IntPtr (0o755 ),
140- Overwrite : to .BoolPtr (true ),
141- Contents : base0_5.Resource {Inline : to .StringPtr (contents )},
142- })
194+ if len (entries ) == 0 {
195+ return butaneconfig
196+ }
197+
198+ tarball , err := buildIgnitionTarball (entries )
199+ if err != nil {
200+ panic (fmt .Errorf ("failed to build ignition tarball: %w" , err ))
143201 }
202+ compressedTarball := getGzippedBufferFromBytes (tarball )
203+ dataURL := "data:;base64," + base64 .StdEncoding .EncodeToString (compressedTarball )
144204
145- butaneconfig .Storage .Files = append (newfiles , butaneconfig .Storage .Files ... )
205+ tarFile := base0_5.File {
206+ Path : ignitionFilesTarPath ,
207+ Mode : to .IntPtr (0o600 ),
208+ Overwrite : to .BoolPtr (true ),
209+ Contents : base0_5.Resource {
210+ Source : to .StringPtr (dataURL ),
211+ Compression : to .StringPtr ("gzip" ),
212+ },
213+ }
214+ butaneconfig .Storage .Files = append (butaneconfig .Storage .Files , tarFile )
146215 return butaneconfig
147216}
148217
@@ -178,35 +247,7 @@ func (t *TemplateGenerator) getFlatcarLinuxNodeCustomDataJSONObject(config *data
178247 panic (fmt .Errorf ("failed to marshal Ignition config: %w" , e ))
179248 }
180249
181- envelope := flatcar1_1.Config {
182- Config : base0_5.Config {
183- Variant : "flatcar" ,
184- Version : "1.1.0" ,
185- Ignition : base0_5.Ignition {
186- Config : base0_5.IgnitionConfig {
187- Replace : base0_5.Resource {
188- Inline : to .StringPtr (string (ignjson )),
189- // TODO: butane 0.24.0 broke support for explicit compression
190- // so we depend on automatic resource compression.
191- // Compression: to.StringPtr("gzip"),
192- },
193- },
194- },
195- },
196- }
197- wrapped , report , e := envelope .ToIgn3_4 (butanecommon.TranslateOptions {})
198- if e != nil {
199- panic (fmt .Errorf ("butane -> ignition: error: %w:\n %s" , e , report .String ()))
200- }
201- if len (report .Entries ) > 0 {
202- panic (fmt .Errorf ("butane -> ignition: warning:\n %s" , report .String ()))
203- }
204- // Marshal the Ignition config to JSON
205- enc , err := json .Marshal (wrapped )
206- if err != nil {
207- panic (fmt .Errorf ("failed to marshal Ignition config: %w" , err ))
208- }
209- escstr := escapeSingleLine (string (enc ))
250+ escstr := escapeSingleLine (string (ignjson ))
210251
211252 return fmt .Sprintf ("{\" customData\" : \" %s\" }" , escstr )
212253}
0 commit comments