Kaynağa Gözat

actions setup completed

Alexander Belanger 5 yıl önce
ebeveyn
işleme
d339d65e15

+ 32 - 0
cli/cmd/api/api.go

@@ -1,11 +1,13 @@
 package api
 
 import (
+	"encoding/base64"
 	"encoding/json"
 	"fmt"
 	"io/ioutil"
 	"net/http"
 	"path/filepath"
+	"strings"
 	"time"
 
 	"k8s.io/client-go/util/homedir"
@@ -137,3 +139,33 @@ func (c *Client) getCookie() (*http.Cookie, error) {
 
 	return cookie.Cookie, nil
 }
+
+type TokenProjectID struct {
+	ProjectID uint `json:"project_id"`
+}
+
+func GetProjectIDFromToken(token string) (uint, error) {
+	var encoded string
+
+	if tokenSplit := strings.Split(token, "."); len(tokenSplit) != 3 {
+		return 0, fmt.Errorf("invalid jwt token format")
+	} else {
+		encoded = tokenSplit[1]
+	}
+
+	decodedBytes, err := base64.RawStdEncoding.DecodeString(encoded)
+
+	if err != nil {
+		return 0, fmt.Errorf("could not decode jwt token from base64: %v", err)
+	}
+
+	res := &TokenProjectID{}
+
+	err = json.Unmarshal(decodedBytes, res)
+
+	if err != nil {
+		return 0, fmt.Errorf("could not get token project id: %v", err)
+	}
+
+	return res.ProjectID, nil
+}

+ 19 - 4
cli/cmd/auth.go

@@ -129,11 +129,26 @@ func login() error {
 
 	color.New(color.FgGreen).Println("Successfully logged in!")
 
-	// get a list of projects, and set the current project
-	projects, err := client.ListUserProjects(context.Background(), _user.ID)
+	// if the login was token-based, decode the claims to get the token
+	if token != "" {
+		projID, err := api.GetProjectIDFromToken(token)
+
+		if err != nil {
+			return err
+		}
+
+		setProject(projID)
+	} else {
+		// get a list of projects, and set the current project
+		projects, err := client.ListUserProjects(context.Background(), _user.ID)
+
+		if err != nil {
+			return err
+		}
 
-	if len(projects) > 0 {
-		setProject(projects[0].ID)
+		if len(projects) > 0 {
+			setProject(projects[0].ID)
+		}
 	}
 
 	return nil

+ 5 - 5
internal/auth/token/token.go

@@ -20,11 +20,11 @@ type TokenGeneratorConf struct {
 }
 
 type Token struct {
-	SubKind   Subject
-	Sub       string
-	ProjectID uint
-	IBy       uint
-	IAt       *time.Time
+	SubKind   Subject    `json:"sub_kind"`
+	Sub       string     `json:"sub"`
+	ProjectID uint       `json:"project_id"`
+	IBy       uint       `json:"iby"`
+	IAt       *time.Time `json:"iat"`
 }
 
 func GetTokenForUser(userID, projID uint) (*Token, error) {

+ 110 - 25
internal/integrations/ci/actions/actions.go

@@ -12,13 +12,15 @@ import (
 	"golang.org/x/oauth2"
 
 	"strings"
+
+	"gopkg.in/yaml.v2"
 )
 
 type GithubActions struct {
-	GitRepo      *models.GitRepo
-	GitRepoName  string
-	GitRepoOwner string
-	Repo         repository.Repository
+	GitIntegration *models.GitRepo
+	GitRepoName    string
+	GitRepoOwner   string
+	Repo           repository.Repository
 
 	GithubConf *oauth2.Config
 
@@ -26,58 +28,112 @@ type GithubActions struct {
 	PorterToken  string
 	ProjectID    uint
 	ReleaseName  string
+
+	DockerFilePath string
+	ImageRepoURL   string
+
+	defaultBranch string
 }
 
-func (g *GithubActions) Setup() error {
+func (g *GithubActions) Setup() (string, error) {
 	client, err := g.getClient()
 
 	if err != nil {
-		return err
+		return "", err
+	}
+
+	// get the repository to find the default branch
+	repo, _, err := client.Repositories.Get(
+		context.TODO(),
+		g.GitRepoOwner,
+		g.GitRepoName,
+	)
+
+	if err != nil {
+		return "", err
 	}
 
+	g.defaultBranch = repo.GetDefaultBranch()
+
 	// create a new secret with a webhook token
 	err = g.createGithubSecret(client, g.getWebhookSecretName(), g.WebhookToken)
 
 	if err != nil {
-		return err
+		return "", err
 	}
 
 	// create a new secret with a porter token
 	err = g.createGithubSecret(client, g.getPorterTokenSecretName(), g.PorterToken)
 
 	if err != nil {
-		return err
+		return "", err
 	}
 
-	return nil
+	fileBytes, err := g.GetGithubActionYAML()
+
+	if err != nil {
+		return "", err
+	}
+
+	return g.commitGithubFile(client, fileBytes)
+}
+
+type GithubActionYAMLStep struct {
+	Name string `yaml:"name"`
+	ID   string `yaml:"id"`
+	Run  string `yaml:"run"`
+}
+
+type GithubActionYAMLOnPushBranches struct {
+	Branches []string `yaml:"branches"`
+}
+
+type GithubActionYAMLOnPush struct {
+	Push GithubActionYAMLOnPushBranches `yaml:"push"`
+}
+
+type GithubActionYAMLJob struct {
+	RunsOn string                 `yaml:"runs-on"`
+	Steps  []GithubActionYAMLStep `yaml:"steps"`
 }
 
 type GithubActionYAML struct {
-	On struct {
-		Push struct {
-			Branches []string `yaml:"branches"`
-		} `yaml:"push"`
-	} `yaml:"on"`
+	On GithubActionYAMLOnPush `yaml:"on"`
 
 	Name string `yaml:"name"`
 
-	Jobs map[string]struct {
-		RunsOn string `yaml:"runs-on"`
-		Steps  []struct {
-			Name string `yaml:"name"`
-			ID   string `yaml:"id"`
-			// TODO -- OTHER RELEVANT STUFF
-		} `yaml:"steps"`
-	} `yaml:"jobs"`
+	Jobs map[string]GithubActionYAMLJob `yaml:"jobs"`
 }
 
-func (g *GithubActions) GetGithubActionYAML() (*github.Client, error) {
-	return nil, nil
+func (g *GithubActions) GetGithubActionYAML() ([]byte, error) {
+	actionYAML := &GithubActionYAML{
+		On: GithubActionYAMLOnPush{
+			Push: GithubActionYAMLOnPushBranches{
+				Branches: []string{
+					g.defaultBranch,
+				},
+			},
+		},
+		Name: "Deploy to Porter",
+		Jobs: map[string]GithubActionYAMLJob{
+			"porter-deploy": GithubActionYAMLJob{
+				RunsOn: "ubuntu-latest",
+				Steps: []GithubActionYAMLStep{
+					getDownloadPorterStep(),
+					getConfigurePorterStep(g.getPorterTokenSecretName()),
+					getDockerBuildPushStep(g.DockerFilePath, g.ImageRepoURL),
+					deployPorterWebhookStep(g.getWebhookSecretName(), g.ImageRepoURL),
+				},
+			},
+		},
+	}
+
+	return yaml.Marshal(actionYAML)
 }
 
 func (g *GithubActions) getClient() (*github.Client, error) {
 	// get the oauth integration
-	oauthInt, err := g.Repo.OAuthIntegration.ReadOAuthIntegration(g.GitRepo.OAuthIntegrationID)
+	oauthInt, err := g.Repo.OAuthIntegration.ReadOAuthIntegration(g.GitIntegration.OAuthIntegrationID)
 
 	if err != nil {
 		return nil, err
@@ -146,3 +202,32 @@ func (g *GithubActions) getWebhookSecretName() string {
 func (g *GithubActions) getPorterTokenSecretName() string {
 	return fmt.Sprintf("PORTER_TOKEN_%d", g.ProjectID)
 }
+
+func (g *GithubActions) commitGithubFile(
+	client *github.Client,
+	contents []byte,
+) (string, error) {
+	opts := &github.RepositoryContentFileOptions{
+		Message: github.String("Create porter.yml file"),
+		Content: contents,
+		Branch:  github.String(g.defaultBranch),
+		Committer: &github.CommitAuthor{
+			Name:  github.String("Porter Bot"),
+			Email: github.String("contact@getporter.dev"),
+		},
+	}
+
+	resp, _, err := client.Repositories.CreateFile(
+		context.TODO(),
+		g.GitRepoOwner,
+		g.GitRepoName,
+		".github/workflows/porter.yml",
+		opts,
+	)
+
+	if err != nil {
+		return "", err
+	}
+
+	return *resp.Commit.SHA, nil
+}

+ 59 - 0
internal/integrations/ci/actions/steps.go

@@ -0,0 +1,59 @@
+package actions
+
+import "fmt"
+
+const download string = `
+name=$(curl -s https://api.github.com/repos/porter-dev/porter/releases/latest | grep "browser_download_url.*/porter_.*_Linux_x86_64\.zip" | cut -d ":" -f 2,3 | tr -d \")
+name=$(basename $name)
+curl -L https://github.com/porter-dev/porter/releases/latest/download/$name --output $name
+unzip -a $name
+rm $name
+chmod +x ./porter
+sudo mv ./porter /usr/local/bin/porter
+`
+
+func getDownloadPorterStep() GithubActionYAMLStep {
+	return GithubActionYAMLStep{
+		Name: "Download Porter",
+		ID:   "download_porter",
+		Run:  download,
+	}
+}
+
+const configure string = `
+porter auth login --token ${{secrets.%s}}
+porter docker configure
+`
+
+func getConfigurePorterStep(porterTokenSecretName string) GithubActionYAMLStep {
+	return GithubActionYAMLStep{
+		Name: "Download Porter",
+		ID:   "configure_porter",
+		Run:  fmt.Sprintf(configure, porterTokenSecretName),
+	}
+}
+
+const dockerBuildPush string = `
+docker build . --file %s -t %s:$(git rev-parse --short HEAD)
+docker push %s:$(git rev-parse --short HEAD)
+`
+
+func getDockerBuildPushStep(dockerFilePath, repoURL string) GithubActionYAMLStep {
+	return GithubActionYAMLStep{
+		Name: "Docker build, push",
+		ID:   "docker_build_push",
+		Run:  fmt.Sprintf(dockerBuildPush, dockerFilePath, repoURL, repoURL),
+	}
+}
+
+const deployPorter string = `
+curl -X POST 'https://dashboard.getporter.dev/api/webhooks/deploy/${{secrets.%s}}?commit=$(git rev-parse --short HEAD)&repository=%s'
+`
+
+func deployPorterWebhookStep(webhookTokenSecretName, repoURL string) GithubActionYAMLStep {
+	return GithubActionYAMLStep{
+		Name: "Docker build, push",
+		ID:   "docker_build_push",
+		Run:  fmt.Sprintf(deployPorter, webhookTokenSecretName, repoURL),
+	}
+}