Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions agent/app/api/v2/runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -493,3 +493,45 @@ func (b *BaseApi) OperateSupervisorProcessFile(c *gin.Context) {
}
helper.SuccessWithData(c, res)
}

// @Tags Runtime
// @Summary Update PHP container config
// @Accept json
// @Param request body request.PHPContainerUpdate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /runtimes/php/container/update [post]
func (b *BaseApi) UpdatePHPContainer(c *gin.Context) {
var req request.PHPContainerConfig
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := runtimeService.UpdatePHPContainer(req); err != nil {
helper.InternalServer(c, err)
return
}
helper.Success(c)
}

// @Tags Runtime
// @Summary Get PHP container config
// @Accept json
// @Param id path integer true "request"
// @Success 200 {object} response.PHPContainerConfig
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /runtimes/php/container/:id [get]
func (b *BaseApi) GetPHPContainerConfig(c *gin.Context) {
id, err := helper.GetParamID(c)
if err != nil {
helper.BadRequest(c, err)
return
}
data, err := runtimeService.GetPHPContainerConfig(id)
if err != nil {
helper.InternalServer(c, err)
return
}
helper.SuccessWithData(c, data)
}
8 changes: 8 additions & 0 deletions agent/app/dto/request/runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,3 +123,11 @@ type PHPSupervisorProcessFileReq struct {
ID uint `json:"id" validate:"required"`
SupervisorProcessFileReq
}

type PHPContainerConfig struct {
ID uint `json:"id" validate:"required"`
ContainerName string `json:"containerName"`
ExposedPorts []ExposedPort `json:"exposedPorts"`
Environments []Environment `json:"environments"`
Volumes []Volume `json:"volumes"`
}
6 changes: 3 additions & 3 deletions agent/app/model/website_acme_account.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ type WebsiteAcmeAccount struct {
URL string `gorm:"not null" json:"url"`
PrivateKey string `gorm:"not null" json:"-"`
Type string `gorm:"not null;default:letsencrypt" json:"type"`
EabKid string `gorm:"default:null;" json:"eabKid"`
EabHmacKey string `gorm:"default:null" json:"eabHmacKey"`
EabKid string `json:"eabKid"`
EabHmacKey string `json:"eabHmacKey"`
KeyType string `gorm:"not null;default:2048" json:"keyType"`
UseProxy bool `gorm:"default:false" json:"useProxy"`
CaDirURL string `gorm:"default:null" json:"caDirURL"`
CaDirURL string `json:"caDirURL"`
}

func (w WebsiteAcmeAccount) TableName() string {
Expand Down
168 changes: 99 additions & 69 deletions agent/app/service/runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,17 @@ type IRuntimeService interface {
GetPHPExtensions(runtimeID uint) (response.PHPExtensionRes, error)
InstallPHPExtension(req request.PHPExtensionInstallReq) error
UnInstallPHPExtension(req request.PHPExtensionInstallReq) error

GetPHPConfig(id uint) (*response.PHPConfig, error)
UpdatePHPConfig(req request.PHPConfigUpdate) (err error)
UpdatePHPConfigFile(req request.PHPFileUpdate) error
GetPHPConfigFile(req request.PHPFileReq) (*response.FileInfo, error)
UpdateFPMConfig(req request.FPMConfig) error
GetFPMConfig(id uint) (*request.FPMConfig, error)

UpdatePHPContainer(req request.PHPContainerConfig) error
GetPHPContainerConfig(id uint) (*request.PHPContainerConfig, error)

GetSupervisorProcess(id uint) ([]response.SupervisorProcessConfig, error)
OperateSupervisorProcess(req request.PHPSupervisorProcessConfig) error
OperateSupervisorProcessFile(req request.PHPSupervisorProcessFileReq) (string, error)
Expand Down Expand Up @@ -383,77 +387,9 @@ func (r *RuntimeService) Get(id uint) (*response.RuntimeDTO, error) {
}
res.AppParams = appParams
case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo, constant.RuntimePython, constant.RuntimeDotNet:
res.Params = make(map[string]interface{})
envs, err := gotenv.Unmarshal(runtime.Env)
if err != nil {
if err := handleRuntimeDTO(&res, *runtime); err != nil {
return nil, err
}
for k, v := range envs {
if strings.Contains(k, "CONTAINER_PORT") || strings.Contains(k, "HOST_PORT") {
if strings.Contains(k, "CONTAINER_PORT") {
r := regexp.MustCompile(`_(\d+)$`)
matches := r.FindStringSubmatch(k)
containerPort, err := strconv.Atoi(v)
if err != nil {
return nil, err
}
hostPort, err := strconv.Atoi(envs[fmt.Sprintf("HOST_PORT_%s", matches[1])])
if err != nil {
return nil, err
}
hostIP := envs[fmt.Sprintf("HOST_IP_%s", matches[1])]
if hostIP == "" {
hostIP = "0.0.0.0"
}
res.ExposedPorts = append(res.ExposedPorts, request.ExposedPort{
ContainerPort: containerPort,
HostPort: hostPort,
HostIP: hostIP,
})
}
} else {
res.Params[k] = v
}
}
if v, ok := envs["CONTAINER_PACKAGE_URL"]; ok {
res.Source = v
}
composeByte, err := files.NewFileOp().GetContent(runtime.GetComposePath())
if err != nil {
return nil, err
}
res.Environments, err = getDockerComposeEnvironments(composeByte)
if err != nil {
return nil, err
}
volumes, err := getDockerComposeVolumes(composeByte)
if err != nil {
return nil, err
}

defaultVolumes := make(map[string]string)
switch runtime.Type {
case constant.RuntimeNode:
defaultVolumes = constant.RuntimeDefaultVolumes
case constant.RuntimeJava:
defaultVolumes = constant.RuntimeDefaultVolumes
case constant.RuntimeGo:
defaultVolumes = constant.GoDefaultVolumes
case constant.RuntimePython:
defaultVolumes = constant.RuntimeDefaultVolumes
}
for _, volume := range volumes {
exist := false
for key, value := range defaultVolumes {
if key == volume.Source && value == volume.Target {
exist = true
break
}
}
if !exist {
res.Volumes = append(res.Volumes, volume)
}
}
}

return &res, nil
Expand Down Expand Up @@ -1080,6 +1016,100 @@ func (r *RuntimeService) GetFPMConfig(id uint) (*request.FPMConfig, error) {
return res, nil
}

func (r *RuntimeService) UpdatePHPContainer(req request.PHPContainerConfig) error {
runtime, err := runtimeRepo.GetFirst(context.Background(), repo.WithByID(req.ID))
if err != nil {
return err
}
var (
hostPorts []string
composeContent []byte
)
for _, export := range req.ExposedPorts {
hostPorts = append(hostPorts, strconv.Itoa(export.HostPort))
if err = checkRuntimePortExist(export.HostPort, false, runtime.ID); err != nil {
return err
}
}
if req.ContainerName != "" && req.ContainerName != getRuntimeEnv(runtime.Env, "CONTAINER_NAME") {
if err := checkContainerName(req.ContainerName); err != nil {
return err
}
runtime.ContainerName = req.ContainerName
}
fileOp := files.NewFileOp()
projectDir := path.Join(global.Dir.RuntimeDir, runtime.Type, runtime.Name)
composeContent, err = fileOp.GetContent(path.Join(projectDir, "docker-compose.yml"))
if err != nil {
return err
}
envPath := path.Join(projectDir, ".env")
if !fileOp.Stat(envPath) {
_ = fileOp.CreateFile(envPath)
}
envs, err := gotenv.Read(envPath)
if err != nil {
return err
}
for k := range envs {
if strings.HasPrefix(k, "CONTAINER_PORT_") || strings.HasPrefix(k, "HOST_PORT_") || strings.HasPrefix(k, "HOST_IP_") || strings.Contains(k, "APP_PORT") {
delete(envs, k)
}
}
create := request.RuntimeCreate{
Image: runtime.Image,
Type: runtime.Type,
Params: make(map[string]interface{}),
NodeConfig: request.NodeConfig{
ExposedPorts: req.ExposedPorts,
Environments: req.Environments,
Volumes: req.Volumes,
},
}
composeContent, err = handleCompose(envs, composeContent, create, projectDir)
if err != nil {
return err
}
newMap := make(map[string]string)
handleMap(create.Params, newMap)
for k, v := range newMap {
envs[k] = v
}
envStr, err := gotenv.Marshal(envs)
if err != nil {
return err
}
if err = gotenv.Write(envs, envPath); err != nil {
return err
}
envContent := []byte(envStr)
runtime.Env = string(envContent)
runtime.DockerCompose = string(composeContent)
runtime.Status = constant.StatusReCreating
_ = runtimeRepo.Save(runtime)
go reCreateRuntime(runtime)
return nil
}

func (r *RuntimeService) GetPHPContainerConfig(id uint) (*request.PHPContainerConfig, error) {
runtime, err := runtimeRepo.GetFirst(context.Background(), repo.WithByID(id))
if err != nil {
return nil, err
}
runtimeDTO := response.NewRuntimeDTO(*runtime)
if err := handleRuntimeDTO(&runtimeDTO, *runtime); err != nil {
return nil, err
}
res := &request.PHPContainerConfig{
ID: runtime.ID,
ContainerName: runtime.ContainerName,
ExposedPorts: runtimeDTO.ExposedPorts,
Environments: runtimeDTO.Environments,
Volumes: runtimeDTO.Volumes,
}
return res, nil
}

func (r *RuntimeService) GetSupervisorProcess(id uint) ([]response.SupervisorProcessConfig, error) {
runtime, err := runtimeRepo.GetFirst(context.Background(), repo.WithByID(id))
if err != nil {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The provided code changes appear to focus on two main areas:

Changes in Get Method

  • No significant changes to the existing logic.

Changes in Methods for PHP-related Operations

  1. Addition of New API Endpoint:

    UpdatePHPContainer(req request.PHPContainerConfig) error
    • This method is supposed to update container configuration (req) for a given runtime ID (req.ID). It includes checking port availability, environment variable updates, volume handling, and updating the Docker Compose file.
  2. Modification of Existing Logic:

    • The UpdatePHPContainer method creates a new instance of create.RuntimeCreate, sets its fields based on req, handles environments and volumes using helper functions like handleRuntimeDTO and getDockerComposeEnvironments, writes updated environment variables back to .env file, and saves changes to the repository after re-starting the runtime with reCreateRuntime.
  3. Return Type of Updated Method:

    func (r *RuntimeService) UpdatePHPContainer(req request.PHPContainerConfig) error {
        ...
        return nil
    }
    
    func (r *RuntimeService) GetPHPContainerConfig(id uint) (*request.PHPContainerConfig, error) {
        ...
        return res, nil
    }

Potential Issues

  1. Concurrency Management: Ensure that concurrent calls to UpdatePHPContainer do not lead to errors, such as conflicting port assignments or duplicate environment definitions.

  2. Resource Leaks: Implement checks and resource management around file operations and data manipulation to prevent memory leaks.

  3. Error Handling and Recovery: Add better error handling and retry mechanisms when dealing with external systems like Docker API and ensuring robustness against transient failures.

  4. Scalability: Evaluate if this approach scales well with high number of concurrent requests and large projects without causing performance bottlenecks.

  5. Testing: Ensure comprehensive testing for edge cases, particularly related to container name conflicts and invalid port configurations to catch bugs early.

  6. Documentation and Validation: Provide clear documentation for all new endpoints and input parameters to ensure ease of use and reduce confusion among users.

Overall, these changes seem reasonable within the context but require careful evaluation of specific use case scenarios and implementation details to fully optimize and validate their effectiveness.

Expand Down
84 changes: 84 additions & 0 deletions agent/app/service/runtime_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import (
"os/exec"
"path"
"path/filepath"
"regexp"
"strconv"
"strings"
"time"

Expand Down Expand Up @@ -564,7 +566,14 @@ func handleCompose(env gotenv.Env, composeContent []byte, create request.Runtime
create.Params[hostPortStr] = port.HostPort
create.Params[hostIPStr] = port.HostIP
}
if create.Type == constant.RuntimePHP {
ports = append(ports, fmt.Sprintf("127.0.0.1:${PANEL_APP_PORT_HTTP}:9000"))
}
serviceValue["ports"] = ports
} else {
if create.Type == constant.RuntimePHP {
serviceValue["ports"] = []interface{}{fmt.Sprintf("127.0.0.1:${PANEL_APP_PORT_HTTP}:9000")}
}
}
var environments []interface{}
for _, e := range create.Environments {
Expand All @@ -581,6 +590,8 @@ func handleCompose(env gotenv.Env, composeContent []byte, create request.Runtime
defaultVolumes = constant.RuntimeDefaultVolumes
case constant.RuntimeGo:
defaultVolumes = constant.GoDefaultVolumes
case constant.RuntimePHP:
defaultVolumes = constant.PHPDefaultVolumes
}
for k, v := range defaultVolumes {
volumes = append(volumes, fmt.Sprintf("%s:%s", k, v))
Expand Down Expand Up @@ -875,3 +886,76 @@ func RestartPHPRuntime() {
}()
}
}

func handleRuntimeDTO(res *response.RuntimeDTO, runtime model.Runtime) error {
res.Params = make(map[string]interface{})
envs, err := gotenv.Unmarshal(runtime.Env)
if err != nil {
return err
}
for k, v := range envs {
if strings.Contains(k, "CONTAINER_PORT") || strings.Contains(k, "HOST_PORT") {
if strings.Contains(k, "CONTAINER_PORT") {
r := regexp.MustCompile(`_(\d+)$`)
matches := r.FindStringSubmatch(k)
containerPort, err := strconv.Atoi(v)
if err != nil {
return err
}
hostPort, err := strconv.Atoi(envs[fmt.Sprintf("HOST_PORT_%s", matches[1])])
if err != nil {
return err
}
hostIP := envs[fmt.Sprintf("HOST_IP_%s", matches[1])]
if hostIP == "" {
hostIP = "0.0.0.0"
}
res.ExposedPorts = append(res.ExposedPorts, request.ExposedPort{
ContainerPort: containerPort,
HostPort: hostPort,
HostIP: hostIP,
})
}
} else {
res.Params[k] = v
}
}
if v, ok := envs["CONTAINER_PACKAGE_URL"]; ok {
res.Source = v
}
composeByte, err := files.NewFileOp().GetContent(runtime.GetComposePath())
if err != nil {
return err
}
res.Environments, err = getDockerComposeEnvironments(composeByte)
if err != nil {
return err
}
volumes, err := getDockerComposeVolumes(composeByte)
if err != nil {
return err
}

defaultVolumes := make(map[string]string)
switch runtime.Type {
case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimePython, constant.RuntimeDotNet:
defaultVolumes = constant.RuntimeDefaultVolumes
case constant.RuntimeGo:
defaultVolumes = constant.GoDefaultVolumes
case constant.RuntimePHP:
defaultVolumes = constant.PHPDefaultVolumes
}
for _, volume := range volumes {
exist := false
for key, value := range defaultVolumes {
if key == volume.Source && value == volume.Target {
exist = true
break
}
}
if !exist {
res.Volumes = append(res.Volumes, volume)
}
}
return nil
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are several notable changes and improvements in the provided code:

  1. Added Regexp and strconv Import Statements: These were added to support parsing and converting strings in handleRuntimeDTO function.

  2. Handling Different Runtime Types: The defaultVolumes map is now initialized based on the runtime type (constant.RuntimePHP) instead of using a switch statement that was previously incorrect due to an extra colon in the condition (incorrectly matching RUNTIME_GO). This ensures that the correct mapping of volumes is applied.

  3. Error Handling in GetDockerComposeEnvironments: Added error checking when retrieving Docker Compose environment variables from file content.

  4. Cleanup Code Formatting: Improved formatting around conditional checks inside functions like getDockerComposeEnvironments.

Optimization Suggestions:

  • Consider moving repetitive logic into helper functions if it becomes complex.
  • Ensure that all paths and filenames refer to actual constants defined elsewhere and avoid hardcoded values.
  • Review the use of concurrency patterns for long-running tasks and ensure they don't cause unnecessary overhead.

Overall, these changes make the code more robust and maintainable while ensuring consistent behavior across different execution contexts.

14 changes: 14 additions & 0 deletions agent/constant/runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,17 @@ var RuntimeDefaultVolumes = map[string]string{
"./run.sh": "/run.sh",
"./.env": "/.env",
}

var PHPDefaultVolumes = map[string]string{
"${PANEL_WEBSITE_DIR}": "/www/",
"./conf": "/usr/local/etc/php",
"./conf/conf.d": "/usr/local/etc/php/conf.d",
"./conf/php-fpm.conf": "/usr/local/etc/php-fpm.d/www.conf",
"./log": "/var/log/php",
"./extensions": "${EXTENSION_DIR}",
"./supervisor/supervisord.conf": "/etc/supervisord.conf",
"./supervisor/supervisor.d/php-fpm.ini": "/etc/supervisor.d/php-fpm.ini",
"./supervisor/supervisor.d": "/etc/supervisor.d",
"./supervisor/log": "/var/log/supervisor",
"./composer": "/tmp/composer",
}
Loading
Loading