Provisioning: Use inline secrets for gitsync (#109908)
Co-authored-by: Clarity-89 <homes89@ukr.net> Co-authored-by: Roberto Jimenez Sanchez <roberto.jimenez@grafana.com>
This commit is contained in:
co-authored by
Clarity-89
Roberto Jimenez Sanchez
parent
04f392d37b
commit
ce65391067
@@ -1,35 +0,0 @@
|
||||
package webhooks
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/controller"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/secrets"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
func Mutator(secrets secrets.RepositorySecrets) controller.Mutator {
|
||||
return func(ctx context.Context, obj runtime.Object) error {
|
||||
repo, ok := obj.(*provisioning.Repository)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
if repo.Status.Webhook == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if repo.Status.Webhook.Secret != "" {
|
||||
secretName := repo.Name + webhookSecretSuffix
|
||||
nameOrValue, err := secrets.Encrypt(ctx, repo, secretName, repo.Status.Webhook.Secret)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
repo.Status.Webhook.EncryptedSecret = nameOrValue
|
||||
repo.Status.Webhook.Secret = ""
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -1,157 +0,0 @@
|
||||
package webhooks
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/secrets"
|
||||
"github.com/stretchr/testify/assert"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
func TestMutator(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
obj runtime.Object
|
||||
secret string
|
||||
setupMocks func(*secrets.MockRepositorySecrets)
|
||||
expectedEncryptedSecret string
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
name: "successful secret encryption",
|
||||
obj: &provisioning.Repository{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-repo",
|
||||
Namespace: "default",
|
||||
},
|
||||
Status: provisioning.RepositoryStatus{
|
||||
Webhook: &provisioning.WebhookStatus{
|
||||
Secret: "webhook-secret",
|
||||
},
|
||||
},
|
||||
},
|
||||
setupMocks: func(mockSecrets *secrets.MockRepositorySecrets) {
|
||||
mockSecrets.EXPECT().Encrypt(
|
||||
context.Background(),
|
||||
&provisioning.Repository{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-repo",
|
||||
Namespace: "default",
|
||||
},
|
||||
Status: provisioning.RepositoryStatus{
|
||||
Webhook: &provisioning.WebhookStatus{
|
||||
Secret: "webhook-secret",
|
||||
},
|
||||
},
|
||||
},
|
||||
"test-repo-webhook-secret",
|
||||
"webhook-secret",
|
||||
).Return([]byte("encrypted-webhook-secret"), nil)
|
||||
},
|
||||
expectedEncryptedSecret: "encrypted-webhook-secret",
|
||||
},
|
||||
{
|
||||
name: "encryption error",
|
||||
obj: &provisioning.Repository{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-repo",
|
||||
Namespace: "default",
|
||||
},
|
||||
Status: provisioning.RepositoryStatus{
|
||||
Webhook: &provisioning.WebhookStatus{
|
||||
Secret: "webhook-secret",
|
||||
},
|
||||
},
|
||||
},
|
||||
setupMocks: func(mockSecrets *secrets.MockRepositorySecrets) {
|
||||
mockSecrets.EXPECT().Encrypt(
|
||||
context.Background(),
|
||||
&provisioning.Repository{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-repo",
|
||||
Namespace: "default",
|
||||
},
|
||||
Status: provisioning.RepositoryStatus{
|
||||
Webhook: &provisioning.WebhookStatus{
|
||||
Secret: "webhook-secret",
|
||||
},
|
||||
},
|
||||
},
|
||||
"test-repo-webhook-secret",
|
||||
"webhook-secret",
|
||||
).Return(nil, errors.New("encryption failed"))
|
||||
},
|
||||
expectedError: "encryption failed",
|
||||
},
|
||||
{
|
||||
name: "no webhook status",
|
||||
obj: &provisioning.Repository{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-repo",
|
||||
Namespace: "default",
|
||||
},
|
||||
Status: provisioning.RepositoryStatus{
|
||||
Webhook: nil,
|
||||
},
|
||||
},
|
||||
setupMocks: func(_ *secrets.MockRepositorySecrets) {
|
||||
// No expectations
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty secret",
|
||||
obj: &provisioning.Repository{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-repo",
|
||||
Namespace: "default",
|
||||
},
|
||||
Status: provisioning.RepositoryStatus{
|
||||
Webhook: &provisioning.WebhookStatus{
|
||||
Secret: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
setupMocks: func(_ *secrets.MockRepositorySecrets) {
|
||||
// No expectations
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "non-repository object",
|
||||
obj: &runtime.Unknown{},
|
||||
setupMocks: func(_ *secrets.MockRepositorySecrets) {
|
||||
// No expectations
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
mockSecrets := secrets.NewMockRepositorySecrets(t)
|
||||
tt.setupMocks(mockSecrets)
|
||||
|
||||
mutator := Mutator(mockSecrets)
|
||||
err := mutator(context.Background(), tt.obj)
|
||||
|
||||
if tt.expectedError != "" {
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), tt.expectedError)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Check that secret was cleared and encrypted secret was set
|
||||
if repo, ok := tt.obj.(*provisioning.Repository); ok && repo.Status.Webhook != nil {
|
||||
if tt.expectedEncryptedSecret != "" {
|
||||
// Secret should be cleared after encryption
|
||||
assert.Empty(t, repo.Status.Webhook.Secret, "Secret should be cleared after encryption")
|
||||
// EncryptedSecret should be set to the expected value
|
||||
assert.Equal(t, tt.expectedEncryptedSecret, string(repo.Status.Webhook.EncryptedSecret), "EncryptedSecret should match expected value")
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,6 @@ import (
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository/git"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository/github"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/resources"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/secrets"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/webhooks/pullrequest"
|
||||
"github.com/grafana/grafana/pkg/services/apiserver"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
@@ -47,7 +46,6 @@ func isPublicURL(url string) bool {
|
||||
func ProvideWebhooks(
|
||||
cfg *setting.Cfg,
|
||||
features featuremgmt.FeatureToggles,
|
||||
repositorySecrets secrets.RepositorySecrets,
|
||||
ghFactory *github.Factory,
|
||||
renderer rendering.Service,
|
||||
blobstore resource.ResourceClient,
|
||||
@@ -79,7 +77,6 @@ func ProvideWebhooks(
|
||||
render,
|
||||
webhook,
|
||||
urlProvider,
|
||||
repositorySecrets,
|
||||
ghFactory,
|
||||
parsers,
|
||||
[]jobs.Worker{pullRequestWorker},
|
||||
@@ -95,7 +92,6 @@ type WebhookExtra struct {
|
||||
render *renderConnector
|
||||
webhook *webhookConnector
|
||||
urlProvider func(namespace string) string
|
||||
secrets secrets.RepositorySecrets
|
||||
ghFactory *github.Factory
|
||||
parsers resources.ParserFactory
|
||||
workers []jobs.Worker
|
||||
@@ -106,7 +102,6 @@ func NewWebhookExtra(
|
||||
render *renderConnector,
|
||||
webhook *webhookConnector,
|
||||
urlProvider func(namespace string) string,
|
||||
secrets secrets.RepositorySecrets,
|
||||
ghFactory *github.Factory,
|
||||
parsers resources.ParserFactory,
|
||||
workers []jobs.Worker,
|
||||
@@ -116,7 +111,6 @@ func NewWebhookExtra(
|
||||
render: render,
|
||||
webhook: webhook,
|
||||
urlProvider: urlProvider,
|
||||
secrets: secrets,
|
||||
ghFactory: ghFactory,
|
||||
parsers: parsers,
|
||||
workers: workers,
|
||||
@@ -136,9 +130,7 @@ func (e *WebhookExtra) Authorize(ctx context.Context, a authorizer.Attributes) (
|
||||
|
||||
// Mutators returns the mutators for the webhook extra
|
||||
func (e *WebhookExtra) Mutators() []controller.Mutator {
|
||||
return []controller.Mutator{
|
||||
Mutator(e.secrets),
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateStorage updates the storage with both render and webhook connectors
|
||||
@@ -164,7 +156,7 @@ func (e *WebhookExtra) GetJobWorkers() []jobs.Worker {
|
||||
}
|
||||
|
||||
// AsRepository delegates repository creation to the webhook connector
|
||||
func (e *WebhookExtra) AsRepository(ctx context.Context, r *provisioning.Repository) (repository.Repository, error) {
|
||||
func (e *WebhookExtra) AsRepository(ctx context.Context, r *provisioning.Repository, secure repository.SecureValues) (repository.Repository, error) {
|
||||
// Only handle GitHub repositories with webhooks if URL is public
|
||||
if r.Spec.Type == provisioning.GitHubRepositoryType && e.isPublic {
|
||||
gvr := provisioning.RepositoryResourceInfo.GroupVersionResource()
|
||||
@@ -186,34 +178,33 @@ func (e *WebhookExtra) AsRepository(ctx context.Context, r *provisioning.Reposit
|
||||
}
|
||||
|
||||
// Decrypt GitHub token if needed
|
||||
ghToken := ghCfg.Token
|
||||
if ghToken == "" && len(ghCfg.EncryptedToken) > 0 {
|
||||
decrypted, err := e.secrets.Decrypt(ctx, r, string(ghCfg.EncryptedToken))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("decrypt github token: %w", err)
|
||||
}
|
||||
ghToken = string(decrypted)
|
||||
ghToken, err := secure.Token(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("decrypt github token: %w", err)
|
||||
}
|
||||
webhookSecret, err := secure.WebhookSecret(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("decrypt webhookSecret: %w", err)
|
||||
}
|
||||
|
||||
gitCfg := git.RepositoryConfig{
|
||||
URL: ghCfg.URL,
|
||||
Branch: ghCfg.Branch,
|
||||
Path: ghCfg.Path,
|
||||
Token: ghToken,
|
||||
EncryptedToken: ghCfg.EncryptedToken,
|
||||
URL: ghCfg.URL,
|
||||
Branch: ghCfg.Branch,
|
||||
Path: ghCfg.Path,
|
||||
Token: ghToken,
|
||||
}
|
||||
|
||||
gitRepo, err := git.NewGitRepository(ctx, r, gitCfg, e.secrets)
|
||||
gitRepo, err := git.NewGitRepository(ctx, r, gitCfg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating git repository: %w", err)
|
||||
}
|
||||
|
||||
basicRepo, err := github.NewGitHub(ctx, r, gitRepo, e.ghFactory, ghToken, e.secrets)
|
||||
basicRepo, err := github.NewGitHub(ctx, r, gitRepo, e.ghFactory, ghToken)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating github repository: %w", err)
|
||||
}
|
||||
|
||||
return NewGithubWebhookRepository(basicRepo, webhookURL, e.secrets), nil
|
||||
return NewGithubWebhookRepository(basicRepo, webhookURL, webhookSecret), nil
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
|
||||
@@ -10,18 +10,16 @@ import (
|
||||
|
||||
"github.com/google/go-github/v70/github"
|
||||
"github.com/google/uuid"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
|
||||
"github.com/grafana/grafana-app-sdk/logging"
|
||||
provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
|
||||
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository"
|
||||
pgh "github.com/grafana/grafana/pkg/registry/apis/provisioning/repository/github"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/secrets"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
)
|
||||
|
||||
var subscribedEvents = []string{"push", "pull_request"}
|
||||
|
||||
//nolint:gosec // This is a constant for a secret suffix
|
||||
const webhookSecretSuffix = "-webhook-secret"
|
||||
var subscribedEvents = []string{"pull_request", "push"} // same order as slices.Sort()
|
||||
|
||||
type WebhookRepository interface {
|
||||
Webhook(ctx context.Context, req *http.Request) (*provisioning.WebhookResponse, error)
|
||||
@@ -39,7 +37,7 @@ type githubWebhookRepository struct {
|
||||
config *provisioning.Repository
|
||||
owner string
|
||||
repo string
|
||||
secrets secrets.RepositorySecrets
|
||||
secret common.RawSecureValue
|
||||
gh pgh.Client
|
||||
webhookURL string
|
||||
}
|
||||
@@ -47,7 +45,7 @@ type githubWebhookRepository struct {
|
||||
func NewGithubWebhookRepository(
|
||||
basic pgh.GithubRepository,
|
||||
webhookURL string,
|
||||
secrets secrets.RepositorySecrets,
|
||||
secret common.RawSecureValue,
|
||||
) GithubWebhookRepository {
|
||||
return &githubWebhookRepository{
|
||||
GithubRepository: basic,
|
||||
@@ -56,7 +54,7 @@ func NewGithubWebhookRepository(
|
||||
repo: basic.Repo(),
|
||||
gh: basic.Client(),
|
||||
webhookURL: webhookURL,
|
||||
secrets: secrets,
|
||||
secret: secret,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,12 +64,11 @@ func (r *githubWebhookRepository) Webhook(ctx context.Context, req *http.Request
|
||||
return nil, fmt.Errorf("unexpected webhook request")
|
||||
}
|
||||
|
||||
secret, err := r.secrets.Decrypt(ctx, r.config, string(r.config.Status.Webhook.EncryptedSecret))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decrypt secret: %w", err)
|
||||
if r.secret.IsZero() {
|
||||
return nil, fmt.Errorf("missing webhook secret")
|
||||
}
|
||||
|
||||
payload, err := github.ValidatePayload(req, secret)
|
||||
payload, err := github.ValidatePayload(req, []byte(r.secret))
|
||||
if err != nil {
|
||||
return nil, apierrors.NewUnauthorized("invalid signature")
|
||||
}
|
||||
@@ -239,8 +236,6 @@ func (r *githubWebhookRepository) updateWebhook(ctx context.Context) (pgh.Webhoo
|
||||
return pgh.WebhookConfig{}, false, fmt.Errorf("get webhook: %w", err)
|
||||
}
|
||||
|
||||
hook.Secret = r.config.Status.Webhook.Secret // we always random gen this, so don't use it for mustUpdate below.
|
||||
|
||||
var mustUpdate bool
|
||||
|
||||
if hook.URL != r.webhookURL {
|
||||
@@ -248,6 +243,7 @@ func (r *githubWebhookRepository) updateWebhook(ctx context.Context) (pgh.Webhoo
|
||||
hook.URL = r.webhookURL
|
||||
}
|
||||
|
||||
slices.Sort(hook.Events) // consistent order for comparison
|
||||
if !slices.Equal(hook.Events, subscribedEvents) {
|
||||
mustUpdate = true
|
||||
hook.Events = subscribedEvents
|
||||
@@ -263,7 +259,6 @@ func (r *githubWebhookRepository) updateWebhook(ctx context.Context) (pgh.Webhoo
|
||||
return pgh.WebhookConfig{}, false, fmt.Errorf("could not generate secret: %w", err)
|
||||
}
|
||||
hook.Secret = secret.String()
|
||||
|
||||
if err := r.gh.EditWebhook(ctx, r.owner, r.repo, hook); err != nil {
|
||||
return pgh.WebhookConfig{}, false, fmt.Errorf("edit webhook: %w", err)
|
||||
}
|
||||
@@ -304,10 +299,16 @@ func (r *githubWebhookRepository) OnCreate(ctx context.Context) ([]map[string]in
|
||||
"value": &provisioning.WebhookStatus{
|
||||
ID: hook.ID,
|
||||
URL: hook.URL,
|
||||
Secret: hook.Secret,
|
||||
SubscribedEvents: hook.Events,
|
||||
},
|
||||
},
|
||||
{
|
||||
"op": "replace",
|
||||
"path": "/secure/webhookSecret",
|
||||
"value": map[string]string{
|
||||
"create": hook.Secret,
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -316,42 +317,34 @@ func (r *githubWebhookRepository) OnUpdate(ctx context.Context) ([]map[string]in
|
||||
return nil, nil
|
||||
}
|
||||
ctx, _ = r.logger(ctx, "")
|
||||
hook, _, err := r.updateWebhook(ctx)
|
||||
if err != nil {
|
||||
hook, changed, err := r.updateWebhook(ctx)
|
||||
if err != nil || !changed {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return []map[string]interface{}{
|
||||
{
|
||||
"op": "replace",
|
||||
"path": "/status/webhook",
|
||||
"value": &provisioning.WebhookStatus{
|
||||
ID: hook.ID,
|
||||
URL: hook.URL,
|
||||
Secret: hook.Secret,
|
||||
SubscribedEvents: hook.Events,
|
||||
},
|
||||
// update the webhook and secret
|
||||
return []map[string]any{{
|
||||
"op": "replace",
|
||||
"path": "/status/webhook",
|
||||
"value": &provisioning.WebhookStatus{
|
||||
ID: hook.ID,
|
||||
URL: hook.URL,
|
||||
SubscribedEvents: hook.Events,
|
||||
},
|
||||
}, nil
|
||||
}, {
|
||||
"op": "replace",
|
||||
"path": "/secure/webhookSecret",
|
||||
"value": map[string]string{
|
||||
"create": hook.Secret,
|
||||
},
|
||||
}}, nil
|
||||
}
|
||||
|
||||
func (r *githubWebhookRepository) OnDelete(ctx context.Context) error {
|
||||
ctx, logger := r.logger(ctx, "")
|
||||
if err := r.GithubRepository.OnDelete(ctx); err != nil {
|
||||
return fmt.Errorf("on delete from basic github repository: %w", err)
|
||||
}
|
||||
|
||||
if r.config.Status.Webhook == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
secretName := r.config.Name + webhookSecretSuffix
|
||||
if err := r.secrets.Delete(ctx, r.config, secretName); err != nil {
|
||||
return fmt.Errorf("delete webhook secret: %w", err)
|
||||
}
|
||||
|
||||
logger.Info("Deleted webhook secret", "secretName", secretName)
|
||||
|
||||
return r.deleteWebhook(ctx)
|
||||
}
|
||||
|
||||
|
||||
@@ -14,13 +14,15 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository/github"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/secrets"
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
|
||||
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/repository/github"
|
||||
)
|
||||
|
||||
func TestParseWebhooks(t *testing.T) {
|
||||
@@ -114,9 +116,7 @@ func TestGitHubRepository_Webhook(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
config *provisioning.Repository
|
||||
webhookSecret string
|
||||
setupRequest func() *http.Request
|
||||
mockSetup func(t *testing.T, mockSecrets *secrets.MockRepositorySecrets)
|
||||
expected *provisioning.WebhookResponse
|
||||
expectedError error
|
||||
}{
|
||||
@@ -138,30 +138,6 @@ func TestGitHubRepository_Webhook(t *testing.T) {
|
||||
},
|
||||
expectedError: fmt.Errorf("unexpected webhook request"),
|
||||
},
|
||||
{
|
||||
name: "secret decryption error",
|
||||
config: &provisioning.Repository{
|
||||
Spec: provisioning.RepositorySpec{
|
||||
GitHub: &provisioning.GitHubRepositoryConfig{
|
||||
Branch: "main",
|
||||
},
|
||||
},
|
||||
Status: provisioning.RepositoryStatus{
|
||||
Webhook: &provisioning.WebhookStatus{
|
||||
EncryptedSecret: []byte("encrypted-secret"),
|
||||
},
|
||||
},
|
||||
},
|
||||
setupRequest: func() *http.Request {
|
||||
req, _ := http.NewRequest("POST", "/webhook", nil)
|
||||
return req
|
||||
},
|
||||
mockSetup: func(t *testing.T, mockSecrets *secrets.MockRepositorySecrets) {
|
||||
mockSecrets.EXPECT().Decrypt(mock.Anything, mock.Anything, "encrypted-secret").
|
||||
Return(nil, errors.New("decryption failed"))
|
||||
},
|
||||
expectedError: fmt.Errorf("failed to decrypt secret: decryption failed"),
|
||||
},
|
||||
{
|
||||
name: "invalid signature",
|
||||
config: &provisioning.Repository{
|
||||
@@ -171,22 +147,15 @@ func TestGitHubRepository_Webhook(t *testing.T) {
|
||||
},
|
||||
},
|
||||
Status: provisioning.RepositoryStatus{
|
||||
Webhook: &provisioning.WebhookStatus{
|
||||
EncryptedSecret: []byte("encrypted-secret"),
|
||||
},
|
||||
Webhook: &provisioning.WebhookStatus{},
|
||||
},
|
||||
},
|
||||
webhookSecret: "webhook-secret",
|
||||
setupRequest: func() *http.Request {
|
||||
req, _ := http.NewRequest("POST", "/webhook", strings.NewReader("invalid payload"))
|
||||
req.Header.Set("X-Hub-Signature-256", "invalid")
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
return req
|
||||
},
|
||||
mockSetup: func(t *testing.T, mockSecrets *secrets.MockRepositorySecrets) {
|
||||
mockSecrets.EXPECT().Decrypt(mock.Anything, mock.Anything, "encrypted-secret").
|
||||
Return([]byte("webhook-secret"), nil)
|
||||
},
|
||||
expectedError: apierrors.NewUnauthorized("invalid signature"),
|
||||
},
|
||||
{
|
||||
@@ -198,12 +167,9 @@ func TestGitHubRepository_Webhook(t *testing.T) {
|
||||
},
|
||||
},
|
||||
Status: provisioning.RepositoryStatus{
|
||||
Webhook: &provisioning.WebhookStatus{
|
||||
EncryptedSecret: []byte("encrypted-secret"),
|
||||
},
|
||||
Webhook: &provisioning.WebhookStatus{},
|
||||
},
|
||||
},
|
||||
webhookSecret: "webhook-secret",
|
||||
setupRequest: func() *http.Request {
|
||||
payload := `{}`
|
||||
req, _ := http.NewRequest("POST", "/webhook", strings.NewReader(payload))
|
||||
@@ -218,10 +184,6 @@ func TestGitHubRepository_Webhook(t *testing.T) {
|
||||
|
||||
return req
|
||||
},
|
||||
mockSetup: func(t *testing.T, mockSecrets *secrets.MockRepositorySecrets) {
|
||||
mockSecrets.EXPECT().Decrypt(mock.Anything, mock.Anything, "encrypted-secret").
|
||||
Return([]byte("webhook-secret"), nil)
|
||||
},
|
||||
expected: &provisioning.WebhookResponse{
|
||||
Code: http.StatusOK,
|
||||
Message: "ping received",
|
||||
@@ -239,12 +201,9 @@ func TestGitHubRepository_Webhook(t *testing.T) {
|
||||
},
|
||||
},
|
||||
Status: provisioning.RepositoryStatus{
|
||||
Webhook: &provisioning.WebhookStatus{
|
||||
EncryptedSecret: []byte("encrypted-secret"),
|
||||
},
|
||||
Webhook: &provisioning.WebhookStatus{},
|
||||
},
|
||||
},
|
||||
webhookSecret: "webhook-secret",
|
||||
setupRequest: func() *http.Request {
|
||||
payload := `{
|
||||
"ref": "refs/heads/feature",
|
||||
@@ -264,10 +223,6 @@ func TestGitHubRepository_Webhook(t *testing.T) {
|
||||
|
||||
return req
|
||||
},
|
||||
mockSetup: func(t *testing.T, mockSecrets *secrets.MockRepositorySecrets) {
|
||||
mockSecrets.EXPECT().Decrypt(mock.Anything, mock.Anything, "encrypted-secret").
|
||||
Return([]byte("webhook-secret"), nil)
|
||||
},
|
||||
expected: &provisioning.WebhookResponse{
|
||||
Code: http.StatusOK,
|
||||
},
|
||||
@@ -287,12 +242,9 @@ func TestGitHubRepository_Webhook(t *testing.T) {
|
||||
},
|
||||
},
|
||||
Status: provisioning.RepositoryStatus{
|
||||
Webhook: &provisioning.WebhookStatus{
|
||||
EncryptedSecret: []byte("encrypted-secret"),
|
||||
},
|
||||
Webhook: &provisioning.WebhookStatus{},
|
||||
},
|
||||
},
|
||||
webhookSecret: "webhook-secret",
|
||||
setupRequest: func() *http.Request {
|
||||
payload := `{
|
||||
"ref": "refs/heads/main",
|
||||
@@ -312,10 +264,6 @@ func TestGitHubRepository_Webhook(t *testing.T) {
|
||||
|
||||
return req
|
||||
},
|
||||
mockSetup: func(t *testing.T, mockSecrets *secrets.MockRepositorySecrets) {
|
||||
mockSecrets.EXPECT().Decrypt(mock.Anything, mock.Anything, "encrypted-secret").
|
||||
Return([]byte("webhook-secret"), nil)
|
||||
},
|
||||
expected: &provisioning.WebhookResponse{
|
||||
Code: http.StatusAccepted,
|
||||
Job: &provisioning.JobSpec{
|
||||
@@ -336,12 +284,9 @@ func TestGitHubRepository_Webhook(t *testing.T) {
|
||||
},
|
||||
},
|
||||
Status: provisioning.RepositoryStatus{
|
||||
Webhook: &provisioning.WebhookStatus{
|
||||
EncryptedSecret: []byte("encrypted-secret"),
|
||||
},
|
||||
Webhook: &provisioning.WebhookStatus{},
|
||||
},
|
||||
},
|
||||
webhookSecret: "webhook-secret",
|
||||
setupRequest: func() *http.Request {
|
||||
payload := `{
|
||||
"ref": "refs/heads/main"
|
||||
@@ -358,10 +303,6 @@ func TestGitHubRepository_Webhook(t *testing.T) {
|
||||
|
||||
return req
|
||||
},
|
||||
mockSetup: func(t *testing.T, mockSecrets *secrets.MockRepositorySecrets) {
|
||||
mockSecrets.EXPECT().Decrypt(mock.Anything, mock.Anything, "encrypted-secret").
|
||||
Return([]byte("webhook-secret"), nil)
|
||||
},
|
||||
expectedError: fmt.Errorf("missing repository in push event"),
|
||||
},
|
||||
{
|
||||
@@ -373,12 +314,9 @@ func TestGitHubRepository_Webhook(t *testing.T) {
|
||||
},
|
||||
},
|
||||
Status: provisioning.RepositoryStatus{
|
||||
Webhook: &provisioning.WebhookStatus{
|
||||
EncryptedSecret: []byte("encrypted-secret"),
|
||||
},
|
||||
Webhook: &provisioning.WebhookStatus{},
|
||||
},
|
||||
},
|
||||
webhookSecret: "webhook-secret",
|
||||
setupRequest: func() *http.Request {
|
||||
payload := `{
|
||||
"ref": "refs/heads/main",
|
||||
@@ -398,10 +336,6 @@ func TestGitHubRepository_Webhook(t *testing.T) {
|
||||
|
||||
return req
|
||||
},
|
||||
mockSetup: func(t *testing.T, mockSecrets *secrets.MockRepositorySecrets) {
|
||||
mockSecrets.EXPECT().Decrypt(mock.Anything, mock.Anything, "encrypted-secret").
|
||||
Return([]byte("webhook-secret"), nil)
|
||||
},
|
||||
expectedError: fmt.Errorf("repository mismatch"),
|
||||
},
|
||||
{
|
||||
@@ -416,12 +350,9 @@ func TestGitHubRepository_Webhook(t *testing.T) {
|
||||
},
|
||||
},
|
||||
Status: provisioning.RepositoryStatus{
|
||||
Webhook: &provisioning.WebhookStatus{
|
||||
EncryptedSecret: []byte("encrypted-secret"),
|
||||
},
|
||||
Webhook: &provisioning.WebhookStatus{},
|
||||
},
|
||||
},
|
||||
webhookSecret: "webhook-secret",
|
||||
setupRequest: func() *http.Request {
|
||||
payload := `{
|
||||
"ref": "refs/heads/main",
|
||||
@@ -441,10 +372,6 @@ func TestGitHubRepository_Webhook(t *testing.T) {
|
||||
|
||||
return req
|
||||
},
|
||||
mockSetup: func(t *testing.T, mockSecrets *secrets.MockRepositorySecrets) {
|
||||
mockSecrets.EXPECT().Decrypt(mock.Anything, mock.Anything, "encrypted-secret").
|
||||
Return([]byte("webhook-secret"), nil)
|
||||
},
|
||||
expected: &provisioning.WebhookResponse{
|
||||
Code: http.StatusOK,
|
||||
},
|
||||
@@ -461,12 +388,9 @@ func TestGitHubRepository_Webhook(t *testing.T) {
|
||||
},
|
||||
},
|
||||
Status: provisioning.RepositoryStatus{
|
||||
Webhook: &provisioning.WebhookStatus{
|
||||
EncryptedSecret: []byte("encrypted-secret"),
|
||||
},
|
||||
Webhook: &provisioning.WebhookStatus{},
|
||||
},
|
||||
},
|
||||
webhookSecret: "webhook-secret",
|
||||
setupRequest: func() *http.Request {
|
||||
payload := `{
|
||||
"action": "opened",
|
||||
@@ -497,10 +421,6 @@ func TestGitHubRepository_Webhook(t *testing.T) {
|
||||
|
||||
return req
|
||||
},
|
||||
mockSetup: func(t *testing.T, mockSecrets *secrets.MockRepositorySecrets) {
|
||||
mockSecrets.EXPECT().Decrypt(mock.Anything, mock.Anything, "encrypted-secret").
|
||||
Return([]byte("webhook-secret"), nil)
|
||||
},
|
||||
expected: &provisioning.WebhookResponse{
|
||||
Code: http.StatusAccepted,
|
||||
Message: "pull request: opened",
|
||||
@@ -528,12 +448,9 @@ func TestGitHubRepository_Webhook(t *testing.T) {
|
||||
},
|
||||
},
|
||||
Status: provisioning.RepositoryStatus{
|
||||
Webhook: &provisioning.WebhookStatus{
|
||||
EncryptedSecret: []byte("encrypted-secret"),
|
||||
},
|
||||
Webhook: &provisioning.WebhookStatus{},
|
||||
},
|
||||
},
|
||||
webhookSecret: "webhook-secret",
|
||||
setupRequest: func() *http.Request {
|
||||
payload := `{
|
||||
"action": "synchronize",
|
||||
@@ -564,10 +481,6 @@ func TestGitHubRepository_Webhook(t *testing.T) {
|
||||
|
||||
return req
|
||||
},
|
||||
mockSetup: func(t *testing.T, mockSecrets *secrets.MockRepositorySecrets) {
|
||||
mockSecrets.EXPECT().Decrypt(mock.Anything, mock.Anything, "encrypted-secret").
|
||||
Return([]byte("webhook-secret"), nil)
|
||||
},
|
||||
expected: &provisioning.WebhookResponse{
|
||||
Code: http.StatusAccepted,
|
||||
Message: "pull request: synchronize",
|
||||
@@ -592,12 +505,9 @@ func TestGitHubRepository_Webhook(t *testing.T) {
|
||||
},
|
||||
},
|
||||
Status: provisioning.RepositoryStatus{
|
||||
Webhook: &provisioning.WebhookStatus{
|
||||
EncryptedSecret: []byte("encrypted-secret"),
|
||||
},
|
||||
Webhook: &provisioning.WebhookStatus{},
|
||||
},
|
||||
},
|
||||
webhookSecret: "webhook-secret",
|
||||
setupRequest: func() *http.Request {
|
||||
payload := `{
|
||||
"action": "opened",
|
||||
@@ -628,10 +538,6 @@ func TestGitHubRepository_Webhook(t *testing.T) {
|
||||
|
||||
return req
|
||||
},
|
||||
mockSetup: func(t *testing.T, mockSecrets *secrets.MockRepositorySecrets) {
|
||||
mockSecrets.EXPECT().Decrypt(mock.Anything, mock.Anything, "encrypted-secret").
|
||||
Return([]byte("webhook-secret"), nil)
|
||||
},
|
||||
expected: &provisioning.WebhookResponse{
|
||||
Code: http.StatusOK,
|
||||
Message: "ignoring pull request event as develop is not the configured branch",
|
||||
@@ -646,12 +552,9 @@ func TestGitHubRepository_Webhook(t *testing.T) {
|
||||
},
|
||||
},
|
||||
Status: provisioning.RepositoryStatus{
|
||||
Webhook: &provisioning.WebhookStatus{
|
||||
EncryptedSecret: []byte("encrypted-secret"),
|
||||
},
|
||||
Webhook: &provisioning.WebhookStatus{},
|
||||
},
|
||||
},
|
||||
webhookSecret: "webhook-secret",
|
||||
setupRequest: func() *http.Request {
|
||||
payload := `{
|
||||
"action": "closed",
|
||||
@@ -682,10 +585,6 @@ func TestGitHubRepository_Webhook(t *testing.T) {
|
||||
|
||||
return req
|
||||
},
|
||||
mockSetup: func(t *testing.T, mockSecrets *secrets.MockRepositorySecrets) {
|
||||
mockSecrets.EXPECT().Decrypt(mock.Anything, mock.Anything, "encrypted-secret").
|
||||
Return([]byte("webhook-secret"), nil)
|
||||
},
|
||||
expected: &provisioning.WebhookResponse{
|
||||
Code: http.StatusOK,
|
||||
Message: "ignore pull request event: closed",
|
||||
@@ -700,12 +599,9 @@ func TestGitHubRepository_Webhook(t *testing.T) {
|
||||
},
|
||||
},
|
||||
Status: provisioning.RepositoryStatus{
|
||||
Webhook: &provisioning.WebhookStatus{
|
||||
EncryptedSecret: []byte("encrypted-secret"),
|
||||
},
|
||||
Webhook: &provisioning.WebhookStatus{},
|
||||
},
|
||||
},
|
||||
webhookSecret: "webhook-secret",
|
||||
setupRequest: func() *http.Request {
|
||||
payload := `{
|
||||
"action": "opened",
|
||||
@@ -733,10 +629,6 @@ func TestGitHubRepository_Webhook(t *testing.T) {
|
||||
|
||||
return req
|
||||
},
|
||||
mockSetup: func(t *testing.T, mockSecrets *secrets.MockRepositorySecrets) {
|
||||
mockSecrets.EXPECT().Decrypt(mock.Anything, mock.Anything, "encrypted-secret").
|
||||
Return([]byte("webhook-secret"), nil)
|
||||
},
|
||||
expectedError: fmt.Errorf("missing repository in pull request event"),
|
||||
},
|
||||
{
|
||||
@@ -746,12 +638,9 @@ func TestGitHubRepository_Webhook(t *testing.T) {
|
||||
// GitHub config is intentionally missing
|
||||
},
|
||||
Status: provisioning.RepositoryStatus{
|
||||
Webhook: &provisioning.WebhookStatus{
|
||||
EncryptedSecret: []byte("encrypted-secret"),
|
||||
},
|
||||
Webhook: &provisioning.WebhookStatus{},
|
||||
},
|
||||
},
|
||||
webhookSecret: "webhook-secret",
|
||||
setupRequest: func() *http.Request {
|
||||
payload := `{
|
||||
"action": "opened",
|
||||
@@ -782,10 +671,6 @@ func TestGitHubRepository_Webhook(t *testing.T) {
|
||||
|
||||
return req
|
||||
},
|
||||
mockSetup: func(t *testing.T, mockSecrets *secrets.MockRepositorySecrets) {
|
||||
mockSecrets.EXPECT().Decrypt(mock.Anything, mock.Anything, "encrypted-secret").
|
||||
Return([]byte("webhook-secret"), nil)
|
||||
},
|
||||
expectedError: fmt.Errorf("missing GitHub config"),
|
||||
},
|
||||
{
|
||||
@@ -797,12 +682,9 @@ func TestGitHubRepository_Webhook(t *testing.T) {
|
||||
},
|
||||
},
|
||||
Status: provisioning.RepositoryStatus{
|
||||
Webhook: &provisioning.WebhookStatus{
|
||||
EncryptedSecret: []byte("encrypted-secret"),
|
||||
},
|
||||
Webhook: &provisioning.WebhookStatus{},
|
||||
},
|
||||
},
|
||||
webhookSecret: "webhook-secret",
|
||||
setupRequest: func() *http.Request {
|
||||
payload := `{
|
||||
"action": "opened",
|
||||
@@ -833,10 +715,6 @@ func TestGitHubRepository_Webhook(t *testing.T) {
|
||||
|
||||
return req
|
||||
},
|
||||
mockSetup: func(t *testing.T, mockSecrets *secrets.MockRepositorySecrets) {
|
||||
mockSecrets.EXPECT().Decrypt(mock.Anything, mock.Anything, "encrypted-secret").
|
||||
Return([]byte("webhook-secret"), nil)
|
||||
},
|
||||
expectedError: fmt.Errorf("repository mismatch"),
|
||||
},
|
||||
{
|
||||
@@ -848,12 +726,9 @@ func TestGitHubRepository_Webhook(t *testing.T) {
|
||||
},
|
||||
},
|
||||
Status: provisioning.RepositoryStatus{
|
||||
Webhook: &provisioning.WebhookStatus{
|
||||
EncryptedSecret: []byte("encrypted-secret"),
|
||||
},
|
||||
Webhook: &provisioning.WebhookStatus{},
|
||||
},
|
||||
},
|
||||
webhookSecret: "webhook-secret",
|
||||
setupRequest: func() *http.Request {
|
||||
payload := `{
|
||||
"action": "opened",
|
||||
@@ -873,39 +748,8 @@ func TestGitHubRepository_Webhook(t *testing.T) {
|
||||
|
||||
return req
|
||||
},
|
||||
mockSetup: func(t *testing.T, mockSecrets *secrets.MockRepositorySecrets) {
|
||||
mockSecrets.EXPECT().Decrypt(mock.Anything, mock.Anything, "encrypted-secret").
|
||||
Return([]byte("webhook-secret"), nil)
|
||||
},
|
||||
expectedError: fmt.Errorf("expected PR in event"),
|
||||
},
|
||||
{
|
||||
name: "secret decryption error with new secrets store",
|
||||
config: &provisioning.Repository{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: provisioning.RepositorySpec{
|
||||
GitHub: &provisioning.GitHubRepositoryConfig{
|
||||
Branch: "main",
|
||||
},
|
||||
},
|
||||
Status: provisioning.RepositoryStatus{
|
||||
Webhook: &provisioning.WebhookStatus{
|
||||
EncryptedSecret: []byte("test-secret"),
|
||||
},
|
||||
},
|
||||
},
|
||||
setupRequest: func() *http.Request {
|
||||
req, _ := http.NewRequest("POST", "/webhook", nil)
|
||||
return req
|
||||
},
|
||||
mockSetup: func(t *testing.T, mockSecrets *secrets.MockRepositorySecrets) {
|
||||
mockSecrets.EXPECT().Decrypt(mock.Anything, mock.Anything, "test-secret").
|
||||
Return(nil, errors.New("decryption failed"))
|
||||
},
|
||||
expectedError: fmt.Errorf("failed to decrypt secret: decryption failed"),
|
||||
},
|
||||
{
|
||||
name: "ping event with new secrets store",
|
||||
config: &provisioning.Repository{
|
||||
@@ -918,12 +762,9 @@ func TestGitHubRepository_Webhook(t *testing.T) {
|
||||
},
|
||||
},
|
||||
Status: provisioning.RepositoryStatus{
|
||||
Webhook: &provisioning.WebhookStatus{
|
||||
EncryptedSecret: []byte("test-secret"),
|
||||
},
|
||||
Webhook: &provisioning.WebhookStatus{},
|
||||
},
|
||||
},
|
||||
webhookSecret: "webhook-secret",
|
||||
setupRequest: func() *http.Request {
|
||||
payload := `{}`
|
||||
req, _ := http.NewRequest("POST", "/webhook", strings.NewReader(payload))
|
||||
@@ -938,10 +779,6 @@ func TestGitHubRepository_Webhook(t *testing.T) {
|
||||
|
||||
return req
|
||||
},
|
||||
mockSetup: func(t *testing.T, mockSecrets *secrets.MockRepositorySecrets) {
|
||||
mockSecrets.EXPECT().Decrypt(mock.Anything, mock.Anything, "test-secret").
|
||||
Return([]byte("webhook-secret"), nil)
|
||||
},
|
||||
expected: &provisioning.WebhookResponse{
|
||||
Code: http.StatusOK,
|
||||
Message: "ping received",
|
||||
@@ -963,12 +800,9 @@ func TestGitHubRepository_Webhook(t *testing.T) {
|
||||
},
|
||||
},
|
||||
Status: provisioning.RepositoryStatus{
|
||||
Webhook: &provisioning.WebhookStatus{
|
||||
EncryptedSecret: []byte("test-secret"),
|
||||
},
|
||||
Webhook: &provisioning.WebhookStatus{},
|
||||
},
|
||||
},
|
||||
webhookSecret: "webhook-secret",
|
||||
setupRequest: func() *http.Request {
|
||||
payload := `{
|
||||
"ref": "refs/heads/main",
|
||||
@@ -988,10 +822,6 @@ func TestGitHubRepository_Webhook(t *testing.T) {
|
||||
|
||||
return req
|
||||
},
|
||||
mockSetup: func(t *testing.T, mockSecrets *secrets.MockRepositorySecrets) {
|
||||
mockSecrets.EXPECT().Decrypt(mock.Anything, mock.Anything, "test-secret").
|
||||
Return([]byte("webhook-secret"), nil)
|
||||
},
|
||||
expected: &provisioning.WebhookResponse{
|
||||
Code: http.StatusAccepted,
|
||||
Job: &provisioning.JobSpec{
|
||||
@@ -1012,12 +842,9 @@ func TestGitHubRepository_Webhook(t *testing.T) {
|
||||
},
|
||||
},
|
||||
Status: provisioning.RepositoryStatus{
|
||||
Webhook: &provisioning.WebhookStatus{
|
||||
EncryptedSecret: []byte("encrypted-secret"),
|
||||
},
|
||||
Webhook: &provisioning.WebhookStatus{},
|
||||
},
|
||||
},
|
||||
webhookSecret: "webhook-secret",
|
||||
setupRequest: func() *http.Request {
|
||||
payload := `{}`
|
||||
req, _ := http.NewRequest("POST", "/webhook", strings.NewReader(payload))
|
||||
@@ -1032,10 +859,6 @@ func TestGitHubRepository_Webhook(t *testing.T) {
|
||||
|
||||
return req
|
||||
},
|
||||
mockSetup: func(t *testing.T, mockSecrets *secrets.MockRepositorySecrets) {
|
||||
mockSecrets.EXPECT().Decrypt(mock.Anything, mock.Anything, "encrypted-secret").
|
||||
Return([]byte("webhook-secret"), nil)
|
||||
},
|
||||
expected: &provisioning.WebhookResponse{
|
||||
Code: http.StatusNotImplemented,
|
||||
Message: "unsupported messageType: team",
|
||||
@@ -1045,20 +868,12 @@ func TestGitHubRepository_Webhook(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Create a mock secrets service
|
||||
mockSecrets := secrets.NewMockRepositorySecrets(t)
|
||||
|
||||
// Set up the mock expectations
|
||||
if tt.mockSetup != nil {
|
||||
tt.mockSetup(t, mockSecrets)
|
||||
}
|
||||
|
||||
// Create a GitHub repository with the test config
|
||||
repo := &githubWebhookRepository{
|
||||
config: tt.config,
|
||||
owner: "grafana",
|
||||
repo: "grafana",
|
||||
secrets: mockSecrets,
|
||||
config: tt.config,
|
||||
owner: "grafana",
|
||||
repo: "grafana",
|
||||
secret: common.RawSecureValue("webhook-secret"),
|
||||
}
|
||||
|
||||
// Call the Webhook method
|
||||
@@ -1070,7 +885,7 @@ func TestGitHubRepository_Webhook(t *testing.T) {
|
||||
var statusErr *apierrors.StatusError
|
||||
if errors.As(tt.expectedError, &statusErr) {
|
||||
var actualStatusErr *apierrors.StatusError
|
||||
require.True(t, errors.As(err, &actualStatusErr), "Expected StatusError but got different error type")
|
||||
require.True(t, errors.As(err, &actualStatusErr), "Expected StatusError but got different error type: %T", err)
|
||||
require.Equal(t, statusErr.Status().Message, actualStatusErr.Status().Message)
|
||||
require.Equal(t, statusErr.Status().Code, actualStatusErr.Status().Code)
|
||||
} else {
|
||||
@@ -1097,9 +912,6 @@ func TestGitHubRepository_Webhook(t *testing.T) {
|
||||
require.Nil(t, response.Job)
|
||||
}
|
||||
}
|
||||
|
||||
// Verify all mock expectations were met
|
||||
mockSecrets.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1202,9 +1014,8 @@ func TestGitHubRepository_OnCreate(t *testing.T) {
|
||||
},
|
||||
webhookURL: "https://example.com/webhook",
|
||||
expectedHook: &provisioning.WebhookStatus{
|
||||
ID: 123,
|
||||
URL: "https://example.com/webhook",
|
||||
Secret: "test-secret",
|
||||
ID: 123,
|
||||
URL: "https://example.com/webhook",
|
||||
},
|
||||
expectedError: nil,
|
||||
},
|
||||
@@ -1270,12 +1081,21 @@ func TestGitHubRepository_OnCreate(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
if tt.expectedHook != nil {
|
||||
require.NotNil(t, hookOps)
|
||||
require.Len(t, hookOps, 1)
|
||||
require.Len(t, hookOps, 2)
|
||||
require.Equal(t, "replace", hookOps[0]["op"])
|
||||
require.Equal(t, "/status/webhook", hookOps[0]["path"])
|
||||
require.Equal(t, tt.expectedHook.ID, hookOps[0]["value"].(*provisioning.WebhookStatus).ID)
|
||||
require.Equal(t, tt.expectedHook.URL, hookOps[0]["value"].(*provisioning.WebhookStatus).URL)
|
||||
require.NotEmpty(t, hookOps[0]["value"].(*provisioning.WebhookStatus).Secret) // Secret is randomly generated, so just check it's not empty
|
||||
|
||||
require.Equal(t, "replace", hookOps[1]["op"])
|
||||
require.Equal(t, "/secure/webhookSecret", hookOps[1]["path"])
|
||||
vals, ok := hookOps[1]["value"].(map[string]string)
|
||||
require.True(t, ok, "expected webhookSecret as map")
|
||||
require.Len(t, vals, 1, "with one property")
|
||||
require.NotEmpty(t, vals["create"], "secret should be created")
|
||||
|
||||
_, err := uuid.Parse(vals["create"])
|
||||
require.NoError(t, err, "the secret is a valid UUID")
|
||||
} else {
|
||||
require.Nil(t, hookOps)
|
||||
}
|
||||
@@ -1607,7 +1427,6 @@ func TestGitHubRepository_OnUpdate(t *testing.T) {
|
||||
URL: "https://example.com/webhook",
|
||||
Events: subscribedEvents,
|
||||
}, nil)
|
||||
|
||||
// No EditWebhook call expected since no changes needed
|
||||
},
|
||||
config: &provisioning.Repository{
|
||||
@@ -1618,19 +1437,18 @@ func TestGitHubRepository_OnUpdate(t *testing.T) {
|
||||
},
|
||||
Status: provisioning.RepositoryStatus{
|
||||
Webhook: &provisioning.WebhookStatus{
|
||||
ID: 123,
|
||||
URL: "https://example.com/webhook",
|
||||
Secret: "secret",
|
||||
ID: 123,
|
||||
URL: "https://example.com/webhook",
|
||||
},
|
||||
},
|
||||
Secure: provisioning.SecureValues{
|
||||
WebhookSecret: common.InlineSecureValue{
|
||||
Name: "valid-secret",
|
||||
},
|
||||
},
|
||||
},
|
||||
webhookURL: "https://example.com/webhook",
|
||||
expectedHook: &provisioning.WebhookStatus{
|
||||
ID: 123,
|
||||
URL: "https://example.com/webhook",
|
||||
SubscribedEvents: subscribedEvents,
|
||||
Secret: "secret",
|
||||
},
|
||||
webhookURL: "https://example.com/webhook",
|
||||
expectedHook: nil, // nothing changed
|
||||
expectedError: nil,
|
||||
},
|
||||
}
|
||||
@@ -1662,17 +1480,22 @@ func TestGitHubRepository_OnUpdate(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
if tt.expectedHook != nil {
|
||||
require.NotNil(t, hookOps)
|
||||
require.Len(t, hookOps, 1)
|
||||
require.Len(t, hookOps, 2)
|
||||
require.Equal(t, "replace", hookOps[0]["op"])
|
||||
require.Equal(t, "/status/webhook", hookOps[0]["path"])
|
||||
require.Equal(t, tt.expectedHook.ID, hookOps[0]["value"].(*provisioning.WebhookStatus).ID)
|
||||
require.Equal(t, tt.expectedHook.URL, hookOps[0]["value"].(*provisioning.WebhookStatus).URL)
|
||||
if tt.expectedHook.Secret != "" {
|
||||
require.Equal(t, tt.expectedHook.Secret, hookOps[0]["value"].(*provisioning.WebhookStatus).Secret)
|
||||
} else {
|
||||
require.NotEmpty(t, hookOps[0]["value"].(*provisioning.WebhookStatus).Secret) // Secret is randomly generated, so just check it's not empty
|
||||
}
|
||||
require.ElementsMatch(t, tt.expectedHook.SubscribedEvents, hookOps[0]["value"].(*provisioning.WebhookStatus).SubscribedEvents)
|
||||
|
||||
require.Equal(t, "replace", hookOps[1]["op"])
|
||||
require.Equal(t, "/secure/webhookSecret", hookOps[1]["path"])
|
||||
vals, ok := hookOps[1]["value"].(map[string]string)
|
||||
require.True(t, ok, "expected webhookSecret as map")
|
||||
require.Len(t, vals, 1, "with one property")
|
||||
require.NotEmpty(t, vals["create"], "secret should be created")
|
||||
|
||||
_, err := uuid.Parse(vals["create"])
|
||||
require.NoError(t, err, "the secret is a valid UUID")
|
||||
} else {
|
||||
require.Nil(t, hookOps)
|
||||
}
|
||||
@@ -1687,17 +1510,14 @@ func TestGitHubRepository_OnUpdate(t *testing.T) {
|
||||
func TestGitHubRepository_OnDelete(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
setupMock func(m *github.MockClient, mockRepo *github.MockGithubRepository, mockSecrets *secrets.MockRepositorySecrets)
|
||||
setupMock func(m *github.MockClient)
|
||||
config *provisioning.Repository
|
||||
webhookURL string
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
name: "successfully delete webhook",
|
||||
setupMock: func(m *github.MockClient, mockRepo *github.MockGithubRepository, mockSecrets *secrets.MockRepositorySecrets) {
|
||||
mockRepo.On("OnDelete", mock.Anything).Return(nil)
|
||||
mockSecrets.EXPECT().Delete(mock.Anything, mock.Anything, mock.Anything).Return(nil)
|
||||
// Mock deleting the webhook
|
||||
setupMock: func(m *github.MockClient) {
|
||||
m.On("DeleteWebhook", mock.Anything, "grafana", "grafana", int64(123)).
|
||||
Return(nil)
|
||||
},
|
||||
@@ -1721,10 +1541,8 @@ func TestGitHubRepository_OnDelete(t *testing.T) {
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "no webhook URL provided",
|
||||
setupMock: func(_ *github.MockClient, mockRepo *github.MockGithubRepository, _ *secrets.MockRepositorySecrets) {
|
||||
mockRepo.On("OnDelete", mock.Anything).Return(nil)
|
||||
},
|
||||
name: "no webhook URL provided",
|
||||
setupMock: func(_ *github.MockClient) {},
|
||||
config: &provisioning.Repository{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-repo",
|
||||
@@ -1740,8 +1558,7 @@ func TestGitHubRepository_OnDelete(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "webhook not found in status",
|
||||
setupMock: func(_ *github.MockClient, mockRepo *github.MockGithubRepository, _ *secrets.MockRepositorySecrets) {
|
||||
mockRepo.On("OnDelete", mock.Anything).Return(nil)
|
||||
setupMock: func(_ *github.MockClient) {
|
||||
// No secrets deletion or webhook deletion mocks needed - method returns early when webhook is nil
|
||||
},
|
||||
config: &provisioning.Repository{
|
||||
@@ -1760,35 +1577,9 @@ func TestGitHubRepository_OnDelete(t *testing.T) {
|
||||
webhookURL: "https://example.com/webhook",
|
||||
expectedError: nil, // No error expected - method returns early when webhook is nil
|
||||
},
|
||||
{
|
||||
name: "error on delete from basic github repository",
|
||||
setupMock: func(_ *github.MockClient, mockRepo *github.MockGithubRepository, _ *secrets.MockRepositorySecrets) {
|
||||
mockRepo.On("OnDelete", mock.Anything).Return(fmt.Errorf("failed to delete webhook"))
|
||||
},
|
||||
config: &provisioning.Repository{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-repo",
|
||||
},
|
||||
Spec: provisioning.RepositorySpec{
|
||||
GitHub: &provisioning.GitHubRepositoryConfig{
|
||||
Branch: "main",
|
||||
},
|
||||
},
|
||||
Status: provisioning.RepositoryStatus{
|
||||
Webhook: &provisioning.WebhookStatus{
|
||||
ID: 123,
|
||||
URL: "https://example.com/webhook",
|
||||
},
|
||||
},
|
||||
},
|
||||
webhookURL: "https://example.com/webhook",
|
||||
expectedError: fmt.Errorf("on delete from basic github repository: failed to delete webhook"),
|
||||
},
|
||||
{
|
||||
name: "error deleting webhook",
|
||||
setupMock: func(m *github.MockClient, mockRepo *github.MockGithubRepository, mockSecrets *secrets.MockRepositorySecrets) {
|
||||
mockRepo.On("OnDelete", mock.Anything).Return(nil)
|
||||
mockSecrets.EXPECT().Delete(mock.Anything, mock.Anything, mock.Anything).Return(nil)
|
||||
setupMock: func(m *github.MockClient) {
|
||||
// Mock webhook deletion failure
|
||||
m.On("DeleteWebhook", mock.Anything, "grafana", "grafana", int64(123)).
|
||||
Return(fmt.Errorf("failed to delete webhook"))
|
||||
@@ -1819,15 +1610,13 @@ func TestGitHubRepository_OnDelete(t *testing.T) {
|
||||
// Setup mock GitHub client
|
||||
mockGH := github.NewMockClient(t)
|
||||
mockRepo := github.NewMockGithubRepository(t)
|
||||
mockSecrets := secrets.NewMockRepositorySecrets(t)
|
||||
tt.setupMock(mockGH, mockRepo, mockSecrets)
|
||||
tt.setupMock(mockGH)
|
||||
|
||||
// Create repository with mock
|
||||
repo := &githubWebhookRepository{
|
||||
GithubRepository: mockRepo,
|
||||
gh: mockGH,
|
||||
config: tt.config,
|
||||
secrets: mockSecrets,
|
||||
owner: "grafana",
|
||||
repo: "grafana",
|
||||
webhookURL: tt.webhookURL,
|
||||
@@ -1849,156 +1638,3 @@ func TestGitHubRepository_OnDelete(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGitHubRepository_OnDelete_WithSecrets(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
setupMock func(m *github.MockClient, mockRepo *github.MockGithubRepository, mockSecrets *secrets.MockRepositorySecrets)
|
||||
config *provisioning.Repository
|
||||
webhookURL string
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
name: "successful deletion with secrets",
|
||||
setupMock: func(m *github.MockClient, mockRepo *github.MockGithubRepository, mockSecrets *secrets.MockRepositorySecrets) {
|
||||
mockRepo.On("OnDelete", mock.Anything).Return(nil)
|
||||
mockSecrets.EXPECT().Delete(
|
||||
mock.Anything,
|
||||
&provisioning.Repository{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-repo",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: provisioning.RepositorySpec{
|
||||
GitHub: &provisioning.GitHubRepositoryConfig{
|
||||
Branch: "main",
|
||||
},
|
||||
},
|
||||
Status: provisioning.RepositoryStatus{
|
||||
Webhook: &provisioning.WebhookStatus{
|
||||
ID: 123,
|
||||
URL: "https://example.com/webhook",
|
||||
},
|
||||
},
|
||||
},
|
||||
"test-repo"+webhookSecretSuffix,
|
||||
).Return(nil)
|
||||
m.On("DeleteWebhook", mock.Anything, "grafana", "grafana", int64(123)).Return(nil)
|
||||
},
|
||||
config: &provisioning.Repository{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-repo",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: provisioning.RepositorySpec{
|
||||
GitHub: &provisioning.GitHubRepositoryConfig{
|
||||
Branch: "main",
|
||||
},
|
||||
},
|
||||
Status: provisioning.RepositoryStatus{
|
||||
Webhook: &provisioning.WebhookStatus{
|
||||
ID: 123,
|
||||
URL: "https://example.com/webhook",
|
||||
},
|
||||
},
|
||||
},
|
||||
webhookURL: "https://example.com/webhook",
|
||||
},
|
||||
{
|
||||
name: "secret deletion error",
|
||||
setupMock: func(_ *github.MockClient, mockRepo *github.MockGithubRepository, mockSecrets *secrets.MockRepositorySecrets) {
|
||||
mockRepo.On("OnDelete", mock.Anything).Return(nil)
|
||||
mockSecrets.EXPECT().Delete(
|
||||
mock.Anything,
|
||||
&provisioning.Repository{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-repo",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: provisioning.RepositorySpec{
|
||||
GitHub: &provisioning.GitHubRepositoryConfig{
|
||||
Branch: "main",
|
||||
},
|
||||
},
|
||||
Status: provisioning.RepositoryStatus{
|
||||
Webhook: &provisioning.WebhookStatus{
|
||||
ID: 123,
|
||||
URL: "https://example.com/webhook",
|
||||
},
|
||||
},
|
||||
},
|
||||
"test-repo"+webhookSecretSuffix,
|
||||
).Return(errors.New("failed to delete webhook secret"))
|
||||
},
|
||||
config: &provisioning.Repository{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-repo",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: provisioning.RepositorySpec{
|
||||
GitHub: &provisioning.GitHubRepositoryConfig{
|
||||
Branch: "main",
|
||||
},
|
||||
},
|
||||
Status: provisioning.RepositoryStatus{
|
||||
Webhook: &provisioning.WebhookStatus{
|
||||
ID: 123,
|
||||
URL: "https://example.com/webhook",
|
||||
},
|
||||
},
|
||||
},
|
||||
webhookURL: "https://example.com/webhook",
|
||||
expectedError: "delete webhook secret: failed to delete webhook secret",
|
||||
},
|
||||
{
|
||||
name: "no webhook URL - no secrets deletion",
|
||||
setupMock: func(_ *github.MockClient, mockRepo *github.MockGithubRepository, _ *secrets.MockRepositorySecrets) {
|
||||
mockRepo.On("OnDelete", mock.Anything).Return(nil)
|
||||
},
|
||||
config: &provisioning.Repository{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-repo",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: provisioning.RepositorySpec{
|
||||
GitHub: &provisioning.GitHubRepositoryConfig{
|
||||
Branch: "main",
|
||||
},
|
||||
},
|
||||
},
|
||||
webhookURL: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
mockClient := github.NewMockClient(t)
|
||||
mockRepo := github.NewMockGithubRepository(t)
|
||||
mockSecrets := secrets.NewMockRepositorySecrets(t)
|
||||
tt.setupMock(mockClient, mockRepo, mockSecrets)
|
||||
|
||||
repo := &githubWebhookRepository{
|
||||
GithubRepository: mockRepo,
|
||||
gh: mockClient,
|
||||
config: tt.config,
|
||||
secrets: mockSecrets,
|
||||
owner: "grafana",
|
||||
repo: "grafana",
|
||||
webhookURL: tt.webhookURL,
|
||||
}
|
||||
|
||||
err := repo.OnDelete(context.Background())
|
||||
|
||||
if tt.expectedError != "" {
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), tt.expectedError)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
mockClient.AssertExpectations(t)
|
||||
mockRepo.AssertExpectations(t)
|
||||
mockSecrets.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,7 +172,7 @@ func (s *webhookConnector) updateLastEvent(ctx context.Context, repo repository.
|
||||
eventAge := time.Since(lastEvent)
|
||||
|
||||
if repo.Config().Status.Webhook != nil && (eventAge > time.Minute) {
|
||||
patchOp := map[string]interface{}{
|
||||
patchOp := map[string]any{
|
||||
"op": "replace",
|
||||
"path": "/status/webhook/lastEvent",
|
||||
"value": time.Now().UnixMilli(),
|
||||
|
||||
Reference in New Issue
Block a user