• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

kubevirt / kubevirt / 7dc86bb8-0ab1-4cde-9ee6-3c44c6e610dd

19 Feb 2025 03:21AM UTC coverage: 71.633% (+0.02%) from 71.609%
7dc86bb8-0ab1-4cde-9ee6-3c44c6e610dd

push

prow

web-flow
Merge pull request #13807 from Barakmor1/diskinfo

Move disk info collection and verification from virt-handler to virt-launcher

54 of 110 new or added lines in 18 files covered. (49.09%)

9 existing lines in 3 files now uncovered.

62190 of 86818 relevant lines covered (71.63%)

0.8 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

72.28
/pkg/cloud-init/cloud-init.go
1
/*
2
 * This file is part of the kubevirt project
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *     http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 *
16
 * Copyright 2017 Red Hat, Inc.
17
 *
18
 */
19

20
package cloudinit
21

22
import (
23
        "encoding/base64"
24
        "encoding/json"
25
        "fmt"
26
        "os"
27
        "os/exec"
28
        "path"
29
        "path/filepath"
30
        "strconv"
31
        "strings"
32
        "time"
33

34
        "github.com/google/uuid"
35

36
        v1 "kubevirt.io/api/core/v1"
37
        "kubevirt.io/client-go/log"
38
        "kubevirt.io/client-go/precond"
39

40
        diskutils "kubevirt.io/kubevirt/pkg/ephemeral-disk-utils"
41
        "kubevirt.io/kubevirt/pkg/util"
42
        "kubevirt.io/kubevirt/pkg/util/net/dns"
43
)
44

45
const isoStagingFmt = "%s.staging"
46

47
type IsoCreationFunc func(isoOutFile, volumeID string, inDir string) error
48

49
var cloudInitLocalDir = "/var/run/libvirt/cloud-init-dir"
50
var cloudInitIsoFunc = defaultIsoFunc
51

52
// Locations of data source disk files
53
const (
54
        noCloudFile     = "noCloud.iso"
55
        configDriveFile = "configdrive.iso"
56
)
57

58
type DataSourceType string
59
type DeviceMetadataType string
60

61
const (
62
        DataSourceNoCloud     DataSourceType     = "noCloud"
63
        DataSourceConfigDrive DataSourceType     = "configDrive"
64
        NICMetadataType       DeviceMetadataType = "nic"
65
        HostDevMetadataType   DeviceMetadataType = "hostdev"
66
)
67

68
// CloudInitData is a data source independent struct that
69
// holds cloud-init user and network data
70
type CloudInitData struct {
71
        DataSource          DataSourceType
72
        NoCloudMetaData     *NoCloudMetadata
73
        ConfigDriveMetaData *ConfigDriveMetadata
74
        UserData            string
75
        NetworkData         string
76
        DevicesData         *[]DeviceData
77
        VolumeName          string
78
}
79

80
type PublicSSHKey struct {
81
        string
82
}
83

84
type NoCloudMetadata struct {
85
        InstanceType  string            `json:"instance-type,omitempty"`
86
        InstanceID    string            `json:"instance-id"`
87
        LocalHostname string            `json:"local-hostname,omitempty"`
88
        PublicSSHKeys map[string]string `json:"public-keys,omitempty"`
89
}
90

91
type ConfigDriveMetadata struct {
92
        InstanceType  string            `json:"instance_type,omitempty"`
93
        InstanceID    string            `json:"instance_id"`
94
        LocalHostname string            `json:"local_hostname,omitempty"`
95
        Hostname      string            `json:"hostname,omitempty"`
96
        UUID          string            `json:"uuid,omitempty"`
97
        Devices       *[]DeviceData     `json:"devices,omitempty"`
98
        PublicSSHKeys map[string]string `json:"public_keys,omitempty"`
99
}
100

101
type DeviceData struct {
102
        Type        DeviceMetadataType `json:"type"`
103
        Bus         string             `json:"bus"`
104
        Address     string             `json:"address"`
105
        MAC         string             `json:"mac,omitempty"`
106
        Serial      string             `json:"serial,omitempty"`
107
        NumaNode    uint32             `json:"numaNode,omitempty"`
108
        AlignedCPUs []uint32           `json:"alignedCPUs,omitempty"`
109
        Tags        []string           `json:"tags"`
110
}
111

