Merge pull request #11491 from cli/andyfeller/10716-project-featuredetection-api-version

Report that v1 classic projects are detected on GHES 3.16.x or older
This commit is contained in:
Andy Feller 2025-08-14 09:46:36 -04:00 committed by GitHub
commit 8699d8596f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 90 additions and 14 deletions

View file

@ -5,6 +5,7 @@ import (
"github.com/cli/cli/v2/api"
"github.com/cli/cli/v2/internal/gh"
"github.com/hashicorp/go-version"
"golang.org/x/sync/errgroup"
ghauth "github.com/cli/go-gh/v2/pkg/auth"
@ -205,12 +206,35 @@ func (d *detector) RepositoryFeatures() (RepositoryFeatures, error) {
return features, nil
}
const (
enterpriseProjectsV1Removed = "3.17.0"
)
func (d *detector) ProjectsV1() gh.ProjectsV1Support {
// Currently, projects v1 support is entirely dependent on the host. As this is deprecated in GHES,
// we will do feature detection on whether the GHES version has support.
if ghauth.IsEnterprise(d.host) {
if !ghauth.IsEnterprise(d.host) {
return gh.ProjectsV1Unsupported
}
hostVersion, hostVersionErr := resolveEnterpriseVersion(d.httpClient, d.host)
v1ProjectCutoffVersion, v1ProjectCutoffVersionErr := version.NewVersion(enterpriseProjectsV1Removed)
if hostVersionErr == nil && v1ProjectCutoffVersionErr == nil && hostVersion.LessThan(v1ProjectCutoffVersion) {
return gh.ProjectsV1Supported
}
return gh.ProjectsV1Unsupported
}
func resolveEnterpriseVersion(httpClient *http.Client, host string) (*version.Version, error) {
var metaResponse struct {
InstalledVersion string `json:"installed_version"`
}
apiClient := api.NewClientFromHTTP(httpClient)
err := apiClient.REST(host, "GET", "meta", nil, &metaResponse)
if err != nil {
return nil, err
}
return version.NewVersion(metaResponse.InstalledVersion)
}

View file

@ -373,17 +373,69 @@ func TestRepositoryFeatures(t *testing.T) {
}
func TestProjectV1Support(t *testing.T) {
t.Parallel()
tests := []struct {
name string
hostname string
httpStubs func(*httpmock.Registry)
wantFeatures gh.ProjectsV1Support
}{
{
name: "github.com",
hostname: "github.com",
wantFeatures: gh.ProjectsV1Unsupported,
},
{
name: "ghec data residency (ghe.com)",
hostname: "stampname.ghe.com",
wantFeatures: gh.ProjectsV1Unsupported,
},
{
name: "GHE 3.16.0",
hostname: "git.my.org",
httpStubs: func(reg *httpmock.Registry) {
reg.Register(
httpmock.REST("GET", "api/v3/meta"),
httpmock.StringResponse(`{"installed_version":"3.16.0"}`),
)
},
wantFeatures: gh.ProjectsV1Supported,
},
{
name: "GHE 3.16.1",
hostname: "git.my.org",
httpStubs: func(reg *httpmock.Registry) {
reg.Register(
httpmock.REST("GET", "api/v3/meta"),
httpmock.StringResponse(`{"installed_version":"3.16.1"}`),
)
},
wantFeatures: gh.ProjectsV1Supported,
},
{
name: "GHE 3.17",
hostname: "git.my.org",
httpStubs: func(reg *httpmock.Registry) {
reg.Register(
httpmock.REST("GET", "api/v3/meta"),
httpmock.StringResponse(`{"installed_version":"3.17.0"}`),
)
},
wantFeatures: gh.ProjectsV1Unsupported,
},
}
t.Run("when the host is enterprise, project v1 is supported", func(t *testing.T) {
detector := detector{host: "my.ghes.com"}
isProjectV1Supported := detector.ProjectsV1()
require.Equal(t, gh.ProjectsV1Supported, isProjectV1Supported)
})
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
reg := &httpmock.Registry{}
if tt.httpStubs != nil {
tt.httpStubs(reg)
}
httpClient := &http.Client{}
httpmock.ReplaceTripper(httpClient, reg)
t.Run("when the host is not enterprise, project v1 is not supported", func(t *testing.T) {
detector := detector{host: "github.com"}
isProjectV1Supported := detector.ProjectsV1()
require.Equal(t, gh.ProjectsV1Unsupported, isProjectV1Supported)
})
detector := NewDetector(httpClient, tt.hostname)
require.Equal(t, tt.wantFeatures, detector.ProjectsV1())
})
}
}