예제 #1
0
        public static void SetOutputVariable(this IVariables variables, string name, string?value)
        {
            variables.Set(name, value);

            // And set the output-variables.
            // Assuming we are running in a step named 'DeployWeb' and are setting a variable named 'Foo'
            // then we will set Octopus.Action[DeployWeb].Output.Foo
            var actionName = variables.Get(ActionVariables.Name);

            if (string.IsNullOrWhiteSpace(actionName))
            {
                return;
            }

            var actionScopedVariable = ActionVariables.GetOutputVariableName(actionName, name);

            variables.Set(actionScopedVariable, value);

            // And if we are on a machine named 'Web01'
            // Then we will set Octopus.Action[DeployWeb].Output[Web01].Foo
            var machineName = variables.Get(MachineVariables.Name);

            if (string.IsNullOrWhiteSpace(machineName))
            {
                return;
            }

            var machineIndexedVariableName = ActionVariables.GetMachineIndexedOutputVariableName(actionName, machineName, name);

            variables.Set(machineIndexedVariableName, value);
        }
예제 #2
0
        static void UpdateConfigurationSettings(XContainer configurationFile, IVariables variables)
        {
            Log.Verbose("Updating configuration settings...");
            var foundSettings = false;

            WithConfigurationSettings(configurationFile, (roleName, settingName, settingValueAttribute) =>
            {
                var setting = variables.Get(roleName + "/" + settingName) ??
                              variables.Get(roleName + "\\" + settingName) ??
                              variables.Get(settingName) ??
                              (variables.GetNames().Contains(settingName) ? "" : null);

                if (setting != null)
                {
                    foundSettings = true;
                    Log.Info("Updating setting for role {0}: {1} = {2}", roleName, settingName, setting);
                    settingValueAttribute.Value = setting;
                }
            });

            if (!foundSettings)
            {
                Log.Info("No settings that match provided variables were found.");
            }
        }
예제 #3
0
        void WriteVariableScriptToFile()
        {
            if (!TryGetScriptFromVariables(out var scriptBody, out var relativeScriptFile, out var scriptSyntax) &&
                !WasProvided(variables.Get(ScriptVariables.ScriptFileName)))
            {
                throw new CommandException($"Could not determine script to run.  Please provide either a `{ScriptVariables.ScriptBody}` variable, " +
                                           $"or a `{ScriptVariables.ScriptFileName}` variable.");
            }

            if (WasProvided(scriptBody))
            {
                var scriptFile = Path.GetFullPath(relativeScriptFile);

                //Set the name of the script we are about to create to the variables collection for replacement later on
                variables.Set(ScriptVariables.ScriptFileName, relativeScriptFile);

                // If the script body was supplied via a variable, then we write it out to a file.
                // This will be deleted with the working directory.
                // Bash files need SheBang as first few characters. This does not play well with BOM characters
                var scriptBytes = scriptSyntax == ScriptSyntax.Bash
                    ? scriptBody.EncodeInUtf8NoBom()
                    : scriptBody.EncodeInUtf8Bom();
                File.WriteAllBytes(scriptFile, scriptBytes);
            }
        }
예제 #4
0
        CommandResult ExecuteCommandInternal(string[] arguments, out string result, bool outputToCalamariConsole)
        {
            var environmentVar = defaultEnvironmentVariables;

            if (environmentVariables != null)
            {
                environmentVar.AddRange(environmentVariables);
            }

            var terraformExecutable = variables.Get(TerraformSpecialVariables.Action.Terraform.CustomTerraformExecutable) ??
                                      $"terraform{(CalamariEnvironment.IsRunningOnWindows ? ".exe" : String.Empty)}";
            var captureOutput         = new CaptureInvocationOutputSink();
            var commandLineInvocation = new CommandLineInvocation(terraformExecutable, arguments)
            {
                WorkingDirectory = templateDirectory,
                EnvironmentVars  = environmentVar,
                OutputToLog      = outputToCalamariConsole,
                AdditionalInvocationOutputSink = captureOutput
            };

            log.Info(commandLineInvocation.ToString());

            var commandResult = commandLineRunner.Execute(commandLineInvocation);

            result = String.Join("\n", captureOutput.Infos);

            return(commandResult);
        }
