diff --git a/source/Calamari.Tests/ArgoCD/Contracts/ArgoCDCustomPropertiesDtoSerializationTests.cs b/source/Calamari.Tests/ArgoCD/Contracts/ArgoCDCustomPropertiesDtoSerializationTests.cs
index 089b7d689..482bc4b90 100644
--- a/source/Calamari.Tests/ArgoCD/Contracts/ArgoCDCustomPropertiesDtoSerializationTests.cs
+++ b/source/Calamari.Tests/ArgoCD/Contracts/ArgoCDCustomPropertiesDtoSerializationTests.cs
@@ -10,8 +10,8 @@ namespace Calamari.Tests.ArgoCD.Contracts;
///
/// Deserialisation tests for with a focus on
/// the polymorphic array. The converter discriminates on
-/// the Type field emitted by Octopus Server (the concrete type name); a missing
-/// Type defaults to for backwards compatibility.
+/// the Type field emitted by Octopus Server; a missing Type defaults to
+/// for backwards compatibility.
///
[TestFixture]
public class ArgoCDCustomPropertiesDtoSerializationTests
@@ -27,7 +27,7 @@ static T DeserializeRaw(string json)
=> JsonConvert.DeserializeObject(json, Settings)!;
[Test]
- public void TypeGitCredentialDto_DeserializesAsHttpsCredential()
+ public void TypeUsernamePassword_DeserializesAsHttpsCredential()
{
const string json = """
{
@@ -52,6 +52,62 @@ public void TypeGitCredentialDto_DeserializesAsHttpsCredential()
credential.Password.Should().Be("pass");
}
+ [Test]
+ public void TypeSshKey_DeserializesAsSshCredential()
+ {
+ const string json = """
+ {
+ "Gateways": [], "Applications": [],
+ "Credentials": [
+ {
+ "Type": "SshKey",
+ "Url": "git@github.com:org/repo.git",
+ "Username": "git",
+ "PrivateKey": "-----BEGIN OPENSSH PRIVATE KEY-----"
+ }
+ ]
+ }
+ """;
+
+ var result = DeserializeRaw(json);
+
+ result.Credentials.Should().HaveCount(1);
+ var credential = result.Credentials[0].Should().BeOfType().Subject;
+ credential.Url.Should().Be("git@github.com:org/repo.git");
+ credential.Username.Should().Be("git");
+ credential.PrivateKey.Should().Be("-----BEGIN OPENSSH PRIVATE KEY-----");
+ }
+
+ [Test]
+ public void MixedArray_BothTypesPreserved()
+ {
+ const string json = """
+ {
+ "Gateways": [], "Applications": [],
+ "Credentials": [
+ {
+ "Type": "UsernamePassword",
+ "Url": "https://github.com/org/repo.git",
+ "Username": "user",
+ "Password": "pass"
+ },
+ {
+ "Type": "SshKey",
+ "Url": "git@github.com:org/other.git",
+ "Username": "git",
+ "PrivateKey": "-----BEGIN OPENSSH PRIVATE KEY-----"
+ }
+ ]
+ }
+ """;
+
+ var result = DeserializeRaw(json);
+
+ result.Credentials.Should().HaveCount(2);
+ result.Credentials[0].Should().BeOfType();
+ result.Credentials[1].Should().BeOfType();
+ }
+
[Test]
public void MissingType_DefaultsToHttpsCredential()
{
diff --git a/source/Calamari.Tests/ArgoCD/Git/AuthenticatingRepositoryFactoryTests.cs b/source/Calamari.Tests/ArgoCD/Git/AuthenticatingRepositoryFactoryTests.cs
index 520ac2305..e471ca16b 100644
--- a/source/Calamari.Tests/ArgoCD/Git/AuthenticatingRepositoryFactoryTests.cs
+++ b/source/Calamari.Tests/ArgoCD/Git/AuthenticatingRepositoryFactoryTests.cs
@@ -1,5 +1,4 @@
using System;
-using System.Collections.Generic;
using System.IO;
using Calamari.ArgoCD.Git;
using Calamari.ArgoCD.Git.PullRequests;
@@ -8,6 +7,7 @@
using Calamari.Testing.Helpers;
using Calamari.Tests.Fixtures.Integration.FileSystem;
using FluentAssertions;
+using NSubstitute;
using NUnit.Framework;
using Octopus.Calamari.Contracts.ArgoCD;
@@ -54,10 +54,7 @@ public void HttpsCredentialIsSelectedWhenUrlMatchesHttpsCredential()
{
var httpsUrl = RepositoryHelpers.ToFileUri(OriginPath);
var factory = new AuthenticatingRepositoryFactory(
- new Dictionary
- {
- [httpsUrl] = new GitCredentialDto(httpsUrl, "", "")
- },
+ [new GitCredentialDto(httpsUrl, "", "")],
repositoryFactory,
log);
@@ -70,7 +67,7 @@ public void AnonymousCloneWhenNoCredentialsMatch()
{
var originUrl = RepositoryHelpers.ToFileUri(OriginPath);
var factory = new AuthenticatingRepositoryFactory(
- new Dictionary(),
+ [],
repositoryFactory,
log);
@@ -80,6 +77,37 @@ public void AnonymousCloneWhenNoCredentialsMatch()
}
}
+ [TestFixture]
+ public class SshUrlTests : AuthenticatingRepositoryFactoryTestBase
+ {
+ [Test]
+ public void SshCredentialBranch_IsSelectedAndDispatchesSshKeyGitConnection()
+ {
+ // Use an ssh:// URL so the new strict validation allows it, and mock the factory
+ // so no real SSH connection is attempted.
+ const string sshUrl = "ssh://git@github.com/org/repo.git";
+ var mockRepoFactory = Substitute.For();
+
+ var factory = new AuthenticatingRepositoryFactory(
+ [new SshKeyGitCredentialDto(sshUrl, "git", "private-key", [])],
+ mockRepoFactory,
+ log);
+
+ factory.CloneRepository(sshUrl, branchName.ToFriendlyName());
+
+ mockRepoFactory.Received()
+ .CloneRepository(
+ Arg.Any(),
+ Arg.Is(c => c is SshKeyGitConnection));
+ }
+
+ [Test]
+ public void HttpsCredentialTakesPriorityOverSshWhenBothMatchAnSshUrl()
+ {
+ AssertHttpsCredentialTakesPriorityOverSsh("ssh://git@github.com/org/repo.git");
+ }
+ }
+
[TestFixture]
public class ScpStyleUrlTests : AuthenticatingRepositoryFactoryTestBase
{
@@ -91,10 +119,7 @@ public void ScpStyleUrlDoesNotMatchHttpsCredential()
var httpsUrl = "https://github.com/org/repo.git";
var factory = new AuthenticatingRepositoryFactory(
- new Dictionary
- {
- [httpsUrl] = new GitCredentialDto(httpsUrl, "user", "pass")
- },
+ [new GitCredentialDto(httpsUrl, "user", "pass")],
repositoryFactory,
log);
@@ -104,5 +129,32 @@ public void ScpStyleUrlDoesNotMatchHttpsCredential()
act.Should().Throw(); // clone failure expected
log.Messages.Should().Contain(m => m.FormattedMessage.Contains("No Git credentials found"));
}
+
+ [Test]
+ public void HttpsCredentialTakesPriorityOverSshWhenBothMatchAnScpUrl()
+ {
+ AssertHttpsCredentialTakesPriorityOverSsh("git@github.com:org/repo.git");
+ }
+ }
+
+ protected void AssertHttpsCredentialTakesPriorityOverSsh(string url)
+ {
+ var mockRepoFactory = Substitute.For();
+
+ // If there are HTTPS and SSH credentials for the same URL, HTTPS wins so API functionality works.
+ IGitCredentialDto[] rawCredentials =
+ [
+ new GitCredentialDto(url, "https-user", "https-pass"),
+ new SshKeyGitCredentialDto(url, "ssh-user", "private-key", [])
+ ];
+
+ var factory = new AuthenticatingRepositoryFactory(rawCredentials, mockRepoFactory, log);
+
+ factory.CloneRepository(url, "main");
+
+ mockRepoFactory.Received()
+ .CloneRepository(
+ Arg.Any(),
+ Arg.Is(c => c is HttpsGitConnection));
}
-}
+}
\ No newline at end of file
diff --git a/source/Calamari.Tests/ArgoCD/Git/PullRequests/GitVendorApiAdapter_PullRequestTests.cs b/source/Calamari.Tests/ArgoCD/Git/PullRequests/GitVendorApiAdapter_PullRequestTests.cs
index de0acc73f..1a170168c 100644
--- a/source/Calamari.Tests/ArgoCD/Git/PullRequests/GitVendorApiAdapter_PullRequestTests.cs
+++ b/source/Calamari.Tests/ArgoCD/Git/PullRequests/GitVendorApiAdapter_PullRequestTests.cs
@@ -104,6 +104,7 @@ async Task TestPullRequest(string repositoryUrl, string defaultBranch, string cl
using var temporaryFolder = TemporaryDirectory.Create();
CredentialsHandler credentialsHandler = (url, usernameFromUrl, types) => new UsernamePasswordCredentials { Username = cloneUsername, Password = clonePassword};
+ LibGit2SharpTransportRegistration.EnsureRegistered();
var repositoryPath = Repository.Clone(repositoryUrl, temporaryFolder.DirectoryPath, new CloneOptions()
{
FetchOptions =
diff --git a/source/Calamari.Tests/ArgoCD/Git/RepositoryFactoryTests.cs b/source/Calamari.Tests/ArgoCD/Git/RepositoryFactoryTests.cs
index 7daa4ea70..c624b86c8 100644
--- a/source/Calamari.Tests/ArgoCD/Git/RepositoryFactoryTests.cs
+++ b/source/Calamari.Tests/ArgoCD/Git/RepositoryFactoryTests.cs
@@ -5,6 +5,7 @@
using Calamari.ArgoCD.Git.PullRequests;
using Calamari.Common.Commands;
using Calamari.Common.Plumbing.FileSystem;
+using Calamari.Common.Plumbing.Logging;
using Calamari.Integration.Time;
using Calamari.Testing.Helpers;
using Calamari.Tests.Fixtures.Integration.FileSystem;
@@ -94,6 +95,33 @@ public void CanCloneAnExistingRepositoryAtHEADAndAssociatedFiles()
fileContent.Should().Be(originalContent);
}
+ [Test]
+ public void CloningSshKeyGitConnectionDoesNotResolveAPullRequestClientAndLogsVerboseMessage()
+ {
+ // Arrange
+ var filename = "sshTest.txt";
+ var content = "ssh test content";
+ CreateCommitOnOrigin(branchName, filename, content);
+
+ var mockResolver = Substitute.For();
+ var factoryWithMockedResolver = new RepositoryFactory(log, fileSystem, tempDirectory, mockResolver, new SystemClock());
+
+ var sshConnection = new SshKeyGitConnection(
+ username: "git",
+ privateKey: "private-key",
+ url: OriginPath,
+ gitReference: branchName);
+
+ // libgit2 skips credential callbacks for local file paths, so this test validates only pull-request-client resolution and verbose logging — not SSH credential validity.
+ // Act
+ factoryWithMockedResolver.CloneRepository("Clone_WithSshConnection", sshConnection);
+
+ mockResolver.DidNotReceive().TryResolve(Arg.Any(), Arg.Any(), Arg.Any());
+
+ log.MessagesVerboseFormatted
+ .Should().Contain(s => s.Contains("SSH authentication") && s.Contains("Git vendor functionality will not be available"));
+ }
+
void CreateCommitOnOrigin(GitBranchName branchName, string fileName, string content)
{
var message = $"Commit: Message";
diff --git a/source/Calamari.Tests/ArgoCD/Git/RepositoryHelpers.cs b/source/Calamari.Tests/ArgoCD/Git/RepositoryHelpers.cs
index ef78e21a2..8e321e6cd 100644
--- a/source/Calamari.Tests/ArgoCD/Git/RepositoryHelpers.cs
+++ b/source/Calamari.Tests/ArgoCD/Git/RepositoryHelpers.cs
@@ -10,6 +10,8 @@ public static class RepositoryHelpers
{
public static Repository CreateBareRepository(string repositoryPath)
{
+ LibGit2SharpTransportRegistration.EnsureRegistered();
+
Directory.CreateDirectory(repositoryPath);
Repository.Init(repositoryPath, isBare: true);
return new Repository(repositoryPath);
@@ -19,6 +21,8 @@ public static Repository CreateBareRepository(string repositoryPath)
public static void CreateBranchIn(GitBranchName branchName, string originPath)
{
+ LibGit2SharpTransportRegistration.EnsureRegistered();
+
var signature = new Signature("Your Name", "your.email@example.com", DateTimeOffset.Now);
var repository = new Repository(originPath);
@@ -47,6 +51,8 @@ public static void CreateBranchIn(GitBranchName branchName, string originPath)
public static string CloneOrigin(string tempDirectory, string originPath, GitBranchName branchName)
{
+ LibGit2SharpTransportRegistration.EnsureRegistered();
+
var subPath = Guid.NewGuid().ToString();
var resultPath = Path.Combine(tempDirectory, subPath);
Repository.Clone(originPath, resultPath);
diff --git a/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs b/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs
index 9e2862835..04fcc55a4 100644
--- a/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs
+++ b/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs
@@ -62,11 +62,7 @@ public void Install(RunningDeployment deployment)
var argoProperties = customPropertiesLoader.Load();
- // Takes the first git credential per URL, with a preference for username/password credentials (they are more broadly useful)
- var gitCredentials = argoProperties.Credentials
- .GroupBy(c => c.Url)
- .ToDictionary(g => g.Key, g => g.OfType().FirstOrDefault() ?? g.First());
- var authenticatingRepositoryFactory = new AuthenticatingRepositoryFactory(gitCredentials, repositoryFactory, log);
+ var authenticatingRepositoryFactory = new AuthenticatingRepositoryFactory(argoProperties.Credentials, repositoryFactory, log);
var deploymentScope = deployment.Variables.GetDeploymentScope();
log.LogApplicationCounts(deploymentScope, argoProperties.Applications);
diff --git a/source/Calamari/ArgoCD/Conventions/UpdateArgoCDApplicationManifestsInstallConvention.cs b/source/Calamari/ArgoCD/Conventions/UpdateArgoCDApplicationManifestsInstallConvention.cs
index f51fb53ed..00de91420 100644
--- a/source/Calamari/ArgoCD/Conventions/UpdateArgoCDApplicationManifestsInstallConvention.cs
+++ b/source/Calamari/ArgoCD/Conventions/UpdateArgoCDApplicationManifestsInstallConvention.cs
@@ -69,11 +69,7 @@ public void Install(RunningDeployment deployment)
var argoProperties = customPropertiesLoader.Load();
- // Takes the first git credential per URL, with a preference for username/password credentials (they are more broadly useful)
- var gitCredentials = argoProperties.Credentials
- .GroupBy(c => c.Url)
- .ToDictionary(g => g.Key, g => g.OfType().FirstOrDefault() ?? g.First());
- var authenticatingRepositoryFactory = new AuthenticatingRepositoryFactory(gitCredentials, repositoryFactory, log);
+ var authenticatingRepositoryFactory = new AuthenticatingRepositoryFactory(argoProperties.Credentials, repositoryFactory, log);
var deploymentScope = deployment.Variables.GetDeploymentScope();
log.LogApplicationCounts(deploymentScope, argoProperties.Applications);
diff --git a/source/Calamari/ArgoCD/Git/AuthenticatingRepositoryFactory.cs b/source/Calamari/ArgoCD/Git/AuthenticatingRepositoryFactory.cs
index 716be8a57..4fab8845a 100644
--- a/source/Calamari/ArgoCD/Git/AuthenticatingRepositoryFactory.cs
+++ b/source/Calamari/ArgoCD/Git/AuthenticatingRepositoryFactory.cs
@@ -1,4 +1,6 @@
+using System;
using System.Collections.Generic;
+using System.Linq;
using Calamari.Common.Plumbing.Logging;
using Octopus.Calamari.Contracts.ArgoCD;
@@ -11,11 +13,15 @@ public class AuthenticatingRepositoryFactory
readonly ILog log;
public AuthenticatingRepositoryFactory(
- Dictionary gitCredentials,
+ IReadOnlyCollection gitCredentials,
IRepositoryFactory repositoryFactory,
ILog log)
{
- this.gitCredentials = gitCredentials;
+ // Takes the first git credential per URL, with a preference for username/password credentials (they are more broadly useful as they can be used for PR creation)
+ this.gitCredentials = gitCredentials
+ .GroupBy(c => c.Url)
+ .ToDictionary(g => g.Key, g => g.OfType().FirstOrDefault() ?? g.First());
+
this.repositoryFactory = repositoryFactory;
this.log = log;
}
@@ -23,13 +29,34 @@ public AuthenticatingRepositoryFactory(
public RepositoryWrapper CloneRepository(string requestedUrl, string targetRevision)
{
var gitCredential = gitCredentials.GetValueOrDefault(requestedUrl);
- if (gitCredential is GitCredentialDto passwordCredential)
+ switch (gitCredential)
{
- var gitConnection = new HttpsGitConnection(passwordCredential.Username, passwordCredential.Password, GitCloneSafeUrl.ConvertToUriString(requestedUrl), GitReference.CreateFromString(targetRevision));
- return repositoryFactory.CloneRepository(UniqueRepoNameGenerator.Generate(), gitConnection);
+ case GitCredentialDto passwordCredential:
+ {
+ var gitConnection = new HttpsGitConnection(passwordCredential.Username, passwordCredential.Password, GitCloneSafeUrl.ConvertToUriString(requestedUrl), GitReference.CreateFromString(targetRevision));
+ return repositoryFactory.CloneRepository(UniqueRepoNameGenerator.Generate(), gitConnection);
+ }
+ case SshKeyGitCredentialDto sshCredential:
+ {
+ var sshConnection = new SshKeyGitConnection(
+ sshCredential.Username,
+ sshCredential.PrivateKey,
+ requestedUrl,
+ GitReference.CreateFromString(targetRevision));
+ return repositoryFactory.CloneRepository(UniqueRepoNameGenerator.Generate(), sshConnection);
+ }
+ case null:
+ {
+ log.Info($"No Git credentials found for: '{requestedUrl}', will attempt to clone repository anonymously.");
+ break;
+ }
+ default:
+ {
+ log.Warn($"An unrecognised credential type '{gitCredential.GetType().Name}' was found for '{requestedUrl}'. Ignoring the credentials and attempting an anonymous clone.");
+ break;
+ }
}
- log.Info($"No Git credentials found for: '{requestedUrl}', will attempt to clone repository anonymously.");
var anonGitConnection = new HttpsGitConnection(null, null, GitCloneSafeUrl.ConvertToUriString(requestedUrl), GitReference.CreateFromString(targetRevision));
return repositoryFactory.CloneRepository(UniqueRepoNameGenerator.Generate(), anonGitConnection);
}
diff --git a/source/Calamari/ArgoCD/Git/GitConnection.cs b/source/Calamari/ArgoCD/Git/GitConnection.cs
index 6c1b1ac68..5c8511274 100644
--- a/source/Calamari/ArgoCD/Git/GitConnection.cs
+++ b/source/Calamari/ArgoCD/Git/GitConnection.cs
@@ -56,4 +56,24 @@ static Uri ParseAsHttpsUri(string repositoryUrl)
return uri;
}
}
+
+ public class SshKeyGitConnection : IGitConnection
+ {
+ public SshKeyGitConnection(
+ string? username,
+ string privateKey,
+ string url,
+ GitReference gitReference)
+ {
+ Username = username;
+ PrivateKey = privateKey;
+ Url = url;
+ GitReference = gitReference;
+ }
+
+ public string? Username { get; }
+ public string PrivateKey { get; }
+ public string Url { get; }
+ public GitReference GitReference { get; }
+ }
}
\ No newline at end of file
diff --git a/source/Calamari/ArgoCD/Git/IGitCredentialDtoJsonConverter.cs b/source/Calamari/ArgoCD/Git/IGitCredentialDtoJsonConverter.cs
index b26babda6..735af842a 100644
--- a/source/Calamari/ArgoCD/Git/IGitCredentialDtoJsonConverter.cs
+++ b/source/Calamari/ArgoCD/Git/IGitCredentialDtoJsonConverter.cs
@@ -32,6 +32,7 @@ public override IGitCredentialDto ReadJson(
return type switch
{
null or GitCredentialDto.DiscriminatorValue => obj.ToObject(ConcreteSerializer)!,
+ SshKeyGitCredentialDto.DiscriminatorValue => obj.ToObject(ConcreteSerializer)!,
_ => throw new JsonSerializationException($"Unrecognised credential Type '{type}'.")
};
}
diff --git a/source/Calamari/ArgoCD/Git/LibGit2SharpCredentialsHandlerExtensionMethods.cs b/source/Calamari/ArgoCD/Git/LibGit2SharpCredentialsHandlerExtensionMethods.cs
new file mode 100644
index 000000000..3296b5435
--- /dev/null
+++ b/source/Calamari/ArgoCD/Git/LibGit2SharpCredentialsHandlerExtensionMethods.cs
@@ -0,0 +1,70 @@
+using System;
+using System.Net;
+using LibGit2Sharp;
+using LibGit2Sharp.Handlers;
+
+namespace Calamari.ArgoCD.Git;
+
+public static class LibGit2SharpCredentialsHandlerExtensionMethods
+{
+ public static CredentialsHandler ToLibGit2SharpCredentialHandler(this IGitConnection? connection)
+ {
+ return connection switch
+ {
+ HttpsGitConnection { Username: null, Password: null } => Anonymous(),
+ HttpsGitConnection https => UsernamePassword(https),
+ SshKeyGitConnection sshKey => SshKey(sshKey),
+ null => Anonymous(),
+ _ => throw new NotSupportedException(),
+ };
+ }
+
+ public static CertificateCheckHandler? ToLibGit2SharpCertificateCheckHandler(this IGitConnection? connection)
+ {
+ return connection switch
+ {
+ SshKeyGitConnection sshKey => SshHostKeyVerificationBypass.AcceptAll,
+ _ => null
+ };
+ }
+
+ static CredentialsHandler Anonymous()
+ {
+ return null!; // A null CredentialsHandler is valid for LibGit2Sharp
+ }
+
+ static CredentialsHandler SshKey(SshKeyGitConnection connection)
+ {
+ return (_, userFromUrl, types) =>
+ {
+ if (!types.HasFlag(SupportedCredentialTypes.SshMemory))
+ {
+ throw new InvalidOperationException("SSH key credentials provided but are not supported by this endpoint.");
+ }
+
+ return new SshKeyMemoryCredentials
+ {
+ Username = connection.Username ?? userFromUrl,
+ PrivateKey = connection.PrivateKey,
+ };
+ };
+ }
+
+ static CredentialsHandler UsernamePassword(HttpsGitConnection connection)
+ {
+ return (_, _, types) =>
+ {
+ if (!types.HasFlag(SupportedCredentialTypes.UsernamePassword))
+ {
+ throw new InvalidOperationException("Username/password credentials provided but are not supported by this endpoint.");
+ }
+
+ var securePassword = new NetworkCredential(string.Empty, connection.Password).SecurePassword;
+ return new SecureUsernamePasswordCredentials
+ {
+ Username = connection.Username,
+ Password = securePassword,
+ };
+ };
+ }
+}
\ No newline at end of file
diff --git a/source/Calamari/ArgoCD/Git/RepositoryFactory.cs b/source/Calamari/ArgoCD/Git/RepositoryFactory.cs
index 8a1d72b38..382d5a3f9 100644
--- a/source/Calamari/ArgoCD/Git/RepositoryFactory.cs
+++ b/source/Calamari/ArgoCD/Git/RepositoryFactory.cs
@@ -66,14 +66,8 @@ RepositoryWrapper CheckoutGitRepository(IGitConnection gitConnection, string che
BranchName = (gitConnection.GitReference as GitBranchName)?.ToFriendlyName()
};
- if (gitConnection is HttpsGitConnection { Username: not null, Password: not null } https)
- {
- options.FetchOptions.CredentialsProvider = (_, _, _) => new UsernamePasswordCredentials
- {
- Username = https.Username,
- Password = https.Password
- };
- }
+ options.FetchOptions.CredentialsProvider = gitConnection.ToLibGit2SharpCredentialHandler();
+ options.FetchOptions.CertificateCheck = gitConnection.ToLibGit2SharpCertificateCheckHandler();
string repoPath;
log.InfoFormat("Cloning repository {0}", log.FormatLink(gitConnection.Url));
@@ -122,6 +116,11 @@ RepositoryWrapper CheckoutGitRepository(IGitConnection gitConnection, string che
? gitVendorPullRequestClientResolver.TryResolve(httpsGitConnection, log, CancellationToken.None).Result
: null;
+ if (gitConnection is SshKeyGitConnection)
+ {
+ log.Verbose("Git is using SSH authentication, Git vendor functionality will not be available");
+ }
+
return new RepositoryWrapper(repo,
fileSystem,
checkoutPath,
diff --git a/source/Calamari/ArgoCD/Git/RepositoryWrapper.cs b/source/Calamari/ArgoCD/Git/RepositoryWrapper.cs
index 88d870b82..7e1c0c577 100644
--- a/source/Calamari/ArgoCD/Git/RepositoryWrapper.cs
+++ b/source/Calamari/ArgoCD/Git/RepositoryWrapper.cs
@@ -38,12 +38,6 @@ public class RepositoryWrapper(
public string WorkingDirectory => repository.Info.WorkingDirectory;
- Credentials RepositoryCredentials => connection switch
- {
- HttpsGitConnection https => new UsernamePasswordCredentials { Username = https.Username, Password = https.Password },
- _ => null
- };
-
// returns true if changes were made to the repository
public bool CommitChanges(string summary, string description)
{
@@ -197,8 +191,9 @@ public void PushChanges(GitBranchName branchName)
PushStatusError? errorsDetected = null;
var pushOptions = new PushOptions
{
- CredentialsProvider = (url, usernameFromUrl, types) => RepositoryCredentials,
- OnPushStatusError = errors => errorsDetected = errors
+ CredentialsProvider = connection.ToLibGit2SharpCredentialHandler(),
+ OnPushStatusError = errors => errorsDetected = errors,
+ CertificateCheck = connection.ToLibGit2SharpCertificateCheckHandler()
};
repository.Network.Push(repository.Head, pushOptions);
@@ -214,7 +209,8 @@ void FetchAndRebase(GitBranchName branchName)
var refSpecs = remote.FetchRefSpecs.Select(x => x.Specification).ToList();
var fetchOptions = new FetchOptions
{
- CredentialsProvider = (url, usernameFromUrl, types) => RepositoryCredentials
+ CredentialsProvider = connection.ToLibGit2SharpCredentialHandler(),
+ CertificateCheck = connection.ToLibGit2SharpCertificateCheckHandler()
};
try
diff --git a/source/Calamari/ArgoCD/Git/SshHostKeyVerificationBypass.cs b/source/Calamari/ArgoCD/Git/SshHostKeyVerificationBypass.cs
new file mode 100644
index 000000000..3723cc778
--- /dev/null
+++ b/source/Calamari/ArgoCD/Git/SshHostKeyVerificationBypass.cs
@@ -0,0 +1,7 @@
+namespace Calamari.ArgoCD.Git;
+
+static class SshHostKeyVerificationBypass
+{
+ // TODO(eddy): Implement proper host key verification
+ public static readonly LibGit2Sharp.Handlers.CertificateCheckHandler AcceptAll = (cert, valid, host) => true;
+}
diff --git a/source/Calamari/CommitToGit/CommitToGitConfigFactory.cs b/source/Calamari/CommitToGit/CommitToGitConfigFactory.cs
index 6b437fe33..30b3f2da9 100644
--- a/source/Calamari/CommitToGit/CommitToGitConfigFactory.cs
+++ b/source/Calamari/CommitToGit/CommitToGitConfigFactory.cs
@@ -36,11 +36,12 @@ public CommitToGitRepositorySettings CreateRepositoryConfig(RunningDeployment de
var properties = customPropertiesLoader.Load();
- var connection = properties.GitCredential switch
- {
- UsernamePasswordGitCredentialDto usernamePassword => new HttpsGitConnection(usernamePassword.Username, usernamePassword.Password, uriAsString, GitReference.CreateFromString(gitReferenceAsString)),
- _ => throw new NotSupportedException("Commit-To-Git only supports the use of username/password. Please select a username/password based credential in your step configuration."),
- };
+ IGitConnection connection = properties.GitCredential switch
+ {
+ UsernamePasswordGitCredentialDto usernamePassword => new HttpsGitConnection(usernamePassword.Username, usernamePassword.Password, uriAsString, GitReference.CreateFromString(gitReferenceAsString)),
+ SshKeyGitCredentialDto ssh => new SshKeyGitConnection(ssh.Username, ssh.PrivateKey, uriAsString, GitReference.CreateFromString(gitReferenceAsString)),
+ _ => throw new NotSupportedException($"An unrecognised credential type '{properties.GitCredential.GetType().Name}' was found for '{uriAsString}'"),
+ };
return new CommitToGitRepositorySettings(connection,
commitParameters,