github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/cmd/reloader/template/main.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 main 21 22 import ( 23 "context" 24 "flag" 25 "fmt" 26 "os" 27 "path/filepath" 28 "strings" 29 30 "github.com/spf13/pflag" 31 corezap "go.uber.org/zap" 32 ctrl "sigs.k8s.io/controller-runtime" 33 "sigs.k8s.io/controller-runtime/pkg/log/zap" 34 35 appsv1alpha1 "github.com/1aal/kubeblocks/apis/apps/v1alpha1" 36 cfgcm "github.com/1aal/kubeblocks/pkg/configuration/config_manager" 37 cfgcore "github.com/1aal/kubeblocks/pkg/configuration/core" 38 cfgutil "github.com/1aal/kubeblocks/pkg/configuration/util" 39 "github.com/1aal/kubeblocks/pkg/controller/configuration" 40 "github.com/1aal/kubeblocks/pkg/gotemplate" 41 ) 42 43 const ( 44 builtinConfigMountPathObject = "ConfigMountPath" 45 ) 46 47 var configSpecMountPoint string 48 var lazyRenderedConfig string 49 50 // for rendered output 51 var outputDir string 52 var setParams []string 53 54 func installFlags() { 55 pflag.StringVar(&lazyRenderedConfig, "config", "", "specify the config spec to be rendered") 56 pflag.StringVar(&configSpecMountPoint, "config-volume", "", "config volume mount point") 57 pflag.StringVar(&outputDir, "output-dir", "", "secondary rendered output dir") 58 pflag.StringSliceVar(&setParams, "set", nil, "set parameter") 59 60 opts := zap.Options{ 61 Development: true, 62 Level: func() *corezap.AtomicLevel { 63 lvl := corezap.NewAtomicLevelAt(corezap.InfoLevel) 64 return &lvl 65 }(), 66 } 67 68 opts.BindFlags(flag.CommandLine) 69 pflag.CommandLine.AddGoFlagSet(flag.CommandLine) 70 pflag.Parse() 71 72 // NOTES: 73 // zap is "Blazing fast, structured, leveled logging in Go.", DON'T event try 74 // to refactor this logging lib to anything else. Check FAQ - https://github.com/uber-go/zap/blob/master/FAQ.md 75 ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) 76 } 77 78 func failed(err error, msg string) { 79 ctrl.Log.Error(err, msg) 80 os.Exit(-1) 81 } 82 83 func buildTplValues() *gotemplate.TplValues { 84 values := gotemplate.TplValues{} 85 for _, param := range setParams { 86 fields := strings.SplitN(param, "=", 2) 87 if len(fields) == 2 { 88 values[fields[0]] = fields[1] 89 } else if len(fields) == 1 { 90 values[fields[0]] = nil 91 } 92 } 93 values[builtinConfigMountPathObject] = configSpecMountPoint 94 return &values 95 } 96 97 func main() { 98 installFlags() 99 100 if configSpecMountPoint == "" { 101 failed(cfgcore.MakeError("config volume mount point is empty"), "") 102 } 103 104 if lazyRenderedConfig == "" { 105 failed(cfgcore.MakeError("config spec yaml is empty"), "") 106 } 107 108 if outputDir == "" { 109 failed(cfgcore.MakeError("output dir is empty"), "") 110 } 111 112 files, err := cfgcm.ScanConfigVolume(configSpecMountPoint) 113 if err != nil { 114 failed(err, "failed to scan config volume") 115 } 116 baseData, err := cfgutil.FromConfigFiles(files) 117 if err != nil { 118 failed(err, "failed to create data map") 119 } 120 121 configRenderMeta := cfgcm.ConfigLazyRenderedMeta{} 122 if err := cfgutil.FromYamlConfig(filepath.Join(lazyRenderedConfig, cfgcm.KBConfigSpecLazyRenderedYamlFile), &configRenderMeta); err != nil { 123 failed(err, "failed to parse config spec") 124 } 125 126 mergePolicy, err := configuration.NewTemplateMerger(configRenderMeta.LegacyRenderedConfigSpec.ConfigTemplateExtension, 127 context.TODO(), nil, nil, *configRenderMeta.ComponentConfigSpec, &appsv1alpha1.ConfigConstraintSpec{ 128 FormatterConfig: &configRenderMeta.FormatterConfig, 129 }) 130 if err != nil { 131 failed(err, "failed to create template merger") 132 } 133 134 engine := gotemplate.NewTplEngine(buildTplValues(), nil, fmt.Sprintf("secondary template %s", configRenderMeta.Name), nclient, context.TODO()) 135 136 renderedData, err := secondaryRender(engine, configRenderMeta.Templates) 137 if err != nil { 138 failed(err, "failed to render secondary templates") 139 } 140 141 mergedData, err := mergePolicy.Merge(baseData, renderedData) 142 if err != nil { 143 failed(err, "failed to merge data") 144 } 145 146 if err := dumpRenderedData(mergedData); err != nil { 147 failed(err, "failed to dump rendered data") 148 } 149 } 150 151 func dumpRenderedData(data map[string]string) error { 152 exist, err := cfgutil.CheckPathExists(outputDir) 153 if err != nil { 154 return err 155 } 156 if !exist { 157 if err := os.MkdirAll(outputDir, 0755); err != nil { 158 return err 159 } 160 } 161 for fileName, fileContext := range data { 162 if err := os.WriteFile(filepath.Join(outputDir, fileName), []byte(fileContext), 0644); err != nil { 163 return err 164 } 165 } 166 return nil 167 } 168 169 func secondaryRender(engine *gotemplate.TplEngine, templates []string) (map[string]string, error) { 170 renderedData := make(map[string]string, len(templates)) 171 for _, tpl := range templates { 172 tpl = strings.TrimSpace(tpl) 173 if tpl == "" { 174 continue 175 } 176 if !filepath.IsAbs(tpl) { 177 tpl = filepath.Join(lazyRenderedConfig, tpl) 178 } 179 180 b, err := os.ReadFile(tpl) 181 if err != nil { 182 return nil, err 183 } 184 rendered, err := engine.Render(string(b)) 185 if err != nil { 186 return nil, err 187 } 188 renderedData[filepath.Base(tpl)] = rendered 189 } 190 return renderedData, nil 191 }