Skip to content

Commit a6a511d

Browse files
committed
Refactor actions, global config, structure.
1 parent 224f5ec commit a6a511d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+1345
-1160
lines changed

app.go

Lines changed: 32 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@ import (
88

99
"github.com/spf13/cobra"
1010

11+
"github.com/launchrctl/launchr/internal/launchr/config"
12+
"github.com/launchrctl/launchr/pkg/action"
1113
"github.com/launchrctl/launchr/pkg/cli"
12-
"github.com/launchrctl/launchr/pkg/cobraadapter"
1314
)
1415

1516
// ActionsGroup is a cobra command group definition
@@ -20,14 +21,15 @@ var ActionsGroup = &cobra.Group{
2021

2122
// App holds app related global variables.
2223
type App struct {
23-
rootCmd *cobra.Command
24-
cli cli.Cli
25-
workDir string
26-
cfgDir string
27-
version *AppVersion
28-
actionMngr ActionManager
29-
serviceMngr ServiceManager
30-
pluginMngr PluginManager
24+
rootCmd *cobra.Command
25+
streams cli.Streams
26+
workDir string
27+
cfgDir string
28+
version *AppVersion
29+
services map[ServiceInfo]Service
30+
actionMngr action.Manager
31+
pluginMngr PluginManager
32+
globalCfg GlobalConfig
3133
}
3234

3335
// NewApp constructs app implementation.
@@ -40,24 +42,18 @@ func (app *App) GetWD() string {
4042
return app.workDir
4143
}
4244

43-
// GetCfgDir returns config directory path.
44-
func (app *App) GetCfgDir() string {
45-
return app.cfgDir
45+
// Streams returns application cli.
46+
func (app *App) Streams() cli.Streams {
47+
return app.streams
4648
}
4749

48-
// GetCli returns application cli.
49-
func (app *App) GetCli() cli.Cli {
50-
return app.cli
51-
}
52-
53-
// SetCli sets application cli.
54-
func (app *App) SetCli(c cli.Cli) {
55-
app.cli = c
56-
}
57-
58-
// ServiceManager returns application service manager.
59-
func (app *App) ServiceManager() ServiceManager {
60-
return app.serviceMngr
50+
// AddService registers a service in the app.
51+
func (app *App) AddService(s Service) {
52+
info := s.ServiceInfo()
53+
if _, ok := app.services[info]; ok {
54+
panic(fmt.Errorf("service %s already exists, review your code", info.ID))
55+
}
56+
app.services[info] = s
6157
}
6258

6359
// init initializes application and plugins.
@@ -70,20 +66,14 @@ func (app *App) init() error {
7066
if err != nil {
7167
return err
7268
}
73-
appCli, err := cli.NewAppCli(
74-
cli.WithStandardStreams(),
75-
cli.WithGlobalConfigFromDir(os.DirFS(app.cfgDir)),
76-
)
77-
if err != nil {
78-
return err
79-
}
80-
app.SetCli(appCli)
81-
app.serviceMngr = newServiceManager()
82-
app.actionMngr = newActionManager()
69+
app.streams = cli.StandardStreams()
70+
app.services = make(map[ServiceInfo]Service)
71+
app.actionMngr = action.NewManager()
8372
app.pluginMngr = pluginManagerMap(registeredPlugins)
84-
app.serviceMngr.Add(ServiceManagerID, app.serviceMngr)
85-
app.serviceMngr.Add(ActionManagerID, app.actionMngr)
86-
app.serviceMngr.Add(PluginManagerID, app.pluginMngr)
73+
app.globalCfg = config.GlobalConfigFromFS(os.DirFS(app.cfgDir))
74+
app.AddService(app.actionMngr)
75+
app.AddService(app.pluginMngr)
76+
app.AddService(app.globalCfg)
8777

8878
// Initialize the plugins.
8979
for _, p := range app.pluginMngr.All() {
@@ -115,7 +105,7 @@ func (app *App) exec() error {
115105
rootCmd.AddGroup(ActionsGroup)
116106
}
117107
for _, cmdDef := range actions {
118-
cobraCmd, err := cobraadapter.GetActionImpl(app.GetCli(), cmdDef, ActionsGroup)
108+
cobraCmd, err := action.CobraImpl(cmdDef, app.Streams(), app.globalCfg, ActionsGroup)
119109
if err != nil {
120110
return err
121111
}
@@ -131,9 +121,9 @@ func (app *App) exec() error {
131121

132122
// Set io streams.
133123
app.rootCmd = rootCmd
134-
rootCmd.SetIn(app.cli.In())
135-
rootCmd.SetOut(app.cli.Out())
136-
rootCmd.SetErr(app.cli.Err())
124+
rootCmd.SetIn(app.streams.In())
125+
rootCmd.SetOut(app.streams.Out())
126+
rootCmd.SetErr(app.streams.Err())
137127
return app.rootCmd.Execute()
138128
}
139129

go.mod

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ require (
1212
github.com/spf13/cobra v1.7.0
1313
github.com/stretchr/testify v1.8.4
1414
golang.org/x/mod v0.12.0
15-
golang.org/x/sys v0.10.0
15+
golang.org/x/sys v0.11.0
1616
gopkg.in/yaml.v3 v3.0.1
1717
)
1818

@@ -33,15 +33,15 @@ require (
3333
github.com/morikuni/aec v1.0.0 // indirect
3434
github.com/opencontainers/go-digest v1.0.0 // indirect
3535
github.com/opencontainers/image-spec v1.1.0-rc4 // indirect
36-
github.com/opencontainers/runc v1.1.8 // indirect
36+
github.com/opencontainers/runc v1.1.9 // indirect
3737
github.com/pkg/errors v0.9.1 // indirect
3838
github.com/pmezard/go-difflib v1.0.0 // indirect
3939
github.com/rogpeppe/go-internal v1.10.0 // indirect
4040
github.com/sirupsen/logrus v1.9.3 // indirect
4141
github.com/spf13/pflag v1.0.5 // indirect
42-
golang.org/x/net v0.13.0 // indirect
42+
golang.org/x/net v0.14.0 // indirect
4343
golang.org/x/time v0.3.0 // indirect
44-
golang.org/x/tools v0.11.1 // indirect
44+
golang.org/x/tools v0.12.0 // indirect
4545
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
4646
gotest.tools/v3 v3.4.0 // indirect
4747
)

go.sum

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ github.com/opencontainers/runc v1.1.7 h1:y2EZDS8sNng4Ksf0GUYNhKbTShZJPJg1FiXJNH/
8080
github.com/opencontainers/runc v1.1.7/go.mod h1:CbUumNnWCuTGFukNXahoo/RFBZvDAgRh/smNYNOhA50=
8181
github.com/opencontainers/runc v1.1.8 h1:zICRlc+C1XzivLc3nzE+cbJV4LIi8tib6YG0MqC6OqA=
8282
github.com/opencontainers/runc v1.1.8/go.mod h1:CbUumNnWCuTGFukNXahoo/RFBZvDAgRh/smNYNOhA50=
83+
github.com/opencontainers/runc v1.1.9 h1:XR0VIHTGce5eWPkaPesqTBrhW2yAcaraWfsEalNwQLM=
84+
github.com/opencontainers/runc v1.1.9/go.mod h1:CbUumNnWCuTGFukNXahoo/RFBZvDAgRh/smNYNOhA50=
8385
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
8486
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
8587
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -127,6 +129,8 @@ golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50=
127129
golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
128130
golang.org/x/net v0.13.0 h1:Nvo8UFsZ8X3BhAC9699Z1j7XQ3rsZnUUm7jfBEk1ueY=
129131
golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
132+
golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
133+
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
130134
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
131135
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
132136
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -146,6 +150,8 @@ golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
146150
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
147151
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
148152
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
153+
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
154+
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
149155
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
150156
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
151157
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@@ -163,6 +169,8 @@ golang.org/x/tools v0.11.0 h1:EMCa6U9S2LtZXLAMoWiR/R8dAQFRqbAitmbJ2UKhoi8=
163169
golang.org/x/tools v0.11.0/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8=
164170
golang.org/x/tools v0.11.1 h1:ojD5zOW8+7dOGzdnNgersm8aPfcDjhMp12UfG93NIMc=
165171
golang.org/x/tools v0.11.1/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8=
172+
golang.org/x/tools v0.12.0 h1:YW6HUoUmYBpwSgyaGaZq1fHjrBjX1rlpZ54T6mu2kss=
173+
golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM=
166174
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
167175
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
168176
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

internal/launchr/config/config.go

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
// Package config provides global app config object.
2+
package config
3+
4+
import (
5+
"errors"
6+
"io"
7+
"io/fs"
8+
"path/filepath"
9+
"reflect"
10+
"regexp"
11+
12+
"gopkg.in/yaml.v3"
13+
14+
"github.com/launchrctl/launchr/internal/launchr"
15+
)
16+
17+
var configRegex = regexp.MustCompile(`^config\.(yaml|yml)$`)
18+
19+
var (
20+
errNoFile = errors.New("config file is not found")
21+
)
22+
23+
// GlobalConfig is a config interface.
24+
type GlobalConfig interface {
25+
launchr.Service
26+
// DirPath returns an absolute path to config directory.
27+
DirPath() string
28+
// Path provides an absolute path to global config.
29+
Path(parts ...string) string
30+
// EnsurePath creates all directories in the path.
31+
EnsurePath(parts ...string) error
32+
// Get returns a value by name to a parameter v. Parameter v must be a pointer to a value.
33+
// Error may be returned on decode.
34+
Get(name string, v interface{}) error
35+
}
36+
37+
// GlobalConfigAware provides an interface for structs to support global configuration setting.
38+
type GlobalConfigAware interface {
39+
// SetGlobalConfig sets a global config to the struct.
40+
SetGlobalConfig(GlobalConfig)
41+
}
42+
43+
type cachedProps map[string]reflect.Value
44+
type globalConfig struct {
45+
root fs.FS
46+
fname fs.DirEntry
47+
rootPath string
48+
cached cachedProps
49+
yaml map[string]yaml.Node
50+
}
51+
52+
func findConfig(root fs.FS) fs.DirEntry {
53+
dir, err := fs.ReadDir(root, ".")
54+
if err != nil {
55+
return nil
56+
}
57+
for _, f := range dir {
58+
if !f.IsDir() && configRegex.MatchString(f.Name()) {
59+
return f
60+
}
61+
}
62+
return nil
63+
}
64+
65+
// GlobalConfigFromFS parses global app config.
66+
func GlobalConfigFromFS(root fs.FS) GlobalConfig {
67+
return &globalConfig{
68+
root: root,
69+
rootPath: launchr.GetFsAbsPath(root),
70+
cached: make(cachedProps),
71+
fname: findConfig(root),
72+
}
73+
}
74+
75+
func (cfg *globalConfig) ServiceInfo() launchr.ServiceInfo {
76+
return launchr.ServiceInfo{
77+
ID: "global_config",
78+
}
79+
}
80+
81+
func (cfg *globalConfig) DirPath() string {
82+
return cfg.rootPath
83+
}
84+
85+
func (cfg *globalConfig) Get(name string, v interface{}) error {
86+
var err error
87+
cached, ok := cfg.cached[name]
88+
if ok {
89+
err, ok = cached.Interface().(error)
90+
if ok {
91+
return err
92+
}
93+
reflect.ValueOf(v).Elem().Set(cached)
94+
return nil
95+
}
96+
97+
if cfg.fname != nil && cfg.yaml == nil {
98+
if err = cfg.parse(); err != nil {
99+
return err
100+
}
101+
}
102+
y, ok := cfg.yaml[name]
103+
if !ok {
104+
// Return default value.
105+
return nil
106+
}
107+
defer func() {
108+
// Save error result to prevent parsing twice.
109+
if err != nil {
110+
cfg.cached[name] = reflect.ValueOf(err)
111+
}
112+
}()
113+
vcopy := reflect.New(reflect.TypeOf(v).Elem()).Elem()
114+
if err = y.Decode(v); err != nil {
115+
// Set value to empty struct not to leak partial parsing to ensure consistent results.
116+
reflect.ValueOf(v).Elem().Set(vcopy)
117+
return err
118+
}
119+
cfg.cached[name] = reflect.ValueOf(v).Elem()
120+
return nil
121+
}
122+
123+
func (cfg *globalConfig) parse() error {
124+
if cfg.fname == nil {
125+
return errNoFile
126+
}
127+
r, err := cfg.root.Open(cfg.fname.Name())
128+
if err != nil {
129+
return err
130+
}
131+
defer r.Close()
132+
133+
d := yaml.NewDecoder(r)
134+
err = d.Decode(&cfg.yaml)
135+
if err != nil && err != io.EOF {
136+
return err
137+
}
138+
return nil
139+
}
140+
141+
func (cfg *globalConfig) Path(parts ...string) string {
142+
parts = append([]string{cfg.rootPath}, parts...)
143+
return filepath.Clean(filepath.Join(parts...))
144+
}
145+
146+
func (cfg *globalConfig) EnsurePath(parts ...string) error {
147+
return launchr.EnsurePath(cfg.Path(parts...))
148+
}

0 commit comments

Comments
 (0)