From 2201959117aa5a120939eaccd57748f344fc011d Mon Sep 17 00:00:00 2001 From: Paulo Gomes Date: Sun, 10 Apr 2022 19:54:09 +0100 Subject: [PATCH 1/3] libgit2: implement single-use SSH connections Public bitbucket.org accounts do not support concurrent connections, making the controller to stumble upon itself with long-running connections, specially if more than one GitRepository is defined. To avoid such problems, bitbucket is now handled as single-use connections, which seems to improve its stability, although may still generate the occasional ssh.Dial TimeOut when a new connection takes places whilst an another is still established. Signed-off-by: Paulo Gomes --- pkg/git/libgit2/managed/const.go | 13 ++++++++++ pkg/git/libgit2/managed/ssh.go | 43 ++++++++++++++++++++------------ pkg/git/libgit2/managed/util.go | 29 +++++++++++++++++++++ 3 files changed, 69 insertions(+), 16 deletions(-) create mode 100644 pkg/git/libgit2/managed/util.go diff --git a/pkg/git/libgit2/managed/const.go b/pkg/git/libgit2/managed/const.go index f41035da7..129c9fe93 100644 --- a/pkg/git/libgit2/managed/const.go +++ b/pkg/git/libgit2/managed/const.go @@ -25,3 +25,16 @@ const ( // when cloning Git repositories via SSH. PathMaxLength = 4096 ) + +var ( + // denyConcurrentConnections is a list of servers (:) + // that should use single-use connections. + // + // Some servers do not support concurrent connections + // (e.g. public bitbucket.org accounts) and may struggle with + // multiple sessions within the same connection. Avoid such problems + // by closing the connection as soon as they are no longer needed. + denyConcurrentConnections = []string{ + "bitbucket.org:22", + } +) diff --git a/pkg/git/libgit2/managed/ssh.go b/pkg/git/libgit2/managed/ssh.go index a36ac1660..400237627 100644 --- a/pkg/git/libgit2/managed/ssh.go +++ b/pkg/git/libgit2/managed/ssh.go @@ -103,6 +103,7 @@ type sshSmartSubtransport struct { currentStream *sshSmartSubtransportStream ckey string addr string + singleUse bool } // aMux is the read-write mutex to control access to sshClients. @@ -305,26 +306,28 @@ func (t *sshSmartSubtransport) createConn(ckey, addr string, sshConfig *ssh.Clie } t.client = c + t.singleUse = !cacheableConnection(addr) - // Mutex is set here to avoid the network latency being - // absorbed by all competing goroutines. - aMux.Lock() - defer aMux.Unlock() + if !t.singleUse { + // Mutex is set here to avoid the network latency being + // absorbed by all competing goroutines. + aMux.Lock() + defer aMux.Unlock() - // A different goroutine won the race, dispose the connection - // and carry on. - if _, ok := sshClients[ckey]; ok { - go func() { - _ = c.Close() - }() - return nil - } + // A different goroutine won the race, dispose the connection + // and carry on. + if _, ok := sshClients[ckey]; ok { + go func() { + _ = c.Close() + }() + return nil + } - sshClients[ckey] = &cachedClient{ - Client: c, - activeSessions: 1, + sshClients[ckey] = &cachedClient{ + Client: c, + activeSessions: 1, + } } - return nil } @@ -379,6 +382,10 @@ func (stream *sshSmartSubtransportStream) Free() { if stream.owner.ckey != "" { decrementActiveSessionIfFound(stream.owner.ckey) } + + if stream.owner.singleUse && stream.owner.client != nil { + _ = stream.owner.client.Close() + } } func cacheKeyAndConfig(remoteAddress string, cred *git2go.Credential) (string, *ssh.ClientConfig, error) { @@ -487,3 +494,7 @@ func decrementActiveSessionIfFound(key string) { v.activeSessions-- } } + +func cacheableConnection(addr string) bool { + return !Contains(denyConcurrentConnections, addr) +} diff --git a/pkg/git/libgit2/managed/util.go b/pkg/git/libgit2/managed/util.go new file mode 100644 index 000000000..fed0c282a --- /dev/null +++ b/pkg/git/libgit2/managed/util.go @@ -0,0 +1,29 @@ +/* +Copyright 2022 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package managed + +import "strings" + +func Contains(list []string, value string) bool { + for _, v := range list { + if v == value { + return true + } + } + return false +} + From 8fbe96c8a98da8b5fcd9640e3c3f568eb02e15f7 Mon Sep 17 00:00:00 2001 From: Paulo Gomes Date: Sun, 10 Apr 2022 21:18:47 +0100 Subject: [PATCH 2/3] libgit2: enable per repository managed transport opt-in/out Managed transport now defines its own protocols, providing the flexibility of opt-in/out per repository. The feature switch now controls the auto upgrade from normal protocols (http, https and ssh) into managed protocols (http+managed, https+managed and ssh+managed). The use of the feature switch matches the previous behaviour, with the difference that now users are allowed to try Managed Transport for a connection at a time, regardless of the feature switch status. Signed-off-by: Paulo Gomes --- controllers/gitrepository_controller.go | 15 +++- main.go | 5 +- pkg/git/libgit2/managed/const.go | 44 ++++++++++ pkg/git/libgit2/managed/flag.go | 19 ++++- pkg/git/libgit2/managed/http.go | 4 +- pkg/git/libgit2/managed/managed_test.go | 11 ++- pkg/git/libgit2/managed/protocol.go | 89 ++++++++++++++++++++ pkg/git/libgit2/managed/protocol_test.go | 102 +++++++++++++++++++++++ pkg/git/libgit2/managed/ssh.go | 16 ++-- pkg/git/libgit2/managed/util.go | 8 ++ 10 files changed, 289 insertions(+), 24 deletions(-) create mode 100644 pkg/git/libgit2/managed/protocol.go create mode 100644 pkg/git/libgit2/managed/protocol_test.go diff --git a/controllers/gitrepository_controller.go b/controllers/gitrepository_controller.go index 37f6c42ad..d3cbdc84e 100644 --- a/controllers/gitrepository_controller.go +++ b/controllers/gitrepository_controller.go @@ -419,21 +419,28 @@ func (r *GitRepositoryReconciler) reconcileSource(ctx context.Context, } repositoryURL := obj.Spec.URL - // managed GIT transport only affects the libgit2 implementation - if managed.Enabled() && obj.Spec.GitImplementation == sourcev1.LibGit2Implementation { + // Managed Transport only affects the libgit2 implementation. + if obj.Spec.GitImplementation == sourcev1.LibGit2Implementation { + // Ensures the correct protocol is being used, taking into + // account opt-in/opt-out and auto upgrade settings. + repositoryURL = managed.EnsureProtocol(repositoryURL) + // At present only HTTP connections have the ability to define remote options. // Although this can be easily extended by ensuring that the fake URL below uses the // target ssh scheme, and the libgit2/managed/ssh.go pulls that information accordingly. // // This is due to the fact the key libgit2 remote callbacks do not take place for HTTP // whilst most still work for SSH. - if strings.HasPrefix(repositoryURL, "http") { + if strings.HasPrefix(repositoryURL, managed.HTTPManagedProtocol) || + strings.HasPrefix(repositoryURL, managed.HTTPSManagedProtocol) { // Due to the lack of the callback feature, a fake target URL is created to allow // for the smart sub transport be able to pick the options specific for this // GitRepository object. // The URL should use unique information that do not collide in a multi tenant // deployment. - repositoryURL = fmt.Sprintf("http://%s/%s/%d", obj.Name, obj.UID, obj.Generation) + repositoryURL = fmt.Sprintf("%s://%s/%s/%d", + managed.HTTPManagedProtocol, obj.Name, obj.UID, obj.Generation) + managed.AddTransportOptions(repositoryURL, managed.TransportOptions{ TargetURL: obj.Spec.URL, diff --git a/main.go b/main.go index 186577a62..2989d46a7 100644 --- a/main.go +++ b/main.go @@ -268,9 +268,8 @@ func main() { startFileServer(storage.BasePath, storageAddr, setupLog) }() - if managed.Enabled() { - managed.InitManagedTransport(ctrl.Log.WithName("managed-transport")) - } + // registers three new Git protocols: http+managed, https+managed and ssh+managed. + managed.InitManagedTransport(ctrl.Log.WithName("managed-transport")) setupLog.Info("starting manager") if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { diff --git a/pkg/git/libgit2/managed/const.go b/pkg/git/libgit2/managed/const.go index 129c9fe93..e0dff1653 100644 --- a/pkg/git/libgit2/managed/const.go +++ b/pkg/git/libgit2/managed/const.go @@ -24,9 +24,53 @@ const ( // PathMaxLength represents the max length for the path element // when cloning Git repositories via SSH. PathMaxLength = 4096 + + // HTTPProtocol represents the HTTP protocol. + HTTPProtocol string = "http" + + // HTTPSProtocol represents the HTTPS protocol. + HTTPSProtocol string = "https" + + // SSHProtocol represents the SSH protocol. + SSHProtocol string = "ssh" + + // SSHGitProtocol represents the ssh+git protocol. + SSHGitProtocol string = "ssh+git" + + // GitSSHProtocol represents the git+ssh protocol. + GitSSHProtocol string = "git+ssh" + + // HTTPManagedProtocol represents the HTTP managed protocol. + // It can be used to opt-in HTTP URLs over Managed Transport. + HTTPManagedProtocol string = "http+managed" + + // HTTPUnmanagedProtocol represents the HTTP unmanaged protocol. + // It can be used to opt-out HTTP URLs over Managed Transport. + HTTPUnmanagedProtocol string = "http+unmanaged" + + // HTTPSManagedProtocol represents the HTTPS managed protocol. + // It can be used to opt-in HTTPS URLs over Managed Transport. + HTTPSManagedProtocol string = "https+managed" + + // HTTPSUnmanagedProtocol represents the HTTPS unmanaged protocol. + // It can be used to opt-out HTTPS URLs over Managed Transport. + HTTPSUnmanagedProtocol string = "https+unmanaged" + + // SSHManagedProtocol represents the SSH managed protocol. + // It can be used to opt-in SSH URLs over Managed Transport. + SSHManagedProtocol string = "ssh+managed" + + // SSHUnmanagedProtocol represents the SSH unmanaged protocol. + // It can be used to opt-out SSH URLs over Managed Transport. + SSHUnmanagedProtocol string = "ssh+unmanaged" ) var ( + // denySSHAutoUpgradeDomains is a list of domains that cannot be + // supported in managed transport via SSH. + denySSHAutoUpgradeDomains = []string{ + } + // denyConcurrentConnections is a list of servers (:) // that should use single-use connections. // diff --git a/pkg/git/libgit2/managed/flag.go b/pkg/git/libgit2/managed/flag.go index 2905c7719..feeebf5ad 100644 --- a/pkg/git/libgit2/managed/flag.go +++ b/pkg/git/libgit2/managed/flag.go @@ -21,14 +21,25 @@ import ( "strings" ) -// Enabled defines whether the use of Managed Transport should be enabled. -// This is only affects git operations that uses libgit2 implementation. +var autoUpgradeEnabled bool = false + +// Enabled defines whether all SSH and HTTP connections should be automatically +// upgraded to Managed Transport. +// This only affects git operations that uses libgit2 implementation. +// +// A per connection opt-in/opt-out can be achieved by using the specific protocols: +// - opt-in: ssh+managed://git@server:22/repo-path +// - opt-out: ssh+unmanaged://git@server:22/repo-path +// +// If a connection opted-in for Managed Transport, it won't be affected by the value +// of Enabled. // // True is returned when the environment variable `EXPERIMENTAL_GIT_TRANSPORT` // is detected with the value of `true` or `1`. func Enabled() bool { if v, ok := os.LookupEnv("EXPERIMENTAL_GIT_TRANSPORT"); ok { - return strings.ToLower(v) == "true" || v == "1" + autoUpgradeEnabled = strings.ToLower(v) == "true" || v == "1" } - return false + + return autoUpgradeEnabled } diff --git a/pkg/git/libgit2/managed/http.go b/pkg/git/libgit2/managed/http.go index 09c0ee26a..0eab004e8 100644 --- a/pkg/git/libgit2/managed/http.go +++ b/pkg/git/libgit2/managed/http.go @@ -62,7 +62,7 @@ import ( // HTTP(S) transport that doesn't rely on any lower-level libraries // such as OpenSSL. func registerManagedHTTP() error { - for _, protocol := range []string{"http", "https"} { + for _, protocol := range []string{HTTPManagedProtocol, HTTPSManagedProtocol} { _, err := git2go.NewRegisteredSmartTransport(protocol, true, httpSmartSubtransportFactory) if err != nil { return fmt.Errorf("failed to register transport for %q: %v", protocol, err) @@ -127,7 +127,7 @@ func (t *httpSmartSubtransport) Action(targetUrl string, action git2go.SmartServ // golang will change POST to GET in case of redirects. if len(via) >= 0 && req.Method != via[0].Method { - if via[0].URL.Scheme == "https" && req.URL.Scheme == "http" { + if via[0].URL.Scheme == HTTPSProtocol && req.URL.Scheme == HTTPProtocol { return fmt.Errorf("downgrade from https to http is not allowed: from %q to %q", via[0].URL.String(), req.URL.String()) } if via[0].URL.Host != req.URL.Host { diff --git a/pkg/git/libgit2/managed/managed_test.go b/pkg/git/libgit2/managed/managed_test.go index 14c473852..a015f6a71 100644 --- a/pkg/git/libgit2/managed/managed_test.go +++ b/pkg/git/libgit2/managed/managed_test.go @@ -263,9 +263,8 @@ func TestManagedTransport_E2E(t *testing.T) { // Test HTTP transport - // Use a fake-url and force it to be overriden by the smart transport. - // This was the way found to ensure that the built-in transport was not used. - httpAddress := "http://fake-url" + // Use a fake-url over managed protocol, and force the URL to be overriden by the smart transport. + httpAddress := "http+managed://fake-url" AddTransportOptions(httpAddress, TransportOptions{ TargetURL: server.HTTPAddress() + "/" + repoPath, }) @@ -293,6 +292,12 @@ func TestManagedTransport_E2E(t *testing.T) { // Test SSH transport sshAddress := server.SSHAddress() + "/" + repoPath + + // Force auto upgrade by enabling it and ensuring protocol + autoUpgradeEnabled = true + sshAddress = EnsureProtocol(sshAddress) + g.Expect(sshAddress).To(HavePrefix(SSHManagedProtocol)) + repo, err = git2go.Clone(sshAddress, tmpDir2, &git2go.CloneOptions{ FetchOptions: git2go.FetchOptions{ RemoteCallbacks: git2go.RemoteCallbacks{ diff --git a/pkg/git/libgit2/managed/protocol.go b/pkg/git/libgit2/managed/protocol.go new file mode 100644 index 000000000..4da7647f5 --- /dev/null +++ b/pkg/git/libgit2/managed/protocol.go @@ -0,0 +1,89 @@ +/* +Copyright 2022 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package managed + +import ( + "net/url" +) + +// EnsureProtocol returns targetURL with the correct SSH or HTTP protocol. +// +// It takes into account whether targetURL includes opt-in protocols +// ssh+unmanaged or ssh+managed. For automatic upgrade, it must be +// enabled at controller level and the target domain must not be in +// the deny list. +// +// If targetURL is not a valid URL, itself is returned. +func EnsureProtocol(targetURL string) string { + u, err := url.Parse(targetURL) + if err != nil { + return targetURL + } + + // opt-in unmanaged SSH, change scheme to ssh:// + if u.Scheme == SSHUnmanagedProtocol { + u.Scheme = SSHProtocol + return u.String() + } + // opt-in managed SSH + if u.Scheme == SSHManagedProtocol { + return u.String() + } + // ignore other SSH protocols (i.e. ssh+git / git+ssh) + if u.Scheme == SSHGitProtocol || u.Scheme == GitSSHProtocol { + return u.String() + } + // auto upgrade to managed transport + if u.Scheme == SSHProtocol && upgradeToManagedSSH(u.Hostname()) { + u.Scheme = SSHManagedProtocol + return u.String() + } + + // opt-in unmanaged HTTP, change scheme to http:// + if u.Scheme == HTTPUnmanagedProtocol { + u.Scheme = HTTPProtocol + return u.String() + } + // opt-in unmanaged HTTPS, change scheme to https:// + if u.Scheme == HTTPSUnmanagedProtocol { + u.Scheme = HTTPSProtocol + return u.String() + } + // opt-in managed HTTP + if u.Scheme == HTTPManagedProtocol { + return u.String() + } + // opt-in managed HTTPS + if u.Scheme == HTTPSManagedProtocol { + return u.String() + } + // auto upgrade to managed transport + if Enabled() && u.Scheme == HTTPProtocol { + u.Scheme = HTTPManagedProtocol + return u.String() + } + if Enabled() && u.Scheme == HTTPSProtocol { + u.Scheme = HTTPSManagedProtocol + return u.String() + } + + return u.String() +} + +func upgradeToManagedSSH(hostName string) bool { + return Enabled() && !HasAnySuffix(denySSHAutoUpgradeDomains, hostName) +} diff --git a/pkg/git/libgit2/managed/protocol_test.go b/pkg/git/libgit2/managed/protocol_test.go new file mode 100644 index 000000000..0734f71e4 --- /dev/null +++ b/pkg/git/libgit2/managed/protocol_test.go @@ -0,0 +1,102 @@ +/* +Copyright 2022 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package managed + +import ( + "testing" + + . "github.com/onsi/gomega" +) + +func Test_EnsureProtocol(t *testing.T) { + g := NewWithT(t) + + tests := []struct { + name string + in string + expected string + autoUpgrade bool + }{ + { + name: "ssh+unmanaged is changed to ssh if autoUpgrade enabled", + in: "ssh+unmanaged://git@server:22/repo-path", + expected: "ssh://git@server:22/repo-path", + }, + { + name: "auto upgrade to ssh+managed if feature enabled", + in: "ssh://git@github.com:22/user-name/repo-name", + expected: "ssh+managed://git@github.com:22/user-name/repo-name", + autoUpgrade: true, + }, + { + name: "do not auto upgrade if feature is disabled", + in: "ssh://git@github.com:22/user-name/repo-name", + expected: "ssh://git@github.com:22/user-name/repo-name", + }, + { + name: "ssh+managed is ignored", + in: "ssh+managed://git@server:22/repo-path", + expected: "ssh+managed://git@server:22/repo-path", + }, + { + name: "ssh+git is ignored", + in: "ssh+git://git@server:22/repo-path", + expected: "ssh+git://git@server:22/repo-path", + }, + { + name: "git+ssh is ignored", + in: "git+ssh://git@server:22/repo-path", + expected: "git+ssh://git@server:22/repo-path", + }, + { + name: "HTTP is ignored if feature disabled", + in: "http://server/repo-path", + expected: "http://server/repo-path", + }, + { + name: "HTTP is upgraded if feature enabled", + in: "http://server/repo-path", + expected: "http+managed://server/repo-path", + autoUpgrade: true, + }, + { + name: "HTTPS is ignored if feature disabled", + in: "https://server/repo-path", + expected: "https://server/repo-path", + }, + { + name: "HTTPS is upgraded if feature enabled", + in: "https://server/repo-path", + expected: "https+managed://server/repo-path", + autoUpgrade: true, + }, + { + name: "empty string is ignored", + in: "", + expected: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + autoUpgradeEnabled = tt.autoUpgrade + + actual := EnsureProtocol(tt.in) + g.Expect(actual).To(Equal(tt.expected)) + }) + } +} diff --git a/pkg/git/libgit2/managed/ssh.go b/pkg/git/libgit2/managed/ssh.go index 400237627..66424ed1a 100644 --- a/pkg/git/libgit2/managed/ssh.go +++ b/pkg/git/libgit2/managed/ssh.go @@ -62,22 +62,22 @@ import ( git2go "github.com/libgit2/git2go/v33" ) -// registerManagedSSH registers a Go-native implementation of -// SSH transport that doesn't rely on any lower-level libraries -// such as libssh2. +// registerManagedSSH registers a new Go-native protocol ssh+managed://. +// It implementation doesn't rely on any lower-level libraries such as libssh2. +// The built-in protocols that rely on libssh2 are kept intact,under protocols: +// "ssh://", "ssh+git://" and "git+ssh://". // -// The underlying SSH connections are kept open and are reused -// across several SSH sessions. This is due to upstream issues in +// When using ssh+managed://, the underlying SSH connections are kept open and +// are reused across several SSH sessions. This is due to upstream issues in // which concurrent/parallel SSH connections may lead to instability. // // Connections are created on first attempt to use a given remote. The -// connection is removed from the cache on the first failed session related -// operation. +// connection is removed from the cache at the first failed SSH session. // // https://github.com/golang/go/issues/51926 // https://github.com/golang/go/issues/27140 func registerManagedSSH() error { - for _, protocol := range []string{"ssh", "ssh+git", "git+ssh"} { + for _, protocol := range []string{SSHManagedProtocol} { _, err := git2go.NewRegisteredSmartTransport(protocol, false, sshSmartSubtransportFactory) if err != nil { return fmt.Errorf("failed to register transport for %q: %v", protocol, err) diff --git a/pkg/git/libgit2/managed/util.go b/pkg/git/libgit2/managed/util.go index fed0c282a..edfb526ec 100644 --- a/pkg/git/libgit2/managed/util.go +++ b/pkg/git/libgit2/managed/util.go @@ -27,3 +27,11 @@ func Contains(list []string, value string) bool { return false } +func HasAnySuffix(suffixList []string, value string) bool { + for _, suffix := range suffixList { + if strings.HasSuffix(value, suffix) { + return true + } + } + return false +} From e29a3a56d6b05ae583a5c419cd0b212005c1717c Mon Sep 17 00:00:00 2001 From: Paulo Gomes Date: Sun, 10 Apr 2022 21:24:27 +0100 Subject: [PATCH 3/3] libgit2: deny Azure DevOps repos from using Managed Transport DevOps requires the Git protocol capabilities (e.g. multi_ack and multi_ack_detailed) that are not fully supported by libgit2/git2go in managed transport mode. This disables the auto upgrade feature for Azure DevOps repositories, but still allow for opt-in in case users want to force the use of Managed Transport. Signed-off-by: Paulo Gomes --- pkg/git/libgit2/managed/const.go | 4 ++ pkg/git/libgit2/managed/protocol_test.go | 60 ++++++++++++++++++------ 2 files changed, 50 insertions(+), 14 deletions(-) diff --git a/pkg/git/libgit2/managed/const.go b/pkg/git/libgit2/managed/const.go index e0dff1653..a3ffe7146 100644 --- a/pkg/git/libgit2/managed/const.go +++ b/pkg/git/libgit2/managed/const.go @@ -69,6 +69,10 @@ var ( // denySSHAutoUpgradeDomains is a list of domains that cannot be // supported in managed transport via SSH. denySSHAutoUpgradeDomains = []string{ + // DevOps requires the Git protocol capabilities (e.g. multi_ack + // and multi_ack_detailed) that are not fully supported by libgit2/git2go + // in managed transport mode. + "dev.azure.com", } // denyConcurrentConnections is a list of servers (:) diff --git a/pkg/git/libgit2/managed/protocol_test.go b/pkg/git/libgit2/managed/protocol_test.go index 0734f71e4..ddc09f2c4 100644 --- a/pkg/git/libgit2/managed/protocol_test.go +++ b/pkg/git/libgit2/managed/protocol_test.go @@ -32,54 +32,86 @@ func Test_EnsureProtocol(t *testing.T) { autoUpgrade bool }{ { - name: "ssh+unmanaged is changed to ssh if autoUpgrade enabled", + name: "azure devops via SSH is not upgraded to ssh+managed when feature enabled", + in: "ssh://git@ssh.dev.azure.com:22/v3/org-name/project-name/repo-name", + expected: "ssh://git@ssh.dev.azure.com:22/v3/org-name/project-name/repo-name", + autoUpgrade: true, + }, + { + name: "azure devops via SSH is not upgraded to ssh+managed when feature disabled", + in: "ssh://git@ssh.dev.azure.com:22/v3/org-name/project-name/repo-name", + expected: "ssh://git@ssh.dev.azure.com:22/v3/org-name/project-name/repo-name", + autoUpgrade: false, + }, + { + name: "ssh+unmanaged is changed to ssh when autoUpgrade enabled", in: "ssh+unmanaged://git@server:22/repo-path", expected: "ssh://git@server:22/repo-path", }, { - name: "auto upgrade to ssh+managed if feature enabled", + name: "auto upgrade to ssh+managed when feature enabled", in: "ssh://git@github.com:22/user-name/repo-name", expected: "ssh+managed://git@github.com:22/user-name/repo-name", autoUpgrade: true, }, { - name: "do not auto upgrade if feature is disabled", + name: "do not auto upgrade when feature is disabled", in: "ssh://git@github.com:22/user-name/repo-name", expected: "ssh://git@github.com:22/user-name/repo-name", }, { - name: "ssh+managed is ignored", + name: "ssh+managed is ignored when feature is enabled", + in: "ssh+managed://git@server:22/repo-path", + expected: "ssh+managed://git@server:22/repo-path", + autoUpgrade: true, + }, + { + name: "ssh+managed is ignored when feature is disabled", in: "ssh+managed://git@server:22/repo-path", expected: "ssh+managed://git@server:22/repo-path", }, { - name: "ssh+git is ignored", - in: "ssh+git://git@server:22/repo-path", - expected: "ssh+git://git@server:22/repo-path", + name: "ssh+git is ignored", + in: "ssh+git://git@server:22/repo-path", + expected: "ssh+git://git@server:22/repo-path", + autoUpgrade: true, + }, + { + name: "git+ssh is ignored", + in: "git+ssh://git@server:22/repo-path", + expected: "git+ssh://git@server:22/repo-path", + autoUpgrade: true, + }, + { + name: "azure devops via HTTPS is upgraded to https+managed when feature enabled", + in: "https://flexfloxflux@dev.azure.com/flexfloxflux/flux-testing/_git/flux-testing", + expected: "https+managed://flexfloxflux@dev.azure.com/flexfloxflux/flux-testing/_git/flux-testing", + autoUpgrade: true, }, { - name: "git+ssh is ignored", - in: "git+ssh://git@server:22/repo-path", - expected: "git+ssh://git@server:22/repo-path", + name: "azure devops via HTTP is not upgraded to https+managed when feature disabled", + in: "https://flexfloxflux@dev.azure.com/flexfloxflux/flux-testing/_git/flux-testing", + expected: "https://flexfloxflux@dev.azure.com/flexfloxflux/flux-testing/_git/flux-testing", + autoUpgrade: false, }, { - name: "HTTP is ignored if feature disabled", + name: "HTTP is ignored when feature disabled", in: "http://server/repo-path", expected: "http://server/repo-path", }, { - name: "HTTP is upgraded if feature enabled", + name: "HTTP is upgraded when feature enabled", in: "http://server/repo-path", expected: "http+managed://server/repo-path", autoUpgrade: true, }, { - name: "HTTPS is ignored if feature disabled", + name: "HTTPS is ignored when feature disabled", in: "https://server/repo-path", expected: "https://server/repo-path", }, { - name: "HTTPS is upgraded if feature enabled", + name: "HTTPS is upgraded when feature enabled", in: "https://server/repo-path", expected: "https+managed://server/repo-path", autoUpgrade: true,