mirror of
https://github.com/portainer/portainer.git
synced 2025-07-24 15:59:41 +02:00
refactor(stacks): extract auto update logic [EE-4945] (#8545)
This commit is contained in:
parent
085381e6fc
commit
6918da2414
42 changed files with 410 additions and 166 deletions
|
@ -2,11 +2,11 @@ package deployments
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/dataservices"
|
||||
"github.com/portainer/portainer/api/git/update"
|
||||
"github.com/portainer/portainer/api/http/security"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
@ -36,6 +36,11 @@ func RedeployWhenChanged(stackID portainer.StackID, deployer StackDeployer, data
|
|||
return nil // do nothing if it isn't a git-based stack
|
||||
}
|
||||
|
||||
endpoint, err := datastore.Endpoint().Endpoint(stack.EndpointID)
|
||||
if err != nil {
|
||||
return errors.WithMessagef(err, "failed to find the environment %v associated to the stack %v", stack.EndpointID, stack.ID)
|
||||
}
|
||||
|
||||
author := stack.UpdatedBy
|
||||
if author == "" {
|
||||
author = stack.CreatedBy
|
||||
|
@ -53,39 +58,22 @@ func RedeployWhenChanged(stackID portainer.StackID, deployer StackDeployer, data
|
|||
return &StackAuthorMissingErr{int(stack.ID), author}
|
||||
}
|
||||
|
||||
username, password := "", ""
|
||||
if stack.GitConfig.Authentication != nil {
|
||||
username, password = stack.GitConfig.Authentication.Username, stack.GitConfig.Authentication.Password
|
||||
}
|
||||
var gitCommitChangedOrForceUpdate bool
|
||||
if !stack.FromAppTemplate {
|
||||
updated, newHash, err := update.UpdateGitObject(gitService, datastore, fmt.Sprintf("stack:%d", stackID), stack.GitConfig, stack.AutoUpdate, stack.ProjectPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
newHash, err := gitService.LatestCommitID(stack.GitConfig.URL, stack.GitConfig.ReferenceName, username, password)
|
||||
if err != nil {
|
||||
return errors.WithMessagef(err, "failed to fetch latest commit id of the stack %v", stack.ID)
|
||||
}
|
||||
|
||||
if strings.EqualFold(newHash, string(stack.GitConfig.ConfigHash)) {
|
||||
return nil
|
||||
}
|
||||
|
||||
cloneParams := &cloneRepositoryParameters{
|
||||
url: stack.GitConfig.URL,
|
||||
ref: stack.GitConfig.ReferenceName,
|
||||
toDir: stack.ProjectPath,
|
||||
}
|
||||
if stack.GitConfig.Authentication != nil {
|
||||
cloneParams.auth = &gitAuth{
|
||||
username: username,
|
||||
password: password,
|
||||
if updated {
|
||||
stack.GitConfig.ConfigHash = newHash
|
||||
stack.UpdateDate = time.Now().Unix()
|
||||
gitCommitChangedOrForceUpdate = updated
|
||||
}
|
||||
}
|
||||
|
||||
if err := cloneGitRepository(gitService, cloneParams); err != nil {
|
||||
return errors.WithMessagef(err, "failed to do a fresh clone of the stack %v", stack.ID)
|
||||
}
|
||||
|
||||
endpoint, err := datastore.Endpoint().Endpoint(stack.EndpointID)
|
||||
if err != nil {
|
||||
return errors.WithMessagef(err, "failed to find the environment %v associated to the stack %v", stack.EndpointID, stack.ID)
|
||||
if !gitCommitChangedOrForceUpdate {
|
||||
return nil
|
||||
}
|
||||
|
||||
registries, err := getUserRegistries(datastore, user, endpoint.ID)
|
||||
|
@ -117,8 +105,6 @@ func RedeployWhenChanged(stackID portainer.StackID, deployer StackDeployer, data
|
|||
return errors.Errorf("cannot update stack, type %v is unsupported", stack.Type)
|
||||
}
|
||||
|
||||
stack.UpdateDate = time.Now().Unix()
|
||||
stack.GitConfig.ConfigHash = newHash
|
||||
if err := datastore.Stack().UpdateStack(stack.ID, stack); err != nil {
|
||||
return errors.WithMessagef(err, "failed to update the stack %v", stack.ID)
|
||||
}
|
||||
|
@ -150,22 +136,3 @@ func getUserRegistries(datastore dataservices.DataStore, user *portainer.User, e
|
|||
|
||||
return filteredRegistries, nil
|
||||
}
|
||||
|
||||
type cloneRepositoryParameters struct {
|
||||
url string
|
||||
ref string
|
||||
toDir string
|
||||
auth *gitAuth
|
||||
}
|
||||
|
||||
type gitAuth struct {
|
||||
username string
|
||||
password string
|
||||
}
|
||||
|
||||
func cloneGitRepository(gitService portainer.GitService, cloneParams *cloneRepositoryParameters) error {
|
||||
if cloneParams.auth != nil {
|
||||
return gitService.CloneRepository(cloneParams.toDir, cloneParams.url, cloneParams.ref, cloneParams.auth.username, cloneParams.auth.password)
|
||||
}
|
||||
return gitService.CloneRepository(cloneParams.toDir, cloneParams.url, cloneParams.ref, "", "")
|
||||
}
|
||||
|
|
|
@ -81,6 +81,11 @@ func Test_redeployWhenChanged_DoesNothingWhenNoGitChanges(t *testing.T) {
|
|||
err := store.User().Create(admin)
|
||||
assert.NoError(t, err, "error creating an admin")
|
||||
|
||||
err = store.Endpoint().Create(&portainer.Endpoint{
|
||||
ID: 0,
|
||||
})
|
||||
assert.NoError(t, err, "error creating environment")
|
||||
|
||||
err = store.Stack().Create(&portainer.Stack{
|
||||
ID: 1,
|
||||
CreatedBy: "admin",
|
||||
|
@ -105,6 +110,11 @@ func Test_redeployWhenChanged_FailsWhenCannotClone(t *testing.T) {
|
|||
err := store.User().Create(admin)
|
||||
assert.NoError(t, err, "error creating an admin")
|
||||
|
||||
err = store.Endpoint().Create(&portainer.Endpoint{
|
||||
ID: 0,
|
||||
})
|
||||
assert.NoError(t, err, "error creating environment")
|
||||
|
||||
err = store.Stack().Create(&portainer.Stack{
|
||||
ID: 1,
|
||||
CreatedBy: "admin",
|
||||
|
@ -142,7 +152,9 @@ func Test_redeployWhenChanged(t *testing.T) {
|
|||
URL: "url",
|
||||
ReferenceName: "ref",
|
||||
ConfigHash: "oldHash",
|
||||
}}
|
||||
},
|
||||
}
|
||||
|
||||
err = store.Stack().Create(&stack)
|
||||
assert.NoError(t, err, "failed to create a test stack")
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ type StackPayload struct {
|
|||
// A list of environment(endpoint) variables used during stack deployment
|
||||
Env []portainer.Pair
|
||||
// Optional auto update configuration
|
||||
AutoUpdate *portainer.StackAutoUpdate
|
||||
AutoUpdate *portainer.AutoUpdateSettings
|
||||
// Whether the stack is from a app template
|
||||
FromAppTemplate bool `example:"false"`
|
||||
// Kubernetes stack name
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
package stackutils
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/asaskevich/govalidator"
|
||||
"github.com/docker/cli/cli/compose/loader"
|
||||
"github.com/docker/cli/cli/compose/types"
|
||||
"github.com/pkg/errors"
|
||||
|
@ -67,21 +64,6 @@ func IsValidStackFile(stackFileContent []byte, securitySettings *portainer.Endpo
|
|||
return nil
|
||||
}
|
||||
|
||||
func ValidateStackAutoUpdate(autoUpdate *portainer.StackAutoUpdate) error {
|
||||
if autoUpdate == nil {
|
||||
return nil
|
||||
}
|
||||
if autoUpdate.Webhook != "" && !govalidator.IsUUID(autoUpdate.Webhook) {
|
||||
return errors.New("invalid Webhook format")
|
||||
}
|
||||
if autoUpdate.Interval != "" {
|
||||
if _, err := time.ParseDuration(autoUpdate.Interval); err != nil {
|
||||
return errors.New("invalid Interval format")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ValidateStackFiles(stack *portainer.Stack, securitySettings *portainer.EndpointSecuritySettings, fileService portainer.FileService) error {
|
||||
for _, file := range GetStackFilePaths(stack, false) {
|
||||
stackContent, err := fileService.GetFileContent(stack.ProjectPath, file)
|
||||
|
|
|
@ -1,42 +0,0 @@
|
|||
package stackutils
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_ValidateStackAutoUpdate(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
value *portainer.StackAutoUpdate
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "webhook is not a valid UUID",
|
||||
value: &portainer.StackAutoUpdate{Webhook: "fake-webhook"},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "incorrect interval value",
|
||||
value: &portainer.StackAutoUpdate{Interval: "1dd2hh3mm"},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "valid auto update",
|
||||
value: &portainer.StackAutoUpdate{
|
||||
Webhook: "8dce8c2f-9ca1-482b-ad20-271e86536ada",
|
||||
Interval: "5h30m40s10ms",
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := ValidateStackAutoUpdate(tt.value)
|
||||
assert.Equalf(t, tt.wantErr, err != nil, "received %+v", err)
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue