github.com/joelanford/operator-sdk@v0.8.2/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 "bytes" 19 "encoding/json" 20 "fmt" 21 "strings" 22 23 "github.com/operator-framework/operator-sdk/pkg/k8sutil" 24 25 "github.com/ghodss/yaml" 26 olmapiv1alpha1 "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1" 27 olminstall "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/install" 28 log "github.com/sirupsen/logrus" 29 appsv1 "k8s.io/api/apps/v1" 30 corev1 "k8s.io/api/core/v1" 31 rbacv1 "k8s.io/api/rbac/v1" 32 apiextv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" 33 ) 34 35 // CSVUpdater is an interface for any data that can be in a CSV, which will be 36 // set to the corresponding field on Apply(). 37 type CSVUpdater interface { 38 // Apply applies a data update to a CSV argument. 39 Apply(*olmapiv1alpha1.ClusterServiceVersion) error 40 } 41 42 type updaterStore struct { 43 installStrategy *InstallStrategyUpdate 44 crds *CustomResourceDefinitionsUpdate 45 almExamples *ALMExamplesUpdate 46 } 47 48 func NewUpdaterStore() *updaterStore { 49 return &updaterStore{ 50 installStrategy: &InstallStrategyUpdate{ 51 &olminstall.StrategyDetailsDeployment{}, 52 }, 53 crds: &CustomResourceDefinitionsUpdate{ 54 &olmapiv1alpha1.CustomResourceDefinitions{}, 55 make(map[string]struct{}), 56 }, 57 almExamples: &ALMExamplesUpdate{}, 58 } 59 } 60 61 // Apply iteratively calls each stored CSVUpdater's Apply() method. 62 func (s *updaterStore) Apply(csv *olmapiv1alpha1.ClusterServiceVersion) error { 63 updaters := []CSVUpdater{s.installStrategy, s.crds, s.almExamples} 64 for _, updater := range updaters { 65 if err := updater.Apply(csv); err != nil { 66 return err 67 } 68 } 69 return nil 70 } 71 72 func getKindfromYAML(yamlData []byte) (string, error) { 73 var temp struct { 74 Kind string 75 } 76 if err := yaml.Unmarshal(yamlData, &temp); err != nil { 77 return "", err 78 } 79 return temp.Kind, nil 80 } 81 82 func (s *updaterStore) AddToUpdater(yamlSpec []byte, kind string) (found bool, err error) { 83 found = true 84 switch kind { 85 case "Role": 86 err = s.AddRole(yamlSpec) 87 case "ClusterRole": 88 err = s.AddClusterRole(yamlSpec) 89 case "Deployment": 90 err = s.AddDeploymentSpec(yamlSpec) 91 case "CustomResourceDefinition": 92 // All CRD's present will be 'owned'. 93 err = s.AddOwnedCRD(yamlSpec) 94 default: 95 found = false 96 } 97 return found, err 98 } 99 100 type InstallStrategyUpdate struct { 101 *olminstall.StrategyDetailsDeployment 102 } 103 104 func (store *updaterStore) AddRole(yamlDoc []byte) error { 105 role := &rbacv1.Role{} 106 if err := yaml.Unmarshal(yamlDoc, role); err != nil { 107 return err 108 } 109 perm := olminstall.StrategyDeploymentPermissions{ 110 ServiceAccountName: role.ObjectMeta.Name, 111 Rules: role.Rules, 112 } 113 store.installStrategy.Permissions = append(store.installStrategy.Permissions, perm) 114 115 return nil 116 } 117 118 func (store *updaterStore) AddClusterRole(yamlDoc []byte) error { 119 clusterRole := &rbacv1.ClusterRole{} 120 if err := yaml.Unmarshal(yamlDoc, clusterRole); err != nil { 121 return err 122 } 123 perm := olminstall.StrategyDeploymentPermissions{ 124 ServiceAccountName: clusterRole.ObjectMeta.Name, 125 Rules: clusterRole.Rules, 126 } 127 store.installStrategy.ClusterPermissions = append(store.installStrategy.ClusterPermissions, perm) 128 129 return nil 130 } 131 132 const olmTNMeta = "metadata.annotations['olm.targetNamespaces']" 133 134 func (store *updaterStore) AddDeploymentSpec(yamlDoc []byte) error { 135 dep := &appsv1.Deployment{} 136 if err := yaml.Unmarshal(yamlDoc, dep); err != nil { 137 return err 138 } 139 140 setWatchNamespacesEnv(dep) 141 // Make sure "olm.targetNamespaces" is referenced somewhere in dep, 142 // and emit a warning of not. 143 if !depHasOLMNamespaces(dep) { 144 log.Warnf(`No WATCH_NAMESPACE environment variable nor reference to "%s"`+ 145 ` detected in operator Deployment. For OLM compatibility, your operator`+ 146 ` MUST watch namespaces defined in "%s"`, olmTNMeta, olmTNMeta) 147 } 148 149 depSpec := olminstall.StrategyDeploymentSpec{ 150 Name: dep.ObjectMeta.Name, 151 Spec: dep.Spec, 152 } 153 store.installStrategy.DeploymentSpecs = append(store.installStrategy.DeploymentSpecs, depSpec) 154 155 return nil 156 } 157 158 // setWatchNamespacesEnv sets WATCH_NAMESPACE to olmTNString in dep if 159 // WATCH_NAMESPACE exists in a pod spec container's env list. 160 func setWatchNamespacesEnv(dep *appsv1.Deployment) { 161 overwriteContainerEnvVar(dep, k8sutil.WatchNamespaceEnvVar, newEnvVar(k8sutil.WatchNamespaceEnvVar, olmTNMeta)) 162 } 163 164 func overwriteContainerEnvVar(dep *appsv1.Deployment, name string, ev corev1.EnvVar) { 165 for _, c := range dep.Spec.Template.Spec.Containers { 166 for i := 0; i < len(c.Env); i++ { 167 if c.Env[i].Name == name { 168 c.Env[i] = ev 169 } 170 } 171 } 172 } 173 174 func newEnvVar(name, fieldPath string) corev1.EnvVar { 175 return corev1.EnvVar{ 176 Name: name, 177 ValueFrom: &corev1.EnvVarSource{ 178 FieldRef: &corev1.ObjectFieldSelector{ 179 FieldPath: fieldPath, 180 }, 181 }, 182 } 183 } 184 185 // OLM places the set of target namespaces for the operator in 186 // "metadata.annotations['olm.targetNamespaces']". This value should be 187 // referenced in either: 188 // - The Deployment's pod spec WATCH_NAMESPACE env variable. 189 // - Some other Deployment pod spec field. 190 func depHasOLMNamespaces(dep *appsv1.Deployment) bool { 191 b, err := dep.Spec.Template.Marshal() 192 if err != nil { 193 // Something is wrong with the deployment manifest, not with CLI inputs. 194 log.Fatalf("marshal Deployment spec: %v", err) 195 } 196 return bytes.Index(b, []byte(olmTNMeta)) != -1 197 } 198 199 func (u *InstallStrategyUpdate) Apply(csv *olmapiv1alpha1.ClusterServiceVersion) (err error) { 200 // Get install strategy from csv. Default to a deployment strategy if none found. 201 var strat olminstall.Strategy 202 if csv.Spec.InstallStrategy.StrategyName == "" { 203 csv.Spec.InstallStrategy.StrategyName = olminstall.InstallStrategyNameDeployment 204 strat = &olminstall.StrategyDetailsDeployment{} 205 } else { 206 var resolver *olminstall.StrategyResolver 207 strat, err = resolver.UnmarshalStrategy(csv.Spec.InstallStrategy) 208 if err != nil { 209 return err 210 } 211 } 212 213 switch s := strat.(type) { 214 case *olminstall.StrategyDetailsDeployment: 215 // Update permissions and deployments. 216 u.updatePermissions(s) 217 u.updateClusterPermissions(s) 218 u.updateDeploymentSpecs(s) 219 default: 220 return fmt.Errorf("install strategy (%v) of unknown type", strat) 221 } 222 223 // Re-serialize permissions into csv strategy. 224 updatedStrat, err := json.Marshal(strat) 225 if err != nil { 226 return err 227 } 228 csv.Spec.InstallStrategy.StrategySpecRaw = updatedStrat 229 230 return nil 231 } 232 233 func (u *InstallStrategyUpdate) updatePermissions(strat *olminstall.StrategyDetailsDeployment) { 234 if len(u.Permissions) != 0 { 235 strat.Permissions = u.Permissions 236 } 237 } 238 239 func (u *InstallStrategyUpdate) updateClusterPermissions(strat *olminstall.StrategyDetailsDeployment) { 240 if len(u.ClusterPermissions) != 0 { 241 strat.ClusterPermissions = u.ClusterPermissions 242 } 243 } 244 245 func (u *InstallStrategyUpdate) updateDeploymentSpecs(strat *olminstall.StrategyDetailsDeployment) { 246 if len(u.DeploymentSpecs) != 0 { 247 strat.DeploymentSpecs = u.DeploymentSpecs 248 } 249 } 250 251 type CustomResourceDefinitionsUpdate struct { 252 *olmapiv1alpha1.CustomResourceDefinitions 253 crKinds map[string]struct{} 254 } 255 256 func (store *updaterStore) AddOwnedCRD(yamlDoc []byte) error { 257 crd := &apiextv1beta1.CustomResourceDefinition{} 258 if err := yaml.Unmarshal(yamlDoc, crd); err != nil { 259 return err 260 } 261 store.crds.Owned = append(store.crds.Owned, olmapiv1alpha1.CRDDescription{ 262 Name: crd.ObjectMeta.Name, 263 Version: crd.Spec.Version, 264 Kind: crd.Spec.Names.Kind, 265 }) 266 store.crds.crKinds[crd.Spec.Names.Kind] = struct{}{} 267 return nil 268 } 269 270 // Apply updates csv's "owned" CRDDescriptions. "required" CRDDescriptions are 271 // left as-is, since they are user-defined values. 272 func (u *CustomResourceDefinitionsUpdate) Apply(csv *olmapiv1alpha1.ClusterServiceVersion) error { 273 set := make(map[string]olmapiv1alpha1.CRDDescription) 274 for _, csvDesc := range csv.Spec.CustomResourceDefinitions.Owned { 275 set[csvDesc.Name] = csvDesc 276 } 277 du := u.DeepCopy() 278 for i, uDesc := range u.Owned { 279 if csvDesc, ok := set[uDesc.Name]; ok { 280 csvDesc.Name = uDesc.Name 281 csvDesc.Version = uDesc.Version 282 csvDesc.Kind = uDesc.Kind 283 du.Owned[i] = csvDesc 284 } 285 } 286 csv.Spec.CustomResourceDefinitions.Owned = du.Owned 287 return nil 288 } 289 290 type ALMExamplesUpdate struct { 291 crs []string 292 } 293 294 func (store *updaterStore) AddCR(yamlDoc []byte) error { 295 if len(yamlDoc) == 0 { 296 return nil 297 } 298 crBytes, err := yaml.YAMLToJSON(yamlDoc) 299 if err != nil { 300 return err 301 } 302 store.almExamples.crs = append(store.almExamples.crs, string(crBytes)) 303 return nil 304 } 305 306 func (u *ALMExamplesUpdate) Apply(csv *olmapiv1alpha1.ClusterServiceVersion) error { 307 if len(u.crs) == 0 { 308 return nil 309 } 310 if csv.GetAnnotations() == nil { 311 csv.SetAnnotations(make(map[string]string)) 312 } 313 sb := &strings.Builder{} 314 sb.WriteString(`[`) 315 for i, example := range u.crs { 316 sb.WriteString(example) 317 if i < len(u.crs)-1 { 318 sb.WriteString(`,`) 319 } 320 } 321 sb.WriteString(`]`) 322 323 csv.GetAnnotations()["alm-examples"] = sb.String() 324 return nil 325 }