fix: make server operations executable
This commit is contained in:
@@ -148,8 +148,25 @@ func (i Installation) ProjectName() string {
|
||||
|
||||
// ComposeArgs builds Docker Compose arguments without shell quoting or interpolation.
|
||||
func (i Installation) ComposeArgs(command ...string) []string {
|
||||
return i.composeArgs(i.ComposeFiles(), command...)
|
||||
}
|
||||
|
||||
// ComposeArgsWithFinalOverride appends one validated, generated override after every durable
|
||||
// installation selector and before the Compose command.
|
||||
func (i Installation) ComposeArgsWithFinalOverride(override string, command ...string) ([]string, error) {
|
||||
if filepath.Clean(override) != override || !filepath.IsAbs(override) {
|
||||
return nil, errors.New("final Compose override must be an absolute canonical path")
|
||||
}
|
||||
if err := requireRegularFile(override, "final Compose override"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
files := append(i.ComposeFiles(), override)
|
||||
return i.composeArgs(files, command...), nil
|
||||
}
|
||||
|
||||
func (i Installation) composeArgs(files []string, command ...string) []string {
|
||||
args := []string{"compose", "--project-name", i.ProjectName(), "--project-directory", i.ProjectDirectory, "--env-file", i.EnvFile}
|
||||
for _, composeFile := range i.ComposeFiles() {
|
||||
for _, composeFile := range files {
|
||||
args = append(args, "-f", composeFile)
|
||||
}
|
||||
return append(args, command...)
|
||||
@@ -193,15 +210,75 @@ func (i Installation) SecretFiles() ([]string, error) {
|
||||
// EnvironmentValue returns one declared installation value without exposing dotenv parsing to
|
||||
// callers. It is used only for operator-visible file locations, never for secret content.
|
||||
func (i Installation) EnvironmentValue(name string) (string, error) {
|
||||
values, err := i.environmentValues()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return values[name], nil
|
||||
}
|
||||
|
||||
func (i Installation) environmentValues() (map[string]string, error) {
|
||||
contents, err := safeio.ReadCanonicalRegular(i.EnvFile, maxEnvironmentFileBytes)
|
||||
if err != nil {
|
||||
return "", errors.New("installation environment could not be read")
|
||||
return nil, errors.New("installation environment could not be read")
|
||||
}
|
||||
values, err := parseComposeDotenv(contents)
|
||||
if err != nil {
|
||||
return "", errors.New("installation environment could not be read")
|
||||
return nil, errors.New("installation environment could not be read")
|
||||
}
|
||||
return values[name], nil
|
||||
return values, nil
|
||||
}
|
||||
|
||||
// PreservationPaths returns the server bind roots, backup root, and declared secret files whose
|
||||
// filesystem identities must survive a data-preserving removal.
|
||||
func (i Installation) PreservationPaths() ([]string, error) {
|
||||
if i.Profile != "server" {
|
||||
return nil, errors.New("data-preserving removal requires a server installation")
|
||||
}
|
||||
values, err := i.environmentValues()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
paths := make([]string, 0)
|
||||
seen := make(map[string]struct{})
|
||||
for _, name := range []string{
|
||||
"THT_DATA_ROOT", "THT_PI_STATE_ROOT", "THT_WORKSPACE_REGISTRY_ROOT", "THT_BACKUP_ROOT",
|
||||
} {
|
||||
path := values[name]
|
||||
if err := requireCanonicalDirectory(path); err != nil {
|
||||
return nil, fmt.Errorf("%s must identify an existing canonical directory", name)
|
||||
}
|
||||
if _, exists := seen[path]; !exists {
|
||||
paths = append(paths, path)
|
||||
seen[path] = struct{}{}
|
||||
}
|
||||
}
|
||||
secretFiles, err := i.SecretFiles()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, path := range secretFiles {
|
||||
if _, exists := seen[path]; !exists {
|
||||
paths = append(paths, path)
|
||||
seen[path] = struct{}{}
|
||||
}
|
||||
}
|
||||
return paths, nil
|
||||
}
|
||||
|
||||
func requireCanonicalDirectory(path string) error {
|
||||
if err := safeio.ValidateCanonicalPath(path); err != nil {
|
||||
return err
|
||||
}
|
||||
resolved, err := filepath.EvalSymlinks(path)
|
||||
if err != nil || resolved != path {
|
||||
return errors.New("directory path is unavailable or contains a symlink")
|
||||
}
|
||||
info, err := os.Stat(path)
|
||||
if err != nil || !info.IsDir() {
|
||||
return errors.New("directory path is unavailable")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseComposeDotenv(contents []byte) (map[string]string, error) {
|
||||
|
||||
Reference in New Issue
Block a user