* Copyright 2016-2017 Gary Kramlich <grim@reaperworld.com> * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. "github.com/aphistic/gomol" "bitbucket.org/rw_grim/convey/environment" "bitbucket.org/rw_grim/convey/normalize" "bitbucket.org/rw_grim/convey/state" "bitbucket.org/rw_grim/convey/tasks" "bitbucket.org/rw_grim/convey/yaml" Command string `yaml:"command"` Detach bool `yaml:"detach"` EntryPoint string `yaml:"entrypoint"` Environment yaml.StringOrSlice `yaml:"environment"` Image string `yaml:"image"` WorkDir string `yaml:"workdir"` WorkSpace string `yaml:"workspace"` Script yaml.StringOrSlice `yaml:"script"` Shell string `yaml:"shell"` Labels yaml.StringOrSlice `yaml:"labels"` HealthCheck HealthCheck `yaml:"healthcheck"` func (r *Run) UnmarshalYAML(unmarshal func(interface{}) error) error { raw := rawRun{Shell: "/bin/sh", HealthCheck: HealthCheck{}} if err := unmarshal(&raw); err != nil { const runTemplate = `run --rm {{if .UID}} -e UID={{.UID}}{{end}} {{if .GID}} -e GID={{.GID}}{{end}} -v {{.WorkspacePath}}:{{.WorkspaceMount}} -e CONVEY_WORKSPACE={{.WorkspaceMount}} {{if .CPUShares }} --cpu-shares {{.CPUShares}}{{end}} {{if .Memory }} --memory {{.Memory}}{{end}} {{if .ScriptFile }} -v {{.ScriptFile}}:{{.ScriptFile}}{{end}} {{if .SSHAgent }} -e SSH_AUTH_SOCK -v {{.SSHAuthSock}}:{{.SSHAuthSock}}{{end}} {{if .EntryPoint}} --entrypoint {{.EntryPoint}}{{end}} {{if .WorkDir}} -w {{.WorkDir}}{{end}} {{if .Network}} --network {{.Network}}{{end}} {{range .Labels}} -l '{{.}}'{{end}} {{range .Environment}} -e {{.}}{{end}} {{if .HealthCheck.Command}} --health-cmd "{{.HealthCheck.Command}}"{{end}} {{if .HealthCheck.Interval}} --health-interval {{.HealthCheck.Interval}}{{end}} {{if .HealthCheck.Retries}} --health-retries {{.HealthCheck.Retries}}{{end}} {{if .HealthCheck.StartPeriod}} --health-start-period {{.HealthCheck.StartPeriod}}{{end}} {{if .HealthCheck.Timeout}} --health-timeout {{.HealthCheck.Timeout}}{{end}} {{.Image}}{{if .Command}} {{.Command}}{{end}}` // buildScript will create a shell script for the given commands func (r *Run) buildScript(st *state.State, fullEnv []string) (string, string, string, error) { // figure out the current working directory // now get the absolute path absPwd, err := filepath.Abs(pwd) // create the temp file to write the script to script, err := ioutil.TempFile(absPwd, "convey-script-") // set scriptFile to the name of the temp file scriptFile := script.Name() // set the run command argument to the script file scripts, err := st.MapSlice(r.Script, fullEnv) // write the script to the file script.WriteString(strings.Join(scripts, "\n")) // make the script executable to the user os.Chmod(scriptFile, 0700) return scriptFile, entryPoint, commandArg, nil func (r *Run) Execute(name string, logger *gomol.LogAdapter, env []string, st *state.State) error { fullEnv := environment.Merge(env, r.Environment) fullEnv = environment.Merge(fullEnv, st.Environment) user, err := user.Current() // now expand the environment for idx, v := range fullEnv { v, err := environment.Mapper(v, fullEnv) // assign a default workspace location // initialize some variables entryPoint := r.EntryPoint commandArg, err := environment.Mapper(r.Command, fullEnv) // if we're using a script defined in the yaml, create it and override scriptFile, entryPoint, commandArg, err = r.buildScript(st, fullEnv) // remove the file when the function exits defer os.Remove(scriptFile) taskLabel := normalize.Normalize( fmt.Sprintf("convey-%d-task=%s", os.Getpid(), name), dockerWorkspace := st.Workspace.(*Workspace) image, err := environment.Mapper(r.Image, fullEnv) labels, err := st.MapSlice(r.Labels, fullEnv) workdir, err := environment.Mapper(r.WorkDir, fullEnv) workspacePath, err := environment.Mapper(dockerWorkspace.mountPoint, fullEnv) workspaceMount, err := environment.Mapper(workSpace, fullEnv) // build the dict for the template params := map[string]interface{}{ "CPUShares": st.CPUShares, "EntryPoint": entryPoint, "HealthCheck": r.HealthCheck, "Network": st.Network.Name(), "ScriptFile": scriptFile, "SSHAgent": st.EnableSSHAgent, "SSHAuthSock": os.Getenv("SSH_AUTH_SOCK"), "WorkspacePath": workspacePath, "WorkspaceMount": workspaceMount, stdout, stderr, err := DockerOutput(name, runTemplate, params, st) logger.Errorf("%s", stderr) cid := strings.TrimSpace(stdout) st.DetachedContainers = append(st.DetachedContainers, cid) logger.Infof("started detached container %s", cid) logger.Infof("checking for healthcheck") // check if the container has a health check hasHealth, err := containerHasHealthCheck(name, cid, st, logger) healthChan := make(chan error) logger.Infof("waiting for container to go healthy") duration := 5 * time.Second healthy, err := containerIsHealthy(name, cid, st, logger) logger.Infof("container still not healthy, waiting %v", duration) logger.Infof("container is ready") logger.Infof("no healthcheck found") return Docker(name, runTemplate, params, st) func (r *Run) New() tasks.Task { func (r *Run) Valid() error {