112
// IsValidCloudInitData checks if the given CloudInitData object is valid in the sense that GenerateLocalData can be called with it.
113
func IsValidCloudInitData(cloudInitData *CloudInitData) bool {
×
114
        return cloudInitData != nil && cloudInitData.UserData != "" && (cloudInitData.NoCloudMetaData != nil || cloudInitData.ConfigDriveMetaData != nil)
×
115
}
×
116

117
func cloudInitUUIDFromVMI(vmi *v1.VirtualMachineInstance) string {
1✔
118
        if vmi.Spec.Domain.Firmware == nil {
2✔
119
                return uuid.NewString()
1✔
120
        }
1✔
121
        return string(vmi.Spec.Domain.Firmware.UUID)
1✔
122
}
123

124
// ReadCloudInitVolumeDataSource scans the given VMI for CloudInit volumes and
125
// reads their content into a CloudInitData struct. Does not resolve secret refs.
126
func ReadCloudInitVolumeDataSource(vmi *v1.VirtualMachineInstance, secretSourceDir string) (cloudInitData *CloudInitData, err error) {
1✔
127
        precond.MustNotBeNil(vmi)
1✔
128
        // ClusterInstancetypeAnnotation will take precedence over a namespaced Instancetype
1✔
129
        // for setting instance_type in the metadata
1✔
130
        instancetype := vmi.Annotations[v1.ClusterInstancetypeAnnotation]
1✔
131
        if instancetype == "" {
2✔
132
                instancetype = vmi.Annotations[v1.InstancetypeAnnotation]
1✔
133
        }
1✔
134

135
        hostname := dns.SanitizeHostname(vmi)
1✔
136

1✔
137
        for _, volume := range vmi.Spec.Volumes {
2✔
138
                if volume.CloudInitNoCloud != nil {
2✔
139
                        keys, err := resolveNoCloudSecrets(vmi, secretSourceDir)
1✔
140
                        if err != nil {
1✔
141
                                return nil, err
×
142
                        }
×
143

144
                        cloudInitData, err = readCloudInitNoCloudSource(volume.CloudInitNoCloud)
1✔
145
                        cloudInitData.NoCloudMetaData = readCloudInitNoCloudMetaData(hostname, cloudInitUUIDFromVMI(vmi), instancetype, keys)
1✔
146
                        cloudInitData.VolumeName = volume.Name
1✔
147
                        return cloudInitData, err
1✔
148
                }
149
                if volume.CloudInitConfigDrive != nil {
1✔
150

×
151
                        keys, err := resolveConfigDriveSecrets(vmi, secretSourceDir)
×
152
                        if err != nil {
×
153
                                return nil, err
×
154
                        }
×
155

156
                        uuid := cloudInitUUIDFromVMI(vmi)
×
157
                        cloudInitData, err = readCloudInitConfigDriveSource(volume.CloudInitConfigDrive)
×
158
                        cloudInitData.ConfigDriveMetaData = readCloudInitConfigDriveMetaData(vmi.Name, uuid, hostname, vmi.Namespace, keys, instancetype)
×
159
                        cloudInitData.VolumeName = volume.Name
×
160
                        return cloudInitData, err
×
161
                }
162
        }
163
        return nil, nil
1✔
164
}
165

166
func isNoCloudAccessCredential(accessCred v1.AccessCredential) bool {
1✔
167
        return accessCred.SSHPublicKey != nil && accessCred.SSHPublicKey.PropagationMethod.NoCloud != nil
1✔
168
}
1✔
169

170
func isConfigDriveAccessCredential(accessCred v1.AccessCredential) bool {
1✔
171
        return accessCred.SSHPublicKey != nil && accessCred.SSHPublicKey.PropagationMethod.ConfigDrive != nil
1✔
172
}
1✔
173

