Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add info command #101

Merged
merged 43 commits into from
Sep 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
944cd6c
refactor: move release reading logic to a function
rebornplusplus Oct 16, 2023
58e47df
feat: add YAML marshalling logic for Package, Slice
rebornplusplus Oct 18, 2023
541e1ff
feat: add info command
rebornplusplus Oct 18, 2023
8e28abb
test(cmd/info): add tests for the info command
rebornplusplus Oct 19, 2023
b886f47
fix: change long names and address feedback from Ben
rebornplusplus Oct 20, 2023
c866c95
fix: address feedback from Cris
rebornplusplus Oct 20, 2023
5161cc2
fix: address feedback from Alberto
rebornplusplus Nov 28, 2023
5ca6ac8
test(cmd/info): compare YAMLs after parsing
rebornplusplus Nov 29, 2023
7f4f48b
refactor: move ``GetRelease`` to setup package
rebornplusplus Dec 4, 2023
afb4820
Revert "refactor: move ``GetRelease`` to setup package"
rebornplusplus Dec 8, 2023
fe2e425
refactor: rename ``getRelease`` to ``readOrFetchRelease``
rebornplusplus Dec 8, 2023
e19b440
fix: address review comments from Alberto
rebornplusplus Mar 8, 2024
0fed951
Merge 'main' into feat/cmd/info
rebornplusplus Mar 8, 2024
89e05a9
test: fix tests due to new changes in schema
rebornplusplus Mar 8, 2024
e3c2a30
test: refactor setup_test: YAML marshalling
rebornplusplus Mar 12, 2024
5bb0b70
Merge 'main' into feat/cmd/info
rebornplusplus Mar 12, 2024
eb20870
test: drop unnecessary comment
rebornplusplus Mar 12, 2024
fdecad7
chore: update spread test to latest release changes
letFunny Jul 12, 2024
cbe7c37
change slice to base-passwd_data
letFunny Jul 15, 2024
831e200
Merge 'upstream/main' into feat/cmd/info
rebornplusplus Jul 25, 2024
245e3c0
fix: contents in "flow" style
rebornplusplus Jul 26, 2024
d050ab4
fix: change marshalling logic per review
rebornplusplus Jul 29, 2024
3e5a7ed
test: re-indent stdout in cmd_info_test.go
rebornplusplus Jul 29, 2024
7c7c081
test: change test summary
rebornplusplus Jul 29, 2024
60a8c74
test: match naming style with existing slicer_test.go
rebornplusplus Jul 29, 2024
ad969f9
test(spread): add spread task for info
rebornplusplus Jul 30, 2024
6f999bd
Merge remote 'letfunny/fix-spread-tests-ca-certificates' into feat/cm…
rebornplusplus Jul 30, 2024
1524536
fix: remove redundant nil checks
rebornplusplus Jul 31, 2024
674140d
test: update spread test
rebornplusplus Jul 31, 2024
e9e23b3
test(info): re-structure cmd_info tests
rebornplusplus Aug 1, 2024
fda125b
test: fix typo on chisel bin path
rebornplusplus Aug 1, 2024
c7a8546
test: install yq as a dep in spread test
rebornplusplus Aug 1, 2024
e8639dd
test: download yq binary instead of go install
rebornplusplus Aug 1, 2024
b85eb2a
fix: address review comments
rebornplusplus Aug 6, 2024
e7cf89e
empty commit
rebornplusplus Aug 6, 2024
72ffb2d
Merge remote-tracking branch 'upstream/main'
rebornplusplus Aug 6, 2024
c3afbd4
Merge remote-tracking branch 'upstream/main'
rebornplusplus Sep 12, 2024
41a9777
fix: minor changes regarding docs and order per review
rebornplusplus Sep 19, 2024
25e11ba
fix: address review comments
rebornplusplus Sep 19, 2024
35cee63
empty commit to trigger the workflows
rebornplusplus Sep 20, 2024
3d792c1
fix: address review comments
rebornplusplus Sep 24, 2024
939cbd6
fix: minor changes requested by Alberto
rebornplusplus Sep 24, 2024
ed480dd
fix: avoid unnecessary type casting
rebornplusplus Sep 25, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cmd/chisel/cmd_help.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ type helpCategory struct {
var helpCategories = []helpCategory{{
Label: "Basic",
Description: "general operations",
Commands: []string{"find", "help", "version"},
Commands: []string{"find", "info", "help", "version"},
}, {
Label: "Action",
Description: "make things happen",
Expand Down
137 changes: 137 additions & 0 deletions cmd/chisel/cmd_info.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
package main

import (
"fmt"
"strconv"
"strings"

"github.com/jessevdk/go-flags"
"gopkg.in/yaml.v3"

"github.com/canonical/chisel/internal/setup"
)

var shortInfoHelp = "Show information about package slices"
var longInfoHelp = `
The info command shows detailed information about package slices.

It accepts a whitespace-separated list of strings. The list can be
composed of package names, slice names, or a combination of both. The
default output format is YAML. When multiple arguments are provided,
the output is a list of YAML documents separated by a "---" line.

Slice definitions are shown verbatim according to their definition in
the selected release. For example, globs are not expanded.
`

var infoDescs = map[string]string{
"release": "Chisel release name or directory (e.g. ubuntu-22.04)",
}

type infoCmd struct {
Release string `long:"release" value-name:"<branch|dir>"`

Positional struct {
Queries []string `positional-arg-name:"<pkg|slice>" required:"yes"`
} `positional-args:"yes"`
}

func init() {
addCommand("info", shortInfoHelp, longInfoHelp, func() flags.Commander { return &infoCmd{} }, infoDescs, nil)
}

func (cmd *infoCmd) Execute(args []string) error {
if len(args) > 0 {
return ErrExtraArgs
}

release, err := obtainRelease(cmd.Release)
if err != nil {
return err
}

packages, notFound := selectPackageSlices(release, cmd.Positional.Queries)

for i, pkg := range packages {
data, err := yaml.Marshal(pkg)
if err != nil {
return err
}
if i > 0 {
fmt.Fprintln(Stdout, "---")
rebornplusplus marked this conversation as resolved.
Show resolved Hide resolved
}
fmt.Fprint(Stdout, string(data))
}

if len(notFound) > 0 {
for i := range notFound {
notFound[i] = strconv.Quote(notFound[i])
}
return fmt.Errorf("no slice definitions found for: " + strings.Join(notFound, ", "))
}

return nil
}

// selectPackageSlices takes in a release and a list of query strings
// of package names and/or slice names, and returns a list of packages
rebornplusplus marked this conversation as resolved.
Show resolved Hide resolved
// containing the found slices. It also returns a list of query
// strings that were not found.
func selectPackageSlices(release *setup.Release, queries []string) (packages []*setup.Package, notFound []string) {
var pkgOrder []string
pkgSlices := make(map[string][]string)
allPkgSlices := make(map[string]bool)

sliceExists := func(key setup.SliceKey) bool {
pkg, ok := release.Packages[key.Package]
if !ok {
return false
}
_, ok = pkg.Slices[key.Slice]
return ok
}
for _, query := range queries {
var pkg, slice string
if strings.Contains(query, "_") {
key, err := setup.ParseSliceKey(query)
if err != nil || !sliceExists(key) {
notFound = append(notFound, query)
continue
}
pkg, slice = key.Package, key.Slice
} else {
if _, ok := release.Packages[query]; !ok {
notFound = append(notFound, query)
continue
}
pkg = query
}
if len(pkgSlices[pkg]) == 0 && !allPkgSlices[pkg] {
pkgOrder = append(pkgOrder, pkg)
}
if slice == "" {
allPkgSlices[pkg] = true
} else {
pkgSlices[pkg] = append(pkgSlices[pkg], slice)
rebornplusplus marked this conversation as resolved.
Show resolved Hide resolved
}
}

for _, pkgName := range pkgOrder {
var pkg *setup.Package
if allPkgSlices[pkgName] {
pkg = release.Packages[pkgName]
} else {
releasePkg := release.Packages[pkgName]
pkg = &setup.Package{
Name: releasePkg.Name,
Archive: releasePkg.Archive,
Slices: make(map[string]*setup.Slice),
}
for _, sliceName := range pkgSlices[pkgName] {
pkg.Slices[sliceName] = releasePkg.Slices[sliceName]
}
}
packages = append(packages, pkg)
}
return packages, notFound
}
234 changes: 234 additions & 0 deletions cmd/chisel/cmd_info_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
package main_test

import (
"os"
"path/filepath"
"strings"

. "gopkg.in/check.v1"

chisel "github.com/canonical/chisel/cmd/chisel"
"github.com/canonical/chisel/internal/testutil"
)

type infoTest struct {
summary string
input map[string]string
query []string
err string
stdout string
}

var infoTests = []infoTest{{
summary: "A single slice inspection",
input: infoRelease,
query: []string{"mypkg1_myslice1"},
stdout: `
package: mypkg1
archive: ubuntu
slices:
myslice1:
contents:
/dir/file: {}
`,
}, {
summary: "A single package inspection",
input: infoRelease,
query: []string{"mypkg2"},
stdout: `
package: mypkg2
archive: ubuntu
slices:
myslice:
contents:
/dir/another-file: {}
`,
}, {
summary: "Multiple slices within the same package",
input: infoRelease,
query: []string{"mypkg1_myslice2", "mypkg1_myslice1"},
stdout: `
package: mypkg1
archive: ubuntu
slices:
myslice1:
contents:
/dir/file: {}
myslice2:
essential:
- mypkg1_myslice1
- mypkg2_myslice
`,
}, {
summary: "Packages and slices",
input: infoRelease,
query: []string{"mypkg1_myslice1", "mypkg2", "mypkg1_myslice2"},
stdout: `
package: mypkg1
archive: ubuntu
slices:
myslice1:
contents:
/dir/file: {}
myslice2:
essential:
- mypkg1_myslice1
- mypkg2_myslice
---
package: mypkg2
archive: ubuntu
slices:
myslice:
contents:
/dir/another-file: {}
`,
}, {
summary: "Package and its slices",
input: infoRelease,
query: []string{"mypkg1_myslice1", "mypkg1"},
stdout: `
package: mypkg1
archive: ubuntu
slices:
myslice1:
contents:
/dir/file: {}
myslice2:
essential:
- mypkg1_myslice1
- mypkg2_myslice
`,
}, {
summary: "Same slice appearing multiple times",
input: infoRelease,
query: []string{"mypkg1_myslice1", "mypkg1_myslice1", "mypkg1_myslice1"},
stdout: `
package: mypkg1
archive: ubuntu
slices:
myslice1:
contents:
/dir/file: {}
`,
}, {
summary: "No slices found",
input: infoRelease,
query: []string{"foo", "bar_foo"},
err: `no slice definitions found for: "foo", "bar_foo"`,
}, {
summary: "Some slices found, others not found",
input: infoRelease,
query: []string{"foo", "mypkg1_myslice1", "bar_foo"},
stdout: `
package: mypkg1
archive: ubuntu
slices:
myslice1:
contents:
/dir/file: {}
/dir/sub-dir/: {make: true, mode: 0644}
`,
err: `no slice definitions found for: "foo", "bar_foo"`,
}, {
summary: "No args",
input: infoRelease,
err: "the required argument `<pkg|slice> (at least 1 argument)` was not provided",
}, {
summary: "Empty, whitespace args",
input: infoRelease,
query: []string{"", " "},
err: `no slice definitions found for: "", " "`,
}, {
summary: "Ignore invalid slice names",
input: infoRelease,
query: []string{"foo_bar_foo", "a_b", "7_c", "a_b c", "a_b x_y"},
err: `no slice definitions found for: "foo_bar_foo", "a_b", "7_c", "a_b c", "a_b x_y"`,
}}

var testKey = testutil.PGPKeys["key1"]
rebornplusplus marked this conversation as resolved.
Show resolved Hide resolved

var defaultChiselYaml = `
format: chisel-v1
archives:
ubuntu:
version: 22.04
components: [main, universe]
v1-public-keys: [test-key]
v1-public-keys:
test-key:
id: ` + testKey.ID + `
armor: |` + "\n" + testutil.PrefixEachLine(testKey.PubKeyArmor, "\t\t\t\t\t\t")

var infoRelease = map[string]string{
"chisel.yaml": string(defaultChiselYaml),
"slices/mypkg1.yaml": `
package: mypkg1
essential:
- mypkg1_myslice1
slices:
myslice1:
contents:
/dir/file:
myslice2:
essential:
- mypkg2_myslice
`,
"slices/mypkg2.yaml": `
package: mypkg2
slices:
myslice:
contents:
/dir/another-file:
`,
"slices/mypkg3.yaml": `
package: mypkg3
essential:
- mypkg1_myslice1
slices:
myslice:
essential:
- mypkg2_myslice
contents:
/dir/other-file:
/dir/glob*:
/dir/sub-dir/: {make: true, mode: 0644}
/dir/copy: {copy: /dir/file}
/dir/symlink: {symlink: /dir/file}
/dir/mutable: {text: TODO, mutable: true, arch: riscv64}
/dir/arch-specific*: {arch: [amd64,arm64,i386]}
/dir/until: {until: mutate}
/dir/unfolded:
copy: /dir/file
mode: 0644
mutate: |
# Test multi-line string.
content.write("/dir/mutable", foo)
`,
}

func (s *ChiselSuite) TestInfoCommand(c *C) {
for _, test := range infoTests {
c.Logf("Summary: %s", test.summary)

s.ResetStdStreams()

dir := c.MkDir()
for path, data := range test.input {
fpath := filepath.Join(dir, path)
err := os.MkdirAll(filepath.Dir(fpath), 0755)
c.Assert(err, IsNil)
err = os.WriteFile(fpath, testutil.Reindent(data), 0644)
c.Assert(err, IsNil)
}
test.query = append([]string{"info", "--release", dir}, test.query...)

_, err := chisel.Parser().ParseArgs(test.query)
if test.err != "" {
c.Assert(err, ErrorMatches, test.err)
continue
}
c.Assert(err, IsNil)
test.stdout = string(testutil.Reindent(test.stdout))
c.Assert(s.Stdout(), Equals, strings.TrimSpace(test.stdout)+"\n")
}
}
Loading
Loading