mirror of https://github.com/portainer/portainer
feat(edge-stack): relative path support for edge stack EE-5521 (#9103)
parent
4cc96b4b30
commit
7cb6e3f66a
|
@ -1,5 +1,7 @@
|
||||||
package edge
|
package edge
|
||||||
|
|
||||||
|
import "github.com/portainer/portainer/api/filesystem"
|
||||||
|
|
||||||
type (
|
type (
|
||||||
|
|
||||||
// StackPayload represents the payload sent to the agent
|
// StackPayload represents the payload sent to the agent
|
||||||
|
@ -8,35 +10,36 @@ type (
|
||||||
ID int
|
ID int
|
||||||
// Name of the stack
|
// Name of the stack
|
||||||
Name string
|
Name string
|
||||||
// Content of the stack file
|
|
||||||
FileContent string
|
// Content of stack folder
|
||||||
|
DirEntries []filesystem.DirEntry
|
||||||
|
// Name of the stack entry file
|
||||||
|
EntryFileName string
|
||||||
// Namespace to use for kubernetes stack. Keep empty to use the manifest namespace.
|
// Namespace to use for kubernetes stack. Keep empty to use the manifest namespace.
|
||||||
Namespace string
|
Namespace string
|
||||||
// Version of the stack file
|
// Version of the stack file
|
||||||
Version int
|
Version int
|
||||||
// Content of the .env file
|
|
||||||
DotEnvFileContent string
|
|
||||||
|
|
||||||
// RegistryCredentials holds the credentials for a Docker registry.
|
// RegistryCredentials holds the credentials for a Docker registry.
|
||||||
//
|
|
||||||
// Used only for EE
|
// Used only for EE
|
||||||
RegistryCredentials []RegistryCredentials
|
RegistryCredentials []RegistryCredentials
|
||||||
// PrePullImage is a flag indicating if the agent should pull the image before deploying the stack.
|
// PrePullImage is a flag indicating if the agent should pull the image before deploying the stack.
|
||||||
//
|
|
||||||
// Used only for EE
|
// Used only for EE
|
||||||
PrePullImage bool
|
PrePullImage bool
|
||||||
// RePullImage is a flag indicating if the agent should pull the image if it is already present on the node.
|
// RePullImage is a flag indicating if the agent should pull the image if it is already present on the node.
|
||||||
//
|
|
||||||
// Used only for EE
|
// Used only for EE
|
||||||
RePullImage bool
|
RePullImage bool
|
||||||
// RetryDeploy is a flag indicating if the agent should retry to deploy the stack if it fails.
|
// RetryDeploy is a flag indicating if the agent should retry to deploy the stack if it fails.
|
||||||
//
|
|
||||||
// Used only for EE
|
// Used only for EE
|
||||||
RetryDeploy bool
|
RetryDeploy bool
|
||||||
// EdgeUpdateID is the ID of the edge update related to this stack.
|
// EdgeUpdateID is the ID of the edge update related to this stack.
|
||||||
//
|
|
||||||
// Used only for EE
|
// Used only for EE
|
||||||
EdgeUpdateID int
|
EdgeUpdateID int
|
||||||
|
|
||||||
|
// Is relative path supported
|
||||||
|
SupportRelativePath bool
|
||||||
|
// Mount point for relative path
|
||||||
|
FilesystemPath string
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegistryCredentials holds the credentials for a Docker registry.
|
// RegistryCredentials holds the credentials for a Docker registry.
|
||||||
|
|
|
@ -0,0 +1,142 @@
|
||||||
|
package filesystem
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DirEntry struct {
|
||||||
|
Name string
|
||||||
|
Content string
|
||||||
|
IsFile bool
|
||||||
|
Permissions os.FileMode
|
||||||
|
}
|
||||||
|
|
||||||
|
// FilterDirForEntryFile filers the given dirEntries, returns entries of the entryFile and .env file
|
||||||
|
func FilterDirForEntryFile(dirEntries []DirEntry, entryFile string) []DirEntry {
|
||||||
|
var filteredDirEntries []DirEntry
|
||||||
|
|
||||||
|
dotEnvFile := filepath.Join(filepath.Dir(entryFile), ".env")
|
||||||
|
filters := []string{entryFile, dotEnvFile}
|
||||||
|
|
||||||
|
for _, dirEntry := range dirEntries {
|
||||||
|
match := false
|
||||||
|
if dirEntry.IsFile {
|
||||||
|
for _, filter := range filters {
|
||||||
|
if filter == dirEntry.Name {
|
||||||
|
match = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for _, filter := range filters {
|
||||||
|
if strings.HasPrefix(filter, dirEntry.Name) {
|
||||||
|
match = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if match {
|
||||||
|
filteredDirEntries = append(filteredDirEntries, dirEntry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return filteredDirEntries
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadDir reads all files and folders recursively from the given directory
|
||||||
|
// File content is base64-encoded
|
||||||
|
func LoadDir(dir string) ([]DirEntry, error) {
|
||||||
|
var dirEntries []DirEntry
|
||||||
|
|
||||||
|
err := filepath.WalkDir(
|
||||||
|
dir,
|
||||||
|
func(path string, d os.DirEntry, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fileInfo, err := d.Info()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
relativePath, err := filepath.Rel(dir, path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if relativePath == "." {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
dirEntry := DirEntry{
|
||||||
|
Name: relativePath,
|
||||||
|
Permissions: fileInfo.Mode().Perm(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if !fileInfo.IsDir() {
|
||||||
|
// Read file contents
|
||||||
|
fileContent, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
dirEntry.Content = base64.StdEncoding.EncodeToString(fileContent)
|
||||||
|
dirEntry.IsFile = true
|
||||||
|
}
|
||||||
|
|
||||||
|
dirEntries = append(dirEntries, dirEntry)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return dirEntries, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PersistDir writes the provided array of files and folders back to the given directory.
|
||||||
|
func PersistDir(dir string, dirEntries []DirEntry) error {
|
||||||
|
for _, dirEntry := range dirEntries {
|
||||||
|
path := filepath.Join(dir, dirEntry.Name)
|
||||||
|
|
||||||
|
if dirEntry.IsFile {
|
||||||
|
// Create the directory path if it doesn't exist
|
||||||
|
err := os.MkdirAll(filepath.Dir(path), 0744)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write file contents
|
||||||
|
err = os.WriteFile(path, []byte(dirEntry.Content), dirEntry.Permissions)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Create the directory
|
||||||
|
err := os.MkdirAll(path, dirEntry.Permissions)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func DecodeDirEntries(dirEntries []DirEntry) error {
|
||||||
|
for index, dirEntry := range dirEntries {
|
||||||
|
if dirEntry.IsFile && dirEntry.Content != "" {
|
||||||
|
decodedBytes, err := base64.StdEncoding.DecodeString(dirEntry.Content)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
dirEntries[index].Content = string(decodedBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -2,18 +2,17 @@ package endpointedge
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
|
|
||||||
httperror "github.com/portainer/libhttp/error"
|
httperror "github.com/portainer/libhttp/error"
|
||||||
"github.com/portainer/libhttp/request"
|
"github.com/portainer/libhttp/request"
|
||||||
"github.com/portainer/libhttp/response"
|
"github.com/portainer/libhttp/response"
|
||||||
portainer "github.com/portainer/portainer/api"
|
portainer "github.com/portainer/portainer/api"
|
||||||
"github.com/portainer/portainer/api/edge"
|
"github.com/portainer/portainer/api/edge"
|
||||||
|
"github.com/portainer/portainer/api/filesystem"
|
||||||
"github.com/portainer/portainer/api/http/middlewares"
|
"github.com/portainer/portainer/api/http/middlewares"
|
||||||
"github.com/portainer/portainer/api/internal/endpointutils"
|
"github.com/portainer/portainer/api/internal/endpointutils"
|
||||||
"github.com/portainer/portainer/api/kubernetes"
|
"github.com/portainer/portainer/api/kubernetes"
|
||||||
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
// @summary Inspect an Edge Stack for an Environment(Endpoint)
|
// @summary Inspect an Edge Stack for an Environment(Endpoint)
|
||||||
|
@ -69,26 +68,19 @@ func (handler *Handler) endpointEdgeStackInspect(w http.ResponseWriter, r *http.
|
||||||
if fileName == "" {
|
if fileName == "" {
|
||||||
return httperror.BadRequest("Kubernetes is not supported by this stack", errors.New("Kubernetes is not supported by this stack"))
|
return httperror.BadRequest("Kubernetes is not supported by this stack", errors.New("Kubernetes is not supported by this stack"))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
stackFileContent, err := handler.FileService.GetFileContent(edgeStack.ProjectPath, fileName)
|
dirEntries, err := filesystem.LoadDir(edgeStack.ProjectPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httperror.InternalServerError("Unable to retrieve Compose file from disk", err)
|
return httperror.InternalServerError("Unable to load repository", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var dotEnvFileContent []byte
|
dirEntries = filesystem.FilterDirForEntryFile(dirEntries, fileName)
|
||||||
if _, err = os.Stat(path.Join(edgeStack.ProjectPath, ".env")); err == nil {
|
|
||||||
dotEnvFileContent, err = handler.FileService.GetFileContent(edgeStack.ProjectPath, ".env")
|
|
||||||
if err != nil {
|
|
||||||
return httperror.InternalServerError("Unable to retrieve .env file from disk", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return response.JSON(w, edge.StackPayload{
|
return response.JSON(w, edge.StackPayload{
|
||||||
DotEnvFileContent: string(dotEnvFileContent),
|
DirEntries: dirEntries,
|
||||||
FileContent: string(stackFileContent),
|
EntryFileName: fileName,
|
||||||
Name: edgeStack.Name,
|
Name: edgeStack.Name,
|
||||||
Namespace: namespace,
|
Namespace: namespace,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue