azure_migrate/internal/azure/client.go

82 lines
2 KiB
Go
Raw Normal View History

2026-03-22 09:43:45 +00:00
package azure
import (
"encoding/base64"
"fmt"
"net/http"
"time"
"github.com/go-resty/resty/v2"
)
type Client struct {
client *resty.Client
organization string
}
type Repository struct {
ID string `json:"id"`
Name string `json:"name"`
Project struct {
Name string `json:"name"`
} `json:"project"`
RemoteURL string `json:"remoteUrl"`
SSHURL string `json:"sshUrl"`
WebURL string `json:"webUrl"`
}
type RepositoriesResponse struct {
Count int `json:"count"`
Value []Repository `json:"value"`
}
func NewClient(org, pat string) *Client {
c := resty.New()
c.SetTimeout(30 * time.Second)
// Azure DevOps uses Basic auth with empty username and PAT as password
auth := base64.StdEncoding.EncodeToString([]byte(":" + pat))
c.SetHeader("Authorization", "Basic "+auth)
c.SetHeader("Content-Type", "application/json")
return &Client{
client: c,
organization: org,
}
}
func (c *Client) GetRepositories() ([]Repository, error) {
url := fmt.Sprintf("https://dev.azure.com/%s/_apis/git/repositories", c.organization)
var result RepositoriesResponse
resp, err := c.client.R().
SetQueryParam("api-version", "7.0").
SetResult(&result).
Get(url)
if err != nil {
return nil, fmt.Errorf("failed to fetch repositories: %w", err)
}
if resp.StatusCode() != http.StatusOK {
return nil, fmt.Errorf("Azure API returned status %d: %s", resp.StatusCode(), resp.String())
}
// Handle pagination if needed (continuationToken)
// For simplicity, this example handles single page
if result.Count != len(result.Value) {
fmt.Printf("Warning: Repository count mismatch, expected %d got %d\n", result.Count, len(result.Value))
}
return result.Value, nil
}
func (c *Client) GetAuthenticatedURL(remoteURL, pat string) string {
// Convert https://dev.azure.com/org/project/_git/repo
// to https://pat@dev.azure.com/org/project/_git/repo
if len(remoteURL) > 8 && remoteURL[:8] == "https://" {
return "https://:" + pat + "@" + remoteURL[8:]
}
return remoteURL
}