github.com/GoogleContainerTools/skaffold@v1.39.18/pkg/skaffold/filemon/changes.go (about)

     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 filemon
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"os"
    23  	"sort"
    24  	"strings"
    25  	"time"
    26  
    27  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/output/log"
    28  )
    29  
    30  // FileMap is a map of filename to modification times.
    31  type FileMap map[string]time.Time
    32  
    33  // Stat returns the modification times for a list of files.
    34  func Stat(deps func() ([]string, error)) (FileMap, error) {
    35  	state := FileMap{}
    36  	paths, err := deps()
    37  	if err != nil {
    38  		return state, fmt.Errorf("listing files: %w", err)
    39  	}
    40  	for _, path := range paths {
    41  		stat, err := os.Stat(path)
    42  		if err != nil {
    43  			if os.IsNotExist(err) {
    44  				log.Entry(context.TODO()).Debugf("could not stat dependency: %s", err)
    45  				continue // Ignore files that don't exist
    46  			}
    47  			return nil, fmt.Errorf("unable to stat file %q: %w", path, err)
    48  		}
    49  		state[path] = stat.ModTime()
    50  	}
    51  
    52  	return state, nil
    53  }
    54  
    55  type Events struct {
    56  	Added    []string
    57  	Modified []string
    58  	Deleted  []string
    59  }
    60  
    61  func (e Events) HasChanged() bool {
    62  	return len(e.Added) != 0 || len(e.Deleted) != 0 || len(e.Modified) != 0
    63  }
    64  
    65  func (e *Events) String() string {
    66  	added, deleted, modified := len(e.Added), len(e.Deleted), len(e.Modified)
    67  
    68  	var sb strings.Builder
    69  	if added > 0 {
    70  		sb.WriteString(fmt.Sprintf("[watch event] added: %s\n", e.Added))
    71  	}
    72  	if deleted > 0 {
    73  		sb.WriteString(fmt.Sprintf("[watch event] deleted: %s\n", e.Deleted))
    74  	}
    75  	if modified > 0 {
    76  		sb.WriteString(fmt.Sprintf("[watch event] modified: %s\n", e.Modified))
    77  	}
    78  	return sb.String()
    79  }
    80  
    81  func events(prev, curr FileMap) Events {
    82  	e := Events{}
    83  	for f, t := range prev {
    84  		modtime, ok := curr[f]
    85  		if !ok {
    86  			// file in prev but not in curr -> file deleted
    87  			e.Deleted = append(e.Deleted, f)
    88  			continue
    89  		}
    90  		if !modtime.Equal(t) {
    91  			// file in both prev and curr
    92  			// time not equal -> file modified
    93  			e.Modified = append(e.Modified, f)
    94  			continue
    95  		}
    96  	}
    97  
    98  	for f := range curr {
    99  		// don't need to check case where file is in both curr and prev
   100  		// covered above
   101  		_, ok := prev[f]
   102  		if !ok {
   103  			// file in curr but not in prev -> file added
   104  			e.Added = append(e.Added, f)
   105  		}
   106  	}
   107  
   108  	sortEvents(e)
   109  	logEvents(e)
   110  	return e
   111  }
   112  
   113  func sortEvents(e Events) {
   114  	sort.Strings(e.Added)
   115  	sort.Strings(e.Modified)
   116  	sort.Strings(e.Deleted)
   117  }
   118  
   119  func logEvents(e Events) {
   120  	if e.Added != nil && len(e.Added) > 0 {
   121  		log.Entry(context.TODO()).Infof("files added: %v", e.Added)
   122  	}
   123  	if e.Modified != nil && len(e.Modified) > 0 {
   124  		log.Entry(context.TODO()).Infof("files modified: %v", e.Modified)
   125  	}
   126  	if e.Deleted != nil && len(e.Deleted) > 0 {
   127  		log.Entry(context.TODO()).Infof("files deleted: %v", e.Deleted)
   128  	}
   129  }