1
0
mirror of https://github.com/Luzifer/gen-dockerfile.git synced 2024-09-16 15:08:28 +00:00
gen-dockerfile/main.go
Knut Ahlers 2e6cf17022
Add korvike functions to templating
Signed-off-by: Knut Ahlers <knut@ahlers.me>
2021-10-07 14:39:35 +02:00

216 lines
5.7 KiB
Go

package main
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"regexp"
"strings"
"text/template"
"github.com/fatih/color"
homedir "github.com/mitchellh/go-homedir"
diff "github.com/sergi/go-diff/diffmatchpatch"
log "github.com/sirupsen/logrus"
"github.com/Luzifer/go_helpers/v2/env"
"github.com/Luzifer/go_helpers/v2/str"
"github.com/Luzifer/korvike/functions"
"github.com/Luzifer/rconfig/v2"
)
var (
cfg = struct {
Diff bool `flag:"diff" default:"false" description:"Show a diff to existing Dockerfile"`
ExposedPorts []string `flag:"expose,e" default:"" description:"Ports to expose (format '8000' or '8000/tcp')"`
Feature []string `flag:"feature,f" default:"" description:"Enable feature defined in template"`
LogLevel string `flag:"log-level" default:"info" description:"Log level (debug, info, warn, error, fatal)"`
TemplateFile string `flag:"template-file" default:"~/.config/gen-dockerfile.tpl" description:"Template to use for generating the docker file"`
Timezone string `flag:"timezone,t" default:"" description:"Set timezone in Dockerfile (format 'Europe/Berlin')"`
Variables []string `flag:"var" description:"Set variables to use in template (format key=value)"`
VersionAndExit bool `flag:"version" default:"false" description:"Prints current version and exits"`
Volumes []string `flag:"volume,v" default:"" description:"Volumes to create mount points for (format '/data')"`
Write bool `flag:"write,w" default:"false" description:"Directly write into Dockerfile"`
}{}
version = "dev"
)
func init() {
rconfig.AutoEnv(true)
if err := rconfig.ParseAndValidate(&cfg); err != nil {
log.Fatalf("Unable to parse commandline options: %s", err)
}
if cfg.VersionAndExit {
fmt.Printf("gen-dockerfile %s\n", version)
os.Exit(0)
}
if l, err := log.ParseLevel(cfg.LogLevel); err != nil {
log.WithError(err).Fatal("Unable to parse log level")
} else {
log.SetLevel(l)
}
}
func main() {
pkg, err := getPackage()
if err != nil {
log.WithError(err).Fatal("Could not get package name")
}
gitName, err := getGitConfig("user.name")
if err != nil {
log.WithError(err).Fatal("Could not get git user.name")
}
gitEmail, err := getGitConfig("user.email")
if err != nil {
log.WithError(err).Fatal("Could not get git user.email")
}
params := map[string]interface{}{
"binary": getBinaryName(pkg),
"expose": deleteEmpty(cfg.ExposedPorts),
"git_mail": gitEmail,
"git_name": gitName,
"package": pkg,
"timezone": cfg.Timezone,
"volumes": `"` + strings.Join(cfg.Volumes, `", "`) + `"`,
}
for k, v := range env.ListToMap(cfg.Variables) {
if _, ok := params[k]; ok {
log.WithField("variable", k).Fatal("Overwriting variables is not supported")
}
params[k] = v
}
tplPath, err := homedir.Expand(cfg.TemplateFile)
if err != nil {
log.WithError(err).Fatal("Could not find users homedir")
}
tpl, err := template.New("gen-dockerfile.tpl").Funcs(templateFuncs()).ParseFiles(tplPath)
if err != nil {
log.WithError(err).Fatalf("Could not parse template %q", tplPath)
}
buf := new(bytes.Buffer)
if err := tpl.Execute(buf, params); err != nil {
log.WithError(err).Fatalf("Could not render template %q", tplPath)
}
var output io.Writer = os.Stdout
if cfg.Diff {
displayDiff(buf)
output = ioutil.Discard
}
if cfg.Write {
f, err := os.Create("Dockerfile")
if err != nil {
log.WithError(err).Fatal("Could not open Dockerfile for writing")
}
defer f.Close()
output = f
}
fmt.Fprintln(output, strings.TrimSpace(regexp.MustCompile(`\n{3,}`).ReplaceAllString(buf.String(), "\n\n")))
}
func displayDiff(buf *bytes.Buffer) {
var (
lenOldDockerfile int
oldDockerfile []byte
oldName = "/dev/null"
)
if _, err := os.Stat("Dockerfile"); err == nil {
oldDockerfile, err = ioutil.ReadFile("Dockerfile")
if err != nil {
log.WithError(err).Fatal("Could not read existing Dockerfile")
}
lenOldDockerfile = len(bytes.Split(oldDockerfile, []byte{'\n'}))
oldName = "a/Dockerfile"
}
differ := diff.New()
wSrc, wDst, warray := differ.DiffLinesToRunes(string(oldDockerfile), regexp.MustCompile(`\n{3,}`).ReplaceAllString(buf.String(), "\n\n"))
diffs := differ.DiffMainRunes(wSrc, wDst, false)
diffs = differ.DiffCharsToLines(diffs, warray)
if len(diffs) == 1 && diffs[0].Type == diff.DiffEqual {
// No diff, everything equal: Nothing to display
return
}
fmt.Printf("--- %s\n", oldName)
fmt.Println("+++ b/Dockerfile")
color.Cyan("@@ -0,%d +0,%d @@",
lenOldDockerfile,
len(strings.Split(differ.DiffText2(diffs), "\n")),
)
for _, d := range diffs {
text := d.Text
if text[len(text)-1] == '\n' {
text = text[:len(text)-1]
}
for _, l := range strings.Split(text, "\n") {
switch d.Type {
case diff.DiffInsert:
color.Green("+ %s\n", l)
case diff.DiffDelete:
color.Red("- %s\n", l)
case diff.DiffEqual:
fmt.Printf(" %s\n", l)
}
}
}
}
func getPackage() (string, error) {
cwd, err := os.Getwd()
if err != nil {
return "", err
}
return strings.Replace(cwd, os.Getenv("GOPATH")+"/src/", "", -1), nil
}
func getBinaryName(pkg string) string {
parts := strings.Split(pkg, "/")
return parts[len(parts)-1]
}
func getGitConfig(config string) (string, error) {
buf := new(bytes.Buffer)
cmd := exec.Command("git", "config", "--get", config)
cmd.Stdout = buf
err := cmd.Run()
return strings.TrimSpace(buf.String()), err
}
func deleteEmpty(s []string) []string {
var r []string
for _, str := range s {
if str != "" {
r = append(r, str)
}
}
return r
}
func templateFuncs() template.FuncMap {
funcMap := functions.GetFunctionMap()
funcMap["hasFeature"] = func(name string) bool { return str.StringInSlice(name, cfg.Feature) }
return funcMap
}