Skip to content

Commit 25e4809

Browse files
authored
Graceful cancellation for network dependent commands (#123)
Issue N/A `ack-generate` uses go-git to clone and fetch repository tags, these actions are time consuming (more than a minute for cloning) and do not tolerate interruption (ctrl+C). Forcing a command to exit when it's running a `git.Clone` or a `git.FetchTags` cause the process to write incomplete data on disk, therefore re-running `ack-generate` will always return an error. The only solution for this problem is to delete the local sdk cache and let `ack-generate` reclone (without interruption) This patch adds a graceful cancellation system for `ack-generate` subcommads. Which will allow users to safely ctrl+C and re-run their commands without any errors. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
1 parent 062afeb commit 25e4809

File tree

6 files changed

+57
-8
lines changed

6 files changed

+57
-8
lines changed

cmd/ack-generate/command/apis.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
package command
1515

1616
import (
17+
"context"
1718
"fmt"
1819
"io/ioutil"
1920
"path/filepath"
@@ -91,7 +92,9 @@ func generateAPIs(cmd *cobra.Command, args []string) error {
9192
if optOutputPath == "" {
9293
optOutputPath = filepath.Join(optServicesDir, svcAlias)
9394
}
94-
if err := ensureSDKRepo(optCacheDir, optRefreshCache); err != nil {
95+
ctx, cancel := contextWithSigterm(context.Background())
96+
defer cancel()
97+
if err := ensureSDKRepo(ctx, optCacheDir, optRefreshCache); err != nil {
9598
return err
9699
}
97100
sdkHelper := model.NewSDKHelper(sdkDir)

cmd/ack-generate/command/common.go

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,46 @@ import (
1818
"fmt"
1919
"io/ioutil"
2020
"os"
21+
"os/signal"
2122
"path/filepath"
2223
"strings"
24+
"syscall"
25+
"time"
2326

2427
"golang.org/x/mod/modfile"
2528

2629
"github.com/aws-controllers-k8s/code-generator/pkg/util"
2730
)
2831

2932
const (
30-
sdkRepoURL = "https://github.com/aws/aws-sdk-go"
33+
sdkRepoURL = "https://github.com/aws/aws-sdk-go"
34+
defaultGitCloneTimeout = 180 * time.Second
35+
defaultGitFetchTimeout = 30 * time.Second
3136
)
3237

38+
func contextWithSigterm(ctx context.Context) (context.Context, context.CancelFunc) {
39+
ctx, cancel := context.WithCancel(ctx)
40+
signalCh := make(chan os.Signal, 1)
41+
42+
// recreate the context.CancelFunc
43+
cancelFunc := func() {
44+
signal.Stop(signalCh)
45+
cancel()
46+
}
47+
48+
// notify on SIGINT or SIGTERM
49+
signal.Notify(signalCh, os.Interrupt, syscall.SIGTERM)
50+
go func() {
51+
select {
52+
case <-signalCh:
53+
cancel()
54+
case <-ctx.Done():
55+
}
56+
}()
57+
58+
return ctx, cancelFunc
59+
}
60+
3361
// ensureDir makes sure that a supplied directory exists and
3462
// returns whether the directory already existed.
3563
func ensureDir(fp string) (bool, error) {
@@ -69,6 +97,7 @@ func isDirWriteable(fp string) bool {
6997
// the aws-sdk-go is found. It will also optionally fetch all the remote tags
7098
// and checkout the given tag.
7199
func ensureSDKRepo(
100+
ctx context.Context,
72101
cacheDir string,
73102
// A boolean instructing ensureSDKRepo whether to fetch the remote tags from
74103
// the upstream repository
@@ -83,15 +112,20 @@ func ensureSDKRepo(
83112
// Clone repository if it doen't exist
84113
sdkDir = filepath.Join(srcPath, "aws-sdk-go")
85114
if _, err := os.Stat(sdkDir); os.IsNotExist(err) {
86-
err = util.CloneRepository(context.Background(), sdkDir, sdkRepoURL)
115+
116+
ctx, cancel := context.WithTimeout(ctx, defaultGitCloneTimeout)
117+
defer cancel()
118+
err = util.CloneRepository(ctx, sdkDir, sdkRepoURL)
87119
if err != nil {
88120
return fmt.Errorf("canot clone repository: %v", err)
89121
}
90122
}
91123

92124
// Fetch all tags
93125
if fetchTags {
94-
err = util.FetchRepositoryTags(context.Background(), sdkDir)
126+
ctx, cancel := context.WithTimeout(ctx, defaultGitFetchTimeout)
127+
defer cancel()
128+
err = util.FetchRepositoryTags(ctx, sdkDir)
95129
if err != nil {
96130
return fmt.Errorf("cannot fetch tags: %v", err)
97131
}

cmd/ack-generate/command/controller.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ package command
1515

1616
import (
1717
"bufio"
18+
"context"
1819
"fmt"
1920
"io/ioutil"
2021
"os"
@@ -57,7 +58,9 @@ func generateController(cmd *cobra.Command, args []string) error {
5758
optOutputPath = filepath.Join(optServicesDir, svcAlias)
5859
}
5960

60-
if err := ensureSDKRepo(optCacheDir, optRefreshCache); err != nil {
61+
ctx, cancel := contextWithSigterm(context.Background())
62+
defer cancel()
63+
if err := ensureSDKRepo(ctx, optCacheDir, optRefreshCache); err != nil {
6164
return err
6265
}
6366
sdkHelper := ackmodel.NewSDKHelper(sdkDir)

cmd/ack-generate/command/crossplane.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
package command
1515

1616
import (
17+
"context"
1718
"fmt"
1819
"io/ioutil"
1920
"os"
@@ -49,7 +50,9 @@ func generateCrossplane(_ *cobra.Command, args []string) error {
4950
if len(args) != 1 {
5051
return fmt.Errorf("please specify the service alias for the AWS service API to generate")
5152
}
52-
if err := ensureSDKRepo(optCacheDir, optRefreshCache); err != nil {
53+
ctx, cancel := contextWithSigterm(context.Background())
54+
defer cancel()
55+
if err := ensureSDKRepo(ctx, optCacheDir, optRefreshCache); err != nil {
5356
return err
5457
}
5558
svcAlias := strings.ToLower(args[0])

cmd/ack-generate/command/olm.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
package command
1515

1616
import (
17+
"context"
1718
"fmt"
1819
"io/ioutil"
1920
"path/filepath"
@@ -81,7 +82,9 @@ func generateOLMAssets(cmd *cobra.Command, args []string) error {
8182
version := args[1]
8283

8384
// get the generator inputs
84-
if err := ensureSDKRepo(optCacheDir, optRefreshCache); err != nil {
85+
ctx, cancel := contextWithSigterm(context.Background())
86+
defer cancel()
87+
if err := ensureSDKRepo(ctx, optCacheDir, optRefreshCache); err != nil {
8588
return err
8689
}
8790
sdkHelper := ackmodel.NewSDKHelper(sdkDir)

cmd/ack-generate/command/release.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
package command
1515

1616
import (
17+
"context"
1718
"fmt"
1819
"io/ioutil"
1920
"path/filepath"
@@ -65,7 +66,9 @@ func generateRelease(cmd *cobra.Command, args []string) error {
6566
// version supplied hasn't been used (as a Git tag) before...
6667
releaseVersion := strings.ToLower(args[1])
6768

68-
if err := ensureSDKRepo(optCacheDir, optRefreshCache); err != nil {
69+
ctx, cancel := contextWithSigterm(context.Background())
70+
defer cancel()
71+
if err := ensureSDKRepo(ctx, optCacheDir, optRefreshCache); err != nil {
6972
return err
7073
}
7174
sdkHelper := ackmodel.NewSDKHelper(sdkDir)

0 commit comments

Comments
 (0)