예제 #5
0
        public X509Certificate2 GetOrAdd(IVariables variables, string certificateVariable, StoreName storeName, StoreLocation storeLocation = StoreLocation.CurrentUser)
        {
            var pfxBytes   = Convert.FromBase64String(variables.Get($"{certificateVariable}.{CertificateVariables.Properties.Pfx}"));
            var thumbprint = variables.Get($"{certificateVariable}.{CertificateVariables.Properties.Thumbprint}");
            var password   = variables.Get($"{certificateVariable}.{CertificateVariables.Properties.Password}");

            return(GetOrAdd(thumbprint, pfxBytes, password, new X509Store(storeName, storeLocation)));
        }
예제 #6
0
        public static bool IsPackageRetentionEnabled(this IVariables variables)
        {
            bool.TryParse(variables.Get(KnownVariables.Calamari.EnablePackageRetention, bool.FalseString), out var retentionEnabled);

            var tentacleHome = variables.Get(TentacleVariables.Agent.TentacleHome);
            var packageRetentionJournalPath = variables.Get(KnownVariables.Calamari.PackageRetentionJournalPath);

            return(retentionEnabled && (!string.IsNullOrWhiteSpace(packageRetentionJournalPath) || !string.IsNullOrWhiteSpace(tentacleHome)));
        }
 public void ShouldAddVariablesIfPreviousInstallation()
 {
     previous = new JournalEntry("123", "tenant", "env", "proj", "rp01", DateTime.Now, "C:\\App", "C:\\MyApp", false,
                                 new List <DeployedPackage> {
         new DeployedPackage("pkg", "0.0.9", "C:\\PackageOld.nupkg")
     });
     DeploymentJournalVariableContributor.Previous(variables, journal, "123");
     Assert.That(variables.Get(TentacleVariables.PreviousInstallation.OriginalInstalledPath), Is.EqualTo("C:\\App"));
 }
예제 #8
0
        public bool IsEnabled(ScriptSyntax syntax)
        {
            if (String.IsNullOrEmpty(variables.Get(ScriptFunctionsVariables.Registration)))
            {
                return(false);
            }

            return(codeGenFunctionsRegistry.SupportedScriptSyntax.Contains(syntax));
        }
예제 #9
0
        void UpdateConfigurationWithCurrentInstanceCount(XContainer localConfigurationFile, string configurationFileName, IVariables variables)
        {
            if (!variables.GetFlag(SpecialVariables.Action.Azure.UseCurrentInstanceCount))
            {
                return;
            }

            var serviceName = variables.Get(SpecialVariables.Action.Azure.CloudServiceName);
            var slot        = (DeploymentSlot)Enum.Parse(typeof(DeploymentSlot), variables.Get(SpecialVariables.Action.Azure.Slot));

            var remoteConfigurationFile = configurationRetriever.GetConfiguration(
                certificateStore,
                account,
                serviceName,
                slot);

            if (remoteConfigurationFile == null)
            {
                Log.Info("There is no current deployment of service '{0}' in slot '{1}', so existing instance counts will not be imported.", serviceName, slot);
                return;
            }

            var rolesByCount = new Dictionary <string, string>(StringComparer.OrdinalIgnoreCase);

            Log.Verbose("Local instance counts (from " + Path.GetFileName(configurationFileName) + "): ");
            WithInstanceCounts(localConfigurationFile, (roleName, attribute) =>
            {
                Log.Verbose(" - " + roleName + " = " + attribute.Value);

                string value;
                if (rolesByCount.TryGetValue(roleName, out value))
                {
                    attribute.SetValue(value);
                }
            });

            Log.Verbose("Remote instance counts: ");
            WithInstanceCounts(remoteConfigurationFile, (roleName, attribute) =>
            {
                rolesByCount[roleName] = attribute.Value;
                Log.Verbose(" - " + roleName + " = " + attribute.Value);
            });

            Log.Verbose("Replacing local instance count settings with remote settings: ");
            WithInstanceCounts(localConfigurationFile, (roleName, attribute) =>
            {
                string value;
                if (!rolesByCount.TryGetValue(roleName, out value))
                {
                    return;
                }

                attribute.SetValue(value);
                Log.Verbose(" - " + roleName + " = " + attribute.Value);
            });
        }
예제 #10
0
        public SubscriptionCloudCredentials GetCredentials(IVariables variables)
        {
            var subscriptionId        = variables.Get(SpecialVariables.Action.Azure.SubscriptionId);
            var certificateThumbprint = variables.Get(SpecialVariables.Action.Azure.CertificateThumbprint);
            var certificateBytes      = Convert.FromBase64String(variables.Get(SpecialVariables.Action.Azure.CertificateBytes));

            var certificate = certificateStore.GetOrAdd(certificateThumbprint, certificateBytes);

            return(new CertificateCloudCredentials(subscriptionId, certificate));
        }
