diff --git a/.yourbase.yml b/.yourbase.yml index d925973c..6cc8ee65 100644 --- a/.yourbase.yml +++ b/.yourbase.yml @@ -1,6 +1,6 @@ dependencies: build: - - go:1.14.2 + - go:1.14.3 - python:3.6 build_targets: diff --git a/cli/build.go b/cli/build.go index 15e42b7f..410c953a 100644 --- a/cli/build.go +++ b/cli/build.go @@ -11,11 +11,8 @@ import ( "os" "time" - "github.com/matishsiao/goInfo" - "github.com/johnewart/subcommands" ybconfig "github.com/yourbase/yb/config" - . "github.com/yourbase/yb/plumbing" "github.com/yourbase/yb/plumbing/log" "github.com/yourbase/yb/workspace" ) @@ -52,28 +49,28 @@ func (b *BuildCmd) SetFlags(f *flag.FlagSet) { func (b *BuildCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus { startTime := time.Now() - if InsideTheMatrix() { - log.StartSection("BUILD CONTAINER", "CONTAINER") - } else { - log.StartSection("BUILD HOST", "HOST") - } - gi := goInfo.GetInfo() - gi.VarDump() - log.EndSection() - - log.StartSection("BUILD PACKAGE SETUP", "SETUP") log.Infof("Build started at %s", startTime.Format(TIME_FORMAT)) - targetPackage, err := GetTargetPackage() + ws, err := workspace.LoadWorkspace() if err != nil { - log.Errorf("%v", err) + log.Errorf("Error loading workspace: %v", err) return subcommands.ExitFailure } - // Determine build target - buildTargetName := "default" + var pkg workspace.Package if len(f.Args()) > 0 { - buildTargetName = f.Args()[0] + pkgName := f.Arg(0) + pkg, err = ws.PackageByName(pkgName) + if err != nil { + log.Errorf("Unable to find package named %s: %v", pkgName, err) + return subcommands.ExitFailure + } + } else { + pkg, err = ws.TargetPackage() + if err != nil { + log.Errorf("Unable to find default package: %v", err) + return subcommands.ExitFailure + } } var targetTimers []workspace.TargetTimer @@ -83,14 +80,14 @@ func (b *BuildCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) CleanBuild: b.CleanBuild, ExecPrefix: b.ExecPrefix, } - stepTimers, buildError := targetPackage.BuildTarget(buildTargetName, buildFlags) + stepTimers, buildError := pkg.Build(buildFlags) if err != nil { log.Errorf("Failed to build target package: %v\n", err) return subcommands.ExitFailure } - targetTimers = append(targetTimers, workspace.TargetTimer{Name: buildTargetName, Timers: stepTimers}) + targetTimers = append(targetTimers, workspace.TargetTimer{Name: pkg.Name, Timers: stepTimers}) endTime := time.Now() buildTime := endTime.Sub(startTime) diff --git a/cli/exec.go b/cli/exec.go index 47d84ba8..e2ab8563 100644 --- a/cli/exec.go +++ b/cli/exec.go @@ -3,14 +3,9 @@ package cli import ( "context" "flag" - "fmt" - "os" - "os/signal" - "syscall" - "github.com/johnewart/subcommands" - "github.com/yourbase/yb/plumbing/log" + "github.com/yourbase/yb/workspace" ) type ExecCmd struct { @@ -37,36 +32,34 @@ Executing the target involves: */ func (b *ExecCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus { - targetPackage, err := GetTargetPackage() + ws, err := workspace.LoadWorkspace() if err != nil { - log.Errorf("%v", err) + log.Errorf("Error loading workspace: %v", err) return subcommands.ExitFailure } - log.ActiveSection("Dependencies") - if _, err := targetPackage.SetupRuntimeDependencies(); err != nil { - log.Infof("Couldn't configure dependencies: %v\n", err) - return subcommands.ExitFailure + var pkg workspace.Package + if len(f.Args()) > 0 { + pkgName := f.Arg(0) + pkg, err = ws.PackageByName(pkgName) + if err != nil { + log.Errorf("Unable to find package named %s: %v", pkgName, err) + return subcommands.ExitFailure + } + } else { + pkg, err = ws.TargetPackage() + if err != nil { + log.Errorf("Unable to find default package: %v", err) + return subcommands.ExitFailure + } } - execRuntime, err := targetPackage.ExecutionRuntime(b.environment) - - c := make(chan os.Signal, 2) - signal.Notify(c, os.Interrupt, syscall.SIGTERM) - go func() { - <-c - fmt.Println("\r- Ctrl+C pressed in Terminal") - execRuntime.Shutdown() - os.Exit(0) - }() - - log.Infof("Executing package '%s'...\n", targetPackage.Name) - - err = targetPackage.Execute(execRuntime) + err = ws.ExecutePackage(pkg) if err != nil { - log.Errorf("Unable to run command: %v", err) + log.Errorf("Unable to run '%s': %v", pkg.Name, err) return subcommands.ExitFailure } + return subcommands.ExitSuccess } diff --git a/cli/package.go b/cli/package.go index d60111ee..62ea7d44 100644 --- a/cli/package.go +++ b/cli/package.go @@ -4,13 +4,12 @@ import ( "context" "flag" "fmt" + "github.com/johnewart/archiver" + "github.com/johnewart/subcommands" "os" "path/filepath" "strings" - "github.com/johnewart/archiver" - "github.com/johnewart/subcommands" - "github.com/yourbase/narwhal" . "github.com/yourbase/yb/plumbing" "github.com/yourbase/yb/plumbing/log" @@ -24,7 +23,7 @@ type PackageCmd struct { func (*PackageCmd) Name() string { return "package" } func (*PackageCmd) Synopsis() string { return "Create a package artifact" } func (*PackageCmd) Usage() string { - return `` + return `package [--target pkg]` } func (p *PackageCmd) SetFlags(f *flag.FlagSet) { diff --git a/cli/run.go b/cli/run.go index 0bfde0b1..f44187a9 100644 --- a/cli/run.go +++ b/cli/run.go @@ -4,11 +4,11 @@ import ( "context" "flag" "fmt" + "github.com/yourbase/yb/workspace" "strings" "github.com/johnewart/subcommands" "github.com/yourbase/yb/plumbing/log" - "github.com/yourbase/yb/runtime" //"path/filepath" ) @@ -40,52 +40,32 @@ func (b *RunCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) s return subcommands.ExitFailure } - targetPackage, err := GetTargetPackage() + ws, err := workspace.LoadWorkspace() if err != nil { - log.Errorf("%v", err) + log.Errorf("Error loading workspace: %v", err) return subcommands.ExitFailure } - runtimeEnv, err := targetPackage.ExecutionRuntime("default") - - if err != nil { - log.Errorf("%v", err) - return subcommands.ExitFailure - } - - argList := f.Args() - - runInTarget := false runtimeTarget := "default" + argList := f.Args() + workDir := "/workspace" if strings.HasPrefix(argList[0], "@") { runtimeTarget = argList[0][1:] + parts := strings.Split(runtimeTarget, ":") + if len(parts) == 2 { + workDir = parts[1] + runtimeTarget = parts[0] + } argList = argList[1:] - runInTarget = true } cmdString := strings.Join(argList, " ") - workDir := "/workspace" - - if runInTarget { - workDir = "/" + if runErr := ws.RunInTarget(cmdString, workDir, runtimeTarget); runErr != nil { + log.Errorf("Unable to run command: %v", runErr) + return subcommands.ExitFailure } - log.Infof("Running %s in %s from %s", cmdString, runtimeTarget, workDir) - - p := runtime.Process{Command: cmdString, Interactive: true, Directory: workDir} - - if runInTarget { - if err := runtimeEnv.RunInTarget(p, runtimeTarget); err != nil { - log.Errorf("%v", err) - return subcommands.ExitFailure - } - } else { - if err := runtimeEnv.Run(p); err != nil { - log.Errorf("%v", err) - return subcommands.ExitFailure - } - } return subcommands.ExitSuccess } diff --git a/cli/workspace.go b/cli/workspace.go index b428c95b..fe01f312 100644 --- a/cli/workspace.go +++ b/cli/workspace.go @@ -5,18 +5,17 @@ import ( "context" "flag" "fmt" - "github.com/johnewart/subcommands" - "gopkg.in/src-d/go-git.v4" - "gopkg.in/src-d/go-git.v4/plumbing" - "gopkg.in/src-d/go-git.v4/plumbing/transport/http" "io/ioutil" "os" "path/filepath" "strings" - util "github.com/yourbase/yb/plumbing" + "github.com/johnewart/subcommands" + "gopkg.in/src-d/go-git.v4" + "gopkg.in/src-d/go-git.v4/plumbing" + "gopkg.in/src-d/go-git.v4/plumbing/transport/http" + "github.com/yourbase/yb/plumbing/log" - ybtypes "github.com/yourbase/yb/types" . "github.com/yourbase/yb/workspace" ) @@ -37,10 +36,68 @@ func (w *WorkspaceCmd) Execute(ctx context.Context, f *flag.FlagSet, _ ...interf cmdr.Register(&workspaceAddCmd{}, "") cmdr.Register(&workspaceTargetCmd{}, "") cmdr.Register(&workspaceLocationCmd{}, "") + cmdr.Register(&workspaceListCmd{}, "") return (cmdr.Execute(ctx)) //return subcommands.ExitFailure } +// LOCATION +type workspaceListCmd struct{} + +func (*workspaceListCmd) Name() string { return "ls" } +func (*workspaceListCmd) Synopsis() string { return "List packages in workspace" } +func (*workspaceListCmd) Usage() string { + return `ls` +} + +func (w *workspaceListCmd) SetFlags(f *flag.FlagSet) { +} + +func (w *workspaceListCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus { + ws, err := LoadWorkspace() + + if err != nil { + log.Errorf("No package here, and no workspace, nothing to do!") + return subcommands.ExitFailure + } + + pkgs := ws.PackageList() + + fmt.Printf("Packages in workspace:\n") + for _, pkg := range pkgs { + fmt.Printf(" * %s\n", pkg.Name) + } + fmt.Println() + + fmt.Println("Containers in workspace:") + if containers, err := ws.RunningContainers(); err != nil { + log.Warnf("Unable to get running containers: %v", err) + } else { + + for _, c := range containers { + + running, err := c.Container.IsRunning() + if err != nil { + log.Warnf("Unable to determine if %s is running: %v", c.Container.Name, err) + } + + status := "idle" + if running { + if address, err := c.Container.IPv4Address(); err == nil { + status = fmt.Sprintf("up @ %s", address) + } else { + status = "up @ unknown ip" + } + } + + + fmt.Printf(" * %s (%s)", c.Container.Name, status) + } + } + + return subcommands.ExitSuccess +} + // LOCATION type workspaceLocationCmd struct{} @@ -54,28 +111,14 @@ func (w *workspaceLocationCmd) SetFlags(f *flag.FlagSet) { } func (w *workspaceLocationCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus { - // check if we're just a package - if util.PathExists(ybtypes.MANIFEST_FILE) { - currentPath, _ := filepath.Abs(".") - _, pkgName := filepath.Split(currentPath) - pkg, err := LoadPackage(pkgName, currentPath) - if err != nil { - log.Errorf("Error loading package '%s': %v", pkgName, err) - return subcommands.ExitFailure - } - - log.Infoln(pkg.BuildRoot()) - return subcommands.ExitSuccess - } else { - ws, err := LoadWorkspace() + ws, err := LoadWorkspace() - if err != nil { - log.Errorf("No package here, and no workspace, nothing to do!") - return subcommands.ExitFailure - } - fmt.Println(ws.Root()) // No logging used, because this can be used by scripts - return subcommands.ExitSuccess + if err != nil { + log.Errorf("No package here, and no workspace, nothing to do!") + return subcommands.ExitFailure } + fmt.Println(ws.Root()) // No logging used, because this can be used by scripts + return subcommands.ExitSuccess } // CREATION diff --git a/plumbing/util.go b/plumbing/util.go index 38327fb8..6453f9ac 100644 --- a/plumbing/util.go +++ b/plumbing/util.go @@ -4,8 +4,6 @@ import ( "bufio" "bytes" "fmt" - "github.com/yourbase/yb/plumbing/log" - . "github.com/yourbase/yb/types" "io" "io/ioutil" "os" @@ -13,6 +11,9 @@ import ( "path/filepath" "strings" + "github.com/yourbase/yb/plumbing/log" + . "github.com/yourbase/yb/types" + "github.com/ulikunitz/xz" "gopkg.in/src-d/go-billy.v4/memfs" @@ -204,6 +205,8 @@ func FindWorkspaceRoot() (string, error) { parent := filepath.Dir(packageDir) if _, err := os.Stat(filepath.Join(parent, "config.yml")); err == nil { return parent, nil + } else { + return packageDir, nil } } else { return "", err diff --git a/runtime/runtime.go b/runtime/runtime.go index 24c84183..a7c09a03 100644 --- a/runtime/runtime.go +++ b/runtime/runtime.go @@ -2,6 +2,7 @@ package runtime import ( "fmt" + "github.com/yourbase/yb/plumbing/log" "io" "strings" @@ -69,6 +70,10 @@ type Runtime struct { DefaultTarget Target } +func (r *Runtime) SupportsContainers() bool { + return r.ContainerServiceContext != nil +} + func (r *Runtime) AddTarget(targetId string, t Target) error { if _, exists := r.Targets[targetId]; exists { return fmt.Errorf("Unable to add target with id %s - already exists", targetId) @@ -91,34 +96,60 @@ func (r *Runtime) RunInTarget(p Process, targetId string) error { } } +func (r *Runtime) FindContainers(definitions []narwhal.ContainerDefinition) []*ContainerTarget { + result := make([]*ContainerTarget, 0) + + if r.SupportsContainers() { + for _, cd := range definitions { + container, err := r.ContainerServiceContext.FindContainer(cd) + if err != nil { + log.Warnf("Error trying to find container %s - %v", cd.Label, err) + } else { + // TODO: Env / workdir? + tgt := &ContainerTarget{Container: container} + result = append(result, tgt) + } + } + } + + return result +} + func (r *Runtime) AddContainer(cd narwhal.ContainerDefinition) (*ContainerTarget, error) { - if r.ContainerServiceContext == nil { - sc, err := narwhal.NewServiceContextWithId(r.Identifier, r.LocalWorkDir) + if r.SupportsContainers() { + container, err := r.ContainerServiceContext.StartContainer(cd) if err != nil { - return nil, err + return nil, fmt.Errorf("could not start container %s: %v", cd.Label, err) } - r.ContainerServiceContext = sc - } - container, err := r.ContainerServiceContext.StartContainer(cd) - if err != nil { - return nil, fmt.Errorf("Couldn't start container %s in service context: %v", cd.Label, err) - } + tgt := &ContainerTarget{ + Container: container, + } - tgt := &ContainerTarget{ - Container: container, - } + if err = r.AddTarget(cd.Label, tgt); err != nil { + return nil, err + } - r.AddTarget(cd.Label, tgt) - return tgt, nil + return tgt, nil + } else { + return nil, fmt.Errorf("current runtime does not support containers") + } } func NewRuntime(identifier string, localWorkDir string) *Runtime { + + sc, err := narwhal.NewServiceContextWithId(identifier, localWorkDir) + if err != nil { + log.Infof("Container service context failed to initialize - containers won't be supported: %v", err) + sc = nil + } + return &Runtime{ - Identifier: identifier, - LocalWorkDir: localWorkDir, - Targets: make(map[string]Target), - DefaultTarget: &MetalTarget{}, + Identifier: identifier, + LocalWorkDir: localWorkDir, + Targets: make(map[string]Target), + DefaultTarget: &MetalTarget{}, + ContainerServiceContext: sc, } } @@ -138,11 +169,19 @@ func (r *Runtime) EnvironmentData() RuntimeEnvironmentData { Containers: ContainerData{ serviceCtx: r.ContainerServiceContext, }, + Services: ServiceData{ + serviceCtx: r.ContainerServiceContext, + }, } } type RuntimeEnvironmentData struct { Containers ContainerData + Services ServiceData +} + +type ServiceData struct { + serviceCtx *narwhal.ServiceContext } type ContainerData struct { @@ -162,6 +201,22 @@ func (c ContainerData) IP(label string) string { return "" } +func (c ServiceData) IP(label string) string { + // Check service context + log.Debugf("Looking up IP for service %s", label) + if c.serviceCtx != nil { + if buildContainer, ok := c.serviceCtx.Containers[label]; ok { + log.Debugf("Found service %s with container %s", label, buildContainer.Id) + if ipv4, err := buildContainer.IPv4Address(); err == nil { + log.Debugf("Service %s has IP %s", label, ipv4) + return ipv4 + } + } + } + + return "" +} + func (c ContainerData) Environment() map[string]string { result := make(map[string]string) if c.serviceCtx != nil { diff --git a/workspace/build_target.go b/workspace/build_target.go index b3b60716..1d73ebe1 100644 --- a/workspace/build_target.go +++ b/workspace/build_target.go @@ -6,6 +6,7 @@ import ( "github.com/yourbase/yb/plumbing" "github.com/yourbase/yb/plumbing/log" "github.com/yourbase/yb/runtime" + "io" "os" "strings" "time" @@ -67,7 +68,7 @@ func (bt BuildTarget) EnvironmentVariables(data runtime.RuntimeEnvironmentData) return result } -func (bt BuildTarget) Build(runtimeCtx *runtime.Runtime, flags BuildFlags, packagePath string, buildpacks []string) ([]CommandTimer, error) { +func (bt BuildTarget) Build(runtimeCtx *runtime.Runtime, output io.Writer, flags BuildFlags, packagePath string, buildpacks []string) ([]CommandTimer, error) { var stepTimes []CommandTimer containers := bt.Dependencies.ContainerList() @@ -108,10 +109,10 @@ func (bt BuildTarget) Build(runtimeCtx *runtime.Runtime, flags BuildFlags, packa // Inject a .ssh/config to skip host key checking sshConfig := "Host github.com\n\tStrictHostKeyChecking no\n" - builder.Run(runtime.Process{Command: "mkdir -p /root/.ssh"}) + builder.Run(runtime.Process{Output: &output, Command: "mkdir -p /root/.ssh"}) builder.WriteFileContents(sshConfig, "/root/.ssh/config") - builder.Run(runtime.Process{Command: "chmod 0600 /root/.ssh/config"}) - builder.Run(runtime.Process{Command: "chown root:root /root/.ssh/config"}) + builder.Run(runtime.Process{Output: &output, Command: "chmod 0600 /root/.ssh/config"}) + builder.Run(runtime.Process{Output: &output, Command: "chown root:root /root/.ssh/config"}) // Inject a useful gitconfig configlines := []string{ @@ -136,13 +137,12 @@ func (bt BuildTarget) Build(runtimeCtx *runtime.Runtime, flags BuildFlags, packa log.Infof("Forwarding SSH agent via %s", hostAddr) } - //buildContainer.Environment = append(buildContainer.Environment, "SSH_AUTH_SOCK=/ssh_agent") builder.SetEnv("SSH_AUTH_SOCK", "/ssh_agent") forwardPath, err := builder.DownloadFile("https://yourbase-artifacts.s3-us-west-2.amazonaws.com/sockforward") - builder.Run(runtime.Process{Command: fmt.Sprintf("chmod a+x %s", forwardPath)}) + builder.Run(runtime.Process{Output: &output, Command: fmt.Sprintf("chmod a+x %s", forwardPath)}) forwardCmd := fmt.Sprintf("%s /ssh_agent %s", forwardPath, hostAddr) go func() { - builder.Run(runtime.Process{Command: forwardCmd}) + builder.Run(runtime.Process{Output: &output, Command: forwardCmd}) }() } } @@ -184,7 +184,7 @@ func (bt BuildTarget) Build(runtimeCtx *runtime.Runtime, flags BuildFlags, packa p := runtime.Process{ Directory: workDir, Command: cmdString, - //Environment: buildData.EnvironmentVariables(), + //Environment: buildData.environmentVariables(), Interactive: false, } diff --git a/workspace/package.go b/workspace/package.go index 986a4d07..61849d18 100644 --- a/workspace/package.go +++ b/workspace/package.go @@ -1,20 +1,22 @@ package workspace import ( - "crypto/sha256" + "errors" "fmt" "github.com/yourbase/narwhal" - . "github.com/yourbase/yb/plumbing" - "github.com/yourbase/yb/plumbing/log" - "github.com/yourbase/yb/runtime" - . "github.com/yourbase/yb/types" "gopkg.in/yaml.v2" "io" "io/ioutil" "os" - "os/user" + "os/signal" "path/filepath" "strings" + "syscall" + + . "github.com/yourbase/yb/plumbing" + "github.com/yourbase/yb/plumbing/log" + "github.com/yourbase/yb/runtime" + . "github.com/yourbase/yb/types" ) // Any flags we want to pass to the build process @@ -25,49 +27,49 @@ type BuildFlags struct { } type Package struct { - Name string - path string - Manifest BuildManifest + Name string + path string + Manifest BuildManifest + Workspace *Workspace } +var ErrNoManifestFile = errors.New("manifest file not found") + func (p Package) Path() string { return p.path } -func (p Package) BuildTargetToWriter(buildTargetName string, flags BuildFlags, output io.Writer) ([]CommandTimer, error) { - manifest := p.Manifest +func (p Package) Build(flags BuildFlags) ([]CommandTimer, error) { + times := make([]CommandTimer, 0) - // Named target, look for that and resolve it - _, err := manifest.ResolveBuildTargets(buildTargetName) - - if err != nil { - log.Errorf("Could not compute build target '%s': %v", buildTargetName, err) - log.Errorf("Valid build targets: %s", strings.Join(manifest.BuildTargetList(), ", ")) - return []CommandTimer{}, err - } + manifest := p.Manifest - primaryTarget, err := manifest.BuildTarget(buildTargetName) + tgt, err := manifest.BuildTarget("default") if err != nil { - log.Errorf("Couldn't get primary build target '%s' specs: %v", buildTargetName, err) - return []CommandTimer{}, err + return times, err } - contextId := fmt.Sprintf("%s-build-%s", p.Name, primaryTarget.Name) + contextId := fmt.Sprintf("%s-build-%s", p.Name, tgt.Name) runtimeCtx := runtime.NewRuntime(contextId, p.BuildRoot()) - return primaryTarget.Build(runtimeCtx, flags, p.Path(), p.Manifest.Dependencies.Build) + return tgt.Build(runtimeCtx, os.Stdout, flags, p.Path(), p.Manifest.Dependencies.Build) +} + +func LoadPackageAtPath(path string) (Package, error) { + _, pkgName := filepath.Split(path) + return LoadPackage(pkgName, path) } -func (p Package) BuildTarget(name string, flags BuildFlags) ([]CommandTimer, error) { - return p.BuildTargetToWriter(name, flags, os.Stdout) +func (p Package) BuildRoot() string { + return p.Workspace.BuildRoot() } func LoadPackage(name string, path string) (Package, error) { manifest := BuildManifest{} buildYaml := filepath.Join(path, MANIFEST_FILE) if _, err := os.Stat(buildYaml); os.IsNotExist(err) { - return Package{}, fmt.Errorf("Can't load %s: %v", MANIFEST_FILE, err) + return Package{}, ErrNoManifestFile } buildyaml, _ := ioutil.ReadFile(buildYaml) @@ -85,49 +87,8 @@ func LoadPackage(name string, path string) (Package, error) { return p, nil } -func (p Package) BuildRoot() string { - // Are we a part of a workspace? - workspaceDir, err := FindWorkspaceRoot() - - if err != nil { - // Nope, just ourselves... - workspacesRoot, exists := os.LookupEnv("YB_WORKSPACES_ROOT") - if !exists { - u, err := user.Current() - if err != nil { - workspacesRoot = "/tmp/yourbase/workspaces" - } else { - workspacesRoot = fmt.Sprintf("%s/.yourbase/workspaces", u.HomeDir) - } - } - - h := sha256.New() - - h.Write([]byte(p.path)) - workspaceHash := fmt.Sprintf("%x", h.Sum(nil)) - workspaceDir = filepath.Join(workspacesRoot, workspaceHash[0:12]) - } - - MkdirAsNeeded(workspaceDir) - - buildDir := "build" - buildRoot := filepath.Join(workspaceDir, buildDir) - - if _, err := os.Stat(buildRoot); os.IsNotExist(err) { - if err := os.Mkdir(buildRoot, 0700); err != nil { - log.Warnf("Unable to create build dir in workspace: %v\n", err) - } - } - - return buildRoot - -} - -func (p Package) SetupRuntimeDependencies() ([]CommandTimer, error) { - deps := p.Manifest.Dependencies.Runtime - deps = append(deps, p.Manifest.Dependencies.Build...) - return []CommandTimer{}, nil - //return LoadBuildPacks(deps, p) +func (p Package) environmentVariables(data runtime.RuntimeEnvironmentData, env string) []string { + return p.Manifest.Exec.EnvironmentVariables(env, data) } func (p Package) Execute(runtimeCtx *runtime.Runtime) error { @@ -136,16 +97,37 @@ func (p Package) Execute(runtimeCtx *runtime.Runtime) error { func (p Package) ExecuteToWriter(runtimeCtx *runtime.Runtime, output io.Writer) error { + target, err := p.createExecutionTarget(runtimeCtx) + if err != nil { + return err + } + + c := make(chan os.Signal, 2) + signal.Notify(c, os.Interrupt, syscall.SIGTERM) + go func() { + <-c + fmt.Println("\r- Ctrl+C pressed in Terminal") + if err := runtimeCtx.Shutdown(); err != nil { + // Oops. + os.Exit(1) + } + // Ok! + os.Exit(0) + }() + + log.Infof("Executing package '%s'...\n", p.Name) + for _, cmdString := range p.Manifest.Exec.Commands { - p := runtime.Process{ + proc := runtime.Process{ Command: cmdString, Directory: "/workspace", Interactive: false, Output: &output, + Environment: p.environmentVariables(runtimeCtx.EnvironmentData(), "default"), } - if err := runtimeCtx.Run(p); err != nil { - return fmt.Errorf("Unable to run command '%s': %v", cmdString, err) + if err := target.Run(proc); err != nil { + return fmt.Errorf("unable to run command '%s': %v", cmdString, err) } } @@ -164,20 +146,14 @@ func (p Package) checkMounts(cd *narwhal.ContainerDefinition, srcDir string) err return nil } -func (p Package) ExecutionRuntime(environment string) (*runtime.Runtime, error) { - manifest := p.Manifest - containers := manifest.Exec.Dependencies.ContainerList() - contextId := fmt.Sprintf("%s-exec", p.Name) - - runtimeCtx := runtime.NewRuntime(contextId, p.BuildRoot()) - +func (p Package) createExecutionTarget(runtimeCtx *runtime.Runtime) (*runtime.ContainerTarget, error) { localContainerWorkDir := filepath.Join(p.BuildRoot(), "containers") - if err := MkdirAsNeeded(localContainerWorkDir); err != nil { - return nil, fmt.Errorf("Unable to set host container work dir: %v", err) - } + MkdirAsNeeded(localContainerWorkDir) log.Infof("Will use %s as the dependency work dir", localContainerWorkDir) + manifest := p.Manifest + containers := manifest.Exec.Dependencies.ContainerList() for _, cd := range containers { cd.LocalWorkDir = localContainerWorkDir if err := p.checkMounts(&cd, localContainerWorkDir); err != nil { @@ -227,9 +203,9 @@ func (p Package) ExecutionRuntime(environment string) (*runtime.Runtime, error) } execContainer := manifest.Exec.Container - execContainer.Environment = manifest.Exec.EnvironmentVariables(environment, runtimeCtx.EnvironmentData()) + execContainer.Environment = manifest.Exec.EnvironmentVariables("default", runtimeCtx.EnvironmentData()) execContainer.Command = "/usr/bin/tail -f /dev/null" - execContainer.Label = "exec" + execContainer.Label = p.Name execContainer.Ports = portMappings // Add package to mounts @ /workspace @@ -249,7 +225,15 @@ func (p Package) ExecutionRuntime(environment string) (*runtime.Runtime, error) LoadBuildPacks(execTarget, p.Manifest.Dependencies.Build) LoadBuildPacks(execTarget, p.Manifest.Dependencies.Runtime) - runtimeCtx.DefaultTarget = execTarget + return execTarget, nil +} + +func (p Package) RuntimeContainers() []narwhal.ContainerDefinition { + m := p.Manifest - return runtimeCtx, nil + result := make([]narwhal.ContainerDefinition, 0) + for _, c := range m.Exec.Dependencies.ContainerList() { + result = append(result, c) + } + return result } diff --git a/workspace/workspace.go b/workspace/workspace.go index d3731437..5c81ad57 100644 --- a/workspace/workspace.go +++ b/workspace/workspace.go @@ -1,21 +1,82 @@ package workspace import ( + "crypto/sha256" "fmt" "github.com/yourbase/yb/runtime" - "gopkg.in/yaml.v2" "io/ioutil" "os" + "os/user" "path/filepath" "strings" + "gopkg.in/yaml.v2" + . "github.com/yourbase/yb/plumbing" "github.com/yourbase/yb/plumbing/log" ) type Workspace struct { - Target string `yaml:"target"` - Path string + Target string `yaml:"target"` + Path string + packages []Package +} + +func (w Workspace) BuildPackage(p Package) error { + return nil +} + +func (w Workspace) RunInTarget(cmdString string, workDir string, targetName string) error { + + log.Infof("Running %s in %s from %s", cmdString, targetName, workDir) + + p := runtime.Process{Command: cmdString, Interactive: true, Directory: workDir} + + ctx, err := w.execRuntimeContext() + if err != nil { + return err + } + + return ctx.RunInTarget(p, targetName) +} + +func (w Workspace) RunningContainers() ([]*runtime.ContainerTarget, error) { + runtimeCtx, err := w.execRuntimeContext() + + if err != nil { + return []*runtime.ContainerTarget{}, fmt.Errorf("unable to determine running containers: %v", err) + } + + result := make([]*runtime.ContainerTarget, 0) + for _, p := range w.PackageList() { + for _, c := range runtimeCtx.FindContainers(p.RuntimeContainers()) { + result = append(result, c) + } + } + + return result, nil +} + +func (w Workspace) ExecutePackage(p Package) error { + if runtimeCtx, err := w.execRuntimeContext(); err != nil { + return err + } else { + return p.Execute(runtimeCtx) + } +} + +func (w Workspace) execRuntimeContext() (*runtime.Runtime, error) { + log.Debugf("Creating runtime context for workspace %s", w.Name()) + contextId := fmt.Sprintf("%s-exec", w.Name()) + + runtimeCtx := runtime.NewRuntime(contextId, w.BuildRoot()) + + return runtimeCtx, nil +} + +func (w Workspace) Name() string { + _, name := filepath.Split(w.Path) + return name } // Change this @@ -32,25 +93,89 @@ func (w Workspace) TargetPackage() (Package, error) { } func (w Workspace) PackageByName(name string) (Package, error) { - pkgList, err := w.PackageList() + for _, pkg := range w.packages { + if pkg.Name == name { + return pkg, nil + } + } + + return Package{}, fmt.Errorf("No package with name %s found in the workspace", name) +} + +func (w Workspace) PackageList() []Package { + return w.packages +} + +func (w Workspace) BuildRoot() string { + buildDir := "build" + buildRoot := filepath.Join(w.Path, buildDir) + + if _, err := os.Stat(buildRoot); os.IsNotExist(err) { + if err := os.Mkdir(buildRoot, 0700); err != nil { + log.Warnf("Unable to create build dir in workspace: %v\n", err) + } + } + + return buildRoot +} + +func (w Workspace) Save() error { + d, err := yaml.Marshal(w) + if err != nil { + log.Fatalf("error: %v", err) + return err + } + err = ioutil.WriteFile(filepath.Join(w.Path, "config.yml"), d, 0644) + if err != nil { + log.Fatalf("Unable to write config: %v", err) + return err + } + return nil +} + +func loadWorkspaceFromPackage(manifestFile string, path string) (Workspace, error) { + workspace := Workspace{} + pkg, err := LoadPackageAtPath(path) + pkg.Workspace = &workspace if err != nil { - return Package{}, err + return workspace, fmt.Errorf("Couldn't load package for workspace: %v", err) } - for _, pkg := range pkgList { - if pkg.Name == name { - return pkg, nil + workspacesRoot, exists := os.LookupEnv("YB_WORKSPACES_ROOT") + if !exists { + u, err := user.Current() + if err != nil { + workspacesRoot = "/tmp/yourbase/workspaces" + } else { + workspacesRoot = fmt.Sprintf("%s/.yourbase/workspaces", u.HomeDir) } } - return Package{}, fmt.Errorf("No package with name %s found in the workspace", name) + h := sha256.New() + + h.Write([]byte(path)) + workspaceHash := fmt.Sprintf("%x", h.Sum(nil)) + workspace.Path = filepath.Join(workspacesRoot, workspaceHash[0:12]) + workspace.packages = []Package{pkg} + workspace.Target = pkg.Name + + return workspace, nil } -func (w Workspace) PackageList() ([]Package, error) { - var packages []Package +func loadWorkspaceFromConfigYaml(configFile string, path string) (Workspace, error) { + workspace := Workspace{} + configyaml, _ := ioutil.ReadFile(configFile) + err := yaml.Unmarshal([]byte(configyaml), &workspace) + + if err != nil { + return Workspace{}, fmt.Errorf("Error loading workspace config!") + } + + workspace.Path = path - globStr := filepath.Join(w.Path, "*") + // Always load packages + globStr := filepath.Join(path, "*") files, err := filepath.Glob(globStr) if err != nil { log.Fatal(err) @@ -67,55 +192,20 @@ func (w Workspace) PackageList() ([]Package, error) { if !strings.HasPrefix(pkgName, ".") { pkg, err := LoadPackage(pkgName, pkgPath) if err != nil { - return packages, err + if err == ErrNoManifestFile { + log.Debugf("No manifest file found for %s", pkgName) + } else { + log.Errorf("Error loading package '%s': %v", pkgName, err) + } + } else { + pkg.Workspace = &workspace + workspace.packages = append(workspace.packages, pkg) } - packages = append(packages, pkg) } } } - return packages, nil - -} - -func (w Workspace) BuildRoot() string { - return filepath.Join(w.Path, "build") -} - -func (w Workspace) SetupEnv() error { - log.Infof("Resetting environment variables!") - criticalVariables := []string{"USER", "USERNAME", "UID", "GID", "TTY", "PWD"} - oldEnv := make(map[string]string) - for _, key := range criticalVariables { - oldEnv[key] = os.Getenv(key) - } - - os.Clearenv() - tmpDir := filepath.Join(w.BuildRoot(), "tmp") - MkdirAsNeeded(tmpDir) - runtime.SetEnv("HOME", w.BuildRoot()) - runtime.SetEnv("TMPDIR", tmpDir) - - for _, key := range criticalVariables { - log.Infof("%s=%s\n", key, oldEnv[key]) - runtime.SetEnv(key, oldEnv[key]) - } - - return nil -} - -func (w Workspace) Save() error { - d, err := yaml.Marshal(w) - if err != nil { - log.Fatalf("error: %v", err) - return err - } - err = ioutil.WriteFile(filepath.Join(w.Path, "config.yml"), d, 0644) - if err != nil { - log.Fatalf("Unable to write config: %v", err) - return err - } - return nil + return workspace, nil } func LoadWorkspace() (Workspace, error) { @@ -126,16 +216,18 @@ func LoadWorkspace() (Workspace, error) { return Workspace{}, fmt.Errorf("Error getting workspace path: %v", err) } - var workspace = Workspace{} + manifestFile := filepath.Join(workspacePath, ".yourbase.yml") configFile := filepath.Join(workspacePath, "config.yml") - configyaml, _ := ioutil.ReadFile(configFile) - err = yaml.Unmarshal([]byte(configyaml), &workspace) - if err != nil { - return Workspace{}, fmt.Errorf("Error loading workspace config!") + if PathExists(manifestFile) { + log.Debugf("Loading workspace from manifest file: %s", manifestFile) + return loadWorkspaceFromPackage(manifestFile, workspacePath) } - log.Infof("Workspace path: %s\n", workspacePath) - workspace.Path = workspacePath - return workspace, nil + if PathExists(configFile) { + log.Debugf("Loading workspace from config file: %s", configFile) + return loadWorkspaceFromConfigYaml(configFile, workspacePath) + } + + return Workspace{}, fmt.Errorf("Error finding workspace at path %s", workspacePath) }