/
cli.go
153 lines (128 loc) · 3.23 KB
/
cli.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
package app
import (
"errors"
"fmt"
"io"
"strings"
"github.com/docker/cli/cli/streams"
"github.com/moby/term"
"go.pantheon.tech/stonework/client"
)
// Cli is a client API for CLI application.
type Cli interface {
Initialize(opts *GlobalOptions) error
Apply(...CliOption) error
Client() client.API
Entities() []Entity
GlobalOptions() *GlobalOptions
Exec(cmd string, args []string, liveOutput bool) (stdout string, stderr string, err error)
AppName() string
Out() *streams.Out
Err() io.Writer
In() *streams.In
}
// CLI implements Cli interface.
type CLI struct {
client client.API
entities []Entity
globalOptions *GlobalOptions
out *streams.Out
err io.Writer
in *streams.In
appName string
// customizations is the generic way how to pass CLI customizations without extending the API. It should
// be used for small modifications or changes that are not worthy to change the CLI API.
customizations map[string]interface{}
}
// NewCli returns a new CLI instance. It accepts CliOption for customization.
func NewCli(appName string, opt ...CliOption) (*CLI, error) {
cli := new(CLI)
if err := cli.Apply(opt...); err != nil {
return nil, err
}
cli.appName = appName
if cli.out == nil || cli.in == nil || cli.err == nil {
stdin, stdout, stderr := term.StdStreams()
if cli.in == nil {
cli.in = streams.NewIn(stdin)
}
if cli.out == nil {
cli.out = streams.NewOut(stdout)
}
if cli.err == nil {
cli.err = stderr
}
}
return cli, nil
}
func (cli *CLI) Initialize(opts *GlobalOptions) (err error) {
InitGlobalOptions(cli, opts)
cli.globalOptions = opts
cli.client, err = initClient(client.WithComposeFiles(cli.globalOptions.ComposeFiles))
if err != nil {
return fmt.Errorf("init client error: %w", err)
}
// load entity files
cli.entities, err = loadEntityFiles(opts.EntityFiles)
if err != nil {
return fmt.Errorf("loading entity files failed: %w", err)
}
if cli.entities == nil {
cli.entities, err = loadEmbeddedEntities(opts.EmbeddedEntityByte)
}
if err != nil {
return fmt.Errorf("loading embedded entity files failed: %w", err)
}
return nil
}
func initClient(opts ...client.Option) (*client.Client, error) {
c, err := client.NewClient(opts...)
if err != nil {
return nil, err
}
return c, nil
}
func (cli *CLI) Client() client.API {
return cli.client
}
func (cli *CLI) Entities() []Entity {
return cli.entities
}
func (cli *CLI) GlobalOptions() *GlobalOptions {
return cli.globalOptions
}
func (cli *CLI) Exec(cmd string, args []string, liveOutput bool) (string, string, error) {
if cmd == "" {
return "", "", errors.New("cannot execute empty command")
}
cmdParts := strings.Split(cmd, " ")
if len(cmdParts) > 1 {
args = append(cmdParts[1:], args...)
}
ecmd := newExternalCmd(externalExe(cmdParts[0]), args, cli)
res, err := ecmd.exec(liveOutput)
if err != nil {
return "", "", err
}
return res.Stdout, res.Stderr, nil
}
func (cli *CLI) Apply(opt ...CliOption) error {
for _, o := range opt {
if err := o(cli); err != nil {
return err
}
}
return nil
}
func (cli *CLI) Out() *streams.Out {
return cli.out
}
func (cli *CLI) Err() io.Writer {
return cli.err
}
func (cli *CLI) In() *streams.In {
return cli.in
}
func (cli *CLI) AppName() string {
return cli.appName
}