Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement ProviderFactory and ProviderClassFactory #3131

Merged
merged 2 commits into from
Apr 19, 2024
Merged

Conversation

dmjb
Copy link
Contributor

@dmjb dmjb commented Apr 18, 2024

Relates to #2845

Introduce two new constructs for creating providers. The top-level ProviderFactory takes the name/project or ID of a provider and instantiates it. In order to instantiate it, it checks the class of the provider, and delegates it to a ProviderClassFactory, which is responsible for creating the specific implementation of the provider. This split exists so that we can register new types of provider while minimizing changes to existing code.

It is intended that this will replace all uses of ProviderBuilder. ProviderBuilder requires the caller to know various details about provider creation, and is therefore a leaky abstraction. ProviderFactory is intended to provide a "black box" interface for creating providers which does not require various provider-creation dependencies to be passed around in the code.

This PR does not make use of the new interfaces. Instead, it just adds the implementations. This is to simplify review - the process of getting rid of the ProviderBuilder will involve a large number of small changes to many parts of code, and that will happen in another PR.

Some points worth noting:

  1. This does not provide a ProviderClassFactory for REST or Git since we
    do not use standalone instances of those providers at this point in
    time. It should be trivial to write factories for those at the point
    in time when we need them.
  2. Based on discussion with Ozz and Jakub, I have decided to assume that
    the provider class column of the providers table will not be null for
    valid providers. This approach uses the class to figure out which
    concrete type is used to instantiate the provider, and is orthogonal
    to the traits, which describe the set of interfaces which that
    provider can support. In a future PR, I will make the class column
    non-nullable.

Summary

Provide a brief overview of the changes and the issue being addressed.
Explain the rationale and any background necessary for understanding the changes.
List dependencies required by this change, if any.

Fixes #(related issue)

Change Type

Mark the type of change your PR introduces:

  • Bug fix (resolves an issue without affecting existing features)
  • Feature (adds new functionality without breaking changes)
  • Breaking change (may impact existing functionalities or require documentation updates)
  • Documentation (updates or additions to documentation)
  • Refactoring or test improvements (no bug fixes or new functionality)

Testing

Outline how the changes were tested, including steps to reproduce and any relevant configurations.
Attach screenshots if helpful.

Review Checklist:

  • Reviewed my own code for quality and clarity.
  • Added comments to complex or tricky code sections.
  • Updated any affected documentation.
  • Included tests that validate the fix or feature.
  • Checked that related changes are merged.

@dmjb dmjb requested a review from a team as a code owner April 18, 2024 15:15
@@ -0,0 +1,223 @@
// Copyright 2024 Stacklok, Inc.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code here is mostly copy-paste from ProviderBuilder. The main difference is that the process of figuring out which type of credential is determined dynamically here, whereas ProviderBuilder requires it to be figured out when the ProviderBuilder struct is instantiated.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another note: I have decided to rely on existing test code to verify this logic once it is wired up in the application.

}

func (p *providerFactory) BuildFromID(ctx context.Context, providerID uuid.UUID) (v1.Provider, error) {
config, err := p.store.GetByID(ctx, providerID)
Copy link
Contributor Author

@dmjb dmjb Apr 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've taken to referring to db.Provider as the "provider configuration" which I feel is an accurate description of its intended purpose. In future, I may introduce a PR to rename the struct type (but not change the table, the rename can be done using sqlc's configuration). This avoids the confusion from having two types called "Provider" in the system which are used for very different things.

@coveralls
Copy link

coveralls commented Apr 18, 2024

Coverage Status

coverage: 47.91% (-0.2%) from 48.143%
when pulling b62306e on provider-factory-iii
into 0796ff8 on main.

@dmjb dmjb mentioned this pull request Apr 18, 2024
10 tasks
@@ -133,3 +134,12 @@ func ParseAndValidate(rawConfig json.RawMessage, to any) error {

return nil
}

// As is a type-cast function for Providers
func As[T Provider](provider Provider) (T, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this used anywhere?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It will be used in the next PR. It will be used as a helper function for casting the Provider to a specific trait

Copy link
Contributor

@JAORMX JAORMX left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this lgtm and makes sense.

Relates to #2845

Introduce two new constructs for creating providers. The top-level
ProviderFactory takes the name/project or ID of a provider and
instantiates it. In order to instantiate it, it checks the class of the
provider, and delegates it to a ProviderClassFactory, which is
responsible for creating it. This split exists so that we can register
new types of provider while minimizing changes to existing code.

This PR does not make use of the new interfaces. Instead, it just adds
the implementations. This is to simplify review - the process of getting
rid of the ProviderBuilder will involve a large number of small changes
to many parts of code, and that will happen in another PR.

Some points worth noting:

1) This does not provide a ProviderClassFactory for REST or Git since we
   do not use standalone instances of those providers at this point in
   time. It should be trivial to write factories for those at the point
   in time when we need them.
2) Based on discussion with Ozz and Jakub, I have decided to assume that
   the provider class column of the providers table will not be null for
   valid providers. This approach uses the class to figure out which
   concrete type is used to instantiate the provider, and is orthogonal
   to the traits, which describe the set of interfaces which that
   provider can support. In a future PR, I will make the class column
   non-nullable.
return nil, fmt.Errorf("provider version not supported")
}

creds, err := g.getProviderCredentials(ctx, config)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are some places where we don't use the DB credential and pass a different credential into the provider constructor.
I think it's valid to not use a provider in those cases, or pass the credential into the function call.
I'm only calling it out here to be aware of it when we refactor that piece of code later.

@dmjb dmjb merged commit e204a53 into main Apr 19, 2024
20 checks passed
@dmjb dmjb deleted the provider-factory-iii branch April 19, 2024 14:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants