github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/controllers/apps/systemaccount_util.go (about) 1 /* 2 Copyright (C) 2022-2023 ApeCloud Co., Ltd 3 4 This file is part of KubeBlocks project 5 6 This program is free software: you can redistribute it and/or modify 7 it under the terms of the GNU Affero General Public License as published by 8 the Free Software Foundation, either version 3 of the License, or 9 (at your option) any later version. 10 11 This program is distributed in the hope that it will be useful 12 but WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 GNU Affero General Public License for more details. 15 16 You should have received a copy of the GNU Affero General Public License 17 along with this program. If not, see <http://www.gnu.org/licenses/>. 18 */ 19 20 package apps 21 22 import ( 23 "strconv" 24 "strings" 25 26 "github.com/sethvargo/go-password/password" 27 batchv1 "k8s.io/api/batch/v1" 28 corev1 "k8s.io/api/core/v1" 29 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 30 "k8s.io/klog/v2" 31 "sigs.k8s.io/controller-runtime/pkg/client" 32 33 appsv1alpha1 "github.com/1aal/kubeblocks/apis/apps/v1alpha1" 34 "github.com/1aal/kubeblocks/pkg/constant" 35 componetutil "github.com/1aal/kubeblocks/pkg/controller/component" 36 viper "github.com/1aal/kubeblocks/pkg/viperx" 37 ) 38 39 // customizedEngine helps render jobs. 40 type customizedEngine struct { 41 cluster *appsv1alpha1.Cluster 42 componentName string 43 image string 44 command []string 45 args []string 46 envVarList []corev1.EnvVar 47 } 48 49 func (e *customizedEngine) getImage() string { 50 return e.image 51 } 52 53 func (e *customizedEngine) getEnvs() []corev1.EnvVar { 54 return e.envVarList 55 } 56 57 // getPodCommand shows how to execute the sql statement. 58 // for instance, mysql -h - demo-cluster-replicasets-1 -e "create user username IDENTIFIED by 'passwd';" 59 func (e customizedEngine) getCommand() []string { 60 return e.command 61 } 62 63 // getPodCommand shows how to execute the sql statement. 64 // for instance, mysql -h - demo-cluster-replicasets-1 -e "create user username IDENTIFIED by 'passwd';" 65 func (e *customizedEngine) getArgs() []string { 66 return e.args 67 } 68 69 func newCustomizedEngine(execConfig *appsv1alpha1.CmdExecutorConfig, dbcluster *appsv1alpha1.Cluster, compName string) *customizedEngine { 70 return &customizedEngine{ 71 cluster: dbcluster, 72 componentName: compName, 73 image: execConfig.Image, 74 command: execConfig.Command, 75 args: execConfig.Args, 76 envVarList: execConfig.Env, 77 } 78 } 79 80 func replaceEnvsValues(clusterName string, sysAccounts *appsv1alpha1.SystemAccountSpec) { 81 namedValuesMap := componetutil.GetEnvReplacementMapForConnCredential(clusterName) 82 // replace systemAccounts.cmdExecutorConfig.env[].valueFrom.secretKeyRef.name variables 83 cmdConfig := sysAccounts.CmdExecutorConfig 84 if cmdConfig != nil { 85 cmdConfig.Env = componetutil.ReplaceSecretEnvVars(namedValuesMap, cmdConfig.Env) 86 } 87 88 accounts := sysAccounts.Accounts 89 for _, acc := range accounts { 90 if acc.ProvisionPolicy.Type == appsv1alpha1.ReferToExisting { 91 // replace systemAccounts.accounts[*].provisionPolicy.secretRef.name variables 92 secretRef := acc.ProvisionPolicy.SecretRef 93 name := componetutil.ReplaceNamedVars(namedValuesMap, secretRef.Name, 1, false) 94 if name != secretRef.Name { 95 secretRef.Name = name 96 } 97 } 98 } 99 } 100 101 // getLabelsForSecretsAndJobs constructs matching labels for secrets and jobs. 102 // This is consistent with that of secrets created during cluster initialization. 103 func getLabelsForSecretsAndJobs(key componentUniqueKey) client.MatchingLabels { 104 return client.MatchingLabels{ 105 constant.AppInstanceLabelKey: key.clusterName, 106 constant.KBAppComponentLabelKey: key.componentName, 107 constant.AppManagedByLabelKey: constant.AppName, 108 } 109 } 110 111 func renderJob(jobName string, engine *customizedEngine, key componentUniqueKey, statement []string, endpoint string) *batchv1.Job { 112 // inject one more system env variables 113 statementEnv := corev1.EnvVar{ 114 Name: kbAccountStmtEnvName, 115 Value: strings.Join(statement, " "), 116 } 117 endpointEnv := corev1.EnvVar{ 118 Name: kbAccountEndPointEnvName, 119 Value: endpoint, 120 } 121 // place statements and endpoints before user defined envs. 122 envs := make([]corev1.EnvVar, 0, 2+len(engine.getEnvs())) 123 envs = append(envs, statementEnv, endpointEnv) 124 if len(engine.getEnvs()) > 0 { 125 envs = append(envs, engine.getEnvs()...) 126 } 127 128 job := &batchv1.Job{ 129 ObjectMeta: metav1.ObjectMeta{ 130 Namespace: key.namespace, 131 Name: jobName, 132 }, 133 Spec: batchv1.JobSpec{ 134 Template: corev1.PodTemplateSpec{ 135 ObjectMeta: metav1.ObjectMeta{ 136 Namespace: key.namespace, 137 Name: jobName}, 138 Spec: corev1.PodSpec{ 139 RestartPolicy: corev1.RestartPolicyNever, 140 Containers: []corev1.Container{ 141 { 142 Name: jobName, 143 Image: engine.getImage(), 144 ImagePullPolicy: corev1.PullIfNotPresent, 145 Command: engine.getCommand(), 146 Args: engine.getArgs(), 147 Env: envs, 148 }, 149 }, 150 }, 151 }, 152 }, 153 } 154 155 return job 156 } 157 158 func renderSecretWithPwd(key componentUniqueKey, username, passwd string) *corev1.Secret { 159 secretData := map[string][]byte{ 160 constant.AccountNameForSecret: []byte(username), 161 constant.AccountPasswdForSecret: []byte(passwd), 162 } 163 164 ml := getLabelsForSecretsAndJobs(key) 165 ml[constant.ClusterAccountLabelKey] = username 166 return renderSecret(key, username, ml, secretData) 167 } 168 169 func renderSecretByCopy(key componentUniqueKey, username string, fromSecret *corev1.Secret) *corev1.Secret { 170 ml := getLabelsForSecretsAndJobs(key) 171 ml[constant.ClusterAccountLabelKey] = username 172 return renderSecret(key, username, ml, fromSecret.Data) 173 } 174 175 func renderSecret(key componentUniqueKey, username string, labels client.MatchingLabels, data map[string][]byte) *corev1.Secret { 176 // secret labels and secret finalizers should be consistent with that of Cluster secret created by Cluster Controller. 177 secret := &corev1.Secret{ 178 ObjectMeta: metav1.ObjectMeta{ 179 Namespace: key.namespace, 180 Name: strings.Join([]string{key.clusterName, key.componentName, username}, "-"), 181 Labels: labels, 182 Finalizers: []string{constant.DBClusterFinalizerName}, 183 }, 184 Data: data, 185 } 186 return secret 187 } 188 189 func retrieveEndpoints(scope appsv1alpha1.ProvisionScope, svcEP *corev1.Endpoints, headlessEP *corev1.Endpoints) []string { 190 // parse endpoints 191 endpoints := make([]string, 0) 192 if scope == appsv1alpha1.AnyPods { 193 for _, ss := range svcEP.Subsets { 194 for _, add := range ss.Addresses { 195 endpoints = append(endpoints, add.IP) 196 break 197 } 198 } 199 } else { 200 for _, ss := range headlessEP.Subsets { 201 for _, add := range ss.Addresses { 202 endpoints = append(endpoints, add.IP) 203 } 204 } 205 } 206 return endpoints 207 } 208 209 func getAcctFromSecretAndJobs(secrets *corev1.SecretList, jobs *batchv1.JobList) (detectedFacts appsv1alpha1.KBAccountType) { 210 detectedFacts = appsv1alpha1.KBAccountInvalid 211 // parse account name from secret's label 212 for _, secret := range secrets.Items { 213 if accountName, exists := secret.ObjectMeta.Labels[constant.ClusterAccountLabelKey]; exists { 214 updateFacts(appsv1alpha1.AccountName(accountName), &detectedFacts) 215 } 216 } 217 // parse account name from job's label 218 for _, job := range jobs.Items { 219 if accountName, exists := job.ObjectMeta.Labels[constant.ClusterAccountLabelKey]; exists { 220 updateFacts(appsv1alpha1.AccountName(accountName), &detectedFacts) 221 } 222 } 223 return 224 } 225 226 func updateFacts(accountName appsv1alpha1.AccountName, detectedFacts *appsv1alpha1.KBAccountType) { 227 switch accountName { 228 case appsv1alpha1.AdminAccount: 229 *detectedFacts |= appsv1alpha1.KBAccountAdmin 230 case appsv1alpha1.DataprotectionAccount: 231 *detectedFacts |= appsv1alpha1.KBAccountDataprotection 232 case appsv1alpha1.ProbeAccount: 233 *detectedFacts |= appsv1alpha1.KBAccountProbe 234 case appsv1alpha1.MonitorAccount: 235 *detectedFacts |= appsv1alpha1.KBAccountMonitor 236 case appsv1alpha1.ReplicatorAccount: 237 *detectedFacts |= appsv1alpha1.KBAccountReplicator 238 } 239 } 240 241 func getCreationStmtForAccount(key componentUniqueKey, passConfig appsv1alpha1.PasswordConfig, 242 accountConfig appsv1alpha1.SystemAccountConfig, strategy updateStrategy) ([]string, string) { 243 // generated password with mixedcases = true 244 passwd, _ := password.Generate((int)(passConfig.Length), (int)(passConfig.NumDigits), (int)(passConfig.NumSymbols), false, false) 245 // refine password to upper or lower cases w.r.t configuration 246 switch passConfig.LetterCase { 247 case appsv1alpha1.UpperCases: 248 passwd = strings.ToUpper(passwd) 249 case appsv1alpha1.LowerCases: 250 passwd = strings.ToLower(passwd) 251 } 252 253 userName := (string)(accountConfig.Name) 254 255 namedVars := getEnvReplacementMapForAccount(userName, passwd) 256 257 execStmts := make([]string, 0) 258 259 statements := accountConfig.ProvisionPolicy.Statements 260 261 if strategy == inPlaceUpdate && len(statements.UpdateStatement) == 0 { 262 // if update statement is empty, use reCreate strategy, which will drop and create the account. 263 strategy = reCreate 264 klog.Warningf("account %s in cluster %s exists, but its update statement is not set, will use %s strategy to update account.", userName, key.clusterName, strategy) 265 } 266 267 if strategy == inPlaceUpdate { 268 // use update statement 269 stmt := componetutil.ReplaceNamedVars(namedVars, statements.UpdateStatement, -1, true) 270 execStmts = append(execStmts, stmt) 271 } else { 272 // drop if exists + create if not exists 273 if len(statements.DeletionStatement) > 0 { 274 stmt := componetutil.ReplaceNamedVars(namedVars, statements.DeletionStatement, -1, true) 275 execStmts = append(execStmts, stmt) 276 } 277 stmt := componetutil.ReplaceNamedVars(namedVars, statements.CreationStatement, -1, true) 278 execStmts = append(execStmts, stmt) 279 } 280 // secret := renderSecretWithPwd(key, userName, passwd) 281 return execStmts, passwd 282 } 283 284 func getAllSysAccounts() []appsv1alpha1.AccountName { 285 return []appsv1alpha1.AccountName{ 286 appsv1alpha1.AdminAccount, 287 appsv1alpha1.DataprotectionAccount, 288 appsv1alpha1.ProbeAccount, 289 appsv1alpha1.MonitorAccount, 290 appsv1alpha1.ReplicatorAccount, 291 } 292 } 293 294 func getDefaultAccounts() appsv1alpha1.KBAccountType { 295 accountID := appsv1alpha1.KBAccountInvalid 296 for _, name := range getAllSysAccounts() { 297 accountID |= name.GetAccountID() 298 } 299 return accountID 300 } 301 302 func getDebugMode(annotatedDebug string) bool { 303 debugOn, _ := strconv.ParseBool(annotatedDebug) 304 return viper.GetBool(systemAccountsDebugMode) || debugOn 305 } 306 307 func calibrateJobMetaAndSpec(job *batchv1.Job, cluster *appsv1alpha1.Cluster, compKey componentUniqueKey, account appsv1alpha1.AccountName) error { 308 debugModeOn := getDebugMode(cluster.Annotations[debugClusterAnnotationKey]) 309 // add label 310 ml := getLabelsForSecretsAndJobs(compKey) 311 ml[constant.ClusterAccountLabelKey] = (string)(account) 312 job.ObjectMeta.Labels = ml 313 314 // if debug mode is on, jobs will retain after execution. 315 if debugModeOn { 316 job.Spec.TTLSecondsAfterFinished = nil 317 } else { 318 defaultTTLZero := (int32)(1) 319 job.Spec.TTLSecondsAfterFinished = &defaultTTLZero 320 } 321 322 // add toleration 323 clusterComp := cluster.Spec.GetComponentByName(compKey.componentName) 324 tolerations, err := componetutil.BuildTolerations(cluster, clusterComp) 325 if err != nil { 326 return err 327 } 328 job.Spec.Template.Spec.Tolerations = tolerations 329 330 return nil 331 } 332 333 // completeExecConfig overrides the image of execConfig if version is not nil. 334 func completeExecConfig(execConfig *appsv1alpha1.CmdExecutorConfig, version *appsv1alpha1.ClusterComponentVersion) { 335 if version == nil || version.SystemAccountSpec == nil || version.SystemAccountSpec.CmdExecutorConfig == nil { 336 return 337 } 338 sysAccountSpec := version.SystemAccountSpec 339 if len(sysAccountSpec.CmdExecutorConfig.Image) > 0 { 340 execConfig.Image = sysAccountSpec.CmdExecutorConfig.Image 341 } 342 343 // envs from sysAccountSpec will override the envs from execConfig 344 if sysAccountSpec.CmdExecutorConfig.Env == nil { 345 return 346 } 347 if len(sysAccountSpec.CmdExecutorConfig.Env) == 0 { 348 // clean up envs 349 execConfig.Env = nil 350 } else { 351 execConfig.Env = sysAccountSpec.CmdExecutorConfig.Env 352 } 353 }