Skip to content

Commit 78d5de0

Browse files
committed
*: switch systemd & dropin Contents to Resource type
Switching the Contents field to the Resource type allows additional flexibility when specifying unit contents.
1 parent 268338f commit 78d5de0

File tree

6 files changed

+68
-56
lines changed

6 files changed

+68
-56
lines changed

config/v3_2_experimental/schema/ignition.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -421,7 +421,7 @@
421421
"type": ["boolean", "null"]
422422
},
423423
"contents": {
424-
"type": ["string", "null"]
424+
"$ref": "#/definitions/resource"
425425
},
426426
"dropins": {
427427
"type": "array",
@@ -441,7 +441,7 @@
441441
"type": "string"
442442
},
443443
"contents": {
444-
"type": ["string", "null"]
444+
"$ref": "#/definitions/resource"
445445
}
446446
},
447447
"required": [

config/v3_2_experimental/types/schema.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ type Disk struct {
2727
}
2828

2929
type Dropin struct {
30-
Contents *string `json:"contents,omitempty"`
31-
Name string `json:"name"`
30+
Contents Resource `json:"contents,omitempty"`
31+
Name string `json:"name"`
3232
}
3333

3434
type File struct {
@@ -199,7 +199,7 @@ type Timeouts struct {
199199
}
200200

201201
type Unit struct {
202-
Contents *string `json:"contents,omitempty"`
202+
Contents Resource `json:"contents,omitempty"`
203203
Dropins []Dropin `json:"dropins,omitempty"`
204204
Enabled *bool `json:"enabled,omitempty"`
205205
Mask *bool `json:"mask,omitempty"`

config/v3_2_experimental/types/unit.go

+2-23
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,10 @@
1515
package types
1616

1717
import (
18-
"fmt"
1918
"path"
20-
"strings"
2119

2220
"github.com/coreos/ignition/v2/config/shared/errors"
23-
"github.com/coreos/ignition/v2/config/shared/validations"
2421

25-
"github.com/coreos/go-systemd/v22/unit"
2622
cpath "github.com/coreos/vcontext/path"
2723
"github.com/coreos/vcontext/report"
2824
)
@@ -36,14 +32,12 @@ func (d Dropin) Key() string {
3632
}
3733

3834
func (u Unit) Validate(c cpath.ContextPath) (r report.Report) {
35+
var err error
3936
r.AddOnError(c.Append("name"), validateName(u.Name))
37+
r.Merge(u.Contents.Validate(c))
4038
c = c.Append("contents")
41-
opts, err := validateUnitContent(u.Contents)
4239
r.AddOnError(c, err)
4340

44-
isEnabled := u.Enabled != nil && *u.Enabled
45-
r.AddOnWarn(c, validations.ValidateInstallSection(u.Name, isEnabled, (u.Contents == nil || *u.Contents == ""), opts))
46-
4741
return
4842
}
4943

@@ -57,9 +51,6 @@ func validateName(name string) error {
5751
}
5852

5953
func (d Dropin) Validate(c cpath.ContextPath) (r report.Report) {
60-
_, err := validateUnitContent(d.Contents)
61-
r.AddOnError(c.Append("contents"), err)
62-
6354
switch path.Ext(d.Name) {
6455
case ".conf":
6556
default:
@@ -68,15 +59,3 @@ func (d Dropin) Validate(c cpath.ContextPath) (r report.Report) {
6859

6960
return
7061
}
71-
72-
func validateUnitContent(content *string) ([]*unit.UnitOption, error) {
73-
if content == nil {
74-
return []*unit.UnitOption{}, nil
75-
}
76-
c := strings.NewReader(*content)
77-
opts, err := unit.Deserialize(c)
78-
if err != nil {
79-
return nil, fmt.Errorf("invalid unit content: %s", err)
80-
}
81-
return opts, nil
82-
}

doc/configuration-v3_2_experimental.md

+14
Original file line numberDiff line numberDiff line change
@@ -121,9 +121,23 @@ The Ignition configuration is a JSON document conforming to the following specif
121121
* **_enabled_** (boolean): whether or not the service shall be enabled. When true, the service is enabled. When false, the service is disabled. When omitted, the service is unmodified. In order for this to have any effect, the unit must have an install section.
122122
* **_mask_** (boolean): whether or not the service shall be masked. When true, the service is masked by symlinking it to `/dev/null`.
123123
* **_contents_** (string): the contents of the unit.
124+
* **_compression_** (string): the type of compression used on the contents (null or gzip). Compression cannot be used with S3.
125+
* **_source_** (string): the URL of the file contents. Supported schemes are `http`, `https`, `tftp`, `s3`, and [`data`][rfc2397]. When using `http`, it is advisable to use the verification option to ensure the contents haven't been modified. If source is omitted and a regular file already exists at the path, Ignition will do nothing. If source is omitted and no file exists, an empty file will be created.
126+
* **_httpHeaders_** (list of objects): a list of HTTP headers to be added to the request. Available for `http` and `https` source schemes only.
127+
* **name** (string): the header name.
128+
* **_value_** (string): the header contents.
129+
* **_verification_** (object): options related to the verification of the file contents.
130+
* **_hash_** (string): the hash of the contents, in the form `<type>-<value>` where type is either `sha512` or `sha256`.
124131
* **_dropins_** (list of objects): the list of drop-ins for the unit. Every drop-in must have a unique `name`.
125132
* **name** (string): the name of the drop-in. This must be suffixed with ".conf".
126133
* **_contents_** (string): the contents of the drop-in.
134+
* **_compression_** (string): the type of compression used on the contents (null or gzip). Compression cannot be used with S3.
135+
* **_source_** (string): the URL of the file contents. Supported schemes are `http`, `https`, `tftp`, `s3`, and [`data`][rfc2397]. When using `http`, it is advisable to use the verification option to ensure the contents haven't been modified. If source is omitted and a regular file already exists at the path, Ignition will do nothing. If source is omitted and no file exists, an empty file will be created.
136+
* **_httpHeaders_** (list of objects): a list of HTTP headers to be added to the request. Available for `http` and `https` source schemes only.
137+
* **name** (string): the header name.
138+
* **_value_** (string): the header contents.
139+
* **_verification_** (object): options related to the verification of the file contents.
140+
* **_hash_** (string): the hash of the contents, in the form `<type>-<value>` where type is either `sha512` or `sha256`.
127141
* **_passwd_** (object): describes the desired additions to the passwd database.
128142
* **_users_** (list of objects): the list of accounts that shall exist. All users must have a unique `name`.
129143
* **name** (string): the username for the account.

internal/exec/stages/files/units.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121

2222
"github.com/coreos/ignition/v2/config/shared/errors"
2323
"github.com/coreos/ignition/v2/config/v3_2_experimental/types"
24+
cUtil "github.com/coreos/ignition/v2/config/util"
2425
"github.com/coreos/ignition/v2/internal/distro"
2526
"github.com/coreos/ignition/v2/internal/exec/util"
2627
"github.com/coreos/ignition/v2/internal/systemd"
@@ -183,7 +184,7 @@ func (s *stage) writeSystemdUnit(unit types.Unit, runtime bool) error {
183184
return s.Logger.LogOp(func() error {
184185
relabeledDropinDir := false
185186
for _, dropin := range unit.Dropins {
186-
if dropin.Contents == nil || *dropin.Contents == "" {
187+
if cUtil.NilOrEmpty(dropin.Contents.Source) {
187188
continue
188189
}
189190
f, err := u.FileFromSystemdUnitDropin(unit, dropin, runtime)
@@ -211,7 +212,7 @@ func (s *stage) writeSystemdUnit(unit types.Unit, runtime bool) error {
211212
}
212213
}
213214

214-
if unit.Contents == nil || *unit.Contents == "" {
215+
if cUtil.NilOrEmpty(unit.Contents.Source) {
215216
return nil
216217
}
217218

internal/exec/util/unit.go

+44-26
Original file line numberDiff line numberDiff line change
@@ -15,62 +15,85 @@
1515
package util
1616

1717
import (
18+
"encoding/hex"
1819
"fmt"
1920
"net/url"
2021
"os"
2122
"path/filepath"
2223

2324
"github.com/coreos/ignition/v2/config/v3_2_experimental/types"
2425
"github.com/coreos/ignition/v2/internal/distro"
25-
26-
"github.com/vincent-petithory/dataurl"
26+
"github.com/coreos/ignition/v2/internal/resource"
27+
"github.com/coreos/ignition/v2/internal/util"
2728
)
2829

2930
const (
3031
PresetPath string = "/etc/systemd/system-preset/20-ignition.preset"
3132
DefaultPresetPermissions os.FileMode = 0644
3233
)
3334

34-
func (ut Util) FileFromSystemdUnit(unit types.Unit, runtime bool) (FetchOp, error) {
35-
if unit.Contents == nil {
36-
empty := ""
37-
unit.Contents = &empty
35+
func (ut Util) getUnitFetch(path string, contents types.Resource) (FetchOp, error) {
36+
u, err := url.Parse(*contents.Source)
37+
if err != nil {
38+
ut.Logger.Crit("Unable to parse systemd contents URL: %s", err)
39+
return FetchOp{}, err
3840
}
39-
u, err := url.Parse(dataurl.EncodeBytes([]byte(*unit.Contents)))
41+
hasher, err := util.GetHasher(contents.Verification)
4042
if err != nil {
43+
ut.Logger.Crit("Unable to get hasher: %s", err)
4144
return FetchOp{}, err
4245
}
4346

44-
var path string
45-
if runtime {
46-
path = SystemdRuntimeUnitsPath()
47-
} else {
48-
path = SystemdUnitsPath()
47+
var expectedSum []byte
48+
if hasher != nil {
49+
// explicitly ignoring the error here because the config should already
50+
// be validated by this point
51+
_, expectedSumString, _ := util.HashParts(contents.Verification)
52+
expectedSum, err = hex.DecodeString(expectedSumString)
53+
if err != nil {
54+
ut.Logger.Crit("Error parsing verification string %q: %v", expectedSumString, err)
55+
return FetchOp{}, err
56+
}
4957
}
5058

51-
if path, err = ut.JoinPath(path, unit.Name); err != nil {
52-
return FetchOp{}, err
59+
var compression string
60+
if contents.Compression != nil {
61+
compression = *contents.Compression
5362
}
5463

5564
return FetchOp{
65+
Hash: hasher,
5666
Node: types.Node{
5767
Path: path,
5868
},
5969
Url: *u,
70+
FetchOptions: resource.FetchOptions{
71+
Hash: hasher,
72+
ExpectedSum: expectedSum,
73+
Compression: compression,
74+
},
6075
}, nil
6176
}
6277

63-
func (ut Util) FileFromSystemdUnitDropin(unit types.Unit, dropin types.Dropin, runtime bool) (FetchOp, error) {
64-
if dropin.Contents == nil {
65-
empty := ""
66-
dropin.Contents = &empty
78+
func (ut Util) FileFromSystemdUnit(unit types.Unit, runtime bool) (FetchOp, error) {
79+
var path string
80+
var err error
81+
if runtime {
82+
path = SystemdRuntimeUnitsPath()
83+
} else {
84+
path = SystemdUnitsPath()
6785
}
68-
u, err := url.Parse(dataurl.EncodeBytes([]byte(*dropin.Contents)))
69-
if err != nil {
86+
87+
if path, err = ut.JoinPath(path, unit.Name); err != nil {
7088
return FetchOp{}, err
7189
}
7290

91+
return ut.getUnitFetch(path, unit.Contents)
92+
}
93+
94+
func (ut Util) FileFromSystemdUnitDropin(unit types.Unit, dropin types.Dropin, runtime bool) (FetchOp, error) {
7395
var path string
96+
var err error
7497
if runtime {
7598
path = SystemdRuntimeDropinsPath(string(unit.Name))
7699
} else {
@@ -81,12 +104,7 @@ func (ut Util) FileFromSystemdUnitDropin(unit types.Unit, dropin types.Dropin, r
81104
return FetchOp{}, err
82105
}
83106

84-
return FetchOp{
85-
Node: types.Node{
86-
Path: path,
87-
},
88-
Url: *u,
89-
}, nil
107+
return ut.getUnitFetch(path, unit.Contents)
90108
}
91109

92110
// MaskUnit writes a symlink to /dev/null to mask the specified unit and returns the path of that unit

0 commit comments

Comments
 (0)