github.com/joelanford/operator-sdk@v0.8.2/internal/pkg/scaffold/role.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 scaffold 16 17 import ( 18 "encoding/json" 19 "errors" 20 "fmt" 21 "io/ioutil" 22 "path/filepath" 23 24 "github.com/operator-framework/operator-sdk/internal/pkg/scaffold/input" 25 "github.com/operator-framework/operator-sdk/internal/util/fileutil" 26 27 log "github.com/sirupsen/logrus" 28 yaml "gopkg.in/yaml.v2" 29 rbacv1 "k8s.io/api/rbac/v1" 30 cgoscheme "k8s.io/client-go/kubernetes/scheme" 31 ) 32 33 const RoleYamlFile = "role.yaml" 34 35 type Role struct { 36 input.Input 37 38 IsClusterScoped bool 39 SkipDefaultRules bool 40 SkipMetricsRules bool 41 CustomRules []rbacv1.PolicyRule 42 } 43 44 func (s *Role) GetInput() (input.Input, error) { 45 if s.Path == "" { 46 s.Path = filepath.Join(DeployDir, RoleYamlFile) 47 } 48 s.TemplateBody = roleTemplate 49 return s.Input, nil 50 } 51 52 func UpdateRoleForResource(r *Resource, absProjectPath string) error { 53 // append rbac rule to deploy/role.yaml 54 roleFilePath := filepath.Join(absProjectPath, DeployDir, RoleYamlFile) 55 roleYAML, err := ioutil.ReadFile(roleFilePath) 56 if err != nil { 57 return fmt.Errorf("failed to read role manifest %v: %v", roleFilePath, err) 58 } 59 obj, _, err := cgoscheme.Codecs.UniversalDeserializer().Decode(roleYAML, nil, nil) 60 if err != nil { 61 return fmt.Errorf("failed to decode role manifest %v: %v", roleFilePath, err) 62 } 63 switch role := obj.(type) { 64 // TODO: use rbac/v1. 65 case *rbacv1.Role: 66 pr := &rbacv1.PolicyRule{} 67 apiGroupFound := false 68 for i := range role.Rules { 69 if role.Rules[i].APIGroups[0] == r.FullGroup { 70 apiGroupFound = true 71 pr = &role.Rules[i] 72 break 73 } 74 } 75 // check if the resource already exists 76 for _, resource := range pr.Resources { 77 if resource == r.Resource { 78 log.Infof("RBAC rules in deploy/role.yaml already up to date for the resource (%v, %v)", r.APIVersion, r.Kind) 79 return nil 80 } 81 } 82 83 pr.Resources = append(pr.Resources, r.Resource) 84 // create a new apiGroup if not found. 85 if !apiGroupFound { 86 pr.APIGroups = []string{r.FullGroup} 87 // Using "*" to allow access to the resource and all its subresources e.g "memcacheds" and "memcacheds/finalizers" 88 // https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#ownerreferencespermissionenforcement 89 pr.Resources = []string{"*"} 90 pr.Verbs = []string{"*"} 91 role.Rules = append(role.Rules, *pr) 92 } 93 // update role.yaml 94 d, err := json.Marshal(&role) 95 if err != nil { 96 return fmt.Errorf("failed to marshal role(%+v): %v", role, err) 97 } 98 m := &map[string]interface{}{} 99 if err = yaml.Unmarshal(d, m); err != nil { 100 return fmt.Errorf("failed to unmarshal role(%+v): %v", role, err) 101 } 102 data, err := yaml.Marshal(m) 103 if err != nil { 104 return fmt.Errorf("failed to marshal role(%+v): %v", role, err) 105 } 106 if err := ioutil.WriteFile(roleFilePath, data, fileutil.DefaultFileMode); err != nil { 107 return fmt.Errorf("failed to update %v: %v", roleFilePath, err) 108 } 109 case *rbacv1.ClusterRole: 110 pr := &rbacv1.PolicyRule{} 111 apiGroupFound := false 112 for i := range role.Rules { 113 if role.Rules[i].APIGroups[0] == r.FullGroup { 114 apiGroupFound = true 115 pr = &role.Rules[i] 116 break 117 } 118 } 119 // check if the resource already exists 120 for _, resource := range pr.Resources { 121 if resource == r.Resource { 122 log.Infof("RBAC rules in deploy/role.yaml already up to date for the resource (%v, %v)", r.APIVersion, r.Kind) 123 return nil 124 } 125 } 126 127 pr.Resources = append(pr.Resources, r.Resource) 128 // create a new apiGroup if not found. 129 if !apiGroupFound { 130 pr.APIGroups = []string{r.FullGroup} 131 // Using "*" to allow access to the resource and all its subresources e.g "memcacheds" and "memcacheds/finalizers" 132 // https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#ownerreferencespermissionenforcement 133 pr.Resources = []string{"*"} 134 pr.Verbs = []string{"*"} 135 role.Rules = append(role.Rules, *pr) 136 } 137 // update role.yaml 138 d, err := json.Marshal(&role) 139 if err != nil { 140 return fmt.Errorf("failed to marshal role(%+v): %v", role, err) 141 } 142 m := &map[string]interface{}{} 143 err = yaml.Unmarshal(d, m) 144 data, err := yaml.Marshal(m) 145 if err != nil { 146 return fmt.Errorf("failed to marshal role(%+v): %v", role, err) 147 } 148 if err := ioutil.WriteFile(roleFilePath, data, fileutil.DefaultFileMode); err != nil { 149 return fmt.Errorf("failed to update %v: %v", roleFilePath, err) 150 } 151 default: 152 return errors.New("failed to parse role.yaml as a role") 153 } 154 // not reachable 155 return nil 156 } 157 158 const roleTemplate = `kind: {{if .IsClusterScoped}}Cluster{{end}}Role 159 apiVersion: rbac.authorization.k8s.io/v1 160 metadata: 161 name: {{.ProjectName}} 162 rules: 163 {{- if not .SkipDefaultRules }} 164 - apiGroups: 165 - "" 166 resources: 167 - pods 168 - services 169 - endpoints 170 - persistentvolumeclaims 171 - events 172 - configmaps 173 - secrets 174 verbs: 175 - "*" 176 - apiGroups: 177 - apps 178 resources: 179 - deployments 180 - daemonsets 181 - replicasets 182 - statefulsets 183 verbs: 184 - "*" 185 {{- end }} 186 {{- range .CustomRules }} 187 - verbs: 188 {{- range .Verbs }} 189 - "{{ . }}" 190 {{- end }} 191 {{- with .APIGroups }} 192 apiGroups: 193 {{- range . }} 194 - "{{ . }}" 195 {{- end }} 196 {{- end }} 197 {{- with .Resources }} 198 resources: 199 {{- range . }} 200 - "{{ . }}" 201 {{- end }} 202 {{- end }} 203 {{- with .ResourceNames }} 204 resourceNames: 205 {{- range . }} 206 - "{{ . }}" 207 {{- end }} 208 {{- end }} 209 {{- with .NonResourceURLs }} 210 nonResourceURLs: 211 {{- range . }} 212 - "{{ . }}" 213 {{- end }} 214 {{- end }} 215 {{- end }} 216 {{- if not .SkipMetricsRules }} 217 - apiGroups: 218 - monitoring.coreos.com 219 resources: 220 - servicemonitors 221 verbs: 222 - "get" 223 - "create" 224 - apiGroups: 225 - apps 226 resources: 227 - deployments/finalizers 228 resourceNames: 229 - {{ .ProjectName }} 230 verbs: 231 - "update" 232 {{- end }} 233 `