예제 #11
0
        public int ExecuteHealthCheck()
        {
            var account = new AzureServicePrincipalAccount(variables);

            var resourceGroupName = variables.Get(SpecialVariables.Action.Azure.ResourceGroupName);
            var webAppName        = variables.Get(SpecialVariables.Action.Azure.WebAppName);

            ConfirmWebAppExists(account, resourceGroupName, webAppName);

            return(0);
        }
예제 #12
0
        Task <WebDeployPublishSettings> GetPublishProfile(IVariables variables)
        {
            var account         = new AzureServicePrincipalAccount(variables);
            var siteAndSlotName = variables.Get(SpecialVariables.Action.Azure.WebAppName);
            var slotName        = variables.Get(SpecialVariables.Action.Azure.WebAppSlot);
            var targetSite      = AzureWebAppHelper.GetAzureTargetSite(siteAndSlotName, slotName);

            return(resourceManagerPublishProfileProvider.GetPublishProperties(account,
                                                                              variables.Get(SpecialVariables.Action.Azure.ResourceGroupName, string.Empty),
                                                                              targetSite));
        }
예제 #13
0
        public void ShouldSkipIfInstalled()
        {
            variables.Set(SpecialVariables.Package.SkipIfAlreadyInstalled, true.ToString());
            previous = new JournalEntry("123", "tenant", "env", "proj", "rp01", DateTime.Now, "C:\\App", "C:\\MyApp", true,
                                        new List <DeployedPackage> {
                new DeployedPackage("pkg", "0.0.9", "C:\\PackageOld.nupkg")
            });

            RunConvention();

            Assert.That(variables.Get(KnownVariables.Action.SkipJournal), Is.EqualTo("true"));
        }
        public X509Certificate2 GetOrAdd(IVariables variables, string certificateVariable, StoreName storeName, StoreLocation storeLocation = StoreLocation.CurrentUser)
        {
            var pfxBytes   = Convert.FromBase64String(variables.Get($"{certificateVariable}.{CertificateVariables.Properties.Pfx}"));
            var thumbprint = variables.Get($"{certificateVariable}.{CertificateVariables.Properties.Thumbprint}");

            if (string.IsNullOrWhiteSpace(thumbprint))
            {
                throw new InvalidOperationException("Certificate thumbprint was not found in variables");
            }
            var password = variables.Get($"{certificateVariable}.{CertificateVariables.Properties.Password}");

            return(GetOrAdd(thumbprint, pfxBytes, password, new X509Store(storeName, storeLocation)));
        }
예제 #15
0
        public string GetJournalPath()
        {
            var packageRetentionJournalPath = variables.Get(KnownVariables.Calamari.PackageRetentionJournalPath);

            if (packageRetentionJournalPath != null)
            {
                return(packageRetentionJournalPath);
            }

            var tentacleHome = variables.Get(TentacleVariables.Agent.TentacleHome) ?? string.Empty;

            return(Path.Combine(tentacleHome, DefaultJournalName));
        }
        private string CreateAzureCertificate(string workingDirectory, IVariables variables)
        {
            var certificateFilePath = Path.Combine(workingDirectory, CertificateFileName);
            var certificatePassword = GenerateCertificatePassword();
            var azureCertificate    = certificateStore.GetOrAdd(
                variables.Get(SpecialVariables.Action.Azure.CertificateThumbprint),
                Convert.FromBase64String(variables.Get(SpecialVariables.Action.Azure.CertificateBytes)),
                StoreName.My);

            variables.Set("OctopusAzureCertificateFileName", certificateFilePath);
            variables.Set("OctopusAzureCertificatePassword", certificatePassword);

            fileSystem.WriteAllBytes(certificateFilePath, azureCertificate.Export(X509ContentType.Pfx, certificatePassword));
            return(certificateFilePath);
        }
        string GetReleaseName(IVariables variables)
        {
            var validChars  = new Regex("[^a-zA-Z0-9-]");
            var releaseName = variables.Get(SpecialVariables.Helm.ReleaseName)?.ToLower();

            if (string.IsNullOrWhiteSpace(releaseName))
            {
                releaseName = $"{variables.Get(ActionVariables.Name)}-{variables.Get(DeploymentEnvironment.Name)}";
                releaseName = validChars.Replace(releaseName, "").ToLowerInvariant();
            }

            log.SetOutputVariable("ReleaseName", releaseName, variables);
            log.Info($"Using Release Name {releaseName}");
            return(releaseName);
        }
