diff --git a/internal/engine/ingester/artifact/artifact.go b/internal/engine/ingester/artifact/artifact.go index b2ce6069b2..a483c7ca9a 100644 --- a/internal/engine/ingester/artifact/artifact.go +++ b/internal/engine/ingester/artifact/artifact.go @@ -19,8 +19,6 @@ package artifact import ( "context" "fmt" - "net/url" - "sort" "strings" "time" @@ -50,7 +48,7 @@ const ( // Ingest is the engine for a rule type that uses artifact data ingest // Implements enginer.ingester.Ingester type Ingest struct { - ghCli provifv1.GitHub + prov provifv1.Provider // artifactVerifier is the verifier for sigstore. It's only used in the Ingest method // but we store it in the Ingest structure to allow tests to set a custom artifactVerifier @@ -74,9 +72,9 @@ type verifiedAttestation struct { } // NewArtifactDataIngest creates a new artifact rule data ingest engine -func NewArtifactDataIngest(ghCli provifv1.GitHub) (*Ingest, error) { +func NewArtifactDataIngest(prov provifv1.Provider) (*Ingest, error) { return &Ingest{ - ghCli: ghCli, + prov: prov, }, nil } @@ -124,18 +122,17 @@ func (i *Ingest) getApplicableArtifactVersions( artifact *pb.Artifact, cfg *ingesterConfig, ) ([]map[string]any, error) { - // Make sure the artifact type matches - if newArtifactIngestType(artifact.Type) != cfg.Type { - return nil, evalerrors.NewErrEvaluationSkipSilently("artifact type mismatch") + if err := validateConfiguration(artifact, cfg); err != nil { + return nil, err } - // If a name is specified, make sure it matches - if cfg.Name != "" && cfg.Name != artifact.Name { - return nil, evalerrors.NewErrEvaluationSkipSilently("artifact name mismatch") + vers, err := getVersioner(i.prov, artifact) + if err != nil { + return nil, err } // Get all artifact versions filtering out those that don't apply to this rule - versions, err := getAndFilterArtifactVersions(ctx, cfg, i.ghCli, artifact) + versions, err := getAndFilterArtifactVersions(ctx, cfg, vers, artifact) if err != nil { return nil, err } @@ -160,6 +157,27 @@ func (i *Ingest) getApplicableArtifactVersions( return result, nil } +func validateConfiguration( + artifact *pb.Artifact, + cfg *ingesterConfig, +) error { + // Make sure the artifact type matches + if newArtifactIngestType(artifact.Type) != cfg.Type { + return evalerrors.NewErrEvaluationSkipSilently("artifact type mismatch") + } + + if cfg.Type != artifactTypeContainer { + return evalerrors.NewErrEvaluationSkipSilently("only container artifacts are supported at the moment") + } + + // If a name is specified, make sure it matches + if cfg.Name != "" && cfg.Name != artifact.Name { + return evalerrors.NewErrEvaluationSkipSilently("artifact name mismatch") + } + + return nil +} + func (i *Ingest) getVerificationResult( ctx context.Context, cfg *ingesterConfig, @@ -230,10 +248,20 @@ func getVerifier(i *Ingest, cfg *ingesterConfig) (verifyif.ArtifactVerifier, err return i.artifactVerifier, nil } + verifieropts := []container.AuthMethod{} + if i.prov.CanImplement(pb.ProviderType_PROVIDER_TYPE_GITHUB) { + ghcli, err := provifv1.As[provifv1.GitHub](i.prov) + if err != nil { + return nil, fmt.Errorf("unable to get github provider from provider configuration") + } + verifieropts = append(verifieropts, container.WithGitHubClient(ghcli)) + } + artifactVerifier, err := verifier.NewVerifier( verifier.VerifierSigstore, cfg.Sigstore, - container.WithGitHubClient(i.ghCli)) + verifieropts..., + ) if err != nil { return nil, fmt.Errorf("error getting sigstore verifier: %w", err) } @@ -244,7 +272,7 @@ func getVerifier(i *Ingest, cfg *ingesterConfig) (verifyif.ArtifactVerifier, err func getAndFilterArtifactVersions( ctx context.Context, cfg *ingesterConfig, - ghCli provifv1.GitHub, + vers versioner, artifact *pb.Artifact, ) ([]string, error) { var res []string @@ -256,23 +284,21 @@ func getAndFilterArtifactVersions( } // Fetch all available versions of the artifact - artifactName := url.QueryEscape(artifact.GetName()) - upstreamVersions, err := ghCli.GetPackageVersions( - ctx, artifact.Owner, artifact.GetTypeLower(), artifactName, - ) + upstreamVersions, err := vers.GetVersions(ctx) if err != nil { return nil, fmt.Errorf("error retrieving artifact versions: %w", err) } + name := artifact.GetName() + // Loop through all and filter out the versions that don't apply to this rule for _, version := range upstreamVersions { - tags := version.Metadata.Container.Tags - sort.Strings(tags) - // Decide if the artifact version should be skipped or not - err = isSkippable(verifyif.ArtifactTypeContainer, version.CreatedAt.Time, map[string]interface{}{"tags": tags}, filter) + tags := version.GetTags() + tagsopt := map[string]interface{}{"tags": tags} + err = isSkippable(version.GetCreatedAt().AsTime(), tagsopt, filter) if err != nil { - zerolog.Ctx(ctx).Debug().Str("name", *version.Name).Strs("tags", tags).Str( + zerolog.Ctx(ctx).Debug().Str("name", name).Strs("tags", tags).Str( "reason", err.Error(), ).Msg("skipping artifact version") @@ -280,8 +306,8 @@ func getAndFilterArtifactVersions( } // If the artifact version is applicable to this rule, add it to the list - zerolog.Ctx(ctx).Debug().Str("name", *version.Name).Strs("tags", tags).Msg("artifact version matched") - res = append(res, *version.Name) + zerolog.Ctx(ctx).Debug().Str("name", name).Strs("tags", tags).Msg("artifact version matched") + res = append(res, name) } // If no applicable artifact versions were found for this rule, we can go ahead and fail the rule evaluation here @@ -299,33 +325,29 @@ var ( ) // isSkippable determines if an artifact should be skipped +// Note this is only applicable to container artifacts. // TODO - this should be refactored as well, for now just a forklift from reconciler -func isSkippable(artifactType verifyif.ArtifactType, createdAt time.Time, opts map[string]interface{}, filter tagMatcher) error { - switch artifactType { - case verifyif.ArtifactTypeContainer: - // if the artifact is older than the retention period, skip it - if createdAt.Before(ArtifactTypeContainerRetentionPeriod) { - return fmt.Errorf("artifact is older than retention period - %s", ArtifactTypeContainerRetentionPeriod) - } - tags, ok := opts["tags"].([]string) - if !ok { - return nil - } else if len(tags) == 0 { - // if the artifact has no tags, skip it - return fmt.Errorf("artifact has no tags") - } - // if the artifact has a .sig tag it's a signature, skip it - if verifier.GetSignatureTag(tags) != "" { - return fmt.Errorf("artifact is a signature") - } - // if the artifact tags don't match the tag matcher, skip it - if !filter.MatchTag(tags...) { - return fmt.Errorf("artifact tags does not match") - } - return nil - default: +func isSkippable(createdAt time.Time, opts map[string]interface{}, filter tagMatcher) error { + // if the artifact is older than the retention period, skip it + if createdAt.Before(ArtifactTypeContainerRetentionPeriod) { + return fmt.Errorf("artifact is older than retention period - %s", ArtifactTypeContainerRetentionPeriod) + } + tags, ok := opts["tags"].([]string) + if !ok { return nil + } else if len(tags) == 0 { + // if the artifact has no tags, skip it + return fmt.Errorf("artifact has no tags") + } + // if the artifact has a .sig tag it's a signature, skip it + if verifier.GetSignatureTag(tags) != "" { + return fmt.Errorf("artifact is a signature") } + // if the artifact tags don't match the tag matcher, skip it + if !filter.MatchTag(tags...) { + return fmt.Errorf("artifact tags does not match") + } + return nil } func branchFromRef(ref string) string { diff --git a/internal/engine/ingester/artifact/artifact_test.go b/internal/engine/ingester/artifact/artifact_test.go index 5ff7fb110b..f4cab75787 100644 --- a/internal/engine/ingester/artifact/artifact_test.go +++ b/internal/engine/ingester/artifact/artifact_test.go @@ -569,7 +569,7 @@ func TestArtifactIngestMatching(t *testing.T) { ing, err := NewArtifactDataIngest(prov) require.NoError(t, err, "expected no error") - ing.ghCli = mockGhClient + ing.prov = mockGhClient ing.artifactVerifier = mockVerifier tt.mockSetup(mockGhClient, mockVerifier) diff --git a/internal/engine/ingester/artifact/versioner.go b/internal/engine/ingester/artifact/versioner.go new file mode 100644 index 0000000000..296ba9ce99 --- /dev/null +++ b/internal/engine/ingester/artifact/versioner.go @@ -0,0 +1,141 @@ +// Copyright 2024 Stacklok, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// Package rule provides the CLI subcommand for managing rules + +// Package artifact provides the artifact ingestion engine +package artifact + +import ( + "context" + "fmt" + "net/url" + "sort" + "time" + + v1 "github.com/opencontainers/image-spec/specs-go/v1" + "google.golang.org/protobuf/types/known/timestamppb" + + minderv1 "github.com/stacklok/minder/pkg/api/protobuf/go/minder/v1" + provifv1 "github.com/stacklok/minder/pkg/providers/v1" +) + +type versioner interface { + // Gets the available versions of a given artifact + GetVersions(ctx context.Context) ([]*minderv1.ArtifactVersion, error) +} + +func getVersioner(prov provifv1.Provider, a *minderv1.Artifact) (versioner, error) { + ghprov, err := provifv1.As[provifv1.GitHub](prov) + if err == nil { + return &githubVersioner{ + ghCli: ghprov, + artifact: a, + }, nil + } + + ociprov, err := provifv1.As[provifv1.OCI](prov) + if err == nil { + return &ociVersioner{ + ocicli: ociprov, + artifact: a, + }, nil + } + + return nil, fmt.Errorf("Unable to get version lister from provider implementation") +} + +type githubVersioner struct { + ghCli provifv1.GitHub + artifact *minderv1.Artifact +} + +// in case of the GitHub provider, a package version may be +// linked to multiple tags +func (gv *githubVersioner) GetVersions(ctx context.Context) ([]*minderv1.ArtifactVersion, error) { + artifactName := url.QueryEscape(gv.artifact.GetName()) + upstreamVersions, err := gv.ghCli.GetPackageVersions( + ctx, gv.artifact.GetOwner(), gv.artifact.GetTypeLower(), artifactName, + ) + if err != nil { + return nil, fmt.Errorf("error retrieving artifact versions: %w", err) + } + + out := make([]*minderv1.ArtifactVersion, 0, 10) + for _, uv := range upstreamVersions { + tags := uv.Metadata.Container.Tags + sort.Strings(tags) + + // only the tags and creation time is relevant to us. + out = append(out, &minderv1.ArtifactVersion{ + Tags: tags, + CreatedAt: timestamppb.New(uv.CreatedAt.Time), + }) + } + + return out, nil +} + +type ociVersioner struct { + ocicli provifv1.OCI + artifact *minderv1.Artifact +} + +func (ov *ociVersioner) GetVersions(ctx context.Context) ([]*minderv1.ArtifactVersion, error) { + tags, err := ov.ocicli.ListTags(ctx, ov.artifact.GetName()) + if err != nil { + return nil, fmt.Errorf("error retrieving artifact versions: %w", err) + } + + out := make([]*minderv1.ArtifactVersion, 0, 10) + for _, t := range tags { + // TODO: We probably should try to surface errors while returning a subset + // of manifests. + man, err := ov.ocicli.GetManifest(ctx, ov.artifact.GetName(), t) + if err != nil { + return nil, err + } + + // NOTE/FIXME: This is going to be a hassle as not a lot of + // container images have the needed annotations. We'd need + // go down to a specific image configuration (e.g. for _some_ + // architecture) to actually verify the creation date... + // Anybody has other ideas? + strcreated, ok := man.Annotations[v1.AnnotationCreated] + var createdAt time.Time + if !ok { + // TODO: Verify if this is correct + createdAt, err = time.Parse(time.RFC3339, strcreated) + if err != nil { + return nil, fmt.Errorf("Unable to get creation time for tag %s: %w", t, err) + } + } else { + // FIXME: This is a hack + createdAt = time.Now() + } + + // TODO: Consider caching + digest, err := ov.ocicli.GetDigest(ctx, ov.artifact.GetName(), t) + if err != nil { + return nil, fmt.Errorf("Unable to get digest") + } + + out = append(out, &minderv1.ArtifactVersion{ + Tags: []string{t}, + Sha: digest, + CreatedAt: timestamppb.New(createdAt), + }) + } + + return out, nil +} diff --git a/internal/engine/ingester/ingester.go b/internal/engine/ingester/ingester.go index 617f3d2097..725ca593e1 100644 --- a/internal/engine/ingester/ingester.go +++ b/internal/engine/ingester/ingester.go @@ -63,11 +63,7 @@ func NewRuleDataIngest(rt *pb.RuleType, provider provinfv1.Provider) (engif.Inge if rt.Def.Ingest.GetArtifact() == nil { return nil, fmt.Errorf("rule type engine missing artifact configuration") } - client, err := provinfv1.As[provinfv1.GitHub](provider) - if err != nil { - return nil, errors.New("provider does not implement github trait") - } - return artifact.NewArtifactDataIngest(client) + return artifact.NewArtifactDataIngest(provider) case git.GitRuleDataIngestType: client, err := provinfv1.As[provinfv1.Git](provider) diff --git a/internal/providers/dockerhub/dockerhub.go b/internal/providers/dockerhub/dockerhub.go index d104f2285c..746254fac5 100644 --- a/internal/providers/dockerhub/dockerhub.go +++ b/internal/providers/dockerhub/dockerhub.go @@ -22,10 +22,12 @@ import ( "fmt" "net/http" "net/url" + "path" "golang.org/x/oauth2" "github.com/stacklok/minder/internal/db" + "github.com/stacklok/minder/internal/providers/oci" minderv1 "github.com/stacklok/minder/pkg/api/protobuf/go/minder/v1" provifv1 "github.com/stacklok/minder/pkg/providers/v1" ) @@ -33,6 +35,10 @@ import ( // DockerHub is the string that represents the DockerHub provider const DockerHub = "dockerhub" +const ( + dockerioBaseURL = "docker.io" +) + // Implements is the list of provider types that the DockerHub provider implements var Implements = []db.ProviderType{ db.ProviderTypeImageLister, @@ -46,6 +52,7 @@ var AuthorizationFlows = []db.AuthorizationFlow{ // dockerHubImageLister is the struct that contains the Docker Hub specific operations type dockerHubImageLister struct { + *oci.OCI cred provifv1.OAuth2TokenCredential cli *http.Client namespace string @@ -68,7 +75,9 @@ func New(cred provifv1.OAuth2TokenCredential, cfg *minderv1.DockerHubProviderCon ns := cfg.GetNamespace() t := u.JoinPath(ns) + o := oci.New(cred, path.Join(dockerioBaseURL, cfg.GetNamespace())) return &dockerHubImageLister{ + OCI: o, namespace: ns, cred: cred, cli: cli, @@ -102,7 +111,8 @@ func (d *dockerHubImageLister) GetNamespaceURL() string { // CanImplement returns true if the provider can implement the specified trait func (_ *dockerHubImageLister) CanImplement(trait minderv1.ProviderType) bool { - return trait == minderv1.ProviderType_PROVIDER_TYPE_IMAGE_LISTER + return trait == minderv1.ProviderType_PROVIDER_TYPE_IMAGE_LISTER || + trait == minderv1.ProviderType_PROVIDER_TYPE_OCI } // ListImages lists the containers in the Docker Hub diff --git a/internal/providers/github/mock/github.go b/internal/providers/github/mock/github.go index deb02893b3..d815603e94 100644 --- a/internal/providers/github/mock/github.go +++ b/internal/providers/github/mock/github.go @@ -15,9 +15,10 @@ import ( reflect "reflect" git "github.com/go-git/go-git/v5" + v1 "github.com/google/go-containerregistry/pkg/v1" github "github.com/google/go-github/v61/github" - v1 "github.com/stacklok/minder/pkg/api/protobuf/go/minder/v1" - v10 "github.com/stacklok/minder/pkg/providers/v1" + v10 "github.com/stacklok/minder/pkg/api/protobuf/go/minder/v1" + v11 "github.com/stacklok/minder/pkg/providers/v1" gomock "go.uber.org/mock/gomock" ) @@ -45,7 +46,7 @@ func (m *MockProvider) EXPECT() *MockProviderMockRecorder { } // CanImplement mocks base method. -func (m *MockProvider) CanImplement(trait v1.ProviderType) bool { +func (m *MockProvider) CanImplement(trait v10.ProviderType) bool { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "CanImplement", trait) ret0, _ := ret[0].(bool) @@ -82,7 +83,7 @@ func (m *MockGit) EXPECT() *MockGitMockRecorder { } // CanImplement mocks base method. -func (m *MockGit) CanImplement(trait v1.ProviderType) bool { +func (m *MockGit) CanImplement(trait v10.ProviderType) bool { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "CanImplement", trait) ret0, _ := ret[0].(bool) @@ -134,7 +135,7 @@ func (m *MockREST) EXPECT() *MockRESTMockRecorder { } // CanImplement mocks base method. -func (m *MockREST) CanImplement(trait v1.ProviderType) bool { +func (m *MockREST) CanImplement(trait v10.ProviderType) bool { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "CanImplement", trait) ret0, _ := ret[0].(bool) @@ -215,7 +216,7 @@ func (m *MockRepoLister) EXPECT() *MockRepoListerMockRecorder { } // CanImplement mocks base method. -func (m *MockRepoLister) CanImplement(trait v1.ProviderType) bool { +func (m *MockRepoLister) CanImplement(trait v10.ProviderType) bool { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "CanImplement", trait) ret0, _ := ret[0].(bool) @@ -229,10 +230,10 @@ func (mr *MockRepoListerMockRecorder) CanImplement(trait any) *gomock.Call { } // ListAllRepositories mocks base method. -func (m *MockRepoLister) ListAllRepositories(arg0 context.Context) ([]*v1.Repository, error) { +func (m *MockRepoLister) ListAllRepositories(arg0 context.Context) ([]*v10.Repository, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ListAllRepositories", arg0) - ret0, _ := ret[0].([]*v1.Repository) + ret0, _ := ret[0].([]*v10.Repository) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -281,7 +282,7 @@ func (mr *MockGitHubMockRecorder) AddAuthToPushOptions(ctx, options any) *gomock } // CanImplement mocks base method. -func (m *MockGitHub) CanImplement(trait v1.ProviderType) bool { +func (m *MockGitHub) CanImplement(trait v10.ProviderType) bool { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "CanImplement", trait) ret0, _ := ret[0].(bool) @@ -503,10 +504,10 @@ func (mr *MockGitHubMockRecorder) GetBranchProtection(arg0, arg1, arg2, arg3 any } // GetCredential mocks base method. -func (m *MockGitHub) GetCredential() v10.GitHubCredential { +func (m *MockGitHub) GetCredential() v11.GitHubCredential { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetCredential") - ret0, _ := ret[0].(v10.GitHubCredential) + ret0, _ := ret[0].(v11.GitHubCredential) return ret0 } @@ -695,10 +696,10 @@ func (mr *MockGitHubMockRecorder) IsOrg() *gomock.Call { } // ListAllRepositories mocks base method. -func (m *MockGitHub) ListAllRepositories(arg0 context.Context) ([]*v1.Repository, error) { +func (m *MockGitHub) ListAllRepositories(arg0 context.Context) ([]*v10.Repository, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ListAllRepositories", arg0) - ret0, _ := ret[0].([]*v1.Repository) + ret0, _ := ret[0].([]*v10.Repository) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -912,7 +913,7 @@ func (m *MockImageLister) EXPECT() *MockImageListerMockRecorder { } // CanImplement mocks base method. -func (m *MockImageLister) CanImplement(trait v1.ProviderType) bool { +func (m *MockImageLister) CanImplement(trait v10.ProviderType) bool { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "CanImplement", trait) ret0, _ := ret[0].(bool) @@ -978,7 +979,7 @@ func (m *MockOCI) EXPECT() *MockOCIMockRecorder { } // CanImplement mocks base method. -func (m *MockOCI) CanImplement(trait v1.ProviderType) bool { +func (m *MockOCI) CanImplement(trait v10.ProviderType) bool { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "CanImplement", trait) ret0, _ := ret[0].(bool) @@ -1007,10 +1008,10 @@ func (mr *MockOCIMockRecorder) GetDigest(ctx, name, tag any) *gomock.Call { } // GetManifest mocks base method. -func (m *MockOCI) GetManifest(ctx context.Context, name, tag string) (any, error) { +func (m *MockOCI) GetManifest(ctx context.Context, name, tag string) (*v1.Manifest, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetManifest", ctx, name, tag) - ret0, _ := ret[0].(any) + ret0, _ := ret[0].(*v1.Manifest) ret1, _ := ret[1].(error) return ret0, ret1 } diff --git a/internal/providers/oci/oci.go b/internal/providers/oci/oci.go index 73e0fc44e1..f87915d61b 100644 --- a/internal/providers/oci/oci.go +++ b/internal/providers/oci/oci.go @@ -21,6 +21,7 @@ import ( "strings" "github.com/google/go-containerregistry/pkg/name" + v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/remote" "github.com/stacklok/minder/internal/constants" @@ -134,20 +135,20 @@ func (o *OCI) GetReferrer(ctx context.Context, contname, tag, artifactType strin // GetManifest returns the manifest for the given tag of the given container in the given namespace // for the OCI provider. It returns the manifest as a golang struct given the OCI spec. -func (o *OCI) GetManifest(ctx context.Context, contname, tag string) (any, error) { +func (o *OCI) GetManifest(ctx context.Context, contname, tag string) (*v1.Manifest, error) { ref, err := o.getReference(contname, tag) if err != nil { - return "", fmt.Errorf("failed to get reference: %w", err) + return nil, fmt.Errorf("failed to get reference: %w", err) } img, err := remote.Image(ref, remote.WithContext(ctx), remote.WithUserAgent(constants.ServerUserAgent)) if err != nil { - return "", fmt.Errorf("failed to get image: %w", err) + return nil, fmt.Errorf("failed to get image: %w", err) } man, err := img.Manifest() if err != nil { - return "", fmt.Errorf("failed to get manifest: %w", err) + return nil, fmt.Errorf("failed to get manifest: %w", err) } return man, nil diff --git a/pkg/providers/v1/providers.go b/pkg/providers/v1/providers.go index ecee0736ee..4e3bf80f28 100644 --- a/pkg/providers/v1/providers.go +++ b/pkg/providers/v1/providers.go @@ -26,6 +26,7 @@ import ( "github.com/go-git/go-git/v5" "github.com/go-playground/validator/v10" + v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-github/v61/github" minderv1 "github.com/stacklok/minder/pkg/api/protobuf/go/minder/v1" @@ -153,7 +154,7 @@ type OCI interface { // GetManifest returns the manifest for the given tag of the given container in the given namespace // for the OCI provider. It returns the manifest as a golang struct given the OCI spec. // TODO - Define the manifest struct - GetManifest(ctx context.Context, name, tag string) (any, error) + GetManifest(ctx context.Context, name, tag string) (*v1.Manifest, error) } // ParseAndValidate parses the given provider configuration and validates it.