cmd/php/supervisor/main.go (177 lines of code) (raw):
// Implements php/supervisor buildpack.
// The supervisor buildpack installs the config needed for PHP runtime with supervisor.
package main
import (
"fmt"
"os"
"os/user"
"path/filepath"
"text/template"
"github.com/GoogleCloudPlatform/buildpacks/pkg/appyaml"
"github.com/GoogleCloudPlatform/buildpacks/pkg/flex"
gcp "github.com/GoogleCloudPlatform/buildpacks/pkg/gcpbuildpack"
"github.com/GoogleCloudPlatform/buildpacks/pkg/nginx"
"github.com/GoogleCloudPlatform/buildpacks/pkg/webconfig"
)
const (
// nginx
defaultFrontController = "index.php"
defaultNginxBinary = "nginx"
defaultNginxPort = 8080
defaultRoot = "/workspace"
defaultNginxConfInclude = "nginx-app.conf"
defaultNginxConfHTTPInclude = "nginx-http.conf"
defaultNginxConf = "nginx.conf"
nginxLog = "nginx.log"
defaultAddress = "127.0.0.1:9000"
// php-fpm
defaultPHPFPMConfOverride = "php-fpm.conf"
defaultDynamicWorkers = false
defaultFPMBinary = "php-fpm"
defaultFPMWorkers = 2
phpFpmPid = "php-fpm.pid"
defaultPHPIni = "php.ini"
)
func main() {
gcp.Main(detectFn, buildFn)
}
func detectFn(ctx *gcp.Context) (gcp.DetectResult, error) {
if flex.NeedsSupervisorPackage(ctx) {
return gcp.OptIn("supervisor package is required"), nil
}
return gcp.OptOut("supervisor package is not required"), nil
}
func buildFn(ctx *gcp.Context) error {
l, err := ctx.Layer("supervisor", gcp.CacheLayer, gcp.LaunchLayerUnlessSkipRuntimeLaunch, gcp.BuildLayer)
if err != nil {
return err
}
l.LaunchEnvironment.Default("APP_DIR", defaultRoot)
runtimeConfig, err := appyaml.PhpConfiguration(ctx.ApplicationRoot())
if err != nil {
return err
}
if err = flex.InstallSupervisor(ctx, l); err != nil {
return err
}
overrides := webconfig.OverriddenProperties(ctx, runtimeConfig)
webconfig.SetEnvVariables(l, overrides)
fpmConfFile, err := writeFpmConfig(l.Path, overrides)
if err != nil {
return err
}
supervisorFiles, err := flex.SupervisorConfFiles(ctx, runtimeConfig, ctx.ApplicationRoot())
if err != nil {
return err
}
nginxPath := filepath.Join(l.Path, defaultNginxConf)
// write the nginx configurations if they do not provide an override.
if !overrides.NginxConfOverride {
// nginx server section
nginxServerConf, err := writeNginxServerConfig(l.Path, overrides)
if err != nil {
return err
}
// the nginx file to start the process.
supervisorNginxConf := supervisorNginxConfig(nginxServerConf, overrides)
if _, err = writeTemplateConfigToPath(nginxPath, flex.NginxConfTemplate, supervisorNginxConf); err != nil {
return err
}
} else {
nginxPath = overrides.NginxConfOverrideFileName
}
supervisorPath, err := supervisorLocation(supervisorFiles, nginxPath, fpmConfFile.Name(), l.Path)
if err != nil {
return err
}
cmd := []string{
"supervisord", "-c", supervisorPath,
}
ctx.AddProcess(gcp.WebProcess, cmd, gcp.AsDefaultProcess())
return nil
}
func supervisorLocation(supervisorFiles flex.SupervisorFiles, nginxPath, fpmConfFile, layer string) (string, error) {
if supervisorFiles.SupervisorConfExists { // supervisord.conf overwritten
return supervisorFiles.SupervisorConf, nil
}
// Generate the supervisord.conf otherwise.
supervisorConf := supervisorConfig(fpmConfFile, nginxPath, supervisorFiles)
supervisorFile, err := writeTemplateConfigToPath(filepath.Join(layer, "supervisord.conf"), flex.SupervisorTemplate, supervisorConf)
if err != nil {
return "", err
}
return supervisorFile.Name(), nil
}
func supervisorNginxConfig(nginxServerPath string, overrides webconfig.OverrideProperties) flex.NginxConfig {
conf := flex.NginxConfig{
MimeTypesPath: filepath.Join("/layers/google.utils.nginx/nginx", "conf/mime.types"),
NginxServerConfPath: nginxServerPath,
}
if overrides.NginxHTTPInclude {
conf.NginxConfHTTPInclude = overrides.NginxHTTPIncludeFileName
}
return conf
}
func writeTemplateConfigToPath(path string, template *template.Template, config any) (*os.File, error) {
file, err := os.Create(path)
if err != nil {
return nil, err
}
defer file.Close()
if err := template.Execute(file, config); err != nil {
return nil, fmt.Errorf("writing template config file: %w", err)
}
return file, nil
}
func writeNginxServerConfig(path string, overrides webconfig.OverrideProperties) (string, error) {
nginxConf := nginxConfig(path, overrides)
nginxConfFile, err := nginx.WriteNginxConfigToPath(path, nginxConf)
if err != nil {
return "", err
}
defer nginxConfFile.Close()
return nginxConfFile.Name(), nil
}
func nginxConfig(layer string, overrides webconfig.OverrideProperties) nginx.Config {
frontController := defaultFrontController
if overrides.FrontController != "" {
frontController = overrides.FrontController
}
nginx := nginx.Config{
Port: defaultNginxPort,
FrontControllerScript: frontController,
Root: filepath.Join(defaultRoot, overrides.DocumentRoot),
AppListenAddress: defaultAddress,
}
if overrides.NginxServerConfInclude {
nginx.NginxConfInclude = overrides.NginxServerConfIncludeFileName
}
return nginx
}
func supervisorConfig(fpmPath, nginxPath string, supervisorFiles flex.SupervisorFiles) flex.SupervisorConfig {
supervisorConf := flex.SupervisorConfig{
PHPFPMConfPath: fpmPath,
NginxConfPath: nginxPath,
}
if supervisorFiles.AddSupervisorConfExists {
supervisorConf.SupervisorIncludeConfPath = filepath.Join(defaultRoot, supervisorFiles.AddSupervisorConf)
}
return supervisorConf
}
func fpmConfig(layer string, overrides webconfig.OverrideProperties) (nginx.FPMConfig, error) {
user, err := user.Current()
if err != nil {
return nginx.FPMConfig{}, fmt.Errorf("getting current user: %w", err)
}
fpm := nginx.FPMConfig{
PidPath: filepath.Join(layer, phpFpmPid),
NumWorkers: defaultFPMWorkers,
ListenAddress: defaultAddress,
DynamicWorkers: defaultDynamicWorkers,
Username: user.Username,
AddNoDecorateWorkers: true,
}
if overrides.PHPFPMOverride {
fpm.ConfOverride = overrides.PHPFPMOverrideFileName
}
return fpm, nil
}
func writeFpmConfig(path string, overrides webconfig.OverrideProperties) (*os.File, error) {
conf, err := fpmConfig(path, overrides)
if err != nil {
return nil, err
}
return nginx.WriteFpmConfigToPath(path, conf)
}