diff --git a/.vscode/settings.json b/.vscode/settings.json index cf92c18..74e4a97 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,6 +2,7 @@ "cSpell.words": [ "charsets", "exem", + "injectenv", "menv", "merrs", "mexec", diff --git a/internal/menv/construct.go b/internal/menv/construct.go index 5e5108b..313d5f6 100644 --- a/internal/menv/construct.go +++ b/internal/menv/construct.go @@ -11,20 +11,12 @@ const ( ) // Construct create the env map with current system environ, extra and rendering MINIT_ENV_ prefixed keys -func Construct(extra map[string]string) (envs map[string]string, err error) { +func Construct(sys map[string]string, extra map[string]string) (envs map[string]string, err error) { envs = make(map[string]string) // system env - for _, item := range osEnviron() { - splits := strings.SplitN(item, "=", 2) - var key, val string - if len(splits) > 0 { - key = splits[0] - if len(splits) > 1 { - val = splits[1] - } - envs[key] = val - } + for key, val := range sys { + envs[key] = val } // merge extra env diff --git a/internal/menv/construct_test.go b/internal/menv/construct_test.go index a728943..d9f187e 100644 --- a/internal/menv/construct_test.go +++ b/internal/menv/construct_test.go @@ -1,37 +1,15 @@ package menv import ( - "os" "testing" "github.com/stretchr/testify/require" ) -func replaceTestEnv(m map[string]string) { - osGetenv = func(key string) string { - return m[key] - } - osEnviron = func() []string { - var env []string - for k, v := range m { - env = append(env, k+"="+v) - } - return env - } -} - -func restoreTestEnv() { - osGetenv = os.Getenv - osEnviron = os.Environ -} - -func TestBuild(t *testing.T) { - replaceTestEnv(map[string]string{ - "HOME": "/home/minit", - }) - defer restoreTestEnv() - +func TestConstruct(t *testing.T) { envs, err := Construct(map[string]string{ + "HOME": "/home/minit", + }, map[string]string{ "HOME-": "NONE", "MINIT_ENV_BUF": "{{stringsToUpper \"bbb\"}}", }) diff --git a/internal/menv/environ.go b/internal/menv/environ.go new file mode 100644 index 0000000..bb7d3a5 --- /dev/null +++ b/internal/menv/environ.go @@ -0,0 +1,23 @@ +package menv + +import ( + "os" + "strings" +) + +// Environ returns the system environment variables as a map +func Environ() (m map[string]string) { + m = make(map[string]string) + for _, item := range os.Environ() { + splits := strings.SplitN(item, "=", 2) + var key, val string + if len(splits) > 0 { + key = splits[0] + if len(splits) > 1 { + val = splits[1] + } + m[key] = val + } + } + return +} diff --git a/internal/menv/environ_test.go b/internal/menv/environ_test.go new file mode 100644 index 0000000..129e314 --- /dev/null +++ b/internal/menv/environ_test.go @@ -0,0 +1,12 @@ +package menv + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestEnviron(t *testing.T) { + m := Environ() + require.NotEmpty(t, m["SHELL"]) +} diff --git a/internal/menv/os_inject.go b/internal/menv/os_inject.go deleted file mode 100644 index c9d3c70..0000000 --- a/internal/menv/os_inject.go +++ /dev/null @@ -1,8 +0,0 @@ -package menv - -import "os" - -var ( - osEnviron = os.Environ - osGetenv = os.Getenv -) diff --git a/internal/mexec/manager.go b/internal/mexec/manager.go index 0c495c2..6e3c77b 100644 --- a/internal/mexec/manager.go +++ b/internal/mexec/manager.go @@ -99,7 +99,7 @@ func (m *manager) Execute(opts ExecuteOptions) (err error) { // build env var env map[string]string - if env, err = menv.Construct(opts.Env); err != nil { + if env, err = menv.Construct(menv.Environ(), opts.Env); err != nil { err = errors.New("failed constructing environment variables: " + err.Error()) return } diff --git a/internal/mrunners/runner_render.go b/internal/mrunners/runner_render.go index e1fa4fb..ad99ebe 100644 --- a/internal/mrunners/runner_render.go +++ b/internal/mrunners/runner_render.go @@ -90,7 +90,7 @@ func (r *actionRender) Do(ctx context.Context) (err error) { var env map[string]string - if env, err = menv.Construct(r.Unit.Env); err != nil { + if env, err = menv.Construct(menv.Environ(), r.Unit.Env); err != nil { err = r.PanicOnCritical("failed constructing environments variables", err) return } diff --git a/internal/munit/loader.go b/internal/munit/load.go similarity index 80% rename from internal/munit/loader.go rename to internal/munit/load.go index 93b859e..56a706a 100644 --- a/internal/munit/loader.go +++ b/internal/munit/load.go @@ -2,9 +2,9 @@ package munit import ( "errors" + "os" "regexp" "strconv" - "strings" ) var ( @@ -18,26 +18,13 @@ const ( PrefixKind = "&" ) -type Loader struct { - filter *Filter -} - -func NewLoader() (ld *Loader) { - return &Loader{ - filter: NewFilter( - strings.TrimSpace(osGetenv("MINIT_ENABLE")), - strings.TrimSpace(osGetenv("MINIT_DISABLE")), - ), - } -} - type LoadOptions struct { Args []string - Env bool + Env map[string]string Dir string } -func (ld *Loader) Load(opts LoadOptions) (output []Unit, skipped []Unit, err error) { +func Load(opts LoadOptions) (output []Unit, skipped []Unit, err error) { var units []Unit // load units @@ -48,6 +35,7 @@ func (ld *Loader) Load(opts LoadOptions) (output []Unit, skipped []Unit, err err } units = append(units, dUnits...) } + if len(opts.Args) > 0 { var unit Unit var ok bool @@ -58,26 +46,35 @@ func (ld *Loader) Load(opts LoadOptions) (output []Unit, skipped []Unit, err err units = append(units, unit) } } - if opts.Env { + + filter := NewFilter("", "") + + if opts.Env != nil { + + filter = NewFilter( + opts.Env["MINIT_ENABLE"], + opts.Env["MINIT_DISABLE"], + ) + { // legacy minit main var ( unit Unit ok bool ) - if unit, ok, err = LoadEnv(); err != nil { + if unit, ok, err = LoadEnv(opts.Env); err != nil { return } else if ok { units = append(units, unit) } } - for _, infix := range DetectEnvInfixes() { + for _, infix := range DetectEnvInfixes(opts.Env) { var ( unit Unit ok bool ) - if unit, ok, err = LoadEnvWithInfix(infix); err != nil { + if unit, ok, err = LoadEnvWithInfix(opts.Env, infix); err != nil { return } if ok { @@ -120,14 +117,16 @@ func (ld *Loader) Load(opts LoadOptions) (output []Unit, skipped []Unit, err err } // skip if needed - if !ld.filter.Match(unit) { + if !filter.Match(unit) { skipped = append(skipped, unit) continue } // eval cron - if unit.Cron != "" { - unit.Cron = osExpandEnv(unit.Cron) + if unit.Cron != "" && opts.Env != nil { + unit.Cron = os.Expand(unit.Cron, func(s string) string { + return opts.Env[s] + }) } // replicas diff --git a/internal/munit/load_env.go b/internal/munit/load_env.go index 8562210..ba007bd 100644 --- a/internal/munit/load_env.go +++ b/internal/munit/load_env.go @@ -13,16 +13,10 @@ const ( ) // DetectEnvInfixes detects infixes from environment variables -func DetectEnvInfixes() (infixes []string) { +func DetectEnvInfixes(env map[string]string) (infixes []string) { _infixes := map[string]struct{}{} - for _, item := range osEnviron() { - splits := strings.SplitN(item, "=", 2) - if len(splits) != 2 { - continue - } - - key := splits[0] + for key := range env { if !strings.HasPrefix(key, EnvPrefixUnit) { continue } @@ -40,7 +34,7 @@ func DetectEnvInfixes() (infixes []string) { if key == "" { continue } - if osGetenv(EnvPrefixUnit+key+"_KIND") == KindRender { + if env[EnvPrefixUnit+key+"_KIND"] == KindRender { _infixes[key] = struct{}{} } } @@ -55,9 +49,9 @@ func DetectEnvInfixes() (infixes []string) { // LoadEnvWithInfix loads unit from environment variables with infix // e.g. MINIT_UNIT_HELLO_KIND, MINIT_UNIT_HELLO_NAME, MINIT_UNIT_HELLO_COMMAND -func LoadEnvWithInfix(infix string) (unit Unit, ok bool, err error) { +func LoadEnvWithInfix(env map[string]string, infix string) (unit Unit, ok bool, err error) { // kind - unit.Kind = osGetenv(EnvPrefixUnit + infix + "_KIND") + unit.Kind = env[EnvPrefixUnit+infix+"_KIND"] if unit.Kind == "" { unit.Kind = KindDaemon @@ -71,17 +65,17 @@ func LoadEnvWithInfix(infix string) (unit Unit, ok bool, err error) { } // name, group, count - unit.Name = osGetenv(EnvPrefixUnit + infix + "_NAME") + unit.Name = env[EnvPrefixUnit+infix+"_NAME"] if unit.Name == "" { unit.Name = "env-" + strings.ToLower(infix) } - unit.Group = osGetenv(EnvPrefixUnit + infix + "_GROUP") - unit.Count, _ = strconv.Atoi(osGetenv(EnvPrefixUnit + infix + "_COUNT")) + unit.Group = env[EnvPrefixUnit+infix+"_GROUP"] + unit.Count, _ = strconv.Atoi(env[EnvPrefixUnit+infix+"_COUNT"]) // command, dir, shell, charset switch unit.Kind { case KindDaemon, KindOnce, KindCron: - cmd := osGetenv(EnvPrefixUnit + infix + "_COMMAND") + cmd := env[EnvPrefixUnit+infix+"_COMMAND"] if unit.Command, err = shellquote.Split(cmd); err != nil { return @@ -92,11 +86,11 @@ func LoadEnvWithInfix(infix string) (unit Unit, ok bool, err error) { return } - unit.Dir = osGetenv(EnvPrefixUnit + infix + "_DIR") - unit.Shell = osGetenv(EnvPrefixUnit + infix + "_SHELL") - unit.Charset = osGetenv(EnvPrefixUnit + infix + "_CHARSET") + unit.Dir = env[EnvPrefixUnit+infix+"_DIR"] + unit.Shell = env[EnvPrefixUnit+infix+"_SHELL"] + unit.Charset = env[EnvPrefixUnit+infix+"_CHARSET"] - for _, item := range strings.Split(osGetenv(EnvPrefixUnit+infix+"_ENV"), ";") { + for _, item := range strings.Split(env[EnvPrefixUnit+infix+"_ENV"], ";") { item = strings.TrimSpace(item) splits := strings.SplitN(item, "=", 2) if len(splits) == 2 { @@ -110,21 +104,21 @@ func LoadEnvWithInfix(infix string) (unit Unit, ok bool, err error) { // cron, immediate if unit.Kind == KindCron { - unit.Cron = osGetenv(EnvPrefixUnit + infix + "_CRON") + unit.Cron = env[EnvPrefixUnit+infix+"_CRON"] if unit.Cron == "" { err = errors.New("missing environment variable $" + EnvPrefixUnit + infix + "_CRON while $" + EnvPrefixUnit + infix + "_KIND is 'cron'") return } - unit.Immediate, _ = strconv.ParseBool(osGetenv(EnvPrefixUnit + infix + "_IMMEDIATE")) + unit.Immediate, _ = strconv.ParseBool(env[EnvPrefixUnit+infix+"_IMMEDIATE"]) } // raw, files if unit.Kind == KindRender { - unit.Raw, _ = strconv.ParseBool(osGetenv(EnvPrefixUnit + infix + "_RAW")) + unit.Raw, _ = strconv.ParseBool(env[EnvPrefixUnit+infix+"_RAW"]) - for _, item := range strings.Split(osGetenv(EnvPrefixUnit+infix+"_FILES"), ";") { + for _, item := range strings.Split(env[EnvPrefixUnit+infix+"_FILES"], ";") { item = strings.TrimSpace(item) if item != "" { unit.Files = append(unit.Files, item) @@ -139,17 +133,17 @@ func LoadEnvWithInfix(infix string) (unit Unit, ok bool, err error) { // blocking if unit.Kind == KindOnce { - if nb, err := strconv.ParseBool(strings.TrimSpace(osGetenv(EnvPrefixUnit + infix + "_BLOCKING"))); err == nil && !nb { + if nb, err := strconv.ParseBool(strings.TrimSpace(env[EnvPrefixUnit+infix+"_BLOCKING"])); err == nil && !nb { unit.Blocking = new(bool) *unit.Blocking = false } } // critical - unit.Critical, _ = strconv.ParseBool(osGetenv(EnvPrefixUnit + infix + "_CRITICAL")) + unit.Critical, _ = strconv.ParseBool(env[EnvPrefixUnit+infix+"_CRITICAL"]) // success codes - for _, item := range strings.Split(osGetenv(EnvPrefixUnit+infix+"_SUCCESS_CODES"), ",") { + for _, item := range strings.Split(env[EnvPrefixUnit+infix+"_SUCCESS_CODES"], ",") { item = strings.TrimSpace(item) if item == "" { continue @@ -165,13 +159,13 @@ func LoadEnvWithInfix(infix string) (unit Unit, ok bool, err error) { } // LoadEnv loads legacy main unit from environment variables -func LoadEnv() (unit Unit, ok bool, err error) { - cmd := strings.TrimSpace(osGetenv("MINIT_MAIN")) +func LoadEnv(env map[string]string) (unit Unit, ok bool, err error) { + cmd := strings.TrimSpace(env["MINIT_MAIN"]) if cmd == "" { return } - name := strings.TrimSpace(osGetenv("MINIT_MAIN_NAME")) + name := strings.TrimSpace(env["MINIT_MAIN_NAME"]) if name == "" { name = "env-main" } @@ -181,21 +175,21 @@ func LoadEnv() (unit Unit, ok bool, err error) { immediate bool ) - kind := strings.TrimSpace(osGetenv("MINIT_MAIN_KIND")) + kind := strings.TrimSpace(env["MINIT_MAIN_KIND"]) switch kind { case KindDaemon, KindOnce: case KindCron: - cron = strings.TrimSpace(osGetenv("MINIT_MAIN_CRON")) + cron = strings.TrimSpace(env["MINIT_MAIN_CRON"]) if cron == "" { err = errors.New("missing environment variable $MINIT_MAIN_CRON while $MINIT_MAIN_KIND is 'cron'") return } - immediate, _ = strconv.ParseBool(osGetenv("MINIT_MAIN_IMMEDIATE")) + immediate, _ = strconv.ParseBool(env["MINIT_MAIN_IMMEDIATE"]) case "": - if once, _ := strconv.ParseBool(strings.TrimSpace(osGetenv("MINIT_MAIN_ONCE"))); once { + if once, _ := strconv.ParseBool(strings.TrimSpace(env["MINIT_MAIN_ONCE"])); once { kind = KindOnce } else { kind = KindDaemon @@ -212,17 +206,17 @@ func LoadEnv() (unit Unit, ok bool, err error) { unit = Unit{ Name: name, - Group: strings.TrimSpace(osGetenv("MINIT_MAIN_GROUP")), + Group: strings.TrimSpace(env["MINIT_MAIN_GROUP"]), Kind: kind, Cron: cron, Immediate: immediate, Command: cmds, - Dir: strings.TrimSpace(osGetenv("MINIT_MAIN_DIR")), - Charset: strings.TrimSpace(osGetenv("MINIT_MAIN_CHARSET")), + Dir: strings.TrimSpace(env["MINIT_MAIN_DIR"]), + Charset: strings.TrimSpace(env["MINIT_MAIN_CHARSET"]), } if unit.Kind == KindOnce { - if nb, err := strconv.ParseBool(strings.TrimSpace(osGetenv("MINIT_MAIN_BLOCKING"))); err == nil && !nb { + if nb, err := strconv.ParseBool(strings.TrimSpace(env["MINIT_MAIN_BLOCKING"])); err == nil && !nb { unit.Blocking = new(bool) *unit.Blocking = false } diff --git a/internal/munit/load_env_test.go b/internal/munit/load_env_test.go index 455eef3..4d5297f 100644 --- a/internal/munit/load_env_test.go +++ b/internal/munit/load_env_test.go @@ -1,55 +1,29 @@ package munit import ( - "os" "sort" "testing" "github.com/stretchr/testify/require" ) -func replaceTestEnv(m map[string]string) { - osGetenv = func(key string) string { - return m[key] - } - osEnviron = func() []string { - var env []string - for k, v := range m { - env = append(env, k+"="+v) - } - return env - } - osExpandEnv = func(s string) string { - return os.Expand(s, func(key string) string { - return m[key] - }) - } -} - -func restoreTestEnv() { - osGetenv = os.Getenv - osEnviron = os.Environ - osExpandEnv = os.ExpandEnv -} - func TestDetectEnvInfixes(t *testing.T) { - replaceTestEnv(map[string]string{ + env := map[string]string{ "MINIT_UNIT_MAIN_COMMAND": "hello", "MINIT_UNIT_COMMAND": "hello", "MINIT_UNIT_A_FILES": "hello", "MINIT_UNIT_B_FILES": "hello", "MINIT_UNIT_B_KIND": "render", - }) - defer restoreTestEnv() + } - infixes := DetectEnvInfixes() + infixes := DetectEnvInfixes(env) sort.StringSlice(infixes).Sort() require.Equal(t, []string{"B", "MAIN"}, infixes) } func TestLoadEnvWithInfix(t *testing.T) { - replaceTestEnv(map[string]string{ + env := map[string]string{ "MINIT_UNIT_A1_COMMAND": "echo 'hello world'", "MINIT_UNIT_A2_COMMAND": "echo 'hello world'", "MINIT_UNIT_A2_KIND": "cron", @@ -70,10 +44,9 @@ func TestLoadEnvWithInfix(t *testing.T) { "MINIT_UNIT_A4_KIND": "render", "MINIT_UNIT_A4_FILES": "hello.txt;world.txt", "MINIT_UNIT_A4_RAW": "true", - }) - defer restoreTestEnv() + } - unit, ok, err := LoadEnvWithInfix("A1") + unit, ok, err := LoadEnvWithInfix(env, "A1") require.NoError(t, err) require.True(t, ok) require.Equal(t, Unit{ @@ -85,7 +58,7 @@ func TestLoadEnvWithInfix(t *testing.T) { }, }, unit) - unit, ok, err = LoadEnvWithInfix("A2") + unit, ok, err = LoadEnvWithInfix(env, "A2") require.NoError(t, err) require.True(t, ok) require.Equal(t, Unit{ @@ -112,7 +85,7 @@ func TestLoadEnvWithInfix(t *testing.T) { blockingTrue := false - unit, ok, err = LoadEnvWithInfix("A3") + unit, ok, err = LoadEnvWithInfix(env, "A3") require.NoError(t, err) require.True(t, ok) require.Equal(t, Unit{ @@ -125,7 +98,7 @@ func TestLoadEnvWithInfix(t *testing.T) { }, }, unit) - unit, ok, err = LoadEnvWithInfix("A4") + unit, ok, err = LoadEnvWithInfix(env, "A4") require.NoError(t, err) require.True(t, ok) require.Equal(t, Unit{ @@ -140,17 +113,16 @@ func TestLoadEnvWithInfix(t *testing.T) { } func TestLoadEnv(t *testing.T) { - replaceTestEnv(map[string]string{ + env := map[string]string{ "MINIT_MAIN": "hello 'world destroyer'", "MINIT_MAIN_KIND": "cron", "MINIT_MAIN_NAME": "test-main", "MINIT_MAIN_CRON": "1 2 3 4 5", "MINIT_MAIN_GROUP": "bbb", "MINIT_MAIN_CHARSET": "gbk", - }) - defer restoreTestEnv() + } - unit, ok, err := LoadEnv() + unit, ok, err := LoadEnv(env) require.NoError(t, err) require.True(t, ok) require.Equal(t, Unit{ diff --git a/internal/munit/loader_test.go b/internal/munit/load_test.go similarity index 82% rename from internal/munit/loader_test.go rename to internal/munit/load_test.go index 6f8e3d4..7de2238 100644 --- a/internal/munit/loader_test.go +++ b/internal/munit/load_test.go @@ -7,17 +7,16 @@ import ( "github.com/stretchr/testify/require" ) -func TestNewLoader(t *testing.T) { - replaceTestEnv(map[string]string{ +func TestLoad(t *testing.T) { + m := map[string]string{ "MINIT_ENABLE": "@default", "MINIT_DISABLE": "task-3,task-5", "DEBUG_EVERY": "10s", - }) - defer restoreTestEnv() + } - ld := NewLoader() - units, skipped, err := ld.Load(LoadOptions{ + units, skipped, err := Load(LoadOptions{ Dir: "testdata", + Env: m, }) require.NoError(t, err) @@ -25,36 +24,17 @@ func TestNewLoader(t *testing.T) { require.Len(t, skipped, 4) require.Equal(t, "task-4", units[0].Name) require.Equal(t, "@every 10s", units[0].Cron) -} - -func TestDupOrMakeMap(t *testing.T) { - var o map[string]any - dupOrMakeMap(&o) - require.NotNil(t, o) - - m1a := map[string]string{ - "a": "b", - } - m1b := m1a - dupOrMakeMap(&m1a) - m1a["c"] = "d" - require.Equal(t, "d", m1a["c"]) - require.Equal(t, "", m1b["c"]) -} -func TestLoaderWithNewEnv(t *testing.T) { - replaceTestEnv(map[string]string{ + m = map[string]string{ "MINIT_MAIN": "legacy main", "MINIT_UNIT_CACHE_COMMAND": "redis-server", "MINIT_UNIT_INITIAL_COMMAND": "touch /tmp/initial", "MINIT_UNIT_INITIAL_NAME": "job-initial", "MINIT_UNIT_INITIAL_ENV": "ZAA=ZBB;ZCC=ZDD", "MINIT_UNIT_INITIAL_KIND": "once", - }) - defer restoreTestEnv() + } - l := NewLoader() - units, _, err := l.Load(LoadOptions{Env: true}) + units, _, err = Load(LoadOptions{Env: m}) require.NoError(t, err) require.Len(t, units, 3) @@ -74,3 +54,18 @@ func TestLoaderWithNewEnv(t *testing.T) { require.Equal(t, []string{"touch", "/tmp/initial"}, units[2].Command) require.Equal(t, KindOnce, units[2].Kind) } + +func TestDupOrMakeMap(t *testing.T) { + var o map[string]any + dupOrMakeMap(&o) + require.NotNil(t, o) + + m1a := map[string]string{ + "a": "b", + } + m1b := m1a + dupOrMakeMap(&m1a) + m1a["c"] = "d" + require.Equal(t, "d", m1a["c"]) + require.Equal(t, "", m1b["c"]) +} diff --git a/internal/munit/os_inject.go b/internal/munit/os_inject.go deleted file mode 100644 index 503ade8..0000000 --- a/internal/munit/os_inject.go +++ /dev/null @@ -1,9 +0,0 @@ -package munit - -import "os" - -var ( - osEnviron = os.Environ - osGetenv = os.Getenv - osExpandEnv = os.ExpandEnv -) diff --git a/main.go b/main.go index 739dc90..eb307ee 100644 --- a/main.go +++ b/main.go @@ -14,6 +14,7 @@ import ( "syscall" "time" + "github.com/yankeguo/minit/internal/menv" "github.com/yankeguo/minit/internal/mexec" "github.com/yankeguo/minit/internal/mlog" "github.com/yankeguo/minit/internal/mrunners" @@ -113,12 +114,11 @@ func main() { rg.Must0(msetups.Setup(log)) // load units - loader := munit.NewLoader() units, skips := rg.Must2( - loader.Load( + munit.Load( munit.LoadOptions{ Args: os.Args[1:], - Env: true, + Env: menv.Environ(), Dir: optUnitDir, }, ),