-
Notifications
You must be signed in to change notification settings - Fork 43
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
Replace use of ProviderBuilder in controlplane #2841
Conversation
4183f6b
to
92c83a5
Compare
internal/providers/factory.go
Outdated
@@ -0,0 +1,243 @@ | |||
// Copyright 2024 Stacklok, Inc. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In a future PR, I'll write unit tests for these methods. Right now - their behaviour is validated through pre-existing tests which exercise these methods.
@@ -322,21 +322,24 @@ func (s *Server) ListRemoteRepositoriesFromProvider( | |||
} | |||
|
|||
for _, provider := range provs { | |||
// keep linter happy | |||
// TODO: this should not be necessary post Go 1.22 - update linter | |||
p := provider |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't we already use and enforce go 1.22?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See the comment I left in this code in my most recent commit - the gosec authors have decided not to get rid of this warning until all versions prior to 1.22 are unsupported.
internal/providers/factory.go
Outdated
// GitHubOptions contains options used when creating a GitHub instance | ||
// If omitted, these will be looked up in the database | ||
type GitHubOptions struct { | ||
Credential provinfv1.GitHubCredential |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We'll use credentials in all providers, why is this specific to GitHub? Did you check the OCI Providers POC?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In most cases, the credentials are loaded from the database. However, there are a few places where the Github trait is instantiated with credentials which are not in the DB - one is in internal/providers/service.go
(which is in this PR), and there are two in the CLI (not in this PR). In those cases, it is necessary to provide some Github specific details, namely the owner filter and the credentials used.
Your comment did make me realize that there was a bug in my change where I assumed Github credentials in more places than I should have. That has been fixed.
@@ -239,27 +236,6 @@ func (s *UnitTestSuite) TestHandleWebHookRepository() { | |||
repositoryID := uuid.New() | |||
projectID := uuid.New() | |||
|
|||
// create a test oauth2 token |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Previously, these operations were called to create a ProviderBuilder. In the TraitInstantiator, they are only called when the provider traits get instantiated. Since these test cases do not need a GitHub instance to be instantiated, they are unused.
92c83a5
to
8ab0533
Compare
This LGTM and I dig the direction. I'll let @eleftherias and @evankanderson chime in |
d2cc14e
to
9fd1b3e
Compare
// CanImplement returns true if the provider implements the given type. | ||
func (p *Provider) CanImplement(impl ProviderType) bool { | ||
return slices.Contains(p.Implements, impl) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was just thinking about this last night. I think this is a good first start, but I'm wondering whether it would make sense to rename db.Provider
to db.ProviderData
to emphasize that it's the data-at-rest representation rather than the active object representing a provider. Either that, or we just accept that a db.Provider
and a provider.Provider
are unrelated to each other, and maybe use the same name more places.
sqlc renaming feature: https://docs.sqlc.dev/en/stable/howto/rename.html#tables
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had a similar thought while working on this PR. I was thinking ProviderConfig
or similar.
I can investigate implementing this using the sqlc renaming functionality, but I feel like that deserves its own PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
internal/providers/instantiator.go
Outdated
// interfaces defined in `github.com/stacklok/minder/pkg/providers/v1` from | ||
// the providers stored in the database. | ||
type TraitInstantiator interface { | ||
GetGit(ctx context.Context, provider *db.Provider) (provinfv1.Git, error) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be part of the Provider
interface?
internal/providers/instantiator.go
Outdated
// interfaces defined in `github.com/stacklok/minder/pkg/providers/v1` from | ||
// the providers stored in the database. | ||
type TraitInstantiator interface { | ||
GetGit(ctx context.Context, provider *db.Provider) (provinfv1.Git, error) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you think of AsGit
rather than GetGit
, to make it more clear that this is effectively a down-casting operation?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good call, will make that change.
// GitHubOptions contains options used when creating a GitHub instance | ||
// If omitted, these will be looked up in the database | ||
type GitHubOptions struct { | ||
Credential provinfv1.GitHubCredential | ||
OwnerFilter *string | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not a fan of having these on GetGitHub
, rather than having them be part of the TraitInstantiator object state. Do you have a use case beyond verifyProviderTokenIdentity
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, the more I think about the approach I took, the less I like it. I might try another approach to avoid this hack.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking more at the code, I wonder if verifyProviderTokenIdentity
should really be verifyGitHubTokenIdentity
, and it should simply instantiate a github.Client
itself.
In both cases, that call is being made before the provider is created, so it's a bit of an odd case that might be be addressed by not trying to re-use the rest of the framework.
provBuilder, err := providers.GetProviderBuilder(ctx, prov, s.store, s.cryptoEngine, &s.cfg.Provider, pbOpts...) | ||
if err != nil { | ||
return fmt.Errorf("error building client: %w", err) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like removing this factory from all the calls, but I feel like I want two layers of objects:
-
A
ProviderFactory
which takes all the current arguments exceptctx
andprov
, and hasBuild(ctx, currentProject, providerName) (Provider, error)
. This combines lines 313-328 into a function, and doesn't directly exposedb.Provider
elsewhere. As we discover other cases where we need to find a provider, theProviderFactory
would get new methods, such asBuildForEntity(ctx, currentProject, entityType)
-
fooProvider
objects which wrap thedb.Provider
type with the selected implementation behavior, e.g.ghAppProvider
andghOAuthProvider
. Both of these would implementv1.Provider
, which would haveAs$Interface
(AsGit
,AsGitHub
,AsREST
, etc) methods for down-casting as well asImplements
if we need the checks.
I'm a little concerned with passing db row objects everywhere in that we end up making it hard to separate the schema from the business logic; ideally the db queries and token / crypto pieces would live in ghAppProvider
and ghOAuthProvider
and we have a central place to implement the mapping from user-provider context -> provider, rather than going directly to the database layer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I completely agree with you, but I also feel like it's a good idea to incrementally move towards that sort of model. Right now, too much knowledge of the creation of profiles exist in too many parts of the codebase. I want to reduce that coupling/lack of cohesion before introducing a new abstraction.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm fine with incremental progress, as long as we agree on the destination. I've just had bad experiences with incremental progress hiding disagreement on the final destination.
30c1e65
to
58dc44a
Compare
Create a new `TraitInstantiator` interface. This is essentially a dedicated factory interface which has a similar interface to ProviderBuilder. This change allows us to stub out the process of creating instances of the Provider interfaces/traits from a `db.Provider`. I changed the name since A) we are moving towards a model where Providers have "traits", and thus the responsibility is to instantiate instances of those traits and B) it always seemed strange to me that `ProviderBuilder` needed a `Provider` as an input.
Decided to take a different approach |
Relates to #2845
Create a new
TraitInstantiator
interface. This is essentially a dedicated factory interface with a similar interface toProviderBuilder
. This change allows us to stub out the process of creating instances of the Provider interfaces/traits from adb.Provider
. Furthermore, because many of the dependencies which previously had to be passed toProviderBuilder
are now passed toTraitInstantiator
during application wireup, this means that fewer dependencies have to be passed around in the code, and less boilerplate is required to create an instance of a Provider interface.I changed the name since A) we are moving towards a model where Providers have "traits", and thus the responsibility is to instantiate instances of those traits and B) it always seemed strange to me that
ProviderBuilder
needed aProvider
as an input - it creates the impression that there are two (or more) things called "Provider" in the codebase.Note that I have not replaced all uses of ProviderBuilder yet. I'd like to get this change merged before converting the engine code to use this new interface.
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:
Testing
Outline how the changes were tested, including steps to reproduce and any relevant configurations.
Attach screenshots if helpful.
Review Checklist: