main.go (327 lines of code) (raw):

package main import ( "context" "encoding/gob" "flag" "fmt" "html/template" "net/http" "os" "os/signal" "strings" "syscall" "time" "github.com/gin-contrib/sessions" "github.com/gin-contrib/sessions/cookie" "github.com/gin-gonic/gin" "github.com/joho/godotenv" "github.com/kelseyhightower/envconfig" "github.com/leonelquinteros/gotext" "github.com/patrickmn/go-cache" "github.com/pkg/errors" "go.uber.org/zap" "go.uber.org/zap/zapcore" "golang.org/x/oauth2" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" ctrl "sigs.k8s.io/controller-runtime" "ddm-admin-console/app/cluster" "ddm-admin-console/app/dashboard" "ddm-admin-console/app/registry" oauth "ddm-admin-console/auth" "ddm-admin-console/config" codebaseController "ddm-admin-console/controller/codebase" mergeRequestController "ddm-admin-console/controller/merge_request" "ddm-admin-console/mocks" "ddm-admin-console/router" "ddm-admin-console/service/codebase" edpComponent "ddm-admin-console/service/edp_component" "ddm-admin-console/service/gerrit" "ddm-admin-console/service/gitserver" "ddm-admin-console/service/jenkins" "ddm-admin-console/service/k8s" "ddm-admin-console/service/keycloak" "ddm-admin-console/service/openshift" "ddm-admin-console/service/permissions" "ddm-admin-console/service/vault" ) var ( configPath string cachePath string ) func main() { flag.StringVar(&configPath, "c", "default.env", "config file path") flag.StringVar(&cachePath, "ch", "cache.db", "cache file path") flag.Parse() cnf, err := loadConfig(configPath) if err != nil { panic(err) } logger, err := getLogger(cnf.LogLevel, cnf.LogEncoding) if err != nil { panic(err) } buildInfo := config.BuildInfoGet() logger.Sugar().Infow("starting the console", "version", buildInfo.Version, "git-commit", buildInfo.GitCommit, "git-tag", buildInfo.GitTag, "build-date", buildInfo.BuildDate, "go-version", buildInfo.Go, "platform", buildInfo.Platform, ) router.ConsoleVersion = buildInfo.Version logger.Info("init gin router") gin.SetMode(cnf.GinMode) r := gin.New() r.SetFuncMap(template.FuncMap{"i18n": i18n, "majorVersion": majorVersion}) r.LoadHTMLGlob("templates/**/*") r.Static("/static", "./static") r.Static("/assets", "./frontend/dist/assets") store := cookie.NewStore([]byte(cnf.SessionSecret)) r.Use(sessions.Sessions("cookie-session", store)) logger.Info("init apps") if err := initApps(logger, cnf, r, buildInfo.Date()); err != nil { panic(fmt.Sprintf("%+v", err)) } logger.Info("init i18n") gotext.Configure("locale", "uk_UA", "default") logger.Info("run router on port", zap.String("port", cnf.HTTPPort)) if err := r.Run(fmt.Sprintf(":%s", cnf.HTTPPort)); err != nil { panic(err) } } func exitWait(sigs chan os.Signal, appCache *cache.Cache) { <-sigs if err := saveCache(appCache); err != nil { panic(err) } os.Exit(0) } func saveCache(appCache *cache.Cache) error { if err := appCache.SaveFile(cachePath); err != nil { return fmt.Errorf("unable to save cache to file") } return nil } func loadConfig(path string) (*config.Settings, error) { if err := godotenv.Load(path); err != nil { return nil, fmt.Errorf("unable to load config file, %w", err) } var cnf config.Settings if err := envconfig.Process("", &cnf); err != nil { return nil, fmt.Errorf("unable to parse env variables, %w", err) } return &cnf, nil } func getLogger(level, encoding string) (*zap.Logger, error) { levels := map[string]zapcore.Level{ "DEBUG": zap.DebugLevel, "INFO": zap.InfoLevel, "WARNING": zap.WarnLevel, "ERROR": zap.ErrorLevel, "DPANIC": zap.DPanicLevel, "PANIC": zap.PanicLevel, "FATAL": zap.FatalLevel, } logLevel, ok := levels[level] if !ok { logLevel = zap.InfoLevel } cfg := zap.Config{ Level: zap.NewAtomicLevelAt(logLevel), Development: false, Sampling: &zap.SamplingConfig{ Initial: 100, Thereafter: 100, }, Encoding: encoding, EncoderConfig: zap.NewProductionEncoderConfig(), OutputPaths: []string{"stderr"}, ErrorOutputPaths: []string{"stderr"}, } logger, err := cfg.Build() if err != nil { return nil, fmt.Errorf("unable to build logger, %w", err) } return logger, nil } func initServices(sch *runtime.Scheme, restConf *rest.Config, appConf *config.Settings, logger *zap.Logger) (*config.Services, error) { if appConf.Mock != "" { return mocks.InitServices(appConf), nil } var err error serviceItems := config.Services{} serviceItems.EDPComponent, err = edpComponent.Make(sch, restConf, appConf.Namespace) if err != nil { return nil, fmt.Errorf("unable to init edp component service, %w", err) } serviceItems.Codebase, err = codebase.Make(sch, restConf, appConf.Namespace) if err != nil { return nil, fmt.Errorf("unable to init codebase service, %w", err) } serviceItems.K8S, err = k8s.Make(restConf, appConf.Namespace) if err != nil { return nil, fmt.Errorf("unable to init k8s service, %w", err) } serviceItems.Jenkins, err = jenkins.Make(sch, restConf, serviceItems.K8S, jenkins.Config{Namespace: appConf.Namespace, APIUrl: appConf.JenkinsAPIURL, AdminSecretName: appConf.JenkinsAdminSecretName}) if err != nil { return nil, fmt.Errorf("unable to init jenkins service, %w", err) } serviceItems.OpenShift, err = openshift.Make(restConf, serviceItems.K8S) if err != nil { return nil, fmt.Errorf("unable to init open shift service, %w", err) } serviceItems.Gerrit, err = gerrit.Make(sch, restConf, gerrit.Config{Namespace: appConf.Namespace, GerritAPIUrlTemplate: appConf.GerritAPIUrlTemplate, RootGerritName: appConf.RootGerritName}) if err != nil { return nil, fmt.Errorf("unable to create gerrit service, %w", err) } serviceItems.GitServer, err = gitserver.New(sch, restConf, appConf.Namespace) if err != nil { return nil, fmt.Errorf("failed to create gitServer service: %w", err) } serviceItems.Keycloak, err = keycloak.Make(sch, restConf, appConf.UsersNamespace) if err != nil { return nil, fmt.Errorf("unable to create keycloak service, %w", err) } serviceItems.Vault, err = vault.Make(appConf.VaultConfig(), serviceItems.K8S) if err != nil { return nil, fmt.Errorf("unable to init vault service, %w", err) } serviceItems.Cache = cache.New(time.Hour, time.Minute) sigs := make(chan os.Signal, 1) signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) go exitWait(sigs, serviceItems.Cache) gob.Register([]registry.CachedFile{}) if err := serviceItems.Cache.LoadFile(cachePath); err != nil { logger.Warn("unable to load cache") } serviceItems.PermService = permissions.Make(serviceItems.Codebase, serviceItems.K8S) return &serviceItems, nil } func initControllers( sch *runtime.Scheme, namespace string, logger *zap.Logger, cnf *config.Settings, services *config.Services, ) error { if cnf.Mock != "" { return nil } cfg := ctrl.GetConfigOrDie() mgr, err := ctrl.NewManager(cfg, ctrl.Options{ Scheme: sch, Namespace: namespace, MetricsBindAddress: "0", }) if err != nil { return fmt.Errorf("unable to ini manager, %w", err) } l := logger.Sugar() if err := codebaseController.Make(mgr, l, cnf, services.Cache, services.Gerrit, services.Codebase); err != nil { return fmt.Errorf("unable to init codebase controller, %w", err) } if err := mergeRequestController.Make(mgr, l, cnf, services.Gerrit, services.Codebase, services.GitServer, services.Jenkins, services.Cache); err != nil { return fmt.Errorf("unable to init merge request controller, %w", err) } go func() { if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { logger.Sugar().Error(err.Error(), "unable to start manager") } }() return nil } func initApps(logger *zap.Logger, cnf *config.Settings, r *gin.Engine, buildTime time.Time) error { restConf, err := initKubeConfig() if err != nil { return fmt.Errorf("unable to init kube config, %w", err) } appRouter := router.Make(r, logger, buildTime) sch := runtime.NewScheme() if err := v1.AddToScheme(sch); err != nil { return fmt.Errorf("unable to add core api to scheme, %w", err) } serviceItems, err := initServices(sch, restConf, cnf, logger) if err != nil { return fmt.Errorf("unable to init services, %w", err) } if err := initControllers(sch, cnf.Namespace, logger, cnf, serviceItems); err != nil { return fmt.Errorf("unable to init controllers, %w", err) } oa, err := initOauth(restConf, cnf, r, serviceItems.K8S) if err != nil { return fmt.Errorf("unable to init oauth, %w", err) } _, err = dashboard.Make(appRouter, oa, serviceItems, cnf.ClusterCodebaseName) if err != nil { return fmt.Errorf("unable to make dashboard app, %w", err) } _, err = registry.Make(appRouter, serviceItems.RegistryServices(), cnf.RegistryConfig()) if err != nil { return fmt.Errorf("unable to make registry app, %w", err) } _, err = cluster.Make(appRouter, serviceItems.ClusterServices(), cnf.ClusterConfig(), serviceItems.Cache) if err != nil { return fmt.Errorf("unable to init cluster app, %w", err) } return nil } func initKubeConfig() (*rest.Config, error) { k8sConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig( clientcmd.NewDefaultClientConfigLoadingRules(), &clientcmd.ConfigOverrides{}, ) restConfig, err := k8sConfig.ClientConfig() if err != nil { return nil, fmt.Errorf("unable to get k8s client config, %w", err) } return restConfig, nil } func initOauth(k8sConfig *rest.Config, cfg *config.Settings, r *gin.Engine, k8sService k8s.ServiceInterface) (dashboard.OAuth, error) { var oAuth dashboard.OAuth if cfg.Mock != "" { oAuth = mocks.OAuth() } else { transport, err := rest.TransportFor(k8sConfig) if err != nil { return nil, errors.Wrap(err, "unable to create transport for k8s config") } oa, err := oauth.InitOauth2( cfg.OCClientID, cfg.OCClientSecret, k8sConfig.Host, cfg.Host+"/auth/callback", &http.Client{Transport: transport}) if err != nil { return nil, errors.Wrap(err, "unable to init oauth2 client") } if !cfg.OAuthUseExternalTokenURL { if err := oa.UseInternalTokenService(context.Background(), cfg.OAuthInternalTokenHost, k8sService); err != nil { return nil, errors.Wrap(err, "unable to load internal oauth host") } } oAuth = oa } gob.Register(&oauth2.Token{}) r.Use(oauth.MakeGinMiddleware(oAuth, router.AuthTokenSessionKey, router.AuthTokenValidSessionKey, "/admin/")) r.Use(router.UserDataMiddleware) return oAuth, nil } func i18n(word ...string) string { message := strings.TrimSpace(strings.Join(word, " ")) return gotext.Get(message) } func majorVersion(word ...string) string { if len(word) == 0 { return "" } return registry.MajorVersion(word[0]) }