diff --git a/pkg/registry/apis/provisioning/github.go b/pkg/registry/apis/provisioning/github.go new file mode 100644 index 00000000000..3b94eae4865 --- /dev/null +++ b/pkg/registry/apis/provisioning/github.go @@ -0,0 +1,135 @@ +package provisioning + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "net/url" + "strings" + + "github.com/google/go-github/v66/github" + "golang.org/x/oauth2" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/validation/field" + + common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" + provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1" +) + +type githubRepository struct { + config *provisioning.Repository +} + +func newGithubRepository(config *provisioning.Repository) *githubRepository { + return &githubRepository{config} +} + +func (r *githubRepository) Config() *provisioning.Repository { + return r.config +} + +// Validate implements provisioning.Repository. +func (r *githubRepository) Validate() (list field.ErrorList) { + gh := r.config.Spec.GitHub + if gh == nil { + list = append(list, field.Required(field.NewPath("spec", "github"), "a github config is required")) + return list + } + if gh.Owner == "" { + list = append(list, field.Required(field.NewPath("spec", "github", "owner"), "a github repo owner is required")) + } + if gh.Repository == "" { + list = append(list, field.Required(field.NewPath("spec", "github", "repository"), "a github repo name is required")) + } + if gh.Token == "" { + list = append(list, field.Required(field.NewPath("spec", "github", "token"), "a github access token is required")) + } + if gh.GenerateDashboardPreviews && !gh.BranchWorkflow { + list = append(list, field.Forbidden(field.NewPath("spec", "github", "token"), "to generate dashboard previews, you must activate the branch workflow")) + } + return list +} + +// Test implements provisioning.Repository. +func (r *githubRepository) Test(ctx context.Context) error { + return &errors.StatusError{ + ErrStatus: metav1.Status{ + Message: "test is not yet implemented", + Code: http.StatusNotImplemented, + }, + } +} + +// TODO: add validation in admission hook +// extractOwnerAndRepo takes a GitHub repository URL and returns the owner and repo name. +func extractOwnerAndRepo(repoURL string) (string, string, error) { + parsedURL, err := url.Parse(repoURL) + if err != nil { + return "", "", fmt.Errorf("invalid URL: %w", err) + } + + // Split the path to get owner and repo + parts := strings.Split(strings.Trim(parsedURL.Path, "/"), "/") + if len(parts) < 2 { + return "", "", fmt.Errorf("URL does not contain owner and repo") + } + + owner := parts[0] + repo := parts[1] + return owner, repo, nil +} + +// ReadResource implements provisioning.Repository. +func (r *githubRepository) ReadResource(ctx context.Context, filePath string, commit string) (*provisioning.ResourceWrapper, error) { + tokenSrc := oauth2.StaticTokenSource( + &oauth2.Token{AccessToken: r.config.Spec.GitHub.Token}, + ) + tokenClient := oauth2.NewClient(ctx, tokenSrc) + githubClient := github.NewClient(tokenClient) + + owner, repoName, err := extractOwnerAndRepo(r.config.Spec.GitHub.Repository) + if err != nil { + return nil, fmt.Errorf("failed to extract owner and repo: %w", err) + } + + content, _, _, err := githubClient.Repositories.GetContents(ctx, owner, repoName, filePath, &github.RepositoryContentGetOptions{ + Ref: commit, + }) + if err != nil { + // TODO: return bad request status code + return nil, fmt.Errorf("failed to get content: %w", err) + } + + data, err := content.GetContent() + if err != nil { + return nil, err + } + + var dashboardJSON map[string]interface{} + if err := json.Unmarshal([]byte(data), &dashboardJSON); err != nil { + // TODO: return bad request status code + return nil, fmt.Errorf("failed to unmarshal content: %w", err) + } + + // TODO: validate the object is a valid dashboard + // Return the wrapped response + // Note we can not return it directly (using responder) because that will make sure the + // top level apiVersion is provisioning, not the remote resource + return &provisioning.ResourceWrapper{ + Commit: commit, + Resource: common.Unstructured{ + Object: dashboardJSON, + }, + }, nil +} + +// Webhook implements provisioning.Repository. +func (r *githubRepository) Webhook() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + // really any handler + // get the repository from the path + _, _ = w.Write([]byte("TODO... handle github webhook " + r.URL.Path)) + } +} diff --git a/pkg/registry/apis/provisioning/local.go b/pkg/registry/apis/provisioning/local.go new file mode 100644 index 00000000000..4b9b686f7f1 --- /dev/null +++ b/pkg/registry/apis/provisioning/local.go @@ -0,0 +1,123 @@ +package provisioning + +import ( + "context" + "net/http" + "path/filepath" + "strings" + + "k8s.io/apimachinery/pkg/api/errors" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/validation/field" + + provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1" +) + +type LocalFolderResolver struct { + // Local path to data directory + ProvisioningPath string +} + +func (r *LocalFolderResolver) LocalPath(path string) string { + // Ignore anything with funky path names + if strings.Contains(path, "..") { + return "" + } + + parts := strings.SplitN(path, "/", 1) + if len(parts) != 2 { + return "" + } + switch parts[0] { + case "provisioning": + if r.ProvisioningPath == "" { + return "" // not allowed + } + return filepath.Join(r.ProvisioningPath, parts[1]) + + // Can we support pointing to files in devenv + case "devenv": + return "" + } + return "" +} + +var _ Repository = (*localRepository)(nil) + +type localRepository struct { + config *provisioning.Repository + + // validated path that can be read if not empty + path string +} + +func newLocalRepository(config *provisioning.Repository, resolver *LocalFolderResolver) *localRepository { + r := &localRepository{ + config: config, + } + if config.Spec.Local != nil { + r.path = resolver.LocalPath(config.Spec.Local.Path) + } + return r +} + +func (r *localRepository) Config() *provisioning.Repository { + return r.config +} + +// Validate implements provisioning.Repository. +func (r *localRepository) Validate() (fields field.ErrorList) { + if r.config.Spec.Type != provisioning.LocalRepositoryType { + fields = append(fields, &field.Error{ + Type: field.ErrorTypeInvalid, + Field: "spec.type", + Detail: "Local repository requires spec.type=local", + }) + } + + cfg := r.config.Spec.Local + if cfg == nil { + fields = append(fields, &field.Error{ + Type: field.ErrorTypeRequired, + Field: "spec.local", + }) + return fields + } + + // The path value must be set for local provisioning + if cfg.Path == "" { + fields = append(fields, field.Required(field.NewPath("spec", "local", "path"), + "must enter a path to local file")) + } else if r.path == "" { + fields = append(fields, field.Invalid(field.NewPath("spec", "local", "path"), + cfg.Path, "configured path is not allowed, see system allow list")) + } + + return fields +} + +// Test implements provisioning.Repository. +func (r *localRepository) Test(ctx context.Context) error { + return &errors.StatusError{ + ErrStatus: v1.Status{ + Message: "test is not yet implemented", + Code: http.StatusNotImplemented, + }, + } +} + +// ReadResource implements provisioning.Repository. +func (r *localRepository) ReadResource(ctx context.Context, path string, commit string) (*provisioning.ResourceWrapper, error) { + return nil, &errors.StatusError{ + ErrStatus: v1.Status{ + Message: "read resource is not yet implemented", + Code: http.StatusNotImplemented, + }, + } +} + +// Webhook implements provisioning.Repository. +func (r *localRepository) Webhook() http.HandlerFunc { + // webhooks are not supported with local + return nil +} diff --git a/pkg/registry/apis/provisioning/read.go b/pkg/registry/apis/provisioning/read.go index 278ac23ae13..69b4b35fb8c 100644 --- a/pkg/registry/apis/provisioning/read.go +++ b/pkg/registry/apis/provisioning/read.go @@ -2,26 +2,18 @@ package provisioning import ( "context" - "encoding/json" - "fmt" "net/http" - "net/url" "strings" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apiserver/pkg/registry/rest" - common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1" - - "golang.org/x/oauth2" - - "github.com/google/go-github/v66/github" ) type readConnector struct { - getter rest.Getter + getter RepoGetter } func (*readConnector) New() runtime.Object { @@ -56,70 +48,26 @@ func (*readConnector) NewConnectOptions() (runtime.Object, bool, string) { } func (s *readConnector) Connect(ctx context.Context, name string, opts runtime.Object, responder rest.Responder) (http.Handler, error) { - obj, err := s.getter.Get(ctx, name, &metav1.GetOptions{}) + repo, err := s.getter.GetRepository(ctx, name) if err != nil { return nil, err } - repo, ok := obj.(*provisioning.Repository) - if !ok { - return nil, fmt.Errorf("expected repository, but got %t", obj) - } - - tokenSrc := oauth2.StaticTokenSource( - &oauth2.Token{AccessToken: repo.Spec.GitHub.Token}, - ) - tokenClient := oauth2.NewClient(ctx, tokenSrc) - githubClient := github.NewClient(tokenClient) return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { idx := strings.Index(r.URL.Path, "/"+name+"/read") filePath := strings.TrimLeft(r.URL.Path[idx+len(name+"/read")+2:], "/") if filePath == "" { - // TODO: return bad request status code - responder.Error(fmt.Errorf("missing path")) + responder.Error(errors.NewBadRequest("missing path")) return } commit := r.URL.Query().Get("commit") - owner, repoName, err := extractOwnerAndRepo(repo.Spec.GitHub.Repository) - if err != nil { - responder.Error(fmt.Errorf("failed to extract owner and repo: %w", err)) - return - } - - content, _, _, err := githubClient.Repositories.GetContents(ctx, owner, repoName, filePath, &github.RepositoryContentGetOptions{ - Ref: commit, - }) - if err != nil { - // TODO: return bad request status code - responder.Error(fmt.Errorf("failed to get content: %w", err)) - return - } - - data, err := content.GetContent() + rsp, err := repo.ReadResource(ctx, filePath, commit) if err != nil { responder.Error(err) - return + } else { + responder.Object(200, rsp) } - - var dashboardJSON map[string]interface{} - if err := json.Unmarshal([]byte(data), &dashboardJSON); err != nil { - // TODO: return bad request status code - responder.Error(fmt.Errorf("failed to unmarshal content: %w", err)) - return - } - - // TODO: validate the object is a valid dashboard - // Return the wrapped response - // Note we can not return it directly (using responder) because that will make sure the - // top level apiVersion is provisioning, not the remote resource - wrapper := &provisioning.ResourceWrapper{ - Commit: commit, - Resource: common.Unstructured{ - Object: dashboardJSON, - }, - } - responder.Object(http.StatusOK, wrapper) }), nil } @@ -130,22 +78,3 @@ var ( _ rest.SingularNameProvider = (*readConnector)(nil) _ rest.StorageMetadata = (*readConnector)(nil) ) - -// TODO: add validation in admission hook -// extractOwnerAndRepo takes a GitHub repository URL and returns the owner and repo name. -func extractOwnerAndRepo(repoURL string) (string, string, error) { - parsedURL, err := url.Parse(repoURL) - if err != nil { - return "", "", fmt.Errorf("invalid URL: %w", err) - } - - // Split the path to get owner and repo - parts := strings.Split(strings.Trim(parsedURL.Path, "/"), "/") - if len(parts) < 2 { - return "", "", fmt.Errorf("URL does not contain owner and repo") - } - - owner := parts[0] - repo := parts[1] - return owner, repo, nil -} diff --git a/pkg/registry/apis/provisioning/register.go b/pkg/registry/apis/provisioning/register.go index ac174e3e371..55412b401df 100644 --- a/pkg/registry/apis/provisioning/register.go +++ b/pkg/registry/apis/provisioning/register.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "slices" - "strings" "github.com/prometheus/client_golang/prometheus" "k8s.io/apimachinery/pkg/api/errors" @@ -23,26 +22,38 @@ import ( grafanaregistry "github.com/grafana/grafana/pkg/apiserver/registry/generic" "github.com/grafana/grafana/pkg/services/apiserver/builder" "github.com/grafana/grafana/pkg/services/featuremgmt" + "github.com/grafana/grafana/pkg/setting" ) -var _ builder.APIGroupBuilder = (*ProvisioningAPIBuilder)(nil) +var ( + _ builder.APIGroupBuilder = (*ProvisioningAPIBuilder)(nil) + _ RepoGetter = (*ProvisioningAPIBuilder)(nil) +) // This is used just so wire has something unique to return -type ProvisioningAPIBuilder struct{} +type ProvisioningAPIBuilder struct { + getter rest.Getter + localFileResolver *LocalFolderResolver +} -func NewProvisioningAPIBuilder() *ProvisioningAPIBuilder { - return &ProvisioningAPIBuilder{} +func NewProvisioningAPIBuilder(local *LocalFolderResolver) *ProvisioningAPIBuilder { + return &ProvisioningAPIBuilder{ + localFileResolver: local, + } } func RegisterAPIService( features featuremgmt.FeatureToggles, apiregistration builder.APIRegistrar, reg prometheus.Registerer, + cfg *setting.Cfg, ) *ProvisioningAPIBuilder { if !features.IsEnabledGlobally(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs) { return nil // skip registration unless opting into experimental apis } - builder := NewProvisioningAPIBuilder() + builder := NewProvisioningAPIBuilder(&LocalFolderResolver{ + ProvisioningPath: cfg.ProvisioningPath, + }) apiregistration.RegisterAPI(builder) return builder } @@ -86,6 +97,7 @@ func (b *ProvisioningAPIBuilder) UpdateAPIGroupInfo(apiGroupInfo *genericapiserv return fmt.Errorf("failed to create repository storage: %w", err) } repositoryStatusStorage := grafanaregistry.NewRegistryStatusStore(opts.Scheme, repositoryStorage) + b.getter = repositoryStorage helloWorld := &helloWorldSubresource{ getter: repositoryStorage, @@ -98,79 +110,74 @@ func (b *ProvisioningAPIBuilder) UpdateAPIGroupInfo(apiGroupInfo *genericapiserv storage[provisioning.RepositoryResourceInfo.StoragePath("status")] = repositoryStatusStorage storage[provisioning.RepositoryResourceInfo.StoragePath("hello")] = helloWorld storage[provisioning.RepositoryResourceInfo.StoragePath("webhook")] = &webhookConnector{ - getter: repositoryStorage, + getter: b, } storage[provisioning.RepositoryResourceInfo.StoragePath("read")] = &readConnector{ - getter: repositoryStorage, + getter: b, } apiGroupInfo.VersionedResourcesStorageMap[provisioning.VERSION] = storage return nil } -func (b *ProvisioningAPIBuilder) Validate(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) (err error) { - obj := a.GetObject() +func (b *ProvisioningAPIBuilder) GetRepository(ctx context.Context, name string) (Repository, error) { + return nil, nil +} + +func (b *ProvisioningAPIBuilder) getRepository(ctx context.Context, obj runtime.Object) (Repository, error) { if obj == nil { - return fmt.Errorf("missing object for validation") + return nil, fmt.Errorf("missing object for validation") } r, ok := obj.(*provisioning.Repository) if !ok { - return fmt.Errorf("expected repository configuration") + return nil, fmt.Errorf("expected repository configuration") } - var list field.ErrorList - if r.Spec.Title == "" { + switch r.Spec.Type { + case provisioning.LocalRepositoryType: + return newLocalRepository(r, b.localFileResolver), nil + case provisioning.GithubRepositoryType: + return newGithubRepository(r), nil + case provisioning.S3RepositoryType: + return newS3Repository(r), nil + default: + } + return &unknownRepository{config: r}, nil +} + +func (b *ProvisioningAPIBuilder) Validate(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) (err error) { + repo, err := b.getRepository(ctx, a.GetObject()) + if err != nil { + return err + } + + // Typed validation + list := repo.Validate() + + cfg := repo.Config() + + if cfg.Spec.Title == "" { list = append(list, field.Required(field.NewPath("spec", "title"), "a repository title must be given")) } // Reserved names (for now) reserved := []string{"classic", "SQL", "plugins", "legacy"} - if slices.Contains(reserved, r.Name) { - list = append(list, field.Invalid(field.NewPath("metadata", "name"), r.Name, "Name is reserved")) + if slices.Contains(reserved, cfg.Name) { + list = append(list, field.Invalid(field.NewPath("metadata", "name"), cfg.Name, "Name is reserved")) } - switch r.Spec.Type { - case provisioning.LocalRepositoryType: - if r.Spec.Local == nil || r.Spec.Local.Path == "" { - list = append(list, field.Required(field.NewPath("spec", "local", "path"), "a path to a local file system is required")) - } else { - // TODO... configure an allow list of paths we can read - if !strings.HasPrefix(r.Spec.Local.Path, "/tmp/") { - list = append(list, field.Invalid(field.NewPath("spec", "local", "path"), r.Spec.Local.Path, - "invalid local file for provisioning")) - } - } - case provisioning.S3RepositoryType: - s3 := r.Spec.S3 - if s3 == nil { - list = append(list, field.Required(field.NewPath("spec", "s3"), "an s3 config is required")) - break - } - if s3.Region == "" { - list = append(list, field.Required(field.NewPath("spec", "s3", "region"), "an s3 region is required")) - } - if s3.Bucket == "" { - list = append(list, field.Required(field.NewPath("spec", "s3", "bucket"), "an s3 bucket name is required")) - } - case provisioning.GithubRepositoryType: - gh := r.Spec.GitHub - if gh == nil { - list = append(list, field.Required(field.NewPath("spec", "github"), "a github config is required")) - break - } - if gh.Owner == "" { - list = append(list, field.Required(field.NewPath("spec", "github", "owner"), "a github repo owner is required")) - } - if gh.Repository == "" { - list = append(list, field.Required(field.NewPath("spec", "github", "repository"), "a github repo name is required")) - } - if gh.Token == "" { - list = append(list, field.Required(field.NewPath("spec", "github", "token"), "a github access token is required")) - } - if gh.GenerateDashboardPreviews && !gh.BranchWorkflow { - list = append(list, field.Forbidden(field.NewPath("spec", "github", "token"), "to generate dashboard previews, you must activate the branch workflow")) - } - default: - list = append(list, field.TypeInvalid(field.NewPath("spec", "type"), r.Spec.Type, "the repository type must be one of local, s3, or github")) + if cfg.Spec.Type != provisioning.LocalRepositoryType && cfg.Spec.Local != nil { + list = append(list, field.Invalid(field.NewPath("spec", "local"), + cfg.Spec.GitHub, "Local config only valid when type is local")) + } + + if cfg.Spec.Type != provisioning.LocalRepositoryType && cfg.Spec.GitHub != nil { + list = append(list, field.Invalid(field.NewPath("spec", "github"), + cfg.Spec.GitHub, "Github config only valid when type is github")) + } + + if cfg.Spec.Type != provisioning.LocalRepositoryType && cfg.Spec.S3 != nil { + list = append(list, field.Invalid(field.NewPath("spec", "s3"), + cfg.Spec.GitHub, "S3 config only valid when type is s3")) } if len(list) > 0 { diff --git a/pkg/registry/apis/provisioning/s3.go b/pkg/registry/apis/provisioning/s3.go new file mode 100644 index 00000000000..edba876eced --- /dev/null +++ b/pkg/registry/apis/provisioning/s3.go @@ -0,0 +1,66 @@ +package provisioning + +import ( + "context" + "net/http" + + "k8s.io/apimachinery/pkg/api/errors" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/validation/field" + + provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1" +) + +type s3Repository struct { + config *provisioning.Repository +} + +func newS3Repository(config *provisioning.Repository) *s3Repository { + return &s3Repository{config} +} + +func (r *s3Repository) Config() *provisioning.Repository { + return r.config +} + +// Validate implements provisioning.Repository. +func (r *s3Repository) Validate() (list field.ErrorList) { + s3 := r.Config().Spec.S3 + if s3 == nil { + list = append(list, field.Required(field.NewPath("spec", "s3"), "an s3 config is required")) + return + } + if s3.Region == "" { + list = append(list, field.Required(field.NewPath("spec", "s3", "region"), "an s3 region is required")) + } + if s3.Bucket == "" { + list = append(list, field.Required(field.NewPath("spec", "s3", "bucket"), "an s3 bucket name is required")) + } + return +} + +// Test implements provisioning.Repository. +func (r *s3Repository) Test(ctx context.Context) error { + return &errors.StatusError{ + ErrStatus: v1.Status{ + Message: "test is not yet implemented", + Code: http.StatusNotImplemented, + }, + } +} + +// ReadResource implements provisioning.Repository. +func (r *s3Repository) ReadResource(ctx context.Context, path string, commit string) (*provisioning.ResourceWrapper, error) { + return nil, &errors.StatusError{ + ErrStatus: v1.Status{ + Message: "read resource is not yet implemented", + Code: http.StatusNotImplemented, + }, + } +} + +// Webhook implements provisioning.Repository. +func (r *s3Repository) Webhook() http.HandlerFunc { + // webhooks are not supported with local + return nil +} diff --git a/pkg/registry/apis/provisioning/types.go b/pkg/registry/apis/provisioning/types.go new file mode 100644 index 00000000000..1b579c3064b --- /dev/null +++ b/pkg/registry/apis/provisioning/types.go @@ -0,0 +1,83 @@ +package provisioning + +import ( + "context" + "net/http" + + "k8s.io/apimachinery/pkg/api/errors" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/validation/field" + + provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1" +) + +type RepoGetter interface { + GetRepository(ctx context.Context, name string) (Repository, error) +} + +type Repository interface { + // The saved object + Config() *provisioning.Repository + + // This is called before trying to create/update a saved resource + // This is not an indication that the connection information works, + // just that they are reasonably configured + Validate() field.ErrorList + + // Called to check if all connection information actually works + Test(ctx context.Context) error + + // Read a resource from settings + ReadResource(ctx context.Context, path string, commit string) (*provisioning.ResourceWrapper, error) + + // For repositories that support webhooks + Webhook() http.HandlerFunc +} + +var _ Repository = (*unknownRepository)(nil) + +type unknownRepository struct { + config *provisioning.Repository +} + +func (r *unknownRepository) Config() *provisioning.Repository { + return r.config +} + +// Validate implements provisioning.Repository. +func (r *unknownRepository) Validate() (fields field.ErrorList) { + if r.config.Spec.Type == "" { + return field.ErrorList{ + field.Required(field.NewPath("spec", "type"), "must specify a repository type"), + } + } + return field.ErrorList{ + field.Invalid(field.NewPath("spec", "type"), r.config.Spec.Type, "unsupported repository type"), + } +} + +// Test implements provisioning.Repository. +func (r *unknownRepository) Test(ctx context.Context) error { + return &errors.StatusError{ + ErrStatus: v1.Status{ + Message: "test is not yet implemented", + Code: http.StatusNotImplemented, + }, + } +} + +// ReadResource implements provisioning.Repository. +func (r *unknownRepository) ReadResource(ctx context.Context, path string, commit string) (*provisioning.ResourceWrapper, error) { + return nil, &errors.StatusError{ + ErrStatus: v1.Status{ + Message: "read resource is not yet implemented", + Code: http.StatusNotImplemented, + }, + } +} + +// Webhook implements provisioning.Repository. +func (r *unknownRepository) Webhook() http.HandlerFunc { + // webhooks are not supported with local + return nil +} diff --git a/pkg/registry/apis/provisioning/webhook.go b/pkg/registry/apis/provisioning/webhook.go index f4a36c11d67..4ef34c8b654 100644 --- a/pkg/registry/apis/provisioning/webhook.go +++ b/pkg/registry/apis/provisioning/webhook.go @@ -5,7 +5,8 @@ import ( "fmt" "net/http" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/api/errors" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apiserver/pkg/registry/rest" @@ -14,7 +15,7 @@ import ( // This only works for github right now type webhookConnector struct { - getter rest.Getter + getter RepoGetter } func (*webhookConnector) New() runtime.Object { @@ -52,23 +53,20 @@ func (*webhookConnector) NewConnectOptions() (runtime.Object, bool, string) { } func (s *webhookConnector) Connect(ctx context.Context, name string, opts runtime.Object, responder rest.Responder) (http.Handler, error) { - obj, err := s.getter.Get(ctx, name, &metav1.GetOptions{}) + repo, err := s.getter.GetRepository(ctx, name) if err != nil { return nil, err } - repo, ok := obj.(*provisioning.Repository) - if !ok { - return nil, fmt.Errorf("expected repository, but got %t", obj) + webhook := repo.Webhook() + if webhook == nil { + return nil, &errors.StatusError{ + ErrStatus: v1.Status{ + Message: fmt.Sprintf("webhook is not implemented for: %s", repo.Config().Spec.Type), + Code: http.StatusNotImplemented, + }, + } } - if repo.Spec.Type != provisioning.GithubRepositoryType { - return nil, fmt.Errorf("only works for github") - } - - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // really any handler - // get the repository from the path - _, _ = w.Write([]byte("TODO... handle webhook " + r.URL.Path)) - }), nil + return webhook, nil } var (