From db1a4fd0e134907e4dde223787f1d107bb1583be Mon Sep 17 00:00:00 2001 From: Abhinav Dahiya Date: Fri, 21 Sep 2018 10:58:36 -0700 Subject: [PATCH 1/2] *: CVO accepts release image to render its deployment object's image CVO uses the value --release-image for templating in default case But when desiredupdate payload is set, it uses that image as release image for templating. --- cmd/main.go | 5 +++++ cmd/start.go | 5 +++++ install/00_clusterversionoperator_04_deployment.yaml | 3 ++- lib/resourcebuilder/interface.go | 6 ++++++ pkg/cvo/cvo.go | 10 +++++++++- pkg/cvo/image.go | 2 +- pkg/cvo/updatepayload.go | 12 +++++++++++- 7 files changed, 39 insertions(+), 4 deletions(-) diff --git a/cmd/main.go b/cmd/main.go index 0c248cae3..e9203aa68 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -18,10 +18,15 @@ var ( Short: "Run Cluster Version Controller", Long: "", } + + rootOpts struct { + releaseImage string + } ) func init() { rootCmd.PersistentFlags().AddGoFlagSet(flag.CommandLine) + rootCmd.PersistentFlags().StringVar(&rootOpts.releaseImage, "release-image", "", "The Openshift release image url.") } func main() { diff --git a/cmd/start.go b/cmd/start.go index 8de9dbd98..06d1fb959 100644 --- a/cmd/start.go +++ b/cmd/start.go @@ -74,6 +74,10 @@ func runStartCmd(cmd *cobra.Command, args []string) { startOpts.nodeName = name } + if rootOpts.releaseImage == "" { + glog.Fatalf("missing --release-image flag, it is required") + } + cb, err := newClientBuilder(startOpts.kubeconfig) if err != nil { glog.Fatalf("error creating clients: %v", err) @@ -226,6 +230,7 @@ func startControllers(ctx *controllerContext) error { go cvo.New( startOpts.nodeName, componentNamespace, componentName, + rootOpts.releaseImage, ctx.InformerFactory.Clusterversion().V1().CVOConfigs(), ctx.InformerFactory.Operatorstatus().V1().OperatorStatuses(), ctx.APIExtInformerFactory.Apiextensions().V1beta1().CustomResourceDefinitions(), diff --git a/install/00_clusterversionoperator_04_deployment.yaml b/install/00_clusterversionoperator_04_deployment.yaml index 293929c65..4aa6ae01c 100644 --- a/install/00_clusterversionoperator_04_deployment.yaml +++ b/install/00_clusterversionoperator_04_deployment.yaml @@ -15,10 +15,11 @@ spec: spec: containers: - name: cluster-version-operator - image: docker.io/origin/origin-cluster-version-operator:v4.0.0 + image: {{.ReleaseImage}} imagePullPolicy: Always args: - "start" + - "--release-image={{.ReleaseImage}}" - "--enable-auto-update=false" - "--v=4" volumeMounts: diff --git a/lib/resourcebuilder/interface.go b/lib/resourcebuilder/interface.go index 1214056b9..d32bba19e 100644 --- a/lib/resourcebuilder/interface.go +++ b/lib/resourcebuilder/interface.go @@ -33,6 +33,12 @@ func (rm *ResourceMapper) AddToMap(irm *ResourceMapper) { } } +// Exist returns true when gvk is known. +func (rm *ResourceMapper) Exists(gvk schema.GroupVersionKind) bool { + _, ok := rm.gvkToNew[gvk] + return ok +} + // RegisterGVK adds GVK to NewInteraceFunc mapping. // It does not lock before adding the mapping. func (rm *ResourceMapper) RegisterGVK(gvk schema.GroupVersionKind, f NewInteraceFunc) { diff --git a/pkg/cvo/cvo.go b/pkg/cvo/cvo.go index 78eecdd58..b8e95b13e 100644 --- a/pkg/cvo/cvo.go +++ b/pkg/cvo/cvo.go @@ -52,6 +52,8 @@ type Operator struct { nodename string // namespace and name are used to find the CVOConfig, OperatorStatus. namespace, name string + // releaseImage allows templating CVO deployment manifest. + releaseImage string // restConfig is used to create resourcebuilder. restConfig *rest.Config @@ -79,6 +81,7 @@ type Operator struct { func New( nodename string, namespace, name string, + releaseImage string, cvoConfigInformer cvinformersv1.CVOConfigInformer, operatorStatusInformer osinformersv1.OperatorStatusInformer, crdInformer apiextinformersv1beta1.CustomResourceDefinitionInformer, @@ -96,6 +99,7 @@ func New( nodename: nodename, namespace: namespace, name: name, + releaseImage: releaseImage, restConfig: restConfig, client: client, kubeClient: kubeClient, @@ -224,7 +228,11 @@ func (optr *Operator) sync(key string) error { if err != nil { return err } - payload, err := loadUpdatePayload(payloadDir) + releaseImage := optr.releaseImage + if config.DesiredUpdate.Payload != "" { + releaseImage = config.DesiredUpdate.Payload + } + payload, err := loadUpdatePayload(payloadDir, releaseImage) if err != nil { return err } diff --git a/pkg/cvo/image.go b/pkg/cvo/image.go index 97bb22bb2..90fa27414 100644 --- a/pkg/cvo/image.go +++ b/pkg/cvo/image.go @@ -5,7 +5,7 @@ import "fmt" // ImageForShortName returns the image using the updatepayload embedded in // the Operator. func ImageForShortName(name string) (string, error) { - up, err := loadUpdatePayload(defaultUpdatePayloadDir) + up, err := loadUpdatePayload(defaultUpdatePayloadDir, "") if err != nil { return "", fmt.Errorf("error loading update payload from %q: %v", defaultUpdatePayloadDir, err) } diff --git a/pkg/cvo/updatepayload.go b/pkg/cvo/updatepayload.go index 1e2dfab7b..774d20d19 100644 --- a/pkg/cvo/updatepayload.go +++ b/pkg/cvo/updatepayload.go @@ -37,7 +37,7 @@ const ( imageReferencesFile = "image-references" ) -func loadUpdatePayload(dir string) (*updatePayload, error) { +func loadUpdatePayload(dir, releaseImage string) (*updatePayload, error) { glog.V(4).Info("Loading updatepayload from %q", dir) if err := validateUpdatePayload(dir); err != nil { return nil, err @@ -75,6 +75,16 @@ func loadUpdatePayload(dir string) (*updatePayload, error) { return nil, err } + mrc := manifestRenderConfig{ReleaseImage: releaseImage} + for idx := range manifests { + mname := fmt.Sprintf("(%s) %s/%s", manifests[idx].GVK.String(), manifests[idx].Object().GetNamespace(), manifests[idx].Object().GetName()) + rendered, err := renderManifest(mrc, manifests[idx].Raw) + if err != nil { + return nil, fmt.Errorf("error when rendering Manifest %s: %v", mname, err) + } + manifests[idx].Raw = rendered + } + return &updatePayload{ imageRef: imageRef, manifests: manifests, From 2223542206d4f9ebe0b0e6b0a159b340308f03af Mon Sep 17 00:00:00 2001 From: Abhinav Dahiya Date: Fri, 21 Sep 2018 11:01:59 -0700 Subject: [PATCH 2/2] cmd,pkg/cvo: add render command Render renderes all the manifests from UpdatePayload It skips certian specific Kinds that have might not be useful It also errors if a manifest exists in UpdatePayload that it cannot manage when reconciling. --- cmd/render.go | 43 +++++++++++++++++++++++++ pkg/cvo/render.go | 81 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 124 insertions(+) create mode 100644 cmd/render.go create mode 100644 pkg/cvo/render.go diff --git a/cmd/render.go b/cmd/render.go new file mode 100644 index 000000000..4f994be9a --- /dev/null +++ b/cmd/render.go @@ -0,0 +1,43 @@ +package main + +import ( + "flag" + + "github.com/golang/glog" + "github.com/spf13/cobra" + + "github.com/openshift/cluster-version-operator/pkg/cvo" +) + +var ( + renderCmd = &cobra.Command{ + Use: "render", + Short: "Renders the UpdatePayload to disk.", + Long: "", + Run: runRenderCmd, + } + + renderOpts struct { + outputDir string + } +) + +func init() { + rootCmd.AddCommand(renderCmd) + renderCmd.PersistentFlags().StringVar(&renderOpts.outputDir, "output-dir", "", "The output directory where the manifests will be rendered.") +} + +func runRenderCmd(cmd *cobra.Command, args []string) { + flag.Set("logtostderr", "true") + flag.Parse() + + if renderOpts.outputDir == "" { + glog.Fatalf("missing --output-dir flag, it is required") + } + if rootOpts.releaseImage == "" { + glog.Fatalf("missing --release-image flag, it is required") + } + if err := cvo.Render(renderOpts.outputDir, rootOpts.releaseImage); err != nil { + glog.Fatalf("Render command failed: %v", err) + } +} diff --git a/pkg/cvo/render.go b/pkg/cvo/render.go new file mode 100644 index 000000000..aa79c6393 --- /dev/null +++ b/pkg/cvo/render.go @@ -0,0 +1,81 @@ +package cvo + +import ( + "bytes" + "fmt" + "io/ioutil" + "path/filepath" + "text/template" + + "github.com/openshift/cluster-version-operator/lib/resourcebuilder" + + "github.com/golang/glog" + batchv1 "k8s.io/api/batch/v1" + batchv1beta1 "k8s.io/api/batch/v1beta1" + "k8s.io/apimachinery/pkg/runtime/schema" + utilerrors "k8s.io/apimachinery/pkg/util/errors" + + osv1 "github.com/openshift/cluster-version-operator/pkg/apis/operatorstatus.openshift.io/v1" +) + +// Render renders all the manifests from updatepayload to outputDir. +// Render skips Jobs, OperatorStatus. +func Render(outputDir, releaseImage string) error { + up, err := loadUpdatePayload(defaultUpdatePayloadDir, releaseImage) + if err != nil { + return fmt.Errorf("error loading update payload from %q: %v", defaultUpdatePayloadDir, err) + } + + var errs []error + skipGVKs := []schema.GroupVersionKind{ + batchv1.SchemeGroupVersion.WithKind("Job"), batchv1beta1.SchemeGroupVersion.WithKind("Job"), + osv1.SchemeGroupVersion.WithKind("OperatorStatus"), + } + for idx, manifest := range up.manifests { + mname := fmt.Sprintf("(%s) %s/%s", manifest.GVK, manifest.Object().GetNamespace(), manifest.Object().GetName()) + skip := false + for _, gvk := range skipGVKs { + if gvk == manifest.GVK { + skip = true + } + } + if skip { + glog.Infof("skipping Manifest %s", mname) + continue + } + + if !resourcebuilder.Mapper.Exists(manifest.GVK) { + return fmt.Errorf("error: Unknown GVK: %v; Operator will not be able to manage this Manifest %s", manifest.GVK, mname) + } + + path := filepath.Join(outputDir, fmt.Sprintf("%03d-manifest.json", idx)) + if err := ioutil.WriteFile(path, manifest.Raw, 0644); err != nil { + errs = append(errs, err) + } + } + + agg := utilerrors.NewAggregate(errs) + if agg != nil { + return fmt.Errorf("error rendering from UpdatePayload: %v", agg.Error()) + } + return nil +} + +type manifestRenderConfig struct { + ReleaseImage string +} + +// renderManifest Executes go text template from `manifestBytes` with `config`. +func renderManifest(config manifestRenderConfig, manifestBytes []byte) ([]byte, error) { + tmpl, err := template.New("manifest").Parse(string(manifestBytes)) + if err != nil { + return nil, fmt.Errorf("failed to parse manifest: %v", err) + } + + buf := new(bytes.Buffer) + if err := tmpl.Execute(buf, config); err != nil { + return nil, fmt.Errorf("failed to execute template: %v", err) + } + + return buf.Bytes(), nil +}