diff --git a/docs/website/content/v0.3/en/configuration/v1alpha1.md b/docs/website/content/v0.3/en/configuration/v1alpha1.md index 36f003893..836e14b23 100644 --- a/docs/website/content/v0.3/en/configuration/v1alpha1.md +++ b/docs/website/content/v0.3/en/configuration/v1alpha1.md @@ -242,6 +242,10 @@ install: #### files Allows the addition of user specified files. +The value of `op` can be `create`, `overwrite`, or `append`. +In the case of `create`, `path` must not exist. +In the case of `overwrite`, and `append`, `path` must be a valid file. +If an `op` value of `append` is used, the existing file will be appended. Note that the file contents are not required to be base64 encoded. Type: `array` @@ -249,11 +253,12 @@ Type: `array` Examples: ```yaml -kubelet: - contents: | - ... - permissions: 0666 - path: /tmp/file.txt +files: + - content: | + ... + permissions: 0666 + path: /tmp/file.txt + op: append ``` diff --git a/docs/website/content/v0.3/en/customization/proxy.md b/docs/website/content/v0.3/en/customization/proxy.md index 439496017..1fa6618d6 100644 --- a/docs/website/content/v0.3/en/customization/proxy.md +++ b/docs/website/content/v0.3/en/customization/proxy.md @@ -10,7 +10,7 @@ Put into each machine the PEM encoded certificate: machine: ... files: - - contents: | + - content: | -----BEGIN CERTIFICATE----- ... -----END CERTIFICATE----- diff --git a/internal/app/machined/internal/phase/config/extra_files.go b/internal/app/machined/internal/phase/config/extra_files.go index b9a8c3247..2ae8cadf0 100644 --- a/internal/app/machined/internal/phase/config/extra_files.go +++ b/internal/app/machined/internal/phase/config/extra_files.go @@ -31,12 +31,30 @@ func (task *ExtraFiles) TaskFunc(mode runtime.Mode) phase.TaskFunc { return task.runtime } +// nolint: gocyclo func (task *ExtraFiles) runtime(r runtime.Runtime) (err error) { var result *multierror.Error for _, f := range r.Config().Machine().Files() { - // Slurp existing file if append is our op and add contents to it - if f.Op == "append" { + content := f.Content + + switch f.Op { + case "create": + if err = doesNotExists(f.Path); err != nil { + result = multierror.Append(result, fmt.Errorf("file must not exist: %q", f.Path)) + continue + } + case "overwrite": + if err = existsAndIsFile(f.Path); err != nil { + result = multierror.Append(result, err) + continue + } + case "append": + if err = existsAndIsFile(f.Path); err != nil { + result = multierror.Append(result, err) + continue + } + var existingFileContents []byte existingFileContents, err = ioutil.ReadFile(f.Path) @@ -45,7 +63,10 @@ func (task *ExtraFiles) runtime(r runtime.Runtime) (err error) { continue } - f.Contents = string(existingFileContents) + "\n" + f.Contents + content = string(existingFileContents) + "\n" + f.Content + default: + result = multierror.Append(result, fmt.Errorf("unknown operation for file %q: %q", f.Path, f.Op)) + continue } // Determine if supplied path is in /var or not. @@ -62,12 +83,18 @@ func (task *ExtraFiles) runtime(r runtime.Runtime) (err error) { inVar = false } + // We do not want to support creating new files anywhere outside of + // /var. If a valid use case comes up, we can reconsider then. + if !inVar && f.Op == "create" { + return fmt.Errorf("create operation not allowed outside of /var: %q", f.Path) + } + if err = os.MkdirAll(filepath.Dir(p), os.ModeDir); err != nil { result = multierror.Append(result, err) continue } - if err = ioutil.WriteFile(p, []byte(f.Contents), f.Permissions); err != nil { + if err = ioutil.WriteFile(p, []byte(content), f.Permissions); err != nil { result = multierror.Append(result, err) continue } @@ -82,3 +109,35 @@ func (task *ExtraFiles) runtime(r runtime.Runtime) (err error) { return result.ErrorOrNil() } + +func doesNotExists(p string) (err error) { + _, err = os.Stat(p) + if err != nil { + if os.IsNotExist(err) { + return nil + } + + return err + } + + return fmt.Errorf("file exists") +} + +func existsAndIsFile(p string) (err error) { + var info os.FileInfo + + info, err = os.Stat(p) + if err != nil { + if !os.IsNotExist(err) { + return err + } + + return fmt.Errorf("file must exist: %q", p) + } + + if !info.Mode().IsRegular() { + return fmt.Errorf("invalid mode: %q", info.Mode().String()) + } + + return nil +} diff --git a/pkg/config/machine/machine.go b/pkg/config/machine/machine.go index 377e87059..07c5e02bf 100644 --- a/pkg/config/machine/machine.go +++ b/pkg/config/machine/machine.go @@ -44,7 +44,7 @@ type Env = map[string]string // File represents a file to write to disk. type File struct { - Contents string `yaml:"contents"` + Content string `yaml:"content"` Permissions os.FileMode `yaml:"permissions"` Path string `yaml:"path"` Op string `yaml:"op"` diff --git a/pkg/config/types/v1alpha1/v1alpha1_types.go b/pkg/config/types/v1alpha1/v1alpha1_types.go index 0726f05d9..85ed28371 100644 --- a/pkg/config/types/v1alpha1/v1alpha1_types.go +++ b/pkg/config/types/v1alpha1/v1alpha1_types.go @@ -125,14 +125,19 @@ type MachineConfig struct { MachineInstall *InstallConfig `yaml:"install,omitempty"` // description: | // Allows the addition of user specified files. + // The value of `op` can be `create`, `overwrite`, or `append`. + // In the case of `create`, `path` must not exist. + // In the case of `overwrite`, and `append`, `path` must be a valid file. + // If an `op` value of `append` is used, the existing file will be appended. // Note that the file contents are not required to be base64 encoded. // examples: // - | - // kubelet: - // contents: | - // ... - // permissions: 0666 - // path: /tmp/file.txt + // files: + // - content: | + // ... + // permissions: 0666 + // path: /tmp/file.txt + // op: append MachineFiles []machine.File `yaml:"files,omitempty"` // Note: The specified `path` is relative to `/var`. // description: | // The `env` field allows for the addition of environment variables to a machine.