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:
Ryan McKinley
2025-08-22 18:38:28 +02:00
committed by GitHub
co-authored by Clarity-89 Roberto Jimenez Sanchez
parent 04f392d37b
commit ce65391067
69 changed files with 694 additions and 4255 deletions
@@ -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(),