Procházet zdrojové kódy

support for local kubeconfig

Alexander Belanger před 5 roky
rodič
revize
b682deb28f

+ 13 - 1
.darwin.goreleaser.yml

@@ -15,7 +15,19 @@ builds:
     flags:
       - -tags=cli
     hooks:
-      post: gon gon.hcl
+      post: gon gon.cli.hcl
+  - id: "porter-server"
+    binary: portersvr
+    env:
+      - CGO_ENABLED=1
+    dir: cmd/app
+    main: ./main.go
+    goos:
+      - darwin
+    goarch:
+      - amd64
+    hooks:
+      post: gon gon.server.hcl
 archives:
   - format: binary
     replacements:

+ 1 - 1
.gitignore

@@ -5,4 +5,4 @@ app
 *.db
 test.yaml
 dist
-gon.hcl
+gon*.hcl

+ 11 - 0
.goreleaser.yml

@@ -15,6 +15,17 @@ builds:
       - amd64
     flags:
       - -tags=cli
+  - id: "porter-server"
+    binary: portersvr
+    env:
+      - CGO_ENABLED=1
+    dir: cmd/app
+    main: ./main.go
+    goos:
+      - linux
+      - windows
+    goarch:
+      - amd64
 archives:
   - format: zip
     replacements:

+ 1 - 0
cli/cmd/api/project.go

