Compare commits

...

30 Commits

Author SHA1 Message Date
Alex Collins
89be1c9ce6 fixes tests 2019-12-10 14:38:52 -08:00
Alex Collins
d16f29f0ac Update manifests to v1.3.6 2019-12-10 14:08:42 -08:00
Simon Behar
5c1f581584 Add support for hidden directories with directory enforcer (#2821)
* Add support for hidden directories with directory enforcer

* Refactor

* Lint

* Rework done, still needs tests

* WIP

* Should be done

* Fix test

* Helm Charts
2019-12-10 14:07:40 -08:00
Alex Collins
3e150df0ed Update manifests to v1.3.5 2019-12-09 12:07:51 -08:00
Alex Collins
ce8d5a4533 Update manifests to v1.3.5 2019-12-09 11:30:03 -08:00
Alex Collins
c93ce9082e Update manifests to v1.3.6 2019-12-09 11:25:48 -08:00
Alex Collins
68e1a5fbeb Make ConvertToVersion maybe 1090% faster on average (#2820) 2019-12-09 10:58:43 -08:00
Alex Collins
6f38cf3133 Update manifests to v1.3.4 2019-12-05 15:23:12 -08:00
Alex Collins
2be56f232a Fixes logging of tracing option in CLI (#2819) 2019-12-05 15:22:26 -08:00
Alex Collins
bb5d2f4197 Adds tracing to key external invocations. (#2811) 2019-12-05 14:29:08 -08:00
Alexander Matyushentsev
fff5355314 argocd-util should allow editing project policies in bulk (#2615)
* Implement 'argocd-util projects update-role-policy' command which allows to update multiple project policies
2019-12-05 13:54:18 -08:00
Alexander Matyushentsev
f44ce07664 Update manifests to v1.3.3 2019-12-05 12:01:15 -08:00
Alexander Matyushentsev
116440690b Issue #2721 Optimize helm repo querying (#2816) 2019-12-05 11:57:15 -08:00
Alexander Matyushentsev
7cef1313c7 Issue #2721 - cache parsed repositories, repo credentials to avoid unnecessary yaml parsing (#2809) 2019-12-04 15:55:50 -08:00
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
84 changed files with 1712 additions and 619 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

94
Gopkg.lock generated
View File

@@ -135,6 +135,17 @@
revision = "06ea1031745cb8b3dab3f6a236daf2b0aa468b7e"
version = "v3.2.0"
[[projects]]
digest = "1:c05f1899f086e3b4613d94d9e6f7ba6f4b6587498a1aa6037c5c294b22f5a743"
name = "github.com/docker/distribution"
packages = [
"digestset",
"reference",
]
pruneopts = ""
revision = "2461543d988979529609e8cb6fca9ca190dc48da"
version = "v2.7.1"
[[projects]]
digest = "1:b021ef379356343bdc13ec101e546b756fcef4b1186d08163bef7d3bc8c1e07f"
name = "github.com/docker/docker"
@@ -651,6 +662,14 @@
revision = "4b7aa43c6742a2c18fdef89dd197aaae7dac7ccd"
version = "1.0.1"
[[projects]]
digest = "1:5d9b668b0b4581a978f07e7d2e3314af18eb27b3fb5d19b70185b7c575723d11"
name = "github.com/opencontainers/go-digest"
packages = ["."]
pruneopts = ""
revision = "279bed98673dd5bef374d3b6e4b09e2af76183bf"
version = "v1.0.0-rc1"
[[projects]]
digest = "1:4c0404dc03d974acd5fcd8b8d3ce687b13bd169db032b89275e8b9d77b98ce8c"
name = "github.com/patrickmn/go-cache"
@@ -1548,15 +1567,74 @@
digest = "1:78aa6079e011ece0d28513c7fe1bd64284fa9eb5d671760803a839ffdf0e9e38"
name = "k8s.io/kubernetes"
packages = [
"pkg/api/legacyscheme",
"pkg/api/v1/pod",
"pkg/apis/apps",
"pkg/apis/apps/install",
"pkg/apis/apps/v1",
"pkg/apis/apps/v1beta1",
"pkg/apis/apps/v1beta2",
"pkg/apis/authentication",
"pkg/apis/authentication/install",
"pkg/apis/authentication/v1",
"pkg/apis/authentication/v1beta1",
"pkg/apis/authorization",
"pkg/apis/authorization/install",
"pkg/apis/authorization/v1",
"pkg/apis/authorization/v1beta1",
"pkg/apis/autoscaling",
"pkg/apis/autoscaling/install",
"pkg/apis/autoscaling/v1",
"pkg/apis/autoscaling/v2beta1",
"pkg/apis/autoscaling/v2beta2",
"pkg/apis/batch",
"pkg/apis/batch/install",
"pkg/apis/batch/v1",
"pkg/apis/batch/v1beta1",
"pkg/apis/batch/v2alpha1",
"pkg/apis/certificates",
"pkg/apis/certificates/install",
"pkg/apis/certificates/v1beta1",
"pkg/apis/coordination",
"pkg/apis/coordination/install",
"pkg/apis/coordination/v1",
"pkg/apis/coordination/v1beta1",
"pkg/apis/core",
"pkg/apis/core/install",
"pkg/apis/core/v1",
"pkg/apis/events",
"pkg/apis/events/install",
"pkg/apis/events/v1beta1",
"pkg/apis/extensions",
"pkg/apis/extensions/install",
"pkg/apis/extensions/v1beta1",
"pkg/apis/networking",
"pkg/apis/policy",
"pkg/apis/policy/install",
"pkg/apis/policy/v1beta1",
"pkg/apis/rbac",
"pkg/apis/rbac/install",
"pkg/apis/rbac/v1",
"pkg/apis/rbac/v1alpha1",
"pkg/apis/rbac/v1beta1",
"pkg/apis/scheduling",
"pkg/apis/scheduling/install",
"pkg/apis/scheduling/v1",
"pkg/apis/scheduling/v1alpha1",
"pkg/apis/scheduling/v1beta1",
"pkg/apis/settings",
"pkg/apis/settings/install",
"pkg/apis/settings/v1alpha1",
"pkg/apis/storage",
"pkg/apis/storage/install",
"pkg/apis/storage/v1",
"pkg/apis/storage/v1alpha1",
"pkg/apis/storage/v1beta1",
"pkg/kubectl/scheme",
"pkg/kubectl/util/term",
"pkg/util/interrupt",
"pkg/util/node",
"pkg/util/parsers",
]
pruneopts = ""
revision = "2d20b5759406ded89f8b25cf085ff4733b144ba5"
@@ -1733,10 +1811,26 @@
"k8s.io/kube-aggregator/pkg/apis/apiregistration/v1beta1",
"k8s.io/kube-openapi/cmd/openapi-gen",
"k8s.io/kube-openapi/pkg/common",
"k8s.io/kubernetes/pkg/api/legacyscheme",
"k8s.io/kubernetes/pkg/api/v1/pod",
"k8s.io/kubernetes/pkg/apis/apps",
"k8s.io/kubernetes/pkg/apis/apps/install",
"k8s.io/kubernetes/pkg/apis/authentication/install",
"k8s.io/kubernetes/pkg/apis/authorization/install",
"k8s.io/kubernetes/pkg/apis/autoscaling/install",
"k8s.io/kubernetes/pkg/apis/batch",
"k8s.io/kubernetes/pkg/apis/batch/install",
"k8s.io/kubernetes/pkg/apis/certificates/install",
"k8s.io/kubernetes/pkg/apis/coordination/install",
"k8s.io/kubernetes/pkg/apis/core",
"k8s.io/kubernetes/pkg/apis/core/install",
"k8s.io/kubernetes/pkg/apis/events/install",
"k8s.io/kubernetes/pkg/apis/extensions/install",
"k8s.io/kubernetes/pkg/apis/policy/install",
"k8s.io/kubernetes/pkg/apis/rbac/install",
"k8s.io/kubernetes/pkg/apis/scheduling/install",
"k8s.io/kubernetes/pkg/apis/settings/install",
"k8s.io/kubernetes/pkg/apis/storage/install",
"k8s.io/kubernetes/pkg/kubectl/scheme",
"k8s.io/kubernetes/pkg/kubectl/util/term",
"k8s.io/kubernetes/pkg/util/node",

View File

@@ -1 +1 @@
1.3.0
1.3.6

View File

@@ -74,6 +74,7 @@ func NewCommand() *cobra.Command {
command.AddCommand(NewImportCommand())
command.AddCommand(NewExportCommand())
command.AddCommand(NewClusterConfig())
command.AddCommand(NewProjectsCommand())
command.Flags().StringVar(&logLevel, "loglevel", "info", "Set the logging level. One of: debug|info|warn|error")
return command
@@ -220,6 +221,7 @@ func NewImportCommand() *cobra.Command {
os.Exit(1)
}
config, err := clientConfig.ClientConfig()
errors.CheckError(err)
config.QPS = 100
config.Burst = 50
errors.CheckError(err)

192
cmd/argocd-util/projects.go Normal file
View File

@@ -0,0 +1,192 @@
package main
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/argoproj/argo-cd/errors"
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
appclientset "github.com/argoproj/argo-cd/pkg/client/clientset/versioned"
appclient "github.com/argoproj/argo-cd/pkg/client/clientset/versioned/typed/application/v1alpha1"
"github.com/argoproj/argo-cd/util/cli"
"github.com/argoproj/argo-cd/util/diff"
"github.com/argoproj/argo-cd/util/kube"
"github.com/spf13/cobra"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/tools/clientcmd"
)
func NewProjectsCommand() *cobra.Command {
var command = &cobra.Command{
Use: "projects",
Run: func(c *cobra.Command, args []string) {
c.HelpFunc()(c, args)
},
}
command.AddCommand(NewUpdatePolicyRuleCommand())
return command
}
func globMatch(pattern string, val string) bool {
if pattern == "*" {
return true
}
if ok, err := filepath.Match(pattern, val); ok && err == nil {
return true
}
return false
}
func getModification(modification string, resource string, scope string, permission string) (func(string, string) string, error) {
switch modification {
case "set":
if scope == "" {
return nil, fmt.Errorf("Flag --group cannot be empty if permission should be set in role")
}
if permission == "" {
return nil, fmt.Errorf("Flag --permission cannot be empty if permission should be set in role")
}
return func(proj string, action string) string {
return fmt.Sprintf("%s, %s, %s/%s, %s", resource, action, proj, scope, permission)
}, nil
case "remove":
return func(proj string, action string) string {
return ""
}, nil
}
return nil, fmt.Errorf("modification %s is not supported", modification)
}
func saveProject(updated v1alpha1.AppProject, orig v1alpha1.AppProject, projectsIf appclient.AppProjectInterface, dryRun bool) error {
fmt.Printf("===== %s ======\n", updated.Name)
target, err := kube.ToUnstructured(&updated)
errors.CheckError(err)
live, err := kube.ToUnstructured(&orig)
if err != nil {
return err
}
_ = diff.PrintDiff(updated.Name, target, live)
if !dryRun {
_, err = projectsIf.Update(&updated)
if err != nil {
return err
}
}
return nil
}
func formatPolicy(proj string, role string, permission string) string {
return fmt.Sprintf("p, proj:%s:%s, %s", proj, role, permission)
}
func split(input string, delimiter string) []string {
parts := strings.Split(input, delimiter)
for i := range parts {
parts[i] = strings.TrimSpace(parts[i])
}
return parts
}
func NewUpdatePolicyRuleCommand() *cobra.Command {
var (
clientConfig clientcmd.ClientConfig
resource string
scope string
rolePattern string
permission string
dryRun bool
)
var command = &cobra.Command{
Use: "update-role-policy PROJECT_GLOB MODIFICATION ACTION",
Short: "Implement bulk project role update. Useful to back-fill existing project policies or remove obsolete actions.",
Example: ` # Add policy that allows executing any action (action/*) to roles which name matches to *deployer* in all projects
argocd-util projects update-role-policy '*' set 'action/*' --role '*deployer*' --resource applications --scope '*' --permission allow
# Remove policy that which manages running (action/*) from all roles which name matches *deployer* in all projects
argocd-util projects update-role-policy '*' remove override --role '*deployer*'
`,
Run: func(c *cobra.Command, args []string) {
if len(args) != 3 {
c.HelpFunc()(c, args)
os.Exit(1)
}
projectGlob := args[0]
modificationType := args[1]
action := args[2]
config, err := clientConfig.ClientConfig()
errors.CheckError(err)
config.QPS = 100
config.Burst = 50
namespace, _, err := clientConfig.Namespace()
errors.CheckError(err)
appclients := appclientset.NewForConfigOrDie(config)
modification, err := getModification(modificationType, resource, scope, permission)
errors.CheckError(err)
projIf := appclients.ArgoprojV1alpha1().AppProjects(namespace)
err = updateProjects(projIf, projectGlob, rolePattern, action, modification, dryRun)
errors.CheckError(err)
},
}
command.Flags().StringVar(&resource, "resource", "", "Resource e.g. 'applications'")
command.Flags().StringVar(&scope, "scope", "", "Resource scope e.g. '*'")
command.Flags().StringVar(&rolePattern, "role", "*", "Role name pattern e.g. '*deployer*'")
command.Flags().StringVar(&permission, "permission", "", "Action permission")
command.Flags().BoolVar(&dryRun, "dry-run", true, "Dry run")
clientConfig = cli.AddKubectlFlagsToCmd(command)
return command
}
func updateProjects(projIf appclient.AppProjectInterface, projectGlob string, rolePattern string, action string, modification func(string, string) string, dryRun bool) error {
projects, err := projIf.List(v1.ListOptions{})
if err != nil {
return err
}
for _, proj := range projects.Items {
if !globMatch(projectGlob, proj.Name) {
continue
}
origProj := proj.DeepCopy()
updated := false
for i, role := range proj.Spec.Roles {
if !globMatch(rolePattern, role.Name) {
continue
}
actionPolicyIndex := -1
for i := range role.Policies {
parts := split(role.Policies[i], ",")
if len(parts) != 6 || parts[3] != action {
continue
}
actionPolicyIndex = i
break
}
policyPermission := modification(proj.Name, action)
if actionPolicyIndex == -1 && policyPermission != "" {
updated = true
role.Policies = append(role.Policies, formatPolicy(proj.Name, role.Name, policyPermission))
} else if actionPolicyIndex > -1 && policyPermission == "" {
updated = true
role.Policies = append(role.Policies[:actionPolicyIndex], role.Policies[actionPolicyIndex+1:]...)
} else if actionPolicyIndex > -1 && policyPermission != "" {
updated = true
role.Policies[actionPolicyIndex] = formatPolicy(proj.Name, role.Name, policyPermission)
}
proj.Spec.Roles[i] = role
}
if updated {
err = saveProject(proj, *origProj, projIf, dryRun)
if err != nil {
return err
}
}
}
return nil
}

View File

@@ -0,0 +1,78 @@
package main
import (
"testing"
"github.com/stretchr/testify/assert"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/pkg/client/clientset/versioned/fake"
)
const (
namespace = "default"
)
func newProj(name string, roleNames ...string) *v1alpha1.AppProject {
var roles []v1alpha1.ProjectRole
for i := range roleNames {
roles = append(roles, v1alpha1.ProjectRole{Name: roleNames[i]})
}
return &v1alpha1.AppProject{ObjectMeta: v1.ObjectMeta{
Name: name,
Namespace: namespace,
}, Spec: v1alpha1.AppProjectSpec{
Roles: roles,
}}
}
func TestUpdateProjects_FindMatchingProject(t *testing.T) {
clientset := fake.NewSimpleClientset(newProj("foo", "test"), newProj("bar", "test"))
modification, err := getModification("set", "*", "*", "allow")
assert.NoError(t, err)
err = updateProjects(clientset.ArgoprojV1alpha1().AppProjects(namespace), "ba*", "*", "set", modification, false)
assert.NoError(t, err)
fooProj, err := clientset.ArgoprojV1alpha1().AppProjects(namespace).Get("foo", v1.GetOptions{})
assert.NoError(t, err)
assert.Len(t, fooProj.Spec.Roles[0].Policies, 0)
barProj, err := clientset.ArgoprojV1alpha1().AppProjects(namespace).Get("bar", v1.GetOptions{})
assert.NoError(t, err)
assert.EqualValues(t, barProj.Spec.Roles[0].Policies, []string{"p, proj:bar:test, *, set, bar/*, allow"})
}
func TestUpdateProjects_FindMatchingRole(t *testing.T) {
clientset := fake.NewSimpleClientset(newProj("proj", "foo", "bar"))
modification, err := getModification("set", "*", "*", "allow")
assert.NoError(t, err)
err = updateProjects(clientset.ArgoprojV1alpha1().AppProjects(namespace), "*", "fo*", "set", modification, false)
assert.NoError(t, err)
proj, err := clientset.ArgoprojV1alpha1().AppProjects(namespace).Get("proj", v1.GetOptions{})
assert.NoError(t, err)
assert.EqualValues(t, proj.Spec.Roles[0].Policies, []string{"p, proj:proj:foo, *, set, proj/*, allow"})
assert.Len(t, proj.Spec.Roles[1].Policies, 0)
}
func TestGetModification_SetPolicy(t *testing.T) {
modification, err := getModification("set", "*", "*", "allow")
assert.NoError(t, err)
policy := modification("proj", "myaction")
assert.Equal(t, "*, myaction, proj/*, allow", policy)
}
func TestGetModification_RemovePolicy(t *testing.T) {
modification, err := getModification("remove", "*", "*", "allow")
assert.NoError(t, err)
policy := modification("proj", "myaction")
assert.Equal(t, "", policy)
}
func TestGetModification_NotSupported(t *testing.T) {
_, err := getModification("bar", "*", "*", "allow")
assert.Errorf(t, err, "modification bar is not supported")
}

View File

@@ -784,7 +784,7 @@ func getLocalObjects(app *argoappv1.Application, local, appLabelKey, kubeVersion
}
func getLocalObjectsString(app *argoappv1.Application, local, appLabelKey, kubeVersion string, kustomizeOptions *argoappv1.KustomizeOptions) []string {
res, err := repository.GenerateManifests(local, &repoapiclient.ManifestRequest{
res, err := repository.GenerateManifests(local, "/", &repoapiclient.ManifestRequest{
ApplicationSource: &app.Spec.Source,
AppLabelKey: appLabelKey,
AppLabelValue: app.Name,
@@ -1297,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{

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()

View File

@@ -107,6 +107,7 @@ func newFakeController(data *fakeData) *ApplicationController {
ctrl.stateCache = &mockStateCache
mockStateCache.On("IsNamespaced", mock.Anything, mock.Anything).Return(true, nil)
mockStateCache.On("GetManagedLiveObjs", mock.Anything, mock.Anything).Return(data.managedLiveObjs, nil)
mockStateCache.On("GetServerVersion", mock.Anything).Return("v1.2.3", nil)
response := make(map[kube.ResourceKey]argoappv1.ResourceNode)
for k, v := range data.namespacedResources {
response[k] = v.ResourceNode

View File

@@ -27,6 +27,9 @@ type cacheSettings struct {
}
type LiveStateCache interface {
// Returns k8s server version
GetServerVersion(serverURL string) (string, error)
// Returns true of given group kind is a namespaced resource
IsNamespaced(server string, gk schema.GroupKind) (bool, error)
// Executes give callback against resource specified by the key and all its children
IterateHierarchy(server string, key kube.ResourceKey, action func(child appv1.ResourceNode, appName string)) error
@@ -187,6 +190,13 @@ func (c *liveStateCache) GetManagedLiveObjs(a *appv1.Application, targetObjs []*
}
return clusterInfo.getManagedLiveObjs(a, targetObjs, c.metricsServer)
}
func (c *liveStateCache) GetServerVersion(serverURL string) (string, error) {
clusterInfo, err := c.getSyncedCluster(serverURL)
if err != nil {
return "", err
}
return clusterInfo.serverVersion, nil
}
func isClusterHasApps(apps []interface{}, cluster *appv1.Cluster) bool {
for _, obj := range apps {

27
controller/cache/cache_test.go vendored Normal file
View File

@@ -0,0 +1,27 @@
package cache
import (
"sync"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestGetServerVersion(t *testing.T) {
now := time.Now()
cache := &liveStateCache{
lock: &sync.Mutex{},
clusters: map[string]*clusterInfo{
"http://localhost": {
syncTime: &now,
syncLock: &sync.Mutex{},
lock: &sync.Mutex{},
serverVersion: "123",
},
}}
version, err := cache.GetServerVersion("http://localhost")
assert.NoError(t, err)
assert.Equal(t, "123", version)
}

View File

@@ -39,10 +39,11 @@ type apiMeta struct {
}
type clusterInfo struct {
syncLock *sync.Mutex
syncTime *time.Time
syncError error
apisMeta map[schema.GroupKind]*apiMeta
syncLock *sync.Mutex
syncTime *time.Time
syncError error
apisMeta map[schema.GroupKind]*apiMeta
serverVersion string
lock *sync.Mutex
nodes map[kube.ResourceKey]*node
@@ -309,8 +310,13 @@ func (c *clusterInfo) sync() (err error) {
}
c.apisMeta = make(map[schema.GroupKind]*apiMeta)
c.nodes = make(map[kube.ResourceKey]*node)
apis, err := c.kubectl.GetAPIResources(c.cluster.RESTConfig(), c.cacheSettingsSrc().ResourcesFilter)
config := c.cluster.RESTConfig()
version, err := c.kubectl.GetServerVersion(config)
if err != nil {
return err
}
c.serverVersion = version
apis, err := c.kubectl.GetAPIResources(config, c.cacheSettingsSrc().ResourcesFilter)
if err != nil {
return err
}

View File

@@ -5,9 +5,8 @@ package mocks
import (
context "context"
mock "github.com/stretchr/testify/mock"
kube "github.com/argoproj/argo-cd/util/kube"
mock "github.com/stretchr/testify/mock"
schema "k8s.io/apimachinery/pkg/runtime/schema"
@@ -67,6 +66,27 @@ func (_m *LiveStateCache) GetNamespaceTopLevelResources(server string, namespace
return r0, r1
}
// GetServerVersion provides a mock function with given fields: server
func (_m *LiveStateCache) GetServerVersion(server string) (string, error) {
ret := _m.Called(server)
var r0 string
if rf, ok := ret.Get(0).(func(string) string); ok {
r0 = rf(server)
} else {
r0 = ret.Get(0).(string)
}
var r1 error
if rf, ok := ret.Get(1).(func(string) error); ok {
r1 = rf(server)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Invalidate provides a mock function with given fields:
func (_m *LiveStateCache) Invalidate() {
_m.Called()

View File

@@ -128,11 +128,7 @@ func (m *appStateManager) getRepoObjs(app *v1alpha1.Application, source v1alpha1
if err != nil {
return nil, nil, nil, err
}
cluster, err := m.db.GetCluster(context.Background(), app.Spec.Destination.Server)
if err != nil {
return nil, nil, nil, err
}
cluster.ServerVersion, err = m.kubectl.GetServerVersion(cluster.RESTConfig())
serverVersion, err := m.liveStateCache.GetServerVersion(app.Spec.Destination.Server)
if err != nil {
return nil, nil, nil, err
}
@@ -149,7 +145,7 @@ func (m *appStateManager) getRepoObjs(app *v1alpha1.Application, source v1alpha1
KustomizeOptions: &appv1.KustomizeOptions{
BuildOptions: buildOptions,
},
KubeVersion: cluster.ServerVersion,
KubeVersion: serverVersion,
})
if err != nil {
return nil, nil, nil, err

View File

@@ -45,7 +45,7 @@ func TestCompareAppStateMissing(t *testing.T) {
data := fakeData{
apps: []runtime.Object{app},
manifestResponse: &apiclient.ManifestResponse{
Manifests: []string{string(test.PodManifest)},
Manifests: []string{test.PodManifest},
Namespace: test.FakeDestNamespace,
Server: test.FakeClusterURL,
Revision: "abc123",

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

@@ -1,4 +1,4 @@
#!/bin/bash
set -eux -o pipefail
"$(dirname $0)/../install.sh" helm-linux jq-linux kustomize-linux protoc-linux swagger-linux
KUSTOMIZE_VERSION=2.0.3 "$(dirname $0)/../install.sh" helm-linux jq-linux kustomize-linux protoc-linux swagger-linux

View File

@@ -1,7 +1,6 @@
#!/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.2.1}
DL=$DOWNLOADS/kustomize-${KUSTOMIZE_VERSION}
@@ -11,7 +10,16 @@ DL=$DOWNLOADS/kustomize-${KUSTOMIZE_VERSION}
# 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
[ -e $DL ] || curl -sLf --retry 3 -o $DL https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize/v${KUSTOMIZE_VERSION}/kustomize_kustomize.v${KUSTOMIZE_VERSION}_linux_amd64
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

@@ -1,18 +1,18 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
images:
- name: argoproj/argocd
newName: argoproj/argocd
newTag: v1.3.0
- name: argoproj/argocd-ui
newName: argoproj/argocd-ui
newTag: v1.3.0
resources:
bases:
- ./application-controller
- ./dex
- ./repo-server
- ./server
- ./config
- ./redis
images:
- name: argoproj/argocd
newName: argoproj/argocd
newTag: v1.3.6
- name: argoproj/argocd-ui
newName: argoproj/argocd-ui
newTag: v1.3.6

View File

@@ -7,18 +7,18 @@ patchesStrategicMerge:
- overlays/argocd-server-deployment.yaml
- overlays/argocd-application-controller-deployment.yaml
images:
- name: argoproj/argocd
newName: argoproj/argocd
newTag: v1.3.0
- name: argoproj/argocd-ui
newName: argoproj/argocd-ui
newTag: v1.3.0
resources:
bases:
- ../../base/application-controller
- ../../base/dex
- ../../base/repo-server
- ../../base/server
- ../../base/config
- ./redis-ha
images:
- name: argoproj/argocd
newName: argoproj/argocd
newTag: v1.3.6
- name: argoproj/argocd-ui
newName: argoproj/argocd-ui
newTag: v1.3.6

View File

@@ -2789,30 +2789,6 @@ spec:
---
apiVersion: v1
kind: Service
metadata:
annotations: null
labels:
app.kubernetes.io/component: redis
app.kubernetes.io/name: argocd-redis-ha
app.kubernetes.io/part-of: argocd
name: argocd-redis-ha
spec:
clusterIP: None
ports:
- name: server
port: 6379
protocol: TCP
targetPort: redis
- name: sentinel
port: 26379
protocol: TCP
targetPort: sentinel
selector:
app.kubernetes.io/name: argocd-redis-ha
type: ClusterIP
---
apiVersion: v1
kind: Service
metadata:
annotations:
service.alpha.kubernetes.io/tolerate-unready-endpoints: "true"
@@ -2891,6 +2867,30 @@ spec:
---
apiVersion: v1
kind: Service
metadata:
annotations: null
labels:
app.kubernetes.io/component: redis
app.kubernetes.io/name: argocd-redis-ha
app.kubernetes.io/part-of: argocd
name: argocd-redis-ha
spec:
clusterIP: None
ports:
- name: server
port: 6379
protocol: TCP
targetPort: redis
- name: sentinel
port: 26379
protocol: TCP
targetPort: sentinel
selector:
app.kubernetes.io/name: argocd-redis-ha
type: ClusterIP
---
apiVersion: v1
kind: Service
metadata:
labels:
app.kubernetes.io/component: repo-server
@@ -2912,6 +2912,23 @@ spec:
---
apiVersion: v1
kind: Service
metadata:
labels:
app.kubernetes.io/component: server
app.kubernetes.io/name: argocd-server-metrics
app.kubernetes.io/part-of: argocd
name: argocd-server-metrics
spec:
ports:
- name: metrics
port: 8083
protocol: TCP
targetPort: 8083
selector:
app.kubernetes.io/name: argocd-server
---
apiVersion: v1
kind: Service
metadata:
labels:
app.kubernetes.io/component: server
@@ -2931,23 +2948,6 @@ spec:
selector:
app.kubernetes.io/name: argocd-server
---
apiVersion: v1
kind: Service
metadata:
labels:
app.kubernetes.io/component: server
app.kubernetes.io/name: argocd-server-metrics
app.kubernetes.io/part-of: argocd
name: argocd-server-metrics
spec:
ports:
- name: metrics
port: 8083
protocol: TCP
targetPort: 8083
selector:
app.kubernetes.io/name: argocd-server
---
apiVersion: apps/v1
kind: Deployment
metadata:
@@ -2982,7 +2982,7 @@ spec:
- argocd-redis-ha-announce-2:26379
- --sentinelmaster
- argocd
image: argoproj/argocd:v1.3.0
image: argoproj/argocd:v1.3.6
imagePullPolicy: Always
livenessProbe:
httpGet:
@@ -3036,7 +3036,7 @@ spec:
- cp
- /usr/local/bin/argocd-util
- /shared
image: argoproj/argocd:v1.3.0
image: argoproj/argocd:v1.3.6
imagePullPolicy: Always
name: copyutil
volumeMounts:
@@ -3092,7 +3092,7 @@ spec:
- argocd-redis-ha-announce-2:26379
- --sentinelmaster
- argocd
image: argoproj/argocd:v1.3.0
image: argoproj/argocd:v1.3.6
imagePullPolicy: Always
livenessProbe:
initialDelaySeconds: 5
@@ -3166,7 +3166,7 @@ spec:
- argocd-redis-ha-announce-2:26379
- --sentinelmaster
- argocd
image: argoproj/argocd:v1.3.0
image: argoproj/argocd:v1.3.6
imagePullPolicy: Always
livenessProbe:
httpGet:

View File

@@ -2704,30 +2704,6 @@ spec:
---
apiVersion: v1
kind: Service
metadata:
annotations: null
labels:
app.kubernetes.io/component: redis
app.kubernetes.io/name: argocd-redis-ha
app.kubernetes.io/part-of: argocd
name: argocd-redis-ha
spec:
clusterIP: None
ports:
- name: server
port: 6379
protocol: TCP
targetPort: redis
- name: sentinel
port: 26379
protocol: TCP
targetPort: sentinel
selector:
app.kubernetes.io/name: argocd-redis-ha
type: ClusterIP
---
apiVersion: v1
kind: Service
metadata:
annotations:
service.alpha.kubernetes.io/tolerate-unready-endpoints: "true"
@@ -2806,6 +2782,30 @@ spec:
---
apiVersion: v1
kind: Service
metadata:
annotations: null
labels:
app.kubernetes.io/component: redis
app.kubernetes.io/name: argocd-redis-ha
app.kubernetes.io/part-of: argocd
name: argocd-redis-ha
spec:
clusterIP: None
ports:
- name: server
port: 6379
protocol: TCP
targetPort: redis
- name: sentinel
port: 26379
protocol: TCP
targetPort: sentinel
selector:
app.kubernetes.io/name: argocd-redis-ha
type: ClusterIP
---
apiVersion: v1
kind: Service
metadata:
labels:
app.kubernetes.io/component: repo-server
@@ -2827,6 +2827,23 @@ spec:
---
apiVersion: v1
kind: Service
metadata:
labels:
app.kubernetes.io/component: server
app.kubernetes.io/name: argocd-server-metrics
app.kubernetes.io/part-of: argocd
name: argocd-server-metrics
spec:
ports:
- name: metrics
port: 8083
protocol: TCP
targetPort: 8083
selector:
app.kubernetes.io/name: argocd-server
---
apiVersion: v1
kind: Service
metadata:
labels:
app.kubernetes.io/component: server
@@ -2846,23 +2863,6 @@ spec:
selector:
app.kubernetes.io/name: argocd-server
---
apiVersion: v1
kind: Service
metadata:
labels:
app.kubernetes.io/component: server
app.kubernetes.io/name: argocd-server-metrics
app.kubernetes.io/part-of: argocd
name: argocd-server-metrics
spec:
ports:
- name: metrics
port: 8083
protocol: TCP
targetPort: 8083
selector:
app.kubernetes.io/name: argocd-server
---
apiVersion: apps/v1
kind: Deployment
metadata:
@@ -2897,7 +2897,7 @@ spec:
- argocd-redis-ha-announce-2:26379
- --sentinelmaster
- argocd
image: argoproj/argocd:v1.3.0
image: argoproj/argocd:v1.3.6
imagePullPolicy: Always
livenessProbe:
httpGet:
@@ -2951,7 +2951,7 @@ spec:
- cp
- /usr/local/bin/argocd-util
- /shared
image: argoproj/argocd:v1.3.0
image: argoproj/argocd:v1.3.6
imagePullPolicy: Always
name: copyutil
volumeMounts:
@@ -3007,7 +3007,7 @@ spec:
- argocd-redis-ha-announce-2:26379
- --sentinelmaster
- argocd
image: argoproj/argocd:v1.3.0
image: argoproj/argocd:v1.3.6
imagePullPolicy: Always
livenessProbe:
initialDelaySeconds: 5
@@ -3081,7 +3081,7 @@ spec:
- argocd-redis-ha-announce-2:26379
- --sentinelmaster
- argocd
image: argoproj/argocd:v1.3.0
image: argoproj/argocd:v1.3.6
imagePullPolicy: Always
livenessProbe:
httpGet:

View File

@@ -2684,6 +2684,23 @@ spec:
---
apiVersion: v1
kind: Service
metadata:
labels:
app.kubernetes.io/component: server
app.kubernetes.io/name: argocd-server-metrics
app.kubernetes.io/part-of: argocd
name: argocd-server-metrics
spec:
ports:
- name: metrics
port: 8083
protocol: TCP
targetPort: 8083
selector:
app.kubernetes.io/name: argocd-server
---
apiVersion: v1
kind: Service
metadata:
labels:
app.kubernetes.io/component: server
@@ -2703,23 +2720,6 @@ spec:
selector:
app.kubernetes.io/name: argocd-server
---
apiVersion: v1
kind: Service
metadata:
labels:
app.kubernetes.io/component: server
app.kubernetes.io/name: argocd-server-metrics
app.kubernetes.io/part-of: argocd
name: argocd-server-metrics
spec:
ports:
- name: metrics
port: 8083
protocol: TCP
targetPort: 8083
selector:
app.kubernetes.io/name: argocd-server
---
apiVersion: apps/v1
kind: Deployment
metadata:
@@ -2746,7 +2746,7 @@ spec:
- "20"
- --operation-processors
- "10"
image: argoproj/argocd:v1.3.0
image: argoproj/argocd:v1.3.6
imagePullPolicy: Always
livenessProbe:
httpGet:
@@ -2800,7 +2800,7 @@ spec:
- cp
- /usr/local/bin/argocd-util
- /shared
image: argoproj/argocd:v1.3.0
image: argoproj/argocd:v1.3.6
imagePullPolicy: Always
name: copyutil
volumeMounts:
@@ -2864,7 +2864,7 @@ spec:
- argocd-repo-server
- --redis
- argocd-redis:6379
image: argoproj/argocd:v1.3.0
image: argoproj/argocd:v1.3.6
imagePullPolicy: Always
livenessProbe:
initialDelaySeconds: 5
@@ -2915,7 +2915,7 @@ spec:
- argocd-server
- --staticassets
- /shared/app
image: argoproj/argocd:v1.3.0
image: argoproj/argocd:v1.3.6
imagePullPolicy: Always
livenessProbe:
httpGet:

View File

@@ -2599,6 +2599,23 @@ spec:
---
apiVersion: v1
kind: Service
metadata:
labels:
app.kubernetes.io/component: server
app.kubernetes.io/name: argocd-server-metrics
app.kubernetes.io/part-of: argocd
name: argocd-server-metrics
spec:
ports:
- name: metrics
port: 8083
protocol: TCP
targetPort: 8083
selector:
app.kubernetes.io/name: argocd-server
---
apiVersion: v1
kind: Service
metadata:
labels:
app.kubernetes.io/component: server
@@ -2618,23 +2635,6 @@ spec:
selector:
app.kubernetes.io/name: argocd-server
---
apiVersion: v1
kind: Service
metadata:
labels:
app.kubernetes.io/component: server
app.kubernetes.io/name: argocd-server-metrics
app.kubernetes.io/part-of: argocd
name: argocd-server-metrics
spec:
ports:
- name: metrics
port: 8083
protocol: TCP
targetPort: 8083
selector:
app.kubernetes.io/name: argocd-server
---
apiVersion: apps/v1
kind: Deployment
metadata:
@@ -2661,7 +2661,7 @@ spec:
- "20"
- --operation-processors
- "10"
image: argoproj/argocd:v1.3.0
image: argoproj/argocd:v1.3.6
imagePullPolicy: Always
livenessProbe:
httpGet:
@@ -2715,7 +2715,7 @@ spec:
- cp
- /usr/local/bin/argocd-util
- /shared
image: argoproj/argocd:v1.3.0
image: argoproj/argocd:v1.3.6
imagePullPolicy: Always
name: copyutil
volumeMounts:
@@ -2779,7 +2779,7 @@ spec:
- argocd-repo-server
- --redis
- argocd-redis:6379
image: argoproj/argocd:v1.3.0
image: argoproj/argocd:v1.3.6
imagePullPolicy: Always
livenessProbe:
initialDelaySeconds: 5
@@ -2830,7 +2830,7 @@ spec:
- argocd-server
- --staticassets
- /shared/app
image: argoproj/argocd:v1.3.0
image: argoproj/argocd:v1.3.6
imagePullPolicy: Always
livenessProbe:
httpGet:

View File

@@ -1291,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

@@ -303,6 +303,7 @@ 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",

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

@@ -5,6 +5,7 @@ import (
"encoding/json"
"fmt"
"io/ioutil"
"net/url"
"os"
"os/exec"
"path/filepath"
@@ -12,7 +13,6 @@ import (
"strings"
"github.com/TomOnTime/utfutil"
argoexec "github.com/argoproj/pkg/exec"
"github.com/ghodss/yaml"
"github.com/google/go-jsonnet"
log "github.com/sirupsen/logrus"
@@ -23,6 +23,9 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
executil "github.com/argoproj/argo-cd/util/exec"
"github.com/argoproj/argo-cd/util/security"
"github.com/argoproj/argo-cd/common"
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/reposerver/apiclient"
@@ -31,7 +34,6 @@ import (
"github.com/argoproj/argo-cd/util/app/discovery"
argopath "github.com/argoproj/argo-cd/util/app/path"
"github.com/argoproj/argo-cd/util/cache"
"github.com/argoproj/argo-cd/util/config"
"github.com/argoproj/argo-cd/util/git"
"github.com/argoproj/argo-cd/util/helm"
"github.com/argoproj/argo-cd/util/ksonnet"
@@ -110,12 +112,12 @@ 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,
operation func(appPath string, revision string) error,
operation func(appPath, repoRoot, revision string) error,
settings operationSettings) error {
var gitClient git.Client
@@ -136,7 +138,7 @@ func (s *Service) runRepoOperation(
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
}
@@ -156,28 +158,28 @@ func (s *Service) runRepoOperation(
return err
}
defer util.Close(closer)
return operation(chartPath, revision)
return operation(chartPath, 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, gitClient.Root(), 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)
@@ -192,9 +194,9 @@ func (s *Service) GenerateManifest(c context.Context, q *apiclient.ManifestReque
}
return false
}
err := s.runRepoOperation(c, q.Revision, q.Repo, q.ApplicationSource, getCached, func(appPath string, revision string) error {
err := s.runRepoOperation(ctx, q.Revision, q.Repo, q.ApplicationSource, getCached, func(appPath, repoRoot, revision string) error {
var err error
res, err = GenerateManifests(appPath, q)
res, err = GenerateManifests(appPath, repoRoot, q)
if err != nil {
return err
}
@@ -216,7 +218,7 @@ func getHelmRepos(repositories []*v1alpha1.Repository) []helm.HelmRepository {
return repos
}
func helmTemplate(appPath string, q *apiclient.ManifestRequest) ([]*unstructured.Unstructured, error) {
func helmTemplate(appPath string, repoRoot string, q *apiclient.ManifestRequest) ([]*unstructured.Unstructured, error) {
templateOpts := &helm.TemplateOpts{
Name: q.AppLabelValue,
Namespace: q.Namespace,
@@ -224,14 +226,43 @@ 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 {
// Ensure that the repo root provided is absolute
absRepoPath, err := filepath.Abs(repoRoot)
if err != nil {
return nil, err
}
// If the path to the file is relative, join it with the current working directory (appPath)
path := val
if !filepath.IsAbs(path) {
absWorkDir, err := filepath.Abs(appPath)
if err != nil {
return nil, err
}
path = filepath.Join(absWorkDir, path)
}
_, err = security.EnforceToCurrentRoot(absRepoPath, path)
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
}
@@ -243,6 +274,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
@@ -282,7 +314,7 @@ func helmTemplate(appPath string, q *apiclient.ManifestRequest) ([]*unstructured
}
// GenerateManifests generates manifests from a path
func GenerateManifests(appPath string, q *apiclient.ManifestRequest) (*apiclient.ManifestResponse, error) {
func GenerateManifests(appPath, repoRoot string, q *apiclient.ManifestRequest) (*apiclient.ManifestResponse, error) {
var targetObjs []*unstructured.Unstructured
var dest *v1alpha1.ApplicationDestination
@@ -295,7 +327,7 @@ func GenerateManifests(appPath string, q *apiclient.ManifestRequest) (*apiclient
case v1alpha1.ApplicationSourceTypeKsonnet:
targetObjs, dest, err = ksShow(q.AppLabelKey, appPath, q.ApplicationSource.Ksonnet)
case v1alpha1.ApplicationSourceTypeHelm:
targetObjs, err = helmTemplate(appPath, q)
targetObjs, err = helmTemplate(appPath, repoRoot, q)
case v1alpha1.ApplicationSourceTypeKustomize:
k := kustomize.NewKustomizeApp(appPath, q.Repo.GetGitCreds(), repoURL)
targetObjs, _, err = k.Build(q.ApplicationSource.Kustomize, q.KustomizeOptions)
@@ -531,7 +563,7 @@ func runCommand(command v1alpha1.Command, path string, env []string) (string, er
cmd := exec.Command(command.Command[0], append(command.Command[1:], command.Args...)...)
cmd.Env = env
cmd.Dir = path
return argoexec.RunCommandExt(cmd, config.CmdOpts())
return executil.Run(cmd)
}
func findPlugin(plugins []*v1alpha1.ConfigManagementPlugin, name string) *v1alpha1.ConfigManagementPlugin {
@@ -572,23 +604,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.Source.TargetRevision, 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, repoRoot, revision string) error {
appSourceType, err := GetAppSourceType(q.Source, appPath)
if err != nil {
return err
@@ -677,32 +709,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

@@ -42,6 +42,7 @@ func newServiceWithMocks(root string) (*Service, *gitmocks.Client, *helmmocks.Cl
gitClient.On("CommitSHA").Return(mock.Anything, nil)
gitClient.On("Root").Return(root)
helmClient.On("CleanChartCache", mock.Anything, mock.Anything).Return(nil)
helmClient.On("ExtractChart", mock.Anything, mock.Anything).Return(func(chart string, version string) string {
return path.Join(root, chart)
}, util.NewCloser(func() error {
@@ -81,8 +82,8 @@ func TestGenerateYamlManifestInDir(t *testing.T) {
assert.Equal(t, countOfManifests, len(res1.Manifests))
// this will test concatenated manifests to verify we split YAMLs correctly
res2, err := GenerateManifests("./testdata/concatenated", &q)
assert.Nil(t, err)
res2, err := GenerateManifests("./testdata/concatenated", "/", &q)
assert.NoError(t, err)
assert.Equal(t, 3, len(res2.Manifests))
})
}
@@ -206,11 +207,10 @@ 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("../..")
_, err := service.GenerateManifest(context.Background(), &apiclient.ManifestRequest{
Repo: &argoappv1.Repository{},
AppLabelValue: "test",
@@ -222,7 +222,106 @@ func TestGenerateHelmWithValuesDirectoryTraversal(t *testing.T) {
},
},
})
assert.Error(t, err)
assert.NoError(t, err)
// Test the case where the path is "."
service = newService("./testdata/my-chart")
_, err = service.GenerateManifest(context.Background(), &apiclient.ManifestRequest{
Repo: &argoappv1.Repository{},
AppLabelValue: "test",
ApplicationSource: &argoappv1.ApplicationSource{
Path: ".",
},
})
assert.NoError(t, err)
}
// This is a Helm first-class app with a values file inside the repo directory
// (`~/go/src/github.com/argoproj/argo-cd/reposerver/repository`), so it is allowed
func TestHelmManifestFromChartRepoWithValueFile(t *testing.T) {
service := newService(".")
source := &argoappv1.ApplicationSource{
Chart: "./testdata/my-chart",
TargetRevision: "1.1.0",
Helm: &argoappv1.ApplicationSourceHelm{
ValueFiles: []string{"./my-chart-values.yaml"},
},
}
request := &apiclient.ManifestRequest{Repo: &argoappv1.Repository{}, ApplicationSource: source, NoCache: true}
response, err := service.GenerateManifest(context.Background(), request)
assert.NoError(t, err)
assert.NotNil(t, response)
assert.Equal(t, &apiclient.ManifestResponse{
Manifests: []string{"{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"my-map\"}}"},
Namespace: "",
Server: "",
Revision: "1.1.0",
SourceType: "Helm",
}, response)
}
// This is a Helm first-class app with a values file outside the repo directory
// (`~/go/src/github.com/argoproj/argo-cd/reposerver/repository`), so it is not allowed
func TestHelmManifestFromChartRepoWithValueFileOutsideRepo(t *testing.T) {
service := newService(".")
source := &argoappv1.ApplicationSource{
Chart: "./testdata/my-chart",
TargetRevision: "1.0.0",
Helm: &argoappv1.ApplicationSourceHelm{
ValueFiles: []string{"../my-chart-2/my-chart-2-values.yaml"},
},
}
request := &apiclient.ManifestRequest{Repo: &argoappv1.Repository{}, ApplicationSource: source, NoCache: true}
_, err := service.GenerateManifest(context.Background(), request)
assert.Error(t, err, "should be on or under current directory")
}
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")
service = newService("./testdata/my-chart")
_, err = service.GenerateManifest(context.Background(), &apiclient.ManifestRequest{
Repo: &argoappv1.Repository{},
AppLabelValue: "test",
ApplicationSource: &argoappv1.ApplicationSource{
Path: ".",
Helm: &argoappv1.ApplicationSourceHelm{
ValueFiles: []string{"../my-chart-2/values.yaml"},
Values: `cluster: {slaveCount: 2}`,
},
},
})
assert.Error(t, err, "should be on or under current directory")
}
@@ -307,7 +406,7 @@ func TestGenerateFromUTF16(t *testing.T) {
q := apiclient.ManifestRequest{
ApplicationSource: &argoappv1.ApplicationSource{},
}
res1, err := GenerateManifests("./testdata/utf-16", &q)
res1, err := GenerateManifests("./testdata/utf-16", "/", &q)
assert.Nil(t, err)
assert.Equal(t, 2, len(res1.Manifests))
}
@@ -324,6 +423,8 @@ func TestListApps(t *testing.T) {
"invalid-kustomize": "Kustomize",
"kustomization_yaml": "Kustomize",
"kustomization_yml": "Kustomize",
"my-chart": "Helm",
"my-chart-2": "Helm",
}
assert.Equal(t, expectedApps, res.Apps)
}
@@ -408,7 +509,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,2 @@
name: my-chart
version: 1.1.0

View File

@@ -0,0 +1 @@
app: go

View File

@@ -0,0 +1,4 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: my-map

View File

@@ -0,0 +1,2 @@
name: my-chart
version: 1.1.0

View File

@@ -0,0 +1,4 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: my-map

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

@@ -552,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

@@ -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

@@ -184,6 +184,20 @@ func TestKubeVersion(t *testing.T) {
})
}
func TestHelmValuesHiddenDirectory(t *testing.T) {
Given(t).
Path(".hidden-helm").
When().
AddFile("foo.yaml", "").
Create().
AppSet("--values", "foo.yaml").
Sync().
Then().
Expect(OperationPhaseIs(OperationSucceeded)).
Expect(HealthIs(HealthStatusHealthy)).
Expect(SyncStatusIs(SyncStatusCodeSynced))
}
func TestHelmWithDependencies(t *testing.T) {
testHelmWithDependencies(t, false)
}
@@ -193,7 +207,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

@@ -0,0 +1,2 @@
version: 1.0.0
name: helm

View File

@@ -0,0 +1,6 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: my-map
data:
foo: bar

View File

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, "Version:kustomize/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
}

View File

@@ -1,9 +1,6 @@
package test
import (
"encoding/json"
"github.com/ghodss/yaml"
appsv1 "k8s.io/api/apps/v1"
apiv1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -23,7 +20,7 @@ const (
FakeClusterURL = "https://fake-cluster:443"
)
var PodManifest = []byte(`
var PodManifest = `
{
"apiVersion": "v1",
"kind": "Pod",
@@ -44,19 +41,14 @@ var PodManifest = []byte(`
]
}
}
`)
`
func NewPod() *unstructured.Unstructured {
var un unstructured.Unstructured
err := json.Unmarshal(PodManifest, &un)
if err != nil {
panic(err)
}
return &un
return Unstructured(PodManifest)
}
func NewCRD() *unstructured.Unstructured {
var un unstructured.Unstructured
err := yaml.Unmarshal([]byte(`apiVersion: apiextensions.k8s.io/v1beta1
return Unstructured(`apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: testcrds.argoproj.io
@@ -66,11 +58,7 @@ spec:
scope: Namespaced
names:
plural: testcrds
kind: TestCrd`), &un)
if err != nil {
panic(err)
}
return &un
kind: TestCrd`)
}
// DEPRECATED
@@ -97,7 +85,7 @@ func Annotate(obj *unstructured.Unstructured, key, val string) *unstructured.Uns
return obj
}
var ServiceManifest = []byte(`
var ServiceManifest = `
{
"apiVersion": "v1",
"kind": "Service",
@@ -118,18 +106,13 @@ var ServiceManifest = []byte(`
}
}
}
`)
`
func NewService() *unstructured.Unstructured {
var un unstructured.Unstructured
err := json.Unmarshal(ServiceManifest, &un)
if err != nil {
panic(err)
}
return &un
return Unstructured(ServiceManifest)
}
var DeploymentManifest = []byte(`
var DeploymentManifest = `
{
"apiVersion": "apps/v1",
"kind": "Deployment",
@@ -168,15 +151,10 @@ var DeploymentManifest = []byte(`
}
}
}
`)
`
func NewDeployment() *unstructured.Unstructured {
var un unstructured.Unstructured
err := json.Unmarshal(DeploymentManifest, &un)
if err != nil {
panic(err)
}
return &un
return Unstructured(DeploymentManifest)
}
func DemoDeployment() *appsv1.Deployment {

32
test/unstructured.go Normal file
View File

@@ -0,0 +1,32 @@
package test
import (
"encoding/json"
"io/ioutil"
"strings"
"github.com/ghodss/yaml"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
func UnstructuredFromFile(path string) *unstructured.Unstructured {
file, err := ioutil.ReadFile(path)
if err != nil {
panic(err)
}
return Unstructured(string(file))
}
func Unstructured(text string) *unstructured.Unstructured {
un := &unstructured.Unstructured{}
var err error
if strings.HasPrefix(text, "{") {
err = json.Unmarshal([]byte(text), &un)
} else {
err = yaml.Unmarshal([]byte(text), &un)
}
if err != nil {
panic(err)
}
return un
}

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

@@ -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)

View File

@@ -1,26 +0,0 @@
package config
import (
"os"
"time"
"github.com/argoproj/pkg/exec"
)
var timeout time.Duration
func init() {
initTimeout()
}
func initTimeout() {
var err error
timeout, err = time.ParseDuration(os.Getenv("ARGOCD_EXEC_TIMEOUT"))
if err != nil {
timeout = 90 * time.Second
}
}
func CmdOpts() exec.CmdOpts {
return exec.CmdOpts{Timeout: timeout}
}

View File

@@ -1,34 +0,0 @@
package config
import (
"os"
"testing"
"time"
"github.com/argoproj/pkg/exec"
"github.com/stretchr/testify/assert"
)
func Test_timeout(t *testing.T) {
defer func() { _ = os.Unsetenv("ARGOCD_EXEC_TIMEOUT") }()
tests := []struct {
name string
text string
want time.Duration
}{
{"Default", "", 90 * time.Second},
{"OneSecond", "1s", 1 * time.Second},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_ = os.Setenv("ARGOCD_EXEC_TIMEOUT", tt.text)
initTimeout()
assert.Equal(t, tt.want, timeout)
})
}
}
func TestCmdOpts(t *testing.T) {
initTimeout()
assert.Equal(t, exec.CmdOpts{Timeout: 90 * time.Second}, CmdOpts())
}

View File

@@ -7,6 +7,7 @@ import (
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
v1 "k8s.io/api/core/v1"
"k8s.io/utils/pointer"
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/util/settings"
@@ -57,7 +58,7 @@ func (db *db) ListHelmRepositories(ctx context.Context) ([]*v1alpha1.Repository,
}
result[i] = repo
}
repos, err := db.ListRepositories(ctx)
repos, err := db.listRepositories(ctx, pointer.StringPtr("helm"))
if err != nil {
return nil, err
}

View File

@@ -5,9 +5,8 @@ package mocks
import (
context "context"
mock "github.com/stretchr/testify/mock"
db "github.com/argoproj/argo-cd/util/db"
mock "github.com/stretchr/testify/mock"
v1alpha1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
)
@@ -40,6 +39,29 @@ func (_m *ArgoDB) CreateCluster(ctx context.Context, c *v1alpha1.Cluster) (*v1al
return r0, r1
}
// CreateRepoCertificate provides a mock function with given fields: ctx, certificate, upsert
func (_m *ArgoDB) CreateRepoCertificate(ctx context.Context, certificate *v1alpha1.RepositoryCertificateList, upsert bool) (*v1alpha1.RepositoryCertificateList, error) {
ret := _m.Called(ctx, certificate, upsert)
var r0 *v1alpha1.RepositoryCertificateList
if rf, ok := ret.Get(0).(func(context.Context, *v1alpha1.RepositoryCertificateList, bool) *v1alpha1.RepositoryCertificateList); ok {
r0 = rf(ctx, certificate, upsert)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*v1alpha1.RepositoryCertificateList)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *v1alpha1.RepositoryCertificateList, bool) error); ok {
r1 = rf(ctx, certificate, upsert)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// CreateRepository provides a mock function with given fields: ctx, r
func (_m *ArgoDB) CreateRepository(ctx context.Context, r *v1alpha1.Repository) (*v1alpha1.Repository, error) {
ret := _m.Called(ctx, r)
@@ -77,13 +99,13 @@ func (_m *ArgoDB) DeleteCluster(ctx context.Context, name string) error {
return r0
}
// DeleteRepository provides a mock function with given fields: ctx, name
func (_m *ArgoDB) DeleteRepository(ctx context.Context, name string) error {
ret := _m.Called(ctx, name)
// DeleteRepository provides a mock function with given fields: ctx, url
func (_m *ArgoDB) DeleteRepository(ctx context.Context, url string) error {
ret := _m.Called(ctx, url)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, string) error); ok {
r0 = rf(ctx, name)
r0 = rf(ctx, url)
} else {
r0 = ret.Error(0)
}
@@ -114,13 +136,13 @@ func (_m *ArgoDB) GetCluster(ctx context.Context, name string) (*v1alpha1.Cluste
return r0, r1
}
// GetRepository provides a mock function with given fields: ctx, name
func (_m *ArgoDB) GetRepository(ctx context.Context, name string) (*v1alpha1.Repository, error) {
ret := _m.Called(ctx, name)
// GetRepository provides a mock function with given fields: ctx, url
func (_m *ArgoDB) GetRepository(ctx context.Context, url string) (*v1alpha1.Repository, error) {
ret := _m.Called(ctx, url)
var r0 *v1alpha1.Repository
if rf, ok := ret.Get(0).(func(context.Context, string) *v1alpha1.Repository); ok {
r0 = rf(ctx, name)
r0 = rf(ctx, url)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*v1alpha1.Repository)
@@ -129,7 +151,7 @@ func (_m *ArgoDB) GetRepository(ctx context.Context, name string) (*v1alpha1.Rep
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
r1 = rf(ctx, name)
r1 = rf(ctx, url)
} else {
r1 = ret.Error(1)
}
@@ -160,6 +182,52 @@ func (_m *ArgoDB) ListClusters(ctx context.Context) (*v1alpha1.ClusterList, erro
return r0, r1
}
// ListHelmRepositories provides a mock function with given fields: ctx
func (_m *ArgoDB) ListHelmRepositories(ctx context.Context) ([]*v1alpha1.Repository, error) {
ret := _m.Called(ctx)
var r0 []*v1alpha1.Repository
if rf, ok := ret.Get(0).(func(context.Context) []*v1alpha1.Repository); ok {
r0 = rf(ctx)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*v1alpha1.Repository)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context) error); ok {
r1 = rf(ctx)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// ListRepoCertificates provides a mock function with given fields: ctx, selector
func (_m *ArgoDB) ListRepoCertificates(ctx context.Context, selector *db.CertificateListSelector) (*v1alpha1.RepositoryCertificateList, error) {
ret := _m.Called(ctx, selector)
var r0 *v1alpha1.RepositoryCertificateList
if rf, ok := ret.Get(0).(func(context.Context, *db.CertificateListSelector) *v1alpha1.RepositoryCertificateList); ok {
r0 = rf(ctx, selector)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*v1alpha1.RepositoryCertificateList)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *db.CertificateListSelector) error); ok {
r1 = rf(ctx, selector)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// ListRepositories provides a mock function with given fields: ctx
func (_m *ArgoDB) ListRepositories(ctx context.Context) ([]*v1alpha1.Repository, error) {
ret := _m.Called(ctx)
@@ -183,6 +251,29 @@ func (_m *ArgoDB) ListRepositories(ctx context.Context) ([]*v1alpha1.Repository,
return r0, r1
}
// RemoveRepoCertificates provides a mock function with given fields: ctx, selector
func (_m *ArgoDB) RemoveRepoCertificates(ctx context.Context, selector *db.CertificateListSelector) (*v1alpha1.RepositoryCertificateList, error) {
ret := _m.Called(ctx, selector)
var r0 *v1alpha1.RepositoryCertificateList
if rf, ok := ret.Get(0).(func(context.Context, *db.CertificateListSelector) *v1alpha1.RepositoryCertificateList); ok {
r0 = rf(ctx, selector)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*v1alpha1.RepositoryCertificateList)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *db.CertificateListSelector) error); ok {
r1 = rf(ctx, selector)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// UpdateCluster provides a mock function with given fields: ctx, c
func (_m *ArgoDB) UpdateCluster(ctx context.Context, c *v1alpha1.Cluster) (*v1alpha1.Cluster, error) {
ret := _m.Called(ctx, c)

View File

@@ -108,6 +108,10 @@ func (db *db) GetRepository(ctx context.Context, repoURL string) (*appsv1.Reposi
}
func (db *db) ListRepositories(ctx context.Context) ([]*appsv1.Repository, error) {
return db.listRepositories(ctx, nil)
}
func (db *db) listRepositories(ctx context.Context, repoType *string) ([]*appsv1.Repository, error) {
inRepos, err := db.settingsMgr.GetRepositories()
if err != nil {
return nil, err
@@ -115,12 +119,13 @@ func (db *db) ListRepositories(ctx context.Context) ([]*appsv1.Repository, error
var repos []*appsv1.Repository
for _, inRepo := range inRepos {
r, err := db.GetRepository(ctx, inRepo.URL)
if err != nil {
return nil, err
if repoType == nil || *repoType == inRepo.Type {
r, err := db.GetRepository(ctx, inRepo.URL)
if err != nil {
return nil, err
}
repos = append(repos, r)
}
repos = append(repos, r)
}
return repos, nil
}

42
util/exec/exec.go Normal file
View File

@@ -0,0 +1,42 @@
package exec
import (
"fmt"
"os"
"os/exec"
"time"
argoexec "github.com/argoproj/pkg/exec"
tracing "github.com/argoproj/argo-cd/util/tracing"
)
var timeout time.Duration
func init() {
initTimeout()
}
func initTimeout() {
var err error
timeout, err = time.ParseDuration(os.Getenv("ARGOCD_EXEC_TIMEOUT"))
if err != nil {
timeout = 90 * time.Second
}
}
func Run(cmd *exec.Cmd) (string, error) {
return RunWithRedactor(cmd, nil)
}
func RunWithRedactor(cmd *exec.Cmd, redactor func(text string) string) (string, error) {
span := tracing.StartSpan(fmt.Sprintf("exec %v", cmd.Args[0]))
span.SetBaggageItem("dir", fmt.Sprintf("%v", cmd.Dir))
span.SetBaggageItem("args", fmt.Sprintf("%v", cmd.Args))
defer span.Finish()
opts := argoexec.CmdOpts{Timeout: timeout}
if redactor != nil {
opts.Redactor = redactor
}
return argoexec.RunCommandExt(cmd, opts)
}

29
util/exec/exec_test.go Normal file
View File

@@ -0,0 +1,29 @@
package exec
import (
"os"
"os/exec"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func Test_timeout(t *testing.T) {
defer func() { _ = os.Unsetenv("ARGOCD_EXEC_TIMEOUT") }()
t.Run("Default", func(t *testing.T) {
initTimeout()
assert.Equal(t, 90*time.Second, timeout)
})
t.Run("Default", func(t *testing.T) {
_ = os.Setenv("ARGOCD_EXEC_TIMEOUT", "1s")
initTimeout()
assert.Equal(t, 1*time.Second, timeout)
})
}
func TestRun(t *testing.T) {
out, err := Run(exec.Command("ls"))
assert.NoError(t, err)
assert.NotEmpty(t, out)
}

View File

@@ -13,7 +13,6 @@ import (
"strings"
"time"
argoexec "github.com/argoproj/pkg/exec"
log "github.com/sirupsen/logrus"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/knownhosts"
@@ -27,7 +26,7 @@ import (
"github.com/argoproj/argo-cd/common"
certutil "github.com/argoproj/argo-cd/util/cert"
argoconfig "github.com/argoproj/argo-cd/util/config"
executil "github.com/argoproj/argo-cd/util/exec"
)
type RevisionMetadata struct {
@@ -221,7 +220,7 @@ func (m *nativeGitClient) Init() error {
return err
}
log.Infof("Initializing %s to %s", m.repoURL, m.root)
_, err = argoexec.RunCommand("rm", argoconfig.CmdOpts(), "-rf", m.root)
_, err = executil.Run(exec.Command("rm", "-rf", m.root))
if err != nil {
return fmt.Errorf("unable to clean repo at %s: %v", m.root, err)
}
@@ -475,5 +474,5 @@ func (m *nativeGitClient) runCmdOutput(cmd *exec.Cmd) (string, error) {
}
}
}
return argoexec.RunCommandExt(cmd, argoconfig.CmdOpts())
return executil.Run(cmd)
}

View File

@@ -15,13 +15,12 @@ import (
"strings"
"time"
argoexec "github.com/argoproj/pkg/exec"
"github.com/patrickmn/go-cache"
log "github.com/sirupsen/logrus"
"gopkg.in/yaml.v2"
"github.com/argoproj/argo-cd/util"
"github.com/argoproj/argo-cd/util/config"
executil "github.com/argoproj/argo-cd/util/exec"
)
var (
@@ -160,7 +159,7 @@ func (c *nativeHelmChart) ExtractChart(chart string, version string) (string, ut
}
cmd := exec.Command("tar", "-zxvf", chartPath)
cmd.Dir = tempDir
_, err = argoexec.RunCommandExt(cmd, config.CmdOpts())
_, err = executil.Run(cmd)
if err != nil {
_ = os.RemoveAll(tempDir)
return "", nil, err

View File

@@ -6,14 +6,10 @@ 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/config"
"github.com/argoproj/argo-cd/util/security"
executil "github.com/argoproj/argo-cd/util/exec"
)
// A thin wrapper around the "helm" command, adding logging and error translation.
@@ -39,10 +35,7 @@ func (c Cmd) run(args ...string) (string, error) {
cmd.Dir = c.WorkDir
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,
})
return executil.RunWithRedactor(cmd, redactor)
}
func (c *Cmd) Init() (string, error) {
@@ -196,18 +189,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,10 +10,10 @@ import (
"regexp"
"strings"
argoexec "github.com/argoproj/pkg/exec"
"github.com/ghodss/yaml"
"github.com/argoproj/argo-cd/util/config"
executil "github.com/argoproj/argo-cd/util/exec"
)
type HelmRepository struct {
@@ -87,10 +87,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,
})
out, err := executil.RunWithRedactor(cmd, redactor)
if err != nil {
return "", fmt.Errorf("could not get helm version: %s", err)
}

8
util/helm/version.go Normal file
View File

@@ -0,0 +1,8 @@
package helm
import "github.com/Masterminds/semver"
func IsVersion(text string) bool {
_, err := semver.NewVersion(text)
return err == nil
}

14
util/helm/version_test.go Normal file
View File

@@ -0,0 +1,14 @@
package helm
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestIsVersion(t *testing.T) {
assert.False(t, IsVersion("*"))
assert.False(t, IsVersion("1.*"))
assert.False(t, IsVersion("1.0.*"))
assert.True(t, IsVersion("1.0.0"))
}

View File

@@ -20,5 +20,8 @@ func DeletePolicies(obj *unstructured.Unstructured) []v1alpha1.HookDeletePolicy
for _, p := range helmhook.DeletePolicies(obj) {
policies = append(policies, p.DeletePolicy())
}
if len(policies) == 0 {
policies = append(policies, v1alpha1.HookDeletePolicyBeforeHookCreation)
}
return policies
}

View File

@@ -10,8 +10,8 @@ import (
)
func TestDeletePolicies(t *testing.T) {
assert.Nil(t, DeletePolicies(NewPod()))
assert.Nil(t, DeletePolicies(Annotate(NewPod(), "argocd.argoproj.io/hook-delete-policy", "garbage")))
assert.Equal(t, []HookDeletePolicy{HookDeletePolicyBeforeHookCreation}, DeletePolicies(NewPod()))
assert.Equal(t, []HookDeletePolicy{HookDeletePolicyBeforeHookCreation}, DeletePolicies(Annotate(NewPod(), "argocd.argoproj.io/hook-delete-policy", "garbage")))
assert.Equal(t, []HookDeletePolicy{HookDeletePolicyBeforeHookCreation}, DeletePolicies(Annotate(NewPod(), "argocd.argoproj.io/hook-delete-policy", "BeforeHookCreation")))
assert.Equal(t, []HookDeletePolicy{HookDeletePolicyHookSucceeded}, DeletePolicies(Annotate(NewPod(), "argocd.argoproj.io/hook-delete-policy", "HookSucceeded")))
assert.Equal(t, []HookDeletePolicy{HookDeletePolicyHookFailed}, DeletePolicies(Annotate(NewPod(), "argocd.argoproj.io/hook-delete-policy", "HookFailed")))

View File

@@ -9,12 +9,11 @@ import (
"path/filepath"
"strings"
argoexec "github.com/argoproj/pkg/exec"
"github.com/ghodss/yaml"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/util/config"
executil "github.com/argoproj/argo-cd/util/exec"
"github.com/argoproj/argo-cd/util/kube"
)
@@ -103,7 +102,7 @@ func (k *ksonnetApp) ksCmd(args ...string) (string, error) {
cmd := exec.Command("ks", args...)
cmd.Dir = k.Root()
return argoexec.RunCommandExt(cmd, config.CmdOpts())
return executil.Run(cmd)
}
func (k *ksonnetApp) Root() string {

25
util/kube/convert.go Normal file
View File

@@ -0,0 +1,25 @@
package kube
import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/kubernetes/pkg/api/legacyscheme"
)
func convertToVersionWithScheme(obj *unstructured.Unstructured, group string, version string) (*unstructured.Unstructured, error) {
s := legacyscheme.Scheme
object, err := s.ConvertToVersion(obj, runtime.InternalGroupVersioner)
if err != nil {
return nil, err
}
unmarshalledObj, err := s.ConvertToVersion(object, schema.GroupVersion{Group: group, Version: version})
if err != nil {
return nil, err
}
unstrBody, err := runtime.DefaultUnstructuredConverter.ToUnstructured(unmarshalledObj)
if err != nil {
return nil, err
}
return &unstructured.Unstructured{Object: unstrBody}, nil
}

93
util/kube/convert_test.go Normal file
View File

@@ -0,0 +1,93 @@
package kube
import (
"testing"
"github.com/ghodss/yaml"
"github.com/stretchr/testify/assert"
"k8s.io/apimachinery/pkg/runtime/schema"
"github.com/argoproj/argo-cd/test"
)
type testcase struct {
name string
file string
outputVersion string
fields []checkField
}
type checkField struct {
expected string
}
func Test_convertToVersionWithScheme(t *testing.T) {
for _, tt := range []testcase{
{
name: "apps deployment to extensions deployment",
file: "appsdeployment.yaml",
outputVersion: "extensions/v1beta1",
fields: []checkField{
{
expected: "apiVersion: extensions/v1beta1",
},
},
},
{
name: "extensions deployment to apps deployment",
file: "extensionsdeployment.yaml",
outputVersion: "apps/v1beta2",
fields: []checkField{
{
expected: "apiVersion: apps/v1beta2",
},
},
},
{
name: "v1 HPA to v2beta1 HPA",
file: "v1HPA.yaml",
outputVersion: "autoscaling/v2beta1",
fields: []checkField{
{
expected: "apiVersion: autoscaling/v2beta1",
},
{
expected: "name: cpu",
},
{
expected: "targetAverageUtilization: 50",
},
},
},
{
name: "v2beta1 HPA to v1 HPA",
file: "v2beta1HPA.yaml",
outputVersion: "autoscaling/v1",
fields: []checkField{
{
expected: "apiVersion: autoscaling/v1",
},
{
expected: "targetCPUUtilizationPercentage: 50",
},
},
},
} {
t.Run(tt.name, func(t *testing.T) {
obj := test.UnstructuredFromFile("testdata/" + tt.file)
target, err := schema.ParseGroupVersion(tt.outputVersion)
assert.NoError(t, err)
out, err := convertToVersionWithScheme(obj, target.Group, target.Version)
if assert.NoError(t, err) {
assert.NotNil(t, out)
assert.Equal(t, target.Group, out.GroupVersionKind().Group)
assert.Equal(t, target.Version, out.GroupVersionKind().Version)
bytes, err := yaml.Marshal(out)
assert.NoError(t, err)
for _, field := range tt.fields {
assert.Contains(t, string(bytes), field.expected)
}
}
})
}
}

View File

@@ -22,8 +22,9 @@ import (
"k8s.io/client-go/rest"
"github.com/argoproj/argo-cd/util"
"github.com/argoproj/argo-cd/util/config"
"github.com/argoproj/argo-cd/util/diff"
executil "github.com/argoproj/argo-cd/util/exec"
"github.com/argoproj/argo-cd/util/tracing"
)
type Kubectl interface {
@@ -109,6 +110,8 @@ func isSupportedVerb(apiResource *metav1.APIResource, verb string) bool {
}
func (k *KubectlCmd) GetAPIResources(config *rest.Config, resourceFilter ResourceFilter) ([]APIResourceInfo, error) {
span := tracing.StartSpan("GetAPIResources")
defer span.Finish()
apiResIfs, err := filterAPIResources(config, resourceFilter, func(apiResource *metav1.APIResource) bool {
return isSupportedVerb(apiResource, listVerb) && isSupportedVerb(apiResource, watchVerb)
}, "")
@@ -120,6 +123,10 @@ func (k *KubectlCmd) GetAPIResources(config *rest.Config, resourceFilter Resourc
// GetResource returns resource
func (k *KubectlCmd) GetResource(config *rest.Config, gvk schema.GroupVersionKind, name string, namespace string) (*unstructured.Unstructured, error) {
span := tracing.StartSpan("GetResource")
span.SetBaggageItem("kind", gvk.Kind)
span.SetBaggageItem("name", name)
defer span.Finish()
dynamicIf, err := dynamic.NewForConfig(config)
if err != nil {
return nil, err
@@ -139,6 +146,10 @@ func (k *KubectlCmd) GetResource(config *rest.Config, gvk schema.GroupVersionKin
// PatchResource patches resource
func (k *KubectlCmd) PatchResource(config *rest.Config, gvk schema.GroupVersionKind, name string, namespace string, patchType types.PatchType, patchBytes []byte) (*unstructured.Unstructured, error) {
span := tracing.StartSpan("PatchResource")
span.SetBaggageItem("kind", gvk.Kind)
span.SetBaggageItem("name", name)
defer span.Finish()
dynamicIf, err := dynamic.NewForConfig(config)
if err != nil {
return nil, err
@@ -158,6 +169,10 @@ func (k *KubectlCmd) PatchResource(config *rest.Config, gvk schema.GroupVersionK
// DeleteResource deletes resource
func (k *KubectlCmd) DeleteResource(config *rest.Config, gvk schema.GroupVersionKind, name string, namespace string, forceDelete bool) error {
span := tracing.StartSpan("DeleteResource")
span.SetBaggageItem("kind", gvk.Kind)
span.SetBaggageItem("name", name)
defer span.Finish()
dynamicIf, err := dynamic.NewForConfig(config)
if err != nil {
return err
@@ -185,6 +200,10 @@ func (k *KubectlCmd) DeleteResource(config *rest.Config, gvk schema.GroupVersion
// ApplyResource performs an apply of a unstructured resource
func (k *KubectlCmd) ApplyResource(config *rest.Config, obj *unstructured.Unstructured, namespace string, dryRun, force, validate bool) (string, error) {
span := tracing.StartSpan("ApplyResource")
span.SetBaggageItem("kind", obj.GetKind())
span.SetBaggageItem("name", obj.GetName())
defer span.Finish()
log.Infof("Applying resource %s/%s in cluster: %s, namespace: %s", obj.GetKind(), obj.GetName(), config.Host, namespace)
f, err := ioutil.TempFile(util.TempDir, "")
if err != nil {
@@ -303,7 +322,7 @@ func (k *KubectlCmd) runKubectl(kubeconfigPath string, namespace string, args []
log.Debug(string(redactedBytes))
}
cmd.Stdin = bytes.NewReader(manifestBytes)
out, err := argoexec.RunCommandExt(cmd, config.CmdOpts())
out, err := executil.Run(cmd)
if err != nil {
return "", convertKubectlError(err)
}
@@ -311,8 +330,10 @@ func (k *KubectlCmd) runKubectl(kubeconfigPath string, namespace string, args []
}
func Version() (string, error) {
span := tracing.StartSpan("Version")
defer span.Finish()
cmd := exec.Command("kubectl", "version", "--client")
out, err := argoexec.RunCommandExt(cmd, config.CmdOpts())
out, err := executil.Run(cmd)
if err != nil {
return "", fmt.Errorf("could not get kubectl version: %s", err)
}
@@ -330,52 +351,20 @@ func Version() (string, error) {
// ConvertToVersion converts an unstructured object into the specified group/version
func (k *KubectlCmd) ConvertToVersion(obj *unstructured.Unstructured, group string, version string) (*unstructured.Unstructured, error) {
gvk := obj.GroupVersionKind()
if gvk.Group == group && gvk.Version == version {
span := tracing.StartSpan("ConvertToVersion")
from := obj.GroupVersionKind().GroupVersion()
span.SetBaggageItem("from", from.String())
span.SetBaggageItem("to", schema.GroupVersion{Group: group, Version: version}.String())
defer span.Finish()
if from.Group == group && from.Version == version {
return obj.DeepCopy(), nil
}
manifestBytes, err := json.Marshal(obj)
if err != nil {
return nil, err
}
f, err := ioutil.TempFile(util.TempDir, "")
if err != nil {
return nil, fmt.Errorf("Failed to generate temp file for kubectl: %v", err)
}
_ = f.Close()
if err := ioutil.WriteFile(f.Name(), manifestBytes, 0600); err != nil {
return nil, err
}
defer util.DeleteFile(f.Name())
closer, err := k.processKubectlRun([]string{"convert"})
if err != nil {
return nil, err
}
defer util.Close(closer)
outputVersion := fmt.Sprintf("%s/%s", group, version)
cmd := exec.Command("kubectl", "convert", "--output-version", outputVersion, "-o", "json", "--local=true", "-f", f.Name())
cmd.Stdin = bytes.NewReader(manifestBytes)
out, err := argoexec.RunCommandExt(cmd, config.CmdOpts())
if err != nil {
return nil, convertKubectlError(err)
}
// NOTE: when kubectl convert runs against stdin (i.e. kubectl convert -f -), the output is
// a unstructured list instead of an unstructured object
var convertedObj unstructured.Unstructured
err = json.Unmarshal([]byte(out), &convertedObj)
if err != nil {
return nil, err
}
if convertedObj.GetNamespace() == "" {
convertedObj.SetNamespace(obj.GetNamespace())
}
return &convertedObj, nil
return convertToVersionWithScheme(obj, group, version)
}
func (k *KubectlCmd) GetServerVersion(config *rest.Config) (string, error) {
span := tracing.StartSpan("GetServerVersion")
defer span.Finish()
client, err := discovery.NewDiscoveryClientForConfig(config)
if err != nil {
return "", err

View File

@@ -1,50 +1,56 @@
package kube
import (
"io/ioutil"
"regexp"
"testing"
"github.com/argoproj/argo-cd/test"
"github.com/argoproj/argo-cd/util"
"github.com/ghodss/yaml"
"github.com/stretchr/testify/assert"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
func TestConvertToVersion(t *testing.T) {
callbackExecuted := false
closerExecuted := false
kubectl := KubectlCmd{}
kubectl.SetOnKubectlRun(func(command string) (util.Closer, error) {
callbackExecuted = true
return util.NewCloser(func() error {
closerExecuted = true
return nil
}), nil
t.Run("AppsDeployment", func(t *testing.T) {
newObj, err := kubectl.ConvertToVersion(test.UnstructuredFromFile("testdata/appsdeployment.yaml"), "extensions", "v1beta1")
if assert.NoError(t, err) {
gvk := newObj.GroupVersionKind()
assert.Equal(t, "extensions", gvk.Group)
assert.Equal(t, "v1beta1", gvk.Version)
}
})
t.Run("CustomResource", func(t *testing.T) {
_, err := kubectl.ConvertToVersion(test.UnstructuredFromFile("testdata/cr.yaml"), "argoproj.io", "v1")
assert.Error(t, err)
})
t.Run("ExtensionsDeployment", func(t *testing.T) {
obj := test.UnstructuredFromFile("testdata/nginx.yaml")
yamlBytes, err := ioutil.ReadFile("testdata/nginx.yaml")
assert.Nil(t, err)
var obj unstructured.Unstructured
err = yaml.Unmarshal(yamlBytes, &obj)
assert.Nil(t, err)
// convert an extensions/v1beta1 object into itself
newObj, err := kubectl.ConvertToVersion(obj, "extensions", "v1beta1")
if assert.NoError(t, err) {
gvk := newObj.GroupVersionKind()
assert.Equal(t, "extensions", gvk.Group)
assert.Equal(t, "v1beta1", gvk.Version)
}
// convert an extensions/v1beta1 object into an apps/v1
newObj, err := kubectl.ConvertToVersion(&obj, "apps", "v1")
assert.Nil(t, err)
gvk := newObj.GroupVersionKind()
assert.Equal(t, "apps", gvk.Group)
assert.Equal(t, "v1", gvk.Version)
assert.True(t, callbackExecuted)
assert.True(t, closerExecuted)
// convert an extensions/v1beta1 object into an apps/v1
newObj, err = kubectl.ConvertToVersion(obj, "apps", "v1")
if assert.NoError(t, err) {
gvk := newObj.GroupVersionKind()
assert.Equal(t, "apps", gvk.Group)
assert.Equal(t, "v1", gvk.Version)
}
// converting it again should not have any affect
newObj, err = kubectl.ConvertToVersion(&obj, "apps", "v1")
assert.Nil(t, err)
gvk = newObj.GroupVersionKind()
assert.Equal(t, "apps", gvk.Group)
assert.Equal(t, "v1", gvk.Version)
// converting it again should not have any affect
newObj, err = kubectl.ConvertToVersion(obj, "apps", "v1")
if assert.NoError(t, err) {
gvk := newObj.GroupVersionKind()
assert.Equal(t, "apps", gvk.Group)
assert.Equal(t, "v1", gvk.Version)
}
})
}
func TestRunKubectl(t *testing.T) {

View File

@@ -0,0 +1,19 @@
package kube
import (
_ "k8s.io/kubernetes/pkg/apis/apps/install"
_ "k8s.io/kubernetes/pkg/apis/authentication/install"
_ "k8s.io/kubernetes/pkg/apis/authorization/install"
_ "k8s.io/kubernetes/pkg/apis/autoscaling/install"
_ "k8s.io/kubernetes/pkg/apis/batch/install"
_ "k8s.io/kubernetes/pkg/apis/certificates/install"
_ "k8s.io/kubernetes/pkg/apis/coordination/install"
_ "k8s.io/kubernetes/pkg/apis/core/install"
_ "k8s.io/kubernetes/pkg/apis/events/install"
_ "k8s.io/kubernetes/pkg/apis/extensions/install"
_ "k8s.io/kubernetes/pkg/apis/policy/install"
_ "k8s.io/kubernetes/pkg/apis/rbac/install"
_ "k8s.io/kubernetes/pkg/apis/scheduling/install"
_ "k8s.io/kubernetes/pkg/apis/settings/install"
_ "k8s.io/kubernetes/pkg/apis/storage/install"
)

View File

@@ -21,6 +21,7 @@ type MockKubectlCmd struct {
Commands map[string]KubectlOutput
Events chan watch.Event
LastValidate bool
Version string
}
func (k *MockKubectlCmd) GetAPIResources(config *rest.Config, resourceFilter kube.ResourceFilter) ([]kube.APIResourceInfo, error) {
@@ -58,7 +59,7 @@ func (k *MockKubectlCmd) ConvertToVersion(obj *unstructured.Unstructured, group,
}
func (k *MockKubectlCmd) GetServerVersion(config *rest.Config) (string, error) {
return "", nil
return k.Version, nil
}
func (k *MockKubectlCmd) SetOnKubectlRun(onKubectlRun func(command string) (util.Closer, error)) {

13
util/kube/testdata/appsdeployment.yaml vendored Normal file
View File

@@ -0,0 +1,13 @@
apiVersion: apps/v1beta2
kind: Deployment
metadata:
name: nginx-deployment
spec:
template:
metadata:
labels:
name: nginx
spec:
containers:
- name: nginx
image: nginx

13
util/kube/testdata/cr.yaml vendored Normal file
View File

@@ -0,0 +1,13 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: custom-resource
namespace: default
spec:
destination:
namespace: default
server: https://kubernetes.default.svc
project: default
source:
path: guestbook
repoURL: https://github.com/argoproj/argocd-example-apps.git

View File

@@ -0,0 +1,13 @@
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: nginx-deployment
spec:
template:
metadata:
labels:
name: nginx
spec:
containers:
- name: nginx
image: nginx

12
util/kube/testdata/v1HPA.yaml vendored Normal file
View File

@@ -0,0 +1,12 @@
apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
name: php-apache
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: php-apache
minReplicas: 1
maxReplicas: 10
targetCPUUtilizationPercentage: 50

16
util/kube/testdata/v2beta1HPA.yaml vendored Normal file
View File

@@ -0,0 +1,16 @@
apiVersion: autoscaling/v2beta1
kind: HorizontalPodAutoscaler
metadata:
name: php-apache
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: php-apache
minReplicas: 1
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
targetAverageUtilization: 50

View File

@@ -9,13 +9,12 @@ import (
"sort"
"strings"
argoexec "github.com/argoproj/pkg/exec"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/util/config"
executil "github.com/argoproj/argo-cd/util/exec"
"github.com/argoproj/argo-cd/util/git"
"github.com/argoproj/argo-cd/util/kube"
@@ -53,9 +52,9 @@ func (k *kustomize) Build(opts *v1alpha1.ApplicationSourceKustomize, kustomizeOp
if opts != nil {
if opts.NamePrefix != "" {
cmd := exec.Command("kustomize", "edit", "set", "nameprefix", opts.NamePrefix)
cmd := exec.Command("kustomize", "edit", "set", "nameprefix", "--", opts.NamePrefix)
cmd.Dir = k.path
_, err := argoexec.RunCommandExt(cmd, config.CmdOpts())
_, err := executil.Run(cmd)
if err != nil {
return nil, nil, err
}
@@ -69,7 +68,7 @@ func (k *kustomize) Build(opts *v1alpha1.ApplicationSourceKustomize, kustomizeOp
}
cmd := exec.Command("kustomize", args...)
cmd.Dir = k.path
_, err := argoexec.RunCommandExt(cmd, config.CmdOpts())
_, err := executil.Run(cmd)
if err != nil {
return nil, nil, err
}
@@ -88,7 +87,7 @@ func (k *kustomize) Build(opts *v1alpha1.ApplicationSourceKustomize, kustomizeOp
args = append(args, arg)
cmd := exec.Command("kustomize", args...)
cmd.Dir = k.path
_, err := argoexec.RunCommandExt(cmd, config.CmdOpts())
_, err := executil.Run(cmd)
if err != nil {
return nil, nil, err
}
@@ -132,7 +131,7 @@ func (k *kustomize) Build(opts *v1alpha1.ApplicationSourceKustomize, kustomizeOp
}
cmd.Env = append(cmd.Env, environ...)
out, err := argoexec.RunCommandExt(cmd, config.CmdOpts())
out, err := executil.Run(cmd)
if err != nil {
return nil, nil, err
}
@@ -173,7 +172,7 @@ func IsKustomization(path string) bool {
func Version() (string, error) {
cmd := exec.Command("kustomize", "version")
out, err := argoexec.RunCommandExt(cmd, config.CmdOpts())
out, err := executil.Run(cmd)
if err != nil {
return "", fmt.Errorf("could not get kustomize version: %s", err)
}

View File

@@ -168,7 +168,8 @@ func (e *Enforcer) newInformer() cache.SharedIndexInformer {
cmFieldSelector := fields.ParseSelectorOrDie(fmt.Sprintf("metadata.name=%s", e.configmap))
options.FieldSelector = cmFieldSelector.String()
}
return v1.NewFilteredConfigMapInformer(e.clientset, e.namespace, defaultRBACSyncPeriod, cache.Indexers{}, tweakConfigMap)
indexers := cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}
return v1.NewFilteredConfigMapInformer(e.clientset, e.namespace, defaultRBACSyncPeriod, indexers, tweakConfigMap)
}
// RunPolicyLoader runs the policy loader which watches policy updates from the configmap and reloads them

View File

@@ -17,13 +17,19 @@ func EnforceToCurrentRoot(currentRoot, requestedPath string) (string, error) {
return requestedDir + string(filepath.Separator) + requestedFile, nil
}
func isRequestedDirUnderCurrentRoot(currentRoot, requestedDir string) bool {
func isRequestedDirUnderCurrentRoot(currentRoot, requestedPath string) bool {
if currentRoot == string(filepath.Separator) {
return true
} else if currentRoot == requestedDir {
} else if currentRoot == requestedPath {
return true
}
return strings.HasPrefix(requestedDir, currentRoot+string(filepath.Separator))
if requestedPath[len(requestedPath)-1] != '/' {
requestedPath = requestedPath + "/"
}
if currentRoot[len(currentRoot)-1] != '/' {
currentRoot = currentRoot + "/"
}
return strings.HasPrefix(requestedPath, currentRoot)
}
func parsePath(path string) (string, string) {

View File

@@ -66,9 +66,9 @@ type ArgoCDSettings struct {
// Secrets holds all secrets in argocd-secret as a map[string]string
Secrets map[string]string `json:"secrets,omitempty"`
// KustomizeBuildOptions is a string of kustomize build parameters
KustomizeBuildOptions string
KustomizeBuildOptions string `json:"kustomizeBuildOptions,omitempty"`
// Indicates if anonymous user is enabled or not
AnonymousUserEnabled bool
AnonymousUserEnabled bool `json:"anonymousUserEnabled,omitempty"`
}
type GoogleAnalytics struct {
@@ -201,6 +201,8 @@ type SettingsManager struct {
// mutex protects concurrency sensitive parts of settings manager: access to subscribers list and initialization flag
mutex *sync.Mutex
initContextCancel func()
reposCache []RepoCredentials
repoCredsCache []RepoCredentials
}
type incompleteSettingsError struct {
@@ -219,6 +221,38 @@ func (mgr *SettingsManager) GetSecretsLister() (v1listers.SecretLister, error) {
return mgr.secrets, nil
}
func (mgr *SettingsManager) updateConfigMap(callback func(*apiv1.ConfigMap) error) error {
argoCDCM, err := mgr.getConfigMap()
createCM := false
if err != nil {
if !apierr.IsNotFound(err) {
return err
}
argoCDCM = &apiv1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: common.ArgoCDConfigMapName,
},
}
createCM = true
}
if argoCDCM.Data == nil {
argoCDCM.Data = make(map[string]string)
}
err = callback(argoCDCM)
if err != nil {
return err
}
if createCM {
_, err = mgr.clientset.CoreV1().ConfigMaps(mgr.namespace).Create(argoCDCM)
} else {
_, err = mgr.clientset.CoreV1().ConfigMaps(mgr.namespace).Update(argoCDCM)
}
mgr.invalidateCache()
return err
}
func (mgr *SettingsManager) getConfigMap() (*apiv1.ConfigMap, error) {
err := mgr.ensureSynced(false)
if err != nil {
@@ -325,7 +359,10 @@ func (mgr *SettingsManager) GetKustomizeBuildOptions() (string, error) {
if err != nil {
return "", err
}
return argoCDCM.Data[kustomizeBuildOptions], nil
if value, ok := argoCDCM.Data[kustomizeBuildOptions]; ok {
return value, nil
}
return "", nil
}
// DEPRECATED. Helm repository credentials are now managed using RepoCredentials
@@ -346,54 +383,78 @@ func (mgr *SettingsManager) GetHelmRepositories() ([]HelmRepoCredentials, error)
}
func (mgr *SettingsManager) GetRepositories() ([]RepoCredentials, error) {
argoCDCM, err := mgr.getConfigMap()
if err != nil {
return nil, err
}
repositories := make([]RepoCredentials, 0)
repositoriesStr := argoCDCM.Data[repositoriesKey]
if repositoriesStr != "" {
err := yaml.Unmarshal([]byte(repositoriesStr), &repositories)
if mgr.reposCache == nil {
argoCDCM, err := mgr.getConfigMap()
if err != nil {
return nil, err
}
mgr.mutex.Lock()
defer mgr.mutex.Unlock()
repositories := make([]RepoCredentials, 0)
repositoriesStr := argoCDCM.Data[repositoriesKey]
if repositoriesStr != "" {
err := yaml.Unmarshal([]byte(repositoriesStr), &repositories)
if err != nil {
return nil, err
}
}
mgr.reposCache = repositories
}
return repositories, nil
return mgr.reposCache, nil
}
func (mgr *SettingsManager) SaveRepositories(repos []RepoCredentials) error {
argoCDCM, err := mgr.getConfigMap()
if err != nil {
return err
}
if len(repos) > 0 {
yamlStr, err := yaml.Marshal(repos)
if err != nil {
return err
return mgr.updateConfigMap(func(argoCDCM *apiv1.ConfigMap) error {
if len(repos) > 0 {
yamlStr, err := yaml.Marshal(repos)
if err != nil {
return err
}
argoCDCM.Data[repositoriesKey] = string(yamlStr)
} else {
delete(argoCDCM.Data, repositoriesKey)
}
argoCDCM.Data[repositoriesKey] = string(yamlStr)
} else {
delete(argoCDCM.Data, repositoriesKey)
}
_, err = mgr.clientset.CoreV1().ConfigMaps(mgr.namespace).Update(argoCDCM)
return err
return nil
})
}
func (mgr *SettingsManager) SaveRepositoryCredentials(creds []RepoCredentials) error {
return mgr.updateConfigMap(func(argoCDCM *apiv1.ConfigMap) error {
if len(creds) > 0 {
yamlStr, err := yaml.Marshal(creds)
if err != nil {
return err
}
argoCDCM.Data[repositoryCredentialsKey] = string(yamlStr)
} else {
delete(argoCDCM.Data, repositoryCredentialsKey)
}
return nil
})
}
func (mgr *SettingsManager) GetRepositoryCredentials() ([]RepoCredentials, error) {
argoCDCM, err := mgr.getConfigMap()
if err != nil {
return nil, err
}
repositoryCredentials := make([]RepoCredentials, 0)
repositoryCredentialsStr := argoCDCM.Data[repositoryCredentialsKey]
if repositoryCredentialsStr != "" {
err := yaml.Unmarshal([]byte(repositoryCredentialsStr), &repositoryCredentials)
if mgr.repoCredsCache == nil {
argoCDCM, err := mgr.getConfigMap()
if err != nil {
return nil, err
}
mgr.mutex.Lock()
defer mgr.mutex.Unlock()
creds := make([]RepoCredentials, 0)
credsStr := argoCDCM.Data[repositoryCredentialsKey]
if credsStr != "" {
err := yaml.Unmarshal([]byte(credsStr), &creds)
if err != nil {
return nil, err
}
}
mgr.repoCredsCache = creds
}
return repositoryCredentials, nil
return mgr.repoCredsCache, nil
}
func (mgr *SettingsManager) GetGoogleAnalytics() (*GoogleAnalytics, error) {
@@ -448,15 +509,31 @@ func (mgr *SettingsManager) GetSettings() (*ArgoCDSettings, error) {
return &settings, nil
}
// Clears cached settings on configmap/secret change
func (mgr *SettingsManager) invalidateCache() {
mgr.mutex.Lock()
defer mgr.mutex.Unlock()
mgr.reposCache = nil
mgr.repoCredsCache = nil
}
func (mgr *SettingsManager) initialize(ctx context.Context) error {
tweakConfigMap := func(options *metav1.ListOptions) {
//cmFieldSelector := fields.ParseSelectorOrDie(fmt.Sprintf("metadata.name=%s", common.ArgoCDConfigMapName))
cmLabelSelector := fields.ParseSelectorOrDie("app.kubernetes.io/part-of=argocd")
options.LabelSelector = cmLabelSelector.String()
}
cmInformer := v1.NewFilteredConfigMapInformer(mgr.clientset, mgr.namespace, 3*time.Minute, cache.Indexers{}, tweakConfigMap)
secretsInformer := v1.NewSecretInformer(mgr.clientset, mgr.namespace, 3*time.Minute, cache.Indexers{})
eventHandler := cache.ResourceEventHandlerFuncs{
UpdateFunc: func(oldObj, newObj interface{}) {
mgr.invalidateCache()
},
}
indexers := cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}
cmInformer := v1.NewFilteredConfigMapInformer(mgr.clientset, mgr.namespace, 3*time.Minute, indexers, tweakConfigMap)
secretsInformer := v1.NewSecretInformer(mgr.clientset, mgr.namespace, 3*time.Minute, indexers)
cmInformer.AddEventHandler(eventHandler)
secretsInformer.AddEventHandler(eventHandler)
log.Info("Starting configmap/secret informers")
go func() {
@@ -592,49 +669,25 @@ func updateSettingsFromSecret(settings *ArgoCDSettings, argoCDSecret *apiv1.Secr
// SaveSettings serializes ArgoCDSettings and upserts it into K8s secret/configmap
func (mgr *SettingsManager) SaveSettings(settings *ArgoCDSettings) error {
err := mgr.ensureSynced(false)
if err != nil {
return err
}
// Upsert the config data
argoCDCM, err := mgr.configmaps.ConfigMaps(mgr.namespace).Get(common.ArgoCDConfigMapName)
createCM := false
if err != nil {
if !apierr.IsNotFound(err) {
return err
err := mgr.updateConfigMap(func(argoCDCM *apiv1.ConfigMap) error {
if settings.URL != "" {
argoCDCM.Data[settingURLKey] = settings.URL
} else {
delete(argoCDCM.Data, settingURLKey)
}
argoCDCM = &apiv1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: common.ArgoCDConfigMapName,
},
if settings.DexConfig != "" {
argoCDCM.Data[settingDexConfigKey] = settings.DexConfig
} else {
delete(argoCDCM.Data, settings.DexConfig)
}
createCM = true
}
if argoCDCM.Data == nil {
argoCDCM.Data = make(map[string]string)
}
if settings.URL != "" {
argoCDCM.Data[settingURLKey] = settings.URL
} else {
delete(argoCDCM.Data, settingURLKey)
}
if settings.DexConfig != "" {
argoCDCM.Data[settingDexConfigKey] = settings.DexConfig
} else {
delete(argoCDCM.Data, settings.DexConfig)
}
if settings.OIDCConfigRAW != "" {
argoCDCM.Data[settingsOIDCConfigKey] = settings.OIDCConfigRAW
} else {
delete(argoCDCM.Data, settingsOIDCConfigKey)
}
if settings.OIDCConfigRAW != "" {
argoCDCM.Data[settingsOIDCConfigKey] = settings.OIDCConfigRAW
} else {
delete(argoCDCM.Data, settingsOIDCConfigKey)
}
return nil
})
if createCM {
_, err = mgr.clientset.CoreV1().ConfigMaps(mgr.namespace).Create(argoCDCM)
} else {
_, err = mgr.clientset.CoreV1().ConfigMaps(mgr.namespace).Update(argoCDCM)
}
if err != nil {
return err
}

View File

@@ -45,6 +45,34 @@ func TestSaveRepositories(t *testing.T) {
cm, err := kubeClient.CoreV1().ConfigMaps("default").Get(common.ArgoCDConfigMapName, metav1.GetOptions{})
assert.NoError(t, err)
assert.Equal(t, cm.Data["repositories"], "- url: http://foo\n")
repos, err := settingsManager.GetRepositories()
assert.NoError(t, err)
assert.ElementsMatch(t, repos, []RepoCredentials{{URL: "http://foo"}})
}
func TestSaveRepositoresNoConfigMap(t *testing.T) {
kubeClient := fake.NewSimpleClientset()
settingsManager := NewSettingsManager(context.Background(), kubeClient, "default")
err := settingsManager.SaveRepositories([]RepoCredentials{{URL: "http://foo"}})
assert.NoError(t, err)
cm, err := kubeClient.CoreV1().ConfigMaps("default").Get(common.ArgoCDConfigMapName, metav1.GetOptions{})
assert.NoError(t, err)
assert.Equal(t, cm.Data["repositories"], "- url: http://foo\n")
}
func TestSaveRepositoryCredentials(t *testing.T) {
kubeClient, settingsManager := fixtures(nil)
err := settingsManager.SaveRepositoryCredentials([]RepoCredentials{{URL: "http://foo"}})
assert.NoError(t, err)
cm, err := kubeClient.CoreV1().ConfigMaps("default").Get(common.ArgoCDConfigMapName, metav1.GetOptions{})
assert.NoError(t, err)
assert.Equal(t, cm.Data["repository.credentials"], "- url: http://foo\n")
creds, err := settingsManager.GetRepositoryCredentials()
assert.NoError(t, err)
assert.ElementsMatch(t, creds, []RepoCredentials{{URL: "http://foo"}})
}
func TestGetRepositoryCredentials(t *testing.T) {

44
util/tracing/span.go Normal file
View File

@@ -0,0 +1,44 @@
package tracing
import (
"os"
"time"
log "github.com/sirupsen/logrus"
)
/*
Poor Mans OpenTracing.
Standardizes logging of operation duration.
*/
var enabled = false
var logger = log.New()
func init() {
enabled = os.Getenv("ARGOCD_TRACING_ENABLED") == "1"
}
type Span struct {
operationName string
baggage map[string]interface{}
start time.Time
}
func (s Span) Finish() {
if enabled {
logger.WithFields(s.baggage).
WithField("operation_name", s.operationName).
WithField("time_ms", time.Since(s.start).Seconds()*1e3).
Info()
}
}
func (s Span) SetBaggageItem(key string, value interface{}) {
s.baggage[key] = value
}
func StartSpan(operationName string) Span {
return Span{operationName, make(map[string]interface{}), time.Now()}
}

42
util/tracing/span_test.go Normal file
View File

@@ -0,0 +1,42 @@
package tracing
import (
"testing"
log "github.com/sirupsen/logrus"
"github.com/sirupsen/logrus/hooks/test"
"github.com/stretchr/testify/assert"
)
func TestStartSpan(t *testing.T) {
testLogger, hook := test.NewNullLogger()
defer hook.Reset()
logger = testLogger
defer func() { logger = log.New() }()
t.Run("Disabled", func(t *testing.T) {
span := StartSpan("my-operation")
span.SetBaggageItem("my-key", "my-value")
span.Finish()
assert.Empty(t, hook.Entries)
})
hook.Reset()
t.Run("Enabled", func(t *testing.T) {
enabled = true
defer func() { enabled = false }()
span := StartSpan("my-operation")
span.SetBaggageItem("my-key", "my-value")
span.Finish()
e := hook.LastEntry()
if assert.NotNil(t, e) {
assert.Empty(t, e.Message)
assert.Equal(t, "my-operation", e.Data["operation_name"])
assert.Equal(t, "my-value", e.Data["my-key"])
assert.Contains(t, e.Data, "time_ms")
}
})
}