Skip to content

Commit b56cd6d

Browse files
joelanfordtimflannagan
authored andcommitted
Add opm alpha init/render commands.
The `init` command is a simple CLI used to generate new `olm.package` blobs. It has flags for default channel, icon file, and description file to enable every field to be populated via the CLI. The `render` command converts various opm-related images to a declarative config format. It: - converts bundle images to `olm.bundle` blobs - converts databases in sqlite-based index images to declarative config blobs. - loads and serializes config directories from declarative-config-based index images, inlining any bundle objects that are found. Combined, these two commands form the foundation for building new DC-based indexes. Signed-off-by: Joe Lanford <[email protected]> Upstream-repository: operator-registry Upstream-commit: fd099516ef679bd14d53212f40b4dae8f61ca8b7
1 parent 4a4b236 commit b56cd6d

File tree

31 files changed

+1317
-83
lines changed

31 files changed

+1317
-83
lines changed
Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
package alpha
22

33
import (
4+
"github.com/spf13/cobra"
5+
46
"github.com/operator-framework/operator-registry/cmd/opm/alpha/add"
57
"github.com/operator-framework/operator-registry/cmd/opm/alpha/bundle"
8+
initcmd "github.com/operator-framework/operator-registry/cmd/opm/alpha/init"
9+
"github.com/operator-framework/operator-registry/cmd/opm/alpha/render"
610
"github.com/operator-framework/operator-registry/cmd/opm/alpha/serve"
711
"github.com/operator-framework/operator-registry/cmd/opm/alpha/validate"
8-
"github.com/spf13/cobra"
912
)
1013

