Przeglądaj źródła

add support for hooks that update stack post-upgrade

Alexander Belanger 3 lat temu
rodzic
commit
a49bdd20b4

+ 26 - 9
api/server/handlers/release/create.go

@@ -137,7 +137,7 @@ func (c *CreateReleaseHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 		}
 	}
 
-	release, err := createReleaseFromHelmRelease(c.Config(), cluster.ProjectID, cluster.ID, helmRelease)
+	release, err := CreateAppReleaseFromHelmRelease(c.Config(), cluster.ProjectID, cluster.ID, 0, helmRelease)
 
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
@@ -206,9 +206,9 @@ func (c *CreateReleaseHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 	))
 }
 
-func createReleaseFromHelmRelease(
+func CreateAppReleaseFromHelmRelease(
 	config *config.Config,
-	projectID, clusterID uint,
+	projectID, clusterID, stackResourceID uint,
 	helmRelease *release.Release,
 ) (*models.Release, error) {
 	token, err := encryption.GenerateRandomBytes(16)
@@ -232,12 +232,29 @@ func createReleaseFromHelmRelease(
 	}
 
 	release := &models.Release{
-		ClusterID:    clusterID,
-		ProjectID:    projectID,
-		Namespace:    helmRelease.Namespace,
-		Name:         helmRelease.Name,
-		WebhookToken: token,
-		ImageRepoURI: repoStr,
+		ClusterID:       clusterID,
+		ProjectID:       projectID,
+		Namespace:       helmRelease.Namespace,
+		Name:            helmRelease.Name,
+		WebhookToken:    token,
+		ImageRepoURI:    repoStr,
+		StackResourceID: stackResourceID,
+	}
+
+	return config.Repo.Release().CreateRelease(release)
+}
+
+func CreateAddonReleaseFromHelmRelease(
+	config *config.Config,
+	projectID, clusterID, stackResourceID uint,
+	helmRelease *release.Release,
+) (*models.Release, error) {
+	release := &models.Release{
+		ClusterID:       clusterID,
+		ProjectID:       projectID,
+		Namespace:       helmRelease.Namespace,
+		Name:            helmRelease.Name,
+		StackResourceID: stackResourceID,
 	}
 
 	return config.Repo.Release().CreateRelease(release)

+ 1 - 1
api/server/handlers/release/create_webhook.go

@@ -29,7 +29,7 @@ func (c *CreateWebhookHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 	helmRelease, _ := r.Context().Value(types.ReleaseScope).(*release.Release)
 	cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
 
-	release, err := createReleaseFromHelmRelease(c.Config(), cluster.ProjectID, cluster.ID, helmRelease)
+	release, err := CreateAppReleaseFromHelmRelease(c.Config(), cluster.ProjectID, cluster.ID, 0, helmRelease)
 
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))

+ 16 - 0
api/server/handlers/release/ugprade.go

@@ -16,6 +16,7 @@ import (
 	"github.com/porter-dev/porter/internal/helm"
 	"github.com/porter-dev/porter/internal/integrations/slack"
 	"github.com/porter-dev/porter/internal/models"
+	"github.com/porter-dev/porter/internal/stacks"
 	"helm.sh/helm/v3/pkg/release"
 )
 
@@ -229,4 +230,19 @@ func (c *UpgradeReleaseHandler) ServeHTTP(w http.ResponseWriter, r *http.Request
 			}
 		}
 	}
