lifecycle/launch/process.go

163 lines
5.0 KiB
Go

package launch
import (
"fmt"
"github.com/pkg/errors"
"github.com/buildpacks/lifecycle/api"
)
// ProcessFor creates a process from container cmd
//
// If the Platform API if 0.4 or greater and DefaultProcess is set:
// * The default process is returned with `cmd` appended to the process args
// If the Platform API is less than 0.4
// * If there is exactly one argument and it matches a process type, it returns that process.
// * If cmd is empty, it returns the default process
// Else
// * it constructs a new process from cmd
// * If the first element in cmd is `cmd` the process shall be direct
func (l *Launcher) ProcessFor(cmd []string) (Process, error) {
if l.DefaultProcessType == "" {
process, err := l.userProvidedProcess(cmd)
if err != nil {
return Process{}, err
}
return process, nil
}
process, ok := l.findProcessType(l.DefaultProcessType)
if !ok {
return Process{}, fmt.Errorf("process type %s was not found", l.DefaultProcessType)
}
if l.PlatformAPI.LessThan("0.10") {
return l.handleUserArgsPlatformLessThan010(process, cmd)
}
return l.handleUserArgs(process, cmd)
}
func (l *Launcher) handleUserArgsPlatformLessThan010(process Process, userArgs []string) (Process, error) {
process.Args = append(process.Args, userArgs...)
return process, nil
}
func (l *Launcher) handleUserArgs(process Process, userArgs []string) (Process, error) {
switch {
case len(process.Command.Entries) > 1: // definitely newer buildpack
overridableArgs := process.Args
process.Args = process.Command.Entries[1:] // set always-provided args
process.Command.Entries = []string{process.Command.Entries[0]} // when exec'ing the process we always expect Command to have just one entry
if len(userArgs) > 0 {
process.Args = append(process.Args, userArgs...)
} else {
process.Args = append(process.Args, overridableArgs...)
}
case len(userArgs) == 0:
// nothing to do, we just provide whatever the original process args were
default:
// we have user-provided args, and we need to check the buildpack API to know how to handle them
bp, err := l.buildpackForProcess(process)
if err != nil {
return Process{}, err
}
if api.MustParse(bp.API).LessThan("0.9") {
process.Args = append(process.Args, userArgs...) // user-provided args are appended to process args
} else {
process.Args = userArgs // user-provided args replace process args
}
}
return process, nil
}
func (l *Launcher) buildpackForProcess(process Process) (Buildpack, error) {
for _, bp := range l.Buildpacks {
if bp.ID == process.BuildpackID {
return bp, nil
}
}
return Buildpack{}, fmt.Errorf("failed to find buildpack for process %s with buildpack ID %s", process.Type, process.BuildpackID)
}
func (l *Launcher) processForLegacy(cmd []string) (Process, error) {
if len(cmd) == 0 {
if process, ok := l.findProcessType(l.DefaultProcessType); ok {
return process, nil
}
return Process{}, fmt.Errorf("process type %s was not found", l.DefaultProcessType)
}
if len(cmd) == 1 {
if process, ok := l.findProcessType(cmd[0]); ok {
return process, nil
}
}
return l.userProvidedProcess(cmd)
}
func (l *Launcher) findProcessType(pType string) (Process, bool) {
for _, p := range l.Processes {
if p.Type == pType && l.isProcessEligibleForExecEnv(p) {
return p, true
}
}
return Process{}, false
}
// isProcessEligibleForExecEnv checks if a process is eligible for the current execution environment
// According to the spec:
// - A process is eligible if it has no exec-env specified, OR
// - Its exec-env includes the current execution environment, OR
// - Its exec-env includes the special value "*" which indicates compatibility with all environments
func (l *Launcher) isProcessEligibleForExecEnv(p Process) bool {
// Note: This is gated by Platform API 0.15, not Buildpack API
// The platform controls the Platform API version and determines when this is available
if l.PlatformAPI == nil || !l.PlatformAPI.AtLeast("0.15") {
return true // Skip execution environment filtering for older Platform APIs
}
// If no exec-env specified, process applies to all execution environments
if len(p.ExecEnv) == 0 {
return true
}
// Check if process supports all execution environments
for _, env := range p.ExecEnv {
if env == "*" {
return true
}
}
// Check if process supports the current execution environment
for _, env := range p.ExecEnv {
if env == l.ExecEnv {
return true
}
}
return false
}
func (l *Launcher) userProvidedProcess(cmd []string) (Process, error) {
if len(cmd) == 0 {
return Process{}, errors.New("when there is no default process a command is required")
}
if len(cmd) > 1 && cmd[0] == "--" {
return Process{Command: RawCommand{Entries: []string{cmd[1]}}, Args: cmd[2:], Direct: true}, nil
}
return Process{Command: RawCommand{Entries: []string{cmd[0]}}, Args: cmd[1:]}, nil
}
func getProcessWorkingDirectory(process Process, appDir string) string {
if process.WorkingDirectory == "" {
return appDir
}
return process.WorkingDirectory
}