chore: Add a GitHub action that runs unit tests with -race to CI build (#4774) (#4775)

* chore: Add a GitHub action that runs unit tests with -race to CI build (#4774)

Signed-off-by: Jonathan West <jonwest@redhat.com>

* chore: Add a GitHub action that runs unit tests with -race to CI build (#4774)

Signed-off-by: Jonathan West <jonwest@redhat.com>
This commit is contained in:
Jonathan West 2020-11-07 06:57:18 -05:00 committed by GitHub
parent c4dcae3442
commit 4c3f97f78a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 556 additions and 370 deletions

View file

@ -132,6 +132,61 @@ jobs:
name: test-results
path: test-results/
test-go-race:
name: Run unit tests with -race, for Go packages
runs-on: ubuntu-latest
needs:
- build-go
steps:
- name: Create checkout directory
run: mkdir -p ~/go/src/github.com/argoproj
- name: Checkout code
uses: actions/checkout@v2
- name: Create symlink in GOPATH
run: ln -s $(pwd) ~/go/src/github.com/argoproj/argo-cd
- name: Setup Golang
uses: actions/setup-go@v1
with:
go-version: '1.14.2'
- name: Install required packages
run: |
sudo apt-get install git -y
- name: Switch to temporal branch so we re-attach head
run: |
git switch -c temporal-pr-branch
git status
- name: Fetch complete history for blame information
run: |
git fetch --prune --no-tags --depth=1 origin +refs/heads/*:refs/remotes/origin/*
- name: Add ~/go/bin to PATH
run: |
echo "/home/runner/go/bin" >> $GITHUB_PATH
- name: Add /usr/local/bin to PATH
run: |
echo "/usr/local/bin" >> $GITHUB_PATH
- name: Restore go build cache
uses: actions/cache@v1
with:
path: ~/.cache/go-build
key: ${{ runner.os }}-go-build-v1-${{ github.run_id }}
- name: Install all tools required for building & testing
run: |
make install-test-tools-local
- name: Setup git username and email
run: |
git config --global user.name "John Doe"
git config --global user.email "john.doe@example.com"
- name: Download and vendor all required packages
run: |
go mod download
- name: Run all unit tests
run: make test-race-local
- name: Generate test results artifacts
uses: actions/upload-artifact@v2
with:
name: race-results
path: test-results/
codegen:
name: Check changes to generated code
runs-on: ubuntu-latest

View file

@ -355,6 +355,20 @@ test-local:
./hack/test.sh -coverprofile=coverage.out "$(TEST_MODULE)"; \
fi
.PHONY: test-race
test-race: test-tools-image
mkdir -p $(GOCACHE)
$(call run-in-test-client,make TEST_MODULE=$(TEST_MODULE) test-race-local)
# Run all unit tests, with data race detection, skipping known failures (local version)
.PHONY: test-race-local
test-race-local:
if test "$(TEST_MODULE)" = ""; then \
./hack/test.sh -race -coverprofile=coverage.out `go list ./... | grep -v 'test/e2e'`; \
else \
./hack/test.sh -race -coverprofile=coverage.out "$(TEST_MODULE)"; \
fi
# Run the E2E test suite. E2E test servers (see start-e2e target) must be
# started before.
.PHONY: test-e2e

View file

@ -0,0 +1,44 @@
// +build !race
package repository
import (
"os"
"path/filepath"
"sync"
"testing"
"github.com/stretchr/testify/assert"
argoappv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/reposerver/apiclient"
)
func TestHelmDependencyWithConcurrency(t *testing.T) {
// !race:
// Un-synchronized use of a random source, will be fixed when this is merged:
// https://github.com/argoproj/argo-cd/issues/4728
cleanup := func() {
_ = os.Remove(filepath.Join("../../util/helm/testdata/helm2-dependency", helmDepUpMarkerFile))
_ = os.RemoveAll(filepath.Join("../../util/helm/testdata/helm2-dependency", "charts"))
}
cleanup()
defer cleanup()
var wg sync.WaitGroup
wg.Add(3)
for i := 0; i < 3; i++ {
go func() {
res, err := helmTemplate("../../util/helm/testdata/helm2-dependency", "../..", nil, &apiclient.ManifestRequest{
ApplicationSource: &argoappv1.ApplicationSource{},
}, false)
assert.NoError(t, err)
assert.NotNil(t, res)
wg.Done()
}()
}
wg.Wait()
}

View file

@ -10,7 +10,6 @@ import (
"path/filepath"
"regexp"
"strings"
"sync"
"testing"
"time"
@ -1216,27 +1215,3 @@ func TestGenerateManifestWithAnnotatedAndRegularGitTagHashes(t *testing.T) {
}
}
func TestHelmDependencyWithConcurrency(t *testing.T) {
cleanup := func() {
_ = os.Remove(filepath.Join("../../util/helm/testdata/helm2-dependency", helmDepUpMarkerFile))
_ = os.RemoveAll(filepath.Join("../../util/helm/testdata/helm2-dependency", "charts"))
}
cleanup()
defer cleanup()
var wg sync.WaitGroup
wg.Add(3)
for i := 0; i < 3; i++ {
go func() {
res, err := helmTemplate("../../util/helm/testdata/helm2-dependency", "../..", nil, &apiclient.ManifestRequest{
ApplicationSource: &argoappv1.ApplicationSource{},
}, false)
assert.NoError(t, err)
assert.NotNil(t, res)
wg.Done()
}()
}
wg.Wait()
}

View file

@ -0,0 +1,191 @@
// +build !race
package server
import (
"context"
"fmt"
"io/ioutil"
"net/http"
"strings"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/argoproj/argo-cd/common"
"github.com/argoproj/argo-cd/pkg/apiclient"
"github.com/argoproj/argo-cd/test"
applicationpkg "github.com/argoproj/argo-cd/pkg/apiclient/application"
)
func TestUserAgent(t *testing.T) {
// !race:
// A data race in go-client's `shared_informer.go`, between `sharedProcessor.run(...)` and itself. Based on
// the data race, it APPEARS to be intentional, but in any case it's nothing we are doing in Argo CD
// that is causing this issue.
s := fakeServer()
cancelInformer := test.StartInformer(s.projInformer)
defer cancelInformer()
port, err := test.GetFreePort()
assert.NoError(t, err)
metricsPort, err := test.GetFreePort()
assert.NoError(t, err)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go s.Run(ctx, port, metricsPort)
defer func() { time.Sleep(3 * time.Second) }()
err = test.WaitForPortListen(fmt.Sprintf("127.0.0.1:%d", port), 10*time.Second)
assert.NoError(t, err)
type testData struct {
userAgent string
errorMsg string
}
currentVersionBytes, err := ioutil.ReadFile("../VERSION")
assert.NoError(t, err)
currentVersion := strings.TrimSpace(string(currentVersionBytes))
var tests = []testData{
{
// Reject out-of-date user-agent
userAgent: fmt.Sprintf("%s/0.10.0", common.ArgoCDUserAgentName),
errorMsg: "unsatisfied client version constraint",
},
{
// Accept up-to-date user-agent
userAgent: fmt.Sprintf("%s/%s", common.ArgoCDUserAgentName, currentVersion),
},
{
// Accept up-to-date pre-release user-agent
userAgent: fmt.Sprintf("%s/%s-rc1", common.ArgoCDUserAgentName, currentVersion),
},
{
// Reject legacy client
// NOTE: after we update the grpc-go client past 1.15.0, this test will break and should be deleted
userAgent: " ", // need a space here since the apiclient will set the default user-agent if empty
errorMsg: "unsatisfied client version constraint",
},
{
// Permit custom clients
userAgent: "foo/1.2.3",
},
}
for _, test := range tests {
opts := apiclient.ClientOptions{
ServerAddr: fmt.Sprintf("localhost:%d", port),
PlainText: true,
UserAgent: test.userAgent,
}
clnt, err := apiclient.NewClient(&opts)
assert.NoError(t, err)
conn, appClnt := clnt.NewApplicationClientOrDie()
_, err = appClnt.List(ctx, &applicationpkg.ApplicationQuery{})
if test.errorMsg != "" {
assert.Error(t, err)
assert.Regexp(t, test.errorMsg, err.Error())
} else {
assert.NoError(t, err)
}
_ = conn.Close()
}
}
func Test_StaticHeaders(t *testing.T) {
// !race:
// Same as TestUserAgent
// Test default policy "sameorigin"
{
s := fakeServer()
cancelInformer := test.StartInformer(s.projInformer)
defer cancelInformer()
port, err := test.GetFreePort()
assert.NoError(t, err)
metricsPort, err := test.GetFreePort()
assert.NoError(t, err)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go s.Run(ctx, port, metricsPort)
defer func() { time.Sleep(3 * time.Second) }()
err = test.WaitForPortListen(fmt.Sprintf("127.0.0.1:%d", port), 10*time.Second)
assert.NoError(t, err)
// Allow server startup
time.Sleep(1 * time.Second)
client := http.Client{}
url := fmt.Sprintf("http://127.0.0.1:%d/test.html", port)
req, err := http.NewRequest("GET", url, nil)
assert.NoError(t, err)
resp, err := client.Do(req)
assert.NoError(t, err)
assert.Equal(t, "sameorigin", resp.Header.Get("X-Frame-Options"))
}
// Test custom policy
{
s := fakeServer()
s.XFrameOptions = "deny"
cancelInformer := test.StartInformer(s.projInformer)
defer cancelInformer()
port, err := test.GetFreePort()
assert.NoError(t, err)
metricsPort, err := test.GetFreePort()
assert.NoError(t, err)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go s.Run(ctx, port, metricsPort)
defer func() { time.Sleep(3 * time.Second) }()
err = test.WaitForPortListen(fmt.Sprintf("127.0.0.1:%d", port), 10*time.Second)
assert.NoError(t, err)
// Allow server startup
time.Sleep(1 * time.Second)
client := http.Client{}
url := fmt.Sprintf("http://127.0.0.1:%d/test.html", port)
req, err := http.NewRequest("GET", url, nil)
assert.NoError(t, err)
resp, err := client.Do(req)
assert.NoError(t, err)
assert.Equal(t, "deny", resp.Header.Get("X-Frame-Options"))
}
// Test disabled
{
s := fakeServer()
s.XFrameOptions = ""
cancelInformer := test.StartInformer(s.projInformer)
defer cancelInformer()
port, err := test.GetFreePort()
assert.NoError(t, err)
metricsPort, err := test.GetFreePort()
assert.NoError(t, err)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go s.Run(ctx, port, metricsPort)
defer func() { time.Sleep(3 * time.Second) }()
err = test.WaitForPortListen(fmt.Sprintf("127.0.0.1:%d", port), 10*time.Second)
assert.NoError(t, err)
// Allow server startup
time.Sleep(1 * time.Second)
client := http.Client{}
url := fmt.Sprintf("http://127.0.0.1:%d/test.html", port)
req, err := http.NewRequest("GET", url, nil)
assert.NoError(t, err)
resp, err := client.Do(req)
assert.NoError(t, err)
assert.Empty(t, resp.Header.Get("X-Frame-Options"))
}
}

View file

@ -3,10 +3,7 @@ package server
import (
"context"
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
@ -19,7 +16,6 @@ import (
"github.com/argoproj/argo-cd/common"
"github.com/argoproj/argo-cd/pkg/apiclient"
applicationpkg "github.com/argoproj/argo-cd/pkg/apiclient/application"
"github.com/argoproj/argo-cd/pkg/apiclient/session"
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
apps "github.com/argoproj/argo-cd/pkg/client/clientset/versioned/fake"
@ -381,75 +377,6 @@ func TestCertsAreNotGeneratedInInsecureMode(t *testing.T) {
assert.Nil(t, s.settings.Certificate)
}
func TestUserAgent(t *testing.T) {
s := fakeServer()
cancelInformer := test.StartInformer(s.projInformer)
defer cancelInformer()
port, err := test.GetFreePort()
assert.NoError(t, err)
metricsPort, err := test.GetFreePort()
assert.NoError(t, err)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go s.Run(ctx, port, metricsPort)
defer func() { time.Sleep(3 * time.Second) }()
err = test.WaitForPortListen(fmt.Sprintf("127.0.0.1:%d", port), 10*time.Second)
assert.NoError(t, err)
type testData struct {
userAgent string
errorMsg string
}
currentVersionBytes, err := ioutil.ReadFile("../VERSION")
assert.NoError(t, err)
currentVersion := strings.TrimSpace(string(currentVersionBytes))
var tests = []testData{
{
// Reject out-of-date user-agent
userAgent: fmt.Sprintf("%s/0.10.0", common.ArgoCDUserAgentName),
errorMsg: "unsatisfied client version constraint",
},
{
// Accept up-to-date user-agent
userAgent: fmt.Sprintf("%s/%s", common.ArgoCDUserAgentName, currentVersion),
},
{
// Accept up-to-date pre-release user-agent
userAgent: fmt.Sprintf("%s/%s-rc1", common.ArgoCDUserAgentName, currentVersion),
},
{
// Reject legacy client
// NOTE: after we update the grpc-go client past 1.15.0, this test will break and should be deleted
userAgent: " ", // need a space here since the apiclient will set the default user-agent if empty
errorMsg: "unsatisfied client version constraint",
},
{
// Permit custom clients
userAgent: "foo/1.2.3",
},
}
for _, test := range tests {
opts := apiclient.ClientOptions{
ServerAddr: fmt.Sprintf("localhost:%d", port),
PlainText: true,
UserAgent: test.userAgent,
}
clnt, err := apiclient.NewClient(&opts)
assert.NoError(t, err)
conn, appClnt := clnt.NewApplicationClientOrDie()
_, err = appClnt.List(ctx, &applicationpkg.ApplicationQuery{})
if test.errorMsg != "" {
assert.Error(t, err)
assert.Regexp(t, test.errorMsg, err.Error())
} else {
assert.NoError(t, err)
}
_ = conn.Close()
}
}
func TestAuthenticate(t *testing.T) {
type testData struct {
test string
@ -507,97 +434,6 @@ func TestAuthenticate(t *testing.T) {
}
}
func Test_StaticHeaders(t *testing.T) {
// Test default policy "sameorigin"
{
s := fakeServer()
cancelInformer := test.StartInformer(s.projInformer)
defer cancelInformer()
port, err := test.GetFreePort()
assert.NoError(t, err)
metricsPort, err := test.GetFreePort()
assert.NoError(t, err)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go s.Run(ctx, port, metricsPort)
defer func() { time.Sleep(3 * time.Second) }()
err = test.WaitForPortListen(fmt.Sprintf("127.0.0.1:%d", port), 10*time.Second)
assert.NoError(t, err)
// Allow server startup
time.Sleep(1 * time.Second)
client := http.Client{}
url := fmt.Sprintf("http://127.0.0.1:%d/test.html", port)
req, err := http.NewRequest("GET", url, nil)
assert.NoError(t, err)
resp, err := client.Do(req)
assert.NoError(t, err)
assert.Equal(t, "sameorigin", resp.Header.Get("X-Frame-Options"))
}
// Test custom policy
{
s := fakeServer()
s.XFrameOptions = "deny"
cancelInformer := test.StartInformer(s.projInformer)
defer cancelInformer()
port, err := test.GetFreePort()
assert.NoError(t, err)
metricsPort, err := test.GetFreePort()
assert.NoError(t, err)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go s.Run(ctx, port, metricsPort)
defer func() { time.Sleep(3 * time.Second) }()
err = test.WaitForPortListen(fmt.Sprintf("127.0.0.1:%d", port), 10*time.Second)
assert.NoError(t, err)
// Allow server startup
time.Sleep(1 * time.Second)
client := http.Client{}
url := fmt.Sprintf("http://127.0.0.1:%d/test.html", port)
req, err := http.NewRequest("GET", url, nil)
assert.NoError(t, err)
resp, err := client.Do(req)
assert.NoError(t, err)
assert.Equal(t, "deny", resp.Header.Get("X-Frame-Options"))
}
// Test disabled
{
s := fakeServer()
s.XFrameOptions = ""
cancelInformer := test.StartInformer(s.projInformer)
defer cancelInformer()
port, err := test.GetFreePort()
assert.NoError(t, err)
metricsPort, err := test.GetFreePort()
assert.NoError(t, err)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go s.Run(ctx, port, metricsPort)
defer func() { time.Sleep(3 * time.Second) }()
err = test.WaitForPortListen(fmt.Sprintf("127.0.0.1:%d", port), 10*time.Second)
assert.NoError(t, err)
// Allow server startup
time.Sleep(1 * time.Second)
client := http.Client{}
url := fmt.Sprintf("http://127.0.0.1:%d/test.html", port)
req, err := http.NewRequest("GET", url, nil)
assert.NoError(t, err)
resp, err := client.Do(req)
assert.NoError(t, err)
assert.Empty(t, resp.Header.Get("X-Frame-Options"))
}
}
func Test_getToken(t *testing.T) {
token := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
t.Run("Empty", func(t *testing.T) {

View file

@ -0,0 +1,83 @@
// +build !race
package db
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"k8s.io/client-go/kubernetes/fake"
"github.com/argoproj/argo-cd/common"
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/util/settings"
)
func TestWatchClusters_CreateRemoveCluster(t *testing.T) {
// !race:
// Intermittent failure when running TestWatchClusters_LocalClusterModifications with -race, likely due to race condition
// https://github.com/argoproj/argo-cd/issues/4755
kubeclientset := fake.NewSimpleClientset()
settingsManager := settings.NewSettingsManager(context.Background(), kubeclientset, fakeNamespace)
db := NewDB(fakeNamespace, settingsManager, kubeclientset)
runWatchTest(t, db, []func(old *v1alpha1.Cluster, new *v1alpha1.Cluster){
func(old *v1alpha1.Cluster, new *v1alpha1.Cluster) {
assert.Nil(t, old)
assert.Equal(t, new.Server, common.KubernetesInternalAPIServerAddr)
_, err := db.CreateCluster(context.Background(), &v1alpha1.Cluster{
Server: "https://minikube",
Name: "minikube",
})
assert.NoError(t, err)
},
func(old *v1alpha1.Cluster, new *v1alpha1.Cluster) {
assert.Nil(t, old)
assert.Equal(t, new.Server, "https://minikube")
assert.Equal(t, new.Name, "minikube")
assert.NoError(t, db.DeleteCluster(context.Background(), "https://minikube"))
},
func(old *v1alpha1.Cluster, new *v1alpha1.Cluster) {
assert.Nil(t, new)
assert.Equal(t, old.Server, "https://minikube")
},
})
}
func TestWatchClusters_LocalClusterModifications(t *testing.T) {
// !race:
// Intermittent failure when running TestWatchClusters_LocalClusterModifications with -race, likely due to race condition
// https://github.com/argoproj/argo-cd/issues/4755
kubeclientset := fake.NewSimpleClientset()
settingsManager := settings.NewSettingsManager(context.Background(), kubeclientset, fakeNamespace)
db := NewDB(fakeNamespace, settingsManager, kubeclientset)
runWatchTest(t, db, []func(old *v1alpha1.Cluster, new *v1alpha1.Cluster){
func(old *v1alpha1.Cluster, new *v1alpha1.Cluster) {
assert.Nil(t, old)
assert.Equal(t, new.Server, common.KubernetesInternalAPIServerAddr)
_, err := db.CreateCluster(context.Background(), &v1alpha1.Cluster{
Server: common.KubernetesInternalAPIServerAddr,
Name: "some name",
})
assert.NoError(t, err)
},
func(old *v1alpha1.Cluster, new *v1alpha1.Cluster) {
assert.NotNil(t, old)
assert.Equal(t, new.Server, common.KubernetesInternalAPIServerAddr)
assert.Equal(t, new.Name, "some name")
assert.NoError(t, db.DeleteCluster(context.Background(), common.KubernetesInternalAPIServerAddr))
},
func(old *v1alpha1.Cluster, new *v1alpha1.Cluster) {
assert.Equal(t, new.Server, common.KubernetesInternalAPIServerAddr)
assert.Equal(t, new.Name, "in-cluster")
},
})
}

View file

@ -118,64 +118,6 @@ func TestDeleteUnknownCluster(t *testing.T) {
assert.EqualError(t, db.DeleteCluster(context.Background(), "http://unknown"), `rpc error: code = NotFound desc = cluster "http://unknown" not found`)
}
func TestWatchClusters_CreateRemoveCluster(t *testing.T) {
kubeclientset := fake.NewSimpleClientset()
settingsManager := settings.NewSettingsManager(context.Background(), kubeclientset, fakeNamespace)
db := NewDB(fakeNamespace, settingsManager, kubeclientset)
runWatchTest(t, db, []func(old *v1alpha1.Cluster, new *v1alpha1.Cluster){
func(old *v1alpha1.Cluster, new *v1alpha1.Cluster) {
assert.Nil(t, old)
assert.Equal(t, new.Server, common.KubernetesInternalAPIServerAddr)
_, err := db.CreateCluster(context.Background(), &v1alpha1.Cluster{
Server: "https://minikube",
Name: "minikube",
})
assert.NoError(t, err)
},
func(old *v1alpha1.Cluster, new *v1alpha1.Cluster) {
assert.Nil(t, old)
assert.Equal(t, new.Server, "https://minikube")
assert.Equal(t, new.Name, "minikube")
assert.NoError(t, db.DeleteCluster(context.Background(), "https://minikube"))
},
func(old *v1alpha1.Cluster, new *v1alpha1.Cluster) {
assert.Nil(t, new)
assert.Equal(t, old.Server, "https://minikube")
},
})
}
func TestWatchClusters_LocalClusterModifications(t *testing.T) {
kubeclientset := fake.NewSimpleClientset()
settingsManager := settings.NewSettingsManager(context.Background(), kubeclientset, fakeNamespace)
db := NewDB(fakeNamespace, settingsManager, kubeclientset)
runWatchTest(t, db, []func(old *v1alpha1.Cluster, new *v1alpha1.Cluster){
func(old *v1alpha1.Cluster, new *v1alpha1.Cluster) {
assert.Nil(t, old)
assert.Equal(t, new.Server, common.KubernetesInternalAPIServerAddr)
_, err := db.CreateCluster(context.Background(), &v1alpha1.Cluster{
Server: common.KubernetesInternalAPIServerAddr,
Name: "some name",
})
assert.NoError(t, err)
},
func(old *v1alpha1.Cluster, new *v1alpha1.Cluster) {
assert.NotNil(t, old)
assert.Equal(t, new.Server, common.KubernetesInternalAPIServerAddr)
assert.Equal(t, new.Name, "some name")
assert.NoError(t, db.DeleteCluster(context.Background(), common.KubernetesInternalAPIServerAddr))
},
func(old *v1alpha1.Cluster, new *v1alpha1.Cluster) {
assert.Equal(t, new.Server, common.KubernetesInternalAPIServerAddr)
assert.Equal(t, new.Name, "in-cluster")
},
})
}
func runWatchTest(t *testing.T, db ArgoDB, actions []func(old *v1alpha1.Cluster, new *v1alpha1.Cluster)) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

View file

@ -0,0 +1,129 @@
// +build !race
package rbac
import (
"context"
"testing"
"time"
"github.com/stretchr/testify/assert"
apiv1 "k8s.io/api/core/v1"
"k8s.io/client-go/kubernetes/fake"
)
// TestPolicyInformer verifies the informer will get updated with a new configmap
func TestPolicyInformer(t *testing.T) {
// !race:
// A BUNCH of data race warnings thrown by running this test and the next... it's tough to guess to what degree this
// is primarily a casbin issue or a Argo CD RBAC issue... A least one data race is an `rbac.go` with
// itself, a bunch are in casbin. You can see the full list by doing a `go test -race github.com/argoproj/argo-cd/util/rbac`
//
// It couldn't hurt to take a look at this code to decide if Argo CD is properly handling concurrent data
// access here, but in the mean time I have disabled data race testing of this test.
cm := fakeConfigMap()
cm.Data[ConfigMapPolicyCSVKey] = "p, admin, applications, delete, */*, allow"
kubeclientset := fake.NewSimpleClientset(cm)
enf := NewEnforcer(kubeclientset, fakeNamespace, fakeConfigMapName, nil)
ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
defer cancel()
go enf.runInformer(ctx, func(cm *apiv1.ConfigMap) error {
return nil
})
loaded := false
for i := 1; i <= 20; i++ {
if enf.Enforce("admin", "applications", "delete", "foo/bar") {
loaded = true
break
}
time.Sleep(50 * time.Millisecond)
}
assert.True(t, loaded, "Policy update failed to load")
// update the configmap and update policy
delete(cm.Data, ConfigMapPolicyCSVKey)
err := enf.syncUpdate(cm, noOpUpdate)
assert.Nil(t, err)
assert.False(t, enf.Enforce("admin", "applications", "delete", "foo/bar"))
}
// TestResourceActionWildcards verifies the ability to use wildcards in resources and actions
func TestResourceActionWildcards(t *testing.T) {
// !race:
// Same as TestPolicyInformer
kubeclientset := fake.NewSimpleClientset(fakeConfigMap())
enf := NewEnforcer(kubeclientset, fakeNamespace, fakeConfigMapName, nil)
policy := `
p, alice, *, get, foo/obj, allow
p, bob, repositories, *, foo/obj, allow
p, cathy, *, *, foo/obj, allow
p, dave, applications, get, foo/obj, allow
p, dave, applications/*, get, foo/obj, allow
p, eve, *, get, foo/obj, deny
p, mallory, repositories, *, foo/obj, deny
p, mallory, repositories, *, foo/obj, allow
p, mike, *, *, foo/obj, allow
p, mike, *, *, foo/obj, deny
p, trudy, applications, get, foo/obj, allow
p, trudy, applications/*, get, foo/obj, allow
p, trudy, applications/secrets, get, foo/obj, deny
p, danny, applications, get, */obj, allow
p, danny, applications, get, proj1/a*p1, allow
`
_ = enf.SetUserPolicy(policy)
// Verify the resource wildcard
assert.True(t, enf.Enforce("alice", "applications", "get", "foo/obj"))
assert.True(t, enf.Enforce("alice", "applications/resources", "get", "foo/obj"))
assert.False(t, enf.Enforce("alice", "applications/resources", "delete", "foo/obj"))
// Verify action wildcards work
assert.True(t, enf.Enforce("bob", "repositories", "get", "foo/obj"))
assert.True(t, enf.Enforce("bob", "repositories", "delete", "foo/obj"))
assert.False(t, enf.Enforce("bob", "applications", "get", "foo/obj"))
// Verify resource and action wildcards work in conjunction
assert.True(t, enf.Enforce("cathy", "repositories", "get", "foo/obj"))
assert.True(t, enf.Enforce("cathy", "repositories", "delete", "foo/obj"))
assert.True(t, enf.Enforce("cathy", "applications", "get", "foo/obj"))
assert.True(t, enf.Enforce("cathy", "applications/resources", "delete", "foo/obj"))
// Verify wildcards with sub-resources
assert.True(t, enf.Enforce("dave", "applications", "get", "foo/obj"))
assert.True(t, enf.Enforce("dave", "applications/logs", "get", "foo/obj"))
// Verify the resource wildcard
assert.False(t, enf.Enforce("eve", "applications", "get", "foo/obj"))
assert.False(t, enf.Enforce("eve", "applications/resources", "get", "foo/obj"))
assert.False(t, enf.Enforce("eve", "applications/resources", "delete", "foo/obj"))
// Verify action wildcards work
assert.False(t, enf.Enforce("mallory", "repositories", "get", "foo/obj"))
assert.False(t, enf.Enforce("mallory", "repositories", "delete", "foo/obj"))
assert.False(t, enf.Enforce("mallory", "applications", "get", "foo/obj"))
// Verify resource and action wildcards work in conjunction
assert.False(t, enf.Enforce("mike", "repositories", "get", "foo/obj"))
assert.False(t, enf.Enforce("mike", "repositories", "delete", "foo/obj"))
assert.False(t, enf.Enforce("mike", "applications", "get", "foo/obj"))
assert.False(t, enf.Enforce("mike", "applications/resources", "delete", "foo/obj"))
// Verify wildcards with sub-resources
assert.True(t, enf.Enforce("trudy", "applications", "get", "foo/obj"))
assert.True(t, enf.Enforce("trudy", "applications/logs", "get", "foo/obj"))
assert.False(t, enf.Enforce("trudy", "applications/secrets", "get", "foo/obj"))
// Verify trailing wildcards don't grant full access
assert.True(t, enf.Enforce("danny", "applications", "get", "foo/obj"))
assert.True(t, enf.Enforce("danny", "applications", "get", "bar/obj"))
assert.False(t, enf.Enforce("danny", "applications", "get", "foo/bar"))
assert.True(t, enf.Enforce("danny", "applications", "get", "proj1/app1"))
assert.False(t, enf.Enforce("danny", "applications", "get", "proj1/app2"))
}

View file

@ -79,109 +79,6 @@ func TestBuiltinPolicyEnforcer(t *testing.T) {
}
}
// TestPolicyInformer verifies the informer will get updated with a new configmap
func TestPolicyInformer(t *testing.T) {
cm := fakeConfigMap()
cm.Data[ConfigMapPolicyCSVKey] = "p, admin, applications, delete, */*, allow"
kubeclientset := fake.NewSimpleClientset(cm)
enf := NewEnforcer(kubeclientset, fakeNamespace, fakeConfigMapName, nil)
ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
defer cancel()
go enf.runInformer(ctx, func(cm *apiv1.ConfigMap) error {
return nil
})
loaded := false
for i := 1; i <= 20; i++ {
if enf.Enforce("admin", "applications", "delete", "foo/bar") {
loaded = true
break
}
time.Sleep(50 * time.Millisecond)
}
assert.True(t, loaded, "Policy update failed to load")
// update the configmap and update policy
delete(cm.Data, ConfigMapPolicyCSVKey)
err := enf.syncUpdate(cm, noOpUpdate)
assert.Nil(t, err)
assert.False(t, enf.Enforce("admin", "applications", "delete", "foo/bar"))
}
// TestResourceActionWildcards verifies the ability to use wildcards in resources and actions
func TestResourceActionWildcards(t *testing.T) {
kubeclientset := fake.NewSimpleClientset(fakeConfigMap())
enf := NewEnforcer(kubeclientset, fakeNamespace, fakeConfigMapName, nil)
policy := `
p, alice, *, get, foo/obj, allow
p, bob, repositories, *, foo/obj, allow
p, cathy, *, *, foo/obj, allow
p, dave, applications, get, foo/obj, allow
p, dave, applications/*, get, foo/obj, allow
p, eve, *, get, foo/obj, deny
p, mallory, repositories, *, foo/obj, deny
p, mallory, repositories, *, foo/obj, allow
p, mike, *, *, foo/obj, allow
p, mike, *, *, foo/obj, deny
p, trudy, applications, get, foo/obj, allow
p, trudy, applications/*, get, foo/obj, allow
p, trudy, applications/secrets, get, foo/obj, deny
p, danny, applications, get, */obj, allow
p, danny, applications, get, proj1/a*p1, allow
`
_ = enf.SetUserPolicy(policy)
// Verify the resource wildcard
assert.True(t, enf.Enforce("alice", "applications", "get", "foo/obj"))
assert.True(t, enf.Enforce("alice", "applications/resources", "get", "foo/obj"))
assert.False(t, enf.Enforce("alice", "applications/resources", "delete", "foo/obj"))
// Verify action wildcards work
assert.True(t, enf.Enforce("bob", "repositories", "get", "foo/obj"))
assert.True(t, enf.Enforce("bob", "repositories", "delete", "foo/obj"))
assert.False(t, enf.Enforce("bob", "applications", "get", "foo/obj"))
// Verify resource and action wildcards work in conjunction
assert.True(t, enf.Enforce("cathy", "repositories", "get", "foo/obj"))
assert.True(t, enf.Enforce("cathy", "repositories", "delete", "foo/obj"))
assert.True(t, enf.Enforce("cathy", "applications", "get", "foo/obj"))
assert.True(t, enf.Enforce("cathy", "applications/resources", "delete", "foo/obj"))
// Verify wildcards with sub-resources
assert.True(t, enf.Enforce("dave", "applications", "get", "foo/obj"))
assert.True(t, enf.Enforce("dave", "applications/logs", "get", "foo/obj"))
// Verify the resource wildcard
assert.False(t, enf.Enforce("eve", "applications", "get", "foo/obj"))
assert.False(t, enf.Enforce("eve", "applications/resources", "get", "foo/obj"))
assert.False(t, enf.Enforce("eve", "applications/resources", "delete", "foo/obj"))
// Verify action wildcards work
assert.False(t, enf.Enforce("mallory", "repositories", "get", "foo/obj"))
assert.False(t, enf.Enforce("mallory", "repositories", "delete", "foo/obj"))
assert.False(t, enf.Enforce("mallory", "applications", "get", "foo/obj"))
// Verify resource and action wildcards work in conjunction
assert.False(t, enf.Enforce("mike", "repositories", "get", "foo/obj"))
assert.False(t, enf.Enforce("mike", "repositories", "delete", "foo/obj"))
assert.False(t, enf.Enforce("mike", "applications", "get", "foo/obj"))
assert.False(t, enf.Enforce("mike", "applications/resources", "delete", "foo/obj"))
// Verify wildcards with sub-resources
assert.True(t, enf.Enforce("trudy", "applications", "get", "foo/obj"))
assert.True(t, enf.Enforce("trudy", "applications/logs", "get", "foo/obj"))
assert.False(t, enf.Enforce("trudy", "applications/secrets", "get", "foo/obj"))
// Verify trailing wildcards don't grant full access
assert.True(t, enf.Enforce("danny", "applications", "get", "foo/obj"))
assert.True(t, enf.Enforce("danny", "applications", "get", "bar/obj"))
assert.False(t, enf.Enforce("danny", "applications", "get", "foo/bar"))
assert.True(t, enf.Enforce("danny", "applications", "get", "proj1/app1"))
assert.False(t, enf.Enforce("danny", "applications", "get", "proj1/app2"))
}
// TestProjectIsolationEnforcement verifies the ability to create Project specific policies
func TestProjectIsolationEnforcement(t *testing.T) {
kubeclientset := fake.NewSimpleClientset(fakeConfigMap())

View file

@ -0,0 +1,40 @@
// +build !race
package session
import (
"context"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/argoproj/argo-cd/util/settings"
)
func TestRandomPasswordVerificationDelay(t *testing.T) {
// !race:
//`SessionManager.VerifyUsernamePassword` uses bcrypt to prevent against time-based attacks
// and verify the hashed password; however this is a CPU intensive algorithm that is made
// significantly slower due to data race detection being enabled, which breaks through
// the maximum time limit required by `TestRandomPasswordVerificationDelay`.
var sleptFor time.Duration
settingsMgr := settings.NewSettingsManager(context.Background(), getKubeClient("password", true), "argocd")
mgr := newSessionManager(settingsMgr, NewInMemoryUserStateStorage())
mgr.verificationDelayNoiseEnabled = true
mgr.sleep = func(d time.Duration) {
sleptFor = d
}
for i := 0; i < 10; i++ {
sleptFor = 0
start := time.Now()
if !assert.NoError(t, mgr.VerifyUsernamePassword("admin", "password")) {
return
}
totalDuration := time.Since(start) + sleptFor
assert.GreaterOrEqual(t, totalDuration.Nanoseconds(), verificationDelayNoiseMin.Nanoseconds())
assert.LessOrEqual(t, totalDuration.Nanoseconds(), verificationDelayNoiseMax.Nanoseconds())
}
}

View file

@ -233,26 +233,6 @@ func TestCacheValueGetters(t *testing.T) {
}
func TestRandomPasswordVerificationDelay(t *testing.T) {
var sleptFor time.Duration
settingsMgr := settings.NewSettingsManager(context.Background(), getKubeClient("password", true), "argocd")
mgr := newSessionManager(settingsMgr, NewInMemoryUserStateStorage())
mgr.verificationDelayNoiseEnabled = true
mgr.sleep = func(d time.Duration) {
sleptFor = d
}
for i := 0; i < 10; i++ {
sleptFor = 0
start := time.Now()
if !assert.NoError(t, mgr.VerifyUsernamePassword("admin", "password")) {
return
}
totalDuration := time.Since(start) + sleptFor
assert.GreaterOrEqual(t, totalDuration.Nanoseconds(), verificationDelayNoiseMin.Nanoseconds())
assert.LessOrEqual(t, totalDuration.Nanoseconds(), verificationDelayNoiseMax.Nanoseconds())
}
}
func TestLoginRateLimiter(t *testing.T) {
settingsMgr := settings.NewSettingsManager(context.Background(), getKubeClient("password", true), "argocd")
storage := NewInMemoryUserStateStorage()