예제 #18
0
        static string WritePatternMatching(IVariables variables)
        {
            var builder = new StringBuilder();

            foreach (var variableName in variables.GetNames())
            {
                var variableValue = variables.Get(variableName);
                if (variableValue == null)
                {
                    builder.AppendFormat("        | \"{0}\" -> Some null", EncodeValue(variableName));
                }
                else
                {
                    builder.AppendFormat("        | \"{0}\" -> {1} |> Some",
                                         EncodeValue(variableName),
                                         EncryptVariable(variableValue));
                }

                builder.Append(Environment.NewLine);
            }

            builder.Append("        | _ -> None");

            return(builder.ToString());
        }
예제 #19
0
        public void LogVariables()
        {
            string ToString(bool useRawValue)
            {
                var text = new StringBuilder();

                var namesToPrint = variables.GetNames().Where(name => !name.Contains("CustomScripts.")).OrderBy(name => name);

                foreach (var name in namesToPrint)
                {
                    var value = useRawValue ? variables.GetRaw(name) : variables.Get(name);
                    text.AppendLine($"[{name}] = '{value}'");
                }

                return(text.ToString());
            }

            if (variables.GetFlag(KnownVariables.PrintVariables))
            {
                log.Warn($"{KnownVariables.PrintVariables} is enabled. This should only be used for debugging problems with variables, and then disabled again for normal deployments.");
                log.Verbose("The following variables are available:" + Environment.NewLine + ToString(true));
            }

            if (variables.GetFlag(KnownVariables.PrintEvaluatedVariables))
            {
                log.Warn($"{KnownVariables.PrintEvaluatedVariables} is enabled. This should only be used for debugging problems with variables, and then disabled again for normal deployments.");
                log.Verbose("The following evaluated variables are available:" + Environment.NewLine + ToString(false));
            }
        }
예제 #20
0
        protected override IEnumerable <ScriptExecution> PrepareExecution(Script script,
                                                                          IVariables variables,
                                                                          Dictionary <string, string> environmentVars = null)
        {
            var powerShellBootstrapper = GetPowerShellBootstrapper(variables);

            var(bootstrapFile, otherTemporaryFiles) = powerShellBootstrapper.PrepareBootstrapFile(script, variables);
            var debuggingBootstrapFile = powerShellBootstrapper.PrepareDebuggingBootstrapFile(script);

            var executable = powerShellBootstrapper.PathToPowerShellExecutable(variables);
            var arguments  = powerShellBootstrapper.FormatCommandArguments(bootstrapFile, debuggingBootstrapFile, variables);

            var invocation = new CommandLineInvocation(executable, arguments)
            {
                EnvironmentVars  = environmentVars,
                WorkingDirectory = Path.GetDirectoryName(script.File),
                UserName         = powerShellBootstrapper.AllowImpersonation() ? variables.Get(PowerShellVariables.UserName) : null,
                Password         = powerShellBootstrapper.AllowImpersonation() ? ToSecureString(variables.Get(PowerShellVariables.Password)) : null
            };

            return(new[]
            {
                new ScriptExecution(
                    invocation,
                    otherTemporaryFiles.Concat(new[] { bootstrapFile, debuggingBootstrapFile })
                    )
            });
        }
