Compare commits

..

7 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
36 changed files with 536 additions and 203 deletions

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.4
1.3.6

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,

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

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

View File

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

View File

@@ -2982,7 +2982,7 @@ spec:
- argocd-redis-ha-announce-2:26379
- --sentinelmaster
- argocd
image: argoproj/argocd:v1.3.4
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.4
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.4
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.4
image: argoproj/argocd:v1.3.6
imagePullPolicy: Always
livenessProbe:
httpGet:

View File

@@ -2897,7 +2897,7 @@ spec:
- argocd-redis-ha-announce-2:26379
- --sentinelmaster
- argocd
image: argoproj/argocd:v1.3.4
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.4
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.4
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.4
image: argoproj/argocd:v1.3.6
imagePullPolicy: Always
livenessProbe:
httpGet:

View File

@@ -2746,7 +2746,7 @@ spec:
- "20"
- --operation-processors
- "10"
image: argoproj/argocd:v1.3.4
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.4
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.4
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.4
image: argoproj/argocd:v1.3.6
imagePullPolicy: Always
livenessProbe:
httpGet:

View File

@@ -2661,7 +2661,7 @@ spec:
- "20"
- --operation-processors
- "10"
image: argoproj/argocd:v1.3.4
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.4
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.4
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.4
image: argoproj/argocd:v1.3.6
imagePullPolicy: Always
livenessProbe:
httpGet:

View File

@@ -117,7 +117,7 @@ func (s *Service) runRepoOperation(
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
@@ -158,7 +158,7 @@ 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())
@@ -174,7 +174,7 @@ func (s *Service) runRepoOperation(
if err != nil {
return err
}
return operation(appPath, revision)
return operation(appPath, gitClient.Root(), revision)
}
}
@@ -194,9 +194,9 @@ func (s *Service) GenerateManifest(ctx context.Context, q *apiclient.ManifestReq
}
return false
}
err := s.runRepoOperation(ctx, 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
}
@@ -218,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,
@@ -236,22 +236,24 @@ func helmTemplate(appPath string, q *apiclient.ManifestRequest) ([]*unstructured
for _, val := range appHelm.ValueFiles {
// If val is not a URL, run it against the directory enforcer. If it is a URL, use it without checking
if _, err := url.ParseRequestURI(val); err != nil {
baseDirectoryPath, err := security.SubtractRelativeFromAbsolutePath(appPath, q.ApplicationSource.Path)
// Ensure that the repo root provided is absolute
absRepoPath, err := filepath.Abs(repoRoot)
if err != nil {
return nil, err
}
absBaseDir, err := filepath.Abs(baseDirectoryPath)
if err != nil {
return nil, err
}
if !filepath.IsAbs(val) {
// 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
}
val = filepath.Join(absWorkDir, val)
path = filepath.Join(absWorkDir, path)
}
_, err = security.EnforceToCurrentRoot(absBaseDir, val)
_, err = security.EnforceToCurrentRoot(absRepoPath, path)
if err != nil {
return nil, err
}
@@ -312,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
@@ -325,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)
@@ -618,7 +620,7 @@ func (s *Service) GetAppDetails(ctx context.Context, q *apiclient.RepoServerAppD
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

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))
})
}
@@ -210,7 +211,6 @@ func TestGenerateHelmWithValues(t *testing.T) {
// 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",
@@ -223,6 +223,57 @@ func TestGenerateHelmWithValuesDirectoryTraversal(t *testing.T) {
},
})
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) {
@@ -246,7 +297,6 @@ func TestGenerateHelmWithURL(t *testing.T) {
// (`~/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",
@@ -259,6 +309,20 @@ func TestGenerateHelmWithValuesDirectoryTraversalOutsideRepo(t *testing.T) {
},
})
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")
}
func TestGenerateNullList(t *testing.T) {
@@ -342,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))
}
@@ -359,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)
}

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

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

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

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

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

@@ -359,45 +359,7 @@ func (k *KubectlCmd) ConvertToVersion(obj *unstructured.Unstructured, group stri
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 := executil.Run(cmd)
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) {

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

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

@@ -17,36 +17,19 @@ func EnforceToCurrentRoot(currentRoot, requestedPath string) (string, error) {
return requestedDir + string(filepath.Separator) + requestedFile, nil
}
func SubtractRelativeFromAbsolutePath(abs, rel string) (string, error) {
if len(rel) == 0 {
return abs, nil
}
if rel[0] == '.' {
return SubtractRelativeFromAbsolutePath(abs, rel[1:])
}
if rel[0] != '/' {
return SubtractRelativeFromAbsolutePath(abs, "/"+rel)
}
if rel[len(rel)-1] == '/' {
return SubtractRelativeFromAbsolutePath(abs, rel[:len(rel)-1])
}
rel = filepath.Clean(rel)
lastIndex := strings.LastIndex(abs, rel)
if lastIndex < 0 {
// This should be unreachable, because by this point the App Path will have already been validated by Path in
// util/app/path/path.go
return "", fmt.Errorf("app path is not under repo path (unreachable and most likely a bug)")
}
return abs[:lastIndex], 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

@@ -24,32 +24,3 @@ func TestEnforceToCurrentRoot(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, "/home/argo/helmapp/values.yaml", cleanDir)
}
func TestSubtractRelativeFromAbsolutePath(t *testing.T) {
for _, test := range []string{"env", "/env", "env/", "/env/", "./env"} {
subtracted, err := SubtractRelativeFromAbsolutePath("/argocd-example-apps/helm-guestbook/env/guestbook/env", test)
assert.NoError(t, err)
assert.Equal(t, "/argocd-example-apps/helm-guestbook/env/guestbook", subtracted)
}
for _, test := range []string{"guestbook/env", "/guestbook/env", "guestbook/env/", "/guestbook/env/", "./guestbook/env"} {
subtracted, err := SubtractRelativeFromAbsolutePath("/argocd-example-apps/helm-guestbook/env/guestbook/env", test)
assert.NoError(t, err)
assert.Equal(t, "/argocd-example-apps/helm-guestbook/env", subtracted)
}
for _, test := range []string{"", ".", "/", "./"} {
subtracted, err := SubtractRelativeFromAbsolutePath("/argocd-example-apps/helm-guestbook/env/guestbook/env", test)
assert.NoError(t, err)
assert.Equal(t, "/argocd-example-apps/helm-guestbook/env/guestbook/env", subtracted)
}
// "Dirty" strings
for _, test := range []string{"guestbook/foo/../env", "/guestbook//env", "../guestbook/env/", "/../guestbook/env/", "./guestbook/env///"} {
subtracted, err := SubtractRelativeFromAbsolutePath("/argocd-example-apps/helm-guestbook/env/guestbook/env", test)
assert.NoError(t, err)
assert.Equal(t, "/argocd-example-apps/helm-guestbook/env", subtracted)
}
// Invalid strings
for _, test := range []string{"/not/in/path", "../not/in/path", "not/in/path"} {
_, err := SubtractRelativeFromAbsolutePath("/argocd-example-apps/helm-guestbook/env/guestbook/env", test)
assert.Error(t, err)
}
}