Просмотр исходного кода

Merge branch 'staging' of https://github.com/porter-dev/porter into beta.3.integration-frontend

jusrhee 5 лет назад
Родитель
Сommit
0d42bd6ced

+ 0 - 42
.darwin.goreleaser.yml

@@ -1,42 +0,0 @@
-before:
-  hooks:
-    - go mod download
-builds:
-  - id: "porter-cli"
-    binary: porter
-    ldflags:
-    - -X 'github.com/porter-dev/porter/cli/cmd.Version={{.Version}}'
-    env:
-      - CGO_ENABLED=1
-    dir: cli
-    main: ./main.go
-    goos:
-      - darwin
-    goarch:
-      - amd64
-    flags:
-      - -tags=cli
-    hooks:
-      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:
-      darwin: Darwin
-release:
-  disable: true
-checksum:
-  disable: true
-changelog:
-  skip: true

+ 0 - 38
.goreleaser.yml

@@ -1,38 +0,0 @@
-before:
-  hooks:
-    - go mod download
-builds:
-  - id: "porter-cli"
-    ldflags:
-    - -X 'github.com/porter-dev/porter/cli/cmd.Version={{.Version}}'
-    binary: porter
-    dir: cli
-    main: ./main.go
-    goos:
-      - linux
-      - windows
-    goarch:
-      - amd64
-    flags:
-      - -tags=cli
-  # - id: "porter-server"
-  #   binary: portersvr
-  #   dir: cmd/app
-  #   main: ./main.go
-  #   goos:
-  #     - linux
-  #     - windows
-  #   goarch:
-  #     - amd64
-archives:
-  - format: zip
-    replacements:
-      linux: Linux
-      windows: Windows
-      amd64: x86_64
-release:
-  disable: true
-# checksum:
-#   disable: true
-changelog:
-  skip: true

+ 45 - 4
cli/cmd/api/registry.go

