-
Notifications
You must be signed in to change notification settings - Fork 468
/
Copy pathcoreos_ignition_def.go
199 lines (167 loc) · 5.25 KB
/
coreos_ignition_def.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
package libvirt
import (
"context"
"encoding/json"
"encoding/xml"
"fmt"
"io"
"log"
"os"
"strings"
libvirt "github.com/digitalocean/go-libvirt"
"github.com/google/uuid"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
)
type defIgnition struct {
Name string
PoolName string
Content string
// Toggle the resource to a combustion script.
// Combustion and ignition have very similar boot parameters, only the format changes:
// combustion is a shell script while ignition is JSON.
Combustion bool
}
// Creates a new cloudinit with the defaults
// the provider uses.
func newIgnitionDef() defIgnition {
ign := defIgnition{}
return ign
}
// Create a ISO file based on the contents of the CloudInit instance and
// uploads it to the libVirt pool
// Returns a string holding terraform's internal ID of this resource.
func (ign *defIgnition) CreateAndUpload(ctx context.Context, client *Client) (string, error) {
virConn := client.libvirt
pool, err := virConn.StoragePoolLookupByName(ign.PoolName)
if err != nil {
return "", fmt.Errorf("can't find storage pool '%s'", ign.PoolName)
}
client.poolMutexKV.Lock(ign.PoolName)
defer client.poolMutexKV.Unlock(ign.PoolName)
// Refresh the pool of the volume so that libvirt knows it is
// not longer in use.
if err := retry.RetryContext(ctx, resourceStateTimeout, func() *retry.RetryError {
if err := virConn.StoragePoolRefresh(pool, 0); err != nil {
return retry.RetryableError(fmt.Errorf("error refreshing pool for volume: %w", err))
}
return nil
}); err != nil {
return "", err
}
volumeDef := newDefVolume()
volumeDef.Name = ign.Name
ignFile, err := ign.createFile()
if err != nil {
return "", err
}
defer func() {
if err = os.Remove(ignFile); err != nil {
log.Printf("Error while removing tmp Ignition file: %v", err)
}
}()
img, err := newImage(ignFile)
if err != nil {
return "", err
}
size, err := img.Size()
if err != nil {
return "", err
}
volumeDef.Capacity.Unit = "B"
volumeDef.Capacity.Value = size
volumeDef.Target.Format.Type = "raw"
volumeDefXML, err := xml.Marshal(volumeDef)
if err != nil {
return "", fmt.Errorf("error serializing libvirt volume: %w", err)
}
// create the volume
volume, err := virConn.StorageVolCreateXML(pool, string(volumeDefXML), 0)
if err != nil {
return "", fmt.Errorf("error creating libvirt volume for Ignition %s: %w", ign.Name, err)
}
// upload ignition file
err = img.Import(newVolumeUploader(virConn, &volume, volumeDef.Capacity.Value), volumeDef)
if err != nil {
return "", fmt.Errorf("error while uploading ignition file %s: %w", img.String(), err)
}
if volume.Key == "" {
return "", fmt.Errorf("error retrieving volume key")
}
return ign.buildTerraformKey(volume.Key), nil
}
// create a unique ID for terraform use
// The ID is made by the volume ID (the internal one used by libvirt)
// joined by the ";" with a UUID.
func (ign *defIgnition) buildTerraformKey(volumeKey string) string {
return fmt.Sprintf("%s;%s", volumeKey, uuid.New())
}
//nolint:mnd
func getIgnitionVolumeKeyFromTerraformID(id string) (string, error) {
s := strings.SplitN(id, ";", 2)
if len(s) != 2 {
return "", fmt.Errorf("%s is not a valid key", id)
}
return s[0], nil
}
// Dumps the Ignition object - either generated by Terraform or supplied as a file -
// to a temporary ignition file.
func (ign *defIgnition) createFile() (string, error) {
log.Print("Creating Ignition temporary file")
tempFile, err := os.CreateTemp("", ign.Name)
if err != nil {
return "", fmt.Errorf("error creating tmp file: %w", err)
}
defer tempFile.Close()
var file bool
file = true
if _, err := os.Stat(ign.Content); err != nil {
var js map[string]interface{}
if errConf := json.Unmarshal([]byte(ign.Content), &js); !ign.Combustion && errConf != nil {
return "", fmt.Errorf("coreos_ignition 'content' is neither a file "+
"nor a valid json object %s", ign.Content)
}
file = false
}
if !file {
if _, err := tempFile.WriteString(ign.Content); err != nil {
return "", fmt.Errorf("cannot write Ignition object to temporary " +
"ignition file")
}
} else if file {
ignFile, err := os.Open(ign.Content)
if err != nil {
return "", fmt.Errorf("error opening supplied Ignition file %s", ign.Content)
}
defer ignFile.Close()
_, err = io.Copy(tempFile, ignFile)
if err != nil {
return "", fmt.Errorf("error copying supplied Igition file to temporary file: %s", ign.Content)
}
}
return tempFile.Name(), nil
}
// Creates a new defIgnition object from provided id.
func newIgnitionDefFromRemoteVol(virConn *libvirt.Libvirt, id string) (defIgnition, error) {
ign := defIgnition{}
key, err := getIgnitionVolumeKeyFromTerraformID(id)
if err != nil {
return ign, err
}
volume, err := virConn.StorageVolLookupByKey(key)
if err != nil {
return ign, fmt.Errorf("can't retrieve volume %s: %w", key, err)
}
ign.Name = volume.Name
if ign.Name == "" {
return ign, fmt.Errorf("error retrieving volume name from key: %s", key)
}
volPool, err := virConn.StoragePoolLookupByVolume(volume)
if err != nil {
return ign, fmt.Errorf("error retrieving pool for volume: %s", volume.Name)
}
ign.PoolName = volPool.Name
if ign.PoolName == "" {
return ign, fmt.Errorf("error retrieving pool name for volume: %s", volume.Name)
}
return ign, nil
}