-
Notifications
You must be signed in to change notification settings - Fork 288
/
fake_client.go
262 lines (212 loc) · 8.41 KB
/
fake_client.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
package docker
import (
"bytes"
"context"
"fmt"
"io"
"sort"
"time"
"github.com/docker/docker/api/types"
"github.com/windmilleng/tilt/internal/container"
"github.com/windmilleng/tilt/pkg/model"
)
const ExampleBuildSHA1 = "sha256:11cd0b38bc3ceb958ffb2f9bd70be3fb317ce7d255c8a4c3f4af30e298aa1aab"
const ExampleBuildOutput1 = `{"stream":"Run 1/1 : FROM alpine"}
{"stream":"\n"}
{"stream":" ---\u003e 11cd0b38bc3c\n"}
{"aux":{"ID":"sha256:11cd0b38bc3ceb958ffb2f9bd70be3fb317ce7d255c8a4c3f4af30e298aa1aab"}}
{"stream":"Successfully built 11cd0b38bc3c\n"}
{"stream":"Successfully tagged hi:latest\n"}
`
const ExampleBuildOutputV1_23 = `{"stream":"Run 1/1 : FROM alpine"}
{"stream":"\n"}
{"stream":" ---\u003e 11cd0b38bc3c\n"}
{"stream":"Successfully built 11cd0b38bc3c\n"}
{"stream":"Successfully tagged hi:latest\n"}
`
const ExamplePushSHA1 = "sha256:cc5f4c463f81c55183d8d737ba2f0d30b3e6f3670dbe2da68f0aac168e93fbb1"
var ExamplePushOutput1 = `{"status":"The push refers to repository [localhost:5005/myimage]"}
{"status":"Preparing","progressDetail":{},"id":"2a88b569da78"}
{"status":"Preparing","progressDetail":{},"id":"73046094a9b8"}
{"status":"Pushing","progressDetail":{"current":512,"total":41},"progress":"[==================================================\u003e] 512B","id":"2a88b569da78"}
{"status":"Pushing","progressDetail":{"current":68608,"total":4413370},"progress":"[\u003e ] 68.61kB/4.413MB","id":"73046094a9b8"}
{"status":"Pushing","progressDetail":{"current":5120,"total":41},"progress":"[==================================================\u003e] 5.12kB","id":"2a88b569da78"}
{"status":"Pushed","progressDetail":{},"id":"2a88b569da78"}
{"status":"Pushing","progressDetail":{"current":1547776,"total":4413370},"progress":"[=================\u003e ] 1.548MB/4.413MB","id":"73046094a9b8"}
{"status":"Pushing","progressDetail":{"current":3247616,"total":4413370},"progress":"[====================================\u003e ] 3.248MB/4.413MB","id":"73046094a9b8"}
{"status":"Pushing","progressDetail":{"current":4672000,"total":4413370},"progress":"[==================================================\u003e] 4.672MB","id":"73046094a9b8"}
{"status":"Pushed","progressDetail":{},"id":"73046094a9b8"}
{"status":"tilt-11cd0b38bc3ceb95: digest: sha256:cc5f4c463f81c55183d8d737ba2f0d30b3e6f3670dbe2da68f0aac168e93fbb1 size: 735"}
{"progressDetail":{},"aux":{"Tag":"tilt-11cd0b38bc3ceb95","Digest":"sha256:cc5f4c463f81c55183d8d737ba2f0d30b3e6f3670dbe2da68f0aac168e93fbb1","Size":735}}`
const (
TestPod = "test_pod"
TestContainer = "test_container"
)
var DefaultContainerListOutput = map[string][]types.Container{
TestPod: []types.Container{
types.Container{ID: TestContainer, ImageID: ExampleBuildSHA1, Command: "./stuff"},
},
"two-containers": []types.Container{
types.Container{ID: "not a match", ImageID: ExamplePushSHA1, Command: "/pause"},
types.Container{ID: "the right container", ImageID: ExampleBuildSHA1, Command: "./stuff"},
},
}
type ExecCall struct {
Container string
Cmd model.Cmd
}
type FakeClient struct {
PushCount int
PushImage string
PushOptions types.ImagePushOptions
PushOutput string
BuildCount int
BuildOptions BuildOptions
BuildOutput string
BuildErrorToThrow error // next call to Build will throw this err (after which we clear the error)
ImageListCount int
TagCount int
TagSource string
TagTarget string
ContainerListOutput map[string][]types.Container
CopyCount int
CopyContainer string
CopyContent io.Reader
ExecCalls []ExecCall
ExecErrorsToThrow []error // next call to exec will throw ExecError[0] (which we then pop)
RestartsByContainer map[string]int
RemovedImageIDs []string
Images map[string]types.ImageInspect
}
func NewFakeClient() *FakeClient {
return &FakeClient{
PushOutput: ExamplePushOutput1,
BuildOutput: ExampleBuildOutput1,
ContainerListOutput: make(map[string][]types.Container),
RestartsByContainer: make(map[string]int),
Images: make(map[string]types.ImageInspect),
}
}
func (c *FakeClient) SetOrchestrator(orc model.Orchestrator) {}
func (c *FakeClient) Env() Env {
return Env{}
}
func (c *FakeClient) BuilderVersion() types.BuilderVersion {
return types.BuilderV1
}
func (c *FakeClient) ServerVersion() types.Version {
return types.Version{}
}
func (c *FakeClient) SetExecError(err error) {
c.ExecErrorsToThrow = []error{err}
}
func (c *FakeClient) SetContainerListOutput(output map[string][]types.Container) {
c.ContainerListOutput = output
}
func (c *FakeClient) SetDefaultContainerListOutput() {
c.SetContainerListOutput(DefaultContainerListOutput)
}
func (c *FakeClient) ContainerList(ctx context.Context, options types.ContainerListOptions) ([]types.Container, error) {
nameFilter := options.Filters.Get("name")
if len(nameFilter) != 1 {
return nil, fmt.Errorf("expected one filter for 'name', got: %v", nameFilter)
}
if len(c.ContainerListOutput) == 0 {
return nil, fmt.Errorf("FakeClient ContainerListOutput not set (use `SetContainerListOutput`)")
}
res := c.ContainerListOutput[nameFilter[0]]
// unset containerListOutput
c.ContainerListOutput = nil
return res, nil
}
func (c *FakeClient) ContainerRestartNoWait(ctx context.Context, containerID string) error {
c.RestartsByContainer[containerID]++
return nil
}
func (c *FakeClient) ExecInContainer(ctx context.Context, cID container.ID, cmd model.Cmd, out io.Writer) error {
execCall := ExecCall{
Container: cID.String(),
Cmd: cmd,
}
c.ExecCalls = append(c.ExecCalls, execCall)
// If we're supposed to throw an error on this call, throw it (and pop from
// the list of ErrorsToThrow)
var err error
if len(c.ExecErrorsToThrow) > 0 {
err = c.ExecErrorsToThrow[0]
c.ExecErrorsToThrow = append([]error{}, c.ExecErrorsToThrow[1:]...)
}
return err
}
func (c *FakeClient) CopyToContainerRoot(ctx context.Context, container string, content io.Reader) error {
c.CopyCount++
c.CopyContainer = container
c.CopyContent = content
return nil
}
func (c *FakeClient) ImagePush(ctx context.Context, image string, options types.ImagePushOptions) (io.ReadCloser, error) {
c.PushCount++
c.PushImage = image
c.PushOptions = options
return NewFakeDockerResponse(c.PushOutput), nil
}
func (c *FakeClient) ImageBuild(ctx context.Context, buildContext io.Reader, options BuildOptions) (types.ImageBuildResponse, error) {
c.BuildCount++
c.BuildOptions = options
// If we're supposed to throw an error on this call, throw it (and reset ErrorToThrow)
err := c.BuildErrorToThrow
if err != nil {
c.BuildErrorToThrow = nil
return types.ImageBuildResponse{}, err
}
return types.ImageBuildResponse{Body: NewFakeDockerResponse(c.BuildOutput)}, nil
}
func (c *FakeClient) ImageTag(ctx context.Context, source, target string) error {
c.TagCount++
c.TagSource = source
c.TagTarget = target
return nil
}
func (c *FakeClient) ImageInspectWithRaw(ctx context.Context, imageID string) (types.ImageInspect, []byte, error) {
result, ok := c.Images[imageID]
if ok {
return result, nil, nil
}
return types.ImageInspect{}, nil, newNotFoundErrorf("fakeClient.Images key: %s", imageID)
}
func (c *FakeClient) ImageList(ctx context.Context, options types.ImageListOptions) ([]types.ImageSummary, error) {
summaries := make([]types.ImageSummary, c.ImageListCount)
for i := range summaries {
summaries[i] = types.ImageSummary{
ID: fmt.Sprintf("build-id-%d", i),
Created: time.Now().Add(-time.Second).Unix(),
}
}
return summaries, nil
}
func (c *FakeClient) ImageRemove(ctx context.Context, imageID string, options types.ImageRemoveOptions) ([]types.ImageDeleteResponseItem, error) {
c.RemovedImageIDs = append(c.RemovedImageIDs, imageID)
sort.Strings(c.RemovedImageIDs)
return nil, nil
}
var _ Client = &FakeClient{}
type fakeDockerResponse struct {
*bytes.Buffer
}
func NewFakeDockerResponse(contents string) fakeDockerResponse {
return fakeDockerResponse{Buffer: bytes.NewBufferString(contents)}
}
func (r fakeDockerResponse) Close() error { return nil }
var _ io.ReadCloser = fakeDockerResponse{}
type notFoundError struct {
details string
}
func newNotFoundErrorf(s string, a ...interface{}) notFoundError {
return notFoundError{details: fmt.Sprintf(s, a...)}
}
func (e notFoundError) NotFound() bool {
return true
}
func (e notFoundError) Error() string {
return fmt.Sprintf("fake docker client error: object not found (%s)", e.details)
}