Переглянути джерело

github action cli cmd completed+working

Alexander Belanger 5 роки тому
батько
коміт
e4c94caa9c

+ 41 - 0
cli/cmd/api/git_repo.go

@@ -0,0 +1,41 @@
+package api
+
+import (
+	"context"
+	"fmt"
+	"net/http"
+
+	"github.com/porter-dev/porter/internal/models"
+)
+
+// ListGitRepoResponse is the list of Git repo integrations for a project
+type ListGitRepoResponse []models.GitRepoExternal
+
+// ListGitRepos returns a list of Git repos for a project
+func (c *Client) ListGitRepos(
+	ctx context.Context,
+	projectID uint,
+) (ListGitRepoResponse, error) {
+	req, err := http.NewRequest(
+		"GET",
+		fmt.Sprintf("%s/projects/%d/gitrepos", c.BaseURL, projectID),
+		nil,
+	)
+
+	if err != nil {
+		return nil, err
+	}
+
+	req = req.WithContext(ctx)
+	bodyResp := &ListGitRepoResponse{}
+
+	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
+}

+ 61 - 0
cli/cmd/api/github_action.go

@@ -0,0 +1,61 @@
+package api
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"strings"
+)
+
+// CreateGithubActionRequest represents the accepted fields for creating
+// a Github action
+type CreateGithubActionRequest struct {
+	GitRepo        string `json:"git_repo"`
+	ImageRepoURI   string `json:"image_repo_uri"`
+	DockerfilePath string `json:"dockerfile_path"`
+	GitRepoID      uint   `json:"git_repo_id"`
+}
+
+// CreateGithubAction creates a Github action with basic authentication
+func (c *Client) CreateGithubAction(
+	ctx context.Context,
+	projectID, clusterID uint,
+	releaseName, releaseNamespace string,
+	createGH *CreateGithubActionRequest,
+) error {
+	data, err := json.Marshal(createGH)
+
+	if err != nil {
+		return err
+	}
+
+	req, err := http.NewRequest(
+		"POST",
+		fmt.Sprintf(
+			"%s/projects/%d/ci/actions?cluster_id=%d&name=%s&namespace=%s",
+			c.BaseURL,
+			projectID,
+			clusterID,
+			releaseName,
+			releaseNamespace,
+		),
+		strings.NewReader(string(data)),
+	)
+
+	if err != nil {
+		return err
+	}
+
+	req = req.WithContext(ctx)
+
+	if httpErr, err := c.sendRequest(req, nil, true); httpErr != nil || err != nil {
+		if httpErr != nil {
+			return fmt.Errorf("code %d, errors %v", httpErr.Code, httpErr.Errors)
+		}
+
+		return err
+	}
+
+	return nil
+}

+ 20 - 0
cli/cmd/connect.go

@@ -43,6 +43,18 @@ var connectECRCmd = &cobra.Command{
 	},
 }
 