예제 #21
0
        string GetTomcatVersion(IVariables variables)
        {
            var catalinaHome = variables.Get(SpecialVariables.Action.Java.TomcatDeployCertificate.CatalinaHome) ??
                               Environment.GetEnvironmentVariable("CATALINA_HOME");;
            var catalinaPath = Path.Combine(catalinaHome, "lib", "catalina.jar");

            if (!File.Exists(catalinaPath))
            {
                throw new CommandException("TOMCAT-HTTPS-ERROR-0018: " +
                                           $"Failed to find the file {catalinaPath} " +
                                           "http://g.octopushq.com/JavaAppDeploy#tomcat-https-error-0018");
            }

            var version      = new StringBuilder();
            var versionCheck = SilentProcessRunner.ExecuteCommand(JavaRuntime.CmdPath,
                                                                  $"-cp \"{catalinaPath}\" org.apache.catalina.util.ServerInfo", ".",
                                                                  (stdOut) =>
            {
                Log.Verbose(stdOut);
                version.AppendLine(stdOut);
            },
                                                                  Console.Error.WriteLine);

            if (versionCheck.ExitCode != 0)
            {
                throw new CommandException($"Attempt to obtain tomcat version failed with exit code {versionCheck.ExitCode}.");
            }
            return(version.ToString());
        }
        static IEnumerable<string> ReplaceStronglyTypeApplicationSetting(XNode document, string xpath, string keyAttributeName, string keyAttributeValue, IVariables variables)
        {
            var changes = new List<string>();

            var settings = (
                from element in document.XPathSelectElements(xpath)
                let keyAttribute = element.Attribute(keyAttributeName)
                where keyAttribute != null
                where string.Equals(keyAttribute.Value, keyAttributeValue, StringComparison.OrdinalIgnoreCase)
                select element).ToList();

            if (settings.Count == 0)
                return changes;

            var value = variables.Get(keyAttributeValue) ?? string.Empty;

            foreach (var setting in settings)
            {
                changes.Add($"Setting '{keyAttributeValue}' = '{value}'");

                var valueElement = setting.Elements().FirstOrDefault(e => e.Name.LocalName == "value");
                if (valueElement == null)
                {
                    setting.Add(new XElement("value", value));
                }
                else
                {
                    valueElement.SetValue(value);
                }
            }

            return changes;
        }
예제 #23
0
        public void Update(IVariables variables)
        {
            bool VariableNameIsNotASystemVariable(string v)
            {
                if (v.StartsWith("Octopus", StringComparison.OrdinalIgnoreCase))
                {
                    // Only include variables starting with 'Octopus'
                    // if it also has a colon (:)
                    if (v.StartsWith("Octopus:", StringComparison.OrdinalIgnoreCase))
                    {
                        return(map.ContainsKey(v));
                    }
                    else
                    {
                        return(false);
                    }
                }
                return(map.ContainsKey(v));
            }

            foreach (var name in variables.GetNames().Where(VariableNameIsNotASystemVariable))
            {
                try
                {
                    map[name](variables.Get(name));
                }
                catch (Exception e)
                {
                    Log.WarnFormat("Unable to set value for {0}. The following error occurred: {1}", name, e.Message);
                }
            }
        }
 static void SetOutputVariable(string name, string value, IVariables variables)
 {
     if (variables.Get(name) != value)
     {
         Log.SetOutputVariable(name, value, variables);
     }
 }
예제 #25
0
        public override int Execute(string[] commandLineArguments)
        {
            Options.Parse(commandLineArguments);

            var contents = variables.Get(SpecialVariables.Execution.Manifest);

            if (contents == null)
            {
                throw new CommandException("Execution manifest not found in variables.");
            }

            var instructions = JsonConvert.DeserializeObject <Instruction[]>(contents, JsonSerialization.GetDefaultSerializerSettings());

            if (instructions.Length == 0)
            {
                throw new CommandException("The execution manifest must have at least one instruction.");
            }

            foreach (var instruction in instructions)
            {
                var tool = executionTools.First(x => x.Metadata.Tool == instruction.Launcher);

                var result = tool.Value.Execute(instruction.LauncherInstructionsRaw, commandLineArguments.Skip(1).ToArray());

                if (result != 0)
                {
                    return(result);
                }
            }

            return(0);
        }
예제 #26
0
        public void Update(IVariables variables)
        {
            bool VariableNameIsMappedPath(string v)
            {
                if (v.StartsWith("Octopus", StringComparison.OrdinalIgnoreCase) &&
                    !v.StartsWith("Octopus:", StringComparison.OrdinalIgnoreCase))
                {
                    // Only include variables starting with 'Octopus'
                    // if it also has a colon (:)
                    return(false);
                }
                return(map.ContainsKey(v));
            }

            var replaced = 0;

            foreach (var name in variables.GetNames().Where(VariableNameIsMappedPath))
            {
                try
                {
                    log.Verbose(StructuredConfigMessages.StructureFound(name));
                    replaced++;
                    map[name](variables.Get(name));
                }
                catch (Exception e)
                {
                    log.WarnFormat("Unable to set value for {0}. The following error occurred: {1}", name, e.Message);
                }
            }

            if (replaced == 0)
            {
                log.Info(StructuredConfigMessages.NoStructuresFound);
            }
        }
