Enforce Go style rules across codebase

Apply five style rules: convert iota string enums to typed
string constants, replace errors.As with errors.AsType,
merge three-group imports into two groups, fix multiline
parameter/argument formatting, and replace fmt.Sprintf URL
construction with net/url.

Signed-off-by: Émile Ré <emile@probo.com>
This commit is contained in:
Émile Ré
2026-05-20 11:46:39 +04:00
parent 34c25c2727
commit f5703d390b
105 changed files with 1180 additions and 940 deletions

View File

@@ -385,51 +385,67 @@ func (s AccessSourceService) ConfigureAccessSource(
switch dbConnector.Provider {
case coredata.ConnectorProviderGitHub:
if err := dbConnector.SetSettings(&coredata.GitHubConnectorSettings{
Organization: req.OrganizationSlug,
}); err != nil {
if err := dbConnector.SetSettings(
&coredata.GitHubConnectorSettings{
Organization: req.OrganizationSlug,
},
); err != nil {
return fmt.Errorf("cannot set github settings: %w", err)
}
case coredata.ConnectorProviderSentry:
if err := dbConnector.SetSettings(&coredata.SentryConnectorSettings{
OrganizationSlug: req.OrganizationSlug,
}); err != nil {
if err := dbConnector.SetSettings(
&coredata.SentryConnectorSettings{
OrganizationSlug: req.OrganizationSlug,
},
); err != nil {
return fmt.Errorf("cannot set sentry settings: %w", err)
}
case coredata.ConnectorProviderGitLab:
if err := dbConnector.SetSettings(&coredata.GitLabConnectorSettings{
GroupID: req.OrganizationSlug,
}); err != nil {
if err := dbConnector.SetSettings(
&coredata.GitLabConnectorSettings{
GroupID: req.OrganizationSlug,
},
); err != nil {
return fmt.Errorf("cannot set gitlab settings: %w", err)
}
case coredata.ConnectorProviderBitbucket:
if err := dbConnector.SetSettings(&coredata.BitbucketConnectorSettings{
Workspace: req.OrganizationSlug,
}); err != nil {
if err := dbConnector.SetSettings(
&coredata.BitbucketConnectorSettings{
Workspace: req.OrganizationSlug,
},
); err != nil {
return fmt.Errorf("cannot set bitbucket settings: %w", err)
}
case coredata.ConnectorProviderHeroku:
if err := dbConnector.SetSettings(&coredata.HerokuConnectorSettings{
TeamID: req.OrganizationSlug,
}); err != nil {
if err := dbConnector.SetSettings(
&coredata.HerokuConnectorSettings{
TeamID: req.OrganizationSlug,
},
); err != nil {
return fmt.Errorf("cannot set heroku settings: %w", err)
}
case coredata.ConnectorProviderAsana:
if err := dbConnector.SetSettings(&coredata.AsanaConnectorSettings{
WorkspaceGID: req.OrganizationSlug,
}); err != nil {
if err := dbConnector.SetSettings(
&coredata.AsanaConnectorSettings{
WorkspaceGID: req.OrganizationSlug,
},
); err != nil {
return fmt.Errorf("cannot set asana settings: %w", err)
}
case coredata.ConnectorProviderNetlify:
if err := dbConnector.SetSettings(&coredata.NetlifyConnectorSettings{
AccountSlug: req.OrganizationSlug,
}); err != nil {
if err := dbConnector.SetSettings(
&coredata.NetlifyConnectorSettings{
AccountSlug: req.OrganizationSlug,
},
); err != nil {
return fmt.Errorf("cannot set netlify settings: %w", err)
}
case coredata.ConnectorProviderClickUp:
if err := dbConnector.SetSettings(&coredata.ClickUpConnectorSettings{
TeamID: req.OrganizationSlug,
}); err != nil {
if err := dbConnector.SetSettings(
&coredata.ClickUpConnectorSettings{
TeamID: req.OrganizationSlug,
},
); err != nil {
return fmt.Errorf("cannot set clickup settings: %w", err)
}
default:

View File

@@ -67,10 +67,21 @@ type asanaUsersPage struct {
func (d *AsanaDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error) {
var records []AccountRecord
next := fmt.Sprintf(
"https://app.asana.com/api/1.0/workspaces/%s/users?opt_fields=email,name&limit=100",
url.PathEscape(d.workspaceGID),
)
u, err := url.JoinPath("https://app.asana.com", "api", "1.0", "workspaces", d.workspaceGID, "users")
if err != nil {
return nil, fmt.Errorf("cannot build asana users URL: %w", err)
}
parsed, err := url.Parse(u)
if err != nil {
return nil, fmt.Errorf("cannot parse asana users URL: %w", err)
}
q := parsed.Query()
q.Set("opt_fields", "email,name")
q.Set("limit", "100")
parsed.RawQuery = q.Encode()
next := parsed.String()
for range maxPaginationPages {
page, err := d.queryUsers(ctx, next)
@@ -85,14 +96,17 @@ func (d *AsanaDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error)
// Active=false from any of those would fabricate state, so
// leave Active nil (unknown) and let downstream review surface
// the gap honestly.
records = append(records, AccountRecord{
Email: u.Email,
FullName: u.Name,
ExternalID: u.GID,
MFAStatus: coredata.MFAStatusUnknown,
AuthMethod: coredata.AccessEntryAuthMethodUnknown,
AccountType: coredata.AccessEntryAccountTypeUser,
})
records = append(
records,
AccountRecord{
Email: u.Email,
FullName: u.Name,
ExternalID: u.GID,
MFAStatus: coredata.MFAStatusUnknown,
AuthMethod: coredata.AccessEntryAuthMethodUnknown,
AccountType: coredata.AccessEntryAccountTypeUser,
},
)
}
if page.NextPage == nil || page.NextPage.URI == "" {

View File

@@ -67,10 +67,21 @@ type bitbucketMembersPage struct {
func (d *BitbucketDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error) {
var records []AccountRecord
next := fmt.Sprintf(
"https://api.bitbucket.org/2.0/workspaces/%s/members?fields=%%2Bvalues.user.email&pagelen=100",
url.PathEscape(d.workspace),
)
u, err := url.JoinPath("https://api.bitbucket.org", "2.0", "workspaces", d.workspace, "members")
if err != nil {
return nil, fmt.Errorf("cannot build bitbucket members URL: %w", err)
}
parsed, err := url.Parse(u)
if err != nil {
return nil, fmt.Errorf("cannot parse bitbucket members URL: %w", err)
}
q := parsed.Query()
q.Set("fields", "+values.user.email")
q.Set("pagelen", "100")
parsed.RawQuery = q.Encode()
next := parsed.String()
for range maxPaginationPages {
page, err := d.queryMembers(ctx, next)

View File

@@ -71,7 +71,10 @@ type clickupTeamResponse struct {
}
func (d *ClickUpDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error) {
endpoint := fmt.Sprintf("https://api.clickup.com/api/v2/team/%s", url.PathEscape(d.teamID))
endpoint, err := url.JoinPath("https://api.clickup.com", "api", "v2", "team", d.teamID)
if err != nil {
return nil, fmt.Errorf("cannot build clickup team URL: %w", err)
}
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
if err != nil {

View File

@@ -19,6 +19,8 @@ import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"strconv"
"strings"
"go.probo.inc/probo/pkg/coredata"
@@ -114,12 +116,17 @@ func (d *CloudflareDriver) queryAllAccounts(ctx context.Context) ([]cloudflareAc
}
func (d *CloudflareDriver) queryAccounts(ctx context.Context, page int) (*cloudflareListAccountsResponse, error) {
url := fmt.Sprintf(
"https://api.cloudflare.com/client/v4/accounts?page=%d&per_page=50",
page,
)
parsed, err := url.Parse("https://api.cloudflare.com/client/v4/accounts")
if err != nil {
return nil, fmt.Errorf("cannot parse cloudflare accounts URL: %w", err)
}
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
q := parsed.Query()
q.Set("page", strconv.Itoa(page))
q.Set("per_page", "50")
parsed.RawQuery = q.Encode()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, parsed.String(), nil)
if err != nil {
return nil, fmt.Errorf("cannot create cloudflare accounts request: %w", err)
}
@@ -208,13 +215,22 @@ func (d *CloudflareDriver) queryAllMembers(ctx context.Context, accountID string
}
func (d *CloudflareDriver) queryMembers(ctx context.Context, accountID string, page int) (*cloudflareListMembersResponse, error) {
url := fmt.Sprintf(
"https://api.cloudflare.com/client/v4/accounts/%s/members?page=%d&per_page=50",
accountID,
page,
)
u, err := url.JoinPath("https://api.cloudflare.com", "client", "v4", "accounts", accountID, "members")
if err != nil {
return nil, fmt.Errorf("cannot build cloudflare members URL: %w", err)
}
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
parsed, err := url.Parse(u)
if err != nil {
return nil, fmt.Errorf("cannot parse cloudflare members URL: %w", err)
}
q := parsed.Query()
q.Set("page", strconv.Itoa(page))
q.Set("per_page", "50")
parsed.RawQuery = q.Encode()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, parsed.String(), nil)
if err != nil {
return nil, fmt.Errorf("cannot create cloudflare members request: %w", err)
}

View File

@@ -19,6 +19,7 @@ import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"strconv"
"strings"
"time"
@@ -179,10 +180,23 @@ func (d *DocuSignDriver) discoverAccount(ctx context.Context) (accountID string,
}
func (d *DocuSignDriver) queryUsers(ctx context.Context, baseURI string, accountID string, startPosition int) (*docusignUsersResponse, error) {
url := fmt.Sprintf("%s/restapi/v2.1/accounts/%s/users?additional_info=true&count=%d&start_position=%d",
baseURI, accountID, docusignUsersPageSize, startPosition)
u, err := url.JoinPath(baseURI, "restapi", "v2.1", "accounts", accountID, "users")
if err != nil {
return nil, fmt.Errorf("cannot build docusign users URL: %w", err)
}
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
parsed, err := url.Parse(u)
if err != nil {
return nil, fmt.Errorf("cannot parse docusign users URL: %w", err)
}
q := parsed.Query()
q.Set("additional_info", "true")
q.Set("count", strconv.Itoa(docusignUsersPageSize))
q.Set("start_position", strconv.Itoa(startPosition))
parsed.RawQuery = q.Encode()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, parsed.String(), nil)
if err != nil {
return nil, fmt.Errorf("cannot create docusign users request: %w", err)
}

View File

@@ -19,6 +19,7 @@ import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"strconv"
"time"
@@ -82,7 +83,9 @@ func (d *GitHubDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error
for _, m := range members {
membership, err := d.fetchMembership(ctx, m.Login)
if err != nil {
d.logger.WarnCtx(ctx, "cannot fetch github membership, skipping member",
d.logger.WarnCtx(
ctx,
"cannot fetch github membership, skipping member",
log.Error(err),
)
@@ -91,7 +94,9 @@ func (d *GitHubDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error
profile, err := d.fetchUserProfile(ctx, m.Login)
if err != nil {
d.logger.WarnCtx(ctx, "cannot fetch github user profile, skipping member",
d.logger.WarnCtx(
ctx,
"cannot fetch github user profile, skipping member",
log.Error(err),
)
@@ -145,13 +150,23 @@ func (d *GitHubDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error
func (d *GitHubDriver) fetchAllMembers(ctx context.Context) ([]githubMember, error) {
var members []githubMember
url := fmt.Sprintf(
"https://api.github.com/orgs/%s/members?per_page=100",
d.org,
)
u, err := url.JoinPath("https://api.github.com", "orgs", d.org, "members")
if err != nil {
return nil, fmt.Errorf("cannot build github members URL: %w", err)
}
parsed, err := url.Parse(u)
if err != nil {
return nil, fmt.Errorf("cannot parse github members URL: %w", err)
}
q := parsed.Query()
q.Set("per_page", "100")
parsed.RawQuery = q.Encode()
endpoint := parsed.String()
for range maxPaginationPages {
page, nextURL, err := d.fetchMembersPage(ctx, url)
page, nextURL, err := d.fetchMembersPage(ctx, endpoint)
if err != nil {
return nil, err
}
@@ -162,7 +177,7 @@ func (d *GitHubDriver) fetchAllMembers(ctx context.Context) ([]githubMember, err
return members, nil
}
url = nextURL
endpoint = nextURL
}
return nil, fmt.Errorf("cannot list all github members: %w", ErrPaginationLimitReached)
@@ -202,13 +217,24 @@ func (d *GitHubDriver) fetchMembersPage(ctx context.Context, url string) ([]gith
func (d *GitHubDriver) fetchAll2FADisabledLogins(ctx context.Context) (map[string]bool, error) {
set := make(map[string]bool)
url := fmt.Sprintf(
"https://api.github.com/orgs/%s/members?filter=2fa_disabled&per_page=100",
d.org,
)
u, err := url.JoinPath("https://api.github.com", "orgs", d.org, "members")
if err != nil {
return nil, fmt.Errorf("cannot build github 2fa-disabled URL: %w", err)
}
parsed, err := url.Parse(u)
if err != nil {
return nil, fmt.Errorf("cannot parse github 2fa-disabled URL: %w", err)
}
q := parsed.Query()
q.Set("filter", "2fa_disabled")
q.Set("per_page", "100")
parsed.RawQuery = q.Encode()
endpoint := parsed.String()
for range maxPaginationPages {
page, nextURL, err := d.fetchMembersPage(ctx, url)
page, nextURL, err := d.fetchMembersPage(ctx, endpoint)
if err != nil {
return nil, err
}
@@ -221,20 +247,19 @@ func (d *GitHubDriver) fetchAll2FADisabledLogins(ctx context.Context) (map[strin
return set, nil
}
url = nextURL
endpoint = nextURL
}
return nil, fmt.Errorf("cannot list all github 2fa-disabled members: %w", ErrPaginationLimitReached)
}
func (d *GitHubDriver) fetchMembership(ctx context.Context, login string) (*githubMembership, error) {
url := fmt.Sprintf(
"https://api.github.com/orgs/%s/memberships/%s",
d.org,
login,
)
endpoint, err := url.JoinPath("https://api.github.com", "orgs", d.org, "memberships", login)
if err != nil {
return nil, fmt.Errorf("cannot build github membership URL: %w", err)
}
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
if err != nil {
return nil, fmt.Errorf("cannot create github membership request: %w", err)
}
@@ -263,9 +288,12 @@ func (d *GitHubDriver) fetchMembership(ctx context.Context, login string) (*gith
}
func (d *GitHubDriver) fetchUserProfile(ctx context.Context, login string) (*githubUserProfile, error) {
url := fmt.Sprintf("https://api.github.com/users/%s", login)
endpoint, err := url.JoinPath("https://api.github.com", "users", login)
if err != nil {
return nil, fmt.Errorf("cannot build github user profile URL: %w", err)
}
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
if err != nil {
return nil, fmt.Errorf("cannot create github user profile request: %w", err)
}

View File

@@ -67,10 +67,20 @@ type gitlabMember struct {
func (d *GitLabDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error) {
var records []AccountRecord
next := fmt.Sprintf(
"https://gitlab.com/api/v4/groups/%s/members/all?per_page=100",
url.PathEscape(d.groupID),
)
u, err := url.JoinPath("https://gitlab.com", "api", "v4", "groups", d.groupID, "members", "all")
if err != nil {
return nil, fmt.Errorf("cannot build gitlab members URL: %w", err)
}
parsed, err := url.Parse(u)
if err != nil {
return nil, fmt.Errorf("cannot parse gitlab members URL: %w", err)
}
q := parsed.Query()
q.Set("per_page", "100")
parsed.RawQuery = q.Encode()
next := parsed.String()
for range maxPaginationPages {
members, linkHeader, err := d.queryMembers(ctx, next)

View File

@@ -21,7 +21,6 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.probo.inc/probo/pkg/coredata"
)

View File

@@ -22,10 +22,9 @@ import (
"net/http"
"time"
"go.probo.inc/probo/pkg/coredata"
admin "google.golang.org/api/admin/directory/v1"
"google.golang.org/api/option"
"go.probo.inc/probo/pkg/coredata"
)
// GoogleWorkspaceDriver fetches user accounts from Google Workspace

View File

@@ -72,7 +72,10 @@ type herokuTeamMember struct {
func (d *HerokuDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error) {
var records []AccountRecord
endpoint := fmt.Sprintf("https://api.heroku.com/teams/%s/members", url.PathEscape(d.teamID))
endpoint, err := url.JoinPath("https://api.heroku.com", "teams", d.teamID, "members")
if err != nil {
return nil, fmt.Errorf("cannot build heroku members URL: %w", err)
}
rangeHeader := ""
for range maxPaginationPages {

View File

@@ -21,7 +21,6 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.probo.inc/probo/pkg/coredata"
)

View File

@@ -248,7 +248,12 @@ func (d *Microsoft365Driver) listUsers(ctx context.Context) ([]microsoft365User,
}
func buildMicrosoft365UsersURL() (string, error) {
u, err := url.Parse(microsoft365GraphBaseURL + "/users")
endpoint, err := url.JoinPath(microsoft365GraphBaseURL, "users")
if err != nil {
return "", fmt.Errorf("cannot build graph users URL: %w", err)
}
u, err := url.Parse(endpoint)
if err != nil {
return "", fmt.Errorf("cannot parse graph users URL: %w", err)
}
@@ -263,13 +268,16 @@ func buildMicrosoft365UsersURL() (string, error) {
}
func (d *Microsoft365Driver) listDirectoryRoles(ctx context.Context) ([]microsoft365DirectoryRole, error) {
url := fmt.Sprintf("%s/directoryRoles", microsoft365GraphBaseURL)
endpoint, err := url.JoinPath(microsoft365GraphBaseURL, "directoryRoles")
if err != nil {
return nil, fmt.Errorf("cannot build graph directory roles URL: %w", err)
}
var all []microsoft365DirectoryRole
for range microsoft365MaxPaginationOK {
var page microsoft365RolesPage
if err := d.fetchJSON(ctx, url, &page); err != nil {
if err := d.fetchJSON(ctx, endpoint, &page); err != nil {
return nil, err
}
@@ -278,20 +286,23 @@ func (d *Microsoft365Driver) listDirectoryRoles(ctx context.Context) ([]microsof
return all, nil
}
url = page.NextLink
endpoint = page.NextLink
}
return nil, fmt.Errorf("cannot list all microsoft 365 directory roles: %w", ErrPaginationLimitReached)
}
func (d *Microsoft365Driver) listRoleMembers(ctx context.Context, roleID string) ([]microsoft365RoleMember, error) {
url := fmt.Sprintf("%s/directoryRoles/%s/members", microsoft365GraphBaseURL, roleID)
endpoint, err := url.JoinPath(microsoft365GraphBaseURL, "directoryRoles", roleID, "members")
if err != nil {
return nil, fmt.Errorf("cannot build graph role members URL: %w", err)
}
var all []microsoft365RoleMember
for range microsoft365MaxPaginationOK {
var page microsoft365MembersPage
if err := d.fetchJSON(ctx, url, &page); err != nil {
if err := d.fetchJSON(ctx, endpoint, &page); err != nil {
return nil, err
}
@@ -300,7 +311,7 @@ func (d *Microsoft365Driver) listRoleMembers(ctx context.Context, roleID string)
return all, nil
}
url = page.NextLink
endpoint = page.NextLink
}
return nil, fmt.Errorf("cannot list all members of role %q: %w", roleID, ErrPaginationLimitReached)

View File

@@ -22,11 +22,10 @@ import (
"net/http"
"net/url"
admin "google.golang.org/api/admin/directory/v1"
"google.golang.org/api/option"
"go.probo.inc/probo/pkg/connector"
"go.probo.inc/probo/pkg/coredata"
admin "google.golang.org/api/admin/directory/v1"
"google.golang.org/api/option"
)
// NameResolver fetches the human-readable instance name from a provider
@@ -204,12 +203,17 @@ func NewCloudflareNameResolver(httpClient *http.Client) NameResolver {
}
func (r *cloudflareNameResolver) ResolveInstanceName(ctx context.Context) (string, error) {
req, err := http.NewRequestWithContext(
ctx,
http.MethodGet,
"https://api.cloudflare.com/client/v4/accounts?page=1&per_page=1",
nil,
)
cfURL, err := url.Parse("https://api.cloudflare.com/client/v4/accounts")
if err != nil {
return "", fmt.Errorf("cannot parse cloudflare accounts URL: %w", err)
}
q := cfURL.Query()
q.Set("page", "1")
q.Set("per_page", "1")
cfURL.RawQuery = q.Encode()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, cfURL.String(), nil)
if err != nil {
return "", fmt.Errorf("cannot create cloudflare accounts request: %w", err)
}
@@ -300,9 +304,12 @@ func NewTallyNameResolver(httpClient *http.Client, organizationID string) NameRe
}
func (r *tallyNameResolver) ResolveInstanceName(ctx context.Context) (string, error) {
url := fmt.Sprintf("https://api.tally.so/organizations/%s", r.organizationID)
endpoint, err := url.JoinPath("https://api.tally.so", "organizations", r.organizationID)
if err != nil {
return "", fmt.Errorf("cannot build tally organization URL: %w", err)
}
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
if err != nil {
return "", fmt.Errorf("cannot create tally organization request: %w", err)
}
@@ -484,9 +491,12 @@ func (r *sentryNameResolver) ResolveInstanceName(ctx context.Context) (string, e
return "", nil
}
url := fmt.Sprintf("https://sentry.io/api/0/organizations/%s/", r.orgSlug)
endpoint, err := url.JoinPath("https://sentry.io", "api", "0", "organizations", r.orgSlug)
if err != nil {
return "", fmt.Errorf("cannot build sentry organization URL: %w", err)
}
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
if err != nil {
return "", fmt.Errorf("cannot create sentry organization request: %w", err)
}
@@ -525,9 +535,12 @@ func NewGitHubNameResolver(httpClient *http.Client, org string) NameResolver {
}
func (r *githubNameResolver) ResolveInstanceName(ctx context.Context) (string, error) {
url := fmt.Sprintf("https://api.github.com/orgs/%s", r.org)
endpoint, err := url.JoinPath("https://api.github.com", "orgs", r.org)
if err != nil {
return "", fmt.Errorf("cannot build github organization URL: %w", err)
}
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
if err != nil {
return "", fmt.Errorf("cannot create github organization request: %w", err)
}
@@ -639,7 +652,10 @@ func (r *gitlabNameResolver) ResolveInstanceName(ctx context.Context) (string, e
return "", nil
}
endpoint := fmt.Sprintf("https://gitlab.com/api/v4/groups/%s", url.PathEscape(r.groupID))
endpoint, err := url.JoinPath("https://gitlab.com", "api", "v4", "groups", r.groupID)
if err != nil {
return "", fmt.Errorf("cannot build gitlab group URL: %w", err)
}
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
if err != nil {
@@ -689,7 +705,10 @@ func (r *bitbucketNameResolver) ResolveInstanceName(ctx context.Context) (string
return "", nil
}
endpoint := fmt.Sprintf("https://api.bitbucket.org/2.0/workspaces/%s", url.PathEscape(r.workspace))
endpoint, err := url.JoinPath("https://api.bitbucket.org", "2.0", "workspaces", r.workspace)
if err != nil {
return "", fmt.Errorf("cannot build bitbucket workspace URL: %w", err)
}
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
if err != nil {
@@ -739,7 +758,10 @@ func (r *herokuNameResolver) ResolveInstanceName(ctx context.Context) (string, e
return "", nil
}
endpoint := fmt.Sprintf("https://api.heroku.com/teams/%s", url.PathEscape(r.teamID))
endpoint, err := url.JoinPath("https://api.heroku.com", "teams", r.teamID)
if err != nil {
return "", fmt.Errorf("cannot build heroku team URL: %w", err)
}
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
if err != nil {
@@ -799,7 +821,10 @@ func (r *asanaNameResolver) ResolveInstanceName(ctx context.Context) (string, er
return "", nil
}
endpoint := fmt.Sprintf("https://app.asana.com/api/1.0/workspaces/%s", url.PathEscape(r.workspaceGID))
endpoint, err := url.JoinPath("https://app.asana.com", "api", "1.0", "workspaces", r.workspaceGID)
if err != nil {
return "", fmt.Errorf("cannot build asana workspace URL: %w", err)
}
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
if err != nil {
@@ -846,7 +871,10 @@ func (r *netlifyNameResolver) ResolveInstanceName(ctx context.Context) (string,
return "", nil
}
endpoint := fmt.Sprintf("https://api.netlify.com/api/v1/accounts/%s", url.PathEscape(r.accountSlug))
endpoint, err := url.JoinPath("https://api.netlify.com", "api", "v1", "accounts", r.accountSlug)
if err != nil {
return "", fmt.Errorf("cannot build netlify account URL: %w", err)
}
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
if err != nil {
@@ -891,7 +919,10 @@ func (r *clickupNameResolver) ResolveInstanceName(ctx context.Context) (string,
return "", nil
}
endpoint := fmt.Sprintf("https://api.clickup.com/api/v2/team/%s", url.PathEscape(r.teamID))
endpoint, err := url.JoinPath("https://api.clickup.com", "api", "v2", "team", r.teamID)
if err != nil {
return "", fmt.Errorf("cannot build clickup team URL: %w", err)
}
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
if err != nil {
@@ -941,7 +972,10 @@ func (r *vercelNameResolver) ResolveInstanceName(ctx context.Context) (string, e
return "", nil
}
teamURL := fmt.Sprintf("https://api.vercel.com/v2/teams/%s", url.PathEscape(r.teamID))
teamURL, err := url.JoinPath("https://api.vercel.com", "v2", "teams", r.teamID)
if err != nil {
return "", fmt.Errorf("cannot build vercel team URL: %w", err)
}
teamReq, err := http.NewRequestWithContext(ctx, http.MethodGet, teamURL, nil)
if err != nil {
@@ -1107,12 +1141,16 @@ func NewMicrosoft365NameResolver(httpClient *http.Client) NameResolver {
}
func (r *microsoft365NameResolver) ResolveInstanceName(ctx context.Context) (string, error) {
req, err := http.NewRequestWithContext(
ctx,
http.MethodGet,
"https://graph.microsoft.com/v1.0/organization?$select=displayName,verifiedDomains",
nil,
)
msURL, err := url.Parse("https://graph.microsoft.com/v1.0/organization")
if err != nil {
return "", fmt.Errorf("cannot parse microsoft 365 organization URL: %w", err)
}
q := msURL.Query()
q.Set("$select", "displayName,verifiedDomains")
msURL.RawQuery = q.Encode()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, msURL.String(), nil)
if err != nil {
return "", fmt.Errorf("cannot create microsoft 365 organization request: %w", err)
}

View File

@@ -56,10 +56,20 @@ type netlifyMember struct {
func (d *NetlifyDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error) {
var records []AccountRecord
next := fmt.Sprintf(
"https://api.netlify.com/api/v1/%s/members?per_page=100",
url.PathEscape(d.accountSlug),
)
u, err := url.JoinPath("https://api.netlify.com", "api", "v1", d.accountSlug, "members")
if err != nil {
return nil, fmt.Errorf("cannot build netlify members URL: %w", err)
}
parsed, err := url.Parse(u)
if err != nil {
return nil, fmt.Errorf("cannot parse netlify members URL: %w", err)
}
q := parsed.Query()
q.Set("per_page", "100")
parsed.RawQuery = q.Encode()
next := parsed.String()
for range maxPaginationPages {
members, linkHeader, err := d.queryMembers(ctx, next)

View File

@@ -65,18 +65,21 @@ func (d *ProboMembershipsDriver) ListAccounts(ctx context.Context) ([]AccountRec
isAdmin := role == string(coredata.MembershipRoleOwner) || role == string(coredata.MembershipRoleAdmin)
createdAt := account.CreatedAt
records = append(records, AccountRecord{
Email: account.Email,
FullName: account.FullName,
Role: role,
Active: new(account.State == string(coredata.ProfileStateActive)),
IsAdmin: isAdmin,
ExternalID: account.ID.String(),
CreatedAt: &createdAt,
MFAStatus: coredata.MFAStatusUnknown,
AuthMethod: coredata.AccessEntryAuthMethodUnknown,
AccountType: coredata.AccessEntryAccountTypeUser,
})
records = append(
records,
AccountRecord{
Email: account.Email,
FullName: account.FullName,
Role: role,
Active: new(account.State == string(coredata.ProfileStateActive)),
IsAdmin: isAdmin,
ExternalID: account.ID.String(),
CreatedAt: &createdAt,
MFAStatus: coredata.MFAStatusUnknown,
AuthMethod: coredata.AccessEntryAuthMethodUnknown,
AccountType: coredata.AccessEntryAccountTypeUser,
},
)
}
return nil

View File

@@ -19,6 +19,7 @@ import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"time"
"go.probo.inc/probo/pkg/coredata"
@@ -108,10 +109,10 @@ func (d *SentryDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error
var records []AccountRecord
nextURL := fmt.Sprintf(
"https://sentry.io/api/0/organizations/%s/members/",
orgSlug,
)
nextURL, err := url.JoinPath("https://sentry.io", "api", "0", "organizations", orgSlug, "members")
if err != nil {
return nil, fmt.Errorf("cannot build sentry members URL: %w", err)
}
for range maxPaginationPages {
members, linkHeader, err := d.queryMembers(ctx, nextURL)

View File

@@ -87,7 +87,9 @@ func (h *sourceNameHandler) Claim(ctx context.Context) (coredata.AccessSource, e
}
func (h *sourceNameHandler) Process(ctx context.Context, source coredata.AccessSource) error {
h.logger.InfoCtx(ctx, "syncing source name",
h.logger.InfoCtx(
ctx,
"syncing source name",
log.String("source_id", source.ID.String()),
log.String("current_name", source.Name),
)
@@ -134,7 +136,9 @@ func (h *sourceNameHandler) Process(ctx context.Context, source coredata.AccessS
},
)
if err != nil {
h.logger.ErrorCtx(ctx, "cannot load connector for source name sync",
h.logger.ErrorCtx(
ctx,
"cannot load connector for source name sync",
log.String("source_id", source.ID.String()),
log.Error(err),
)
@@ -143,7 +147,9 @@ func (h *sourceNameHandler) Process(ctx context.Context, source coredata.AccessS
}
if resolver == nil {
h.logger.InfoCtx(ctx, "no name resolver for provider, keeping generic name",
h.logger.InfoCtx(
ctx,
"no name resolver for provider, keeping generic name",
log.String("source_id", source.ID.String()),
log.String("provider", dbConnector.Provider.String()),
)
@@ -156,7 +162,9 @@ func (h *sourceNameHandler) Process(ctx context.Context, source coredata.AccessS
instanceName, err := resolver.ResolveInstanceName(resolveCtx)
if err != nil {
h.logger.ErrorCtx(ctx, "cannot resolve instance name",
h.logger.ErrorCtx(
ctx,
"cannot resolve instance name",
log.String("source_id", source.ID.String()),
log.String("provider", dbConnector.Provider.String()),
log.Error(err),
@@ -166,7 +174,9 @@ func (h *sourceNameHandler) Process(ctx context.Context, source coredata.AccessS
}
if instanceName == "" {
h.logger.InfoCtx(ctx, "instance name is empty, keeping generic name",
h.logger.InfoCtx(
ctx,
"instance name is empty, keeping generic name",
log.String("source_id", source.ID.String()),
log.String("provider", dbConnector.Provider.String()),
)
@@ -177,7 +187,9 @@ func (h *sourceNameHandler) Process(ctx context.Context, source coredata.AccessS
displayName := drivers.ProviderDisplayName(dbConnector.Provider)
newName := displayName + " " + instanceName
h.logger.InfoCtx(ctx, "resolved source name",
h.logger.InfoCtx(
ctx,
"resolved source name",
log.String("source_id", source.ID.String()),
log.String("old_name", source.Name),
log.String("new_name", newName),