mirror of
https://hubproxy.babadafafafafa.cn/https://github.com/1Panel-dev/1Panel.git
synced 2026-09-20 08:03:55 +08:00
feat: Optimize the OpenResty module compilation logic. (#13355)
* refactor: separate openresty module catalog and state * feat: Optimize the OpenResty module compilation logic.
This commit is contained in:
@@ -90,14 +90,13 @@ var RealIPKeys = map[string]struct{}{"X-Forwarded-For": {}, "X-Real-IP": {}, "CF
|
|||||||
|
|
||||||
type NginxModule struct {
|
type NginxModule struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
Custom bool `json:"custom,omitempty"`
|
||||||
Script string `json:"script"`
|
Script string `json:"script"`
|
||||||
Packages []string `json:"packages"`
|
Packages []string `json:"packages"`
|
||||||
Params string `json:"params"`
|
Params string `json:"params"`
|
||||||
Enable bool `json:"enable"`
|
Enable bool `json:"enable"`
|
||||||
Deleted bool `json:"deleted,omitempty"`
|
|
||||||
BuildMode string `json:"buildMode,omitempty"`
|
BuildMode string `json:"buildMode,omitempty"`
|
||||||
Provider string `json:"provider,omitempty"`
|
Provider string `json:"provider,omitempty"`
|
||||||
DynamicSupport string `json:"dynamicSupport,omitempty"`
|
|
||||||
LoadOrder int `json:"loadOrder,omitempty"`
|
LoadOrder int `json:"loadOrder,omitempty"`
|
||||||
Builds []NginxModuleBuild `json:"builds,omitempty"`
|
Builds []NginxModuleBuild `json:"builds,omitempty"`
|
||||||
LastError string `json:"lastError,omitempty"`
|
LastError string `json:"lastError,omitempty"`
|
||||||
@@ -105,6 +104,7 @@ type NginxModule struct {
|
|||||||
|
|
||||||
type NginxModuleBuild struct {
|
type NginxModuleBuild struct {
|
||||||
Provider string `json:"provider"`
|
Provider string `json:"provider"`
|
||||||
|
BuildMode string `json:"buildMode"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
Hash string `json:"hash"`
|
Hash string `json:"hash"`
|
||||||
Target NginxModuleTarget `json:"target"`
|
Target NginxModuleTarget `json:"target"`
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ type NginxModuleUpdate struct {
|
|||||||
Packages string `json:"packages"`
|
Packages string `json:"packages"`
|
||||||
Enable bool `json:"enable"`
|
Enable bool `json:"enable"`
|
||||||
Params string `json:"params"`
|
Params string `json:"params"`
|
||||||
BuildMode string `json:"buildMode" validate:"omitempty,oneof=auto dynamic static"`
|
BuildMode string `json:"buildMode" validate:"omitempty,oneof=dynamic static"`
|
||||||
Provider string `json:"provider" validate:"omitempty,oneof=local prebuilt"`
|
Provider string `json:"provider" validate:"omitempty,oneof=local prebuilt"`
|
||||||
LoadOrder int `json:"loadOrder" validate:"omitempty,min=0,max=9999"`
|
LoadOrder int `json:"loadOrder" validate:"omitempty,min=0,max=9999"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,17 +70,16 @@ type NginxProxyCache struct {
|
|||||||
|
|
||||||
type NginxModule struct {
|
type NginxModule struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
Custom bool `json:"custom"`
|
||||||
Script string `json:"script"`
|
Script string `json:"script"`
|
||||||
Packages string `json:"packages"`
|
Packages string `json:"packages"`
|
||||||
Params string `json:"params"`
|
Params string `json:"params"`
|
||||||
Enable bool `json:"enable"`
|
Enable bool `json:"enable"`
|
||||||
BuildMode string `json:"buildMode"`
|
BuildMode string `json:"buildMode"`
|
||||||
Provider string `json:"provider"`
|
Provider string `json:"provider"`
|
||||||
DynamicSupport string `json:"dynamicSupport"`
|
|
||||||
LoadOrder int `json:"loadOrder"`
|
LoadOrder int `json:"loadOrder"`
|
||||||
BuildStatus string `json:"buildStatus"`
|
BuildStatus string `json:"buildStatus"`
|
||||||
LoadStatus string `json:"loadStatus"`
|
LoadStatus string `json:"loadStatus"`
|
||||||
Compatibility string `json:"compatibility"`
|
|
||||||
Artifacts []dto.NginxModuleArtifact `json:"artifacts"`
|
Artifacts []dto.NginxModuleArtifact `json:"artifacts"`
|
||||||
LastError string `json:"lastError"`
|
LastError string `json:"lastError"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -752,13 +752,13 @@ func getUpgradeCompose(install model.AppInstall, detail model.AppDetail) (string
|
|||||||
return string(composeByte), nil
|
return string(composeByte), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildNginx(parentTask *task.Task, nginxInstall model.AppInstall) error {
|
func buildNginx(parentTask *task.Task, nginxInstall model.AppInstall, catalogPath string) error {
|
||||||
fileOp := files.NewFileOp()
|
fileOp := files.NewFileOp()
|
||||||
buildPath := path.Join(nginxInstall.GetPath(), nginxModuleBuildDir)
|
buildPath := path.Join(nginxInstall.GetPath(), nginxModuleBuildDir)
|
||||||
if !fileOp.Stat(buildPath) {
|
if !fileOp.Stat(buildPath) {
|
||||||
return buserr.New("ErrBuildDirNotFound")
|
return buserr.New("ErrBuildDirNotFound")
|
||||||
}
|
}
|
||||||
modules, err := loadNginxModules(nginxInstall)
|
modules, err := loadNginxModulesWithCatalog(nginxInstall, catalogPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -776,11 +776,11 @@ func buildNginx(parentTask *task.Task, nginxInstall model.AppInstall) error {
|
|||||||
}
|
}
|
||||||
parentTask.LogSuccess(logStr)
|
parentTask.LogSuccess(logStr)
|
||||||
}
|
}
|
||||||
modules, err = buildDynamicNginxModules(nginxInstall, modules, nil, false, "", parentTask)
|
modules, err = buildDynamicNginxModules(nginxInstall, modules, nil, false, "", catalogPath, parentTask)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return commitNginxModuleBuilds(nginxInstall, previousModules, modules, false)
|
return commitNginxModuleBuilds(nginxInstall, previousModules, modules, false, catalogPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
func upgradeInstall(req request.AppInstallUpgrade) error {
|
func upgradeInstall(req request.AppInstallUpgrade) error {
|
||||||
@@ -788,6 +788,7 @@ func upgradeInstall(req request.AppInstallUpgrade) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
originalInstall := install
|
||||||
oldVersion := install.Version
|
oldVersion := install.Version
|
||||||
detail, err := appDetailRepo.GetFirst(repo.WithByID(req.DetailID))
|
detail, err := appDetailRepo.GetFirst(repo.WithByID(req.DetailID))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -809,6 +810,7 @@ func upgradeInstall(req request.AppInstallUpgrade) error {
|
|||||||
var (
|
var (
|
||||||
upErr error
|
upErr error
|
||||||
backupFile string
|
backupFile string
|
||||||
|
nginxUpgradeSnapshot *openrestyUpgradeSnapshot
|
||||||
)
|
)
|
||||||
backUpApp := func(t *task.Task) error {
|
backUpApp := func(t *task.Task) error {
|
||||||
backupService := NewIBackupService()
|
backupService := NewIBackupService()
|
||||||
@@ -864,6 +866,13 @@ func upgradeInstall(req request.AppInstallUpgrade) error {
|
|||||||
}
|
}
|
||||||
oldEnvContent := append([]byte(nil), content...)
|
oldEnvContent := append([]byte(nil), content...)
|
||||||
oldDockerCompose := install.DockerCompose
|
oldDockerCompose := install.DockerCompose
|
||||||
|
targetNginxCatalogPath := ""
|
||||||
|
if install.App.Key == constant.AppOpenresty {
|
||||||
|
nginxUpgradeSnapshot, err = createOpenrestyUpgradeSnapshot(install.GetPath())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
if install.App.Key == vllmAppKeyForUpgrade {
|
if install.App.Key == vllmAppKeyForUpgrade {
|
||||||
envs := make(map[string]interface{})
|
envs := make(map[string]interface{})
|
||||||
if err = json.Unmarshal([]byte(install.Env), &envs); err != nil {
|
if err = json.Unmarshal([]byte(install.Env), &envs); err != nil {
|
||||||
@@ -896,16 +905,17 @@ func upgradeInstall(req request.AppInstallUpgrade) error {
|
|||||||
if err := fileOp.CopyFile(path.Join(detailBuildDir, "Dockerfile"), installBuildDir); err != nil {
|
if err := fileOp.CopyFile(path.Join(detailBuildDir, "Dockerfile"), installBuildDir); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if fileOp.Stat(path.Join(detailBuildDir, nginxModuleBuilderFile)) {
|
if err := syncNginxModuleBuilder(detailBuildDir, installBuildDir); err != nil {
|
||||||
if err := fileOp.CopyFile(path.Join(detailBuildDir, nginxModuleBuilderFile), installBuildDir); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
targetCatalogSource := path.Join(detailBuildDir, nginxModuleCatalogFile)
|
||||||
|
if !fileOp.Stat(targetCatalogSource) {
|
||||||
|
return fmt.Errorf("target OpenResty module catalog not found: %s", targetCatalogSource)
|
||||||
}
|
}
|
||||||
if fileOp.Stat(path.Join(detailBuildDir, nginxModuleCatalogFile)) {
|
targetNginxCatalogPath = path.Join(installBuildDir, nginxModuleCatalogPendingFile)
|
||||||
if err := fileOp.CopyFile(path.Join(detailBuildDir, nginxModuleCatalogFile), installBuildDir); err != nil {
|
if err := stageNginxModuleCatalog(targetCatalogSource, targetNginxCatalogPath); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if err := fileOp.CopyFile(path.Join(detailBuildDir, "nginx.conf"), installBuildDir); err != nil {
|
if err := fileOp.CopyFile(path.Join(detailBuildDir, "nginx.conf"), installBuildDir); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -984,7 +994,7 @@ func upgradeInstall(req request.AppInstallUpgrade) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if install.App.Key == constant.AppOpenresty {
|
if install.App.Key == constant.AppOpenresty {
|
||||||
modules, moduleErr := loadNginxModules(install)
|
modules, moduleErr := loadNginxModulesWithCatalog(install, targetNginxCatalogPath)
|
||||||
if moduleErr != nil {
|
if moduleErr != nil {
|
||||||
return moduleErr
|
return moduleErr
|
||||||
}
|
}
|
||||||
@@ -992,11 +1002,11 @@ func upgradeInstall(req request.AppInstallUpgrade) error {
|
|||||||
// current container. Static modules retain the full rebuild path.
|
// current container. Static modules retain the full rebuild path.
|
||||||
if !hasEnabledStaticNginxModules(modules) {
|
if !hasEnabledStaticNginxModules(modules) {
|
||||||
previousModules := cloneNginxModules(modules)
|
previousModules := cloneNginxModules(modules)
|
||||||
modules, moduleErr = buildDynamicNginxModules(install, modules, nil, false, "", t)
|
modules, moduleErr = buildDynamicNginxModules(install, modules, nil, false, "", targetNginxCatalogPath, t)
|
||||||
if moduleErr != nil {
|
if moduleErr != nil {
|
||||||
return moduleErr
|
return moduleErr
|
||||||
}
|
}
|
||||||
if moduleErr = saveNginxModules(install, modules); moduleErr != nil {
|
if moduleErr = saveNginxModulesWithCatalog(install, modules, targetNginxCatalogPath); moduleErr != nil {
|
||||||
removeNginxModuleOutputsNotReferenced(install, modules, previousModules)
|
removeNginxModuleOutputsNotReferenced(install, modules, previousModules)
|
||||||
return moduleErr
|
return moduleErr
|
||||||
}
|
}
|
||||||
@@ -1037,7 +1047,7 @@ func upgradeInstall(req request.AppInstallUpgrade) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if install.App.Key == constant.AppOpenresty {
|
if install.App.Key == constant.AppOpenresty {
|
||||||
if err = buildNginx(t, install); err != nil {
|
if err = buildNginx(t, install, targetNginxCatalogPath); err != nil {
|
||||||
t.Log(err.Error())
|
t.Log(err.Error())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -1053,9 +1063,25 @@ func upgradeInstall(req request.AppInstallUpgrade) error {
|
|||||||
}
|
}
|
||||||
t.LogSuccess(logStr)
|
t.LogSuccess(logStr)
|
||||||
install.Status = constant.StatusRunning
|
install.Status = constant.StatusRunning
|
||||||
|
if install.App.Key == constant.AppOpenresty {
|
||||||
|
if err = commitStaticNginxModuleBuilds(install, targetNginxCatalogPath, t); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
activeCatalogPath := path.Join(install.GetPath(), nginxModuleBuildDir, nginxModuleCatalogFile)
|
||||||
|
if err = activateNginxModuleCatalogAndCommit(targetNginxCatalogPath, activeCatalogPath, func() error {
|
||||||
|
return appInstallRepo.Save(context.Background(), &install)
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
if err = appInstallRepo.Save(context.Background(), &install); err != nil {
|
if err = appInstallRepo.Save(context.Background(), &install); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if nginxUpgradeSnapshot != nil {
|
||||||
|
nginxUpgradeSnapshot.Cleanup()
|
||||||
|
nginxUpgradeSnapshot = nil
|
||||||
|
}
|
||||||
if req.DeleteImage {
|
if req.DeleteImage {
|
||||||
newEnvContent, err := fileOp.GetContent(install.GetEnvPath())
|
newEnvContent, err := fileOp.GetContent(install.GetEnvPath())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1083,30 +1109,88 @@ func upgradeInstall(req request.AppInstallUpgrade) error {
|
|||||||
rollBackApp := func(t *task.Task) {
|
rollBackApp := func(t *task.Task) {
|
||||||
if req.Backup {
|
if req.Backup {
|
||||||
t.Log(i18n.GetWithName("AppRecover", install.Name))
|
t.Log(i18n.GetWithName("AppRecover", install.Name))
|
||||||
if err := NewIBackupService().AppRecover(dto.CommonRecover{Name: install.App.Key, DetailName: install.Name, Type: "app", DownloadAccountID: 1, File: backupFile}); err != nil {
|
recoverErr := NewIBackupService().AppRecover(dto.CommonRecover{
|
||||||
t.LogFailedWithErr(i18n.GetWithName("AppRecover", install.Name), err)
|
Name: install.App.Key, DetailName: install.Name, Type: "app", DownloadAccountID: 1, File: backupFile,
|
||||||
return
|
})
|
||||||
|
if recoverErr == nil {
|
||||||
|
if nginxUpgradeSnapshot != nil {
|
||||||
|
nginxUpgradeSnapshot.Cleanup()
|
||||||
|
nginxUpgradeSnapshot = nil
|
||||||
}
|
}
|
||||||
t.LogSuccess(i18n.GetWithName("AppRecover", install.Name))
|
t.LogSuccess(i18n.GetWithName("AppRecover", install.Name))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
t.LogFailedWithErr(i18n.GetWithName("AppRecover", install.Name), recoverErr)
|
||||||
|
if install.App.Key != constant.AppOpenresty {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if install.App.Key == constant.AppOpenresty && nginxUpgradeSnapshot != nil {
|
||||||
|
if out, rollbackErr := compose.Down(install.GetComposePath()); rollbackErr != nil {
|
||||||
|
if out != "" {
|
||||||
|
rollbackErr = fmt.Errorf("%s: %w", out, rollbackErr)
|
||||||
|
}
|
||||||
|
t.LogFailedWithErr(i18n.GetWithName("AppRecover", install.Name), rollbackErr)
|
||||||
|
}
|
||||||
|
if rollbackErr := nginxUpgradeSnapshot.Restore(); rollbackErr != nil {
|
||||||
|
t.LogFailedWithErr(i18n.GetWithName("AppRecover", install.Name), rollbackErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
nginxUpgradeSnapshot.Cleanup()
|
||||||
|
nginxUpgradeSnapshot = nil
|
||||||
|
if out, rollbackErr := compose.Up(originalInstall.GetComposePath()); rollbackErr != nil {
|
||||||
|
if out != "" {
|
||||||
|
rollbackErr = fmt.Errorf("%s: %w", out, rollbackErr)
|
||||||
|
}
|
||||||
|
t.LogFailedWithErr(i18n.GetWithName("AppRecover", install.Name), rollbackErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
originalInstall.Status = constant.StatusRunning
|
||||||
|
originalInstall.Message = ""
|
||||||
|
if rollbackErr := appInstallRepo.Save(context.Background(), &originalInstall); rollbackErr != nil {
|
||||||
|
t.LogFailedWithErr(i18n.GetWithName("AppRecover", install.Name), rollbackErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
install = originalInstall
|
||||||
|
t.LogSuccess(i18n.GetWithName("AppRecover", install.Name))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if install.App.Key == constant.AppOpenresty {
|
||||||
|
if rollbackErr := appInstallRepo.Save(context.Background(), &originalInstall); rollbackErr != nil {
|
||||||
|
t.LogFailedWithErr(i18n.GetWithName("AppRecover", install.Name), rollbackErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
install = originalInstall
|
||||||
|
t.LogSuccess(i18n.GetWithName("AppRecover", install.Name))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
upgradeTask.AddSubTaskWithOps(task.GetTaskName(install.Name, task.TaskUpgrade, task.TaskScopeApp), upgradeApp, rollBackApp, 0, 1*time.Hour)
|
upgradeTimeout := 1 * time.Hour
|
||||||
|
if install.App.Key == constant.AppOpenresty {
|
||||||
|
// Dynamic modules are built serially and each Docker build has its own
|
||||||
|
// timeout. An outer deadline would start rollback while upgradeApp is
|
||||||
|
// still mutating the installation because SubTask does not stop its
|
||||||
|
// action goroutine on timeout.
|
||||||
|
upgradeTimeout = 0
|
||||||
|
}
|
||||||
|
upgradeTask.AddSubTaskWithOps(task.GetTaskName(install.Name, task.TaskUpgrade, task.TaskScopeApp), upgradeApp, rollBackApp, 0, upgradeTimeout)
|
||||||
|
|
||||||
|
upgradingInstall := install
|
||||||
|
if err = appInstallRepo.Save(context.Background(), &upgradingInstall); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
go func() {
|
go func() {
|
||||||
err = upgradeTask.Execute()
|
if taskErr := upgradeTask.Execute(); taskErr != nil {
|
||||||
if err != nil {
|
|
||||||
existInstall, _ := appInstallRepo.GetFirst(repo.WithByID(req.InstallID))
|
existInstall, _ := appInstallRepo.GetFirst(repo.WithByID(req.InstallID))
|
||||||
if existInstall.ID > 0 && existInstall.Status != constant.StatusRunning {
|
if existInstall.ID > 0 && existInstall.Status != constant.StatusRunning {
|
||||||
existInstall.Status = constant.StatusUpgradeErr
|
existInstall.Status = constant.StatusUpgradeErr
|
||||||
existInstall.Message = err.Error()
|
existInstall.Message = taskErr.Error()
|
||||||
_ = appInstallRepo.Save(context.Background(), &existInstall)
|
_ = appInstallRepo.Save(context.Background(), &existInstall)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return appInstallRepo.Save(context.Background(), &install)
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func skipCheckStatus(service types.ServiceConfig) bool {
|
func skipCheckStatus(service types.ServiceConfig) bool {
|
||||||
|
|||||||
@@ -213,33 +213,39 @@ func (n NginxService) GetModules() (*response.NginxBuildConfig, error) {
|
|||||||
if targetWarning != "" {
|
if targetWarning != "" {
|
||||||
global.LOG.Warn(targetWarning)
|
global.LOG.Warn(targetWarning)
|
||||||
}
|
}
|
||||||
|
runtimeTarget := target
|
||||||
|
runtimeTarget.BuilderDigest = ""
|
||||||
|
setNginxModuleTargetKey(&runtimeTarget)
|
||||||
|
runtimeTargetErr := targetErr
|
||||||
|
if runtimeTarget.OpenRestyVersion != "" {
|
||||||
|
runtimeTargetErr = nil
|
||||||
|
}
|
||||||
var resList []response.NginxModule
|
var resList []response.NginxModule
|
||||||
for _, module := range modules {
|
for _, module := range modules {
|
||||||
if module.Deleted {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
buildStatus := nginxModuleStatusPending
|
buildStatus := nginxModuleStatusPending
|
||||||
loadStatus := nginxModuleLoadDisabled
|
loadStatus := nginxModuleLoadDisabled
|
||||||
compatibility := "unknown"
|
|
||||||
var artifacts []dto.NginxModuleArtifact
|
var artifacts []dto.NginxModuleArtifact
|
||||||
if module.BuildMode == nginxModuleBuildStatic {
|
if module.BuildMode == nginxModuleBuildStatic && runtimeTargetErr == nil {
|
||||||
|
if build := findCurrentNginxModuleBuild(module, runtimeTarget); build != nil && build.Status == nginxModuleStatusReady {
|
||||||
buildStatus = nginxModuleStatusReady
|
buildStatus = nginxModuleStatusReady
|
||||||
compatibility = "static"
|
|
||||||
if module.Enable {
|
if module.Enable {
|
||||||
loadStatus = nginxModuleLoadEnabled
|
loadStatus = nginxModuleLoadEnabled
|
||||||
}
|
}
|
||||||
|
} else if findLatestNginxModuleBuild(module, runtimeTarget) != nil {
|
||||||
|
if module.Enable {
|
||||||
|
loadStatus = nginxModuleLoadEnabled
|
||||||
|
}
|
||||||
|
}
|
||||||
} else if targetErr == nil {
|
} else if targetErr == nil {
|
||||||
if build := findCurrentNginxModuleBuild(module, target); build != nil {
|
if build := findCurrentNginxModuleBuild(module, target); build != nil {
|
||||||
buildStatus = build.Status
|
buildStatus = build.Status
|
||||||
artifacts = build.Artifacts
|
artifacts = build.Artifacts
|
||||||
if build.Status == nginxModuleStatusReady {
|
if build.Status == nginxModuleStatusReady {
|
||||||
compatibility = "compatible"
|
|
||||||
if module.Enable {
|
if module.Enable {
|
||||||
loadStatus = nginxModuleLoadEnabled
|
loadStatus = nginxModuleLoadEnabled
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if latestBuild := findLatestNginxModuleBuild(module, target); latestBuild != nil {
|
} else if latestBuild := findLatestNginxModuleBuild(module, target); latestBuild != nil {
|
||||||
compatibility = "stale"
|
|
||||||
artifacts = latestBuild.Artifacts
|
artifacts = latestBuild.Artifacts
|
||||||
if module.Enable {
|
if module.Enable {
|
||||||
loadStatus = nginxModuleLoadEnabled
|
loadStatus = nginxModuleLoadEnabled
|
||||||
@@ -251,17 +257,16 @@ func (n NginxService) GetModules() (*response.NginxBuildConfig, error) {
|
|||||||
}
|
}
|
||||||
resList = append(resList, response.NginxModule{
|
resList = append(resList, response.NginxModule{
|
||||||
Name: module.Name,
|
Name: module.Name,
|
||||||
|
Custom: module.Custom,
|
||||||
Script: module.Script,
|
Script: module.Script,
|
||||||
Packages: strings.Join(module.Packages, ","),
|
Packages: strings.Join(module.Packages, ","),
|
||||||
Params: module.Params,
|
Params: module.Params,
|
||||||
Enable: module.Enable,
|
Enable: module.Enable,
|
||||||
BuildMode: module.BuildMode,
|
BuildMode: module.BuildMode,
|
||||||
Provider: module.Provider,
|
Provider: module.Provider,
|
||||||
DynamicSupport: module.DynamicSupport,
|
|
||||||
LoadOrder: module.LoadOrder,
|
LoadOrder: module.LoadOrder,
|
||||||
BuildStatus: buildStatus,
|
BuildStatus: buildStatus,
|
||||||
LoadStatus: loadStatus,
|
LoadStatus: loadStatus,
|
||||||
Compatibility: compatibility,
|
|
||||||
Artifacts: artifacts,
|
Artifacts: artifacts,
|
||||||
LastError: module.LastError,
|
LastError: module.LastError,
|
||||||
})
|
})
|
||||||
@@ -278,6 +283,70 @@ func (n NginxService) GetModules() (*response.NginxBuildConfig, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func applyNginxModuleUpdate(modules []dto.NginxModule, req request.NginxModuleUpdate) ([]dto.NginxModule, *dto.NginxModule, error) {
|
||||||
|
switch req.Operate {
|
||||||
|
case nginxModuleOperateCreate:
|
||||||
|
if err := validateNginxModuleBuildMode(dto.NginxModule{Name: req.Name, BuildMode: req.BuildMode}); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
for _, module := range modules {
|
||||||
|
if module.Name == req.Name {
|
||||||
|
return nil, nil, buserr.New("ErrNameIsExist")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
modules = append(modules, dto.NginxModule{
|
||||||
|
Name: req.Name,
|
||||||
|
Custom: true,
|
||||||
|
Script: req.Script,
|
||||||
|
Packages: strings.Split(req.Packages, ","),
|
||||||
|
Params: req.Params,
|
||||||
|
Enable: req.Enable,
|
||||||
|
BuildMode: req.BuildMode,
|
||||||
|
Provider: req.Provider,
|
||||||
|
LoadOrder: req.LoadOrder,
|
||||||
|
})
|
||||||
|
case nginxModuleOperateUpdate:
|
||||||
|
for i := range modules {
|
||||||
|
if modules[i].Name != req.Name {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if modules[i].Custom {
|
||||||
|
if err := validateNginxModuleBuildMode(dto.NginxModule{Name: req.Name, BuildMode: req.BuildMode}); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
modules[i].Enable = req.Enable
|
||||||
|
return modules, nil, nil
|
||||||
|
}
|
||||||
|
modules[i].Enable = req.Enable
|
||||||
|
modules[i].Script = req.Script
|
||||||
|
modules[i].Packages = strings.Split(req.Packages, ",")
|
||||||
|
modules[i].Params = req.Params
|
||||||
|
modules[i].BuildMode = req.BuildMode
|
||||||
|
modules[i].Provider = req.Provider
|
||||||
|
modules[i].LoadOrder = req.LoadOrder
|
||||||
|
return modules, nil, nil
|
||||||
|
}
|
||||||
|
return nil, nil, fmt.Errorf("OpenResty module %s not found", req.Name)
|
||||||
|
case nginxModuleOperateDelete:
|
||||||
|
for i := range modules {
|
||||||
|
if modules[i].Name != req.Name {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !modules[i].Custom {
|
||||||
|
return nil, nil, fmt.Errorf("built-in OpenResty module %s cannot be deleted", req.Name)
|
||||||
|
}
|
||||||
|
deleted := modules[i]
|
||||||
|
modules = append(modules[:i], modules[i+1:]...)
|
||||||
|
return modules, &deleted, nil
|
||||||
|
}
|
||||||
|
return nil, nil, fmt.Errorf("OpenResty module %s not found", req.Name)
|
||||||
|
default:
|
||||||
|
return nil, nil, fmt.Errorf("unsupported OpenResty module operation %q", req.Operate)
|
||||||
|
}
|
||||||
|
return modules, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (n NginxService) UpdateModule(req request.NginxModuleUpdate) error {
|
func (n NginxService) UpdateModule(req request.NginxModuleUpdate) error {
|
||||||
nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
|
nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -291,70 +360,9 @@ func (n NginxService) UpdateModule(req request.NginxModuleUpdate) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
oldModules := cloneNginxModules(modules)
|
oldModules := cloneNginxModules(modules)
|
||||||
var deletedModule *dto.NginxModule
|
modules, deletedModule, err := applyNginxModuleUpdate(modules, req)
|
||||||
|
if err != nil {
|
||||||
switch req.Operate {
|
return err
|
||||||
case nginxModuleOperateCreate:
|
|
||||||
recreated := false
|
|
||||||
for i, module := range modules {
|
|
||||||
if module.Name == req.Name {
|
|
||||||
if module.Deleted {
|
|
||||||
modules[i] = dto.NginxModule{
|
|
||||||
Name: req.Name, Script: req.Script, Packages: strings.Split(req.Packages, ","),
|
|
||||||
Params: req.Params,
|
|
||||||
Enable: req.Enable, BuildMode: req.BuildMode, Provider: req.Provider, LoadOrder: req.LoadOrder,
|
|
||||||
}
|
|
||||||
recreated = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
return buserr.New("ErrNameIsExist")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !recreated {
|
|
||||||
modules = append(modules, dto.NginxModule{
|
|
||||||
Name: req.Name,
|
|
||||||
Script: req.Script,
|
|
||||||
Packages: strings.Split(req.Packages, ","),
|
|
||||||
Params: req.Params,
|
|
||||||
Enable: req.Enable,
|
|
||||||
BuildMode: req.BuildMode,
|
|
||||||
Provider: req.Provider,
|
|
||||||
LoadOrder: req.LoadOrder,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
case nginxModuleOperateUpdate:
|
|
||||||
found := false
|
|
||||||
for i, module := range modules {
|
|
||||||
if module.Name == req.Name {
|
|
||||||
found = true
|
|
||||||
modules[i].Script = req.Script
|
|
||||||
modules[i].Packages = strings.Split(req.Packages, ",")
|
|
||||||
modules[i].Params = req.Params
|
|
||||||
modules[i].Enable = req.Enable
|
|
||||||
modules[i].BuildMode = req.BuildMode
|
|
||||||
modules[i].Provider = req.Provider
|
|
||||||
modules[i].LoadOrder = req.LoadOrder
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
return fmt.Errorf("OpenResty module %s not found", req.Name)
|
|
||||||
}
|
|
||||||
case nginxModuleOperateDelete:
|
|
||||||
found := false
|
|
||||||
for i, module := range modules {
|
|
||||||
if module.Name == req.Name {
|
|
||||||
found = true
|
|
||||||
moduleCopy := module
|
|
||||||
deletedModule = &moduleCopy
|
|
||||||
modules[i].Deleted = true
|
|
||||||
modules[i].Enable = false
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
return fmt.Errorf("OpenResty module %s not found", req.Name)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if err = saveNginxModules(nginxInstall, modules); err != nil {
|
if err = saveNginxModules(nginxInstall, modules); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -30,16 +30,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
nginxModuleBuildAuto = "auto"
|
|
||||||
nginxModuleBuildDynamic = "dynamic"
|
nginxModuleBuildDynamic = "dynamic"
|
||||||
nginxModuleBuildStatic = "static"
|
nginxModuleBuildStatic = "static"
|
||||||
|
|
||||||
nginxModuleProviderLocal = "local"
|
nginxModuleProviderLocal = "local"
|
||||||
|
|
||||||
nginxModuleSupportUnknown = "unknown"
|
|
||||||
nginxModuleSupportSupported = "supported"
|
|
||||||
nginxModuleSupportUnsupported = "unsupported"
|
|
||||||
|
|
||||||
nginxModuleStatusPending = "pending"
|
nginxModuleStatusPending = "pending"
|
||||||
nginxModuleStatusReady = "ready"
|
nginxModuleStatusReady = "ready"
|
||||||
nginxModuleStatusFailed = "failed"
|
nginxModuleStatusFailed = "failed"
|
||||||
@@ -61,6 +56,7 @@ const (
|
|||||||
nginxModuleBuilderFile = "Dockerfile.modules"
|
nginxModuleBuilderFile = "Dockerfile.modules"
|
||||||
nginxModuleStoreFile = "module.json"
|
nginxModuleStoreFile = "module.json"
|
||||||
nginxModuleCatalogFile = "module.catalog.json"
|
nginxModuleCatalogFile = "module.catalog.json"
|
||||||
|
nginxModuleCatalogPendingFile = "module.catalog.pending.json"
|
||||||
nginxModulePreScriptFile = "module-pre.sh"
|
nginxModulePreScriptFile = "module-pre.sh"
|
||||||
nginxModuleConfigArgsFile = "module-config.args"
|
nginxModuleConfigArgsFile = "module-config.args"
|
||||||
nginxModuleStaticPreScript = "pre.sh"
|
nginxModuleStaticPreScript = "pre.sh"
|
||||||
@@ -93,21 +89,44 @@ type nginxModuleArtifactProvider interface {
|
|||||||
|
|
||||||
type localNginxModuleProvider struct{}
|
type localNginxModuleProvider struct{}
|
||||||
|
|
||||||
|
type nginxModuleState struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Custom bool `json:"custom,omitempty"`
|
||||||
|
Script string `json:"script,omitempty"`
|
||||||
|
Packages []string `json:"packages,omitempty"`
|
||||||
|
Params string `json:"params,omitempty"`
|
||||||
|
Enable bool `json:"enable"`
|
||||||
|
BuildMode string `json:"buildMode,omitempty"`
|
||||||
|
Provider string `json:"provider,omitempty"`
|
||||||
|
LoadOrder int `json:"loadOrder,omitempty"`
|
||||||
|
Builds []dto.NginxModuleBuild `json:"builds,omitempty"`
|
||||||
|
LastError string `json:"lastError,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
func (localNginxModuleProvider) Name() string {
|
func (localNginxModuleProvider) Name() string {
|
||||||
return nginxModuleProviderLocal
|
return nginxModuleProviderLocal
|
||||||
}
|
}
|
||||||
|
|
||||||
func resolveNginxModuleTarget(install model.AppInstall) (dto.NginxModuleTarget, string, error) {
|
func resolveNginxModuleTarget(install model.AppInstall) (dto.NginxModuleTarget, string, error) {
|
||||||
|
target, warning, err := resolveNginxRuntimeTarget(install)
|
||||||
|
if err != nil {
|
||||||
|
return target, warning, err
|
||||||
|
}
|
||||||
builderPath := path.Join(install.GetPath(), nginxModuleBuildDir, nginxModuleBuilderFile)
|
builderPath := path.Join(install.GetPath(), nginxModuleBuildDir, nginxModuleBuilderFile)
|
||||||
builderContent, err := os.ReadFile(builderPath)
|
builderContent, err := os.ReadFile(builderPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return dto.NginxModuleTarget{}, "", fmt.Errorf("%w: %v", errNginxModuleBuilderMissing, err)
|
return target, warning, fmt.Errorf("%w: %v", errNginxModuleBuilderMissing, err)
|
||||||
}
|
}
|
||||||
builderSum := sha256.Sum256(builderContent)
|
builderSum := sha256.Sum256(builderContent)
|
||||||
|
target.BuilderDigest = hex.EncodeToString(builderSum[:])
|
||||||
|
setNginxModuleTargetKey(&target)
|
||||||
|
return target, warning, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolveNginxRuntimeTarget(install model.AppInstall) (dto.NginxModuleTarget, string, error) {
|
||||||
target := dto.NginxModuleTarget{
|
target := dto.NginxModuleTarget{
|
||||||
OpenRestyVersion: install.Version,
|
OpenRestyVersion: install.Version,
|
||||||
Architecture: runtime.GOARCH,
|
Architecture: runtime.GOARCH,
|
||||||
BuilderDigest: hex.EncodeToString(builderSum[:]),
|
|
||||||
}
|
}
|
||||||
envContent, _ := os.ReadFile(install.GetEnvPath())
|
envContent, _ := os.ReadFile(install.GetEnvPath())
|
||||||
images, imageErr := dockerUtils.GetImagesFromDockerCompose(envContent, []byte(install.DockerCompose))
|
images, imageErr := dockerUtils.GetImagesFromDockerCompose(envContent, []byte(install.DockerCompose))
|
||||||
@@ -126,10 +145,14 @@ func resolveNginxModuleTarget(install model.AppInstall) (dto.NginxModuleTarget,
|
|||||||
target.Architecture = fields[1]
|
target.Architecture = fields[1]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
setNginxModuleTargetKey(&target)
|
||||||
|
return target, warning, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setNginxModuleTargetKey(target *dto.NginxModuleTarget) {
|
||||||
keyInput := strings.Join([]string{target.OpenRestyVersion, target.Architecture, target.ImageDigest, target.BuilderDigest}, "\x00")
|
keyInput := strings.Join([]string{target.OpenRestyVersion, target.Architecture, target.ImageDigest, target.BuilderDigest}, "\x00")
|
||||||
keySum := sha256.Sum256([]byte(keyInput))
|
keySum := sha256.Sum256([]byte(keyInput))
|
||||||
target.Key = fmt.Sprintf("%s-%s-%s", sanitizeModulePathPart(target.OpenRestyVersion), target.Architecture, hex.EncodeToString(keySum[:6]))
|
target.Key = fmt.Sprintf("%s-%s-%s", sanitizeModulePathPart(target.OpenRestyVersion), target.Architecture, hex.EncodeToString(keySum[:6]))
|
||||||
return target, warning, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// nginxModuleDynamicSupported reports whether the installed version ships both
|
// nginxModuleDynamicSupported reports whether the installed version ships both
|
||||||
@@ -141,6 +164,21 @@ func nginxModuleDynamicSupported(install model.AppInstall) bool {
|
|||||||
fileOp.Stat(path.Join(buildPath, nginxModuleCatalogFile))
|
fileOp.Stat(path.Join(buildPath, nginxModuleCatalogFile))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func syncNginxModuleBuilder(detailBuildDir, installBuildDir string) error {
|
||||||
|
sourcePath := path.Join(detailBuildDir, nginxModuleBuilderFile)
|
||||||
|
targetPath := path.Join(installBuildDir, nginxModuleBuilderFile)
|
||||||
|
if _, err := os.Stat(sourcePath); err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
if removeErr := os.Remove(targetPath); removeErr != nil && !os.IsNotExist(removeErr) {
|
||||||
|
return removeErr
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return files.NewFileOp().CopyFile(sourcePath, installBuildDir)
|
||||||
|
}
|
||||||
|
|
||||||
// resolveNginxModuleBuildMirror picks the apt mirror for module builds: the
|
// resolveNginxModuleBuildMirror picks the apt mirror for module builds: the
|
||||||
// explicit request value wins, otherwise the install's saved
|
// explicit request value wins, otherwise the install's saved
|
||||||
// CONTAINER_PACKAGE_URL.
|
// CONTAINER_PACKAGE_URL.
|
||||||
@@ -155,7 +193,7 @@ func resolveNginxModuleBuildMirror(install model.AppInstall, mirror string) stri
|
|||||||
return strings.TrimSpace(envs["CONTAINER_PACKAGE_URL"])
|
return strings.TrimSpace(envs["CONTAINER_PACKAGE_URL"])
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildDynamicNginxModules(install model.AppInstall, modules []dto.NginxModule, selected []string, force bool, mirror string, parentTask *task.Task) ([]dto.NginxModule, error) {
|
func buildDynamicNginxModules(install model.AppInstall, modules []dto.NginxModule, selected []string, force bool, mirror string, catalogPath string, parentTask *task.Task) ([]dto.NginxModule, error) {
|
||||||
// Skip target resolution entirely when nothing needs a dynamic build, so
|
// Skip target resolution entirely when nothing needs a dynamic build, so
|
||||||
// installs without dynamic modules do not require Dockerfile.modules.
|
// installs without dynamic modules do not require Dockerfile.modules.
|
||||||
if !hasDynamicNginxModuleBuildTask(modules, selected) {
|
if !hasDynamicNginxModuleBuildTask(modules, selected) {
|
||||||
@@ -163,27 +201,8 @@ func buildDynamicNginxModules(install model.AppInstall, modules []dto.NginxModul
|
|||||||
}
|
}
|
||||||
target, targetWarning, err := resolveNginxModuleTarget(install)
|
target, targetWarning, err := resolveNginxModuleTarget(install)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !errors.Is(err, errNginxModuleBuilderMissing) {
|
|
||||||
return modules, err
|
return modules, err
|
||||||
}
|
}
|
||||||
// The new app version ships no dynamic builder: degrade to inactive
|
|
||||||
// modules instead of failing the surrounding install or upgrade.
|
|
||||||
degraded := fmt.Sprintf("dynamic module builder missing, enabled dynamic modules stay inactive: %v", err)
|
|
||||||
if parentTask != nil {
|
|
||||||
parentTask.Logf("WARNING: %s", degraded)
|
|
||||||
} else {
|
|
||||||
global.LOG.Warn(degraded)
|
|
||||||
}
|
|
||||||
for i := range modules {
|
|
||||||
module := &modules[i]
|
|
||||||
normalizeNginxModule(module)
|
|
||||||
if module.Deleted || !module.Enable || module.BuildMode == nginxModuleBuildStatic {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
module.LastError = "dynamic module builder (Dockerfile.modules) not found for target version; module kept inactive"
|
|
||||||
}
|
|
||||||
return modules, nil
|
|
||||||
}
|
|
||||||
if targetWarning != "" {
|
if targetWarning != "" {
|
||||||
parentTask.Logf("WARNING: %s", targetWarning)
|
parentTask.Logf("WARNING: %s", targetWarning)
|
||||||
}
|
}
|
||||||
@@ -214,12 +233,11 @@ func buildDynamicNginxModules(install model.AppInstall, modules []dto.NginxModul
|
|||||||
build.Target = target
|
build.Target = target
|
||||||
build.Error = buildErr.Error()
|
build.Error = buildErr.Error()
|
||||||
build.BuiltAt = time.Now()
|
build.BuiltAt = time.Now()
|
||||||
failedModules := recordNginxModuleBuildFailure(originalModules, module.Name, build, previousBuild, false)
|
failedModules := recordNginxModuleBuildFailure(originalModules, module.Name, build, previousBuild)
|
||||||
removeNginxModuleOutputsNotReferenced(install, modules, failedModules)
|
removeNginxModuleOutputsNotReferenced(install, modules, failedModules)
|
||||||
_ = saveNginxModules(install, failedModules)
|
_ = saveNginxModulesWithCatalog(install, failedModules, catalogPath)
|
||||||
return failedModules, fmt.Errorf("build dynamic module %s: %w%s", module.Name, buildErr, nginxModuleStaticBuildHint)
|
return failedModules, fmt.Errorf("build dynamic module %s: %w%s", module.Name, buildErr, nginxModuleStaticBuildErrorHint(*module))
|
||||||
}
|
}
|
||||||
module.DynamicSupport = nginxModuleSupportSupported
|
|
||||||
err = validateNginxModuleArtifacts(install, build.Artifacts)
|
err = validateNginxModuleArtifacts(install, build.Artifacts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
for outputDir := range nginxModuleOutputDirectories(install, []dto.NginxModule{{Builds: []dto.NginxModuleBuild{build}}}) {
|
for outputDir := range nginxModuleOutputDirectories(install, []dto.NginxModule{{Builds: []dto.NginxModuleBuild{build}}}) {
|
||||||
@@ -229,10 +247,10 @@ func buildDynamicNginxModules(install model.AppInstall, modules []dto.NginxModul
|
|||||||
build.Error = err.Error()
|
build.Error = err.Error()
|
||||||
build.Artifacts = nil
|
build.Artifacts = nil
|
||||||
build.BuiltAt = time.Now()
|
build.BuiltAt = time.Now()
|
||||||
failedModules := recordNginxModuleBuildFailure(originalModules, module.Name, build, previousBuild, true)
|
failedModules := recordNginxModuleBuildFailure(originalModules, module.Name, build, previousBuild)
|
||||||
removeNginxModuleOutputsNotReferenced(install, modules, failedModules)
|
removeNginxModuleOutputsNotReferenced(install, modules, failedModules)
|
||||||
_ = saveNginxModules(install, failedModules)
|
_ = saveNginxModulesWithCatalog(install, failedModules, catalogPath)
|
||||||
return failedModules, fmt.Errorf("validate dynamic module %s: %w%s", module.Name, err, nginxModuleStaticBuildHint)
|
return failedModules, fmt.Errorf("validate dynamic module %s: %w%s", module.Name, err, nginxModuleStaticBuildErrorHint(*module))
|
||||||
}
|
}
|
||||||
module.LastError = ""
|
module.LastError = ""
|
||||||
upsertNginxModuleBuild(module, build)
|
upsertNginxModuleBuild(module, build)
|
||||||
@@ -258,6 +276,7 @@ func (localNginxModuleProvider) Resolve(spec nginxModuleBuildSpec) (dto.NginxMod
|
|||||||
}
|
}
|
||||||
result := dto.NginxModuleBuild{
|
result := dto.NginxModuleBuild{
|
||||||
Provider: nginxModuleProviderLocal,
|
Provider: nginxModuleProviderLocal,
|
||||||
|
BuildMode: nginxModuleBuildDynamic,
|
||||||
Status: nginxModuleStatusPending,
|
Status: nginxModuleStatusPending,
|
||||||
Hash: buildHash,
|
Hash: buildHash,
|
||||||
Target: spec.Target,
|
Target: spec.Target,
|
||||||
@@ -363,14 +382,14 @@ func (localNginxModuleProvider) Resolve(spec nginxModuleBuildSpec) (dto.NginxMod
|
|||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func commitNginxModuleBuilds(install model.AppInstall, previousModules, modules []dto.NginxModule, reload bool) error {
|
func commitNginxModuleBuilds(install model.AppInstall, previousModules, modules []dto.NginxModule, reload bool, catalogPath string) error {
|
||||||
if err := saveNginxModules(install, modules); err != nil {
|
if err := saveNginxModulesWithCatalog(install, modules, catalogPath); err != nil {
|
||||||
removeNginxModuleOutputsNotReferenced(install, modules, previousModules)
|
removeNginxModuleOutputsNotReferenced(install, modules, previousModules)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := reconcileDynamicNginxModuleConfig(install, modules, reload); err != nil {
|
if err := reconcileDynamicNginxModuleConfig(install, modules, reload); err != nil {
|
||||||
rollbackModules := recordNginxModuleActivationFailure(previousModules, modules, err)
|
rollbackModules := recordNginxModuleActivationFailure(previousModules, modules, err)
|
||||||
_ = saveNginxModules(install, rollbackModules)
|
_ = saveNginxModulesWithCatalog(install, rollbackModules, catalogPath)
|
||||||
removeNginxModuleOutputsNotReferenced(install, modules, previousModules)
|
removeNginxModuleOutputsNotReferenced(install, modules, previousModules)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -488,20 +507,101 @@ func validateNginxModuleLoadConfig(install model.AppInstall, target dto.NginxMod
|
|||||||
|
|
||||||
type nginxModuleConfigSnapshot map[string][]byte
|
type nginxModuleConfigSnapshot map[string][]byte
|
||||||
|
|
||||||
func reconcileDynamicNginxModuleConfig(install model.AppInstall, modules []dto.NginxModule, reload bool) error {
|
type openrestyUpgradeSnapshot struct {
|
||||||
target, targetWarning, err := resolveNginxModuleTarget(install)
|
installPath string
|
||||||
|
backupPath string
|
||||||
|
existing map[string]bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var openrestyUpgradeSnapshotPaths = []string{
|
||||||
|
nginxModuleBuildDir,
|
||||||
|
"scripts",
|
||||||
|
path.Join(nginxModuleConfDir, nginxModuleEnabledConfDir),
|
||||||
|
path.Join(nginxModuleConfDir, "nginx.conf"),
|
||||||
|
"docker-compose.yml",
|
||||||
|
".env",
|
||||||
|
}
|
||||||
|
|
||||||
|
func createOpenrestyUpgradeSnapshot(installPath string) (*openrestyUpgradeSnapshot, error) {
|
||||||
|
backupPath, err := os.MkdirTemp("", "1panel-openresty-upgrade-*")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !errors.Is(err, errNginxModuleBuilderMissing) {
|
return nil, err
|
||||||
|
}
|
||||||
|
snapshot := &openrestyUpgradeSnapshot{
|
||||||
|
installPath: installPath,
|
||||||
|
backupPath: backupPath,
|
||||||
|
existing: make(map[string]bool, len(openrestyUpgradeSnapshotPaths)),
|
||||||
|
}
|
||||||
|
for _, relativePath := range openrestyUpgradeSnapshotPaths {
|
||||||
|
sourcePath := path.Join(installPath, relativePath)
|
||||||
|
if _, err = os.Stat(sourcePath); err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
snapshot.Cleanup()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
snapshot.existing[relativePath] = true
|
||||||
|
if err = copyOpenrestyUpgradeSnapshotEntry(sourcePath, path.Join(backupPath, relativePath)); err != nil {
|
||||||
|
snapshot.Cleanup()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return snapshot, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyOpenrestyUpgradeSnapshotEntry(sourcePath, targetPath string) error {
|
||||||
|
if err := os.MkdirAll(path.Dir(targetPath), constant.DirPerm); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// Without the builder every dynamic module stays inactive: reconcile
|
info, err := os.Stat(sourcePath)
|
||||||
// towards an empty desired state instead of failing the caller.
|
if err != nil {
|
||||||
global.LOG.Warn(err.Error())
|
return err
|
||||||
} else if targetWarning != "" {
|
}
|
||||||
|
fileOp := files.NewFileOp()
|
||||||
|
if info.IsDir() {
|
||||||
|
return fileOp.CopyDir(sourcePath, path.Dir(targetPath))
|
||||||
|
}
|
||||||
|
return fileOp.CopyFile(sourcePath, path.Dir(targetPath))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *openrestyUpgradeSnapshot) Restore() error {
|
||||||
|
for _, relativePath := range openrestyUpgradeSnapshotPaths {
|
||||||
|
targetPath := path.Join(s.installPath, relativePath)
|
||||||
|
if err := os.RemoveAll(targetPath); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !s.existing[relativePath] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := copyOpenrestyUpgradeSnapshotEntry(path.Join(s.backupPath, relativePath), targetPath); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *openrestyUpgradeSnapshot) Cleanup() {
|
||||||
|
if s != nil && s.backupPath != "" {
|
||||||
|
_ = os.RemoveAll(s.backupPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func reconcileDynamicNginxModuleConfig(install model.AppInstall, modules []dto.NginxModule, reload bool) error {
|
||||||
|
var target dto.NginxModuleTarget
|
||||||
|
if hasDynamicNginxModuleBuildTask(modules, nil) {
|
||||||
|
var targetWarning string
|
||||||
|
var err error
|
||||||
|
target, targetWarning, err = resolveNginxModuleTarget(install)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if targetWarning != "" {
|
||||||
global.LOG.Warn(targetWarning)
|
global.LOG.Warn(targetWarning)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
configDir := path.Join(install.GetPath(), nginxModuleConfDir, nginxModuleEnabledConfDir)
|
configDir := path.Join(install.GetPath(), nginxModuleConfDir, nginxModuleEnabledConfDir)
|
||||||
if err = os.MkdirAll(configDir, constant.DirPerm); err != nil {
|
if err := os.MkdirAll(configDir, constant.DirPerm); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
snapshot, err := snapshotManagedNginxModuleConfigs(configDir)
|
snapshot, err := snapshotManagedNginxModuleConfigs(configDir)
|
||||||
@@ -519,7 +619,7 @@ func reconcileDynamicNginxModuleConfig(install model.AppInstall, modules []dto.N
|
|||||||
})
|
})
|
||||||
for _, module := range sortedModules {
|
for _, module := range sortedModules {
|
||||||
normalizeNginxModule(&module)
|
normalizeNginxModule(&module)
|
||||||
if module.Deleted || !module.Enable || module.BuildMode == nginxModuleBuildStatic {
|
if !module.Enable || module.BuildMode == nginxModuleBuildStatic {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
build := findCurrentNginxModuleBuild(module, target)
|
build := findCurrentNginxModuleBuild(module, target)
|
||||||
@@ -624,7 +724,7 @@ func applyManagedNginxModuleConfigs(configDir string, desired map[string][]byte)
|
|||||||
func hasEnabledStaticNginxModules(modules []dto.NginxModule) bool {
|
func hasEnabledStaticNginxModules(modules []dto.NginxModule) bool {
|
||||||
for _, module := range modules {
|
for _, module := range modules {
|
||||||
normalizeNginxModule(&module)
|
normalizeNginxModule(&module)
|
||||||
if !module.Deleted && module.Enable && module.BuildMode == nginxModuleBuildStatic {
|
if module.Enable && module.BuildMode == nginxModuleBuildStatic {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -652,7 +752,7 @@ func configureStaticNginxModules(install model.AppInstall, modules []dto.NginxMo
|
|||||||
}
|
}
|
||||||
for _, module := range modules {
|
for _, module := range modules {
|
||||||
normalizeNginxModule(&module)
|
normalizeNginxModule(&module)
|
||||||
if module.Deleted || !module.Enable || module.BuildMode != nginxModuleBuildStatic {
|
if !module.Enable || module.BuildMode != nginxModuleBuildStatic {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if _, err = preScript.WriteString(module.Script + "\n"); err != nil {
|
if _, err = preScript.WriteString(module.Script + "\n"); err != nil {
|
||||||
@@ -688,15 +788,17 @@ func executeStaticNginxModuleBuild(install model.AppInstall, modules []dto.Nginx
|
|||||||
previousModules := cloneNginxModules(modules)
|
previousModules := cloneNginxModules(modules)
|
||||||
// A rebuilt runtime changes the target ABI, so every enabled dynamic module
|
// A rebuilt runtime changes the target ABI, so every enabled dynamic module
|
||||||
// must be rebuilt even when the user selected only one module.
|
// must be rebuilt even when the user selected only one module.
|
||||||
modules, err := buildDynamicNginxModules(install, modules, nil, force, mirror, parentTask)
|
modules, err := buildDynamicNginxModules(install, modules, nil, force, mirror, "", parentTask)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err = commitNginxModuleBuilds(install, previousModules, modules, false); err != nil {
|
if err = commitNginxModuleBuilds(install, previousModules, modules, false, ""); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, err = compose.DownAndUp(install.GetComposePath())
|
if _, err = compose.DownAndUp(install.GetComposePath()); err != nil {
|
||||||
return err
|
return err
|
||||||
|
}
|
||||||
|
return commitStaticNginxModuleBuilds(install, "", parentTask)
|
||||||
}
|
}
|
||||||
|
|
||||||
func executeNginxModuleBuild(install model.AppInstall, reqModules []string, force bool, mirror string, parentTask *task.Task, reload bool) error {
|
func executeNginxModuleBuild(install model.AppInstall, reqModules []string, force bool, mirror string, parentTask *task.Task, reload bool) error {
|
||||||
@@ -706,21 +808,19 @@ func executeNginxModuleBuild(install model.AppInstall, reqModules []string, forc
|
|||||||
}
|
}
|
||||||
staticBuild := staticNginxBuildRequired(install, modules)
|
staticBuild := staticNginxBuildRequired(install, modules)
|
||||||
if !staticBuild && hasDynamicNginxModuleBuildTask(modules, reqModules) {
|
if !staticBuild && hasDynamicNginxModuleBuildTask(modules, reqModules) {
|
||||||
// An explicit build request must fail loudly when the installed version
|
|
||||||
// does not support dynamic builds; the automatic flows degrade instead.
|
|
||||||
if !nginxModuleDynamicSupported(install) {
|
if !nginxModuleDynamicSupported(install) {
|
||||||
return errors.New("the installed OpenResty version does not support dynamic module builds; use static build mode instead")
|
return errors.New("the installed OpenResty version does not support dynamic module builds")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if staticBuild {
|
if staticBuild {
|
||||||
return executeStaticNginxModuleBuild(install, modules, mirror, force, parentTask)
|
return executeStaticNginxModuleBuild(install, modules, mirror, force, parentTask)
|
||||||
}
|
}
|
||||||
previousModules := cloneNginxModules(modules)
|
previousModules := cloneNginxModules(modules)
|
||||||
modules, err = buildDynamicNginxModules(install, modules, reqModules, force, mirror, parentTask)
|
modules, err = buildDynamicNginxModules(install, modules, reqModules, force, mirror, "", parentTask)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return commitNginxModuleBuilds(install, previousModules, modules, reload)
|
return commitNginxModuleBuilds(install, previousModules, modules, reload, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
func removeNginxModuleArtifacts(install model.AppInstall, module dto.NginxModule) error {
|
func removeNginxModuleArtifacts(install model.AppInstall, module dto.NginxModule) error {
|
||||||
@@ -734,59 +834,82 @@ func removeNginxModuleArtifacts(install model.AppInstall, module dto.NginxModule
|
|||||||
}
|
}
|
||||||
|
|
||||||
func loadNginxModules(install model.AppInstall) ([]dto.NginxModule, error) {
|
func loadNginxModules(install model.AppInstall) ([]dto.NginxModule, error) {
|
||||||
|
return loadNginxModulesWithCatalog(install, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadNginxModulesWithCatalog(install model.AppInstall, catalogPath string) ([]dto.NginxModule, error) {
|
||||||
modulePath := path.Join(install.GetPath(), nginxModuleBuildDir, nginxModuleStoreFile)
|
modulePath := path.Join(install.GetPath(), nginxModuleBuildDir, nginxModuleStoreFile)
|
||||||
modules, err := readNginxModuleFile(modulePath)
|
states, err := readNginxModuleStateFile(modulePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
catalogPath := path.Join(install.GetPath(), nginxModuleBuildDir, nginxModuleCatalogFile)
|
if catalogPath == "" {
|
||||||
catalog, err := readNginxModuleFile(catalogPath)
|
catalogPath = path.Join(install.GetPath(), nginxModuleBuildDir, nginxModuleCatalogFile)
|
||||||
|
}
|
||||||
|
modules, err := readNginxModuleFile(catalogPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
moduleIndexes := make(map[string]int, len(modules))
|
moduleIndexes := make(map[string]int, len(modules))
|
||||||
for i := range modules {
|
for i := range modules {
|
||||||
|
modules[i].Custom = false
|
||||||
|
modules[i].Enable = false
|
||||||
|
modules[i].Builds = nil
|
||||||
|
modules[i].LastError = ""
|
||||||
|
if _, exists := moduleIndexes[modules[i].Name]; exists {
|
||||||
|
return nil, fmt.Errorf("duplicate OpenResty module catalog name %q", modules[i].Name)
|
||||||
|
}
|
||||||
moduleIndexes[modules[i].Name] = i
|
moduleIndexes[modules[i].Name] = i
|
||||||
}
|
}
|
||||||
for _, catalogModule := range catalog {
|
stateNames := make(map[string]struct{}, len(states))
|
||||||
if index, ok := moduleIndexes[catalogModule.Name]; ok {
|
for _, state := range states {
|
||||||
if modules[index].Provider == "" {
|
if _, exists := stateNames[state.Name]; exists {
|
||||||
modules[index].Provider = catalogModule.Provider
|
return nil, fmt.Errorf("duplicate OpenResty module state name %q", state.Name)
|
||||||
}
|
}
|
||||||
if modules[index].DynamicSupport == "" {
|
stateNames[state.Name] = struct{}{}
|
||||||
modules[index].DynamicSupport = catalogModule.DynamicSupport
|
if index, ok := moduleIndexes[state.Name]; ok {
|
||||||
}
|
if state.Custom {
|
||||||
if modules[index].LoadOrder == 0 {
|
return nil, fmt.Errorf("custom OpenResty module %s conflicts with the module catalog", state.Name)
|
||||||
modules[index].LoadOrder = catalogModule.LoadOrder
|
|
||||||
}
|
}
|
||||||
|
modules[index].Enable = state.Enable
|
||||||
|
modules[index].Builds = state.Builds
|
||||||
|
modules[index].LastError = state.LastError
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
modules = append(modules, catalogModule)
|
if !state.Custom {
|
||||||
|
return nil, fmt.Errorf("OpenResty module state %s is missing from the module catalog", state.Name)
|
||||||
|
}
|
||||||
|
modules = append(modules, dto.NginxModule{
|
||||||
|
Name: state.Name, Custom: true, Script: state.Script, Packages: state.Packages, Params: state.Params,
|
||||||
|
Enable: state.Enable, BuildMode: state.BuildMode, Provider: state.Provider, LoadOrder: state.LoadOrder,
|
||||||
|
Builds: state.Builds, LastError: state.LastError,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
for i := range modules {
|
for i := range modules {
|
||||||
|
if err = validateNginxModuleBuildMode(modules[i]); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
normalizeNginxModule(&modules[i])
|
normalizeNginxModule(&modules[i])
|
||||||
}
|
}
|
||||||
probeNginxModuleDynamicSupport(install, modules)
|
|
||||||
return modules, nil
|
return modules, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// probeNginxModuleDynamicSupport marks the dynamic build capability of each
|
func readNginxModuleStateFile(filePath string) ([]nginxModuleState, error) {
|
||||||
// dynamic module from its configure params. It only runs when the installed
|
content, err := os.ReadFile(filePath)
|
||||||
// version supports dynamic builds; otherwise the marker stays "unknown".
|
if err != nil {
|
||||||
func probeNginxModuleDynamicSupport(install model.AppInstall, modules []dto.NginxModule) {
|
if os.IsNotExist(err) {
|
||||||
if !nginxModuleDynamicSupported(install) {
|
return []nginxModuleState{}, nil
|
||||||
return
|
|
||||||
}
|
}
|
||||||
for i := range modules {
|
return nil, err
|
||||||
if modules[i].Deleted || modules[i].BuildMode == nginxModuleBuildStatic {
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
if _, err := normalizeDynamicModuleParams(modules[i].Params); err != nil {
|
if len(strings.TrimSpace(string(content))) == 0 {
|
||||||
modules[i].DynamicSupport = nginxModuleSupportUnsupported
|
return []nginxModuleState{}, nil
|
||||||
} else {
|
|
||||||
modules[i].DynamicSupport = nginxModuleSupportSupported
|
|
||||||
}
|
}
|
||||||
|
var states []nginxModuleState
|
||||||
|
if err = json.Unmarshal(content, &states); err != nil {
|
||||||
|
return nil, fmt.Errorf("parse OpenResty module state %s: %w", filePath, err)
|
||||||
}
|
}
|
||||||
|
return states, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func readNginxModuleFile(filePath string) ([]dto.NginxModule, error) {
|
func readNginxModuleFile(filePath string) ([]dto.NginxModule, error) {
|
||||||
@@ -808,10 +931,53 @@ func readNginxModuleFile(filePath string) ([]dto.NginxModule, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func saveNginxModules(install model.AppInstall, modules []dto.NginxModule) error {
|
func saveNginxModules(install model.AppInstall, modules []dto.NginxModule) error {
|
||||||
for i := range modules {
|
return saveNginxModulesWithCatalog(install, modules, "")
|
||||||
normalizeNginxModule(&modules[i])
|
}
|
||||||
|
|
||||||
|
func saveNginxModulesWithCatalog(install model.AppInstall, modules []dto.NginxModule, catalogPath string) error {
|
||||||
|
if catalogPath == "" {
|
||||||
|
catalogPath = path.Join(install.GetPath(), nginxModuleBuildDir, nginxModuleCatalogFile)
|
||||||
}
|
}
|
||||||
content, err := json.MarshalIndent(modules, "", " ")
|
catalog, err := readNginxModuleFile(catalogPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
catalogNames := make(map[string]struct{}, len(catalog))
|
||||||
|
for _, module := range catalog {
|
||||||
|
if _, exists := catalogNames[module.Name]; exists {
|
||||||
|
return fmt.Errorf("duplicate OpenResty module catalog name %q", module.Name)
|
||||||
|
}
|
||||||
|
catalogNames[module.Name] = struct{}{}
|
||||||
|
}
|
||||||
|
states := make([]nginxModuleState, 0, len(modules))
|
||||||
|
for i := range modules {
|
||||||
|
module := &modules[i]
|
||||||
|
if err = validateNginxModuleBuildMode(*module); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
normalizeNginxModule(module)
|
||||||
|
if _, builtin := catalogNames[module.Name]; builtin {
|
||||||
|
if module.Custom {
|
||||||
|
return fmt.Errorf("custom OpenResty module %s conflicts with the module catalog", module.Name)
|
||||||
|
}
|
||||||
|
if !module.Enable && len(module.Builds) == 0 && module.LastError == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
states = append(states, nginxModuleState{
|
||||||
|
Name: module.Name, Enable: module.Enable, Builds: module.Builds, LastError: module.LastError,
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !module.Custom {
|
||||||
|
return fmt.Errorf("OpenResty module %s is missing from the module catalog", module.Name)
|
||||||
|
}
|
||||||
|
states = append(states, nginxModuleState{
|
||||||
|
Name: module.Name, Custom: true, Script: module.Script, Packages: module.Packages, Params: module.Params,
|
||||||
|
Enable: module.Enable, BuildMode: module.BuildMode, Provider: module.Provider, LoadOrder: module.LoadOrder,
|
||||||
|
Builds: module.Builds, LastError: module.LastError,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
content, err := json.MarshalIndent(states, "", " ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -820,33 +986,64 @@ func saveNginxModules(install model.AppInstall, modules []dto.NginxModule) error
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
tmpPath := modulePath + ".tmp"
|
tmpPath := modulePath + ".tmp"
|
||||||
if err = os.WriteFile(tmpPath, content, constant.FilePerm); err != nil {
|
if err := os.WriteFile(tmpPath, content, constant.FilePerm); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return os.Rename(tmpPath, modulePath)
|
return os.Rename(tmpPath, modulePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func activateNginxModuleCatalog(pendingPath, activePath string) error {
|
||||||
|
return os.Rename(pendingPath, activePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func activateNginxModuleCatalogAndCommit(pendingPath, activePath string, commit func() error) error {
|
||||||
|
previous, err := os.ReadFile(activePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = activateNginxModuleCatalog(pendingPath, activePath); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = commit(); err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if restoreErr := writeNginxModuleCatalog(activePath, previous); restoreErr != nil {
|
||||||
|
return fmt.Errorf("%w; restore previous OpenResty module catalog: %v", err, restoreErr)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func stageNginxModuleCatalog(sourcePath, pendingPath string) error {
|
||||||
|
content, err := os.ReadFile(sourcePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return writeNginxModuleCatalog(pendingPath, content)
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeNginxModuleCatalog(targetPath string, content []byte) error {
|
||||||
|
tmpPath := targetPath + ".tmp"
|
||||||
|
defer func() { _ = os.Remove(tmpPath) }()
|
||||||
|
if err := os.WriteFile(tmpPath, content, constant.FilePerm); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return os.Rename(tmpPath, targetPath)
|
||||||
|
}
|
||||||
|
|
||||||
func normalizeNginxModule(module *dto.NginxModule) {
|
func normalizeNginxModule(module *dto.NginxModule) {
|
||||||
if module.BuildMode == "" {
|
|
||||||
// Entries created before the dynamic-module schema must preserve their old behavior.
|
|
||||||
module.BuildMode = nginxModuleBuildStatic
|
|
||||||
}
|
|
||||||
if module.BuildMode == nginxModuleBuildAuto {
|
|
||||||
// auto is a legacy alias for the dynamic build mode.
|
|
||||||
module.BuildMode = nginxModuleBuildDynamic
|
|
||||||
}
|
|
||||||
if module.Provider == "" {
|
if module.Provider == "" {
|
||||||
module.Provider = nginxModuleProviderLocal
|
module.Provider = nginxModuleProviderLocal
|
||||||
}
|
}
|
||||||
if module.DynamicSupport == "" {
|
|
||||||
module.DynamicSupport = nginxModuleSupportUnknown
|
|
||||||
}
|
|
||||||
if module.LoadOrder == 0 {
|
|
||||||
module.LoadOrder = 50
|
|
||||||
}
|
|
||||||
module.Packages = compactStrings(module.Packages)
|
module.Packages = compactStrings(module.Packages)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func validateNginxModuleBuildMode(module dto.NginxModule) error {
|
||||||
|
if module.BuildMode != nginxModuleBuildDynamic && module.BuildMode != nginxModuleBuildStatic {
|
||||||
|
return fmt.Errorf("OpenResty module %s has invalid build mode %q", module.Name, module.BuildMode)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func compactStrings(items []string) []string {
|
func compactStrings(items []string) []string {
|
||||||
result := make([]string, 0, len(items))
|
result := make([]string, 0, len(items))
|
||||||
seen := make(map[string]struct{})
|
seen := make(map[string]struct{})
|
||||||
@@ -864,12 +1061,19 @@ func compactStrings(items []string) []string {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func nginxModuleStaticBuildErrorHint(module dto.NginxModule) string {
|
||||||
|
if module.Custom {
|
||||||
|
return nginxModuleStaticBuildHint
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
// nginxModuleNeedsDynamicBuild mirrors the dynamic-build filter of
|
// nginxModuleNeedsDynamicBuild mirrors the dynamic-build filter of
|
||||||
// buildDynamicNginxModules. It normalizes a copy so prescan callers never
|
// buildDynamicNginxModules. It normalizes a copy so prescan callers never
|
||||||
// mutate the stored entities.
|
// mutate the stored entities.
|
||||||
func nginxModuleNeedsDynamicBuild(module dto.NginxModule, selectedNames map[string]struct{}) bool {
|
func nginxModuleNeedsDynamicBuild(module dto.NginxModule, selectedNames map[string]struct{}) bool {
|
||||||
normalizeNginxModule(&module)
|
normalizeNginxModule(&module)
|
||||||
if module.Deleted || module.BuildMode == nginxModuleBuildStatic {
|
if module.BuildMode != nginxModuleBuildDynamic {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if len(selectedNames) > 0 {
|
if len(selectedNames) > 0 {
|
||||||
@@ -895,7 +1099,9 @@ func hasDynamicNginxModuleBuildTask(modules []dto.NginxModule, selected []string
|
|||||||
func findLatestNginxModuleBuild(module dto.NginxModule, target dto.NginxModuleTarget) *dto.NginxModuleBuild {
|
func findLatestNginxModuleBuild(module dto.NginxModule, target dto.NginxModuleTarget) *dto.NginxModuleBuild {
|
||||||
var latest *dto.NginxModuleBuild
|
var latest *dto.NginxModuleBuild
|
||||||
for i := range module.Builds {
|
for i := range module.Builds {
|
||||||
if module.Builds[i].Target.Key != target.Key || module.Builds[i].Status != nginxModuleStatusReady {
|
if module.Builds[i].BuildMode != module.BuildMode ||
|
||||||
|
module.Builds[i].Target.Key != target.Key ||
|
||||||
|
module.Builds[i].Status != nginxModuleStatusReady {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if latest == nil || module.Builds[i].BuiltAt.After(latest.BuiltAt) {
|
if latest == nil || module.Builds[i].BuiltAt.After(latest.BuiltAt) {
|
||||||
@@ -906,7 +1112,7 @@ func findLatestNginxModuleBuild(module dto.NginxModule, target dto.NginxModuleTa
|
|||||||
}
|
}
|
||||||
|
|
||||||
func findCurrentNginxModuleBuild(module dto.NginxModule, target dto.NginxModuleTarget) *dto.NginxModuleBuild {
|
func findCurrentNginxModuleBuild(module dto.NginxModule, target dto.NginxModuleTarget) *dto.NginxModuleBuild {
|
||||||
params, err := normalizeDynamicModuleParams(module.Params)
|
params, err := nginxModuleBuildParams(module)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -915,13 +1121,83 @@ func findCurrentNginxModuleBuild(module dto.NginxModule, target dto.NginxModuleT
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
for i := range module.Builds {
|
for i := range module.Builds {
|
||||||
if module.Builds[i].Target.Key == target.Key && module.Builds[i].Hash == buildHash {
|
if module.Builds[i].BuildMode == module.BuildMode &&
|
||||||
|
module.Builds[i].Target.Key == target.Key &&
|
||||||
|
module.Builds[i].Hash == buildHash {
|
||||||
return &module.Builds[i]
|
return &module.Builds[i]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func nginxModuleBuildParams(module dto.NginxModule) (string, error) {
|
||||||
|
if module.BuildMode == nginxModuleBuildStatic {
|
||||||
|
params := strings.TrimSpace(module.Params)
|
||||||
|
if params == "" {
|
||||||
|
return "", errors.New("static module parameters are empty")
|
||||||
|
}
|
||||||
|
return params, nil
|
||||||
|
}
|
||||||
|
return normalizeDynamicModuleParams(module.Params)
|
||||||
|
}
|
||||||
|
|
||||||
|
func recordStaticNginxModuleBuilds(modules []dto.NginxModule, target dto.NginxModuleTarget) ([]dto.NginxModule, error) {
|
||||||
|
for i := range modules {
|
||||||
|
module := &modules[i]
|
||||||
|
normalizeNginxModule(module)
|
||||||
|
if !module.Enable || module.BuildMode != nginxModuleBuildStatic {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
params, err := nginxModuleBuildParams(*module)
|
||||||
|
if err != nil {
|
||||||
|
return modules, err
|
||||||
|
}
|
||||||
|
buildHash, err := nginxModuleBuildHash(*module, target, params)
|
||||||
|
if err != nil {
|
||||||
|
return modules, err
|
||||||
|
}
|
||||||
|
upsertNginxModuleBuild(module, dto.NginxModuleBuild{
|
||||||
|
Provider: nginxModuleProviderLocal,
|
||||||
|
BuildMode: nginxModuleBuildStatic,
|
||||||
|
Status: nginxModuleStatusReady,
|
||||||
|
Hash: buildHash,
|
||||||
|
Target: target,
|
||||||
|
BuiltAt: time.Now(),
|
||||||
|
})
|
||||||
|
module.LastError = ""
|
||||||
|
}
|
||||||
|
return modules, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func commitStaticNginxModuleBuilds(install model.AppInstall, catalogPath string, parentTask *task.Task) error {
|
||||||
|
modules, err := loadNginxModulesWithCatalog(install, catalogPath)
|
||||||
|
if err != nil || !hasEnabledStaticNginxModules(modules) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
status, err := checkContainerStatus(install.ContainerName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if status != "running" {
|
||||||
|
return fmt.Errorf("OpenResty container %s is not running after static module build", install.ContainerName)
|
||||||
|
}
|
||||||
|
if err = opNginx(install.ContainerName, constant.NginxCheck); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
target, targetWarning, err := resolveNginxRuntimeTarget(install)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if targetWarning != "" && parentTask != nil {
|
||||||
|
parentTask.Logf("WARNING: %s", targetWarning)
|
||||||
|
}
|
||||||
|
modules, err = recordStaticNginxModuleBuilds(modules, target)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return saveNginxModulesWithCatalog(install, modules, catalogPath)
|
||||||
|
}
|
||||||
|
|
||||||
func upsertNginxModuleBuild(module *dto.NginxModule, build dto.NginxModuleBuild) {
|
func upsertNginxModuleBuild(module *dto.NginxModule, build dto.NginxModuleBuild) {
|
||||||
for i := range module.Builds {
|
for i := range module.Builds {
|
||||||
if module.Builds[i].Target.Key == build.Target.Key && module.Builds[i].Hash == build.Hash {
|
if module.Builds[i].Target.Key == build.Target.Key && module.Builds[i].Hash == build.Hash {
|
||||||
@@ -946,16 +1222,13 @@ func cloneNginxModules(modules []dto.NginxModule) []dto.NginxModule {
|
|||||||
return cloned
|
return cloned
|
||||||
}
|
}
|
||||||
|
|
||||||
func recordNginxModuleBuildFailure(originalModules []dto.NginxModule, moduleName string, build dto.NginxModuleBuild, previousBuild *dto.NginxModuleBuild, dynamicCompileSucceeded bool) []dto.NginxModule {
|
func recordNginxModuleBuildFailure(originalModules []dto.NginxModule, moduleName string, build dto.NginxModuleBuild, previousBuild *dto.NginxModuleBuild) []dto.NginxModule {
|
||||||
failedModules := cloneNginxModules(originalModules)
|
failedModules := cloneNginxModules(originalModules)
|
||||||
for i := range failedModules {
|
for i := range failedModules {
|
||||||
if failedModules[i].Name != moduleName {
|
if failedModules[i].Name != moduleName {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
failedModules[i].LastError = build.Error
|
failedModules[i].LastError = build.Error
|
||||||
if dynamicCompileSucceeded {
|
|
||||||
failedModules[i].DynamicSupport = nginxModuleSupportSupported
|
|
||||||
}
|
|
||||||
if previousBuild == nil || previousBuild.Status != nginxModuleStatusReady {
|
if previousBuild == nil || previousBuild.Status != nginxModuleStatusReady {
|
||||||
upsertNginxModuleBuild(&failedModules[i], build)
|
upsertNginxModuleBuild(&failedModules[i], build)
|
||||||
}
|
}
|
||||||
@@ -972,11 +1245,10 @@ func recordNginxModuleActivationFailure(previousModules, candidateModules []dto.
|
|||||||
}
|
}
|
||||||
for i := range rollbackModules {
|
for i := range rollbackModules {
|
||||||
candidate, ok := candidates[rollbackModules[i].Name]
|
candidate, ok := candidates[rollbackModules[i].Name]
|
||||||
if !ok || candidate.Deleted || !candidate.Enable || candidate.BuildMode == nginxModuleBuildStatic {
|
if !ok || !candidate.Enable || candidate.BuildMode == nginxModuleBuildStatic {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
rollbackModules[i].LastError = activationErr.Error()
|
rollbackModules[i].LastError = activationErr.Error()
|
||||||
rollbackModules[i].DynamicSupport = candidate.DynamicSupport
|
|
||||||
}
|
}
|
||||||
return rollbackModules
|
return rollbackModules
|
||||||
}
|
}
|
||||||
@@ -1035,9 +1307,10 @@ func nginxModuleBuildHash(module dto.NginxModule, target dto.NginxModuleTarget,
|
|||||||
Script string
|
Script string
|
||||||
Packages []string
|
Packages []string
|
||||||
Params string
|
Params string
|
||||||
|
BuildMode string
|
||||||
TargetKey string
|
TargetKey string
|
||||||
Provider string
|
Provider string
|
||||||
}{module.Name, module.Script, module.Packages, params, target.Key, module.Provider}
|
}{module.Name, module.Script, module.Packages, params, module.BuildMode, target.Key, module.Provider}
|
||||||
content, err := json.Marshal(payload)
|
content, err := json.Marshal(payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
|||||||
@@ -10,12 +10,13 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/app/dto/request"
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||||
"github.com/1Panel-dev/1Panel/agent/global"
|
"github.com/1Panel-dev/1Panel/agent/global"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNormalizeNginxModulePreservesLegacyStaticMode(t *testing.T) {
|
func TestNormalizeNginxModuleDoesNotInferBuildMode(t *testing.T) {
|
||||||
module := dto.NginxModule{
|
module := dto.NginxModule{
|
||||||
Name: "legacy",
|
Name: "legacy",
|
||||||
Packages: []string{"git", "", "git", " curl "},
|
Packages: []string{"git", "", "git", " curl "},
|
||||||
@@ -23,8 +24,8 @@ func TestNormalizeNginxModulePreservesLegacyStaticMode(t *testing.T) {
|
|||||||
|
|
||||||
normalizeNginxModule(&module)
|
normalizeNginxModule(&module)
|
||||||
|
|
||||||
if module.BuildMode != nginxModuleBuildStatic {
|
if module.BuildMode != "" {
|
||||||
t.Fatalf("expected legacy module to remain static, got %s", module.BuildMode)
|
t.Fatalf("build mode must be explicit, got %s", module.BuildMode)
|
||||||
}
|
}
|
||||||
if module.Provider != nginxModuleProviderLocal {
|
if module.Provider != nginxModuleProviderLocal {
|
||||||
t.Fatalf("expected local provider, got %s", module.Provider)
|
t.Fatalf("expected local provider, got %s", module.Provider)
|
||||||
@@ -105,19 +106,19 @@ func TestRecordNginxModuleBuildFailureKeepsPreviousReadyBuild(t *testing.T) {
|
|||||||
Hash: "ready", Status: nginxModuleStatusReady, Target: target, BuiltAt: time.Now().Add(-time.Hour),
|
Hash: "ready", Status: nginxModuleStatusReady, Target: target, BuiltAt: time.Now().Add(-time.Hour),
|
||||||
}
|
}
|
||||||
original := []dto.NginxModule{{
|
original := []dto.NginxModule{{
|
||||||
Name: "example", BuildMode: nginxModuleBuildDynamic, DynamicSupport: nginxModuleSupportUnknown,
|
Name: "example", BuildMode: nginxModuleBuildDynamic,
|
||||||
Builds: []dto.NginxModuleBuild{ready},
|
Builds: []dto.NginxModuleBuild{ready},
|
||||||
}}
|
}}
|
||||||
failed := dto.NginxModuleBuild{
|
failed := dto.NginxModuleBuild{
|
||||||
Hash: "candidate", Status: nginxModuleStatusFailed, Target: target, Error: "load failed", BuiltAt: time.Now(),
|
Hash: "candidate", Status: nginxModuleStatusFailed, Target: target, Error: "load failed", BuiltAt: time.Now(),
|
||||||
}
|
}
|
||||||
|
|
||||||
result := recordNginxModuleBuildFailure(original, "example", failed, &ready, true)
|
result := recordNginxModuleBuildFailure(original, "example", failed, &ready)
|
||||||
|
|
||||||
if len(result[0].Builds) != 1 || result[0].Builds[0].Hash != "ready" {
|
if len(result[0].Builds) != 1 || result[0].Builds[0].Hash != "ready" {
|
||||||
t.Fatalf("previous ready build was replaced: %#v", result[0].Builds)
|
t.Fatalf("previous ready build was replaced: %#v", result[0].Builds)
|
||||||
}
|
}
|
||||||
if result[0].LastError != failed.Error || result[0].DynamicSupport != nginxModuleSupportSupported {
|
if result[0].LastError != failed.Error {
|
||||||
t.Fatalf("failure metadata was not retained: %#v", result[0])
|
t.Fatalf("failure metadata was not retained: %#v", result[0])
|
||||||
}
|
}
|
||||||
result[0].Builds[0].Hash = "mutated"
|
result[0].Builds[0].Hash = "mutated"
|
||||||
@@ -129,7 +130,6 @@ func TestRecordNginxModuleBuildFailureKeepsPreviousReadyBuild(t *testing.T) {
|
|||||||
func TestHasDynamicNginxModuleBuildTask(t *testing.T) {
|
func TestHasDynamicNginxModuleBuildTask(t *testing.T) {
|
||||||
dynamicEnabled := dto.NginxModule{Name: "brotli", Enable: true, BuildMode: nginxModuleBuildDynamic}
|
dynamicEnabled := dto.NginxModule{Name: "brotli", Enable: true, BuildMode: nginxModuleBuildDynamic}
|
||||||
staticEnabled := dto.NginxModule{Name: "pagespeed", Enable: true, BuildMode: nginxModuleBuildStatic}
|
staticEnabled := dto.NginxModule{Name: "pagespeed", Enable: true, BuildMode: nginxModuleBuildStatic}
|
||||||
deletedDynamic := dto.NginxModule{Name: "geoip", Enable: true, BuildMode: nginxModuleBuildDynamic, Deleted: true}
|
|
||||||
disabledDynamic := dto.NginxModule{Name: "waf", Enable: false, BuildMode: nginxModuleBuildDynamic}
|
disabledDynamic := dto.NginxModule{Name: "waf", Enable: false, BuildMode: nginxModuleBuildDynamic}
|
||||||
|
|
||||||
if hasDynamicNginxModuleBuildTask(nil, nil) {
|
if hasDynamicNginxModuleBuildTask(nil, nil) {
|
||||||
@@ -138,9 +138,6 @@ func TestHasDynamicNginxModuleBuildTask(t *testing.T) {
|
|||||||
if hasDynamicNginxModuleBuildTask([]dto.NginxModule{staticEnabled}, nil) {
|
if hasDynamicNginxModuleBuildTask([]dto.NginxModule{staticEnabled}, nil) {
|
||||||
t.Fatal("static-only modules should not require a dynamic build")
|
t.Fatal("static-only modules should not require a dynamic build")
|
||||||
}
|
}
|
||||||
if hasDynamicNginxModuleBuildTask([]dto.NginxModule{deletedDynamic}, nil) {
|
|
||||||
t.Fatal("deleted modules should not require a dynamic build")
|
|
||||||
}
|
|
||||||
if hasDynamicNginxModuleBuildTask([]dto.NginxModule{dynamicEnabled}, []string{"other"}) {
|
if hasDynamicNginxModuleBuildTask([]dto.NginxModule{dynamicEnabled}, []string{"other"}) {
|
||||||
t.Fatal("enabled module outside the selection should not require a dynamic build")
|
t.Fatal("enabled module outside the selection should not require a dynamic build")
|
||||||
}
|
}
|
||||||
@@ -160,6 +157,15 @@ func TestHasDynamicNginxModuleBuildTask(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNginxModuleStaticBuildErrorHintOnlyForCustomModules(t *testing.T) {
|
||||||
|
if hint := nginxModuleStaticBuildErrorHint(dto.NginxModule{Name: "builtin"}); hint != "" {
|
||||||
|
t.Fatalf("built-in module cannot switch build mode, got hint %q", hint)
|
||||||
|
}
|
||||||
|
if hint := nginxModuleStaticBuildErrorHint(dto.NginxModule{Name: "custom", Custom: true}); hint == "" {
|
||||||
|
t.Fatal("custom module should receive the static-build alternative")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestResolveNginxModuleTargetWithoutBuilder(t *testing.T) {
|
func TestResolveNginxModuleTargetWithoutBuilder(t *testing.T) {
|
||||||
oldDir := global.Dir.AppInstallDir
|
oldDir := global.Dir.AppInstallDir
|
||||||
global.Dir.AppInstallDir = t.TempDir()
|
global.Dir.AppInstallDir = t.TempDir()
|
||||||
@@ -176,6 +182,22 @@ func TestResolveNginxModuleTargetWithoutBuilder(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBuildDynamicNginxModulesFailsWithoutBuilder(t *testing.T) {
|
||||||
|
oldDir := global.Dir.AppInstallDir
|
||||||
|
global.Dir.AppInstallDir = t.TempDir()
|
||||||
|
t.Cleanup(func() { global.Dir.AppInstallDir = oldDir })
|
||||||
|
install := model.AppInstall{Name: "openresty", Version: "1.31.1.1"}
|
||||||
|
install.App.Key = constant.AppOpenresty
|
||||||
|
modules := []dto.NginxModule{{
|
||||||
|
Name: "rtmp", Enable: true, BuildMode: nginxModuleBuildDynamic,
|
||||||
|
}}
|
||||||
|
|
||||||
|
_, err := buildDynamicNginxModules(install, modules, nil, false, "", "", nil)
|
||||||
|
if !errors.Is(err, errNginxModuleBuilderMissing) {
|
||||||
|
t.Fatalf("missing target builder must fail the dynamic build, got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestMergeOpenrestyModuleVolumes(t *testing.T) {
|
func TestMergeOpenrestyModuleVolumes(t *testing.T) {
|
||||||
newService := map[string]interface{}{}
|
newService := map[string]interface{}{}
|
||||||
oldService := map[string]interface{}{
|
oldService := map[string]interface{}{
|
||||||
@@ -226,97 +248,6 @@ func TestMergeOpenrestyModuleVolumesKeepsExisting(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNormalizeNginxModuleFoldsAutoIntoDynamic(t *testing.T) {
|
|
||||||
module := dto.NginxModule{Name: "legacy-auto", BuildMode: nginxModuleBuildAuto}
|
|
||||||
|
|
||||||
normalizeNginxModule(&module)
|
|
||||||
|
|
||||||
if module.BuildMode != nginxModuleBuildDynamic {
|
|
||||||
t.Fatalf("auto should normalize to dynamic, got %s", module.BuildMode)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeNginxModuleFixture(t *testing.T, install model.AppInstall, withBuilder bool, modules []dto.NginxModule) {
|
|
||||||
t.Helper()
|
|
||||||
buildDir := path.Join(install.GetPath(), nginxModuleBuildDir)
|
|
||||||
if err := os.MkdirAll(buildDir, constant.DirPerm); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if withBuilder {
|
|
||||||
if err := os.WriteFile(path.Join(buildDir, nginxModuleBuilderFile), []byte("FROM scratch\n"), constant.FilePerm); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if err := os.WriteFile(path.Join(buildDir, nginxModuleCatalogFile), []byte("[]"), constant.FilePerm); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
content, err := json.Marshal(modules)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if err = os.WriteFile(path.Join(buildDir, nginxModuleStoreFile), content, constant.FilePerm); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLoadNginxModulesProbesDynamicSupport(t *testing.T) {
|
|
||||||
oldDir := global.Dir.AppInstallDir
|
|
||||||
global.Dir.AppInstallDir = t.TempDir()
|
|
||||||
t.Cleanup(func() { global.Dir.AppInstallDir = oldDir })
|
|
||||||
install := model.AppInstall{Name: "openresty", Version: "1.27.1.2"}
|
|
||||||
install.App.Key = constant.AppOpenresty
|
|
||||||
writeNginxModuleFixture(t, install, true, []dto.NginxModule{
|
|
||||||
{Name: "good", Enable: true, BuildMode: nginxModuleBuildDynamic, Params: "--add-module=/tmp/good"},
|
|
||||||
{Name: "bad", Enable: true, BuildMode: nginxModuleBuildDynamic, Params: "--with-nothing"},
|
|
||||||
{Name: "meta", Enable: true, BuildMode: nginxModuleBuildDynamic, Params: "--add-module=/tmp/x;touch /tmp/y"},
|
|
||||||
{Name: "static-mod", Enable: true, BuildMode: nginxModuleBuildStatic},
|
|
||||||
{Name: "deleted", Deleted: true, BuildMode: nginxModuleBuildDynamic, Params: "--with-nothing"},
|
|
||||||
})
|
|
||||||
|
|
||||||
loaded, err := loadNginxModules(install)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
support := make(map[string]string, len(loaded))
|
|
||||||
for _, module := range loaded {
|
|
||||||
support[module.Name] = module.DynamicSupport
|
|
||||||
}
|
|
||||||
if support["good"] != nginxModuleSupportSupported {
|
|
||||||
t.Fatalf("valid dynamic params should probe supported, got %q", support["good"])
|
|
||||||
}
|
|
||||||
if support["bad"] != nginxModuleSupportUnsupported {
|
|
||||||
t.Fatalf("params without a dynamic option should probe unsupported, got %q", support["bad"])
|
|
||||||
}
|
|
||||||
if support["meta"] != nginxModuleSupportUnsupported {
|
|
||||||
t.Fatalf("params with shell metacharacters should probe unsupported, got %q", support["meta"])
|
|
||||||
}
|
|
||||||
if support["static-mod"] != nginxModuleSupportUnknown {
|
|
||||||
t.Fatalf("static module must not be probed, got %q", support["static-mod"])
|
|
||||||
}
|
|
||||||
if support["deleted"] != nginxModuleSupportUnknown {
|
|
||||||
t.Fatalf("deleted module must not be probed, got %q", support["deleted"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLoadNginxModulesWithoutBuilderKeepsUnknownSupport(t *testing.T) {
|
|
||||||
oldDir := global.Dir.AppInstallDir
|
|
||||||
global.Dir.AppInstallDir = t.TempDir()
|
|
||||||
t.Cleanup(func() { global.Dir.AppInstallDir = oldDir })
|
|
||||||
install := model.AppInstall{Name: "openresty", Version: "1.27.1.2"}
|
|
||||||
install.App.Key = constant.AppOpenresty
|
|
||||||
writeNginxModuleFixture(t, install, false, []dto.NginxModule{
|
|
||||||
{Name: "good", Enable: true, BuildMode: nginxModuleBuildDynamic, Params: "--add-module=/tmp/good"},
|
|
||||||
})
|
|
||||||
|
|
||||||
loaded, err := loadNginxModules(install)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if len(loaded) != 1 || loaded[0].DynamicSupport != nginxModuleSupportUnknown {
|
|
||||||
t.Fatalf("without the builder the support marker must stay unknown, got %#v", loaded)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestResolveNginxModuleBuildMirror(t *testing.T) {
|
func TestResolveNginxModuleBuildMirror(t *testing.T) {
|
||||||
oldDir := global.Dir.AppInstallDir
|
oldDir := global.Dir.AppInstallDir
|
||||||
global.Dir.AppInstallDir = t.TempDir()
|
global.Dir.AppInstallDir = t.TempDir()
|
||||||
@@ -345,3 +276,401 @@ func TestResolveNginxModuleBuildMirror(t *testing.T) {
|
|||||||
t.Fatalf("request mirror should still win over the env value, got %q", got)
|
t.Fatalf("request mirror should still win over the env value, got %q", got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func writeNginxModuleCatalogStateFixture(t *testing.T, install model.AppInstall, catalog []dto.NginxModule, state string) {
|
||||||
|
t.Helper()
|
||||||
|
buildDir := path.Join(install.GetPath(), nginxModuleBuildDir)
|
||||||
|
if err := os.MkdirAll(buildDir, constant.DirPerm); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
catalogContent, err := json.Marshal(catalog)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err = os.WriteFile(path.Join(buildDir, nginxModuleCatalogFile), catalogContent, constant.FilePerm); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if state != "" {
|
||||||
|
if err = os.WriteFile(path.Join(buildDir, nginxModuleStoreFile), []byte(state), constant.FilePerm); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadNginxModulesBuiltinStateCannotOverrideCatalogDefinition(t *testing.T) {
|
||||||
|
oldDir := global.Dir.AppInstallDir
|
||||||
|
global.Dir.AppInstallDir = t.TempDir()
|
||||||
|
t.Cleanup(func() { global.Dir.AppInstallDir = oldDir })
|
||||||
|
install := model.AppInstall{Name: "openresty", Version: "1.31.1.1"}
|
||||||
|
install.App.Key = constant.AppOpenresty
|
||||||
|
|
||||||
|
catalog := []dto.NginxModule{{
|
||||||
|
Name: "rtmp", Script: "catalog-script", Packages: []string{"unzip"}, Params: "--add-module=/tmp/rtmp",
|
||||||
|
BuildMode: nginxModuleBuildDynamic, Provider: nginxModuleProviderLocal, LoadOrder: 20,
|
||||||
|
}}
|
||||||
|
state := `[{"name":"rtmp","enable":true,"script":"user-script","params":"--with-user","buildMode":"static","loadOrder":99,"lastError":"failed"}]`
|
||||||
|
writeNginxModuleCatalogStateFixture(t, install, catalog, state)
|
||||||
|
|
||||||
|
modules, err := loadNginxModules(install)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(modules) != 1 {
|
||||||
|
t.Fatalf("expected one merged module, got %#v", modules)
|
||||||
|
}
|
||||||
|
module := modules[0]
|
||||||
|
if !module.Enable || module.LastError != "failed" {
|
||||||
|
t.Fatalf("builtin state was not applied: %#v", module)
|
||||||
|
}
|
||||||
|
if module.Script != "catalog-script" || module.Params != "--add-module=/tmp/rtmp" ||
|
||||||
|
module.BuildMode != nginxModuleBuildDynamic || module.LoadOrder != 20 {
|
||||||
|
t.Fatalf("builtin definition must come from catalog: %#v", module)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadNginxModulesWithoutStateShowsDisabledCatalogModules(t *testing.T) {
|
||||||
|
oldDir := global.Dir.AppInstallDir
|
||||||
|
global.Dir.AppInstallDir = t.TempDir()
|
||||||
|
t.Cleanup(func() { global.Dir.AppInstallDir = oldDir })
|
||||||
|
install := model.AppInstall{Name: "openresty", Version: "1.31.1.1"}
|
||||||
|
install.App.Key = constant.AppOpenresty
|
||||||
|
|
||||||
|
catalog := []dto.NginxModule{
|
||||||
|
{Name: "rtmp", Enable: true, BuildMode: nginxModuleBuildDynamic},
|
||||||
|
{Name: "geoip2", BuildMode: nginxModuleBuildDynamic},
|
||||||
|
}
|
||||||
|
writeNginxModuleCatalogStateFixture(t, install, catalog, "")
|
||||||
|
|
||||||
|
modules, err := loadNginxModules(install)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(modules) != len(catalog) {
|
||||||
|
t.Fatalf("expected all catalog modules, got %#v", modules)
|
||||||
|
}
|
||||||
|
for _, module := range modules {
|
||||||
|
if module.Enable || module.Custom {
|
||||||
|
t.Fatalf("catalog modules must default to disabled built-ins: %#v", module)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSaveNginxModulesPersistsOnlyBuiltinState(t *testing.T) {
|
||||||
|
oldDir := global.Dir.AppInstallDir
|
||||||
|
global.Dir.AppInstallDir = t.TempDir()
|
||||||
|
t.Cleanup(func() { global.Dir.AppInstallDir = oldDir })
|
||||||
|
install := model.AppInstall{Name: "openresty", Version: "1.31.1.1"}
|
||||||
|
install.App.Key = constant.AppOpenresty
|
||||||
|
|
||||||
|
catalog := []dto.NginxModule{{
|
||||||
|
Name: "rtmp", Script: "catalog-script", Params: "--add-module=/tmp/rtmp",
|
||||||
|
BuildMode: nginxModuleBuildDynamic, Provider: nginxModuleProviderLocal, LoadOrder: 20,
|
||||||
|
}}
|
||||||
|
writeNginxModuleCatalogStateFixture(t, install, catalog, "")
|
||||||
|
modules, err := loadNginxModules(install)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
modules[0].Enable = true
|
||||||
|
modules[0].LastError = "failed"
|
||||||
|
|
||||||
|
if err = saveNginxModules(install, modules); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
content, err := os.ReadFile(path.Join(install.GetPath(), nginxModuleBuildDir, nginxModuleStoreFile))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
var states []map[string]any
|
||||||
|
if err = json.Unmarshal(content, &states); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(states) != 1 || states[0]["name"] != "rtmp" || states[0]["enable"] != true || states[0]["lastError"] != "failed" {
|
||||||
|
t.Fatalf("unexpected builtin state: %s", content)
|
||||||
|
}
|
||||||
|
for _, key := range []string{"script", "packages", "params", "buildMode", "provider", "loadOrder"} {
|
||||||
|
if _, exists := states[0][key]; exists {
|
||||||
|
t.Fatalf("builtin definition field %q leaked into module state: %s", key, content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSaveNginxModulesOmitsPristineBuiltinState(t *testing.T) {
|
||||||
|
oldDir := global.Dir.AppInstallDir
|
||||||
|
global.Dir.AppInstallDir = t.TempDir()
|
||||||
|
t.Cleanup(func() { global.Dir.AppInstallDir = oldDir })
|
||||||
|
install := model.AppInstall{Name: "openresty", Version: "1.31.1.1"}
|
||||||
|
install.App.Key = constant.AppOpenresty
|
||||||
|
catalog := []dto.NginxModule{{Name: "rtmp", BuildMode: nginxModuleBuildDynamic}}
|
||||||
|
writeNginxModuleCatalogStateFixture(t, install, catalog, "")
|
||||||
|
|
||||||
|
modules, err := loadNginxModules(install)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err = saveNginxModules(install, modules); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
content, err := os.ReadFile(path.Join(install.GetPath(), nginxModuleBuildDir, nginxModuleStoreFile))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(string(content)) != "[]" {
|
||||||
|
t.Fatalf("pristine built-in state should not be persisted: %s", content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadAndSaveNginxCustomModule(t *testing.T) {
|
||||||
|
oldDir := global.Dir.AppInstallDir
|
||||||
|
global.Dir.AppInstallDir = t.TempDir()
|
||||||
|
t.Cleanup(func() { global.Dir.AppInstallDir = oldDir })
|
||||||
|
install := model.AppInstall{Name: "openresty", Version: "1.31.1.1"}
|
||||||
|
install.App.Key = constant.AppOpenresty
|
||||||
|
|
||||||
|
state := `[{"name":"custom","custom":true,"script":"prepare","packages":["git"],"params":"--add-module=/tmp/custom","enable":true,"buildMode":"static","provider":"local","loadOrder":100}]`
|
||||||
|
writeNginxModuleCatalogStateFixture(t, install, nil, state)
|
||||||
|
|
||||||
|
modules, err := loadNginxModules(install)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(modules) != 1 || !modules[0].Custom {
|
||||||
|
t.Fatalf("custom module source was not restored: %#v", modules)
|
||||||
|
}
|
||||||
|
if modules[0].Script != "prepare" || modules[0].BuildMode != nginxModuleBuildStatic || modules[0].LoadOrder != 100 {
|
||||||
|
t.Fatalf("custom module definition was not restored: %#v", modules[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = saveNginxModules(install, modules); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
content, err := os.ReadFile(path.Join(install.GetPath(), nginxModuleBuildDir, nginxModuleStoreFile))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !strings.Contains(string(content), `"custom": true`) || !strings.Contains(string(content), `"buildMode": "static"`) {
|
||||||
|
t.Fatalf("custom module definition was not persisted: %s", content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestActivateNginxModuleCatalogReplacesCatalogAtomically(t *testing.T) {
|
||||||
|
buildDir := t.TempDir()
|
||||||
|
activePath := path.Join(buildDir, nginxModuleCatalogFile)
|
||||||
|
pendingPath := path.Join(buildDir, nginxModuleCatalogPendingFile)
|
||||||
|
sourcePath := path.Join(buildDir, "target.catalog.json")
|
||||||
|
if err := os.WriteFile(activePath, []byte(`[{"name":"old"}]`), constant.FilePerm); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(sourcePath, []byte(`[{"name":"new"}]`), constant.FilePerm); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := stageNginxModuleCatalog(sourcePath, pendingPath); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
content, err := os.ReadFile(activePath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if string(content) != `[{"name":"old"}]` {
|
||||||
|
t.Fatalf("staging target catalog changed the active catalog: %s", content)
|
||||||
|
}
|
||||||
|
if err := activateNginxModuleCatalog(pendingPath, activePath); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
content, err = os.ReadFile(activePath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if string(content) != `[{"name":"new"}]` {
|
||||||
|
t.Fatalf("active catalog was not replaced: %s", content)
|
||||||
|
}
|
||||||
|
if _, err = os.Stat(pendingPath); !os.IsNotExist(err) {
|
||||||
|
t.Fatalf("pending catalog should be consumed, got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestActivateNginxModuleCatalogRestoresCatalogWhenCommitFails(t *testing.T) {
|
||||||
|
buildDir := t.TempDir()
|
||||||
|
activePath := path.Join(buildDir, nginxModuleCatalogFile)
|
||||||
|
pendingPath := path.Join(buildDir, nginxModuleCatalogPendingFile)
|
||||||
|
if err := os.WriteFile(activePath, []byte(`[{"name":"old"}]`), constant.FilePerm); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(pendingPath, []byte(`[{"name":"new"}]`), constant.FilePerm); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
commitErr := errors.New("save install failed")
|
||||||
|
|
||||||
|
err := activateNginxModuleCatalogAndCommit(pendingPath, activePath, func() error {
|
||||||
|
return commitErr
|
||||||
|
})
|
||||||
|
if !errors.Is(err, commitErr) {
|
||||||
|
t.Fatalf("expected commit error, got %v", err)
|
||||||
|
}
|
||||||
|
content, readErr := os.ReadFile(activePath)
|
||||||
|
if readErr != nil {
|
||||||
|
t.Fatal(readErr)
|
||||||
|
}
|
||||||
|
if string(content) != `[{"name":"old"}]` {
|
||||||
|
t.Fatalf("failed upgrade must restore the old catalog: %s", content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadNginxModulesRejectsDuplicateCatalogNames(t *testing.T) {
|
||||||
|
oldDir := global.Dir.AppInstallDir
|
||||||
|
global.Dir.AppInstallDir = t.TempDir()
|
||||||
|
t.Cleanup(func() { global.Dir.AppInstallDir = oldDir })
|
||||||
|
install := model.AppInstall{Name: "openresty", Version: "1.31.1.1"}
|
||||||
|
install.App.Key = constant.AppOpenresty
|
||||||
|
|
||||||
|
catalog := []dto.NginxModule{
|
||||||
|
{Name: "rtmp", Params: "--add-module=/tmp/one", BuildMode: nginxModuleBuildDynamic},
|
||||||
|
{Name: "rtmp", Params: "--add-module=/tmp/two", BuildMode: nginxModuleBuildDynamic},
|
||||||
|
}
|
||||||
|
writeNginxModuleCatalogStateFixture(t, install, catalog, "")
|
||||||
|
|
||||||
|
if _, err := loadNginxModules(install); err == nil || !strings.Contains(err.Error(), "duplicate") {
|
||||||
|
t.Fatalf("expected duplicate catalog name error, got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadNginxModulesRejectsDuplicateStateNames(t *testing.T) {
|
||||||
|
oldDir := global.Dir.AppInstallDir
|
||||||
|
global.Dir.AppInstallDir = t.TempDir()
|
||||||
|
t.Cleanup(func() { global.Dir.AppInstallDir = oldDir })
|
||||||
|
install := model.AppInstall{Name: "openresty", Version: "1.31.1.1"}
|
||||||
|
install.App.Key = constant.AppOpenresty
|
||||||
|
|
||||||
|
catalog := []dto.NginxModule{{Name: "rtmp", Params: "--add-module=/tmp/rtmp", BuildMode: nginxModuleBuildDynamic}}
|
||||||
|
writeNginxModuleCatalogStateFixture(t, install, catalog, `[{"name":"rtmp","enable":true},{"name":"rtmp","enable":false}]`)
|
||||||
|
|
||||||
|
if _, err := loadNginxModules(install); err == nil || !strings.Contains(err.Error(), "duplicate") {
|
||||||
|
t.Fatalf("expected duplicate state name error, got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadNginxModulesRejectsOrphanBuiltinState(t *testing.T) {
|
||||||
|
oldDir := global.Dir.AppInstallDir
|
||||||
|
global.Dir.AppInstallDir = t.TempDir()
|
||||||
|
t.Cleanup(func() { global.Dir.AppInstallDir = oldDir })
|
||||||
|
install := model.AppInstall{Name: "openresty", Version: "1.31.1.1"}
|
||||||
|
install.App.Key = constant.AppOpenresty
|
||||||
|
|
||||||
|
writeNginxModuleCatalogStateFixture(t, install, nil, `[{"name":"removed","enable":true}]`)
|
||||||
|
|
||||||
|
if _, err := loadNginxModules(install); err == nil || !strings.Contains(err.Error(), "missing from the module catalog") {
|
||||||
|
t.Fatalf("expected orphan builtin state error, got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadNginxModulesRejectsCustomCatalogNameConflict(t *testing.T) {
|
||||||
|
oldDir := global.Dir.AppInstallDir
|
||||||
|
global.Dir.AppInstallDir = t.TempDir()
|
||||||
|
t.Cleanup(func() { global.Dir.AppInstallDir = oldDir })
|
||||||
|
install := model.AppInstall{Name: "openresty", Version: "1.31.1.1"}
|
||||||
|
install.App.Key = constant.AppOpenresty
|
||||||
|
|
||||||
|
catalog := []dto.NginxModule{{Name: "rtmp", BuildMode: nginxModuleBuildDynamic}}
|
||||||
|
writeNginxModuleCatalogStateFixture(t, install, catalog, `[{"name":"rtmp","custom":true,"enable":true}]`)
|
||||||
|
|
||||||
|
if _, err := loadNginxModules(install); err == nil || !strings.Contains(err.Error(), "conflicts") {
|
||||||
|
t.Fatalf("expected custom/catalog conflict error, got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadNginxModulesRejectsInvalidBuildModes(t *testing.T) {
|
||||||
|
oldDir := global.Dir.AppInstallDir
|
||||||
|
global.Dir.AppInstallDir = t.TempDir()
|
||||||
|
t.Cleanup(func() { global.Dir.AppInstallDir = oldDir })
|
||||||
|
install := model.AppInstall{Name: "openresty", Version: "1.31.1.1"}
|
||||||
|
install.App.Key = constant.AppOpenresty
|
||||||
|
|
||||||
|
writeNginxModuleCatalogStateFixture(t, install, []dto.NginxModule{{
|
||||||
|
Name: "rtmp", BuildMode: "auto",
|
||||||
|
}}, "")
|
||||||
|
if _, err := loadNginxModules(install); err == nil || !strings.Contains(err.Error(), "invalid build mode") {
|
||||||
|
t.Fatalf("expected invalid catalog build mode error, got %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
writeNginxModuleCatalogStateFixture(t, install, nil, `[{"name":"custom","custom":true,"buildMode":"auto"}]`)
|
||||||
|
if _, err := loadNginxModules(install); err == nil || !strings.Contains(err.Error(), "invalid build mode") {
|
||||||
|
t.Fatalf("expected invalid custom build mode error, got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApplyNginxModuleUpdateKeepsBuiltinDefinitionImmutable(t *testing.T) {
|
||||||
|
modules := []dto.NginxModule{{
|
||||||
|
Name: "rtmp", Script: "catalog-script", Params: "--add-module=/tmp/rtmp",
|
||||||
|
BuildMode: nginxModuleBuildDynamic, Provider: nginxModuleProviderLocal, LoadOrder: 20,
|
||||||
|
}}
|
||||||
|
req := request.NginxModuleUpdate{
|
||||||
|
Operate: nginxModuleOperateUpdate, Name: "rtmp", Enable: true, Script: "user-script",
|
||||||
|
Params: "--with-user", BuildMode: nginxModuleBuildStatic, Provider: "prebuilt", LoadOrder: 99,
|
||||||
|
}
|
||||||
|
|
||||||
|
updated, _, err := applyNginxModuleUpdate(modules, req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !updated[0].Enable {
|
||||||
|
t.Fatal("builtin enable state was not updated")
|
||||||
|
}
|
||||||
|
if updated[0].Script != "catalog-script" || updated[0].Params != "--add-module=/tmp/rtmp" ||
|
||||||
|
updated[0].BuildMode != nginxModuleBuildDynamic || updated[0].Provider != nginxModuleProviderLocal ||
|
||||||
|
updated[0].LoadOrder != 20 {
|
||||||
|
t.Fatalf("builtin definition was modified: %#v", updated[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApplyNginxModuleUpdateRejectsBuiltinDelete(t *testing.T) {
|
||||||
|
modules := []dto.NginxModule{{Name: "rtmp", BuildMode: nginxModuleBuildDynamic}}
|
||||||
|
|
||||||
|
_, _, err := applyNginxModuleUpdate(modules, request.NginxModuleUpdate{
|
||||||
|
Operate: nginxModuleOperateDelete, Name: "rtmp",
|
||||||
|
})
|
||||||
|
if err == nil || !strings.Contains(err.Error(), "cannot be deleted") {
|
||||||
|
t.Fatalf("expected builtin delete error, got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApplyNginxModuleUpdateCreatesAndDeletesCustomModule(t *testing.T) {
|
||||||
|
if _, _, err := applyNginxModuleUpdate(nil, request.NginxModuleUpdate{
|
||||||
|
Operate: nginxModuleOperateCreate, Name: "invalid", BuildMode: "auto",
|
||||||
|
}); err == nil || !strings.Contains(err.Error(), "invalid build mode") {
|
||||||
|
t.Fatalf("expected invalid custom build mode error, got %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
created, _, err := applyNginxModuleUpdate(nil, request.NginxModuleUpdate{
|
||||||
|
Operate: nginxModuleOperateCreate, Name: "custom", Script: "prepare", Packages: "git,curl",
|
||||||
|
Params: "--add-module=/tmp/custom", Enable: true, BuildMode: nginxModuleBuildStatic,
|
||||||
|
Provider: nginxModuleProviderLocal, LoadOrder: 100,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(created) != 1 || !created[0].Custom || created[0].BuildMode != nginxModuleBuildStatic {
|
||||||
|
t.Fatalf("custom module was not created correctly: %#v", created)
|
||||||
|
}
|
||||||
|
updated, _, err := applyNginxModuleUpdate(created, request.NginxModuleUpdate{
|
||||||
|
Operate: nginxModuleOperateUpdate, Name: "custom", Script: "updated",
|
||||||
|
Params: "--add-module=/tmp/custom-v2", Enable: false, BuildMode: nginxModuleBuildDynamic,
|
||||||
|
Provider: nginxModuleProviderLocal, LoadOrder: 75,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if updated[0].Script != "updated" || updated[0].BuildMode != nginxModuleBuildDynamic || updated[0].LoadOrder != 75 {
|
||||||
|
t.Fatalf("custom module was not updated correctly: %#v", updated[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
remaining, deleted, err := applyNginxModuleUpdate(updated, request.NginxModuleUpdate{
|
||||||
|
Operate: nginxModuleOperateDelete, Name: "custom",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(remaining) != 0 || deleted == nil || deleted.Name != "custom" {
|
||||||
|
t.Fatalf("custom module was not removed: remaining=%#v deleted=%#v", remaining, deleted)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -44,17 +44,16 @@ export namespace Nginx {
|
|||||||
|
|
||||||
export interface NginxModule {
|
export interface NginxModule {
|
||||||
name: string;
|
name: string;
|
||||||
|
custom: boolean;
|
||||||
script?: string;
|
script?: string;
|
||||||
packages?: string;
|
packages?: string;
|
||||||
enable: boolean;
|
enable: boolean;
|
||||||
params: string;
|
params: string;
|
||||||
buildMode: 'auto' | 'dynamic' | 'static';
|
buildMode: 'dynamic' | 'static';
|
||||||
provider: 'local' | 'prebuilt';
|
provider: 'local' | 'prebuilt';
|
||||||
dynamicSupport: 'unknown' | 'supported' | 'unsupported';
|
|
||||||
loadOrder: number;
|
loadOrder: number;
|
||||||
buildStatus: 'pending' | 'ready' | 'failed';
|
buildStatus: 'pending' | 'ready' | 'failed';
|
||||||
loadStatus: 'enabled' | 'disabled';
|
loadStatus: 'enabled' | 'disabled';
|
||||||
compatibility: 'unknown' | 'compatible' | 'stale' | 'static';
|
|
||||||
artifacts?: NginxModuleArtifact[];
|
artifacts?: NginxModuleArtifact[];
|
||||||
lastError?: string;
|
lastError?: string;
|
||||||
}
|
}
|
||||||
@@ -72,7 +71,7 @@ export namespace Nginx {
|
|||||||
packages?: string;
|
packages?: string;
|
||||||
enable?: boolean;
|
enable?: boolean;
|
||||||
params?: string;
|
params?: string;
|
||||||
buildMode?: 'auto' | 'dynamic' | 'static';
|
buildMode?: 'dynamic' | 'static';
|
||||||
provider?: 'local' | 'prebuilt';
|
provider?: 'local' | 'prebuilt';
|
||||||
loadOrder?: number;
|
loadOrder?: number;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,15 @@
|
|||||||
:trigger="trigger"
|
:trigger="trigger"
|
||||||
:dropdown-style="dropdownStyle"
|
:dropdown-style="dropdownStyle"
|
||||||
/>
|
/>
|
||||||
<el-table-column v-else v-bind="$attrs" :label="label" :width="resolvedWidth" :align="align" :fixed="resolvedFixed">
|
<el-table-column
|
||||||
|
v-else
|
||||||
|
v-bind="$attrs"
|
||||||
|
:label="label"
|
||||||
|
:width="resolvedWidth"
|
||||||
|
:min-width="resolvedMinWidth"
|
||||||
|
:align="align"
|
||||||
|
:fixed="resolvedFixed"
|
||||||
|
>
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<FuTableOperationActions
|
<FuTableOperationActions
|
||||||
:buttons="buttons"
|
:buttons="buttons"
|
||||||
@@ -137,9 +145,16 @@ const estimatedWidth = computed(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const resolvedWidth = computed(() => {
|
const resolvedWidth = computed(() => {
|
||||||
|
if (props.width === 'auto') {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
return normalizeWidth(props.width) ?? estimatedWidth.value;
|
return normalizeWidth(props.width) ?? estimatedWidth.value;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const resolvedMinWidth = computed(() => {
|
||||||
|
return props.width === 'auto' ? normalizeWidth(props.minWidth) : undefined;
|
||||||
|
});
|
||||||
|
|
||||||
const dropdownStyle = computed(() => {
|
const dropdownStyle = computed(() => {
|
||||||
if (props.maxHeight === undefined || props.maxHeight === null || props.maxHeight === '') {
|
if (props.maxHeight === undefined || props.maxHeight === null || props.maxHeight === '') {
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|||||||
@@ -3806,7 +3806,6 @@ const message = {
|
|||||||
buildModeDynamic: 'Dynamic',
|
buildModeDynamic: 'Dynamic',
|
||||||
buildModeStatic: 'Static',
|
buildModeStatic: 'Static',
|
||||||
buildStatus: 'Build status',
|
buildStatus: 'Build status',
|
||||||
compatibility: 'Compatibility',
|
|
||||||
loadOrder: 'Load order',
|
loadOrder: 'Load order',
|
||||||
modulesToBuild: 'Dynamic modules (hot-reload after build, no container restart)',
|
modulesToBuild: 'Dynamic modules (hot-reload after build, no container restart)',
|
||||||
staticModules: 'Static modules',
|
staticModules: 'Static modules',
|
||||||
@@ -3816,10 +3815,6 @@ const message = {
|
|||||||
pending: 'Pending',
|
pending: 'Pending',
|
||||||
ready: 'Ready',
|
ready: 'Ready',
|
||||||
failed: 'Failed',
|
failed: 'Failed',
|
||||||
unknown: 'Unknown',
|
|
||||||
compatible: 'Compatible',
|
|
||||||
stale: 'Rebuild required',
|
|
||||||
static: 'Static build',
|
|
||||||
dynamicUnsupported: 'Dynamic build is not supported on the current OpenResty version',
|
dynamicUnsupported: 'Dynamic build is not supported on the current OpenResty version',
|
||||||
moduleDynamicUnsupported: 'The parameters of this module do not support dynamic build',
|
moduleDynamicUnsupported: 'The parameters of this module do not support dynamic build',
|
||||||
mirrorUrl: 'Software Source',
|
mirrorUrl: 'Software Source',
|
||||||
|
|||||||
@@ -3849,7 +3849,6 @@ const message = {
|
|||||||
buildModeDynamic: 'Dinámico',
|
buildModeDynamic: 'Dinámico',
|
||||||
buildModeStatic: 'Estático',
|
buildModeStatic: 'Estático',
|
||||||
buildStatus: 'Estado de compilación',
|
buildStatus: 'Estado de compilación',
|
||||||
compatibility: 'Compatibilidad',
|
|
||||||
loadOrder: 'Orden de carga',
|
loadOrder: 'Orden de carga',
|
||||||
modulesToBuild: 'Módulos dinámicos (carga en caliente tras la compilación, sin reiniciar el contenedor)',
|
modulesToBuild: 'Módulos dinámicos (carga en caliente tras la compilación, sin reiniciar el contenedor)',
|
||||||
staticModules: 'Módulos estáticos',
|
staticModules: 'Módulos estáticos',
|
||||||
@@ -3859,10 +3858,6 @@ const message = {
|
|||||||
pending: 'Pendiente',
|
pending: 'Pendiente',
|
||||||
ready: 'Listo',
|
ready: 'Listo',
|
||||||
failed: 'Fallido',
|
failed: 'Fallido',
|
||||||
unknown: 'Desconocido',
|
|
||||||
compatible: 'Compatible',
|
|
||||||
stale: 'Requiere recompilación',
|
|
||||||
static: 'Compilación estática',
|
|
||||||
dynamicUnsupported: 'La compilación dinámica no es compatible con la versión actual de OpenResty',
|
dynamicUnsupported: 'La compilación dinámica no es compatible con la versión actual de OpenResty',
|
||||||
moduleDynamicUnsupported: 'Los parámetros de este módulo no admiten la compilación dinámica',
|
moduleDynamicUnsupported: 'Los parámetros de este módulo no admiten la compilación dinámica',
|
||||||
mirrorUrl: 'Fuente de software',
|
mirrorUrl: 'Fuente de software',
|
||||||
|
|||||||
@@ -3775,7 +3775,6 @@ const message = {
|
|||||||
buildModeDynamic: 'پویا',
|
buildModeDynamic: 'پویا',
|
||||||
buildModeStatic: 'ایستا',
|
buildModeStatic: 'ایستا',
|
||||||
buildStatus: 'وضعیت ساخت',
|
buildStatus: 'وضعیت ساخت',
|
||||||
compatibility: 'سازگاری',
|
|
||||||
loadOrder: 'ترتیب بارگذاری',
|
loadOrder: 'ترتیب بارگذاری',
|
||||||
modulesToBuild: 'ماژولهای پویا (بارگذاری گرم پس از ساخت، بدون راهاندازی مجدد کانتینر)',
|
modulesToBuild: 'ماژولهای پویا (بارگذاری گرم پس از ساخت، بدون راهاندازی مجدد کانتینر)',
|
||||||
staticModules: 'ماژولهای ایستا',
|
staticModules: 'ماژولهای ایستا',
|
||||||
@@ -3785,10 +3784,6 @@ const message = {
|
|||||||
pending: 'در انتظار',
|
pending: 'در انتظار',
|
||||||
ready: 'آماده',
|
ready: 'آماده',
|
||||||
failed: 'ناموفق',
|
failed: 'ناموفق',
|
||||||
unknown: 'نامشخص',
|
|
||||||
compatible: 'سازگار',
|
|
||||||
stale: 'نیاز به ساخت مجدد',
|
|
||||||
static: 'ساخت ایستا',
|
|
||||||
dynamicUnsupported: 'ساخت پویا در نسخه فعلی OpenResty پشتیبانی نمیشود',
|
dynamicUnsupported: 'ساخت پویا در نسخه فعلی OpenResty پشتیبانی نمیشود',
|
||||||
moduleDynamicUnsupported: 'پارامترهای این ماژول از ساخت پویا پشتیبانی نمیکنند',
|
moduleDynamicUnsupported: 'پارامترهای این ماژول از ساخت پویا پشتیبانی نمیکنند',
|
||||||
mirrorUrl: 'منبع نرمافزار',
|
mirrorUrl: 'منبع نرمافزار',
|
||||||
|
|||||||
@@ -3824,7 +3824,6 @@ const message = {
|
|||||||
buildModeDynamic: '動的',
|
buildModeDynamic: '動的',
|
||||||
buildModeStatic: '静的',
|
buildModeStatic: '静的',
|
||||||
buildStatus: 'ビルドステータス',
|
buildStatus: 'ビルドステータス',
|
||||||
compatibility: '互換性',
|
|
||||||
loadOrder: '読み込み順',
|
loadOrder: '読み込み順',
|
||||||
modulesToBuild: '動的モジュール(ビルド後ホットリロード、コンテナ再起動なし)',
|
modulesToBuild: '動的モジュール(ビルド後ホットリロード、コンテナ再起動なし)',
|
||||||
staticModules: '静的モジュール',
|
staticModules: '静的モジュール',
|
||||||
@@ -3834,10 +3833,6 @@ const message = {
|
|||||||
pending: 'ビルド待ち',
|
pending: 'ビルド待ち',
|
||||||
ready: 'ビルド済み',
|
ready: 'ビルド済み',
|
||||||
failed: 'ビルド失敗',
|
failed: 'ビルド失敗',
|
||||||
unknown: '不明',
|
|
||||||
compatible: '互換',
|
|
||||||
stale: '再ビルドが必要',
|
|
||||||
static: '静的ビルド',
|
|
||||||
dynamicUnsupported: '現在の OpenResty バージョンでは動的ビルドはサポートされていません',
|
dynamicUnsupported: '現在の OpenResty バージョンでは動的ビルドはサポートされていません',
|
||||||
moduleDynamicUnsupported: 'このモジュールのパラメータは動的ビルドに対応していません',
|
moduleDynamicUnsupported: 'このモジュールのパラメータは動的ビルドに対応していません',
|
||||||
mirrorUrl: 'ソフトウェアソース',
|
mirrorUrl: 'ソフトウェアソース',
|
||||||
|
|||||||
@@ -3743,7 +3743,6 @@ const message = {
|
|||||||
buildModeDynamic: '동적',
|
buildModeDynamic: '동적',
|
||||||
buildModeStatic: '정적',
|
buildModeStatic: '정적',
|
||||||
buildStatus: '빌드 상태',
|
buildStatus: '빌드 상태',
|
||||||
compatibility: '호환성',
|
|
||||||
loadOrder: '로드 순서',
|
loadOrder: '로드 순서',
|
||||||
modulesToBuild: '동적 모듈 (빌드 후 핫 리로드, 컨테이너 재시작 없음)',
|
modulesToBuild: '동적 모듈 (빌드 후 핫 리로드, 컨테이너 재시작 없음)',
|
||||||
staticModules: '정적 모듈',
|
staticModules: '정적 모듈',
|
||||||
@@ -3753,10 +3752,6 @@ const message = {
|
|||||||
pending: '대기 중',
|
pending: '대기 중',
|
||||||
ready: '준비됨',
|
ready: '준비됨',
|
||||||
failed: '실패',
|
failed: '실패',
|
||||||
unknown: '알 수 없음',
|
|
||||||
compatible: '호환됨',
|
|
||||||
stale: '다시 빌드 필요',
|
|
||||||
static: '정적 빌드',
|
|
||||||
dynamicUnsupported: '현재 OpenResty 버전에서는 동적 빌드를 지원하지 않습니다',
|
dynamicUnsupported: '현재 OpenResty 버전에서는 동적 빌드를 지원하지 않습니다',
|
||||||
moduleDynamicUnsupported: '이 모듈의 파라미터는 동적 빌드를 지원하지 않습니다',
|
moduleDynamicUnsupported: '이 모듈의 파라미터는 동적 빌드를 지원하지 않습니다',
|
||||||
mirrorUrl: '소프트웨어 소스',
|
mirrorUrl: '소프트웨어 소스',
|
||||||
|
|||||||
@@ -3714,7 +3714,6 @@ const message = {
|
|||||||
buildModeDynamic: 'ໄດນາມິກ',
|
buildModeDynamic: 'ໄດນາມິກ',
|
||||||
buildModeStatic: 'ສະແຕຕິກ',
|
buildModeStatic: 'ສະແຕຕິກ',
|
||||||
buildStatus: 'ສະຖານະການບິວ',
|
buildStatus: 'ສະຖານະການບິວ',
|
||||||
compatibility: 'ຄວາມເຂົ້າກັນ',
|
|
||||||
loadOrder: 'ລຳດັບການໂຫຼດ',
|
loadOrder: 'ລຳດັບການໂຫຼດ',
|
||||||
modulesToBuild: 'ໂມດູນໄດນາມິກ (ຫຼັງຈາກບິວໂຫຼດຮ້ອນ, ຄອນເທນເນີບໍ່ຣີສະຕາດ)',
|
modulesToBuild: 'ໂມດູນໄດນາມິກ (ຫຼັງຈາກບິວໂຫຼດຮ້ອນ, ຄອນເທນເນີບໍ່ຣີສະຕາດ)',
|
||||||
staticModules: 'ໂມດູນສະແຕຕິກ',
|
staticModules: 'ໂມດູນສະແຕຕິກ',
|
||||||
@@ -3724,10 +3723,6 @@ const message = {
|
|||||||
pending: 'ລໍຖ້າ',
|
pending: 'ລໍຖ້າ',
|
||||||
ready: 'ພ້ອມແລ້ວ',
|
ready: 'ພ້ອມແລ້ວ',
|
||||||
failed: 'ລົ້ມເຫຼວ',
|
failed: 'ລົ້ມເຫຼວ',
|
||||||
unknown: 'ບໍ່ຮູ້',
|
|
||||||
compatible: 'ເຂົ້າກັນໄດ້',
|
|
||||||
stale: 'ລ້າສະໄໝ',
|
|
||||||
static: 'ສະແຕຕິກ',
|
|
||||||
dynamicUnsupported: 'ເວີຊັນ OpenResty ປັດຈຸບັນບໍ່ຮອງຮັບການບິວແບບໄດນາມິກ',
|
dynamicUnsupported: 'ເວີຊັນ OpenResty ປັດຈຸບັນບໍ່ຮອງຮັບການບິວແບບໄດນາມິກ',
|
||||||
moduleDynamicUnsupported: 'ພາລາມິເຕີຂອງໂມດູນນີ້ບໍ່ຮອງຮັບການບິວແບບໄດນາມິກ',
|
moduleDynamicUnsupported: 'ພາລາມິເຕີຂອງໂມດູນນີ້ບໍ່ຮອງຮັບການບິວແບບໄດນາມິກ',
|
||||||
mirrorUrl: 'ແຫຼ່ງຊອບແວ',
|
mirrorUrl: 'ແຫຼ່ງຊອບແວ',
|
||||||
|
|||||||
@@ -3879,7 +3879,6 @@ const message = {
|
|||||||
buildModeDynamic: 'Dinamik',
|
buildModeDynamic: 'Dinamik',
|
||||||
buildModeStatic: 'Statik',
|
buildModeStatic: 'Statik',
|
||||||
buildStatus: 'Status binaan',
|
buildStatus: 'Status binaan',
|
||||||
compatibility: 'Keserasian',
|
|
||||||
loadOrder: 'Turutan muatan',
|
loadOrder: 'Turutan muatan',
|
||||||
modulesToBuild: 'Modul dinamik (muatan semula panas selepas binaan, kontena tidak dimulakan semula)',
|
modulesToBuild: 'Modul dinamik (muatan semula panas selepas binaan, kontena tidak dimulakan semula)',
|
||||||
staticModules: 'Modul statik',
|
staticModules: 'Modul statik',
|
||||||
@@ -3889,10 +3888,6 @@ const message = {
|
|||||||
pending: 'Menunggu',
|
pending: 'Menunggu',
|
||||||
ready: 'Sedia',
|
ready: 'Sedia',
|
||||||
failed: 'Gagal',
|
failed: 'Gagal',
|
||||||
unknown: 'Tidak diketahui',
|
|
||||||
compatible: 'Serasi',
|
|
||||||
stale: 'Perlu dibina semula',
|
|
||||||
static: 'Binaan statik',
|
|
||||||
dynamicUnsupported: 'Binaan dinamik tidak disokong pada versi OpenResty semasa',
|
dynamicUnsupported: 'Binaan dinamik tidak disokong pada versi OpenResty semasa',
|
||||||
moduleDynamicUnsupported: 'Parameter modul ini tidak menyokong binaan dinamik',
|
moduleDynamicUnsupported: 'Parameter modul ini tidak menyokong binaan dinamik',
|
||||||
mirrorUrl: 'Sumber Perisian',
|
mirrorUrl: 'Sumber Perisian',
|
||||||
|
|||||||
@@ -4015,7 +4015,6 @@ const message = {
|
|||||||
buildModeDynamic: 'Dinâmico',
|
buildModeDynamic: 'Dinâmico',
|
||||||
buildModeStatic: 'Estático',
|
buildModeStatic: 'Estático',
|
||||||
buildStatus: 'Status da compilação',
|
buildStatus: 'Status da compilação',
|
||||||
compatibility: 'Compatibilidade',
|
|
||||||
loadOrder: 'Ordem de carregamento',
|
loadOrder: 'Ordem de carregamento',
|
||||||
modulesToBuild: 'Módulos dinâmicos (hot reload após a compilação, sem reiniciar o contêiner)',
|
modulesToBuild: 'Módulos dinâmicos (hot reload após a compilação, sem reiniciar o contêiner)',
|
||||||
staticModules: 'Módulos estáticos',
|
staticModules: 'Módulos estáticos',
|
||||||
@@ -4025,10 +4024,6 @@ const message = {
|
|||||||
pending: 'Pendente',
|
pending: 'Pendente',
|
||||||
ready: 'Pronto',
|
ready: 'Pronto',
|
||||||
failed: 'Falhou',
|
failed: 'Falhou',
|
||||||
unknown: 'Desconhecido',
|
|
||||||
compatible: 'Compatível',
|
|
||||||
stale: 'Requer recompilação',
|
|
||||||
static: 'Compilação estática',
|
|
||||||
dynamicUnsupported: 'A compilação dinâmica não é suportada na versão atual do OpenResty',
|
dynamicUnsupported: 'A compilação dinâmica não é suportada na versão atual do OpenResty',
|
||||||
moduleDynamicUnsupported: 'Os parâmetros deste módulo não suportam compilação dinâmica',
|
moduleDynamicUnsupported: 'Os parâmetros deste módulo não suportam compilação dinâmica',
|
||||||
mirrorUrl: 'Fonte de Software',
|
mirrorUrl: 'Fonte de Software',
|
||||||
|
|||||||
@@ -3867,7 +3867,6 @@ const message = {
|
|||||||
buildModeDynamic: 'Динамическая',
|
buildModeDynamic: 'Динамическая',
|
||||||
buildModeStatic: 'Статическая',
|
buildModeStatic: 'Статическая',
|
||||||
buildStatus: 'Статус сборки',
|
buildStatus: 'Статус сборки',
|
||||||
compatibility: 'Совместимость',
|
|
||||||
loadOrder: 'Порядок загрузки',
|
loadOrder: 'Порядок загрузки',
|
||||||
modulesToBuild: 'Динамические модули (горячая перезагрузка после сборки, без перезапуска контейнера)',
|
modulesToBuild: 'Динамические модули (горячая перезагрузка после сборки, без перезапуска контейнера)',
|
||||||
staticModules: 'Статические модули',
|
staticModules: 'Статические модули',
|
||||||
@@ -3877,10 +3876,6 @@ const message = {
|
|||||||
pending: 'Ожидание',
|
pending: 'Ожидание',
|
||||||
ready: 'Готово',
|
ready: 'Готово',
|
||||||
failed: 'Ошибка',
|
failed: 'Ошибка',
|
||||||
unknown: 'Неизвестно',
|
|
||||||
compatible: 'Совместим',
|
|
||||||
stale: 'Требуется пересборка',
|
|
||||||
static: 'Статическая сборка',
|
|
||||||
dynamicUnsupported: 'Динамическая сборка не поддерживается текущей версией OpenResty',
|
dynamicUnsupported: 'Динамическая сборка не поддерживается текущей версией OpenResty',
|
||||||
moduleDynamicUnsupported: 'Параметры этого модуля не поддерживают динамическую сборку',
|
moduleDynamicUnsupported: 'Параметры этого модуля не поддерживают динамическую сборку',
|
||||||
mirrorUrl: 'Источник программного обеспечения',
|
mirrorUrl: 'Источник программного обеспечения',
|
||||||
|
|||||||
@@ -3866,7 +3866,6 @@ const message = {
|
|||||||
buildModeDynamic: 'Dinamik',
|
buildModeDynamic: 'Dinamik',
|
||||||
buildModeStatic: 'Statik',
|
buildModeStatic: 'Statik',
|
||||||
buildStatus: 'Derleme durumu',
|
buildStatus: 'Derleme durumu',
|
||||||
compatibility: 'Uyumluluk',
|
|
||||||
loadOrder: 'Yükleme sırası',
|
loadOrder: 'Yükleme sırası',
|
||||||
modulesToBuild: 'Dinamik modüller (derleme sonrası sıcak yeniden yükleme, konteyner yeniden başlatılmaz)',
|
modulesToBuild: 'Dinamik modüller (derleme sonrası sıcak yeniden yükleme, konteyner yeniden başlatılmaz)',
|
||||||
staticModules: 'Statik modüller',
|
staticModules: 'Statik modüller',
|
||||||
@@ -3876,10 +3875,6 @@ const message = {
|
|||||||
pending: 'Beklemede',
|
pending: 'Beklemede',
|
||||||
ready: 'Hazır',
|
ready: 'Hazır',
|
||||||
failed: 'Başarısız',
|
failed: 'Başarısız',
|
||||||
unknown: 'Bilinmiyor',
|
|
||||||
compatible: 'Uyumlu',
|
|
||||||
stale: 'Yeniden derleme gerekli',
|
|
||||||
static: 'Statik derleme',
|
|
||||||
dynamicUnsupported: 'Mevcut OpenResty sürümünde dinamik derleme desteklenmiyor',
|
dynamicUnsupported: 'Mevcut OpenResty sürümünde dinamik derleme desteklenmiyor',
|
||||||
moduleDynamicUnsupported: 'Bu modülün parametreleri dinamik derlemeyi desteklemiyor',
|
moduleDynamicUnsupported: 'Bu modülün parametreleri dinamik derlemeyi desteklemiyor',
|
||||||
mirrorUrl: 'Yazılım Kaynağı',
|
mirrorUrl: 'Yazılım Kaynağı',
|
||||||
|
|||||||
@@ -3532,7 +3532,6 @@ const message = {
|
|||||||
buildModeDynamic: '動態模組',
|
buildModeDynamic: '動態模組',
|
||||||
buildModeStatic: '靜態模組',
|
buildModeStatic: '靜態模組',
|
||||||
buildStatus: '構建狀態',
|
buildStatus: '構建狀態',
|
||||||
compatibility: '相容性',
|
|
||||||
loadOrder: '載入順序',
|
loadOrder: '載入順序',
|
||||||
modulesToBuild: '動態模組(構建後熱載入,容器不重新啟動)',
|
modulesToBuild: '動態模組(構建後熱載入,容器不重新啟動)',
|
||||||
staticModules: '靜態模組',
|
staticModules: '靜態模組',
|
||||||
@@ -3542,10 +3541,6 @@ const message = {
|
|||||||
pending: '待構建',
|
pending: '待構建',
|
||||||
ready: '已構建',
|
ready: '已構建',
|
||||||
failed: '構建失敗',
|
failed: '構建失敗',
|
||||||
unknown: '未知',
|
|
||||||
compatible: '相容',
|
|
||||||
stale: '需要重新構建',
|
|
||||||
static: '靜態編譯',
|
|
||||||
dynamicUnsupported: '目前 OpenResty 版本不支援動態構建',
|
dynamicUnsupported: '目前 OpenResty 版本不支援動態構建',
|
||||||
moduleDynamicUnsupported: '此模組參數不支援動態構建',
|
moduleDynamicUnsupported: '此模組參數不支援動態構建',
|
||||||
mirrorUrl: '軟體源',
|
mirrorUrl: '軟體源',
|
||||||
|
|||||||
@@ -3531,7 +3531,6 @@ const message = {
|
|||||||
buildModeDynamic: '动态模块',
|
buildModeDynamic: '动态模块',
|
||||||
buildModeStatic: '静态模块',
|
buildModeStatic: '静态模块',
|
||||||
buildStatus: '构建状态',
|
buildStatus: '构建状态',
|
||||||
compatibility: '兼容性',
|
|
||||||
loadOrder: '加载顺序',
|
loadOrder: '加载顺序',
|
||||||
modulesToBuild: '动态模块(构建后热加载,容器不重启)',
|
modulesToBuild: '动态模块(构建后热加载,容器不重启)',
|
||||||
staticModules: '静态模块',
|
staticModules: '静态模块',
|
||||||
@@ -3541,10 +3540,6 @@ const message = {
|
|||||||
pending: '待构建',
|
pending: '待构建',
|
||||||
ready: '已构建',
|
ready: '已构建',
|
||||||
failed: '构建失败',
|
failed: '构建失败',
|
||||||
unknown: '未知',
|
|
||||||
compatible: '兼容',
|
|
||||||
stale: '需要重新构建',
|
|
||||||
static: '静态编译',
|
|
||||||
dynamicUnsupported: '当前 OpenResty 版本不支持动态构建',
|
dynamicUnsupported: '当前 OpenResty 版本不支持动态构建',
|
||||||
moduleDynamicUnsupported: '该模块参数不支持动态构建',
|
moduleDynamicUnsupported: '该模块参数不支持动态构建',
|
||||||
mirrorUrl: '软件源',
|
mirrorUrl: '软件源',
|
||||||
|
|||||||
@@ -10,15 +10,15 @@
|
|||||||
</el-button>
|
</el-button>
|
||||||
<el-text type="warning" class="!ml-2">{{ $t('nginx.buildHelper') }}</el-text>
|
<el-text type="warning" class="!ml-2">{{ $t('nginx.buildHelper') }}</el-text>
|
||||||
</template>
|
</template>
|
||||||
<el-table-column prop="name" :label="$t('commons.table.name')" />
|
<el-table-column prop="name" :label="$t('commons.table.name')" min-width="320" />
|
||||||
<el-table-column :label="$t('nginx.buildMode')" width="150">
|
<el-table-column :label="$t('nginx.buildMode')" min-width="160" align="center">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-tag effect="plain" :type="row.buildMode === 'static' ? 'warning' : 'primary'">
|
<el-tag effect="plain" :type="row.buildMode === 'static' ? 'warning' : 'primary'">
|
||||||
{{ $t('nginx.buildMode' + capitalize(displayBuildMode(row.buildMode))) }}
|
{{ $t('nginx.buildMode' + capitalize(row.buildMode)) }}
|
||||||
</el-tag>
|
</el-tag>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column :label="$t('nginx.buildStatus')" width="120">
|
<el-table-column :label="$t('nginx.buildStatus')" min-width="160" align="center">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-tooltip v-if="row.lastError" :content="row.lastError" placement="top">
|
<el-tooltip v-if="row.lastError" :content="row.lastError" placement="top">
|
||||||
<el-tag :type="statusType(row.buildStatus)">{{ $t('nginx.' + row.buildStatus) }}</el-tag>
|
<el-tag :type="statusType(row.buildStatus)">{{ $t('nginx.' + row.buildStatus) }}</el-tag>
|
||||||
@@ -26,21 +26,15 @@
|
|||||||
<el-tag v-else :type="statusType(row.buildStatus)">{{ $t('nginx.' + row.buildStatus) }}</el-tag>
|
<el-tag v-else :type="statusType(row.buildStatus)">{{ $t('nginx.' + row.buildStatus) }}</el-tag>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column :label="$t('nginx.compatibility')" width="130">
|
<el-table-column :label="$t('commons.table.status')" min-width="160" align="center" fix>
|
||||||
<template #default="{ row }">
|
|
||||||
<el-tag effect="plain" :type="compatibilityType(row.compatibility)">
|
|
||||||
{{ $t('nginx.' + row.compatibility) }}
|
|
||||||
</el-tag>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column :label="$t('commons.table.status')" fix>
|
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-switch v-permission v-model="row.enable" @change="updateModule(row)" />
|
<el-switch v-permission v-model="row.enable" @change="updateModule(row)" />
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<fu-table-operations
|
<fu-table-operations
|
||||||
:ellipsis="2"
|
:ellipsis="2"
|
||||||
width="200px"
|
width="auto"
|
||||||
|
min-width="160"
|
||||||
:buttons="buttons"
|
:buttons="buttons"
|
||||||
:label="$t('commons.table.operate')"
|
:label="$t('commons.table.operate')"
|
||||||
fixed="right"
|
fixed="right"
|
||||||
@@ -64,9 +58,18 @@ import { onMounted, ref } from 'vue';
|
|||||||
const data = ref<Nginx.NginxModule[]>([]);
|
const data = ref<Nginx.NginxModule[]>([]);
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const buttons = [
|
const buttons = [
|
||||||
|
{
|
||||||
|
label: i18n.global.t('commons.button.view'),
|
||||||
|
permission: true,
|
||||||
|
show: (row: Nginx.NginxModule) => !row.custom,
|
||||||
|
click: function (row: Nginx.NginxModule) {
|
||||||
|
openView(row);
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: i18n.global.t('commons.button.edit'),
|
label: i18n.global.t('commons.button.edit'),
|
||||||
permission: true,
|
permission: true,
|
||||||
|
show: (row: Nginx.NginxModule) => row.custom,
|
||||||
click: function (row: Nginx.NginxModule) {
|
click: function (row: Nginx.NginxModule) {
|
||||||
openEdit(row);
|
openEdit(row);
|
||||||
},
|
},
|
||||||
@@ -74,6 +77,7 @@ const buttons = [
|
|||||||
{
|
{
|
||||||
label: i18n.global.t('commons.button.delete'),
|
label: i18n.global.t('commons.button.delete'),
|
||||||
permission: true,
|
permission: true,
|
||||||
|
show: (row: Nginx.NginxModule) => row.custom,
|
||||||
click: function (row: Nginx.NginxModule) {
|
click: function (row: Nginx.NginxModule) {
|
||||||
deleteModule(row);
|
deleteModule(row);
|
||||||
},
|
},
|
||||||
@@ -108,6 +112,10 @@ const openEdit = (row: Nginx.NginxModule) => {
|
|||||||
operateRef.value.acceptParams('update', row, dynamicSupported.value);
|
operateRef.value.acceptParams('update', row, dynamicSupported.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const openView = (row: Nginx.NginxModule) => {
|
||||||
|
operateRef.value.acceptParams('view', row, dynamicSupported.value);
|
||||||
|
};
|
||||||
|
|
||||||
const updateModule = (row: Nginx.NginxModule) => {
|
const updateModule = (row: Nginx.NginxModule) => {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
const data = {
|
const data = {
|
||||||
@@ -129,21 +137,12 @@ const updateModule = (row: Nginx.NginxModule) => {
|
|||||||
|
|
||||||
const capitalize = (value: string) => value.charAt(0).toUpperCase() + value.slice(1);
|
const capitalize = (value: string) => value.charAt(0).toUpperCase() + value.slice(1);
|
||||||
|
|
||||||
const displayBuildMode = (mode: string) => (mode === 'auto' ? 'dynamic' : mode);
|
|
||||||
|
|
||||||
const statusType = (status: string) => {
|
const statusType = (status: string) => {
|
||||||
if (status === 'ready') return 'success';
|
if (status === 'ready') return 'success';
|
||||||
if (status === 'failed') return 'danger';
|
if (status === 'failed') return 'danger';
|
||||||
return 'info';
|
return 'info';
|
||||||
};
|
};
|
||||||
|
|
||||||
const compatibilityType = (status: string) => {
|
|
||||||
if (status === 'compatible') return 'success';
|
|
||||||
if (status === 'stale') return 'warning';
|
|
||||||
if (status === 'static') return 'info';
|
|
||||||
return 'info';
|
|
||||||
};
|
|
||||||
|
|
||||||
const deleteModule = async (row: Nginx.NginxModule) => {
|
const deleteModule = async (row: Nginx.NginxModule) => {
|
||||||
const data = {
|
const data = {
|
||||||
name: row.name,
|
name: row.name,
|
||||||
|
|||||||
@@ -1,21 +1,18 @@
|
|||||||
<template>
|
<template>
|
||||||
<DrawerPro
|
<DrawerPro
|
||||||
v-model="open"
|
v-model="open"
|
||||||
:header="$t('nginx.' + mode)"
|
:header="mode === 'view' ? $t('commons.button.view') : $t('nginx.' + mode)"
|
||||||
size="large"
|
size="large"
|
||||||
:resource="mode === 'update' ? module.name : ''"
|
:resource="mode === 'create' ? '' : module.name"
|
||||||
@close="handleClose"
|
@close="handleClose"
|
||||||
>
|
>
|
||||||
<el-form ref="moduleForm" label-position="top" :model="module" :rules="rules">
|
<el-form ref="moduleForm" label-position="top" :model="module" :rules="rules" :disabled="mode === 'view'">
|
||||||
<el-form-item :label="$t('commons.table.name')" prop="name">
|
<el-form-item :label="$t('commons.table.name')" prop="name">
|
||||||
<el-input v-model.trim="module.name" :disabled="mode === 'update'"></el-input>
|
<el-input v-model.trim="module.name" :disabled="mode === 'update'"></el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item :label="$t('nginx.buildMode')" prop="buildMode">
|
<el-form-item :label="$t('nginx.buildMode')" prop="buildMode">
|
||||||
<el-radio-group v-model="module.buildMode">
|
<el-radio-group v-model="module.buildMode">
|
||||||
<el-radio-button
|
<el-radio-button value="dynamic" :disabled="!dynamicSupported">
|
||||||
value="dynamic"
|
|
||||||
:disabled="!dynamicSupported || module.dynamicSupport === 'unsupported'"
|
|
||||||
>
|
|
||||||
{{ $t('nginx.buildModeDynamic') }}
|
{{ $t('nginx.buildModeDynamic') }}
|
||||||
</el-radio-button>
|
</el-radio-button>
|
||||||
<el-radio-button value="static">{{ $t('nginx.buildModeStatic') }}</el-radio-button>
|
<el-radio-button value="static">{{ $t('nginx.buildModeStatic') }}</el-radio-button>
|
||||||
@@ -23,9 +20,6 @@
|
|||||||
<el-text v-if="!dynamicSupported" type="warning" class="!ml-2">
|
<el-text v-if="!dynamicSupported" type="warning" class="!ml-2">
|
||||||
{{ $t('nginx.dynamicUnsupported') }}
|
{{ $t('nginx.dynamicUnsupported') }}
|
||||||
</el-text>
|
</el-text>
|
||||||
<el-text v-else-if="module.dynamicSupport === 'unsupported'" type="warning" class="!ml-2">
|
|
||||||
{{ $t('nginx.moduleDynamicUnsupported') }}
|
|
||||||
</el-text>
|
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item :label="$t('nginx.params')" prop="params">
|
<el-form-item :label="$t('nginx.params')" prop="params">
|
||||||
<el-input v-model.trim="module.params" :placeholder="$t('nginx.paramsHelper')"></el-input>
|
<el-input v-model.trim="module.params" :placeholder="$t('nginx.paramsHelper')"></el-input>
|
||||||
@@ -55,8 +49,16 @@
|
|||||||
/>
|
/>
|
||||||
</el-form>
|
</el-form>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<el-button @click="handleClose" :disabled="loading">{{ $t('commons.button.cancel') }}</el-button>
|
<el-button @click="handleClose" :disabled="loading">
|
||||||
<el-button v-permission type="primary" @click="submit(moduleForm)" :disabled="loading">
|
{{ $t(mode === 'view' ? 'commons.button.close' : 'commons.button.cancel') }}
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
v-if="mode !== 'view'"
|
||||||
|
v-permission
|
||||||
|
type="primary"
|
||||||
|
@click="submit(moduleForm)"
|
||||||
|
:disabled="loading"
|
||||||
|
>
|
||||||
{{ $t('commons.button.confirm') }}
|
{{ $t('commons.button.confirm') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
@@ -87,7 +89,6 @@ type ModuleForm = {
|
|||||||
packages: string;
|
packages: string;
|
||||||
buildMode: Nginx.NginxModule['buildMode'];
|
buildMode: Nginx.NginxModule['buildMode'];
|
||||||
provider: Nginx.NginxModule['provider'];
|
provider: Nginx.NginxModule['provider'];
|
||||||
dynamicSupport: Nginx.NginxModule['dynamicSupport'];
|
|
||||||
loadOrder: number;
|
loadOrder: number;
|
||||||
lastError: string;
|
lastError: string;
|
||||||
};
|
};
|
||||||
@@ -100,7 +101,6 @@ const defaultModule = (): ModuleForm => ({
|
|||||||
packages: '',
|
packages: '',
|
||||||
buildMode: 'dynamic',
|
buildMode: 'dynamic',
|
||||||
provider: 'local',
|
provider: 'local',
|
||||||
dynamicSupport: 'unknown',
|
|
||||||
loadOrder: 50,
|
loadOrder: 50,
|
||||||
lastError: '',
|
lastError: '',
|
||||||
});
|
});
|
||||||
@@ -120,19 +120,18 @@ const acceptParams = async (operate: string, editModule?: Nginx.NginxModule, sup
|
|||||||
mode.value = operate;
|
mode.value = operate;
|
||||||
dynamicSupported.value = supported ?? true;
|
dynamicSupported.value = supported ?? true;
|
||||||
module.value = defaultModule();
|
module.value = defaultModule();
|
||||||
if (operate === 'update' && editModule) {
|
if ((operate === 'update' || operate === 'view') && editModule) {
|
||||||
module.value = {
|
module.value = {
|
||||||
name: editModule.name,
|
name: editModule.name,
|
||||||
script: editModule.script || '',
|
script: editModule.script || '',
|
||||||
enable: editModule.enable,
|
enable: editModule.enable,
|
||||||
params: editModule.params,
|
params: editModule.params,
|
||||||
packages: editModule.packages || '',
|
packages: editModule.packages || '',
|
||||||
buildMode: editModule.buildMode === 'auto' ? 'dynamic' : editModule.buildMode,
|
buildMode: editModule.buildMode,
|
||||||
provider: editModule.provider,
|
provider: editModule.provider,
|
||||||
dynamicSupport: editModule.dynamicSupport,
|
|
||||||
loadOrder: editModule.loadOrder,
|
loadOrder: editModule.loadOrder,
|
||||||
lastError: editModule.lastError || '',
|
lastError: editModule.lastError || '',
|
||||||
operate: 'update',
|
operate,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
open.value = true;
|
open.value = true;
|
||||||
|
|||||||
@@ -1,16 +1,12 @@
|
|||||||
# OpenResty Dynamic Module Linux Tests
|
# OpenResty Dynamic Module Diagnostics
|
||||||
|
|
||||||
These scripts test the local dynamic-module build path and collect diagnostics
|
This script collects diagnostics from an installed 1Panel OpenResty instance.
|
||||||
from an installed 1Panel OpenResty instance. Run them on a disposable Linux
|
|
||||||
host with Docker access before testing on a production installation.
|
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
- Bash 4.3 or newer
|
- Bash 4.3 or newer
|
||||||
- Docker Engine with the Compose v2 plugin
|
- Docker Engine with the Compose v2 plugin
|
||||||
- `jq`, `python3`, `file`, `binutils`, `tar`, and GNU coreutils
|
- `jq`, `python3`, `file`, `binutils`, `tar`, and GNU coreutils
|
||||||
- Internet access for runtime images and Ubuntu build packages
|
|
||||||
- Go, only when `--source-checks` is used
|
|
||||||
|
|
||||||
On Debian or Ubuntu:
|
On Debian or Ubuntu:
|
||||||
|
|
||||||
@@ -25,71 +21,6 @@ Make the scripts executable:
|
|||||||
chmod +x scripts/openresty-modules/*.sh
|
chmod +x scripts/openresty-modules/*.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
## Builder Test
|
|
||||||
|
|
||||||
Start with one version and one small module:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
./scripts/openresty-modules/test-builder.sh \
|
|
||||||
--appstore ../appstore \
|
|
||||||
--versions 1.31.1.1-0-noble \
|
|
||||||
--modules ngx_brotli \
|
|
||||||
--source-checks
|
|
||||||
```
|
|
||||||
|
|
||||||
Test every catalog module against all refactored OpenResty versions:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
./scripts/openresty-modules/test-builder.sh --appstore ../appstore
|
|
||||||
```
|
|
||||||
|
|
||||||
Bypass Docker's module build cache when reproducing a compiler problem:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
./scripts/openresty-modules/test-builder.sh \
|
|
||||||
--appstore ../appstore \
|
|
||||||
--versions 1.31.1.1-0-noble \
|
|
||||||
--modules geoip2 \
|
|
||||||
--no-cache \
|
|
||||||
--keep-context \
|
|
||||||
--keep-docker
|
|
||||||
```
|
|
||||||
|
|
||||||
The builder test performs these phases for every selected version:
|
|
||||||
|
|
||||||
1. Validate appstore JSON, shell scripts, Compose mounts, and Nginx include.
|
|
||||||
2. Pull and identify the exact target runtime image.
|
|
||||||
3. Convert catalog options to dynamic configure options.
|
|
||||||
4. Build every module with `Dockerfile.modules` and copy `/out` locally.
|
|
||||||
5. Record SHA-256, ELF metadata, compiler output, and runtime dependencies.
|
|
||||||
6. Validate individual modules for debugging. Individual failures are warnings
|
|
||||||
by default because modules may depend on an earlier module.
|
|
||||||
7. Validate all modules together in catalog `loadOrder`.
|
|
||||||
8. Start an isolated OpenResty master, add module configs, and hot reload.
|
|
||||||
9. Inject a missing module, prove `nginx -t` rejects it, restore the config,
|
|
||||||
and prove the running process remains healthy.
|
|
||||||
|
|
||||||
Use `--strict-individual` when every selected module is expected to load alone.
|
|
||||||
|
|
||||||
Use `--mirror URL` (environment variable `MIRROR`) to pass an apt mirror as
|
|
||||||
`CONTAINER_PACKAGE_URL` to module builds, matching the 1Panel module build.
|
|
||||||
|
|
||||||
Results are written to:
|
|
||||||
|
|
||||||
```text
|
|
||||||
openresty-module-test-results/<run-id>/
|
|
||||||
```
|
|
||||||
|
|
||||||
Important files:
|
|
||||||
|
|
||||||
- `summary.tsv`: result per OpenResty version
|
|
||||||
- `work/<version>/logs/build-*.log`: complete BuildKit output
|
|
||||||
- `work/<version>/logs/load-combined.log`: authoritative ABI/load-order test
|
|
||||||
- `work/<version>/artifacts.tsv`: module paths, checksums, and sizes
|
|
||||||
- `work/<version>/image-inspect.json`: exact target image identity
|
|
||||||
- `work/<version>/runtime/`: reload and rollback test logs
|
|
||||||
- `<result-dir>.tar.gz`: automatically created when an unexpected failure occurs
|
|
||||||
|
|
||||||
## Installed Instance Diagnostics
|
## Installed Instance Diagnostics
|
||||||
|
|
||||||
Find the OpenResty installation directory first. A common path is similar to:
|
Find the OpenResty installation directory first. A common path is similar to:
|
||||||
@@ -130,42 +61,5 @@ Module scripts are redacted from the copied state files by default. Container
|
|||||||
logs and error strings can still contain site names, URLs, or command output;
|
logs and error strings can still contain site names, URLs, or command output;
|
||||||
review an archive before sharing it outside your team.
|
review an archive before sharing it outside your team.
|
||||||
|
|
||||||
## Final Manual Matrix
|
|
||||||
|
|
||||||
Run this matrix through the 1Panel UI on a disposable installation. Collect a
|
|
||||||
diagnostic archive after each important transition.
|
|
||||||
|
|
||||||
1. Install the oldest selected OpenResty version with every module disabled.
|
|
||||||
2. Switch one module to `auto`, enable it, and build it locally.
|
|
||||||
3. Confirm `buildStatus=ready`, `compatibility=compatible`, and `nginx -t`.
|
|
||||||
4. Force rebuild it. Confirm the artifact path changes and the old config is
|
|
||||||
replaced only after the new artifact passes validation.
|
|
||||||
5. Enable all catalog modules and verify catalog load order with the builder
|
|
||||||
test and the installed-instance collector.
|
|
||||||
6. On the test host, make one module script return a failure. Confirm the old
|
|
||||||
managed config and old ready artifact remain active.
|
|
||||||
7. Restore the module definition and rebuild successfully.
|
|
||||||
8. Upgrade OpenResty. Confirm every enabled dynamic module has a ready build
|
|
||||||
whose target image ID matches the new running container.
|
|
||||||
9. Restart the container and host. Run the diagnostic collector again to prove
|
|
||||||
the persisted mounts and configs remain valid.
|
|
||||||
10. Switch a module to `static`, rebuild, then switch it back to `dynamic` and
|
|
||||||
verify that all enabled dynamic modules are regenerated for the new image.
|
|
||||||
|
|
||||||
## Failure Triage
|
|
||||||
|
|
||||||
| Symptom | First evidence to inspect |
|
|
||||||
| --- | --- |
|
|
||||||
| Docker build fails | `logs/build-<module>.log`, `inputs/<module>/` |
|
|
||||||
| `.so` missing | build log and the `module-output` stage `/out` checks |
|
|
||||||
| Individual load fails, combined passes | module dependency and `loadOrder` |
|
|
||||||
| Combined load fails | ABI mismatch, duplicate module, missing shared library |
|
|
||||||
| `ldd` shows `not found` | bundled `lib/`, RPATH, or future runtime packages |
|
|
||||||
| Checksum mismatch | interrupted copy, manual modification, stale state file |
|
|
||||||
| Target image mismatch | module was not rebuilt after image upgrade/rebuild |
|
|
||||||
| Builder passes, installed `nginx -t` fails | Compose mounts or managed config |
|
|
||||||
| Reload fails but old process runs | inspect rollback logs and old config snapshot |
|
|
||||||
|
|
||||||
Do not edit generated module state or managed config files while a 1Panel app
|
Do not edit generated module state or managed config files while a 1Panel app
|
||||||
task is running. Preserve the result directory and archive before retrying a
|
task is running. Review the diagnostic archive before sharing it.
|
||||||
failed build.
|
|
||||||
|
|||||||
@@ -1,542 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
set -Eeuo pipefail
|
|
||||||
export DOCKER_BUILDKIT="${DOCKER_BUILDKIT:-1}"
|
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
|
|
||||||
REPO_ROOT="$(cd -- "${SCRIPT_DIR}/../.." && pwd -P)"
|
|
||||||
DEFAULT_APPSTORE_ROOT="$(cd -- "${REPO_ROOT}/.." && pwd -P)/appstore"
|
|
||||||
RUN_ID="$(date -u +%Y%m%dT%H%M%SZ)-$$"
|
|
||||||
|
|
||||||
APPSTORE_ROOT="${APPSTORE_ROOT:-${DEFAULT_APPSTORE_ROOT}}"
|
|
||||||
VERSIONS_CSV="1.27.1.2-5-1-focal,1.29.2.5-0-noble,1.31.1.1-0-noble"
|
|
||||||
MODULES_CSV=""
|
|
||||||
OUTPUT_DIR="${OUTPUT_DIR:-${PWD}/openresty-module-test-results/${RUN_ID}}"
|
|
||||||
MIRROR="${MIRROR:-}"
|
|
||||||
SKIP_PULL=0
|
|
||||||
NO_CACHE=0
|
|
||||||
KEEP_DOCKER=0
|
|
||||||
KEEP_CONTEXT=0
|
|
||||||
RUN_SOURCE_CHECKS=0
|
|
||||||
STRICT_INDIVIDUAL=0
|
|
||||||
CLEANUP_READY=0
|
|
||||||
|
|
||||||
declare -a CREATED_CONTAINERS=()
|
|
||||||
declare -a CREATED_IMAGES=()
|
|
||||||
declare -a VERSIONS=()
|
|
||||||
declare -a REQUESTED_MODULES=()
|
|
||||||
|
|
||||||
usage() {
|
|
||||||
cat <<'EOF'
|
|
||||||
Usage: test-builder.sh [options]
|
|
||||||
|
|
||||||
Build and load-test OpenResty dynamic modules directly from the appstore tree.
|
|
||||||
|
|
||||||
Options:
|
|
||||||
--appstore PATH Appstore repository root (default: sibling appstore repo)
|
|
||||||
--versions CSV App versions to test
|
|
||||||
--modules CSV Module names to test (default: every catalog module)
|
|
||||||
--output PATH Persistent result directory
|
|
||||||
--mirror URL apt mirror for module build packages (CONTAINER_PACKAGE_URL)
|
|
||||||
--skip-pull Use local runtime images without pulling
|
|
||||||
--no-cache Pass --no-cache to every module Docker build
|
|
||||||
--strict-individual Fail when a module cannot load by itself
|
|
||||||
--source-checks Run Go module tests and go vet before Docker tests
|
|
||||||
--keep-docker Keep temporary images and containers
|
|
||||||
--keep-context Keep copied Docker build contexts
|
|
||||||
-h, --help Show this help
|
|
||||||
|
|
||||||
Environment equivalents: APPSTORE_ROOT, OUTPUT_DIR, MIRROR, DOCKER_BUILDKIT.
|
|
||||||
EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
log() {
|
|
||||||
printf '[%s] %s\n' "$(date -u +%H:%M:%S)" "$*" | tee -a "${OUTPUT_DIR}/run.log"
|
|
||||||
}
|
|
||||||
|
|
||||||
die() {
|
|
||||||
log "ERROR: $*"
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
require_command() {
|
|
||||||
command -v "$1" >/dev/null 2>&1 || die "required command not found: $1"
|
|
||||||
}
|
|
||||||
|
|
||||||
split_csv() {
|
|
||||||
local value="$1"
|
|
||||||
local -n destination="$2"
|
|
||||||
IFS=',' read -r -a destination <<<"${value}"
|
|
||||||
}
|
|
||||||
|
|
||||||
safe_name() {
|
|
||||||
local value="$1"
|
|
||||||
local base digest
|
|
||||||
base="$(printf '%s' "${value}" | sed -E 's/[^a-zA-Z0-9._-]+/-/g; s/^-+//; s/-+$//' | cut -c1-48)"
|
|
||||||
[[ -n "${base}" ]] || base="module"
|
|
||||||
digest="$(printf '%s' "${value}" | sha256sum | awk '{print substr($1,1,8)}')"
|
|
||||||
printf '%s-%s' "${base}" "${digest}"
|
|
||||||
}
|
|
||||||
|
|
||||||
run_logged() {
|
|
||||||
local log_file="$1"
|
|
||||||
shift
|
|
||||||
mkdir -p "$(dirname -- "${log_file}")"
|
|
||||||
set +e
|
|
||||||
"$@" > >(tee "${log_file}") 2>&1
|
|
||||||
local status=$?
|
|
||||||
set -e
|
|
||||||
return "${status}"
|
|
||||||
}
|
|
||||||
|
|
||||||
docker_rm_container() {
|
|
||||||
local name="$1"
|
|
||||||
docker rm -f "${name}" >/dev/null 2>&1 || true
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup() {
|
|
||||||
local status=$?
|
|
||||||
if [[ "${CLEANUP_READY}" -eq 0 ]]; then
|
|
||||||
exit "${status}"
|
|
||||||
fi
|
|
||||||
if [[ "${KEEP_DOCKER}" -eq 0 ]]; then
|
|
||||||
local item
|
|
||||||
for item in "${CREATED_CONTAINERS[@]:-}"; do
|
|
||||||
[[ -n "${item}" ]] && docker_rm_container "${item}"
|
|
||||||
done
|
|
||||||
for item in "${CREATED_IMAGES[@]:-}"; do
|
|
||||||
[[ -n "${item}" ]] && docker image rm -f "${item}" >/dev/null 2>&1 || true
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
if [[ "${KEEP_CONTEXT}" -eq 0 && -d "${OUTPUT_DIR}/work" ]]; then
|
|
||||||
find "${OUTPUT_DIR}/work" -mindepth 2 -maxdepth 2 -type d -name context -prune -exec rm -rf -- {} + 2>/dev/null || true
|
|
||||||
fi
|
|
||||||
exit "${status}"
|
|
||||||
}
|
|
||||||
|
|
||||||
write_debug_bundle() {
|
|
||||||
local status="$1" line="$2" command="$3"
|
|
||||||
{
|
|
||||||
printf 'exit_status=%s\n' "${status}"
|
|
||||||
printf 'line=%s\n' "${line}"
|
|
||||||
printf 'command=%s\n' "${command}"
|
|
||||||
printf 'run_id=%s\n' "${RUN_ID}"
|
|
||||||
} >"${OUTPUT_DIR}/failure.txt"
|
|
||||||
|
|
||||||
docker ps -a --no-trunc >"${OUTPUT_DIR}/docker-ps.txt" 2>&1 || true
|
|
||||||
docker image ls --digests --no-trunc >"${OUTPUT_DIR}/docker-images.txt" 2>&1 || true
|
|
||||||
docker system df >"${OUTPUT_DIR}/docker-system-df.txt" 2>&1 || true
|
|
||||||
|
|
||||||
local item
|
|
||||||
for item in "${CREATED_CONTAINERS[@]:-}"; do
|
|
||||||
[[ -n "${item}" ]] || continue
|
|
||||||
docker inspect "${item}" >"${OUTPUT_DIR}/container-${item}.json" 2>&1 || true
|
|
||||||
docker logs "${item}" >"${OUTPUT_DIR}/container-${item}.log" 2>&1 || true
|
|
||||||
done
|
|
||||||
|
|
||||||
local archive="${OUTPUT_DIR%/}.tar.gz"
|
|
||||||
tar --exclude='*/context' -czf "${archive}" -C "$(dirname -- "${OUTPUT_DIR}")" "$(basename -- "${OUTPUT_DIR}")" 2>/dev/null || true
|
|
||||||
printf 'Debug bundle: %s\n' "${archive}" >&2
|
|
||||||
}
|
|
||||||
|
|
||||||
on_error() {
|
|
||||||
local status="$1" line="$2" command="$3"
|
|
||||||
set +e
|
|
||||||
log "FAILED at line ${line}: ${command} (exit ${status})"
|
|
||||||
write_debug_bundle "${status}" "${line}" "${command}"
|
|
||||||
return "${status}"
|
|
||||||
}
|
|
||||||
|
|
||||||
trap cleanup EXIT
|
|
||||||
|
|
||||||
while [[ $# -gt 0 ]]; do
|
|
||||||
case "$1" in
|
|
||||||
--appstore)
|
|
||||||
APPSTORE_ROOT="$2"
|
|
||||||
shift 2
|
|
||||||
;;
|
|
||||||
--versions)
|
|
||||||
VERSIONS_CSV="$2"
|
|
||||||
shift 2
|
|
||||||
;;
|
|
||||||
--modules)
|
|
||||||
MODULES_CSV="$2"
|
|
||||||
shift 2
|
|
||||||
;;
|
|
||||||
--output)
|
|
||||||
OUTPUT_DIR="$2"
|
|
||||||
shift 2
|
|
||||||
;;
|
|
||||||
--mirror)
|
|
||||||
MIRROR="$2"
|
|
||||||
shift 2
|
|
||||||
;;
|
|
||||||
--skip-pull)
|
|
||||||
SKIP_PULL=1
|
|
||||||
shift
|
|
||||||
;;
|
|
||||||
--no-cache)
|
|
||||||
NO_CACHE=1
|
|
||||||
shift
|
|
||||||
;;
|
|
||||||
--strict-individual)
|
|
||||||
STRICT_INDIVIDUAL=1
|
|
||||||
shift
|
|
||||||
;;
|
|
||||||
--source-checks)
|
|
||||||
RUN_SOURCE_CHECKS=1
|
|
||||||
shift
|
|
||||||
;;
|
|
||||||
--keep-docker)
|
|
||||||
KEEP_DOCKER=1
|
|
||||||
shift
|
|
||||||
;;
|
|
||||||
--keep-context)
|
|
||||||
KEEP_CONTEXT=1
|
|
||||||
shift
|
|
||||||
;;
|
|
||||||
-h|--help)
|
|
||||||
usage
|
|
||||||
exit 0
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
printf 'Unknown option: %s\n' "$1" >&2
|
|
||||||
usage >&2
|
|
||||||
exit 2
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
if [[ -d "${OUTPUT_DIR}" ]] && find "${OUTPUT_DIR}" -mindepth 1 -print -quit | grep -q .; then
|
|
||||||
printf 'Output directory must be empty: %s\n' "${OUTPUT_DIR}" >&2
|
|
||||||
exit 2
|
|
||||||
fi
|
|
||||||
mkdir -p "${OUTPUT_DIR}/work"
|
|
||||||
OUTPUT_DIR="$(cd -- "${OUTPUT_DIR}" && pwd -P)"
|
|
||||||
CLEANUP_READY=1
|
|
||||||
trap 'on_error "$?" "$LINENO" "$BASH_COMMAND"' ERR
|
|
||||||
APPSTORE_ROOT="$(cd -- "${APPSTORE_ROOT}" && pwd -P)"
|
|
||||||
split_csv "${VERSIONS_CSV}" VERSIONS
|
|
||||||
if [[ "${#VERSIONS[@]}" -eq 0 || -z "${VERSIONS[0]}" ]]; then
|
|
||||||
die "at least one OpenResty version is required"
|
|
||||||
fi
|
|
||||||
if [[ -n "${MODULES_CSV}" ]]; then
|
|
||||||
split_csv "${MODULES_CSV}" REQUESTED_MODULES
|
|
||||||
fi
|
|
||||||
|
|
||||||
preflight() {
|
|
||||||
[[ "$(uname -s)" == "Linux" ]] || die "this integration test must run on Linux"
|
|
||||||
require_command docker
|
|
||||||
require_command jq
|
|
||||||
require_command python3
|
|
||||||
require_command sha256sum
|
|
||||||
require_command sed
|
|
||||||
require_command awk
|
|
||||||
require_command tar
|
|
||||||
require_command file
|
|
||||||
require_command readelf
|
|
||||||
|
|
||||||
docker version >"${OUTPUT_DIR}/docker-version.txt" 2>&1
|
|
||||||
docker info >"${OUTPUT_DIR}/docker-info.txt" 2>&1
|
|
||||||
docker compose version >"${OUTPUT_DIR}/docker-compose-version.txt" 2>&1
|
|
||||||
uname -a >"${OUTPUT_DIR}/uname.txt"
|
|
||||||
cp /etc/os-release "${OUTPUT_DIR}/os-release.txt" 2>/dev/null || true
|
|
||||||
df -h >"${OUTPUT_DIR}/disk-free.txt"
|
|
||||||
free -h >"${OUTPUT_DIR}/memory.txt" 2>&1 || true
|
|
||||||
|
|
||||||
[[ -d "${APPSTORE_ROOT}/apps/openresty" ]] || die "invalid appstore root: ${APPSTORE_ROOT}"
|
|
||||||
log "Results: ${OUTPUT_DIR}"
|
|
||||||
log "Appstore: ${APPSTORE_ROOT}"
|
|
||||||
log "Docker architecture: $(docker info --format '{{.Architecture}}')"
|
|
||||||
}
|
|
||||||
|
|
||||||
run_source_checks() {
|
|
||||||
[[ "${RUN_SOURCE_CHECKS}" -eq 1 ]] || return 0
|
|
||||||
require_command go
|
|
||||||
log "Running Go dynamic-module tests"
|
|
||||||
run_logged "${OUTPUT_DIR}/go-test.log" bash -c \
|
|
||||||
"cd '${REPO_ROOT}/agent' && go test ./app/service -run 'NginxModule|DynamicModule' -count=1 -v"
|
|
||||||
log "Running go vet"
|
|
||||||
run_logged "${OUTPUT_DIR}/go-vet.log" bash -c "cd '${REPO_ROOT}/agent' && go vet ./..."
|
|
||||||
}
|
|
||||||
|
|
||||||
validate_template() {
|
|
||||||
local version="$1"
|
|
||||||
local app_dir="${APPSTORE_ROOT}/apps/openresty/${version}"
|
|
||||||
local catalog="${app_dir}/build/module.catalog.json"
|
|
||||||
|
|
||||||
[[ "${version}" =~ ^[a-zA-Z0-9._-]+$ ]] || die "unsafe version value: ${version}"
|
|
||||||
[[ -f "${app_dir}/build/Dockerfile.modules" ]] || die "missing Dockerfile.modules for ${version}"
|
|
||||||
[[ -f "${catalog}" ]] || die "missing module catalog for ${version}"
|
|
||||||
jq -e 'type == "array" and length > 0 and all(.[]; .name and .params and .provider == "local")' \
|
|
||||||
"${catalog}" >/dev/null
|
|
||||||
bash -n "${app_dir}/scripts/init.sh"
|
|
||||||
bash -n "${app_dir}/scripts/upgrade.sh"
|
|
||||||
grep -Fq 'conf/modules-enabled:/usr/local/openresty/nginx/conf/modules-enabled/:ro' "${app_dir}/docker-compose.yml"
|
|
||||||
grep -Fq './modules:/usr/local/openresty/nginx/modules/1panel/:ro' "${app_dir}/docker-compose.yml"
|
|
||||||
grep -Fq 'include /usr/local/openresty/nginx/conf/modules-enabled/*.conf;' "${app_dir}/conf/nginx.conf"
|
|
||||||
|
|
||||||
mkdir -p "${OUTPUT_DIR}/work/${version}/website/conf.d" "${OUTPUT_DIR}/work/${version}/website/stream.d"
|
|
||||||
(
|
|
||||||
cd "${app_dir}"
|
|
||||||
CONTAINER_NAME="openresty-template-check" \
|
|
||||||
WEBSITE_DIR="${OUTPUT_DIR}/work/${version}/website" \
|
|
||||||
PANEL_APP_PORT_HTTP=18080 \
|
|
||||||
docker compose config -q
|
|
||||||
) >"${OUTPUT_DIR}/work/${version}/compose-config.log" 2>&1
|
|
||||||
}
|
|
||||||
|
|
||||||
write_module_inputs() {
|
|
||||||
local catalog="$1" module="$2" context="$3" input_dir="$4"
|
|
||||||
local module_script params dynamic_params packages
|
|
||||||
|
|
||||||
module_script="$(jq -er --arg name "${module}" '.[] | select(.name == $name) | .script' "${catalog}")"
|
|
||||||
params="$(jq -er --arg name "${module}" '.[] | select(.name == $name) | .params' "${catalog}")"
|
|
||||||
packages="$(jq -er --arg name "${module}" '.[] | select(.name == $name) | (.packages // []) | join(" ")' "${catalog}")"
|
|
||||||
dynamic_params="${params//--add-module=/--add-dynamic-module=}"
|
|
||||||
|
|
||||||
mkdir -p "${input_dir}"
|
|
||||||
printf '%s\n' "${module_script}" >"${input_dir}/script.txt"
|
|
||||||
printf '%s\n' "${params}" >"${input_dir}/params.original.txt"
|
|
||||||
printf '%s\n' "${dynamic_params}" >"${input_dir}/params.dynamic.txt"
|
|
||||||
printf '%s\n' "${packages}" >"${input_dir}/packages.txt"
|
|
||||||
printf '#!/bin/bash\nset -e\n%s\n' "${module_script}" >"${context}/tmp/module-pre.sh"
|
|
||||||
|
|
||||||
python3 - "${dynamic_params}" >"${context}/tmp/module-config.args" <<'PY'
|
|
||||||
import shlex
|
|
||||||
import sys
|
|
||||||
|
|
||||||
params = sys.argv[1]
|
|
||||||
args = shlex.split(params, posix=True)
|
|
||||||
if not args:
|
|
||||||
raise SystemExit("dynamic module parameters are empty")
|
|
||||||
if not any(arg.startswith("--add-dynamic-module=") or "=dynamic" in arg for arg in args):
|
|
||||||
raise SystemExit("module does not declare a dynamic configure option")
|
|
||||||
for arg in args:
|
|
||||||
if not arg.startswith("--"):
|
|
||||||
raise SystemExit(f"unsupported configure argument: {arg!r}")
|
|
||||||
if any(char in arg for char in "\x00\r\n;&|<>"):
|
|
||||||
raise SystemExit(f"unsafe configure argument: {arg!r}")
|
|
||||||
print(arg)
|
|
||||||
PY
|
|
||||||
}
|
|
||||||
|
|
||||||
validate_load_directives() {
|
|
||||||
local image="$1" modules_root="$2" directives_file="$3" config_path="$4" log_file="$5"
|
|
||||||
{
|
|
||||||
cat "${directives_file}"
|
|
||||||
printf 'error_log stderr notice;\npid /tmp/nginx.pid;\nevents {}\nhttp {}\n'
|
|
||||||
} >"${config_path}"
|
|
||||||
|
|
||||||
run_logged "${log_file}" docker run --rm --network none \
|
|
||||||
-v "${modules_root}:/usr/local/openresty/nginx/modules/1panel:ro" \
|
|
||||||
-v "${config_path}:/tmp/1panel-module-test.conf:ro" \
|
|
||||||
--entrypoint /usr/local/openresty/nginx/sbin/nginx \
|
|
||||||
"${image}" -t -c /tmp/1panel-module-test.conf
|
|
||||||
}
|
|
||||||
|
|
||||||
build_module() {
|
|
||||||
local version="$1" module="$2" image="$3" app_dir="$4" version_dir="$5" context="$6" sequence="$7"
|
|
||||||
local catalog="${app_dir}/build/module.catalog.json"
|
|
||||||
local module_key module_dir input_dir tag build_log cid packages artifact relative checksum
|
|
||||||
local -a artifacts=()
|
|
||||||
module_key="$(safe_name "${module}")"
|
|
||||||
module_dir="${version_dir}/modules/${module_key}/${RUN_ID}"
|
|
||||||
input_dir="${version_dir}/inputs/${module_key}"
|
|
||||||
tag="1panel/openresty-module-test:${module_key}-$(safe_name "${version}")-${RUN_ID}"
|
|
||||||
tag="${tag:0:127}"
|
|
||||||
build_log="${version_dir}/logs/build-${module_key}.log"
|
|
||||||
|
|
||||||
log "[${version}] building ${module}"
|
|
||||||
write_module_inputs "${catalog}" "${module}" "${context}" "${input_dir}"
|
|
||||||
packages="$(cat "${input_dir}/packages.txt")"
|
|
||||||
|
|
||||||
local -a build_args=(
|
|
||||||
build --progress=plain --target module-output
|
|
||||||
-f "${context}/Dockerfile.modules"
|
|
||||||
-t "${tag}"
|
|
||||||
--build-arg "PANEL_OPENRESTY_VERSION=${version}"
|
|
||||||
--build-arg "RESTY_ADD_PACKAGE_BUILDDEPS=${packages}"
|
|
||||||
)
|
|
||||||
[[ "${NO_CACHE}" -eq 0 ]] || build_args+=(--no-cache)
|
|
||||||
[[ -z "${MIRROR}" ]] || build_args+=(--build-arg "CONTAINER_PACKAGE_URL=${MIRROR}")
|
|
||||||
build_args+=("${context}")
|
|
||||||
|
|
||||||
run_logged "${build_log}" docker "${build_args[@]}"
|
|
||||||
CREATED_IMAGES+=("${tag}")
|
|
||||||
|
|
||||||
cid="1panel-module-copy-${module_key}-${RUN_ID}"
|
|
||||||
cid="${cid:0:63}"
|
|
||||||
docker create --name "${cid}" "${tag}" /bin/true >"${input_dir}/container-id.txt"
|
|
||||||
CREATED_CONTAINERS+=("${cid}")
|
|
||||||
mkdir -p "${module_dir}"
|
|
||||||
docker cp "${cid}:/out/." "${module_dir}"
|
|
||||||
docker_rm_container "${cid}"
|
|
||||||
|
|
||||||
mapfile -t artifacts < <(find "${module_dir}" -maxdepth 1 -type f -name '*.so' -print | sort)
|
|
||||||
[[ "${#artifacts[@]}" -gt 0 ]] || die "${module} produced no top-level .so files"
|
|
||||||
|
|
||||||
: >"${input_dir}/load-directives.conf"
|
|
||||||
for artifact in "${artifacts[@]}"; do
|
|
||||||
relative="${artifact#${version_dir}/modules/}"
|
|
||||||
[[ "${relative}" =~ ^[a-zA-Z0-9_./+-]+\.so$ ]] || \
|
|
||||||
die "unsafe module artifact path: ${relative}"
|
|
||||||
checksum="$(sha256sum "${artifact}" | awk '{print $1}')"
|
|
||||||
printf '%s\t%s\t%s\t%s\n' "${module}" "${relative}" "${checksum}" "$(stat -c '%s' "${artifact}")" \
|
|
||||||
>>"${version_dir}/artifacts.tsv"
|
|
||||||
printf 'load_module /usr/local/openresty/nginx/modules/1panel/%s;\n' "${relative}" \
|
|
||||||
>>"${input_dir}/load-directives.conf"
|
|
||||||
file "${artifact}" >>"${input_dir}/file.txt"
|
|
||||||
readelf -d "${artifact}" >>"${input_dir}/readelf-dynamic.txt" 2>&1 || true
|
|
||||||
done
|
|
||||||
|
|
||||||
if ! validate_load_directives "${image}" "${version_dir}/modules" \
|
|
||||||
"${input_dir}/load-directives.conf" "${input_dir}/individual-nginx.conf" \
|
|
||||||
"${version_dir}/logs/load-${module_key}.log"; then
|
|
||||||
printf '%s\tindividual-load-failed\n' "${module}" >>"${version_dir}/status.tsv"
|
|
||||||
if [[ "${STRICT_INDIVIDUAL}" -eq 1 ]]; then
|
|
||||||
die "${module} failed individual load validation"
|
|
||||||
fi
|
|
||||||
log "[${version}] ${module} cannot load alone; combined validation will decide"
|
|
||||||
else
|
|
||||||
printf '%s\tindividual-load-ok\n' "${module}" >>"${version_dir}/status.tsv"
|
|
||||||
fi
|
|
||||||
|
|
||||||
cat "${input_dir}/load-directives.conf" >>"${version_dir}/combined-load-directives.conf"
|
|
||||||
cp "${input_dir}/load-directives.conf" \
|
|
||||||
"${version_dir}/ordered-configs/$(printf '%04d' "${sequence}")-${module_key}.conf"
|
|
||||||
}
|
|
||||||
|
|
||||||
runtime_reload_test() {
|
|
||||||
local version="$1" image="$2" version_dir="$3"
|
|
||||||
local runtime_dir="${version_dir}/runtime" container="1panel-module-runtime-$(safe_name "${version}")-${RUN_ID}"
|
|
||||||
local -a module_configs=()
|
|
||||||
container="${container:0:63}"
|
|
||||||
mkdir -p "${runtime_dir}/modules-enabled"
|
|
||||||
|
|
||||||
cat >"${runtime_dir}/nginx.conf" <<'EOF'
|
|
||||||
error_log stderr notice;
|
|
||||||
pid /tmp/nginx.pid;
|
|
||||||
include /tmp/modules-enabled/*.conf;
|
|
||||||
events {}
|
|
||||||
http {}
|
|
||||||
EOF
|
|
||||||
printf '# empty initial module set\n' >"${runtime_dir}/modules-enabled/0000-empty.conf"
|
|
||||||
|
|
||||||
mapfile -t module_configs < <(find "${version_dir}/ordered-configs" -type f -name '*.conf' -print | sort)
|
|
||||||
[[ "${#module_configs[@]}" -gt 0 ]] || die "no module configs available for runtime test"
|
|
||||||
|
|
||||||
log "[${version}] starting runtime reload test"
|
|
||||||
docker run -d --name "${container}" --network none \
|
|
||||||
-v "${version_dir}/modules:/usr/local/openresty/nginx/modules/1panel:ro" \
|
|
||||||
-v "${runtime_dir}/modules-enabled:/tmp/modules-enabled:ro" \
|
|
||||||
-v "${runtime_dir}/nginx.conf:/tmp/1panel-runtime-nginx.conf:ro" \
|
|
||||||
--entrypoint /usr/local/openresty/nginx/sbin/nginx \
|
|
||||||
"${image}" -c /tmp/1panel-runtime-nginx.conf -g 'daemon off;' \
|
|
||||||
>"${runtime_dir}/container-id.txt"
|
|
||||||
CREATED_CONTAINERS+=("${container}")
|
|
||||||
|
|
||||||
local attempt
|
|
||||||
for ((attempt = 1; attempt <= 20; attempt++)); do
|
|
||||||
if [[ "$(docker inspect --format '{{.State.Running}}' "${container}" 2>/dev/null || true)" == "true" ]]; then
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
sleep 1
|
|
||||||
done
|
|
||||||
if [[ "$(docker inspect --format '{{.State.Running}}' "${container}" 2>/dev/null || true)" != "true" ]]; then
|
|
||||||
docker logs "${container}" >"${runtime_dir}/startup-failure.log" 2>&1 || true
|
|
||||||
die "runtime container failed to start"
|
|
||||||
fi
|
|
||||||
|
|
||||||
local config
|
|
||||||
for config in "${module_configs[@]}"; do
|
|
||||||
cp "${config}" "${runtime_dir}/modules-enabled/$(basename -- "${config}")"
|
|
||||||
done
|
|
||||||
run_logged "${runtime_dir}/nginx-test.log" docker exec "${container}" \
|
|
||||||
/usr/local/openresty/nginx/sbin/nginx -t -c /tmp/1panel-runtime-nginx.conf
|
|
||||||
run_logged "${runtime_dir}/nginx-reload.log" docker exec "${container}" \
|
|
||||||
/usr/local/openresty/nginx/sbin/nginx -s reload -c /tmp/1panel-runtime-nginx.conf
|
|
||||||
|
|
||||||
printf 'load_module /usr/local/openresty/nginx/modules/1panel/not-found.so;\n' \
|
|
||||||
>"${runtime_dir}/modules-enabled/9999-invalid.conf"
|
|
||||||
if docker exec "${container}" /usr/local/openresty/nginx/sbin/nginx \
|
|
||||||
-t -c /tmp/1panel-runtime-nginx.conf >"${runtime_dir}/expected-invalid.log" 2>&1; then
|
|
||||||
die "nginx -t unexpectedly accepted a missing module"
|
|
||||||
fi
|
|
||||||
rm -f "${runtime_dir}/modules-enabled/9999-invalid.conf"
|
|
||||||
run_logged "${runtime_dir}/rollback-nginx-test.log" docker exec "${container}" \
|
|
||||||
/usr/local/openresty/nginx/sbin/nginx -t -c /tmp/1panel-runtime-nginx.conf
|
|
||||||
[[ "$(docker inspect --format '{{.State.Running}}' "${container}")" == "true" ]] || \
|
|
||||||
die "runtime container stopped during rollback test"
|
|
||||||
|
|
||||||
docker logs "${container}" >"${runtime_dir}/container.log" 2>&1 || true
|
|
||||||
docker exec "${container}" /bin/sh -c \
|
|
||||||
'for f in /usr/local/openresty/nginx/modules/1panel/*/*/*.so; do echo "### $f"; ldd "$f" || true; done' \
|
|
||||||
>"${runtime_dir}/ldd.txt" 2>&1 || true
|
|
||||||
docker_rm_container "${container}"
|
|
||||||
}
|
|
||||||
|
|
||||||
test_version() {
|
|
||||||
local version="$1"
|
|
||||||
local app_dir="${APPSTORE_ROOT}/apps/openresty/${version}"
|
|
||||||
local version_dir="${OUTPUT_DIR}/work/${version}"
|
|
||||||
local context="${version_dir}/context"
|
|
||||||
local catalog="${app_dir}/build/module.catalog.json"
|
|
||||||
local image="1panel/openresty:${version}"
|
|
||||||
local -a modules=()
|
|
||||||
|
|
||||||
validate_template "${version}"
|
|
||||||
mkdir -p "${version_dir}/logs" "${version_dir}/inputs" "${version_dir}/modules" \
|
|
||||||
"${version_dir}/ordered-configs"
|
|
||||||
cp -a "${app_dir}/build" "${context}"
|
|
||||||
: >"${version_dir}/artifacts.tsv"
|
|
||||||
: >"${version_dir}/status.tsv"
|
|
||||||
: >"${version_dir}/combined-load-directives.conf"
|
|
||||||
|
|
||||||
if [[ "${#REQUESTED_MODULES[@]}" -gt 0 ]]; then
|
|
||||||
modules=("${REQUESTED_MODULES[@]}")
|
|
||||||
else
|
|
||||||
mapfile -t modules < <(jq -r 'sort_by([.loadOrder // 50, .name])[] | .name' "${catalog}")
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "${SKIP_PULL}" -eq 0 ]]; then
|
|
||||||
log "[${version}] pulling ${image}"
|
|
||||||
run_logged "${version_dir}/logs/image-pull.log" docker pull "${image}"
|
|
||||||
fi
|
|
||||||
docker image inspect "${image}" >"${version_dir}/image-inspect.json"
|
|
||||||
run_logged "${version_dir}/logs/nginx-version.log" docker run --rm \
|
|
||||||
--entrypoint /usr/local/openresty/nginx/sbin/nginx "${image}" -V
|
|
||||||
sha256sum "${app_dir}/build/Dockerfile.modules" >"${version_dir}/builder.sha256"
|
|
||||||
find "${app_dir}/build/tmp" -maxdepth 1 -type f -print0 | sort -z | xargs -0 sha256sum \
|
|
||||||
>"${version_dir}/build-inputs.sha256"
|
|
||||||
|
|
||||||
local module sequence=0
|
|
||||||
for module in "${modules[@]}"; do
|
|
||||||
sequence=$((sequence + 1))
|
|
||||||
jq -e --arg name "${module}" 'any(.[]; .name == $name)' "${catalog}" >/dev/null || \
|
|
||||||
die "module ${module} is not present in ${version} catalog"
|
|
||||||
build_module "${version}" "${module}" "${image}" "${app_dir}" "${version_dir}" "${context}" "${sequence}"
|
|
||||||
done
|
|
||||||
|
|
||||||
log "[${version}] validating the combined load order"
|
|
||||||
validate_load_directives "${image}" "${version_dir}/modules" \
|
|
||||||
"${version_dir}/combined-load-directives.conf" "${version_dir}/combined-nginx.conf" \
|
|
||||||
"${version_dir}/logs/load-combined.log"
|
|
||||||
runtime_reload_test "${version}" "${image}" "${version_dir}"
|
|
||||||
printf '%s\tPASS\n' "${version}" >>"${OUTPUT_DIR}/summary.tsv"
|
|
||||||
log "[${version}] PASS"
|
|
||||||
}
|
|
||||||
|
|
||||||
main() {
|
|
||||||
preflight
|
|
||||||
run_source_checks
|
|
||||||
printf 'version\tresult\n' >"${OUTPUT_DIR}/summary.tsv"
|
|
||||||
local version
|
|
||||||
for version in "${VERSIONS[@]}"; do
|
|
||||||
test_version "${version}"
|
|
||||||
done
|
|
||||||
log "All requested OpenResty module tests passed"
|
|
||||||
log "Summary: ${OUTPUT_DIR}/summary.tsv"
|
|
||||||
}
|
|
||||||
|
|
||||||
main "$@"
|
|
||||||
Reference in New Issue
Block a user