Răsfoiți Sursa

add option for initializing default project/cluster in self-hosted

Alexander Belanger 4 ani în urmă
părinte
comite
c550bd78f3

+ 38 - 0
api/server/handlers/user/create.go

@@ -76,6 +76,13 @@ func (u *UserCreateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
+	err = addUserToDefaultProject(u.Config(), user)
+
+	if err != nil {
+		u.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+
 	// save the user as authenticated in the session
 	redirect, err := authn.SaveUserAuthenticated(w, r, u.Config(), user)
 
@@ -113,3 +120,34 @@ func doesUserExist(userRepo repository.UserRepository, user *models.User) bool {
 
 	return user != nil && err == nil
 }
+
+// addUserToDefaultProject adds the created user to any default projects if required by
+// config variables.
+func addUserToDefaultProject(config *config.Config, user *models.User) error {
+	if config.ServerConf.InitInCluster {
+		// if this is the first user, add the user to the default project
+		if user.ID == 1 {
+			// read the default project
+			project, err := config.Repo.Project().ReadProject(1)
+
+			if err != nil {
+				return err
+			}
+
+			// create a new Role with the user as the admin
+			_, err = config.Repo.Project().CreateProjectRole(project, &models.Role{
+				Role: types.Role{
+					UserID:    user.ID,
+					ProjectID: project.ID,
+					Kind:      types.RoleAdmin,
+				},
+			})
+
+			if err != nil {
+				return err
+			}
+		}
+	}
+
+	return nil
+}

+ 6 - 0
api/server/handlers/user/github_callback.go

@@ -158,6 +158,12 @@ func upsertUserFromToken(config *config.Config, tok *oauth2.Token) (*models.User
 				return nil, err
 			}
 
+			err = addUserToDefaultProject(config, user)
+
+			if err != nil {
+				return nil, err
+			}
+
 			config.AnalyticsClient.Track(analytics.UserCreateTrack(&analytics.UserCreateTrackOpts{
 				UserScopedTrackOpts: analytics.GetUserScopedTrackOpts(user.ID),
 				Email:               user.Email,

+ 6 - 0
api/server/handlers/user/google_callback.go

@@ -143,6 +143,12 @@ func upsertGoogleUserFromToken(config *config.Config, tok *oauth2.Token) (*model
 				return nil, err
 			}
 
+			err = addUserToDefaultProject(config, user)
+
+			if err != nil {
+				return nil, err
+			}
+
 			config.AnalyticsClient.Track(analytics.UserCreateTrack(&analytics.UserCreateTrackOpts{
 				UserScopedTrackOpts: analytics.GetUserScopedTrackOpts(user.ID),
 				Email:               user.Email,

+ 1 - 0
api/server/shared/config/env/envconfs.go

@@ -89,6 +89,7 @@ type ServerConf struct {
 	ProvisionerCluster string `env:"PROVISIONER_CLUSTER"`
 	IngressCluster     string `env:"INGRESS_CLUSTER"`
 	SelfKubeconfig     string `env:"SELF_KUBECONFIG"`
+	InitInCluster      bool   `env:"INIT_IN_CLUSTER"`
 
 	WelcomeFormWebhook string `env:"WELCOME_FORM_WEBHOOK"`
 

+ 61 - 0
cmd/app/main.go

@@ -8,9 +8,12 @@ import (
 	"os"
 
 	"github.com/porter-dev/porter/api/server/router"
+	"github.com/porter-dev/porter/api/server/shared/config"
 	"github.com/porter-dev/porter/api/server/shared/config/loader"
 	"github.com/porter-dev/porter/internal/adapter"
+	"github.com/porter-dev/porter/internal/models"
 	"github.com/porter-dev/porter/internal/redis_stream"
+	"gorm.io/gorm"
 )
 
 // Version will be linked by an ldflag during build
@@ -35,6 +38,12 @@ func main() {
 		log.Fatal("Config loading failed: ", err)
 	}
 
+	err = initData(config)
+
+	if err != nil {
+		log.Fatal("Data initialization failed: ", err)
+	}
+
 	if config.RedisConf.Enabled {
 		redis, err := adapter.NewRedisClient(config.RedisConf)
 
@@ -68,3 +77,55 @@ func main() {
 		config.Logger.Fatal().Err(err).Msg("Server startup failed")
 	}
 }
+
+const defaultProjectName = "default"
+const defaultClusterName = "cluster-1"
+
+func initData(conf *config.Config) error {
+	// if the config specifies in-cluster connections are permitted, create a new project with a
+	// cluster that uses the in-cluster config. this will be the default project for this instance.
+	if conf.ServerConf.InitInCluster {
+		l := conf.Logger.Debug()
+		l.Msg("in-cluster config variable set: checking for default project and cluster")
+
+		// look for a project with id 1 with name of defaultProjectName
+		_, err := conf.Repo.Project().ReadProject(1)
+
+		if err == gorm.ErrRecordNotFound {
+			l.Msg("default project not found: attempting creation")
+
+			_, err = conf.Repo.Project().CreateProject(&models.Project{
+				Name: defaultProjectName,
+			})
+
+			if err != nil {
+				return err
+			}
+
+			l.Msg("successfully created default project")
+		} else if err != nil {
+			return err
+		}
+
+		_, err = conf.Repo.Cluster().ReadCluster(1, 1)
+
+		if err == gorm.ErrRecordNotFound {
+			l.Msg("default cluster not found: attempting creation")
+
+			_, err = conf.Repo.Cluster().CreateCluster(&models.Cluster{
+				Name:          defaultClusterName,
+				AuthMechanism: models.InCluster,
+			})
+
+			if err != nil {
+				return err
+			}
+
+			l.Msg("successfully created default cluster")
+		} else if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}

+ 16 - 4
internal/kubernetes/config.go

@@ -34,7 +34,14 @@ import (
 
 // GetDynamicClientOutOfClusterConfig creates a new dynamic client using the OutOfClusterConfig
 func GetDynamicClientOutOfClusterConfig(conf *OutOfClusterConfig) (dynamic.Interface, error) {
-	restConf, err := conf.ToRESTConfig()
+	var restConf *rest.Config
+	var err error
+
+	if conf.AllowInClusterConnections && conf.Cluster.AuthMechanism == models.InCluster {
+		restConf, err = rest.InClusterConfig()
+	} else {
+		restConf, err = conf.ToRESTConfig()
+	}
 
 	if err != nil {
 		return nil, err
@@ -51,6 +58,10 @@ func GetDynamicClientOutOfClusterConfig(conf *OutOfClusterConfig) (dynamic.Inter
 
 // GetAgentOutOfClusterConfig creates a new Agent using the OutOfClusterConfig
 func GetAgentOutOfClusterConfig(conf *OutOfClusterConfig) (*Agent, error) {
+	if conf.AllowInClusterConnections && conf.Cluster.AuthMechanism == models.InCluster {
+		return GetAgentInClusterConfig()
+	}
+
 	restConf, err := conf.ToRESTConfig()
 
 	if err != nil {
@@ -99,9 +110,10 @@ func GetAgentTesting(objects ...runtime.Object) *Agent {
 // OutOfClusterConfig is the set of parameters required for an out-of-cluster connection.
 // This implements RESTClientGetter
 type OutOfClusterConfig struct {
-	Cluster          *models.Cluster
-	Repo             repository.Repository
-	DefaultNamespace string // optional
+	Cluster                   *models.Cluster
+	Repo                      repository.Repository
+	DefaultNamespace          string // optional
+	AllowInClusterConnections bool
 
 	// Only required if using DigitalOcean OAuth as an auth mechanism
 	DigitalOceanOAuth *oauth2.Config

+ 9 - 8
internal/models/cluster.go

@@ -13,14 +13,15 @@ type ClusterAuth string
 
 // The support cluster candidate auth mechanisms
 const (
-	X509   ClusterAuth = "x509"
-	Basic  ClusterAuth = "basic"
-	Bearer ClusterAuth = "bearerToken"
-	OIDC   ClusterAuth = "oidc"
-	GCP    ClusterAuth = "gcp-sa"
-	AWS    ClusterAuth = "aws-sa"
-	DO     ClusterAuth = "do-oauth"
-	Local  ClusterAuth = "local"
+	X509      ClusterAuth = "x509"
+	Basic     ClusterAuth = "basic"
+	Bearer    ClusterAuth = "bearerToken"
+	OIDC      ClusterAuth = "oidc"
+	GCP       ClusterAuth = "gcp-sa"
+	AWS       ClusterAuth = "aws-sa"
+	DO        ClusterAuth = "do-oauth"
+	Local     ClusterAuth = "local"
+	InCluster ClusterAuth = "in-cluster"
 )
 
 // Cluster is an integration that can connect to a Kubernetes cluster via