@@ -153,6 +153,7 @@ func (c *Client) CreateProject(
 // which can be resolved to create a service account
 type CreateProjectCandidatesRequest struct {
 	Kubeconfig string `json:"kubeconfig"`
+	IsLocal    bool   `json:"is_local"`
 }
 
 // CreateProjectCandidatesResponse is the list of candidates returned after

+ 16 - 0
cli/cmd/config.go

@@ -11,6 +11,7 @@ import (
 
 // a set of shared flags
 var (
+	driver    string
 	host      string
 	projectID uint
 	clusterID uint
@@ -85,6 +86,21 @@ func init() {
 	configCmd.AddCommand(setHostCmd)
 }
 
+func setDriver(driver string) error {
+	viper.Set("driver", driver)
+	err := viper.WriteConfig()
+	color.New(color.FgGreen).Printf("Set the current driver as %s\n", driver)
+	return err
+}
+
+func getDriver() string {
+	if driver != "" {
+		return driver
+	}
+
+	return viper.GetString("driver")
+}
+
 func setProject(id uint) error {
 	viper.Set("project", id)
 	color.New(color.FgGreen).Printf("Set the current project id as %d\n", id)

+ 7 - 0
cli/cmd/connect.go

@@ -66,10 +66,17 @@ func init() {
 }
 
 func runConnect(_ *api.AuthCheckResponse, client *api.Client, _ []string) error {
+	isLocal := false
+
+	if getDriver() == "local" {
+		isLocal = true
+	}
+
 	return connect.Kubeconfig(
 		client,
 		kubeconfigPath,
 		*contexts,
 		getProjectID(),
+		isLocal,
 	)
 }

+ 2 - 0
cli/cmd/connect/kubeconfig.go

@@ -26,6 +26,7 @@ func Kubeconfig(
 	kubeconfigPath string,
 	contexts []string,
 	projectID uint,
+	isLocal bool,
 ) error {
 	// if project ID is 0, ask the user to set the project ID or create a project
 	if projectID == 0 {
@@ -45,6 +46,7 @@ func Kubeconfig(
 		projectID,
 		&api.CreateProjectCandidatesRequest{
 			Kubeconfig: string(rawBytes),
+			IsLocal:    isLocal,
 		},
 	)
 

+ 60 - 23
cli/cmd/server.go

@@ -3,10 +3,9 @@ package cmd
 import (
 	"fmt"
 	"os"
+	"os/exec"
 	"path/filepath"
 
-	"github.com/porter-dev/porter/cli/cmd/github"
-
 	"github.com/fatih/color"
 	"github.com/porter-dev/porter/cli/cmd/docker"
 
@@ -16,6 +15,7 @@ import (
 type startOps struct {
 	imageTag string `form:"required"`
 	db       string `form:"oneof=sqlite postgres"`
+	driver   string `form:"required"`
 	port     *int   `form:"required"`
 }
 
@@ -30,9 +30,35 @@ var testCmd = &cobra.Command{
 	Use:   "test",
 	Short: "Testing",
 	Run: func(cmd *cobra.Command, args []string) {
-		porterDir := filepath.Join(home, ".porter")
+		setDriver("local")
+
+		// TODO -- DOWNLOAD THE LATEST RELEASE, IF NOT EXIST
+		// porterDir := filepath.Join(home, ".porter")
+
+		// err := github.DownloadLatestServerRelease(porterDir)
+
+		// if err != nil {
+		// 	color.New(color.FgRed).Println("Failed:", err.Error())
+		// 	os.Exit(1)
+		// }
+
+		cmdPath := filepath.Join(home, ".porter", "portersvr")
+		sqlLitePath := filepath.Join(home, ".porter", "porter.db")
+		staticFilePath := filepath.Join(home, ".porter", "static")
 
-		err := github.DownloadLatestServerRelease(porterDir)
+		cmdPorter := exec.Command(cmdPath)
+		cmdPorter.Env = os.Environ()
+		cmdPorter.Env = append(cmdPorter.Env, []string{
+			"IS_LOCAL=true",
+			"SQL_LITE=true",
+			"SQL_LITE_PATH=" + sqlLitePath,
+			"STATIC_FILE_PATH=" + staticFilePath,
+		}...)
+
+		cmdPorter.Stdout = os.Stdout
+		cmdPorter.Stderr = os.Stderr
+
+		err := cmdPorter.Run()
 
 		if err != nil {
 			color.New(color.FgRed).Println("Failed:", err.Error())
@@ -46,24 +72,26 @@ var startCmd = &cobra.Command{
 	Use:   "start",
 	Short: "Starts a Porter instance using the Docker engine",
 	Run: func(cmd *cobra.Command, args []string) {
-		err := start(
-			opts.imageTag,
-			opts.db,
-			*opts.port,
-		)
+		if getDriver() == "docker" {
+			err := startDocker(
+				opts.imageTag,
+				opts.db,
+				*opts.port,
+			)
 
-		if err != nil {
-			red := color.New(color.FgRed)
-			red.Println("Error running start:", err.Error())
-			red.Println("Shutting down...")
+			if err != nil {
+				red := color.New(color.FgRed)
+				red.Println("Error running start:", err.Error())
+				red.Println("Shutting down...")
 
-			err = stop()
+				err = stopDocker()
 
-			if err != nil {
-				red.Println("Shutdown unsuccessful:", err.Error())
-			}
+				if err != nil {
+					red.Println("Shutdown unsuccessful:", err.Error())
+				}
 
-			os.Exit(1)
+				os.Exit(1)
+			}
 		}
 	},
 }
@@ -72,9 +100,11 @@ var stopCmd = &cobra.Command{
 	Use:   "stop",
 	Short: "Stops a Porter instance running on the Docker engine",
 	Run: func(cmd *cobra.Command, args []string) {
-		if err := stop(); err != nil {
-			color.New(color.FgRed).Println("Shutdown unsuccessful:", err.Error())
-			os.Exit(1)
+		if getDriver() == "docker" {
+			if err := stopDocker(); err != nil {
+				color.New(color.FgRed).Println("Shutdown unsuccessful:", err.Error())
+				os.Exit(1)
+			}
 		}
 	},
 }
@@ -94,6 +124,13 @@ func init() {
 		"the db to use, one of sqlite or postgres",
 	)
 
+	startCmd.PersistentFlags().StringVar(
+		&opts.driver,
+		"driver",
+		"local",
+		"the db to use, one of local or docker",
+	)
+
 	startCmd.PersistentFlags().StringVar(
 		&opts.imageTag,
 		"image-tag",
@@ -109,7 +146,7 @@ func init() {
 	)
 }
 
-func start(
+func startDocker(
 	imageTag string,
 	db string,
 	port int,
@@ -149,7 +186,7 @@ func start(
 	return setHost(fmt.Sprintf("http://localhost:%d", port))
 }
 
-func stop() error {
+func stopDocker() error {
 	agent, err := docker.NewAgentFromEnv()
 
 	if err != nil {

+ 1 - 0
cmd/app/main.go

@@ -71,6 +71,7 @@ func main() {
 		store,
 		appConf.Server.CookieName,
 		false,
+		appConf.Server.IsLocal,
 		&oauth.Config{
 			ClientID:     appConf.Server.GithubClientID,
 			ClientSecret: appConf.Server.GithubClientSecret,

+ 8 - 0
go.sum

@@ -58,20 +58,24 @@ github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX
 github.com/Azure/go-autorest v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
 github.com/Azure/go-autorest v11.7.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
 github.com/Azure/go-autorest v12.0.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
+github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
 github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
 github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI=
 github.com/Azure/go-autorest/autorest v0.9.3/go.mod h1:GsRuLYvwzLjjjRoWEIyMUaYq8GNUx2nRB378IPt/1p0=
 github.com/Azure/go-autorest/autorest v0.10.0/go.mod h1:/FALq9T/kS7b5J5qsQ+RSTUdAmGFqi0vUdVNNx8q630=
+github.com/Azure/go-autorest/autorest v0.11.1 h1:eVvIXUKiTgv++6YnWb42DUA1YL7qDugnKP0HljexdnQ=
 github.com/Azure/go-autorest/autorest v0.11.1/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw=
 github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0=
 github.com/Azure/go-autorest/autorest/adal v0.8.0/go.mod h1:Z6vX6WXXuyieHAXwMj0S6HY6e6wcHn37qQMBQlvY3lc=
 github.com/Azure/go-autorest/autorest/adal v0.8.1/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q=
 github.com/Azure/go-autorest/autorest/adal v0.8.2/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q=
+github.com/Azure/go-autorest/autorest/adal v0.9.0 h1:SigMbuFNuKgc1xcGhaeapbh+8fgsu+GxgDRFyg7f5lM=
 github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg=
 github.com/Azure/go-autorest/autorest/azure/auth v0.4.2/go.mod h1:90gmfKdlmKgfjUpnCEpOJzsUEjrWDSLwHIG73tSXddM=
 github.com/Azure/go-autorest/autorest/azure/cli v0.3.1/go.mod h1:ZG5p860J94/0kI9mNJVoIoLgXcirM2gF5i2kWloofxw=
 github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA=
 github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g=
+github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw=
 github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74=
 github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
 github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
@@ -80,8 +84,10 @@ github.com/Azure/go-autorest/autorest/mocks v0.4.0/go.mod h1:LTp+uSrOhSkaKrUy935
 github.com/Azure/go-autorest/autorest/to v0.2.0/go.mod h1:GunWKJp1AEqgMaGLV+iocmRAJWqST1wQYhyyjXJ3SJc=
 github.com/Azure/go-autorest/autorest/validation v0.1.0/go.mod h1:Ha3z/SqBeaalWQvokg3NZAlQTalVMtOIAs1aGK7G6u8=
 github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc=
+github.com/Azure/go-autorest/logger v0.2.0 h1:e4RVHVZKC5p6UANLJHkM4OfR1UKZPj8Wt8Pcx+3oqrE=
 github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
 github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk=
+github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo=
 github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
 github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
@@ -306,6 +312,7 @@ github.com/denverdino/aliyungo v0.0.0-20180316152028-2581e433b270/go.mod h1:dV8l
 github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0=
 github.com/devigned/tab v0.1.1/go.mod h1:XG9mPq0dFghrYvoBF3xdRrJzSTX1b7IQrvaL9mzjeJY=
 github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
+github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
 github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
 github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
 github.com/digitalocean/godo v1.19.0/go.mod h1:AAPQ+tiM4st79QHlEBTg8LM7JQNre4SAQCbn56wEyKY=
@@ -631,6 +638,7 @@ github.com/googleapis/gnostic v0.2.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTV
 github.com/gookit/color v1.2.4/go.mod h1:AhIE+pS6D4Ql0SQWbBeXPHw7gY0/sjHoA4s/n1KB7xg=
 github.com/gophercloud/gophercloud v0.0.0-20180807015416-4ea085781bae/go.mod h1:3WdhXV3rUYy9p6AUW8d94kr+HS62Y4VL9mBnFxsD8q4=
 github.com/gophercloud/gophercloud v0.0.0-20190216224116-dcc6e84aef1b/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=
+github.com/gophercloud/gophercloud v0.1.0 h1:P/nh25+rzXouhytV2pUHBb65fnds26Ghl8/391+sT5o=
 github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
 github.com/goreleaser/goreleaser v0.136.0/go.mod h1:wiKrPUeSNh6Wu8nUHxZydSOVQ/OZvOaO7DTtFqie904=

+ 2 - 0
internal/config/config.go

@@ -26,6 +26,8 @@ type ServerConf struct {
 	TimeoutWrite   time.Duration `env:"SERVER_TIMEOUT_WRITE,default=10s"`
 	TimeoutIdle    time.Duration `env:"SERVER_TIMEOUT_IDLE,default=15s"`
 
+	IsLocal bool `env:"IS_LOCAL,default=false"`
+
 	GithubClientID     string `env:"GITHUB_CLIENT_ID"`
 	GithubClientSecret string `env:"GITHUB_CLIENT_SECRET"`
 }

+ 7 - 0
internal/forms/action.go

@@ -92,6 +92,13 @@ func (sar *ServiceAccountActionResolver) PopulateServiceAccount(
 		}
 	}
 
+	// if auth mechanism is local, just write the kubeconfig and return: rest of config is
+	// unnecessary
+	if sar.SACandidate.AuthMechanism == models.Local && len(sar.SACandidate.Kubeconfig) > 0 {
+		sar.SA.Kubeconfig = sar.SACandidate.Kubeconfig
+		return nil
+	}
+
 	if len(authInfo.ClientCertificateData) > 0 {
 		sar.SA.ClientCertificateData = authInfo.ClientCertificateData
 	}

+ 9 - 9
internal/forms/action_test.go

@@ -20,7 +20,7 @@ func TestPopulateServiceAccountBasic(t *testing.T) {
 	})
 
 	// create a ServiceAccountCandidate from a kubeconfig
-	saCandidates, err := kubernetes.GetServiceAccountCandidates([]byte(ClusterCAWithData))
+	saCandidates, err := kubernetes.GetServiceAccountCandidates([]byte(ClusterCAWithData), false)
 
 	if err != nil {
 		t.Fatalf("%v\n", err)
@@ -82,7 +82,7 @@ func TestPopulateServiceAccountClusterDataAction(t *testing.T) {
 	})
 
 	// create a ServiceAccountCandidate from a kubeconfig
-	saCandidates, err := kubernetes.GetServiceAccountCandidates([]byte(ClusterCAWithoutData))
+	saCandidates, err := kubernetes.GetServiceAccountCandidates([]byte(ClusterCAWithoutData), false)
 
 	if err != nil {
 		t.Fatalf("%v\n", err)
@@ -147,7 +147,7 @@ func TestPopulateServiceAccountClusterLocalhostAction(t *testing.T) {
 	})
 
 	// create a ServiceAccountCandidate from a kubeconfig
-	saCandidates, err := kubernetes.GetServiceAccountCandidates([]byte(ClusterLocalhost))
+	saCandidates, err := kubernetes.GetServiceAccountCandidates([]byte(ClusterLocalhost), false)
 
 	if err != nil {
 		t.Fatalf("%v\n", err)
@@ -212,7 +212,7 @@ func TestPopulateServiceAccountClientCertAction(t *testing.T) {
 	})
 
 	// create a ServiceAccountCandidate from a kubeconfig
-	saCandidates, err := kubernetes.GetServiceAccountCandidates([]byte(ClientWithoutCertData))
+	saCandidates, err := kubernetes.GetServiceAccountCandidates([]byte(ClientWithoutCertData), false)
 
 	if err != nil {
 		t.Fatalf("%v\n", err)
@@ -277,7 +277,7 @@ func TestPopulateServiceAccountClientCertAndKeyActions(t *testing.T) {
 	})
 
 	// create a ServiceAccountCandidate from a kubeconfig
-	saCandidates, err := kubernetes.GetServiceAccountCandidates([]byte(ClientWithoutCertAndKeyData))
+	saCandidates, err := kubernetes.GetServiceAccountCandidates([]byte(ClientWithoutCertAndKeyData), false)
 
 	if err != nil {
 		t.Fatalf("%v\n", err)
@@ -354,7 +354,7 @@ func TestPopulateServiceAccountTokenDataAction(t *testing.T) {
 	})
 
 	// create a ServiceAccountCandidate from a kubeconfig
-	saCandidates, err := kubernetes.GetServiceAccountCandidates([]byte(BearerTokenWithoutData))
+	saCandidates, err := kubernetes.GetServiceAccountCandidates([]byte(BearerTokenWithoutData), false)
 
 	if err != nil {
 		t.Fatalf("%v\n", err)
@@ -409,7 +409,7 @@ func TestPopulateServiceAccountGCPKeyDataAction(t *testing.T) {
 	})
 
 	// create a ServiceAccountCandidate from a kubeconfig
-	saCandidates, err := kubernetes.GetServiceAccountCandidates([]byte(GCPPlugin))
+	saCandidates, err := kubernetes.GetServiceAccountCandidates([]byte(GCPPlugin), false)
 
 	if err != nil {
 		t.Fatalf("%v\n", err)
@@ -463,7 +463,7 @@ func TestPopulateServiceAccountAWSKeyDataAction(t *testing.T) {
 	})
 
 	// create a ServiceAccountCandidate from a kubeconfig
-	saCandidates, err := kubernetes.GetServiceAccountCandidates([]byte(AWSEKSGetTokenExec))
+	saCandidates, err := kubernetes.GetServiceAccountCandidates([]byte(AWSEKSGetTokenExec), false)
 
 	if err != nil {
 		t.Fatalf("%v\n", err)
@@ -529,7 +529,7 @@ func TestPopulateServiceAccountOIDCAction(t *testing.T) {
 	})
 
 	// create a ServiceAccountCandidate from a kubeconfig
-	saCandidates, err := kubernetes.GetServiceAccountCandidates([]byte(OIDCAuthWithoutData))
+	saCandidates, err := kubernetes.GetServiceAccountCandidates([]byte(OIDCAuthWithoutData), false)
 
 	if err != nil {
 		t.Fatalf("%v\n", err)

+ 10 - 2
internal/forms/candidate.go

@@ -10,12 +10,20 @@ import (
 type CreateServiceAccountCandidatesForm struct {
 	ProjectID  uint   `json:"project_id"`
 	Kubeconfig string `json:"kubeconfig"`
+
+	// Represents whether the auth mechanism should be designated as
+	// "local": if so, the auth mechanism uses local plugins/mechanisms purely from the
+	// kubeconfig.
+	IsLocal bool `json:"is_local"`
 }
 
 // ToServiceAccountCandidates creates a ServiceAccountCandidate from the kubeconfig and
 // project id
-func (csa *CreateServiceAccountCandidatesForm) ToServiceAccountCandidates() ([]*models.ServiceAccountCandidate, error) {
-	candidates, err := kubernetes.GetServiceAccountCandidates([]byte(csa.Kubeconfig))
+func (csa *CreateServiceAccountCandidatesForm) ToServiceAccountCandidates(
+	isServerLocal bool,
+) ([]*models.ServiceAccountCandidate, error) {
+	// can only use "local" auth mechanism if the server is running locally
+	candidates, err := kubernetes.GetServiceAccountCandidates([]byte(csa.Kubeconfig), isServerLocal && csa.IsLocal)
 
 	if err != nil {
 		return nil, err

+ 1 - 1
internal/kubernetes/config.go

@@ -20,7 +20,7 @@ import (
 	"k8s.io/client-go/util/homedir"
 
 	// add oidc provider here
-	_ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
+	_ "k8s.io/client-go/plugin/pkg/client/auth"
 )
 
 // GetAgentOutOfClusterConfig creates a new Agent using the OutOfClusterConfig

+ 27 - 13
internal/kubernetes/kubeconfig.go

@@ -20,7 +20,11 @@ import (
 
 // GetServiceAccountCandidates parses a kubeconfig for a list of service account
 // candidates.
-func GetServiceAccountCandidates(kubeconfig []byte) ([]*models.ServiceAccountCandidate, error) {
+//
+// The local boolean represents whether the auth mechanism should be designated as
+// "local": if so, the auth mechanism uses local plugins/mechanisms purely from the
+// kubeconfig.
+func GetServiceAccountCandidates(kubeconfig []byte, local bool) ([]*models.ServiceAccountCandidate, error) {
 	config, err := clientcmd.NewClientConfigFromBytes(kubeconfig)
 
 	if err != nil {
@@ -40,19 +44,25 @@ func GetServiceAccountCandidates(kubeconfig []byte) ([]*models.ServiceAccountCan
 		awsClusterID := ""
 		authInfoName := context.AuthInfo
 
-		// get the auth mechanism and actions
-		authMechanism, authInfoActions := parseAuthInfoForActions(rawConf.AuthInfos[authInfoName])
-		clusterActions := parseClusterForActions(rawConf.Clusters[clusterName])
-
-		actions := append(authInfoActions, clusterActions...)
+		actions := make([]models.ServiceAccountAction, 0)
+		var authMechanism string
 
-		// if auth mechanism is unsupported, we'll skip it
-		if authMechanism == models.NotAvailable {
-			continue
-		} else if authMechanism == models.AWS {
-			// if the auth mechanism is AWS, we need to parse more explicitly
-			// for the cluster id
-			awsClusterID = parseAuthInfoForAWSClusterID(rawConf.AuthInfos[authInfoName], clusterName)
+		if local {
+			authMechanism = models.Local
+		} else {
+			// get the auth mechanism and actions
+			authMechanism, actions = parseAuthInfoForActions(rawConf.AuthInfos[authInfoName])
+			clusterActions := parseClusterForActions(rawConf.Clusters[clusterName])
+			actions = append(actions, clusterActions...)
+
+			// if auth mechanism is unsupported, we'll skip it
+			if authMechanism == models.NotAvailable {
+				continue
+			} else if authMechanism == models.AWS {
+				// if the auth mechanism is AWS, we need to parse more explicitly
+				// for the cluster id
+				awsClusterID = parseAuthInfoForAWSClusterID(rawConf.AuthInfos[authInfoName], clusterName)
+			}
 		}
 
 		// construct the raw kubeconfig that's relevant for that context
@@ -282,6 +292,10 @@ func GetClientConfigFromServiceAccount(
 	clusterID uint,
 	updateTokenCache UpdateTokenCacheFunc,
 ) (clientcmd.ClientConfig, error) {
+	if sa.AuthMechanism == models.Local {
+		return clientcmd.NewClientConfigFromBytes(sa.Kubeconfig)
+	}
+
 	apiConfig, err := createRawConfigFromServiceAccount(sa, clusterID, updateTokenCache)
 
 	if err != nil {

+ 4 - 4
internal/kubernetes/kubeconfig_test.go

@@ -432,9 +432,9 @@ var SACandidatesTests = []saCandidatesTest{
 	},
 }
 
-func TestGetServiceAccountCandidates(t *testing.T) {
+func TestGetServiceAccountCandidatesNonLocal(t *testing.T) {
 	for _, c := range SACandidatesTests {
-		result, err := kubernetes.GetServiceAccountCandidates(c.raw)
+		result, err := kubernetes.GetServiceAccountCandidates(c.raw, false)
 
 		if err != nil {
 			t.Fatalf("error occurred %v\n", err)
@@ -511,7 +511,7 @@ func TestGetServiceAccountCandidates(t *testing.T) {
 }
 
 func TestAWSClusterIDGuess(t *testing.T) {
-	result, err := kubernetes.GetServiceAccountCandidates([]byte(AWSIamAuthenticatorExec))
+	result, err := kubernetes.GetServiceAccountCandidates([]byte(AWSIamAuthenticatorExec), false)
 
 	if err != nil {
 		t.Fatalf("error occurred %v\n", err)
@@ -525,7 +525,7 @@ func TestAWSClusterIDGuess(t *testing.T) {
 		t.Errorf("Guess AWS cluster id failed: expected %s, got %s\n", "cluster-test-aws-id-guess", result[0].AWSClusterIDGuess)
 	}
 
-	result, err = kubernetes.GetServiceAccountCandidates([]byte(AWSEKSGetTokenExec))
+	result, err = kubernetes.GetServiceAccountCandidates([]byte(AWSEKSGetTokenExec), false)
 
 	if err != nil {
 		t.Fatalf("error occurred %v\n", err)

+ 4 - 0
internal/models/serviceaccount.go

@@ -12,6 +12,7 @@ const (
 	OIDC                = "oidc"
 	GCP                 = "gcp-sa"
 	AWS                 = "aws-sa"
+	Local               = "local"
 	NotAvailable        = "n/a"
 )
 
@@ -139,6 +140,9 @@ type ServiceAccount struct {
 	OIDCCertificateAuthorityData []byte `json:"idp-certificate-authority-data"`
 	OIDCIDToken                  []byte `json:"id-token"`
 	OIDCRefreshToken             []byte `json:"refresh-token"`
+
+	// The raw kubeconfig, used by local auth mechanisms
+	Kubeconfig []byte `json:"kubeconfig"`
 }
 
 // ServiceAccountExternal is an external ServiceAccount to be shared over REST

+ 4 - 0
server/api/api.go

@@ -34,11 +34,13 @@ type App struct {
 	translator   *ut.Translator
 	cookieName   string
 	testing      bool
+	isLocal      bool
 	TestAgents   *TestAgents
 	GithubConfig *oauth2.Config
 }
 
 // New returns a new App instance
+// TODO -- this should accept an app/server config
 func New(
 	logger *lr.Logger,
 	db *gorm.DB,
@@ -47,6 +49,7 @@ func New(
 	store sessions.Store,
 	cookieName string,
 	testing bool,
+	isLocal bool,
 	githubConfig *oauth.Config,
 ) *App {
 	// for now, will just support the english translator from the
@@ -82,6 +85,7 @@ func New(
 		translator:   &trans,
 		cookieName:   cookieName,
 		testing:      testing,
+		isLocal:      isLocal,
 		TestAgents:   testAgents,
 		GithubConfig: oauthGithubConf,
 	}

+ 1 - 1
server/api/helpers_test.go

@@ -79,7 +79,7 @@ func newTester(canQuery bool) *tester {
 	repo := test.NewRepository(canQuery)
 
 	store, _ := sessionstore.NewStore(repo, appConf.Server)
-	app := api.New(logger, nil, repo, validator, store, appConf.Server.CookieName, true, nil)
+	app := api.New(logger, nil, repo, validator, store, appConf.Server.CookieName, true, false, nil)
 	r := router.New(app, store, appConf.Server.CookieName, appConf.Server.StaticFilePath, repo)
 
 	return &tester{

+ 1 - 1
server/api/project_handler.go

@@ -210,7 +210,7 @@ func (app *App) HandleCreateProjectSACandidates(w http.ResponseWriter, r *http.R
 	}
 
 	// convert the form to a ServiceAccountCandidate
-	saCandidates, err := form.ToServiceAccountCandidates()
+	saCandidates, err := form.ToServiceAccountCandidates(app.isLocal)
 
 	if err != nil {
 		app.handleErrorFormDecoding(err, ErrProjectDecode, w)

+ 2 - 2
server/api/project_handler_test.go

@@ -341,7 +341,7 @@ func initProjectSACandidate(tester *tester) {
 	}
 
 	// convert the form to a ServiceAccountCandidate
-	saCandidates, _ := form.ToServiceAccountCandidates()
+	saCandidates, _ := form.ToServiceAccountCandidates(false)
 
 	for _, saCandidate := range saCandidates {
 		tester.repo.ServiceAccount.CreateServiceAccountCandidate(saCandidate)
@@ -357,7 +357,7 @@ func initProjectSADefault(tester *tester) {
 	}
 
 	// convert the form to a ServiceAccountCandidate
-	saCandidates, _ := form.ToServiceAccountCandidates()
+	saCandidates, _ := form.ToServiceAccountCandidates(false)
 
 	for _, saCandidate := range saCandidates {
 		tester.repo.ServiceAccount.CreateServiceAccountCandidate(saCandidate)