2
0
Alexander Belanger 5 жил өмнө
parent
commit
64818ee982

+ 24 - 0
internal/forms/git_action.go

@@ -0,0 +1,24 @@
+package forms
+
+import (
+	"github.com/porter-dev/porter/internal/models"
+)
+
+// 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"`
+}
+
+// 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,
+	}, nil
+}

+ 48 - 0
internal/models/gitrepo.go

@@ -43,3 +43,51 @@ func (r *GitRepo) Externalize() *GitRepoExternal {
 		Service:    integrations.Github,
 	}
 }
+
+// GitActionConfig is a configuration for release's CI integration via
+// Github Actions
+type GitActionConfig struct {
+	gorm.Model
+
+	// The ID of the release that this is linked to
+	ReleaseID uint `json:"release_id"`
+
+	// The git repo in ${owner}/${repo} form
+	GitRepo string `json:"git_repo"`
+
+	// The complete image repository uri to pull from
+	ImageRepoURI string `json:"image_repo_uri"`
+
+	// The git integration id
+	GitRepoID uint `json:"git_repo_id"`
+
+	// The path to the dockerfile in the git repo
+	DockerfilePath string `json:"dockerfile_path" form:"required"`
+}
+
+// GitActionConfigExternal is an external GitActionConfig to be shared over REST
+type GitActionConfigExternal struct {
+	gorm.Model
+
+	// The git repo in ${owner}/${repo} form
+	GitRepo string `json:"git_repo"`
+
+	// The complete image repository uri to pull from
+	ImageRepoURI string `json:"image_repo_uri"`
+
+	// The git integration id
+	GitRepoID uint `json:"git_repo_id"`
+
+	// The path to the dockerfile in the git repo
+	DockerfilePath string `json:"dockerfile_path" form:"required"`
+}
+
+// Externalize generates an external GitActionConfig to be shared over REST
+func (r *GitActionConfig) Externalize() *GitActionConfigExternal {
+	return &GitActionConfigExternal{
+		GitRepo:        r.GitRepo,
+		ImageRepoURI:   r.ImageRepoURI,
+		GitRepoID:      r.GitRepoID,
+		DockerfilePath: r.DockerfilePath,
+	}
+}

+ 11 - 8
internal/models/release.go

