Skip to content

Commit 906ab1e

Browse files
authored
feat: prototype control api devloop intent (#6636)
* feat: prototype control api devloop intent * fix lint * use separate callback * reset devlop intent * add an integration test * fix lint * fix integration test * fix continuously
1 parent 719a890 commit 906ab1e

File tree

17 files changed

+364
-161
lines changed

17 files changed

+364
-161
lines changed

docs/content/en/api/skaffold.swagger.json

+4
Original file line numberDiff line numberDiff line change
@@ -3535,6 +3535,10 @@
35353535
"deploy": {
35363536
"type": "boolean",
35373537
"format": "boolean"
3538+
},
3539+
"devloop": {
3540+
"type": "boolean",
3541+
"format": "boolean"
35383542
}
35393543
},
35403544
"description": "Intent represents user intents for a given phase."

docs/content/en/docs/references/api/grpc.md

+1
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,7 @@ Intent represents user intents for a given phase.
384384
| build | [bool](#bool) | | in case skaffold dev is ran with autoBuild=false, a build intent enables building once |
385385
| sync | [bool](#bool) | | in case skaffold dev is ran with autoSync=false, a sync intent enables file sync once |
386386
| deploy | [bool](#bool) | | in case skaffold dev is ran with autoDeploy=false, a deploy intent enables deploys once |
387+
| devloop | [bool](#bool) | | in case skaffold dev is ran with autoDeploy=false, autoSync=false and autoBuild=false a devloop intent enables the entire dev loop once |
387388

388389

389390

integration/control_api_test.go

+83
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/*
2+
Copyright 2019 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 integration
18+
19+
import (
20+
"bytes"
21+
"context"
22+
"fmt"
23+
"strings"
24+
"testing"
25+
"time"
26+
27+
"k8s.io/apimachinery/pkg/util/wait"
28+
29+
"github.com/GoogleContainerTools/skaffold/integration/skaffold"
30+
"github.com/GoogleContainerTools/skaffold/proto/v1"
31+
)
32+
33+
const (
34+
testDev = "test-dev"
35+
)
36+
37+
func TestControlAPIManualTriggers(t *testing.T) {
38+
MarkIntegrationTest(t, CanRunWithoutGcp)
39+
40+
Run(t, "testdata/dev", "sh", "-c", "echo foo > foo")
41+
defer Run(t, "testdata/dev", "rm", "foo")
42+
43+
ns, client := SetupNamespace(t)
44+
out := bytes.Buffer{}
45+
rpcAddr := randomPort()
46+
skaffold.Dev("--auto-build=false", "--auto-sync=false", "--auto-deploy=false", "--rpc-port", rpcAddr, "--cache-artifacts=false").InDir("testdata/dev").InNs(ns.Name).RunInBackgroundWithOutput(t, &out)
47+
48+
rpcClient, entries := apiEvents(t, rpcAddr)
49+
50+
// throw away first 5 entries of log (from first run of dev loop)
51+
for i := 0; i < 5; i++ {
52+
<-entries
53+
}
54+
55+
dep := client.GetDeployment(testDev)
56+
57+
// Make a change to foo
58+
Run(t, "testdata/dev", "sh", "-c", "echo bar > foo")
59+
60+
// Execute dev loop trigger
61+
rpcClient.Execute(context.Background(), &proto.UserIntentRequest{
62+
Intent: &proto.Intent{
63+
Devloop: true,
64+
},
65+
})
66+
// Ensure we see a build triggered in the event log
67+
err := wait.Poll(time.Millisecond*500, 2*time.Minute, func() (bool, error) {
68+
e := <-entries
69+
return e.GetEvent().GetBuildEvent().GetArtifact() == testDev, nil
70+
})
71+
failNowIfError(t, err)
72+
// verify deployment happened.
73+
verifyDeployment(t, entries, client, dep)
74+
75+
// Make another change to foo and we should not see any event log.
76+
Run(t, "testdata/dev", "sh", "-c", "echo bar > foo")
77+
78+
// Give skaffold some time to register a file change.
79+
time.Sleep(1 * time.Second)
80+
if c := strings.Count(out.String(), "Generating tags"); c != 2 {
81+
failNowIfError(t, fmt.Errorf("expected to see tags generated twice (1st build and 1 trigger), saw %d times", c))
82+
}
83+
}

integration/dev_test.go

+7-7
Original file line numberDiff line numberDiff line change
@@ -68,14 +68,14 @@ func TestDevNotification(t *testing.T) {
6868

6969
skaffold.Dev("--trigger", test.trigger).InDir("testdata/dev").InNs(ns.Name).RunBackground(t)
7070

71-
dep := client.GetDeployment("test-dev")
71+
dep := client.GetDeployment(testDev)
7272

7373
// Make a change to foo so that dev is forced to delete the Deployment and redeploy
7474
Run(t, "testdata/dev", "sh", "-c", "echo bar > foo")
7575

7676
// Make sure the old Deployment and the new Deployment are different
7777
err := wait.PollImmediate(time.Millisecond*500, 1*time.Minute, func() (bool, error) {
78-
newDep := client.GetDeployment("test-dev")
78+
newDep := client.GetDeployment(testDev)
7979
logrus.Infof("old gen: %d, new gen: %d", dep.GetGeneration(), newDep.GetGeneration())
8080
return dep.GetGeneration() != newDep.GetGeneration(), nil
8181
})
@@ -157,7 +157,7 @@ func TestDevAPITriggers(t *testing.T) {
157157
<-entries
158158
}
159159

160-
dep := client.GetDeployment("test-dev")
160+
dep := client.GetDeployment(testDev)
161161

162162
// Make a change to foo
163163
Run(t, "testdata/dev", "sh", "-c", "echo bar > foo")
@@ -172,7 +172,7 @@ func TestDevAPITriggers(t *testing.T) {
172172
// Ensure we see a build triggered in the event log
173173
err := wait.PollImmediate(time.Millisecond*500, 2*time.Minute, func() (bool, error) {
174174
e := <-entries
175-
return e.GetEvent().GetBuildEvent().GetArtifact() == "test-dev", nil
175+
return e.GetEvent().GetBuildEvent().GetArtifact() == testDev, nil
176176
})
177177
failNowIfError(t, err)
178178

@@ -207,7 +207,7 @@ func TestDevAPIAutoTriggers(t *testing.T) {
207207
<-entries
208208
}
209209

210-
dep := client.GetDeployment("test-dev")
210+
dep := client.GetDeployment(testDev)
211211

212212
// Make a change to foo
213213
Run(t, "testdata/dev", "sh", "-c", "echo bar > foo")
@@ -223,7 +223,7 @@ func TestDevAPIAutoTriggers(t *testing.T) {
223223
// Ensure we see a build triggered in the event log
224224
err := wait.Poll(time.Millisecond*500, 2*time.Minute, func() (bool, error) {
225225
e := <-entries
226-
return e.GetEvent().GetBuildEvent().GetArtifact() == "test-dev", nil
226+
return e.GetEvent().GetBuildEvent().GetArtifact() == testDev, nil
227227
})
228228
failNowIfError(t, err)
229229

@@ -247,7 +247,7 @@ func verifyDeployment(t *testing.T, entries chan *proto.LogEntry, client *NSKube
247247

248248
// Make sure the old Deployment and the new Deployment are different
249249
err = wait.Poll(time.Millisecond*500, 1*time.Minute, func() (bool, error) {
250-
newDep := client.GetDeployment("test-dev")
250+
newDep := client.GetDeployment(testDev)
251251
logrus.Infof("old gen: %d, new gen: %d", dep.GetGeneration(), newDep.GetGeneration())
252252
return dep.GetGeneration() != newDep.GetGeneration(), nil
253253
})

integration/skaffold/helper.go

+14-1
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,11 @@ func GeneratePipeline(args ...string) *RunBuilder {
137137
}
138138

139139
func withDefaults(command string, args []string) *RunBuilder {
140-
return &RunBuilder{command: command, args: args, repo: "gcr.io/k8s-skaffold"}
140+
repo := os.Getenv("DEFAULT_REPO")
141+
if repo == "" {
142+
repo = "gcr.io/k8s-skaffold"
143+
}
144+
return &RunBuilder{command: command, args: args, repo: repo}
141145
}
142146

143147
// InDir sets the directory in which skaffold is running.
@@ -197,6 +201,9 @@ func (b *RunBuilder) RunBackground(t *testing.T) {
197201
}
198202

199203
// RunLive runs the skaffold command in the background with live output.
204+
// !!Warning!! RunLive blocks the skaffold command until the caller reads from
205+
// the returned `PipeReader`. Please use `WaitForLogs` or similar to read
206+
// continuously from the returned `PipeReader`.
200207
func (b *RunBuilder) RunLive(t *testing.T) io.ReadCloser {
201208
t.Helper()
202209
pr, pw := io.Pipe()
@@ -207,6 +214,12 @@ func (b *RunBuilder) RunLive(t *testing.T) io.ReadCloser {
207214
return pr
208215
}
209216

217+
// RunInBackgroundWithOutput runs the skaffold command in the background.
218+
func (b *RunBuilder) RunInBackgroundWithOutput(t *testing.T, out io.Writer) {
219+
t.Helper()
220+
b.runForked(t, out)
221+
}
222+
210223
// runForked runs the skaffold command in the background with stdout sent to the provided writer.
211224
func (b *RunBuilder) runForked(t *testing.T, out io.Writer) {
212225
t.Helper()

pkg/skaffold/runner/intent.go

+31-2
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,15 @@ limitations under the License.
1616

1717
package runner
1818

19-
import "sync"
19+
import (
20+
"sync"
21+
)
2022

2123
type Intents struct {
2224
build bool
2325
sync bool
2426
deploy bool
27+
devloop bool
2528
autoBuild bool
2629
autoSync bool
2730
autoDeploy bool
@@ -88,6 +91,12 @@ func (i *Intents) SetDeploy(val bool) {
8891
i.lock.Unlock()
8992
}
9093

94+
func (i *Intents) SetDevloop(val bool) {
95+
i.lock.Lock()
96+
i.devloop = val
97+
i.lock.Unlock()
98+
}
99+
91100
func (i *Intents) GetAutoBuild() bool {
92101
i.lock.Lock()
93102
defer i.lock.Unlock()
@@ -106,6 +115,12 @@ func (i *Intents) GetAutoDeploy() bool {
106115
return i.autoDeploy
107116
}
108117

118+
func (i *Intents) GetAutoDevloop() bool {
119+
i.lock.Lock()
120+
defer i.lock.Unlock()
121+
return i.autoDeploy && i.autoBuild && i.autoSync
122+
}
123+
109124
func (i *Intents) SetAutoBuild(val bool) {
110125
i.lock.Lock()
111126
i.autoBuild = val
@@ -124,10 +139,24 @@ func (i *Intents) SetAutoDeploy(val bool) {
124139
i.lock.Unlock()
125140
}
126141

127-
// returns build, sync, and deploy intents (in that order)
142+
func (i *Intents) SetAutoDevloop(val bool) {
143+
i.lock.Lock()
144+
i.autoDeploy = val
145+
i.autoSync = val
146+
i.autoBuild = val
147+
i.lock.Unlock()
148+
}
149+
150+
// GetIntents returns build, sync, and deploy intents (in that order)
151+
// If intent is devloop intent, all are returned true
128152
func (i *Intents) GetIntents() (bool, bool, bool) {
129153
i.lock.Lock()
130154
defer i.lock.Unlock()
155+
if i.devloop {
156+
// reset Devloop intent
157+
i.devloop = false
158+
return true, true, true
159+
}
131160
return i.build, i.sync, i.deploy
132161
}
133162

pkg/skaffold/runner/v1/dev.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,14 @@ func (r *SkaffoldRunner) doDev(ctx context.Context, out io.Writer) error {
5555

5656
buildIntent, syncIntent, deployIntent := r.intents.GetIntents()
5757
log.Entry(ctx).Tracef("dev intents: build %t, sync %t, deploy %t\n", buildIntent, syncIntent, deployIntent)
58-
needsSync := syncIntent && len(r.changeSet.NeedsResync()) > 0
5958
needsBuild := buildIntent && len(r.changeSet.NeedsRebuild()) > 0
59+
needsSync := syncIntent && (len(r.changeSet.NeedsResync()) > 0 || needsBuild)
6060
needsTest := len(r.changeSet.NeedsRetest()) > 0
61-
needsDeploy := deployIntent && r.changeSet.NeedsRedeploy()
61+
needsDeploy := deployIntent && (r.changeSet.NeedsRedeploy() || needsBuild)
6262
if !needsSync && !needsBuild && !needsTest && !needsDeploy {
6363
return nil
6464
}
65+
log.Entry(ctx).Debugf(" devloop: build %t, sync %t, deploy %t\n", needsBuild, needsSync, needsDeploy)
6566

6667
r.deployer.GetLogger().Mute()
6768
// if any action is going to be performed, reset the monitor's changed component tracker for debouncing

pkg/skaffold/runner/v1/new.go

+2
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,8 @@ func setupIntents(runCtx *runcontext.RunContext) (*runner.Intents, chan bool) {
150150
setupTrigger("build", intents.SetBuild, intents.SetAutoBuild, intents.GetAutoBuild, server.SetBuildCallback, server.SetAutoBuildCallback, intentChan)
151151
setupTrigger("sync", intents.SetSync, intents.SetAutoSync, intents.GetAutoSync, server.SetSyncCallback, server.SetAutoSyncCallback, intentChan)
152152
setupTrigger("deploy", intents.SetDeploy, intents.SetAutoDeploy, intents.GetAutoDeploy, server.SetDeployCallback, server.SetAutoDeployCallback, intentChan)
153+
// Setup callback function to buildCallback since build is the start of the devloop.
154+
setupTrigger("devloop", intents.SetDevloop, intents.SetAutoDevloop, intents.GetAutoDevloop, server.SetDevloopCallback, server.SetAutoDevloopCallback, intentChan)
153155

154156
return intents, intentChan
155157
}

pkg/skaffold/server/endpoints.go

+8
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,14 @@ func (s *server) Handle(ctx context.Context, e *proto.Event) (*empty.Empty, erro
4545
}
4646

4747
func (s *server) Execute(ctx context.Context, intent *proto.UserIntentRequest) (*empty.Empty, error) {
48+
if intent.GetIntent().GetDevloop() {
49+
event.ResetStateOnBuild()
50+
go func() {
51+
s.devloopIntentCallback()
52+
}()
53+
return &empty.Empty{}, nil
54+
}
55+
4856
if intent.GetIntent().GetBuild() {
4957
event.ResetStateOnBuild()
5058
go func() {

pkg/skaffold/server/server.go

+36-18
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,14 @@ var (
4747
)
4848

4949
type server struct {
50-
buildIntentCallback func()
51-
syncIntentCallback func()
52-
deployIntentCallback func()
53-
autoBuildCallback func(bool)
54-
autoSyncCallback func(bool)
55-
autoDeployCallback func(bool)
50+
buildIntentCallback func()
51+
syncIntentCallback func()
52+
deployIntentCallback func()
53+
devloopIntentCallback func()
54+
autoBuildCallback func(bool)
55+
autoSyncCallback func(bool)
56+
autoDeployCallback func(bool)
57+
autoDevloopCallback func(bool)
5658
}
5759

5860
func SetBuildCallback(callback func()) {
@@ -61,6 +63,12 @@ func SetBuildCallback(callback func()) {
6163
}
6264
}
6365

66+
func SetDevloopCallback(callback func()) {
67+
if srv != nil {
68+
srv.devloopIntentCallback = callback
69+
}
70+
}
71+
6472
func SetDeployCallback(callback func()) {
6573
if srv != nil {
6674
srv.deployIntentCallback = callback
@@ -79,6 +87,12 @@ func SetAutoBuildCallback(callback func(bool)) {
7987
}
8088
}
8189

90+
func SetAutoDevloopCallback(callback func(bool)) {
91+
if srv != nil {
92+
srv.autoDevloopCallback = callback
93+
}
94+
}
95+
8296
func SetAutoDeployCallback(callback func(bool)) {
8397
if srv != nil {
8498
srv.autoDeployCallback = callback
@@ -164,20 +178,24 @@ func newGRPCServer(preferredPort int) (func() error, int, error) {
164178

165179
s := grpc.NewServer()
166180
srv = &server{
167-
buildIntentCallback: func() {},
168-
deployIntentCallback: func() {},
169-
syncIntentCallback: func() {},
170-
autoBuildCallback: func(bool) {},
171-
autoSyncCallback: func(bool) {},
172-
autoDeployCallback: func(bool) {},
181+
buildIntentCallback: func() {},
182+
deployIntentCallback: func() {},
183+
syncIntentCallback: func() {},
184+
devloopIntentCallback: func() {},
185+
autoBuildCallback: func(bool) {},
186+
autoSyncCallback: func(bool) {},
187+
autoDeployCallback: func(bool) {},
188+
autoDevloopCallback: func(bool) {},
173189
}
174190
v2.Srv = &v2.Server{
175-
BuildIntentCallback: func() {},
176-
DeployIntentCallback: func() {},
177-
SyncIntentCallback: func() {},
178-
AutoBuildCallback: func(bool) {},
179-
AutoSyncCallback: func(bool) {},
180-
AutoDeployCallback: func(bool) {},
191+
BuildIntentCallback: func() {},
192+
DeployIntentCallback: func() {},
193+
SyncIntentCallback: func() {},
194+
DevloopIntentCallback: func() {},
195+
AutoBuildCallback: func(bool) {},
196+
AutoSyncCallback: func(bool) {},
197+
AutoDeployCallback: func(bool) {},
198+
AutoDevloopCallback: func(bool) {},
181199
}
182200
protoV1.RegisterSkaffoldServiceServer(s, srv)
183201
protoV2.RegisterSkaffoldV2ServiceServer(s, v2.Srv)

0 commit comments

Comments
 (0)