private (string workingDirectory, bool isWorkspaceContained) GetTarWorkingDirectory(string[] segments, string workspaceRoot)
        {
            // If path segment is single directory outside of Pipeline.Workspace extract tarball directly to this path
            if (segments.Count() == 1)
            {
                var workingDirectory = segments[0];
                if (FingerprintCreator.IsPathySegment(workingDirectory) && !workingDirectory.StartsWith(workspaceRoot))
                {
                    return(workingDirectory, false);
                }
            }

            // All other scenarios means that paths must within and relative to Pipeline.Workspace
            return(workspaceRoot.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar), true);
        }
        public async virtual Task RunAsync(AgentTaskPluginExecutionContext context, CancellationToken token)
        {
            ArgUtil.NotNull(context, nameof(context));

            VariableValue saltValue = context.Variables.GetValueOrDefault(SaltVariableName);
            string        salt      = saltValue?.Value ?? string.Empty;

            VariableValue workspaceRootValue = context.Variables.GetValueOrDefault("pipeline.workspace");
            string        workspaceRoot      = workspaceRootValue?.Value;

            string key = context.GetInput(PipelineCacheTaskPluginConstants.Key, required: true);
            string restoreKeysBlock = context.GetInput(PipelineCacheTaskPluginConstants.RestoreKeys, required: false);
            // TODO: Translate path from container to host (Ting)
            string path = context.GetInput(PipelineCacheTaskPluginConstants.Path, required: true);

            (bool isOldFormat, string[] keySegments, IEnumerable <string[]> restoreKeys, string[] pathSegments) = ParseIntoSegments(salt, key, restoreKeysBlock, path);

            if (isOldFormat)
            {
                context.Warning(OldKeyFormatMessage);
            }

            context.Output("Resolving key:");
            Fingerprint keyFp = FingerprintCreator.EvaluateToFingerprint(context, workspaceRoot, keySegments, FingerprintType.Key);

            context.Output($"Resolved to: {keyFp}");

            Func <Fingerprint[]> restoreKeysGenerator = () =>
                                                        restoreKeys.Select(restoreKey =>
            {
                context.Output("Resolving restore key:");
                Fingerprint f = FingerprintCreator.EvaluateToFingerprint(context, workspaceRoot, restoreKey, FingerprintType.Key);
                f.Segments    = f.Segments.Concat(new[] { Fingerprint.Wildcard }).ToArray();
                context.Output($"Resolved to: {f}");
                return(f);
            }).ToArray();


            await ProcessCommandInternalAsync(
                context,
                keyFp,
                restoreKeysGenerator,
                pathSegments,
                workspaceRoot,
                token);
        }
        internal async Task UploadAsync(
            AgentTaskPluginExecutionContext context,
            Fingerprint keyFingerprint,
            string[] pathSegments,
            string workspaceRoot,
            CancellationToken cancellationToken,
            ContentFormat contentFormat)
        {
            VssConnection               connection = context.VssConnection;
            BlobStoreClientTelemetry    clientTelemetry;
            DedupManifestArtifactClient dedupManifestClient = DedupManifestArtifactClientFactory.Instance.CreateDedupManifestClient(context, connection, cancellationToken, out clientTelemetry);
            PipelineCacheClient         pipelineCacheClient = this.CreateClient(clientTelemetry, context, connection);

            using (clientTelemetry)
            {
                // Check if the key exists.
                PipelineCacheActionRecord cacheRecordGet = clientTelemetry.CreateRecord <PipelineCacheActionRecord>((level, uri, type) =>
                                                                                                                    new PipelineCacheActionRecord(level, uri, type, PipelineArtifactConstants.RestoreCache, context));
                PipelineCacheArtifact getResult = await pipelineCacheClient.GetPipelineCacheArtifactAsync(new[] { keyFingerprint }, cancellationToken, cacheRecordGet);

                // Send results to CustomerIntelligence
                context.PublishTelemetry(area: PipelineArtifactConstants.AzurePipelinesAgent, feature: PipelineArtifactConstants.PipelineCache, record: cacheRecordGet);
                //If cache exists, return.
                if (getResult != null)
                {
                    context.Output($"Cache with fingerprint `{getResult.Fingerprint}` already exists.");
                    return;
                }

                context.Output("Resolving path:");
                Fingerprint pathFp = FingerprintCreator.EvaluateToFingerprint(context, workspaceRoot, pathSegments, FingerprintType.Path);
                context.Output($"Resolved to: {pathFp}");

                string uploadPath = await this.GetUploadPathAsync(contentFormat, context, pathFp, pathSegments, workspaceRoot, cancellationToken);

                //Upload the pipeline artifact.
                PipelineCacheActionRecord uploadRecord = clientTelemetry.CreateRecord <PipelineCacheActionRecord>((level, uri, type) =>
                                                                                                                  new PipelineCacheActionRecord(level, uri, type, nameof(dedupManifestClient.PublishAsync), context));

                PublishResult result = await clientTelemetry.MeasureActionAsync(
                    record : uploadRecord,
                    actionAsync : async() =>
                    await AsyncHttpRetryHelper.InvokeAsync(
                        async() =>
                {
                    return(await dedupManifestClient.PublishAsync(uploadPath, cancellationToken));
                },
                        maxRetries: 3,
                        tracer: tracer,
                        canRetryDelegate: e => true,     // this isn't great, but failing on upload stinks, so just try a couple of times
                        cancellationToken: cancellationToken,
                        continueOnCapturedContext: false)
                    );

                CreatePipelineCacheArtifactContract options = new CreatePipelineCacheArtifactContract
                {
                    Fingerprint   = keyFingerprint,
                    RootId        = result.RootId,
                    ManifestId    = result.ManifestId,
                    ProofNodes    = result.ProofNodes.ToArray(),
                    ContentFormat = contentFormat.ToString(),
                };

                // delete archive file if it's tar.
                if (contentFormat == ContentFormat.SingleTar)
                {
                    try
                    {
                        if (File.Exists(uploadPath))
                        {
                            File.Delete(uploadPath);
                        }
                    }
                    catch { }
                }

                // Cache the artifact
                PipelineCacheActionRecord cacheRecord = clientTelemetry.CreateRecord <PipelineCacheActionRecord>((level, uri, type) =>
                                                                                                                 new PipelineCacheActionRecord(level, uri, type, PipelineArtifactConstants.SaveCache, context));
                CreateStatus status = await pipelineCacheClient.CreatePipelineCacheArtifactAsync(options, cancellationToken, cacheRecord);

                // Send results to CustomerIntelligence
                context.PublishTelemetry(area: PipelineArtifactConstants.AzurePipelinesAgent, feature: PipelineArtifactConstants.PipelineCache, record: uploadRecord);
                context.PublishTelemetry(area: PipelineArtifactConstants.AzurePipelinesAgent, feature: PipelineArtifactConstants.PipelineCache, record: cacheRecord);
                context.Output("Saved item.");
            }
        }