mirror of
https://github.com/argoproj/argo-cd.git
synced 2026-02-20 01:28:45 +01:00
Feat: cmp server (#6585)
* feat: config management plugin enhancement (#6585) Signed-off-by: kshamajain99 <kshamajain99@gmail.com> Signed-off-by: May Zhang <may_zhang@intuit.com> Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
This commit is contained in:
@@ -130,6 +130,7 @@ COPY --from=argocd-build /go/src/github.com/argoproj/argo-cd/dist/argocd* /usr/l
|
||||
USER root
|
||||
RUN ln -s /usr/local/bin/argocd /usr/local/bin/argocd-server
|
||||
RUN ln -s /usr/local/bin/argocd /usr/local/bin/argocd-repo-server
|
||||
RUN ln -s /usr/local/bin/argocd /usr/local/bin/argocd-cmp-server
|
||||
RUN ln -s /usr/local/bin/argocd /usr/local/bin/argocd-application-controller
|
||||
RUN ln -s /usr/local/bin/argocd /usr/local/bin/argocd-dex
|
||||
|
||||
|
||||
3
Makefile
3
Makefile
@@ -266,6 +266,7 @@ image:
|
||||
ln -sfn ${DIST_DIR}/argocd ${DIST_DIR}/argocd-server
|
||||
ln -sfn ${DIST_DIR}/argocd ${DIST_DIR}/argocd-application-controller
|
||||
ln -sfn ${DIST_DIR}/argocd ${DIST_DIR}/argocd-repo-server
|
||||
ln -sfn ${DIST_DIR}/argocd ${DIST_DIR}/argocd-cmp-server
|
||||
ln -sfn ${DIST_DIR}/argocd ${DIST_DIR}/argocd-dex
|
||||
cp Dockerfile.dev dist
|
||||
docker build -t $(IMAGE_PREFIX)argocd:$(IMAGE_TAG) -f dist/Dockerfile.dev dist
|
||||
@@ -409,12 +410,14 @@ start-e2e-local:
|
||||
if test -d /tmp/argo-e2e/app/config/gpg; then rm -rf /tmp/argo-e2e/app/config/gpg/*; fi
|
||||
mkdir -p /tmp/argo-e2e/app/config/gpg/keys && chmod 0700 /tmp/argo-e2e/app/config/gpg/keys
|
||||
mkdir -p /tmp/argo-e2e/app/config/gpg/source && chmod 0700 /tmp/argo-e2e/app/config/gpg/source
|
||||
mkdir -p /tmp/argo-e2e/app/config/plugin && chmod 0700 /tmp/argo-e2e/app/config/plugin
|
||||
# set paths for locally managed ssh known hosts and tls certs data
|
||||
ARGOCD_SSH_DATA_PATH=/tmp/argo-e2e/app/config/ssh \
|
||||
ARGOCD_TLS_DATA_PATH=/tmp/argo-e2e/app/config/tls \
|
||||
ARGOCD_GPG_DATA_PATH=/tmp/argo-e2e/app/config/gpg/source \
|
||||
ARGOCD_GNUPGHOME=/tmp/argo-e2e/app/config/gpg/keys \
|
||||
ARGOCD_GPG_ENABLED=$(ARGOCD_GPG_ENABLED) \
|
||||
ARGOCD_PLUGINCONFIGFILEPATH=/tmp/argo-e2e/app/config/plugin \
|
||||
ARGOCD_E2E_DISABLE_AUTH=false \
|
||||
ARGOCD_ZJWT_FEATURE_FLAG=always \
|
||||
ARGOCD_IN_CI=$(ARGOCD_IN_CI) \
|
||||
|
||||
2
Procfile
2
Procfile
@@ -2,7 +2,7 @@ controller: sh -c "FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_TLS_DAT
|
||||
api-server: sh -c "FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_TLS_DATA_PATH=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} ARGOCD_SSH_DATA_PATH=${ARGOCD_SSH_DATA_PATH:-/tmp/argocd-local/ssh} ARGOCD_BINARY_NAME=argocd-server go run ./cmd/main.go --loglevel debug --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379} --disable-auth=${ARGOCD_E2E_DISABLE_AUTH:-'true'} --insecure --dex-server http://localhost:${ARGOCD_E2E_DEX_PORT:-5556} --repo-server localhost:${ARGOCD_E2E_REPOSERVER_PORT:-8081} --port ${ARGOCD_E2E_APISERVER_PORT:-8080} "
|
||||
dex: sh -c "ARGOCD_BINARY_NAME=argocd-dex go run github.com/argoproj/argo-cd/v2/cmd gendexcfg -o `pwd`/dist/dex.yaml && docker run --rm -p ${ARGOCD_E2E_DEX_PORT:-5556}:${ARGOCD_E2E_DEX_PORT:-5556} -v `pwd`/dist/dex.yaml:/dex.yaml ghcr.io/dexidp/dex:v2.30.0 serve /dex.yaml"
|
||||
redis: bash -c "if [ \"$ARGOCD_REDIS_LOCAL\" == 'true' ]; then redis-server --save '' --appendonly no --port ${ARGOCD_E2E_REDIS_PORT:-6379}; else docker run --rm --name argocd-redis -i -p ${ARGOCD_E2E_REDIS_PORT:-6379}:${ARGOCD_E2E_REDIS_PORT:-6379} redis:6.2.4-alpine --save '' --appendonly no --port ${ARGOCD_E2E_REDIS_PORT:-6379}; fi"
|
||||
repo-server: sh -c "FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_GNUPGHOME=${ARGOCD_GNUPGHOME:-/tmp/argocd-local/gpg/keys} ARGOCD_GPG_DATA_PATH=${ARGOCD_GPG_DATA_PATH:-/tmp/argocd-local/gpg/source} ARGOCD_TLS_DATA_PATH=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} ARGOCD_SSH_DATA_PATH=${ARGOCD_SSH_DATA_PATH:-/tmp/argocd-local/ssh} ARGOCD_BINARY_NAME=argocd-repo-server ARGOCD_GPG_ENABLED=${ARGOCD_GPG_ENABLED:-false} go run ./cmd/main.go --loglevel debug --port ${ARGOCD_E2E_REPOSERVER_PORT:-8081} --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379}"
|
||||
repo-server: sh -c "FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_GNUPGHOME=${ARGOCD_GNUPGHOME:-/tmp/argocd-local/gpg/keys} ARGOCD_PLUGINSOCKFILEPATH=${ARGOCD_PLUGINSOCKFILEPATH:-/tmp/argo-e2e/app/config/plugin} ARGOCD_GPG_DATA_PATH=${ARGOCD_GPG_DATA_PATH:-/tmp/argocd-local/gpg/source} ARGOCD_TLS_DATA_PATH=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} ARGOCD_SSH_DATA_PATH=${ARGOCD_SSH_DATA_PATH:-/tmp/argocd-local/ssh} ARGOCD_BINARY_NAME=argocd-repo-server ARGOCD_GPG_ENABLED=${ARGOCD_GPG_ENABLED:-false} go run ./cmd/main.go --loglevel debug --port ${ARGOCD_E2E_REPOSERVER_PORT:-8081} --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379}"
|
||||
ui: sh -c 'cd ui && ${ARGOCD_E2E_YARN_CMD:-yarn} start'
|
||||
git-server: test/fixture/testrepos/start-git.sh
|
||||
helm-registry: test/fixture/testrepos/start-helm-registry.sh
|
||||
|
||||
58
cmd/argocd-cmp-server/commands/argocd_cmp_server.go
Normal file
58
cmd/argocd-cmp-server/commands/argocd_cmp_server.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/argoproj/pkg/stats"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
cmdutil "github.com/argoproj/argo-cd/v2/cmd/util"
|
||||
"github.com/argoproj/argo-cd/v2/cmpserver"
|
||||
"github.com/argoproj/argo-cd/v2/cmpserver/plugin"
|
||||
"github.com/argoproj/argo-cd/v2/common"
|
||||
"github.com/argoproj/argo-cd/v2/util/cli"
|
||||
"github.com/argoproj/argo-cd/v2/util/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
// CLIName is the name of the CLI
|
||||
cliName = "argocd-cmp-server"
|
||||
)
|
||||
|
||||
func NewCommand() *cobra.Command {
|
||||
var (
|
||||
configFilePath string
|
||||
)
|
||||
var command = cobra.Command{
|
||||
Use: cliName,
|
||||
Short: "Run ArgoCD ConfigManagementPlugin Server",
|
||||
Long: "ArgoCD ConfigManagementPlugin Server is an internal service which runs as sidecar container in reposerver deployment. It can be configured by following options.",
|
||||
DisableAutoGenTag: true,
|
||||
RunE: func(c *cobra.Command, args []string) error {
|
||||
cli.SetLogFormat(cmdutil.LogFormat)
|
||||
cli.SetLogLevel(cmdutil.LogLevel)
|
||||
|
||||
config, err := plugin.ReadPluginConfig(configFilePath)
|
||||
errors.CheckError(err)
|
||||
|
||||
server, err := cmpserver.NewServer(plugin.CMPServerInitConstants{
|
||||
PluginConfig: *config,
|
||||
})
|
||||
errors.CheckError(err)
|
||||
|
||||
// register dumper
|
||||
stats.RegisterStackDumper()
|
||||
stats.StartStatsTicker(10 * time.Minute)
|
||||
stats.RegisterHeapDumper("memprofile")
|
||||
|
||||
// run argocd-cmp-server server
|
||||
server.Run()
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
command.Flags().StringVar(&cmdutil.LogFormat, "logformat", "text", "Set the logging format. One of: text|json")
|
||||
command.Flags().StringVar(&cmdutil.LogLevel, "loglevel", "info", "Set the logging level. One of: debug|info|warn|error")
|
||||
command.Flags().StringVar(&configFilePath, "config-dir-path", common.DefaultPluginConfigFilePath, "Config management plugin configuration file location, Default is '/home/argocd/cmp-server/config/'")
|
||||
return &command
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
appcontroller "github.com/argoproj/argo-cd/v2/cmd/argocd-application-controller/commands"
|
||||
cmpserver "github.com/argoproj/argo-cd/v2/cmd/argocd-cmp-server/commands"
|
||||
dex "github.com/argoproj/argo-cd/v2/cmd/argocd-dex/commands"
|
||||
reposerver "github.com/argoproj/argo-cd/v2/cmd/argocd-repo-server/commands"
|
||||
apiserver "github.com/argoproj/argo-cd/v2/cmd/argocd-server/commands"
|
||||
@@ -34,6 +35,8 @@ func main() {
|
||||
command = appcontroller.NewCommand()
|
||||
case "argocd-repo-server":
|
||||
command = reposerver.NewCommand()
|
||||
case "argocd-cmp-server":
|
||||
command = cmpserver.NewCommand()
|
||||
case "argocd-dex":
|
||||
command = dex.NewCommand()
|
||||
default:
|
||||
|
||||
66
cmpserver/apiclient/clientset.go
Normal file
66
cmpserver/apiclient/clientset.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package apiclient
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
|
||||
grpc_retry "github.com/grpc-ecosystem/go-grpc-middleware/retry"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"google.golang.org/grpc"
|
||||
|
||||
grpc_util "github.com/argoproj/argo-cd/v2/util/grpc"
|
||||
"github.com/argoproj/argo-cd/v2/util/io"
|
||||
)
|
||||
|
||||
const (
|
||||
// MaxGRPCMessageSize contains max grpc message size
|
||||
MaxGRPCMessageSize = 100 * 1024 * 1024
|
||||
)
|
||||
|
||||
// Clientset represents config management plugin server api clients
|
||||
type Clientset interface {
|
||||
NewConfigManagementPluginClient() (io.Closer, ConfigManagementPluginServiceClient, error)
|
||||
}
|
||||
|
||||
type clientSet struct {
|
||||
address string
|
||||
timeoutSeconds int
|
||||
}
|
||||
|
||||
func (c *clientSet) NewConfigManagementPluginClient() (io.Closer, ConfigManagementPluginServiceClient, error) {
|
||||
conn, err := NewConnection(c.address, c.timeoutSeconds)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return conn, NewConfigManagementPluginServiceClient(conn), nil
|
||||
}
|
||||
|
||||
func NewConnection(address string, timeoutSeconds int) (*grpc.ClientConn, error) {
|
||||
retryOpts := []grpc_retry.CallOption{
|
||||
grpc_retry.WithMax(3),
|
||||
grpc_retry.WithBackoff(grpc_retry.BackoffLinear(1000 * time.Millisecond)),
|
||||
}
|
||||
unaryInterceptors := []grpc.UnaryClientInterceptor{grpc_retry.UnaryClientInterceptor(retryOpts...)}
|
||||
if timeoutSeconds > 0 {
|
||||
unaryInterceptors = append(unaryInterceptors, grpc_util.WithTimeout(time.Duration(timeoutSeconds)*time.Second))
|
||||
}
|
||||
dialOpts := []grpc.DialOption{
|
||||
grpc.WithStreamInterceptor(grpc_retry.StreamClientInterceptor(retryOpts...)),
|
||||
grpc.WithUnaryInterceptor(grpc_middleware.ChainUnaryClient(unaryInterceptors...)),
|
||||
grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(MaxGRPCMessageSize), grpc.MaxCallSendMsgSize(MaxGRPCMessageSize)),
|
||||
}
|
||||
|
||||
dialOpts = append(dialOpts, grpc.WithInsecure())
|
||||
conn, err := grpc_util.BlockingDial(context.Background(), "unix", address, nil, dialOpts...)
|
||||
if err != nil {
|
||||
log.Errorf("Unable to connect to config management plugin service with address %s", address)
|
||||
return nil, err
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
// NewCMPServerClientset creates new instance of config management plugin server Clientset
|
||||
func NewConfigManagementPluginClientSet(address string, timeoutSeconds int) Clientset {
|
||||
return &clientSet{address: address, timeoutSeconds: timeoutSeconds}
|
||||
}
|
||||
1944
cmpserver/apiclient/plugin.pb.go
Normal file
1944
cmpserver/apiclient/plugin.pb.go
Normal file
File diff suppressed because it is too large
Load Diff
86
cmpserver/plugin/config.go
Normal file
86
cmpserver/plugin/config.go
Normal file
@@ -0,0 +1,86 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/common"
|
||||
configUtil "github.com/argoproj/argo-cd/v2/util/config"
|
||||
)
|
||||
|
||||
const (
|
||||
ConfigManagementPluginKind string = "ConfigManagementPlugin"
|
||||
)
|
||||
|
||||
type PluginConfig struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
Metadata metav1.ObjectMeta `json:"metadata"`
|
||||
Spec PluginConfigSpec `json:"spec"`
|
||||
}
|
||||
|
||||
type PluginConfigSpec struct {
|
||||
Version string `json:"version"`
|
||||
Init Command `json:"init,omitempty"`
|
||||
Generate Command `json:"generate"`
|
||||
Discover Discover `json:"discover"`
|
||||
AllowConcurrency bool `json:"allowConcurrency"`
|
||||
LockRepo bool `json:"lockRepo"`
|
||||
}
|
||||
|
||||
//Discover holds find and fileName
|
||||
type Discover struct {
|
||||
Find Command `json:"find"`
|
||||
FileName string `json:"fileName"`
|
||||
}
|
||||
|
||||
// Command holds binary path and arguments list
|
||||
type Command struct {
|
||||
Command []string `json:"command,omitempty"`
|
||||
Args []string `json:"args,omitempty"`
|
||||
Glob string `json:"glob"`
|
||||
}
|
||||
|
||||
func ReadPluginConfig(filePath string) (*PluginConfig, error) {
|
||||
path := fmt.Sprintf("%s/%s", strings.TrimRight(filePath, "/"), common.PluginConfigFileName)
|
||||
|
||||
var config PluginConfig
|
||||
err := configUtil.UnmarshalLocalFile(path, &config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = ValidatePluginConfig(config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &config, nil
|
||||
}
|
||||
|
||||
func ValidatePluginConfig(config PluginConfig) error {
|
||||
if config.Metadata.Name == "" {
|
||||
return fmt.Errorf("invalid plugin configuration file. metadata.name should be non-empty.")
|
||||
}
|
||||
if config.TypeMeta.Kind != ConfigManagementPluginKind {
|
||||
return fmt.Errorf("invalid plugin configuration file. kind should be %s, found %s", ConfigManagementPluginKind, config.TypeMeta.Kind)
|
||||
}
|
||||
if len(config.Spec.Generate.Command) == 0 {
|
||||
return fmt.Errorf("invalid plugin configuration file. spec.generate command should be non-empty")
|
||||
}
|
||||
if config.Spec.Discover.Find.Glob == "" && len(config.Spec.Discover.Find.Command) == 0 && config.Spec.Discover.FileName == "" {
|
||||
return fmt.Errorf("invalid plugin configuration file. atleast one of discover.find.command or discover.find.glob or discover.fineName should be non-empty")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cfg *PluginConfig) Address() string {
|
||||
var address string
|
||||
pluginSockFilePath := common.GetPluginSockFilePath()
|
||||
if cfg.Spec.Version != "" {
|
||||
address = fmt.Sprintf("%s/%s-%s.sock", pluginSockFilePath, cfg.Metadata.Name, cfg.Spec.Version)
|
||||
} else {
|
||||
address = fmt.Sprintf("%s/%s.sock", pluginSockFilePath, cfg.Metadata.Name)
|
||||
}
|
||||
return address
|
||||
}
|
||||
137
cmpserver/plugin/plugin.go
Normal file
137
cmpserver/plugin/plugin.go
Normal file
@@ -0,0 +1,137 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/argoproj/gitops-engine/pkg/utils/kube"
|
||||
"github.com/mattn/go-zglob"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/cmpserver/apiclient"
|
||||
executil "github.com/argoproj/argo-cd/v2/util/exec"
|
||||
)
|
||||
|
||||
// Service implements ConfigManagementPluginService interface
|
||||
type Service struct {
|
||||
initConstants CMPServerInitConstants
|
||||
}
|
||||
|
||||
type CMPServerInitConstants struct {
|
||||
PluginConfig PluginConfig
|
||||
}
|
||||
|
||||
// NewService returns a new instance of the ConfigManagementPluginService
|
||||
func NewService(initConstants CMPServerInitConstants) *Service {
|
||||
return &Service{
|
||||
initConstants: initConstants,
|
||||
}
|
||||
}
|
||||
|
||||
func runCommand(command Command, path string, env []string) (string, error) {
|
||||
if len(command.Command) == 0 {
|
||||
return "", fmt.Errorf("Command is empty")
|
||||
}
|
||||
cmd := exec.Command(command.Command[0], append(command.Command[1:], command.Args...)...)
|
||||
cmd.Env = env
|
||||
cmd.Dir = path
|
||||
return executil.Run(cmd)
|
||||
}
|
||||
|
||||
// Environ returns a list of environment variables in name=value format from a list of variables
|
||||
func environ(envVars []*apiclient.EnvEntry) []string {
|
||||
var environ []string
|
||||
for _, item := range envVars {
|
||||
if item != nil && item.Name != "" && item.Value != "" {
|
||||
environ = append(environ, fmt.Sprintf("%s=%s", item.Name, item.Value))
|
||||
}
|
||||
}
|
||||
return environ
|
||||
}
|
||||
|
||||
// GenerateManifest runs generate command from plugin config file and returns generated manifest files
|
||||
func (s *Service) GenerateManifest(ctx context.Context, q *apiclient.ManifestRequest) (*apiclient.ManifestResponse, error) {
|
||||
config := s.initConstants.PluginConfig
|
||||
|
||||
env := append(os.Environ(), environ(q.Env)...)
|
||||
if len(config.Spec.Init.Command) > 0 {
|
||||
_, err := runCommand(config.Spec.Init, q.AppPath, env)
|
||||
if err != nil {
|
||||
return &apiclient.ManifestResponse{}, err
|
||||
}
|
||||
}
|
||||
|
||||
out, err := runCommand(config.Spec.Generate, q.AppPath, env)
|
||||
if err != nil {
|
||||
return &apiclient.ManifestResponse{}, err
|
||||
}
|
||||
|
||||
manifests, err := kube.SplitYAMLToString([]byte(out))
|
||||
if err != nil {
|
||||
return &apiclient.ManifestResponse{}, err
|
||||
}
|
||||
|
||||
return &apiclient.ManifestResponse{
|
||||
Manifests: manifests,
|
||||
}, err
|
||||
}
|
||||
|
||||
// MatchRepository checks whether the application repository type is supported by config management plugin server
|
||||
func (s *Service) MatchRepository(ctx context.Context, q *apiclient.RepositoryRequest) (*apiclient.RepositoryResponse, error) {
|
||||
var repoResponse apiclient.RepositoryResponse
|
||||
config := s.initConstants.PluginConfig
|
||||
if config.Spec.Discover.FileName != "" {
|
||||
log.Debugf("config.Spec.Discover.FileName is provided")
|
||||
pattern := strings.TrimSuffix(q.Path, "/") + "/" + strings.TrimPrefix(config.Spec.Discover.FileName, "/")
|
||||
matches, err := filepath.Glob(pattern)
|
||||
if err != nil || len(matches) == 0 {
|
||||
log.Debugf("Could not find match for pattern %s. Error is %v.", pattern, err)
|
||||
return &repoResponse, err
|
||||
} else if len(matches) > 0 {
|
||||
repoResponse.IsSupported = true
|
||||
return &repoResponse, nil
|
||||
}
|
||||
}
|
||||
|
||||
if config.Spec.Discover.Find.Glob != "" {
|
||||
log.Debugf("config.Spec.Discover.Find.Glob is provided")
|
||||
pattern := strings.TrimSuffix(q.Path, "/") + "/" + strings.TrimPrefix(config.Spec.Discover.Find.Glob, "/")
|
||||
// filepath.Glob doesn't have '**' support hence selecting third-party lib
|
||||
// https://github.com/golang/go/issues/11862
|
||||
matches, err := zglob.Glob(pattern)
|
||||
if err != nil || len(matches) == 0 {
|
||||
log.Debugf("Could not find match for pattern %s. Error is %v.", pattern, err)
|
||||
return &repoResponse, err
|
||||
} else if len(matches) > 0 {
|
||||
repoResponse.IsSupported = true
|
||||
return &repoResponse, nil
|
||||
}
|
||||
}
|
||||
|
||||
log.Debugf("Going to try runCommand.")
|
||||
find, err := runCommand(config.Spec.Discover.Find, q.Path, os.Environ())
|
||||
if err != nil {
|
||||
return &repoResponse, err
|
||||
}
|
||||
|
||||
var isSupported bool
|
||||
if find != "" {
|
||||
isSupported = true
|
||||
}
|
||||
return &apiclient.RepositoryResponse{
|
||||
IsSupported: isSupported,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetPluginConfig returns plugin config
|
||||
func (s *Service) GetPluginConfig(ctx context.Context, q *apiclient.ConfigRequest) (*apiclient.ConfigResponse, error) {
|
||||
config := s.initConstants.PluginConfig
|
||||
return &apiclient.ConfigResponse{
|
||||
AllowConcurrency: config.Spec.AllowConcurrency,
|
||||
LockRepo: config.Spec.LockRepo,
|
||||
}, nil
|
||||
}
|
||||
61
cmpserver/plugin/plugin.proto
Normal file
61
cmpserver/plugin/plugin.proto
Normal file
@@ -0,0 +1,61 @@
|
||||
syntax = "proto3";
|
||||
option go_package = "github.com/argoproj/argo-cd/v2/cmpserver/apiclient";
|
||||
|
||||
package plugin;
|
||||
|
||||
import "k8s.io/api/core/v1/generated.proto";
|
||||
|
||||
// ManifestRequest is a query for manifest generation.
|
||||
message ManifestRequest {
|
||||
// Name of the application for which the request is triggered
|
||||
string appName = 1;
|
||||
string appPath = 2;
|
||||
string repoPath = 3;
|
||||
bool noCache = 4;
|
||||
repeated EnvEntry env = 5;
|
||||
}
|
||||
|
||||
// EnvEntry represents an entry in the application's environment
|
||||
message EnvEntry {
|
||||
// Name is the name of the variable, usually expressed in uppercase
|
||||
string name = 1;
|
||||
// Value is the value of the variable
|
||||
string value = 2;
|
||||
}
|
||||
|
||||
message ManifestResponse {
|
||||
repeated string manifests = 1;
|
||||
string sourceType = 2;
|
||||
}
|
||||
|
||||
message RepositoryRequest {
|
||||
string path = 1;
|
||||
repeated EnvEntry env = 2;
|
||||
}
|
||||
|
||||
message RepositoryResponse {
|
||||
bool isSupported = 1;
|
||||
}
|
||||
|
||||
message ConfigRequest {
|
||||
}
|
||||
|
||||
message ConfigResponse {
|
||||
bool allowConcurrency = 1;
|
||||
bool lockRepo = 2;
|
||||
}
|
||||
|
||||
// ConfigManagementPlugin Service
|
||||
service ConfigManagementPluginService {
|
||||
// GenerateManifest generates manifest for application in specified repo name and revision
|
||||
rpc GenerateManifest(ManifestRequest) returns (ManifestResponse) {
|
||||
}
|
||||
|
||||
// MatchRepository returns whether or not the given path is supported by the plugin
|
||||
rpc MatchRepository(RepositoryRequest) returns (RepositoryResponse) {
|
||||
}
|
||||
|
||||
// Get configuration of the plugin
|
||||
rpc GetPluginConfig(ConfigRequest) returns (ConfigResponse) {
|
||||
}
|
||||
}
|
||||
65
cmpserver/plugin/plugin_test.go
Normal file
65
cmpserver/plugin/plugin_test.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/cmpserver/apiclient"
|
||||
)
|
||||
|
||||
func newService(configFilePath string) (*Service, error) {
|
||||
config, err := ReadPluginConfig(configFilePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
initConstants := CMPServerInitConstants{
|
||||
PluginConfig: *config,
|
||||
}
|
||||
|
||||
service := &Service{
|
||||
initConstants: initConstants,
|
||||
}
|
||||
return service, nil
|
||||
}
|
||||
|
||||
func TestMatchRepository(t *testing.T) {
|
||||
configFilePath := "./testdata/ksonnet/config"
|
||||
service, err := newService(configFilePath)
|
||||
require.NoError(t, err)
|
||||
|
||||
q := apiclient.RepositoryRequest{}
|
||||
path, err := os.Getwd()
|
||||
require.NoError(t, err)
|
||||
q.Path = path
|
||||
|
||||
res1, err := service.MatchRepository(context.Background(), &q)
|
||||
require.NoError(t, err)
|
||||
require.True(t, res1.IsSupported)
|
||||
}
|
||||
|
||||
func Test_Negative_ConfigFile_DoesnotExist(t *testing.T) {
|
||||
configFilePath := "./testdata/kustomize-neg/config"
|
||||
service, err := newService(configFilePath)
|
||||
require.Error(t, err)
|
||||
require.Nil(t, service)
|
||||
}
|
||||
|
||||
func TestGenerateManifest(t *testing.T) {
|
||||
configFilePath := "./testdata/kustomize/config"
|
||||
service, err := newService(configFilePath)
|
||||
require.NoError(t, err)
|
||||
|
||||
q := apiclient.ManifestRequest{}
|
||||
res1, err := service.GenerateManifest(context.Background(), &q)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, res1)
|
||||
|
||||
expectedOutput := "{\"apiVersion\":\"v1\",\"data\":{\"foo\":\"bar\"},\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"my-map\"}}"
|
||||
if res1 != nil {
|
||||
require.Equal(t, expectedOutput, res1.Manifests[0])
|
||||
}
|
||||
}
|
||||
15
cmpserver/plugin/testdata/ksonnet/config/plugin.yaml
vendored
Normal file
15
cmpserver/plugin/testdata/ksonnet/config/plugin.yaml
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: ConfigManagementPlugin
|
||||
metadata:
|
||||
name: ksonnet
|
||||
spec:
|
||||
version: v1.0
|
||||
init:
|
||||
command: [ks, version]
|
||||
generate:
|
||||
command: [sh, -c, "ks show $ARGOCD_APP_ENV"]
|
||||
discover:
|
||||
find:
|
||||
glob: "**/*/main.jsonnet"
|
||||
allowConcurrency: false
|
||||
lockRepo: false
|
||||
0
cmpserver/plugin/testdata/ksonnet/main.jsonnet
vendored
Normal file
0
cmpserver/plugin/testdata/ksonnet/main.jsonnet
vendored
Normal file
16
cmpserver/plugin/testdata/kustomize-neg/config/plugin-bad.yaml
vendored
Normal file
16
cmpserver/plugin/testdata/kustomize-neg/config/plugin-bad.yaml
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: ConfigManagementPlugin
|
||||
metadata:
|
||||
name: kustomize
|
||||
spec:
|
||||
version: v1.0
|
||||
init:
|
||||
command: [kustomize, version]
|
||||
generate:
|
||||
command: [sh, -c, "cd testdata/kustomize && kustomize build"]
|
||||
discover:
|
||||
find:
|
||||
command: [sh, -c, find . -name kustomization.yaml]
|
||||
glob: "**/*/kustomization.yaml"
|
||||
allowConcurrency: true
|
||||
lockRepo: false
|
||||
6
cmpserver/plugin/testdata/kustomize/cm.yaml
vendored
Normal file
6
cmpserver/plugin/testdata/kustomize/cm.yaml
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: my-map
|
||||
data:
|
||||
foo: bar
|
||||
16
cmpserver/plugin/testdata/kustomize/config/plugin.yaml
vendored
Normal file
16
cmpserver/plugin/testdata/kustomize/config/plugin.yaml
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: ConfigManagementPlugin
|
||||
metadata:
|
||||
name: kustomize
|
||||
spec:
|
||||
version: v1.0
|
||||
init:
|
||||
command: [kustomize, version]
|
||||
generate:
|
||||
command: [sh, -c, "cd testdata/kustomize && kustomize build"]
|
||||
discover:
|
||||
find:
|
||||
command: [sh, -c, find . -name kustomization.yaml]
|
||||
glob: "**/*/kustomization.yaml"
|
||||
allowConcurrency: true
|
||||
lockRepo: false
|
||||
5
cmpserver/plugin/testdata/kustomize/kustomization.yaml
vendored
Normal file
5
cmpserver/plugin/testdata/kustomize/kustomization.yaml
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
resources:
|
||||
- ./cm.yaml
|
||||
107
cmpserver/server.go
Normal file
107
cmpserver/server.go
Normal file
@@ -0,0 +1,107 @@
|
||||
package cmpserver
|
||||
|
||||
import (
|
||||
"net"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
|
||||
grpc_logrus "github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus"
|
||||
grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/health"
|
||||
"google.golang.org/grpc/health/grpc_health_v1"
|
||||
"google.golang.org/grpc/reflection"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/cmpserver/apiclient"
|
||||
"github.com/argoproj/argo-cd/v2/cmpserver/plugin"
|
||||
"github.com/argoproj/argo-cd/v2/common"
|
||||
versionpkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/version"
|
||||
"github.com/argoproj/argo-cd/v2/server/version"
|
||||
"github.com/argoproj/argo-cd/v2/util/errors"
|
||||
grpc_util "github.com/argoproj/argo-cd/v2/util/grpc"
|
||||
)
|
||||
|
||||
// ArgoCDCMPServer is the config management plugin server implementation
|
||||
type ArgoCDCMPServer struct {
|
||||
log *log.Entry
|
||||
opts []grpc.ServerOption
|
||||
initConstants plugin.CMPServerInitConstants
|
||||
stopCh chan os.Signal
|
||||
doneCh chan interface{}
|
||||
sig os.Signal
|
||||
}
|
||||
|
||||
// NewServer returns a new instance of the Argo CD config management plugin server
|
||||
func NewServer(initConstants plugin.CMPServerInitConstants) (*ArgoCDCMPServer, error) {
|
||||
if os.Getenv(common.EnvEnableGRPCTimeHistogramEnv) == "true" {
|
||||
grpc_prometheus.EnableHandlingTimeHistogram()
|
||||
}
|
||||
|
||||
serverLog := log.NewEntry(log.StandardLogger())
|
||||
streamInterceptors := []grpc.StreamServerInterceptor{grpc_logrus.StreamServerInterceptor(serverLog), grpc_prometheus.StreamServerInterceptor, grpc_util.PanicLoggerStreamServerInterceptor(serverLog)}
|
||||
unaryInterceptors := []grpc.UnaryServerInterceptor{grpc_logrus.UnaryServerInterceptor(serverLog), grpc_prometheus.UnaryServerInterceptor, grpc_util.PanicLoggerUnaryServerInterceptor(serverLog)}
|
||||
|
||||
serverOpts := []grpc.ServerOption{
|
||||
grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(unaryInterceptors...)),
|
||||
grpc.StreamInterceptor(grpc_middleware.ChainStreamServer(streamInterceptors...)),
|
||||
grpc.MaxRecvMsgSize(apiclient.MaxGRPCMessageSize),
|
||||
grpc.MaxSendMsgSize(apiclient.MaxGRPCMessageSize),
|
||||
}
|
||||
|
||||
return &ArgoCDCMPServer{
|
||||
log: serverLog,
|
||||
opts: serverOpts,
|
||||
stopCh: make(chan os.Signal),
|
||||
doneCh: make(chan interface{}),
|
||||
initConstants: initConstants,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a *ArgoCDCMPServer) Run() {
|
||||
config := a.initConstants.PluginConfig
|
||||
|
||||
// Listen on the socket address
|
||||
_ = os.Remove(config.Address())
|
||||
listener, err := net.Listen("unix", config.Address())
|
||||
errors.CheckError(err)
|
||||
log.Infof("argocd-cmp-server %s serving on %s", common.GetVersion(), listener.Addr())
|
||||
|
||||
signal.Notify(a.stopCh, syscall.SIGINT, syscall.SIGTERM)
|
||||
go a.Shutdown(config.Address())
|
||||
|
||||
grpcServer := a.CreateGRPC()
|
||||
err = grpcServer.Serve(listener)
|
||||
errors.CheckError(err)
|
||||
|
||||
if a.sig != nil {
|
||||
<-a.doneCh
|
||||
}
|
||||
}
|
||||
|
||||
// CreateGRPC creates new configured grpc server
|
||||
func (a *ArgoCDCMPServer) CreateGRPC() *grpc.Server {
|
||||
server := grpc.NewServer(a.opts...)
|
||||
versionpkg.RegisterVersionServiceServer(server, version.NewServer(nil, func() (bool, error) {
|
||||
return true, nil
|
||||
}))
|
||||
pluginService := plugin.NewService(a.initConstants)
|
||||
apiclient.RegisterConfigManagementPluginServiceServer(server, pluginService)
|
||||
|
||||
healthService := health.NewServer()
|
||||
grpc_health_v1.RegisterHealthServer(server, healthService)
|
||||
|
||||
// Register reflection service on gRPC server.
|
||||
reflection.Register(server)
|
||||
|
||||
return server
|
||||
}
|
||||
|
||||
func (a *ArgoCDCMPServer) Shutdown(address string) {
|
||||
defer signal.Stop(a.stopCh)
|
||||
a.sig = <-a.stopCh
|
||||
_ = os.Remove(address)
|
||||
close(a.doneCh)
|
||||
}
|
||||
@@ -54,6 +54,12 @@ const (
|
||||
DefaultGnuPgHomePath = "/app/config/gpg/keys"
|
||||
// Default path to repo server TLS endpoint config
|
||||
DefaultAppConfigPath = "/app/config"
|
||||
// Default path to cmp server plugin socket file
|
||||
DefaultPluginSockFilePath = "/home/argocd/cmp-server/plugins"
|
||||
// Default path to cmp server plugin configuration file
|
||||
DefaultPluginConfigFilePath = "/home/argocd/cmp-server/config"
|
||||
// Plugin Config File is a ConfigManagementPlugin manifest located inside the plugin container
|
||||
PluginConfigFileName = "plugin.yaml"
|
||||
)
|
||||
|
||||
// Argo CD application related constants
|
||||
@@ -183,6 +189,8 @@ const (
|
||||
EnvLogLevel = "ARGOCD_LOG_LEVEL"
|
||||
// EnvMaxCookieNumber max number of chunks a cookie can be broken into
|
||||
EnvMaxCookieNumber = "ARGOCD_MAX_COOKIE_NUMBER"
|
||||
// EnvPluginSockFilePath allows to override the pluginSockFilePath for repo server and cmp server
|
||||
EnvPluginSockFilePath = "ARGOCD_PLUGINSOCKFILEPATH"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -209,3 +217,12 @@ func GetGnuPGHomePath() string {
|
||||
return gnuPgHome
|
||||
}
|
||||
}
|
||||
|
||||
// GetPluginSockFilePath retrieves the path of plugin sock file, which is either taken from PluginSockFilePath environment or a default value
|
||||
func GetPluginSockFilePath() string {
|
||||
if pluginSockFilePath := os.Getenv(EnvPluginSockFilePath); pluginSockFilePath == "" {
|
||||
return DefaultPluginSockFilePath
|
||||
} else {
|
||||
return pluginSockFilePath
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
# Plugins
|
||||
|
||||
Argo CD allows integrating more config management tools using config management plugins. Following changes are required to configure new plugin:
|
||||
Argo CD allows integrating more config management tools using config management plugins.
|
||||
|
||||
## Configure plugins via Argo CD configmap
|
||||
Following changes are required to configure new plugin:
|
||||
|
||||
* Make sure required binaries are available in `argocd-repo-server` pod. The binaries can be added via volume mounts or using custom image (see [custom_tools](../operator-manual/custom_tools.md)).
|
||||
* Register a new plugin in `argocd-cm` ConfigMap:
|
||||
@@ -34,6 +37,103 @@ More config management plugin examples are available in [argocd-example-apps](ht
|
||||
repository at the time it is executed. Otherwise, two applications synced
|
||||
at the same time may result in a race condition and sync failure.
|
||||
|
||||
## Configure plugin via sidecar
|
||||
|
||||
As an effort to provide first-class support for additional plugin tools, we have enhanced the feature where an operator
|
||||
can configure additional plugin tool via sidecar to repo-server. Following changes are required to configure new plugin:
|
||||
|
||||
### Register plugin sidecar
|
||||
|
||||
To install a plugin, simply patch argocd-repo-server to run config management plugin container as a sidecar, with argocd-cmp-server as it’s entrypoint.
|
||||
You can use either off-the-shelf or custom built plugin image as sidecar image. For example:
|
||||
|
||||
```yaml
|
||||
containers:
|
||||
- name: cmp
|
||||
command: [/var/run/argocd/argocd-cmp-server] # Entrypoint should be Argo CD lightweight CMP server i.e. argocd-cmp-server
|
||||
image: busybox # This can be off-the-shelf or custom built image
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 999
|
||||
volumeMounts:
|
||||
- mountPath: /var/run/argocd
|
||||
name: var-files
|
||||
- mountPath: /home/argocd/cmp-server/plugins
|
||||
name: plugins
|
||||
- mountPath: /home/argocd/cmp-server/config/plugin.yaml # Plugin config file can either be volume mapped or baked into image
|
||||
subPath: plugin.yaml
|
||||
name: config-files
|
||||
- mountPath: /tmp
|
||||
name: tmp
|
||||
```
|
||||
|
||||
* Make sure that entrypoint is Argo CD lightweight cmp server i.e. argocd-cmp-server
|
||||
* Make sure that sidecar container is running as user 999
|
||||
* Make sure that plugin configuration file is present at `/home/argocd/cmp-server/config/`. It can either be volume mapped via configmap or baked into image
|
||||
|
||||
### Plugin configuration file
|
||||
|
||||
Plugins will be configured via a ConfigManagementPlugin manifest located inside the plugin container, placed at a well-known location
|
||||
(e.g. /home/argocd/cmp-server/config/plugin.yaml). Argo CD is agnostic to the mechanism of how the configuration file would be placed,
|
||||
but various options can be used on how to place this file, including:
|
||||
- Baking the file into the plugin image as part of docker build
|
||||
- Volume mapping the file through a configmap.
|
||||
|
||||
```yaml
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: ConfigManagementPlugin
|
||||
metadata:
|
||||
name: cmp-plugin
|
||||
spec:
|
||||
version: v1.0
|
||||
generate:
|
||||
command: [sh, -c, 'echo "{\"kind\": \"ConfigMap\", \"apiVersion\": \"v1\", \"metadata\": { \"name\": \"$ARGOCD_APP_NAME\", \"namespace\": \"$ARGOCD_APP_NAMESPACE\", \"annotations\": {\"Foo\": \"$FOO\", \"KubeVersion\": \"$KUBE_VERSION\", \"KubeApiVersion\": \"$KUBE_API_VERSIONS\",\"Bar\": \"baz\"}}}"']
|
||||
discover:
|
||||
fileName: "./subdir/s*.yaml"
|
||||
allowConcurrency: true
|
||||
lockRepo: false
|
||||
```
|
||||
|
||||
Note that, while the ConfigManagementPlugin looks like a Kubernetes object, it is not actually a custom resource.
|
||||
It only follows kubernetes-style spec conventions.
|
||||
|
||||
The `generate` command must print a valid YAML stream to stdout. Both `init` and `generate` commands are executed inside the application source directory.
|
||||
|
||||
The `discover.fileName` is used as matching pattern to determine whether application repository is supported by the plugin or not.
|
||||
|
||||
```yaml
|
||||
discover:
|
||||
find:
|
||||
command: [sh, -c, find . -name env.yaml]
|
||||
```
|
||||
If `discover.fileName` is not provided, the `discover.find.command` is executed in order to determine whether application repository is supported by the plugin or not. The `find` command should returns
|
||||
non-error response in case when application source type is supported.
|
||||
|
||||
If your plugin makes use of `git` (e.g. `git crypt`), it is advised to set `lockRepo` to `true` so that your plugin will have exclusive access to the
|
||||
repository at the time it is executed. Otherwise, two applications synced at the same time may result in a race condition and sync failure.
|
||||
|
||||
### Volume map plugin configuration file via configmap
|
||||
|
||||
If you are volume mapping the plugin configuration file through a configmap. Register a new plugin configuration file in `argocd-cmp-cm` configmap.
|
||||
For example:
|
||||
|
||||
```yaml
|
||||
data:
|
||||
plugin.yaml: |
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: ConfigManagementPlugin
|
||||
metadata:
|
||||
name: cmp-plugin
|
||||
spec:
|
||||
version: v1.0
|
||||
generate:
|
||||
command: [sh, -c, 'echo "{\"kind\": \"ConfigMap\", \"apiVersion\": \"v1\", \"metadata\": { \"name\": \"$ARGOCD_APP_NAME\", \"namespace\": \"$ARGOCD_APP_NAMESPACE\", \"annotations\": {\"Foo\": \"$FOO\", \"KubeVersion\": \"$KUBE_VERSION\", \"KubeApiVersion\": \"$KUBE_API_VERSIONS\",\"Bar\": \"baz\"}}}"']
|
||||
discover:
|
||||
fileName: "./subdir/s*.yaml"
|
||||
allowConcurrency: true
|
||||
lockRepo: false
|
||||
```
|
||||
|
||||
## Environment
|
||||
|
||||
Commands have access to
|
||||
|
||||
1
go.mod
1
go.mod
@@ -46,6 +46,7 @@ require (
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
|
||||
github.com/malexdev/utfutil v0.0.0-20180510171754-00c8d4a8e7a8 // indirect
|
||||
github.com/mattn/go-isatty v0.0.12
|
||||
github.com/mattn/go-zglob v0.0.3
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/pquerna/cachecontrol v0.0.0-20180306154005-525d0eb5f91d // indirect
|
||||
|
||||
2
go.sum
2
go.sum
@@ -599,6 +599,8 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky
|
||||
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-zglob v0.0.3 h1:6Ry4EYsScDyt5di4OI6xw1bYhOqfE5S33Z1OPy+d+To=
|
||||
github.com/mattn/go-zglob v0.0.3/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
||||
|
||||
@@ -72,7 +72,7 @@ go build -o dist/protoc-gen-grpc-gateway ./vendor/github.com/grpc-ecosystem/grpc
|
||||
go build -o dist/protoc-gen-swagger ./vendor/github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger
|
||||
|
||||
# Generate server/<service>/(<service>.pb.go|<service>.pb.gw.go)
|
||||
PROTO_FILES=$(find $PROJECT_ROOT \( -name "*.proto" -and -path '*/server/*' -or -path '*/reposerver/*' -and -name "*.proto" \) | sort)
|
||||
PROTO_FILES=$(find $PROJECT_ROOT \( -name "*.proto" -and -path '*/server/*' -or -path '*/reposerver/*' -and -name "*.proto" -or -path '*/cmpserver/*' -and -name "*.proto" \) | sort)
|
||||
for i in ${PROTO_FILES}; do
|
||||
GOOGLE_PROTO_API_PATH=${MOD_ROOT}/github.com/grpc-ecosystem/grpc-gateway@${grpc_gateway_version}/third_party/googleapis
|
||||
GOGO_PROTOBUF_PATH=${PROJECT_ROOT}/vendor/github.com/gogo/protobuf
|
||||
@@ -130,3 +130,4 @@ collect_swagger server ${EXPECTED_COLLISION_COUNT}
|
||||
clean_swagger server
|
||||
clean_swagger reposerver
|
||||
clean_swagger controller
|
||||
clean_swagger cmpserver
|
||||
|
||||
7
manifests/base/config/argocd-cmp-cm.yaml
Normal file
7
manifests/base/config/argocd-cmp-cm.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: argocd-cmp-cm
|
||||
labels:
|
||||
app.kubernetes.io/name: argocd-cmp-cm
|
||||
app.kubernetes.io/part-of: argocd
|
||||
@@ -3,6 +3,7 @@ kind: Kustomization
|
||||
|
||||
resources:
|
||||
- argocd-cm.yaml
|
||||
- argocd-cmp-cm.yaml
|
||||
- argocd-cmd-params-cm.yaml
|
||||
- argocd-secret.yaml
|
||||
- argocd-rbac-cm.yaml
|
||||
|
||||
@@ -142,6 +142,19 @@ spec:
|
||||
mountPath: /tmp
|
||||
- mountPath: /helm-working-dir
|
||||
name: helm-working-dir
|
||||
- mountPath: /home/argocd/cmp-server/plugins
|
||||
name: plugins
|
||||
initContainers:
|
||||
- command:
|
||||
- cp
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /var/run/argocd/argocd-cmp-server
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
name: copyutil
|
||||
volumeMounts:
|
||||
- mountPath: /var/run/argocd
|
||||
name: var-files
|
||||
volumes:
|
||||
- name: ssh-known-hosts
|
||||
configMap:
|
||||
@@ -169,6 +182,13 @@ spec:
|
||||
path: tls.key
|
||||
- key: ca.crt
|
||||
path: ca.crt
|
||||
- emptyDir: {}
|
||||
name: var-files
|
||||
- emptyDir: {}
|
||||
name: plugins
|
||||
- name: config-files
|
||||
configMap:
|
||||
name: argocd-cmp-cm
|
||||
affinity:
|
||||
podAntiAffinity:
|
||||
preferredDuringSchedulingIgnoredDuringExecution:
|
||||
|
||||
@@ -2747,6 +2747,14 @@ metadata:
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: argocd-cmp-cm
|
||||
app.kubernetes.io/part-of: argocd
|
||||
name: argocd-cmp-cm
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: argocd-gpg-keys-cm
|
||||
@@ -3057,6 +3065,19 @@ spec:
|
||||
name: tmp
|
||||
- mountPath: /helm-working-dir
|
||||
name: helm-working-dir
|
||||
- mountPath: /home/argocd/cmp-server/plugins
|
||||
name: plugins
|
||||
initContainers:
|
||||
- command:
|
||||
- cp
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /var/run/argocd/argocd-cmp-server
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
name: copyutil
|
||||
volumeMounts:
|
||||
- mountPath: /var/run/argocd
|
||||
name: var-files
|
||||
volumes:
|
||||
- configMap:
|
||||
name: argocd-ssh-known-hosts-cm
|
||||
@@ -3084,6 +3105,13 @@ spec:
|
||||
path: ca.crt
|
||||
optional: true
|
||||
secretName: argocd-repo-server-tls
|
||||
- emptyDir: {}
|
||||
name: var-files
|
||||
- emptyDir: {}
|
||||
name: plugins
|
||||
- configMap:
|
||||
name: argocd-cmp-cm
|
||||
name: config-files
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
|
||||
@@ -2916,6 +2916,14 @@ metadata:
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: argocd-cmp-cm
|
||||
app.kubernetes.io/part-of: argocd
|
||||
name: argocd-cmp-cm
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: argocd-gpg-keys-cm
|
||||
@@ -3965,6 +3973,19 @@ spec:
|
||||
name: tmp
|
||||
- mountPath: /helm-working-dir
|
||||
name: helm-working-dir
|
||||
- mountPath: /home/argocd/cmp-server/plugins
|
||||
name: plugins
|
||||
initContainers:
|
||||
- command:
|
||||
- cp
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /var/run/argocd/argocd-cmp-server
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
name: copyutil
|
||||
volumeMounts:
|
||||
- mountPath: /var/run/argocd
|
||||
name: var-files
|
||||
volumes:
|
||||
- configMap:
|
||||
name: argocd-ssh-known-hosts-cm
|
||||
@@ -3992,6 +4013,13 @@ spec:
|
||||
path: ca.crt
|
||||
optional: true
|
||||
secretName: argocd-repo-server-tls
|
||||
- emptyDir: {}
|
||||
name: var-files
|
||||
- emptyDir: {}
|
||||
name: plugins
|
||||
- configMap:
|
||||
name: argocd-cmp-cm
|
||||
name: config-files
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
|
||||
@@ -275,6 +275,14 @@ metadata:
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: argocd-cmp-cm
|
||||
app.kubernetes.io/part-of: argocd
|
||||
name: argocd-cmp-cm
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: argocd-gpg-keys-cm
|
||||
@@ -1324,6 +1332,19 @@ spec:
|
||||
name: tmp
|
||||
- mountPath: /helm-working-dir
|
||||
name: helm-working-dir
|
||||
- mountPath: /home/argocd/cmp-server/plugins
|
||||
name: plugins
|
||||
initContainers:
|
||||
- command:
|
||||
- cp
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /var/run/argocd/argocd-cmp-server
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
name: copyutil
|
||||
volumeMounts:
|
||||
- mountPath: /var/run/argocd
|
||||
name: var-files
|
||||
volumes:
|
||||
- configMap:
|
||||
name: argocd-ssh-known-hosts-cm
|
||||
@@ -1351,6 +1372,13 @@ spec:
|
||||
path: ca.crt
|
||||
optional: true
|
||||
secretName: argocd-repo-server-tls
|
||||
- emptyDir: {}
|
||||
name: var-files
|
||||
- emptyDir: {}
|
||||
name: plugins
|
||||
- configMap:
|
||||
name: argocd-cmp-cm
|
||||
name: config-files
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
|
||||
@@ -2859,6 +2859,14 @@ metadata:
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: argocd-cmp-cm
|
||||
app.kubernetes.io/part-of: argocd
|
||||
name: argocd-cmp-cm
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: argocd-gpg-keys-cm
|
||||
@@ -3299,6 +3307,19 @@ spec:
|
||||
name: tmp
|
||||
- mountPath: /helm-working-dir
|
||||
name: helm-working-dir
|
||||
- mountPath: /home/argocd/cmp-server/plugins
|
||||
name: plugins
|
||||
initContainers:
|
||||
- command:
|
||||
- cp
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /var/run/argocd/argocd-cmp-server
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
name: copyutil
|
||||
volumeMounts:
|
||||
- mountPath: /var/run/argocd
|
||||
name: var-files
|
||||
volumes:
|
||||
- configMap:
|
||||
name: argocd-ssh-known-hosts-cm
|
||||
@@ -3326,6 +3347,13 @@ spec:
|
||||
path: ca.crt
|
||||
optional: true
|
||||
secretName: argocd-repo-server-tls
|
||||
- emptyDir: {}
|
||||
name: var-files
|
||||
- emptyDir: {}
|
||||
name: plugins
|
||||
- configMap:
|
||||
name: argocd-cmp-cm
|
||||
name: config-files
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
|
||||
@@ -218,6 +218,14 @@ metadata:
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: argocd-cmp-cm
|
||||
app.kubernetes.io/part-of: argocd
|
||||
name: argocd-cmp-cm
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: argocd-gpg-keys-cm
|
||||
@@ -658,6 +666,19 @@ spec:
|
||||
name: tmp
|
||||
- mountPath: /helm-working-dir
|
||||
name: helm-working-dir
|
||||
- mountPath: /home/argocd/cmp-server/plugins
|
||||
name: plugins
|
||||
initContainers:
|
||||
- command:
|
||||
- cp
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /var/run/argocd/argocd-cmp-server
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
name: copyutil
|
||||
volumeMounts:
|
||||
- mountPath: /var/run/argocd
|
||||
name: var-files
|
||||
volumes:
|
||||
- configMap:
|
||||
name: argocd-ssh-known-hosts-cm
|
||||
@@ -685,6 +706,13 @@ spec:
|
||||
path: ca.crt
|
||||
optional: true
|
||||
secretName: argocd-repo-server-tls
|
||||
- emptyDir: {}
|
||||
name: var-files
|
||||
- emptyDir: {}
|
||||
name: plugins
|
||||
- configMap:
|
||||
name: argocd-cmp-cm
|
||||
name: config-files
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
|
||||
@@ -34,6 +34,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
pluginclient "github.com/argoproj/argo-cd/v2/cmpserver/apiclient"
|
||||
"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"
|
||||
@@ -56,6 +57,7 @@ import (
|
||||
|
||||
const (
|
||||
cachedManifestGenerationPrefix = "Manifest generation error (cached)"
|
||||
pluginNotSupported = "config management plugin not supported."
|
||||
helmDepUpMarkerFile = ".argocd-helm-dep-up"
|
||||
allowConcurrencyFile = ".argocd-allow-concurrency"
|
||||
repoSourceFile = ".argocd-source.yaml"
|
||||
@@ -746,7 +748,21 @@ func GenerateManifests(appPath, repoRoot, revision string, q *apiclient.Manifest
|
||||
k := kustomize.NewKustomizeApp(appPath, q.Repo.GetGitCreds(), repoURL, kustomizeBinary)
|
||||
targetObjs, _, err = k.Build(q.ApplicationSource.Kustomize, q.KustomizeOptions)
|
||||
case v1alpha1.ApplicationSourceTypePlugin:
|
||||
targetObjs, err = runConfigManagementPlugin(appPath, repoRoot, env, q, q.Repo.GetGitCreds())
|
||||
if q.ApplicationSource.Plugin != nil && q.ApplicationSource.Plugin.Name != "" {
|
||||
targetObjs, err = runConfigManagementPlugin(appPath, repoRoot, env, q, q.Repo.GetGitCreds())
|
||||
} else {
|
||||
var cmpManifests []string
|
||||
var cmpErr error
|
||||
cmpManifests, cmpErr = runConfigManagementPluginSidecars(appPath, repoRoot, env, q, q.Repo.GetGitCreds())
|
||||
if cmpErr == nil {
|
||||
return &apiclient.ManifestResponse{
|
||||
Manifests: cmpManifests,
|
||||
SourceType: string(appSourceType),
|
||||
}, nil
|
||||
} else {
|
||||
err = fmt.Errorf("plugin sidecar failed. %s", cmpErr.Error())
|
||||
}
|
||||
}
|
||||
case v1alpha1.ApplicationSourceTypeDirectory:
|
||||
var directory *v1alpha1.ApplicationSourceDirectory
|
||||
if directory = q.ApplicationSource.Directory; directory == nil {
|
||||
@@ -1107,7 +1123,7 @@ func findPlugin(plugins []*v1alpha1.ConfigManagementPlugin, name string) *v1alph
|
||||
func runConfigManagementPlugin(appPath, repoRoot string, envVars *v1alpha1.Env, q *apiclient.ManifestRequest, creds git.Creds) ([]*unstructured.Unstructured, error) {
|
||||
plugin := findPlugin(q.Plugins, q.ApplicationSource.Plugin.Name)
|
||||
if plugin == nil {
|
||||
return nil, fmt.Errorf("config management plugin with name '%s' is not supported", q.ApplicationSource.Plugin.Name)
|
||||
return nil, fmt.Errorf(pluginNotSupported+" plugin name %s", q.ApplicationSource.Plugin.Name)
|
||||
}
|
||||
|
||||
// Plugins can request to lock the complete repository when they need to
|
||||
@@ -1123,6 +1139,25 @@ func runConfigManagementPlugin(appPath, repoRoot string, envVars *v1alpha1.Env,
|
||||
}
|
||||
}
|
||||
|
||||
env, err := getPluginEnvs(envVars, q, creds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if plugin.Init != nil {
|
||||
_, err := runCommand(*plugin.Init, appPath, env)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
out, err := runCommand(plugin.Generate, appPath, env)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return kube.SplitYAML([]byte(out))
|
||||
}
|
||||
|
||||
func getPluginEnvs(envVars *v1alpha1.Env, q *apiclient.ManifestRequest, creds git.Creds) ([]string, error) {
|
||||
env := append(os.Environ(), envVars.Environ()...)
|
||||
if creds != nil {
|
||||
closer, environ, err := creds.Environ()
|
||||
@@ -1144,23 +1179,61 @@ func runConfigManagementPlugin(appPath, repoRoot string, envVars *v1alpha1.Env,
|
||||
parsedEnv[i] = parsedVar
|
||||
}
|
||||
|
||||
pluginEnv := q.ApplicationSource.Plugin.Env
|
||||
for i, j := range pluginEnv {
|
||||
pluginEnv[i].Value = parsedEnv.Envsubst(j.Value)
|
||||
}
|
||||
env = append(env, pluginEnv.Environ()...)
|
||||
|
||||
if plugin.Init != nil {
|
||||
_, err := runCommand(*plugin.Init, appPath, env)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if q.ApplicationSource.Plugin != nil {
|
||||
pluginEnv := q.ApplicationSource.Plugin.Env
|
||||
for i, j := range pluginEnv {
|
||||
pluginEnv[i].Value = parsedEnv.Envsubst(j.Value)
|
||||
}
|
||||
env = append(env, pluginEnv.Environ()...)
|
||||
}
|
||||
out, err := runCommand(plugin.Generate, appPath, env)
|
||||
return env, nil
|
||||
}
|
||||
func runConfigManagementPluginSidecars(appPath, repoPath string, envVars *v1alpha1.Env, q *apiclient.ManifestRequest, creds git.Creds) ([]string, error) {
|
||||
// detect config management plugin server (sidecar)
|
||||
conn, cmpClient, err := discovery.DetectConfigManagementPlugin(appPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return kube.SplitYAML([]byte(out))
|
||||
defer io.Close(conn)
|
||||
|
||||
config, err := cmpClient.GetPluginConfig(context.Background(), &pluginclient.ConfigRequest{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if config.LockRepo {
|
||||
manifestGenerateLock.Lock(repoPath)
|
||||
defer manifestGenerateLock.Unlock(repoPath)
|
||||
} else if !config.AllowConcurrency {
|
||||
manifestGenerateLock.Lock(appPath)
|
||||
defer manifestGenerateLock.Unlock(appPath)
|
||||
}
|
||||
|
||||
// generate manifests using commands provided in plugin config file in detected cmp-server sidecar
|
||||
env, err := getPluginEnvs(envVars, q, creds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cmpManifests, err := cmpClient.GenerateManifest(context.Background(), &pluginclient.ManifestRequest{
|
||||
AppPath: appPath,
|
||||
RepoPath: repoPath,
|
||||
Env: toEnvEntry(env),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cmpManifests.Manifests, nil
|
||||
}
|
||||
|
||||
func toEnvEntry(envVars []string) []*pluginclient.EnvEntry {
|
||||
envEntry := make([]*pluginclient.EnvEntry, 0)
|
||||
for _, env := range envVars {
|
||||
pair := strings.Split(env, "=")
|
||||
if len(pair) != 2 {
|
||||
continue
|
||||
}
|
||||
envEntry = append(envEntry, &pluginclient.EnvEntry{Name: pair[0], Value: pair[1]})
|
||||
}
|
||||
return envEntry
|
||||
}
|
||||
|
||||
func (s *Service) GetAppDetails(ctx context.Context, q *apiclient.RepoServerAppDetailsQuery) (*apiclient.RepoAppDetailsResponse, error) {
|
||||
|
||||
@@ -132,7 +132,7 @@ func TestGenerateYamlManifestInDir(t *testing.T) {
|
||||
q := apiclient.ManifestRequest{Repo: &argoappv1.Repository{}, ApplicationSource: &src}
|
||||
|
||||
// update this value if we add/remove manifests
|
||||
const countOfManifests = 34
|
||||
const countOfManifests = 35
|
||||
|
||||
res1, err := service.GenerateManifest(context.Background(), &q)
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ controller: sh -c "FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_BINARY_
|
||||
api-server: sh -c "FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_BINARY_NAME=argocd-server go run ./cmd/main.go --loglevel debug --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379} --disable-auth=${ARGOCD_E2E_DISABLE_AUTH:-'true'} --insecure --dex-server http://localhost:${ARGOCD_E2E_DEX_PORT:-5556} --repo-server localhost:${ARGOCD_E2E_REPOSERVER_PORT:-8081} --port ${ARGOCD_E2E_APISERVER_PORT:-8080}"
|
||||
dex: sh -c "test $ARGOCD_IN_CI = true && exit 0; ARGOCD_BINARY_NAME=argocd-dex go run github.com/argoproj/argo-cd/cmd gendexcfg -o `pwd`/dist/dex.yaml && docker run --rm -p ${ARGOCD_E2E_DEX_PORT:-5556}:${ARGOCD_E2E_DEX_PORT:-5556} -v `pwd`/dist/dex.yaml:/dex.yaml ghcr.io/dexidp/dex:v2.30.0 serve /dex.yaml"
|
||||
redis: sh -c "/usr/local/bin/redis-server --save "" --appendonly no --port ${ARGOCD_E2E_REDIS_PORT:-6379}"
|
||||
repo-server: sh -c "FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_GNUPGHOME=${ARGOCD_GNUPGHOME:-/tmp/argocd-local/gpg/keys} ARGOCD_GPG_DATA_PATH=${ARGOCD_GPG_DATA_PATH:-/tmp/argocd-local/gpg/source} ARGOCD_BINARY_NAME=argocd-repo-server go run ./cmd/main.go --loglevel debug --port ${ARGOCD_E2E_REPOSERVER_PORT:-8081} --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379}"
|
||||
repo-server: sh -c "FORCE_LOG_COLORS=1 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_GNUPGHOME=${ARGOCD_GNUPGHOME:-/tmp/argocd-local/gpg/keys} ARGOCD_PLUGINSOCKFILEPATH=${ARGOCD_PLUGINSOCKFILEPATH:-/tmp/argo-e2e/app/config/plugin} ARGOCD_GPG_DATA_PATH=${ARGOCD_GPG_DATA_PATH:-/tmp/argocd-local/gpg/source} ARGOCD_BINARY_NAME=argocd-repo-server go run ./cmd/main.go --loglevel debug --port ${ARGOCD_E2E_REPOSERVER_PORT:-8081} --redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379}"
|
||||
ui: sh -c "test $ARGOCD_IN_CI = true && exit 0; cd ui && ARGOCD_E2E_YARN_HOST=0.0.0.0 ${ARGOCD_E2E_YARN_CMD:-yarn} start"
|
||||
reaper: ./test/container/reaper.sh
|
||||
sshd: sudo sh -c "test $ARGOCD_E2E_TEST = true && /usr/sbin/sshd -p 2222 -D -e"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package e2e
|
||||
|
||||
import (
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -194,3 +195,97 @@ func TestCustomToolSyncAndDiffLocal(t *testing.T) {
|
||||
FailOnErr(RunCli("app", "diff", app.Name, "--local", "testdata/guestbook"))
|
||||
})
|
||||
}
|
||||
func startCMPServer(configFile string) {
|
||||
pluginSockFilePath := TmpDir + PluginSockFilePath
|
||||
os.Setenv("ARGOCD_BINARY_NAME", "argocd-cmp-server")
|
||||
// ARGOCD_PLUGINSOCKFILEPATH should be set as the same value as repo server env var
|
||||
os.Setenv("ARGOCD_PLUGINSOCKFILEPATH", pluginSockFilePath)
|
||||
if _, err := os.Stat(pluginSockFilePath); os.IsNotExist(err) {
|
||||
// path/to/whatever does not exist
|
||||
err := os.Mkdir(pluginSockFilePath, 0700)
|
||||
CheckError(err)
|
||||
}
|
||||
FailOnErr(RunWithStdin("", "", "../../dist/argocd", "--config-dir-path", configFile))
|
||||
}
|
||||
|
||||
//Discover by fileName
|
||||
func TestCMPDiscoverWithFileName(t *testing.T) {
|
||||
pluginName := "cmp-fileName"
|
||||
Given(t).
|
||||
And(func() {
|
||||
go startCMPServer("./testdata/cmp-fileName")
|
||||
time.Sleep(1 * time.Second)
|
||||
os.Setenv("ARGOCD_BINARY_NAME", "argocd")
|
||||
}).
|
||||
Path(pluginName).
|
||||
When().
|
||||
Create().
|
||||
Sync().
|
||||
Then().
|
||||
Expect(OperationPhaseIs(OperationSucceeded)).
|
||||
Expect(SyncStatusIs(SyncStatusCodeSynced)).
|
||||
Expect(HealthIs(health.HealthStatusHealthy))
|
||||
}
|
||||
|
||||
//Discover by Find glob
|
||||
func TestCMPDiscoverWithFindGlob(t *testing.T) {
|
||||
Given(t).
|
||||
And(func() {
|
||||
go startCMPServer("./testdata/cmp-find-glob")
|
||||
time.Sleep(1 * time.Second)
|
||||
os.Setenv("ARGOCD_BINARY_NAME", "argocd")
|
||||
}).
|
||||
Path("guestbook").
|
||||
When().
|
||||
Create().
|
||||
Sync().
|
||||
Then().
|
||||
Expect(OperationPhaseIs(OperationSucceeded)).
|
||||
Expect(SyncStatusIs(SyncStatusCodeSynced)).
|
||||
Expect(HealthIs(health.HealthStatusHealthy))
|
||||
}
|
||||
|
||||
//Discover by Find command
|
||||
func TestCMPDiscoverWithFindCommandWithEnv(t *testing.T) {
|
||||
pluginName := "cmp-find-command"
|
||||
Given(t).
|
||||
And(func() {
|
||||
go startCMPServer("./testdata/cmp-find-command")
|
||||
time.Sleep(1 * time.Second)
|
||||
os.Setenv("ARGOCD_BINARY_NAME", "argocd")
|
||||
}).
|
||||
Path(pluginName).
|
||||
When().
|
||||
Create().
|
||||
Sync().
|
||||
Then().
|
||||
Expect(OperationPhaseIs(OperationSucceeded)).
|
||||
Expect(SyncStatusIs(SyncStatusCodeSynced)).
|
||||
Expect(HealthIs(health.HealthStatusHealthy)).
|
||||
And(func(app *Application) {
|
||||
time.Sleep(1 * time.Second)
|
||||
}).
|
||||
And(func(app *Application) {
|
||||
output, err := Run("", "kubectl", "-n", DeploymentNamespace(), "get", "cm", Name(), "-o", "jsonpath={.metadata.annotations.Bar}")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "baz", output)
|
||||
}).
|
||||
And(func(app *Application) {
|
||||
expectedKubeVersion := GetVersions().ServerVersion.Format("%s.%s")
|
||||
output, err := Run("", "kubectl", "-n", DeploymentNamespace(), "get", "cm", Name(), "-o", "jsonpath={.metadata.annotations.KubeVersion}")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, expectedKubeVersion, output)
|
||||
}).
|
||||
And(func(app *Application) {
|
||||
expectedApiVersion := GetApiResources()
|
||||
expectedApiVersionSlice := strings.Split(expectedApiVersion, ",")
|
||||
sort.Strings(expectedApiVersionSlice)
|
||||
|
||||
output, err := Run("", "kubectl", "-n", DeploymentNamespace(), "get", "cm", Name(), "-o", "jsonpath={.metadata.annotations.KubeApiVersion}")
|
||||
assert.NoError(t, err)
|
||||
outputSlice := strings.Split(output, ",")
|
||||
sort.Strings(outputSlice)
|
||||
|
||||
assert.EqualValues(t, expectedApiVersionSlice, outputSlice)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -54,6 +54,9 @@ const (
|
||||
GuestbookPath = "guestbook"
|
||||
|
||||
ProjectName = "argo-project"
|
||||
|
||||
// cmp plugin sock file path
|
||||
PluginSockFilePath = "/app/config/plugin"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -566,6 +569,8 @@ func EnsureCleanState(t *testing.T) {
|
||||
FailOnErr(Run("", "mkdir", "-p", TmpDir+"/app/config/gpg/source"))
|
||||
FailOnErr(Run("", "mkdir", "-p", TmpDir+"/app/config/gpg/keys"))
|
||||
FailOnErr(Run("", "chmod", "0700", TmpDir+"/app/config/gpg/keys"))
|
||||
FailOnErr(Run("", "mkdir", "-p", TmpDir+PluginSockFilePath))
|
||||
FailOnErr(Run("", "chmod", "0700", TmpDir+PluginSockFilePath))
|
||||
}
|
||||
|
||||
// set-up tmp repo, must have unique name
|
||||
|
||||
12
test/e2e/testdata/cmp-fileName/plugin.yaml
vendored
Normal file
12
test/e2e/testdata/cmp-fileName/plugin.yaml
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: ConfigManagementPlugin
|
||||
metadata:
|
||||
name: cmp-fileName
|
||||
spec:
|
||||
version: v1.0
|
||||
generate:
|
||||
command: [sh, -c, 'echo "{\"kind\": \"ConfigMap\", \"apiVersion\": \"v1\", \"metadata\": { \"name\": \"$ARGOCD_APP_NAME\", \"namespace\": \"$ARGOCD_APP_NAMESPACE\", \"annotations\": {\"Foo\": \"$FOO\", \"KubeVersion\": \"$KUBE_VERSION\", \"KubeApiVersion\": \"$KUBE_API_VERSIONS\",\"Bar\": \"baz\"}}}"']
|
||||
discover:
|
||||
fileName: "./subdir/s*.yaml"
|
||||
allowConcurrency: true
|
||||
lockRepo: false
|
||||
0
test/e2e/testdata/cmp-fileName/subdir/special.yaml
vendored
Normal file
0
test/e2e/testdata/cmp-fileName/subdir/special.yaml
vendored
Normal file
0
test/e2e/testdata/cmp-find-command/env.yaml
vendored
Normal file
0
test/e2e/testdata/cmp-find-command/env.yaml
vendored
Normal file
13
test/e2e/testdata/cmp-find-command/plugin.yaml
vendored
Normal file
13
test/e2e/testdata/cmp-find-command/plugin.yaml
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: ConfigManagementPlugin
|
||||
metadata:
|
||||
name: cmp-find-command
|
||||
spec:
|
||||
version: v1.0
|
||||
generate:
|
||||
command: [sh, -c, 'echo "{\"kind\": \"ConfigMap\", \"apiVersion\": \"v1\", \"metadata\": { \"name\": \"$ARGOCD_APP_NAME\", \"namespace\": \"$ARGOCD_APP_NAMESPACE\", \"annotations\": {\"KubeVersion\": \"$KUBE_VERSION\", \"KubeApiVersion\": \"$KUBE_API_VERSIONS\",\"Bar\": \"baz\"}}}"']
|
||||
discover:
|
||||
find:
|
||||
command: [sh, -c, find . -name env.yaml]
|
||||
allowConcurrency: true
|
||||
lockRepo: false
|
||||
16
test/e2e/testdata/cmp-find-glob/plugin.yaml
vendored
Normal file
16
test/e2e/testdata/cmp-find-glob/plugin.yaml
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: ConfigManagementPlugin
|
||||
metadata:
|
||||
name: cmp-find-glob
|
||||
spec:
|
||||
version: v1.0
|
||||
init:
|
||||
command: [kustomize, version]
|
||||
generate:
|
||||
command: [sh, -c, "kustomize build"]
|
||||
discover:
|
||||
find:
|
||||
command: [sh, -c, find . -name kustomization.yaml]
|
||||
glob: "**/kustomization.yaml"
|
||||
allowConcurrency: true
|
||||
lockRepo: false
|
||||
@@ -1,16 +1,36 @@
|
||||
package discovery
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
pluginclient "github.com/argoproj/argo-cd/v2/cmpserver/apiclient"
|
||||
"github.com/argoproj/argo-cd/v2/common"
|
||||
"github.com/argoproj/argo-cd/v2/util/io"
|
||||
"github.com/argoproj/argo-cd/v2/util/kustomize"
|
||||
)
|
||||
|
||||
func Discover(root string) (map[string]string, error) {
|
||||
apps := make(map[string]string)
|
||||
err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
|
||||
|
||||
// Check if it is CMP
|
||||
conn, _, err := DetectConfigManagementPlugin(root)
|
||||
if err == nil {
|
||||
// Found CMP
|
||||
io.Close(conn)
|
||||
|
||||
apps["."] = string(v1alpha1.ApplicationSourceTypePlugin)
|
||||
return apps, nil
|
||||
}
|
||||
|
||||
err = filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -23,13 +43,13 @@ func Discover(root string) (map[string]string, error) {
|
||||
}
|
||||
base := filepath.Base(path)
|
||||
if base == "params.libsonnet" && strings.HasSuffix(dir, "components") {
|
||||
apps[filepath.Dir(dir)] = "Ksonnet"
|
||||
apps[filepath.Dir(dir)] = string(v1alpha1.ApplicationSourceTypeKsonnet)
|
||||
}
|
||||
if strings.HasSuffix(base, "Chart.yaml") {
|
||||
apps[dir] = "Helm"
|
||||
apps[dir] = string(v1alpha1.ApplicationSourceTypeHelm)
|
||||
}
|
||||
if kustomize.IsKustomization(base) {
|
||||
apps[dir] = "Kustomize"
|
||||
apps[dir] = string(v1alpha1.ApplicationSourceTypeKustomize)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
@@ -47,3 +67,54 @@ func AppType(path string) (string, error) {
|
||||
}
|
||||
return "Directory", nil
|
||||
}
|
||||
|
||||
// 1. list all plugins in /plugins folder
|
||||
// 2. foreach plugin setup connection with respective cmp-server
|
||||
// 3. check isSupported(path)?
|
||||
// 4.a if no then close connection
|
||||
// 4.b if yes then return conn for detected plugin
|
||||
func DetectConfigManagementPlugin(appPath string) (io.Closer, pluginclient.ConfigManagementPluginServiceClient, error) {
|
||||
var conn io.Closer
|
||||
var cmpClient pluginclient.ConfigManagementPluginServiceClient
|
||||
|
||||
pluginSockFilePath := common.GetPluginSockFilePath()
|
||||
log.Debugf("pluginSockFilePath is: %s", pluginSockFilePath)
|
||||
|
||||
files, err := os.ReadDir(pluginSockFilePath)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var connFound bool
|
||||
for _, file := range files {
|
||||
if file.Type() == os.ModeSocket {
|
||||
address := fmt.Sprintf("%s/%s", strings.TrimRight(pluginSockFilePath, "/"), file.Name())
|
||||
cmpclientset := pluginclient.NewConfigManagementPluginClientSet(address, 5)
|
||||
|
||||
conn, cmpClient, err = cmpclientset.NewConfigManagementPluginClient()
|
||||
if err != nil {
|
||||
log.Errorf("error dialing to cmp-server for plugin %s, %v", file.Name(), err)
|
||||
continue
|
||||
}
|
||||
|
||||
resp, err := cmpClient.MatchRepository(context.Background(), &pluginclient.RepositoryRequest{Path: appPath})
|
||||
if err != nil {
|
||||
log.Errorf("repository %s is not the match because %v", appPath, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if !resp.IsSupported {
|
||||
log.Debugf("Reponse from socket file %s is not supported", file.Name())
|
||||
io.Close(conn)
|
||||
} else {
|
||||
connFound = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !connFound {
|
||||
return nil, nil, fmt.Errorf("Couldn't find cmp-server plugin supporting repository %s", appPath)
|
||||
}
|
||||
return conn, cmpClient, err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user