github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/configuration/config_manager/builder.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 configmanager 21 22 import ( 23 "context" 24 "fmt" 25 "path/filepath" 26 27 "gopkg.in/yaml.v2" 28 corev1 "k8s.io/api/core/v1" 29 apierrors "k8s.io/apimachinery/pkg/api/errors" 30 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 31 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 32 apiruntime "k8s.io/apimachinery/pkg/runtime" 33 "sigs.k8s.io/controller-runtime/pkg/client" 34 "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" 35 36 appsv1alpha1 "github.com/1aal/kubeblocks/apis/apps/v1alpha1" 37 "github.com/1aal/kubeblocks/pkg/configuration/core" 38 cfgutil "github.com/1aal/kubeblocks/pkg/configuration/util" 39 "github.com/1aal/kubeblocks/pkg/constant" 40 viper "github.com/1aal/kubeblocks/pkg/viperx" 41 ) 42 43 const ( 44 configTemplateName = "reload.yaml" 45 scriptVolumePrefix = "cm-script-" 46 configVolumePrefix = "cm-config-" 47 48 scriptConfigField = "scripts" 49 formatterConfigField = "formatterConfig" 50 51 configManagerConfigVolumeName = "config-manager-config" 52 configManagerConfig = "config-manager.yaml" 53 configManagerConfigMountPoint = "/opt/config-manager" 54 configManagerCMPrefix = "sidecar-" 55 ) 56 57 const ( 58 KBScriptVolumePath = "/opt/kb-tools/reload" 59 KBConfigVolumePath = "/opt/kb-tools/config" 60 61 KBTOOLSScriptsPathEnv = "TOOLS_SCRIPTS_PATH" 62 KBConfigManagerPathEnv = "TOOLS_PATH" 63 ) 64 65 const KBConfigSpecLazyRenderedYamlFile = "lazy-rendered-config.yaml" 66 67 func BuildConfigManagerContainerParams(cli client.Client, ctx context.Context, managerParams *CfgManagerBuildParams, volumeDirs []corev1.VolumeMount) error { 68 var volume *corev1.VolumeMount 69 var buildParam *ConfigSpecMeta 70 71 allVolumeMounts := getWatchedVolume(volumeDirs, managerParams.ConfigSpecsBuildParams) 72 for i := range managerParams.ConfigSpecsBuildParams { 73 buildParam = &managerParams.ConfigSpecsBuildParams[i] 74 volume = FindVolumeMount(managerParams.Volumes, buildParam.ConfigSpec.VolumeName) 75 if volume == nil { 76 logger.Info(fmt.Sprintf("volume mount not be use : %s", buildParam.ConfigSpec.VolumeName)) 77 continue 78 } 79 buildParam.MountPoint = volume.MountPath 80 if err := buildConfigSpecHandleMeta(cli, ctx, buildParam, managerParams); err != nil { 81 return err 82 } 83 if err := buildLazyRenderedConfig(cli, ctx, buildParam, managerParams); err != nil { 84 return err 85 } 86 } 87 downwardAPIVolumes := buildDownwardAPIVolumes(managerParams) 88 allVolumeMounts = append(allVolumeMounts, downwardAPIVolumes...) 89 managerParams.Volumes = append(managerParams.Volumes, downwardAPIVolumes...) 90 return buildConfigManagerArgs(managerParams, allVolumeMounts, cli, ctx) 91 } 92 93 func getWatchedVolume(volumeDirs []corev1.VolumeMount, buildParams []ConfigSpecMeta) []corev1.VolumeMount { 94 enableWatchVolume := func(volume corev1.VolumeMount) bool { 95 for _, param := range buildParams { 96 if param.ConfigSpec.VolumeName != volume.Name { 97 continue 98 } 99 switch param.ReloadType { 100 case appsv1alpha1.TPLScriptType: 101 return core.IsWatchModuleForTplTrigger(param.ReloadOptions.TPLScriptTrigger) 102 case appsv1alpha1.ShellType: 103 return core.IsWatchModuleForShellTrigger(param.ReloadOptions.ShellTrigger) 104 default: 105 return true 106 } 107 } 108 return false 109 } 110 111 allVolumeMounts := make([]corev1.VolumeMount, 0, len(volumeDirs)) 112 for _, volume := range volumeDirs { 113 if enableWatchVolume(volume) { 114 allVolumeMounts = append(allVolumeMounts, volume) 115 } 116 } 117 return allVolumeMounts 118 } 119 120 // buildLazyRenderedConfig prepare secondary render config and volume 121 func buildLazyRenderedConfig(cli client.Client, ctx context.Context, param *ConfigSpecMeta, manager *CfgManagerBuildParams) error { 122 processYamlConfig := func(cm *corev1.ConfigMap) error { 123 renderMeta := ConfigLazyRenderedMeta{ 124 ComponentConfigSpec: ¶m.ConfigSpec, 125 Templates: cfgutil.ToSet(cm.Data).AsSlice(), 126 FormatterConfig: param.FormatterConfig, 127 } 128 b, err := cfgutil.ToYamlConfig(renderMeta) 129 if err != nil { 130 return err 131 } 132 cm.Data[KBConfigSpecLazyRenderedYamlFile] = string(b) 133 return nil 134 } 135 136 secondaryTemplate := param.ConfigSpec.LegacyRenderedConfigSpec 137 if secondaryTemplate == nil { 138 return nil 139 } 140 referenceCMKey := client.ObjectKey{ 141 Namespace: secondaryTemplate.Namespace, 142 Name: secondaryTemplate.TemplateRef, 143 } 144 configCMKey := client.ObjectKey{ 145 Namespace: manager.Cluster.GetNamespace(), 146 Name: fmt.Sprintf("%s%s-%s", configManagerCMPrefix, secondaryTemplate.TemplateRef, manager.Cluster.GetName()), 147 } 148 if err := checkOrCreateConfigMap(referenceCMKey, configCMKey, cli, ctx, manager.Cluster, processYamlConfig); err != nil { 149 return err 150 } 151 buildLazyRenderedConfigVolume(configCMKey.Name, manager, GetConfigMountPoint(param.ConfigSpec), GetConfigVolumeName(param.ConfigSpec), param.ConfigSpec) 152 return nil 153 } 154 155 func buildDownwardAPIVolumes(params *CfgManagerBuildParams) []corev1.VolumeMount { 156 for _, buildParam := range params.ConfigSpecsBuildParams { 157 for _, info := range buildParam.DownwardAPIOptions { 158 if FindVolumeMount(params.DownwardAPIVolumes, info.Name) == nil { 159 buildDownwardAPIVolume(params, info) 160 } 161 } 162 } 163 return params.DownwardAPIVolumes 164 } 165 166 func buildConfigManagerArgs(params *CfgManagerBuildParams, volumeDirs []corev1.VolumeMount, cli client.Client, ctx context.Context) error { 167 args := buildConfigManagerCommonArgs(volumeDirs) 168 args = append(args, "--operator-update-enable") 169 args = append(args, "--tcp", viper.GetString(constant.ConfigManagerGPRCPortEnv)) 170 171 if err := createOrUpdateConfigMap(fromConfigSpecMeta(params.ConfigSpecsBuildParams), params, cli, ctx); err != nil { 172 return err 173 } 174 args = append(args, "--config", filepath.Join(configManagerConfigMountPoint, configManagerConfig)) 175 params.Args = args 176 return nil 177 } 178 179 func buildCMForConfig(manager *CfgManagerBuildParams, cmKey client.ObjectKey, config string) *corev1.ConfigMap { 180 return &corev1.ConfigMap{ 181 ObjectMeta: metav1.ObjectMeta{ 182 Name: cmKey.Name, 183 Namespace: cmKey.Namespace, 184 Labels: map[string]string{ 185 constant.AppInstanceLabelKey: manager.Cluster.Name, 186 constant.AppManagedByLabelKey: constant.AppName, 187 constant.KBAppComponentLabelKey: manager.ComponentName, 188 }, 189 }, 190 Data: map[string]string{ 191 configManagerConfig: config, 192 }, 193 } 194 } 195 196 func createOrUpdateConfigMap(configInfo []ConfigSpecInfo, manager *CfgManagerBuildParams, cli client.Client, ctx context.Context) error { 197 createConfigCM := func(configKey client.ObjectKey, config string) error { 198 scheme, err := appsv1alpha1.SchemeBuilder.Build() 199 if err != nil { 200 return err 201 } 202 cmObj := buildCMForConfig(manager, configKey, config) 203 if err := controllerutil.SetOwnerReference(manager.Cluster, cmObj, scheme); err != nil { 204 return err 205 } 206 return cli.Create(ctx, cmObj) 207 } 208 updateConfigCM := func(cm *corev1.ConfigMap, newConfig string) error { 209 patch := client.MergeFrom(cm.DeepCopy()) 210 cm.Data[configManagerConfig] = newConfig 211 return cli.Patch(ctx, cm, patch) 212 } 213 214 config, err := cfgutil.ToYamlConfig(configInfo) 215 if err != nil { 216 return err 217 } 218 cmObj := &corev1.ConfigMap{} 219 cmKey := client.ObjectKey{ 220 Namespace: manager.Cluster.GetNamespace(), 221 Name: fmt.Sprintf("%s%s-%s-config-manager-config", configManagerCMPrefix, manager.Cluster.GetName(), manager.ComponentName), 222 } 223 err = cli.Get(ctx, cmKey, cmObj) 224 switch { 225 default: 226 return err 227 case err == nil: 228 err = updateConfigCM(cmObj, string(config)) 229 case apierrors.IsNotFound(err): 230 err = createConfigCM(cmKey, string(config)) 231 } 232 if err == nil { 233 buildReloadScriptVolume(cmKey.Name, manager, configManagerConfigMountPoint, configManagerConfigVolumeName) 234 } 235 return err 236 } 237 238 func fromConfigSpecMeta(metas []ConfigSpecMeta) []ConfigSpecInfo { 239 configSpecs := make([]ConfigSpecInfo, 0, len(metas)) 240 for _, meta := range metas { 241 configSpecs = append(configSpecs, meta.ConfigSpecInfo) 242 } 243 return configSpecs 244 } 245 246 func FindVolumeMount(volumeDirs []corev1.VolumeMount, volumeName string) *corev1.VolumeMount { 247 for i := range volumeDirs { 248 if volumeDirs[i].Name == volumeName { 249 return &volumeDirs[i] 250 } 251 } 252 return nil 253 } 254 255 func buildConfigSpecHandleMeta(cli client.Client, ctx context.Context, buildParam *ConfigSpecMeta, cmBuildParam *CfgManagerBuildParams) error { 256 for _, script := range buildParam.ScriptConfig { 257 if err := buildCfgManagerScripts(script, cmBuildParam, cli, ctx, buildParam.ConfigSpec); err != nil { 258 return err 259 } 260 } 261 if buildParam.ReloadType == appsv1alpha1.TPLScriptType { 262 return buildTPLScriptCM(buildParam, cmBuildParam, cli, ctx) 263 } 264 return nil 265 } 266 267 func buildTPLScriptCM(configSpecBuildMeta *ConfigSpecMeta, manager *CfgManagerBuildParams, cli client.Client, ctx context.Context) error { 268 var ( 269 options = configSpecBuildMeta.TPLScriptTrigger 270 formatConfig = configSpecBuildMeta.FormatterConfig 271 mountPoint = GetScriptsMountPoint(configSpecBuildMeta.ConfigSpec) 272 ) 273 274 reloadYamlFn := func(cm *corev1.ConfigMap) error { 275 newData, err := checkAndUpdateReloadYaml(cm.Data, configTemplateName, formatConfig) 276 if err != nil { 277 return err 278 } 279 cm.Data = newData 280 return nil 281 } 282 283 referenceCMKey := client.ObjectKey{ 284 Namespace: options.Namespace, 285 Name: options.ScriptConfigMapRef, 286 } 287 scriptCMKey := client.ObjectKey{ 288 Namespace: manager.Cluster.GetNamespace(), 289 Name: fmt.Sprintf("%s%s-%s", configManagerCMPrefix, options.ScriptConfigMapRef, manager.Cluster.GetName()), 290 } 291 if err := checkOrCreateConfigMap(referenceCMKey, scriptCMKey, cli, ctx, manager.Cluster, reloadYamlFn); err != nil { 292 return err 293 } 294 buildReloadScriptVolume(scriptCMKey.Name, manager, mountPoint, GetScriptsVolumeName(configSpecBuildMeta.ConfigSpec)) 295 configSpecBuildMeta.TPLConfig = filepath.Join(mountPoint, configTemplateName) 296 return nil 297 } 298 299 func buildDownwardAPIVolume(manager *CfgManagerBuildParams, fieldInfo appsv1alpha1.DownwardAPIOption) { 300 manager.DownwardAPIVolumes = append(manager.DownwardAPIVolumes, corev1.VolumeMount{ 301 Name: fieldInfo.Name, 302 MountPath: fieldInfo.MountPoint, 303 }) 304 manager.CMConfigVolumes = append(manager.CMConfigVolumes, corev1.Volume{ 305 Name: fieldInfo.Name, 306 VolumeSource: corev1.VolumeSource{ 307 DownwardAPI: &corev1.DownwardAPIVolumeSource{ 308 Items: fieldInfo.Items, 309 }}, 310 }) 311 } 312 313 func buildReloadScriptVolume(scriptCMName string, manager *CfgManagerBuildParams, mountPoint, volumeName string) { 314 var execMode int32 = 0755 315 manager.Volumes = append(manager.Volumes, corev1.VolumeMount{ 316 Name: volumeName, 317 MountPath: mountPoint, 318 }) 319 manager.ScriptVolume = append(manager.ScriptVolume, corev1.Volume{ 320 Name: volumeName, 321 VolumeSource: corev1.VolumeSource{ 322 ConfigMap: &corev1.ConfigMapVolumeSource{ 323 LocalObjectReference: corev1.LocalObjectReference{Name: scriptCMName}, 324 DefaultMode: &execMode, 325 }, 326 }, 327 }) 328 } 329 330 func buildLazyRenderedConfigVolume(cmName string, manager *CfgManagerBuildParams, mountPoint, volumeName string, configSpec appsv1alpha1.ComponentConfigSpec) { 331 n := len(manager.Volumes) 332 manager.Volumes = append(manager.Volumes, corev1.VolumeMount{ 333 Name: volumeName, 334 MountPath: mountPoint, 335 }) 336 manager.CMConfigVolumes = append(manager.CMConfigVolumes, corev1.Volume{ 337 Name: volumeName, 338 VolumeSource: corev1.VolumeSource{ 339 ConfigMap: &corev1.ConfigMapVolumeSource{ 340 LocalObjectReference: corev1.LocalObjectReference{Name: cmName}, 341 }, 342 }, 343 }) 344 manager.ConfigLazyRenderedVolumes[configSpec.VolumeName] = manager.Volumes[n] 345 } 346 347 func checkOrCreateConfigMap(referenceCM client.ObjectKey, scriptCMKey client.ObjectKey, cli client.Client, ctx context.Context, cluster *appsv1alpha1.Cluster, fn func(cm *corev1.ConfigMap) error) error { 348 var ( 349 err error 350 351 refCM = corev1.ConfigMap{} 352 sidecarCM = corev1.ConfigMap{} 353 ) 354 355 if err = cli.Get(ctx, referenceCM, &refCM); err != nil { 356 return err 357 } 358 if err = cli.Get(ctx, scriptCMKey, &sidecarCM); err != nil { 359 if !apierrors.IsNotFound(err) { 360 return err 361 } 362 363 scheme, _ := appsv1alpha1.SchemeBuilder.Build() 364 sidecarCM.Data = refCM.Data 365 if fn != nil { 366 if err := fn(&sidecarCM); err != nil { 367 return err 368 } 369 } 370 sidecarCM.SetLabels(refCM.GetLabels()) 371 sidecarCM.SetName(scriptCMKey.Name) 372 sidecarCM.SetNamespace(scriptCMKey.Namespace) 373 sidecarCM.SetLabels(refCM.Labels) 374 if err := controllerutil.SetOwnerReference(cluster, &sidecarCM, scheme); err != nil { 375 return err 376 } 377 if err := cli.Create(ctx, &sidecarCM); err != nil { 378 return err 379 } 380 } 381 return nil 382 } 383 384 func checkAndUpdateReloadYaml(data map[string]string, reloadConfig string, formatterConfig appsv1alpha1.FormatterConfig) (map[string]string, error) { 385 configObject := make(map[string]interface{}) 386 if content, ok := data[reloadConfig]; ok { 387 if err := yaml.Unmarshal([]byte(content), &configObject); err != nil { 388 return nil, err 389 } 390 } 391 if res, _, _ := unstructured.NestedFieldNoCopy(configObject, scriptConfigField); res == nil { 392 return nil, core.MakeError("reload.yaml required field: %s", scriptConfigField) 393 } 394 395 formatObject, err := apiruntime.DefaultUnstructuredConverter.ToUnstructured(&formatterConfig) 396 if err != nil { 397 return nil, err 398 } 399 if err := unstructured.SetNestedField(configObject, formatObject, formatterConfigField); err != nil { 400 return nil, err 401 } 402 b, err := yaml.Marshal(configObject) 403 if err != nil { 404 return nil, err 405 } 406 data[reloadConfig] = string(b) 407 return data, nil 408 } 409 410 func buildCfgManagerScripts(options appsv1alpha1.ScriptConfig, manager *CfgManagerBuildParams, cli client.Client, ctx context.Context, configSpec appsv1alpha1.ComponentConfigSpec) error { 411 mountPoint := filepath.Join(KBScriptVolumePath, configSpec.Name) 412 referenceCMKey := client.ObjectKey{ 413 Namespace: options.Namespace, 414 Name: options.ScriptConfigMapRef, 415 } 416 scriptsCMKey := client.ObjectKey{ 417 Namespace: manager.Cluster.GetNamespace(), 418 Name: fmt.Sprintf("%s%s-%s", configManagerCMPrefix, options.ScriptConfigMapRef, manager.Cluster.GetName()), 419 } 420 if err := checkOrCreateConfigMap(referenceCMKey, scriptsCMKey, cli, ctx, manager.Cluster, nil); err != nil { 421 return err 422 } 423 buildReloadScriptVolume(scriptsCMKey.Name, manager, mountPoint, GetScriptsVolumeName(configSpec)) 424 return nil 425 } 426 427 func GetConfigMountPoint(configSpec appsv1alpha1.ComponentConfigSpec) string { 428 return filepath.Join(KBConfigVolumePath, configSpec.Name) 429 } 430 431 func GetScriptsMountPoint(configSpec appsv1alpha1.ComponentConfigSpec) string { 432 return filepath.Join(KBScriptVolumePath, configSpec.Name) 433 } 434 435 func GetScriptsVolumeName(configSpec appsv1alpha1.ComponentConfigSpec) string { 436 return fmt.Sprintf("%s%s", scriptVolumePrefix, configSpec.Name) 437 } 438 439 func GetConfigVolumeName(configSpec appsv1alpha1.ComponentConfigSpec) string { 440 return fmt.Sprintf("%s%s", configVolumePrefix, configSpec.Name) 441 } 442 443 func buildConfigManagerCommonArgs(volumeDirs []corev1.VolumeMount) []string { 444 args := make([]string, 0) 445 // set grpc port 446 // args = append(args, "--tcp", viper.GetString(cfgcore.ConfigManagerGPRCPortEnv)) 447 args = append(args, "--log-level", viper.GetString(constant.ConfigManagerLogLevel)) 448 for _, volume := range volumeDirs { 449 args = append(args, "--volume-dir", volume.MountPath) 450 } 451 return args 452 }