forked from carimura/cli
-
Notifications
You must be signed in to change notification settings - Fork 1
/
deploy.go
224 lines (193 loc) · 5.2 KB
/
deploy.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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
package main
import (
"errors"
"fmt"
"log"
"os"
"path"
"path/filepath"
"time"
client "github.com/fnproject/cli/client"
functions "github.com/funcy/functions_go"
"github.com/funcy/functions_go/models"
"github.com/urfave/cli"
)
func deploy() cli.Command {
cmd := deploycmd{
RoutesApi: functions.NewRoutesApi(),
}
var flags []cli.Flag
flags = append(flags, cmd.flags()...)
return cli.Command{
Name: "deploy",
ArgsUsage: "<appName>",
Usage: "deploys a function to the functions server. (bumps, build, pushes and updates route)",
Flags: flags,
Action: cmd.scan,
}
}
type deploycmd struct {
appName string
*functions.RoutesApi
wd string
verbose bool
incremental bool
skippush bool
noCache bool
registry string
}
func (cmd *deploycmd) Registry() string {
return cmd.registry
}
func (p *deploycmd) flags() []cli.Flag {
return []cli.Flag{
cli.BoolFlag{
Name: "v",
Usage: "verbose mode",
Destination: &p.verbose,
},
cli.StringFlag{
Name: "d",
Usage: "working directory",
Destination: &p.wd,
EnvVar: "WORK_DIR",
// Value: "./",
},
cli.BoolFlag{
Name: "i",
Usage: "uses incremental building",
Destination: &p.incremental,
},
cli.BoolFlag{
Name: "no-cache",
Usage: "Don't use Docker cache for the build",
Destination: &p.noCache,
},
cli.BoolFlag{
Name: "skip-push",
Usage: "does not push Docker built images onto Docker Hub - useful for local development.",
Destination: &p.skippush,
},
cli.StringFlag{
Name: "registry",
Usage: "Sets the Docker owner for images and optionally the registry. This will be prefixed to your function name for pushing to Docker registries. eg: `--registry username` will set your Docker Hub owner. `--registry registry.hub.docker.com/username` will set the registry and owner.",
Destination: &p.registry,
},
}
}
func (p *deploycmd) scan(c *cli.Context) error {
p.appName = c.Args().First()
var walked bool
wd, err := os.Getwd()
if err != nil {
log.Fatalln("Couldn't get working directory:", err)
}
setRegistryEnv(p)
err = filepath.Walk(wd, func(path string, info os.FileInfo, err error) error {
if path != wd && info.IsDir() {
return filepath.SkipDir
}
if !isFuncfile(path, info) {
return nil
}
if p.incremental && !isstale(path) {
return nil
}
e := p.deploy(c, path)
if err != nil {
fmt.Println(path, e)
}
now := time.Now()
os.Chtimes(path, now, now)
walked = true
return e
})
if err != nil {
fmt.Printf("error: %s\n", err)
}
if !walked {
return errors.New("No function file found.")
}
return nil
}
// deploy will perform several actions to deploy to an functions server.
// Parse functions file, bump version, build image, push to registry, and
// finally it will update function's route. Optionally,
// the route can be overriden inside the functions file.
func (p *deploycmd) deploy(c *cli.Context, funcFilePath string) error {
funcFileName := path.Base(funcFilePath)
err := c.App.Command("bump").Run(c)
if err != nil {
return err
}
funcfile, err := buildfunc(funcFileName, p.noCache)
if err != nil {
return err
}
if funcfile.Path == "" {
funcfile.Path = "/" + path.Base(path.Dir(funcFilePath))
}
if p.skippush {
return nil
}
if err := dockerpush(funcfile); err != nil {
return err
}
return p.route(c, funcfile)
}
func (p *deploycmd) route(c *cli.Context, ff *funcfile) error {
fmt.Printf("Updating route %s using image %s...\n", ff.Path, ff.ImageName())
if err := resetBasePath(p.Configuration); err != nil {
return fmt.Errorf("error setting endpoint: %v", err)
}
routesCmd := routesCmd{client: client.APIClient()}
rt := &models.Route{}
if err := routeWithFuncFile(ff, rt); err != nil {
return fmt.Errorf("error getting route with funcfile: %s", err)
}
return routesCmd.putRoute(c, p.appName, ff.Path, rt)
}
func expandEnvConfig(configs map[string]string) map[string]string {
for k, v := range configs {
configs[k] = os.ExpandEnv(v)
}
return configs
}
func isFuncfile(path string, info os.FileInfo) bool {
if info.IsDir() {
return false
}
basefn := filepath.Base(path)
for _, fn := range validfn {
if basefn == fn {
return true
}
}
return false
}
// Theory of operation: this takes an optimistic approach to detect whether a
// package must be rebuild/bump/deployed. It loads for all files mtime's and
// compare with functions.json own mtime. If any file is younger than
// functions.json, it triggers a rebuild.
// The problem with this approach is that depending on the OS running it, the
// time granularity of these timestamps might lead to false negatives - that is
// a package that is stale but it is not recompiled. A more elegant solution
// could be applied here, like https://golang.org/src/cmd/go/pkg.go#L1111
func isstale(path string) bool {
fi, err := os.Stat(path)
if err != nil {
return true
}
fnmtime := fi.ModTime()
dir := filepath.Dir(path)
err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if info.IsDir() {
return nil
}
if info.ModTime().After(fnmtime) {
return errors.New("found stale package")
}
return nil
})
return err != nil
}