Skip to content

Commit e622115

Browse files
satotakebep
authored andcommitted
Add 'ignore' flag
Via 'ignore' flag, you can exclude local files and keep remote objects stale that are matched with regexp. Usage example: ``` s3deploy \ -source=public/ \ -bucket=example.com \ -region=eu-west-1 \ -key=$AWS_ACCESS_KEY_ID \ -secret=$AWS_SECRET_ACCESS_KEY \ -ignore="^unsynced-dir/.*$" \ -v ``` Number of ignored items is not added to any stats count.
1 parent ca63653 commit e622115

File tree

5 files changed

+142
-0
lines changed

5 files changed

+142
-0
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ Usage of ./s3deploy:
4242
-force
4343
upload even if the etags match
4444
-h help
45+
-ignore string
46+
regexp pattern for ignoring files
4547
-key string
4648
access key ID for AWS
4749
-max-delete int

lib/config.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"log"
1212
"os"
1313
"path/filepath"
14+
"regexp"
1415
"strings"
1516
)
1617

@@ -42,6 +43,8 @@ type Config struct {
4243
Silent bool
4344
Force bool
4445
Try bool
46+
Ignore string
47+
IgnoreRE *regexp.Regexp // compiled version of Ignore
4548

4649
// CLI state
4750
PrintVersion bool
@@ -73,6 +76,7 @@ func flagsToConfig(f *flag.FlagSet) (*Config, error) {
7376
f.BoolVar(&cfg.PublicReadACL, "public-access", false, "DEPRECATED: please set -acl='public-read'")
7477
f.StringVar(&cfg.ACL, "acl", "", "provide an ACL for uploaded objects. to make objects public, set to 'public-read'. all possible values are listed here: https://docs.aws.amazon.com/AmazonS3/latest/userguide/acl-overview.html#canned-acl (default \"private\")")
7578
f.BoolVar(&cfg.Force, "force", false, "upload even if the etags match")
79+
f.StringVar(&cfg.Ignore, "ignore", "", "regexp pattern for ignoring files")
7680
f.BoolVar(&cfg.Try, "try", false, "trial run, no remote updates")
7781
f.BoolVar(&cfg.Verbose, "v", false, "enable verbose logging")
7882
f.BoolVar(&cfg.Silent, "quiet", false, "enable silent mode")
@@ -105,5 +109,32 @@ func (cfg *Config) check() error {
105109
return errors.New("you passed a value for the flags public-access and acl, which is not supported. the public-access flag is deprecated. please use the acl flag moving forward")
106110
}
107111

112+
if cfg.Ignore != "" {
113+
re, err := regexp.Compile(cfg.Ignore)
114+
if err != nil {
115+
return errors.New("cannot compile 'ignore' flag pattern " + err.Error())
116+
}
117+
cfg.IgnoreRE = re
118+
}
119+
108120
return nil
109121
}
122+
123+
func (cfg *Config) shouldIgnoreLocal(key string) bool {
124+
if cfg.Ignore == "" {
125+
return false
126+
}
127+
128+
return cfg.IgnoreRE.MatchString(key)
129+
}
130+
131+
func (cfg *Config) shouldIgnoreRemote(key string) bool {
132+
if cfg.Ignore == "" {
133+
return false
134+
}
135+
136+
sub := key[len(cfg.BucketPath):]
137+
sub = strings.TrimPrefix(sub, "/")
138+
139+
return cfg.IgnoreRE.MatchString(sub)
140+
}

lib/config_test.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ func TestFlagsToConfig(t *testing.T) {
2828
"-region=myregion",
2929
"-source=mysource",
3030
"-distribution-id=mydistro",
31+
"-ignore=^ignored-prefix.*",
3132
"-try=true",
3233
}
3334

@@ -47,6 +48,7 @@ func TestFlagsToConfig(t *testing.T) {
4748
assert.Equal(true, cfg.Try)
4849
assert.Equal("myregion", cfg.RegionName)
4950
assert.Equal("mydistro", cfg.CDNDistributionID)
51+
assert.Equal("^ignored-prefix.*", cfg.Ignore)
5052
}
5153

5254
func TestSetAclAndPublicAccessFlag(t *testing.T) {
@@ -66,3 +68,61 @@ func TestSetAclAndPublicAccessFlag(t *testing.T) {
6668
assert.Error(check_err)
6769
assert.Contains(check_err.Error(), "you passed a value for the flags public-access and acl")
6870
}
71+
72+
func TestIgnoreFlagError(t *testing.T) {
73+
assert := require.New(t)
74+
flags := flag.NewFlagSet("test", flag.PanicOnError)
75+
args := []string{
76+
"-bucket=mybucket",
77+
"-ignore=((INVALID_PATTERN",
78+
}
79+
80+
cfg, err := flagsToConfig(flags)
81+
assert.NoError(err)
82+
assert.NoError(flags.Parse(args))
83+
84+
check_err := cfg.check()
85+
assert.Error(check_err)
86+
assert.Contains(check_err.Error(), "cannot compile 'ignore' flag pattern")
87+
}
88+
89+
func TestShouldIgnore(t *testing.T) {
90+
assert := require.New(t)
91+
92+
flags_default := flag.NewFlagSet("test", flag.PanicOnError)
93+
flags_ignore := flag.NewFlagSet("test", flag.PanicOnError)
94+
95+
args_default := []string{
96+
"-bucket=mybucket",
97+
"-path=my/path",
98+
}
99+
args_ignore := []string{
100+
"-bucket=mybucket",
101+
"-path=my/path",
102+
"-ignore=^ignored-prefix.*",
103+
}
104+
105+
cfg_default, _ := flagsToConfig(flags_default)
106+
cfg_ignore, _ := flagsToConfig(flags_ignore)
107+
108+
flags_default.Parse(args_default)
109+
flags_ignore.Parse(args_ignore)
110+
111+
check_err_default := cfg_default.check()
112+
check_err_ignore := cfg_ignore.check()
113+
114+
assert.NoError(check_err_default)
115+
assert.NoError(check_err_ignore)
116+
117+
assert.False(cfg_default.shouldIgnoreLocal("any"))
118+
assert.False(cfg_default.shouldIgnoreLocal("ignored-prefix/file.txt"))
119+
120+
assert.False(cfg_ignore.shouldIgnoreLocal("any"))
121+
assert.True(cfg_ignore.shouldIgnoreLocal("ignored-prefix/file.txt"))
122+
123+
assert.False(cfg_default.shouldIgnoreRemote("my/path/any"))
124+
assert.False(cfg_default.shouldIgnoreRemote("my/path/ignored-prefix/file.txt"))
125+
126+
assert.False(cfg_ignore.shouldIgnoreRemote("my/path/any"))
127+
assert.True(cfg_ignore.shouldIgnoreRemote("my/path/ignored-prefix/file.txt"))
128+
}

lib/deployer.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,12 @@ func (d *Deployer) plan(ctx context.Context) error {
233233
close(d.filesToUpload)
234234

235235
// any remote files not found locally should be removed:
236+
// except for ignored files
236237
for key := range remoteFiles {
238+
if d.cfg.shouldIgnoreRemote(key) {
239+
fmt.Fprintf(d.outv, "%s ignored …\n", key)
240+
continue
241+
}
237242
d.enqueueDelete(key)
238243
}
239244

@@ -273,6 +278,11 @@ func (d *Deployer) walk(ctx context.Context, basePath string, files chan<- *osFi
273278
if err != nil {
274279
return err
275280
}
281+
282+
if d.cfg.shouldIgnoreLocal(rel) {
283+
return nil
284+
}
285+
276286
f, err := newOSFile(d.cfg.conf.Routes, d.cfg.BucketPath, rel, abs, info)
277287
if err != nil {
278288
return err

lib/deployer_test.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,45 @@ func TestDeployForce(t *testing.T) {
103103
assert.Equal("Deleted 1 of 1, uploaded 4, skipped 0 (100% changed)", stats.Summary())
104104
}
105105

106+
func TestDeployWitIgnorePattern(t *testing.T) {
107+
assert := require.New(t)
108+
root := "my/path"
109+
re := `^(main\.css|deleteme\.txt)$`
110+
111+
store, m := newTestStore(0, root)
112+
source := testSourcePath()
113+
configFile := filepath.Join(source, ".s3deploy.yml")
114+
115+
cfg := &Config{
116+
BucketName: "example.com",
117+
RegionName: "eu-west-1",
118+
ConfigFile: configFile,
119+
BucketPath: root,
120+
MaxDelete: 300,
121+
Silent: false,
122+
SourcePath: source,
123+
baseStore: store,
124+
Ignore: re,
125+
}
126+
127+
prevCss := m["my/path/main.css"]
128+
prevTag := prevCss.ETag()
129+
130+
stats, err := Deploy(cfg)
131+
assert.NoError(err)
132+
assert.Equal("Deleted 0 of 0, uploaded 2, skipped 1 (67% changed)", stats.Summary())
133+
assertKeys(t, m,
134+
"my/path/.s3deploy.yml",
135+
"my/path/index.html",
136+
"my/path/ab.txt",
137+
"my/path/deleteme.txt", // ignored: stale
138+
"my/path/main.css", // ignored: not updated
139+
)
140+
mainCss := m["my/path/main.css"]
141+
assert.Equal(mainCss.ETag(), prevTag)
142+
143+
}
144+
106145
func TestDeploySourceNotFound(t *testing.T) {
107146
assert := require.New(t)
108147
store, _ := newTestStore(0, "")

0 commit comments

Comments
 (0)