mirror of
https://hubproxy.babadafafafafa.cn/https://github.com/1Panel-dev/1Panel.git
synced 2026-09-20 08:03:55 +08:00
feat: Add support for importing environment variables into the runtime environment. (#13737)
This commit is contained in:
106
agent/app/service/runtime_update.go
Normal file
106
agent/app/service/runtime_update.go
Normal file
@@ -0,0 +1,106 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto/request"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/files"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func getRuntimeUpdateAppDetail(runtime *model.Runtime, req request.RuntimeUpdate) (model.AppDetail, error) {
|
||||
current, err := appDetailRepo.GetFirst(repo.WithByID(runtime.AppDetailID))
|
||||
if err != nil {
|
||||
return model.AppDetail{}, err
|
||||
}
|
||||
if current.ID == 0 {
|
||||
return model.AppDetail{}, buserr.New("ErrRecordNotFound")
|
||||
}
|
||||
opts := []repo.DBOption{appDetailRepo.WithAppId(current.AppId)}
|
||||
if req.AppDetailID != 0 {
|
||||
opts = append(opts, repo.WithByID(req.AppDetailID))
|
||||
} else if req.Version != "" {
|
||||
opts = append(opts, appDetailRepo.WithVersion(req.Version))
|
||||
} else {
|
||||
return current, nil
|
||||
}
|
||||
detail, err := appDetailRepo.GetFirst(opts...)
|
||||
if err != nil {
|
||||
return model.AppDetail{}, err
|
||||
}
|
||||
if detail.ID == 0 || (req.Version != "" && detail.Version != req.Version) {
|
||||
return model.AppDetail{}, buserr.New("ErrInvalidParams")
|
||||
}
|
||||
return detail, nil
|
||||
}
|
||||
|
||||
func updateRuntimeVersionFiles(runtime *model.Runtime, appVersionDir string) error {
|
||||
fileOp := files.NewFileOp()
|
||||
template, err := fileOp.GetContent(filepath.Join(appVersionDir, "docker-compose.yml"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
current, err := fileOp.GetContent(runtime.GetComposePath())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
composeContent, err := updateRuntimeImageConfig(current, template)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if runtime.Type == constant.RuntimePHP {
|
||||
if err = fileOp.CopyDir(filepath.Join(appVersionDir, "build"), runtime.GetPath()); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = fileOp.CopyFile(filepath.Join(appVersionDir, "data.yml"), runtime.GetPath()); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err = fileOp.CopyFile(filepath.Join(appVersionDir, "run.sh"), runtime.GetPath()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return fileOp.SaveFile(runtime.GetComposePath(), string(composeContent), constant.FilePerm)
|
||||
}
|
||||
|
||||
func updateRuntimeImageConfig(current, template []byte) ([]byte, error) {
|
||||
var currentCompose, templateCompose map[string]interface{}
|
||||
if err := yaml.Unmarshal(current, ¤tCompose); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := yaml.Unmarshal(template, &templateCompose); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
currentServices, ok := currentCompose["services"].(map[string]interface{})
|
||||
if !ok || len(currentServices) != 1 {
|
||||
return nil, buserr.New("ErrFileParse")
|
||||
}
|
||||
templateServices, ok := templateCompose["services"].(map[string]interface{})
|
||||
if !ok || len(templateServices) != 1 {
|
||||
return nil, buserr.New("ErrFileParse")
|
||||
}
|
||||
// Keep container settings and mounts; only the image and build definition belong to the version.
|
||||
for _, currentService := range currentServices {
|
||||
service, ok := currentService.(map[string]interface{})
|
||||
if !ok {
|
||||
return nil, buserr.New("ErrFileParse")
|
||||
}
|
||||
for _, templateService := range templateServices {
|
||||
target, ok := templateService.(map[string]interface{})
|
||||
if !ok || (target["image"] == nil && target["build"] == nil) {
|
||||
return nil, buserr.New("ErrFileParse")
|
||||
}
|
||||
for _, key := range []string{"image", "build"} {
|
||||
delete(service, key)
|
||||
if value, exists := target[key]; exists {
|
||||
service[key] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return yaml.Marshal(currentCompose)
|
||||
}
|
||||
65
frontend/src/utils/runtime-environment.ts
Normal file
65
frontend/src/utils/runtime-environment.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
export interface EnvironmentEntry {
|
||||
key: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export const parseEnvironment = (text: string) => {
|
||||
const entries: EnvironmentEntry[] = [];
|
||||
const lines = text
|
||||
.replace(/^\uFEFF/, '')
|
||||
.replace(/\r\n?/g, '\n')
|
||||
.split('\n');
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i].trimStart();
|
||||
if (!line || line.startsWith('#')) continue;
|
||||
const match = /^(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/.exec(line);
|
||||
if (!match) return { entries: [], error: { line: i + 1, reason: 'envInvalidAssignment' } };
|
||||
const key = match[1];
|
||||
let value = match[2];
|
||||
const quote = value[0];
|
||||
if (quote === '"' || quote === "'") {
|
||||
const startLine = i + 1;
|
||||
let end = 1;
|
||||
// Quoted values may span lines; never evaluate variable or command substitutions.
|
||||
for (; ; end++) {
|
||||
if (end >= value.length) {
|
||||
if (++i >= lines.length) {
|
||||
return { entries: [], error: { line: startLine, reason: 'envUnclosedQuote' } };
|
||||
}
|
||||
value += '\n' + lines[i];
|
||||
}
|
||||
if (value[end] === '\\' && (value[end + 1] === quote || value[end + 1] === '\\')) {
|
||||
end++;
|
||||
} else if (value[end] === quote) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
const trailing = value.slice(end + 1).trim();
|
||||
if (trailing && !trailing.startsWith('#')) {
|
||||
return { entries: [], error: { line: i + 1, reason: 'envUnexpectedText' } };
|
||||
}
|
||||
value = value.slice(1, end);
|
||||
if (quote === '"') {
|
||||
value = value.replace(/\\([nrt"\\])/g, (_, char: string) => {
|
||||
return ({ n: '\n', r: '\r', t: '\t', '"': '"', '\\': '\\' } as Record<string, string>)[char];
|
||||
});
|
||||
} else {
|
||||
value = value.replace(/\\'/g, "'");
|
||||
}
|
||||
} else {
|
||||
value = value.startsWith('#') ? '' : value.replace(/\s+#.*$/, '').trimEnd();
|
||||
}
|
||||
const previous = entries.find((entry) => entry.key === key);
|
||||
if (previous) previous.value = value;
|
||||
else entries.push({ key, value });
|
||||
}
|
||||
return { entries, error: null };
|
||||
};
|
||||
|
||||
export const mergeEnvironments = (current: EnvironmentEntry[], incoming: EnvironmentEntry[]) => {
|
||||
for (const entry of incoming) {
|
||||
const existing = current.find((item) => item.key === entry.key);
|
||||
if (existing) existing.value = entry.value;
|
||||
else current.push({ ...entry });
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user