174
func resolveSSHPublicKeys(accessCredentials []v1.AccessCredential, secretSourceDir string, isAccessCredentialValidFunc func(v1.AccessCredential) bool) (map[string]string, error) {
1✔
175
        keys := make(map[string]string)
1✔
176
        count := 0
1✔
177
        for _, accessCred := range accessCredentials {
2✔
178

1✔
179
                if !isAccessCredentialValidFunc(accessCred) {
1✔
180
                        continue
×
181
                }
182

183
                secretName := ""
1✔
184
                if accessCred.SSHPublicKey.Source.Secret != nil {
2✔
185
                        secretName = accessCred.SSHPublicKey.Source.Secret.SecretName
1✔
186
                }
1✔
187

188
                if secretName == "" {
1✔
189
                        continue
×
190
                }
191

192
                baseDir := filepath.Join(secretSourceDir, secretName+"-access-cred")
1✔
193
                files, err := os.ReadDir(baseDir)
1✔
194
                if err != nil {
1✔
195
                        return keys, err
×
196
                }
×
197

198
                for _, file := range files {
2✔
199
                        if file.IsDir() || strings.HasPrefix(file.Name(), "..") {
1✔
200
                                continue
×
201
                        }
202
                        keyData, err := readFileFromDir(baseDir, file.Name())
1✔
203

1✔
204
                        if err != nil {
1✔
205
                                return keys, fmt.Errorf("Unable to read public keys found at volume: %s/%s error: %v", baseDir, file.Name(), err)
×
206
                        }
×
207

208
                        if keyData == "" {
1✔
209
                                continue
×
210
                        }
211
                        keys[strconv.Itoa(count)] = keyData
1✔
212
                        count++
1✔
213
                }
214
        }
215
        return keys, nil
1✔
216
}
217

218
// resolveNoCloudSecrets is looking for CloudInitNoCloud volumes with UserDataSecretRef
219
// requests. It reads the `userdata` secret the corresponds to the given CloudInitNoCloud
220
// volume and sets the UserData field on that volume.
221
//
222
// Note: when using this function, make sure that your code can access the secret volumes.
223
func resolveNoCloudSecrets(vmi *v1.VirtualMachineInstance, secretSourceDir string) (map[string]string, error) {
1✔
224
        keys, err := resolveSSHPublicKeys(vmi.Spec.AccessCredentials, secretSourceDir, isNoCloudAccessCredential)
1✔
225
        if err != nil {
1✔
226
                return keys, err
×
227
        }
×
228

229
        volume := findCloudInitNoCloudSecretVolume(vmi.Spec.Volumes)
1✔
230
        if volume == nil {
2✔
231
                return keys, nil
1✔
232
        }
1✔
233

234
        baseDir := filepath.Join(secretSourceDir, volume.Name)
1✔
235
        var userDataError, networkDataError error
1✔
236
        var userData, networkData string
1✔
237
        if volume.CloudInitNoCloud.UserDataSecretRef != nil {
2✔
238
                userData, userDataError = readFirstFoundFileFromDir(baseDir, []string{"userdata", "userData"})
1✔
239
        }
1✔
240
        if volume.CloudInitNoCloud.NetworkDataSecretRef != nil {
2✔
241
                networkData, networkDataError = readFirstFoundFileFromDir(baseDir, []string{"networkdata", "networkData"})
1✔
242
        }
1✔
243
        if userDataError != nil && networkDataError != nil {
2✔
244
                return keys, fmt.Errorf("no cloud-init data-source found at volume: %s", volume.Name)
1✔
245
        }
1✔
246

247
        if userData != "" {
2✔
248
                volume.CloudInitNoCloud.UserData = userData
1✔
249
        }
1✔
250
        if networkData != "" {
2✔
251
                volume.CloudInitNoCloud.NetworkData = networkData
1✔
252
        }
1✔
253

254
        return keys, nil
1✔
255
}
256

