Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions cmd/opm/alpha/bundle/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@ func NewCmd() *cobra.Command {

runCmd.AddCommand(newBundleGenerateCmd())
runCmd.AddCommand(newBundleBuildCmd())
runCmd.AddCommand(extractCmd)
return runCmd
}
65 changes: 65 additions & 0 deletions cmd/opm/alpha/bundle/extract.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package bundle

import (
"fmt"

"github.com/sirupsen/logrus"
"github.com/spf13/cobra"

"github.com/operator-framework/operator-registry/pkg/configmap"
)

var extractCmd = &cobra.Command{
Use: "extract",
Short: "Extracts the data in a bundle directory via ConfigMap",
Long: "Extract takes as input a directory containing manifests and writes the per file contents to a ConfipMap",

PreRunE: func(cmd *cobra.Command, args []string) error {
if debug, _ := cmd.Flags().GetBool("debug"); debug {
logrus.SetLevel(logrus.DebugLevel)
}
return nil
},

RunE: runExtractCmd,
}

func init() {
extractCmd.Flags().Bool("debug", false, "enable debug logging")
extractCmd.Flags().StringP("kubeconfig", "k", "", "absolute path to kubeconfig file")
extractCmd.Flags().StringP("manifestsdir", "m", "/", "path to directory containing manifests")
extractCmd.Flags().StringP("configmapname", "c", "", "name of configmap to write bundle data")
extractCmd.Flags().StringP("namespace", "n", "openshift-operator-lifecycle-manager", "namespace to write configmap data")
extractCmd.Flags().Uint64P("datalimit", "l", 1<<20, "maximum limit in bytes for total bundle data")
extractCmd.MarkPersistentFlagRequired("configmapname")
}