1114
func NewCmd() *cobra.Command {
@@ -15,6 +18,6 @@ func NewCmd() *cobra.Command {
1518
Short: "Run an alpha subcommand",
1619
}
1720

18-
runCmd.AddCommand(bundle.NewCmd(), add.NewCmd(), serve.NewCmd(), validate.NewCmd())
21+
runCmd.AddCommand(bundle.NewCmd(), add.NewCmd(), initcmd.NewCmd(), serve.NewCmd(), render.NewCmd(), validate.NewCmd())
1922
return runCmd
2023
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package init
2+
3+
import (
4+
"io"
5+
"os"
6+
7+
log "github.com/sirupsen/logrus"
8+
"github.com/spf13/cobra"
9+
10+
"github.com/operator-framework/operator-registry/internal/action"
11+
"github.com/operator-framework/operator-registry/internal/declcfg"
12+
)
13+
14+
func NewCmd() *cobra.Command {
15+
var (
16+
init action.Init
17+
iconFile string
18+
descriptionFile string
19+
output string
20+
)
21+
cmd := &cobra.Command{
22+
Use: "init <packageName>",
23+
Short: "Generate an olm.package declarative config blob",
24+
Args: cobra.ExactArgs(1),
25+
Run: func(cmd *cobra.Command, args []string) {
26+
init.Package = args[0]
27+
28+
var write func(declcfg.DeclarativeConfig, io.Writer) error
29+
switch output {
30+
case "yaml":
31+
write = declcfg.WriteYAML
32+
case "json":
33+
write = declcfg.WriteJSON
34+
default:
35+
log.Fatalf("invalid --output value %q, expected (json|yaml)", output)
36+
}
37+
38+
if iconFile != "" {
39+
iconReader, err := os.Open(iconFile)
40+
if err != nil {
41+
log.Fatalf("open icon file: %v", err)
42+
}
43+
defer closeReader(iconReader)
44+
init.IconReader = iconReader
45+
}
46+
47+
if descriptionFile != "" {
48+
descriptionReader, err := os.Open(descriptionFile)
49+
if err != nil {
50+
log.Fatalf("open description file: %v", err)
51+
}
52+
defer closeReader(descriptionReader)
53+
init.DescriptionReader = descriptionReader
54+
}
55+
56+
pkg, err := init.Run()
57+
if err != nil {
58+
log.Fatal(err)
59+
}
60+
cfg := declcfg.DeclarativeConfig{Packages: []declcfg.Package{*pkg}}
61+
if err := write(cfg, os.Stdout); err != nil {
62+
log.Fatal(err)
63+
}
64+
},
65+
}
66+
cmd.Flags().StringVarP(&init.DefaultChannel, "default-channel", "c", "", "The channel that subscriptions will default to if unspecified")
67+
cmd.Flags().StringVarP(&iconFile, "icon", "i", "", "Path to package's icon")
68+
cmd.Flags().StringVarP(&descriptionFile, "description", "d", "", "Path to the operator's README.md (or other documentation)")
69+
cmd.Flags().StringVarP(&output, "output", "o", "json", "Output format (json|yaml)")
70+
return cmd
71+
}
72+
73+
func closeReader(closer io.ReadCloser) {
74+
if err := closer.Close(); err != nil {
75+
log.Warn(err)
76+
}
77+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package render
2+
3+
import (
4+
"io"
5+
"io/ioutil"
6+
"log"
7+
"os"
8+
9+
"github.com/sirupsen/logrus"
10+
"github.com/spf13/cobra"
11+
12+
"github.com/operator-framework/operator-registry/internal/action"
13+
"github.com/operator-framework/operator-registry/internal/declcfg"
14+
)
15+
16+
func NewCmd() *cobra.Command {
17+
var (
18+
render action.Render
19+
output string
20+
)
21+
cmd := &cobra.Command{
22+
Use: "render <index-or-bundle-image1> <index-or-bundle-image2> <index-or-bundle-imageN>",
23+
Short: "Generate declarative config blobs from the provided index and bundle images",
24+
Args: cobra.MinimumNArgs(1),
25+
Run: func(cmd *cobra.Command, args []string) {
26+
render.Refs = args
27+
28+
var write func(declcfg.DeclarativeConfig, io.Writer) error
29+
switch output {
30+
case "yaml":
31+
write = declcfg.WriteYAML
32+
case "json":
33+
write = declcfg.WriteJSON
34+
default:
35+
log.Fatalf("invalid --output value %q, expected (json|yaml)", output)
36+
}
37+
38+
// TODO(joelanford): What's this for?
39+
logrus.SetOutput(ioutil.Discard)
40+
41+
cfg, err := render.Run(cmd.Context())
42+
if err != nil {
43+
log.Fatal(err)
44+
}
45+
46+
if err := write(*cfg, os.Stdout); err != nil {
47+
log.Fatal(err)
48+
}
49+
},
50+
}
51+
cmd.Flags().StringVarP(&output, "output", "o", "json", "Output format (json|yaml)")
52+
return cmd
53+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package action
2+
3+
import (
4+
"fmt"
5+
"io"
6+
"io/ioutil"
7+
8+
"github.com/h2non/filetype"
9+
10+
"github.com/operator-framework/operator-registry/internal/declcfg"
11+
)
12+
13+
type Init struct {
14+
Package string
15+
DefaultChannel string
16+
DescriptionReader io.Reader
17+
IconReader io.Reader
18+
}
19+
20+
func (i Init) Run() (*declcfg.Package, error) {
21+
pkg := &declcfg.Package{
22+
Schema: "olm.package",
23+
Name: i.Package,
24+
DefaultChannel: i.DefaultChannel,
25+
}
26+
if i.DescriptionReader != nil {
27+
descriptionData, err := ioutil.ReadAll(i.DescriptionReader)
28+
if err != nil {
29+
return nil, fmt.Errorf("read description: %v", err)
30+
}
31+
pkg.Description = string(descriptionData)
32+
}
33+
34+
if i.IconReader != nil {
35+
iconData, err := ioutil.ReadAll(i.IconReader)
36+
if err != nil {
37+
return nil, fmt.Errorf("read icon: %v", err)
38+
}
39+
iconType, err := filetype.Match(iconData)
40+
if err != nil {
41+
return nil, fmt.Errorf("detect icon mediatype: %v", err)
42+
}
43+
if iconType.MIME.Type != "image" {
44+
return nil, fmt.Errorf("detected invalid type %q: not an image", iconType.MIME.Value)
45+
}
46+
pkg.Icon = &declcfg.Icon{
47+
Data: iconData,
48+
MediaType: iconType.MIME.Value,
49+
}
50+
}
51+
return pkg, nil
52+
}
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
package action_test
2+
3+
import (
4+
"bytes"
5+
"errors"
6+
"testing"
7+
"testing/iotest"
8+
9+
"github.com/stretchr/testify/require"
10+
11+
"github.com/operator-framework/operator-registry/internal/action"
12+
"github.com/operator-framework/operator-registry/internal/declcfg"
13+
)
14+
15+
const (
16+
svgIcon = `<svg viewBox="0 0 100 100"><circle cx="25" cy="25" r="25"/></svg>`
17+
)
18+
19+
func TestInit(t *testing.T) {
20+
type spec struct {
21+
name string
22+
init action.Init
23+
expectPkg *declcfg.Package
24+
assertion require.ErrorAssertionFunc
25+
}
26+
27+
specs := []spec{
28+
{
29+
name: "Success/Empty",
30+
init: action.Init{},
31+
expectPkg: &declcfg.Package{
32+
Schema: "olm.package",
33+
},
34+
assertion: require.NoError,
35+
},
36+
{
37+
name: "Success/SetPackage",
38+
init: action.Init{
39+
Package: "foo",
40+
},
41+
expectPkg: &declcfg.Package{
42+
Schema: "olm.package",
43+
Name: "foo",
44+
},
45+
assertion: require.NoError,
46+
},
47+
{
48+
name: "Success/SetDefaultChannel",
49+
init: action.Init{
50+
DefaultChannel: "foo",
51+
},
52+
expectPkg: &declcfg.Package{
53+
Schema: "olm.package",
54+
DefaultChannel: "foo",
55+
},
56+
assertion: require.NoError,
57+
},
58+
{
59+
name: "Success/SetDescription",
60+
init: action.Init{
61+
DescriptionReader: bytes.NewBufferString("foo"),
62+
},
63+
expectPkg: &declcfg.Package{
64+
Schema: "olm.package",
65+
Description: "foo",
66+
},
67+
assertion: require.NoError,
68+
},
69+
{
70+
name: "Success/SetIcon",
71+
init: action.Init{
72+
IconReader: bytes.NewBufferString(svgIcon),
73+
},
74+
expectPkg: &declcfg.Package{
75+
Schema: "olm.package",
76+
Icon: &declcfg.Icon{
77+
Data: bytes.NewBufferString(svgIcon).Bytes(),
78+
MediaType: "image/svg+xml",
79+
},
80+
},
81+
assertion: require.NoError,
82+
},
83+
{
84+
name: "Success/SetAll",
85+
init: action.Init{
86+
Package: "a",
87+
DefaultChannel: "b",
88+
DescriptionReader: bytes.NewBufferString("c"),
89+
IconReader: bytes.NewBufferString(svgIcon),
90+
},
91+
expectPkg: &declcfg.Package{
92+
Schema: "olm.package",
93+
Name: "a",
94+
DefaultChannel: "b",
95+
Description: "c",
96+
Icon: &declcfg.Icon{
97+
Data: bytes.NewBufferString(svgIcon).Bytes(),
98+
MediaType: "image/svg+xml",
99+
},
100+
},
101+
assertion: require.NoError,
102+
},
103+
{
104+
name: "Fail/ReadDescription",
105+
init: action.Init{
106+
DescriptionReader: iotest.ErrReader(errors.New("fail")),
107+
},
108+
assertion: require.Error,
109+
},
110+
{
111+
name: "Fail/ReadIcon",
112+
init: action.Init{
113+
IconReader: iotest.ErrReader(errors.New("fail")),
114+
},
115+
assertion: require.Error,
116+
},
117+
{
118+
name: "Fail/IconNotImage",
119+
init: action.Init{
120+
IconReader: bytes.NewBufferString("foo"),
121+
},
122+
assertion: require.Error,
123+
},
124+
{
125+
name: "Fail/EmptyIcon",
126+
init: action.Init{
127+
IconReader: bytes.NewBuffer(nil),
128+
},
129+
assertion: require.Error,
130+
},
131+
}
132+
for _, s := range specs {
133+
t.Run(s.name, func(t *testing.T) {
134+
actualPkg, actualErr := s.init.Run()
135+
s.assertion(t, actualErr)
136+
require.Equal(t, s.expectPkg, actualPkg)
137+
})
138+
}
139+
}

0 commit comments

Comments
 (0)