+var connectActionsCmd = &cobra.Command{
+	Use:   "actions",
+	Short: "Adds Github Actions to a project",
+	Run: func(cmd *cobra.Command, args []string) {
+		err := checkLoginAndRun(args, runConnectActions)
+
+		if err != nil {
+			os.Exit(1)
+		}
+	},
+}
+
 var connectGCRCmd = &cobra.Command{
 	Use:   "gcr",
 	Short: "Adds a GCR instance to a project",
@@ -113,6 +125,7 @@ func init() {
 		"the context to connect (defaults to the current context)",
 	)
 
+	connectCmd.AddCommand(connectActionsCmd)
 	connectCmd.AddCommand(connectECRCmd)
 	connectCmd.AddCommand(connectGCRCmd)
 	connectCmd.AddCommand(connectDOCRCmd)
@@ -192,3 +205,10 @@ func runConnectHelmRepoBasic(_ *api.AuthCheckResponse, client *api.Client, _ []s
 
 	return setHelmRepo(hrID)
 }
+
+func runConnectActions(_ *api.AuthCheckResponse, client *api.Client, _ []string) error {
+	return connect.Actions(
+		client,
+		getProjectID(),
+	)
+}

+ 125 - 0
cli/cmd/connect/actions.go

@@ -0,0 +1,125 @@
+package connect
+
+import (
+	"context"
+	"fmt"
+	"strconv"
+	"time"
+
+	"github.com/porter-dev/porter/cli/cmd/api"
+	"github.com/porter-dev/porter/cli/cmd/utils"
+
+	ints "github.com/porter-dev/porter/internal/models/integrations"
+)
+
+// Actions creates a github actions integration
+func Actions(
+	client *api.Client,
+	projectID uint,
+) error {
+	// if project ID is 0, ask the user to set the project ID or create a project
+	if projectID == 0 {
+		return fmt.Errorf("no project set, please run porter project set [id]")
+	}
+
+	// list oauth integrations and make sure Github exists
+	oauthInts, err := client.ListOAuthIntegrations(context.TODO(), projectID)
+
+	if err != nil {
+		return err
+	}
+
+	linkedGH := false
+
+	// iterate through oauth integrations to find do
+	for _, oauthInt := range oauthInts {
+		if oauthInt.Client == ints.OAuthGithub {
+			linkedGH = true
+			break
+		}
+	}
+
+	if !linkedGH {
+		_, err = triggerGithubOAuth(client, projectID)
+
+		if err != nil {
+			return err
+		}
+	}
+
+	gitRepos, err := client.ListGitRepos(context.TODO(), projectID)
+
+	gitRepoID := gitRepos[0].ID
+
+	// prompts (unfortunately a lot)
+	clusterIDStr, _ := utils.PromptPlaintext(fmt.Sprintf(`Please provide the cluster id (can be found with "porter clusters list").
+Cluster ID: `))
+	clusterID, err := strconv.ParseUint(clusterIDStr, 10, 64)
+
+	if err != nil {
+		return err
+	}
+
+	releaseName, _ := utils.PromptPlaintext(fmt.Sprintf(`Release name:`))
+	releaseNamespace, _ := utils.PromptPlaintext(fmt.Sprintf(`Release namespace:`))
+	gitRepo, _ := utils.PromptPlaintext(fmt.Sprintf(`Please enter the Github repo, in the form ${owner}/${repo_name}. For example, porter-dev/porter.
+Github repo:`))
+
+	imageRepo, _ := utils.PromptPlaintext(fmt.Sprintf(`Please enter the image repo url.
+Image repo:`))
+
+	dockerfilePath, _ := utils.PromptPlaintext(fmt.Sprintf(`Please enter the path in the repo to your dockerfile.
+Dockerfile path:`))
+
+	err = client.CreateGithubAction(
+		context.Background(),
+		projectID,
+		uint(clusterID),
+		releaseName,
+		releaseNamespace,
+		&api.CreateGithubActionRequest{
+			GitRepo:        gitRepo,
+			ImageRepoURI:   imageRepo,
+			DockerfilePath: dockerfilePath,
+			GitRepoID:      gitRepoID,
+		},
+	)
+
+	return err
+}
+
+func triggerGithubOAuth(client *api.Client, projectID uint) (ints.OAuthIntegrationExternal, error) {
+	var ghAuth ints.OAuthIntegrationExternal
+
+	oauthURL := fmt.Sprintf("%s/oauth/projects/%d/github", client.BaseURL, projectID)
+
+	fmt.Printf("Please visit %s in your browser to connect to Github (it should open automatically).", oauthURL)
+	utils.OpenBrowser(oauthURL)
+
+	for {
+		oauthInts, err := client.ListOAuthIntegrations(context.TODO(), projectID)
+
+		if err != nil {
+			return ghAuth, err
+		}
+
+		linkedGH := false
+
+		// iterate through oauth integrations to find do
+		for _, oauthInt := range oauthInts {
+			if oauthInt.Client == ints.OAuthGithub {
+				linkedGH = true
+				ghAuth = oauthInt
+				break
+			}
+		}
+
+		if linkedGH {
+			break
+		}
+
+		time.Sleep(2 * time.Second)
+	}
+
+	return ghAuth, nil
+}

+ 1 - 0
cmd/app/main.go

@@ -57,6 +57,7 @@ func main() {
 		&models.ClusterCandidate{},
 		&models.ClusterResolver{},
 		&models.Infra{},
+		&models.GitActionConfig{},
 		&ints.KubeIntegration{},
 		&ints.BasicIntegration{},
 		&ints.OIDCIntegration{},

+ 1 - 0
cmd/migrate/main.go

@@ -37,6 +37,7 @@ func main() {
 		&models.ClusterCandidate{},
 		&models.ClusterResolver{},
 		&models.Infra{},
+		&models.GitActionConfig{},
 		&ints.KubeIntegration{},
 		&ints.BasicIntegration{},
 		&ints.OIDCIntegration{},

+ 10 - 8
internal/forms/git_action.go

@@ -7,18 +7,20 @@ import (
 // CreateGitAction represents the accepted values for creating a
 // github action integration
 type CreateGitAction struct {
-	ReleaseID    uint   `json:"release_id" form:"required"`
-	GitRepo      string `json:"git_repo" form:"required"`
-	ImageRepoURI string `json:"image_repo_uri" form:"required"`
-	GitRepoID    uint   `json:"git_repo_id" form:"required"`
+	ReleaseID      uint   `json:"release_id" form:"required"`
+	GitRepo        string `json:"git_repo" form:"required"`
+	ImageRepoURI   string `json:"image_repo_uri" form:"required"`
+	DockerfilePath string `json:"dockerfile_path" form:"required"`
+	GitRepoID      uint   `json:"git_repo_id" form:"required"`
 }
 
 // ToGitActionConfig converts the form to a gorm git action config model
 func (ca *CreateGitAction) ToGitActionConfig() (*models.GitActionConfig, error) {
 	return &models.GitActionConfig{
-		ReleaseID:    ca.ReleaseID,
-		GitRepo:      ca.GitRepo,
-		ImageRepoURI: ca.ImageRepoURI,
-		GitRepoID:    ca.GitRepoID,
+		ReleaseID:      ca.ReleaseID,
+		GitRepo:        ca.GitRepo,
+		ImageRepoURI:   ca.ImageRepoURI,
+		DockerfilePath: ca.DockerfilePath,
+		GitRepoID:      ca.GitRepoID,
 	}, nil
 }

+ 2 - 0
internal/integrations/ci/actions/actions.go

@@ -207,6 +207,8 @@ func (g *GithubActions) commitGithubFile(
 	client *github.Client,
 	contents []byte,
 ) (string, error) {
+	fmt.Println("GITHUB ACTION CONTENTS ARE", string(contents))
+
 	opts := &github.RepositoryContentFileOptions{
 		Message: github.String("Create porter.yml file"),
 		Content: contents,

+ 3 - 3
internal/integrations/ci/actions/steps.go

@@ -27,7 +27,7 @@ porter docker configure
 
 func getConfigurePorterStep(porterTokenSecretName string) GithubActionYAMLStep {
 	return GithubActionYAMLStep{
-		Name: "Download Porter",
+		Name: "Configure Porter",
 		ID:   "configure_porter",
 		Run:  fmt.Sprintf(configure, porterTokenSecretName),
 	}
@@ -52,8 +52,8 @@ curl -X POST 'https://dashboard.getporter.dev/api/webhooks/deploy/${{secrets.%s}
 
 func deployPorterWebhookStep(webhookTokenSecretName, repoURL string) GithubActionYAMLStep {
 	return GithubActionYAMLStep{
-		Name: "Docker build, push",
-		ID:   "docker_build_push",
+		Name: "Deploy on Porter",
+		ID:   "deploy_porter",
 		Run:  fmt.Sprintf(deployPorter, webhookTokenSecretName, repoURL),
 	}
 }

+ 1 - 1
server/api/api.go

@@ -120,7 +120,7 @@ func New(conf *AppConfig) (*App, error) {
 		app.GithubConf = oauth.NewGithubClient(&oauth.Config{
 			ClientID:     sc.GithubClientID,
 			ClientSecret: sc.GithubClientSecret,
-			Scopes:       []string{"repo", "user", "read:user"},
+			Scopes:       []string{"repo", "read:user", "workflow"},
 			BaseURL:      sc.ServerURL,
 		})
 	}

+ 3 - 2
server/api/git_action_handler.go

@@ -24,9 +24,8 @@ func (app *App) HandleCreateGitAction(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	name := chi.URLParam(r, "name")
-
 	vals, err := url.ParseQuery(r.URL.RawQuery)
+	name := vals["name"][0]
 	namespace := vals["namespace"][0]
 
 	clusterID, err := strconv.ParseUint(vals["cluster_id"][0], 10, 64)
@@ -103,6 +102,7 @@ func (app *App) HandleCreateGitAction(w http.ResponseWriter, r *http.Request) {
 	})
 
 	if err != nil {
+		fmt.Println("ERROR GENERATING TOKEN", err)
 		http.Error(w, err.Error(), http.StatusInternalServerError)
 		return
 	}
@@ -125,6 +125,7 @@ func (app *App) HandleCreateGitAction(w http.ResponseWriter, r *http.Request) {
 	_, err = gaRunner.Setup()
 
 	if err != nil {
+		fmt.Println("ERROR RUNNING SETUP", err)
 		http.Error(w, err.Error(), http.StatusInternalServerError)
 		return
 	}

+ 15 - 0
server/router/router.go

@@ -196,6 +196,21 @@ func New(a *api.App) *chi.Mux {
 			),
 		)
 
+		// /api/projects/{project_id}/ci routes
+		r.Method(
+			"POST",
+			"/projects/{project_id}/ci/actions",
+			auth.DoesUserHaveProjectAccess(
+				auth.DoesUserHaveClusterAccess(
+					requestlog.NewHandler(a.HandleCreateGitAction, l),
+					mw.URLParam,
+					mw.QueryParam,
+				),
+				mw.URLParam,
+				mw.ReadAccess,
+			),
+		)
+
 		// /api/projects/{project_id}/infra routes
 		r.Method(
 			"GET",