Compare commits

...

59 Commits

Author SHA1 Message Date
Alex Collins
a5a65cdfe7 Update manifests to v1.3.2 2019-12-03 13:26:34 -08:00
Alex Collins
219fae8380 make manifests 2019-12-03 11:18:14 -08:00
Alex Collins
ba04a028c1 Revert "Use Kustomize 3 to generate manifetsts. Closes #2487 (#2510)" (#2696) 2019-12-03 10:53:10 -08:00
Simon Behar
634e0d6323 Fix directory traversal edge case and enhance tests (#2797) 2019-12-02 18:27:19 -08:00
Alex Collins
962fb84fba Update manifests to v1.3.1 2019-12-02 12:49:53 -08:00
Alex Collins
0a8507ac6e Fixes merge conflicts. See #2770 2019-12-02 12:43:19 -08:00
Alex Collins
3026882f17 Fix bug where manifests are not cached. Fixes #2770 (#2771) 2019-12-02 12:15:23 -08:00
Alex Collins
e84c56b279 Fixes bug whereby retry does not work for CLI. Fixes #2767 (#2768) 2019-12-02 10:10:07 -08:00
Alex Collins
25a1b4ccc6 Make BeforeHookCreation the default. Fixes #2754 (#2759) 2019-12-02 09:45:47 -08:00
Alex Collins
90bc83d1f7 Adds support for /api/v1/account* via HTTP. Fixes #2664 (#2701) 2019-12-02 09:44:28 -08:00
Alex Collins
130b1e6218 Allow dot in project policy. Closes #2724 (#2755) 2019-12-01 19:15:25 -08:00
Simon Behar
af5f1a7e69 Make directory enforcer more lenient (#2716)
* Make directory enforcer more lenient and add flag

* Fixes

* Lint fixes

* Lint fixes

* Fixed test

* Minor

* Removed enforcer option

* Move directory traversal check higher up

* Go fmt

* Allow URLs

* Added test
2019-11-27 10:00:19 -08:00
Alex Collins
be93e7918b Removes log warning regarding indexer and may improve performance. Closes #1345 (#2761) 2019-11-26 19:10:03 -08:00
Alex Collins
4ea88b621d lint 2019-11-20 18:38:57 -08:00
Alex Collins
e1336f1f23 Allow you to sync local Helm apps. Fixes #2741 (#2747) 2019-11-20 18:23:11 -08:00
Alex Collins
0af64eaf9a Shows chart name in apps tiles and apps table pages. Closes #2726 (#2728) 2019-11-19 07:50:40 -08:00
Alexander Matyushentsev
9f8608c9fc Update manifests to v1.3.0 2019-11-12 17:03:24 -08:00
Alexander Matyushentsev
4c42c5fc70 Restore 'argocd app run action' backward compatibility (#2700) 2019-11-12 16:23:54 -08:00
Alexander Matyushentsev
1c01f9b7f5 Issue #2691 - Remove annoying toolbar flickering (#2692) 2019-11-12 08:53:39 -08:00
Alexander Matyushentsev
24d43e45f7 Update manifests to v1.3.0-rc5 2019-11-11 12:48:26 -08:00
Alexander Matyushentsev
22826baf46 Issue #2673 - Application controller flag is broken (#2674) 2019-11-08 16:20:52 -08:00
Alexander Matyushentsev
58675b5266 Issue #2670 - API server does not allow creating role with action 'action/*' (#2671) 2019-11-08 11:03:15 -08:00
Alexander Matyushentsev
786a94b03b Issue #2559 - Add gauge Prometheus metric which represents the number of pending manifest requests. (#2658) 2019-11-08 09:16:07 -08:00
dthomson25
79e364228a Add AnalysisRun and Experiment HealthCheck (#2579) 2019-11-07 16:41:22 -08:00
Alexander Matyushentsev
fba1c0d4f3 Issue #2659 - Fix 1.3 login regressions (#2660)
* Issue #2659 - Fix 1.3 login regressions

* Add server.go tests
2019-11-07 16:38:14 -08:00
Alexander Matyushentsev
65e427eb4f Issue #2662 - Don't parse kustomize version outout (#2663) 2019-11-07 16:20:02 -08:00
Alex Collins
a808fd2989 Use Kustomize 3 to generate manifetsts. Closes #2487 (#2510) 2019-11-06 17:57:06 -08:00
Alexander Matyushentsev
947b074d9a Issue #2645 - /api/version should not fail if unable to load tool version (#2654) 2019-11-06 16:34:16 -08:00
Alexander Matyushentsev
53252aa6a1 Issue #2655 - Application list page is not updated automatically (#2656) 2019-11-06 16:34:01 -08:00
David Hong
9df5e9560a chore: Upgrade kustomize to 3.2.1 (#2607) 2019-11-06 00:27:33 -08:00
Alex Collins
7a9f7b01f8 Use the same tools for make image to make dev-tools-image. Closes #2488 (#2511) 2019-11-05 10:32:28 -08:00
Alexander Matyushentsev
52f7a66826 Issue #2635 - Custom actions are disabled in Argo CD UI (#2636) 2019-11-04 14:32:44 -08:00
Alexander Matyushentsev
bcdaddcf69 Issue #2633 - Application list page incorrectly filter apps by label selector (#2634) 2019-11-04 10:49:51 -08:00
Alexander Matyushentsev
593715038c Update manifests to v1.3.0-rc4 2019-11-03 22:05:34 -08:00
jannfis
4f84498265 Assume git as default repository type (fixes #2622) (#2628)
* Assume git as default repository type

* Add helm repo with name in E2E tests
2019-11-03 20:19:54 -08:00
Alexander Matyushentsev
8186ff0bb9 Issue #2626 - Repo server executes unnecessary ls-remotes (#2627) 2019-11-02 23:26:00 -07:00
Alexander Matyushentsev
0432a85832 Issue #2620 - Cluster list page fails if any cluster is not reachable (#2621) 2019-11-01 15:36:10 -07:00
Alexander Matyushentsev
a0d37654b4 Issue #2616 - argocd app diff prints only first difference (#2617) 2019-11-01 15:23:07 -07:00
Alexander Matyushentsev
fe3b17322a Bump min client cache version (#2619) 2019-11-01 15:23:03 -07:00
Alexander Matyushentsev
59623b85fe Execute application label filtering on client side (#2605) 2019-10-31 11:23:42 -07:00
Alex Collins
905a8b0d75 Fix lint and merge issues 2019-10-30 17:04:43 -07:00
Alex Collins
30935e2019 Adds timeout to Helm commands. (#2570) 2019-10-30 16:42:06 -07:00
Alex Collins
f3e0e097de UI fixes for "Sync Apps" panel. (#2604) 2019-10-30 16:41:26 -07:00
Alex Collins
e1ff01cb56 Upgrade Helm to v2.15.2. Closes #2587 (#2590) 2019-10-30 15:32:58 -07:00
Alex Collins
e0de3300d2 Sets app status to unknown if there is an error. Closes #2577 (#2578) 2019-10-29 11:45:56 -07:00
Alex Collins
40cb6fa9ce Merge test from master 2019-10-29 11:45:22 -07:00
Alex Collins
11de95cbac Fixes merge issue 2019-10-29 11:37:12 -07:00
Alex Collins
c7cd2e92bb Update manifests to v1.3.0-rc3 2019-10-29 11:10:34 -07:00
Alex Collins
a4f13f5f29 codegen 2019-10-29 11:01:22 -07:00
Alexander Matyushentsev
6460de9d03 Issue #2339 - Don't update 'status.reconciledAt' unless compared with latest git version (#2581) 2019-10-29 10:54:31 -07:00
Alex Collins
6f6b03f74c Allows Helm charts to have arbitrary file names. Fixes #2549 (#2569) 2019-10-28 10:53:40 -07:00
Alex Collins
36775ae1bb Fixes panic when creating repo (#2568) 2019-10-25 12:35:47 -07:00
Alex Collins
4a360cf9f4 Update manifests to v1.3.0-rc2 2019-10-22 18:16:31 -07:00
Alexander Matyushentsev
6e56302fa4 Issue #2339 - Controller should compare with latest git revision if app has changed (#2543) 2019-10-22 15:31:20 -07:00
Alexander Matyushentsev
3d82d5aab7 Unknown child app should not affect app health (#2544) 2019-10-22 15:31:14 -07:00
Simon Behar
7df03b3c89 Redact secrets in dex logs (#2538)
* Done

* Pre-commit

* Added test

* Pre-commit

* Goimports
2019-10-22 10:46:46 -07:00
Alex Collins
e059760906 Allows Helm parameters that contains arrays or maps. (#2525) 2019-10-18 15:31:48 -07:00
jannfis
8925b52bc8 Set cookie policy to SameSite=lax and httpOnly (#2498) 2019-10-17 11:29:39 -07:00
Alex Collins
8a43840f0b Update manifests to v1.3.0-rc1 2019-10-16 13:56:05 -07:00
112 changed files with 2427 additions and 949 deletions

View File

@@ -177,7 +177,6 @@ jobs:
command: PATH=dist:$PATH make test-e2e
environment:
ARGOCD_OPTS: "--server localhost:8080 --plaintext"
ARGOCD_E2E_EXPECT_TIMEOUT: "30"
ARGOCD_E2E_K3S: "true"
- store_test_results:
path: test-results

View File

@@ -23,47 +23,16 @@ RUN apt-get update && apt-get install -y \
WORKDIR /tmp
# Install dep
ENV DEP_VERSION=0.5.0
RUN wget https://github.com/golang/dep/releases/download/v${DEP_VERSION}/dep-linux-amd64 -O /usr/local/bin/dep && \
chmod +x /usr/local/bin/dep
ADD hack/install.sh .
ADD hack/installers installers
# Install packr
ENV PACKR_VERSION=1.21.9
RUN wget https://github.com/gobuffalo/packr/releases/download/v${PACKR_VERSION}/packr_${PACKR_VERSION}_linux_amd64.tar.gz && \
tar -vxf packr*.tar.gz -C /tmp/ && \
mv /tmp/packr /usr/local/bin/packr
# Install kubectl
# NOTE: keep the version synced with https://storage.googleapis.com/kubernetes-release/release/stable.txt
ENV KUBECTL_VERSION=1.14.0
RUN curl -L -o /usr/local/bin/kubectl -LO https://storage.googleapis.com/kubernetes-release/release/v${KUBECTL_VERSION}/bin/linux/amd64/kubectl && \
chmod +x /usr/local/bin/kubectl && \
kubectl version --client
# Install ksonnet
ENV KSONNET_VERSION=0.13.1
RUN wget https://github.com/ksonnet/ksonnet/releases/download/v${KSONNET_VERSION}/ks_${KSONNET_VERSION}_linux_amd64.tar.gz && \
tar -C /tmp/ -xf ks_${KSONNET_VERSION}_linux_amd64.tar.gz && \
mv /tmp/ks_${KSONNET_VERSION}_linux_amd64/ks /usr/local/bin/ks && \
ks version
# Install helm
ENV HELM_VERSION=2.12.1
RUN wget https://storage.googleapis.com/kubernetes-helm/helm-v${HELM_VERSION}-linux-amd64.tar.gz && \
tar -C /tmp/ -xf helm-v${HELM_VERSION}-linux-amd64.tar.gz && \
mv /tmp/linux-amd64/helm /usr/local/bin/helm && \
helm version --client
ENV KUSTOMIZE_VERSION=3.1.0
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
# Install AWS IAM Authenticator
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
RUN ./install.sh dep-linux
RUN ./install.sh packr-linux
RUN ./install.sh kubectl-linux
RUN ./install.sh ksonnet-linux
RUN ./install.sh helm-linux
RUN ./install.sh kustomize-linux
RUN ./install.sh aws-iam-authenticator-linux
####################################################################################################
# Argo CD Base - used as the base for both the release and dev argocd images

View File

@@ -1 +1 @@
1.3.0
1.3.2

View File

@@ -77,7 +77,7 @@ func newCommand() *cobra.Command {
errors.CheckError(err)
settingsMgr := settings.NewSettingsManager(ctx, kubeClient, namespace)
kubectl := kube.KubectlCmd{}
kubectl := &kube.KubectlCmd{}
appController, err := controller.NewApplicationController(
namespace,
settingsMgr,

View File

@@ -8,6 +8,7 @@ import (
"io/ioutil"
"os"
"os/exec"
"regexp"
"syscall"
"github.com/ghodss/yaml"
@@ -108,7 +109,7 @@ func NewRunDexCommand() *cobra.Command {
} else {
err = ioutil.WriteFile("/tmp/dex.yaml", dexCfgBytes, 0644)
errors.CheckError(err)
log.Info(string(dexCfgBytes))
log.Info(redactor(string(dexCfgBytes)))
cmd = exec.Command("dex", "serve", "/tmp/dex.yaml")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
@@ -532,6 +533,11 @@ func NewClusterConfig() *cobra.Command {
return command
}
func redactor(dirtyString string) string {
dirtyString = regexp.MustCompile("(clientSecret: )[^ \n]*").ReplaceAllString(dirtyString, "$1********")
return regexp.MustCompile("(secret: )[^ \n]*").ReplaceAllString(dirtyString, "$1********")
}
func main() {
if err := NewCommand().Execute(); err != nil {
fmt.Println(err)

View File

@@ -0,0 +1,73 @@
package main
import (
"testing"
"github.com/stretchr/testify/assert"
)
var textToRedact = `
- config:
clientID: aabbccddeeff00112233
clientSecret: $dex.github.clientSecret
orgs:
- name: your-github-org
redirectURI: https://argocd.example.com/api/dex/callback
id: github
name: GitHub
type: github
grpc:
addr: 0.0.0.0:5557
issuer: https://argocd.example.com/api/dex
oauth2:
skipApprovalScreen: true
staticClients:
- id: argo-cd
name: Argo CD
redirectURIs:
- https://argocd.example.com/auth/callback
secret: Dis9M-GA11oTwZVQQWdDklPQw-sWXZkWJFyyEhMs
- id: argo-cd-cli
name: Argo CD CLI
public: true
redirectURIs:
- http://localhost
storage:
type: memory
web:
http: 0.0.0.0:5556`
var expectedRedaction = `
- config:
clientID: aabbccddeeff00112233
clientSecret: ********
orgs:
- name: your-github-org
redirectURI: https://argocd.example.com/api/dex/callback
id: github
name: GitHub
type: github
grpc:
addr: 0.0.0.0:5557
issuer: https://argocd.example.com/api/dex
oauth2:
skipApprovalScreen: true
staticClients:
- id: argo-cd
name: Argo CD
redirectURIs:
- https://argocd.example.com/auth/callback
secret: ********
- id: argo-cd-cli
name: Argo CD CLI
public: true
redirectURIs:
- http://localhost
storage:
type: memory
web:
http: 0.0.0.0:5556`
func TestSecretsRedactor(t *testing.T) {
assert.Equal(t, expectedRedaction, redactor(textToRedact))
}

View File

@@ -972,8 +972,7 @@ func NewApplicationDiffCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
}
foundDiffs = true
err = diff.PrintDiff(item.key.Name, target, live)
errors.CheckError(err)
_ = diff.PrintDiff(item.key.Name, target, live)
}
}
if foundDiffs {
@@ -1298,7 +1297,7 @@ func NewApplicationSyncCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
cluster, err := clusterIf.Get(context.Background(), &clusterpkg.ClusterQuery{Server: app.Spec.Destination.Server})
errors.CheckError(err)
util.Close(conn)
localObjsStrings = getLocalObjectsString(app, local, cluster.ServerVersion, argoSettings.AppLabelKey, argoSettings.KustomizeOptions)
localObjsStrings = getLocalObjectsString(app, local, argoSettings.AppLabelKey, cluster.ServerVersion, argoSettings.KustomizeOptions)
}
syncReq := applicationpkg.ApplicationSyncRequest{
@@ -1962,7 +1961,7 @@ func filterResources(command *cobra.Command, resources []*argoappv1.ResourceDiff
if resourceName != "" && resourceName != obj.GetName() {
continue
}
if kind != "" && kind != gvk.Kind {
if kind != gvk.Kind {
continue
}
copy := obj.DeepCopy()

View File

@@ -7,7 +7,6 @@ import (
"log"
"os"
"strconv"
"strings"
"text/tabwriter"
"github.com/ghodss/yaml"
@@ -121,7 +120,8 @@ func NewApplicationResourceActionsListCommand(clientOpts *argocdclient.ClientOpt
func NewApplicationResourceActionsRunCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var namespace string
var resourceName string
var kindArg string
var kind string
var group string
var all bool
var command = &cobra.Command{
Use: "run APPNAME ACTION",
@@ -130,7 +130,9 @@ func NewApplicationResourceActionsRunCommand(clientOpts *argocdclient.ClientOpti
command.Flags().StringVar(&resourceName, "resource-name", "", "Name of resource")
command.Flags().StringVar(&namespace, "namespace", "", "Namespace")
command.Flags().StringVar(&kindArg, "kind", "", "Kind")
command.Flags().StringVar(&kind, "kind", "", "Kind")
command.Flags().StringVar(&group, "group", "", "Group")
errors.CheckError(command.MarkFlagRequired("kind"))
command.Flags().BoolVar(&all, "all", false, "Indicates whether to run the action on multiple matching resources")
command.Run = func(c *cobra.Command, args []string) {
@@ -146,31 +148,14 @@ func NewApplicationResourceActionsRunCommand(clientOpts *argocdclient.ClientOpti
ctx := context.Background()
resources, err := appIf.ManagedResources(ctx, &applicationpkg.ResourcesQuery{ApplicationName: &appName})
errors.CheckError(err)
var group string
var kind string
var actionNameOnly string
// Backwards comparability for running resume actions
if actionName == "resume" && kindArg == "Rollout" {
group = "argoproj.io"
kind = "Rollout"
actionNameOnly = "resume"
commandTail := ""
if resourceName != "" {
commandTail += " --resource-name " + resourceName
filteredObjects := filterResources(command, resources.Items, group, kind, namespace, resourceName, all)
var resGroup = filteredObjects[0].GroupVersionKind().Group
for i := range filteredObjects[1:] {
if filteredObjects[i].GroupVersionKind().Group != resGroup {
log.Fatal("Ambiguous resource group. Use flag --group to specify resource group explicitly.")
}
if namespace != "" {
commandTail += " --namespace " + namespace
}
if all {
commandTail += " --all"
}
fmt.Printf("\nWarning: this syntax for running the \"resume\" action has been deprecated. Please run the action as\n\n\targocd app actions run %s argoproj.io/Rollout/resume%s\n\n", appName, commandTail)
} else {
group, kind, actionNameOnly = parseActionName(actionName)
}
filteredObjects := filterResources(command, resources.Items, group, kind, namespace, resourceName, all)
for i := range filteredObjects {
obj := filteredObjects[i]
gvk := obj.GroupVersionKind()
@@ -181,18 +166,10 @@ func NewApplicationResourceActionsRunCommand(clientOpts *argocdclient.ClientOpti
ResourceName: objResourceName,
Group: gvk.Group,
Kind: gvk.Kind,
Action: actionNameOnly,
Action: actionName,
})
errors.CheckError(err)
}
}
return command
}
func parseActionName(action string) (string, string, string) {
actionSplit := strings.Split(action, "/")
if len(actionSplit) != 3 {
log.Fatal("Action name is malformed")
}
return actionSplit[0], actionSplit[1], actionSplit[2]
}

View File

@@ -10,6 +10,7 @@ import (
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/argoproj/argo-cd/common"
"github.com/argoproj/argo-cd/errors"
argocdclient "github.com/argoproj/argo-cd/pkg/apiclient"
repositorypkg "github.com/argoproj/argo-cd/pkg/apiclient/repository"
@@ -50,13 +51,20 @@ func NewRepoAddCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
)
// For better readability and easier formatting
var repoAddExamples = `
Add a SSH repository using a private key for authentication, ignoring the server's host key:",
$ argocd repo add git@git.example.com --insecure-ignore-host-key --ssh-private-key-path ~/id_rsa",
Add a HTTPS repository using username/password and TLS client certificates:",
$ argocd repo add https://git.example.com --username git --password secret --tls-client-cert-path ~/mycert.crt --tls-client-cert-key-path ~/mycert.key",
Add a HTTPS repository using username/password without verifying the server's TLS certificate:",
$ argocd repo add https://git.example.com --username git --password secret --insecure-skip-server-verification",
var repoAddExamples = ` # Add a Git repository via SSH using a private key for authentication, ignoring the server's host key:
argocd repo add git@git.example.com:repos/repo --insecure-ignore-host-key --ssh-private-key-path ~/id_rsa
# Add a private Git repository via HTTPS using username/password and TLS client certificates:
argocd repo add https://git.example.com/repos/repo --username git --password secret --tls-client-cert-path ~/mycert.crt --tls-client-cert-key-path ~/mycert.key
# Add a private Git repository via HTTPS using username/password without verifying the server's TLS certificate
argocd repo add https://git.example.com/repos/repo --username git --password secret --insecure-skip-server-verification
# Add a public Helm repository named 'stable' via HTTPS
argocd repo add https://kubernetes-charts.storage.googleapis.com --type helm --name stable
# Add a private Helm repository named 'stable' via HTTPS
argocd repo add https://kubernetes-charts.storage.googleapis.com --type helm --name stable --username test --password test
`
var command = &cobra.Command{
@@ -113,6 +121,10 @@ Add a HTTPS repository using username/password without verifying the server's TL
repo.Insecure = insecureSkipServerVerification
repo.EnableLFS = enableLfs
if repo.Type == "helm" && repo.Name == "" {
errors.CheckError(fmt.Errorf("Must specify --name for repos of type 'helm'"))
}
conn, repoIf := argocdclient.NewClientOrDie(clientOpts).NewRepoClientOrDie()
defer util.Close(conn)
@@ -148,8 +160,8 @@ Add a HTTPS repository using username/password without verifying the server's TL
fmt.Printf("repository '%s' added\n", createdRepo.Repo)
},
}
command.Flags().StringVar(&repo.Type, "type", "", "type of the repository, \"git\" or \"helm\"")
command.Flags().StringVar(&repo.Name, "name", "", "name of the repository")
command.Flags().StringVar(&repo.Type, "type", common.DefaultRepoType, "type of the repository, \"git\" or \"helm\"")
command.Flags().StringVar(&repo.Name, "name", "", "name of the repository, mandatory for repositories of type helm")
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)")

View File

@@ -21,9 +21,10 @@ const (
ArgoCDTLSCertsConfigMapName = "argocd-tls-certs-cm"
)
// Default system namespace
// Some default configurables
const (
DefaultSystemNamespace = "kube-system"
DefaultRepoType = "git"
)
// Default listener ports for ArgoCD components

View File

@@ -136,7 +136,8 @@ func NewApplicationController(
if err != nil {
return nil, err
}
projInformer := v1alpha1.NewAppProjectInformer(applicationClientset, namespace, appResyncPeriod, cache.Indexers{})
indexers := cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}
projInformer := v1alpha1.NewAppProjectInformer(applicationClientset, namespace, appResyncPeriod, indexers)
metricsAddr := fmt.Sprintf("0.0.0.0:%d", metricsPort)
ctrl.metricsServer = metrics.NewMetricsServer(metricsAddr, appLister, func() error {
_, err := kubeClientset.Discovery().ServerVersion()
@@ -803,6 +804,7 @@ func (ctrl *ApplicationController) processAppRefreshQueueItem() (processNext boo
revision = app.Status.Sync.Revision
}
observedAt := metav1.Now()
compareResult := ctrl.appStateManager.CompareAppState(app, revision, app.Spec.Source, refreshType == appv1.RefreshTypeHard, localManifests)
ctrl.normalizeApplication(origApp, app)
@@ -830,8 +832,10 @@ func (ctrl *ApplicationController) processAppRefreshQueueItem() (processNext boo
}
}
app.Status.ObservedAt = &compareResult.reconciledAt
app.Status.ReconciledAt = &compareResult.reconciledAt
if app.Status.ReconciledAt == nil || comparisonLevel == CompareWithLatest {
app.Status.ReconciledAt = &observedAt
}
app.Status.ObservedAt = &observedAt
app.Status.Sync = *compareResult.syncStatus
app.Status.Health = *compareResult.healthStatus
app.Status.Resources = compareResult.resources
@@ -850,23 +854,22 @@ func (ctrl *ApplicationController) needRefreshAppStatus(app *appv1.Application,
compareWith := CompareWithLatest
refreshType := appv1.RefreshTypeNormal
expired := app.Status.ReconciledAt == nil || app.Status.ReconciledAt.Add(statusRefreshTimeout).Before(time.Now().UTC())
if requestedType, ok := app.IsRefreshRequested(); ok || expired {
if ok {
refreshType = requestedType
reason = fmt.Sprintf("%s refresh requested", refreshType)
} else if expired {
reason = fmt.Sprintf("comparison expired. reconciledAt: %v, expiry: %v", app.Status.ReconciledAt, statusRefreshTimeout)
}
} else if requested, level := ctrl.isRefreshRequested(app.Name); requested {
compareWith = level
reason = fmt.Sprintf("controller refresh requested")
} else if app.Status.Sync.Status == appv1.SyncStatusCodeUnknown && expired {
reason = "comparison status unknown"
if requestedType, ok := app.IsRefreshRequested(); ok {
// user requested app refresh.
refreshType = requestedType
reason = fmt.Sprintf("%s refresh requested", refreshType)
} else if expired {
reason = fmt.Sprintf("comparison expired. reconciledAt: %v, expiry: %v", app.Status.ReconciledAt, statusRefreshTimeout)
} else if !app.Spec.Source.Equals(app.Status.Sync.ComparedTo.Source) {
reason = "spec.source differs"
} else if !app.Spec.Destination.Equals(app.Status.Sync.ComparedTo.Destination) {
reason = "spec.destination differs"
} else if requested, level := ctrl.isRefreshRequested(app.Name); requested {
compareWith = level
reason = fmt.Sprintf("controller refresh requested")
}
if reason != "" {
logCtx.Infof("Refreshing app status (%s), level (%d)", reason, compareWith)
return true, refreshType, compareWith

View File

@@ -2,6 +2,7 @@ package controller
import (
"context"
"encoding/json"
"testing"
"time"
@@ -597,6 +598,24 @@ func TestNeedRefreshAppStatus(t *testing.T) {
assert.Equal(t, argoappv1.RefreshTypeHard, refreshType)
assert.Equal(t, CompareWithLatest, compareWith)
}
{
app := app.DeepCopy()
// ensure that CompareWithLatest level is used if application source has changed
ctrl.requestAppRefresh(app.Name, ComparisonWithNothing)
// sample app source change
app.Spec.Source.Helm = &argoappv1.ApplicationSourceHelm{
Parameters: []argoappv1.HelmParameter{{
Name: "foo",
Value: "bar",
}},
}
needRefresh, refreshType, compareWith = ctrl.needRefreshAppStatus(app, 1*time.Hour)
assert.True(t, needRefresh)
assert.Equal(t, argoappv1.RefreshTypeNormal, refreshType)
assert.Equal(t, CompareWithLatest, compareWith)
}
}
func TestRefreshAppConditions(t *testing.T) {
@@ -651,3 +670,63 @@ func TestRefreshAppConditions(t *testing.T) {
assert.Equal(t, "Application referencing project wrong project which does not exist", app.Status.Conditions[0].Message)
})
}
func TestUpdateReconciledAt(t *testing.T) {
app := newFakeApp()
reconciledAt := metav1.NewTime(time.Now().Add(-1 * time.Second))
app.Status = argoappv1.ApplicationStatus{ReconciledAt: &reconciledAt}
app.Status.Sync = argoappv1.SyncStatus{ComparedTo: argoappv1.ComparedTo{Source: app.Spec.Source, Destination: app.Spec.Destination}}
ctrl := newFakeController(&fakeData{
apps: []runtime.Object{app, &defaultProj},
manifestResponse: &apiclient.ManifestResponse{
Manifests: []string{},
Namespace: test.FakeDestNamespace,
Server: test.FakeClusterURL,
Revision: "abc123",
},
managedLiveObjs: make(map[kube.ResourceKey]*unstructured.Unstructured),
})
key, _ := cache.MetaNamespaceKeyFunc(app)
fakeAppCs := ctrl.applicationClientset.(*appclientset.Clientset)
fakeAppCs.ReactionChain = nil
receivedPatch := map[string]interface{}{}
fakeAppCs.AddReactor("patch", "*", func(action kubetesting.Action) (handled bool, ret runtime.Object, err error) {
if patchAction, ok := action.(kubetesting.PatchAction); ok {
assert.NoError(t, json.Unmarshal(patchAction.GetPatch(), &receivedPatch))
}
return true, nil, nil
})
t.Run("UpdatedOnFullReconciliation", func(t *testing.T) {
receivedPatch = map[string]interface{}{}
ctrl.requestAppRefresh(app.Name, CompareWithLatest)
ctrl.appRefreshQueue.Add(key)
ctrl.processAppRefreshQueueItem()
_, updated, err := unstructured.NestedString(receivedPatch, "status", "reconciledAt")
assert.NoError(t, err)
assert.True(t, updated)
_, updated, err = unstructured.NestedString(receivedPatch, "status", "observedAt")
assert.NoError(t, err)
assert.True(t, updated)
})
t.Run("NotUpdatedOnPartialReconciliation", func(t *testing.T) {
receivedPatch = map[string]interface{}{}
ctrl.appRefreshQueue.Add(key)
ctrl.requestAppRefresh(app.Name, CompareWithRecent)
ctrl.processAppRefreshQueueItem()
_, updated, err := unstructured.NestedString(receivedPatch, "status", "reconciledAt")
assert.NoError(t, err)
assert.False(t, updated)
_, updated, err = unstructured.NestedString(receivedPatch, "status", "observedAt")
assert.NoError(t, err)
assert.True(t, updated)
})
}

View File

@@ -63,7 +63,6 @@ type AppStateManager interface {
}
type comparisonResult struct {
reconciledAt metav1.Time
syncStatus *v1alpha1.SyncStatus
healthStatus *v1alpha1.HealthStatus
resources []v1alpha1.ResourceStatus
@@ -275,13 +274,11 @@ func (m *appStateManager) getComparisonSettings(app *appv1.Application) (string,
// revision and supplied source. If revision or overrides are empty, then compares against
// revision and overrides in the app spec.
func (m *appStateManager) CompareAppState(app *v1alpha1.Application, revision string, source v1alpha1.ApplicationSource, noCache bool, localManifests []string) *comparisonResult {
reconciledAt := metav1.Now()
appLabelKey, resourceOverrides, diffNormalizer, err := m.getComparisonSettings(app)
// return unknown comparison result if basic comparison settings cannot be loaded
if err != nil {
return &comparisonResult{
reconciledAt: reconciledAt,
syncStatus: &v1alpha1.SyncStatus{
ComparedTo: appv1.ComparedTo{Source: source, Destination: app.Spec.Destination},
Status: appv1.SyncStatusCodeUnknown,
@@ -435,6 +432,10 @@ func (m *appStateManager) CompareAppState(app *v1alpha1.Application, revision st
} else {
resState.Status = v1alpha1.SyncStatusCodeSynced
}
// we can't say anything about the status if we were unable to get the target objects
if failedToLoadObjs {
resState.Status = v1alpha1.SyncStatusCodeUnknown
}
managedResources[i] = managedResource{
Name: resState.Name,
Namespace: resState.Namespace,
@@ -472,7 +473,6 @@ func (m *appStateManager) CompareAppState(app *v1alpha1.Application, revision st
}
compRes := comparisonResult{
reconciledAt: reconciledAt,
syncStatus: &syncStatus,
healthStatus: healthStatus,
resources: resourceSummaries,

View File

@@ -354,7 +354,6 @@ func TestReturnUnknownComparisonStateOnSettingLoadError(t *testing.T) {
assert.Equal(t, argoappv1.HealthStatusUnknown, compRes.healthStatus.Status)
assert.Equal(t, argoappv1.SyncStatusCodeUnknown, compRes.syncStatus.Status)
assert.NotNil(t, compRes.reconciledAt)
}
func TestSetManagedResourcesKnownOrphanedResourceExceptions(t *testing.T) {

View File

@@ -56,6 +56,8 @@ Ensure dependencies are up to date first:
dep ensure
make dev-builder-image
make install-lint-tools
go get github.com/mattn/goreman
go get github.com/jstemmer/go-junit-report
```
Common make targets:

View File

@@ -0,0 +1,8 @@
#!/bin/bash
set -eux -o pipefail
AWS_IAM_AUTHENTICATOR_VERSION=0.4.0-alpha.1
[ -e $DOWNLOADS/aws-iam-authenticator ] || curl -sLf --retry 3 -o $DOWNLOADS/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
cp $DOWNLOADS/aws-iam-authenticator $BIN/
chmod +x $BIN/aws-iam-authenticator
aws-iam-authenticator version

View File

@@ -1,7 +1,7 @@
#!/bin/bash
set -eux -o pipefail
[ -e $DOWNLOADS/helm.tar.gz ] || curl -sLf --retry 3 -o $DOWNLOADS/helm.tar.gz https://storage.googleapis.com/kubernetes-helm/helm-v2.13.1-linux-amd64.tar.gz
[ -e $DOWNLOADS/helm.tar.gz ] || curl -sLf --retry 3 -o $DOWNLOADS/helm.tar.gz https://storage.googleapis.com/kubernetes-helm/helm-v2.15.2-linux-amd64.tar.gz
tar -C /tmp/ -xf $DOWNLOADS/helm.tar.gz
cp /tmp/linux-amd64/helm $BIN/helm
helm version --client

View File

@@ -1,11 +1,25 @@
#!/bin/bash
set -eux -o pipefail
# TODO we use v2 for generating manifests, v3 for production - we should always use v3
KUSTOMIZE_VERSION=${KUSTOMIZE_VERSION:-3.1.0}
KUSTOMIZE_VERSION=${KUSTOMIZE_VERSION:-3.2.1}
DL=$DOWNLOADS/kustomize-${KUSTOMIZE_VERSION}
[ -e $DL ] || curl -sLf --retry 3 -o $DL https://github.com/kubernetes-sigs/kustomize/releases/download/v${KUSTOMIZE_VERSION}/kustomize_${KUSTOMIZE_VERSION}_linux_amd64
# Note that kustomize release URIs have changed for v3.2.1. Then again for
# v3.3.0. When upgrading to versions >= v3.3.0 please change the URI format. And
# also note that as of version v3.3.0, assets are in .tar.gz form.
# v3.2.0 = https://github.com/kubernetes-sigs/kustomize/releases/download/v3.2.0/kustomize_3.2.0_linux_amd64
# v3.2.1 = https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize/v3.2.1/kustomize_kustomize.v3.2.1_linux_amd64
# v3.3.0 = https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize/v3.3.0/kustomize_v3.3.0_linux_amd64.tar.gz
case $KUSTOMIZE_VERSION in
2.*)
URL=https://github.com/kubernetes-sigs/kustomize/releases/download/v${KUSTOMIZE_VERSION}/kustomize_${KUSTOMIZE_VERSION}_linux_amd64
;;
*)
URL=https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize/v${KUSTOMIZE_VERSION}/kustomize_kustomize.v${KUSTOMIZE_VERSION}_linux_amd64
;;
esac
[ -e $DL ] || curl -sLf --retry 3 -o $DL $URL
cp $DL $BIN/kustomize
chmod +x $BIN/kustomize
kustomize version

View File

@@ -0,0 +1,9 @@
#!/bin/bash
set -eux -o pipefail
PACKR_VERSION=1.21.9
[ -e $DOWNLOADS/parkr.tar.gz ] || curl -sLf --retry 3 -o $DOWNLOADS/parkr.tar.gz https://github.com/gobuffalo/packr/releases/download/v${PACKR_VERSION}/packr_${PACKR_VERSION}_linux_amd64.tar.gz
tar -vxf $DOWNLOADS/parkr.tar.gz -C /tmp/
cp /tmp/packr $BIN/
chmod +x $BIN/packr
packr version

View File

@@ -12,7 +12,7 @@ bases:
images:
- name: argoproj/argocd
newName: argoproj/argocd
newTag: latest
newTag: v1.3.2
- name: argoproj/argocd-ui
newName: argoproj/argocd-ui
newTag: latest
newTag: v1.3.2

View File

@@ -1032,6 +1032,8 @@ spec:
type: object
type: array
observedAt:
description: ObservedAt indicates when the application state was updated
without querying latest git state
format: date-time
type: string
operationState:
@@ -1509,6 +1511,8 @@ spec:
- startedAt
type: object
reconciledAt:
description: ReconciledAt indicates when the application state was reconciled
using the latest git version
format: date-time
type: string
resources:

View File

@@ -18,7 +18,7 @@ bases:
images:
- name: argoproj/argocd
newName: argoproj/argocd
newTag: latest
newTag: v1.3.2
- name: argoproj/argocd-ui
newName: argoproj/argocd-ui
newTag: latest
newTag: v1.3.2

View File

@@ -1033,6 +1033,8 @@ spec:
type: object
type: array
observedAt:
description: ObservedAt indicates when the application state was updated
without querying latest git state
format: date-time
type: string
operationState:
@@ -1510,6 +1512,8 @@ spec:
- startedAt
type: object
reconciledAt:
description: ReconciledAt indicates when the application state was reconciled
using the latest git version
format: date-time
type: string
resources:
@@ -2978,7 +2982,7 @@ spec:
- argocd-redis-ha-announce-2:26379
- --sentinelmaster
- argocd
image: argoproj/argocd:latest
image: argoproj/argocd:v1.3.2
imagePullPolicy: Always
livenessProbe:
httpGet:
@@ -3032,7 +3036,7 @@ spec:
- cp
- /usr/local/bin/argocd-util
- /shared
image: argoproj/argocd:latest
image: argoproj/argocd:v1.3.2
imagePullPolicy: Always
name: copyutil
volumeMounts:
@@ -3088,7 +3092,7 @@ spec:
- argocd-redis-ha-announce-2:26379
- --sentinelmaster
- argocd
image: argoproj/argocd:latest
image: argoproj/argocd:v1.3.2
imagePullPolicy: Always
livenessProbe:
initialDelaySeconds: 5
@@ -3162,7 +3166,7 @@ spec:
- argocd-redis-ha-announce-2:26379
- --sentinelmaster
- argocd
image: argoproj/argocd:latest
image: argoproj/argocd:v1.3.2
imagePullPolicy: Always
livenessProbe:
httpGet:

View File

@@ -1033,6 +1033,8 @@ spec:
type: object
type: array
observedAt:
description: ObservedAt indicates when the application state was updated
without querying latest git state
format: date-time
type: string
operationState:
@@ -1510,6 +1512,8 @@ spec:
- startedAt
type: object
reconciledAt:
description: ReconciledAt indicates when the application state was reconciled
using the latest git version
format: date-time
type: string
resources:
@@ -2893,7 +2897,7 @@ spec:
- argocd-redis-ha-announce-2:26379
- --sentinelmaster
- argocd
image: argoproj/argocd:latest
image: argoproj/argocd:v1.3.2
imagePullPolicy: Always
livenessProbe:
httpGet:
@@ -2947,7 +2951,7 @@ spec:
- cp
- /usr/local/bin/argocd-util
- /shared
image: argoproj/argocd:latest
image: argoproj/argocd:v1.3.2
imagePullPolicy: Always
name: copyutil
volumeMounts:
@@ -3003,7 +3007,7 @@ spec:
- argocd-redis-ha-announce-2:26379
- --sentinelmaster
- argocd
image: argoproj/argocd:latest
image: argoproj/argocd:v1.3.2
imagePullPolicy: Always
livenessProbe:
initialDelaySeconds: 5
@@ -3077,7 +3081,7 @@ spec:
- argocd-redis-ha-announce-2:26379
- --sentinelmaster
- argocd
image: argoproj/argocd:latest
image: argoproj/argocd:v1.3.2
imagePullPolicy: Always
livenessProbe:
httpGet:

View File

@@ -1033,6 +1033,8 @@ spec:
type: object
type: array
observedAt:
description: ObservedAt indicates when the application state was updated
without querying latest git state
format: date-time
type: string
operationState:
@@ -1510,6 +1512,8 @@ spec:
- startedAt
type: object
reconciledAt:
description: ReconciledAt indicates when the application state was reconciled
using the latest git version
format: date-time
type: string
resources:
@@ -2742,7 +2746,7 @@ spec:
- "20"
- --operation-processors
- "10"
image: argoproj/argocd:latest
image: argoproj/argocd:v1.3.2
imagePullPolicy: Always
livenessProbe:
httpGet:
@@ -2796,7 +2800,7 @@ spec:
- cp
- /usr/local/bin/argocd-util
- /shared
image: argoproj/argocd:latest
image: argoproj/argocd:v1.3.2
imagePullPolicy: Always
name: copyutil
volumeMounts:
@@ -2860,7 +2864,7 @@ spec:
- argocd-repo-server
- --redis
- argocd-redis:6379
image: argoproj/argocd:latest
image: argoproj/argocd:v1.3.2
imagePullPolicy: Always
livenessProbe:
initialDelaySeconds: 5
@@ -2911,7 +2915,7 @@ spec:
- argocd-server
- --staticassets
- /shared/app
image: argoproj/argocd:latest
image: argoproj/argocd:v1.3.2
imagePullPolicy: Always
livenessProbe:
httpGet:

View File

@@ -1033,6 +1033,8 @@ spec:
type: object
type: array
observedAt:
description: ObservedAt indicates when the application state was updated
without querying latest git state
format: date-time
type: string
operationState:
@@ -1510,6 +1512,8 @@ spec:
- startedAt
type: object
reconciledAt:
description: ReconciledAt indicates when the application state was reconciled
using the latest git version
format: date-time
type: string
resources:
@@ -2657,7 +2661,7 @@ spec:
- "20"
- --operation-processors
- "10"
image: argoproj/argocd:latest
image: argoproj/argocd:v1.3.2
imagePullPolicy: Always
livenessProbe:
httpGet:
@@ -2711,7 +2715,7 @@ spec:
- cp
- /usr/local/bin/argocd-util
- /shared
image: argoproj/argocd:latest
image: argoproj/argocd:v1.3.2
imagePullPolicy: Always
name: copyutil
volumeMounts:
@@ -2775,7 +2779,7 @@ spec:
- argocd-repo-server
- --redis
- argocd-redis:6379
image: argoproj/argocd:latest
image: argoproj/argocd:v1.3.2
imagePullPolicy: Always
livenessProbe:
initialDelaySeconds: 5
@@ -2826,7 +2830,7 @@ spec:
- argocd-server
- --staticassets
- /shared/app
image: argoproj/argocd:latest
image: argoproj/argocd:v1.3.2
imagePullPolicy: Always
livenessProbe:
httpGet:

View File

@@ -33,7 +33,7 @@ const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package
func (m *AWSAuthConfig) Reset() { *m = AWSAuthConfig{} }
func (*AWSAuthConfig) ProtoMessage() {}
func (*AWSAuthConfig) Descriptor() ([]byte, []int) {
return fileDescriptor_generated_622b11e42e23cc72, []int{0}
return fileDescriptor_generated_b34c2064a02cfa33, []int{0}
}
func (m *AWSAuthConfig) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -61,7 +61,7 @@ var xxx_messageInfo_AWSAuthConfig proto.InternalMessageInfo
func (m *AppProject) Reset() { *m = AppProject{} }
func (*AppProject) ProtoMessage() {}
func (*AppProject) Descriptor() ([]byte, []int) {
return fileDescriptor_generated_622b11e42e23cc72, []int{1}
return fileDescriptor_generated_b34c2064a02cfa33, []int{1}
}
func (m *AppProject) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -89,7 +89,7 @@ var xxx_messageInfo_AppProject proto.InternalMessageInfo
func (m *AppProjectList) Reset() { *m = AppProjectList{} }
func (*AppProjectList) ProtoMessage() {}
func (*AppProjectList) Descriptor() ([]byte, []int) {
return fileDescriptor_generated_622b11e42e23cc72, []int{2}
return fileDescriptor_generated_b34c2064a02cfa33, []int{2}
}
func (m *AppProjectList) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -117,7 +117,7 @@ var xxx_messageInfo_AppProjectList proto.InternalMessageInfo
func (m *AppProjectSpec) Reset() { *m = AppProjectSpec{} }
func (*AppProjectSpec) ProtoMessage() {}
func (*AppProjectSpec) Descriptor() ([]byte, []int) {
return fileDescriptor_generated_622b11e42e23cc72, []int{3}
return fileDescriptor_generated_b34c2064a02cfa33, []int{3}
}
func (m *AppProjectSpec) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -145,7 +145,7 @@ var xxx_messageInfo_AppProjectSpec proto.InternalMessageInfo
func (m *Application) Reset() { *m = Application{} }
func (*Application) ProtoMessage() {}
func (*Application) Descriptor() ([]byte, []int) {
return fileDescriptor_generated_622b11e42e23cc72, []int{4}
return fileDescriptor_generated_b34c2064a02cfa33, []int{4}
}
func (m *Application) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -173,7 +173,7 @@ var xxx_messageInfo_Application proto.InternalMessageInfo
func (m *ApplicationCondition) Reset() { *m = ApplicationCondition{} }
func (*ApplicationCondition) ProtoMessage() {}
func (*ApplicationCondition) Descriptor() ([]byte, []int) {
return fileDescriptor_generated_622b11e42e23cc72, []int{5}
return fileDescriptor_generated_b34c2064a02cfa33, []int{5}
}
func (m *ApplicationCondition) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -201,7 +201,7 @@ var xxx_messageInfo_ApplicationCondition proto.InternalMessageInfo
func (m *ApplicationDestination) Reset() { *m = ApplicationDestination{} }
func (*ApplicationDestination) ProtoMessage() {}
func (*ApplicationDestination) Descriptor() ([]byte, []int) {
return fileDescriptor_generated_622b11e42e23cc72, []int{6}
return fileDescriptor_generated_b34c2064a02cfa33, []int{6}
}
func (m *ApplicationDestination) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -229,7 +229,7 @@ var xxx_messageInfo_ApplicationDestination proto.InternalMessageInfo
func (m *ApplicationList) Reset() { *m = ApplicationList{} }
func (*ApplicationList) ProtoMessage() {}
func (*ApplicationList) Descriptor() ([]byte, []int) {
return fileDescriptor_generated_622b11e42e23cc72, []int{7}
return fileDescriptor_generated_b34c2064a02cfa33, []int{7}
}
func (m *ApplicationList) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -257,7 +257,7 @@ var xxx_messageInfo_ApplicationList proto.InternalMessageInfo
func (m *ApplicationSource) Reset() { *m = ApplicationSource{} }
func (*ApplicationSource) ProtoMessage() {}
func (*ApplicationSource) Descriptor() ([]byte, []int) {
return fileDescriptor_generated_622b11e42e23cc72, []int{8}
return fileDescriptor_generated_b34c2064a02cfa33, []int{8}
}
func (m *ApplicationSource) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -285,7 +285,7 @@ var xxx_messageInfo_ApplicationSource proto.InternalMessageInfo
func (m *ApplicationSourceDirectory) Reset() { *m = ApplicationSourceDirectory{} }
func (*ApplicationSourceDirectory) ProtoMessage() {}
func (*ApplicationSourceDirectory) Descriptor() ([]byte, []int) {
return fileDescriptor_generated_622b11e42e23cc72, []int{9}
return fileDescriptor_generated_b34c2064a02cfa33, []int{9}
}
func (m *ApplicationSourceDirectory) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -313,7 +313,7 @@ var xxx_messageInfo_ApplicationSourceDirectory proto.InternalMessageInfo
func (m *ApplicationSourceHelm) Reset() { *m = ApplicationSourceHelm{} }
func (*ApplicationSourceHelm) ProtoMessage() {}
func (*ApplicationSourceHelm) Descriptor() ([]byte, []int) {
return fileDescriptor_generated_622b11e42e23cc72, []int{10}
return fileDescriptor_generated_b34c2064a02cfa33, []int{10}
}
func (m *ApplicationSourceHelm) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -341,7 +341,7 @@ var xxx_messageInfo_ApplicationSourceHelm proto.InternalMessageInfo
func (m *ApplicationSourceJsonnet) Reset() { *m = ApplicationSourceJsonnet{} }
func (*ApplicationSourceJsonnet) ProtoMessage() {}
func (*ApplicationSourceJsonnet) Descriptor() ([]byte, []int) {
return fileDescriptor_generated_622b11e42e23cc72, []int{11}
return fileDescriptor_generated_b34c2064a02cfa33, []int{11}
}
func (m *ApplicationSourceJsonnet) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -369,7 +369,7 @@ var xxx_messageInfo_ApplicationSourceJsonnet proto.InternalMessageInfo
func (m *ApplicationSourceKsonnet) Reset() { *m = ApplicationSourceKsonnet{} }
func (*ApplicationSourceKsonnet) ProtoMessage() {}
func (*ApplicationSourceKsonnet) Descriptor() ([]byte, []int) {
return fileDescriptor_generated_622b11e42e23cc72, []int{12}
return fileDescriptor_generated_b34c2064a02cfa33, []int{12}
}
func (m *ApplicationSourceKsonnet) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -397,7 +397,7 @@ var xxx_messageInfo_ApplicationSourceKsonnet proto.InternalMessageInfo
func (m *ApplicationSourceKustomize) Reset() { *m = ApplicationSourceKustomize{} }
func (*ApplicationSourceKustomize) ProtoMessage() {}
func (*ApplicationSourceKustomize) Descriptor() ([]byte, []int) {
return fileDescriptor_generated_622b11e42e23cc72, []int{13}
return fileDescriptor_generated_b34c2064a02cfa33, []int{13}
}
func (m *ApplicationSourceKustomize) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -425,7 +425,7 @@ var xxx_messageInfo_ApplicationSourceKustomize proto.InternalMessageInfo
func (m *ApplicationSourcePlugin) Reset() { *m = ApplicationSourcePlugin{} }
func (*ApplicationSourcePlugin) ProtoMessage() {}
func (*ApplicationSourcePlugin) Descriptor() ([]byte, []int) {
return fileDescriptor_generated_622b11e42e23cc72, []int{14}
return fileDescriptor_generated_b34c2064a02cfa33, []int{14}
}
func (m *ApplicationSourcePlugin) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -453,7 +453,7 @@ var xxx_messageInfo_ApplicationSourcePlugin proto.InternalMessageInfo
func (m *ApplicationSpec) Reset() { *m = ApplicationSpec{} }
func (*ApplicationSpec) ProtoMessage() {}
func (*ApplicationSpec) Descriptor() ([]byte, []int) {
return fileDescriptor_generated_622b11e42e23cc72, []int{15}
return fileDescriptor_generated_b34c2064a02cfa33, []int{15}
}
func (m *ApplicationSpec) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -481,7 +481,7 @@ var xxx_messageInfo_ApplicationSpec proto.InternalMessageInfo
func (m *ApplicationStatus) Reset() { *m = ApplicationStatus{} }
func (*ApplicationStatus) ProtoMessage() {}
func (*ApplicationStatus) Descriptor() ([]byte, []int) {
return fileDescriptor_generated_622b11e42e23cc72, []int{16}
return fileDescriptor_generated_b34c2064a02cfa33, []int{16}
}
func (m *ApplicationStatus) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -509,7 +509,7 @@ var xxx_messageInfo_ApplicationStatus proto.InternalMessageInfo
func (m *ApplicationSummary) Reset() { *m = ApplicationSummary{} }
func (*ApplicationSummary) ProtoMessage() {}
func (*ApplicationSummary) Descriptor() ([]byte, []int) {
return fileDescriptor_generated_622b11e42e23cc72, []int{17}
return fileDescriptor_generated_b34c2064a02cfa33, []int{17}
}
func (m *ApplicationSummary) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -537,7 +537,7 @@ var xxx_messageInfo_ApplicationSummary proto.InternalMessageInfo
func (m *ApplicationTree) Reset() { *m = ApplicationTree{} }
func (*ApplicationTree) ProtoMessage() {}
func (*ApplicationTree) Descriptor() ([]byte, []int) {
return fileDescriptor_generated_622b11e42e23cc72, []int{18}
return fileDescriptor_generated_b34c2064a02cfa33, []int{18}
}
func (m *ApplicationTree) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -565,7 +565,7 @@ var xxx_messageInfo_ApplicationTree proto.InternalMessageInfo
func (m *ApplicationWatchEvent) Reset() { *m = ApplicationWatchEvent{} }
func (*ApplicationWatchEvent) ProtoMessage() {}
func (*ApplicationWatchEvent) Descriptor() ([]byte, []int) {
return fileDescriptor_generated_622b11e42e23cc72, []int{19}
return fileDescriptor_generated_b34c2064a02cfa33, []int{19}
}
func (m *ApplicationWatchEvent) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -593,7 +593,7 @@ var xxx_messageInfo_ApplicationWatchEvent proto.InternalMessageInfo
func (m *Cluster) Reset() { *m = Cluster{} }
func (*Cluster) ProtoMessage() {}
func (*Cluster) Descriptor() ([]byte, []int) {
return fileDescriptor_generated_622b11e42e23cc72, []int{20}
return fileDescriptor_generated_b34c2064a02cfa33, []int{20}
}
func (m *Cluster) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -621,7 +621,7 @@ var xxx_messageInfo_Cluster proto.InternalMessageInfo
func (m *ClusterConfig) Reset() { *m = ClusterConfig{} }
func (*ClusterConfig) ProtoMessage() {}
func (*ClusterConfig) Descriptor() ([]byte, []int) {
return fileDescriptor_generated_622b11e42e23cc72, []int{21}
return fileDescriptor_generated_b34c2064a02cfa33, []int{21}
}
func (m *ClusterConfig) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -649,7 +649,7 @@ var xxx_messageInfo_ClusterConfig proto.InternalMessageInfo
func (m *ClusterList) Reset() { *m = ClusterList{} }
func (*ClusterList) ProtoMessage() {}
func (*ClusterList) Descriptor() ([]byte, []int) {
return fileDescriptor_generated_622b11e42e23cc72, []int{22}
return fileDescriptor_generated_b34c2064a02cfa33, []int{22}
}
func (m *ClusterList) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -677,7 +677,7 @@ var xxx_messageInfo_ClusterList proto.InternalMessageInfo
func (m *Command) Reset() { *m = Command{} }
func (*Command) ProtoMessage() {}
func (*Command) Descriptor() ([]byte, []int) {
return fileDescriptor_generated_622b11e42e23cc72, []int{23}
return fileDescriptor_generated_b34c2064a02cfa33, []int{23}
}
func (m *Command) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -705,7 +705,7 @@ var xxx_messageInfo_Command proto.InternalMessageInfo
func (m *ComparedTo) Reset() { *m = ComparedTo{} }
func (*ComparedTo) ProtoMessage() {}
func (*ComparedTo) Descriptor() ([]byte, []int) {
return fileDescriptor_generated_622b11e42e23cc72, []int{24}
return fileDescriptor_generated_b34c2064a02cfa33, []int{24}
}
func (m *ComparedTo) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -733,7 +733,7 @@ var xxx_messageInfo_ComparedTo proto.InternalMessageInfo
func (m *ComponentParameter) Reset() { *m = ComponentParameter{} }
func (*ComponentParameter) ProtoMessage() {}
func (*ComponentParameter) Descriptor() ([]byte, []int) {
return fileDescriptor_generated_622b11e42e23cc72, []int{25}
return fileDescriptor_generated_b34c2064a02cfa33, []int{25}
}
func (m *ComponentParameter) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -761,7 +761,7 @@ var xxx_messageInfo_ComponentParameter proto.InternalMessageInfo
func (m *ConfigManagementPlugin) Reset() { *m = ConfigManagementPlugin{} }
func (*ConfigManagementPlugin) ProtoMessage() {}
func (*ConfigManagementPlugin) Descriptor() ([]byte, []int) {
return fileDescriptor_generated_622b11e42e23cc72, []int{26}
return fileDescriptor_generated_b34c2064a02cfa33, []int{26}
}
func (m *ConfigManagementPlugin) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -789,7 +789,7 @@ var xxx_messageInfo_ConfigManagementPlugin proto.InternalMessageInfo
func (m *ConnectionState) Reset() { *m = ConnectionState{} }
func (*ConnectionState) ProtoMessage() {}
func (*ConnectionState) Descriptor() ([]byte, []int) {
return fileDescriptor_generated_622b11e42e23cc72, []int{27}
return fileDescriptor_generated_b34c2064a02cfa33, []int{27}
}
func (m *ConnectionState) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -817,7 +817,7 @@ var xxx_messageInfo_ConnectionState proto.InternalMessageInfo
func (m *EnvEntry) Reset() { *m = EnvEntry{} }
func (*EnvEntry) ProtoMessage() {}
func (*EnvEntry) Descriptor() ([]byte, []int) {
return fileDescriptor_generated_622b11e42e23cc72, []int{28}
return fileDescriptor_generated_b34c2064a02cfa33, []int{28}
}
func (m *EnvEntry) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -845,7 +845,7 @@ var xxx_messageInfo_EnvEntry proto.InternalMessageInfo
func (m *HealthStatus) Reset() { *m = HealthStatus{} }
func (*HealthStatus) ProtoMessage() {}
func (*HealthStatus) Descriptor() ([]byte, []int) {
return fileDescriptor_generated_622b11e42e23cc72, []int{29}
return fileDescriptor_generated_b34c2064a02cfa33, []int{29}
}
func (m *HealthStatus) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -873,7 +873,7 @@ var xxx_messageInfo_HealthStatus proto.InternalMessageInfo
func (m *HelmParameter) Reset() { *m = HelmParameter{} }
func (*HelmParameter) ProtoMessage() {}
func (*HelmParameter) Descriptor() ([]byte, []int) {
return fileDescriptor_generated_622b11e42e23cc72, []int{30}
return fileDescriptor_generated_b34c2064a02cfa33, []int{30}
}
func (m *HelmParameter) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -901,7 +901,7 @@ var xxx_messageInfo_HelmParameter proto.InternalMessageInfo
func (m *Info) Reset() { *m = Info{} }
func (*Info) ProtoMessage() {}
func (*Info) Descriptor() ([]byte, []int) {
return fileDescriptor_generated_622b11e42e23cc72, []int{31}
return fileDescriptor_generated_b34c2064a02cfa33, []int{31}
}
func (m *Info) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -929,7 +929,7 @@ var xxx_messageInfo_Info proto.InternalMessageInfo
func (m *InfoItem) Reset() { *m = InfoItem{} }
func (*InfoItem) ProtoMessage() {}
func (*InfoItem) Descriptor() ([]byte, []int) {
return fileDescriptor_generated_622b11e42e23cc72, []int{32}
return fileDescriptor_generated_b34c2064a02cfa33, []int{32}
}
func (m *InfoItem) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -957,7 +957,7 @@ var xxx_messageInfo_InfoItem proto.InternalMessageInfo
func (m *JWTToken) Reset() { *m = JWTToken{} }
func (*JWTToken) ProtoMessage() {}
func (*JWTToken) Descriptor() ([]byte, []int) {
return fileDescriptor_generated_622b11e42e23cc72, []int{33}
return fileDescriptor_generated_b34c2064a02cfa33, []int{33}
}
func (m *JWTToken) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -985,7 +985,7 @@ var xxx_messageInfo_JWTToken proto.InternalMessageInfo
func (m *JsonnetVar) Reset() { *m = JsonnetVar{} }
func (*JsonnetVar) ProtoMessage() {}
func (*JsonnetVar) Descriptor() ([]byte, []int) {
return fileDescriptor_generated_622b11e42e23cc72, []int{34}
return fileDescriptor_generated_b34c2064a02cfa33, []int{34}
}
func (m *JsonnetVar) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -1013,7 +1013,7 @@ var xxx_messageInfo_JsonnetVar proto.InternalMessageInfo
func (m *KsonnetParameter) Reset() { *m = KsonnetParameter{} }
func (*KsonnetParameter) ProtoMessage() {}
func (*KsonnetParameter) Descriptor() ([]byte, []int) {
return fileDescriptor_generated_622b11e42e23cc72, []int{35}
return fileDescriptor_generated_b34c2064a02cfa33, []int{35}
}
func (m *KsonnetParameter) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -1041,7 +1041,7 @@ var xxx_messageInfo_KsonnetParameter proto.InternalMessageInfo
func (m *KustomizeOptions) Reset() { *m = KustomizeOptions{} }
func (*KustomizeOptions) ProtoMessage() {}
func (*KustomizeOptions) Descriptor() ([]byte, []int) {
return fileDescriptor_generated_622b11e42e23cc72, []int{36}
return fileDescriptor_generated_b34c2064a02cfa33, []int{36}
}
func (m *KustomizeOptions) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -1069,7 +1069,7 @@ var xxx_messageInfo_KustomizeOptions proto.InternalMessageInfo
func (m *Operation) Reset() { *m = Operation{} }
func (*Operation) ProtoMessage() {}
func (*Operation) Descriptor() ([]byte, []int) {
return fileDescriptor_generated_622b11e42e23cc72, []int{37}
return fileDescriptor_generated_b34c2064a02cfa33, []int{37}
}
func (m *Operation) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -1097,7 +1097,7 @@ var xxx_messageInfo_Operation proto.InternalMessageInfo
func (m *OperationState) Reset() { *m = OperationState{} }
func (*OperationState) ProtoMessage() {}
func (*OperationState) Descriptor() ([]byte, []int) {
return fileDescriptor_generated_622b11e42e23cc72, []int{38}
return fileDescriptor_generated_b34c2064a02cfa33, []int{38}
}
func (m *OperationState) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -1125,7 +1125,7 @@ var xxx_messageInfo_OperationState proto.InternalMessageInfo
func (m *OrphanedResourcesMonitorSettings) Reset() { *m = OrphanedResourcesMonitorSettings{} }
func (*OrphanedResourcesMonitorSettings) ProtoMessage() {}
func (*OrphanedResourcesMonitorSettings) Descriptor() ([]byte, []int) {
return fileDescriptor_generated_622b11e42e23cc72, []int{39}
return fileDescriptor_generated_b34c2064a02cfa33, []int{39}
}
func (m *OrphanedResourcesMonitorSettings) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -1153,7 +1153,7 @@ var xxx_messageInfo_OrphanedResourcesMonitorSettings proto.InternalMessageInfo
func (m *ProjectRole) Reset() { *m = ProjectRole{} }
func (*ProjectRole) ProtoMessage() {}
func (*ProjectRole) Descriptor() ([]byte, []int) {
return fileDescriptor_generated_622b11e42e23cc72, []int{40}
return fileDescriptor_generated_b34c2064a02cfa33, []int{40}
}
func (m *ProjectRole) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -1181,7 +1181,7 @@ var xxx_messageInfo_ProjectRole proto.InternalMessageInfo
func (m *Repository) Reset() { *m = Repository{} }
func (*Repository) ProtoMessage() {}
func (*Repository) Descriptor() ([]byte, []int) {
return fileDescriptor_generated_622b11e42e23cc72, []int{41}
return fileDescriptor_generated_b34c2064a02cfa33, []int{41}
}
func (m *Repository) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -1209,7 +1209,7 @@ var xxx_messageInfo_Repository proto.InternalMessageInfo
func (m *RepositoryCertificate) Reset() { *m = RepositoryCertificate{} }
func (*RepositoryCertificate) ProtoMessage() {}
func (*RepositoryCertificate) Descriptor() ([]byte, []int) {
return fileDescriptor_generated_622b11e42e23cc72, []int{42}
return fileDescriptor_generated_b34c2064a02cfa33, []int{42}
}
func (m *RepositoryCertificate) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -1237,7 +1237,7 @@ var xxx_messageInfo_RepositoryCertificate proto.InternalMessageInfo
func (m *RepositoryCertificateList) Reset() { *m = RepositoryCertificateList{} }
func (*RepositoryCertificateList) ProtoMessage() {}
func (*RepositoryCertificateList) Descriptor() ([]byte, []int) {
return fileDescriptor_generated_622b11e42e23cc72, []int{43}
return fileDescriptor_generated_b34c2064a02cfa33, []int{43}
}
func (m *RepositoryCertificateList) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -1265,7 +1265,7 @@ var xxx_messageInfo_RepositoryCertificateList proto.InternalMessageInfo
func (m *RepositoryList) Reset() { *m = RepositoryList{} }
func (*RepositoryList) ProtoMessage() {}
func (*RepositoryList) Descriptor() ([]byte, []int) {
return fileDescriptor_generated_622b11e42e23cc72, []int{44}
return fileDescriptor_generated_b34c2064a02cfa33, []int{44}
}
func (m *RepositoryList) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -1293,7 +1293,7 @@ var xxx_messageInfo_RepositoryList proto.InternalMessageInfo
func (m *ResourceAction) Reset() { *m = ResourceAction{} }
func (*ResourceAction) ProtoMessage() {}
func (*ResourceAction) Descriptor() ([]byte, []int) {
return fileDescriptor_generated_622b11e42e23cc72, []int{45}
return fileDescriptor_generated_b34c2064a02cfa33, []int{45}
}
func (m *ResourceAction) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -1321,7 +1321,7 @@ var xxx_messageInfo_ResourceAction proto.InternalMessageInfo
func (m *ResourceActionDefinition) Reset() { *m = ResourceActionDefinition{} }
func (*ResourceActionDefinition) ProtoMessage() {}
func (*ResourceActionDefinition) Descriptor() ([]byte, []int) {
return fileDescriptor_generated_622b11e42e23cc72, []int{46}
return fileDescriptor_generated_b34c2064a02cfa33, []int{46}
}
func (m *ResourceActionDefinition) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -1349,7 +1349,7 @@ var xxx_messageInfo_ResourceActionDefinition proto.InternalMessageInfo
func (m *ResourceActionParam) Reset() { *m = ResourceActionParam{} }
func (*ResourceActionParam) ProtoMessage() {}
func (*ResourceActionParam) Descriptor() ([]byte, []int) {
return fileDescriptor_generated_622b11e42e23cc72, []int{47}
return fileDescriptor_generated_b34c2064a02cfa33, []int{47}
}
func (m *ResourceActionParam) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -1377,7 +1377,7 @@ var xxx_messageInfo_ResourceActionParam proto.InternalMessageInfo
func (m *ResourceActions) Reset() { *m = ResourceActions{} }
func (*ResourceActions) ProtoMessage() {}
func (*ResourceActions) Descriptor() ([]byte, []int) {
return fileDescriptor_generated_622b11e42e23cc72, []int{48}
return fileDescriptor_generated_b34c2064a02cfa33, []int{48}
}
func (m *ResourceActions) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -1405,7 +1405,7 @@ var xxx_messageInfo_ResourceActions proto.InternalMessageInfo
func (m *ResourceDiff) Reset() { *m = ResourceDiff{} }
func (*ResourceDiff) ProtoMessage() {}
func (*ResourceDiff) Descriptor() ([]byte, []int) {
return fileDescriptor_generated_622b11e42e23cc72, []int{49}
return fileDescriptor_generated_b34c2064a02cfa33, []int{49}
}
func (m *ResourceDiff) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -1433,7 +1433,7 @@ var xxx_messageInfo_ResourceDiff proto.InternalMessageInfo
func (m *ResourceIgnoreDifferences) Reset() { *m = ResourceIgnoreDifferences{} }
func (*ResourceIgnoreDifferences) ProtoMessage() {}
func (*ResourceIgnoreDifferences) Descriptor() ([]byte, []int) {
return fileDescriptor_generated_622b11e42e23cc72, []int{50}
return fileDescriptor_generated_b34c2064a02cfa33, []int{50}
}
func (m *ResourceIgnoreDifferences) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -1461,7 +1461,7 @@ var xxx_messageInfo_ResourceIgnoreDifferences proto.InternalMessageInfo
func (m *ResourceNetworkingInfo) Reset() { *m = ResourceNetworkingInfo{} }
func (*ResourceNetworkingInfo) ProtoMessage() {}
func (*ResourceNetworkingInfo) Descriptor() ([]byte, []int) {
return fileDescriptor_generated_622b11e42e23cc72, []int{51}
return fileDescriptor_generated_b34c2064a02cfa33, []int{51}
}
func (m *ResourceNetworkingInfo) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -1489,7 +1489,7 @@ var xxx_messageInfo_ResourceNetworkingInfo proto.InternalMessageInfo
func (m *ResourceNode) Reset() { *m = ResourceNode{} }
func (*ResourceNode) ProtoMessage() {}
func (*ResourceNode) Descriptor() ([]byte, []int) {
return fileDescriptor_generated_622b11e42e23cc72, []int{52}
return fileDescriptor_generated_b34c2064a02cfa33, []int{52}
}
func (m *ResourceNode) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -1517,7 +1517,7 @@ var xxx_messageInfo_ResourceNode proto.InternalMessageInfo
func (m *ResourceOverride) Reset() { *m = ResourceOverride{} }
func (*ResourceOverride) ProtoMessage() {}
func (*ResourceOverride) Descriptor() ([]byte, []int) {
return fileDescriptor_generated_622b11e42e23cc72, []int{53}
return fileDescriptor_generated_b34c2064a02cfa33, []int{53}
}
func (m *ResourceOverride) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -1545,7 +1545,7 @@ var xxx_messageInfo_ResourceOverride proto.InternalMessageInfo
func (m *ResourceRef) Reset() { *m = ResourceRef{} }
func (*ResourceRef) ProtoMessage() {}
func (*ResourceRef) Descriptor() ([]byte, []int) {
return fileDescriptor_generated_622b11e42e23cc72, []int{54}
return fileDescriptor_generated_b34c2064a02cfa33, []int{54}
}
func (m *ResourceRef) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -1573,7 +1573,7 @@ var xxx_messageInfo_ResourceRef proto.InternalMessageInfo
func (m *ResourceResult) Reset() { *m = ResourceResult{} }
func (*ResourceResult) ProtoMessage() {}
func (*ResourceResult) Descriptor() ([]byte, []int) {
return fileDescriptor_generated_622b11e42e23cc72, []int{55}
return fileDescriptor_generated_b34c2064a02cfa33, []int{55}
}
func (m *ResourceResult) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -1601,7 +1601,7 @@ var xxx_messageInfo_ResourceResult proto.InternalMessageInfo
func (m *ResourceStatus) Reset() { *m = ResourceStatus{} }
func (*ResourceStatus) ProtoMessage() {}
func (*ResourceStatus) Descriptor() ([]byte, []int) {
return fileDescriptor_generated_622b11e42e23cc72, []int{56}
return fileDescriptor_generated_b34c2064a02cfa33, []int{56}
}
func (m *ResourceStatus) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -1629,7 +1629,7 @@ var xxx_messageInfo_ResourceStatus proto.InternalMessageInfo
func (m *RevisionHistory) Reset() { *m = RevisionHistory{} }
func (*RevisionHistory) ProtoMessage() {}
func (*RevisionHistory) Descriptor() ([]byte, []int) {
return fileDescriptor_generated_622b11e42e23cc72, []int{57}
return fileDescriptor_generated_b34c2064a02cfa33, []int{57}
}
func (m *RevisionHistory) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -1657,7 +1657,7 @@ var xxx_messageInfo_RevisionHistory proto.InternalMessageInfo
func (m *RevisionMetadata) Reset() { *m = RevisionMetadata{} }
func (*RevisionMetadata) ProtoMessage() {}
func (*RevisionMetadata) Descriptor() ([]byte, []int) {
return fileDescriptor_generated_622b11e42e23cc72, []int{58}
return fileDescriptor_generated_b34c2064a02cfa33, []int{58}
}
func (m *RevisionMetadata) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -1685,7 +1685,7 @@ var xxx_messageInfo_RevisionMetadata proto.InternalMessageInfo
func (m *SyncOperation) Reset() { *m = SyncOperation{} }
func (*SyncOperation) ProtoMessage() {}
func (*SyncOperation) Descriptor() ([]byte, []int) {
return fileDescriptor_generated_622b11e42e23cc72, []int{59}
return fileDescriptor_generated_b34c2064a02cfa33, []int{59}
}
func (m *SyncOperation) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -1713,7 +1713,7 @@ var xxx_messageInfo_SyncOperation proto.InternalMessageInfo
func (m *SyncOperationResource) Reset() { *m = SyncOperationResource{} }
func (*SyncOperationResource) ProtoMessage() {}
func (*SyncOperationResource) Descriptor() ([]byte, []int) {
return fileDescriptor_generated_622b11e42e23cc72, []int{60}
return fileDescriptor_generated_b34c2064a02cfa33, []int{60}
}
func (m *SyncOperationResource) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -1741,7 +1741,7 @@ var xxx_messageInfo_SyncOperationResource proto.InternalMessageInfo
func (m *SyncOperationResult) Reset() { *m = SyncOperationResult{} }
func (*SyncOperationResult) ProtoMessage() {}
func (*SyncOperationResult) Descriptor() ([]byte, []int) {
return fileDescriptor_generated_622b11e42e23cc72, []int{61}
return fileDescriptor_generated_b34c2064a02cfa33, []int{61}
}
func (m *SyncOperationResult) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -1769,7 +1769,7 @@ var xxx_messageInfo_SyncOperationResult proto.InternalMessageInfo
func (m *SyncPolicy) Reset() { *m = SyncPolicy{} }
func (*SyncPolicy) ProtoMessage() {}
func (*SyncPolicy) Descriptor() ([]byte, []int) {
return fileDescriptor_generated_622b11e42e23cc72, []int{62}
return fileDescriptor_generated_b34c2064a02cfa33, []int{62}
}
func (m *SyncPolicy) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -1797,7 +1797,7 @@ var xxx_messageInfo_SyncPolicy proto.InternalMessageInfo
func (m *SyncPolicyAutomated) Reset() { *m = SyncPolicyAutomated{} }
func (*SyncPolicyAutomated) ProtoMessage() {}
func (*SyncPolicyAutomated) Descriptor() ([]byte, []int) {
return fileDescriptor_generated_622b11e42e23cc72, []int{63}
return fileDescriptor_generated_b34c2064a02cfa33, []int{63}
}
func (m *SyncPolicyAutomated) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -1825,7 +1825,7 @@ var xxx_messageInfo_SyncPolicyAutomated proto.InternalMessageInfo
func (m *SyncStatus) Reset() { *m = SyncStatus{} }
func (*SyncStatus) ProtoMessage() {}
func (*SyncStatus) Descriptor() ([]byte, []int) {
return fileDescriptor_generated_622b11e42e23cc72, []int{64}
return fileDescriptor_generated_b34c2064a02cfa33, []int{64}
}
func (m *SyncStatus) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -1853,7 +1853,7 @@ var xxx_messageInfo_SyncStatus proto.InternalMessageInfo
func (m *SyncStrategy) Reset() { *m = SyncStrategy{} }
func (*SyncStrategy) ProtoMessage() {}
func (*SyncStrategy) Descriptor() ([]byte, []int) {
return fileDescriptor_generated_622b11e42e23cc72, []int{65}
return fileDescriptor_generated_b34c2064a02cfa33, []int{65}
}
func (m *SyncStrategy) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -1881,7 +1881,7 @@ var xxx_messageInfo_SyncStrategy proto.InternalMessageInfo
func (m *SyncStrategyApply) Reset() { *m = SyncStrategyApply{} }
func (*SyncStrategyApply) ProtoMessage() {}
func (*SyncStrategyApply) Descriptor() ([]byte, []int) {
return fileDescriptor_generated_622b11e42e23cc72, []int{66}
return fileDescriptor_generated_b34c2064a02cfa33, []int{66}
}
func (m *SyncStrategyApply) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -1909,7 +1909,7 @@ var xxx_messageInfo_SyncStrategyApply proto.InternalMessageInfo
func (m *SyncStrategyHook) Reset() { *m = SyncStrategyHook{} }
func (*SyncStrategyHook) ProtoMessage() {}
func (*SyncStrategyHook) Descriptor() ([]byte, []int) {
return fileDescriptor_generated_622b11e42e23cc72, []int{67}
return fileDescriptor_generated_b34c2064a02cfa33, []int{67}
}
func (m *SyncStrategyHook) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -1937,7 +1937,7 @@ var xxx_messageInfo_SyncStrategyHook proto.InternalMessageInfo
func (m *SyncWindow) Reset() { *m = SyncWindow{} }
func (*SyncWindow) ProtoMessage() {}
func (*SyncWindow) Descriptor() ([]byte, []int) {
return fileDescriptor_generated_622b11e42e23cc72, []int{68}
return fileDescriptor_generated_b34c2064a02cfa33, []int{68}
}
func (m *SyncWindow) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -1965,7 +1965,7 @@ var xxx_messageInfo_SyncWindow proto.InternalMessageInfo
func (m *TLSClientConfig) Reset() { *m = TLSClientConfig{} }
func (*TLSClientConfig) ProtoMessage() {}
func (*TLSClientConfig) Descriptor() ([]byte, []int) {
return fileDescriptor_generated_622b11e42e23cc72, []int{69}
return fileDescriptor_generated_b34c2064a02cfa33, []int{69}
}
func (m *TLSClientConfig) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -18635,10 +18635,10 @@ var (
)
func init() {
proto.RegisterFile("github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1/generated.proto", fileDescriptor_generated_622b11e42e23cc72)
proto.RegisterFile("github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1/generated.proto", fileDescriptor_generated_b34c2064a02cfa33)
}
var fileDescriptor_generated_622b11e42e23cc72 = []byte{
var fileDescriptor_generated_b34c2064a02cfa33 = []byte{
// 4679 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe4, 0x3c, 0x5b, 0x8c, 0x1c, 0xd9,
0x55, 0xae, 0x7e, 0xcf, 0x99, 0x87, 0x3d, 0x77, 0xd7, 0x9b, 0xce, 0x68, 0xe3, 0xb1, 0xca, 0x4a,

View File

@@ -236,10 +236,12 @@ message ApplicationStatus {
repeated ApplicationCondition conditions = 5;
// ReconciledAt indicates when the application state was reconciled using the latest git version
optional k8s.io.apimachinery.pkg.apis.meta.v1.Time reconciledAt = 6;
optional OperationState operationState = 7;
// ObservedAt indicates when the application state was updated without querying latest git state
optional k8s.io.apimachinery.pkg.apis.meta.v1.Time observedAt = 8;
optional string sourceType = 9;

View File

@@ -895,7 +895,8 @@ func schema_pkg_apis_application_v1alpha1_ApplicationStatus(ref common.Reference
},
"reconciledAt": {
SchemaProps: spec.SchemaProps{
Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"),
Description: "ReconciledAt indicates when the application state was reconciled using the latest git version",
Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"),
},
},
"operationState": {
@@ -905,7 +906,8 @@ func schema_pkg_apis_application_v1alpha1_ApplicationStatus(ref common.Reference
},
"observedAt": {
SchemaProps: spec.SchemaProps{
Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"),
Description: "ObservedAt indicates when the application state was updated without querying latest git state",
Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"),
},
},
"sourceType": {

View File

@@ -331,16 +331,18 @@ type ApplicationDestination struct {
// ApplicationStatus contains information about application sync, health status
type ApplicationStatus struct {
Resources []ResourceStatus `json:"resources,omitempty" protobuf:"bytes,1,opt,name=resources"`
Sync SyncStatus `json:"sync,omitempty" protobuf:"bytes,2,opt,name=sync"`
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"`
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"`
Summary ApplicationSummary `json:"summary,omitempty" protobuf:"bytes,10,opt,name=summary"`
Resources []ResourceStatus `json:"resources,omitempty" protobuf:"bytes,1,opt,name=resources"`
Sync SyncStatus `json:"sync,omitempty" protobuf:"bytes,2,opt,name=sync"`
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"`
// ReconciledAt indicates when the application state was reconciled using the latest git version
ReconciledAt *metav1.Time `json:"reconciledAt,omitempty" protobuf:"bytes,6,opt,name=reconciledAt"`
OperationState *OperationState `json:"operationState,omitempty" protobuf:"bytes,7,opt,name=operationState"`
// ObservedAt indicates when the application state was updated without querying latest git state
ObservedAt *metav1.Time `json:"observedAt,omitempty" protobuf:"bytes,8,opt,name=observedAt"`
SourceType ApplicationSourceType `json:"sourceType,omitempty" protobuf:"bytes,9,opt,name=sourceType"`
Summary ApplicationSummary `json:"summary,omitempty" protobuf:"bytes,10,opt,name=summary"`
}
// Operation contains requested operation parameters.
@@ -1250,8 +1252,20 @@ var validActions = map[string]bool{
"*": true,
}
var validActionPatterns = []*regexp.Regexp{
regexp.MustCompile("action/.*"),
}
func isValidAction(action string) bool {
return validActions[action]
if validActions[action] {
return true
}
for i := range validActionPatterns {
if validActionPatterns[i].MatchString(action) {
return true
}
}
return false
}
func validatePolicy(proj string, role string, policy string) error {
@@ -1277,7 +1291,7 @@ func validatePolicy(proj string, role string, policy string) error {
}
// object
object := strings.Trim(policyComponents[4], " ")
objectRegexp, err := regexp.Compile(fmt.Sprintf(`^%s/[*\w-]+$`, proj))
objectRegexp, err := regexp.Compile(fmt.Sprintf(`^%s/[*\w-.]+$`, proj))
if err != nil || !objectRegexp.MatchString(object) {
return status.Errorf(codes.InvalidArgument, "invalid policy rule '%s': object must be of form '%s/*' or '%s/<APPNAME>', not '%s'", policy, proj, proj, object)
}

View File

@@ -257,8 +257,6 @@ func TestAppProject_InvalidPolicyRules(t *testing.T) {
errmsg string
}
badPolicies := []badPolicy{
// should have spaces
{"p,proj:my-proj:my-role,applications,get,my-proj/*,allow", "syntax"},
// incorrect form
{"g, proj:my-proj:my-role, applications, get, my-proj/*, allow", "must be of the form: 'p, sub, res, act, obj, eft'"},
{"p, not, enough, parts", "must be of the form: 'p, sub, res, act, obj, eft'"},
@@ -305,11 +303,14 @@ func TestAppProject_ValidPolicyRules(t *testing.T) {
"p, proj:my-proj:my-role, applications, get, my-proj/*-foo, allow",
"p, proj:my-proj:my-role, applications, get, my-proj/foo-*, allow",
"p, proj:my-proj:my-role, applications, get, my-proj/*-*, allow",
"p, proj:my-proj:my-role, applications, get, my-proj/*.*, allow",
"p, proj:my-proj:my-role, applications, *, my-proj/foo, allow",
"p, proj:my-proj:my-role, applications, create, my-proj/foo, allow",
"p, proj:my-proj:my-role, applications, update, my-proj/foo, allow",
"p, proj:my-proj:my-role, applications, sync, my-proj/foo, allow",
"p, proj:my-proj:my-role, applications, delete, my-proj/foo, allow",
"p, proj:my-proj:my-role, applications, action/*, my-proj/foo, allow",
"p, proj:my-proj:my-role, applications, action/apps/Deployment/restart, my-proj/foo, allow",
}
for _, good := range goodPolicies {
p.Spec.Roles[0].Policies = []string{good}

View File

@@ -4,6 +4,7 @@ import (
"crypto/tls"
"time"
grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
grpc_retry "github.com/grpc-ecosystem/go-grpc-middleware/retry"
log "github.com/sirupsen/logrus"
"google.golang.org/grpc"
@@ -28,13 +29,16 @@ func (c *clientSet) NewRepoServerClient() (util.Closer, RepoServerServiceClient,
grpc_retry.WithMax(3),
grpc_retry.WithBackoff(grpc_retry.BackoffLinear(1000 * time.Millisecond)),
}
unaryInterceptors := []grpc.UnaryClientInterceptor{grpc_retry.UnaryClientInterceptor(retryOpts...)}
if c.timeoutSeconds > 0 {
unaryInterceptors = append(unaryInterceptors, argogrpc.WithTimeout(time.Duration(c.timeoutSeconds)*time.Second))
}
opts := []grpc.DialOption{
grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{InsecureSkipVerify: true})),
grpc.WithStreamInterceptor(grpc_retry.StreamClientInterceptor(retryOpts...)),
grpc.WithUnaryInterceptor(grpc_retry.UnaryClientInterceptor(retryOpts...))}
if c.timeoutSeconds > 0 {
opts = append(opts, grpc.WithUnaryInterceptor(argogrpc.WithTimeout(time.Duration(c.timeoutSeconds)*time.Second)))
grpc.WithUnaryInterceptor(grpc_middleware.ChainUnaryClient(unaryInterceptors...)),
}
conn, err := grpc.Dial(c.address, opts...)
if err != nil {
log.Errorf("Unable to connect to repository service with address %s", c.address)

View File

@@ -8,8 +8,9 @@ import (
)
type MetricsServer struct {
handler http.Handler
gitRequestCounter *prometheus.CounterVec
handler http.Handler
gitRequestCounter *prometheus.CounterVec
repoPendingRequestsGauge *prometheus.GaugeVec
}
type GitRequestType string
@@ -34,9 +35,19 @@ func NewMetricsServer() *MetricsServer {
)
registry.MustRegister(gitRequestCounter)
repoPendingRequestsGauge := prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "argocd_repo_pending_request_total",
Help: "Number of pending requests requiring repository lock",
},
[]string{"repo"},
)
registry.MustRegister(repoPendingRequestsGauge)
return &MetricsServer{
handler: promhttp.HandlerFor(registry, promhttp.HandlerOpts{}),
gitRequestCounter: gitRequestCounter,
handler: promhttp.HandlerFor(registry, promhttp.HandlerOpts{}),
gitRequestCounter: gitRequestCounter,
repoPendingRequestsGauge: repoPendingRequestsGauge,
}
}
@@ -48,3 +59,11 @@ func (m *MetricsServer) GetHandler() http.Handler {
func (m *MetricsServer) IncGitRequest(repo string, requestType GitRequestType) {
m.gitRequestCounter.WithLabelValues(repo, string(requestType)).Inc()
}
func (m *MetricsServer) IncPendingRepoRequest(repo string) {
m.repoPendingRequestsGauge.WithLabelValues(repo).Inc()
}
func (m *MetricsServer) DecPendingRepoRequest(repo string) {
m.repoPendingRequestsGauge.WithLabelValues(repo).Dec()
}

View File

@@ -5,6 +5,7 @@ import (
"encoding/json"
"fmt"
"io/ioutil"
"net/url"
"os"
"os/exec"
"path/filepath"
@@ -37,6 +38,7 @@ import (
"github.com/argoproj/argo-cd/util/ksonnet"
"github.com/argoproj/argo-cd/util/kube"
"github.com/argoproj/argo-cd/util/kustomize"
"github.com/argoproj/argo-cd/util/security"
"github.com/argoproj/argo-cd/util/text"
)
@@ -110,7 +112,8 @@ type operationSettings struct {
// runRepoOperation downloads either git folder or helm chart and executes specified operation
func (s *Service) runRepoOperation(
c context.Context,
ctx context.Context,
revision string,
repo *v1alpha1.Repository,
source *v1alpha1.ApplicationSource,
getCached func(revision string) bool,
@@ -119,9 +122,9 @@ func (s *Service) runRepoOperation(
var gitClient git.Client
var err error
revision := source.TargetRevision
revision = util.FirstNonEmpty(revision, source.TargetRevision)
if !source.IsHelm() {
gitClient, revision, err = s.newClientResolveRevision(repo, source.TargetRevision)
gitClient, revision, err = s.newClientResolveRevision(repo, revision)
if err != nil {
return err
}
@@ -131,8 +134,11 @@ func (s *Service) runRepoOperation(
return nil
}
s.metricsServer.IncPendingRepoRequest(repo.Repo)
defer s.metricsServer.DecPendingRepoRequest(repo.Repo)
if settings.sem != nil {
err = settings.sem.Acquire(c, 1)
err = settings.sem.Acquire(ctx, 1)
if err != nil {
return err
}
@@ -153,27 +159,27 @@ func (s *Service) runRepoOperation(
}
defer util.Close(closer)
return operation(chartPath, revision)
} else {
s.repoLock.Lock(gitClient.Root())
defer s.repoLock.Unlock(gitClient.Root())
// double-check locking
if !settings.noCache && getCached(revision) {
return nil
}
revision, err = checkoutRevision(gitClient, revision)
if err != nil {
return err
}
appPath, err := argopath.Path(gitClient.Root(), source.Path)
if err != nil {
return err
}
return operation(appPath, revision)
}
s.repoLock.Lock(gitClient.Root())
defer s.repoLock.Unlock(gitClient.Root())
if !settings.noCache && getCached(revision) {
return nil
}
revision, err = checkoutRevision(gitClient, revision)
if err != nil {
return err
}
appPath, err := argopath.Path(gitClient.Root(), source.Path)
if err != nil {
return err
}
return operation(appPath, revision)
}
func (s *Service) GenerateManifest(c context.Context, q *apiclient.ManifestRequest) (*apiclient.ManifestResponse, error) {
var res *apiclient.ManifestResponse
func (s *Service) GenerateManifest(ctx context.Context, q *apiclient.ManifestRequest) (*apiclient.ManifestResponse, error) {
res := &apiclient.ManifestResponse{}
getCached := func(revision string) bool {
err := s.cache.GetManifests(revision, q.ApplicationSource, q.Namespace, q.AppLabelKey, q.AppLabelValue, &res)
@@ -188,7 +194,7 @@ func (s *Service) GenerateManifest(c context.Context, q *apiclient.ManifestReque
}
return false
}
err := s.runRepoOperation(c, q.Repo, q.ApplicationSource, getCached, func(appPath string, revision string) error {
err := s.runRepoOperation(ctx, q.Revision, q.Repo, q.ApplicationSource, getCached, func(appPath string, revision string) error {
var err error
res, err = GenerateManifests(appPath, q)
if err != nil {
@@ -220,14 +226,41 @@ func helmTemplate(appPath string, q *apiclient.ManifestRequest) ([]*unstructured
Set: map[string]string{},
SetString: map[string]string{},
}
appHelm := q.ApplicationSource.Helm
if appHelm != nil {
if appHelm.ReleaseName != "" {
templateOpts.Name = appHelm.ReleaseName
}
templateOpts.Values = appHelm.ValueFiles
for _, val := range appHelm.ValueFiles {
// If val is not a URL, run it against the directory enforcer. If it is a URL, use it without checking
if _, err := url.ParseRequestURI(val); err != nil {
baseDirectoryPath, err := security.SubtractRelativeFromAbsolutePath(appPath, q.ApplicationSource.Path)
if err != nil {
return nil, err
}
absBaseDir, err := filepath.Abs(baseDirectoryPath)
if err != nil {
return nil, err
}
if !filepath.IsAbs(val) {
absWorkDir, err := filepath.Abs(appPath)
if err != nil {
return nil, err
}
val = filepath.Join(absWorkDir, val)
}
_, err = security.EnforceToCurrentRoot(absBaseDir, val)
if err != nil {
return nil, err
}
}
templateOpts.Values = append(templateOpts.Values, val)
}
if appHelm.Values != "" {
file, err := ioutil.TempFile(appPath, "values-*.yaml")
file, err := ioutil.TempFile("", "values-*.yaml")
if err != nil {
return nil, err
}
@@ -239,6 +272,7 @@ func helmTemplate(appPath string, q *apiclient.ManifestRequest) ([]*unstructured
}
templateOpts.Values = append(templateOpts.Values, p)
}
for _, p := range appHelm.Parameters {
if p.ForceString {
templateOpts.SetString[p.Name] = p.Value
@@ -568,23 +602,23 @@ func runConfigManagementPlugin(appPath string, q *apiclient.ManifestRequest, cre
}
func (s *Service) GetAppDetails(ctx context.Context, q *apiclient.RepoServerAppDetailsQuery) (*apiclient.RepoAppDetailsResponse, error) {
var res apiclient.RepoAppDetailsResponse
res := &apiclient.RepoAppDetailsResponse{}
getCached := func(revision string) bool {
err := s.cache.GetAppDetails(revision, q.Source, &res)
if err == nil {
log.Infof("manifest cache hit: %s/%s", revision, q.Source.Path)
log.Infof("app details cache hit: %s/%s", revision, q.Source.Path)
return true
} else {
if err != cache.ErrCacheMiss {
log.Warnf("manifest cache error %s: %v", revision, q.Source)
log.Warnf("app details cache error %s: %v", revision, q.Source)
} else {
log.Infof("manifest cache miss: %s/%s", revision, q.Source)
log.Infof("app details cache miss: %s/%s", revision, q.Source)
}
}
return false
}
err := s.runRepoOperation(ctx, q.Repo, q.Source, getCached, func(appPath string, revision string) error {
err := s.runRepoOperation(ctx, q.Source.TargetRevision, q.Repo, q.Source, getCached, func(appPath string, revision string) error {
appSourceType, err := GetAppSourceType(q.Source, appPath)
if err != nil {
return err
@@ -673,32 +707,33 @@ func (s *Service) GetAppDetails(ctx context.Context, q *apiclient.RepoServerAppD
return nil
}, operationSettings{})
return &res, err
return res, err
}
func (s *Service) GetRevisionMetadata(ctx context.Context, q *apiclient.RepoServerRevisionMetadataRequest) (*v1alpha1.RevisionMetadata, error) {
gitClient, commitSHA, err := s.newClientResolveRevision(q.Repo, q.Revision)
if err != nil {
return nil, err
if !git.IsCommitSHA(q.Revision) {
return nil, fmt.Errorf("revision %s must be resolved", q.Revision)
}
metadata, err := s.cache.GetRevisionMetadata(q.Repo.Repo, commitSHA)
metadata, err := s.cache.GetRevisionMetadata(q.Repo.Repo, q.Revision)
if err == nil {
log.Infof("manifest cache hit: %s/%s", q.Repo.Repo, commitSHA)
log.Infof("revision metadata cache hit: %s/%s", q.Repo.Repo, q.Revision)
return metadata, nil
} else {
if err != cache.ErrCacheMiss {
log.Warnf("manifest cache error %s/%s: %v", q.Repo.Repo, commitSHA, err)
log.Warnf("revision metadata cache error %s/%s: %v", q.Repo.Repo, q.Revision, err)
} else {
log.Infof("manifest cache miss: %s/%s", q.Repo.Repo, commitSHA)
log.Infof("revision metadata cache miss: %s/%s", q.Repo.Repo, q.Revision)
}
}
commitSHA, err = checkoutRevision(gitClient, commitSHA)
gitClient, _, err := s.newClientResolveRevision(q.Repo, q.Revision)
if err != nil {
return nil, err
}
m, err := gitClient.RevisionMetadata(commitSHA)
_, err = checkoutRevision(gitClient, q.Revision)
if err != nil {
return nil, err
}
m, err := gitClient.RevisionMetadata(q.Revision)
if err != nil {
return nil, err
}

View File

@@ -88,6 +88,19 @@ func TestGenerateYamlManifestInDir(t *testing.T) {
}
}
func TestGenerateManifestsUseExactRevision(t *testing.T) {
service, gitClient, _ := newServiceWithMocks(".")
src := argoappv1.ApplicationSource{Path: "./testdata/recurse", Directory: &argoappv1.ApplicationSourceDirectory{Recurse: true}}
q := apiclient.ManifestRequest{Repo: &argoappv1.Repository{}, ApplicationSource: &src, Revision: "abc"}
res1, err := service.GenerateManifest(context.Background(), &q)
assert.Nil(t, err)
assert.Equal(t, 2, len(res1.Manifests))
assert.Equal(t, gitClient.Calls[0].Arguments[0], "abc")
}
func TestRecurseManifestsInDir(t *testing.T) {
service := newService(".")
@@ -193,8 +206,8 @@ func TestGenerateHelmWithValues(t *testing.T) {
}
// This tests against a path traversal attack. The requested value file (`../minio/values.yaml`) is outside the
// app path (`./util/helm/testdata/redis`)
// The requested value file (`../minio/values.yaml`) is outside the app path (`./util/helm/testdata/redis`), however
// since the requested value is sill under the repo directory (`~/go/src/github.com/argoproj/argo-cd`), it is allowed
func TestGenerateHelmWithValuesDirectoryTraversal(t *testing.T) {
service := newService("../..")
@@ -209,7 +222,42 @@ func TestGenerateHelmWithValuesDirectoryTraversal(t *testing.T) {
},
},
})
assert.Error(t, err)
assert.NoError(t, err)
}
func TestGenerateHelmWithURL(t *testing.T) {
service := newService("../..")
_, err := service.GenerateManifest(context.Background(), &apiclient.ManifestRequest{
Repo: &argoappv1.Repository{},
AppLabelValue: "test",
ApplicationSource: &argoappv1.ApplicationSource{
Path: "./util/helm/testdata/redis",
Helm: &argoappv1.ApplicationSourceHelm{
ValueFiles: []string{"https://raw.githubusercontent.com/argoproj/argocd-example-apps/master/helm-guestbook/values.yaml"},
Values: `cluster: {slaveCount: 2}`,
},
},
})
assert.NoError(t, err)
}
// The requested value file (`../../../../../minio/values.yaml`) is outside the repo directory
// (`~/go/src/github.com/argoproj/argo-cd`), so it is blocked
func TestGenerateHelmWithValuesDirectoryTraversalOutsideRepo(t *testing.T) {
service := newService("../..")
_, err := service.GenerateManifest(context.Background(), &apiclient.ManifestRequest{
Repo: &argoappv1.Repository{},
AppLabelValue: "test",
ApplicationSource: &argoappv1.ApplicationSource{
Path: "./util/helm/testdata/redis",
Helm: &argoappv1.ApplicationSourceHelm{
ValueFiles: []string{"../../../../../minio/values.yaml"},
Values: `cluster: {slaveCount: 2}`,
},
},
})
assert.Error(t, err, "should be on or under current directory")
}
@@ -395,7 +443,7 @@ func TestGetRevisionMetadata(t *testing.T) {
res, err := service.GetRevisionMetadata(context.Background(), &apiclient.RepoServerRevisionMetadataRequest{
Repo: &argoappv1.Repository{},
Revision: "123",
Revision: "c0b400fc458875d925171398f9ba9eabd5529923",
})
assert.NoError(t, err)

View File

@@ -0,0 +1,32 @@
hs = {}
if obj.status ~= nil then
if obj.status.phase == "Pending" then
hs.status = "Progressing"
hs.message = "Analysis run is running"
end
if obj.status.phase == "Running" then
hs.status = "Progressing"
hs.message = "Analysis run is running"
end
if obj.status.phase == "Successful" then
hs.status = "Healthy"
hs.message = "Analysis run completed successfully"
end
if obj.status.phase == "Failed" then
hs.status = "Degraded"
hs.message = "Analysis run failed"
end
if obj.status.phase == "Error" then
hs.status = "Degraded"
hs.message = "Analysis run had an error"
end
if obj.status.phase == "Inconclusive" then
hs.status = "Unknown"
hs.message = "Analysis run was inconclusive"
end
return hs
end
hs.status = "Progressing"
hs.message = "Waiting for analysis run to finish: status has not been reconciled."
return hs

View File

@@ -0,0 +1,29 @@
tests:
- healthStatus:
status: Progressing
message: "Analysis run is running"
inputPath: testdata/pendingAnalysisRun.yaml
- healthStatus:
status: Progressing
message: "Waiting for analysis run to finish: status has not been reconciled."
inputPath: testdata/noStatusAnalysisRun.yaml
- healthStatus:
status: Progressing
message: "Analysis run is running"
inputPath: testdata/runningAnalysisRun.yaml
- healthStatus:
status: Healthy
message: "Analysis run completed successfully"
inputPath: testdata/successfulAnalysisRun.yaml
- healthStatus:
status: Degraded
message: "Analysis run failed"
inputPath: testdata/failedAnalysisRun.yaml
- healthStatus:
status: Degraded
message: "Analysis run had an error"
inputPath: testdata/errorAnalysisRun.yaml
- healthStatus:
status: Unknown
message: "Analysis run was inconclusive"
inputPath: testdata/inconclusiveAnalysisRun.yaml

View File

@@ -0,0 +1,47 @@
apiVersion: argoproj.io/v1alpha1
kind: AnalysisRun
metadata:
name: canary-demo-analysis-template-6c6bb7cf6f-btpgc
namespace: default
spec:
analysisSpec:
metrics:
- failureCondition: result < 92
interval: 10
name: memory-usage
provider:
prometheus:
address: 'http://prometheus-operator-prometheus.prometheus-operator:9090'
query: >
sum(rate(nginx_ingress_controller_requests{ingress="canary-demo-preview",status!~"[4-5].*"}[2m]))
/
sum(rate(nginx_ingress_controller_requests{ingress="canary-demo-preview"}[2m]))
successCondition: result > 95
status:
metricResults:
- consecutiveError: 5
error: 5
measurements:
- finishedAt: '2019-10-28T18:13:01Z'
startedAt: '2019-10-28T18:13:01Z'
phase: Error
value: '[0.9832775919732442]'
- finishedAt: '2019-10-28T18:13:11Z'
startedAt: '2019-10-28T18:13:11Z'
phase: Error
value: '[0.9832775919732442]'
- finishedAt: '2019-10-28T18:13:21Z'
startedAt: '2019-10-28T18:13:21Z'
phase: Error
value: '[0.9722530521642618]'
- finishedAt: '2019-10-28T18:13:31Z'
startedAt: '2019-10-28T18:13:31Z'
phase: Error
value: '[0.9722530521642618]'
- finishedAt: '2019-10-28T18:13:41Z'
startedAt: '2019-10-28T18:13:41Z'
phase: Error
value: '[0.9722530521642618]'
name: memory-usage
phase: Error
phase: Error

View File

@@ -0,0 +1,31 @@
apiVersion: argoproj.io/v1alpha1
kind: AnalysisRun
metadata:
name: canary-demo-analysis-template-6c6bb7cf6f-9k5rj
namespace: default
spec:
analysisSpec:
metrics:
- failureCondition: len(result) > 0
interval: 10
name: memory-usage
provider:
prometheus:
address: 'http://prometheus-operator-prometheus.prometheus-operator:9090'
query: >
sum(rate(nginx_ingress_controller_requests{ingress="canary-demo-preview",status!~"[4-5].*"}[2m]))
/
sum(rate(nginx_ingress_controller_requests{ingress="canary-demo-preview"}[2m]))
successCondition: len(result) > 0
status:
metricResults:
- count: 1
failed: 1
measurements:
- finishedAt: '2019-10-28T18:23:23Z'
startedAt: '2019-10-28T18:23:23Z'
phase: Failed
value: '[0.9768211920529802]'
name: memory-usage
phase: Failed
phase: Failed

View File

@@ -0,0 +1,31 @@
apiVersion: argoproj.io/v1alpha1
kind: AnalysisRun
metadata:
name: canary-demo-analysis-template-6c6bb7cf6f-ddvn8
namespace: default
spec:
analysisSpec:
metrics:
- failureCondition: len(result) == 0
interval: 10
name: memory-usage
provider:
prometheus:
address: 'http://prometheus-operator-prometheus.prometheus-operator:9090'
query: >
sum(rate(nginx_ingress_controller_requests{ingress="canary-demo-preview",status!~"[4-5].*"}[2m]))
/
sum(rate(nginx_ingress_controller_requests{ingress="canary-demo-preview"}[2m]))
successCondition: len(result) == 0
status:
metricResults:
- count: 1
inconclusive: 1
measurements:
- finishedAt: '2019-10-28T18:24:31Z'
startedAt: '2019-10-28T18:24:31Z'
phase: Inconclusive
value: '[0.9744444444444443]'
name: memory-usage
phase: Inconclusive
phase: Inconclusive

View File

@@ -0,0 +1,19 @@
apiVersion: argoproj.io/v1alpha1
kind: AnalysisRun
metadata:
name: canary-demo-analysis-template-6c6bb7cf6f-9k5rj
namespace: default
spec:
analysisSpec:
metrics:
- failureCondition: len(result) > 0
interval: 10
name: memory-usage
provider:
prometheus:
address: 'http://prometheus-operator-prometheus.prometheus-operator:9090'
query: >
sum(rate(nginx_ingress_controller_requests{ingress="canary-demo-preview",status!~"[4-5].*"}[2m]))
/
sum(rate(nginx_ingress_controller_requests{ingress="canary-demo-preview"}[2m]))
successCondition: len(result) > 0

View File

@@ -0,0 +1,17 @@
apiVersion: argoproj.io/v1alpha1
kind: AnalysisRun
metadata:
name: analysis-template
spec:
metrics:
- name: memory-usage
interval: 10
successCondition: result > 95
failureCondition: result < 92
provider:
prometheus:
address: http://prometheus-operator-prometheus.prometheus-operator:9090
query: |
sum(rate(nginx_ingress_controller_requests{ingress="canary-demo-preview",status!~"[4-5].*"}[2m])) / sum(rate(nginx_ingress_controller_requests{ingress="canary-demo-preview"}[2m]))
status:
phase: Pending

View File

@@ -0,0 +1,35 @@
apiVersion: argoproj.io/v1alpha1
kind: AnalysisRun
metadata:
name: canary-demo-analysis-template-6c6bb7cf6f-5bpxj
namespace: default
spec:
analysisSpec:
metrics:
- failureCondition: len(result) == 0
interval: 10
name: memory-usage
provider:
prometheus:
address: 'http://prometheus-operator-prometheus.prometheus-operator:9090'
query: >
sum(rate(nginx_ingress_controller_requests{ingress="canary-demo-preview",status!~"[4-5].*"}[2m]))
/
sum(rate(nginx_ingress_controller_requests{ingress="canary-demo-preview"}[2m]))
successCondition: len(result) > 0
status:
metricResults:
- count: 2
measurements:
- finishedAt: '2019-10-28T18:22:05Z'
startedAt: '2019-10-28T18:22:05Z'
phase: Successful
value: '[0.9721293199554069]'
- finishedAt: '2019-10-28T18:22:15Z'
startedAt: '2019-10-28T18:22:15Z'
phase: Successful
value: '[0.9721293199554069]'
name: memory-usage
phase: Running
successful: 2
phase: Running

View File

@@ -0,0 +1,30 @@
apiVersion: argoproj.io/v1alpha1
kind: AnalysisRun
metadata:
name: canary-demo-analysis-template-6c6bb7cf6f-zvcmx
namespace: default
spec:
analysisSpec:
metrics:
- failureCondition: len(result) == 0
name: memory-usage
provider:
prometheus:
address: 'http://prometheus-operator-prometheus.prometheus-operator:9090'
query: >
sum(rate(nginx_ingress_controller_requests{ingress="canary-demo-preview",status!~"[4-5].*"}[2m]))
/
sum(rate(nginx_ingress_controller_requests{ingress="canary-demo-preview"}[2m]))
successCondition: len(result) > 0
status:
metricResults:
- count: 1
measurements:
- finishedAt: '2019-10-28T18:20:37Z'
startedAt: '2019-10-28T18:20:37Z'
phase: Successful
value: '[0.965324384787472]'
name: memory-usage
phase: Successful
successful: 1
phase: Successful

View File

@@ -0,0 +1,28 @@
hs = {}
if obj.status ~= nil then
if obj.status.phase == "Pending" then
hs.status = "Progressing"
hs.message = "Experiment is pending"
end
if obj.status.phase == "Running" then
hs.status = "Progressing"
hs.message = "Experiment is running"
end
if obj.status.phase == "Successful" then
hs.status = "Healthy"
hs.message = "Experiment is successful"
end
if obj.status.phase == "Failed" then
hs.status = "Degraded"
hs.message = "Experiment has failed"
end
if obj.status.phase == "Error" then
hs.status = "Degraded"
hs.message = "Experiment had an error"
end
return hs
end
hs.status = "Progressing"
hs.message = "Waiting for experiment to finish: status has not been reconciled."
return hs

View File

@@ -0,0 +1,25 @@
tests:
- healthStatus:
status: Progressing
message: "Experiment is pending"
inputPath: testdata/pendingExperiment.yaml
- healthStatus:
status: Progressing
message: "Waiting for experiment to finish: status has not been reconciled."
inputPath: testdata/noStatusExperiment.yaml
- healthStatus:
status: Progressing
message: "Experiment is running"
inputPath: testdata/runningExperiment.yaml
- healthStatus:
status: Healthy
message: "Experiment is successful"
inputPath: testdata/successfulExperiment.yaml
- healthStatus:
status: Degraded
message: "Experiment has failed"
inputPath: testdata/failedExperiment.yaml
- healthStatus:
status: Degraded
message: "Experiment had an error"
inputPath: testdata/errorExperiment.yaml

View File

@@ -0,0 +1,49 @@
apiVersion: argoproj.io/v1alpha1
kind: Experiment
metadata:
name: experiment-error-template-missing
namespace: jesse-test
spec:
analyses:
- name: does-not-exist
templateName: does-not-exist
templates:
- name: baseline
selector:
matchLabels:
app: rollouts-demo
template:
metadata:
labels:
app: rollouts-demo
spec:
containers:
- image: argoproj/rollouts-demo:blue
name: rollouts-demo
status:
analysisRuns:
- analysisRun: ""
message: 'AnalysisTemplate verification failed for analysis ''does-not-exist'':
analysistemplate.argoproj.io "does-not-exist" not found'
name: does-not-exist
phase: Error
availableAt: "2019-10-27T23:13:10Z"
conditions:
- lastTransitionTime: "2019-10-27T23:13:07Z"
lastUpdateTime: "2019-10-28T05:59:33Z"
message: Experiment "experiment-error-template-missing" is running.
reason: NewReplicaSetAvailable
phase: "True"
type: Progressing
message: 'AnalysisTemplate verification failed for analysis ''does-not-exist'':
analysistemplate.argoproj.io "does-not-exist" not found'
running: true
phase: Error
templateStatuses:
- availableReplicas: 0
lastTransitionTime: "2019-10-28T05:59:33Z"
name: baseline
readyReplicas: 0
replicas: 0
phase: Successful
updatedReplicas: 0

View File

@@ -0,0 +1,54 @@
apiVersion: argoproj.io/v1alpha1
kind: Experiment
metadata:
name: example-experiment
namespace: default
spec:
analyses:
- name: test
templateName: analysis-template
duration: 60
templates:
- name: baseline
selector:
matchLabels:
app: rollouts-demo
color: blue
template:
metadata:
labels:
app: rollouts-demo
color: blue
spec:
containers:
- image: 'argoproj/rollouts-demo:blue'
name: guestbook
status:
analysisRuns:
- analysisRun: example-experiment-test-57vl8
name: test
phase: Failed
availableAt: '2019-10-28T20:58:00Z'
conditions:
- lastTransitionTime: '2019-10-28T20:57:58Z'
lastUpdateTime: '2019-10-28T20:58:01Z'
message: Experiment "example-experiment" is running.
reason: NewReplicaSetAvailable
phase: 'True'
type: Progressing
phase: Failed
templateStatuses:
- availableReplicas: 0
lastTransitionTime: '2019-10-28T20:58:01Z'
name: baseline
readyReplicas: 0
replicas: 0
phase: Successful
updatedReplicas: 0
- availableReplicas: 0
lastTransitionTime: '2019-10-28T20:58:01Z'
name: canary
readyReplicas: 0
replicas: 0
phase: Successful
updatedReplicas: 0

View File

@@ -0,0 +1,33 @@
apiVersion: argoproj.io/v1alpha1
kind: Experiment
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: >
{"apiVersion":"argoproj.io/v1alpha1","kind":"Experiment","metadata":{"annotations":{},"labels":{"app.kubernetes.io/instance":"rollouts-canary"},"name":"example-experiment","namespace":"argo-rollouts"},"spec":{"duration":60,"templates":[{"name":"baseline","selector":{"matchLabels":{"app":"rollouts-demo","color":"blue"}},"template":{"metadata":{"labels":{"app":"rollouts-demo","color":"blue"}},"spec":{"containers":[{"image":"argoproj/rollouts-demo:blue","name":"guestbook"}]}}},{"name":"canary","selector":{"matchLabels":{"app":"rollouts-demo","color":"yellow"}},"template":{"metadata":{"labels":{"app":"rollouts-demo","color":"yellow"}},"spec":{"containers":[{"image":"argoproj/rollouts-demo:yellow","name":"guestbook"}]}}}]}}
creationTimestamp: '2019-10-28T20:13:28Z'
generation: 1
labels:
app.kubernetes.io/instance: rollouts-canary
name: example-experiment
namespace: argo-rollouts
resourceVersion: '28562006'
selfLink: >-
/apis/argoproj.io/v1alpha1/namespaces/argo-rollouts/experiments/example-experiment
uid: 67792f8a-f9bf-11e9-a15b-42010aa80033
spec:
duration: 60
templates:
- name: baseline
selector:
matchLabels:
app: rollouts-demo
color: blue
template:
metadata:
labels:
app: rollouts-demo
color: blue
spec:
containers:
- image: 'argoproj/rollouts-demo:blue'
name: guestbook

View File

@@ -0,0 +1,47 @@
apiVersion: argoproj.io/v1alpha1
kind: Experiment
metadata:
name: experiment-with-analysis-5hm74
namespace: default
spec:
analyses:
- name: job
templateName: job
duration: 3600
templates:
- name: baseline
selector:
matchLabels:
app: rollouts-demo
template:
metadata:
labels:
app: rollouts-demo
spec:
containers:
- image: argoproj/rollouts-demo:blue
name: rollouts-demo
status:
analysisRuns:
- analysisRun: experiment-with-analysis-5hm74-job-h4bgb
name: job
phase: Running
availableAt: "2019-10-21T03:40:28Z"
conditions:
- lastTransitionTime: "2019-10-21T03:40:28Z"
lastUpdateTime: "2019-10-21T03:40:28Z"
message: Experiment "experiment-with-analysis-5hm74" has successfully ran and
completed.
reason: ExperimentCompleted
phase: "False"
type: Progressing
running: false
phase: Pending
templateStatuses:
- availableReplicas: 0
lastTransitionTime: "2019-10-28T20:22:01Z"
name: baseline
readyReplicas: 0
replicas: 0
phase: Progressing
updatedReplicas: 0

View File

@@ -0,0 +1,40 @@
apiVersion: argoproj.io/v1alpha1
kind: Experiment
metadata:
name: example-experiment
namespace: argo-rollouts
spec:
duration: 60
templates:
- name: baseline
selector:
matchLabels:
app: rollouts-demo
color: blue
template:
metadata:
labels:
app: rollouts-demo
color: blue
spec:
containers:
- image: 'argoproj/rollouts-demo:blue'
name: guestbook
status:
availableAt: '2019-10-28T20:15:02Z'
conditions:
- lastTransitionTime: '2019-10-28T20:14:59Z'
lastUpdateTime: '2019-10-28T20:15:02Z'
message: Experiment "example-experiment" is running.
reason: NewReplicaSetAvailable
phase: 'True'
type: Progressing
phase: Running
templateStatuses:
- availableReplicas: 1
lastTransitionTime: '2019-10-28T20:15:02Z'
name: baseline
readyReplicas: 1
replicas: 1
phase: Running
updatedReplicas: 1

View File

@@ -0,0 +1,61 @@
apiVersion: argoproj.io/v1alpha1
kind: Experiment
metadata:
name: example-experiment
namespace: argo-rollouts
spec:
duration: 60
templates:
- name: baseline
selector:
matchLabels:
app: rollouts-demo
color: blue
template:
metadata:
labels:
app: rollouts-demo
color: blue
spec:
containers:
- image: 'argoproj/rollouts-demo:blue'
name: guestbook
- name: canary
selector:
matchLabels:
app: rollouts-demo
color: yellow
template:
metadata:
labels:
app: rollouts-demo
color: yellow
spec:
containers:
- image: 'argoproj/rollouts-demo:yellow'
name: guestbook
status:
availableAt: '2019-10-28T20:15:02Z'
conditions:
- lastTransitionTime: '2019-10-28T20:20:54Z'
lastUpdateTime: '2019-10-28T20:20:54Z'
message: Experiment "example-experiment" has successfully ran and completed.
reason: ExperimentCompleted
phase: 'False'
type: Progressing
phase: Successful
templateStatuses:
- availableReplicas: 1
lastTransitionTime: '2019-10-28T20:15:02Z'
name: baseline
readyReplicas: 1
replicas: 1
phase: Successful
updatedReplicas: 1
- availableReplicas: 1
lastTransitionTime: '2019-10-28T20:15:01Z'
name: canary
readyReplicas: 1
replicas: 1
phase: Successful
updatedReplicas: 1

View File

@@ -1,24 +1,28 @@
discoveryTests:
- inputPath: testdata/paused_rollout.yaml
- inputPath: testdata/pre_v0.6_paused_rollout.yaml
result:
- name: resume
disabled: false
- inputPath: testdata/v0.2_paused_rollout.yaml
result:
- name: resume
disabled: false
- inputPath: testdata/not_paused_rollout.yaml
- inputPath: testdata/pre_v0.6_not_paused_rollout.yaml
result:
- name: resume
disabled: true
- inputPath: testdata/nil_paused_rollout.yaml
- inputPath: testdata/pre_v0.6_nil_paused_rollout.yaml
result:
- name: resume
disabled: true
- inputPath: testdata/has_pause_condition_rollout.yaml
result:
- name: resume
disabled: false
- inputPath: testdata/no_pause_condition_rollout.yaml
result:
- name: resume
disabled: true
actionTests:
- action: resume
inputPath: testdata/paused_rollout.yaml
expectedOutputPath: testdata/not_paused_rollout.yaml
inputPath: testdata/pre_v0.6_paused_rollout.yaml
expectedOutputPath: testdata/pre_v0.6_not_paused_rollout.yaml
- action: resume
inputPath: testdata/v0.2_paused_rollout.yaml
expectedOutputPath: testdata/v0.2_not_paused_rollout.yaml
inputPath: testdata/has_pause_condition_rollout.yaml
expectedOutputPath: testdata/no_pause_condition_rollout.yaml

View File

@@ -3,8 +3,8 @@ actions["resume"] = {["disabled"] = false}
local paused = false
if obj.status ~= nil and obj.status.verifyingPreview ~= nil then
paused = obj.status.verifyingPreview
if obj.status ~= nil and obj.status.pauseConditions ~= nil then
paused = table.getn(obj.status.pauseConditions) > 0
elseif obj.spec.paused ~= nil then
paused = obj.spec.paused
end

View File

@@ -1,5 +1,5 @@
if obj.status.verifyingPreview ~= nil and obj.status.verifyingPreview then
obj.status.verifyingPreview = false
if obj.status.pauseConditions ~= nil and table.getn(obj.status.pauseConditions) > 0 then
obj.status.pauseConditions = nil
end
if obj.spec.paused ~= nil and obj.spec.paused then

View File

@@ -0,0 +1,61 @@
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: canary-demo
namespace: default
spec:
replicas: 5
revisionHistoryLimit: 3
selector:
matchLabels:
app: canary-demo
strategy:
canary:
analysis:
name: analysis
templateName: analysis-template
canaryService: canary-demo-preview
steps:
- setWeight: 40
- pause: {}
- setWeight: 60
- pause: {}
- setWeight: 80
- pause:
duration: 10
template:
metadata:
labels:
app: canary-demo
spec:
containers:
- image: argoproj/rollouts-demo:yellow
imagePullPolicy: Always
name: canary-demo
ports:
- containerPort: 8080
name: http
protocol: TCP
resources:
requests:
cpu: 5m
memory: 32Mi
status:
HPAReplicas: 5
availableReplicas: 5
blueGreen: {}
canary:
currentBackgroundAnalysisRun: canary-demo-6758949f55-6-analysis
stableRS: 645d5dbc4c
controllerPause: true
currentPodHash: 6758949f55
currentStepHash: 59f8666948
currentStepIndex: 1
observedGeneration: 58b949649c
pauseConditions:
- reason: CanaryPauseStep
startTime: "2019-11-05T18:10:29Z"
readyReplicas: 5
replicas: 5
selector: app=canary-demo
updatedReplicas: 2

View File

@@ -0,0 +1,58 @@
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: canary-demo
namespace: default
spec:
replicas: 5
revisionHistoryLimit: 3
selector:
matchLabels:
app: canary-demo
strategy:
canary:
analysis:
name: analysis
templateName: analysis-template
canaryService: canary-demo-preview
steps:
- setWeight: 40
- pause: {}
- setWeight: 60
- pause: {}
- setWeight: 80
- pause:
duration: 10
template:
metadata:
labels:
app: canary-demo
spec:
containers:
- image: argoproj/rollouts-demo:yellow
imagePullPolicy: Always
name: canary-demo
ports:
- containerPort: 8080
name: http
protocol: TCP
resources:
requests:
cpu: 5m
memory: 32Mi
status:
HPAReplicas: 5
availableReplicas: 5
blueGreen: {}
canary:
currentBackgroundAnalysisRun: canary-demo-6758949f55-6-analysis
stableRS: 645d5dbc4c
controllerPause: true
currentPodHash: 6758949f55
currentStepHash: 59f8666948
currentStepIndex: 1
observedGeneration: 58b949649c
readyReplicas: 5
replicas: 5
selector: app=canary-demo
updatedReplicas: 2

View File

@@ -1,55 +0,0 @@
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
annotations:
rollout.argoproj.io/revision: "7"
clusterName: ""
creationTimestamp: 2019-01-22T16:52:54Z
generation: 1
labels:
app.kubernetes.io/instance: guestbook-default
name: ks-guestbook-ui
namespace: default
resourceVersion: "164113"
selfLink: /apis/argoproj.io/v1alpha1/namespaces/default/rollouts/ks-guestbook-ui
uid: 29802403-1e66-11e9-a6a4-025000000001
spec:
minReadySeconds: 30
replicas: 3
selector:
matchLabels:
app: ks-guestbook-ui
strategy:
blueGreen:
activeService: ks-guestbook-ui-active
previewService: ks-guestbook-ui-preview
type: BlueGreenUpdate
template:
metadata:
labels:
app: ks-guestbook-ui
spec:
containers:
- image: gcr.io/heptio-images/ks-guestbook-demo:0.1
name: ks-guestbook-ui
ports:
- containerPort: 83
resources: {}
status:
blueGreen:
activeSelector: 85f9884f5d
previewSelector: 697fb9575c
availableReplicas: 6
conditions:
- lastTransitionTime: 2019-01-25T07:44:26Z
lastUpdateTime: 2019-01-25T07:44:26Z
message: Rollout is serving traffic from the active service.
reason: Available
status: "True"
type: Available
currentPodHash: 697fb9575c
observedGeneration: 767f98959f
readyReplicas: 6
replicas: 6
updatedReplicas: 3
verifyingPreview: false

View File

@@ -1,55 +0,0 @@
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
annotations:
rollout.argoproj.io/revision: "7"
clusterName: ""
creationTimestamp: 2019-01-22T16:52:54Z
generation: 1
labels:
app.kubernetes.io/instance: guestbook-default
name: ks-guestbook-ui
namespace: default
resourceVersion: "164113"
selfLink: /apis/argoproj.io/v1alpha1/namespaces/default/rollouts/ks-guestbook-ui
uid: 29802403-1e66-11e9-a6a4-025000000001
spec:
minReadySeconds: 30
replicas: 3
selector:
matchLabels:
app: ks-guestbook-ui
strategy:
blueGreen:
activeService: ks-guestbook-ui-active
previewService: ks-guestbook-ui-preview
type: BlueGreenUpdate
template:
metadata:
labels:
app: ks-guestbook-ui
spec:
containers:
- image: gcr.io/heptio-images/ks-guestbook-demo:0.1
name: ks-guestbook-ui
ports:
- containerPort: 83
resources: {}
status:
blueGreen:
activeSelector: 85f9884f5d
previewSelector: 697fb9575c
availableReplicas: 6
conditions:
- lastTransitionTime: 2019-01-25T07:44:26Z
lastUpdateTime: 2019-01-25T07:44:26Z
message: Rollout is serving traffic from the active service.
reason: Available
status: "True"
type: Available
currentPodHash: 697fb9575c
observedGeneration: 767f98959f
readyReplicas: 6
replicas: 6
updatedReplicas: 3
verifyingPreview: true

View File

@@ -10,22 +10,9 @@ function checkReplicasStatus(obj)
hs.message = "Waiting for roll out to finish: More replicas need to be updated"
return hs
end
if replicasStatus > updatedReplicas then
hs.status = "Progressing"
hs.message = "Waiting for roll out to finish: old replicas are pending termination"
return hs
end
if availableReplicas < updatedReplicas then
hs.status = "Progressing"
hs.message = "Waiting for roll out to finish: updated replicas are still becoming available"
return hs
end
if updatedReplicas < replicasCount then
hs.status = "Progressing"
hs.message = "Waiting for roll out to finish: More replicas need to be updated"
return hs
end
if replicasStatus > updatedReplicas then
-- Since the scale down delay can be very high, BlueGreen does not wait for all the old replicas to scale
-- down before marking itself healthy. As a result, only evaluate this condition if the strategy is canary.
if obj.spec.strategy.canary ~= nil and replicasStatus > updatedReplicas then
hs.status = "Progressing"
hs.message = "Waiting for roll out to finish: old replicas are pending termination"
return hs
@@ -71,6 +58,11 @@ if obj.status ~= nil then
hs.message = condition.message
return hs
end
if condition.type == "Progressing" and condition.reason == "RolloutAborted" then
hs.status = "Degraded"
hs.message = condition.message
return hs
end
if condition.type == "Progressing" and condition.reason == "ProgressDeadlineExceeded" then
hs.status = "Degraded"
hs.message = condition.message
@@ -88,7 +80,7 @@ if obj.status ~= nil then
if replicasHS ~= nil then
return replicasHS
end
if obj.status.blueGreen ~= nil and obj.status.blueGreen.activeSelector ~= nil and obj.status.currentPodHash ~= nil and obj.status.blueGreen.activeSelector == obj.status.currentPodHash then
if obj.status.blueGreen ~= nil and obj.status.blueGreen.activeSelector ~= nil and obj.status.blueGreen.activeSelector == obj.status.currentPodHash then
hs.status = "Healthy"
hs.message = "The active Service is serving traffic to the current pod spec"
return hs

View File

@@ -11,6 +11,10 @@ tests:
status: Degraded
message: ReplicaSet "guestbook-bluegreen-helm-guestbook-6b8cf6f7db" has timed out progressing.
inputPath: testdata/degraded_rolloutTimeout.yaml
- healthStatus:
status: Degraded
message: Rollout is aborted
inputPath: testdata/degraded_abortedRollout.yaml
#BlueGreen
- healthStatus:
status: Healthy
@@ -28,10 +32,6 @@ tests:
status: Progressing
message: "Waiting for roll out to finish: More replicas need to be updated"
inputPath: testdata/bluegreen/progressing_addingMoreReplicas.yaml
- healthStatus:
status: Progressing
message: "Waiting for roll out to finish: old replicas are pending termination"
inputPath: testdata/bluegreen/progressing_killingOldReplicas.yaml
- healthStatus:
status: Progressing
message: "Waiting for roll out to finish: updated replicas are still becoming available"
@@ -41,6 +41,10 @@ tests:
status: Progressing
message: Waiting for rollout to finish steps
inputPath: testdata/canary/progressing_setWeightStep.yaml
- healthStatus:
status: Progressing
message: "Waiting for roll out to finish: old replicas are pending termination"
inputPath: testdata/canary/progressing_killingOldReplicas.yaml
- healthStatus:
status: Suspended
message: Rollout is paused

View File

@@ -1,57 +0,0 @@
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"argoproj.io/v1alpha1","kind":"Rollout","metadata":{"annotations":{},"labels":{"app.kubernetes.io/instance":"guestbook-default","ksonnet.io/component":"guestbook-ui"},"name":"ks-guestbook-ui","namespace":"default"},"spec":{"minReadySeconds":30,"replicas":3,"selector":{"matchLabels":{"app":"ks-guestbook-ui"}},"strategy":{"blueGreen":{"activeService":"ks-guestbook-ui-active","previewService":"ks-guestbook-ui-preview"},"type":"BlueGreenUpdate"},"template":{"metadata":{"labels":{"app":"ks-guestbook-ui"}},"spec":{"containers":[{"image":"gcr.io/heptio-images/ks-guestbook-demo:0.1","name":"ks-guestbook-ui","ports":[{"containerPort":83}]}]}}}}
rollout.argoproj.io/revision: "7"
clusterName: ""
creationTimestamp: 2019-01-22T16:52:54Z
generation: 1
labels:
app.kubernetes.io/instance: guestbook-default
ksonnet.io/component: guestbook-ui
name: ks-guestbook-ui
namespace: default
resourceVersion: "164141"
selfLink: /apis/argoproj.io/v1alpha1/namespaces/default/rollouts/ks-guestbook-ui
uid: 29802403-1e66-11e9-a6a4-025000000001
spec:
minReadySeconds: 30
replicas: 3
selector:
matchLabels:
app: ks-guestbook-ui
strategy:
blueGreen:
activeService: ks-guestbook-ui-active
previewService: ks-guestbook-ui-preview
type: BlueGreenUpdate
template:
metadata:
creationTimestamp: null
labels:
app: ks-guestbook-ui
spec:
containers:
- image: gcr.io/heptio-images/ks-guestbook-demo:0.1
name: ks-guestbook-ui
ports:
- containerPort: 83
resources: {}
status:
activeSelector: 697fb9575c
availableReplicas: 6
conditions:
- lastTransitionTime: 2019-01-25T07:44:26Z
lastUpdateTime: 2019-01-25T07:44:26Z
message: Rollout is serving traffic from the active service.
reason: Available
status: "True"
type: Available
currentPodHash: 697fb9575c
observedGeneration: 767f98959f
previewSelector: ""
readyReplicas: 6
replicas: 6
updatedReplicas: 3

View File

@@ -0,0 +1,61 @@
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"argoproj.io/v1alpha1","kind":"Rollout","metadata":{"annotations":{},"name":"example-rollout-canary","namespace":"default"},"spec":{"minReadySeconds":30,"replicas":5,"revisionHistoryLimit":3,"selector":{"matchLabels":{"app":"guestbook"}},"strategy":{"canary":{"steps":[{"setWeight":20},{"pause":{"duration":20}},{"setWeight":40},{"pause":{}}]}},"template":{"metadata":{"labels":{"app":"guestbook"}},"spec":{"containers":[{"image":"gcr.io/heptio-images/ks-guestbook-demo:0.1","name":"guestbook","ports":[{"containerPort":80}]}]}}}}
rollout.argoproj.io/revision: "3"
creationTimestamp: "2019-10-20T15:42:26Z"
generation: 101
name: example-rollout-canary
namespace: default
resourceVersion: "1779901"
selfLink: /apis/argoproj.io/v1alpha1/namespaces/default/rollouts/example-rollout-canary
uid: f8ebf794-8b4e-4a1f-a2d1-a85bc1d206ef
spec:
minReadySeconds: 30
replicas: 5
revisionHistoryLimit: 3
selector:
matchLabels:
app: guestbook
strategy:
canary: {}
template:
metadata:
creationTimestamp: null
labels:
app: guestbook
spec:
containers:
- image: gcr.io/heptio-images/ks-guestbook-demo:0.1
name: guestbook
ports:
- containerPort: 80
resources: {}
status:
HPAReplicas: 6
availableReplicas: 5
blueGreen: {}
canary:
stableRS: 74d6dc8544
conditions:
- lastTransitionTime: "2019-10-25T16:08:02Z"
lastUpdateTime: "2019-10-25T16:08:02Z"
message: Rollout has minimum availability
reason: AvailableReason
status: "True"
type: Available
- lastTransitionTime: "2019-10-25T16:50:19Z"
lastUpdateTime: "2019-11-07T18:19:25Z"
message: ReplicaSet "example-rollout-canary-694fb7759c" is progressing.
reason: ReplicaSetUpdated
status: "True"
type: Progressing
currentPodHash: 694fb7759c
currentStepHash: 5ffbfbbd64
observedGeneration: 7fcc96c7b7
readyReplicas: 6
replicas: 6
selector: app=guestbook
updatedReplicas: 5

View File

@@ -0,0 +1,70 @@
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: canary-demo
namespace: default
spec:
replicas: 5
revisionHistoryLimit: 3
selector:
matchLabels:
app: canary-demo
strategy:
canary:
analysis:
name: analysis
templateName: analysis-template
canaryService: canary-demo-preview
steps:
- setWeight: 40
- pause: {}
- setWeight: 60
- pause: {}
- setWeight: 80
- pause:
duration: 10
template:
metadata:
creationTimestamp: null
labels:
app: canary-demo
spec:
containers:
- image: argoproj/rollouts-demo:yellow
imagePullPolicy: Always
name: canary-demo
ports:
- containerPort: 8080
name: http
protocol: TCP
resources:
requests:
cpu: 5m
memory: 32Mi
status:
HPAReplicas: 5
abort: true
availableReplicas: 5
blueGreen: {}
canary:
stableRS: 645d5dbc4c
conditions:
- lastTransitionTime: "2019-11-03T01:32:46Z"
lastUpdateTime: "2019-11-03T01:32:46Z"
message: Rollout has minimum availability
reason: AvailableReason
status: "True"
type: Available
- lastTransitionTime: "2019-11-05T18:20:12Z"
lastUpdateTime: "2019-11-05T18:20:12Z"
message: Rollout is aborted
reason: RolloutAborted
status: "False"
type: Progressing
currentPodHash: 6758949f55
currentStepHash: 59f8666948
currentStepIndex: 0
observedGeneration: 58b949649c
readyReplicas: 5
replicas: 5
selector: app=canary-demo

View File

@@ -1037,24 +1037,29 @@ func (s *Server) resolveRevision(ctx context.Context, app *appv1.Application, sy
if ambiguousRevision == "" {
ambiguousRevision = app.Spec.Source.TargetRevision
}
if git.IsCommitSHA(ambiguousRevision) {
// If it's already a commit SHA, then no need to look it up
if app.Spec.Source.IsHelm() {
return ambiguousRevision, ambiguousRevision, nil
} else {
if git.IsCommitSHA(ambiguousRevision) {
// If it's already a commit SHA, then no need to look it up
return ambiguousRevision, ambiguousRevision, nil
}
repo, err := s.db.GetRepository(ctx, app.Spec.Source.RepoURL)
if err != nil {
return "", "", err
}
gitClient, err := git.NewClient(repo.Repo, repo.GetGitCreds(), repo.IsInsecure(), repo.IsLFSEnabled())
if err != nil {
return "", "", err
}
commitSHA, err := gitClient.LsRemote(ambiguousRevision)
if err != nil {
return "", "", err
}
displayRevision := fmt.Sprintf("%s (%s)", ambiguousRevision, commitSHA)
return commitSHA, displayRevision, nil
}
repo, err := s.db.GetRepository(ctx, app.Spec.Source.RepoURL)
if err != nil {
return "", "", err
}
gitClient, err := git.NewClient(repo.Repo, repo.GetGitCreds(), repo.IsInsecure(), repo.IsLFSEnabled())
if err != nil {
return "", "", err
}
commitSHA, err := gitClient.LsRemote(ambiguousRevision)
if err != nil {
return "", "", err
}
displayRevision := fmt.Sprintf("%s (%s)", ambiguousRevision, commitSHA)
return commitSHA, displayRevision, nil
}
func (s *Server) TerminateOperation(ctx context.Context, termOpReq *application.OperationTerminateRequest) (*application.OperationTerminateResponse, error) {

View File

@@ -41,37 +41,38 @@ func NewServer(db db.ArgoDB, enf *rbac.Enforcer, cache *cache.Cache, kubectl kub
}
}
func (s *Server) getConnectionState(cluster appv1.Cluster, errorMessage string) appv1.ConnectionState {
if connectionState, err := s.cache.GetClusterConnectionState(cluster.Server); err == nil {
return connectionState
func (s *Server) getConnectionState(cluster appv1.Cluster, errorMessage string) (appv1.ConnectionState, string) {
if clusterInfo, err := s.cache.GetClusterInfo(cluster.Server); err == nil {
return clusterInfo.ConnectionState, clusterInfo.Version
}
now := v1.Now()
connectionState := appv1.ConnectionState{
Status: appv1.ConnectionStatusSuccessful,
ModifiedAt: &now,
clusterInfo := cache.ClusterInfo{
ConnectionState: appv1.ConnectionState{
Status: appv1.ConnectionStatusSuccessful,
ModifiedAt: &now,
},
}
config := cluster.RESTConfig()
config.Timeout = time.Second
kubeClientset, err := kubernetes.NewForConfig(config)
if err == nil {
_, err = kubeClientset.Discovery().ServerVersion()
}
version, err := s.kubectl.GetServerVersion(config)
if err != nil {
connectionState.Status = appv1.ConnectionStatusFailed
connectionState.Message = fmt.Sprintf("Unable to connect to cluster: %v", err)
clusterInfo.Status = appv1.ConnectionStatusFailed
clusterInfo.Message = fmt.Sprintf("Unable to connect to cluster: %v", err)
} else {
clusterInfo.Version = version
}
if errorMessage != "" {
connectionState.Status = appv1.ConnectionStatusFailed
connectionState.Message = fmt.Sprintf("%s %s", errorMessage, connectionState.Message)
clusterInfo.Status = appv1.ConnectionStatusFailed
clusterInfo.Message = fmt.Sprintf("%s %s", errorMessage, clusterInfo.Message)
}
err = s.cache.SetClusterConnectionState(cluster.Server, &connectionState)
err = s.cache.SetClusterInfo(cluster.Server, &clusterInfo)
if err != nil {
log.Warnf("getConnectionState cache set error %s: %v", cluster.Server, err)
log.Warnf("getClusterInfo cache set error %s: %v", cluster.Server, err)
}
return connectionState
return clusterInfo.ConnectionState, clusterInfo.Version
}
// List returns list of clusters
@@ -100,11 +101,9 @@ func (s *Server) List(ctx context.Context, q *cluster.ClusterQuery) (*appv1.Clus
warningMessage = fmt.Sprintf("There are %d credentials configured this cluster.", len(clusters))
}
if clust.ConnectionState.Status == "" {
clust.ConnectionState = s.getConnectionState(clust, warningMessage)
}
clust.ServerVersion, err = s.kubectl.GetServerVersion(clust.RESTConfig())
if err != nil {
return err
state, serverVersion := s.getConnectionState(clust, warningMessage)
clust.ConnectionState = state
clust.ServerVersion = serverVersion
}
items[i] = *redact(&clust)
return nil

View File

@@ -10,6 +10,7 @@ import (
"google.golang.org/grpc/status"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/argoproj/argo-cd/common"
repositorypkg "github.com/argoproj/argo-cd/pkg/apiclient/repository"
appsv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/reposerver/apiclient"
@@ -82,10 +83,15 @@ func (s *Server) List(ctx context.Context, q *repositorypkg.RepoQuery) (*appsv1.
items := appsv1.Repositories{}
for _, repo := range repos {
if s.enf.Enforce(ctx.Value("claims"), rbacpolicy.ResourceRepositories, rbacpolicy.ActionGet, repo.Repo) {
// For backwards compatibility, if we have no repo type set assume a default
rType := repo.Type
if rType == "" {
rType = common.DefaultRepoType
}
// remove secrets
items = append(items, &appsv1.Repository{
Repo: repo.Repo,
Type: repo.Type,
Type: rType,
Name: repo.Name,
Username: repo.Username,
Insecure: repo.IsInsecure(),
@@ -222,7 +228,10 @@ func (s *Server) Create(ctx context.Context, q *repositorypkg.RepoCreateRequest)
return nil, status.Errorf(codes.InvalidArgument, "existing repository spec is different; use upsert flag to force update")
}
}
return &appsv1.Repository{Repo: repo.Repo, Type: repo.Type, Name: repo.Name}, err
if err != nil {
return nil, err
}
return &appsv1.Repository{Repo: repo.Repo, Type: repo.Type, Name: repo.Name}, nil
}
// Update updates a repository

View File

@@ -450,7 +450,7 @@ func (a *ArgoCDServer) newGRPCServer() *grpc.Server {
)))
grpcS := grpc.NewServer(sOpts...)
db := db.NewDB(a.Namespace, a.settingsMgr, a.KubeClientset)
kubectl := kube.KubectlCmd{}
kubectl := &kube.KubectlCmd{}
clusterService := cluster.NewServer(db, a.enf, a.Cache, kubectl)
repoService := repository.NewServer(a.RepoClientset, db, a.enf, a.Cache, a.settingsMgr)
sessionService := session.NewServer(a.sessionMgr, a)
@@ -478,22 +478,23 @@ func (a *ArgoCDServer) newGRPCServer() *grpc.Server {
// TranslateGrpcCookieHeader conditionally sets a cookie on the response.
func (a *ArgoCDServer) translateGrpcCookieHeader(ctx context.Context, w http.ResponseWriter, resp golang_proto.Message) error {
if sessionResp, ok := resp.(*sessionpkg.SessionResponse); ok {
flags := []string{"path=/"}
flags := []string{"path=/", "SameSite=lax", "httpOnly"}
if !a.Insecure {
flags = append(flags, "Secure")
}
token := sessionResp.Token
if token != "" {
token, err := zjwt.ZJWT(token)
var err error
token, err = zjwt.ZJWT(token)
if err != nil {
return err
}
cookie, err := httputil.MakeCookieMetadata(common.AuthCookieName, token, flags...)
if err != nil {
return err
}
w.Header().Set("Set-Cookie", cookie)
}
cookie, err := httputil.MakeCookieMetadata(common.AuthCookieName, token, flags...)
if err != nil {
return err
}
w.Header().Set("Set-Cookie", cookie)
}
return nil
}
@@ -551,6 +552,7 @@ func (a *ArgoCDServer) newHTTPServer(ctx context.Context, port int, grpcWebHandl
mustRegisterGWHandler(sessionpkg.RegisterSessionServiceHandlerFromEndpoint, ctx, gwmux, endpoint, dOpts)
mustRegisterGWHandler(settingspkg.RegisterSettingsServiceHandlerFromEndpoint, ctx, gwmux, endpoint, dOpts)
mustRegisterGWHandler(projectpkg.RegisterProjectServiceHandlerFromEndpoint, ctx, gwmux, endpoint, dOpts)
mustRegisterGWHandler(accountpkg.RegisterAccountServiceHandlerFromEndpoint, ctx, gwmux, endpoint, dOpts)
mustRegisterGWHandler(certificatepkg.RegisterCertificateServiceHandlerFromEndpoint, ctx, gwmux, endpoint, dOpts)
// Swagger UI

View File

@@ -4,10 +4,13 @@ import (
"context"
"fmt"
"io/ioutil"
"net/http/httptest"
"strings"
"testing"
"time"
"github.com/argoproj/argo-cd/pkg/apiclient/session"
"google.golang.org/grpc/metadata"
"github.com/dgrijalva/jwt-go"
@@ -479,3 +482,31 @@ func Test_getToken(t *testing.T) {
assert.Equal(t, token, getToken(metadata.New(map[string]string{"grpcgateway-cookie": "argocd.token=" + token})))
})
}
func TestTranslateGrpcCookieHeader(t *testing.T) {
argoCDOpts := ArgoCDServerOpts{
Namespace: test.FakeArgoCDNamespace,
KubeClientset: fake.NewSimpleClientset(test.NewFakeConfigMap(), test.NewFakeSecret()),
AppClientset: apps.NewSimpleClientset(),
}
argocd := NewServer(context.Background(), argoCDOpts)
t.Run("TokenIsNotEmpty", func(t *testing.T) {
recorder := httptest.NewRecorder()
err := argocd.translateGrpcCookieHeader(context.Background(), recorder, &session.SessionResponse{
Token: "xyz",
})
assert.NoError(t, err)
assert.Equal(t, "argocd.token=xyz; path=/; SameSite=lax; httpOnly; Secure", recorder.Result().Header.Get("Set-Cookie"))
})
t.Run("TokenIsEmpty", func(t *testing.T) {
recorder := httptest.NewRecorder()
err := argocd.translateGrpcCookieHeader(context.Background(), recorder, &session.SessionResponse{
Token: "",
})
assert.NoError(t, err)
assert.Equal(t, "argocd.token=; path=/; SameSite=lax; httpOnly; Secure", recorder.Result().Header.Get("Set-Cookie"))
})
}

View File

@@ -25,31 +25,36 @@ func (s *Server) Version(context.Context, *empty.Empty) (*version.VersionMessage
vers := common.GetVersion()
if s.ksonnetVersion == "" {
ksonnetVersion, err := ksutil.Version()
if err != nil {
return nil, err
if err == nil {
s.ksonnetVersion = ksonnetVersion
} else {
s.ksonnetVersion = err.Error()
}
s.ksonnetVersion = ksonnetVersion
}
if s.kustomizeVersion == "" {
kustomizeVersion, err := kustomize.Version()
if err != nil {
return nil, err
if err == nil {
s.kustomizeVersion = kustomizeVersion
} else {
s.kustomizeVersion = err.Error()
}
s.kustomizeVersion = kustomizeVersion
}
if s.helmVersion == "" {
helmVersion, err := helm.Version()
if err != nil {
return nil, err
if err == nil {
s.helmVersion = helmVersion
} else {
s.helmVersion = err.Error()
}
s.helmVersion = helmVersion
}
if s.kubectlVersion == "" {
kubectlVersion, err := kube.Version()
if err != nil {
return nil, err
if err == nil {
s.kubectlVersion = kubectlVersion
} else {
s.kubectlVersion = err.Error()
}
s.kubectlVersion = kubectlVersion
}
return &version.VersionMessage{
Version: vers.Version,

View File

@@ -567,6 +567,18 @@ func TestLocalManifestSync(t *testing.T) {
})
}
func TestLocalSync(t *testing.T) {
Given(t).
// we've got to use Helm as this uses kubeVersion
Path("helm").
When().
Create().
Then().
And(func(app *Application) {
FailOnErr(RunCli("app", "sync", app.Name, "--local", "testdata/helm"))
})
}
func TestNoLocalSyncWithAutosyncEnabled(t *testing.T) {
Given(t).
Path(guestbookPath).

View File

@@ -1,8 +1,6 @@
package app
import (
"os"
"strconv"
"time"
log "github.com/sirupsen/logrus"
@@ -24,7 +22,7 @@ func (c *Consequences) Expect(e Expectation) *Consequences {
c.context.t.Helper()
var message string
var state state
timeout := c.timeout()
timeout := time.Duration(15) * time.Second
for start := time.Now(); time.Since(start) < timeout; time.Sleep(3 * time.Second) {
state, message = e(c)
switch state {
@@ -78,12 +76,3 @@ func (c *Consequences) resource(kind, name string) ResourceStatus {
},
}
}
func (c *Consequences) timeout() time.Duration {
value := os.Getenv("ARGOCD_E2E_EXPECT_TIMEOUT")
timeout, err := strconv.Atoi(value)
if err != nil {
timeout = 15
}
return time.Duration(timeout) * time.Second
}

View File

@@ -90,7 +90,7 @@ func TestDeclarativeHelmInvalidValuesFile(t *testing.T) {
func TestHelmRepo(t *testing.T) {
Given(t).
CustomCACertAdded().
HelmRepoAdded("").
HelmRepoAdded("custom-repo").
RepoURLType(RepoURLTypeHelm).
Chart("helm").
Revision("1.0.0").
@@ -193,7 +193,10 @@ func TestHelmWithDependenciesLegacyRepo(t *testing.T) {
}
func testHelmWithDependencies(t *testing.T, legacyRepo bool) {
ctx := Given(t).CustomCACertAdded()
ctx := Given(t).
CustomCACertAdded().
// these are slow tests
Timeout(30)
if legacyRepo {
ctx.And(func() {
errors.FailOnErr(fixture.Run("", "kubectl", "create", "secret", "generic", "helm-repo",

View File

@@ -4,21 +4,24 @@ import (
"fmt"
"os"
"path/filepath"
. "strings"
"testing"
argoexec "github.com/argoproj/pkg/exec"
"github.com/stretchr/testify/assert"
"github.com/argoproj/argo-cd/test/fixture/test"
)
func TestKustomizeVersion(t *testing.T) {
test.CIOnly(t)
out, err := argoexec.RunCommand("kustomize", argoexec.CmdOpts{}, "version")
assert.NoError(t, err)
assert.Contains(t, out, "Version:kustomize/v3", "kustomize should be version 3")
}
// TestBuildManifests makes sure we are consistent in naming, and all kustomization.yamls are buildable
func TestBuildManifests(t *testing.T) {
out, err := argoexec.RunCommand("kustomize", argoexec.CmdOpts{}, "version")
assert.NoError(t, err)
assert.True(t, Contains(out, "KustomizeVersion:3") || Contains(out, "KustomizeVersion:v3"), "kustomize should be version 3")
err = filepath.Walk("../manifests", func(path string, f os.FileInfo, err error) error {
err := filepath.Walk("../manifests", func(path string, f os.FileInfo, err error) error {
if err != nil {
return err
}

9
ui/.prettierrc Normal file
View File

@@ -0,0 +1,9 @@
{
"bracketSpacing": false,
"jsxSingleQuote": true,
"printWidth": 180,
"singleQuote": true,
"tabWidth": 4,
"jsxBracketSameLine": true,
"quoteProps": "consistent"
}

View File

@@ -72,15 +72,26 @@ async function isExpiredSSO() {
requests.onError.subscribe(async (err) => {
if (err.status === 401) {
if (!history.location.pathname.startsWith('/login')) {
// Query for basehref and remove trailing /.
// If basehref is the default `/` it will become an empty string.
const basehref = document.querySelector('head > base').getAttribute('href').replace(/\/$/, '');
if (await isExpiredSSO()) {
window.location.href = `${basehref}/auth/login?return_url=${encodeURIComponent(location.href)}`;
} else {
history.push(`${basehref}/login?return_url=${encodeURIComponent(location.href)}`);
}
if (history.location.pathname.startsWith('/login')) {
return;
}
const isSSO = await isExpiredSSO();
// location might change after async method call, so we need to check again.
if (history.location.pathname.startsWith('/login')) {
return;
}
// Query for basehref and remove trailing /.
// If basehref is the default `/` it will become an empty string.
const basehref = document
.querySelector('head > base')
.getAttribute('href')
.replace(/\/$/, '');
if (isSSO) {
window.location.href = `${basehref}/auth/login?return_url=${encodeURIComponent(location.href)}`;
} else {
history.push(`${basehref}/login?return_url=${encodeURIComponent(location.href)}`);
}
}
});

View File

@@ -522,7 +522,7 @@ export class ApplicationDetails extends React.Component<RouteComponentProps<{ na
const resourceActions = services.applications.getResourceActions(application.metadata.name, resource)
.then((actions) => items.concat(actions.map((action) => ({
title: action.name,
disabled: !action.available,
disabled: !!action.disabled,
action: async () => {
try {
const confirmed = await this.appContext.apis.popup.confirm(

View File

@@ -1,21 +1,23 @@
import { ErrorNotification, MockupList, NotificationType, SlidingPanel } from 'argo-ui';
import {ErrorNotification, MockupList, NotificationType, SlidingPanel} from 'argo-ui';
import * as classNames from 'classnames';
import * as minimatch from 'minimatch';
import * as React from 'react';
import { RouteComponentProps } from 'react-router';
import { Observable } from 'rxjs';
import {RouteComponentProps} from 'react-router';
import {Observable} from 'rxjs';
import { Autocomplete, ClusterCtx, DataLoader, EmptyState, ObservableQuery, Page, Paginate, Query } from '../../../shared/components';
import { Consumer } from '../../../shared/context';
import {Autocomplete, ClusterCtx, DataLoader, EmptyState, ObservableQuery, Page, Paginate, Query} from '../../../shared/components';
import {Consumer} from '../../../shared/context';
import * as models from '../../../shared/models';
import { AppsListPreferences, AppsListViewType, services } from '../../../shared/services';
import { ApplicationCreatePanel } from '../application-create-panel/application-create-panel';
import { ApplicationSyncPanel } from '../application-sync-panel/application-sync-panel';
import {AppsListPreferences, AppsListViewType, services} from '../../../shared/services';
import {ApplicationCreatePanel} from '../application-create-panel/application-create-panel';
import {ApplicationSyncPanel} from '../application-sync-panel/application-sync-panel';
import {ApplicationsSyncPanel} from '../applications-sync-panel/applications-sync-panel';
import * as LabelSelector from '../label-selector';
import * as AppUtils from '../utils';
import { ApplicationsFilter } from './applications-filter';
import { ApplicationsSummary } from './applications-summary';
import { ApplicationsTable } from './applications-table';
import { ApplicationTiles } from './applications-tiles';
import {ApplicationsFilter} from './applications-filter';
import {ApplicationsSummary} from './applications-summary';
import {ApplicationsTable} from './applications-table';
import {ApplicationTiles} from './applications-tiles';
require('./applications-list.scss');
@@ -25,93 +27,125 @@ const APP_FIELDS = [
'metadata.labels',
'metadata.resourceVersion',
'metadata.creationTimestamp',
'metadata.deletionTimestamp',
'spec',
'operation.sync',
'status.sync.status',
'status.health',
'status.summary'];
'status.operationState.phase',
'status.operationState.operation.sync',
'status.summary',
];
const APP_LIST_FIELDS = APP_FIELDS.map((field) => `items.${field}`);
const APP_WATCH_FIELDS = ['result.type', ...APP_FIELDS.map((field) => `result.application.${field}`)];
function loadApplications(selector: string): Observable<models.Application[]> {
return Observable.fromPromise(services.applications.list([], { fields: APP_LIST_FIELDS, selector })).flatMap((applications) =>
function loadApplications(): Observable<models.Application[]> {
return Observable.fromPromise(services.applications.list([], {fields: APP_LIST_FIELDS})).flatMap((applications) =>
Observable.merge(
Observable.from([applications]),
services.applications.watch(null, { fields: APP_WATCH_FIELDS, selector }).map((appChange) => {
const index = applications.findIndex((item) => item.metadata.name === appChange.application.metadata.name);
if (index > -1 && appChange.application.metadata.resourceVersion === applications[index].metadata.resourceVersion) {
return {applications, updated: false};
}
switch (appChange.type) {
case 'DELETED':
if (index > -1) {
applications.splice(index, 1);
}
break;
default:
if (index > -1) {
applications[index] = appChange.application;
} else {
applications.unshift(appChange.application);
}
break;
}
return {applications, updated: true};
}).filter((item) => item.updated).map((item) => item.applications),
services.applications
.watch(null, {fields: APP_WATCH_FIELDS})
.map((appChange) => {
const index = applications.findIndex((item) => item.metadata.name === appChange.application.metadata.name);
if (index > -1 && appChange.application.metadata.resourceVersion === applications[index].metadata.resourceVersion) {
return {applications, updated: false};
}
switch (appChange.type) {
case 'DELETED':
if (index > -1) {
applications.splice(index, 1);
}
break;
default:
if (index > -1) {
applications[index] = appChange.application;
} else {
applications.unshift(appChange.application);
}
break;
}
return {applications, updated: true};
})
.filter((item) => item.updated)
.map((item) => item.applications),
),
);
}
const ViewPref = ({children}: { children: (pref: AppsListPreferences & { page: number, search: string }) => React.ReactNode }) => (
const ViewPref = ({children}: {children: (pref: AppsListPreferences & {page: number; search: string}) => React.ReactNode}) => (
<ObservableQuery>
{(q) => (
<DataLoader load={() => Observable.combineLatest(
services.viewPreferences.getPreferences().map((item) => item.appList), q).map((items) => {
const params = items[1];
const viewPref: AppsListPreferences = {...items[0]};
if (params.get('proj') != null) {
viewPref.projectsFilter = params.get('proj').split(',').filter((item) => !!item);
}
if (params.get('sync') != null) {
viewPref.syncFilter = params.get('sync').split(',').filter((item) => !!item);
}
if (params.get('health') != null) {
viewPref.healthFilter = params.get('health').split(',').filter((item) => !!item);
}
if (params.get('namespace') != null) {
viewPref.namespacesFilter = params.get('namespace').split(',').filter((item) => !!item);
}
if (params.get('cluster') != null) {
viewPref.clustersFilter = params.get('cluster').split(',').filter((item) => !!item);
}
if (params.get('view') != null) {
viewPref.view = params.get('view') as AppsListViewType;
}
if (params.get('labels') != null) {
viewPref.labelsFilter = params.get('labels').split(',').filter((item) => !!item);
}
return {...viewPref, page: parseInt(params.get('page') || '0', 10), search: params.get('search') || ''};
})}>
{(pref) => children(pref)}
<DataLoader
load={() =>
Observable.combineLatest(services.viewPreferences.getPreferences().map((item) => item.appList), q).map((items) => {
const params = items[1];
const viewPref: AppsListPreferences = {...items[0]};
if (params.get('proj') != null) {
viewPref.projectsFilter = params
.get('proj')
.split(',')
.filter((item) => !!item);
}
if (params.get('sync') != null) {
viewPref.syncFilter = params
.get('sync')
.split(',')
.filter((item) => !!item);
}
if (params.get('health') != null) {
viewPref.healthFilter = params
.get('health')
.split(',')
.filter((item) => !!item);
}
if (params.get('namespace') != null) {
viewPref.namespacesFilter = params
.get('namespace')
.split(',')
.filter((item) => !!item);
}
if (params.get('cluster') != null) {
viewPref.clustersFilter = params
.get('cluster')
.split(',')
.filter((item) => !!item);
}
if (params.get('view') != null) {
viewPref.view = params.get('view') as AppsListViewType;
}
if (params.get('labels') != null) {
viewPref.labelsFilter = params
.get('labels')
.split(',')
.map(decodeURIComponent)
.filter((item) => !!item);
}
return {...viewPref, page: parseInt(params.get('page') || '0', 10), search: params.get('search') || ''};
})
}>
{(pref) => children(pref)}
</DataLoader>
)}
</ObservableQuery>
);
function filterApps(applications: models.Application[], pref: AppsListPreferences, search: string) {
return applications.filter((app) =>
(search === '' || app.metadata.name.includes(search)) &&
(pref.projectsFilter.length === 0 || pref.projectsFilter.includes(app.spec.project)) &&
(pref.reposFilter.length === 0 || pref.reposFilter.includes(app.spec.source.repoURL)) &&
(pref.syncFilter.length === 0 || pref.syncFilter.includes(app.status.sync.status)) &&
(pref.healthFilter.length === 0 || pref.healthFilter.includes(app.status.health.status)) &&
(pref.namespacesFilter.length === 0 || pref.namespacesFilter.some((ns) => minimatch(app.spec.destination.namespace, ns))) &&
(pref.clustersFilter.length === 0 || pref.clustersFilter.some((server) => minimatch(app.spec.destination.server, server))),
return applications.filter(
(app) =>
(search === '' || app.metadata.name.includes(search)) &&
(pref.projectsFilter.length === 0 || pref.projectsFilter.includes(app.spec.project)) &&
(pref.reposFilter.length === 0 || pref.reposFilter.includes(app.spec.source.repoURL)) &&
(pref.syncFilter.length === 0 || pref.syncFilter.includes(app.status.sync.status)) &&
(pref.healthFilter.length === 0 || pref.healthFilter.includes(app.status.health.status)) &&
(pref.namespacesFilter.length === 0 || pref.namespacesFilter.some((ns) => minimatch(app.spec.destination.namespace, ns))) &&
(pref.clustersFilter.length === 0 || pref.clustersFilter.some((server) => minimatch(app.spec.destination.server, server))) &&
(pref.labelsFilter.length === 0 || pref.labelsFilter.every((selector) => LabelSelector.match(selector, app.metadata.labels))),
);
}
function tryJsonParse(input: string) {
try {
return input && JSON.parse(input) || null;
return (input && JSON.parse(input)) || null;
} catch {
return null;
}
@@ -120,179 +154,234 @@ function tryJsonParse(input: string) {
export const ApplicationsList = (props: RouteComponentProps<{}>) => {
const query = new URLSearchParams(props.location.search);
const appInput = tryJsonParse(query.get('new'));
const syncAppsInput = tryJsonParse(query.get('syncApps'));
const [createApi, setCreateApi] = React.useState(null);
const clusters = React.useMemo(() => services.clusters.list(), []);
return (
<ClusterCtx.Provider value={clusters}>
<Consumer>{
(ctx) => (
<Page title='Applications' toolbar={services.viewPreferences.getPreferences().map((pref) => ({
breadcrumbs: [{ title: 'Applications', path: '/applications' }],
tools: (
<React.Fragment key='app-list-tools'>
<span className='applications-list__view-type'>
<i className={classNames('fa fa-th', {selected: pref.appList.view === 'tiles'})} onClick={() => {
ctx.navigation.goto('.', { view: 'tiles'});
services.viewPreferences.updatePreferences({ appList: {...pref.appList, view: 'tiles'} });
}} />
<i className={classNames('fa fa-th-list', {selected: pref.appList.view === 'list'})} onClick={() => {
ctx.navigation.goto('.', { view: 'list'});
services.viewPreferences.updatePreferences({ appList: {...pref.appList, view: 'list'} });
}} />
<i className={classNames('fa fa-chart-pie', {selected: pref.appList.view === 'summary'})} onClick={() => {
ctx.navigation.goto('.', { view: 'summary'});
services.viewPreferences.updatePreferences({ appList: {...pref.appList, view: 'summary'} });
}} />
</span>
</React.Fragment>
),
actionMenu: {
className: 'fa fa-plus',
items: [{
title: 'New Application',
action: () => ctx.navigation.goto('.', { new: '{}' }),
}],
},
}))}>
<div className='applications-list'>
<ViewPref>
{(pref) =>
<DataLoader
input={(pref.labelsFilter || []).join(',')}
load={(selector) => loadApplications(selector)}
loadingRenderer={() => (<div className='argo-container'><MockupList height={100} marginTop={30}/></div>)}>
{(applications: models.Application[]) => (
applications.length === 0 && (pref.labelsFilter || []).length === 0 ? (
<EmptyState icon='argo-icon-application'>
<h4>No applications yet</h4>
<h5>Create new application to start managing resources in your cluster</h5>
<button className='argo-button argo-button--base' onClick={() => ctx.navigation.goto('.', { new: JSON.stringify({}) })}>Create application</button>
</EmptyState>
) : (
<div className='row'>
<div className='columns small-12 xxlarge-2'>
<Query>
{(q) => (
<div className='applications-list__search'>
<i className='fa fa-search'/>
{q.get('search') && (
<i className='fa fa-times' onClick={() => ctx.navigation.goto('.', { search: null }, { replace: true })}/>
)}
<Autocomplete
filterSuggestions={true}
renderInput={(inputProps) => (
<input {...inputProps} onFocus={(e) => {
e.target.select();
if (inputProps.onFocus) {
inputProps.onFocus(e);
}
}} className='argo-field' />
)}
renderItem={(item) => (
<React.Fragment>
<i className='icon argo-icon-application'/> {item.label}
</React.Fragment>
)}
onSelect={(val) => {
ctx.navigation.goto(`./${val}`);
<ClusterCtx.Provider value={clusters}>
<Consumer>
{(ctx) => (
<Page
title='Applications'
toolbar={services.viewPreferences.getPreferences().map((pref) => ({
breadcrumbs: [{title: 'Applications', path: '/applications'}],
tools: (
<React.Fragment key='app-list-tools'>
<span className='applications-list__view-type'>
<i
className={classNames('fa fa-th', {selected: pref.appList.view === 'tiles'})}
onClick={() => {
ctx.navigation.goto('.', {view: 'tiles'});
services.viewPreferences.updatePreferences({appList: {...pref.appList, view: 'tiles'}});
}}
onChange={(e) => ctx.navigation.goto('.', { search: e.target.value }, { replace: true })}
value={q.get('search') || ''} items={applications.map((app) => app.metadata.name)}/>
</div>
)}
</Query>
<ApplicationsFilter applications={applications} pref={pref} onChange={(newPref) => {
services.viewPreferences.updatePreferences({appList: newPref});
ctx.navigation.goto('.', {
proj: newPref.projectsFilter.join(','),
sync: newPref.syncFilter.join(','),
health: newPref.healthFilter.join(','),
namespace: newPref.namespacesFilter.join(','),
cluster: newPref.clustersFilter.join(','),
labels: newPref.labelsFilter.join(','),
});
}} />
</div>
<div className='columns small-12 xxlarge-10'>
{pref.view === 'summary' && (
<ApplicationsSummary applications={filterApps(applications, pref, pref.search)} />
) || (
<Paginate
preferencesKey='applications-list'
page={pref.page}
emptyState={() => (
<EmptyState icon='fa fa-search'>
<h4>No applications found</h4>
<h5>Try to change filter criteria</h5>
</EmptyState>
)}
data={filterApps(applications, pref, pref.search)} onPageChange={(page) => ctx.navigation.goto('.', { page })} >
{(data) => (
pref.view === 'tiles' && (
<ApplicationTiles
applications={data}
syncApplication={(appName) => ctx.navigation.goto('.', { syncApp: appName })}
refreshApplication={(appName) => services.applications.get(appName, 'normal')}
deleteApplication={(appName) => AppUtils.deleteApplication(appName, ctx)}
/>
) || (
<ApplicationsTable applications={data}
syncApplication={(appName) => ctx.navigation.goto('.', { syncApp: appName })}
refreshApplication={(appName) => services.applications.get(appName, 'normal')}
deleteApplication={(appName) => AppUtils.deleteApplication(appName, ctx)}
<i
className={classNames('fa fa-th-list', {selected: pref.appList.view === 'list'})}
onClick={() => {
ctx.navigation.goto('.', {view: 'list'});
services.viewPreferences.updatePreferences({appList: {...pref.appList, view: 'list'}});
}}
/>
)
<i
className={classNames('fa fa-chart-pie', {selected: pref.appList.view === 'summary'})}
onClick={() => {
ctx.navigation.goto('.', {view: 'summary'});
services.viewPreferences.updatePreferences({appList: {...pref.appList, view: 'summary'}});
}}
/>
</span>
</React.Fragment>
),
actionMenu: {
items: [
{
title: 'New App',
iconClassName: 'fa fa-plus',
action: () => ctx.navigation.goto('.', {new: '{}'}),
},
{
title: 'Sync Apps',
iconClassName: 'fa fa-sync',
action: () => ctx.navigation.goto('.', {syncApps: true}),
},
],
},
}))}>
<div className='applications-list'>
<ViewPref>
{(pref) => (
<DataLoader
load={() => loadApplications()}
loadingRenderer={() => (
<div className='argo-container'>
<MockupList height={100} marginTop={30} />
</div>
)}>
{(applications: models.Application[]) =>
applications.length === 0 && (pref.labelsFilter || []).length === 0 ? (
<EmptyState icon='argo-icon-application'>
<h4>No applications yet</h4>
<h5>Create new application to start managing resources in your cluster</h5>
<button className='argo-button argo-button--base' onClick={() => ctx.navigation.goto('.', {new: JSON.stringify({})})}>
Create application
</button>
</EmptyState>
) : (
<div className='row'>
<div className='columns small-12 xxlarge-2'>
<Query>
{(q) => (
<div className='applications-list__search'>
<i className='fa fa-search' />
{q.get('search') && (
<i className='fa fa-times' onClick={() => ctx.navigation.goto('.', {search: null}, {replace: true})} />
)}
<Autocomplete
filterSuggestions={true}
renderInput={(inputProps) => (
<input
{...inputProps}
onFocus={(e) => {
e.target.select();
if (inputProps.onFocus) {
inputProps.onFocus(e);
}
}}
className='argo-field'
/>
)}
renderItem={(item) => (
<React.Fragment>
<i className='icon argo-icon-application' /> {item.label}
</React.Fragment>
)}
onSelect={(val) => {
ctx.navigation.goto(`./${val}`);
}}
onChange={(e) => ctx.navigation.goto('.', {search: e.target.value}, {replace: true})}
value={q.get('search') || ''}
items={applications.map((app) => app.metadata.name)}
/>
</div>
)}
</Query>
<ApplicationsFilter
applications={applications}
pref={pref}
onChange={(newPref) => {
services.viewPreferences.updatePreferences({appList: newPref});
ctx.navigation.goto('.', {
proj: newPref.projectsFilter.join(','),
sync: newPref.syncFilter.join(','),
health: newPref.healthFilter.join(','),
namespace: newPref.namespacesFilter.join(','),
cluster: newPref.clustersFilter.join(','),
labels: newPref.labelsFilter.map(encodeURIComponent).join(','),
});
}}
/>
{syncAppsInput && (
<ApplicationsSyncPanel
key='syncsPanel'
show={syncAppsInput}
hide={() => ctx.navigation.goto('.', {syncApps: null})}
apps={filterApps(applications, pref, pref.search)}
/>
)}
</div>
<div className='columns small-12 xxlarge-10'>
{(pref.view === 'summary' && <ApplicationsSummary applications={filterApps(applications, pref, pref.search)} />) || (
<Paginate
preferencesKey='applications-list'
page={pref.page}
emptyState={() => (
<EmptyState icon='fa fa-search'>
<h4>No applications found</h4>
<h5>Try to change filter criteria</h5>
</EmptyState>
)}
data={filterApps(applications, pref, pref.search)}
onPageChange={(page) => ctx.navigation.goto('.', {page})}>
{(data) =>
(pref.view === 'tiles' && (
<ApplicationTiles
applications={data}
syncApplication={(appName) => ctx.navigation.goto('.', {syncApp: appName})}
refreshApplication={(appName) => services.applications.get(appName, 'normal')}
deleteApplication={(appName) => AppUtils.deleteApplication(appName, ctx)}
/>
)) || (
<ApplicationsTable
applications={data}
syncApplication={(appName) => ctx.navigation.goto('.', {syncApp: appName})}
refreshApplication={(appName) => services.applications.get(appName, 'normal')}
deleteApplication={(appName) => AppUtils.deleteApplication(appName, ctx)}
/>
)
}
</Paginate>
)}
</div>
</div>
)
}
</DataLoader>
)}
</Paginate>
)}
</div>
</ViewPref>
</div>
)
<ObservableQuery>
{(q) => (
<DataLoader
load={() =>
q.flatMap((params) => {
const syncApp = params.get('syncApp');
return (syncApp && Observable.fromPromise(services.applications.get(syncApp))) || Observable.from([null]);
})
}>
{(app) => (
<ApplicationSyncPanel key='syncPanel' application={app} selectedResource={'all'} hide={() => ctx.navigation.goto('.', {syncApp: null})} />
)}
</DataLoader>
)}
</ObservableQuery>
<SlidingPanel
isShown={!!appInput}
onClose={() => ctx.navigation.goto('.', {new: null})}
header={
<div>
<button className='argo-button argo-button--base' onClick={() => createApi && createApi.submitForm(null)}>
Create
</button>{' '}
<button onClick={() => ctx.navigation.goto('.', {new: null})} className='argo-button argo-button--base-o'>
Cancel
</button>
</div>
}>
{appInput && (
<ApplicationCreatePanel
getFormApi={(api) => {
setCreateApi(api);
}}
createApp={async (app) => {
try {
await services.applications.create(app);
ctx.navigation.goto('.', {new: null});
} catch (e) {
ctx.notifications.show({
content: <ErrorNotification title='Unable to create application' e={e} />,
type: NotificationType.Error,
});
}
}}
app={appInput}
onAppChanged={(app) => ctx.navigation.goto('.', {new: JSON.stringify(app)}, {replace: true})}
/>
)}
</SlidingPanel>
</Page>
)}
</DataLoader>
}</ViewPref>
</div>
<ObservableQuery>
{(q) => (
<DataLoader load={() => q.flatMap((params) => {
const syncApp = params.get('syncApp');
return syncApp && Observable.fromPromise(services.applications.get(syncApp)) || Observable.from([null]);
}) }>
{(app) => (
<ApplicationSyncPanel key='syncPanel' application={app} selectedResource={'all'} hide={() => ctx.navigation.goto('.', { syncApp: null })} />
)}
</DataLoader>
)}
</ObservableQuery>
<SlidingPanel isShown={!!appInput} onClose={() => ctx.navigation.goto('.', { new: null })} header={
<div>
<button className='argo-button argo-button--base'
onClick={() => createApi && createApi.submitForm(null)}>
Create
</button> <button onClick={() => ctx.navigation.goto('.', { new: null })} className='argo-button argo-button--base-o'>
Cancel
</button>
</div>
}>
{appInput && <ApplicationCreatePanel getFormApi={(api) => {
setCreateApi(api);
}} createApp={ async (app) => {
try {
await services.applications.create(app);
ctx.navigation.goto('.', { new: null });
} catch (e) {
ctx.notifications.show({
content: <ErrorNotification title='Unable to create application' e={e}/>,
type: NotificationType.Error,
});
}
}}
app={appInput}
onAppChanged={(app) => ctx.navigation.goto('.', { new: JSON.stringify(app) }, { replace: true })} />}
</SlidingPanel>
</Page>
)}
</Consumer>
</ClusterCtx.Provider>);
</Consumer>
</ClusterCtx.Provider>
);
};

View File

@@ -1,7 +1,5 @@
import { DropDownMenu, Tooltip } from 'argo-ui';
import {DropDownMenu} from 'argo-ui';
import * as React from 'react';
const GitUrlParse = require('git-url-parse');
import { Cluster } from '../../../shared/components';
import { Consumer } from '../../../shared/context';
import * as models from '../../../shared/models';
@@ -10,11 +8,6 @@ import * as AppUtils from '../utils';
require('./applications-table.scss');
function shortRepo(repo: string) {
const url = GitUrlParse(repo);
return <Tooltip content={repo}><span>{url.pathname.slice(1)}</span></Tooltip>;
}
export const ApplicationsTable = (props: {
applications: models.Application[];
syncApplication: (appName: string) => any;
@@ -44,7 +37,7 @@ export const ApplicationsTable = (props: {
<div className='row'>
<div className='show-for-xxlarge columns small-2'>Source:</div>
<div className='columns small-12 xxlarge-10' style={{position: 'relative'}}>
{shortRepo(app.spec.source.repoURL)}/{app.spec.source.path}
{app.spec.source.repoURL}/{app.spec.source.path || app.spec.source.chart}
<div className='applications-table__meta'>
<span>{app.spec.source.targetRevision || 'HEAD'}</span>
{Object.keys(app.metadata.labels || {}).map((label) => <span key={label}>{`${label}=${app.metadata.labels[label]}`}</span>)}

View File

@@ -71,10 +71,18 @@ export const ApplicationTiles = ({applications, syncApplication, refreshApplicat
<div className='columns small-3'>Target Revision:</div>
<div className='columns small-9'>{app.spec.source.targetRevision || 'latest'}</div>
</div>
{app.spec.source.path && (
<div className='row'>
<div className='columns small-3'>Path:</div>
<div className='columns small-9'>{app.spec.source.path}</div>
</div>
)}
{app.spec.source.chart && (
<div className='row'>
<div className='columns small-3'>Chart:</div>
<div className='columns small-9'>{app.spec.source.chart}</div>
</div>
)}
<div className='row'>
<div className='columns small-3'>Destination:</div>
<div className='columns small-9'>{app.spec.destination.server}</div>

View File

@@ -0,0 +1,109 @@
import {ErrorNotification, NotificationType, SlidingPanel} from 'argo-ui';
import * as React from 'react';
import {Checkbox, Form, FormApi} from 'react-form';
import {Consumer} from '../../../shared/context';
import * as models from '../../../shared/models';
import {services} from '../../../shared/services';
import {ComparisonStatusIcon, HealthStatusIcon} from '../utils';
export const ApplicationsSyncPanel = ({show, apps, hide}: {show: boolean; apps: models.Application[]; hide: () => void}) => {
const [form, setForm] = React.useState<FormApi>(null);
const getSelectedApps = (params: any) => apps.filter((app) => params['app/' + app.metadata.name]);
return (
<Consumer>
{(ctx) => (
<SlidingPanel
isMiddle={true}
isShown={show}
onClose={() => hide()}
header={
<div>
<button className='argo-button argo-button--base' onClick={() => form.submitForm(null)}>
Sync
</button>{' '}
<button onClick={() => hide()} className='argo-button argo-button--base-o'>
Cancel
</button>
</div>
}>
<Form
onSubmit={async (params: any) => {
const selectedApps = getSelectedApps(params);
if (selectedApps.length === 0) {
ctx.notifications.show({content: `No apps selected`, type: NotificationType.Error});
return;
}
ctx.notifications.show({
content: `Syncing ${selectedApps.length} app(s)`,
type: NotificationType.Success,
});
const syncStrategy = (params.applyOnly ? {apply: {force: params.force}} : {hook: {force: params.force}}) as models.SyncStrategy;
for (const app of selectedApps) {
await services.applications.sync(app.metadata.name, app.spec.source.targetRevision, params.prune, params.dryRun, syncStrategy, null).catch((e) => {
ctx.notifications.show({
content: <ErrorNotification title={`Unable to sync ${app.metadata.name}`} e={e} />,
type: NotificationType.Error,
});
});
}
hide();
}}
getApi={setForm}>
{(formApi) => (
<React.Fragment>
<div className='argo-form-row'>
<h4>Sync app(s)</h4>
<label>Options:</label>
<div style={{paddingLeft: '1em'}}>
<label>
<Checkbox field='prune' /> Prune
</label>
&nbsp;
<label>
<Checkbox field='dryRun' /> Dry Run
</label>
&nbsp;
<label>
<Checkbox field='applyOnly' /> Apply Only
</label>
&nbsp;
<label>
<Checkbox field='force' /> Force
</label>
</div>
<label>
Apps (<a
onClick={() => apps.forEach((app) => formApi.setValue('app/' + app.metadata.name, true))}>all</a>/
<a
onClick={() =>
apps.forEach((app) => formApi.setValue('app/' + app.metadata.name, app.status.sync.status === models.SyncStatuses.OutOfSync))
}>
out of sync
</a>
/<a
onClick={() => apps.forEach((app) => formApi.setValue('app/' + app.metadata.name, false))}>none</a>
):
</label>
<div style={{paddingLeft: '1em'}}>
{apps.map((app) => (
<label key={app.metadata.name}>
<Checkbox field={`app/${app.metadata.name}`} />
&nbsp;
{app.metadata.name}
&nbsp;
<ComparisonStatusIcon status={app.status.sync.status} />
&nbsp;
<HealthStatusIcon state={app.status.health} />
<br />
</label>
))}
</div>
</div>
</React.Fragment>
)}
</Form>
</SlidingPanel>
)}
</Consumer>
);
};

View File

@@ -0,0 +1,45 @@
import * as LabelSelector from './label-selector';
test('exists', () => {
expect(LabelSelector.match('test', {test: 'hello'})).toBeTruthy();
expect(LabelSelector.match('test1', {test: 'hello'})).toBeFalsy();
expect(LabelSelector.match('app.kubernetes.io/instance', {'app.kubernetes.io/instance': 'hello'})).toBeTruthy();
});
test('not exists', () => {
expect(LabelSelector.match('!test', {test: 'hello'})).toBeFalsy();
expect(LabelSelector.match('!test1', {test: 'hello'})).toBeTruthy();
});
test('in', () => {
expect(LabelSelector.match('test in 1, 2, 3', {test: '1'})).toBeTruthy();
expect(LabelSelector.match('test in 1, 2, 3', {test: '4'})).toBeFalsy();
expect(LabelSelector.match('test in 1, 2, 3', {test1: '1'})).toBeFalsy();
});
test('notIn', () => {
expect(LabelSelector.match('test notin 1, 2, 3', {test: '1'})).toBeFalsy();
expect(LabelSelector.match('test notin 1, 2, 3', {test: '4'})).toBeTruthy();
expect(LabelSelector.match('test notin 1, 2, 3', {test1: '1'})).toBeTruthy();
});
test('equal', () => {
expect(LabelSelector.match('test=hello', {test: 'hello'})).toBeTruthy();
expect(LabelSelector.match('test=world', {test: 'hello'})).toBeFalsy();
expect(LabelSelector.match('test==hello', {test: 'hello'})).toBeTruthy();
});
test('notEqual', () => {
expect(LabelSelector.match('test!=hello', {test: 'hello'})).toBeFalsy();
expect(LabelSelector.match('test!=world', {test: 'hello'})).toBeTruthy();
});
test('greaterThen', () => {
expect(LabelSelector.match('test gt 1', {test: '2'})).toBeTruthy();
expect(LabelSelector.match('test gt 3', {test: '2'})).toBeFalsy();
});
test('lessThen', () => {
expect(LabelSelector.match('test lt 1', {test: '2'})).toBeFalsy();
expect(LabelSelector.match('test lt 3', {test: '2'})).toBeTruthy();
});

View File

@@ -0,0 +1,39 @@
type operatorFn = (labels: {[name: string]: string}, key: string, values: string[]) => boolean;
const operators: {[type: string]: operatorFn} = {
'!=': (labels, key, values) => labels[key] !== values[0],
'==': (labels, key, values) => labels[key] === values[0],
'=': (labels, key, values) => labels[key] === values[0],
'[\\W]notin[\\W]': (labels, key, values) => !values.includes(labels[key]),
'[\\W]in[\\W]': (labels, key, values) => values.includes(labels[key]),
'[\\W]gt[\\W]': (labels, key, values) => parseFloat(labels[key]) > parseFloat(values[0]),
'[\\W]lt[\\W]': (labels, key, values) => parseFloat(labels[key]) < parseFloat(values[0]),
};
function split(input: string, delimiter: string | RegExp): string[] {
return input
.split(delimiter)
.map((part) => part.trim())
.filter((part) => part !== '');
}
export type LabelSelector = (labels: {[name: string]: string}) => boolean;
export function parse(selector: string): LabelSelector {
for (const type of Object.keys(operators)) {
const operator = operators[type];
const parts = split(selector, new RegExp(type));
if (parts.length > 1) {
const values = split(parts[1], ',');
return (labels) => operator(labels, parts[0], values);
}
}
if (selector.startsWith('!')) {
return (labels) => !labels.hasOwnProperty(selector.slice(1));
}
return (labels) => labels.hasOwnProperty(selector);
}
export function match(selector: string, labels: {[name: string]: string}): boolean {
return parse(selector)(labels || {});
}

View File

@@ -1,11 +1,17 @@
import {DataLoader, Page as ArgoPage, Toolbar, Utils} from 'argo-ui';
import { parse } from 'cookie';
import * as PropTypes from 'prop-types';
import * as React from 'react';
import {Observable} from 'rxjs';
import {BehaviorSubject, Observable} from 'rxjs';
import {AppContext} from '../context';
import {services} from '../services';
const mostRecentLoggedIn = new BehaviorSubject<boolean>(false);
function isLoggedIn(): Observable<boolean> {
services.users.get().then((info) => mostRecentLoggedIn.next(info.loggedIn));
return mostRecentLoggedIn;
}
export class Page extends React.Component<{ title: string, toolbar?: Toolbar | Observable<Toolbar> }> {
public static contextTypes = {
router: PropTypes.object,
@@ -14,20 +20,31 @@ export class Page extends React.Component<{ title: string, toolbar?: Toolbar | O
public render() {
return (
<DataLoader input={new Date()} load={() => Utils.toObservable(this.props.toolbar).map((toolbar) => {
toolbar = toolbar || {};
toolbar.tools = [
toolbar.tools,
// this is a crummy check, as the token maybe expired, but it is better than flashing user interface
parse(document.cookie)['argocd.token'] ?
<a key='logout' onClick={() => this.goToLogin(true)}>Logout</a> :
<a key='login' onClick={() => this.goToLogin(false)}>Login</a>,
];
return toolbar;
})}>
{(toolbar) => (
<ArgoPage title={this.props.title} children={this.props.children} toolbar={toolbar} />
)}
<DataLoader
input={new Date()}
load={() =>
Utils.toObservable(this.props.toolbar).map((toolbar) => {
toolbar = toolbar || {};
toolbar.tools = [
toolbar.tools,
<DataLoader key='loginPanel' load={() => isLoggedIn()}>
{(loggedIn) =>
loggedIn ? (
<a key='logout' onClick={() => this.goToLogin(true)}>
Logout
</a>
) : (
<a key='login' onClick={() => this.goToLogin(false)}>
Login
</a>
)
}
</DataLoader>,
];
return toolbar;
})
}>
{(toolbar) => <ArgoPage title={this.props.title} children={this.props.children} toolbar={toolbar} />}
</DataLoader>
);
}

View File

@@ -585,7 +585,7 @@ export interface ResourceActionParam {
export interface ResourceAction {
name: string;
params: ResourceActionParam[];
available: boolean;
disabled: boolean;
}
export interface SyncWindowsState {

View File

@@ -12,7 +12,7 @@ interface QueryOptions {
function optionsToSearch(options?: QueryOptions) {
if (options) {
return { fields: (options.exclude ? '-' : '') + options.fields.join(','), selector: options.selector };
return { fields: (options.exclude ? '-' : '') + options.fields.join(','), selector: options.selector || '' };
}
return {};
}

View File

@@ -34,7 +34,7 @@ export interface ViewPreferences {
const VIEW_PREFERENCES_KEY = 'view_preferences';
const minVer = 3;
const minVer = 4;
const DEFAULT_PREFERENCES: ViewPreferences = {
version: 1,

View File

@@ -48,7 +48,8 @@ func TestGetAppProjectWithNoProjDefined(t *testing.T) {
appClientset := appclientset.NewSimpleClientset(testProj)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
informer := v1alpha1.NewAppProjectInformer(appClientset, namespace, 0, cache.Indexers{})
indexers := cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}
informer := v1alpha1.NewAppProjectInformer(appClientset, namespace, 0, indexers)
go informer.Run(ctx.Done())
cache.WaitForCacheSync(ctx.Done(), informer.HasSynced)
proj, err := GetAppProject(&testApp.Spec, applisters.NewAppProjectLister(informer.GetIndexer()), namespace)

11
util/cache/cache.go vendored
View File

@@ -30,6 +30,11 @@ type OIDCState struct {
ReturnURL string `json:"returnURL"`
}
type ClusterInfo struct {
appv1.ConnectionState
Version string
}
// NewCache creates new instance of Cache
func NewCache(cacheClient CacheClient) *Cache {
return &Cache{client: cacheClient}
@@ -153,13 +158,13 @@ func (c *Cache) SetAppResourcesTree(appName string, resourcesTree *appv1.Applica
return c.setItem(appResourcesTreeKey(appName), resourcesTree, appStateCacheExpiration, resourcesTree == nil)
}
func (c *Cache) GetClusterConnectionState(server string) (appv1.ConnectionState, error) {
res := appv1.ConnectionState{}
func (c *Cache) GetClusterInfo(server string) (ClusterInfo, error) {
res := ClusterInfo{}
err := c.getItem(clusterConnectionStateKey(server), &res)
return res, err
}
func (c *Cache) SetClusterConnectionState(server string, state *appv1.ConnectionState) error {
func (c *Cache) SetClusterInfo(server string, state *ClusterInfo) error {
return c.setItem(clusterConnectionStateKey(server), &state, connectionStatusCacheExpiration, state == nil)
}

View File

@@ -65,7 +65,7 @@ func ignoreLiveObjectHealth(liveObj *unstructured.Unstructured, resHealth appv1.
return true
}
gvk := liveObj.GroupVersionKind()
if gvk.Group == "argoproj.io" && gvk.Kind == "Application" && resHealth.Status == appv1.HealthStatusMissing {
if gvk.Group == "argoproj.io" && gvk.Kind == "Application" && (resHealth.Status == appv1.HealthStatusMissing || resHealth.Status == appv1.HealthStatusUnknown) {
// Covers the app-of-apps corner case where child app is deployed but that app itself
// has a status of 'Missing', which we don't want to cause the parent's health status
// to also be Missing

View File

@@ -115,6 +115,7 @@ func TestAppOfAppsHealth(t *testing.T) {
}
missingApp, missingStatus := newAppLiveObj("foo", appv1.HealthStatusMissing)
unknownApp, unknownStatus := newAppLiveObj("fooz", appv1.HealthStatusUnknown)
healthyApp, healthyStatus := newAppLiveObj("bar", appv1.HealthStatusHealthy)
degradedApp, degradedStatus := newAppLiveObj("baz", appv1.HealthStatusDegraded)
@@ -127,6 +128,15 @@ func TestAppOfAppsHealth(t *testing.T) {
assert.Equal(t, appv1.HealthStatusHealthy, healthStatus.Status)
}
// verify unknown child app does not affect app health
{
unknownAndHealthyStatuses := []appv1.ResourceStatus{unknownStatus, healthyStatus}
unknownAndHealthyLiveObjects := []*unstructured.Unstructured{unknownApp, healthyApp}
healthStatus, err := SetApplicationHealth(unknownAndHealthyStatuses, unknownAndHealthyLiveObjects, nil, noFilter)
assert.NoError(t, err)
assert.Equal(t, appv1.HealthStatusHealthy, healthStatus.Status)
}
// verify degraded does affect
{
degradedAndHealthyStatuses := []appv1.ResourceStatus{degradedStatus, healthyStatus}

View File

@@ -21,6 +21,7 @@ import (
"gopkg.in/yaml.v2"
"github.com/argoproj/argo-cd/util"
"github.com/argoproj/argo-cd/util/config"
)
var (
@@ -128,22 +129,41 @@ func (c *nativeHelmChart) ExtractChart(chart string, version string) (string, ut
return "", nil, err
}
// download chart tar file into persistent helm repository directory
_, err = helmCmd.Fetch(c.repoURL, chart, version, c.creds)
// (1) because `helm fetch` downloads an arbitrary file name, we download to an empty temp directory
tempDest, err := ioutil.TempDir("", "helm")
if err != nil {
return "", nil, err
}
defer func() { _ = os.RemoveAll(tempDest) }()
_, err = helmCmd.Fetch(c.repoURL, chart, version, tempDest, c.creds)
if err != nil {
return "", nil, err
}
// (2) then we assume that the only file downloaded into the directory is the tgz file
// and we move that to where we want it
infos, err := ioutil.ReadDir(tempDest)
if err != nil {
return "", nil, err
}
if len(infos) != 1 {
return "", nil, fmt.Errorf("expected 1 file, found %v", len(infos))
}
err = os.Rename(filepath.Join(tempDest, infos[0].Name()), chartPath)
if err != nil {
return "", nil, err
}
}
// untar helm chart into throw away temp directory which should be deleted as soon as no longer needed
tempDir, err := ioutil.TempDir("", "")
tempDir, err := ioutil.TempDir("", "helm")
if err != nil {
return "", nil, err
}
cmd := exec.Command("tar", "-zxvf", chartPath)
cmd.Dir = tempDir
_, err = argoexec.RunCommandExt(cmd, argoexec.CmdOpts{})
_, err = argoexec.RunCommandExt(cmd, config.CmdOpts())
if err != nil {
_ = os.RemoveAll(tempDir)
return "", nil, err
}
return path.Join(tempDir, chart), util.NewCloser(func() error {
return os.RemoveAll(tempDir)

View File

@@ -1,27 +1,43 @@
package helm
import (
"os"
"testing"
"github.com/stretchr/testify/assert"
"github.com/argoproj/argo-cd/util"
)
func TestIndex(t *testing.T) {
t.Run("Invalid", func(t *testing.T) {
_, err := NewClient("", Creds{}).GetIndex()
client := NewClient("", Creds{})
_, err := client.GetIndex()
assert.Error(t, err)
})
t.Run("Stable", func(t *testing.T) {
index, err := NewClient("https://kubernetes-charts.storage.googleapis.com", Creds{}).GetIndex()
client := NewClient("https://argoproj.github.io/argo-helm", Creds{})
index, err := client.GetIndex()
assert.NoError(t, err)
assert.NotNil(t, index)
})
t.Run("BasicAuth", func(t *testing.T) {
index, err := NewClient("https://kubernetes-charts.storage.googleapis.com", Creds{
client := NewClient("https://argoproj.github.io/argo-helm", Creds{
Username: "my-password",
Password: "my-username",
}).GetIndex()
})
index, err := client.GetIndex()
assert.NoError(t, err)
assert.NotNil(t, index)
})
}
func Test_nativeHelmChart_ExtractChart(t *testing.T) {
client := NewClient("https://argoproj.github.io/argo-helm", Creds{})
path, closer, err := client.ExtractChart("argo-cd", "0.7.1")
defer util.Close(closer)
assert.NoError(t, err)
info, err := os.Stat(path)
assert.NoError(t, err)
assert.True(t, info.IsDir())
}

View File

@@ -6,13 +6,12 @@ import (
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"regexp"
argoexec "github.com/argoproj/pkg/exec"
"github.com/argoproj/argo-cd/util"
"github.com/argoproj/argo-cd/util/security"
"github.com/argoproj/argo-cd/util/config"
)
// A thin wrapper around the "helm" command, adding logging and error translation.
@@ -39,6 +38,7 @@ func (c Cmd) run(args ...string) (string, error) {
cmd.Env = os.Environ()
cmd.Env = append(cmd.Env, fmt.Sprintf("HELM_HOME=%s", c.helmHome))
return argoexec.RunCommandExt(cmd, argoexec.CmdOpts{
Timeout: config.CmdOpts().Timeout,
Redactor: redactor,
})
}
@@ -117,31 +117,31 @@ func writeToTmp(data []byte) (string, io.Closer, error) {
}), nil
}
func (c *Cmd) Fetch(repo, chartName string, version string, opts Creds) (string, error) {
args := []string{"fetch"}
func (c *Cmd) Fetch(repo, chartName, version, destination string, creds Creds) (string, error) {
args := []string{"fetch", "--destination", destination}
if version != "" {
args = append(args, "--version", version)
}
if opts.Username != "" {
args = append(args, "--username", opts.Username)
if creds.Username != "" {
args = append(args, "--username", creds.Username)
}
if opts.Password != "" {
args = append(args, "--password", opts.Password)
if creds.Password != "" {
args = append(args, "--password", creds.Password)
}
if opts.CAPath != "" {
args = append(args, "--ca-file", opts.CAPath)
if creds.CAPath != "" {
args = append(args, "--ca-file", creds.CAPath)
}
if len(opts.CertData) > 0 {
filePath, closer, err := writeToTmp(opts.CertData)
if len(creds.CertData) > 0 {
filePath, closer, err := writeToTmp(creds.CertData)
if err != nil {
return "", err
}
defer util.Close(closer)
args = append(args, "--cert-file", filePath)
}
if len(opts.KeyData) > 0 {
filePath, closer, err := writeToTmp(opts.KeyData)
if len(creds.KeyData) > 0 {
filePath, closer, err := writeToTmp(creds.KeyData)
if err != nil {
return "", err
}
@@ -194,18 +194,7 @@ func (c *Cmd) template(chart string, opts *TemplateOpts) (string, error) {
args = append(args, "--set-string", key+"="+cleanSetParameters(val))
}
for _, val := range opts.Values {
absWorkDir, err := filepath.Abs(c.WorkDir)
if err != nil {
return "", err
}
if !filepath.IsAbs(val) {
val = filepath.Join(absWorkDir, val)
}
cleanVal, err := security.EnforceToCurrentRoot(absWorkDir, val)
if err != nil {
return "", err
}
args = append(args, "--values", cleanVal)
args = append(args, "--values", val)
}
return c.run(args...)

View File

@@ -21,27 +21,3 @@ func TestCmd_template_kubeVersion(t *testing.T) {
assert.NoError(t, err)
assert.NotEmpty(t, s)
}
func TestCmd_template_PathTraversal(t *testing.T) {
cmd, err := NewCmd("./testdata/redis")
assert.NoError(t, err)
s, err := cmd.template(".", &TemplateOpts{
KubeVersion: "1.14",
Values: []string{"values.yaml"},
})
assert.NoError(t, err)
assert.NotEmpty(t, s)
_, err = cmd.template(".", &TemplateOpts{
KubeVersion: "1.14",
Values: []string{"../minio/values.yaml"},
})
assert.Error(t, err)
s, err = cmd.template(".", &TemplateOpts{
KubeVersion: "1.14",
Values: []string{"../minio/../redis/values.yaml"},
})
assert.NoError(t, err)
assert.NotEmpty(t, s)
}

View File

@@ -10,9 +10,8 @@ import (
"regexp"
"strings"
"github.com/ghodss/yaml"
argoexec "github.com/argoproj/pkg/exec"
"github.com/ghodss/yaml"
"github.com/argoproj/argo-cd/util/config"
)
@@ -89,6 +88,7 @@ func (h *helm) Dispose() {
func Version() (string, error) {
cmd := exec.Command("helm", "version", "--client")
out, err := argoexec.RunCommandExt(cmd, argoexec.CmdOpts{
Timeout: config.CmdOpts().Timeout,
Redactor: redactor,
})
if err != nil {
@@ -138,12 +138,17 @@ func (h *helm) GetParameters(valuesFiles []string) (map[string]string, error) {
return output, nil
}
func flatVals(input map[string]interface{}, output map[string]string, prefixes ...string) {
for key, val := range input {
if subMap, ok := val.(map[string]interface{}); ok {
flatVals(subMap, output, append(prefixes, fmt.Sprintf("%v", key))...)
} else {
output[strings.Join(append(prefixes, fmt.Sprintf("%v", key)), ".")] = fmt.Sprintf("%v", val)
func flatVals(input interface{}, output map[string]string, prefixes ...string) {
switch i := input.(type) {
case map[string]interface{}:
for k, v := range i {
flatVals(v, output, append(prefixes, k)...)
}
case []interface{}:
for j, v := range i {
flatVals(v, output, append(prefixes[0:len(prefixes)-1], fmt.Sprintf("%s[%v]", prefixes[len(prefixes)-1], j))...)
}
default:
output[strings.Join(prefixes, ".")] = fmt.Sprintf("%v", i)
}
}

View File

@@ -164,3 +164,27 @@ func TestVersion(t *testing.T) {
re := regexp.MustCompile(SemverRegexValidation)
assert.True(t, re.MatchString(ver))
}
func Test_flatVals(t *testing.T) {
t.Run("Map", func(t *testing.T) {
output := map[string]string{}
flatVals(map[string]interface{}{"foo": map[string]interface{}{"bar": "baz"}}, output)
assert.Equal(t, map[string]string{"foo.bar": "baz"}, output)
})
t.Run("Array", func(t *testing.T) {
output := map[string]string{}
flatVals(map[string]interface{}{"foo": []interface{}{"bar"}}, output)
assert.Equal(t, map[string]string{"foo[0]": "bar"}, output)
})
t.Run("Val", func(t *testing.T) {
output := map[string]string{}
flatVals(map[string]interface{}{"foo": 1}, output)
assert.Equal(t, map[string]string{"foo": "1"}, output)
})
}

Some files were not shown because too many files have changed in this diff Show More