diff --git a/Dockerfile b/Dockerfile index ddaecedfc7..d2a381dbe4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,14 +1,21 @@ # Note: This dockerfile does not build the binaries # required and is intended to be built only with the # 'make build' or 'make release' targets. +# Stage 1: +FROM gcr.io/distroless/static:debug-nonroot AS builder + +# Stage 2: FROM gcr.io/distroless/static:nonroot +# Grab the cp binary so we can cp the unpack +# binary to a shared volume in the bundle image +COPY --from=builder /busybox/cp /cp + WORKDIR / COPY manager manager +COPY unpack unpack EXPOSE 8080 USER 65532:65532 - -ENTRYPOINT ["/manager"] diff --git a/Makefile b/Makefile index 6cab1c9024..ab60649f58 100644 --- a/Makefile +++ b/Makefile @@ -209,7 +209,10 @@ export GO_BUILD_GCFLAGS ?= all=-trimpath=${PWD} export GO_BUILD_TAGS ?= upstream export GO_BUILD_FLAGS ?= -BUILDCMD = go build $(GO_BUILD_FLAGS) -tags '$(GO_BUILD_TAGS)' -ldflags '$(GO_BUILD_LDFLAGS)' -gcflags '$(GO_BUILD_GCFLAGS)' -asmflags '$(GO_BUILD_ASMFLAGS)' -o $(BUILDBIN)/manager ./cmd/manager +BINARIES=manager unpack + +$(BINARIES): + go build $(GO_BUILD_FLAGS) -tags '$(GO_BUILD_TAGS)' -ldflags '$(GO_BUILD_LDFLAGS)' -gcflags '$(GO_BUILD_GCFLAGS)' -asmflags '$(GO_BUILD_ASMFLAGS)' -o $(BUILDBIN)/$@ ./cmd/$@ .PHONY: build-deps build-deps: manifests generate fmt vet @@ -217,14 +220,13 @@ build-deps: manifests generate fmt vet .PHONY: build go-build-local build: build-deps go-build-local #HELP Build manager binary for current GOOS and GOARCH. Default target. go-build-local: BUILDBIN = bin -go-build-local: - $(BUILDCMD) +go-build-local: $(BINARIES) .PHONY: build-linux go-build-linux build-linux: build-deps go-build-linux #EXHELP Build manager binary for GOOS=linux and local GOARCH. go-build-linux: BUILDBIN = bin/linux -go-build-linux: - GOOS=linux $(BUILDCMD) +go-build-linux: GOOS=linux +go-build-linux: $(BINARIES) .PHONY: run run: docker-build kind-cluster kind-load kind-deploy #HELP Build the operator-controller then deploy it into a new kind cluster. diff --git a/cmd/manager/main.go b/cmd/manager/main.go index fc7bfac9d0..c8033885d5 100644 --- a/cmd/manager/main.go +++ b/cmd/manager/main.go @@ -40,7 +40,6 @@ import ( helmclient "github.com/operator-framework/helm-operator-plugins/pkg/client" "github.com/operator-framework/rukpak/pkg/source" "github.com/operator-framework/rukpak/pkg/storage" - "github.com/operator-framework/rukpak/pkg/util" "github.com/operator-framework/operator-controller/api/v1alpha1" "github.com/operator-framework/operator-controller/internal/catalogmetadata/cache" @@ -53,9 +52,23 @@ import ( ) var ( - setupLog = ctrl.Log.WithName("setup") + setupLog = ctrl.Log.WithName("setup") + defaultUnpackImage = "quay.io/operator-framework/operator-controller:latest" + defaultSystemNamespace = "operator-controller-system" ) +// podNamespace checks whether the controller is running in a Pod vs. +// being run locally by inspecting the namespace file that gets mounted +// automatically for Pods at runtime. If that file doesn't exist, then +// return defaultSystemNamespace. +func podNamespace() string { + namespace, err := os.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/namespace") + if err != nil { + return defaultSystemNamespace + } + return string(namespace) +} + func main() { var ( metricsAddr string @@ -75,7 +88,7 @@ func main() { "Enabling this will ensure there is only one active controller manager.") flag.StringVar(&cachePath, "cache-path", "/var/cache", "The local directory path used for filesystem based caching") flag.StringVar(&systemNamespace, "system-namespace", "", "Configures the namespace that gets used to deploy system resources.") - flag.StringVar(&unpackImage, "unpack-image", util.DefaultUnpackImage, "Configures the container image that gets used to unpack Bundle contents.") + flag.StringVar(&unpackImage, "unpack-image", defaultUnpackImage, "Configures the container image that gets used to unpack Bundle contents.") flag.StringVar(&provisionerStorageDirectory, "provisioner-storage-dir", storage.DefaultBundleCacheDir, "The directory that is used to store bundle contents.") opts := zap.Options{ Development: true, @@ -89,7 +102,7 @@ func main() { ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts), zap.StacktraceLevel(zapcore.DPanicLevel))) if systemNamespace == "" { - systemNamespace = util.PodNamespace() + systemNamespace = podNamespace() } dependentRequirement, err := k8slabels.NewRequirement(labels.OwnerKindKey, selection.In, []string{v1alpha1.ClusterExtensionKind}) diff --git a/cmd/unpack/main.go b/cmd/unpack/main.go new file mode 100644 index 0000000000..c2584bd2d9 --- /dev/null +++ b/cmd/unpack/main.go @@ -0,0 +1,121 @@ +package main + +import ( + "archive/tar" + "bytes" + "compress/gzip" + "encoding/json" + "fmt" + "io" + "io/fs" + "log" + "os" + "path/filepath" + + "github.com/spf13/cobra" + "k8s.io/apimachinery/pkg/util/sets" +) + +func main() { + var bundleDir string + var opConVersion bool + + skipRootPaths := sets.NewString( + "/dev", + "/etc", + "/proc", + "/product_name", + "/product_uuid", + "/sys", + "/bin", + ) + cmd := &cobra.Command{ + Use: "unpack", + Args: cobra.ExactArgs(0), + RunE: func(cmd *cobra.Command, _ []string) error { + if opConVersion { + // TODO + //fmt.Println(version.String()) + os.Exit(0) + } + var err error + bundleDir, err = filepath.Abs(bundleDir) + if err != nil { + log.Fatalf("get absolute path of bundle directory %q: %v", bundleDir, err) + } + + bundleFS := os.DirFS(bundleDir) + buf := &bytes.Buffer{} + gzw := gzip.NewWriter(buf) + tw := tar.NewWriter(gzw) + if err := fs.WalkDir(bundleFS, ".", func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + + if d.Type()&os.ModeSymlink != 0 { + return nil + } + if bundleDir == "/" { + // If bundleDir is the filesystem root, skip some known unrelated directories + fullPath := filepath.Join(bundleDir, path) + if skipRootPaths.Has(fullPath) { + return filepath.SkipDir + } + } + info, err := d.Info() + if err != nil { + return fmt.Errorf("get file info for %q: %v", path, err) + } + + h, err := tar.FileInfoHeader(info, "") + if err != nil { + return fmt.Errorf("build tar file info header for %q: %v", path, err) + } + h.Uid = 0 + h.Gid = 0 + h.Uname = "" + h.Gname = "" + h.Name = path + + if err := tw.WriteHeader(h); err != nil { + return fmt.Errorf("write tar header for %q: %v", path, err) + } + if d.IsDir() { + return nil + } + f, err := bundleFS.Open(path) + if err != nil { + return fmt.Errorf("open file %q: %v", path, err) + } + if _, err := io.Copy(tw, f); err != nil { + return fmt.Errorf("write tar data for %q: %v", path, err) + } + return nil + }); err != nil { + log.Fatalf("generate tar.gz for bundle dir %q: %v", bundleDir, err) + } + if err := tw.Close(); err != nil { + log.Fatal(err) + } + if err := gzw.Close(); err != nil { + log.Fatal(err) + } + + bundleMap := map[string]interface{}{ + "content": buf.Bytes(), + } + enc := json.NewEncoder(os.Stdout) + if err := enc.Encode(bundleMap); err != nil { + log.Fatalf("encode bundle map as JSON: %v", err) + } + return nil + }, + } + cmd.Flags().StringVar(&bundleDir, "bundle-dir", "", "directory in which the bundle can be found") + cmd.Flags().BoolVar(&opConVersion, "version", false, "displays operator-controller version information") + + if err := cmd.Execute(); err != nil { + log.Fatal(err) + } +} diff --git a/config/manager/kustomization.yaml b/config/manager/kustomization.yaml index 754433c318..f888631fad 100644 --- a/config/manager/kustomization.yaml +++ b/config/manager/kustomization.yaml @@ -6,3 +6,23 @@ images: - name: controller newName: quay.io/operator-framework/operator-controller newTag: devel +replacements: +- source: # replaces UNPACK_IMAGE in manager.yaml with image set by kustomize above + kind: Deployment + group: apps + version: v1 + name: controller-manager + namespace: system + fieldPath: spec.template.spec.containers.[name=manager].image + targets: + - select: + kind: Deployment + group: apps + version: v1 + name: controller-manager + namespace: system + fieldPaths: + - spec.template.spec.containers.[name=manager].args.0 + options: + delimiter: "=" + index: 1 diff --git a/config/manager/manager.yaml b/config/manager/manager.yaml index 04e53a2086..8eba1284b6 100644 --- a/config/manager/manager.yaml +++ b/config/manager/manager.yaml @@ -52,6 +52,8 @@ spec: - command: - /manager args: + # The unpack-image arg must remain at index 0 for the kustomize replacement to work + - "--unpack-image=UNPACK_IMAGE" - "--health-probe-bind-address=:8081" - "--metrics-bind-address=127.0.0.1:8080" - "--leader-elect" diff --git a/go.mod b/go.mod index 35f886efb8..8ae3ee0d41 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,8 @@ require ( github.com/operator-framework/deppy v0.3.0 github.com/operator-framework/helm-operator-plugins v0.2.1 github.com/operator-framework/operator-registry v1.40.0 - github.com/operator-framework/rukpak v0.20.1-0.20240503190249-f2fc69ef9ff1 + github.com/operator-framework/rukpak v0.20.1-0.20240506151208-b6c74d40c3e9 + github.com/spf13/cobra v1.8.0 github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.9.0 github.com/vmware-tanzu/carvel-kapp-controller v0.51.0 @@ -167,7 +168,6 @@ require ( github.com/sirupsen/logrus v1.9.3 // indirect github.com/skeema/knownhosts v1.2.2 // indirect github.com/spf13/cast v1.5.0 // indirect - github.com/spf13/cobra v1.8.0 // indirect github.com/stoewer/go-strcase v1.3.0 // indirect github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 // indirect github.com/ulikunitz/xz v0.5.11 // indirect diff --git a/go.sum b/go.sum index 6f085e80a5..da0982c613 100644 --- a/go.sum +++ b/go.sum @@ -408,8 +408,8 @@ github.com/operator-framework/operator-lib v0.12.0 h1:OzpMU5N7mvFgg/uje8FUUeD24A github.com/operator-framework/operator-lib v0.12.0/go.mod h1:ClpLUI7hctEF7F5DBe/kg041dq/4NLR7XC5tArY7bG4= github.com/operator-framework/operator-registry v1.40.0 h1:CaYNE4F/jzahpC7UCILItaIHmB5/oE0sS066nK+5Glw= github.com/operator-framework/operator-registry v1.40.0/go.mod h1:D2YxapkfRDgjqNTO9d3h3v0DeREbV+8utCLG52zrOy4= -github.com/operator-framework/rukpak v0.20.1-0.20240503190249-f2fc69ef9ff1 h1:dB9owrQy5d/yjHuPNLw1dkudfWYLldJQBbmZ6pq+EAg= -github.com/operator-framework/rukpak v0.20.1-0.20240503190249-f2fc69ef9ff1/go.mod h1:WAyS3DXZ19pLg/324PEoudWZmaRlYZ6i4j4NV3/T/mI= +github.com/operator-framework/rukpak v0.20.1-0.20240506151208-b6c74d40c3e9 h1:itmEvkRAglIyVIFg7bmL+3G+HAYIrdK4ALEx7Ww1Nkc= +github.com/operator-framework/rukpak v0.20.1-0.20240506151208-b6c74d40c3e9/go.mod h1:WAyS3DXZ19pLg/324PEoudWZmaRlYZ6i4j4NV3/T/mI= github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU= github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w= github.com/otiai10/mint v1.5.1 h1:XaPLeE+9vGbuyEHem1JNk3bYc7KKqyI/na0/mLd/Kks=