@@ -10,24 +10,27 @@ import (
 type Release struct {
 	gorm.Model
 
-	WebhookToken string `json:"webhook_token" gorm:"unique"`
-	ClusterID    uint   `json:"cluster_id"`
-	ProjectID    uint   `json:"project_id"`
-	Name         string `json:"name"`
-	Namespace    string `json:"namespace"`
+	WebhookToken    string          `json:"webhook_token" gorm:"unique"`
+	ClusterID       uint            `json:"cluster_id"`
+	ProjectID       uint            `json:"project_id"`
+	Name            string          `json:"name"`
+	Namespace       string          `json:"namespace"`
+	GitActionConfig GitActionConfig `json:"git_action_config"`
 }
 
 // ReleaseExternal represents the Release type that is sent over REST
 type ReleaseExternal struct {
 	ID uint `json:"id"`
 
-	WebhookToken string `json:"webhook_token"`
+	WebhookToken    string                   `json:"webhook_token"`
+	GitActionConfig *GitActionConfigExternal `json:"git_action_config,omitempty"`
 }
 
 // Externalize generates an external User to be shared over REST
 func (r *Release) Externalize() *ReleaseExternal {
 	return &ReleaseExternal{
-		ID:           r.ID,
-		WebhookToken: r.WebhookToken,
+		ID:              r.ID,
+		WebhookToken:    r.WebhookToken,
+		GitActionConfig: r.GitActionConfig.Externalize(),
 	}
 }

+ 10 - 0
internal/repository/git_action_config.go

@@ -0,0 +1,10 @@
+package repository
+
+import "github.com/porter-dev/porter/internal/models"
+
+// GitActionConfigRepository represents the set of queries on the
+// GitActionConfig model
+type GitActionConfigRepository interface {
+	CreateGitActionConfig(gr *models.GitActionConfig) (*models.GitActionConfig, error)
+	ReadGitActionConfig(id uint) (*models.GitActionConfig, error)
+}

+ 51 - 0
internal/repository/gorm/git_action_config.go

@@ -0,0 +1,51 @@
+package gorm
+
+import (
+	"github.com/porter-dev/porter/internal/models"
+	"github.com/porter-dev/porter/internal/repository"
+	"gorm.io/gorm"
+)
+
+// GitActionConfigRepository uses gorm.DB for querying the database
+type GitActionConfigRepository struct {
+	db *gorm.DB
+}
+
+// NewGitActionConfigRepository returns a GitActionConfigRepository which uses
+// gorm.DB for querying the database. It accepts an encryption key to encrypt
+// sensitive data
+func NewGitActionConfigRepository(db *gorm.DB) repository.GitActionConfigRepository {
+	return &GitActionConfigRepository{db}
+}
+
+// CreateGitActionConfig creates a new git repo
+func (repo *GitActionConfigRepository) CreateGitActionConfig(ga *models.GitActionConfig) (*models.GitActionConfig, error) {
+	release := &models.Release{}
+
+	if err := repo.db.Where("id = ?", ga.ReleaseID).First(&release).Error; err != nil {
+		return nil, err
+	}
+
+	assoc := repo.db.Model(&release).Association("GitActionConfig")
+
+	if assoc.Error != nil {
+		return nil, assoc.Error
+	}
+
+	if err := assoc.Append(ga); err != nil {
+		return nil, err
+	}
+
+	return ga, nil
+}
+
+// ReadGitActionConfig gets a git repo specified by a unique id
+func (repo *GitActionConfigRepository) ReadGitActionConfig(id uint) (*models.GitActionConfig, error) {
+	ga := &models.GitActionConfig{}
+
+	if err := repo.db.Where("id = ?", id).First(&ga).Error; err != nil {
+		return nil, err
+	}
+
+	return ga, nil
+}

+ 70 - 0
internal/repository/gorm/git_action_config_test.go

@@ -0,0 +1,70 @@
+package gorm_test
+
+import (
+	"testing"
+
+	"github.com/go-test/deep"
+	"github.com/porter-dev/porter/internal/models"
+	orm "gorm.io/gorm"
+)
+
+func TestCreateGitActionConfig(t *testing.T) {
+	tester := &tester{
+		dbFileName: "./porter_create_ga.db",
+	}
+
+	setupTestEnv(tester, t)
+	initUser(tester, t)
+	initProject(tester, t)
+	initRelease(tester, t)
+	defer cleanup(tester, t)
+
+	ga := &models.GitActionConfig{
+		ReleaseID:    1,
+		GitRepo:      "porter-dev/porter",
+		ImageRepoURI: "gcr.io/project-123456/nginx",
+		GitRepoID:    1,
+	}
+
+	expGA := *ga
+
+	ga, err := tester.repo.GitActionConfig.CreateGitActionConfig(ga)
+
+	if err != nil {
+		t.Fatalf("%v\n", err)
+	}
+
+	ga, err = tester.repo.GitActionConfig.ReadGitActionConfig(ga.Model.ID)
+
+	if err != nil {
+		t.Fatalf("%v\n", err)
+	}
+
+	// make sure id is 1
+	if ga.Model.ID != 1 {
+		t.Errorf("incorrect git repo ID: expected %d, got %d\n", 1, ga.Model.ID)
+	}
+
+	// reset fields for reflect.DeepEqual
+	ga.Model = orm.Model{}
+
+	if diff := deep.Equal(expGA, *ga); diff != nil {
+		t.Errorf("incorrect git action config")
+		t.Error(diff)
+	}
+
+	// read the release and make sure GitActionConfig is expected
+	release, err := tester.repo.Release.ReadReleaseByWebhookToken("abcdefgh")
+
+	if err != nil {
+		t.Fatalf("%v\n", err)
+	}
+
+	gotReleaseGA := release.GitActionConfig
+	gotReleaseGA.Model = orm.Model{}
+
+	if diff := deep.Equal(expGA, gotReleaseGA); diff != nil {
+		t.Errorf("incorrect git action config")
+		t.Error(diff)
+	}
+}

+ 26 - 0
internal/repository/gorm/helpers_test.go

@@ -23,6 +23,7 @@ type tester struct {
 	initClusters []*models.Cluster
 	initHRs      []*models.HelmRepo
 	initInfras   []*models.Infra
+	initReleases []*models.Release
 	initCCs      []*models.ClusterCandidate
 	initKIs      []*ints.KubeIntegration
 	initBasics   []*ints.BasicIntegration
@@ -58,6 +59,7 @@ func setupTestEnv(tester *tester, t *testing.T) {
 		&models.ClusterCandidate{},
 		&models.ClusterResolver{},
 		&models.Infra{},
+		&models.GitActionConfig{},
 		&ints.KubeIntegration{},
 		&ints.BasicIntegration{},
 		&ints.OIDCIntegration{},
@@ -457,3 +459,27 @@ func initInfra(tester *tester, t *testing.T) {
 
 	tester.initInfras = append(tester.initInfras, infra)
 }
+
+func initRelease(tester *tester, t *testing.T) {
+	t.Helper()
+
+	if len(tester.initProjects) == 0 {
+		initProject(tester, t)
+	}
+
+	release := &models.Release{
+		Name:         "denver-meister-dakota",
+		Namespace:    "default",
+		ProjectID:    1,
+		ClusterID:    1,
+		WebhookToken: "abcdefgh",
+	}
+
+	release, err := tester.repo.Release.CreateRelease(release)
+
+	if err != nil {
+		t.Fatalf("%v\n", err)
+	}
+
+	tester.initReleases = append(tester.initReleases, release)
+}

+ 3 - 3
internal/repository/gorm/release.go

@@ -26,9 +26,9 @@ func (repo *ReleaseRepository) CreateRelease(release *models.Release) (*models.R
 }
 
 // ReadRelease finds a single release based on their unique name and namespace pair.
-func (repo *ReleaseRepository) ReadRelease(name string, namespace string) (*models.Release, error) {
+func (repo *ReleaseRepository) ReadRelease(clusterID uint, name, namespace string) (*models.Release, error) {
 	release := &models.Release{}
-	if err := repo.db.Where("name = ?", name).Where("namespace = ?", namespace).First(&release).Error; err != nil {
+	if err := repo.db.Where("cluster_id = ?", clusterID).Where("name = ?", name).Where("namespace = ?", namespace).First(&release).Error; err != nil {
 		return nil, err
 	}
 	return release, nil
@@ -37,7 +37,7 @@ func (repo *ReleaseRepository) ReadRelease(name string, namespace string) (*mode
 // ReadReleaseByWebhookToken finds a single release based on their unique webhook token.
 func (repo *ReleaseRepository) ReadReleaseByWebhookToken(token string) (*models.Release, error) {
 	release := &models.Release{}
-	if err := repo.db.Where("webhook_token = ?", token).First(&release).Error; err != nil {
+	if err := repo.db.Preload("GitActionConfig").Where("webhook_token = ?", token).First(&release).Error; err != nil {
 		return nil, err
 	}
 	return release, nil

+ 3 - 3
internal/repository/gorm/release_test.go

@@ -29,7 +29,7 @@ func TestCreateRelease(t *testing.T) {
 		t.Fatalf("%v\n", err)
 	}
 
-	release, err = tester.repo.Release.ReadRelease(release.Name, release.Namespace)
+	release, err = tester.repo.Release.ReadRelease(1, release.Name, release.Namespace)
 
 	if err != nil {
 		t.Fatalf("%v\n", err)
@@ -77,7 +77,7 @@ func TestDeleteRelease(t *testing.T) {
 		t.Fatalf("%v\n", err)
 	}
 
-	release, err = tester.repo.Release.ReadRelease(release.Name, release.Namespace)
+	release, err = tester.repo.Release.ReadRelease(1, release.Name, release.Namespace)
 
 	if err != nil {
 		t.Fatalf("%v\n", err)
@@ -89,7 +89,7 @@ func TestDeleteRelease(t *testing.T) {
 		t.Fatalf("%v\n", err)
 	}
 
-	_, err = tester.repo.Release.ReadRelease(release.Name, release.Namespace)
+	_, err = tester.repo.Release.ReadRelease(1, release.Name, release.Namespace)
 
 	if err != orm.ErrRecordNotFound {
 		t.Fatalf("incorrect error: expected %v, got %v\n", orm.ErrRecordNotFound, err)

+ 1 - 0
internal/repository/gorm/repository.go

@@ -18,6 +18,7 @@ func NewRepository(db *gorm.DB, key *[32]byte) *repository.Repository {
 		HelmRepo:         NewHelmRepoRepository(db, key),
 		Registry:         NewRegistryRepository(db, key),
 		Infra:            NewInfraRepository(db, key),
+		GitActionConfig:  NewGitActionConfigRepository(db),
 		KubeIntegration:  NewKubeIntegrationRepository(db, key),
 		BasicIntegration: NewBasicIntegrationRepository(db, key),
 		OIDCIntegration:  NewOIDCIntegrationRepository(db, key),

+ 1 - 1
internal/repository/release.go

@@ -10,7 +10,7 @@ type WriteRelease func(release *models.Release) (*models.Release, error)
 // ReleaseRepository represents the set of queries on the Release model
 type ReleaseRepository interface {
 	CreateRelease(release *models.Release) (*models.Release, error)
-	ReadRelease(name string, namespace string) (*models.Release, error)
+	ReadRelease(clusterID uint, name, namespace string) (*models.Release, error)
 	ReadReleaseByWebhookToken(token string) (*models.Release, error)
 	UpdateRelease(release *models.Release) (*models.Release, error)
 	DeleteRelease(release *models.Release) (*models.Release, error)

+ 2 - 1
internal/repository/repository.go

@@ -10,7 +10,8 @@ type Repository struct {
 	Cluster          ClusterRepository
 	HelmRepo         HelmRepoRepository
 	Registry         RegistryRepository
-	Infra         InfraRepository
+	Infra            InfraRepository
+	GitActionConfig  GitActionConfigRepository
 	KubeIntegration  KubeIntegrationRepository
 	BasicIntegration BasicIntegrationRepository
 	OIDCIntegration  OIDCIntegrationRepository

+ 103 - 0
server/api/git_action_handler.go

@@ -0,0 +1,103 @@
+package api
+
+import (
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"strconv"
+	"strings"
+
+	"github.com/go-chi/chi"
+	"github.com/porter-dev/porter/internal/forms"
+	"github.com/porter-dev/porter/internal/integrations/ci/actions"
+)
+
+// HandleCreateGitAction creates a new Github action in a repository for a given
+// release
+func (app *App) HandleCreateGitAction(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
+	}
+
+	form := &forms.CreateGitAction{}
+
+	// decode from JSON to form value
+	if err := json.NewDecoder(r.Body).Decode(form); err != nil {
+		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
+		return
+	}
+
+	// validate the form
+	if err := app.validator.Struct(form); err != nil {
+		app.handleErrorFormValidation(err, ErrProjectValidateFields, w)
+		return
+	}
+
+	// convert the form to a git action config
+	gitAction, err := form.ToGitActionConfig()
+
+	if err != nil {
+		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
+		return
+	}
+
+	// read the git repo
+	gr, err := app.Repo.GitRepo.ReadGitRepo(gitAction.GitRepoID)
+
+	if err != nil {
+		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
+		return
+	}
+
+	repoSplit := strings.Split(gitAction.GitRepo, "/")
+
+	if len(repoSplit) != 2 {
+		app.handleErrorFormDecoding(fmt.Errorf("invalid formatting of repo name"), ErrProjectDecode, w)
+		return
+	}
+
+	// get webhook token from release
+
+	// generate porter jwt token
+
+	// create the commit in the git repo
+	_ = &actions.GithubActions{
+		GitIntegration: gr,
+		GitRepoName:    repoSplit[1],
+		GitRepoOwner:   repoSplit[0],
+		Repo:           *app.Repo,
+		GithubConf:     app.GithubConf,
+
+		// WebhookToken string
+		// PorterToken  string
+		// ProjectID    uint
+		// ReleaseName  string
+
+		// DockerFilePath string
+		// ImageRepoURL   string
+
+		// defaultBranch string
+	}
+
+	// handle write to the database
+	// hr, err = app.Repo.HelmRepo.CreateHelmRepo(hr)
+
+	// if err != nil {
+	// 	app.handleErrorDataWrite(err, w)
+	// 	return
+	// }
+
+	// app.Logger.Info().Msgf("New helm repo created: %d", hr.ID)
+
+	// w.WriteHeader(http.StatusCreated)
+
+	// hrExt := hr.Externalize()
+
+	// if err := json.NewEncoder(w).Encode(hrExt); err != nil {
+	// 	app.handleErrorFormDecoding(err, ErrProjectDecode, w)
+	// 	return
+	// }
+}

+ 10 - 1
server/api/release_handler.go

@@ -418,7 +418,16 @@ func (app *App) HandleGetReleaseToken(w http.ResponseWriter, r *http.Request) {
 	vals, err := url.ParseQuery(r.URL.RawQuery)
 	namespace := vals["namespace"][0]
 
-	release, err := app.Repo.Release.ReadRelease(name, namespace)
+	clusterID, err := strconv.ParseUint(vals["cluster_id"][0], 10, 64)
+
+	if err != nil {
+		app.sendExternalError(err, http.StatusInternalServerError, HTTPError{
+			Code:   ErrReleaseReadData,
+			Errors: []string{"release not found"},
+		}, w)
+	}
+
+	release, err := app.Repo.Release.ReadRelease(uint(clusterID), name, namespace)
 
 	if err != nil {
 		app.sendExternalError(err, http.StatusInternalServerError, HTTPError{