mirror of
https://github.com/argoproj/argo-cd.git
synced 2026-02-20 01:28:45 +01:00
feat: add notifications API (#10279)
* add notifications API we allow to list triggers, templates and services Signed-off-by: Pavel <aborilov@gmail.com> * fix: add notification manifest to tests Signed-off-by: Pavel <aborilov@gmail.com> * fix: add sleep to fix integration tests for some reason notification configmap has old data, trying to fix with the sleep Signed-off-by: Pavel <aborilov@gmail.com> * add proposal Signed-off-by: Pavel <aborilov@gmail.com> * more info to proposal Signed-off-by: Pavel <aborilov@gmail.com> * use struct for notifications objects instead of just strings to be able easily extend API in the future return list of trigger/template/service as list of structs Signed-off-by: Pavel <aborilov@gmail.com> Signed-off-by: Pavel <aborilov@gmail.com>
This commit is contained in:
@@ -2139,6 +2139,75 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/notifications/services": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"NotificationService"
|
||||
],
|
||||
"summary": "List returns list of services",
|
||||
"operationId": "NotificationService_ListServices",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/notificationServiceList"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/runtimeError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/notifications/templates": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"NotificationService"
|
||||
],
|
||||
"summary": "List returns list of templates",
|
||||
"operationId": "NotificationService_ListTemplates",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/notificationTemplateList"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/runtimeError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/notifications/triggers": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"NotificationService"
|
||||
],
|
||||
"summary": "List returns list of triggers",
|
||||
"operationId": "NotificationService_ListTriggers",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/notificationTriggerList"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/runtimeError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/projects": {
|
||||
"get": {
|
||||
"tags": [
|
||||
@@ -3979,6 +4048,63 @@
|
||||
"type": "object",
|
||||
"title": "Generic (empty) response for GPG public key CRUD requests"
|
||||
},
|
||||
"notificationService": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"notificationServiceList": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"items": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/notificationService"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"notificationTemplate": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"notificationTemplateList": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"items": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/notificationTemplate"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"notificationTrigger": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"notificationTriggerList": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"items": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/notificationTrigger"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"oidcClaim": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
@@ -7,9 +7,12 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/common"
|
||||
"github.com/argoproj/argo-cd/v2/reposerver/apiclient"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/util/env"
|
||||
"github.com/argoproj/argo-cd/v2/util/errors"
|
||||
service "github.com/argoproj/argo-cd/v2/util/notification/argocd"
|
||||
"github.com/argoproj/argo-cd/v2/util/tls"
|
||||
|
||||
notificationscontroller "github.com/argoproj/argo-cd/v2/notification_controller/controller"
|
||||
|
||||
@@ -105,7 +108,22 @@ func NewCommand() *cobra.Command {
|
||||
return fmt.Errorf("Unknown log format '%s'", logFormat)
|
||||
}
|
||||
|
||||
argocdService, err := service.NewArgoCDService(k8sClient, namespace, argocdRepoServer, argocdRepoServerPlaintext, argocdRepoServerStrictTLS)
|
||||
tlsConfig := apiclient.TLSConfiguration{
|
||||
DisableTLS: argocdRepoServerPlaintext,
|
||||
StrictValidation: argocdRepoServerStrictTLS,
|
||||
}
|
||||
if !tlsConfig.DisableTLS && tlsConfig.StrictValidation {
|
||||
pool, err := tls.LoadX509CertPool(
|
||||
fmt.Sprintf("%s/reposerver/tls/tls.crt", env.StringFromEnv(common.EnvAppConfigPath, common.DefaultAppConfigPath)),
|
||||
fmt.Sprintf("%s/reposerver/tls/ca.crt", env.StringFromEnv(common.EnvAppConfigPath, common.DefaultAppConfigPath)),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tlsConfig.Certificates = pool
|
||||
}
|
||||
repoClientset := apiclient.NewRepoServerClientset(argocdRepoServer, 5, tlsConfig)
|
||||
argocdService, err := service.NewArgoCDService(k8sClient, namespace, repoClientset)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/common"
|
||||
"github.com/argoproj/argo-cd/v2/reposerver/apiclient"
|
||||
"github.com/argoproj/argo-cd/v2/util/env"
|
||||
service "github.com/argoproj/argo-cd/v2/util/notification/argocd"
|
||||
settings "github.com/argoproj/argo-cd/v2/util/notification/settings"
|
||||
"github.com/argoproj/argo-cd/v2/util/tls"
|
||||
|
||||
"github.com/argoproj/notifications-engine/pkg/cmd"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -39,7 +44,22 @@ func NewNotificationsCommand() *cobra.Command {
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to parse k8s config: %v", err)
|
||||
}
|
||||
argocdService, err = service.NewArgoCDService(kubernetes.NewForConfigOrDie(k8sCfg), ns, argocdRepoServer, argocdRepoServerPlaintext, argocdRepoServerStrictTLS)
|
||||
tlsConfig := apiclient.TLSConfiguration{
|
||||
DisableTLS: argocdRepoServerPlaintext,
|
||||
StrictValidation: argocdRepoServerStrictTLS,
|
||||
}
|
||||
if !tlsConfig.DisableTLS && tlsConfig.StrictValidation {
|
||||
pool, err := tls.LoadX509CertPool(
|
||||
fmt.Sprintf("%s/reposerver/tls/tls.crt", env.StringFromEnv(common.EnvAppConfigPath, common.DefaultAppConfigPath)),
|
||||
fmt.Sprintf("%s/reposerver/tls/ca.crt", env.StringFromEnv(common.EnvAppConfigPath, common.DefaultAppConfigPath)),
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to load tls certs: %v", err)
|
||||
}
|
||||
tlsConfig.Certificates = pool
|
||||
}
|
||||
repoClientset := apiclient.NewRepoServerClientset(argocdRepoServer, 5, tlsConfig)
|
||||
argocdService, err = service.NewArgoCDService(kubernetes.NewForConfigOrDie(k8sCfg), ns, repoClientset)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to initialize Argo CD service: %v", err)
|
||||
}
|
||||
|
||||
@@ -21,9 +21,11 @@ const (
|
||||
|
||||
// Kubernetes ConfigMap and Secret resource names which hold Argo CD settings
|
||||
const (
|
||||
ArgoCDConfigMapName = "argocd-cm"
|
||||
ArgoCDSecretName = "argocd-secret"
|
||||
ArgoCDRBACConfigMapName = "argocd-rbac-cm"
|
||||
ArgoCDConfigMapName = "argocd-cm"
|
||||
ArgoCDSecretName = "argocd-secret"
|
||||
ArgoCDNotificationsConfigMapName = "argocd-notifications-cm"
|
||||
ArgoCDNotificationsSecretName = "argocd-notifications-secret"
|
||||
ArgoCDRBACConfigMapName = "argocd-rbac-cm"
|
||||
// Contains SSH known hosts data for connecting repositories. Will get mounted as volume to pods
|
||||
ArgoCDKnownHostsConfigMapName = "argocd-ssh-known-hosts-cm"
|
||||
// Contains TLS certificate data for connecting repositories. Will get mounted as volume to pods
|
||||
|
||||
99
docs/proposals/notifications-API.md
Normal file
99
docs/proposals/notifications-API.md
Normal file
@@ -0,0 +1,99 @@
|
||||
---
|
||||
title: Subscribe to a notification from the Application Details page
|
||||
authors:
|
||||
- "@aborilov"
|
||||
sponsors:
|
||||
- TBD
|
||||
reviewers:
|
||||
- "@alexmt"
|
||||
- TBD
|
||||
approvers:
|
||||
- "@alexmt"
|
||||
- TBD
|
||||
|
||||
creation-date: 2022-08-16
|
||||
last-updated: 2022-08-16
|
||||
---
|
||||
|
||||
# Subscribe to a notification from the Application Details page
|
||||
|
||||
Provide the ability to subscribe to a notification from the Application Details page
|
||||
|
||||
## Summary
|
||||
|
||||
Allow users to subscribe application with a notification from the Application Details page
|
||||
using provided instruments for selecting available triggers and services.
|
||||
|
||||
## Motivation
|
||||
|
||||
It is already possible to subscribe to notifications by modifying annotations however this is a pretty
|
||||
poor user experience. Users have to understand annotation structure and have to find available services and triggers in configmap.
|
||||
|
||||
### Goals
|
||||
|
||||
Be able to subscribe to a notification from the Application Details page without forcing users to read the notification configmap.
|
||||
|
||||
### Non-Goals
|
||||
|
||||
We provide only ability to select existing services and triggers, we don't provide instruments to add/edit/delete notification services and triggers.
|
||||
|
||||
## Proposal
|
||||
|
||||
Two changes are required:
|
||||
|
||||
* Implement notifications API that would expose a list of configured triggers and services
|
||||
* Implement UI that leverages notifications API and helps users to create a correct annotation.
|
||||
|
||||
### Use cases
|
||||
|
||||
Add a list of detailed use cases this enhancement intends to take care of.
|
||||
|
||||
#### Use case 1:
|
||||
As a user, I would like to be able to subscribe application to a notification from the Application Details Page
|
||||
without reading knowing of annotation format and reading notification configmap.
|
||||
|
||||
### Implementation Details/Notes/Constraints [optional]
|
||||
|
||||
Three read-only API endpoints will be added to provide a list of notification services, triggers, and templates.
|
||||
|
||||
```
|
||||
message Triggers { repeated string triggers = 1; }
|
||||
message TriggersListRequest {}
|
||||
message Services { repeated string services = 1; }
|
||||
message ServicesListRequest {}
|
||||
message Templates { repeated string templates = 1; }
|
||||
message TemplatesListRequest {}
|
||||
service NotificationService {
|
||||
rpc ListTriggers(TriggersListRequest) returns (Triggers) {
|
||||
option (google.api.http).get = "/api/v1/notifications/triggers";
|
||||
}
|
||||
rpc ListServices(ServicesListRequest) returns (Services) {
|
||||
option (google.api.http).get = "/api/v1/notifications/services";
|
||||
}
|
||||
rpc ListTemplates(TemplatesListRequest) returns (Templates) {
|
||||
option (google.api.http).get = "/api/v1/notifications/templates";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Detailed examples
|
||||
|
||||
### Security Considerations
|
||||
|
||||
New API endpoints are available only for authenticated users. API endpoints response does not contain any sensitive data.
|
||||
|
||||
### Risks and Mitigations
|
||||
|
||||
TBD
|
||||
|
||||
### Upgrade / Downgrade Strategy
|
||||
|
||||
By default, we don't have a notification configmap in the system; in that case, API should return an empty list instead of erroring.
|
||||
|
||||
## Drawbacks
|
||||
|
||||
|
||||
## Alternatives
|
||||
|
||||
Continue to do that manually.
|
||||
|
||||
@@ -11,6 +11,7 @@ set -o pipefail
|
||||
|
||||
PROJECT_ROOT=$(cd $(dirname ${BASH_SOURCE})/..; pwd)
|
||||
PATH="${PROJECT_ROOT}/dist:${PATH}"
|
||||
GOPATH=$(go env GOPATH)
|
||||
|
||||
# output tool versions
|
||||
protoc --version
|
||||
@@ -121,7 +122,7 @@ clean_swagger() {
|
||||
}
|
||||
|
||||
echo "If additional types are added, the number of expected collisions may need to be increased"
|
||||
EXPECTED_COLLISION_COUNT=62
|
||||
EXPECTED_COLLISION_COUNT=64
|
||||
collect_swagger server ${EXPECTED_COLLISION_COUNT}
|
||||
clean_swagger server
|
||||
clean_swagger reposerver
|
||||
|
||||
@@ -36,6 +36,7 @@ import (
|
||||
certificatepkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/certificate"
|
||||
clusterpkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/cluster"
|
||||
gpgkeypkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/gpgkey"
|
||||
notificationpkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/notification"
|
||||
projectpkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/project"
|
||||
repocredspkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/repocreds"
|
||||
repositorypkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/repository"
|
||||
@@ -87,6 +88,8 @@ type Client interface {
|
||||
NewGPGKeyClientOrDie() (io.Closer, gpgkeypkg.GPGKeyServiceClient)
|
||||
NewApplicationClient() (io.Closer, applicationpkg.ApplicationServiceClient, error)
|
||||
NewApplicationClientOrDie() (io.Closer, applicationpkg.ApplicationServiceClient)
|
||||
NewNotificationClient() (io.Closer, notificationpkg.NotificationServiceClient, error)
|
||||
NewNotificationClientOrDie() (io.Closer, notificationpkg.NotificationServiceClient)
|
||||
NewSessionClient() (io.Closer, sessionpkg.SessionServiceClient, error)
|
||||
NewSessionClientOrDie() (io.Closer, sessionpkg.SessionServiceClient)
|
||||
NewSettingsClient() (io.Closer, settingspkg.SettingsServiceClient, error)
|
||||
@@ -669,11 +672,28 @@ func (c *client) NewApplicationClient() (io.Closer, applicationpkg.ApplicationSe
|
||||
}
|
||||
|
||||
func (c *client) NewApplicationClientOrDie() (io.Closer, applicationpkg.ApplicationServiceClient) {
|
||||
conn, repoIf, err := c.NewApplicationClient()
|
||||
conn, appIf, err := c.NewApplicationClient()
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to establish connection to %s: %v", c.ServerAddr, err)
|
||||
}
|
||||
return conn, repoIf
|
||||
return conn, appIf
|
||||
}
|
||||
|
||||
func (c *client) NewNotificationClient() (io.Closer, notificationpkg.NotificationServiceClient, error) {
|
||||
conn, closer, err := c.newConn()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
notifIf := notificationpkg.NewNotificationServiceClient(conn)
|
||||
return closer, notifIf, nil
|
||||
}
|
||||
|
||||
func (c *client) NewNotificationClientOrDie() (io.Closer, notificationpkg.NotificationServiceClient) {
|
||||
conn, notifIf, err := c.NewNotificationClient()
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to establish connection to %s: %v", c.ServerAddr, err)
|
||||
}
|
||||
return conn, notifIf
|
||||
}
|
||||
|
||||
func (c *client) NewSessionClient() (io.Closer, sessionpkg.SessionServiceClient, error) {
|
||||
|
||||
1861
pkg/apiclient/notification/notification.pb.go
Normal file
1861
pkg/apiclient/notification/notification.pb.go
Normal file
File diff suppressed because it is too large
Load Diff
283
pkg/apiclient/notification/notification.pb.gw.go
Normal file
283
pkg/apiclient/notification/notification.pb.gw.go
Normal file
@@ -0,0 +1,283 @@
|
||||
// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT.
|
||||
// source: server/notification/notification.proto
|
||||
|
||||
/*
|
||||
Package notification is a reverse proxy.
|
||||
|
||||
It translates gRPC into RESTful JSON APIs.
|
||||
*/
|
||||
package notification
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/golang/protobuf/descriptor"
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/grpc-ecosystem/grpc-gateway/runtime"
|
||||
"github.com/grpc-ecosystem/grpc-gateway/utilities"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/grpclog"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// Suppress "imported and not used" errors
|
||||
var _ codes.Code
|
||||
var _ io.Reader
|
||||
var _ status.Status
|
||||
var _ = runtime.String
|
||||
var _ = utilities.NewDoubleArray
|
||||
var _ = descriptor.ForMessage
|
||||
var _ = metadata.Join
|
||||
|
||||
func request_NotificationService_ListTriggers_0(ctx context.Context, marshaler runtime.Marshaler, client NotificationServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq TriggersListRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
msg, err := client.ListTriggers(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
func local_request_NotificationService_ListTriggers_0(ctx context.Context, marshaler runtime.Marshaler, server NotificationServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq TriggersListRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
msg, err := server.ListTriggers(ctx, &protoReq)
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
func request_NotificationService_ListServices_0(ctx context.Context, marshaler runtime.Marshaler, client NotificationServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq ServicesListRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
msg, err := client.ListServices(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
func local_request_NotificationService_ListServices_0(ctx context.Context, marshaler runtime.Marshaler, server NotificationServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq ServicesListRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
msg, err := server.ListServices(ctx, &protoReq)
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
func request_NotificationService_ListTemplates_0(ctx context.Context, marshaler runtime.Marshaler, client NotificationServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq TemplatesListRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
msg, err := client.ListTemplates(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
func local_request_NotificationService_ListTemplates_0(ctx context.Context, marshaler runtime.Marshaler, server NotificationServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq TemplatesListRequest
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
msg, err := server.ListTemplates(ctx, &protoReq)
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
// RegisterNotificationServiceHandlerServer registers the http handlers for service NotificationService to "mux".
|
||||
// UnaryRPC :call NotificationServiceServer directly.
|
||||
// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
|
||||
// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterNotificationServiceHandlerFromEndpoint instead.
|
||||
func RegisterNotificationServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux, server NotificationServiceServer) error {
|
||||
|
||||
mux.Handle("GET", pattern_NotificationService_ListTriggers_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
var stream runtime.ServerTransportStream
|
||||
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := local_request_NotificationService_ListTriggers_0(rctx, inboundMarshaler, server, req, pathParams)
|
||||
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
|
||||
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_NotificationService_ListTriggers_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
mux.Handle("GET", pattern_NotificationService_ListServices_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
var stream runtime.ServerTransportStream
|
||||
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := local_request_NotificationService_ListServices_0(rctx, inboundMarshaler, server, req, pathParams)
|
||||
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
|
||||
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_NotificationService_ListServices_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
mux.Handle("GET", pattern_NotificationService_ListTemplates_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
var stream runtime.ServerTransportStream
|
||||
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := local_request_NotificationService_ListTemplates_0(rctx, inboundMarshaler, server, req, pathParams)
|
||||
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
|
||||
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_NotificationService_ListTemplates_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RegisterNotificationServiceHandlerFromEndpoint is same as RegisterNotificationServiceHandler but
|
||||
// automatically dials to "endpoint" and closes the connection when "ctx" gets done.
|
||||
func RegisterNotificationServiceHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) {
|
||||
conn, err := grpc.Dial(endpoint, opts...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
if cerr := conn.Close(); cerr != nil {
|
||||
grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
|
||||
}
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
if cerr := conn.Close(); cerr != nil {
|
||||
grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
|
||||
}
|
||||
}()
|
||||
}()
|
||||
|
||||
return RegisterNotificationServiceHandler(ctx, mux, conn)
|
||||
}
|
||||
|
||||
// RegisterNotificationServiceHandler registers the http handlers for service NotificationService to "mux".
|
||||
// The handlers forward requests to the grpc endpoint over "conn".
|
||||
func RegisterNotificationServiceHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error {
|
||||
return RegisterNotificationServiceHandlerClient(ctx, mux, NewNotificationServiceClient(conn))
|
||||
}
|
||||
|
||||
// RegisterNotificationServiceHandlerClient registers the http handlers for service NotificationService
|
||||
// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "NotificationServiceClient".
|
||||
// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "NotificationServiceClient"
|
||||
// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in
|
||||
// "NotificationServiceClient" to call the correct interceptors.
|
||||
func RegisterNotificationServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux, client NotificationServiceClient) error {
|
||||
|
||||
mux.Handle("GET", pattern_NotificationService_ListTriggers_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
rctx, err := runtime.AnnotateContext(ctx, mux, req)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := request_NotificationService_ListTriggers_0(rctx, inboundMarshaler, client, req, pathParams)
|
||||
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_NotificationService_ListTriggers_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
mux.Handle("GET", pattern_NotificationService_ListServices_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
rctx, err := runtime.AnnotateContext(ctx, mux, req)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := request_NotificationService_ListServices_0(rctx, inboundMarshaler, client, req, pathParams)
|
||||
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_NotificationService_ListServices_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
mux.Handle("GET", pattern_NotificationService_ListTemplates_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
rctx, err := runtime.AnnotateContext(ctx, mux, req)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := request_NotificationService_ListTemplates_0(rctx, inboundMarshaler, client, req, pathParams)
|
||||
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_NotificationService_ListTemplates_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
pattern_NotificationService_ListTriggers_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"api", "v1", "notifications", "triggers"}, "", runtime.AssumeColonVerbOpt(true)))
|
||||
|
||||
pattern_NotificationService_ListServices_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"api", "v1", "notifications", "services"}, "", runtime.AssumeColonVerbOpt(true)))
|
||||
|
||||
pattern_NotificationService_ListTemplates_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"api", "v1", "notifications", "templates"}, "", runtime.AssumeColonVerbOpt(true)))
|
||||
)
|
||||
|
||||
var (
|
||||
forward_NotificationService_ListTriggers_0 = runtime.ForwardResponseMessage
|
||||
|
||||
forward_NotificationService_ListServices_0 = runtime.ForwardResponseMessage
|
||||
|
||||
forward_NotificationService_ListTemplates_0 = runtime.ForwardResponseMessage
|
||||
)
|
||||
68
server/notification/notification.go
Normal file
68
server/notification/notification.go
Normal file
@@ -0,0 +1,68 @@
|
||||
package notification
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apiclient/notification"
|
||||
"github.com/argoproj/notifications-engine/pkg/api"
|
||||
apierr "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/utils/pointer"
|
||||
)
|
||||
|
||||
// Server provides an Application service
|
||||
type Server struct {
|
||||
apiFactory api.Factory
|
||||
}
|
||||
|
||||
// NewServer returns a new instance of the Application service
|
||||
func NewServer(apiFactory api.Factory) notification.NotificationServiceServer {
|
||||
s := &Server{apiFactory: apiFactory}
|
||||
return s
|
||||
}
|
||||
|
||||
// List returns list of notification triggers
|
||||
func (s *Server) ListTriggers(ctx context.Context, q *notification.TriggersListRequest) (*notification.TriggerList, error) {
|
||||
api, err := s.apiFactory.GetAPI()
|
||||
if err != nil {
|
||||
if apierr.IsNotFound(err) {
|
||||
return ¬ification.TriggerList{}, nil
|
||||
}
|
||||
}
|
||||
triggers := []*notification.Trigger{}
|
||||
for trigger := range api.GetConfig().Triggers {
|
||||
triggers = append(triggers, ¬ification.Trigger{Name: pointer.String(trigger)})
|
||||
}
|
||||
return ¬ification.TriggerList{Items: triggers}, nil
|
||||
}
|
||||
|
||||
// List returns list of notification services
|
||||
func (s *Server) ListServices(ctx context.Context, q *notification.ServicesListRequest) (*notification.ServiceList, error) {
|
||||
api, err := s.apiFactory.GetAPI()
|
||||
if err != nil {
|
||||
if apierr.IsNotFound(err) {
|
||||
return ¬ification.ServiceList{}, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
services := []*notification.Service{}
|
||||
for svc := range api.GetConfig().Services {
|
||||
services = append(services, ¬ification.Service{Name: pointer.String(svc)})
|
||||
}
|
||||
return ¬ification.ServiceList{Items: services}, nil
|
||||
}
|
||||
|
||||
// List returns list of notification templates
|
||||
func (s *Server) ListTemplates(ctx context.Context, q *notification.TemplatesListRequest) (*notification.TemplateList, error) {
|
||||
api, err := s.apiFactory.GetAPI()
|
||||
if err != nil {
|
||||
if apierr.IsNotFound(err) {
|
||||
return ¬ification.TemplateList{}, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
templates := []*notification.Template{}
|
||||
for tmpl := range api.GetConfig().Templates {
|
||||
templates = append(templates, ¬ification.Template{Name: pointer.String(tmpl)})
|
||||
}
|
||||
return ¬ification.TemplateList{Items: templates}, nil
|
||||
}
|
||||
58
server/notification/notification.proto
Normal file
58
server/notification/notification.proto
Normal file
@@ -0,0 +1,58 @@
|
||||
syntax = "proto2";
|
||||
option go_package = "github.com/argoproj/argo-cd/v2/pkg/apiclient/notification";
|
||||
|
||||
// Notification Service
|
||||
//
|
||||
// Notification Service API performs query actions against notification resources
|
||||
package notification;
|
||||
|
||||
import "google/api/annotations.proto";
|
||||
|
||||
message Trigger {
|
||||
required string name = 1;
|
||||
}
|
||||
|
||||
message TriggerList {
|
||||
repeated Trigger items = 1;
|
||||
}
|
||||
|
||||
message TriggersListRequest {}
|
||||
|
||||
message Service {
|
||||
required string name = 1;
|
||||
}
|
||||
|
||||
message ServiceList {
|
||||
repeated Service items = 1;
|
||||
}
|
||||
|
||||
message ServicesListRequest {}
|
||||
|
||||
message Template {
|
||||
required string name = 1;
|
||||
}
|
||||
|
||||
message TemplateList {
|
||||
repeated Template items = 1;
|
||||
}
|
||||
|
||||
message TemplatesListRequest {}
|
||||
|
||||
// NotificationService
|
||||
service NotificationService {
|
||||
|
||||
// List returns list of triggers
|
||||
rpc ListTriggers(TriggersListRequest) returns (TriggerList) {
|
||||
option (google.api.http).get = "/api/v1/notifications/triggers";
|
||||
}
|
||||
|
||||
// List returns list of services
|
||||
rpc ListServices(ServicesListRequest) returns (ServiceList) {
|
||||
option (google.api.http).get = "/api/v1/notifications/services";
|
||||
}
|
||||
|
||||
// List returns list of templates
|
||||
rpc ListTemplates(TemplatesListRequest) returns (TemplateList) {
|
||||
option (google.api.http).get = "/api/v1/notifications/templates";
|
||||
}
|
||||
}
|
||||
99
server/notification/notification_test.go
Normal file
99
server/notification/notification_test.go
Normal file
@@ -0,0 +1,99 @@
|
||||
package notification
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apiclient/notification"
|
||||
"github.com/argoproj/argo-cd/v2/reposerver/apiclient/mocks"
|
||||
service "github.com/argoproj/argo-cd/v2/util/notification/argocd"
|
||||
"github.com/argoproj/argo-cd/v2/util/notification/k8s"
|
||||
"github.com/argoproj/argo-cd/v2/util/notification/settings"
|
||||
"github.com/argoproj/notifications-engine/pkg/api"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/utils/pointer"
|
||||
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
k8scache "k8s.io/client-go/tools/cache"
|
||||
"k8s.io/kubectl/pkg/scheme"
|
||||
)
|
||||
|
||||
const testNamespace = "default"
|
||||
|
||||
func TestNotificationServer(t *testing.T) {
|
||||
|
||||
// catalogPath := path.Join(paths[1], "config", "notifications-catalog")
|
||||
b, err := os.ReadFile("../../notifications_catalog/install.yaml")
|
||||
require.NoError(t, err)
|
||||
|
||||
cm := &corev1.ConfigMap{}
|
||||
_, _, err = scheme.Codecs.UniversalDeserializer().Decode(b, nil, cm)
|
||||
require.NoError(t, err)
|
||||
cm.Namespace = testNamespace
|
||||
|
||||
kubeclientset := fake.NewSimpleClientset(&corev1.ConfigMap{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Namespace: testNamespace,
|
||||
Name: "argocd-notifications-cm",
|
||||
},
|
||||
Data: map[string]string{
|
||||
"service.webhook.test": "url: https://test.com",
|
||||
"template.app-created": "email:\n subject: Application {{.app.metadata.name}} has been created.\nmessage: Application {{.app.metadata.name}} has been created.\nteams:\n title: Application {{.app.metadata.name}} has been created.\n",
|
||||
"trigger.on-created": "- description: Application is created.\n oncePer: app.metadata.name\n send:\n - app-created\n when: \"true\"\n",
|
||||
},
|
||||
},
|
||||
&corev1.Secret{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "argocd-notifications-secret",
|
||||
Namespace: testNamespace,
|
||||
},
|
||||
Data: map[string][]byte{},
|
||||
})
|
||||
|
||||
ctx := context.Background()
|
||||
secretInformer := k8s.NewSecretInformer(kubeclientset, testNamespace, "argocd-notifications-secret")
|
||||
configMapInformer := k8s.NewConfigMapInformer(kubeclientset, testNamespace, "argocd-notifications-cm")
|
||||
go secretInformer.Run(ctx.Done())
|
||||
if !k8scache.WaitForCacheSync(ctx.Done(), secretInformer.HasSynced) {
|
||||
panic("Timed out waiting for caches to sync")
|
||||
}
|
||||
go configMapInformer.Run(ctx.Done())
|
||||
if !k8scache.WaitForCacheSync(ctx.Done(), configMapInformer.HasSynced) {
|
||||
panic("Timed out waiting for caches to sync")
|
||||
}
|
||||
mockRepoClient := &mocks.Clientset{RepoServerServiceClient: &mocks.RepoServerServiceClient{}}
|
||||
|
||||
argocdService, err := service.NewArgoCDService(kubeclientset, testNamespace, mockRepoClient)
|
||||
require.NoError(t, err)
|
||||
defer argocdService.Close()
|
||||
apiFactory := api.NewFactory(settings.GetFactorySettings(argocdService, "argocd-notifications-secret", "argocd-notifications-cm"), testNamespace, secretInformer, configMapInformer)
|
||||
|
||||
t.Run("TestListServices", func(t *testing.T) {
|
||||
server := NewServer(apiFactory)
|
||||
services, err := server.ListServices(ctx, ¬ification.ServicesListRequest{})
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, services.Items, 1)
|
||||
assert.Equal(t, services.Items[0].Name, pointer.String("test"))
|
||||
assert.NotEmpty(t, services.Items[0])
|
||||
})
|
||||
t.Run("TestListTriggers", func(t *testing.T) {
|
||||
server := NewServer(apiFactory)
|
||||
triggers, err := server.ListTriggers(ctx, ¬ification.TriggersListRequest{})
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, triggers.Items, 1)
|
||||
assert.Equal(t, triggers.Items[0].Name, pointer.String("on-created"))
|
||||
assert.NotEmpty(t, triggers.Items[0])
|
||||
})
|
||||
t.Run("TestListTemplates", func(t *testing.T) {
|
||||
server := NewServer(apiFactory)
|
||||
templates, err := server.ListTemplates(ctx, ¬ification.TemplatesListRequest{})
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, templates.Items, 1)
|
||||
assert.Equal(t, templates.Items[0].Name, pointer.String("app-created"))
|
||||
assert.NotEmpty(t, templates.Items[0])
|
||||
})
|
||||
}
|
||||
@@ -26,6 +26,7 @@ import (
|
||||
|
||||
netCtx "context"
|
||||
|
||||
"github.com/argoproj/notifications-engine/pkg/api"
|
||||
"github.com/argoproj/pkg/sync"
|
||||
"github.com/go-redis/redis/v8"
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
@@ -62,6 +63,7 @@ import (
|
||||
certificatepkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/certificate"
|
||||
clusterpkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/cluster"
|
||||
gpgkeypkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/gpgkey"
|
||||
notificationpkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/notification"
|
||||
projectpkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/project"
|
||||
repocredspkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/repocreds"
|
||||
repositorypkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/repository"
|
||||
@@ -83,6 +85,7 @@ import (
|
||||
"github.com/argoproj/argo-cd/v2/server/gpgkey"
|
||||
"github.com/argoproj/argo-cd/v2/server/logout"
|
||||
"github.com/argoproj/argo-cd/v2/server/metrics"
|
||||
"github.com/argoproj/argo-cd/v2/server/notification"
|
||||
"github.com/argoproj/argo-cd/v2/server/project"
|
||||
"github.com/argoproj/argo-cd/v2/server/rbacpolicy"
|
||||
"github.com/argoproj/argo-cd/v2/server/repocreds"
|
||||
@@ -105,6 +108,9 @@ import (
|
||||
"github.com/argoproj/argo-cd/v2/util/io/files"
|
||||
jwtutil "github.com/argoproj/argo-cd/v2/util/jwt"
|
||||
kubeutil "github.com/argoproj/argo-cd/v2/util/kube"
|
||||
service "github.com/argoproj/argo-cd/v2/util/notification/argocd"
|
||||
"github.com/argoproj/argo-cd/v2/util/notification/k8s"
|
||||
settings_notif "github.com/argoproj/argo-cd/v2/util/notification/settings"
|
||||
"github.com/argoproj/argo-cd/v2/util/oidc"
|
||||
"github.com/argoproj/argo-cd/v2/util/rbac"
|
||||
util_session "github.com/argoproj/argo-cd/v2/util/session"
|
||||
@@ -171,12 +177,15 @@ type ArgoCDServer struct {
|
||||
db db.ArgoDB
|
||||
|
||||
// stopCh is the channel which when closed, will shutdown the Argo CD server
|
||||
stopCh chan struct{}
|
||||
userStateStorage util_session.UserStateStorage
|
||||
indexDataInit gosync.Once
|
||||
indexData []byte
|
||||
indexDataErr error
|
||||
staticAssets http.FileSystem
|
||||
stopCh chan struct{}
|
||||
userStateStorage util_session.UserStateStorage
|
||||
indexDataInit gosync.Once
|
||||
indexData []byte
|
||||
indexDataErr error
|
||||
staticAssets http.FileSystem
|
||||
apiFactory api.Factory
|
||||
secretInformer cache.SharedIndexInformer
|
||||
configMapInformer cache.SharedIndexInformer
|
||||
}
|
||||
|
||||
type ArgoCDServerOpts struct {
|
||||
@@ -261,21 +270,32 @@ func NewServer(ctx context.Context, opts ArgoCDServerOpts) *ArgoCDServer {
|
||||
staticFS = io.NewComposableFS(staticFS, os.DirFS(opts.StaticAssetsDir))
|
||||
}
|
||||
|
||||
argocdService, err := service.NewArgoCDService(opts.KubeClientset, opts.Namespace, opts.RepoClientset)
|
||||
errors.CheckError(err)
|
||||
|
||||
secretInformer := k8s.NewSecretInformer(opts.KubeClientset, opts.Namespace, "argocd-notifications-secret")
|
||||
configMapInformer := k8s.NewConfigMapInformer(opts.KubeClientset, opts.Namespace, "argocd-notifications-cm")
|
||||
|
||||
apiFactory := api.NewFactory(settings_notif.GetFactorySettings(argocdService, "argocd-notifications-secret", "argocd-notifications-cm"), opts.Namespace, secretInformer, configMapInformer)
|
||||
|
||||
return &ArgoCDServer{
|
||||
ArgoCDServerOpts: opts,
|
||||
log: log.NewEntry(log.StandardLogger()),
|
||||
settings: settings,
|
||||
sessionMgr: sessionMgr,
|
||||
settingsMgr: settingsMgr,
|
||||
enf: enf,
|
||||
projInformer: projInformer,
|
||||
projLister: projLister,
|
||||
appInformer: appInformer,
|
||||
appLister: appLister,
|
||||
policyEnforcer: policyEnf,
|
||||
userStateStorage: userStateStorage,
|
||||
staticAssets: http.FS(staticFS),
|
||||
db: db.NewDB(opts.Namespace, settingsMgr, opts.KubeClientset),
|
||||
ArgoCDServerOpts: opts,
|
||||
log: log.NewEntry(log.StandardLogger()),
|
||||
settings: settings,
|
||||
sessionMgr: sessionMgr,
|
||||
settingsMgr: settingsMgr,
|
||||
enf: enf,
|
||||
projInformer: projInformer,
|
||||
projLister: projLister,
|
||||
appInformer: appInformer,
|
||||
appLister: appLister,
|
||||
policyEnforcer: policyEnf,
|
||||
userStateStorage: userStateStorage,
|
||||
staticAssets: http.FS(staticFS),
|
||||
db: db.NewDB(opts.Namespace, settingsMgr, opts.KubeClientset),
|
||||
apiFactory: apiFactory,
|
||||
secretInformer: secretInformer,
|
||||
configMapInformer: configMapInformer,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -379,6 +399,8 @@ func (a *ArgoCDServer) Listen() (*Listeners, error) {
|
||||
func (a *ArgoCDServer) Init(ctx context.Context) {
|
||||
go a.projInformer.Run(ctx.Done())
|
||||
go a.appInformer.Run(ctx.Done())
|
||||
go a.configMapInformer.Run(ctx.Done())
|
||||
go a.secretInformer.Run(ctx.Done())
|
||||
}
|
||||
|
||||
// Run runs the API Server
|
||||
@@ -700,6 +722,8 @@ func (a *ArgoCDServer) newGRPCServer() (*grpc.Server, application.AppResourceTre
|
||||
projectService := project.NewServer(a.Namespace, a.KubeClientset, a.AppClientset, a.enf, projectLock, a.sessionMgr, a.policyEnforcer, a.projInformer, a.settingsMgr, a.db)
|
||||
settingsService := settings.NewServer(a.settingsMgr, a, a.DisableAuth)
|
||||
accountService := account.NewServer(a.sessionMgr, a.settingsMgr, a.enf)
|
||||
|
||||
notificationService := notification.NewServer(a.apiFactory)
|
||||
certificateService := certificate.NewServer(a.RepoClientset, a.db, a.enf)
|
||||
gpgkeyService := gpgkey.NewServer(a.RepoClientset, a.db, a.enf)
|
||||
versionpkg.RegisterVersionServiceServer(grpcS, version.NewServer(a, func() (bool, error) {
|
||||
@@ -714,6 +738,7 @@ func (a *ArgoCDServer) newGRPCServer() (*grpc.Server, application.AppResourceTre
|
||||
}))
|
||||
clusterpkg.RegisterClusterServiceServer(grpcS, clusterService)
|
||||
applicationpkg.RegisterApplicationServiceServer(grpcS, applicationService)
|
||||
notificationpkg.RegisterNotificationServiceServer(grpcS, notificationService)
|
||||
repositorypkg.RegisterRepositoryServiceServer(grpcS, repoService)
|
||||
repocredspkg.RegisterRepoCredsServiceServer(grpcS, repoCredsService)
|
||||
sessionpkg.RegisterSessionServiceServer(grpcS, sessionService)
|
||||
@@ -859,6 +884,7 @@ func (a *ArgoCDServer) newHTTPServer(ctx context.Context, port int, grpcWebHandl
|
||||
mustRegisterGWHandler(versionpkg.RegisterVersionServiceHandler, ctx, gwmux, conn)
|
||||
mustRegisterGWHandler(clusterpkg.RegisterClusterServiceHandler, ctx, gwmux, conn)
|
||||
mustRegisterGWHandler(applicationpkg.RegisterApplicationServiceHandler, ctx, gwmux, conn)
|
||||
mustRegisterGWHandler(notificationpkg.RegisterNotificationServiceHandler, ctx, gwmux, conn)
|
||||
mustRegisterGWHandler(repositorypkg.RegisterRepositoryServiceHandler, ctx, gwmux, conn)
|
||||
mustRegisterGWHandler(repocredspkg.RegisterRepoCredsServiceHandler, ctx, gwmux, conn)
|
||||
mustRegisterGWHandler(sessionpkg.RegisterSessionServiceHandler, ctx, gwmux, conn)
|
||||
|
||||
@@ -25,6 +25,7 @@ import (
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apiclient/session"
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
apps "github.com/argoproj/argo-cd/v2/pkg/client/clientset/versioned/fake"
|
||||
"github.com/argoproj/argo-cd/v2/reposerver/apiclient/mocks"
|
||||
servercache "github.com/argoproj/argo-cd/v2/server/cache"
|
||||
"github.com/argoproj/argo-cd/v2/server/rbacpolicy"
|
||||
"github.com/argoproj/argo-cd/v2/test"
|
||||
@@ -42,6 +43,8 @@ func fakeServer() (*ArgoCDServer, func()) {
|
||||
appClientSet := apps.NewSimpleClientset()
|
||||
redis, closer := test.NewInMemoryRedis()
|
||||
port, err := test.GetFreePort()
|
||||
mockRepoClient := &mocks.Clientset{RepoServerServiceClient: &mocks.RepoServerServiceClient{}}
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -64,7 +67,8 @@ func fakeServer() (*ArgoCDServer, func()) {
|
||||
1*time.Minute,
|
||||
1*time.Minute,
|
||||
),
|
||||
RedisClient: redis,
|
||||
RedisClient: redis,
|
||||
RepoClientset: mockRepoClient,
|
||||
}
|
||||
srv := NewServer(context.Background(), argoCDOpts)
|
||||
return srv, closer
|
||||
@@ -98,9 +102,10 @@ func TestEnforceProjectToken(t *testing.T) {
|
||||
cm := test.NewFakeConfigMap()
|
||||
secret := test.NewFakeSecret()
|
||||
kubeclientset := fake.NewSimpleClientset(cm, secret)
|
||||
mockRepoClient := &mocks.Clientset{RepoServerServiceClient: &mocks.RepoServerServiceClient{}}
|
||||
|
||||
t.Run("TestEnforceProjectTokenSuccessful", func(t *testing.T) {
|
||||
s := NewServer(context.Background(), ArgoCDServerOpts{Namespace: test.FakeArgoCDNamespace, KubeClientset: kubeclientset, AppClientset: apps.NewSimpleClientset(&existingProj)})
|
||||
s := NewServer(context.Background(), ArgoCDServerOpts{Namespace: test.FakeArgoCDNamespace, KubeClientset: kubeclientset, AppClientset: apps.NewSimpleClientset(&existingProj), RepoClientset: mockRepoClient})
|
||||
cancel := test.StartInformer(s.projInformer)
|
||||
defer cancel()
|
||||
claims := jwt.MapClaims{"sub": defaultSub, "iat": defaultIssuedAt}
|
||||
@@ -109,21 +114,21 @@ func TestEnforceProjectToken(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("TestEnforceProjectTokenWithDiffCreateAtFailure", func(t *testing.T) {
|
||||
s := NewServer(context.Background(), ArgoCDServerOpts{Namespace: test.FakeArgoCDNamespace, KubeClientset: kubeclientset, AppClientset: apps.NewSimpleClientset(&existingProj)})
|
||||
s := NewServer(context.Background(), ArgoCDServerOpts{Namespace: test.FakeArgoCDNamespace, KubeClientset: kubeclientset, AppClientset: apps.NewSimpleClientset(&existingProj), RepoClientset: mockRepoClient})
|
||||
diffCreateAt := defaultIssuedAt + 1
|
||||
claims := jwt.MapClaims{"sub": defaultSub, "iat": diffCreateAt}
|
||||
assert.False(t, s.enf.Enforce(claims, "applications", "get", defaultTestObject))
|
||||
})
|
||||
|
||||
t.Run("TestEnforceProjectTokenIncorrectSubFormatFailure", func(t *testing.T) {
|
||||
s := NewServer(context.Background(), ArgoCDServerOpts{Namespace: test.FakeArgoCDNamespace, KubeClientset: kubeclientset, AppClientset: apps.NewSimpleClientset(&existingProj)})
|
||||
s := NewServer(context.Background(), ArgoCDServerOpts{Namespace: test.FakeArgoCDNamespace, KubeClientset: kubeclientset, AppClientset: apps.NewSimpleClientset(&existingProj), RepoClientset: mockRepoClient})
|
||||
invalidSub := "proj:test"
|
||||
claims := jwt.MapClaims{"sub": invalidSub, "iat": defaultIssuedAt}
|
||||
assert.False(t, s.enf.Enforce(claims, "applications", "get", defaultTestObject))
|
||||
})
|
||||
|
||||
t.Run("TestEnforceProjectTokenNoTokenFailure", func(t *testing.T) {
|
||||
s := NewServer(context.Background(), ArgoCDServerOpts{Namespace: test.FakeArgoCDNamespace, KubeClientset: kubeclientset, AppClientset: apps.NewSimpleClientset(&existingProj)})
|
||||
s := NewServer(context.Background(), ArgoCDServerOpts{Namespace: test.FakeArgoCDNamespace, KubeClientset: kubeclientset, AppClientset: apps.NewSimpleClientset(&existingProj), RepoClientset: mockRepoClient})
|
||||
nonExistentToken := "fake-token"
|
||||
invalidSub := fmt.Sprintf(subFormat, projectName, nonExistentToken)
|
||||
claims := jwt.MapClaims{"sub": invalidSub, "iat": defaultIssuedAt}
|
||||
@@ -133,7 +138,7 @@ func TestEnforceProjectToken(t *testing.T) {
|
||||
t.Run("TestEnforceProjectTokenNotJWTTokenFailure", func(t *testing.T) {
|
||||
proj := existingProj.DeepCopy()
|
||||
proj.Spec.Roles[0].JWTTokens = nil
|
||||
s := NewServer(context.Background(), ArgoCDServerOpts{Namespace: test.FakeArgoCDNamespace, KubeClientset: kubeclientset, AppClientset: apps.NewSimpleClientset(proj)})
|
||||
s := NewServer(context.Background(), ArgoCDServerOpts{Namespace: test.FakeArgoCDNamespace, KubeClientset: kubeclientset, AppClientset: apps.NewSimpleClientset(proj), RepoClientset: mockRepoClient})
|
||||
claims := jwt.MapClaims{"sub": defaultSub, "iat": defaultIssuedAt}
|
||||
assert.False(t, s.enf.Enforce(claims, "applications", "get", defaultTestObject))
|
||||
})
|
||||
@@ -146,7 +151,7 @@ func TestEnforceProjectToken(t *testing.T) {
|
||||
proj := existingProj.DeepCopy()
|
||||
proj.Spec.Roles[0] = role
|
||||
|
||||
s := NewServer(context.Background(), ArgoCDServerOpts{Namespace: test.FakeArgoCDNamespace, KubeClientset: kubeclientset, AppClientset: apps.NewSimpleClientset(proj)})
|
||||
s := NewServer(context.Background(), ArgoCDServerOpts{Namespace: test.FakeArgoCDNamespace, KubeClientset: kubeclientset, AppClientset: apps.NewSimpleClientset(proj), RepoClientset: mockRepoClient})
|
||||
cancel := test.StartInformer(s.projInformer)
|
||||
defer cancel()
|
||||
claims := jwt.MapClaims{"sub": defaultSub, "iat": defaultIssuedAt}
|
||||
@@ -157,7 +162,7 @@ func TestEnforceProjectToken(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("TestEnforceProjectTokenWithIdSuccessful", func(t *testing.T) {
|
||||
s := NewServer(context.Background(), ArgoCDServerOpts{Namespace: test.FakeArgoCDNamespace, KubeClientset: kubeclientset, AppClientset: apps.NewSimpleClientset(&existingProj)})
|
||||
s := NewServer(context.Background(), ArgoCDServerOpts{Namespace: test.FakeArgoCDNamespace, KubeClientset: kubeclientset, AppClientset: apps.NewSimpleClientset(&existingProj), RepoClientset: mockRepoClient})
|
||||
cancel := test.StartInformer(s.projInformer)
|
||||
defer cancel()
|
||||
claims := jwt.MapClaims{"sub": defaultSub, "jti": defaultId}
|
||||
@@ -166,7 +171,7 @@ func TestEnforceProjectToken(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("TestEnforceProjectTokenWithInvalidIdFailure", func(t *testing.T) {
|
||||
s := NewServer(context.Background(), ArgoCDServerOpts{Namespace: test.FakeArgoCDNamespace, KubeClientset: kubeclientset, AppClientset: apps.NewSimpleClientset(&existingProj)})
|
||||
s := NewServer(context.Background(), ArgoCDServerOpts{Namespace: test.FakeArgoCDNamespace, KubeClientset: kubeclientset, AppClientset: apps.NewSimpleClientset(&existingProj), RepoClientset: mockRepoClient})
|
||||
invalidId := "invalidId"
|
||||
claims := jwt.MapClaims{"sub": defaultSub, "jti": defaultId}
|
||||
res := s.enf.Enforce(claims, "applications", "get", invalidId)
|
||||
@@ -242,10 +247,13 @@ func TestInitializingExistingDefaultProject(t *testing.T) {
|
||||
}
|
||||
appClientSet := apps.NewSimpleClientset(defaultProj)
|
||||
|
||||
mockRepoClient := &mocks.Clientset{RepoServerServiceClient: &mocks.RepoServerServiceClient{}}
|
||||
|
||||
argoCDOpts := ArgoCDServerOpts{
|
||||
Namespace: test.FakeArgoCDNamespace,
|
||||
KubeClientset: kubeclientset,
|
||||
AppClientset: appClientSet,
|
||||
RepoClientset: mockRepoClient,
|
||||
}
|
||||
|
||||
argocd := NewServer(context.Background(), argoCDOpts)
|
||||
@@ -262,11 +270,13 @@ func TestInitializingNotExistingDefaultProject(t *testing.T) {
|
||||
secret := test.NewFakeSecret()
|
||||
kubeclientset := fake.NewSimpleClientset(cm, secret)
|
||||
appClientSet := apps.NewSimpleClientset()
|
||||
mockRepoClient := &mocks.Clientset{RepoServerServiceClient: &mocks.RepoServerServiceClient{}}
|
||||
|
||||
argoCDOpts := ArgoCDServerOpts{
|
||||
Namespace: test.FakeArgoCDNamespace,
|
||||
KubeClientset: kubeclientset,
|
||||
AppClientset: appClientSet,
|
||||
RepoClientset: mockRepoClient,
|
||||
}
|
||||
|
||||
argocd := NewServer(context.Background(), argoCDOpts)
|
||||
@@ -309,8 +319,9 @@ func TestEnforceProjectGroups(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
mockRepoClient := &mocks.Clientset{RepoServerServiceClient: &mocks.RepoServerServiceClient{}}
|
||||
kubeclientset := fake.NewSimpleClientset(test.NewFakeConfigMap(), test.NewFakeSecret())
|
||||
s := NewServer(context.Background(), ArgoCDServerOpts{Namespace: test.FakeArgoCDNamespace, KubeClientset: kubeclientset, AppClientset: apps.NewSimpleClientset(&existingProj)})
|
||||
s := NewServer(context.Background(), ArgoCDServerOpts{Namespace: test.FakeArgoCDNamespace, KubeClientset: kubeclientset, AppClientset: apps.NewSimpleClientset(&existingProj), RepoClientset: mockRepoClient})
|
||||
cancel := test.StartInformer(s.projInformer)
|
||||
defer cancel()
|
||||
claims := jwt.MapClaims{
|
||||
@@ -344,6 +355,7 @@ func TestRevokedToken(t *testing.T) {
|
||||
defaultSub := fmt.Sprintf(subFormat, projectName, roleName)
|
||||
defaultPolicy := fmt.Sprintf(policyTemplate, defaultSub, projectName, defaultObject, defaultEffect)
|
||||
kubeclientset := fake.NewSimpleClientset(test.NewFakeConfigMap(), test.NewFakeSecret())
|
||||
mockRepoClient := &mocks.Clientset{RepoServerServiceClient: &mocks.RepoServerServiceClient{}}
|
||||
|
||||
jwtTokenByRole := make(map[string]v1alpha1.JWTTokens)
|
||||
jwtTokenByRole[roleName] = v1alpha1.JWTTokens{Items: []v1alpha1.JWTToken{{IssuedAt: defaultIssuedAt}}}
|
||||
@@ -371,7 +383,7 @@ func TestRevokedToken(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
s := NewServer(context.Background(), ArgoCDServerOpts{Namespace: test.FakeArgoCDNamespace, KubeClientset: kubeclientset, AppClientset: apps.NewSimpleClientset(&existingProj)})
|
||||
s := NewServer(context.Background(), ArgoCDServerOpts{Namespace: test.FakeArgoCDNamespace, KubeClientset: kubeclientset, AppClientset: apps.NewSimpleClientset(&existingProj), RepoClientset: mockRepoClient})
|
||||
cancel := test.StartInformer(s.projInformer)
|
||||
defer cancel()
|
||||
claims := jwt.MapClaims{"sub": defaultSub, "iat": defaultIssuedAt}
|
||||
@@ -419,10 +431,12 @@ func TestAuthenticate(t *testing.T) {
|
||||
secret := test.NewFakeSecret()
|
||||
kubeclientset := fake.NewSimpleClientset(cm, secret)
|
||||
appClientSet := apps.NewSimpleClientset()
|
||||
mockRepoClient := &mocks.Clientset{RepoServerServiceClient: &mocks.RepoServerServiceClient{}}
|
||||
argoCDOpts := ArgoCDServerOpts{
|
||||
Namespace: test.FakeArgoCDNamespace,
|
||||
KubeClientset: kubeclientset,
|
||||
AppClientset: appClientSet,
|
||||
RepoClientset: mockRepoClient,
|
||||
}
|
||||
argocd := NewServer(context.Background(), argoCDOpts)
|
||||
ctx := context.Background()
|
||||
@@ -535,10 +549,12 @@ connectors:
|
||||
secret := test.NewFakeSecret()
|
||||
kubeclientset := fake.NewSimpleClientset(cm, secret)
|
||||
appClientSet := apps.NewSimpleClientset()
|
||||
mockRepoClient := &mocks.Clientset{RepoServerServiceClient: &mocks.RepoServerServiceClient{}}
|
||||
argoCDOpts := ArgoCDServerOpts{
|
||||
Namespace: test.FakeArgoCDNamespace,
|
||||
KubeClientset: kubeclientset,
|
||||
AppClientset: appClientSet,
|
||||
RepoClientset: mockRepoClient,
|
||||
}
|
||||
if withFakeSSO {
|
||||
argoCDOpts.DexServerAddr = ts.URL
|
||||
@@ -852,6 +868,7 @@ func TestTranslateGrpcCookieHeader(t *testing.T) {
|
||||
Namespace: test.FakeArgoCDNamespace,
|
||||
KubeClientset: fake.NewSimpleClientset(test.NewFakeConfigMap(), test.NewFakeSecret()),
|
||||
AppClientset: apps.NewSimpleClientset(),
|
||||
RepoClientset: &mocks.Clientset{RepoServerServiceClient: &mocks.RepoServerServiceClient{}},
|
||||
}
|
||||
argocd := NewServer(context.Background(), argoCDOpts)
|
||||
|
||||
@@ -891,6 +908,7 @@ func TestInitializeDefaultProject_ProjectDoesNotExist(t *testing.T) {
|
||||
Namespace: test.FakeArgoCDNamespace,
|
||||
KubeClientset: fake.NewSimpleClientset(test.NewFakeConfigMap(), test.NewFakeSecret()),
|
||||
AppClientset: apps.NewSimpleClientset(),
|
||||
RepoClientset: &mocks.Clientset{RepoServerServiceClient: &mocks.RepoServerServiceClient{}},
|
||||
}
|
||||
|
||||
err := initializeDefaultProject(argoCDOpts)
|
||||
@@ -928,6 +946,7 @@ func TestInitializeDefaultProject_ProjectAlreadyInitialized(t *testing.T) {
|
||||
Namespace: test.FakeArgoCDNamespace,
|
||||
KubeClientset: fake.NewSimpleClientset(test.NewFakeConfigMap(), test.NewFakeSecret()),
|
||||
AppClientset: apps.NewSimpleClientset(&existingDefaultProject),
|
||||
RepoClientset: &mocks.Clientset{RepoServerServiceClient: &mocks.RepoServerServiceClient{}},
|
||||
}
|
||||
|
||||
err := initializeDefaultProject(argoCDOpts)
|
||||
|
||||
@@ -317,6 +317,11 @@ func updateSettingConfigMap(updater func(cm *corev1.ConfigMap) error) {
|
||||
updateGenericConfigMap(common.ArgoCDConfigMapName, updater)
|
||||
}
|
||||
|
||||
// Convenience wrapper for updating argocd-notifications-cm
|
||||
func updateNotificationsConfigMap(updater func(cm *corev1.ConfigMap) error) {
|
||||
updateGenericConfigMap(common.ArgoCDNotificationsConfigMapName, updater)
|
||||
}
|
||||
|
||||
// Convenience wrapper for updating argocd-cm-rbac
|
||||
func updateRBACConfigMap(updater func(cm *corev1.ConfigMap) error) {
|
||||
updateGenericConfigMap(common.ArgoCDRBACConfigMapName, updater)
|
||||
@@ -502,6 +507,13 @@ func SetParamInSettingConfigMap(key, value string) {
|
||||
})
|
||||
}
|
||||
|
||||
func SetParamInNotificationsConfigMap(key, value string) {
|
||||
updateNotificationsConfigMap(func(cm *corev1.ConfigMap) error {
|
||||
cm.Data[key] = value
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func EnsureCleanState(t *testing.T) {
|
||||
// In large scenarios, we can skip tests that already run
|
||||
SkipIfAlreadyRun(t)
|
||||
@@ -543,6 +555,11 @@ func EnsureCleanState(t *testing.T) {
|
||||
return nil
|
||||
})
|
||||
|
||||
updateNotificationsConfigMap(func(cm *corev1.ConfigMap) error {
|
||||
cm.Data = map[string]string{}
|
||||
return nil
|
||||
})
|
||||
|
||||
// reset rbac
|
||||
updateRBACConfigMap(func(cm *corev1.ConfigMap) error {
|
||||
cm.Data = map[string]string{}
|
||||
|
||||
27
test/e2e/fixture/notification/actions.go
Normal file
27
test/e2e/fixture/notification/actions.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package notification
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/test/e2e/fixture"
|
||||
)
|
||||
|
||||
// this implements the "when" part of given/when/then
|
||||
//
|
||||
// none of the func implement error checks, and that is complete intended, you should check for errors
|
||||
// using the Then()
|
||||
type Actions struct {
|
||||
context *Context
|
||||
}
|
||||
|
||||
func (a *Actions) SetParamInNotificationConfigMap(key, value string) *Actions {
|
||||
fixture.SetParamInNotificationsConfigMap(key, value)
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *Actions) Then() *Consequences {
|
||||
a.context.t.Helper()
|
||||
// in case any settings have changed, pause for 1s, not great, but fine
|
||||
time.Sleep(1 * time.Second)
|
||||
return &Consequences{a.context, a}
|
||||
}
|
||||
55
test/e2e/fixture/notification/consequences.go
Normal file
55
test/e2e/fixture/notification/consequences.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package notification
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apiclient/notification"
|
||||
"github.com/argoproj/argo-cd/v2/test/e2e/fixture"
|
||||
)
|
||||
|
||||
// this implements the "then" part of given/when/then
|
||||
type Consequences struct {
|
||||
context *Context
|
||||
actions *Actions
|
||||
}
|
||||
|
||||
func (c *Consequences) Services(block func(services *notification.ServiceList, err error)) *Consequences {
|
||||
c.context.t.Helper()
|
||||
block(c.listServices())
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Consequences) Triggers(block func(services *notification.TriggerList, err error)) *Consequences {
|
||||
c.context.t.Helper()
|
||||
block(c.listTriggers())
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Consequences) Templates(block func(services *notification.TemplateList, err error)) *Consequences {
|
||||
c.context.t.Helper()
|
||||
block(c.listTemplates())
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Consequences) listServices() (*notification.ServiceList, error) {
|
||||
_, notifClient, _ := fixture.ArgoCDClientset.NewNotificationClient()
|
||||
return notifClient.ListServices(context.Background(), ¬ification.ServicesListRequest{})
|
||||
}
|
||||
|
||||
func (c *Consequences) listTriggers() (*notification.TriggerList, error) {
|
||||
_, notifClient, _ := fixture.ArgoCDClientset.NewNotificationClient()
|
||||
return notifClient.ListTriggers(context.Background(), ¬ification.TriggersListRequest{})
|
||||
}
|
||||
|
||||
func (c *Consequences) listTemplates() (*notification.TemplateList, error) {
|
||||
_, notifClient, _ := fixture.ArgoCDClientset.NewNotificationClient()
|
||||
return notifClient.ListTemplates(context.Background(), ¬ification.TemplatesListRequest{})
|
||||
}
|
||||
|
||||
func (c *Consequences) When() *Actions {
|
||||
return c.actions
|
||||
}
|
||||
|
||||
func (c *Consequences) Given() *Context {
|
||||
return c.context
|
||||
}
|
||||
26
test/e2e/fixture/notification/context.go
Normal file
26
test/e2e/fixture/notification/context.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package notification
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/test/e2e/fixture"
|
||||
)
|
||||
|
||||
// this implements the "given" part of given/when/then
|
||||
type Context struct {
|
||||
t *testing.T
|
||||
}
|
||||
|
||||
func Given(t *testing.T) *Context {
|
||||
fixture.EnsureCleanState(t)
|
||||
return &Context{t: t}
|
||||
}
|
||||
|
||||
func (c *Context) And(block func()) *Context {
|
||||
block()
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Context) When() *Actions {
|
||||
return &Actions{context: c}
|
||||
}
|
||||
40
test/e2e/notification_test.go
Normal file
40
test/e2e/notification_test.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package e2e
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apiclient/notification"
|
||||
notifFixture "github.com/argoproj/argo-cd/v2/test/e2e/fixture/notification"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"k8s.io/utils/pointer"
|
||||
)
|
||||
|
||||
func TestNotificationsListServices(t *testing.T) {
|
||||
ctx := notifFixture.Given(t)
|
||||
ctx.When().
|
||||
SetParamInNotificationConfigMap("service.webhook.test", "url: https://test.com").
|
||||
Then().Services(func(services *notification.ServiceList, err error) {
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, []*notification.Service{¬ification.Service{Name: pointer.String("test")}}, services.Items)
|
||||
})
|
||||
}
|
||||
|
||||
func TestNotificationsListTemplates(t *testing.T) {
|
||||
ctx := notifFixture.Given(t)
|
||||
ctx.When().
|
||||
SetParamInNotificationConfigMap("template.app-created", "email:\n subject: Application {{.app.metadata.name}} has been created.\nmessage: Application {{.app.metadata.name}} has been created.\nteams:\n title: Application {{.app.metadata.name}} has been created.\n").
|
||||
Then().Templates(func(templates *notification.TemplateList, err error) {
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, []*notification.Template{¬ification.Template{Name: pointer.String("app-created")}}, templates.Items)
|
||||
})
|
||||
}
|
||||
|
||||
func TestNotificationsListTriggers(t *testing.T) {
|
||||
ctx := notifFixture.Given(t)
|
||||
ctx.When().
|
||||
SetParamInNotificationConfigMap("trigger.on-created", "- description: Application is created.\n oncePer: app.metadata.name\n send:\n - app-created\n when: \"true\"\n").
|
||||
Then().Triggers(func(triggers *notification.TriggerList, err error) {
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, []*notification.Trigger{¬ification.Trigger{Name: pointer.String("on-created")}}, triggers.Items)
|
||||
})
|
||||
}
|
||||
@@ -5,6 +5,7 @@ bases:
|
||||
- ../../../manifests/crds
|
||||
- ../../../manifests/base/config
|
||||
- ../../../manifests/cluster-rbac
|
||||
- ../../../manifests/base/notification
|
||||
|
||||
patchesStrategicMerge:
|
||||
- patches.yaml
|
||||
- patches.yaml
|
||||
|
||||
@@ -2,20 +2,17 @@ package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/util/notification/expression/shared"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/common"
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
"github.com/argoproj/argo-cd/v2/reposerver/apiclient"
|
||||
repoapiclient "github.com/argoproj/argo-cd/v2/reposerver/apiclient"
|
||||
"github.com/argoproj/argo-cd/v2/util/db"
|
||||
"github.com/argoproj/argo-cd/v2/util/env"
|
||||
"github.com/argoproj/argo-cd/v2/util/settings"
|
||||
"github.com/argoproj/argo-cd/v2/util/tls"
|
||||
)
|
||||
|
||||
//go:generate mockgen -destination=./mocks/service.go -package=mocks github.com/argoproj-labs/argocd-notifications/shared/argocd Service
|
||||
@@ -25,25 +22,9 @@ type Service interface {
|
||||
GetAppDetails(ctx context.Context, appSource *v1alpha1.ApplicationSource) (*shared.AppDetail, error)
|
||||
}
|
||||
|
||||
func NewArgoCDService(clientset kubernetes.Interface, namespace string, repoServerAddress string, disableTLS bool, strictValidation bool) (*argoCDService, error) {
|
||||
func NewArgoCDService(clientset kubernetes.Interface, namespace string, repoClientset repoapiclient.Clientset) (*argoCDService, error) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
settingsMgr := settings.NewSettingsManager(ctx, clientset, namespace)
|
||||
tlsConfig := apiclient.TLSConfiguration{
|
||||
DisableTLS: disableTLS,
|
||||
StrictValidation: strictValidation,
|
||||
}
|
||||
if !disableTLS && strictValidation {
|
||||
pool, err := tls.LoadX509CertPool(
|
||||
fmt.Sprintf("%s/reposerver/tls/tls.crt", env.StringFromEnv(common.EnvAppConfigPath, common.DefaultAppConfigPath)),
|
||||
fmt.Sprintf("%s/reposerver/tls/ca.crt", env.StringFromEnv(common.EnvAppConfigPath, common.DefaultAppConfigPath)),
|
||||
)
|
||||
if err != nil {
|
||||
cancel()
|
||||
return nil, err
|
||||
}
|
||||
tlsConfig.Certificates = pool
|
||||
}
|
||||
repoClientset := apiclient.NewRepoServerClientset(repoServerAddress, 5, tlsConfig)
|
||||
closer, repoClient, err := repoClientset.NewRepoServerClient()
|
||||
if err != nil {
|
||||
cancel()
|
||||
|
||||
Reference in New Issue
Block a user