Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(servstate): expand environment variables for service commands #402

Closed
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
104 changes: 72 additions & 32 deletions internals/overlord/servstate/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"os/exec"
"os/user"
"strconv"
"strings"
"syscall"
"time"

Expand Down Expand Up @@ -350,28 +351,88 @@ func logError(err error) {
// command. It assumes the caller has ensures the service is in a valid state,
// and it sets s.cmd and other relevant fields.
func (s *serviceData) startInternal() error {
base, extra, err := s.config.ParseCommand()
// Copy environment to avoid updating original.
serviceEnvironment := make(map[string]string)
for k, v := range s.config.Environment {
serviceEnvironment[k] = v
}

// Get uid and gid to set user/group later.
uid, gid, err := osutil.NormalizeUidGid(s.config.UserID, s.config.GroupID, s.config.User, s.config.Group)
if err != nil {
return err
}
args := append(base, extra...)
s.cmd = exec.Command(args[0], args[1:]...)
s.cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
idsNotNil := (uid != nil && gid != nil)

// Copy environment to avoid updating original.
environment := make(map[string]string)
for k, v := range s.config.Environment {
// Also set HOME and USER if not explicitly specified in config.
if idsNotNil {
if serviceEnvironment["HOME"] == "" || serviceEnvironment["USER"] == "" {
u, err := user.LookupId(strconv.Itoa(*uid))
if err != nil {
logger.Noticef("Cannot look up user %d: %v", *uid, err)
} else {
if serviceEnvironment["HOME"] == "" {
serviceEnvironment["HOME"] = u.HomeDir
}
if serviceEnvironment["USER"] == "" {
serviceEnvironment["USER"] = u.Username
}
}
}
}

// Get system environment variables for command expansion.
getEnvironment := func(data []string, getkeyval func(item string) (key, val string)) map[string]string {
items := make(map[string]string)
for _, item := range data {
key, val := getkeyval(item)
items[key] = val
}
return items
}
environment := getEnvironment(os.Environ(), func(item string) (key, val string) {
splits := strings.Split(item, "=")
key = splits[0]
val = splits[1]
return
})

// Merge service description's environment variables to system environment variables.
for k, v := range serviceEnvironment {
environment[k] = v
}

s.cmd.Dir = s.config.WorkingDir
// Assemble the overall environment variables.
var env []string
for k, v := range environment {
env = append(env, k+"="+v)
}

// Start as another user if specified in plan.
uid, gid, err := osutil.NormalizeUidGid(s.config.UserID, s.config.GroupID, s.config.User, s.config.Group)
// Parse and obtain command tokens.
base, extra, err := s.config.ParseCommand()
if err != nil {
return err
}
if uid != nil && gid != nil {
args := append(base, extra...)

// Expand environment variables in the command with its actual value.
for i, v := range args {
if strings.HasPrefix(v, "$") {
s := strings.TrimLeft(v, "$")
s = strings.TrimLeft(s, "{")
s = strings.TrimRight(s, "}")
args[i] = environment[s]
}
}

// Execute command with expanded environment variables.
s.cmd = exec.Command(args[0], args[1:]...)
s.cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
s.cmd.Dir = s.config.WorkingDir
s.cmd.Env = env

// Start as another user if specified in plan.
if idsNotNil {
isCurrent, err := osutil.IsCurrent(*uid, *gid)
if err != nil {
logger.Debugf("Cannot determine if uid %d gid %d is current user", *uid, *gid)
Expand All @@ -382,27 +443,6 @@ func (s *serviceData) startInternal() error {
Gid: uint32(*gid),
})
}

// Also set HOME and USER if not explicitly specified in config.
if environment["HOME"] == "" || environment["USER"] == "" {
u, err := user.LookupId(strconv.Itoa(*uid))
if err != nil {
logger.Noticef("Cannot look up user %d: %v", *uid, err)
} else {
if environment["HOME"] == "" {
environment["HOME"] = u.HomeDir
}
if environment["USER"] == "" {
environment["USER"] = u.Username
}
}
}
}

// Pass service description's environment variables to child process.
s.cmd.Env = os.Environ()
for k, v := range environment {
s.cmd.Env = append(s.cmd.Env, k+"="+v)
}

// Set up stdout and stderr to write to log ring buffer.
Expand Down
Loading