Skip to content

Commit c2b06e5

Browse files
committed
Add a skaffold diagnose command
Fixes GoogleContainerTools#1094 Signed-off-by: David Gageot <[email protected]>
1 parent eab71cc commit c2b06e5

File tree

6 files changed

+149
-16
lines changed

6 files changed

+149
-16
lines changed

cmd/skaffold/app/cmd/cmd.go

+1
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ func NewSkaffoldCommand(out, err io.Writer) *cobra.Command {
8080
rootCmd.AddCommand(NewCmdFix(out))
8181
rootCmd.AddCommand(NewCmdConfig(out))
8282
rootCmd.AddCommand(NewCmdInit(out))
83+
rootCmd.AddCommand(NewCmdDiagnose(out))
8384

8485
rootCmd.PersistentFlags().StringVarP(&v, "verbosity", "v", constants.DefaultLogLevel.String(), "Log level (debug, info, warn, error, fatal, panic)")
8586

cmd/skaffold/app/cmd/diagnose.go

+130
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
/*
2+
Copyright 2018 The Skaffold Authors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package cmd
18+
19+
import (
20+
"fmt"
21+
"io"
22+
"io/ioutil"
23+
"time"
24+
25+
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/docker"
26+
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/runner"
27+
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest"
28+
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/version"
29+
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/watch"
30+
31+
"github.com/pkg/errors"
32+
"github.com/spf13/cobra"
33+
)
34+
35+
// NewCmdDiagnose describes the CLI command to diagnose skaffold.
36+
func NewCmdDiagnose(out io.Writer) *cobra.Command {
37+
cmd := &cobra.Command{
38+
Use: "diagnose",
39+
Short: "Run a diagnostic on Skaffold",
40+
Args: cobra.NoArgs,
41+
RunE: func(cmd *cobra.Command, args []string) error {
42+
return doDiagnose(out)
43+
},
44+
}
45+
cmd.Flags().StringVarP(&opts.ConfigurationFile, "filename", "f", "skaffold.yaml", "Filename or URL to the pipeline file")
46+
return cmd
47+
}
48+
49+
func doDiagnose(out io.Writer) error {
50+
_, config, err := newRunner(opts)
51+
if err != nil {
52+
return errors.Wrap(err, "creating runner")
53+
}
54+
55+
fmt.Fprintln(out, "Skaffold version:", version.Get().GitCommit)
56+
fmt.Fprintln(out, "Configuration version:", config.APIVersion)
57+
fmt.Fprintln(out, "Number of artifacts:", len(config.Build.Artifacts))
58+
59+
fmt.Fprintln(out, "\nRunning a diagnostic on artifacts and their dependencies:")
60+
61+
if err := diagnoseArtifacts(out, config.Build.Artifacts); err != nil {
62+
return errors.Wrap(err, "running diagnostic on artifacts")
63+
}
64+
65+
fmt.Fprintln(out, "\nSecond run (that benefits from cache):")
66+
67+
if err := diagnoseArtifacts(out, config.Build.Artifacts); err != nil {
68+
return errors.Wrap(err, "running second diagnostic on artifacts")
69+
}
70+
71+
return nil
72+
}
73+
74+
func diagnoseArtifacts(out io.Writer, artifacts []*latest.Artifact) error {
75+
for _, artifact := range artifacts {
76+
start := time.Now()
77+
deps, err := runner.DependenciesForArtifact(artifact)
78+
if err != nil {
79+
return errors.Wrap(err, "listing artifact dependencies")
80+
}
81+
82+
fmt.Fprintln(out, "Type:", typeOfArtifact(artifact))
83+
fmt.Fprintln(out, " - Dependencies to watch:", len(deps))
84+
fmt.Fprintln(out, " - Time to list dependencies:", time.Since(start))
85+
86+
if artifact.DockerArtifact != nil {
87+
size, err := sizeOfTheDockerContext(artifact)
88+
if err != nil {
89+
return errors.Wrap(err, "computing the size of the Docker context")
90+
}
91+
92+
fmt.Fprintln(out, " - Size of the Docker context:", size)
93+
}
94+
95+
start = time.Now()
96+
_, err = watch.Stat(func() ([]string, error) { return deps, nil })
97+
if err != nil {
98+
return errors.Wrap(err, "computing modTimes")
99+
}
100+
101+
fmt.Fprintln(out, " - Time to compute mTimes for all dependencies:", time.Since(start))
102+
}
103+
104+
return nil
105+
}
106+
107+
func sizeOfTheDockerContext(a *latest.Artifact) (int64, error) {
108+
buildCtx, buildCtxWriter := io.Pipe()
109+
go func() {
110+
err := docker.CreateDockerTarContext(buildCtxWriter, a.Workspace, a.DockerArtifact)
111+
if err != nil {
112+
buildCtxWriter.CloseWithError(errors.Wrap(err, "creating docker context"))
113+
return
114+
}
115+
buildCtxWriter.Close()
116+
}()
117+
118+
return io.Copy(ioutil.Discard, buildCtx)
119+
}
120+
121+
func typeOfArtifact(a *latest.Artifact) string {
122+
switch {
123+
case a.DockerArtifact != nil:
124+
return "Docker"
125+
case a.BazelArtifact != nil:
126+
return "Bazel"
127+
default:
128+
return "Unknown"
129+
}
130+
}

pkg/skaffold/runner/runner.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,7 @@ func (r *SkaffoldRunner) Dev(ctx context.Context, out io.Writer, artifacts []*la
321321
}
322322

323323
if err := watcher.Register(
324-
func() ([]string, error) { return dependenciesForArtifact(artifact) },
324+
func() ([]string, error) { return DependenciesForArtifact(artifact) },
325325
func(e watch.Events) { changed.AddDirtyArtifact(artifact, e) },
326326
); err != nil {
327327
return nil, errors.Wrapf(err, "watching files for artifact %s", artifact.ImageName)
@@ -427,7 +427,8 @@ func mergeWithPreviousBuilds(builds, previous []build.Artifact) []build.Artifact
427427
return merged
428428
}
429429

430-
func dependenciesForArtifact(a *latest.Artifact) ([]string, error) {
430+
// DependenciesForArtifact lists the dependencies for a given artifact.
431+
func DependenciesForArtifact(a *latest.Artifact) ([]string, error) {
431432
var (
432433
paths []string
433434
err error

pkg/skaffold/watch/changes.go

+6-5
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,12 @@ import (
2727
"github.com/sirupsen/logrus"
2828
)
2929

30-
type fileMap map[string]time.Time
30+
// FileMap is a map of filename to modification times.
31+
type FileMap map[string]time.Time
3132

32-
// TODO(mrick): cached tree extension ala git
33-
func stat(deps func() ([]string, error)) (fileMap, error) {
34-
state := fileMap{}
33+
// Stat returns the modification times for a list of files.
34+
func Stat(deps func() ([]string, error)) (FileMap, error) {
35+
state := FileMap{}
3536
paths, err := deps()
3637
if err != nil {
3738
return state, errors.Wrap(err, "listing files")
@@ -77,7 +78,7 @@ func (e *Events) String() string {
7778
return sb.String()
7879
}
7980

80-
func events(prev, curr fileMap) Events {
81+
func events(prev, curr FileMap) Events {
8182
e := Events{}
8283
for f, t := range prev {
8384
modtime, ok := curr[f]

pkg/skaffold/watch/changes_test.go

+6-6
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ var (
3535
func TestEvents(t *testing.T) {
3636
var tests = []struct {
3737
description string
38-
prev, current fileMap
38+
prev, current FileMap
3939
expected Events
4040
}{
4141
{
@@ -99,7 +99,7 @@ func TestStat(t *testing.T) {
9999
var tests = []struct {
100100
description string
101101
setup func(folder *testutil.TempDir)
102-
expected fileMap
102+
expected FileMap
103103
shouldErr bool
104104
}{
105105
{
@@ -118,7 +118,7 @@ func TestStat(t *testing.T) {
118118
test.setup(folder)
119119
list, _ := folder.List()
120120

121-
actual, err := stat(folder.List)
121+
actual, err := Stat(folder.List)
122122
testutil.CheckError(t, test.shouldErr, err)
123123
checkListInMap(t, list, actual)
124124
})
@@ -131,7 +131,7 @@ func TestStatNotExist(t *testing.T) {
131131
setup func(folder *testutil.TempDir)
132132
deps []string
133133
depsErr error
134-
expected fileMap
134+
expected FileMap
135135
shouldErr bool
136136
}{
137137
{
@@ -159,13 +159,13 @@ func TestStatNotExist(t *testing.T) {
159159

160160
test.setup(folder)
161161

162-
_, err := stat(func() ([]string, error) { return test.deps, test.depsErr })
162+
_, err := Stat(func() ([]string, error) { return test.deps, test.depsErr })
163163
testutil.CheckError(t, test.shouldErr, err)
164164
})
165165
}
166166
}
167167

168-
func checkListInMap(t *testing.T, list []string, m fileMap) {
168+
func checkListInMap(t *testing.T, list []string, m FileMap) {
169169
for _, f := range list {
170170
if _, ok := m[f]; !ok {
171171
t.Errorf("File %s not in map", f)

pkg/skaffold/watch/watch.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,13 @@ func NewWatcher() Watcher {
4141
type component struct {
4242
deps func() ([]string, error)
4343
onChange func(Events)
44-
state fileMap
44+
state FileMap
4545
events Events
4646
}
4747

4848
// Register adds a new component to the watch list.
4949
func (w *watchList) Register(deps func() ([]string, error), onChange func(Events)) error {
50-
state, err := stat(deps)
50+
state, err := Stat(deps)
5151
if err != nil {
5252
return errors.Wrap(err, "listing files")
5353
}
@@ -74,7 +74,7 @@ func (w *watchList) Run(ctx context.Context, trigger Trigger, onChange func() er
7474
case <-t:
7575
changed := 0
7676
for i, component := range *w {
77-
state, err := stat(component.deps)
77+
state, err := Stat(component.deps)
7878
if err != nil {
7979
return errors.Wrap(err, "listing files")
8080
}

0 commit comments

Comments
 (0)