Skip to content

Commit

Permalink
Merge branch 'master' into 9727-in-cluster-name-normalized
Browse files Browse the repository at this point in the history
  • Loading branch information
lacarvalho91 committed Jun 27, 2022
2 parents 3aab82d + a041bf8 commit 497fcab
Show file tree
Hide file tree
Showing 66 changed files with 1,350 additions and 342 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci-build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ jobs:
runs-on: ubuntu-22.04
strategy:
matrix:
k3s-version: [v1.23.3, v1.22.6, v1.21.2]
k3s-version: [v1.24.1, v1.23.3, v1.22.6, v1.21.2]
needs:
- build-go
env:
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ RUN ln -s /usr/local/bin/entrypoint.sh /usr/local/bin/uid_entrypoint.sh
# support for mounting configuration from a configmap
WORKDIR /app/config/ssh
RUN touch ssh_known_hosts && \
ln -s ssh_known_hosts /etc/ssh/ssh_known_hosts
ln -s /app/config/ssh/ssh_known_hosts /etc/ssh/ssh_known_hosts

WORKDIR /app/config
RUN mkdir -p tls && \
Expand Down
54 changes: 47 additions & 7 deletions applicationset/generators/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package generators
import (
"context"
"fmt"
"github.com/valyala/fasttemplate"
"regexp"
"strings"
"time"
Expand Down Expand Up @@ -37,6 +38,8 @@ type ClusterGenerator struct {
settingsManager *settings.SettingsManager
}

var render = &utils.Render{}

func NewClusterGenerator(c client.Client, ctx context.Context, clientset kubernetes.Interface, namespace string) Generator {

settingsManager := settings.NewSettingsManager(ctx, clientset, namespace)
Expand Down Expand Up @@ -107,19 +110,21 @@ func (g *ClusterGenerator) GenerateParams(
params["nameNormalized"] = cluster.Name
params["server"] = cluster.Server

for key, value := range appSetGenerator.Clusters.Values {
params[fmt.Sprintf("values.%s", key)] = value
err = appendTemplatedValues(appSetGenerator.Clusters.Values, params)
if err != nil {
return nil, err
}

log.WithField("cluster", "local cluster").Info("matched local cluster")

res = append(res, params)

log.WithField("cluster", "local cluster").Info("matched local cluster")
}
}

// For each matching cluster secret (non-local clusters only)
for _, cluster := range secretsFound {
params := map[string]string{}

params["name"] = string(cluster.Data["name"])
params["nameNormalized"] = sanitizeName(string(cluster.Data["name"]))
params["server"] = string(cluster.Data["server"])
Expand All @@ -129,17 +134,52 @@ func (g *ClusterGenerator) GenerateParams(
for key, value := range cluster.ObjectMeta.Labels {
params[fmt.Sprintf("metadata.labels.%s", key)] = value
}
for key, value := range appSetGenerator.Clusters.Values {
params[fmt.Sprintf("values.%s", key)] = value

err = appendTemplatedValues(appSetGenerator.Clusters.Values, params)
if err != nil {
return nil, err
}
log.WithField("cluster", cluster.Name).Info("matched cluster secret")

res = append(res, params)

log.WithField("cluster", cluster.Name).Info("matched cluster secret")
}

return res, nil
}

func appendTemplatedValues(clusterValues map[string]string, params map[string]string) error {
// We create a local map to ensure that we do not fall victim to a billion-laughs attack. We iterate through the
// cluster values map and only replace values in said map if it has already been whitelisted in the params map.
// Once we iterate through all the cluster values we can then safely merge the `tmp` map into the main params map.
tmp := map[string]string{}

for key, value := range clusterValues {
result, err := replaceTemplatedString(value, params)

if err != nil {
return err
}

tmp[fmt.Sprintf("values.%s", key)] = result
}

for key, value := range tmp {
params[key] = value
}

return nil
}

func replaceTemplatedString(value string, params map[string]string) (string, error) {
fstTmpl := fasttemplate.New(value, "{{", "}}")
replacedTmplStr, err := render.Replace(fstTmpl, params, true)
if err != nil {
return "", err
}
return replacedTmplStr, nil
}

func (g *ClusterGenerator) getSecretsByClusterName(appSetGenerator *argoappsetv1alpha1.ApplicationSetGenerator) (map[string]corev1.Secret, error) {
// List all Clusters:
clusterSecretList := &corev1.SecretList{}
Expand Down
18 changes: 13 additions & 5 deletions applicationset/generators/cluster_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,15 +94,23 @@ func TestGenerateParams(t *testing.T) {
{
name: "no label selector",
selector: metav1.LabelSelector{},
values: nil,
expected: []map[string]string{
{"name": "production_01/west", "nameNormalized": "production-01-west", "server": "https://production-01.example.com", "metadata.labels.environment": "production", "metadata.labels.org": "bar",
values: map[string]string{
"lol1": "lol",
"lol2": "{{values.lol1}}{{values.lol1}}",
"lol3": "{{values.lol2}}{{values.lol2}}{{values.lol2}}",
"foo": "bar",
"bar": "{{ metadata.annotations.foo.argoproj.io }}",
"bat": "{{ metadata.labels.environment }}",
"aaa": "{{ server }}",
"no-op": "{{ this-does-not-exist }}",
}, expected: []map[string]string{
{"values.lol1": "lol", "values.lol2": "{{values.lol1}}{{values.lol1}}", "values.lol3": "{{values.lol2}}{{values.lol2}}{{values.lol2}}", "values.foo": "bar", "values.bar": "production", "values.no-op": "{{ this-does-not-exist }}", "values.bat": "production", "values.aaa": "https://production-01.example.com", "name": "production_01/west", "nameNormalized": "production-01-west", "server": "https://production-01.example.com", "metadata.labels.environment": "production", "metadata.labels.org": "bar",
"metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.annotations.foo.argoproj.io": "production"},

{"name": "staging-01", "nameNormalized": "staging-01", "server": "https://staging-01.example.com", "metadata.labels.environment": "staging", "metadata.labels.org": "foo",
{"values.lol1": "lol", "values.lol2": "{{values.lol1}}{{values.lol1}}", "values.lol3": "{{values.lol2}}{{values.lol2}}{{values.lol2}}", "values.foo": "bar", "values.bar": "staging", "values.no-op": "{{ this-does-not-exist }}", "values.bat": "staging", "values.aaa": "https://staging-01.example.com", "name": "staging-01", "nameNormalized": "staging-01", "server": "https://staging-01.example.com", "metadata.labels.environment": "staging", "metadata.labels.org": "foo",
"metadata.labels.argocd.argoproj.io/secret-type": "cluster", "metadata.annotations.foo.argoproj.io": "staging"},

{"name": "in-cluster", "nameNormalized": "in-cluster", "server": "https://kubernetes.default.svc"},
{"values.lol1": "lol", "values.lol2": "{{values.lol1}}{{values.lol1}}", "values.lol3": "{{values.lol2}}{{values.lol2}}{{values.lol2}}", "values.foo": "bar", "values.bar": "{{ metadata.annotations.foo.argoproj.io }}", "values.no-op": "{{ this-does-not-exist }}", "values.bat": "{{ metadata.labels.environment }}", "values.aaa": "https://kubernetes.default.svc", "nameNormalized": "in-cluster", "name": "in-cluster", "server": "https://kubernetes.default.svc"},
},
clientError: false,
expectedError: nil,
Expand Down
2 changes: 1 addition & 1 deletion applicationset/utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ func (r *Render) RenderTemplateParams(tmpl *argoappsv1.Application, syncPolicy *
}

// Replace executes basic string substitution of a template with replacement values.
// 'allowUnresolved' indicates whether or not it is acceptable to have unresolved variables
// 'allowUnresolved' indicates whether it is acceptable to have unresolved variables
// remaining in the substituted template.
func (r *Render) Replace(fstTmpl *fasttemplate.Template, replaceMap map[string]string, allowUnresolved bool) (string, error) {
var unresolvedErr error
Expand Down
27 changes: 17 additions & 10 deletions cmd/argocd-repo-server/commands/argocd_repo_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"google.golang.org/grpc/health/grpc_health_v1"
"k8s.io/apimachinery/pkg/api/resource"

cmdutil "github.com/argoproj/argo-cd/v2/cmd/util"
"github.com/argoproj/argo-cd/v2/common"
Expand Down Expand Up @@ -70,15 +71,16 @@ func getSubmoduleEnabled() bool {

func NewCommand() *cobra.Command {
var (
parallelismLimit int64
listenPort int
metricsPort int
otlpAddress string
cacheSrc func() (*reposervercache.Cache, error)
tlsConfigCustomizer tls.ConfigCustomizer
tlsConfigCustomizerSrc func() (tls.ConfigCustomizer, error)
redisClient *redis.Client
disableTLS bool
parallelismLimit int64
listenPort int
metricsPort int
otlpAddress string
cacheSrc func() (*reposervercache.Cache, error)
tlsConfigCustomizer tls.ConfigCustomizer
tlsConfigCustomizerSrc func() (tls.ConfigCustomizer, error)
redisClient *redis.Client
disableTLS bool
maxCombinedDirectoryManifestsSize string
)
var command = cobra.Command{
Use: cliName,
Expand All @@ -98,15 +100,19 @@ func NewCommand() *cobra.Command {
cache, err := cacheSrc()
errors.CheckError(err)

maxCombinedDirectoryManifestsQuantity, err := resource.ParseQuantity(maxCombinedDirectoryManifestsSize)
errors.CheckError(err)

askPassServer := askpass.NewServer()
metricsServer := metrics.NewMetricsServer()
cacheutil.CollectMetrics(redisClient, metricsServer)
server, err := reposerver.NewServer(metricsServer, cache, tlsConfigCustomizer, repository.RepoServerInitConstants{
ParallelismLimit: parallelismLimit,
ParallelismLimit: parallelismLimit,
PauseGenerationAfterFailedGenerationAttempts: getPauseGenerationAfterFailedGenerationAttempts(),
PauseGenerationOnFailureForMinutes: getPauseGenerationOnFailureForMinutes(),
PauseGenerationOnFailureForRequests: getPauseGenerationOnFailureForRequests(),
SubmoduleEnabled: getSubmoduleEnabled(),
MaxCombinedDirectoryManifestsSize: maxCombinedDirectoryManifestsQuantity,
}, askPassServer)
errors.CheckError(err)

Expand Down Expand Up @@ -182,6 +188,7 @@ func NewCommand() *cobra.Command {
command.Flags().IntVar(&metricsPort, "metrics-port", common.DefaultPortRepoServerMetrics, "Start metrics server on given port")
command.Flags().StringVar(&otlpAddress, "otlp-address", env.StringFromEnv("ARGOCD_REPO_SERVER_OTLP_ADDRESS", ""), "OpenTelemetry collector address to send traces to")
command.Flags().BoolVar(&disableTLS, "disable-tls", env.ParseBoolFromEnv("ARGOCD_REPO_SERVER_DISABLE_TLS", false), "Disable TLS on the gRPC endpoint")
command.Flags().StringVar(&maxCombinedDirectoryManifestsSize, "max-combined-directory-manifests-size", env.StringFromEnv("ARGOCD_REPO_SERVER_MAX_COMBINED_DIRECTORY_MANIFESTS_SIZE", "10M"), "Max combined size of manifest files in a directory-type Application")

tlsConfigCustomizerSrc = tls.AddTLSFlagsToCmd(&command)
cacheSrc = reposervercache.AddCacheFlagsToCmd(&command, func(client *redis.Client) {
Expand Down
7 changes: 4 additions & 3 deletions cmd/argocd-server/commands/argocd_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,9 @@ func NewCommand() *cobra.Command {
stats.RegisterStackDumper()
stats.StartStatsTicker(10 * time.Minute)
stats.RegisterHeapDumper("memprofile")

argocd := server.NewServer(context.Background(), argoCDOpts)
lns, err := argocd.Listen()
errors.CheckError(err)
for {
var closer func()
ctx := context.Background()
Expand All @@ -163,8 +165,7 @@ func NewCommand() *cobra.Command {
log.Fatalf("failed to initialize tracing: %v", err)
}
}
argocd := server.NewServer(ctx, argoCDOpts)
argocd.Run(ctx, listenPort, metricsPort)
argocd.Run(ctx, lns)
cancel()
if closer != nil {
closer()
Expand Down
3 changes: 2 additions & 1 deletion cmd/argocd/commands/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"github.com/spf13/cobra"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"k8s.io/apimachinery/pkg/api/resource"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/utils/pointer"
Expand Down Expand Up @@ -764,7 +765,7 @@ func getLocalObjectsString(app *argoappv1.Application, local, localRepoRoot, app
ApiVersions: apiVersions,
Plugins: configManagementPlugins,
TrackingMethod: trackingMethod,
}, true, &git.NoopCredsStore{})
}, true, &git.NoopCredsStore{}, resource.MustParse("0"))
errors.CheckError(err)

return res.Manifests
Expand Down
6 changes: 5 additions & 1 deletion cmd/argocd/commands/headless/headless.go
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,11 @@ func StartLocalServer(clientOpts *apiclient.ClientOptions, ctxStr string, port *
RepoClientset: &forwardRepoClientset{namespace: namespace, context: ctxStr},
})

go srv.Run(ctx, *port, 0)
lns, err := srv.Listen()
if err != nil {
return err
}
go srv.Run(ctx, lns)
clientOpts.ServerAddr = fmt.Sprintf("%s:%d", *address, *port)
clientOpts.PlainText = true
if !cache2.WaitForCacheSync(ctx.Done(), srv.Initialized) {
Expand Down
11 changes: 8 additions & 3 deletions cmd/argocd/commands/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,10 @@ func oauth2Login(ctx context.Context, port int, oidcSettings *settingspkg.OIDCCo
// completionChan is to signal flow completed. Non-empty string indicates error
completionChan := make(chan string)
// stateNonce is an OAuth2 state nonce
stateNonce := rand.RandString(10)
// According to the spec (https://www.rfc-editor.org/rfc/rfc6749#section-10.10), this must be guessable with
// probability <= 2^(-128). The following call generates one of 52^24 random strings, ~= 2^136 possibilities.
stateNonce, err := rand.String(24)
errors.CheckError(err)
var tokenString string
var refreshToken string

Expand All @@ -212,7 +215,8 @@ func oauth2Login(ctx context.Context, port int, oidcSettings *settingspkg.OIDCCo
}

// PKCE implementation of https://tools.ietf.org/html/rfc7636
codeVerifier := rand.RandStringCharset(43, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~")
codeVerifier, err := rand.StringFromCharset(43, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~")
errors.CheckError(err)
codeChallengeHash := sha256.Sum256([]byte(codeVerifier))
codeChallenge := base64.RawURLEncoding.EncodeToString(codeChallengeHash[:])

Expand Down Expand Up @@ -296,7 +300,8 @@ func oauth2Login(ctx context.Context, port int, oidcSettings *settingspkg.OIDCCo
opts = append(opts, oauth2.SetAuthURLParam("code_challenge_method", "S256"))
url = oauth2conf.AuthCodeURL(stateNonce, opts...)
case oidcutil.GrantTypeImplicit:
url = oidcutil.ImplicitFlowURL(oauth2conf, stateNonce, opts...)
url, err = oidcutil.ImplicitFlowURL(oauth2conf, stateNonce, opts...)
errors.CheckError(err)
default:
log.Fatalf("Unsupported grant type: %v", grantType)
}
Expand Down
8 changes: 7 additions & 1 deletion controller/sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,13 @@ func (m *appStateManager) SyncAppState(app *v1alpha1.Application, state *v1alpha
}

atomic.AddUint64(&syncIdPrefix, 1)
syncId := fmt.Sprintf("%05d-%s", syncIdPrefix, rand.RandString(5))
randSuffix, err := rand.String(5)
if err != nil {
state.Phase = common.OperationError
state.Message = fmt.Sprintf("Failed generate random sync ID: %v", err)
return
}
syncId := fmt.Sprintf("%05d-%s", syncIdPrefix, randSuffix)

logEntry := log.WithFields(log.Fields{"application": app.Name, "syncId": syncId})
initialResourcesRes := make([]common.ResourceSyncResult, 0)
Expand Down
46 changes: 46 additions & 0 deletions docs/operator-manual/applicationset/Generators-Cluster.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,3 +159,49 @@ In this example the `revision` value from the `generators.clusters` fields is pa

!!! note
The `values.` prefix is always prepended to values provided via `generators.clusters.values` field. Ensure you include this prefix in the parameter name within the `template` when using it.

In `values` we can also interpolate the following parameter values (i.e. the same values as presented in the beginning of this page)

- `name`
- `nameNormalized` *('name' but normalized to contain only lowercase alphanumeric characters, '-' or '.')*
- `server`
- `metadata.labels.<key>` *(for each label in the Secret)*
- `metadata.annotations.<key>` *(for each annotation in the Secret)*

Extending the example above, we could do something like this:

```yaml
spec:
generators:
- clusters:
selector:
matchLabels:
type: 'staging'
# A key-value map for arbitrary parameters
values:
# If `my-custom-annotation` is in your cluster secret, `revision` will be substituted with it.
revision: '{{metadata.annotations.my-custom-annotation}}'
clusterName: '{{name}}'
- clusters:
selector:
matchLabels:
type: 'production'
values:
# production uses a different revision value, for 'stable' branch
revision: stable
clusterName: '{{name}}'
template:
metadata:
name: '{{name}}-guestbook'
spec:
project: "my-project"
source:
repoURL: https://github.com/argoproj/argocd-example-apps/
# The cluster values field for each generator will be substituted here:
targetRevision: '{{values.revision}}'
path: guestbook
destination:
# In this case this is equivalent to just using {{name}}
server: '{{values.clusterName}}'
namespace: guestbook
```
6 changes: 5 additions & 1 deletion docs/operator-manual/argocd-cmd-params-cm.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -106,4 +106,8 @@ data:
reposerver.repo.cache.expiration: "24h0m0s"
# Cache expiration default (default 24h0m0s)
reposerver.default.cache.expiration: "24h0m0s"

# Max combined manifest file size for a single directory-type Application. In-memory manifest representation may be as
# much as 300x the manifest file size. Limit this to stay within the memory limits of the repo-server while allowing
# for 300x memory expansion and N Applications running at the same time.
# (example 10M max * 300 expansion * 10 Apps = 30G max theoretical memory usage).
reposerver.max.combined.directory.manifests.size: '10M'

0 comments on commit 497fcab

Please sign in to comment.