@@ -64,6 +64,7 @@ func (c *Client) CreateECR(
 type CreateGCRRequest struct {
 	Name             string `json:"name"`
 	GCPIntegrationID uint   `json:"gcp_integration_id"`
+	URL              string `json:"url"`
 }
 
 // CreateGCRResponse is the resulting registry after creation
@@ -166,8 +167,8 @@ func (c *Client) DeleteProjectRegistry(
 	return nil
 }
 
-// GetECRTokenResponse blah
-type GetECRTokenResponse struct {
+// GetTokenResponse blah
+type GetTokenResponse struct {
 	Token     string     `json:"token"`
 	ExpiresAt *time.Time `json:"expires_at"`
 }
@@ -177,7 +178,7 @@ func (c *Client) GetECRAuthorizationToken(
 	ctx context.Context,
 	projectID uint,
 	region string,
-) (*GetECRTokenResponse, error) {
+) (*GetTokenResponse, error) {
 	req, err := http.NewRequest(
 		"GET",
 		fmt.Sprintf("%s/projects/%d/registries/ecr/%s/token", c.BaseURL, projectID, region),
@@ -188,7 +189,47 @@ func (c *Client) GetECRAuthorizationToken(
 		return nil, err
 	}
 
-	bodyResp := &GetECRTokenResponse{}
+	bodyResp := &GetTokenResponse{}
+	req = req.WithContext(ctx)
+
+	if httpErr, err := c.sendRequest(req, bodyResp, true); httpErr != nil || err != nil {
+		if httpErr != nil {
+			return nil, fmt.Errorf("code %d, errors %v", httpErr.Code, httpErr.Errors)
+		}
+
+		return nil, err
+	}
+
+	return bodyResp, nil
+}
+
+type GetGCRTokenRequest struct {
+	ServerURL string `json:"server_url"`
+}
+
+// GetGCRAuthorizationToken gets a GCR authorization token
+func (c *Client) GetGCRAuthorizationToken(
+	ctx context.Context,
+	projectID uint,
+	gcrRequest *GetGCRTokenRequest,
+) (*GetTokenResponse, error) {
+	data, err := json.Marshal(gcrRequest)
+
+	if err != nil {
+		return nil, err
+	}
+
+	req, err := http.NewRequest(
+		"GET",
+		fmt.Sprintf("%s/projects/%d/registries/gcr/token", c.BaseURL, projectID),
+		strings.NewReader(string(data)),
+	)
+
+	if err != nil {
+		return nil, err
+	}
+
+	bodyResp := &GetTokenResponse{}
 	req = req.WithContext(ctx)
 
 	if httpErr, err := c.sendRequest(req, bodyResp, true); httpErr != nil || err != nil {

+ 8 - 0
cli/cmd/connect/gcr.go

@@ -52,6 +52,13 @@ Key file location: `))
 
 		color.New(color.FgGreen).Printf("created gcp integration with id %d\n", integration.ID)
 
+		regURL, err := utils.PromptPlaintext(fmt.Sprintf(`Please provide the registry URL, in the form [GCP_DOMAIN]/[GCP_PROJECT_ID]. For example, gcr.io/my-project-123456.
+Registry URL: `))
+
+		if err != nil {
+			return 0, err
+		}
+
 		// create the registry
 		// query for registry name
 		regName, err := utils.PromptPlaintext(fmt.Sprintf(`Give this registry a name: `))
@@ -66,6 +73,7 @@ Key file location: `))
 			&api.CreateGCRRequest{
 				Name:             regName,
 				GCPIntegrationID: integration.ID,
+				URL:              regURL,
 			},
 		)
 

+ 52 - 0
cmd/docker-credential-porter/helper/helper.go

@@ -5,6 +5,7 @@ import (
 	"encoding/base64"
 	"fmt"
 	"log"
+	"net/url"
 	"os"
 	"path/filepath"
 	"regexp"
@@ -43,6 +44,57 @@ var ecrPattern = regexp.MustCompile(`(^[a-zA-Z0-9][a-zA-Z0-9-_]*)\.dkr\.ecr(\-fi
 // Get retrieves credentials from the store.
 // It returns username and secret as strings.
 func (p *PorterHelper) Get(serverURL string) (user string, secret string, err error) {
+	if strings.Contains(serverURL, "gcr.io") {
+		return p.getGCR(serverURL)
+	}
+
+	return p.getECR(serverURL)
+}
+
+func (p *PorterHelper) getGCR(serverURL string) (user string, secret string, err error) {
+	urlP, err := url.Parse(serverURL)
+
+	if err != nil {
+		return "", "", err
+	}
+
+	credCache := BuildCredentialsCache(urlP.Host)
+	cachedEntry := credCache.Get(serverURL)
+
+	var token string
+
+	if cachedEntry != nil && cachedEntry.IsValid(time.Now()) {
+		token = cachedEntry.AuthorizationToken
+	} else {
+		host := viper.GetString("host")
+		projID := viper.GetUint("project")
+
+		client := api.NewClient(host+"/api", "cookie.json")
+
+		// get a token from the server
+		tokenResp, err := client.GetGCRAuthorizationToken(context.Background(), projID, &api.GetGCRTokenRequest{
+			ServerURL: serverURL,
+		})
+
+		if err != nil {
+			return "", "", err
+		}
+
+		token = tokenResp.Token
+
+		// set the token in cache
+		credCache.Set(serverURL, &AuthEntry{
+			AuthorizationToken: token,
+			RequestedAt:        time.Now(),
+			ExpiresAt:          *tokenResp.ExpiresAt,
+			ProxyEndpoint:      serverURL,
+		})
+	}
+
+	return "oauth2accesstoken", token, nil
+}
+
+func (p *PorterHelper) getECR(serverURL string) (user string, secret string, err error) {
 	// parse the server url for region
 	matches := ecrPattern.FindStringSubmatch(serverURL)
 

+ 18 - 3
internal/forms/infra.go

@@ -1,7 +1,9 @@
 package forms
 
 import (
-	cmdutils "github.com/porter-dev/porter/cli/cmd/utils"
+	"math/rand"
+	"time"
+
 	"github.com/porter-dev/porter/internal/models"
 )
 
@@ -20,7 +22,7 @@ func (ce *CreateECRInfra) ToAWSInfra() (*models.AWSInfra, error) {
 	return &models.AWSInfra{
 		Kind:             models.AWSInfraECR,
 		ProjectID:        ce.ProjectID,
-		Suffix:           cmdutils.StringWithCharset(6, randCharset),
+		Suffix:           stringWithCharset(6, randCharset),
 		Status:           models.StatusCreating,
 		AWSIntegrationID: ce.AWSIntegrationID,
 	}, nil
@@ -39,7 +41,7 @@ func (ce *CreateEKSInfra) ToAWSInfra() (*models.AWSInfra, error) {
 	return &models.AWSInfra{
 		Kind:             models.AWSInfraEKS,
 		ProjectID:        ce.ProjectID,
-		Suffix:           cmdutils.StringWithCharset(6, randCharset),
+		Suffix:           stringWithCharset(6, randCharset),
 		Status:           models.StatusCreating,
 		AWSIntegrationID: ce.AWSIntegrationID,
 	}, nil
@@ -56,3 +58,16 @@ type DestroyECRInfra struct {
 type DestroyEKSInfra struct {
 	EKSName string `json:"eks_name" form:"required"`
 }
+
+// helpers for random string
+var seededRand *rand.Rand = rand.New(
+	rand.NewSource(time.Now().UnixNano()))
+
+// stringWithCharset returns a random string by pulling from a given charset
+func stringWithCharset(length int, charset string) string {
+	b := make([]byte, length)
+	for i := range b {
+		b[i] = charset[seededRand.Intn(len(charset))]
+	}
+	return string(b)
+}

+ 8 - 6
internal/forms/integration.go

@@ -7,17 +7,19 @@ import (
 // CreateGCPIntegrationForm represents the accepted values for creating a
 // GCP Integration
 type CreateGCPIntegrationForm struct {
-	UserID     uint   `json:"user_id" form:"required"`
-	ProjectID  uint   `json:"project_id" form:"required"`
-	GCPKeyData string `json:"gcp_key_data" form:"required"`
+	UserID       uint   `json:"user_id" form:"required"`
+	ProjectID    uint   `json:"project_id" form:"required"`
+	GCPKeyData   string `json:"gcp_key_data" form:"required"`
+	GCPProjectID string `json:"gcp_project_id"`
 }
 
 // ToGCPIntegration converts the project to a gorm project model
 func (cgf *CreateGCPIntegrationForm) ToGCPIntegration() (*ints.GCPIntegration, error) {
 	return &ints.GCPIntegration{
-		UserID:     cgf.UserID,
-		ProjectID:  cgf.ProjectID,
-		GCPKeyData: []byte(cgf.GCPKeyData),
+		UserID:       cgf.UserID,
+		ProjectID:    cgf.ProjectID,
+		GCPKeyData:   []byte(cgf.GCPKeyData),
+		GCPProjectID: cgf.GCPProjectID,
 	}, nil
 }
 

+ 1 - 1
internal/models/infra.go

@@ -83,7 +83,7 @@ func (ai *AWSInfra) GetID() string {
 func ParseWorkspaceID(workspaceID string) (string, uint, uint, error) {
 	strArr := strings.Split(workspaceID, "-")
 
-	if len(strArr) != 3 {
+	if len(strArr) < 3 {
 		return "", 0, 0, fmt.Errorf("workspace id improperly formatted")
 	}
 

+ 22 - 54
internal/registry/registry.go

@@ -66,67 +66,35 @@ type gcrRepositoryResp struct {
 	Repositories []string `json:"repositories"`
 }
 
-func (r *Registry) listGCRRepositories(
-	repo repository.Repository,
-) ([]*Repository, error) {
-	// jwtTok := string(r.DockerTokenCache.Token)
-
-	// // if a jwt token does not exist or is expired, refresh it
-	// if r.DockerTokenCache.IsExpired() || len(jwtTok) == 0 {
-	// 	gcp, err := repo.GCPIntegration.ReadGCPIntegration(
-	// 		r.GCPIntegrationID,
-	// 	)
-
-	// 	if err != nil {
-	// 		return nil, err
-	// 	}
-
-	// 	// get oauth2 access token
-	// 	oauthTok, err := gcp.GetBearerToken(r.getTokenCache, r.setTokenCacheFunc(repo))
-
-	// 	if err != nil {
-	// 		return nil, err
-	// 	}
-
-	// 	// get jwt token
-	// 	client := &http.Client{}
-
-	// 	req, err := http.NewRequest(
-	// 		"GET",
-	// 		"https://gcr.io/v2/token?service=gcr.io&scope=registry:catalog:*",
-	// 		nil,
-	// 	)
-
-	// 	req.SetBasicAuth("_token", oauthTok)
-
-	// 	resp, err := client.Do(req)
+func (r *Registry) GetGCRToken(repo repository.Repository) (*ints.TokenCache, error) {
+	gcp, err := repo.GCPIntegration.ReadGCPIntegration(
+		r.GCPIntegrationID,
+	)
 
-	// 	if err != nil {
-	// 		return nil, err
-	// 	}
+	if err != nil {
+		return nil, err
+	}
 
-	// 	jwtSource := gcrJWT{}
+	// get oauth2 access token
+	_, err = gcp.GetBearerToken(r.getTokenCache, r.setTokenCacheFunc(repo))
 
-	// 	if err := json.NewDecoder(resp.Body).Decode(&jwtSource); err != nil {
-	// 		return nil, fmt.Errorf("Invalid token JSON from metadata: %v", err)
-	// 	}
+	if err != nil {
+		return nil, err
+	}
 
-	// 	_, err = repo.Registry.UpdateRegistryDockerTokenCache(
-	// 		&ints.RegTokenCache{
-	// 			RegistryID: r.ID,
-	// 			Token:      []byte(jwtSource.AccessToken),
-	// 			// subtract some time from expiry for buffer
-	// 			Expiry: time.Now().Add(time.Second*time.Duration(jwtSource.ExpiresInSec) - 5*time.Second),
-	// 		},
-	// 	)
+	// it's now written to the token cache, so return
+	cache, err := r.getTokenCache()
 
-	// 	if err != nil {
-	// 		return nil, err
-	// 	}
+	if err != nil {
+		return nil, err
+	}
 
-	// 	jwtTok = jwtSource.AccessToken
-	// }
+	return cache, nil
+}
 
+func (r *Registry) listGCRRepositories(
+	repo repository.Repository,
+) ([]*Repository, error) {
 	gcp, err := repo.GCPIntegration.ReadGCPIntegration(
 		r.GCPIntegrationID,
 	)

+ 0 - 11
package-lock.json

@@ -1,11 +0,0 @@
-{
-  "requires": true,
-  "lockfileVersion": 1,
-  "dependencies": {
-    "@types/random-words": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/@types/random-words/-/random-words-1.1.0.tgz",
-      "integrity": "sha512-YZqkHIAGoXv6mlyEOwluhMjF9aotH2m9HrCCR+PNtES/ED00u5u4W7X1lWxc5AaFRKdKx+5orWTFW7iyGrZpOQ=="
-    }
-  }
-}

+ 57 - 2
server/api/registry_handler.go

@@ -99,7 +99,7 @@ func (app *App) HandleListProjectRegistries(w http.ResponseWriter, r *http.Reque
 }
 
 // temp -- token response
-type ECRTokenResponse struct {
+type RegTokenResponse struct {
 	Token     string     `json:"token"`
 	ExpiresAt *time.Time `json:"expires_at"`
 }
@@ -158,7 +158,62 @@ func (app *App) HandleGetProjectRegistryECRToken(w http.ResponseWriter, r *http.
 		}
 	}
 
-	resp := &ECRTokenResponse{
+	resp := &RegTokenResponse{
+		Token:     token,
+		ExpiresAt: expiresAt,
+	}
+
+	w.WriteHeader(http.StatusOK)
+
+	if err := json.NewEncoder(w).Encode(resp); err != nil {
+		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
+		return
+	}
+}
+
+type GCRTokenRequestBody struct {
+	ServerURL string `json:"server_url"`
+}
+
+// HandleGetProjectRegistryGCRToken gets a GCR token for a registry
+func (app *App) HandleGetProjectRegistryGCRToken(w http.ResponseWriter, r *http.Request) {
+	projID, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
+
+	if err != nil || projID == 0 {
+		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
+		return
+	}
+
+	reqBody := &GCRTokenRequestBody{}
+
+	// decode from JSON to form value
+	if err := json.NewDecoder(r.Body).Decode(reqBody); err != nil {
+		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
+		return
+	}
+
+	// list registries and find one that matches the region
+	regs, err := app.Repo.Registry.ListRegistriesByProjectID(uint(projID))
+	var token string
+	var expiresAt *time.Time
+
+	for _, reg := range regs {
+		if reg.GCPIntegrationID != 0 && reg.URL == reqBody.ServerURL {
+			_reg := registry.Registry(*reg)
+
+			tokenCache, err := _reg.GetGCRToken(*app.Repo)
+
+			if err != nil {
+				app.handleErrorDataRead(err, w)
+				return
+			}
+
+			token = string(tokenCache.Token)
+			expiresAt = &tokenCache.Expiry
+		}
+	}
+
+	resp := &RegTokenResponse{
 		Token:     token,
 		ExpiresAt: expiresAt,
 	}

+ 1 - 1
server/router/middleware/auth.go

@@ -327,7 +327,7 @@ func (auth *Auth) DoesUserHaveInfraAccess(
 	infraLoc IDLocation,
 ) http.Handler {
 	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		infraID, err := findGitRepoIDInRequest(r, infraLoc)
+		infraID, err := findInfraIDInRequest(r, infraLoc)
 
 		if err != nil {
 			http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)

+ 10 - 0
server/router/router.go

@@ -476,6 +476,16 @@ func New(a *api.App) *chi.Mux {
 			),
 		)
 
+		r.Method(
+			"GET",
+			"/projects/{project_id}/registries/gcr/token",
+			auth.DoesUserHaveProjectAccess(
+				requestlog.NewHandler(a.HandleGetProjectRegistryGCRToken, l),
+				mw.URLParam,
+				mw.WriteAccess,
+			),
+		)
+
 		r.Method(
 			"DELETE",
 			"/projects/{project_id}/registries/{registry_id}",