github.com/mkimuram/operator-sdk@v0.7.1-0.20190410172100-52ad33a4bda0/internal/pkg/scaffold/olm-catalog/csv_updaters.go (about)

     1  // Copyright 2018 The Operator-SDK Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package catalog
    16  
    17  import (
    18  	"encoding/json"
    19  	"fmt"
    20  	"strings"
    21  
    22  	"github.com/ghodss/yaml"
    23  	olmapiv1alpha1 "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1"
    24  	olminstall "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/install"
    25  	appsv1 "k8s.io/api/apps/v1"
    26  	rbacv1 "k8s.io/api/rbac/v1"
    27  	apiextv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
    28  )
    29  
    30  // CSVUpdater is an interface for any data that can be in a CSV, which will be
    31  // set to the corresponding field on Apply().
    32  type CSVUpdater interface {
    33  	// Apply applies a data update to a CSV argument.
    34  	Apply(*olmapiv1alpha1.ClusterServiceVersion) error
    35  }
    36  
    37  type updaterStore struct {
    38  	installStrategy *InstallStrategyUpdate
    39  	crds            *CustomResourceDefinitionsUpdate
    40  	almExamples     *ALMExamplesUpdate
    41  }
    42  
    43  func NewUpdaterStore() *updaterStore {
    44  	return &updaterStore{
    45  		installStrategy: &InstallStrategyUpdate{
    46  			&olminstall.StrategyDetailsDeployment{},
    47  		},
    48  		crds: &CustomResourceDefinitionsUpdate{
    49  			&olmapiv1alpha1.CustomResourceDefinitions{},
    50  			make(map[string]struct{}),
    51  		},
    52  		almExamples: &ALMExamplesUpdate{},
    53  	}
    54  }
    55  
    56  // Apply iteratively calls each stored CSVUpdater's Apply() method.
    57  func (s *updaterStore) Apply(csv *olmapiv1alpha1.ClusterServiceVersion) error {
    58  	updaters := []CSVUpdater{s.installStrategy, s.crds, s.almExamples}
    59  	for _, updater := range updaters {
    60  		if err := updater.Apply(csv); err != nil {
    61  			return err
    62  		}
    63  	}
    64  	return nil
    65  }
    66  
    67  func getKindfromYAML(yamlData []byte) (string, error) {
    68  	var temp struct {
    69  		Kind string
    70  	}
    71  	if err := yaml.Unmarshal(yamlData, &temp); err != nil {
    72  		return "", err
    73  	}
    74  	return temp.Kind, nil
    75  }
    76  
    77  func (s *updaterStore) AddToUpdater(yamlSpec []byte, kind string) (found bool, err error) {
    78  	found = true
    79  	switch kind {
    80  	case "Role":
    81  		err = s.AddRole(yamlSpec)
    82  	case "ClusterRole":
    83  		err = s.AddClusterRole(yamlSpec)
    84  	case "Deployment":
    85  		err = s.AddDeploymentSpec(yamlSpec)
    86  	case "CustomResourceDefinition":
    87  		// All CRD's present will be 'owned'.
    88  		err = s.AddOwnedCRD(yamlSpec)
    89  	default:
    90  		found = false
    91  	}
    92  	return found, err
    93  }
    94  
    95  type InstallStrategyUpdate struct {
    96  	*olminstall.StrategyDetailsDeployment
    97  }
    98  
    99  func (store *updaterStore) AddRole(yamlDoc []byte) error {
   100  	role := &rbacv1.Role{}
   101  	if err := yaml.Unmarshal(yamlDoc, role); err != nil {
   102  		return err
   103  	}
   104  	perm := olminstall.StrategyDeploymentPermissions{
   105  		ServiceAccountName: role.ObjectMeta.Name,
   106  		Rules:              role.Rules,
   107  	}
   108  	store.installStrategy.Permissions = append(store.installStrategy.Permissions, perm)
   109  
   110  	return nil
   111  }
   112  
   113  func (store *updaterStore) AddClusterRole(yamlDoc []byte) error {
   114  	clusterRole := &rbacv1.ClusterRole{}
   115  	if err := yaml.Unmarshal(yamlDoc, clusterRole); err != nil {
   116  		return err
   117  	}
   118  	perm := olminstall.StrategyDeploymentPermissions{
   119  		ServiceAccountName: clusterRole.ObjectMeta.Name,
   120  		Rules:              clusterRole.Rules,
   121  	}
   122  	store.installStrategy.ClusterPermissions = append(store.installStrategy.ClusterPermissions, perm)
   123  
   124  	return nil
   125  }
   126  
   127  func (store *updaterStore) AddDeploymentSpec(yamlDoc []byte) error {
   128  	dep := &appsv1.Deployment{}
   129  	if err := yaml.Unmarshal(yamlDoc, dep); err != nil {
   130  		return err
   131  	}
   132  	depSpec := olminstall.StrategyDeploymentSpec{
   133  		Name: dep.ObjectMeta.Name,
   134  		Spec: dep.Spec,
   135  	}
   136  	store.installStrategy.DeploymentSpecs = append(store.installStrategy.DeploymentSpecs, depSpec)
   137  
   138  	return nil
   139  }
   140  
   141  func (u *InstallStrategyUpdate) Apply(csv *olmapiv1alpha1.ClusterServiceVersion) (err error) {
   142  	// Get install strategy from csv. Default to a deployment strategy if none found.
   143  	var strat olminstall.Strategy
   144  	if csv.Spec.InstallStrategy.StrategyName == "" {
   145  		csv.Spec.InstallStrategy.StrategyName = olminstall.InstallStrategyNameDeployment
   146  		strat = &olminstall.StrategyDetailsDeployment{}
   147  	} else {
   148  		var resolver *olminstall.StrategyResolver
   149  		strat, err = resolver.UnmarshalStrategy(csv.Spec.InstallStrategy)
   150  		if err != nil {
   151  			return err
   152  		}
   153  	}
   154  
   155  	switch s := strat.(type) {
   156  	case *olminstall.StrategyDetailsDeployment:
   157  		// Update permissions and deployments.
   158  		u.updatePermissions(s)
   159  		u.updateClusterPermissions(s)
   160  		u.updateDeploymentSpecs(s)
   161  	default:
   162  		return fmt.Errorf("install strategy (%v) of unknown type", strat)
   163  	}
   164  
   165  	// Re-serialize permissions into csv strategy.
   166  	updatedStrat, err := json.Marshal(strat)
   167  	if err != nil {
   168  		return err
   169  	}
   170  	csv.Spec.InstallStrategy.StrategySpecRaw = updatedStrat
   171  
   172  	return nil
   173  }
   174  
   175  func (u *InstallStrategyUpdate) updatePermissions(strat *olminstall.StrategyDetailsDeployment) {
   176  	if len(u.Permissions) != 0 {
   177  		strat.Permissions = u.Permissions
   178  	}
   179  }
   180  
   181  func (u *InstallStrategyUpdate) updateClusterPermissions(strat *olminstall.StrategyDetailsDeployment) {
   182  	if len(u.ClusterPermissions) != 0 {
   183  		strat.ClusterPermissions = u.ClusterPermissions
   184  	}
   185  }
   186  
   187  func (u *InstallStrategyUpdate) updateDeploymentSpecs(strat *olminstall.StrategyDetailsDeployment) {
   188  	if len(u.DeploymentSpecs) != 0 {
   189  		strat.DeploymentSpecs = u.DeploymentSpecs
   190  	}
   191  }
   192  
   193  type CustomResourceDefinitionsUpdate struct {
   194  	*olmapiv1alpha1.CustomResourceDefinitions
   195  	crKinds map[string]struct{}
   196  }
   197  
   198  func (store *updaterStore) AddOwnedCRD(yamlDoc []byte) error {
   199  	crd := &apiextv1beta1.CustomResourceDefinition{}
   200  	if err := yaml.Unmarshal(yamlDoc, crd); err != nil {
   201  		return err
   202  	}
   203  	store.crds.Owned = append(store.crds.Owned, olmapiv1alpha1.CRDDescription{
   204  		Name:    crd.ObjectMeta.Name,
   205  		Version: crd.Spec.Version,
   206  		Kind:    crd.Spec.Names.Kind,
   207  	})
   208  	store.crds.crKinds[crd.Spec.Names.Kind] = struct{}{}
   209  	return nil
   210  }
   211  
   212  // Apply updates csv's "owned" CRDDescriptions. "required" CRDDescriptions are
   213  // left as-is, since they are user-defined values.
   214  func (u *CustomResourceDefinitionsUpdate) Apply(csv *olmapiv1alpha1.ClusterServiceVersion) error {
   215  	set := make(map[string]olmapiv1alpha1.CRDDescription)
   216  	for _, csvDesc := range csv.Spec.CustomResourceDefinitions.Owned {
   217  		set[csvDesc.Name] = csvDesc
   218  	}
   219  	du := u.DeepCopy()
   220  	for i, uDesc := range u.Owned {
   221  		if csvDesc, ok := set[uDesc.Name]; ok {
   222  			csvDesc.Name = uDesc.Name
   223  			csvDesc.Version = uDesc.Version
   224  			csvDesc.Kind = uDesc.Kind
   225  			du.Owned[i] = csvDesc
   226  		}
   227  	}
   228  	csv.Spec.CustomResourceDefinitions.Owned = du.Owned
   229  	return nil
   230  }
   231  
   232  type ALMExamplesUpdate struct {
   233  	crs []string
   234  }
   235  
   236  func (store *updaterStore) AddCR(yamlDoc []byte) error {
   237  	if len(yamlDoc) == 0 {
   238  		return nil
   239  	}
   240  	crBytes, err := yaml.YAMLToJSON(yamlDoc)
   241  	if err != nil {
   242  		return err
   243  	}
   244  	store.almExamples.crs = append(store.almExamples.crs, string(crBytes))
   245  	return nil
   246  }
   247  
   248  func (u *ALMExamplesUpdate) Apply(csv *olmapiv1alpha1.ClusterServiceVersion) error {
   249  	if len(u.crs) == 0 {
   250  		return nil
   251  	}
   252  	if csv.GetAnnotations() == nil {
   253  		csv.SetAnnotations(make(map[string]string))
   254  	}
   255  	sb := &strings.Builder{}
   256  	sb.WriteString(`[`)
   257  	for i, example := range u.crs {
   258  		sb.WriteString(example)
   259  		if i < len(u.crs)-1 {
   260  			sb.WriteString(`,`)
   261  		}
   262  	}
   263  	sb.WriteString(`]`)
   264  
   265  	csv.GetAnnotations()["alm-examples"] = sb.String()
   266  	return nil
   267  }