Add tenancy support (#7636)

This commit is contained in:
Sam Coe 2023-06-30 20:20:53 +09:00 committed by GitHub
parent 5a0f892d4a
commit d21c11d774
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 186 additions and 10 deletions

View file

@ -6,37 +6,56 @@ import (
"strings"
)
// DefaultHostname is the domain name of the default GitHub instance.
const defaultHostname = "github.com"
// localhost is the domain name of a local GitHub instance
// Localhost is the domain name of a local GitHub instance.
const localhost = "github.localhost"
// Default returns the host name of the default GitHub instance
// TenancyHost is the domain name of a tenancy GitHub instance.
const tenancyHost = "ghe.com"
// Default returns the host name of the default GitHub instance.
func Default() string {
return defaultHostname
}
// IsEnterprise reports whether a non-normalized host name looks like a GHE instance
// IsEnterprise reports whether a non-normalized host name looks like a GHE instance.
func IsEnterprise(h string) bool {
normalizedHostName := NormalizeHostname(h)
return normalizedHostName != defaultHostname && normalizedHostName != localhost
}
// IsTenancy reports whether a non-normalized host name looks like a tenancy instance.
func IsTenancy(h string) bool {
normalizedHostName := NormalizeHostname(h)
return strings.HasSuffix(normalizedHostName, "."+tenancyHost)
}
// TenantName extracts the tenant name from tenancy host name and
// reports whether it found the tenant name.
func TenantName(h string) (string, bool) {
normalizedHostName := NormalizeHostname(h)
return cutSuffix(normalizedHostName, "."+tenancyHost)
}
func isGarage(h string) bool {
return strings.EqualFold(h, "garage.github.com")
}
// NormalizeHostname returns the canonical host name of a GitHub instance
// NormalizeHostname returns the canonical host name of a GitHub instance.
func NormalizeHostname(h string) string {
hostname := strings.ToLower(h)
if strings.HasSuffix(hostname, "."+defaultHostname) {
return defaultHostname
}
if strings.HasSuffix(hostname, "."+localhost) {
return localhost
}
if before, found := cutSuffix(hostname, "."+tenancyHost); found {
idx := strings.LastIndex(before, ".")
return fmt.Sprintf("%s.%s", before[idx+1:], tenancyHost)
}
return hostname
}
@ -78,11 +97,9 @@ func RESTPrefix(hostname string) string {
func GistPrefix(hostname string) string {
prefix := "https://"
if strings.EqualFold(hostname, localhost) {
prefix = "http://"
}
return prefix + GistHost(hostname)
}
@ -105,3 +122,11 @@ func HostPrefix(hostname string) string {
}
return fmt.Sprintf("https://%s/", hostname)
}
// Backport strings.CutSuffix from Go 1.20.
func cutSuffix(s, suffix string) (string, bool) {
if !strings.HasSuffix(s, suffix) {
return s, false
}
return s[:len(s)-len(suffix)], true
}

View file

@ -49,6 +49,87 @@ func TestIsEnterprise(t *testing.T) {
}
}
func TestIsTenancy(t *testing.T) {
tests := []struct {
host string
want bool
}{
{
host: "github.com",
want: false,
},
{
host: "github.localhost",
want: false,
},
{
host: "garage.github.com",
want: false,
},
{
host: "ghe.com",
want: false,
},
{
host: "tenant.ghe.com",
want: true,
},
{
host: "api.tenant.ghe.com",
want: true,
},
}
for _, tt := range tests {
t.Run(tt.host, func(t *testing.T) {
if got := IsTenancy(tt.host); got != tt.want {
t.Errorf("IsTenancy() = %v, want %v", got, tt.want)
}
})
}
}
func TestTenantName(t *testing.T) {
tests := []struct {
host string
wantTenant string
wantFound bool
}{
{
host: "github.com",
wantTenant: "github.com",
},
{
host: "github.localhost",
wantTenant: "github.localhost",
},
{
host: "garage.github.com",
wantTenant: "github.com",
},
{
host: "ghe.com",
wantTenant: "ghe.com",
},
{
host: "tenant.ghe.com",
wantTenant: "tenant",
wantFound: true,
},
{
host: "api.tenant.ghe.com",
wantTenant: "tenant",
wantFound: true,
},
}
for _, tt := range tests {
t.Run(tt.host, func(t *testing.T) {
if tenant, found := TenantName(tt.host); tenant != tt.wantTenant || found != tt.wantFound {
t.Errorf("TenantName(%v) = %v %v, want %v %v", tt.host, tenant, found, tt.wantTenant, tt.wantFound)
}
})
}
}
func TestNormalizeHostname(t *testing.T) {
tests := []struct {
host string
@ -90,6 +171,18 @@ func TestNormalizeHostname(t *testing.T) {
host: "git.my.org",
want: "git.my.org",
},
{
host: "ghe.com",
want: "ghe.com",
},
{
host: "tenant.ghe.com",
want: "tenant.ghe.com",
},
{
host: "api.tenant.ghe.com",
want: "tenant.ghe.com",
},
}
for _, tt := range tests {
t.Run(tt.host, func(t *testing.T) {
@ -139,6 +232,7 @@ func TestHostnameValidator(t *testing.T) {
})
}
}
func TestGraphQLEndpoint(t *testing.T) {
tests := []struct {
host string

View file

@ -92,12 +92,13 @@ func GenerateRepoURL(repo Interface, p string, args ...interface{}) string {
return baseURL
}
// TODO there is a parallel implementation for non-isolated commands
func FormatRemoteURL(repo Interface, protocol string) string {
if protocol == "ssh" {
if tenant, found := ghinstance.TenantName(repo.RepoHost()); found {
return fmt.Sprintf("%s@%s:%s/%s.git", tenant, repo.RepoHost(), repo.RepoOwner(), repo.RepoName())
}
return fmt.Sprintf("git@%s:%s/%s.git", repo.RepoHost(), repo.RepoOwner(), repo.RepoName())
}
return fmt.Sprintf("%s%s/%s.git", ghinstance.HostPrefix(repo.RepoHost()), repo.RepoOwner(), repo.RepoName())
}

View file

@ -220,3 +220,59 @@ func TestFromFullName(t *testing.T) {
})
}
}
func TestFormatRemoteURL(t *testing.T) {
tests := []struct {
name string
repoHost string
repoOwner string
repoName string
protocol string
want string
}{
{
name: "https protocol",
repoHost: "github.com",
repoOwner: "owner",
repoName: "name",
protocol: "https",
want: "https://github.com/owner/name.git",
},
{
name: "https protocol local host",
repoHost: "github.localhost",
repoOwner: "owner",
repoName: "name",
protocol: "https",
want: "http://github.localhost/owner/name.git",
},
{
name: "ssh protocol",
repoHost: "github.com",
repoOwner: "owner",
repoName: "name",
protocol: "ssh",
want: "git@github.com:owner/name.git",
},
{
name: "ssh protocol tenancy host",
repoHost: "tenant.ghe.com",
repoOwner: "owner",
repoName: "name",
protocol: "ssh",
want: "tenant@tenant.ghe.com:owner/name.git",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := ghRepo{
hostname: tt.repoHost,
owner: tt.repoOwner,
name: tt.repoName,
}
if url := FormatRemoteURL(r, tt.protocol); url != tt.want {
t.Errorf("expected url %q, got %q", tt.want, url)
}
})
}
}