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 }