Skip to content

Commit a72da4e

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

File tree

6 files changed

+173
-16
lines changed

6 files changed

+173
-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

+154
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
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+
"context"
21+
"fmt"
22+
"io"
23+
"io/ioutil"
24+
"time"
25+
26+
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/color"
27+
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/docker"
28+
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/runner"
29+
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest"
30+
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/version"
31+
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/watch"
32+
33+
"github.com/pkg/errors"
34+
"github.com/spf13/cobra"
35+
)
36+
37+
// NewCmdDiagnose describes the CLI command to diagnose skaffold.
38+
func NewCmdDiagnose(out io.Writer) *cobra.Command {
39+
cmd := &cobra.Command{
40+
Use: "diagnose",
41+
Short: "Run a diagnostic on Skaffold",
42+
Args: cobra.NoArgs,
43+
RunE: func(cmd *cobra.Command, args []string) error {
44+
return doDiagnose(out)
45+
},
46+
}
47+
cmd.Flags().StringVarP(&opts.ConfigurationFile, "filename", "f", "skaffold.yaml", "Filename or URL to the pipeline file")
48+
return cmd
49+
}
50+
51+
func doDiagnose(out io.Writer) error {
52+
_, config, err := newRunner(opts)
53+
if err != nil {
54+
return errors.Wrap(err, "creating runner")
55+
}
56+
57+
fmt.Fprintln(out, "Skaffold version:", version.Get().GitCommit)
58+
fmt.Fprintln(out, "Configuration version:", config.APIVersion)
59+
fmt.Fprintln(out, "Number of artifacts:", len(config.Build.Artifacts))
60+
61+
if err := diagnoseArtifacts(out, config.Build.Artifacts); err != nil {
62+
return errors.Wrap(err, "running diagnostic on artifacts")
63+
}
64+
65+
return nil
66+
}
67+
68+
func diagnoseArtifacts(out io.Writer, artifacts []*latest.Artifact) error {
69+
ctx := context.Background()
70+
71+
for _, artifact := range artifacts {
72+
color.Default.Fprintf(out, "\n%s: %s\n", typeOfArtifact(artifact), artifact.ImageName)
73+
74+
if artifact.DockerArtifact != nil {
75+
size, err := sizeOfDockerContext(ctx, artifact)
76+
if err != nil {
77+
return errors.Wrap(err, "computing the size of the Docker context")
78+
}
79+
80+
fmt.Fprintf(out, " - Size of the context: %vbytes\n", size)
81+
}
82+
83+
timeDeps1, deps, err := timeToListDependencies(ctx, artifact)
84+
if err != nil {
85+
return errors.Wrap(err, "listing artifact dependencies")
86+
}
87+
timeDeps2, _, err := timeToListDependencies(ctx, artifact)
88+
if err != nil {
89+
return errors.Wrap(err, "listing artifact dependencies")
90+
}
91+
92+
fmt.Fprintln(out, " - Dependencies:", len(deps), "files")
93+
fmt.Fprintf(out, " - Time to list dependencies: %v (2nd time: %v)\n", timeDeps1, timeDeps2)
94+
95+
timeMTimes1, err := timeToComputeMTimes(deps)
96+
if err != nil {
97+
return errors.Wrap(err, "computing modTimes")
98+
}
99+
timeMTimes2, err := timeToComputeMTimes(deps)
100+
if err != nil {
101+
return errors.Wrap(err, "computing modTimes")
102+
}
103+
104+
fmt.Fprintf(out, " - Time to compute mTimes on dependencies: %v (2nd time: %v)\n", timeMTimes1, timeMTimes2)
105+
}
106+
107+
return nil
108+
}
109+
110+
func timeToListDependencies(ctx context.Context, a *latest.Artifact) (time.Duration, []string, error) {
111+
start := time.Now()
112+
113+
deps, err := runner.DependenciesForArtifact(ctx, a)
114+
if err != nil {
115+
return 0, nil, errors.Wrap(err, "listing artifact dependencies")
116+
}
117+
118+
return time.Since(start), deps, nil
119+
}
120+
121+
func timeToComputeMTimes(deps []string) (time.Duration, error) {
122+
start := time.Now()
123+
124+
if _, err := watch.Stat(func() ([]string, error) { return deps, nil }); err != nil {
125+
return 0, errors.Wrap(err, "computing modTimes")
126+
}
127+
128+
return time.Since(start), nil
129+
}
130+
131+
func sizeOfDockerContext(ctx context.Context, a *latest.Artifact) (int64, error) {
132+
buildCtx, buildCtxWriter := io.Pipe()
133+
go func() {
134+
err := docker.CreateDockerTarContext(ctx, buildCtxWriter, a.Workspace, a.DockerArtifact)
135+
if err != nil {
136+
buildCtxWriter.CloseWithError(errors.Wrap(err, "creating docker context"))
137+
return
138+
}
139+
buildCtxWriter.Close()
140+
}()
141+
142+
return io.Copy(ioutil.Discard, buildCtx)
143+
}
144+
145+
func typeOfArtifact(a *latest.Artifact) string {
146+
switch {
147+
case a.DockerArtifact != nil:
148+
return "Docker artifact"
149+
case a.BazelArtifact != nil:
150+
return "Bazel artifact"
151+
default:
152+
return "Unknown artifact"
153+
}
154+
}

pkg/skaffold/runner/runner.go

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

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

431-
func dependenciesForArtifact(ctx context.Context, a *latest.Artifact) ([]string, error) {
431+
// DependenciesForArtifact lists the dependencies for a given artifact.
432+
func DependenciesForArtifact(ctx context.Context, a *latest.Artifact) ([]string, error) {
432433
var (
433434
paths []string
434435
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)