257
// resolveConfigDriveSecrets is looking for CloudInitConfigDriveSource volume source with
258
// UserDataSecretRef and NetworkDataSecretRef and resolves the secret from the corresponding
259
// VolumeMount.
260
//
261
// Note: when using this function, make sure that your code can access the secret volumes.
262
func resolveConfigDriveSecrets(vmi *v1.VirtualMachineInstance, secretSourceDir string) (map[string]string, error) {
1✔
263
        keys, err := resolveSSHPublicKeys(vmi.Spec.AccessCredentials, secretSourceDir, isConfigDriveAccessCredential)
1✔
264
        if err != nil {
1✔
265
                return keys, err
×
266
        }
×
267

268
        volume := findCloudInitConfigDriveSecretVolume(vmi.Spec.Volumes)
1✔
269
        if volume == nil {
2✔
270
                return keys, nil
1✔
271
        }
1✔
272

273
        baseDir := filepath.Join(secretSourceDir, volume.Name)
1✔
274
        var userDataError, networkDataError error
1✔
275
        var userData, networkData string
1✔
276
        if volume.CloudInitConfigDrive.UserDataSecretRef != nil {
2✔
277
                userData, userDataError = readFirstFoundFileFromDir(baseDir, []string{"userdata", "userData"})
1✔
278
        }
1✔
279
        if volume.CloudInitConfigDrive.NetworkDataSecretRef != nil {
2✔
280
                networkData, networkDataError = readFirstFoundFileFromDir(baseDir, []string{"networkdata", "networkData"})
1✔
281
        }
1✔
282
        if userDataError != nil && networkDataError != nil {
2✔
283
                return keys, fmt.Errorf("no cloud-init data-source found at volume: %s", volume.Name)
1✔
284
        }
1✔
285
        if userData != "" {
2✔
286
                volume.CloudInitConfigDrive.UserData = userData
1✔
287
        }
1✔
288
        if networkData != "" {
2✔
289
                volume.CloudInitConfigDrive.NetworkData = networkData
1✔
290
        }
1✔
291

292
        return keys, nil
1✔
293
}
294

295
// findCloudInitConfigDriveSecretVolume loops over a given list of volumes and return a pointer
296
// to the first volume with a CloudInitConfigDrive source and UserDataSecretRef field set.
297
func findCloudInitConfigDriveSecretVolume(volumes []v1.Volume) *v1.Volume {
1✔
298
        for _, volume := range volumes {
2✔
299
                if volume.CloudInitConfigDrive == nil {
1✔
300
                        continue
×
301
                }
302
                if volume.CloudInitConfigDrive.UserDataSecretRef != nil ||
1✔
303
                        volume.CloudInitConfigDrive.NetworkDataSecretRef != nil {
2✔
304
                        return &volume
1✔
305
                }
1✔
306
        }
307

308
        return nil
1✔
309
}
310

311
func readFirstFoundFileFromDir(basedir string, files []string) (string, error) {
1✔
312
        var err error
1✔
313
        var data string
1✔
314
        for _, file := range files {
2✔
315
                data, err = readFileFromDir(basedir, file)
1✔
316
                if err == nil {
2✔
317
                        break
1✔
318
                }
319
        }
320
        return data, err
1✔
321
}
322
func readFileFromDir(basedir, file string) (string, error) {
1✔
323
        filePath := filepath.Join(basedir, file)
1✔
324
        // #nosec No risk for path injection: basedir & secretFile are static strings
1✔
325
        data, err := os.ReadFile(filePath)
1✔
326
        if err != nil {
2✔
327
                log.Log.Reason(err).Errorf("could not read data from source: %s", filePath)
1✔
328
                return "", err
1✔
329
        }
1✔
330
        return string(data), nil
1✔
331
}
332

333
// findCloudInitNoCloudSecretVolume loops over a given list of volumes and return a pointer
334
// to the first CloudInitNoCloud volume with a UserDataSecretRef field set.
335
func findCloudInitNoCloudSecretVolume(volumes []v1.Volume) *v1.Volume {
1✔
336
        for _, volume := range volumes {
2✔
337
                if volume.CloudInitNoCloud == nil {
1✔
338
                        continue
×
339
                }
340
                if volume.CloudInitNoCloud.UserDataSecretRef != nil ||
1✔
341
                        volume.CloudInitNoCloud.NetworkDataSecretRef != nil {
2✔
342
                        return &volume
1✔
343
                }
1✔
344
        }
345
        return nil
1✔
346
}
347

