diff --git a/base/util/file.go b/base/util/file.go index 4336ff2c..ecc12406 100644 --- a/base/util/file.go +++ b/base/util/file.go @@ -15,6 +15,7 @@ package util import ( + "os" "path/filepath" "strings" @@ -36,6 +37,19 @@ func EnsurePathWithinFilesDir(path, filesDir string) error { return nil } +func ReadLocalFile(configPath, filesDir string) ([]byte, error) { + if filesDir == "" { + // a files dir isn't configured; refuse to read anything + return nil, common.ErrNoFilesDir + } + // calculate file path within FilesDir and check for path traversal + filePath := filepath.Join(filesDir, filepath.FromSlash(configPath)) + if err := EnsurePathWithinFilesDir(filePath, filesDir); err != nil { + return nil, err + } + return os.ReadFile(filePath) +} + // CheckForDecimalMode fails if the specified mode appears to have been // incorrectly specified in decimal instead of octal. func CheckForDecimalMode(mode int, directory bool) error { diff --git a/base/v0_2/translate.go b/base/v0_2/translate.go index dbe4df5f..ded46dbc 100644 --- a/base/v0_2/translate.go +++ b/base/v0_2/translate.go @@ -118,26 +118,11 @@ func translateResource(from Resource, options common.TranslateOptions) (to types if from.Local != nil { c := path.New("yaml", "local") - - if options.FilesDir == "" { - r.AddOnError(c, common.ErrNoFilesDir) - return - } - - // calculate file path within FilesDir and check for - // path traversal - filePath := filepath.Join(options.FilesDir, filepath.FromSlash(*from.Local)) - if err := baseutil.EnsurePathWithinFilesDir(filePath, options.FilesDir); err != nil { - r.AddOnError(c, err) - return - } - - contents, err := os.ReadFile(filePath) + contents, err := baseutil.ReadLocalFile(*from.Local, options.FilesDir) if err != nil { r.AddOnError(c, err) return } - src, compression, err := baseutil.MakeDataURL(contents, to.Compression, !options.NoResourceAutoCompression) if err != nil { r.AddOnError(c, err) diff --git a/base/v0_3/translate.go b/base/v0_3/translate.go index 9d1d5c8e..a539b518 100644 --- a/base/v0_3/translate.go +++ b/base/v0_3/translate.go @@ -125,26 +125,11 @@ func translateResource(from Resource, options common.TranslateOptions) (to types if from.Local != nil { c := path.New("yaml", "local") - - if options.FilesDir == "" { - r.AddOnError(c, common.ErrNoFilesDir) - return - } - - // calculate file path within FilesDir and check for - // path traversal - filePath := filepath.Join(options.FilesDir, filepath.FromSlash(*from.Local)) - if err := baseutil.EnsurePathWithinFilesDir(filePath, options.FilesDir); err != nil { - r.AddOnError(c, err) - return - } - - contents, err := os.ReadFile(filePath) + contents, err := baseutil.ReadLocalFile(*from.Local, options.FilesDir) if err != nil { r.AddOnError(c, err) return } - src, compression, err := baseutil.MakeDataURL(contents, to.Compression, !options.NoResourceAutoCompression) if err != nil { r.AddOnError(c, err) diff --git a/base/v0_4/translate.go b/base/v0_4/translate.go index a8c8b0b6..74c0c187 100644 --- a/base/v0_4/translate.go +++ b/base/v0_4/translate.go @@ -140,26 +140,11 @@ func translateResource(from Resource, options common.TranslateOptions) (to types if from.Local != nil { c := path.New("yaml", "local") - - if options.FilesDir == "" { - r.AddOnError(c, common.ErrNoFilesDir) - return - } - - // calculate file path within FilesDir and check for - // path traversal - filePath := filepath.Join(options.FilesDir, filepath.FromSlash(*from.Local)) - if err := baseutil.EnsurePathWithinFilesDir(filePath, options.FilesDir); err != nil { - r.AddOnError(c, err) - return - } - - contents, err := os.ReadFile(filePath) + contents, err := baseutil.ReadLocalFile(*from.Local, options.FilesDir) if err != nil { r.AddOnError(c, err) return } - src, compression, err := baseutil.MakeDataURL(contents, to.Compression, !options.NoResourceAutoCompression) if err != nil { r.AddOnError(c, err) diff --git a/base/v0_5_exp/translate.go b/base/v0_5_exp/translate.go index 19c0db1e..7aa06877 100644 --- a/base/v0_5_exp/translate.go +++ b/base/v0_5_exp/translate.go @@ -143,26 +143,11 @@ func translateResource(from Resource, options common.TranslateOptions) (to types if from.Local != nil { c := path.New("yaml", "local") - - if options.FilesDir == "" { - r.AddOnError(c, common.ErrNoFilesDir) - return - } - - // calculate file path within FilesDir and check for - // path traversal - filePath := filepath.Join(options.FilesDir, filepath.FromSlash(*from.Local)) - if err := baseutil.EnsurePathWithinFilesDir(filePath, options.FilesDir); err != nil { - r.AddOnError(c, err) - return - } - - contents, err := os.ReadFile(filePath) + contents, err := baseutil.ReadLocalFile(*from.Local, options.FilesDir) if err != nil { r.AddOnError(c, err) return } - src, compression, err := baseutil.MakeDataURL(contents, to.Compression, !options.NoResourceAutoCompression) if err != nil { r.AddOnError(c, err) @@ -241,17 +226,17 @@ func translatePasswdUser(from PasswdUser, options common.TranslateOptions) (to t return } - for _, sshKeyFile := range from.SSHAuthorizedKeysLocal { - sshKeys, err := readSshKeyFile(options.FilesDir, sshKeyFile) + for keyFileIndex, sshKeyFile := range from.SSHAuthorizedKeysLocal { + sshKeys, err := baseutil.ReadLocalFile(sshKeyFile, options.FilesDir) if err != nil { - r.AddOnError(c, err) + r.AddOnError(c.Append(keyFileIndex), err) continue } - - // offset for TranslationSets when both ssh_authorized_keys and ssh_authorized_keys_local are available - offset := len(to.SSHAuthorizedKeys) - for i, line := range regexp.MustCompile("\r?\n").Split(sshKeys, -1) { - tm.AddTranslation(c, path.New("json", "sshAuthorizedKeys", i+offset)) + for _, line := range regexp.MustCompile("\r?\n").Split(string(sshKeys), -1) { + if line == "" { + continue + } + tm.AddTranslation(c.Append(keyFileIndex), path.New("json", "sshAuthorizedKeys", len(to.SSHAuthorizedKeys))) to.SSHAuthorizedKeys = append(to.SSHAuthorizedKeys, types.SSHAuthorizedKey(line)) } } @@ -260,22 +245,9 @@ func translatePasswdUser(from PasswdUser, options common.TranslateOptions) (to t return } -func readSshKeyFile(filesDir string, sshKeyFile string) (string, error) { - // calculate file path within FilesDir and check for path traversal - filePath := filepath.Join(filesDir, sshKeyFile) - if err := baseutil.EnsurePathWithinFilesDir(filePath, filesDir); err != nil { - return "", err - } - contents, err := os.ReadFile(filePath) - if err != nil { - return "", err - } - return string(contents), nil -} - func translateUnit(from Unit, options common.TranslateOptions) (to types.Unit, tm translate.TranslationSet, r report.Report) { tr := translate.NewTranslator("yaml", "json", options) - tr.AddCustomTranslator(translateUnitDropIn) + tr.AddCustomTranslator(translateDropin) tm, r = translate.Prefixed(tr, "contents", &from.Contents, &to.Contents) translate.MergeP(tr, tm, &r, "dropins", &from.Dropins, &to.Dropins) translate.MergeP(tr, tm, &r, "enabled", &from.Enabled, &to.Enabled) @@ -284,19 +256,7 @@ func translateUnit(from Unit, options common.TranslateOptions) (to types.Unit, t if util.NotEmpty(from.ContentsLocal) { c := path.New("yaml", "contents_local") - if options.FilesDir == "" { - r.AddOnError(c, common.ErrNoFilesDir) - return - } - - // calculate file path within FilesDir and check for - // path traversal - filePath := filepath.Join(options.FilesDir, *from.ContentsLocal) - if err := baseutil.EnsurePathWithinFilesDir(filePath, options.FilesDir); err != nil { - r.AddOnError(c, err) - return - } - contents, err := os.ReadFile(filePath) + contents, err := baseutil.ReadLocalFile(*from.ContentsLocal, options.FilesDir) if err != nil { r.AddOnError(c, err) return @@ -308,34 +268,20 @@ func translateUnit(from Unit, options common.TranslateOptions) (to types.Unit, t return } -func translateUnitDropIn(from Dropin, options common.TranslateOptions) (to types.Dropin, tm translate.TranslationSet, r report.Report) { +func translateDropin(from Dropin, options common.TranslateOptions) (to types.Dropin, tm translate.TranslationSet, r report.Report) { tr := translate.NewTranslator("yaml", "json", options) tm, r = translate.Prefixed(tr, "contents", &from.Contents, &to.Contents) translate.MergeP(tr, tm, &r, "name", &from.Name, &to.Name) if util.NotEmpty(from.ContentsLocal) { c := path.New("yaml", "contents_local") - tm.AddTranslation(c, path.New("json", "contents")) - - if options.FilesDir == "" { - r.AddOnError(c, common.ErrNoFilesDir) - return - } - - // calculate file path within FilesDir and check for - // path traversal - filePath := filepath.Join(options.FilesDir, *from.ContentsLocal) - if err := baseutil.EnsurePathWithinFilesDir(filePath, options.FilesDir); err != nil { - r.AddOnError(c, err) - return - } - contents, err := os.ReadFile(filePath) + contents, err := baseutil.ReadLocalFile(*from.ContentsLocal, options.FilesDir) if err != nil { r.AddOnError(c, err) return } - stringContents := string(contents) - to.Contents = util.StrToPtr(stringContents) + tm.AddTranslation(c, path.New("json", "contents")) + to.Contents = util.StrToPtr(string(contents)) } return diff --git a/base/v0_5_exp/translate_test.go b/base/v0_5_exp/translate_test.go index b3b461a1..e43c713e 100644 --- a/base/v0_5_exp/translate_test.go +++ b/base/v0_5_exp/translate_test.go @@ -1923,7 +1923,7 @@ func TestTranslateSSHAuthorizedKey(t *testing.T) { in PasswdUser out types.PasswdUser translations []translate.Translation - reportSuffix string + report string fileDir string }{ { @@ -1935,231 +1935,138 @@ func TestTranslateSSHAuthorizedKey(t *testing.T) { sshKeyDir, }, { - "valid ssh_keys_inline", + "valid inline keys", PasswdUser{SSHAuthorizedKeys: []SSHAuthorizedKey{SSHAuthorizedKey(sshKeyInline)}}, types.PasswdUser{SSHAuthorizedKeys: []types.SSHAuthorizedKey{types.SSHAuthorizedKey(sshKeyInline)}}, []translate.Translation{ - { - From: path.New("yaml", "ssh_authorized_keys"), - To: path.New("json", "sshAuthorizedKeys"), - }, - { - From: path.New("yaml", "ssh_authorized_keys", 0), - To: path.New("json", "sshAuthorizedKeys", 0), - }, + {From: path.New("yaml", "ssh_authorized_keys"), To: path.New("json", "sshAuthorizedKeys")}, + {From: path.New("yaml", "ssh_authorized_keys", 0), To: path.New("json", "sshAuthorizedKeys", 0)}, }, "", sshKeyDir, }, { - "valid ssh_keys_local", + "valid local keys", PasswdUser{SSHAuthorizedKeysLocal: []string{sshKeyFileName}}, types.PasswdUser{SSHAuthorizedKeys: []types.SSHAuthorizedKey{types.SSHAuthorizedKey(sshKey1)}}, []translate.Translation{ - { - From: path.New("yaml", "ssh_authorized_keys_local"), - To: path.New("json", "sshAuthorizedKeys"), - }, - { - From: path.New("yaml", "ssh_authorized_keys_local"), - To: path.New("json", "sshAuthorizedKeys", 0), - }, + {From: path.New("yaml", "ssh_authorized_keys_local"), To: path.New("json", "sshAuthorizedKeys")}, + {From: path.New("yaml", "ssh_authorized_keys_local", 0), To: path.New("json", "sshAuthorizedKeys", 0)}, }, "", sshKeyDir, }, { - "valid ssh_keys_local with multiple keys per file", + "valid local keys with multiple keys per file", PasswdUser{SSHAuthorizedKeysLocal: []string{sshKeyMultipleKeysFileName}}, types.PasswdUser{SSHAuthorizedKeys: []types.SSHAuthorizedKey{ types.SSHAuthorizedKey(sshKey2), types.SSHAuthorizedKey("#comment"), - types.SSHAuthorizedKey(""), - types.SSHAuthorizedKey(""), types.SSHAuthorizedKey(sshKey3), - types.SSHAuthorizedKey(""), }}, []translate.Translation{ - { - From: path.New("yaml", "ssh_authorized_keys_local"), - To: path.New("json", "sshAuthorizedKeys"), - }, - { - From: path.New("yaml", "ssh_authorized_keys_local"), - To: path.New("json", "sshAuthorizedKeys", 0), - }, - { - From: path.New("yaml", "ssh_authorized_keys_local"), - To: path.New("json", "sshAuthorizedKeys", 1), - }, - { - From: path.New("yaml", "ssh_authorized_keys_local"), - To: path.New("json", "sshAuthorizedKeys", 2), - }, - { - From: path.New("yaml", "ssh_authorized_keys_local"), - To: path.New("json", "sshAuthorizedKeys", 3), - }, - { - From: path.New("yaml", "ssh_authorized_keys_local"), - To: path.New("json", "sshAuthorizedKeys", 4), - }, - { - From: path.New("yaml", "ssh_authorized_keys_local"), - To: path.New("json", "sshAuthorizedKeys", 5), - }, + {From: path.New("yaml", "ssh_authorized_keys_local"), To: path.New("json", "sshAuthorizedKeys")}, + {From: path.New("yaml", "ssh_authorized_keys_local", 0), To: path.New("json", "sshAuthorizedKeys", 0)}, + {From: path.New("yaml", "ssh_authorized_keys_local", 0), To: path.New("json", "sshAuthorizedKeys", 1)}, + {From: path.New("yaml", "ssh_authorized_keys_local", 0), To: path.New("json", "sshAuthorizedKeys", 2)}, + }, + "", + sshKeyDir, + }, + { + "valid multiple local key files", + PasswdUser{SSHAuthorizedKeysLocal: []string{sshKeyFileName, sshKeyMultipleKeysFileName}}, + types.PasswdUser{SSHAuthorizedKeys: []types.SSHAuthorizedKey{ + types.SSHAuthorizedKey(sshKey1), + types.SSHAuthorizedKey(sshKey2), + types.SSHAuthorizedKey("#comment"), + types.SSHAuthorizedKey(sshKey3), + }}, + []translate.Translation{ + {From: path.New("yaml", "ssh_authorized_keys_local"), To: path.New("json", "sshAuthorizedKeys")}, + {From: path.New("yaml", "ssh_authorized_keys_local", 0), To: path.New("json", "sshAuthorizedKeys", 0)}, + {From: path.New("yaml", "ssh_authorized_keys_local", 1), To: path.New("json", "sshAuthorizedKeys", 1)}, + {From: path.New("yaml", "ssh_authorized_keys_local", 1), To: path.New("json", "sshAuthorizedKeys", 2)}, + {From: path.New("yaml", "ssh_authorized_keys_local", 1), To: path.New("json", "sshAuthorizedKeys", 3)}, }, "", sshKeyDir, }, { - "valid ssh_keys_local and ssh_keys", + "valid local and inline keys", PasswdUser{SSHAuthorizedKeysLocal: []string{sshKeyFileName}, SSHAuthorizedKeys: []SSHAuthorizedKey{SSHAuthorizedKey(sshKeyInline)}}, types.PasswdUser{SSHAuthorizedKeys: []types.SSHAuthorizedKey{types.SSHAuthorizedKey(sshKeyInline), types.SSHAuthorizedKey(sshKey1)}}, []translate.Translation{ - { - From: path.New("yaml", "ssh_authorized_keys_local"), - To: path.New("json", "sshAuthorizedKeys"), - }, - { - From: path.New("yaml", "ssh_authorized_keys", 0), - To: path.New("json", "sshAuthorizedKeys", 0), - }, - { - From: path.New("yaml", "ssh_authorized_keys_local"), - To: path.New("json", "sshAuthorizedKeys", 1), - }, + {From: path.New("yaml", "ssh_authorized_keys_local"), To: path.New("json", "sshAuthorizedKeys")}, + {From: path.New("yaml", "ssh_authorized_keys", 0), To: path.New("json", "sshAuthorizedKeys", 0)}, + {From: path.New("yaml", "ssh_authorized_keys_local", 0), To: path.New("json", "sshAuthorizedKeys", 1)}, }, "", sshKeyDir, }, { - "valid ssh_keys_local with multiple keys per file and ssh_keys", + "valid local keys with multiple keys per file and inline keys", PasswdUser{SSHAuthorizedKeysLocal: []string{sshKeyMultipleKeysFileName}, SSHAuthorizedKeys: []SSHAuthorizedKey{SSHAuthorizedKey(sshKeyInline)}}, types.PasswdUser{SSHAuthorizedKeys: []types.SSHAuthorizedKey{ types.SSHAuthorizedKey(sshKeyInline), types.SSHAuthorizedKey(sshKey2), types.SSHAuthorizedKey("#comment"), - types.SSHAuthorizedKey(""), - types.SSHAuthorizedKey(""), types.SSHAuthorizedKey(sshKey3), - types.SSHAuthorizedKey(""), }}, []translate.Translation{ - { - From: path.New("yaml", "ssh_authorized_keys_local"), - To: path.New("json", "sshAuthorizedKeys"), - }, - { - From: path.New("yaml", "ssh_authorized_keys", 0), - To: path.New("json", "sshAuthorizedKeys", 0), - }, - { - From: path.New("yaml", "ssh_authorized_keys_local"), - To: path.New("json", "sshAuthorizedKeys", 1), - }, - { - From: path.New("yaml", "ssh_authorized_keys_local"), - To: path.New("json", "sshAuthorizedKeys", 2), - }, - { - From: path.New("yaml", "ssh_authorized_keys_local"), - To: path.New("json", "sshAuthorizedKeys", 3), - }, - { - From: path.New("yaml", "ssh_authorized_keys_local"), - To: path.New("json", "sshAuthorizedKeys", 4), - }, - { - From: path.New("yaml", "ssh_authorized_keys_local"), - To: path.New("json", "sshAuthorizedKeys", 5), - }, - { - From: path.New("yaml", "ssh_authorized_keys_local"), - To: path.New("json", "sshAuthorizedKeys", 6), - }, + {From: path.New("yaml", "ssh_authorized_keys_local"), To: path.New("json", "sshAuthorizedKeys")}, + {From: path.New("yaml", "ssh_authorized_keys", 0), To: path.New("json", "sshAuthorizedKeys", 0)}, + {From: path.New("yaml", "ssh_authorized_keys_local", 0), To: path.New("json", "sshAuthorizedKeys", 1)}, + {From: path.New("yaml", "ssh_authorized_keys_local", 0), To: path.New("json", "sshAuthorizedKeys", 2)}, + {From: path.New("yaml", "ssh_authorized_keys_local", 0), To: path.New("json", "sshAuthorizedKeys", 3)}, }, "", sshKeyDir, }, { - "valid empty ssh_keys_local file", + "valid empty local file", PasswdUser{SSHAuthorizedKeysLocal: []string{sshKeyEmptyFileName}}, - types.PasswdUser{SSHAuthorizedKeys: []types.SSHAuthorizedKey{types.SSHAuthorizedKey("")}}, + types.PasswdUser{}, []translate.Translation{ - { - From: path.New("yaml", "ssh_authorized_keys_local"), - To: path.New("json", "sshAuthorizedKeys"), - }, - { - From: path.New("yaml", "ssh_authorized_keys_local"), - To: path.New("json", "sshAuthorizedKeys", 0), - }, + {From: path.New("yaml", "ssh_authorized_keys_local"), To: path.New("json", "sshAuthorizedKeys")}, }, "", sshKeyDir, }, { - "valid blank ssh_keys_local file", + "valid blank local file", PasswdUser{SSHAuthorizedKeysLocal: []string{sshKeyBlankFileName}}, - types.PasswdUser{SSHAuthorizedKeys: []types.SSHAuthorizedKey{types.SSHAuthorizedKey(""), types.SSHAuthorizedKey("\t")}}, + types.PasswdUser{SSHAuthorizedKeys: []types.SSHAuthorizedKey{types.SSHAuthorizedKey("\t")}}, []translate.Translation{ - { - From: path.New("yaml", "ssh_authorized_keys_local"), - To: path.New("json", "sshAuthorizedKeys"), - }, - { - From: path.New("yaml", "ssh_authorized_keys_local"), - To: path.New("json", "sshAuthorizedKeys", 0), - }, - { - From: path.New("yaml", "ssh_authorized_keys_local"), - To: path.New("json", "sshAuthorizedKeys", 1), - }, + {From: path.New("yaml", "ssh_authorized_keys_local"), To: path.New("json", "sshAuthorizedKeys")}, + {From: path.New("yaml", "ssh_authorized_keys_local", 0), To: path.New("json", "sshAuthorizedKeys", 0)}, }, "", sshKeyDir, }, { - "valid Windows style line endings in ssh_keys_local file", + "valid Windows style line endings in local file", PasswdUser{SSHAuthorizedKeysLocal: []string{sshKeyWindowsLineEndingsFileName}}, types.PasswdUser{SSHAuthorizedKeys: []types.SSHAuthorizedKey{ types.SSHAuthorizedKey(sshKey1), types.SSHAuthorizedKey("#comment"), - types.SSHAuthorizedKey(""), }}, []translate.Translation{ - { - From: path.New("yaml", "ssh_authorized_keys_local"), - To: path.New("json", "sshAuthorizedKeys"), - }, - { - From: path.New("yaml", "ssh_authorized_keys_local"), - To: path.New("json", "sshAuthorizedKeys", 0), - }, - { - From: path.New("yaml", "ssh_authorized_keys_local"), - To: path.New("json", "sshAuthorizedKeys", 1), - }, - { - From: path.New("yaml", "ssh_authorized_keys_local"), - To: path.New("json", "sshAuthorizedKeys", 2), - }, + {From: path.New("yaml", "ssh_authorized_keys_local"), To: path.New("json", "sshAuthorizedKeys")}, + {From: path.New("yaml", "ssh_authorized_keys_local", 0), To: path.New("json", "sshAuthorizedKeys", 0)}, + {From: path.New("yaml", "ssh_authorized_keys_local", 0), To: path.New("json", "sshAuthorizedKeys", 1)}, }, "", sshKeyDir, }, { - "non existing ssh_keys_local file name", + "missing local file", PasswdUser{SSHAuthorizedKeysLocal: []string{sshKeyNonExistingFileName}}, types.PasswdUser{}, []translate.Translation{ - { - From: path.New("yaml", "ssh_authorized_keys_local"), - To: path.New("json", "sshAuthorizedKeys"), - }, + {From: path.New("yaml", "ssh_authorized_keys_local"), To: path.New("json", "sshAuthorizedKeys")}, }, - osNotFound, + "error at $.ssh_authorized_keys_local.0: open " + filepath.Join(sshKeyDir, sshKeyNonExistingFileName) + ": " + osNotFound + "\n", sshKeyDir, }, { @@ -2167,12 +2074,9 @@ func TestTranslateSSHAuthorizedKey(t *testing.T) { PasswdUser{SSHAuthorizedKeysLocal: []string{sshKeyFileName}}, types.PasswdUser{}, []translate.Translation{ - { - From: path.New("yaml", "ssh_authorized_keys_local"), - To: path.New("json", "sshAuthorizedKeys"), - }, + {From: path.New("yaml", "ssh_authorized_keys_local"), To: path.New("json", "sshAuthorizedKeys")}, }, - common.ErrNoFilesDir.Error(), + "error at $.ssh_authorized_keys_local: " + common.ErrNoFilesDir.Error() + "\n", "", }, { @@ -2180,12 +2084,9 @@ func TestTranslateSSHAuthorizedKey(t *testing.T) { PasswdUser{SSHAuthorizedKeysLocal: []string{sshKeyFileName}}, types.PasswdUser{}, []translate.Translation{ - { - From: path.New("yaml", "ssh_authorized_keys_local"), - To: path.New("json", "sshAuthorizedKeys"), - }, + {From: path.New("yaml", "ssh_authorized_keys_local"), To: path.New("json", "sshAuthorizedKeys")}, }, - osNotFound, + "error at $.ssh_authorized_keys_local.0: open " + filepath.Join(randomDir, sshKeyFileName) + ": " + osNotFound + "\n", randomDir, }, } @@ -2194,11 +2095,7 @@ func TestTranslateSSHAuthorizedKey(t *testing.T) { t.Run(test.name, func(t *testing.T) { actual, translations, r := translatePasswdUser(test.in, common.TranslateOptions{FilesDir: test.fileDir}) assert.Equal(t, test.out, actual, "translation mismatch") - if len(r.Entries) > 0 { - assert.Truef(t, strings.HasSuffix(r.Entries[0].Message, test.reportSuffix), "report mismatch: expected %q but got %q", test.reportSuffix, r.Entries[0].Message) - } else { - assert.True(t, len(test.reportSuffix) == 0, "unexpected report encountered") - } + assert.Equal(t, test.report, r.String(), "bad report") baseutil.VerifyTranslations(t, translations, test.translations) assert.NoError(t, translations.DebugVerifyCoverage(actual), "incomplete TranslationSet coverage") }) @@ -2231,7 +2128,7 @@ func TestTranslateUnitLocal(t *testing.T) { in Unit out types.Unit translations []translate.Translation - reportSuffix string + report string fileDir string }{ { @@ -2255,10 +2152,7 @@ func TestTranslateUnitLocal(t *testing.T) { Unit{ContentsLocal: &unitName, Name: unitName}, types.Unit{Contents: &unitDefinitionFile, Name: unitName}, []translate.Translation{ - { - From: path.New("yaml", "contents_local"), - To: path.New("json", "contents"), - }, + {From: path.New("yaml", "contents_local"), To: path.New("json", "contents")}, }, "", unitDir, @@ -2268,7 +2162,7 @@ func TestTranslateUnitLocal(t *testing.T) { Unit{ContentsLocal: &unitNonExistingFileName, Name: unitName}, types.Unit{Name: unitName}, []translate.Translation{}, - osNotFound, + "error at $.contents_local: open " + filepath.Join(unitDir, unitNonExistingFileName) + ": " + osNotFound + "\n", unitDir, }, { @@ -2276,10 +2170,7 @@ func TestTranslateUnitLocal(t *testing.T) { Unit{ContentsLocal: &unitEmptyFileName, Name: unitName}, types.Unit{Contents: &unitEmptyDefinition, Name: unitName}, []translate.Translation{ - { - From: path.New("yaml", "contents_local"), - To: path.New("json", "contents"), - }, + {From: path.New("yaml", "contents_local"), To: path.New("json", "contents")}, }, "", unitDir, @@ -2289,7 +2180,7 @@ func TestTranslateUnitLocal(t *testing.T) { Unit{ContentsLocal: &unitName, Name: unitName}, types.Unit{Name: unitName}, []translate.Translation{}, - common.ErrNoFilesDir.Error(), + "error at $.contents_local: " + common.ErrNoFilesDir.Error() + "\n", "", }, { @@ -2297,7 +2188,7 @@ func TestTranslateUnitLocal(t *testing.T) { Unit{ContentsLocal: &unitName, Name: unitName}, types.Unit{Name: unitName}, []translate.Translation{}, - osNotFound, + "error at $.contents_local: open " + filepath.Join(randomDir, unitName) + ": " + osNotFound + "\n", randomDir, }, { @@ -2317,40 +2208,29 @@ func TestTranslateUnitLocal(t *testing.T) { "", }, { - "valid dropin_contents_local", + "valid dropin contents_local", Unit{Dropins: []Dropin{{Name: dropinName, ContentsLocal: &unitName}}, Name: unitName}, types.Unit{Dropins: []types.Dropin{{Name: dropinName, Contents: &unitDefinitionFile}}, Name: unitName}, []translate.Translation{ - { - From: path.New("yaml", "dropins", 0, "contents_local"), - To: path.New("json", "dropins", 0, "contents"), - }, + {From: path.New("yaml", "dropins", 0, "contents_local"), To: path.New("json", "dropins", 0, "contents")}, }, "", unitDir, }, { - "non existing dropin_contents_local file name", + "non existing dropin contents_local file name", Unit{Dropins: []Dropin{{Name: dropinName, ContentsLocal: &unitNonExistingFileName}}, Name: unitName}, types.Unit{Dropins: []types.Dropin{{Name: dropinName}}, Name: unitName}, - []translate.Translation{ - { - From: path.New("yaml", "dropins", 0, "contents_local"), - To: path.New("json", "dropins", 0, "contents"), - }, - }, - osNotFound, + []translate.Translation{}, + "error at $.dropins.0.contents_local: open " + filepath.Join(unitDir, unitNonExistingFileName) + ": " + osNotFound + "\n", unitDir, }, { - "valid empty dropin_contents_local file", + "valid empty dropin contents_local file", Unit{Dropins: []Dropin{{Name: dropinName, ContentsLocal: &unitEmptyFileName}}, Name: unitName}, types.Unit{Dropins: []types.Dropin{{Name: dropinName, Contents: &unitEmptyDefinition}}, Name: unitName}, []translate.Translation{ - { - From: path.New("yaml", "dropins", 0, "contents_local"), - To: path.New("json", "dropins", 0, "contents"), - }, + {From: path.New("yaml", "dropins", 0, "contents_local"), To: path.New("json", "dropins", 0, "contents")}, }, "", unitDir, @@ -2359,26 +2239,16 @@ func TestTranslateUnitLocal(t *testing.T) { "missing embed directory for dropin", Unit{Dropins: []Dropin{{Name: dropinName, ContentsLocal: &unitName}}, Name: unitName}, types.Unit{Dropins: []types.Dropin{{Name: dropinName}}, Name: unitName}, - []translate.Translation{ - { - From: path.New("yaml", "dropins", 0, "contents_local"), - To: path.New("json", "dropins", 0, "contents"), - }, - }, - common.ErrNoFilesDir.Error(), + []translate.Translation{}, + "error at $.dropins.0.contents_local: " + common.ErrNoFilesDir.Error() + "\n", "", }, { "wrong embed directory for dropin", Unit{Dropins: []Dropin{{Name: dropinName, ContentsLocal: &unitName}}, Name: unitName}, types.Unit{Dropins: []types.Dropin{{Name: dropinName}}, Name: unitName}, - []translate.Translation{ - { - From: path.New("yaml", "dropins", 0, "contents_local"), - To: path.New("json", "dropins", 0, "contents"), - }, - }, - osNotFound, + []translate.Translation{}, + "error at $.dropins.0.contents_local: open " + filepath.Join(randomDir, unitName) + ": " + osNotFound + "\n", randomDir, }, } @@ -2387,11 +2257,7 @@ func TestTranslateUnitLocal(t *testing.T) { t.Run(test.name, func(t *testing.T) { actual, translations, r := translateUnit(test.in, common.TranslateOptions{FilesDir: test.fileDir}) assert.Equal(t, test.out, actual, "translation mismatch") - if len(r.Entries) > 0 { - assert.Truef(t, strings.HasSuffix(r.Entries[0].Message, test.reportSuffix), "report mismatch: expected %q but got %q", test.reportSuffix, r.Entries[0].Message) - } else { - assert.True(t, len(test.reportSuffix) == 0, "unexpected report encountered") - } + assert.Equal(t, test.report, r.String(), "bad report") baseutil.VerifyTranslations(t, translations, test.translations) assert.NoError(t, translations.DebugVerifyCoverage(actual), "incomplete TranslationSet coverage") }) diff --git a/base/v0_5_exp/validate.go b/base/v0_5_exp/validate.go index 0b8332ac..f24db2ff 100644 --- a/base/v0_5_exp/validate.go +++ b/base/v0_5_exp/validate.go @@ -79,14 +79,14 @@ func (t Tree) Validate(c path.ContextPath) (r report.Report) { func (rs Unit) Validate(c path.ContextPath) (r report.Report) { if rs.ContentsLocal != nil && rs.Contents != nil { - r.AddOnError(c.Append("inline"), common.ErrTooManySystemdSources) + r.AddOnError(c.Append("contents_local"), common.ErrTooManySystemdSources) } return } func (rs Dropin) Validate(c path.ContextPath) (r report.Report) { if rs.ContentsLocal != nil && rs.Contents != nil { - r.AddOnError(c.Append("inline"), common.ErrTooManySystemdSources) + r.AddOnError(c.Append("contents_local"), common.ErrTooManySystemdSources) } return } diff --git a/base/v0_5_exp/validate_test.go b/base/v0_5_exp/validate_test.go index 683516fb..199a95c3 100644 --- a/base/v0_5_exp/validate_test.go +++ b/base/v0_5_exp/validate_test.go @@ -290,7 +290,7 @@ func TestValidateFilesystem(t *testing.T) { } } -// TestValidateUnit tests that multiple sources (i.e. local and inline) are not allowed but zero or one sources are +// TestValidateUnit tests that multiple sources (i.e. contents and contents_local) are not allowed but zero or one sources are func TestValidateUnit(t *testing.T) { tests := []struct { in Unit @@ -298,7 +298,7 @@ func TestValidateUnit(t *testing.T) { errPath path.ContextPath }{ {}, - // inline specified + // contents specified { Unit{ Contents: util.StrToPtr("hello"), @@ -306,7 +306,7 @@ func TestValidateUnit(t *testing.T) { nil, path.New("yaml"), }, - // local specified + // contents_local specified { Unit{ ContentsLocal: util.StrToPtr("hello"), @@ -314,14 +314,14 @@ func TestValidateUnit(t *testing.T) { nil, path.New("yaml"), }, - // inline + local, invalid + // contents + contents_local, invalid { Unit{ Contents: util.StrToPtr("hello"), ContentsLocal: util.StrToPtr("hello, too"), }, common.ErrTooManySystemdSources, - path.New("yaml", "inline"), + path.New("yaml", "contents_local"), }, } @@ -335,7 +335,7 @@ func TestValidateUnit(t *testing.T) { } } -// TestValidateDropin tests that multiple sources (i.e. local and inline) are not allowed but zero or one sources are +// TestValidateDropin tests that multiple sources (i.e. contents and contents_local) are not allowed but zero or one sources are func TestValidateDropin(t *testing.T) { tests := []struct { in Dropin @@ -343,7 +343,7 @@ func TestValidateDropin(t *testing.T) { errPath path.ContextPath }{ {}, - // inline specified + // contents specified { Dropin{ Contents: util.StrToPtr("hello"), @@ -351,7 +351,7 @@ func TestValidateDropin(t *testing.T) { nil, path.New("yaml"), }, - // local specified + // contents_local specified { Dropin{ ContentsLocal: util.StrToPtr("hello"), @@ -359,14 +359,14 @@ func TestValidateDropin(t *testing.T) { nil, path.New("yaml"), }, - // inline + local, invalid + // contents + contents_local, invalid { Dropin{ Contents: util.StrToPtr("hello"), ContentsLocal: util.StrToPtr("hello, too"), }, common.ErrTooManySystemdSources, - path.New("yaml", "inline"), + path.New("yaml", "contents_local"), }, } diff --git a/config/common/errors.go b/config/common/errors.go index 704e7e70..bf1e3c2c 100644 --- a/config/common/errors.go +++ b/config/common/errors.go @@ -32,7 +32,6 @@ var ( // resources and trees ErrTooManyResourceSources = errors.New("only one of the following can be set: inline, local, source") - ErrTooManySystemdSources = errors.New("only one of the following can be set: inline, local") ErrFilesDirEscape = errors.New("local file path traverses outside the files directory") ErrFileType = errors.New("trees may only contain files, directories, and symlinks") ErrNodeExists = errors.New("matching filesystem node has existing contents or different type") @@ -43,6 +42,9 @@ var ( // filesystem nodes ErrDecimalMode = errors.New("unreasonable mode would be reasonable if specified in octal; remember to add a leading zero") + // systemd + ErrTooManySystemdSources = errors.New("only one of the following can be set: contents, contents_local") + // mount units ErrMountUnitNoPath = errors.New("path is required if with_mount_unit is true and format is not swap") ErrMountUnitNoFormat = errors.New("format is required if with_mount_unit is true") diff --git a/docs/config-r4e-v1_1-exp.md b/docs/config-r4e-v1_1-exp.md index fd2d4206..d64e4228 100644 --- a/docs/config-r4e-v1_1-exp.md +++ b/docs/config-r4e-v1_1-exp.md @@ -113,10 +113,12 @@ The RHEL for Edge configuration is a YAML document conforming to the following s * **name** (string): the name of the unit. This must be suffixed with a valid unit type (e.g. "thing.service"). * **_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. * **_mask_** (boolean): whether or not the service shall be masked. When true, the service is masked by symlinking it to `/dev/null`. - * **_contents_** (string): the contents of the unit. + * **_contents_** (string): the contents of the unit. Mutually exclusive with `contents_local`. + * **_contents_local_** (string): a local path to the contents of the unit, relative to the directory specified by the `--files-dir` command-line argument. Mutually exclusive with `contents`. * **_dropins_** (list of objects): the list of drop-ins for the unit. Every drop-in must have a unique `name`. * **name** (string): the name of the drop-in. This must be suffixed with ".conf". - * **_contents_** (string): the contents of the drop-in. + * **_contents_** (string): the contents of the drop-in. Mutually exclusive with `contents_local`. + * **_contents_local_** (string): a local path to the contents of the drop-in, relative to the directory specified by the `--files-dir` command-line argument. Mutually exclusive with `contents`. * **_passwd_** (object): describes the desired additions to the passwd database. * **_users_** (list of objects): the list of accounts that shall exist. All users must have a unique `name`. * **name** (string): the username for the account. diff --git a/docs/release-notes.md b/docs/release-notes.md index 7fdac43c..675bea10 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -21,7 +21,8 @@ nav_order: 9 flatcar 1.1.0-exp, openshift 4.14.0-exp)_ - Allow specifying user password hash _(openshift 4.13.0+)_ - Support offline Tang provisioning via pre-shared advertisement _(fcos 1.5.0-exp, openshift 4.14.0-exp)_ -- Support local file embedding for SSH keys and systemd units +- Support local file embedding for SSH keys and systemd units _(fcos 1.5.0-exp, + flatcar 1.1.0-exp, openshift 4.14.0-exp, r4e 1.1.0-exp)_ ### Bug fixes