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  }