348
func readRawOrBase64Data(rawData, base64Data string) (string, error) {
1✔
349
        if rawData != "" {
2✔
350
                return rawData, nil
1✔
351
        } else if base64Data != "" {
3✔
352
                bytes, err := base64.StdEncoding.DecodeString(base64Data)
1✔
353
                return string(bytes), err
1✔
354
        }
1✔
355
        return "", nil
1✔
356
}
357

358
// readCloudInitData reads user and network data raw or in base64 encoding,
359
// regardless from which data source they are coming from
360
func readCloudInitData(userData, userDataBase64, networkData, networkDataBase64 string) (string, string, error) {
1✔
361
        readUserData, err := readRawOrBase64Data(userData, userDataBase64)
1✔
362
        if err != nil {
2✔
363
                return "", "", err
1✔
364
        }
1✔
365

366
        readNetworkData, err := readRawOrBase64Data(networkData, networkDataBase64)
1✔
367
        if err != nil {
2✔
368
                return "", "", err
1✔
369
        }
1✔
370

371
        if readUserData == "" && readNetworkData == "" {
2✔
372
                return "", "", fmt.Errorf("userDataBase64, userData, networkDataBase64 or networkData is required for a cloud-init data source")
1✔
373
        }
1✔
374

375
        return readUserData, readNetworkData, nil
1✔
376
}
377

378
func readCloudInitNoCloudSource(source *v1.CloudInitNoCloudSource) (*CloudInitData, error) {
1✔
379
        userData, networkData, err := readCloudInitData(source.UserData,
1✔
380
                source.UserDataBase64, source.NetworkData, source.NetworkDataBase64)
1✔
381
        if err != nil {
2✔
382
                return &CloudInitData{}, err
1✔
383
        }
1✔
384

385
        return &CloudInitData{
1✔
386
                DataSource:  DataSourceNoCloud,
1✔
387
                UserData:    userData,
1✔
388
                NetworkData: networkData,
1✔
389
        }, nil
1✔
390
}
391

392
func readCloudInitConfigDriveSource(source *v1.CloudInitConfigDriveSource) (*CloudInitData, error) {
1✔
393
        userData, networkData, err := readCloudInitData(source.UserData,
1✔
394
                source.UserDataBase64, source.NetworkData, source.NetworkDataBase64)
1✔
395
        if err != nil {
2✔
396
                return &CloudInitData{}, err
1✔
397
        }
1✔
398

399
        return &CloudInitData{
1✔
400
                DataSource:  DataSourceConfigDrive,
1✔
401
                UserData:    userData,
1✔
402
                NetworkData: networkData,
1✔
403
        }, nil
1✔
404
}
405

406
func readCloudInitNoCloudMetaData(hostname, instanceId string, instanceType string, keys map[string]string) *NoCloudMetadata {
1✔
407
        return &NoCloudMetadata{
1✔
408
                InstanceType:  instanceType,
1✔
409
                InstanceID:    instanceId,
1✔
410
                LocalHostname: hostname,
1✔
411
                PublicSSHKeys: keys,
1✔
412
        }
1✔
413
}
1✔
414

415
func readCloudInitConfigDriveMetaData(name, uuid, hostname, namespace string, keys map[string]string, instanceType string) *ConfigDriveMetadata {
×
416
        return &ConfigDriveMetadata{
×
417
                InstanceType:  instanceType,
×
418
                UUID:          uuid,
×
419
                InstanceID:    fmt.Sprintf("%s.%s", name, namespace),
×
420
                Hostname:      hostname,
×
421
                PublicSSHKeys: keys,
×
422
        }
×
423
}
×
424

