public string GetHash()
 {
     return(MatchedFile.GenerateHash(new [] { this }));
 }
        public static Fingerprint EvaluateKeyToFingerprint(
            AgentTaskPluginExecutionContext context,
            string filePathRoot,
            IEnumerable <string> keySegments)
        {
            // Quickly validate all segments
            foreach (string keySegment in keySegments)
            {
                CheckKeySegment(keySegment);
            }

            string defaultWorkingDirectory = context.Variables.GetValueOrDefault(
                "system.defaultworkingdirectory" // Constants.Variables.System.DefaultWorkingDirectory
                )?.Value;

            var resolvedSegments = new List <string>();
            var exceptions       = new List <Exception>();

            Action <string, KeySegmentType, Object> LogKeySegment = (segment, type, details) =>
            {
                Func <string, int, string> FormatForDisplay = (value, displayLength) =>
                {
                    if (value.Length > displayLength)
                    {
                        value = value.Substring(0, displayLength - 3) + "...";
                    }

                    return(value.PadRight(displayLength));
                };

                string formattedSegment = FormatForDisplay(segment, Math.Min(keySegments.Select(s => s.Length).Max(), 50));

                if (type == KeySegmentType.String)
                {
                    context.Output($" - {formattedSegment} [string]");
                }
                else
                {
                    var matches = (details as MatchedFile[]) ?? new MatchedFile[0];

                    if (type == KeySegmentType.FilePath)
                    {
                        string fileHash = matches.Length > 0 ? matches[0].Hash : null;
                        context.Output($" - {formattedSegment} [file] {(!string.IsNullOrWhiteSpace(fileHash) ? $"--> {fileHash}" : "(not found)")}");
                    }
                    else if (type == KeySegmentType.FilePattern)
                    {
                        context.Output($" - {formattedSegment} [file pattern; matches: {matches.Length}]");
                        if (matches.Any())
                        {
                            int filePathDisplayLength = Math.Min(matches.Select(mf => mf.DisplayPath.Length).Max(), 70);
                            foreach (var match in matches)
                            {
                                context.Output($"   - {FormatForDisplay(match.DisplayPath, filePathDisplayLength)} --> {match.Hash}");
                            }
                        }
                    }
                }
            };

            foreach (string keySegment in keySegments)
            {
                if (!IsPathyKeySegment(keySegment))
                {
                    LogKeySegment(keySegment, KeySegmentType.String, null);
                    resolvedSegments.Add(keySegment);
                }
                else
                {
                    string[] pathRules    = keySegment.Split(new [] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(s => s.Trim()).ToArray();
                    string[] includeRules = pathRules.Where(p => !p.StartsWith('!')).ToArray();

                    if (!includeRules.Any())
                    {
                        throw new ArgumentException("No include rules specified.");
                    }

                    var enumerations = new Dictionary <Enumeration, List <string> >();
                    foreach (string includeRule in includeRules)
                    {
                        string absoluteRootRule = MakePathCanonical(defaultWorkingDirectory, includeRule);
                        context.Verbose($"Expanded include rule is `{absoluteRootRule}`.");
                        Enumeration   enumeration = DetermineFileEnumerationFromGlob(absoluteRootRule);
                        List <string> globs;
                        if (!enumerations.TryGetValue(enumeration, out globs))
                        {
                            enumerations[enumeration] = globs = new List <string>();
                        }
                        globs.Add(absoluteRootRule);
                    }

                    string[] excludeRules         = pathRules.Where(p => p.StartsWith('!')).ToArray();
                    string[] absoluteExcludeRules = excludeRules.Select(excludeRule => {
                        excludeRule = excludeRule.Substring(1);
                        return(MakePathCanonical(defaultWorkingDirectory, excludeRule));
                    }).ToArray();

                    var matchedFiles = new SortedDictionary <string, MatchedFile>(StringComparer.Ordinal);

                    foreach (var kvp in enumerations)
                    {
                        Enumeration   enumerate            = kvp.Key;
                        List <string> absoluteIncludeGlobs = kvp.Value;
                        context.Verbose($"Enumerating starting at root `{enumerate.RootPath}` with pattern `{enumerate.Pattern}` and depth `{enumerate.Depth}`.");
                        IEnumerable <string> files  = Directory.EnumerateFiles(enumerate.RootPath, enumerate.Pattern, enumerate.Depth);
                        Func <string, bool>  filter = CreateFilter(context, absoluteIncludeGlobs, absoluteExcludeRules);
                        files = files.Where(f => filter(f)).Distinct();

                        foreach (string path in files)
                        {
                            using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
                            {
                                // Path.GetRelativePath returns 'The relative path, or path if the paths don't share the same root.'
                                string displayPath = filePathRoot == null ? path : Path.GetRelativePath(filePathRoot, path);
                                matchedFiles.Add(path, new MatchedFile(displayPath, fs));
                            }
                        }
                    }

                    var patternSegment = keySegment.IndexOfAny(GlobChars) >= 0 || matchedFiles.Count() > 1;

                    LogKeySegment(keySegment,
                                  patternSegment ? KeySegmentType.FilePattern : KeySegmentType.FilePath,
                                  matchedFiles.Values.ToArray());

                    if (!matchedFiles.Any())
                    {
                        if (patternSegment)
                        {
                            exceptions.Add(new FileNotFoundException($"No matching files found for pattern: {keySegment}"));
                        }
                        else
                        {
                            exceptions.Add(new FileNotFoundException($"File not found: {keySegment}"));
                        }
                    }

                    resolvedSegments.Add(MatchedFile.GenerateHash(matchedFiles.Values));
                }
            }

            if (exceptions.Any())
            {
                throw new AggregateException(exceptions);
            }

            return(new Fingerprint()
            {
                Segments = resolvedSegments.ToArray()
            });
        }
Example #3
0
        public static Fingerprint EvaluateToFingerprint(
            AgentTaskPluginExecutionContext context,
            string filePathRoot,
            IEnumerable <string> segments,
            FingerprintType fingerprintType)
        {
            // Quickly validate all segments
            foreach (string segment in segments)
            {
                CheckSegment(segment, fingerprintType.ToString());
            }

            string defaultWorkingDirectory = context.Variables.GetValueOrDefault(
                "system.defaultworkingdirectory" // Constants.Variables.System.DefaultWorkingDirectory
                )?.Value ?? filePathRoot;

            var resolvedSegments   = new List <string>();
            var exceptions         = new List <Exception>();
            var hasPatternSegments = false;

            foreach (string segment in segments)
            {
                if (!IsPathySegment(segment))
                {
                    LogSegment(context, segments, segment, KeySegmentType.String, null);
                    resolvedSegments.Add(segment);
                }
                else
                {
                    string[] pathRules    = segment.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(s => s.Trim()).ToArray();
                    string[] includeRules = pathRules.Where(p => !p.StartsWith('!')).ToArray();

                    if (!includeRules.Any())
                    {
                        throw new ArgumentException("No include rules specified.");
                    }

                    var enumerations = new Dictionary <Enumeration, List <string> >();
                    foreach (string includeRule in includeRules)
                    {
                        string absoluteRootRule = MakePathCanonical(defaultWorkingDirectory, includeRule);
                        context.Verbose($"Expanded include rule is `{absoluteRootRule}`.");
                        Enumeration   enumeration = DetermineFileEnumerationFromGlob(absoluteRootRule);
                        List <string> globs;
                        if (!enumerations.TryGetValue(enumeration, out globs))
                        {
                            enumerations[enumeration] = globs = new List <string>();
                        }
                        globs.Add(absoluteRootRule);
                    }

                    string[] excludeRules         = pathRules.Where(p => p.StartsWith('!')).ToArray();
                    string[] absoluteExcludeRules = excludeRules.Select(excludeRule =>
                    {
                        excludeRule = excludeRule.Substring(1);
                        return(MakePathCanonical(defaultWorkingDirectory, excludeRule));
                    }).ToArray();

                    var matchedFiles       = new SortedDictionary <string, MatchedFile>(StringComparer.Ordinal);
                    var matchedDirectories = new SortedDictionary <string, string>(StringComparer.Ordinal);

                    foreach (var kvp in enumerations)
                    {
                        Enumeration   enumerate            = kvp.Key;
                        List <string> absoluteIncludeGlobs = kvp.Value;
                        context.Verbose($"Enumerating starting at root `{enumerate.RootPath}` with pattern `{enumerate.Pattern}` and depth `{enumerate.Depth}`.");
                        IEnumerable <string> files = fingerprintType == FingerprintType.Key
                            ? Directory.EnumerateFiles(enumerate.RootPath, enumerate.Pattern, enumerate.Depth)
                            : Directory.EnumerateDirectories(enumerate.RootPath, enumerate.Pattern, enumerate.Depth);

                        Func <string, bool> filter = CreateFilter(context, absoluteIncludeGlobs, absoluteExcludeRules);
                        files = files.Where(f => filter(f)).Distinct();

                        foreach (string path in files)
                        {
                            // Path.GetRelativePath returns 'The relative path, or path if the paths don't share the same root.'
                            string displayPath = filePathRoot == null ? path : Path.GetRelativePath(filePathRoot, path);

                            if (fingerprintType == FingerprintType.Key)
                            {
                                using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
                                {
                                    matchedFiles.Add(path, new MatchedFile(displayPath, fs));
                                }
                            }
                            else
                            {
                                matchedDirectories.Add(path, displayPath);
                            }
                        }
                    }

                    var patternSegment = segment.IndexOfAny(GlobChars) >= 0 || matchedFiles.Count > 1;
                    hasPatternSegments |= patternSegment;

                    var displaySegment = segment;

                    if (context.Container != null)
                    {
                        displaySegment = context.Container.TranslateToContainerPath(displaySegment);
                    }

                    KeySegmentType segmentType;
                    object         details;
                    if (fingerprintType == FingerprintType.Key)
                    {
                        segmentType = patternSegment ? KeySegmentType.FilePattern : KeySegmentType.FilePath;
                        details     = matchedFiles.Values.ToArray();
                        resolvedSegments.Add(MatchedFile.GenerateHash(matchedFiles.Values));

                        if (!matchedFiles.Any())
                        {
                            var message = patternSegment ? $"No matching files found for pattern: {displaySegment}" : $"File not found: {displaySegment}";
                            exceptions.Add(new FileNotFoundException(message));
                        }
                    }
                    else
                    {
                        segmentType = patternSegment ? KeySegmentType.DirectoryPattern : KeySegmentType.Directory;
                        details     = matchedDirectories.Values.ToArray();
                        resolvedSegments.AddRange(matchedDirectories.Values);

                        if (!matchedDirectories.Any())
                        {
                            var message = patternSegment ? $"No matching directories found for pattern: {displaySegment}" : $"Directory not found: {displaySegment}";
                            exceptions.Add(new DirectoryNotFoundException(message));
                        }
                    }

                    LogSegment(
                        context,
                        segments,
                        displaySegment,
                        segmentType,
                        details
                        );
                }
            }

            if (fingerprintType == FingerprintType.Path)
            {
                // If there are segments or contains a glob pattern, all resolved segments must be rooted within filePathRoot (e.g. Pipeline.Workspace)
                // This limitation is mainly due to 7z not extracting backtraced paths
                if (resolvedSegments.Count() > 1 || hasPatternSegments)
                {
                    foreach (var backtracedPath in resolvedSegments.Where(x => x.StartsWith("..")))
                    {
                        exceptions.Add(new ArgumentException($"Resolved path is not within `Pipeline.Workspace`: {backtracedPath}"));
                    }
                }
            }

            if (exceptions.Any())
            {
                throw new AggregateException(exceptions);
            }

            return(new Fingerprint()
            {
                Segments = resolvedSegments.ToArray()
            });
        }