11package main
22
33import (
4- "fmt"
54 "io"
65 "mime"
76 "os"
@@ -85,7 +84,7 @@ type Plugin struct {
8584 Source string
8685 Target string
8786
88- // Strip the prefix from the target path (supports wildcards)
87+ // Strip the prefix from the target path
8988 StripPrefix string
9089
9190 // Exclude files matching this pattern.
@@ -139,64 +138,13 @@ func (p *Plugin) Exec() error {
139138 return err
140139 }
141140
142- // Validate strip prefix pattern and precompile regex once
143- normalizedStrip := strings .ReplaceAll (p .StripPrefix , "\\ " , "/" )
144- if p .StripPrefix != "" {
145- if err := validateStripPrefix (p .StripPrefix ); err != nil {
146- log .WithFields (log.Fields {
147- "error" : err ,
148- "pattern" : p .StripPrefix ,
149- }).Error ("Invalid strip_prefix pattern" )
150- return err
151- }
152- }
153-
154- var compiled * regexp.Regexp
155- if normalizedStrip != "" && strings .ContainsAny (normalizedStrip , "*?" ) {
156- var err error
157- compiled , err = patternToRegex (normalizedStrip )
158- if err != nil {
159- log .WithFields (log.Fields {
160- "error" : err ,
161- "pattern" : p .StripPrefix ,
162- }).Error ("Failed to compile strip_prefix pattern" )
163- return err
164- }
165- }
166-
167- anyMatched := false
168-
169141 for _ , match := range matches {
170142 // skip directories
171143 if isDir (match , matches ) {
172144 continue
173145 }
174146
175- // Preview stripping (using precompiled regex when available)
176- stripped := match
177- matched := false
178- if normalizedStrip != "" {
179- var err error
180- stripped , matched , err = stripWildcardPrefixWithRegex (match , normalizedStrip , compiled )
181- if err != nil {
182- log .WithFields (log.Fields {
183- "error" : err ,
184- "path" : match ,
185- "pattern" : p .StripPrefix ,
186- }).Warn ("Failed to strip prefix, using original path" )
187- stripped = match
188- }
189- }
190- if matched {
191- anyMatched = true
192- }
193-
194- // Build final key (ensure relative component for join)
195- rel := strings .TrimPrefix (filepath .ToSlash (stripped ), "/" )
196- target := filepath .ToSlash (filepath .Join (p .Target , rel ))
197- if ! strings .HasPrefix (target , "/" ) {
198- target = "/" + target
199- }
147+ target := resolveKey (p .Target , match , p .StripPrefix )
200148
201149 contentType := matchExtension (match , p .ContentType )
202150 contentEncoding := matchExtension (match , p .ContentEncoding )
@@ -217,22 +165,9 @@ func (p *Plugin) Exec() error {
217165 "target" : target ,
218166 }).Info ("Uploading file" )
219167
220- // when executing a dry-run print what would be stripped and skip upload.
168+ // when executing a dry-run we exit because we don't actually want to
169+ // upload the file to S3.
221170 if p .DryRun {
222- removed := ""
223- if matched {
224- // removed prefix = original - stripped suffix
225- orig := filepath .ToSlash (match )
226- rem := strings .TrimSuffix (orig , filepath .ToSlash (stripped ))
227- removed = rem
228- }
229- log .WithFields (log.Fields {
230- "name" : match ,
231- "bucket" : p .Bucket ,
232- "target" : target ,
233- "strip_pattern" : p .StripPrefix ,
234- "removed_prefix" : removed ,
235- }).Info ("Dry-run: would upload" )
236171 continue
237172 }
238173
@@ -291,12 +226,6 @@ func (p *Plugin) Exec() error {
291226 f .Close ()
292227 }
293228
294- if normalizedStrip != "" && ! anyMatched {
295- log .WithFields (log.Fields {
296- "pattern" : p .StripPrefix ,
297- }).Warn ("strip_prefix did not match any paths; keys will include original path" )
298- }
299-
300229 return nil
301230}
302231
@@ -376,21 +305,7 @@ func assumeRole(roleArn, roleSessionName, externalID string) *credentials.Creden
376305// resolveKey is a helper function that returns s3 object key where file present at srcPath is uploaded to.
377306// srcPath is assumed to be in forward slash format
378307func resolveKey (target , srcPath , stripPrefix string ) string {
379- // Use wildcard-aware prefix stripping
380- stripped , err := stripWildcardPrefix (srcPath , stripPrefix )
381- if err != nil {
382- // Log error but continue with original path
383- log .WithFields (log.Fields {
384- "error" : err ,
385- "path" : srcPath ,
386- "pattern" : stripPrefix ,
387- }).Warn ("Failed to strip prefix, using original path" )
388- stripped = srcPath
389- }
390- // Ensure we never drop the target when the stripped path is absolute.
391- // Always join with a relative path component.
392- stripped = strings .TrimPrefix (stripped , "/" )
393- key := filepath .Join (target , stripped )
308+ key := filepath .Join (target , strings .TrimPrefix (srcPath , filepath .ToSlash (stripPrefix )))
394309 key = filepath .ToSlash (key )
395310 if ! strings .HasPrefix (key , "/" ) {
396311 key = "/" + key
@@ -455,6 +370,7 @@ func (p *Plugin) downloadS3Object(client *s3.S3, sourceDir, key, target string)
455370
456371 // Create the destination file path
457372 destination := filepath .Join (p .Target , target )
373+ log .Println ("Destination: " , destination )
458374
459375 // Extract the directory from the destination path
460376 dir := filepath .Dir (destination )
@@ -550,6 +466,7 @@ func (p *Plugin) createS3Client() *s3.S3 {
550466 log .Warn ("AWS Key and/or Secret not provided (falling back to ec2 instance profile)" )
551467 }
552468
469+
553470 // Create session with primary credentials
554471 sess , err = session .NewSession (conf )
555472 if err != nil {
@@ -562,8 +479,8 @@ func (p *Plugin) createS3Client() *s3.S3 {
562479 // Handle secondary role assumption if UserRoleArn is provided
563480 if len (p .UserRoleArn ) > 0 {
564481 log .WithField ("UserRoleArn" , p .UserRoleArn ).Info ("Using user role ARN" )
565-
566- // Create credentials using the existing session for role assumption
482+
483+ // Create credentials using the existing session for role assumption
567484 // by assuming the UserRoleArn (with ExternalID when provided)
568485 creds := stscreds .NewCredentials (sess , p .UserRoleArn , func (provider * stscreds.AssumeRoleProvider ) {
569486 if p .UserRoleExternalID != "" {
@@ -591,113 +508,3 @@ func assumeRoleWithWebIdentity(sess *session.Session, roleArn, roleSessionName,
591508 }
592509 return credentials .NewStaticCredentials (* result .Credentials .AccessKeyId , * result .Credentials .SecretAccessKey , * result .Credentials .SessionToken ), nil
593510}
594-
595- // validateStripPrefix validates a strip prefix pattern with wildcards
596- func validateStripPrefix (pattern string ) error {
597- // Normalize Windows backslashes to forward slashes for validation (OS-independent)
598- pattern = strings .ReplaceAll (pattern , "\\ " , "/" )
599-
600- // Pattern must start with /
601- if ! strings .HasPrefix (pattern , "/" ) {
602- return fmt .Errorf ("strip_prefix must start with '/'" )
603- }
604-
605- // Reject Windows drive-letter prefixes like C:/...
606- if len (pattern ) >= 2 && pattern [1 ] == ':' {
607- return fmt .Errorf ("strip_prefix must be an absolute POSIX-style path (e.g. '/root/...'), drive letters are not supported" )
608- }
609-
610- // Check length limit
611- if len (pattern ) > 256 {
612- return fmt .Errorf ("strip_prefix pattern too long (max 256 characters)" )
613- }
614-
615- // Count wildcards
616- wildcardCount := strings .Count (pattern , "*" ) + strings .Count (pattern , "?" )
617- if wildcardCount > 20 {
618- return fmt .Errorf ("strip_prefix pattern contains too many wildcards (max 20)" )
619- }
620-
621- // Check for empty segments
622- if strings .Contains (pattern , "//" ) {
623- return fmt .Errorf ("strip_prefix pattern contains empty segment '//'" )
624- }
625-
626- // Check for invalid ** usage (must be standalone segment)
627- parts := strings .Split (pattern , "/" )
628- for _ , part := range parts {
629- if strings .Contains (part , "**" ) && part != "**" {
630- return fmt .Errorf ("'**' must be a standalone directory segment" )
631- }
632- }
633-
634- return nil
635- }
636-
637- // patternToRegex converts shell-style wildcards to regex
638- func patternToRegex (pattern string ) (* regexp.Regexp , error ) {
639- // Escape special regex characters except our wildcards
640- escaped := regexp .QuoteMeta (pattern )
641-
642- // Replace escaped wildcards with regex equivalents
643- // Order matters: ** must be replaced before *
644- escaped = strings .ReplaceAll (escaped , `\*\*` , "(.+)" ) // ** -> (.+) any depth
645- escaped = strings .ReplaceAll (escaped , `\*` , "([^/]+)" ) // * -> ([^/]+) one segment
646- escaped = strings .ReplaceAll (escaped , `\?` , "([^/])" ) // ? -> ([^/]) one character
647-
648- // Anchor at start
649- escaped = "^" + escaped
650-
651- return regexp .Compile (escaped )
652- }
653-
654- // stripWildcardPrefixWithRegex strips prefix using wildcard pattern matching, reusing
655- // a precompiled regex when provided. It returns the possibly stripped path, whether
656- // the pattern matched, and any error if stripping would remove the entire key.
657- func stripWildcardPrefixWithRegex (path , pattern string , re * regexp.Regexp ) (string , bool , error ) {
658- if pattern == "" {
659- return path , false , nil
660- }
661-
662- // Normalize paths to forward slashes (OS-independent)
663- path = strings .ReplaceAll (path , "\\ " , "/" )
664- pattern = strings .ReplaceAll (pattern , "\\ " , "/" )
665-
666- // Literal prefix (no wildcards)
667- if ! strings .ContainsAny (pattern , "*?" ) {
668- if ! strings .HasPrefix (path , pattern ) {
669- return path , false , nil
670- }
671- stripped := strings .TrimPrefix (path , pattern )
672- if stripped == "" || stripped == "/" || strings .TrimPrefix (stripped , "/" ) == "" {
673- return path , true , fmt .Errorf ("strip_prefix removes entire path for '%s'" , filepath .Base (path ))
674- }
675- return stripped , true , nil
676- }
677-
678- // Wildcard pattern
679- var err error
680- if re == nil {
681- re , err = patternToRegex (pattern )
682- if err != nil {
683- return path , false , fmt .Errorf ("invalid pattern: %v" , err )
684- }
685- }
686-
687- m := re .FindStringSubmatch (path )
688- if len (m ) == 0 {
689- return path , false , nil
690- }
691- full := m [0 ]
692- stripped := strings .TrimPrefix (path , full )
693- if stripped == "" || stripped == "/" || strings .TrimPrefix (stripped , "/" ) == "" {
694- return path , true , fmt .Errorf ("strip_prefix removes entire path for '%s'" , filepath .Base (path ))
695- }
696- return stripped , true , nil
697- }
698-
699- // stripWildcardPrefix strips prefix using wildcard pattern matching
700- func stripWildcardPrefix (path , pattern string ) (string , error ) {
701- stripped , _ , err := stripWildcardPrefixWithRegex (path , pattern , nil )
702- return stripped , err
703- }
0 commit comments