425
func defaultIsoFunc(isoOutFile, volumeID string, inDir string) error {
×
426

×
427
        var args []string
×
428

×
429
        args = append(args, "-output")
×
430
        args = append(args, isoOutFile)
×
431
        args = append(args, "-volid")
×
432
        args = append(args, volumeID)
×
433
        args = append(args, "-joliet")
×
434
        args = append(args, "-rock")
×
435
        args = append(args, "-partition_cyl_align")
×
436
        args = append(args, "on")
×
437
        args = append(args, inDir)
×
438

×
439
        isoBinary := "xorrisofs"
×
440

×
NEW
441
        // #nosec No risk for attacker injection. Parameters are predefined strings
×
442
        cmd := exec.Command(isoBinary, args...)
×
443

×
444
        err := cmd.Start()
×
445
        if err != nil {
×
446
                log.Log.Reason(err).Errorf("%s cmd failed to start while generating iso file %s", isoBinary, isoOutFile)
×
447
                return err
×
448
        }
×
449

450
        done := make(chan error)
×
451
        go func() { done <- cmd.Wait() }()
×
452

453
        timeout := time.After(10 * time.Second)
×
454

×
455
        for {
×
456
                select {
×
457
                case <-timeout:
×
458
                        log.Log.Errorf("Timed out generating cloud-init iso at path %s", isoOutFile)
×
459
                        cmd.Process.Kill()
×
460
                case err := <-done:
×
461
                        if err != nil {
×
462
                                log.Log.Reason(err).Errorf("%s returned non-zero exit code while generating iso file %s with args '%s'", isoBinary, isoOutFile, strings.Join(cmd.Args, " "))
×
463
                                return err
×
464
                        }
×
465
                        return nil
×
466
                }
467
        }
468
}
469

470
// The unit test suite uses this function
471
func SetIsoCreationFunction(isoFunc IsoCreationFunc) {
1✔
472
        cloudInitIsoFunc = isoFunc
1✔
473
}
1✔
474

475
func SetLocalDirectory(dir string) error {
1✔
476
        err := util.MkdirAllWithNosec(dir)
1✔
477
        if err != nil {
1✔
478
                return fmt.Errorf("unable to initialize cloudInit local cache directory (%s). %v", dir, err)
×
479
        }
×
480

481
        exists, err := diskutils.FileExists(dir)
1✔
482
        if err != nil {
1✔
483
                return fmt.Errorf("CloudInit local cache directory (%s) does not exist or is inaccessible. %v", dir, err)
×
484
        } else if exists == false {
1✔
485
                return fmt.Errorf("CloudInit local cache directory (%s) does not exist or is inaccessible", dir)
×
486
        }
×
487

488
        SetLocalDirectoryOnly(dir)
1✔
489
        return nil
1✔
490
}
491

492
// XXX refactor this whole package
493
// This is just a cheap workaround to make e2e tests pass
494
func SetLocalDirectoryOnly(dir string) {
1✔
495
        cloudInitLocalDir = dir
1✔
496
}
1✔
497

498
func getDomainBasePath(domain string, namespace string) string {
1✔
499
        return fmt.Sprintf("%s/%s/%s", cloudInitLocalDir, namespace, domain)
1✔
500
}
1✔
501

502
func GetIsoFilePath(source DataSourceType, domain, namespace string) string {
1✔
503
        switch source {
1✔
504
        case DataSourceNoCloud:
1✔
505
                return fmt.Sprintf("%s/%s", getDomainBasePath(domain, namespace), noCloudFile)
1✔
506
        case DataSourceConfigDrive:
1✔
507
                return fmt.Sprintf("%s/%s", getDomainBasePath(domain, namespace), configDriveFile)
1✔
508
        }
509
        return fmt.Sprintf("%s/%s", getDomainBasePath(domain, namespace), noCloudFile)
×
510
}
511

512
func PrepareLocalPath(vmiName string, namespace string) error {
1✔
513
        return util.MkdirAllWithNosec(getDomainBasePath(vmiName, namespace))
1✔
514
}
1✔
515

