Skip to content

Commit

Permalink
Add global and local config support. (#5)
Browse files Browse the repository at this point in the history
* Add global and local config support.

* Add ducker config generation option.

* Add overwrite ask option.

Co-authored-by: JeiKeiLim <limjk@jmarple.ai>
  • Loading branch information
JeiKeiLim and JeiKeiLim authored Oct 18, 2022
1 parent 17b3aff commit 7777e56
Show file tree
Hide file tree
Showing 8 changed files with 485 additions and 100 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@

.last_exec_cont_id.txt
/ducker

.ducker.yaml
213 changes: 132 additions & 81 deletions cmd/ducker/ducker.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
package main

import (
"bufio"
"fmt"
"log"
"os"
Expand All @@ -15,60 +14,60 @@ import (
"github.com/urfave/cli/v2"
)

func checkError(err error) {
if err != nil {
fmt.Println(err)
os.Exit(1)
}
}
// Cross compile options
// env GOOS=darwin GOARCH=arm64 go build ./cmd/ducker

func runTerminalCmd(cmd string, option string) string {
cmdRun, cmdOut := exec.Command(cmd, strings.Split(option, " ")...), new(strings.Builder)
cmdRun.Stdout = cmdOut
err := cmdRun.Run()
func duckerConfig(ctx *cli.Context) {
// TODO(jeikeilim): add more flexible setting options
genGlobal := ctx.Bool("global")
genLocal := ctx.Bool("local")

checkError(err)
return strings.TrimSpace(cmdOut.String())
}

func getArchType() string {
return runTerminalCmd("uname", "-m")
}
if genGlobal {
doWrite := true
defaultGlobalConfig := getDefaultGlobalConfig("", "", "")

func writeFile(contents string, path string, overwrite bool) bool {
_, err := os.Stat(path)
if !overwrite && err == nil {
fmt.Printf("%s already exist!\n", path)
return false
}

fp, err := os.Create(path)
checkError(err)
write_buf := bufio.NewWriter(fp)
_, err = write_buf.WriteString(contents)
write_buf.Flush()
if _, err := os.Stat(getDefaultGlobalConfigPath()); err == nil {
doWrite = asksAreYouSure("Ducker global config file already exists at " + getDefaultGlobalConfigPath() + " Do you want to OVERWRITE?")
}
if doWrite {
defaultGlobalConfig.Write(getDefaultGlobalConfigPath())
fmt.Println("Global config has been written in", getDefaultGlobalConfigPath())
}
}

checkError(err)
if genLocal {
doWrite := true
defaultLocalConfig := getDefaultLocalConfig()
if _, err := os.Stat(getDefaultLocalConfigPath()); err == nil {
doWrite = asksAreYouSure("Ducker local config file already exists at " + getDefaultLocalConfigPath() + " Do you want to OVERWRITE?")
}

return true
if doWrite {
defaultLocalConfig.Write(getDefaultLocalConfigPath())
fmt.Println("Local config has been written in", getDefaultLocalConfigPath())
}
}
}

func dockerBuild(ctx *cli.Context, dockerTag string) {
dockerFilePath := "docker/Dockerfile"
if !strings.HasSuffix(dockerTag, "x86_64") {
dockerFilePath += "." + getArchType()
}
localConfig := readDefaultLocalConfig()

buildArgs := ctx.String("args")

buildCmd := "docker build . -t " + dockerTag
buildCmd += " -f " + dockerFilePath
buildCmd += " --build-arg UID=" + runTerminalCmd("id", "-u")
buildCmd += " --build-arg GID=" + runTerminalCmd("id", "-g")
buildCmd += " " + localConfig.GetBuildArg()

if buildArgs != "" {
buildCmd += " " + buildArgs
}

fmt.Println(buildCmd)

cmdRun := exec.Command("/bin/sh", "-c", buildCmd)
cmdRun.Stdout = os.Stdout
if err := cmdRun.Run(); err != nil {
Expand All @@ -77,36 +76,40 @@ func dockerBuild(ctx *cli.Context, dockerTag string) {
}

func dockerRun(ctx *cli.Context, dockerTag string) {

dockerArgs := ctx.String("docker-args")
shellType := ctx.String("shell")
mountPWD := ctx.Bool("mount-pwd")
mountPWD := ctx.Bool("mount-pwd")
shellCmd := "/bin/bash"
dockerOpt := "-tid"

runOption := ""
localConfig := readDefaultLocalConfig()
if !localConfig.IsEmpty() {
runOption += localConfig.GetRunArg()
}

if shellType == "zsh" {
shellCmd = "/usr/bin/zsh"
} else if shellType == "nosh" {
shellCmd = ctx.String("run-cmd")
dockerOpt = "-ti"
}

runCmd := "docker run --privileged " + dockerOpt
runCmd += " -e DISPLAY=" + os.Getenv("DISPLAY")
runCmd += " -e TERM=xterm-256color"
runCmd += " -v /tmp/.X11-unix:/tmp/.X11-unix:ro"
runCmd += " -v /dev:/dev"

if mountPWD {
mydir, err := os.Getwd()
if err != nil {
fmt.Println(err)
} else {
baseDir := filepath.Base(mydir)
projectName := strings.ToLower(baseDir)

runCmd += " -v " + mydir + ":/home/user/" + projectName
}
}
runCmd := "docker run " + dockerOpt + " "
runCmd += runOption

if mountPWD || localConfig.Mount_PWD {
mydir, err := os.Getwd()
if err != nil {
fmt.Println(err)
} else {
baseDir := filepath.Base(mydir)
projectName := strings.ToLower(baseDir)

runCmd += " -v " + mydir + ":/home/user/" + projectName
}
}

// Add GPU option if NVIDIA driver exist
if _, err := os.Stat("/proc/driver/nvidia/version"); err == nil {
Expand All @@ -119,7 +122,6 @@ func dockerRun(ctx *cli.Context, dockerTag string) {
runCmd += " -v " + gitConfigPath + ":/home/user/.gitconfig"
}

runCmd += " --network host"
runCmd += " " + dockerArgs
runCmd += " " + dockerTag
runCmd += " " + shellCmd
Expand All @@ -135,23 +137,32 @@ func dockerRun(ctx *cli.Context, dockerTag string) {
fmt.Println(err)
}

// TODO(jeikeilim): It's bad idea to check last container ID with docker ps -qn 1
lastContainerID := runTerminalCmd("docker", "ps -qn 1")
writeFile(lastContainerID, ".last_exec_cont_id.txt", true)
localConfig.LastExecID = lastContainerID
localConfig.Write(getDefaultLocalConfigPath())

if shellType != "nosh" {
dockerExec(ctx)
}
}

func dockerExec(ctx *cli.Context) {
localConfig := readDefaultLocalConfig()

shellType := ctx.String("shell")
shellCmd := "/bin/bash"

if shellType == "zsh" {
shellCmd = "/usr/bin/zsh"
}

lastContainerID := runTerminalCmd("tail", "-1 .last_exec_cont_id.txt")
lastContainerID := localConfig.LastExecID

if lastContainerID == "" {
fmt.Println("Last container ID can not be found.")
return
}

execCmd := "docker exec -ti " + lastContainerID
execCmd += " " + shellCmd
Expand All @@ -166,40 +177,34 @@ func dockerExec(ctx *cli.Context) {
}

func initDockerfile(ctx *cli.Context) {
organization := ctx.String("organization")
name := ctx.String("name")
contact := ctx.String("contact")
template := ctx.String("template")
forceOverwrite := ctx.Bool("force")
forceOverwrite := ctx.Bool("force")

fmt.Println(template)
templateContent := checkTemplates(template)

if !ctx.Bool("quite") {
reader := bufio.NewReader(os.Stdin)
fmt.Printf("Enter your name(Default: %s): ", name)
keyIn, err := reader.ReadString('\n')
checkError(err)
if strings.TrimSpace(keyIn) != "" {
name = keyIn
}
name = strings.TrimSpace(name)
globalConfig := readDefaultGlobalConfig()

fmt.Printf("Enter contact address (Default: %s): ", contact)
keyIn, err = reader.ReadString('\n')
checkError(err)
if strings.TrimSpace(keyIn) != "" {
contact = keyIn
}
contact = strings.TrimSpace(contact)
}
if !ctx.Bool("quite") && globalConfig.IsEmpty() {
globalConfig = getDefaultGlobalConfig("", "", "")
} else if ctx.Bool("quite") && globalConfig.IsEmpty() {
globalConfig = getDefaultGlobalConfig(organization, name, contact)
}

tzname, _ := tzlocal.RuntimeTZ()

// TODO(jeikeilim): Add custom base image support
dockerBaseImage := "ubuntu:bionic"
dockerContents := fmt.Sprintf("FROM %s\n\n", dockerBaseImage)

dockerContents += fmt.Sprintf("LABEL maintainer=\"%s <%s>\"\n\n", name, contact)
dockerContents += fmt.Sprintf("LABEL maintainer=\"%s <%s>\"\n\n",
globalConfig.Name,
globalConfig.Contact)

// TODO(jeikeilim): Modify below to use base Dockerfile
dockerContents += "ENV DEBIAN_FRONTEND=noninteractive\n"
dockerContents += fmt.Sprintf("ENV TZ=%s\n", tzname)
dockerContents += "ENV TERM xterm-256color\n\n"
Expand Down Expand Up @@ -239,9 +244,9 @@ func initDockerfile(ctx *cli.Context) {
dockerContents += "\n# Place your environment here\n\n"

err := os.Mkdir("docker", os.ModePerm)
if !forceOverwrite {
checkError(err)
}
if !forceOverwrite {
checkError(err)
}

isSuccess1 := writeFile(dockerContents, "docker/Dockerfile", forceOverwrite)
isSuccess2 := writeFile(dockerContents, "docker/Dockerfile.aarch64", forceOverwrite)
Expand All @@ -253,23 +258,41 @@ func initDockerfile(ctx *cli.Context) {
fmt.Println("Failed")
fmt.Println("Dockerfile already exist in docker directory")
}

globalConfig.Write(getDefaultGlobalConfigPath())
writeDefaultLocalConfig()
}

func main() {
mydir, err := os.Getwd()
if err != nil {
fmt.Println(err)
}
globalConfig := readDefaultGlobalConfig()
organizationName := "jeikeilim"

if !globalConfig.IsEmpty() {
organizationName = globalConfig.Organization
}

baseDir := filepath.Base(mydir)
projectName := strings.ToLower(baseDir)

osArchType := getArchType()
dockerTag := fmt.Sprintf("%s/%s:%s", organizationName, projectName, osArchType)
const duckIcon = `
=====================================
__ __ __
___( o)> ___( o)> ___( o)>
\ <_. ) \ <_. ) \ <_. )
'---' '---' '---'
=====================================
`

app := &cli.App{
Name: "ducker",
Version: "0.1.1",
Version: "0.1.2",
Compiled: time.Now(),
Authors: []*cli.Author{
&cli.Author{
Expand All @@ -278,7 +301,7 @@ func main() {
},
},
EnableBashCompletion: true,
Usage: "Ducker the docker helper",
Usage: "Ducker the docker helper" + duckIcon,
Commands: []*cli.Command{
{
Name: "init",
Expand All @@ -299,6 +322,13 @@ func main() {
Value: "None",
DefaultText: "None",
},
&cli.StringFlag{
Name: "organization",
Aliases: []string{"o"},
Usage: "Organization name",
Value: "None",
DefaultText: "None",
},
&cli.BoolFlag{
Name: "quite",
Aliases: []string{"q"},
Expand All @@ -307,7 +337,7 @@ func main() {
&cli.BoolFlag{
Name: "force",
Aliases: []string{"f"},
Usage: "Force to create Dockerfile (WARNING: file can be overwritten)",
Usage: "Force to create Dockerfile (WARNING: file can be overwritten)",
},
&cli.StringFlag{
Name: "template",
Expand Down Expand Up @@ -362,7 +392,7 @@ func main() {
&cli.BoolFlag{
Name: "mount-pwd",
Aliases: []string{"m"},
Usage: "Mount current directory to the container",
Usage: "Mount current directory to the container",
},
&cli.StringFlag{
Name: "run-cmd",
Expand Down Expand Up @@ -395,6 +425,27 @@ func main() {
return nil
},
},
{
Name: "config",
Aliases: []string{"c"},
Usage: "Ducker config file generation",
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "global",
Aliases: []string{"g"},
Usage: "Generate global config file at $HOME/.ducker_global.yaml",
},
&cli.BoolFlag{
Name: "local",
Aliases: []string{"l"},
Usage: "Generate local config file at $PWD/.ducker.yaml",
},
},
Action: func(cCtx *cli.Context) error {
duckerConfig(cCtx)
return nil
},
},
},
Action: func(cCtx *cli.Context) error {
cli.ShowAppHelp(cCtx)
Expand Down
Loading

0 comments on commit 7777e56

Please sign in to comment.