+
+	c.WriteResult(w, r, nil)
+
+	err = postUpgrade(c.Config(), cluster.ProjectID, cluster.ID, helmRelease)
+
+	if err != nil {
+		c.HandleAPIErrorNoWrite(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+}
+
+// postUpgrade runs any necessary scripting after the release has been upgraded.
+func postUpgrade(config *config.Config, projectID, clusterID uint, release *release.Release) error {
+	// update the relevant helm revision number if tied to a stack resource
+	return stacks.UpdateHelmRevision(config, projectID, clusterID, release)
 }

+ 21 - 1
api/server/handlers/stack/create.go

@@ -6,12 +6,15 @@ import (
 
 	"github.com/porter-dev/porter/api/server/authz"
 	"github.com/porter-dev/porter/api/server/handlers"
+	"github.com/porter-dev/porter/api/server/handlers/release"
 	"github.com/porter-dev/porter/api/server/shared"
 	"github.com/porter-dev/porter/api/server/shared/apierrors"
 	"github.com/porter-dev/porter/api/server/shared/config"
 	"github.com/porter-dev/porter/api/types"
 	"github.com/porter-dev/porter/internal/encryption"
 	"github.com/porter-dev/porter/internal/models"
+
+	helmrelease "helm.sh/helm/v3/pkg/release"
 )
 
 type StackCreateHandler struct {
@@ -108,8 +111,10 @@ func (p *StackCreateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
+	helmReleaseMap := make(map[string]*helmrelease.Release)
+
 	for _, appResource := range req.AppResources {
-		err = applyAppResource(&applyAppResourceOpts{
+		rel, err := applyAppResource(&applyAppResourceOpts{
 			config:     p.Config(),
 			projectID:  proj.ID,
 			namespace:  namespace,
@@ -124,6 +129,8 @@ func (p *StackCreateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 			p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 			return
 		}
+
+		helmReleaseMap[fmt.Sprintf("%s/%s", namespace, appResource.Name)] = rel
 	}
 
 	// update stack revision status
@@ -137,6 +144,19 @@ func (p *StackCreateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
+	for _, resource := range revision.Resources {
+		rel := helmReleaseMap[fmt.Sprintf("%s/%s", namespace, resource.Name)]
+
+		// TODO: case on addon vs application
+		_, err = release.CreateAppReleaseFromHelmRelease(p.Config(), proj.ID, cluster.ID, resource.ID, rel)
+
+		if err != nil {
+			// TODO: mark stack with error
+			p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+			return
+		}
+	}
+
 	// read the stack again to get the latest revision info
 	stack, err = p.Repo().Stack().ReadStackByStringID(proj.ID, stack.UID)
 

+ 4 - 82
api/server/handlers/stack/helpers.go

@@ -1,14 +1,12 @@
 package stack
 
 import (
-	"fmt"
-
 	"github.com/porter-dev/porter/api/server/shared/config"
 	"github.com/porter-dev/porter/api/types"
-	"github.com/porter-dev/porter/internal/encryption"
 	"github.com/porter-dev/porter/internal/helm"
 	"github.com/porter-dev/porter/internal/helm/loader"
 	"github.com/porter-dev/porter/internal/models"
+	"helm.sh/helm/v3/pkg/release"
 )
 
 type applyAppResourceOpts struct {
@@ -21,7 +19,7 @@ type applyAppResourceOpts struct {
 	registries []*models.Registry
 }
 
-func applyAppResource(opts *applyAppResourceOpts) error {
+func applyAppResource(opts *applyAppResourceOpts) (*release.Release, error) {
 	if opts.request.TemplateVersion == "latest" {
 		opts.request.TemplateVersion = ""
 	}
@@ -29,7 +27,7 @@ func applyAppResource(opts *applyAppResourceOpts) error {
 	chart, err := loader.LoadChartPublic(opts.request.TemplateRepoURL, opts.request.TemplateName, opts.request.TemplateVersion)
 
 	if err != nil {
-		return err
+		return nil, err
 	}
 
 	conf := &helm.InstallChartConfig{
@@ -42,13 +40,7 @@ func applyAppResource(opts *applyAppResourceOpts) error {
 		Registries: opts.registries,
 	}
 
-	_, err = opts.helmAgent.InstallChart(conf, opts.config.DOConf)
-
-	if err != nil {
-		return err
-	}
-
-	return nil
+	return opts.helmAgent.InstallChart(conf, opts.config.DOConf)
 }
 
 type rollbackAppResourceOpts struct {
@@ -108,74 +100,4 @@ func deleteAppResource(opts *deleteAppResourceOpts) error {
 	return err
 }
 
-func cloneSourceConfigs(sourceConfigs []models.StackSourceConfig) ([]models.StackSourceConfig, error) {
-	res := make([]models.StackSourceConfig, 0)
-
-	// for now, only write source configs which are deployed as a docker image
-	// TODO: add parsing/writes for git-based sources
-	for _, sourceConfig := range sourceConfigs {
-		uid, err := encryption.GenerateRandomBytes(16)
-
-		if err != nil {
-			return nil, err
-		}
-
-		res = append(res, models.StackSourceConfig{
-			UID:          uid,
-			Name:         sourceConfig.Name,
-			ImageRepoURI: sourceConfig.ImageRepoURI,
-			ImageTag:     sourceConfig.ImageTag,
-		})
-	}
-
-	return res, nil
-}
-
-func cloneAppResources(
-	appResources []models.StackResource,
-	prevSourceConfigs []models.StackSourceConfig,
-	newSourceConfigs []models.StackSourceConfig,
-) ([]models.StackResource, error) {
-	res := make([]models.StackResource, 0)
-
-	// for now, only write source configs which are deployed as a docker image
-	// TODO: add parsing/writes for git-based sources
-	for _, appResource := range appResources {
-		uid, err := encryption.GenerateRandomBytes(16)
-
-		if err != nil {
-			return nil, err
-		}
-
-		var linkedSourceConfigUID string
-
-		for _, prevSourceConfig := range prevSourceConfigs {
-			if prevSourceConfig.UID == appResource.StackSourceConfigUID {
-				// find the corresponding new source config
-				for _, newSourceConfig := range newSourceConfigs {
-					if newSourceConfig.Name == prevSourceConfig.Name {
-						linkedSourceConfigUID = newSourceConfig.UID
-					}
-				}
-			}
-		}
-
-		if linkedSourceConfigUID == "" {
-			return nil, fmt.Errorf("source config does not exist in source config list")
-		}
-
-		res = append(res, models.StackResource{
-			Name:                 appResource.Name,
-			UID:                  uid,
-			StackSourceConfigUID: linkedSourceConfigUID,
-			TemplateRepoURL:      appResource.TemplateRepoURL,
-			TemplateName:         appResource.TemplateName,
-			TemplateVersion:      appResource.TemplateVersion,
-			HelmRevisionID:       appResource.HelmRevisionID,
-		})
-	}
-
-	return res, nil
-}
-
 // func setValuesWithSourceConfig(values map[string]interface{}, sourceConfig )

+ 3 - 2
api/server/handlers/stack/rollback.go

@@ -10,6 +10,7 @@ import (
 	"github.com/porter-dev/porter/api/server/shared/config"
 	"github.com/porter-dev/porter/api/types"
 	"github.com/porter-dev/porter/internal/models"
+	"github.com/porter-dev/porter/internal/stacks"
 	"gorm.io/gorm"
 )
 
@@ -70,14 +71,14 @@ func (p *StackRollbackHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 	revision.RevisionNumber = latestRevision.RevisionNumber + 1
 	revision.Status = string(types.StackRevisionStatusDeploying)
 
-	newSourceConfigs, err := cloneSourceConfigs(revision.SourceConfigs)
+	newSourceConfigs, err := stacks.CloneSourceConfigs(revision.SourceConfigs)
 
 	if err != nil {
 		p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
 	}
 
-	appResources, err := cloneAppResources(revision.Resources, revision.SourceConfigs, newSourceConfigs)
+	appResources, err := stacks.CloneAppResources(revision.Resources, revision.SourceConfigs, newSourceConfigs)
 
 	if err != nil {
 		p.HandleAPIError(w, r, apierrors.NewErrInternal(err))

+ 2 - 1
api/server/handlers/stack/update_source_put.go

@@ -10,6 +10,7 @@ import (
 	"github.com/porter-dev/porter/api/server/shared/config"
 	"github.com/porter-dev/porter/api/types"
 	"github.com/porter-dev/porter/internal/models"
+	"github.com/porter-dev/porter/internal/stacks"
 	"gorm.io/gorm"
 )
 
@@ -69,7 +70,7 @@ func (p *StackPutSourceConfigHandler) ServeHTTP(w http.ResponseWriter, r *http.R
 	revision.Status = string(types.StackRevisionStatusDeploying)
 	prevSourceConfigs := revision.SourceConfigs
 	revision.SourceConfigs = sourceConfigs
-	clonedAppResources, err := cloneAppResources(revision.Resources, prevSourceConfigs, revision.SourceConfigs)
+	clonedAppResources, err := stacks.CloneAppResources(revision.Resources, prevSourceConfigs, revision.SourceConfigs)
 
 	if err != nil {
 		p.HandleAPIError(w, r, apierrors.NewErrInternal(err))

+ 2 - 0
internal/models/release.go

@@ -17,6 +17,8 @@ type Release struct {
 	Name         string `json:"name"`
 	Namespace    string `json:"namespace"`
 
+	StackResourceID uint
+
 	// The complete image repository uri to pull from. This is also stored in GitActionConfig,
 	// but this should be used for the source of truth going forward.
 	ImageRepoURI string `json:"image_repo_uri,omitempty"`

+ 28 - 0
internal/repository/gorm/stack.go

@@ -77,6 +77,16 @@ func (repo *StackRepository) UpdateStackRevision(revision *models.StackRevision)
 	return revision, nil
 }
 
+func (repo *StackRepository) ReadStackRevision(stackRevisionID uint) (*models.StackRevision, error) {
+	revision := &models.StackRevision{}
+
+	if err := repo.db.Preload("Resources").Preload("SourceConfigs").Where("id = ?", stackRevisionID).First(&revision).Error; err != nil {
+		return nil, err
+	}
+
+	return revision, nil
+}
+
 func (repo *StackRepository) ReadStackRevisionByNumber(stackID uint, revisionNumber uint) (*models.StackRevision, error) {
 	revision := &models.StackRevision{}
 
@@ -106,3 +116,21 @@ func (repo *StackRepository) AppendNewRevision(revision *models.StackRevision) (
 
 	return revision, nil
 }
+
+func (repo *StackRepository) ReadStackResource(resourceID uint) (*models.StackResource, error) {
+	resource := &models.StackResource{}
+
+	if err := repo.db.Where("id = ?", resourceID).First(&resource).Error; err != nil {
+		return nil, err
+	}
+
+	return resource, nil
+}
+
+func (repo *StackRepository) UpdateStackResource(resource *models.StackResource) (*models.StackResource, error) {
+	if err := repo.db.Save(resource).Error; err != nil {
+		return nil, err
+	}
+
+	return resource, nil
+}

+ 5 - 1
internal/repository/stack.go

@@ -8,8 +8,12 @@ type StackRepository interface {
 	ReadStackByStringID(projectID uint, stackID string) (*models.Stack, error)
 	ListStacks(projectID uint, clusterID uint, namespace string) ([]*models.Stack, error)
 	DeleteStack(stack *models.Stack) (*models.Stack, error)
-	UpdateStackRevision(revision *models.StackRevision) (*models.StackRevision, error)
 
+	UpdateStackRevision(revision *models.StackRevision) (*models.StackRevision, error)
+	ReadStackRevision(stackRevisionID uint) (*models.StackRevision, error)
 	ReadStackRevisionByNumber(stackID uint, revisionNumber uint) (*models.StackRevision, error)
 	AppendNewRevision(revision *models.StackRevision) (*models.StackRevision, error)
+
+	ReadStackResource(resourceID uint) (*models.StackResource, error)
+	UpdateStackResource(resource *models.StackResource) (*models.StackResource, error)
 }

+ 78 - 0
internal/stacks/helpers.go

@@ -0,0 +1,78 @@
+package stacks
+
+import (
+	"fmt"
+
+	"github.com/porter-dev/porter/internal/encryption"
+	"github.com/porter-dev/porter/internal/models"
+)
+
+func CloneSourceConfigs(sourceConfigs []models.StackSourceConfig) ([]models.StackSourceConfig, error) {
+	res := make([]models.StackSourceConfig, 0)
+
+	// for now, only write source configs which are deployed as a docker image
+	// TODO: add parsing/writes for git-based sources
+	for _, sourceConfig := range sourceConfigs {
+		uid, err := encryption.GenerateRandomBytes(16)
+
+		if err != nil {
+			return nil, err
+		}
+
+		res = append(res, models.StackSourceConfig{
+			UID:          uid,
+			Name:         sourceConfig.Name,
+			ImageRepoURI: sourceConfig.ImageRepoURI,
+			ImageTag:     sourceConfig.ImageTag,
+		})
+	}
+
+	return res, nil
+}
+
+func CloneAppResources(
+	appResources []models.StackResource,
+	prevSourceConfigs []models.StackSourceConfig,
+	newSourceConfigs []models.StackSourceConfig,
+) ([]models.StackResource, error) {
+	res := make([]models.StackResource, 0)
+
+	// for now, only write source configs which are deployed as a docker image
+	// TODO: add parsing/writes for git-based sources
+	for _, appResource := range appResources {
+		uid, err := encryption.GenerateRandomBytes(16)
+
+		if err != nil {
+			return nil, err
+		}
+
+		var linkedSourceConfigUID string
+
+		for _, prevSourceConfig := range prevSourceConfigs {
+			if prevSourceConfig.UID == appResource.StackSourceConfigUID {
+				// find the corresponding new source config
+				for _, newSourceConfig := range newSourceConfigs {
+					if newSourceConfig.Name == prevSourceConfig.Name {
+						linkedSourceConfigUID = newSourceConfig.UID
+					}
+				}
+			}
+		}
+
+		if linkedSourceConfigUID == "" {
+			return nil, fmt.Errorf("source config does not exist in source config list")
+		}
+
+		res = append(res, models.StackResource{
+			Name:                 appResource.Name,
+			UID:                  uid,
+			StackSourceConfigUID: linkedSourceConfigUID,
+			TemplateRepoURL:      appResource.TemplateRepoURL,
+			TemplateName:         appResource.TemplateName,
+			TemplateVersion:      appResource.TemplateVersion,
+			HelmRevisionID:       appResource.HelmRevisionID,
+		})
+	}
+
+	return res, nil
+}

+ 60 - 0
internal/stacks/hooks.go

@@ -0,0 +1,60 @@
+package stacks
+
+import (
+	"github.com/porter-dev/porter/api/server/shared/config"
+	"gorm.io/gorm"
+	"helm.sh/helm/v3/pkg/release"
+)
+
+func UpdateHelmRevision(config *config.Config, projID, clusterID uint, rel *release.Release) error {
+	// read release by stack ID
+	relModel, err := config.Repo.Release().ReadRelease(clusterID, rel.Name, rel.Namespace)
+
+	if err != nil {
+		return err
+	}
+
+	if relModel.StackResourceID == 0 {
+		return nil
+	}
+
+	stackResource, err := config.Repo.Stack().ReadStackResource(relModel.StackResourceID)
+
+	if err != nil {
+		return err
+	}
+
+	// read the revision number and create a new revision of the stack
+	stackRevision, err := config.Repo.Stack().ReadStackRevision(stackResource.StackRevisionID)
+
+	if err != nil {
+		return err
+	}
+
+	clonedSourceConfigs, err := CloneSourceConfigs(stackRevision.SourceConfigs)
+
+	if err != nil {
+		return err
+	}
+
+	clonedAppResources, err := CloneAppResources(stackRevision.Resources, stackRevision.SourceConfigs, clonedSourceConfigs)
+
+	if err != nil {
+		return err
+	}
+
+	for i, appResource := range clonedAppResources {
+		if appResource.Name == rel.Name {
+			clonedAppResources[i].HelmRevisionID = uint(rel.Version)
+		}
+	}
+
+	stackRevision.Model = gorm.Model{}
+	stackRevision.RevisionNumber++
+	stackRevision.Resources = clonedAppResources
+	stackRevision.SourceConfigs = clonedSourceConfigs
+
+	_, err = config.Repo.Stack().AppendNewRevision(stackRevision)
+
+	return err
+}