Преглед изворни кода

gcr w/ server url and docker configure

Alexander Belanger пре 5 година
родитељ
комит
90dd826c87

+ 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)
 

+ 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
 }
 

+ 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,
 	)

+ 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,
 	}

+ 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}",