Skip to content

Commit f170e90

Browse files
authoredApr 16, 2020
Merge pull request operator-framework#279 from ecordell/demonless-index-cmds
fix(add): allow containertool to be specified for registry add
2 parents 6a831e7 + a16399f commit f170e90

22 files changed

+447
-92
lines changed
 

‎cmd/opm/index/add.go

+13-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"github.com/spf13/cobra"
88
"k8s.io/kubectl/pkg/util/templates"
99

10+
"github.com/operator-framework/operator-registry/pkg/containertools"
1011
"github.com/operator-framework/operator-registry/pkg/lib/indexer"
1112
"github.com/operator-framework/operator-registry/pkg/registry"
1213
)
@@ -52,6 +53,7 @@ func addIndexAddCmd(parent *cobra.Command) {
5253
if err := indexCmd.MarkFlagRequired("bundles"); err != nil {
5354
logrus.Panic("Failed to set required `bundles` flag for `index add`")
5455
}
56+
indexCmd.Flags().Bool("skip-tls", false, "skip TLS certificate verification for container image registries while pulling bundles")
5557
indexCmd.Flags().StringP("binary-image", "i", "", "container image for on-image `opm` command")
5658
indexCmd.Flags().StringP("container-tool", "c", "podman", "tool to interact with container images (save, build, etc.). One of: [docker, podman]")
5759
indexCmd.Flags().StringP("tag", "t", "", "custom tag for container image being built")
@@ -99,6 +101,10 @@ func runIndexAddCmdFunc(cmd *cobra.Command, args []string) error {
99101
return err
100102
}
101103

104+
if containerTool == "none" {
105+
return fmt.Errorf("none is not a valid container-tool for index add")
106+
}
107+
102108
tag, err := cmd.Flags().GetString("tag")
103109
if err != nil {
104110
return err
@@ -109,6 +115,11 @@ func runIndexAddCmdFunc(cmd *cobra.Command, args []string) error {
109115
return err
110116
}
111117

118+
skipTLS, err := cmd.Flags().GetBool("skip-tls")
119+
if err != nil {
120+
return err
121+
}
122+
112123
mode, err := cmd.Flags().GetString("mode")
113124
if err != nil {
114125
return err
@@ -123,7 +134,7 @@ func runIndexAddCmdFunc(cmd *cobra.Command, args []string) error {
123134

124135
logger.Info("building the index")
125136

126-
indexAdder := indexer.NewIndexAdder(containerTool, logger)
137+
indexAdder := indexer.NewIndexAdder(containertools.NewContainerTool(containerTool, containertools.PodmanTool), logger)
127138

128139
request := indexer.AddToIndexRequest{
129140
Generate: generate,
@@ -134,6 +145,7 @@ func runIndexAddCmdFunc(cmd *cobra.Command, args []string) error {
134145
Bundles: bundles,
135146
Permissive: permissive,
136147
Mode: modeEnum,
148+
SkipTLS: skipTLS,
137149
}
138150

139151
err = indexAdder.AddToIndex(request)

‎cmd/opm/index/delete.go

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package index
22

33
import (
4+
"fmt"
45
"github.com/sirupsen/logrus"
56
"github.com/spf13/cobra"
67

8+
"github.com/operator-framework/operator-registry/pkg/containertools"
79
"github.com/operator-framework/operator-registry/pkg/lib/indexer"
810
)
911

@@ -35,7 +37,7 @@ func newIndexDeleteCmd() *cobra.Command {
3537
logrus.Panic("Failed to set required `operators` flag for `index delete`")
3638
}
3739
indexCmd.Flags().StringP("binary-image", "i", "", "container image for on-image `opm` command")
38-
indexCmd.Flags().StringP("container-tool", "c", "podman", "tool to interact with container images (save, build, etc.). One of: [docker, podman]")
40+
indexCmd.Flags().StringP("container-tool", "c", "podman", "tool to interact with container images (save, build, etc.). One of: [none, docker, podman]")
3941
indexCmd.Flags().StringP("tag", "t", "", "custom tag for container image being built")
4042
indexCmd.Flags().Bool("permissive", false, "allow registry load errors")
4143

@@ -78,6 +80,10 @@ func runIndexDeleteCmdFunc(cmd *cobra.Command, args []string) error {
7880
return err
7981
}
8082

83+
if containerTool == "none" {
84+
return fmt.Errorf("none is not a valid container-tool for index add")
85+
}
86+
8187
tag, err := cmd.Flags().GetString("tag")
8288
if err != nil {
8389
return err
@@ -92,7 +98,7 @@ func runIndexDeleteCmdFunc(cmd *cobra.Command, args []string) error {
9298

9399
logger.Info("building the index")
94100

95-
indexDeleter := indexer.NewIndexDeleter(containerTool, logger)
101+
indexDeleter := indexer.NewIndexDeleter(containertools.NewContainerTool(containerTool, containertools.PodmanTool), logger)
96102

97103
request := indexer.DeleteFromIndexRequest{
98104
Generate: generate,

‎cmd/opm/index/export.go

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
package index
22

33
import (
4+
"fmt"
45
"github.com/sirupsen/logrus"
56
"github.com/spf13/cobra"
67
"k8s.io/kubectl/pkg/util/templates"
78

9+
"github.com/operator-framework/operator-registry/pkg/containertools"
810
"github.com/operator-framework/operator-registry/pkg/lib/indexer"
911
)
1012

@@ -44,7 +46,7 @@ func newIndexExportCmd() *cobra.Command {
4446
logrus.Panic("Failed to set required `package` flag for `index export`")
4547
}
4648
indexCmd.Flags().StringP("download-folder", "f", "downloaded", "directory where downloaded operator bundle(s) will be stored")
47-
indexCmd.Flags().StringP("container-tool", "c", "podman", "tool to interact with container images (save, build, etc.). One of: [docker, podman]")
49+
indexCmd.Flags().StringP("container-tool", "c", "podman", "tool to interact with container images (save, build, etc.). One of: [none, docker, podman]")
4850
if err := indexCmd.Flags().MarkHidden("debug"); err != nil {
4951
logrus.Panic(err.Error())
5052
}
@@ -74,11 +76,15 @@ func runIndexExportCmdFunc(cmd *cobra.Command, args []string) error {
7476
return err
7577
}
7678

79+
if containerTool == "none" {
80+
return fmt.Errorf("none is not a valid container-tool for index add")
81+
}
82+
7783
logger := logrus.WithFields(logrus.Fields{"index": index, "package": packageName})
7884

7985
logger.Info("export from the index")
8086

81-
indexExporter := indexer.NewIndexExporter(containerTool, logger)
87+
indexExporter := indexer.NewIndexExporter(containertools.NewContainerTool(containerTool, containertools.PodmanTool), logger)
8288

8389
request := indexer.ExportFromIndexRequest{
8490
Index: index,

‎cmd/opm/registry/add.go

+7-7
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"github.com/sirupsen/logrus"
55
"github.com/spf13/cobra"
66

7+
"github.com/operator-framework/operator-registry/pkg/containertools"
78
"github.com/operator-framework/operator-registry/pkg/lib/registry"
89
reg "github.com/operator-framework/operator-registry/pkg/registry"
910
)
@@ -30,11 +31,7 @@ func newRegistryAddCmd() *cobra.Command {
3031
rootCmd.Flags().Bool("permissive", false, "allow registry load errors")
3132
rootCmd.Flags().Bool("skip-tls", false, "skip TLS certificate verification for container image registries while pulling bundles")
3233
rootCmd.Flags().StringP("mode", "", "replaces", "graph update mode that defines how channel graphs are updated. One of: [replaces, semver, semver-skippatch]")
33-
34-
rootCmd.Flags().StringP("container-tool", "c", "", "")
35-
if err := rootCmd.Flags().MarkDeprecated("container-tool", "ignored in favor of standalone image manipulation"); err != nil {
36-
logrus.Panic(err.Error())
37-
}
34+
rootCmd.Flags().StringP("container-tool", "c", "none", "tool to interact with container images (save, build, etc.). One of: [none, docker, podman]")
3835

3936
return rootCmd
4037
}
@@ -56,12 +53,14 @@ func addFunc(cmd *cobra.Command, args []string) error {
5653
if err != nil {
5754
return err
5855
}
59-
56+
containerTool, err := cmd.Flags().GetString("container-tool")
57+
if err != nil {
58+
return err
59+
}
6060
mode, err := cmd.Flags().GetString("mode")
6161
if err != nil {
6262
return err
6363
}
64-
6564
modeEnum, err := reg.GetModeFromString(mode)
6665
if err != nil {
6766
return err
@@ -73,6 +72,7 @@ func addFunc(cmd *cobra.Command, args []string) error {
7372
InputDatabase: fromFilename,
7473
Bundles: bundleImages,
7574
Mode: modeEnum,
75+
ContainerTool: containertools.NewContainerTool(containerTool, containertools.NoneTool),
7676
}
7777

7878
logger := logrus.WithFields(logrus.Fields{"bundles": bundleImages})

‎pkg/containertools/command.go

+9-26
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,6 @@ import (
88
"github.com/sirupsen/logrus"
99
)
1010

11-
const (
12-
// Podman cli tool
13-
Podman = "podman"
14-
// Docker cli tool
15-
Docker = "docker"
16-
)
17-
1811
// CommandRunner defines methods to shell out to common container tools
1912
type CommandRunner interface {
2013
GetToolName() string
@@ -28,42 +21,32 @@ type CommandRunner interface {
2821
// execute commands with that tooling.
2922
type ContainerCommandRunner struct {
3023
logger *logrus.Entry
31-
containerTool string
24+
containerTool ContainerTool
3225
}
3326

3427
// NewCommandRunner takes the containerTool as an input string and returns a
3528
// CommandRunner to run commands with that cli tool
36-
func NewCommandRunner(containerTool string, logger *logrus.Entry) CommandRunner {
29+
func NewCommandRunner(containerTool ContainerTool, logger *logrus.Entry) CommandRunner {
3730
r := ContainerCommandRunner{
3831
logger: logger,
32+
containerTool: containerTool,
3933
}
40-
41-
switch containerTool {
42-
case Podman:
43-
r.containerTool = Podman
44-
case Docker:
45-
r.containerTool = Docker
46-
default:
47-
r.containerTool = Podman
48-
}
49-
5034
return &r
5135
}
5236

5337
// GetToolName returns the container tool this command runner is using
5438
func (r *ContainerCommandRunner) GetToolName() string {
55-
return r.containerTool
39+
return r.containerTool.String()
5640
}
5741

5842
// Pull takes a container image path hosted on a container registry and runs the
5943
// pull command to download it onto the local environment
6044
func (r *ContainerCommandRunner) Pull(image string) error {
6145
args := []string{"pull", image}
6246

63-
command := exec.Command(r.containerTool, args...)
47+
command := exec.Command(r.containerTool.String(), args...)
6448

65-
r.logger.Infof("running %s pull", r.containerTool)
66-
r.logger.Debugf("%s", command.Args)
49+
r.logger.Infof("running %s", command.String())
6750

6851
out, err := command.CombinedOutput()
6952
if err != nil {
@@ -84,7 +67,7 @@ func (r *ContainerCommandRunner) Build(dockerfile, tag string) error {
8467

8568
args = append(args, ".")
8669

87-
command := exec.Command(r.containerTool, args...)
70+
command := exec.Command(r.containerTool.String(), args...)
8871

8972
r.logger.Infof("running %s build", r.containerTool)
9073
r.logger.Infof("%s", command.Args)
@@ -103,7 +86,7 @@ func (r *ContainerCommandRunner) Build(dockerfile, tag string) error {
10386
func (r *ContainerCommandRunner) Save(image, tarFile string) error {
10487
args := []string{"save", image, "-o", tarFile}
10588

106-
command := exec.Command(r.containerTool, args...)
89+
command := exec.Command(r.containerTool.String(), args...)
10790

10891
r.logger.Infof("running %s save", r.containerTool)
10992
r.logger.Debugf("%s", command.Args)
@@ -122,7 +105,7 @@ func (r *ContainerCommandRunner) Save(image, tarFile string) error {
122105
func (r *ContainerCommandRunner) Inspect(image string) ([]byte, error) {
123106
args := []string{"inspect", image}
124107

125-
command := exec.Command(r.containerTool, args...)
108+
command := exec.Command(r.containerTool.String(), args...)
126109

127110
r.logger.Infof("running %s inspect", r.containerTool)
128111
r.logger.Debugf("%s", command.Args)

‎pkg/containertools/containertool.go

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package containertools
2+
3+
type ContainerTool int
4+
5+
const (
6+
NoneTool ContainerTool = iota
7+
PodmanTool
8+
DockerTool
9+
)
10+
11+
func (t ContainerTool) String() (s string) {
12+
switch t {
13+
case NoneTool:
14+
s = "none"
15+
case PodmanTool:
16+
s = "podman"
17+
case DockerTool:
18+
s = "docker"
19+
}
20+
return
21+
}
22+
23+
func NewContainerTool(s string, defaultTool ContainerTool) (t ContainerTool) {
24+
switch s {
25+
case "podman":
26+
t = PodmanTool
27+
case "docker":
28+
t = DockerTool
29+
case "none":
30+
t = NoneTool
31+
default:
32+
t = defaultTool
33+
}
34+
return
35+
}
36+
37+
// NewCommandContainerTool returns a tool that can be used in `exec` statements.
38+
func NewCommandContainerTool(s string) (t ContainerTool) {
39+
switch s {
40+
case "docker":
41+
t = DockerTool
42+
default:
43+
t = PodmanTool
44+
}
45+
return
46+
}

‎pkg/containertools/dockerfilegenerator.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ type IndexDockerfileGenerator struct {
2424
}
2525

2626
// NewDockerfileGenerator is a constructor that returns a DockerfileGenerator
27-
func NewDockerfileGenerator(containerTool string, logger *logrus.Entry) DockerfileGenerator {
27+
func NewDockerfileGenerator(logger *logrus.Entry) DockerfileGenerator {
2828
return &IndexDockerfileGenerator{
2929
Logger: logger,
3030
}

‎pkg/containertools/imagereader.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ type ImageLayerReader struct {
3333
Logger *logrus.Entry
3434
}
3535

36-
func NewImageReader(containerTool string, logger *logrus.Entry) ImageReader {
36+
func NewImageReader(containerTool ContainerTool, logger *logrus.Entry) ImageReader {
3737
cmd := NewCommandRunner(containerTool, logger)
3838

3939
return &ImageLayerReader{

‎pkg/containertools/labelreader.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ type ImageLabelReader struct {
1717
Cmd CommandRunner
1818
}
1919

20-
func NewLabelReader(containerTool string, logger *logrus.Entry) LabelReader {
20+
func NewLabelReader(containerTool ContainerTool, logger *logrus.Entry) LabelReader {
2121
cmd := NewCommandRunner(containerTool, logger)
2222

2323
return ImageLabelReader{

‎pkg/image/containerdregistry/options.go

+13-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package containerdregistry
22

33
import (
4+
"crypto/x509"
45
"os"
56
"path/filepath"
67
"sync"
@@ -20,6 +21,7 @@ type RegistryConfig struct {
2021
CacheDir string
2122
PreserveCache bool
2223
SkipTLS bool
24+
Roots *x509.CertPool
2325
}
2426

2527
func (r *RegistryConfig) apply(options []RegistryOption) {
@@ -52,7 +54,7 @@ func defaultConfig() *RegistryConfig {
5254

5355
// NewRegistry returns a new containerd Registry and a function to destroy it after use.
5456
// The destroy function is safe to call more than once, but is a no-op after the first call.
55-
func NewRegistry(options ...RegistryOption) (registry *Registry, destroy func() error, err error) {
57+
func NewRegistry(options ...RegistryOption) (registry *Registry, err error) {
5658
config := defaultConfig()
5759
config.apply(options)
5860
if err = config.complete(); err != nil {
@@ -71,7 +73,7 @@ func NewRegistry(options ...RegistryOption) (registry *Registry, destroy func()
7173
}
7274

7375
var once sync.Once
74-
destroy = func() (destroyErr error) {
76+
destroy := func() (destroyErr error) {
7577
once.Do(func() {
7678
if destroyErr = bdb.Close(); destroyErr != nil {
7779
return
@@ -87,14 +89,14 @@ func NewRegistry(options ...RegistryOption) (registry *Registry, destroy func()
8789
}
8890

8991
var resolver remotes.Resolver
90-
resolver, err = NewResolver(config.ResolverConfigDir, config.SkipTLS)
92+
resolver, err = NewResolver(config.ResolverConfigDir, config.SkipTLS, config.Roots)
9193
if err != nil {
9294
return
9395
}
9496

9597
registry = &Registry{
96-
Store: newStore(metadata.NewDB(bdb, cs, nil)),
97-
98+
Store: newStore(metadata.NewDB(bdb, cs, nil)),
99+
destroy: destroy,
98100
log: config.Log,
99101
resolver: resolver,
100102
platform: platforms.Only(platforms.DefaultSpec()),
@@ -122,6 +124,12 @@ func WithCacheDir(dir string) RegistryOption {
122124
}
123125
}
124126

127+
func WithRootCAs(pool *x509.CertPool) RegistryOption {
128+
return func(config *RegistryConfig) {
129+
config.Roots = pool
130+
}
131+
}
132+
125133
func PreserveCache(preserve bool) RegistryOption {
126134
return func(config *RegistryConfig) {
127135
config.PreserveCache = preserve

‎pkg/image/containerdregistry/registry.go

+14-5
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,7 @@ package containerdregistry
33
import (
44
"archive/tar"
55
"context"
6-
"io"
7-
"os"
8-
6+
"fmt"
97
"github.com/containerd/containerd/archive"
108
"github.com/containerd/containerd/archive/compression"
119
"github.com/containerd/containerd/errdefs"
@@ -15,14 +13,16 @@ import (
1513
"github.com/containerd/containerd/remotes"
1614
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
1715
"github.com/sirupsen/logrus"
16+
"io"
17+
"os"
1818

1919
"github.com/operator-framework/operator-registry/pkg/image"
2020
)
2121

2222
// Registry enables manipulation of images via containerd modules.
2323
type Registry struct {
2424
Store
25-
25+
destroy func() error
2626
log *logrus.Entry
2727
resolver remotes.Resolver
2828
platform platforms.MatchComparer
@@ -37,7 +37,7 @@ func (r *Registry) Pull(ctx context.Context, ref image.Reference) error {
3737

3838
name, root, err := r.resolver.Resolve(ctx, ref.String())
3939
if err != nil {
40-
return err
40+
return fmt.Errorf("error resolving name %s: %v", name, err)
4141
}
4242
r.log.Infof("resolved name: %s", name)
4343

@@ -93,13 +93,22 @@ func (r *Registry) Unpack(ctx context.Context, ref image.Reference, dir string)
9393
return nil
9494
}
9595

96+
// Destroy cleans up the on-disk boltdb file and other cache files, unless preserve cache is true
97+
func (r *Registry) Destroy() (err error) {
98+
return r.destroy()
99+
}
100+
96101
func (r *Registry) fetch(ctx context.Context, fetcher remotes.Fetcher, root ocispec.Descriptor) error {
97102
visitor := images.HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
98103
r.log.WithField("digest", desc.Digest).Info("fetched")
99104
r.log.Debug(desc)
100105
return nil, nil
101106
})
102107

108+
if root.MediaType == images.MediaTypeDockerSchema1Manifest {
109+
return fmt.Errorf("specified image is a docker schema v1 manifest, which is not supported")
110+
}
111+
103112
handler := images.Handlers(
104113
visitor,
105114
remotes.FetchHandler(r.Content(), fetcher),

‎pkg/image/containerdregistry/resolver.go

+24-3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package containerdregistry
22

33
import (
44
"crypto/tls"
5+
"crypto/x509"
56
"net"
67
"net/http"
78
"time"
@@ -14,7 +15,7 @@ import (
1415
"github.com/docker/docker/registry"
1516
)
1617

17-
func NewResolver(configDir string, insecure bool) (remotes.Resolver, error) {
18+
func NewResolver(configDir string, insecure bool, roots *x509.CertPool) (remotes.Resolver, error) {
1819
transport := &http.Transport{
1920
Proxy: http.ProxyFromEnvironment,
2021
DialContext: (&net.Dialer{
@@ -25,12 +26,20 @@ func NewResolver(configDir string, insecure bool) (remotes.Resolver, error) {
2526
IdleConnTimeout: 30 * time.Second,
2627
TLSHandshakeTimeout: 10 * time.Second,
2728
ExpectContinueTimeout: 5 * time.Second,
29+
TLSClientConfig: &tls.Config{
30+
InsecureSkipVerify: false,
31+
RootCAs: roots,
32+
},
2833
}
34+
2935
if insecure {
3036
transport.TLSClientConfig = &tls.Config{
3137
InsecureSkipVerify: insecure,
3238
}
3339
}
40+
headers := http.Header{}
41+
headers.Set("User-Agent", "opm/alpha")
42+
3443
client := http.DefaultClient
3544
client.Transport = transport
3645

@@ -39,9 +48,21 @@ func NewResolver(configDir string, insecure bool) (remotes.Resolver, error) {
3948
return nil, err
4049
}
4150

51+
regopts := []docker.RegistryOpt{
52+
docker.WithAuthorizer(docker.NewDockerAuthorizer(
53+
docker.WithAuthClient(client),
54+
docker.WithAuthHeader(headers),
55+
docker.WithAuthCreds(credential(cfg)),
56+
)),
57+
docker.WithClient(client),
58+
}
59+
if insecure {
60+
regopts = append(regopts, docker.WithPlainHTTP(docker.MatchAllHosts))
61+
}
62+
4263
opts := docker.ResolverOptions{
43-
Client: client,
44-
Credentials: credential(cfg),
64+
Hosts: docker.ConfigureDefaultRegistries(regopts...),
65+
Headers: headers,
4566
}
4667

4768
return docker.NewResolver(opts), nil

‎pkg/image/execregistry/registry.go

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package execregistry
2+
3+
import (
4+
"context"
5+
"github.com/operator-framework/operator-registry/pkg/containertools"
6+
"github.com/sirupsen/logrus"
7+
8+
"github.com/operator-framework/operator-registry/pkg/image"
9+
)
10+
11+
// Registry enables manipulation of images via exec podman/docker commands.
12+
type Registry struct {
13+
log *logrus.Entry
14+
cmd containertools.CommandRunner
15+
}
16+
17+
// Adapt the cmd interface to the registry interface
18+
var _ image.Registry = &Registry{}
19+
20+
func NewRegistry(tool containertools.ContainerTool, logger *logrus.Entry) (registry *Registry, err error) {
21+
return &Registry{
22+
log: logger,
23+
cmd: containertools.NewCommandRunner(tool, logger),
24+
}, nil
25+
}
26+
27+
// Pull fetches and stores an image by reference.
28+
func (r *Registry) Pull(ctx context.Context, ref image.Reference) error {
29+
return r.cmd.Pull(ref.String())
30+
}
31+
32+
// Unpack writes the unpackaged content of an image to a directory.
33+
// If the referenced image does not exist in the registry, an error is returned.
34+
func (r *Registry) Unpack(ctx context.Context, ref image.Reference, dir string) error {
35+
return containertools.ImageLayerReader{
36+
Cmd: r.cmd,
37+
Logger: r.log,
38+
}.GetImageData(ref.String(), dir)
39+
}
40+
41+
// Destroy is no-op for exec tools
42+
func (r *Registry) Destroy() error {
43+
return nil
44+
}

‎pkg/image/registry.go

+3
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ type Registry interface {
1818
// If the referenced image does not exist in the registry, an error is returned.
1919
Unpack(ctx context.Context, ref Reference, dir string) error
2020

21+
// Destroy cleans up any on-disk resources used to track images
22+
Destroy() error
23+
2124
// Pack creates and stores an image based on the given reference and returns a reference to the new image.
2225
// If the referenced image does not exist in the registry, a new image is created from scratch.
2326
// If it exists, it's used as the base image.

‎pkg/image/registry_test.go

+38-10
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ package image_test
22

33
import (
44
"context"
5+
"crypto/x509"
56
"fmt"
7+
"io/ioutil"
68
"math/rand"
79
"os"
810
"testing"
@@ -16,27 +18,50 @@ import (
1618
libimage "github.com/operator-framework/operator-registry/pkg/lib/image"
1719
)
1820

21+
1922
// cleanupFunc is a function that cleans up after some test infra.
2023
type cleanupFunc func()
2124

2225
// newRegistryFunc is a function that creates and returns a new image.Registry to test its cleanupFunc.
23-
type newRegistryFunc func(t *testing.T) (image.Registry, cleanupFunc)
26+
type newRegistryFunc func(t *testing.T, cafile string) (image.Registry, cleanupFunc)
27+
28+
func poolForCertFile(t *testing.T, file string) *x509.CertPool {
29+
rootCAs := x509.NewCertPool()
30+
certs, err := ioutil.ReadFile(file)
31+
require.NoError(t, err)
32+
require.True(t, rootCAs.AppendCertsFromPEM(certs))
33+
return rootCAs
34+
}
2435

2536
func TestRegistries(t *testing.T) {
26-
registries := []newRegistryFunc{
27-
func(t *testing.T) (image.Registry, cleanupFunc) {
28-
// TODO: should this fail because the registry isn't TLS and we haven't specified skiptls?
29-
r, d, err := containerdregistry.NewRegistry(
37+
registries := map[string]newRegistryFunc{
38+
"containerd": func(t *testing.T, cafile string) (image.Registry, cleanupFunc) {
39+
r, err := containerdregistry.NewRegistry(
3040
containerdregistry.WithLog(logrus.New().WithField("test", t.Name())),
3141
containerdregistry.WithCacheDir(fmt.Sprintf("cache-%x", rand.Int())),
42+
containerdregistry.WithRootCAs(poolForCertFile(t, cafile)),
3243
)
3344
require.NoError(t, err)
3445
cleanup := func() {
35-
require.NoError(t, d())
46+
require.NoError(t, r.Destroy())
3647
}
3748

3849
return r, cleanup
3950
},
51+
// TODO: enable docker tests - currently blocked on a cross-platform way to configure either insecure registries
52+
// or CA certs
53+
//"docker": func(t *testing.T, cafile string) (image.Registry, cleanupFunc) {
54+
// r, err := execregistry.NewRegistry(containertools.DockerTool,
55+
// logrus.New().WithField("test", t.Name()),
56+
// cafile,
57+
// )
58+
// require.NoError(t, err)
59+
// cleanup := func() {
60+
// require.NoError(t, r.Destroy())
61+
// }
62+
//
63+
// return r, cleanup
64+
//},
4065
// TODO: Enable buildah tests
4166
// func(t *testing.T) image.Registry {
4267
// r, err := buildahregistry.NewRegistry(
@@ -49,8 +74,11 @@ func TestRegistries(t *testing.T) {
4974
// },
5075
}
5176

52-
for _, registry := range registries {
53-
testPullAndUnpack(t, registry)
77+
for name, registry := range registries {
78+
t.Run(name, func(t *testing.T) {
79+
testPullAndUnpack(t, registry)
80+
})
81+
5482
}
5583
}
5684

@@ -94,10 +122,10 @@ func testPullAndUnpack(t *testing.T, newRegistry newRegistryFunc) {
94122
ctx, close := context.WithCancel(context.Background())
95123
defer close()
96124

97-
host, err := libimage.RunDockerRegistry(ctx, tt.args.dockerRootDir)
125+
host, cafile, err := libimage.RunDockerRegistry(ctx, tt.args.dockerRootDir)
98126
require.NoError(t, err)
99127

100-
r, cleanup := newRegistry(t)
128+
r, cleanup := newRegistry(t, cafile)
101129
defer cleanup()
102130

103131
ref := image.SimpleReference(host + tt.args.img)

‎pkg/lib/bundle/exporter.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,14 @@ import (
1515
type BundleExporter struct {
1616
image string
1717
directory string
18-
containerTool string
18+
containerTool containertools.ContainerTool
1919
}
2020

2121
func NewSQLExporterForBundle(image, directory, containerTool string) *BundleExporter {
2222
return &BundleExporter{
2323
image: image,
2424
directory: directory,
25-
containerTool: containerTool,
25+
containerTool: containertools.NewContainerTool(containerTool, containertools.NoneTool),
2626
}
2727
}
2828

‎pkg/lib/bundle/interfaces.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ type BundleImageValidator interface {
2323
// NewImageValidator is a constructor that returns an ImageValidator
2424
func NewImageValidator(containerTool string, logger *logrus.Entry) BundleImageValidator {
2525
return imageValidator{
26-
imageReader: containertools.NewImageReader(containerTool, logger),
26+
imageReader: containertools.NewImageReader(containertools.NewContainerTool(containerTool, containertools.NoneTool), logger),
2727
logger: logger,
2828
}
2929
}

‎pkg/lib/image/registry.go

+167-5
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,23 @@
11
package image
22

33
import (
4+
"bytes"
45
"context"
6+
"crypto/ecdsa"
7+
"crypto/elliptic"
8+
"crypto/rand"
9+
"crypto/tls"
10+
"crypto/x509"
11+
"crypto/x509/pkix"
12+
"encoding/pem"
513
"fmt"
14+
"io"
15+
"io/ioutil"
16+
"k8s.io/apimachinery/pkg/util/wait"
17+
"math/big"
18+
"net"
19+
"net/http"
20+
"os"
621
"time"
722

823
"github.com/docker/distribution/configuration"
@@ -15,14 +30,45 @@ import (
1530
// RunDockerRegistry runs a docker registry on an available port and returns its host string if successful, otherwise it returns an error.
1631
// If the rootDir argument isn't empty, the registry is configured to use this as the root directory for persisting image data to the filesystem.
1732
// If the rootDir argument is empty, the registry is configured to keep image data in memory.
18-
func RunDockerRegistry(ctx context.Context, rootDir string) (string, error) {
33+
func RunDockerRegistry(ctx context.Context, rootDir string) (string, string, error) {
1934
dockerPort, err := freeport.GetFreePort()
2035
if err != nil {
21-
return "", err
36+
return "", "", err
37+
}
38+
host := fmt.Sprintf("localhost:%d", dockerPort)
39+
certPool := x509.NewCertPool()
40+
41+
cafile, err := ioutil.TempFile("", "ca")
42+
if err != nil {
43+
return "", "", err
44+
}
45+
certfile, err := ioutil.TempFile("", "cert")
46+
if err != nil {
47+
return "", "", err
48+
}
49+
keyfile, err := ioutil.TempFile("", "key")
50+
if err != nil {
51+
return "", "", err
52+
}
53+
if err := GenerateCerts(cafile, certfile, keyfile, certPool); err != nil {
54+
return "", "", err
55+
}
56+
if err := cafile.Close(); err != nil {
57+
return "", "", err
58+
}
59+
if err := certfile.Close(); err != nil {
60+
return "", "", err
61+
}
62+
if err := keyfile.Close(); err != nil {
63+
return "", "", err
2264
}
2365

2466
config := &configuration.Configuration{}
25-
config.HTTP.Addr = fmt.Sprintf(":%d", dockerPort)
67+
config.HTTP.Addr = host
68+
config.HTTP.TLS.Certificate = certfile.Name()
69+
config.HTTP.TLS.Key = keyfile.Name()
70+
config.Log.Level = "debug"
71+
2672
if rootDir != "" {
2773
config.Storage = map[string]configuration.Parameters{"filesystem": map[string]interface{}{
2874
"rootdirectory": rootDir,
@@ -34,15 +80,131 @@ func RunDockerRegistry(ctx context.Context, rootDir string) (string, error) {
3480

3581
dockerRegistry, err := registry.NewRegistry(ctx, config)
3682
if err != nil {
37-
return "", err
83+
return "", "", err
3884
}
3985

4086
go func() {
87+
defer func() {
88+
os.Remove(cafile.Name())
89+
os.Remove(certfile.Name())
90+
os.Remove(keyfile.Name())
91+
}()
4192
if err := dockerRegistry.ListenAndServe(); err != nil {
4293
panic(fmt.Errorf("docker registry stopped listening: %v", err))
4394
}
4495
}()
4596

97+
err = wait.Poll(100*time.Millisecond, 10*time.Second, func() (done bool, err error) {
98+
tr := &http.Transport{TLSClientConfig: &tls.Config{
99+
InsecureSkipVerify: false,
100+
RootCAs: certPool,
101+
}}
102+
client := &http.Client{Transport: tr}
103+
r, err := client.Get("https://"+host+"/v2/")
104+
if err != nil {
105+
return false, nil
106+
}
107+
if r.StatusCode == http.StatusOK {
108+
return true, nil
109+
}
110+
return false, nil
111+
})
112+
if err != nil {
113+
return "", "", err
114+
}
115+
46116
// Return the registry host string
47-
return fmt.Sprintf("localhost:%d", dockerPort), nil
117+
return host, cafile.Name(), nil
118+
}
119+
120+
func certToPem(der []byte) ([]byte, error) {
121+
out := &bytes.Buffer{}
122+
if err := pem.Encode(out, &pem.Block{Type: "CERTIFICATE", Bytes: der}); err != nil {
123+
return nil, err
124+
}
125+
return out.Bytes(), nil
126+
}
127+
128+
func keyToPem(key *ecdsa.PrivateKey) ([]byte, error) {
129+
b, err := x509.MarshalECPrivateKey(key)
130+
if err != nil {
131+
return nil, fmt.Errorf( "unable to marshal private key: %v", err)
132+
}
133+
out := &bytes.Buffer{}
134+
if err := pem.Encode(out, &pem.Block{Type: "EC PRIVATE KEY", Bytes: b}); err != nil {
135+
return nil, err
136+
}
137+
return out.Bytes(), nil
138+
}
139+
140+
func GenerateCerts(caWriter, certWriter, keyWriter io.Writer, pool *x509.CertPool ) error {
141+
priv, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader)
142+
if err != nil {
143+
return err
144+
}
145+
ca := x509.Certificate{
146+
SerialNumber: big.NewInt(1),
147+
Subject: pkix.Name{
148+
Organization: []string{"test ca"},
149+
},
150+
NotBefore: time.Now(),
151+
NotAfter: time.Now().Add(time.Hour),
152+
153+
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
154+
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
155+
BasicConstraintsValid: true,
156+
IsCA: true,
157+
}
158+
cert := x509.Certificate{
159+
SerialNumber: big.NewInt(2),
160+
Subject: pkix.Name{
161+
Organization: []string{"test cert"},
162+
},
163+
NotBefore: time.Now(),
164+
NotAfter: time.Now().Add(time.Hour),
165+
166+
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
167+
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
168+
BasicConstraintsValid: true,
169+
IPAddresses: []net.IP{
170+
net.ParseIP("127.0.0.1"),
171+
net.ParseIP("::1"),
172+
},
173+
DNSNames: []string{"localhost"},
174+
}
175+
176+
caBytes, err := x509.CreateCertificate(rand.Reader, &ca, &ca, &priv.PublicKey, priv)
177+
if err != nil {
178+
return err
179+
}
180+
181+
caFile, err := certToPem(caBytes)
182+
if err != nil {
183+
return err
184+
}
185+
if _, err := caWriter.Write(caFile); err != nil {
186+
return err
187+
}
188+
pool.AppendCertsFromPEM(caFile)
189+
190+
certBytes, err := x509.CreateCertificate(rand.Reader, &cert, &ca, &priv.PublicKey, priv)
191+
if err != nil {
192+
return err
193+
}
194+
certFile, err := certToPem(certBytes)
195+
if err != nil {
196+
return err
197+
}
198+
if _, err := certWriter.Write(certFile); err != nil {
199+
return err
200+
}
201+
202+
keyFile, err := keyToPem(priv)
203+
if err != nil {
204+
return err
205+
}
206+
if _, err := keyWriter.Write(keyFile); err != nil {
207+
return err
208+
}
209+
return nil
48210
}

‎pkg/lib/indexer/indexer.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ type ImageIndexer struct {
3737
ImageReader containertools.ImageReader
3838
RegistryAdder registry.RegistryAdder
3939
RegistryDeleter registry.RegistryDeleter
40-
ContainerTool string
40+
ContainerTool containertools.ContainerTool
4141
Logger *logrus.Entry
4242
}
4343

@@ -51,6 +51,7 @@ type AddToIndexRequest struct {
5151
Bundles []string
5252
Tag string
5353
Mode pregistry.Mode
54+
SkipTLS bool
5455
}
5556

5657
// AddToIndex is an aggregate API used to generate a registry index image with additional bundles
@@ -74,6 +75,7 @@ func (i ImageIndexer) AddToIndex(request AddToIndexRequest) error {
7475
InputDatabase: databaseFile,
7576
Permissive: request.Permissive,
7677
Mode: request.Mode,
78+
SkipTLS: request.SkipTLS,
7779
}
7880

7981
// Add the bundles to the registry

‎pkg/lib/indexer/interfaces.go

+6-7
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ package indexer
44
import (
55
"github.com/operator-framework/operator-registry/pkg/containertools"
66
"github.com/operator-framework/operator-registry/pkg/lib/registry"
7-
87
"github.com/sirupsen/logrus"
98
)
109

@@ -16,9 +15,9 @@ type IndexAdder interface {
1615
}
1716

1817
// NewIndexAdder is a constructor that returns an IndexAdder
19-
func NewIndexAdder(containerTool string, logger *logrus.Entry) IndexAdder {
18+
func NewIndexAdder(containerTool containertools.ContainerTool, logger *logrus.Entry) IndexAdder {
2019
return ImageIndexer{
21-
DockerfileGenerator: containertools.NewDockerfileGenerator(containerTool, logger),
20+
DockerfileGenerator: containertools.NewDockerfileGenerator(logger),
2221
CommandRunner: containertools.NewCommandRunner(containerTool, logger),
2322
LabelReader: containertools.NewLabelReader(containerTool, logger),
2423
RegistryAdder: registry.NewRegistryAdder(logger),
@@ -36,9 +35,9 @@ type IndexDeleter interface {
3635
}
3736

3837
// NewIndexDeleter is a constructor that returns an IndexDeleter
39-
func NewIndexDeleter(containerTool string, logger *logrus.Entry) IndexDeleter {
38+
func NewIndexDeleter(containerTool containertools.ContainerTool, logger *logrus.Entry) IndexDeleter {
4039
return ImageIndexer{
41-
DockerfileGenerator: containertools.NewDockerfileGenerator(containerTool, logger),
40+
DockerfileGenerator: containertools.NewDockerfileGenerator(logger),
4241
CommandRunner: containertools.NewCommandRunner(containerTool, logger),
4342
LabelReader: containertools.NewLabelReader(containerTool, logger),
4443
RegistryDeleter: registry.NewRegistryDeleter(logger),
@@ -54,9 +53,9 @@ type IndexExporter interface {
5453
}
5554

5655
// NewIndexExporter is a constructor that returns an IndexExporter
57-
func NewIndexExporter(containerTool string, logger *logrus.Entry) IndexExporter {
56+
func NewIndexExporter(containerTool containertools.ContainerTool, logger *logrus.Entry) IndexExporter {
5857
return ImageIndexer{
59-
DockerfileGenerator: containertools.NewDockerfileGenerator(containerTool, logger),
58+
DockerfileGenerator: containertools.NewDockerfileGenerator(logger),
6059
CommandRunner: containertools.NewCommandRunner(containerTool, logger),
6160
LabelReader: containertools.NewLabelReader(containerTool, logger),
6261
ImageReader: containertools.NewImageReader(containerTool, logger),

‎pkg/lib/registry/registry.go

+34-9
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@ package registry
22

33
import (
44
"context"
5+
"crypto/x509"
56
"database/sql"
67
"fmt"
8+
"github.com/operator-framework/operator-registry/pkg/containertools"
9+
"github.com/operator-framework/operator-registry/pkg/image/execregistry"
710
"io/ioutil"
811
"os"
912

@@ -17,15 +20,17 @@ import (
1720
)
1821

1922
type RegistryUpdater struct {
20-
Logger *logrus.Entry
23+
Logger *logrus.Entry
2124
}
2225

2326
type AddToRegistryRequest struct {
2427
Permissive bool
2528
SkipTLS bool
29+
CaFile string
2630
InputDatabase string
2731
Bundles []string
2832
Mode registry.Mode
33+
ContainerTool containertools.ContainerTool
2934
}
3035

3136
func (r RegistryUpdater) AddToRegistry(request AddToRegistryRequest) error {
@@ -51,16 +56,36 @@ func (r RegistryUpdater) AddToRegistry(request AddToRegistryRequest) error {
5156
}
5257
dbQuerier := sqlite.NewSQLLiteQuerierFromDb(db)
5358

54-
// TODO: Dependency inject the registry if we want to swap it out.
55-
reg, destroy, err := containerdregistry.NewRegistry(
56-
containerdregistry.SkipTLS(request.SkipTLS),
57-
)
58-
if err != nil {
59-
return err
59+
// add custom ca certs to resolver
60+
rootCAs, err := x509.SystemCertPool()
61+
if err != nil || rootCAs == nil {
62+
rootCAs = x509.NewCertPool()
63+
}
64+
if len(request.CaFile) > 0 {
65+
certs, err := ioutil.ReadFile(request.CaFile)
66+
if err != nil {
67+
return fmt.Errorf("failed to append %q to RootCAs: %v", certs, err)
68+
}
69+
if ok := rootCAs.AppendCertsFromPEM(certs); !ok {
70+
return fmt.Errorf("unable to add certs specified in %s", request.CaFile)
71+
}
72+
}
73+
74+
var reg image.Registry
75+
var rerr error
76+
switch request.ContainerTool {
77+
case containertools.NoneTool:
78+
reg, rerr = containerdregistry.NewRegistry(containerdregistry.SkipTLS(request.SkipTLS), containerdregistry.WithRootCAs(rootCAs))
79+
case containertools.PodmanTool:
80+
case containertools.DockerTool:
81+
reg, rerr = execregistry.NewRegistry(request.ContainerTool, r.Logger)
82+
}
83+
if rerr != nil {
84+
return rerr
6085
}
6186
defer func() {
62-
if err := destroy(); err != nil {
63-
r.Logger.WithError(err).Warn("error destroying internal image registry")
87+
if err := reg.Destroy(); err != nil {
88+
r.Logger.WithError(err).Warn("error destroying local cache")
6489
}
6590
}()
6691

‎test/e2e/opm_test.go

+4-3
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package e2e_test
33
import (
44
"context"
55
"database/sql"
6+
"github.com/operator-framework/operator-registry/pkg/containertools"
67
"io/ioutil"
78
"os"
89
"os/exec"
@@ -86,7 +87,7 @@ func buildIndexWith(containerTool string) error {
8687
bundleImage + ":" + bundleTag2,
8788
}
8889
logger := logrus.WithFields(logrus.Fields{"bundles": bundles})
89-
indexAdder := indexer.NewIndexAdder(containerTool, logger)
90+
indexAdder := indexer.NewIndexAdder(containertools.NewContainerTool(containerTool, containertools.NoneTool), logger)
9091

9192
request := indexer.AddToIndexRequest{
9293
Generate: false,
@@ -106,7 +107,7 @@ func buildFromIndexWith(containerTool string) error {
106107
bundleImage + ":" + bundleTag3,
107108
}
108109
logger := logrus.WithFields(logrus.Fields{"bundles": bundles})
109-
indexAdder := indexer.NewIndexAdder(containerTool, logger)
110+
indexAdder := indexer.NewIndexAdder(containertools.NewContainerTool(containerTool, containertools.NoneTool), logger)
110111

111112
request := indexer.AddToIndexRequest{
112113
Generate: false,
@@ -141,7 +142,7 @@ func pushBundles(containerTool string) error {
141142

142143
func exportWith(containerTool string) error {
143144
logger := logrus.WithFields(logrus.Fields{"package": packageName})
144-
indexExporter := indexer.NewIndexExporter(containerTool, logger)
145+
indexExporter := indexer.NewIndexExporter(containertools.NewContainerTool(containerTool, containertools.NoneTool), logger)
145146

146147
request := indexer.ExportFromIndexRequest{
147148
Index: indexImage2,

0 commit comments

Comments
 (0)
Please sign in to comment.