From bc87fa7726335218b08111c203fd32964c7935b6 Mon Sep 17 00:00:00 2001 From: Mike Mason Date: Mon, 17 Jul 2023 19:02:55 +0000 Subject: [PATCH] add variable and method comments --- internal/metal/metal.go | 5 +++++ internal/metal/models/doc.go | 2 ++ internal/metal/models/memberships.go | 1 + internal/metal/models/projects.go | 2 ++ internal/metal/models/users.go | 8 +++++++- internal/metal/providers/emapi/client.go | 5 +++++ internal/metal/providers/emapi/config.go | 1 + internal/metal/providers/emapi/doc.go | 2 ++ internal/metal/providers/emapi/errors.go | 1 + internal/metal/providers/emapi/memberships.go | 7 +++++++ internal/metal/providers/emapi/organizations.go | 6 ++++++ internal/metal/providers/emapi/projects.go | 6 ++++++ internal/metal/providers/emapi/users.go | 8 ++++++++ internal/metal/providers/emgql/client.go | 2 ++ internal/metal/providers/emgql/config.go | 2 +- internal/metal/providers/emgql/doc.go | 2 ++ internal/metal/providers/emgql/organizations.go | 4 +++- internal/metal/providers/emgql/projects.go | 4 +++- internal/metal/providers/emgql/users.go | 3 +++ internal/metal/providers/provider.go | 2 ++ internal/permissions/assignments.go | 7 +++++++ internal/permissions/client.go | 4 ++++ internal/permissions/config.go | 1 + internal/permissions/doc.go | 2 ++ internal/permissions/errors.go | 17 +++++++++++++---- internal/permissions/options.go | 2 ++ internal/permissions/relationships.go | 7 +++++++ internal/permissions/roles.go | 9 +++++++++ internal/service/organizations.go | 4 ++++ internal/service/process_memberships.go | 10 ++++++++++ internal/service/process_relationships.go | 14 +++++++------- internal/service/projects.go | 4 ++++ internal/service/relationships.go | 10 +++++++++- internal/service/service.go | 4 ++++ internal/service/users.go | 5 +++++ 35 files changed, 157 insertions(+), 16 deletions(-) create mode 100644 internal/metal/models/doc.go create mode 100644 internal/metal/providers/emapi/doc.go create mode 100644 internal/metal/providers/emgql/doc.go create mode 100644 internal/permissions/doc.go diff --git a/internal/metal/metal.go b/internal/metal/metal.go index b48c1c9..ceab2c9 100644 --- a/internal/metal/metal.go +++ b/internal/metal/metal.go @@ -17,22 +17,27 @@ type Client struct { provider providers.Provider } +// GetOrganizationDetails fetches the organization id provided with its memberships. func (c *Client) GetOrganizationDetails(ctx context.Context, id gidx.PrefixedID) (*models.OrganizationDetails, error) { return c.provider.GetOrganizationDetails(ctx, id) } +// GetProjectDetails fetchs the provided project id with membership information. func (c *Client) GetProjectDetails(ctx context.Context, id gidx.PrefixedID) (*models.ProjectDetails, error) { return c.provider.GetProjectDetails(ctx, id) } +// GetUserDetails fetches the provided user id. func (c *Client) GetUserDetails(ctx context.Context, id gidx.PrefixedID) (*models.UserDetails, error) { return c.provider.GetUserDetails(ctx, id) } +// GetUserOrganizationRole returns the role for the user in the organization. func (c *Client) GetUserOrganizationRole(ctx context.Context, userID, orgID gidx.PrefixedID) (string, error) { return c.provider.GetUserOrganizationRole(ctx, userID, orgID) } +// GetUserProjectRole returns the role for the user in the project. func (c *Client) GetUserProjectRole(ctx context.Context, userID, projID gidx.PrefixedID) (string, error) { return c.provider.GetUserProjectRole(ctx, userID, projID) } diff --git a/internal/metal/models/doc.go b/internal/metal/models/doc.go new file mode 100644 index 0000000..f03f4f7 --- /dev/null +++ b/internal/metal/models/doc.go @@ -0,0 +1,2 @@ +// Package models defines generic models each provider must be able to return. +package models diff --git a/internal/metal/models/memberships.go b/internal/metal/models/memberships.go index be61413..ad5446b 100644 --- a/internal/metal/models/memberships.go +++ b/internal/metal/models/memberships.go @@ -1,5 +1,6 @@ package models +// Membership contains metal membership details. type Membership[T any] struct { ID string `json:"id"` User *UserDetails `json:"user"` diff --git a/internal/metal/models/projects.go b/internal/metal/models/projects.go index f2a0869..656137f 100644 --- a/internal/metal/models/projects.go +++ b/internal/metal/models/projects.go @@ -2,6 +2,7 @@ package models import "go.infratographer.com/x/gidx" +// ProjectDetails contains project and membership information. type ProjectDetails struct { ID string `json:"id"` Name string `json:"name"` @@ -9,6 +10,7 @@ type ProjectDetails struct { Organization *OrganizationDetails `json:"organization"` } +// PrefixedID returns the prefixed id for the project. func (d *ProjectDetails) PrefixedID() gidx.PrefixedID { if d.ID == "" { return gidx.NullPrefixedID diff --git a/internal/metal/models/users.go b/internal/metal/models/users.go index 8042c74..ecb1c4d 100644 --- a/internal/metal/models/users.go +++ b/internal/metal/models/users.go @@ -8,10 +8,14 @@ import ( ) const ( - MetalUserIssuer = "https://auth.equinix.com/" + // MetalUserIssuer is the issuer that is used for metal api token users. + MetalUserIssuer = "https://auth.equinix.com/" + + // MetaluserIssuerIDPrefix is the issuer id prefix added by the issuer. MetalUserIssuerIDPrefix = "auth|" ) +// UserDetails contains the user information. type UserDetails struct { id *gidx.PrefixedID ID string `json:"id"` @@ -21,6 +25,7 @@ type UserDetails struct { Roles []string `json:"roles"` } +// PrefixedID returns the identity prefixed id for the user. func (d *UserDetails) PrefixedID() gidx.PrefixedID { if d.id != nil { return *d.id @@ -44,6 +49,7 @@ func (d *UserDetails) PrefixedID() gidx.PrefixedID { return *d.id } +// GenerateSubjectID builds a identity prefixed id with the provided prefix for the issuer and subject. func GenerateSubjectID(prefix, iss, sub string) (gidx.PrefixedID, error) { // Concatenate the iss and sub values, then hash them issSub := iss + sub diff --git a/internal/metal/providers/emapi/client.go b/internal/metal/providers/emapi/client.go index 98279b3..c8a14e1 100644 --- a/internal/metal/providers/emapi/client.go +++ b/internal/metal/providers/emapi/client.go @@ -25,12 +25,14 @@ const ( staffHeaderValue = "true" ) +// DefaultHTTPClient is the default http client used if no client is provided. var DefaultHTTPClient = &http.Client{ Timeout: defaultHTTPTimeout, } var _ providers.Provider = &Client{} +// Client is the client to interact with the equinix metal api. type Client struct { logger *zap.SugaredLogger httpClient *http.Client @@ -39,6 +41,8 @@ type Client struct { consumerToken string } +// Do executes the provided request. +// If the out value is provided, the response will attempt to be json decoded. func (c *Client) Do(req *http.Request, out any) (*http.Response, error) { if c.authToken != "" { req.Header.Set(authHeader, c.authToken) @@ -68,6 +72,7 @@ func (c *Client) Do(req *http.Request, out any) (*http.Response, error) { return resp, nil } +// DoRequest creates a new request from the provided parameters and executes the request. func (c *Client) DoRequest(ctx context.Context, method, path string, body io.Reader, out any) (*http.Response, error) { path = strings.TrimPrefix(path, c.baseURL.Path) diff --git a/internal/metal/providers/emapi/config.go b/internal/metal/providers/emapi/config.go index bbb939a..6627a41 100644 --- a/internal/metal/providers/emapi/config.go +++ b/internal/metal/providers/emapi/config.go @@ -17,6 +17,7 @@ type Config struct { ConsumerToken string } +// Populated checks if any field has been populated. func (c Config) Populated() bool { return c.AuthToken != "" || c.ConsumerToken != "" || c.BaseURL != "" } diff --git a/internal/metal/providers/emapi/doc.go b/internal/metal/providers/emapi/doc.go new file mode 100644 index 0000000..6b38602 --- /dev/null +++ b/internal/metal/providers/emapi/doc.go @@ -0,0 +1,2 @@ +// Package emapi implement a metal provider which fetches details from the Equinix Metal API. +package emapi diff --git a/internal/metal/providers/emapi/errors.go b/internal/metal/providers/emapi/errors.go index 5eb2931..ab6ded5 100644 --- a/internal/metal/providers/emapi/errors.go +++ b/internal/metal/providers/emapi/errors.go @@ -2,4 +2,5 @@ package emapi import "errors" +// ErrBaseURLRequired is returned if no base url is provided. var ErrBaseURLRequired = errors.New("emapi base url required") diff --git a/internal/metal/providers/emapi/memberships.go b/internal/metal/providers/emapi/memberships.go index 4a36662..857497b 100644 --- a/internal/metal/providers/emapi/memberships.go +++ b/internal/metal/providers/emapi/memberships.go @@ -4,10 +4,13 @@ import ( "go.equinixmetal.net/infra9-metal-bridge/internal/metal/models" ) +// Roles contains a list of roles. type Roles []string +// Memberships contains a list of memberships type Memberships []*Membership +// ToDetailsWithOrganizationDetails convers the memberships to generic membership models with organization details. func (m Memberships) ToDetailsWithOrganizationDetails(orgDetails *models.OrganizationDetails) []*models.Membership[models.OrganizationDetails] { memberships := make([]*models.Membership[models.OrganizationDetails], len(m)) @@ -28,6 +31,7 @@ func (m Memberships) ToDetailsWithOrganizationDetails(orgDetails *models.Organiz return memberships } +// ToDetailsWithProjectDetails convers the memberships to generic membership models with project details. func (m Memberships) ToDetailsWithProjectDetails(projDetails *models.ProjectDetails) []*models.Membership[models.ProjectDetails] { memberships := make([]*models.Membership[models.ProjectDetails], len(m)) @@ -48,6 +52,7 @@ func (m Memberships) ToDetailsWithProjectDetails(projDetails *models.ProjectDeta return memberships } +// Membership contains membership information. type Membership struct { client *Client @@ -57,6 +62,7 @@ type Membership struct { User *User `json:"user"` } +// ToDetailsWithOrganizationDetails convers the membership to generic membership model with organization details. func (m *Membership) ToDetailsWithOrganizationDetails(orgDetails *models.OrganizationDetails) *models.Membership[models.OrganizationDetails] { if m.ID == "" { return nil @@ -70,6 +76,7 @@ func (m *Membership) ToDetailsWithOrganizationDetails(orgDetails *models.Organiz } } +// ToDetailsWithOrganizationDetails convers the membership to generic membership model with organization details. func (m *Membership) ToDetailsWithProjectDetails(projDetails *models.ProjectDetails) *models.Membership[models.ProjectDetails] { if m.ID == "" { return nil diff --git a/internal/metal/providers/emapi/organizations.go b/internal/metal/providers/emapi/organizations.go index 639e44f..5f2577c 100644 --- a/internal/metal/providers/emapi/organizations.go +++ b/internal/metal/providers/emapi/organizations.go @@ -14,8 +14,10 @@ const ( organizationsPath = "/organizations" ) +// Organizations contains a list of organizations. type Organizations []*Organization +// ToDetails converts to a generic model organization details. func (o Organizations) ToDetails() []*models.OrganizationDetails { orgs := make([]*models.OrganizationDetails, len(o)) @@ -36,6 +38,7 @@ func (o Organizations) ToDetails() []*models.OrganizationDetails { return orgs } +// Organization contains organization information. type Organization struct { client *Client @@ -47,6 +50,7 @@ type Organization struct { Projects Projects `json:"projects"` } +// ToDetails converts the object to a generic orgnization details. func (o *Organization) ToDetails() *models.OrganizationDetails { var id string @@ -69,6 +73,7 @@ func (o *Organization) ToDetails() *models.OrganizationDetails { return details } +// getOrganizationWithMemberships fetches an organization from the equinix metal api with membership user information. func (c *Client) getOrganizationWithMemberships(ctx context.Context, id string) (*Organization, error) { var org Organization @@ -80,6 +85,7 @@ func (c *Client) getOrganizationWithMemberships(ctx context.Context, id string) return &org, nil } +// GetOrganizationDetails fetches the organization id provided with its memberships. func (c *Client) GetOrganizationDetails(ctx context.Context, id gidx.PrefixedID) (*models.OrganizationDetails, error) { org, err := c.getOrganizationWithMemberships(ctx, id.String()[gidx.PrefixPartLength+1:]) if err != nil { diff --git a/internal/metal/providers/emapi/projects.go b/internal/metal/providers/emapi/projects.go index 71639a8..42ad6a4 100644 --- a/internal/metal/providers/emapi/projects.go +++ b/internal/metal/providers/emapi/projects.go @@ -14,8 +14,10 @@ const ( projectsPath = "/projects" ) +// Projects contains a list of projects. type Projects []*Project +// ToDetails converts the objects to generic project details. func (p Projects) ToDetails() []*models.ProjectDetails { projects := make([]*models.ProjectDetails, len(p)) @@ -36,6 +38,7 @@ func (p Projects) ToDetails() []*models.ProjectDetails { return projects } +// Project contains project information. type Project struct { client *Client @@ -47,6 +50,7 @@ type Project struct { Organization *Organization `json:"organization"` } +// ToDetails converts the project to generic project details. func (p *Project) ToDetails() *models.ProjectDetails { var id string @@ -69,6 +73,7 @@ func (p *Project) ToDetails() *models.ProjectDetails { return details } +// getProjectWithMemberships fetches the provided project with membership information. func (c *Client) getProjectWithMemberships(ctx context.Context, id string) (*Project, error) { var project Project @@ -80,6 +85,7 @@ func (c *Client) getProjectWithMemberships(ctx context.Context, id string) (*Pro return &project, nil } +// GetProjectDetails fetchs the provided project id with membership information. func (c *Client) GetProjectDetails(ctx context.Context, id gidx.PrefixedID) (*models.ProjectDetails, error) { project, err := c.getProjectWithMemberships(ctx, id.String()[gidx.PrefixPartLength+1:]) if err != nil { diff --git a/internal/metal/providers/emapi/users.go b/internal/metal/providers/emapi/users.go index 966cbef..850db7b 100644 --- a/internal/metal/providers/emapi/users.go +++ b/internal/metal/providers/emapi/users.go @@ -14,8 +14,10 @@ const ( usersPath = "/users" ) +// Users contains a list of users. type Users []*User +// ToDetails converts the objects to generic user details. func (u Users) ToDetails() []*models.UserDetails { users := make([]*models.UserDetails, len(u)) @@ -36,6 +38,7 @@ func (u Users) ToDetails() []*models.UserDetails { return users } +// User contains user information. type User struct { client *Client @@ -46,6 +49,7 @@ type User struct { Projects Projects `json:"projects"` } +// ToDetails converts the user to generic user details. func (u *User) ToDetails() *models.UserDetails { var id string @@ -65,6 +69,7 @@ func (u *User) ToDetails() *models.UserDetails { } } +// getUser fetches the provided user. func (c *Client) getUser(ctx context.Context, id string) (*User, error) { var user User @@ -76,6 +81,7 @@ func (c *Client) getUser(ctx context.Context, id string) (*User, error) { return &user, nil } +// GetUserDetails fetches the provided user id. func (c *Client) GetUserDetails(ctx context.Context, id gidx.PrefixedID) (*models.UserDetails, error) { user, err := c.getUser(ctx, id.String()[gidx.PrefixPartLength+1:]) if err != nil { @@ -85,10 +91,12 @@ func (c *Client) GetUserDetails(ctx context.Context, id gidx.PrefixedID) (*model return user.ToDetails(), nil } +// GetUserOrganizationRole returns collaborator for all organizations. func (c *Client) GetUserOrganizationRole(ctx context.Context, userID, orgID gidx.PrefixedID) (string, error) { return "collaborator", nil } +// GetUserProjectRole returns collaborator for all projects. func (c *Client) GetUserProjectRole(ctx context.Context, userID, projectID gidx.PrefixedID) (string, error) { return "collaborator", nil } diff --git a/internal/metal/providers/emgql/client.go b/internal/metal/providers/emgql/client.go index 2e992b3..5b5fec3 100644 --- a/internal/metal/providers/emgql/client.go +++ b/internal/metal/providers/emgql/client.go @@ -14,12 +14,14 @@ const ( defaultHTTPTimeout = 5 * time.Second ) +// DefaultHTTPClient is the default http client used if no client is provided. var DefaultHTTPClient = &http.Client{ Timeout: defaultHTTPTimeout, } var _ providers.Provider = &Client{} +// Client is the client to interact with the equinix metal graphql service. type Client struct { logger *zap.SugaredLogger httpClient *http.Client diff --git a/internal/metal/providers/emgql/config.go b/internal/metal/providers/emgql/config.go index 86f8ba9..7a23789 100644 --- a/internal/metal/providers/emgql/config.go +++ b/internal/metal/providers/emgql/config.go @@ -2,11 +2,11 @@ package emgql // Config provides configuration for connecting to the Equinix Metal API provider. type Config struct { - // BaseURL is the baseurl to use when connecting to the Equinix Metal API Provider. BaseURL string } +// Populated checks if any field has been populated. func (c Config) Populated() bool { return c.BaseURL != "" } diff --git a/internal/metal/providers/emgql/doc.go b/internal/metal/providers/emgql/doc.go new file mode 100644 index 0000000..9dba58f --- /dev/null +++ b/internal/metal/providers/emgql/doc.go @@ -0,0 +1,2 @@ +// Package emgql implements a metal provider which fetches details from the Equinix Metal GraphQL. +package emgql diff --git a/internal/metal/providers/emgql/organizations.go b/internal/metal/providers/emgql/organizations.go index b3b354a..8338f21 100644 --- a/internal/metal/providers/emgql/organizations.go +++ b/internal/metal/providers/emgql/organizations.go @@ -3,10 +3,12 @@ package emgql import ( "context" - "go.equinixmetal.net/infra9-metal-bridge/internal/metal/models" "go.infratographer.com/x/gidx" + + "go.equinixmetal.net/infra9-metal-bridge/internal/metal/models" ) +// GetOrganizationDetails fetches the organization id provided with its memberships. func (c *Client) GetOrganizationDetails(ctx context.Context, id gidx.PrefixedID) (*models.OrganizationDetails, error) { return nil, nil } diff --git a/internal/metal/providers/emgql/projects.go b/internal/metal/providers/emgql/projects.go index f0caf29..9b449d2 100644 --- a/internal/metal/providers/emgql/projects.go +++ b/internal/metal/providers/emgql/projects.go @@ -3,10 +3,12 @@ package emgql import ( "context" - "go.equinixmetal.net/infra9-metal-bridge/internal/metal/models" "go.infratographer.com/x/gidx" + + "go.equinixmetal.net/infra9-metal-bridge/internal/metal/models" ) +// GetProjectDetails fetchs the provided project id with membership information. func (c *Client) GetProjectDetails(ctx context.Context, id gidx.PrefixedID) (*models.ProjectDetails, error) { return nil, nil } diff --git a/internal/metal/providers/emgql/users.go b/internal/metal/providers/emgql/users.go index 52e36c1..2f4ec4b 100644 --- a/internal/metal/providers/emgql/users.go +++ b/internal/metal/providers/emgql/users.go @@ -8,14 +8,17 @@ import ( "go.equinixmetal.net/infra9-metal-bridge/internal/metal/models" ) +// GetUserDetails fetches the provided user id. func (c *Client) GetUserDetails(ctx context.Context, id gidx.PrefixedID) (*models.UserDetails, error) { return nil, nil } +// GetUserOrganizationRole returns collaborator for all organizations. func (c *Client) GetUserOrganizationRole(ctx context.Context, userID, orgID gidx.PrefixedID) (string, error) { return "collaborator", nil } +// GetUserProjectRole returns collaborator for all projects. func (c *Client) GetUserProjectRole(ctx context.Context, userID, projID gidx.PrefixedID) (string, error) { return "collaborator", nil } diff --git a/internal/metal/providers/provider.go b/internal/metal/providers/provider.go index ddbc947..5b7b2d6 100644 --- a/internal/metal/providers/provider.go +++ b/internal/metal/providers/provider.go @@ -1,3 +1,4 @@ +// Package providers defines the provider interface for fetching metal resources. package providers import ( @@ -8,6 +9,7 @@ import ( "go.equinixmetal.net/infra9-metal-bridge/internal/metal/models" ) +// Provider defines the provider implementation. type Provider interface { GetOrganizationDetails(ctx context.Context, id gidx.PrefixedID) (*models.OrganizationDetails, error) GetProjectDetails(ctx context.Context, id gidx.PrefixedID) (*models.ProjectDetails, error) diff --git a/internal/permissions/assignments.go b/internal/permissions/assignments.go index ca23d21..f2dadd4 100644 --- a/internal/permissions/assignments.go +++ b/internal/permissions/assignments.go @@ -8,20 +8,24 @@ import ( "go.infratographer.com/x/gidx" ) +// RoleAssign is the role assignment request body. type RoleAssign struct { SubjectID string `json:"subject_id"` } +// RoleAssignResponse is the response from a role assignment. type RoleAssignResponse struct { Success bool `json:"success"` } +// roleAssignmentData is the response from listing a role assignment type roleAssignmentData struct { Data []struct { SubjectID string `json:"subject_id"` } `json:"data"` } +// AssignRole assigns the provided member ID to the given role ID. func (c *Client) AssignRole(ctx context.Context, roleID gidx.PrefixedID, memberID gidx.PrefixedID) error { path := fmt.Sprintf("/api/v1/roles/%s/assignments", roleID.String()) @@ -45,6 +49,7 @@ func (c *Client) AssignRole(ctx context.Context, roleID gidx.PrefixedID, memberI return nil } +// UnassignRole removes the provided member ID from the given role ID. func (c *Client) UnassignRole(ctx context.Context, roleID gidx.PrefixedID, memberID gidx.PrefixedID) error { path := fmt.Sprintf("/api/v1/roles/%s/assignments", roleID.String()) @@ -68,6 +73,7 @@ func (c *Client) UnassignRole(ctx context.Context, roleID gidx.PrefixedID, membe return nil } +// ListRoleAssignments lists all assignments for the given role. func (c *Client) ListRoleAssignments(ctx context.Context, roleID gidx.PrefixedID) ([]gidx.PrefixedID, error) { path := fmt.Sprintf("/api/v1/roles/%s/assignments", roleID.String()) @@ -91,6 +97,7 @@ func (c *Client) ListRoleAssignments(ctx context.Context, roleID gidx.PrefixedID return assignments, nil } +// RoleHasAssignment gets the assignments for the given role and check for the provided member id. func (c *Client) RoleHasAssignment(ctx context.Context, roleID gidx.PrefixedID, memberID gidx.PrefixedID) (bool, error) { assignments, err := c.ListRoleAssignments(ctx, roleID) if err != nil { diff --git a/internal/permissions/client.go b/internal/permissions/client.go index 38d3106..1ae0f20 100644 --- a/internal/permissions/client.go +++ b/internal/permissions/client.go @@ -21,6 +21,7 @@ var defaultHTTPClient = &http.Client{ Timeout: 5 * time.Second, } +// Client is the permissions client. type Client struct { logger *zap.SugaredLogger @@ -32,6 +33,8 @@ type Client struct { allowURL *url.URL } +// Do executes the provided request. +// If the out value is provided, the response will attempt to be json decoded. func (c *Client) Do(req *http.Request, out any) (*http.Response, error) { if c.token != "" { req.Header.Set(echo.HeaderAuthorization, "Bearer "+c.token) @@ -55,6 +58,7 @@ func (c *Client) Do(req *http.Request, out any) (*http.Response, error) { return resp, nil } +// DoRequest creates a new request from the provided parameters and executes the request. func (c *Client) DoRequest(ctx context.Context, method, path string, body io.Reader, out any) (*http.Response, error) { path = strings.TrimPrefix(path, c.baseURL.Path) diff --git a/internal/permissions/config.go b/internal/permissions/config.go index ccc21d7..f41d9cc 100644 --- a/internal/permissions/config.go +++ b/internal/permissions/config.go @@ -18,6 +18,7 @@ type Config struct { BearerToken string } +// MustViperFlags registers command flags along with the viper bindings. func MustViperFlags(v *viper.Viper, flags *pflag.FlagSet) { flags.String("permissions-baseurl", "", "permissions base url") viperx.MustBindFlag(v, "permissions.baseurl", flags.Lookup("permissions-baseurl")) diff --git a/internal/permissions/doc.go b/internal/permissions/doc.go new file mode 100644 index 0000000..5a481e1 --- /dev/null +++ b/internal/permissions/doc.go @@ -0,0 +1,2 @@ +// Package permissions implements a Permissions API client for fetching and manipulating relationships and role assignments. +package permissions diff --git a/internal/permissions/errors.go b/internal/permissions/errors.go index 877b74d..0a179eb 100644 --- a/internal/permissions/errors.go +++ b/internal/permissions/errors.go @@ -3,9 +3,18 @@ package permissions import "errors" var ( - ErrRoleNotFound = errors.New("role not found") - ErrAssignmentFailed = errors.New("assignment failed") - ErrUnassignmentFailed = errors.New("unassignment failed") - ErrUnexpectedRoleDeleteFailed = errors.New("unknown role delete error") + // ErrRoleNotFound is returned when no role is found for a given list of actions. + ErrRoleNotFound = errors.New("role not found") + + // ErrAssignmentFailed is returned when a user assignment to a role fails. + ErrAssignmentFailed = errors.New("assignment failed") + + // ErrUnassignmentFailed is returned when a user assignment is removed from a role fails. + ErrUnassignmentFailed = errors.New("unassignment failed") + + // ErrUnexpectedRoleDeleteFailed is returned when an unknown error is returned when deleting a role. + ErrUnexpectedRoleDeleteFailed = errors.New("unknown role delete error") + + // ErrUnexpectedRelationshipDeleteFailed is returned when an unknown error is returned when deleting a relationship. ErrUnexpectedRelationshipDeleteFailed = errors.New("unknown relationship delete error") ) diff --git a/internal/permissions/options.go b/internal/permissions/options.go index 30942f5..61a3b2f 100644 --- a/internal/permissions/options.go +++ b/internal/permissions/options.go @@ -4,8 +4,10 @@ import ( "go.uber.org/zap" ) +// Option is a client configuration option definition. type Option func(*Client) error +// WithLogger sets the logger for the client. func WithLogger(logger *zap.SugaredLogger) Option { return func(c *Client) error { c.logger = logger diff --git a/internal/permissions/relationships.go b/internal/permissions/relationships.go index 7da8e25..50cf314 100644 --- a/internal/permissions/relationships.go +++ b/internal/permissions/relationships.go @@ -15,21 +15,25 @@ type resourceRelationship struct { SubjectID string `json:"subject_id"` } +// ResourceRelationship defines the resource to subject relationship. type ResourceRelationship struct { ResourceID gidx.PrefixedID Relation string SubjectID gidx.PrefixedID } +// ResourceRelationshipRequest defines the request to relate to a subject. type ResourceRelationshipRequest struct { Relation string `json:"relation"` SubjectID string `json:"subject_id"` } +// ResourceRelationshipDeleteResponse defines the response for a delete of a relationship. type ResourceRelationshipDeleteResponse struct { Success bool `json:"success"` } +// DeleteResourceRelationship deletes the provided resources relationship to the given subject id. func (c *Client) DeleteResourceRelationship(ctx context.Context, resourceID gidx.PrefixedID, relation string, relatedResourceID gidx.PrefixedID) error { path := fmt.Sprintf("/api/v1/resources/%s/relationships", resourceID.String()) @@ -54,6 +58,9 @@ func (c *Client) DeleteResourceRelationship(ctx context.Context, resourceID gidx return nil } +// ListResourceRelationships returns resources related to the given id. +// If relatedResourceType is not provied, relations to subjects are returned. +// If relatedResourceType is provided, relations to the given resource are returned which match the given type. func (c *Client) ListResourceRelationships(ctx context.Context, resourceID gidx.PrefixedID, relatedResourceType string) ([]ResourceRelationship, error) { query := url.Values{ "resourceType": []string{relatedResourceType}, diff --git a/internal/permissions/roles.go b/internal/permissions/roles.go index 695eae9..0a1e4fa 100644 --- a/internal/permissions/roles.go +++ b/internal/permissions/roles.go @@ -9,25 +9,31 @@ import ( "golang.org/x/exp/slices" ) +// ResourceRoleCreate is the role create request. type ResourceRoleCreate struct { Actions []string `json:"actions"` } +// ResourceRoleCreateResponse is the role creation response. type ResourceRoleCreateResponse struct { ID string `json:"id"` } +// ResourceRoleDeleteResponse is the role deletion response. type ResourceRoleDeleteResponse struct { Success bool `json:"success"` } +// ResourceRoles is a listg of resource roles. type ResourceRoles []ResourceRole +// ResourceRole contains the role id and its actions. type ResourceRole struct { ID gidx.PrefixedID `json:"id"` Actions []string `json:"actions"` } +// CreateRole creates a role on the given resource id with the provided actions. func (c *Client) CreateRole(ctx context.Context, resourceID gidx.PrefixedID, actions []string) (gidx.PrefixedID, error) { path := fmt.Sprintf("/api/v1/resources/%s/roles", resourceID.String()) @@ -52,6 +58,7 @@ func (c *Client) CreateRole(ctx context.Context, resourceID gidx.PrefixedID, act return roleID, nil } +// DeleteRole deletes the provided role. func (c *Client) DeleteRole(ctx context.Context, roleID gidx.PrefixedID) error { path := fmt.Sprintf("/api/v1/roles/%s", roleID.String()) @@ -68,6 +75,7 @@ func (c *Client) DeleteRole(ctx context.Context, roleID gidx.PrefixedID) error { return nil } +// ListResourceRoles fetches all roles assigned to the provided resource. func (c *Client) ListResourceRoles(ctx context.Context, resourceID gidx.PrefixedID) (ResourceRoles, error) { path := fmt.Sprintf("/api/v1/resources/%s/roles", resourceID.String()) @@ -82,6 +90,7 @@ func (c *Client) ListResourceRoles(ctx context.Context, resourceID gidx.Prefixed return response.Data, nil } +// FindResourceRoleByActions fetches roles assigned to the provided resource and finds the first role where the actions match the provided actions. func (c *Client) FindResourceRoleByActions(ctx context.Context, resourceID gidx.PrefixedID, actions []string) (ResourceRole, error) { roles, err := c.ListResourceRoles(ctx, resourceID) if err != nil { diff --git a/internal/service/organizations.go b/internal/service/organizations.go index 01ad064..1f5ba80 100644 --- a/internal/service/organizations.go +++ b/internal/service/organizations.go @@ -11,6 +11,7 @@ import ( const organizationEvent = "metalorganization" +// buildOrganizationRelationships compiles all relations into a relationships object to be processed by the processors. func (s *service) buildOrganizationRelationships(org *models.OrganizationDetails) (Relationships, error) { relations := Relationships{ Resource: org, @@ -46,6 +47,7 @@ func (s *service) buildOrganizationRelationships(org *models.OrganizationDetails return relations, nil } +// IsOrganizationID checks if the provided id has the metal organization prefix. func (s *service) IsOrganizationID(id gidx.PrefixedID) bool { if idType, ok := s.idPrefixMap[id.Prefix()]; ok { return idType == TypeOrganization @@ -54,6 +56,7 @@ func (s *service) IsOrganizationID(id gidx.PrefixedID) bool { return false } +// TouchOrganization initializes a sync for the provided organization id for relationships and memberships. func (s *service) TouchOrganization(ctx context.Context, id gidx.PrefixedID) error { logger := s.logger.With("organization.id", id.String()) @@ -84,6 +87,7 @@ func (s *service) TouchOrganization(ctx context.Context, id gidx.PrefixedID) err return nil } +// DeleteOrganization deletes the provided organization id. func (s *service) DeleteOrganization(ctx context.Context, id gidx.PrefixedID) error { err := s.publisher.PublishChange(ctx, organizationEvent, events.ChangeMessage{ SubjectID: id, diff --git a/internal/service/process_memberships.go b/internal/service/process_memberships.go index 543480b..8be2902 100644 --- a/internal/service/process_memberships.go +++ b/internal/service/process_memberships.go @@ -10,6 +10,8 @@ import ( "go.equinixmetal.net/infra9-metal-bridge/internal/permissions" ) +// syncMemberships determines the changes between what is wanted and what is live and executes on the differences. +// If skipDeletions is true, no deletes will be executed. func (s *service) syncMemberships(ctx context.Context, relationships Relationships, skipDeletions bool) (int, int) { if len(relationships.Memberships) == 0 { return 0, 0 @@ -170,6 +172,10 @@ func (s *service) syncMemberships(ctx context.Context, relationships Relationshi return rolesCreated + rolesDeleted, roleAssignments + roleUnassignments } +// mapResourceWants processes the provided memberships and returns two maps. +// A Role Key is computed based on a sorted slice of actions for each role. +// The first map is of Role Key -> list of actions +// The second map is of Role Key -> Member ID -> true func (s *service) mapResourceWants(memberships []ResourceMemberships) (map[string][]string, map[string]map[gidx.PrefixedID]bool) { roleActionsKey := make(map[string]string) @@ -196,6 +202,10 @@ func (s *service) mapResourceWants(memberships []ResourceMemberships) (map[strin return wantRoles, wantAssignments } +// mapResourceDetails fetches the provided ResourceID's live state and returns two maps and an error. +// A Role Key is computed based on a sorted slice of actions for each role. +// The first map is of Role Key -> Permissions Resource Role +// The second map is of Role Key -> Member ID -> true func (s *service) mapResourceDetails(ctx context.Context, resourceID gidx.PrefixedID) (map[string]permissions.ResourceRole, map[string]map[gidx.PrefixedID]bool, error) { roles := make(map[string]permissions.ResourceRole) assignments := make(map[string]map[gidx.PrefixedID]bool) diff --git a/internal/service/process_relationships.go b/internal/service/process_relationships.go index 8b8e1d4..ebee2cd 100644 --- a/internal/service/process_relationships.go +++ b/internal/service/process_relationships.go @@ -9,13 +9,9 @@ import ( "go.equinixmetal.net/infra9-metal-bridge/internal/permissions" ) -type relationshipStats struct { - parentCreated bool - parentsDeleted int - subjectRelationshipsCreated int - subjectRelationshipsDeleted int -} - +// processRelationships determines the changes between what is wanted and what is live and executes on the differences. +// Relationship creations use events. +// Relationship deletions use the api, as delete events delete all related resources and not just the provided ones. func (s *service) processRelationships(ctx context.Context, eventType string, relationships Relationships) int { rlogger := s.logger.With("resource.id", relationships.Resource.PrefixedID()) @@ -161,6 +157,7 @@ func (s *service) processRelationships(ctx context.Context, eventType string, re return changes } +// mapRelationWants returns the parent relation if provided and a map of Subjects -> relation. func (s *service) mapRelationWants(relationships Relationships) (*Relation, map[gidx.PrefixedID]RelationshipType) { var wantParent *Relation @@ -177,6 +174,9 @@ func (s *service) mapRelationWants(relationships Relationships) (*Relation, map[ return wantParent, wantSubject } +// getRelationshipMap fetches the provided resources relationships, as the source resource and the destination subject. +// Returned are two maps, the first maps Subject IDs -> Relationship +// The second map, maps Resource IDs -> relationship func (s *service) getRelationshipMap(ctx context.Context, resource IDPrefixableResource, relatedObjectType ObjectType) (map[gidx.PrefixedID]RelationshipType, map[gidx.PrefixedID]RelationshipType, error) { liveResource, err := s.perms.ListResourceRelationships(ctx, resource.PrefixedID(), "") if err != nil { diff --git a/internal/service/projects.go b/internal/service/projects.go index 7ad68f5..beb5ce1 100644 --- a/internal/service/projects.go +++ b/internal/service/projects.go @@ -11,6 +11,7 @@ import ( const projectEvent = "metalproject" +// buildProjectRelationships compiles all relations into a relationships object to be processed by the processors. func (s *service) buildProjectRelationships(project *models.ProjectDetails) (Relationships, error) { relations := Relationships{ Resource: project, @@ -39,6 +40,7 @@ func (s *service) buildProjectRelationships(project *models.ProjectDetails) (Rel return relations, nil } +// IsProjectID checks if the provided id has the metal project prefix. func (s *service) IsProjectID(id gidx.PrefixedID) bool { if idType, ok := s.idPrefixMap[id.Prefix()]; ok { return idType == TypeProject @@ -47,6 +49,7 @@ func (s *service) IsProjectID(id gidx.PrefixedID) bool { return false } +// TouchProject initializes a sync for the provided project id for relationships and memberships. func (s *service) TouchProject(ctx context.Context, id gidx.PrefixedID) error { logger := s.logger.With("project.id", id.String()) @@ -77,6 +80,7 @@ func (s *service) TouchProject(ctx context.Context, id gidx.PrefixedID) error { return nil } +// DeleteProject deletes the provided project id. func (s *service) DeleteProject(ctx context.Context, id gidx.PrefixedID) error { err := s.publisher.PublishChange(ctx, projectEvent, events.ChangeMessage{ SubjectID: id, diff --git a/internal/service/relationships.go b/internal/service/relationships.go index 252daf8..b06ee31 100644 --- a/internal/service/relationships.go +++ b/internal/service/relationships.go @@ -5,16 +5,22 @@ import ( ) const ( - RelateOwner RelationshipType = "owner" + // RelateOwner is the owner relationship type. + RelateOwner RelationshipType = "owner" + + // RelateParent is the parent relationship type. RelateParent RelationshipType = "parent" ) +// RelationshipType are relationship types. type RelationshipType string +// IDPrefixableResource ensures the the interface passed provides prefixed ids. type IDPrefixableResource interface { PrefixedID() gidx.PrefixedID } +// Relationships defines a resource and all possible relationships and memberships. type Relationships struct { Resource IDPrefixableResource Parent Relation @@ -23,11 +29,13 @@ type Relationships struct { Memberships []ResourceMemberships } +// Relation defines a relation to a resource. type Relation struct { Relation RelationshipType Resource IDPrefixableResource } +// ResourceMemberships defines a member and role. type ResourceMemberships struct { Role string Member IDPrefixableResource diff --git a/internal/service/service.go b/internal/service/service.go index aa4b2ed..a3f4a9c 100644 --- a/internal/service/service.go +++ b/internal/service/service.go @@ -29,8 +29,10 @@ var DefaultPrefixMap = map[string]ObjectType{ TypeUser.Prefix(): TypeUser, } +// ObjectType defines a type of object. type ObjectType string +// Prefix returns the objects id prefix. func (t ObjectType) Prefix() string { switch t { case TypeOrganization: @@ -44,6 +46,7 @@ func (t ObjectType) Prefix() string { } } +// String returns a string fo the object type. func (t ObjectType) String() string { return string(t) } @@ -96,6 +99,7 @@ func (r prefixedID) PrefixedID() gidx.PrefixedID { return r.id } +// New creates a new service. func New(publisher *events.Publisher, metal *metal.Client, perms *permissions.Client, options ...Option) (Service, error) { svc := &service{ publisher: publisher, diff --git a/internal/service/users.go b/internal/service/users.go index 71834c0..02ff9cd 100644 --- a/internal/service/users.go +++ b/internal/service/users.go @@ -6,6 +6,7 @@ import ( "go.infratographer.com/x/gidx" ) +// IsUser checks the provided id has the metal user prefix. func (s *service) IsUser(id gidx.PrefixedID) bool { if idType, ok := s.idPrefixMap[id.Prefix()]; ok { return idType == TypeUser @@ -14,6 +15,7 @@ func (s *service) IsUser(id gidx.PrefixedID) bool { return false } +// IsAssignableResource checks that the provided id is an id which can have memberships assignments. func (s *service) IsAssignableResource(id gidx.PrefixedID) bool { if idType, ok := s.idPrefixMap[id.Prefix()]; ok { switch idType { @@ -27,6 +29,7 @@ func (s *service) IsAssignableResource(id gidx.PrefixedID) bool { return false } +// Assignuser assigns the provided users to the given resource ids. func (s *service) AssignUser(ctx context.Context, userID gidx.PrefixedID, resourceIDs ...gidx.PrefixedID) error { var totalResources, rolesChanged, assignmentsChanged int @@ -68,6 +71,7 @@ func (s *service) AssignUser(ctx context.Context, userID gidx.PrefixedID, resour return nil } +// UnassignUser removes the assignment for the provided user id to the given resources. func (s *service) UnassignUser(ctx context.Context, userID gidx.PrefixedID, resourceIDs ...gidx.PrefixedID) error { for _, resourceID := range resourceIDs { rlogger := s.logger.With("user.id", userID, "resource.id", resourceID) @@ -119,6 +123,7 @@ func (s *service) UnassignUser(ctx context.Context, userID gidx.PrefixedID, reso return nil } +// getuserResourceRole fetches the appropriate object types user role for the given resource. func (s *service) getUserResourceRole(ctx context.Context, userID, resourceID gidx.PrefixedID) (string, error) { var ( role string