mirror of
https://github.com/argoproj/argo-cd.git
synced 2026-03-03 06:58:46 +01:00
Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dbdfc71270 | ||
|
|
31473491f5 | ||
|
|
016c2f25cd | ||
|
|
31e6e28ca4 | ||
|
|
f07088281b | ||
|
|
bf77b09b7b | ||
|
|
e982e0b80e | ||
|
|
3a468c6862 | ||
|
|
383f2a288b | ||
|
|
e006908fe9 | ||
|
|
2bc94af7bd | ||
|
|
b9a32bb86e | ||
|
|
356e33ac29 | ||
|
|
9ffef110da | ||
|
|
af721bbb63 | ||
|
|
91d249ec84 | ||
|
|
004ca26b9e | ||
|
|
2a17ca57ec | ||
|
|
89495d72df | ||
|
|
885bb57ae8 | ||
|
|
d6d7b1452d | ||
|
|
490c78b75f | ||
|
|
092e55e279 | ||
|
|
fe526dd33c | ||
|
|
cade0e970d | ||
|
|
e58eaaf36f | ||
|
|
0c4e249922 | ||
|
|
8ea6650dd7 |
@@ -1,4 +1,4 @@
|
||||
ARG BASE_IMAGE=docker.io/library/ubuntu:22.04@sha256:ac58ff7fe25edc58bdf0067ca99df00014dbd032e2246d30a722fa348fd799a5
|
||||
ARG BASE_IMAGE=docker.io/library/ubuntu:22.04@sha256:0bced47fffa3361afa981854fcabcd4577cd43cebbb808cea2b1f33a3dd7f508
|
||||
####################################################################################################
|
||||
# Builder image
|
||||
# Initial stage which pulls prepares build dependencies and CLI tooling we need for our final image
|
||||
|
||||
@@ -295,7 +295,6 @@ func (r *ApplicationSetReconciler) Reconcile(ctx context.Context, req ctrl.Reque
|
||||
}
|
||||
|
||||
requeueAfter := r.getMinRequeueAfter(&applicationSetInfo)
|
||||
logCtx.WithField("requeueAfter", requeueAfter).Info("end reconcile")
|
||||
|
||||
if len(validateErrors) == 0 {
|
||||
if err := r.setApplicationSetStatusCondition(ctx,
|
||||
@@ -309,8 +308,13 @@ func (r *ApplicationSetReconciler) Reconcile(ctx context.Context, req ctrl.Reque
|
||||
); err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
} else if requeueAfter == time.Duration(0) {
|
||||
// Ensure that the request is requeued if there are validation errors.
|
||||
requeueAfter = ReconcileRequeueOnValidationError
|
||||
}
|
||||
|
||||
logCtx.WithField("requeueAfter", requeueAfter).Info("end reconcile")
|
||||
|
||||
return ctrl.Result{
|
||||
RequeueAfter: requeueAfter,
|
||||
}, nil
|
||||
|
||||
@@ -2003,7 +2003,7 @@ func TestReconcilerValidationErrorBehaviour(t *testing.T) {
|
||||
// Verify that on validation error, no error is returned, but the object is requeued
|
||||
res, err := r.Reconcile(context.Background(), req)
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, res.RequeueAfter == 0)
|
||||
assert.True(t, res.RequeueAfter == ReconcileRequeueOnValidationError)
|
||||
|
||||
var app v1alpha1.Application
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package scm_provider
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/applicationset/utils"
|
||||
bitbucketv1 "github.com/gfleury/go-bitbucket-v1"
|
||||
@@ -183,8 +184,9 @@ func (b *BitbucketServerProvider) listBranches(repo *Repository) ([]bitbucketv1.
|
||||
|
||||
func (b *BitbucketServerProvider) getDefaultBranch(org string, repo string) (*bitbucketv1.Branch, error) {
|
||||
response, err := b.client.DefaultApi.GetDefaultBranch(org, repo)
|
||||
if response != nil && response.StatusCode == 404 {
|
||||
// There's no default branch i.e. empty repo, not an error
|
||||
// The API will return 404 if a default branch is set but doesn't exist. In case the repo is empty and default branch is unset,
|
||||
// we will get an EOF and a nil response.
|
||||
if (response != nil && response.StatusCode == 404) || (response == nil && err == io.EOF) {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
|
||||
@@ -365,6 +365,28 @@ func TestGetBranchesMissingDefault(t *testing.T) {
|
||||
assert.Empty(t, repos)
|
||||
}
|
||||
|
||||
func TestGetBranchesEmptyRepo(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assert.Empty(t, r.Header.Get("Authorization"))
|
||||
switch r.RequestURI {
|
||||
case "/rest/api/1.0/projects/PROJECT/repos/REPO/branches/default":
|
||||
return
|
||||
}
|
||||
}))
|
||||
defer ts.Close()
|
||||
provider, err := NewBitbucketServerProviderNoAuth(context.Background(), ts.URL, "PROJECT", false)
|
||||
assert.NoError(t, err)
|
||||
repos, err := provider.GetBranches(context.Background(), &Repository{
|
||||
Organization: "PROJECT",
|
||||
Repository: "REPO",
|
||||
URL: "ssh://git@mycompany.bitbucket.org/PROJECT/REPO.git",
|
||||
Labels: []string{},
|
||||
RepositoryId: 1,
|
||||
})
|
||||
assert.Empty(t, repos)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestGetBranchesErrorDefaultBranch(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assert.Empty(t, r.Header.Get("Authorization"))
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
|
||||
"github.com/Masterminds/sprig/v3"
|
||||
"github.com/valyala/fasttemplate"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
@@ -51,6 +52,22 @@ func copyUnexported(copy, original reflect.Value) {
|
||||
copyValueIntoUnexported(copy, unexported)
|
||||
}
|
||||
|
||||
func IsJSONStr(str string) bool {
|
||||
str = strings.TrimSpace(str)
|
||||
return len(str) > 0 && str[0] == '{'
|
||||
}
|
||||
|
||||
func ConvertYAMLToJSON(str string) (string, error) {
|
||||
if !IsJSONStr(str) {
|
||||
jsonStr, err := yaml.YAMLToJSON([]byte(str))
|
||||
if err != nil {
|
||||
return str, err
|
||||
}
|
||||
return string(jsonStr), nil
|
||||
}
|
||||
return str, nil
|
||||
}
|
||||
|
||||
// This function is in charge of searching all String fields of the object recursively and apply templating
|
||||
// thanks to https://gist.github.com/randallmlough/1fd78ec8a1034916ca52281e3b886dc7
|
||||
func (r *Render) deeplyReplace(copy, original reflect.Value, replaceMap map[string]interface{}, useGoTemplate bool, goTemplateOptions []string) error {
|
||||
@@ -86,11 +103,18 @@ func (r *Render) deeplyReplace(copy, original reflect.Value, replaceMap map[stri
|
||||
originalValue := original.Elem()
|
||||
// Create a new object. Now new gives us a pointer, but we want the value it
|
||||
// points to, so we have to call Elem() to unwrap it
|
||||
copyValue := reflect.New(originalValue.Type()).Elem()
|
||||
if err := r.deeplyReplace(copyValue, originalValue, replaceMap, useGoTemplate, goTemplateOptions); err != nil {
|
||||
return err
|
||||
|
||||
if originalValue.IsValid() {
|
||||
reflectType := originalValue.Type()
|
||||
|
||||
reflectValue := reflect.New(reflectType)
|
||||
|
||||
copyValue := reflectValue.Elem()
|
||||
if err := r.deeplyReplace(copyValue, originalValue, replaceMap, useGoTemplate, goTemplateOptions); err != nil {
|
||||
return err
|
||||
}
|
||||
copy.Set(copyValue)
|
||||
}
|
||||
copy.Set(copyValue)
|
||||
|
||||
// If it is a struct we translate each field
|
||||
case reflect.Struct:
|
||||
@@ -99,10 +123,14 @@ func (r *Render) deeplyReplace(copy, original reflect.Value, replaceMap map[stri
|
||||
// specific case time
|
||||
if currentType == "time.Time" {
|
||||
copy.Field(i).Set(original.Field(i))
|
||||
} else if currentType == "Raw.k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" {
|
||||
} else if currentType == "Raw.k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" || currentType == "Raw.k8s.io/apimachinery/pkg/runtime" {
|
||||
var unmarshaled interface{}
|
||||
originalBytes := original.Field(i).Bytes()
|
||||
err := json.Unmarshal(originalBytes, &unmarshaled)
|
||||
convertedToJson, err := ConvertYAMLToJSON(string(originalBytes))
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while converting template to json %q: %w", convertedToJson, err)
|
||||
}
|
||||
err = json.Unmarshal([]byte(convertedToJson), &unmarshaled)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to unmarshal JSON field: %w", err)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package utils
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
@@ -198,6 +199,113 @@ func TestRenderTemplateParams(t *testing.T) {
|
||||
|
||||
}
|
||||
|
||||
func TestRenderHelmValuesObjectJson(t *testing.T) {
|
||||
|
||||
params := map[string]interface{}{
|
||||
"test": "Hello world",
|
||||
}
|
||||
|
||||
application := &argoappsv1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Annotations: map[string]string{"annotation-key": "annotation-value", "annotation-key2": "annotation-value2"},
|
||||
Labels: map[string]string{"label-key": "label-value", "label-key2": "label-value2"},
|
||||
CreationTimestamp: metav1.NewTime(time.Now()),
|
||||
UID: types.UID("d546da12-06b7-4f9a-8ea2-3adb16a20e2b"),
|
||||
Name: "application-one",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: argoappsv1.ApplicationSpec{
|
||||
Source: &argoappsv1.ApplicationSource{
|
||||
Path: "",
|
||||
RepoURL: "",
|
||||
TargetRevision: "",
|
||||
Chart: "",
|
||||
Helm: &argoappsv1.ApplicationSourceHelm{
|
||||
ValuesObject: &runtime.RawExtension{
|
||||
Raw: []byte(`{
|
||||
"some": {
|
||||
"string": "{{.test}}"
|
||||
}
|
||||
}`),
|
||||
},
|
||||
},
|
||||
},
|
||||
Destination: argoappsv1.ApplicationDestination{
|
||||
Server: "",
|
||||
Namespace: "",
|
||||
Name: "",
|
||||
},
|
||||
Project: "",
|
||||
},
|
||||
}
|
||||
|
||||
// Render the cloned application, into a new application
|
||||
render := Render{}
|
||||
newApplication, err := render.RenderTemplateParams(application, nil, params, true, []string{})
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, newApplication)
|
||||
|
||||
var unmarshaled interface{}
|
||||
err = json.Unmarshal(newApplication.Spec.Source.Helm.ValuesObject.Raw, &unmarshaled)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, unmarshaled.(map[string]interface{})["some"].(map[string]interface{})["string"], "Hello world")
|
||||
|
||||
}
|
||||
|
||||
func TestRenderHelmValuesObjectYaml(t *testing.T) {
|
||||
|
||||
params := map[string]interface{}{
|
||||
"test": "Hello world",
|
||||
}
|
||||
|
||||
application := &argoappsv1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Annotations: map[string]string{"annotation-key": "annotation-value", "annotation-key2": "annotation-value2"},
|
||||
Labels: map[string]string{"label-key": "label-value", "label-key2": "label-value2"},
|
||||
CreationTimestamp: metav1.NewTime(time.Now()),
|
||||
UID: types.UID("d546da12-06b7-4f9a-8ea2-3adb16a20e2b"),
|
||||
Name: "application-one",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: argoappsv1.ApplicationSpec{
|
||||
Source: &argoappsv1.ApplicationSource{
|
||||
Path: "",
|
||||
RepoURL: "",
|
||||
TargetRevision: "",
|
||||
Chart: "",
|
||||
Helm: &argoappsv1.ApplicationSourceHelm{
|
||||
ValuesObject: &runtime.RawExtension{
|
||||
Raw: []byte(`some:
|
||||
string: "{{.test}}"`),
|
||||
},
|
||||
},
|
||||
},
|
||||
Destination: argoappsv1.ApplicationDestination{
|
||||
Server: "",
|
||||
Namespace: "",
|
||||
Name: "",
|
||||
},
|
||||
Project: "",
|
||||
},
|
||||
}
|
||||
|
||||
// Render the cloned application, into a new application
|
||||
render := Render{}
|
||||
newApplication, err := render.RenderTemplateParams(application, nil, params, true, []string{})
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, newApplication)
|
||||
|
||||
var unmarshaled interface{}
|
||||
err = json.Unmarshal(newApplication.Spec.Source.Helm.ValuesObject.Raw, &unmarshaled)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, unmarshaled.(map[string]interface{})["some"].(map[string]interface{})["string"], "Hello world")
|
||||
|
||||
}
|
||||
|
||||
func TestRenderTemplateParamsGoTemplate(t *testing.T) {
|
||||
|
||||
// Believe it or not, this is actually less complex than the equivalent solution using reflection
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
|
||||
grpc_retry "github.com/grpc-ecosystem/go-grpc-middleware/retry"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
|
||||
@@ -47,8 +46,8 @@ func NewConnection(address string) (*grpc.ClientConn, error) {
|
||||
grpc.WithStreamInterceptor(grpc_retry.StreamClientInterceptor(retryOpts...)),
|
||||
grpc.WithUnaryInterceptor(grpc_middleware.ChainUnaryClient(unaryInterceptors...)),
|
||||
grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(MaxGRPCMessageSize), grpc.MaxCallSendMsgSize(MaxGRPCMessageSize)),
|
||||
grpc.WithUnaryInterceptor(otelgrpc.UnaryClientInterceptor()),
|
||||
grpc.WithStreamInterceptor(otelgrpc.StreamClientInterceptor()),
|
||||
grpc.WithUnaryInterceptor(grpc_util.OTELUnaryClientInterceptor()),
|
||||
grpc.WithStreamInterceptor(grpc_util.OTELStreamClientInterceptor()),
|
||||
}
|
||||
|
||||
dialOpts = append(dialOpts, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||
|
||||
@@ -97,6 +97,14 @@ func runCommand(ctx context.Context, command Command, path string, env []string)
|
||||
<-ctx.Done()
|
||||
// Kill by group ID to make sure child processes are killed. The - tells `kill` that it's a group ID.
|
||||
// Since we didn't set Pgid in SysProcAttr, the group ID is the same as the process ID. https://pkg.go.dev/syscall#SysProcAttr
|
||||
|
||||
// Sending a TERM signal first to allow any potential cleanup if needed, and then sending a KILL signal
|
||||
_ = sysCallTerm(-cmd.Process.Pid)
|
||||
|
||||
// modify cleanup timeout to allow process to cleanup
|
||||
cleanupTimeout := 5 * time.Second
|
||||
time.Sleep(cleanupTimeout)
|
||||
|
||||
_ = sysCallKill(-cmd.Process.Pid)
|
||||
}()
|
||||
|
||||
|
||||
@@ -369,6 +369,28 @@ func TestRunCommandEmptyCommand(t *testing.T) {
|
||||
assert.ErrorContains(t, err, "Command is empty")
|
||||
}
|
||||
|
||||
// TestRunCommandContextTimeoutWithGracefulTermination makes sure that the process is given enough time to cleanup before sending SIGKILL.
|
||||
func TestRunCommandContextTimeoutWithCleanup(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 900*time.Millisecond)
|
||||
defer cancel()
|
||||
|
||||
// Use a subshell so there's a child command.
|
||||
// This command sleeps for 4 seconds which is currently less than the 5 second delay between SIGTERM and SIGKILL signal and then exits successfully.
|
||||
command := Command{
|
||||
Command: []string{"sh", "-c"},
|
||||
Args: []string{`(trap 'echo "cleanup completed"; exit' TERM; sleep 4)`},
|
||||
}
|
||||
|
||||
before := time.Now()
|
||||
output, err := runCommand(ctx, command, "", []string{})
|
||||
after := time.Now()
|
||||
|
||||
assert.Error(t, err) // The command should time out, causing an error.
|
||||
assert.Less(t, after.Sub(before), 1*time.Second)
|
||||
// The command should still have completed the cleanup after termination.
|
||||
assert.Contains(t, output, "cleanup completed")
|
||||
}
|
||||
|
||||
func Test_getParametersAnnouncement_empty_command(t *testing.T) {
|
||||
staticYAML := `
|
||||
- name: static-a
|
||||
|
||||
@@ -14,3 +14,7 @@ func newSysProcAttr(setpgid bool) *syscall.SysProcAttr {
|
||||
func sysCallKill(pid int) error {
|
||||
return syscall.Kill(pid, syscall.SIGKILL)
|
||||
}
|
||||
|
||||
func sysCallTerm(pid int) error {
|
||||
return syscall.Kill(pid, syscall.SIGTERM)
|
||||
}
|
||||
|
||||
@@ -14,3 +14,7 @@ func newSysProcAttr(setpgid bool) *syscall.SysProcAttr {
|
||||
func sysCallKill(pid int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func sysCallTerm(pid int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -214,6 +214,9 @@ spec:
|
||||
kind: Deployment
|
||||
jsonPointers:
|
||||
- /spec/replicas
|
||||
- kind: ConfigMap
|
||||
jqPathExpressions:
|
||||
- '.data["config.yaml"].auth'
|
||||
# for the specified managedFields managers
|
||||
- group: "*"
|
||||
kind: "*"
|
||||
|
||||
@@ -157,7 +157,7 @@ Or, a shorter way (using [path.Match](https://golang.org/pkg/path/#Match) syntax
|
||||
|
||||
```yaml
|
||||
- path: /d/*
|
||||
- path: /d/[f|g]
|
||||
- path: /d/[fg]
|
||||
exclude: true
|
||||
```
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ As an experimental feature, progressive syncs must be explicitly enabled, in one
|
||||
|
||||
1. Pass `--enable-progressive-syncs` to the ApplicationSet controller args.
|
||||
1. Set `ARGOCD_APPLICATIONSET_CONTROLLER_ENABLE_PROGRESSIVE_SYNCS=true` in the ApplicationSet controller environment variables.
|
||||
1. Set `applicationsetcontroller.enable.progressive.syncs: true` in the Argo CD ConfigMap.
|
||||
1. Set `applicationsetcontroller.enable.progressive.syncs: true` in the Argo CD `argocd-cmd-params-cm` ConfigMap.
|
||||
|
||||
## Strategies
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ All resources, including `Application` and `AppProject` specs, have to be instal
|
||||
|-----------------------------------------------------------------------|------------------------------------------------------------------------------------|-----------|--------------------------------------------------------------------------------------|
|
||||
| [`argocd-cm.yaml`](argocd-cm-yaml.md) | argocd-cm | ConfigMap | General Argo CD configuration |
|
||||
| [`argocd-repositories.yaml`](argocd-repositories-yaml.md) | my-private-repo / istio-helm-repo / private-helm-repo / private-repo | Secrets | Sample repository connection details |
|
||||
| [`argocd-repo-creds.yaml`](argocd-repo-creds.yaml) | argoproj-https-creds / argoproj-ssh-creds / github-creds / github-enterprise-creds | Secrets | Sample repository credential templates |
|
||||
| [`argocd-repo-creds.yaml`](argocd-repo-creds-yaml.md) | argoproj-https-creds / argoproj-ssh-creds / github-creds / github-enterprise-creds | Secrets | Sample repository credential templates |
|
||||
| [`argocd-cmd-params-cm.yaml`](argocd-cmd-params-cm-yaml.md) | argocd-cmd-params-cm | ConfigMap | Argo CD env variables configuration |
|
||||
| [`argocd-secret.yaml`](argocd-secret-yaml.md) | argocd-secret | Secret | User Passwords, Certificates (deprecated), Signing Key, Dex secrets, Webhook secrets |
|
||||
| [`argocd-rbac-cm.yaml`](argocd-rbac-cm-yaml.md) | argocd-rbac-cm | ConfigMap | RBAC Configuration |
|
||||
|
||||
@@ -86,3 +86,13 @@ spec:
|
||||
clusters:
|
||||
- in-cluster
|
||||
- cluster1
|
||||
|
||||
# By default, apps may sync to any cluster specified under the `destinations` field, even if they are not
|
||||
# scoped to this project. Set the following field to `true` to restrict apps in this cluster to only clusters
|
||||
# scoped to this project.
|
||||
permitOnlyProjectScopedClusters: false
|
||||
|
||||
# When using Applications-in-any-namespace, this field determines which namespaces this AppProject permits
|
||||
# Applications to reside in. Details: https://argo-cd.readthedocs.io/en/stable/operator-manual/app-any-namespace/
|
||||
sourceNamespaces:
|
||||
- "argocd-apps-*"
|
||||
|
||||
26
docs/user-guide/annotations-and-labels.md
Normal file
26
docs/user-guide/annotations-and-labels.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# Annotations and Labels used by Argo CD
|
||||
|
||||
## Annotations
|
||||
|
||||
| Annotation key | Target resource(es) | Possible values | Description |
|
||||
|--------------------------------------------|---------------------|---------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| argocd.argoproj.io/application-set-refresh | ApplicationSet | `"true"` | Added when an ApplicationSet is requested to be refreshed by a webhook. The ApplicationSet controller will remove this annotation at the end of reconciliation. |
|
||||
| argocd.argoproj.io/compare-options | any | [see compare options docs](compare-options.md) | Configures how an app's current state is compared to its desired state. |
|
||||
| argocd.argoproj.io/hook | any | [see resource hooks docs](resource_hooks.md) | Used to configure [resource hooks](resource_hooks.md). |
|
||||
| argocd.argoproj.io/hook-delete-policy | any | [see resource hooks docs](resource_hooks.md#hook-deletion-policies) | Used to set a [resource hook's deletion policy](resource_hooks.md#hook-deletion-policies). |
|
||||
| argocd.argoproj.io/manifest-generate-paths | Application | [see scaling docs](../operator-manual/high_availability.md#webhook-and-manifest-paths-annotation) | Used to avoid unnecessary Application refreshes, especially in mono-repos. |
|
||||
| argocd.argoproj.io/refresh | Application | `normal`, `hard` | Indicates that app needs to be refreshed. Removed by application controller after app is refreshed. Value `"hard"` means manifest cache and target cluster state cache should be invalidated before refresh. |
|
||||
| argocd.argoproj.io/skip-reconcile | Application | `"true"` | Indicates to the Argo CD application controller that the Application should not be reconciled. See the [skip reconcile documentation](skip_reconcile.md) for use cases. |
|
||||
| argocd.argoproj.io/sync-options | any | [see sync options docs](sync-options.md) | Provides a variety of settings to determine how an Application's resources are synced. |
|
||||
| argocd.argoproj.io/sync-wave | any | [see sync waves docs](sync-waves.md) | |
|
||||
| argocd.argoproj.io/tracking-id | any | any | Used by Argo CD to track resources it manages. See [resource tracking docs](resource_tracking.md) for details. |
|
||||
| link.argocd.argoproj.io/{some link name} | any | An http(s) URL | Adds a link to the Argo CD UI for the resource. See [external URL docs](external-url.md) for details. |
|
||||
| pref.argocd.argoproj.io/default-pod-sort | Application | [see UI customization docs](../operator-manual/ui-customization.md) | Sets the Application's default grouping mechanism. |
|
||||
| pref.argocd.argoproj.io/default-view | Application | [see UI customization docs](../operator-manual/ui-customization.md) | Sets the Application's default view mode (e.g. "tree" or "list") |
|
||||
|
||||
## Labels
|
||||
|
||||
| Label key | Target resource(es) | Possible values | Description |
|
||||
|--------------------------------|---------------------|---------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| argocd.argoproj.io/instance | Application | any | Recommended tracking label to [avoid conflicts with other tools which use `app.kubernetes.io/instance`](../faq.md#why-is-my-app-out-of-sync-even-after-syncing. |
|
||||
| argocd.argoproj.io/secret-type | Secret | `cluster`, `repository`, `repo-creds` | Identifies certain types of Secrets used by Argo CD. See the [Declarative Setup docs](../operator-manual/declarative-setup.md) for details. |
|
||||
@@ -54,7 +54,7 @@ source:
|
||||
|
||||
Argo CD supports the equivalent of a values file directly in the Application manifest using the `source.helm.valuesObject` key.
|
||||
|
||||
```
|
||||
```yaml
|
||||
source:
|
||||
helm:
|
||||
valuesObject:
|
||||
@@ -75,7 +75,7 @@ source:
|
||||
|
||||
Alternatively, values can be passed in as a string using the `source.helm.values` key.
|
||||
|
||||
```
|
||||
```yaml
|
||||
source:
|
||||
helm:
|
||||
values: |
|
||||
@@ -254,7 +254,7 @@ One way to use this plugin is to prepare your own ArgoCD image where it is inclu
|
||||
|
||||
Example `Dockerfile`:
|
||||
|
||||
```
|
||||
```dockerfile
|
||||
FROM argoproj/argocd:v1.5.7
|
||||
|
||||
USER root
|
||||
@@ -284,7 +284,7 @@ Some users find this pattern preferable to maintaining their own version of the
|
||||
|
||||
Below is an example of how to add Helm plugins when installing ArgoCD with the [official ArgoCD helm chart](https://github.com/argoproj/argo-helm/tree/master/charts/argo-cd):
|
||||
|
||||
```
|
||||
```yaml
|
||||
repoServer:
|
||||
volumes:
|
||||
- name: gcp-credentials
|
||||
|
||||
@@ -279,7 +279,7 @@ It is possible to add and remove TLS certificates using the ArgoCD web UI:
|
||||
|
||||
### Managing TLS certificates using declarative configuration
|
||||
|
||||
You can also manage TLS certificates in a declarative, self-managed ArgoCD setup. All TLS certificates are stored in the ConfigMap object `argocd-tls-cert-cm`.
|
||||
You can also manage TLS certificates in a declarative, self-managed ArgoCD setup. All TLS certificates are stored in the ConfigMap object `argocd-tls-certs-cm`.
|
||||
Please refer to the [Operator Manual](../../operator-manual/declarative-setup/#repositories-using-self-signed-tls-certificates-or-are-signed-by-custom-ca) for more information.
|
||||
|
||||
## Unknown SSH Hosts
|
||||
|
||||
144
docs/user-guide/sync-kubectl.md
Normal file
144
docs/user-guide/sync-kubectl.md
Normal file
@@ -0,0 +1,144 @@
|
||||
# Sync Applications with Kubectl
|
||||
|
||||
You can use "kubectl" to ask Argo CD to synchronize applications the same way you can use the CLI or UI. Many configurations like "force", "prune", "apply" and even synchronize a specific list of resources are equally supported. This is done by applying or patching the Argo CD application with a document that defines an "operation".
|
||||
|
||||
This "operation" defines how a synchronization should be done and for what resources these synchronization is to be done.
|
||||
|
||||
There are many configuration options that can be added to the "operation". Next, a few of them are explained. For more details, you can have a look at the CRD [applications.argoproj.io](https://github.com/argoproj/argo-cd/blob/master/manifests/crds/application-crd.yaml). Some of them are required, whereas others are optional.
|
||||
|
||||
To ask Argo CD to synchronize all resources of a given application, we can do:
|
||||
|
||||
```yaml
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: <app-name>
|
||||
namespace: <namespace>
|
||||
spec:
|
||||
...
|
||||
operation:
|
||||
initiatedBy:
|
||||
username: <username>
|
||||
sync:
|
||||
syncStrategy:
|
||||
hook: {}
|
||||
```
|
||||
|
||||
```bash
|
||||
$ kubectl apply -f <apply-file>
|
||||
```
|
||||
|
||||
The most important part is the "sync" definition in the "operation" field. You can pass optional information like "info" or "initiatedBy". "info" allows you to add information about the operation in the form of a list. "initiatedBy" contains information about who initiated the operation request.
|
||||
|
||||
Or if you prefer, you also can patch:
|
||||
|
||||
```yaml
|
||||
operation:
|
||||
initiatedBy:
|
||||
username: <username>
|
||||
sync:
|
||||
syncStrategy:
|
||||
hook: {}
|
||||
```
|
||||
|
||||
```bash
|
||||
$ kubectl patch -n <namespace> app <app-name> --patch-file <patch-file> --type merge
|
||||
```
|
||||
|
||||
Be aware that patches, specially with merge strategies, may not work the way you expect especially if you change sync strategies or options.
|
||||
In these cases, "kubectl apply" gives better results.
|
||||
|
||||
Either with a "kubectl patch" or "kubectl apply", the state of the synchronization is reported in the "operationState" field in the application object.
|
||||
|
||||
```bash
|
||||
$ kubectl get -n <namespace> get app <app-name> -o yaml
|
||||
...
|
||||
status:
|
||||
operationState:
|
||||
finishedAt: "2023-08-03T11:16:17Z"
|
||||
message: successfully synced (all tasks run)
|
||||
phase: Succeeded
|
||||
```
|
||||
|
||||
# Apply and Hook synchronization strategies
|
||||
|
||||
There are two types of synchronization strategies: "hook", which is the default value, and "apply".
|
||||
|
||||
An "apply" sync strategy tells Argo CD to "kubectl apply", whereas a "hook" sync strategy informs Argo CD to submit any resource that's referenced in the operation. This way the synchronization of these resources will take into consideration any hook the resource has been annotated with.
|
||||
|
||||
```yaml
|
||||
operation:
|
||||
sync:
|
||||
syncStrategy:
|
||||
apply: {}
|
||||
```
|
||||
|
||||
```yaml
|
||||
operation:
|
||||
sync:
|
||||
syncStrategy:
|
||||
hook: {}
|
||||
```
|
||||
|
||||
Both strategies support "force". However, you need to be aware that a force operation deletes the resource when patch encounters a conflict after having retried 5 times.
|
||||
|
||||
```yaml
|
||||
operation:
|
||||
sync:
|
||||
syncStrategy:
|
||||
apply:
|
||||
force: true
|
||||
```
|
||||
|
||||
```yaml
|
||||
operation:
|
||||
sync:
|
||||
syncStrategy:
|
||||
hook:
|
||||
force: true
|
||||
```
|
||||
|
||||
# Prune
|
||||
|
||||
If you want to prune your resources before applying, you can instruct Argo CD to do so:
|
||||
|
||||
```yaml
|
||||
operation:
|
||||
sync:
|
||||
prune: true
|
||||
```
|
||||
|
||||
# List of resources
|
||||
|
||||
There's always the possibility to pass a list of resources. This list can be all resources the application manages or only a subset, for example resources that remained out of sync for some reason.
|
||||
|
||||
Only "kind" and "name" are required fields when referencing resources, but the fields "groups" and "namespace" can also be defined:
|
||||
|
||||
```yaml
|
||||
operation:
|
||||
sync:
|
||||
resources:
|
||||
- kind: Namespace
|
||||
name: namespace-name
|
||||
- kind: ServiceAccount
|
||||
name: service-account-name
|
||||
namespace: namespace-name
|
||||
- group: networking.k8s.io
|
||||
kind: NetworkPolicy
|
||||
name: network-policy-name
|
||||
namespace: namespace-name
|
||||
```
|
||||
|
||||
# Sync Options
|
||||
|
||||
In an operation, you can also pass sync-options. Each of these options is passed as "name=value" pairs. For example:
|
||||
|
||||
```yaml
|
||||
operations:
|
||||
sync:
|
||||
syncOptions:
|
||||
- Validate=false
|
||||
- Prune=false
|
||||
```
|
||||
|
||||
For more information about sync options, please refer to [sync-options](https://argo-cd.readthedocs.io/en/stable/user-guide/sync-options/)
|
||||
2
go.mod
2
go.mod
@@ -115,6 +115,7 @@ require (
|
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
|
||||
gopkg.in/retry.v1 v1.0.3 // indirect
|
||||
k8s.io/klog v1.0.0 // indirect
|
||||
nhooyr.io/websocket v1.8.7 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -261,7 +262,6 @@ require (
|
||||
k8s.io/gengo v0.0.0-20211129171323-c02415ce4185 // indirect
|
||||
k8s.io/kube-aggregator v0.24.2 // indirect
|
||||
k8s.io/kubernetes v1.24.2 // indirect
|
||||
nhooyr.io/websocket v1.8.6 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20220525155127-227cbc7cc124 // indirect
|
||||
sigs.k8s.io/kustomize/api v0.11.5 // indirect
|
||||
sigs.k8s.io/kustomize/kyaml v0.13.7 // indirect
|
||||
|
||||
3
go.sum
3
go.sum
@@ -1963,8 +1963,9 @@ modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk=
|
||||
modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k=
|
||||
modernc.org/strutil v1.0.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs=
|
||||
modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I=
|
||||
nhooyr.io/websocket v1.8.6 h1:s+C3xAMLwGmlI31Nyn/eAehUlZPwfYZu2JXM621Q5/k=
|
||||
nhooyr.io/websocket v1.8.6/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
|
||||
nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g=
|
||||
nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
|
||||
oras.land/oras-go/v2 v2.2.0 h1:E1fqITD56Eg5neZbxBtAdZVgDHD6wBabJo6xESTcQyo=
|
||||
oras.land/oras-go/v2 v2.2.0/go.mod h1:pXjn0+KfarspMHHNR3A56j3tgvr+mxArHuI8qVn59v8=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
|
||||
@@ -5,7 +5,7 @@ kind: Kustomization
|
||||
images:
|
||||
- name: quay.io/argoproj/argocd
|
||||
newName: quay.io/argoproj/argocd
|
||||
newTag: v2.8.0
|
||||
newTag: v2.8.2
|
||||
resources:
|
||||
- ./application-controller
|
||||
- ./dex
|
||||
|
||||
@@ -18880,7 +18880,7 @@ spec:
|
||||
key: applicationsetcontroller.allowed.scm.providers
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.8.0
|
||||
image: quay.io/argoproj/argocd:v2.8.2
|
||||
imagePullPolicy: Always
|
||||
name: argocd-applicationset-controller
|
||||
ports:
|
||||
@@ -19168,7 +19168,7 @@ spec:
|
||||
value: /helm-working-dir
|
||||
- name: HELM_DATA_HOME
|
||||
value: /helm-working-dir
|
||||
image: quay.io/argoproj/argocd:v2.8.0
|
||||
image: quay.io/argoproj/argocd:v2.8.2
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -19220,7 +19220,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /var/run/argocd/argocd-cmp-server
|
||||
image: quay.io/argoproj/argocd:v2.8.0
|
||||
image: quay.io/argoproj/argocd:v2.8.2
|
||||
name: copyutil
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
@@ -19439,7 +19439,7 @@ spec:
|
||||
key: controller.kubectl.parallelism.limit
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.8.0
|
||||
image: quay.io/argoproj/argocd:v2.8.2
|
||||
imagePullPolicy: Always
|
||||
name: argocd-application-controller
|
||||
ports:
|
||||
|
||||
@@ -12,4 +12,4 @@ resources:
|
||||
images:
|
||||
- name: quay.io/argoproj/argocd
|
||||
newName: quay.io/argoproj/argocd
|
||||
newTag: v2.8.0
|
||||
newTag: v2.8.2
|
||||
|
||||
@@ -12,7 +12,7 @@ patches:
|
||||
images:
|
||||
- name: quay.io/argoproj/argocd
|
||||
newName: quay.io/argoproj/argocd
|
||||
newTag: v2.8.0
|
||||
newTag: v2.8.2
|
||||
resources:
|
||||
- ../../base/application-controller
|
||||
- ../../base/applicationset-controller
|
||||
|
||||
@@ -20117,7 +20117,7 @@ spec:
|
||||
key: applicationsetcontroller.allowed.scm.providers
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.8.0
|
||||
image: quay.io/argoproj/argocd:v2.8.2
|
||||
imagePullPolicy: Always
|
||||
name: argocd-applicationset-controller
|
||||
ports:
|
||||
@@ -20240,7 +20240,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /shared/argocd-dex
|
||||
image: quay.io/argoproj/argocd:v2.8.0
|
||||
image: quay.io/argoproj/argocd:v2.8.2
|
||||
imagePullPolicy: Always
|
||||
name: copyutil
|
||||
securityContext:
|
||||
@@ -20310,7 +20310,7 @@ spec:
|
||||
key: notificationscontroller.log.level
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.8.0
|
||||
image: quay.io/argoproj/argocd:v2.8.2
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
@@ -20624,7 +20624,7 @@ spec:
|
||||
value: /helm-working-dir
|
||||
- name: HELM_DATA_HOME
|
||||
value: /helm-working-dir
|
||||
image: quay.io/argoproj/argocd:v2.8.0
|
||||
image: quay.io/argoproj/argocd:v2.8.2
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -20676,7 +20676,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /var/run/argocd/argocd-cmp-server
|
||||
image: quay.io/argoproj/argocd:v2.8.0
|
||||
image: quay.io/argoproj/argocd:v2.8.2
|
||||
name: copyutil
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
@@ -20965,7 +20965,7 @@ spec:
|
||||
key: server.enable.proxy.extension
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.8.0
|
||||
image: quay.io/argoproj/argocd:v2.8.2
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
@@ -21211,7 +21211,7 @@ spec:
|
||||
key: controller.kubectl.parallelism.limit
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.8.0
|
||||
image: quay.io/argoproj/argocd:v2.8.2
|
||||
imagePullPolicy: Always
|
||||
name: argocd-application-controller
|
||||
ports:
|
||||
|
||||
@@ -1635,7 +1635,7 @@ spec:
|
||||
key: applicationsetcontroller.allowed.scm.providers
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.8.0
|
||||
image: quay.io/argoproj/argocd:v2.8.2
|
||||
imagePullPolicy: Always
|
||||
name: argocd-applicationset-controller
|
||||
ports:
|
||||
@@ -1758,7 +1758,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /shared/argocd-dex
|
||||
image: quay.io/argoproj/argocd:v2.8.0
|
||||
image: quay.io/argoproj/argocd:v2.8.2
|
||||
imagePullPolicy: Always
|
||||
name: copyutil
|
||||
securityContext:
|
||||
@@ -1828,7 +1828,7 @@ spec:
|
||||
key: notificationscontroller.log.level
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.8.0
|
||||
image: quay.io/argoproj/argocd:v2.8.2
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
@@ -2142,7 +2142,7 @@ spec:
|
||||
value: /helm-working-dir
|
||||
- name: HELM_DATA_HOME
|
||||
value: /helm-working-dir
|
||||
image: quay.io/argoproj/argocd:v2.8.0
|
||||
image: quay.io/argoproj/argocd:v2.8.2
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -2194,7 +2194,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /var/run/argocd/argocd-cmp-server
|
||||
image: quay.io/argoproj/argocd:v2.8.0
|
||||
image: quay.io/argoproj/argocd:v2.8.2
|
||||
name: copyutil
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
@@ -2483,7 +2483,7 @@ spec:
|
||||
key: server.enable.proxy.extension
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.8.0
|
||||
image: quay.io/argoproj/argocd:v2.8.2
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
@@ -2729,7 +2729,7 @@ spec:
|
||||
key: controller.kubectl.parallelism.limit
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.8.0
|
||||
image: quay.io/argoproj/argocd:v2.8.2
|
||||
imagePullPolicy: Always
|
||||
name: argocd-application-controller
|
||||
ports:
|
||||
|
||||
@@ -19218,7 +19218,7 @@ spec:
|
||||
key: applicationsetcontroller.allowed.scm.providers
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.8.0
|
||||
image: quay.io/argoproj/argocd:v2.8.2
|
||||
imagePullPolicy: Always
|
||||
name: argocd-applicationset-controller
|
||||
ports:
|
||||
@@ -19341,7 +19341,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /shared/argocd-dex
|
||||
image: quay.io/argoproj/argocd:v2.8.0
|
||||
image: quay.io/argoproj/argocd:v2.8.2
|
||||
imagePullPolicy: Always
|
||||
name: copyutil
|
||||
securityContext:
|
||||
@@ -19411,7 +19411,7 @@ spec:
|
||||
key: notificationscontroller.log.level
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.8.0
|
||||
image: quay.io/argoproj/argocd:v2.8.2
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
@@ -19681,7 +19681,7 @@ spec:
|
||||
value: /helm-working-dir
|
||||
- name: HELM_DATA_HOME
|
||||
value: /helm-working-dir
|
||||
image: quay.io/argoproj/argocd:v2.8.0
|
||||
image: quay.io/argoproj/argocd:v2.8.2
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -19733,7 +19733,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /var/run/argocd/argocd-cmp-server
|
||||
image: quay.io/argoproj/argocd:v2.8.0
|
||||
image: quay.io/argoproj/argocd:v2.8.2
|
||||
name: copyutil
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
@@ -20020,7 +20020,7 @@ spec:
|
||||
key: server.enable.proxy.extension
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.8.0
|
||||
image: quay.io/argoproj/argocd:v2.8.2
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
@@ -20266,7 +20266,7 @@ spec:
|
||||
key: controller.kubectl.parallelism.limit
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.8.0
|
||||
image: quay.io/argoproj/argocd:v2.8.2
|
||||
imagePullPolicy: Always
|
||||
name: argocd-application-controller
|
||||
ports:
|
||||
|
||||
@@ -736,7 +736,7 @@ spec:
|
||||
key: applicationsetcontroller.allowed.scm.providers
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.8.0
|
||||
image: quay.io/argoproj/argocd:v2.8.2
|
||||
imagePullPolicy: Always
|
||||
name: argocd-applicationset-controller
|
||||
ports:
|
||||
@@ -859,7 +859,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /shared/argocd-dex
|
||||
image: quay.io/argoproj/argocd:v2.8.0
|
||||
image: quay.io/argoproj/argocd:v2.8.2
|
||||
imagePullPolicy: Always
|
||||
name: copyutil
|
||||
securityContext:
|
||||
@@ -929,7 +929,7 @@ spec:
|
||||
key: notificationscontroller.log.level
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.8.0
|
||||
image: quay.io/argoproj/argocd:v2.8.2
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
@@ -1199,7 +1199,7 @@ spec:
|
||||
value: /helm-working-dir
|
||||
- name: HELM_DATA_HOME
|
||||
value: /helm-working-dir
|
||||
image: quay.io/argoproj/argocd:v2.8.0
|
||||
image: quay.io/argoproj/argocd:v2.8.2
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -1251,7 +1251,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /var/run/argocd/argocd-cmp-server
|
||||
image: quay.io/argoproj/argocd:v2.8.0
|
||||
image: quay.io/argoproj/argocd:v2.8.2
|
||||
name: copyutil
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
@@ -1538,7 +1538,7 @@ spec:
|
||||
key: server.enable.proxy.extension
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.8.0
|
||||
image: quay.io/argoproj/argocd:v2.8.2
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
@@ -1784,7 +1784,7 @@ spec:
|
||||
key: controller.kubectl.parallelism.limit
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.8.0
|
||||
image: quay.io/argoproj/argocd:v2.8.2
|
||||
imagePullPolicy: Always
|
||||
name: argocd-application-controller
|
||||
ports:
|
||||
|
||||
@@ -167,6 +167,7 @@ nav:
|
||||
- user-guide/selective_sync.md
|
||||
- user-guide/sync-waves.md
|
||||
- user-guide/sync_windows.md
|
||||
- user-guide/sync-kubectl.md
|
||||
- user-guide/skip_reconcile.md
|
||||
- Generating Applications with ApplicationSet: user-guide/application-set.md
|
||||
- user-guide/ci_automation.md
|
||||
@@ -176,6 +177,7 @@ nav:
|
||||
- user-guide/external-url.md
|
||||
- user-guide/extra_info.md
|
||||
- Notification subscriptions: user-guide/subscriptions.md
|
||||
- user-guide/annotations-and-labels.md
|
||||
- Command Reference: user-guide/commands/argocd.md
|
||||
- Application Specification Reference: user-guide/application-specification.md
|
||||
- Developer Guide:
|
||||
|
||||
@@ -22,7 +22,6 @@ import (
|
||||
grpc_retry "github.com/grpc-ecosystem/go-grpc-middleware/retry"
|
||||
"github.com/hashicorp/go-retryablehttp"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
|
||||
"golang.org/x/oauth2"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
@@ -520,8 +519,8 @@ func (c *client) newConn() (*grpc.ClientConn, io.Closer, error) {
|
||||
dialOpts = append(dialOpts, grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(MaxGRPCMessageSize), grpc.MaxCallSendMsgSize(MaxGRPCMessageSize)))
|
||||
dialOpts = append(dialOpts, grpc.WithStreamInterceptor(grpc_retry.StreamClientInterceptor(retryOpts...)))
|
||||
dialOpts = append(dialOpts, grpc.WithUnaryInterceptor(grpc_middleware.ChainUnaryClient(grpc_retry.UnaryClientInterceptor(retryOpts...))))
|
||||
dialOpts = append(dialOpts, grpc.WithUnaryInterceptor(otelgrpc.UnaryClientInterceptor()))
|
||||
dialOpts = append(dialOpts, grpc.WithStreamInterceptor(otelgrpc.StreamClientInterceptor()))
|
||||
dialOpts = append(dialOpts, grpc.WithUnaryInterceptor(grpc_util.OTELUnaryClientInterceptor()))
|
||||
dialOpts = append(dialOpts, grpc.WithStreamInterceptor(grpc_util.OTELStreamClientInterceptor()))
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
|
||||
grpc_retry "github.com/grpc-ecosystem/go-grpc-middleware/retry"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
@@ -67,8 +66,8 @@ func NewConnection(address string, timeoutSeconds int, tlsConfig *TLSConfigurati
|
||||
grpc.WithStreamInterceptor(grpc_retry.StreamClientInterceptor(retryOpts...)),
|
||||
grpc.WithUnaryInterceptor(grpc_middleware.ChainUnaryClient(unaryInterceptors...)),
|
||||
grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(MaxGRPCMessageSize), grpc.MaxCallSendMsgSize(MaxGRPCMessageSize)),
|
||||
grpc.WithUnaryInterceptor(otelgrpc.UnaryClientInterceptor()),
|
||||
grpc.WithStreamInterceptor(otelgrpc.StreamClientInterceptor()),
|
||||
grpc.WithUnaryInterceptor(argogrpc.OTELUnaryClientInterceptor()),
|
||||
grpc.WithStreamInterceptor(argogrpc.OTELStreamClientInterceptor()),
|
||||
}
|
||||
|
||||
tlsC := &tls.Config{}
|
||||
|
||||
@@ -2,3 +2,6 @@ actionTests:
|
||||
- action: create-workflow
|
||||
inputPath: testdata/cronworkflow.yaml
|
||||
expectedOutputPath: testdata/workflow.yaml
|
||||
- action: create-workflow
|
||||
inputPath: testdata/cronworkflow-without-label.yaml
|
||||
expectedOutputPath: testdata/workflow-without-label.yaml
|
||||
|
||||
@@ -50,7 +50,7 @@ if (obj.spec.workflowMetadata ~= nil) then
|
||||
end
|
||||
end
|
||||
workflow.metadata.labels["workflows.argoproj.io/cron-workflow"] = obj.metadata.name
|
||||
if (obj.metadata.labels["workflows.argoproj.io/controller-instanceid"] ~= nil) then
|
||||
if (obj.metadata.labels ~= nil and obj.metadata.labels["workflows.argoproj.io/controller-instanceid"] ~= nil) then
|
||||
workflow.metadata.labels["workflows.argoproj.io/controller-instanceid"] = obj.metadata.labels["workflows.argoproj.io/controller-instanceid"]
|
||||
end
|
||||
workflow.metadata.annotations["workflows.argoproj.io/scheduled-time"] = os.date("!%Y-%m-%dT%d:%H:%MZ")
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: CronWorkflow
|
||||
metadata:
|
||||
annotations:
|
||||
cronworkflows.argoproj.io/last-used-schedule: CRON_TZ=America/Los_Angeles * * * * *
|
||||
name: hello-world
|
||||
namespace: default
|
||||
spec:
|
||||
concurrencyPolicy: Replace
|
||||
failedJobsHistoryLimit: 4
|
||||
schedule: '* * * * *'
|
||||
startingDeadlineSeconds: 0
|
||||
successfulJobsHistoryLimit: 4
|
||||
suspend: true
|
||||
timezone: America/Los_Angeles
|
||||
workflowSpec:
|
||||
entrypoint: whalesay
|
||||
templates:
|
||||
- container:
|
||||
args:
|
||||
- "\U0001F553 hello world. Scheduled on: {{workflow.scheduledTime}}"
|
||||
command:
|
||||
- cowsay
|
||||
image: 'docker/whalesay:latest'
|
||||
name: whalesay
|
||||
workflowMetadata:
|
||||
labels:
|
||||
example: test
|
||||
annotations:
|
||||
another-example: another-test
|
||||
finalizers: [test-finalizer]
|
||||
26
resource_customizations/argoproj.io/CronWorkflow/actions/testdata/workflow-without-label.yaml
vendored
Normal file
26
resource_customizations/argoproj.io/CronWorkflow/actions/testdata/workflow-without-label.yaml
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
- k8sOperation: create
|
||||
unstructuredObj:
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Workflow
|
||||
metadata:
|
||||
annotations:
|
||||
another-example: another-test
|
||||
labels:
|
||||
example: test
|
||||
name: hello-world-202306221736
|
||||
namespace: default
|
||||
ownerReferences:
|
||||
- apiVersion: argoproj.io/v1alpha1
|
||||
kind: CronWorkflow
|
||||
name: hello-world
|
||||
finalizers: [test-finalizer]
|
||||
spec:
|
||||
entrypoint: whalesay
|
||||
templates:
|
||||
- container:
|
||||
args:
|
||||
- "\U0001F553 hello world. Scheduled on: {{workflow.scheduledTime}}"
|
||||
command:
|
||||
- cowsay
|
||||
image: 'docker/whalesay:latest'
|
||||
name: whalesay
|
||||
@@ -5,10 +5,10 @@ infinity = 2^1024-1
|
||||
local function executor_range_api()
|
||||
min_executor_instances = 0
|
||||
max_executor_instances = infinity
|
||||
if obj.spec.dynamicAllocation.maxExecutors then
|
||||
if obj.spec.dynamicAllocation.maxExecutors then
|
||||
max_executor_instances = obj.spec.dynamicAllocation.maxExecutors
|
||||
end
|
||||
if obj.spec.dynamicAllocation.minExecutors then
|
||||
if obj.spec.dynamicAllocation.minExecutors then
|
||||
min_executor_instances = obj.spec.dynamicAllocation.minExecutors
|
||||
end
|
||||
return min_executor_instances, max_executor_instances
|
||||
@@ -17,7 +17,7 @@ end
|
||||
local function maybe_executor_range_spark_conf()
|
||||
min_executor_instances = 0
|
||||
max_executor_instances = infinity
|
||||
if obj.spec.sparkConf["spark.streaming.dynamicAllocation.enabled"] ~= nil and
|
||||
if obj.spec.sparkConf["spark.streaming.dynamicAllocation.enabled"] ~= nil and
|
||||
obj.spec.sparkConf["spark.streaming.dynamicAllocation.enabled"] == "true" then
|
||||
if(obj.spec.sparkConf["spark.streaming.dynamicAllocation.maxExecutors"] ~= nil) then
|
||||
max_executor_instances = tonumber(obj.spec.sparkConf["spark.streaming.dynamicAllocation.maxExecutors"])
|
||||
@@ -26,7 +26,7 @@ local function maybe_executor_range_spark_conf()
|
||||
min_executor_instances = tonumber(obj.spec.sparkConf["spark.streaming.dynamicAllocation.minExecutors"])
|
||||
end
|
||||
return min_executor_instances, max_executor_instances
|
||||
elseif obj.spec.sparkConf["spark.dynamicAllocation.enabled"] ~= nil and
|
||||
elseif obj.spec.sparkConf["spark.dynamicAllocation.enabled"] ~= nil and
|
||||
obj.spec.sparkConf["spark.dynamicAllocation.enabled"] == "true" then
|
||||
if(obj.spec.sparkConf["spark.dynamicAllocation.maxExecutors"] ~= nil) then
|
||||
max_executor_instances = tonumber(obj.spec.sparkConf["spark.dynamicAllocation.maxExecutors"])
|
||||
@@ -45,11 +45,19 @@ local function maybe_executor_range()
|
||||
return executor_range_api()
|
||||
elseif obj.spec["sparkConf"] ~= nil then
|
||||
return maybe_executor_range_spark_conf()
|
||||
else
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
local function dynamic_executors_without_spec_config()
|
||||
if obj.spec.dynamicAllocation == nil and obj.spec.executor.instances == nil then
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
if obj.status ~= nil then
|
||||
if obj.status.applicationState.state ~= nil then
|
||||
if obj.status.applicationState.state == "" then
|
||||
@@ -60,23 +68,26 @@ if obj.status ~= nil then
|
||||
if obj.status.applicationState.state == "RUNNING" then
|
||||
if obj.status.executorState ~= nil then
|
||||
count=0
|
||||
executor_instances = obj.spec.executor.instances
|
||||
for i, executorState in pairs(obj.status.executorState) do
|
||||
if executorState == "RUNNING" then
|
||||
count=count+1
|
||||
end
|
||||
end
|
||||
if executor_instances == count then
|
||||
if obj.spec.executor.instances ~= nil and obj.spec.executor.instances == count then
|
||||
health_status.status = "Healthy"
|
||||
health_status.message = "SparkApplication is Running"
|
||||
return health_status
|
||||
elseif maybe_executor_range() then
|
||||
min_executor_instances, max_executor_instances = maybe_executor_range()
|
||||
if count >= min_executor_instances and count <= max_executor_instances then
|
||||
if count >= min_executor_instances and count <= max_executor_instances then
|
||||
health_status.status = "Healthy"
|
||||
health_status.message = "SparkApplication is Running"
|
||||
return health_status
|
||||
end
|
||||
elseif dynamic_executors_without_spec_config() and count >= 1 then
|
||||
health_status.status = "Healthy"
|
||||
health_status.message = "SparkApplication is Running"
|
||||
return health_status
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -23,3 +23,7 @@ tests:
|
||||
status: Healthy
|
||||
message: "SparkApplication is Running"
|
||||
inputPath: testdata/healthy_dynamic_alloc_operator_api.yaml
|
||||
- healthStatus:
|
||||
status: Healthy
|
||||
message: "SparkApplication is Running"
|
||||
inputPath: testdata/healthy_dynamic_alloc_without_spec_config.yaml
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
apiVersion: sparkoperator.k8s.io/v1beta2
|
||||
kind: SparkApplication
|
||||
metadata:
|
||||
generation: 4
|
||||
labels:
|
||||
argocd.argoproj.io/instance: spark-job
|
||||
name: spark-job-app
|
||||
namespace: spark-cluster
|
||||
resourceVersion: "31812990"
|
||||
uid: bfee52b0-74ca-4465-8005-f6643097ed64
|
||||
spec:
|
||||
executor: {}
|
||||
status:
|
||||
applicationState:
|
||||
state: RUNNING
|
||||
driverInfo:
|
||||
podName: ingestion-datalake-news-app-driver
|
||||
webUIAddress: 172.20.207.161:4040
|
||||
webUIPort: 4040
|
||||
webUIServiceName: ingestion-datalake-news-app-ui-svc
|
||||
executionAttempts: 13
|
||||
executorState:
|
||||
ingestion-datalake-news-app-1591613851251-exec-1: RUNNING
|
||||
ingestion-datalake-news-app-1591613851251-exec-2: RUNNING
|
||||
ingestion-datalake-news-app-1591613851251-exec-4: RUNNING
|
||||
ingestion-datalake-news-app-1591613851251-exec-5: RUNNING
|
||||
lastSubmissionAttemptTime: "2020-06-08T10:57:32Z"
|
||||
sparkApplicationId: spark-a5920b2a5aa04d22a737c60759b5bf82
|
||||
submissionAttempts: 1
|
||||
submissionID: 3e713ec8-9f6c-4e78-ac28-749797c846f0
|
||||
terminationTime: null
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
util_session "github.com/argoproj/argo-cd/v2/util/session"
|
||||
"github.com/argoproj/gitops-engine/pkg/utils/kube"
|
||||
log "github.com/sirupsen/logrus"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
@@ -37,11 +38,12 @@ type terminalHandler struct {
|
||||
allowedShells []string
|
||||
namespace string
|
||||
enabledNamespaces []string
|
||||
sessionManager util_session.SessionManager
|
||||
}
|
||||
|
||||
// NewHandler returns a new terminal handler.
|
||||
func NewHandler(appLister applisters.ApplicationLister, namespace string, enabledNamespaces []string, db db.ArgoDB, enf *rbac.Enforcer, cache *servercache.Cache,
|
||||
appResourceTree AppResourceTreeFn, allowedShells []string) *terminalHandler {
|
||||
appResourceTree AppResourceTreeFn, allowedShells []string, sessionManager util_session.SessionManager) *terminalHandler {
|
||||
return &terminalHandler{
|
||||
appLister: appLister,
|
||||
db: db,
|
||||
@@ -51,6 +53,7 @@ func NewHandler(appLister applisters.ApplicationLister, namespace string, enable
|
||||
allowedShells: allowedShells,
|
||||
namespace: namespace,
|
||||
enabledNamespaces: enabledNamespaces,
|
||||
sessionManager: sessionManager,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,7 +225,7 @@ func (s *terminalHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
fieldLog.Info("terminal session starting")
|
||||
|
||||
session, err := newTerminalSession(w, r, nil)
|
||||
session, err := newTerminalSession(w, r, nil, s.sessionManager)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to start terminal session", http.StatusBadRequest)
|
||||
return
|
||||
@@ -282,6 +285,11 @@ type TerminalMessage struct {
|
||||
Cols uint16 `json:"cols"`
|
||||
}
|
||||
|
||||
// TerminalCommand is the struct for websocket commands,For example you need ask client to reconnect
|
||||
type TerminalCommand struct {
|
||||
Code int
|
||||
}
|
||||
|
||||
// startProcess executes specified commands in the container and connects it up with the ptyHandler (a session)
|
||||
func startProcess(k8sClient kubernetes.Interface, cfg *rest.Config, namespace, podName, containerName string, cmd []string, ptyHandler PtyHandler) error {
|
||||
req := k8sClient.CoreV1().RESTClient().Post().
|
||||
|
||||
@@ -3,6 +3,9 @@ package application
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/argoproj/argo-cd/v2/common"
|
||||
httputil "github.com/argoproj/argo-cd/v2/util/http"
|
||||
util_session "github.com/argoproj/argo-cd/v2/util/session"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -12,6 +15,11 @@ import (
|
||||
"k8s.io/client-go/tools/remotecommand"
|
||||
)
|
||||
|
||||
const (
|
||||
ReconnectCode = 1
|
||||
ReconnectMessage = "\nReconnect because the token was refreshed...\n"
|
||||
)
|
||||
|
||||
var upgrader = func() websocket.Upgrader {
|
||||
upgrader := websocket.Upgrader{}
|
||||
upgrader.HandshakeTimeout = time.Second * 2
|
||||
@@ -23,25 +31,40 @@ var upgrader = func() websocket.Upgrader {
|
||||
|
||||
// terminalSession implements PtyHandler
|
||||
type terminalSession struct {
|
||||
wsConn *websocket.Conn
|
||||
sizeChan chan remotecommand.TerminalSize
|
||||
doneChan chan struct{}
|
||||
tty bool
|
||||
readLock sync.Mutex
|
||||
writeLock sync.Mutex
|
||||
wsConn *websocket.Conn
|
||||
sizeChan chan remotecommand.TerminalSize
|
||||
doneChan chan struct{}
|
||||
tty bool
|
||||
readLock sync.Mutex
|
||||
writeLock sync.Mutex
|
||||
sessionManager util_session.SessionManager
|
||||
token *string
|
||||
}
|
||||
|
||||
// getToken get auth token from web socket request
|
||||
func getToken(r *http.Request) (string, error) {
|
||||
cookies := r.Cookies()
|
||||
return httputil.JoinCookies(common.AuthCookieName, cookies)
|
||||
}
|
||||
|
||||
// newTerminalSession create terminalSession
|
||||
func newTerminalSession(w http.ResponseWriter, r *http.Request, responseHeader http.Header) (*terminalSession, error) {
|
||||
func newTerminalSession(w http.ResponseWriter, r *http.Request, responseHeader http.Header, sessionManager util_session.SessionManager) (*terminalSession, error) {
|
||||
token, err := getToken(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
conn, err := upgrader.Upgrade(w, r, responseHeader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
session := &terminalSession{
|
||||
wsConn: conn,
|
||||
tty: true,
|
||||
sizeChan: make(chan remotecommand.TerminalSize),
|
||||
doneChan: make(chan struct{}),
|
||||
wsConn: conn,
|
||||
tty: true,
|
||||
sizeChan: make(chan remotecommand.TerminalSize),
|
||||
doneChan: make(chan struct{}),
|
||||
sessionManager: sessionManager,
|
||||
token: &token,
|
||||
}
|
||||
return session, nil
|
||||
}
|
||||
@@ -78,8 +101,40 @@ func (t *terminalSession) Next() *remotecommand.TerminalSize {
|
||||
}
|
||||
}
|
||||
|
||||
// reconnect send reconnect code to client and ask them init new ws session
|
||||
func (t *terminalSession) reconnect() (int, error) {
|
||||
reconnectCommand, _ := json.Marshal(TerminalCommand{
|
||||
Code: ReconnectCode,
|
||||
})
|
||||
reconnectMessage, _ := json.Marshal(TerminalMessage{
|
||||
Operation: "stdout",
|
||||
Data: ReconnectMessage,
|
||||
})
|
||||
t.writeLock.Lock()
|
||||
err := t.wsConn.WriteMessage(websocket.TextMessage, reconnectMessage)
|
||||
if err != nil {
|
||||
log.Errorf("write message err: %v", err)
|
||||
return 0, err
|
||||
}
|
||||
err = t.wsConn.WriteMessage(websocket.TextMessage, reconnectCommand)
|
||||
if err != nil {
|
||||
log.Errorf("write message err: %v", err)
|
||||
return 0, err
|
||||
}
|
||||
t.writeLock.Unlock()
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// Read called in a loop from remotecommand as long as the process is running
|
||||
func (t *terminalSession) Read(p []byte) (int, error) {
|
||||
// check if token still valid
|
||||
_, newToken, err := t.sessionManager.VerifyToken(*t.token)
|
||||
// err in case if token is revoked, newToken in case if refresh happened
|
||||
if err != nil || newToken != "" {
|
||||
// need to send reconnect code in case if token was refreshed
|
||||
return t.reconnect()
|
||||
}
|
||||
|
||||
t.readLock.Lock()
|
||||
_, message, err := t.wsConn.ReadMessage()
|
||||
t.readLock.Unlock()
|
||||
|
||||
46
server/application/websocket_test.go
Normal file
46
server/application/websocket_test.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func reconnect(w http.ResponseWriter, r *http.Request) {
|
||||
var upgrader = websocket.Upgrader{}
|
||||
c, err := upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
ts := terminalSession{wsConn: c}
|
||||
_, _ = ts.reconnect()
|
||||
}
|
||||
|
||||
func TestReconnect(t *testing.T) {
|
||||
|
||||
s := httptest.NewServer(http.HandlerFunc(reconnect))
|
||||
defer s.Close()
|
||||
|
||||
u := "ws" + strings.TrimPrefix(s.URL, "http")
|
||||
|
||||
// Connect to the server
|
||||
ws, _, err := websocket.DefaultDialer.Dial(u, nil)
|
||||
assert.NoError(t, err)
|
||||
|
||||
defer ws.Close()
|
||||
|
||||
_, p, _ := ws.ReadMessage()
|
||||
|
||||
var message TerminalMessage
|
||||
|
||||
err = json.Unmarshal(p, &message)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, message.Data, ReconnectMessage)
|
||||
|
||||
}
|
||||
@@ -63,7 +63,6 @@ import (
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apiclient"
|
||||
accountpkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/account"
|
||||
applicationpkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/application"
|
||||
|
||||
applicationsetpkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/applicationset"
|
||||
certificatepkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/certificate"
|
||||
clusterpkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/cluster"
|
||||
@@ -437,8 +436,8 @@ func (a *ArgoCDServer) Listen() (*Listeners, error) {
|
||||
var dOpts []grpc.DialOption
|
||||
dOpts = append(dOpts, grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(apiclient.MaxGRPCMessageSize)))
|
||||
dOpts = append(dOpts, grpc.WithUserAgent(fmt.Sprintf("%s/%s", common.ArgoCDUserAgentName, common.GetVersion().Version)))
|
||||
dOpts = append(dOpts, grpc.WithUnaryInterceptor(otelgrpc.UnaryClientInterceptor()))
|
||||
dOpts = append(dOpts, grpc.WithStreamInterceptor(otelgrpc.StreamClientInterceptor()))
|
||||
dOpts = append(dOpts, grpc.WithUnaryInterceptor(grpc_util.OTELUnaryClientInterceptor()))
|
||||
dOpts = append(dOpts, grpc.WithStreamInterceptor(grpc_util.OTELStreamClientInterceptor()))
|
||||
if a.useTLS() {
|
||||
// The following sets up the dial Options for grpc-gateway to talk to gRPC server over TLS.
|
||||
// grpc-gateway is just translating HTTP/HTTPS requests as gRPC requests over localhost,
|
||||
@@ -977,7 +976,7 @@ func (a *ArgoCDServer) newHTTPServer(ctx context.Context, port int, grpcWebHandl
|
||||
}
|
||||
mux.Handle("/api/", handler)
|
||||
|
||||
terminal := application.NewHandler(a.appLister, a.Namespace, a.ApplicationNamespaces, a.db, a.enf, a.Cache, appResourceTreeFn, a.settings.ExecShells).
|
||||
terminal := application.NewHandler(a.appLister, a.Namespace, a.ApplicationNamespaces, a.db, a.enf, a.Cache, appResourceTreeFn, a.settings.ExecShells, *a.sessionMgr).
|
||||
WithFeatureFlagMiddleware(a.settingsMgr.GetSettings)
|
||||
th := util_session.WithAuthMiddleware(a.DisableAuth, a.sessionMgr, terminal)
|
||||
mux.Handle("/terminal", th)
|
||||
@@ -990,6 +989,7 @@ func (a *ArgoCDServer) newHTTPServer(ctx context.Context, port int, grpcWebHandl
|
||||
// will be added in mux.
|
||||
registerExtensions(mux, a)
|
||||
}
|
||||
|
||||
mustRegisterGWHandler(versionpkg.RegisterVersionServiceHandler, ctx, gwmux, conn)
|
||||
mustRegisterGWHandler(clusterpkg.RegisterClusterServiceHandler, ctx, gwmux, conn)
|
||||
mustRegisterGWHandler(applicationpkg.RegisterApplicationServiceHandler, ctx, gwmux, conn)
|
||||
|
||||
@@ -14,7 +14,7 @@ FROM docker.io/library/registry:2.8@sha256:41f413c22d6156587e2a51f3e80c09808b8c7
|
||||
|
||||
FROM docker.io/bitnami/kubectl:1.27@sha256:670fe3f50d45c0511bb0f2af018e2fc082ac8cdfaea02dba4e32866296036926 as kubectl
|
||||
|
||||
FROM docker.io/library/ubuntu:22.04@sha256:ac58ff7fe25edc58bdf0067ca99df00014dbd032e2246d30a722fa348fd799a5
|
||||
FROM docker.io/library/ubuntu:22.04@sha256:0bced47fffa3361afa981854fcabcd4577cd43cebbb808cea2b1f33a3dd7f508
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
RUN apt-get update && apt-get install --fix-missing -y \
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
argov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
@@ -352,6 +353,85 @@ func TestSimpleListGeneratorGoTemplate(t *testing.T) {
|
||||
|
||||
}
|
||||
|
||||
func TestRenderHelmValuesObject(t *testing.T) {
|
||||
|
||||
expectedApp := argov1alpha1.Application{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: application.ApplicationKind,
|
||||
APIVersion: "argoproj.io/v1alpha1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "my-cluster-guestbook",
|
||||
Namespace: fixture.TestNamespace(),
|
||||
Finalizers: []string{"resources-finalizer.argocd.argoproj.io"},
|
||||
Labels: map[string]string{
|
||||
LabelKeyAppSetInstance: "test-values-object",
|
||||
},
|
||||
},
|
||||
Spec: argov1alpha1.ApplicationSpec{
|
||||
Project: "default",
|
||||
Source: &argov1alpha1.ApplicationSource{
|
||||
RepoURL: "https://github.com/argoproj/argocd-example-apps.git",
|
||||
TargetRevision: "HEAD",
|
||||
Path: "helm-guestbook",
|
||||
Helm: &argov1alpha1.ApplicationSourceHelm{
|
||||
ValuesObject: &runtime.RawExtension{
|
||||
// This will always be converted as yaml
|
||||
Raw: []byte(`{"some":{"string":"Hello world"}}`),
|
||||
},
|
||||
},
|
||||
},
|
||||
Destination: argov1alpha1.ApplicationDestination{
|
||||
Server: "https://kubernetes.default.svc",
|
||||
Namespace: "guestbook",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
Given(t).
|
||||
// Create a ListGenerator-based ApplicationSet
|
||||
When().Create(v1alpha1.ApplicationSet{ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-values-object",
|
||||
},
|
||||
Spec: v1alpha1.ApplicationSetSpec{
|
||||
GoTemplate: true,
|
||||
Template: v1alpha1.ApplicationSetTemplate{
|
||||
ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{Name: "{{.cluster}}-guestbook"},
|
||||
Spec: argov1alpha1.ApplicationSpec{
|
||||
Project: "default",
|
||||
Source: &argov1alpha1.ApplicationSource{
|
||||
RepoURL: "https://github.com/argoproj/argocd-example-apps.git",
|
||||
TargetRevision: "HEAD",
|
||||
Path: "helm-guestbook",
|
||||
Helm: &argov1alpha1.ApplicationSourceHelm{
|
||||
ValuesObject: &runtime.RawExtension{
|
||||
Raw: []byte(`{"some":{"string":"{{.test}}"}}`),
|
||||
},
|
||||
},
|
||||
},
|
||||
Destination: argov1alpha1.ApplicationDestination{
|
||||
Server: "{{.url}}",
|
||||
Namespace: "guestbook",
|
||||
},
|
||||
},
|
||||
},
|
||||
Generators: []v1alpha1.ApplicationSetGenerator{
|
||||
{
|
||||
List: &v1alpha1.ListGenerator{
|
||||
Elements: []apiextensionsv1.JSON{{
|
||||
Raw: []byte(`{"cluster": "my-cluster","url": "https://kubernetes.default.svc", "test": "Hello world"}`),
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}).Then().Expect(ApplicationsExist([]argov1alpha1.Application{expectedApp})).
|
||||
// Delete the ApplicationSet, and verify it deletes the Applications
|
||||
When().
|
||||
Delete().Then().Expect(ApplicationsDoNotExist([]argov1alpha1.Application{expectedApp}))
|
||||
|
||||
}
|
||||
|
||||
func TestSyncPolicyCreateUpdate(t *testing.T) {
|
||||
|
||||
expectedApp := argov1alpha1.Application{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {DropDownMenu, NotificationType, SlidingPanel} from 'argo-ui';
|
||||
import {DropDownMenu, NotificationType, SlidingPanel, Tooltip} from 'argo-ui';
|
||||
import * as classNames from 'classnames';
|
||||
import * as PropTypes from 'prop-types';
|
||||
import * as React from 'react';
|
||||
@@ -147,7 +147,8 @@ export class ApplicationDetails extends React.Component<RouteComponentProps<{app
|
||||
this.setState({slidingPanelPage: 0});
|
||||
}
|
||||
|
||||
private toggleCompactView(pref: AppDetailsPreferences) {
|
||||
private toggleCompactView(appName: string, pref: AppDetailsPreferences) {
|
||||
pref.userHelpTipMsgs = pref.userHelpTipMsgs.map(usrMsg => (usrMsg.appName === appName && usrMsg.msgKey === 'groupNodes' ? {...usrMsg, display: true} : usrMsg));
|
||||
services.viewPreferences.updatePreferences({appDetails: {...pref, groupNodes: !pref.groupNodes}});
|
||||
}
|
||||
|
||||
@@ -231,6 +232,7 @@ export class ApplicationDetails extends React.Component<RouteComponentProps<{app
|
||||
const syncResourceKey = new URLSearchParams(this.props.history.location.search).get('deploy');
|
||||
const tab = new URLSearchParams(this.props.history.location.search).get('tab');
|
||||
const source = getAppDefaultSource(application);
|
||||
const showToolTip = pref?.userHelpTipMsgs.find(usrMsg => usrMsg.appName === application.metadata.name);
|
||||
const resourceNodes = (): any[] => {
|
||||
const statusByKey = new Map<string, models.ResourceStatus>();
|
||||
application.status.resources.forEach(res => statusByKey.set(AppUtils.nodeKey(res), res));
|
||||
@@ -296,6 +298,14 @@ export class ApplicationDetails extends React.Component<RouteComponentProps<{app
|
||||
const setShowCompactNodes = (showCompactView: boolean) => {
|
||||
services.viewPreferences.updatePreferences({appDetails: {...pref, groupNodes: showCompactView}});
|
||||
};
|
||||
const updateHelpTipState = (usrHelpTip: models.UserMessages) => {
|
||||
const existingIndex = pref.userHelpTipMsgs.findIndex(msg => msg.appName === usrHelpTip.appName && msg.msgKey === usrHelpTip.msgKey);
|
||||
if (existingIndex !== -1) {
|
||||
pref.userHelpTipMsgs[existingIndex] = usrHelpTip;
|
||||
} else {
|
||||
(pref.userHelpTipMsgs || []).push(usrHelpTip);
|
||||
}
|
||||
};
|
||||
const toggleNameDirection = () => {
|
||||
this.setState({truncateNameOnRight: !this.state.truncateNameOnRight});
|
||||
};
|
||||
@@ -446,13 +456,19 @@ export class ApplicationDetails extends React.Component<RouteComponentProps<{app
|
||||
/>
|
||||
</a>
|
||||
{(pref.view === 'tree' || pref.view === 'network') && (
|
||||
<a
|
||||
className={`group-nodes-button group-nodes-button${!pref.groupNodes ? '' : '-on'}`}
|
||||
title={pref.view === 'tree' ? 'Group Nodes' : 'Collapse Pods'}
|
||||
onClick={() => this.toggleCompactView(pref)}>
|
||||
<i className={classNames('fa fa-object-group fa-fw')} />
|
||||
</a>
|
||||
<Tooltip
|
||||
content={AppUtils.userMsgsList[showToolTip?.msgKey] || 'Group Nodes'}
|
||||
visible={pref.groupNodes && showToolTip !== undefined && !showToolTip?.display}
|
||||
duration={showToolTip?.duration}>
|
||||
<a
|
||||
className={`group-nodes-button group-nodes-button${!pref.groupNodes ? '' : '-on'}`}
|
||||
title={pref.view === 'tree' ? 'Group Nodes' : 'Collapse Pods'}
|
||||
onClick={() => this.toggleCompactView(application.metadata.name, pref)}>
|
||||
<i className={classNames('fa fa-object-group fa-fw')} />
|
||||
</a>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
<span className={`separator`} />
|
||||
<a className={`group-nodes-button`} onClick={() => expandAll()} title='Expand all child nodes of all parent nodes'>
|
||||
<i className='fa fa-plus fa-fw' />
|
||||
@@ -481,6 +497,7 @@ export class ApplicationDetails extends React.Component<RouteComponentProps<{app
|
||||
)
|
||||
}
|
||||
showCompactNodes={pref.groupNodes}
|
||||
userMsgs={pref.userHelpTipMsgs}
|
||||
tree={tree}
|
||||
app={application}
|
||||
showOrphanedResources={pref.orphanedResources}
|
||||
@@ -493,6 +510,7 @@ export class ApplicationDetails extends React.Component<RouteComponentProps<{app
|
||||
nameDirection={this.state.truncateNameOnRight}
|
||||
filters={pref.resourceFilter}
|
||||
setTreeFilterGraph={setFilterGraph}
|
||||
updateUsrHelpTipMsgs={updateHelpTipState}
|
||||
setShowCompactNodes={setShowCompactNodes}
|
||||
setNodeExpansion={(node, isExpanded) => this.setNodeExpansion(node, isExpanded)}
|
||||
getNodeExpansion={node => this.getNodeExpansion(node)}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {DropDown, DropDownMenu, NotificationType, Tooltip} from 'argo-ui';
|
||||
import {DropDown, DropDownMenu, Tooltip} from 'argo-ui';
|
||||
import * as classNames from 'classnames';
|
||||
import * as dagre from 'dagre';
|
||||
import * as React from 'react';
|
||||
@@ -22,7 +22,8 @@ import {
|
||||
isYoungerThanXMinutes,
|
||||
NodeId,
|
||||
nodeKey,
|
||||
PodHealthIcon
|
||||
PodHealthIcon,
|
||||
getUsrMsgKeyToDisplay
|
||||
} from '../utils';
|
||||
import {NodeUpdateAnimation} from './node-update-animation';
|
||||
import {PodGroup} from '../application-pod-view/pod-view';
|
||||
@@ -59,6 +60,8 @@ export interface ApplicationResourceTreeProps {
|
||||
appContext?: AppContext;
|
||||
showOrphanedResources: boolean;
|
||||
showCompactNodes: boolean;
|
||||
userMsgs: models.UserMessages[];
|
||||
updateUsrHelpTipMsgs: (userMsgs: models.UserMessages) => void;
|
||||
setShowCompactNodes: (showCompactNodes: boolean) => void;
|
||||
zoom: number;
|
||||
podGroupCount: number;
|
||||
@@ -927,6 +930,7 @@ export const ApplicationResourceTree = (props: ApplicationResourceTreeProps) =>
|
||||
const [filters, setFilters] = React.useState(props.filters);
|
||||
const [filteredGraph, setFilteredGraph] = React.useState([]);
|
||||
const filteredNodes: any[] = [];
|
||||
|
||||
React.useEffect(() => {
|
||||
if (props.filters !== filters) {
|
||||
setFilters(props.filters);
|
||||
@@ -934,19 +938,16 @@ export const ApplicationResourceTree = (props: ApplicationResourceTreeProps) =>
|
||||
props.setTreeFilterGraph(filteredGraph);
|
||||
}
|
||||
}, [props.filters]);
|
||||
|
||||
const {podGroupCount, userMsgs, updateUsrHelpTipMsgs, setShowCompactNodes} = props;
|
||||
const podCount = nodes.filter(node => node.kind === 'Pod').length;
|
||||
|
||||
React.useEffect(() => {
|
||||
const {podGroupCount, setShowCompactNodes, appContext} = props;
|
||||
if (podCount > podGroupCount) {
|
||||
setShowCompactNodes(true);
|
||||
appContext.apis.notifications.show({
|
||||
content: `Since the number of pods has surpassed the threshold pod count of ${podGroupCount}, you will now be switched to the group node view.
|
||||
If you prefer the tree view, you can simply click on the Group Nodes toolbar button to deselect the current view.`,
|
||||
type: NotificationType.Success
|
||||
});
|
||||
} else {
|
||||
props.setShowCompactNodes(false);
|
||||
const userMsg = getUsrMsgKeyToDisplay(appNode.name, 'groupNodes', userMsgs);
|
||||
updateUsrHelpTipMsgs(userMsg);
|
||||
if (!userMsg.display) {
|
||||
setShowCompactNodes(true);
|
||||
}
|
||||
}
|
||||
}, [podCount]);
|
||||
|
||||
|
||||
@@ -72,7 +72,13 @@ export const PodTerminalViewer: React.FC<PodTerminalViewerProps> = ({
|
||||
|
||||
const onConnectionMessage = (e: MessageEvent) => {
|
||||
const msg = JSON.parse(e.data);
|
||||
connSubject.next(msg);
|
||||
if (!msg?.Code) {
|
||||
connSubject.next(msg);
|
||||
} else {
|
||||
// Do reconnect due to refresh token event
|
||||
onConnectionClose();
|
||||
setupConnection();
|
||||
}
|
||||
};
|
||||
|
||||
const onConnectionOpen = () => {
|
||||
|
||||
@@ -1252,3 +1252,17 @@ export function formatCreationTimestamp(creationTimestamp: string) {
|
||||
}
|
||||
|
||||
export const selectPostfix = (arr: string[], singular: string, plural: string) => (arr.length > 1 ? plural : singular);
|
||||
|
||||
export function getUsrMsgKeyToDisplay(appName: string, msgKey: string, usrMessages: appModels.UserMessages[]) {
|
||||
const usrMsg = usrMessages?.find((msg: appModels.UserMessages) => msg.appName === appName && msg.msgKey === msgKey);
|
||||
if (usrMsg !== undefined) {
|
||||
return {...usrMsg, display: true};
|
||||
} else {
|
||||
return {appName, msgKey, display: false, duration: 1} as appModels.UserMessages;
|
||||
}
|
||||
}
|
||||
|
||||
export const userMsgsList: {[key: string]: string} = {
|
||||
groupNodes: `Since the number of pods has surpassed the threshold pod count of 15, you will now be switched to the group node view.
|
||||
If you prefer the tree view, you can simply click on the Group Nodes toolbar button to deselect the current view.`
|
||||
};
|
||||
|
||||
@@ -105,7 +105,7 @@ export class VersionPanel extends React.Component<VersionPanelProps, {copyState:
|
||||
}
|
||||
|
||||
private async onCopy(version: VersionMessage): Promise<void> {
|
||||
const stringifiedVersion = JSON.stringify(version, undefined, 4);
|
||||
const stringifiedVersion = JSON.stringify(version, undefined, 4) + '\n';
|
||||
try {
|
||||
await navigator.clipboard.writeText(stringifiedVersion);
|
||||
this.setState({copyState: 'success'});
|
||||
|
||||
@@ -955,3 +955,12 @@ export interface LinkInfo {
|
||||
export interface LinksResponse {
|
||||
items: LinkInfo[];
|
||||
}
|
||||
|
||||
export interface UserMessages {
|
||||
appName: string;
|
||||
msgKey: string;
|
||||
display: boolean;
|
||||
condition?: HealthStatusCode;
|
||||
duration?: number;
|
||||
animation?: string;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import * as deepMerge from 'deepmerge';
|
||||
import {BehaviorSubject, Observable} from 'rxjs';
|
||||
|
||||
import {PodGroupType} from '../../applications/components/application-pod-view/pod-view';
|
||||
import {UserMessages} from '../models';
|
||||
|
||||
export type AppsDetailsViewType = 'tree' | 'network' | 'list' | 'pods';
|
||||
|
||||
@@ -28,6 +29,7 @@ export interface AppDetailsPreferences {
|
||||
groupNodes?: boolean;
|
||||
zoom: number;
|
||||
podGroupCount: number;
|
||||
userHelpTipMsgs: UserMessages[];
|
||||
}
|
||||
|
||||
export interface PodViewPreferences {
|
||||
@@ -122,7 +124,8 @@ const DEFAULT_PREFERENCES: ViewPreferences = {
|
||||
followLogs: false,
|
||||
wrapLines: false,
|
||||
zoom: 1.0,
|
||||
podGroupCount: 15.0
|
||||
podGroupCount: 15.0,
|
||||
userHelpTipMsgs: []
|
||||
},
|
||||
appList: {
|
||||
view: 'tiles' as AppsListViewType,
|
||||
|
||||
33
util/grpc/trace.go
Normal file
33
util/grpc/trace.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
var (
|
||||
otelUnaryInterceptor grpc.UnaryClientInterceptor
|
||||
otelStreamInterceptor grpc.StreamClientInterceptor
|
||||
interceptorsInitialized = sync.Once{}
|
||||
)
|
||||
|
||||
// otel interceptors must be created once to avoid memory leak
|
||||
// see https://github.com/open-telemetry/opentelemetry-go-contrib/issues/4226 for details
|
||||
func ensureInitialized() {
|
||||
interceptorsInitialized.Do(func() {
|
||||
otelUnaryInterceptor = otelgrpc.UnaryClientInterceptor()
|
||||
otelStreamInterceptor = otelgrpc.StreamClientInterceptor()
|
||||
})
|
||||
}
|
||||
|
||||
func OTELUnaryClientInterceptor() grpc.UnaryClientInterceptor {
|
||||
ensureInitialized()
|
||||
return otelUnaryInterceptor
|
||||
}
|
||||
|
||||
func OTELStreamClientInterceptor() grpc.StreamClientInterceptor {
|
||||
ensureInitialized()
|
||||
return otelStreamInterceptor
|
||||
}
|
||||
@@ -18,8 +18,8 @@ import (
|
||||
const maxCookieLength = 4093
|
||||
|
||||
// max number of chunks a cookie can be broken into. To be compatible with
|
||||
// widest range of browsers, we shouldn't create more than 30 cookies per domain
|
||||
var maxCookieNumber = env.ParseNumFromEnv(common.EnvMaxCookieNumber, 10, 0, 30)
|
||||
// widest range of browsers, you shouldn't create more than 30 cookies per domain
|
||||
var maxCookieNumber = env.ParseNumFromEnv(common.EnvMaxCookieNumber, 20, 0, math.MaxInt64)
|
||||
|
||||
// MakeCookieMetadata generates a string representing a Web cookie. Yum!
|
||||
func MakeCookieMetadata(key, value string, flags ...string) ([]string, error) {
|
||||
|
||||
@@ -15,10 +15,18 @@ func TestCookieMaxLength(t *testing.T) {
|
||||
|
||||
// keys will be of format foo, foo-1, foo-2 ..
|
||||
cookies, err = MakeCookieMetadata("foo", strings.Repeat("_", (maxCookieLength-5)*maxCookieNumber))
|
||||
assert.EqualError(t, err, "the authentication token is 40880 characters long and requires 11 cookies but the max number of cookies is 10. Contact your Argo CD administrator to increase the max number of cookies")
|
||||
assert.EqualError(t, err, "the authentication token is 81760 characters long and requires 21 cookies but the max number of cookies is 20. Contact your Argo CD administrator to increase the max number of cookies")
|
||||
assert.Equal(t, 0, len(cookies))
|
||||
}
|
||||
|
||||
func TestCookieWithAttributes(t *testing.T) {
|
||||
flags := []string{"SameSite=lax", "httpOnly"}
|
||||
|
||||
cookies, err := MakeCookieMetadata("foo", "bar", flags...)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "foo=bar; SameSite=lax; httpOnly", cookies[0])
|
||||
}
|
||||
|
||||
func TestSplitCookie(t *testing.T) {
|
||||
cookieValue := strings.Repeat("_", (maxCookieLength-6)*4)
|
||||
cookies, err := MakeCookieMetadata("foo", cookieValue)
|
||||
|
||||
Reference in New Issue
Block a user