This commit is contained in:
2021-09-29 08:55:11 -03:00
parent 85e464c559
commit 481ebc30e0
15 changed files with 856 additions and 5 deletions
+19
View File
@@ -0,0 +1,19 @@
package lib
import "github.com/alecthomas/kong"
var CLI = struct {
File string `help:"File to be used - Defaults are: .mk.yaml, .mk, mk, mk.yaml" short:"f"`
Init bool `help:"Creates a new empty file (default is .mk.yaml in case no filename is provided)" short:"i"`
Tasks []string `arg:"" help:"Tasks to be run - Default is main." default:"."`
Ver bool `help:"Prints version and exit" short:"v"`
List bool `help:"Check file and print tasks" short:"l"`
Dbg bool `help:"Debugs execution" short:"d"`
DumpValidator bool `help:"Dumps Validator JSON File" default:"false"`
Env bool `help:"Dumps env and vars" default:"false" short:"e"`
}{}
func InitCli() {
kong.Parse(&CLI)
Ver(CLI.Ver)
}
+95
View File
@@ -0,0 +1,95 @@
package lib
import (
"archive/zip"
"errors"
"fmt"
"io"
"os"
"path"
"path/filepath"
"strings"
"time"
)
const (
CMD_ZIP = "zip"
CMD_PACK = "pack"
)
func RecursiveZip(i int, pathToZip, destinationPath string) error {
Log(i, "mk:zip", "O", "Starting Zip")
destinationFile, err := os.Create(destinationPath)
if err != nil {
return err
}
myZip := zip.NewWriter(destinationFile)
err = filepath.Walk(pathToZip, func(filePath string, info os.FileInfo, err error) error {
Log(i, "mk:zip", "O", fmt.Sprintf("Adding file: %s", filePath))
if info.IsDir() {
return nil
}
if err != nil {
return err
}
relPath := strings.TrimPrefix(filePath, filepath.Dir(pathToZip))
zipFile, err := myZip.Create(relPath)
if err != nil {
return err
}
fsFile, err := os.Open(filePath)
if err != nil {
return err
}
_, err = io.Copy(zipFile, fsFile)
if err != nil {
return err
}
return nil
})
if err != nil {
return err
}
err = myZip.Close()
if err != nil {
return err
}
Log(i, "mk:zip", "O", fmt.Sprintf("Zip finished: %s", destinationPath))
return nil
}
func RunMkCmd(i int, line string) error {
parts := strings.Split(line, " ")
switch parts[0] {
case CMD_ZIP:
if len(parts) < 3 {
return errors.New(fmt.Sprintf("Not enought params: must be zip <dir> <zipfile>. Got: %s", strings.Join(parts, " ")))
}
return RecursiveZip(i, parts[1], parts[2])
case CMD_PACK:
var ptz string
if len(parts) > 1 {
ptz = parts[1]
} else {
for _, f := range []string{"stage", "deploy", "pack", "release"} {
st, err := os.Stat(f)
if err == nil && st.IsDir() {
ptz = f
break
}
}
}
dir, err := os.Getwd()
if err != nil {
return err
}
dname := path.Base(dir)
ts := time.Now().Format("060102150405")
fname := dname + "-" + ts + ".zip"
return RecursiveZip(i, ptz, fname)
default:
Log(i, parts[0], "E", fmt.Sprintf("mk:cmd %s is not known", parts[0]))
return errors.New(fmt.Sprintf("mk:cmd %s is not known", parts[0]))
}
}
+20
View File
@@ -0,0 +1,20 @@
package lib
import (
_ "embed"
"os"
)
//go:embed res/.mk.yaml
var sample []byte
//go:embed res/mk.json
var validator []byte
func InitFile() error {
return os.WriteFile(CLI.File, sample, 0600)
}
func DumpValidator() error {
return os.WriteFile(CLI.File, validator, 0600)
}
+22
View File
@@ -0,0 +1,22 @@
# SAMPLE mk file
default: main
env:
tasks:
deploy:
pre: [ build,test ]
help: Deploys the project
cmd: echo deploying
test:
pre: [ c ]
help: Tests project
cmd: echo testing
build:
help: Build binaries
cmd: echo building
main:
help: Main task
pre: [ build ]
cmd: |-
echo main
echo done
+89
View File
@@ -0,0 +1,89 @@
{
"$id": "https://www.digitalticircle.com.br/_schemas/mk.json",
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Digital Circle - Make Instructions",
"type": "object",
"additionalProperties": false,
"$d": {
"task": {
"type": "object",
"properties": {
"pre": {
"description": "Predecessor tasks",
"type": "array",
"items": {
"type": "string"
}
},
"cmd": {
"description": "Command this task should run",
"type": "string"
},
"help": {
"description": "Help for this task",
"type": "string"
},
"onerror": {
"description": "What to do in case of error"
},
"env": {
"type": "object",
"description": "Global Env Var",
"patternProperties": {
".{1.}": {
"type": "string"
}
}
},
"vars": {
"type": "object",
"description": "Global Text Var",
"patternProperties": {
".{1.}": {
"type": "string"
}
}
}
},
"additionalProperties": "false"
}
},
"properties": {
"default": {
"type": "string",
"default": "main",
"description": "Default task to run"
},
"tasks": {
"type": "object",
"patternProperties": {
".{1,}": {
"allOf": [
{
"$ref": "#/$d/task"
}
]
}
},
"description": "Tasks configured for execution"
},
"env": {
"type": "object",
"description": "Global Env Var",
"patternProperties": {
".{1.}": {
"type": "string"
}
}
},
"vars": {
"type": "object",
"description": "Global Text Var",
"patternProperties": {
".{1.}": {
"type": "string"
}
}
}
}
}
+396
View File
@@ -0,0 +1,396 @@
package lib
import (
"bufio"
"errors"
"fmt"
"github.com/fatih/color"
"gopkg.in/yaml.v3"
"log"
"os"
"os/exec"
"os/user"
"runtime"
"sort"
"strings"
"sync"
"time"
)
var model *MkModel
func FileExists(filename string) bool {
info, err := os.Stat(filename)
if os.IsNotExist(err) {
return false
}
return !info.IsDir()
}
func ResolveConfig() (err error) {
if CLI.File != "" {
if CLI.Init && FileExists(CLI.File) {
println(fmt.Sprintf("Using file: %s", CLI.File))
return
} else {
err = errors.New(fmt.Sprintf("Config File %s not found", CLI.File))
}
}
deffiles := []string{".mk.yaml", ".mk", "mk", "mk.yaml"}
for _, v := range deffiles {
if CLI.Init || FileExists(v) {
println(fmt.Sprintf("Using file: %s", v))
CLI.File = v
return
}
}
err = errors.New(fmt.Sprintf("No Config File found"))
return
}
func ResolveTask(n string) *MkTask {
osname := runtime.GOOS
arch := runtime.GOARCH
vars["BASETASK"] = n
t, ok := model.Tasks[n+"_"+osname+"_"+arch]
if ok {
vars["TASK"] = n + "_" + osname + "_" + arch
return t
}
t, ok = model.Tasks[n+"_"+osname]
if ok {
vars["TASK"] = n + "_" + osname
return t
}
vars["TASK"] = n
return model.Tasks[n]
}
func Log(i int, task string, stream string, b string) {
timeLb := time.Now().Format("15:04:05")
tab := ""
for x := 0; x < i; x++ {
tab = tab + "\t"
}
if stream == "O" {
println(color.GreenString(fmt.Sprintf("%s %s [%s - %s]: %s", timeLb, tab, task, stream, b)))
} else if stream == "E" {
println(color.RedString(fmt.Sprintf("%s %s [%s - %s]: %s", timeLb, tab, task, stream, b)))
} else {
println(fmt.Sprintf("%s %s [%s - %s]: %s", timeLb, tab, task, stream, b))
}
}
func RunTask(name string, t *MkTask, l int) error {
for k, v := range t.Env {
env[k] = v
}
for k, v := range t.Vars {
vars[k] = v
}
Log(l, name, "**", fmt.Sprintf("Starting %s", name))
if len(t.Pre) > 0 {
Log(l, name, "**", fmt.Sprintf("Will run Pre tasks: [%s]", strings.Join(t.Pre, ",")))
for _, v := range t.Pre {
caller, repeat := model.Stack[v]
if repeat {
Log(l, name, "**", fmt.Sprintf("Task %s already called by %s - skipping.", v, caller))
continue
} else {
model.Stack[v] = name
}
pr, ok := model.Tasks[v]
if ok {
err := RunTask(v, pr, l+1)
if err != nil {
return err
}
} else {
return errors.New(fmt.Sprintf("Task %s, prereq of: %s not found in model", v, name))
}
}
}
var c *exec.Cmd
if runtime.GOOS == "windows" {
c = exec.Command("cmd.exe")
} else {
c = exec.Command("sh")
}
for k, v := range env {
c.Env = append(c.Env, fmt.Sprintf("%s=%s", k, v))
}
pi, _ := c.StdinPipe()
po, err := c.StdoutPipe()
if err != nil {
log.Printf(err.Error())
return err
}
scannero := bufio.NewScanner(po)
pe, err := c.StderrPipe()
if err != nil {
log.Printf(err.Error())
return err
}
scannere := bufio.NewScanner(pe)
wg := sync.WaitGroup{}
wg.Add(2)
ch := make(chan string)
go func() {
time.Sleep(time.Millisecond * 100)
defer wg.Done()
for scannero.Scan() {
line := scannero.Text()
if strings.Contains(line, "__CMD_ENDED__:") {
ret := strings.Split(line, "__CMD_ENDED__:")[1]
if ret != "%errorlevel%" {
ch <- ret
}
} else {
Log(l+1, name, "O", line)
}
}
}()
go func() {
time.Sleep(time.Millisecond * 100)
defer wg.Done()
for scannere.Scan() {
Log(l+1, name, "E", scannere.Text())
}
}()
err = c.Start()
if err != nil {
return err
}
lines := bufio.NewScanner(strings.NewReader(t.Cmd))
//if runtime.GOOS == "windows" {
// pi.Write([]byte("@echo off\n"))
//}
for lines.Scan() {
txt := lines.Text()
for k, v := range vars {
txt = strings.Replace(txt, fmt.Sprintf("${%s}", k), v, -1)
}
if strings.HasPrefix(txt, "@") {
txt = strings.TrimLeft(txt, "@")
} else {
Log(l+1, name, "I", txt)
}
if strings.HasPrefix(txt, "mk:") {
txt = strings.Replace(txt, "mk:", "", 1)
txt = strings.TrimSpace(txt)
err = RunMkCmd(l+1, txt)
if err != nil {
log.Printf("Error running: %s", txt)
}
} else {
_, err = pi.Write([]byte(txt + "\n"))
if runtime.GOOS == "windows" {
_, err = pi.Write([]byte("echo __CMD_ENDED__:%errorlevel%" + "\n"))
} else {
_, err = pi.Write([]byte("echo __CMD_ENDED__:$?" + "\n"))
}
ret := <-ch
if ret != "0" && t.Onerror == "skip" {
Log(l+1, name, "E", "Error Code: "+ret+" will continue, but watch out")
} else if ret != "0" && t.Onerror != "skip" {
Log(l+1, name, "E", "Error Code is: "+ret+" ABORTING")
break
}
}
}
_, err = pi.Write([]byte("exit\n"))
if err != nil {
return err
}
wg.Wait()
err = c.Wait()
Log(l, name, "**", "End")
return err
}
func DumpEnv() {
if CLI.Env {
println("Vars:")
varnames := make([]string, 0)
for k := range vars {
varnames = append(varnames, k)
}
sort.Strings(varnames)
for _, k := range varnames {
v := vars[k]
println(fmt.Sprintf("%s => %s", k, v))
}
println("========")
println("Env:")
envnames := make([]string, 0)
for k := range env {
envnames = append(envnames, k)
}
sort.Strings(envnames)
for _, k := range envnames {
v := env[k]
println(fmt.Sprintf("%s => %s", k, v))
}
os.Exit(0)
}
}
func Prepare() error {
InitCli()
err := ResolveConfig()
if err != nil {
return err
}
if CLI.Init {
err = InitFile()
if err != nil {
return err
}
os.Exit(0)
}
if CLI.DumpValidator {
err = DumpValidator()
if err != nil {
log.Printf("Error DumpValidator: %s", err.Error())
}
os.Exit(0)
}
bs, err := os.ReadFile(CLI.File)
if err != nil {
panic(err)
}
model = &MkModel{}
err = yaml.Unmarshal(bs, model)
if err != nil {
return err
}
if model.Default == "" {
model.Default = "main"
}
for k, v := range model.Tasks {
v.Name = k
}
if CLI.List {
println("Tasks:")
tasknames := make([]string, 0)
for k := range model.Tasks {
tasknames = append(tasknames, k)
}
sort.Strings(tasknames)
for _, k := range tasknames {
v := model.Tasks[k]
def := ""
if v.Name == model.Default {
def = "DEF>"
} else {
def = ""
}
println(fmt.Sprintf("%s %s [%s]: %s", def, k, strings.Join(v.Pre, ","), v.Help))
}
os.Exit(0)
}
if len(CLI.Tasks) < 1 || CLI.Tasks[0] == "" || CLI.Tasks[0] == "." {
CLI.Tasks = []string{model.Default}
}
return err
}
var env map[string]string
var vars map[string]string
func Run() error {
env = make(map[string]string)
vars = make(map[string]string)
envstrs := os.Environ()
for _, k := range envstrs {
parts := strings.Split(k, "=")
ek := strings.TrimSpace(parts[0])
ev := os.Getenv(ek)
env[ek] = ev
}
err := Prepare()
if err != nil {
log.Printf("Could not execute: %s", err.Error())
os.Exit(0)
}
for k, v := range model.Env {
env[k] = v
}
for k, v := range model.Vars {
vars[k] = v
}
now := time.Now()
vars["DT_YYMMDDHHmmss"] = now.Format("060102150405")
vars["DT_YYMMDD"] = now.Format("060102")
vars["DS_TS"] = fmt.Sprintf("%d", now.Unix())
u, err := user.Current()
if err != nil {
panic(err)
}
vars["USERNAME"] = u.Username
vars["HOMEDIR"] = u.HomeDir
if err != nil {
return err
}
DumpEnv()
model.Stack = make(map[string]string)
for _, v := range CLI.Tasks {
tasko := ResolveTask(v)
if tasko != nil {
err = RunTask(v, tasko, 0)
} else {
return errors.New(fmt.Sprintf("No task named %s found", v))
}
}
return err
}
+18
View File
@@ -0,0 +1,18 @@
package lib
type MkTask struct {
Name string
Help string
Cmd string `yaml:"cmd"`
Pre []string `yaml:"pre"`
Onerror string `yaml:"onerror"`
Env map[string]string `yaml:"env"`
Vars map[string]string `yaml:"vars"`
}
type MkModel struct {
Env map[string]string `yaml:"env"`
Vars map[string]string `yaml:"vars"`
Tasks map[string]*MkTask `yaml:"tasks"`
Default string `yaml:"default"`
Stack map[string]string `yaml:"-"`
}
+15
View File
@@ -0,0 +1,15 @@
package lib
import "os"
import _ "embed"
//go:embed ver.txt
var ver string
func Ver(end bool) {
println("DC MK Tool - ver: " + ver)
if end {
os.Exit(0)
}
}
+1
View File
@@ -0,0 +1 @@
Sat Sep 18 20:38:27 -03 2021