From f828546cfeb59e25c22e0d25d4c48f01faa5b2ad Mon Sep 17 00:00:00 2001 From: Mike Mason Date: Tue, 18 Jul 2023 13:36:41 +0000 Subject: [PATCH] make client interface --- internal/metal/metal.go | 25 ++++++++++++--------- internal/metal/options.go | 8 +++---- internal/permissions/assignments.go | 8 +++---- internal/permissions/client.go | 32 ++++++++++++++++++++------- internal/permissions/config.go | 6 ++--- internal/permissions/options.go | 10 ++++----- internal/permissions/relationships.go | 4 ++-- internal/permissions/roles.go | 8 +++---- internal/service/options.go | 4 ++-- internal/service/service.go | 13 +++++++---- 10 files changed, 72 insertions(+), 46 deletions(-) diff --git a/internal/metal/metal.go b/internal/metal/metal.go index ceab2c9..779958f 100644 --- a/internal/metal/metal.go +++ b/internal/metal/metal.go @@ -10,41 +10,46 @@ import ( "go.equinixmetal.net/infra9-metal-bridge/internal/metal/providers" ) -// Client is the Equinix Metal API Client struct. -type Client struct { +// Client is the Equinix Metal Client Interface. +type Client interface { + providers.Provider +} + +// client is the Equinix Metal Client struct. +type client struct { logger *zap.Logger 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) { +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) { +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) { +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) { +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) { +func (c *client) GetUserProjectRole(ctx context.Context, userID, projID gidx.PrefixedID) (string, error) { return c.provider.GetUserProjectRole(ctx, userID, projID) } -// New creates a new Client. -func New(options ...Option) (*Client, error) { - client := new(Client) +// New creates a new Equinix Metal Client. +func New(options ...Option) (Client, error) { + client := new(client) for _, opt := range options { if err := opt(client); err != nil { diff --git a/internal/metal/options.go b/internal/metal/options.go index 4d17efd..2b78af9 100644 --- a/internal/metal/options.go +++ b/internal/metal/options.go @@ -9,11 +9,11 @@ import ( ) // Option is a Client configuration Option definition. -type Option func(c *Client) error +type Option func(c *client) error // WithProvider sets the provider on the client. func WithProvider(provider providers.Provider) Option { - return func(c *Client) error { + return func(c *client) error { c.provider = provider return nil @@ -22,7 +22,7 @@ func WithProvider(provider providers.Provider) Option { // WithLogger sets the logger for the client. func WithLogger(logger *zap.Logger) Option { - return func(c *Client) error { + return func(c *client) error { c.logger = logger return nil @@ -31,7 +31,7 @@ func WithLogger(logger *zap.Logger) Option { // WithConfig applies all configurations defined in the config. func WithConfig(config Config) Option { - return func(c *Client) error { + return func(c *client) error { var options []Option if config.EMGQL.Populated() { diff --git a/internal/permissions/assignments.go b/internal/permissions/assignments.go index 895a4e2..9958e92 100644 --- a/internal/permissions/assignments.go +++ b/internal/permissions/assignments.go @@ -26,7 +26,7 @@ type roleAssignmentData struct { } // 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 { +func (c *client) AssignRole(ctx context.Context, roleID gidx.PrefixedID, memberID gidx.PrefixedID) error { path := fmt.Sprintf("/api/v1/roles/%s/assignments", roleID.String()) body, err := encodeJSON(RoleAssign{ @@ -50,7 +50,7 @@ func (c *Client) AssignRole(ctx context.Context, roleID gidx.PrefixedID, memberI } // 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 { +func (c *client) UnassignRole(ctx context.Context, roleID gidx.PrefixedID, memberID gidx.PrefixedID) error { path := fmt.Sprintf("/api/v1/roles/%s/assignments", roleID.String()) body, err := encodeJSON(RoleAssign{ @@ -74,7 +74,7 @@ func (c *Client) UnassignRole(ctx context.Context, roleID gidx.PrefixedID, membe } // ListRoleAssignments lists all assignments for the given role. -func (c *Client) ListRoleAssignments(ctx context.Context, roleID gidx.PrefixedID) ([]gidx.PrefixedID, error) { +func (c *client) ListRoleAssignments(ctx context.Context, roleID gidx.PrefixedID) ([]gidx.PrefixedID, error) { path := fmt.Sprintf("/api/v1/roles/%s/assignments", roleID.String()) var response roleAssignmentData @@ -98,7 +98,7 @@ func (c *Client) ListRoleAssignments(ctx context.Context, roleID gidx.PrefixedID } // 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) { +func (c *client) RoleHasAssignment(ctx context.Context, roleID gidx.PrefixedID, memberID gidx.PrefixedID) (bool, error) { assignments, err := c.ListRoleAssignments(ctx, roleID) if err != nil { return false, err diff --git a/internal/permissions/client.go b/internal/permissions/client.go index fcc8e83..d2eff88 100644 --- a/internal/permissions/client.go +++ b/internal/permissions/client.go @@ -12,21 +12,37 @@ import ( "time" "github.com/labstack/echo/v4" + "go.infratographer.com/x/gidx" "go.uber.org/zap" ) const ( defaultPermissionsURL = "https://permissions-api.hollow-a.sv15.metalkube.net" - DefaultHTTPClientTimeout = 5 * time.Second + defaultHTTPClientTimeout = 5 * time.Second ) +// DefaultHTTPClient is the default HTTP client for the Permissions Client. var DefaultHTTPClient = &http.Client{ - Timeout: DefaultHTTPClientTimeout, + Timeout: defaultHTTPClientTimeout, } -// Client is the permissions client. -type Client struct { +// Client defines the Permissions API client interface. +type Client interface { + AssignRole(ctx context.Context, roleID gidx.PrefixedID, memberID gidx.PrefixedID) error + CreateRole(ctx context.Context, resourceID gidx.PrefixedID, actions []string) (gidx.PrefixedID, error) + DeleteResourceRelationship(ctx context.Context, resourceID gidx.PrefixedID, relation string, relatedResourceID gidx.PrefixedID) error + DeleteRole(ctx context.Context, roleID gidx.PrefixedID) error + FindResourceRoleByActions(ctx context.Context, resourceID gidx.PrefixedID, actions []string) (ResourceRole, error) + ListResourceRelationships(ctx context.Context, resourceID gidx.PrefixedID, relatedResourceType string) ([]ResourceRelationship, error) + ListResourceRoles(ctx context.Context, resourceID gidx.PrefixedID) (ResourceRoles, error) + ListRoleAssignments(ctx context.Context, roleID gidx.PrefixedID) ([]gidx.PrefixedID, error) + RoleHasAssignment(ctx context.Context, roleID gidx.PrefixedID, memberID gidx.PrefixedID) (bool, error) + UnassignRole(ctx context.Context, roleID gidx.PrefixedID, memberID gidx.PrefixedID) error +} + +// client is the permissions client. +type client struct { logger *zap.SugaredLogger httpClient *http.Client @@ -39,7 +55,7 @@ type Client struct { // 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) { +func (c *client) Do(req *http.Request, out any) (*http.Response, error) { if c.token != "" { req.Header.Set(echo.HeaderAuthorization, "Bearer "+c.token) } @@ -63,7 +79,7 @@ func (c *Client) Do(req *http.Request, out any) (*http.Response, error) { } // 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) { +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) pathURL, err := url.Parse(path) @@ -109,8 +125,8 @@ func encodeJSON(v any) (*bytes.Buffer, error) { } // NewClient creats a new permissions client. -func NewClient(token string, options ...Option) (*Client, error) { - client := &Client{ +func NewClient(token string, options ...Option) (Client, error) { + client := &client{ logger: zap.NewNop().Sugar(), httpClient: DefaultHTTPClient, token: token, diff --git a/internal/permissions/config.go b/internal/permissions/config.go index f41d9cc..7a3e1bd 100644 --- a/internal/permissions/config.go +++ b/internal/permissions/config.go @@ -29,7 +29,7 @@ func MustViperFlags(v *viper.Viper, flags *pflag.FlagSet) { // WithConfig applies all configurations defined in the config. func WithConfig(config Config) Option { - return func(c *Client) error { + return func(c *client) error { var options []Option if config.BaseURL != "" { @@ -52,7 +52,7 @@ func WithConfig(config Config) Option { // WithBaseURL updates the baseurl used by the client. func WithBaseURL(baseURL string) Option { - return func(c *Client) error { + return func(c *client) error { u, err := url.Parse(baseURL) if err != nil { return fmt.Errorf("failed to parse emapi base url %s: %w", baseURL, err) @@ -66,7 +66,7 @@ func WithBaseURL(baseURL string) Option { // WithBearerToken sets the bearer token to authenticate the request with. func WithBearerToken(token string) Option { - return func(c *Client) error { + return func(c *client) error { c.token = token return nil diff --git a/internal/permissions/options.go b/internal/permissions/options.go index f3e0608..3b8a3b2 100644 --- a/internal/permissions/options.go +++ b/internal/permissions/options.go @@ -7,11 +7,11 @@ import ( ) // Option is a client configuration option definition. -type Option func(*Client) error +type Option func(*client) error // WithLogger sets the logger for the client. func WithLogger(logger *zap.SugaredLogger) Option { - return func(c *Client) error { + return func(c *client) error { c.logger = logger return nil @@ -19,9 +19,9 @@ func WithLogger(logger *zap.SugaredLogger) Option { } // WithHTTPClient sets the http client to be used by the client. -func WithHTTPClient(client *http.Client) Option { - return func(c *Client) error { - c.httpClient = client +func WithHTTPClient(httpClient *http.Client) Option { + return func(c *client) error { + c.httpClient = httpClient return nil } diff --git a/internal/permissions/relationships.go b/internal/permissions/relationships.go index a9e8f8d..5d79a1d 100644 --- a/internal/permissions/relationships.go +++ b/internal/permissions/relationships.go @@ -34,7 +34,7 @@ type ResourceRelationshipDeleteResponse struct { } // 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 { +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()) body, err := encodeJSON(ResourceRelationshipRequest{ @@ -61,7 +61,7 @@ func (c *Client) DeleteResourceRelationship(ctx context.Context, resourceID gidx // 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) { +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 cf1a884..08ad258 100644 --- a/internal/permissions/roles.go +++ b/internal/permissions/roles.go @@ -34,7 +34,7 @@ type ResourceRole struct { } // 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) { +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()) body, err := encodeJSON(ResourceRoleCreate{ @@ -59,7 +59,7 @@ func (c *Client) CreateRole(ctx context.Context, resourceID gidx.PrefixedID, act } // DeleteRole deletes the provided role. -func (c *Client) DeleteRole(ctx context.Context, roleID gidx.PrefixedID) error { +func (c *client) DeleteRole(ctx context.Context, roleID gidx.PrefixedID) error { path := fmt.Sprintf("/api/v1/roles/%s", roleID.String()) var response ResourceRoleDeleteResponse @@ -76,7 +76,7 @@ func (c *Client) DeleteRole(ctx context.Context, roleID gidx.PrefixedID) error { } // ListResourceRoles fetches all roles assigned to the provided resource. -func (c *Client) ListResourceRoles(ctx context.Context, resourceID gidx.PrefixedID) (ResourceRoles, error) { +func (c *client) ListResourceRoles(ctx context.Context, resourceID gidx.PrefixedID) (ResourceRoles, error) { path := fmt.Sprintf("/api/v1/resources/%s/roles", resourceID.String()) var response struct { @@ -91,7 +91,7 @@ func (c *Client) ListResourceRoles(ctx context.Context, resourceID gidx.Prefixed } // 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) { +func (c *client) FindResourceRoleByActions(ctx context.Context, resourceID gidx.PrefixedID, actions []string) (ResourceRole, error) { roles, err := c.ListResourceRoles(ctx, resourceID) if err != nil { return ResourceRole{}, err diff --git a/internal/service/options.go b/internal/service/options.go index f1f7e6f..fc7b8a3 100644 --- a/internal/service/options.go +++ b/internal/service/options.go @@ -21,7 +21,7 @@ func WithLogger(logger *zap.SugaredLogger) Option { } // WithMetalClient sets the Equinix Metal client used by the service. -func WithMetalClient(client *metal.Client) Option { +func WithMetalClient(client metal.Client) Option { return func(s *service) error { s.metal = client @@ -30,7 +30,7 @@ func WithMetalClient(client *metal.Client) Option { } // WithPermissionsClient sets the permissions client used by the service. -func WithPermissionsClient(client *permissions.Client) Option { +func WithPermissionsClient(client permissions.Client) Option { return func(s *service) error { s.perms = client diff --git a/internal/service/service.go b/internal/service/service.go index a3f4a9c..5ea1ab7 100644 --- a/internal/service/service.go +++ b/internal/service/service.go @@ -78,13 +78,18 @@ type Service interface { IsAssignableResource(id gidx.PrefixedID) bool } +// EventPublisher defines the required methods to publish events. +type EventPublisher interface { + PublishChange(ctx context.Context, subjectType string, change events.ChangeMessage) error +} + var _ Service = &service{} type service struct { logger *zap.SugaredLogger - publisher *events.Publisher - metal *metal.Client - perms *permissions.Client + publisher EventPublisher + metal metal.Client + perms permissions.Client idPrefixMap map[string]ObjectType rootResource prefixedID @@ -100,7 +105,7 @@ func (r prefixedID) PrefixedID() gidx.PrefixedID { } // New creates a new service. -func New(publisher *events.Publisher, metal *metal.Client, perms *permissions.Client, options ...Option) (Service, error) { +func New(publisher EventPublisher, metal metal.Client, perms permissions.Client, options ...Option) (Service, error) { svc := &service{ publisher: publisher, metal: metal,