func runExtractCmd(cmd *cobra.Command, args []string) error {
manifestsDir, err := cmd.Flags().GetString("manifestsdir")
if err != nil {
return err
}
kubeconfig, err := cmd.Flags().GetString("kubeconfig")
if err != nil {
return err
}
configmapName, err := cmd.Flags().GetString("configmapname")
if err != nil {
return err
}
namespace, err := cmd.Flags().GetString("namespace")
if err != nil {
return err
}
datalimit, err := cmd.Flags().GetUint64("datalimit")
if err != nil {
return err
}

loader := configmap.NewConfigMapLoaderForDirectory(configmapName, namespace, manifestsDir, kubeconfig)
if err := loader.Populate(datalimit); err != nil {
return fmt.Errorf("error loading manifests from directory: %s", err)
}

return nil
}
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ require (
github.com/grpc-ecosystem/grpc-health-probe v0.2.1-0.20181220223928-2bf0a5b182db
github.com/imdario/mergo v0.3.7 // indirect
github.com/mattn/go-sqlite3 v1.10.0
github.com/onsi/ginkgo v1.8.0
github.com/onsi/gomega v1.5.0
github.com/otiai10/copy v1.0.1
github.com/otiai10/curr v0.0.0-20190513014714-f5a3d24e5776 // indirect
github.com/sirupsen/logrus v1.4.2
Expand Down
26 changes: 2 additions & 24 deletions pkg/appregistry/appregistry.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,8 @@ import (

"github.com/sirupsen/logrus"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"

"github.com/operator-framework/operator-registry/pkg/client"
"github.com/operator-framework/operator-registry/pkg/registry"
)

Expand All @@ -19,7 +17,7 @@ import (
// downloadPath specifies the folder where the downloaded nested bundle(s) will
// be stored.
func NewLoader(kubeconfig string, dbName string, downloadPath string, logger *logrus.Entry) (*AppregistryLoader, error) {
kubeClient, err := NewKubeClient(kubeconfig, logger)
kubeClient, err := client.NewKubeClient(kubeconfig, logger.Logger)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -104,23 +102,3 @@ func (a *AppregistryLoader) Load(csvSources []string, csvPackages string) (regis

return store, utilerrors.NewAggregate(errs)
}

func NewKubeClient(kubeconfig string, logger *logrus.Entry) (clientset *kubernetes.Clientset, err error) {
var config *rest.Config

if kubeconfig != "" {
logger.Infof("Loading kube client config from path %q", kubeconfig)
config, err = clientcmd.BuildConfigFromFlags("", kubeconfig)
} else {
logger.Infof("Using in-cluster kube client config")
config, err = rest.InClusterConfig()
}

if err != nil {
err = fmt.Errorf("Cannot load config for REST client: %v", err)
return
}

clientset, err = kubernetes.NewForConfig(config)
return
}
35 changes: 35 additions & 0 deletions pkg/client/kubeclient.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package client

import (
"fmt"
"os"

"github.com/sirupsen/logrus"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
)

func NewKubeClient(kubeconfig string, logger *logrus.Logger) (clientset *kubernetes.Clientset, err error) {
var config *rest.Config

if overrideConfig := os.Getenv(clientcmd.RecommendedConfigPathEnvVar); overrideConfig != "" {
kubeconfig = overrideConfig
}

if kubeconfig != "" {
logger.Infof("Loading kube client config from path %q", kubeconfig)
config, err = clientcmd.BuildConfigFromFlags("", kubeconfig)
} else {
logger.Infof("Using in-cluster kube client config")
config, err = rest.InClusterConfig()
}

if err != nil {
err = fmt.Errorf("Cannot load config for REST client: %v", err)
return
}

clientset, err = kubernetes.NewForConfig(config)
return
}
222 changes: 222 additions & 0 deletions pkg/configmap/configmap_writer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
package configmap

import (
"fmt"
"io/ioutil"
"os"
"regexp"

"github.com/ghodss/yaml"
_ "github.com/mattn/go-sqlite3"
"github.com/sirupsen/logrus"
batchv1 "k8s.io/api/batch/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"

"github.com/operator-framework/operator-registry/pkg/client"
"github.com/operator-framework/operator-registry/pkg/lib/bundle"
)

// configmap keys can contain underscores, but configmap names can not
var unallowedKeyChars = regexp.MustCompile("[^-A-Za-z0-9_.]")

const (
EnvContainerImage = "CONTAINER_IMAGE"
ConfigMapImageAnnotationKey = "olm.sourceImage"
)

type AnnotationsFile struct {
Annotations struct {
Resources string `json:"operators.operatorframework.io.bundle.manifests.v1"`
MediaType string `json:"operators.operatorframework.io.bundle.mediatype.v1"`
Metadata string `json:"operators.operatorframework.io.bundle.metadata.v1"`
Package string `json:"operators.operatorframework.io.bundle.package.v1"`
Channels string `json:"operators.operatorframework.io.bundle.channels.v1"`
ChannelDefault string `json:"operators.operatorframework.io.bundle.channel.default.v1"`
} `json:"annotations"`
}

type ConfigMapWriter struct {
manifestsDir string
configMapName string
namespace string
clientset *kubernetes.Clientset
}

func NewConfigMapLoaderForDirectory(configMapName, namespace, manifestsDir, kubeconfig string) *ConfigMapWriter {
Copy link
Copy Markdown
Member

@ecordell ecordell Oct 29, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: NewConfigMapWriterForDirectory

nit: might want to use the functional option pattern for arguments we've been using elsewhere

clientset, err := client.NewKubeClient(kubeconfig, logrus.StandardLogger())
if err != nil {
logrus.Fatalf("cluster config failed: %v", err)
}

return &ConfigMapWriter{
manifestsDir: manifestsDir,
configMapName: configMapName,
namespace: namespace,
clientset: clientset,
}
}

func TranslateInvalidChars(input string) string {
validConfigMapKey := unallowedKeyChars.ReplaceAllString(input, "~")
return validConfigMapKey
}

func (c *ConfigMapWriter) Populate(maxDataSizeLimit uint64) error {
subDirs := []string{"manifests/", "metadata/"}

configMapPopulate, err := c.clientset.CoreV1().ConfigMaps(c.namespace).Get(c.configMapName, metav1.GetOptions{})
if err != nil {
return err
}
configMapPopulate.Data = map[string]string{}

var totalSize uint64
for _, dir := range subDirs {
completePath := c.manifestsDir + dir
files, err := ioutil.ReadDir(completePath)
if err != nil {
logrus.Errorf("read dir failed: %v", err)
return err
}

for _, file := range files {
log := logrus.WithField("file", completePath+file.Name())
log.Info("Reading file")
content, err := ioutil.ReadFile(completePath + file.Name())
if err != nil {
log.Errorf("read failed: %v", err)
return err
}
totalSize += uint64(len(content))
if totalSize > maxDataSizeLimit {
log.Errorf("File with size %v exceeded %v limit, aboring", len(content), maxDataSizeLimit)
return fmt.Errorf("file %v bigger than total allowed limit", file.Name())
}

validConfigMapKey := TranslateInvalidChars(file.Name())
if validConfigMapKey != file.Name() {
logrus.WithFields(logrus.Fields{
"file.Name": file.Name(),
"validConfigMapKey": validConfigMapKey,
}).Info("translated filename for configmap comptability")
}
if file.Name() == bundle.AnnotationsFile {
var annotationsFile AnnotationsFile
err := yaml.Unmarshal(content, &annotationsFile)
if err != nil {
return err
}
configMapPopulate.SetAnnotations(map[string]string{
bundle.ManifestsLabel: annotationsFile.Annotations.Resources,
bundle.MediatypeLabel: annotationsFile.Annotations.MediaType,
bundle.MetadataLabel: annotationsFile.Annotations.Metadata,
bundle.PackageLabel: annotationsFile.Annotations.Package,
bundle.ChannelsLabel: annotationsFile.Annotations.Channels,
bundle.ChannelDefaultLabel: annotationsFile.Annotations.ChannelDefault,
})
} else {
configMapPopulate.Data[validConfigMapKey] = string(content)
}
}
}

if sourceImage := os.Getenv(EnvContainerImage); sourceImage != "" {
annotations := configMapPopulate.GetAnnotations()
annotations[ConfigMapImageAnnotationKey] = sourceImage
}

_, err = c.clientset.CoreV1().ConfigMaps(c.namespace).Update(configMapPopulate)
if err != nil {
return err
}
return nil
}

// LaunchBundleImage will launch a bundle image and also create a configmap for
// storing the data that will be updated to contain the bundle image data. It is
// the responsibility of the caller to delete the job, the pod, and the configmap
// when done. This function is intended to be called from OLM, but is put here
// for locality.
func LaunchBundleImage(kubeclient kubernetes.Interface, bundleImage, initImage, namespace string) (*corev1.ConfigMap, *batchv1.Job, error) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: should be able to set the path to find opm in initImage?

// create configmap for bundle image data to write to (will be returned)
newConfigMap, err := kubeclient.CoreV1().ConfigMaps(namespace).Create(&corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
GenerateName: "bundle-image-",
},
})
if err != nil {
return nil, nil, err
}

launchJob := batchv1.Job{
ObjectMeta: metav1.ObjectMeta{
GenerateName: "deploy-bundle-image-",
},
Spec: batchv1.JobSpec{
//ttlSecondsAfterFinished: 0 // can use in the future to not have to clean up job
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Name: "bundle-image",
},
Spec: corev1.PodSpec{
RestartPolicy: corev1.RestartPolicyOnFailure,
Containers: []corev1.Container{
{
Name: "bundle-image",
Image: bundleImage,
ImagePullPolicy: "Never",
Command: []string{"/injected/opm", "alpha", "bundle", "extract", "-n", namespace, "-c", newConfigMap.GetName()},
Env: []corev1.EnvVar{
{
Name: EnvContainerImage,
Value: bundleImage,
},
},
VolumeMounts: []corev1.VolumeMount{
{
Name: "copydir",
MountPath: "/injected",
},
},
},
},
InitContainers: []corev1.Container{
{
Name: "copy-binary",
Image: initImage,
ImagePullPolicy: "Never",
Command: []string{"/bin/cp", "opm", "/copy-dest"},
VolumeMounts: []corev1.VolumeMount{
{
Name: "copydir",
MountPath: "/copy-dest",
},
},
},
},
Volumes: []corev1.Volume{
{
Name: "copydir",
VolumeSource: corev1.VolumeSource{
EmptyDir: &corev1.EmptyDirVolumeSource{},
},
},
},
},
},
},
}
launchedJob, err := kubeclient.BatchV1().Jobs(namespace).Create(&launchJob)
if err != nil {
err := kubeclient.CoreV1().ConfigMaps(namespace).Delete(newConfigMap.GetName(), &metav1.DeleteOptions{})
if err != nil {
// already in an error, so just report it
logrus.Errorf("failed to remove configmap: %v", err)
}
return nil, nil, err
}

return newConfigMap, launchedJob, nil
}
Loading