516
func GenerateEmptyIso(vmiName string, namespace string, data *CloudInitData, size int64) error {
1✔
517
        precond.MustNotBeEmpty(vmiName)
1✔
518
        precond.MustNotBeNil(data)
1✔
519

1✔
520
        var err error
1✔
521
        var isoStaging, iso string
1✔
522

1✔
523
        switch data.DataSource {
1✔
524
        case DataSourceNoCloud, DataSourceConfigDrive:
1✔
525
                iso = GetIsoFilePath(data.DataSource, vmiName, namespace)
1✔
526
        default:
×
527
                return fmt.Errorf("invalid cloud-init data source: '%v'", data.DataSource)
×
528
        }
529
        isoStaging = fmt.Sprintf(isoStagingFmt, iso)
1✔
530

1✔
531
        err = diskutils.RemoveFilesIfExist(isoStaging)
1✔
532
        if err != nil {
1✔
533
                return err
×
534
        }
×
535

536
        err = util.MkdirAllWithNosec(path.Dir(isoStaging))
1✔
537
        if err != nil {
1✔
538
                log.Log.Reason(err).Errorf("unable to create cloud-init base path %s", path.Dir(isoStaging))
×
539
                return err
×
540
        }
×
541

542
        f, err := os.Create(isoStaging)
1✔
543
        if err != nil {
1✔
544
                return fmt.Errorf("failed to create empty iso: '%s'", isoStaging)
×
545
        }
×
546

547
        err = util.WriteBytes(f, 0, size)
1✔
548
        if err != nil {
1✔
549
                return err
×
550
        }
×
551
        util.CloseIOAndCheckErr(f, &err)
1✔
552
        if err != nil {
1✔
553
                return err
×
554
        }
×
555

556
        if err := diskutils.DefaultOwnershipManager.UnsafeSetFileOwnership(isoStaging); err != nil {
1✔
557
                return err
×
558
        }
×
559
        err = os.Rename(isoStaging, iso)
1✔
560
        if err != nil {
1✔
561
                log.Log.Reason(err).Errorf("Cloud-init failed to rename file %s to %s", isoStaging, iso)
×
562
                return err
×
563
        }
×
564

565
        log.Log.V(2).Infof("generated empty iso file %s", iso)
1✔
566
        return nil
1✔
567
}
568