예제 #27
0
        static IEnumerable <string> ReplaceAppSettingOrConnectionString(XNode document, string xpath, string keyAttributeName, string keyAttributeValue, string valueAttributeName, IVariables variables)
        {
            var changes  = new List <string>();
            var settings = (
                from element in document.XPathSelectElements(xpath)
                let keyAttribute = element.Attribute(keyAttributeName)
                                   where keyAttribute != null
                                   where string.Equals(keyAttribute.Value, keyAttributeValue, StringComparison.OrdinalIgnoreCase)
                                   select element).ToList();

            if (settings.Count == 0)
            {
                return(changes);
            }

            var value = variables.Get(keyAttributeValue) ?? string.Empty;

            foreach (var setting in settings)
            {
                changes.Add(string.Format("Setting '{0}' = '{1}'", keyAttributeValue, value));

                var valueAttribute = setting.Attribute(valueAttributeName);
                if (valueAttribute == null)
                {
                    setting.Add(new XAttribute(valueAttributeName, value));
                }
                else
                {
                    valueAttribute.SetValue(value);
                }
            }

            return(changes);
        }
        ITopLevelExpression TryReplaceValue(ITopLevelExpression expr, IVariables variables)
        {
            switch (expr)
            {
            case KeyValuePairExpression pair:
                var logicalName = pair.Key?.Text?.LogicalValue ?? "";
                if (!IsOctopusVariableName(logicalName) && variables.IsSet(logicalName))
                {
                    log.Verbose(StructuredConfigMessages.StructureFound(logicalName));

                    var logicalValue = variables.Get(logicalName);
                    var encodedValue = Encode.Value(logicalValue);
                    var newValueExpr = new ValueExpression(new StringValue(logicalValue, encodedValue));

                    // In cases where a key was specified with neither separator nor value
                    // we have to add a separator, otherwise the value becomes part of the key.
                    var separator = pair.Separator ?? new SeparatorExpression(":");
                    return(new KeyValuePairExpression(pair.Key, separator, newValueExpr));
                }
                else
                {
                    return(expr);
                }

            default:
                return(expr);
            }
        }
        static string[] WriteScriptModules(IVariables variables, string parentDirectory, StringBuilder output)
        {
            var scriptModules = new List <string>();

            foreach (var variableName in variables.GetNames().Where(ScriptVariables.IsLibraryScriptModule))
            {
                if (ScriptVariables.GetLibraryScriptModuleLanguage(variables, variableName) == ScriptSyntax.PowerShell)
                {
                    var libraryScriptModuleName = ScriptVariables.GetLibraryScriptModuleName(variableName);
                    var name           = "Library_" + new string(libraryScriptModuleName.Where(char.IsLetterOrDigit).ToArray()) + "_" + DateTime.Now.Ticks;
                    var moduleFileName = $"{name}.psm1";
                    var moduleFilePath = Path.Combine(parentDirectory, moduleFileName);
                    Log.VerboseFormat("Writing script module '{0}' as PowerShell module {1}. This module will be automatically imported - functions will automatically be in scope.", libraryScriptModuleName, moduleFileName, name);
                    var contents = variables.Get(variableName);
                    if (contents == null)
                    {
                        throw new InvalidOperationException($"Value for variable {variableName} could not be found.");
                    }
                    CalamariFileSystem.OverwriteFile(moduleFilePath, contents, Encoding.UTF8);
                    output.AppendLine($"Import-ScriptModule '{libraryScriptModuleName.EscapeSingleQuotedString()}' '{moduleFilePath.EscapeSingleQuotedString()}'");
                    output.AppendLine();
                    scriptModules.Add(moduleFilePath);
                }
            }

            return(scriptModules.ToArray());
        }
        TimeSpan GetHttpTimeout()
        {
            const string expectedTimespanFormat = "c";

            // Equal to Timeout.InfiniteTimeSpan, which isn't available in net40
            var defaultTimeout = new TimeSpan(0, 0, 0, 0, -1);

            var rawTimeout = variables.Get(KnownVariables.NugetHttpTimeout);

            if (string.IsNullOrWhiteSpace(rawTimeout))
            {
                return(defaultTimeout);
            }

            if (TimeSpan.TryParseExact(rawTimeout, expectedTimespanFormat, null, out var parsedTimeout))
            {
                return(parsedTimeout);
            }

            var exampleTimespan = new TimeSpan(0, 0, 1, 0).ToString(expectedTimespanFormat);

            var message = $"The variable {KnownVariables.NugetHttpTimeout} couldn't be parsed as a timespan. " +
                          $"Expected a value like '{exampleTimespan}' but received '{rawTimeout}'. " +
                          $"Defaulting to '{defaultTimeout.ToString(expectedTimespanFormat)}'.";

            Log.Warn(message);
            return(defaultTimeout);
        }