From fb47eee2fb340317c522841ae265a95602b8a35c Mon Sep 17 00:00:00 2001 From: John Akre Date: Thu, 23 Jan 2020 08:54:30 -0800 Subject: [PATCH] Add content keyword to bundle definition file The content keyword in the bundle definition file specifies the path to a chroot whose file contents will be added to the bundle's manifest. Ex: content(/path) Signed-off-by: John Akre --- builder/bundle_control.go | 6 +- builder/bundleset.go | 21 ++++++- builder/bundleset_test.go | 118 ++++++++++++++++++++++++++++++++++---- 3 files changed, 132 insertions(+), 13 deletions(-) diff --git a/builder/bundle_control.go b/builder/bundle_control.go index c79008a..8ba25b7 100644 --- a/builder/bundle_control.go +++ b/builder/bundle_control.go @@ -748,8 +748,10 @@ const bundleTemplateFormat = `# [TITLE]: %s # [CAPABILITIES]: # [MAINTAINER]: # -# List bundles one per line. Includes have format: include(bundle) -# also-adds have format: also-add(bundle) +# List packages one per line. +# includes have format: include(bundle) +# also-adds have format: also-add(bundle) +# content chroots have format: content(path) ` func createBundleFile(bundle string, path string) error { diff --git a/builder/bundleset.go b/builder/bundleset.go index 332f2f3..0aae29e 100644 --- a/builder/bundleset.go +++ b/builder/bundleset.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "io/ioutil" + "os" "path/filepath" "regexp" "strings" @@ -31,7 +32,8 @@ type bundle struct { Files map[string]bool /* hidden property, not to be included in file usr/share/clear/allbundles */ - AllRpms map[string]packageMetadata `json:"-"` + AllRpms map[string]packageMetadata `json:"-"` + ContentChroots map[string]bool `json:"-"` } type bundleSet map[string]*bundle @@ -308,6 +310,8 @@ func parseBundle(contents []byte) (*bundle, error) { var duplicate bool var includes, packages, optional []string + b.ContentChroots = make(map[string]bool) + line := 0 for scanner.Scan() { line++ @@ -355,6 +359,21 @@ func parseBundle(contents []byte) (*bundle, error) { return nil, fmt.Errorf("Invalid bundle name %q in line %d", text, line) } optional = append(optional, text) + } else if strings.HasPrefix(text, "content(") { + if !strings.HasSuffix(text, ")") { + return nil, fmt.Errorf("Missing end parenthesis in line %d: %q", line, text) + } + text = text[8 : len(text)-1] + + // Remove unnecessary trailing slashes + for len(text) > 1 && strings.HasSuffix(text, "/") { + text = strings.TrimSuffix(text, "/") + } + + if _, err := os.Stat(text); err != nil { + return nil, fmt.Errorf("Invalid content path %q in line %d", text, line) + } + b.ContentChroots[text] = true } else { if !validPackageNameRegex.MatchString(text) { return nil, fmt.Errorf("Invalid package name %q in line %d", text, line) diff --git a/builder/bundleset_test.go b/builder/bundleset_test.go index 15cc928..ac7dccf 100644 --- a/builder/bundleset_test.go +++ b/builder/bundleset_test.go @@ -13,16 +13,29 @@ import ( ) func TestParseBundle(t *testing.T) { + dir1, err := ioutil.TempDir("", "") + if err != nil { + t.Fatalf("could not create valid testdir") + } + defer func() { _ = os.RemoveAll(dir1) }() + + dir2, err := ioutil.TempDir("", "") + if err != nil { + t.Fatalf("could not create valid testdir") + } + defer func() { _ = os.RemoveAll(dir2) }() + tests := []struct { Contents []byte ExpectedHeader swupd.BundleHeader ExpectedIncludes []string ExpectedOptional []string ExpectedPackages map[string]bool + ExpectedChroots map[string]bool ShouldFail bool }{ { - Contents: []byte(`# Simple fake bundle + Contents: []byte(fmt.Sprintf(`# Simple fake bundle # [TITLE]: fake # [DESCRIPTION]: a description # [STATUS]: a status @@ -32,9 +45,11 @@ include(a) include(b) also-add(c) also-add(d) +content(%s) +content(%s) pkg1 # Comment pkg2 -`), +`, dir1, dir2)), ExpectedHeader: swupd.BundleHeader{ Title: "fake", Description: "a description", @@ -45,9 +60,10 @@ pkg2 ExpectedIncludes: []string{"a", "b"}, ExpectedOptional: []string{"c", "d"}, ExpectedPackages: map[string]bool{"pkg1": true, "pkg2": true}, + ExpectedChroots: map[string]bool{dir1: true, dir2: true}, }, { - Contents: []byte(`# Bundle with empty header values + Contents: []byte(fmt.Sprintf(`# Bundle with empty header values # [TITLE]: fake # [DESCRIPTION]: a description # [STATUS]: @@ -55,8 +71,9 @@ pkg2 # [MAINTAINER]: include(a) also-add(b) +content(%s) pkg1 -`), +`, dir1)), ExpectedHeader: swupd.BundleHeader{ Title: "fake", Description: "a description", @@ -64,6 +81,7 @@ pkg1 ExpectedIncludes: []string{"a"}, ExpectedOptional: []string{"b"}, ExpectedPackages: map[string]bool{"pkg1": true}, + ExpectedChroots: map[string]bool{dir1: true}, }, { Contents: []byte(`# Bundle with tricky comments @@ -81,6 +99,28 @@ pkg1 # [TITLE]: wrongtitle }, ExpectedIncludes: []string{"a"}, ExpectedPackages: map[string]bool{"pkg1": true}, + ExpectedChroots: map[string]bool{}, + }, + { + Contents: []byte(fmt.Sprintf(`# Duplicate content chroots and trailing /s +# [TITLE]: fake +# [DESCRIPTION]: a description +# [STATUS]: a status +# [CAPABILITIES]: the capabilities +# [MAINTAINER]: the maintainer +content(%s/) +content(%s) +content(%s///) +`, dir1, dir1, dir2)), + ExpectedHeader: swupd.BundleHeader{ + Title: "fake", + Description: "a description", + Status: "a status", + Capabilities: "the capabilities", + Maintainer: "the maintainer", + }, + ExpectedPackages: map[string]bool{}, + ExpectedChroots: map[string]bool{dir1: true, dir2: true}, }, // Error cases. @@ -93,6 +133,11 @@ pkg1 # [TITLE]: wrongtitle {Contents: []byte(`Also-add(`), ShouldFail: true}, {Contents: []byte(`also-add())`), ShouldFail: true}, {Contents: []byte(`also-add(abc))`), ShouldFail: true}, + {Contents: []byte(`content(`), ShouldFail: true}, + {Contents: []byte(`Content(`), ShouldFail: true}, + {Contents: []byte(`content())`), ShouldFail: true}, + {Contents: []byte(fmt.Sprintf(`content(%s))`, dir1)), ShouldFail: true}, + {Contents: []byte(fmt.Sprintf(`content(%s/invalidPath)`, dir1)), ShouldFail: true}, } for _, tt := range tests { @@ -125,31 +170,51 @@ pkg1 # [TITLE]: wrongtitle if !reflect.DeepEqual(b.DirectPackages, tt.ExpectedPackages) { t.Errorf("got wrong packages when parsing bundle\nCONTENTS:\n%s\nPARSED PACKAGES (%d):\n%v\nEXPECTED PACKAGES (%d):\n%v", tt.Contents, len(b.DirectPackages), b.DirectPackages, len(tt.ExpectedPackages), tt.ExpectedPackages) } + + if !reflect.DeepEqual(b.ContentChroots, tt.ExpectedChroots) { + t.Errorf("got wrong content chroot when parsing bundle\nCONTENTS:\n%s\nPARSED CHROOTS (%d):\n%v\nEXPECTED CHROOTS (%d):\n%v", tt.Contents, len(b.ContentChroots), b.ContentChroots, len(tt.ExpectedChroots), tt.ExpectedChroots) + } } } func TestParseBundleFile(t *testing.T) { + dir1, err := ioutil.TempDir("", "") + if err != nil { + t.Fatalf("could not create valid testdir") + } + defer func() { _ = os.RemoveAll(dir1) }() + + dir2, err := ioutil.TempDir("", "") + if err != nil { + t.Fatalf("could not create valid testdir") + } + defer func() { _ = os.RemoveAll(dir2) }() + tests := []struct { Filename string Contents []byte ExpectedIncludes []string ExpectedOptional []string ExpectedPackages map[string]bool + ExpectedChroots map[string]bool ShouldFail bool }{ { Filename: "simple-bundle", - Contents: []byte(`# Simple fake bundle + Contents: []byte(fmt.Sprintf(`# Simple fake bundle include(a) include(b) also-add(c) also-add(d) +content(%s) +content(%s) pkg1 # Comment pkg2 -`), +`, dir1, dir2)), ExpectedIncludes: []string{"a", "b"}, ExpectedOptional: []string{"c", "d"}, ExpectedPackages: map[string]bool{"pkg1": true, "pkg2": true}, + ExpectedChroots: map[string]bool{dir1: true, dir2: true}, }, // Bundle contents error (catching parseBundle's error) @@ -196,17 +261,33 @@ pkg2 if !reflect.DeepEqual(bundle.DirectPackages, tt.ExpectedPackages) { t.Errorf("got wrong packages when parsing bundle\nCONTENTS:\n%s\nPARSED PACKAGES (%d):\n%v\nEXPECTED PACKAGES (%d):\n%v", tt.Contents, len(bundle.DirectPackages), bundle.DirectPackages, len(tt.ExpectedPackages), tt.ExpectedPackages) } + + if !reflect.DeepEqual(bundle.ContentChroots, tt.ExpectedChroots) { + t.Errorf("got wrong content chroot when parsing bundle\nCONTENTS:\n%s\nPARSED CHROOTS (%d):\n%v\nEXPECTED CHROOTS (%d):\n%v", tt.Contents, len(bundle.ContentChroots), bundle.ContentChroots, len(tt.ExpectedChroots), tt.ExpectedChroots) + } } } func TestValidateBundle(t *testing.T) { + dir1, err := ioutil.TempDir("", "") + if err != nil { + t.Fatalf("could not create valid testdir") + } + defer func() { _ = os.RemoveAll(dir1) }() + + dir2, err := ioutil.TempDir("", "") + if err != nil { + t.Fatalf("could not create valid testdir") + } + defer func() { _ = os.RemoveAll(dir2) }() + tests := []struct { Contents []byte ExpectedErrors []string ShouldFail bool }{ { - Contents: []byte(`# Simple fake bundle + Contents: []byte(fmt.Sprintf(`# Simple fake bundle # [TITLE]: fake # [DESCRIPTION]: a description # [STATUS]: a status @@ -216,9 +297,11 @@ include(a) include(b) also-add(c) also-add(d) +content(%s) +content(%s) pkg1 # Comment pkg2 -`), +`, dir1, dir2)), }, // Bundle header errors @@ -265,6 +348,18 @@ pkg2 } func TestValidateBundleFile(t *testing.T) { + dir1, err := ioutil.TempDir("", "") + if err != nil { + t.Fatalf("could not create valid testdir") + } + defer func() { _ = os.RemoveAll(dir1) }() + + dir2, err := ioutil.TempDir("", "") + if err != nil { + t.Fatalf("could not create valid testdir") + } + defer func() { _ = os.RemoveAll(dir2) }() + tests := []struct { Filename string Contents []byte @@ -274,7 +369,7 @@ func TestValidateBundleFile(t *testing.T) { }{ { Filename: "simple-bundle", - Contents: []byte(`# Simple fake bundle + Contents: []byte(fmt.Sprintf(`# Simple fake bundle # [TITLE]: simple-bundle # [DESCRIPTION]: a description # [STATUS]: a status @@ -284,9 +379,11 @@ include(a) include(b) also-add(c) also-add(d) +content(%s) +content(%s) pkg1 # Comment pkg2 -`), +`, dir1, dir2)), Level: StrictValidation, }, // Bundle filename header Title missmatch with basic validatoin @@ -303,6 +400,7 @@ pkg2 // Bundle contents error (catching errors passed up from parseBundle) {Filename: "b", Contents: []byte(`include(`), Level: BasicValidation, ExpectedErrors: []string{"Missing end parenthesis in line"}, ShouldFail: true}, {Filename: "c", Contents: []byte(`also-add(a`), Level: BasicValidation, ExpectedErrors: []string{"Missing end parenthesis in line"}, ShouldFail: true}, + {Filename: "d", Contents: []byte(`content(a`), Level: BasicValidation, ExpectedErrors: []string{"Missing end parenthesis in line"}, ShouldFail: true}, } testDir, err := ioutil.TempDir("", "bundleset-test-")