github.com/sealerio/sealer@v0.11.1-0.20240507115618-f4f89c5853ae/pkg/application/appdriver.go (about) 1 // Copyright © 2022 Alibaba Group Holding Ltd. 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 application 16 17 import ( 18 "encoding/json" 19 "fmt" 20 "os" 21 "path/filepath" 22 "strings" 23 "syscall" 24 25 "github.com/sealerio/sealer/common" 26 v1 "github.com/sealerio/sealer/pkg/define/application/v1" 27 imagev1 "github.com/sealerio/sealer/pkg/define/image/v1" 28 "github.com/sealerio/sealer/pkg/infradriver" 29 "github.com/sealerio/sealer/pkg/rootfs" 30 v2 "github.com/sealerio/sealer/types/api/v2" 31 mapUtils "github.com/sealerio/sealer/utils/maps" 32 strUtils "github.com/sealerio/sealer/utils/strings" 33 "github.com/sirupsen/logrus" 34 ) 35 36 type applicationDriver struct { 37 app *v2.Application 38 39 // launchApps indicate that which applications will be launched 40 //if launchApps==nil, use default launch apps got from image extension to launch. 41 //if launchApps==[""], skip launch apps. 42 //if launchApps==["app1","app2"], launch app1,app2. 43 launchApps []string 44 45 // registeredApps is the app name list which registered in image extension at build stage. 46 registeredApps []string 47 48 // globalCmds is raw cmds without any application info 49 globalCmds []string 50 51 // globalEnv is global env registered in image extension 52 globalEnv map[string]string 53 54 // appLaunchCmdsMap contains the whole appLaunchCmds with app name as its key. 55 appLaunchCmdsMap map[string][]string 56 //appDeleteCmdsMap map[string][]string 57 58 //extension is ImageExtension 59 extension imagev1.ImageExtension 60 61 // appRootMap contains the whole app root with app name as its key. 62 appRootMap map[string]string 63 64 // appEnvMap contains the whole app env with app name as its key. 65 appEnvMap map[string]map[string]string 66 67 // appFileProcessorMap contains the whole FileProcessors with app name as its key. 68 appFileProcessorMap map[string][]FileProcessor 69 } 70 71 func (a *applicationDriver) GetAppLaunchCmds(appName string) []string { 72 return a.appLaunchCmdsMap[appName] 73 } 74 75 func (a *applicationDriver) GetAppNames() []string { 76 return a.launchApps 77 } 78 79 func (a *applicationDriver) GetAppRoot(appName string) string { 80 return a.appRootMap[appName] 81 } 82 83 func (a *applicationDriver) GetImageLaunchCmds() []string { 84 if a.globalCmds != nil { 85 return a.globalCmds 86 } 87 88 var cmds []string 89 90 for _, appName := range a.launchApps { 91 if appCmds, ok := a.appLaunchCmdsMap[appName]; ok { 92 cmds = append(cmds, appCmds...) 93 } 94 } 95 96 return cmds 97 } 98 99 func (a *applicationDriver) GetApplication() v2.Application { 100 return *a.app 101 } 102 103 func (a *applicationDriver) Launch(infraDriver infradriver.InfraDriver) error { 104 var ( 105 rootfsPath = infraDriver.GetClusterRootfsPath() 106 masters = infraDriver.GetHostIPListByRole(common.MASTER) 107 master0 = masters[0] 108 launchCmds = a.GetImageLaunchCmds() 109 ) 110 111 logrus.Infof("start to launch sealer applications: %s", a.GetAppNames()) 112 113 logrus.Debugf("will to launch applications with cmd: %s", launchCmds) 114 115 for _, cmdline := range launchCmds { 116 if cmdline == "" { 117 continue 118 } 119 120 if err := infraDriver.CmdAsync(master0, nil, fmt.Sprintf(common.CdAndExecCmd, rootfsPath, cmdline)); err != nil { 121 return err 122 } 123 } 124 125 return nil 126 } 127 128 // Save application install history 129 // TODO save to cluster, also need a save struct. 130 func (a *applicationDriver) Save(opts SaveOptions) error { 131 applicationFile := common.GetDefaultApplicationFile() 132 133 f, err := os.OpenFile(filepath.Clean(applicationFile), os.O_RDWR|os.O_APPEND|os.O_CREATE, 0600) 134 if err != nil { 135 return err 136 } 137 defer func() { 138 _ = f.Close() 139 }() 140 141 err = syscall.Flock(int(f.Fd()), syscall.LOCK_EX|syscall.LOCK_NB) 142 if err != nil { 143 return fmt.Errorf("cannot flock file %s - %s", applicationFile, err) 144 } 145 defer func() { 146 err := syscall.Flock(int(f.Fd()), syscall.LOCK_UN) 147 if err != nil { 148 logrus.Errorf("failed to unlock %s", applicationFile) 149 } 150 }() 151 152 // TODO do not need all ImageExtension 153 content, err := json.MarshalIndent(a.extension, "", " ") 154 if err != nil { 155 return fmt.Errorf("failed to marshal image extension: %v", err) 156 } 157 158 if _, err = f.Write(content); err != nil { 159 return err 160 } 161 162 return nil 163 } 164 165 func (a *applicationDriver) FileProcess(mountDir string) error { 166 for appName, processors := range a.appFileProcessorMap { 167 for _, fp := range processors { 168 if err := fp.Process(filepath.Join(mountDir, a.GetAppRoot(appName))); err != nil { 169 return fmt.Errorf("failed to process appFiles for %s: %v", appName, err) 170 } 171 } 172 } 173 return nil 174 } 175 176 // NewAppDriver :unify v2.Application and image extension into same Interface using to do Application ops. 177 func NewAppDriver(app *v2.Application, extension imagev1.ImageExtension) (Interface, error) { 178 appDriver := formatImageExtension(extension) 179 appDriver.app = app 180 // initialize globalCmds, overwrite default cmds from image extension. 181 if len(app.Spec.Cmds) > 0 { 182 appDriver.globalCmds = app.Spec.Cmds 183 } 184 185 // initialize appNames field, overwrite default app names from image extension. 186 if app.Spec.LaunchApps != nil { 187 // validate app.Spec.LaunchApps, if not in image extension,will return error 188 // NOTE: app name =="" is valid 189 for _, wanted := range app.Spec.LaunchApps { 190 if len(wanted) == 0 { 191 continue 192 } 193 if !strUtils.IsInSlice(wanted, appDriver.registeredApps) { 194 return nil, fmt.Errorf("app name `%s` is not found in %s", wanted, appDriver.registeredApps) 195 } 196 } 197 198 appDriver.launchApps = app.Spec.LaunchApps 199 } 200 201 // initialize Configs field 202 for _, config := range app.Spec.Configs { 203 if config.Name == "" { 204 return nil, fmt.Errorf("application configs name could not be nil") 205 } 206 207 name := config.Name 208 // make sure config in launchApps, if not will ignore this config. 209 if !strUtils.IsInSlice(name, appDriver.launchApps) { 210 continue 211 } 212 213 if config.Launch != nil { 214 launchCmds := parseLaunchCmds(config.Launch) 215 if launchCmds == nil { 216 return nil, fmt.Errorf("failed to get launchCmds from application configs") 217 } 218 appDriver.appLaunchCmdsMap[name] = launchCmds 219 } 220 221 // merge config env with extension env 222 if len(config.Env) > 0 { 223 appEnvFromExtension := appDriver.appEnvMap[name] 224 appEnvFromConfig := strUtils.ConvertStringSliceToMap(config.Env) 225 appDriver.appEnvMap[name] = mapUtils.Merge(appEnvFromConfig, appEnvFromExtension) 226 } 227 228 // initialize app FileProcessors 229 var fileProcessors []FileProcessor 230 if len(appDriver.appEnvMap[name]) > 0 { 231 fileProcessors = append(fileProcessors, envRender{envData: appDriver.appEnvMap[name]}) 232 } 233 234 for _, appFile := range config.Files { 235 fp, err := newFileProcessor(appFile) 236 if err != nil { 237 return nil, err 238 } 239 fileProcessors = append(fileProcessors, fp) 240 } 241 appDriver.appFileProcessorMap[name] = fileProcessors 242 243 // TODO initialize delete field 244 } 245 246 return appDriver, nil 247 } 248 249 func formatImageExtension(extension imagev1.ImageExtension) *applicationDriver { 250 appDriver := &applicationDriver{ 251 extension: extension, 252 globalCmds: extension.Launch.Cmds, 253 globalEnv: extension.Env, 254 launchApps: extension.Launch.AppNames, 255 registeredApps: []string{}, 256 appLaunchCmdsMap: map[string][]string{}, 257 appRootMap: map[string]string{}, 258 appEnvMap: map[string]map[string]string{}, 259 appFileProcessorMap: map[string][]FileProcessor{}, 260 } 261 262 for _, registeredApp := range extension.Applications { 263 appName := registeredApp.Name() 264 // initialize app name 265 appDriver.registeredApps = append(appDriver.registeredApps, appName) 266 267 // initialize app root path 268 appRoot := makeItDir(filepath.Join(rootfs.GlobalManager.App().Root(), appName)) 269 appDriver.appRootMap[appName] = appRoot 270 271 // initialize app LaunchCmds 272 app := registeredApp.(*v1.Application) 273 appDriver.appLaunchCmdsMap[appName] = []string{v1.GetAppLaunchCmd(appRoot, app)} 274 275 // initialize app env 276 appDriver.appEnvMap[appName] = mapUtils.Merge(app.AppEnv, extension.Env) 277 278 // initialize app FileProcessors 279 if len(appDriver.appEnvMap[appName]) > 0 { 280 appDriver.appFileProcessorMap[appName] = []FileProcessor{envRender{envData: appDriver.appEnvMap[appName]}} 281 } 282 } 283 284 return appDriver 285 } 286 287 // parseLaunchCmds parse shell, kube,helm type launch cmds 288 // kubectl apply -n sealer-io -f ns.yaml -f app.yaml 289 // helm install my-nginx bitnami/nginx 290 // key1=value1 key2=value2 && bash install1.sh && bash install2.sh 291 func parseLaunchCmds(launch *v2.Launch) []string { 292 if launch.Cmds != nil { 293 return launch.Cmds 294 } 295 // TODO add shell,helm,kube type cmds. 296 return nil 297 } 298 299 func makeItDir(str string) string { 300 if !strings.HasSuffix(str, "/") { 301 return str + "/" 302 } 303 return str 304 }