569
func GenerateLocalData(vmi *v1.VirtualMachineInstance, instanceType string, data *CloudInitData) error {
1✔
570
        precond.MustNotBeEmpty(vmi.Name)
1✔
571
        precond.MustNotBeNil(data)
1✔
572

1✔
573
        var metaData []byte
1✔
574
        var err error
1✔
575

1✔
576
        domainBasePath := getDomainBasePath(vmi.Name, vmi.Namespace)
1✔
577
        dataBasePath := fmt.Sprintf("%s/data", domainBasePath)
1✔
578

1✔
579
        var dataPath, metaFile, userFile, networkFile, iso, isoStaging string
1✔
580
        switch data.DataSource {
1✔
581
        case DataSourceNoCloud:
1✔
582
                dataPath = dataBasePath
1✔
583
                metaFile = fmt.Sprintf("%s/%s", dataPath, "meta-data")
1✔
584
                userFile = fmt.Sprintf("%s/%s", dataPath, "user-data")
1✔
585
                networkFile = fmt.Sprintf("%s/%s", dataPath, "network-config")
1✔
586
                iso = GetIsoFilePath(DataSourceNoCloud, vmi.Name, vmi.Namespace)
1✔
587
                isoStaging = fmt.Sprintf(isoStagingFmt, iso)
1✔
588
                if data.NoCloudMetaData == nil {
2✔
589
                        log.Log.V(2).Infof("No metadata found in cloud-init data. Create minimal metadata with instance-id.")
1✔
590
                        data.NoCloudMetaData = &NoCloudMetadata{
1✔
591
                                InstanceID: cloudInitUUIDFromVMI(vmi),
1✔
592
                        }
1✔
593
                        data.NoCloudMetaData.InstanceType = instanceType
1✔
594
                }
1✔
595
                metaData, err = json.Marshal(data.NoCloudMetaData)
1✔
596
                if err != nil {
1✔
597
                        return err
×
598
                }
×
599
        case DataSourceConfigDrive:
1✔
600
                dataPath = fmt.Sprintf("%s/openstack/latest", dataBasePath)
1✔
601
                metaFile = fmt.Sprintf("%s/%s", dataPath, "meta_data.json")
1✔
602
                userFile = fmt.Sprintf("%s/%s", dataPath, "user_data")
1✔
603
                networkFile = fmt.Sprintf("%s/%s", dataPath, "network_data.json")
1✔
604
                iso = GetIsoFilePath(DataSourceConfigDrive, vmi.Name, vmi.Namespace)
1✔
605
                isoStaging = fmt.Sprintf(isoStagingFmt, iso)
1✔
606
                if data.ConfigDriveMetaData == nil {
2✔
607
                        log.Log.V(2).Infof("No metadata found in cloud-init data. Create minimal metadata with instance-id.")
1✔
608
                        instanceId := fmt.Sprintf("%s.%s", vmi.Name, vmi.Namespace)
1✔
609
                        data.ConfigDriveMetaData = &ConfigDriveMetadata{
1✔
610
                                InstanceID: instanceId,
1✔
611
                                UUID:       cloudInitUUIDFromVMI(vmi),
1✔
612
                        }
1✔
613
                        data.ConfigDriveMetaData.InstanceType = instanceType
1✔
614
                }
1✔
615
                data.ConfigDriveMetaData.Devices = data.DevicesData
1✔
616
                metaData, err = json.Marshal(data.ConfigDriveMetaData)
1✔
617
                if err != nil {
1✔
618
                        return err
×
619
                }
×
620

621
        default:
×
622
                return fmt.Errorf("Invalid cloud-init data source: '%v'", data.DataSource)
×
623
        }
624

625
        err = util.MkdirAllWithNosec(dataPath)
1✔
626
        if err != nil {
1✔
627
                log.Log.Reason(err).Errorf("unable to create cloud-init base path %s", domainBasePath)
×
628
                return err
×
629
        }
×
630

631
        if data.UserData == "" && data.NetworkData == "" {
1✔
632
                return fmt.Errorf("UserData or NetworkData is required for cloud-init data source")
×
633
        }
×
634
        userData := []byte(data.UserData)
1✔
635

1✔
636
        var networkData []byte
1✔
637
        if data.NetworkData != "" {
2✔
638
                networkData = []byte(data.NetworkData)
1✔
639
        }
1✔
640

641
        err = diskutils.RemoveFilesIfExist(userFile, metaFile, networkFile, isoStaging)
1✔
642
        if err != nil {
1✔
643
                return err
×
644
        }
×
645

646
        err = os.WriteFile(userFile, userData, 0600)
1✔
647
        if err != nil {
1✔
648
                return err
×
649
        }
×
650
        defer os.Remove(userFile)
1✔
651

1✔
652
        err = os.WriteFile(metaFile, metaData, 0600)
1✔
653
        if err != nil {
1✔
654
                return err
×
655
        }
×
656
        defer os.Remove(metaFile)
1✔
657

1✔
658
        if len(networkData) > 0 {
2✔
659
                err = os.WriteFile(networkFile, networkData, 0600)
1✔
660
                if err != nil {
1✔
661
                        return err
×
662
                }
×
663
                defer os.Remove(networkFile)
1✔
664
        }
665

666
        switch data.DataSource {
1✔
667
        case DataSourceNoCloud:
1✔
668
                err = cloudInitIsoFunc(isoStaging, "cidata", dataBasePath)
1✔
669
        case DataSourceConfigDrive:
1✔
670
                err = cloudInitIsoFunc(isoStaging, "config-2", dataBasePath)
1✔
671
        }
672
        if err != nil {
2✔
673
                return err
1✔
674
        }
1✔
675

676
        if err := diskutils.DefaultOwnershipManager.UnsafeSetFileOwnership(isoStaging); err != nil {
1✔
677
                return err
×
678
        }
×
679

680
        err = os.Rename(isoStaging, iso)
1✔
681
        if err != nil {
1✔
682
                log.Log.Reason(err).Errorf("Cloud-init failed to rename file %s to %s", isoStaging, iso)
×
683
                return err
×
684
        }
×
685

686
        log.Log.V(2).Infof("generated nocloud iso file %s", iso)
1✔
687
        return nil
1✔
688
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc