From 1d2281a1fc3e51386eba8df1669326f2225a7074 Mon Sep 17 00:00:00 2001 From: Matt Rickard Date: Wed, 26 Sep 2018 11:09:30 -0700 Subject: [PATCH 01/24] [examples] add webserver file sync example --- examples/webserver/Dockerfile | 8 ++++++++ examples/webserver/index.html | 1 + examples/webserver/k8s-pod.yaml | 10 ++++++++++ examples/webserver/main.go | 10 ++++++++++ examples/webserver/skaffold.yaml | 11 +++++++++++ 5 files changed, 40 insertions(+) create mode 100644 examples/webserver/Dockerfile create mode 100644 examples/webserver/index.html create mode 100644 examples/webserver/k8s-pod.yaml create mode 100644 examples/webserver/main.go create mode 100644 examples/webserver/skaffold.yaml diff --git a/examples/webserver/Dockerfile b/examples/webserver/Dockerfile new file mode 100644 index 00000000000..6b9262b7f24 --- /dev/null +++ b/examples/webserver/Dockerfile @@ -0,0 +1,8 @@ +FROM golang:1.10.1-alpine3.7 as builder +COPY main.go . +RUN go build -o /app main.go + +FROM alpine:3.7 +CMD ["./app"] +COPY --from=builder /app . +COPY *.html /static/ diff --git a/examples/webserver/index.html b/examples/webserver/index.html new file mode 100644 index 00000000000..b297683f36d --- /dev/null +++ b/examples/webserver/index.html @@ -0,0 +1 @@ +Hello world!! diff --git a/examples/webserver/k8s-pod.yaml b/examples/webserver/k8s-pod.yaml new file mode 100644 index 00000000000..00a9adc38c9 --- /dev/null +++ b/examples/webserver/k8s-pod.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: Pod +metadata: + name: webserver +spec: + containers: + - name: webserver + image: gcr.io/k8s-skaffold/webserver-example + ports: + - containerPort: 8080 diff --git a/examples/webserver/main.go b/examples/webserver/main.go new file mode 100644 index 00000000000..036690cdc4b --- /dev/null +++ b/examples/webserver/main.go @@ -0,0 +1,10 @@ +package main + +import ( + "net/http" +) + +func main() { + http.Handle("/", http.FileServer(http.Dir("/static"))) + http.ListenAndServe(":8080", nil) +} diff --git a/examples/webserver/skaffold.yaml b/examples/webserver/skaffold.yaml new file mode 100644 index 00000000000..218bb5f4d62 --- /dev/null +++ b/examples/webserver/skaffold.yaml @@ -0,0 +1,11 @@ +apiVersion: skaffold/v1alpha3 +kind: Config +build: + artifacts: + - imageName: gcr.io/k8s-skaffold/webserver-example + sync: + '*.html': '/static' +deploy: + kubectl: + manifests: + - k8s-* From 350f0b0857649bd4592ae2433fae6b10ca508d4c Mon Sep 17 00:00:00 2001 From: Matt Rickard Date: Wed, 26 Sep 2018 11:09:57 -0700 Subject: [PATCH 02/24] [kubernetes] add kubectl syncer --- pkg/skaffold/kubernetes/sync.go | 78 +++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 pkg/skaffold/kubernetes/sync.go diff --git a/pkg/skaffold/kubernetes/sync.go b/pkg/skaffold/kubernetes/sync.go new file mode 100644 index 00000000000..4508e325cae --- /dev/null +++ b/pkg/skaffold/kubernetes/sync.go @@ -0,0 +1,78 @@ +/* +Copyright 2018 The Skaffold Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package kubernetes + +import ( + "fmt" + "os/exec" + "strings" + + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util" + "github.com/sirupsen/logrus" + + "github.com/pkg/errors" + "k8s.io/api/core/v1" + meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +type Syncer interface { + CopyFilesForImage(image string, syncMap map[string]string) error + DeleteFilesForImage(image string, syncMap map[string]string) error +} + +type KubectlSyncer struct{} + +func (*KubectlSyncer) CopyFilesForImage(image string, syncMap map[string]string) error { + return perform(image, syncMap, copyFileFn) +} + +func (*KubectlSyncer) DeleteFilesForImage(image string, syncMap map[string]string) error { + return perform(image, syncMap, deleteFileFn) +} + +func deleteFileFn(pod v1.Pod, container v1.Container, src, dst string) *exec.Cmd { + return exec.Command("kubectl", "exec", fmt.Sprintf("%s", pod.Name), "-c", container.Name, "--", "rm", "-rf", dst) +} + +func copyFileFn(pod v1.Pod, container v1.Container, src, dst string) *exec.Cmd { + return exec.Command("kubectl", "cp", src, fmt.Sprintf("%s/%s:%s", pod.Namespace, pod.Name, dst), "-c", container.Name) +} + +func perform(image string, files map[string]string, cmdFn func(v1.Pod, v1.Container, string, string) *exec.Cmd) error { + logrus.Info("Syncing files:", files) + client, err := Client() + if err != nil { + return errors.Wrap(err, "getting k8s client") + } + pods, err := client.CoreV1().Pods("").List(meta_v1.ListOptions{}) + if err != nil { + return errors.Wrap(err, "getting pods") + } + for _, p := range pods.Items { + for _, c := range p.Spec.Containers { + if strings.HasPrefix(c.Image, image) { + for src, dst := range files { + cmd := cmdFn(p, c, src, dst) + if err := util.RunCmd(cmd); err != nil { + return errors.Wrapf(err, "syncing with kubectl") + } + } + } + } + } + return nil +} From 9a1d4d0fe7dd73a6417d94899a83fc9d00118c69 Mon Sep 17 00:00:00 2001 From: Matt Rickard Date: Wed, 26 Sep 2018 11:10:12 -0700 Subject: [PATCH 03/24] [runner] add sync step to skaffold dev --- pkg/skaffold/runner/runner.go | 68 +++++++++++++- pkg/skaffold/runner/runner_test.go | 139 +++++++++++++++++++++++++++++ pkg/skaffold/util/util.go | 7 ++ 3 files changed, 213 insertions(+), 1 deletion(-) diff --git a/pkg/skaffold/runner/runner.go b/pkg/skaffold/runner/runner.go index ad461cc5cfa..8ba1a58f975 100644 --- a/pkg/skaffold/runner/runner.go +++ b/pkg/skaffold/runner/runner.go @@ -37,6 +37,7 @@ import ( kubectx "github.com/GoogleContainerTools/skaffold/pkg/skaffold/kubernetes/context" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/test" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/watch" "github.com/pkg/errors" @@ -52,7 +53,11 @@ type SkaffoldRunner struct { deploy.Deployer test.Tester tag.Tagger +<<<<<<< HEAD watch.Trigger +======= + kubernetes.Syncer +>>>>>>> 84d22e8e... [runner] add sync step to skaffold dev opts *config.SkaffoldOptions watchFactory watch.Factory @@ -103,7 +108,11 @@ func NewForConfig(opts *config.SkaffoldOptions, cfg *latest.SkaffoldConfig) (*Sk Tester: tester, Deployer: deployer, Tagger: tagger, +<<<<<<< HEAD Trigger: trigger, +======= + Syncer: &kubernetes.KubectlSyncer{}, +>>>>>>> 84d22e8e... [runner] add sync step to skaffold dev opts: opts, watchFactory: watch.NewWatcher, }, nil @@ -298,7 +307,18 @@ func (r *SkaffoldRunner) Dev(ctx context.Context, out io.Writer, artifacts []*la if err := watcher.Register( func() ([]string, error) { return dependenciesForArtifact(artifact) }, - func(watch.Events) { changed.Add(artifact) }, + func(e watch.WatchEvents) { + sync, err := r.shouldSync(artifact.ImageName, artifact.Sync, e) + if err != nil { + return errors.Wrap(err, "checking sync files") + } + if !sync { + changed.Add(artifact) + } else { + color.Default.Fprintln(out, "Synced:", "copied", append(e.Added, e.Modified...), "deleted", e.Deleted) + } + return nil + }, ); err != nil { return nil, errors.Wrapf(err, "watching files for artifact %s", artifact.ImageName) } @@ -361,6 +381,52 @@ func (r *SkaffoldRunner) Dev(ctx context.Context, out io.Writer, artifacts []*la return nil, watcher.Run(ctx, r.Trigger, onChange) } +func (r *SkaffoldRunner) shouldSync(image string, syncPatterns map[string]string, e watch.WatchEvents) (bool, error) { + // If there are no changes, there is nothing to sync + if !e.HasChanged() { + return false, nil + } + + toCopy, err := intersect(syncPatterns, append(e.Added, e.Modified...)) + if err != nil { + return false, errors.Wrap(err, "intersecting sync map and added, modified files") + } + // The only error that intersect can return is a bad pattern, which is checked above + toDelete, _ := intersect(syncPatterns, e.Deleted) + if toCopy == nil || toDelete == nil { + return false, nil + } + if err := r.Syncer.DeleteFilesForImage(image, toDelete); err != nil { + return false, errors.Wrap(err, "deleting files for image") + } + if err := r.Syncer.CopyFilesForImage(image, toCopy); err != nil { + return false, errors.Wrap(err, "copying files for image") + } + return true, nil +} + +func intersect(syncMap map[string]string, files []string) (map[string]string, error) { + ret := map[string]string{} + for _, f := range files { + for p, dst := range syncMap { + match, err := filepath.Match(p, f) + if err != nil { + return nil, errors.Wrap(err, "pattern error") + } + if !match { + return nil, nil + } + // If the source has special match characters, + // the destination must be a directory + if util.HasMeta(p) { + dst = filepath.Join(dst, f) + } + ret[f] = dst + } + } + return ret, nil +} + func (r *SkaffoldRunner) shouldWatch(artifact *latest.Artifact) bool { if len(r.opts.Watch) == 0 { return true diff --git a/pkg/skaffold/runner/runner_test.go b/pkg/skaffold/runner/runner_test.go index f0634988d25..64b70d5fa07 100644 --- a/pkg/skaffold/runner/runner_test.go +++ b/pkg/skaffold/runner/runner_test.go @@ -110,6 +110,39 @@ func (t *TestDeployer) Cleanup(ctx context.Context, out io.Writer) error { return nil } +type TestSyncer struct { + copyErr, deleteErr error + copies map[string]string + deletes []string +} + +func NewTestSyncer() *TestSyncer { + return &TestSyncer{ + copies: map[string]string{}, + } +} + +func NewTestSyncerWithErrors(copyErr, deleteErr error) *TestSyncer { + return &TestSyncer{ + copies: map[string]string{}, + copyErr: copyErr, + deleteErr: deleteErr, + } +} + +func (t *TestSyncer) CopyFilesForImage(image string, f map[string]string) error { + for src, dst := range f { + t.copies[src] = dst + } + return t.copyErr +} +func (t *TestSyncer) DeleteFilesForImage(image string, f map[string]string) error { + for _, dst := range f { + t.deletes = append(t.deletes, dst) + } + return t.deleteErr +} + func resetClient() { kubernetes.Client = kubernetes.GetClientset } func fakeGetClient() (clientgo.Interface, error) { return fake.NewSimpleClientset(), nil } @@ -420,6 +453,10 @@ func TestDev(t *testing.T) { Trigger: trigger, watchFactory: test.watcherFactory, opts: opts, + opts: &config.SkaffoldOptions{ + WatchPollInterval: 100, + }, + Syncer: NewTestSyncer(), } _, err := runner.Dev(context.Background(), ioutil.Discard, nil) @@ -450,6 +487,7 @@ func TestBuildAndDeployAllArtifacts(t *testing.T) { Deployer: deployer, Trigger: trigger, opts: opts, + Syncer: NewTestSyncer(), } ctx := context.Background() @@ -532,3 +570,104 @@ func TestShouldWatch(t *testing.T) { }) } } + +func TestShouldSync(t *testing.T) { + var tests = []struct { + description string + syncPatterns map[string]string + evt watch.WatchEvents + copies map[string]string + deletes []string + shouldErr bool + expected bool + syncer *TestSyncer + }{ + { + description: "match copy", + syncPatterns: map[string]string{ + "*.html": ".", + }, + evt: watch.WatchEvents{ + Added: []string{"index.html"}, + }, + copies: map[string]string{ + "index.html": "index.html", + }, + syncer: NewTestSyncer(), + expected: true, + }, + { + description: "not copy syncable", + syncPatterns: map[string]string{ + "*.html": ".", + }, + evt: watch.WatchEvents{ + Added: []string{"main.go"}, + Deleted: []string{"index.html"}, + }, + syncer: NewTestSyncer(), + expected: false, + }, + { + description: "not delete syncable", + syncPatterns: map[string]string{ + "*.html": "/static", + }, + evt: watch.WatchEvents{ + Added: []string{"index.html"}, + Deleted: []string{"some/other/file"}, + }, + syncer: NewTestSyncer(), + expected: false, + }, + { + description: "err bad pattern", + syncPatterns: map[string]string{ + "[*.html": "*", + }, + evt: watch.WatchEvents{ + Added: []string{"index.html"}, + Deleted: []string{"some/other/file"}, + }, + syncer: NewTestSyncer(), + shouldErr: true, + }, + { + description: "err copy", + syncPatterns: map[string]string{ + "*.html": "*", + }, + evt: watch.WatchEvents{ + Added: []string{"index.html"}, + }, + syncer: NewTestSyncerWithErrors(fmt.Errorf(""), nil), + shouldErr: true, + }, + { + description: "err copy", + syncPatterns: map[string]string{ + "*.html": "*", + }, + evt: watch.WatchEvents{ + Added: []string{"index.html"}, + }, + syncer: NewTestSyncerWithErrors(nil, fmt.Errorf("")), + shouldErr: true, + }, + } + + for _, test := range tests { + t.Run(test.description, func(t *testing.T) { + r := &SkaffoldRunner{ + Syncer: test.syncer, + } + actual, err := r.shouldSync("", test.syncPatterns, test.evt) + testutil.CheckErrorAndDeepEqual(t, test.shouldErr, err, test.expected, actual) + if test.expected { + testutil.CheckDeepEqual(t, test.copies, test.syncer.copies) + testutil.CheckDeepEqual(t, test.deletes, test.syncer.deletes) + } + }) + } + +} diff --git a/pkg/skaffold/util/util.go b/pkg/skaffold/util/util.go index 7eade1ac029..a8bcd5a0b2c 100644 --- a/pkg/skaffold/util/util.go +++ b/pkg/skaffold/util/util.go @@ -107,6 +107,13 @@ func ExpandPathsGlob(workingDir string, paths []string) ([]string, error) { return ret, nil } +// HasMeta reports whether path contains any of the magic characters +// recognized by filepath.Match. +// This is a copy of filepath/match.go's hasMeta +func HasMeta(path string) bool { + return strings.ContainsAny(path, "*?[") +} + // BoolPtr returns a pointer to a bool func BoolPtr(b bool) *bool { o := b From 64af3ab5a44dd3555a150686cccc5afdc1d36c21 Mon Sep 17 00:00:00 2001 From: Matt Rickard Date: Wed, 26 Sep 2018 11:28:21 -0700 Subject: [PATCH 04/24] [examples] add readme to webserver example --- examples/webserver/README.adoc | 77 ++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 examples/webserver/README.adoc diff --git a/examples/webserver/README.adoc b/examples/webserver/README.adoc new file mode 100644 index 00000000000..b45e41779de --- /dev/null +++ b/examples/webserver/README.adoc @@ -0,0 +1,77 @@ +=== Example: File Sync with Skaffold +:icons: font + +ifndef::env-github[] +link:{github-repo-tree}/examples/webserver[see on Github icon:github[]] +endif::[] + +In this example: + +* Deploy a basic golang webserver that serves static files +* In development, sync static HTML files without triggering a rebuild or redeploy of the artifacts + +Not all file changes should require a complete rebuild and redeploy of the skaffold artifacts. +This tutorial shows how you can use the file sync feature of skaffold for very quick iterative development. + +==== Running the example on minikube + +From this directory, run + +```bash +skaffold dev +``` + +Now in a different terminal, hit the webserver's endpoint to see index.html + +```bash +$ curl localhost:8080 +Hello World!! +``` + +Now, edit the index.html file to contain something else. You'll see that skaffold syncs the file to the already running container, without rebuilding or redeploying. +```bash +echo "Hello skaffold" > index.html +``` + +You should see that skaffold has synced those changes in the other terminal +```bash +... +Synced: copied [index.html] deleted [] +Watching for changes... +``` + +Now, lets add a new file that matches the glob pattern but wasn't in the original container. +```bash +echo "Dogs are great" > cats.html +``` + +Now you can see that change immediately. +```bash +$ curl localhost:8080/cats.html +"Dogs are great" +``` + +Delete the file +```bash +rm cats.html +``` + +Now you can see that change immediately +```bash +$ curl localhost:8080/cats.html +404 page not found +``` + +==== Configuration walkthrough + +Let's walk through the first part of the skaffold.yaml + +```yaml + artifacts: + - imageName: gcr.io/k8s-skaffold/webserver-example + sync: + '*.html': '/static' +``` + +This will sync all files that match the *.html pattern to the /static/ folder in the container. +Sync can be multiple keys and the only restriction is that the destination must be a folder if the source is a pattern. \ No newline at end of file From 884e6b309a2dbf293553b89efcf00621377ace67 Mon Sep 17 00:00:00 2001 From: Matt Rickard Date: Wed, 26 Sep 2018 11:48:39 -0700 Subject: [PATCH 05/24] [linter] fixes --- pkg/skaffold/kubernetes/sync.go | 3 ++- pkg/skaffold/runner/runner.go | 11 +++-------- pkg/skaffold/runner/runner_test.go | 14 +++++++------- 3 files changed, 12 insertions(+), 16 deletions(-) diff --git a/pkg/skaffold/kubernetes/sync.go b/pkg/skaffold/kubernetes/sync.go index 4508e325cae..fba62d5a143 100644 --- a/pkg/skaffold/kubernetes/sync.go +++ b/pkg/skaffold/kubernetes/sync.go @@ -44,8 +44,9 @@ func (*KubectlSyncer) DeleteFilesForImage(image string, syncMap map[string]strin return perform(image, syncMap, deleteFileFn) } +// TODO(r2d4): kubectl exec doesn't seem to take a namespace flag? func deleteFileFn(pod v1.Pod, container v1.Container, src, dst string) *exec.Cmd { - return exec.Command("kubectl", "exec", fmt.Sprintf("%s", pod.Name), "-c", container.Name, "--", "rm", "-rf", dst) + return exec.Command("kubectl", "exec", pod.Name, "-c", container.Name, "--", "rm", "-rf", dst) } func copyFileFn(pod v1.Pod, container v1.Container, src, dst string) *exec.Cmd { diff --git a/pkg/skaffold/runner/runner.go b/pkg/skaffold/runner/runner.go index 8ba1a58f975..6a740b8d639 100644 --- a/pkg/skaffold/runner/runner.go +++ b/pkg/skaffold/runner/runner.go @@ -30,6 +30,7 @@ import ( "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/kaniko" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/local" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/tag" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/color" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/config" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/deploy" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/docker" @@ -53,11 +54,8 @@ type SkaffoldRunner struct { deploy.Deployer test.Tester tag.Tagger -<<<<<<< HEAD watch.Trigger -======= kubernetes.Syncer ->>>>>>> 84d22e8e... [runner] add sync step to skaffold dev opts *config.SkaffoldOptions watchFactory watch.Factory @@ -108,11 +106,8 @@ func NewForConfig(opts *config.SkaffoldOptions, cfg *latest.SkaffoldConfig) (*Sk Tester: tester, Deployer: deployer, Tagger: tagger, -<<<<<<< HEAD Trigger: trigger, -======= Syncer: &kubernetes.KubectlSyncer{}, ->>>>>>> 84d22e8e... [runner] add sync step to skaffold dev opts: opts, watchFactory: watch.NewWatcher, }, nil @@ -307,7 +302,7 @@ func (r *SkaffoldRunner) Dev(ctx context.Context, out io.Writer, artifacts []*la if err := watcher.Register( func() ([]string, error) { return dependenciesForArtifact(artifact) }, - func(e watch.WatchEvents) { + func(e watch.Events) { sync, err := r.shouldSync(artifact.ImageName, artifact.Sync, e) if err != nil { return errors.Wrap(err, "checking sync files") @@ -381,7 +376,7 @@ func (r *SkaffoldRunner) Dev(ctx context.Context, out io.Writer, artifacts []*la return nil, watcher.Run(ctx, r.Trigger, onChange) } -func (r *SkaffoldRunner) shouldSync(image string, syncPatterns map[string]string, e watch.WatchEvents) (bool, error) { +func (r *SkaffoldRunner) shouldSync(image string, syncPatterns map[string]string, e watch.Events) (bool, error) { // If there are no changes, there is nothing to sync if !e.HasChanged() { return false, nil diff --git a/pkg/skaffold/runner/runner_test.go b/pkg/skaffold/runner/runner_test.go index 64b70d5fa07..1ca49c3ba24 100644 --- a/pkg/skaffold/runner/runner_test.go +++ b/pkg/skaffold/runner/runner_test.go @@ -575,7 +575,7 @@ func TestShouldSync(t *testing.T) { var tests = []struct { description string syncPatterns map[string]string - evt watch.WatchEvents + evt watch.Events copies map[string]string deletes []string shouldErr bool @@ -587,7 +587,7 @@ func TestShouldSync(t *testing.T) { syncPatterns: map[string]string{ "*.html": ".", }, - evt: watch.WatchEvents{ + evt: watch.Events{ Added: []string{"index.html"}, }, copies: map[string]string{ @@ -601,7 +601,7 @@ func TestShouldSync(t *testing.T) { syncPatterns: map[string]string{ "*.html": ".", }, - evt: watch.WatchEvents{ + evt: watch.Events{ Added: []string{"main.go"}, Deleted: []string{"index.html"}, }, @@ -613,7 +613,7 @@ func TestShouldSync(t *testing.T) { syncPatterns: map[string]string{ "*.html": "/static", }, - evt: watch.WatchEvents{ + evt: watch.Events{ Added: []string{"index.html"}, Deleted: []string{"some/other/file"}, }, @@ -625,7 +625,7 @@ func TestShouldSync(t *testing.T) { syncPatterns: map[string]string{ "[*.html": "*", }, - evt: watch.WatchEvents{ + evt: watch.Events{ Added: []string{"index.html"}, Deleted: []string{"some/other/file"}, }, @@ -637,7 +637,7 @@ func TestShouldSync(t *testing.T) { syncPatterns: map[string]string{ "*.html": "*", }, - evt: watch.WatchEvents{ + evt: watch.Events{ Added: []string{"index.html"}, }, syncer: NewTestSyncerWithErrors(fmt.Errorf(""), nil), @@ -648,7 +648,7 @@ func TestShouldSync(t *testing.T) { syncPatterns: map[string]string{ "*.html": "*", }, - evt: watch.WatchEvents{ + evt: watch.Events{ Added: []string{"index.html"}, }, syncer: NewTestSyncerWithErrors(nil, fmt.Errorf("")), From 16415be69ab886dd1569aca936ef2f4b1cd516eb Mon Sep 17 00:00:00 2001 From: Matt Rickard Date: Wed, 26 Sep 2018 14:10:16 -0700 Subject: [PATCH 06/24] review feedback --- hack/linter.sh | 8 +- pkg/skaffold/kubernetes/sync.go | 17 ++-- pkg/skaffold/kubernetes/sync_test.go | 114 +++++++++++++++++++++++++++ 3 files changed, 132 insertions(+), 7 deletions(-) create mode 100644 pkg/skaffold/kubernetes/sync_test.go diff --git a/hack/linter.sh b/hack/linter.sh index c3a55355b7b..20e657c181e 100755 --- a/hack/linter.sh +++ b/hack/linter.sh @@ -20,7 +20,12 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" if ! [ -x "$(command -v golangci-lint)" ]; then echo "Installing GolangCI-Lint" - ${DIR}/install_golint.sh -b $GOPATH/bin v1.9.3 + ${DIR}/install_golint.sh -b $GOPATH/bin v1.10.2 +fi + +if ! [ "$(golangci-lint --)" == ]; then + echo "Upgrading GolangCI-Lint" + ${DIR}/install_golint.sh -b $GOPATH/bin v1.10.2 fi golangci-lint run \ @@ -33,4 +38,5 @@ golangci-lint run \ -E misspell \ -E unconvert \ -E unparam \ + -D typecheck \ -D errcheck diff --git a/pkg/skaffold/kubernetes/sync.go b/pkg/skaffold/kubernetes/sync.go index fba62d5a143..d5e5556fca6 100644 --- a/pkg/skaffold/kubernetes/sync.go +++ b/pkg/skaffold/kubernetes/sync.go @@ -23,6 +23,7 @@ import ( "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util" "github.com/sirupsen/logrus" + "golang.org/x/sync/errgroup" "github.com/pkg/errors" "k8s.io/api/core/v1" @@ -44,9 +45,8 @@ func (*KubectlSyncer) DeleteFilesForImage(image string, syncMap map[string]strin return perform(image, syncMap, deleteFileFn) } -// TODO(r2d4): kubectl exec doesn't seem to take a namespace flag? func deleteFileFn(pod v1.Pod, container v1.Container, src, dst string) *exec.Cmd { - return exec.Command("kubectl", "exec", pod.Name, "-c", container.Name, "--", "rm", "-rf", dst) + return exec.Command("kubectl", "exec", pod.Name, "--namespace", pod.Namespace, "-c", container.Name, "--", "rm", "-rf", dst) } func copyFileFn(pod v1.Pod, container v1.Container, src, dst string) *exec.Cmd { @@ -66,11 +66,16 @@ func perform(image string, files map[string]string, cmdFn func(v1.Pod, v1.Contai for _, p := range pods.Items { for _, c := range p.Spec.Containers { if strings.HasPrefix(c.Image, image) { + var e errgroup.Group for src, dst := range files { - cmd := cmdFn(p, c, src, dst) - if err := util.RunCmd(cmd); err != nil { - return errors.Wrapf(err, "syncing with kubectl") - } + src, dst := src, dst + e.Go(func() error { + cmd := cmdFn(p, c, src, dst) + return util.RunCmd(cmd) + }) + } + if err := e.Wait(); err != nil { + return errors.Wrap(err, "syncing files:") } } } diff --git a/pkg/skaffold/kubernetes/sync_test.go b/pkg/skaffold/kubernetes/sync_test.go new file mode 100644 index 00000000000..4c0fb85ec20 --- /dev/null +++ b/pkg/skaffold/kubernetes/sync_test.go @@ -0,0 +1,114 @@ +package kubernetes + +import ( + "fmt" + "os/exec" + "strings" + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes/fake" + + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util" + "github.com/GoogleContainerTools/skaffold/testutil" + v1 "k8s.io/api/core/v1" + "k8s.io/client-go/kubernetes" +) + +type TestCmdRecorder struct { + cmds []string + err error +} + +func (t *TestCmdRecorder) RunCmd(cmd *exec.Cmd) error { + if t.err != nil { + return t.err + } + t.cmds = append(t.cmds, strings.Join(cmd.Args, " ")) + return nil +} + +func (t *TestCmdRecorder) RunCmdOut(cmd *exec.Cmd) ([]byte, error) { + return nil, t.RunCmd(cmd) +} + +func fakeCmd(p v1.Pod, c v1.Container, src, dst string) *exec.Cmd { + return exec.Command("copy", src, dst) +} + +var pod = &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "podname", + }, + Status: v1.PodStatus{ + Phase: v1.PodRunning, + }, + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "container_name", + Image: "gcr.io/k8s-skaffold:123", + }, + }, + }, +} + +func TestPerform(t *testing.T) { + var tests = []struct { + description string + image string + files map[string]string + cmdFn func(v1.Pod, v1.Container, string, string) *exec.Cmd + cmdErr error + clientErr error + expected []string + shouldErr bool + }{ + { + description: "no error", + image: "gcr.io/k8s-skaffold:123", + files: map[string]string{"test.go": "/test.go"}, + cmdFn: fakeCmd, + expected: []string{"copy test.go /test.go"}, + }, + { + description: "cmd error", + image: "gcr.io/k8s-skaffold:123", + files: map[string]string{"test.go": "/test.go"}, + cmdFn: fakeCmd, + cmdErr: fmt.Errorf(""), + shouldErr: true, + }, + { + description: "client error", + image: "gcr.io/k8s-skaffold:123", + files: map[string]string{"test.go": "/test.go"}, + cmdFn: fakeCmd, + clientErr: fmt.Errorf(""), + shouldErr: true, + }, + { + description: "no copy", + image: "gcr.io/different-pod:123", + files: map[string]string{"test.go": "/test.go"}, + cmdFn: fakeCmd, + }, + } + + for _, test := range tests { + t.Run(test.description, func(t *testing.T) { + cmdRecord := &TestCmdRecorder{err: test.cmdErr} + defer func(c util.Command) { util.DefaultExecCommand = c }(util.DefaultExecCommand) + util.DefaultExecCommand = cmdRecord + + defer func(c func() (kubernetes.Interface, error)) { Client = c }(GetClientset) + Client = func() (kubernetes.Interface, error) { + return fake.NewSimpleClientset(pod), test.clientErr + } + + util.DefaultExecCommand = cmdRecord + err := perform(test.image, test.files, test.cmdFn) + testutil.CheckErrorAndDeepEqual(t, test.shouldErr, err, test.expected, cmdRecord.cmds) + }) + } +} From 98b2219c0a56b8391ae7b69f9429f65aff2c4b9f Mon Sep 17 00:00:00 2001 From: Matt Rickard Date: Wed, 26 Sep 2018 14:19:43 -0700 Subject: [PATCH 07/24] [vendor] add x/sync/errgroup --- Gopkg.lock | 9 +++ hack/linter.sh | 8 +-- vendor/golang.org/x/sync/AUTHORS | 3 + vendor/golang.org/x/sync/CONTRIBUTORS | 3 + vendor/golang.org/x/sync/LICENSE | 27 ++++++++ vendor/golang.org/x/sync/PATENTS | 22 ++++++ vendor/golang.org/x/sync/errgroup/errgroup.go | 67 +++++++++++++++++++ 7 files changed, 132 insertions(+), 7 deletions(-) create mode 100644 vendor/golang.org/x/sync/AUTHORS create mode 100644 vendor/golang.org/x/sync/CONTRIBUTORS create mode 100644 vendor/golang.org/x/sync/LICENSE create mode 100644 vendor/golang.org/x/sync/PATENTS create mode 100644 vendor/golang.org/x/sync/errgroup/errgroup.go diff --git a/Gopkg.lock b/Gopkg.lock index 4d34a23d829..33e9738b525 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -722,6 +722,14 @@ pruneopts = "NUT" revision = "d2e6202438beef2727060aa7cabdd924d92ebfd9" +[[projects]] + branch = "master" + digest = "1:39ebcc2b11457b703ae9ee2e8cca0f68df21969c6102cb3b705f76cca0ea0239" + name = "golang.org/x/sync" + packages = ["errgroup"] + pruneopts = "NUT" + revision = "1d60e4601c6fd243af51cc01ddf169918a5407ca" + [[projects]] branch = "master" digest = "1:581828cee9f0d31195c1ae52ea8935dfaec395cee6c23adc02109b892b391c2e" @@ -1180,6 +1188,7 @@ "golang.org/x/crypto/ssh/terminal", "golang.org/x/oauth2", "golang.org/x/oauth2/google", + "golang.org/x/sync/errgroup", "google.golang.org/api/cloudbuild/v1", "google.golang.org/api/googleapi", "google.golang.org/api/iterator", diff --git a/hack/linter.sh b/hack/linter.sh index 20e657c181e..c3a55355b7b 100755 --- a/hack/linter.sh +++ b/hack/linter.sh @@ -20,12 +20,7 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" if ! [ -x "$(command -v golangci-lint)" ]; then echo "Installing GolangCI-Lint" - ${DIR}/install_golint.sh -b $GOPATH/bin v1.10.2 -fi - -if ! [ "$(golangci-lint --)" == ]; then - echo "Upgrading GolangCI-Lint" - ${DIR}/install_golint.sh -b $GOPATH/bin v1.10.2 + ${DIR}/install_golint.sh -b $GOPATH/bin v1.9.3 fi golangci-lint run \ @@ -38,5 +33,4 @@ golangci-lint run \ -E misspell \ -E unconvert \ -E unparam \ - -D typecheck \ -D errcheck diff --git a/vendor/golang.org/x/sync/AUTHORS b/vendor/golang.org/x/sync/AUTHORS new file mode 100644 index 00000000000..15167cd746c --- /dev/null +++ b/vendor/golang.org/x/sync/AUTHORS @@ -0,0 +1,3 @@ +# This source code refers to The Go Authors for copyright purposes. +# The master list of authors is in the main Go distribution, +# visible at http://tip.golang.org/AUTHORS. diff --git a/vendor/golang.org/x/sync/CONTRIBUTORS b/vendor/golang.org/x/sync/CONTRIBUTORS new file mode 100644 index 00000000000..1c4577e9680 --- /dev/null +++ b/vendor/golang.org/x/sync/CONTRIBUTORS @@ -0,0 +1,3 @@ +# This source code was written by the Go contributors. +# The master list of contributors is in the main Go distribution, +# visible at http://tip.golang.org/CONTRIBUTORS. diff --git a/vendor/golang.org/x/sync/LICENSE b/vendor/golang.org/x/sync/LICENSE new file mode 100644 index 00000000000..6a66aea5eaf --- /dev/null +++ b/vendor/golang.org/x/sync/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/golang.org/x/sync/PATENTS b/vendor/golang.org/x/sync/PATENTS new file mode 100644 index 00000000000..733099041f8 --- /dev/null +++ b/vendor/golang.org/x/sync/PATENTS @@ -0,0 +1,22 @@ +Additional IP Rights Grant (Patents) + +"This implementation" means the copyrightable works distributed by +Google as part of the Go project. + +Google hereby grants to You a perpetual, worldwide, non-exclusive, +no-charge, royalty-free, irrevocable (except as stated in this section) +patent license to make, have made, use, offer to sell, sell, import, +transfer and otherwise run, modify and propagate the contents of this +implementation of Go, where such license applies only to those patent +claims, both currently owned or controlled by Google and acquired in +the future, licensable by Google that are necessarily infringed by this +implementation of Go. This grant does not include claims that would be +infringed only as a consequence of further modification of this +implementation. If you or your agent or exclusive licensee institute or +order or agree to the institution of patent litigation against any +entity (including a cross-claim or counterclaim in a lawsuit) alleging +that this implementation of Go or any code incorporated within this +implementation of Go constitutes direct or contributory patent +infringement, or inducement of patent infringement, then any patent +rights granted to you under this License for this implementation of Go +shall terminate as of the date such litigation is filed. diff --git a/vendor/golang.org/x/sync/errgroup/errgroup.go b/vendor/golang.org/x/sync/errgroup/errgroup.go new file mode 100644 index 00000000000..533438d91c1 --- /dev/null +++ b/vendor/golang.org/x/sync/errgroup/errgroup.go @@ -0,0 +1,67 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package errgroup provides synchronization, error propagation, and Context +// cancelation for groups of goroutines working on subtasks of a common task. +package errgroup + +import ( + "sync" + + "golang.org/x/net/context" +) + +// A Group is a collection of goroutines working on subtasks that are part of +// the same overall task. +// +// A zero Group is valid and does not cancel on error. +type Group struct { + cancel func() + + wg sync.WaitGroup + + errOnce sync.Once + err error +} + +// WithContext returns a new Group and an associated Context derived from ctx. +// +// The derived Context is canceled the first time a function passed to Go +// returns a non-nil error or the first time Wait returns, whichever occurs +// first. +func WithContext(ctx context.Context) (*Group, context.Context) { + ctx, cancel := context.WithCancel(ctx) + return &Group{cancel: cancel}, ctx +} + +// Wait blocks until all function calls from the Go method have returned, then +// returns the first non-nil error (if any) from them. +func (g *Group) Wait() error { + g.wg.Wait() + if g.cancel != nil { + g.cancel() + } + return g.err +} + +// Go calls the given function in a new goroutine. +// +// The first call to return a non-nil error cancels the group; its error will be +// returned by Wait. +func (g *Group) Go(f func() error) { + g.wg.Add(1) + + go func() { + defer g.wg.Done() + + if err := f(); err != nil { + g.errOnce.Do(func() { + g.err = err + if g.cancel != nil { + g.cancel() + } + }) + } + }() +} From f0ed4cda0d0a1f8d2758898bb972fa81fdcb178f Mon Sep 17 00:00:00 2001 From: Matt Rickard Date: Wed, 26 Sep 2018 16:14:03 -0700 Subject: [PATCH 08/24] [linter] add boilerplate to sync_test.go --- pkg/skaffold/kubernetes/sync_test.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/pkg/skaffold/kubernetes/sync_test.go b/pkg/skaffold/kubernetes/sync_test.go index 4c0fb85ec20..9e071cacc27 100644 --- a/pkg/skaffold/kubernetes/sync_test.go +++ b/pkg/skaffold/kubernetes/sync_test.go @@ -1,3 +1,19 @@ +/* +Copyright 2018 The Skaffold Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package kubernetes import ( From c511693f0b9a028cf504e08916104410fba2100f Mon Sep 17 00:00:00 2001 From: r2d4 Date: Thu, 27 Sep 2018 16:40:23 -0700 Subject: [PATCH 09/24] File changes should be relative to artifact context --- pkg/skaffold/docker/parse.go | 2 +- pkg/skaffold/runner/runner.go | 25 ++++++++++++++-------- pkg/skaffold/runner/runner_test.go | 33 ++++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 10 deletions(-) diff --git a/pkg/skaffold/docker/parse.go b/pkg/skaffold/docker/parse.go index 80d9b7a320b..52abf8993ed 100644 --- a/pkg/skaffold/docker/parse.go +++ b/pkg/skaffold/docker/parse.go @@ -249,7 +249,7 @@ func expandPaths(workspace string, copied [][]string) ([]string, error) { for dep := range expandedPaths { deps = append(deps, dep) } - logrus.Infof("Found dependencies for dockerfile %s", deps) + // logrus.Infof("Found dependencies for dockerfile %s", deps) return deps, nil } diff --git a/pkg/skaffold/runner/runner.go b/pkg/skaffold/runner/runner.go index 6a740b8d639..97e12c6a113 100644 --- a/pkg/skaffold/runner/runner.go +++ b/pkg/skaffold/runner/runner.go @@ -303,7 +303,7 @@ func (r *SkaffoldRunner) Dev(ctx context.Context, out io.Writer, artifacts []*la if err := watcher.Register( func() ([]string, error) { return dependenciesForArtifact(artifact) }, func(e watch.Events) { - sync, err := r.shouldSync(artifact.ImageName, artifact.Sync, e) + sync, err := r.shouldSync(artifact.ImageName, artifact.Workspace, artifact.Sync, e) if err != nil { return errors.Wrap(err, "checking sync files") } @@ -376,18 +376,18 @@ func (r *SkaffoldRunner) Dev(ctx context.Context, out io.Writer, artifacts []*la return nil, watcher.Run(ctx, r.Trigger, onChange) } -func (r *SkaffoldRunner) shouldSync(image string, syncPatterns map[string]string, e watch.Events) (bool, error) { - // If there are no changes, there is nothing to sync - if !e.HasChanged() { +func (r *SkaffoldRunner) shouldSync(image string, context string, syncPatterns map[string]string, e watch.Events) (bool, error) { + // If there are no changes, short circuit and don't sync anything + if !e.HasChanged() || syncPatterns == nil || len(syncPatterns) == 0 { return false, nil } - toCopy, err := intersect(syncPatterns, append(e.Added, e.Modified...)) + toCopy, err := intersect(context, syncPatterns, append(e.Added, e.Modified...)) if err != nil { return false, errors.Wrap(err, "intersecting sync map and added, modified files") } // The only error that intersect can return is a bad pattern, which is checked above - toDelete, _ := intersect(syncPatterns, e.Deleted) + toDelete, _ := intersect(context, syncPatterns, e.Deleted) if toCopy == nil || toDelete == nil { return false, nil } @@ -400,21 +400,28 @@ func (r *SkaffoldRunner) shouldSync(image string, syncPatterns map[string]string return true, nil } -func intersect(syncMap map[string]string, files []string) (map[string]string, error) { +func intersect(context string, syncMap map[string]string, files []string) (map[string]string, error) { ret := map[string]string{} + fmt.Println("context", context) for _, f := range files { + relPath, err := filepath.Rel(context, f) + if err != nil { + return nil, errors.Wrapf(err, "changed file %s is not relative to context %s", f, context) + } + fmt.Println("relPath", relPath) for p, dst := range syncMap { - match, err := filepath.Match(p, f) + match, err := filepath.Match(p, relPath) if err != nil { return nil, errors.Wrap(err, "pattern error") } + fmt.Println("match", match, "pattern", p, "f", f, "dst", dst) if !match { return nil, nil } // If the source has special match characters, // the destination must be a directory if util.HasMeta(p) { - dst = filepath.Join(dst, f) + dst = filepath.Join(dst, filepath.Base(relPath)) } ret[f] = dst } diff --git a/pkg/skaffold/runner/runner_test.go b/pkg/skaffold/runner/runner_test.go index 1ca49c3ba24..ccbe1646fe9 100644 --- a/pkg/skaffold/runner/runner_test.go +++ b/pkg/skaffold/runner/runner_test.go @@ -284,6 +284,39 @@ func TestNewForConfig(t *testing.T) { } } +func TestIntersect(t *testing.T) { + var tests = []struct { + description string + syncPatterns map[string]string + files []string + expected map[string]string + shouldErr bool + }{ + { + description: "nil sync patterns doesn't sync", + expected: map[string]string{}, + }, + { + description: "copy nested file to correct destination", + files: []string{"static/index.html", "static/test.html"}, + syncPatterns: map[string]string{ + "static/*.html": "/html", + }, + expected: map[string]string{ + "static/index.html": "/html/index.html", + "static/test.html": "/html/test.html", + }, + }, + } + + for _, test := range tests { + t.Run(test.description, func(t *testing.T) { + actual, err := intersect(test.syncPatterns, test.files) + testutil.CheckErrorAndDeepEqual(t, test.shouldErr, err, test.expected, actual) + }) + } +} + func TestRun(t *testing.T) { var tests = []struct { description string From 88f3d02c07e9b32bb48679da9d18ed75b9dfef63 Mon Sep 17 00:00:00 2001 From: r2d4 Date: Fri, 28 Sep 2018 10:37:15 -0500 Subject: [PATCH 10/24] rename webserver example to hot reload --- examples/webserver/Dockerfile | 8 - examples/webserver/index.html | 1 - examples/webserver/k8s-pod.yaml | 10 - examples/webserver/main.go | 10 - examples/webserver/skaffold.yaml | 11 - .../examples/hot-reload}/README.adoc | 0 .../examples/hot-reload/node/Dockerfile | 4 + .../examples/hot-reload/node/k8s/pod.yaml | 10 + .../hot-reload/node/package-lock.json | 2316 +++++++++++++++++ .../examples/hot-reload/node/server.js | 14 + integration/examples/hot-reload/skaffold.yaml | 16 + pkg/skaffold/runner/runner.go | 3 - pkg/skaffold/runner/runner_test.go | 26 +- 13 files changed, 2384 insertions(+), 45 deletions(-) delete mode 100644 examples/webserver/Dockerfile delete mode 100644 examples/webserver/index.html delete mode 100644 examples/webserver/k8s-pod.yaml delete mode 100644 examples/webserver/main.go delete mode 100644 examples/webserver/skaffold.yaml rename {examples/webserver => integration/examples/hot-reload}/README.adoc (100%) create mode 100644 integration/examples/hot-reload/node/Dockerfile create mode 100644 integration/examples/hot-reload/node/k8s/pod.yaml create mode 100644 integration/examples/hot-reload/node/package-lock.json create mode 100644 integration/examples/hot-reload/node/server.js create mode 100644 integration/examples/hot-reload/skaffold.yaml diff --git a/examples/webserver/Dockerfile b/examples/webserver/Dockerfile deleted file mode 100644 index 6b9262b7f24..00000000000 --- a/examples/webserver/Dockerfile +++ /dev/null @@ -1,8 +0,0 @@ -FROM golang:1.10.1-alpine3.7 as builder -COPY main.go . -RUN go build -o /app main.go - -FROM alpine:3.7 -CMD ["./app"] -COPY --from=builder /app . -COPY *.html /static/ diff --git a/examples/webserver/index.html b/examples/webserver/index.html deleted file mode 100644 index b297683f36d..00000000000 --- a/examples/webserver/index.html +++ /dev/null @@ -1 +0,0 @@ -Hello world!! diff --git a/examples/webserver/k8s-pod.yaml b/examples/webserver/k8s-pod.yaml deleted file mode 100644 index 00a9adc38c9..00000000000 --- a/examples/webserver/k8s-pod.yaml +++ /dev/null @@ -1,10 +0,0 @@ -apiVersion: v1 -kind: Pod -metadata: - name: webserver -spec: - containers: - - name: webserver - image: gcr.io/k8s-skaffold/webserver-example - ports: - - containerPort: 8080 diff --git a/examples/webserver/main.go b/examples/webserver/main.go deleted file mode 100644 index 036690cdc4b..00000000000 --- a/examples/webserver/main.go +++ /dev/null @@ -1,10 +0,0 @@ -package main - -import ( - "net/http" -) - -func main() { - http.Handle("/", http.FileServer(http.Dir("/static"))) - http.ListenAndServe(":8080", nil) -} diff --git a/examples/webserver/skaffold.yaml b/examples/webserver/skaffold.yaml deleted file mode 100644 index 218bb5f4d62..00000000000 --- a/examples/webserver/skaffold.yaml +++ /dev/null @@ -1,11 +0,0 @@ -apiVersion: skaffold/v1alpha3 -kind: Config -build: - artifacts: - - imageName: gcr.io/k8s-skaffold/webserver-example - sync: - '*.html': '/static' -deploy: - kubectl: - manifests: - - k8s-* diff --git a/examples/webserver/README.adoc b/integration/examples/hot-reload/README.adoc similarity index 100% rename from examples/webserver/README.adoc rename to integration/examples/hot-reload/README.adoc diff --git a/integration/examples/hot-reload/node/Dockerfile b/integration/examples/hot-reload/node/Dockerfile new file mode 100644 index 00000000000..abcafcbf67c --- /dev/null +++ b/integration/examples/hot-reload/node/Dockerfile @@ -0,0 +1,4 @@ +FROM node:6.10.3 +CMD ["nodemon", "server.js"] +RUN npm install -g nodemon +COPY *.js . diff --git a/integration/examples/hot-reload/node/k8s/pod.yaml b/integration/examples/hot-reload/node/k8s/pod.yaml new file mode 100644 index 00000000000..8fe53e3720e --- /dev/null +++ b/integration/examples/hot-reload/node/k8s/pod.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: Pod +metadata: + name: node +spec: + containers: + - name: node + image: gcr.io/k8s-skaffold/node-example + ports: + - containerPort: 3000 diff --git a/integration/examples/hot-reload/node/package-lock.json b/integration/examples/hot-reload/node/package-lock.json new file mode 100644 index 00000000000..c9661cb9950 --- /dev/null +++ b/integration/examples/hot-reload/node/package-lock.json @@ -0,0 +1,2316 @@ +{ + "requires": true, + "lockfileVersion": 1, + "dependencies": { + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + }, + "ansi-align": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-2.0.0.tgz", + "integrity": "sha1-w2rsy6VjuJzrVW82kPCx2eNUf38=", + "requires": { + "string-width": "^2.0.0" + } + }, + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + } + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=" + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==" + }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=" + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=" + }, + "assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=" + }, + "async-each": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz", + "integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=" + }, + "atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==" + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "requires": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "binary-extensions": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.12.0.tgz", + "integrity": "sha512-DYWGk01lDcxeS/K9IHPGWfT8PsJmbXRtRd2Sx72Tnb8pcYZQFF1oSDb8hJtS1vhp212q1Rzi5dUf9+nq0o9UIg==" + }, + "boxen": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-1.3.0.tgz", + "integrity": "sha512-TNPjfTr432qx7yOjQyaXm3dSR0MH9vXp7eT1BFSl/C51g+EFnOR9hTg1IreahGBmDNCehscshe45f+C1TBZbLw==", + "requires": { + "ansi-align": "^2.0.0", + "camelcase": "^4.0.0", + "chalk": "^2.0.1", + "cli-boxes": "^1.0.0", + "string-width": "^2.0.0", + "term-size": "^1.2.0", + "widest-line": "^2.0.0" + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "requires": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + } + }, + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=" + }, + "capture-stack-trace": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz", + "integrity": "sha512-mYQLZnx5Qt1JgB1WEiMCf2647plpGeQ2NMR/5L0HNZzGQo4fuSPnK+wjfPnKZV0aiJDgzmWqqkV/g7JD+DW0qw==" + }, + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "chokidar": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.4.tgz", + "integrity": "sha512-z9n7yt9rOvIJrMhvDtDictKrkFHeihkNl6uWMmZlmL6tJtX9Cs+87oK+teBx+JIgzvbX3yZHT3eF8vpbDxHJXQ==", + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.0", + "braces": "^2.3.0", + "fsevents": "^1.2.2", + "glob-parent": "^3.1.0", + "inherits": "^2.0.1", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "lodash.debounce": "^4.0.8", + "normalize-path": "^2.1.1", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.0.0", + "upath": "^1.0.5" + } + }, + "ci-info": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz", + "integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==" + }, + "class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "requires": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "cli-boxes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-1.0.0.tgz", + "integrity": "sha1-T6kXw+WclKAEzWH47lCdplFocUM=" + }, + "collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "requires": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=" + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "configstore": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-3.1.2.tgz", + "integrity": "sha512-vtv5HtGjcYUgFrXc6Kx747B83MRRVS5R1VTEQoXvuP+kMI+if6uywV0nDGoiydJRy4yk7h9od5Og0kxx4zUXmw==", + "requires": { + "dot-prop": "^4.1.0", + "graceful-fs": "^4.1.2", + "make-dir": "^1.0.0", + "unique-string": "^1.0.0", + "write-file-atomic": "^2.0.0", + "xdg-basedir": "^3.0.0" + } + }, + "copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "create-error-class": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz", + "integrity": "sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=", + "requires": { + "capture-stack-trace": "^1.0.0" + } + }, + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "requires": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "crypto-random-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz", + "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=" + }, + "debug": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.5.tgz", + "integrity": "sha512-D61LaDQPQkxJ5AUM2mbSJRbPkNs/TmdmOeLAi1hgDkpDfIfetSrjmWhccwtuResSwMbACjx/xXQofvM9CE/aeg==", + "requires": { + "ms": "^2.1.1" + }, + "dependencies": { + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + } + } + }, + "decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=" + }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" + }, + "define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "requires": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "dependencies": { + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "dot-prop": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz", + "integrity": "sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==", + "requires": { + "is-obj": "^1.0.0" + } + }, + "duplexer": { + "version": "0.1.1", + "resolved": "http://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", + "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=" + }, + "duplexer3": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", + "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=" + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + }, + "event-stream": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.6.tgz", + "integrity": "sha512-dGXNg4F/FgVzlApjzItL+7naHutA3fDqbV/zAZqDDlXTjiMnQmZKu+prImWKszeBM5UQeGvAl3u1wBiKeDh61g==", + "requires": { + "duplexer": "^0.1.1", + "flatmap-stream": "^0.1.0", + "from": "^0.1.7", + "map-stream": "0.0.7", + "pause-stream": "^0.0.11", + "split": "^1.0.1", + "stream-combiner": "^0.2.2", + "through": "^2.3.8" + } + }, + "execa": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", + "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", + "requires": { + "cross-spawn": "^5.0.1", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "flatmap-stream": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/flatmap-stream/-/flatmap-stream-0.1.0.tgz", + "integrity": "sha512-Nlic4ZRYxikqnK5rj3YoxDVKGGtUjcNDUtvQ7XsdGLZmMwdUYnXf10o1zcXtzEZTBgc6GxeRpQxV/Wu3WPIIHA==" + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=" + }, + "fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "requires": { + "map-cache": "^0.2.2" + } + }, + "from": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", + "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=" + }, + "fsevents": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.4.tgz", + "integrity": "sha512-z8H8/diyk76B7q5wg+Ud0+CqzcAF3mBBI/bA5ne5zrRUUIvNkJY//D3BqyH571KuAC4Nr7Rw7CjWX4r0y9DvNg==", + "optional": true, + "requires": { + "nan": "^2.9.2", + "node-pre-gyp": "^0.10.0" + }, + "dependencies": { + "abbrev": { + "version": "1.1.1", + "bundled": true, + "optional": true + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true + }, + "aproba": { + "version": "1.2.0", + "bundled": true, + "optional": true + }, + "are-we-there-yet": { + "version": "1.1.4", + "bundled": true, + "optional": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "chownr": { + "version": "1.0.1", + "bundled": true, + "optional": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true, + "optional": true + }, + "debug": { + "version": "2.6.9", + "bundled": true, + "optional": true, + "requires": { + "ms": "2.0.0" + } + }, + "deep-extend": { + "version": "0.5.1", + "bundled": true, + "optional": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true, + "optional": true + }, + "detect-libc": { + "version": "1.0.3", + "bundled": true, + "optional": true + }, + "fs-minipass": { + "version": "1.2.5", + "bundled": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true, + "optional": true + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "optional": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "glob": { + "version": "7.1.2", + "bundled": true, + "optional": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true, + "optional": true + }, + "iconv-lite": { + "version": "0.4.21", + "bundled": true, + "optional": true, + "requires": { + "safer-buffer": "^2.1.0" + } + }, + "ignore-walk": { + "version": "3.0.1", + "bundled": true, + "optional": true, + "requires": { + "minimatch": "^3.0.4" + } + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "optional": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true + }, + "ini": { + "version": "1.3.5", + "bundled": true, + "optional": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "bundled": true, + "optional": true + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "bundled": true + }, + "minipass": { + "version": "2.2.4", + "bundled": true, + "requires": { + "safe-buffer": "^5.1.1", + "yallist": "^3.0.0" + } + }, + "minizlib": { + "version": "1.1.0", + "bundled": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.0.0", + "bundled": true, + "optional": true + }, + "needle": { + "version": "2.2.0", + "bundled": true, + "optional": true, + "requires": { + "debug": "^2.1.2", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + } + }, + "node-pre-gyp": { + "version": "0.10.0", + "bundled": true, + "optional": true, + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.0", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.1.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4" + } + }, + "nopt": { + "version": "4.0.1", + "bundled": true, + "optional": true, + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "npm-bundled": { + "version": "1.0.3", + "bundled": true, + "optional": true + }, + "npm-packlist": { + "version": "1.1.10", + "bundled": true, + "optional": true, + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" + } + }, + "npmlog": { + "version": "4.1.2", + "bundled": true, + "optional": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "optional": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "requires": { + "wrappy": "1" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "optional": true + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true, + "optional": true + }, + "osenv": { + "version": "0.1.5", + "bundled": true, + "optional": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "optional": true + }, + "process-nextick-args": { + "version": "2.0.0", + "bundled": true, + "optional": true + }, + "rc": { + "version": "1.2.7", + "bundled": true, + "optional": true, + "requires": { + "deep-extend": "^0.5.1", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "bundled": true, + "optional": true + } + } + }, + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "optional": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "rimraf": { + "version": "2.6.2", + "bundled": true, + "optional": true, + "requires": { + "glob": "^7.0.5" + } + }, + "safe-buffer": { + "version": "5.1.1", + "bundled": true + }, + "safer-buffer": { + "version": "2.1.2", + "bundled": true, + "optional": true + }, + "sax": { + "version": "1.2.4", + "bundled": true, + "optional": true + }, + "semver": { + "version": "5.5.0", + "bundled": true, + "optional": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "optional": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "optional": true + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "optional": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true, + "optional": true + }, + "tar": { + "version": "4.4.1", + "bundled": true, + "optional": true, + "requires": { + "chownr": "^1.0.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.2.4", + "minizlib": "^1.1.0", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.1", + "yallist": "^3.0.2" + } + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true, + "optional": true + }, + "wide-align": { + "version": "1.1.2", + "bundled": true, + "optional": true, + "requires": { + "string-width": "^1.0.2" + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true + }, + "yallist": { + "version": "3.0.2", + "bundled": true + } + } + }, + "get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=" + }, + "get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=" + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "global-dirs": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz", + "integrity": "sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU=", + "requires": { + "ini": "^1.3.4" + } + }, + "got": { + "version": "6.7.1", + "resolved": "http://registry.npmjs.org/got/-/got-6.7.1.tgz", + "integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=", + "requires": { + "create-error-class": "^3.0.0", + "duplexer3": "^0.1.4", + "get-stream": "^3.0.0", + "is-redirect": "^1.0.0", + "is-retry-allowed": "^1.0.0", + "is-stream": "^1.0.0", + "lowercase-keys": "^1.0.0", + "safe-buffer": "^5.0.1", + "timed-out": "^4.0.0", + "unzip-response": "^2.0.1", + "url-parse-lax": "^1.0.0" + } + }, + "graceful-fs": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + }, + "has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "requires": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + } + }, + "has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "requires": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "dependencies": { + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=" + }, + "import-lazy": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", + "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=" + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "ini": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "requires": { + "binary-extensions": "^1.0.0" + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + }, + "is-ci": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.2.1.tgz", + "integrity": "sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==", + "requires": { + "ci-info": "^1.5.0" + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" + } + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + }, + "is-glob": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz", + "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=", + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-installed-globally": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.1.0.tgz", + "integrity": "sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA=", + "requires": { + "global-dirs": "^0.1.0", + "is-path-inside": "^1.0.0" + } + }, + "is-npm": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-1.0.0.tgz", + "integrity": "sha1-8vtjpl5JBbQGyGBydloaTceTufQ=" + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-obj": { + "version": "1.0.1", + "resolved": "http://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=" + }, + "is-path-inside": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", + "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", + "requires": { + "path-is-inside": "^1.0.1" + } + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "requires": { + "isobject": "^3.0.1" + } + }, + "is-redirect": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz", + "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=" + }, + "is-retry-allowed": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz", + "integrity": "sha1-EaBgVotnM5REAz0BJaYaINVk+zQ=" + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" + }, + "latest-version": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-3.1.0.tgz", + "integrity": "sha1-ogU4P+oyKzO1rjsYq+4NwvNW7hU=", + "requires": { + "package-json": "^4.0.0" + } + }, + "lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=" + }, + "lowercase-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==" + }, + "lru-cache": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.3.tgz", + "integrity": "sha512-fFEhvcgzuIoJVUF8fYr5KR0YqxD238zgObTps31YdADwPPAp82a4M8TrckkWyx7ekNlf9aBcVn81cFwwXngrJA==", + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "make-dir": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "requires": { + "pify": "^3.0.0" + } + }, + "map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=" + }, + "map-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.0.7.tgz", + "integrity": "sha1-ih8HiW2CsQkmvTdEokIACfiJdKg=" + }, + "map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "requires": { + "object-visit": "^1.0.0" + } + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + }, + "mixin-deep": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz", + "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==", + "requires": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "nan": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.11.0.tgz", + "integrity": "sha512-F4miItu2rGnV2ySkXOQoA8FKz/SR2Q2sWP0sbTxNxz/tuokeC8WxOhPMcwi0qIyGtVn/rrSeLbvVkznqCdwYnw==", + "optional": true + }, + "nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + } + }, + "nodemon": { + "version": "1.18.4", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-1.18.4.tgz", + "integrity": "sha512-hyK6vl65IPnky/ee+D3IWvVGgJa/m3No2/Xc/3wanS6Ce1MWjCzH6NnhPJ/vZM+6JFym16jtHx51lmCMB9HDtg==", + "requires": { + "chokidar": "^2.0.2", + "debug": "^3.1.0", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.0.4", + "pstree.remy": "^1.1.0", + "semver": "^5.5.0", + "supports-color": "^5.2.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.2", + "update-notifier": "^2.3.0" + } + }, + "nopt": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", + "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", + "requires": { + "abbrev": "1" + } + }, + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "requires": { + "remove-trailing-separator": "^1.0.1" + } + }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "requires": { + "path-key": "^2.0.0" + } + }, + "object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "requires": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "requires": { + "isobject": "^3.0.0" + } + }, + "object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "requires": { + "isobject": "^3.0.1" + } + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" + }, + "package-json": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/package-json/-/package-json-4.0.1.tgz", + "integrity": "sha1-iGmgQBJTZhxMTKPabCEh7VVfXu0=", + "requires": { + "got": "^6.7.1", + "registry-auth-token": "^3.0.1", + "registry-url": "^3.0.3", + "semver": "^5.1.0" + } + }, + "pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=" + }, + "path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=" + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=" + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" + }, + "pause-stream": { + "version": "0.0.11", + "resolved": "http://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", + "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=", + "requires": { + "through": "~2.3" + } + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" + }, + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=" + }, + "prepend-http": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", + "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=" + }, + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" + }, + "ps-tree": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ps-tree/-/ps-tree-1.1.0.tgz", + "integrity": "sha1-tCGyQUDWID8e08dplrRCewjowBQ=", + "requires": { + "event-stream": "~3.3.0" + } + }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" + }, + "pstree.remy": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.0.tgz", + "integrity": "sha512-q5I5vLRMVtdWa8n/3UEzZX7Lfghzrg9eG2IKk2ENLSofKRCXVqMvMUHxCKgXNaqH/8ebhBxrqftHWnyTFweJ5Q==", + "requires": { + "ps-tree": "^1.1.0" + } + }, + "rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + } + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "requires": { + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" + } + }, + "regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "requires": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + } + }, + "registry-auth-token": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.2.tgz", + "integrity": "sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==", + "requires": { + "rc": "^1.1.6", + "safe-buffer": "^5.0.1" + } + }, + "registry-url": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", + "integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=", + "requires": { + "rc": "^1.0.1" + } + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=" + }, + "repeat-element": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", + "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==" + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" + }, + "resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=" + }, + "ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==" + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "requires": { + "ret": "~0.1.10" + } + }, + "semver": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.1.tgz", + "integrity": "sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw==" + }, + "semver-diff": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-2.1.0.tgz", + "integrity": "sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY=", + "requires": { + "semver": "^5.0.3" + } + }, + "set-value": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", + "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==", + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" + }, + "snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "requires": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "requires": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "requires": { + "kind-of": "^3.2.0" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + }, + "source-map-resolve": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", + "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", + "requires": { + "atob": "^2.1.1", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "source-map-url": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=" + }, + "split": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", + "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", + "requires": { + "through": "2" + } + }, + "split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "requires": { + "extend-shallow": "^3.0.0" + } + }, + "static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "requires": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "stream-combiner": { + "version": "0.2.2", + "resolved": "http://registry.npmjs.org/stream-combiner/-/stream-combiner-0.2.2.tgz", + "integrity": "sha1-rsjLrBd7Vrb0+kec7YwZEs7lKFg=", + "requires": { + "duplexer": "~0.1.1", + "through": "~2.3.4" + } + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "requires": { + "ansi-regex": "^3.0.0" + } + }, + "strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + }, + "term-size": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/term-size/-/term-size-1.2.0.tgz", + "integrity": "sha1-RYuDiH8oj8Vtb/+/rSYuJmOO+mk=", + "requires": { + "execa": "^0.7.0" + } + }, + "through": { + "version": "2.3.8", + "resolved": "http://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" + }, + "timed-out": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", + "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=" + }, + "to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "requires": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + }, + "touch": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", + "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", + "requires": { + "nopt": "~1.0.10" + } + }, + "undefsafe": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.2.tgz", + "integrity": "sha1-Il9rngM3Zj4Njnz9aG/Cg2zKznY=", + "requires": { + "debug": "^2.2.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + } + } + }, + "union-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", + "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=", + "requires": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^0.4.3" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + }, + "set-value": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz", + "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=", + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.1", + "to-object-path": "^0.3.0" + } + } + } + }, + "unique-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-1.0.0.tgz", + "integrity": "sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo=", + "requires": { + "crypto-random-string": "^1.0.0" + } + }, + "unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "requires": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "requires": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=" + } + } + }, + "unzip-response": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-2.0.1.tgz", + "integrity": "sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c=" + }, + "upath": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.0.tgz", + "integrity": "sha512-bzpH/oBhoS/QI/YtbkqCg6VEiPYjSZtrHQM6/QnJS6OL9pKUFLqb3aFh4Scvwm45+7iAgiMkLhSbaZxUqmrprw==" + }, + "update-notifier": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-2.5.0.tgz", + "integrity": "sha512-gwMdhgJHGuj/+wHJJs9e6PcCszpxR1b236igrOkUofGhqJuG+amlIKwApH1IW1WWl7ovZxsX49lMBWLxSdm5Dw==", + "requires": { + "boxen": "^1.2.1", + "chalk": "^2.0.1", + "configstore": "^3.0.0", + "import-lazy": "^2.1.0", + "is-ci": "^1.0.10", + "is-installed-globally": "^0.1.0", + "is-npm": "^1.0.0", + "latest-version": "^3.0.0", + "semver-diff": "^2.0.0", + "xdg-basedir": "^3.0.0" + } + }, + "urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=" + }, + "url-parse-lax": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", + "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=", + "requires": { + "prepend-http": "^1.0.1" + } + }, + "use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==" + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "requires": { + "isexe": "^2.0.0" + } + }, + "widest-line": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-2.0.0.tgz", + "integrity": "sha1-AUKk6KJD+IgsAjOqDgKBqnYVInM=", + "requires": { + "string-width": "^2.1.1" + } + }, + "write-file-atomic": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.3.0.tgz", + "integrity": "sha512-xuPeK4OdjWqtfi59ylvVL0Yn35SF3zgcAcv7rBPFHVaEapaDr4GdGgm3j7ckTwH9wHL7fGmgfAnb0+THrHb8tA==", + "requires": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" + } + }, + "xdg-basedir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-3.0.0.tgz", + "integrity": "sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ=" + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" + } + } +} diff --git a/integration/examples/hot-reload/node/server.js b/integration/examples/hot-reload/node/server.js new file mode 100644 index 00000000000..e70e2778394 --- /dev/null +++ b/integration/examples/hot-reload/node/server.js @@ -0,0 +1,14 @@ +const http = require('http'); + +const hostname = '127.0.0.1'; +const port = 3000; + +const server = http.createServer((req, res) => { + res.statusCode = 200; + res.setHeader('Content-Type', 'text/plain'); + res.end('Hello World?\n'); +}); + +server.listen(port, hostname, () => { + console.log(`Node server running at http://${hostname}:${port}/`); +}); \ No newline at end of file diff --git a/integration/examples/hot-reload/skaffold.yaml b/integration/examples/hot-reload/skaffold.yaml new file mode 100644 index 00000000000..4f16076fef6 --- /dev/null +++ b/integration/examples/hot-reload/skaffold.yaml @@ -0,0 +1,16 @@ +apiVersion: skaffold/v1alpha3 +kind: Config +build: + artifacts: + - imageName: gcr.io/k8s-skaffold/node-example + workspace: node + sync: + '*.js': . + - imageName: gcr.io/k8s-skaffold/react-example + workspace: react + sync: + src/*: . +deploy: + kubectl: + manifests: + - node/k8s/** diff --git a/pkg/skaffold/runner/runner.go b/pkg/skaffold/runner/runner.go index 97e12c6a113..4a364f38a9e 100644 --- a/pkg/skaffold/runner/runner.go +++ b/pkg/skaffold/runner/runner.go @@ -402,19 +402,16 @@ func (r *SkaffoldRunner) shouldSync(image string, context string, syncPatterns m func intersect(context string, syncMap map[string]string, files []string) (map[string]string, error) { ret := map[string]string{} - fmt.Println("context", context) for _, f := range files { relPath, err := filepath.Rel(context, f) if err != nil { return nil, errors.Wrapf(err, "changed file %s is not relative to context %s", f, context) } - fmt.Println("relPath", relPath) for p, dst := range syncMap { match, err := filepath.Match(p, relPath) if err != nil { return nil, errors.Wrap(err, "pattern error") } - fmt.Println("match", match, "pattern", p, "f", f, "dst", dst) if !match { return nil, nil } diff --git a/pkg/skaffold/runner/runner_test.go b/pkg/skaffold/runner/runner_test.go index ccbe1646fe9..e99e468b4eb 100644 --- a/pkg/skaffold/runner/runner_test.go +++ b/pkg/skaffold/runner/runner_test.go @@ -289,6 +289,7 @@ func TestIntersect(t *testing.T) { description string syncPatterns map[string]string files []string + context string expected map[string]string shouldErr bool }{ @@ -307,11 +308,31 @@ func TestIntersect(t *testing.T) { "static/test.html": "/html/test.html", }, }, + { + description: "file not in . copies to correct destination", + files: []string{"node/server.js"}, + context: "node", + syncPatterns: map[string]string{ + "*.js": "/", + }, + expected: map[string]string{ + "node/server.js": "/server.js", + }, + }, + { + description: "file change not relative to context throws error", + files: []string{"node/server.js", "/something/test.js"}, + context: "node", + syncPatterns: map[string]string{ + "*.js": "/", + }, + shouldErr: true, + }, } for _, test := range tests { t.Run(test.description, func(t *testing.T) { - actual, err := intersect(test.syncPatterns, test.files) + actual, err := intersect(test.context, test.syncPatterns, test.files) testutil.CheckErrorAndDeepEqual(t, test.shouldErr, err, test.expected, actual) }) } @@ -611,6 +632,7 @@ func TestShouldSync(t *testing.T) { evt watch.Events copies map[string]string deletes []string + context string shouldErr bool expected bool syncer *TestSyncer @@ -694,7 +716,7 @@ func TestShouldSync(t *testing.T) { r := &SkaffoldRunner{ Syncer: test.syncer, } - actual, err := r.shouldSync("", test.syncPatterns, test.evt) + actual, err := r.shouldSync(test.context, "", test.syncPatterns, test.evt) testutil.CheckErrorAndDeepEqual(t, test.shouldErr, err, test.expected, actual) if test.expected { testutil.CheckDeepEqual(t, test.copies, test.syncer.copies) From 6edae2e1264b38652ecd64bb845025f97418bade Mon Sep 17 00:00:00 2001 From: Matt Rickard Date: Fri, 28 Sep 2018 14:55:40 -0500 Subject: [PATCH 11/24] [sync] move to sync package, move logic to watch main loop --- pkg/skaffold/kubernetes/sync.go | 20 ++-- pkg/skaffold/runner/changes.go | 26 +++- pkg/skaffold/runner/runner.go | 41 ++++--- pkg/skaffold/runner/runner_test.go | 184 +++-------------------------- pkg/skaffold/sync/sync.go | 73 ++++++++++++ pkg/skaffold/sync/sync_test.go | 176 +++++++++++++++++++++++++++ 6 files changed, 320 insertions(+), 200 deletions(-) create mode 100644 pkg/skaffold/sync/sync.go create mode 100644 pkg/skaffold/sync/sync_test.go diff --git a/pkg/skaffold/kubernetes/sync.go b/pkg/skaffold/kubernetes/sync.go index d5e5556fca6..1db5642f4f6 100644 --- a/pkg/skaffold/kubernetes/sync.go +++ b/pkg/skaffold/kubernetes/sync.go @@ -21,6 +21,7 @@ import ( "os/exec" "strings" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/sync" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util" "github.com/sirupsen/logrus" "golang.org/x/sync/errgroup" @@ -30,19 +31,16 @@ import ( meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -type Syncer interface { - CopyFilesForImage(image string, syncMap map[string]string) error - DeleteFilesForImage(image string, syncMap map[string]string) error -} - type KubectlSyncer struct{} -func (*KubectlSyncer) CopyFilesForImage(image string, syncMap map[string]string) error { - return perform(image, syncMap, copyFileFn) -} - -func (*KubectlSyncer) DeleteFilesForImage(image string, syncMap map[string]string) error { - return perform(image, syncMap, deleteFileFn) +func (k *KubectlSyncer) Sync(s *sync.SyncItem) error { + if err := perform(s.Image, s.Copy, copyFileFn); err != nil { + return errors.Wrap(err, "copying files") + } + if err := perform(s.Image, s.Delete, deleteFileFn); err != nil { + return errors.Wrap(err, "deleting files") + } + return nil } func deleteFileFn(pod v1.Pod, container v1.Container, src, dst string) *exec.Cmd { diff --git a/pkg/skaffold/runner/changes.go b/pkg/skaffold/runner/changes.go index e33137564fd..f5501aa0876 100644 --- a/pkg/skaffold/runner/changes.go +++ b/pkg/skaffold/runner/changes.go @@ -18,20 +18,40 @@ package runner import ( "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/sync" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/watch" ) type changes struct { - dirtyArtifacts []*latest.Artifact + dirtyArtifacts []*artifactChange + needsRebuild []*latest.Artifact + needsResync []*sync.SyncItem needsRedeploy bool needsReload bool } -func (c *changes) Add(a *latest.Artifact) { - c.dirtyArtifacts = append(c.dirtyArtifacts, a) +type artifactChange struct { + artifact *latest.Artifact + events watch.Events +} + +func (c *changes) AddDirtyArtifact(a *latest.Artifact, e watch.Events) { + c.dirtyArtifacts = append(c.dirtyArtifacts, &artifactChange{artifact: a, events: e}) +} + +func (c *changes) AddRebuild(a *latest.Artifact) { + c.needsRebuild = append(c.needsRebuild, a) +} + +func (c *changes) AddResync(s *sync.SyncItem) { + c.needsResync = append(c.needsResync, s) } func (c *changes) reset() { c.dirtyArtifacts = nil + c.needsRebuild = nil + c.needsResync = nil + c.needsRedeploy = false c.needsReload = false } diff --git a/pkg/skaffold/runner/runner.go b/pkg/skaffold/runner/runner.go index 4a364f38a9e..ba72e20ded4 100644 --- a/pkg/skaffold/runner/runner.go +++ b/pkg/skaffold/runner/runner.go @@ -30,13 +30,13 @@ import ( "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/kaniko" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/local" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/tag" - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/color" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/config" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/deploy" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/docker" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/kubernetes" kubectx "github.com/GoogleContainerTools/skaffold/pkg/skaffold/kubernetes/context" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/sync" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/test" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/watch" @@ -55,7 +55,7 @@ type SkaffoldRunner struct { test.Tester tag.Tagger watch.Trigger - kubernetes.Syncer + sync.Syncer opts *config.SkaffoldOptions watchFactory watch.Factory @@ -254,12 +254,32 @@ func (r *SkaffoldRunner) Dev(ctx context.Context, out io.Writer, artifacts []*la } }() + for _, a := range changed.dirtyArtifacts { + s, err := sync.NewSyncItem(a.artifact, a.events) + if err != nil { + return errors.Wrap(err, "sync") + } + if s != nil { + changed.AddResync(s) + } + if s == nil { + changed.AddRebuild(a.artifact) + } + } + switch { case changed.needsReload: logger.Stop() return ErrorConfigurationChanged - case len(changed.dirtyArtifacts) > 0: - bRes, err := r.Build(ctx, out, r.Tagger, changed.dirtyArtifacts) + case len(changed.needsResync) > 0: + for _, s := range changed.needsResync { + if err := r.Syncer.Sync(s); err != nil { + logrus.Warnln("Skipping build and deploy due to sync error:", err) + return nil + } + } + case len(changed.needsRebuild) > 0: + bRes, err := r.Build(ctx, out, r.Tagger, changed.needsRebuild) if err != nil { logrus.Warnln("Skipping Deploy due to build error:", err) return nil @@ -302,18 +322,7 @@ func (r *SkaffoldRunner) Dev(ctx context.Context, out io.Writer, artifacts []*la if err := watcher.Register( func() ([]string, error) { return dependenciesForArtifact(artifact) }, - func(e watch.Events) { - sync, err := r.shouldSync(artifact.ImageName, artifact.Workspace, artifact.Sync, e) - if err != nil { - return errors.Wrap(err, "checking sync files") - } - if !sync { - changed.Add(artifact) - } else { - color.Default.Fprintln(out, "Synced:", "copied", append(e.Added, e.Modified...), "deleted", e.Deleted) - } - return nil - }, + func(e watch.Events) { changed.AddDirtyArtifact(artifact, e) }, ); err != nil { return nil, errors.Wrapf(err, "watching files for artifact %s", artifact.ImageName) } diff --git a/pkg/skaffold/runner/runner_test.go b/pkg/skaffold/runner/runner_test.go index e99e468b4eb..c23f1516bca 100644 --- a/pkg/skaffold/runner/runner_test.go +++ b/pkg/skaffold/runner/runner_test.go @@ -30,6 +30,7 @@ import ( "github.com/GoogleContainerTools/skaffold/pkg/skaffold/deploy" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/kubernetes" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/sync" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/test" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/watch" "github.com/GoogleContainerTools/skaffold/testutil" @@ -111,9 +112,9 @@ func (t *TestDeployer) Cleanup(ctx context.Context, out io.Writer) error { } type TestSyncer struct { - copyErr, deleteErr error - copies map[string]string - deletes []string + err error + copies map[string]string + deletes []string } func NewTestSyncer() *TestSyncer { @@ -122,25 +123,24 @@ func NewTestSyncer() *TestSyncer { } } -func NewTestSyncerWithErrors(copyErr, deleteErr error) *TestSyncer { +func NewTestSyncerWithError(err error) *TestSyncer { return &TestSyncer{ - copies: map[string]string{}, - copyErr: copyErr, - deleteErr: deleteErr, + copies: map[string]string{}, + err: err, } } -func (t *TestSyncer) CopyFilesForImage(image string, f map[string]string) error { - for src, dst := range f { +func (t *TestSyncer) Sync(s *sync.SyncItem) error { + if t.err != nil { + return t.err + } + for src, dst := range s.Copy { t.copies[src] = dst } - return t.copyErr -} -func (t *TestSyncer) DeleteFilesForImage(image string, f map[string]string) error { - for _, dst := range f { + for _, dst := range s.Delete { t.deletes = append(t.deletes, dst) } - return t.deleteErr + return nil } func resetClient() { kubernetes.Client = kubernetes.GetClientset } @@ -284,60 +284,6 @@ func TestNewForConfig(t *testing.T) { } } -func TestIntersect(t *testing.T) { - var tests = []struct { - description string - syncPatterns map[string]string - files []string - context string - expected map[string]string - shouldErr bool - }{ - { - description: "nil sync patterns doesn't sync", - expected: map[string]string{}, - }, - { - description: "copy nested file to correct destination", - files: []string{"static/index.html", "static/test.html"}, - syncPatterns: map[string]string{ - "static/*.html": "/html", - }, - expected: map[string]string{ - "static/index.html": "/html/index.html", - "static/test.html": "/html/test.html", - }, - }, - { - description: "file not in . copies to correct destination", - files: []string{"node/server.js"}, - context: "node", - syncPatterns: map[string]string{ - "*.js": "/", - }, - expected: map[string]string{ - "node/server.js": "/server.js", - }, - }, - { - description: "file change not relative to context throws error", - files: []string{"node/server.js", "/something/test.js"}, - context: "node", - syncPatterns: map[string]string{ - "*.js": "/", - }, - shouldErr: true, - }, - } - - for _, test := range tests { - t.Run(test.description, func(t *testing.T) { - actual, err := intersect(test.context, test.syncPatterns, test.files) - testutil.CheckErrorAndDeepEqual(t, test.shouldErr, err, test.expected, actual) - }) - } -} - func TestRun(t *testing.T) { var tests = []struct { description string @@ -624,105 +570,3 @@ func TestShouldWatch(t *testing.T) { }) } } - -func TestShouldSync(t *testing.T) { - var tests = []struct { - description string - syncPatterns map[string]string - evt watch.Events - copies map[string]string - deletes []string - context string - shouldErr bool - expected bool - syncer *TestSyncer - }{ - { - description: "match copy", - syncPatterns: map[string]string{ - "*.html": ".", - }, - evt: watch.Events{ - Added: []string{"index.html"}, - }, - copies: map[string]string{ - "index.html": "index.html", - }, - syncer: NewTestSyncer(), - expected: true, - }, - { - description: "not copy syncable", - syncPatterns: map[string]string{ - "*.html": ".", - }, - evt: watch.Events{ - Added: []string{"main.go"}, - Deleted: []string{"index.html"}, - }, - syncer: NewTestSyncer(), - expected: false, - }, - { - description: "not delete syncable", - syncPatterns: map[string]string{ - "*.html": "/static", - }, - evt: watch.Events{ - Added: []string{"index.html"}, - Deleted: []string{"some/other/file"}, - }, - syncer: NewTestSyncer(), - expected: false, - }, - { - description: "err bad pattern", - syncPatterns: map[string]string{ - "[*.html": "*", - }, - evt: watch.Events{ - Added: []string{"index.html"}, - Deleted: []string{"some/other/file"}, - }, - syncer: NewTestSyncer(), - shouldErr: true, - }, - { - description: "err copy", - syncPatterns: map[string]string{ - "*.html": "*", - }, - evt: watch.Events{ - Added: []string{"index.html"}, - }, - syncer: NewTestSyncerWithErrors(fmt.Errorf(""), nil), - shouldErr: true, - }, - { - description: "err copy", - syncPatterns: map[string]string{ - "*.html": "*", - }, - evt: watch.Events{ - Added: []string{"index.html"}, - }, - syncer: NewTestSyncerWithErrors(nil, fmt.Errorf("")), - shouldErr: true, - }, - } - - for _, test := range tests { - t.Run(test.description, func(t *testing.T) { - r := &SkaffoldRunner{ - Syncer: test.syncer, - } - actual, err := r.shouldSync(test.context, "", test.syncPatterns, test.evt) - testutil.CheckErrorAndDeepEqual(t, test.shouldErr, err, test.expected, actual) - if test.expected { - testutil.CheckDeepEqual(t, test.copies, test.syncer.copies) - testutil.CheckDeepEqual(t, test.deletes, test.syncer.deletes) - } - }) - } - -} diff --git a/pkg/skaffold/sync/sync.go b/pkg/skaffold/sync/sync.go new file mode 100644 index 00000000000..eac9278c910 --- /dev/null +++ b/pkg/skaffold/sync/sync.go @@ -0,0 +1,73 @@ +package sync + +import ( + "path/filepath" + + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha3" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/watch" + "github.com/pkg/errors" +) + +type Syncer interface { + Sync(s *SyncItem) error +} + +type SyncItem struct { + Image string + Copy map[string]string + Delete map[string]string +} + +func NewSyncItem(a *v1alpha3.Artifact, e watch.Events) (*SyncItem, error) { + // If there are no changes, short circuit and don't sync anything + if !e.HasChanged() || a.Sync == nil || len(a.Sync) == 0 { + return nil, nil + } + + toCopy, err := intersect(a.Workspace, a.Sync, append(e.Added, e.Modified...)) + if err != nil { + return nil, errors.Wrap(err, "intersecting sync map and added, modified files") + } + + toDelete, err := intersect(a.Workspace, a.Sync, e.Deleted) + if err != nil { + return nil, errors.Wrap(err, "intersecting sync map and added, modified files") + } + + if toCopy == nil || toDelete == nil { + return nil, nil + } + + return &SyncItem{ + Image: a.ImageName, + Copy: toCopy, + Delete: toDelete, + }, nil +} + +func intersect(context string, syncMap map[string]string, files []string) (map[string]string, error) { + ret := map[string]string{} + for _, f := range files { + relPath, err := filepath.Rel(context, f) + if err != nil { + return nil, errors.Wrapf(err, "changed file %s is not relative to context %s", f, context) + } + for p, dst := range syncMap { + match, err := filepath.Match(p, relPath) + if err != nil { + return nil, errors.Wrap(err, "pattern error") + } + if !match { + return nil, nil + } + // If the source has special match characters, + // the destination must be a directory + if util.HasMeta(p) { + dst = filepath.Join(dst, filepath.Base(relPath)) + } + ret[f] = dst + } + } + return ret, nil +} diff --git a/pkg/skaffold/sync/sync_test.go b/pkg/skaffold/sync/sync_test.go new file mode 100644 index 00000000000..19904c70d6f --- /dev/null +++ b/pkg/skaffold/sync/sync_test.go @@ -0,0 +1,176 @@ +package sync + +import ( + "testing" + + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha3" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/watch" + "github.com/GoogleContainerTools/skaffold/testutil" +) + +func TestShouldSync(t *testing.T) { + var tests = []struct { + description string + artifact *v1alpha3.Artifact + evt watch.Events + shouldErr bool + expected *SyncItem + }{ + { + description: "match copy", + artifact: &v1alpha3.Artifact{ + ImageName: "test", + Sync: map[string]string{ + "*.html": ".", + }, + Workspace: ".", + }, + evt: watch.Events{ + Added: []string{"index.html"}, + }, + expected: &SyncItem{ + Image: "test", + Copy: map[string]string{ + "index.html": "index.html", + }, + Delete: map[string]string{}, + }, + }, + { + description: "sync all", + artifact: &v1alpha3.Artifact{ + ImageName: "test", + Sync: map[string]string{ + "*": ".", + }, + Workspace: "node", + }, + evt: watch.Events{ + Added: []string{"node/index.html"}, + Modified: []string{"node/server.js"}, + Deleted: []string{"node/package.json"}, + }, + expected: &SyncItem{ + Image: "test", + Copy: map[string]string{ + "node/server.js": "server.js", + "node/index.html": "index.html", + }, + Delete: map[string]string{ + "node/package.json": "package.json", + }, + }, + }, + { + description: "not copy syncable", + artifact: &v1alpha3.Artifact{ + Sync: map[string]string{ + "*.html": ".", + }, + Workspace: ".", + }, + evt: watch.Events{ + Added: []string{"main.go"}, + Deleted: []string{"index.html"}, + }, + }, + { + description: "not delete syncable", + artifact: &v1alpha3.Artifact{ + Sync: map[string]string{ + "*.html": "/static", + }, + Workspace: ".", + }, + evt: watch.Events{ + Added: []string{"index.html"}, + Deleted: []string{"some/other/file"}, + }, + }, + { + description: "err bad pattern", + artifact: &v1alpha3.Artifact{ + Sync: map[string]string{ + "[*.html": "*", + }, + Workspace: ".", + }, + evt: watch.Events{ + Added: []string{"index.html"}, + Deleted: []string{"some/other/file"}, + }, + shouldErr: true, + }, + { + description: "no change no sync", + artifact: &v1alpha3.Artifact{ + Sync: map[string]string{ + "[*.html": "*", + }, + Workspace: ".", + }, + }, + } + + for _, test := range tests { + t.Run(test.description, func(t *testing.T) { + actual, err := NewSyncItem(test.artifact, test.evt) + testutil.CheckErrorAndDeepEqual(t, test.shouldErr, err, test.expected, actual) + }) + } + +} + +func TestIntersect(t *testing.T) { + var tests = []struct { + description string + syncPatterns map[string]string + files []string + context string + expected map[string]string + shouldErr bool + }{ + { + description: "nil sync patterns doesn't sync", + expected: map[string]string{}, + }, + { + description: "copy nested file to correct destination", + files: []string{"static/index.html", "static/test.html"}, + syncPatterns: map[string]string{ + "static/*.html": "/html", + }, + expected: map[string]string{ + "static/index.html": "/html/index.html", + "static/test.html": "/html/test.html", + }, + }, + { + description: "file not in . copies to correct destination", + files: []string{"node/server.js"}, + context: "node", + syncPatterns: map[string]string{ + "*.js": "/", + }, + expected: map[string]string{ + "node/server.js": "/server.js", + }, + }, + { + description: "file change not relative to context throws error", + files: []string{"node/server.js", "/something/test.js"}, + context: "node", + syncPatterns: map[string]string{ + "*.js": "/", + }, + shouldErr: true, + }, + } + + for _, test := range tests { + t.Run(test.description, func(t *testing.T) { + actual, err := intersect(test.context, test.syncPatterns, test.files) + testutil.CheckErrorAndDeepEqual(t, test.shouldErr, err, test.expected, actual) + }) + } +} From 81bd94ca3f6cb67e719efc506ff68c7da9ac1302 Mon Sep 17 00:00:00 2001 From: Matt Rickard Date: Fri, 28 Sep 2018 14:56:07 -0500 Subject: [PATCH 12/24] [examples] add flask hot-reload example --- integration/examples/hot-reload/node/Dockerfile | 3 +-- integration/examples/hot-reload/node/server.js | 2 +- integration/examples/hot-reload/python/Dockerfile | 9 +++++++++ integration/examples/hot-reload/python/app.py | 6 ++++++ integration/examples/hot-reload/python/k8s/pod.yaml | 10 ++++++++++ .../examples/hot-reload/python/requirements.txt | 1 + integration/examples/hot-reload/skaffold.yaml | 8 ++++---- 7 files changed, 32 insertions(+), 7 deletions(-) create mode 100644 integration/examples/hot-reload/python/Dockerfile create mode 100644 integration/examples/hot-reload/python/app.py create mode 100644 integration/examples/hot-reload/python/k8s/pod.yaml create mode 100644 integration/examples/hot-reload/python/requirements.txt diff --git a/integration/examples/hot-reload/node/Dockerfile b/integration/examples/hot-reload/node/Dockerfile index abcafcbf67c..64839925af7 100644 --- a/integration/examples/hot-reload/node/Dockerfile +++ b/integration/examples/hot-reload/node/Dockerfile @@ -1,4 +1,3 @@ -FROM node:6.10.3 +FROM gcr.io/k8s-skaffold/nodemon CMD ["nodemon", "server.js"] -RUN npm install -g nodemon COPY *.js . diff --git a/integration/examples/hot-reload/node/server.js b/integration/examples/hot-reload/node/server.js index e70e2778394..5115ab9cd63 100644 --- a/integration/examples/hot-reload/node/server.js +++ b/integration/examples/hot-reload/node/server.js @@ -6,7 +6,7 @@ const port = 3000; const server = http.createServer((req, res) => { res.statusCode = 200; res.setHeader('Content-Type', 'text/plain'); - res.end('Hello World?\n'); + res.end('Hello World from Node!\n'); }); server.listen(port, hostname, () => { diff --git a/integration/examples/hot-reload/python/Dockerfile b/integration/examples/hot-reload/python/Dockerfile new file mode 100644 index 00000000000..84ba519f0bf --- /dev/null +++ b/integration/examples/hot-reload/python/Dockerfile @@ -0,0 +1,9 @@ +FROM python:3.7-slim +CMD ["python", "-m", "flask", "run"] +ENV FLASK_DEBUG=1 +ENV FLASK_APP=app.py + +COPY requirements.txt . +RUN pip install -r requirements.txt +COPY *.py . + diff --git a/integration/examples/hot-reload/python/app.py b/integration/examples/hot-reload/python/app.py new file mode 100644 index 00000000000..be4bff4ac5b --- /dev/null +++ b/integration/examples/hot-reload/python/app.py @@ -0,0 +1,6 @@ +from flask import Flask +app = Flask(__name__) + +@app.route('/') +def hello_world(): + return 'Hello, World from Flask!' \ No newline at end of file diff --git a/integration/examples/hot-reload/python/k8s/pod.yaml b/integration/examples/hot-reload/python/k8s/pod.yaml new file mode 100644 index 00000000000..54634a5c217 --- /dev/null +++ b/integration/examples/hot-reload/python/k8s/pod.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: Pod +metadata: + name: python +spec: + containers: + - name: python + image: gcr.io/k8s-skaffold/python-reload + ports: + - containerPort: 5000 diff --git a/integration/examples/hot-reload/python/requirements.txt b/integration/examples/hot-reload/python/requirements.txt new file mode 100644 index 00000000000..2c34d09e113 --- /dev/null +++ b/integration/examples/hot-reload/python/requirements.txt @@ -0,0 +1 @@ +Flask==1.0 \ No newline at end of file diff --git a/integration/examples/hot-reload/skaffold.yaml b/integration/examples/hot-reload/skaffold.yaml index 4f16076fef6..fb4a293bb20 100644 --- a/integration/examples/hot-reload/skaffold.yaml +++ b/integration/examples/hot-reload/skaffold.yaml @@ -6,11 +6,11 @@ build: workspace: node sync: '*.js': . - - imageName: gcr.io/k8s-skaffold/react-example - workspace: react + - imageName: gcr.io/k8s-skaffold/python-reload + workspace: python sync: - src/*: . + '*.py': . deploy: kubectl: manifests: - - node/k8s/** + - "**/k8s" From f7b8a442ed2d3bafef46ec2caa33a5293ce0a179 Mon Sep 17 00:00:00 2001 From: Matt Rickard Date: Fri, 28 Sep 2018 14:56:55 -0500 Subject: [PATCH 13/24] add boilerplate --- pkg/skaffold/sync/sync.go | 16 ++++++++++++++++ pkg/skaffold/sync/sync_test.go | 15 +++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/pkg/skaffold/sync/sync.go b/pkg/skaffold/sync/sync.go index eac9278c910..4a85c06079d 100644 --- a/pkg/skaffold/sync/sync.go +++ b/pkg/skaffold/sync/sync.go @@ -1,5 +1,21 @@ package sync +/* +Copyright 2018 The Skaffold Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + import ( "path/filepath" diff --git a/pkg/skaffold/sync/sync_test.go b/pkg/skaffold/sync/sync_test.go index 19904c70d6f..01bd5e108c8 100644 --- a/pkg/skaffold/sync/sync_test.go +++ b/pkg/skaffold/sync/sync_test.go @@ -1,3 +1,18 @@ +/* +Copyright 2018 The Skaffold Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ package sync import ( From 799d630c830db577dc0ea8325f85465e288669c8 Mon Sep 17 00:00:00 2001 From: Matt Rickard Date: Sun, 30 Sep 2018 10:30:32 -0500 Subject: [PATCH 14/24] linter fixes --- pkg/skaffold/kubernetes/sync.go | 2 +- pkg/skaffold/runner/changes.go | 4 ++-- pkg/skaffold/runner/runner.go | 2 +- pkg/skaffold/runner/runner_test.go | 9 +-------- pkg/skaffold/sync/sync.go | 12 ++++++------ pkg/skaffold/sync/sync_test.go | 8 ++++---- 6 files changed, 15 insertions(+), 22 deletions(-) diff --git a/pkg/skaffold/kubernetes/sync.go b/pkg/skaffold/kubernetes/sync.go index 1db5642f4f6..0bf02065fbf 100644 --- a/pkg/skaffold/kubernetes/sync.go +++ b/pkg/skaffold/kubernetes/sync.go @@ -33,7 +33,7 @@ import ( type KubectlSyncer struct{} -func (k *KubectlSyncer) Sync(s *sync.SyncItem) error { +func (k *KubectlSyncer) Sync(s *sync.Item) error { if err := perform(s.Image, s.Copy, copyFileFn); err != nil { return errors.Wrap(err, "copying files") } diff --git a/pkg/skaffold/runner/changes.go b/pkg/skaffold/runner/changes.go index f5501aa0876..d62bf6f2347 100644 --- a/pkg/skaffold/runner/changes.go +++ b/pkg/skaffold/runner/changes.go @@ -25,7 +25,7 @@ import ( type changes struct { dirtyArtifacts []*artifactChange needsRebuild []*latest.Artifact - needsResync []*sync.SyncItem + needsResync []*sync.Item needsRedeploy bool needsReload bool } @@ -43,7 +43,7 @@ func (c *changes) AddRebuild(a *latest.Artifact) { c.needsRebuild = append(c.needsRebuild, a) } -func (c *changes) AddResync(s *sync.SyncItem) { +func (c *changes) AddResync(s *sync.Item) { c.needsResync = append(c.needsResync, s) } diff --git a/pkg/skaffold/runner/runner.go b/pkg/skaffold/runner/runner.go index ba72e20ded4..97f66361373 100644 --- a/pkg/skaffold/runner/runner.go +++ b/pkg/skaffold/runner/runner.go @@ -255,7 +255,7 @@ func (r *SkaffoldRunner) Dev(ctx context.Context, out io.Writer, artifacts []*la }() for _, a := range changed.dirtyArtifacts { - s, err := sync.NewSyncItem(a.artifact, a.events) + s, err := sync.NewItem(a.artifact, a.events) if err != nil { return errors.Wrap(err, "sync") } diff --git a/pkg/skaffold/runner/runner_test.go b/pkg/skaffold/runner/runner_test.go index c23f1516bca..39784f780ba 100644 --- a/pkg/skaffold/runner/runner_test.go +++ b/pkg/skaffold/runner/runner_test.go @@ -123,14 +123,7 @@ func NewTestSyncer() *TestSyncer { } } -func NewTestSyncerWithError(err error) *TestSyncer { - return &TestSyncer{ - copies: map[string]string{}, - err: err, - } -} - -func (t *TestSyncer) Sync(s *sync.SyncItem) error { +func (t *TestSyncer) Sync(s *sync.Item) error { if t.err != nil { return t.err } diff --git a/pkg/skaffold/sync/sync.go b/pkg/skaffold/sync/sync.go index 4a85c06079d..9e56ca0e333 100644 --- a/pkg/skaffold/sync/sync.go +++ b/pkg/skaffold/sync/sync.go @@ -1,5 +1,3 @@ -package sync - /* Copyright 2018 The Skaffold Authors @@ -16,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +package sync + import ( "path/filepath" @@ -26,16 +26,16 @@ import ( ) type Syncer interface { - Sync(s *SyncItem) error + Sync(s *Item) error } -type SyncItem struct { +type Item struct { Image string Copy map[string]string Delete map[string]string } -func NewSyncItem(a *v1alpha3.Artifact, e watch.Events) (*SyncItem, error) { +func NewItem(a *v1alpha3.Artifact, e watch.Events) (*Item, error) { // If there are no changes, short circuit and don't sync anything if !e.HasChanged() || a.Sync == nil || len(a.Sync) == 0 { return nil, nil @@ -55,7 +55,7 @@ func NewSyncItem(a *v1alpha3.Artifact, e watch.Events) (*SyncItem, error) { return nil, nil } - return &SyncItem{ + return &Item{ Image: a.ImageName, Copy: toCopy, Delete: toDelete, diff --git a/pkg/skaffold/sync/sync_test.go b/pkg/skaffold/sync/sync_test.go index 01bd5e108c8..81de0f3ce7d 100644 --- a/pkg/skaffold/sync/sync_test.go +++ b/pkg/skaffold/sync/sync_test.go @@ -29,7 +29,7 @@ func TestShouldSync(t *testing.T) { artifact *v1alpha3.Artifact evt watch.Events shouldErr bool - expected *SyncItem + expected *Item }{ { description: "match copy", @@ -43,7 +43,7 @@ func TestShouldSync(t *testing.T) { evt: watch.Events{ Added: []string{"index.html"}, }, - expected: &SyncItem{ + expected: &Item{ Image: "test", Copy: map[string]string{ "index.html": "index.html", @@ -65,7 +65,7 @@ func TestShouldSync(t *testing.T) { Modified: []string{"node/server.js"}, Deleted: []string{"node/package.json"}, }, - expected: &SyncItem{ + expected: &Item{ Image: "test", Copy: map[string]string{ "node/server.js": "server.js", @@ -129,7 +129,7 @@ func TestShouldSync(t *testing.T) { for _, test := range tests { t.Run(test.description, func(t *testing.T) { - actual, err := NewSyncItem(test.artifact, test.evt) + actual, err := NewItem(test.artifact, test.evt) testutil.CheckErrorAndDeepEqual(t, test.shouldErr, err, test.expected, actual) }) } From d565e05d0068d6846c143bc33349fbf901ba5603 Mon Sep 17 00:00:00 2001 From: Matt Rickard Date: Sun, 30 Sep 2018 11:02:37 -0500 Subject: [PATCH 15/24] rebase on v1alpha4 --- integration/examples/hot-reload/skaffold.yaml | 2 +- pkg/skaffold/kubernetes/sync.go | 2 +- pkg/skaffold/runner/runner.go | 51 ------------------- pkg/skaffold/schema/latest/config.go | 5 +- pkg/skaffold/sync/sync.go | 4 +- pkg/skaffold/sync/sync_test.go | 16 +++--- 6 files changed, 15 insertions(+), 65 deletions(-) diff --git a/integration/examples/hot-reload/skaffold.yaml b/integration/examples/hot-reload/skaffold.yaml index fb4a293bb20..d888518b4e0 100644 --- a/integration/examples/hot-reload/skaffold.yaml +++ b/integration/examples/hot-reload/skaffold.yaml @@ -1,4 +1,4 @@ -apiVersion: skaffold/v1alpha3 +apiVersion: skaffold/v1alpha4 kind: Config build: artifacts: diff --git a/pkg/skaffold/kubernetes/sync.go b/pkg/skaffold/kubernetes/sync.go index 0bf02065fbf..68570fe7189 100644 --- a/pkg/skaffold/kubernetes/sync.go +++ b/pkg/skaffold/kubernetes/sync.go @@ -73,7 +73,7 @@ func perform(image string, files map[string]string, cmdFn func(v1.Pod, v1.Contai }) } if err := e.Wait(); err != nil { - return errors.Wrap(err, "syncing files:") + return errors.Wrap(err, "syncing files") } } } diff --git a/pkg/skaffold/runner/runner.go b/pkg/skaffold/runner/runner.go index 97f66361373..6a143474b6e 100644 --- a/pkg/skaffold/runner/runner.go +++ b/pkg/skaffold/runner/runner.go @@ -38,7 +38,6 @@ import ( "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/sync" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/test" - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/watch" "github.com/pkg/errors" @@ -385,56 +384,6 @@ func (r *SkaffoldRunner) Dev(ctx context.Context, out io.Writer, artifacts []*la return nil, watcher.Run(ctx, r.Trigger, onChange) } -func (r *SkaffoldRunner) shouldSync(image string, context string, syncPatterns map[string]string, e watch.Events) (bool, error) { - // If there are no changes, short circuit and don't sync anything - if !e.HasChanged() || syncPatterns == nil || len(syncPatterns) == 0 { - return false, nil - } - - toCopy, err := intersect(context, syncPatterns, append(e.Added, e.Modified...)) - if err != nil { - return false, errors.Wrap(err, "intersecting sync map and added, modified files") - } - // The only error that intersect can return is a bad pattern, which is checked above - toDelete, _ := intersect(context, syncPatterns, e.Deleted) - if toCopy == nil || toDelete == nil { - return false, nil - } - if err := r.Syncer.DeleteFilesForImage(image, toDelete); err != nil { - return false, errors.Wrap(err, "deleting files for image") - } - if err := r.Syncer.CopyFilesForImage(image, toCopy); err != nil { - return false, errors.Wrap(err, "copying files for image") - } - return true, nil -} - -func intersect(context string, syncMap map[string]string, files []string) (map[string]string, error) { - ret := map[string]string{} - for _, f := range files { - relPath, err := filepath.Rel(context, f) - if err != nil { - return nil, errors.Wrapf(err, "changed file %s is not relative to context %s", f, context) - } - for p, dst := range syncMap { - match, err := filepath.Match(p, relPath) - if err != nil { - return nil, errors.Wrap(err, "pattern error") - } - if !match { - return nil, nil - } - // If the source has special match characters, - // the destination must be a directory - if util.HasMeta(p) { - dst = filepath.Join(dst, filepath.Base(relPath)) - } - ret[f] = dst - } - } - return ret, nil -} - func (r *SkaffoldRunner) shouldWatch(artifact *latest.Artifact) bool { if len(r.opts.Watch) == 0 { return true diff --git a/pkg/skaffold/schema/latest/config.go b/pkg/skaffold/schema/latest/config.go index c9f7efbc1a8..02aa6c3faf4 100644 --- a/pkg/skaffold/schema/latest/config.go +++ b/pkg/skaffold/schema/latest/config.go @@ -210,8 +210,9 @@ type HelmConventionConfig struct { // Artifact represents items that need to be built, along with the context in which // they should be built. type Artifact struct { - ImageName string `yaml:"image"` - Workspace string `yaml:"context,omitempty"` + ImageName string `yaml:"image"` + Workspace string `yaml:"context,omitempty"` + Sync map[string]string `yaml:"sync,omitempty"` ArtifactType `yaml:",inline"` } diff --git a/pkg/skaffold/sync/sync.go b/pkg/skaffold/sync/sync.go index 9e56ca0e333..186db2e39a1 100644 --- a/pkg/skaffold/sync/sync.go +++ b/pkg/skaffold/sync/sync.go @@ -19,7 +19,7 @@ package sync import ( "path/filepath" - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha3" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/watch" "github.com/pkg/errors" @@ -35,7 +35,7 @@ type Item struct { Delete map[string]string } -func NewItem(a *v1alpha3.Artifact, e watch.Events) (*Item, error) { +func NewItem(a *latest.Artifact, e watch.Events) (*Item, error) { // If there are no changes, short circuit and don't sync anything if !e.HasChanged() || a.Sync == nil || len(a.Sync) == 0 { return nil, nil diff --git a/pkg/skaffold/sync/sync_test.go b/pkg/skaffold/sync/sync_test.go index 81de0f3ce7d..75a792a2ac7 100644 --- a/pkg/skaffold/sync/sync_test.go +++ b/pkg/skaffold/sync/sync_test.go @@ -18,7 +18,7 @@ package sync import ( "testing" - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha3" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/watch" "github.com/GoogleContainerTools/skaffold/testutil" ) @@ -26,14 +26,14 @@ import ( func TestShouldSync(t *testing.T) { var tests = []struct { description string - artifact *v1alpha3.Artifact + artifact *latest.Artifact evt watch.Events shouldErr bool expected *Item }{ { description: "match copy", - artifact: &v1alpha3.Artifact{ + artifact: &latest.Artifact{ ImageName: "test", Sync: map[string]string{ "*.html": ".", @@ -53,7 +53,7 @@ func TestShouldSync(t *testing.T) { }, { description: "sync all", - artifact: &v1alpha3.Artifact{ + artifact: &latest.Artifact{ ImageName: "test", Sync: map[string]string{ "*": ".", @@ -78,7 +78,7 @@ func TestShouldSync(t *testing.T) { }, { description: "not copy syncable", - artifact: &v1alpha3.Artifact{ + artifact: &latest.Artifact{ Sync: map[string]string{ "*.html": ".", }, @@ -91,7 +91,7 @@ func TestShouldSync(t *testing.T) { }, { description: "not delete syncable", - artifact: &v1alpha3.Artifact{ + artifact: &latest.Artifact{ Sync: map[string]string{ "*.html": "/static", }, @@ -104,7 +104,7 @@ func TestShouldSync(t *testing.T) { }, { description: "err bad pattern", - artifact: &v1alpha3.Artifact{ + artifact: &latest.Artifact{ Sync: map[string]string{ "[*.html": "*", }, @@ -118,7 +118,7 @@ func TestShouldSync(t *testing.T) { }, { description: "no change no sync", - artifact: &v1alpha3.Artifact{ + artifact: &latest.Artifact{ Sync: map[string]string{ "[*.html": "*", }, From 931b5d8c74f2d1d0a112f7da47ac41198c1baad7 Mon Sep 17 00:00:00 2001 From: Matt Rickard Date: Sun, 30 Sep 2018 11:23:09 -0500 Subject: [PATCH 16/24] fix windows test --- pkg/skaffold/sync/sync.go | 5 ++++- pkg/skaffold/sync/sync_test.go | 27 ++++++++++++++------------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/pkg/skaffold/sync/sync.go b/pkg/skaffold/sync/sync.go index 186db2e39a1..1cea5cf1481 100644 --- a/pkg/skaffold/sync/sync.go +++ b/pkg/skaffold/sync/sync.go @@ -17,6 +17,7 @@ limitations under the License. package sync import ( + "path" "path/filepath" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" @@ -79,8 +80,10 @@ func intersect(context string, syncMap map[string]string, files []string) (map[s } // If the source has special match characters, // the destination must be a directory + // The path package must be used here, since the destination is always + // a linux filesystem. if util.HasMeta(p) { - dst = filepath.Join(dst, filepath.Base(relPath)) + dst = path.Join(dst, filepath.Base(relPath)) } ret[f] = dst } diff --git a/pkg/skaffold/sync/sync_test.go b/pkg/skaffold/sync/sync_test.go index 75a792a2ac7..549648895a9 100644 --- a/pkg/skaffold/sync/sync_test.go +++ b/pkg/skaffold/sync/sync_test.go @@ -16,6 +16,7 @@ limitations under the License. package sync import ( + "path/filepath" "testing" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" @@ -61,18 +62,18 @@ func TestShouldSync(t *testing.T) { Workspace: "node", }, evt: watch.Events{ - Added: []string{"node/index.html"}, - Modified: []string{"node/server.js"}, - Deleted: []string{"node/package.json"}, + Added: []string{filepath.Join("node", "index.html")}, + Modified: []string{filepath.Join("node", "server.js")}, + Deleted: []string{filepath.Join("node", "package.json")}, }, expected: &Item{ Image: "test", Copy: map[string]string{ - "node/server.js": "server.js", - "node/index.html": "index.html", + filepath.Join("node", "server.js"): "server.js", + filepath.Join("node", "index.html"): "index.html", }, Delete: map[string]string{ - "node/package.json": "package.json", + filepath.Join("node", "package.json"): "package.json", }, }, }, @@ -151,29 +152,29 @@ func TestIntersect(t *testing.T) { }, { description: "copy nested file to correct destination", - files: []string{"static/index.html", "static/test.html"}, + files: []string{filepath.Join("static", "index.html"), filepath.Join("static", "test.html")}, syncPatterns: map[string]string{ - "static/*.html": "/html", + filepath.Join("static", "*.html"): "/html", }, expected: map[string]string{ - "static/index.html": "/html/index.html", - "static/test.html": "/html/test.html", + filepath.Join("static", "index.html"): "/html/index.html", + filepath.Join("static", "test.html"): "/html/test.html", }, }, { description: "file not in . copies to correct destination", - files: []string{"node/server.js"}, + files: []string{filepath.Join("node", "server.js")}, context: "node", syncPatterns: map[string]string{ "*.js": "/", }, expected: map[string]string{ - "node/server.js": "/server.js", + filepath.Join("node", "server.js"): "/server.js", }, }, { description: "file change not relative to context throws error", - files: []string{"node/server.js", "/something/test.js"}, + files: []string{filepath.Join("node", "server.js"), filepath.Join("/", "something", "test.js")}, context: "node", syncPatterns: map[string]string{ "*.js": "/", From 35ef51c7d662379ee001c90013cd80dd3145fa4e Mon Sep 17 00:00:00 2001 From: Matt Rickard Date: Mon, 1 Oct 2018 10:47:19 -0500 Subject: [PATCH 17/24] [parse] remove loud debug statements These run every second, so its difficult to read other debug statements --- pkg/skaffold/docker/parse.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/skaffold/docker/parse.go b/pkg/skaffold/docker/parse.go index 52abf8993ed..80d9b7a320b 100644 --- a/pkg/skaffold/docker/parse.go +++ b/pkg/skaffold/docker/parse.go @@ -249,7 +249,7 @@ func expandPaths(workspace string, copied [][]string) ([]string, error) { for dep := range expandedPaths { deps = append(deps, dep) } - // logrus.Infof("Found dependencies for dockerfile %s", deps) + logrus.Infof("Found dependencies for dockerfile %s", deps) return deps, nil } From 3f8ba1ee2431c50b478b7f616fb502bcfe03198e Mon Sep 17 00:00:00 2001 From: Matt Rickard Date: Mon, 1 Oct 2018 11:12:49 -0500 Subject: [PATCH 18/24] [sync] query only skaffold pods, use exact tag --- pkg/skaffold/kubernetes/sync.go | 15 ++++++-- pkg/skaffold/kubernetes/sync_test.go | 4 ++- pkg/skaffold/runner/runner.go | 4 +-- pkg/skaffold/sync/sync.go | 20 +++++++++-- pkg/skaffold/sync/sync_test.go | 51 +++++++++++++++++++++++++--- 5 files changed, 82 insertions(+), 12 deletions(-) diff --git a/pkg/skaffold/kubernetes/sync.go b/pkg/skaffold/kubernetes/sync.go index 68570fe7189..511f35563b2 100644 --- a/pkg/skaffold/kubernetes/sync.go +++ b/pkg/skaffold/kubernetes/sync.go @@ -21,6 +21,7 @@ import ( "os/exec" "strings" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/constants" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/sync" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util" "github.com/sirupsen/logrus" @@ -51,19 +52,29 @@ func copyFileFn(pod v1.Pod, container v1.Container, src, dst string) *exec.Cmd { return exec.Command("kubectl", "cp", src, fmt.Sprintf("%s/%s:%s", pod.Namespace, pod.Name, dst), "-c", container.Name) } +func labelSelector() string { + var reqs []string + for k, v := range constants.Labels.DefaultLabels { + reqs = append(reqs, fmt.Sprintf("%s=%s", k, v)) + } + return strings.Join(reqs, ",") +} + func perform(image string, files map[string]string, cmdFn func(v1.Pod, v1.Container, string, string) *exec.Cmd) error { logrus.Info("Syncing files:", files) client, err := Client() if err != nil { return errors.Wrap(err, "getting k8s client") } - pods, err := client.CoreV1().Pods("").List(meta_v1.ListOptions{}) + pods, err := client.CoreV1().Pods("").List(meta_v1.ListOptions{ + LabelSelector: labelSelector(), + }) if err != nil { return errors.Wrap(err, "getting pods") } for _, p := range pods.Items { for _, c := range p.Spec.Containers { - if strings.HasPrefix(c.Image, image) { + if c.Image == image { var e errgroup.Group for src, dst := range files { src, dst := src, dst diff --git a/pkg/skaffold/kubernetes/sync_test.go b/pkg/skaffold/kubernetes/sync_test.go index 9e071cacc27..2cc70021dc0 100644 --- a/pkg/skaffold/kubernetes/sync_test.go +++ b/pkg/skaffold/kubernetes/sync_test.go @@ -25,6 +25,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes/fake" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/constants" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util" "github.com/GoogleContainerTools/skaffold/testutil" v1 "k8s.io/api/core/v1" @@ -54,7 +55,8 @@ func fakeCmd(p v1.Pod, c v1.Container, src, dst string) *exec.Cmd { var pod = &v1.Pod{ ObjectMeta: metav1.ObjectMeta{ - Name: "podname", + Name: "podname", + Labels: constants.Labels.DefaultLabels, }, Status: v1.PodStatus{ Phase: v1.PodRunning, diff --git a/pkg/skaffold/runner/runner.go b/pkg/skaffold/runner/runner.go index 6a143474b6e..d515cd2a6b8 100644 --- a/pkg/skaffold/runner/runner.go +++ b/pkg/skaffold/runner/runner.go @@ -252,9 +252,8 @@ func (r *SkaffoldRunner) Dev(ctx context.Context, out io.Writer, artifacts []*la logger.Unmute() } }() - for _, a := range changed.dirtyArtifacts { - s, err := sync.NewItem(a.artifact, a.events) + s, err := sync.NewItem(a.artifact, a.events, r.builds) if err != nil { return errors.Wrap(err, "sync") } @@ -276,6 +275,7 @@ func (r *SkaffoldRunner) Dev(ctx context.Context, out io.Writer, artifacts []*la logrus.Warnln("Skipping build and deploy due to sync error:", err) return nil } + color.Default.Fprintf(out, "Synced files for %s...\nCopied: %s\nDeleted: %s\n", s.Image, s.Copy, s.Delete) } case len(changed.needsRebuild) > 0: bRes, err := r.Build(ctx, out, r.Tagger, changed.needsRebuild) diff --git a/pkg/skaffold/sync/sync.go b/pkg/skaffold/sync/sync.go index 1cea5cf1481..472a581b40b 100644 --- a/pkg/skaffold/sync/sync.go +++ b/pkg/skaffold/sync/sync.go @@ -17,10 +17,12 @@ limitations under the License. package sync import ( + "fmt" "path" "path/filepath" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/watch" "github.com/pkg/errors" @@ -36,7 +38,7 @@ type Item struct { Delete map[string]string } -func NewItem(a *latest.Artifact, e watch.Events) (*Item, error) { +func NewItem(a *latest.Artifact, e watch.Events, builds []build.Artifact) (*Item, error) { // If there are no changes, short circuit and don't sync anything if !e.HasChanged() || a.Sync == nil || len(a.Sync) == 0 { return nil, nil @@ -56,13 +58,27 @@ func NewItem(a *latest.Artifact, e watch.Events) (*Item, error) { return nil, nil } + tag := latestTag(a.ImageName, builds) + if tag == "" { + return nil, fmt.Errorf("Could not find latest tag for image %s in builds: %s", a.ImageName, builds) + } + return &Item{ - Image: a.ImageName, + Image: tag, Copy: toCopy, Delete: toDelete, }, nil } +func latestTag(image string, builds []build.Artifact) string { + for _, build := range builds { + if build.ImageName == image { + return build.Tag + } + } + return "" +} + func intersect(context string, syncMap map[string]string, files []string) (map[string]string, error) { ret := map[string]string{} for _, f := range files { diff --git a/pkg/skaffold/sync/sync_test.go b/pkg/skaffold/sync/sync_test.go index 549648895a9..ea310375177 100644 --- a/pkg/skaffold/sync/sync_test.go +++ b/pkg/skaffold/sync/sync_test.go @@ -19,16 +19,19 @@ import ( "path/filepath" "testing" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/watch" "github.com/GoogleContainerTools/skaffold/testutil" ) -func TestShouldSync(t *testing.T) { +func TestNewSyncItem(t *testing.T) { var tests = []struct { description string artifact *latest.Artifact evt watch.Events + builds []build.Artifact shouldErr bool expected *Item }{ @@ -41,17 +44,43 @@ func TestShouldSync(t *testing.T) { }, Workspace: ".", }, + builds: []build.Artifact{ + { + ImageName: "test", + Tag: "test:123", + }, + }, evt: watch.Events{ Added: []string{"index.html"}, }, expected: &Item{ - Image: "test", + Image: "test:123", Copy: map[string]string{ "index.html": "index.html", }, Delete: map[string]string{}, }, }, + { + description: "no tag for image", + artifact: &latest.Artifact{ + ImageName: "notbuildyet", + Sync: map[string]string{ + "*.html": ".", + }, + Workspace: ".", + }, + builds: []build.Artifact{ + { + ImageName: "test", + Tag: "test:123", + }, + }, + evt: watch.Events{ + Added: []string{"index.html"}, + }, + shouldErr: true, + }, { description: "sync all", artifact: &latest.Artifact{ @@ -61,13 +90,19 @@ func TestShouldSync(t *testing.T) { }, Workspace: "node", }, + builds: []build.Artifact{ + { + ImageName: "test", + Tag: "test:123", + }, + }, evt: watch.Events{ Added: []string{filepath.Join("node", "index.html")}, Modified: []string{filepath.Join("node", "server.js")}, Deleted: []string{filepath.Join("node", "package.json")}, }, expected: &Item{ - Image: "test", + Image: "test:123", Copy: map[string]string{ filepath.Join("node", "server.js"): "server.js", filepath.Join("node", "index.html"): "index.html", @@ -121,16 +156,22 @@ func TestShouldSync(t *testing.T) { description: "no change no sync", artifact: &latest.Artifact{ Sync: map[string]string{ - "[*.html": "*", + "*.html": "*", }, Workspace: ".", }, + builds: []build.Artifact{ + { + ImageName: "test", + Tag: "test:123", + }, + }, }, } for _, test := range tests { t.Run(test.description, func(t *testing.T) { - actual, err := NewItem(test.artifact, test.evt) + actual, err := NewItem(test.artifact, test.evt, test.builds) testutil.CheckErrorAndDeepEqual(t, test.shouldErr, err, test.expected, actual) }) } From c7b0fd5f2c362cdb3ba1d9554b4e2154866be874 Mon Sep 17 00:00:00 2001 From: Matt Rickard Date: Tue, 2 Oct 2018 12:51:36 -0500 Subject: [PATCH 19/24] rebase on v1alpha4 config --- cmd/skaffold/app/cmd/fix.go | 3 ++- integration/examples/hot-reload/skaffold.yaml | 8 ++++---- pkg/skaffold/schema/latest/config.go | 2 +- pkg/skaffold/sync/sync.go | 2 +- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/cmd/skaffold/app/cmd/fix.go b/cmd/skaffold/app/cmd/fix.go index 1a6d7f1335c..9831f3c3574 100644 --- a/cmd/skaffold/app/cmd/fix.go +++ b/cmd/skaffold/app/cmd/fix.go @@ -22,12 +22,13 @@ import ( "github.com/GoogleContainerTools/skaffold/pkg/skaffold/color" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" schemautil "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/util" + "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" yaml "gopkg.in/yaml.v2" - "k8s.io/client-go/tools/clientcmd/api/latest" ) func NewCmdFix(out io.Writer) *cobra.Command { diff --git a/integration/examples/hot-reload/skaffold.yaml b/integration/examples/hot-reload/skaffold.yaml index d888518b4e0..4daf1c6fba1 100644 --- a/integration/examples/hot-reload/skaffold.yaml +++ b/integration/examples/hot-reload/skaffold.yaml @@ -2,12 +2,12 @@ apiVersion: skaffold/v1alpha4 kind: Config build: artifacts: - - imageName: gcr.io/k8s-skaffold/node-example - workspace: node + - image: gcr.io/k8s-skaffold/node-example + context: node sync: '*.js': . - - imageName: gcr.io/k8s-skaffold/python-reload - workspace: python + - image: gcr.io/k8s-skaffold/python-reload + context: python sync: '*.py': . deploy: diff --git a/pkg/skaffold/schema/latest/config.go b/pkg/skaffold/schema/latest/config.go index 02aa6c3faf4..6fbdc34e296 100644 --- a/pkg/skaffold/schema/latest/config.go +++ b/pkg/skaffold/schema/latest/config.go @@ -210,7 +210,7 @@ type HelmConventionConfig struct { // Artifact represents items that need to be built, along with the context in which // they should be built. type Artifact struct { - ImageName string `yaml:"image"` + Image string `yaml:"image"` Workspace string `yaml:"context,omitempty"` Sync map[string]string `yaml:"sync,omitempty"` ArtifactType `yaml:",inline"` diff --git a/pkg/skaffold/sync/sync.go b/pkg/skaffold/sync/sync.go index 472a581b40b..d2130721ada 100644 --- a/pkg/skaffold/sync/sync.go +++ b/pkg/skaffold/sync/sync.go @@ -21,8 +21,8 @@ import ( "path" "path/filepath" - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/watch" "github.com/pkg/errors" From 10d5d31a390d5239f8acbdd006c9d8eb9296fad6 Mon Sep 17 00:00:00 2001 From: r2d4 Date: Wed, 3 Oct 2018 09:37:51 -0500 Subject: [PATCH 20/24] delete out of date readme --- integration/examples/hot-reload/README.adoc | 77 --------------------- pkg/skaffold/schema/latest/config.go | 2 +- 2 files changed, 1 insertion(+), 78 deletions(-) delete mode 100644 integration/examples/hot-reload/README.adoc diff --git a/integration/examples/hot-reload/README.adoc b/integration/examples/hot-reload/README.adoc deleted file mode 100644 index b45e41779de..00000000000 --- a/integration/examples/hot-reload/README.adoc +++ /dev/null @@ -1,77 +0,0 @@ -=== Example: File Sync with Skaffold -:icons: font - -ifndef::env-github[] -link:{github-repo-tree}/examples/webserver[see on Github icon:github[]] -endif::[] - -In this example: - -* Deploy a basic golang webserver that serves static files -* In development, sync static HTML files without triggering a rebuild or redeploy of the artifacts - -Not all file changes should require a complete rebuild and redeploy of the skaffold artifacts. -This tutorial shows how you can use the file sync feature of skaffold for very quick iterative development. - -==== Running the example on minikube - -From this directory, run - -```bash -skaffold dev -``` - -Now in a different terminal, hit the webserver's endpoint to see index.html - -```bash -$ curl localhost:8080 -Hello World!! -``` - -Now, edit the index.html file to contain something else. You'll see that skaffold syncs the file to the already running container, without rebuilding or redeploying. -```bash -echo "Hello skaffold" > index.html -``` - -You should see that skaffold has synced those changes in the other terminal -```bash -... -Synced: copied [index.html] deleted [] -Watching for changes... -``` - -Now, lets add a new file that matches the glob pattern but wasn't in the original container. -```bash -echo "Dogs are great" > cats.html -``` - -Now you can see that change immediately. -```bash -$ curl localhost:8080/cats.html -"Dogs are great" -``` - -Delete the file -```bash -rm cats.html -``` - -Now you can see that change immediately -```bash -$ curl localhost:8080/cats.html -404 page not found -``` - -==== Configuration walkthrough - -Let's walk through the first part of the skaffold.yaml - -```yaml - artifacts: - - imageName: gcr.io/k8s-skaffold/webserver-example - sync: - '*.html': '/static' -``` - -This will sync all files that match the *.html pattern to the /static/ folder in the container. -Sync can be multiple keys and the only restriction is that the destination must be a folder if the source is a pattern. \ No newline at end of file diff --git a/pkg/skaffold/schema/latest/config.go b/pkg/skaffold/schema/latest/config.go index 6fbdc34e296..02aa6c3faf4 100644 --- a/pkg/skaffold/schema/latest/config.go +++ b/pkg/skaffold/schema/latest/config.go @@ -210,7 +210,7 @@ type HelmConventionConfig struct { // Artifact represents items that need to be built, along with the context in which // they should be built. type Artifact struct { - Image string `yaml:"image"` + ImageName string `yaml:"image"` Workspace string `yaml:"context,omitempty"` Sync map[string]string `yaml:"sync,omitempty"` ArtifactType `yaml:",inline"` From 8f9ac9a35f4b8da8b7c4fa8a5416594e7ed7b12e Mon Sep 17 00:00:00 2001 From: Matt Rickard Date: Wed, 3 Oct 2018 12:02:08 -0500 Subject: [PATCH 21/24] add legacy watch for nodemon example --- integration/examples/hot-reload/node/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration/examples/hot-reload/node/Dockerfile b/integration/examples/hot-reload/node/Dockerfile index 64839925af7..c248d57ffe2 100644 --- a/integration/examples/hot-reload/node/Dockerfile +++ b/integration/examples/hot-reload/node/Dockerfile @@ -1,3 +1,3 @@ FROM gcr.io/k8s-skaffold/nodemon -CMD ["nodemon", "server.js"] +CMD ["nodemon","--legacy-watch", "server.js"] COPY *.js . From 8a22fd8f81df07e90e7eaa1db02590387f41170f Mon Sep 17 00:00:00 2001 From: Matt Rickard Date: Thu, 4 Oct 2018 09:45:48 -0500 Subject: [PATCH 22/24] review feedback --- pkg/skaffold/sync/sync.go | 4 ++-- pkg/skaffold/util/util.go | 7 ++++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/pkg/skaffold/sync/sync.go b/pkg/skaffold/sync/sync.go index d2130721ada..d53ab842b0b 100644 --- a/pkg/skaffold/sync/sync.go +++ b/pkg/skaffold/sync/sync.go @@ -84,12 +84,12 @@ func intersect(context string, syncMap map[string]string, files []string) (map[s for _, f := range files { relPath, err := filepath.Rel(context, f) if err != nil { - return nil, errors.Wrapf(err, "changed file %s is not relative to context %s", f, context) + return nil, errors.Wrapf(err, "changed file %s can't be found relative to context %s", f, context) } for p, dst := range syncMap { match, err := filepath.Match(p, relPath) if err != nil { - return nil, errors.Wrap(err, "pattern error") + return nil, errors.Wrapf(err, "pattern error for %s", relPath) } if !match { return nil, nil diff --git a/pkg/skaffold/util/util.go b/pkg/skaffold/util/util.go index a8bcd5a0b2c..28f9fde99bc 100644 --- a/pkg/skaffold/util/util.go +++ b/pkg/skaffold/util/util.go @@ -24,6 +24,7 @@ import ( "os" "path/filepath" "regexp" + "runtime" "sort" "strings" @@ -111,7 +112,11 @@ func ExpandPathsGlob(workingDir string, paths []string) ([]string, error) { // recognized by filepath.Match. // This is a copy of filepath/match.go's hasMeta func HasMeta(path string) bool { - return strings.ContainsAny(path, "*?[") + magicChars := `*?[` + if runtime.GOOS != "windows" { + magicChars = `*?[\` + } + return strings.ContainsAny(path, magicChars) } // BoolPtr returns a pointer to a bool From be4cc6c7a6fd0e00e34d1e50df0e53d3357086fb Mon Sep 17 00:00:00 2001 From: Matt Rickard Date: Thu, 4 Oct 2018 09:58:18 -0500 Subject: [PATCH 23/24] rebase changes --- Gopkg.lock | 1 + pkg/skaffold/runner/runner.go | 1 + pkg/skaffold/runner/runner_test.go | 5 +---- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index 33e9738b525..4e4bfd9a869 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -1203,6 +1203,7 @@ "k8s.io/apimachinery/pkg/api/meta", "k8s.io/apimachinery/pkg/apis/meta/v1", "k8s.io/apimachinery/pkg/fields", + "k8s.io/apimachinery/pkg/labels", "k8s.io/apimachinery/pkg/runtime", "k8s.io/apimachinery/pkg/runtime/schema", "k8s.io/apimachinery/pkg/types", diff --git a/pkg/skaffold/runner/runner.go b/pkg/skaffold/runner/runner.go index d515cd2a6b8..97c88467d34 100644 --- a/pkg/skaffold/runner/runner.go +++ b/pkg/skaffold/runner/runner.go @@ -30,6 +30,7 @@ import ( "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/kaniko" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/local" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/tag" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/color" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/config" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/deploy" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/docker" diff --git a/pkg/skaffold/runner/runner_test.go b/pkg/skaffold/runner/runner_test.go index 39784f780ba..8ab01d131a1 100644 --- a/pkg/skaffold/runner/runner_test.go +++ b/pkg/skaffold/runner/runner_test.go @@ -446,10 +446,7 @@ func TestDev(t *testing.T) { Trigger: trigger, watchFactory: test.watcherFactory, opts: opts, - opts: &config.SkaffoldOptions{ - WatchPollInterval: 100, - }, - Syncer: NewTestSyncer(), + Syncer: NewTestSyncer(), } _, err := runner.Dev(context.Background(), ioutil.Discard, nil) From 9b11bc4cbfae3b652b223b6dde4e88ffab3019d2 Mon Sep 17 00:00:00 2001 From: Matt Rickard Date: Thu, 4 Oct 2018 12:38:22 -0500 Subject: [PATCH 24/24] debug and info messages only for sync --- pkg/skaffold/runner/runner.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/skaffold/runner/runner.go b/pkg/skaffold/runner/runner.go index 97c88467d34..e9aac715715 100644 --- a/pkg/skaffold/runner/runner.go +++ b/pkg/skaffold/runner/runner.go @@ -30,7 +30,6 @@ import ( "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/kaniko" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/local" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/tag" - "github.com/GoogleContainerTools/skaffold/pkg/skaffold/color" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/config" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/deploy" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/docker" @@ -276,7 +275,8 @@ func (r *SkaffoldRunner) Dev(ctx context.Context, out io.Writer, artifacts []*la logrus.Warnln("Skipping build and deploy due to sync error:", err) return nil } - color.Default.Fprintf(out, "Synced files for %s...\nCopied: %s\nDeleted: %s\n", s.Image, s.Copy, s.Delete) + logrus.Infof("Synced %d files for %s", len(s.Copy)+len(s.Delete), s.Image) + logrus.Debugf("Synced files for %s...\nCopied: %s\nDeleted: %s\n", s.Image, s.Copy, s.Delete) } case len(changed.needsRebuild) > 0: bRes, err := r.Build(ctx, out, r.Tagger, changed.needsRebuild)