Compare commits

...

24 Commits

Author SHA1 Message Date
Alexander Matyushentsev
268df4364d Update manifests to v0.12.0-rc5 2019-03-18 23:49:42 -07:00
Alexander Matyushentsev
68bb7e2046 Issue #1290 - Fix concurrent read/write error in state cache (#1293) 2019-03-18 23:39:18 -07:00
Jesse Suen
4e921a279c Fix a goroutine leak in api-server application.PodLogs and application.Watch (#1292) 2019-03-18 21:51:33 -07:00
Alexander Matyushentsev
ff72b82bd6 Issue #1287 - Fix local diff of non-namespaced resources. Also handle duplicates in local diff (#1289) 2019-03-18 21:51:26 -07:00
Jesse Suen
17393c3e70 Fix isssue where argocd app set -p required repo privileges. (#1280)
Grant patch privileges to argocd-server
2019-03-18 14:42:18 -07:00
Alexander Matyushentsev
3ed3a44944 Issue #1070 - Handle duplicated resource definitions (#1284) 2019-03-18 14:41:53 -07:00
Jesse Suen
27922c8f83 Add golang prometheus metrics to controller and repo-server (#1281) 2019-03-18 14:41:13 -07:00
Jesse Suen
c35f35666f Git cloning via SSH was not verifying host public key (#1276) 2019-03-15 14:29:40 -07:00
Alexander Matyushentsev
6a61987d3d Rename Application observedAt to reconciledAt and use observedAt to notify about partial app refresh (#1270) 2019-03-14 16:42:59 -07:00
Alexander Matyushentsev
c68e4a5a56 Bug fix: set 'Version' field while saving application resources tree (#1268) 2019-03-14 15:53:21 -07:00
Alexander Matyushentsev
1525f8e051 Avoid doing full reconciliation unless application 'managed' resource has changed (#1267) 2019-03-14 15:01:38 -07:00
Jesse Suen
1c2b248d27 Support kustomize apps with remote bases in private repos in the same host (#1264) 2019-03-14 14:26:07 -07:00
Alexander Matyushentsev
22fe538645 Update manifests to v0.12.0-rc4 2019-03-12 14:27:35 -07:00
Alexander Matyushentsev
24d73e56f1 Issue #1252 - Application controller incorrectly build application objects tree (#1253) 2019-03-12 12:21:50 -07:00
Alexander Matyushentsev
8f93bdc2a5 Issue #1247 - Fix CRD creation/deletion handling (#1249) 2019-03-12 12:21:45 -07:00
Alex Collins
6d982ca397 Migrates from gometalinter to golangci-lint. Closes #1225 (#1226) 2019-03-12 12:21:34 -07:00
Jesse Suen
eff67bffad Replace git fetch implementation with git CLI (from go-git) (#1244) 2019-03-08 14:09:22 -08:00
Jesse Suen
2b781eea49 Bump version and manifests to v0.12.0-rc3 2019-03-06 16:53:14 -08:00
Jesse Suen
2f0dc20235 Fix nil pointer dereference in CompareAppState (#1234) 2019-03-06 13:46:52 -08:00
Jesse Suen
36dc50c121 Bump version and manifests to v0.12.0-rc2 2019-03-06 02:01:07 -08:00
Alexander Matyushentsev
f8f974e871 Issue #1231 - Deprecated resource kinds from 'extensions' groups are not reconciled correctly (#1232) 2019-03-06 01:59:33 -08:00
Alexander Matyushentsev
c4b474ae98 Issue #1229 - App creation failed for public repository (#1230) 2019-03-06 01:59:26 -08:00
Jesse Suen
8d98d6e058 Sort kustomize params in GetAppDetails 2019-03-05 15:45:12 -08:00
Jesse Suen
233708ecdd Set release to v0.12.0-rc1 2019-03-05 15:23:34 -08:00
74 changed files with 1520 additions and 1291 deletions

View File

@@ -44,7 +44,7 @@ spec:
repo: "{{workflow.parameters.repo}}"
revision: "{{workflow.parameters.revision}}"
container:
image: argoproj/argo-cd-ci-builder:v0.12.0
image: argoproj/argo-cd-ci-builder:v0.13.0
imagePullPolicy: Always
command: [bash, -c]
args: ["{{inputs.parameters.cmd}}"]
@@ -73,7 +73,7 @@ spec:
repo: "{{workflow.parameters.repo}}"
revision: "{{workflow.parameters.revision}}"
container:
image: argoproj/argo-cd-ci-builder:v0.12.0
image: argoproj/argo-cd-ci-builder:v0.13.0
imagePullPolicy: Always
command: [sh, -c]
args: ["until docker ps; do sleep 3; done && {{inputs.parameters.cmd}}"]

22
.golangci.yml Normal file
View File

@@ -0,0 +1,22 @@
run:
deadline: 8m
skip-files:
- ".*\\.pb\\.go"
skip-dirs:
- pkg/client
- vendor
linter-settings:
goimports:
local-prefixes: github.com/argoproj/argo-cd
linters:
enable:
- vet
- gofmt
- goimports
- deadcode
- varcheck
- structcheck
- ineffassign
- unconvert
- misspell
disable-all: true

View File

@@ -40,10 +40,8 @@ go get -u github.com/golang/protobuf/protoc-gen-go
go get -u github.com/go-swagger/go-swagger/cmd/swagger
go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway
go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger
go get -u gopkg.in/alecthomas/gometalinter.v2
go get -u github.com/golangci/golangci-lint/cmd/golangci-lint
go get -u github.com/mattn/goreman
gometalinter.v2 --install
```
## Building

View File

@@ -69,7 +69,7 @@ RUN curl -L -o /usr/local/bin/kustomize1 https://github.com/kubernetes-sigs/kust
kustomize1 version
ENV KUSTOMIZE_VERSION=2.0.2
ENV KUSTOMIZE_VERSION=2.0.3
RUN curl -L -o /usr/local/bin/kustomize https://github.com/kubernetes-sigs/kustomize/releases/download/v${KUSTOMIZE_VERSION}/kustomize_${KUSTOMIZE_VERSION}_linux_amd64 && \
chmod +x /usr/local/bin/kustomize && \
kustomize version
@@ -79,6 +79,17 @@ ENV AWS_IAM_AUTHENTICATOR_VERSION=0.4.0-alpha.1
RUN curl -L -o /usr/local/bin/aws-iam-authenticator https://github.com/kubernetes-sigs/aws-iam-authenticator/releases/download/${AWS_IAM_AUTHENTICATOR_VERSION}/aws-iam-authenticator_${AWS_IAM_AUTHENTICATOR_VERSION}_linux_amd64 && \
chmod +x /usr/local/bin/aws-iam-authenticator
# Install golangci-lint
RUN wget https://install.goreleaser.com/github.com/golangci/golangci-lint.sh && \
chmod +x ./golangci-lint.sh && \
./golangci-lint.sh -b $GOPATH/bin && \
golangci-lint linters
COPY .golangci.yml ${GOPATH}/src/dummy/.golangci.yml
RUN cd ${GOPATH}/src/dummy && \
touch dummy.go \
golangci-lint run
####################################################################################################
# Argo CD Base - used as the base for both the release and dev argocd images
@@ -94,6 +105,8 @@ RUN groupadd -g 999 argocd && \
apt-get clean && \
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
COPY hack/ssh_known_hosts /etc/ssh/ssh_known_hosts
COPY hack/git-ask-pass.sh /usr/local/bin/git-ask-pass.sh
COPY --from=builder /usr/local/bin/ks /usr/local/bin/ks
COPY --from=builder /usr/local/bin/helm /usr/local/bin/helm
COPY --from=builder /usr/local/bin/kubectl /usr/local/bin/kubectl

33
Gopkg.lock generated
View File

@@ -48,25 +48,6 @@
pruneopts = ""
revision = "09c41003ee1d5015b75f331e52215512e7145b8d"
[[projects]]
branch = "master"
digest = "1:a74730e052a45a3fab1d310fdef2ec17ae3d6af16228421e238320846f2aaec8"
name = "github.com/alecthomas/template"
packages = [
".",
"parse",
]
pruneopts = ""
revision = "a0175ee3bccc567396460bf5acd36800cb10c49c"
[[projects]]
branch = "master"
digest = "1:8483994d21404c8a1d489f6be756e25bfccd3b45d65821f25695577791a08e68"
name = "github.com/alecthomas/units"
packages = ["."]
pruneopts = ""
revision = "2efee857e7cfd4f3d0138cc3cbb1b4966962b93a"
[[projects]]
branch = "master"
digest = "1:0caf9208419fa5db5a0ca7112affaa9550c54291dda8e2abac0c0e76181c959e"
@@ -760,7 +741,6 @@
packages = [
"expfmt",
"internal/bitbucket.org/ww/goautoneg",
"log",
"model",
]
pruneopts = ""
@@ -992,8 +972,6 @@
packages = [
"unix",
"windows",
"windows/registry",
"windows/svc/eventlog",
]
pruneopts = ""
revision = "d0be0721c37eeb5299f245a996a483160fc36940"
@@ -1115,14 +1093,6 @@
revision = "8dea3dc473e90c8179e519d91302d0597c0ca1d1"
version = "v1.15.0"
[[projects]]
digest = "1:15d017551627c8bb091bde628215b2861bed128855343fdd570c62d08871f6e1"
name = "gopkg.in/alecthomas/kingpin.v2"
packages = ["."]
pruneopts = ""
revision = "947dcec5ba9c011838740e680966fd7087a71d0d"
version = "v2.2.6"
[[projects]]
digest = "1:bf7444e1e6a36e633f4f1624a67b9e4734cfb879c27ac0a2082ac16aff8462ac"
name = "gopkg.in/go-playground/webhooks.v3"
@@ -1378,6 +1348,7 @@
"discovery",
"discovery/fake",
"dynamic",
"dynamic/fake",
"informers/core/v1",
"informers/internalinterfaces",
"kubernetes",
@@ -1610,7 +1581,6 @@
"github.com/pkg/errors",
"github.com/prometheus/client_golang/prometheus",
"github.com/prometheus/client_golang/prometheus/promhttp",
"github.com/prometheus/common/log",
"github.com/sirupsen/logrus",
"github.com/skratchdot/open-golang/open",
"github.com/soheilhy/cmux",
@@ -1673,6 +1643,7 @@
"k8s.io/client-go/discovery",
"k8s.io/client-go/discovery/fake",
"k8s.io/client-go/dynamic",
"k8s.io/client-go/dynamic/fake",
"k8s.io/client-go/informers/core/v1",
"k8s.io/client-go/kubernetes",
"k8s.io/client-go/kubernetes/fake",

View File

@@ -119,10 +119,11 @@ endif
.PHONY: builder-image
builder-image:
docker build -t $(IMAGE_PREFIX)argo-cd-ci-builder:$(IMAGE_TAG) --target builder .
docker push $(IMAGE_PREFIX)argo-cd-ci-builder:$(IMAGE_TAG)
.PHONY: dep-ensure
dep-ensure:
dep ensure
dep ensure -no-vendor
.PHONY: format-code
format-code:
@@ -130,7 +131,7 @@ format-code:
.PHONY: lint
lint:
gometalinter.v2 --config gometalinter.json ./...
golangci-lint run
.PHONY: test
test:
@@ -159,4 +160,4 @@ release-precheck: manifests
@if [ "$(GIT_TAG)" != "v`cat VERSION`" ]; then echo 'VERSION does not match git tag'; exit 1; fi
.PHONY: release
release: release-precheck precheckin image release-cli
release: release-precheck pre-commit image release-cli

View File

@@ -1 +1 @@
0.12.0
0.12.0-rc5

View File

@@ -2543,12 +2543,18 @@
"operationState": {
"$ref": "#/definitions/v1alpha1OperationState"
},
"reconciledAt": {
"$ref": "#/definitions/v1Time"
},
"resources": {
"type": "array",
"items": {
"$ref": "#/definitions/v1alpha1ResourceStatus"
}
},
"sourceType": {
"type": "string"
},
"sync": {
"$ref": "#/definitions/v1alpha1SyncStatus"
}
@@ -2841,6 +2847,10 @@
"connectionState": {
"$ref": "#/definitions/v1alpha1ConnectionState"
},
"insecureIgnoreHostKey": {
"type": "boolean",
"format": "boolean"
},
"password": {
"type": "string"
},

View File

@@ -3,19 +3,21 @@ package main
import (
"fmt"
"net"
"net/http"
"os"
"time"
"github.com/prometheus/client_golang/prometheus/promhttp"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/argoproj/argo-cd"
argocd "github.com/argoproj/argo-cd"
"github.com/argoproj/argo-cd/common"
"github.com/argoproj/argo-cd/errors"
"github.com/argoproj/argo-cd/reposerver"
"github.com/argoproj/argo-cd/util/cache"
"github.com/argoproj/argo-cd/util/cli"
"github.com/argoproj/argo-cd/util/git"
"github.com/argoproj/argo-cd/util/ksonnet"
"github.com/argoproj/argo-cd/util/stats"
"github.com/argoproj/argo-cd/util/tls"
)
@@ -23,7 +25,6 @@ import (
const (
// CLIName is the name of the CLI
cliName = "argocd-repo-server"
port = 8081
)
func newCommand() *cobra.Command {
@@ -48,14 +49,13 @@ func newCommand() *cobra.Command {
server, err := reposerver.NewServer(git.NewFactory(), cache, tlsConfigCustomizer, parallelismLimit)
errors.CheckError(err)
grpc := server.CreateGRPC()
listener, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
listener, err := net.Listen("tcp", fmt.Sprintf(":%d", common.PortRepoServer))
errors.CheckError(err)
ksVers, err := ksonnet.KsonnetVersion()
errors.CheckError(err)
http.Handle("/metrics", promhttp.Handler())
go func() { errors.CheckError(http.ListenAndServe(fmt.Sprintf(":%d", common.PortRepoServerMetrics), nil)) }()
log.Infof("argocd-repo-server %s serving on %s", argocd.GetVersion(), listener.Addr())
log.Infof("ksonnet version: %s", ksVers)
stats.RegisterStackDumper()
stats.StartStatsTicker(10 * time.Minute)
stats.RegisterHeapDumper("memprofile")

View File

@@ -81,7 +81,7 @@ func NewCommand() *cobra.Command {
ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
argocd := server.NewServer(ctx, argoCDOpts)
argocd.Run(ctx, 8080)
argocd.Run(ctx, common.PortAPIServer)
cancel()
}
},

View File

@@ -27,13 +27,13 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"github.com/argoproj/argo-cd/controller"
"github.com/argoproj/argo-cd/errors"
"github.com/argoproj/argo-cd/pkg/apiclient"
argocdclient "github.com/argoproj/argo-cd/pkg/apiclient"
argoappv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/reposerver/repository"
"github.com/argoproj/argo-cd/server/application"
apirepository "github.com/argoproj/argo-cd/server/repository"
"github.com/argoproj/argo-cd/server/settings"
"github.com/argoproj/argo-cd/util"
"github.com/argoproj/argo-cd/util/argo"
@@ -116,7 +116,7 @@ func NewApplicationCreateCommand(clientOpts *argocdclient.ClientOptions) *cobra.
},
}
setAppOptions(c.Flags(), &app, &appOpts)
setParameterOverrides(&app, argocdClient, appOpts.parameters)
setParameterOverrides(&app, appOpts.parameters)
}
if app.Name == "" {
c.HelpFunc()(c, args)
@@ -225,6 +225,7 @@ func NewApplicationGetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Com
func printAppSummaryTable(app *argoappv1.Application, appURL string) {
fmt.Printf(printOpFmtStr, "Name:", app.Name)
fmt.Printf(printOpFmtStr, "Project:", app.Spec.GetProject())
fmt.Printf(printOpFmtStr, "Server:", app.Spec.Destination.Server)
fmt.Printf(printOpFmtStr, "Namespace:", app.Spec.Destination.Namespace)
fmt.Printf(printOpFmtStr, "URL:", appURL)
@@ -353,7 +354,7 @@ func NewApplicationSetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Com
c.HelpFunc()(c, args)
os.Exit(1)
}
setParameterOverrides(app, argocdClient, appOpts.parameters)
setParameterOverrides(app, appOpts.parameters)
_, err = appIf.UpdateSpec(ctx, &application.ApplicationUpdateSpecRequest{
Name: &app.Name,
Spec: app.Spec,
@@ -611,6 +612,17 @@ func getLocalObjects(app *argoappv1.Application, local string, appLabelKey strin
return objs
}
type resourceInfoProvider struct {
namespacedByGk map[schema.GroupKind]bool
}
// Infer if obj is namespaced or not from corresponding live objects list. If corresponding live object has namespace then target object is also namespaced.
// If live object is missing then it does not matter if target is namespaced or not.
func (p *resourceInfoProvider) IsNamespaced(server string, obj *unstructured.Unstructured) (bool, error) {
key := kube.GetResourceKey(obj)
return p.namespacedByGk[key.GroupKind()], nil
}
func groupLocalObjs(localObs []*unstructured.Unstructured, liveObjs []*unstructured.Unstructured, appNamespace string) map[kube.ResourceKey]*unstructured.Unstructured {
namespacedByGk := make(map[schema.GroupKind]bool)
for i := range liveObjs {
@@ -619,22 +631,13 @@ func groupLocalObjs(localObs []*unstructured.Unstructured, liveObjs []*unstructu
namespacedByGk[schema.GroupKind{Group: key.Group, Kind: key.Kind}] = key.Namespace != ""
}
}
localObs, _, err := controller.DeduplicateTargetObjects("", appNamespace, localObs, &resourceInfoProvider{namespacedByGk: namespacedByGk})
errors.CheckError(err)
objByKey := make(map[kube.ResourceKey]*unstructured.Unstructured)
for i := range localObs {
obj := localObs[i]
gk := obj.GroupVersionKind().GroupKind()
// Infer if obj is namespaced or not from corresponding live objects list. If corresponding live object has namespace then target object is also namespaced.
// If live object is missing then it does not matter if target is namespaced or not.
namespace := obj.GetNamespace()
if !namespacedByGk[gk] {
namespace = ""
} else {
if namespace == "" {
namespace = appNamespace
}
}
if !hook.IsHook(obj) {
objByKey[kube.NewResourceKey(gk.Group, gk.Kind, namespace, obj.GetName())] = obj
objByKey[kube.GetResourceKey(obj)] = obj
}
}
return objByKey
@@ -684,9 +687,9 @@ func NewApplicationDiffCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
err := json.Unmarshal([]byte(res.LiveState), &live)
errors.CheckError(err)
key := kube.NewResourceKey(res.Group, res.Kind, res.Namespace, res.Name)
key := kube.GetResourceKey(live)
if local, ok := localObjs[key]; ok || live != nil {
if local != nil {
if local != nil && !kube.IsCRD(live) {
err = kube.SetAppInstanceLabel(local, argoSettings.AppLabelKey, appName)
errors.CheckError(err)
}
@@ -1290,21 +1293,30 @@ func waitOnApplicationStatus(acdClient apiclient.Client, appName string, timeout
// If the app is a ksonnet app, then parameters are expected to be in the form: component=param=value
// Otherwise, the app is assumed to be a helm app and is expected to be in the form:
// param=value
func setParameterOverrides(app *argoappv1.Application, argocdClient argocdclient.Client, parameters []string) {
func setParameterOverrides(app *argoappv1.Application, parameters []string) {
if len(parameters) == 0 {
return
}
conn, repoIf := argocdClient.NewRepoClientOrDie()
defer util.Close(conn)
var sourceType argoappv1.ApplicationSourceType
if st, _ := app.Spec.Source.ExplicitType(); st != nil {
sourceType = *st
} else if app.Status.SourceType != "" {
sourceType = app.Status.SourceType
} else {
// HACK: we don't know the source type, so make an educated guess based on the supplied
// parameter string. This code handles the corner case where app doesn't exist yet, and the
// command is something like: `argocd app create MYAPP -p foo=bar`
// This logic is not foolproof, but when ksonnet is deprecated, this will no longer matter
// since helm will remain as the only source type which has parameters.
if len(strings.SplitN(parameters[0], "=", 3)) == 3 {
sourceType = argoappv1.ApplicationSourceTypeKsonnet
} else if len(strings.SplitN(parameters[0], "=", 2)) == 2 {
sourceType = argoappv1.ApplicationSourceTypeHelm
}
}
appDetails, err := repoIf.GetAppDetails(context.Background(), &apirepository.RepoAppDetailsQuery{
Repo: app.Spec.Source.RepoURL,
Revision: app.Spec.Source.TargetRevision,
Path: app.Spec.Source.Path,
})
errors.CheckError(err)
if appDetails.Ksonnet != nil {
switch sourceType {
case argoappv1.ApplicationSourceTypeKsonnet:
if app.Spec.Source.Ksonnet == nil {
app.Spec.Source.Ksonnet = &argoappv1.ApplicationSourceKsonnet{}
}
@@ -1330,7 +1342,7 @@ func setParameterOverrides(app *argoappv1.Application, argocdClient argocdclient
app.Spec.Source.Ksonnet.Parameters = append(app.Spec.Source.Ksonnet.Parameters, newParam)
}
}
} else if appDetails.Helm != nil {
case argoappv1.ApplicationSourceTypeHelm:
if app.Spec.Source.Helm == nil {
app.Spec.Source.Helm = &argoappv1.ApplicationSourceHelm{}
}
@@ -1355,7 +1367,7 @@ func setParameterOverrides(app *argoappv1.Application, argocdClient argocdclient
app.Spec.Source.Helm.Parameters = append(app.Spec.Source.Helm.Parameters, newParam)
}
}
} else {
default:
log.Fatalf("Parameters can only be set against Ksonnet or Helm applications")
}
}

View File

@@ -15,7 +15,7 @@ import (
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"k8s.io/apimachinery/pkg/apis/meta/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/argoproj/argo-cd/errors"
argocdclient "github.com/argoproj/argo-cd/pkg/apiclient"

View File

@@ -39,9 +39,10 @@ func NewRepoCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
// NewRepoAddCommand returns a new instance of an `argocd repo add` command
func NewRepoAddCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var (
repo appsv1.Repository
upsert bool
sshPrivateKeyPath string
repo appsv1.Repository
upsert bool
sshPrivateKeyPath string
insecureIgnoreHostKey bool
)
var command = &cobra.Command{
Use: "add REPO",
@@ -59,12 +60,13 @@ func NewRepoAddCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
}
repo.SSHPrivateKey = string(keyData)
}
repo.InsecureIgnoreHostKey = insecureIgnoreHostKey
// First test the repo *without* username/password. This gives us a hint on whether this
// is a private repo.
// NOTE: it is important not to run git commands to test git credentials on the user's
// system since it may mess with their git credential store (e.g. osx keychain).
// See issue #315
err := git.TestRepo(repo.Repo, "", "", repo.SSHPrivateKey)
err := git.TestRepo(repo.Repo, "", "", repo.SSHPrivateKey, repo.InsecureIgnoreHostKey)
if err != nil {
if git.IsSSHURL(repo.Repo) {
// If we failed using git SSH credentials, then the repo is automatically bad
@@ -88,6 +90,7 @@ func NewRepoAddCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
command.Flags().StringVar(&repo.Username, "username", "", "username to the repository")
command.Flags().StringVar(&repo.Password, "password", "", "password to the repository")
command.Flags().StringVar(&sshPrivateKeyPath, "ssh-private-key-path", "", "path to the private ssh key (e.g. ~/.ssh/id_rsa)")
command.Flags().BoolVar(&insecureIgnoreHostKey, "insecure-ignore-host-key", false, "disables SSH strict host key checking")
command.Flags().BoolVar(&upsert, "upsert", false, "Override an existing repository with the same name even if the spec differs")
return command
}

View File

@@ -22,6 +22,7 @@ const (
PortRepoServer = 8081
PortArgoCDMetrics = 8082
PortArgoCDAPIServerMetrics = 8083
PortRepoServerMetrics = 8084
)
// Argo CD application related constants

View File

@@ -112,8 +112,8 @@ func NewApplicationController(
}
appInformer, appLister := ctrl.newApplicationInformerAndLister()
projInformer := v1alpha1.NewAppProjectInformer(applicationClientset, namespace, appResyncPeriod, cache.Indexers{})
stateCache := statecache.NewLiveStateCache(db, appInformer, ctrl.settings, kubectlCmd, func(appName string) {
ctrl.requestAppRefresh(appName)
stateCache := statecache.NewLiveStateCache(db, appInformer, ctrl.settings, kubectlCmd, func(appName string, fullRefresh bool) {
ctrl.requestAppRefresh(appName, fullRefresh)
ctrl.appRefreshQueue.Add(fmt.Sprintf("%s/%s", ctrl.namespace, appName))
})
appStateManager := NewAppStateManager(db, applicationClientset, repoClientset, namespace, kubectlCmd, ctrl.settings, stateCache, projInformer)
@@ -143,11 +143,11 @@ func (ctrl *ApplicationController) getApp(name string) (*appv1.Application, erro
}
func (ctrl *ApplicationController) setAppManagedResources(a *appv1.Application, comparisonResult *comparisonResult) error {
tree, err := ctrl.resourceTree(a, comparisonResult.managedResources)
managedResources, err := ctrl.managedResources(a, comparisonResult)
if err != nil {
return err
}
managedResources, err := ctrl.managedResources(a, comparisonResult)
tree, err := ctrl.resourceTree(a, managedResources)
if err != nil {
return err
}
@@ -158,20 +158,40 @@ func (ctrl *ApplicationController) setAppManagedResources(a *appv1.Application,
return ctrl.cache.SetAppManagedResources(a.Name, managedResources)
}
func (ctrl *ApplicationController) resourceTree(a *appv1.Application, resources []managedResource) ([]*appv1.ResourceNode, error) {
func (ctrl *ApplicationController) resourceTree(a *appv1.Application, managedResources []*appv1.ResourceDiff) ([]*appv1.ResourceNode, error) {
items := make([]*appv1.ResourceNode, 0)
for i := range resources {
managedResource := resources[i]
node := appv1.ResourceNode{
Name: managedResource.Name,
Version: managedResource.Version,
Kind: managedResource.Kind,
Group: managedResource.Group,
Namespace: managedResource.Namespace,
for i := range managedResources {
managedResource := managedResources[i]
var live = &unstructured.Unstructured{}
err := json.Unmarshal([]byte(managedResource.LiveState), &live)
if err != nil {
return nil, err
}
if managedResource.Live != nil {
node.ResourceVersion = managedResource.Live.GetResourceVersion()
children, err := ctrl.stateCache.GetChildren(a.Spec.Destination.Server, managedResource.Live)
var target = &unstructured.Unstructured{}
err = json.Unmarshal([]byte(managedResource.TargetState), &target)
if err != nil {
return nil, err
}
version := ""
resourceVersion := ""
if live != nil {
resourceVersion = live.GetResourceVersion()
version = live.GroupVersionKind().Version
} else if target != nil {
version = target.GroupVersionKind().Version
}
node := appv1.ResourceNode{
Version: version,
ResourceVersion: resourceVersion,
Name: managedResource.Name,
Kind: managedResource.Kind,
Group: managedResource.Group,
Namespace: managedResource.Namespace,
}
if live != nil {
children, err := ctrl.stateCache.GetChildren(a.Spec.Destination.Server, live)
if err != nil {
return nil, err
}
@@ -269,20 +289,20 @@ func (ctrl *ApplicationController) Run(ctx context.Context, statusProcessors int
<-ctx.Done()
}
func (ctrl *ApplicationController) requestAppRefresh(appName string) {
func (ctrl *ApplicationController) requestAppRefresh(appName string, fullRefresh bool) {
ctrl.refreshRequestedAppsMutex.Lock()
defer ctrl.refreshRequestedAppsMutex.Unlock()
ctrl.refreshRequestedApps[appName] = true
ctrl.refreshRequestedApps[appName] = fullRefresh || ctrl.refreshRequestedApps[appName]
}
func (ctrl *ApplicationController) isRefreshRequested(appName string) bool {
func (ctrl *ApplicationController) isRefreshRequested(appName string) (bool, bool) {
ctrl.refreshRequestedAppsMutex.Lock()
defer ctrl.refreshRequestedAppsMutex.Unlock()
_, ok := ctrl.refreshRequestedApps[appName]
fullRefresh, ok := ctrl.refreshRequestedApps[appName]
if ok {
delete(ctrl.refreshRequestedApps, appName)
}
return ok
return ok, fullRefresh
}
func (ctrl *ApplicationController) processAppOperationQueueItem() (processNext bool) {
@@ -475,7 +495,7 @@ func (ctrl *ApplicationController) processRequestedAppOperation(app *appv1.Appli
if state.Phase.Completed() {
// if we just completed an operation, force a refresh so that UI will report up-to-date
// sync/health information
ctrl.requestAppRefresh(app.ObjectMeta.Name)
ctrl.requestAppRefresh(app.ObjectMeta.Name, true)
}
}
@@ -566,19 +586,39 @@ func (ctrl *ApplicationController) processAppRefreshQueueItem() (processNext boo
log.Warnf("Key '%s' in index is not an application", appKey)
return
}
needRefresh, refreshType := ctrl.needRefreshAppStatus(origApp, ctrl.statusRefreshTimeout)
needRefresh, refreshType, fullRefresh := ctrl.needRefreshAppStatus(origApp, ctrl.statusRefreshTimeout)
if !needRefresh {
return
}
startTime := time.Now()
defer func() {
reconcileDuration := time.Now().Sub(startTime)
ctrl.metricsServer.IncReconcile(origApp, reconcileDuration)
logCtx := log.WithFields(log.Fields{"application": origApp.Name, "time_ms": reconcileDuration.Seconds() * 1e3})
logCtx := log.WithFields(log.Fields{"application": origApp.Name, "time_ms": reconcileDuration.Seconds() * 1e3, "full": fullRefresh})
logCtx.Info("Reconciliation completed")
}()
app := origApp.DeepCopy()
if !fullRefresh {
logCtx := log.WithFields(log.Fields{"application": app.Name})
if managedResources, err := ctrl.cache.GetAppManagedResources(app.Name); err != nil {
logCtx.Warnf("Failed to get cached managed resources for tree reconciliation, fallback to full reconciliation")
} else {
if tree, err := ctrl.resourceTree(app, managedResources); err != nil {
app.Status.Conditions = []appv1.ApplicationCondition{{Type: appv1.ApplicationConditionComparisonError, Message: err.Error()}}
} else {
if err = ctrl.cache.SetAppResourcesTree(app.Name, tree); err != nil {
logCtx.Errorf("Failed to cache resources tree: %v", err)
return
}
}
app.Status.ObservedAt = metav1.Now()
ctrl.persistAppStatus(origApp, &app.Status)
return
}
}
conditions, hasErrors := ctrl.refreshAppConditions(app)
if hasErrors {
@@ -606,26 +646,32 @@ func (ctrl *ApplicationController) processAppRefreshQueueItem() (processNext boo
conditions = append(conditions, *syncErrCond)
}
app.Status.ObservedAt = compareResult.observedAt
app.Status.ObservedAt = compareResult.reconciledAt
app.Status.ReconciledAt = compareResult.reconciledAt
app.Status.Sync = *compareResult.syncStatus
app.Status.Health = *compareResult.healthStatus
app.Status.Resources = compareResult.resources
app.Status.Conditions = conditions
app.Status.SourceType = compareResult.appSourceType
ctrl.persistAppStatus(origApp, &app.Status)
return
}
// needRefreshAppStatus answers if application status needs to be refreshed.
// Returns true if application never been compared, has changed or comparison result has expired.
func (ctrl *ApplicationController) needRefreshAppStatus(app *appv1.Application, statusRefreshTimeout time.Duration) (bool, appv1.RefreshType) {
// Additionally returns whether full refresh was requested or not.
// If full refresh is requested then target and live state should be reconciled, else only live state tree should be updated.
func (ctrl *ApplicationController) needRefreshAppStatus(app *appv1.Application, statusRefreshTimeout time.Duration) (bool, appv1.RefreshType, bool) {
logCtx := log.WithFields(log.Fields{"application": app.Name})
var reason string
fullRefresh := true
refreshType := appv1.RefreshTypeNormal
expired := app.Status.ObservedAt.Add(statusRefreshTimeout).Before(time.Now().UTC())
expired := app.Status.ReconciledAt.Add(statusRefreshTimeout).Before(time.Now().UTC())
if requestedType, ok := app.IsRefreshRequested(); ok {
refreshType = requestedType
reason = fmt.Sprintf("%s refresh requested", refreshType)
} else if ctrl.isRefreshRequested(app.Name) {
} else if requested, full := ctrl.isRefreshRequested(app.Name); requested {
fullRefresh = full
reason = fmt.Sprintf("controller refresh requested")
} else if app.Status.Sync.Status == appv1.SyncStatusCodeUnknown && expired {
reason = "comparison status unknown"
@@ -634,13 +680,13 @@ func (ctrl *ApplicationController) needRefreshAppStatus(app *appv1.Application,
} else if !app.Spec.Destination.Equals(app.Status.Sync.ComparedTo.Destination) {
reason = "spec.destination differs"
} else if expired {
reason = fmt.Sprintf("comparison expired. observedAt: %v, expiry: %v", app.Status.ObservedAt, statusRefreshTimeout)
reason = fmt.Sprintf("comparison expired. reconciledAt: %v, expiry: %v", app.Status.ReconciledAt, statusRefreshTimeout)
}
if reason != "" {
logCtx.Infof("Refreshing app status (%s)", reason)
return true, refreshType
return true, refreshType, fullRefresh
}
return false, refreshType
return false, refreshType, fullRefresh
}
func (ctrl *ApplicationController) refreshAppConditions(app *appv1.Application) ([]appv1.ApplicationCondition, bool) {
@@ -659,7 +705,7 @@ func (ctrl *ApplicationController) refreshAppConditions(app *appv1.Application)
})
}
} else {
specConditions, err := argo.GetSpecErrors(context.Background(), &app.Spec, proj, ctrl.repoClientset, ctrl.db)
specConditions, _, err := argo.GetSpecErrors(context.Background(), &app.Spec, proj, ctrl.repoClientset, ctrl.db)
if err != nil {
conditions = append(conditions, appv1.ApplicationCondition{
Type: appv1.ApplicationConditionUnknownError,
@@ -672,11 +718,12 @@ func (ctrl *ApplicationController) refreshAppConditions(app *appv1.Application)
// List of condition types which have to be reevaluated by controller; all remaining conditions should stay as is.
reevaluateTypes := map[appv1.ApplicationConditionType]bool{
appv1.ApplicationConditionInvalidSpecError: true,
appv1.ApplicationConditionUnknownError: true,
appv1.ApplicationConditionComparisonError: true,
appv1.ApplicationConditionSharedResourceWarning: true,
appv1.ApplicationConditionSyncError: true,
appv1.ApplicationConditionInvalidSpecError: true,
appv1.ApplicationConditionUnknownError: true,
appv1.ApplicationConditionComparisonError: true,
appv1.ApplicationConditionSharedResourceWarning: true,
appv1.ApplicationConditionSyncError: true,
appv1.ApplicationConditionRepeatedResourceWarning: true,
}
appConditions := make([]appv1.ApplicationCondition, 0)
for i := 0; i < len(app.Status.Conditions); i++ {
@@ -858,7 +905,7 @@ func (ctrl *ApplicationController) newApplicationInformerAndLister() (cache.Shar
if oldOK && newOK {
if toggledAutomatedSync(oldApp, newApp) {
log.WithField("application", newApp.Name).Info("Enabled automated sync")
ctrl.requestAppRefresh(newApp.Name)
ctrl.requestAppRefresh(newApp.Name, true)
}
}
ctrl.appRefreshQueue.Add(key)

View File

@@ -2,7 +2,6 @@ package cache
import (
"context"
"fmt"
"sync"
log "github.com/sirupsen/logrus"
@@ -19,7 +18,7 @@ import (
)
type LiveStateCache interface {
IsNamespaced(server string, gvk schema.GroupVersionKind) (bool, error)
IsNamespaced(server string, obj *unstructured.Unstructured) (bool, error)
// Returns child nodes for a given k8s resource
GetChildren(server string, obj *unstructured.Unstructured) ([]appv1.ResourceNode, error)
// Returns state of live nodes which correspond for target nodes of specified application.
@@ -43,7 +42,7 @@ func GetTargetObjKey(a *appv1.Application, un *unstructured.Unstructured, isName
return key
}
func NewLiveStateCache(db db.ArgoDB, appInformer cache.SharedIndexInformer, settings *settings.ArgoCDSettings, kubectl kube.Kubectl, onAppUpdated func(appName string)) LiveStateCache {
func NewLiveStateCache(db db.ArgoDB, appInformer cache.SharedIndexInformer, settings *settings.ArgoCDSettings, kubectl kube.Kubectl, onAppUpdated func(appName string, fullRefresh bool)) LiveStateCache {
return &liveStateCache{
appInformer: appInformer,
db: db,
@@ -60,7 +59,7 @@ type liveStateCache struct {
clusters map[string]*clusterInfo
lock *sync.Mutex
appInformer cache.SharedIndexInformer
onAppUpdated func(appName string)
onAppUpdated func(appName string, fullRefresh bool)
kubectl kube.Kubectl
settings *settings.ArgoCDSettings
}
@@ -73,13 +72,6 @@ func (c *liveStateCache) processEvent(event watch.EventType, obj *unstructured.U
return info.processEvent(event, obj)
}
func (c *liveStateCache) removeCluster(server string) {
c.lock.Lock()
defer c.lock.Unlock()
delete(c.clusters, server)
log.Infof("Dropped cluster %s cache", server)
}
func (c *liveStateCache) getCluster(server string) (*clusterInfo, error) {
c.lock.Lock()
defer c.lock.Unlock()
@@ -90,7 +82,7 @@ func (c *liveStateCache) getCluster(server string) (*clusterInfo, error) {
return nil, err
}
info = &clusterInfo{
apis: make(map[schema.GroupKind]*gkInfo),
apisMeta: make(map[schema.GroupKind]*apiMeta),
lock: &sync.Mutex{},
nodes: make(map[kube.ResourceKey]*node),
nsIndex: make(map[string]map[kube.ResourceKey]*node),
@@ -140,12 +132,12 @@ func (c *liveStateCache) Delete(server string, obj *unstructured.Unstructured) e
return clusterInfo.delete(obj)
}
func (c *liveStateCache) IsNamespaced(server string, gvk schema.GroupVersionKind) (bool, error) {
func (c *liveStateCache) IsNamespaced(server string, obj *unstructured.Unstructured) (bool, error) {
clusterInfo, err := c.getSyncedCluster(server)
if err != nil {
return false, err
}
return clusterInfo.isNamespaced(gvk.GroupKind()), nil
return clusterInfo.isNamespaced(obj), nil
}
func (c *liveStateCache) GetChildren(server string, obj *unstructured.Unstructured) ([]appv1.ResourceNode, error) {
@@ -175,135 +167,29 @@ func isClusterHasApps(apps []interface{}, cluster *appv1.Cluster) bool {
// Run watches for resource changes annotated with application label on all registered clusters and schedule corresponding app refresh.
func (c *liveStateCache) Run(ctx context.Context) {
watchingClustersLock := sync.Mutex{}
watchingClusters := make(map[string]struct {
cancel context.CancelFunc
cluster *appv1.Cluster
})
util.RetryUntilSucceed(func() error {
clusterEventCallback := func(event *db.ClusterEvent) {
info, ok := watchingClusters[event.Cluster.Server]
hasApps := isClusterHasApps(c.appInformer.GetStore().List(), event.Cluster)
// cluster resources must be watched only if cluster has at least one app
if (event.Type == watch.Deleted || !hasApps) && ok {
info.cancel()
watchingClustersLock.Lock()
delete(watchingClusters, event.Cluster.Server)
watchingClustersLock.Unlock()
} else if event.Type != watch.Deleted && !ok && hasApps {
ctx, cancel := context.WithCancel(ctx)
watchingClustersLock.Lock()
watchingClusters[event.Cluster.Server] = struct {
cancel context.CancelFunc
cluster *appv1.Cluster
}{
cancel: func() {
c.removeCluster(event.Cluster.Server)
cancel()
},
cluster: event.Cluster,
c.lock.Lock()
defer c.lock.Unlock()
if cluster, ok := c.clusters[event.Cluster.Server]; ok {
if event.Type == watch.Deleted {
cluster.invalidate()
delete(c.clusters, event.Cluster.Server)
} else if event.Type == watch.Modified {
cluster.cluster = event.Cluster
cluster.invalidate()
}
watchingClustersLock.Unlock()
go c.watchClusterResources(ctx, *event.Cluster)
} else if event.Type == watch.Added && isClusterHasApps(c.appInformer.GetStore().List(), event.Cluster) {
go func() {
// warm up cache for cluster with apps
_, _ = c.getSyncedCluster(event.Cluster.Server)
}()
}
}
onAppModified := func(obj interface{}) {
if app, ok := obj.(*appv1.Application); ok {
var cluster *appv1.Cluster
info, infoOk := watchingClusters[app.Spec.Destination.Server]
if infoOk {
cluster = info.cluster
} else {
cluster, _ = c.db.GetCluster(ctx, app.Spec.Destination.Server)
}
if cluster != nil {
// trigger cluster event every time when app created/deleted to either start or stop watching resources
clusterEventCallback(&db.ClusterEvent{Cluster: cluster, Type: watch.Modified})
}
}
}
c.appInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: onAppModified,
UpdateFunc: func(oldObj, newObj interface{}) {
oldApp, oldOk := oldObj.(*appv1.Application)
newApp, newOk := newObj.(*appv1.Application)
if oldOk && newOk {
if oldApp.Spec.Destination.Server != newApp.Spec.Destination.Server {
onAppModified(oldObj)
onAppModified(newApp)
}
}
},
DeleteFunc: onAppModified,
})
return c.db.WatchClusters(ctx, clusterEventCallback)
}, "watch clusters", ctx, clusterRetryTimeout)
<-ctx.Done()
}
// watchClusterResources watches for resource changes annotated with application label on specified cluster and schedule corresponding app refresh.
func (c *liveStateCache) watchClusterResources(ctx context.Context, item appv1.Cluster) {
util.RetryUntilSucceed(func() (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("Recovered from panic: %v\n", r)
}
}()
config := item.RESTConfig()
ctx, cancel := context.WithCancel(ctx)
defer cancel()
ch, err := c.kubectl.WatchResources(ctx, config, c.settings, func(gk schema.GroupKind) (s string, e error) {
clusterInfo, err := c.getSyncedCluster(item.Server)
if err != nil {
return "", err
}
return clusterInfo.getResourceVersion(gk), nil
})
if err != nil {
return err
}
for event := range ch {
if event.WatchEvent != nil {
eventObj := event.WatchEvent.Object.(*unstructured.Unstructured)
if kube.IsCRD(eventObj) {
// restart if new CRD has been created after watch started
if event.WatchEvent.Type == watch.Added {
c.removeCluster(item.Server)
return fmt.Errorf("Restarting the watch because a new CRD %s was added", eventObj.GetName())
} else if event.WatchEvent.Type == watch.Deleted {
c.removeCluster(item.Server)
return fmt.Errorf("Restarting the watch because CRD %s was deleted", eventObj.GetName())
}
}
err = c.processEvent(event.WatchEvent.Type, eventObj, item.Server)
if err != nil {
log.Warnf("Failed to process event %s for obj %v: %v", event.WatchEvent.Type, event.WatchEvent.Object, err)
}
} else {
err = c.updateCache(item.Server, event.CacheRefresh.GVK.GroupKind(), event.CacheRefresh.ResourceVersion, event.CacheRefresh.Objects)
if err != nil {
log.Warnf("Failed to process event %s for obj %v: %v", event.WatchEvent.Type, event.WatchEvent.Object, err)
}
}
}
return fmt.Errorf("resource updates channel has closed")
}, fmt.Sprintf("watch app resources on %s", item.Server), ctx, clusterRetryTimeout)
}
func (c *liveStateCache) updateCache(server string, gk schema.GroupKind, resourceVersion string, objs []unstructured.Unstructured) error {
clusterInfo, err := c.getSyncedCluster(server)
if err != nil {
return err
}
clusterInfo.updateCache(gk, resourceVersion, objs)
return nil
}

View File

@@ -1,129 +0,0 @@
package cache
import (
"context"
"sync"
"testing"
"time"
"github.com/stretchr/testify/assert"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/apimachinery/pkg/watch"
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/util/kube"
"github.com/argoproj/argo-cd/util/kube/kubetest"
)
const (
pollInterval = 500 * time.Millisecond
)
func TestWatchClusterResourcesHandlesResourceEvents(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
events := make(chan kube.WatchEvent)
defer func() {
cancel()
close(events)
}()
pod := testPod.DeepCopy()
kubeMock := &kubetest.MockKubectlCmd{
Resources: []kube.ResourcesBatch{{
GVK: pod.GroupVersionKind(),
Objects: make([]unstructured.Unstructured, 0),
}},
Events: events,
}
server := "https://test"
clusterCache := newClusterExt(kubeMock)
cache := &liveStateCache{
clusters: map[string]*clusterInfo{server: clusterCache},
lock: &sync.Mutex{},
kubectl: kubeMock,
}
go cache.watchClusterResources(ctx, v1alpha1.Cluster{Server: server})
assert.False(t, clusterCache.synced())
events <- kube.WatchEvent{WatchEvent: &watch.Event{Object: pod, Type: watch.Added}}
err := wait.Poll(pollInterval, wait.ForeverTestTimeout, func() (bool, error) {
_, hasPod := clusterCache.nodes[kube.GetResourceKey(pod)]
return hasPod, nil
})
assert.Nil(t, err)
pod.SetResourceVersion("updated-resource-version")
events <- kube.WatchEvent{WatchEvent: &watch.Event{Object: pod, Type: watch.Modified}}
err = wait.Poll(pollInterval, wait.ForeverTestTimeout, func() (bool, error) {
updatedPodInfo, hasPod := clusterCache.nodes[kube.GetResourceKey(pod)]
return hasPod && updatedPodInfo.resourceVersion == "updated-resource-version", nil
})
assert.Nil(t, err)
events <- kube.WatchEvent{WatchEvent: &watch.Event{Object: pod, Type: watch.Deleted}}
err = wait.Poll(pollInterval, wait.ForeverTestTimeout, func() (bool, error) {
_, hasPod := clusterCache.nodes[kube.GetResourceKey(pod)]
return !hasPod, nil
})
assert.Nil(t, err)
}
func TestClusterCacheDroppedOnCreatedDeletedCRD(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
events := make(chan kube.WatchEvent)
defer func() {
cancel()
close(events)
}()
kubeMock := &kubetest.MockKubectlCmd{
Resources: []kube.ResourcesBatch{{
GVK: testCRD.GroupVersionKind(),
Objects: make([]unstructured.Unstructured, 0),
}},
Events: events,
}
server := "https://test"
clusterCache := newClusterExt(kubeMock)
cache := &liveStateCache{
clusters: map[string]*clusterInfo{server: clusterCache},
lock: &sync.Mutex{},
kubectl: kubeMock,
}
go cache.watchClusterResources(ctx, v1alpha1.Cluster{Server: server})
err := clusterCache.ensureSynced()
assert.Nil(t, err)
events <- kube.WatchEvent{WatchEvent: &watch.Event{Object: testCRD, Type: watch.Added}}
err = wait.Poll(pollInterval, wait.ForeverTestTimeout, func() (bool, error) {
cache.lock.Lock()
defer cache.lock.Unlock()
_, hasCache := cache.clusters[server]
return !hasCache, nil
})
assert.Nil(t, err)
cache.clusters[server] = clusterCache
events <- kube.WatchEvent{WatchEvent: &watch.Event{Object: testCRD, Type: watch.Deleted}}
err = wait.Poll(pollInterval, wait.ForeverTestTimeout, func() (bool, error) {
cache.lock.Lock()
defer cache.lock.Unlock()
_, hasCache := cache.clusters[server]
return !hasCache, nil
})
assert.Nil(t, err)
}

View File

@@ -1,6 +1,7 @@
package cache
import (
"context"
"fmt"
"runtime/debug"
"sync"
@@ -21,44 +22,38 @@ import (
)
const (
clusterSyncTimeout = 24 * time.Hour
clusterRetryTimeout = 10 * time.Second
clusterSyncTimeout = 24 * time.Hour
clusterRetryTimeout = 10 * time.Second
watchResourcesRetryTimeout = 1 * time.Second
)
type gkInfo struct {
resource metav1.APIResource
type apiMeta struct {
namespaced bool
resourceVersion string
watchCancel context.CancelFunc
}
type clusterInfo struct {
apis map[schema.GroupKind]*gkInfo
nodes map[kube.ResourceKey]*node
nsIndex map[string]map[kube.ResourceKey]*node
lock *sync.Mutex
onAppUpdated func(appName string)
syncLock *sync.Mutex
syncTime *time.Time
syncError error
apisMeta map[schema.GroupKind]*apiMeta
lock *sync.Mutex
nodes map[kube.ResourceKey]*node
nsIndex map[string]map[kube.ResourceKey]*node
onAppUpdated func(appName string, fullRefresh bool)
kubectl kube.Kubectl
cluster *appv1.Cluster
syncLock *sync.Mutex
syncTime *time.Time
syncError error
log *log.Entry
settings *settings.ArgoCDSettings
}
func (c *clusterInfo) getResourceVersion(gk schema.GroupKind) string {
func (c *clusterInfo) replaceResourceCache(gk schema.GroupKind, resourceVersion string, objs []unstructured.Unstructured) {
c.lock.Lock()
defer c.lock.Unlock()
info, ok := c.apis[gk]
if ok {
return info.resourceVersion
}
return ""
}
func (c *clusterInfo) updateCache(gk schema.GroupKind, resourceVersion string, objs []unstructured.Unstructured) {
c.lock.Lock()
defer c.lock.Unlock()
info, ok := c.apis[gk]
info, ok := c.apisMeta[gk]
if ok {
objByKind := make(map[kube.ResourceKey]*unstructured.Unstructured)
for i := range objs {
@@ -136,7 +131,13 @@ func (c *clusterInfo) removeNode(key kube.ResourceKey) {
}
func (c *clusterInfo) invalidate() {
c.syncLock.Lock()
defer c.syncLock.Unlock()
c.syncTime = nil
for i := range c.apisMeta {
c.apisMeta[i].watchCancel()
}
c.apisMeta = nil
}
func (c *clusterInfo) synced() bool {
@@ -149,38 +150,160 @@ func (c *clusterInfo) synced() bool {
return time.Now().Before(c.syncTime.Add(clusterSyncTimeout))
}
func (c *clusterInfo) sync() (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("Recovered from panic: %+v\n%s", r, debug.Stack())
}
}()
func (c *clusterInfo) stopWatching(gk schema.GroupKind) {
c.syncLock.Lock()
defer c.syncLock.Unlock()
if info, ok := c.apisMeta[gk]; ok {
info.watchCancel()
delete(c.apisMeta, gk)
c.replaceResourceCache(gk, "", []unstructured.Unstructured{})
log.Warnf("Stop watching %s not found on %s.", gk, c.cluster.Server)
}
}
c.log.Info("Start syncing cluster")
// startMissingWatches lists supported cluster resources and start watching for changes unless watch is already running
func (c *clusterInfo) startMissingWatches() error {
c.apis = make(map[schema.GroupKind]*gkInfo)
c.nodes = make(map[kube.ResourceKey]*node)
resources, err := c.kubectl.GetResources(c.cluster.RESTConfig(), c.settings, "")
apis, err := c.kubectl.GetAPIResources(c.cluster.RESTConfig(), c.settings)
if err != nil {
log.Errorf("Failed to sync cluster %s: %v", c.cluster.Server, err)
return err
}
appLabelKey := c.settings.GetAppInstanceLabelKey()
for res := range resources {
if res.Error != nil {
return res.Error
for i := range apis {
api := apis[i]
if _, ok := c.apisMeta[api.GroupKind]; !ok {
ctx, cancel := context.WithCancel(context.Background())
info := &apiMeta{namespaced: api.Meta.Namespaced, watchCancel: cancel}
c.apisMeta[api.GroupKind] = info
go c.watchEvents(ctx, api, info)
}
if _, ok := c.apis[res.GVK.GroupKind()]; !ok {
c.apis[res.GVK.GroupKind()] = &gkInfo{
resourceVersion: res.ListResourceVersion,
resource: res.ResourceInfo,
}
return nil
}
func runSynced(lock *sync.Mutex, action func() error) error {
lock.Lock()
defer lock.Unlock()
return action()
}
func (c *clusterInfo) watchEvents(ctx context.Context, api kube.APIResourceInfo, info *apiMeta) {
util.RetryUntilSucceed(func() (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("Recovered from panic: %+v\n%s", r, debug.Stack())
}
}()
err = runSynced(c.syncLock, func() error {
if info.resourceVersion == "" {
list, err := api.Interface.List(metav1.ListOptions{})
if err != nil {
return err
}
c.replaceResourceCache(api.GroupKind, list.GetResourceVersion(), list.Items)
}
return nil
})
if err != nil {
return err
}
w, err := api.Interface.Watch(metav1.ListOptions{ResourceVersion: info.resourceVersion})
if errors.IsNotFound(err) {
c.stopWatching(api.GroupKind)
return nil
}
err = runSynced(c.syncLock, func() error {
if errors.IsGone(err) {
info.resourceVersion = ""
log.Warnf("Resource version of %s on %s is too old.", api.GroupKind, c.cluster.Server)
}
return err
})
if err != nil {
return err
}
defer w.Stop()
for {
select {
case <-ctx.Done():
return nil
case event, ok := <-w.ResultChan():
if ok {
obj := event.Object.(*unstructured.Unstructured)
info.resourceVersion = obj.GetResourceVersion()
err = c.processEvent(event.Type, obj)
if err != nil {
log.Warnf("Failed to process event %s %s/%s/%s: %v", event.Type, obj.GroupVersionKind(), obj.GetNamespace(), obj.GetName(), err)
continue
}
if kube.IsCRD(obj) {
if event.Type == watch.Deleted {
group, groupOk, groupErr := unstructured.NestedString(obj.Object, "spec", "group")
kind, kindOk, kindErr := unstructured.NestedString(obj.Object, "spec", "names", "kind")
if groupOk && groupErr == nil && kindOk && kindErr == nil {
gk := schema.GroupKind{Group: group, Kind: kind}
c.stopWatching(gk)
}
} else {
err = runSynced(c.syncLock, func() error {
return c.startMissingWatches()
})
}
}
if err != nil {
log.Warnf("Failed to start missing watch: %v", err)
}
} else {
return fmt.Errorf("Watch %s on %s has closed", api.GroupKind, c.cluster.Server)
}
}
}
for i := range res.Objects {
c.setNode(createObjInfo(&res.Objects[i], appLabelKey))
}, fmt.Sprintf("watch %s on %s", api.GroupKind, c.cluster.Server), ctx, watchResourcesRetryTimeout)
}
func (c *clusterInfo) sync() (err error) {
c.log.Info("Start syncing cluster")
c.apisMeta = make(map[schema.GroupKind]*apiMeta)
c.nodes = make(map[kube.ResourceKey]*node)
apis, err := c.kubectl.GetAPIResources(c.cluster.RESTConfig(), c.settings)
if err != nil {
return err
}
lock := sync.Mutex{}
err = util.RunAllAsync(len(apis), func(i int) error {
api := apis[i]
list, err := api.Interface.List(metav1.ListOptions{})
if err != nil {
return err
}
lock.Lock()
for i := range list.Items {
c.setNode(createObjInfo(&list.Items[i], c.settings.GetAppInstanceLabelKey()))
}
lock.Unlock()
return nil
})
if err == nil {
err = c.startMissingWatches()
}
if err != nil {
log.Errorf("Failed to sync cluster %s: %v", c.cluster.Server, err)
return err
}
c.log.Info("Cluster successfully synced")
@@ -219,8 +342,8 @@ func (c *clusterInfo) getChildren(obj *unstructured.Unstructured) []appv1.Resour
return children
}
func (c *clusterInfo) isNamespaced(gk schema.GroupKind) bool {
if api, ok := c.apis[gk]; ok && !api.resource.Namespaced {
func (c *clusterInfo) isNamespaced(obj *unstructured.Unstructured) bool {
if api, ok := c.apisMeta[kube.GetResourceKey(obj).GroupKind()]; ok && !api.namespaced {
return false
}
return true
@@ -242,7 +365,7 @@ func (c *clusterInfo) getManagedLiveObjs(a *appv1.Application, targetObjs []*uns
lock := &sync.Mutex{}
err := util.RunAllAsync(len(targetObjs), func(i int) error {
targetObj := targetObjs[i]
key := GetTargetObjKey(a, targetObj, c.isNamespaced(targetObj.GroupVersionKind().GroupKind()))
key := GetTargetObjKey(a, targetObj, c.isNamespaced(targetObj))
lock.Lock()
managedObj := managedObjs[key]
lock.Unlock()
@@ -311,9 +434,6 @@ func (c *clusterInfo) processEvent(event watch.EventType, un *unstructured.Unstr
c.lock.Lock()
defer c.lock.Unlock()
key := kube.GetResourceKey(un)
if info, ok := c.apis[schema.GroupKind{Group: key.Group, Kind: key.Kind}]; ok {
info.resourceVersion = un.GetResourceVersion()
}
existingNode, exists := c.nodes[key]
if event == watch.Deleted {
if exists {
@@ -342,18 +462,23 @@ func (c *clusterInfo) onNodeUpdated(exists bool, existingNode *node, un *unstruc
if app == "" || skipAppRequeing(key) {
continue
}
toNotify[app] = true
toNotify[app] = n.isRootAppNode() || toNotify[app]
}
}
for name := range toNotify {
c.onAppUpdated(name)
for name, full := range toNotify {
c.onAppUpdated(name, full)
}
}
func (c *clusterInfo) onNodeRemoved(key kube.ResourceKey, existingNode *node) {
func (c *clusterInfo) onNodeRemoved(key kube.ResourceKey, n *node) {
appName := n.appName
if ns, ok := c.nsIndex[key.Namespace]; ok {
appName = n.getApp(ns)
}
c.removeNode(key)
if existingNode.appName != "" {
c.onAppUpdated(existingNode.appName)
if appName != "" {
c.onAppUpdated(appName, n.isRootAppNode())
}
}

View File

@@ -1,11 +1,15 @@
package cache
import (
"fmt"
"sort"
"strings"
"sync"
"testing"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/dynamic/fake"
"github.com/ghodss/yaml"
log "github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
@@ -49,8 +53,7 @@ var (
resourceVersion: "123"`)
testRS = strToUnstructured(`
apiVersion: v1
apiVersion: extensions/v1beta1
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: helm-guestbook-rs
@@ -62,52 +65,52 @@ var (
resourceVersion: "123"`)
testDeploy = strToUnstructured(`
apiVersion: extensions/v1beta1
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app.kubernetes.io/instance: helm-guestbook
name: helm-guestbook
namespace: default
resourceVersion: "123"`)
testCRD = strToUnstructured(`
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: my-custom-resource-definition
resourceVersion: "123"`)
)
func newCluster(objs ...*unstructured.Unstructured) *clusterInfo {
resByGVK := make(map[schema.GroupVersionKind][]unstructured.Unstructured)
runtimeObjs := make([]runtime.Object, len(objs))
for i := range objs {
resByGVK[objs[i].GroupVersionKind()] = append(resByGVK[objs[i].GroupVersionKind()], *objs[i])
runtimeObjs[i] = objs[i]
}
resources := make([]kube.ResourcesBatch, 0)
for gvk, objects := range resByGVK {
resources = append(resources, kube.ResourcesBatch{
ListResourceVersion: "1",
GVK: gvk,
Objects: objects,
})
}
return newClusterExt(kubetest.MockKubectlCmd{
Resources: resources,
})
scheme := runtime.NewScheme()
client := fake.NewSimpleDynamicClient(scheme, runtimeObjs...)
apiResources := []kube.APIResourceInfo{{
GroupKind: schema.GroupKind{Group: "", Kind: "Pod"},
Interface: client.Resource(schema.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"}),
Meta: metav1.APIResource{Namespaced: true},
}, {
GroupKind: schema.GroupKind{Group: "apps", Kind: "ReplicaSet"},
Interface: client.Resource(schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "replicasets"}),
Meta: metav1.APIResource{Namespaced: true},
}, {
GroupKind: schema.GroupKind{Group: "apps", Kind: "Deployment"},
Interface: client.Resource(schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployments"}),
Meta: metav1.APIResource{Namespaced: true},
}}
return newClusterExt(kubetest.MockKubectlCmd{APIResources: apiResources})
}
func newClusterExt(kubectl kube.Kubectl) *clusterInfo {
return &clusterInfo{
lock: &sync.Mutex{},
nodes: make(map[kube.ResourceKey]*node),
onAppUpdated: func(appName string) {},
onAppUpdated: func(appName string, fullRefresh bool) {},
kubectl: kubectl,
nsIndex: make(map[string]map[kube.ResourceKey]*node),
cluster: &appv1.Cluster{},
syncTime: nil,
syncLock: &sync.Mutex{},
apis: make(map[schema.GroupKind]*gkInfo),
apisMeta: make(map[schema.GroupKind]*apiMeta),
log: log.WithField("cluster", "test"),
settings: &settings.ArgoCDSettings{},
}
@@ -135,8 +138,8 @@ func TestGetChildren(t *testing.T) {
Kind: "ReplicaSet",
Namespace: "default",
Name: "helm-guestbook-rs",
Group: "extensions",
Version: "v1beta1",
Group: "apps",
Version: "v1",
ResourceVersion: "123",
Children: rsChildren,
Info: []appv1.InfoItem{},
@@ -149,7 +152,7 @@ func TestGetManagedLiveObjs(t *testing.T) {
assert.Nil(t, err)
targetDeploy := strToUnstructured(`
apiVersion: extensions/v1beta1
apiVersion: apps/v1
kind: Deployment
metadata:
name: helm-guestbook
@@ -269,8 +272,8 @@ func TestUpdateResourceTags(t *testing.T) {
func TestUpdateAppResource(t *testing.T) {
updatesReceived := make([]string, 0)
cluster := newCluster(testPod, testRS, testDeploy)
cluster.onAppUpdated = func(appName string) {
updatesReceived = append(updatesReceived, appName)
cluster.onAppUpdated = func(appName string, fullRefresh bool) {
updatesReceived = append(updatesReceived, fmt.Sprintf("%s: %v", appName, fullRefresh))
}
err := cluster.ensureSynced()
@@ -279,7 +282,7 @@ func TestUpdateAppResource(t *testing.T) {
err = cluster.processEvent(watch.Modified, mustToUnstructured(testPod))
assert.Nil(t, err)
assert.Equal(t, []string{"helm-guestbook"}, updatesReceived)
assert.Contains(t, updatesReceived, "helm-guestbook: false")
}
func TestCircularReference(t *testing.T) {
@@ -316,7 +319,7 @@ func TestWatchCacheUpdated(t *testing.T) {
podGroupKind := testPod.GroupVersionKind().GroupKind()
cluster.updateCache(podGroupKind, "updated-list-version", []unstructured.Unstructured{*updated, *added})
cluster.replaceResourceCache(podGroupKind, "updated-list-version", []unstructured.Unstructured{*updated, *added})
_, ok := cluster.nodes[kube.GetResourceKey(removed)]
assert.False(t, ok)
@@ -327,6 +330,4 @@ func TestWatchCacheUpdated(t *testing.T) {
_, ok = cluster.nodes[kube.GetResourceKey(added)]
assert.True(t, ok)
assert.Equal(t, cluster.getResourceVersion(podGroupKind), "updated-list-version")
}

View File

@@ -3,7 +3,7 @@ package cache
import (
"fmt"
"k8s.io/api/core/v1"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
k8snode "k8s.io/kubernetes/pkg/util/node"

View File

@@ -2,14 +2,11 @@
package mocks
import (
"context"
)
import "github.com/argoproj/argo-cd/util/kube"
import "github.com/stretchr/testify/mock"
import "k8s.io/apimachinery/pkg/runtime/schema"
import "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
import "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
import context "context"
import kube "github.com/argoproj/argo-cd/util/kube"
import mock "github.com/stretchr/testify/mock"
import unstructured "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
import v1alpha1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
// LiveStateCache is an autogenerated mock type for the LiveStateCache type
type LiveStateCache struct {
@@ -81,20 +78,20 @@ func (_m *LiveStateCache) Invalidate() {
_m.Called()
}
// IsNamespaced provides a mock function with given fields: server, gvk
func (_m *LiveStateCache) IsNamespaced(server string, gvk schema.GroupVersionKind) (bool, error) {
ret := _m.Called(server, gvk)
// IsNamespaced provides a mock function with given fields: server, obj
func (_m *LiveStateCache) IsNamespaced(server string, obj *unstructured.Unstructured) (bool, error) {
ret := _m.Called(server, obj)
var r0 bool
if rf, ok := ret.Get(0).(func(string, schema.GroupVersionKind) bool); ok {
r0 = rf(server, gvk)
if rf, ok := ret.Get(0).(func(string, *unstructured.Unstructured) bool); ok {
r0 = rf(server, obj)
} else {
r0 = ret.Get(0).(bool)
}
var r1 error
if rf, ok := ret.Get(1).(func(string, schema.GroupVersionKind) error); ok {
r1 = rf(server, gvk)
if rf, ok := ret.Get(1).(func(string, *unstructured.Unstructured) error); ok {
r1 = rf(server, obj)
} else {
r1 = ret.Error(1)
}

View File

@@ -6,7 +6,7 @@ import (
appv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/util/kube"
"k8s.io/api/core/v1"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
@@ -21,13 +21,17 @@ type node struct {
resource *unstructured.Unstructured
}
func (n *node) isRootAppNode() bool {
return n.appName != "" && len(n.ownerRefs) == 0
}
func (n *node) resourceKey() kube.ResourceKey {
return kube.NewResourceKey(n.ref.GroupVersionKind().Group, n.ref.Kind, n.ref.Namespace, n.ref.Name)
}
func (n *node) isParentOf(child *node) bool {
ownerGvk := n.ref.GroupVersionKind()
for _, ownerRef := range child.ownerRefs {
ownerGvk := schema.FromAPIVersionAndKind(ownerRef.APIVersion, ownerRef.Kind)
if kube.NewResourceKey(ownerGvk.Group, ownerRef.Kind, n.ref.Namespace, ownerRef.Name) == n.resourceKey() {
return true
}

25
controller/cache/node_test.go vendored Normal file
View File

@@ -0,0 +1,25 @@
package cache
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestIsParentOf(t *testing.T) {
child := createObjInfo(testPod, "")
parent := createObjInfo(testRS, "")
grandParent := createObjInfo(testDeploy, "")
assert.True(t, parent.isParentOf(child))
assert.False(t, grandParent.isParentOf(child))
}
func TestIsParentOfSameKindDifferentGroup(t *testing.T) {
rs := testRS.DeepCopy()
rs.SetAPIVersion("somecrd.io/v1")
child := createObjInfo(testPod, "")
invalidParent := createObjInfo(rs, "")
assert.False(t, invalidParent.isParentOf(child))
}

View File

@@ -60,6 +60,8 @@ var (
func NewMetricsServer(addr string, appLister applister.ApplicationLister) *MetricsServer {
mux := http.NewServeMux()
appRegistry := NewAppRegistry(appLister)
appRegistry.MustRegister(prometheus.NewProcessCollector(prometheus.ProcessCollectorOpts{}))
appRegistry.MustRegister(prometheus.NewGoCollector())
mux.Handle(MetricsPath, promhttp.HandlerFor(appRegistry, promhttp.HandlerOpts{}))
syncCounter := prometheus.NewCounterVec(

View File

@@ -220,7 +220,7 @@ func TestReconcileMetrics(t *testing.T) {
metricsServ := NewMetricsServer("localhost:8082", appLister)
fakeApp := newFakeApp(fakeApp)
metricsServ.IncReconcile(fakeApp, time.Duration(5*time.Second))
metricsServ.IncReconcile(fakeApp, 5*time.Second)
req, err := http.NewRequest("GET", "/metrics", nil)
assert.NoError(t, err)

View File

@@ -49,6 +49,10 @@ func GetLiveObjs(res []managedResource) []*unstructured.Unstructured {
return objs
}
type ResourceInfoProvider interface {
IsNamespaced(server string, obj *unstructured.Unstructured) (bool, error)
}
// AppStateManager defines methods which allow to compare application spec and actual application state.
type AppStateManager interface {
CompareAppState(app *v1alpha1.Application, revision string, source v1alpha1.ApplicationSource, noCache bool) (*comparisonResult, error)
@@ -56,7 +60,7 @@ type AppStateManager interface {
}
type comparisonResult struct {
observedAt metav1.Time
reconciledAt metav1.Time
syncStatus *v1alpha1.SyncStatus
healthStatus *v1alpha1.HealthStatus
resources []v1alpha1.ResourceStatus
@@ -131,6 +135,43 @@ func (m *appStateManager) getRepoObjs(app *v1alpha1.Application, source v1alpha1
return targetObjs, hooks, manifestInfo, nil
}
func DeduplicateTargetObjects(
server string,
namespace string,
objs []*unstructured.Unstructured,
infoProvider ResourceInfoProvider,
) ([]*unstructured.Unstructured, []v1alpha1.ApplicationCondition, error) {
targetByKey := make(map[kubeutil.ResourceKey][]*unstructured.Unstructured)
for i := range objs {
obj := objs[i]
isNamespaced, err := infoProvider.IsNamespaced(server, obj)
if err != nil {
return objs, nil, err
}
if !isNamespaced {
obj.SetNamespace("")
} else if obj.GetNamespace() == "" {
obj.SetNamespace(namespace)
}
key := kubeutil.GetResourceKey(obj)
targetByKey[key] = append(targetByKey[key], obj)
}
conditions := make([]v1alpha1.ApplicationCondition, 0)
result := make([]*unstructured.Unstructured, 0)
for key, targets := range targetByKey {
if len(targets) > 1 {
conditions = append(conditions, appv1.ApplicationCondition{
Type: appv1.ApplicationConditionRepeatedResourceWarning,
Message: fmt.Sprintf("Resource %s appeared %d times among application resources.", key.String(), len(targets)),
})
}
result = append(result, targets[len(targets)-1])
}
return result, conditions, nil
}
// CompareAppState compares application git state to the live app state, using the specified
// revision and supplied source. If revision or overrides are empty, then compares against
// revision and overrides in the app spec.
@@ -151,6 +192,12 @@ func (m *appStateManager) CompareAppState(app *v1alpha1.Application, revision st
conditions = append(conditions, v1alpha1.ApplicationCondition{Type: v1alpha1.ApplicationConditionComparisonError, Message: err.Error()})
failedToLoadObjs = true
}
targetObjs, dedupConditions, err := DeduplicateTargetObjects(app.Spec.Destination.Server, app.Spec.Destination.Namespace, targetObjs, m.liveStateCache)
if err != nil {
conditions = append(conditions, v1alpha1.ApplicationCondition{Type: v1alpha1.ApplicationConditionComparisonError, Message: err.Error()})
}
conditions = append(conditions, dedupConditions...)
logCtx.Debugf("Generated config manifests")
liveObjByKey, err := m.liveStateCache.GetManagedLiveObjs(app, targetObjs)
if err != nil {
@@ -175,7 +222,7 @@ func (m *appStateManager) CompareAppState(app *v1alpha1.Application, revision st
for i, obj := range targetObjs {
gvk := obj.GroupVersionKind()
ns := util.FirstNonEmpty(obj.GetNamespace(), app.Spec.Destination.Namespace)
if namespaced, err := m.liveStateCache.IsNamespaced(app.Spec.Destination.Server, obj.GroupVersionKind()); err == nil && !namespaced {
if namespaced, err := m.liveStateCache.IsNamespaced(app.Spec.Destination.Server, obj); err == nil && !namespaced {
ns = ""
}
key := kubeutil.NewResourceKey(gvk.Group, gvk.Kind, ns, obj.GetName())
@@ -270,7 +317,7 @@ func (m *appStateManager) CompareAppState(app *v1alpha1.Application, revision st
}
compRes := comparisonResult{
observedAt: observedAt,
reconciledAt: observedAt,
syncStatus: &syncStatus,
healthStatus: healthStatus,
resources: resourceSummaries,
@@ -278,7 +325,9 @@ func (m *appStateManager) CompareAppState(app *v1alpha1.Application, revision st
conditions: conditions,
hooks: hooks,
diffNormalizer: diffNormalizer,
appSourceType: v1alpha1.ApplicationSourceType(manifestInfo.SourceType),
}
if manifestInfo != nil {
compRes.appSourceType = v1alpha1.ApplicationSourceType(manifestInfo.SourceType)
}
return &compRes, nil
}

View File

@@ -141,3 +141,40 @@ func TestCompareAppStateExtraHook(t *testing.T) {
assert.Equal(t, 1, len(compRes.managedResources))
assert.Equal(t, 0, len(compRes.conditions))
}
func toJSON(t *testing.T, obj *unstructured.Unstructured) string {
data, err := json.Marshal(obj)
assert.NoError(t, err)
return string(data)
}
func TestCompareAppStateDuplicatedNamespacedResources(t *testing.T) {
obj1 := test.NewPod()
obj1.SetNamespace(test.FakeDestNamespace)
obj2 := test.NewPod()
obj3 := test.NewPod()
obj3.SetNamespace("kube-system")
app := newFakeApp()
data := fakeData{
manifestResponse: &repository.ManifestResponse{
Manifests: []string{toJSON(t, obj1), toJSON(t, obj2), toJSON(t, obj3)},
Namespace: test.FakeDestNamespace,
Server: test.FakeClusterURL,
Revision: "abc123",
},
managedLiveObjs: map[kube.ResourceKey]*unstructured.Unstructured{
kube.GetResourceKey(obj1): obj1,
kube.GetResourceKey(obj3): obj3,
},
}
ctrl := newFakeController(&data)
compRes, err := ctrl.appStateManager.CompareAppState(app, "", app.Spec.Source, false)
assert.NoError(t, err)
assert.NotNil(t, compRes)
assert.Contains(t, compRes.conditions, argoappv1.ApplicationCondition{
Message: "Resource /Pod/fake-dest-ns/my-pod appeared 2 times among application resources.",
Type: argoappv1.ApplicationConditionRepeatedResourceWarning,
})
assert.Equal(t, 2, len(compRes.resources))
}

View File

@@ -4,7 +4,7 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"k8s.io/apimachinery/pkg/apis/meta/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"

View File

@@ -1,31 +0,0 @@
{
"Vendor": true,
"DisableAll": true,
"Deadline": "8m",
"Enable": [
"vet",
"gofmt",
"goimports",
"deadcode",
"errcheck",
"varcheck",
"structcheck",
"ineffassign",
"unconvert",
"misspell"
],
"Linters": {
"goimports": {"Command": "goimports -l --local github.com/argoproj/argo-cd"}
},
"Skip": [
"pkg/client",
"vendor/",
".pb.go"
],
"Exclude": [
"pkg/client",
"vendor/",
".pb.go",
".*warning.*fmt.Fprint"
]
}

7
hack/git-ask-pass.sh Executable file
View File

@@ -0,0 +1,7 @@
#!/bin/sh
# This script is used as the commaned supplied to GIT_ASKPASS as a way to supply username/password
# credentials to git, without having to use git credentials helpers, or having on-disk config.
case "$1" in
Username*) echo "${GIT_USERNAME}" ;;
Password*) echo "${GIT_PASSWORD}" ;;
esac

8
hack/ssh_known_hosts Normal file
View File

@@ -0,0 +1,8 @@
# This file was automatically generated. DO NOT EDIT
bitbucket.org ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAubiN81eDcafrgMeLzaFPsw2kNvEcqTKl/VqLat/MaB33pZy0y3rJZtnqwR2qOOvbwKZYKiEO1O6VqNEBxKvJJelCq0dTXWT5pbO2gDXC6h6QDXCaHo6pOHGPUy+YBaGQRGuSusMEASYiWunYN0vCAI8QaXnWMXNMdFP3jHAJH0eDsoiGnLPBlBp4TNm6rYI74nMzgz3B9IikW4WVK+dc8KZJZWYjAuORU3jc1c/NPskD2ASinf8v3xnfXeukU0sJ5N6m5E8VLjObPEO+mN2t/FZTMZLiFqPWc/ALSqnMnnhwrNi2rbfg/rd/IpL8Le3pSBne8+seeFVBoGqzHM9yXw==
github.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==
gitlab.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFSMqzJeV9rUzU4kWitGjeR4PWSa29SPqJ1fVkhtj3Hw9xjLVXVYrU9QlYWrOLXBpQ6KWjbjTDTdDkoohFzgbEY=
gitlab.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAfuCHKVTjquxvt6CM6tdG4SLp1Btn/nOeHHE5UOzRdf
gitlab.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCsj2bNKTBSpIYDEGk9KxsGh3mySTRgMtXL583qmBpzeQ+jqCMRgBqB98u3z++J1sKlXHWfM9dyhSevkMwSbhoR8XIq/U0tCNyokEi/ueaBMCvbcTHhO7FcwzY92WK4Yt0aGROY5qX2UKSeOvuP4D6TPqKF1onrSzH9bx9XUf2lEdWT/ia1NEKjunUqu1xOB/StKDHMoX4/OKyIzuS0q/T1zOATthvasJFoPrAjkohTyaDUz2LN5JoH839hViyEG82yB+MjcFV5MU3N1l1QL3cVUCh93xSaua1N85qivl+siMkPGbO5xR/En4iEY6K2XPASUEMaieWVNTRCtJ4S8H+9
ssh.dev.azure.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC7Hr1oTWqNqOlzGJOfGJ4NakVyIzf1rXYd4d7wo6jBlkLvCA4odBlL0mDUyZ0/QUfTTqeu+tm22gOsv+VrVTMk6vwRU75gY/y9ut5Mb3bR5BV58dKXyq9A9UeB5Cakehn5Zgm6x1mKoVyf+FFn26iYqXJRgzIZZcZ5V6hrE0Qg39kZm4az48o0AUbf6Sp4SLdvnuMa2sVNwHBboS7EJkm57XQPVU3/QpyNLHbWDdzwtrlS+ez30S3AdYhLKEOxAG8weOnyrtLJAUen9mTkol8oII1edf7mWWbWVf0nBmly21+nZcmCTISQBtdcyPaEno7fFQMDD26/s0lfKob4Kw8H
vs-ssh.visualstudio.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC7Hr1oTWqNqOlzGJOfGJ4NakVyIzf1rXYd4d7wo6jBlkLvCA4odBlL0mDUyZ0/QUfTTqeu+tm22gOsv+VrVTMk6vwRU75gY/y9ut5Mb3bR5BV58dKXyq9A9UeB5Cakehn5Zgm6x1mKoVyf+FFn26iYqXJRgzIZZcZ5V6hrE0Qg39kZm4az48o0AUbf6Sp4SLdvnuMa2sVNwHBboS7EJkm57XQPVU3/QpyNLHbWDdzwtrlS+ez30S3AdYhLKEOxAG8weOnyrtLJAUen9mTkol8oII1edf7mWWbWVf0nBmly21+nZcmCTISQBtdcyPaEno7fFQMDD26/s0lfKob4Kw8H

24
hack/update-ssh-known-hosts.sh Executable file
View File

@@ -0,0 +1,24 @@
#!/bin/bash
set -e
KNOWN_HOSTS_FILE=$(dirname "$0")/ssh_known_hosts
HEADER="# This file was automatically generated. DO NOT EDIT"
echo "$HEADER" > $KNOWN_HOSTS_FILE
ssh-keyscan github.com gitlab.com bitbucket.org ssh.dev.azure.com vs-ssh.visualstudio.com | sort -u >> $KNOWN_HOSTS_FILE
chmod 0644 $KNOWN_HOSTS_FILE
# Public SSH keys can be verified at the following URLs:
# - github.com: https://help.github.com/articles/github-s-ssh-key-fingerprints/
# - gitlab.com: https://docs.gitlab.com/ee/user/gitlab_com/#ssh-host-keys-fingerprints
# - bitbucket.org: https://confluence.atlassian.com/bitbucket/ssh-keys-935365775.html
# - ssh.dev.azure.com, vs-ssh.visualstudio.com: https://docs.microsoft.com/en-us/azure/devops/repos/git/use-ssh-keys-to-authenticate?view=azure-devops
diff - <(ssh-keygen -l -f $KNOWN_HOSTS_FILE | sort -k 3) <<EOF
2048 SHA256:zzXQOXSRBEiUtuE8AikJYKwbHaxvSc0ojez9YXaGp1A bitbucket.org (RSA)
2048 SHA256:nThbg6kXUpJWGl7E1IGOCspRomTxdCARLviKw6E5SY8 github.com (RSA)
256 SHA256:HbW3g8zUjNSksFbqTiUWPWg2Bq1x8xdGUrliXFzSnUw gitlab.com (ECDSA)
256 SHA256:eUXGGm1YGsMAS7vkcx6JOJdOGHPem5gQp4taiCfCLB8 gitlab.com (ED25519)
2048 SHA256:ROQFvPThGrW4RuWLoL9tq9I9zJ42fK4XywyRtbOz/EQ gitlab.com (RSA)
2048 SHA256:ohD8VZEXGWo6Ez8GSEJQ9WpafgLFsOfLOtGGQCQo6Og ssh.dev.azure.com (RSA)
2048 SHA256:ohD8VZEXGWo6Ez8GSEJQ9WpafgLFsOfLOtGGQCQo6Og vs-ssh.visualstudio.com (RSA)
EOF

View File

@@ -12,7 +12,7 @@ bases:
images:
- name: argoproj/argocd
newName: argoproj/argocd
newTag: latest
newTag: v0.12.0-rc5
- name: argoproj/argocd-ui
newName: argoproj/argocd-ui
newTag: latest
newTag: v0.12.0-rc5

View File

@@ -26,6 +26,7 @@ spec:
- argocd-redis:6379
ports:
- containerPort: 8081
- containerPort: 8084
readinessProbe:
tcpSocket:
port: 8081

View File

@@ -8,7 +8,13 @@ metadata:
name: argocd-repo-server
spec:
ports:
- port: 8081
- name: server
protocol: TCP
port: 8081
targetPort: 8081
- name: metrics
protocol: TCP
port: 8084
targetPort: 8084
selector:
app.kubernetes.io/name: argocd-repo-server

View File

@@ -7,26 +7,24 @@ metadata:
app.kubernetes.io/component: server
name: argocd-server
rules:
# support viewing and deleting a live object view in UI
- apiGroups:
- '*'
resources:
- '*'
verbs:
- delete
- get
# support listing events of
- delete # supports deletion a live object in UI
- get # supports viewing live object manifest in UI
- patch # supports `argocd app patch`
- apiGroups:
- ""
resources:
- events
verbs:
- list
# support viewing pod logs from UI
- list # supports listing events in UI
- apiGroups:
- ""
resources:
- pods
- pods/log
verbs:
- get
- get # supports viewing pod logs from UI

View File

@@ -17,7 +17,7 @@ patchesStrategicMerge:
images:
- name: argoproj/argocd
newName: argoproj/argocd
newTag: latest
newTag: v0.12.0-rc5
- name: argoproj/argocd-ui
newName: argoproj/argocd-ui
newTag: latest
newTag: v0.12.0-rc5

View File

@@ -199,6 +199,7 @@ rules:
verbs:
- delete
- get
- patch
- apiGroups:
- ""
resources:
@@ -571,8 +572,14 @@ metadata:
name: argocd-repo-server
spec:
ports:
- port: 8081
- name: server
port: 8081
protocol: TCP
targetPort: 8081
- name: metrics
port: 8084
protocol: TCP
targetPort: 8084
selector:
app.kubernetes.io/name: argocd-repo-server
---
@@ -646,7 +653,7 @@ spec:
- argocd-redis-ha-announce-2:26379
- --sentinelmaster
- argocd
image: argoproj/argocd:latest
image: argoproj/argocd:v0.12.0-rc5
imagePullPolicy: Always
name: argocd-application-controller
ports:
@@ -693,7 +700,7 @@ spec:
- cp
- /usr/local/bin/argocd-util
- /shared
image: argoproj/argocd:latest
image: argoproj/argocd:v0.12.0-rc5
imagePullPolicy: Always
name: copyutil
volumeMounts:
@@ -748,11 +755,12 @@ spec:
- argocd-redis-ha-announce-2:26379
- --sentinelmaster
- argocd
image: argoproj/argocd:latest
image: argoproj/argocd:v0.12.0-rc5
imagePullPolicy: Always
name: argocd-repo-server
ports:
- containerPort: 8081
- containerPort: 8084
readinessProbe:
initialDelaySeconds: 5
periodSeconds: 10
@@ -804,7 +812,7 @@ spec:
- argocd-redis-ha-announce-2:26379
- --sentinelmaster
- argocd
image: argoproj/argocd:latest
image: argoproj/argocd:v0.12.0-rc5
imagePullPolicy: Always
name: argocd-server
ports:
@@ -825,7 +833,7 @@ spec:
- -r
- /app
- /shared
image: argoproj/argocd-ui:latest
image: argoproj/argocd-ui:v0.12.0-rc5
imagePullPolicy: Always
name: ui
volumeMounts:

View File

@@ -487,8 +487,14 @@ metadata:
name: argocd-repo-server
spec:
ports:
- port: 8081
- name: server
port: 8081
protocol: TCP
targetPort: 8081
- name: metrics
port: 8084
protocol: TCP
targetPort: 8084
selector:
app.kubernetes.io/name: argocd-repo-server
---
@@ -562,7 +568,7 @@ spec:
- argocd-redis-ha-announce-2:26379
- --sentinelmaster
- argocd
image: argoproj/argocd:latest
image: argoproj/argocd:v0.12.0-rc5
imagePullPolicy: Always
name: argocd-application-controller
ports:
@@ -609,7 +615,7 @@ spec:
- cp
- /usr/local/bin/argocd-util
- /shared
image: argoproj/argocd:latest
image: argoproj/argocd:v0.12.0-rc5
imagePullPolicy: Always
name: copyutil
volumeMounts:
@@ -664,11 +670,12 @@ spec:
- argocd-redis-ha-announce-2:26379
- --sentinelmaster
- argocd
image: argoproj/argocd:latest
image: argoproj/argocd:v0.12.0-rc5
imagePullPolicy: Always
name: argocd-repo-server
ports:
- containerPort: 8081
- containerPort: 8084
readinessProbe:
initialDelaySeconds: 5
periodSeconds: 10
@@ -720,7 +727,7 @@ spec:
- argocd-redis-ha-announce-2:26379
- --sentinelmaster
- argocd
image: argoproj/argocd:latest
image: argoproj/argocd:v0.12.0-rc5
imagePullPolicy: Always
name: argocd-server
ports:
@@ -741,7 +748,7 @@ spec:
- -r
- /app
- /shared
image: argoproj/argocd-ui:latest
image: argoproj/argocd-ui:v0.12.0-rc5
imagePullPolicy: Always
name: ui
volumeMounts:

View File

@@ -199,6 +199,7 @@ rules:
verbs:
- delete
- get
- patch
- apiGroups:
- ""
resources:
@@ -384,8 +385,14 @@ metadata:
name: argocd-repo-server
spec:
ports:
- port: 8081
- name: server
port: 8081
protocol: TCP
targetPort: 8081
- name: metrics
port: 8084
protocol: TCP
targetPort: 8084
selector:
app.kubernetes.io/name: argocd-repo-server
---
@@ -451,7 +458,7 @@ spec:
- "20"
- --operation-processors
- "10"
image: argoproj/argocd:latest
image: argoproj/argocd:v0.12.0-rc5
imagePullPolicy: Always
name: argocd-application-controller
ports:
@@ -498,7 +505,7 @@ spec:
- cp
- /usr/local/bin/argocd-util
- /shared
image: argoproj/argocd:latest
image: argoproj/argocd:v0.12.0-rc5
imagePullPolicy: Always
name: copyutil
volumeMounts:
@@ -561,11 +568,12 @@ spec:
- argocd-repo-server
- --redis
- argocd-redis:6379
image: argoproj/argocd:latest
image: argoproj/argocd:v0.12.0-rc5
imagePullPolicy: Always
name: argocd-repo-server
ports:
- containerPort: 8081
- containerPort: 8084
readinessProbe:
initialDelaySeconds: 5
periodSeconds: 10
@@ -594,7 +602,7 @@ spec:
- argocd-server
- --staticassets
- /shared/app
image: argoproj/argocd:latest
image: argoproj/argocd:v0.12.0-rc5
imagePullPolicy: Always
name: argocd-server
ports:
@@ -615,7 +623,7 @@ spec:
- -r
- /app
- /shared
image: argoproj/argocd-ui:latest
image: argoproj/argocd-ui:v0.12.0-rc5
imagePullPolicy: Always
name: ui
volumeMounts:

View File

@@ -300,8 +300,14 @@ metadata:
name: argocd-repo-server
spec:
ports:
- port: 8081
- name: server
port: 8081
protocol: TCP
targetPort: 8081
- name: metrics
port: 8084
protocol: TCP
targetPort: 8084
selector:
app.kubernetes.io/name: argocd-repo-server
---
@@ -367,7 +373,7 @@ spec:
- "20"
- --operation-processors
- "10"
image: argoproj/argocd:latest
image: argoproj/argocd:v0.12.0-rc5
imagePullPolicy: Always
name: argocd-application-controller
ports:
@@ -414,7 +420,7 @@ spec:
- cp
- /usr/local/bin/argocd-util
- /shared
image: argoproj/argocd:latest
image: argoproj/argocd:v0.12.0-rc5
imagePullPolicy: Always
name: copyutil
volumeMounts:
@@ -477,11 +483,12 @@ spec:
- argocd-repo-server
- --redis
- argocd-redis:6379
image: argoproj/argocd:latest
image: argoproj/argocd:v0.12.0-rc5
imagePullPolicy: Always
name: argocd-repo-server
ports:
- containerPort: 8081
- containerPort: 8084
readinessProbe:
initialDelaySeconds: 5
periodSeconds: 10
@@ -510,7 +517,7 @@ spec:
- argocd-server
- --staticassets
- /shared/app
image: argoproj/argocd:latest
image: argoproj/argocd:v0.12.0-rc5
imagePullPolicy: Always
name: argocd-server
ports:
@@ -531,7 +538,7 @@ spec:
- -r
- /app
- /shared
image: argoproj/argocd-ui:latest
image: argoproj/argocd-ui:v0.12.0-rc5
imagePullPolicy: Always
name: ui
volumeMounts:

View File

@@ -3,7 +3,6 @@ package apiclient
import (
"context"
"crypto/tls"
"crypto/x509"
"encoding/base64"
"errors"
"fmt"
@@ -25,7 +24,7 @@ import (
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/status"
"github.com/argoproj/argo-cd"
argocd "github.com/argoproj/argo-cd"
"github.com/argoproj/argo-cd/common"
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
argoappv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
@@ -39,6 +38,7 @@ import (
"github.com/argoproj/argo-cd/server/version"
grpc_util "github.com/argoproj/argo-cd/util/grpc"
"github.com/argoproj/argo-cd/util/localconfig"
tls_util "github.com/argoproj/argo-cd/util/tls"
)
const (
@@ -389,7 +389,7 @@ func (c *client) newConn() (*grpc.ClientConn, io.Closer, error) {
func (c *client) tlsConfig() (*tls.Config, error) {
var tlsConfig tls.Config
if len(c.CertPEMData) > 0 {
cp := x509.NewCertPool()
cp := tls_util.BestEffortSystemCertPool()
if !cp.AppendCertsFromPEM(c.CertPEMData) {
return nil, fmt.Errorf("credentials: failed to append certificates")
}

File diff suppressed because it is too large Load Diff

View File

@@ -215,9 +215,13 @@ message ApplicationStatus {
repeated ApplicationCondition conditions = 5;
optional k8s.io.apimachinery.pkg.apis.meta.v1.Time observedAt = 6;
optional k8s.io.apimachinery.pkg.apis.meta.v1.Time reconciledAt = 6;
optional OperationState operationState = 7;
optional k8s.io.apimachinery.pkg.apis.meta.v1.Time observedAt = 8;
optional string sourceType = 9;
}
// ApplicationWatchEvent contains information about application change.
@@ -444,6 +448,8 @@ message Repository {
optional string sshPrivateKey = 4;
optional ConnectionState connectionState = 5;
optional bool insecureIgnoreHostKey = 6;
}
// RepositoryList is a collection of Repositories.

View File

@@ -220,8 +220,10 @@ type ApplicationStatus struct {
Health HealthStatus `json:"health,omitempty" protobuf:"bytes,3,opt,name=health"`
History []RevisionHistory `json:"history,omitempty" protobuf:"bytes,4,opt,name=history"`
Conditions []ApplicationCondition `json:"conditions,omitempty" protobuf:"bytes,5,opt,name=conditions"`
ObservedAt metav1.Time `json:"observedAt,omitempty" protobuf:"bytes,6,opt,name=observedAt"`
ReconciledAt metav1.Time `json:"reconciledAt,omitempty" protobuf:"bytes,6,opt,name=reconciledAt"`
OperationState *OperationState `json:"operationState,omitempty" protobuf:"bytes,7,opt,name=operationState"`
ObservedAt metav1.Time `json:"observedAt,omitempty" protobuf:"bytes,8,opt,name=observedAt"`
SourceType ApplicationSourceType `json:"sourceType,omitempty" protobuf:"bytes,9,opt,name=sourceType"`
}
// Operation contains requested operation parameters.
@@ -468,6 +470,8 @@ const (
ApplicationConditionUnknownError = "UnknownError"
// ApplicationConditionSharedResourceWarning indicates that controller detected resources which belongs to more than one application
ApplicationConditionSharedResourceWarning = "SharedResourceWarning"
// ApplicationConditionRepeatedResourceWarning indicates that application source has resource with same Group, Kind, Name, Namespace multiple times
ApplicationConditionRepeatedResourceWarning = "RepeatedResourceWarning"
)
// ApplicationCondition contains details about current application condition
@@ -667,11 +671,12 @@ type HelmRepository struct {
// Repository is a Git repository holding application configurations
type Repository struct {
Repo string `json:"repo" protobuf:"bytes,1,opt,name=repo"`
Username string `json:"username,omitempty" protobuf:"bytes,2,opt,name=username"`
Password string `json:"password,omitempty" protobuf:"bytes,3,opt,name=password"`
SSHPrivateKey string `json:"sshPrivateKey,omitempty" protobuf:"bytes,4,opt,name=sshPrivateKey"`
ConnectionState ConnectionState `json:"connectionState,omitempty" protobuf:"bytes,5,opt,name=connectionState"`
Repo string `json:"repo" protobuf:"bytes,1,opt,name=repo"`
Username string `json:"username,omitempty" protobuf:"bytes,2,opt,name=username"`
Password string `json:"password,omitempty" protobuf:"bytes,3,opt,name=password"`
SSHPrivateKey string `json:"sshPrivateKey,omitempty" protobuf:"bytes,4,opt,name=sshPrivateKey"`
ConnectionState ConnectionState `json:"connectionState,omitempty" protobuf:"bytes,5,opt,name=connectionState"`
InsecureIgnoreHostKey bool `json:"insecureIgnoreHostKey,omitempty" protobuf:"bytes,6,opt,name=insecureIgnoreHostKey"`
}
// RepositoryList is a collection of Repositories.

View File

@@ -479,7 +479,7 @@ func (in *ApplicationStatus) DeepCopyInto(out *ApplicationStatus) {
*out = make([]ApplicationCondition, len(*in))
copy(*out, *in)
}
in.ObservedAt.DeepCopyInto(&out.ObservedAt)
in.ReconciledAt.DeepCopyInto(&out.ReconciledAt)
if in.OperationState != nil {
in, out := &in.OperationState, &out.OperationState
if *in == nil {
@@ -489,6 +489,7 @@ func (in *ApplicationStatus) DeepCopyInto(out *ApplicationStatus) {
(*in).DeepCopyInto(*out)
}
}
in.ObservedAt.DeepCopyInto(&out.ObservedAt)
return
}

View File

@@ -215,7 +215,7 @@ func GenerateManifests(appPath string, q *ManifestRequest) (*ManifestResponse, e
}
}
case v1alpha1.ApplicationSourceTypeKustomize:
k := kustomize.NewKustomizeApp(appPath)
k := kustomize.NewKustomizeApp(appPath, kustomizeCredentials(q.Repo))
targetObjs, _, err = k.Build(q.ApplicationSource.Kustomize)
case v1alpha1.ApplicationSourceTypePlugin:
targetObjs, err = runConfigManagementPlugin(appPath, q, q.Plugins)
@@ -489,7 +489,7 @@ func pathExists(ss ...string) bool {
func (s *Service) newClientResolveRevision(repo *v1alpha1.Repository, revision string) (git.Client, string, error) {
repoURL := git.NormalizeGitURL(repo.Repo)
appRepoPath := tempRepoPath(repoURL)
gitClient, err := s.gitFactory.NewClient(repoURL, appRepoPath, repo.Username, repo.Password, repo.SSHPrivateKey)
gitClient, err := s.gitFactory.NewClient(repoURL, appRepoPath, repo.Username, repo.Password, repo.SSHPrivateKey, repo.InsecureIgnoreHostKey)
if err != nil {
return nil, "", err
}
@@ -635,7 +635,7 @@ func (s *Service) GetAppDetails(ctx context.Context, q *RepoServerAppDetailsQuer
case v1alpha1.ApplicationSourceTypeKustomize:
res.Kustomize = &KustomizeAppSpec{}
res.Kustomize.Path = q.Path
k := kustomize.NewKustomizeApp(appPath)
k := kustomize.NewKustomizeApp(appPath, kustomizeCredentials(q.Repo))
_, params, err := k.Build(nil)
if err != nil {
return nil, err
@@ -651,3 +651,13 @@ func (q *RepoServerAppDetailsQuery) valueFiles() []string {
}
return q.Helm.ValueFiles
}
func kustomizeCredentials(repo *v1alpha1.Repository) *kustomize.GitCredentials {
if repo == nil || repo.Password == "" {
return nil
}
return &kustomize.GitCredentials{
Username: repo.Username,
Password: repo.Password,
}
}

View File

@@ -36,7 +36,7 @@ type fakeGitClientFactory struct {
root string
}
func (f *fakeGitClientFactory) NewClient(repoURL, path, username, password, sshPrivateKey string) (git.Client, error) {
func (f *fakeGitClientFactory) NewClient(repoURL, path, username, password, sshPrivateKey string, insecureIgnoreHostKey bool) (git.Client, error) {
mockClient := gitmocks.Client{}
root := "./testdata"
if f.root != "" {
@@ -251,7 +251,7 @@ func TestGetAppDetailsKustomize(t *testing.T) {
Path: "kustomization_yaml",
})
assert.NoError(t, err)
assert.Equal(t, "nginx", res.Kustomize.ImageTags[0].Name)
assert.Equal(t, "1.15.4", res.Kustomize.ImageTags[0].Value)
assert.Equal(t, "k8s.gcr.io/nginx-slim", res.Kustomize.ImageTags[0].Name)
assert.Equal(t, "0.8", res.Kustomize.ImageTags[0].Value)
assert.Equal(t, 2, len(res.Kustomize.ImageTags))
}

View File

@@ -433,6 +433,11 @@ func (s *Server) Watch(q *ApplicationQuery, ws ApplicationService_WatchServer) e
if err != nil {
return err
}
defer w.Stop()
logCtx := log.NewEntry(log.New())
if q.Name != nil {
logCtx = logCtx.WithField("application", *q.Name)
}
claims := ws.Context().Value("claims")
done := make(chan bool)
go func() {
@@ -448,17 +453,17 @@ func (s *Server) Watch(q *ApplicationQuery, ws ApplicationService_WatchServer) e
Application: a,
})
if err != nil {
log.Warnf("Unable to send stream message: %v", err)
logCtx.Warnf("Unable to send stream message: %v", err)
}
}
}
done <- true
logCtx.Info("k8s application watch event channel closed")
close(done)
}()
select {
case <-ws.Context().Done():
w.Stop()
logCtx.Info("client watch grpc context closed")
case <-done:
w.Stop()
}
return nil
}
@@ -492,18 +497,13 @@ func (s *Server) validateAndNormalizeApp(ctx context.Context, app *appv1.Applica
}
}
conditions, err := argo.GetSpecErrors(ctx, &app.Spec, proj, s.repoClientset, s.db)
conditions, appSourceType, err := argo.GetSpecErrors(ctx, &app.Spec, proj, s.repoClientset, s.db)
if err != nil {
return err
}
if len(conditions) > 0 {
return status.Errorf(codes.InvalidArgument, "application spec is invalid: %s", argo.FormatAppConditions(conditions))
}
appSourceType, err := argo.QueryAppSourceType(ctx, app, s.repoClientset, s.db)
if err != nil {
return err
}
app.Spec = *argo.NormalizeApplicationSpec(&app.Spec, appSourceType)
return nil
}
@@ -728,7 +728,10 @@ func (s *Server) PodLogs(q *ApplicationPodLogsQuery, ws ApplicationService_PodLo
if err != nil {
return err
}
logCtx := log.WithField("application", q.Name)
defer util.Close(stream)
done := make(chan bool)
gracefulExit := false
go func() {
scanner := bufio.NewScanner(stream)
for scanner.Scan() {
@@ -745,18 +748,25 @@ func (s *Server) PodLogs(q *ApplicationPodLogsQuery, ws ApplicationService_PodLo
TimeStamp: metaLogTime,
})
if err != nil {
log.Warnf("Unable to send stream message: %v", err)
logCtx.Warnf("Unable to send stream message: %v", err)
}
}
}
}
}
done <- true
if gracefulExit {
logCtx.Info("k8s pod logs scanner completed due to closed grpc context")
} else if err := scanner.Err(); err != nil {
logCtx.Warnf("k8s pod logs scanner failed with error: %v", err)
} else {
logCtx.Info("k8s pod logs scanner completed with EOF")
}
close(done)
}()
select {
case <-ws.Context().Done():
util.Close(stream)
logCtx.Info("client pod logs grpc context closed")
gracefulExit = true
case <-done:
}
return nil
@@ -889,7 +899,7 @@ func (s *Server) resolveRevision(ctx context.Context, app *appv1.Application, sy
// If we couldn't retrieve from the repo service, assume public repositories
repo = &appv1.Repository{Repo: app.Spec.Source.RepoURL}
}
gitClient, err := s.gitFactory.NewClient(repo.Repo, "", repo.Username, repo.Password, repo.SSHPrivateKey)
gitClient, err := s.gitFactory.NewClient(repo.Repo, "", repo.Username, repo.Password, repo.SSHPrivateKey, repo.InsecureIgnoreHostKey)
if err != nil {
return "", "", err
}

View File

@@ -59,7 +59,7 @@ func (s *Server) getConnectionState(ctx context.Context, url string) appsv1.Conn
}
repo, err := s.db.GetRepository(ctx, url)
if err == nil {
err = git.TestRepo(repo.Repo, repo.Username, repo.Password, repo.SSHPrivateKey)
err = git.TestRepo(repo.Repo, repo.Username, repo.Password, repo.SSHPrivateKey, repo.InsecureIgnoreHostKey)
}
if err != nil {
connectionState.Status = appsv1.ConnectionStatusFailed
@@ -235,7 +235,7 @@ func (s *Server) Create(ctx context.Context, q *RepoCreateRequest) (*appsv1.Repo
return nil, err
}
r := q.Repo
err := git.TestRepo(r.Repo, r.Username, r.Password, r.SSHPrivateKey)
err := git.TestRepo(r.Repo, r.Username, r.Password, r.SSHPrivateKey, r.InsecureIgnoreHostKey)
if err != nil {
return nil, err
}

View File

@@ -380,7 +380,7 @@ func WaitUntil(t *testing.T, condition wait.ConditionFunc) {
type FakeGitClientFactory struct{}
func (f *FakeGitClientFactory) NewClient(repoURL, path, username, password, sshPrivateKey string) (git.Client, error) {
func (f *FakeGitClientFactory) NewClient(repoURL, path, username, password, sshPrivateKey string, insecureIgnoreHostKey bool) (git.Client, error) {
return &FakeGitClient{
root: path,
}, nil

View File

@@ -0,0 +1,33 @@
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: clusterdummies.argoproj.io
spec:
group: argoproj.io
version: v1alpha1
scope: Cluster
names:
kind: ClusterDummy
plural: clusterdummies
---
apiVersion: argoproj.io/v1alpha1
kind: ClusterDummy
metadata:
name: cluster-dummy-crd-instance
---
apiVersion: argoproj.io/v1alpha1
kind: ClusterDummy
metadata:
name: cluster-dummy-crd-instance
namespace: default
---
apiVersion: argoproj.io/v1alpha1
kind: ClusterDummy
metadata:
name: cluster-dummy-crd-instance
namespace: kube-system

View File

@@ -0,0 +1,32 @@
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: dummies.argoproj.io
spec:
group: argoproj.io
version: v1alpha1
scope: Namespaced
names:
kind: Dummy
plural: dummies
---
apiVersion: argoproj.io/v1alpha1
kind: Dummy
metadata:
name: dummy-crd-instance
namespace: default
---
apiVersion: argoproj.io/v1alpha1
kind: Dummy
metadata:
name: dummy-crd-instance
namespace: kube-system
---
apiVersion: argoproj.io/v1alpha1
kind: Dummy
metadata:
name: dummy-crd-instance

View File

@@ -140,20 +140,20 @@ func GetSpecErrors(
proj *argoappv1.AppProject,
repoClientset reposerver.Clientset,
db db.ArgoDB,
) ([]argoappv1.ApplicationCondition, error) {
) ([]argoappv1.ApplicationCondition, argoappv1.ApplicationSourceType, error) {
conditions := make([]argoappv1.ApplicationCondition, 0)
if spec.Source.RepoURL == "" || spec.Source.Path == "" {
conditions = append(conditions, argoappv1.ApplicationCondition{
Type: argoappv1.ApplicationConditionInvalidSpecError,
Message: "spec.source.repoURL and spec.source.path are required",
})
return conditions, nil
return conditions, "", nil
}
// Test the repo
conn, repoClient, err := repoClientset.NewRepoServerClient()
if err != nil {
return nil, err
return nil, "", err
}
defer util.Close(conn)
repoAccessable := false
@@ -164,7 +164,7 @@ func GetSpecErrors(
// The repo has not been added to Argo CD so we do not have credentials to access it.
// We support the mode where apps can be created from public repositories. Test the
// repo to make sure it is publicly accessible
err = git.TestRepo(spec.Source.RepoURL, "", "", "")
err = git.TestRepo(spec.Source.RepoURL, "", "", "", false)
if err != nil {
conditions = append(conditions, argoappv1.ApplicationCondition{
Type: argoappv1.ApplicationConditionInvalidSpecError,
@@ -174,12 +174,13 @@ func GetSpecErrors(
repoAccessable = true
}
} else {
return nil, err
return nil, "", err
}
} else {
repoAccessable = true
}
var appSourceType argoappv1.ApplicationSourceType
// Verify only one source type is defined
explicitSourceType, err := spec.Source.ExplicitType()
if err != nil {
@@ -190,7 +191,6 @@ func GetSpecErrors(
}
if repoAccessable {
var appSourceType argoappv1.ApplicationSourceType
if explicitSourceType != nil {
appSourceType = *explicitSourceType
} else {
@@ -249,11 +249,11 @@ func GetSpecErrors(
Message: fmt.Sprintf("cluster '%s' has not been configured", spec.Destination.Server),
})
} else {
return nil, err
return nil, "", err
}
}
}
return conditions, nil
return conditions, appSourceType, nil
}
// GetAppProject returns a project from an application
@@ -261,27 +261,6 @@ func GetAppProject(spec *argoappv1.ApplicationSpec, projLister applicationsv1.Ap
return projLister.AppProjects(ns).Get(spec.GetProject())
}
// QueryAppSourceType queries repo server for yaml files in a directory, and determines its
// application source type based on the files in the directory.
// This code is redundant to the logic in argo.GetSpecErrors, but since it's is hard to
// extract out of there. We will be throwing away this code when we remove
// componentParameterOverrides.
func QueryAppSourceType(ctx context.Context, app *argoappv1.Application, repoClientset reposerver.Clientset, db db.ArgoDB) (argoappv1.ApplicationSourceType, error) {
if t, _ := app.Spec.Source.ExplicitType(); t != nil {
return *t, nil
}
repoRes, err := db.GetRepository(ctx, app.Spec.Source.RepoURL)
if err != nil {
return "", err
}
conn, repoClient, err := repoClientset.NewRepoServerClient()
if err != nil {
return "", err
}
defer util.Close(conn)
return queryAppSourceType(ctx, &app.Spec, repoRes, repoClient)
}
func queryAppSourceType(ctx context.Context, spec *argoappv1.ApplicationSpec, repoRes *argoappv1.Repository, repoClient repository.RepoServerServiceClient) (argoappv1.ApplicationSourceType, error) {
req := repository.ListDirRequest{
Repo: &argoappv1.Repository{

View File

@@ -2,7 +2,7 @@ package argo
import (
log "github.com/sirupsen/logrus"
"k8s.io/api/core/v1"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/kubernetes"

View File

@@ -28,7 +28,7 @@ func TestCache(t *testing.T) {
cacheObj.Foo = "baz"
err = c.Get("key", &obj)
assert.Nil(t, err)
assert.EqualValues(t, string(obj.Foo), "foo")
assert.EqualValues(t, obj.Foo, "foo")
assert.EqualValues(t, string(obj.Bar), "bar")
err = c.Delete("key")

View File

@@ -21,7 +21,7 @@ import (
"k8s.io/client-go/tools/clientcmd"
"k8s.io/kubernetes/pkg/kubectl/util/term"
"github.com/argoproj/argo-cd"
argocd "github.com/argoproj/argo-cd"
"github.com/argoproj/argo-cd/errors"
)

View File

@@ -3,7 +3,7 @@ package db
import (
"context"
"k8s.io/api/core/v1"
v1 "k8s.io/api/core/v1"
"k8s.io/client-go/kubernetes"
appv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"

View File

@@ -61,7 +61,10 @@ func (db *db) CreateRepository(ctx context.Context, r *appsv1.Repository) (*apps
data[sshPrivateKey] = []byte(r.SSHPrivateKey)
}
repoInfo := settings.RepoCredentials{URL: r.Repo}
repoInfo := settings.RepoCredentials{
URL: r.Repo,
InsecureIgnoreHostKey: r.InsecureIgnoreHostKey,
}
err = db.updateSecrets(&repoInfo, r)
if err != nil {
return nil, err
@@ -87,12 +90,15 @@ func (db *db) GetRepository(ctx context.Context, repoURL string) (*appsv1.Reposi
return nil, status.Errorf(codes.NotFound, "repo '%s' not found", repoURL)
}
repoInfo := s.Repositories[index]
repo := &appsv1.Repository{Repo: repoInfo.URL}
repo := &appsv1.Repository{
Repo: repoInfo.URL,
InsecureIgnoreHostKey: repoInfo.InsecureIgnoreHostKey,
}
err = db.unmarshalFromSecretsStr(map[*string]*apiv1.SecretKeySelector{
&repo.Username: repoInfo.UsernameSecret,
&repo.Password: repoInfo.PasswordSecret,
&repo.SSHPrivateKey: repoInfo.SshPrivateKeySecret,
&repo.SSHPrivateKey: repoInfo.SSHPrivateKeySecret,
}, make(map[string]*apiv1.Secret))
if err != nil {
return nil, err
@@ -179,7 +185,7 @@ func (db *db) updateSecrets(repoInfo *settings.RepoCredentials, r *appsv1.Reposi
repoInfo.UsernameSecret = setSecretData(repoInfo.UsernameSecret, r.Username, username)
repoInfo.PasswordSecret = setSecretData(repoInfo.PasswordSecret, r.Password, password)
repoInfo.SshPrivateKeySecret = setSecretData(repoInfo.SshPrivateKeySecret, r.SSHPrivateKey, sshPrivateKey)
repoInfo.SSHPrivateKeySecret = setSecretData(repoInfo.SSHPrivateKeySecret, r.SSHPrivateKey, sshPrivateKey)
for k, v := range secretsData {
err := db.upsertSecret(k, v)
if err != nil {

View File

@@ -31,7 +31,7 @@ type Client interface {
// ClientFactory is a factory of Git Clients
// Primarily used to support creation of mock git clients during unit testing
type ClientFactory interface {
NewClient(repoURL, path, username, password, sshPrivateKey string) (Client, error)
NewClient(repoURL, path, username, password, sshPrivateKey string, insecureIgnoreHostKey bool) (Client, error)
}
// nativeGitClient implements Client interface using git CLI
@@ -47,7 +47,7 @@ func NewFactory() ClientFactory {
return &factory{}
}
func (f *factory) NewClient(repoURL, path, username, password, sshPrivateKey string) (Client, error) {
func (f *factory) NewClient(repoURL, path, username, password, sshPrivateKey string, insecureIgnoreHostKey bool) (Client, error) {
clnt := nativeGitClient{
repoURL: repoURL,
root: path,
@@ -58,7 +58,9 @@ func (f *factory) NewClient(repoURL, path, username, password, sshPrivateKey str
return nil, err
}
auth := &ssh2.PublicKeys{User: "git", Signer: signer}
auth.HostKeyCallback = ssh.InsecureIgnoreHostKey()
if insecureIgnoreHostKey {
auth.HostKeyCallback = ssh.InsecureIgnoreHostKey()
}
clnt.auth = auth
} else if username != "" || password != "" {
auth := &http.BasicAuth{Username: username, Password: password}
@@ -103,6 +105,18 @@ func (m *nativeGitClient) Init() error {
// Fetch fetches latest updates from origin
func (m *nativeGitClient) Fetch() error {
log.Debugf("Fetching repo %s at %s", m.repoURL, m.root)
// Two techniques are used for fetching the remote depending if the remote is SSH vs. HTTPS
// If http, we fork/exec the git CLI since the go-git client does not properly support git
// providers such as AWS CodeCommit and Azure DevOps.
if _, ok := m.auth.(*ssh2.PublicKeys); ok {
return m.goGitFetch()
}
_, err := m.runCredentialedCmd("git", "fetch", "origin", "--tags", "--force")
return err
}
// goGitFetch fetches the remote using go-git
func (m *nativeGitClient) goGitFetch() error {
repo, err := git.PlainOpen(m.root)
if err != nil {
return err
@@ -119,17 +133,6 @@ func (m *nativeGitClient) Fetch() error {
return nil
}
return err
// git fetch does not update the HEAD reference. The following command will update the local
// knowledge of what remote considers the “default branch”
// See: https://stackoverflow.com/questions/8839958/how-does-origin-head-get-set
// NOTE(jessesuen): disabling the following code because:
// 1. we no longer perform a `git checkout HEAD`, instead relying on `ls-remote` and checking
// out a specific SHA1.
// 2. This command is the only other command that we use (excluding fetch/ls-remote) which
// requires remote access, and there appears to be no go-git equivalent to this command.
// _, err = m.runCmd("git", "remote", "set-head", "origin", "-a")
// return err
}
// LsFiles lists the local working tree, including only files that are under source control
@@ -241,13 +244,27 @@ func (m *nativeGitClient) CommitSHA() (string, error) {
// runCmd is a convenience function to run a command in a given directory and return its output
func (m *nativeGitClient) runCmd(command string, args ...string) (string, error) {
cmd := exec.Command(command, args...)
return m.runCmdOutput(cmd)
}
// runCredentialedCmd is a convenience function to run a git command with username/password credentials
func (m *nativeGitClient) runCredentialedCmd(command string, args ...string) (string, error) {
cmd := exec.Command(command, args...)
if auth, ok := m.auth.(*http.BasicAuth); ok {
cmd.Env = append(cmd.Env, fmt.Sprintf("GIT_ASKPASS=git-ask-pass.sh"))
cmd.Env = append(cmd.Env, fmt.Sprintf("GIT_USERNAME=%s", auth.Username))
cmd.Env = append(cmd.Env, fmt.Sprintf("GIT_PASSWORD=%s", auth.Password))
}
return m.runCmdOutput(cmd)
}
func (m *nativeGitClient) runCmdOutput(cmd *exec.Cmd) (string, error) {
log.Debug(strings.Join(cmd.Args, " "))
cmd.Dir = m.root
env := os.Environ()
env = append(env, "HOME=/dev/null")
env = append(env, "GIT_CONFIG_NOSYSTEM=true")
env = append(env, "GIT_ASKPASS=")
cmd.Env = env
cmd.Env = append(cmd.Env, os.Environ()...)
cmd.Env = append(cmd.Env, "HOME=/dev/null")
cmd.Env = append(cmd.Env, "GIT_CONFIG_NOSYSTEM=true")
cmd.Env = append(cmd.Env, "GIT_CONFIG_NOGLOBAL=true")
out, err := cmd.Output()
if len(out) > 0 {
log.Debug(string(out))

View File

@@ -65,8 +65,8 @@ func IsSSHURL(url string) bool {
}
// TestRepo tests if a repo exists and is accessible with the given credentials
func TestRepo(repo, username, password string, sshPrivateKey string) error {
clnt, err := NewFactory().NewClient(repo, "", username, password, sshPrivateKey)
func TestRepo(repo, username, password string, sshPrivateKey string, insecureIgnoreHostKey bool) error {
clnt, err := NewFactory().NewClient(repo, "", username, password, sshPrivateKey, insecureIgnoreHostKey)
if err != nil {
return err
}

View File

@@ -1,13 +1,24 @@
package git
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
)
// TODO: move this into shared test package after resolving import cycle
const (
// This is a throwaway gitlab test account/repo with a read-only personal access token for the
// purposes of testing private git repos
PrivateGitRepo = "https://gitlab.com/argo-cd-test/test-apps.git"
PrivateGitUsername = "blah"
PrivateGitPassword = "B5sBDeoqAVUouoHkrovy"
)
func TestIsCommitSHA(t *testing.T) {
assert.True(t, IsCommitSHA("9d921f65f3c5373b682e2eb4b37afba6592e8f8b"))
assert.True(t, IsCommitSHA("9D921F65F3C5373B682E2EB4B37AFBA6592E8F8B"))
@@ -92,7 +103,7 @@ func TestSameURL(t *testing.T) {
}
func TestLsRemote(t *testing.T) {
clnt, err := NewFactory().NewClient("https://github.com/argoproj/argo-cd.git", "/tmp", "", "", "")
clnt, err := NewFactory().NewClient("https://github.com/argoproj/argo-cd.git", "/tmp", "", "", "", false)
assert.NoError(t, err)
xpass := []string{
"HEAD",
@@ -127,43 +138,35 @@ func TestLsRemote(t *testing.T) {
func TestGitClient(t *testing.T) {
testRepos := []string{
"https://github.com/argoproj/argocd-example-apps",
// TODO: add this back when azure repos are supported
//"https://jsuen0437@dev.azure.com/jsuen0437/jsuen/_git/jsuen",
"https://jsuen0437@dev.azure.com/jsuen0437/jsuen/_git/jsuen",
}
for _, repo := range testRepos {
dirName, err := ioutil.TempDir("", "git-client-test-")
assert.NoError(t, err)
defer func() { _ = os.RemoveAll(dirName) }()
clnt, err := NewFactory().NewClient(repo, dirName, "", "", "")
clnt, err := NewFactory().NewClient(repo, dirName, "", "", "", false)
assert.NoError(t, err)
testGitClient(t, clnt)
}
}
// TestPrivateGitRepo tests the ability to operate on a private git repo. This test needs to be run
// manually since we do not have a private git repo for testing
//
// export TEST_REPO=https://github.com/jessesuen/private-argocd-example-apps
// export GITHUB_TOKEN=<YOURGITHUBTOKEN>
// go test -v -run ^(TestPrivateGitRepo)$ ./util/git/...
// TestPrivateGitRepo tests the ability to operate on a private git repo.
func TestPrivateGitRepo(t *testing.T) {
repo := os.Getenv("TEST_REPO")
username := os.Getenv("TEST_USERNAME")
password := os.Getenv("GITHUB_TOKEN")
if username == "" {
username = "git" // username does not matter for tokens
}
if repo == "" || password == "" {
t.Skip("skipping private git repo test since no repo or password supplied")
}
// add the hack path which has the git-ask-pass.sh shell script
osPath := os.Getenv("PATH")
hackPath, err := filepath.Abs("../../hack")
assert.NoError(t, err)
err = os.Setenv("PATH", fmt.Sprintf("%s:%s", osPath, hackPath))
assert.NoError(t, err)
defer func() { _ = os.Setenv("PATH", osPath) }()
dirName, err := ioutil.TempDir("", "git-client-test-")
assert.NoError(t, err)
defer func() { _ = os.RemoveAll(dirName) }()
clnt, err := NewFactory().NewClient(repo, dirName, username, password, "")
clnt, err := NewFactory().NewClient(PrivateGitRepo, dirName, PrivateGitUsername, PrivateGitPassword, "", false)
assert.NoError(t, err)
testGitClient(t, clnt)

View File

@@ -5,14 +5,13 @@ import (
"encoding/json"
"fmt"
"github.com/gogo/protobuf/proto"
grpc_logging "github.com/grpc-ecosystem/go-grpc-middleware/logging"
grpc_logrus "github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus"
ctx_logrus "github.com/grpc-ecosystem/go-grpc-middleware/tags/logrus"
"github.com/sirupsen/logrus"
"golang.org/x/net/context"
"google.golang.org/grpc"
"github.com/gogo/protobuf/proto"
"github.com/grpc-ecosystem/go-grpc-middleware/logging"
"github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus"
"github.com/grpc-ecosystem/go-grpc-middleware/tags/logrus"
"github.com/sirupsen/logrus"
)
func logRequest(entry *logrus.Entry, info string, pbMsg interface{}, ctx context.Context, logClaims bool) {

View File

@@ -3,8 +3,8 @@ package health
import (
"fmt"
"k8s.io/api/apps/v1"
appsv1 "k8s.io/api/apps/v1"
v1 "k8s.io/api/apps/v1"
batchv1 "k8s.io/api/batch/v1"
coreV1 "k8s.io/api/core/v1"
extv1beta1 "k8s.io/api/extensions/v1beta1"

View File

@@ -2,24 +2,18 @@ package kube
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io/ioutil"
"os/exec"
"runtime/debug"
"strings"
"sync"
"time"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
apierr "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/discovery"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes"
@@ -29,170 +23,91 @@ import (
"github.com/argoproj/argo-cd/util/diff"
)
type ResourcesBatch struct {
GVK schema.GroupVersionKind
ResourceInfo metav1.APIResource
ListResourceVersion string
Objects []unstructured.Unstructured
Error error
}
type CacheRefreshEvent struct {
GVK schema.GroupVersionKind
ResourceVersion string
Objects []unstructured.Unstructured
}
type WatchEvent struct {
WatchEvent *watch.Event
CacheRefresh *CacheRefreshEvent
}
type CachedVersionSource func(gk schema.GroupKind) (string, error)
type Kubectl interface {
ApplyResource(config *rest.Config, obj *unstructured.Unstructured, namespace string, dryRun, force bool) (string, error)
ConvertToVersion(obj *unstructured.Unstructured, group, version string) (*unstructured.Unstructured, error)
DeleteResource(config *rest.Config, gvk schema.GroupVersionKind, name string, namespace string, forceDelete bool) error
GetResource(config *rest.Config, gvk schema.GroupVersionKind, name string, namespace string) (*unstructured.Unstructured, error)
PatchResource(config *rest.Config, gvk schema.GroupVersionKind, name string, namespace string, patchType types.PatchType, patchBytes []byte) (*unstructured.Unstructured, error)
WatchResources(ctx context.Context, config *rest.Config, resourceFilter ResourceFilter, versionSource CachedVersionSource) (chan WatchEvent, error)
GetResources(config *rest.Config, resourceFilter ResourceFilter, namespace string) (chan ResourcesBatch, error)
GetAPIResources(config *rest.Config) ([]*metav1.APIResourceList, error)
GetAPIResources(config *rest.Config, resourceFilter ResourceFilter) ([]APIResourceInfo, error)
}
type KubectlCmd struct{}
func (k KubectlCmd) GetAPIResources(config *rest.Config) ([]*metav1.APIResourceList, error) {
type APIResourceInfo struct {
GroupKind schema.GroupKind
Meta metav1.APIResource
Interface dynamic.ResourceInterface
}
type filterFunc func(apiResource *metav1.APIResource) bool
func filterAPIResources(config *rest.Config, resourceFilter ResourceFilter, filter filterFunc, namespace string) ([]APIResourceInfo, error) {
dynamicIf, err := dynamic.NewForConfig(config)
if err != nil {
return nil, err
}
disco, err := discovery.NewDiscoveryClientForConfig(config)
if err != nil {
return nil, err
}
return disco.ServerResources()
}
// GetResources returns all kubernetes resources
func (k KubectlCmd) GetResources(config *rest.Config, resourceFilter ResourceFilter, namespace string) (chan ResourcesBatch, error) {
res := make(chan ResourcesBatch)
listSupported := func(apiResource *metav1.APIResource) bool {
return isSupportedVerb(apiResource, listVerb)
}
apiResIfs, err := filterAPIResources(config, resourceFilter, listSupported, namespace)
serverResources, err := disco.ServerPreferredResources()
if err != nil {
return nil, err
if len(serverResources) == 0 {
return nil, err
}
log.Warnf("Partial success when performing preferred resource discovery: %v", err)
}
go func() {
defer close(res)
var wg sync.WaitGroup
wg.Add(len(apiResIfs))
for _, apiResIf := range apiResIfs {
go func(apiResIf apiResourceInterface) {
defer wg.Done()
batch := ResourcesBatch{
GVK: apiResIf.groupVersion.WithKind(apiResIf.apiResource.Kind),
ResourceInfo: apiResIf.apiResource,
}
list, err := apiResIf.resourceIf.List(metav1.ListOptions{})
apiResIfs := make([]APIResourceInfo, 0)
for _, apiResourcesList := range serverResources {
gv, err := schema.ParseGroupVersion(apiResourcesList.GroupVersion)
if err != nil {
gv = schema.GroupVersion{}
}
for _, apiResource := range apiResourcesList.APIResources {
if resourceFilter.IsExcludedResource(gv.Group, apiResource.Kind, config.Host) {
continue
}
if _, ok := isObsoleteExtensionsGroupKind(gv.Group, apiResource.Kind); ok {
continue
}
if filter(&apiResource) {
resource := ToGroupVersionResource(apiResourcesList.GroupVersion, &apiResource)
resourceIf := ToResourceInterface(dynamicIf, &apiResource, resource, namespace)
gv, err := schema.ParseGroupVersion(apiResourcesList.GroupVersion)
if err != nil {
if !apierr.IsNotFound(err) {
batch.Error = err
res <- batch
}
return
return nil, err
}
batch.ListResourceVersion = list.GetResourceVersion()
batch.Objects = list.Items
res <- batch
}(apiResIf)
apiResIf := APIResourceInfo{
GroupKind: schema.GroupKind{Group: gv.Group, Kind: apiResource.Kind},
Meta: apiResource,
Interface: resourceIf,
}
apiResIfs = append(apiResIfs, apiResIf)
}
}
wg.Wait()
}()
return res, nil
}
return apiResIfs, nil
}
const watchResourcesRetryTimeout = 1 * time.Second
// WatchResources watches all the existing resources in the cluster provided by the config. Method retries watch with the most recent resource version stored in cache.
// The WatchResources returns channel which container either watch event with updated resource info or new list of resources if cached resource version had expired.
func (k KubectlCmd) WatchResources(
ctx context.Context,
config *rest.Config,
resourceFilter ResourceFilter,
versionSource CachedVersionSource,
) (chan WatchEvent, error) {
watchSupported := func(apiResource *metav1.APIResource) bool {
return isSupportedVerb(apiResource, watchVerb)
// isSupportedVerb returns whether or not a APIResource supports a specific verb
func isSupportedVerb(apiResource *metav1.APIResource, verb string) bool {
for _, v := range apiResource.Verbs {
if v == verb {
return true
}
}
log.Infof("Start watching for resources changes with in cluster %s", config.Host)
apiResIfs, err := filterAPIResources(config, resourceFilter, watchSupported, "")
return false
}
func (k KubectlCmd) GetAPIResources(config *rest.Config, resourceFilter ResourceFilter) ([]APIResourceInfo, error) {
apiResIfs, err := filterAPIResources(config, resourceFilter, func(apiResource *metav1.APIResource) bool {
return isSupportedVerb(apiResource, listVerb) && isSupportedVerb(apiResource, watchVerb)
}, "")
if err != nil {
return nil, err
}
ch := make(chan WatchEvent)
go func() {
var wg sync.WaitGroup
wg.Add(len(apiResIfs))
for _, a := range apiResIfs {
go func(apiResIf apiResourceInterface) {
defer wg.Done()
util.RetryUntilSucceed(func() (err error) {
defer func() {
if r := recover(); r != nil {
message := fmt.Sprintf("Recovered from panic: %+v\n%s", r, debug.Stack())
log.Error(message)
err = errors.New(message)
}
}()
watchCh := WatchWithRetry(ctx, func() (i watch.Interface, e error) {
gvk := apiResIf.groupVersion.WithKind(apiResIf.apiResource.Kind)
resourceVersion, err := versionSource(gvk.GroupKind())
if err != nil {
return nil, err
}
w, err := apiResIf.resourceIf.Watch(metav1.ListOptions{
ResourceVersion: resourceVersion,
})
if apierr.IsGone(err) {
log.Infof("Resource version of %s has expired at cluster %s, reloading list", gvk, config.Host)
list, err := apiResIf.resourceIf.List(metav1.ListOptions{})
if err != nil {
return nil, err
}
ch <- WatchEvent{
CacheRefresh: &CacheRefreshEvent{
GVK: gvk,
ResourceVersion: list.GetResourceVersion(),
Objects: list.Items,
},
}
return apiResIf.resourceIf.Watch(metav1.ListOptions{ResourceVersion: list.GetResourceVersion()})
}
return w, err
})
for next := range watchCh {
if next.Error != nil {
return next.Error
}
ch <- WatchEvent{
WatchEvent: &watch.Event{
Object: next.Object,
Type: next.Type,
},
}
}
return nil
}, fmt.Sprintf("watch resources %s %s/%s", config.Host, apiResIf.groupVersion, apiResIf.apiResource.Kind), ctx, watchResourcesRetryTimeout)
}(a)
}
wg.Wait()
close(ch)
log.Infof("Stop watching for resources changes with in cluster %s", config.Host)
}()
return ch, nil
return apiResIfs, err
}
// GetResource returns resource

View File

@@ -75,6 +75,10 @@ func (k *ResourceKey) String() string {
return fmt.Sprintf("%s/%s/%s/%s", k.Group, k.Kind, k.Namespace, k.Name)
}
func (k ResourceKey) GroupKind() schema.GroupKind {
return schema.GroupKind{Group: k.Group, Kind: k.Kind}
}
func isObsoleteExtensionsGroupKind(group string, kind string) (string, bool) {
if group == "extensions" {
newGroup, ok := obsoleteExtensionsKinds[kind]
@@ -246,72 +250,6 @@ func IsCRD(obj *unstructured.Unstructured) bool {
return IsCRDGroupVersionKind(obj.GroupVersionKind())
}
type apiResourceInterface struct {
groupVersion schema.GroupVersion
apiResource metav1.APIResource
resourceIf dynamic.ResourceInterface
}
type filterFunc func(apiResource *metav1.APIResource) bool
func filterAPIResources(config *rest.Config, resourceFilter ResourceFilter, filter filterFunc, namespace string) ([]apiResourceInterface, error) {
dynamicIf, err := dynamic.NewForConfig(config)
if err != nil {
return nil, err
}
disco, err := discovery.NewDiscoveryClientForConfig(config)
if err != nil {
return nil, err
}
serverResources, err := disco.ServerPreferredResources()
if err != nil {
if len(serverResources) == 0 {
return nil, err
}
log.Warnf("Partial success when performing preferred resource discovery: %v", err)
}
apiResIfs := make([]apiResourceInterface, 0)
for _, apiResourcesList := range serverResources {
gv, err := schema.ParseGroupVersion(apiResourcesList.GroupVersion)
if err != nil {
gv = schema.GroupVersion{}
}
if resourceFilter.IsExcludedResource(gv.Group, apiResourcesList.Kind, config.Host) {
continue
}
for _, apiResource := range apiResourcesList.APIResources {
if _, ok := isObsoleteExtensionsGroupKind(gv.Group, apiResource.Kind); ok || gv.Group == "" && apiResource.Kind == "Event" {
continue
}
if filter(&apiResource) {
resource := ToGroupVersionResource(apiResourcesList.GroupVersion, &apiResource)
resourceIf := ToResourceInterface(dynamicIf, &apiResource, resource, namespace)
gv, err := schema.ParseGroupVersion(apiResourcesList.GroupVersion)
if err != nil {
return nil, err
}
apiResIf := apiResourceInterface{
groupVersion: gv,
apiResource: apiResource,
resourceIf: resourceIf,
}
apiResIfs = append(apiResIfs, apiResIf)
}
}
}
return apiResIfs, nil
}
// isSupportedVerb returns whether or not a APIResource supports a specific verb
func isSupportedVerb(apiResource *metav1.APIResource, verb string) bool {
for _, v := range apiResource.Verbs {
if v == verb {
return true
}
}
return false
}
// See: https://github.com/ksonnet/ksonnet/blob/master/utils/client.go
func ServerResourceForGroupVersionKind(disco discovery.DiscoveryInterface, gvk schema.GroupVersionKind) (*metav1.APIResource, error) {
resources, err := disco.ServerResourcesForGroupVersion(gvk.GroupVersion().String())

View File

@@ -1,12 +1,10 @@
package kubetest
import (
"context"
"k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/rest"
"github.com/argoproj/argo-cd/util/kube"
@@ -18,27 +16,15 @@ type KubectlOutput struct {
}
type MockKubectlCmd struct {
APIResources []*v1.APIResourceList
Resources []kube.ResourcesBatch
APIResources []kube.APIResourceInfo
Commands map[string]KubectlOutput
Events chan kube.WatchEvent
Events chan watch.Event
}
func (k MockKubectlCmd) GetAPIResources(config *rest.Config) ([]*v1.APIResourceList, error) {
func (k MockKubectlCmd) GetAPIResources(config *rest.Config, resourceFilter kube.ResourceFilter) ([]kube.APIResourceInfo, error) {
return k.APIResources, nil
}
func (k MockKubectlCmd) GetResources(config *rest.Config, resourceFilter kube.ResourceFilter, namespace string) (chan kube.ResourcesBatch, error) {
res := make(chan kube.ResourcesBatch)
go func() {
defer close(res)
for i := range k.Resources {
res <- k.Resources[i]
}
}()
return res, nil
}
func (k MockKubectlCmd) GetResource(config *rest.Config, gvk schema.GroupVersionKind, name string, namespace string) (*unstructured.Unstructured, error) {
return nil, nil
}
@@ -47,12 +33,6 @@ func (k MockKubectlCmd) PatchResource(config *rest.Config, gvk schema.GroupVersi
return nil, nil
}
func (k MockKubectlCmd) WatchResources(
ctx context.Context, config *rest.Config, resourceFilter kube.ResourceFilter, versionSource kube.CachedVersionSource) (chan kube.WatchEvent, error) {
return k.Events, nil
}
func (k MockKubectlCmd) DeleteResource(config *rest.Config, gvk schema.GroupVersionKind, name string, namespace string, forceDelete bool) error {
command, ok := k.Commands[name]
if !ok {

View File

@@ -6,6 +6,7 @@ import (
"os"
"os/exec"
"path/filepath"
"sort"
"strings"
argoexec "github.com/argoproj/pkg/exec"
@@ -24,13 +25,22 @@ type Kustomize interface {
Build(opts *v1alpha1.ApplicationSourceKustomize) ([]*unstructured.Unstructured, []*v1alpha1.KustomizeImageTag, error)
}
type GitCredentials struct {
Username string
Password string
}
// NewKustomizeApp create a new wrapper to run commands on the `kustomize` command-line tool.
func NewKustomizeApp(path string) Kustomize {
return &kustomize{path: path}
func NewKustomizeApp(path string, creds *GitCredentials) Kustomize {
return &kustomize{
path: path,
creds: creds,
}
}
type kustomize struct {
path string
path string
creds *GitCredentials
}
func (k *kustomize) Build(opts *v1alpha1.ApplicationSourceKustomize) ([]*unstructured.Unstructured, []*v1alpha1.KustomizeImageTag, error) {
@@ -66,7 +76,15 @@ func (k *kustomize) Build(opts *v1alpha1.ApplicationSourceKustomize) ([]*unstruc
}
}
out, err := argoexec.RunCommand(commandName, "build", k.path)
cmd := exec.Command(commandName, "build", k.path)
cmd.Env = os.Environ()
if k.creds != nil {
cmd.Env = append(cmd.Env, "GIT_ASKPASS=git-ask-pass.sh")
cmd.Env = append(cmd.Env, fmt.Sprintf("GIT_USERNAME=%s", k.creds.Username))
cmd.Env = append(cmd.Env, fmt.Sprintf("GIT_PASSWORD=%s", k.creds.Password))
}
out, err := argoexec.RunCommandExt(cmd)
if err != nil {
return nil, nil, err
}
@@ -103,7 +121,6 @@ var KustomizationNames = []string{"kustomization.yaml", "kustomization.yml", "Ku
func (k *kustomize) findKustomization() (string, error) {
for _, file := range KustomizationNames {
kustomization := filepath.Join(k.path, file)
log.Infof("path=%s, file=%s", k.path, file)
if _, err := os.Stat(kustomization); err == nil {
return kustomization, nil
}
@@ -131,8 +148,6 @@ func (k *kustomize) getKustomizationVersion() (int, error) {
return 0, err
}
log.Infof("using kustomization=%s", kustomizationFile)
dat, err := ioutil.ReadFile(kustomizationFile)
if err != nil {
return 0, err
@@ -170,6 +185,9 @@ func getImageParameters(objs []*unstructured.Unstructured) []*v1alpha1.Kustomize
Value: version,
})
}
sort.Slice(params, func(i, j int) bool {
return params[i].Name < params[j].Name
})
return params
}

View File

@@ -1,18 +1,28 @@
package kustomize
import (
"fmt"
"io/ioutil"
"os"
"path"
"path/filepath"
"testing"
"github.com/argoproj/pkg/exec"
"github.com/stretchr/testify/assert"
"github.com/argoproj/pkg/exec"
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
)
// TODO: move this into shared test package after resolving import cycle
const (
// This is a throwaway gitlab test account/repo with a read-only personal access token for the
// purposes of testing private git repos
PrivateGitRepo = "https://gitlab.com/argo-cd-test/test-apps.git"
PrivateGitUsername = "blah"
PrivateGitPassword = "B5sBDeoqAVUouoHkrovy"
)
const kustomization1 = "kustomization_yaml"
const kustomization2a = "kustomization_yml"
const kustomization2b = "Kustomization"
@@ -33,7 +43,7 @@ func TestKustomizeBuild(t *testing.T) {
appPath, err := testDataDir()
assert.Nil(t, err)
namePrefix := "namePrefix-"
kustomize := NewKustomizeApp(appPath)
kustomize := NewKustomizeApp(appPath, nil)
kustomizeSource := v1alpha1.ApplicationSourceKustomize{
NamePrefix: namePrefix,
ImageTags: []v1alpha1.KustomizeImageTag{
@@ -98,9 +108,28 @@ func TestGetCommandName(t *testing.T) {
}
func TestIsKustomization(t *testing.T) {
assert.True(t, IsKustomization("kustomization.yaml"))
assert.True(t, IsKustomization("kustomization.yml"))
assert.True(t, IsKustomization("Kustomization"))
assert.False(t, IsKustomization("rubbish.yml"))
}
// TestPrivateRemoteBase verifies we can supply git credentials to a private remote base
func TestPrivateRemoteBase(t *testing.T) {
os.Setenv("GIT_CONFIG_NOSYSTEM", "true")
defer os.Unsetenv("GIT_CONFIG_NOSYSTEM")
// add the hack path which has the git-ask-pass.sh shell script
osPath := os.Getenv("PATH")
hackPath, err := filepath.Abs("../../hack")
assert.NoError(t, err)
err = os.Setenv("PATH", fmt.Sprintf("%s:%s", osPath, hackPath))
assert.NoError(t, err)
defer func() { _ = os.Setenv("PATH", osPath) }()
kust := NewKustomizeApp("./testdata/private-remote-base", &GitCredentials{Username: PrivateGitUsername, Password: PrivateGitPassword})
objs, _, err := kust.Build(nil)
assert.NoError(t, err)
assert.Len(t, objs, 2)
}

View File

@@ -0,0 +1,7 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
bases:
- https://gitlab.com/argo-cd-test/test-apps//remote-base
namePrefix: child-

View File

@@ -62,11 +62,6 @@ type ClientApp struct {
cache *cache.Cache
}
type appState struct {
// ReturnURL is the URL in which to redirect a user back to after completing an OAuth2 login
ReturnURL string `json:"returnURL"`
}
// NewClientApp will register the Argo CD client app (either via Dex or external OIDC) and return an
// object which has HTTP handlers for handling the HTTP responses for login and callback
func NewClientApp(settings *settings.ArgoCDSettings, cache *cache.Cache, dexServerAddr string) (*ClientApp, error) {

View File

@@ -86,10 +86,11 @@ type OIDCConfig struct {
}
type RepoCredentials struct {
URL string `json:"url,omitempty"`
UsernameSecret *apiv1.SecretKeySelector `json:"usernameSecret,omitempty"`
PasswordSecret *apiv1.SecretKeySelector `json:"passwordSecret,omitempty"`
SshPrivateKeySecret *apiv1.SecretKeySelector `json:"sshPrivateKeySecret,omitempty"`
URL string `json:"url,omitempty"`
UsernameSecret *apiv1.SecretKeySelector `json:"usernameSecret,omitempty"`
PasswordSecret *apiv1.SecretKeySelector `json:"passwordSecret,omitempty"`
SSHPrivateKeySecret *apiv1.SecretKeySelector `json:"sshPrivateKeySecret,omitempty"`
InsecureIgnoreHostKey bool `json:"insecureIgnoreHostKey,omitempty"`
}
type HelmRepoCredentials struct {
@@ -234,7 +235,7 @@ func (mgr *SettingsManager) MigrateLegacyRepoSettings(settings *ArgoCDSettings)
}
}
if sshPrivateKey, ok := s.Data["sshPrivateKey"]; ok && string(sshPrivateKey) != "" {
cred.SshPrivateKeySecret = &apiv1.SecretKeySelector{
cred.SSHPrivateKeySecret = &apiv1.SecretKeySelector{
LocalObjectReference: apiv1.LocalObjectReference{Name: s.Name},
Key: "sshPrivateKey",
}
@@ -818,6 +819,7 @@ func (a *ArgoCDSettings) GetAppInstanceLabelKey() string {
func (a *ArgoCDSettings) getExcludedResources() []ExcludedResource {
coreExcludedResources := []ExcludedResource{
{APIGroups: []string{"events.k8s.io", "metrics.k8s.io"}},
{APIGroups: []string{""}, Kinds: []string{"Event"}},
}
return append(coreExcludedResources, a.ResourceExclusions...)
}

View File

@@ -50,6 +50,15 @@ type CertOptions struct {
type ConfigCustomizer = func(*tls.Config)
// BestEffortSystemCertPool returns system cert pool as best effort, otherwise an empty cert pool
func BestEffortSystemCertPool() *x509.CertPool {
rootCAs, _ := x509.SystemCertPool()
if rootCAs == nil {
return x509.NewCertPool()
}
return rootCAs
}
func getTLSVersionByString(version string) (uint16, error) {
if version == "" {
return 0, nil

View File

@@ -33,7 +33,9 @@ type Closer interface {
// Close is a convenience function to close a object that has a Close() method, ignoring any errors
// Used to satisfy errcheck lint
func Close(c Closer) {
_ = c.Close()
if err := c.Close(); err != nil {
log.Warnf("failed to close %v: %v", c, err)
}
}
// DeleteFile is best effort deletion of a file
@@ -130,7 +132,10 @@ func RunAllAsync(count int, action func(i int) error) (err error) {
wg.Add(1)
go func(index int) {
defer wg.Done()
err = action(index)
actionErr := action(index)
if actionErr != nil {
err = actionErr
}
}(i)
if err != nil {
break