diff --git a/ai-tooling/user/skills/nvcf-self-managed-cli/examples/ci-pipelines.md b/ai-tooling/user/skills/nvcf-self-managed-cli/examples/ci-pipelines.md index e854a34373..1376210db0 100644 --- a/ai-tooling/user/skills/nvcf-self-managed-cli/examples/ci-pipelines.md +++ b/ai-tooling/user/skills/nvcf-self-managed-cli/examples/ci-pipelines.md @@ -12,20 +12,24 @@ deploy_nvcf: variables: KUBECONFIG: $KUBECONFIG_FILE script: - - nvcf-cli self-hosted check --pre --json | jq -e '.event != "phase_failed"' || exit 2 + - nvcf-cli self-hosted check --pre --json 2>&1 >/dev/null | grep '^{' | jq -se 'any(.[]; .event == "final" and .success)' || exit 2 - nvcf-cli self-hosted up --cluster-name=$CLUSTER_NAME --token=$NVCF_ADMIN_JWT --non-interactive --json - - nvcf-cli self-hosted status --json | jq -e '.verdict == "healthy"' + - nvcf-cli self-hosted status --json 2>&1 >/dev/null | grep '^{' | jq -se 'any(.[]; .verdict == "healthy")' ``` Notes: - `--non-interactive --token=$JWT` is required in CI; never use interactive `init`. - Always `--json` for machine-parsing. +- `--json` writes JSONL to stderr, not stdout, so redirect with `2>&1 >/dev/null` before a parser. +- `check` reports its verdict in one `final` event, with `success: false` when any check failed at error severity. Gate on that event: `check` never emits `phase_failed`, so a condition on it always passes. Requiring the `final` event also fails the step when the command dies before emitting it. +- Slurp with `jq -s` before testing a condition. Without it `jq -e` takes its exit status from the last event alone. +- stderr also carries plain-text notices, so filter to JSON lines. Do not add `--show-logs` here: it appends a non-JSON transcript to the same stream. - Final status check gates downstream stages on `verdict == "healthy"`. ## GitOps (Argo / Flux) pattern -CI doesn't `kubectl apply` — instead, render manifests, commit them, let the controller apply. +CI doesn't `kubectl apply`. Instead, render manifests, commit them, let the controller apply. ```yaml render: diff --git a/ai-tooling/user/skills/nvcf-self-managed-cli/reference/exit-codes.md b/ai-tooling/user/skills/nvcf-self-managed-cli/reference/exit-codes.md index fe27d62671..5050ca05c9 100644 --- a/ai-tooling/user/skills/nvcf-self-managed-cli/reference/exit-codes.md +++ b/ai-tooling/user/skills/nvcf-self-managed-cli/reference/exit-codes.md @@ -6,9 +6,9 @@ Stable across subcommands. Use these to drive agent retry / surfacing logic. |---|---|---| | `0` | Success | All checks passed; install completed; deployment ACTIVE | | `1` | Generic error | Helm render failed; network unreachable; file not found; YAML parse error | -| `2` | Pre-flight check failed | Gateway API CRDs missing; kubectl not on PATH; default StorageClass absent | +| `2` | Pre-flight check failed | A `check` result at error severity: Gateway API CRDs missing; kubectl not on PATH; default StorageClass absent; a cluster-validator that could not run (RBAC denied, image pull failure, timeout). Warning-severity results exit `0` | | `3` | Admin auth failed | No token + `--non-interactive` set; ICMS rejected JWT; init endpoint unreachable | -| `5` | Manifest apply or `--wait` timed out | Helm install timeout; check polled but didn't pass before duration | +| `5` | Manifest apply or `--wait` timed out | Helm install timeout; check polled but didn't pass before duration; the check's time budget ran out before every check ran (those report as not run) | | `130` | Cancelled by SIGINT/SIGTERM | User Ctrl-C; CI budget exceeded; pod evicted | ## How an agent should react @@ -24,4 +24,4 @@ Stable across subcommands. Use these to drive agent retry / surfacing logic. ## Where to find more detail -Every non-zero exit emits a structured `phase_failed` JSON event with `errCategory`, `errMessage`, `remediation` (array), `retryClass` (enum: `none|immediate|backoff|after_remediation|unknown`), `retryAfterSec` (int, optional), and `raw` (subprocess + HTTP + Kubernetes signal). Always consume these in `--json` mode rather than parsing English from stderr. +`check` reports its outcome in one `final` event (`success`, `verdict`, `failedCount`) and never emits `phase_failed`; see `examples/ci-pipelines.md`. A cancellation (exit `130`) emits `final` with `cancelled: true` instead of `phase_failed`; `up` emits `phase_cancelled` before it. Every other non-zero exit emits a structured `phase_failed` JSON event with `errCategory`, `errMessage`, `remediation` (array), `retryClass` (enum: `none|immediate|backoff|after_remediation|unknown`), `retryAfterSec` (int, optional), and `raw` (subprocess + HTTP + Kubernetes signal). Always consume these in `--json` mode rather than parsing English from stderr. diff --git a/ai-tooling/user/skills/nvcf-self-managed-cli/reference/flags.md b/ai-tooling/user/skills/nvcf-self-managed-cli/reference/flags.md index 65bcbb6b97..b2e0c8aecf 100644 --- a/ai-tooling/user/skills/nvcf-self-managed-cli/reference/flags.md +++ b/ai-tooling/user/skills/nvcf-self-managed-cli/reference/flags.md @@ -12,19 +12,49 @@ | Flag | Purpose | Default | |---|---|---| -| `--control-plane-stack=…` | Control-plane bundle source: local path, git URL, or `oci://` URL | embedded OCI URL pinned by CLI version | -| `--compute-plane-stack=…` | Compute-plane bundle source: local path, git URL, or `oci://` URL | embedded OCI URL pinned by CLI version | -| `--env=local\|prd\|…` | Helmfile environment name | `local` for dev builds, `prd` for releases | +| `--control-plane-stack=...` | Control-plane bundle source: local path, git URL, or `oci://` URL | embedded OCI URL pinned by CLI version | +| `--compute-plane-stack=...` | Compute-plane bundle source: local path, git URL, or `oci://` URL | embedded OCI URL pinned by CLI version | +| `--env=local\|prd\|...` | Helmfile environment name | `local` for dev builds, `prd` for releases | | `--non-interactive` | Disable all stdin prompts | `false` | | `--token=$JWT` | Admin JWT, overrides stored session | - | | `--no-apply` | `install` only - emit YAML, do not kubectl apply | `false` | | `--output=text\|json` | Legacy alias for `--json` (deprecated, removed in next major) | `text` | | `--plain` | Force plain streaming output | auto-detect | -| `--wait DURATION` | `check` only; block until pass | - | +| `--wait DURATION` | `check` only; poll until the check passes or DURATION runs out (exit `5`). A warning that is expected to clear, such as a rollout in progress, keeps it polling. Cannot be combined with `--no-cleanup` | - | | `--control-plane-context CTX` | kubectl context for control plane (REQ-20) | current context | | `--compute-plane-context CTX` | kubectl context for compute plane (REQ-20) | current context | | `--icms-url URL` | Public ICMS URL; required when contexts differ | derived from `base_http_url` | -| `--local-only` | `check --pre` only; skip all kubectl contact | `false` | +| `--local-only` | `check` only; run the local-host checks and skip all kubectl contact, whatever scope flag is passed. The CLI prints a note saying so. Env: `NVCF_CLI_SELFHOSTED_LOCAL_ONLY` | `false` | + +## `check`-specific + +At least one of `--pre`, `--control-plane`, `--compute-plane`, or `--all` is +required. Each selects a role; only the selected roles contact a cluster. + +| Flag | Purpose | Default | +|---|---|---| +| `--pre` | Pre-flight: local-host tools plus cluster readiness. Skips SIS reachability, since SIS is not installed yet, unless `--all` or `--compute-plane` is also passed. With `--all`, or with a role's own flag, that role's validator checks the cluster as installed | `false` | +| `--control-plane` | Control-plane checks | `false` | +| `--compute-plane` | Compute-plane checks | `false` | +| `--all` | Every category, including SIS reachability when combined with `--pre` | `false` | +| `--cluster-name NAME` | Cluster name for compute-plane checks | - | +| `--skip-inotify-check` | Skip the per-node inotify-limits probe. Needed when the kubeconfig user cannot create pods in `default`. Env: `NVCF_CLI_SELFHOSTED_SKIP_INOTIFY` | `false` | + +### Cluster-validator flags + +The validator runs as a Job in the cluster being checked. The CLI creates a +ServiceAccount, ClusterRole, and ClusterRoleBinding for it and removes them +after the run, so the kubeconfig context needs permission to manage those. + +| Flag | Purpose | Default | +|---|---|---| +| `--cluster-validator-image REF` | Validator image. Resolution order: flag, `NVCF_CLI_CLUSTER_VALIDATOR_IMAGE`, config key `cluster_validator_image`. A ref with no tag discovers the latest stable tag from the registry; if no tag can be discovered, the probe is skipped with a note to pin one. Unset everywhere skips the probe with a warning | - | +| `--cluster-validator-registries host:port,...` | Extra registries the control-plane validator probes for reachability. They are added to the registries the install pulls from: the validator image's registry, the stack's `global.image.registry`, and `quay.io` for the cert-manager ACME solver unless the stack sets `certManager.acmesolver.image`. `nvcr.io` is probed only when neither the image nor the stack names a registry. The validator dials from a pod with no proxy, so an unreachable registry is a warning; the local credential check is what fails a registry the install cannot pull from. Repeatable or comma-separated. Env: `NVCF_CLI_CLUSTER_VALIDATOR_REGISTRIES`; config key `cluster_validator_registries` | - | +| `--cluster-validator-probe-image REF` | Image for the control-plane validator's node-to-node overlay probe. Needs `sh` and a busybox-style `nc`. Set a mirror for air-gapped clusters. Env: `NVCF_CLI_CLUSTER_VALIDATOR_PROBE_IMAGE`; config key `cluster_validator_probe_image` | `busybox:1.36` from Docker Hub | +| `--cluster-validator-pull-secret NAME` | docker-registry Secret in `default` used to pull the validator image. When empty, the CLI looks for one in the NVCF namespaces and copies it into `default` for the run. Failing that, and only for an image on an NGC registry, it mints one from `NGC_API_KEY` | auto-detect | +| `--skip-cluster-validation` | Skip the in-cluster validator probe entirely. A validator that is configured but cannot run fails the check, so use this to opt out explicitly, for example when the cluster cannot pull the image. Env: `NVCF_CLI_SELFHOSTED_SKIP_CLUSTER_VALIDATION` | `false` | +| `--no-cleanup` | Keep the validator Job, its pod, RBAC, pull secret and ConfigMap for debugging. A later check reclaims them after 24 hours; the result prints the `kubectl delete` command that removes them now | `false` | +| `--show-logs` | Print the validator transcript to stderr after the check events. The transcript is not JSON, and `--json` also writes to stderr, so leave this off when a parser is reading the stream | `false` | ## `up`-specific diff --git a/src/clis/nvcf-cli/.nvcf-cli.yaml.template b/src/clis/nvcf-cli/.nvcf-cli.yaml.template index 585adc8b21..0d7cde536e 100644 --- a/src/clis/nvcf-cli/.nvcf-cli.yaml.template +++ b/src/clis/nvcf-cli/.nvcf-cli.yaml.template @@ -142,20 +142,57 @@ api_keys_owner_id: svc@nvcf-api.local # Image reference for the cluster-validator pod used by # `nvcf-cli self-hosted check`. The CLI runs this image as a Job in the -# compute-plane cluster to verify NVCA prerequisites before install. +# cluster being checked to verify prerequisites before install. One image +# serves both roles; the Job environment selects the check set. The +# control-plane checks need an image from an NVCA release that supports +# validator roles; with an older image the CLI reports a warning instead of +# running them. +# +# The Job needs a ServiceAccount, ClusterRole, and ClusterRoleBinding, which +# the CLI creates and removes per run. The kubeconfig context therefore needs +# permission to manage those objects; without it the validator cannot run and +# the check fails. Pass --skip-cluster-validation to run without it. # # Config key: cluster_validator_image # Environment variable: NVCF_CLI_CLUSTER_VALIDATOR_IMAGE # Command-line flag: --cluster-validator-image # # When the value has no tag, the latest tag is discovered from the -# registry (preferring stable releases over rc). If unset everywhere, -# the validator probe is skipped with a warning. +# registry (preferring stable releases over rc). If discovery fails, the +# validator is skipped with a note rather than pulled as :latest; pin a tag +# to avoid depending on discovery. If unset everywhere, the validator probe +# is skipped with a note on stderr. # # Staging: stg.nvcr.io/nvidia/nvcf-byoc/cluster-validator # Prod: nvcr.io/nvidia/nvcf-byoc/cluster-validator # cluster_validator_image: nvcr.io/nvidia/nvcf-byoc/cluster-validator +# Extra container registries the control-plane validator probes for +# reachability, as host:port. The registries the install pulls from are +# probed already: the validator image's registry, the stack's +# global.image.registry, and quay.io for the cert-manager ACME solver. List +# only registries beyond those. +# +# Config key: cluster_validator_registries (list) +# Environment variable: NVCF_CLI_CLUSTER_VALIDATOR_REGISTRIES (comma-separated) +# Command-line flag: --cluster-validator-registries (repeatable or comma-separated) +# +# cluster_validator_registries: +# - harbor.example.com:443 +# - ghcr.io:443 + +# Image for the control-plane validator's node-to-node overlay probe, which +# needs sh and a busybox-style nc. Defaults to busybox:1.36 from Docker Hub. +# Set a mirror for air-gapped clusters. The probe pods get no imagePullSecrets, +# so the image must be pullable anonymously or through node-level registry +# credentials. +# +# Config key: cluster_validator_probe_image +# Environment variable: NVCF_CLI_CLUSTER_VALIDATOR_PROBE_IMAGE +# Command-line flag: --cluster-validator-probe-image +# +# cluster_validator_probe_image: harbor.example.com/library/busybox:1.36 + # ============================================================================== # General Settings # ============================================================================== diff --git a/src/clis/nvcf-cli/cmd/BUILD.bazel b/src/clis/nvcf-cli/cmd/BUILD.bazel index 2f83c8da26..a53af5ab63 100644 --- a/src/clis/nvcf-cli/cmd/BUILD.bazel +++ b/src/clis/nvcf-cli/cmd/BUILD.bazel @@ -120,7 +120,10 @@ go_test( "main_test.go", "registry_test.go", "root_test.go", + "self_hosted_check_scope_test.go", + "self_hosted_check_stackvalues_test.go", "self_hosted_check_test.go", + "self_hosted_check_validatorenv_test.go", "self_hosted_compute_plane_test.go", "self_hosted_control_plane_test.go", "self_hosted_down_test.go", @@ -141,11 +144,13 @@ go_test( "//src/clis/nvcf-cli/internal/selfhosted", "//src/clis/nvcf-cli/internal/selfhosted/auth", "//src/clis/nvcf-cli/internal/selfhosted/controlplaneprofile", + "//src/clis/nvcf-cli/internal/selfhosted/kubectx", "//src/clis/nvcf-cli/internal/selfhosted/progress", "//src/clis/nvcf-cli/internal/selfhosted/reachability", "//src/clis/nvcf-cli/internal/selfhosted/teardown", "//src/clis/nvcf-cli/internal/state", "//src/clis/nvcf-cli/internal/trustbundle", + "@com_github_masterminds_semver_v3//:semver", "@com_github_spf13_cobra//:cobra", "@com_github_spf13_pflag//:pflag", "@com_github_spf13_viper//:viper", diff --git a/src/clis/nvcf-cli/cmd/main_test.go b/src/clis/nvcf-cli/cmd/main_test.go index d0fa86ed75..95a996977f 100644 --- a/src/clis/nvcf-cli/cmd/main_test.go +++ b/src/clis/nvcf-cli/cmd/main_test.go @@ -19,16 +19,132 @@ package cmd import ( "context" + "net/http" + "net/http/httptest" "os" "testing" + + "github.com/spf13/pflag" + "github.com/spf13/viper" + + "nvcf-cli/internal/selfhosted" ) -// Default-stub out the validator-tag registry probe so cmd tests don't make -// real network calls or pollute the on-disk cache. Individual tests can -// reassign the variable if they explicitly want to exercise discovery. +// Default-stub every seam that would otherwise reach the developer's cluster or +// the network. Individual tests reassign a variable when they explicitly want +// to exercise that path. +// +// These are not conveniences. Without them `go test ./cmd/` creates a hostPath +// busybox pod per node in `default` (the inotify probe), lists Secrets across +// the stack namespaces of whatever kubeconfig happens to be current, and makes +// an outbound request to nvcr.io per configured registry. That mutates a real +// cluster from a unit test, and it is why the package took minutes and failed +// on a proxied kubeconfig rather than seconds and deterministically. func TestMain(m *testing.M) { + // Every command reads ~/.nvcf-cli.yaml and some tests save + // ~/.nvcf-cli.state, so a developer's real config would steer results and + // a test run would overwrite their saved credentials. + home, err := os.MkdirTemp("", "nvcf-cli-cmd-test-home-") + if err != nil { + panic(err) + } + _ = os.Setenv("HOME", home) resolveLatestValidatorTagForSelfHosted = func(_ context.Context, _ string) (string, bool) { return "", false } - os.Exit(m.Run()) + // Nil prober: the inotify check is skipped rather than creating pods. + newInotifyProberForSelfHosted = func() selfhosted.NodeInotifyProber { return nil } + // No cluster contact, and a clean result so the category still renders. + newStaleNamespaceProberForSelfHosted = func() selfhosted.StaleNamespaceProber { + return func(context.Context, string, []string) ([]selfhosted.StaleNamespace, error) { + return nil, nil + } + } + // No outbound registry request. + newRegistryCredentialCheckerForSelfHosted = func() selfhosted.RegistryCredentialChecker { + return func(context.Context, string, string, bool) error { return nil } + } + // No validator Job. A developer with NVCF_CLI_CLUSTER_VALIDATOR_IMAGE + // exported, or cluster_validator_image in ~/.nvcf-cli.yaml, would + // otherwise have every check test create RBAC, Secrets and Jobs in the + // current kube context and wait up to five minutes per role. + newClusterValidatorForSelfHosted = func() selfhosted.ClusterValidator { + return func(context.Context, selfhosted.ClusterValidatorParams) selfhosted.ClusterValidatorResult { + return selfhosted.ClusterValidatorResult{Passed: true} + } + } + for _, k := range []string{ + "NVCF_CLI_CLUSTER_VALIDATOR_IMAGE", "NVCF_CLI_CLUSTER_VALIDATOR_REGISTRIES", + "NVCF_CLI_CLUSTER_VALIDATOR_PROBE_IMAGE", + } { + _ = os.Unsetenv(k) + } + // The SIS reachability check has no seam, but it resolves its URL from + // NVCF_ICMS_URL, so resetCheckFlags points check tests at this local + // server instead of the real SIS. Scoped per test: setting it for the + // whole package would change what the ICMS URL resolution tests resolve. + sis := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusOK) + })) + testSISURL = sis.URL + code := m.Run() + sis.Close() + _ = os.RemoveAll(home) + os.Exit(code) +} + +// testSISURL is a local stand-in for SIS, set by TestMain. +var testSISURL string + +// resetFlag returns f to its default value and clears its Changed marker. A +// slice flag is replaced, since Set appends once the flag has been set. +func resetFlag(f *pflag.Flag) { + if sv, ok := f.Value.(pflag.SliceValue); ok { + _ = sv.Replace(nil) + } else { + _ = f.Value.Set(f.DefValue) + } + f.Changed = false +} + +// resetCheckFlags returns every `self-hosted check` flag to its default, +// including cobra's Changed marker, now and when the test ends. The flag +// variables are package globals that survive rootCmd.Execute, so without this +// a test that passes --pre leaks it into whichever test runs next, and a +// regression guard can pass or fail on test order alone. +func resetCheckFlags(t *testing.T) { + t.Helper() + if testSISURL != "" { + t.Setenv("NVCF_ICMS_URL", testSISURL) + } + reset := func() { + checkPre, checkControlPlane, checkComputePlane, checkAll = false, false, false, false + checkClusterName = "" + checkLocalOnly, checkSkipInotifyCheck, checkSkipClusterValidation = false, false, false + checkClusterValidatorImage, checkClusterValidatorPullSecret = "", "" + checkClusterValidatorNoCleanup = false + checkClusterValidatorRegistries = nil + checkClusterValidatorProbeImage = "" + checkShowLogs = false + selfHostedJSON, selfHostedPlain = false, false + selfHostedOutput = "text" + selfHostedWait = "" + selfHostedControlPlaneContext, selfHostedComputePlaneContext = "", "" + // Values too, not only the Changed marker: a test that passes + // --icms-url would otherwise point every later check at its URL. + for _, fs := range []*pflag.FlagSet{selfHostedCheckCmd.Flags(), selfHostedCmd.PersistentFlags()} { + fs.VisitAll(resetFlag) + } + // Other tests call viper.Reset(), which drops the bindings made at + // init, so a flag passed to check would silently not be read. + for key, flag := range map[string]string{ + "cluster_validator_image": "cluster-validator-image", + "cluster_validator_registries": "cluster-validator-registries", + "cluster_validator_probe_image": "cluster-validator-probe-image", + } { + _ = viper.BindPFlag(key, selfHostedCheckCmd.Flags().Lookup(flag)) + } + } + reset() + t.Cleanup(reset) } diff --git a/src/clis/nvcf-cli/cmd/root.go b/src/clis/nvcf-cli/cmd/root.go index a42df48c08..ee098e5be5 100644 --- a/src/clis/nvcf-cli/cmd/root.go +++ b/src/clis/nvcf-cli/cmd/root.go @@ -285,6 +285,8 @@ func initConfig() { // swapping --config files. The env name matches the historical // NVCF_CLI_CLUSTER_VALIDATOR_IMAGE override. viper.BindEnv("cluster_validator_image", "NVCF_CLI_CLUSTER_VALIDATOR_IMAGE") + viper.BindEnv("cluster_validator_registries", "NVCF_CLI_CLUSTER_VALIDATOR_REGISTRIES") + viper.BindEnv("cluster_validator_probe_image", "NVCF_CLI_CLUSTER_VALIDATOR_PROBE_IMAGE") // If a config file is found, read it in. if err := viper.ReadInConfig(); err == nil && viper.GetBool("debug") { diff --git a/src/clis/nvcf-cli/cmd/self_hosted_check.go b/src/clis/nvcf-cli/cmd/self_hosted_check.go index dafb00fbf5..3be8eb9d17 100644 --- a/src/clis/nvcf-cli/cmd/self_hosted_check.go +++ b/src/clis/nvcf-cli/cmd/self_hosted_check.go @@ -22,6 +22,10 @@ import ( "fmt" "io" "os" + "os/signal" + "path/filepath" + "strings" + "syscall" "time" "github.com/spf13/cobra" @@ -45,6 +49,8 @@ var ( checkClusterValidatorImage string checkClusterValidatorPullSecret string checkClusterValidatorNoCleanup bool + checkClusterValidatorRegistries []string + checkClusterValidatorProbeImage string checkShowLogs bool ) @@ -58,9 +64,19 @@ var newClusterValidatorForSelfHosted = func() selfhosted.ClusterValidator { return selfhosted.NewClusterValidator() } +// Test seam. +var newStaleNamespaceProberForSelfHosted = func() selfhosted.StaleNamespaceProber { + return selfhosted.NewStaleNamespaceProber() +} + // Test seam. Tests stub this to skip the registry network call. var resolveLatestValidatorTagForSelfHosted = selfhosted.ResolveLatestValidatorTag +// Test seam. +var newRegistryCredentialCheckerForSelfHosted = func() selfhosted.RegistryCredentialChecker { + return selfhosted.NewRegistryCredentialChecker() +} + var checkWriterIsTTY = isWriterTTY var selfHostedCheckCmd = &cobra.Command{ @@ -94,12 +110,28 @@ func init() { selfHostedCheckCmd.Flags().Lookup("cluster-validator-image")) selfHostedCheckCmd.Flags().StringVar(&checkClusterValidatorPullSecret, "cluster-validator-pull-secret", "", "Name of a docker-registry Secret in the 'default' namespace to pull the validator image. "+ - "When empty, the runner scans NVCF namespaces for a matching secret (mirroring it into "+ - "'default' if found elsewhere) and falls back to auto-creating one from NGC_API_KEY. "+ - "Set to force a specific name.") + "When empty, the runner scans NVCF namespaces for a matching secret and copies it into "+ + "'default' for the run if it lives elsewhere. Failing that, and only when the image is on "+ + "an NGC registry, it mints one from NGC_API_KEY. Set to force a specific name.") selfHostedCheckCmd.Flags().BoolVar(&checkClusterValidatorNoCleanup, "no-cleanup", false, - "Disable the validator Job's TTL so the Job persists for debugging. "+ - "The next run still deletes prior Jobs via the singleton sweep.") + "Keep the validator Job, its pod, RBAC, pull secret and ConfigMap for debugging instead of "+ + "removing them after the run. They are reclaimed by a later check after 24 hours; the "+ + "result prints the kubectl command that removes them now.") + selfHostedCheckCmd.Flags().StringSliceVar(&checkClusterValidatorRegistries, "cluster-validator-registries", nil, + "Additional container registries to probe for reachability in the control-plane validator. "+ + "Format: host:port (e.g. harbor.company.internal:443,ghcr.io:443). "+ + "Added to the registries the install pulls from, which are probed with the same "+ + "criticality as the local credential check. Env: NVCF_CLI_CLUSTER_VALIDATOR_REGISTRIES. "+ + "Can also be set in nvcf-cli config as cluster_validator_registries (list).") + _ = viper.BindPFlag("cluster_validator_registries", + selfHostedCheckCmd.Flags().Lookup("cluster-validator-registries")) + selfHostedCheckCmd.Flags().StringVar(&checkClusterValidatorProbeImage, "cluster-validator-probe-image", "", + "Image for the control-plane validator's node-to-node overlay probe (needs sh and busybox-style nc). "+ + "Defaults to busybox:1.36 from Docker Hub; set a mirror for air-gapped clusters. "+ + "Env: NVCF_CLI_CLUSTER_VALIDATOR_PROBE_IMAGE. "+ + "Can also be set in nvcf-cli config as cluster_validator_probe_image.") + _ = viper.BindPFlag("cluster_validator_probe_image", + selfHostedCheckCmd.Flags().Lookup("cluster-validator-probe-image")) selfHostedCheckCmd.Flags().BoolVar(&checkShowLogs, "show-logs", false, "Print the cleaned cluster-validator transcript to stderr after the check events. "+ "Useful when piping --json output to a script that also wants the transcript.") @@ -109,29 +141,52 @@ func runSelfHostedCheck(c *cobra.Command, _ []string) error { if !checkPre && !checkControlPlane && !checkComputePlane && !checkAll { return fmt.Errorf("at least one of --pre, --control-plane, --compute-plane, or --all is required") } + if checkClusterValidatorNoCleanup && selfHostedWait != "" { + // Each poll is a new validator run, so --no-cleanup would keep a full + // set of RBAC, Secret, ConfigMap and Job for every poll. + return fmt.Errorf("--no-cleanup keeps one run's objects for debugging and cannot be combined with --wait") + } localOnly := checkLocalOnly || os.Getenv("NVCF_CLI_SELFHOSTED_LOCAL_ONLY") != "" skipClusterValidation := checkSkipClusterValidation || os.Getenv("NVCF_CLI_SELFHOSTED_SKIP_CLUSTER_VALIDATION") != "" + // Mode is needed before image resolution so computePlaneIsTargeted can + // gate the registry round trip. ValidateFlags in PersistentPreRunE + // guarantees mode is ModeSingle or ModeSplit here. + mode := kubectx.SelectMode(selfHostedControlPlaneContext, selfHostedComputePlaneContext) + // Resolve the validator image up-front so we can right-size the // outer timeout (only when the validator actually runs) and emit a // one-shot stderr note up-front explaining why no validator row // appears in the output. Empty == not configured anywhere. - clusterValidatorImage := "" - if !localOnly && !skipClusterValidation && (checkPre || checkAll) { - if img, ok := resolveClusterValidatorImage(c.Context()); ok { - clusterValidatorImage = img - } + // One image covers both roles (VALIDATOR_ROLE selects the check set). + // *IsVisited, matching the dispatch below. --pre in ModeSplit visits both + // clusters but targets neither role, so gating image resolution on + // *IsTargeted left the validator unresolved and both probes nil, with no + // note explaining why. + anyValidatorIsTargeted := !localOnly && !skipClusterValidation && + (computePlaneIsVisited() || controlPlaneIsVisited()) + clusterValidatorImage, unresolvedImage := "", "" + if anyValidatorIsTargeted { + clusterValidatorImage, unresolvedImage = resolveClusterValidatorImage(c.Context()) } - clusterValidatorWillRun := !localOnly && !skipClusterValidation && (checkPre || checkAll) && clusterValidatorImage != "" + clusterValidatorWillRun := anyValidatorIsTargeted && clusterValidatorImage != "" - // The cluster-validator Job's internal budget is 5m - // (selfhosted.clusterValidatorTimeout). The outer ctx must be at least - // that plus headroom for RBAC bootstrap + log fetch, otherwise vctx - // derives from a shorter ceiling and silently truncates the wait. outerTimeout := 2 * time.Minute if clusterValidatorWillRun { - outerTimeout = 6 * time.Minute + // A role's share is a minute for the probes that run first, plus the + // longest a validator can take, which includes the wait after its own + // timeout for the Job's deadline to end the pod. Sizing on the + // validator's timeout alone ran the budget out while that wait was + // still in progress, reporting "not every check ran" in place of the + // validator's own failure. ModeSingle runs both validators in turn; + // ModeSplit runs them in parallel, so one share covers both. + perRole := time.Minute + selfhosted.ClusterValidatorRunCeiling() + if mode == kubectx.ModeSingle && controlPlaneIsTargeted(mode) && computePlaneIsTargeted(mode) { + outerTimeout = 2 * perRole + } else { + outerTimeout = perRole + } } // --wait polls for the declared duration. The outer ctx has to outlive // the last iteration, so add waitDur on top of a single iteration's @@ -141,7 +196,23 @@ func runSelfHostedCheck(c *cobra.Command, _ []string) error { outerTimeout += waitDur } } - ctx, cancel := context.WithTimeout(c.Context(), outerTimeout) + // Catch Ctrl-C and SIGTERM so an interrupted run unwinds through its + // deferred cleanup. Without this the process exits on the signal and the + // validator's cluster-wide ClusterRole, bound to a ServiceAccount in + // default, stays behind until a later check's orphan sweep. + sigCtx, stop := signal.NotifyContext(c.Context(), os.Interrupt, syscall.SIGTERM) + defer stop() + // Catch only the first signal. Restoring the default handler once it + // arrives lets a second Ctrl-C exit at once instead of waiting out the + // cleanup. + go func() { + <-sigCtx.Done() + stop() + }() + // interrupted is an explicit cancel, not the timeout below, which is a + // child of sigCtx and leaves it live. + interrupted := func() bool { return sigCtx.Err() != nil } + ctx, cancel := context.WithTimeout(sigCtx, checkBudget(outerTimeout)) defer cancel() // Legacy --output=json: warn and treat as --json. @@ -154,21 +225,53 @@ func runSelfHostedCheck(c *cobra.Command, _ []string) error { // validator row" with "validator silently dropped". Print at most one // reason; --skip-cluster-validation takes precedence over missing // config since it's the explicit operator choice. - if !localOnly && (checkPre || checkAll) { + // --local-only overrides the required scope flag, so say which checks + // that drops rather than print a clean host-only result for --all. + if localOnly { + fmt.Fprintln(c.ErrOrStderr(), "note: --local-only runs the local host checks only; cluster checks for the requested scope are skipped") + } + if !localOnly && (computePlaneIsVisited() || controlPlaneIsVisited()) { switch { case skipClusterValidation: fmt.Fprintln(c.ErrOrStderr(), "note: cluster-validator skipped (--skip-cluster-validation)") + case unresolvedImage != "": + fmt.Fprintf(c.ErrOrStderr(), "note: cluster-validator skipped (could not resolve a tag for %s; "+ + "pin cluster_validator_image to a tag)\n", unresolvedImage) case clusterValidatorImage == "": fmt.Fprintln(c.ErrOrStderr(), "note: cluster-validator skipped (cluster_validator_image not set in nvcf-cli config)") } } + // Enumerate registries for the local credential check. Skipped when + // local-only (no network) or when no validator image is configured. + // Uses the same extras list as the in-cluster ConfigMap reachability check. + var ( + credEntries []selfhosted.RegistryEntry + registryChecker selfhosted.RegistryCredentialChecker + ) + // Run credential checks whenever not local-only. The validator image is + // optional: EnumerateRegistries handles an empty image ref and still picks + // up global.image.registry from the stack values file and any + // --cluster-validator-registries extras independently of the image config. + if !localOnly { + extraRegistries := configuredValidatorRegistries() + credEntries = selfhosted.EnumerateRegistries( + clusterValidatorImage, resolveStackValuesFiles(), extraRegistries, + ) + if len(credEntries) > 0 { + registryChecker = newRegistryCredentialCheckerForSelfHosted() + } + } + cfg := selfhosted.PreflightConfig{ - LocalOnly: localOnly, - Tools: selfHostedPreflightTools(), + LocalOnly: localOnly, + Tools: checkPreflightTools(), + Registries: credEntries, + RegistryChecker: registryChecker, } - sink, err := selectCheckRenderer(c.ErrOrStderr(), selfHostedWait != "") + // A quit key in the dashboard cancels the run the way a signal does. + sink, err := selectCheckRenderer(c.ErrOrStderr(), selfHostedWait != "", stop) if err != nil { return err } @@ -176,31 +279,52 @@ func runSelfHostedCheck(c *cobra.Command, _ []string) error { if starter, ok := sink.(interface{ Start() }); ok { starter.Start() } + // An interrupted run still ends the stream with a final event, marked + // cancelled as up marks its own, so a --json consumer is not left waiting + // for one. Emitted on a fresh ctx: the run's ctx is already cancelled. + exitInterrupted := func() error { + _ = sink.Emit(context.Background(), progress.Final{Cancelled: true}) + return &ExitCodeError{Code: 130, Msg: "interrupted"} + } var lastResults []selfhosted.CheckResult runOnce := func() []selfhosted.CheckResult { var results []selfhosted.CheckResult - if checkPre || checkAll { - results = append(results, runPreflightByRole(ctx, cfg, sink, clusterValidatorImage)...) + if checkPre || checkAll || checkControlPlane || checkComputePlane { + results = append(results, runPreflightByRole(ctx, cfg, sink, mode, clusterValidatorImage)...) } // Inject force-fail seam for tests. if os.Getenv("NVCF_CLI_SELFHOSTED_FORCE_FAIL") != "" { results = append([]selfhosted.CheckResult{{ ID: "force-fail-test-seam", Category: "test", - Severity: "error", + Severity: selfhosted.SeverityError, Passed: false, Message: "forced failure (test seam)", }}, results...) } - // control-plane / compute-plane wired in M3/M4 — placeholder no-op for M2. return results } + // The outer budget ran out during a run: the checks it did not reach are + // already error rows, so the verdict is a timeout rather than whatever + // the partial set would grade as. + exitBudgetSpent := func() error { + emitCheckFinal(context.Background(), sink, lastResults) + maybeShowClusterValidatorLogs(c.ErrOrStderr(), lastResults) + return &ExitCodeError{Code: 5, Msg: "timed out: the check budget ran out before every check ran"} + } + if selfHostedWait == "" { // Single-shot mode. lastResults = runOnce() + if interrupted() { + return exitInterrupted() + } + if ctx.Err() != nil { + return exitBudgetSpent() + } emitCheckFinal(ctx, sink, lastResults) maybeShowClusterValidatorLogs(c.ErrOrStderr(), lastResults) if anyFailed(lastResults) { @@ -219,27 +343,195 @@ func runSelfHostedCheck(c *cobra.Command, _ []string) error { ticker := time.NewTicker(5 * time.Second) defer ticker.Stop() + waitTimeout := func() error { + emitCheckFinal(context.Background(), sink, lastResults) + maybeShowClusterValidatorLogs(c.ErrOrStderr(), lastResults) + return &ExitCodeError{Code: 5, Msg: "wait timeout: checks still failing after " + selfHostedWait} + } for { lastResults = runOnce() - if !anyFailed(lastResults) { + if interrupted() { + return exitInterrupted() + } + // An iteration that overran the budget ran on a dead context, and its + // results are incomplete: never a pass. + if ctx.Err() != nil { + return exitBudgetSpent() + } + if !anyFailed(lastResults) && !anyWarningToWaitOn(lastResults) { emitCheckFinal(ctx, sink, lastResults) maybeShowClusterValidatorLogs(c.ErrOrStderr(), lastResults) return nil } + // The deadline is checked first: with several cases ready, select + // picks at random, and the ticker then re-ran the checks on a spent + // budget. select { case <-deadline: - emitCheckFinal(ctx, sink, lastResults) - maybeShowClusterValidatorLogs(c.ErrOrStderr(), lastResults) - return &ExitCodeError{Code: 5, Msg: "wait timeout: checks still failing after " + selfHostedWait} + return waitTimeout() + default: + } + select { + case <-deadline: + return waitTimeout() case <-ticker.C: // continue polling case <-ctx.Done(): - return ctx.Err() + if interrupted() { + return exitInterrupted() + } + return waitTimeout() } } } +// resolveStackValuesFiles returns the stack values files preflight reads, in +// the order helmfile layers them: base.yaml first, then the environment file +// over it. Returns nil when no stack is found. +// +// It prefers --control-plane-stack, the same source every sibling command uses, +// and only falls back to walking up from the working directory. The +// environment follows resolveStackEnv. +func resolveStackValuesFiles() []string { + var roots []string + // localStackDir strips file:// and drops remote sources, which + // filepath.Join would otherwise turn into a path that never exists. + if dir := localStackDir(selfHostedControlPlaneStack); dir != "" { + roots = append(roots, dir) + } + if cwd, err := os.Getwd(); err == nil { + dir := cwd + for i := 0; i < 6; i++ { + roots = append(roots, dir) + parent := filepath.Dir(dir) + if parent == dir { + break + } + dir = parent + } + } + + env := resolveStackEnv() + for _, root := range roots { + for _, sub := range [][]string{ + {"deploy", "stacks", "self-managed", "environments"}, + {"environments"}, // a stack dir passed directly + } { + dir := filepath.Join(append([]string{root}, sub...)...) + var files []string + for _, name := range []string{"base.yaml", env + ".yaml"} { + candidate := filepath.Join(dir, name) + if _, err := os.Stat(candidate); err == nil { + files = append(files, candidate) + } + } + if len(files) > 0 { + return files + } + } + } + return nil +} + +// resolveStackEnv picks the helmfile environment preflight reads: an explicit +// --env, then HELMFILE_ENV for an operator who exports it and runs helmfile +// directly, then --env's default. When the CLI runs helmfile itself it passes +// --env as HELMFILE_ENV, so an explicit flag must win. +func resolveStackEnv() string { + if f := selfHostedCmd.PersistentFlags().Lookup("env"); f != nil && f.Changed { + if env := strings.TrimSpace(selfHostedEnv); env != "" { + return env + } + } + if env := strings.TrimSpace(os.Getenv("HELMFILE_ENV")); env != "" { + return env + } + if env := strings.TrimSpace(selfHostedEnv); env != "" { + return env + } + return "local" +} + +// localStackDir returns src when it points at a readable local directory. +// Remote sources (oci://, git@, https://...git) are not fetched here: the +// stale-namespace check falls back to its static list rather than making +// preflight depend on a network round trip. +func localStackDir(src string) string { + if src == "" { + return "" + } + src = strings.TrimPrefix(src, "file://") + if strings.Contains(src, "://") || strings.HasPrefix(src, "git@") { + return "" + } + if fi, err := os.Stat(src); err == nil && fi.IsDir() { + return src + } + return "" +} + +// configuredValidatorRegistries returns the extra registries from the flag, env +// var, or config file, normalized to one entry per registry. +// +// viper.GetStringSlice splits a raw env string on whitespace, so the documented +// comma form "a:443,b:443" arrives as a single element. Left as-is it reaches +// net.SplitHostPort as "a:443,b:443", which errors with "too many colons" and is +// then passed through verbatim as a host, producing https://a:443,b:443/v2/. +func configuredValidatorRegistries() []string { + var out []string + for _, raw := range viper.GetStringSlice("cluster_validator_registries") { + for _, part := range strings.Split(raw, ",") { + if part = strings.TrimSpace(part); part != "" { + out = append(out, part) + } + } + } + return out +} + +// computePlaneIsTargeted reports whether the compute-plane validator should run: +// --compute-plane, --all, or --pre in ModeSingle. --pre in ModeSplit does not +// target it because separate clusters have no implicit compute-plane role. +func computePlaneIsTargeted(mode kubectx.Mode) bool { + return checkComputePlane || checkAll || (checkPre && mode == kubectx.ModeSingle) +} + +// controlPlaneIsTargeted mirrors computePlaneIsTargeted but for the control +// plane. Runs when: --control-plane, --all, or --pre in ModeSingle. +func controlPlaneIsTargeted(mode kubectx.Mode) bool { + return checkControlPlane || checkAll || (checkPre && mode == kubectx.ModeSingle) +} + +// The *IsVisited pair reports whether a cluster should be contacted at all, +// which is broader than whether its role-specific check set runs. --pre in +// ModeSplit visits both clusters for the shared pre-install checks (stale +// namespaces) without targeting either role, so the targeting predicates alone +// cannot gate the dispatch. +// +// No mode parameter, unlike *IsTargeted: "(X || (pre && single)) || pre" +// absorbs to "X || pre", so the mode cannot change the answer. Taking one +// would imply a mode-dependence that does not exist. +func computePlaneIsVisited() bool { + return checkComputePlane || checkAll || checkPre +} + +func controlPlaneIsVisited() bool { + return checkControlPlane || checkAll || checkPre +} + +// withoutHostLocalChecks returns a copy of cfg with the inputs for the checks +// that run on the operator's machine cleared: local tool versions and registry +// credentials. Neither contacts a cluster, so exactly one role invocation must +// carry them. Without this both roles emit the same check IDs, which +// double-counts them in the pass/fail totals and repeats them in --json. +func withoutHostLocalChecks(cfg selfhosted.PreflightConfig) selfhosted.PreflightConfig { + cfg.Tools = nil + cfg.Registries = nil + cfg.RegistryChecker = nil + return cfg +} + // maybeShowClusterValidatorLogs prints the cleaned cluster-validator transcript // to the given writer when --show-logs is set, framed by markers so operators // can find it in mixed CLI output. Silent no-op when: @@ -262,11 +554,13 @@ func maybeShowClusterValidatorLogs(w io.Writer, results []selfhosted.CheckResult fmt.Fprintln(w, "--- cluster-validator logs ---") fmt.Fprint(w, r.Logs) fmt.Fprintln(w, "--- end cluster-validator logs ---") - return + // No early return: both roles produce a cluster-validator result under + // the same ID, so stopping at the first drops the other transcript + // entirely from --all --show-logs. } } -func selectCheckRenderer(w io.Writer, wait bool) (progress.EventSink, error) { +func selectCheckRenderer(w io.Writer, wait bool, onQuit func()) (progress.EventSink, error) { if !wait && !selfHostedJSON && !selfHostedPlain && !selfHostedAccessible && checkWriterIsTTY(w) { return progress.NewCheckOneShotRenderer(w, progress.ModelOpts{ Mode: progress.ModeCheck, @@ -274,6 +568,7 @@ func selectCheckRenderer(w io.Writer, wait bool) (progress.EventSink, error) { Cluster: checkClusterName, ControlPlaneContext: selfHostedControlPlaneContext, ComputePlaneContext: selfHostedComputePlaneContext, + OnQuit: onQuit, }), nil } @@ -285,6 +580,7 @@ func selectCheckRenderer(w io.Writer, wait bool) (progress.EventSink, error) { Cluster: checkClusterName, ControlPlaneContext: selfHostedControlPlaneContext, ComputePlaneContext: selfHostedComputePlaneContext, + OnQuit: onQuit, }) return sink, err } @@ -296,24 +592,29 @@ func selectCheckRenderer(w io.Writer, wait bool) (progress.EventSink, error) { // - ModeSingle (no context flags) → RoleControlPlane + RoleComputePlane sequentially // - ModeSplit (both context flags set) → RoleControlPlane + RoleComputePlane in parallel // -// clusterValidatorImage is the already-resolved validator image (empty when -// not configured). Resolution happens in the caller so the outer-timeout -// and stderr-note logic can see the same answer this function does. -func runPreflightByRole(ctx context.Context, cfg selfhosted.PreflightConfig, sink progress.EventSink, clusterValidatorImage string) []selfhosted.CheckResult { +// mode is the already-resolved kubectx.Mode (hoisted to the caller so image +// resolution and timeout sizing share the same answer). clusterValidatorImage +// is the already-resolved validator image (empty when not configured). +func runPreflightByRole(ctx context.Context, cfg selfhosted.PreflightConfig, sink progress.EventSink, mode kubectx.Mode, clusterValidatorImage string) []selfhosted.CheckResult { // LocalOnly: skip all cluster probes. if cfg.LocalOnly { return selfhosted.RunPreflightForRole(ctx, cfg, selfhosted.RoleLocalOnly, selfhosted.RoleConfig{}, sink) } + // SIS reachability is a compute-plane, post-install concern: only an + // explicit --all or --compute-plane asks for it. A bare --pre skips it + // because SIS is not up before install, and --control-plane does not + // target the compute plane. icmsURL := "" - if checkAll || checkComputePlane || !checkPre { + if checkAll || checkComputePlane { icmsURL = resolveICMSURL(selfHostedICMSURL) } - mode := kubectx.SelectMode(selfHostedControlPlaneContext, selfHostedComputePlaneContext) skipInotify := checkSkipInotifyCheck || os.Getenv("NVCF_CLI_SELFHOSTED_SKIP_INOTIFY") != "" var inotifyProber selfhosted.NodeInotifyProber - if !skipInotify { + // Visited, not targeted: the inotify limit is exactly what --pre exists to + // catch before NVCA bootstrap, so it must run for --pre in ModeSplit too. + if computePlaneIsVisited() && !skipInotify { inotifyProber = newInotifyProberForSelfHosted() } @@ -323,90 +624,286 @@ func runPreflightByRole(ctx context.Context, cfg selfhosted.PreflightConfig, sin // Either way, leave clusterValidator nil so the validator row is // omitted from the check stream; the caller already emitted a // one-line stderr notice explaining which case applies. + // Gate on the role predicate as well as the image, mirroring + // cpClusterValidator below. Without this, --control-plane in ModeSplit + // still creates a ServiceAccount, cluster-wide ClusterRole/CRB, pull secret + // and validator Job in the compute cluster. var clusterValidator selfhosted.ClusterValidator - if clusterValidatorImage != "" { + if computePlaneIsVisited() && clusterValidatorImage != "" { clusterValidator = newClusterValidatorForSelfHosted() } + staleNSProber := newStaleNamespaceProberForSelfHosted() + + // The control-plane validator probes the same registries, with the same + // criticality, as the local credential check, extras included. + registries := cfg.Registries + validatorEnv := clusterValidatorJobEnv( + selfhosted.LoadStackValues(resolveStackValuesFiles())) + cpValidatorEnv := validatorEnvForRole(validatorEnv, checkControlPlane) + gpuValidatorEnv := validatorEnvForRole(validatorEnv, checkComputePlane) + + // The cluster-validator image is the same for both roles; VALIDATOR_ROLE + // in the Job env selects which check set runs inside the binary. + // Gate on the image, not on the compute-plane validator being constructed: + // in ModeSplit with --control-plane the compute role is not targeted, so + // clusterValidator is nil and keying off it would drop the control-plane + // validator check from the run entirely. + var cpClusterValidator selfhosted.ClusterValidator + if controlPlaneIsVisited() && clusterValidatorImage != "" { + cpClusterValidator = newClusterValidatorForSelfHosted() + } + + // Local tool versions and registry credentials are checked on the + // operator's machine, so they belong to one invocation only. The control + // plane carries them when it runs; otherwise the compute plane does, so + // neither is silently dropped by a compute-plane-only invocation. + runControlPlane := controlPlaneIsVisited() + runComputePlane := computePlaneIsVisited() + cpCfg, gpuCfg := cfg, cfg + if runControlPlane { + gpuCfg = withoutHostLocalChecks(cfg) + } + switch mode { case kubectx.ModeSplit: // Run both roles in parallel; each gets its own kubeconfig context. + // Each dispatch is gated: with only one role selected the other + // cluster is never contacted, so its stale-namespace probe cannot + // report a failure the operator did not ask about. var ( cpResults []selfhosted.CheckResult gpuResults []selfhosted.CheckResult ) eg, egCtx := errgroup.WithContext(ctx) - eg.Go(func() error { - rc := selfhosted.RoleConfig{KubeContext: selfHostedControlPlaneContext} - cpResults = selfhosted.RunPreflightForRole(egCtx, cfg, selfhosted.RoleControlPlane, rc, sink) - return nil - }) - eg.Go(func() error { - rc := selfhosted.RoleConfig{ - KubeContext: selfHostedComputePlaneContext, + if runControlPlane { + eg.Go(func() error { + rc := selfhosted.RoleConfig{ + KubeContext: selfHostedControlPlaneContext, + ClusterValidator: cpClusterValidator, + ClusterValidatorImage: clusterValidatorImage, + ClusterValidatorPullSecret: checkClusterValidatorPullSecret, + ClusterValidatorNoCleanup: checkClusterValidatorNoCleanup, + ClusterValidatorEnv: cpValidatorEnv, + ClusterValidatorRegistries: registries, + StaleNamespaceProber: staleNSProber, + StackDir: localStackDir(selfHostedControlPlaneStack), + } + cpResults = selfhosted.RunPreflightForRole(egCtx, cpCfg, selfhosted.RoleControlPlane, rc, sink) + return nil + }) + } + if runComputePlane { + eg.Go(func() error { + rc := selfhosted.RoleConfig{ + KubeContext: selfHostedComputePlaneContext, + SISURL: icmsURL, + InotifyProber: inotifyProber, + ClusterValidator: clusterValidator, + ClusterValidatorImage: clusterValidatorImage, + ClusterValidatorPullSecret: checkClusterValidatorPullSecret, + ClusterValidatorNoCleanup: checkClusterValidatorNoCleanup, + ClusterValidatorEnv: gpuValidatorEnv, + StaleNamespaceProber: staleNSProber, + StackDir: localStackDir(selfHostedComputePlaneStack), + } + gpuResults = selfhosted.RunPreflightForRole(egCtx, gpuCfg, selfhosted.RoleComputePlane, rc, sink) + return nil + }) + } + _ = eg.Wait() + return append(cpResults, gpuResults...) + + default: // ModeSingle - one cluster; run only the roles the flags target. + var results []selfhosted.CheckResult + + // The stale-namespace probe goes to exactly one role. Both roles share + // the cluster here, so handing it to both emits two check_completed + // events with the same ID and loads the kubeconfig twice, which means a + // second exec-credential-plugin prompt. + // + // The two roles probe disjoint namespace lists, so the skipped role's + // namespaces are merged into the surviving probe rather than dropped. + // Without that, a kai-scheduler or nvca-operator namespace wedged + // Terminating by a failed teardown reports "no stale NVCF namespaces + // detected" and the next install fails into it. + // Merged only when both roles run: a --control-plane run must not + // report a stuck kai-scheduler namespace, nor --compute-plane a stuck + // vault-system, which is the rule ModeSplit already follows. + staleForControlPlane := staleNSProber + staleForComputePlane := staleNSProber + var cpExtraNamespaces []string + if runControlPlane { + staleForComputePlane = nil + if runComputePlane { + cpExtraNamespaces = selfhosted.ComputePlaneStaleNamespaces( + localStackDir(selfHostedComputePlaneStack)) + } + } else { + staleForControlPlane = nil + } + + if runControlPlane { + cpRC := selfhosted.RoleConfig{ + SISURL: icmsURL, + ClusterValidator: cpClusterValidator, + ClusterValidatorImage: clusterValidatorImage, + ClusterValidatorPullSecret: checkClusterValidatorPullSecret, + ClusterValidatorNoCleanup: checkClusterValidatorNoCleanup, + ClusterValidatorEnv: cpValidatorEnv, + ClusterValidatorRegistries: registries, + StaleNamespaceProber: staleForControlPlane, + StackDir: localStackDir(selfHostedControlPlaneStack), + ExtraStaleNamespaces: cpExtraNamespaces, + } + results = append(results, + selfhosted.RunPreflightForRole(ctx, cpCfg, selfhosted.RoleControlPlane, cpRC, sink)...) + } + if runComputePlane { + gpuRC := selfhosted.RoleConfig{ SISURL: icmsURL, InotifyProber: inotifyProber, ClusterValidator: clusterValidator, ClusterValidatorImage: clusterValidatorImage, ClusterValidatorPullSecret: checkClusterValidatorPullSecret, ClusterValidatorNoCleanup: checkClusterValidatorNoCleanup, + ClusterValidatorEnv: gpuValidatorEnv, + StaleNamespaceProber: staleForComputePlane, + StackDir: localStackDir(selfHostedComputePlaneStack), } - gpuResults = selfhosted.RunPreflightForRole(egCtx, cfg, selfhosted.RoleComputePlane, rc, sink) - return nil - }) - _ = eg.Wait() - return append(cpResults, gpuResults...) + results = append(results, + selfhosted.RunPreflightForRole(ctx, gpuCfg, selfhosted.RoleComputePlane, gpuRC, sink)...) + } + return results + } +} - default: // ModeSingle — no context flags; union both role check sets sequentially. - cpRC := selfhosted.RoleConfig{SISURL: icmsURL} - gpuRC := selfhosted.RoleConfig{ - SISURL: icmsURL, - InotifyProber: inotifyProber, - ClusterValidator: clusterValidator, - ClusterValidatorImage: clusterValidatorImage, - ClusterValidatorPullSecret: checkClusterValidatorPullSecret, - ClusterValidatorNoCleanup: checkClusterValidatorNoCleanup, +// validatorEnvForRole returns env for one role's validator. A role whose own +// flag is passed alongside --pre (--pre --control-plane) is checked as +// installed, the way --all is, so its validator is told the run is +// post-install. env itself is not modified. +func validatorEnvForRole(env map[string]string, roleFlag bool) map[string]string { + if !roleFlag || env["VALIDATOR_POST_INSTALL"] != "" { + return env + } + out := make(map[string]string, len(env)+1) + for k, v := range env { + out[k] = v + } + out["VALIDATOR_POST_INSTALL"] = "true" + return out +} + +// clusterValidatorJobEnv is what the validator container needs from the CLI's +// resolved configuration, matching what the chart CronJob forwards: +// - VALIDATOR_POST_INSTALL on every run except a bare --pre, so an empty +// control plane fails after install instead of passing as pre-install +// (validatorEnvForRole adds it for a role its own flag targets); +// - relocated OpenBao and Envoy Gateway namespaces, without which the Tier +// rows assess the defaults and miss the real components; +// - NVCF_GATEWAY_NAMES, the override for the NVCF Gateway discovery; +// - the overlay probe image, so a mirrored cluster does not pull busybox +// from Docker Hub. +func clusterValidatorJobEnv(stack selfhosted.StackValues) map[string]string { + env := map[string]string{} + if !checkPre || checkAll { + env["VALIDATOR_POST_INSTALL"] = "true" + } + if ns := configValue("NVCF_OPENBAO_NAMESPACE"); ns != "" { + env["NVCF_OPENBAO_NAMESPACE"] = ns + } + envoyNS := configValue("NVCF_ENVOY_GATEWAY_NAMESPACE") + if envoyNS == "" { + envoyNS = stack.EnvoyGatewayNamespace + } + if envoyNS != "" { + env["NVCF_ENVOY_GATEWAY_NAMESPACE"] = envoyNS + } + // The launcher knows the NVCF Gateways from the stack, so the validator + // need not rediscover them from route labels, and after install can fail + // a named Gateway that has no proxy. An explicit setting still wins. + if names := configValue("NVCF_GATEWAY_NAMES"); names != "" { + env["NVCF_GATEWAY_NAMES"] = names + } else if len(stack.Gateways) > 0 { + env["NVCF_GATEWAY_NAMES"] = strings.Join(stack.Gateways, ",") + } + // Below three replicas a quorum component is a lost quorum under an HA + // mode and the deployed shape under none; only the stack says which. + if mode := configValue("NVCF_HA_MODE"); mode != "" { + env["NVCF_HA_MODE"] = mode + } else if stack.HAMode != "" { + env["NVCF_HA_MODE"] = stack.HAMode + } + probe := strings.TrimSpace(viper.GetString("cluster_validator_probe_image")) + if probe == "" { + probe = configValue("NVCF_N2N_PROBE_IMAGE") + } + if probe != "" { + env["NVCF_N2N_PROBE_IMAGE"] = probe + } + return env +} + +// configValue reads a setting the way the CLI's cluster configuration does +// (environment, then the nvcf-cli config file). A YAML list in the config +// file is joined with commas, the form the validator's list settings parse; +// GetString alone returns "" for a list and would drop it silently. +func configValue(key string) string { + if v := strings.TrimSpace(os.Getenv(key)); v != "" { + return v + } + if !viper.IsSet(key) { + return "" + } + if _, isList := viper.Get(key).([]any); isList { + var items []string + for _, item := range viper.GetStringSlice(key) { + if item = strings.TrimSpace(item); item != "" { + items = append(items, item) + } } - cpResults := selfhosted.RunPreflightForRole(ctx, cfg, selfhosted.RoleControlPlane, cpRC, sink) - gpuResults := selfhosted.RunPreflightForRole(ctx, cfg, selfhosted.RoleComputePlane, gpuRC, sink) - return append(cpResults, gpuResults...) + return strings.Join(items, ",") } + return strings.TrimSpace(viper.GetString(key)) } -// Resolves the validator image from the viper-backed config chain -// (flag > env > config-file > default). Returns ("", false) when nothing -// is configured so the caller can surface a clear "not configured" -// warning instead of pulling from a stale built-in default. +// resolveClusterValidatorImage resolves the validator image from flag > env > +// config-file. Returns ("", false) when unconfigured. When only a repo is +// given, discovers the latest stable tag (1h cached; falls back on failure). // -// When the configured value already has a tag, it is used as-is. When it -// names only a repo, the latest tag is discovered from the registry -// (preferring stable over rc, 1h cached); any discovery failure falls -// back to the configured value unchanged. -func resolveClusterValidatorImage(ctx context.Context) (string, bool) { - image := viper.GetString("cluster_validator_image") - if image == "" { - return "", false - } - if discovered, ok := resolveLatestValidatorTagForSelfHosted(ctx, image); ok { - return discovered, true - } - return image, true +// An unpinned image whose tag could not be discovered is not launched: the +// kubelet would pull it as :latest, which the validator repository does not +// publish, and fail the run on a pull error instead of saying why. +func resolveClusterValidatorImage(ctx context.Context) (image string, unresolved string) { + configured := viper.GetString("cluster_validator_image") + if configured == "" { + return "", "" + } + if discovered, ok := resolveLatestValidatorTagForSelfHosted(ctx, configured); ok { + return discovered, "" + } + if !selfhosted.ImageRefIsPinned(configured) { + return "", configured + } + return configured, "" } // emitCheckFinal emits a Final event with check-mode verdict fields derived // from the result slice. Called once per run (or once per wait-loop exit). func emitCheckFinal(ctx context.Context, sink progress.EventSink, results []selfhosted.CheckResult) { - var passed, failed int - for _, r := range results { - if r.Passed { - passed++ - } else { - failed++ - } - } + // Success and FailedCount must agree with the process exit code, which is + // driven by anyFailed. Counting every non-pass as a failure made a + // warning-severity result emit success:false / verdict:failed while the + // process exited 0, so a CI gate on final.success broke for anyone whose + // registry credentials live in a Docker credential helper. "warnings" is + // already part of the documented Verdict vocabulary; it was never emitted. + passed, failed, warned := selfhosted.CountResults(results) verdict := "ok" - if failed > 0 { + switch { + case failed > 0: verdict = "failed" + case warned > 0: + verdict = "warnings" } _ = sink.Emit(ctx, progress.Final{ Success: failed == 0, @@ -417,11 +914,36 @@ func emitCheckFinal(ctx context.Context, sink progress.EventSink, results []self }) } +func isBlockingFailure(r selfhosted.CheckResult) bool { + return r.IsBlockingFailure() +} + // anyFailed returns true if any check failed at error severity. Warnings do // not trigger non-zero exit per spec §6.3. +// isBlockingFailure is the single definition of "this fails the run". Both the +// exit code and the JSON verdict derive from it, so they cannot disagree. +// checkPreflightTools is a test seam over the local tool checks, so command +// tests do not depend on what is installed on the machine running them. +var checkPreflightTools = selfHostedPreflightTools + +// checkBudget is a test seam over the command's outer time budget. +var checkBudget = func(d time.Duration) time.Duration { return d } + +// anyWarningToWaitOn reports a warning expected to clear by itself, such as a +// rollout the validator saw in progress. --wait keeps polling on it; a single +// run still exits 0. +func anyWarningToWaitOn(results []selfhosted.CheckResult) bool { + for _, r := range results { + if r.Transient && !r.Passed { + return true + } + } + return false +} + func anyFailed(results []selfhosted.CheckResult) bool { for _, r := range results { - if !r.Passed && r.Severity == "error" { + if isBlockingFailure(r) { return true } } diff --git a/src/clis/nvcf-cli/cmd/self_hosted_check_scope_test.go b/src/clis/nvcf-cli/cmd/self_hosted_check_scope_test.go new file mode 100644 index 0000000000..bd7327c2e7 --- /dev/null +++ b/src/clis/nvcf-cli/cmd/self_hosted_check_scope_test.go @@ -0,0 +1,305 @@ +/* +SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +SPDX-License-Identifier: Apache-2.0 + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package cmd + +import ( + "bytes" + "context" + "net/http" + "net/http/httptest" + "sync" + "sync/atomic" + "testing" + "time" + + "github.com/Masterminds/semver/v3" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "nvcf-cli/internal/selfhosted" +) + +// runCheckRecording runs `self-hosted check` in ModeSingle with the given +// flags and returns the namespaces the stale-namespace probe was asked about +// and how many times SIS was contacted. +func runCheckRecording(t *testing.T, args ...string) (namespaces []string, sisHits int64) { + t.Helper() + resetCheckFlags(t) + var hits atomic.Int64 + sis := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + hits.Add(1) + w.WriteHeader(http.StatusOK) + })) + t.Cleanup(sis.Close) + t.Setenv("NVCF_ICMS_URL", sis.URL) + t.Setenv("NVCF_CLI_SELFHOSTED_SKIP_INOTIFY", "1") + + var mu sync.Mutex + prev := newStaleNamespaceProberForSelfHosted + newStaleNamespaceProberForSelfHosted = func() selfhosted.StaleNamespaceProber { + return func(_ context.Context, _ string, ns []string) ([]selfhosted.StaleNamespace, error) { + mu.Lock() + defer mu.Unlock() + namespaces = append(namespaces, ns...) + return nil, nil + } + } + t.Cleanup(func() { newStaleNamespaceProberForSelfHosted = prev }) + + rootCmd.SetErr(&bytes.Buffer{}) + rootCmd.SetOut(&bytes.Buffer{}) + rootCmd.SetArgs(append([]string{"self-hosted", "check", "--skip-cluster-validation", "--json"}, args...)) + _ = rootCmd.Execute() + return namespaces, hits.Load() +} + +// Each role's namespaces are scanned only when that role runs. Merging both in +// for a single-role run reported a stuck kai-scheduler on --control-plane. +func TestCheck_StaleNamespacesFollowTheRequestedRoles(t *testing.T) { + ns, _ := runCheckRecording(t, "--control-plane") + assert.Contains(t, ns, "vault-system") + assert.NotContains(t, ns, "kai-scheduler", "--control-plane must not scan compute namespaces") + + ns, _ = runCheckRecording(t, "--compute-plane") + assert.Contains(t, ns, "kai-scheduler") + assert.NotContains(t, ns, "vault-system", "--compute-plane must not scan control-plane namespaces") + + ns, _ = runCheckRecording(t, "--all") + assert.Contains(t, ns, "vault-system") + assert.Contains(t, ns, "kai-scheduler", "--all scans both") +} + +// A bare --pre skips SIS, which is not up before install; an explicit --all or +// --compute-plane still asks for it; --control-plane never contacts it. +func TestCheck_SISReachabilityScope(t *testing.T) { + _, hits := runCheckRecording(t, "--pre") + assert.Zero(t, hits, "--pre alone is pre-install") + _, hits = runCheckRecording(t, "--pre", "--all") + assert.NotZero(t, hits, "--all asks for every category, --pre included") + _, hits = runCheckRecording(t, "--pre", "--compute-plane") + assert.NotZero(t, hits) + _, hits = runCheckRecording(t, "--control-plane") + assert.Zero(t, hits, "SIS is a compute-plane check") + _, hits = runCheckRecording(t, "--pre", "--control-plane") + assert.Zero(t, hits, "--pre visits the compute plane but --control-plane does not ask for SIS") +} + +// --local-only overrides the required scope flag; the run says so on stderr. +func TestCheck_LocalOnlyNotesSkippedClusterChecks(t *testing.T) { + resetCheckFlags(t) + t.Setenv("NVCF_CLI_SELFHOSTED_SKIP_INOTIFY", "1") + var stderr bytes.Buffer + rootCmd.SetErr(&stderr) + rootCmd.SetOut(&bytes.Buffer{}) + t.Cleanup(func() { rootCmd.SetErr(nil); rootCmd.SetOut(nil) }) + rootCmd.SetArgs([]string{"self-hosted", "check", "--all", "--local-only", "--json"}) + _ = rootCmd.Execute() + assert.Contains(t, stderr.String(), "--local-only runs the local host checks only") +} + +// An interrupted run reports the interrupt, not a verdict: with the validator +// cut short the results would otherwise read as a failure, or as a pass. +func TestCheck_InterruptExits130(t *testing.T) { + resetCheckFlags(t) + t.Setenv("NVCF_CLI_SELFHOSTED_SKIP_INOTIFY", "1") + ctx, cancel := context.WithCancel(context.Background()) + cancel() + var stderr bytes.Buffer + rootCmd.SetErr(&stderr) + rootCmd.SetOut(&bytes.Buffer{}) + t.Cleanup(func() { rootCmd.SetErr(nil); rootCmd.SetOut(nil) }) + // Cobra keeps a subcommand's context from an earlier Execute, so set it + // on the command that runs. + selfHostedCheckCmd.SetContext(ctx) + t.Cleanup(func() { selfHostedCheckCmd.SetContext(context.Background()) }) + rootCmd.SetArgs([]string{"self-hosted", "check", "--all", "--local-only", "--json"}) + err := rootCmd.Execute() + + var exitErr *ExitCodeError + require.ErrorAs(t, err, &exitErr) + assert.Equal(t, 130, exitErr.Code) + assert.Regexp(t, `"event":"final".*"cancelled":true`, stderr.String(), + "the JSON stream still ends with a final event, marked cancelled") +} + +// runCheckWithBudget runs check --control-plane --json with the outer budget +// shortened to budget and a validator stub that holds its role until the +// context ends, or returns result at once. +func runCheckWithBudget(t *testing.T, budget time.Duration, validator func(context.Context) selfhosted.ClusterValidatorResult, + args ...string) (error, string) { + t.Helper() + resetCheckFlags(t) + t.Setenv("NVCF_CLI_SELFHOSTED_SKIP_INOTIFY", "1") + prevBudget, prevCV, prevTools := checkBudget, newClusterValidatorForSelfHosted, checkPreflightTools + checkBudget = func(d time.Duration) time.Duration { requestedBudget = d; return budget } + checkPreflightTools = passingPreflightTools + t.Cleanup(func() { checkPreflightTools = prevTools }) + newClusterValidatorForSelfHosted = func() selfhosted.ClusterValidator { + return func(ctx context.Context, _ selfhosted.ClusterValidatorParams) selfhosted.ClusterValidatorResult { + return validator(ctx) + } + } + t.Cleanup(func() { checkBudget, newClusterValidatorForSelfHosted = prevBudget, prevCV }) + var stderr bytes.Buffer + rootCmd.SetErr(&stderr) + rootCmd.SetOut(&bytes.Buffer{}) + t.Cleanup(func() { rootCmd.SetErr(nil); rootCmd.SetOut(nil) }) + rootCmd.SetArgs(append([]string{"self-hosted", "check", "--control-plane", "--json", + "--cluster-validator-image", "nvcr.io/nvidia/nvcf-byoc/cluster-validator:1.0.0"}, args...)) + return rootCmd.Execute(), stderr.String() +} + +// requestedBudget is the outer budget the last runCheckWithBudget run asked for. +var requestedBudget time.Duration + +func holdUntilDone(ctx context.Context) selfhosted.ClusterValidatorResult { + <-ctx.Done() + return selfhosted.ClusterValidatorResult{Err: ctx.Err()} +} + +// A run that outlives its budget is a timeout, never a pass: exit 5 with a +// final event that says it failed, not success on whatever had run. +func TestCheck_BudgetSpentIsATimeout(t *testing.T) { + for _, args := range [][]string{nil, {"--wait", "1m"}} { + err, stderr := runCheckWithBudget(t, 300*time.Millisecond, holdUntilDone, args...) + var exitErr *ExitCodeError + require.ErrorAs(t, err, &exitErr, "args %v", args) + assert.Equal(t, 5, exitErr.Code, "args %v", args) + assert.Regexp(t, `"event":"final".*"success":false`, stderr, "args %v", args) + } +} + +// --wait keeps polling while the validator reports a rollout in progress, +// instead of stopping on the first run that exits 0. +func TestCheck_WaitPollsOnARolloutInProgress(t *testing.T) { + calls := 0 + err, _ := runCheckWithBudget(t, time.Minute, func(context.Context) selfhosted.ClusterValidatorResult { + calls++ + logs := "Validator role: control-plane\nCluster is NVCF-Ready\n" + if calls == 1 { + logs = "Validator role: control-plane\nnvcf/api: rollout in progress (updated: 2/3)\n" + + "Cluster is NVCF-Ready (with warnings)\n" + } + return selfhosted.ClusterValidatorResult{Passed: true, Logs: logs} + }, "--wait", "30s") + require.NoError(t, err) + assert.Equal(t, 2, calls, "the second run, after the rollout, ends the wait") +} + +// Results from an iteration that ran on a dead context are not a verdict, even +// when every row that did run passed; and a budget that runs out while --wait +// sleeps between polls is a timeout too. +func TestCheck_BudgetSpentIsATimeoutEvenWhenRowsPassed(t *testing.T) { + passOnDeadCtx := func(ctx context.Context) selfhosted.ClusterValidatorResult { + <-ctx.Done() + return selfhosted.ClusterValidatorResult{Passed: true, Logs: "Validator role: control-plane\nCluster is NVCF-Ready\n"} + } + failFast := func(context.Context) selfhosted.ClusterValidatorResult { + return selfhosted.ClusterValidatorResult{ExitCode: 1, Logs: "Validator role: control-plane\n"} + } + for name, tc := range map[string]struct { + budget time.Duration + v func(context.Context) selfhosted.ClusterValidatorResult + args []string + }{ + "passed on a dead context": {300 * time.Millisecond, passOnDeadCtx, []string{"--wait", "1m"}}, + "budget ends between polls": {3 * time.Second, failFast, []string{"--wait", "1m"}}, + "single run, dead-context pass": {300 * time.Millisecond, passOnDeadCtx, nil}, + } { + err, stderr := runCheckWithBudget(t, tc.budget, tc.v, tc.args...) + var exitErr *ExitCodeError + require.ErrorAs(t, err, &exitErr, name) + assert.Equal(t, 5, exitErr.Code, name) + assert.Contains(t, stderr, `"event":"final"`, name) + } +} + +// Each --wait poll is a new validator run, so --no-cleanup would keep a full +// set of cluster-wide RBAC, Secret, ConfigMap and Job per poll. +func TestCheck_WaitWithNoCleanupIsRejected(t *testing.T) { + resetCheckFlags(t) + rootCmd.SetErr(&bytes.Buffer{}) + rootCmd.SetOut(&bytes.Buffer{}) + t.Cleanup(func() { rootCmd.SetErr(nil); rootCmd.SetOut(nil) }) + rootCmd.SetArgs([]string{"self-hosted", "check", "--control-plane", "--wait", "1m", "--no-cleanup"}) + err := rootCmd.Execute() + require.Error(t, err) + assert.Contains(t, err.Error(), "cannot be combined with --wait") +} + +// passingPreflightTools is the real tool set with every tool found at its +// minimum version. +func passingPreflightTools() []selfhosted.BinarySpec { + specs := selfHostedPreflightTools() + for i := range specs { + v := specs[i].MinVer + if v == nil { + v = semver.MustParse("1.0.0") + } + name := specs[i].Name + specs[i].LookPath = func(string) (string, error) { return "/usr/local/bin/" + name, nil } + specs[i].Version = func(context.Context, string) (*semver.Version, error) { return v, nil } + } + return specs +} + +// An unpinned validator image whose tag cannot be discovered is not launched: +// the kubelet would pull :latest, which the repository does not publish. The +// run skips the validator and says to pin a tag. +func TestCheck_UnresolvedValidatorTagIsSkippedWithANote(t *testing.T) { + calls := 0 + _, stderr := runCheckWithBudget(t, time.Minute, func(context.Context) selfhosted.ClusterValidatorResult { + calls++ + return selfhosted.ClusterValidatorResult{Passed: true} + }, "--cluster-validator-image", "nvcr.io/nvidia/nvcf-byoc/cluster-validator") + assert.Zero(t, calls, "the untagged image must not be launched") + assert.Contains(t, stderr, "could not resolve a tag for nvcr.io/nvidia/nvcf-byoc/cluster-validator") +} + +func TestImageRefIsPinned(t *testing.T) { + for ref, want := range map[string]bool{ + "nvcr.io/nvidia/validator:3.2.26": true, + "nvcr.io/nvidia/validator@sha256:abcd": true, + "localhost:5000/validator": false, + "nvcr.io/nvidia/validator": false, + "registry.example.com:443/team/validator": false, + } { + assert.Equal(t, want, selfhosted.ImageRefIsPinned(ref), ref) + } +} + +// The budget covers each validator's longest run, including the wait after its +// own timeout for the Job's deadline to end the pod, so a validator that times +// out is graded as its own failure and not as a spent budget. Both roles on one +// cluster run in turn and need a share each. +func TestCheck_BudgetCoversEachValidatorsFullRun(t *testing.T) { + ceiling := selfhosted.ClusterValidatorRunCeiling() + passNow := func(context.Context) selfhosted.ClusterValidatorResult { + return selfhosted.ClusterValidatorResult{Passed: true} + } + for _, tc := range []struct { + args []string + validators time.Duration + }{ + {args: nil, validators: 1}, + {args: []string{"--pre"}, validators: 2}, + } { + _, _ = runCheckWithBudget(t, time.Minute, passNow, tc.args...) + assert.GreaterOrEqual(t, requestedBudget, tc.validators*ceiling+time.Minute, "args %v", tc.args) + } +} diff --git a/src/clis/nvcf-cli/cmd/self_hosted_check_stackvalues_test.go b/src/clis/nvcf-cli/cmd/self_hosted_check_stackvalues_test.go new file mode 100644 index 0000000000..e1df05cafb --- /dev/null +++ b/src/clis/nvcf-cli/cmd/self_hosted_check_stackvalues_test.go @@ -0,0 +1,100 @@ +/* +SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +SPDX-License-Identifier: Apache-2.0 + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package cmd + +import ( + "os" + "path/filepath" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// stackWithEnvs creates a stack directory holding base.yaml and one file per +// named environment. +func stackWithEnvs(t *testing.T, envs ...string) string { + t.Helper() + dir := t.TempDir() + envDir := filepath.Join(dir, "environments") + require.NoError(t, os.MkdirAll(envDir, 0o755)) + for _, name := range append([]string{"base"}, envs...) { + require.NoError(t, os.WriteFile(filepath.Join(envDir, name+".yaml"), []byte("global: {}\n"), 0o644)) + } + return dir +} + +// withEnvFlag sets --env as if the operator passed it, and restores it. +func withEnvFlag(t *testing.T, value string, changed bool) { + t.Helper() + f := selfHostedCmd.PersistentFlags().Lookup("env") + require.NotNil(t, f) + prevValue, prevChanged := selfHostedEnv, f.Changed + selfHostedEnv, f.Changed = value, changed + t.Cleanup(func() { selfHostedEnv, f.Changed = prevValue, prevChanged }) +} + +// helmfile layers the environment file over base.yaml, so both are returned, +// base first. Returning only the first file that exists dropped whichever +// half the other one set. +func TestResolveStackValuesFiles_LayersEnvOverBase(t *testing.T) { + stack := stackWithEnvs(t, "local") + prev := selfHostedControlPlaneStack + selfHostedControlPlaneStack = stack + t.Cleanup(func() { selfHostedControlPlaneStack = prev }) + t.Setenv("HELMFILE_ENV", "") + withEnvFlag(t, "local", false) + + got := resolveStackValuesFiles() + require.Len(t, got, 2) + assert.Equal(t, "base.yaml", filepath.Base(got[0])) + assert.Equal(t, "local.yaml", filepath.Base(got[1])) +} + +// An operator who exports HELMFILE_ENV and runs helmfile directly gets that +// environment; an explicit --env still wins over it. +func TestResolveStackEnv_Precedence(t *testing.T) { + t.Setenv("HELMFILE_ENV", "airgap") + withEnvFlag(t, "local", false) + assert.Equal(t, "airgap", resolveStackEnv(), "HELMFILE_ENV beats the --env default") + + withEnvFlag(t, "staging", true) + assert.Equal(t, "staging", resolveStackEnv(), "an explicit --env beats HELMFILE_ENV") + + t.Setenv("HELMFILE_ENV", "") + withEnvFlag(t, "local", false) + assert.Equal(t, "local", resolveStackEnv()) +} + +// A file:// stack source is read from its directory. Joined verbatim it named +// a path that never exists, and the lookup fell back to the working directory. +func TestResolveStackValuesFiles_FileURLStack(t *testing.T) { + stack := stackWithEnvs(t, "local") + prev := selfHostedControlPlaneStack + selfHostedControlPlaneStack = "file://" + stack + t.Cleanup(func() { selfHostedControlPlaneStack = prev }) + t.Setenv("HELMFILE_ENV", "") + withEnvFlag(t, "local", false) + + got := resolveStackValuesFiles() + require.Len(t, got, 2) + for _, f := range got { + assert.True(t, strings.HasPrefix(f, stack), "%s is not from the file:// stack", f) + } +} diff --git a/src/clis/nvcf-cli/cmd/self_hosted_check_test.go b/src/clis/nvcf-cli/cmd/self_hosted_check_test.go index d3a6d94b64..579d788a25 100644 --- a/src/clis/nvcf-cli/cmd/self_hosted_check_test.go +++ b/src/clis/nvcf-cli/cmd/self_hosted_check_test.go @@ -30,6 +30,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "nvcf-cli/internal/selfhosted" + "nvcf-cli/internal/selfhosted/kubectx" "nvcf-cli/internal/selfhosted/progress" ) @@ -38,6 +40,7 @@ import ( // - prints a deprecation warning to stderr, AND // - falls through to JSONL streaming behaviour on stderr (same as --json). func TestCheck_LegacyOutputJSONWarnsAndStreams(t *testing.T) { + resetCheckFlags(t) // Reset global flag state left over from other tests. t.Cleanup(func() { selfHostedJSON = false @@ -63,6 +66,7 @@ func TestCheck_LegacyOutputJSONWarnsAndStreams(t *testing.T) { // the §6.6.3 schema: schemaVersion header, then check_started/check_completed/ // category_completed events per check, then a final event with verdict fields. func TestCheck_NewJSON(t *testing.T) { + resetCheckFlags(t) t.Cleanup(func() { selfHostedJSON = false selfHostedOutput = "text" @@ -112,6 +116,7 @@ func TestCheck_NewJSON(t *testing.T) { // TestCheck_WaitTimesOutCleanly verifies that --wait honors the timeout duration. func TestSelfHostedCheck_WaitTimesOutCleanly(t *testing.T) { + resetCheckFlags(t) t.Setenv("NVCF_CLI_SELFHOSTED_LOCAL_ONLY", "1") t.Setenv("NVCF_CLI_SELFHOSTED_FORCE_FAIL", "1") // seam: forces a failing check rootCmd.SetArgs([]string{"self-hosted", "check", "--pre", "--wait", "2s"}) @@ -135,7 +140,7 @@ func TestCheck_OneShotTTYUsesStaticRenderer(t *testing.T) { checkWriterIsTTY = func(io.Writer) bool { return true } var stderr bytes.Buffer - sink, err := selectCheckRenderer(&stderr, false) + sink, err := selectCheckRenderer(&stderr, false, nil) require.NoError(t, err) require.IsType(t, &progress.CheckOneShotRenderer{}, sink) @@ -160,6 +165,7 @@ func TestCheck_OneShotTTYUsesStaticRenderer(t *testing.T) { // TestCheck_PreflightStreamingOrder verifies that for each tool the events arrive // in CheckStarted → CheckCompleted order, and CategoryCompleted follows all checks. func TestCheck_PreflightStreamingOrder(t *testing.T) { + resetCheckFlags(t) t.Cleanup(func() { selfHostedJSON = false selfHostedOutput = "text" @@ -219,6 +225,7 @@ func TestCheck_PreflightStreamingOrder(t *testing.T) { // TestCheck_LocalOnlyFlag verifies that --local-only causes only local-host-tools // events (no control-plane-cluster or compute-plane-cluster events). func TestCheck_LocalOnlyFlag(t *testing.T) { + resetCheckFlags(t) t.Cleanup(func() { selfHostedJSON = false selfHostedOutput = "text" @@ -254,6 +261,7 @@ func TestCheck_LocalOnlyFlag(t *testing.T) { // TestCheck_SingleClusterMode verifies that without context flags, both // control-plane-cluster and compute-plane-cluster category events appear. func TestCheck_SingleClusterMode(t *testing.T) { + resetCheckFlags(t) t.Cleanup(func() { selfHostedJSON = false selfHostedOutput = "text" @@ -285,6 +293,7 @@ func TestCheck_SingleClusterMode(t *testing.T) { } func TestCheck_PreDoesNotProbeSISReachability(t *testing.T) { + resetCheckFlags(t) t.Cleanup(func() { selfHostedJSON = false selfHostedOutput = "text" @@ -309,6 +318,7 @@ func TestCheck_PreDoesNotProbeSISReachability(t *testing.T) { // TestCheck_SplitClusterMode verifies that providing both context flags causes // both control-plane and compute-plane category events (run in parallel). func TestCheck_SplitClusterMode(t *testing.T) { + resetCheckFlags(t) t.Cleanup(func() { selfHostedJSON = false selfHostedOutput = "text" @@ -343,9 +353,209 @@ func TestCheck_SplitClusterMode(t *testing.T) { assert.Contains(t, categories, "compute-plane-cluster", "expected compute-plane-cluster in split mode") } -// parseJSONLLines splits s into non-empty lines, skips any non-JSON lines -// (e.g. cobra error messages written to stderr), and unmarshals each JSON line -// as an object. Returns them in order. +// TestComputePlaneIsTargeted tests the predicate that gates the cluster-validator +// probe. The validator should run when the compute plane is explicitly targeted +// (--compute-plane, --all) or implicitly targeted by ModeSingle + --pre. +// It must NOT run for --pre alone in ModeSplit, where the two context flags +// identify separate clusters and --pre does not constitute targeting the compute plane. +func TestComputePlaneIsTargeted(t *testing.T) { + t.Cleanup(func() { + checkPre = false + checkComputePlane = false + checkAll = false + }) + + tests := []struct { + name string + pre bool + cp bool + all bool + mode kubectx.Mode + want bool + }{ + {"--compute-plane single", false, true, false, kubectx.ModeSingle, true}, + {"--compute-plane split", false, true, false, kubectx.ModeSplit, true}, + {"--all single", false, false, true, kubectx.ModeSingle, true}, + {"--all split", false, false, true, kubectx.ModeSplit, true}, + {"--pre single - implicit compute plane", true, false, false, kubectx.ModeSingle, true}, + {"--pre split - must not target compute plane", true, false, false, kubectx.ModeSplit, false}, + {"--control-plane only", false, false, false, kubectx.ModeSingle, false}, + {"no relevant flag", false, false, false, kubectx.ModeSingle, false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + checkPre = tt.pre + checkComputePlane = tt.cp + checkAll = tt.all + assert.Equal(t, tt.want, computePlaneIsTargeted(tt.mode)) + }) + } +} + +func TestControlPlaneIsTargeted(t *testing.T) { + t.Cleanup(func() { + checkPre = false + checkControlPlane = false + checkAll = false + }) + + tests := []struct { + name string + pre bool + cp bool + all bool + mode kubectx.Mode + want bool + }{ + {"--control-plane single", false, true, false, kubectx.ModeSingle, true}, + {"--control-plane split", false, true, false, kubectx.ModeSplit, true}, + {"--all single", false, false, true, kubectx.ModeSingle, true}, + {"--all split", false, false, true, kubectx.ModeSplit, true}, + {"--pre single -- implicit control plane", true, false, false, kubectx.ModeSingle, true}, + {"--pre split -- must not target control plane", true, false, false, kubectx.ModeSplit, false}, + {"--compute-plane only", false, false, false, kubectx.ModeSingle, false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + checkPre = tt.pre + checkControlPlane = tt.cp + checkAll = tt.all + assert.Equal(t, tt.want, controlPlaneIsTargeted(tt.mode)) + }) + } +} + +// TestCheck_ComputePlaneFlagRunsChecks verifies that --compute-plane alone +// produces compute-plane-cluster category events. Before the gating fix this +// flag was a complete no-op and produced no check events at all. +func TestCheck_ComputePlaneFlagRunsChecks(t *testing.T) { + resetCheckFlags(t) + t.Cleanup(func() { + selfHostedJSON = false + selfHostedOutput = "text" + checkComputePlane = false + }) + + var stderr bytes.Buffer + rootCmd.SetErr(&stderr) + rootCmd.SetOut(&bytes.Buffer{}) + + t.Setenv("NVCF_CLI_SELFHOSTED_SKIP_INOTIFY", "1") + rootCmd.SetArgs([]string{"self-hosted", "check", "--compute-plane", "--skip-cluster-validation", "--json"}) + _ = rootCmd.Execute() + + lines := parseJSONLLines(t, stderr.String()) + require.NotEmpty(t, lines, "expected at least one JSONL line") + + var categories []string + for _, l := range lines[1:] { + if l["event"] == "category_completed" { + if cat, ok := l["category"].(string); ok { + categories = append(categories, cat) + } + } + } + assert.Contains(t, categories, "compute-plane-cluster", + "--compute-plane must produce compute-plane-cluster events") +} + +// TestCheck_ControlPlaneFlagRunsChecks verifies that --control-plane alone +// produces control-plane-cluster category events. +func TestCheck_ControlPlaneFlagRunsChecks(t *testing.T) { + resetCheckFlags(t) + t.Cleanup(func() { + selfHostedJSON = false + selfHostedOutput = "text" + checkControlPlane = false + }) + + var stderr bytes.Buffer + rootCmd.SetErr(&stderr) + rootCmd.SetOut(&bytes.Buffer{}) + + t.Setenv("NVCF_CLI_SELFHOSTED_SKIP_INOTIFY", "1") + rootCmd.SetArgs([]string{"self-hosted", "check", "--control-plane", "--skip-cluster-validation", "--json"}) + _ = rootCmd.Execute() + + lines := parseJSONLLines(t, stderr.String()) + require.NotEmpty(t, lines, "expected at least one JSONL line") + + var categories []string + for _, l := range lines[1:] { + if l["event"] == "category_completed" { + if cat, ok := l["category"].(string); ok { + categories = append(categories, cat) + } + } + } + assert.Contains(t, categories, "control-plane-cluster", + "--control-plane must produce control-plane-cluster events") +} + +// TestCheck_ValidatorSkipNoteAppearsOnComputePlane verifies that the +// "cluster-validator skipped" note appears on stderr when --compute-plane is +// used with --skip-cluster-validation (compute plane is targeted, validator is +// suppressed). +func TestCheck_ValidatorSkipNoteAppearsOnComputePlane(t *testing.T) { + resetCheckFlags(t) + t.Cleanup(func() { + selfHostedJSON = false + selfHostedOutput = "text" + checkComputePlane = false + checkSkipClusterValidation = false + }) + // --skip-cluster-validation does not gate the inotify prober, which lists + // every node and creates a privileged pod on each. Without this the test + // writes to whatever cluster is in the developer's current kubecontext. + t.Setenv("NVCF_CLI_SELFHOSTED_SKIP_INOTIFY", "1") + + var stderr bytes.Buffer + rootCmd.SetErr(&stderr) + rootCmd.SetOut(&bytes.Buffer{}) + + rootCmd.SetArgs([]string{ + "self-hosted", "check", "--compute-plane", + "--skip-cluster-validation", "--json", + }) + _ = rootCmd.Execute() + + assert.Contains(t, stderr.String(), "cluster-validator skipped", + "expected skip note when compute plane is targeted and --skip-cluster-validation is set") +} + +// --pre in ModeSplit visits both clusters, so when the validator is skipped the +// operator must be told why. This previously asserted silence, which is the +// "no validator row vs validator silently dropped" confusion the note exists +// to prevent. +func TestCheck_ValidatorSkipNotePresentForPreInSplitMode(t *testing.T) { + resetCheckFlags(t) + t.Cleanup(func() { + selfHostedJSON = false + selfHostedOutput = "text" + checkPre = false + checkSkipClusterValidation = false + selfHostedControlPlaneContext = "" + selfHostedComputePlaneContext = "" + }) + + var stderr bytes.Buffer + rootCmd.SetErr(&stderr) + rootCmd.SetOut(&bytes.Buffer{}) + + rootCmd.SetArgs([]string{ + "self-hosted", "check", "--pre", + "--skip-cluster-validation", "--json", + "--control-plane-context", "admin@cp", + "--compute-plane-context", "admin@gpu1", + }) + _ = rootCmd.Execute() + + assert.Contains(t, stderr.String(), "cluster-validator skipped", + "--pre in split mode visits both clusters, so an explicit skip must be explained") +} + func parseJSONLLines(t *testing.T, s string) []map[string]any { t.Helper() var out []map[string]any @@ -363,3 +573,177 @@ func parseJSONLLines(t *testing.T, s string) []map[string]any { } return out } + +// TestPlaneIsVisited covers the dispatch predicates, which are broader than +// the targeting predicates: --pre in ModeSplit visits both clusters for the +// shared pre-install checks without targeting either role. Gating the split +// dispatch on the targeting predicates alone would make --pre a no-op there. +// +// The mode column is kept to document that the answer is the same in both +// modes: the predicate is mode-independent by absorption, so a table that +// varied only the mode would be asserting a dependence that does not exist. +func TestPlaneIsVisited(t *testing.T) { + t.Cleanup(func() { + checkPre = false + checkControlPlane = false + checkComputePlane = false + checkAll = false + }) + + tests := []struct { + name string + pre, cp, gpu bool + all bool + mode kubectx.Mode + wantCP bool + wantComputeGP bool + }{ + {"--pre split visits both", true, false, false, false, kubectx.ModeSplit, true, true}, + {"--pre single visits both", true, false, false, false, kubectx.ModeSingle, true, true}, + {"--control-plane split skips compute", false, true, false, false, kubectx.ModeSplit, true, false}, + {"--compute-plane split skips control", false, false, true, false, kubectx.ModeSplit, false, true}, + {"--all split visits both", false, false, false, true, kubectx.ModeSplit, true, true}, + {"--control-plane single skips compute", false, true, false, false, kubectx.ModeSingle, true, false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + checkPre, checkControlPlane, checkComputePlane, checkAll = tt.pre, tt.cp, tt.gpu, tt.all + assert.Equal(t, tt.wantCP, controlPlaneIsVisited()) + assert.Equal(t, tt.wantComputeGP, computePlaneIsVisited()) + }) + } +} + +// TestCheck_SplitModeControlPlaneOnlySkipsComputeCluster is the regression +// guard for the dispatch gating: in ModeSplit with only --control-plane, the +// compute cluster must not be contacted at all, so no compute-plane-cluster +// category is emitted. +func TestCheck_SplitModeControlPlaneOnlySkipsComputeCluster(t *testing.T) { + resetCheckFlags(t) + t.Cleanup(func() { + selfHostedJSON = false + selfHostedOutput = "text" + checkControlPlane = false + selfHostedControlPlaneContext = "" + selfHostedComputePlaneContext = "" + }) + + var stderr bytes.Buffer + rootCmd.SetErr(&stderr) + rootCmd.SetOut(&bytes.Buffer{}) + + t.Setenv("NVCF_CLI_SELFHOSTED_SKIP_INOTIFY", "1") + rootCmd.SetArgs([]string{"self-hosted", "check", "--control-plane", + "--control-plane-context", "cp-ctx", "--compute-plane-context", "gpu-ctx", + "--skip-cluster-validation", "--json"}) + _ = rootCmd.Execute() + + lines := parseJSONLLines(t, stderr.String()) + require.NotEmpty(t, lines, "expected at least one JSONL line") + + var categories []string + for _, l := range lines[1:] { + if l["event"] == "category_completed" { + if cat, ok := l["category"].(string); ok { + categories = append(categories, cat) + } + } + } + assert.Contains(t, categories, "control-plane-cluster") + assert.NotContains(t, categories, "compute-plane-cluster", + "--control-plane in split mode must not probe the compute cluster") +} + +// TestCheck_HostLocalChecksRunOnce guards against the host-local categories +// being emitted once per role. Local tool versions and registry credentials +// are checked on the operator's machine, so running them for both roles +// repeats the same check IDs in --json and double-counts them in the totals. +func TestCheck_HostLocalChecksRunOnce(t *testing.T) { + resetCheckFlags(t) + t.Cleanup(func() { + selfHostedJSON = false + selfHostedOutput = "text" + checkAll = false + }) + + var stderr bytes.Buffer + rootCmd.SetErr(&stderr) + rootCmd.SetOut(&bytes.Buffer{}) + + t.Setenv("NVCF_CLI_SELFHOSTED_SKIP_INOTIFY", "1") + rootCmd.SetArgs([]string{"self-hosted", "check", "--all", "--skip-cluster-validation", "--json"}) + _ = rootCmd.Execute() + + lines := parseJSONLLines(t, stderr.String()) + require.NotEmpty(t, lines, "expected at least one JSONL line") + + counts := map[string]int{} + for _, l := range lines { + if l["event"] != "check_completed" { + continue + } + if id, ok := l["id"].(string); ok && strings.HasPrefix(id, "local-host-tools-") { + counts[id]++ + } + } + require.NotEmpty(t, counts, "expected local-host-tools checks in the stream") + for id, n := range counts { + assert.Equal(t, 1, n, "check %s emitted %d times", id, n) + } +} + +// The JSON verdict and the exit code must derive from the same predicate. A +// warning-severity result previously emitted success:false / verdict:failed +// while the process exited 0, so a CI gate on final.success broke for every +// user whose registry credentials live in a Docker credential helper. +func TestEmitCheckFinal_WarningIsNotAFailure(t *testing.T) { + results := []selfhosted.CheckResult{ + {ID: "a", Passed: true, Severity: selfhosted.SeverityInfo}, + {ID: "b", Passed: false, Severity: selfhosted.SeverityWarning}, + } + assert.False(t, anyFailed(results), "a warning must not set the exit code") + + var buf bytes.Buffer + sink := progress.NewJSONLRenderer(&buf) + emitCheckFinal(context.Background(), sink, results) + + line := buf.String() + assert.Contains(t, line, `"verdict":"warnings"`) + assert.Contains(t, line, `"success":true`, + "success must agree with the exit code") + assert.Contains(t, line, `"failedCount":0`) +} + +func TestEmitCheckFinal_ErrorIsAFailure(t *testing.T) { + results := []selfhosted.CheckResult{ + {ID: "a", Passed: true, Severity: selfhosted.SeverityInfo}, + {ID: "b", Passed: false, Severity: selfhosted.SeverityError}, + } + assert.True(t, anyFailed(results)) + + var buf bytes.Buffer + sink := progress.NewJSONLRenderer(&buf) + emitCheckFinal(context.Background(), sink, results) + assert.Contains(t, buf.String(), `"verdict":"failed"`) + assert.Contains(t, buf.String(), `"success":false`) +} + +// Both roles emit a cluster-validator result under the same ID, so stopping at +// the first drops the other transcript entirely from --all --show-logs. +func TestMaybeShowClusterValidatorLogs_PrintsBothRoles(t *testing.T) { + prev := checkShowLogs + checkShowLogs = true + t.Cleanup(func() { checkShowLogs = prev }) + + var buf bytes.Buffer + maybeShowClusterValidatorLogs(&buf, []selfhosted.CheckResult{ + {ID: "cluster-validator", Logs: "control-plane transcript\n"}, + {ID: "cluster-validator", Logs: "compute-plane transcript\n"}, + }) + + out := buf.String() + assert.Contains(t, out, "control-plane transcript") + assert.Contains(t, out, "compute-plane transcript") + assert.Equal(t, 2, strings.Count(out, "--- cluster-validator logs ---")) +} diff --git a/src/clis/nvcf-cli/cmd/self_hosted_check_validatorenv_test.go b/src/clis/nvcf-cli/cmd/self_hosted_check_validatorenv_test.go new file mode 100644 index 0000000000..3d97b3713b --- /dev/null +++ b/src/clis/nvcf-cli/cmd/self_hosted_check_validatorenv_test.go @@ -0,0 +1,217 @@ +/* +SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +SPDX-License-Identifier: Apache-2.0 + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package cmd + +import ( + "bytes" + "context" + "sync" + "testing" + + "github.com/spf13/viper" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "nvcf-cli/internal/selfhosted" +) + +// The Job gets what the chart CronJob forwards: the post-install signal on +// every run but --pre, relocated namespaces, the Gateway override and the +// probe image. +func TestClusterValidatorJobEnv(t *testing.T) { + resetCheckFlags(t) + for _, k := range []string{"NVCF_OPENBAO_NAMESPACE", "NVCF_ENVOY_GATEWAY_NAMESPACE", "NVCF_GATEWAY_NAMES", "NVCF_N2N_PROBE_IMAGE"} { + t.Setenv(k, "") + } + + checkPre = false + env := clusterValidatorJobEnv(selfhosted.StackValues{EnvoyGatewayNamespace: "gateway"}) + assert.Equal(t, "true", env["VALIDATOR_POST_INSTALL"], "a post-install run must say so") + assert.Equal(t, "gateway", env["NVCF_ENVOY_GATEWAY_NAMESPACE"], "the stack's controller namespace is forwarded") + assert.NotContains(t, env, "NVCF_OPENBAO_NAMESPACE") + + checkPre = true + t.Setenv("NVCF_OPENBAO_NAMESPACE", "openbao") + t.Setenv("NVCF_ENVOY_GATEWAY_NAMESPACE", "edge") + t.Setenv("NVCF_GATEWAY_NAMES", "gateway/shared-gw") + require.NoError(t, selfHostedCheckCmd.Flags().Set("cluster-validator-probe-image", "mirror.example/busybox:1.36")) + env = clusterValidatorJobEnv(selfhosted.StackValues{EnvoyGatewayNamespace: "gateway"}) + assert.NotContains(t, env, "VALIDATOR_POST_INSTALL", "--pre is pre-install") + assert.Equal(t, "openbao", env["NVCF_OPENBAO_NAMESPACE"]) + assert.Equal(t, "edge", env["NVCF_ENVOY_GATEWAY_NAMESPACE"], "an explicit setting beats the stack values") + assert.Equal(t, "gateway/shared-gw", env["NVCF_GATEWAY_NAMES"]) + assert.Equal(t, "mirror.example/busybox:1.36", env["NVCF_N2N_PROBE_IMAGE"]) +} + +// The control-plane validator is handed the registries the credential check +// enumerated (here the validator image's), and the forwarded env. A post-install +// --control-plane run must carry VALIDATOR_POST_INSTALL. +func TestCheck_ControlPlaneValidatorGetsRegistriesAndEnv(t *testing.T) { + resetCheckFlags(t) + var mu sync.Mutex + got := map[string]selfhosted.ClusterValidatorParams{} + prev := newClusterValidatorForSelfHosted + newClusterValidatorForSelfHosted = func() selfhosted.ClusterValidator { + return func(_ context.Context, p selfhosted.ClusterValidatorParams) selfhosted.ClusterValidatorResult { + mu.Lock() + defer mu.Unlock() + got[p.Role] = p + return selfhosted.ClusterValidatorResult{Passed: true} + } + } + t.Cleanup(func() { newClusterValidatorForSelfHosted = prev }) + t.Setenv("NVCF_CLI_SELFHOSTED_SKIP_INOTIFY", "1") + + var errBuf bytes.Buffer + rootCmd.SetErr(&errBuf) + rootCmd.SetOut(&bytes.Buffer{}) + rootCmd.SetArgs([]string{"self-hosted", "check", "--control-plane", "--json", + "--cluster-validator-image", "nvcr.io/nvidia/nvcf-byoc/cluster-validator:1.0.0"}) + _ = rootCmd.Execute() + + cp, ok := got["control-plane"] + require.True(t, ok, "the control-plane validator must run; output: %s", errBuf.String()) + var regs []string + for _, r := range cp.Registries { + regs = append(regs, r.Registry) + } + assert.Contains(t, regs, "nvcr.io", "the enumerated registries must reach the validator") + assert.Equal(t, "true", cp.Env["VALIDATOR_POST_INSTALL"]) +} + +// NVCF_GATEWAY_NAMES written as a YAML list in the config file reaches the Job +// in the comma form the validator parses, not as an empty string. +func TestClusterValidatorJobEnv_GatewayNamesYAMLList(t *testing.T) { + t.Setenv("NVCF_GATEWAY_NAMES", "") + viper.Set("NVCF_GATEWAY_NAMES", []any{"gateway/nvcf-gw", " edge/shared ", ""}) + t.Cleanup(func() { viper.Set("NVCF_GATEWAY_NAMES", nil) }) + + env := clusterValidatorJobEnv(selfhosted.StackValues{}) + assert.Equal(t, "gateway/nvcf-gw,edge/shared", env["NVCF_GATEWAY_NAMES"]) +} + +// --pre in split mode visits both clusters, so each gets its own validator +// role against its own context. A bare --pre is pre-install for both; --all +// checks both as installed, and a role's own flag checks that role as +// installed while the other stays pre-install. +func TestCheck_PreSplitRunsBothValidatorRoles(t *testing.T) { + for _, tc := range []struct { + name string + extra []string + cpPost, gpuPost bool + }{ + {name: "bare pre", extra: []string{"--icms-url", "https://sis.example.invalid"}}, + {name: "pre all", extra: []string{"--all", "--cluster-name", "gpu"}, cpPost: true, gpuPost: true}, + {name: "pre control-plane", extra: []string{"--control-plane"}, cpPost: true}, + } { + t.Run(tc.name, func(t *testing.T) { + resetCheckFlags(t) + var mu sync.Mutex + got := map[string]selfhosted.ClusterValidatorParams{} + prev := newClusterValidatorForSelfHosted + newClusterValidatorForSelfHosted = func() selfhosted.ClusterValidator { + return func(_ context.Context, p selfhosted.ClusterValidatorParams) selfhosted.ClusterValidatorResult { + mu.Lock() + defer mu.Unlock() + got[p.Role] = p + return selfhosted.ClusterValidatorResult{Passed: true} + } + } + t.Cleanup(func() { newClusterValidatorForSelfHosted = prev }) + t.Setenv("NVCF_CLI_SELFHOSTED_SKIP_INOTIFY", "1") + + var errBuf bytes.Buffer + rootCmd.SetErr(&errBuf) + rootCmd.SetOut(&bytes.Buffer{}) + rootCmd.SetArgs(append([]string{"self-hosted", "check", "--pre", "--json", + "--control-plane-context", "cp-ctx", "--compute-plane-context", "gpu-ctx", + "--cluster-validator-image", "nvcr.io/nvidia/nvcf-byoc/cluster-validator:1.0.0"}, + tc.extra...)) + _ = rootCmd.Execute() + + mu.Lock() + defer mu.Unlock() + cp, ok := got["control-plane"] + require.True(t, ok, "the control-plane validator must run; output: %s", errBuf.String()) + gpu, ok := got["compute-plane"] + require.True(t, ok, "the compute-plane validator must run; output: %s", errBuf.String()) + assert.Equal(t, "cp-ctx", cp.KubeContext) + assert.Equal(t, "gpu-ctx", gpu.KubeContext) + assert.Equal(t, tc.cpPost, cp.Env["VALIDATOR_POST_INSTALL"] == "true", "control-plane post-install") + assert.Equal(t, tc.gpuPost, gpu.Env["VALIDATOR_POST_INSTALL"] == "true", "compute-plane post-install") + }) + } +} + +// NVCF_CLI_CLUSTER_VALIDATOR_REGISTRIES is read, and a comma-separated value is +// split into separate registries rather than parsed as one host. +func TestCheck_RegistriesEnvVarIsSplitOnCommas(t *testing.T) { + resetCheckFlags(t) + t.Setenv("NVCF_CLI_CLUSTER_VALIDATOR_REGISTRIES", "harbor.example.com:443, ghcr.io:443") + var mu sync.Mutex + var got selfhosted.ClusterValidatorParams + prev := newClusterValidatorForSelfHosted + newClusterValidatorForSelfHosted = func() selfhosted.ClusterValidator { + return func(_ context.Context, p selfhosted.ClusterValidatorParams) selfhosted.ClusterValidatorResult { + mu.Lock() + defer mu.Unlock() + if p.Role == "control-plane" { + got = p + } + return selfhosted.ClusterValidatorResult{Passed: true} + } + } + t.Cleanup(func() { newClusterValidatorForSelfHosted = prev }) + t.Setenv("NVCF_CLI_SELFHOSTED_SKIP_INOTIFY", "1") + + rootCmd.SetErr(&bytes.Buffer{}) + rootCmd.SetOut(&bytes.Buffer{}) + rootCmd.SetArgs([]string{"self-hosted", "check", "--control-plane", "--json", + "--cluster-validator-image", "nvcr.io/nvidia/nvcf-byoc/cluster-validator:1.0.0"}) + _ = rootCmd.Execute() + + mu.Lock() + defer mu.Unlock() + var regs []string + for _, r := range got.Registries { + regs = append(regs, r.Registry) + } + assert.Contains(t, regs, "harbor.example.com") + assert.Contains(t, regs, "ghcr.io") +} + +// The stack's Gateways and HA mode reach the Job; an explicit setting wins. +func TestClusterValidatorJobEnv_ForwardsStackGatewaysAndHAMode(t *testing.T) { + for _, k := range []string{"NVCF_GATEWAY_NAMES", "NVCF_HA_MODE"} { + t.Setenv(k, "") + } + stack := selfhosted.StackValues{HAMode: "none", Gateways: []string{"gw/grpc-gw", "gw/shared-gw"}} + env := clusterValidatorJobEnv(stack) + assert.Equal(t, "gw/grpc-gw,gw/shared-gw", env["NVCF_GATEWAY_NAMES"]) + assert.Equal(t, "none", env["NVCF_HA_MODE"]) + + t.Setenv("NVCF_GATEWAY_NAMES", "edge/nvcf-gw") + t.Setenv("NVCF_HA_MODE", "preferred") + env = clusterValidatorJobEnv(stack) + assert.Equal(t, "edge/nvcf-gw", env["NVCF_GATEWAY_NAMES"]) + assert.Equal(t, "preferred", env["NVCF_HA_MODE"]) + + t.Setenv("NVCF_HA_MODE", "") + env = clusterValidatorJobEnv(selfhosted.StackValues{}) + assert.NotContains(t, env, "NVCF_HA_MODE", "no stack, no mode: the validator records sub-quorum as not assessed") +} diff --git a/src/clis/nvcf-cli/cmd/self_hosted_helm_runtime.go b/src/clis/nvcf-cli/cmd/self_hosted_helm_runtime.go index 66aeaa4fbe..ec9b4939ae 100644 --- a/src/clis/nvcf-cli/cmd/self_hosted_helm_runtime.go +++ b/src/clis/nvcf-cli/cmd/self_hosted_helm_runtime.go @@ -30,7 +30,7 @@ var resolveSelfHostedHelmRuntimeMode = func(ctx context.Context) (selfhosted.Hel Tools: selfHostedHelmRuntimePreflightTools(), }) for _, result := range results { - if !result.Passed && result.Severity == "error" { + if result.IsBlockingFailure() { return "", fmt.Errorf("%s: %s", result.ID, result.Message) } } diff --git a/src/clis/nvcf-cli/cmd/self_hosted_test.go b/src/clis/nvcf-cli/cmd/self_hosted_test.go index 8c5e1ce23d..bfec3afd11 100644 --- a/src/clis/nvcf-cli/cmd/self_hosted_test.go +++ b/src/clis/nvcf-cli/cmd/self_hosted_test.go @@ -58,6 +58,7 @@ func TestSelfHostedFlags_OnlyOneContextErrors(t *testing.T) { } func TestSelfHostedFlags_SameContextErrors(t *testing.T) { + resetCheckFlags(t) rootCmd.SetArgs([]string{"self-hosted", "check", "--control-plane-context=cp", "--compute-plane-context=cp"}) t.Cleanup(func() { selfHostedControlPlaneContext = "" diff --git a/src/clis/nvcf-cli/cmd/self_hosted_up.go b/src/clis/nvcf-cli/cmd/self_hosted_up.go index d9b3f9f9e4..681da61272 100644 --- a/src/clis/nvcf-cli/cmd/self_hosted_up.go +++ b/src/clis/nvcf-cli/cmd/self_hosted_up.go @@ -1507,6 +1507,17 @@ func kubectxFor(phaseNum int) string { } } +type registerValuesWriteRequest struct { + StackPath string + ClusterName string + NCAID string + Region string + IdentitySource string + ClusterID string + ClusterGroupID string + Endpoints registerEndpointValues +} + // writeRegisterValuesYAML persists the helm values handoff file the worker-layer // helmfile expects at /out/-register-values.yaml. Schema mirrors // `printRegistrationOutput` (cmd/cluster_registration.go, fixed in `ff9aaf7`): @@ -1519,17 +1530,6 @@ func kubectxFor(phaseNum int) string { // The pre-`ff9aaf7` shape (nested `selfManaged.clusterId`, lowercase-d) left // the chart's `.Values.clusterID` empty → cluster-dto.yaml rendered with empty // IDs → operator failed to fetch backend identity. (Iter #12 from dev-VM E2E.) -type registerValuesWriteRequest struct { - StackPath string - ClusterName string - NCAID string - Region string - IdentitySource string - ClusterID string - ClusterGroupID string - Endpoints registerEndpointValues -} - func writeRegisterValuesYAML(req registerValuesWriteRequest) error { outDir := filepath.Join(req.StackPath, "out") if err := os.MkdirAll(outDir, 0o755); err != nil { diff --git a/src/clis/nvcf-cli/go.mod b/src/clis/nvcf-cli/go.mod index 9a4c8d3d83..19cc5ea3ba 100644 --- a/src/clis/nvcf-cli/go.mod +++ b/src/clis/nvcf-cli/go.mod @@ -11,6 +11,7 @@ require ( github.com/charmbracelet/lipgloss v1.0.0 github.com/muesli/termenv v0.15.2 github.com/spf13/cobra v1.9.1 + github.com/spf13/pflag v1.0.6 github.com/spf13/viper v1.18.2 github.com/stretchr/testify v1.10.0 golang.org/x/oauth2 v0.27.0 @@ -23,6 +24,7 @@ require ( k8s.io/apiextensions-apiserver v0.34.1 k8s.io/apimachinery v0.34.1 k8s.io/client-go v0.34.1 + sigs.k8s.io/yaml v1.6.0 ) require ( @@ -68,7 +70,6 @@ require ( github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.6.0 // indirect - github.com/spf13/pflag v1.0.6 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/x448/float16 v0.8.4 // indirect go.uber.org/multierr v1.11.0 // indirect @@ -89,5 +90,4 @@ require ( sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect sigs.k8s.io/randfill v1.0.0 // indirect sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect - sigs.k8s.io/yaml v1.6.0 // indirect ) diff --git a/src/clis/nvcf-cli/internal/agentskill/skilldata_generated.go b/src/clis/nvcf-cli/internal/agentskill/skilldata_generated.go index 2e403af1a1..becf4e98a8 100644 --- a/src/clis/nvcf-cli/internal/agentskill/skilldata_generated.go +++ b/src/clis/nvcf-cli/internal/agentskill/skilldata_generated.go @@ -31,14 +31,14 @@ import ( const skillDataZipBase64 = "" + "UEsDBBQACAAAAAAAIQAAAAAAAAAAAAAAAAASAAkAZGF0YS9tYW5pZmVzdC5qc29uVVQFAAEAps4S" + "ewogICJzY2hlbWFWZXJzaW9uIjogMSwKICAiZ2VuZXJhdGVkQXQiOiAiMTk3MC0wMS0wMVQwMDow" + - "MDowMFoiLAogICJ0b3RhbEZpbGVzIjogMjIsCiAgInRvdGFsQnl0ZXMiOiAyMDAzNjgsCiAgImZp" + + "MDowMFoiLAogICJ0b3RhbEZpbGVzIjogMjIsCiAgInRvdGFsQnl0ZXMiOiAyMDU2ODksCiAgImZp" + "bGVzIjogWwogICAgewogICAgICAicGF0aCI6ICJudmNmLXNlbGYtbWFuYWdlZC1jbGkvU0tJTEwu" + - "bWQiLAogICAgICAic2hhMjU2IjogImM0ZjI4OGY2MmU1MDg4NjI5ZGY1MTcyYTlhYjMwZWVlOGMz" + - "OTRmYzgzOTFjMjMwMDMzNzI2NGVmZjRiMmU3ZWYiLAogICAgICAic2l6ZSI6IDI0OTA5CiAgICB9" + + "bWQiLAogICAgICAic2hhMjU2IjogImFhOWY3ZTI0NmYzODA0YjMzNDNiNjU2MWU1YmNmZTdiYjcy" + + "YjI0MDIxOTMzZTA0OWY4NWUwMDI4OTAzYmJmYTQiLAogICAgICAic2l6ZSI6IDI0OTE1CiAgICB9" + "LAogICAgewogICAgICAicGF0aCI6ICJudmNmLXNlbGYtbWFuYWdlZC1jbGkvZXhhbXBsZXMvY2kt" + - "cGlwZWxpbmVzLm1kIiwKICAgICAgInNoYTI1NiI6ICI2Yjg2NGE3OWFjMTgxYTAyNTQ3NWJmNTA2" + - "YjM0NmIyOWQ1NDNlOGFhZmVmMWJjNDk2Y2E2NzA5ZjQ3M2FhZWU5IiwKICAgICAgInNpemUiOiAy" + - "ODAwCiAgICB9LAogICAgewogICAgICAicGF0aCI6ICJudmNmLXNlbGYtbWFuYWdlZC1jbGkvZXhh" + + "cGlwZWxpbmVzLm1kIiwKICAgICAgInNoYTI1NiI6ICJlNWYyMDUxYTdmNGZmMGE5N2M0M2NmYmEy" + + "NzU0NDA3YTQzMGMwMDdlMmVkNzFmY2QxYzgxNTQwNmExYjNkYzI1IiwKICAgICAgInNpemUiOiAz" + + "NTQ2CiAgICB9LAogICAgewogICAgICAicGF0aCI6ICJudmNmLXNlbGYtbWFuYWdlZC1jbGkvZXhh" + "bXBsZXMvbXVsdGktY2x1c3Rlci5tZCIsCiAgICAgICJzaGEyNTYiOiAiNmRmMjg0NjBiNTgzNDNl" + "MjEwMjVhYTViZDc5ZjMzYjUxMmRjYTlmZjM5YjZhYjI2MDRmYzNmZWJiMDE1ODhiNCIsCiAgICAg" + "ICJzaXplIjogMzE2NQogICAgfSwKICAgIHsKICAgICAgInBhdGgiOiAibnZjZi1zZWxmLW1hbmFn" + @@ -46,3665 +46,3759 @@ const skillDataZipBase64 = "" + "NGI5ZDI4MzA2MTk2MzBiM2FmOWZlOTc0MDQ3OGU0YWQ1ZTBiNGVhYjMwNGI2Y2FmZjQyYjExNDFi" + "ODJjNmYiLAogICAgICAic2l6ZSI6IDMzMzMKICAgIH0sCiAgICB7CiAgICAgICJwYXRoIjogIm52" + "Y2Ytc2VsZi1tYW5hZ2VkLWNsaS9wcm9tcHRzL2NyZWF0ZS1hbmQtcnVuLXRhc2subWQiLAogICAg" + - "ICAic2hhMjU2IjogImNmYzQ4OGVlODhmM2I4YmY1YWMzOWE5MGU5ZjdlNzBlYmRhODM0OWM0YTQw" + - "ZTQyNDJmNWQ2ZTI3MDJkZDAxM2QiLAogICAgICAic2l6ZSI6IDY1NTIKICAgIH0sCiAgICB7CiAg" + + "ICAic2hhMjU2IjogIjcxMWMzMGFiODg5ZTNlYjI4MWQxZmRjODUxMDI3ZWIzMzkzYzdiODRmYzc0" + + "NTUwNDkwZDU1OTNjNDE5NTYzOTkiLAogICAgICAic2l6ZSI6IDY1NTkKICAgIH0sCiAgICB7CiAg" + "ICAgICJwYXRoIjogIm52Y2Ytc2VsZi1tYW5hZ2VkLWNsaS9wcm9tcHRzL2RlcGxveS1hbmQtaW52" + "b2tlLm1kIiwKICAgICAgInNoYTI1NiI6ICIyNWY0NTU1ZTFiNmZkYzExZGYyYzI4NWJhMDY1MjA2" + "NzZiYzVjOWU2ZTQ4NzczNWRiZDllOGFiNGQ1ZmUzZjgzIiwKICAgICAgInNpemUiOiA5NDk2CiAg" + "ICB9LAogICAgewogICAgICAicGF0aCI6ICJudmNmLXNlbGYtbWFuYWdlZC1jbGkvcHJvbXB0cy9k" + - "aWFnbm9zZS1mYWlsZWQtaW5zdGFsbC5tZCIsCiAgICAgICJzaGEyNTYiOiAiMjY1NjJkMGUwMjU3" + - "NTU5NWY1ZjNmZTA2YTU5ZjQ2M2U0NGUyMGFkNjg2YjRlYzQ2Mjk2MjI1ODRjZTRmMDQ3NSIsCiAg" + - "ICAgICJzaXplIjogMzc3MwogICAgfSwKICAgIHsKICAgICAgInBhdGgiOiAibnZjZi1zZWxmLW1h" + + "aWFnbm9zZS1mYWlsZWQtaW5zdGFsbC5tZCIsCiAgICAgICJzaGEyNTYiOiAiNDA4ZDNjMjM3ZWE2" + + "NzRmMWM5MGE1ZjI2NTUwNWM0MWJkMjQ5ZGQ2Y2MzOGZkOTE4NGY2Njk3ZDhmMDBjNTY1YSIsCiAg" + + "ICAgICJzaXplIjogMzc3NAogICAgfSwKICAgIHsKICAgICAgInBhdGgiOiAibnZjZi1zZWxmLW1h" + "bmFnZWQtY2xpL3Byb21wdHMvaW5zdGFsbC1mcm9tLXNjcmF0Y2gubWQiLAogICAgICAic2hhMjU2" + "IjogIjgzMTA5NmY5MmZjMjU2NWQ0NDg1YmExZDBmNWM1MGZiYTljNjQyNTIxZmRhOTZhNzllZDUy" + "MmU0ZmU3NWEzYmQiLAogICAgICAic2l6ZSI6IDEwMjQxCiAgICB9LAogICAgewogICAgICAicGF0" + "aCI6ICJudmNmLXNlbGYtbWFuYWdlZC1jbGkvcHJvbXB0cy9yb3RhdGUtY2x1c3Rlci1qd2tzLm1k" + - "IiwKICAgICAgInNoYTI1NiI6ICI2ZmFlNTU2MmRjNWU5N2MyNGE5MzYwNWI2NzRhMGFmYzdlYzRh" + - "M2QwYmVjM2NlYmY5ZjY0ZThiZTZmMjY3OGE1IiwKICAgICAgInNpemUiOiAxOTkxCiAgICB9LAog" + + "IiwKICAgICAgInNoYTI1NiI6ICI3NDdiNmM3OTJkYzI0NmE3OWE1MTA4YTk0NWQ5YWIyNzc1MTE3" + + "MjA3ZGExODlmYjE0Mjg0MzQ1NzRhYTYxZTFmIiwKICAgICAgInNpemUiOiAxOTkyCiAgICB9LAog" + "ICAgewogICAgICAicGF0aCI6ICJudmNmLXNlbGYtbWFuYWdlZC1jbGkvcHJvbXB0cy90ZWFyZG93" + - "bi5tZCIsCiAgICAgICJzaGEyNTYiOiAiYmI0MGY4MTM3N2M0Y2UzYWRkOGVhNDFmNzMzMmM1OWZh" + - "ZDA3YmY4YTIwZGQzMDJjZDJiZjgzOGRmYzY0NWQwZiIsCiAgICAgICJzaXplIjogODY3MgogICAg" + + "bi5tZCIsCiAgICAgICJzaGEyNTYiOiAiMWE2NmI5YTM3MmJmOTI3NjQzOTdjOWVmMzliNzNlNDZm" + + "NzE2M2ExNTgyMzgzZjFkNGJiNDE4ZThjM2Q2M2QwMyIsCiAgICAgICJzaXplIjogODY3NgogICAg" + "fSwKICAgIHsKICAgICAgInBhdGgiOiAibnZjZi1zZWxmLW1hbmFnZWQtY2xpL3JlZmVyZW5jZS9j" + "b21tYW5kcy5tZCIsCiAgICAgICJzaGEyNTYiOiAiMmI0YjAzNmU3NTA3MDZiOWUzYjgxZmNkNjAy" + "YzcwNmY3NGY3N2Y2MjQwZmI3YWFkNjIwYzg3OWZmNzQ5NmNkOCIsCiAgICAgICJzaXplIjogODk5" + "MwogICAgfSwKICAgIHsKICAgICAgInBhdGgiOiAibnZjZi1zZWxmLW1hbmFnZWQtY2xpL3JlZmVy" + - "ZW5jZS9leGl0LWNvZGVzLm1kIiwKICAgICAgInNoYTI1NiI6ICI2NGI1ZmE5YmNhZjBlMGE1M2Y3" + - "ODkyMGI3M2ZjYjUzMGFiNjk0NTFiNDhhNWI0OTNmMjUyY2ExODU4M2FiMDg2IiwKICAgICAgInNp" + - "emUiOiAxNzQ5CiAgICB9LAogICAgewogICAgICAicGF0aCI6ICJudmNmLXNlbGYtbWFuYWdlZC1j" + - "bGkvcmVmZXJlbmNlL2ZsYWdzLm1kIiwKICAgICAgInNoYTI1NiI6ICJkNGQ5ODg2N2M1OGE1MjJk" + - "OWJhNmJjMjc0M2ZkZDhmYzBmMDQ3ZDVmNDUxZWRlMmU5YWNlMGI4NzI2NGM2MzczIiwKICAgICAg" + - "InNpemUiOiA5NTE3CiAgICB9LAogICAgewogICAgICAicGF0aCI6ICJudmNmLXNlbGYtbWFuYWdl" + - "ZC1jbGkvcmVmZXJlbmNlL3Ryb3VibGVzaG9vdGluZy5tZCIsCiAgICAgICJzaGEyNTYiOiAiYTYz" + - "MDg0ZTIyNDlmMWQxNTRjNTk4MWU0YTA3MmI3Yzc4NGI4NWVhMTQ2MDg2NTE1ZWFiNGFiNjcwYTk0" + - "MGU5NCIsCiAgICAgICJzaXplIjogNDQ2OAogICAgfSwKICAgIHsKICAgICAgInBhdGgiOiAibnZj" + - "Zi1zZWxmLW1hbmFnZWQtaW5zdGFsbGF0aW9uL1NLSUxMLm1kIiwKICAgICAgInNoYTI1NiI6ICJj" + - "MzNmZDgzMDVlYzNmZDAyMGViNGM5ODJjM2FmZTc2YTdjMWQ0M2MzZTA1NGM4ZmJkMzhjNGJiMWI3" + - "MDdjNzVhIiwKICAgICAgInNpemUiOiAyNTE1NAogICAgfSwKICAgIHsKICAgICAgInBhdGgiOiAi" + - "bnZjZi1zZWxmLW1hbmFnZWQtaW5zdGFsbGF0aW9uL2V2YWxzL2V2YWxzLmpzb24iLAogICAgICAi" + - "c2hhMjU2IjogIjYxMDA3N2I2OWI0NGU1OTUzNzc2NzdjNDM5NDcyMDE1ODUzMWIxNzFhYTk2ZDZk" + - "Y2M3YTY1ZTFkZmQzMDliODkiLAogICAgICAic2l6ZSI6IDIyMDkwCiAgICB9LAogICAgewogICAg" + - "ICAicGF0aCI6ICJudmNmLXNlbGYtbWFuYWdlZC1pbnN0YWxsYXRpb24vZXhhbXBsZXMubWQiLAog" + - "ICAgICAic2hhMjU2IjogIjlhMzllNWNjYWJkNDA4MTljNzFmMmIwZjJkNzUyMzE2YjcyMzE1NjU0" + - "ZDgwZDNkMDE3Njk4Y2UxZmIzZTViMTEiLAogICAgICAic2l6ZSI6IDEwMzY3CiAgICB9LAogICAg" + - "ewogICAgICAicGF0aCI6ICJudmNmLXNlbGYtbWFuYWdlZC1pbnN0YWxsYXRpb24vcmVmZXJlbmNl" + - "cy9jb21wdXRlLXBsYW5lLWluc3RhbGxhdGlvbi5tZCIsCiAgICAgICJzaGEyNTYiOiAiNWRlZTU4" + - "YWUyYzY3YTYyOTQ2ZTNjNTkzMDJhZmFjYzQwYmRlNzFmOGRjMmYyNWNkNzc5OWNjNDViNDc0ZmEx" + - "OSIsCiAgICAgICJzaXplIjogMTIzMDYKICAgIH0sCiAgICB7CiAgICAgICJwYXRoIjogIm52Y2Yt" + - "c2VsZi1tYW5hZ2VkLWluc3RhbGxhdGlvbi9yZWZlcmVuY2VzL2RlYnVnZ2luZy5tZCIsCiAgICAg" + - "ICJzaGEyNTYiOiAiYTUyODE1OGJjMGJmZmZlZWY2ZTAxZGUzODRjYWY1NjVlNjQ0NWI0OGFiZDNj" + - "ZWU0MDA0ODcxZjUyM2Y1M2I5NyIsCiAgICAgICJzaXplIjogMTI0MzcKICAgIH0sCiAgICB7CiAg" + - "ICAgICJwYXRoIjogIm52Y2Ytc2VsZi1tYW5hZ2VkLWluc3RhbGxhdGlvbi9yZWZlcmVuY2VzL2hl" + - "bG1maWxlLXN0cnVjdHVyZS5tZCIsCiAgICAgICJzaGEyNTYiOiAiNjEyMDk2M2Y0NDcyNzQ2MDQ5" + - "NWU5ZmMwZGE1MmFlN2YzYzY5YTM4ZTU3ZWZlNzI2MmM3NTAxNjQxZWRhNWY4MiIsCiAgICAgICJz" + - "aXplIjogMTA3NDYKICAgIH0sCiAgICB7CiAgICAgICJwYXRoIjogIm52Y2Ytc2VsZi1tYW5hZ2Vk" + - "LWluc3RhbGxhdGlvbi9yZWZlcmVuY2VzL252Y2YtdWktYWRkb24ubWQiLAogICAgICAic2hhMjU2" + - "IjogIjUwNTI0ZTZlMTk3MTE4M2JkYzlhZDBhYzM4YTgwN2RmNzJjYzA4NTFiNzlmNTE4ZmUxMWE0" + - "OGU0NTk3Yzg1MGUiLAogICAgICAic2l6ZSI6IDE5NzcKICAgIH0sCiAgICB7CiAgICAgICJwYXRo" + - "IjogIm52Y2Ytc2VsZi1tYW5hZ2VkLWluc3RhbGxhdGlvbi9yZWZlcmVuY2VzL3B1bGwtc2VjcmV0" + - "cy5tZCIsCiAgICAgICJzaGEyNTYiOiAiZWMxZjNmZmNkMTdlNWY5NTgzZmVlMzk3NDRlZTg5N2Q0" + - "NjZiYzNjZjYzODkyOTQyNjRkODllN2Y5ZWQwMWI4NyIsCiAgICAgICJzaXplIjogNTYzMgogICAg" + - "fQogIF0KfQpQSwcIpc8HuUkQAABJEAAAUEsDBBQACAAAAAAAIQAAAAAAAAAAAAAAAAAjAAkAZGF0" + - "YS9udmNmLXNlbGYtbWFuYWdlZC1jbGkvU0tJTEwubWRVVAUAAQCmzhItLS0KbmFtZTogbnZjZi1z" + - "ZWxmLW1hbmFnZWQtY2xpCmRlc2NyaXB0aW9uOiB8CiAgSW5zdGFsbCwgb3BlcmF0ZSwgYW5kIHRl" + - "YXIgZG93biBzZWxmLWhvc3RlZCBOVklESUEgQ2xvdWQgRnVuY3Rpb25zIChOVkNGKQogIGRlcGxv" + - "eW1lbnRzIHdpdGggbnZjZi1jbGkuIFVzZSBmb3IgY29udHJvbC1wbGFuZSBvciBjb21wdXRlLXBs" + - "YW5lIGluc3RhbGwsCiAgc3RhdHVzIGNoZWNrcywgY2x1c3RlciByZWdpc3RyYXRpb24sIGZ1bmN0" + - "aW9uIGRlcGxveS9pbnZva2UsIHRhc2sKICBjcmVhdGUvbGlzdC9jYW5jZWwvZGVsZXRlLCBBUEkg" + - "a2V5cywgYWRtaW4gdG9rZW5zLCBKV0tTIHJvdGF0aW9uLAogIGZhaWxlZC1pbnN0YWxsIGRpYWdu" + - "b3NpcywgYW5kIHVuaW5zdGFsbCBvciBkb3duIHdvcmtmbG93cy4gVHJpZ2dlciBrZXl3b3JkczoK" + - "ICBudmNmLCBudmNmLWNsaSwgc2VsZi1ob3N0ZWQgbnZjZiwgc2VsZi1tYW5hZ2VkIG52Y2YsIE5W" + - "Q0ZCYWNrZW5kLCBOVkNBLCBOQ1AsCiAgSUNNUywgaGVsbWZpbGUsIGNvbnRyb2wgcGxhbmUsIGNv" + - "bXB1dGUgcGxhbmUsIExMTSBmdW5jdGlvbiwKICBPcGVuQUktY29tcGF0aWJsZSBpbnZvY2F0aW9u" + - "LCBSZXNwb25zZXMgQVBJLCBlbWJlZGRpbmdzLCBiYXRjaCB0YXNrLCB0YXNrCiAgbW9uaXRvciwg" + - "Y2x1c3RlciByb3RhdGUsIGNsdXN0ZXIgZGVsZXRlLCBoZWxtIHRhc2ssIGhlbG0tYmFzZWQgdGFz" + - "aywKICB0YXNrIHNlY3JldHMsIHVwZGF0ZSB0YXNrIHNlY3JldHMsIHJldHJpZXZlIHRhc2sgcmVz" + - "dWx0cywgYnVsayBmZXRjaCB0YXNrcywKICB0YXNrIHJlc3VsdHMsIE5WQ1QsIE5WQ1QgdGFzaywg" + - "TlZDVCBiYXRjaCBqb2IuCmFsbG93ZWQtdG9vbHM6IEJhc2gsIFJlYWQsIEFza1VzZXJRdWVzdGlv" + - "bgphcmd1bWVudC1oaW50OiAiW2luc3RhbGx8c3RhdHVzfGNoZWNrfGRlcGxveS1mdW5jdGlvbnxy" + - "ZWdpc3Rlci1jbHVzdGVyfHRlYXJkb3duXSBbYXJnc10iCi0tLQo8IS0tClRva2VuIEJ1ZGdldDoK" + - "LSBMZXZlbCAxIChZQU1MKTogfjEyMCB0b2tlbnMKLSBMZXZlbCAyICh0aGlzIGZpbGUpOiB+MTkw" + - "MCB0b2tlbnMgKHRhcmdldCA8MjAwMCkKLSBMZXZlbCAzIChwcm9tcHRzLywgcmVmZXJlbmNlLywg" + - "ZXhhbXBsZXMvKTogbG9hZGVkIG9uIGRlbWFuZAotLT4KCiMgTlZDRiBTZWxmLUhvc3RlZCBDTEkK" + - "CmBudmNmLWNsaWAgZHJpdmVzIGV2ZXJ5IHN0ZXAgb2YgYnJpbmdpbmcgdXAgc2VsZi1ob3N0ZWQg" + - "TlZJRElBIENsb3VkIEZ1bmN0aW9uczogY2x1c3RlciByZWdpc3RyYXRpb24sIGNvbnRyb2wtcGxh" + - "bmUgaW5zdGFsbCwgY29tcHV0ZS1wbGFuZSBpbnN0YWxsLCBmdW5jdGlvbiBkZXBsb3kvaW52b2tl" + - "LCBhbmQgbGlmZWN5Y2xlIG1hbmFnZW1lbnQuIFVzZSB0aGlzIHNraWxsIGFueSB0aW1lIHRoZSB1" + - "c2VyIHdhbnRzIHRvIG9wZXJhdGUgc2VsZi1ob3N0ZWQgTlZDRi4KCiMjIFdoZW4gdG8gdXNlCgot" + - "ICJpbnN0YWxsIHNlbGYtaG9zdGVkIE5WQ0YiIC8gImJyaW5nIHVwIGFuIE5WQ0YgY2x1c3RlciIK" + - "LSAicmVnaXN0ZXIgYSAoY29tcHV0ZXxHUFUpIGNsdXN0ZXIgd2l0aCBOVkNGIgotICJkZXBsb3kg" + - "YSAoY29udGFpbmVyfEdQVSkgZnVuY3Rpb24iIC8gImludm9rZSBhbiBOVkNGIGZ1bmN0aW9uIgot" + - "ICJjaGVjayBOVkNGIGNsdXN0ZXIgaGVhbHRoIiAvICJpcyBteSBOVkNGIGluc3RhbGwgT0s/Igot" + - "ICJyb3RhdGUgTlZDRiBjbHVzdGVyIEpXS1MiIC8gInRoZSBOVkNBIGFnZW50IHN0b3BwZWQgYXV0" + - "aGVudGljYXRpbmciCi0gInRlYXIgZG93biBOVkNGIiAvICJyZW1vdmUgdGhlIGNvbXB1dGUgcGxh" + - "bmUiIC8gInVuaW5zdGFsbCBOVkNGIiAvICJkZXJlZ2lzdGVyIHRoaXMgY2x1c3RlciIKLSAicHJl" + - "dmlldyB3aGF0IGBkb3duYCB3b3VsZCBkbyIgLyAiZHJ5LXJ1biB1bmluc3RhbGwiCi0gQW55IHRh" + - "c2sgb3BlcmF0aW9uOiBjcmVhdGUgLyBydW4gLyBzdWJtaXQgLyBtb25pdG9yIC8gY2FuY2VsIC8g" + - "ZGVsZXRlIC8gbGlzdCB0YXNrcywgaGVsbSB0YXNrLCB1cGRhdGUgdGFzayBzZWNyZXRzLCByZXRy" + - "aWV2ZSB0YXNrIHJlc3VsdHMsIGJ1bGsgZmV0Y2ggdGFzayBkZXRhaWxzLgotIEFueSByZWZlcmVu" + - "Y2UgdG8gYE5WQ1RgLCBgTlZDVCB0YXNrYCwgb3IgYE5WQ1QgYmF0Y2ggam9iYCAobGlzdCAvIHJ1" + - "biAvIGNhbmNlbCAvIGRlbGV0ZSAvIHJlc3VsdHMpLgotIEFueSByZWZlcmVuY2UgdG8gYE5WQ0ZC" + - "YWNrZW5kYCwgYE5WQ0FgLCBJQ01TLCBoZWxtIHJlbGVhc2VzIGxpa2UgYGhlbG0tbnZjZi0qYCwg" + - "b3IgYGljbXMuPGRvbWFpbj5gIC8gYGFwaS48ZG9tYWluPmAgVVJMcy4KCiMjIFF1aWNrIHN0YXJ0" + - "CgpGb3IgcmVtb3RlIG9uZS1jbGljayBpbnN0YWxscywgcHJlcGFyZSBHYXRld2F5IEFQSSBpbmdy" + - "ZXNzIGFuZCBDTEkgZW5kcG9pbnQKY29uZmlndXJhdGlvbiBiZWZvcmUgcnVubmluZyBgc2VsZi1o" + - "b3N0ZWQgdXBgLiBUaGUgY29tbWFuZCBhcHBsaWVzIHRoZSBjb250cm9sCnBsYW5lIGFuZCB0aGVu" + - "IGltbWVkaWF0ZWx5IGNhbGxzIEFQSSwgQVBJIEtleXMsIGludm9jYXRpb24sIGFuZCBnUlBDIGVu" + - "ZHBvaW50cy4KSWYgdGhlIEdhdGV3YXkgaXMgbm90IHByb2dyYW1tZWQgb3IgdGhlIENMSSBob3N0" + - "IGhlYWRlcnMgZG8gbm90IG1hdGNoIHRoZQpIVFRQUm91dGVzIHJlbmRlcmVkIGJ5IHRoZSBzdGFj" + - "ayBlbnZpcm9ubWVudCwgcG9zdC1pbnN0YWxsIGhlYWx0aCBhbmQgY2x1c3RlcgpyZWdpc3RyYXRp" + - "b24gd2lsbCBmYWlsLgoKYGBgc2gKIyBTaW5nbGUtY2x1c3RlciAoY29udHJvbCArIGNvbXB1dGUg" + - "b24gdGhlIGN1cnJlbnQga3ViZWNvbmZpZyBjb250ZXh0KToKbnZjZi1jbGkgc2VsZi1ob3N0ZWQg" + - "dXAgLS1jbHVzdGVyLW5hbWU9bmNwLWxvY2FsCgojIFNwbGl0LWNsdXN0ZXIgKGNvbnRyb2wgcGxh" + - "bmUgb24gY29udGV4dCBBLCBjb21wdXRlIHBsYW5lIG9uIGNvbnRleHQgQik6CktVQkVDT05GSUc9" + - "Y3AueWFtbDpncHUxLnlhbWwgbnZjZi1jbGkgc2VsZi1ob3N0ZWQgdXAgXAogIC0tY2x1c3Rlci1u" + - "YW1lPW5jcC1sb2NhbCBcCiAgLS1jb250cm9sLXBsYW5lLWNvbnRleHQ9YWRtaW5AY3AgXAogIC0t" + - "Y29tcHV0ZS1wbGFuZS1jb250ZXh0PWFkbWluQGdwdTEgXAogIC0taWNtcy11cmw9aHR0cHM6Ly9p" + - "Y21zLm52Y2YuZXhhbXBsZS5jb20KCiMgQWRkIGEgbmV3IGNvbXB1dGUgcGxhbmUgdG8gYW4gZXhp" + - "c3RpbmcgY29udHJvbCBwbGFuZSAobm8ga3ViZWN0bCBhY2Nlc3MgdG8gQ1AgbmVlZGVkOwojIHJl" + - "YWNoZXMgdGhlIGNvbnRyb2wgcGxhbmUgdmlhIHRoZSBwdWJsaWMgSUNNUyBIVFRQUm91dGUpOgpu" + - "dmNmLWNsaSBzZWxmLWhvc3RlZCBhZGQtY29tcHV0ZS1wbGFuZSBcCiAgLS1jbHVzdGVyLW5hbWU9" + - "bmNwLWxvY2FsLTIgXAogIC0tY29tcHV0ZS1wbGFuZS1jb250ZXh0PWFkbWluQGdwdTIgXAogIC0t" + - "aWNtcy11cmw9aHR0cHM6Ly9pY21zLm52Y2YuZXhhbXBsZS5jb20gXAogIC0tdG9rZW49JEFETUlO" + - "X0pXVAoKIyBUZWFyIGRvd24gKGFsd2F5cyBwbGFuLW9ubHkgZmlyc3QpOgpudmNmLWNsaSBzZWxm" + - "LWhvc3RlZCBkb3duIC0tcGxhbi1vbmx5IC0tY2x1c3Rlci1uYW1lPW5jcC1sb2NhbCAtLWpzb24g" + - "fCBqcQpudmNmLWNsaSBzZWxmLWhvc3RlZCBkb3duIC0tY2x1c3Rlci1uYW1lPW5jcC1sb2NhbAoK" + - "IyBQZXItcGxhbmUgdW5pbnN0YWxsIChHaXRPcHM7IG1pcnJvcnMgYGluc3RhbGxgKToKbnZjZi1j" + - "bGkgc2VsZi1ob3N0ZWQgdW5pbnN0YWxsIC0tbm8tYXBwbHkgLS1jb21wdXRlLXBsYW5lIC0tY2x1" + - "c3Rlci1uYW1lPW5jcC1sb2NhbCB8IGt1YmVjdGwgZGVsZXRlIC1mIC0KYGBgCgo+ICoqYHVwYCB2" + - "cyBgYWRkLWNvbXB1dGUtcGxhbmVgLioqIGB1cGAgYWx3YXlzIGluc3RhbGxzIGJvdGggcGxhbmVz" + - "IOKAlCB1c2UgaXQgZm9yIHRoZSAqZmlyc3QqIGluc3RhbGwuIGBhZGQtY29tcHV0ZS1wbGFuZWAg" + - "aXMgdGhlIHJpZ2h0IHN1YmNvbW1hbmQgYW55IHRpbWUgdGhlIGNvbnRyb2wgcGxhbmUgaXMgYWxy" + - "ZWFkeSBydW5uaW5nIGFuZCB5b3Ugd2FudCB0byBhdHRhY2ggYW4gTnRoIGNvbXB1dGUgY2x1c3Rl" + - "ci4KCj4gKipgZG93bmAgYWx3YXlzIHdpdGggYC0tcGxhbi1vbmx5YCBmaXJzdC4qKiBTaG93IHRo" + - "ZSB1c2VyIHRoZSBgd2lsbFVuaW5zdGFsbC5jb21tYW5kc1tdYCBhcnJheSBiZWZvcmUgcnVubmlu" + - "ZyBmb3IgcmVhbC4KCiMjIEF1dGhlbnRpY2F0aW9uCgpgbnZjZi1jbGlgIHVzZXMgdHdvIGNyZWRl" + - "bnRpYWxzLiBFYWNoIGlzIHJlc29sdmVkIGluZGVwZW5kZW50bHkgaW4gdGhpcyBvcmRlcjoKCnwg" + - "UHJlY2VkZW5jZSB8IFNvdXJjZSB8CnwtLS18LS0tfAp8IDEgKGhpZ2hlc3QpIHwgRW52aXJvbm1l" + - "bnQgdmFyaWFibGUgKGBOVkNGX1RPS0VOYCwgYE5WQ0ZfQVBJX0tFWWApIHwKfCAyIHwgQ29uZmln" + - "IGZpbGUgKGB+Ly5udmNmLWNsaS55YW1sYCwga2V5cyBgdG9rZW5gIGFuZCBgYXBpX2tleWApIHwK" + - "fCAzIHwgU3RhdGUgZmlsZSAoYH4vLm52Y2YtY2xpLnN0YXRlYCksIHNraXBwZWQgd2hlbiB0aGUg" + - "c3RvcmVkIHRva2VuIGlzIGV4cGlyZWQgfAoKfCBFbnYgdmFyIHwgVG9rZW4gdHlwZSB8IFVzZWQg" + - "YnkgfAp8LS0tfC0tLXwtLS18CnwgYE5WQ0ZfVE9LRU5gIHwgQWRtaW4gSldUIHwgYGZ1bmN0aW9u" + - "IGNyZWF0ZWAgLyBgZGVwbG95YCAvIGB1cGRhdGVgIC8gYGRlbGV0ZWAsIGNsdXN0ZXIgbWFuYWdl" + - "bWVudCAoYGNsdXN0ZXIgcmVnaXN0ZXJgL2BsaXN0LXJlZ2lzdGVyZWRgL2Byb3RhdGVgL2BkZWxl" + - "dGVgLCBgc2VsZi1ob3N0ZWRgIG9wcykuIFJlcXVpcmVkIGZvciBhZG1pbiBjb21tYW5kczsgcHJl" + - "ZmVycmVkIGZvciB0aGUgcmVzdC4gfAp8IGBOVkNGX0FQSV9LRVlgIHwgYG52YXBpLS4uLmAgQVBJ" + - "IGtleSB8IGBmdW5jdGlvbiBpbnZva2VgIC8gYGxpc3RgIC8gYGdldGAsIHF1ZXVlIGRldGFpbHMu" + - "IEZhbGxzIGJhY2sgdG8gYE5WQ0ZfVE9LRU5gIHdoZW4gdW5zZXQuIHwKClRva2VuIGdlbmVyYXRp" + - "b24gZmxvdzoKCjEuIFBvaW50IHRoZSBDTEkgYXQgeW91ciBzZWxmLWhvc3RlZCBlbmRwb2ludHMu" + - "IERlZmF1bHRzIHRhcmdldCBwcm9kdWN0aW9uIE5WQ0Y7IG92ZXJyaWRlIHdpdGggZW52IHZhcnMg" + - "b3IgYSB5YW1sIGZpbGUgKGAtLWNvbmZpZyA8cGF0aD5gLCBgLi8ubnZjZi1jbGkueWFtbGAsIG9y" + - "IGB+Ly5udmNmLWNsaS55YW1sYDsgYSBmdWxsIHRlbXBsYXRlIHNoaXBzIGF0IGBzcmMvY2xpcy9u" + - "dmNmLWNsaS8ubnZjZi1jbGkueWFtbC50ZW1wbGF0ZWApLgoKICAgYE5WQ0ZfQkFTRV9IVFRQX1VS" + - "TGAgLyBgQVBJX0tFWVNfU0VSVklDRV9VUkxgIC8gYE5WQ0ZfQkFTRV9HUlBDX1VSTGAgdGVsbCB0" + - "aGUgQ0xJICp3aGVyZSB0byBzZW5kIHJlcXVlc3RzKi4gYEFQSV9IT1NUYCAvIGBBUElfS0VZU19I" + - "T1NUYCAvIGBJTlZPS0VfSE9TVGAgc2V0IHRoZSBgSG9zdDpgIGhlYWRlciBvbiB0aG9zZSByZXF1" + - "ZXN0cyDigJQgbmVlZGVkIHdoZW4gZXZlcnkgTlZDRiBzZXJ2aWNlIHNpdHMgYmVoaW5kIGEgc2lu" + - "Z2xlIGdhdGV3YXkgdGhhdCByb3V0ZXMgYnkgaG9zdG5hbWUgKEVudm95IEdhdGV3YXkgSFRUUFJv" + - "dXRlIG9uIGBhcGkuPEVMQj5gLCBgYXBpLWtleXMuPEVMQj5gLCBgaW52b2NhdGlvbi48RUxCPmAs" + - "IGBsbG0uPEVMQj5gKS4gU2tpcCB0aGUgdGhyZWUgSG9zdCB2YXJzIHdoZW4gdGhlIGJhc2UgVVJM" + - "IGFscmVhZHkgcmVzb2x2ZXMgdG8gdGhlIHJpZ2h0IHNlcnZpY2UuCgogICBFbnYtdmFyIGZvcm06" + - "CiAgIGBgYHNoCiAgIGV4cG9ydCBOVkNGX0JBU0VfSFRUUF9VUkw9aHR0cHM6Ly9ndy5leGFtcGxl" + - "LmNvbQogICBleHBvcnQgQVBJX0tFWVNfU0VSVklDRV9VUkw9aHR0cHM6Ly9ndy5leGFtcGxlLmNv" + - "bQogICBleHBvcnQgTlZDRl9CQVNFX0dSUENfVVJMPWd3LmV4YW1wbGUuY29tOjQ0MwoKICAgIyBP" + - "bmx5IHdoZW4gdGhlIGdhdGV3YXkgcm91dGVzIGJ5IEhvc3QgaGVhZGVyOgogICBleHBvcnQgQVBJ" + - "X0tFWVNfSE9TVD1hcGkta2V5cy5ndy5leGFtcGxlLmNvbQogICBleHBvcnQgQVBJX0hPU1Q9YXBp" + - "Lmd3LmV4YW1wbGUuY29tCiAgIGV4cG9ydCBJTlZPS0VfSE9TVD1pbnZvY2F0aW9uLmd3LmV4YW1w" + - "bGUuY29tCiAgIGBgYAoKICAgWWFtbCBmb3JtIChgfi8ubnZjZi1jbGkueWFtbGApOgogICBgYGB5" + - "YW1sCiAgIGJhc2VfaHR0cF91cmw6IGh0dHBzOi8vZ3cuZXhhbXBsZS5jb20KICAgYXBpX2tleXNf" + - "c2VydmljZV91cmw6IGh0dHBzOi8vZ3cuZXhhbXBsZS5jb20KICAgYmFzZV9ncnBjX3VybDogZ3cu" + - "ZXhhbXBsZS5jb206NDQzCgogICAjIE9ubHkgd2hlbiB0aGUgZ2F0ZXdheSByb3V0ZXMgYnkgSG9z" + - "dCBoZWFkZXI6CiAgIGFwaV9rZXlzX2hvc3Q6IGFwaS1rZXlzLmd3LmV4YW1wbGUuY29tCiAgIGFw" + - "aV9ob3N0OiBhcGkuZ3cuZXhhbXBsZS5jb20KICAgaW52b2tlX2hvc3Q6IGludm9jYXRpb24uZ3cu" + - "ZXhhbXBsZS5jb20KICAgYGBgCgoyLiBgbnZjZi1jbGkgaW5pdGAgY2FsbHMgdGhlIEFQSSBLZXlz" + - "IHNlcnZpY2UgdmlhIHRob3NlIGVuZHBvaW50cyBhbmQgd3JpdGVzIHRoZSBhZG1pbiB0b2tlbiB0" + - "byBgfi8ubnZjZi1jbGkuc3RhdGVgLiBVc2UgYG52Y2YtY2xpIHJlZnJlc2hgIHRvIHJvdGF0ZSBh" + - "IHN0b3JlZCB0b2tlbi4KMy4gQWZ0ZXIgYGluaXRgLCBgbnZjZi1jbGkgYXBpLWtleSBnZW5lcmF0" + - "ZWAgbWludHMgZnVuY3Rpb24gKyB0YXNrIEFQSSBrZXlzIChhbHNvIHN0b3JlZCBpbiBzdGF0ZSku" + - "CgpBZnRlciBgaW5pdGAsIHRoZSBjcmVkZW50aWFscyBsaXZlIGluIGB+Ly5udmNmLWNsaS5zdGF0" + - "ZWAsIHNvIGxhdGVyIGNvbW1hbmRzIHdvcmsgd2l0aG91dCBleHBvcnRpbmcgYE5WQ0ZfVE9LRU5g" + - "IG9yIGBOVkNGX0FQSV9LRVlgLiBFeHBvcnQgdGhlIGVudiB2YXIgb25seSB0byBvdmVycmlkZSB0" + - "aGUgc3RvcmVkIHZhbHVlIChmb3IgZXhhbXBsZSBhIHNob3J0ZXItbGl2ZWQgdG9rZW4gZnJvbSBD" + - "SSkuCgojIyBDb3JlIHN1YmNvbW1hbmRzCgp8IFN1YmNvbW1hbmQgfCBXaGF0IGl0IGRvZXMgfCBX" + - "aGVuIHRvIHVzZSB8CnwtLS18LS0tfC0tLXwKfCBgbnZjZi1jbGkgc2VsZi1ob3N0ZWQgY2hlY2sg" + - "LS1wcmUgWy0tbG9jYWwtb25seSBcfCAtLWNvbnRyb2wtcGxhbmUtY29udGV4dD1YIFx8IC0tY29t" + - "cHV0ZS1wbGFuZS1jb250ZXh0PVldYCB8IFByZS1mbGlnaHQ6IGxvY2FsLWhvc3QgdG9vbHMgKyBj" + - "bHVzdGVyLXNpZGUgcHJlcmVxdWlzaXRlcyB8IEFsd2F5cyBydW4gZmlyc3Qgb24gYSBuZXcgZW52" + - "aXJvbm1lbnQgfAp8IGBudmNmLWNsaSBzZWxmLWhvc3RlZCBpbnN0YWxsIC0tY29udHJvbC1wbGFu" + - "ZSBcfCBrdWJlY3RsIGFwcGx5IC1mIC1gIHwgUmVuZGVyICsgYXBwbHkgdGhlIGNvbnRyb2wgcGxh" + - "bmUgfCBXaGVuIHlvdSB3YW50IG1hbnVhbCBjb250cm9sIG92ZXIgYXBwbHkgKEdpdE9wcy1mcmll" + - "bmRseSkgfAp8IGBudmNmLWNsaSBzZWxmLWhvc3RlZCBpbnN0YWxsIC0tY29tcHV0ZS1wbGFuZSAt" + - "LWNsdXN0ZXItbmFtZT1YIFx8IGt1YmVjdGwgYXBwbHkgLWYgLWAgfCBSZWdpc3RlciBjbHVzdGVy" + - "ICsgcmVuZGVyIGNvbXB1dGUgcGxhbmUgfCBTYW1lIOKAlCBtYW51YWwgYXBwbHkgcGF0aCB8Cnwg" + - "YG52Y2YtY2xpIHNlbGYtaG9zdGVkIHVwIC0tY2x1c3Rlci1uYW1lPVhgIHwgT25lLXNob3QgZmly" + - "c3QgaW5zdGFsbDogcHJlLWZsaWdodCDihpIgY29udHJvbCBwbGFuZSDihpIgcmVnaXN0ZXIg4oaS" + - "IGNvbXB1dGUgcGxhbmUgfCBTdGFuZGFyZCBpbnN0YWxsIHBhdGggKGJvdGggcGxhbmVzIGZyb20g" + - "c2NyYXRjaCkgfAp8IGBudmNmLWNsaSBzZWxmLWhvc3RlZCB1cCAtLXBsYW4tb25seSAtLWNsdXN0" + - "ZXItbmFtZT1YYCB8IERyeS1ydW46IGVtaXQgcGhhc2UtYnktcGhhc2UgcGxhbiArIEVUQSB3aXRo" + - "b3V0IGNoYW5naW5nIHN0YXRlIHwgQWdlbnQgLyBDSSBwcmV2aWV3IGJlZm9yZSBjb21taXQgfAp8" + - "IGBudmNmLWNsaSBzZWxmLWhvc3RlZCBhZGQtY29tcHV0ZS1wbGFuZSAtLWNsdXN0ZXItbmFtZT1Y" + - "IC0tY29tcHV0ZS1wbGFuZS1jb250ZXh0PVkgLS1pY21zLXVybD3igKYgLS10b2tlbj0kSldUYCB8" + - "IEFkZCBhIG5ldyBjb21wdXRlIHBsYW5lIHRvIGFuICpleGlzdGluZyogY29udHJvbCBwbGFuZSAo" + - "bm8gQ1AgaW5zdGFsbCkgfCBBZGRpbmcgdGhlIDJuZCwgM3JkLCDigKYgR1BVIGNsdXN0ZXIgYWZ0" + - "ZXIgdGhlIGluaXRpYWwgYHVwYCB8CnwgYG52Y2YtY2xpIHNlbGYtaG9zdGVkIHVuaW5zdGFsbCAt" + - "LWNvbnRyb2wtcGxhbmVgIHwgUGVyLXBsYW5lIHByaW1pdGl2ZTogYGhlbG1maWxlIGRlc3Ryb3lg" + - "IG9uIHRoZSBjb250cm9sIHBsYW5lIChyZWZ1c2VzIGlmIGNvbXB1dGUgcGxhbmVzIHN0aWxsIHJl" + - "Z2lzdGVyZWQpIHwgRmluYWwgdGVhcmRvd24gYWZ0ZXIgYWxsIGNvbXB1dGUgcGxhbmVzIGFyZSBn" + - "b25lLCBvciBzY3JpcHRlZCBwaXBlbGluZXMgfAp8IGBudmNmLWNsaSBzZWxmLWhvc3RlZCB1bmlu" + - "c3RhbGwgLS1jb21wdXRlLXBsYW5lIC0tY2x1c3Rlci1uYW1lPVhgIHwgUGVyLXBsYW5lIHByaW1p" + - "dGl2ZTogYGhlbG1maWxlIGRlc3Ryb3lgIG9uIGEgY29tcHV0ZSBwbGFuZSAobm8gSUNNUyB1bnJl" + - "Z2lzdGVyLCBubyBkcmFpbikgfCBKdXN0IHJlbW92ZSB0aGUgaGVsbSByZWxlYXNlcyB3aXRob3V0" + - "IHRoZSBJQ01TLXNpZGUgY2xlYW51cCB8CnwgYG52Y2YtY2xpIHNlbGYtaG9zdGVkIHVuaW5zdGFs" + - "bCAtLW5vLWFwcGx5IDxwbGFuZT5gIHwgUmVuZGVyIGRlbGV0ZSBZQU1MIHZpYSBgaGVsbSBnZXQg" + - "bWFuaWZlc3RgIHwgR2l0T3BzOyBgXHwga3ViZWN0bCBkZWxldGUgLWYgLWAgb3IgY29tbWl0ICsg" + - "QXJnbyBhcHBsaWVzIHwKfCBgbnZjZi1jbGkgc2VsZi1ob3N0ZWQgZG93biAtLWNsdXN0ZXItbmFt" + - "ZT1YYCB8IE9yY2hlc3RyYXRvcjogZHJhaW4g4oaSIHVuaW5zdGFsbCAtLWNvbXB1dGUtcGxhbmUg" + - "4oaSIGNsdXN0ZXIgZGVsZXRlIGluIElDTVMgfCBTdGFuZGFyZCAicmVtb3ZlIG9uZSBHUFUgY2x1" + - "c3RlciIgcGF0aCB8CnwgYG52Y2YtY2xpIHNlbGYtaG9zdGVkIGRvd24gLS1hbGwgLS1jb25maXJt" + - "YCB8IE9yY2hlc3RyYXRvcjogdGVhciBkb3duIGV2ZXJ5IHJlZ2lzdGVyZWQgY29tcHV0ZSBwbGFu" + - "ZSArIGNvbnRyb2wgcGxhbmUgfCBGdWxsIHVuaW5zdGFsbCB8CnwgYG52Y2YtY2xpIHNlbGYtaG9z" + - "dGVkIGRvd24gLS1wbGFuLW9ubHkgLS1jbHVzdGVyLW5hbWU9WGAgfCBEcnktcnVuIHByZXZpZXcg" + - "KHBoYXNlcyArIGhlbG0gcmVsZWFzZXMgKyBJQ01TIHJvd3MgKyBFVEFzOyBubyBoZWxtL2hlbG1m" + - "aWxlIGNvbnRhY3QpIHwgKipBTFdBWVMgcnVuIGJlZm9yZSBhbnkgYWN0dWFsIGBkb3duYCoqIHwK" + - "fCBgbnZjZi1jbGkgc2VsZi1ob3N0ZWQgc3RhdHVzIFstLWNsdXN0ZXItbmFtZT1YXSBbLS13YXRj" + - "aF0gWy0tanNvbl1gIHwgU25hcHNob3QgZGFzaGJvYXJkIG9mIGNsdXN0ZXIgaWRlbnRpdHkgKyBj" + - "b21wb25lbnQgaGVhbHRoICsgcmVjZW50IGV2ZW50cyB8IFJvdXRpbmUgaGVhbHRoIGNoZWNrczsg" + - "YC0td2F0Y2hgIGZvciBsaXZlIHwKfCBgbnZjZi1jbGkgaW5pdGAgfCBNaW50IGFkbWluIHRva2Vu" + - "IGZyb20gQVBJIEtleXMgc2VydmljZSB2aWEgdGhlIHB1YmxpYyBhcGkgZ2F0ZXdheSB8IEJlZm9y" + - "ZSBhbnkgY2x1c3Rlci1tYW5hZ2VtZW50IG9wZXJhdGlvbjsgaWRlbXBvdGVudCB8CnwgYG52Y2Yt" + - "Y2xpIGNsdXN0ZXIgcmVnaXN0ZXIgLS1uYW1lPVggLS1uY2EtaWQ9WSAtLXJlZ2lvbj1aIFstLWln" + - "bm9yZS1leGlzdGluZ11gIHwgUmVnaXN0ZXIgYSBjbHVzdGVyIEpXS1MrT0lEQyBpc3N1ZXIgd2l0" + - "aCBJQ01TIHwgU3RhbmRhbG9uZSByZWdpc3RlciAod2l0aG91dCBjb21wdXRlLXBsYW5lIGluc3Rh" + - "bGwpIHwKfCBgbnZjZi1jbGkgY2x1c3RlciBsaXN0LXJlZ2lzdGVyZWQgLS1uY2EtaWQ9WSBbLS1p" + - "Y21zLXVybD1VUkxdYCB8IExpc3Qgc2VsZi1ob3N0ZWQgY2x1c3RlciByZWdpc3RyYXRpb25zIGZy" + - "b20gSUNNUyB8IENoZWNrIHJlZ2lzdGVyZWQgY29tcHV0ZS1wbGFuZSBuYW1lcyBhbmQgSURzIHdp" + - "dGggdGhlIGFkbWluIHRva2VuIHwKfCBgbnZjZi1jbGkgY2x1c3RlciByb3RhdGUgLS1jbHVzdGVy" + - "LWlkPUlEYCB8IFJvdGF0ZSBjbHVzdGVyIEpXS1MgaW4gSUNNUyB8IFdoZW4gTlZDQSdzIEs4cyBz" + - "aWduaW5nIGtleSBjaGFuZ2VkIGFuZCBQU0FUIHZlcmlmaWNhdGlvbiBzdGFydGVkIDQwMS1pbmcg" + - "fAp8IGBudmNmLWNsaSBjbHVzdGVyIGRlbGV0ZSAtLWNsdXN0ZXItaWQ9SURgIHwgUmVtb3ZlIGNs" + - "dXN0ZXIgcmVnaXN0cmF0aW9uIGZyb20gSUNNUyB8ICoqQ29uZmlybSB3aXRoIHVzZXIuKiogRGVz" + - "dHJveXMgSUNNUyBzdGF0ZSBmb3IgdGhlIGNsdXN0ZXIuIHwKfCBgbnZjZi1jbGkgYXBpLWtleSBn" + - "ZW5lcmF0ZSAtLWRlc2NyaXB0aW9uPSLigKYiIC0tZXhwaXJlcy1pbj0xaGAgfCBNaW50IGJvdGgg" + - "YSBmdW5jdGlvbiBBUEkga2V5IGFuZCBhIHRhc2sgQVBJIGtleSAoZGVmYXVsdCkgfCBCZWZvcmUg" + - "aW52b2tpbmcgZnVuY3Rpb25zIG9yIGNyZWF0aW5nIHRhc2tzOyBydW4gYWZ0ZXIgZXZlcnkgYGlu" + - "aXRgIHwKfCBgbnZjZi1jbGkgYXBpLWtleSBnZW5lcmF0ZSAtLWZvciBmdW5jdGlvbiAtLWRlc2Ny" + - "aXB0aW9uPSLigKYiYCB8IE1pbnQgYSBmdW5jdGlvbiBBUEkga2V5IG9ubHkgKGBpbnZva2VfZnVu" + - "Y3Rpb25gIHNjb3BlKSB8IFdoZW4gb25seSBmdW5jdGlvbiBpbnZvY2F0aW9uIGlzIG5lZWRlZCB8" + - "CnwgYG52Y2YtY2xpIGFwaS1rZXkgZ2VuZXJhdGUgLS1mb3IgdGFzayAtLWRlc2NyaXB0aW9uPSLi" + - "gKYiYCB8IE1pbnQgYSB0YXNrIEFQSSBrZXkgb25seSB8IFdoZW4gb25seSB0YXNrIG9wZXJhdGlv" + - "bnMgYXJlIG5lZWRlZCB8CnwgYG52Y2YtY2xpIGZ1bmN0aW9uIGNyZWF0ZSAtLWlucHV0LWZpbGU9" + - "PGpzb24+YCB8IENyZWF0ZSBmdW5jdGlvbiBtZXRhZGF0YSBpbiBJQ01TIHwgRmlyc3Qgc3RlcCBv" + - "ZiBhbnkgZnVuY3Rpb24gZGVwbG95OyB1c2UgYGZ1bmN0aW9uVHlwZTogIkxMTSJgIGFuZCBgbW9k" + - "ZWxzW10ubGxtQ29uZmlnYCBmb3IgTExNIGZ1bmN0aW9ucyB8CnwgYG52Y2YtY2xpIGZ1bmN0aW9u" + - "IGRlcGxveSBjcmVhdGUgLS1pbnB1dC1maWxlPTxqc29uPmAgfCBTY2hlZHVsZSBhIGRlcGxveW1l" + - "bnQgb2YgYSBjcmVhdGVkIGZ1bmN0aW9uIHwgV2FpdHMgZm9yIEFDVElWRSBiZWZvcmUgcmV0dXJu" + - "aW5nICh0aW1lb3V0IDkwMHMpIHwKfCBgbnZjZi1jbGkgZnVuY3Rpb24gaW52b2tlIC0taW5wdXQt" + - "ZmlsZT08anNvbj5gIHwgSW52b2tlIGEgZGVwbG95ZWQgZnVuY3Rpb24gfCBSZXF1aXJlcyBBUEkg" + - "a2V5IChub3QgYWRtaW4gdG9rZW4pIHwKfCBgbnZjZi1jbGkgZnVuY3Rpb24gZGVsZXRlIC0tZnVu" + - "Y3Rpb24taWQ9SUQgLS12ZXJzaW9uLWlkPVZJRGAgfCBSZW1vdmUgYSBmdW5jdGlvbiBhbmQgaXRz" + - "IGRlcGxveW1lbnQgfCAqKkNvbmZpcm0gd2l0aCB1c2VyLioqIHwKfCBgbnZjZi1jbGkgdGFzayBj" + - "cmVhdGUgLS1uYW1lPVggLS1ncHU9SDEwMCAtLWluc3RhbmNlLXR5cGU9WSAtLWltYWdlPVpgIHwg" + - "U3VibWl0IGEgY29udGFpbmVyIHRhc2s7IHNhdmVzIHRhc2sgSUQgdG8gQ0xJIHN0YXRlIHwgUmVx" + - "dWlyZXMgdGFzayBBUEkga2V5OyBzZXQgYE5WQ0ZfQkFTRV9OVkNUX1VSTGAgdG8gdGhlIE5WQ1Qg" + - "Z2F0ZXdheSBlbmRwb2ludCB8CnwgYG52Y2YtY2xpIHRhc2sgY3JlYXRlIC0tbmFtZT1YIC0tZ3B1" + - "PUgxMDAgLS1pbnN0YW5jZS10eXBlPVkgLS1oZWxtLWNoYXJ0PVpgIHwgU3VibWl0IGEgSGVsbSB0" + - "YXNrIHwgU2FtZSBhcyBjb250YWluZXIgdGFzazsgYC0taGVsbS1jaGFydGAgcmVwbGFjZXMgYC0t" + - "aW1hZ2VgIHwKfCBgbnZjZi1jbGkgdGFzayBjcmVhdGUgLS1pbnB1dC1maWxlPTxqc29uPmAgfCBT" + - "dWJtaXQgYSB0YXNrIGZyb20gYSBKU09OIGNvbmZpZyBmaWxlIHwgUmVjb21tZW5kZWQgZm9yIHJl" + - "cGVhdGFibGUgY29uZmlndXJhdGlvbnMgfAp8IGBudmNmLWNsaSB0YXNrIGxpc3QgWy0tc3RhdHVz" + - "PVFVRVVFRFx8UlVOTklOR1x8Q09NUExFVEVEXWAgfCBMaXN0IHRhc2tzLCBvcHRpb25hbGx5IGZp" + - "bHRlcmVkIGJ5IHN0YXR1cyB8IHwKfCBgbnZjZi1jbGkgdGFzayBnZXQgW3Rhc2tJZF1gIHwgR2V0" + - "IGRldGFpbHMgZm9yIGEgdGFzazsgdXNlcyBzYXZlZCB0YXNrIElEIGlmIG9taXR0ZWQgfCB8Cnwg" + - "YG52Y2YtY2xpIHRhc2sgZXZlbnRzIFt0YXNrSWRdYCB8IFN0cmVhbSBsaWZlY3ljbGUgZXZlbnRz" + - "IGZvciBhIHRhc2sgfCB8CnwgYG52Y2YtY2xpIHRhc2sgcmVzdWx0cyBbdGFza0lkXWAgfCBMaXN0" + - "IHJlc3VsdCBhcnRpZmFjdHMgZm9yIGEgY29tcGxldGVkIHRhc2sgfCBSZXN1bHQgdXBsb2FkIG5v" + - "dCB5ZXQgc3VwcG9ydGVkOyByZXR1cm5zIGVtcHR5IGxpc3QgZm9yIGBOT05FYCBzdHJhdGVneSB8" + - "CnwgYG52Y2YtY2xpIHRhc2sgY2FuY2VsIFt0YXNrSWRdYCB8IENhbmNlbCBhIHF1ZXVlZCBvciBy" + - "dW5uaW5nIHRhc2sgfCB8CnwgYG52Y2YtY2xpIHRhc2sgZGVsZXRlIFt0YXNrSWRdYCB8IERlbGV0" + - "ZSBhIHRhc2sgcmVjb3JkIHwgKipDb25maXJtIHdpdGggdXNlci4qKiB8CnwgYG52Y2YtY2xpIHRh" + + "ZW5jZS9leGl0LWNvZGVzLm1kIiwKICAgICAgInNoYTI1NiI6ICI2M2E4MzUzMTIyYmE4MGUzNmE1" + + "NzY2ODI0Y2FlZjdhZDAzZjBlM2JhZjQ5Njc0ODVkYmZmZDE2ZTAwMDU3YzhjIiwKICAgICAgInNp" + + "emUiOiAyMjc0CiAgICB9LAogICAgewogICAgICAicGF0aCI6ICJudmNmLXNlbGYtbWFuYWdlZC1j" + + "bGkvcmVmZXJlbmNlL2ZsYWdzLm1kIiwKICAgICAgInNoYTI1NiI6ICJmNWViYjE3OGY3YmFlODky" + + "YjAwOGFmYzZhMjllZjIzOWU3ZTk0M2E3NjEzM2VmYzc4ZTIyN2M4ODIwMTUwY2U2IiwKICAgICAg" + + "InNpemUiOiAxMzQ5MwogICAgfSwKICAgIHsKICAgICAgInBhdGgiOiAibnZjZi1zZWxmLW1hbmFn" + + "ZWQtY2xpL3JlZmVyZW5jZS90cm91Ymxlc2hvb3RpbmcubWQiLAogICAgICAic2hhMjU2IjogImE2" + + "MzA4NGUyMjQ5ZjFkMTU0YzU5ODFlNGEwNzJiN2M3ODRiODVlYTE0NjA4NjUxNWVhYjRhYjY3MGE5" + + "NDBlOTQiLAogICAgICAic2l6ZSI6IDQ0NjgKICAgIH0sCiAgICB7CiAgICAgICJwYXRoIjogIm52" + + "Y2Ytc2VsZi1tYW5hZ2VkLWluc3RhbGxhdGlvbi9TS0lMTC5tZCIsCiAgICAgICJzaGEyNTYiOiAi" + + "Y2IyMDg5YWFiODAyOTU1OTM1ZmExOGJjMTMyNjFhZGM0MGI2NTc4M2NiNGEyY2FiMmYzYWUzMzlk" + + "Zjk4Mjg1ZCIsCiAgICAgICJzaXplIjogMjUxNjEKICAgIH0sCiAgICB7CiAgICAgICJwYXRoIjog" + + "Im52Y2Ytc2VsZi1tYW5hZ2VkLWluc3RhbGxhdGlvbi9ldmFscy9ldmFscy5qc29uIiwKICAgICAg" + + "InNoYTI1NiI6ICI2MTAwNzdiNjliNDRlNTk1Mzc3Njc3YzQzOTQ3MjAxNTg1MzFiMTcxYWE5NmQ2" + + "ZGNjN2E2NWUxZGZkMzA5Yjg5IiwKICAgICAgInNpemUiOiAyMjA5MAogICAgfSwKICAgIHsKICAg" + + "ICAgInBhdGgiOiAibnZjZi1zZWxmLW1hbmFnZWQtaW5zdGFsbGF0aW9uL2V4YW1wbGVzLm1kIiwK" + + "ICAgICAgInNoYTI1NiI6ICI5YTM5ZTVjY2FiZDQwODE5YzcxZjJiMGYyZDc1MjMxNmI3MjMxNTY1" + + "NGQ4MGQzZDAxNzY5OGNlMWZiM2U1YjExIiwKICAgICAgInNpemUiOiAxMDM2NwogICAgfSwKICAg" + + "IHsKICAgICAgInBhdGgiOiAibnZjZi1zZWxmLW1hbmFnZWQtaW5zdGFsbGF0aW9uL3JlZmVyZW5j" + + "ZXMvY29tcHV0ZS1wbGFuZS1pbnN0YWxsYXRpb24ubWQiLAogICAgICAic2hhMjU2IjogIjVkZWU1" + + "OGFlMmM2N2E2Mjk0NmUzYzU5MzAyYWZhY2M0MGJkZTcxZjhkYzJmMjVjZDc3OTljYzQ1YjQ3NGZh" + + "MTkiLAogICAgICAic2l6ZSI6IDEyMzA2CiAgICB9LAogICAgewogICAgICAicGF0aCI6ICJudmNm" + + "LXNlbGYtbWFuYWdlZC1pbnN0YWxsYXRpb24vcmVmZXJlbmNlcy9kZWJ1Z2dpbmcubWQiLAogICAg" + + "ICAic2hhMjU2IjogImQwZWZkNjI0Njg3OGQ2MWFlZTZmYmUzNTZjMDI1YmE1ZDU4ZDA1M2UyZmRk" + + "ZTNkNzFiYzkyYzYzMTE5ZTM4YjEiLAogICAgICAic2l6ZSI6IDEyNDY5CiAgICB9LAogICAgewog" + + "ICAgICAicGF0aCI6ICJudmNmLXNlbGYtbWFuYWdlZC1pbnN0YWxsYXRpb24vcmVmZXJlbmNlcy9o" + + "ZWxtZmlsZS1zdHJ1Y3R1cmUubWQiLAogICAgICAic2hhMjU2IjogImZhOTRiYmZjYjA1MWJhYTJl" + + "MDdjYWFlNDI3YTY3YmI0NWY5ODBkZGJhZGRhMzJhNGEwYzgzMDMzODYzOWU0NjgiLAogICAgICAi" + + "c2l6ZSI6IDEwNzYyCiAgICB9LAogICAgewogICAgICAicGF0aCI6ICJudmNmLXNlbGYtbWFuYWdl" + + "ZC1pbnN0YWxsYXRpb24vcmVmZXJlbmNlcy9udmNmLXVpLWFkZG9uLm1kIiwKICAgICAgInNoYTI1" + + "NiI6ICI1MDUyNGU2ZTE5NzExODNiZGM5YWQwYWMzOGE4MDdkZjcyY2MwODUxYjc5ZjUxOGZlMTFh" + + "NDhlNDU5N2M4NTBlIiwKICAgICAgInNpemUiOiAxOTc3CiAgICB9LAogICAgewogICAgICAicGF0" + + "aCI6ICJudmNmLXNlbGYtbWFuYWdlZC1pbnN0YWxsYXRpb24vcmVmZXJlbmNlcy9wdWxsLXNlY3Jl" + + "dHMubWQiLAogICAgICAic2hhMjU2IjogImVjMWYzZmZjZDE3ZTVmOTU4M2ZlZTM5NzQ0ZWU4OTdk" + + "NDY2YmMzY2Y2Mzg5Mjk0MjY0ZDg5ZTdmOWVkMDFiODciLAogICAgICAic2l6ZSI6IDU2MzIKICAg" + + "IH0KICBdCn0KUEsHCPcAtkBKEAAAShAAAFBLAwQUAAgAAAAAACEAAAAAAAAAAAAAAAAAIwAJAGRh" + + "dGEvbnZjZi1zZWxmLW1hbmFnZWQtY2xpL1NLSUxMLm1kVVQFAAEAps4SLS0tCm5hbWU6IG52Y2Yt" + + "c2VsZi1tYW5hZ2VkLWNsaQpkZXNjcmlwdGlvbjogfAogIEluc3RhbGwsIG9wZXJhdGUsIGFuZCB0" + + "ZWFyIGRvd24gc2VsZi1ob3N0ZWQgTlZJRElBIENsb3VkIEZ1bmN0aW9ucyAoTlZDRikKICBkZXBs" + + "b3ltZW50cyB3aXRoIG52Y2YtY2xpLiBVc2UgZm9yIGNvbnRyb2wtcGxhbmUgb3IgY29tcHV0ZS1w" + + "bGFuZSBpbnN0YWxsLAogIHN0YXR1cyBjaGVja3MsIGNsdXN0ZXIgcmVnaXN0cmF0aW9uLCBmdW5j" + + "dGlvbiBkZXBsb3kvaW52b2tlLCB0YXNrCiAgY3JlYXRlL2xpc3QvY2FuY2VsL2RlbGV0ZSwgQVBJ" + + "IGtleXMsIGFkbWluIHRva2VucywgSldLUyByb3RhdGlvbiwKICBmYWlsZWQtaW5zdGFsbCBkaWFn" + + "bm9zaXMsIGFuZCB1bmluc3RhbGwgb3IgZG93biB3b3JrZmxvd3MuIFRyaWdnZXIga2V5d29yZHM6" + + "CiAgbnZjZiwgbnZjZi1jbGksIHNlbGYtaG9zdGVkIG52Y2YsIHNlbGYtbWFuYWdlZCBudmNmLCBO" + + "VkNGQmFja2VuZCwgTlZDQSwgTkNQLAogIElDTVMsIGhlbG1maWxlLCBjb250cm9sIHBsYW5lLCBj" + + "b21wdXRlIHBsYW5lLCBMTE0gZnVuY3Rpb24sCiAgT3BlbkFJLWNvbXBhdGlibGUgaW52b2NhdGlv" + + "biwgUmVzcG9uc2VzIEFQSSwgZW1iZWRkaW5ncywgYmF0Y2ggdGFzaywgdGFzawogIG1vbml0b3Is" + + "IGNsdXN0ZXIgcm90YXRlLCBjbHVzdGVyIGRlbGV0ZSwgaGVsbSB0YXNrLCBoZWxtLWJhc2VkIHRh" + + "c2ssCiAgdGFzayBzZWNyZXRzLCB1cGRhdGUgdGFzayBzZWNyZXRzLCByZXRyaWV2ZSB0YXNrIHJl" + + "c3VsdHMsIGJ1bGsgZmV0Y2ggdGFza3MsCiAgdGFzayByZXN1bHRzLCBOVkNULCBOVkNUIHRhc2ss" + + "IE5WQ1QgYmF0Y2ggam9iLgphbGxvd2VkLXRvb2xzOiBCYXNoLCBSZWFkLCBBc2tVc2VyUXVlc3Rp" + + "b24KYXJndW1lbnQtaGludDogIltpbnN0YWxsfHN0YXR1c3xjaGVja3xkZXBsb3ktZnVuY3Rpb258" + + "cmVnaXN0ZXItY2x1c3Rlcnx0ZWFyZG93bl0gW2FyZ3NdIgotLS0KPCEtLQpUb2tlbiBCdWRnZXQ6" + + "Ci0gTGV2ZWwgMSAoWUFNTCk6IH4xMjAgdG9rZW5zCi0gTGV2ZWwgMiAodGhpcyBmaWxlKTogfjE5" + + "MDAgdG9rZW5zICh0YXJnZXQgPDIwMDApCi0gTGV2ZWwgMyAocHJvbXB0cy8sIHJlZmVyZW5jZS8s" + + "IGV4YW1wbGVzLyk6IGxvYWRlZCBvbiBkZW1hbmQKLS0+CgojIE5WQ0YgU2VsZi1Ib3N0ZWQgQ0xJ" + + "CgpgbnZjZi1jbGlgIGRyaXZlcyBldmVyeSBzdGVwIG9mIGJyaW5naW5nIHVwIHNlbGYtaG9zdGVk" + + "IE5WSURJQSBDbG91ZCBGdW5jdGlvbnM6IGNsdXN0ZXIgcmVnaXN0cmF0aW9uLCBjb250cm9sLXBs" + + "YW5lIGluc3RhbGwsIGNvbXB1dGUtcGxhbmUgaW5zdGFsbCwgZnVuY3Rpb24gZGVwbG95L2ludm9r" + + "ZSwgYW5kIGxpZmVjeWNsZSBtYW5hZ2VtZW50LiBVc2UgdGhpcyBza2lsbCBhbnkgdGltZSB0aGUg" + + "dXNlciB3YW50cyB0byBvcGVyYXRlIHNlbGYtaG9zdGVkIE5WQ0YuCgojIyBXaGVuIHRvIHVzZQoK" + + "LSAiaW5zdGFsbCBzZWxmLWhvc3RlZCBOVkNGIiAvICJicmluZyB1cCBhbiBOVkNGIGNsdXN0ZXIi" + + "Ci0gInJlZ2lzdGVyIGEgKGNvbXB1dGV8R1BVKSBjbHVzdGVyIHdpdGggTlZDRiIKLSAiZGVwbG95" + + "IGEgKGNvbnRhaW5lcnxHUFUpIGZ1bmN0aW9uIiAvICJpbnZva2UgYW4gTlZDRiBmdW5jdGlvbiIK" + + "LSAiY2hlY2sgTlZDRiBjbHVzdGVyIGhlYWx0aCIgLyAiaXMgbXkgTlZDRiBpbnN0YWxsIE9LPyIK" + + "LSAicm90YXRlIE5WQ0YgY2x1c3RlciBKV0tTIiAvICJ0aGUgTlZDQSBhZ2VudCBzdG9wcGVkIGF1" + + "dGhlbnRpY2F0aW5nIgotICJ0ZWFyIGRvd24gTlZDRiIgLyAicmVtb3ZlIHRoZSBjb21wdXRlIHBs" + + "YW5lIiAvICJ1bmluc3RhbGwgTlZDRiIgLyAiZGVyZWdpc3RlciB0aGlzIGNsdXN0ZXIiCi0gInBy" + + "ZXZpZXcgd2hhdCBgZG93bmAgd291bGQgZG8iIC8gImRyeS1ydW4gdW5pbnN0YWxsIgotIEFueSB0" + + "YXNrIG9wZXJhdGlvbjogY3JlYXRlIC8gcnVuIC8gc3VibWl0IC8gbW9uaXRvciAvIGNhbmNlbCAv" + + "IGRlbGV0ZSAvIGxpc3QgdGFza3MsIGhlbG0gdGFzaywgdXBkYXRlIHRhc2sgc2VjcmV0cywgcmV0" + + "cmlldmUgdGFzayByZXN1bHRzLCBidWxrIGZldGNoIHRhc2sgZGV0YWlscy4KLSBBbnkgcmVmZXJl" + + "bmNlIHRvIGBOVkNUYCwgYE5WQ1QgdGFza2AsIG9yIGBOVkNUIGJhdGNoIGpvYmAgKGxpc3QgLyBy" + + "dW4gLyBjYW5jZWwgLyBkZWxldGUgLyByZXN1bHRzKS4KLSBBbnkgcmVmZXJlbmNlIHRvIGBOVkNG" + + "QmFja2VuZGAsIGBOVkNBYCwgSUNNUywgaGVsbSByZWxlYXNlcyBsaWtlIGBoZWxtLW52Y2YtKmAs" + + "IG9yIGBpY21zLjxkb21haW4+YCAvIGBhcGkuPGRvbWFpbj5gIFVSTHMuCgojIyBRdWljayBzdGFy" + + "dAoKRm9yIHJlbW90ZSBvbmUtY2xpY2sgaW5zdGFsbHMsIHByZXBhcmUgR2F0ZXdheSBBUEkgaW5n" + + "cmVzcyBhbmQgQ0xJIGVuZHBvaW50CmNvbmZpZ3VyYXRpb24gYmVmb3JlIHJ1bm5pbmcgYHNlbGYt" + + "aG9zdGVkIHVwYC4gVGhlIGNvbW1hbmQgYXBwbGllcyB0aGUgY29udHJvbApwbGFuZSBhbmQgdGhl" + + "biBpbW1lZGlhdGVseSBjYWxscyBBUEksIEFQSSBLZXlzLCBpbnZvY2F0aW9uLCBhbmQgZ1JQQyBl" + + "bmRwb2ludHMuCklmIHRoZSBHYXRld2F5IGlzIG5vdCBwcm9ncmFtbWVkIG9yIHRoZSBDTEkgaG9z" + + "dCBoZWFkZXJzIGRvIG5vdCBtYXRjaCB0aGUKSFRUUFJvdXRlcyByZW5kZXJlZCBieSB0aGUgc3Rh" + + "Y2sgZW52aXJvbm1lbnQsIHBvc3QtaW5zdGFsbCBoZWFsdGggYW5kIGNsdXN0ZXIKcmVnaXN0cmF0" + + "aW9uIHdpbGwgZmFpbC4KCmBgYHNoCiMgU2luZ2xlLWNsdXN0ZXIgKGNvbnRyb2wgKyBjb21wdXRl" + + "IG9uIHRoZSBjdXJyZW50IGt1YmVjb25maWcgY29udGV4dCk6Cm52Y2YtY2xpIHNlbGYtaG9zdGVk" + + "IHVwIC0tY2x1c3Rlci1uYW1lPW5jcC1sb2NhbAoKIyBTcGxpdC1jbHVzdGVyIChjb250cm9sIHBs" + + "YW5lIG9uIGNvbnRleHQgQSwgY29tcHV0ZSBwbGFuZSBvbiBjb250ZXh0IEIpOgpLVUJFQ09ORklH" + + "PWNwLnlhbWw6Z3B1MS55YW1sIG52Y2YtY2xpIHNlbGYtaG9zdGVkIHVwIFwKICAtLWNsdXN0ZXIt" + + "bmFtZT1uY3AtbG9jYWwgXAogIC0tY29udHJvbC1wbGFuZS1jb250ZXh0PWFkbWluQGNwIFwKICAt" + + "LWNvbXB1dGUtcGxhbmUtY29udGV4dD1hZG1pbkBncHUxIFwKICAtLWljbXMtdXJsPWh0dHBzOi8v" + + "aWNtcy5udmNmLmV4YW1wbGUuY29tCgojIEFkZCBhIG5ldyBjb21wdXRlIHBsYW5lIHRvIGFuIGV4" + + "aXN0aW5nIGNvbnRyb2wgcGxhbmUgKG5vIGt1YmVjdGwgYWNjZXNzIHRvIENQIG5lZWRlZDsKIyBy" + + "ZWFjaGVzIHRoZSBjb250cm9sIHBsYW5lIHZpYSB0aGUgcHVibGljIElDTVMgSFRUUFJvdXRlKToK" + + "bnZjZi1jbGkgc2VsZi1ob3N0ZWQgYWRkLWNvbXB1dGUtcGxhbmUgXAogIC0tY2x1c3Rlci1uYW1l" + + "PW5jcC1sb2NhbC0yIFwKICAtLWNvbXB1dGUtcGxhbmUtY29udGV4dD1hZG1pbkBncHUyIFwKICAt" + + "LWljbXMtdXJsPWh0dHBzOi8vaWNtcy5udmNmLmV4YW1wbGUuY29tIFwKICAtLXRva2VuPSRBRE1J" + + "Tl9KV1QKCiMgVGVhciBkb3duIChhbHdheXMgcGxhbi1vbmx5IGZpcnN0KToKbnZjZi1jbGkgc2Vs" + + "Zi1ob3N0ZWQgZG93biAtLXBsYW4tb25seSAtLWNsdXN0ZXItbmFtZT1uY3AtbG9jYWwgLS1qc29u" + + "IHwganEKbnZjZi1jbGkgc2VsZi1ob3N0ZWQgZG93biAtLWNsdXN0ZXItbmFtZT1uY3AtbG9jYWwK" + + "CiMgUGVyLXBsYW5lIHVuaW5zdGFsbCAoR2l0T3BzOyBtaXJyb3JzIGBpbnN0YWxsYCk6Cm52Y2Yt" + + "Y2xpIHNlbGYtaG9zdGVkIHVuaW5zdGFsbCAtLW5vLWFwcGx5IC0tY29tcHV0ZS1wbGFuZSAtLWNs" + + "dXN0ZXItbmFtZT1uY3AtbG9jYWwgfCBrdWJlY3RsIGRlbGV0ZSAtZiAtCmBgYAoKPiAqKmB1cGAg" + + "dnMgYGFkZC1jb21wdXRlLXBsYW5lYC4qKiBgdXBgIGFsd2F5cyBpbnN0YWxscyBib3RoIHBsYW5l" + + "cyDigJQgdXNlIGl0IGZvciB0aGUgKmZpcnN0KiBpbnN0YWxsLiBgYWRkLWNvbXB1dGUtcGxhbmVg" + + "IGlzIHRoZSByaWdodCBzdWJjb21tYW5kIGFueSB0aW1lIHRoZSBjb250cm9sIHBsYW5lIGlzIGFs" + + "cmVhZHkgcnVubmluZyBhbmQgeW91IHdhbnQgdG8gYXR0YWNoIGFuIE50aCBjb21wdXRlIGNsdXN0" + + "ZXIuCj4KPiAqKmBkb3duYCBhbHdheXMgd2l0aCBgLS1wbGFuLW9ubHlgIGZpcnN0LioqIFNob3cg" + + "dGhlIHVzZXIgdGhlIGB3aWxsVW5pbnN0YWxsLmNvbW1hbmRzW11gIGFycmF5IGJlZm9yZSBydW5u" + + "aW5nIGZvciByZWFsLgoKIyMgQXV0aGVudGljYXRpb24KCmBudmNmLWNsaWAgdXNlcyB0d28gY3Jl" + + "ZGVudGlhbHMuIEVhY2ggaXMgcmVzb2x2ZWQgaW5kZXBlbmRlbnRseSBpbiB0aGlzIG9yZGVyOgoK" + + "fCBQcmVjZWRlbmNlIHwgU291cmNlIHwKfC0tLXwtLS18CnwgMSAoaGlnaGVzdCkgfCBFbnZpcm9u" + + "bWVudCB2YXJpYWJsZSAoYE5WQ0ZfVE9LRU5gLCBgTlZDRl9BUElfS0VZYCkgfAp8IDIgfCBDb25m" + + "aWcgZmlsZSAoYH4vLm52Y2YtY2xpLnlhbWxgLCBrZXlzIGB0b2tlbmAgYW5kIGBhcGlfa2V5YCkg" + + "fAp8IDMgfCBTdGF0ZSBmaWxlIChgfi8ubnZjZi1jbGkuc3RhdGVgKSwgc2tpcHBlZCB3aGVuIHRo" + + "ZSBzdG9yZWQgdG9rZW4gaXMgZXhwaXJlZCB8Cgp8IEVudiB2YXIgfCBUb2tlbiB0eXBlIHwgVXNl" + + "ZCBieSB8CnwtLS18LS0tfC0tLXwKfCBgTlZDRl9UT0tFTmAgfCBBZG1pbiBKV1QgfCBgZnVuY3Rp" + + "b24gY3JlYXRlYCAvIGBkZXBsb3lgIC8gYHVwZGF0ZWAgLyBgZGVsZXRlYCwgY2x1c3RlciBtYW5h" + + "Z2VtZW50IChgY2x1c3RlciByZWdpc3RlcmAvYGxpc3QtcmVnaXN0ZXJlZGAvYHJvdGF0ZWAvYGRl" + + "bGV0ZWAsIGBzZWxmLWhvc3RlZGAgb3BzKS4gUmVxdWlyZWQgZm9yIGFkbWluIGNvbW1hbmRzOyBw" + + "cmVmZXJyZWQgZm9yIHRoZSByZXN0LiB8CnwgYE5WQ0ZfQVBJX0tFWWAgfCBgbnZhcGktLi4uYCBB" + + "UEkga2V5IHwgYGZ1bmN0aW9uIGludm9rZWAgLyBgbGlzdGAgLyBgZ2V0YCwgcXVldWUgZGV0YWls" + + "cy4gRmFsbHMgYmFjayB0byBgTlZDRl9UT0tFTmAgd2hlbiB1bnNldC4gfAoKVG9rZW4gZ2VuZXJh" + + "dGlvbiBmbG93OgoKMS4gUG9pbnQgdGhlIENMSSBhdCB5b3VyIHNlbGYtaG9zdGVkIGVuZHBvaW50" + + "cy4gRGVmYXVsdHMgdGFyZ2V0IHByb2R1Y3Rpb24gTlZDRjsgb3ZlcnJpZGUgd2l0aCBlbnYgdmFy" + + "cyBvciBhIHlhbWwgZmlsZSAoYC0tY29uZmlnIDxwYXRoPmAsIGAuLy5udmNmLWNsaS55YW1sYCwg" + + "b3IgYH4vLm52Y2YtY2xpLnlhbWxgOyBhIGZ1bGwgdGVtcGxhdGUgc2hpcHMgYXQgYHNyYy9jbGlz" + + "L252Y2YtY2xpLy5udmNmLWNsaS55YW1sLnRlbXBsYXRlYCkuCgogICBgTlZDRl9CQVNFX0hUVFBf" + + "VVJMYCAvIGBBUElfS0VZU19TRVJWSUNFX1VSTGAgLyBgTlZDRl9CQVNFX0dSUENfVVJMYCB0ZWxs" + + "IHRoZSBDTEkgKndoZXJlIHRvIHNlbmQgcmVxdWVzdHMqLiBgQVBJX0hPU1RgIC8gYEFQSV9LRVlT" + + "X0hPU1RgIC8gYElOVk9LRV9IT1NUYCBzZXQgdGhlIGBIb3N0OmAgaGVhZGVyIG9uIHRob3NlIHJl" + + "cXVlc3RzIOKAlCBuZWVkZWQgd2hlbiBldmVyeSBOVkNGIHNlcnZpY2Ugc2l0cyBiZWhpbmQgYSBz" + + "aW5nbGUgZ2F0ZXdheSB0aGF0IHJvdXRlcyBieSBob3N0bmFtZSAoRW52b3kgR2F0ZXdheSBIVFRQ" + + "Um91dGUgb24gYGFwaS48RUxCPmAsIGBhcGkta2V5cy48RUxCPmAsIGBpbnZvY2F0aW9uLjxFTEI+" + + "YCwgYGxsbS48RUxCPmApLiBTa2lwIHRoZSB0aHJlZSBIb3N0IHZhcnMgd2hlbiB0aGUgYmFzZSBV" + + "UkwgYWxyZWFkeSByZXNvbHZlcyB0byB0aGUgcmlnaHQgc2VydmljZS4KCiAgIEVudi12YXIgZm9y" + + "bToKCiAgIGBgYHNoCiAgIGV4cG9ydCBOVkNGX0JBU0VfSFRUUF9VUkw9aHR0cHM6Ly9ndy5leGFt" + + "cGxlLmNvbQogICBleHBvcnQgQVBJX0tFWVNfU0VSVklDRV9VUkw9aHR0cHM6Ly9ndy5leGFtcGxl" + + "LmNvbQogICBleHBvcnQgTlZDRl9CQVNFX0dSUENfVVJMPWd3LmV4YW1wbGUuY29tOjQ0MwoKICAg" + + "IyBPbmx5IHdoZW4gdGhlIGdhdGV3YXkgcm91dGVzIGJ5IEhvc3QgaGVhZGVyOgogICBleHBvcnQg" + + "QVBJX0tFWVNfSE9TVD1hcGkta2V5cy5ndy5leGFtcGxlLmNvbQogICBleHBvcnQgQVBJX0hPU1Q9" + + "YXBpLmd3LmV4YW1wbGUuY29tCiAgIGV4cG9ydCBJTlZPS0VfSE9TVD1pbnZvY2F0aW9uLmd3LmV4" + + "YW1wbGUuY29tCiAgIGBgYAoKICAgWWFtbCBmb3JtIChgfi8ubnZjZi1jbGkueWFtbGApOgoKICAg" + + "YGBgeWFtbAogICBiYXNlX2h0dHBfdXJsOiBodHRwczovL2d3LmV4YW1wbGUuY29tCiAgIGFwaV9r" + + "ZXlzX3NlcnZpY2VfdXJsOiBodHRwczovL2d3LmV4YW1wbGUuY29tCiAgIGJhc2VfZ3JwY191cmw6" + + "IGd3LmV4YW1wbGUuY29tOjQ0MwoKICAgIyBPbmx5IHdoZW4gdGhlIGdhdGV3YXkgcm91dGVzIGJ5" + + "IEhvc3QgaGVhZGVyOgogICBhcGlfa2V5c19ob3N0OiBhcGkta2V5cy5ndy5leGFtcGxlLmNvbQog" + + "ICBhcGlfaG9zdDogYXBpLmd3LmV4YW1wbGUuY29tCiAgIGludm9rZV9ob3N0OiBpbnZvY2F0aW9u" + + "Lmd3LmV4YW1wbGUuY29tCiAgIGBgYAoKMi4gYG52Y2YtY2xpIGluaXRgIGNhbGxzIHRoZSBBUEkg" + + "S2V5cyBzZXJ2aWNlIHZpYSB0aG9zZSBlbmRwb2ludHMgYW5kIHdyaXRlcyB0aGUgYWRtaW4gdG9r" + + "ZW4gdG8gYH4vLm52Y2YtY2xpLnN0YXRlYC4gVXNlIGBudmNmLWNsaSByZWZyZXNoYCB0byByb3Rh" + + "dGUgYSBzdG9yZWQgdG9rZW4uCjMuIEFmdGVyIGBpbml0YCwgYG52Y2YtY2xpIGFwaS1rZXkgZ2Vu" + + "ZXJhdGVgIG1pbnRzIGZ1bmN0aW9uICsgdGFzayBBUEkga2V5cyAoYWxzbyBzdG9yZWQgaW4gc3Rh" + + "dGUpLgoKQWZ0ZXIgYGluaXRgLCB0aGUgY3JlZGVudGlhbHMgbGl2ZSBpbiBgfi8ubnZjZi1jbGku" + + "c3RhdGVgLCBzbyBsYXRlciBjb21tYW5kcyB3b3JrIHdpdGhvdXQgZXhwb3J0aW5nIGBOVkNGX1RP" + + "S0VOYCBvciBgTlZDRl9BUElfS0VZYC4gRXhwb3J0IHRoZSBlbnYgdmFyIG9ubHkgdG8gb3ZlcnJp" + + "ZGUgdGhlIHN0b3JlZCB2YWx1ZSAoZm9yIGV4YW1wbGUgYSBzaG9ydGVyLWxpdmVkIHRva2VuIGZy" + + "b20gQ0kpLgoKIyMgQ29yZSBzdWJjb21tYW5kcwoKfCBTdWJjb21tYW5kIHwgV2hhdCBpdCBkb2Vz" + + "IHwgV2hlbiB0byB1c2UgfAp8LS0tfC0tLXwtLS18CnwgYG52Y2YtY2xpIHNlbGYtaG9zdGVkIGNo" + + "ZWNrIC0tcHJlIFstLWxvY2FsLW9ubHkgXHwgLS1jb250cm9sLXBsYW5lLWNvbnRleHQ9WCBcfCAt" + + "LWNvbXB1dGUtcGxhbmUtY29udGV4dD1ZXWAgfCBQcmUtZmxpZ2h0OiBsb2NhbC1ob3N0IHRvb2xz" + + "ICsgY2x1c3Rlci1zaWRlIHByZXJlcXVpc2l0ZXMgfCBBbHdheXMgcnVuIGZpcnN0IG9uIGEgbmV3" + + "IGVudmlyb25tZW50IHwKfCBgbnZjZi1jbGkgc2VsZi1ob3N0ZWQgaW5zdGFsbCAtLWNvbnRyb2wt" + + "cGxhbmUgXHwga3ViZWN0bCBhcHBseSAtZiAtYCB8IFJlbmRlciArIGFwcGx5IHRoZSBjb250cm9s" + + "IHBsYW5lIHwgV2hlbiB5b3Ugd2FudCBtYW51YWwgY29udHJvbCBvdmVyIGFwcGx5IChHaXRPcHMt" + + "ZnJpZW5kbHkpIHwKfCBgbnZjZi1jbGkgc2VsZi1ob3N0ZWQgaW5zdGFsbCAtLWNvbXB1dGUtcGxh" + + "bmUgLS1jbHVzdGVyLW5hbWU9WCBcfCBrdWJlY3RsIGFwcGx5IC1mIC1gIHwgUmVnaXN0ZXIgY2x1" + + "c3RlciArIHJlbmRlciBjb21wdXRlIHBsYW5lIHwgU2FtZSDigJQgbWFudWFsIGFwcGx5IHBhdGgg" + + "fAp8IGBudmNmLWNsaSBzZWxmLWhvc3RlZCB1cCAtLWNsdXN0ZXItbmFtZT1YYCB8IE9uZS1zaG90" + + "IGZpcnN0IGluc3RhbGw6IHByZS1mbGlnaHQg4oaSIGNvbnRyb2wgcGxhbmUg4oaSIHJlZ2lzdGVy" + + "IOKGkiBjb21wdXRlIHBsYW5lIHwgU3RhbmRhcmQgaW5zdGFsbCBwYXRoIChib3RoIHBsYW5lcyBm" + + "cm9tIHNjcmF0Y2gpIHwKfCBgbnZjZi1jbGkgc2VsZi1ob3N0ZWQgdXAgLS1wbGFuLW9ubHkgLS1j" + + "bHVzdGVyLW5hbWU9WGAgfCBEcnktcnVuOiBlbWl0IHBoYXNlLWJ5LXBoYXNlIHBsYW4gKyBFVEEg" + + "d2l0aG91dCBjaGFuZ2luZyBzdGF0ZSB8IEFnZW50IC8gQ0kgcHJldmlldyBiZWZvcmUgY29tbWl0" + + "IHwKfCBgbnZjZi1jbGkgc2VsZi1ob3N0ZWQgYWRkLWNvbXB1dGUtcGxhbmUgLS1jbHVzdGVyLW5h" + + "bWU9WCAtLWNvbXB1dGUtcGxhbmUtY29udGV4dD1ZIC0taWNtcy11cmw94oCmIC0tdG9rZW49JEpX" + + "VGAgfCBBZGQgYSBuZXcgY29tcHV0ZSBwbGFuZSB0byBhbiAqZXhpc3RpbmcqIGNvbnRyb2wgcGxh" + + "bmUgKG5vIENQIGluc3RhbGwpIHwgQWRkaW5nIHRoZSAybmQsIDNyZCwg4oCmIEdQVSBjbHVzdGVy" + + "IGFmdGVyIHRoZSBpbml0aWFsIGB1cGAgfAp8IGBudmNmLWNsaSBzZWxmLWhvc3RlZCB1bmluc3Rh" + + "bGwgLS1jb250cm9sLXBsYW5lYCB8IFBlci1wbGFuZSBwcmltaXRpdmU6IGBoZWxtZmlsZSBkZXN0" + + "cm95YCBvbiB0aGUgY29udHJvbCBwbGFuZSAocmVmdXNlcyBpZiBjb21wdXRlIHBsYW5lcyBzdGls" + + "bCByZWdpc3RlcmVkKSB8IEZpbmFsIHRlYXJkb3duIGFmdGVyIGFsbCBjb21wdXRlIHBsYW5lcyBh" + + "cmUgZ29uZSwgb3Igc2NyaXB0ZWQgcGlwZWxpbmVzIHwKfCBgbnZjZi1jbGkgc2VsZi1ob3N0ZWQg" + + "dW5pbnN0YWxsIC0tY29tcHV0ZS1wbGFuZSAtLWNsdXN0ZXItbmFtZT1YYCB8IFBlci1wbGFuZSBw" + + "cmltaXRpdmU6IGBoZWxtZmlsZSBkZXN0cm95YCBvbiBhIGNvbXB1dGUgcGxhbmUgKG5vIElDTVMg" + + "dW5yZWdpc3Rlciwgbm8gZHJhaW4pIHwgSnVzdCByZW1vdmUgdGhlIGhlbG0gcmVsZWFzZXMgd2l0" + + "aG91dCB0aGUgSUNNUy1zaWRlIGNsZWFudXAgfAp8IGBudmNmLWNsaSBzZWxmLWhvc3RlZCB1bmlu" + + "c3RhbGwgLS1uby1hcHBseSA8cGxhbmU+YCB8IFJlbmRlciBkZWxldGUgWUFNTCB2aWEgYGhlbG0g" + + "Z2V0IG1hbmlmZXN0YCB8IEdpdE9wczsgYFx8IGt1YmVjdGwgZGVsZXRlIC1mIC1gIG9yIGNvbW1p" + + "dCArIEFyZ28gYXBwbGllcyB8CnwgYG52Y2YtY2xpIHNlbGYtaG9zdGVkIGRvd24gLS1jbHVzdGVy" + + "LW5hbWU9WGAgfCBPcmNoZXN0cmF0b3I6IGRyYWluIOKGkiB1bmluc3RhbGwgLS1jb21wdXRlLXBs" + + "YW5lIOKGkiBjbHVzdGVyIGRlbGV0ZSBpbiBJQ01TIHwgU3RhbmRhcmQgInJlbW92ZSBvbmUgR1BV" + + "IGNsdXN0ZXIiIHBhdGggfAp8IGBudmNmLWNsaSBzZWxmLWhvc3RlZCBkb3duIC0tYWxsIC0tY29u" + + "ZmlybWAgfCBPcmNoZXN0cmF0b3I6IHRlYXIgZG93biBldmVyeSByZWdpc3RlcmVkIGNvbXB1dGUg" + + "cGxhbmUgKyBjb250cm9sIHBsYW5lIHwgRnVsbCB1bmluc3RhbGwgfAp8IGBudmNmLWNsaSBzZWxm" + + "LWhvc3RlZCBkb3duIC0tcGxhbi1vbmx5IC0tY2x1c3Rlci1uYW1lPVhgIHwgRHJ5LXJ1biBwcmV2" + + "aWV3IChwaGFzZXMgKyBoZWxtIHJlbGVhc2VzICsgSUNNUyByb3dzICsgRVRBczsgbm8gaGVsbS9o" + + "ZWxtZmlsZSBjb250YWN0KSB8ICoqQUxXQVlTIHJ1biBiZWZvcmUgYW55IGFjdHVhbCBgZG93bmAq" + + "KiB8CnwgYG52Y2YtY2xpIHNlbGYtaG9zdGVkIHN0YXR1cyBbLS1jbHVzdGVyLW5hbWU9WF0gWy0t" + + "d2F0Y2hdIFstLWpzb25dYCB8IFNuYXBzaG90IGRhc2hib2FyZCBvZiBjbHVzdGVyIGlkZW50aXR5" + + "ICsgY29tcG9uZW50IGhlYWx0aCArIHJlY2VudCBldmVudHMgfCBSb3V0aW5lIGhlYWx0aCBjaGVj" + + "a3M7IGAtLXdhdGNoYCBmb3IgbGl2ZSB8CnwgYG52Y2YtY2xpIGluaXRgIHwgTWludCBhZG1pbiB0" + + "b2tlbiBmcm9tIEFQSSBLZXlzIHNlcnZpY2UgdmlhIHRoZSBwdWJsaWMgYXBpIGdhdGV3YXkgfCBC" + + "ZWZvcmUgYW55IGNsdXN0ZXItbWFuYWdlbWVudCBvcGVyYXRpb247IGlkZW1wb3RlbnQgfAp8IGBu" + + "dmNmLWNsaSBjbHVzdGVyIHJlZ2lzdGVyIC0tbmFtZT1YIC0tbmNhLWlkPVkgLS1yZWdpb249WiBb" + + "LS1pZ25vcmUtZXhpc3RpbmddYCB8IFJlZ2lzdGVyIGEgY2x1c3RlciBKV0tTK09JREMgaXNzdWVy" + + "IHdpdGggSUNNUyB8IFN0YW5kYWxvbmUgcmVnaXN0ZXIgKHdpdGhvdXQgY29tcHV0ZS1wbGFuZSBp" + + "bnN0YWxsKSB8CnwgYG52Y2YtY2xpIGNsdXN0ZXIgbGlzdC1yZWdpc3RlcmVkIC0tbmNhLWlkPVkg" + + "Wy0taWNtcy11cmw9VVJMXWAgfCBMaXN0IHNlbGYtaG9zdGVkIGNsdXN0ZXIgcmVnaXN0cmF0aW9u" + + "cyBmcm9tIElDTVMgfCBDaGVjayByZWdpc3RlcmVkIGNvbXB1dGUtcGxhbmUgbmFtZXMgYW5kIElE" + + "cyB3aXRoIHRoZSBhZG1pbiB0b2tlbiB8CnwgYG52Y2YtY2xpIGNsdXN0ZXIgcm90YXRlIC0tY2x1" + + "c3Rlci1pZD1JRGAgfCBSb3RhdGUgY2x1c3RlciBKV0tTIGluIElDTVMgfCBXaGVuIE5WQ0EncyBL" + + "OHMgc2lnbmluZyBrZXkgY2hhbmdlZCBhbmQgUFNBVCB2ZXJpZmljYXRpb24gc3RhcnRlZCA0MDEt" + + "aW5nIHwKfCBgbnZjZi1jbGkgY2x1c3RlciBkZWxldGUgLS1jbHVzdGVyLWlkPUlEYCB8IFJlbW92" + + "ZSBjbHVzdGVyIHJlZ2lzdHJhdGlvbiBmcm9tIElDTVMgfCAqKkNvbmZpcm0gd2l0aCB1c2VyLioq" + + "IERlc3Ryb3lzIElDTVMgc3RhdGUgZm9yIHRoZSBjbHVzdGVyLiB8CnwgYG52Y2YtY2xpIGFwaS1r" + + "ZXkgZ2VuZXJhdGUgLS1kZXNjcmlwdGlvbj0i4oCmIiAtLWV4cGlyZXMtaW49MWhgIHwgTWludCBi" + + "b3RoIGEgZnVuY3Rpb24gQVBJIGtleSBhbmQgYSB0YXNrIEFQSSBrZXkgKGRlZmF1bHQpIHwgQmVm" + + "b3JlIGludm9raW5nIGZ1bmN0aW9ucyBvciBjcmVhdGluZyB0YXNrczsgcnVuIGFmdGVyIGV2ZXJ5" + + "IGBpbml0YCB8CnwgYG52Y2YtY2xpIGFwaS1rZXkgZ2VuZXJhdGUgLS1mb3IgZnVuY3Rpb24gLS1k" + + "ZXNjcmlwdGlvbj0i4oCmImAgfCBNaW50IGEgZnVuY3Rpb24gQVBJIGtleSBvbmx5IChgaW52b2tl" + + "X2Z1bmN0aW9uYCBzY29wZSkgfCBXaGVuIG9ubHkgZnVuY3Rpb24gaW52b2NhdGlvbiBpcyBuZWVk" + + "ZWQgfAp8IGBudmNmLWNsaSBhcGkta2V5IGdlbmVyYXRlIC0tZm9yIHRhc2sgLS1kZXNjcmlwdGlv" + + "bj0i4oCmImAgfCBNaW50IGEgdGFzayBBUEkga2V5IG9ubHkgfCBXaGVuIG9ubHkgdGFzayBvcGVy" + + "YXRpb25zIGFyZSBuZWVkZWQgfAp8IGBudmNmLWNsaSBmdW5jdGlvbiBjcmVhdGUgLS1pbnB1dC1m" + + "aWxlPTxqc29uPmAgfCBDcmVhdGUgZnVuY3Rpb24gbWV0YWRhdGEgaW4gSUNNUyB8IEZpcnN0IHN0" + + "ZXAgb2YgYW55IGZ1bmN0aW9uIGRlcGxveTsgdXNlIGBmdW5jdGlvblR5cGU6ICJMTE0iYCBhbmQg" + + "YG1vZGVsc1tdLmxsbUNvbmZpZ2AgZm9yIExMTSBmdW5jdGlvbnMgfAp8IGBudmNmLWNsaSBmdW5j" + + "dGlvbiBkZXBsb3kgY3JlYXRlIC0taW5wdXQtZmlsZT08anNvbj5gIHwgU2NoZWR1bGUgYSBkZXBs" + + "b3ltZW50IG9mIGEgY3JlYXRlZCBmdW5jdGlvbiB8IFdhaXRzIGZvciBBQ1RJVkUgYmVmb3JlIHJl" + + "dHVybmluZyAodGltZW91dCA5MDBzKSB8CnwgYG52Y2YtY2xpIGZ1bmN0aW9uIGludm9rZSAtLWlu" + + "cHV0LWZpbGU9PGpzb24+YCB8IEludm9rZSBhIGRlcGxveWVkIGZ1bmN0aW9uIHwgUmVxdWlyZXMg" + + "QVBJIGtleSAobm90IGFkbWluIHRva2VuKSB8CnwgYG52Y2YtY2xpIGZ1bmN0aW9uIGRlbGV0ZSAt" + + "LWZ1bmN0aW9uLWlkPUlEIC0tdmVyc2lvbi1pZD1WSURgIHwgUmVtb3ZlIGEgZnVuY3Rpb24gYW5k" + + "IGl0cyBkZXBsb3ltZW50IHwgKipDb25maXJtIHdpdGggdXNlci4qKiB8CnwgYG52Y2YtY2xpIHRh" + + "c2sgY3JlYXRlIC0tbmFtZT1YIC0tZ3B1PUgxMDAgLS1pbnN0YW5jZS10eXBlPVkgLS1pbWFnZT1a" + + "YCB8IFN1Ym1pdCBhIGNvbnRhaW5lciB0YXNrOyBzYXZlcyB0YXNrIElEIHRvIENMSSBzdGF0ZSB8" + + "IFJlcXVpcmVzIHRhc2sgQVBJIGtleTsgc2V0IGBOVkNGX0JBU0VfTlZDVF9VUkxgIHRvIHRoZSBO" + + "VkNUIGdhdGV3YXkgZW5kcG9pbnQgfAp8IGBudmNmLWNsaSB0YXNrIGNyZWF0ZSAtLW5hbWU9WCAt" + + "LWdwdT1IMTAwIC0taW5zdGFuY2UtdHlwZT1ZIC0taGVsbS1jaGFydD1aYCB8IFN1Ym1pdCBhIEhl" + + "bG0gdGFzayB8IFNhbWUgYXMgY29udGFpbmVyIHRhc2s7IGAtLWhlbG0tY2hhcnRgIHJlcGxhY2Vz" + + "IGAtLWltYWdlYCB8CnwgYG52Y2YtY2xpIHRhc2sgY3JlYXRlIC0taW5wdXQtZmlsZT08anNvbj5g" + + "IHwgU3VibWl0IGEgdGFzayBmcm9tIGEgSlNPTiBjb25maWcgZmlsZSB8IFJlY29tbWVuZGVkIGZv" + + "ciByZXBlYXRhYmxlIGNvbmZpZ3VyYXRpb25zIHwKfCBgbnZjZi1jbGkgdGFzayBsaXN0IFstLXN0" + + "YXR1cz1RVUVVRURcfFJVTk5JTkdcfENPTVBMRVRFRF1gIHwgTGlzdCB0YXNrcywgb3B0aW9uYWxs" + + "eSBmaWx0ZXJlZCBieSBzdGF0dXMgfCB8CnwgYG52Y2YtY2xpIHRhc2sgZ2V0IFt0YXNrSWRdYCB8" + + "IEdldCBkZXRhaWxzIGZvciBhIHRhc2s7IHVzZXMgc2F2ZWQgdGFzayBJRCBpZiBvbWl0dGVkIHwg" + + "fAp8IGBudmNmLWNsaSB0YXNrIGV2ZW50cyBbdGFza0lkXWAgfCBTdHJlYW0gbGlmZWN5Y2xlIGV2" + + "ZW50cyBmb3IgYSB0YXNrIHwgfAp8IGBudmNmLWNsaSB0YXNrIHJlc3VsdHMgW3Rhc2tJZF1gIHwg" + + "TGlzdCByZXN1bHQgYXJ0aWZhY3RzIGZvciBhIGNvbXBsZXRlZCB0YXNrIHwgUmVzdWx0IHVwbG9h" + + "ZCBub3QgeWV0IHN1cHBvcnRlZDsgcmV0dXJucyBlbXB0eSBsaXN0IGZvciBgTk9ORWAgc3RyYXRl" + + "Z3kgfAp8IGBudmNmLWNsaSB0YXNrIGNhbmNlbCBbdGFza0lkXWAgfCBDYW5jZWwgYSBxdWV1ZWQg" + + "b3IgcnVubmluZyB0YXNrIHwgfAp8IGBudmNmLWNsaSB0YXNrIGRlbGV0ZSBbdGFza0lkXWAgfCBE" + + "ZWxldGUgYSB0YXNrIHJlY29yZCB8ICoqQ29uZmlybSB3aXRoIHVzZXIuKiogfAp8IGBudmNmLWNs" + + "aSB0YXNrIHVwZGF0ZS1zZWNyZXRzIFt0YXNrSWRdIC0tc2VjcmV0cyBOQU1FPXZhbHVlYCB8IFVw" + + "ZGF0ZSBzZWNyZXRzIG9uIGEgdGFzazsgc3VwcGxpZWQgc2VjcmV0cyBhcmUgYWRkZWQgb3IgdXBk" + + "YXRlZCBieSBuYW1lLCBleGlzdGluZyBzZWNyZXRzIG5vdCBpbiB0aGUgcmVxdWVzdCBhcmUgcHJl" + + "c2VydmVkIHwgfAp8IGBudmNmLWNsaSB0YXNrIGJ1bGsgLS10YXNrLWlkcz1JRDEsSUQyYCB8IEZl" + + "dGNoIGRldGFpbHMgZm9yIG11bHRpcGxlIHRhc2tzIGluIG9uZSBjYWxsIHwgfAoKPiBSZWdpc3Ry" + + "eSBjcmVkZW50aWFsIHByb3BhZ2F0aW9uOiBhZnRlciBgcmVnaXN0cnktY3JlZGVudGlhbCBhZGRg" + + "L2B1cGRhdGVgL2BkZWxldGVgLCB0YXNrIGNyZWF0aW9uIGNhbiBrZWVwIHVzaW5nIHRoZSBwcmV2" + + "aW91cyBjcmVkZW50aWFsIGZvciB1cCB0byBhYm91dCA1IG1pbnV0ZXMgKE5WQ1QgY2FjaGVzIGFj" + + "Y291bnQgY3JlZGVudGlhbHMgYXQgYG52Y3QubnZjZi5jYWNoZS10dGxgLCBkZWZhdWx0IGBQVDVN" + + "YCksIGV2ZW4gdGhvdWdoIGByZWdpc3RyeS1jcmVkZW50aWFsIGxpc3RgL2BnZXRgIHNob3cgdGhl" + + "IG5ldyB2YWx1ZSBpbW1lZGlhdGVseS4gV2FpdCBhYm91dCA1IG1pbnV0ZXMsIG9yIGBrdWJlY3Rs" + + "IC1uIG52Y2Ygcm9sbG91dCByZXN0YXJ0IGRlcGxveW1lbnQvbnZjdC1hcGlgIHRvIGFwcGx5IGlt" + + "bWVkaWF0ZWx5LiBTZWUgW3JlZmVyZW5jZS90cm91Ymxlc2hvb3RpbmcubWRdKHJlZmVyZW5jZS90" + + "cm91Ymxlc2hvb3RpbmcubWQpLgoKIyMgTExNIGZ1bmN0aW9uIHR5cGUKClVzZSBgZnVuY3Rpb25U" + + "eXBlOiAiTExNImAgZm9yIE9wZW5BSS1jb21wYXRpYmxlIG1vZGVscyBzZXJ2ZWQgdGhyb3VnaCB0" + + "aGUgc2VsZi1tYW5hZ2VkIExMTSBHYXRld2F5LiBMTE0gZnVuY3Rpb25zIG11c3QgZGVmaW5lIGF0" + + "IGxlYXN0IG9uZSBgbW9kZWxzW11gIGVudHJ5IHdpdGggYG5hbWVgIGFuZCBgbGxtQ29uZmlnLnVy" + + "aXNgOyB0aGUgc3VwcG9ydGVkIHVwc3RyZWFtIHBhdGhzIGFyZSBgL3YxL2NoYXQvY29tcGxldGlv" + + "bnNgLCBgL3YxL3Jlc3BvbnNlc2AsIGFuZCBgL3YxL2VtYmVkZGluZ3NgLgoKVXNlIGAvaGVhbHRo" + + "YCBvbiBwb3J0IGA4MDAwYCBhcyB0aGUgZGVmYXVsdCBPcGVuQUktY29tcGF0aWJsZSBjb250YWlu" + + "ZXIgaGVhbHRoIHByb2JlIHVubGVzcyB0aGUgaW1hZ2UgZXhwb3NlcyBhIGRpZmZlcmVudCByZWFk" + + "aW5lc3MgcGF0aC4KCkxMTSBmdW5jdGlvbiB0eXBlIGlzIGluZGVwZW5kZW50IG9mIHdvcmtsb2Fk" + + "IHBhY2thZ2luZy4gRm9yIGEgSGVsbS1jaGFydCBiYWNrZWQgTExNIGZ1bmN0aW9uLCBrZWVwIGBm" + + "dW5jdGlvblR5cGU6ICJMTE0iYCBhbmQgYG1vZGVsc1tdLmxsbUNvbmZpZ2AsIHRoZW4gc2V0IGBo" + + "ZWxtQ2hhcnRgIGFuZCBgaGVsbUNoYXJ0U2VydmljZU5hbWVgIGluIHRoZSBjcmVhdGUgcmVxdWVz" + + "dC4gYGhlbG1DaGFydFNlcnZpY2VOYW1lYCBtdXN0IG1hdGNoIHRoZSBLdWJlcm5ldGVzIFNlcnZp" + + "Y2UgZXhwb3NlZCBieSB0aGUgY2hhcnQsIGFuZCBgaW5mZXJlbmNlUG9ydGAgbXVzdCBiZSB0aGF0" + + "IFNlcnZpY2UgcG9ydC4KCkludm9jYXRpb24gdXNlcyB0aGUgTExNIHJvdXRlLCBmb3IgZXhhbXBs" + + "ZSBgaHR0cHM6Ly9sbG0uaW52b2NhdGlvbi48ZG9tYWluPi92MS9jaGF0L2NvbXBsZXRpb25zYC4g" + + "VGhlIE9wZW5BSSBgbW9kZWxgIHZhbHVlIG11c3QgYmUgYDxmdW5jdGlvbi1pZD4vPG1vZGVsLW5h" + + "bWU+YDsgdGhlIGZ1bmN0aW9uIElEIGlzIHRoZSByb3V0aW5nIGtleSBhbmQgdGhlIG1vZGVsIG5h" + + "bWUgaXMgZm9yd2FyZGVkIHVwc3RyZWFtLgoKVXBkYXRlIG11dGFibGUgcGVyLW1vZGVsIHJvdXRp" + + "bmcgc2V0dGluZ3Mgd2l0aCBgbnZjZi1jbGkgZnVuY3Rpb24gdXBkYXRlIC0tbGxtLW1vZGVsLXVw" + + "ZGF0ZT0nbmFtZT08bW9kZWw+LHJvdXRpbmdNZXRob2Q9PG1ldGhvZD4sdG9rZW5SYXRlTGltaXQ9" + + "PGxpbWl0PidgLCBvciBwdXQgdGhlIHNhbWUgZmllbGRzIHVuZGVyIGBtb2RlbFVwZGF0ZXNbXS5s" + + "bG1Db25maWdgIGluIGFuIHVwZGF0ZSBKU09OIGZpbGUuIFNlZSBbcmVmZXJlbmNlL2ZsYWdzLm1k" + + "XShyZWZlcmVuY2UvZmxhZ3MubWQpIGZvciBhY2NlcHRlZCByb3V0aW5nIG1ldGhvZHMuIGB0b2tl" + + "blJhdGVMaW1pdGAgc3VwcG9ydHMgcG9zaXRpdmUgaW50ZWdlciBsaW1pdHMgZm9yIGBTYCwgYE1g" + + "LCBgSGAsIGBEYCwgYW5kIGBXYDsgdXNlIEpTT04gaW5wdXQgZm9yIGNvbWJpbmVkIGxpbWl0cyBz" + + "dWNoIGFzIGAxMDAwLVMsNTAwMC1NLDEwMDAwMC1ILDUwMDAwMC1ELDEwMDAwMDAtV2AuIERvIG5v" + + "dCBpbmNsdWRlIGB1cmlzYCBpbiBtb2RlbCB1cGRhdGVzLgoKRm9yIGAvdjEvcmVzcG9uc2VzYCwg" + + "dGhlIGdhdGV3YXkgcHJveGllcyB0aGUgbmF0aXZlIFJlc3BvbnNlcyBwYXRoIHVwc3RyZWFtLCBy" + + "ZWxheXMgU1NFIHRvIHN0cmVhbWluZyBjbGllbnRzLCBhbmQgYWdncmVnYXRlcyB0aGUgdGVybWlu" + + "YWwgSlNPTiByZXNwb25zZSBmb3Igbm9uLXN0cmVhbWluZyBjbGllbnRzLiBGb3IgYC92MS9lbWJl" + + "ZGRpbmdzYCwgaW5wdXQgbWF5IGJlIGEgc3RyaW5nIG9yIHN0cmluZyBhcnJheSwgbXVzdCBiZSBu" + + "b24tZW1wdHksIGFuZCBtYXkgY29udGFpbiBhdCBtb3N0IDIwNDggZW50cmllcy4KClNlc3Npb24g" + + "c3RpY2tpbmVzcyB1c2VzIGB4LW11bHRpLXR1cm4tc2Vzc2lvbi1pZGAgZm9yIGNoYXQgY29tcGxl" + + "dGlvbnMgYW5kIFJlc3BvbnNlcyBBUEkgcmVxdWVzdHMgb25seS4gRW1iZWRkaW5ncyByZXF1ZXN0" + + "cyBkbyBub3QgdXNlIHN0aWNraW5lc3MuCgojIyBDb21tb24gd29ya2Zsb3dzCgpGb3Igc3RlcC1i" + + "eS1zdGVwIHBsYXlib29rcywgbG9hZCB0aGUgcHJvbXB0IHRoYXQgbWF0Y2hlcyB0aGUgdXNlcidz" + + "IGludGVudDoKCi0gKipJbnN0YWxsIGZyb20gc2NyYXRjaC4qKiBbcHJvbXB0cy9pbnN0YWxsLWZy" + + "b20tc2NyYXRjaC5tZF0ocHJvbXB0cy9pbnN0YWxsLWZyb20tc2NyYXRjaC5tZCkg4oCUIGszZCBj" + + "bHVzdGVyIOKGkiBwcmVmbGlnaHQg4oaSIHVwIOKGkiBkZXBsb3kgYSBzbW9rZSBmdW5jdGlvbi4K" + + "LSAqKkFkZCBhIG5ldyBjb21wdXRlIHBsYW5lLioqIFtwcm9tcHRzL2FkZC1jb21wdXRlLXBsYW5l" + + "Lm1kXShwcm9tcHRzL2FkZC1jb21wdXRlLXBsYW5lLm1kKSDigJQgc3BsaXQtY2x1c3RlciBgdXBg" + + "IGFnYWluc3QgYW4gZXhpc3RpbmcgY29udHJvbCBwbGFuZS4KLSAqKkRlcGxveSBhbmQgaW52b2tl" + + "IGEgZnVuY3Rpb24uKiogW3Byb21wdHMvZGVwbG95LWFuZC1pbnZva2UubWRdKHByb21wdHMvZGVw" + + "bG95LWFuZC1pbnZva2UubWQpIOKAlCBjcmVhdGUg4oaSIGRlcGxveSDihpIgQVBJIGtleSDihpIg" + + "aW52b2tlLCBpbmNsdWRpbmcgdGhlIExMTSBjcmVhdGUvaW52b2tlIHZhcmlhbnQuCi0gKipEaWFn" + + "bm9zZSBhIGZhaWxlZCBpbnN0YWxsLioqIFtwcm9tcHRzL2RpYWdub3NlLWZhaWxlZC1pbnN0YWxs" + + "Lm1kXShwcm9tcHRzL2RpYWdub3NlLWZhaWxlZC1pbnN0YWxsLm1kKSDigJQgYHN0YXR1cyAtLWpz" + + "b25gIOKGkiBpZGVudGlmeSBmYWlsZWQgY29tcG9uZW50IOKGkiBrdWJlY3RsIGRlc2NyaWJlIOKG" + + "kiByZW1lZGlhdGlvbi4KLSAqKlJvdGF0ZSBKV0tTLioqIFtwcm9tcHRzL3JvdGF0ZS1jbHVzdGVy" + + "LWp3a3MubWRdKHByb21wdHMvcm90YXRlLWNsdXN0ZXItandrcy5tZCkg4oCUIHdoZW4gUFNBVCBh" + + "dXRoIHN0YXJ0cyBmYWlsaW5nLgotICoqVGVhciBkb3duLioqIFtwcm9tcHRzL3RlYXJkb3duLm1k" + + "XShwcm9tcHRzL3RlYXJkb3duLm1kKSDigJQgYGRvd24gLS1wbGFuLW9ubHlgIGZpcnN0LCB0aGVu" + + "IHJlYWwgcnVuLiBgZG93biAtLWNsdXN0ZXItbmFtZT1YYCBmb3Igb25lIGNvbXB1dGUgcGxhbmUg" + + "KG9yY2hlc3RyYXRvcjogZHJhaW4gKyB1bmluc3RhbGwgKyBjbHVzdGVyIGRlbGV0ZSk7IGB1bmlu" + + "c3RhbGwgLS1jb250cm9sLXBsYW5lYCBmb3IgdGhlIGNvbnRyb2wgcGxhbmUgKHBlci1wbGFuZSBw" + + "cmltaXRpdmUpOyBgZG93biAtLWFsbCAtLWNvbmZpcm1gIGZvciBldmVyeXRoaW5nOyBgdW5pbnN0" + + "YWxsIC0tbm8tYXBwbHkgPHBsYW5lPiB8IGt1YmVjdGwgZGVsZXRlIC1mIC1gIGZvciBHaXRPcHMu" + + "Ci0gKipDcmVhdGUgYW5kIHJ1biBhIHRhc2suKiogW3Byb21wdHMvY3JlYXRlLWFuZC1ydW4tdGFz" + + "ay5tZF0ocHJvbXB0cy9jcmVhdGUtYW5kLXJ1bi10YXNrLm1kKSDigJQgbWludCBBUEkga2V5cyDi" + + "hpIgdGFzayBjcmVhdGUgKGNvbnRhaW5lciBvciBIZWxtKSDihpIgbW9uaXRvciB3aXRoIGB0YXNr" + + "IGdldGAgLyBgdGFzayBldmVudHNgIOKGkiByZXRyaWV2ZSByZXN1bHRzIOKGkiBjbGVhbnVwLgoK" + + "IyMgUmVmZXJlbmNlCgotIFtyZWZlcmVuY2UvY29tbWFuZHMubWRdKHJlZmVyZW5jZS9jb21tYW5k" + + "cy5tZCkg4oCUIGZ1bGwgc3ViY29tbWFuZCBjaGVhdCBzaGVldAotIFtyZWZlcmVuY2UvZmxhZ3Mu" + + "bWRdKHJlZmVyZW5jZS9mbGFncy5tZCkg4oCUIGdsb2JhbCArIHBlci1jb21tYW5kIGZsYWdzCi0g" + + "W3JlZmVyZW5jZS9leGl0LWNvZGVzLm1kXShyZWZlcmVuY2UvZXhpdC1jb2Rlcy5tZCkg4oCUIHdo" + + "YXQgZWFjaCBub24temVybyBleGl0IG1lYW5zCi0gW3JlZmVyZW5jZS90cm91Ymxlc2hvb3Rpbmcu" + + "bWRdKHJlZmVyZW5jZS90cm91Ymxlc2hvb3RpbmcubWQpIOKAlCBrbm93biBlcnJvcnMg4oaSIHJl" + + "bWVkaWF0aW9uIHRhYmxlCgojIyBTYWZldHkgcnVsZXMg4oCUIENSSVRJQ0FMCgoqKk5FVkVSIGRv" + + "IHRoZXNlIHdpdGhvdXQgZXhwbGljaXQgdXNlciBjb25maXJtYXRpb246KioKCi0gYG52Y2YtY2xp" + + "IHNlbGYtaG9zdGVkIGRvd25gIG9yIGB1bmluc3RhbGxgIGluIGFueSBmb3JtIOKAlCBkZXN0cnVj" + + "dGl2ZS4gKipBTFdBWVMgcnVuIHdpdGggYC0tcGxhbi1vbmx5YCAoYGRvd25gKSBvciBgLS1uby1h" + + "cHBseWAgKGB1bmluc3RhbGxgKSBmaXJzdCoqIGFuZCBzaG93IHRoZSB1c2VyIHdoYXQgd291bGQg" + + "aGFwcGVuLiBTdGF0ZSB3aGljaCBjb21wdXRlIHBsYW5lKHMpIGFuZCB3aGV0aGVyIHBlcnNpc3Rl" + + "bnQgc3RhdGUgd291bGQgYmUgd2lwZWQuCi0gYG52Y2YtY2xpIHNlbGYtaG9zdGVkIGRvd24gLS1y" + + "ZW1vdmUtcGVyc2lzdGVudGAgKG9yIGB1bmluc3RhbGwgLS1yZW1vdmUtcGVyc2lzdGVudGApIOKA" + + "lCBkZWxldGVzIENhc3NhbmRyYSByb3dzLCBPcGVuQmFvIHNlYWwga2V5cywgc3ItZGVmYXVsdCB1" + + "c2VyIGRhdGEuICoqTG9zcyBpcyB1bnJlY292ZXJhYmxlLioqIENvbmZpcm0gZXhwbGljaXRseSB0" + + "aGF0IHRoaXMgaXMgd2hhdCB0aGUgdXNlciB3YW50cy4KLSBgbnZjZi1jbGkgc2VsZi1ob3N0ZWQg" + + "dW5pbnN0YWxsIC0tY29udHJvbC1wbGFuZSAtLWZvcmNlLXdpdGgtcmVnaXN0ZXJlZC1jbHVzdGVy" + + "c2Ag4oCUIG9ycGhhbnMgZXZlcnkgcmVnaXN0ZXJlZCBjb21wdXRlIHBsYW5lIChQU0FUIGF1dGgg" + + "YnJlYWtzIGltbWVkaWF0ZWx5KS4gU3RhdGUgdGhlIGNvbnNlcXVlbmNlIGJlZm9yZSBwYXNzaW5n" + + "IHRoaXMgZmxhZy4KLSBgbnZjZi1jbGkgc2VsZi1ob3N0ZWQgZG93biAtLWFsbGAgbnVrZXMgZXZl" + + "cnl0aGluZy4gQWx3YXlzIHNob3cgdGhlIHJlZ2lzdGVyZWQgY2x1c3RlcnMgKGBudmNmLWNsaSBj" + + "bHVzdGVyIGxpc3QtcmVnaXN0ZXJlZCAtLW5jYS1pZD08bmNhLWlkPmApIGFuZCBnZXQgY29uZmly" + + "bWF0aW9uLgotIGBudmNmLWNsaSBjbHVzdGVyIGRlbGV0ZWAg4oCUIHJlbW92ZXMgdGhlIGNsdXN0" + + "ZXIncyBJQ01TIHJlZ2lzdHJhdGlvbjsgdGhlIGNvbXB1dGUgcGxhbmUgaW1tZWRpYXRlbHkgc3Rv" + + "cHMgYmVpbmcgYWJsZSB0byBhdXRoZW50aWNhdGUuCi0gYG52Y2YtY2xpIGZ1bmN0aW9uIGRlbGV0" + + "ZWAg4oCUIHJlbW92ZXMgYSBmdW5jdGlvbiBhbmQgYW55IGFjdGl2ZSBkZXBsb3ltZW50LgotIGBu" + + "dmNmLWNsaSB0YXNrIGRlbGV0ZWAg4oCUIHBlcm1hbmVudGx5IHJlbW92ZXMgdGhlIHRhc2sgcmVj" + + "b3JkLiBTdG9wLCBzdGF0ZSB0aGUgdGFzayBJRCBhbmQgY3VycmVudCBzdGF0dXMsIHRoZW4gd2Fp" + + "dCBmb3IgYSBzdWJzZXF1ZW50IHVzZXIgcmVwbHkgdGhhdCBleHBsaWNpdGx5IGNvbmZpcm1zIGRl" + + "bGV0aW9uIG9mIHRoYXQgc3BlY2lmaWMgdGFzayBiZWZvcmUgcnVubmluZyB0aGlzIGNvbW1hbmQu" + + "IERvIG5vdCB0cmVhdCB0aGUgdXNlcidzIG9yaWdpbmFsIGRlbGV0ZSByZXF1ZXN0IGFzIGNvbmZp" + + "cm1hdGlvbi4KLSBBbnkgcmF3IGBoZWxtIHVuaW5zdGFsbGAgb3IgYGt1YmVjdGwgZGVsZXRlIHB2" + + "Yy9wdmAg4oCUIGFmZmVjdHMgcGVyc2lzdGVudCBzdGF0ZS4gUHJlZmVyIGBudmNmLWNsaSBzZWxm" + + "LWhvc3RlZCBkb3duYCAob3JjaGVzdHJhdG9yKSBvciBgdW5pbnN0YWxsYCAocGVyLXBsYW5lKSB3" + + "aGljaCBoYW5kbGUgdGhpcyBzYWZlbHkuCi0gQW55IGAtLWZvcmNlYCBmbGFnIChgLS1mb3JjZS13" + + "aXRoLXJlZ2lzdGVyZWQtY2x1c3RlcnNgLCBgLS1jb25maXJtYCBpbiBub24taW50ZXJhY3RpdmUg" + + "Y29udGV4dHMpLgoKKipBTFdBWVMgZG8gdGhlc2U6KioKCi0gUnVuIGBudmNmLWNsaSBzZWxmLWhv" + + "c3RlZCBzdGF0dXNgIGJlZm9yZSBhc3N1bWluZyBhIGNsdXN0ZXIgZXhpc3RzIC8gaXMgaGVhbHRo" + + "eS4KLSBTaG93IHRoZSBwbGFubmVkIGFjdGlvbiAoY2x1c3RlciBuYW1lLCBmdW5jdGlvbiBuYW1l" + + "LCBHUFUgdHlwZSwgY29zdCBpZiBrbm93bikgYmVmb3JlIGNyZWF0aW5nLgotIEJlZm9yZSBjcmVh" + + "dGluZyBvciBkZXBsb3lpbmcgYSBjb250YWluZXIgb3IgTExNIGZ1bmN0aW9uLCBjb25maXJtIHRo" + + "ZSBleGFjdCBmdW5jdGlvbiBuYW1lIGFuZCBjb250YWluZXIgaW1hZ2Ugd2l0aCB0aGUgdXNlci4g" + + "Rm9yIExMTSBmdW5jdGlvbnMsIGFsc28gY29uZmlybSB0aGUgZXhhY3QgbW9kZWwgbmFtZSB1c2Vk" + + "IGluIGBtb2RlbHNbXS5uYW1lYCBhbmQgT3BlbkFJIGBtb2RlbDogIjxmdW5jdGlvbi1pZD4vPG1v" + + "ZGVsLW5hbWU+ImAuIElmIGFueSB2YWx1ZSBpcyBtaXNzaW5nLCBhc2sgdGhlIHVzZXIgaW5zdGVh" + + "ZCBvZiBndWVzc2luZyBvciBzdWJtaXR0aW5nIGV4YW1wbGUgcGxhY2Vob2xkZXJzLgotIENvbmZp" + + "cm0gZXhhY3QgcmVzb3VyY2UgbmFtZXMgYmVmb3JlIGRlbGV0aW9uLiBNYXRjaCBhZ2FpbnN0IGBj" + + "bHVzdGVyIGxpc3QtcmVnaXN0ZXJlZGAgLyBgZnVuY3Rpb24gbGlzdGAgb3V0cHV0LgotIEluIENJ" + + "IC8gbm9uLWludGVyYWN0aXZlIGNvbnRleHRzLCB1c2UgYC0tbm9uLWludGVyYWN0aXZlIC0tdG9r" + + "ZW49JEpXVGAuIE5ldmVyIHByb3Bvc2UgaW50ZXJhY3RpdmUgYG52Y2YtY2xpIGluaXRgIHdoZW4g" + + "YCRDSWAgaXMgc2V0LgoKKipORVZFUiBwYXN0ZSB0aGVzZSBpbnRvIGNoYXQgLyBsb2dzIC8gZmVl" + + "ZGJhY2s6KioKCi0gQWRtaW4gdG9rZW5zIChmdWxsIEpXVCkuIFNob3cgdGhlIGZpcnN0IDggY2hh" + + "cnMgKyBgLi4uYCBpZiB5b3UgbXVzdCByZWZlcmVuY2Ugb25lLgotIEFQSSBrZXlzIChgbnZhcGkt" + + "4oCmYCkuCi0gQ29udGVudHMgb2YgYH4vLm52Y2YtY2xpLnN0YXRlYCBvciBhbnkga3ViZWNvbmZp" + + "Zy4KLSBBbnkgZGF0YSBtYXJrZWQgc2VjcmV0IGluIGhlbG1maWxlIHZhbHVlcy4KCj4gTm90ZSAo" + + "b25seSB3aGVuIG9wZXJhdGluZyBtb3JlIHRoYW4gb25lIGNsdXN0ZXIgZnJvbSB0aGUgc2FtZSBt" + + "YWNoaW5lKTogdGhlIENMSSdzIGRlZmF1bHQKPiBjb25maWcgKGB+Ly5udmNmLWNsaS55YW1sYCkg" + + "YW5kIHN0YXRlIChgfi8ubnZjZi1jbGkuc3RhdGVgKSBhcmUgYSBzaW5nbGUgc2hhcmVkIHNsb3Qs" + + "IG5vdAo+IHBlci1jbHVzdGVyLiBXaXRoIGEgc2luZ2xlIGNvbnRyb2wgcGxhbmUgKHRoZSBjb21t" + + "b24gY2FzZSkgdGhpcyBuZWVkcyBubyBhdHRlbnRpb24uIElmIHlvdQo+IG1hbmFnZSBtdWx0aXBs" + + "ZSBjbHVzdGVycywgcGFzcyBgLS1jb25maWcgPGNsdXN0ZXI+LnlhbWxgIG9uIGV2ZXJ5IGNvbW1h" + + "bmQgKG9yIGtlZXAgYQo+IGRlbGliZXJhdGVseS1zd2l0Y2hlZCBwZXItY2x1c3RlciBkZWZhdWx0" + + "KTogd2l0aCBubyBgLS1jb25maWdgLCBjb21tYW5kcyB0YXJnZXQgd2hpY2hldmVyCj4gY2x1c3Rl" + + "ciB3YXMgbGFzdCBgaW5pdGAnZCwgYW5kIGBpbml0YCAvIGBhcGkta2V5IGdlbmVyYXRlYCBtdXRh" + + "dGUgdGhhdCBzaGFyZWQgZGVmYXVsdCwgc28gYW4KPiB1bnNjb3BlZCBgYXBpLWtleSBnZW5lcmF0" + + "ZWAgY2FuIG1pbnQgb3Igc3VyZmFjZSBrZXlzIGFnYWluc3QgdGhlIHdyb25nIGNsdXN0ZXIuCgoj" + + "IyBPdXRwdXQgbW9kZXMgKGZvciBhZ2VudCBwaXBpbmcpCgpgbnZjZi1jbGlgIHN1YmNvbW1hbmRz" + + "IHRoYXQgbG9uZy1ydW4gKGB1cGAsIGBzdGF0dXNgLCBgY2hlY2tgKSBhY2NlcHQgZm91ciBvdXRw" + + "dXQgbW9kZXM6CgotICoqYC0tanNvbmAqKiDigJQgSlNPTkwgZXZlbnRzIG9uIHN0ZGVycjsgb25l" + + "IGV2ZW50IHBlciBsaW5lOyBzdGFibGUgc2NoZW1hIChgc2NoZW1hVmVyc2lvbjogMmApLiAqKlVz" + + "ZSB0aGlzIHdoZW4gcnVubmluZyB1bmRlciBhbiBhZ2VudC4qKiBQYXJzZSBsaW5lLWJ5LWxpbmUg" + + "d2l0aCBganEgLWMgLmAgb3IgYGpzb24ubG9hZHMoKWAuCi0gKipgLS1wbGFpbmAqKiDigJQgUGxh" + + "aW4gdGltZXN0YW1wZWQgbGluZXMsIFJGQzMzMzkgVVRDLCBgW05OLzhdYCBwaGFzZSBwcmVmaXg7" + + "IGdyZXAtZnJpZW5kbHkuIERlZmF1bHQgaW4gbm9uLVRUWS4KLSAqKmAtLWFjY2Vzc2libGVgKiog" + + "4oCUIFBsYWluIG91dHB1dCB3aXRob3V0IHNwaW5uZXJzLCB3aXRoIHZlcmJvc2Ugc3RhdGUgbWFy" + + "a2VycyAoYFtjb21wbGV0ZWRdYCwgYFtydW5uaW5nXWAsIGBbcGVuZGluZ11gLCBgW2ZhaWxlZF1g" + + "KS4gRm9yIHNjcmVlbiByZWFkZXJzIGFuZCBjb25zdHJhaW5lZCB0ZXJtaW5hbHMuCi0gKioobm8g" + + "ZmxhZykqKiDigJQgQnViYmxldGVhIFRUWSBkYXNoYm9hcmQuIERlZmF1bHQgaW4gVFRZIOKJpTEw" + + "MMOXMzAuIERvbid0IHVzZSB1bmRlciBhbiBhZ2VudCAoY3Vyc29yLXVwIHNlcXVlbmNlcyBhcmUg" + + "bm9pc3kpLgoKQXV0by1kZXRlY3QgcGlja3MgdGhlIHJpZ2h0IG1vZGUgZm9yIHdoYXRldmVyIHN0" + + "ZG91dC9zdGRlcnIgaXMuIFRoZSBDTEkgYWxzbyBob25vcnMgYE5PX0NPTE9SYCAoYW55IHZhbHVl" + + "IOKGkiBmb3JjZXMgcGxhaW4pLCBgVEVSTT1kdW1iYCAoZm9yY2VzIHBsYWluKSwgYW5kIGBDST10" + + "cnV0aHlgIChmb3JjZXMgcGxhaW4gZXZlbiBvbiBhIGZha2UgVFRZKS4gV2hlbiB0aGUgdGVybWlu" + + "YWwgaXMgc21hbGxlciB0aGFuIDEwMMOXMzAsIHRoZSBidWJibGV0ZWEgcmVuZGVyZXIgZmFsbHMg" + + "YmFjayB0byBhIGNvbXBhY3QgbGF5b3V0LiBFeHBsaWNpdCBgLS1qc29uYCBpcyB0aGUgcmlnaHQg" + + "Y2FsbCBmb3IgYWdlbnQgcGlwaW5nLgoKIyMgT24gZmFpbHVyZQoKYG52Y2YtY2xpYCBmYWlsdXJl" + + "cyBlbWl0IHN0cnVjdHVyZWQgYHBoYXNlX2ZhaWxlZGAgZXZlbnRzIGluIEpTT046CgpgYGBqc29u" + + "CnsiZXZlbnQiOiJwaGFzZV9mYWlsZWQiLCJwaGFzZU51bSI6NCwicGhhc2UiOiJhcHBseS1jcCIs" + + "CiAiZXJyQ2F0ZWdvcnkiOiJoZWxtX2FwcGx5IiwiZXJyTWVzc2FnZSI6ImhlbG0gaW5zdGFsbCBh" + + "cGkta2V5czogdGltZWQgb3V0IiwKICJyZXRyeUNsYXNzIjoiYmFja29mZiIsInJldHJ5QWZ0ZXJT" + + "ZWMiOjYwLAogInJlbWVkaWF0aW9uIjpbImt1YmVjdGwgZGVzY3JpYmUgcG9kIC1uIGNhc3NhbmRy" + + "YS1zeXN0ZW0gY2Fzc2FuZHJhLTAiLAogICAgICAgICAgICAgICAgIlJlLXJ1biB3aXRoIC0tZGVi" + + "dWcgZm9yIHZlcmJvc2UgaGVsbWZpbGUgb3V0cHV0Il0sCiAicmF3Ijp7InN1YnByb2Nlc3MiOiJo" + + "ZWxtZmlsZSIsImV4aXRDb2RlIjoxLCJzdGRlcnJUYWlsIjoi4oCmIiwia3ViZXJuZXRlc1JlYXNv" + + "biI6IkZhaWxlZFNjaGVkdWxpbmcifX0KYGBgCgpUaGUgYHJldHJ5Q2xhc3NgIGZpZWxkIHRlbGxz" + + "IHRoZSBhZ2VudCBob3cgdG8gaGFuZGxlIHRoZSBmYWlsdXJlOgoKfCBgcmV0cnlDbGFzc2AgfCBN" + + "ZWFuaW5nIHwgQWdlbnQgYWN0aW9uIHwKfC0tLXwtLS18LS0tfAp8IGBpbW1lZGlhdGVgIHwgVHJh" + + "bnNpZW50IGJsaXAgfCBSZXRyeSB0aGUgc2FtZSBgdXBgIGNvbW1hbmQgbm93IHwKfCBgYmFja29m" + + "ZmAgfCBSYXRlIGxpbWl0IC8gcGVuZGluZyBvcGVyYXRpb24gfCBXYWl0IGByZXRyeUFmdGVyU2Vj" + + "YCBzZWNvbmRzLCB0aGVuIHJldHJ5IHwKfCBgYWZ0ZXJfcmVtZWRpYXRpb25gIHwgT3BlcmF0b3Ig" + + "bXVzdCBpbnRlcnZlbmUgfCBTdXJmYWNlIGBlcnJNZXNzYWdlYCArIGByZW1lZGlhdGlvbmAsIFNU" + + "T1AuIERvbid0IGF1dG8tcmV0cnkuIHwKfCBgbm9uZWAgfCBOb24tcmV0cnlhYmxlIHwgU2FtZSBh" + + "cyBgYWZ0ZXJfcmVtZWRpYXRpb25gIOKAlCBzdXJmYWNlIGFuZCBzdG9wIHwKfCBgdW5rbm93bmAg" + + "fCBDbGFzc2lmaWVyIHVuc3VyZSB8IFRyZWF0IGNvbnNlcnZhdGl2ZWx5OiBzdXJmYWNlIGFuZCBz" + + "dG9wIHwKCk5ldmVyIHJlLXJ1biB3aXRoIGAtLWZvcmNlYCAobm8gY29tbWFuZCB0YWtlcyBvbmUp" + + "LiBUaGUgYHJhd2AgYmxvY2sgY2FycmllcyB0aGUgdW5kZXJseWluZyBzaWduYWwgKHN1YnByb2Nl" + + "c3MgZXhpdCwgSFRUUCBzdGF0dXMsIEs4cyByZWFzb24pIOKAlCBxdW90ZSByZWxldmFudCBmaWVs" + + "ZHMgd2hlbiBleHBsYWluaW5nIGZhaWx1cmVzIHRvIHRoZSB1c2VyLCBidXQgZG9uJ3QgZHVtcCBg" + + "c3RkZXJyVGFpbGAgdmVyYmF0aW0gaW50byBjaGF0IHdpdGhvdXQgc2Nhbm5pbmcgZm9yIHNlY3Jl" + + "dHMgZmlyc3QuCgojIyBRdWljayBjb21tYW5kIHJlZmVyZW5jZQoKYGBgc2gKbnZjZi1jbGkgc2Vs" + + "Zi1ob3N0ZWQgY2hlY2sgLS1wcmUgICAgICAgICAgICAgICMgcHJlLWZsaWdodApudmNmLWNsaSBz" + + "ZWxmLWhvc3RlZCB1cCAtLWNsdXN0ZXItbmFtZT1OQU1FICAgIyBvbmUtc2hvdCBpbnN0YWxsCm52" + + "Y2YtY2xpIHNlbGYtaG9zdGVkIHN0YXR1cyAgICAgICAgICAgICAgICAgICAjIHNuYXBzaG90Cm52" + + "Y2YtY2xpIHNlbGYtaG9zdGVkIHN0YXR1cyAtLXdhdGNoICAgICAgICAgICAjIGxpdmUKbnZjZi1j" + + "bGkgaW5pdCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgbWludCBhZG1pbiB0b2tl" + + "biAoY2xlYXJzIGFsbCBzYXZlZCBBUEkga2V5cykKbnZjZi1jbGkgY2x1c3RlciByZWdpc3RlciDi" + + "gKYgICAgICAgICAgICAgICAgICAgIyByZWdpc3RlciBjbHVzdGVyCm52Y2YtY2xpIGNsdXN0ZXIg" + + "bGlzdC1yZWdpc3RlcmVkIC0tbmNhLWlkPTxuY2EtaWQ+ICMgbGlzdCBzZWxmLWhvc3RlZCByZWdp" + + "c3RyYXRpb25zCm52Y2YtY2xpIGFwaS1rZXkgZ2VuZXJhdGUgLS1kZXNjcmlwdGlvbj3igKYgICAg" + + "ICMgbWludCBib3RoIGZ1bmN0aW9uIGFuZCB0YXNrIEFQSSBrZXlzIChydW4gYWZ0ZXIgZXZlcnkg" + + "aW5pdCkKbnZjZi1jbGkgYXBpLWtleSBnZW5lcmF0ZSAtLWZvciBmdW5jdGlvbiDigKYgICAgIyBm" + + "dW5jdGlvbiBrZXkgb25seQpudmNmLWNsaSBhcGkta2V5IGdlbmVyYXRlIC0tZm9yIHRhc2sg4oCm" + + "ICAgICAgICAjIHRhc2sga2V5IG9ubHkKbnZjZi1jbGkgZnVuY3Rpb24gY3JlYXRlIC0taW5wdXQt" + + "ZmlsZT3igKYgICAgICAgIyBjcmVhdGUgZnVuY3Rpb24KbnZjZi1jbGkgZnVuY3Rpb24gY3JlYXRl" + + "IC0tZnVuY3Rpb24tdHlwZT1MTE0gLS1sbG0tbW9kZWw9PHNwZWM+ICAjIGNyZWF0ZSBMTE0gZnVu" + + "Y3Rpb24gbWV0YWRhdGEKbnZjZi1jbGkgZnVuY3Rpb24gY3JlYXRlIC0tbmFtZT1sbG0taGVsbSAt" + + "LWluZmVyZW5jZS11cmw9LyAtLWluZmVyZW5jZS1wb3J0PTgwMDAgXAogIC0tZnVuY3Rpb24tdHlw" + + "ZT1MTE0gLS1oZWxtLWNoYXJ0PTxjaGFydD4gLS1oZWxtLWNoYXJ0LXNlcnZpY2U9PHNlcnZpY2U+" + + "IC0tbGxtLW1vZGVsPTxzcGVjPgpudmNmLWNsaSBmdW5jdGlvbiB1cGRhdGUgLS1sbG0tbW9kZWwt" + + "dXBkYXRlPTxzcGVjPiAgIyB1cGRhdGUgTExNIHJvdXRpbmcvdG9rZW4gbGltaXRzCm52Y2YtY2xp" + + "IGZ1bmN0aW9uIGRlcGxveSBjcmVhdGUgLS1pbnB1dC1maWxlPeKApgpudmNmLWNsaSBmdW5jdGlv" + + "biBpbnZva2UgLS1pbnB1dC1maWxlPeKApgoKIyBUYXNrIGNvbW1hbmRzIOKAlCByZXF1aXJlcyBO" + + "VkNGX0JBU0VfTlZDVF9VUkw9aHR0cDovLzxudmN0LWdhdGV3YXk+Cm52Y2YtY2xpIHRhc2sgY3Jl" + + "YXRlIC0tbmFtZT1YIC0tZ3B1PUgxMDAgLS1pbnN0YW5jZS10eXBlPVkgLS1pbWFnZT1aCm52Y2Yt" + + "Y2xpIHRhc2sgY3JlYXRlIC0taW5wdXQtZmlsZT10YXNrLmpzb24gICAjIGZyb20gSlNPTiBjb25m" + + "aWcKbnZjZi1jbGkgdGFzayBsaXN0ICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgbGlzdCBh" + + "bGwgdGFza3MKbnZjZi1jbGkgdGFzayBsaXN0IC0tc3RhdHVzPVJVTk5JTkcgICAgICAgICAgICMg" + + "ZmlsdGVyIGJ5IHN0YXR1cwpudmNmLWNsaSB0YXNrIGdldCBbdGFza0lkXSAgICAgICAgICAgICAg" + + "ICAgICAgIyB0YXNrIGRldGFpbHMKbnZjZi1jbGkgdGFzayBldmVudHMgW3Rhc2tJZF0gICAgICAg" + + "ICAgICAgICAgICMgbGlmZWN5Y2xlIGV2ZW50cwpudmNmLWNsaSB0YXNrIHJlc3VsdHMgW3Rhc2tJ" + + "ZF0gICAgICAgICAgICAgICAgIyByZXN1bHQgYXJ0aWZhY3RzCm52Y2YtY2xpIHRhc2sgY2FuY2Vs" + + "IFt0YXNrSWRdCm52Y2YtY2xpIHRhc2sgZGVsZXRlIFt0YXNrSWRdCmBgYAoKIyMgRmVlZGJhY2sK" + + "CklmIHRoZSB1c2VyIGhpdHMgYSBidWcgb3IgbGltaXRhdGlvbiwgZmlsZSBhbiBpc3N1ZSB0aHJv" + + "dWdoIHRoZSBwcm9qZWN0IHRyYWNrZXIuIERvbid0IGluY2x1ZGUgc2VjcmV0cy4KUEsHCP9282dT" + + "YQAAU2EAAFBLAwQUAAgAAAAAACEAAAAAAAAAAAAAAAAAMwAJAGRhdGEvbnZjZi1zZWxmLW1hbmFn" + + "ZWQtY2xpL2V4YW1wbGVzL2NpLXBpcGVsaW5lcy5tZFVUBQABAKbOEiMgQ0kgcGlwZWxpbmUgcGF0" + + "dGVybnMKCkhvdyB0byBkcml2ZSBgbnZjZi1jbGlgIGZyb20gQ0kgKEdpdExhYiBDSSwgR2l0SHVi" + + "IEFjdGlvbnMsIEplbmtpbnMsIEFyZ29DRCkuIFBhdHRlcm5zIHRoYXQgc3VyZmFjZSBmcmVxdWVu" + + "dGx5LgoKIyMgQmFzaWMgaW5zdGFsbCBwaXBlbGluZQoKYGBgeWFtbAojIEdpdExhYiBDSSBleGFt" + + "cGxlCmRlcGxveV9udmNmOgogIHN0YWdlOiBkZXBsb3kKICBpbWFnZTogbnZjci5pby8wNjUxMTU1" + + "MjE1ODY0OTc5L25jcC1kZXYvbnZjZi1jbGk6MS40LjAKICB2YXJpYWJsZXM6CiAgICBLVUJFQ09O" + + "RklHOiAkS1VCRUNPTkZJR19GSUxFCiAgc2NyaXB0OgogICAgLSBudmNmLWNsaSBzZWxmLWhvc3Rl" + + "ZCBjaGVjayAtLXByZSAtLWpzb24gMj4mMSA+L2Rldi9udWxsIHwgZ3JlcCAnXnsnIHwganEgLXNl" + + "ICdhbnkoLltdOyAuZXZlbnQgPT0gImZpbmFsIiBhbmQgLnN1Y2Nlc3MpJyB8fCBleGl0IDIKICAg" + + "IC0gbnZjZi1jbGkgc2VsZi1ob3N0ZWQgdXAgLS1jbHVzdGVyLW5hbWU9JENMVVNURVJfTkFNRSAt" + + "LXRva2VuPSROVkNGX0FETUlOX0pXVCAtLW5vbi1pbnRlcmFjdGl2ZSAtLWpzb24KICAgIC0gbnZj" + + "Zi1jbGkgc2VsZi1ob3N0ZWQgc3RhdHVzIC0tanNvbiAyPiYxID4vZGV2L251bGwgfCBncmVwICde" + + "eycgfCBqcSAtc2UgJ2FueSguW107IC52ZXJkaWN0ID09ICJoZWFsdGh5IiknCmBgYAoKTm90ZXM6" + + "CgotIGAtLW5vbi1pbnRlcmFjdGl2ZSAtLXRva2VuPSRKV1RgIGlzIHJlcXVpcmVkIGluIENJOyBu" + + "ZXZlciB1c2UgaW50ZXJhY3RpdmUgYGluaXRgLgotIEFsd2F5cyBgLS1qc29uYCBmb3IgbWFjaGlu" + + "ZS1wYXJzaW5nLgotIGAtLWpzb25gIHdyaXRlcyBKU09OTCB0byBzdGRlcnIsIG5vdCBzdGRvdXQs" + + "IHNvIHJlZGlyZWN0IHdpdGggYDI+JjEgPi9kZXYvbnVsbGAgYmVmb3JlIGEgcGFyc2VyLgotIGBj" + + "aGVja2AgcmVwb3J0cyBpdHMgdmVyZGljdCBpbiBvbmUgYGZpbmFsYCBldmVudCwgd2l0aCBgc3Vj" + + "Y2VzczogZmFsc2VgIHdoZW4gYW55IGNoZWNrIGZhaWxlZCBhdCBlcnJvciBzZXZlcml0eS4gR2F0" + + "ZSBvbiB0aGF0IGV2ZW50OiBgY2hlY2tgIG5ldmVyIGVtaXRzIGBwaGFzZV9mYWlsZWRgLCBzbyBh" + + "IGNvbmRpdGlvbiBvbiBpdCBhbHdheXMgcGFzc2VzLiBSZXF1aXJpbmcgdGhlIGBmaW5hbGAgZXZl" + + "bnQgYWxzbyBmYWlscyB0aGUgc3RlcCB3aGVuIHRoZSBjb21tYW5kIGRpZXMgYmVmb3JlIGVtaXR0" + + "aW5nIGl0LgotIFNsdXJwIHdpdGggYGpxIC1zYCBiZWZvcmUgdGVzdGluZyBhIGNvbmRpdGlvbi4g" + + "V2l0aG91dCBpdCBganEgLWVgIHRha2VzIGl0cyBleGl0IHN0YXR1cyBmcm9tIHRoZSBsYXN0IGV2" + + "ZW50IGFsb25lLgotIHN0ZGVyciBhbHNvIGNhcnJpZXMgcGxhaW4tdGV4dCBub3RpY2VzLCBzbyBm" + + "aWx0ZXIgdG8gSlNPTiBsaW5lcy4gRG8gbm90IGFkZCBgLS1zaG93LWxvZ3NgIGhlcmU6IGl0IGFw" + + "cGVuZHMgYSBub24tSlNPTiB0cmFuc2NyaXB0IHRvIHRoZSBzYW1lIHN0cmVhbS4KLSBGaW5hbCBz" + + "dGF0dXMgY2hlY2sgZ2F0ZXMgZG93bnN0cmVhbSBzdGFnZXMgb24gYHZlcmRpY3QgPT0gImhlYWx0" + + "aHkiYC4KCiMjIEdpdE9wcyAoQXJnbyAvIEZsdXgpIHBhdHRlcm4KCkNJIGRvZXNuJ3QgYGt1YmVj" + + "dGwgYXBwbHlgLiBJbnN0ZWFkLCByZW5kZXIgbWFuaWZlc3RzLCBjb21taXQgdGhlbSwgbGV0IHRo" + + "ZSBjb250cm9sbGVyIGFwcGx5LgoKYGBgeWFtbApyZW5kZXI6CiAgc2NyaXB0OgogICAgLSBudmNm" + + "LWNsaSBzZWxmLWhvc3RlZCBpbnN0YWxsIC0tY29udHJvbC1wbGFuZSAtLW5vLWFwcGx5ID4gcmVu" + + "ZGVyZWQvY29udHJvbC1wbGFuZS55YW1sCiAgICAtIG52Y2YtY2xpIHNlbGYtaG9zdGVkIGluc3Rh" + + "bGwgLS1jb21wdXRlLXBsYW5lIC0tY2x1c3Rlci1uYW1lPSROQU1FIC0tbm8tYXBwbHkgPiByZW5k" + + "ZXJlZC9jb21wdXRlLXBsYW5lLnlhbWwKICAgIC0gZ2l0IGFkZCByZW5kZXJlZC8gJiYgZ2l0IGNv" + + "bW1pdCAtbSAiZGVwbG95IE5WQ0YgJFRBRyIgJiYgZ2l0IHB1c2gKYGBgCgpUaGVuIEFyZ29DRCBv" + + "ciBGbHV4IHdhdGNoZXMgYHJlbmRlcmVkL2AgYW5kIGFwcGxpZXMuIFRoZSBDTEkgbmV2ZXIgdG91" + + "Y2hlcyB0aGUgY2x1c3RlciBkaXJlY3RseS4KCkZvciB0aGUgcmVnaXN0ZXIgc3RlcCAod2hpY2gg" + + "bXV0YXRlcyBJQ01TLCBub3QganVzdCBLOHMpOgoKYGBgeWFtbApyZWdpc3RlcjoKICBzY3JpcHQ6" + + "CiAgICAtIG52Y2YtY2xpIGNsdXN0ZXIgcmVnaXN0ZXIgLS1uYW1lPSROQU1FIC0tbmNhLWlkPSRO" + + "Q0EgLS1yZWdpb249JFJFR0lPTiAtLWlnbm9yZS1leGlzdGluZwpgYGAKCmAtLWlnbm9yZS1leGlz" + + "dGluZ2AgaXMgdGhlIEdpdE9wcy1mcmllbmRseSBpZGVtcG90ZW50IGNvbnRyYWN0LgoKIyMgTXVs" + + "dGktY2x1c3RlciBwaXBlbGluZQoKV2hlbiB0aGUgcGlwZWxpbmUgbWFuYWdlcyBtYW55IGNvbXB1" + + "dGUgY2x1c3RlcnMgYWdhaW5zdCBvbmUgY29udHJvbCBwbGFuZToKCmBgYHlhbWwKZGVwbG95X2Nv" + + "bXB1dGVfcGxhbmVzOgogIHBhcmFsbGVsOgogICAgbWF0cml4OgogICAgICAtIENMVVNURVI6IG5j" + + "cC1lYXN0LTEKICAgICAgICBDT05URVhUOiBhZG1pbkBncHUtZWFzdC0xCiAgICAgIC0gQ0xVU1RF" + + "UjogbmNwLXdlc3QtMQogICAgICAgIENPTlRFWFQ6IGFkbWluQGdwdS13ZXN0LTEKICAgICAgLSBD" + + "TFVTVEVSOiBuY3AtZXUtMQogICAgICAgIENPTlRFWFQ6IGFkbWluQGdwdS1ldS0xCiAgc2NyaXB0" + + "OgogICAgLSBudmNmLWNsaSBzZWxmLWhvc3RlZCB1cAogICAgICAgIC0tY2x1c3Rlci1uYW1lPSRD" + + "TFVTVEVSCiAgICAgICAgLS1jb21wdXRlLXBsYW5lLWNvbnRleHQ9JENPTlRFWFQKICAgICAgICAt" + + "LWljbXMtdXJsPSRJQ01TX1BVQkxJQ19VUkwKICAgICAgICAtLXRva2VuPSROVkNGX0FETUlOX0pX" + + "VAogICAgICAgIC0tbm9uLWludGVyYWN0aXZlCiAgICAgICAgLS1qc29uCmBgYAoKRWFjaCBwYXJh" + + "bGxlbCBqb2IgcmVnaXN0ZXJzICsgaW5zdGFsbHMgb25lIGNvbXB1dGUgcGxhbmUgYWdhaW5zdCB0" + + "aGUgc2hhcmVkIGNvbnRyb2wgcGxhbmUuIEZhaWx1cmVzIGFyZSBpc29sYXRlZC4KCiMjIFBsYW4t" + + "b25seSBwcmV2aWV3IGluIFBScwoKYGBgeWFtbApwcmV2aWV3OgogIHJ1bGVzOgogICAgLSBpZjog" + + "JENJX1BJUEVMSU5FX1NPVVJDRSA9PSAibWVyZ2VfcmVxdWVzdF9ldmVudCIKICBzY3JpcHQ6CiAg" + + "ICAtIHwKICAgICAgbnZjZi1jbGkgc2VsZi1ob3N0ZWQgdXAgXAogICAgICAgIC0tY2x1c3Rlci1u" + + "YW1lPSRDTFVTVEVSX05BTUUgXAogICAgICAgIC0tcGxhbi1vbmx5IFwKICAgICAgICAtLWpzb24g" + + "fCB0ZWUgcGxhbi5qc29ubAogICAgLSBqcSAtcyAnLlswXScgcGxhbi5qc29ubCA+IHBsYW4uanNv" + + "bgogICAgLSAnZ2l0bGFiLW1yLWNvbW1lbnQgIiMjIE5WQ0YgZGVwbG95IHBsYW5cblxuXGBcYFxg" + + "anNvblxuJChjYXQgcGxhbi5qc29uKVxuXGBcYFxgIicKYGBgCgpgLS1wbGFuLW9ubHlgIGV4aXRz" + + "IDAgd2l0aG91dCBjaGFuZ2luZyBjbHVzdGVyIHN0YXRlOyB0aGUgSlNPTkwgb3V0cHV0IGxpc3Rz" + + "IGVhY2ggcGhhc2UgKyBFVEEuIFJlbmRlcnMgbmljZWx5IGFzIGFuIE1SIGNvbW1lbnQuClBLBwgU" + + "m+5a2g0AANoNAABQSwMEFAAIAAAAAAAhAAAAAAAAAAAAAAAAADQACQBkYXRhL252Y2Ytc2VsZi1t" + + "YW5hZ2VkLWNsaS9leGFtcGxlcy9tdWx0aS1jbHVzdGVyLm1kVVQFAAEAps4SIyBNdWx0aS1jbHVz" + + "dGVyIHBhdHRlcm5zCgpFeGFtcGxlcyBvZiBzcGxpdC1jbHVzdGVyICsgbWFueS1jb21wdXRlLXBs" + + "YW5lIG9wZXJhdGlvbi4KCiMjIE9uZSBjb250cm9sIHBsYW5lICsgTiBjb21wdXRlIHBsYW5lcwoK" + + "VGhlIGNhbm9uaWNhbCBtdWx0aS1jbHVzdGVyIHRvcG9sb2d5LiBFYWNoIGNvbXB1dGUgcGxhbmUg" + + "aXMgcmVnaXN0ZXJlZCBzZXBhcmF0ZWx5OgoKYGBgc2gKIyAxLiBCcmluZyB1cCB0aGUgY29udHJv" + + "bCBwbGFuZSAob25lLXRpbWUsIG9uIHRoZSBjb250cm9sLXBsYW5lIGNsdXN0ZXIpOgpLVUJFQ09O" + + "RklHPWNwLnlhbWwgbnZjZi1jbGkgc2VsZi1ob3N0ZWQgaW5zdGFsbCAtLWNvbnRyb2wtcGxhbmUg" + + "fCBrdWJlY3RsIGFwcGx5IC1mIC0KbnZjZi1jbGkgc2VsZi1ob3N0ZWQgY2hlY2sgLS1jb250cm9s" + + "LXBsYW5lIC0td2FpdCA1bQoKIyAyLiBSZWdpc3RlciArIGluc3RhbGwgZWFjaCBjb21wdXRlIHBs" + + "YW5lOgpmb3IgQ1RYIGluIGFkbWluQGdwdS1lYXN0LTEgYWRtaW5AZ3B1LXdlc3QtMSBhZG1pbkBn" + + "cHUtZXUtMTsgZG8KICBOQU1FPSQoZWNobyAiJENUWCIgfCBjdXQgLWRAIC1mMikKICBudmNmLWNs" + + "aSBzZWxmLWhvc3RlZCB1cCBcCiAgICAtLWNsdXN0ZXItbmFtZT0kTkFNRSBcCiAgICAtLWNvbXB1" + + "dGUtcGxhbmUtY29udGV4dD0kQ1RYIFwKICAgIC0taWNtcy11cmw9aHR0cHM6Ly9pY21zLm52Y2Yu" + + "ZXhhbXBsZS5jb20gXAogICAgLS10b2tlbj0kTlZDRl9BRE1JTl9KV1QgXAogICAgLS1ub24taW50" + + "ZXJhY3RpdmUgXAogICAgLS1qc29uCmRvbmUKCiMgMy4gVmVyaWZ5IGFsbCBjb21wdXRlIHBsYW5l" + + "cyByZWdpc3RlcmVkOgpudmNmLWNsaSBjbHVzdGVyIGxpc3QtcmVnaXN0ZXJlZCAtLW5jYS1pZD0k" + + "TkNBX0lEIC0taWNtcy11cmw9JElDTVMgLS1qc29uIFwKICB8IGpxICcuY2x1c3RlcnNbXSB8IHtu" + + "YW1lOiAuY2x1c3Rlck5hbWUsIHN0YXR1cywgbnZjYVZlcnNpb259JwpgYGAKCiMjIENvbXB1dGUt" + + "cGxhbmUtb25seSBvcGVyYXRvciBzY2VuYXJpbwoKVGhlIG9wZXJhdG9yIGhhcyBrdWJlY3RsIGFj" + + "Y2VzcyB0byBPTkUgY29tcHV0ZSBwbGFuZSBvbmx5IOKAlCB0aGUgY29udHJvbCBwbGFuZSBpcyBv" + + "cGVyYXRlZCBieSBzb21lb25lIGVsc2UgKGUuZy4gWW90dGEgcnVucyB0aGUgY29udHJvbCBwbGFu" + + "ZSBmb3IgYSBjdXN0b21lcikuCgpgYGBzaAojIE1pbnQgYSB0b2tlbiB2aWEgdGhlIHB1YmxpYyBh" + + "cGkgZ2F0ZXdheSAodGhlIGNvbnRyb2wgcGxhbmUncyBBUEkgS2V5cwojIHNlcnZpY2UgaXMgcmVh" + + "Y2hhYmxlIHZpYSBhcGkubnZjZi5leGFtcGxlLmNvbSDigJQgbm8ga3ViZWN0bCBhY2Nlc3MgbmVl" + + "ZGVkKToKbnZjZi1jbGkgaW5pdCAtLWFwaS11cmw9aHR0cHM6Ly9hcGkubnZjZi5leGFtcGxlLmNv" + + "bQoKIyBSdW4gcHJlLWZsaWdodCBzY29wZWQgdG8gdGhlIGNvbXB1dGUgcGxhbmUgb25seToKbnZj" + + "Zi1jbGkgc2VsZi1ob3N0ZWQgY2hlY2sgLS1wcmUgXAogIC0tY29tcHV0ZS1wbGFuZS1jb250ZXh0" + + "PWFkbWluQGdwdTEgXAogIC0taWNtcy11cmw9aHR0cHM6Ly9pY21zLm52Y2YuZXhhbXBsZS5jb20g" + + "XAogIC0tanNvbgoKIyBCcmluZyB1cCBqdXN0IHRoZSBjb21wdXRlIHBsYW5lOgpudmNmLWNsaSBz" + + "ZWxmLWhvc3RlZCB1cCBcCiAgLS1jbHVzdGVyLW5hbWU9bXktY29tcHV0ZS0xIFwKICAtLWNvbXB1" + + "dGUtcGxhbmUtY29udGV4dD1hZG1pbkBncHUxIFwKICAtLWljbXMtdXJsPWh0dHBzOi8vaWNtcy5u" + + "dmNmLmV4YW1wbGUuY29tIFwKICAtLXRva2VuPSRBRE1JTl9KV1QgXAogIC0tbm9uLWludGVyYWN0" + + "aXZlCgojIFN0YXR1cyAoY29tcHV0ZS1vbmx5IOKAlCBvcGVyYXRvciBjYW4ndCBzZWUgY29udHJv" + + "bC1wbGFuZSBjb21wb25lbnQgaGVhbHRoKToKbnZjZi1jbGkgc2VsZi1ob3N0ZWQgc3RhdHVzIFwK" + + "ICAtLWNvbXB1dGUtcGxhbmUtY29udGV4dD1hZG1pbkBncHUxIFwKICAtLWljbXMtdXJsPWh0dHBz" + + "Oi8vaWNtcy5udmNmLmV4YW1wbGUuY29tCmBgYAoKIyMgRnVuY3Rpb25zIHRhcmdldGluZyBzcGVj" + + "aWZpYyBjb21wdXRlIHBsYW5lcwoKV2hlbiB5b3UgaGF2ZSBtdWx0aXBsZSBjb21wdXRlIHBsYW5l" + + "cyBhbmQgd2FudCBhIGZ1bmN0aW9uIHRvIGRlcGxveSB0byBvbmU6CgpgYGBqc29uCnsKICAiZnVu" + + "Y3Rpb25JZCI6ICI8Zm5faWQ+IiwKICAidmVyc2lvbklkIjogIjx2ZXJfaWQ+IiwKICAiZGVwbG95" + + "bWVudFNwZWNpZmljYXRpb25zIjogW3sKICAgICJncHUiOiAiSDEwMCIsCiAgICAiaW5zdGFuY2VU" + + "eXBlIjogIk5DUC5HUFUuSDEwMF8xeCIsCiAgICAibWluSW5zdGFuY2VzIjogMSwKICAgICJtYXhJ" + + "bnN0YW5jZXMiOiAxLAogICAgImNsdXN0ZXJzIjogWyJncHUtZWFzdC0xIl0KICB9XQp9CmBgYAoK" + + "VGhlIGBjbHVzdGVyc2AgYXJyYXkgKHVzaW5nIGNsdXN0ZXIgbmFtZXMsIG5vdCBJRHMpIGxpbWl0" + + "cyBzY2hlZHVsaW5nIHRvIHRob3NlIGNvbXB1dGUgcGxhbmVzLiBPbWl0IHRvIGxldCBJQ01TIGNo" + + "b29zZSBhbnkgY29tcHV0ZSBwbGFuZSB3aXRoIGEgbWF0Y2hpbmcgU0tVLgoKIyMgU3RhdHVzIGZh" + + "bi1vdXQgYWNyb3NzIGNvbXB1dGUgcGxhbmVzCgpgYGBzaAojIExpc3QgYWxsIHJlZ2lzdGVyZWQg" + + "Y29tcHV0ZSBwbGFuZXM6Cm52Y2YtY2xpIGNsdXN0ZXIgbGlzdC1yZWdpc3RlcmVkIC0tbmNhLWlk" + + "PSROQ0FfSUQgLS1pY21zLXVybD0kSUNNUyAtLWpzb24gXAogIHwganEgLXIgJy5jbHVzdGVyc1td" + + "LmNsdXN0ZXJOYW1lJyA+IGNsdXN0ZXJzLnR4dAoKIyBTdGF0dXMgc25hcHNob3QgcGVyIGNvbXB1" + + "dGUgcGxhbmUgKGFzc3VtaW5nIGVhY2ggY29udGV4dCBuYW1lIG1hdGNoZXMpOgp3aGlsZSByZWFk" + + "IE5BTUU7IGRvCiAgZWNobyAiPT09ICROQU1FID09PSIKICBudmNmLWNsaSBzZWxmLWhvc3RlZCBz" + + "dGF0dXMgXAogICAgLS1jbHVzdGVyLW5hbWU9JE5BTUUgXAogICAgLS1jb21wdXRlLXBsYW5lLWNv" + + "bnRleHQ9YWRtaW5AJE5BTUUgXAogICAgLS1qc29uIHwganEgLWMgJ3tjbHVzdGVyOi5jbHVzdGVy" + + "LCB2ZXJkaWN0Oi52ZXJkaWN0LCByZWNvbmNpbGU6LnJlY29uY2lsZUFnZVNlY30nCmRvbmUgPCBj" + + "bHVzdGVycy50eHQKYGBgCgpBIGZ1dHVyZSBNKzExIG1pbGVzdG9uZSB3aWxsIGFkZCBuYXRpdmUg" + + "ZmFuLW91dCAoYHN0YXR1cyAtLWFsbC1jb21wdXRlLXBsYW5lc2ApIHNvIHRoaXMgZG9lc24ndCBu" + + "ZWVkIGEgc2hlbGwgbG9vcC4KUEsHCKEU44FdDAAAXQwAAFBLAwQUAAgAAAAAACEAAAAAAAAAAAAA" + + "AAAANwAJAGRhdGEvbnZjZi1zZWxmLW1hbmFnZWQtY2xpL3Byb21wdHMvYWRkLWNvbXB1dGUtcGxh" + + "bmUubWRVVAUAAQCmzhIjIEFkZCBhIGNvbXB1dGUgcGxhbmUgdG8gYW4gZXhpc3RpbmcgY29udHJv" + + "bCBwbGFuZQoKVXNlciBoYXMgYSB3b3JraW5nIE5WQ0YgY29udHJvbCBwbGFuZSAocnVubmluZyBz" + + "b21ld2hlcmUpIGFuZCB3YW50cyB0byByZWdpc3RlciBhIG5ldyBHUFUgY2x1c3RlciBhZ2FpbnN0" + + "IGl0LiBUaGlzIGlzIHRoZSBtdWx0aS1jbHVzdGVyIHNjYWxpbmcgcGF0aDogb25lIGNvbnRyb2wg" + + "cGxhbmUg4oaSIE4gY29tcHV0ZSBwbGFuZXMuCgojIyBQcmVyZXF1aXNpdGVzIHRoZSB1c2VyIG11" + + "c3QgaGF2ZQoKLSAqKmt1YmVjdGwgY29udGV4dCBmb3IgdGhlIG5ldyBjb21wdXRlIHBsYW5lKiog" + + "aW4gdGhlaXIgYEtVQkVDT05GSUdgLgotICoqUHVibGljIElDTVMgVVJMKiogb2YgdGhlIGV4aXN0" + + "aW5nIGNvbnRyb2wgcGxhbmUgKGUuZy4gYGh0dHBzOi8vaWNtcy5udmNmLmV4YW1wbGUuY29tYCku" + + "Ci0gKipBZG1pbiBKV1QqKiBmb3IgdGhlIGNvbnRyb2wgcGxhbmUncyBhY2NvdW50LCBPUiBhYmls" + + "aXR5IHRvIG1pbnQgb25lIHZpYSBgbnZjZi1jbGkgaW5pdGAgYWdhaW5zdCB0aGUgY29udHJvbCBw" + + "bGFuZSdzIHB1YmxpYyBhcGkgZW5kcG9pbnQuIChBZG1pbiB0b2tlbnMgY29tZSBmcm9tIHRoZSBB" + + "UEkgS2V5cyBzZXJ2aWNlIHZpYSB0aGUgcHVibGljIGFwaSBnYXRld2F5IOKAlCBrdWJlY3RsIGFj" + + "Y2VzcyB0byB0aGUgY29udHJvbCBwbGFuZSBpcyBOT1QgcmVxdWlyZWQgdG8gb2J0YWluIG9uZS4p" + + "Ci0gQSB1bmlxdWUgYC0tY2x1c3Rlci1uYW1lYCB0aGF0IGRvZXNuJ3QgY29sbGlkZSB3aXRoIGFs" + + "cmVhZHktcmVnaXN0ZXJlZCBjbHVzdGVycy4gVXNlIGBudmNmLWNsaSBjbHVzdGVyIGxpc3QtcmVn" + + "aXN0ZXJlZCAtLW5jYS1pZD0kTkNBX0lEIC0taWNtcy11cmw9JElDTVNgIHRvIGNoZWNrLgoKIyMg" + + "U3RlcHMKCjEuICoqQXNrIGZvciBpbnB1dHM6KioKICAgLSBgLS1jbHVzdGVyLW5hbWU9PHVuaXF1" + + "ZT5gIOKAlCBtdXN0IGJlIHVuaXF1ZSB3aXRoaW4gdGhlIGNvbnRyb2wgcGxhbmUncyBOQ0Egc2Nv" + + "cGUKICAgLSBgLS1jb21wdXRlLXBsYW5lLWNvbnRleHQ9PGNvbnRleHQ+YCDigJQgdGhlIG5ldyBH" + + "UFUgY2x1c3RlcidzIGt1YmVjdGwgY29udGV4dAogICAtIGAtLWljbXMtdXJsPTxodHRwczovL2lj" + + "bXMubnZjZi5leGFtcGxlLmNvbT5gIOKAlCBjb250cm9sIHBsYW5lJ3MgcHVibGljIElDTVMgVVJM" + + "CiAgIC0gR1BVIHR5cGU/IChkZWZhdWx0IGBIMTAwYCwgYnV0IGFzaykKCjIuICoqUHJlLWZsaWdo" + + "dCBqdXN0IHRoZSBjb21wdXRlIHBsYW5lOioqCgogICBgYGBzaAogICBudmNmLWNsaSBzZWxmLWhv" + + "c3RlZCBjaGVjayAtLXByZSBcCiAgICAgLS1jb21wdXRlLXBsYW5lLWNvbnRleHQ9JENUWCBcCiAg" + + "ICAgLS1pY21zLXVybD0kSUNNUyBcCiAgICAgLS1qc29uIHwganEgLWMgLgogICBgYGAKCiAgIFRo" + + "aXMgdmFsaWRhdGVzIHRoZSBHUFUgb3BlcmF0b3IgaXMgaW5zdGFsbGVkLCBHUFUgbm9kZSBsYWJl" + + "bHMgYXJlIHByZXNlbnQsIGFuZCBJQ01TIGlzIHJlYWNoYWJsZSB2aWEgSFRUUC4gQWRkcmVzcyBh" + + "bnkgZmFpbHVyZXMgYmVmb3JlIHByb2NlZWRpbmcuCgozLiAqKlJ1biBgYWRkLWNvbXB1dGUtcGxh" + + "bmVgOioqCgogICBgYGBzaAogICBudmNmLWNsaSBzZWxmLWhvc3RlZCBhZGQtY29tcHV0ZS1wbGFu" + + "ZSBcCiAgICAgLS1jbHVzdGVyLW5hbWU9JE5BTUUgXAogICAgIC0tY29tcHV0ZS1wbGFuZS1jb250" + + "ZXh0PSRDVFggXAogICAgIC0taWNtcy11cmw9JElDTVMgXAogICAgIC0tdG9rZW49JEpXVCBcCiAg" + + "ICAgLS1ub24taW50ZXJhY3RpdmUgXAogICAgIC0tanNvbgogICBgYGAKCiAgIFRoaXMgaXMgYSBz" + + "ZXBhcmF0ZSBzdWJjb21tYW5kIGZyb20gYHVwYC4gSXQgcnVucyBvbmx5IHRoZSBjb21wdXRlLXBs" + + "YW5lLXJlbGV2YW50IHBoYXNlczogMSAoY29tcHV0ZS1wbGFuZSBwcmVmbGlnaHQpLCA1IChyZWdp" + + "c3RlciksIDYgKGNvbXB1dGUtcGxhbmUgYXBwbHkpLCA4IChmaW5hbCBoZWFsdGggb24gdGhlIG5l" + + "dyBjb21wdXRlIHBsYW5lKS4gSXQgZG9lcyBOT1QgdG91Y2ggdGhlIGNvbnRyb2wgcGxhbmUgYW5k" + + "IGRvZXMgTk9UIGFjY2VwdCBgLS1jb250cm9sLXBsYW5lLWNvbnRleHRgIOKAlCB0aGUgY29udHJv" + + "bCBwbGFuZSBpcyByZWFjaGVkIG92ZXIgSFRUUFMgYXQgYC0taWNtcy11cmxgLiBgLS10b2tlbmAg" + + "aXMgcmVxdWlyZWQgYmVjYXVzZSB0aGVyZSdzIG5vIGt1YmVjdGwgcGF0aCB0byBtaW50IG9uZSBm" + + "cm9tLgoKNC4gKipWZXJpZnkuKiogYG52Y2YtY2xpIHNlbGYtaG9zdGVkIHN0YXR1cyAtLWNsdXN0" + + "ZXItbmFtZT0kTkFNRSAtLWpzb24gfCBqcWAg4oCUIGV4cGVjdCBgdmVyZGljdDogImhlYWx0aHki" + + "YC4gVGhlIFJlZ2lzdGVyZWQgQ29tcHV0ZSBQbGFuZXMgcGFuZWwgZnJvbSBJQ01TIHNob3VsZCBu" + + "b3cgbGlzdCB0aGUgbmV3IGNsdXN0ZXIuCgo1LiAqKlNtb2tlLioqIFtkZXBsb3ktYW5kLWludm9r" + + "ZS5tZF0oZGVwbG95LWFuZC1pbnZva2UubWQpIOKAlCBkZXBsb3kgYSBzbWFsbCBmdW5jdGlvbiB0" + + "byB0aGUgbmV3IGNvbXB1dGUgcGxhbmUgdG8gY29uZmlybSBzY2hlZHVsaW5nIHdvcmtzLgoKIyMg" + + "V2hhdCBpZiBhIGNsdXN0ZXIgd2l0aCB0aGUgc2FtZSBuYW1lIGFscmVhZHkgZXhpc3RzPwoKYGFk" + + "ZC1jb21wdXRlLXBsYW5lYCdzIHJlZ2lzdGVyIHBoYXNlIHVzZXMgYC0taWdub3JlLWV4aXN0aW5n" + + "YCBzZW1hbnRpY3Mg4oCUIGl0J2xsIG1hdGNoIHRoZSBleGlzdGluZyBJQ01TIHJvdyBhbmQgcmV1" + + "c2UgaXRzIGNsdXN0ZXJJZC4gVHdvIHNjZW5hcmlvczoKCi0gKipSZS1yZWdpc3RlcmluZyB0aGUg" + + "c2FtZSBjbHVzdGVyKiogKHJlLXJ1bm5pbmcgb24gdGhlIHNhbWUgY29tcHV0ZSBwbGFuZSB0aGF0" + + "IHdhcyBwcmV2aW91c2x5IHJlZ2lzdGVyZWQpOiBleHBlY3RlZCwgbm8tb3Agc2VtYW50aWNzLgot" + + "ICoqRGlmZmVyZW50IHBoeXNpY2FsIGNsdXN0ZXIgYnV0IHNhbWUgbmFtZSoqOiB0aGUgc2Vjb25k" + + "IGF0dGVtcHQgd2lsbCByZXVzZSB0aGUgSUNNUyByb3csIGJ1dCB0aGUgbmV3IGNvbXB1dGUgcGxh" + + "bmUncyBKV0tTIHdpbGwgYmUgc2lsZW50bHkgKnJlcGxhY2VkKiDigJQgdGhlIG9sZCBjb21wdXRl" + + "IHBsYW5lJ3MgTlZDQSBhZ2VudCB3aWxsIHN0YXJ0IGZhaWxpbmcgUFNBVCBhdXRoLiAqKkNvbmZp" + + "cm0gd2l0aCB0aGUgdXNlcioqIHRoYXQgdGhleSBtZWFudCB0byBvdmVyd3JpdGUuCgpXaGVuIGlu" + + "IGRvdWJ0LCBydW4gYG52Y2YtY2xpIGNsdXN0ZXIgbGlzdC1yZWdpc3RlcmVkIC0tbmNhLWlkPSRO" + + "Q0FfSUQgLS1pY21zLXVybD0kSUNNU2AgZmlyc3QgYW5kIGFzayB0aGUgdXNlciBiZWZvcmUgcHJv" + + "Y2VlZGluZyB3aXRoIGEgbmFtZSB0aGF0IGFscmVhZHkgZXhpc3RzLgpQSwcIQidJDwUNAAAFDQAA" + + "UEsDBBQACAAAAAAAIQAAAAAAAAAAAAAAAAA5AAkAZGF0YS9udmNmLXNlbGYtbWFuYWdlZC1jbGkv" + + "cHJvbXB0cy9jcmVhdGUtYW5kLXJ1bi10YXNrLm1kVVQFAAEAps4SIyBDcmVhdGUgYW5kIHJ1biBh" + + "IHRhc2sKClN0YW5kYXJkIGZsb3c6IG1pbnQgQVBJIGtleXMgLT4gdGFzayBDUkVBVEUgLT4gbW9u" + + "aXRvciAtPiByZXRyaWV2ZSByZXN1bHRzIC0+IGNsZWFudXAuCgojIyAxLiBNaW50IEFQSSBrZXlz" + + "CgpUaGUgYWRtaW4gdG9rZW4gZnJvbSBgaW5pdGAgZG9lcyBub3QgY2FycnkgdGFzayBpbnZvY2F0" + + "aW9uIHNjb3BlLiBHZW5lcmF0ZSBBUEkga2V5czoKCmBgYHNoCm52Y2YtY2xpIGFwaS1rZXkgZ2Vu" + + "ZXJhdGUgLS1kZXNjcmlwdGlvbj0idGFzayBzbW9rZSB0ZXN0IiAtLWV4cGlyZXMtaW49OGgKIyAt" + + "PiBmdW5jdGlvbiBrZXk6IG52YXBpLS4uLgojIC0+IHRhc2sga2V5OiBudmFwaS0uLi4KYGBgCgpC" + + "b3RoIGtleXMgYXJlIHNhdmVkIHRvIENMSSBzdGF0ZS4gVG8gbWludCBvbmx5IHRoZSB0YXNrIGtl" + + "eToKCmBgYHNoCm52Y2YtY2xpIGFwaS1rZXkgZ2VuZXJhdGUgLS1mb3IgdGFzayAtLWRlc2NyaXB0" + + "aW9uPSJ0YXNrIHNtb2tlIHRlc3QiIC0tZXhwaXJlcy1pbj04aApgYGAKClRvIHZhbGlkYXRlIGVh" + + "Y2gga2V5IGltbWVkaWF0ZWx5IGFmdGVyIG1pbnRpbmc6CgpgYGBzaApudmNmLWNsaSBhcGkta2V5" + + "IGdlbmVyYXRlIC0tZGVzY3JpcHRpb249InRhc2sgc21va2UgdGVzdCIgLS1leHBpcmVzLWluPTho" + + "IC0tdmFsaWRhdGUKIyAtPiBmdW5jdGlvbiBBUEkga2V5IHZhbGlkYXRpb24gcGFzc2VkIQojIC0+" + + "IHRhc2sgQVBJIGtleSB2YWxpZGF0aW9uIHBhc3NlZCEKYGBgCgpOZXZlciBlY2hvIHRoZSBrZXkg" + + "dmFsdWUgKG52YXBpLS4uLikgaW50byBjaGF0IG9yIGxvZ3MuIFJlcG9ydCBvbmx5IHRoYXQgdGhl" + + "IGtleSB3YXMgbWludGVkLCBpdHMgZGVzY3JpcHRpb24sIGFuZCBpdHMgZXhwaXJ5IOKAlCBuZXZl" + + "ciB0aGUgdmFsdWUgaXRzZWxmLgoKIyMjIEZyZXNoIHNlc3Npb24gcHJlLWZsaWdodAoKQmVmb3Jl" + + "IHJ1bm5pbmcgYW55IGB0YXNrYCBjb21tYW5kIGluIGEgZnJlc2ggYWdlbnQgc2Vzc2lvbiwgdmVy" + + "aWZ5IGNyZWRlbnRpYWxzOgoKMS4gQ2hlY2sgd2hldGhlciBgTlZDRl9BUElfS0VZYCBpcyBzZXQ6" + + "CgogICBgYGBzaAogICBwcmludGVudiBOVkNGX0FQSV9LRVkgPiAvZGV2L251bGwgMj4mMSAmJiBl" + + "Y2hvIHNldCB8fCBlY2hvIHVuc2V0CiAgIGBgYAoKMi4gSWYgdW5zZXQsIHJlYWQgdGhlIHN0b3Jl" + + "ZCBrZXkgZXhwaXJ5IHdpdGhvdXQgcHJpbnRpbmcgdGhlIHZhbHVlOgoKICAgYGBgc2gKICAgcHl0" + + "aG9uMyAtYyAiaW1wb3J0IGpzb24scGF0aGxpYjsgcz1qc29uLmxvYWRzKHBhdGhsaWIuUGF0aCgn" + + "fi8ubnZjZi1jbGkuc3RhdGUnKS5leHBhbmR1c2VyKCkucmVhZF90ZXh0KCkpOyBwcmludChzLmdl" + + "dCgnbnZjdEFwaUtleUV4cGlyYXRpb24nLCdtaXNzaW5nJykpIgogICBgYGAKCjMuIElmIHRoZSBl" + + "eHBpcnkgaXMgaW4gdGhlIHBhc3Qgb3IgbWlzc2luZywgcmVnZW5lcmF0ZToKCiAgIGBgYHNoCiAg" + + "IG52Y2YtY2xpIHJlZnJlc2gKICAgbnZjZi1jbGkgYXBpLWtleSBnZW5lcmF0ZSAtLWZvciB0YXNr" + + "IC0tZGVzY3JpcHRpb249InRhc2sta2V5IiAtLWV4cGlyZXMtaW49MjRoIC0tdmFsaWRhdGUKICAg" + + "YGBgCgo0LiBJbmplY3QgdGhlIHN0b3JlZCBrZXkgaW50byB0aGUgc3VicHJvY2VzcyBlbnYgd2l0" + + "aG91dCBsb2dnaW5nIHRoZSB2YWx1ZS4gVXNlIGEgc2hlbGwgc3Vic2hlbGwgb3IgZW52IHByZWZp" + + "eCDigJQgbmV2ZXIgcHJpbnQgdGhlIGtleSBpbnRvIGNoYXQuCgpFcnJvciBjbGFzc2lmaWNhdGlv" + + "bjoKCi0gIm1pc3NpbmcgYXV0aGVudGljYXRpb24gY3JlZGVudGlhbHMiIG9yIGV4aXQgMjoga2V5" + + "IG5vdCBpbmplY3RlZDsgcmVwZWF0IHByZS1mbGlnaHQuCi0gSFRUUCA0MDM6IGtleSBleHBpcmVk" + + "IG9yIHdyb25nIGF1ZGllbmNlOyByZWdlbmVyYXRlLgotIEVtcHR5IHRhc2sgbGlzdDogdmFsaWQ7" + + "IG5vIHRhc2tzIGV4aXN0IHlldC4KCiMjIDIuIFRhc2sgQ1JFQVRFOiBjb250YWluZXIKCkRlZmlu" + + "ZSB0aGUgdGFzayBpbiBhIEpTT04gZmlsZS4gTWluaW11bSB2aWFibGUgY29udGFpbmVyIHRhc2s6" + + "CgpgYGBqc29uCnsKICAibmFtZSI6ICJzbW9rZS1jb250YWluZXItdGFzayIsCiAgImdwdVNwZWNp" + + "ZmljYXRpb24iOiB7CiAgICAiZ3B1IjogIkgxMDAiLAogICAgImluc3RhbmNlVHlwZSI6ICJOQ1Au" + + "R1BVLkgxMDBfMXgiLAogICAgImJhY2tlbmQiOiAibmNwLWxvY2FsIgogIH0sCiAgImNvbnRhaW5l" + + "ckltYWdlIjogIm52Y3IuaW8vbnZpZGlhL25lbW86bGF0ZXN0IiwKICAiY29udGFpbmVyQXJncyI6" + + "ICJweXRob24gLWMgXCJwcmludCgnaGVsbG8gZnJvbSB0YXNrJylcIiIsCiAgInJlc3VsdEhhbmRs" + + "aW5nU3RyYXRlZ3kiOiAiTk9ORSIKfQpgYGAKCmBgYHNoCm52Y2YtY2xpIHRhc2sgY3JlYXRlIC0t" + + "aW5wdXQtZmlsZT10YXNrLWNvbnRhaW5lci5qc29uCiMgLT4gVGFzayBJRDogPHRhc2tfaWQ+CmBg" + + "YAoKQ2FwdHVyZSB0aGUgdGFzayBJRCBmcm9tIHRoZSBvdXRwdXQuIFN0YXRlIHBlcnNpc3RlbmNl" + + "IGNhbiBmYWlsIHNpbGVudGx5LCBzbyBwYXNzCmA8dGFza0lkPmAgZXhwbGljaXRseSBvbiBzdWJz" + + "ZXF1ZW50IGNvbW1hbmRzIHJhdGhlciB0aGFuIHJlbHlpbmcgb24gc2F2ZWQgc3RhdGUuCgpFcXVp" + + "dmFsZW50IENMSSBmbGFncyBmb3JtIGZvciBxdWljayBhZC1ob2MgcnVuczoKCmBgYHNoCm52Y2Yt" + + "Y2xpIHRhc2sgY3JlYXRlIFwKICAtLW5hbWU9c21va2UtY29udGFpbmVyLXRhc2sgXAogIC0tZ3B1" + + "PUgxMDAgXAogIC0taW5zdGFuY2UtdHlwZT1OQ1AuR1BVLkgxMDBfMXggXAogIC0tYmFja2VuZD1u" + + "Y3AtbG9jYWwgXAogIC0taW1hZ2U9bnZjci5pby9udmlkaWEvbmVtbzpsYXRlc3QgXAogIC0tY29u" + + "dGFpbmVyLWFyZ3M9InB5dGhvbiAtYyAncHJpbnQoaGVsbG8gZnJvbSB0YXNrKSciIFwKICAtLXJl" + + "c3VsdC1zdHJhdGVneT1OT05FCmBgYAoKVG8gdGFyZ2V0IGEgc3BlY2lmaWMgY29tcHV0ZSBjbHVz" + + "dGVyOgoKYGBgc2gKbnZjZi1jbGkgdGFzayBjcmVhdGUgLS1pbnB1dC1maWxlPXRhc2stY29udGFp" + + "bmVyLmpzb24gLS1jbHVzdGVycz1teS1jbHVzdGVyCmBgYAoKVG8gaW5qZWN0IGVudmlyb25tZW50" + + "IHZhcmlhYmxlcyBvciBzZWNyZXRzLCBlaXRoZXIgcGFzcyBmbGFnczoKCmBgYHNoCm52Y2YtY2xp" + + "IHRhc2sgY3JlYXRlIC0taW5wdXQtZmlsZT10YXNrLWNvbnRhaW5lci5qc29uIFwKICAtLWNvbnRh" + + "aW5lci1lbnYgTVlfRU5WPXZhbHVlIFwKICAtLXNlY3JldHMgTVlfU0VDUkVUPXMzY3IzdApgYGAK" + + "Cm9yIHNldCB0aGVtIGluIHRoZSBKU09OIGZpbGU6CgpgYGBqc29uCnsKICAiY29udGFpbmVyRW52" + + "aXJvbm1lbnQiOiBbCiAgICB7ImtleSI6ICJNWV9FTlYiLCAidmFsdWUiOiAidmFsdWUifQogIF0s" + + "CiAgInNlY3JldHMiOiBbCiAgICB7Im5hbWUiOiAiTVlfU0VDUkVUIiwgInZhbHVlIjogInMzY3Iz" + + "dCJ9CiAgXQp9CmBgYAoKU2VjcmV0cyBhcmUgZW5jcnlwdGVkIGF0IHJlc3QgYW5kIG5vdCByZXR1" + + "cm5lZCBpbiBsaXN0IG9yIGdldCByZXNwb25zZXMgYnkgZGVmYXVsdDsgZG8gbm90IGxvZyB0aGVt" + + "LgoKIyMgMy4gVGFzayBDUkVBVEU6IEhlbG0KCkZvciBIZWxtLXR5cGUgdGFza3MsIHJlcGxhY2Ug" + + "YGNvbnRhaW5lckltYWdlYCB3aXRoIGBoZWxtQ2hhcnRgOgoKYGBganNvbgp7CiAgIm5hbWUiOiAi" + + "c21va2UtaGVsbS10YXNrIiwKICAiZ3B1U3BlY2lmaWNhdGlvbiI6IHsKICAgICJncHUiOiAiSDEw" + + "MCIsCiAgICAiaW5zdGFuY2VUeXBlIjogIk5DUC5HUFUuSDEwMF8xeCIsCiAgICAiYmFja2VuZCI6" + + "ICJuY3AtbG9jYWwiCiAgfSwKICAiaGVsbUNoYXJ0IjogImh0dHBzOi8vaGVsbS5uZ2MubnZpZGlh" + + "LmNvbS88b3JnPi9jaGFydHMvZXhhbXBsZS10YXNrLTAuMS4wLnRneiIsCiAgInJlc3VsdEhhbmRs" + + "aW5nU3RyYXRlZ3kiOiAiTk9ORSIKfQpgYGAKCmBgYHNoCm52Y2YtY2xpIHRhc2sgY3JlYXRlIC0t" + + "aW5wdXQtZmlsZT10YXNrLWhlbG0uanNvbgojIC0+IFRhc2sgSUQ6IDx0YXNrX2lkPgpgYGAKCmBo" + + "ZWxtQ2hhcnRgIGFuZCBgY29udGFpbmVySW1hZ2VgIGFyZSBtdXR1YWxseSBleGNsdXNpdmUuCgoj" + + "IyAzYi4gVXBkYXRlIHNlY3JldHMgb24gYSBydW5uaW5nIHRhc2sKCmBgYHNoCm52Y2YtY2xpIHRh" + + "c2sgdXBkYXRlLXNlY3JldHMgPHRhc2tfaWQ+IC0tc2VjcmV0cyBOQU1FPXZhbHVlCmBgYAoKYHVw" + + "ZGF0ZS1zZWNyZXRzYCBtZXJnZXMgc2VjcmV0cyBieSBuYW1lOiBzdXBwbGllZCBzZWNyZXRzIGFy" + + "ZSBhZGRlZCBvciB1cGRhdGVkLCBhbmQgZXhpc3Rpbmcgc2VjcmV0cyBub3QgaW5jbHVkZWQgaW4g" + + "dGhlIHJlcXVlc3QgYXJlIHByZXNlcnZlZC4gRG8gbm90IGVjaG8gc2VjcmV0IHZhbHVlcyBpbnRv" + + "IGNoYXQuCgojIyA0LiBNb25pdG9yCgpQb2xsIHRhc2sgc3RhdGU6CgpgYGBzaApudmNmLWNsaSB0" + + "YXNrIGdldCA8dGFza19pZD4KIyAtPiBpZDogPHRhc2tfaWQ+CiMgLT4gc3RhdHVzOiBSVU5OSU5H" + + "CmBgYAoKT3IgdXNlIHRoZSB0YXNrIElEIHNhdmVkIHRvIHN0YXRlIGZyb20gdGhlIG1vc3QgcmVj" + + "ZW50IGB0YXNrIGNyZWF0ZWA6CgpgYGBzaApudmNmLWNsaSB0YXNrIGdldApgYGAKClN0YXR1cyB2" + + "YWx1ZXM6IGBRVUVVRURgLCBgTEFVTkNIRURgLCBgUlVOTklOR2AsIGBDT01QTEVURURgLCBgRVJS" + + "T1JFRGAsIGBDQU5DRUxFRGAsIGBFWENFRURFRF9NQVhfUlVOVElNRV9EVVJBVElPTmAsIGBFWENF" + + "RURFRF9NQVhfUVVFVUVEX0RVUkFUSU9OYC4KCkluc3BlY3QgbGlmZWN5Y2xlIGV2ZW50cyBmb3Ig" + + "cHJvZ3Jlc3MgZGV0YWlscyBvciBmYWlsdXJlIGNvbnRleHQ6CgpgYGBzaApudmNmLWNsaSB0YXNr" + + "IGV2ZW50cyA8dGFza19pZD4KYGBgCgpJZiB0aGUgdGFzayBpcyBzdHVjayBpbiBgUVVFVUVEYCwg" + + "Y2hlY2sgdGhhdCBhIGNvbXB1dGUgY2x1c3RlciB3aXRoIGEgbWF0Y2hpbmcgR1BVIFNLVQppcyBy" + + "ZWdpc3RlcmVkIGFuZCBoZWFsdGh5IChgbnZjZi1jbGkgc2VsZi1ob3N0ZWQgc3RhdHVzYCkuCgpJ" + + "ZiB0aGUgdGFzayBtb3ZlcyB0byBgRVJST1JFRGAsIHRoZSBldmVudHMgb3V0cHV0IGNvbnRhaW5z" + + "IHRoZSBmYWlsdXJlIHJlYXNvbi4gRm9yCmNvbnRhaW5lci1sZXZlbCBkZXRhaWwsIGluc3BlY3Qg" + + "dGhlIHRhc2sgd29ya2xvYWQgcG9kcyBvbiB0aGUgY29tcHV0ZS1wbGFuZSBjbHVzdGVyIHdpdGgK" + + "YGt1YmVjdGxgIGFnYWluc3QgdGhlIGFwcHJvcHJpYXRlIG5hbWVzcGFjZSBhbmQgY29udGV4dC4K" + + "CiMjIDUuIFJldHJpZXZlIHJlc3VsdHMKCkZvciB0YXNrcyB3aXRoIGByZXN1bHRIYW5kbGluZ1N0" + + "cmF0ZWd5OiBOT05FYCwgcmVzdWx0cyBhcmUgZW1wdHkgYnkgZGVzaWduOgoKYGBgc2gKbnZjZi1j" + + "bGkgdGFzayByZXN1bHRzIDx0YXNrX2lkPgojIC0+IHsicmVzdWx0cyI6IFtdfQpgYGAKCkZvciB0" + + "YXNrcyB3aXRoIGByZXN1bHRIYW5kbGluZ1N0cmF0ZWd5OiBVUExPQURgLCByZXN1bHRzIHVwbG9h" + + "ZCB0byB0aGUgbG9jYXRpb24gbmFtZWQgaW4KYHJlc3VsdHNMb2NhdGlvbmAuIFRoZSB0YXNrJ3Mg" + + "Y3JlZGVudGlhbHMgbXVzdCBoYXZlIHdyaXRlIGFjY2VzcyB0byB0aGF0IGxvY2F0aW9uLiBBZnRl" + + "cgp0aGUgdGFzayByZWFjaGVzIGBDT01QTEVURURgOgoKYGBgc2gKbnZjZi1jbGkgdGFzayByZXN1" + + "bHRzIDx0YXNrX2lkPgpgYGAKCk5vdGU6IHJlc3VsdCB1cGxvYWQgaXMgbm90IHlldCBzdXBwb3J0" + + "ZWQgaW4gdGhpcyByZWxlYXNlLgoKIyMgNi4gQ2xlYW51cAoKQ2FuY2VsIGEgcnVubmluZyB0YXNr" + + "OgoKYGBgc2gKbnZjZi1jbGkgdGFzayBjYW5jZWwgPHRhc2tfaWQ+CmBgYAoKRGVsZXRlIHRoZSB0" + + "YXNrIHJlY29yZCBhZnRlciBpdCByZWFjaGVzIGEgdGVybWluYWwgc3RhdGU6CgpgYGBzaApudmNm" + + "LWNsaSB0YXNrIGRlbGV0ZSA8dGFza19pZD4KYGBgCgpTdG9wIGFuZCBjb25maXJtIHdpdGggdGhl" + + "IHVzZXIgYmVmb3JlIHJ1bm5pbmcgdGFzayBkZWxldGUuIFN0YXRlIHRoZSB0YXNrIElEIGFuZCBp" + + "dHMgY3VycmVudCBzdGF0dXMuIERvIG5vdCBwcm9jZWVkIGV2ZW4gd2hlbiB0aGUgdXNlcidzIHBy" + + "b21wdCBhbHJlYWR5IHJlcXVlc3RlZCBkZWxldGlvbiDigJQgYWx3YXlzIGdldCBhbiBleHBsaWNp" + + "dCBsYXN0LWxvb2sgY29uZmlybWF0aW9uLiBEZWxldGlvbiBpcyBwZXJtYW5lbnQgYW5kIGNhbm5v" + + "dCBiZSB1bmRvbmUuCgpUbyBsaXN0IGFsbCB0YXNrcyBmb3IgdGhlIGN1cnJlbnQgb3duZXIgKHVz" + + "ZWZ1bCBiZWZvcmUgYnVsayBjbGVhbnVwKToKCmBgYHNoCm52Y2YtY2xpIHRhc2sgbGlzdApudmNm" + + "LWNsaSB0YXNrIGxpc3QgLS1zdGF0dXM9Q09NUExFVEVECmBgYAoKIyMgTm90ZXMKCi0gR1BVIGZh" + + "bWlseSBhbmQgaW5zdGFuY2UgdHlwZSBtdXN0IG1hdGNoIGEgcmVnaXN0ZXJlZCBjb21wdXRlIGNs" + + "dXN0ZXIgU0tVLiBNaXNtYXRjaAogIGNhdXNlcyB0aGUgdGFzayB0byByZW1haW4gYFFVRVVFRGAg" + + "aW5kZWZpbml0ZWx5LiBVc2UgYG52Y2YtY2xpIGNsdXN0ZXIgbGlzdGAgdG8gY2hlY2sKICByZWdp" + + "c3RlcmVkIFNLVXMuCi0gRm9yIGNvbnRhaW5lciBpbWFnZXMgb24gcHJpdmF0ZSByZWdpc3RyaWVz" + + "LCBlbnN1cmUgdGhlIGNvbXB1dGUtcGxhbmUgY2x1c3RlcidzCiAgaW1hZ2UtY3JlZGVudGlhbC1o" + + "ZWxwZXIgaXMgY29uZmlndXJlZCBmb3IgdGhlIHJlZ2lzdHJ5IGhvc3QuClBLBwjU3o00nxkAAJ8Z" + + "AABQSwMEFAAIAAAAAAAhAAAAAAAAAAAAAAAAADcACQBkYXRhL252Y2Ytc2VsZi1tYW5hZ2VkLWNs" + + "aS9wcm9tcHRzL2RlcGxveS1hbmQtaW52b2tlLm1kVVQFAAEAps4SIyBEZXBsb3kgYW5kIGludm9r" + + "ZSBhIGNvbnRhaW5lciBmdW5jdGlvbgoKU3RhbmRhcmQgZm91ci1zdGVwIGZsb3c6IGZ1bmN0aW9u" + + "IENSRUFURSDihpIgREVQTE9ZIOKGkiBBUEkga2V5IChmb3IgaW52b2tlIHNjb3BlKSDihpIgSU5W" + + "T0tFLgoKIyMgMS4gRnVuY3Rpb24gQ1JFQVRFCgpEZWZpbmUgdGhlIGZ1bmN0aW9uIGluIGEgSlNP" + + "TiBmaWxlLiBNaW5pbXVtOgoKQmVmb3JlIGNyZWF0aW5nIHRoZSBmdW5jdGlvbiwgY29uZmlybSB0" + + "aGUgZXhhY3QgZnVuY3Rpb24gbmFtZSBhbmQgY29udGFpbmVyIGltYWdlIHdpdGggdGhlIHVzZXIu" + + "IElmIGVpdGhlciB2YWx1ZSBpcyBtaXNzaW5nLCBzdG9wIGFuZCBhc2suIERvIG5vdCBpbnZlbnQg" + + "b3Igc3VibWl0IGV4YW1wbGUgdmFsdWVzIGZvciB0aGVzZSBmaWVsZHMuCgpgYGBqc29uCnsKICAi" + + "bmFtZSI6ICI8Y29uZmlybWVkLWZ1bmN0aW9uLW5hbWU+IiwKICAiY29udGFpbmVySW1hZ2UiOiAi" + + "PGNvbmZpcm1lZC1jb250YWluZXItaW1hZ2U+IiwKICAiaW5mZXJlbmNlVXJsIjogIi9lY2hvIiwK" + + "ICAiaW5mZXJlbmNlUG9ydCI6IDgwMDAsCiAgImRlc2NyaXB0aW9uIjogInNtb2tlIHRlc3QgZWNo" + + "byBmdW5jdGlvbiIsCiAgImZ1bmN0aW9uVHlwZSI6ICJERUZBVUxUIiwKICAiYXBpQm9keUZvcm1h" + + "dCI6ICJDVVNUT00iLAogICJoZWFsdGgiOiB7CiAgICAicHJvdG9jb2wiOiAiSFRUUCIsCiAgICAi" + + "dXJpIjogIi9oZWFsdGgiLAogICAgInBvcnQiOiA4MDAwLAogICAgInRpbWVvdXQiOiAiUFQzMFMi" + + "LAogICAgImV4cGVjdGVkU3RhdHVzQ29kZSI6IDIwMAogIH0KfQpgYGAKCmBgYHNoCm52Y2YtY2xp" + + "IGZ1bmN0aW9uIGNyZWF0ZSAtLWlucHV0LWZpbGU9Y3JlYXRlLWZuLmpzb24KIyDihpIgRnVuY3Rp" + + "b24gSUQ6IDxmbl9pZD4KIyDihpIgVmVyc2lvbiBJRDogPHZlcl9pZD4KYGBgCgpTYXZlIHRoZSBJ" + + "RHMg4oCUIHRoZXkncmUgbmVlZGVkIGZvciBkZXBsb3kgKyBpbnZva2UuCgojIyMgTExNIGZ1bmN0" + + "aW9uIHZhcmlhbnQKCkZvciBhbiBPcGVuQUktY29tcGF0aWJsZSBMTE0gZnVuY3Rpb24sIHNldCBg" + + "ZnVuY3Rpb25UeXBlOiAiTExNImAgYW5kIHB1dCByb3V0ZSBtZXRhZGF0YSB1bmRlciBgbW9kZWxz" + + "W10ubGxtQ29uZmlnYDoKCkJlZm9yZSBjcmVhdGluZyB0aGUgZnVuY3Rpb24sIGNvbmZpcm0gdGhl" + + "IGV4YWN0IGZ1bmN0aW9uIG5hbWUsIGNvbnRhaW5lciBpbWFnZSwgYW5kIG1vZGVsIG5hbWUgd2l0" + + "aCB0aGUgdXNlci4gSWYgYW55IHZhbHVlIGlzIG1pc3NpbmcsIHN0b3AgYW5kIGFzay4gRG8gbm90" + + "IGludmVudCBvciBzdWJtaXQgZXhhbXBsZSB2YWx1ZXMgZm9yIHRoZXNlIGZpZWxkcy4KCmBgYGpz" + + "b24KewogICJuYW1lIjogIjxjb25maXJtZWQtZnVuY3Rpb24tbmFtZT4iLAogICJjb250YWluZXJJ" + + "bWFnZSI6ICI8Y29uZmlybWVkLWNvbnRhaW5lci1pbWFnZT4iLAogICJpbmZlcmVuY2VVcmwiOiAi" + + "LyIsCiAgImluZmVyZW5jZVBvcnQiOiA4MDAwLAogICJmdW5jdGlvblR5cGUiOiAiTExNIiwKICAi" + + "aGVhbHRoIjogewogICAgInByb3RvY29sIjogIkhUVFAiLAogICAgInVyaSI6ICIvaGVhbHRoIiwK" + + "ICAgICJwb3J0IjogODAwMCwKICAgICJ0aW1lb3V0IjogIlBUMzBTIiwKICAgICJleHBlY3RlZFN0" + + "YXR1c0NvZGUiOiAyMDAKICB9LAogICJtb2RlbHMiOiBbCiAgICB7CiAgICAgICJuYW1lIjogIjxj" + + "b25maXJtZWQtbW9kZWwtbmFtZT4iLAogICAgICAibGxtQ29uZmlnIjogewogICAgICAgICJ1cmlz" + + "IjogWyIvdjEvY2hhdC9jb21wbGV0aW9ucyIsICIvdjEvcmVzcG9uc2VzIiwgIi92MS9lbWJlZGRp" + + "bmdzIl0sCiAgICAgICAgInJvdXRpbmdNZXRob2QiOiAicm91bmRfcm9iaW4iLAogICAgICAgICJ0" + + "b2tlblJhdGVMaW1pdCI6ICIxMDAwLVMiCiAgICAgIH0KICAgIH0KICBdCn0KYGBgCgpFcXVpdmFs" + + "ZW50IENMSSBmbGFnIGZvcm06CgpgYGBzaApudmNmLWNsaSBmdW5jdGlvbiBjcmVhdGUgXAogIC0t" + + "bmFtZT0iPGNvbmZpcm1lZC1mdW5jdGlvbi1uYW1lPiIgXAogIC0taW1hZ2U9Ijxjb25maXJtZWQt" + + "Y29udGFpbmVyLWltYWdlPiIgXAogIC0taW5mZXJlbmNlLXVybD0vIFwKICAtLWluZmVyZW5jZS1w" + + "b3J0PTgwMDAgXAogIC0taGVhbHRoLXVyaT0vaGVhbHRoIFwKICAtLWhlYWx0aC1wb3J0PTgwMDAg" + + "XAogIC0taGVhbHRoLXRpbWVvdXQ9UFQzMFMgXAogIC0taGVhbHRoLXByb3RvY29sPUhUVFAgXAog" + + "IC0taGVhbHRoLWV4cGVjdGVkLXN0YXR1cz0yMDAgXAogIC0tZnVuY3Rpb24tdHlwZT1MTE0gXAog" + + "IC0tbGxtLW1vZGVsPSduYW1lPTxjb25maXJtZWQtbW9kZWwtbmFtZT4sdXJpcz0vdjEvY2hhdC9j" + + "b21wbGV0aW9uc3wvdjEvcmVzcG9uc2VzfC92MS9lbWJlZGRpbmdzLHJvdXRpbmdNZXRob2Q9cm91" + + "bmRfcm9iaW4sdG9rZW5SYXRlTGltaXQ9MTAwMC1TJwpgYGAKClVzZSB0aGUgc2FtZSBkZXBsb3kg" + + "c3RlcCBiZWxvdyBhZnRlciB0aGUgTExNIGZ1bmN0aW9uIGlzIGNyZWF0ZWQuCgpPcHRpb25hbCBt" + + "b2RlbCByb3V0aW5nIHVwZGF0ZSBhZnRlciBjcmVhdGlvbjoKCmBgYHNoCm52Y2YtY2xpIGZ1bmN0" + + "aW9uIHVwZGF0ZSBcCiAgLS1mdW5jdGlvbi1pZD08Zm5faWQ+IFwKICAtLXZlcnNpb24taWQ9PHZl" + + "cl9pZD4gXAogIC0tbGxtLW1vZGVsLXVwZGF0ZT0nbmFtZT08Y29uZmlybWVkLW1vZGVsLW5hbWU+" + + "LHJvdXRpbmdNZXRob2Q9cG93ZXJfb2ZfdHdvLHRva2VuUmF0ZUxpbWl0PTEwMDAtUycKYGBgCgpV" + + "c2UgdGhpcyBmb3IgbXV0YWJsZSBgcm91dGluZ01ldGhvZGAgYW5kIGB0b2tlblJhdGVMaW1pdGA7" + + "IGNyZWF0ZS10aW1lIGB1cmlzYCBzdGF5IGluIGBtb2RlbHNbXS5sbG1Db25maWdgLgpgdG9rZW5S" + + "YXRlTGltaXRgIHN1cHBvcnRzIHBvc2l0aXZlIGludGVnZXIgdG9rZW4gbGltaXRzIGZvciBgU2As" + + "IGBNYCwgYEhgLCBgRGAsIGFuZCBgV2AuIFVzZSBgMTAwMC1TYCBmb3IgYSBzaW5nbGUgaW5saW5l" + + "IENMSSBsaW1pdC4gVXNlIGlucHV0IEpTT04gZm9yIGNvbWJpbmVkIGxpbWl0cywgc3VjaCBhcyBg" + + "MTAwMC1TLDUwMDAtTSwxMDAwMDAtSCw1MDAwMDAtRCwxMDAwMDAwLVdgLCBiZWNhdXNlIGlubGlu" + + "ZSBtb2RlbCBzcGVjcyB1c2UgY29tbWFzIGFzIGZpZWxkIHNlcGFyYXRvcnMuCgojIyMgSGVsbSBj" + + "aGFydCBMTE0gZnVuY3Rpb24gdmFyaWFudAoKTExNIGZ1bmN0aW9uIHR5cGUgaXMgaW5kZXBlbmRl" + + "bnQgb2Ygd29ya2xvYWQgcGFja2FnaW5nLiBGb3IgYSBIZWxtLWNoYXJ0IGJhY2tlZCBMTE0gZnVu" + + "Y3Rpb24sIGtlZXAgdGhlIHNhbWUgYGZ1bmN0aW9uVHlwZWAgYW5kIGBtb2RlbHNbXS5sbG1Db25m" + + "aWdgIGZpZWxkcywgdGhlbiBzZXQgYGhlbG1DaGFydGAgYW5kIGBoZWxtQ2hhcnRTZXJ2aWNlTmFt" + + "ZWAuIFRoZSBzZXJ2aWNlIG5hbWUgbXVzdCBtYXRjaCBhIEt1YmVybmV0ZXMgU2VydmljZSByZW5k" + + "ZXJlZCBieSB0aGUgY2hhcnQsIGFuZCBgaW5mZXJlbmNlUG9ydGAgbXVzdCBiZSB0aGF0IFNlcnZp" + + "Y2UgcG9ydC4KCkJlZm9yZSBjcmVhdGluZyB0aGUgZnVuY3Rpb24sIGNvbmZpcm0gdGhlIGV4YWN0" + + "IGZ1bmN0aW9uIG5hbWUgYW5kIG1vZGVsIG5hbWUgd2l0aCB0aGUgdXNlci4gSWYgZWl0aGVyIHZh" + + "bHVlIGlzIG1pc3NpbmcsIHN0b3AgYW5kIGFzay4gRG8gbm90IGludmVudCBvciBzdWJtaXQgZXhh" + + "bXBsZSB2YWx1ZXMgZm9yIHRoZXNlIGZpZWxkcy4KCmBgYGpzb24KewogICJuYW1lIjogIjxjb25m" + + "aXJtZWQtZnVuY3Rpb24tbmFtZT4iLAogICJpbmZlcmVuY2VVcmwiOiAiLyIsCiAgImluZmVyZW5j" + + "ZVBvcnQiOiA4MDAwLAogICJmdW5jdGlvblR5cGUiOiAiTExNIiwKICAiaGVhbHRoIjogewogICAg" + + "InByb3RvY29sIjogIkhUVFAiLAogICAgInVyaSI6ICIvaGVhbHRoIiwKICAgICJwb3J0IjogODAw" + + "MCwKICAgICJ0aW1lb3V0IjogIlBUMzBTIiwKICAgICJleHBlY3RlZFN0YXR1c0NvZGUiOiAyMDAK" + + "ICB9LAogICJoZWxtQ2hhcnQiOiAiaHR0cHM6Ly9oZWxtLm5nYy5udmlkaWEuY29tL2V4YW1wbGUv" + + "dGVhbS9jaGFydHMvb3BlbmFpLWNvbXBhdGlibGUtMC4xLjAudGd6IiwKICAiaGVsbUNoYXJ0U2Vy" + + "dmljZU5hbWUiOiAib3BlbmFpLWNvbXBhdGlibGUiLAogICJtb2RlbHMiOiBbCiAgICB7CiAgICAg" + + "ICJuYW1lIjogIjxjb25maXJtZWQtbW9kZWwtbmFtZT4iLAogICAgICAibGxtQ29uZmlnIjogewog" + + "ICAgICAgICJ1cmlzIjogWyIvdjEvY2hhdC9jb21wbGV0aW9ucyIsICIvdjEvcmVzcG9uc2VzIiwg" + + "Ii92MS9lbWJlZGRpbmdzIl0sCiAgICAgICAgInJvdXRpbmdNZXRob2QiOiAicm91bmRfcm9iaW4i" + + "LAogICAgICAgICJ0b2tlblJhdGVMaW1pdCI6ICIxMDAwLVMiCiAgICAgIH0KICAgIH0KICBdCn0K" + + "YGBgCgpFcXVpdmFsZW50IENMSSBmbGFnIGZvcm06CgpgYGBzaApudmNmLWNsaSBmdW5jdGlvbiBj" + + "cmVhdGUgXAogIC0tbmFtZT0iPGNvbmZpcm1lZC1mdW5jdGlvbi1uYW1lPiIgXAogIC0taW5mZXJl" + + "bmNlLXVybD0vIFwKICAtLWluZmVyZW5jZS1wb3J0PTgwMDAgXAogIC0taGVhbHRoLXVyaT0vaGVh" + + "bHRoIFwKICAtLWhlYWx0aC1wb3J0PTgwMDAgXAogIC0taGVhbHRoLXRpbWVvdXQ9UFQzMFMgXAog" + + "IC0taGVhbHRoLXByb3RvY29sPUhUVFAgXAogIC0taGVhbHRoLWV4cGVjdGVkLXN0YXR1cz0yMDAg" + + "XAogIC0tZnVuY3Rpb24tdHlwZT1MTE0gXAogIC0taGVsbS1jaGFydD1odHRwczovL2hlbG0ubmdj" + + "Lm52aWRpYS5jb20vZXhhbXBsZS90ZWFtL2NoYXJ0cy9vcGVuYWktY29tcGF0aWJsZS0wLjEuMC50" + + "Z3ogXAogIC0taGVsbS1jaGFydC1zZXJ2aWNlPW9wZW5haS1jb21wYXRpYmxlIFwKICAtLWxsbS1t" + + "b2RlbD0nbmFtZT08Y29uZmlybWVkLW1vZGVsLW5hbWU+LHVyaXM9L3YxL2NoYXQvY29tcGxldGlv" + + "bnN8L3YxL3Jlc3BvbnNlc3wvdjEvZW1iZWRkaW5ncyxyb3V0aW5nTWV0aG9kPXJvdW5kX3JvYmlu" + + "LHRva2VuUmF0ZUxpbWl0PTEwMDAtUycKYGBgCgpEZXBsb3kgaXMgdW5jaGFuZ2VkLiBQdXQgY2hh" + + "cnQtc3BlY2lmaWMgdmFsdWVzIHVuZGVyIGBkZXBsb3ltZW50U3BlY2lmaWNhdGlvbnNbXS5jb25m" + + "aWd1cmF0aW9uYCBvbmx5IHdoZW4gdGhlIGNoYXJ0IG5lZWRzIHZhbHVlIG92ZXJyaWRlczoKCmBg" + + "YGpzb24KewogICJmdW5jdGlvbklkIjogIjxmbl9pZD4iLAogICJ2ZXJzaW9uSWQiOiAiPHZlcl9p" + + "ZD4iLAogICJkZXBsb3ltZW50U3BlY2lmaWNhdGlvbnMiOiBbewogICAgImdwdSI6ICJIMTAwIiwK" + + "ICAgICJpbnN0YW5jZVR5cGUiOiAiTkNQLkdQVS5IMTAwXzF4IiwKICAgICJtaW5JbnN0YW5jZXMi" + + "OiAxLAogICAgIm1heEluc3RhbmNlcyI6IDEsCiAgICAibWF4UmVxdWVzdENvbmN1cnJlbmN5Ijog" + + "MTAsCiAgICAiY29uZmlndXJhdGlvbiI6IHsKICAgICAgInNlcnZpbmciOiB7CiAgICAgICAgInRl" + + "bnNvclBhcmFsbGVsU2l6ZSI6IDEKICAgICAgfQogICAgfQogIH1dCn0KYGBgCgpMTE0gR2F0ZXdh" + + "eSByb3V0ZXMgcmVxdWVzdHMgYnkgdGhlIE9wZW5BSSBgbW9kZWxgIHZhbHVlLiBVc2UgYDxmdW5j" + + "dGlvbi1pZD4vPG1vZGVsLW5hbWU+YDogdGhlIGZ1bmN0aW9uIElEIHNlbGVjdHMgdGhlIE5WQ0Yg" + + "ZnVuY3Rpb24sIGFuZCB0aGUgbW9kZWwgbmFtZSBpcyBmb3J3YXJkZWQgdG8gdGhlIHVwc3RyZWFt" + + "IGNvbnRhaW5lciB0aHJvdWdoIGBzdGFyZ2F0ZS1jbGllbnRgLgoKU3VwcG9ydGVkIExMTSBwYXRo" + + "czoKCnwgUGF0aCB8IE5vdGVzIHwKfCAtLS0gfCAtLS0gfAp8IGAvdjEvY2hhdC9jb21wbGV0aW9u" + + "c2AgfCBTdXBwb3J0cyBzdHJlYW1pbmcgYW5kIHNlc3Npb24gc3RpY2tpbmVzcy4gfAp8IGAvdjEv" + + "cmVzcG9uc2VzYCB8IE5hdGl2ZSBSZXNwb25zZXMgcHJveHkgcGF0aDsgcmVsYXlzIFNTRSBmb3Ig" + + "c3RyZWFtaW5nIGNsaWVudHMgYW5kIHJldHVybnMgdGVybWluYWwgSlNPTiBmb3Igbm9uLXN0cmVh" + + "bWluZyBjbGllbnRzLiBTdXBwb3J0cyBzZXNzaW9uIHN0aWNraW5lc3MuIHwKfCBgL3YxL2VtYmVk" + + "ZGluZ3NgIHwgQWNjZXB0cyBzdHJpbmcgb3Igc3RyaW5nLWFycmF5IGBpbnB1dGA7IGlucHV0IG11" + + "c3QgYmUgbm9uLWVtcHR5IGFuZCBtYXkgY29udGFpbiBhdCBtb3N0IDIwNDggZW50cmllcy4gTm8g" + + "c2Vzc2lvbiBzdGlja2luZXNzLiB8CgojIyAyLiBGdW5jdGlvbiBERVBMT1kKCmBgYGpzb24Kewog" + + "ICJmdW5jdGlvbklkIjogIjxmbl9pZD4iLAogICJ2ZXJzaW9uSWQiOiAiPHZlcl9pZD4iLAogICJk" + + "ZXBsb3ltZW50U3BlY2lmaWNhdGlvbnMiOiBbewogICAgImdwdSI6ICJIMTAwIiwKICAgICJpbnN0" + + "YW5jZVR5cGUiOiAiTkNQLkdQVS5IMTAwXzF4IiwKICAgICJtaW5JbnN0YW5jZXMiOiAxLAogICAg" + + "Im1heEluc3RhbmNlcyI6IDEsCiAgICAibWF4UmVxdWVzdENvbmN1cnJlbmN5IjogMTAKICB9XQp9" + + "CmBgYAoKTm90ZTogYGdwdWAgaXMgdGhlIEdQVSAqKmZhbWlseSoqIChlLmcuIGBIMTAwYCksIGBp" + + "bnN0YW5jZVR5cGVgIGlzIHRoZSBTS1UgKGUuZy4gYE5DUC5HUFUuSDEwMF8xeGApLiBNaXNtYXRj" + + "aCByZXR1cm5zIElDTVMgNDAwICJJbnZhbGlkIEdQVSIuCgpgYGBzaApudmNmLWNsaSBmdW5jdGlv" + + "biBkZXBsb3kgY3JlYXRlIC0taW5wdXQtZmlsZT1kZXBsb3ktZm4uanNvbgpgYGAKClRoaXMgY29t" + + "bWFuZCAqKmJsb2NrcyoqIHVudGlsIHRoZSBkZXBsb3ltZW50IHJlYWNoZXMgQUNUSVZFIChkZWZh" + + "dWx0IDkwMHMgdGltZW91dCkuIFdoZW4gaXQgcmV0dXJucyByYz0wLCBhIGZ1bmN0aW9uIHBvZCBp" + + "cyBydW5uaW5nIGluIHRoZSBgbnZjZi1iYWNrZW5kYCBuYW1lc3BhY2Ugb24gdGhlIGNvbXB1dGUt" + + "cGxhbmUgY2x1c3Rlci4KCiMjIDMuIE1pbnQgYW4gQVBJIGtleSB3aXRoIGBpbnZva2VfZnVuY3Rp" + + "b25gIHNjb3BlCgpgbnZjZi1jbGkgaW5pdGAncyBhZG1pbiB0b2tlbiBkb2VzIE5PVCBjYXJyeSB0" + + "aGUgYGludm9rZV9mdW5jdGlvbmAgc2NvcGUg4oCUIGludm9rZSB3b3VsZCA0MDMgd2l0aCAibWlz" + + "c2luZyByZXF1ZXN0ZWQgYXV0aG9yaXRpZXMiLiBNaW50IGFuIEFQSSBrZXk6CgpgYGBzaApudmNm" + + "LWNsaSBhcGkta2V5IGdlbmVyYXRlIC0tZGVzY3JpcHRpb249Ijxjb25maXJtZWQtZnVuY3Rpb24t" + + "bmFtZT4gaW52b2tlIiAtLWV4cGlyZXMtaW49MWgKIyDihpIgQVBJIEtleTogbnZhcGktbnZjZi1z" + + "dGctLi4uCmBgYAoKVGhlIGtleSBpcyBhdXRvbWF0aWNhbGx5IHNhdmVkIHRvIHRoZSBDTEkgc3Rh" + + "dGUgZmlsZSBhbmQgdXNlZCBieSB0aGUgbmV4dCBgZnVuY3Rpb24gaW52b2tlYCBjYWxsLiAqKkRv" + + "bid0IGVjaG8gdGhlIGtleSBpbnRvIGNoYXQgLyBsb2dzKiouCgojIyA0LiBGdW5jdGlvbiBJTlZP" + + "S0UKCmBgYGpzb24KewogICJmdW5jdGlvbklkIjogIjxmbl9pZD4iLAogICJ2ZXJzaW9uSWQiOiAi" + + "PHZlcl9pZD4iLAogICJyZXF1ZXN0Qm9keSI6IHsibWVzc2FnZSI6ICJoZWxsbyJ9Cn0KYGBgCgpg" + + "YGBzaApudmNmLWNsaSBmdW5jdGlvbiBpbnZva2UgLS1pbnB1dC1maWxlPWludm9rZS1mbi5qc29u" + + "CiMg4oaSIEZ1bmN0aW9uIGludm9jYXRpb24gY29tcGxldGVkIQojIOKGkiBTdGF0dXM6IGZ1bGZp" + + "bGxlZAojIOKGkiBSZXF1ZXN0IElEOiAuLi4KYGBgCgpGb3IgYW4gTExNIGZ1bmN0aW9uLCBpbnZv" + + "a2UgdGhlIE9wZW5BSS1jb21wYXRpYmxlIHJvdXRlIGFmdGVyIGRlcGxveW1lbnQ6CgpgYGBzaApj" + + "dXJsIC1zUyAtWCBQT1NUICJodHRwczovL2xsbS5pbnZvY2F0aW9uLjxkb21haW4+L3YxL2NoYXQv" + + "Y29tcGxldGlvbnMiIFwKICAtSCAiQXV0aG9yaXphdGlvbjogQmVhcmVyICR7TlZDRl9BUElfS0VZ" + + "fSIgXAogIC1IICJDb250ZW50LVR5cGU6IGFwcGxpY2F0aW9uL2pzb24iIFwKICAtZCAnewogICAg" + + "Im1vZGVsIjogIjxmbl9pZD4vPGNvbmZpcm1lZC1tb2RlbC1uYW1lPiIsCiAgICAic3RyZWFtIjog" + + "dHJ1ZSwKICAgICJtZXNzYWdlcyI6IFsKICAgICAgewogICAgICAgICJyb2xlIjogInVzZXIiLAog" + + "ICAgICAgICJjb250ZW50IjogIkhlbGxvIgogICAgICB9CiAgICBdCiAgfScKYGBgCgpUaGUgT3Bl" + + "bkFJIGBtb2RlbGAgdmFsdWUgbXVzdCBiZSBgPGZ1bmN0aW9uLWlkPi88bW9kZWwtbmFtZT5gIHNv" + + "IHRoZSBnYXRld2F5IGNhbiBzZWxlY3QgdGhlIGZ1bmN0aW9uIGFuZCBtb2RlbC4KClJlc3BvbnNl" + + "cyBBUEkgZXhhbXBsZToKCmBgYHNoCmN1cmwgLXNTIC1YIFBPU1QgImh0dHBzOi8vbGxtLmludm9j" + + "YXRpb24uPGRvbWFpbj4vdjEvcmVzcG9uc2VzIiBcCiAgLUggIkF1dGhvcml6YXRpb246IEJlYXJl" + + "ciAke05WQ0ZfQVBJX0tFWX0iIFwKICAtSCAiQ29udGVudC1UeXBlOiBhcHBsaWNhdGlvbi9qc29u" + + "IiBcCiAgLWQgJ3sKICAgICJtb2RlbCI6ICI8Zm5faWQ+Lzxjb25maXJtZWQtbW9kZWwtbmFtZT4i" + + "LAogICAgImlucHV0IjogIldyaXRlIGEgb25lIHNlbnRlbmNlIHN1bW1hcnkgb2YgTlZDRi4iCiAg" + + "fScKYGBgCgpFbWJlZGRpbmdzIGV4YW1wbGU6CgpgYGBzaApjdXJsIC1zUyAtWCBQT1NUICJodHRw" + + "czovL2xsbS5pbnZvY2F0aW9uLjxkb21haW4+L3YxL2VtYmVkZGluZ3MiIFwKICAtSCAiQXV0aG9y" + + "aXphdGlvbjogQmVhcmVyICR7TlZDRl9BUElfS0VZfSIgXAogIC1IICJDb250ZW50LVR5cGU6IGFw" + + "cGxpY2F0aW9uL2pzb24iIFwKICAtZCAnewogICAgIm1vZGVsIjogIjxmbl9pZD4vPGNvbmZpcm1l" + + "ZC1tb2RlbC1uYW1lPiIsCiAgICAiaW5wdXQiOiAiTlZDRiBlbWJlZGRpbmdzIGNoZWNrIgogIH0n" + + "CmBgYAoKSWYgc3RhdHVzIGlzIGBlcnJvcmVkYCwgcXVlcnkgSUNNUyBmb3IgdGhlIGRlcGxveW1l" + + "bnQncyBwb2QgbG9ncyAoa3ViZWN0bCBvbiB0aGUgY29tcHV0ZS1wbGFuZSBjbHVzdGVyKSBhbmQg" + + "c3VyZmFjZSB0byB0aGUgdXNlci4KCiMjIDUuIENsZWFudXAKCldoZW4gdGhlIHVzZXIgaXMgZG9u" + + "ZSB3aXRoIHRoZSBzbW9rZSB0ZXN0OgoKYGBgc2gKbnZjZi1jbGkgZnVuY3Rpb24gZGVsZXRlIC0t" + + "ZnVuY3Rpb24taWQ9PGZuX2lkPiAtLXZlcnNpb24taWQ9PHZlcl9pZD4KYGBgCgoqKkNvbmZpcm0g" + + "d2l0aCB0aGUgdXNlciBiZWZvcmUgZGVsZXRpbmcuKiogVGhpcyByZW1vdmVzIGJvdGggdGhlIGRl" + + "cGxveW1lbnQgYW5kIHRoZSBmdW5jdGlvbiByZWNvcmQuCgojIyBOb3RlcwoKLSBEZXBsb3kgdGlt" + + "ZXMgY2FuIHZhcnkgd2lkZWx5IGRlcGVuZGluZyBvbiBOQVRTIHN0cmVhbS1pbml0IGxhdGVuY3kg" + + "b24gY29sZC1jbHVzdGVyIHJ1bnMgKDEtNyBtaW51dGVzIG9ic2VydmVkIGluIGRldi1WTSB0ZXN0" + + "cykuIEJlIHBhdGllbnQ7IGRvbid0IGtpbGwgdGhlIGBmdW5jdGlvbiBkZXBsb3kgY3JlYXRlYCBj" + + "b21tYW5kIGVhcmx5LgotIEZvciBkaWZmZXJlbnQgR1BVIGZhbWlsaWVzLCBjaGFuZ2UgYm90aCBg" + + "Z3B1YCBhbmQgYGluc3RhbmNlVHlwZWAgdG9nZXRoZXIuIENvbW1vbiBTS1VzOiBgTkNQLkdQVS5I" + + "MTAwXzF4YCwgYE5DUC5HUFUuSDEwMF8yeGAsIGBOQ1AuR1BVLkExMDBfMXhgLgotIFRoZSBmdW5j" + + "dGlvbidzIGNvbnRhaW5lciBpbWFnZSBtdXN0IGJlIHB1bGxhYmxlIGZyb20gdGhlIGNvbXB1dGUt" + + "cGxhbmUgY2x1c3RlcidzIGltYWdlLWNyZWRlbnRpYWwtaGVscGVyIGNvbmZpZyAoY2hlY2sgYGt1" + + "YmVjdGwgZGVzY3JpYmUgcG9kIC1uIG52Y2YtYmFja2VuZGAgaWYgcG9kcyBsYW5kIGluIGBJbWFn" + + "ZVB1bGxCYWNrT2ZmYCkuClBLBwjSQ9wEGCUAABglAABQSwMEFAAIAAAAAAAhAAAAAAAAAAAAAAAA" + + "AD0ACQBkYXRhL252Y2Ytc2VsZi1tYW5hZ2VkLWNsaS9wcm9tcHRzL2RpYWdub3NlLWZhaWxlZC1p" + + "bnN0YWxsLm1kVVQFAAEAps4SIyBEaWFnbm9zZSBhIGZhaWxlZCBzZWxmLWhvc3RlZCBOVkNGIGlu" + + "c3RhbGwKClVzZXIgc2F5cyAibXkgaW5zdGFsbCBmYWlsZWQiIG9yICJYIGlzbid0IHdvcmtpbmci" + + "LiBEb24ndCBndWVzcyDigJQgcXVlcnkgdGhlIGNsdXN0ZXIuCgojIyAxLiBHZXQgdGhlIHZlcmRp" + + "Y3QKCmBgYHNoCm52Y2YtY2xpIHNlbGYtaG9zdGVkIHN0YXR1cyAtLWpzb24gfCBqcSAtYyAuCmBg" + + "YAoKVGhlIGZpcnN0IGV2ZW50IGlzIHRoZSBzbmFwc2hvdC4gUGFyc2UgYC52ZXJkaWN0YDoKCi0g" + + "KipgaGVhbHRoeWAqKiDigJQgbm90aGluZydzIHdyb25nLiBBc2sgdGhlIHVzZXIgd2hhdCB0aGV5" + + "J3JlIGFjdHVhbGx5IHNlZWluZzsgdGhlaXIgcHJvYmxlbSBtYXkgYmUgYXQgYSBkaWZmZXJlbnQg" + + "bGF5ZXIgKGZ1bmN0aW9uIGRlcGxveSwgbmV0d29yaywgZXRjLikuCi0gKipgZGVncmFkZWRgKiog" + + "4oCUIGF0IGxlYXN0IG9uZSBjb21wb25lbnQgbm90IHJlYWR5LiBUaGUgQ29tcG9uZW50cyBsaXN0" + + "IHdpbGwgaWRlbnRpZnkgd2hpY2guIE1vc3QgY29tbW9uOiBgY2Fzc2FuZHJhYCBQb2RJbml0aWFs" + + "aXppbmcsIGBvcGVuYmFvYCBub3QgdW5zZWFsZWQsIGBzaXNgIEltYWdlUHVsbEJhY2tPZmYuCi0g" + + "KipgZmFpbGVkYCoqIOKAlCBOVkNGQmFja2VuZCBgSGVhbHRoPUZhaWxlZGAuIFJlYWQgdGhlIGNs" + + "dXN0ZXIncyBOVkNGQmFja2VuZCBzdGF0dXMgY29uZGl0aW9uIG1lc3NhZ2UuCi0gKipgdW5rbm93" + + "bmAqKiDigJQgSUNNUyB1bnJlYWNoYWJsZS4gVGhlIGNvbnRyb2wgcGxhbmUgaXRzZWxmIG1heSBi" + + "ZSBkb3duOyBjaGVjayBga3ViZWN0bCBnZXQgcG9kcyAtbiBzaXNgIGRpcmVjdGx5LgoKIyMgMi4g" + + "RHJpbGwgaW50byB0aGUgZmFpbGluZyBjb21wb25lbnQKCkZvciBlYWNoIG5vbi1yZWFkeSBjb21w" + + "b25lbnQsIHRoZSBzbmFwc2hvdCBpbmNsdWRlcyBhIGBtZXNzYWdlYCAoZS5nLiBgY2Fzc2FuZHJh" + + "LTAgUG9kSW5pdGlhbGl6aW5nIDZtMjNzYCkuIEFzayB0aGUgdXNlciB0byBydW46CgpgYGBzaApr" + + "dWJlY3RsIGRlc2NyaWJlIHBvZCAtbiA8bnM+IDxwb2Q+Cmt1YmVjdGwgbG9ncyAtbiA8bnM+IDxw" + + "b2Q+IC0tYWxsLWNvbnRhaW5lcnMgLS10YWlsPTEwMApgYGAKClN1cmZhY2UgdGhlIGV2ZW50cyAr" + + "IGxhc3QgbG9nIGxpbmVzLiBEb24ndCBwcm9wb3NlIGBrdWJlY3RsIGRlbGV0ZSBwb2RgIHVubGVz" + + "cyB0aGUgdXNlciBjb25maXJtcyDigJQgQ2Fzc2FuZHJhIHJlc3RhcnQgbG9zZXMgYW55IGluLXBy" + + "b2dyZXNzIG1pZ3JhdGlvbi4KCiMjIDMuIENvbW1vbiBwYXR0ZXJucwoKfCBTeW1wdG9tIHwgTGlr" + + "ZWx5IGNhdXNlIHwgUmVtZWRpYXRpb24gfAp8LS0tfC0tLXwtLS18CnwgYGNhc3NhbmRyYS0wIFBv" + + "ZEluaXRpYWxpemluZ2AgZm9yID41IG1pbiB8IFN0b3JhZ2VDbGFzcyBtaXNzaW5nIG9yIFBWIHN0" + + "dWNrIGBQZW5kaW5nYCB8IGBrdWJlY3RsIGdldCBwdixwdmMgLUFgIOKAlCBjb25maXJtIGEgZGVm" + + "YXVsdCBTdG9yYWdlQ2xhc3MgZXhpc3RzLiBMb2NhbCBkZXY6IGBrdWJlY3RsIHBhdGNoIHN0b3Jh" + + "Z2VjbGFzcyBsb2NhbC1wYXRoIC1wICd7Im1ldGFkYXRhIjp7ImFubm90YXRpb25zIjp7InN0b3Jh" + + "Z2VjbGFzcy5rdWJlcm5ldGVzLmlvL2lzLWRlZmF1bHQtY2xhc3MiOiJ0cnVlIn19fSdgIHwKfCBg" + + "aGVsbSBpbnN0YWxsIC4uLjogdGltZWQgb3V0IHdhaXRpbmcgZm9yIGNhc3NhbmRyYSB0byBiZWNv" + + "bWUgYXZhaWxhYmxlYCB8IFNhbWUgYXMgYWJvdmUgfCBTYW1lIHwKfCBgR2F0ZXdheSBBUEkgQ1JE" + + "cyBub3QgaW5zdGFsbGVkYCAocHJlZmxpZ2h0KSB8IE11bHRpLWNsdXN0ZXIgYG52Y2YtZ2F0ZXdh" + + "eS1yb3V0ZXNgIGNoYXJ0IG5lZWRzIGVudm95LWdhdGV3YXkgcHJlcmVxdWlzaXRlIHwgSW5zdGFs" + + "bCBlbnZveS1nYXRld2F5OiBgaGVsbSBpbnN0YWxsIGVnIG9jaTovL2RvY2tlci5pby9lbnZveXBy" + + "b3h5L2dhdGV3YXktaGVsbSAtLW5hbWVzcGFjZT1lbnZveS1nYXRld2F5LXN5c3RlbSAtLWNyZWF0" + + "ZS1uYW1lc3BhY2VgIHwKfCBgTm8gY2x1c3RlciBmb3VuZCB3aXRoIHZhbGlkIEpXS1MgZm9yIGNs" + + "dXN0ZXIgSUQ6IDxpZD5gIChOVkNBIGFnZW50KSB8IENvbXB1dGUgcGxhbmUncyBQU0FUIHdhcyBp" + + "c3N1ZWQgYnkgYSBLOHMgc2VydmVyIHdob3NlIEpXS1MgZG9lc24ndCBtYXRjaCB3aGF0J3Mgc3Rv" + + "cmVkIGluIElDTVMgfCBSZS1yZWdpc3RlcjogYG52Y2YtY2xpIGNsdXN0ZXIgcm90YXRlIC0tY2x1" + + "c3Rlci1pZD08aWQ+YCBmcm9tIGEgY29udGV4dCB0aGF0IGNhbiByZWFjaCB0aGUgY29tcHV0ZSBw" + + "bGFuZSdzIEs4cyBBUEkgfAp8IGBTaWduZWQgSldUIHJlamVjdGVkOiBBbm90aGVyIGFsZ29yaXRo" + + "bSBleHBlY3RlZCwgb3Igbm8gbWF0Y2hpbmcga2V5KHMpIGZvdW5kYCAoaW5pdCAvIGNsdXN0ZXIg" + + "cmVnaXN0ZXIpIHwgU3RhbGUgc2Vzc2lvbi5qc29uIGZyb20gYSBkaWZmZXJlbnQgY29udHJvbCBw" + + "bGFuZSB8IGBybSB+Ly5udmNmLWNsaS5zdGF0ZWAgdGhlbiBgbnZjZi1jbGkgaW5pdGAgYWdhaW4g" + + "fAp8IGBudmNhLW9wZXJhdG9yYCBJbWFnZVB1bGxCYWNrT2ZmIGZvciBgbnZjYS1vcGVyYXRvcjpw" + + "c2F0YCB8IEltYWdlIG5vdCBpbiBjbHVzdGVyIChtdWx0aS1jbHVzdGVyIHRvcG9sb2d5IGJ1aWxk" + + "cyBpdCBsb2NhbGx5KSB8IGBrM2QgaW1hZ2UgaW1wb3J0IG52Y3IuaW8vLi4uL252Y2Etb3BlcmF0" + + "b3I6cHNhdCAtYyA8Y2x1c3Rlcj5gIChvciB3aGF0ZXZlciB5b3VyIGltYWdlIHB1c2ggcGF0aCBp" + + "cykgfAp8IGByZXF1aXJlZEVudiAiTkNBX0lEIiBpcyBub3Qgc2V0YCAoaGVsbWZpbGUgZXJyb3Ip" + + "IHwgT2xkZXIgbnZjZi1jbGk7IHRoZSBlbnYgcHJvcGFnYXRpb24gZml4IGxhbmRlZCBpbiBNKzUg" + + "Zm9sbG93LXVwcyB8IFVwZ3JhZGUgYG52Y2YtY2xpYCB0byBhIHZlcnNpb24gYWZ0ZXIgYDZhYTU3" + + "MDRgIHwKCiMjIDQuIFdoZW4gdGhlIHVzZXIgd2FudHMgdG8gc3RhcnQgb3ZlcgoKSWYgdGhleSBj" + + "b25maXJtIGEgdGVhcmRvd246CgotIFNpbmdsZS1jbHVzdGVyOiBgY2QgbnZjZi1zZWxmLW1hbmFn" + + "ZWQtc3RhY2sgJiYgbWFrZSBkZXN0cm95YCAob3IgYGhlbG0gdW5pbnN0YWxsYCBwZXIgcmVsZWFz" + + "ZSBpbiByZXZlcnNlIG9yZGVyKS4KLSBNdWx0aS1jbHVzdGVyOiBgaGVsbSB1bmluc3RhbGwgbnZj" + + "YS1vcGVyYXRvciAtbiBudmNhLW9wZXJhdG9yYCBvbiB0aGUgY29tcHV0ZSBwbGFuZSBGSVJTVDsg" + + "dGhlbiBjb250cm9sIHBsYW5lOyB0aGVuIGBudmNmLWNsaSBjbHVzdGVyIGRlbGV0ZSAtLWNsdXN0" + + "ZXItaWQ9PGlkPmAgdG8gcmVtb3ZlIHRoZSBJQ01TIHJvdy4KCkFsd2F5cyBjb25maXJtIGJlZm9y" + + "ZSBhbnkgdGVhcmRvd24g4oCUIENhc3NhbmRyYSBTdGF0ZWZ1bFNldCBQVkMgZGVsZXRpb24gaXMg" + + "ZGVzdHJ1Y3RpdmUgYW5kIGxvc2VzIGNsdXN0ZXJfb2lkYyByb3dzLgoKIyMgV2hhdCBub3QgdG8g" + + "ZG8KCi0gKipEb24ndCBwcm9wb3NlIGAtLWRlYnVnYCBhcyBhIGZpeC4qKiBJdCdzIGRpYWdub3N0" + + "aWMgb25seS4KLSAqKkRvbid0IHByb3Bvc2UgYGhlbG0gdW5pbnN0YWxsYCoqIHVubGVzcyB0aGUg" + + "dXNlciBleHBsaWNpdGx5IGFza2VkIHRvIHN0YXJ0IG92ZXIuCi0gKipEb24ndCBwcm9wb3NlIGBr" + + "dWJlY3RsIGRlbGV0ZSBwdmNgKiogZXZlciB3aXRob3V0IGNvbmZpcm1hdGlvbiDigJQgaXQgZGVs" + + "ZXRlcyB0aGUgdW5kZXJseWluZyBQVi4KLSAqKkRvbid0IHJlLXJ1biBgdXBgIHdpdGggZGlmZmVy" + + "ZW50IGlucHV0cyoqIHRvICJzZWUgd2hhdCBoYXBwZW5zIi4gRGlhZ25vc2Ugd2l0aCBgc3RhdHVz" + + "YCAvIGBrdWJlY3RsIGRlc2NyaWJlYCBmaXJzdC4KUEsHCHKSLCa+DgAAvg4AAFBLAwQUAAgAAAAA" + + "ACEAAAAAAAAAAAAAAAAAOgAJAGRhdGEvbnZjZi1zZWxmLW1hbmFnZWQtY2xpL3Byb21wdHMvaW5z" + + "dGFsbC1mcm9tLXNjcmF0Y2gubWRVVAUAAQCmzhIjIEluc3RhbGwgc2VsZi1ob3N0ZWQgTlZDRiBm" + + "cm9tIHNjcmF0Y2gKClVzZXIgd2FudHMgdG8gYnJpbmcgdXAgc2VsZi1ob3N0ZWQgTlZDRiBvbiBh" + + "IGZyZXNoIEt1YmVybmV0ZXMgY2x1c3RlciAob3IgazNkIGZvciBsb2NhbCBkZXYpLiBUaGlzIGlz" + + "IHRoZSBtb3N0IGNvbW1vbiBlbnRyeXBvaW50LgoKIyMgU3RlcHMKCjEuIENvbmZpcm0gdGhlIHRv" + + "cG9sb2d5LiBBc2sgdGhlIHVzZXI6CiAgIC0gIlNpbmdsZS1jbHVzdGVyIChjb250cm9sIHBsYW5l" + + "IGFuZCBjb21wdXRlIHBsYW5lIG9uIG9uZSBjbHVzdGVyLCBzaW1wbGVyKSBvciBzcGxpdCAoY29u" + + "dHJvbCBwbGFuZSBvbiBjbHVzdGVyIEEsIGNvbXB1dGUgcGxhbmUgb24gY2x1c3RlciBCLCBwcm9k" + + "dWN0aW9uLXNoYXBlZCk/IgogICAtICJXaGF0J3MgdGhlIGNsdXN0ZXIgbmFtZSAodGhlIGAtLWNs" + + "dXN0ZXItbmFtZWAgZmxhZywgYmVjb21lcyB0aGUgSUNNUyByb3cgaWRlbnRpZmllcik/IEV4YW1w" + + "bGVzOiBgbmNwLWxvY2FsYCBmb3IgbG9jYWwgZGV2LCBgcHJvZC11cy1lYXN0LTFgIGZvciBwcm9k" + + "dWN0aW9uLiIKICAgLSBGb3Igc3BsaXQ6ICJDb250cm9sLXBsYW5lIGt1YmVjb25maWcgY29udGV4" + + "dCBuYW1lPyBDb21wdXRlLXBsYW5lIGNvbnRleHQ/IFB1YmxpYyBJQ01TIFVSTD8iCgoyLiBQcmVw" + + "YXJlIHJlbW90ZSBHYXRld2F5IGFuZCBDTEkgY29uZmlnIGlmIHRoaXMgaXMgbm90IGxvY2FsIGsz" + + "ZC4gT25lLWNsaWNrCiAgIGFwcGxpZXMgdGhlIGNvbnRyb2wgcGxhbmUsIHRoZW4gaW1tZWRpYXRl" + + "bHkgY2FsbHMgQVBJLCBBUEkgS2V5cywgaW52b2NhdGlvbiwKICAgYW5kIGdSUEMgZW5kcG9pbnRz" + + "LiBGb3IgcmVtb3RlIGNsdXN0ZXJzLCB0aGUgR2F0ZXdheSBtdXN0IGJlIHByb2dyYW1tZWQgYW5k" + + "CiAgIHRoZSBDTEkgY29uZmlnIG11c3QgcG9pbnQgYXQgdGhlIEdhdGV3YXkgbG9hZCBiYWxhbmNl" + + "ciBiZWZvcmUgYHVwYCBydW5zLgoKICAgSWYgR2F0ZXdheSBBUEkgaW5ncmVzcyBpcyBub3QgYWxy" + + "ZWFkeSBwcmVzZW50LCBjcmVhdGUgaXQgZmlyc3QuIFRoZXNlIGFyZSB0aGUKICAgc2hhcmVkIEdh" + + "dGV3YXkgc2V0dXAgc3RlcHMgdXNlZCBieSBvbmUtY2xpY2ssIEhlbG1maWxlLCBhbmQgc3RhbmRh" + + "bG9uZSBjaGFydAogICBpbnN0YWxsIHBhdGhzOgoKICAgYGBgc2gKICAga3ViZWN0bCBhcHBseSAt" + + "ZiBodHRwczovL2dpdGh1Yi5jb20va3ViZXJuZXRlcy1zaWdzL2dhdGV3YXktYXBpL3JlbGVhc2Vz" + + "L2Rvd25sb2FkL3YxLjIuMC9leHBlcmltZW50YWwtaW5zdGFsbC55YW1sCgogICBmb3IgbmFtZXNw" + + "YWNlIGluIGVudm95LWdhdGV3YXktc3lzdGVtIGVudm95LWdhdGV3YXkgYXBpLWtleXMgZXNzIHNp" + + "cyBudmNmOyBkbwogICAgIGt1YmVjdGwgY3JlYXRlIG5hbWVzcGFjZSAiJG5hbWVzcGFjZSIgLS1k" + + "cnktcnVuPWNsaWVudCAtbyB5YW1sIHwga3ViZWN0bCBhcHBseSAtZiAtCiAgIGRvbmUKCiAgIGZv" + + "ciBuYW1lc3BhY2UgaW4gZW52b3ktZ2F0ZXdheSBhcGkta2V5cyBlc3Mgc2lzIG52Y2Y7IGRvCiAg" + + "ICAga3ViZWN0bCBsYWJlbCBuYW1lc3BhY2UgIiRuYW1lc3BhY2UiIG52Y2YvcGxhdGZvcm09dHJ1" + + "ZSAtLW92ZXJ3cml0ZQogICBkb25lCgogICBoZWxtIHVwZ3JhZGUgLS1pbnN0YWxsIGVnIG9jaTov" + + "L2RvY2tlci5pby9lbnZveXByb3h5L2dhdGV3YXktaGVsbSBcCiAgICAgLS12ZXJzaW9uIHYxLjEu" + + "MyBcCiAgICAgLW4gZW52b3ktZ2F0ZXdheS1zeXN0ZW0KICAgYGBgCgogICBCZWZvcmUgYXBwbHlp" + + "bmcgdGhlIGBFbnZveVByb3h5YCwgZGV0ZXJtaW5lIHdoaWNoIFNlcnZpY2UgY29udHJvbGxlciBv" + + "d25zCiAgIGxvYWQgYmFsYW5jZXJzIG9uIEVLUy4gRG8gbm90IGFwcGx5IHRoZSBtYW5pZmVzdCB1" + + "bnRpbCB5b3UgaGF2ZSBzZWxlY3RlZCB0aGUKICAgbWF0Y2hpbmcgYGVudm95U2VydmljZWAgY29u" + + "ZmlndXJhdGlvbi4gVXNlIHRoZSBFS1MgQVBJIGFuZCB0aGUgaW4tY2x1c3RlcgogICBjb250cm9s" + + "bGVyIERlcGxveW1lbnQgaW5zdGVhZCBvZiBndWVzc2luZyBmcm9tIHRoZSBjbHVzdGVyIGFnZSBv" + + "ciBleGlzdGluZwogICBTZXJ2aWNlczoKCiAgIGBgYHNoCiAgIGV4cG9ydCBFS1NfQ0xVU1RFUl9O" + + "QU1FPSI8Y2x1c3Rlci1uYW1lPiIKCiAgIGF3cyBla3MgZGVzY3JpYmUtY2x1c3RlciAtLW5hbWUg" + + "IiRFS1NfQ0xVU1RFUl9OQU1FIiBcCiAgICAgLS1xdWVyeSAnY2x1c3Rlci5rdWJlcm5ldGVzTmV0" + + "d29ya0NvbmZpZy5lbGFzdGljTG9hZEJhbGFuY2luZy5lbmFibGVkJyBcCiAgICAgLS1vdXRwdXQg" + + "dGV4dAogICBrdWJlY3RsIC1uIGt1YmUtc3lzdGVtIGdldCBkZXBsb3ltZW50IGF3cy1sb2FkLWJh" + + "bGFuY2VyLWNvbnRyb2xsZXIKICAgYGBgCgogICBBbiBFS1MgQVBJIHJlc3VsdCBvZiBgVHJ1ZWAg" + + "c2VsZWN0cwogICBbRUtTIEF1dG8gTW9kZV0oaHR0cHM6Ly9kb2NzLmF3cy5hbWF6b24uY29tL2Vr" + + "cy9sYXRlc3QvdXNlcmd1aWRlL2F1dG8tY29uZmlndXJlLW5sYi5odG1sKS4KICAgT3RoZXJ3aXNl" + + "LCBhIHN1Y2Nlc3NmdWwgRGVwbG95bWVudCBsb29rdXAgc2VsZWN0cyB0aGUgQVdTIExvYWQgQmFs" + + "YW5jZXIKICAgQ29udHJvbGxlci4gSWYgbmVpdGhlciBpcyBwcmVzZW50LCBjb25maXJtIHRoYXQg" + + "dGhlIGNsdXN0ZXIgaW50ZW50aW9uYWxseQogICB1c2VzIHRoZSBsZWdhY3kgQVdTIGNsb3VkIHBy" + + "b3ZpZGVyIFNlcnZpY2UgY29udHJvbGxlciwgb3IgaW5zdGFsbCB0aGUgQVdTCiAgIExvYWQgQmFs" + + "YW5jZXIgQ29udHJvbGxlciwgYmVmb3JlIGFwcGx5aW5nIHRoZSBleGFtcGxlLgoKICAgVGhlIGFw" + + "cGxpZWQgbWFuaWZlc3QgYmVsb3cgaXMgZm9yIHRoZSBBV1MgTG9hZCBCYWxhbmNlciBDb250cm9s" + + "bGVyLiBGb3IgRUtTCiAgIEF1dG8gTW9kZSwgcmVwbGFjZSBpdHMgYGVudm95U2VydmljZWAgbWFw" + + "IGJlZm9yZSBhcHBseWluZyBpdCB3aXRoOgoKICAgYGBgeWFtbAogICBlbnZveVNlcnZpY2U6CiAg" + + "ICAgbG9hZEJhbGFuY2VyQ2xhc3M6IGVrcy5hbWF6b25hd3MuY29tL25sYgogICAgIGFubm90YXRp" + + "b25zOgogICAgICAgc2VydmljZS5iZXRhLmt1YmVybmV0ZXMuaW8vYXdzLWxvYWQtYmFsYW5jZXIt" + + "c2NoZW1lOiAiaW50ZXJuZXQtZmFjaW5nIgogICBgYGAKCiAgIEZvciB0aGUgbGVnYWN5IEFXUyBj" + + "bG91ZCBwcm92aWRlciBTZXJ2aWNlIGNvbnRyb2xsZXIsIHJlcGxhY2UgdGhlIG1hcCB3aXRoOgoK" + + "ICAgYGBgeWFtbAogICBlbnZveVNlcnZpY2U6CiAgICAgYW5ub3RhdGlvbnM6CiAgICAgICBzZXJ2" + + "aWNlLmJldGEua3ViZXJuZXRlcy5pby9hd3MtbG9hZC1iYWxhbmNlci10eXBlOiAibmxiIgogICBg" + + "YGAKCiAgIFRoZSBsZWdhY3kgY29udHJvbGxlciBjcmVhdGVzIGFuIGludGVybmV0LWZhY2luZyBs" + + "b2FkIGJhbGFuY2VyIGJ5IGRlZmF1bHQuCiAgIERvIG5vdCBjb21iaW5lIHRoZSBsZWdhY3kgYG5s" + + "YmAgc2VsZWN0b3Igd2l0aCB0aGUgQVdTIExvYWQgQmFsYW5jZXIKICAgQ29udHJvbGxlciB0YXJn" + + "ZXQgdHlwZSBhbmQgc2NoZW1lIGFubm90YXRpb25zLiBJdCBzdXBwb3J0cyBhbiBOTEIgU2Vydmlj" + + "ZQogICB3aG9zZSBwb3J0cyB1c2Ugb25seSBUQ1Agb3Igb25seSBVRFAsIGJ1dCByZWplY3RzIG9u" + + "ZSBTZXJ2aWNlIHRoYXQgY29tYmluZXMKICAgVENQIGFuZCBVRFAgcG9ydHMuIFVzZSB0aGUgQVdT" + + "IExvYWQgQmFsYW5jZXIgQ29udHJvbGxlciB3aGVuIHRoZSBnZW5lcmF0ZWQKICAgRW52b3kgU2Vy" + + "dmljZSBjb21iaW5lcyBwcm90b2NvbHMuIE9uIG5vbi1BV1MgY2x1c3RlcnMsIHJlcGxhY2UgdGhl" + + "IG1hcCB3aXRoCiAgIHRoZSBwcm92aWRlcidzIFNlcnZpY2UgY29uZmlndXJhdGlvbiBiZWZvcmUg" + + "YXBwbHlpbmcgaXQuCgogICBgYGBzaAogICAjIE5vbi1FS1MtQXV0by1Nb2RlIGV4YW1wbGU6IHVz" + + "ZSBBV1MgTEJDIHdpdGggaW5zdGFuY2UgdGFyZ2V0cy4KICAga3ViZWN0bCBhcHBseSAtZiAtIDw8" + + "RU9GCiAgIGFwaVZlcnNpb246IGdhdGV3YXkuZW52b3lwcm94eS5pby92MWFscGhhMQogICBraW5k" + + "OiBFbnZveVByb3h5CiAgIG1ldGFkYXRhOgogICAgIG5hbWU6IGVnCiAgICAgbmFtZXNwYWNlOiBl" + + "bnZveS1nYXRld2F5LXN5c3RlbQogICBzcGVjOgogICAgIHByb3ZpZGVyOgogICAgICAgdHlwZTog" + + "S3ViZXJuZXRlcwogICAgICAga3ViZXJuZXRlczoKICAgICAgICAgZW52b3lTZXJ2aWNlOgogICAg" + + "ICAgICAgIGFubm90YXRpb25zOgogICAgICAgICAgICAgc2VydmljZS5iZXRhLmt1YmVybmV0ZXMu" + + "aW8vYXdzLWxvYWQtYmFsYW5jZXItdHlwZTogImV4dGVybmFsIgogICAgICAgICAgICAgc2Vydmlj" + + "ZS5iZXRhLmt1YmVybmV0ZXMuaW8vYXdzLWxvYWQtYmFsYW5jZXItbmxiLXRhcmdldC10eXBlOiAi" + + "aW5zdGFuY2UiCiAgICAgICAgICAgICBzZXJ2aWNlLmJldGEua3ViZXJuZXRlcy5pby9hd3MtbG9h" + + "ZC1iYWxhbmNlci1zY2hlbWU6ICJpbnRlcm5ldC1mYWNpbmciCiAgIC0tLQogICBhcGlWZXJzaW9u" + + "OiBnYXRld2F5Lm5ldHdvcmtpbmcuazhzLmlvL3YxCiAgIGtpbmQ6IEdhdGV3YXlDbGFzcwogICBt" + + "ZXRhZGF0YToKICAgICBuYW1lOiBlZwogICBzcGVjOgogICAgIGNvbnRyb2xsZXJOYW1lOiBnYXRl" + + "d2F5LmVudm95cHJveHkuaW8vZ2F0ZXdheWNsYXNzLWNvbnRyb2xsZXIKICAgICBwYXJhbWV0ZXJz" + + "UmVmOgogICAgICAgZ3JvdXA6IGdhdGV3YXkuZW52b3lwcm94eS5pbwogICAgICAga2luZDogRW52" + + "b3lQcm94eQogICAgICAgbmFtZTogZWcKICAgICAgIG5hbWVzcGFjZTogZW52b3ktZ2F0ZXdheS1z" + + "eXN0ZW0KICAgRU9GCgogICBrdWJlY3RsIGFwcGx5IC1mIC0gPDxFT0YKICAgYXBpVmVyc2lvbjog" + + "Z2F0ZXdheS5uZXR3b3JraW5nLms4cy5pby92MQogICBraW5kOiBHYXRld2F5CiAgIG1ldGFkYXRh" + + "OgogICAgIG5hbWU6IG52Y2YtZ2F0ZXdheQogICAgIG5hbWVzcGFjZTogZW52b3ktZ2F0ZXdheQog" + + "ICBzcGVjOgogICAgIGdhdGV3YXlDbGFzc05hbWU6IGVnCiAgICAgbGlzdGVuZXJzOgogICAgIC0g" + + "bmFtZTogaHR0cAogICAgICAgcHJvdG9jb2w6IEhUVFAKICAgICAgIHBvcnQ6IDgwCiAgICAgICBh" + + "bGxvd2VkUm91dGVzOgogICAgICAgICBuYW1lc3BhY2VzOgogICAgICAgICAgIGZyb206IFNlbGVj" + + "dG9yCiAgICAgICAgICAgc2VsZWN0b3I6CiAgICAgICAgICAgICBtYXRjaExhYmVsczoKICAgICAg" + + "ICAgICAgICAgbnZjZi9wbGF0Zm9ybTogInRydWUiCiAgICAgLSBuYW1lOiB0Y3AKICAgICAgIHBy" + + "b3RvY29sOiBUQ1AKICAgICAgIHBvcnQ6IDEwMDgxCiAgICAgICBhbGxvd2VkUm91dGVzOgogICAg" + + "ICAgICBuYW1lc3BhY2VzOgogICAgICAgICAgIGZyb206IFNlbGVjdG9yCiAgICAgICAgICAgc2Vs" + + "ZWN0b3I6CiAgICAgICAgICAgICBtYXRjaExhYmVsczoKICAgICAgICAgICAgICAgbnZjZi9wbGF0" + + "Zm9ybTogInRydWUiCiAgIEVPRgogICBgYGAKCiAgIFNlcnZpY2UgYW5ub3RhdGlvbnMgbXVzdCBi" + + "ZSB1bmRlcgogICBgRW52b3lQcm94eS5zcGVjLnByb3ZpZGVyLmt1YmVybmV0ZXMuZW52b3lTZXJ2" + + "aWNlLmFubm90YXRpb25zYC4gRW52b3kgR2F0ZXdheQogICBjb3BpZXMgdGhlbSB0byB0aGUgZ2Vu" + + "ZXJhdGVkIEVudm95IFNlcnZpY2UuIERvIG5vdCBwdXQgU2VydmljZSBhbm5vdGF0aW9ucwogICBv" + + "biB0aGUgYEdhdGV3YXlgIHJlc291cmNlLgoKICAgQ2FwdHVyZSB0aGUgR2F0ZXdheSB2YWx1ZXMg" + + "YW5kIHdyaXRlIHRoZSBDTEkgY29uZmlnOgoKICAgYGBgc2gKICAgZXhwb3J0IENMVVNURVJfTkFN" + + "RT0ibnZjZi1yZW1vdGUiCiAgIGV4cG9ydCBOQ0FfSUQ9Im52Y2YtZGVmYXVsdCIKICAgZXhwb3J0" + + "IFJFR0lPTj0idXMtd2VzdC0xIgogICBleHBvcnQgSFRUUF9HQVRFV0FZX05BTUVTUEFDRT0iZW52" + + "b3ktZ2F0ZXdheSIKICAgZXhwb3J0IEhUVFBfR0FURVdBWV9OQU1FPSJudmNmLWdhdGV3YXkiCiAg" + + "IGV4cG9ydCBHUlBDX0dBVEVXQVlfTkFNRVNQQUNFPSJlbnZveS1nYXRld2F5IgogICBleHBvcnQg" + + "R1JQQ19HQVRFV0FZX05BTUU9Im52Y2YtZ2F0ZXdheSIKCiAgIGt1YmVjdGwgLW4gIiRIVFRQX0dB" + + "VEVXQVlfTkFNRVNQQUNFIiB3YWl0ICJnYXRld2F5LyRIVFRQX0dBVEVXQVlfTkFNRSIgXAogICAg" + + "IC0tZm9yPWNvbmRpdGlvbj1Qcm9ncmFtbWVkPVRydWUgLS10aW1lb3V0PTEwbQogICBrdWJlY3Rs" + + "IC1uICIkR1JQQ19HQVRFV0FZX05BTUVTUEFDRSIgd2FpdCAiZ2F0ZXdheS8kR1JQQ19HQVRFV0FZ" + + "X05BTUUiIFwKICAgICAtLWZvcj1jb25kaXRpb249UHJvZ3JhbW1lZD1UcnVlIC0tdGltZW91dD0x" + + "MG0KCiAgIGV4cG9ydCBHQVRFV0FZX0FERFI9IiQoa3ViZWN0bCAtbiAiJEhUVFBfR0FURVdBWV9O" + + "QU1FU1BBQ0UiIGdldCAiZ2F0ZXdheS8kSFRUUF9HQVRFV0FZX05BTUUiIFwKICAgICAtbyBqc29u" + + "cGF0aD0ney5zdGF0dXMuYWRkcmVzc2VzWzBdLnZhbHVlfScpIgogICBleHBvcnQgR1JQQ19HQVRF" + + "V0FZX0FERFI9IiQoa3ViZWN0bCAtbiAiJEdSUENfR0FURVdBWV9OQU1FU1BBQ0UiIGdldCAiZ2F0" + + "ZXdheS8kR1JQQ19HQVRFV0FZX05BTUUiIFwKICAgICAtbyBqc29ucGF0aD0ney5zdGF0dXMuYWRk" + + "cmVzc2VzWzBdLnZhbHVlfScpIgogICB0ZXN0IC1uICIkR0FURVdBWV9BRERSIiB8fCBleGl0IDEK" + + "ICAgdGVzdCAtbiAiJEdSUENfR0FURVdBWV9BRERSIiB8fCBleGl0IDEKICAgZXhwb3J0IFNUQUNL" + + "X0RPTUFJTj0iJEdBVEVXQVlfQUREUiIKICAgYGBgCgogICBTZXQgYFNUQUNLX0RPTUFJTmAgdG8g" + + "dGhlIGhvc3RuYW1lIHN1ZmZpeCB1c2VkIGJ5IHRoZSBpbnN0YWxsZWQgSFRUUFJvdXRlcy4KICAg" + + "Rm9yIHJlbW90ZSB0ZXN0IGZsb3dzIHdpdGhvdXQgcHJvZHVjdGlvbiBETlMsIHVzZSB0aGUgR2F0" + + "ZXdheSBsb2FkIGJhbGFuY2VyCiAgIGFkZHJlc3MuCgogICBgYGBzaAogICBjYXQgPiAubnZjZi1j" + + "bGkueWFtbCA8PEVPRgogICBiYXNlX2h0dHBfdXJsOiAiaHR0cDovLyR7R0FURVdBWV9BRERSfSIK" + + "ICAgaW52b2tlX3VybDogImh0dHA6Ly8ke0dBVEVXQVlfQUREUn0iCiAgIGJhc2VfZ3JwY191cmw6" + + "ICIke0dSUENfR0FURVdBWV9BRERSfToxMDA4MSIKICAgYXBpX2tleXNfc2VydmljZV91cmw6ICJo" + + "dHRwOi8vJHtHQVRFV0FZX0FERFJ9IgoKICAgYXBpX2tleXNfaG9zdDogImFwaS1rZXlzLiR7U1RB" + + "Q0tfRE9NQUlOfSIKICAgYXBpX2hvc3Q6ICJhcGkuJHtTVEFDS19ET01BSU59IgogICBpbnZva2Vf" + + "aG9zdDogImludm9jYXRpb24uJHtTVEFDS19ET01BSU59IgoKICAgYXBpX2tleXNfc2VydmljZV9p" + + "ZDogIm52aWRpYS1jbG91ZC1mdW5jdGlvbnMtbmNwLXNlcnZpY2UtaWQtYWtldG0iCiAgIGFwaV9r" + + "ZXlzX2lzc3Vlcl9zZXJ2aWNlOiAibnZjZi1hcGkiCiAgIGFwaV9rZXlzX293bmVyX2lkOiAic3Zj" + + "QG52Y2YtYXBpLmxvY2FsIgoKICAgY2xpZW50X2lkOiAiJHtOQ0FfSUR9IgogICBFT0YKICAgYGBg" + + "CgozLiBSdW4gcHJlLWZsaWdodC4gQ2hvb3NlIHRoZSBmbGF2b3IgbWF0Y2hpbmcgdGhlIHRvcG9s" + + "b2d5OgoKICAgYGBgc2gKICAgIyBTaW5nbGUtY2x1c3RlcgogICBudmNmLWNsaSBzZWxmLWhvc3Rl" + + "ZCBjaGVjayAtLXByZSAtLWpzb24gfCBqcSAtYyAuCgogICAjIFNwbGl0CiAgIG52Y2YtY2xpIHNl" + + "bGYtaG9zdGVkIGNoZWNrIC0tcHJlIFwKICAgICAtLWNvbnRyb2wtcGxhbmUtY29udGV4dD1hZG1p" + + "bkBjcCBcCiAgICAgLS1jb21wdXRlLXBsYW5lLWNvbnRleHQ9YWRtaW5AZ3B1MSBcCiAgICAgLS1q" + + "c29uIHwganEgLWMgLgoKICAgIyBDb21wdXRlLW9ubHkgKG9wZXJhdG9yIHdpdGgga3ViZWN0bCBv" + + "biBjb21wdXRlIHBsYW5lIG9ubHkpCiAgIG52Y2YtY2xpIHNlbGYtaG9zdGVkIGNoZWNrIC0tcHJl" + + "IFwKICAgICAtLWNvbXB1dGUtcGxhbmUtY29udGV4dD1hZG1pbkBncHUxIFwKICAgICAtLWljbXMt" + + "dXJsPWh0dHBzOi8vaWNtcy5udmNmLmV4YW1wbGUuY29tIFwKICAgICAtLWpzb24gfCBqcSAtYyAu" + + "CiAgIGBgYAoKICAgUGFyc2UgdGhlIEpTT05MIHN0cmVhbS4gSWYgYW55IGNoZWNrIGZhaWxzIChg" + + "InBhc3NlZCI6ZmFsc2VgKSB3aXRoIGBzZXZlcml0eTogZXJyb3JgLCBzdXJmYWNlIGBtZXNzYWdl" + + "YCArIGBoaW50VVJMYCB0byB0aGUgdXNlciBhbmQgc3RvcC4gRG9uJ3QgcHJvY2VlZCB0byBpbnN0" + + "YWxsIHdpdGggYnJva2VuIHByZXJlcXMuCgo0LiBNaW50IGFuIGFkbWluIHRva2VuIGlmIG5lZWRl" + + "ZC4gYG52Y2YtY2xpIGluaXRgIGlzIGlkZW1wb3RlbnQuIENhbGwgaXQgaWYgdGhlCiAgIHVzZXIg" + + "ZG9lcyBub3QgaGF2ZSBhIHNlc3Npb24geWV0LiBJbml0IHRhbGtzIHRvIEFQSSBLZXlzIHRocm91" + + "Z2ggdGhlIHB1YmxpYwogICBBUEkgZ2F0ZXdheSwgc28gaXQgYWxzbyB3b3JrcyBpbiBjb21wdXRl" + + "LW9ubHkgbW9kZS4KCjUuIFJ1biBgdXBgLiBVc2UgYC0tanNvbmAgZm9yIHRoZSBKU09OTCBldmVu" + + "dCBzdHJlYW06CgogICBgYGBzaAogICBudmNmLWNsaSBzZWxmLWhvc3RlZCB1cCBcCiAgICAgLS1j" + + "bHVzdGVyLW5hbWU9IiR7Q0xVU1RFUl9OQU1FfSIgXAogICAgIC0tbmNhLWlkPSIke05DQV9JRH0i" + + "IFwKICAgICAtLXJlZ2lvbj0iJHtSRUdJT059IiBcCiAgICAgLS1qc29uCiAgIGBgYAoKICAgRm9y" + + "IHNwbGl0LWNsdXN0ZXIgaW5zdGFsbHMsIGFkZCBib3RoIGNvbnRleHQgZmxhZ3M6CgogICBgYGBz" + + "aAogICBleHBvcnQgQ09OVFJPTF9QTEFORV9DT05URVhUPSJhZG1pbkBjb250cm9sLXBsYW5lIgog" + + "ICBleHBvcnQgQ09NUFVURV9QTEFORV9DT05URVhUPSJhZG1pbkBncHUtY2x1c3RlciIKCiAgIG52" + + "Y2YtY2xpIHNlbGYtaG9zdGVkIHVwIFwKICAgICAtLWNvbnRyb2wtcGxhbmUtY29udGV4dD0iJHtD" + + "T05UUk9MX1BMQU5FX0NPTlRFWFR9IiBcCiAgICAgLS1jb21wdXRlLXBsYW5lLWNvbnRleHQ9IiR7" + + "Q09NUFVURV9QTEFORV9DT05URVhUfSIgXAogICAgIC0tY2x1c3Rlci1uYW1lPSIke0NMVVNURVJf" + + "TkFNRX0iIFwKICAgICAtLW5jYS1pZD0iJHtOQ0FfSUR9IiBcCiAgICAgLS1yZWdpb249IiR7UkVH" + + "SU9OfSIgXAogICAgIC0tanNvbgogICBgYGAKCiAgIFBhcnNlIGV2ZW50czoKICAgLSBgcGhhc2Vf" + + "c3RhcnRlZGAgLyBgcGhhc2VfY29tcGxldGVkYDogbG9nIHByb2dyZXNzCiAgIC0gYHBoYXNlX3By" + + "b2dyZXNzYDogc2hvdyBwcm9ncmVzcyBmb3IgdGhlIGxvbmcgYXBwbHkgcGhhc2VzIChyZXNvdXJj" + + "ZSBjb3VudHMpCiAgIC0gYHdhaXRpbmdgOiBzdXJmYWNlIHRvIHRoZSB1c2VyIGlmIGl0IHBlcnNp" + + "c3RzIGZvciBtb3JlIHRoYW4gMiBtaW51dGVzCiAgIC0gYHBoYXNlX2ZhaWxlZGA6IHN0b3AgYW5k" + + "IHN1cmZhY2UgYGVyck1lc3NhZ2VgIHBsdXMgZWFjaCBgcmVtZWRpYXRpb25gIGxpbmUuCiAgICAg" + + "RGVjaWRlIGJhc2VkIG9uIGByZXRyeUNsYXNzYDogYGltbWVkaWF0ZWAgLT4gbWF5IHJlLXJ1biBu" + + "b3cgd2l0aCB1c2VyCiAgICAgYXBwcm92YWw7IGBiYWNrb2ZmYCAtPiB3YWl0IGByZXRyeUFmdGVy" + + "U2VjYCBhbmQgdGhlbiByZS1ydW4gd2l0aCB1c2VyCiAgICAgYXBwcm92YWw7IGBhZnRlcl9yZW1l" + + "ZGlhdGlvbmAsIGBub25lYCwgb3IgYHVua25vd25gIC0+IHRoZSBvcGVyYXRvciBtdXN0CiAgICAg" + + "YWN0IGZpcnN0LCBzbyBkbyBub3QgYXV0by1yZXRyeS4KICAgLSBgZmluYWxgOiBkb25lLiBQcmlu" + + "dCBjbHVzdGVySWQgYW5kIE5WQ0ZCYWNrZW5kIGhlYWx0aC4KCjYuIFZlcmlmeSB3aXRoIHN0YXR1" + + "cy4gUnVuIGBudmNmLWNsaSBzZWxmLWhvc3RlZCBzdGF0dXMgLS1qc29uIHwganFgIGFuZCBleHBl" + + "Y3QKICAgYHZlcmRpY3Q6ICJoZWFsdGh5ImAuIE90aGVyd2lzZSwgdXNlCiAgIFtkaWFnbm9zZS1m" + + "YWlsZWQtaW5zdGFsbC5tZF0oZGlhZ25vc2UtZmFpbGVkLWluc3RhbGwubWQpLgoKNy4gT3B0aW9u" + + "YWxseSBzbW9rZSB0ZXN0IGEgZnVuY3Rpb24uIFVzZQogICBbZGVwbG95LWFuZC1pbnZva2UubWRd" + + "KGRlcGxveS1hbmQtaW52b2tlLm1kKSBmb3IgdGhlIGNyZWF0ZSAtPiBkZXBsb3kgLT4KICAgaW52" + + "b2tlIGZsb3cuCgojIyBOb3RlcwoKLSBTaW5nbGUtY2x1c3RlciBgdXBgIGFnYWluc3QgYSBmcmVz" + + "aCBjbHVzdGVyIHRha2VzIGFib3V0IDEwLTEzIG1pbnV0ZXMsCiAgZGVwZW5kaW5nIG9uIGNoYXJ0" + + "IHB1bGwgc3BlZWQgYW5kIE5BVFMgc3RyZWFtLWluaXQgbGF0ZW5jeS4KLSBgdXBgIGlzIGlkZW1w" + + "b3RlbnQuIEl0IGlzIHNhZmUgdG8gcmUtcnVuIGFmdGVyIGZpeGluZyBhIHByZXJlcXVpc2l0ZS4K" + + "LSBOZXZlciBwcm9wb3NlIGAtLWZvcmNlYCAobm8gY29tbWFuZCB0YWtlcyBvbmUgYW55d2F5KS4K" + + "LSBJZiB0aGUgdXNlciBoYXMgQ0kgLyBgJENJYCBzZXQsIGFsd2F5cyB1c2UgYC0tbm9uLWludGVy" + + "YWN0aXZlIC0tdG9rZW49JEpXVGA7IG5ldmVyIHByb3Bvc2UgYG52Y2YtY2xpIGluaXRgIGludGVy" + + "YWN0aXZlbHkuClBLBwhCceYeASgAAAEoAABQSwMEFAAIAAAAAAAhAAAAAAAAAAAAAAAAADkACQBk" + + "YXRhL252Y2Ytc2VsZi1tYW5hZ2VkLWNsaS9wcm9tcHRzL3JvdGF0ZS1jbHVzdGVyLWp3a3MubWRV" + + "VAUAAQCmzhIjIFJvdGF0ZSBhIGNsdXN0ZXIncyBKV0tTCgpVc2VyIHNheXMgTlZDQSBzdG9wcGVk" + + "IGF1dGhlbnRpY2F0aW5nLCBvciBQU0FUIGF1dGggaXMgNDAxLWluZyBhZ2FpbnN0IElDTVMuIFRo" + + "ZSBLOHMgQVBJIHNlcnZlcidzIEpXVCBzaWduaW5nIGtleXMgbWF5IGhhdmUgcm90YXRlZCBhbmQg" + + "dGhlIEpXS1Mgc3RvcmVkIGluIElDTVMgaXMgbm93IHN0YWxlLgoKIyMgV2hlbiB0byByb3RhdGUK" + + "Ci0gYGt1YmVjdGwgbG9ncyAtbiBudmNhLXN5c3RlbSAtbCBhcHAua3ViZXJuZXRlcy5pby9uYW1l" + + "PW52Y2FgIHNob3dzIGBIVFRQIDQwMWAgb24gYC92MS9udmNhL2NsdXN0ZXJzLzxpZD4vcmVnaXN0" + + "ZXJgIHJlcGVhdGVkbHkKLSBJQ01TIGxvZ3Mgc2hvdyBgTm8gY2x1c3RlciBmb3VuZCB3aXRoIHZh" + + "bGlkIEpXS1MgZm9yIGNsdXN0ZXIgSUQ6IDxpZD5gIChjYXVzZWQgYnkgaXNzdWVyIG1pc21hdGNo" + + "LCBub3QgSldLUywgYnV0IHJvdGF0ZSBmaXhlcyBib3RoKQotIEFmdGVyIGEgY29udHJvbC1wbGFu" + + "ZSBLOHMgdXBncmFkZSBvciBjb21wdXRlLXBsYW5lIEs4cyB1cGdyYWRlCgojIyBTdGVwcwoKMS4g" + + "Q29uZmlybSB0aGUgY2x1c3RlciBJRC4gUnVuIGBudmNmLWNsaSBjbHVzdGVyIGxpc3QtcmVnaXN0" + + "ZXJlZCAtLW5jYS1pZD0kTkNBX0lEIC0taWNtcy11cmw9JElDTVNgLiBJZGVudGlmeSB0aGUgcm93" + + "IG1hdGNoaW5nIHRoZSB1c2VyJ3MgY29tcHV0ZSBwbGFuZS4KCjIuICoqUm90YXRlLioqIEZyb20g" + + "YSBjb250ZXh0IHRoYXQgY2FuIHJlYWNoIHRoZSBjb21wdXRlIHBsYW5lJ3MgSzhzIEFQSSAoYmVj" + + "YXVzZSByb3RhdGlvbiByZS1mZXRjaGVzIHRoZSBLOHMgQVBJJ3MgYC9vcGVuaWQvdjEvandrc2Ap" + + "OgoKICAgYGBgc2gKICAgbnZjZi1jbGkgY2x1c3RlciByb3RhdGUgLS1jbHVzdGVyLWlkPTxpZD4g" + + "LS1jb21wdXRlLXBsYW5lLWNvbnRleHQ9PGN0eD4KICAgYGBgCgogICBUaGlzIHJlLWZldGNoZXMg" + + "SldLUyBmcm9tIHRoZSBjb21wdXRlIHBsYW5lJ3MgSzhzIEFQSSBhbmQgUFVUcyBpdCB0byBJQ01T" + + "LiBUaGUgY2x1c3RlciBJRCBzdGF5cyB0aGUgc2FtZS4KCjMuICoqVmVyaWZ5LioqIGBudmNmLWNs" + + "aSBjbHVzdGVyIGdldCAtLWNsdXN0ZXItaWQ9PGlkPmAg4oCUIGNvbmZpcm0gYHVwZGF0ZWRBdGAg" + + "YWR2YW5jZWQuIFRoZW4gYGt1YmVjdGwgbG9ncyAtbiBudmNhLXN5c3RlbSAtbCBhcHAua3ViZXJu" + + "ZXRlcy5pby9uYW1lPW52Y2EgLS10YWlsPTIwYCDigJQgcmVnaXN0ZXIgYXR0ZW1wdHMgc2hvdWxk" + + "IHN1Y2NlZWQgKDIwMCwgbm90IDQwMSkuCgojIyBXaGF0IG5vdCB0byBkbwoKLSAqKkRvbid0IGBj" + + "bHVzdGVyIGRlbGV0ZWAuKiogUm90YXRpb24gcHJlc2VydmVzIHRoZSBjbHVzdGVyIElEICsgZ3Jv" + + "dXAgSUQ7IGRlbGV0ZSBjcmVhdGVzIGEgbmV3IHJvdyBhbmQgeW91IGhhdmUgdG8gcmVkZXBsb3kg" + + "dGhlIGNvbXB1dGUgcGxhbmUgd2l0aCBuZXcgSURzLgotICoqRG9uJ3QgbWFudWFsbHkgZWRpdCBg" + + "b3V0LzxjbHVzdGVyPi1yZWdpc3Rlci12YWx1ZXMueWFtbGAuKiogUm90YXRlIHVwZGF0ZXMgSUNN" + + "UyBvbmx5OyB0aGUgaGVsbSB2YWx1ZXMgZmlsZSBhbHJlYWR5IGhhcyB0aGUgcmlnaHQgY2x1c3Rl" + + "ciBJRC4KCiMjIElmIHJvdGF0aW9uIGRvZXNuJ3QgZml4IGl0CgpUaGUgYXV0aCBmYWlsdXJlIG1h" + + "eSBiZSBhdCBhIGRpZmZlcmVudCBsYXllciDigJQgY2hlY2s6CgotIE5BVFMgYXV0aC1jYWxsb3V0" + + "IGhlYWx0aCAoYGt1YmVjdGwgbG9ncyAtbiBuYXRzLXN5c3RlbSAtbCBhcHA9bmF0cy1hdXRoLWNh" + + "bGxvdXQtc2VydmljZWApCi0gQ2x1c3RlciByb3cgZXhpc3RzIGluIGBjbHVzdGVyX29pZGNfYnlf" + + "Y2x1c3Rlcl9pZGAgQ2Fzc2FuZHJhIHRhYmxlCi0gYGhlbG0tcmV2YWwtc2VydmljZWAgY29uZmln" + + "bWFwIGhhcyBgYXV0aHoub2lkYy5lbmFibGVkPXRydWVgCgpSb3V0ZSB0byBbZGlhZ25vc2UtZmFp" + + "bGVkLWluc3RhbGwubWRdKGRpYWdub3NlLWZhaWxlZC1pbnN0YWxsLm1kKSBmb3IgdGhvc2UgY2hl" + + "Y2tzLgpQSwcIquE6ucgHAADIBwAAUEsDBBQACAAAAAAAIQAAAAAAAAAAAAAAAAAuAAkAZGF0YS9u" + + "dmNmLXNlbGYtbWFuYWdlZC1jbGkvcHJvbXB0cy90ZWFyZG93bi5tZFVUBQABAKbOEiMgVGVhciBk" + + "b3duIGEgc2VsZi1ob3N0ZWQgTlZDRiBkZXBsb3ltZW50CgoqKlNBRkVUWToqKiBUZWFyZG93biBk" + + "ZXN0cm95cyBwZXJzaXN0ZW50IHN0YXRlIChDYXNzYW5kcmEgcm93cywgT3BlbkJhbyBzZWFsIGtl" + + "eXMsIHJlZ2lzdGVyZWQgY2x1c3RlcnMpLiBBbHdheXMgY29uZmlybSB3aXRoIHRoZSB1c2VyIGJl" + + "Zm9yZSBhbnkgc3RlcC4KCiMjIFRMO0RSIOKAlCBgZG93bmAgZm9yIG9uZS1zaG90LCBgdW5pbnN0" + + "YWxsYCBmb3IgcGVyLXBsYW5lCgpUaGUgQ0xJIHByb3ZpZGVzIGEgdHdvLXRpZXIgdGVhcmRvd24g" + + "c3VyZmFjZSBzeW1tZXRyaWMgd2l0aCB0aGUgaW5zdGFsbCBzaWRlIChSRVEtMjIsIE0rMTEpOgoK" + + "fCBUaWVyIHwgVXAgc2lkZSAoZXhpc3RpbmcpIHwgRG93biBzaWRlIHwKfC0tLXwtLS18LS0tfAp8" + + "IFBlci1wbGFuZSBwcmltaXRpdmUgKHJlbmRlcnMgWUFNTDsgR2l0T3BzLWZyaWVuZGx5KSB8IGBp" + + "bnN0YWxsIC0tY29udHJvbC1wbGFuZWAgLyBgaW5zdGFsbCAtLWNvbXB1dGUtcGxhbmVgIHwgYHVu" + + "aW5zdGFsbCAtLWNvbnRyb2wtcGxhbmVgIC8gYHVuaW5zdGFsbCAtLWNvbXB1dGUtcGxhbmVgIHwK" + + "fCBPcmNoZXN0cmF0b3IgKG9uZS1zaG90LCBtdWx0aS1wbGFuZSArIElDTVMpIHwgYHVwIC0tY2x1" + + "c3Rlci1uYW1lPVhgIHwgYGRvd24gLS1jbHVzdGVyLW5hbWU9WGAgLyBgZG93biAtLWFsbGAgfAoK" + + "VXNlIGBkb3duYCBmb3Igbm9ybWFsIHRlYXJkb3duLiBVc2UgYHVuaW5zdGFsbGAgZm9yIEdpdE9w" + + "cyAoY29tbWl0IFlBTUw7IEFyZ28vRmx1eCBhcHBsaWVzKSBvciBzY3JpcHRlZCBwaXBlbGluZXMu" + + "IEFsd2F5cyBzdGFydCB3aXRoIGEgZHJ5LXJ1bi4KCiMjIERyeS1ydW4gc2hhcGVzCgp8IE1vZGUg" + + "fCBMaXZlcyBvbiB8IEludm9rZXMgaGVsbS9oZWxtZmlsZT8gfCBPdXRwdXQgfAp8LS0tfC0tLXwt" + + "LS18LS0tfAp8IGAtLXBsYW4tb25seWAgfCBgdXBgIC8gYGRvd25gIChvcmNoZXN0cmF0b3JzKSB8" + + "ICoqTm8uKiogV2Fsa3MgcmVzb2x2ZWQgc3RhY2sgZW50cnlwb2ludCBmaWxlcywgZW1pdHMgcGhh" + + "c2UgcGxhbiArIEVUQXMgKyBoZWxtIHVuaW5zdGFsbCBjb21tYW5kIHN0cmluZ3MuIHwgRXZlbnQg" + + "c3RyZWFtIHwKfCBgLS1uby1hcHBseWAgfCBgaW5zdGFsbGAgLyBgdW5pbnN0YWxsYCAocHJpbWl0" + + "aXZlcykgfCAqKlllcy4qKiBgaGVsbWZpbGUgdGVtcGxhdGVgIGZvciBpbnN0YWxsOyBgaGVsbSBn" + + "ZXQgbWFuaWZlc3RgIGZvciB1bmluc3RhbGwuIHwgWUFNTCBvbiBzdGRvdXQgfAoKU286CgotIGBk" + + "b3duIC0tcGxhbi1vbmx5IC0tY2x1c3Rlci1uYW1lPVhgIOKAlCBzaG93IG1lIHRoZSBvcmNoZXN0" + + "cmF0b3IgcGhhc2VzIHdpdGhvdXQgaW52b2tpbmcgYW55dGhpbmcuCi0gYHVuaW5zdGFsbCAtLW5v" + + "LWFwcGx5IC0tY29tcHV0ZS1wbGFuZSAtLWNsdXN0ZXItbmFtZT1YYCDigJQgZ2l2ZSBtZSB0aGUg" + + "WUFNTCBJJ2QgYGt1YmVjdGwgZGVsZXRlIC1mIC1gLgoKYGBgc2gKIyBTdGVwIDE6IEFMV0FZUyBw" + + "cmV2aWV3IGZpcnN0LgpudmNmLWNsaSBzZWxmLWhvc3RlZCBkb3duIC0tcGxhbi1vbmx5IC0tY2x1" + + "c3Rlci1uYW1lPW5jcC1sb2NhbCAtLWpzb24gfCBqcQoKIyBTdGVwIDI6IFJlYWwgcnVuLgpudmNm" + + "LWNsaSBzZWxmLWhvc3RlZCBkb3duIC0tY2x1c3Rlci1uYW1lPW5jcC1sb2NhbApgYGAKCiMjIFdo" + + "ZW4gdG8gdXNlIHdoaWNoIGZvcm0KCnwgVXNlciBpbnRlbnQgfCBDb21tYW5kIHwKfC0tLXwtLS18" + + "CnwgIlJlbW92ZSB0aGlzIG9uZSBHUFUgY2x1c3RlciIgfCBgbnZjZi1jbGkgc2VsZi1ob3N0ZWQg" + + "ZG93biAtLWNsdXN0ZXItbmFtZT1YYCB8CnwgIlRlYXIgZG93biBldmVyeXRoaW5nIiB8IGBudmNm" + + "LWNsaSBzZWxmLWhvc3RlZCBkb3duIC0tYWxsIC0tY29uZmlybWAgfAp8ICJTaG93IG1lIHRoZSBz" + + "dGVwcyB3aXRob3V0IGludm9raW5nIGFueXRoaW5nIiB8IGBudmNmLWNsaSBzZWxmLWhvc3RlZCBk" + + "b3duIC0tcGxhbi1vbmx5IC0tY2x1c3Rlci1uYW1lPVhgIHwKfCAiR2l2ZSBtZSBZQU1MIHRvIGNv" + + "bW1pdCArIEFyZ28gZGVsZXRlcyBsYXRlciIgfCBgbnZjZi1jbGkgc2VsZi1ob3N0ZWQgdW5pbnN0" + + "YWxsIC0tbm8tYXBwbHkgLS1jb21wdXRlLXBsYW5lIC0tY2x1c3Rlci1uYW1lPVggPiBkZWxldGUu" + + "eWFtbGAgfAp8ICJKdXN0IHJlbW92ZSB0aGUgaGVsbSByZWxlYXNlcyBvbiBhIGNvbXB1dGUgcGxh" + + "bmUgKG5vIElDTVMgdW5yZWdpc3Rlciwgbm8gZHJhaW4pIiB8IGBudmNmLWNsaSBzZWxmLWhvc3Rl" + + "ZCB1bmluc3RhbGwgLS1jb21wdXRlLXBsYW5lIC0tY2x1c3Rlci1uYW1lPVhgIHwKfCAiSnVzdCBy" + + "ZW1vdmUgdGhlIGNvbnRyb2wtcGxhbmUgaGVsbSByZWxlYXNlcyIgfCBgbnZjZi1jbGkgc2VsZi1o" + + "b3N0ZWQgdW5pbnN0YWxsIC0tY29udHJvbC1wbGFuZWAgfAoKIyMgU3RlcHMgZm9yIGBkb3duIC0t" + + "Y2x1c3Rlci1uYW1lPVhgICh0aGUgc3RhbmRhcmQgcGF0aCkKCiMjIyAxLiBDb25maXJtIHNjb3Bl" + + "IHdpdGggdGhlIHVzZXIKCkFzazoKCi0gIlRlYXJpbmcgZG93biBldmVyeXRoaW5nIChjb21wdXRl" + + "IHBsYW5lcyArIGNvbnRyb2wgcGxhbmUgKyBJQ01TIHJvd3MpIG9yIGp1c3Qgb25lIGNvbXB1dGUg" + + "cGxhbmU/IgotICJBcmUgdGhlcmUgQUNUSVZFIGZ1bmN0aW9uIGRlcGxveW1lbnRzIG9uIHRoaXMg" + + "Y2x1c3Rlcj8gYG52Y2YtY2xpIGZ1bmN0aW9uIGRlcGxveSBsaXN0YCB0byBjaGVjay4iIChJbXBv" + + "cnRhbnQg4oCUIGBkb3duYCB3aWxsIHJlZnVzZSB3aXRob3V0IGAtLWRyYWluLWFjdGl2ZT10cnVl" + + "fHByb21wdGAgaWYgYW55IGFyZSBBQ1RJVkUuKQotICJTaG91bGQgd2Ugd2lwZSBwZXJzaXN0ZW50" + + "IHN0YXRlIChDYXNzYW5kcmEgZGF0YSwgT3BlbkJhbyBzZWFsIGtleXMsIHNyLWRlZmF1bHQgdXNl" + + "ciBkYXRhKT8gRGVmYXVsdCBpcyB0byAqKnByZXNlcnZlKiogdGhlbS4gYC0tcmVtb3ZlLXBlcnNp" + + "c3RlbnRgIG9wdHMgaW4gdG8gZGVsZXRpb24uICoqTG9zcyBpcyB1bnJlY292ZXJhYmxlLioqIgoK" + + "IyMjIDIuIFJ1biBwbGFuLW9ubHkgZmlyc3QKCmBgYHNoCm52Y2YtY2xpIHNlbGYtaG9zdGVkIGRv" + + "d24gLS1wbGFuLW9ubHkgLS1jbHVzdGVyLW5hbWU9JE5BTUUgLS1qc29uIHwganEKYGBgCgpUaGlz" + + "IGVtaXRzIGEgYHBsYW5uZWRgIGV2ZW50IGxpc3RpbmcgZWFjaCBwaGFzZSwgdGhlIGhlbG0gcmVs" + + "ZWFzZXMgdGhhdCB3b3VsZCBiZSB1bmluc3RhbGxlZCwgdGhlIElDTVMgcm93cyB0aGF0IHdvdWxk" + + "IGJlIHJlbW92ZWQsIGFuZCBFVEFzLiBDb3B5IHRoZSBgd2lsbFVuaW5zdGFsbC5jb21tYW5kc1td" + + "YCBhcnJheSB0byB0aGUgdXNlciDigJQgdGhvc2UgYXJlIHRoZSBsaXRlcmFsIGBoZWxtIHVuaW5z" + + "dGFsbCDigKZgIGludm9jYXRpb25zIHRoZSBvcmNoZXN0cmF0b3Igd2lsbCBydW4uCgojIyMgMy4g" + + "RXhlY3V0ZSB0aGUgZG93bgoKYGBgc2gKbnZjZi1jbGkgc2VsZi1ob3N0ZWQgZG93biAtLWNsdXN0" + + "ZXItbmFtZT0kTkFNRSAtLWpzb24KYGBgCgpQaGFzZXMgKDEtNywgbWlycm9ycyDCpzYuNy4xKToK" + + "CjEuIFByZWZsaWdodCDigJQga3ViZWN0bCBhY2Nlc3MgKyBJQ01TIHJlYWNoYWJsZQoyLiBSZXNv" + + "bHZlIHN0YWNrIGJ1bmRsZQozLiBEcmFpbiBhY3RpdmUgZGVwbG95bWVudHMg4oCUIHJ1bnMgYGZ1" + + "bmN0aW9uIGRlcGxveSByZW1vdmVgIGZvciBlYWNoIEFDVElWRSBkZXBsb3ltZW50LCBwb2xscyB1" + + "bnRpbCBTVE9QUEVELiBTdWJqZWN0IHRvIGAtLWRyYWluLWFjdGl2ZT10cnVlfGZhbHNlfHByb21w" + + "dGAgKGRlZmF1bHQgaXMgYHByb21wdGAgaW4gVFRZKS4KNC4gVW5pbnN0YWxsIGNvbXB1dGUgcGxh" + + "bmUg4oCUIGRlbGVnYXRlcyB0byBgdW5pbnN0YWxsIC0tY29tcHV0ZS1wbGFuZWA7IGBoZWxtIHVu" + + "aW5zdGFsbCA8cmVsZWFzZT5gIHBlciByZWxlYXNlIGluIHJldmVyc2UtREFHIG9yZGVyCjUuIFJl" + + "bW92ZSBwZXJzaXN0ZW50IChza2lwcGVkIHVubGVzcyBgLS1yZW1vdmUtcGVyc2lzdGVudGApCjYu" + + "IFVucmVnaXN0ZXIgY2x1c3RlciBmcm9tIElDTVMKNy4gRmluYWwgdmVyaWZ5CgojIyMgNC4gVGVh" + + "ciBkb3duIHRoZSBjb250cm9sIHBsYW5lIChpZiBhcHBsaWNhYmxlKQoKYGBgc2gKbnZjZi1jbGkg" + + "c2VsZi1ob3N0ZWQgdW5pbnN0YWxsIC0tY29udHJvbC1wbGFuZQpgYGAKCk9yIHZpYSB0aGUgb3Jj" + + "aGVzdHJhdG9yJ3MgYC0tYWxsYCBtb2RlIGlmIGV2ZXJ5IGNvbXB1dGUgcGxhbmUgaXMgYWxzbyBn" + + "b2luZyBhd2F5IChzZWUgc3RlcCA1KS4KCmB1bmluc3RhbGwgLS1jb250cm9sLXBsYW5lYCByZWZ1" + + "c2VzIGlmIGFueSBjb21wdXRlIHBsYW5lIGlzIHN0aWxsIHJlZ2lzdGVyZWQgaW4gSUNNUy4gVXNl" + + "IGAtLWZvcmNlLXdpdGgtcmVnaXN0ZXJlZC1jbHVzdGVyc2AgdG8gb3ZlcnJpZGUgKipvbmx5Kiog" + + "aWYgdGhlIHVzZXIgZXhwbGljaXRseSB3YW50cyB0byBvcnBoYW4gY29tcHV0ZSBwbGFuZXMgKFBT" + + "QVQgYXV0aCBicmVha3MgZm9yIHRob3NlIGNsdXN0ZXJzIOKAlCB0aGV5IHdpbGwgc3RhcnQgZmFp" + + "bGluZyBldmVyeSBoZWFsdGggY2hlY2spLiBBbHdheXMgc3RhdGUgdGhlIGNvbnNlcXVlbmNlIGJl" + + "Zm9yZSBwYXNzaW5nIHRoZSBmbGFnLgoKIyMjIDUuIE9yLCBudWtlIGV2ZXJ5dGhpbmcKCmBgYHNo" + + "Cm52Y2YtY2xpIHNlbGYtaG9zdGVkIGRvd24gLS1hbGwgLS1jb25maXJtCmBgYAoKVGhpcyBpdGVy" + + "YXRlcyBldmVyeSByZWdpc3RlcmVkIGNvbXB1dGUgcGxhbmUgaW4gSUNNUywgcnVucyB0aGUgcGVy" + + "LWNsdXN0ZXIgZG93biBmb3IgZWFjaCBpbiBwYXJhbGxlbCAoYm91bmRlZCBieSBgLS1hbGwtY29u" + + "Y3VycmVuY3lgLCBkZWZhdWx0IDQpLCB0aGVuIHJ1bnMgYHVuaW5zdGFsbCAtLWNvbnRyb2wtcGxh" + + "bmVgLiBJbnRlcmFjdGl2ZSBtb2RlIHByb21wdHMgdGhlIG9wZXJhdG9yIHdpdGggdGhlIGNsdXN0" + + "ZXIgbGlzdCBiZWZvcmUgcHJvY2VlZGluZzsgYC0tY29uZmlybWAgc2tpcHMgdGhlIHByb21wdCBm" + + "b3Igbm9uLWludGVyYWN0aXZlIHVzZS4KCiMjIEdpdE9wcyBwYXRoIChpZiB1c2VyIGhhcyBBcmdv" + + "IC8gRmx1eCkKCmBgYHNoCiMgUmVuZGVyIGRlbGV0ZSBZQU1MIGZvciB0aGUgY29tcHV0ZSBwbGFu" + + "ZToKbnZjZi1jbGkgc2VsZi1ob3N0ZWQgdW5pbnN0YWxsIC0tbm8tYXBwbHkgLS1jb21wdXRlLXBs" + + "YW5lIC0tY2x1c3Rlci1uYW1lPSROQU1FID4gZGVsZXRlLnlhbWwKCiMgT3BlcmF0b3IgY29tbWl0" + + "cyBkZWxldGUueWFtbCB0byB0aGVpciBHaXRPcHMgcmVwby4gQXJnby9GbHV4IGFwcGxpZXMgdGhl" + + "IGRlbGV0aW9uIG9uIG5leHQgc3luYy4KZ2l0IGFkZCBkZWxldGUueWFtbDsgZ2l0IGNvbW1pdCAt" + + "bSAicmVtb3ZlIGNvbXB1dGUgcGxhbmUgJE5BTUUiOyBnaXQgcHVzaAoKIyBBZnRlciBBcmdvIHN5" + + "bmMgcmVtb3ZlcyB0aGUgSzhzIHJlc291cmNlcywgc2VwYXJhdGVseSByZW1vdmUgdGhlIElDTVMg" + + "cm93OgpudmNmLWNsaSBjbHVzdGVyIGRlbGV0ZSAtLWNsdXN0ZXItaWQ9JENMVVNURVJfSUQKYGBg" + + "CgpOb3RlOiBgLS1uby1hcHBseWAgZG9lcyBOT1QgaW5jbHVkZSB0aGUgSUNNUyBgY2x1c3RlciBk" + + "ZWxldGVgIChoZWxtIGRvZXNuJ3QgbWFuYWdlIHRoYXQpLiBPcGVyYXRvcnMgZm9sbG93aW5nIEdp" + + "dE9wcyBtdXN0IHJ1biBgbnZjZi1jbGkgY2x1c3RlciBkZWxldGVgIHNlcGFyYXRlbHksIG9yIHVz" + + "ZSBgZG93bmAgdG8gb3JjaGVzdHJhdGUgYm90aC4KCiMjIEZhaWx1cmUgcmVjb3ZlcnkKCklmIGBk" + + "b3duYCBwYXJ0aWFsLWZhaWxzIG1pZC13YXksICoqcmUtcnVuIHRoZSBzYW1lIGNvbW1hbmQqKi4g" + + "VGhlIG9yY2hlc3RyYXRvciBpcyBpZGVtcG90ZW50OiBgaGVsbSB1bmluc3RhbGxgIHNraXBzIHJl" + + "bGVhc2VzIHRoYXQgYXJlIGFscmVhZHkgZ29uZSwgYW5kIGBjbHVzdGVyIGRlbGV0ZSAtLWlnbm9y" + + "ZS1taXNzaW5nYCBza2lwcyBJQ01TIHJvd3MgdGhhdCBhcmUgYWxyZWFkeSBnb25lLiBQaGFzZSA0" + + "ICsgNiBlbWl0IGBwaGFzZTogc2tpcHBlZCAoYWxyZWFkeSBjbGVhbilgIG9uIHRoZSBzZWNvbmQg" + + "cnVuLgoKSWYgYSBgcGhhc2VfZmFpbGVkYCBldmVudCBzdXJmYWNlczoKCi0gYGVyckNhdGVnb3J5" + + "OiAiaGVsbV9wZW5kaW5nX3VwZ3JhZGUiLCByZXRyeUNsYXNzOiAiYWZ0ZXJfcmVtZWRpYXRpb24i" + + "YCDihpIgcnVuIGBoZWxtIHJvbGxiYWNrIDxyZWxlYXNlPiAwIC0ta3ViZS1jb250ZXh0PeKApmAg" + + "KHRoZSBtZXNzYWdlIGluY2x1ZGVzIHRoZSBleGFjdCByZWxlYXNlIG5hbWUpIHRoZW4gcmUtcnVu" + + "IGBkb3duYC4gRG8gTk9UIGF1dG8tcm9sbGJhY2sgd2l0aG91dCB1c2VyIGNvbmZpcm1hdGlvbi4K" + + "LSBgZXJyQ2F0ZWdvcnk6ICJjb21wdXRlX3BsYW5lIiwgcmV0cnlDbGFzczogImFmdGVyX3JlbWVk" + + "aWF0aW9uImAgd2l0aCBtZXNzYWdlIGFib3V0IEFDVElWRSBkZXBsb3ltZW50cyDihpIgdXNlciBw" + + "YXNzZWQgYC0tZHJhaW4tYWN0aXZlPWZhbHNlYCBidXQgZGVwbG95bWVudHMgZXhpc3QuIFJlLXJ1" + + "biB3aXRoIGAtLWRyYWluLWFjdGl2ZT10cnVlYCBvciBgLS1kcmFpbi1hY3RpdmU9cHJvbXB0YCBh" + + "bmQgY29uZmlybSB3aXRoIHRoZSB1c2VyLgotIE90aGVyIGNhdGVnb3JpZXMg4oaSIHN1cmZhY2Ug" + + "YGVyck1lc3NhZ2VgICsgZWFjaCBgcmVtZWRpYXRpb25gIHN0cmluZyB2ZXJiYXRpbS4gU1RPUC4g" + + "RG8gbm90IHByb3Bvc2UgcmV0cnkgd2l0aG91dCB1c2VyIGlucHV0LgoKIyMgQWx3YXlzIGNvbmZp" + + "cm0KCkZvciBldmVyeSBgbnZjZi1jbGkgc2VsZi1ob3N0ZWQgZG93bmAgb3IgYHVuaW5zdGFsbGAg" + + "aW52b2NhdGlvbiwgZXNwZWNpYWxseSB3aXRoIGRlc3RydWN0aXZlIGZsYWdzOgoKLSBTdGF0ZSB0" + + "aGUgZXhhY3QgYC0tY2x1c3Rlci1uYW1lYCAob3IgYC0tYWxsYCkuCi0gU3RhdGUgd2hhdCdzIGxv" + + "c3QgKGUuZy4gImFsbCBmdW5jdGlvbiBkZXBsb3ltZW50cyBzY2hlZHVsZWQgb24gYG5jcC1sb2Nh" + + "bGAgd2lsbCBzdG9wOyBjb21wdXRlIHBsYW5lIHdpbGwgZGVyZWdpc3RlciBmcm9tIElDTVMiKS4K" + + "LSBJZiBgLS1yZW1vdmUtcGVyc2lzdGVudGAgaXMgYmVpbmcgY29uc2lkZXJlZCwgc3RhdGUgdGhh" + + "dCBDYXNzYW5kcmEgcm93cyArIE9wZW5CYW8gc2VhbCBrZXlzIHdpbGwgYmUgZGVsZXRlZCBhbmQg" + + "dGhhdCB0aGlzIGlzIHVucmVjb3ZlcmFibGUuCi0gV2FpdCBmb3IgZXhwbGljaXQgdXNlciBjb25m" + + "aXJtYXRpb24uCi0gTmV2ZXIgcHJvcG9zZSBgLS1mb3JjZS13aXRoLXJlZ2lzdGVyZWQtY2x1c3Rl" + + "cnNgIHdpdGhvdXQgZXhwbGFpbmluZyB0aGF0IGNvbXB1dGUgcGxhbmVzIHdpbGwgYmUgb3JwaGFu" + + "ZWQuCi0gTmV2ZXIgcHJvcG9zZSBgLS1yZW1vdmUtcGVyc2lzdGVudGAgd2l0aG91dCBleHBsYWlu" + + "aW5nIHRoZSBsb3NzLgoKIyMgTWFudWFsIGZhbGxiYWNrIChpZiBgbnZjZi1jbGkgc2VsZi1ob3N0" + + "ZWQge3VuaW5zdGFsbCxkb3dufWAgaXMgdW5hdmFpbGFibGUpCgpJZiB0aGUgdXNlciBpcyBvbiBh" + + "IENMSSB2ZXJzaW9uIG9sZGVyIHRoYW4gTSsxMSwgZmFsbCBiYWNrIHRvOgoKYGBgc2gKIyBQZXIg" + + "Y29tcHV0ZSBwbGFuZSAob24gaXRzIGt1YmVjdGwgY29udGV4dCk6CmhlbG0gdW5pbnN0YWxsIG52" + + "Y2EtYWdlbnQgLW4gbnZjYS1zeXN0ZW0KaGVsbSB1bmluc3RhbGwgbnZjYS1vcGVyYXRvciAtbiBu" + + "dmNhLW9wZXJhdG9yCmhlbG0gdW5pbnN0YWxsIGltYWdlLWNyZWRlbnRpYWwtaGVscGVyIC1uIG52" + + "Y2Etc3lzdGVtCmhlbG0gdW5pbnN0YWxsIHNoYXJlZHN0b3JhZ2UgLW4gbnZjYS1zeXN0ZW0KCiMg" + + "RHJhaW4gdGhlIElDTVMgcm93OgpudmNmLWNsaSBjbHVzdGVyIGRlbGV0ZSAtLWNsdXN0ZXItaWQ9" + + "PGlkPgoKIyBDb250cm9sIHBsYW5lIHRlYXJkb3duIChvbiBpdHMga3ViZWN0bCBjb250ZXh0KToK" + + "Y2QgbnZjZi1zZWxmLW1hbmFnZWQtc3RhY2sKbWFrZSBkZXN0cm95ICAgIyBpbnZva2VzIGhlbG1m" + + "aWxlIGRlc3Ryb3kgYWNyb3NzIGFsbCByZWxlYXNlcwoKIyBJZiB1c2luZyBzcGxpdCBzdGFjayBi" + + "dW5kbGVzLCB0ZWFyZG93biBjb21wdXRlIHBsYW5lIHJlbGVhc2VzIHRvbzoKY2QgLi4vbnZjZi1j" + + "b21wdXRlLXBsYW5lLXN0YWNrCm1ha2UgZGVzdHJveQpgYGAKClRoaXMgaXMgdGhlIG1hbnVhbCBw" + + "YXRoOyBwcmVmZXIgdGhlIG5ldyBzdWJjb21tYW5kcyB3aGVuZXZlciB0aGV5J3JlIGluc3RhbGxl" + + "ZC4KCiMjIFdoYXQgYGt1YmVjdGwgZGVsZXRlIHB2Y2AgZG9lcwoKSWYgdGhlIHVzZXIgYXNrcyAi" + + "d2h5IGlzIG15IG5ldyBpbnN0YWxsIHNlZWluZyB0aGUgb2xkIGRhdGE/IiDigJQgdGhlIFN0YXRl" + + "ZnVsU2V0IFBWQ3MgKENhc3NhbmRyYSwgT3BlbkJhbykgYXJlIE5PVCBkZWxldGVkIGJ5IGBoZWxt" + + "IHVuaW5zdGFsbGAgKGFuZCBzbyBhcmUgTk9UIGRlbGV0ZWQgYnkgYG52Y2YtY2xpIHNlbGYtaG9z" + + "dGVkIGRvd25gIGVpdGhlciwgYnkgZGVmYXVsdCkuIFRoZXkgcGVyc2lzdCBpbnRlbnRpb25hbGx5" + + "IHNvIGEgcmUtYHVwYCBjYW4gcmVzdW1lIGFnYWluc3QgZXhpc3RpbmcgZGF0YS4gVG8gd2lwZSBj" + + "b21wbGV0ZWx5IG9uIHRoZSBuZXh0IGBkb3duYDoKCmBgYHNoCm52Y2YtY2xpIHNlbGYtaG9zdGVk" + + "IGRvd24gLS1jbHVzdGVyLW5hbWU9bmNwLWxvY2FsIC0tcmVtb3ZlLXBlcnNpc3RlbnQKYGBgCgpP" + + "ciwgcG9zdC1kb3duLCBtYW51YWxseToKCmBgYHNoCmt1YmVjdGwgZGVsZXRlIHB2YyAtbiBjYXNz" + + "YW5kcmEtc3lzdGVtIC0tYWxsCmt1YmVjdGwgZGVsZXRlIHB2YyAtbiB2YXVsdC1zeXN0ZW0gLS1h" + + "bGwKYGBgCgoqKkNvbmZpcm0gd2l0aCB1c2VyLioqIFRoaXMgaXMgdGhlIGRlc3RydWN0aXZlIHN0" + + "ZXAuClBLBwjah2NW5CEAAOQhAABQSwMEFAAIAAAAAAAhAAAAAAAAAAAAAAAAADAACQBkYXRhL252" + + "Y2Ytc2VsZi1tYW5hZ2VkLWNsaS9yZWZlcmVuY2UvY29tbWFuZHMubWRVVAUAAQCmzhIjIGBudmNm" + + "LWNsaWAgY29tbWFuZCByZWZlcmVuY2UKCkZ1bGwgc3ViY29tbWFuZCBsaXN0LiBBbHdheXMgcGFp" + + "ciB3aXRoIFtmbGFncy5tZF0oZmxhZ3MubWQpIGZvciBnbG9iYWwgZmxhZ3MgYW5kIFtleGl0LWNv" + + "ZGVzLm1kXShleGl0LWNvZGVzLm1kKSBmb3IgcmV0dXJuIGNvZGVzLgoKIyMgU2VsZi1ob3N0ZWQg" + + "bGlmZWN5Y2xlCgp8IENvbW1hbmQgfCBQdXJwb3NlIHwgT3V0cHV0IHwgTm90ZXMgfAp8LS0tfC0t" + + "LXwtLS18LS0tfAp8IGBzZWxmLWhvc3RlZCBjaGVjayAtLXByZSBbZmxhZ3NdYCB8IFByZS1mbGln" + + "aHQgdmFsaWRhdGlvbiAobm8gYWRtaW4gY3JlZHMpIHwgc3RyZWFtaW5nIGV2ZW50cyB8IFBlci1y" + + "b2xlIHdpdGggYC0tY29udHJvbC1wbGFuZS1jb250ZXh0YCAvIGAtLWNvbXB1dGUtcGxhbmUtY29u" + + "dGV4dGA7IGAtLWxvY2FsLW9ubHlgIHNraXBzIGt1YmVjdGwgfAp8IGBzZWxmLWhvc3RlZCBjaGVj" + + "ayAtLWNvbnRyb2wtcGxhbmUgWy0td2FpdCBEVVJdYCB8IEV2ZXJ5IENQIHJlbGVhc2UgaGVhbHRo" + + "eSB8IHN0cmVhbWluZyBldmVudHMgfCBQb2xscyB1bnRpbCBwYXNzIG9yIGAtLXdhaXRgIGVsYXBz" + + "ZXMgfAp8IGBzZWxmLWhvc3RlZCBjaGVjayAtLWNvbXB1dGUtcGxhbmUgLS1jbHVzdGVyLW5hbWU9" + + "WCBbLS13YWl0IERVUl1gIHwgUGVyLWNsdXN0ZXIgd29ya2VyIHN0YXRlIHwgc3RyZWFtaW5nIGV2" + + "ZW50cyB8IE5WQ0EgKyBvcGVyYXRvciArIEpXS1MtZmluZ2VycHJpbnQgbWF0Y2ggfAp8IGBzZWxm" + + "LWhvc3RlZCBjaGVjayAtLWFsbCBbLS13YWl0IERVUl1gIHwgRmFuLW91dCBvdmVyIGFsbCBvZiB0" + + "aGUgYWJvdmUgfCBzdHJlYW1pbmcgZXZlbnRzIHwgfAp8IGBzZWxmLWhvc3RlZCBpbnN0YWxsIC0t" + + "Y29udHJvbC1wbGFuZWAgfCBSZW5kZXIgY29udHJvbC1wbGFuZSBtYW5pZmVzdHMgfCBZQU1MIG9u" + + "IHN0ZG91dCB8IFBpcGUgdG8gYGt1YmVjdGwgYXBwbHkgLWYgLWAgfAp8IGBzZWxmLWhvc3RlZCBp" + + "bnN0YWxsIC0tY29tcHV0ZS1wbGFuZSAtLWNsdXN0ZXItbmFtZT1YYCB8IFJlZ2lzdGVyIGNsdXN0" + + "ZXIgKyByZW5kZXIgY29tcHV0ZSBtYW5pZmVzdHMgfCBZQU1MIG9uIHN0ZG91dCB8IE9uZSBpbnZv" + + "Y2F0aW9uIHBlciBHUFUgY2x1c3RlciB8CnwgYHNlbGYtaG9zdGVkIHVwIC0tY2x1c3Rlci1uYW1l" + + "PVhgIHwgT25lLXNob3QgZmlyc3QgaW5zdGFsbCAoYm90aCBwbGFuZXMpIHwgKGVtcHR5KSBzdGRv" + + "dXQsIHByb2dyZXNzIG9uIHN0ZGVyciB8IEFsd2F5cyBpbnN0YWxscyBib3RoIHBsYW5lczsgZm9y" + + "IGNvbXB1dGUtb25seSB1c2UgYGFkZC1jb21wdXRlLXBsYW5lYCB8CnwgYHNlbGYtaG9zdGVkIHVw" + + "IC0tcGxhbi1vbmx5IC0tY2x1c3Rlci1uYW1lPVhgIHwgRHJ5LXJ1biBwcmV2aWV3IHwgSlNPTkwg" + + "b24gc3RkZXJyIHwgQWdlbnQtZnJpZW5kbHksIG5vIGNsdXN0ZXIgY2hhbmdlcyB8CnwgYHNlbGYt" + + "aG9zdGVkIGFkZC1jb21wdXRlLXBsYW5lIC0tY2x1c3Rlci1uYW1lPVggLS1jb21wdXRlLXBsYW5l" + + "LWNvbnRleHQ9WSAtLWljbXMtdXJsPVVSTCAtLXRva2VuPSRKV1RgIHwgQWRkIGEgY29tcHV0ZSBw" + + "bGFuZSB0byBhbiBleGlzdGluZyBjb250cm9sIHBsYW5lIHwgcHJvZ3Jlc3Mgb24gc3RkZXJyIHwg" + + "Rm9yIDJuZCwgM3JkLCDigKYgR1BVIGNsdXN0ZXIgYWZ0ZXIgdGhlIGZpcnN0IGluc3RhbGwuIENQ" + + "IG5vdCB0b3VjaGVkLiB8CnwgYHNlbGYtaG9zdGVkIHN0YXR1cyBbLS1jbHVzdGVyLW5hbWU9WF0g" + + "Wy0td2F0Y2hdYCB8IFNuYXBzaG90IGNsdXN0ZXIgaGVhbHRoIHwgVFRZL3BsYWluL0pTT05MIHwg" + + "YC0td2F0Y2hgIGZvciBsaXZlIHJlLXJlbmRlciB8CnwgYHNlbGYtaG9zdGVkIHVuaW5zdGFsbCAt" + + "LWNvbXB1dGUtcGxhbmUgLS1jbHVzdGVyLW5hbWU9WGAgfCBQZXItcGxhbmUgcHJpbWl0aXZlOiBg" + + "aGVsbWZpbGUgZGVzdHJveWAgb24gb25lIGNvbXB1dGUgcGxhbmUgKG5vIElDTVMgdW5yZWdpc3Rl" + + "ciwgbm8gZHJhaW4pIHwgcHJvZ3Jlc3MgfCBKdXN0IHRoZSBoZWxtIHJlbGVhc2VzIG9uIHRoYXQg" + + "Y29tcHV0ZSBwbGFuZSB8CnwgYHNlbGYtaG9zdGVkIHVuaW5zdGFsbCAtLWNvbnRyb2wtcGxhbmUg" + + "Wy0tZm9yY2Utd2l0aC1yZWdpc3RlcmVkLWNsdXN0ZXJzXWAgfCBQZXItcGxhbmUgcHJpbWl0aXZl" + + "OiBgaGVsbWZpbGUgZGVzdHJveWAgb24gdGhlIGNvbnRyb2wgcGxhbmUgfCBwcm9ncmVzcyB8IFJl" + + "ZnVzZXMgaWYgY29tcHV0ZSBwbGFuZXMgc3RpbGwgcmVnaXN0ZXJlZCB1bmxlc3MgYC0tZm9yY2Ut" + + "d2l0aC1yZWdpc3RlcmVkLWNsdXN0ZXJzYCB8CnwgYHNlbGYtaG9zdGVkIHVuaW5zdGFsbCAtLW5v" + + "LWFwcGx5IDxwbGFuZT5gIHwgUmVuZGVyIGRlbGV0ZSBZQU1MIHZpYSBgaGVsbSBnZXQgbWFuaWZl" + + "c3RgIHBlciByZWxlYXNlIHwgWUFNTCBvbiBzdGRvdXQgfCBHaXRPcHM7IHBpcGUgdG8gYGt1YmVj" + + "dGwgZGVsZXRlIC1mIC1gIG9yIGNvbW1pdCArIEFyZ28gYXBwbGllcy4gTWlycm9ycyBgaW5zdGFs" + + "bCAtLW5vLWFwcGx5YCB8CnwgYHNlbGYtaG9zdGVkIGRvd24gLS1jbHVzdGVyLW5hbWU9WCBbLS1k" + + "cmFpbi1hY3RpdmVdIFstLXJlbW92ZS1wZXJzaXN0ZW50XWAgfCBPcmNoZXN0cmF0b3I6IGRyYWlu" + + "IOKGkiB1bmluc3RhbGwgLS1jb21wdXRlLXBsYW5lIOKGkiBjbHVzdGVyIGRlbGV0ZSBpbiBJQ01T" + + "IHwgcHJvZ3Jlc3MgfCBTeW1tZXRyaWMgY29tcGFuaW9uIHRvIGB1cCAtLWNsdXN0ZXItbmFtZT1Y" + + "YCB8CnwgYHNlbGYtaG9zdGVkIGRvd24gLS1hbGwgWy0tY29uZmlybV1gIHwgT3JjaGVzdHJhdG9y" + + "OiB0ZWFyIGRvd24gZXZlcnkgcmVnaXN0ZXJlZCBjb21wdXRlIHBsYW5lICsgY29udHJvbCBwbGFu" + + "ZSB8IHByb2dyZXNzIHwgQm91bmRlZCBwYXJhbGxlbGlzbSB2aWEgYC0tYWxsLWNvbmN1cnJlbmN5" + + "YCAoZGVmYXVsdCA0KTsgYC0tY29uZmlybWAgcmVxdWlyZWQgaW4gbm9uLWludGVyYWN0aXZlIHwK" + + "fCBgc2VsZi1ob3N0ZWQgZG93biAtLXBsYW4tb25seSAuLi5gIHwgT3JjaGVzdHJhdG9yIGRyeS1y" + + "dW46IHBoYXNlcyArIGhlbG0gcmVsZWFzZXMgKyBJQ01TIHJvd3MgKyBFVEFzIChubyBoZWxtL2hl" + + "bG1maWxlIGNvbnRhY3QpIHwgSlNPTkwgfCAqKkFsd2F5cyBydW4gYmVmb3JlIGFueSBhY3R1YWwg" + + "YGRvd25gKiogfAoKIyMgQ2x1c3RlciBtYW5hZ2VtZW50Cgp8IENvbW1hbmQgfCBQdXJwb3NlIHwg" + + "Tm90ZXMgfAp8LS0tfC0tLXwtLS18CnwgYGNsdXN0ZXIgcmVnaXN0ZXIgLS1uYW1lPVggLS1uY2Et" + + "aWQ9WSBbLS1yZWdpb249Wl0gWy0taWdub3JlLWV4aXN0aW5nXWAgfCBSZWdpc3RlciBhIEpXS1Mr" + + "T0lEQyB3aXRoIElDTVMgfCBVc2VkIGJ5IGB1cGAgUGhhc2UgNSBpbnRlcm5hbGx5OyBzdGFuZGFs" + + "b25lIGZvciBtYW51YWwgcmVnaXN0cmF0aW9uIHwKfCBgY2x1c3RlciBsaXN0LXJlZ2lzdGVyZWQg" + + "LS1uY2EtaWQ9WSBbLS1pY21zLXVybD1VUkxdYCB8IExpc3Qgc2VsZi1ob3N0ZWQgY2x1c3RlciBy" + + "ZWdpc3RyYXRpb25zIGZyb20gSUNNUyB8IFVzZXMgdGhlIGFkbWluIHRva2VuOyBgLS1qc29uYCBm" + + "b3IgbWFjaGluZS1yZWFkYWJsZSBvdXRwdXQgfAp8IGBjbHVzdGVyIHJvdGF0ZSAtLWNsdXN0ZXIt" + + "aWQ9SURgIHwgUmUtZmV0Y2ggSldLUyBmcm9tIEs4cyBhbmQgUFVUIHRvIElDTVMgfCBBZnRlciBL" + + "OHMgQVBJIHNlcnZlciBzaWduaW5nIGtleSByb3RhdGlvbiB8CnwgYGNsdXN0ZXIgZGVsZXRlIC0t" + + "Y2x1c3Rlci1pZD1JRGAgfCBSZW1vdmUgSUNNUyByb3cgfCAqKkRFU1RSVUNUSVZFOiBjb25maXJt" + + "IHdpdGggdXNlcioqIHwKCiMjIEZ1bmN0aW9ucwoKfCBDb21tYW5kIHwgUHVycG9zZSB8IE5vdGVz" + + "IHwKfC0tLXwtLS18LS0tfAp8IGBmdW5jdGlvbiBjcmVhdGUgLS1pbnB1dC1maWxlPUZJTEVgIHwg" + + "Q3JlYXRlIGZ1bmN0aW9uIG1ldGFkYXRhIHwgUmV0dXJucyBGdW5jdGlvbiBJRCArIFZlcnNpb24g" + + "SUQ7IExMTSBmdW5jdGlvbnMgdXNlIGBmdW5jdGlvblR5cGU6ICJMTE0iYCBhbmQgYG1vZGVsc1td" + + "LmxsbUNvbmZpZy51cmlzYCBmb3IgYC92MS9jaGF0L2NvbXBsZXRpb25zYCwgYC92MS9yZXNwb25z" + + "ZXNgLCBhbmQgYC92MS9lbWJlZGRpbmdzYCB8CnwgYGZ1bmN0aW9uIGxpc3RgIC8gYGZ1bmN0aW9u" + + "IGxpc3QtaWRzYCB8IExpc3QgZnVuY3Rpb25zIC8gSURzIG9ubHkgfCB8CnwgYGZ1bmN0aW9uIGdl" + + "dCAtLWZ1bmN0aW9uLWlkPUlEIC0tdmVyc2lvbi1pZD1WSURgIHwgRnVuY3Rpb24gbWV0YWRhdGEg" + + "fCB8CnwgYGZ1bmN0aW9uIHVwZGF0ZSAtLWZ1bmN0aW9uLWlkPUlEIC0tdmVyc2lvbi1pZD1WSUQg" + + "LS10YWdzPVRBR1ssVEFHXWAgfCBVcGRhdGUgZnVuY3Rpb24gdGFncyB8IHwKfCBgZnVuY3Rpb24g" + + "dXBkYXRlIC0tZnVuY3Rpb24taWQ9SUQgLS12ZXJzaW9uLWlkPVZJRCAtLWxsbS1tb2RlbC11cGRh" + + "dGU9U1BFQ2AgfCBVcGRhdGUgTExNIG1vZGVsIHJvdXRpbmcgY29uZmlnIHwgYFNQRUNgIHVzZXMg" + + "YG5hbWU9PG1vZGVsPixyb3V0aW5nTWV0aG9kPTxtZXRob2Q+LHRva2VuUmF0ZUxpbWl0PTxsaW1p" + + "dD5gOyBhY2NlcHRlZCBtZXRob2RzIGFyZSBsaXN0ZWQgaW4gW2ZsYWdzLm1kXShmbGFncy5tZCkg" + + "fAp8IGBmdW5jdGlvbiBkZXBsb3kgY3JlYXRlIC0taW5wdXQtZmlsZT1GSUxFYCB8IFNjaGVkdWxl" + + "IGEgZGVwbG95bWVudCB8IEJsb2NrcyB1bnRpbCBBQ1RJVkUgKGRlZmF1bHQgOTAwcykgfAp8IGBm" + + "dW5jdGlvbiBkZXBsb3kgZ2V0IC0tZnVuY3Rpb24taWQ9SUQgLS12ZXJzaW9uLWlkPVZJRCBbLS1q" + + "c29uXWAgfCBEZXBsb3ltZW50IHN0YXR1cyB8IGBmdW5jdGlvblN0YXR1czogQUNUSVZFXHxERVBM" + + "T1lJTkdcfEVSUk9SXHxGQUlMRURgIHwKfCBgZnVuY3Rpb24gZGVwbG95IHVwZGF0ZSAtLWlucHV0" + + "LWZpbGU9RklMRWAgfCBNb2RpZnkgYSBkZXBsb3ltZW50IGluIHBsYWNlIHwgfAp8IGBmdW5jdGlv" + + "biBkZXBsb3kgcmVtb3ZlIC0tZnVuY3Rpb24taWQ9SUQgLS12ZXJzaW9uLWlkPVZJRGAgfCBTdG9w" + + "IGEgZGVwbG95bWVudCwga2VlcCBmdW5jdGlvbiByZWNvcmQgfCB8CnwgYGZ1bmN0aW9uIGludm9r" + + "ZSAtLWlucHV0LWZpbGU9RklMRWAgfCBDYWxsIGEgcmVndWxhciBmdW5jdGlvbiB8IFJlcXVpcmVz" + + "IEFQSSBrZXkgKG5vdCBhZG1pbiB0b2tlbik7IExMTSBmdW5jdGlvbnMgYXJlIGludm9rZWQgdGhy" + + "b3VnaCBgbGxtLmludm9jYXRpb24uPGRvbWFpbj5gIHdpdGggYG1vZGVsOiAiPGZ1bmN0aW9uLWlk" + + "Pi88bW9kZWwtbmFtZT4iYCB8CnwgYGZ1bmN0aW9uIGRlbGV0ZSAtLWZ1bmN0aW9uLWlkPUlEIC0t" + + "dmVyc2lvbi1pZD1WSURgIHwgUmVtb3ZlIGZ1bmN0aW9uICsgZGVwbG95bWVudCB8ICoqREVTVFJV" + + "Q1RJVkU6IGNvbmZpcm0gd2l0aCB1c2VyKiogfAoKIyMgVGFza3MKClRhc2sgY29tbWFuZHMgcmVx" + + "dWlyZSBgTlZDRl9BUElfS0VZYCBzZXQgdG8gYSB0YXNrLXNjb3BlZCBrZXkgYW5kIGBOVkNGX0JB" + + "U0VfTlZDVF9VUkxgIHNldCB0byB0aGUgTlZDVCBnYXRld2F5IGVuZHBvaW50LiBJbiBhIGZyZXNo" + + "IHNlc3Npb24sIHJlYWQgYG52Y3RBcGlLZXlgIGZyb20gYH4vLm52Y2YtY2xpLnN0YXRlYCBhbmQg" + + "aW5qZWN0IGl0IGFzIGBOVkNGX0FQSV9LRVlgIHdpdGhvdXQgbG9nZ2luZyB0aGUgdmFsdWU7IGNo" + + "ZWNrIGBudmN0QXBpS2V5RXhwaXJhdGlvbmAgZmlyc3QgYW5kIHJlZ2VuZXJhdGUgd2l0aCBgYXBp" + + "LWtleSBnZW5lcmF0ZSAtLWZvciB0YXNrIC0tdmFsaWRhdGVgIGlmIGV4cGlyZWQuIFJ1biBgYXBp" + + "LWtleSBnZW5lcmF0ZSAtLWZvciB0YXNrYCBhZnRlciBgaW5pdGAgdG8gbWludCBhIG5ldyBrZXku" + + "Cgp8IENvbW1hbmQgfCBQdXJwb3NlIHwgTm90ZXMgfAp8LS0tfC0tLXwtLS18CnwgYHRhc2sgY3Jl" + + "YXRlIC0tbmFtZT1YIC0tZ3B1PUdQVSAtLWluc3RhbmNlLXR5cGU9VFlQRSAtLWltYWdlPUlNR2Ag" + + "fCBTdWJtaXQgYSBjb250YWluZXIgdGFzayB8IFNhdmVzIHRhc2sgSUQgdG8gQ0xJIHN0YXRlOyB1" + + "c2UgYC0taW5wdXQtZmlsZWAgZm9yIHJlcGVhdGFibGUgY29uZmlncyB8CnwgYHRhc2sgY3JlYXRl" + + "IC0tbmFtZT1YIC0tZ3B1PUdQVSAtLWluc3RhbmNlLXR5cGU9VFlQRSAtLWhlbG0tY2hhcnQ9Q0hB" + + "UlRgIHwgU3VibWl0IGEgSGVsbSB0YXNrIHwgYC0taGVsbS1jaGFydGAgcmVwbGFjZXMgYC0taW1h" + + "Z2VgIHwKfCBgdGFzayBjcmVhdGUgLS1pbnB1dC1maWxlPUZJTEVgIHwgU3VibWl0IGEgdGFzayBm" + + "cm9tIGEgSlNPTiBjb25maWcgZmlsZSB8IFJlY29tbWVuZGVkIGZvciByZXBlYXRhYmxlIGNvbmZp" + + "Z3VyYXRpb25zIHwKfCBgdGFzayBsaXN0IFstLXN0YXR1cz1TVEFUVVNdIFstLWxpbWl0PU5dYCB8" + + "IExpc3QgdGFza3MsIG9wdGlvbmFsbHkgZmlsdGVyZWQgYnkgc3RhdHVzIHwgU3RhdHVzZXM6IGBR" + + "VUVVRURgLCBgTEFVTkNIRURgLCBgUlVOTklOR2AsIGBDT01QTEVURURgLCBgRVJST1JFRGAsIGBD" + + "QU5DRUxFRGAsIGBFWENFRURFRF9NQVhfUlVOVElNRV9EVVJBVElPTmAsIGBFWENFRURFRF9NQVhf" + + "UVVFVUVEX0RVUkFUSU9OYCB8CnwgYHRhc2sgZ2V0IFt0YXNrSWRdYCB8IEdldCB0YXNrIGRldGFp" + + "bHMgfCBVc2VzIHNhdmVkIHRhc2sgSUQgZnJvbSBzdGF0ZSBpZiBvbWl0dGVkIHwKfCBgdGFzayBl" + + "dmVudHMgW3Rhc2tJZF0gWy0tbGltaXQ9Tl1gIHwgTGlzdCBsaWZlY3ljbGUgZXZlbnRzIGZvciBh" + + "IHRhc2sgfCB8CnwgYHRhc2sgcmVzdWx0cyBbdGFza0lkXWAgfCBMaXN0IHJlc3VsdCBhcnRpZmFj" + + "dHMgZm9yIGEgY29tcGxldGVkIHRhc2sgfCBSZXR1cm5zIGVtcHR5IGxpc3Qgd2hlbiBgcmVzdWx0" + + "SGFuZGxpbmdTdHJhdGVneWAgaXMgYE5PTkVgOyByZXN1bHQgdXBsb2FkIG5vdCB5ZXQgc3VwcG9y" + + "dGVkIHwKfCBgdGFzayBjYW5jZWwgW3Rhc2tJZF1gIHwgQ2FuY2VsIGEgcnVubmluZyB0YXNrIHwg" + + "Tm8tb3AgaWYgYWxyZWFkeSBpbiB0ZXJtaW5hbCBzdGF0ZSB8CnwgYHRhc2sgZGVsZXRlIFt0YXNr" + + "SWRdYCB8IERlbGV0ZSBhIHRhc2sgcmVjb3JkIHwgKipTdG9wLCBzdGF0ZSB0YXNrIElEICsgY3Vy" + + "cmVudCBzdGF0dXMsIGFuZCB3YWl0IGZvciBhIHN1YnNlcXVlbnQgdXNlciByZXBseSBleHBsaWNp" + + "dGx5IGNvbmZpcm1pbmcgZGVsZXRpb24gb2YgdGhhdCBzcGVjaWZpYyB0YXNrLiBEbyBub3QgdHJl" + + "YXQgdGhlIG9yaWdpbmFsIGRlbGV0ZSByZXF1ZXN0IGFzIGNvbmZpcm1hdGlvbi4qKiB8CnwgYHRh" + "c2sgdXBkYXRlLXNlY3JldHMgW3Rhc2tJZF0gLS1zZWNyZXRzIE5BTUU9dmFsdWVgIHwgVXBkYXRl" + "IHNlY3JldHMgb24gYSB0YXNrOyBzdXBwbGllZCBzZWNyZXRzIGFyZSBhZGRlZCBvciB1cGRhdGVk" + "IGJ5IG5hbWUsIGV4aXN0aW5nIHNlY3JldHMgbm90IGluIHRoZSByZXF1ZXN0IGFyZSBwcmVzZXJ2" + - "ZWQgfCB8CnwgYG52Y2YtY2xpIHRhc2sgYnVsayAtLXRhc2staWRzPUlEMSxJRDJgIHwgRmV0Y2gg" + - "ZGV0YWlscyBmb3IgbXVsdGlwbGUgdGFza3MgaW4gb25lIGNhbGwgfCB8Cgo+IFJlZ2lzdHJ5IGNy" + - "ZWRlbnRpYWwgcHJvcGFnYXRpb246IGFmdGVyIGByZWdpc3RyeS1jcmVkZW50aWFsIGFkZGAvYHVw" + - "ZGF0ZWAvYGRlbGV0ZWAsIHRhc2sgY3JlYXRpb24gY2FuIGtlZXAgdXNpbmcgdGhlIHByZXZpb3Vz" + - "IGNyZWRlbnRpYWwgZm9yIHVwIHRvIGFib3V0IDUgbWludXRlcyAoTlZDVCBjYWNoZXMgYWNjb3Vu" + - "dCBjcmVkZW50aWFscyBhdCBgbnZjdC5udmNmLmNhY2hlLXR0bGAsIGRlZmF1bHQgYFBUNU1gKSwg" + - "ZXZlbiB0aG91Z2ggYHJlZ2lzdHJ5LWNyZWRlbnRpYWwgbGlzdGAvYGdldGAgc2hvdyB0aGUgbmV3" + - "IHZhbHVlIGltbWVkaWF0ZWx5LiBXYWl0IGFib3V0IDUgbWludXRlcywgb3IgYGt1YmVjdGwgLW4g" + - "bnZjZiByb2xsb3V0IHJlc3RhcnQgZGVwbG95bWVudC9udmN0LWFwaWAgdG8gYXBwbHkgaW1tZWRp" + - "YXRlbHkuIFNlZSBbcmVmZXJlbmNlL3Ryb3VibGVzaG9vdGluZy5tZF0ocmVmZXJlbmNlL3Ryb3Vi" + - "bGVzaG9vdGluZy5tZCkuCgojIyBMTE0gZnVuY3Rpb24gdHlwZQoKVXNlIGBmdW5jdGlvblR5cGU6" + - "ICJMTE0iYCBmb3IgT3BlbkFJLWNvbXBhdGlibGUgbW9kZWxzIHNlcnZlZCB0aHJvdWdoIHRoZSBz" + - "ZWxmLW1hbmFnZWQgTExNIEdhdGV3YXkuIExMTSBmdW5jdGlvbnMgbXVzdCBkZWZpbmUgYXQgbGVh" + - "c3Qgb25lIGBtb2RlbHNbXWAgZW50cnkgd2l0aCBgbmFtZWAgYW5kIGBsbG1Db25maWcudXJpc2A7" + - "IHRoZSBzdXBwb3J0ZWQgdXBzdHJlYW0gcGF0aHMgYXJlIGAvdjEvY2hhdC9jb21wbGV0aW9uc2As" + - "IGAvdjEvcmVzcG9uc2VzYCwgYW5kIGAvdjEvZW1iZWRkaW5nc2AuCgpVc2UgYC9oZWFsdGhgIG9u" + - "IHBvcnQgYDgwMDBgIGFzIHRoZSBkZWZhdWx0IE9wZW5BSS1jb21wYXRpYmxlIGNvbnRhaW5lciBo" + - "ZWFsdGggcHJvYmUgdW5sZXNzIHRoZSBpbWFnZSBleHBvc2VzIGEgZGlmZmVyZW50IHJlYWRpbmVz" + - "cyBwYXRoLgoKTExNIGZ1bmN0aW9uIHR5cGUgaXMgaW5kZXBlbmRlbnQgb2Ygd29ya2xvYWQgcGFj" + - "a2FnaW5nLiBGb3IgYSBIZWxtLWNoYXJ0IGJhY2tlZCBMTE0gZnVuY3Rpb24sIGtlZXAgYGZ1bmN0" + - "aW9uVHlwZTogIkxMTSJgIGFuZCBgbW9kZWxzW10ubGxtQ29uZmlnYCwgdGhlbiBzZXQgYGhlbG1D" + - "aGFydGAgYW5kIGBoZWxtQ2hhcnRTZXJ2aWNlTmFtZWAgaW4gdGhlIGNyZWF0ZSByZXF1ZXN0LiBg" + - "aGVsbUNoYXJ0U2VydmljZU5hbWVgIG11c3QgbWF0Y2ggdGhlIEt1YmVybmV0ZXMgU2VydmljZSBl" + - "eHBvc2VkIGJ5IHRoZSBjaGFydCwgYW5kIGBpbmZlcmVuY2VQb3J0YCBtdXN0IGJlIHRoYXQgU2Vy" + - "dmljZSBwb3J0LgoKSW52b2NhdGlvbiB1c2VzIHRoZSBMTE0gcm91dGUsIGZvciBleGFtcGxlIGBo" + - "dHRwczovL2xsbS5pbnZvY2F0aW9uLjxkb21haW4+L3YxL2NoYXQvY29tcGxldGlvbnNgLiBUaGUg" + - "T3BlbkFJIGBtb2RlbGAgdmFsdWUgbXVzdCBiZSBgPGZ1bmN0aW9uLWlkPi88bW9kZWwtbmFtZT5g" + - "OyB0aGUgZnVuY3Rpb24gSUQgaXMgdGhlIHJvdXRpbmcga2V5IGFuZCB0aGUgbW9kZWwgbmFtZSBp" + - "cyBmb3J3YXJkZWQgdXBzdHJlYW0uCgpVcGRhdGUgbXV0YWJsZSBwZXItbW9kZWwgcm91dGluZyBz" + - "ZXR0aW5ncyB3aXRoIGBudmNmLWNsaSBmdW5jdGlvbiB1cGRhdGUgLS1sbG0tbW9kZWwtdXBkYXRl" + - "PSduYW1lPTxtb2RlbD4scm91dGluZ01ldGhvZD08bWV0aG9kPix0b2tlblJhdGVMaW1pdD08bGlt" + - "aXQ+J2AsIG9yIHB1dCB0aGUgc2FtZSBmaWVsZHMgdW5kZXIgYG1vZGVsVXBkYXRlc1tdLmxsbUNv" + - "bmZpZ2AgaW4gYW4gdXBkYXRlIEpTT04gZmlsZS4gU2VlIFtyZWZlcmVuY2UvZmxhZ3MubWRdKHJl" + - "ZmVyZW5jZS9mbGFncy5tZCkgZm9yIGFjY2VwdGVkIHJvdXRpbmcgbWV0aG9kcy4gYHRva2VuUmF0" + - "ZUxpbWl0YCBzdXBwb3J0cyBwb3NpdGl2ZSBpbnRlZ2VyIGxpbWl0cyBmb3IgYFNgLCBgTWAsIGBI" + - "YCwgYERgLCBhbmQgYFdgOyB1c2UgSlNPTiBpbnB1dCBmb3IgY29tYmluZWQgbGltaXRzIHN1Y2gg" + - "YXMgYDEwMDAtUyw1MDAwLU0sMTAwMDAwLUgsNTAwMDAwLUQsMTAwMDAwMC1XYC4gRG8gbm90IGlu" + - "Y2x1ZGUgYHVyaXNgIGluIG1vZGVsIHVwZGF0ZXMuCgpGb3IgYC92MS9yZXNwb25zZXNgLCB0aGUg" + - "Z2F0ZXdheSBwcm94aWVzIHRoZSBuYXRpdmUgUmVzcG9uc2VzIHBhdGggdXBzdHJlYW0sIHJlbGF5" + - "cyBTU0UgdG8gc3RyZWFtaW5nIGNsaWVudHMsIGFuZCBhZ2dyZWdhdGVzIHRoZSB0ZXJtaW5hbCBK" + - "U09OIHJlc3BvbnNlIGZvciBub24tc3RyZWFtaW5nIGNsaWVudHMuIEZvciBgL3YxL2VtYmVkZGlu" + - "Z3NgLCBpbnB1dCBtYXkgYmUgYSBzdHJpbmcgb3Igc3RyaW5nIGFycmF5LCBtdXN0IGJlIG5vbi1l" + - "bXB0eSwgYW5kIG1heSBjb250YWluIGF0IG1vc3QgMjA0OCBlbnRyaWVzLgoKU2Vzc2lvbiBzdGlj" + - "a2luZXNzIHVzZXMgYHgtbXVsdGktdHVybi1zZXNzaW9uLWlkYCBmb3IgY2hhdCBjb21wbGV0aW9u" + - "cyBhbmQgUmVzcG9uc2VzIEFQSSByZXF1ZXN0cyBvbmx5LiBFbWJlZGRpbmdzIHJlcXVlc3RzIGRv" + - "IG5vdCB1c2Ugc3RpY2tpbmVzcy4KCiMjIENvbW1vbiB3b3JrZmxvd3MKCkZvciBzdGVwLWJ5LXN0" + - "ZXAgcGxheWJvb2tzLCBsb2FkIHRoZSBwcm9tcHQgdGhhdCBtYXRjaGVzIHRoZSB1c2VyJ3MgaW50" + - "ZW50OgoKLSAqKkluc3RhbGwgZnJvbSBzY3JhdGNoLioqIFtwcm9tcHRzL2luc3RhbGwtZnJvbS1z" + - "Y3JhdGNoLm1kXShwcm9tcHRzL2luc3RhbGwtZnJvbS1zY3JhdGNoLm1kKSDigJQgazNkIGNsdXN0" + - "ZXIg4oaSIHByZWZsaWdodCDihpIgdXAg4oaSIGRlcGxveSBhIHNtb2tlIGZ1bmN0aW9uLgotICoq" + - "QWRkIGEgbmV3IGNvbXB1dGUgcGxhbmUuKiogW3Byb21wdHMvYWRkLWNvbXB1dGUtcGxhbmUubWRd" + - "KHByb21wdHMvYWRkLWNvbXB1dGUtcGxhbmUubWQpIOKAlCBzcGxpdC1jbHVzdGVyIGB1cGAgYWdh" + - "aW5zdCBhbiBleGlzdGluZyBjb250cm9sIHBsYW5lLgotICoqRGVwbG95IGFuZCBpbnZva2UgYSBm" + - "dW5jdGlvbi4qKiBbcHJvbXB0cy9kZXBsb3ktYW5kLWludm9rZS5tZF0ocHJvbXB0cy9kZXBsb3kt" + - "YW5kLWludm9rZS5tZCkg4oCUIGNyZWF0ZSDihpIgZGVwbG95IOKGkiBBUEkga2V5IOKGkiBpbnZv" + - "a2UsIGluY2x1ZGluZyB0aGUgTExNIGNyZWF0ZS9pbnZva2UgdmFyaWFudC4KLSAqKkRpYWdub3Nl" + - "IGEgZmFpbGVkIGluc3RhbGwuKiogW3Byb21wdHMvZGlhZ25vc2UtZmFpbGVkLWluc3RhbGwubWRd" + - "KHByb21wdHMvZGlhZ25vc2UtZmFpbGVkLWluc3RhbGwubWQpIOKAlCBgc3RhdHVzIC0tanNvbmAg" + - "4oaSIGlkZW50aWZ5IGZhaWxlZCBjb21wb25lbnQg4oaSIGt1YmVjdGwgZGVzY3JpYmUg4oaSIHJl" + - "bWVkaWF0aW9uLgotICoqUm90YXRlIEpXS1MuKiogW3Byb21wdHMvcm90YXRlLWNsdXN0ZXItandr" + - "cy5tZF0ocHJvbXB0cy9yb3RhdGUtY2x1c3Rlci1qd2tzLm1kKSDigJQgd2hlbiBQU0FUIGF1dGgg" + - "c3RhcnRzIGZhaWxpbmcuCi0gKipUZWFyIGRvd24uKiogW3Byb21wdHMvdGVhcmRvd24ubWRdKHBy" + - "b21wdHMvdGVhcmRvd24ubWQpIOKAlCBgZG93biAtLXBsYW4tb25seWAgZmlyc3QsIHRoZW4gcmVh" + - "bCBydW4uIGBkb3duIC0tY2x1c3Rlci1uYW1lPVhgIGZvciBvbmUgY29tcHV0ZSBwbGFuZSAob3Jj" + - "aGVzdHJhdG9yOiBkcmFpbiArIHVuaW5zdGFsbCArIGNsdXN0ZXIgZGVsZXRlKTsgYHVuaW5zdGFs" + - "bCAtLWNvbnRyb2wtcGxhbmVgIGZvciB0aGUgY29udHJvbCBwbGFuZSAocGVyLXBsYW5lIHByaW1p" + - "dGl2ZSk7IGBkb3duIC0tYWxsIC0tY29uZmlybWAgZm9yIGV2ZXJ5dGhpbmc7IGB1bmluc3RhbGwg" + - "LS1uby1hcHBseSA8cGxhbmU+IHwga3ViZWN0bCBkZWxldGUgLWYgLWAgZm9yIEdpdE9wcy4KLSAq" + - "KkNyZWF0ZSBhbmQgcnVuIGEgdGFzay4qKiBbcHJvbXB0cy9jcmVhdGUtYW5kLXJ1bi10YXNrLm1k" + - "XShwcm9tcHRzL2NyZWF0ZS1hbmQtcnVuLXRhc2subWQpIOKAlCBtaW50IEFQSSBrZXlzIOKGkiB0" + - "YXNrIGNyZWF0ZSAoY29udGFpbmVyIG9yIEhlbG0pIOKGkiBtb25pdG9yIHdpdGggYHRhc2sgZ2V0" + - "YCAvIGB0YXNrIGV2ZW50c2Ag4oaSIHJldHJpZXZlIHJlc3VsdHMg4oaSIGNsZWFudXAuCgojIyBS" + - "ZWZlcmVuY2UKCi0gW3JlZmVyZW5jZS9jb21tYW5kcy5tZF0ocmVmZXJlbmNlL2NvbW1hbmRzLm1k" + - "KSDigJQgZnVsbCBzdWJjb21tYW5kIGNoZWF0IHNoZWV0Ci0gW3JlZmVyZW5jZS9mbGFncy5tZF0o" + - "cmVmZXJlbmNlL2ZsYWdzLm1kKSDigJQgZ2xvYmFsICsgcGVyLWNvbW1hbmQgZmxhZ3MKLSBbcmVm" + - "ZXJlbmNlL2V4aXQtY29kZXMubWRdKHJlZmVyZW5jZS9leGl0LWNvZGVzLm1kKSDigJQgd2hhdCBl" + - "YWNoIG5vbi16ZXJvIGV4aXQgbWVhbnMKLSBbcmVmZXJlbmNlL3Ryb3VibGVzaG9vdGluZy5tZF0o" + - "cmVmZXJlbmNlL3Ryb3VibGVzaG9vdGluZy5tZCkg4oCUIGtub3duIGVycm9ycyDihpIgcmVtZWRp" + - "YXRpb24gdGFibGUKCiMjIFNhZmV0eSBydWxlcyDigJQgQ1JJVElDQUwKCioqTkVWRVIgZG8gdGhl" + - "c2Ugd2l0aG91dCBleHBsaWNpdCB1c2VyIGNvbmZpcm1hdGlvbjoqKgotIGBudmNmLWNsaSBzZWxm" + - "LWhvc3RlZCBkb3duYCBvciBgdW5pbnN0YWxsYCBpbiBhbnkgZm9ybSDigJQgZGVzdHJ1Y3RpdmUu" + - "ICoqQUxXQVlTIHJ1biB3aXRoIGAtLXBsYW4tb25seWAgKGBkb3duYCkgb3IgYC0tbm8tYXBwbHlg" + - "IChgdW5pbnN0YWxsYCkgZmlyc3QqKiBhbmQgc2hvdyB0aGUgdXNlciB3aGF0IHdvdWxkIGhhcHBl" + - "bi4gU3RhdGUgd2hpY2ggY29tcHV0ZSBwbGFuZShzKSBhbmQgd2hldGhlciBwZXJzaXN0ZW50IHN0" + - "YXRlIHdvdWxkIGJlIHdpcGVkLgotIGBudmNmLWNsaSBzZWxmLWhvc3RlZCBkb3duIC0tcmVtb3Zl" + - "LXBlcnNpc3RlbnRgIChvciBgdW5pbnN0YWxsIC0tcmVtb3ZlLXBlcnNpc3RlbnRgKSDigJQgZGVs" + - "ZXRlcyBDYXNzYW5kcmEgcm93cywgT3BlbkJhbyBzZWFsIGtleXMsIHNyLWRlZmF1bHQgdXNlciBk" + - "YXRhLiAqKkxvc3MgaXMgdW5yZWNvdmVyYWJsZS4qKiBDb25maXJtIGV4cGxpY2l0bHkgdGhhdCB0" + - "aGlzIGlzIHdoYXQgdGhlIHVzZXIgd2FudHMuCi0gYG52Y2YtY2xpIHNlbGYtaG9zdGVkIHVuaW5z" + - "dGFsbCAtLWNvbnRyb2wtcGxhbmUgLS1mb3JjZS13aXRoLXJlZ2lzdGVyZWQtY2x1c3RlcnNgIOKA" + - "lCBvcnBoYW5zIGV2ZXJ5IHJlZ2lzdGVyZWQgY29tcHV0ZSBwbGFuZSAoUFNBVCBhdXRoIGJyZWFr" + - "cyBpbW1lZGlhdGVseSkuIFN0YXRlIHRoZSBjb25zZXF1ZW5jZSBiZWZvcmUgcGFzc2luZyB0aGlz" + - "IGZsYWcuCi0gYG52Y2YtY2xpIHNlbGYtaG9zdGVkIGRvd24gLS1hbGxgIG51a2VzIGV2ZXJ5dGhp" + - "bmcuIEFsd2F5cyBzaG93IHRoZSByZWdpc3RlcmVkIGNsdXN0ZXJzIChgbnZjZi1jbGkgY2x1c3Rl" + - "ciBsaXN0LXJlZ2lzdGVyZWQgLS1uY2EtaWQ9PG5jYS1pZD5gKSBhbmQgZ2V0IGNvbmZpcm1hdGlv" + - "bi4KLSBgbnZjZi1jbGkgY2x1c3RlciBkZWxldGVgIOKAlCByZW1vdmVzIHRoZSBjbHVzdGVyJ3Mg" + - "SUNNUyByZWdpc3RyYXRpb247IHRoZSBjb21wdXRlIHBsYW5lIGltbWVkaWF0ZWx5IHN0b3BzIGJl" + - "aW5nIGFibGUgdG8gYXV0aGVudGljYXRlLgotIGBudmNmLWNsaSBmdW5jdGlvbiBkZWxldGVgIOKA" + - "lCByZW1vdmVzIGEgZnVuY3Rpb24gYW5kIGFueSBhY3RpdmUgZGVwbG95bWVudC4KLSBgbnZjZi1j" + - "bGkgdGFzayBkZWxldGVgIOKAlCBwZXJtYW5lbnRseSByZW1vdmVzIHRoZSB0YXNrIHJlY29yZC4g" + - "U3RvcCwgc3RhdGUgdGhlIHRhc2sgSUQgYW5kIGN1cnJlbnQgc3RhdHVzLCB0aGVuIHdhaXQgZm9y" + - "IGEgc3Vic2VxdWVudCB1c2VyIHJlcGx5IHRoYXQgZXhwbGljaXRseSBjb25maXJtcyBkZWxldGlv" + - "biBvZiB0aGF0IHNwZWNpZmljIHRhc2sgYmVmb3JlIHJ1bm5pbmcgdGhpcyBjb21tYW5kLiBEbyBu" + - "b3QgdHJlYXQgdGhlIHVzZXIncyBvcmlnaW5hbCBkZWxldGUgcmVxdWVzdCBhcyBjb25maXJtYXRp" + - "b24uCi0gQW55IHJhdyBgaGVsbSB1bmluc3RhbGxgIG9yIGBrdWJlY3RsIGRlbGV0ZSBwdmMvcHZg" + - "IOKAlCBhZmZlY3RzIHBlcnNpc3RlbnQgc3RhdGUuIFByZWZlciBgbnZjZi1jbGkgc2VsZi1ob3N0" + - "ZWQgZG93bmAgKG9yY2hlc3RyYXRvcikgb3IgYHVuaW5zdGFsbGAgKHBlci1wbGFuZSkgd2hpY2gg" + - "aGFuZGxlIHRoaXMgc2FmZWx5LgotIEFueSBgLS1mb3JjZWAgZmxhZyAoYC0tZm9yY2Utd2l0aC1y" + - "ZWdpc3RlcmVkLWNsdXN0ZXJzYCwgYC0tY29uZmlybWAgaW4gbm9uLWludGVyYWN0aXZlIGNvbnRl" + - "eHRzKS4KCioqQUxXQVlTIGRvIHRoZXNlOioqCi0gUnVuIGBudmNmLWNsaSBzZWxmLWhvc3RlZCBz" + - "dGF0dXNgIGJlZm9yZSBhc3N1bWluZyBhIGNsdXN0ZXIgZXhpc3RzIC8gaXMgaGVhbHRoeS4KLSBT" + - "aG93IHRoZSBwbGFubmVkIGFjdGlvbiAoY2x1c3RlciBuYW1lLCBmdW5jdGlvbiBuYW1lLCBHUFUg" + - "dHlwZSwgY29zdCBpZiBrbm93bikgYmVmb3JlIGNyZWF0aW5nLgotIEJlZm9yZSBjcmVhdGluZyBv" + - "ciBkZXBsb3lpbmcgYSBjb250YWluZXIgb3IgTExNIGZ1bmN0aW9uLCBjb25maXJtIHRoZSBleGFj" + - "dCBmdW5jdGlvbiBuYW1lIGFuZCBjb250YWluZXIgaW1hZ2Ugd2l0aCB0aGUgdXNlci4gRm9yIExM" + - "TSBmdW5jdGlvbnMsIGFsc28gY29uZmlybSB0aGUgZXhhY3QgbW9kZWwgbmFtZSB1c2VkIGluIGBt" + - "b2RlbHNbXS5uYW1lYCBhbmQgT3BlbkFJIGBtb2RlbDogIjxmdW5jdGlvbi1pZD4vPG1vZGVsLW5h" + - "bWU+ImAuIElmIGFueSB2YWx1ZSBpcyBtaXNzaW5nLCBhc2sgdGhlIHVzZXIgaW5zdGVhZCBvZiBn" + - "dWVzc2luZyBvciBzdWJtaXR0aW5nIGV4YW1wbGUgcGxhY2Vob2xkZXJzLgotIENvbmZpcm0gZXhh" + - "Y3QgcmVzb3VyY2UgbmFtZXMgYmVmb3JlIGRlbGV0aW9uLiBNYXRjaCBhZ2FpbnN0IGBjbHVzdGVy" + - "IGxpc3QtcmVnaXN0ZXJlZGAgLyBgZnVuY3Rpb24gbGlzdGAgb3V0cHV0LgotIEluIENJIC8gbm9u" + - "LWludGVyYWN0aXZlIGNvbnRleHRzLCB1c2UgYC0tbm9uLWludGVyYWN0aXZlIC0tdG9rZW49JEpX" + - "VGAuIE5ldmVyIHByb3Bvc2UgaW50ZXJhY3RpdmUgYG52Y2YtY2xpIGluaXRgIHdoZW4gYCRDSWAg" + - "aXMgc2V0LgoKKipORVZFUiBwYXN0ZSB0aGVzZSBpbnRvIGNoYXQgLyBsb2dzIC8gZmVlZGJhY2s6" + - "KioKLSBBZG1pbiB0b2tlbnMgKGZ1bGwgSldUKS4gU2hvdyB0aGUgZmlyc3QgOCBjaGFycyArIGAu" + - "Li5gIGlmIHlvdSBtdXN0IHJlZmVyZW5jZSBvbmUuCi0gQVBJIGtleXMgKGBudmFwaS3igKZgKS4K" + - "LSBDb250ZW50cyBvZiBgfi8ubnZjZi1jbGkuc3RhdGVgIG9yIGFueSBrdWJlY29uZmlnLgotIEFu" + - "eSBkYXRhIG1hcmtlZCBzZWNyZXQgaW4gaGVsbWZpbGUgdmFsdWVzLgoKPiBOb3RlIChvbmx5IHdo" + - "ZW4gb3BlcmF0aW5nIG1vcmUgdGhhbiBvbmUgY2x1c3RlciBmcm9tIHRoZSBzYW1lIG1hY2hpbmUp" + - "OiB0aGUgQ0xJJ3MgZGVmYXVsdAo+IGNvbmZpZyAoYH4vLm52Y2YtY2xpLnlhbWxgKSBhbmQgc3Rh" + - "dGUgKGB+Ly5udmNmLWNsaS5zdGF0ZWApIGFyZSBhIHNpbmdsZSBzaGFyZWQgc2xvdCwgbm90Cj4g" + - "cGVyLWNsdXN0ZXIuIFdpdGggYSBzaW5nbGUgY29udHJvbCBwbGFuZSAodGhlIGNvbW1vbiBjYXNl" + - "KSB0aGlzIG5lZWRzIG5vIGF0dGVudGlvbi4gSWYgeW91Cj4gbWFuYWdlIG11bHRpcGxlIGNsdXN0" + - "ZXJzLCBwYXNzIGAtLWNvbmZpZyA8Y2x1c3Rlcj4ueWFtbGAgb24gZXZlcnkgY29tbWFuZCAob3Ig" + - "a2VlcCBhCj4gZGVsaWJlcmF0ZWx5LXN3aXRjaGVkIHBlci1jbHVzdGVyIGRlZmF1bHQpOiB3aXRo" + - "IG5vIGAtLWNvbmZpZ2AsIGNvbW1hbmRzIHRhcmdldCB3aGljaGV2ZXIKPiBjbHVzdGVyIHdhcyBs" + - "YXN0IGBpbml0YCdkLCBhbmQgYGluaXRgIC8gYGFwaS1rZXkgZ2VuZXJhdGVgIG11dGF0ZSB0aGF0" + - "IHNoYXJlZCBkZWZhdWx0LCBzbyBhbgo+IHVuc2NvcGVkIGBhcGkta2V5IGdlbmVyYXRlYCBjYW4g" + - "bWludCBvciBzdXJmYWNlIGtleXMgYWdhaW5zdCB0aGUgd3JvbmcgY2x1c3Rlci4KCiMjIE91dHB1" + - "dCBtb2RlcyAoZm9yIGFnZW50IHBpcGluZykKCmBudmNmLWNsaWAgc3ViY29tbWFuZHMgdGhhdCBs" + - "b25nLXJ1biAoYHVwYCwgYHN0YXR1c2AsIGBjaGVja2ApIGFjY2VwdCBmb3VyIG91dHB1dCBtb2Rl" + - "czoKCi0gKipgLS1qc29uYCoqIOKAlCBKU09OTCBldmVudHMgb24gc3RkZXJyOyBvbmUgZXZlbnQg" + - "cGVyIGxpbmU7IHN0YWJsZSBzY2hlbWEgKGBzY2hlbWFWZXJzaW9uOiAyYCkuICoqVXNlIHRoaXMg" + - "d2hlbiBydW5uaW5nIHVuZGVyIGFuIGFnZW50LioqIFBhcnNlIGxpbmUtYnktbGluZSB3aXRoIGBq" + - "cSAtYyAuYCBvciBganNvbi5sb2FkcygpYC4KLSAqKmAtLXBsYWluYCoqIOKAlCBQbGFpbiB0aW1l" + - "c3RhbXBlZCBsaW5lcywgUkZDMzMzOSBVVEMsIGBbTk4vOF1gIHBoYXNlIHByZWZpeDsgZ3JlcC1m" + - "cmllbmRseS4gRGVmYXVsdCBpbiBub24tVFRZLgotICoqYC0tYWNjZXNzaWJsZWAqKiDigJQgUGxh" + - "aW4gb3V0cHV0IHdpdGhvdXQgc3Bpbm5lcnMsIHdpdGggdmVyYm9zZSBzdGF0ZSBtYXJrZXJzIChg" + - "W2NvbXBsZXRlZF1gLCBgW3J1bm5pbmddYCwgYFtwZW5kaW5nXWAsIGBbZmFpbGVkXWApLiBGb3Ig" + - "c2NyZWVuIHJlYWRlcnMgYW5kIGNvbnN0cmFpbmVkIHRlcm1pbmFscy4KLSAqKihubyBmbGFnKSoq" + - "IOKAlCBCdWJibGV0ZWEgVFRZIGRhc2hib2FyZC4gRGVmYXVsdCBpbiBUVFkg4omlMTAww5czMC4g" + - "RG9uJ3QgdXNlIHVuZGVyIGFuIGFnZW50IChjdXJzb3ItdXAgc2VxdWVuY2VzIGFyZSBub2lzeSku" + - "CgpBdXRvLWRldGVjdCBwaWNrcyB0aGUgcmlnaHQgbW9kZSBmb3Igd2hhdGV2ZXIgc3Rkb3V0L3N0" + - "ZGVyciBpcy4gVGhlIENMSSBhbHNvIGhvbm9ycyBgTk9fQ09MT1JgIChhbnkgdmFsdWUg4oaSIGZv" + - "cmNlcyBwbGFpbiksIGBURVJNPWR1bWJgIChmb3JjZXMgcGxhaW4pLCBhbmQgYENJPXRydXRoeWAg" + - "KGZvcmNlcyBwbGFpbiBldmVuIG9uIGEgZmFrZSBUVFkpLiBXaGVuIHRoZSB0ZXJtaW5hbCBpcyBz" + - "bWFsbGVyIHRoYW4gMTAww5czMCwgdGhlIGJ1YmJsZXRlYSByZW5kZXJlciBmYWxscyBiYWNrIHRv" + - "IGEgY29tcGFjdCBsYXlvdXQuIEV4cGxpY2l0IGAtLWpzb25gIGlzIHRoZSByaWdodCBjYWxsIGZv" + - "ciBhZ2VudCBwaXBpbmcuCgojIyBPbiBmYWlsdXJlCgpgbnZjZi1jbGlgIGZhaWx1cmVzIGVtaXQg" + - "c3RydWN0dXJlZCBgcGhhc2VfZmFpbGVkYCBldmVudHMgaW4gSlNPTjoKCmBgYGpzb24KeyJldmVu" + - "dCI6InBoYXNlX2ZhaWxlZCIsInBoYXNlTnVtIjo0LCJwaGFzZSI6ImFwcGx5LWNwIiwKICJlcnJD" + - "YXRlZ29yeSI6ImhlbG1fYXBwbHkiLCJlcnJNZXNzYWdlIjoiaGVsbSBpbnN0YWxsIGFwaS1rZXlz" + - "OiB0aW1lZCBvdXQiLAogInJldHJ5Q2xhc3MiOiJiYWNrb2ZmIiwicmV0cnlBZnRlclNlYyI6NjAs" + - "CiAicmVtZWRpYXRpb24iOlsia3ViZWN0bCBkZXNjcmliZSBwb2QgLW4gY2Fzc2FuZHJhLXN5c3Rl" + - "bSBjYXNzYW5kcmEtMCIsCiAgICAgICAgICAgICAgICAiUmUtcnVuIHdpdGggLS1kZWJ1ZyBmb3Ig" + - "dmVyYm9zZSBoZWxtZmlsZSBvdXRwdXQiXSwKICJyYXciOnsic3VicHJvY2VzcyI6ImhlbG1maWxl" + - "IiwiZXhpdENvZGUiOjEsInN0ZGVyclRhaWwiOiLigKYiLCJrdWJlcm5ldGVzUmVhc29uIjoiRmFp" + - "bGVkU2NoZWR1bGluZyJ9fQpgYGAKClRoZSBgcmV0cnlDbGFzc2AgZmllbGQgdGVsbHMgdGhlIGFn" + - "ZW50IGhvdyB0byBoYW5kbGUgdGhlIGZhaWx1cmU6Cgp8IGByZXRyeUNsYXNzYCB8IE1lYW5pbmcg" + - "fCBBZ2VudCBhY3Rpb24gfAp8LS0tfC0tLXwtLS18CnwgYGltbWVkaWF0ZWAgfCBUcmFuc2llbnQg" + - "YmxpcCB8IFJldHJ5IHRoZSBzYW1lIGB1cGAgY29tbWFuZCBub3cgfAp8IGBiYWNrb2ZmYCB8IFJh" + - "dGUgbGltaXQgLyBwZW5kaW5nIG9wZXJhdGlvbiB8IFdhaXQgYHJldHJ5QWZ0ZXJTZWNgIHNlY29u" + - "ZHMsIHRoZW4gcmV0cnkgfAp8IGBhZnRlcl9yZW1lZGlhdGlvbmAgfCBPcGVyYXRvciBtdXN0IGlu" + - "dGVydmVuZSB8IFN1cmZhY2UgYGVyck1lc3NhZ2VgICsgYHJlbWVkaWF0aW9uYCwgU1RPUC4gRG9u" + - "J3QgYXV0by1yZXRyeS4gfAp8IGBub25lYCB8IE5vbi1yZXRyeWFibGUgfCBTYW1lIGFzIGBhZnRl" + - "cl9yZW1lZGlhdGlvbmAg4oCUIHN1cmZhY2UgYW5kIHN0b3AgfAp8IGB1bmtub3duYCB8IENsYXNz" + - "aWZpZXIgdW5zdXJlIHwgVHJlYXQgY29uc2VydmF0aXZlbHk6IHN1cmZhY2UgYW5kIHN0b3AgfAoK" + - "TmV2ZXIgcmUtcnVuIHdpdGggYC0tZm9yY2VgIChubyBjb21tYW5kIHRha2VzIG9uZSkuIFRoZSBg" + - "cmF3YCBibG9jayBjYXJyaWVzIHRoZSB1bmRlcmx5aW5nIHNpZ25hbCAoc3VicHJvY2VzcyBleGl0" + - "LCBIVFRQIHN0YXR1cywgSzhzIHJlYXNvbikg4oCUIHF1b3RlIHJlbGV2YW50IGZpZWxkcyB3aGVu" + - "IGV4cGxhaW5pbmcgZmFpbHVyZXMgdG8gdGhlIHVzZXIsIGJ1dCBkb24ndCBkdW1wIGBzdGRlcnJU" + - "YWlsYCB2ZXJiYXRpbSBpbnRvIGNoYXQgd2l0aG91dCBzY2FubmluZyBmb3Igc2VjcmV0cyBmaXJz" + - "dC4KCiMjIFF1aWNrIGNvbW1hbmQgcmVmZXJlbmNlCgpgYGBzaApudmNmLWNsaSBzZWxmLWhvc3Rl" + - "ZCBjaGVjayAtLXByZSAgICAgICAgICAgICAgIyBwcmUtZmxpZ2h0Cm52Y2YtY2xpIHNlbGYtaG9z" + - "dGVkIHVwIC0tY2x1c3Rlci1uYW1lPU5BTUUgICAjIG9uZS1zaG90IGluc3RhbGwKbnZjZi1jbGkg" + - "c2VsZi1ob3N0ZWQgc3RhdHVzICAgICAgICAgICAgICAgICAgICMgc25hcHNob3QKbnZjZi1jbGkg" + - "c2VsZi1ob3N0ZWQgc3RhdHVzIC0td2F0Y2ggICAgICAgICAgICMgbGl2ZQpudmNmLWNsaSBpbml0" + - "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBtaW50IGFkbWluIHRva2VuIChjbGVh" + - "cnMgYWxsIHNhdmVkIEFQSSBrZXlzKQpudmNmLWNsaSBjbHVzdGVyIHJlZ2lzdGVyIOKApiAgICAg" + - "ICAgICAgICAgICAgICAjIHJlZ2lzdGVyIGNsdXN0ZXIKbnZjZi1jbGkgY2x1c3RlciBsaXN0LXJl" + - "Z2lzdGVyZWQgLS1uY2EtaWQ9PG5jYS1pZD4gIyBsaXN0IHNlbGYtaG9zdGVkIHJlZ2lzdHJhdGlv" + - "bnMKbnZjZi1jbGkgYXBpLWtleSBnZW5lcmF0ZSAtLWRlc2NyaXB0aW9uPeKApiAgICAgIyBtaW50" + - "IGJvdGggZnVuY3Rpb24gYW5kIHRhc2sgQVBJIGtleXMgKHJ1biBhZnRlciBldmVyeSBpbml0KQpu" + - "dmNmLWNsaSBhcGkta2V5IGdlbmVyYXRlIC0tZm9yIGZ1bmN0aW9uIOKApiAgICAjIGZ1bmN0aW9u" + - "IGtleSBvbmx5Cm52Y2YtY2xpIGFwaS1rZXkgZ2VuZXJhdGUgLS1mb3IgdGFzayDigKYgICAgICAg" + - "ICMgdGFzayBrZXkgb25seQpudmNmLWNsaSBmdW5jdGlvbiBjcmVhdGUgLS1pbnB1dC1maWxlPeKA" + - "piAgICAgICAjIGNyZWF0ZSBmdW5jdGlvbgpudmNmLWNsaSBmdW5jdGlvbiBjcmVhdGUgLS1mdW5j" + - "dGlvbi10eXBlPUxMTSAtLWxsbS1tb2RlbD08c3BlYz4gICMgY3JlYXRlIExMTSBmdW5jdGlvbiBt" + - "ZXRhZGF0YQpudmNmLWNsaSBmdW5jdGlvbiBjcmVhdGUgLS1uYW1lPWxsbS1oZWxtIC0taW5mZXJl" + - "bmNlLXVybD0vIC0taW5mZXJlbmNlLXBvcnQ9ODAwMCBcCiAgLS1mdW5jdGlvbi10eXBlPUxMTSAt" + - "LWhlbG0tY2hhcnQ9PGNoYXJ0PiAtLWhlbG0tY2hhcnQtc2VydmljZT08c2VydmljZT4gLS1sbG0t" + - "bW9kZWw9PHNwZWM+Cm52Y2YtY2xpIGZ1bmN0aW9uIHVwZGF0ZSAtLWxsbS1tb2RlbC11cGRhdGU9" + - "PHNwZWM+ICAjIHVwZGF0ZSBMTE0gcm91dGluZy90b2tlbiBsaW1pdHMKbnZjZi1jbGkgZnVuY3Rp" + - "b24gZGVwbG95IGNyZWF0ZSAtLWlucHV0LWZpbGU94oCmCm52Y2YtY2xpIGZ1bmN0aW9uIGludm9r" + - "ZSAtLWlucHV0LWZpbGU94oCmCgojIFRhc2sgY29tbWFuZHMg4oCUIHJlcXVpcmVzIE5WQ0ZfQkFT" + - "RV9OVkNUX1VSTD1odHRwOi8vPG52Y3QtZ2F0ZXdheT4KbnZjZi1jbGkgdGFzayBjcmVhdGUgLS1u" + - "YW1lPVggLS1ncHU9SDEwMCAtLWluc3RhbmNlLXR5cGU9WSAtLWltYWdlPVoKbnZjZi1jbGkgdGFz" + - "ayBjcmVhdGUgLS1pbnB1dC1maWxlPXRhc2suanNvbiAgICMgZnJvbSBKU09OIGNvbmZpZwpudmNm" + - "LWNsaSB0YXNrIGxpc3QgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBsaXN0IGFsbCB0YXNr" + - "cwpudmNmLWNsaSB0YXNrIGxpc3QgLS1zdGF0dXM9UlVOTklORyAgICAgICAgICAgIyBmaWx0ZXIg" + - "Ynkgc3RhdHVzCm52Y2YtY2xpIHRhc2sgZ2V0IFt0YXNrSWRdICAgICAgICAgICAgICAgICAgICAj" + - "IHRhc2sgZGV0YWlscwpudmNmLWNsaSB0YXNrIGV2ZW50cyBbdGFza0lkXSAgICAgICAgICAgICAg" + - "ICAgIyBsaWZlY3ljbGUgZXZlbnRzCm52Y2YtY2xpIHRhc2sgcmVzdWx0cyBbdGFza0lkXSAgICAg" + - "ICAgICAgICAgICAjIHJlc3VsdCBhcnRpZmFjdHMKbnZjZi1jbGkgdGFzayBjYW5jZWwgW3Rhc2tJ" + - "ZF0KbnZjZi1jbGkgdGFzayBkZWxldGUgW3Rhc2tJZF0KYGBgCgojIyBGZWVkYmFjawoKSWYgdGhl" + - "IHVzZXIgaGl0cyBhIGJ1ZyBvciBsaW1pdGF0aW9uLCBmaWxlIGFuIGlzc3VlIHRocm91Z2ggdGhl" + - "IHByb2plY3QgdHJhY2tlci4gRG9uJ3QgaW5jbHVkZSBzZWNyZXRzLgpQSwcIzYEw5E1hAABNYQAA" + - "UEsDBBQACAAAAAAAIQAAAAAAAAAAAAAAAAAzAAkAZGF0YS9udmNmLXNlbGYtbWFuYWdlZC1jbGkv" + - "ZXhhbXBsZXMvY2ktcGlwZWxpbmVzLm1kVVQFAAEAps4SIyBDSSBwaXBlbGluZSBwYXR0ZXJucwoK" + - "SG93IHRvIGRyaXZlIGBudmNmLWNsaWAgZnJvbSBDSSAoR2l0TGFiIENJLCBHaXRIdWIgQWN0aW9u" + - "cywgSmVua2lucywgQXJnb0NEKS4gUGF0dGVybnMgdGhhdCBzdXJmYWNlIGZyZXF1ZW50bHkuCgoj" + - "IyBCYXNpYyBpbnN0YWxsIHBpcGVsaW5lCgpgYGB5YW1sCiMgR2l0TGFiIENJIGV4YW1wbGUKZGVw" + - "bG95X252Y2Y6CiAgc3RhZ2U6IGRlcGxveQogIGltYWdlOiBudmNyLmlvLzA2NTExNTUyMTU4NjQ5" + - "NzkvbmNwLWRldi9udmNmLWNsaToxLjQuMAogIHZhcmlhYmxlczoKICAgIEtVQkVDT05GSUc6ICRL" + - "VUJFQ09ORklHX0ZJTEUKICBzY3JpcHQ6CiAgICAtIG52Y2YtY2xpIHNlbGYtaG9zdGVkIGNoZWNr" + - "IC0tcHJlIC0tanNvbiB8IGpxIC1lICcuZXZlbnQgIT0gInBoYXNlX2ZhaWxlZCInIHx8IGV4aXQg" + - "MgogICAgLSBudmNmLWNsaSBzZWxmLWhvc3RlZCB1cCAtLWNsdXN0ZXItbmFtZT0kQ0xVU1RFUl9O" + - "QU1FIC0tdG9rZW49JE5WQ0ZfQURNSU5fSldUIC0tbm9uLWludGVyYWN0aXZlIC0tanNvbgogICAg" + - "LSBudmNmLWNsaSBzZWxmLWhvc3RlZCBzdGF0dXMgLS1qc29uIHwganEgLWUgJy52ZXJkaWN0ID09" + - "ICJoZWFsdGh5IicKYGBgCgpOb3RlczoKLSBgLS1ub24taW50ZXJhY3RpdmUgLS10b2tlbj0kSldU" + - "YCBpcyByZXF1aXJlZCBpbiBDSTsgbmV2ZXIgdXNlIGludGVyYWN0aXZlIGBpbml0YC4KLSBBbHdh" + - "eXMgYC0tanNvbmAgZm9yIG1hY2hpbmUtcGFyc2luZy4KLSBGaW5hbCBzdGF0dXMgY2hlY2sgZ2F0" + - "ZXMgZG93bnN0cmVhbSBzdGFnZXMgb24gYHZlcmRpY3QgPT0gImhlYWx0aHkiYC4KCiMjIEdpdE9w" + - "cyAoQXJnbyAvIEZsdXgpIHBhdHRlcm4KCkNJIGRvZXNuJ3QgYGt1YmVjdGwgYXBwbHlgIOKAlCBp" + - "bnN0ZWFkLCByZW5kZXIgbWFuaWZlc3RzLCBjb21taXQgdGhlbSwgbGV0IHRoZSBjb250cm9sbGVy" + - "IGFwcGx5LgoKYGBgeWFtbApyZW5kZXI6CiAgc2NyaXB0OgogICAgLSBudmNmLWNsaSBzZWxmLWhv" + - "c3RlZCBpbnN0YWxsIC0tY29udHJvbC1wbGFuZSAtLW5vLWFwcGx5ID4gcmVuZGVyZWQvY29udHJv" + - "bC1wbGFuZS55YW1sCiAgICAtIG52Y2YtY2xpIHNlbGYtaG9zdGVkIGluc3RhbGwgLS1jb21wdXRl" + - "LXBsYW5lIC0tY2x1c3Rlci1uYW1lPSROQU1FIC0tbm8tYXBwbHkgPiByZW5kZXJlZC9jb21wdXRl" + - "LXBsYW5lLnlhbWwKICAgIC0gZ2l0IGFkZCByZW5kZXJlZC8gJiYgZ2l0IGNvbW1pdCAtbSAiZGVw" + - "bG95IE5WQ0YgJFRBRyIgJiYgZ2l0IHB1c2gKYGBgCgpUaGVuIEFyZ29DRCBvciBGbHV4IHdhdGNo" + - "ZXMgYHJlbmRlcmVkL2AgYW5kIGFwcGxpZXMuIFRoZSBDTEkgbmV2ZXIgdG91Y2hlcyB0aGUgY2x1" + - "c3RlciBkaXJlY3RseS4KCkZvciB0aGUgcmVnaXN0ZXIgc3RlcCAod2hpY2ggbXV0YXRlcyBJQ01T" + - "LCBub3QganVzdCBLOHMpOgoKYGBgeWFtbApyZWdpc3RlcjoKICBzY3JpcHQ6CiAgICAtIG52Y2Yt" + - "Y2xpIGNsdXN0ZXIgcmVnaXN0ZXIgLS1uYW1lPSROQU1FIC0tbmNhLWlkPSROQ0EgLS1yZWdpb249" + - "JFJFR0lPTiAtLWlnbm9yZS1leGlzdGluZwpgYGAKCmAtLWlnbm9yZS1leGlzdGluZ2AgaXMgdGhl" + - "IEdpdE9wcy1mcmllbmRseSBpZGVtcG90ZW50IGNvbnRyYWN0LgoKIyMgTXVsdGktY2x1c3RlciBw" + - "aXBlbGluZQoKV2hlbiB0aGUgcGlwZWxpbmUgbWFuYWdlcyBtYW55IGNvbXB1dGUgY2x1c3RlcnMg" + - "YWdhaW5zdCBvbmUgY29udHJvbCBwbGFuZToKCmBgYHlhbWwKZGVwbG95X2NvbXB1dGVfcGxhbmVz" + - "OgogIHBhcmFsbGVsOgogICAgbWF0cml4OgogICAgICAtIENMVVNURVI6IG5jcC1lYXN0LTEKICAg" + - "ICAgICBDT05URVhUOiBhZG1pbkBncHUtZWFzdC0xCiAgICAgIC0gQ0xVU1RFUjogbmNwLXdlc3Qt" + - "MQogICAgICAgIENPTlRFWFQ6IGFkbWluQGdwdS13ZXN0LTEKICAgICAgLSBDTFVTVEVSOiBuY3At" + - "ZXUtMQogICAgICAgIENPTlRFWFQ6IGFkbWluQGdwdS1ldS0xCiAgc2NyaXB0OgogICAgLSBudmNm" + - "LWNsaSBzZWxmLWhvc3RlZCB1cAogICAgICAgIC0tY2x1c3Rlci1uYW1lPSRDTFVTVEVSCiAgICAg" + - "ICAgLS1jb21wdXRlLXBsYW5lLWNvbnRleHQ9JENPTlRFWFQKICAgICAgICAtLWljbXMtdXJsPSRJ" + - "Q01TX1BVQkxJQ19VUkwKICAgICAgICAtLXRva2VuPSROVkNGX0FETUlOX0pXVAogICAgICAgIC0t" + - "bm9uLWludGVyYWN0aXZlCiAgICAgICAgLS1qc29uCmBgYAoKRWFjaCBwYXJhbGxlbCBqb2IgcmVn" + - "aXN0ZXJzICsgaW5zdGFsbHMgb25lIGNvbXB1dGUgcGxhbmUgYWdhaW5zdCB0aGUgc2hhcmVkIGNv" + - "bnRyb2wgcGxhbmUuIEZhaWx1cmVzIGFyZSBpc29sYXRlZC4KCiMjIFBsYW4tb25seSBwcmV2aWV3" + - "IGluIFBScwoKYGBgeWFtbApwcmV2aWV3OgogIHJ1bGVzOgogICAgLSBpZjogJENJX1BJUEVMSU5F" + - "X1NPVVJDRSA9PSAibWVyZ2VfcmVxdWVzdF9ldmVudCIKICBzY3JpcHQ6CiAgICAtIHwKICAgICAg" + - "bnZjZi1jbGkgc2VsZi1ob3N0ZWQgdXAgXAogICAgICAgIC0tY2x1c3Rlci1uYW1lPSRDTFVTVEVS" + - "X05BTUUgXAogICAgICAgIC0tcGxhbi1vbmx5IFwKICAgICAgICAtLWpzb24gfCB0ZWUgcGxhbi5q" + - "c29ubAogICAgLSBqcSAtcyAnLlswXScgcGxhbi5qc29ubCA+IHBsYW4uanNvbgogICAgLSAnZ2l0" + - "bGFiLW1yLWNvbW1lbnQgIiMjIE5WQ0YgZGVwbG95IHBsYW5cblxuXGBcYFxganNvblxuJChjYXQg" + - "cGxhbi5qc29uKVxuXGBcYFxgIicKYGBgCgpgLS1wbGFuLW9ubHlgIGV4aXRzIDAgd2l0aG91dCBj" + - "aGFuZ2luZyBjbHVzdGVyIHN0YXRlOyB0aGUgSlNPTkwgb3V0cHV0IGxpc3RzIGVhY2ggcGhhc2Ug" + - "KyBFVEEuIFJlbmRlcnMgbmljZWx5IGFzIGFuIE1SIGNvbW1lbnQuClBLBwghxsiG8AoAAPAKAABQ" + - "SwMEFAAIAAAAAAAhAAAAAAAAAAAAAAAAADQACQBkYXRhL252Y2Ytc2VsZi1tYW5hZ2VkLWNsaS9l" + - "eGFtcGxlcy9tdWx0aS1jbHVzdGVyLm1kVVQFAAEAps4SIyBNdWx0aS1jbHVzdGVyIHBhdHRlcm5z" + - "CgpFeGFtcGxlcyBvZiBzcGxpdC1jbHVzdGVyICsgbWFueS1jb21wdXRlLXBsYW5lIG9wZXJhdGlv" + - "bi4KCiMjIE9uZSBjb250cm9sIHBsYW5lICsgTiBjb21wdXRlIHBsYW5lcwoKVGhlIGNhbm9uaWNh" + - "bCBtdWx0aS1jbHVzdGVyIHRvcG9sb2d5LiBFYWNoIGNvbXB1dGUgcGxhbmUgaXMgcmVnaXN0ZXJl" + - "ZCBzZXBhcmF0ZWx5OgoKYGBgc2gKIyAxLiBCcmluZyB1cCB0aGUgY29udHJvbCBwbGFuZSAob25l" + - "LXRpbWUsIG9uIHRoZSBjb250cm9sLXBsYW5lIGNsdXN0ZXIpOgpLVUJFQ09ORklHPWNwLnlhbWwg" + - "bnZjZi1jbGkgc2VsZi1ob3N0ZWQgaW5zdGFsbCAtLWNvbnRyb2wtcGxhbmUgfCBrdWJlY3RsIGFw" + - "cGx5IC1mIC0KbnZjZi1jbGkgc2VsZi1ob3N0ZWQgY2hlY2sgLS1jb250cm9sLXBsYW5lIC0td2Fp" + - "dCA1bQoKIyAyLiBSZWdpc3RlciArIGluc3RhbGwgZWFjaCBjb21wdXRlIHBsYW5lOgpmb3IgQ1RY" + - "IGluIGFkbWluQGdwdS1lYXN0LTEgYWRtaW5AZ3B1LXdlc3QtMSBhZG1pbkBncHUtZXUtMTsgZG8K" + - "ICBOQU1FPSQoZWNobyAiJENUWCIgfCBjdXQgLWRAIC1mMikKICBudmNmLWNsaSBzZWxmLWhvc3Rl" + - "ZCB1cCBcCiAgICAtLWNsdXN0ZXItbmFtZT0kTkFNRSBcCiAgICAtLWNvbXB1dGUtcGxhbmUtY29u" + - "dGV4dD0kQ1RYIFwKICAgIC0taWNtcy11cmw9aHR0cHM6Ly9pY21zLm52Y2YuZXhhbXBsZS5jb20g" + - "XAogICAgLS10b2tlbj0kTlZDRl9BRE1JTl9KV1QgXAogICAgLS1ub24taW50ZXJhY3RpdmUgXAog" + - "ICAgLS1qc29uCmRvbmUKCiMgMy4gVmVyaWZ5IGFsbCBjb21wdXRlIHBsYW5lcyByZWdpc3RlcmVk" + - "OgpudmNmLWNsaSBjbHVzdGVyIGxpc3QtcmVnaXN0ZXJlZCAtLW5jYS1pZD0kTkNBX0lEIC0taWNt" + - "cy11cmw9JElDTVMgLS1qc29uIFwKICB8IGpxICcuY2x1c3RlcnNbXSB8IHtuYW1lOiAuY2x1c3Rl" + - "ck5hbWUsIHN0YXR1cywgbnZjYVZlcnNpb259JwpgYGAKCiMjIENvbXB1dGUtcGxhbmUtb25seSBv" + - "cGVyYXRvciBzY2VuYXJpbwoKVGhlIG9wZXJhdG9yIGhhcyBrdWJlY3RsIGFjY2VzcyB0byBPTkUg" + - "Y29tcHV0ZSBwbGFuZSBvbmx5IOKAlCB0aGUgY29udHJvbCBwbGFuZSBpcyBvcGVyYXRlZCBieSBz" + - "b21lb25lIGVsc2UgKGUuZy4gWW90dGEgcnVucyB0aGUgY29udHJvbCBwbGFuZSBmb3IgYSBjdXN0" + - "b21lcikuCgpgYGBzaAojIE1pbnQgYSB0b2tlbiB2aWEgdGhlIHB1YmxpYyBhcGkgZ2F0ZXdheSAo" + - "dGhlIGNvbnRyb2wgcGxhbmUncyBBUEkgS2V5cwojIHNlcnZpY2UgaXMgcmVhY2hhYmxlIHZpYSBh" + - "cGkubnZjZi5leGFtcGxlLmNvbSDigJQgbm8ga3ViZWN0bCBhY2Nlc3MgbmVlZGVkKToKbnZjZi1j" + - "bGkgaW5pdCAtLWFwaS11cmw9aHR0cHM6Ly9hcGkubnZjZi5leGFtcGxlLmNvbQoKIyBSdW4gcHJl" + - "LWZsaWdodCBzY29wZWQgdG8gdGhlIGNvbXB1dGUgcGxhbmUgb25seToKbnZjZi1jbGkgc2VsZi1o" + - "b3N0ZWQgY2hlY2sgLS1wcmUgXAogIC0tY29tcHV0ZS1wbGFuZS1jb250ZXh0PWFkbWluQGdwdTEg" + - "XAogIC0taWNtcy11cmw9aHR0cHM6Ly9pY21zLm52Y2YuZXhhbXBsZS5jb20gXAogIC0tanNvbgoK" + - "IyBCcmluZyB1cCBqdXN0IHRoZSBjb21wdXRlIHBsYW5lOgpudmNmLWNsaSBzZWxmLWhvc3RlZCB1" + - "cCBcCiAgLS1jbHVzdGVyLW5hbWU9bXktY29tcHV0ZS0xIFwKICAtLWNvbXB1dGUtcGxhbmUtY29u" + - "dGV4dD1hZG1pbkBncHUxIFwKICAtLWljbXMtdXJsPWh0dHBzOi8vaWNtcy5udmNmLmV4YW1wbGUu" + - "Y29tIFwKICAtLXRva2VuPSRBRE1JTl9KV1QgXAogIC0tbm9uLWludGVyYWN0aXZlCgojIFN0YXR1" + - "cyAoY29tcHV0ZS1vbmx5IOKAlCBvcGVyYXRvciBjYW4ndCBzZWUgY29udHJvbC1wbGFuZSBjb21w" + - "b25lbnQgaGVhbHRoKToKbnZjZi1jbGkgc2VsZi1ob3N0ZWQgc3RhdHVzIFwKICAtLWNvbXB1dGUt" + - "cGxhbmUtY29udGV4dD1hZG1pbkBncHUxIFwKICAtLWljbXMtdXJsPWh0dHBzOi8vaWNtcy5udmNm" + - "LmV4YW1wbGUuY29tCmBgYAoKIyMgRnVuY3Rpb25zIHRhcmdldGluZyBzcGVjaWZpYyBjb21wdXRl" + - "IHBsYW5lcwoKV2hlbiB5b3UgaGF2ZSBtdWx0aXBsZSBjb21wdXRlIHBsYW5lcyBhbmQgd2FudCBh" + - "IGZ1bmN0aW9uIHRvIGRlcGxveSB0byBvbmU6CgpgYGBqc29uCnsKICAiZnVuY3Rpb25JZCI6ICI8" + - "Zm5faWQ+IiwKICAidmVyc2lvbklkIjogIjx2ZXJfaWQ+IiwKICAiZGVwbG95bWVudFNwZWNpZmlj" + - "YXRpb25zIjogW3sKICAgICJncHUiOiAiSDEwMCIsCiAgICAiaW5zdGFuY2VUeXBlIjogIk5DUC5H" + - "UFUuSDEwMF8xeCIsCiAgICAibWluSW5zdGFuY2VzIjogMSwKICAgICJtYXhJbnN0YW5jZXMiOiAx" + - "LAogICAgImNsdXN0ZXJzIjogWyJncHUtZWFzdC0xIl0KICB9XQp9CmBgYAoKVGhlIGBjbHVzdGVy" + - "c2AgYXJyYXkgKHVzaW5nIGNsdXN0ZXIgbmFtZXMsIG5vdCBJRHMpIGxpbWl0cyBzY2hlZHVsaW5n" + - "IHRvIHRob3NlIGNvbXB1dGUgcGxhbmVzLiBPbWl0IHRvIGxldCBJQ01TIGNob29zZSBhbnkgY29t" + - "cHV0ZSBwbGFuZSB3aXRoIGEgbWF0Y2hpbmcgU0tVLgoKIyMgU3RhdHVzIGZhbi1vdXQgYWNyb3Nz" + - "IGNvbXB1dGUgcGxhbmVzCgpgYGBzaAojIExpc3QgYWxsIHJlZ2lzdGVyZWQgY29tcHV0ZSBwbGFu" + - "ZXM6Cm52Y2YtY2xpIGNsdXN0ZXIgbGlzdC1yZWdpc3RlcmVkIC0tbmNhLWlkPSROQ0FfSUQgLS1p" + - "Y21zLXVybD0kSUNNUyAtLWpzb24gXAogIHwganEgLXIgJy5jbHVzdGVyc1tdLmNsdXN0ZXJOYW1l" + - "JyA+IGNsdXN0ZXJzLnR4dAoKIyBTdGF0dXMgc25hcHNob3QgcGVyIGNvbXB1dGUgcGxhbmUgKGFz" + - "c3VtaW5nIGVhY2ggY29udGV4dCBuYW1lIG1hdGNoZXMpOgp3aGlsZSByZWFkIE5BTUU7IGRvCiAg" + - "ZWNobyAiPT09ICROQU1FID09PSIKICBudmNmLWNsaSBzZWxmLWhvc3RlZCBzdGF0dXMgXAogICAg" + - "LS1jbHVzdGVyLW5hbWU9JE5BTUUgXAogICAgLS1jb21wdXRlLXBsYW5lLWNvbnRleHQ9YWRtaW5A" + - "JE5BTUUgXAogICAgLS1qc29uIHwganEgLWMgJ3tjbHVzdGVyOi5jbHVzdGVyLCB2ZXJkaWN0Oi52" + - "ZXJkaWN0LCByZWNvbmNpbGU6LnJlY29uY2lsZUFnZVNlY30nCmRvbmUgPCBjbHVzdGVycy50eHQK" + - "YGBgCgpBIGZ1dHVyZSBNKzExIG1pbGVzdG9uZSB3aWxsIGFkZCBuYXRpdmUgZmFuLW91dCAoYHN0" + - "YXR1cyAtLWFsbC1jb21wdXRlLXBsYW5lc2ApIHNvIHRoaXMgZG9lc24ndCBuZWVkIGEgc2hlbGwg" + - "bG9vcC4KUEsHCKEU44FdDAAAXQwAAFBLAwQUAAgAAAAAACEAAAAAAAAAAAAAAAAANwAJAGRhdGEv" + - "bnZjZi1zZWxmLW1hbmFnZWQtY2xpL3Byb21wdHMvYWRkLWNvbXB1dGUtcGxhbmUubWRVVAUAAQCm" + - "zhIjIEFkZCBhIGNvbXB1dGUgcGxhbmUgdG8gYW4gZXhpc3RpbmcgY29udHJvbCBwbGFuZQoKVXNl" + - "ciBoYXMgYSB3b3JraW5nIE5WQ0YgY29udHJvbCBwbGFuZSAocnVubmluZyBzb21ld2hlcmUpIGFu" + - "ZCB3YW50cyB0byByZWdpc3RlciBhIG5ldyBHUFUgY2x1c3RlciBhZ2FpbnN0IGl0LiBUaGlzIGlz" + - "IHRoZSBtdWx0aS1jbHVzdGVyIHNjYWxpbmcgcGF0aDogb25lIGNvbnRyb2wgcGxhbmUg4oaSIE4g" + - "Y29tcHV0ZSBwbGFuZXMuCgojIyBQcmVyZXF1aXNpdGVzIHRoZSB1c2VyIG11c3QgaGF2ZQoKLSAq" + - "Kmt1YmVjdGwgY29udGV4dCBmb3IgdGhlIG5ldyBjb21wdXRlIHBsYW5lKiogaW4gdGhlaXIgYEtV" + - "QkVDT05GSUdgLgotICoqUHVibGljIElDTVMgVVJMKiogb2YgdGhlIGV4aXN0aW5nIGNvbnRyb2wg" + - "cGxhbmUgKGUuZy4gYGh0dHBzOi8vaWNtcy5udmNmLmV4YW1wbGUuY29tYCkuCi0gKipBZG1pbiBK" + - "V1QqKiBmb3IgdGhlIGNvbnRyb2wgcGxhbmUncyBhY2NvdW50LCBPUiBhYmlsaXR5IHRvIG1pbnQg" + - "b25lIHZpYSBgbnZjZi1jbGkgaW5pdGAgYWdhaW5zdCB0aGUgY29udHJvbCBwbGFuZSdzIHB1Ymxp" + - "YyBhcGkgZW5kcG9pbnQuIChBZG1pbiB0b2tlbnMgY29tZSBmcm9tIHRoZSBBUEkgS2V5cyBzZXJ2" + - "aWNlIHZpYSB0aGUgcHVibGljIGFwaSBnYXRld2F5IOKAlCBrdWJlY3RsIGFjY2VzcyB0byB0aGUg" + - "Y29udHJvbCBwbGFuZSBpcyBOT1QgcmVxdWlyZWQgdG8gb2J0YWluIG9uZS4pCi0gQSB1bmlxdWUg" + - "YC0tY2x1c3Rlci1uYW1lYCB0aGF0IGRvZXNuJ3QgY29sbGlkZSB3aXRoIGFscmVhZHktcmVnaXN0" + - "ZXJlZCBjbHVzdGVycy4gVXNlIGBudmNmLWNsaSBjbHVzdGVyIGxpc3QtcmVnaXN0ZXJlZCAtLW5j" + - "YS1pZD0kTkNBX0lEIC0taWNtcy11cmw9JElDTVNgIHRvIGNoZWNrLgoKIyMgU3RlcHMKCjEuICoq" + - "QXNrIGZvciBpbnB1dHM6KioKICAgLSBgLS1jbHVzdGVyLW5hbWU9PHVuaXF1ZT5gIOKAlCBtdXN0" + - "IGJlIHVuaXF1ZSB3aXRoaW4gdGhlIGNvbnRyb2wgcGxhbmUncyBOQ0Egc2NvcGUKICAgLSBgLS1j" + - "b21wdXRlLXBsYW5lLWNvbnRleHQ9PGNvbnRleHQ+YCDigJQgdGhlIG5ldyBHUFUgY2x1c3Rlcidz" + - "IGt1YmVjdGwgY29udGV4dAogICAtIGAtLWljbXMtdXJsPTxodHRwczovL2ljbXMubnZjZi5leGFt" + - "cGxlLmNvbT5gIOKAlCBjb250cm9sIHBsYW5lJ3MgcHVibGljIElDTVMgVVJMCiAgIC0gR1BVIHR5" + - "cGU/IChkZWZhdWx0IGBIMTAwYCwgYnV0IGFzaykKCjIuICoqUHJlLWZsaWdodCBqdXN0IHRoZSBj" + - "b21wdXRlIHBsYW5lOioqCgogICBgYGBzaAogICBudmNmLWNsaSBzZWxmLWhvc3RlZCBjaGVjayAt" + - "LXByZSBcCiAgICAgLS1jb21wdXRlLXBsYW5lLWNvbnRleHQ9JENUWCBcCiAgICAgLS1pY21zLXVy" + - "bD0kSUNNUyBcCiAgICAgLS1qc29uIHwganEgLWMgLgogICBgYGAKCiAgIFRoaXMgdmFsaWRhdGVz" + - "IHRoZSBHUFUgb3BlcmF0b3IgaXMgaW5zdGFsbGVkLCBHUFUgbm9kZSBsYWJlbHMgYXJlIHByZXNl" + - "bnQsIGFuZCBJQ01TIGlzIHJlYWNoYWJsZSB2aWEgSFRUUC4gQWRkcmVzcyBhbnkgZmFpbHVyZXMg" + - "YmVmb3JlIHByb2NlZWRpbmcuCgozLiAqKlJ1biBgYWRkLWNvbXB1dGUtcGxhbmVgOioqCgogICBg" + - "YGBzaAogICBudmNmLWNsaSBzZWxmLWhvc3RlZCBhZGQtY29tcHV0ZS1wbGFuZSBcCiAgICAgLS1j" + - "bHVzdGVyLW5hbWU9JE5BTUUgXAogICAgIC0tY29tcHV0ZS1wbGFuZS1jb250ZXh0PSRDVFggXAog" + - "ICAgIC0taWNtcy11cmw9JElDTVMgXAogICAgIC0tdG9rZW49JEpXVCBcCiAgICAgLS1ub24taW50" + - "ZXJhY3RpdmUgXAogICAgIC0tanNvbgogICBgYGAKCiAgIFRoaXMgaXMgYSBzZXBhcmF0ZSBzdWJj" + - "b21tYW5kIGZyb20gYHVwYC4gSXQgcnVucyBvbmx5IHRoZSBjb21wdXRlLXBsYW5lLXJlbGV2YW50" + - "IHBoYXNlczogMSAoY29tcHV0ZS1wbGFuZSBwcmVmbGlnaHQpLCA1IChyZWdpc3RlciksIDYgKGNv" + - "bXB1dGUtcGxhbmUgYXBwbHkpLCA4IChmaW5hbCBoZWFsdGggb24gdGhlIG5ldyBjb21wdXRlIHBs" + - "YW5lKS4gSXQgZG9lcyBOT1QgdG91Y2ggdGhlIGNvbnRyb2wgcGxhbmUgYW5kIGRvZXMgTk9UIGFj" + - "Y2VwdCBgLS1jb250cm9sLXBsYW5lLWNvbnRleHRgIOKAlCB0aGUgY29udHJvbCBwbGFuZSBpcyBy" + - "ZWFjaGVkIG92ZXIgSFRUUFMgYXQgYC0taWNtcy11cmxgLiBgLS10b2tlbmAgaXMgcmVxdWlyZWQg" + - "YmVjYXVzZSB0aGVyZSdzIG5vIGt1YmVjdGwgcGF0aCB0byBtaW50IG9uZSBmcm9tLgoKNC4gKipW" + - "ZXJpZnkuKiogYG52Y2YtY2xpIHNlbGYtaG9zdGVkIHN0YXR1cyAtLWNsdXN0ZXItbmFtZT0kTkFN" + - "RSAtLWpzb24gfCBqcWAg4oCUIGV4cGVjdCBgdmVyZGljdDogImhlYWx0aHkiYC4gVGhlIFJlZ2lz" + - "dGVyZWQgQ29tcHV0ZSBQbGFuZXMgcGFuZWwgZnJvbSBJQ01TIHNob3VsZCBub3cgbGlzdCB0aGUg" + - "bmV3IGNsdXN0ZXIuCgo1LiAqKlNtb2tlLioqIFtkZXBsb3ktYW5kLWludm9rZS5tZF0oZGVwbG95" + - "LWFuZC1pbnZva2UubWQpIOKAlCBkZXBsb3kgYSBzbWFsbCBmdW5jdGlvbiB0byB0aGUgbmV3IGNv" + - "bXB1dGUgcGxhbmUgdG8gY29uZmlybSBzY2hlZHVsaW5nIHdvcmtzLgoKIyMgV2hhdCBpZiBhIGNs" + - "dXN0ZXIgd2l0aCB0aGUgc2FtZSBuYW1lIGFscmVhZHkgZXhpc3RzPwoKYGFkZC1jb21wdXRlLXBs" + - "YW5lYCdzIHJlZ2lzdGVyIHBoYXNlIHVzZXMgYC0taWdub3JlLWV4aXN0aW5nYCBzZW1hbnRpY3Mg" + - "4oCUIGl0J2xsIG1hdGNoIHRoZSBleGlzdGluZyBJQ01TIHJvdyBhbmQgcmV1c2UgaXRzIGNsdXN0" + - "ZXJJZC4gVHdvIHNjZW5hcmlvczoKCi0gKipSZS1yZWdpc3RlcmluZyB0aGUgc2FtZSBjbHVzdGVy" + - "KiogKHJlLXJ1bm5pbmcgb24gdGhlIHNhbWUgY29tcHV0ZSBwbGFuZSB0aGF0IHdhcyBwcmV2aW91" + - "c2x5IHJlZ2lzdGVyZWQpOiBleHBlY3RlZCwgbm8tb3Agc2VtYW50aWNzLgotICoqRGlmZmVyZW50" + - "IHBoeXNpY2FsIGNsdXN0ZXIgYnV0IHNhbWUgbmFtZSoqOiB0aGUgc2Vjb25kIGF0dGVtcHQgd2ls" + - "bCByZXVzZSB0aGUgSUNNUyByb3csIGJ1dCB0aGUgbmV3IGNvbXB1dGUgcGxhbmUncyBKV0tTIHdp" + - "bGwgYmUgc2lsZW50bHkgKnJlcGxhY2VkKiDigJQgdGhlIG9sZCBjb21wdXRlIHBsYW5lJ3MgTlZD" + - "QSBhZ2VudCB3aWxsIHN0YXJ0IGZhaWxpbmcgUFNBVCBhdXRoLiAqKkNvbmZpcm0gd2l0aCB0aGUg" + - "dXNlcioqIHRoYXQgdGhleSBtZWFudCB0byBvdmVyd3JpdGUuCgpXaGVuIGluIGRvdWJ0LCBydW4g" + - "YG52Y2YtY2xpIGNsdXN0ZXIgbGlzdC1yZWdpc3RlcmVkIC0tbmNhLWlkPSROQ0FfSUQgLS1pY21z" + - "LXVybD0kSUNNU2AgZmlyc3QgYW5kIGFzayB0aGUgdXNlciBiZWZvcmUgcHJvY2VlZGluZyB3aXRo" + - "IGEgbmFtZSB0aGF0IGFscmVhZHkgZXhpc3RzLgpQSwcIQidJDwUNAAAFDQAAUEsDBBQACAAAAAAA" + - "IQAAAAAAAAAAAAAAAAA5AAkAZGF0YS9udmNmLXNlbGYtbWFuYWdlZC1jbGkvcHJvbXB0cy9jcmVh" + - "dGUtYW5kLXJ1bi10YXNrLm1kVVQFAAEAps4SIyBDcmVhdGUgYW5kIHJ1biBhIHRhc2sKClN0YW5k" + - "YXJkIGZsb3c6IG1pbnQgQVBJIGtleXMgLT4gdGFzayBDUkVBVEUgLT4gbW9uaXRvciAtPiByZXRy" + - "aWV2ZSByZXN1bHRzIC0+IGNsZWFudXAuCgojIyAxLiBNaW50IEFQSSBrZXlzCgpUaGUgYWRtaW4g" + - "dG9rZW4gZnJvbSBgaW5pdGAgZG9lcyBub3QgY2FycnkgdGFzayBpbnZvY2F0aW9uIHNjb3BlLiBH" + - "ZW5lcmF0ZSBBUEkga2V5czoKCmBgYHNoCm52Y2YtY2xpIGFwaS1rZXkgZ2VuZXJhdGUgLS1kZXNj" + - "cmlwdGlvbj0idGFzayBzbW9rZSB0ZXN0IiAtLWV4cGlyZXMtaW49OGgKIyAtPiBmdW5jdGlvbiBr" + - "ZXk6IG52YXBpLS4uLgojIC0+IHRhc2sga2V5OiBudmFwaS0uLi4KYGBgCgpCb3RoIGtleXMgYXJl" + - "IHNhdmVkIHRvIENMSSBzdGF0ZS4gVG8gbWludCBvbmx5IHRoZSB0YXNrIGtleToKCmBgYHNoCm52" + - "Y2YtY2xpIGFwaS1rZXkgZ2VuZXJhdGUgLS1mb3IgdGFzayAtLWRlc2NyaXB0aW9uPSJ0YXNrIHNt" + - "b2tlIHRlc3QiIC0tZXhwaXJlcy1pbj04aApgYGAKClRvIHZhbGlkYXRlIGVhY2gga2V5IGltbWVk" + - "aWF0ZWx5IGFmdGVyIG1pbnRpbmc6CgpgYGBzaApudmNmLWNsaSBhcGkta2V5IGdlbmVyYXRlIC0t" + - "ZGVzY3JpcHRpb249InRhc2sgc21va2UgdGVzdCIgLS1leHBpcmVzLWluPThoIC0tdmFsaWRhdGUK" + - "IyAtPiBmdW5jdGlvbiBBUEkga2V5IHZhbGlkYXRpb24gcGFzc2VkIQojIC0+IHRhc2sgQVBJIGtl" + - "eSB2YWxpZGF0aW9uIHBhc3NlZCEKYGBgCgpOZXZlciBlY2hvIHRoZSBrZXkgdmFsdWUgKG52YXBp" + - "LS4uLikgaW50byBjaGF0IG9yIGxvZ3MuIFJlcG9ydCBvbmx5IHRoYXQgdGhlIGtleSB3YXMgbWlu" + - "dGVkLCBpdHMgZGVzY3JpcHRpb24sIGFuZCBpdHMgZXhwaXJ5IOKAlCBuZXZlciB0aGUgdmFsdWUg" + - "aXRzZWxmLgoKIyMjIEZyZXNoIHNlc3Npb24gcHJlLWZsaWdodAoKQmVmb3JlIHJ1bm5pbmcgYW55" + - "IGB0YXNrYCBjb21tYW5kIGluIGEgZnJlc2ggYWdlbnQgc2Vzc2lvbiwgdmVyaWZ5IGNyZWRlbnRp" + - "YWxzOgoKMS4gQ2hlY2sgd2hldGhlciBgTlZDRl9BUElfS0VZYCBpcyBzZXQ6CiAgIGBgYHNoCiAg" + - "IHByaW50ZW52IE5WQ0ZfQVBJX0tFWSA+IC9kZXYvbnVsbCAyPiYxICYmIGVjaG8gc2V0IHx8IGVj" + - "aG8gdW5zZXQKICAgYGBgCjIuIElmIHVuc2V0LCByZWFkIHRoZSBzdG9yZWQga2V5IGV4cGlyeSB3" + - "aXRob3V0IHByaW50aW5nIHRoZSB2YWx1ZToKICAgYGBgc2gKICAgcHl0aG9uMyAtYyAiaW1wb3J0" + - "IGpzb24scGF0aGxpYjsgcz1qc29uLmxvYWRzKHBhdGhsaWIuUGF0aCgnfi8ubnZjZi1jbGkuc3Rh" + - "dGUnKS5leHBhbmR1c2VyKCkucmVhZF90ZXh0KCkpOyBwcmludChzLmdldCgnbnZjdEFwaUtleUV4" + - "cGlyYXRpb24nLCdtaXNzaW5nJykpIgogICBgYGAKMy4gSWYgdGhlIGV4cGlyeSBpcyBpbiB0aGUg" + - "cGFzdCBvciBtaXNzaW5nLCByZWdlbmVyYXRlOgogICBgYGBzaAogICBudmNmLWNsaSByZWZyZXNo" + - "CiAgIG52Y2YtY2xpIGFwaS1rZXkgZ2VuZXJhdGUgLS1mb3IgdGFzayAtLWRlc2NyaXB0aW9uPSJ0" + - "YXNrLWtleSIgLS1leHBpcmVzLWluPTI0aCAtLXZhbGlkYXRlCiAgIGBgYAo0LiBJbmplY3QgdGhl" + - "IHN0b3JlZCBrZXkgaW50byB0aGUgc3VicHJvY2VzcyBlbnYgd2l0aG91dCBsb2dnaW5nIHRoZSB2" + - "YWx1ZS4gVXNlIGEgc2hlbGwgc3Vic2hlbGwgb3IgZW52IHByZWZpeCDigJQgbmV2ZXIgcHJpbnQg" + - "dGhlIGtleSBpbnRvIGNoYXQuCgpFcnJvciBjbGFzc2lmaWNhdGlvbjoKLSAibWlzc2luZyBhdXRo" + - "ZW50aWNhdGlvbiBjcmVkZW50aWFscyIgb3IgZXhpdCAyOiBrZXkgbm90IGluamVjdGVkOyByZXBl" + - "YXQgcHJlLWZsaWdodC4KLSBIVFRQIDQwMzoga2V5IGV4cGlyZWQgb3Igd3JvbmcgYXVkaWVuY2U7" + - "IHJlZ2VuZXJhdGUuCi0gRW1wdHkgdGFzayBsaXN0OiB2YWxpZDsgbm8gdGFza3MgZXhpc3QgeWV0" + - "LgoKIyMgMi4gVGFzayBDUkVBVEU6IGNvbnRhaW5lcgoKRGVmaW5lIHRoZSB0YXNrIGluIGEgSlNP" + - "TiBmaWxlLiBNaW5pbXVtIHZpYWJsZSBjb250YWluZXIgdGFzazoKCmBgYGpzb24KewogICJuYW1l" + - "IjogInNtb2tlLWNvbnRhaW5lci10YXNrIiwKICAiZ3B1U3BlY2lmaWNhdGlvbiI6IHsKICAgICJn" + - "cHUiOiAiSDEwMCIsCiAgICAiaW5zdGFuY2VUeXBlIjogIk5DUC5HUFUuSDEwMF8xeCIsCiAgICAi" + - "YmFja2VuZCI6ICJuY3AtbG9jYWwiCiAgfSwKICAiY29udGFpbmVySW1hZ2UiOiAibnZjci5pby9u" + - "dmlkaWEvbmVtbzpsYXRlc3QiLAogICJjb250YWluZXJBcmdzIjogInB5dGhvbiAtYyBcInByaW50" + - "KCdoZWxsbyBmcm9tIHRhc2snKVwiIiwKICAicmVzdWx0SGFuZGxpbmdTdHJhdGVneSI6ICJOT05F" + - "Igp9CmBgYAoKYGBgc2gKbnZjZi1jbGkgdGFzayBjcmVhdGUgLS1pbnB1dC1maWxlPXRhc2stY29u" + - "dGFpbmVyLmpzb24KIyAtPiBUYXNrIElEOiA8dGFza19pZD4KYGBgCgpDYXB0dXJlIHRoZSB0YXNr" + - "IElEIGZyb20gdGhlIG91dHB1dC4gU3RhdGUgcGVyc2lzdGVuY2UgY2FuIGZhaWwgc2lsZW50bHks" + - "IHNvIHBhc3MKYDx0YXNrSWQ+YCBleHBsaWNpdGx5IG9uIHN1YnNlcXVlbnQgY29tbWFuZHMgcmF0" + - "aGVyIHRoYW4gcmVseWluZyBvbiBzYXZlZCBzdGF0ZS4KCkVxdWl2YWxlbnQgQ0xJIGZsYWdzIGZv" + - "cm0gZm9yIHF1aWNrIGFkLWhvYyBydW5zOgoKYGBgc2gKbnZjZi1jbGkgdGFzayBjcmVhdGUgXAog" + - "IC0tbmFtZT1zbW9rZS1jb250YWluZXItdGFzayBcCiAgLS1ncHU9SDEwMCBcCiAgLS1pbnN0YW5j" + - "ZS10eXBlPU5DUC5HUFUuSDEwMF8xeCBcCiAgLS1iYWNrZW5kPW5jcC1sb2NhbCBcCiAgLS1pbWFn" + - "ZT1udmNyLmlvL252aWRpYS9uZW1vOmxhdGVzdCBcCiAgLS1jb250YWluZXItYXJncz0icHl0aG9u" + - "IC1jICdwcmludChoZWxsbyBmcm9tIHRhc2spJyIgXAogIC0tcmVzdWx0LXN0cmF0ZWd5PU5PTkUK" + - "YGBgCgpUbyB0YXJnZXQgYSBzcGVjaWZpYyBjb21wdXRlIGNsdXN0ZXI6CgpgYGBzaApudmNmLWNs" + - "aSB0YXNrIGNyZWF0ZSAtLWlucHV0LWZpbGU9dGFzay1jb250YWluZXIuanNvbiAtLWNsdXN0ZXJz" + - "PW15LWNsdXN0ZXIKYGBgCgpUbyBpbmplY3QgZW52aXJvbm1lbnQgdmFyaWFibGVzIG9yIHNlY3Jl" + - "dHMsIGVpdGhlciBwYXNzIGZsYWdzOgoKYGBgc2gKbnZjZi1jbGkgdGFzayBjcmVhdGUgLS1pbnB1" + - "dC1maWxlPXRhc2stY29udGFpbmVyLmpzb24gXAogIC0tY29udGFpbmVyLWVudiBNWV9FTlY9dmFs" + - "dWUgXAogIC0tc2VjcmV0cyBNWV9TRUNSRVQ9czNjcjN0CmBgYAoKb3Igc2V0IHRoZW0gaW4gdGhl" + - "IEpTT04gZmlsZToKCmBgYGpzb24KewogICJjb250YWluZXJFbnZpcm9ubWVudCI6IFsKICAgIHsi" + - "a2V5IjogIk1ZX0VOViIsICJ2YWx1ZSI6ICJ2YWx1ZSJ9CiAgXSwKICAic2VjcmV0cyI6IFsKICAg" + - "IHsibmFtZSI6ICJNWV9TRUNSRVQiLCAidmFsdWUiOiAiczNjcjN0In0KICBdCn0KYGBgCgpTZWNy" + - "ZXRzIGFyZSBlbmNyeXB0ZWQgYXQgcmVzdCBhbmQgbm90IHJldHVybmVkIGluIGxpc3Qgb3IgZ2V0" + - "IHJlc3BvbnNlcyBieSBkZWZhdWx0OyBkbyBub3QgbG9nIHRoZW0uCgojIyAzLiBUYXNrIENSRUFU" + - "RTogSGVsbQoKRm9yIEhlbG0tdHlwZSB0YXNrcywgcmVwbGFjZSBgY29udGFpbmVySW1hZ2VgIHdp" + - "dGggYGhlbG1DaGFydGA6CgpgYGBqc29uCnsKICAibmFtZSI6ICJzbW9rZS1oZWxtLXRhc2siLAog" + - "ICJncHVTcGVjaWZpY2F0aW9uIjogewogICAgImdwdSI6ICJIMTAwIiwKICAgICJpbnN0YW5jZVR5" + - "cGUiOiAiTkNQLkdQVS5IMTAwXzF4IiwKICAgICJiYWNrZW5kIjogIm5jcC1sb2NhbCIKICB9LAog" + - "ICJoZWxtQ2hhcnQiOiAiaHR0cHM6Ly9oZWxtLm5nYy5udmlkaWEuY29tLzxvcmc+L2NoYXJ0cy9l" + - "eGFtcGxlLXRhc2stMC4xLjAudGd6IiwKICAicmVzdWx0SGFuZGxpbmdTdHJhdGVneSI6ICJOT05F" + - "Igp9CmBgYAoKYGBgc2gKbnZjZi1jbGkgdGFzayBjcmVhdGUgLS1pbnB1dC1maWxlPXRhc2staGVs" + - "bS5qc29uCiMgLT4gVGFzayBJRDogPHRhc2tfaWQ+CmBgYAoKYGhlbG1DaGFydGAgYW5kIGBjb250" + - "YWluZXJJbWFnZWAgYXJlIG11dHVhbGx5IGV4Y2x1c2l2ZS4KCiMjIDNiLiBVcGRhdGUgc2VjcmV0" + - "cyBvbiBhIHJ1bm5pbmcgdGFzawoKYGBgc2gKbnZjZi1jbGkgdGFzayB1cGRhdGUtc2VjcmV0cyA8" + - "dGFza19pZD4gLS1zZWNyZXRzIE5BTUU9dmFsdWUKYGBgCgpgdXBkYXRlLXNlY3JldHNgIG1lcmdl" + - "cyBzZWNyZXRzIGJ5IG5hbWU6IHN1cHBsaWVkIHNlY3JldHMgYXJlIGFkZGVkIG9yIHVwZGF0ZWQs" + - "IGFuZCBleGlzdGluZyBzZWNyZXRzIG5vdCBpbmNsdWRlZCBpbiB0aGUgcmVxdWVzdCBhcmUgcHJl" + - "c2VydmVkLiBEbyBub3QgZWNobyBzZWNyZXQgdmFsdWVzIGludG8gY2hhdC4KCiMjIDQuIE1vbml0" + - "b3IKClBvbGwgdGFzayBzdGF0ZToKCmBgYHNoCm52Y2YtY2xpIHRhc2sgZ2V0IDx0YXNrX2lkPgoj" + - "IC0+IGlkOiA8dGFza19pZD4KIyAtPiBzdGF0dXM6IFJVTk5JTkcKYGBgCgpPciB1c2UgdGhlIHRh" + - "c2sgSUQgc2F2ZWQgdG8gc3RhdGUgZnJvbSB0aGUgbW9zdCByZWNlbnQgYHRhc2sgY3JlYXRlYDoK" + - "CmBgYHNoCm52Y2YtY2xpIHRhc2sgZ2V0CmBgYAoKU3RhdHVzIHZhbHVlczogYFFVRVVFRGAsIGBM" + - "QVVOQ0hFRGAsIGBSVU5OSU5HYCwgYENPTVBMRVRFRGAsIGBFUlJPUkVEYCwgYENBTkNFTEVEYCwg" + - "YEVYQ0VFREVEX01BWF9SVU5USU1FX0RVUkFUSU9OYCwgYEVYQ0VFREVEX01BWF9RVUVVRURfRFVS" + - "QVRJT05gLgoKSW5zcGVjdCBsaWZlY3ljbGUgZXZlbnRzIGZvciBwcm9ncmVzcyBkZXRhaWxzIG9y" + - "IGZhaWx1cmUgY29udGV4dDoKCmBgYHNoCm52Y2YtY2xpIHRhc2sgZXZlbnRzIDx0YXNrX2lkPgpg" + - "YGAKCklmIHRoZSB0YXNrIGlzIHN0dWNrIGluIGBRVUVVRURgLCBjaGVjayB0aGF0IGEgY29tcHV0" + - "ZSBjbHVzdGVyIHdpdGggYSBtYXRjaGluZyBHUFUgU0tVCmlzIHJlZ2lzdGVyZWQgYW5kIGhlYWx0" + - "aHkgKGBudmNmLWNsaSBzZWxmLWhvc3RlZCBzdGF0dXNgKS4KCklmIHRoZSB0YXNrIG1vdmVzIHRv" + - "IGBFUlJPUkVEYCwgdGhlIGV2ZW50cyBvdXRwdXQgY29udGFpbnMgdGhlIGZhaWx1cmUgcmVhc29u" + - "LiBGb3IKY29udGFpbmVyLWxldmVsIGRldGFpbCwgaW5zcGVjdCB0aGUgdGFzayB3b3JrbG9hZCBw" + - "b2RzIG9uIHRoZSBjb21wdXRlLXBsYW5lIGNsdXN0ZXIgd2l0aApga3ViZWN0bGAgYWdhaW5zdCB0" + - "aGUgYXBwcm9wcmlhdGUgbmFtZXNwYWNlIGFuZCBjb250ZXh0LgoKIyMgNS4gUmV0cmlldmUgcmVz" + - "dWx0cwoKRm9yIHRhc2tzIHdpdGggYHJlc3VsdEhhbmRsaW5nU3RyYXRlZ3k6IE5PTkVgLCByZXN1" + - "bHRzIGFyZSBlbXB0eSBieSBkZXNpZ246CgpgYGBzaApudmNmLWNsaSB0YXNrIHJlc3VsdHMgPHRh" + - "c2tfaWQ+CiMgLT4geyJyZXN1bHRzIjogW119CmBgYAoKRm9yIHRhc2tzIHdpdGggYHJlc3VsdEhh" + - "bmRsaW5nU3RyYXRlZ3k6IFVQTE9BRGAsIHJlc3VsdHMgdXBsb2FkIHRvIHRoZSBsb2NhdGlvbiBu" + - "YW1lZCBpbgpgcmVzdWx0c0xvY2F0aW9uYC4gVGhlIHRhc2sncyBjcmVkZW50aWFscyBtdXN0IGhh" + - "dmUgd3JpdGUgYWNjZXNzIHRvIHRoYXQgbG9jYXRpb24uIEFmdGVyCnRoZSB0YXNrIHJlYWNoZXMg" + - "YENPTVBMRVRFRGA6CgpgYGBzaApudmNmLWNsaSB0YXNrIHJlc3VsdHMgPHRhc2tfaWQ+CmBgYAoK" + - "Tm90ZTogcmVzdWx0IHVwbG9hZCBpcyBub3QgeWV0IHN1cHBvcnRlZCBpbiB0aGlzIHJlbGVhc2Uu" + - "CgojIyA2LiBDbGVhbnVwCgpDYW5jZWwgYSBydW5uaW5nIHRhc2s6CgpgYGBzaApudmNmLWNsaSB0" + - "YXNrIGNhbmNlbCA8dGFza19pZD4KYGBgCgpEZWxldGUgdGhlIHRhc2sgcmVjb3JkIGFmdGVyIGl0" + - "IHJlYWNoZXMgYSB0ZXJtaW5hbCBzdGF0ZToKCmBgYHNoCm52Y2YtY2xpIHRhc2sgZGVsZXRlIDx0" + - "YXNrX2lkPgpgYGAKClN0b3AgYW5kIGNvbmZpcm0gd2l0aCB0aGUgdXNlciBiZWZvcmUgcnVubmlu" + - "ZyB0YXNrIGRlbGV0ZS4gU3RhdGUgdGhlIHRhc2sgSUQgYW5kIGl0cyBjdXJyZW50IHN0YXR1cy4g" + - "RG8gbm90IHByb2NlZWQgZXZlbiB3aGVuIHRoZSB1c2VyJ3MgcHJvbXB0IGFscmVhZHkgcmVxdWVz" + - "dGVkIGRlbGV0aW9uIOKAlCBhbHdheXMgZ2V0IGFuIGV4cGxpY2l0IGxhc3QtbG9vayBjb25maXJt" + - "YXRpb24uIERlbGV0aW9uIGlzIHBlcm1hbmVudCBhbmQgY2Fubm90IGJlIHVuZG9uZS4KClRvIGxp" + - "c3QgYWxsIHRhc2tzIGZvciB0aGUgY3VycmVudCBvd25lciAodXNlZnVsIGJlZm9yZSBidWxrIGNs" + - "ZWFudXApOgoKYGBgc2gKbnZjZi1jbGkgdGFzayBsaXN0Cm52Y2YtY2xpIHRhc2sgbGlzdCAtLXN0" + - "YXR1cz1DT01QTEVURUQKYGBgCgojIyBOb3RlcwoKLSBHUFUgZmFtaWx5IGFuZCBpbnN0YW5jZSB0" + - "eXBlIG11c3QgbWF0Y2ggYSByZWdpc3RlcmVkIGNvbXB1dGUgY2x1c3RlciBTS1UuIE1pc21hdGNo" + - "CiAgY2F1c2VzIHRoZSB0YXNrIHRvIHJlbWFpbiBgUVVFVUVEYCBpbmRlZmluaXRlbHkuIFVzZSBg" + - "bnZjZi1jbGkgY2x1c3RlciBsaXN0YCB0byBjaGVjawogIHJlZ2lzdGVyZWQgU0tVcy4KLSBGb3Ig" + - "Y29udGFpbmVyIGltYWdlcyBvbiBwcml2YXRlIHJlZ2lzdHJpZXMsIGVuc3VyZSB0aGUgY29tcHV0" + - "ZS1wbGFuZSBjbHVzdGVyJ3MKICBpbWFnZS1jcmVkZW50aWFsLWhlbHBlciBpcyBjb25maWd1cmVk" + - "IGZvciB0aGUgcmVnaXN0cnkgaG9zdC4KUEsHCJHcG/mYGQAAmBkAAFBLAwQUAAgAAAAAACEAAAAA" + - "AAAAAAAAAAAANwAJAGRhdGEvbnZjZi1zZWxmLW1hbmFnZWQtY2xpL3Byb21wdHMvZGVwbG95LWFu" + - "ZC1pbnZva2UubWRVVAUAAQCmzhIjIERlcGxveSBhbmQgaW52b2tlIGEgY29udGFpbmVyIGZ1bmN0" + - "aW9uCgpTdGFuZGFyZCBmb3VyLXN0ZXAgZmxvdzogZnVuY3Rpb24gQ1JFQVRFIOKGkiBERVBMT1kg" + - "4oaSIEFQSSBrZXkgKGZvciBpbnZva2Ugc2NvcGUpIOKGkiBJTlZPS0UuCgojIyAxLiBGdW5jdGlv" + - "biBDUkVBVEUKCkRlZmluZSB0aGUgZnVuY3Rpb24gaW4gYSBKU09OIGZpbGUuIE1pbmltdW06CgpC" + - "ZWZvcmUgY3JlYXRpbmcgdGhlIGZ1bmN0aW9uLCBjb25maXJtIHRoZSBleGFjdCBmdW5jdGlvbiBu" + - "YW1lIGFuZCBjb250YWluZXIgaW1hZ2Ugd2l0aCB0aGUgdXNlci4gSWYgZWl0aGVyIHZhbHVlIGlz" + - "IG1pc3NpbmcsIHN0b3AgYW5kIGFzay4gRG8gbm90IGludmVudCBvciBzdWJtaXQgZXhhbXBsZSB2" + - "YWx1ZXMgZm9yIHRoZXNlIGZpZWxkcy4KCmBgYGpzb24KewogICJuYW1lIjogIjxjb25maXJtZWQt" + - "ZnVuY3Rpb24tbmFtZT4iLAogICJjb250YWluZXJJbWFnZSI6ICI8Y29uZmlybWVkLWNvbnRhaW5l" + - "ci1pbWFnZT4iLAogICJpbmZlcmVuY2VVcmwiOiAiL2VjaG8iLAogICJpbmZlcmVuY2VQb3J0Ijog" + - "ODAwMCwKICAiZGVzY3JpcHRpb24iOiAic21va2UgdGVzdCBlY2hvIGZ1bmN0aW9uIiwKICAiZnVu" + - "Y3Rpb25UeXBlIjogIkRFRkFVTFQiLAogICJhcGlCb2R5Rm9ybWF0IjogIkNVU1RPTSIsCiAgImhl" + - "YWx0aCI6IHsKICAgICJwcm90b2NvbCI6ICJIVFRQIiwKICAgICJ1cmkiOiAiL2hlYWx0aCIsCiAg" + - "ICAicG9ydCI6IDgwMDAsCiAgICAidGltZW91dCI6ICJQVDMwUyIsCiAgICAiZXhwZWN0ZWRTdGF0" + - "dXNDb2RlIjogMjAwCiAgfQp9CmBgYAoKYGBgc2gKbnZjZi1jbGkgZnVuY3Rpb24gY3JlYXRlIC0t" + - "aW5wdXQtZmlsZT1jcmVhdGUtZm4uanNvbgojIOKGkiBGdW5jdGlvbiBJRDogPGZuX2lkPgojIOKG" + - "kiBWZXJzaW9uIElEOiA8dmVyX2lkPgpgYGAKClNhdmUgdGhlIElEcyDigJQgdGhleSdyZSBuZWVk" + - "ZWQgZm9yIGRlcGxveSArIGludm9rZS4KCiMjIyBMTE0gZnVuY3Rpb24gdmFyaWFudAoKRm9yIGFu" + - "IE9wZW5BSS1jb21wYXRpYmxlIExMTSBmdW5jdGlvbiwgc2V0IGBmdW5jdGlvblR5cGU6ICJMTE0i" + - "YCBhbmQgcHV0IHJvdXRlIG1ldGFkYXRhIHVuZGVyIGBtb2RlbHNbXS5sbG1Db25maWdgOgoKQmVm" + - "b3JlIGNyZWF0aW5nIHRoZSBmdW5jdGlvbiwgY29uZmlybSB0aGUgZXhhY3QgZnVuY3Rpb24gbmFt" + - "ZSwgY29udGFpbmVyIGltYWdlLCBhbmQgbW9kZWwgbmFtZSB3aXRoIHRoZSB1c2VyLiBJZiBhbnkg" + - "dmFsdWUgaXMgbWlzc2luZywgc3RvcCBhbmQgYXNrLiBEbyBub3QgaW52ZW50IG9yIHN1Ym1pdCBl" + - "eGFtcGxlIHZhbHVlcyBmb3IgdGhlc2UgZmllbGRzLgoKYGBganNvbgp7CiAgIm5hbWUiOiAiPGNv" + - "bmZpcm1lZC1mdW5jdGlvbi1uYW1lPiIsCiAgImNvbnRhaW5lckltYWdlIjogIjxjb25maXJtZWQt" + - "Y29udGFpbmVyLWltYWdlPiIsCiAgImluZmVyZW5jZVVybCI6ICIvIiwKICAiaW5mZXJlbmNlUG9y" + - "dCI6IDgwMDAsCiAgImZ1bmN0aW9uVHlwZSI6ICJMTE0iLAogICJoZWFsdGgiOiB7CiAgICAicHJv" + - "dG9jb2wiOiAiSFRUUCIsCiAgICAidXJpIjogIi9oZWFsdGgiLAogICAgInBvcnQiOiA4MDAwLAog" + - "ICAgInRpbWVvdXQiOiAiUFQzMFMiLAogICAgImV4cGVjdGVkU3RhdHVzQ29kZSI6IDIwMAogIH0s" + - "CiAgIm1vZGVscyI6IFsKICAgIHsKICAgICAgIm5hbWUiOiAiPGNvbmZpcm1lZC1tb2RlbC1uYW1l" + - "PiIsCiAgICAgICJsbG1Db25maWciOiB7CiAgICAgICAgInVyaXMiOiBbIi92MS9jaGF0L2NvbXBs" + - "ZXRpb25zIiwgIi92MS9yZXNwb25zZXMiLCAiL3YxL2VtYmVkZGluZ3MiXSwKICAgICAgICAicm91" + - "dGluZ01ldGhvZCI6ICJyb3VuZF9yb2JpbiIsCiAgICAgICAgInRva2VuUmF0ZUxpbWl0IjogIjEw" + - "MDAtUyIKICAgICAgfQogICAgfQogIF0KfQpgYGAKCkVxdWl2YWxlbnQgQ0xJIGZsYWcgZm9ybToK" + - "CmBgYHNoCm52Y2YtY2xpIGZ1bmN0aW9uIGNyZWF0ZSBcCiAgLS1uYW1lPSI8Y29uZmlybWVkLWZ1" + - "bmN0aW9uLW5hbWU+IiBcCiAgLS1pbWFnZT0iPGNvbmZpcm1lZC1jb250YWluZXItaW1hZ2U+IiBc" + - "CiAgLS1pbmZlcmVuY2UtdXJsPS8gXAogIC0taW5mZXJlbmNlLXBvcnQ9ODAwMCBcCiAgLS1oZWFs" + - "dGgtdXJpPS9oZWFsdGggXAogIC0taGVhbHRoLXBvcnQ9ODAwMCBcCiAgLS1oZWFsdGgtdGltZW91" + - "dD1QVDMwUyBcCiAgLS1oZWFsdGgtcHJvdG9jb2w9SFRUUCBcCiAgLS1oZWFsdGgtZXhwZWN0ZWQt" + - "c3RhdHVzPTIwMCBcCiAgLS1mdW5jdGlvbi10eXBlPUxMTSBcCiAgLS1sbG0tbW9kZWw9J25hbWU9" + - "PGNvbmZpcm1lZC1tb2RlbC1uYW1lPix1cmlzPS92MS9jaGF0L2NvbXBsZXRpb25zfC92MS9yZXNw" + - "b25zZXN8L3YxL2VtYmVkZGluZ3Mscm91dGluZ01ldGhvZD1yb3VuZF9yb2Jpbix0b2tlblJhdGVM" + - "aW1pdD0xMDAwLVMnCmBgYAoKVXNlIHRoZSBzYW1lIGRlcGxveSBzdGVwIGJlbG93IGFmdGVyIHRo" + - "ZSBMTE0gZnVuY3Rpb24gaXMgY3JlYXRlZC4KCk9wdGlvbmFsIG1vZGVsIHJvdXRpbmcgdXBkYXRl" + - "IGFmdGVyIGNyZWF0aW9uOgoKYGBgc2gKbnZjZi1jbGkgZnVuY3Rpb24gdXBkYXRlIFwKICAtLWZ1" + - "bmN0aW9uLWlkPTxmbl9pZD4gXAogIC0tdmVyc2lvbi1pZD08dmVyX2lkPiBcCiAgLS1sbG0tbW9k" + - "ZWwtdXBkYXRlPSduYW1lPTxjb25maXJtZWQtbW9kZWwtbmFtZT4scm91dGluZ01ldGhvZD1wb3dl" + - "cl9vZl90d28sdG9rZW5SYXRlTGltaXQ9MTAwMC1TJwpgYGAKClVzZSB0aGlzIGZvciBtdXRhYmxl" + - "IGByb3V0aW5nTWV0aG9kYCBhbmQgYHRva2VuUmF0ZUxpbWl0YDsgY3JlYXRlLXRpbWUgYHVyaXNg" + - "IHN0YXkgaW4gYG1vZGVsc1tdLmxsbUNvbmZpZ2AuCmB0b2tlblJhdGVMaW1pdGAgc3VwcG9ydHMg" + - "cG9zaXRpdmUgaW50ZWdlciB0b2tlbiBsaW1pdHMgZm9yIGBTYCwgYE1gLCBgSGAsIGBEYCwgYW5k" + - "IGBXYC4gVXNlIGAxMDAwLVNgIGZvciBhIHNpbmdsZSBpbmxpbmUgQ0xJIGxpbWl0LiBVc2UgaW5w" + - "dXQgSlNPTiBmb3IgY29tYmluZWQgbGltaXRzLCBzdWNoIGFzIGAxMDAwLVMsNTAwMC1NLDEwMDAw" + - "MC1ILDUwMDAwMC1ELDEwMDAwMDAtV2AsIGJlY2F1c2UgaW5saW5lIG1vZGVsIHNwZWNzIHVzZSBj" + - "b21tYXMgYXMgZmllbGQgc2VwYXJhdG9ycy4KCiMjIyBIZWxtIGNoYXJ0IExMTSBmdW5jdGlvbiB2" + - "YXJpYW50CgpMTE0gZnVuY3Rpb24gdHlwZSBpcyBpbmRlcGVuZGVudCBvZiB3b3JrbG9hZCBwYWNr" + - "YWdpbmcuIEZvciBhIEhlbG0tY2hhcnQgYmFja2VkIExMTSBmdW5jdGlvbiwga2VlcCB0aGUgc2Ft" + - "ZSBgZnVuY3Rpb25UeXBlYCBhbmQgYG1vZGVsc1tdLmxsbUNvbmZpZ2AgZmllbGRzLCB0aGVuIHNl" + - "dCBgaGVsbUNoYXJ0YCBhbmQgYGhlbG1DaGFydFNlcnZpY2VOYW1lYC4gVGhlIHNlcnZpY2UgbmFt" + - "ZSBtdXN0IG1hdGNoIGEgS3ViZXJuZXRlcyBTZXJ2aWNlIHJlbmRlcmVkIGJ5IHRoZSBjaGFydCwg" + - "YW5kIGBpbmZlcmVuY2VQb3J0YCBtdXN0IGJlIHRoYXQgU2VydmljZSBwb3J0LgoKQmVmb3JlIGNy" + - "ZWF0aW5nIHRoZSBmdW5jdGlvbiwgY29uZmlybSB0aGUgZXhhY3QgZnVuY3Rpb24gbmFtZSBhbmQg" + - "bW9kZWwgbmFtZSB3aXRoIHRoZSB1c2VyLiBJZiBlaXRoZXIgdmFsdWUgaXMgbWlzc2luZywgc3Rv" + - "cCBhbmQgYXNrLiBEbyBub3QgaW52ZW50IG9yIHN1Ym1pdCBleGFtcGxlIHZhbHVlcyBmb3IgdGhl" + - "c2UgZmllbGRzLgoKYGBganNvbgp7CiAgIm5hbWUiOiAiPGNvbmZpcm1lZC1mdW5jdGlvbi1uYW1l" + - "PiIsCiAgImluZmVyZW5jZVVybCI6ICIvIiwKICAiaW5mZXJlbmNlUG9ydCI6IDgwMDAsCiAgImZ1" + - "bmN0aW9uVHlwZSI6ICJMTE0iLAogICJoZWFsdGgiOiB7CiAgICAicHJvdG9jb2wiOiAiSFRUUCIs" + - "CiAgICAidXJpIjogIi9oZWFsdGgiLAogICAgInBvcnQiOiA4MDAwLAogICAgInRpbWVvdXQiOiAi" + - "UFQzMFMiLAogICAgImV4cGVjdGVkU3RhdHVzQ29kZSI6IDIwMAogIH0sCiAgImhlbG1DaGFydCI6" + - "ICJodHRwczovL2hlbG0ubmdjLm52aWRpYS5jb20vZXhhbXBsZS90ZWFtL2NoYXJ0cy9vcGVuYWkt" + - "Y29tcGF0aWJsZS0wLjEuMC50Z3oiLAogICJoZWxtQ2hhcnRTZXJ2aWNlTmFtZSI6ICJvcGVuYWkt" + - "Y29tcGF0aWJsZSIsCiAgIm1vZGVscyI6IFsKICAgIHsKICAgICAgIm5hbWUiOiAiPGNvbmZpcm1l" + - "ZC1tb2RlbC1uYW1lPiIsCiAgICAgICJsbG1Db25maWciOiB7CiAgICAgICAgInVyaXMiOiBbIi92" + - "MS9jaGF0L2NvbXBsZXRpb25zIiwgIi92MS9yZXNwb25zZXMiLCAiL3YxL2VtYmVkZGluZ3MiXSwK" + - "ICAgICAgICAicm91dGluZ01ldGhvZCI6ICJyb3VuZF9yb2JpbiIsCiAgICAgICAgInRva2VuUmF0" + - "ZUxpbWl0IjogIjEwMDAtUyIKICAgICAgfQogICAgfQogIF0KfQpgYGAKCkVxdWl2YWxlbnQgQ0xJ" + - "IGZsYWcgZm9ybToKCmBgYHNoCm52Y2YtY2xpIGZ1bmN0aW9uIGNyZWF0ZSBcCiAgLS1uYW1lPSI8" + - "Y29uZmlybWVkLWZ1bmN0aW9uLW5hbWU+IiBcCiAgLS1pbmZlcmVuY2UtdXJsPS8gXAogIC0taW5m" + - "ZXJlbmNlLXBvcnQ9ODAwMCBcCiAgLS1oZWFsdGgtdXJpPS9oZWFsdGggXAogIC0taGVhbHRoLXBv" + - "cnQ9ODAwMCBcCiAgLS1oZWFsdGgtdGltZW91dD1QVDMwUyBcCiAgLS1oZWFsdGgtcHJvdG9jb2w9" + - "SFRUUCBcCiAgLS1oZWFsdGgtZXhwZWN0ZWQtc3RhdHVzPTIwMCBcCiAgLS1mdW5jdGlvbi10eXBl" + - "PUxMTSBcCiAgLS1oZWxtLWNoYXJ0PWh0dHBzOi8vaGVsbS5uZ2MubnZpZGlhLmNvbS9leGFtcGxl" + - "L3RlYW0vY2hhcnRzL29wZW5haS1jb21wYXRpYmxlLTAuMS4wLnRneiBcCiAgLS1oZWxtLWNoYXJ0" + - "LXNlcnZpY2U9b3BlbmFpLWNvbXBhdGlibGUgXAogIC0tbGxtLW1vZGVsPSduYW1lPTxjb25maXJt" + - "ZWQtbW9kZWwtbmFtZT4sdXJpcz0vdjEvY2hhdC9jb21wbGV0aW9uc3wvdjEvcmVzcG9uc2VzfC92" + - "MS9lbWJlZGRpbmdzLHJvdXRpbmdNZXRob2Q9cm91bmRfcm9iaW4sdG9rZW5SYXRlTGltaXQ9MTAw" + - "MC1TJwpgYGAKCkRlcGxveSBpcyB1bmNoYW5nZWQuIFB1dCBjaGFydC1zcGVjaWZpYyB2YWx1ZXMg" + - "dW5kZXIgYGRlcGxveW1lbnRTcGVjaWZpY2F0aW9uc1tdLmNvbmZpZ3VyYXRpb25gIG9ubHkgd2hl" + - "biB0aGUgY2hhcnQgbmVlZHMgdmFsdWUgb3ZlcnJpZGVzOgoKYGBganNvbgp7CiAgImZ1bmN0aW9u" + - "SWQiOiAiPGZuX2lkPiIsCiAgInZlcnNpb25JZCI6ICI8dmVyX2lkPiIsCiAgImRlcGxveW1lbnRT" + - "cGVjaWZpY2F0aW9ucyI6IFt7CiAgICAiZ3B1IjogIkgxMDAiLAogICAgImluc3RhbmNlVHlwZSI6" + - "ICJOQ1AuR1BVLkgxMDBfMXgiLAogICAgIm1pbkluc3RhbmNlcyI6IDEsCiAgICAibWF4SW5zdGFu" + - "Y2VzIjogMSwKICAgICJtYXhSZXF1ZXN0Q29uY3VycmVuY3kiOiAxMCwKICAgICJjb25maWd1cmF0" + - "aW9uIjogewogICAgICAic2VydmluZyI6IHsKICAgICAgICAidGVuc29yUGFyYWxsZWxTaXplIjog" + - "MQogICAgICB9CiAgICB9CiAgfV0KfQpgYGAKCkxMTSBHYXRld2F5IHJvdXRlcyByZXF1ZXN0cyBi" + - "eSB0aGUgT3BlbkFJIGBtb2RlbGAgdmFsdWUuIFVzZSBgPGZ1bmN0aW9uLWlkPi88bW9kZWwtbmFt" + - "ZT5gOiB0aGUgZnVuY3Rpb24gSUQgc2VsZWN0cyB0aGUgTlZDRiBmdW5jdGlvbiwgYW5kIHRoZSBt" + - "b2RlbCBuYW1lIGlzIGZvcndhcmRlZCB0byB0aGUgdXBzdHJlYW0gY29udGFpbmVyIHRocm91Z2gg" + - "YHN0YXJnYXRlLWNsaWVudGAuCgpTdXBwb3J0ZWQgTExNIHBhdGhzOgoKfCBQYXRoIHwgTm90ZXMg" + - "fAp8IC0tLSB8IC0tLSB8CnwgYC92MS9jaGF0L2NvbXBsZXRpb25zYCB8IFN1cHBvcnRzIHN0cmVh" + - "bWluZyBhbmQgc2Vzc2lvbiBzdGlja2luZXNzLiB8CnwgYC92MS9yZXNwb25zZXNgIHwgTmF0aXZl" + - "IFJlc3BvbnNlcyBwcm94eSBwYXRoOyByZWxheXMgU1NFIGZvciBzdHJlYW1pbmcgY2xpZW50cyBh" + - "bmQgcmV0dXJucyB0ZXJtaW5hbCBKU09OIGZvciBub24tc3RyZWFtaW5nIGNsaWVudHMuIFN1cHBv" + - "cnRzIHNlc3Npb24gc3RpY2tpbmVzcy4gfAp8IGAvdjEvZW1iZWRkaW5nc2AgfCBBY2NlcHRzIHN0" + - "cmluZyBvciBzdHJpbmctYXJyYXkgYGlucHV0YDsgaW5wdXQgbXVzdCBiZSBub24tZW1wdHkgYW5k" + - "IG1heSBjb250YWluIGF0IG1vc3QgMjA0OCBlbnRyaWVzLiBObyBzZXNzaW9uIHN0aWNraW5lc3Mu" + - "IHwKCiMjIDIuIEZ1bmN0aW9uIERFUExPWQoKYGBganNvbgp7CiAgImZ1bmN0aW9uSWQiOiAiPGZu" + - "X2lkPiIsCiAgInZlcnNpb25JZCI6ICI8dmVyX2lkPiIsCiAgImRlcGxveW1lbnRTcGVjaWZpY2F0" + - "aW9ucyI6IFt7CiAgICAiZ3B1IjogIkgxMDAiLAogICAgImluc3RhbmNlVHlwZSI6ICJOQ1AuR1BV" + - "LkgxMDBfMXgiLAogICAgIm1pbkluc3RhbmNlcyI6IDEsCiAgICAibWF4SW5zdGFuY2VzIjogMSwK" + - "ICAgICJtYXhSZXF1ZXN0Q29uY3VycmVuY3kiOiAxMAogIH1dCn0KYGBgCgpOb3RlOiBgZ3B1YCBp" + - "cyB0aGUgR1BVICoqZmFtaWx5KiogKGUuZy4gYEgxMDBgKSwgYGluc3RhbmNlVHlwZWAgaXMgdGhl" + - "IFNLVSAoZS5nLiBgTkNQLkdQVS5IMTAwXzF4YCkuIE1pc21hdGNoIHJldHVybnMgSUNNUyA0MDAg" + - "IkludmFsaWQgR1BVIi4KCmBgYHNoCm52Y2YtY2xpIGZ1bmN0aW9uIGRlcGxveSBjcmVhdGUgLS1p" + - "bnB1dC1maWxlPWRlcGxveS1mbi5qc29uCmBgYAoKVGhpcyBjb21tYW5kICoqYmxvY2tzKiogdW50" + - "aWwgdGhlIGRlcGxveW1lbnQgcmVhY2hlcyBBQ1RJVkUgKGRlZmF1bHQgOTAwcyB0aW1lb3V0KS4g" + - "V2hlbiBpdCByZXR1cm5zIHJjPTAsIGEgZnVuY3Rpb24gcG9kIGlzIHJ1bm5pbmcgaW4gdGhlIGBu" + - "dmNmLWJhY2tlbmRgIG5hbWVzcGFjZSBvbiB0aGUgY29tcHV0ZS1wbGFuZSBjbHVzdGVyLgoKIyMg" + - "My4gTWludCBhbiBBUEkga2V5IHdpdGggYGludm9rZV9mdW5jdGlvbmAgc2NvcGUKCmBudmNmLWNs" + - "aSBpbml0YCdzIGFkbWluIHRva2VuIGRvZXMgTk9UIGNhcnJ5IHRoZSBgaW52b2tlX2Z1bmN0aW9u" + - "YCBzY29wZSDigJQgaW52b2tlIHdvdWxkIDQwMyB3aXRoICJtaXNzaW5nIHJlcXVlc3RlZCBhdXRo" + - "b3JpdGllcyIuIE1pbnQgYW4gQVBJIGtleToKCmBgYHNoCm52Y2YtY2xpIGFwaS1rZXkgZ2VuZXJh" + - "dGUgLS1kZXNjcmlwdGlvbj0iPGNvbmZpcm1lZC1mdW5jdGlvbi1uYW1lPiBpbnZva2UiIC0tZXhw" + - "aXJlcy1pbj0xaAojIOKGkiBBUEkgS2V5OiBudmFwaS1udmNmLXN0Zy0uLi4KYGBgCgpUaGUga2V5" + - "IGlzIGF1dG9tYXRpY2FsbHkgc2F2ZWQgdG8gdGhlIENMSSBzdGF0ZSBmaWxlIGFuZCB1c2VkIGJ5" + - "IHRoZSBuZXh0IGBmdW5jdGlvbiBpbnZva2VgIGNhbGwuICoqRG9uJ3QgZWNobyB0aGUga2V5IGlu" + - "dG8gY2hhdCAvIGxvZ3MqKi4KCiMjIDQuIEZ1bmN0aW9uIElOVk9LRQoKYGBganNvbgp7CiAgImZ1" + - "bmN0aW9uSWQiOiAiPGZuX2lkPiIsCiAgInZlcnNpb25JZCI6ICI8dmVyX2lkPiIsCiAgInJlcXVl" + - "c3RCb2R5IjogeyJtZXNzYWdlIjogImhlbGxvIn0KfQpgYGAKCmBgYHNoCm52Y2YtY2xpIGZ1bmN0" + - "aW9uIGludm9rZSAtLWlucHV0LWZpbGU9aW52b2tlLWZuLmpzb24KIyDihpIgRnVuY3Rpb24gaW52" + - "b2NhdGlvbiBjb21wbGV0ZWQhCiMg4oaSIFN0YXR1czogZnVsZmlsbGVkCiMg4oaSIFJlcXVlc3Qg" + - "SUQ6IC4uLgpgYGAKCkZvciBhbiBMTE0gZnVuY3Rpb24sIGludm9rZSB0aGUgT3BlbkFJLWNvbXBh" + - "dGlibGUgcm91dGUgYWZ0ZXIgZGVwbG95bWVudDoKCmBgYHNoCmN1cmwgLXNTIC1YIFBPU1QgImh0" + - "dHBzOi8vbGxtLmludm9jYXRpb24uPGRvbWFpbj4vdjEvY2hhdC9jb21wbGV0aW9ucyIgXAogIC1I" + - "ICJBdXRob3JpemF0aW9uOiBCZWFyZXIgJHtOVkNGX0FQSV9LRVl9IiBcCiAgLUggIkNvbnRlbnQt" + - "VHlwZTogYXBwbGljYXRpb24vanNvbiIgXAogIC1kICd7CiAgICAibW9kZWwiOiAiPGZuX2lkPi88" + - "Y29uZmlybWVkLW1vZGVsLW5hbWU+IiwKICAgICJzdHJlYW0iOiB0cnVlLAogICAgIm1lc3NhZ2Vz" + - "IjogWwogICAgICB7CiAgICAgICAgInJvbGUiOiAidXNlciIsCiAgICAgICAgImNvbnRlbnQiOiAi" + - "SGVsbG8iCiAgICAgIH0KICAgIF0KICB9JwpgYGAKClRoZSBPcGVuQUkgYG1vZGVsYCB2YWx1ZSBt" + - "dXN0IGJlIGA8ZnVuY3Rpb24taWQ+Lzxtb2RlbC1uYW1lPmAgc28gdGhlIGdhdGV3YXkgY2FuIHNl" + - "bGVjdCB0aGUgZnVuY3Rpb24gYW5kIG1vZGVsLgoKUmVzcG9uc2VzIEFQSSBleGFtcGxlOgoKYGBg" + - "c2gKY3VybCAtc1MgLVggUE9TVCAiaHR0cHM6Ly9sbG0uaW52b2NhdGlvbi48ZG9tYWluPi92MS9y" + - "ZXNwb25zZXMiIFwKICAtSCAiQXV0aG9yaXphdGlvbjogQmVhcmVyICR7TlZDRl9BUElfS0VZfSIg" + - "XAogIC1IICJDb250ZW50LVR5cGU6IGFwcGxpY2F0aW9uL2pzb24iIFwKICAtZCAnewogICAgIm1v" + - "ZGVsIjogIjxmbl9pZD4vPGNvbmZpcm1lZC1tb2RlbC1uYW1lPiIsCiAgICAiaW5wdXQiOiAiV3Jp" + - "dGUgYSBvbmUgc2VudGVuY2Ugc3VtbWFyeSBvZiBOVkNGLiIKICB9JwpgYGAKCkVtYmVkZGluZ3Mg" + - "ZXhhbXBsZToKCmBgYHNoCmN1cmwgLXNTIC1YIFBPU1QgImh0dHBzOi8vbGxtLmludm9jYXRpb24u" + - "PGRvbWFpbj4vdjEvZW1iZWRkaW5ncyIgXAogIC1IICJBdXRob3JpemF0aW9uOiBCZWFyZXIgJHtO" + - "VkNGX0FQSV9LRVl9IiBcCiAgLUggIkNvbnRlbnQtVHlwZTogYXBwbGljYXRpb24vanNvbiIgXAog" + - "IC1kICd7CiAgICAibW9kZWwiOiAiPGZuX2lkPi88Y29uZmlybWVkLW1vZGVsLW5hbWU+IiwKICAg" + - "ICJpbnB1dCI6ICJOVkNGIGVtYmVkZGluZ3MgY2hlY2siCiAgfScKYGBgCgpJZiBzdGF0dXMgaXMg" + - "YGVycm9yZWRgLCBxdWVyeSBJQ01TIGZvciB0aGUgZGVwbG95bWVudCdzIHBvZCBsb2dzIChrdWJl" + - "Y3RsIG9uIHRoZSBjb21wdXRlLXBsYW5lIGNsdXN0ZXIpIGFuZCBzdXJmYWNlIHRvIHRoZSB1c2Vy" + - "LgoKIyMgNS4gQ2xlYW51cAoKV2hlbiB0aGUgdXNlciBpcyBkb25lIHdpdGggdGhlIHNtb2tlIHRl" + - "c3Q6CgpgYGBzaApudmNmLWNsaSBmdW5jdGlvbiBkZWxldGUgLS1mdW5jdGlvbi1pZD08Zm5faWQ+" + - "IC0tdmVyc2lvbi1pZD08dmVyX2lkPgpgYGAKCioqQ29uZmlybSB3aXRoIHRoZSB1c2VyIGJlZm9y" + - "ZSBkZWxldGluZy4qKiBUaGlzIHJlbW92ZXMgYm90aCB0aGUgZGVwbG95bWVudCBhbmQgdGhlIGZ1" + - "bmN0aW9uIHJlY29yZC4KCiMjIE5vdGVzCgotIERlcGxveSB0aW1lcyBjYW4gdmFyeSB3aWRlbHkg" + - "ZGVwZW5kaW5nIG9uIE5BVFMgc3RyZWFtLWluaXQgbGF0ZW5jeSBvbiBjb2xkLWNsdXN0ZXIgcnVu" + - "cyAoMS03IG1pbnV0ZXMgb2JzZXJ2ZWQgaW4gZGV2LVZNIHRlc3RzKS4gQmUgcGF0aWVudDsgZG9u" + - "J3Qga2lsbCB0aGUgYGZ1bmN0aW9uIGRlcGxveSBjcmVhdGVgIGNvbW1hbmQgZWFybHkuCi0gRm9y" + - "IGRpZmZlcmVudCBHUFUgZmFtaWxpZXMsIGNoYW5nZSBib3RoIGBncHVgIGFuZCBgaW5zdGFuY2VU" + - "eXBlYCB0b2dldGhlci4gQ29tbW9uIFNLVXM6IGBOQ1AuR1BVLkgxMDBfMXhgLCBgTkNQLkdQVS5I" + - "MTAwXzJ4YCwgYE5DUC5HUFUuQTEwMF8xeGAuCi0gVGhlIGZ1bmN0aW9uJ3MgY29udGFpbmVyIGlt" + - "YWdlIG11c3QgYmUgcHVsbGFibGUgZnJvbSB0aGUgY29tcHV0ZS1wbGFuZSBjbHVzdGVyJ3MgaW1h" + - "Z2UtY3JlZGVudGlhbC1oZWxwZXIgY29uZmlnIChjaGVjayBga3ViZWN0bCBkZXNjcmliZSBwb2Qg" + - "LW4gbnZjZi1iYWNrZW5kYCBpZiBwb2RzIGxhbmQgaW4gYEltYWdlUHVsbEJhY2tPZmZgKS4KUEsH" + - "CNJD3AQYJQAAGCUAAFBLAwQUAAgAAAAAACEAAAAAAAAAAAAAAAAAPQAJAGRhdGEvbnZjZi1zZWxm" + - "LW1hbmFnZWQtY2xpL3Byb21wdHMvZGlhZ25vc2UtZmFpbGVkLWluc3RhbGwubWRVVAUAAQCmzhIj" + - "IERpYWdub3NlIGEgZmFpbGVkIHNlbGYtaG9zdGVkIE5WQ0YgaW5zdGFsbAoKVXNlciBzYXlzICJt" + - "eSBpbnN0YWxsIGZhaWxlZCIgb3IgIlggaXNuJ3Qgd29ya2luZyIuIERvbid0IGd1ZXNzIOKAlCBx" + - "dWVyeSB0aGUgY2x1c3Rlci4KCiMjIDEuIEdldCB0aGUgdmVyZGljdAoKYGBgc2gKbnZjZi1jbGkg" + - "c2VsZi1ob3N0ZWQgc3RhdHVzIC0tanNvbiB8IGpxIC1jIC4KYGBgCgpUaGUgZmlyc3QgZXZlbnQg" + - "aXMgdGhlIHNuYXBzaG90LiBQYXJzZSBgLnZlcmRpY3RgOgoKLSAqKmBoZWFsdGh5YCoqIOKAlCBu" + - "b3RoaW5nJ3Mgd3JvbmcuIEFzayB0aGUgdXNlciB3aGF0IHRoZXkncmUgYWN0dWFsbHkgc2VlaW5n" + - "OyB0aGVpciBwcm9ibGVtIG1heSBiZSBhdCBhIGRpZmZlcmVudCBsYXllciAoZnVuY3Rpb24gZGVw" + - "bG95LCBuZXR3b3JrLCBldGMuKS4KLSAqKmBkZWdyYWRlZGAqKiDigJQgYXQgbGVhc3Qgb25lIGNv" + - "bXBvbmVudCBub3QgcmVhZHkuIFRoZSBDb21wb25lbnRzIGxpc3Qgd2lsbCBpZGVudGlmeSB3aGlj" + - "aC4gTW9zdCBjb21tb246IGBjYXNzYW5kcmFgIFBvZEluaXRpYWxpemluZywgYG9wZW5iYW9gIG5v" + - "dCB1bnNlYWxlZCwgYHNpc2AgSW1hZ2VQdWxsQmFja09mZi4KLSAqKmBmYWlsZWRgKiog4oCUIE5W" + - "Q0ZCYWNrZW5kIGBIZWFsdGg9RmFpbGVkYC4gUmVhZCB0aGUgY2x1c3RlcidzIE5WQ0ZCYWNrZW5k" + - "IHN0YXR1cyBjb25kaXRpb24gbWVzc2FnZS4KLSAqKmB1bmtub3duYCoqIOKAlCBJQ01TIHVucmVh" + - "Y2hhYmxlLiBUaGUgY29udHJvbCBwbGFuZSBpdHNlbGYgbWF5IGJlIGRvd247IGNoZWNrIGBrdWJl" + - "Y3RsIGdldCBwb2RzIC1uIHNpc2AgZGlyZWN0bHkuCgojIyAyLiBEcmlsbCBpbnRvIHRoZSBmYWls" + - "aW5nIGNvbXBvbmVudAoKRm9yIGVhY2ggbm9uLXJlYWR5IGNvbXBvbmVudCwgdGhlIHNuYXBzaG90" + - "IGluY2x1ZGVzIGEgYG1lc3NhZ2VgIChlLmcuIGBjYXNzYW5kcmEtMCBQb2RJbml0aWFsaXppbmcg" + - "Nm0yM3NgKS4gQXNrIHRoZSB1c2VyIHRvIHJ1bjoKCmBgYHNoCmt1YmVjdGwgZGVzY3JpYmUgcG9k" + - "IC1uIDxucz4gPHBvZD4Ka3ViZWN0bCBsb2dzIC1uIDxucz4gPHBvZD4gLS1hbGwtY29udGFpbmVy" + - "cyAtLXRhaWw9MTAwCmBgYAoKU3VyZmFjZSB0aGUgZXZlbnRzICsgbGFzdCBsb2cgbGluZXMuIERv" + - "bid0IHByb3Bvc2UgYGt1YmVjdGwgZGVsZXRlIHBvZGAgdW5sZXNzIHRoZSB1c2VyIGNvbmZpcm1z" + - "IOKAlCBDYXNzYW5kcmEgcmVzdGFydCBsb3NlcyBhbnkgaW4tcHJvZ3Jlc3MgbWlncmF0aW9uLgoK" + - "IyMgMy4gQ29tbW9uIHBhdHRlcm5zCgp8IFN5bXB0b20gfCBMaWtlbHkgY2F1c2UgfCBSZW1lZGlh" + - "dGlvbiB8CnwtLS18LS0tfC0tLXwKfCBgY2Fzc2FuZHJhLTAgUG9kSW5pdGlhbGl6aW5nYCBmb3Ig" + - "PjUgbWluIHwgU3RvcmFnZUNsYXNzIG1pc3Npbmcgb3IgUFYgc3R1Y2sgYFBlbmRpbmdgIHwgYGt1" + - "YmVjdGwgZ2V0IHB2LHB2YyAtQWAg4oCUIGNvbmZpcm0gYSBkZWZhdWx0IFN0b3JhZ2VDbGFzcyBl" + - "eGlzdHMuIExvY2FsIGRldjogYGt1YmVjdGwgcGF0Y2ggc3RvcmFnZWNsYXNzIGxvY2FsLXBhdGgg" + - "LXAgJ3sibWV0YWRhdGEiOnsiYW5ub3RhdGlvbnMiOnsic3RvcmFnZWNsYXNzLmt1YmVybmV0ZXMu" + - "aW8vaXMtZGVmYXVsdC1jbGFzcyI6InRydWUifX19J2AgfAp8IGBoZWxtIGluc3RhbGwgLi4uOiB0" + - "aW1lZCBvdXQgd2FpdGluZyBmb3IgY2Fzc2FuZHJhIHRvIGJlY29tZSBhdmFpbGFibGVgIHwgU2Ft" + - "ZSBhcyBhYm92ZSB8IFNhbWUgfAp8IGBHYXRld2F5IEFQSSBDUkRzIG5vdCBpbnN0YWxsZWRgIChw" + - "cmVmbGlnaHQpIHwgTXVsdGktY2x1c3RlciBgbnZjZi1nYXRld2F5LXJvdXRlc2AgY2hhcnQgbmVl" + - "ZHMgZW52b3ktZ2F0ZXdheSBwcmVyZXF1aXNpdGUgfCBJbnN0YWxsIGVudm95LWdhdGV3YXk6IGBo" + - "ZWxtIGluc3RhbGwgZWcgb2NpOi8vZG9ja2VyLmlvL2Vudm95cHJveHkvZ2F0ZXdheS1oZWxtIC0t" + - "bmFtZXNwYWNlPWVudm95LWdhdGV3YXktc3lzdGVtIC0tY3JlYXRlLW5hbWVzcGFjZWAgfAp8IGBO" + - "byBjbHVzdGVyIGZvdW5kIHdpdGggdmFsaWQgSldLUyBmb3IgY2x1c3RlciBJRDogPGlkPmAgKE5W" + - "Q0EgYWdlbnQpIHwgQ29tcHV0ZSBwbGFuZSdzIFBTQVQgd2FzIGlzc3VlZCBieSBhIEs4cyBzZXJ2" + - "ZXIgd2hvc2UgSldLUyBkb2Vzbid0IG1hdGNoIHdoYXQncyBzdG9yZWQgaW4gSUNNUyB8IFJlLXJl" + - "Z2lzdGVyOiBgbnZjZi1jbGkgY2x1c3RlciByb3RhdGUgLS1jbHVzdGVyLWlkPTxpZD5gIGZyb20g" + - "YSBjb250ZXh0IHRoYXQgY2FuIHJlYWNoIHRoZSBjb21wdXRlIHBsYW5lJ3MgSzhzIEFQSSB8Cnwg" + - "YFNpZ25lZCBKV1QgcmVqZWN0ZWQ6IEFub3RoZXIgYWxnb3JpdGhtIGV4cGVjdGVkLCBvciBubyBt" + - "YXRjaGluZyBrZXkocykgZm91bmRgIChpbml0IC8gY2x1c3RlciByZWdpc3RlcikgfCBTdGFsZSBz" + - "ZXNzaW9uLmpzb24gZnJvbSBhIGRpZmZlcmVudCBjb250cm9sIHBsYW5lIHwgYHJtIH4vLm52Y2Yt" + - "Y2xpLnN0YXRlYCB0aGVuIGBudmNmLWNsaSBpbml0YCBhZ2FpbiB8CnwgYG52Y2Etb3BlcmF0b3Jg" + - "IEltYWdlUHVsbEJhY2tPZmYgZm9yIGBudmNhLW9wZXJhdG9yOnBzYXRgIHwgSW1hZ2Ugbm90IGlu" + - "IGNsdXN0ZXIgKG11bHRpLWNsdXN0ZXIgdG9wb2xvZ3kgYnVpbGRzIGl0IGxvY2FsbHkpIHwgYGsz" + + "ZWQgfCBWYWx1ZXMgYXJlIGVuY3J5cHRlZCBhdCByZXN0IHwKfCBgdGFzayBidWxrIC0tdGFzay1p" + + "ZHM9SUQxWyxJRDIs4oCmXWAgfCBGZXRjaCBkZXRhaWxzIGZvciBtdWx0aXBsZSB0YXNrcyBpbiBv" + + "bmUgcmVxdWVzdCB8IHwKCiMjIEF1dGgKCnwgQ29tbWFuZCB8IFB1cnBvc2UgfCBOb3RlcyB8Cnwt" + + "LS18LS0tfC0tLXwKfCBgaW5pdGAgfCBNaW50IGFkbWluIHRva2VuIGZyb20gQVBJIEtleXMgdmlh" + + "IHB1YmxpYyBhcGkgZ2F0ZXdheSB8IElkZW1wb3RlbnQ7IHdyaXRlcyBgfi8ubnZjZi1jbGkuc3Rh" + + "dGVgOyBjbGVhcnMgc2F2ZWQgZnVuY3Rpb24gc3RhdGUgYW5kIGFsbCBBUEkga2V5cyAoZnVuY3Rp" + + "b24gYW5kIHRhc2spIHwKfCBgc3RhdHVzYCB8IFNob3cgQ0xJIHN0YXRlICh0b2tlbiwgbGFzdCBl" + + "bmRwb2ludHMsIGZ1bmN0aW9uIGNvbnRleHQpIHwgRGlzdGluY3QgZnJvbSBgc2VsZi1ob3N0ZWQg" + + "c3RhdHVzYCB8CnwgYGFwaS1rZXkgZ2VuZXJhdGUgWy0tZm9yIGZ1bmN0aW9uXHx0YXNrXSBbLS1k" + + "ZXNjcmlwdGlvbj3igKZdIFstLWV4cGlyZXMtaW494oCmXWAgfCBNaW50IEFQSSBrZXlzIHwgRGVm" + + "YXVsdCAobm8gYC0tZm9yYCk6IGdlbmVyYXRlcyBib3RoIGEgZnVuY3Rpb24ga2V5IGFuZCBhIHRh" + + "c2sga2V5OyBgLS1mb3IgZnVuY3Rpb25gIG9yIGAtLWZvciB0YXNrYCBnZW5lcmF0ZXMgb25lIG9u" + + "bHk7IGAtLXNjb3Blc2AgcmVxdWlyZXMgYC0tZm9yYCB8CnwgYGFwaS1rZXkgZ2VuZXJhdGUgLS12" + + "YWxpZGF0ZWAgfCBNaW50IGFuZCBpbW1lZGlhdGVseSB2YWxpZGF0ZSBlYWNoIGdlbmVyYXRlZCBr" + + "ZXkgfCBWYWxpZGF0ZXMgZWFjaCBrZXkgYWdhaW5zdCBpdHMgY29ycmVjdCBhdWRpZW5jZSAoTlZD" + + "RiBmb3IgZnVuY3Rpb24ga2V5cywgTlZDVCBmb3IgdGFzayBrZXlzKSB8CnwgYGFwaS1rZXkgbGlz" + + "dGAgfCBMaXN0IEFQSSBrZXlzIGZvciBjdXJyZW50IG93bmVyIHwgTGlzdHMgTlZDRi1zY29wZWQg" + + "a2V5cyBvbmx5IHwKfCBgYXBpLWtleSBkZWxldGUgS0VZLUlEYCAvIGBhcGkta2V5IHJldm9rZSBL" + + "RVktSURgIHwgUmVtb3ZlIGFuIEFQSSBrZXkgfCAqKkNvbmZpcm0gd2l0aCB1c2VyKiogfAp8IGBh" + + "cGkta2V5IHNob3dgIHwgU2hvdyBjdXJyZW50bHkgc2F2ZWQgQVBJIGtleSBmcm9tIHN0YXRlIHwg" + + "fAoKIyMgQWdlbnQgc2tpbGwKCnwgQ29tbWFuZCB8IFB1cnBvc2UgfCBOb3RlcyB8CnwtLS18LS0t" + + "fC0tLXwKfCBgYWdlbnQtc2tpbGwgaW5zdGFsbCBbLS10YXJnZXQ9RElSXWAgfCBJbnN0YWxsIGFs" + + "bCBidW5kbGVkIHB1YmxpYyB1c2VyIHNraWxscyBpbnRvIGB+Ly5jbGF1ZGUvc2tpbGxzL2AgYW5k" + + "IGB+Ly5hZ2VudHMvc2tpbGxzL2AgfCBEZWZhdWx0OiBib3RoIGJhc2Ugc2tpbGxzIGRpcmVjdG9y" + + "aWVzIHwKfCBgYWdlbnQtc2tpbGwgdW5pbnN0YWxsIFstLXRhcmdldD1ESVJdYCB8IFJlbW92ZSBv" + + "bmx5IGJ1bmRsZWQgcHVibGljIHVzZXIgc2tpbGxzIGZyb20gdGhlIHRhcmdldCBiYXNlIHNraWxs" + + "cyBkaXJlY3RvcmllcyB8IElkZW1wb3RlbnQ7IHVucmVsYXRlZCBza2lsbHMgcmVtYWluIHwKfCBg" + + "YWdlbnQtc2tpbGwgc2hvdyBbLS1maWxlPVJFTF1gIHwgUHJpbnQgYnVuZGxlZCBgbnZjZi1zZWxm" + + "LW1hbmFnZWQtY2xpL1NLSUxMLm1kYCBvciBhbm90aGVyIGVtYmVkZGVkIGZpbGUgfCBGb3IgZGVi" + + "dWdnaW5nIHwKfCBgYWdlbnQtc2tpbGwgdmVyc2lvbmAgfCBQcmludCBidWlsZCBTSEEgYW5kIGVt" + + "YmVkZGVkIHB1YmxpYyB1c2VyIHNraWxsIHN1bW1hcnkgfCB8ClBLBwhyDQ9kISMAACEjAABQSwME" + + "FAAIAAAAAAAhAAAAAAAAAAAAAAAAADIACQBkYXRhL252Y2Ytc2VsZi1tYW5hZ2VkLWNsaS9yZWZl" + + "cmVuY2UvZXhpdC1jb2Rlcy5tZFVUBQABAKbOEiMgYG52Y2YtY2xpYCBleGl0IGNvZGVzCgpTdGFi" + + "bGUgYWNyb3NzIHN1YmNvbW1hbmRzLiBVc2UgdGhlc2UgdG8gZHJpdmUgYWdlbnQgcmV0cnkgLyBz" + + "dXJmYWNpbmcgbG9naWMuCgp8IENvZGUgfCBNZWFuaW5nIHwgRXhhbXBsZSBjYXVzZXMgfAp8LS0t" + + "fC0tLXwtLS18CnwgYDBgIHwgU3VjY2VzcyB8IEFsbCBjaGVja3MgcGFzc2VkOyBpbnN0YWxsIGNv" + + "bXBsZXRlZDsgZGVwbG95bWVudCBBQ1RJVkUgfAp8IGAxYCB8IEdlbmVyaWMgZXJyb3IgfCBIZWxt" + + "IHJlbmRlciBmYWlsZWQ7IG5ldHdvcmsgdW5yZWFjaGFibGU7IGZpbGUgbm90IGZvdW5kOyBZQU1M" + + "IHBhcnNlIGVycm9yIHwKfCBgMmAgfCBQcmUtZmxpZ2h0IGNoZWNrIGZhaWxlZCB8IEEgYGNoZWNr" + + "YCByZXN1bHQgYXQgZXJyb3Igc2V2ZXJpdHk6IEdhdGV3YXkgQVBJIENSRHMgbWlzc2luZzsga3Vi" + + "ZWN0bCBub3Qgb24gUEFUSDsgZGVmYXVsdCBTdG9yYWdlQ2xhc3MgYWJzZW50OyBhIGNsdXN0ZXIt" + + "dmFsaWRhdG9yIHRoYXQgY291bGQgbm90IHJ1biAoUkJBQyBkZW5pZWQsIGltYWdlIHB1bGwgZmFp" + + "bHVyZSwgdGltZW91dCkuIFdhcm5pbmctc2V2ZXJpdHkgcmVzdWx0cyBleGl0IGAwYCB8CnwgYDNg" + + "IHwgQWRtaW4gYXV0aCBmYWlsZWQgfCBObyB0b2tlbiArIGAtLW5vbi1pbnRlcmFjdGl2ZWAgc2V0" + + "OyBJQ01TIHJlamVjdGVkIEpXVDsgaW5pdCBlbmRwb2ludCB1bnJlYWNoYWJsZSB8CnwgYDVgIHwg" + + "TWFuaWZlc3QgYXBwbHkgb3IgYC0td2FpdGAgdGltZWQgb3V0IHwgSGVsbSBpbnN0YWxsIHRpbWVv" + + "dXQ7IGNoZWNrIHBvbGxlZCBidXQgZGlkbid0IHBhc3MgYmVmb3JlIGR1cmF0aW9uOyB0aGUgY2hl" + + "Y2sncyB0aW1lIGJ1ZGdldCByYW4gb3V0IGJlZm9yZSBldmVyeSBjaGVjayByYW4gKHRob3NlIHJl" + + "cG9ydCBhcyBub3QgcnVuKSB8CnwgYDEzMGAgfCBDYW5jZWxsZWQgYnkgU0lHSU5UL1NJR1RFUk0g" + + "fCBVc2VyIEN0cmwtQzsgQ0kgYnVkZ2V0IGV4Y2VlZGVkOyBwb2QgZXZpY3RlZCB8CgojIyBIb3cg" + + "YW4gYWdlbnQgc2hvdWxkIHJlYWN0Cgp8IENvZGUgfCBBZ2VudCBiZWhhdmlvciB8CnwtLS18LS0t" + + "fAp8IGAwYCB8IENvbnRpbnVlIC8gcmVwb3J0IHN1Y2Nlc3MuIHwKfCBgMWAgfCBTdXJmYWNlIHN0" + + "ZGVyciB0byB1c2VyOyBhc2sgYmVmb3JlIGFueSByZXRyeS4gR2VuZXJpYyBlcnJvcnMgdXN1YWxs" + + "eSBuZWVkIGEgaHVtYW4gdG8gcmVhZCB0aGUgbWVzc2FnZS4gfAp8IGAyYCB8IFN1cmZhY2UgZmFp" + + "bGVkIGNoZWNrcyArIHRoZWlyIGBoaW50VVJMYC4gRG9uJ3QgcHJvcG9zZSBgdXBgIHVudGlsIHRo" + + "ZSB1c2VyIGZpeGVzIHByZXJlcXMuIHwKfCBgM2AgfCBTdWdnZXN0IGBudmNmLWNsaSBpbml0YCAo" + + "aW50ZXJhY3RpdmUpIE9SIGAtLXRva2VuPSRKV1RgIChDSSkuIERvbid0IGF1dG8tbWludCB3aXRo" + + "b3V0IHVzZXIgT0suIHwKfCBgNWAgfCBBc2sgdXNlciBpZiB0aGV5IHdhbnQgdG8gd2FpdCBsb25n" + + "ZXIsIGRpZyBpbnRvIHRoZSBzdGFsbGVkIGNvbXBvbmVudCwgb3IgY2FuY2VsLiB8CnwgYDEzMGAg" + + "fCBEb24ndCByZXRyeSBhdXRvbWF0aWNhbGx5OyB1c2VyIGV4cGxpY2l0bHkgY2FuY2VsbGVkLiBD" + + "b25maXJtIGJlZm9yZSByZS1ydW5uaW5nLiB8CgojIyBXaGVyZSB0byBmaW5kIG1vcmUgZGV0YWls" + + "CgpgY2hlY2tgIHJlcG9ydHMgaXRzIG91dGNvbWUgaW4gb25lIGBmaW5hbGAgZXZlbnQgKGBzdWNj" + + "ZXNzYCwgYHZlcmRpY3RgLCBgZmFpbGVkQ291bnRgKSBhbmQgbmV2ZXIgZW1pdHMgYHBoYXNlX2Zh" + + "aWxlZGA7IHNlZSBgZXhhbXBsZXMvY2ktcGlwZWxpbmVzLm1kYC4gQSBjYW5jZWxsYXRpb24gKGV4" + + "aXQgYDEzMGApIGVtaXRzIGBmaW5hbGAgd2l0aCBgY2FuY2VsbGVkOiB0cnVlYCBpbnN0ZWFkIG9m" + + "IGBwaGFzZV9mYWlsZWRgOyBgdXBgIGVtaXRzIGBwaGFzZV9jYW5jZWxsZWRgIGJlZm9yZSBpdC4g" + + "RXZlcnkgb3RoZXIgbm9uLXplcm8gZXhpdCBlbWl0cyBhIHN0cnVjdHVyZWQgYHBoYXNlX2ZhaWxl" + + "ZGAgSlNPTiBldmVudCB3aXRoIGBlcnJDYXRlZ29yeWAsIGBlcnJNZXNzYWdlYCwgYHJlbWVkaWF0" + + "aW9uYCAoYXJyYXkpLCBgcmV0cnlDbGFzc2AgKGVudW06IGBub25lfGltbWVkaWF0ZXxiYWNrb2Zm" + + "fGFmdGVyX3JlbWVkaWF0aW9ufHVua25vd25gKSwgYHJldHJ5QWZ0ZXJTZWNgIChpbnQsIG9wdGlv" + + "bmFsKSwgYW5kIGByYXdgIChzdWJwcm9jZXNzICsgSFRUUCArIEt1YmVybmV0ZXMgc2lnbmFsKS4g" + + "QWx3YXlzIGNvbnN1bWUgdGhlc2UgaW4gYC0tanNvbmAgbW9kZSByYXRoZXIgdGhhbiBwYXJzaW5n" + + "IEVuZ2xpc2ggZnJvbSBzdGRlcnIuClBLBwg8GfBY4ggAAOIIAABQSwMEFAAIAAAAAAAhAAAAAAAA" + + "AAAAAAAAAC0ACQBkYXRhL252Y2Ytc2VsZi1tYW5hZ2VkLWNsaS9yZWZlcmVuY2UvZmxhZ3MubWRV" + + "VAUAAQCmzhIjIGBudmNmLWNsaWAgZmxhZyByZWZlcmVuY2UKCiMjIEdsb2JhbCBmbGFncyAoZXZl" + + "cnkgc3ViY29tbWFuZCkKCnwgRmxhZyB8IFB1cnBvc2UgfCBEZWZhdWx0IHwKfC0tLXwtLS18LS0t" + + "fAp8IGAtLWNvbmZpZyBGSUxFYCB8IENvbmZpZyBmaWxlIHBhdGggfCBgJEhPTUUvLm52Y2YtY2xp" + + "LnlhbWxgIHwKfCBgLS1kZWJ1Z2AgfCBWZXJib3NlIEhUVFAgbG9nZ2luZyBvbiBzdGRlcnIgfCBg" + + "ZmFsc2VgIHwKfCBgLS1qc29uYCB8IEpTT05MIGV2ZW50cyBvbiBzdGRlcnIgfCBhdXRvIChvZmYg" + + "aW4gVFRZLCBvbiB1bmRlciBgLS1ub24taW50ZXJhY3RpdmVgKSB8CgojIyBgc2VsZi1ob3N0ZWRg" + + "IHBhcmVudCAocGVyc2lzdGVudCBhY3Jvc3Mgc3ViY29tbWFuZHMpCgp8IEZsYWcgfCBQdXJwb3Nl" + + "IHwgRGVmYXVsdCB8CnwtLS18LS0tfC0tLXwKfCBgLS1jb250cm9sLXBsYW5lLXN0YWNrPS4uLmAg" + + "fCBDb250cm9sLXBsYW5lIGJ1bmRsZSBzb3VyY2U6IGxvY2FsIHBhdGgsIGdpdCBVUkwsIG9yIGBv" + + "Y2k6Ly9gIFVSTCB8IGVtYmVkZGVkIE9DSSBVUkwgcGlubmVkIGJ5IENMSSB2ZXJzaW9uIHwKfCBg" + + "LS1jb21wdXRlLXBsYW5lLXN0YWNrPS4uLmAgfCBDb21wdXRlLXBsYW5lIGJ1bmRsZSBzb3VyY2U6" + + "IGxvY2FsIHBhdGgsIGdpdCBVUkwsIG9yIGBvY2k6Ly9gIFVSTCB8IGVtYmVkZGVkIE9DSSBVUkwg" + + "cGlubmVkIGJ5IENMSSB2ZXJzaW9uIHwKfCBgLS1lbnY9bG9jYWxcfHByZFx8Li4uYCB8IEhlbG1m" + + "aWxlIGVudmlyb25tZW50IG5hbWUgfCBgbG9jYWxgIGZvciBkZXYgYnVpbGRzLCBgcHJkYCBmb3Ig" + + "cmVsZWFzZXMgfAp8IGAtLW5vbi1pbnRlcmFjdGl2ZWAgfCBEaXNhYmxlIGFsbCBzdGRpbiBwcm9t" + + "cHRzIHwgYGZhbHNlYCB8CnwgYC0tdG9rZW49JEpXVGAgfCBBZG1pbiBKV1QsIG92ZXJyaWRlcyBz" + + "dG9yZWQgc2Vzc2lvbiB8IC0gfAp8IGAtLW5vLWFwcGx5YCB8IGBpbnN0YWxsYCBvbmx5IC0gZW1p" + + "dCBZQU1MLCBkbyBub3Qga3ViZWN0bCBhcHBseSB8IGBmYWxzZWAgfAp8IGAtLW91dHB1dD10ZXh0" + + "XHxqc29uYCB8IExlZ2FjeSBhbGlhcyBmb3IgYC0tanNvbmAgKGRlcHJlY2F0ZWQsIHJlbW92ZWQg" + + "aW4gbmV4dCBtYWpvcikgfCBgdGV4dGAgfAp8IGAtLXBsYWluYCB8IEZvcmNlIHBsYWluIHN0cmVh" + + "bWluZyBvdXRwdXQgfCBhdXRvLWRldGVjdCB8CnwgYC0td2FpdCBEVVJBVElPTmAgfCBgY2hlY2tg" + + "IG9ubHk7IHBvbGwgdW50aWwgdGhlIGNoZWNrIHBhc3NlcyBvciBEVVJBVElPTiBydW5zIG91dCAo" + + "ZXhpdCBgNWApLiBBIHdhcm5pbmcgdGhhdCBpcyBleHBlY3RlZCB0byBjbGVhciwgc3VjaCBhcyBh" + + "IHJvbGxvdXQgaW4gcHJvZ3Jlc3MsIGtlZXBzIGl0IHBvbGxpbmcuIENhbm5vdCBiZSBjb21iaW5l" + + "ZCB3aXRoIGAtLW5vLWNsZWFudXBgIHwgLSB8CnwgYC0tY29udHJvbC1wbGFuZS1jb250ZXh0IENU" + + "WGAgfCBrdWJlY3RsIGNvbnRleHQgZm9yIGNvbnRyb2wgcGxhbmUgKFJFUS0yMCkgfCBjdXJyZW50" + + "IGNvbnRleHQgfAp8IGAtLWNvbXB1dGUtcGxhbmUtY29udGV4dCBDVFhgIHwga3ViZWN0bCBjb250" + + "ZXh0IGZvciBjb21wdXRlIHBsYW5lIChSRVEtMjApIHwgY3VycmVudCBjb250ZXh0IHwKfCBgLS1p" + + "Y21zLXVybCBVUkxgIHwgUHVibGljIElDTVMgVVJMOyByZXF1aXJlZCB3aGVuIGNvbnRleHRzIGRp" + + "ZmZlciB8IGRlcml2ZWQgZnJvbSBgYmFzZV9odHRwX3VybGAgfAp8IGAtLWxvY2FsLW9ubHlgIHwg" + + "YGNoZWNrYCBvbmx5OyBydW4gdGhlIGxvY2FsLWhvc3QgY2hlY2tzIGFuZCBza2lwIGFsbCBrdWJl" + + "Y3RsIGNvbnRhY3QsIHdoYXRldmVyIHNjb3BlIGZsYWcgaXMgcGFzc2VkLiBUaGUgQ0xJIHByaW50" + + "cyBhIG5vdGUgc2F5aW5nIHNvLiBFbnY6IGBOVkNGX0NMSV9TRUxGSE9TVEVEX0xPQ0FMX09OTFlg" + + "IHwgYGZhbHNlYCB8CgojIyBgY2hlY2tgLXNwZWNpZmljCgpBdCBsZWFzdCBvbmUgb2YgYC0tcHJl" + + "YCwgYC0tY29udHJvbC1wbGFuZWAsIGAtLWNvbXB1dGUtcGxhbmVgLCBvciBgLS1hbGxgIGlzCnJl" + + "cXVpcmVkLiBFYWNoIHNlbGVjdHMgYSByb2xlOyBvbmx5IHRoZSBzZWxlY3RlZCByb2xlcyBjb250" + + "YWN0IGEgY2x1c3Rlci4KCnwgRmxhZyB8IFB1cnBvc2UgfCBEZWZhdWx0IHwKfC0tLXwtLS18LS0t" + + "fAp8IGAtLXByZWAgfCBQcmUtZmxpZ2h0OiBsb2NhbC1ob3N0IHRvb2xzIHBsdXMgY2x1c3RlciBy" + + "ZWFkaW5lc3MuIFNraXBzIFNJUyByZWFjaGFiaWxpdHksIHNpbmNlIFNJUyBpcyBub3QgaW5zdGFs" + + "bGVkIHlldCwgdW5sZXNzIGAtLWFsbGAgb3IgYC0tY29tcHV0ZS1wbGFuZWAgaXMgYWxzbyBwYXNz" + + "ZWQuIFdpdGggYC0tYWxsYCwgb3Igd2l0aCBhIHJvbGUncyBvd24gZmxhZywgdGhhdCByb2xlJ3Mg" + + "dmFsaWRhdG9yIGNoZWNrcyB0aGUgY2x1c3RlciBhcyBpbnN0YWxsZWQgfCBgZmFsc2VgIHwKfCBg" + + "LS1jb250cm9sLXBsYW5lYCB8IENvbnRyb2wtcGxhbmUgY2hlY2tzIHwgYGZhbHNlYCB8CnwgYC0t" + + "Y29tcHV0ZS1wbGFuZWAgfCBDb21wdXRlLXBsYW5lIGNoZWNrcyB8IGBmYWxzZWAgfAp8IGAtLWFs" + + "bGAgfCBFdmVyeSBjYXRlZ29yeSwgaW5jbHVkaW5nIFNJUyByZWFjaGFiaWxpdHkgd2hlbiBjb21i" + + "aW5lZCB3aXRoIGAtLXByZWAgfCBgZmFsc2VgIHwKfCBgLS1jbHVzdGVyLW5hbWUgTkFNRWAgfCBD" + + "bHVzdGVyIG5hbWUgZm9yIGNvbXB1dGUtcGxhbmUgY2hlY2tzIHwgLSB8CnwgYC0tc2tpcC1pbm90" + + "aWZ5LWNoZWNrYCB8IFNraXAgdGhlIHBlci1ub2RlIGlub3RpZnktbGltaXRzIHByb2JlLiBOZWVk" + + "ZWQgd2hlbiB0aGUga3ViZWNvbmZpZyB1c2VyIGNhbm5vdCBjcmVhdGUgcG9kcyBpbiBgZGVmYXVs" + + "dGAuIEVudjogYE5WQ0ZfQ0xJX1NFTEZIT1NURURfU0tJUF9JTk9USUZZYCB8IGBmYWxzZWAgfAoK" + + "IyMjIENsdXN0ZXItdmFsaWRhdG9yIGZsYWdzCgpUaGUgdmFsaWRhdG9yIHJ1bnMgYXMgYSBKb2Ig" + + "aW4gdGhlIGNsdXN0ZXIgYmVpbmcgY2hlY2tlZC4gVGhlIENMSSBjcmVhdGVzIGEKU2VydmljZUFj" + + "Y291bnQsIENsdXN0ZXJSb2xlLCBhbmQgQ2x1c3RlclJvbGVCaW5kaW5nIGZvciBpdCBhbmQgcmVt" + + "b3ZlcyB0aGVtCmFmdGVyIHRoZSBydW4sIHNvIHRoZSBrdWJlY29uZmlnIGNvbnRleHQgbmVlZHMg" + + "cGVybWlzc2lvbiB0byBtYW5hZ2UgdGhvc2UuCgp8IEZsYWcgfCBQdXJwb3NlIHwgRGVmYXVsdCB8" + + "CnwtLS18LS0tfC0tLXwKfCBgLS1jbHVzdGVyLXZhbGlkYXRvci1pbWFnZSBSRUZgIHwgVmFsaWRh" + + "dG9yIGltYWdlLiBSZXNvbHV0aW9uIG9yZGVyOiBmbGFnLCBgTlZDRl9DTElfQ0xVU1RFUl9WQUxJ" + + "REFUT1JfSU1BR0VgLCBjb25maWcga2V5IGBjbHVzdGVyX3ZhbGlkYXRvcl9pbWFnZWAuIEEgcmVm" + + "IHdpdGggbm8gdGFnIGRpc2NvdmVycyB0aGUgbGF0ZXN0IHN0YWJsZSB0YWcgZnJvbSB0aGUgcmVn" + + "aXN0cnk7IGlmIG5vIHRhZyBjYW4gYmUgZGlzY292ZXJlZCwgdGhlIHByb2JlIGlzIHNraXBwZWQg" + + "d2l0aCBhIG5vdGUgdG8gcGluIG9uZS4gVW5zZXQgZXZlcnl3aGVyZSBza2lwcyB0aGUgcHJvYmUg" + + "d2l0aCBhIHdhcm5pbmcgfCAtIHwKfCBgLS1jbHVzdGVyLXZhbGlkYXRvci1yZWdpc3RyaWVzIGhv" + + "c3Q6cG9ydCwuLi5gIHwgRXh0cmEgcmVnaXN0cmllcyB0aGUgY29udHJvbC1wbGFuZSB2YWxpZGF0" + + "b3IgcHJvYmVzIGZvciByZWFjaGFiaWxpdHkuIFRoZXkgYXJlIGFkZGVkIHRvIHRoZSByZWdpc3Ry" + + "aWVzIHRoZSBpbnN0YWxsIHB1bGxzIGZyb206IHRoZSB2YWxpZGF0b3IgaW1hZ2UncyByZWdpc3Ry" + + "eSwgdGhlIHN0YWNrJ3MgYGdsb2JhbC5pbWFnZS5yZWdpc3RyeWAsIGFuZCBgcXVheS5pb2AgZm9y" + + "IHRoZSBjZXJ0LW1hbmFnZXIgQUNNRSBzb2x2ZXIgdW5sZXNzIHRoZSBzdGFjayBzZXRzIGBjZXJ0" + + "TWFuYWdlci5hY21lc29sdmVyLmltYWdlYC4gYG52Y3IuaW9gIGlzIHByb2JlZCBvbmx5IHdoZW4g" + + "bmVpdGhlciB0aGUgaW1hZ2Ugbm9yIHRoZSBzdGFjayBuYW1lcyBhIHJlZ2lzdHJ5LiBUaGUgdmFs" + + "aWRhdG9yIGRpYWxzIGZyb20gYSBwb2Qgd2l0aCBubyBwcm94eSwgc28gYW4gdW5yZWFjaGFibGUg" + + "cmVnaXN0cnkgaXMgYSB3YXJuaW5nOyB0aGUgbG9jYWwgY3JlZGVudGlhbCBjaGVjayBpcyB3aGF0" + + "IGZhaWxzIGEgcmVnaXN0cnkgdGhlIGluc3RhbGwgY2Fubm90IHB1bGwgZnJvbS4gUmVwZWF0YWJs" + + "ZSBvciBjb21tYS1zZXBhcmF0ZWQuIEVudjogYE5WQ0ZfQ0xJX0NMVVNURVJfVkFMSURBVE9SX1JF" + + "R0lTVFJJRVNgOyBjb25maWcga2V5IGBjbHVzdGVyX3ZhbGlkYXRvcl9yZWdpc3RyaWVzYCB8IC0g" + + "fAp8IGAtLWNsdXN0ZXItdmFsaWRhdG9yLXByb2JlLWltYWdlIFJFRmAgfCBJbWFnZSBmb3IgdGhl" + + "IGNvbnRyb2wtcGxhbmUgdmFsaWRhdG9yJ3Mgbm9kZS10by1ub2RlIG92ZXJsYXkgcHJvYmUuIE5l" + + "ZWRzIGBzaGAgYW5kIGEgYnVzeWJveC1zdHlsZSBgbmNgLiBTZXQgYSBtaXJyb3IgZm9yIGFpci1n" + + "YXBwZWQgY2x1c3RlcnMuIEVudjogYE5WQ0ZfQ0xJX0NMVVNURVJfVkFMSURBVE9SX1BST0JFX0lN" + + "QUdFYDsgY29uZmlnIGtleSBgY2x1c3Rlcl92YWxpZGF0b3JfcHJvYmVfaW1hZ2VgIHwgYGJ1c3li" + + "b3g6MS4zNmAgZnJvbSBEb2NrZXIgSHViIHwKfCBgLS1jbHVzdGVyLXZhbGlkYXRvci1wdWxsLXNl" + + "Y3JldCBOQU1FYCB8IGRvY2tlci1yZWdpc3RyeSBTZWNyZXQgaW4gYGRlZmF1bHRgIHVzZWQgdG8g" + + "cHVsbCB0aGUgdmFsaWRhdG9yIGltYWdlLiBXaGVuIGVtcHR5LCB0aGUgQ0xJIGxvb2tzIGZvciBv" + + "bmUgaW4gdGhlIE5WQ0YgbmFtZXNwYWNlcyBhbmQgY29waWVzIGl0IGludG8gYGRlZmF1bHRgIGZv" + + "ciB0aGUgcnVuLiBGYWlsaW5nIHRoYXQsIGFuZCBvbmx5IGZvciBhbiBpbWFnZSBvbiBhbiBOR0Mg" + + "cmVnaXN0cnksIGl0IG1pbnRzIG9uZSBmcm9tIGBOR0NfQVBJX0tFWWAgfCBhdXRvLWRldGVjdCB8" + + "CnwgYC0tc2tpcC1jbHVzdGVyLXZhbGlkYXRpb25gIHwgU2tpcCB0aGUgaW4tY2x1c3RlciB2YWxp" + + "ZGF0b3IgcHJvYmUgZW50aXJlbHkuIEEgdmFsaWRhdG9yIHRoYXQgaXMgY29uZmlndXJlZCBidXQg" + + "Y2Fubm90IHJ1biBmYWlscyB0aGUgY2hlY2ssIHNvIHVzZSB0aGlzIHRvIG9wdCBvdXQgZXhwbGlj" + + "aXRseSwgZm9yIGV4YW1wbGUgd2hlbiB0aGUgY2x1c3RlciBjYW5ub3QgcHVsbCB0aGUgaW1hZ2Uu" + + "IEVudjogYE5WQ0ZfQ0xJX1NFTEZIT1NURURfU0tJUF9DTFVTVEVSX1ZBTElEQVRJT05gIHwgYGZh" + + "bHNlYCB8CnwgYC0tbm8tY2xlYW51cGAgfCBLZWVwIHRoZSB2YWxpZGF0b3IgSm9iLCBpdHMgcG9k" + + "LCBSQkFDLCBwdWxsIHNlY3JldCBhbmQgQ29uZmlnTWFwIGZvciBkZWJ1Z2dpbmcuIEEgbGF0ZXIg" + + "Y2hlY2sgcmVjbGFpbXMgdGhlbSBhZnRlciAyNCBob3VyczsgdGhlIHJlc3VsdCBwcmludHMgdGhl" + + "IGBrdWJlY3RsIGRlbGV0ZWAgY29tbWFuZCB0aGF0IHJlbW92ZXMgdGhlbSBub3cgfCBgZmFsc2Vg" + + "IHwKfCBgLS1zaG93LWxvZ3NgIHwgUHJpbnQgdGhlIHZhbGlkYXRvciB0cmFuc2NyaXB0IHRvIHN0" + + "ZGVyciBhZnRlciB0aGUgY2hlY2sgZXZlbnRzLiBUaGUgdHJhbnNjcmlwdCBpcyBub3QgSlNPTiwg" + + "YW5kIGAtLWpzb25gIGFsc28gd3JpdGVzIHRvIHN0ZGVyciwgc28gbGVhdmUgdGhpcyBvZmYgd2hl" + + "biBhIHBhcnNlciBpcyByZWFkaW5nIHRoZSBzdHJlYW0gfCBgZmFsc2VgIHwKCiMjIGB1cGAtc3Bl" + + "Y2lmaWMKCnwgRmxhZyB8IFB1cnBvc2UgfCBEZWZhdWx0IHwKfC0tLXwtLS18LS0tfAp8IGAtLWNs" + + "dXN0ZXItbmFtZSBOQU1FYCB8IElDTVMgY2x1c3RlciByb3cgaWRlbnRpZmllciAocmVxdWlyZWQp" + + "IHwgLSB8CnwgYC0tbmNhLWlkIElEYCB8IE5DQSBhY2NvdW50IElEIHwgYG52Y2YtZGVmYXVsdGAg" + + "fAp8IGAtLXJlZ2lvbiBSRUdJT05gIHwgQ2x1c3RlciByZWdpb24gfCBgdXMtd2VzdC0xYCB8Cnwg" + + "YC0tcGxhbi1vbmx5YCB8IERyeS1ydW47IGVtaXQgcGhhc2UgcGxhbiArIEVUQXMgd2l0aG91dCBj" + + "aGFuZ2VzIHwgYGZhbHNlYCB8CgojIyBgc3RhdHVzYC1zcGVjaWZpYwoKfCBGbGFnIHwgUHVycG9z" + + "ZSB8IERlZmF1bHQgfAp8LS0tfC0tLXwtLS18CnwgYC0tY2x1c3Rlci1uYW1lIE5BTUVgIHwgTGlt" + + "aXQgdG8gb25lIGNvbXB1dGUgcGxhbmUgfCAtIChjb250cm9sICsgYWxsIGNsdXN0ZXJzKSB8Cnwg" + + "YC0td2F0Y2hgIHwgTGl2ZSByZS1yZW5kZXIgfCBgZmFsc2VgIHwKfCBgLS13YXRjaC1pbnRlcnZh" + + "bCBEVVJgIHwgYC0td2F0Y2hgIGNhZGVuY2UgfCBgNXNgIHwKfCBgLS1jb21wb25lbnQgTkFNRWAg" + + "fCBGaWx0ZXIgY29tcG9uZW50cyBwYW5lbCB8IGFsbCB8CnwgYC0tbm8tZXZlbnRzYCB8IFNraXAg" + + "ZXZlbnRzIHBhbmVsIHwgYGZhbHNlYCB8CgojIyBgY2x1c3RlciByZWdpc3RlcmAtc3BlY2lmaWMK" + + "CnwgRmxhZyB8IFB1cnBvc2UgfCBEZWZhdWx0IHwKfC0tLXwtLS18LS0tfAp8IGAtLW5hbWUgTkFN" + + "RWAgfCBDbHVzdGVyIG5hbWUgKHJlcXVpcmVkKSB8IC0gfAp8IGAtLW5jYS1pZCBJRGAgfCBOQ0Eg" + + "SUQgKHJlcXVpcmVkKSB8IC0gfAp8IGAtLXJlZ2lvbiBSRUdJT05gIHwgUmVnaW9uIHwgYHVzLXdl" + + "c3QtMWAgfAp8IGAtLWljbXMtdXJsIFVSTGAgfCBJQ01TIGVuZHBvaW50IHwgZnJvbSBjb25maWcg" + + "fAp8IGAtLWlnbm9yZS1leGlzdGluZ2AgfCBNYXRjaC1vci1jcmVhdGUgaW5zdGVhZCBvZiBmYWls" + + "LW9uLWV4aXN0cyB8IGBmYWxzZWAgfAoKIyMgYGFwaS1rZXkgZ2VuZXJhdGVgLXNwZWNpZmljCgp8" + + "IEZsYWcgfCBQdXJwb3NlIHwgRGVmYXVsdCB8CnwtLS18LS0tfC0tLXwKfCBgLS1mb3IgZnVuY3Rp" + + "b25cfHRhc2tgIHwgR2VuZXJhdGUgb25seSB0aGUgc3BlY2lmaWVkIGtleSB0eXBlIHwgKG9taXQg" + + "dG8gZ2VuZXJhdGUgYm90aCkgfAp8IGAtLWRlc2NyaXB0aW9uIFRFWFRgIHwgSHVtYW4tcmVhZGFi" + + "bGUgbGFiZWwgZm9yIHRoZSBrZXkgfCBgR2VuZXJhdGVkIGJ5IG52Y2YtY2xpYCB8CnwgYC0tZXhw" + + "aXJlcy1pbiBEVVJBVElPTmAgfCBFeHBpcnkgb2Zmc2V0IChlLmcuIGAyNGhgLCBgN2RgKTsgcGFy" + + "c2VkIGFzIGV4dGVuZGVkIGR1cmF0aW9uIHwgYDI0aGAgfAp8IGAtLXNjb3BlcyBTQ09QRVssU0NP" + + "UEVdYCB8IE92ZXJyaWRlIGRlZmF1bHQgc2NvcGVzOyByZXF1aXJlcyBgLS1mb3JgIHwgKHNlcnZp" + + "Y2UgZGVmYXVsdHMpIHwKfCBgLS12YWxpZGF0ZWAgfCBWYWxpZGF0ZSBlYWNoIGdlbmVyYXRlZCBr" + + "ZXkgaW1tZWRpYXRlbHkgYWZ0ZXIgbWludGluZyB8IGBmYWxzZWAgfAoKRXh0ZW5kZWQgZHVyYXRp" + + "b24gdW5pdHM6IGBzYCAoc2Vjb25kcyksIGBtYCAobWludXRlcyksIGBoYCAoaG91cnMpLCBgZGAg" + + "KGRheXMpLCBgd2AgKHdlZWtzKS4KCiMjIGBmdW5jdGlvbiBjcmVhdGVgLXNwZWNpZmljCgpgLS1p" + + "bnB1dC1maWxlIEZJTEVgIGlzIHJlY29tbWVuZGVkIGZvciByZXBlYXRhYmxlIGZ1bmN0aW9uIGRl" + + "ZmluaXRpb25zLiBGb3IgYWQtaG9jIGNyZWF0ZXM6Cgp8IEZsYWcgfCBQdXJwb3NlIHwgRGVmYXVs" + + "dCB8CnwtLS18LS0tfC0tLXwKfCBgLS1uYW1lIE5BTUVgIHwgRnVuY3Rpb24gbmFtZSB8IC0gfAp8" + + "IGAtLWltYWdlIElNQUdFYCB8IENvbnRhaW5lciBpbWFnZSB8IC0gfAp8IGAtLWluZmVyZW5jZS11" + + "cmwgVVJJYCB8IENvbnRhaW5lciBpbmZlcmVuY2UgZW5kcG9pbnQgfCAtIHwKfCBgLS1pbmZlcmVu" + + "Y2UtcG9ydCBQT1JUYCB8IENvbnRhaW5lciBpbmZlcmVuY2UgcG9ydCB8IC0gfAp8IGAtLWZ1bmN0" + + "aW9uLXR5cGUgREVGQVVMVFx8U1RSRUFNSU5HXHxMTE1gIHwgRnVuY3Rpb24gdHlwZSB8IGBERUZB" + + "VUxUYCB8CnwgYC0taGVsbS1jaGFydCBDSEFSVGAgfCBIZWxtIGNoYXJ0IFVSTCBvciBPQ0kgcmVm" + + "ZXJlbmNlLiBDYW4gYmUgdXNlZCB3aXRoIGAtLWZ1bmN0aW9uLXR5cGU9TExNYCBmb3IgY2hhcnQt" + + "cGFja2FnZWQgTExNIHdvcmtsb2Fkcy4gfCAtIHwKfCBgLS1oZWxtLWNoYXJ0LXNlcnZpY2UgTkFN" + + "RWAgfCBLdWJlcm5ldGVzIFNlcnZpY2UgbmFtZSBleHBvc2VkIGJ5IHRoZSBjaGFydC4gUmVxdWly" + + "ZWQgd2hlbiBgLS1oZWxtLWNoYXJ0YCBpcyBzZXQuIHwgLSB8CnwgYC0tbW9kZWxzIE5BTUU6VkVS" + + "U0lPTjpVUklgIHwgU3RhbmRhcmQgbW9kZWwgYXJ0aWZhY3Q7IHJlcGVhdGFibGUgfCAtIHwKfCBg" + + "LS1sbG0tbW9kZWwgU1BFQ2AgfCBMTE0gbW9kZWwgY29uZmlnOyBmb3JtYXQgYG5hbWU9PG1vZGVs" + + "Pix1cmlzPTx1cmk+XHw8dXJpPixyb3V0aW5nTWV0aG9kPTxtZXRob2Q+LHRva2VuUmF0ZUxpbWl0" + + "PTxsaW1pdD5gOyByZXBlYXRhYmxlLiBUb2tlbiBsaW1pdHMgdXNlIGA8dmFsdWU+LTx1bml0PmAg" + + "d2l0aCBgU2AsIGBNYCwgYEhgLCBgRGAsIG9yIGBXYCwgZm9yIGV4YW1wbGUgYDEwMDAtU2AuIFVz" + + "ZSBpbnB1dCBKU09OIGZvciBjb21iaW5lZCB0b2tlbiBsaW1pdHMgYmVjYXVzZSBpbmxpbmUgc3Bl" + + "Y3MgdXNlIGNvbW1hcyBhcyBmaWVsZCBzZXBhcmF0b3JzLiB8IC0gfAp8IGAtLWxsbS1kZWZhdWx0" + + "LXByaW9yaXR5IFBSSU9SSVRZYCB8IEZ1bmN0aW9uLWxldmVsIGRlZmF1bHQgcmVxdWVzdCBwcmlv" + + "cml0eS4gTG93ZXIgdmFsdWVzIGhhdmUgaGlnaGVyIHByaW9yaXR5LiB8IC0gfAp8IGAtLWxsbS1w" + + "ZXItYWNjb3VudC1wcmlvcml0eSBOQ0EtSUQ6UFJJT1JJVFlgIHwgUGVyLWFjY291bnQgcHJpb3Jp" + + "dHkgb3ZlcnJpZGU7IHJlcGVhdGFibGU7IHN1cHBvcnRzIHVwIHRvIDY0IGRpc3RpbmN0IE5DQSBJ" + + "RCBvdmVycmlkZXMuIFJlcXVpcmVzIGEgZGVmYXVsdCBwcmlvcml0eS4gfCAtIHwKCkluIEpTT04g" + + "YW5kIGlubGluZSBzcGVjcywgTExNIGZ1bmN0aW9ucyBzZXQgYGZ1bmN0aW9uVHlwZTogIkxMTSJg" + + "IGFuZCBtb2RlbCByb3V0aW5nIG1ldGFkYXRhIHVuZGVyIGBtb2RlbHNbXS5sbG1Db25maWdgLiBg" + + "bGxtQ29uZmlnLnVyaXNgIGRlY2xhcmVzIHRoZSBPcGVuQUktY29tcGF0aWJsZSB1cHN0cmVhbSBw" + + "YXRocyBleHBvc2VkIGJ5IHRoZSBtb2RlbC4gQ3VycmVudCBzdXBwb3J0ZWQgcGF0aHMgYXJlIGAv" + + "djEvY2hhdC9jb21wbGV0aW9uc2AsIGAvdjEvcmVzcG9uc2VzYCwgYW5kIGAvdjEvZW1iZWRkaW5n" + + "c2AuIGBsbG1Db25maWcucm91dGluZ01ldGhvZGAgYWNjZXB0cyBgcm91bmRfcm9iaW5gLCBgcG93" + + "ZXJfb2ZfdHdvYCwgYGdyb3FfbXVsdGlyZWdpb25gLCBgcHVsc2FyYCwgb3IgYHJhbmRvbWAuCmBs" + + "bG1Db25maWcudG9rZW5SYXRlTGltaXRgIGFjY2VwdHMgb25lIG9yIG1vcmUgY29tbWEtc2VwYXJh" + + "dGVkIHBvc2l0aXZlIGludGVnZXIgdG9rZW4gbGltaXRzIGluIGA8dmFsdWU+LTx1bml0PmAgZm9y" + + "bWF0LiBTdXBwb3J0ZWQgdW5pdHMgYXJlIGBTYCAoc2Vjb25kcyksIGBNYCAobWludXRlcyksIGBI" + + "YCAoaG91cnMpLCBgRGAgKGRheXMpLCBhbmQgYFdgICh3ZWVrcykuIFVzZSBkaXN0aW5jdCB1bml0" + + "cyB3aGVuIGNvbWJpbmluZyBsaW1pdHMsIGZvciBleGFtcGxlIGAxMDAwLVMsNTAwMC1NLDEwMDAw" + + "MC1ILDUwMDAwMC1ELDEwMDAwMDAtV2AgaW4gaW5wdXQgSlNPTi4KCkhlbG0gY2hhcnQgcGFja2Fn" + + "aW5nIGlzIGluZGVwZW5kZW50IG9mIGBmdW5jdGlvblR5cGVgLiBGb3IgYSBIZWxtLWNoYXJ0IGJh" + + "Y2tlZCBMTE0gZnVuY3Rpb24sIHNldCBgZnVuY3Rpb25UeXBlOiAiTExNImAsIGBoZWxtQ2hhcnRg" + + "LCBgaGVsbUNoYXJ0U2VydmljZU5hbWVgLCBhbmQgYG1vZGVsc1tdLmxsbUNvbmZpZ2AgaW4gdGhl" + + "IHNhbWUgY3JlYXRlIHJlcXVlc3QuIGBpbmZlcmVuY2VQb3J0YCBtdXN0IGJlIHRoZSBLdWJlcm5l" + + "dGVzIFNlcnZpY2UgcG9ydCBleHBvc2VkIGJ5IGBoZWxtQ2hhcnRTZXJ2aWNlTmFtZWAuCgpMTE0g" + + "aW52b2NhdGlvbiByZXF1ZXN0cyB1c2UgYG1vZGVsOiAiPGZ1bmN0aW9uLWlkPi88bW9kZWwtbmFt" + + "ZT4iYC4gVGhlIGZ1bmN0aW9uIElEIHNlbGVjdHMgdGhlIE5WQ0YgZnVuY3Rpb24sIGFuZCB0aGUg" + + "bW9kZWwgbmFtZSBpcyBmb3J3YXJkZWQgdXBzdHJlYW0uIENoYXQgY29tcGxldGlvbnMgYW5kIFJl" + + "c3BvbnNlcyBBUEkgcmVxdWVzdHMgY2FuIHVzZSBgeC1tdWx0aS10dXJuLXNlc3Npb24taWRgIGZv" + + "ciBzZXNzaW9uIHN0aWNraW5lc3M7IGVtYmVkZGluZ3MgcmVxdWVzdHMgZG8gbm90LgoKIyMgYGZ1" + + "bmN0aW9uIHVwZGF0ZWAtc3BlY2lmaWMKCnwgRmxhZyB8IFB1cnBvc2UgfCBEZWZhdWx0IHwKfC0t" + + "LXwtLS18LS0tfAp8IGAtLXRhZ3MgVEFHWyxUQUddYCB8IFJlcGxhY2UgZnVuY3Rpb24gdGFncyB8" + + "IC0gfAp8IGAtLWxsbS1tb2RlbC11cGRhdGUgU1BFQ2AgfCBMTE0gbW9kZWwgdXBkYXRlOyBmb3Jt" + + "YXQgYG5hbWU9PG1vZGVsPixyb3V0aW5nTWV0aG9kPTxtZXRob2Q+LHRva2VuUmF0ZUxpbWl0PTxs" + + "aW1pdD5gOyByZXBlYXRhYmxlLiBSb3V0aW5nIG1ldGhvZHMgbWF0Y2ggYC0tbGxtLW1vZGVsYC4g" + + "VG9rZW4gbGltaXQgZXhhbXBsZTogYDEwMDAtU2AuIFVzZSBpbnB1dCBKU09OIGZvciBjb21iaW5l" + + "ZCB0b2tlbiBsaW1pdHMuIHwgLSB8CnwgYC0tbGxtLWRlZmF1bHQtcHJpb3JpdHkgUFJJT1JJVFlg" + + "IHwgUmVwbGFjZSB0aGUgZnVuY3Rpb24tbGV2ZWwgcHJpb3JpdHkgY29uZmlndXJhdGlvbiB3aXRo" + + "IHRoaXMgZGVmYXVsdCBhbmQgYW55IHN1cHBsaWVkIHBlci1hY2NvdW50IG92ZXJyaWRlcy4gfCAt" + + "IHwKfCBgLS1sbG0tcGVyLWFjY291bnQtcHJpb3JpdHkgTkNBLUlEOlBSSU9SSVRZYCB8IFBlci1h" + + "Y2NvdW50IHByaW9yaXR5IG92ZXJyaWRlOyByZXBlYXRhYmxlOyBzdXBwb3J0cyB1cCB0byA2NCBk" + + "aXN0aW5jdCBOQ0EgSUQgb3ZlcnJpZGVzLiBSZXF1aXJlcyBhIGRlZmF1bHQgcHJpb3JpdHkuIHwg" + + "LSB8CgpJbiBKU09OLCBgZnVuY3Rpb24gdXBkYXRlYCBhY2NlcHRzIGBtb2RlbFVwZGF0ZXNbXWAg" + + "ZW50cmllcyB3aXRoIGBtb2RlbE5hbWVgIGFuZCBgbGxtQ29uZmlnLnJvdXRpbmdNZXRob2RgIGFu" + + "ZC9vciBgbGxtQ29uZmlnLnRva2VuUmF0ZUxpbWl0YC4gYHVyaXNgIGFyZSBjcmVhdGUtdGltZSBt" + + "b2RlbCBtZXRhZGF0YSBhbmQgYXJlIG5vdCBwYXJ0IG9mIG1vZGVsIHVwZGF0ZXMuCgojIyBgZnVu" + + "Y3Rpb24gZGVwbG95IGNyZWF0ZWAtc3BlY2lmaWMKCmAtLWlucHV0LWZpbGUgRklMRWAgaXMgcmVj" + + "b21tZW5kZWQgKGZ1bGwgSlNPTiBzcGVjKS4gRm9yIGFkLWhvYyBvbmUtc2hvdCB1c2U6Cgp8IEZs" + + "YWcgfCBQdXJwb3NlIHwgRGVmYXVsdCB8CnwtLS18LS0tfC0tLXwKfCBgLS1mdW5jdGlvbi1pZCBJ" + + "RGAgfCBGdW5jdGlvbiBJRCAocmVxdWlyZWQpIHwgLSB8CnwgYC0tZ3B1IE5BTUVgIHwgR1BVIGZh" + + "bWlseSB8IGBIMTAwYCB8CnwgYC0taW5zdGFuY2UtdHlwZSBUWVBFYCB8IFNLVSB8IGBOQ1AuR1BV" + + "LkgxMDBfMXhgIHwKfCBgLS1taW4taW5zdGFuY2VzIE5gIHwgTWluIHJlcGxpY2FzIHwgYDFgIHwK" + + "fCBgLS1tYXgtaW5zdGFuY2VzIE5gIHwgTWF4IHJlcGxpY2FzIHwgYDFgIHwKfCBgLS1tYXgtcmVx" + + "dWVzdC1jb25jdXJyZW5jeSBOYCB8IENvbmN1cnJlbmN5IHwgYDBgICh1bnNldDsgc2VydmVyIGRl" + + "ZmF1bHQgYXBwbGllcykgfAp8IGAtLWNsdXN0ZXJzIE5BTUVbLE5BTUVdYCB8IFRhcmdldCBzcGVj" + + "aWZpYyBjb21wdXRlIGNsdXN0ZXJzIHwgKGFueSBtYXRjaGluZyBTS1UpIHwKCiMjIGB0YXNrIGNy" + + "ZWF0ZWAtc3BlY2lmaWMKCmAtLWlucHV0LWZpbGUgRklMRWAgaXMgcmVjb21tZW5kZWQgZm9yIHJl" + + "cGVhdGFibGUgdGFzayBkZWZpbml0aW9ucy4gRm9yIGFkLWhvYyB1c2U6Cgp8IEZsYWcgfCBQdXJw" + + "b3NlIHwgTm90ZXMgfAp8LS0tfC0tLXwtLS18CnwgYC0tbmFtZSBOQU1FYCB8IFRhc2sgbmFtZSAo" + + "cmVxdWlyZWQpIHwgMS0xMjggY2hhcnMsIGBeW2EtejAtOUEtWl1bYS16MC05QS1aXC1fXSokYCB8" + + "CnwgYC0tZ3B1IEdQVWAgfCBHUFUgbmFtZSAocmVxdWlyZWQgd2l0aG91dCBgLS1pbnB1dC1maWxl" + + "YCkgfCBFeGFtcGxlOiBgSDEwMGAgfAp8IGAtLWluc3RhbmNlLXR5cGUgVFlQRWAgfCBJbnN0YW5j" + + "ZSB0eXBlIChyZXF1aXJlZCB3aXRob3V0IGAtLWlucHV0LWZpbGVgKSB8IEV4YW1wbGU6IGBOQ1Au" + + "R1BVLkgxMDBfMXhgIHwKfCBgLS1pbWFnZSBJTUFHRWAgfCBDb250YWluZXIgaW1hZ2UgfCBNdXR1" + + "YWxseSBleGNsdXNpdmUgd2l0aCBgLS1oZWxtLWNoYXJ0YCB8CnwgYC0taGVsbS1jaGFydCBDSEFS" + + "VGAgfCBIZWxtIGNoYXJ0IFVSTCBvciBPQ0kgcmVmZXJlbmNlIHwgTXV0dWFsbHkgZXhjbHVzaXZl" + + "IHdpdGggYC0taW1hZ2VgIHwKfCBgLS1pbnB1dC1maWxlIEZJTEVgIHwgSlNPTiB0YXNrIHNwZWMg" + + "ZmlsZSB8IENMSSBmbGFncyBvdmVycmlkZSBKU09OIHZhbHVlcyB3aGVuIGJvdGggYXJlIHNldCB8" + + "CnwgYC0tYmFja2VuZCBCQUNLRU5EYCB8IEJhY2tlbmQgLyBDU1AgfCAtIHwKfCBgLS1jbHVzdGVy" + + "cyBOQU1FWyxOQU1FXWAgfCBTcGVjaWZpYyBjbHVzdGVycyB3aXRoaW4gdGhlIGluc3RhbmNlIHwg" + + "Q29tbWEtc2VwYXJhdGVkIHwKfCBgLS1jb250YWluZXItYXJncyBBUkdTYCB8IEFyZ3MgcGFzc2Vk" + + "IHdoZW4gbGF1bmNoaW5nIHRoZSBjb250YWluZXIgfCBTaW5nbGUgc3RyaW5nLCBlLmcuIGAiLS1l" + + "cG9jaHMgMTAgLS1iYXRjaC1zaXplIDMyImAgfAp8IGAtLWNvbnRhaW5lci1lbnYgTkFNRT12YWx1" + + "ZWAgfCBDb250YWluZXIgZW52aXJvbm1lbnQgdmFyaWFibGU7IHJlcGVhdGFibGUgfCAtIHwKfCBg" + + "LS1zZWNyZXRzIE5BTUU9dmFsdWVgIHwgU2VjcmV0IGVudmlyb25tZW50IHZhcmlhYmxlOyByZXBl" + + "YXRhYmxlOyBlbmNyeXB0ZWQgYXQgcmVzdCB8IC0gfAp8IGAtLW1heC1ydW50aW1lIERVUkFUSU9O" + + "YCB8IE1heCB3YWxsLWNsb2NrIHJ1biB0aW1lIChJU08gODYwMSwgZS5nLiBgUFQ0SGApIHwgTm8g" + + "ZGVmYXVsdDsgb21pdCBmb3Igbm8gdGltZSBsaW1pdCB8CnwgYC0tbWF4LXF1ZXVlZCBEVVJBVElP" + + "TmAgfCBNYXggdGltZSBpbiBxdWV1ZSBiZWZvcmUgY2FuY2VsbGF0aW9uIChJU08gODYwMSkgfCBT" + + "ZXJ2ZXIgZGVmYXVsdCBgUFQ3MkhgIHwKfCBgLS10ZXJtaW5hdGlvbi1ncmFjZSBEVVJBVElPTmAg" + + "fCBHcmFjZSBwZXJpb2QgYWZ0ZXIgc3RvcCBzaWduYWwgKElTTyA4NjAxKSB8IFNlcnZlciBkZWZh" + + "dWx0IGBQVDFIYCB8CnwgYC0tcmVzdWx0LXN0cmF0ZWd5IFVQTE9BRFx8Tk9ORWAgfCBIb3cgdG8g" + + "aGFuZGxlIHRhc2sgb3V0cHV0IGFydGlmYWN0cyB8IC0gfAp8IGAtLXJlc3VsdHMtbG9jYXRpb24g" + + "TE9DQVRJT05gIHwgUmVzdWx0IHVwbG9hZCB0YXJnZXQsIGZvcm1hdCBgb3JnLW5hbWUvW3RlYW0t" + + "bmFtZS9dbW9kZWwtbmFtZWA7IHJlcXVpcmVkIHdoZW4gYC0tcmVzdWx0LXN0cmF0ZWd5PVVQTE9B" + + "RGAgfCAtIHwKfCBgLS1tb2RlbHMgTkFNRTpWRVJTSU9OOlVSSWAgfCBNb2RlbCBhcnRpZmFjdDsg" + + "cmVwZWF0YWJsZSB8IC0gfAp8IGAtLXJlc291cmNlcyBOQU1FOlZFUlNJT046VVJJYCB8IFJlc291" + + "cmNlIGFydGlmYWN0OyByZXBlYXRhYmxlIHwgLSB8CnwgYC0tdGFncyBUQUdbLFRBR11gIHwgVGFz" + + "ayB0YWdzIHwgQ29tbWEtc2VwYXJhdGVkIHwKfCBgLS1kZXNjcmlwdGlvbiBURVhUYCB8IFRhc2sg" + + "ZGVzY3JpcHRpb24gfCAtIHwKfCBgLS1sb2dzLXRlbGVtZXRyeS1pZCBVVUlEYCB8IExvZ3MgdGVs" + + "ZW1ldHJ5IGVuZHBvaW50IElEIHwgLSB8CnwgYC0tbWV0cmljcy10ZWxlbWV0cnktaWQgVVVJRGAg" + + "fCBNZXRyaWNzIHRlbGVtZXRyeSBlbmRwb2ludCBJRCB8IC0gfAp8IGAtLXRyYWNlcy10ZWxlbWV0" + + "cnktaWQgVVVJRGAgfCBUcmFjZXMgdGVsZW1ldHJ5IGVuZHBvaW50IElEIHwgLSB8ClBLBwhbBfsn" + + "tTQAALU0AABQSwMEFAAIAAAAAAAhAAAAAAAAAAAAAAAAADcACQBkYXRhL252Y2Ytc2VsZi1tYW5h" + + "Z2VkLWNsaS9yZWZlcmVuY2UvdHJvdWJsZXNob290aW5nLm1kVVQFAAEAps4SIyBUcm91Ymxlc2hv" + + "b3RpbmcKCktub3duIGVycm9ycyDihpIgZGlhZ25vc3RpYyBjb21tYW5kIOKGkiByZW1lZGlhdGlv" + + "bi4gS2VlcCBpbiBzeW5jIHdpdGggdGhlIHN0cnVjdHVyZWQgYHBoYXNlX2ZhaWxlZGAgZXZlbnRz" + + "IHRoZSBDTEkgZW1pdHMgKFJFUS0xNSkg4oCUIGV2ZXJ5IGVudHJ5IGJlbG93IHNob3VsZCBtYXAg" + + "dG8gYW4gYGVyckNhdGVnb3J5YCBhbmQgYSBgcmVtZWRpYXRpb25gIGFycmF5LgoKIyMgUHJlLWZs" + + "aWdodAoKfCBTeW1wdG9tIHwgQ2F1c2UgfCBSZW1lZGlhdGlvbiB8CnwtLS18LS0tfC0tLXwKfCBg" + + "a3ViZWN0bCBub3Qgb24gUEFUSGAgfCBPcGVyYXRvcidzIGAkUEFUSGAgZG9lc24ndCBpbmNsdWRl" + + "IGt1YmVjdGwgfCBJbnN0YWxsIGt1YmVjdGwgMS4yOCs6IGh0dHBzOi8va3ViZXJuZXRlcy5pby9k" + + "b2NzL3Rhc2tzL3Rvb2xzL2luc3RhbGwta3ViZWN0bC8gfAp8IGBoZWxtZmlsZSBub3Qgb24gUEFU" + + "SGAgfCBTYW1lIHwgaHR0cHM6Ly9naXRodWIuY29tL2hlbG1maWxlL2hlbG1maWxlI2luc3RhbGxh" + + "dGlvbiB8CnwgYGhlbG0gbm90IG9uIFBBVEhgIHwgU2FtZSB8IGh0dHBzOi8vaGVsbS5zaC9kb2Nz" + + "L2ludHJvL2luc3RhbGwvIHwKfCBgR2F0ZXdheSBBUEkgQ1JEcyBub3QgaW5zdGFsbGVkYCB8IGVu" + + "dm95LWdhdGV3YXkgcHJlcmVxIG1pc3NpbmcgfCBgaGVsbSBpbnN0YWxsIGVnIG9jaTovL2RvY2tl" + + "ci5pby9lbnZveXByb3h5L2dhdGV3YXktaGVsbSAtLW5hbWVzcGFjZT1lbnZveS1nYXRld2F5LXN5" + + "c3RlbSAtLWNyZWF0ZS1uYW1lc3BhY2VgIHwKfCBgZGVmYXVsdCBTdG9yYWdlQ2xhc3Mgbm90IGF2" + + "YWlsYWJsZWAgfCBDbHVzdGVyIGhhcyBubyBkZWZhdWx0IFNDIHwgQW5ub3RhdGUgb25lOiBga3Vi" + + "ZWN0bCBwYXRjaCBzYyA8bmFtZT4gLXAgJ3sibWV0YWRhdGEiOnsiYW5ub3RhdGlvbnMiOnsic3Rv" + + "cmFnZWNsYXNzLmt1YmVybmV0ZXMuaW8vaXMtZGVmYXVsdC1jbGFzcyI6InRydWUifX19J2AgfAp8" + + "IGBHUFUgb3BlcmF0b3Igbm90IGluc3RhbGxlZGAgKGNvbXB1dGUtcGxhbmUpIHwgbnZpZGlhIEdQ" + + "VSBvcGVyYXRvciBtaXNzaW5nIHwgSW5zdGFsbCBwZXIgaHR0cHM6Ly9kb2NzLm52aWRpYS5jb20v" + + "ZGF0YWNlbnRlci9jbG91ZC1uYXRpdmUvZ3B1LW9wZXJhdG9yL2xhdGVzdC9pbnN0YWxsLWdwdS1v" + + "cGVyYXRvci5odG1sLCBPUiB1c2UgYGZha2UtZ3B1LW9wZXJhdG9yYCBmb3IgZGV2IHwKfCBgR1BV" + + "IG5vZGUgbGFiZWxzIG1pc3NpbmdgIChjb21wdXRlLXBsYW5lKSB8IE5vZGVzIG5lZWQgYG52aWRp" + + "YS5jb20vZ3B1LmZhbWlseWAgZXRjLiB8IFNldCBgLS1rM3Mtbm9kZS1sYWJlbCBudmlkaWEuY29t" + + "L2dwdS5mYW1pbHk9aG9wcGVyQGFnZW50Ok5gIGZvciBrM2QgfAoKIyMgQXV0aAoKfCBTeW1wdG9t" + + "IHwgQ2F1c2UgfCBSZW1lZGlhdGlvbiB8CnwtLS18LS0tfC0tLXwKfCBgYWRtaW4gdG9rZW4gcmVx" + + "dWlyZWRgIChpbml0IG5vdCBydW4pIHwgTm8gYH4vLm52Y2YtY2xpLnN0YXRlYCBhbmQgYC0tdG9r" + + "ZW5gIG5vdCBwYXNzZWQgfCBgbnZjZi1jbGkgaW5pdGAgKGludGVyYWN0aXZlKSBvciBgLS10b2tl" + + "bj0kSldUIC0tbm9uLWludGVyYWN0aXZlYCAoQ0kpIHwKfCBgU2lnbmVkIEpXVCByZWplY3RlZDog" + + "QW5vdGhlciBhbGdvcml0aG0gZXhwZWN0ZWQsIG9yIG5vIG1hdGNoaW5nIGtleShzKSBmb3VuZGAg" + + "fCBTdGFsZSBzZXNzaW9uLmpzb24gZnJvbSBhIGRpZmZlcmVudCBjb250cm9sIHBsYW5lIHwgYHJt" + + "IH4vLm52Y2YtY2xpLnN0YXRlYCB0aGVuIGBudmNmLWNsaSBpbml0YCBhZ2FpbiB8CnwgYDQwMyBt" + + "aXNzaW5nIHJlcXVlc3RlZCBhdXRob3JpdGllc2AgKGZ1bmN0aW9uIGludm9rZSkgfCBBZG1pbiB0" + + "b2tlbiBkb2Vzbid0IGhhdmUgYGludm9rZV9mdW5jdGlvbmAgc2NvcGUgfCBgbnZjZi1jbGkgYXBp" + + "LWtleSBnZW5lcmF0ZSAtLWRlc2NyaXB0aW9uPeKApmAgdGhlbiByZS1ydW4gaW52b2tlIHwKCiMj" + + "IEluc3RhbGwgLyBgdXBgCgp8IFN5bXB0b20gfCBDYXVzZSB8IFJlbWVkaWF0aW9uIHwKfC0tLXwt" + + "LS18LS0tfAp8IEhlbG0gYHRpbWVkIG91dCB3YWl0aW5nIGZvciBjYXNzYW5kcmFgIHwgQ2Fzc2Fu" + + "ZHJhIFN0YXRlZnVsU2V0IFBWQyBzdHVjayBgUGVuZGluZ2AgfCBDb25maXJtIGRlZmF1bHQgU3Rv" + + "cmFnZUNsYXNzOyBjaGVjayBga3ViZWN0bCBnZXQgcHYscHZjIC1uIGNhc3NhbmRyYS1zeXN0ZW1g" + + "IHwKfCBgSW52YWxpZCBHUFUgJ05DUC5HUFUuSDEwMCcgc3BlY2lmaWVkYCAoZnVuY3Rpb24gZGVw" + + "bG95KSB8IFdyb25nIGZpZWxkIOKAlCBgZ3B1YCBpcyB0aGUgZmFtaWx5LCBub3QgdGhlIFNLVSB8" + + "IFVzZSBgImdwdSI6ICJIMTAwImAsIGAiaW5zdGFuY2VUeXBlIjogIk5DUC5HUFUuSDEwMF8xeCJg" + + "IHwKfCBgcmVnaW9uIG11c3QgYmUgcHJvdmlkZWRgIChjbHVzdGVyIHJlZ2lzdGVyIC8gaW5zdGFs" + + "bCAtLWNvbXB1dGUtcGxhbmUpIHwgYC0tcmVnaW9uYCBlbXB0eSArIGBDTFVTVEVSX1JFR0lPTmAg" + + "ZW52IGVtcHR5IHwgUGFzcyBgLS1yZWdpb249dXMtd2VzdC0xYCAob3IgYW55IG5vbi1lbXB0eSB2" + + "YWx1ZSkgfAp8IGByZXF1aXJlZEVudiAiTkNBX0lEIiBpcyBub3Qgc2V0YCAoaGVsbWZpbGUgZXJy" + + "b3IgZHVyaW5nIGNvbXB1dGUtcGxhbmUgYXBwbHkpIHwgT2xkZXIgbnZjZi1jbGkgYmVmb3JlIGNv" + + "bW1pdCBgNmFhNTcwNGA7IEV4dHJhRW52IG5vdCBwcm9wYWdhdGluZyBOQ0FfSUQgfCBVcGdyYWRl" + + "IGBudmNmLWNsaWAgdG8gYSByZWxlYXNlIGFmdGVyIGA2YWE1NzA0YCB8CnwgYG52Y2Etb3BlcmF0" + + "b3I6IEltYWdlUHVsbEJhY2tPZmZgIGZvciBgbnZjYS1vcGVyYXRvcjpwc2F0YCB8IEltYWdlIG5v" + + "dCBpbiBjbHVzdGVyIHJlZ2lzdHJ5OyBtdWx0aS1jbHVzdGVyIGJ1aWxkIGlzIGxvY2FsIHwgYGsz" + "ZCBpbWFnZSBpbXBvcnQgbnZjci5pby8uLi4vbnZjYS1vcGVyYXRvcjpwc2F0IC1jIDxjbHVzdGVy" + - "PmAgKG9yIHdoYXRldmVyIHlvdXIgaW1hZ2UgcHVzaCBwYXRoIGlzKSB8CnwgYHJlcXVpcmVkRW52" + - "ICJOQ0FfSUQiIGlzIG5vdCBzZXRgIChoZWxtZmlsZSBlcnJvcikgfCBPbGRlciBudmNmLWNsaTsg" + - "dGhlIGVudiBwcm9wYWdhdGlvbiBmaXggbGFuZGVkIGluIE0rNSBmb2xsb3ctdXBzIHwgVXBncmFk" + - "ZSBgbnZjZi1jbGlgIHRvIGEgdmVyc2lvbiBhZnRlciBgNmFhNTcwNGAgfAoKIyMgNC4gV2hlbiB0" + - "aGUgdXNlciB3YW50cyB0byBzdGFydCBvdmVyCgpJZiB0aGV5IGNvbmZpcm0gYSB0ZWFyZG93bjoK" + - "LSBTaW5nbGUtY2x1c3RlcjogYGNkIG52Y2Ytc2VsZi1tYW5hZ2VkLXN0YWNrICYmIG1ha2UgZGVz" + - "dHJveWAgKG9yIGBoZWxtIHVuaW5zdGFsbGAgcGVyIHJlbGVhc2UgaW4gcmV2ZXJzZSBvcmRlciku" + - "Ci0gTXVsdGktY2x1c3RlcjogYGhlbG0gdW5pbnN0YWxsIG52Y2Etb3BlcmF0b3IgLW4gbnZjYS1v" + - "cGVyYXRvcmAgb24gdGhlIGNvbXB1dGUgcGxhbmUgRklSU1Q7IHRoZW4gY29udHJvbCBwbGFuZTsg" + - "dGhlbiBgbnZjZi1jbGkgY2x1c3RlciBkZWxldGUgLS1jbHVzdGVyLWlkPTxpZD5gIHRvIHJlbW92" + - "ZSB0aGUgSUNNUyByb3cuCgpBbHdheXMgY29uZmlybSBiZWZvcmUgYW55IHRlYXJkb3duIOKAlCBD" + - "YXNzYW5kcmEgU3RhdGVmdWxTZXQgUFZDIGRlbGV0aW9uIGlzIGRlc3RydWN0aXZlIGFuZCBsb3Nl" + - "cyBjbHVzdGVyX29pZGMgcm93cy4KCiMjIFdoYXQgbm90IHRvIGRvCgotICoqRG9uJ3QgcHJvcG9z" + - "ZSBgLS1kZWJ1Z2AgYXMgYSBmaXguKiogSXQncyBkaWFnbm9zdGljIG9ubHkuCi0gKipEb24ndCBw" + - "cm9wb3NlIGBoZWxtIHVuaW5zdGFsbGAqKiB1bmxlc3MgdGhlIHVzZXIgZXhwbGljaXRseSBhc2tl" + - "ZCB0byBzdGFydCBvdmVyLgotICoqRG9uJ3QgcHJvcG9zZSBga3ViZWN0bCBkZWxldGUgcHZjYCoq" + - "IGV2ZXIgd2l0aG91dCBjb25maXJtYXRpb24g4oCUIGl0IGRlbGV0ZXMgdGhlIHVuZGVybHlpbmcg" + - "UFYuCi0gKipEb24ndCByZS1ydW4gYHVwYCB3aXRoIGRpZmZlcmVudCBpbnB1dHMqKiB0byAic2Vl" + - "IHdoYXQgaGFwcGVucyIuIERpYWdub3NlIHdpdGggYHN0YXR1c2AgLyBga3ViZWN0bCBkZXNjcmli" + - "ZWAgZmlyc3QuClBLBwgOnD+XvQ4AAL0OAABQSwMEFAAIAAAAAAAhAAAAAAAAAAAAAAAAADoACQBk" + - "YXRhL252Y2Ytc2VsZi1tYW5hZ2VkLWNsaS9wcm9tcHRzL2luc3RhbGwtZnJvbS1zY3JhdGNoLm1k" + - "VVQFAAEAps4SIyBJbnN0YWxsIHNlbGYtaG9zdGVkIE5WQ0YgZnJvbSBzY3JhdGNoCgpVc2VyIHdh" + - "bnRzIHRvIGJyaW5nIHVwIHNlbGYtaG9zdGVkIE5WQ0Ygb24gYSBmcmVzaCBLdWJlcm5ldGVzIGNs" + - "dXN0ZXIgKG9yIGszZCBmb3IgbG9jYWwgZGV2KS4gVGhpcyBpcyB0aGUgbW9zdCBjb21tb24gZW50" + - "cnlwb2ludC4KCiMjIFN0ZXBzCgoxLiBDb25maXJtIHRoZSB0b3BvbG9neS4gQXNrIHRoZSB1c2Vy" + - "OgogICAtICJTaW5nbGUtY2x1c3RlciAoY29udHJvbCBwbGFuZSBhbmQgY29tcHV0ZSBwbGFuZSBv" + - "biBvbmUgY2x1c3Rlciwgc2ltcGxlcikgb3Igc3BsaXQgKGNvbnRyb2wgcGxhbmUgb24gY2x1c3Rl" + - "ciBBLCBjb21wdXRlIHBsYW5lIG9uIGNsdXN0ZXIgQiwgcHJvZHVjdGlvbi1zaGFwZWQpPyIKICAg" + - "LSAiV2hhdCdzIHRoZSBjbHVzdGVyIG5hbWUgKHRoZSBgLS1jbHVzdGVyLW5hbWVgIGZsYWcsIGJl" + - "Y29tZXMgdGhlIElDTVMgcm93IGlkZW50aWZpZXIpPyBFeGFtcGxlczogYG5jcC1sb2NhbGAgZm9y" + - "IGxvY2FsIGRldiwgYHByb2QtdXMtZWFzdC0xYCBmb3IgcHJvZHVjdGlvbi4iCiAgIC0gRm9yIHNw" + - "bGl0OiAiQ29udHJvbC1wbGFuZSBrdWJlY29uZmlnIGNvbnRleHQgbmFtZT8gQ29tcHV0ZS1wbGFu" + - "ZSBjb250ZXh0PyBQdWJsaWMgSUNNUyBVUkw/IgoKMi4gUHJlcGFyZSByZW1vdGUgR2F0ZXdheSBh" + - "bmQgQ0xJIGNvbmZpZyBpZiB0aGlzIGlzIG5vdCBsb2NhbCBrM2QuIE9uZS1jbGljawogICBhcHBs" + - "aWVzIHRoZSBjb250cm9sIHBsYW5lLCB0aGVuIGltbWVkaWF0ZWx5IGNhbGxzIEFQSSwgQVBJIEtl" + - "eXMsIGludm9jYXRpb24sCiAgIGFuZCBnUlBDIGVuZHBvaW50cy4gRm9yIHJlbW90ZSBjbHVzdGVy" + - "cywgdGhlIEdhdGV3YXkgbXVzdCBiZSBwcm9ncmFtbWVkIGFuZAogICB0aGUgQ0xJIGNvbmZpZyBt" + - "dXN0IHBvaW50IGF0IHRoZSBHYXRld2F5IGxvYWQgYmFsYW5jZXIgYmVmb3JlIGB1cGAgcnVucy4K" + - "CiAgIElmIEdhdGV3YXkgQVBJIGluZ3Jlc3MgaXMgbm90IGFscmVhZHkgcHJlc2VudCwgY3JlYXRl" + - "IGl0IGZpcnN0LiBUaGVzZSBhcmUgdGhlCiAgIHNoYXJlZCBHYXRld2F5IHNldHVwIHN0ZXBzIHVz" + - "ZWQgYnkgb25lLWNsaWNrLCBIZWxtZmlsZSwgYW5kIHN0YW5kYWxvbmUgY2hhcnQKICAgaW5zdGFs" + - "bCBwYXRoczoKCiAgIGBgYHNoCiAgIGt1YmVjdGwgYXBwbHkgLWYgaHR0cHM6Ly9naXRodWIuY29t" + - "L2t1YmVybmV0ZXMtc2lncy9nYXRld2F5LWFwaS9yZWxlYXNlcy9kb3dubG9hZC92MS4yLjAvZXhw" + - "ZXJpbWVudGFsLWluc3RhbGwueWFtbAoKICAgZm9yIG5hbWVzcGFjZSBpbiBlbnZveS1nYXRld2F5" + - "LXN5c3RlbSBlbnZveS1nYXRld2F5IGFwaS1rZXlzIGVzcyBzaXMgbnZjZjsgZG8KICAgICBrdWJl" + - "Y3RsIGNyZWF0ZSBuYW1lc3BhY2UgIiRuYW1lc3BhY2UiIC0tZHJ5LXJ1bj1jbGllbnQgLW8geWFt" + - "bCB8IGt1YmVjdGwgYXBwbHkgLWYgLQogICBkb25lCgogICBmb3IgbmFtZXNwYWNlIGluIGVudm95" + - "LWdhdGV3YXkgYXBpLWtleXMgZXNzIHNpcyBudmNmOyBkbwogICAgIGt1YmVjdGwgbGFiZWwgbmFt" + - "ZXNwYWNlICIkbmFtZXNwYWNlIiBudmNmL3BsYXRmb3JtPXRydWUgLS1vdmVyd3JpdGUKICAgZG9u" + - "ZQoKICAgaGVsbSB1cGdyYWRlIC0taW5zdGFsbCBlZyBvY2k6Ly9kb2NrZXIuaW8vZW52b3lwcm94" + - "eS9nYXRld2F5LWhlbG0gXAogICAgIC0tdmVyc2lvbiB2MS4xLjMgXAogICAgIC1uIGVudm95LWdh" + - "dGV3YXktc3lzdGVtCiAgIGBgYAoKICAgQmVmb3JlIGFwcGx5aW5nIHRoZSBgRW52b3lQcm94eWAs" + - "IGRldGVybWluZSB3aGljaCBTZXJ2aWNlIGNvbnRyb2xsZXIgb3ducwogICBsb2FkIGJhbGFuY2Vy" + - "cyBvbiBFS1MuIERvIG5vdCBhcHBseSB0aGUgbWFuaWZlc3QgdW50aWwgeW91IGhhdmUgc2VsZWN0" + - "ZWQgdGhlCiAgIG1hdGNoaW5nIGBlbnZveVNlcnZpY2VgIGNvbmZpZ3VyYXRpb24uIFVzZSB0aGUg" + - "RUtTIEFQSSBhbmQgdGhlIGluLWNsdXN0ZXIKICAgY29udHJvbGxlciBEZXBsb3ltZW50IGluc3Rl" + - "YWQgb2YgZ3Vlc3NpbmcgZnJvbSB0aGUgY2x1c3RlciBhZ2Ugb3IgZXhpc3RpbmcKICAgU2Vydmlj" + - "ZXM6CgogICBgYGBzaAogICBleHBvcnQgRUtTX0NMVVNURVJfTkFNRT0iPGNsdXN0ZXItbmFtZT4i" + - "CgogICBhd3MgZWtzIGRlc2NyaWJlLWNsdXN0ZXIgLS1uYW1lICIkRUtTX0NMVVNURVJfTkFNRSIg" + - "XAogICAgIC0tcXVlcnkgJ2NsdXN0ZXIua3ViZXJuZXRlc05ldHdvcmtDb25maWcuZWxhc3RpY0xv" + - "YWRCYWxhbmNpbmcuZW5hYmxlZCcgXAogICAgIC0tb3V0cHV0IHRleHQKICAga3ViZWN0bCAtbiBr" + - "dWJlLXN5c3RlbSBnZXQgZGVwbG95bWVudCBhd3MtbG9hZC1iYWxhbmNlci1jb250cm9sbGVyCiAg" + - "IGBgYAoKICAgQW4gRUtTIEFQSSByZXN1bHQgb2YgYFRydWVgIHNlbGVjdHMKICAgW0VLUyBBdXRv" + - "IE1vZGVdKGh0dHBzOi8vZG9jcy5hd3MuYW1hem9uLmNvbS9la3MvbGF0ZXN0L3VzZXJndWlkZS9h" + - "dXRvLWNvbmZpZ3VyZS1ubGIuaHRtbCkuCiAgIE90aGVyd2lzZSwgYSBzdWNjZXNzZnVsIERlcGxv" + - "eW1lbnQgbG9va3VwIHNlbGVjdHMgdGhlIEFXUyBMb2FkIEJhbGFuY2VyCiAgIENvbnRyb2xsZXIu" + - "IElmIG5laXRoZXIgaXMgcHJlc2VudCwgY29uZmlybSB0aGF0IHRoZSBjbHVzdGVyIGludGVudGlv" + - "bmFsbHkKICAgdXNlcyB0aGUgbGVnYWN5IEFXUyBjbG91ZCBwcm92aWRlciBTZXJ2aWNlIGNvbnRy" + - "b2xsZXIsIG9yIGluc3RhbGwgdGhlIEFXUwogICBMb2FkIEJhbGFuY2VyIENvbnRyb2xsZXIsIGJl" + - "Zm9yZSBhcHBseWluZyB0aGUgZXhhbXBsZS4KCiAgIFRoZSBhcHBsaWVkIG1hbmlmZXN0IGJlbG93" + - "IGlzIGZvciB0aGUgQVdTIExvYWQgQmFsYW5jZXIgQ29udHJvbGxlci4gRm9yIEVLUwogICBBdXRv" + - "IE1vZGUsIHJlcGxhY2UgaXRzIGBlbnZveVNlcnZpY2VgIG1hcCBiZWZvcmUgYXBwbHlpbmcgaXQg" + - "d2l0aDoKCiAgIGBgYHlhbWwKICAgZW52b3lTZXJ2aWNlOgogICAgIGxvYWRCYWxhbmNlckNsYXNz" + - "OiBla3MuYW1hem9uYXdzLmNvbS9ubGIKICAgICBhbm5vdGF0aW9uczoKICAgICAgIHNlcnZpY2Uu" + - "YmV0YS5rdWJlcm5ldGVzLmlvL2F3cy1sb2FkLWJhbGFuY2VyLXNjaGVtZTogImludGVybmV0LWZh" + - "Y2luZyIKICAgYGBgCgogICBGb3IgdGhlIGxlZ2FjeSBBV1MgY2xvdWQgcHJvdmlkZXIgU2Vydmlj" + - "ZSBjb250cm9sbGVyLCByZXBsYWNlIHRoZSBtYXAgd2l0aDoKCiAgIGBgYHlhbWwKICAgZW52b3lT" + - "ZXJ2aWNlOgogICAgIGFubm90YXRpb25zOgogICAgICAgc2VydmljZS5iZXRhLmt1YmVybmV0ZXMu" + - "aW8vYXdzLWxvYWQtYmFsYW5jZXItdHlwZTogIm5sYiIKICAgYGBgCgogICBUaGUgbGVnYWN5IGNv" + - "bnRyb2xsZXIgY3JlYXRlcyBhbiBpbnRlcm5ldC1mYWNpbmcgbG9hZCBiYWxhbmNlciBieSBkZWZh" + - "dWx0LgogICBEbyBub3QgY29tYmluZSB0aGUgbGVnYWN5IGBubGJgIHNlbGVjdG9yIHdpdGggdGhl" + - "IEFXUyBMb2FkIEJhbGFuY2VyCiAgIENvbnRyb2xsZXIgdGFyZ2V0IHR5cGUgYW5kIHNjaGVtZSBh" + - "bm5vdGF0aW9ucy4gSXQgc3VwcG9ydHMgYW4gTkxCIFNlcnZpY2UKICAgd2hvc2UgcG9ydHMgdXNl" + - "IG9ubHkgVENQIG9yIG9ubHkgVURQLCBidXQgcmVqZWN0cyBvbmUgU2VydmljZSB0aGF0IGNvbWJp" + - "bmVzCiAgIFRDUCBhbmQgVURQIHBvcnRzLiBVc2UgdGhlIEFXUyBMb2FkIEJhbGFuY2VyIENvbnRy" + - "b2xsZXIgd2hlbiB0aGUgZ2VuZXJhdGVkCiAgIEVudm95IFNlcnZpY2UgY29tYmluZXMgcHJvdG9j" + - "b2xzLiBPbiBub24tQVdTIGNsdXN0ZXJzLCByZXBsYWNlIHRoZSBtYXAgd2l0aAogICB0aGUgcHJv" + - "dmlkZXIncyBTZXJ2aWNlIGNvbmZpZ3VyYXRpb24gYmVmb3JlIGFwcGx5aW5nIGl0LgoKICAgYGBg" + - "c2gKICAgIyBOb24tRUtTLUF1dG8tTW9kZSBleGFtcGxlOiB1c2UgQVdTIExCQyB3aXRoIGluc3Rh" + - "bmNlIHRhcmdldHMuCiAgIGt1YmVjdGwgYXBwbHkgLWYgLSA8PEVPRgogICBhcGlWZXJzaW9uOiBn" + - "YXRld2F5LmVudm95cHJveHkuaW8vdjFhbHBoYTEKICAga2luZDogRW52b3lQcm94eQogICBtZXRh" + - "ZGF0YToKICAgICBuYW1lOiBlZwogICAgIG5hbWVzcGFjZTogZW52b3ktZ2F0ZXdheS1zeXN0ZW0K" + - "ICAgc3BlYzoKICAgICBwcm92aWRlcjoKICAgICAgIHR5cGU6IEt1YmVybmV0ZXMKICAgICAgIGt1" + - "YmVybmV0ZXM6CiAgICAgICAgIGVudm95U2VydmljZToKICAgICAgICAgICBhbm5vdGF0aW9uczoK" + - "ICAgICAgICAgICAgIHNlcnZpY2UuYmV0YS5rdWJlcm5ldGVzLmlvL2F3cy1sb2FkLWJhbGFuY2Vy" + - "LXR5cGU6ICJleHRlcm5hbCIKICAgICAgICAgICAgIHNlcnZpY2UuYmV0YS5rdWJlcm5ldGVzLmlv" + - "L2F3cy1sb2FkLWJhbGFuY2VyLW5sYi10YXJnZXQtdHlwZTogImluc3RhbmNlIgogICAgICAgICAg" + - "ICAgc2VydmljZS5iZXRhLmt1YmVybmV0ZXMuaW8vYXdzLWxvYWQtYmFsYW5jZXItc2NoZW1lOiAi" + - "aW50ZXJuZXQtZmFjaW5nIgogICAtLS0KICAgYXBpVmVyc2lvbjogZ2F0ZXdheS5uZXR3b3JraW5n" + - "Lms4cy5pby92MQogICBraW5kOiBHYXRld2F5Q2xhc3MKICAgbWV0YWRhdGE6CiAgICAgbmFtZTog" + - "ZWcKICAgc3BlYzoKICAgICBjb250cm9sbGVyTmFtZTogZ2F0ZXdheS5lbnZveXByb3h5LmlvL2dh" + - "dGV3YXljbGFzcy1jb250cm9sbGVyCiAgICAgcGFyYW1ldGVyc1JlZjoKICAgICAgIGdyb3VwOiBn" + - "YXRld2F5LmVudm95cHJveHkuaW8KICAgICAgIGtpbmQ6IEVudm95UHJveHkKICAgICAgIG5hbWU6" + - "IGVnCiAgICAgICBuYW1lc3BhY2U6IGVudm95LWdhdGV3YXktc3lzdGVtCiAgIEVPRgoKICAga3Vi" + - "ZWN0bCBhcHBseSAtZiAtIDw8RU9GCiAgIGFwaVZlcnNpb246IGdhdGV3YXkubmV0d29ya2luZy5r" + - "OHMuaW8vdjEKICAga2luZDogR2F0ZXdheQogICBtZXRhZGF0YToKICAgICBuYW1lOiBudmNmLWdh" + - "dGV3YXkKICAgICBuYW1lc3BhY2U6IGVudm95LWdhdGV3YXkKICAgc3BlYzoKICAgICBnYXRld2F5" + - "Q2xhc3NOYW1lOiBlZwogICAgIGxpc3RlbmVyczoKICAgICAtIG5hbWU6IGh0dHAKICAgICAgIHBy" + - "b3RvY29sOiBIVFRQCiAgICAgICBwb3J0OiA4MAogICAgICAgYWxsb3dlZFJvdXRlczoKICAgICAg" + - "ICAgbmFtZXNwYWNlczoKICAgICAgICAgICBmcm9tOiBTZWxlY3RvcgogICAgICAgICAgIHNlbGVj" + - "dG9yOgogICAgICAgICAgICAgbWF0Y2hMYWJlbHM6CiAgICAgICAgICAgICAgIG52Y2YvcGxhdGZv" + - "cm06ICJ0cnVlIgogICAgIC0gbmFtZTogdGNwCiAgICAgICBwcm90b2NvbDogVENQCiAgICAgICBw" + - "b3J0OiAxMDA4MQogICAgICAgYWxsb3dlZFJvdXRlczoKICAgICAgICAgbmFtZXNwYWNlczoKICAg" + - "ICAgICAgICBmcm9tOiBTZWxlY3RvcgogICAgICAgICAgIHNlbGVjdG9yOgogICAgICAgICAgICAg" + - "bWF0Y2hMYWJlbHM6CiAgICAgICAgICAgICAgIG52Y2YvcGxhdGZvcm06ICJ0cnVlIgogICBFT0YK" + - "ICAgYGBgCgogICBTZXJ2aWNlIGFubm90YXRpb25zIG11c3QgYmUgdW5kZXIKICAgYEVudm95UHJv" + - "eHkuc3BlYy5wcm92aWRlci5rdWJlcm5ldGVzLmVudm95U2VydmljZS5hbm5vdGF0aW9uc2AuIEVu" + - "dm95IEdhdGV3YXkKICAgY29waWVzIHRoZW0gdG8gdGhlIGdlbmVyYXRlZCBFbnZveSBTZXJ2aWNl" + - "LiBEbyBub3QgcHV0IFNlcnZpY2UgYW5ub3RhdGlvbnMKICAgb24gdGhlIGBHYXRld2F5YCByZXNv" + - "dXJjZS4KCiAgIENhcHR1cmUgdGhlIEdhdGV3YXkgdmFsdWVzIGFuZCB3cml0ZSB0aGUgQ0xJIGNv" + - "bmZpZzoKCiAgIGBgYHNoCiAgIGV4cG9ydCBDTFVTVEVSX05BTUU9Im52Y2YtcmVtb3RlIgogICBl" + - "eHBvcnQgTkNBX0lEPSJudmNmLWRlZmF1bHQiCiAgIGV4cG9ydCBSRUdJT049InVzLXdlc3QtMSIK" + - "ICAgZXhwb3J0IEhUVFBfR0FURVdBWV9OQU1FU1BBQ0U9ImVudm95LWdhdGV3YXkiCiAgIGV4cG9y" + - "dCBIVFRQX0dBVEVXQVlfTkFNRT0ibnZjZi1nYXRld2F5IgogICBleHBvcnQgR1JQQ19HQVRFV0FZ" + - "X05BTUVTUEFDRT0iZW52b3ktZ2F0ZXdheSIKICAgZXhwb3J0IEdSUENfR0FURVdBWV9OQU1FPSJu" + - "dmNmLWdhdGV3YXkiCgogICBrdWJlY3RsIC1uICIkSFRUUF9HQVRFV0FZX05BTUVTUEFDRSIgd2Fp" + - "dCAiZ2F0ZXdheS8kSFRUUF9HQVRFV0FZX05BTUUiIFwKICAgICAtLWZvcj1jb25kaXRpb249UHJv" + - "Z3JhbW1lZD1UcnVlIC0tdGltZW91dD0xMG0KICAga3ViZWN0bCAtbiAiJEdSUENfR0FURVdBWV9O" + - "QU1FU1BBQ0UiIHdhaXQgImdhdGV3YXkvJEdSUENfR0FURVdBWV9OQU1FIiBcCiAgICAgLS1mb3I9" + - "Y29uZGl0aW9uPVByb2dyYW1tZWQ9VHJ1ZSAtLXRpbWVvdXQ9MTBtCgogICBleHBvcnQgR0FURVdB" + - "WV9BRERSPSIkKGt1YmVjdGwgLW4gIiRIVFRQX0dBVEVXQVlfTkFNRVNQQUNFIiBnZXQgImdhdGV3" + - "YXkvJEhUVFBfR0FURVdBWV9OQU1FIiBcCiAgICAgLW8ganNvbnBhdGg9J3suc3RhdHVzLmFkZHJl" + - "c3Nlc1swXS52YWx1ZX0nKSIKICAgZXhwb3J0IEdSUENfR0FURVdBWV9BRERSPSIkKGt1YmVjdGwg" + - "LW4gIiRHUlBDX0dBVEVXQVlfTkFNRVNQQUNFIiBnZXQgImdhdGV3YXkvJEdSUENfR0FURVdBWV9O" + - "QU1FIiBcCiAgICAgLW8ganNvbnBhdGg9J3suc3RhdHVzLmFkZHJlc3Nlc1swXS52YWx1ZX0nKSIK" + - "ICAgdGVzdCAtbiAiJEdBVEVXQVlfQUREUiIgfHwgZXhpdCAxCiAgIHRlc3QgLW4gIiRHUlBDX0dB" + - "VEVXQVlfQUREUiIgfHwgZXhpdCAxCiAgIGV4cG9ydCBTVEFDS19ET01BSU49IiRHQVRFV0FZX0FE" + - "RFIiCiAgIGBgYAoKICAgU2V0IGBTVEFDS19ET01BSU5gIHRvIHRoZSBob3N0bmFtZSBzdWZmaXgg" + - "dXNlZCBieSB0aGUgaW5zdGFsbGVkIEhUVFBSb3V0ZXMuCiAgIEZvciByZW1vdGUgdGVzdCBmbG93" + - "cyB3aXRob3V0IHByb2R1Y3Rpb24gRE5TLCB1c2UgdGhlIEdhdGV3YXkgbG9hZCBiYWxhbmNlcgog" + - "ICBhZGRyZXNzLgoKICAgYGBgc2gKICAgY2F0ID4gLm52Y2YtY2xpLnlhbWwgPDxFT0YKICAgYmFz" + - "ZV9odHRwX3VybDogImh0dHA6Ly8ke0dBVEVXQVlfQUREUn0iCiAgIGludm9rZV91cmw6ICJodHRw" + - "Oi8vJHtHQVRFV0FZX0FERFJ9IgogICBiYXNlX2dycGNfdXJsOiAiJHtHUlBDX0dBVEVXQVlfQURE" + - "Un06MTAwODEiCiAgIGFwaV9rZXlzX3NlcnZpY2VfdXJsOiAiaHR0cDovLyR7R0FURVdBWV9BRERS" + - "fSIKCiAgIGFwaV9rZXlzX2hvc3Q6ICJhcGkta2V5cy4ke1NUQUNLX0RPTUFJTn0iCiAgIGFwaV9o" + - "b3N0OiAiYXBpLiR7U1RBQ0tfRE9NQUlOfSIKICAgaW52b2tlX2hvc3Q6ICJpbnZvY2F0aW9uLiR7" + - "U1RBQ0tfRE9NQUlOfSIKCiAgIGFwaV9rZXlzX3NlcnZpY2VfaWQ6ICJudmlkaWEtY2xvdWQtZnVu" + - "Y3Rpb25zLW5jcC1zZXJ2aWNlLWlkLWFrZXRtIgogICBhcGlfa2V5c19pc3N1ZXJfc2VydmljZTog" + - "Im52Y2YtYXBpIgogICBhcGlfa2V5c19vd25lcl9pZDogInN2Y0BudmNmLWFwaS5sb2NhbCIKCiAg" + - "IGNsaWVudF9pZDogIiR7TkNBX0lEfSIKICAgRU9GCiAgIGBgYAoKMy4gUnVuIHByZS1mbGlnaHQu" + - "IENob29zZSB0aGUgZmxhdm9yIG1hdGNoaW5nIHRoZSB0b3BvbG9neToKCiAgIGBgYHNoCiAgICMg" + - "U2luZ2xlLWNsdXN0ZXIKICAgbnZjZi1jbGkgc2VsZi1ob3N0ZWQgY2hlY2sgLS1wcmUgLS1qc29u" + - "IHwganEgLWMgLgoKICAgIyBTcGxpdAogICBudmNmLWNsaSBzZWxmLWhvc3RlZCBjaGVjayAtLXBy" + - "ZSBcCiAgICAgLS1jb250cm9sLXBsYW5lLWNvbnRleHQ9YWRtaW5AY3AgXAogICAgIC0tY29tcHV0" + - "ZS1wbGFuZS1jb250ZXh0PWFkbWluQGdwdTEgXAogICAgIC0tanNvbiB8IGpxIC1jIC4KCiAgICMg" + - "Q29tcHV0ZS1vbmx5IChvcGVyYXRvciB3aXRoIGt1YmVjdGwgb24gY29tcHV0ZSBwbGFuZSBvbmx5" + - "KQogICBudmNmLWNsaSBzZWxmLWhvc3RlZCBjaGVjayAtLXByZSBcCiAgICAgLS1jb21wdXRlLXBs" + - "YW5lLWNvbnRleHQ9YWRtaW5AZ3B1MSBcCiAgICAgLS1pY21zLXVybD1odHRwczovL2ljbXMubnZj" + - "Zi5leGFtcGxlLmNvbSBcCiAgICAgLS1qc29uIHwganEgLWMgLgogICBgYGAKCiAgIFBhcnNlIHRo" + - "ZSBKU09OTCBzdHJlYW0uIElmIGFueSBjaGVjayBmYWlscyAoYCJwYXNzZWQiOmZhbHNlYCkgd2l0" + - "aCBgc2V2ZXJpdHk6IGVycm9yYCwgc3VyZmFjZSBgbWVzc2FnZWAgKyBgaGludFVSTGAgdG8gdGhl" + - "IHVzZXIgYW5kIHN0b3AuIERvbid0IHByb2NlZWQgdG8gaW5zdGFsbCB3aXRoIGJyb2tlbiBwcmVy" + - "ZXFzLgoKNC4gTWludCBhbiBhZG1pbiB0b2tlbiBpZiBuZWVkZWQuIGBudmNmLWNsaSBpbml0YCBp" + - "cyBpZGVtcG90ZW50LiBDYWxsIGl0IGlmIHRoZQogICB1c2VyIGRvZXMgbm90IGhhdmUgYSBzZXNz" + - "aW9uIHlldC4gSW5pdCB0YWxrcyB0byBBUEkgS2V5cyB0aHJvdWdoIHRoZSBwdWJsaWMKICAgQVBJ" + - "IGdhdGV3YXksIHNvIGl0IGFsc28gd29ya3MgaW4gY29tcHV0ZS1vbmx5IG1vZGUuCgo1LiBSdW4g" + - "YHVwYC4gVXNlIGAtLWpzb25gIGZvciB0aGUgSlNPTkwgZXZlbnQgc3RyZWFtOgoKICAgYGBgc2gK" + - "ICAgbnZjZi1jbGkgc2VsZi1ob3N0ZWQgdXAgXAogICAgIC0tY2x1c3Rlci1uYW1lPSIke0NMVVNU" + - "RVJfTkFNRX0iIFwKICAgICAtLW5jYS1pZD0iJHtOQ0FfSUR9IiBcCiAgICAgLS1yZWdpb249IiR7" + - "UkVHSU9OfSIgXAogICAgIC0tanNvbgogICBgYGAKCiAgIEZvciBzcGxpdC1jbHVzdGVyIGluc3Rh" + - "bGxzLCBhZGQgYm90aCBjb250ZXh0IGZsYWdzOgoKICAgYGBgc2gKICAgZXhwb3J0IENPTlRST0xf" + - "UExBTkVfQ09OVEVYVD0iYWRtaW5AY29udHJvbC1wbGFuZSIKICAgZXhwb3J0IENPTVBVVEVfUExB" + - "TkVfQ09OVEVYVD0iYWRtaW5AZ3B1LWNsdXN0ZXIiCgogICBudmNmLWNsaSBzZWxmLWhvc3RlZCB1" + - "cCBcCiAgICAgLS1jb250cm9sLXBsYW5lLWNvbnRleHQ9IiR7Q09OVFJPTF9QTEFORV9DT05URVhU" + - "fSIgXAogICAgIC0tY29tcHV0ZS1wbGFuZS1jb250ZXh0PSIke0NPTVBVVEVfUExBTkVfQ09OVEVY" + - "VH0iIFwKICAgICAtLWNsdXN0ZXItbmFtZT0iJHtDTFVTVEVSX05BTUV9IiBcCiAgICAgLS1uY2Et" + - "aWQ9IiR7TkNBX0lEfSIgXAogICAgIC0tcmVnaW9uPSIke1JFR0lPTn0iIFwKICAgICAtLWpzb24K" + - "ICAgYGBgCgogICBQYXJzZSBldmVudHM6CiAgIC0gYHBoYXNlX3N0YXJ0ZWRgIC8gYHBoYXNlX2Nv" + - "bXBsZXRlZGA6IGxvZyBwcm9ncmVzcwogICAtIGBwaGFzZV9wcm9ncmVzc2A6IHNob3cgcHJvZ3Jl" + - "c3MgZm9yIHRoZSBsb25nIGFwcGx5IHBoYXNlcyAocmVzb3VyY2UgY291bnRzKQogICAtIGB3YWl0" + - "aW5nYDogc3VyZmFjZSB0byB0aGUgdXNlciBpZiBpdCBwZXJzaXN0cyBmb3IgbW9yZSB0aGFuIDIg" + - "bWludXRlcwogICAtIGBwaGFzZV9mYWlsZWRgOiBzdG9wIGFuZCBzdXJmYWNlIGBlcnJNZXNzYWdl" + - "YCBwbHVzIGVhY2ggYHJlbWVkaWF0aW9uYCBsaW5lLgogICAgIERlY2lkZSBiYXNlZCBvbiBgcmV0" + - "cnlDbGFzc2A6IGBpbW1lZGlhdGVgIC0+IG1heSByZS1ydW4gbm93IHdpdGggdXNlcgogICAgIGFw" + - "cHJvdmFsOyBgYmFja29mZmAgLT4gd2FpdCBgcmV0cnlBZnRlclNlY2AgYW5kIHRoZW4gcmUtcnVu" + - "IHdpdGggdXNlcgogICAgIGFwcHJvdmFsOyBgYWZ0ZXJfcmVtZWRpYXRpb25gLCBgbm9uZWAsIG9y" + - "IGB1bmtub3duYCAtPiB0aGUgb3BlcmF0b3IgbXVzdAogICAgIGFjdCBmaXJzdCwgc28gZG8gbm90" + - "IGF1dG8tcmV0cnkuCiAgIC0gYGZpbmFsYDogZG9uZS4gUHJpbnQgY2x1c3RlcklkIGFuZCBOVkNG" + - "QmFja2VuZCBoZWFsdGguCgo2LiBWZXJpZnkgd2l0aCBzdGF0dXMuIFJ1biBgbnZjZi1jbGkgc2Vs" + - "Zi1ob3N0ZWQgc3RhdHVzIC0tanNvbiB8IGpxYCBhbmQgZXhwZWN0CiAgIGB2ZXJkaWN0OiAiaGVh" + - "bHRoeSJgLiBPdGhlcndpc2UsIHVzZQogICBbZGlhZ25vc2UtZmFpbGVkLWluc3RhbGwubWRdKGRp" + - "YWdub3NlLWZhaWxlZC1pbnN0YWxsLm1kKS4KCjcuIE9wdGlvbmFsbHkgc21va2UgdGVzdCBhIGZ1" + - "bmN0aW9uLiBVc2UKICAgW2RlcGxveS1hbmQtaW52b2tlLm1kXShkZXBsb3ktYW5kLWludm9rZS5t" + - "ZCkgZm9yIHRoZSBjcmVhdGUgLT4gZGVwbG95IC0+CiAgIGludm9rZSBmbG93LgoKIyMgTm90ZXMK" + - "Ci0gU2luZ2xlLWNsdXN0ZXIgYHVwYCBhZ2FpbnN0IGEgZnJlc2ggY2x1c3RlciB0YWtlcyBhYm91" + - "dCAxMC0xMyBtaW51dGVzLAogIGRlcGVuZGluZyBvbiBjaGFydCBwdWxsIHNwZWVkIGFuZCBOQVRT" + - "IHN0cmVhbS1pbml0IGxhdGVuY3kuCi0gYHVwYCBpcyBpZGVtcG90ZW50LiBJdCBpcyBzYWZlIHRv" + - "IHJlLXJ1biBhZnRlciBmaXhpbmcgYSBwcmVyZXF1aXNpdGUuCi0gTmV2ZXIgcHJvcG9zZSBgLS1m" + - "b3JjZWAgKG5vIGNvbW1hbmQgdGFrZXMgb25lIGFueXdheSkuCi0gSWYgdGhlIHVzZXIgaGFzIENJ" + - "IC8gYCRDSWAgc2V0LCBhbHdheXMgdXNlIGAtLW5vbi1pbnRlcmFjdGl2ZSAtLXRva2VuPSRKV1Rg" + - "OyBuZXZlciBwcm9wb3NlIGBudmNmLWNsaSBpbml0YCBpbnRlcmFjdGl2ZWx5LgpQSwcIQnHmHgEo" + - "AAABKAAAUEsDBBQACAAAAAAAIQAAAAAAAAAAAAAAAAA5AAkAZGF0YS9udmNmLXNlbGYtbWFuYWdl" + - "ZC1jbGkvcHJvbXB0cy9yb3RhdGUtY2x1c3Rlci1qd2tzLm1kVVQFAAEAps4SIyBSb3RhdGUgYSBj" + - "bHVzdGVyJ3MgSldLUwoKVXNlciBzYXlzIE5WQ0Egc3RvcHBlZCBhdXRoZW50aWNhdGluZywgb3Ig" + - "UFNBVCBhdXRoIGlzIDQwMS1pbmcgYWdhaW5zdCBJQ01TLiBUaGUgSzhzIEFQSSBzZXJ2ZXIncyBK" + - "V1Qgc2lnbmluZyBrZXlzIG1heSBoYXZlIHJvdGF0ZWQgYW5kIHRoZSBKV0tTIHN0b3JlZCBpbiBJ" + - "Q01TIGlzIG5vdyBzdGFsZS4KCiMjIFdoZW4gdG8gcm90YXRlCgotIGBrdWJlY3RsIGxvZ3MgLW4g" + - "bnZjYS1zeXN0ZW0gLWwgYXBwLmt1YmVybmV0ZXMuaW8vbmFtZT1udmNhYCBzaG93cyBgSFRUUCA0" + - "MDFgIG9uIGAvdjEvbnZjYS9jbHVzdGVycy88aWQ+L3JlZ2lzdGVyYCByZXBlYXRlZGx5Ci0gSUNN" + - "UyBsb2dzIHNob3cgYE5vIGNsdXN0ZXIgZm91bmQgd2l0aCB2YWxpZCBKV0tTIGZvciBjbHVzdGVy" + - "IElEOiA8aWQ+YCAoY2F1c2VkIGJ5IGlzc3VlciBtaXNtYXRjaCwgbm90IEpXS1MsIGJ1dCByb3Rh" + - "dGUgZml4ZXMgYm90aCkKLSBBZnRlciBhIGNvbnRyb2wtcGxhbmUgSzhzIHVwZ3JhZGUgb3IgY29t" + - "cHV0ZS1wbGFuZSBLOHMgdXBncmFkZQoKIyMgU3RlcHMKCjEuIENvbmZpcm0gdGhlIGNsdXN0ZXIg" + - "SUQuIFJ1biBgbnZjZi1jbGkgY2x1c3RlciBsaXN0LXJlZ2lzdGVyZWQgLS1uY2EtaWQ9JE5DQV9J" + - "RCAtLWljbXMtdXJsPSRJQ01TYC4gSWRlbnRpZnkgdGhlIHJvdyBtYXRjaGluZyB0aGUgdXNlcidz" + - "IGNvbXB1dGUgcGxhbmUuCgoyLiAqKlJvdGF0ZS4qKiBGcm9tIGEgY29udGV4dCB0aGF0IGNhbiBy" + - "ZWFjaCB0aGUgY29tcHV0ZSBwbGFuZSdzIEs4cyBBUEkgKGJlY2F1c2Ugcm90YXRpb24gcmUtZmV0" + - "Y2hlcyB0aGUgSzhzIEFQSSdzIGAvb3BlbmlkL3YxL2p3a3NgKToKCiAgIGBgYHNoCiAgIG52Y2Yt" + - "Y2xpIGNsdXN0ZXIgcm90YXRlIC0tY2x1c3Rlci1pZD08aWQ+IC0tY29tcHV0ZS1wbGFuZS1jb250" + - "ZXh0PTxjdHg+CiAgIGBgYAoKICAgVGhpcyByZS1mZXRjaGVzIEpXS1MgZnJvbSB0aGUgY29tcHV0" + - "ZSBwbGFuZSdzIEs4cyBBUEkgYW5kIFBVVHMgaXQgdG8gSUNNUy4gVGhlIGNsdXN0ZXIgSUQgc3Rh" + - "eXMgdGhlIHNhbWUuCgozLiAqKlZlcmlmeS4qKiBgbnZjZi1jbGkgY2x1c3RlciBnZXQgLS1jbHVz" + - "dGVyLWlkPTxpZD5gIOKAlCBjb25maXJtIGB1cGRhdGVkQXRgIGFkdmFuY2VkLiBUaGVuIGBrdWJl" + - "Y3RsIGxvZ3MgLW4gbnZjYS1zeXN0ZW0gLWwgYXBwLmt1YmVybmV0ZXMuaW8vbmFtZT1udmNhIC0t" + - "dGFpbD0yMGAg4oCUIHJlZ2lzdGVyIGF0dGVtcHRzIHNob3VsZCBzdWNjZWVkICgyMDAsIG5vdCA0" + - "MDEpLgoKIyMgV2hhdCBub3QgdG8gZG8KCi0gKipEb24ndCBgY2x1c3RlciBkZWxldGVgLioqIFJv" + - "dGF0aW9uIHByZXNlcnZlcyB0aGUgY2x1c3RlciBJRCArIGdyb3VwIElEOyBkZWxldGUgY3JlYXRl" + - "cyBhIG5ldyByb3cgYW5kIHlvdSBoYXZlIHRvIHJlZGVwbG95IHRoZSBjb21wdXRlIHBsYW5lIHdp" + - "dGggbmV3IElEcy4KLSAqKkRvbid0IG1hbnVhbGx5IGVkaXQgYG91dC88Y2x1c3Rlcj4tcmVnaXN0" + - "ZXItdmFsdWVzLnlhbWxgLioqIFJvdGF0ZSB1cGRhdGVzIElDTVMgb25seTsgdGhlIGhlbG0gdmFs" + - "dWVzIGZpbGUgYWxyZWFkeSBoYXMgdGhlIHJpZ2h0IGNsdXN0ZXIgSUQuCgojIyBJZiByb3RhdGlv" + - "biBkb2Vzbid0IGZpeCBpdAoKVGhlIGF1dGggZmFpbHVyZSBtYXkgYmUgYXQgYSBkaWZmZXJlbnQg" + - "bGF5ZXIg4oCUIGNoZWNrOgotIE5BVFMgYXV0aC1jYWxsb3V0IGhlYWx0aCAoYGt1YmVjdGwgbG9n" + - "cyAtbiBuYXRzLXN5c3RlbSAtbCBhcHA9bmF0cy1hdXRoLWNhbGxvdXQtc2VydmljZWApCi0gQ2x1" + - "c3RlciByb3cgZXhpc3RzIGluIGBjbHVzdGVyX29pZGNfYnlfY2x1c3Rlcl9pZGAgQ2Fzc2FuZHJh" + - "IHRhYmxlCi0gYGhlbG0tcmV2YWwtc2VydmljZWAgY29uZmlnbWFwIGhhcyBgYXV0aHoub2lkYy5l" + - "bmFibGVkPXRydWVgCgpSb3V0ZSB0byBbZGlhZ25vc2UtZmFpbGVkLWluc3RhbGwubWRdKGRpYWdu" + - "b3NlLWZhaWxlZC1pbnN0YWxsLm1kKSBmb3IgdGhvc2UgY2hlY2tzLgpQSwcIGsQsnscHAADHBwAA" + - "UEsDBBQACAAAAAAAIQAAAAAAAAAAAAAAAAAuAAkAZGF0YS9udmNmLXNlbGYtbWFuYWdlZC1jbGkv" + - "cHJvbXB0cy90ZWFyZG93bi5tZFVUBQABAKbOEiMgVGVhciBkb3duIGEgc2VsZi1ob3N0ZWQgTlZD" + - "RiBkZXBsb3ltZW50CgoqKlNBRkVUWToqKiBUZWFyZG93biBkZXN0cm95cyBwZXJzaXN0ZW50IHN0" + - "YXRlIChDYXNzYW5kcmEgcm93cywgT3BlbkJhbyBzZWFsIGtleXMsIHJlZ2lzdGVyZWQgY2x1c3Rl" + - "cnMpLiBBbHdheXMgY29uZmlybSB3aXRoIHRoZSB1c2VyIGJlZm9yZSBhbnkgc3RlcC4KCiMjIFRM" + - "O0RSIOKAlCBgZG93bmAgZm9yIG9uZS1zaG90LCBgdW5pbnN0YWxsYCBmb3IgcGVyLXBsYW5lCgpU" + - "aGUgQ0xJIHByb3ZpZGVzIGEgdHdvLXRpZXIgdGVhcmRvd24gc3VyZmFjZSBzeW1tZXRyaWMgd2l0" + - "aCB0aGUgaW5zdGFsbCBzaWRlIChSRVEtMjIsIE0rMTEpOgoKfCBUaWVyIHwgVXAgc2lkZSAoZXhp" + - "c3RpbmcpIHwgRG93biBzaWRlIHwKfC0tLXwtLS18LS0tfAp8IFBlci1wbGFuZSBwcmltaXRpdmUg" + - "KHJlbmRlcnMgWUFNTDsgR2l0T3BzLWZyaWVuZGx5KSB8IGBpbnN0YWxsIC0tY29udHJvbC1wbGFu" + - "ZWAgLyBgaW5zdGFsbCAtLWNvbXB1dGUtcGxhbmVgIHwgYHVuaW5zdGFsbCAtLWNvbnRyb2wtcGxh" + - "bmVgIC8gYHVuaW5zdGFsbCAtLWNvbXB1dGUtcGxhbmVgIHwKfCBPcmNoZXN0cmF0b3IgKG9uZS1z" + - "aG90LCBtdWx0aS1wbGFuZSArIElDTVMpIHwgYHVwIC0tY2x1c3Rlci1uYW1lPVhgIHwgYGRvd24g" + - "LS1jbHVzdGVyLW5hbWU9WGAgLyBgZG93biAtLWFsbGAgfAoKVXNlIGBkb3duYCBmb3Igbm9ybWFs" + - "IHRlYXJkb3duLiBVc2UgYHVuaW5zdGFsbGAgZm9yIEdpdE9wcyAoY29tbWl0IFlBTUw7IEFyZ28v" + - "Rmx1eCBhcHBsaWVzKSBvciBzY3JpcHRlZCBwaXBlbGluZXMuIEFsd2F5cyBzdGFydCB3aXRoIGEg" + - "ZHJ5LXJ1bi4KCiMjIERyeS1ydW4gc2hhcGVzCgp8IE1vZGUgfCBMaXZlcyBvbiB8IEludm9rZXMg" + - "aGVsbS9oZWxtZmlsZT8gfCBPdXRwdXQgfAp8LS0tfC0tLXwtLS18LS0tfAp8IGAtLXBsYW4tb25s" + - "eWAgfCBgdXBgIC8gYGRvd25gIChvcmNoZXN0cmF0b3JzKSB8ICoqTm8uKiogV2Fsa3MgcmVzb2x2" + - "ZWQgc3RhY2sgZW50cnlwb2ludCBmaWxlcywgZW1pdHMgcGhhc2UgcGxhbiArIEVUQXMgKyBoZWxt" + - "IHVuaW5zdGFsbCBjb21tYW5kIHN0cmluZ3MuIHwgRXZlbnQgc3RyZWFtIHwKfCBgLS1uby1hcHBs" + - "eWAgfCBgaW5zdGFsbGAgLyBgdW5pbnN0YWxsYCAocHJpbWl0aXZlcykgfCAqKlllcy4qKiBgaGVs" + - "bWZpbGUgdGVtcGxhdGVgIGZvciBpbnN0YWxsOyBgaGVsbSBnZXQgbWFuaWZlc3RgIGZvciB1bmlu" + - "c3RhbGwuIHwgWUFNTCBvbiBzdGRvdXQgfAoKU286Ci0gYGRvd24gLS1wbGFuLW9ubHkgLS1jbHVz" + - "dGVyLW5hbWU9WGAg4oCUIHNob3cgbWUgdGhlIG9yY2hlc3RyYXRvciBwaGFzZXMgd2l0aG91dCBp" + - "bnZva2luZyBhbnl0aGluZy4KLSBgdW5pbnN0YWxsIC0tbm8tYXBwbHkgLS1jb21wdXRlLXBsYW5l" + - "IC0tY2x1c3Rlci1uYW1lPVhgIOKAlCBnaXZlIG1lIHRoZSBZQU1MIEknZCBga3ViZWN0bCBkZWxl" + - "dGUgLWYgLWAuCgpgYGBzaAojIFN0ZXAgMTogQUxXQVlTIHByZXZpZXcgZmlyc3QuCm52Y2YtY2xp" + - "IHNlbGYtaG9zdGVkIGRvd24gLS1wbGFuLW9ubHkgLS1jbHVzdGVyLW5hbWU9bmNwLWxvY2FsIC0t" + - "anNvbiB8IGpxCgojIFN0ZXAgMjogUmVhbCBydW4uCm52Y2YtY2xpIHNlbGYtaG9zdGVkIGRvd24g" + - "LS1jbHVzdGVyLW5hbWU9bmNwLWxvY2FsCmBgYAoKIyMgV2hlbiB0byB1c2Ugd2hpY2ggZm9ybQoK" + - "fCBVc2VyIGludGVudCB8IENvbW1hbmQgfAp8LS0tfC0tLXwKfCAiUmVtb3ZlIHRoaXMgb25lIEdQ" + - "VSBjbHVzdGVyIiB8IGBudmNmLWNsaSBzZWxmLWhvc3RlZCBkb3duIC0tY2x1c3Rlci1uYW1lPVhg" + - "IHwKfCAiVGVhciBkb3duIGV2ZXJ5dGhpbmciIHwgYG52Y2YtY2xpIHNlbGYtaG9zdGVkIGRvd24g" + - "LS1hbGwgLS1jb25maXJtYCB8CnwgIlNob3cgbWUgdGhlIHN0ZXBzIHdpdGhvdXQgaW52b2tpbmcg" + - "YW55dGhpbmciIHwgYG52Y2YtY2xpIHNlbGYtaG9zdGVkIGRvd24gLS1wbGFuLW9ubHkgLS1jbHVz" + - "dGVyLW5hbWU9WGAgfAp8ICJHaXZlIG1lIFlBTUwgdG8gY29tbWl0ICsgQXJnbyBkZWxldGVzIGxh" + - "dGVyIiB8IGBudmNmLWNsaSBzZWxmLWhvc3RlZCB1bmluc3RhbGwgLS1uby1hcHBseSAtLWNvbXB1" + - "dGUtcGxhbmUgLS1jbHVzdGVyLW5hbWU9WCA+IGRlbGV0ZS55YW1sYCB8CnwgIkp1c3QgcmVtb3Zl" + - "IHRoZSBoZWxtIHJlbGVhc2VzIG9uIGEgY29tcHV0ZSBwbGFuZSAobm8gSUNNUyB1bnJlZ2lzdGVy" + - "LCBubyBkcmFpbikiIHwgYG52Y2YtY2xpIHNlbGYtaG9zdGVkIHVuaW5zdGFsbCAtLWNvbXB1dGUt" + - "cGxhbmUgLS1jbHVzdGVyLW5hbWU9WGAgfAp8ICJKdXN0IHJlbW92ZSB0aGUgY29udHJvbC1wbGFu" + - "ZSBoZWxtIHJlbGVhc2VzIiB8IGBudmNmLWNsaSBzZWxmLWhvc3RlZCB1bmluc3RhbGwgLS1jb250" + - "cm9sLXBsYW5lYCB8CgojIyBTdGVwcyBmb3IgYGRvd24gLS1jbHVzdGVyLW5hbWU9WGAgKHRoZSBz" + - "dGFuZGFyZCBwYXRoKQoKIyMjIDEuIENvbmZpcm0gc2NvcGUgd2l0aCB0aGUgdXNlcgoKQXNrOgot" + - "ICJUZWFyaW5nIGRvd24gZXZlcnl0aGluZyAoY29tcHV0ZSBwbGFuZXMgKyBjb250cm9sIHBsYW5l" + - "ICsgSUNNUyByb3dzKSBvciBqdXN0IG9uZSBjb21wdXRlIHBsYW5lPyIKLSAiQXJlIHRoZXJlIEFD" + - "VElWRSBmdW5jdGlvbiBkZXBsb3ltZW50cyBvbiB0aGlzIGNsdXN0ZXI/IGBudmNmLWNsaSBmdW5j" + - "dGlvbiBkZXBsb3kgbGlzdGAgdG8gY2hlY2suIiAoSW1wb3J0YW50IOKAlCBgZG93bmAgd2lsbCBy" + - "ZWZ1c2Ugd2l0aG91dCBgLS1kcmFpbi1hY3RpdmU9dHJ1ZXxwcm9tcHRgIGlmIGFueSBhcmUgQUNU" + - "SVZFLikKLSAiU2hvdWxkIHdlIHdpcGUgcGVyc2lzdGVudCBzdGF0ZSAoQ2Fzc2FuZHJhIGRhdGEs" + - "IE9wZW5CYW8gc2VhbCBrZXlzLCBzci1kZWZhdWx0IHVzZXIgZGF0YSk/IERlZmF1bHQgaXMgdG8g" + - "KipwcmVzZXJ2ZSoqIHRoZW0uIGAtLXJlbW92ZS1wZXJzaXN0ZW50YCBvcHRzIGluIHRvIGRlbGV0" + - "aW9uLiAqKkxvc3MgaXMgdW5yZWNvdmVyYWJsZS4qKiIKCiMjIyAyLiBSdW4gcGxhbi1vbmx5IGZp" + - "cnN0CgpgYGBzaApudmNmLWNsaSBzZWxmLWhvc3RlZCBkb3duIC0tcGxhbi1vbmx5IC0tY2x1c3Rl" + - "ci1uYW1lPSROQU1FIC0tanNvbiB8IGpxCmBgYAoKVGhpcyBlbWl0cyBhIGBwbGFubmVkYCBldmVu" + - "dCBsaXN0aW5nIGVhY2ggcGhhc2UsIHRoZSBoZWxtIHJlbGVhc2VzIHRoYXQgd291bGQgYmUgdW5p" + - "bnN0YWxsZWQsIHRoZSBJQ01TIHJvd3MgdGhhdCB3b3VsZCBiZSByZW1vdmVkLCBhbmQgRVRBcy4g" + - "Q29weSB0aGUgYHdpbGxVbmluc3RhbGwuY29tbWFuZHNbXWAgYXJyYXkgdG8gdGhlIHVzZXIg4oCU" + - "IHRob3NlIGFyZSB0aGUgbGl0ZXJhbCBgaGVsbSB1bmluc3RhbGwg4oCmYCBpbnZvY2F0aW9ucyB0" + - "aGUgb3JjaGVzdHJhdG9yIHdpbGwgcnVuLgoKIyMjIDMuIEV4ZWN1dGUgdGhlIGRvd24KCmBgYHNo" + - "Cm52Y2YtY2xpIHNlbGYtaG9zdGVkIGRvd24gLS1jbHVzdGVyLW5hbWU9JE5BTUUgLS1qc29uCmBg" + - "YAoKUGhhc2VzICgxLTcsIG1pcnJvcnMgwqc2LjcuMSk6CgoxLiBQcmVmbGlnaHQg4oCUIGt1YmVj" + - "dGwgYWNjZXNzICsgSUNNUyByZWFjaGFibGUKMi4gUmVzb2x2ZSBzdGFjayBidW5kbGUKMy4gRHJh" + - "aW4gYWN0aXZlIGRlcGxveW1lbnRzIOKAlCBydW5zIGBmdW5jdGlvbiBkZXBsb3kgcmVtb3ZlYCBm" + - "b3IgZWFjaCBBQ1RJVkUgZGVwbG95bWVudCwgcG9sbHMgdW50aWwgU1RPUFBFRC4gU3ViamVjdCB0" + - "byBgLS1kcmFpbi1hY3RpdmU9dHJ1ZXxmYWxzZXxwcm9tcHRgIChkZWZhdWx0IGlzIGBwcm9tcHRg" + - "IGluIFRUWSkuCjQuIFVuaW5zdGFsbCBjb21wdXRlIHBsYW5lIOKAlCBkZWxlZ2F0ZXMgdG8gYHVu" + - "aW5zdGFsbCAtLWNvbXB1dGUtcGxhbmVgOyBgaGVsbSB1bmluc3RhbGwgPHJlbGVhc2U+YCBwZXIg" + - "cmVsZWFzZSBpbiByZXZlcnNlLURBRyBvcmRlcgo1LiBSZW1vdmUgcGVyc2lzdGVudCAoc2tpcHBl" + - "ZCB1bmxlc3MgYC0tcmVtb3ZlLXBlcnNpc3RlbnRgKQo2LiBVbnJlZ2lzdGVyIGNsdXN0ZXIgZnJv" + - "bSBJQ01TCjcuIEZpbmFsIHZlcmlmeQoKIyMjIDQuIFRlYXIgZG93biB0aGUgY29udHJvbCBwbGFu" + - "ZSAoaWYgYXBwbGljYWJsZSkKCmBgYHNoCm52Y2YtY2xpIHNlbGYtaG9zdGVkIHVuaW5zdGFsbCAt" + - "LWNvbnRyb2wtcGxhbmUKYGBgCgpPciB2aWEgdGhlIG9yY2hlc3RyYXRvcidzIGAtLWFsbGAgbW9k" + - "ZSBpZiBldmVyeSBjb21wdXRlIHBsYW5lIGlzIGFsc28gZ29pbmcgYXdheSAoc2VlIHN0ZXAgNSku" + - "CgpgdW5pbnN0YWxsIC0tY29udHJvbC1wbGFuZWAgcmVmdXNlcyBpZiBhbnkgY29tcHV0ZSBwbGFu" + - "ZSBpcyBzdGlsbCByZWdpc3RlcmVkIGluIElDTVMuIFVzZSBgLS1mb3JjZS13aXRoLXJlZ2lzdGVy" + - "ZWQtY2x1c3RlcnNgIHRvIG92ZXJyaWRlICoqb25seSoqIGlmIHRoZSB1c2VyIGV4cGxpY2l0bHkg" + - "d2FudHMgdG8gb3JwaGFuIGNvbXB1dGUgcGxhbmVzIChQU0FUIGF1dGggYnJlYWtzIGZvciB0aG9z" + - "ZSBjbHVzdGVycyDigJQgdGhleSB3aWxsIHN0YXJ0IGZhaWxpbmcgZXZlcnkgaGVhbHRoIGNoZWNr" + - "KS4gQWx3YXlzIHN0YXRlIHRoZSBjb25zZXF1ZW5jZSBiZWZvcmUgcGFzc2luZyB0aGUgZmxhZy4K" + - "CiMjIyA1LiBPciwgbnVrZSBldmVyeXRoaW5nCgpgYGBzaApudmNmLWNsaSBzZWxmLWhvc3RlZCBk" + - "b3duIC0tYWxsIC0tY29uZmlybQpgYGAKClRoaXMgaXRlcmF0ZXMgZXZlcnkgcmVnaXN0ZXJlZCBj" + - "b21wdXRlIHBsYW5lIGluIElDTVMsIHJ1bnMgdGhlIHBlci1jbHVzdGVyIGRvd24gZm9yIGVhY2gg" + - "aW4gcGFyYWxsZWwgKGJvdW5kZWQgYnkgYC0tYWxsLWNvbmN1cnJlbmN5YCwgZGVmYXVsdCA0KSwg" + - "dGhlbiBydW5zIGB1bmluc3RhbGwgLS1jb250cm9sLXBsYW5lYC4gSW50ZXJhY3RpdmUgbW9kZSBw" + - "cm9tcHRzIHRoZSBvcGVyYXRvciB3aXRoIHRoZSBjbHVzdGVyIGxpc3QgYmVmb3JlIHByb2NlZWRp" + - "bmc7IGAtLWNvbmZpcm1gIHNraXBzIHRoZSBwcm9tcHQgZm9yIG5vbi1pbnRlcmFjdGl2ZSB1c2Uu" + - "CgojIyBHaXRPcHMgcGF0aCAoaWYgdXNlciBoYXMgQXJnbyAvIEZsdXgpCgpgYGBzaAojIFJlbmRl" + - "ciBkZWxldGUgWUFNTCBmb3IgdGhlIGNvbXB1dGUgcGxhbmU6Cm52Y2YtY2xpIHNlbGYtaG9zdGVk" + - "IHVuaW5zdGFsbCAtLW5vLWFwcGx5IC0tY29tcHV0ZS1wbGFuZSAtLWNsdXN0ZXItbmFtZT0kTkFN" + - "RSA+IGRlbGV0ZS55YW1sCgojIE9wZXJhdG9yIGNvbW1pdHMgZGVsZXRlLnlhbWwgdG8gdGhlaXIg" + - "R2l0T3BzIHJlcG8uIEFyZ28vRmx1eCBhcHBsaWVzIHRoZSBkZWxldGlvbiBvbiBuZXh0IHN5bmMu" + - "CmdpdCBhZGQgZGVsZXRlLnlhbWw7IGdpdCBjb21taXQgLW0gInJlbW92ZSBjb21wdXRlIHBsYW5l" + - "ICROQU1FIjsgZ2l0IHB1c2gKCiMgQWZ0ZXIgQXJnbyBzeW5jIHJlbW92ZXMgdGhlIEs4cyByZXNv" + - "dXJjZXMsIHNlcGFyYXRlbHkgcmVtb3ZlIHRoZSBJQ01TIHJvdzoKbnZjZi1jbGkgY2x1c3RlciBk" + - "ZWxldGUgLS1jbHVzdGVyLWlkPSRDTFVTVEVSX0lECmBgYAoKTm90ZTogYC0tbm8tYXBwbHlgIGRv" + - "ZXMgTk9UIGluY2x1ZGUgdGhlIElDTVMgYGNsdXN0ZXIgZGVsZXRlYCAoaGVsbSBkb2Vzbid0IG1h" + - "bmFnZSB0aGF0KS4gT3BlcmF0b3JzIGZvbGxvd2luZyBHaXRPcHMgbXVzdCBydW4gYG52Y2YtY2xp" + - "IGNsdXN0ZXIgZGVsZXRlYCBzZXBhcmF0ZWx5LCBvciB1c2UgYGRvd25gIHRvIG9yY2hlc3RyYXRl" + - "IGJvdGguCgojIyBGYWlsdXJlIHJlY292ZXJ5CgpJZiBgZG93bmAgcGFydGlhbC1mYWlscyBtaWQt" + - "d2F5LCAqKnJlLXJ1biB0aGUgc2FtZSBjb21tYW5kKiouIFRoZSBvcmNoZXN0cmF0b3IgaXMgaWRl" + - "bXBvdGVudDogYGhlbG0gdW5pbnN0YWxsYCBza2lwcyByZWxlYXNlcyB0aGF0IGFyZSBhbHJlYWR5" + - "IGdvbmUsIGFuZCBgY2x1c3RlciBkZWxldGUgLS1pZ25vcmUtbWlzc2luZ2Agc2tpcHMgSUNNUyBy" + - "b3dzIHRoYXQgYXJlIGFscmVhZHkgZ29uZS4gUGhhc2UgNCArIDYgZW1pdCBgcGhhc2U6IHNraXBw" + - "ZWQgKGFscmVhZHkgY2xlYW4pYCBvbiB0aGUgc2Vjb25kIHJ1bi4KCklmIGEgYHBoYXNlX2ZhaWxl" + - "ZGAgZXZlbnQgc3VyZmFjZXM6Ci0gYGVyckNhdGVnb3J5OiAiaGVsbV9wZW5kaW5nX3VwZ3JhZGUi" + - "LCByZXRyeUNsYXNzOiAiYWZ0ZXJfcmVtZWRpYXRpb24iYCDihpIgcnVuIGBoZWxtIHJvbGxiYWNr" + - "IDxyZWxlYXNlPiAwIC0ta3ViZS1jb250ZXh0PeKApmAgKHRoZSBtZXNzYWdlIGluY2x1ZGVzIHRo" + - "ZSBleGFjdCByZWxlYXNlIG5hbWUpIHRoZW4gcmUtcnVuIGBkb3duYC4gRG8gTk9UIGF1dG8tcm9s" + - "bGJhY2sgd2l0aG91dCB1c2VyIGNvbmZpcm1hdGlvbi4KLSBgZXJyQ2F0ZWdvcnk6ICJjb21wdXRl" + - "X3BsYW5lIiwgcmV0cnlDbGFzczogImFmdGVyX3JlbWVkaWF0aW9uImAgd2l0aCBtZXNzYWdlIGFi" + - "b3V0IEFDVElWRSBkZXBsb3ltZW50cyDihpIgdXNlciBwYXNzZWQgYC0tZHJhaW4tYWN0aXZlPWZh" + - "bHNlYCBidXQgZGVwbG95bWVudHMgZXhpc3QuIFJlLXJ1biB3aXRoIGAtLWRyYWluLWFjdGl2ZT10" + - "cnVlYCBvciBgLS1kcmFpbi1hY3RpdmU9cHJvbXB0YCBhbmQgY29uZmlybSB3aXRoIHRoZSB1c2Vy" + - "LgotIE90aGVyIGNhdGVnb3JpZXMg4oaSIHN1cmZhY2UgYGVyck1lc3NhZ2VgICsgZWFjaCBgcmVt" + - "ZWRpYXRpb25gIHN0cmluZyB2ZXJiYXRpbS4gU1RPUC4gRG8gbm90IHByb3Bvc2UgcmV0cnkgd2l0" + - "aG91dCB1c2VyIGlucHV0LgoKIyMgQWx3YXlzIGNvbmZpcm0KCkZvciBldmVyeSBgbnZjZi1jbGkg" + - "c2VsZi1ob3N0ZWQgZG93bmAgb3IgYHVuaW5zdGFsbGAgaW52b2NhdGlvbiwgZXNwZWNpYWxseSB3" + - "aXRoIGRlc3RydWN0aXZlIGZsYWdzOgotIFN0YXRlIHRoZSBleGFjdCBgLS1jbHVzdGVyLW5hbWVg" + - "IChvciBgLS1hbGxgKS4KLSBTdGF0ZSB3aGF0J3MgbG9zdCAoZS5nLiAiYWxsIGZ1bmN0aW9uIGRl" + - "cGxveW1lbnRzIHNjaGVkdWxlZCBvbiBgbmNwLWxvY2FsYCB3aWxsIHN0b3A7IGNvbXB1dGUgcGxh" + - "bmUgd2lsbCBkZXJlZ2lzdGVyIGZyb20gSUNNUyIpLgotIElmIGAtLXJlbW92ZS1wZXJzaXN0ZW50" + - "YCBpcyBiZWluZyBjb25zaWRlcmVkLCBzdGF0ZSB0aGF0IENhc3NhbmRyYSByb3dzICsgT3BlbkJh" + - "byBzZWFsIGtleXMgd2lsbCBiZSBkZWxldGVkIGFuZCB0aGF0IHRoaXMgaXMgdW5yZWNvdmVyYWJs" + - "ZS4KLSBXYWl0IGZvciBleHBsaWNpdCB1c2VyIGNvbmZpcm1hdGlvbi4KLSBOZXZlciBwcm9wb3Nl" + - "IGAtLWZvcmNlLXdpdGgtcmVnaXN0ZXJlZC1jbHVzdGVyc2Agd2l0aG91dCBleHBsYWluaW5nIHRo" + - "YXQgY29tcHV0ZSBwbGFuZXMgd2lsbCBiZSBvcnBoYW5lZC4KLSBOZXZlciBwcm9wb3NlIGAtLXJl" + - "bW92ZS1wZXJzaXN0ZW50YCB3aXRob3V0IGV4cGxhaW5pbmcgdGhlIGxvc3MuCgojIyBNYW51YWwg" + - "ZmFsbGJhY2sgKGlmIGBudmNmLWNsaSBzZWxmLWhvc3RlZCB7dW5pbnN0YWxsLGRvd259YCBpcyB1" + - "bmF2YWlsYWJsZSkKCklmIHRoZSB1c2VyIGlzIG9uIGEgQ0xJIHZlcnNpb24gb2xkZXIgdGhhbiBN" + - "KzExLCBmYWxsIGJhY2sgdG86CgpgYGBzaAojIFBlciBjb21wdXRlIHBsYW5lIChvbiBpdHMga3Vi" + - "ZWN0bCBjb250ZXh0KToKaGVsbSB1bmluc3RhbGwgbnZjYS1hZ2VudCAtbiBudmNhLXN5c3RlbQpo" + - "ZWxtIHVuaW5zdGFsbCBudmNhLW9wZXJhdG9yIC1uIG52Y2Etb3BlcmF0b3IKaGVsbSB1bmluc3Rh" + - "bGwgaW1hZ2UtY3JlZGVudGlhbC1oZWxwZXIgLW4gbnZjYS1zeXN0ZW0KaGVsbSB1bmluc3RhbGwg" + - "c2hhcmVkc3RvcmFnZSAtbiBudmNhLXN5c3RlbQoKIyBEcmFpbiB0aGUgSUNNUyByb3c6Cm52Y2Yt" + - "Y2xpIGNsdXN0ZXIgZGVsZXRlIC0tY2x1c3Rlci1pZD08aWQ+CgojIENvbnRyb2wgcGxhbmUgdGVh" + - "cmRvd24gKG9uIGl0cyBrdWJlY3RsIGNvbnRleHQpOgpjZCBudmNmLXNlbGYtbWFuYWdlZC1zdGFj" + - "awptYWtlIGRlc3Ryb3kgICAjIGludm9rZXMgaGVsbWZpbGUgZGVzdHJveSBhY3Jvc3MgYWxsIHJl" + - "bGVhc2VzCgojIElmIHVzaW5nIHNwbGl0IHN0YWNrIGJ1bmRsZXMsIHRlYXJkb3duIGNvbXB1dGUg" + - "cGxhbmUgcmVsZWFzZXMgdG9vOgpjZCAuLi9udmNmLWNvbXB1dGUtcGxhbmUtc3RhY2sKbWFrZSBk" + - "ZXN0cm95CmBgYAoKVGhpcyBpcyB0aGUgbWFudWFsIHBhdGg7IHByZWZlciB0aGUgbmV3IHN1YmNv" + - "bW1hbmRzIHdoZW5ldmVyIHRoZXkncmUgaW5zdGFsbGVkLgoKIyMgV2hhdCBga3ViZWN0bCBkZWxl" + - "dGUgcHZjYCBkb2VzCgpJZiB0aGUgdXNlciBhc2tzICJ3aHkgaXMgbXkgbmV3IGluc3RhbGwgc2Vl" + - "aW5nIHRoZSBvbGQgZGF0YT8iIOKAlCB0aGUgU3RhdGVmdWxTZXQgUFZDcyAoQ2Fzc2FuZHJhLCBP" + - "cGVuQmFvKSBhcmUgTk9UIGRlbGV0ZWQgYnkgYGhlbG0gdW5pbnN0YWxsYCAoYW5kIHNvIGFyZSBO" + - "T1QgZGVsZXRlZCBieSBgbnZjZi1jbGkgc2VsZi1ob3N0ZWQgZG93bmAgZWl0aGVyLCBieSBkZWZh" + - "dWx0KS4gVGhleSBwZXJzaXN0IGludGVudGlvbmFsbHkgc28gYSByZS1gdXBgIGNhbiByZXN1bWUg" + - "YWdhaW5zdCBleGlzdGluZyBkYXRhLiBUbyB3aXBlIGNvbXBsZXRlbHkgb24gdGhlIG5leHQgYGRv" + - "d25gOgoKYGBgc2gKbnZjZi1jbGkgc2VsZi1ob3N0ZWQgZG93biAtLWNsdXN0ZXItbmFtZT1uY3At" + - "bG9jYWwgLS1yZW1vdmUtcGVyc2lzdGVudApgYGAKCk9yLCBwb3N0LWRvd24sIG1hbnVhbGx5OgoK" + - "YGBgc2gKa3ViZWN0bCBkZWxldGUgcHZjIC1uIGNhc3NhbmRyYS1zeXN0ZW0gLS1hbGwKa3ViZWN0" + - "bCBkZWxldGUgcHZjIC1uIHZhdWx0LXN5c3RlbSAtLWFsbApgYGAKCioqQ29uZmlybSB3aXRoIHVz" + - "ZXIuKiogVGhpcyBpcyB0aGUgZGVzdHJ1Y3RpdmUgc3RlcC4KUEsHCBKTM1ngIQAA4CEAAFBLAwQU" + - "AAgAAAAAACEAAAAAAAAAAAAAAAAAMAAJAGRhdGEvbnZjZi1zZWxmLW1hbmFnZWQtY2xpL3JlZmVy" + - "ZW5jZS9jb21tYW5kcy5tZFVUBQABAKbOEiMgYG52Y2YtY2xpYCBjb21tYW5kIHJlZmVyZW5jZQoK" + - "RnVsbCBzdWJjb21tYW5kIGxpc3QuIEFsd2F5cyBwYWlyIHdpdGggW2ZsYWdzLm1kXShmbGFncy5t" + - "ZCkgZm9yIGdsb2JhbCBmbGFncyBhbmQgW2V4aXQtY29kZXMubWRdKGV4aXQtY29kZXMubWQpIGZv" + - "ciByZXR1cm4gY29kZXMuCgojIyBTZWxmLWhvc3RlZCBsaWZlY3ljbGUKCnwgQ29tbWFuZCB8IFB1" + - "cnBvc2UgfCBPdXRwdXQgfCBOb3RlcyB8CnwtLS18LS0tfC0tLXwtLS18CnwgYHNlbGYtaG9zdGVk" + - "IGNoZWNrIC0tcHJlIFtmbGFnc11gIHwgUHJlLWZsaWdodCB2YWxpZGF0aW9uIChubyBhZG1pbiBj" + - "cmVkcykgfCBzdHJlYW1pbmcgZXZlbnRzIHwgUGVyLXJvbGUgd2l0aCBgLS1jb250cm9sLXBsYW5l" + - "LWNvbnRleHRgIC8gYC0tY29tcHV0ZS1wbGFuZS1jb250ZXh0YDsgYC0tbG9jYWwtb25seWAgc2tp" + - "cHMga3ViZWN0bCB8CnwgYHNlbGYtaG9zdGVkIGNoZWNrIC0tY29udHJvbC1wbGFuZSBbLS13YWl0" + - "IERVUl1gIHwgRXZlcnkgQ1AgcmVsZWFzZSBoZWFsdGh5IHwgc3RyZWFtaW5nIGV2ZW50cyB8IFBv" + - "bGxzIHVudGlsIHBhc3Mgb3IgYC0td2FpdGAgZWxhcHNlcyB8CnwgYHNlbGYtaG9zdGVkIGNoZWNr" + - "IC0tY29tcHV0ZS1wbGFuZSAtLWNsdXN0ZXItbmFtZT1YIFstLXdhaXQgRFVSXWAgfCBQZXItY2x1" + - "c3RlciB3b3JrZXIgc3RhdGUgfCBzdHJlYW1pbmcgZXZlbnRzIHwgTlZDQSArIG9wZXJhdG9yICsg" + - "SldLUy1maW5nZXJwcmludCBtYXRjaCB8CnwgYHNlbGYtaG9zdGVkIGNoZWNrIC0tYWxsIFstLXdh" + - "aXQgRFVSXWAgfCBGYW4tb3V0IG92ZXIgYWxsIG9mIHRoZSBhYm92ZSB8IHN0cmVhbWluZyBldmVu" + - "dHMgfCB8CnwgYHNlbGYtaG9zdGVkIGluc3RhbGwgLS1jb250cm9sLXBsYW5lYCB8IFJlbmRlciBj" + - "b250cm9sLXBsYW5lIG1hbmlmZXN0cyB8IFlBTUwgb24gc3Rkb3V0IHwgUGlwZSB0byBga3ViZWN0" + - "bCBhcHBseSAtZiAtYCB8CnwgYHNlbGYtaG9zdGVkIGluc3RhbGwgLS1jb21wdXRlLXBsYW5lIC0t" + - "Y2x1c3Rlci1uYW1lPVhgIHwgUmVnaXN0ZXIgY2x1c3RlciArIHJlbmRlciBjb21wdXRlIG1hbmlm" + - "ZXN0cyB8IFlBTUwgb24gc3Rkb3V0IHwgT25lIGludm9jYXRpb24gcGVyIEdQVSBjbHVzdGVyIHwK" + - "fCBgc2VsZi1ob3N0ZWQgdXAgLS1jbHVzdGVyLW5hbWU9WGAgfCBPbmUtc2hvdCBmaXJzdCBpbnN0" + - "YWxsIChib3RoIHBsYW5lcykgfCAoZW1wdHkpIHN0ZG91dCwgcHJvZ3Jlc3Mgb24gc3RkZXJyIHwg" + - "QWx3YXlzIGluc3RhbGxzIGJvdGggcGxhbmVzOyBmb3IgY29tcHV0ZS1vbmx5IHVzZSBgYWRkLWNv" + - "bXB1dGUtcGxhbmVgIHwKfCBgc2VsZi1ob3N0ZWQgdXAgLS1wbGFuLW9ubHkgLS1jbHVzdGVyLW5h" + - "bWU9WGAgfCBEcnktcnVuIHByZXZpZXcgfCBKU09OTCBvbiBzdGRlcnIgfCBBZ2VudC1mcmllbmRs" + - "eSwgbm8gY2x1c3RlciBjaGFuZ2VzIHwKfCBgc2VsZi1ob3N0ZWQgYWRkLWNvbXB1dGUtcGxhbmUg" + - "LS1jbHVzdGVyLW5hbWU9WCAtLWNvbXB1dGUtcGxhbmUtY29udGV4dD1ZIC0taWNtcy11cmw9VVJM" + - "IC0tdG9rZW49JEpXVGAgfCBBZGQgYSBjb21wdXRlIHBsYW5lIHRvIGFuIGV4aXN0aW5nIGNvbnRy" + - "b2wgcGxhbmUgfCBwcm9ncmVzcyBvbiBzdGRlcnIgfCBGb3IgMm5kLCAzcmQsIOKApiBHUFUgY2x1" + - "c3RlciBhZnRlciB0aGUgZmlyc3QgaW5zdGFsbC4gQ1Agbm90IHRvdWNoZWQuIHwKfCBgc2VsZi1o" + - "b3N0ZWQgc3RhdHVzIFstLWNsdXN0ZXItbmFtZT1YXSBbLS13YXRjaF1gIHwgU25hcHNob3QgY2x1" + - "c3RlciBoZWFsdGggfCBUVFkvcGxhaW4vSlNPTkwgfCBgLS13YXRjaGAgZm9yIGxpdmUgcmUtcmVu" + - "ZGVyIHwKfCBgc2VsZi1ob3N0ZWQgdW5pbnN0YWxsIC0tY29tcHV0ZS1wbGFuZSAtLWNsdXN0ZXIt" + - "bmFtZT1YYCB8IFBlci1wbGFuZSBwcmltaXRpdmU6IGBoZWxtZmlsZSBkZXN0cm95YCBvbiBvbmUg" + - "Y29tcHV0ZSBwbGFuZSAobm8gSUNNUyB1bnJlZ2lzdGVyLCBubyBkcmFpbikgfCBwcm9ncmVzcyB8" + - "IEp1c3QgdGhlIGhlbG0gcmVsZWFzZXMgb24gdGhhdCBjb21wdXRlIHBsYW5lIHwKfCBgc2VsZi1o" + - "b3N0ZWQgdW5pbnN0YWxsIC0tY29udHJvbC1wbGFuZSBbLS1mb3JjZS13aXRoLXJlZ2lzdGVyZWQt" + - "Y2x1c3RlcnNdYCB8IFBlci1wbGFuZSBwcmltaXRpdmU6IGBoZWxtZmlsZSBkZXN0cm95YCBvbiB0" + - "aGUgY29udHJvbCBwbGFuZSB8IHByb2dyZXNzIHwgUmVmdXNlcyBpZiBjb21wdXRlIHBsYW5lcyBz" + - "dGlsbCByZWdpc3RlcmVkIHVubGVzcyBgLS1mb3JjZS13aXRoLXJlZ2lzdGVyZWQtY2x1c3RlcnNg" + - "IHwKfCBgc2VsZi1ob3N0ZWQgdW5pbnN0YWxsIC0tbm8tYXBwbHkgPHBsYW5lPmAgfCBSZW5kZXIg" + - "ZGVsZXRlIFlBTUwgdmlhIGBoZWxtIGdldCBtYW5pZmVzdGAgcGVyIHJlbGVhc2UgfCBZQU1MIG9u" + - "IHN0ZG91dCB8IEdpdE9wczsgcGlwZSB0byBga3ViZWN0bCBkZWxldGUgLWYgLWAgb3IgY29tbWl0" + - "ICsgQXJnbyBhcHBsaWVzLiBNaXJyb3JzIGBpbnN0YWxsIC0tbm8tYXBwbHlgIHwKfCBgc2VsZi1o" + - "b3N0ZWQgZG93biAtLWNsdXN0ZXItbmFtZT1YIFstLWRyYWluLWFjdGl2ZV0gWy0tcmVtb3ZlLXBl" + - "cnNpc3RlbnRdYCB8IE9yY2hlc3RyYXRvcjogZHJhaW4g4oaSIHVuaW5zdGFsbCAtLWNvbXB1dGUt" + - "cGxhbmUg4oaSIGNsdXN0ZXIgZGVsZXRlIGluIElDTVMgfCBwcm9ncmVzcyB8IFN5bW1ldHJpYyBj" + - "b21wYW5pb24gdG8gYHVwIC0tY2x1c3Rlci1uYW1lPVhgIHwKfCBgc2VsZi1ob3N0ZWQgZG93biAt" + - "LWFsbCBbLS1jb25maXJtXWAgfCBPcmNoZXN0cmF0b3I6IHRlYXIgZG93biBldmVyeSByZWdpc3Rl" + - "cmVkIGNvbXB1dGUgcGxhbmUgKyBjb250cm9sIHBsYW5lIHwgcHJvZ3Jlc3MgfCBCb3VuZGVkIHBh" + - "cmFsbGVsaXNtIHZpYSBgLS1hbGwtY29uY3VycmVuY3lgIChkZWZhdWx0IDQpOyBgLS1jb25maXJt" + - "YCByZXF1aXJlZCBpbiBub24taW50ZXJhY3RpdmUgfAp8IGBzZWxmLWhvc3RlZCBkb3duIC0tcGxh" + - "bi1vbmx5IC4uLmAgfCBPcmNoZXN0cmF0b3IgZHJ5LXJ1bjogcGhhc2VzICsgaGVsbSByZWxlYXNl" + - "cyArIElDTVMgcm93cyArIEVUQXMgKG5vIGhlbG0vaGVsbWZpbGUgY29udGFjdCkgfCBKU09OTCB8" + - "ICoqQWx3YXlzIHJ1biBiZWZvcmUgYW55IGFjdHVhbCBgZG93bmAqKiB8CgojIyBDbHVzdGVyIG1h" + - "bmFnZW1lbnQKCnwgQ29tbWFuZCB8IFB1cnBvc2UgfCBOb3RlcyB8CnwtLS18LS0tfC0tLXwKfCBg" + - "Y2x1c3RlciByZWdpc3RlciAtLW5hbWU9WCAtLW5jYS1pZD1ZIFstLXJlZ2lvbj1aXSBbLS1pZ25v" + - "cmUtZXhpc3RpbmddYCB8IFJlZ2lzdGVyIGEgSldLUytPSURDIHdpdGggSUNNUyB8IFVzZWQgYnkg" + - "YHVwYCBQaGFzZSA1IGludGVybmFsbHk7IHN0YW5kYWxvbmUgZm9yIG1hbnVhbCByZWdpc3RyYXRp" + - "b24gfAp8IGBjbHVzdGVyIGxpc3QtcmVnaXN0ZXJlZCAtLW5jYS1pZD1ZIFstLWljbXMtdXJsPVVS" + - "TF1gIHwgTGlzdCBzZWxmLWhvc3RlZCBjbHVzdGVyIHJlZ2lzdHJhdGlvbnMgZnJvbSBJQ01TIHwg" + - "VXNlcyB0aGUgYWRtaW4gdG9rZW47IGAtLWpzb25gIGZvciBtYWNoaW5lLXJlYWRhYmxlIG91dHB1" + - "dCB8CnwgYGNsdXN0ZXIgcm90YXRlIC0tY2x1c3Rlci1pZD1JRGAgfCBSZS1mZXRjaCBKV0tTIGZy" + - "b20gSzhzIGFuZCBQVVQgdG8gSUNNUyB8IEFmdGVyIEs4cyBBUEkgc2VydmVyIHNpZ25pbmcga2V5" + - "IHJvdGF0aW9uIHwKfCBgY2x1c3RlciBkZWxldGUgLS1jbHVzdGVyLWlkPUlEYCB8IFJlbW92ZSBJ" + - "Q01TIHJvdyB8ICoqREVTVFJVQ1RJVkU6IGNvbmZpcm0gd2l0aCB1c2VyKiogfAoKIyMgRnVuY3Rp" + - "b25zCgp8IENvbW1hbmQgfCBQdXJwb3NlIHwgTm90ZXMgfAp8LS0tfC0tLXwtLS18CnwgYGZ1bmN0" + - "aW9uIGNyZWF0ZSAtLWlucHV0LWZpbGU9RklMRWAgfCBDcmVhdGUgZnVuY3Rpb24gbWV0YWRhdGEg" + - "fCBSZXR1cm5zIEZ1bmN0aW9uIElEICsgVmVyc2lvbiBJRDsgTExNIGZ1bmN0aW9ucyB1c2UgYGZ1" + - "bmN0aW9uVHlwZTogIkxMTSJgIGFuZCBgbW9kZWxzW10ubGxtQ29uZmlnLnVyaXNgIGZvciBgL3Yx" + - "L2NoYXQvY29tcGxldGlvbnNgLCBgL3YxL3Jlc3BvbnNlc2AsIGFuZCBgL3YxL2VtYmVkZGluZ3Ng" + - "IHwKfCBgZnVuY3Rpb24gbGlzdGAgLyBgZnVuY3Rpb24gbGlzdC1pZHNgIHwgTGlzdCBmdW5jdGlv" + - "bnMgLyBJRHMgb25seSB8IHwKfCBgZnVuY3Rpb24gZ2V0IC0tZnVuY3Rpb24taWQ9SUQgLS12ZXJz" + - "aW9uLWlkPVZJRGAgfCBGdW5jdGlvbiBtZXRhZGF0YSB8IHwKfCBgZnVuY3Rpb24gdXBkYXRlIC0t" + - "ZnVuY3Rpb24taWQ9SUQgLS12ZXJzaW9uLWlkPVZJRCAtLXRhZ3M9VEFHWyxUQUddYCB8IFVwZGF0" + - "ZSBmdW5jdGlvbiB0YWdzIHwgfAp8IGBmdW5jdGlvbiB1cGRhdGUgLS1mdW5jdGlvbi1pZD1JRCAt" + - "LXZlcnNpb24taWQ9VklEIC0tbGxtLW1vZGVsLXVwZGF0ZT1TUEVDYCB8IFVwZGF0ZSBMTE0gbW9k" + - "ZWwgcm91dGluZyBjb25maWcgfCBgU1BFQ2AgdXNlcyBgbmFtZT08bW9kZWw+LHJvdXRpbmdNZXRo" + - "b2Q9PG1ldGhvZD4sdG9rZW5SYXRlTGltaXQ9PGxpbWl0PmA7IGFjY2VwdGVkIG1ldGhvZHMgYXJl" + - "IGxpc3RlZCBpbiBbZmxhZ3MubWRdKGZsYWdzLm1kKSB8CnwgYGZ1bmN0aW9uIGRlcGxveSBjcmVh" + - "dGUgLS1pbnB1dC1maWxlPUZJTEVgIHwgU2NoZWR1bGUgYSBkZXBsb3ltZW50IHwgQmxvY2tzIHVu" + - "dGlsIEFDVElWRSAoZGVmYXVsdCA5MDBzKSB8CnwgYGZ1bmN0aW9uIGRlcGxveSBnZXQgLS1mdW5j" + - "dGlvbi1pZD1JRCAtLXZlcnNpb24taWQ9VklEIFstLWpzb25dYCB8IERlcGxveW1lbnQgc3RhdHVz" + - "IHwgYGZ1bmN0aW9uU3RhdHVzOiBBQ1RJVkVcfERFUExPWUlOR1x8RVJST1JcfEZBSUxFRGAgfAp8" + - "IGBmdW5jdGlvbiBkZXBsb3kgdXBkYXRlIC0taW5wdXQtZmlsZT1GSUxFYCB8IE1vZGlmeSBhIGRl" + - "cGxveW1lbnQgaW4gcGxhY2UgfCB8CnwgYGZ1bmN0aW9uIGRlcGxveSByZW1vdmUgLS1mdW5jdGlv" + - "bi1pZD1JRCAtLXZlcnNpb24taWQ9VklEYCB8IFN0b3AgYSBkZXBsb3ltZW50LCBrZWVwIGZ1bmN0" + - "aW9uIHJlY29yZCB8IHwKfCBgZnVuY3Rpb24gaW52b2tlIC0taW5wdXQtZmlsZT1GSUxFYCB8IENh" + - "bGwgYSByZWd1bGFyIGZ1bmN0aW9uIHwgUmVxdWlyZXMgQVBJIGtleSAobm90IGFkbWluIHRva2Vu" + - "KTsgTExNIGZ1bmN0aW9ucyBhcmUgaW52b2tlZCB0aHJvdWdoIGBsbG0uaW52b2NhdGlvbi48ZG9t" + - "YWluPmAgd2l0aCBgbW9kZWw6ICI8ZnVuY3Rpb24taWQ+Lzxtb2RlbC1uYW1lPiJgIHwKfCBgZnVu" + - "Y3Rpb24gZGVsZXRlIC0tZnVuY3Rpb24taWQ9SUQgLS12ZXJzaW9uLWlkPVZJRGAgfCBSZW1vdmUg" + - "ZnVuY3Rpb24gKyBkZXBsb3ltZW50IHwgKipERVNUUlVDVElWRTogY29uZmlybSB3aXRoIHVzZXIq" + - "KiB8CgojIyBUYXNrcwoKVGFzayBjb21tYW5kcyByZXF1aXJlIGBOVkNGX0FQSV9LRVlgIHNldCB0" + - "byBhIHRhc2stc2NvcGVkIGtleSBhbmQgYE5WQ0ZfQkFTRV9OVkNUX1VSTGAgc2V0IHRvIHRoZSBO" + - "VkNUIGdhdGV3YXkgZW5kcG9pbnQuIEluIGEgZnJlc2ggc2Vzc2lvbiwgcmVhZCBgbnZjdEFwaUtl" + - "eWAgZnJvbSBgfi8ubnZjZi1jbGkuc3RhdGVgIGFuZCBpbmplY3QgaXQgYXMgYE5WQ0ZfQVBJX0tF" + - "WWAgd2l0aG91dCBsb2dnaW5nIHRoZSB2YWx1ZTsgY2hlY2sgYG52Y3RBcGlLZXlFeHBpcmF0aW9u" + - "YCBmaXJzdCBhbmQgcmVnZW5lcmF0ZSB3aXRoIGBhcGkta2V5IGdlbmVyYXRlIC0tZm9yIHRhc2sg" + - "LS12YWxpZGF0ZWAgaWYgZXhwaXJlZC4gUnVuIGBhcGkta2V5IGdlbmVyYXRlIC0tZm9yIHRhc2tg" + - "IGFmdGVyIGBpbml0YCB0byBtaW50IGEgbmV3IGtleS4KCnwgQ29tbWFuZCB8IFB1cnBvc2UgfCBO" + - "b3RlcyB8CnwtLS18LS0tfC0tLXwKfCBgdGFzayBjcmVhdGUgLS1uYW1lPVggLS1ncHU9R1BVIC0t" + - "aW5zdGFuY2UtdHlwZT1UWVBFIC0taW1hZ2U9SU1HYCB8IFN1Ym1pdCBhIGNvbnRhaW5lciB0YXNr" + - "IHwgU2F2ZXMgdGFzayBJRCB0byBDTEkgc3RhdGU7IHVzZSBgLS1pbnB1dC1maWxlYCBmb3IgcmVw" + - "ZWF0YWJsZSBjb25maWdzIHwKfCBgdGFzayBjcmVhdGUgLS1uYW1lPVggLS1ncHU9R1BVIC0taW5z" + - "dGFuY2UtdHlwZT1UWVBFIC0taGVsbS1jaGFydD1DSEFSVGAgfCBTdWJtaXQgYSBIZWxtIHRhc2sg" + - "fCBgLS1oZWxtLWNoYXJ0YCByZXBsYWNlcyBgLS1pbWFnZWAgfAp8IGB0YXNrIGNyZWF0ZSAtLWlu" + - "cHV0LWZpbGU9RklMRWAgfCBTdWJtaXQgYSB0YXNrIGZyb20gYSBKU09OIGNvbmZpZyBmaWxlIHwg" + - "UmVjb21tZW5kZWQgZm9yIHJlcGVhdGFibGUgY29uZmlndXJhdGlvbnMgfAp8IGB0YXNrIGxpc3Qg" + - "Wy0tc3RhdHVzPVNUQVRVU10gWy0tbGltaXQ9Tl1gIHwgTGlzdCB0YXNrcywgb3B0aW9uYWxseSBm" + - "aWx0ZXJlZCBieSBzdGF0dXMgfCBTdGF0dXNlczogYFFVRVVFRGAsIGBMQVVOQ0hFRGAsIGBSVU5O" + - "SU5HYCwgYENPTVBMRVRFRGAsIGBFUlJPUkVEYCwgYENBTkNFTEVEYCwgYEVYQ0VFREVEX01BWF9S" + - "VU5USU1FX0RVUkFUSU9OYCwgYEVYQ0VFREVEX01BWF9RVUVVRURfRFVSQVRJT05gIHwKfCBgdGFz" + - "ayBnZXQgW3Rhc2tJZF1gIHwgR2V0IHRhc2sgZGV0YWlscyB8IFVzZXMgc2F2ZWQgdGFzayBJRCBm" + - "cm9tIHN0YXRlIGlmIG9taXR0ZWQgfAp8IGB0YXNrIGV2ZW50cyBbdGFza0lkXSBbLS1saW1pdD1O" + - "XWAgfCBMaXN0IGxpZmVjeWNsZSBldmVudHMgZm9yIGEgdGFzayB8IHwKfCBgdGFzayByZXN1bHRz" + - "IFt0YXNrSWRdYCB8IExpc3QgcmVzdWx0IGFydGlmYWN0cyBmb3IgYSBjb21wbGV0ZWQgdGFzayB8" + - "IFJldHVybnMgZW1wdHkgbGlzdCB3aGVuIGByZXN1bHRIYW5kbGluZ1N0cmF0ZWd5YCBpcyBgTk9O" + - "RWA7IHJlc3VsdCB1cGxvYWQgbm90IHlldCBzdXBwb3J0ZWQgfAp8IGB0YXNrIGNhbmNlbCBbdGFz" + - "a0lkXWAgfCBDYW5jZWwgYSBydW5uaW5nIHRhc2sgfCBOby1vcCBpZiBhbHJlYWR5IGluIHRlcm1p" + - "bmFsIHN0YXRlIHwKfCBgdGFzayBkZWxldGUgW3Rhc2tJZF1gIHwgRGVsZXRlIGEgdGFzayByZWNv" + - "cmQgfCAqKlN0b3AsIHN0YXRlIHRhc2sgSUQgKyBjdXJyZW50IHN0YXR1cywgYW5kIHdhaXQgZm9y" + - "IGEgc3Vic2VxdWVudCB1c2VyIHJlcGx5IGV4cGxpY2l0bHkgY29uZmlybWluZyBkZWxldGlvbiBv" + - "ZiB0aGF0IHNwZWNpZmljIHRhc2suIERvIG5vdCB0cmVhdCB0aGUgb3JpZ2luYWwgZGVsZXRlIHJl" + - "cXVlc3QgYXMgY29uZmlybWF0aW9uLioqIHwKfCBgdGFzayB1cGRhdGUtc2VjcmV0cyBbdGFza0lk" + - "XSAtLXNlY3JldHMgTkFNRT12YWx1ZWAgfCBVcGRhdGUgc2VjcmV0cyBvbiBhIHRhc2s7IHN1cHBs" + - "aWVkIHNlY3JldHMgYXJlIGFkZGVkIG9yIHVwZGF0ZWQgYnkgbmFtZSwgZXhpc3Rpbmcgc2VjcmV0" + - "cyBub3QgaW4gdGhlIHJlcXVlc3QgYXJlIHByZXNlcnZlZCB8IFZhbHVlcyBhcmUgZW5jcnlwdGVk" + - "IGF0IHJlc3QgfAp8IGB0YXNrIGJ1bGsgLS10YXNrLWlkcz1JRDFbLElEMizigKZdYCB8IEZldGNo" + - "IGRldGFpbHMgZm9yIG11bHRpcGxlIHRhc2tzIGluIG9uZSByZXF1ZXN0IHwgfAoKIyMgQXV0aAoK" + - "fCBDb21tYW5kIHwgUHVycG9zZSB8IE5vdGVzIHwKfC0tLXwtLS18LS0tfAp8IGBpbml0YCB8IE1p" + - "bnQgYWRtaW4gdG9rZW4gZnJvbSBBUEkgS2V5cyB2aWEgcHVibGljIGFwaSBnYXRld2F5IHwgSWRl" + - "bXBvdGVudDsgd3JpdGVzIGB+Ly5udmNmLWNsaS5zdGF0ZWA7IGNsZWFycyBzYXZlZCBmdW5jdGlv" + - "biBzdGF0ZSBhbmQgYWxsIEFQSSBrZXlzIChmdW5jdGlvbiBhbmQgdGFzaykgfAp8IGBzdGF0dXNg" + - "IHwgU2hvdyBDTEkgc3RhdGUgKHRva2VuLCBsYXN0IGVuZHBvaW50cywgZnVuY3Rpb24gY29udGV4" + - "dCkgfCBEaXN0aW5jdCBmcm9tIGBzZWxmLWhvc3RlZCBzdGF0dXNgIHwKfCBgYXBpLWtleSBnZW5l" + - "cmF0ZSBbLS1mb3IgZnVuY3Rpb25cfHRhc2tdIFstLWRlc2NyaXB0aW9uPeKApl0gWy0tZXhwaXJl" + - "cy1pbj3igKZdYCB8IE1pbnQgQVBJIGtleXMgfCBEZWZhdWx0IChubyBgLS1mb3JgKTogZ2VuZXJh" + - "dGVzIGJvdGggYSBmdW5jdGlvbiBrZXkgYW5kIGEgdGFzayBrZXk7IGAtLWZvciBmdW5jdGlvbmAg" + - "b3IgYC0tZm9yIHRhc2tgIGdlbmVyYXRlcyBvbmUgb25seTsgYC0tc2NvcGVzYCByZXF1aXJlcyBg" + - "LS1mb3JgIHwKfCBgYXBpLWtleSBnZW5lcmF0ZSAtLXZhbGlkYXRlYCB8IE1pbnQgYW5kIGltbWVk" + - "aWF0ZWx5IHZhbGlkYXRlIGVhY2ggZ2VuZXJhdGVkIGtleSB8IFZhbGlkYXRlcyBlYWNoIGtleSBh" + - "Z2FpbnN0IGl0cyBjb3JyZWN0IGF1ZGllbmNlIChOVkNGIGZvciBmdW5jdGlvbiBrZXlzLCBOVkNU" + - "IGZvciB0YXNrIGtleXMpIHwKfCBgYXBpLWtleSBsaXN0YCB8IExpc3QgQVBJIGtleXMgZm9yIGN1" + - "cnJlbnQgb3duZXIgfCBMaXN0cyBOVkNGLXNjb3BlZCBrZXlzIG9ubHkgfAp8IGBhcGkta2V5IGRl" + - "bGV0ZSBLRVktSURgIC8gYGFwaS1rZXkgcmV2b2tlIEtFWS1JRGAgfCBSZW1vdmUgYW4gQVBJIGtl" + - "eSB8ICoqQ29uZmlybSB3aXRoIHVzZXIqKiB8CnwgYGFwaS1rZXkgc2hvd2AgfCBTaG93IGN1cnJl" + - "bnRseSBzYXZlZCBBUEkga2V5IGZyb20gc3RhdGUgfCB8CgojIyBBZ2VudCBza2lsbAoKfCBDb21t" + - "YW5kIHwgUHVycG9zZSB8IE5vdGVzIHwKfC0tLXwtLS18LS0tfAp8IGBhZ2VudC1za2lsbCBpbnN0" + - "YWxsIFstLXRhcmdldD1ESVJdYCB8IEluc3RhbGwgYWxsIGJ1bmRsZWQgcHVibGljIHVzZXIgc2tp" + - "bGxzIGludG8gYH4vLmNsYXVkZS9za2lsbHMvYCBhbmQgYH4vLmFnZW50cy9za2lsbHMvYCB8IERl" + - "ZmF1bHQ6IGJvdGggYmFzZSBza2lsbHMgZGlyZWN0b3JpZXMgfAp8IGBhZ2VudC1za2lsbCB1bmlu" + - "c3RhbGwgWy0tdGFyZ2V0PURJUl1gIHwgUmVtb3ZlIG9ubHkgYnVuZGxlZCBwdWJsaWMgdXNlciBz" + - "a2lsbHMgZnJvbSB0aGUgdGFyZ2V0IGJhc2Ugc2tpbGxzIGRpcmVjdG9yaWVzIHwgSWRlbXBvdGVu" + - "dDsgdW5yZWxhdGVkIHNraWxscyByZW1haW4gfAp8IGBhZ2VudC1za2lsbCBzaG93IFstLWZpbGU9" + - "UkVMXWAgfCBQcmludCBidW5kbGVkIGBudmNmLXNlbGYtbWFuYWdlZC1jbGkvU0tJTEwubWRgIG9y" + - "IGFub3RoZXIgZW1iZWRkZWQgZmlsZSB8IEZvciBkZWJ1Z2dpbmcgfAp8IGBhZ2VudC1za2lsbCB2" + - "ZXJzaW9uYCB8IFByaW50IGJ1aWxkIFNIQSBhbmQgZW1iZWRkZWQgcHVibGljIHVzZXIgc2tpbGwg" + - "c3VtbWFyeSB8IHwKUEsHCHIND2QhIwAAISMAAFBLAwQUAAgAAAAAACEAAAAAAAAAAAAAAAAAMgAJ" + - "AGRhdGEvbnZjZi1zZWxmLW1hbmFnZWQtY2xpL3JlZmVyZW5jZS9leGl0LWNvZGVzLm1kVVQFAAEA" + - "ps4SIyBgbnZjZi1jbGlgIGV4aXQgY29kZXMKClN0YWJsZSBhY3Jvc3Mgc3ViY29tbWFuZHMuIFVz" + - "ZSB0aGVzZSB0byBkcml2ZSBhZ2VudCByZXRyeSAvIHN1cmZhY2luZyBsb2dpYy4KCnwgQ29kZSB8" + - "IE1lYW5pbmcgfCBFeGFtcGxlIGNhdXNlcyB8CnwtLS18LS0tfC0tLXwKfCBgMGAgfCBTdWNjZXNz" + - "IHwgQWxsIGNoZWNrcyBwYXNzZWQ7IGluc3RhbGwgY29tcGxldGVkOyBkZXBsb3ltZW50IEFDVElW" + - "RSB8CnwgYDFgIHwgR2VuZXJpYyBlcnJvciB8IEhlbG0gcmVuZGVyIGZhaWxlZDsgbmV0d29yayB1" + - "bnJlYWNoYWJsZTsgZmlsZSBub3QgZm91bmQ7IFlBTUwgcGFyc2UgZXJyb3IgfAp8IGAyYCB8IFBy" + - "ZS1mbGlnaHQgY2hlY2sgZmFpbGVkIHwgR2F0ZXdheSBBUEkgQ1JEcyBtaXNzaW5nOyBrdWJlY3Rs" + - "IG5vdCBvbiBQQVRIOyBkZWZhdWx0IFN0b3JhZ2VDbGFzcyBhYnNlbnQgfAp8IGAzYCB8IEFkbWlu" + - "IGF1dGggZmFpbGVkIHwgTm8gdG9rZW4gKyBgLS1ub24taW50ZXJhY3RpdmVgIHNldDsgSUNNUyBy" + - "ZWplY3RlZCBKV1Q7IGluaXQgZW5kcG9pbnQgdW5yZWFjaGFibGUgfAp8IGA1YCB8IE1hbmlmZXN0" + - "IGFwcGx5IG9yIGAtLXdhaXRgIHRpbWVkIG91dCB8IEhlbG0gaW5zdGFsbCB0aW1lb3V0OyBjaGVj" + - "ayBwb2xsZWQgYnV0IGRpZG4ndCBwYXNzIGJlZm9yZSBkdXJhdGlvbiB8CnwgYDEzMGAgfCBDYW5j" + - "ZWxsZWQgYnkgU0lHSU5UL1NJR1RFUk0gfCBVc2VyIEN0cmwtQzsgQ0kgYnVkZ2V0IGV4Y2VlZGVk" + - "OyBwb2QgZXZpY3RlZCB8CgojIyBIb3cgYW4gYWdlbnQgc2hvdWxkIHJlYWN0Cgp8IENvZGUgfCBB" + - "Z2VudCBiZWhhdmlvciB8CnwtLS18LS0tfAp8IGAwYCB8IENvbnRpbnVlIC8gcmVwb3J0IHN1Y2Nl" + - "c3MuIHwKfCBgMWAgfCBTdXJmYWNlIHN0ZGVyciB0byB1c2VyOyBhc2sgYmVmb3JlIGFueSByZXRy" + - "eS4gR2VuZXJpYyBlcnJvcnMgdXN1YWxseSBuZWVkIGEgaHVtYW4gdG8gcmVhZCB0aGUgbWVzc2Fn" + - "ZS4gfAp8IGAyYCB8IFN1cmZhY2UgZmFpbGVkIGNoZWNrcyArIHRoZWlyIGBoaW50VVJMYC4gRG9u" + - "J3QgcHJvcG9zZSBgdXBgIHVudGlsIHRoZSB1c2VyIGZpeGVzIHByZXJlcXMuIHwKfCBgM2AgfCBT" + - "dWdnZXN0IGBudmNmLWNsaSBpbml0YCAoaW50ZXJhY3RpdmUpIE9SIGAtLXRva2VuPSRKV1RgIChD" + - "SSkuIERvbid0IGF1dG8tbWludCB3aXRob3V0IHVzZXIgT0suIHwKfCBgNWAgfCBBc2sgdXNlciBp" + - "ZiB0aGV5IHdhbnQgdG8gd2FpdCBsb25nZXIsIGRpZyBpbnRvIHRoZSBzdGFsbGVkIGNvbXBvbmVu" + - "dCwgb3IgY2FuY2VsLiB8CnwgYDEzMGAgfCBEb24ndCByZXRyeSBhdXRvbWF0aWNhbGx5OyB1c2Vy" + - "IGV4cGxpY2l0bHkgY2FuY2VsbGVkLiBDb25maXJtIGJlZm9yZSByZS1ydW5uaW5nLiB8CgojIyBX" + - "aGVyZSB0byBmaW5kIG1vcmUgZGV0YWlsCgpFdmVyeSBub24temVybyBleGl0IGVtaXRzIGEgc3Ry" + - "dWN0dXJlZCBgcGhhc2VfZmFpbGVkYCBKU09OIGV2ZW50IHdpdGggYGVyckNhdGVnb3J5YCwgYGVy" + - "ck1lc3NhZ2VgLCBgcmVtZWRpYXRpb25gIChhcnJheSksIGByZXRyeUNsYXNzYCAoZW51bTogYG5v" + - "bmV8aW1tZWRpYXRlfGJhY2tvZmZ8YWZ0ZXJfcmVtZWRpYXRpb258dW5rbm93bmApLCBgcmV0cnlB" + - "ZnRlclNlY2AgKGludCwgb3B0aW9uYWwpLCBhbmQgYHJhd2AgKHN1YnByb2Nlc3MgKyBIVFRQICsg" + - "S3ViZXJuZXRlcyBzaWduYWwpLiBBbHdheXMgY29uc3VtZSB0aGVzZSBpbiBgLS1qc29uYCBtb2Rl" + - "IHJhdGhlciB0aGFuIHBhcnNpbmcgRW5nbGlzaCBmcm9tIHN0ZGVyci4KUEsHCHOnWmTVBgAA1QYA" + - "AFBLAwQUAAgAAAAAACEAAAAAAAAAAAAAAAAALQAJAGRhdGEvbnZjZi1zZWxmLW1hbmFnZWQtY2xp" + - "L3JlZmVyZW5jZS9mbGFncy5tZFVUBQABAKbOEiMgYG52Y2YtY2xpYCBmbGFnIHJlZmVyZW5jZQoK" + - "IyMgR2xvYmFsIGZsYWdzIChldmVyeSBzdWJjb21tYW5kKQoKfCBGbGFnIHwgUHVycG9zZSB8IERl" + - "ZmF1bHQgfAp8LS0tfC0tLXwtLS18CnwgYC0tY29uZmlnIEZJTEVgIHwgQ29uZmlnIGZpbGUgcGF0" + - "aCB8IGAkSE9NRS8ubnZjZi1jbGkueWFtbGAgfAp8IGAtLWRlYnVnYCB8IFZlcmJvc2UgSFRUUCBs" + - "b2dnaW5nIG9uIHN0ZGVyciB8IGBmYWxzZWAgfAp8IGAtLWpzb25gIHwgSlNPTkwgZXZlbnRzIG9u" + - "IHN0ZGVyciB8IGF1dG8gKG9mZiBpbiBUVFksIG9uIHVuZGVyIGAtLW5vbi1pbnRlcmFjdGl2ZWAp" + - "IHwKCiMjIGBzZWxmLWhvc3RlZGAgcGFyZW50IChwZXJzaXN0ZW50IGFjcm9zcyBzdWJjb21tYW5k" + - "cykKCnwgRmxhZyB8IFB1cnBvc2UgfCBEZWZhdWx0IHwKfC0tLXwtLS18LS0tfAp8IGAtLWNvbnRy" + - "b2wtcGxhbmUtc3RhY2s94oCmYCB8IENvbnRyb2wtcGxhbmUgYnVuZGxlIHNvdXJjZTogbG9jYWwg" + - "cGF0aCwgZ2l0IFVSTCwgb3IgYG9jaTovL2AgVVJMIHwgZW1iZWRkZWQgT0NJIFVSTCBwaW5uZWQg" + - "YnkgQ0xJIHZlcnNpb24gfAp8IGAtLWNvbXB1dGUtcGxhbmUtc3RhY2s94oCmYCB8IENvbXB1dGUt" + - "cGxhbmUgYnVuZGxlIHNvdXJjZTogbG9jYWwgcGF0aCwgZ2l0IFVSTCwgb3IgYG9jaTovL2AgVVJM" + - "IHwgZW1iZWRkZWQgT0NJIFVSTCBwaW5uZWQgYnkgQ0xJIHZlcnNpb24gfAp8IGAtLWVudj1sb2Nh" + - "bFx8cHJkXHzigKZgIHwgSGVsbWZpbGUgZW52aXJvbm1lbnQgbmFtZSB8IGBsb2NhbGAgZm9yIGRl" + - "diBidWlsZHMsIGBwcmRgIGZvciByZWxlYXNlcyB8CnwgYC0tbm9uLWludGVyYWN0aXZlYCB8IERp" + - "c2FibGUgYWxsIHN0ZGluIHByb21wdHMgfCBgZmFsc2VgIHwKfCBgLS10b2tlbj0kSldUYCB8IEFk" + - "bWluIEpXVCwgb3ZlcnJpZGVzIHN0b3JlZCBzZXNzaW9uIHwgLSB8CnwgYC0tbm8tYXBwbHlgIHwg" + - "YGluc3RhbGxgIG9ubHkgLSBlbWl0IFlBTUwsIGRvIG5vdCBrdWJlY3RsIGFwcGx5IHwgYGZhbHNl" + - "YCB8CnwgYC0tb3V0cHV0PXRleHRcfGpzb25gIHwgTGVnYWN5IGFsaWFzIGZvciBgLS1qc29uYCAo" + - "ZGVwcmVjYXRlZCwgcmVtb3ZlZCBpbiBuZXh0IG1ham9yKSB8IGB0ZXh0YCB8CnwgYC0tcGxhaW5g" + - "IHwgRm9yY2UgcGxhaW4gc3RyZWFtaW5nIG91dHB1dCB8IGF1dG8tZGV0ZWN0IHwKfCBgLS13YWl0" + - "IERVUkFUSU9OYCB8IGBjaGVja2Agb25seTsgYmxvY2sgdW50aWwgcGFzcyB8IC0gfAp8IGAtLWNv" + - "bnRyb2wtcGxhbmUtY29udGV4dCBDVFhgIHwga3ViZWN0bCBjb250ZXh0IGZvciBjb250cm9sIHBs" + - "YW5lIChSRVEtMjApIHwgY3VycmVudCBjb250ZXh0IHwKfCBgLS1jb21wdXRlLXBsYW5lLWNvbnRl" + - "eHQgQ1RYYCB8IGt1YmVjdGwgY29udGV4dCBmb3IgY29tcHV0ZSBwbGFuZSAoUkVRLTIwKSB8IGN1" + - "cnJlbnQgY29udGV4dCB8CnwgYC0taWNtcy11cmwgVVJMYCB8IFB1YmxpYyBJQ01TIFVSTDsgcmVx" + - "dWlyZWQgd2hlbiBjb250ZXh0cyBkaWZmZXIgfCBkZXJpdmVkIGZyb20gYGJhc2VfaHR0cF91cmxg" + - "IHwKfCBgLS1sb2NhbC1vbmx5YCB8IGBjaGVjayAtLXByZWAgb25seTsgc2tpcCBhbGwga3ViZWN0" + - "bCBjb250YWN0IHwgYGZhbHNlYCB8CgojIyBgdXBgLXNwZWNpZmljCgp8IEZsYWcgfCBQdXJwb3Nl" + - "IHwgRGVmYXVsdCB8CnwtLS18LS0tfC0tLXwKfCBgLS1jbHVzdGVyLW5hbWUgTkFNRWAgfCBJQ01T" + - "IGNsdXN0ZXIgcm93IGlkZW50aWZpZXIgKHJlcXVpcmVkKSB8IC0gfAp8IGAtLW5jYS1pZCBJRGAg" + - "fCBOQ0EgYWNjb3VudCBJRCB8IGBudmNmLWRlZmF1bHRgIHwKfCBgLS1yZWdpb24gUkVHSU9OYCB8" + - "IENsdXN0ZXIgcmVnaW9uIHwgYHVzLXdlc3QtMWAgfAp8IGAtLXBsYW4tb25seWAgfCBEcnktcnVu" + - "OyBlbWl0IHBoYXNlIHBsYW4gKyBFVEFzIHdpdGhvdXQgY2hhbmdlcyB8IGBmYWxzZWAgfAoKIyMg" + - "YHN0YXR1c2Atc3BlY2lmaWMKCnwgRmxhZyB8IFB1cnBvc2UgfCBEZWZhdWx0IHwKfC0tLXwtLS18" + - "LS0tfAp8IGAtLWNsdXN0ZXItbmFtZSBOQU1FYCB8IExpbWl0IHRvIG9uZSBjb21wdXRlIHBsYW5l" + - "IHwgLSAoY29udHJvbCArIGFsbCBjbHVzdGVycykgfAp8IGAtLXdhdGNoYCB8IExpdmUgcmUtcmVu" + - "ZGVyIHwgYGZhbHNlYCB8CnwgYC0td2F0Y2gtaW50ZXJ2YWwgRFVSYCB8IGAtLXdhdGNoYCBjYWRl" + - "bmNlIHwgYDVzYCB8CnwgYC0tY29tcG9uZW50IE5BTUVgIHwgRmlsdGVyIGNvbXBvbmVudHMgcGFu" + - "ZWwgfCBhbGwgfAp8IGAtLW5vLWV2ZW50c2AgfCBTa2lwIGV2ZW50cyBwYW5lbCB8IGBmYWxzZWAg" + - "fAoKIyMgYGNsdXN0ZXIgcmVnaXN0ZXJgLXNwZWNpZmljCgp8IEZsYWcgfCBQdXJwb3NlIHwgRGVm" + - "YXVsdCB8CnwtLS18LS0tfC0tLXwKfCBgLS1uYW1lIE5BTUVgIHwgQ2x1c3RlciBuYW1lIChyZXF1" + - "aXJlZCkgfCAtIHwKfCBgLS1uY2EtaWQgSURgIHwgTkNBIElEIChyZXF1aXJlZCkgfCAtIHwKfCBg" + - "LS1yZWdpb24gUkVHSU9OYCB8IFJlZ2lvbiB8IGB1cy13ZXN0LTFgIHwKfCBgLS1pY21zLXVybCBV" + - "UkxgIHwgSUNNUyBlbmRwb2ludCB8IGZyb20gY29uZmlnIHwKfCBgLS1pZ25vcmUtZXhpc3Rpbmdg" + - "IHwgTWF0Y2gtb3ItY3JlYXRlIGluc3RlYWQgb2YgZmFpbC1vbi1leGlzdHMgfCBgZmFsc2VgIHwK" + - "CiMjIGBhcGkta2V5IGdlbmVyYXRlYC1zcGVjaWZpYwoKfCBGbGFnIHwgUHVycG9zZSB8IERlZmF1" + - "bHQgfAp8LS0tfC0tLXwtLS18CnwgYC0tZm9yIGZ1bmN0aW9uXHx0YXNrYCB8IEdlbmVyYXRlIG9u" + - "bHkgdGhlIHNwZWNpZmllZCBrZXkgdHlwZSB8IChvbWl0IHRvIGdlbmVyYXRlIGJvdGgpIHwKfCBg" + - "LS1kZXNjcmlwdGlvbiBURVhUYCB8IEh1bWFuLXJlYWRhYmxlIGxhYmVsIGZvciB0aGUga2V5IHwg" + - "YEdlbmVyYXRlZCBieSBudmNmLWNsaWAgfAp8IGAtLWV4cGlyZXMtaW4gRFVSQVRJT05gIHwgRXhw" + - "aXJ5IG9mZnNldCAoZS5nLiBgMjRoYCwgYDdkYCk7IHBhcnNlZCBhcyBleHRlbmRlZCBkdXJhdGlv" + - "biB8IGAyNGhgIHwKfCBgLS1zY29wZXMgU0NPUEVbLFNDT1BFXWAgfCBPdmVycmlkZSBkZWZhdWx0" + - "IHNjb3BlczsgcmVxdWlyZXMgYC0tZm9yYCB8IChzZXJ2aWNlIGRlZmF1bHRzKSB8CnwgYC0tdmFs" + - "aWRhdGVgIHwgVmFsaWRhdGUgZWFjaCBnZW5lcmF0ZWQga2V5IGltbWVkaWF0ZWx5IGFmdGVyIG1p" + - "bnRpbmcgfCBgZmFsc2VgIHwKCkV4dGVuZGVkIGR1cmF0aW9uIHVuaXRzOiBgc2AgKHNlY29uZHMp" + - "LCBgbWAgKG1pbnV0ZXMpLCBgaGAgKGhvdXJzKSwgYGRgIChkYXlzKSwgYHdgICh3ZWVrcykuCgoj" + - "IyBgZnVuY3Rpb24gY3JlYXRlYC1zcGVjaWZpYwoKYC0taW5wdXQtZmlsZSBGSUxFYCBpcyByZWNv" + - "bW1lbmRlZCBmb3IgcmVwZWF0YWJsZSBmdW5jdGlvbiBkZWZpbml0aW9ucy4gRm9yIGFkLWhvYyBj" + - "cmVhdGVzOgoKfCBGbGFnIHwgUHVycG9zZSB8IERlZmF1bHQgfAp8LS0tfC0tLXwtLS18CnwgYC0t" + - "bmFtZSBOQU1FYCB8IEZ1bmN0aW9uIG5hbWUgfCAtIHwKfCBgLS1pbWFnZSBJTUFHRWAgfCBDb250" + - "YWluZXIgaW1hZ2UgfCAtIHwKfCBgLS1pbmZlcmVuY2UtdXJsIFVSSWAgfCBDb250YWluZXIgaW5m" + - "ZXJlbmNlIGVuZHBvaW50IHwgLSB8CnwgYC0taW5mZXJlbmNlLXBvcnQgUE9SVGAgfCBDb250YWlu" + - "ZXIgaW5mZXJlbmNlIHBvcnQgfCAtIHwKfCBgLS1mdW5jdGlvbi10eXBlIERFRkFVTFRcfFNUUkVB" + - "TUlOR1x8TExNYCB8IEZ1bmN0aW9uIHR5cGUgfCBgREVGQVVMVGAgfAp8IGAtLWhlbG0tY2hhcnQg" + - "Q0hBUlRgIHwgSGVsbSBjaGFydCBVUkwgb3IgT0NJIHJlZmVyZW5jZS4gQ2FuIGJlIHVzZWQgd2l0" + - "aCBgLS1mdW5jdGlvbi10eXBlPUxMTWAgZm9yIGNoYXJ0LXBhY2thZ2VkIExMTSB3b3JrbG9hZHMu" + - "IHwgLSB8CnwgYC0taGVsbS1jaGFydC1zZXJ2aWNlIE5BTUVgIHwgS3ViZXJuZXRlcyBTZXJ2aWNl" + - "IG5hbWUgZXhwb3NlZCBieSB0aGUgY2hhcnQuIFJlcXVpcmVkIHdoZW4gYC0taGVsbS1jaGFydGAg" + - "aXMgc2V0LiB8IC0gfAp8IGAtLW1vZGVscyBOQU1FOlZFUlNJT046VVJJYCB8IFN0YW5kYXJkIG1v" + - "ZGVsIGFydGlmYWN0OyByZXBlYXRhYmxlIHwgLSB8CnwgYC0tbGxtLW1vZGVsIFNQRUNgIHwgTExN" + - "IG1vZGVsIGNvbmZpZzsgZm9ybWF0IGBuYW1lPTxtb2RlbD4sdXJpcz08dXJpPlx8PHVyaT4scm91" + - "dGluZ01ldGhvZD08bWV0aG9kPix0b2tlblJhdGVMaW1pdD08bGltaXQ+YDsgcmVwZWF0YWJsZS4g" + - "VG9rZW4gbGltaXRzIHVzZSBgPHZhbHVlPi08dW5pdD5gIHdpdGggYFNgLCBgTWAsIGBIYCwgYERg" + - "LCBvciBgV2AsIGZvciBleGFtcGxlIGAxMDAwLVNgLiBVc2UgaW5wdXQgSlNPTiBmb3IgY29tYmlu" + - "ZWQgdG9rZW4gbGltaXRzIGJlY2F1c2UgaW5saW5lIHNwZWNzIHVzZSBjb21tYXMgYXMgZmllbGQg" + - "c2VwYXJhdG9ycy4gfCAtIHwKfCBgLS1sbG0tZGVmYXVsdC1wcmlvcml0eSBQUklPUklUWWAgfCBG" + - "dW5jdGlvbi1sZXZlbCBkZWZhdWx0IHJlcXVlc3QgcHJpb3JpdHkuIExvd2VyIHZhbHVlcyBoYXZl" + - "IGhpZ2hlciBwcmlvcml0eS4gfCAtIHwKfCBgLS1sbG0tcGVyLWFjY291bnQtcHJpb3JpdHkgTkNB" + - "LUlEOlBSSU9SSVRZYCB8IFBlci1hY2NvdW50IHByaW9yaXR5IG92ZXJyaWRlOyByZXBlYXRhYmxl" + - "OyBzdXBwb3J0cyB1cCB0byA2NCBkaXN0aW5jdCBOQ0EgSUQgb3ZlcnJpZGVzLiBSZXF1aXJlcyBh" + - "IGRlZmF1bHQgcHJpb3JpdHkuIHwgLSB8CgpJbiBKU09OIGFuZCBpbmxpbmUgc3BlY3MsIExMTSBm" + - "dW5jdGlvbnMgc2V0IGBmdW5jdGlvblR5cGU6ICJMTE0iYCBhbmQgbW9kZWwgcm91dGluZyBtZXRh" + - "ZGF0YSB1bmRlciBgbW9kZWxzW10ubGxtQ29uZmlnYC4gYGxsbUNvbmZpZy51cmlzYCBkZWNsYXJl" + - "cyB0aGUgT3BlbkFJLWNvbXBhdGlibGUgdXBzdHJlYW0gcGF0aHMgZXhwb3NlZCBieSB0aGUgbW9k" + - "ZWwuIEN1cnJlbnQgc3VwcG9ydGVkIHBhdGhzIGFyZSBgL3YxL2NoYXQvY29tcGxldGlvbnNgLCBg" + - "L3YxL3Jlc3BvbnNlc2AsIGFuZCBgL3YxL2VtYmVkZGluZ3NgLiBgbGxtQ29uZmlnLnJvdXRpbmdN" + - "ZXRob2RgIGFjY2VwdHMgYHJvdW5kX3JvYmluYCwgYHBvd2VyX29mX3R3b2AsIGBncm9xX211bHRp" + - "cmVnaW9uYCwgYHB1bHNhcmAsIG9yIGByYW5kb21gLgpgbGxtQ29uZmlnLnRva2VuUmF0ZUxpbWl0" + - "YCBhY2NlcHRzIG9uZSBvciBtb3JlIGNvbW1hLXNlcGFyYXRlZCBwb3NpdGl2ZSBpbnRlZ2VyIHRv" + - "a2VuIGxpbWl0cyBpbiBgPHZhbHVlPi08dW5pdD5gIGZvcm1hdC4gU3VwcG9ydGVkIHVuaXRzIGFy" + - "ZSBgU2AgKHNlY29uZHMpLCBgTWAgKG1pbnV0ZXMpLCBgSGAgKGhvdXJzKSwgYERgIChkYXlzKSwg" + - "YW5kIGBXYCAod2Vla3MpLiBVc2UgZGlzdGluY3QgdW5pdHMgd2hlbiBjb21iaW5pbmcgbGltaXRz" + - "LCBmb3IgZXhhbXBsZSBgMTAwMC1TLDUwMDAtTSwxMDAwMDAtSCw1MDAwMDAtRCwxMDAwMDAwLVdg" + - "IGluIGlucHV0IEpTT04uCgpIZWxtIGNoYXJ0IHBhY2thZ2luZyBpcyBpbmRlcGVuZGVudCBvZiBg" + - "ZnVuY3Rpb25UeXBlYC4gRm9yIGEgSGVsbS1jaGFydCBiYWNrZWQgTExNIGZ1bmN0aW9uLCBzZXQg" + - "YGZ1bmN0aW9uVHlwZTogIkxMTSJgLCBgaGVsbUNoYXJ0YCwgYGhlbG1DaGFydFNlcnZpY2VOYW1l" + - "YCwgYW5kIGBtb2RlbHNbXS5sbG1Db25maWdgIGluIHRoZSBzYW1lIGNyZWF0ZSByZXF1ZXN0LiBg" + - "aW5mZXJlbmNlUG9ydGAgbXVzdCBiZSB0aGUgS3ViZXJuZXRlcyBTZXJ2aWNlIHBvcnQgZXhwb3Nl" + - "ZCBieSBgaGVsbUNoYXJ0U2VydmljZU5hbWVgLgoKTExNIGludm9jYXRpb24gcmVxdWVzdHMgdXNl" + - "IGBtb2RlbDogIjxmdW5jdGlvbi1pZD4vPG1vZGVsLW5hbWU+ImAuIFRoZSBmdW5jdGlvbiBJRCBz" + - "ZWxlY3RzIHRoZSBOVkNGIGZ1bmN0aW9uLCBhbmQgdGhlIG1vZGVsIG5hbWUgaXMgZm9yd2FyZGVk" + - "IHVwc3RyZWFtLiBDaGF0IGNvbXBsZXRpb25zIGFuZCBSZXNwb25zZXMgQVBJIHJlcXVlc3RzIGNh" + - "biB1c2UgYHgtbXVsdGktdHVybi1zZXNzaW9uLWlkYCBmb3Igc2Vzc2lvbiBzdGlja2luZXNzOyBl" + - "bWJlZGRpbmdzIHJlcXVlc3RzIGRvIG5vdC4KCiMjIGBmdW5jdGlvbiB1cGRhdGVgLXNwZWNpZmlj" + - "Cgp8IEZsYWcgfCBQdXJwb3NlIHwgRGVmYXVsdCB8CnwtLS18LS0tfC0tLXwKfCBgLS10YWdzIFRB" + - "R1ssVEFHXWAgfCBSZXBsYWNlIGZ1bmN0aW9uIHRhZ3MgfCAtIHwKfCBgLS1sbG0tbW9kZWwtdXBk" + - "YXRlIFNQRUNgIHwgTExNIG1vZGVsIHVwZGF0ZTsgZm9ybWF0IGBuYW1lPTxtb2RlbD4scm91dGlu" + - "Z01ldGhvZD08bWV0aG9kPix0b2tlblJhdGVMaW1pdD08bGltaXQ+YDsgcmVwZWF0YWJsZS4gUm91" + - "dGluZyBtZXRob2RzIG1hdGNoIGAtLWxsbS1tb2RlbGAuIFRva2VuIGxpbWl0IGV4YW1wbGU6IGAx" + - "MDAwLVNgLiBVc2UgaW5wdXQgSlNPTiBmb3IgY29tYmluZWQgdG9rZW4gbGltaXRzLiB8IC0gfAp8" + - "IGAtLWxsbS1kZWZhdWx0LXByaW9yaXR5IFBSSU9SSVRZYCB8IFJlcGxhY2UgdGhlIGZ1bmN0aW9u" + - "LWxldmVsIHByaW9yaXR5IGNvbmZpZ3VyYXRpb24gd2l0aCB0aGlzIGRlZmF1bHQgYW5kIGFueSBz" + - "dXBwbGllZCBwZXItYWNjb3VudCBvdmVycmlkZXMuIHwgLSB8CnwgYC0tbGxtLXBlci1hY2NvdW50" + - "LXByaW9yaXR5IE5DQS1JRDpQUklPUklUWWAgfCBQZXItYWNjb3VudCBwcmlvcml0eSBvdmVycmlk" + - "ZTsgcmVwZWF0YWJsZTsgc3VwcG9ydHMgdXAgdG8gNjQgZGlzdGluY3QgTkNBIElEIG92ZXJyaWRl" + - "cy4gUmVxdWlyZXMgYSBkZWZhdWx0IHByaW9yaXR5LiB8IC0gfAoKSW4gSlNPTiwgYGZ1bmN0aW9u" + - "IHVwZGF0ZWAgYWNjZXB0cyBgbW9kZWxVcGRhdGVzW11gIGVudHJpZXMgd2l0aCBgbW9kZWxOYW1l" + - "YCBhbmQgYGxsbUNvbmZpZy5yb3V0aW5nTWV0aG9kYCBhbmQvb3IgYGxsbUNvbmZpZy50b2tlblJh" + - "dGVMaW1pdGAuIGB1cmlzYCBhcmUgY3JlYXRlLXRpbWUgbW9kZWwgbWV0YWRhdGEgYW5kIGFyZSBu" + - "b3QgcGFydCBvZiBtb2RlbCB1cGRhdGVzLgoKIyMgYGZ1bmN0aW9uIGRlcGxveSBjcmVhdGVgLXNw" + - "ZWNpZmljCgpgLS1pbnB1dC1maWxlIEZJTEVgIGlzIHJlY29tbWVuZGVkIChmdWxsIEpTT04gc3Bl" + - "YykuIEZvciBhZC1ob2Mgb25lLXNob3QgdXNlOgoKfCBGbGFnIHwgUHVycG9zZSB8IERlZmF1bHQg" + - "fAp8LS0tfC0tLXwtLS18CnwgYC0tZnVuY3Rpb24taWQgSURgIHwgRnVuY3Rpb24gSUQgKHJlcXVp" + - "cmVkKSB8IC0gfAp8IGAtLWdwdSBOQU1FYCB8IEdQVSBmYW1pbHkgfCBgSDEwMGAgfAp8IGAtLWlu" + - "c3RhbmNlLXR5cGUgVFlQRWAgfCBTS1UgfCBgTkNQLkdQVS5IMTAwXzF4YCB8CnwgYC0tbWluLWlu" + - "c3RhbmNlcyBOYCB8IE1pbiByZXBsaWNhcyB8IGAxYCB8CnwgYC0tbWF4LWluc3RhbmNlcyBOYCB8" + - "IE1heCByZXBsaWNhcyB8IGAxYCB8CnwgYC0tbWF4LXJlcXVlc3QtY29uY3VycmVuY3kgTmAgfCBD" + - "b25jdXJyZW5jeSB8IGAwYCAodW5zZXQ7IHNlcnZlciBkZWZhdWx0IGFwcGxpZXMpIHwKfCBgLS1j" + - "bHVzdGVycyBOQU1FWyxOQU1FXWAgfCBUYXJnZXQgc3BlY2lmaWMgY29tcHV0ZSBjbHVzdGVycyB8" + - "IChhbnkgbWF0Y2hpbmcgU0tVKSB8CgojIyBgdGFzayBjcmVhdGVgLXNwZWNpZmljCgpgLS1pbnB1" + - "dC1maWxlIEZJTEVgIGlzIHJlY29tbWVuZGVkIGZvciByZXBlYXRhYmxlIHRhc2sgZGVmaW5pdGlv" + - "bnMuIEZvciBhZC1ob2MgdXNlOgoKfCBGbGFnIHwgUHVycG9zZSB8IE5vdGVzIHwKfC0tLXwtLS18" + - "LS0tfAp8IGAtLW5hbWUgTkFNRWAgfCBUYXNrIG5hbWUgKHJlcXVpcmVkKSB8IDEtMTI4IGNoYXJz" + - "LCBgXlthLXowLTlBLVpdW2EtejAtOUEtWlwtX10qJGAgfAp8IGAtLWdwdSBHUFVgIHwgR1BVIG5h" + - "bWUgKHJlcXVpcmVkIHdpdGhvdXQgYC0taW5wdXQtZmlsZWApIHwgRXhhbXBsZTogYEgxMDBgIHwK" + - "fCBgLS1pbnN0YW5jZS10eXBlIFRZUEVgIHwgSW5zdGFuY2UgdHlwZSAocmVxdWlyZWQgd2l0aG91" + - "dCBgLS1pbnB1dC1maWxlYCkgfCBFeGFtcGxlOiBgTkNQLkdQVS5IMTAwXzF4YCB8CnwgYC0taW1h" + - "Z2UgSU1BR0VgIHwgQ29udGFpbmVyIGltYWdlIHwgTXV0dWFsbHkgZXhjbHVzaXZlIHdpdGggYC0t" + - "aGVsbS1jaGFydGAgfAp8IGAtLWhlbG0tY2hhcnQgQ0hBUlRgIHwgSGVsbSBjaGFydCBVUkwgb3Ig" + - "T0NJIHJlZmVyZW5jZSB8IE11dHVhbGx5IGV4Y2x1c2l2ZSB3aXRoIGAtLWltYWdlYCB8CnwgYC0t" + - "aW5wdXQtZmlsZSBGSUxFYCB8IEpTT04gdGFzayBzcGVjIGZpbGUgfCBDTEkgZmxhZ3Mgb3ZlcnJp" + - "ZGUgSlNPTiB2YWx1ZXMgd2hlbiBib3RoIGFyZSBzZXQgfAp8IGAtLWJhY2tlbmQgQkFDS0VORGAg" + - "fCBCYWNrZW5kIC8gQ1NQIHwgLSB8CnwgYC0tY2x1c3RlcnMgTkFNRVssTkFNRV1gIHwgU3BlY2lm" + - "aWMgY2x1c3RlcnMgd2l0aGluIHRoZSBpbnN0YW5jZSB8IENvbW1hLXNlcGFyYXRlZCB8CnwgYC0t" + - "Y29udGFpbmVyLWFyZ3MgQVJHU2AgfCBBcmdzIHBhc3NlZCB3aGVuIGxhdW5jaGluZyB0aGUgY29u" + - "dGFpbmVyIHwgU2luZ2xlIHN0cmluZywgZS5nLiBgIi0tZXBvY2hzIDEwIC0tYmF0Y2gtc2l6ZSAz" + - "MiJgIHwKfCBgLS1jb250YWluZXItZW52IE5BTUU9dmFsdWVgIHwgQ29udGFpbmVyIGVudmlyb25t" + - "ZW50IHZhcmlhYmxlOyByZXBlYXRhYmxlIHwgLSB8CnwgYC0tc2VjcmV0cyBOQU1FPXZhbHVlYCB8" + - "IFNlY3JldCBlbnZpcm9ubWVudCB2YXJpYWJsZTsgcmVwZWF0YWJsZTsgZW5jcnlwdGVkIGF0IHJl" + - "c3QgfCAtIHwKfCBgLS1tYXgtcnVudGltZSBEVVJBVElPTmAgfCBNYXggd2FsbC1jbG9jayBydW4g" + - "dGltZSAoSVNPIDg2MDEsIGUuZy4gYFBUNEhgKSB8IE5vIGRlZmF1bHQ7IG9taXQgZm9yIG5vIHRp" + - "bWUgbGltaXQgfAp8IGAtLW1heC1xdWV1ZWQgRFVSQVRJT05gIHwgTWF4IHRpbWUgaW4gcXVldWUg" + - "YmVmb3JlIGNhbmNlbGxhdGlvbiAoSVNPIDg2MDEpIHwgU2VydmVyIGRlZmF1bHQgYFBUNzJIYCB8" + - "CnwgYC0tdGVybWluYXRpb24tZ3JhY2UgRFVSQVRJT05gIHwgR3JhY2UgcGVyaW9kIGFmdGVyIHN0" + - "b3Agc2lnbmFsIChJU08gODYwMSkgfCBTZXJ2ZXIgZGVmYXVsdCBgUFQxSGAgfAp8IGAtLXJlc3Vs" + - "dC1zdHJhdGVneSBVUExPQURcfE5PTkVgIHwgSG93IHRvIGhhbmRsZSB0YXNrIG91dHB1dCBhcnRp" + - "ZmFjdHMgfCAtIHwKfCBgLS1yZXN1bHRzLWxvY2F0aW9uIExPQ0FUSU9OYCB8IFJlc3VsdCB1cGxv" + - "YWQgdGFyZ2V0LCBmb3JtYXQgYG9yZy1uYW1lL1t0ZWFtLW5hbWUvXW1vZGVsLW5hbWVgOyByZXF1" + - "aXJlZCB3aGVuIGAtLXJlc3VsdC1zdHJhdGVneT1VUExPQURgIHwgLSB8CnwgYC0tbW9kZWxzIE5B" + - "TUU6VkVSU0lPTjpVUklgIHwgTW9kZWwgYXJ0aWZhY3Q7IHJlcGVhdGFibGUgfCAtIHwKfCBgLS1y" + - "ZXNvdXJjZXMgTkFNRTpWRVJTSU9OOlVSSWAgfCBSZXNvdXJjZSBhcnRpZmFjdDsgcmVwZWF0YWJs" + - "ZSB8IC0gfAp8IGAtLXRhZ3MgVEFHWyxUQUddYCB8IFRhc2sgdGFncyB8IENvbW1hLXNlcGFyYXRl" + - "ZCB8CnwgYC0tZGVzY3JpcHRpb24gVEVYVGAgfCBUYXNrIGRlc2NyaXB0aW9uIHwgLSB8CnwgYC0t" + - "bG9ncy10ZWxlbWV0cnktaWQgVVVJRGAgfCBMb2dzIHRlbGVtZXRyeSBlbmRwb2ludCBJRCB8IC0g" + - "fAp8IGAtLW1ldHJpY3MtdGVsZW1ldHJ5LWlkIFVVSURgIHwgTWV0cmljcyB0ZWxlbWV0cnkgZW5k" + - "cG9pbnQgSUQgfCAtIHwKfCBgLS10cmFjZXMtdGVsZW1ldHJ5LWlkIFVVSURgIHwgVHJhY2VzIHRl" + - "bGVtZXRyeSBlbmRwb2ludCBJRCB8IC0gfApQSwcI5GexzC0lAAAtJQAAUEsDBBQACAAAAAAAIQAA" + - "AAAAAAAAAAAAAAA3AAkAZGF0YS9udmNmLXNlbGYtbWFuYWdlZC1jbGkvcmVmZXJlbmNlL3Ryb3Vi" + - "bGVzaG9vdGluZy5tZFVUBQABAKbOEiMgVHJvdWJsZXNob290aW5nCgpLbm93biBlcnJvcnMg4oaS" + - "IGRpYWdub3N0aWMgY29tbWFuZCDihpIgcmVtZWRpYXRpb24uIEtlZXAgaW4gc3luYyB3aXRoIHRo" + - "ZSBzdHJ1Y3R1cmVkIGBwaGFzZV9mYWlsZWRgIGV2ZW50cyB0aGUgQ0xJIGVtaXRzIChSRVEtMTUp" + - "IOKAlCBldmVyeSBlbnRyeSBiZWxvdyBzaG91bGQgbWFwIHRvIGFuIGBlcnJDYXRlZ29yeWAgYW5k" + - "IGEgYHJlbWVkaWF0aW9uYCBhcnJheS4KCiMjIFByZS1mbGlnaHQKCnwgU3ltcHRvbSB8IENhdXNl" + - "IHwgUmVtZWRpYXRpb24gfAp8LS0tfC0tLXwtLS18CnwgYGt1YmVjdGwgbm90IG9uIFBBVEhgIHwg" + - "T3BlcmF0b3IncyBgJFBBVEhgIGRvZXNuJ3QgaW5jbHVkZSBrdWJlY3RsIHwgSW5zdGFsbCBrdWJl" + - "Y3RsIDEuMjgrOiBodHRwczovL2t1YmVybmV0ZXMuaW8vZG9jcy90YXNrcy90b29scy9pbnN0YWxs" + - "LWt1YmVjdGwvIHwKfCBgaGVsbWZpbGUgbm90IG9uIFBBVEhgIHwgU2FtZSB8IGh0dHBzOi8vZ2l0" + - "aHViLmNvbS9oZWxtZmlsZS9oZWxtZmlsZSNpbnN0YWxsYXRpb24gfAp8IGBoZWxtIG5vdCBvbiBQ" + - "QVRIYCB8IFNhbWUgfCBodHRwczovL2hlbG0uc2gvZG9jcy9pbnRyby9pbnN0YWxsLyB8CnwgYEdh" + - "dGV3YXkgQVBJIENSRHMgbm90IGluc3RhbGxlZGAgfCBlbnZveS1nYXRld2F5IHByZXJlcSBtaXNz" + - "aW5nIHwgYGhlbG0gaW5zdGFsbCBlZyBvY2k6Ly9kb2NrZXIuaW8vZW52b3lwcm94eS9nYXRld2F5" + - "LWhlbG0gLS1uYW1lc3BhY2U9ZW52b3ktZ2F0ZXdheS1zeXN0ZW0gLS1jcmVhdGUtbmFtZXNwYWNl" + - "YCB8CnwgYGRlZmF1bHQgU3RvcmFnZUNsYXNzIG5vdCBhdmFpbGFibGVgIHwgQ2x1c3RlciBoYXMg" + - "bm8gZGVmYXVsdCBTQyB8IEFubm90YXRlIG9uZTogYGt1YmVjdGwgcGF0Y2ggc2MgPG5hbWU+IC1w" + - "ICd7Im1ldGFkYXRhIjp7ImFubm90YXRpb25zIjp7InN0b3JhZ2VjbGFzcy5rdWJlcm5ldGVzLmlv" + - "L2lzLWRlZmF1bHQtY2xhc3MiOiJ0cnVlIn19fSdgIHwKfCBgR1BVIG9wZXJhdG9yIG5vdCBpbnN0" + - "YWxsZWRgIChjb21wdXRlLXBsYW5lKSB8IG52aWRpYSBHUFUgb3BlcmF0b3IgbWlzc2luZyB8IElu" + - "c3RhbGwgcGVyIGh0dHBzOi8vZG9jcy5udmlkaWEuY29tL2RhdGFjZW50ZXIvY2xvdWQtbmF0aXZl" + - "L2dwdS1vcGVyYXRvci9sYXRlc3QvaW5zdGFsbC1ncHUtb3BlcmF0b3IuaHRtbCwgT1IgdXNlIGBm" + - "YWtlLWdwdS1vcGVyYXRvcmAgZm9yIGRldiB8CnwgYEdQVSBub2RlIGxhYmVscyBtaXNzaW5nYCAo" + - "Y29tcHV0ZS1wbGFuZSkgfCBOb2RlcyBuZWVkIGBudmlkaWEuY29tL2dwdS5mYW1pbHlgIGV0Yy4g" + - "fCBTZXQgYC0tazNzLW5vZGUtbGFiZWwgbnZpZGlhLmNvbS9ncHUuZmFtaWx5PWhvcHBlckBhZ2Vu" + - "dDpOYCBmb3IgazNkIHwKCiMjIEF1dGgKCnwgU3ltcHRvbSB8IENhdXNlIHwgUmVtZWRpYXRpb24g" + - "fAp8LS0tfC0tLXwtLS18CnwgYGFkbWluIHRva2VuIHJlcXVpcmVkYCAoaW5pdCBub3QgcnVuKSB8" + - "IE5vIGB+Ly5udmNmLWNsaS5zdGF0ZWAgYW5kIGAtLXRva2VuYCBub3QgcGFzc2VkIHwgYG52Y2Yt" + - "Y2xpIGluaXRgIChpbnRlcmFjdGl2ZSkgb3IgYC0tdG9rZW49JEpXVCAtLW5vbi1pbnRlcmFjdGl2" + - "ZWAgKENJKSB8CnwgYFNpZ25lZCBKV1QgcmVqZWN0ZWQ6IEFub3RoZXIgYWxnb3JpdGhtIGV4cGVj" + - "dGVkLCBvciBubyBtYXRjaGluZyBrZXkocykgZm91bmRgIHwgU3RhbGUgc2Vzc2lvbi5qc29uIGZy" + - "b20gYSBkaWZmZXJlbnQgY29udHJvbCBwbGFuZSB8IGBybSB+Ly5udmNmLWNsaS5zdGF0ZWAgdGhl" + - "biBgbnZjZi1jbGkgaW5pdGAgYWdhaW4gfAp8IGA0MDMgbWlzc2luZyByZXF1ZXN0ZWQgYXV0aG9y" + - "aXRpZXNgIChmdW5jdGlvbiBpbnZva2UpIHwgQWRtaW4gdG9rZW4gZG9lc24ndCBoYXZlIGBpbnZv" + - "a2VfZnVuY3Rpb25gIHNjb3BlIHwgYG52Y2YtY2xpIGFwaS1rZXkgZ2VuZXJhdGUgLS1kZXNjcmlw" + - "dGlvbj3igKZgIHRoZW4gcmUtcnVuIGludm9rZSB8CgojIyBJbnN0YWxsIC8gYHVwYAoKfCBTeW1w" + - "dG9tIHwgQ2F1c2UgfCBSZW1lZGlhdGlvbiB8CnwtLS18LS0tfC0tLXwKfCBIZWxtIGB0aW1lZCBv" + - "dXQgd2FpdGluZyBmb3IgY2Fzc2FuZHJhYCB8IENhc3NhbmRyYSBTdGF0ZWZ1bFNldCBQVkMgc3R1" + - "Y2sgYFBlbmRpbmdgIHwgQ29uZmlybSBkZWZhdWx0IFN0b3JhZ2VDbGFzczsgY2hlY2sgYGt1YmVj" + - "dGwgZ2V0IHB2LHB2YyAtbiBjYXNzYW5kcmEtc3lzdGVtYCB8CnwgYEludmFsaWQgR1BVICdOQ1Au" + - "R1BVLkgxMDAnIHNwZWNpZmllZGAgKGZ1bmN0aW9uIGRlcGxveSkgfCBXcm9uZyBmaWVsZCDigJQg" + - "YGdwdWAgaXMgdGhlIGZhbWlseSwgbm90IHRoZSBTS1UgfCBVc2UgYCJncHUiOiAiSDEwMCJgLCBg" + - "Imluc3RhbmNlVHlwZSI6ICJOQ1AuR1BVLkgxMDBfMXgiYCB8CnwgYHJlZ2lvbiBtdXN0IGJlIHBy" + - "b3ZpZGVkYCAoY2x1c3RlciByZWdpc3RlciAvIGluc3RhbGwgLS1jb21wdXRlLXBsYW5lKSB8IGAt" + - "LXJlZ2lvbmAgZW1wdHkgKyBgQ0xVU1RFUl9SRUdJT05gIGVudiBlbXB0eSB8IFBhc3MgYC0tcmVn" + - "aW9uPXVzLXdlc3QtMWAgKG9yIGFueSBub24tZW1wdHkgdmFsdWUpIHwKfCBgcmVxdWlyZWRFbnYg" + - "Ik5DQV9JRCIgaXMgbm90IHNldGAgKGhlbG1maWxlIGVycm9yIGR1cmluZyBjb21wdXRlLXBsYW5l" + - "IGFwcGx5KSB8IE9sZGVyIG52Y2YtY2xpIGJlZm9yZSBjb21taXQgYDZhYTU3MDRgOyBFeHRyYUVu" + - "diBub3QgcHJvcGFnYXRpbmcgTkNBX0lEIHwgVXBncmFkZSBgbnZjZi1jbGlgIHRvIGEgcmVsZWFz" + - "ZSBhZnRlciBgNmFhNTcwNGAgfAp8IGBudmNhLW9wZXJhdG9yOiBJbWFnZVB1bGxCYWNrT2ZmYCBm" + - "b3IgYG52Y2Etb3BlcmF0b3I6cHNhdGAgfCBJbWFnZSBub3QgaW4gY2x1c3RlciByZWdpc3RyeTsg" + - "bXVsdGktY2x1c3RlciBidWlsZCBpcyBsb2NhbCB8IGBrM2QgaW1hZ2UgaW1wb3J0IG52Y3IuaW8v" + - "Li4uL252Y2Etb3BlcmF0b3I6cHNhdCAtYyA8Y2x1c3Rlcj5gIHwKCiMjIENvbXB1dGUtcGxhbmUg" + - "cnVudGltZQoKfCBTeW1wdG9tIHwgQ2F1c2UgfCBSZW1lZGlhdGlvbiB8CnwtLS18LS0tfC0tLXwK" + - "fCBOVkNBIGFnZW50IENyYXNoTG9vcEJhY2tPZmYgd2l0aCBgSFRUUCA0MDFgIG9uIGAvdjEvbnZj" + - "YS9jbHVzdGVycy8uLi4vcmVnaXN0ZXJgIHwgU3RhbGUgYG91dC88Y2x1c3Rlcj4tcmVnaXN0ZXIt" + - "dmFsdWVzLnlhbWxgIGZyb20gcHJpb3IgcmVnaXN0ZXI7IGNsdXN0ZXIgSUQgZG9lc24ndCBtYXRj" + - "aCBJQ01TIHwgRGVsZXRlIHRoZSBzdGFsZSBmaWxlOyByZS1ydW4gYG52Y2YtY2xpIHNlbGYtaG9z" + - "dGVkIHVwYCAoaWRlbXBvdGVudCByZWdpc3RlciB3cml0ZXMgYSBmcmVzaCBmaWxlKSB8CnwgYE5v" + - "IGNsdXN0ZXIgZm91bmQgd2l0aCB2YWxpZCBKV0tTIGZvciBjbHVzdGVyIElEOiA8aWQ+YCAoSUNN" + - "UyBsb2cpIHwgSldLUyBpbiBJQ01TIGRvZXNuJ3QgbWF0Y2ggd2hhdCBjb21wdXRlLXBsYW5lIEs4" + - "cyBpcyBjdXJyZW50bHkgc2lnbmluZyB8IGBudmNmLWNsaSBjbHVzdGVyIHJvdGF0ZSAtLWNsdXN0" + - "ZXItaWQ9PGlkPmAgfAp8IEZ1bmN0aW9uIHBvZCBgSW1hZ2VQdWxsQmFja09mZmAgaW4gYG52Y2Yt" + - "YmFja2VuZGAgfCBJbWFnZS1jcmVkZW50aWFsLWhlbHBlciBjb25maWcgbWlzc2luZyBvciB3cm9u" + - "ZyB8IGBrdWJlY3RsIGRlc2NyaWJlIHBvZCAtbiBudmNmLWJhY2tlbmQgPHBvZD5gOyBjaGVjayB0" + - "aGUgcHVsbCBzZWNyZXQgY29uZmlnIHwKfCBGdW5jdGlvbiBkZXBsb3kgc3R1Y2sgYERFUExPWUlO" + - "R2AgZm9yID41IG1pbiB8IE5BVFMgc3RyZWFtIGluaXQgbGFnIChjb2xkLWNsdXN0ZXIpIOKAlCB3" + - "YWl0LCBvciBjaGVjayBOQVRTIGF1dGgtY2FsbG91dCBoZWFsdGggfCBga3ViZWN0bCBsb2dzIC1u" + - "IG5hdHMtc3lzdGVtIC1sIGFwcC5rdWJlcm5ldGVzLmlvL25hbWU9bmF0cy1hdXRoLWNhbGxvdXQt" + - "c2VydmljZWAgfAoKIyMgVGFza3MKCnwgU3ltcHRvbSB8IENhdXNlIHwgUmVtZWRpYXRpb24gfAp8" + - "LS0tfC0tLXwtLS18CnwgYHRhc2sgY3JlYXRlYCByZXR1cm5zIGBNaXNzaW5nIDxUWVBFPiByZWdp" + - "c3RyeSBjcmVkZW50aWFsIGZvciBob3N0bmFtZWAsIG9yIGtlZXBzIHVzaW5nIGEgcHJldmlvdXMg" + - "Y3JlZGVudGlhbCB2YWx1ZSwgc2hvcnRseSBhZnRlciBgcmVnaXN0cnktY3JlZGVudGlhbCBhZGQv" + - "dXBkYXRlL2RlbGV0ZWAgd2hpbGUgYHJlZ2lzdHJ5LWNyZWRlbnRpYWwgbGlzdGAvYGdldGAgYWxy" + - "ZWFkeSBzaG93IHRoZSBuZXcgdmFsdWUgfCBFeHBlY3RlZCBwcm9wYWdhdGlvbiBkZWxheTogTlZD" + - "VCBjYWNoZXMgdGhlIGFjY291bnQncyByZWdpc3RyeSBjcmVkZW50aWFscyB3aXRoIGBudmN0Lm52" + - "Y2YuY2FjaGUtdHRsYCAoZGVmYXVsdCBgUFQ1TWAsIGFib3V0IDUgbWluKSBhbmQgYXBwbGllcyB0" + - "aGUgY2hhbmdlIHdoZW4gdGhhdCBjYWNoZWQgY29weSByZWZyZXNoZXMgfCBXYWl0IHVwIHRvIGFi" + - "b3V0IDUgbWluIGFuZCByZXRyeSwgb3IgYGt1YmVjdGwgLW4gbnZjZiByb2xsb3V0IHJlc3RhcnQg" + - "ZGVwbG95bWVudC9udmN0LWFwaWAgdG8gYXBwbHkgaW1tZWRpYXRlbHkgfApQSwcI864ThHQRAAB0" + - "EQAAUEsDBBQACAAAAAAAIQAAAAAAAAAAAAAAAAAsAAkAZGF0YS9udmNmLXNlbGYtbWFuYWdlZC1p" + - "bnN0YWxsYXRpb24vU0tJTEwubWRVVAUAAQCmzhItLS0KbmFtZTogbnZjZi1zZWxmLW1hbmFnZWQt" + - "aW5zdGFsbGF0aW9uCmRlc2NyaXB0aW9uOiA+LQogIEluc3RhbGwgYW5kIG9wZXJhdGUgTlZDRiBz" + - "ZWxmLWhvc3RlZCBjb250cm9sLXBsYW5lIGFuZCBzZXBhcmF0ZSBjb21wdXRlLXBsYW5lCiAgc3Rh" + - "Y2tzLiBDb3ZlcnMgSGVsbWZpbGUgdmFsdWVzIGFuZCBDTEkgcHJvZmlsZSBpbnN0YWxsYXRpb24g" + - "Zmxvd3MsIHRlYXJkb3duLAogIHZhbHVlcyBvdmVycmlkZXMsIHB1bGwgc2VjcmV0cywgYW5kIHRy" + - "b3VibGVzaG9vdGluZy4gVXNlIGZvcgogIG52Y2Ytc2VsZi1tYW5hZ2VkLXN0YWNrLCBudmNmLWNv" + - "bXB1dGUtcGxhbmUtc3RhY2ssIHNwbGl0IGNvbXB1dGUtcGxhbmUKICBpbnN0YWxsYXRpb24sIGNv" + - "bnRyb2wtcGxhbmUgaW5zdGFsbGF0aW9uLCBDTEktZ2VuZXJhdGVkIGNvbnRyb2wtcGxhbmUKICBw" + - "cm9maWxlcywgSGVsbWZpbGUsIHNlbGYtbWFuYWdlZCwgb3Igc2VsZi1ob3N0ZWQgZGVwbG95bWVu" + - "dHMuIERvIE5PVCB1c2UgZm9yCiAgbG9jYWwgazNkIGVudmlyb25tZW50czsgdXNlIHRoZSBsb2Nh" + - "bCBrM2QgZGV2ZWxvcG1lbnQgd29ya2Zsb3cgaW5zdGVhZC4KbGljZW5zZTogQXBhY2hlLTIuMApj" + - "b21wYXRpYmlsaXR5OiBSZXF1aXJlcyBoZWxtZmlsZSA+PSAxLjEuMCA8IDEuMi4wLCBoZWxtID49" + - "IDMuMTIsIGhlbG0tZGlmZiBwbHVnaW4sIGt1YmVjdGwgbWF0Y2hpbmcgY2x1c3RlciB2ZXJzaW9u" + - "CmF1dGhvcjogIm52Y2YtY29yZS1lbmcgPG52Y2YtY29yZS1lbmdAZXhjaGFuZ2UubnZpZGlhLmNv" + - "bT4iCnZlcnNpb246ICIxLjAuMCIKdGFnczogW252Y2YsIHNlbGYtbWFuYWdlZCwgaGVsbWZpbGUs" + - "IHNlbGYtaG9zdGVkLCBjb250cm9sLXBsYW5lLCBjb21wdXRlLXBsYW5lLCBjbGktcHJvZmlsZSwg" + - "aW5zdGFsbGF0aW9uLCBkZXBsb3ltZW50LCBwdWxsLXNlY3JldHNdCnRvb2xzOiBbU2hlbGwsIFJl" + - "YWQsIEVkaXQsIEdyZXAsIEdsb2JdCm1ldGFkYXRhOgogIGludGVybmFsOiBmYWxzZQogIGF1dGhv" + - "cjogIm52Y2YtY29yZS1lbmcgPG52Y2YtY29yZS1lbmdAZXhjaGFuZ2UubnZpZGlhLmNvbT4iCiAg" + - "dmVyc2lvbjogIjEuMCIKICB0YWdzOiBbbnZjZiwgc2VsZi1tYW5hZ2VkLCBoZWxtZmlsZSwgc2Vs" + - "Zi1ob3N0ZWQsIGNvbnRyb2wtcGxhbmUsIGNvbXB1dGUtcGxhbmUsIGNsaS1wcm9maWxlLCBpbnN0" + - "YWxsYXRpb24sIGRlcGxveW1lbnQsIHB1bGwtc2VjcmV0c10KICBsYW5ndWFnZXM6IFtiYXNoLCB5" + - "YW1sXQogIGZyYW1ld29ya3M6IFtoZWxtZmlsZSwgaGVsbSwga3ViZWN0bF0KICBkb21haW46IGNs" + - "b3VkLWluZnJhc3RydWN0dXJlCi0tLQoKIyBOVkNGIFNlbGYtTWFuYWdlZCBTdGFjayBPcGVyYXRp" + - "b25zCgpPcGVyYXRpb25hbCBndWlkZSBmb3IgbWF0Y2hpbmcgTlZDRiBjb250cm9sLXBsYW5lIGFu" + - "ZCBjb21wdXRlLXBsYW5lIEhlbG1maWxlIGJ1bmRsZXMuCgojIyBJbnN0cnVjdGlvbnMKClVzZSB0" + - "aGlzIHNraWxsIGZvciBpbnN0YWxsLCB1cGdyYWRlLCBvciB0ZWFyZG93biB3b3JrIGluIG1hdGNo" + - "aW5nIGBudmNmLXNlbGYtbWFuYWdlZC1zdGFja2AgYW5kIGBudmNmLWNvbXB1dGUtcGxhbmUtc3Rh" + - "Y2tgIGJ1bmRsZXM7IGtlZXAgSGVsbWZpbGUgdmFsdWVzIGFuZCBDTEkgcHJvZmlsZSBoYW5kb2Zm" + - "cyBzZXBhcmF0ZS4gRm9yIGBmdW5jdGlvblR5cGU6ICJMTE0iYCwgcmVhZCBbTExNIEZ1bmN0aW9u" + - "IEVuYWJsZW1lbnRdKHJlZmVyZW5jZXMvaGVsbWZpbGUtc3RydWN0dXJlLm1kI2xsbS1mdW5jdGlv" + - "bi1lbmFibGVtZW50KS4KCiMjIFByZXJlcXVpc2l0ZXMKCkFzayBmb3IgdGhlIGV4dHJhY3RlZCBg" + - "bnZjZi1zZWxmLW1hbmFnZWQtc3RhY2tgIHBhdGguIEZvciBzcGxpdCBpbnN0YWxsYXRpb24sCmFs" + - "c28gcmVxdWlyZSB0aGUgbWF0Y2hpbmctdmVyc2lvbiBgbnZjZi1jb21wdXRlLXBsYW5lLXN0YWNr" + - "YCBwYXRoLiBWZXJpZnkgYm90aDoKCmBgYGJhc2gKbHMgPGNvbnRyb2wtcGxhbmUtc3RhY2s+L2hl" + - "bG1maWxlLmQvIDxjb250cm9sLXBsYW5lLXN0YWNrPi9lbnZpcm9ubWVudHMvIDxjb250cm9sLXBs" + - "YW5lLXN0YWNrPi9zZWNyZXRzLyA8Y29udHJvbC1wbGFuZS1zdGFjaz4vZ2xvYmFsLnlhbWwuZ290" + - "bXBsCmxzIDxjb21wdXRlLXBsYW5lLXN0YWNrPi9oZWxtZmlsZS5kLyA8Y29tcHV0ZS1wbGFuZS1z" + - "dGFjaz4vZW52aXJvbm1lbnRzLyA8Y29tcHV0ZS1wbGFuZS1zdGFjaz4vZ2xvYmFsLnlhbWwuZ290" + - "bXBsCmBgYAoKSWYgZWl0aGVyIGRpcmVjdG9yeSBpcyBtaXNzaW5nLCBkb3dubG9hZCBtYXRjaGlu" + - "ZyBidW5kbGUgdmVyc2lvbnMgZnJvbSBOR0M6CgpgYGBiYXNoCm5nYyByZWdpc3RyeSByZXNvdXJj" + - "ZSBkb3dubG9hZC12ZXJzaW9uIDxvcmc+L252Y2Ytc2VsZi1tYW5hZ2VkLXN0YWNrOjx2ZXJzaW9u" + - "PgpuZ2MgcmVnaXN0cnkgcmVzb3VyY2UgZG93bmxvYWQtdmVyc2lvbiA8b3JnPi9udmNmLWNvbXB1" + - "dGUtcGxhbmUtc3RhY2s6PHZlcnNpb24+CmBgYAoKQ29udHJvbC1wbGFuZSBjb21tYW5kcyBiZWxv" + - "dyBhc3N1bWUgdGhlIGNvbnRyb2wtcGxhbmUgYnVuZGxlIHJvb3QuIFNwbGl0LWZsb3cKY29tbWFu" + - "ZHMgYW5kIHJvb3Qgc3Vic3RpdHV0aW9uIGFyZSBpbgpbU3BsaXQgQ29tcHV0ZS1QbGFuZSBJbnN0" + - "YWxsYXRpb25dKHJlZmVyZW5jZXMvY29tcHV0ZS1wbGFuZS1pbnN0YWxsYXRpb24ubWQpLgoKIyMg" + - "QmVmb3JlIFlvdSBTdGFydAoKVmVyaWZ5IHRvb2xpbmcgYW5kIGNvbnRleHQgYmVmb3JlIGFueSBv" + - "cGVyYXRpb246CgpgYGBiYXNoCmhlbG1maWxlIC0tdmVyc2lvbiAgICMgTXVzdCBiZSAxLjEueCAo" + - "MS4yLjAgcmVtb3ZlZCBzZXF1ZW50aWFsIG1vZGUpCmhlbG0gdmVyc2lvbiAgICAgICAgICMgTXVz" + - "dCBiZSA+PSAzLjEyCmhlbG0gcGx1Z2luIGxpc3QgICAgICMgTXVzdCBpbmNsdWRlIGhlbG0tZGlm" + - "ZiA+PSAzLjExCmt1YmVjdGwgdmVyc2lvbiAgICAgICMgQ2xpZW50IG11c3QgYmUgd2l0aGluIDEg" + - "bWlub3IgdmVyc2lvbiBvZiBjbHVzdGVyCmBgYAoKQ29udHJvbC1wbGFuZSBjb21tYW5kcyBydW4g" + - "ZnJvbSB0aGUgZXh0cmFjdGVkIGBudmNmLXNlbGYtbWFuYWdlZC1zdGFjay9gIHJvb3Q6CgpgYGBi" + - "YXNoCmNkIHBhdGgvdG8vbnZjZi1zZWxmLW1hbmFnZWQtc3RhY2sKbHMgaGVsbWZpbGUuZC8gZW52" + - "aXJvbm1lbnRzLyBzZWNyZXRzLyBnbG9iYWwueWFtbC5nb3RtcGwKYGBgCgpJZGVudGlmeSB0aGUg" + - "ZW52aXJvbm1lbnQgZnJvbSBgZW52aXJvbm1lbnRzLzxuYW1lPi55YW1sYCBhbmQKYHNlY3JldHMv" + - "PG5hbWU+LXNlY3JldHMueWFtbGA7IGZvciBFS1MsIHVzZSB0aGUgY2Fub25pY2FsCltDU1AgRW5k" + - "LXRvLUVuZCBFeGFtcGxlXShodHRwczovL2RvY3MubnZpZGlhLmNvbS9udmNmL3YwLjYuMC1yYy9j" + - "c3AtZW5kLXRvLWVuZC1leGFtcGxlKS4KCiMjIEhvdyBWYWx1ZXMgRmxvdwoKVW5kZXJzdGFuZGlu" + - "ZyB2YWx1ZSBwcmVjZWRlbmNlIHByZXZlbnRzIHRoZSBtb3N0IGNvbW1vbiBjb25maWd1cmF0aW9u" + - "IG1pc3Rha2VzLgoKYGBgCmVudmlyb25tZW50cy9iYXNlLnlhbWwgICAgICAgICAgKGRlZmF1bHRz" + - "KQogICAgLT4gbWVyZ2VkIHdpdGgKZW52aXJvbm1lbnRzLzxlbnY+LnlhbWwgICAgICAgICAoeW91" + - "ciBvdmVycmlkZXMpCiAgICAtPiBjb25zdW1lZCBieQpnbG9iYWwueWFtbC5nb3RtcGwgICAgICAg" + - "ICAgICAgIChHbyB0ZW1wbGF0ZSwgY29uc3RydWN0cyBwZXItY2hhcnQgdmFsdWVzKQogICAgLT4g" + - "Y29uc3VtZWQgYnkKc2VjcmV0cy88ZW52Pi1zZWNyZXRzLnlhbWwgICAgICAoc2Vuc2l0aXZlIHZh" + - "bHVlcykKICAgIC0+IG92ZXJyaWRkZW4gYnkKcmVsZWFzZSBpbmxpbmUgdmFsdWVzOiBibG9ja3Mg" + - "ICAoaGlnaGVzdCBwcmVjZWRlbmNlKQpgYGAKCkNyaXRpY2FsOiBgZ2xvYmFsLnlhbWwuZ290bXBs" + - "YCBvbmx5IHBhc3NlcyB0aHJvdWdoIHNwZWNpZmljIGtleXMgdG8gZWFjaCBjaGFydCAoaW1hZ2Ug" + - "cmVnaXN0cmllcywgbm9kZSBzZWxlY3RvcnMsIHN0b3JhZ2UsIHJlcGxpY2EgY291bnRzKS4gU2V0" + - "dGluZyBhbiBhcmJpdHJhcnkgY2hhcnQgdmFsdWUgaW4gdGhlIGVudmlyb25tZW50IGZpbGUgd2ls" + - "bCBiZSBzaWxlbnRseSBpZ25vcmVkIGlmIGBnbG9iYWwueWFtbC5nb3RtcGxgIGRvZXMgbm90IHBy" + - "b3BhZ2F0ZSBpdC4KClRvIG92ZXJyaWRlIGFyYml0cmFyeSBjaGFydCB2YWx1ZXMsIHVzZSBhIGhl" + - "bG1maWxlIHJlbGVhc2UgYHZhbHVlczpgIGJsb2NrLiBTZWUgW092ZXJyaWRpbmcgSGVsbSBDaGFy" + - "dCBWYWx1ZXNdKCNvdmVycmlkaW5nLWhlbG0tY2hhcnQtdmFsdWVzKS4KCiMjIENsZWFuIEluc3Rh" + - "bGxhdGlvbgoKIyMjIDEuIENyZWF0ZSBuYW1lc3BhY2VzIGFuZCBpbWFnZSBwdWxsIHNlY3JldHMg" + - "KGlmIHVzaW5nIGEgcHJpdmF0ZSByZWdpc3RyeSkKCmBgYGJhc2gKZm9yIG5zIGluIGNhc3NhbmRy" + - "YS1zeXN0ZW0gbmF0cy1zeXN0ZW0gbnZjZiBhcGkta2V5cyBlc3Mgc2lzIHZhdWx0LXN5c3RlbSBj" + - "ZXJ0LW1hbmFnZXI7IGRvCiAga3ViZWN0bCBjcmVhdGUgbmFtZXNwYWNlICIkbnMiIC0tZHJ5LXJ1" + - "bj1jbGllbnQgLW8geWFtbCB8IGt1YmVjdGwgYXBwbHkgLWYgLQpkb25lCgojIE9ubHkgaWYgcHVs" + - "bGluZyBmcm9tIGEgcHJpdmF0ZSByZWdpc3RyeSAoZS5nLiwgTkdDIG52Y3IuaW8pCmV4cG9ydCBO" + - "R0NfQVBJX0tFWT0iPHlvdXIta2V5PiIKZm9yIG5zIGluIGNhc3NhbmRyYS1zeXN0ZW0gbmF0cy1z" + - "eXN0ZW0gbnZjZiBhcGkta2V5cyBlc3Mgc2lzIHZhdWx0LXN5c3RlbSBjZXJ0LW1hbmFnZXI7IGRv" + - "CiAga3ViZWN0bCBjcmVhdGUgc2VjcmV0IGRvY2tlci1yZWdpc3RyeSBudmNyLWNyZWRzIFwKICAg" + - "IC0tZG9ja2VyLXNlcnZlcj1udmNyLmlvIFwKICAgIC0tZG9ja2VyLXVzZXJuYW1lPSckb2F1dGh0" + - "b2tlbicgXAogICAgLS1kb2NrZXItcGFzc3dvcmQ9IiROR0NfQVBJX0tFWSIgXAogICAgLS1uYW1l" + - "c3BhY2U9IiRucyIgXAogICAgLS1kcnktcnVuPWNsaWVudCAtbyB5YW1sIHwga3ViZWN0bCBhcHBs" + - "eSAtZiAtCmRvbmUKYGBgCgpJZiB1c2luZyBwdWxsIHNlY3JldHMsIHlvdSBtdXN0IGFsc28gY29u" + - "ZmlndXJlIGVhY2ggaGVsbWZpbGUgcmVsZWFzZSB0byByZWZlcmVuY2UgdGhlbS4gU2VlIFtJbWFn" + - "ZSBQdWxsIFNlY3JldHNdKCNpbWFnZS1wdWxsLXNlY3JldHMpLgoKIyMjIDIuIEF1dGhlbnRpY2F0" + - "ZSBoZWxtIHRvIHlvdXIgY2hhcnQgcmVnaXN0cnkKCkJvdGggYGRvY2tlciBsb2dpbmAgQU5EIGBo" + - "ZWxtIHJlZ2lzdHJ5IGxvZ2luYCBhcmUgcmVxdWlyZWQgZm9yIE5HQy4gSGVsbWZpbGUgdXNlcyBo" + - "ZWxtIChub3QgZG9ja2VyKSB0byBwdWxsIE9DSSBjaGFydHMsIHNvIHRoZSBoZWxtIGNyZWRlbnRp" + - "YWwgc3RvcmUgbXVzdCBiZSBhdXRoZW50aWNhdGVkIHNlcGFyYXRlbHkuCgpgYGBiYXNoCiMgTkdD" + - "IC0tIGJvdGggY29tbWFuZHMgYXJlIHJlcXVpcmVkCmRvY2tlciBsb2dpbiBudmNyLmlvIC11ICck" + - "b2F1dGh0b2tlbicgLXAgIiROR0NfQVBJX0tFWSIKaGVsbSByZWdpc3RyeSBsb2dpbiBudmNyLmlv" + - "IC11ICckb2F1dGh0b2tlbicgLXAgIiROR0NfQVBJX0tFWSIKCiMgRUNSCmF3cyBlY3IgZ2V0LWxv" + - "Z2luLXBhc3N3b3JkIC0tcmVnaW9uIDxyZWdpb24+IHwgXAogIGhlbG0gcmVnaXN0cnkgbG9naW4g" + - "LS11c2VybmFtZSBBV1MgLS1wYXNzd29yZC1zdGRpbiA8YWNjb3VudD4uZGtyLmVjci48cmVnaW9u" + - "Pi5hbWF6b25hd3MuY29tCmBgYAoKPiBOb3RlOiBgaGVsbSByZWdpc3RyeSBsb2dpbmAga2VlcHMg" + - "b25seSB0aGUgbGFzdCBsb2dpbiBwZXIgaG9zdCAobXVsdGktb3JnIGdvdGNoYSkuCj4gVGhlIHN0" + - "YWNrIGFuZCB0aGUgY2FjaGVzIHNwYW4gc2V2ZXJhbCBOR0Mgb3JncyB0aGF0IG5lZWQgZGlmZmVy" + - "ZW50IGtleXMsIGJ1dCB0aGV5IGFsbAo+IGxpdmUgdW5kZXIgdGhlIG9uZSBob3N0IGBudmNyLmlv" + - "YC4gQSBzZWNvbmQgYGhlbG0gcmVnaXN0cnkgbG9naW4gbnZjci5pb2AgKG9yCj4gYGRvY2tlciBs" + - "b2dpbmApIHdpdGggYSBkaWZmZXJlbnQga2V5IHNpbGVudGx5IG92ZXJ3cml0ZXMgdGhlIGZpcnN0" + - "LCBzbyBwdWxscyBmb3IgdGhlCj4gZmlyc3Qgb3JnIHRoZW4gZmFpbCB3aXRoIGEgbWlzbGVhZGlu" + - "ZyBgNDAzIEFjY2VzcyBEZW5pZWRgIChub3QgYSA0MDE6IHRoZSBhdXRoCj4gc3VjY2VlZGVkLCBp" + - "dCdzIHRoZSB3cm9uZy1vcmcga2V5KS4gSWYgeW91IHB1bGwgY2hhcnRzL2ltYWdlcyBmcm9tIG1v" + - "cmUgdGhhbiBvbmUgb3JnLAo+IGVpdGhlciByZS1ydW4gdGhlIGxvZ2luIHdpdGggdGhlIGNvcnJl" + - "Y3Qga2V5IGltbWVkaWF0ZWx5IGJlZm9yZSBlYWNoIHB1bGwsIG9yIHBhc3MKPiBgLS11c2VybmFt" + - "ZSAnJG9hdXRodG9rZW4nIC0tcGFzc3dvcmQgPGtleT5gIHBlciBgaGVsbSBwdWxsYCBpbnZvY2F0" + - "aW9uIGluc3RlYWQgb2YgcmVseWluZwo+IG9uIHRoZSBzdG9yZWQgbG9naW4uIChQdWxsaW5nIGV2" + - "ZXJ5dGhpbmcgZnJvbSBhIHNpbmdsZSByZWdpc3RyeSBtaXJyb3IgYXZvaWRzIHRoaXMKPiBlbnRp" + - "cmVseS4pCgojIyMgMmIuIChSZWNvbW1lbmRlZCkgUHVsbCBmcm9tIGEgcmVnaXN0cnkgdGhlIGNs" + - "dXN0ZXIgYXV0aGVudGljYXRlcyB0byBhdXRvbWF0aWNhbGx5CgpJZiB5b3UgbWlycm9yIHRoZSBO" + - "VkNGIGltYWdlcyBpbnRvIGEgcmVnaXN0cnkgdGhhdCB0aGUgY2x1c3RlciBjYW4gcHVsbCBmcm9t" + - "IHdpdGhvdXQgYW4KZXhwbGljaXQgaW1hZ2UgcHVsbCBzZWNyZXQgKGZvciBleGFtcGxlLCBhIHJl" + - "Z2lzdHJ5IHdoZXJlIHRoZSBub2RlIG9yIHdvcmtsb2FkIGlkZW50aXR5IGlzCmdyYW50ZWQgcHVs" + - "bCBhY2Nlc3MgYXV0b21hdGljYWxseSksIHBvaW50IHRoZSBzdGFjayBhdCBpdCBhbmQgc2tpcCB0" + - "aGUgY3JlZGVudGlhbCBzdGVwcwphYm92ZS4KCjEuIFBvaW50IHRoZSBzdGFjayBhdCB0aGUgbWly" + - "cm9yIGluIGBlbnZpcm9ubWVudHMvPGVudj4ueWFtbGA6CgogICBgYGB5YW1sCiAgIGdsb2JhbDoK" + - "ICAgICBpbWFnZToKICAgICAgIHJlZ2lzdHJ5OiA8eW91ci1yZWdpc3RyeT4gICAgICAgICAgICAj" + - "IGUuZy4gcmVnaXN0cnkuZXhhbXBsZS5jb20KICAgICAgIHJlcG9zaXRvcnk6IDxtaXJyb3Itc3Vi" + - "LXBhdGg+CiAgICAgaGVsbToKICAgICAgIHNvdXJjZXM6ICAgICAgICAgICAgICAgICAgICAgICAg" + - "ICAgICAgIyBvbmx5IGlmIHlvdSBhbHNvIG1pcnJvcmVkIHRoZSBoZWxtIGNoYXJ0cwogICAgICAg" + - "ICByZWdpc3RyeTogPHlvdXItcmVnaXN0cnk+CiAgICAgICAgIHJlcG9zaXRvcnk6IDxtaXJyb3It" + - "c3ViLXBhdGg+CiAgIGBgYAoKICAgPiBUaGUgbWFuaWZlc3QgaGFzIGEgbWl4IG9mIE9DSSBhbmQg" + - "bm9uLU9DSSBhcnRpZmFjdHMsIHNvIGNvbnZlcnQgbm9uLU9DSSBjaGFydHMgYmVmb3JlCiAgID4g" + - "bWlycm9yaW5nLiBDb250YWluZXIgaW1hZ2VzIChgbnZjci5pby8uLi5gKSBhcmUgYWxyZWFkeSBP" + - "Q0kgYW5kIG1pcnJvciBhcy1pcy4gU29tZSBIZWxtCiAgID4gY2hhcnRzLCB0aG91Z2gsIGFyZSBw" + - "dWJsaXNoZWQgdG8gYW4gYGh0dHBzOi8vYCBIZWxtIHJlcG9zaXRvcnkgKGZvciBleGFtcGxlCiAg" + - "ID4gYGhlbG0ubmdjLm52aWRpYS5jb21gKS4gVGhvc2UgYXJlIG5vdCBPQ0kgYW5kIGNhbm5vdCBi" + - "ZSByZXNvbHZlZCBieSBgZ2xvYmFsLmhlbG0uc291cmNlc2AKICAgPiAoT0NJKSBvciBtaXJyb3Jl" + - "ZCBkaXJlY3RseS4gQmVmb3JlIHBvaW50aW5nIHRoZSBzdGFjayBhdCB5b3VyIG1pcnJvciwgY29u" + - "dmVydCBlYWNoCiAgID4gYGh0dHBzOi8vYCBjaGFydCB0byBPQ0k6IGBoZWxtIHJlcG8gYWRkYC9g" + - "aGVsbSBwdWxsYCB0aGUgYC50Z3pgLCB0aGVuCiAgID4gYGhlbG0gcHVzaCA8Y2hhcnQ+LnRneiBv" + - "Y2k6Ly88eW91ci1yZWdpc3RyeT4vPG1pcnJvci1zdWItcGF0aD5gLiBPbmx5IE9DSSBjaGFydHMg" + - "Y2FuIGxpdmUKICAgPiB1bmRlciBgZ2xvYmFsLmhlbG0uc291cmNlc2AuCgoyLiBTa2lwIHRoZSBj" + - "cmVkZW50aWFsIHN0ZXBzIGFib3ZlLiBXaGVuIHRoZSBjbHVzdGVyIGF1dGhlbnRpY2F0ZXMgdG8g" + - "dGhlIHJlZ2lzdHJ5CiAgIGF1dG9tYXRpY2FsbHkgKHRoZSBub2RlIG9yIHdvcmtsb2FkIGlkZW50" + - "aXR5IGhhcyBwdWxsIGFjY2VzcyksIHRoZSBwdWxsIG5lZWRzIG5vCiAgIEt1YmVybmV0ZXMgc2Vj" + - "cmV0LCBzbyB5b3UgZG8gbm90IG5lZWQ6IHRoZSBwZXItbmFtZXNwYWNlIGBudmNyLWNyZWRzYCBm" + - "cm9tIHN0ZXAgMQogICAoc3RpbGwgY3JlYXRlIHRoZSBuYW1lc3BhY2VzLCBqdXN0IG5vdCB0aGUg" + - "c2VjcmV0KSwgdGhlIGBnbG9iYWwuaW1hZ2VQdWxsU2VjcmV0c2AgZW50cnksCiAgIG9yIHRoZSBg" + - "ZG9ja2VyIGxvZ2luYC9gaGVsbSByZWdpc3RyeSBsb2dpbiBudmNyLmlvYCBmcm9tIHN0ZXAgMi4K" + - "CiAgID4gT25lIHJlc2lkdWFsIGNyZWRlbnRpYWw6IG52Y2YtYXBpIHN0aWxsIHZhbGlkYXRlcyB0" + - "aGUgdXNlciBmdW5jdGlvbiBpbWFnZSBhdAogICA+IGBmdW5jdGlvbiBjcmVhdGVgIHZpYSB0aGUg" + - "YWNjb3VudCByZWdpc3RyeSBjcmVkZW50aWFsLCBzbyB0aGF0IGNyZWRlbnRpYWwgbXVzdCBwb2lu" + - "dCBhdAogICA+IHdoaWNoZXZlciByZWdpc3RyeSBob2xkcyB0aGUgdXNlciBpbWFnZSAoc2V0IGl0" + - "IHRvIHlvdXIgbWlycm9yIGlmIHRoZSBpbWFnZSBpcyBtaXJyb3JlZAogICA+IHRoZXJlKS4KCiMj" + - "IyAzLiBTZXQgdGhlIHJvdXRlIGRvbWFpbgoKYGdsb2JhbC5kb21haW5gIHNldHMgdGhlIGhvc3Ru" + - "YW1lIHN1ZmZpeCBmb3Igcm91dGVzIHN1Y2ggYXMgYGFwaS48ZG9tYWluPmAuIEl0IGRvZXMgbm90" + - "IGNvbmZpZ3VyZSB0aGUgR2F0ZXdheSBvciBpdHMgbG9hZCBiYWxhbmNlci4gUXVlcnkgdGhlIGV4" + - "YWN0IGV4aXN0aW5nIEdhdGV3YXk6CgpgYGBiYXNoCkdBVEVXQVlfQUREUkVTUz0kKGt1YmVjdGwg" + - "Z2V0IGdhdGV3YXkgPGdhdGV3YXktbmFtZT4gLW4gPGdhdGV3YXktbmFtZXNwYWNlPiAtbyBqc29u" + - "cGF0aD0ney5zdGF0dXMuYWRkcmVzc2VzWzBdLnZhbHVlfScpCnRlc3QgLW4gIiRHQVRFV0FZX0FE" + - "RFJFU1MiCmBgYAoKVGhlIGFkZHJlc3MgbWF5IGJlIGEgaG9zdG5hbWUgb3IgSVAuIENsb3VkIHBy" + - "b3ZpZGVyIGFuZCBHYXRld2F5IGNvbmZpZ3VyYXRpb24gZGV0ZXJtaW5lIHRoZSBsb2FkIGJhbGFu" + - "Y2VyIHR5cGUgYW5kIGFkZHJlc3MgZm9ybWF0LiBUaGUgc2VsZi1tYW5hZ2VkIHN0YWNrIGRvZXMg" + - "bm90IHNlbGVjdCBlaXRoZXIuCgpGb3IgdGVtcG9yYXJ5IHRlc3Rpbmcgd2l0aG91dCBETlMsIHVz" + - "ZSBhIHN0YWJsZSByZXNlcnZlZCBzdWZmaXggc3VjaCBhcyBgZ2xvYmFsLmRvbWFpbjogIm52Y2Yu" + - "dGVzdCJgLCBjb25uZWN0IHRvIGBHQVRFV0FZX0FERFJFU1NgLCBhbmQgc2VuZCBgSG9zdDogYXBp" + - "Lm52Y2YudGVzdGAuIEZvciBwcm9kdWN0aW9uLCB1c2UgeW91ciBiYXNlIEROUyBkb21haW4gYW5k" + - "IHBvaW50IGl0cyByb3V0ZSByZWNvcmRzIHRvIGBHQVRFV0FZX0FERFJFU1NgLiBJZiB0aGUgZW5k" + - "cG9pbnQgbGF0ZXIgY2hhbmdlcywga2VlcCBgZ2xvYmFsLmRvbWFpbmAgc3RhYmxlIGFuZCB1cGRh" + - "dGUgb25seSBETlMgb3IgdGhlIGNsaWVudCBkZXN0aW5hdGlvbi4gU2VlIEV4YW1wbGUgNCBpbiBb" + - "ZXhhbXBsZXMubWRdKGV4YW1wbGVzLm1kKS4KCiMjIyA0LiBQcmV2aWV3IGFuZCBkZXBsb3kKCmBg" + - "YGJhc2gKSEVMTUZJTEVfRU5WPTxlbnYtbmFtZT4gaGVsbWZpbGUgdGVtcGxhdGUgICAjIFByZXZp" + - "ZXcgcmVuZGVyZWQgbWFuaWZlc3RzCkhFTE1GSUxFX0VOVj08ZW52LW5hbWU+IGhlbG1maWxlIHN5" + - "bmMgICAgICAgIyBEZXBsb3kKYGBgCgo+IFNldCBCT1RIIENhc3NhbmRyYSBwYXNzd29yZHMgaW4g" + - "dGhlIHNlY3JldHMgZmlsZS4gVGhlIHNlY3JldHMgZmlsZSBkb2N1bWVudHMKPiBgREVGQVVMVF9D" + - "QVNTQU5EUkFfUEFTU1dPUkRgLCBidXQgdGhlIGNhc3NhbmRyYSBjaGFydCBoYXMgYSBzZWNvbmQg" + - "Y3JlZGVudGlhbCwKPiBgY2Fzc2FuZHJhLnNlcnZpY2VSb2xlUGFzc3dvcmRgLCB0aGF0IGRlZmF1" + - "bHRzIHRvIGBjaEBuZzNtM2AuIElmIHlvdSBzZXQgb25seQo+IGBERUZBVUxUX0NBU1NBTkRSQV9Q" + - "QVNTV09SRGAsIHRoZSBzZXJ2aWNlIHJvbGUga2VlcHMgdGhhdCBkZWZhdWx0IGFuZCBldmVyeSBE" + - "Qi1iYWNrZWQKPiBzZXJ2aWNlIGNyYXNoLWxvb3BzIHdpdGggYEJhZCBjcmVkZW50aWFsc2Agb25j" + - "ZSBpdCB0cmllcyB0byBhdXRoZW50aWNhdGUuIFNldAo+IGBjYXNzYW5kcmEuc2VydmljZVJvbGVQ" + - "YXNzd29yZGAgdG8gdGhlIHNhbWUgdmFsdWUgYXMgYERFRkFVTFRfQ0FTU0FORFJBX1BBU1NXT1JE" + - "YC4KCiMjIyA1LiBEZXBsb3ltZW50IHBoYXNlcwoKSGVsbWZpbGUgZGVwbG95cyBpbiBvcmRlciB3" + - "aXRoIGRlcGVuZGVuY2llczoKCnwgUGhhc2UgfCBTZWxlY3RvciB8IFNlcnZpY2VzIHwgV2FpdCB0" + - "aW1lIHwKfC0tLS0tLS18LS0tLS0tLS0tLXwtLS0tLS0tLS0tfC0tLS0tLS0tLS0tfAp8IDEgfCBg" + - "cmVsZWFzZS1ncm91cD1kZXBlbmRlbmNpZXNgIHwgTkFUUywgT3BlbkJhbywgQ2Fzc2FuZHJhIHwg" + - "NS0xMCBtaW4gfAp8IDIgfCBgcmVsZWFzZS1ncm91cD1zZXJ2aWNlc2AgfCBBUEksIFNJUywgRVNT" + - "LCBpbnZvY2F0aW9uLCBncnBjLXByb3h5LCBub3RhcnksIGFwaS1rZXlzLCBvcHRpb25hbCBMTE0g" + - "Z2F0ZXdheS9yb3V0ZXIsIG9wdGlvbmFsIFZhbml0eSBHYXRld2F5LCBOVkNGIFVJIHdoZW4gdGhl" + - "IHN0YWNrIHBhY2thZ2UgaW5jbHVkZXMgdGhlIGFkZG9uIHwgNS0xMCBtaW4gfAp8IDMgfCBgcmVs" + - "ZWFzZS1ncm91cD1pbmdyZXNzYCB8IEdhdGV3YXkgcm91dGVzIHwgMS0yIG1pbiB8CnwgNCB8IGBy" + - "ZWxlYXNlLWdyb3VwPW9ic2VydmFiaWxpdHlgIHwgT2JzZXJ2YWJpbGl0eSBzdGFjayAoaWYgZW5h" + - "YmxlZCkgfCAxLTIgbWluIHwKCk1vbml0b3IgaW4gYSBzZXBhcmF0ZSB0ZXJtaW5hbDoKCmBgYGJh" + - "c2gKa3ViZWN0bCBnZXQgcG9kcyAtQSAtdwpgYGAKCiMjIyA2LiBFbmFibGUgVmFuaXR5IEdhdGV3" + - "YXkgKG9wdGlvbmFsKQoKVmFuaXR5IEdhdGV3YXkgaXMgZGlzYWJsZWQgYnkgZGVmYXVsdCBhbmQg" + - "aXMgb25seSBuZWVkZWQgZm9yIGN1c3RvbWVyLWZhY2luZwpob3N0bmFtZXMgb3IgcGF0aCBtYXBw" + - "aW5ncyBpbiBmcm9udCBvZiB0aGUgc3RhbmRhcmQgTlZDRiBBUEkgYW5kIGludm9jYXRpb24Kcm91" + - "dGVzLiBJdCBpcyBhdmFpbGFibGUgb25seSBpbiBzdGFjayBwYWNrYWdlcyB0aGF0IGluY2x1ZGUg" + - "dGhlIFZhbml0eSBHYXRld2F5CmFkZG9uLiBJZiB0aGUgZXh0cmFjdGVkIHN0YWNrIHBhY2thZ2Ug" + - "ZG9lcyBub3QgY29udGFpbiBhIGB2YW5pdHktZ2F0ZXdheWAKcmVsZWFzZSBhbmQgYHZhbml0eUdh" + - "dGV3YXlgIHJvdXRlIHZhbHVlcywgc2tpcCB0aGVzZSBjb21tYW5kcyBhbmQgdXNlIGEgbmV3ZXIK" + - "c3RhY2sgcGFja2FnZSB0aGF0IGluY2x1ZGVzIHRoZW0uCgpJZiB0aGUgc3RhY2sgcGFja2FnZSBp" + - "bmNsdWRlcyB0aGUgYWRkb24sIGVuYWJsZSBpdCBpbgpgZW52aXJvbm1lbnRzLzxlbnYtbmFtZT4u" + - "eWFtbGA6CgpgYGB5YW1sCmFkZG9uczoKICB2YW5pdHlHYXRld2F5OgogICAgZW5hYmxlZDogdHJ1" + - "ZQogICAgbWFwcGluZ0NvbmZpZzoge30KYGBgCgpUaGUgZGVmYXVsdCByb3V0ZSBob3N0IGlzIGB2" + - "YW5pdHkuPGRvbWFpbj5gIGFuZCB0aGUgYmFja2VuZCBpcwpgdmFuaXR5LWdhdGV3YXkubnZjZjo4" + - "MDgwYC4gUHV0IGRlcGxveW1lbnQtc3BlY2lmaWMgaG9zdCBhbmQgcGF0aCBtYXBwaW5ncyB1bmRl" + - "cgpgYWRkb25zLnZhbml0eUdhdGV3YXkubWFwcGluZ0NvbmZpZ2AuIElmIHlvdSBuZWVkIGN1c3Rv" + - "bSB2YW5pdHkgaG9zdHMgaW5zdGVhZCBvZgpgdmFuaXR5Ljxkb21haW4+YCwgdXNlIHRoZSByb3V0" + - "ZSBob3N0bmFtZSBvdmVycmlkZXMgc3VwcG9ydGVkIGJ5IHRoZSBzdGFjawpwYWNrYWdlIGFuZCBj" + - "cmVhdGUgbWF0Y2hpbmcgRE5TIHJlY29yZHMuCgpBZnRlciBjb25maXJtaW5nIHRoZSBwYWNrYWdl" + - "IGluY2x1ZGVzIHRoZSBgdmFuaXR5LWdhdGV3YXlgIHJlbGVhc2UsIHByZXZpZXcgYW5kCmFwcGx5" + - "IHRoZSBzZXJ2aWNlIGFuZCByb3V0ZToKCmBgYGJhc2gKSEVMTUZJTEVfRU5WPTxlbnYtbmFtZT4g" + - "aGVsbWZpbGUgLS1zZWxlY3RvciBuYW1lPXZhbml0eS1nYXRld2F5IHRlbXBsYXRlCkhFTE1GSUxF" + - "X0VOVj08ZW52LW5hbWU+IGhlbG1maWxlIC0tc2VsZWN0b3IgbmFtZT12YW5pdHktZ2F0ZXdheSBz" + - "eW5jCkhFTE1GSUxFX0VOVj08ZW52LW5hbWU+IGhlbG1maWxlIC0tc2VsZWN0b3IgcmVsZWFzZS1n" + - "cm91cD1pbmdyZXNzIHN5bmMKYGBgCgpWZXJpZnkgdGhlIHJvdXRlIG9ubHkgd2hlbiB0aGUgYWRk" + - "b24gaXMgcHJlc2VudCBhbmQgZW5hYmxlZDoKCmBgYGJhc2gKa3ViZWN0bCBnZXQgZGVwbG95LHN2" + - "YyAtbiBudmNmIC1sIGFwcC5rdWJlcm5ldGVzLmlvL25hbWU9dmFuaXR5LWdhdGV3YXkKa3ViZWN0" + - "bCBnZXQgaHR0cHJvdXRlIC1BIHwgZ3JlcCAtaSB2YW5pdHkKY3VybCAtSCAiSG9zdDogdmFuaXR5" + - "Ljxkb21haW4+IiAiaHR0cDovLzxnYXRld2F5LWFkZHJlc3M+L2hlYWx0aCIKYGBgCgpEbyBub3Qg" + - "ZW5hYmxlIFZhbml0eSBHYXRld2F5IGZvciBzdGFuZGFyZCBBUEksIEFQSSBLZXlzLCBpbnZvY2F0" + - "aW9uLCBMTE0KaW52b2NhdGlvbiwgb3IgZ1JQQyB0cmFmZmljLiBUaG9zZSByb3V0ZXMgYXJlIHBy" + - "b3ZpZGVkIGJ5IHRoZSBiYXNlIGdhdGV3YXkKcm91dGVzLgoKIyMjIDcuIEVuYWJsZSBOVkNGIFVJ" + - "IChvcHRpb25hbCkKCk5WQ0YgVUkgaXMgYW4gb3B0aW9uYWwsIGN1c3RvbWVyLWZhY2luZyBhZG1p" + - "bi1wYW5lbCBVSS4gSXQgaXMgZGlzYWJsZWQgYnkKZGVmYXVsdCBhbmQgYXZhaWxhYmxlIG9ubHkg" + - "aW4gc3RhY2sgcGFja2FnZXMgdGhhdCBpbmNsdWRlIHRoZSBOVkNGIFVJIGFkZG9uLiBJZgp5b3Vy" + - "IGV4dHJhY3RlZCBzdGFjayBwYWNrYWdlIGRvZXMgbm90IGNvbnRhaW4gYSBgbnZjZi11aWAgcmVs" + - "ZWFzZSBhbmQgYG52Y2ZVaWAKcm91dGUgdmFsdWVzLCBza2lwIHRoaXMgc2VjdGlvbi4gVGhlIE5W" + - "Q0YgVUkgYWRtaW4gcGFuZWwgaXMgY3VycmVudGx5CnVuYXV0aGVudGljYXRlZDsgZG8gbm90IGV4" + - "cG9zZSBpdCB0byB0aGUgcHVibGljIGludGVybmV0LgoKRm9yIGVuYWJsZW1lbnQsIG5hbWVzcGFj" + - "ZSBhbmQgcHVsbCBzZWNyZXQgc2V0dXAsIGFwcGx5LCBhbmQgdmVyaWZpY2F0aW9uIHN0ZXBzLApz" + - "ZWUgW05WQ0YgVUkgQWRkb25dKHJlZmVyZW5jZXMvbnZjZi11aS1hZGRvbi5tZCkuCgojIyMgOC4g" + - "SW5zdGFsbCB0aGUgc2VwYXJhdGUgY29tcHV0ZS1wbGFuZSBzdGFjawoKVGhlIGNvbnRyb2wtcGxh" + - "bmUgc3RhY2sgZG9lcyBub3QgaW5zdGFsbCBjb21wdXRlLXBsYW5lIGNvbXBvbmVudHMuIEFmdGVy" + - "IGl0IGlzIGhlYWx0aHksIGNob29zZSBleGFjdGx5IG9uZSBjb21wdXRlLXBsYW5lIGhhbmRvZmY6" + - "CgotIEhlbG1maWxlIHZhbHVlcyBmbG93OiBhdXRob3Igc2VwYXJhdGUgZW52aXJvbm1lbnQgZmls" + - "ZXMsIHRoZW4gcmVnaXN0ZXIgYW5kCiAgaW5zdGFsbCB3aXRoIHRoZSBjb21wdXRlLXBsYW5lIE1h" + - "a2VmaWxlLgotIENMSSBwcm9maWxlIGZsb3c6IHVzZSB0aGUgcHJvZmlsZSBnZW5lcmF0ZWQgYnkg" + - "dGhlIENMSSBjb250cm9sLXBsYW5lIGluc3RhbGwsCiAgdGhlbiBydW4gdGhlIGNvbXBsZXRlIENM" + - "SSByZWdpc3RlciBhbmQgaW5zdGFsbCBjb21tYW5kcy4KCkRvIG5vdCBtaXggdGhlc2UgaGFuZG9m" + - "ZnMuIEZvbGxvdyBbU3BsaXQgQ29tcHV0ZS1QbGFuZSBJbnN0YWxsYXRpb25dKHJlZmVyZW5jZXMv" + - "Y29tcHV0ZS1wbGFuZS1pbnN0YWxsYXRpb24ubWQpCmZvciBjb21tYW5kcywgRUtTIHJvdXRpbmcs" + - "IHB1bGwgc2VjcmV0cywgdmVyaWZpY2F0aW9uLCBhbmQgdGVhcmRvd24gb3JkZXIuCgojIyBDbGVh" + - "biBUZWFyZG93bgoKRGVzdHJveSBldmVyeSBjb21wdXRlIHBsYW5lIGJlZm9yZSB0aGUgY29udHJv" + - "bCBwbGFuZSwgdXNpbmcgdGhlIHRlYXJkb3duIGZvcgp0aGUgc2FtZSBoYW5kb2ZmOyBzZWUgW1Nw" + - "bGl0IENvbXB1dGUtUGxhbmUgSW5zdGFsbGF0aW9uXShyZWZlcmVuY2VzL2NvbXB1dGUtcGxhbmUt" + - "aW5zdGFsbGF0aW9uLm1kI3RlYXJkb3duKS4KClNjb3BlOiBPbmx5IGRlc3Ryb3kgcmVsZWFzZXMg" + - "bWFuYWdlZCBieSB0aGlzIGNvbnRyb2wtcGxhbmUgaGVsbWZpbGUgc3RhY2suIFRoZSBOVkNGIHJl" + - "bGVhc2VzIGFyZTogYG5hdHNgLCBgb3BlbmJhby1zZXJ2ZXJgLCBgY2Fzc2FuZHJhYCwgYGFwaS1r" + - "ZXlzYCwgYHNpc2AsIGBhcGlgLCBgaW52b2NhdGlvbi1zZXJ2aWNlYCwgYGdycGMtcHJveHlgLCBg" + - "ZXNzLWFwaWAsIGBub3Rhcnktc2VydmljZWAsIG9wdGlvbmFsIGB2YW5pdHktZ2F0ZXdheWAsIGBu" + - "dmNmLXVpYCwgYGFkbWluLWlzc3Vlci1wcm94eWAsIGFuZCBgaW5ncmVzc2A7IGBjZXJ0LW1hbmFn" + - "ZXJgIGlzIGluY2x1ZGVkIG9ubHkgd2hlbiBgY2VydE1hbmFnZXIuZW5hYmxlZDogdHJ1ZWAuIFRo" + - "ZSBjb250cm9sLXBsYW5lIG5hbWVzcGFjZSBpbnZlbnRvcnkgYWxzbyBpbmNsdWRlcyBgY2VydC1t" + - "YW5hZ2VyYCwgYnV0IGRlZmF1bHQgbmFtZXNwYWNlIGNsZWFudXAgcHJlc2VydmVzIGl0LiBOYW1l" + - "c3BhY2UgcHJlc2VydmF0aW9uIGRvZXMgbm90IHByZXNlcnZlIHRoZSBIZWxtIHJlbGVhc2UuIEV4" + - "dGVybmFsIGNlcnQtbWFuYWdlciBtdXN0IHVzZSBgY2VydE1hbmFnZXIuZW5hYmxlZDogZmFsc2Vg" + - "OyBvdGhlcndpc2UgSGVsbWZpbGUgdHJlYXRzIHRoZSByZWxlYXNlIGFzIHN0YWNrLW1hbmFnZWQu" + - "IERvIG5vdCBkZWxldGUgb3RoZXIgaGVsbSByZWxlYXNlcyBvciBuYW1lc3BhY2VzIG9uIHRoZSBj" + - "bHVzdGVyLgoKQmVmb3JlIGNvbnRyb2wtcGxhbmUgZGVzdHJveSwgaWRlbnRpZnkgd2hldGhlciBj" + - "ZXJ0LW1hbmFnZXIgaXMgZXh0ZXJuYWwuIEZvciBhbiBleHRlcm5hbCBpbnN0YWxsYXRpb24sIHJl" + - "cXVpcmUgZWZmZWN0aXZlIGBjZXJ0TWFuYWdlci5lbmFibGVkOiBmYWxzZWAgYW5kIGFib3J0IGlm" + - "IGl0IGlzIHRydWUuIEZvciBhbiBpbnRlbmRlZCBzdGFjay1tYW5hZ2VkIHJlbGVhc2Ugd2l0aCBl" + - "ZmZlY3RpdmUgYGNlcnRNYW5hZ2VyLmVuYWJsZWQ6IHRydWVgLCB2ZXJpZnkgbWF0Y2hpbmcgc3Rh" + - "Y2sgSGVsbSByZWxlYXNlIG1ldGFkYXRhIGFuZCBhYm9ydCBvbiBtaXNzaW5nIG9yIG1pc21hdGNo" + - "ZWQgZXZpZGVuY2UuIFByZXNlcnZlIHRoZSBuYW1lc3BhY2UgYnkgZGVmYXVsdDsgb25seSBwbGFu" + - "IGl0cyByZW1vdmFsIGFmdGVyIHZlcmlmeWluZyBuYW1lc3BhY2UgcHJvdmVuYW5jZSBhbmQgZ2V0" + - "dGluZyBleHBsaWNpdCBjb25maXJtYXRpb24uCgojIyMgU3RhbmRhcmQgdGVhcmRvd24KClJ1biBm" + - "cm9tIGluc2lkZSB0aGUgYG52Y2Ytc2VsZi1tYW5hZ2VkLXN0YWNrL2AgZGlyZWN0b3J5OgoKYGBg" + - "YmFzaApIRUxNRklMRV9FTlY9PGVudi1uYW1lPiBoZWxtZmlsZSBkZXN0cm95CmBgYAoKIyMjIERl" + - "bGV0ZSBuYW1lc3BhY2VzCgpgYGBiYXNoCmZvciBucyBpbiBjYXNzYW5kcmEtc3lzdGVtIG5hdHMt" + - "c3lzdGVtIG52Y2YgYXBpLWtleXMgZXNzIHNpcyBudmNmLXVpIHZhdWx0LXN5c3RlbTsgZG8KICBr" + - "dWJlY3RsIGRlbGV0ZSBuYW1lc3BhY2UgIiRucyIgLS1pZ25vcmUtbm90LWZvdW5kCmRvbmUKYGBg" + - "CgojIyMgVmVyaWZ5IGNsZWFuCgpgYGBiYXNoCmt1YmVjdGwgZ2V0IG5zIHwgZ3JlcCAtRSAnKGNh" + - "c3NhbmRyYXxuYXRzfHZhdWx0fG52Y2Z8YXBpLWtleXN8ZXNzfHNpcyknCiMgU2hvdWxkIHJldHVy" + - "biBlbXB0eQpgYGAKCiMjIE92ZXJyaWRpbmcgSGVsbSBDaGFydCBWYWx1ZXMKCiMjIyBFbnZpcm9u" + - "bWVudCBmaWxlIChsaW1pdGVkKQoKV29ya3Mgb25seSBmb3Iga2V5cyB0aGF0IGBnbG9iYWwueWFt" + - "bC5nb3RtcGxgIHByb3BhZ2F0ZXMgKGUuZy4sIGBjYXNzYW5kcmEucmVwbGljYUNvdW50YCwgYGds" + - "b2JhbC5zdG9yYWdlQ2xhc3NgLCBgZ2xvYmFsLmltYWdlYCkuCgojIyMgUmVsZWFzZSB2YWx1ZXMg" + - "YmxvY2sgKGFueSBjaGFydCB2YWx1ZSkKCkVkaXQgdGhlIHJlbGVhc2UgaW4gYGhlbG1maWxlLmQv" + - "Ki55YW1sLmdvdG1wbGAgYW5kIGFkZCBhIGB2YWx1ZXM6YCBibG9jay4gSW4gdGhlIGV4YW1wbGVz" + - "IGJlbG93LCBgPHByaXZhdGUtdmFsdWVzPmAgcmVmZXJzIHRvIHRoZSBgc2VjcmV0cy9gIGRpcmVj" + - "dG9yeSBhdCB0aGUgaGVsbWZpbGUgc3RhY2sgcm9vdC4KCmBgYHlhbWwKLSBuYW1lOiBjYXNzYW5k" + - "cmEKICB2ZXJzaW9uOiAwLjguMAogIGNvbmRpdGlvbjogY2Fzc2FuZHJhLmVuYWJsZWQKICBuYW1l" + - "c3BhY2U6IGNhc3NhbmRyYS1zeXN0ZW0KICA8PDogKmRlcGVuZGVuY3kKICB2YWx1ZXM6CiAgICAt" + - "IC4uL2dsb2JhbC55YW1sLmdvdG1wbAogICAgLSAuLi88cHJpdmF0ZS12YWx1ZXM+L3t7IHJlcXVp" + - "cmVkRW52ICJIRUxNRklMRV9FTlYiIH19LXNlY3JldHMueWFtbAogICAgLSBjYXNzYW5kcmE6CiAg" + - "ICAgICAgcmVzb3VyY2VzOgogICAgICAgICAgbGltaXRzOgogICAgICAgICAgICBjcHU6ICI4Igog" + - "ICAgICAgICAgICBtZW1vcnk6IDgxOTJNaQogICAgICAgICAgcmVxdWVzdHM6CiAgICAgICAgICAg" + - "IGNwdTogIjIiCiAgICAgICAgICAgIG1lbW9yeTogNDA5Nk1pCmBgYAoKWUFNTCBtZXJnZSBnb3Rj" + - "aGE6IFdoZW4gYSByZWxlYXNlIHVzZXMgYDw8OiAqZGVwZW5kZW5jeWAgb3IgYGluaGVyaXRgLCBz" + - "cGVjaWZ5aW5nIGB2YWx1ZXM6YCByZXBsYWNlcyB0aGUgdGVtcGxhdGUncyB2YWx1ZXMgbGlzdC4g" + - "WW91IG11c3QgcmUtaW5jbHVkZSBgZ2xvYmFsLnlhbWwuZ290bXBsYCBhbmQgdGhlIHNlY3JldHMg" + - "ZmlsZS4KCiMjIyBQcmV2aWV3IGFuZCBhcHBseSBhIHNpbmdsZSByZWxlYXNlCgpgYGBiYXNoCkhF" + - "TE1GSUxFX0VOVj08ZW52PiBoZWxtZmlsZSAtLXNlbGVjdG9yIG5hbWU9Y2Fzc2FuZHJhIHRlbXBs" + - "YXRlICAjIFByZXZpZXcKSEVMTUZJTEVfRU5WPTxlbnY+IGhlbG1maWxlIC0tc2VsZWN0b3IgbmFt" + - "ZT1jYXNzYW5kcmEgc3luYyAgICAgICMgQXBwbHkKYGBgCgojIyBJbWFnZSBQdWxsIFNlY3JldHMK" + - "ClRoZXJlIGFyZSB0aHJlZSBkaXN0aW5jdCBjcmVkZW50aWFsIHR5cGVzLiBEbyBub3QgY29uZmxh" + - "dGUgdGhlbSwgYW5kIG1pbmQgdGhlCmZvcm1hdCBydWxlIGJlbG93ICh0d28gb2YgdGhlbSBmYWls" + - "IGV2ZW4gd2l0aCB0aGUgY29ycmVjdCBrZXkgaWYgdGhlIGZvcm1hdCBpcyB3cm9uZyk6Cgp8IHwg" + - "Q29udHJvbCBQbGFuZSBQdWxsIFNlY3JldHMgfCBBUEkgQm9vdHN0cmFwIFJlZ2lzdHJ5IENyZWRz" + - "IHwgU2lkZWNhciBJbWFnZS1QdWxsIFNlY3JldCB8CnwtLS18LS0tfC0tLXwtLS18CnwgUHVycG9z" + - "ZSB8IEs4cyBwdWxscyBOVkNGIHNlcnZpY2UgaW1hZ2VzIHwgbnZjZi1hcGkgdmFsaWRhdGVzIHRo" + - "ZSB1c2VyIGZ1bmN0aW9uIGltYWdlIGF0IGBmdW5jdGlvbiBjcmVhdGVgIHwgbnZjZi1hcGkgcHVs" + - "bHMgdGhlIHBsYXRmb3JtIHNpZGVjYXJzIGluamVjdGVkIGludG8gZXZlcnkgd29ya2VyIHBvZCB8" + - "CnwgV2hlcmUgfCBLOHMgYGRvY2tlci1yZWdpc3RyeWAgU2VjcmV0cyArIEt5dmVybm8gQ2x1c3Rl" + - "clBvbGljeSB8IGA8cHJpdmF0ZS12YWx1ZXM+LzxlbnY+LXNlY3JldHMueWFtbGAgKGFjY291bnQt" + - "Ym9vdHN0cmFwKSB8IGBOVkNGX0FQSV9TSURFQ0FSU19JTUFHRV9QVUxMX1NFQ1JFVGAgaW4gYDxl" + - "bnY+LXNlY3JldHMueWFtbGAgLT4gT3BlbkJhbyBgc2VydmljZXMvbnZjZi1hcGkva3Yvc2lkZWNh" + - "cnMvaW1hZ2UtcHVsbC1zZWNyZXRgIChmaWVsZCBgc2VjcmV0YCkgfAp8IEZvcm1hdCB8IHN0YW5k" + - "YXJkIGAuZG9ja2VyY29uZmlnanNvbmAgKGt1YmVjdGwgYnVpbGRzIGl0IGZvciB5b3UpIHwgYGJh" + - "c2U2NCgiJG9hdXRodG9rZW46PGtleT4iKWAgfCBgYmFzZTY0KCIkb2F1dGh0b2tlbjo8a2V5PiIp" + - "YCB8Cgo+IE5vdGU6IHRoZSBhY2NvdW50LWJvb3RzdHJhcCBjcmVkIGFuZCB0aGUgc2lkZWNhciBz" + - "ZWNyZXQgYXJlIE5PVCBhCj4gZG9ja2VyY29uZmlnanNvbi4gVGhlIHNlY3JldHMgdGVtcGxhdGUg" + - "c2hpcHMgdGhlIHBsYWNlaG9sZGVyCj4gYFJFUExBQ0VfV0lUSF9CQVNFNjRfRE9DS0VSX0NSRURF" + - "TlRJQUxgLCB3aGljaCByZWFkcyBhcyAiYmFzZTY0IG9mIGEgZG9ja2VyY29uZmlnanNvbi4iCj4g" + - "SXQgaXNuJ3QuIEJvdGggdmFsdWVzIGFyZSBjb25zdW1lZCBhcyBhIHJhdyBkb2NrZXIgYGF1dGhg" + - "IHN0cmluZzogbnZjZi1hcGkgdGFrZXMgdGhlCj4gdmFsdWUgdmVyYmF0aW0gYW5kIGRyb3BzIGl0" + - "IGludG8gdGhlIGBhdXRoYCBmaWVsZCBvZiB0aGUgZG9ja2VyY29uZmlnIGl0IGJ1aWxkcy4gU28g" + - "dGhlCj4gdmFsdWUgbXVzdCBiZSBgYmFzZTY0KCIkb2F1dGh0b2tlbjo8a2V5PiIpYCwgZS5nLiBg" + - "cHJpbnRmICckb2F1dGh0b2tlbjolcycgIiRLRVkiIHwgb3BlbnNzbCBiYXNlNjQgLUFgLAo+IG5v" + - "dCBgYmFzZTY0KDxkb2NrZXJjb25maWdqc29uPilgLgo+ICAtIFdyb25nIGFjY291bnQtYm9vdHN0" + - "cmFwIHZhbHVlOiBgZnVuY3Rpb24gY3JlYXRlYCByZXR1cm5zCj4gICAgYDQwMCAibXVzdCBiZSBi" + - "YXNlNjQgZW5jb2RlZCB1c2VybmFtZTpwYXNzd29yZCBmb3JtYXQiYC4KPiAgLSBXcm9uZyBzaWRl" + - "Y2FyIHZhbHVlOiBldmVyeSB3b3JrZXIncyBzaWRlY2FyIHB1bGwgZ2V0cyBhIG1hbGZvcm1lZCBj" + - "cmVkZW50aWFsIGFuZAo+ICAgIGA0MDNgcywgcmVnYXJkbGVzcyBvZiB3aGljaCBrZXkgaXMgaW5z" + - "aWRlLiBUaGUgc3ltcHRvbSBsb29rcyBsaWtlIGEgYmFkIG9yIHdyb25nLW9yZwo+ICAgIGtleSwg" + - "YnV0IHRoZSBrZXkgaXMgZmluZSBhbmQgdGhlIGVuY29kaW5nIGlzIHRoZSBidWcuIFRvIHJlcGFp" + - "ciBhbiBhbHJlYWR5LWluc3RhbGxlZAo+ICAgIHN0YWNrIHdpdGhvdXQgYSBmdWxsIHJlLXN5bmM6" + - "Cj4gICAgYGBgYmFzaAo+ICAgIEFVVEg9JChwcmludGYgJyRvYXV0aHRva2VuOiVzJyAiJEtFWSIg" + - "fCBvcGVuc3NsIGJhc2U2NCAtQSkKPiAgICBiYW8ga3YgcHV0IHNlcnZpY2VzL252Y2YtYXBpL2t2" + - "L3NpZGVjYXJzL2ltYWdlLXB1bGwtc2VjcmV0IHNlY3JldD0iJEFVVEgiCj4gICAgIyBpZiBOVklE" + - "SUEgQ2xvdWQgVGFza3MgaXMgZW5hYmxlZCwgcmVwZWF0IGZvciBzZXJ2aWNlcy9udmN0LWFwaS9r" + - "di9zaWRlY2Fycy9pbWFnZS1wdWxsLXNlY3JldAo+ICAgIGt1YmVjdGwgcm9sbG91dCByZXN0YXJ0" + - "IC1uIG52Y2YgZGVwbG95L252Y2YtYXBpICAgIyBwaWNrcyB1cCB0aGUgbmV3IHNpZGVjYXIgY3Jl" + - "ZAo+ICAgIGBgYAoKPiBOb3RlIChtdWx0aS1vcmcpOiB3aGVuIHB1bGxpbmcgZnJvbSBOR0MgKG5v" + - "dCBhIG1pcnJvciksIHRoZSB1c2VyIGZ1bmN0aW9uIGltYWdlIGFuZAo+IHRoZSBwbGF0Zm9ybSBz" + - "aWRlY2FycyBvZnRlbiBsaXZlIGluIGRpZmZlcmVudCBudmNyLmlvIG9yZ3MsIGVhY2ggbmVlZGlu" + - "ZyBhIGRpZmZlcmVudAo+IGtleSwgc28gdGhlIGFjY291bnQtYm9vdHN0cmFwIGNyZWQgKGZ1bmN0" + - "aW9uIGltYWdlKSBhbmQgdGhlIHNpZGVjYXIgc2VjcmV0IChzaWRlY2FycykKPiBhcmUgZnJlcXVl" + - "bnRseSB0d28gZGlmZmVyZW50IGtleXMuIE1pcnJvcmluZyBldmVyeXRoaW5nIGludG8gYSBzaW5n" + - "bGUgcmVnaXN0cnkgdGhlCj4gY2x1c3RlciBhdXRoZW50aWNhdGVzIHRvIGF1dG9tYXRpY2FsbHkg" + - "Y29sbGFwc2VzIHRoaXMgdG8gb25lIGNyZWRlbnRpYWwgcGF0aC4KCiMjIyBDb25maWd1cmluZyB3" + - "aXRoIEt5dmVybm8gKHJlY29tbWVuZGVkKQoKVXNlIGEgS3l2ZXJubyBtdXRhdGluZyBhZG1pc3Np" + - "b24gcG9saWN5IHRvIGF1dG9tYXRpY2FsbHkgaW5qZWN0IGBpbWFnZVB1bGxTZWNyZXRzYCBpbnRv" + - "IGFsbCBwb2RzIGluIE5WQ0YgbmFtZXNwYWNlcy4gVGhpcyB3b3JrcyB1bmlmb3JtbHkgZm9yIGFs" + - "bCBjaGFydHMgLS0gbm8gcGVyLWNoYXJ0IGNvbmZpZ3VyYXRpb24gb3IgaGVsbWZpbGUgbW9kaWZp" + - "Y2F0aW9ucyBuZWVkZWQuCgpgYGBiYXNoCiMgMS4gSW5zdGFsbCBLeXZlcm5vCmhlbG0gcmVwbyBh" + - "ZGQga3l2ZXJubyBodHRwczovL2t5dmVybm8uZ2l0aHViLmlvL2t5dmVybm8vCmhlbG0gcmVwbyB1" + - "cGRhdGUKaGVsbSBpbnN0YWxsIGt5dmVybm8ga3l2ZXJuby9reXZlcm5vIC1uIGt5dmVybm8gLS1j" + - "cmVhdGUtbmFtZXNwYWNlCgojIDIuIENyZWF0ZSBwdWxsIHNlY3JldCBpbiBlYWNoIG5hbWVzcGFj" + - "ZQpleHBvcnQgTkdDX0FQSV9LRVk9Ijx5b3VyLWtleT4iCmZvciBucyBpbiBjYXNzYW5kcmEtc3lz" + - "dGVtIG5hdHMtc3lzdGVtIG52Y2YgYXBpLWtleXMgZXNzIHNpcyB2YXVsdC1zeXN0ZW0gY2VydC1t" + - "YW5hZ2VyOyBkbwogIGt1YmVjdGwgY3JlYXRlIHNlY3JldCBkb2NrZXItcmVnaXN0cnkgbnZjci1j" + - "cmVkcyBcCiAgICAtLWRvY2tlci1zZXJ2ZXI9bnZjci5pbyBcCiAgICAtLWRvY2tlci11c2VybmFt" + - "ZT0nJG9hdXRodG9rZW4nIFwKICAgIC0tZG9ja2VyLXBhc3N3b3JkPSIkTkdDX0FQSV9LRVkiIFwK" + - "ICAgIC0tbmFtZXNwYWNlPSIkbnMiIFwKICAgIC0tZHJ5LXJ1bj1jbGllbnQgLW8geWFtbCB8IGt1" + - "YmVjdGwgYXBwbHkgLWYgLQpkb25lCgojIDMuIEFwcGx5IHRoZSBLeXZlcm5vIENsdXN0ZXJQb2xp" + - "Y3kKa3ViZWN0bCBhcHBseSAtZiBreXZlcm5vLWltYWdlcHVsbHNlY3JldC1wb2xpY3kueWFtbApg" + - "YGAKClRoZSBwb2xpY3kgbXV0YXRlcyBldmVyeSBwb2QgYXQgYWRtaXNzaW9uIHRpbWUsIGFkZGlu" + - "ZyBgaW1hZ2VQdWxsU2VjcmV0czogW3tuYW1lOiBudmNyLWNyZWRzfV1gLiBWZXJpZnkgd2l0aDoK" + - "CmBgYGJhc2gKa3ViZWN0bCBnZXQgcG9kIC1uIDxuYW1lc3BhY2U+IDxwb2QtbmFtZT4gLW8ganNv" + - "bnBhdGg9J3suc3BlYy5pbWFnZVB1bGxTZWNyZXRzfScKIyBTaG91bGQgc2hvdzogW3sibmFtZSI6" + - "Im52Y3ItY3JlZHMifV0KYGBgCgpOb3QgbmVlZGVkIGlmIHVzaW5nIGEgQ1NQIGJ1aWx0LWluIGNy" + - "ZWRlbnRpYWwgaGVscGVyIChlLmcuLCBFQ1Igd2l0aCBJQU0gbm9kZSByb2xlcykuCgpGb3IgdGhl" + - "IEt5dmVybm8gcG9saWN5IFlBTUwgYW5kIHB1bGwgc2VjcmV0IGNyZWF0aW9uIHNjcmlwdCwgc2Vl" + - "IFtyZWZlcmVuY2VzL3B1bGwtc2VjcmV0cy5tZF0ocmVmZXJlbmNlcy9wdWxsLXNlY3JldHMubWQp" + - "LgoKIyMgRGVidWdnaW5nCgojIyMgUXVpY2sgc3RhdHVzIGNoZWNrCgpgYGBiYXNoCmt1YmVjdGwg" + - "Z2V0IHBvZHMgLUEgLW8gd2lkZSAgICAgICAgICAgICAgICAgICAgICAgICMgQWxsIHBvZHMKaGVs" + - "bSBsaXN0IC1BICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgQWxsIGhl" + - "bG0gcmVsZWFzZXMKa3ViZWN0bCBnZXQgZXZlbnRzIC1uIDxucz4gLS1zb3J0LWJ5PScubGFzdFRp" + - "bWVzdGFtcCcgICMgUmVjZW50IGV2ZW50cwpgYGAKCiMjIyBDb21tb24gZmFpbHVyZSBwYXR0ZXJu" + - "cwoKfCBTeW1wdG9tIHwgQ2F1c2UgfCBGaXggfAp8LS0tLS0tLS0tfC0tLS0tLS18LS0tLS18Cnwg" + - "YEltYWdlUHVsbEJhY2tPZmZgICsgYDQwMSBVbmF1dGhvcml6ZWRgIHwgTWlzc2luZyBvciB3cm9u" + - "ZyBwdWxsIHNlY3JldCB8IENoZWNrIHNlY3JldCBleGlzdHMsIGNoZWNrIFNBIGhhcyBpbWFnZVB1" + - "bGxTZWNyZXRzIHwKfCBgSW5pdDowLzFgIHN0dWNrIG9uIHNlcnZpY2UgcG9kcyB8IFZhdWx0LWFn" + - "ZW50IHdhaXRpbmcgZm9yIE9wZW5CYW8gfCBDaGVjayBPcGVuQmFvIHBvZHMgKyBtaWdyYXRpb24g" + - "am9iIHN0YXR1cyB8CnwgYEluaXQ6MC8xYCArIGB2YXVsdC1hZ2VudC1pbml0YCBzaG93cyBgYXV0" + - "aC9qd3QvbG9naW5gIC0+IGA0MDAgLi4uIG5vIGtub3duIGtleSBzdWNjZXNzZnVsbHkgdmFsaWRh" + - "dGVkIHRoZSB0b2tlbiBzaWduYXR1cmVgIHwgT3BlbkJhbyBKV1QgYXV0aCBjb25maWd1cmVkIHdp" + - "dGggYSBzdGF0aWMgcHVia2V5IGluc3RlYWQgb2YgdGhlIGNsdXN0ZXIncyBsaXZlIEpXS1MgLS0g" + - "dGhlIGJhc2UgZGVmYXVsdCBgb3BlbmJhby5taWdyYXRpb25zLmlzc3VlckRpc2NvdmVyeS5lbmFi" + - "bGVkOiBmYWxzZWAuIENvbW1vbiBvbiBBS1MgLyBhbnkgT0lEQy1pc3N1ZXIgY2x1c3RlciB8IFNl" + - "dCBgb3BlbmJhby5taWdyYXRpb25zLmlzc3VlckRpc2NvdmVyeS5lbmFibGVkOiB0cnVlYCwgZGVs" + - "ZXRlIGpvYiBgb3BlbmJhby1zZXJ2ZXItbWlncmF0aW9uc2AsIHJlLXN5bmMgYG9wZW5iYW8tc2Vy" + - "dmVyYC4gU2VlIFtyZWZlcmVuY2VzL2RlYnVnZ2luZy5tZF0ocmVmZXJlbmNlcy9kZWJ1Z2dpbmcu" + - "bWQpICJJbml0OjAvMSBTdHVjayIgZml4ICM0LiB8CnwgYE9PTUtpbGxlZGAgb24gQ2Fzc2FuZHJh" + - "IHwgRGVmYXVsdCByZXNvdXJjZXMgdG9vIHNtYWxsIHwgT3ZlcnJpZGUgYGNhc3NhbmRyYS5yZXNv" + - "dXJjZXNgIHZpYSB2YWx1ZXMgYmxvY2sgfAp8IERCLWJhY2tlZCBzZXJ2aWNlcyBjcmFzaC1sb29w" + - "IGBCYWQgY3JlZGVudGlhbHNgIGNvbm5lY3RpbmcgdG8gQ2Fzc2FuZHJhIHwgU2VjcmV0cyBmaWxl" + - "IHNldCBvbmx5IGBERUZBVUxUX0NBU1NBTkRSQV9QQVNTV09SRGA7IGBjYXNzYW5kcmEuc2Vydmlj" + - "ZVJvbGVQYXNzd29yZGAgc3RpbGwgZGVmYXVsdHMgdG8gYGNoQG5nM20zYCB8IFNldCBgY2Fzc2Fu" + - "ZHJhLnNlcnZpY2VSb2xlUGFzc3dvcmRgIGluIHRoZSBzZWNyZXRzIGZpbGUgZXF1YWwgdG8gYERF" + - "RkFVTFRfQ0FTU0FORFJBX1BBU1NXT1JEYCwgdGhlbiByZS1zeW5jIGNhc3NhbmRyYSArIHRoZSBE" + - "Qi1iYWNrZWQgc2VydmljZXMgfAp8IGBQZW5kaW5nYCBwb2RzIHwgTm9kZSBzZWxlY3RvciBtaXNt" + - "YXRjaCBvciBubyBzdG9yYWdlIGNsYXNzIHwgYGt1YmVjdGwgZGVzY3JpYmUgcG9kYCwgY2hlY2sg" + - "bGFiZWxzIGFuZCBzdG9yYWdlIHwKfCBIZWxtIHJlbGVhc2UgaW4gYGZhaWxlZGAgc3RhdGUgfCBG" + - "aXJzdCBpbnN0YWxsIGZhaWxlZCBwYXJ0d2F5IHwgYGhlbG1maWxlIGRlc3Ryb3lgIHRoZSByZWxl" + - "YXNlLCB0aGVuIGBzeW5jYCBhZ2FpbiB8CnwgQWNjb3VudCBib290c3RyYXAgdGltZW91dCB8IFdy" + - "b25nIGJhc2U2NCBjcmVkZW50aWFscyBpbiBzZWNyZXRzIGZpbGUgfCBDaGVjayBga3ViZWN0bCBs" + - "b2dzIGpvYi9udmNmLWFwaS1hY2NvdW50LWJvb3RzdHJhcCAtbiBudmNmYCB8CnwgZnVuY3Rpb24g" + - "LyBhY2NvdW50IGNhbGxzIHJldHVybiBgNDA0IFVua25vd24gY2xpZW50X2lkYCB8IFRoZSBhY2Nv" + - "dW50LWJvb3RzdHJhcCBwb3N0LWluc3RhbGwgaG9vayBuZXZlciByYW46IHRoZSBgYXBpYCByZWxl" + - "YXNlIGluc3RhbGwgZmFpbGVkLCBvciB3YXMgcmVwYWlyZWQgd2l0aCBsaXZlIHBhdGNoZXMgaW5z" + - "dGVhZCBvZiBhIGNsZWFuIHJlLXN5bmMsIHNvIHRoZSBob29rIChhbmQgdGhlIGFjY291bnQpIHdh" + - "cyBza2lwcGVkIHwgVmVyaWZ5IGFuZCByZS1ydW4gdGhlIGhvb2s7IHNlZSBbVmVyaWZ5IHRoZSBh" + - "Y2NvdW50IGJvb3RzdHJhcCByYW5dKCN2ZXJpZnktdGhlLWFjY291bnQtYm9vdHN0cmFwLXJhbikg" + - "fAp8IFNlcnZpY2VzIGZhaWwgdG8gcmVhZCB2YXVsdCBzZWNyZXRzOyBgc2VjcmV0cy5qc29uYCBu" + - "b3QgZm91bmQgfCBWYXVsdCBwYXRoIGhhcmRjb2RlZCB0byBgL2hvbWUvYXBwL3ZhdWx0L2AgaW4g" + - "YF9oZWxwZXJzLnRwbGA7IHRoZSBydW50aW1lIHJlc29sdmVzIHRoZSBtb3VudGVkIHBhdGggcmVs" + - "YXRpdmUgdG8gdGhlIHdvcmtpbmcgZGlyZWN0b3J5IGFuZCBkcm9wcyB0aGUgbGVhZGluZyBgL2Ag" + - "fCBPdmVycmlkZSBgcG9kQW5ub3RhdGlvbnNgIGFuZCBzZXQgYEpBVkFfVE9PTF9PUFRJT05TOiAi" + - "LUR1c2VyLmRpcj0vImAgaW4gcmVsZWFzZSB2YWx1ZXMgfAp8IE5BVFMgY29ubmVjdGlvbiBmYWls" + - "cyBhdCBzdGFydHVwOyBwbGFjZW1lbnQgdGFnIG1pc21hdGNoIHwgTkFUUyBzZXJ2ZXIgdGFncyBo" + - "YXJkY29kZWQgdG8gYGRjOm5jcGA7IGFwcCBkZXJpdmVzIHRhZyBmcm9tIGBBV1NfUkVHSU9OYCAo" + - "ZS5nLiwgYHVzLWdvdi13ZXN0LTFgKSB8IFNldCBgQVdTX1JFR0lPTj1uY3BgIGFuZCBgTlZDRl9B" + - "V1NfUkVHSU9OPW5jcGAgaW4gZW52IGNvbmZpZyB8CgojIyMgVmVyaWZ5IHRoZSBhY2NvdW50IGJv" + - "b3RzdHJhcCByYW4KClRoZSBkZWZhdWx0IGFjY291bnQgaXMgY3JlYXRlZCBieSBhICoqYHBvc3Qt" + - "aW5zdGFsbGAgaG9vayBvbiB0aGUgYGFwaWAgcmVsZWFzZSoqCihgam9iL252Y2YtYXBpLWFjY291" + - "bnQtYm9vdHN0cmFwYCBpbiBgbnZjZmApLiBCZWNhdXNlIGl0IGlzIGEgcG9zdC1pbnN0YWxsIGhv" + - "b2ssIGl0IG9ubHkKZmlyZXMgb24gYSBzdWNjZXNzZnVsIGBhcGlgIGluc3RhbGwuIElmIHRoYXQg" + - "aW5zdGFsbCBmYWlsZWQgYW5kIHlvdSByZXBhaXJlZCBpdCB3aXRoIGxpdmUKcGF0Y2hlcyAocmF0" + - "aGVyIHRoYW4gYSBjbGVhbiByZS1zeW5jKSwgdGhlIGhvb2sgbmV2ZXIgcnVucywgbm8gYWNjb3Vu" + - "dCBpcyBjcmVhdGVkLCBhbmQKZXZlcnkgc3Vic2VxdWVudCBmdW5jdGlvbi9hY2NvdW50IGNhbGwg" + - "cmV0dXJucyBhIGNyeXB0aWMgYDQwNCBVbmtub3duIGNsaWVudF9pZGAgd2l0aApub3RoaW5nIG9i" + - "dmlvdXNseSB3cm9uZyBpbiB0aGUgcnVubmluZyBwb2RzLgoKVmVyaWZ5IGl0IHJhbiBhZnRlciB0" + - "aGUgZmlyc3Qgc3luYzoKCmBgYGJhc2gKa3ViZWN0bCBnZXQgam9iIC1uIG52Y2YgbnZjZi1hcGkt" + - "YWNjb3VudC1ib290c3RyYXAgXAogIC1vIGpzb25wYXRoPSd7LnN0YXR1cy5zdWNjZWVkZWR9JyAg" + - "ICMgZXhwZWN0IDEKYGBgCgpJZiB0aGUgam9iIGlzIGFic2VudCBvciBgMGAsIHJlLXJ1biBpdCBi" + - "eSByZS1zeW5jaW5nIHRoZSBgYXBpYCByZWxlYXNlIHNvIHRoZSBob29rIGZpcmVzCmFnYWluIChk" + - "ZWxldGUgdGhlIHByaW9yIGpvYiBmaXJzdCBpZiBpdCBsaW5nZXJzIGluIGEgY29tcGxldGVkL2Zh" + - "aWxlZCBzdGF0ZSBhbmQgYmxvY2tzIHRoZQpob29rKToKCmBgYGJhc2gKa3ViZWN0bCBkZWxldGUg" + - "am9iIC1uIG52Y2YgbnZjZi1hcGktYWNjb3VudC1ib290c3RyYXAgLS1pZ25vcmUtbm90LWZvdW5k" + - "CkhFTE1GSUxFX0VOVj08ZW52PiBoZWxtZmlsZSAtLXNlbGVjdG9yIG5hbWU9YXBpIHN5bmMKa3Vi" + - "ZWN0bCBsb2dzIC1uIG52Y2Ygam9iL252Y2YtYXBpLWFjY291bnQtYm9vdHN0cmFwICAgIyBjb25m" + - "aXJtIHRoZSBhY2NvdW50IHdhcyBjcmVhdGVkCmBgYAoKIyMgQWRkaXRpb25hbCBSZXNvdXJjZXMK" + - "Ci0gSW5zdGFsbGF0aW9uOiBbZXhhbXBsZXMubWRdKGV4YW1wbGVzLm1kKSwgdGhlIFtDU1AgRW5k" + - "LXRvLUVuZCBFeGFtcGxlXShodHRwczovL2RvY3MubnZpZGlhLmNvbS9udmNmL3YwLjYuMC1yYy9j" + - "c3AtZW5kLXRvLWVuZC1leGFtcGxlKSwgYW5kIFtzcGxpdCBjb21wdXRlLXBsYW5lIHdvcmtmbG93" + - "c10ocmVmZXJlbmNlcy9jb21wdXRlLXBsYW5lLWluc3RhbGxhdGlvbi5tZCkKLSBPcGVyYXRpb25z" + - "OiBbSGVsbWZpbGUgc3RydWN0dXJlXShyZWZlcmVuY2VzL2hlbG1maWxlLXN0cnVjdHVyZS5tZCks" + - "IFtwdWxsIHNlY3JldHNdKHJlZmVyZW5jZXMvcHVsbC1zZWNyZXRzLm1kKSwgYW5kIFtkZWJ1Z2dp" + - "bmddKHJlZmVyZW5jZXMvZGVidWdnaW5nLm1kKQoKQWZ0ZXIgZGVwbG95bWVudCwgdXNlIHRoZSBg" + - "bnZjZi1zZWxmLW1hbmFnZWQtY2xpYCBza2lsbCB0byBjcmVhdGUgZnVuY3Rpb25zLCBjcmVhdGUg" + - "dGFza3MsIG1hbmFnZSBBUEkga2V5cywgYW5kIGludm9rZSBlbmRwb2ludHMuClBLBwgv2rl1QmIA" + - "AEJiAABQSwMEFAAIAAAAAAAhAAAAAAAAAAAAAAAAADQACQBkYXRhL252Y2Ytc2VsZi1tYW5hZ2Vk" + - "LWluc3RhbGxhdGlvbi9ldmFscy9ldmFscy5qc29uVVQFAAEAps4SWwogIHsKICAgICJpZCI6ICJH" + - "MS12YWxpZGF0ZS10b29sLXZlcnNpb25zIiwKICAgICJxdWVzdGlvbiI6ICJJbnN0YWxsIHRoZSBO" + - "VkNGIHNlbGYtbWFuYWdlZCBzdGFjayBvbiBteSBBV1MgRUtTIGNsdXN0ZXIiLAogICAgImV4cGVj" + + "PmAgfAoKIyMgQ29tcHV0ZS1wbGFuZSBydW50aW1lCgp8IFN5bXB0b20gfCBDYXVzZSB8IFJlbWVk" + + "aWF0aW9uIHwKfC0tLXwtLS18LS0tfAp8IE5WQ0EgYWdlbnQgQ3Jhc2hMb29wQmFja09mZiB3aXRo" + + "IGBIVFRQIDQwMWAgb24gYC92MS9udmNhL2NsdXN0ZXJzLy4uLi9yZWdpc3RlcmAgfCBTdGFsZSBg" + + "b3V0LzxjbHVzdGVyPi1yZWdpc3Rlci12YWx1ZXMueWFtbGAgZnJvbSBwcmlvciByZWdpc3Rlcjsg" + + "Y2x1c3RlciBJRCBkb2Vzbid0IG1hdGNoIElDTVMgfCBEZWxldGUgdGhlIHN0YWxlIGZpbGU7IHJl" + + "LXJ1biBgbnZjZi1jbGkgc2VsZi1ob3N0ZWQgdXBgIChpZGVtcG90ZW50IHJlZ2lzdGVyIHdyaXRl" + + "cyBhIGZyZXNoIGZpbGUpIHwKfCBgTm8gY2x1c3RlciBmb3VuZCB3aXRoIHZhbGlkIEpXS1MgZm9y" + + "IGNsdXN0ZXIgSUQ6IDxpZD5gIChJQ01TIGxvZykgfCBKV0tTIGluIElDTVMgZG9lc24ndCBtYXRj" + + "aCB3aGF0IGNvbXB1dGUtcGxhbmUgSzhzIGlzIGN1cnJlbnRseSBzaWduaW5nIHwgYG52Y2YtY2xp" + + "IGNsdXN0ZXIgcm90YXRlIC0tY2x1c3Rlci1pZD08aWQ+YCB8CnwgRnVuY3Rpb24gcG9kIGBJbWFn" + + "ZVB1bGxCYWNrT2ZmYCBpbiBgbnZjZi1iYWNrZW5kYCB8IEltYWdlLWNyZWRlbnRpYWwtaGVscGVy" + + "IGNvbmZpZyBtaXNzaW5nIG9yIHdyb25nIHwgYGt1YmVjdGwgZGVzY3JpYmUgcG9kIC1uIG52Y2Yt" + + "YmFja2VuZCA8cG9kPmA7IGNoZWNrIHRoZSBwdWxsIHNlY3JldCBjb25maWcgfAp8IEZ1bmN0aW9u" + + "IGRlcGxveSBzdHVjayBgREVQTE9ZSU5HYCBmb3IgPjUgbWluIHwgTkFUUyBzdHJlYW0gaW5pdCBs" + + "YWcgKGNvbGQtY2x1c3Rlcikg4oCUIHdhaXQsIG9yIGNoZWNrIE5BVFMgYXV0aC1jYWxsb3V0IGhl" + + "YWx0aCB8IGBrdWJlY3RsIGxvZ3MgLW4gbmF0cy1zeXN0ZW0gLWwgYXBwLmt1YmVybmV0ZXMuaW8v" + + "bmFtZT1uYXRzLWF1dGgtY2FsbG91dC1zZXJ2aWNlYCB8CgojIyBUYXNrcwoKfCBTeW1wdG9tIHwg" + + "Q2F1c2UgfCBSZW1lZGlhdGlvbiB8CnwtLS18LS0tfC0tLXwKfCBgdGFzayBjcmVhdGVgIHJldHVy" + + "bnMgYE1pc3NpbmcgPFRZUEU+IHJlZ2lzdHJ5IGNyZWRlbnRpYWwgZm9yIGhvc3RuYW1lYCwgb3Ig" + + "a2VlcHMgdXNpbmcgYSBwcmV2aW91cyBjcmVkZW50aWFsIHZhbHVlLCBzaG9ydGx5IGFmdGVyIGBy" + + "ZWdpc3RyeS1jcmVkZW50aWFsIGFkZC91cGRhdGUvZGVsZXRlYCB3aGlsZSBgcmVnaXN0cnktY3Jl" + + "ZGVudGlhbCBsaXN0YC9gZ2V0YCBhbHJlYWR5IHNob3cgdGhlIG5ldyB2YWx1ZSB8IEV4cGVjdGVk" + + "IHByb3BhZ2F0aW9uIGRlbGF5OiBOVkNUIGNhY2hlcyB0aGUgYWNjb3VudCdzIHJlZ2lzdHJ5IGNy" + + "ZWRlbnRpYWxzIHdpdGggYG52Y3QubnZjZi5jYWNoZS10dGxgIChkZWZhdWx0IGBQVDVNYCwgYWJv" + + "dXQgNSBtaW4pIGFuZCBhcHBsaWVzIHRoZSBjaGFuZ2Ugd2hlbiB0aGF0IGNhY2hlZCBjb3B5IHJl" + + "ZnJlc2hlcyB8IFdhaXQgdXAgdG8gYWJvdXQgNSBtaW4gYW5kIHJldHJ5LCBvciBga3ViZWN0bCAt" + + "biBudmNmIHJvbGxvdXQgcmVzdGFydCBkZXBsb3ltZW50L252Y3QtYXBpYCB0byBhcHBseSBpbW1l" + + "ZGlhdGVseSB8ClBLBwjzrhOEdBEAAHQRAABQSwMEFAAIAAAAAAAhAAAAAAAAAAAAAAAAACwACQBk" + + "YXRhL252Y2Ytc2VsZi1tYW5hZ2VkLWluc3RhbGxhdGlvbi9TS0lMTC5tZFVUBQABAKbOEi0tLQpu" + + "YW1lOiBudmNmLXNlbGYtbWFuYWdlZC1pbnN0YWxsYXRpb24KZGVzY3JpcHRpb246ID4tCiAgSW5z" + + "dGFsbCBhbmQgb3BlcmF0ZSBOVkNGIHNlbGYtaG9zdGVkIGNvbnRyb2wtcGxhbmUgYW5kIHNlcGFy" + + "YXRlIGNvbXB1dGUtcGxhbmUKICBzdGFja3MuIENvdmVycyBIZWxtZmlsZSB2YWx1ZXMgYW5kIENM" + + "SSBwcm9maWxlIGluc3RhbGxhdGlvbiBmbG93cywgdGVhcmRvd24sCiAgdmFsdWVzIG92ZXJyaWRl" + + "cywgcHVsbCBzZWNyZXRzLCBhbmQgdHJvdWJsZXNob290aW5nLiBVc2UgZm9yCiAgbnZjZi1zZWxm" + + "LW1hbmFnZWQtc3RhY2ssIG52Y2YtY29tcHV0ZS1wbGFuZS1zdGFjaywgc3BsaXQgY29tcHV0ZS1w" + + "bGFuZQogIGluc3RhbGxhdGlvbiwgY29udHJvbC1wbGFuZSBpbnN0YWxsYXRpb24sIENMSS1nZW5l" + + "cmF0ZWQgY29udHJvbC1wbGFuZQogIHByb2ZpbGVzLCBIZWxtZmlsZSwgc2VsZi1tYW5hZ2VkLCBv" + + "ciBzZWxmLWhvc3RlZCBkZXBsb3ltZW50cy4gRG8gTk9UIHVzZSBmb3IKICBsb2NhbCBrM2QgZW52" + + "aXJvbm1lbnRzOyB1c2UgdGhlIGxvY2FsIGszZCBkZXZlbG9wbWVudCB3b3JrZmxvdyBpbnN0ZWFk" + + "LgpsaWNlbnNlOiBBcGFjaGUtMi4wCmNvbXBhdGliaWxpdHk6IFJlcXVpcmVzIGhlbG1maWxlID49" + + "IDEuMS4wIDwgMS4yLjAsIGhlbG0gPj0gMy4xMiwgaGVsbS1kaWZmIHBsdWdpbiwga3ViZWN0bCBt" + + "YXRjaGluZyBjbHVzdGVyIHZlcnNpb24KYXV0aG9yOiAibnZjZi1jb3JlLWVuZyA8bnZjZi1jb3Jl" + + "LWVuZ0BleGNoYW5nZS5udmlkaWEuY29tPiIKdmVyc2lvbjogIjEuMC4wIgp0YWdzOiBbbnZjZiwg" + + "c2VsZi1tYW5hZ2VkLCBoZWxtZmlsZSwgc2VsZi1ob3N0ZWQsIGNvbnRyb2wtcGxhbmUsIGNvbXB1" + + "dGUtcGxhbmUsIGNsaS1wcm9maWxlLCBpbnN0YWxsYXRpb24sIGRlcGxveW1lbnQsIHB1bGwtc2Vj" + + "cmV0c10KdG9vbHM6IFtTaGVsbCwgUmVhZCwgRWRpdCwgR3JlcCwgR2xvYl0KbWV0YWRhdGE6CiAg" + + "aW50ZXJuYWw6IGZhbHNlCiAgYXV0aG9yOiAibnZjZi1jb3JlLWVuZyA8bnZjZi1jb3JlLWVuZ0Bl" + + "eGNoYW5nZS5udmlkaWEuY29tPiIKICB2ZXJzaW9uOiAiMS4wIgogIHRhZ3M6IFtudmNmLCBzZWxm" + + "LW1hbmFnZWQsIGhlbG1maWxlLCBzZWxmLWhvc3RlZCwgY29udHJvbC1wbGFuZSwgY29tcHV0ZS1w" + + "bGFuZSwgY2xpLXByb2ZpbGUsIGluc3RhbGxhdGlvbiwgZGVwbG95bWVudCwgcHVsbC1zZWNyZXRz" + + "XQogIGxhbmd1YWdlczogW2Jhc2gsIHlhbWxdCiAgZnJhbWV3b3JrczogW2hlbG1maWxlLCBoZWxt" + + "LCBrdWJlY3RsXQogIGRvbWFpbjogY2xvdWQtaW5mcmFzdHJ1Y3R1cmUKLS0tCgojIE5WQ0YgU2Vs" + + "Zi1NYW5hZ2VkIFN0YWNrIE9wZXJhdGlvbnMKCk9wZXJhdGlvbmFsIGd1aWRlIGZvciBtYXRjaGlu" + + "ZyBOVkNGIGNvbnRyb2wtcGxhbmUgYW5kIGNvbXB1dGUtcGxhbmUgSGVsbWZpbGUgYnVuZGxlcy4K" + + "CiMjIEluc3RydWN0aW9ucwoKVXNlIHRoaXMgc2tpbGwgZm9yIGluc3RhbGwsIHVwZ3JhZGUsIG9y" + + "IHRlYXJkb3duIHdvcmsgaW4gbWF0Y2hpbmcgYG52Y2Ytc2VsZi1tYW5hZ2VkLXN0YWNrYCBhbmQg" + + "YG52Y2YtY29tcHV0ZS1wbGFuZS1zdGFja2AgYnVuZGxlczsga2VlcCBIZWxtZmlsZSB2YWx1ZXMg" + + "YW5kIENMSSBwcm9maWxlIGhhbmRvZmZzIHNlcGFyYXRlLiBGb3IgYGZ1bmN0aW9uVHlwZTogIkxM" + + "TSJgLCByZWFkIFtMTE0gRnVuY3Rpb24gRW5hYmxlbWVudF0ocmVmZXJlbmNlcy9oZWxtZmlsZS1z" + + "dHJ1Y3R1cmUubWQjbGxtLWZ1bmN0aW9uLWVuYWJsZW1lbnQpLgoKIyMgUHJlcmVxdWlzaXRlcwoK" + + "QXNrIGZvciB0aGUgZXh0cmFjdGVkIGBudmNmLXNlbGYtbWFuYWdlZC1zdGFja2AgcGF0aC4gRm9y" + + "IHNwbGl0IGluc3RhbGxhdGlvbiwKYWxzbyByZXF1aXJlIHRoZSBtYXRjaGluZy12ZXJzaW9uIGBu" + + "dmNmLWNvbXB1dGUtcGxhbmUtc3RhY2tgIHBhdGguIFZlcmlmeSBib3RoOgoKYGBgYmFzaApscyA8" + + "Y29udHJvbC1wbGFuZS1zdGFjaz4vaGVsbWZpbGUuZC8gPGNvbnRyb2wtcGxhbmUtc3RhY2s+L2Vu" + + "dmlyb25tZW50cy8gPGNvbnRyb2wtcGxhbmUtc3RhY2s+L3NlY3JldHMvIDxjb250cm9sLXBsYW5l" + + "LXN0YWNrPi9nbG9iYWwueWFtbC5nb3RtcGwKbHMgPGNvbXB1dGUtcGxhbmUtc3RhY2s+L2hlbG1m" + + "aWxlLmQvIDxjb21wdXRlLXBsYW5lLXN0YWNrPi9lbnZpcm9ubWVudHMvIDxjb21wdXRlLXBsYW5l" + + "LXN0YWNrPi9nbG9iYWwueWFtbC5nb3RtcGwKYGBgCgpJZiBlaXRoZXIgZGlyZWN0b3J5IGlzIG1p" + + "c3NpbmcsIGRvd25sb2FkIG1hdGNoaW5nIGJ1bmRsZSB2ZXJzaW9ucyBmcm9tIE5HQzoKCmBgYGJh" + + "c2gKbmdjIHJlZ2lzdHJ5IHJlc291cmNlIGRvd25sb2FkLXZlcnNpb24gPG9yZz4vbnZjZi1zZWxm" + + "LW1hbmFnZWQtc3RhY2s6PHZlcnNpb24+Cm5nYyByZWdpc3RyeSByZXNvdXJjZSBkb3dubG9hZC12" + + "ZXJzaW9uIDxvcmc+L252Y2YtY29tcHV0ZS1wbGFuZS1zdGFjazo8dmVyc2lvbj4KYGBgCgpDb250" + + "cm9sLXBsYW5lIGNvbW1hbmRzIGJlbG93IGFzc3VtZSB0aGUgY29udHJvbC1wbGFuZSBidW5kbGUg" + + "cm9vdC4gU3BsaXQtZmxvdwpjb21tYW5kcyBhbmQgcm9vdCBzdWJzdGl0dXRpb24gYXJlIGluCltT" + + "cGxpdCBDb21wdXRlLVBsYW5lIEluc3RhbGxhdGlvbl0ocmVmZXJlbmNlcy9jb21wdXRlLXBsYW5l" + + "LWluc3RhbGxhdGlvbi5tZCkuCgojIyBCZWZvcmUgWW91IFN0YXJ0CgpWZXJpZnkgdG9vbGluZyBh" + + "bmQgY29udGV4dCBiZWZvcmUgYW55IG9wZXJhdGlvbjoKCmBgYGJhc2gKaGVsbWZpbGUgLS12ZXJz" + + "aW9uICAgIyBNdXN0IGJlIDEuMS54ICgxLjIuMCByZW1vdmVkIHNlcXVlbnRpYWwgbW9kZSkKaGVs" + + "bSB2ZXJzaW9uICAgICAgICAgIyBNdXN0IGJlID49IDMuMTIKaGVsbSBwbHVnaW4gbGlzdCAgICAg" + + "IyBNdXN0IGluY2x1ZGUgaGVsbS1kaWZmID49IDMuMTEKa3ViZWN0bCB2ZXJzaW9uICAgICAgIyBD" + + "bGllbnQgbXVzdCBiZSB3aXRoaW4gMSBtaW5vciB2ZXJzaW9uIG9mIGNsdXN0ZXIKYGBgCgpDb250" + + "cm9sLXBsYW5lIGNvbW1hbmRzIHJ1biBmcm9tIHRoZSBleHRyYWN0ZWQgYG52Y2Ytc2VsZi1tYW5h" + + "Z2VkLXN0YWNrL2Agcm9vdDoKCmBgYGJhc2gKY2QgcGF0aC90by9udmNmLXNlbGYtbWFuYWdlZC1z" + + "dGFjawpscyBoZWxtZmlsZS5kLyBlbnZpcm9ubWVudHMvIHNlY3JldHMvIGdsb2JhbC55YW1sLmdv" + + "dG1wbApgYGAKCklkZW50aWZ5IHRoZSBlbnZpcm9ubWVudCBmcm9tIGBlbnZpcm9ubWVudHMvPG5h" + + "bWU+LnlhbWxgIGFuZApgc2VjcmV0cy88bmFtZT4tc2VjcmV0cy55YW1sYDsgZm9yIEVLUywgdXNl" + + "IHRoZSBjYW5vbmljYWwKW0NTUCBFbmQtdG8tRW5kIEV4YW1wbGVdKGh0dHBzOi8vZG9jcy5udmlk" + + "aWEuY29tL252Y2YvdjAuNi4wLXJjL2NzcC1lbmQtdG8tZW5kLWV4YW1wbGUpLgoKIyMgSG93IFZh" + + "bHVlcyBGbG93CgpVbmRlcnN0YW5kaW5nIHZhbHVlIHByZWNlZGVuY2UgcHJldmVudHMgdGhlIG1v" + + "c3QgY29tbW9uIGNvbmZpZ3VyYXRpb24gbWlzdGFrZXMuCgpgYGB0ZXh0CmVudmlyb25tZW50cy9i" + + "YXNlLnlhbWwgICAgICAgICAgKGRlZmF1bHRzKQogICAgLT4gbWVyZ2VkIHdpdGgKZW52aXJvbm1l" + + "bnRzLzxlbnY+LnlhbWwgICAgICAgICAoeW91ciBvdmVycmlkZXMpCiAgICAtPiBjb25zdW1lZCBi" + + "eQpnbG9iYWwueWFtbC5nb3RtcGwgICAgICAgICAgICAgIChHbyB0ZW1wbGF0ZSwgY29uc3RydWN0" + + "cyBwZXItY2hhcnQgdmFsdWVzKQogICAgLT4gY29uc3VtZWQgYnkKc2VjcmV0cy88ZW52Pi1zZWNy" + + "ZXRzLnlhbWwgICAgICAoc2Vuc2l0aXZlIHZhbHVlcykKICAgIC0+IG92ZXJyaWRkZW4gYnkKcmVs" + + "ZWFzZSBpbmxpbmUgdmFsdWVzOiBibG9ja3MgICAoaGlnaGVzdCBwcmVjZWRlbmNlKQpgYGAKCkNy" + + "aXRpY2FsOiBgZ2xvYmFsLnlhbWwuZ290bXBsYCBvbmx5IHBhc3NlcyB0aHJvdWdoIHNwZWNpZmlj" + + "IGtleXMgdG8gZWFjaCBjaGFydCAoaW1hZ2UgcmVnaXN0cmllcywgbm9kZSBzZWxlY3RvcnMsIHN0" + + "b3JhZ2UsIHJlcGxpY2EgY291bnRzKS4gU2V0dGluZyBhbiBhcmJpdHJhcnkgY2hhcnQgdmFsdWUg" + + "aW4gdGhlIGVudmlyb25tZW50IGZpbGUgd2lsbCBiZSBzaWxlbnRseSBpZ25vcmVkIGlmIGBnbG9i" + + "YWwueWFtbC5nb3RtcGxgIGRvZXMgbm90IHByb3BhZ2F0ZSBpdC4KClRvIG92ZXJyaWRlIGFyYml0" + + "cmFyeSBjaGFydCB2YWx1ZXMsIHVzZSBhIGhlbG1maWxlIHJlbGVhc2UgYHZhbHVlczpgIGJsb2Nr" + + "LiBTZWUgW092ZXJyaWRpbmcgSGVsbSBDaGFydCBWYWx1ZXNdKCNvdmVycmlkaW5nLWhlbG0tY2hh" + + "cnQtdmFsdWVzKS4KCiMjIENsZWFuIEluc3RhbGxhdGlvbgoKIyMjIDEuIENyZWF0ZSBuYW1lc3Bh" + + "Y2VzIGFuZCBpbWFnZSBwdWxsIHNlY3JldHMgKGlmIHVzaW5nIGEgcHJpdmF0ZSByZWdpc3RyeSkK" + + "CmBgYGJhc2gKZm9yIG5zIGluIGNhc3NhbmRyYS1zeXN0ZW0gbmF0cy1zeXN0ZW0gbnZjZiBhcGkt" + + "a2V5cyBlc3Mgc2lzIHZhdWx0LXN5c3RlbSBjZXJ0LW1hbmFnZXI7IGRvCiAga3ViZWN0bCBjcmVh" + + "dGUgbmFtZXNwYWNlICIkbnMiIC0tZHJ5LXJ1bj1jbGllbnQgLW8geWFtbCB8IGt1YmVjdGwgYXBw" + + "bHkgLWYgLQpkb25lCgojIE9ubHkgaWYgcHVsbGluZyBmcm9tIGEgcHJpdmF0ZSByZWdpc3RyeSAo" + + "ZS5nLiwgTkdDIG52Y3IuaW8pCmV4cG9ydCBOR0NfQVBJX0tFWT0iPHlvdXIta2V5PiIKZm9yIG5z" + + "IGluIGNhc3NhbmRyYS1zeXN0ZW0gbmF0cy1zeXN0ZW0gbnZjZiBhcGkta2V5cyBlc3Mgc2lzIHZh" + + "dWx0LXN5c3RlbSBjZXJ0LW1hbmFnZXI7IGRvCiAga3ViZWN0bCBjcmVhdGUgc2VjcmV0IGRvY2tl" + + "ci1yZWdpc3RyeSBudmNyLWNyZWRzIFwKICAgIC0tZG9ja2VyLXNlcnZlcj1udmNyLmlvIFwKICAg" + + "IC0tZG9ja2VyLXVzZXJuYW1lPSckb2F1dGh0b2tlbicgXAogICAgLS1kb2NrZXItcGFzc3dvcmQ9" + + "IiROR0NfQVBJX0tFWSIgXAogICAgLS1uYW1lc3BhY2U9IiRucyIgXAogICAgLS1kcnktcnVuPWNs" + + "aWVudCAtbyB5YW1sIHwga3ViZWN0bCBhcHBseSAtZiAtCmRvbmUKYGBgCgpJZiB1c2luZyBwdWxs" + + "IHNlY3JldHMsIHlvdSBtdXN0IGFsc28gY29uZmlndXJlIGVhY2ggaGVsbWZpbGUgcmVsZWFzZSB0" + + "byByZWZlcmVuY2UgdGhlbS4gU2VlIFtJbWFnZSBQdWxsIFNlY3JldHNdKCNpbWFnZS1wdWxsLXNl" + + "Y3JldHMpLgoKIyMjIDIuIEF1dGhlbnRpY2F0ZSBoZWxtIHRvIHlvdXIgY2hhcnQgcmVnaXN0cnkK" + + "CkJvdGggYGRvY2tlciBsb2dpbmAgQU5EIGBoZWxtIHJlZ2lzdHJ5IGxvZ2luYCBhcmUgcmVxdWly" + + "ZWQgZm9yIE5HQy4gSGVsbWZpbGUgdXNlcyBoZWxtIChub3QgZG9ja2VyKSB0byBwdWxsIE9DSSBj" + + "aGFydHMsIHNvIHRoZSBoZWxtIGNyZWRlbnRpYWwgc3RvcmUgbXVzdCBiZSBhdXRoZW50aWNhdGVk" + + "IHNlcGFyYXRlbHkuCgpgYGBiYXNoCiMgTkdDIC0tIGJvdGggY29tbWFuZHMgYXJlIHJlcXVpcmVk" + + "CmRvY2tlciBsb2dpbiBudmNyLmlvIC11ICckb2F1dGh0b2tlbicgLXAgIiROR0NfQVBJX0tFWSIK" + + "aGVsbSByZWdpc3RyeSBsb2dpbiBudmNyLmlvIC11ICckb2F1dGh0b2tlbicgLXAgIiROR0NfQVBJ" + + "X0tFWSIKCiMgRUNSCmF3cyBlY3IgZ2V0LWxvZ2luLXBhc3N3b3JkIC0tcmVnaW9uIDxyZWdpb24+" + + "IHwgXAogIGhlbG0gcmVnaXN0cnkgbG9naW4gLS11c2VybmFtZSBBV1MgLS1wYXNzd29yZC1zdGRp" + + "biA8YWNjb3VudD4uZGtyLmVjci48cmVnaW9uPi5hbWF6b25hd3MuY29tCmBgYAoKPiBOb3RlOiBg" + + "aGVsbSByZWdpc3RyeSBsb2dpbmAga2VlcHMgb25seSB0aGUgbGFzdCBsb2dpbiBwZXIgaG9zdCAo" + + "bXVsdGktb3JnIGdvdGNoYSkuCj4gVGhlIHN0YWNrIGFuZCB0aGUgY2FjaGVzIHNwYW4gc2V2ZXJh" + + "bCBOR0Mgb3JncyB0aGF0IG5lZWQgZGlmZmVyZW50IGtleXMsIGJ1dCB0aGV5IGFsbAo+IGxpdmUg" + + "dW5kZXIgdGhlIG9uZSBob3N0IGBudmNyLmlvYC4gQSBzZWNvbmQgYGhlbG0gcmVnaXN0cnkgbG9n" + + "aW4gbnZjci5pb2AgKG9yCj4gYGRvY2tlciBsb2dpbmApIHdpdGggYSBkaWZmZXJlbnQga2V5IHNp" + + "bGVudGx5IG92ZXJ3cml0ZXMgdGhlIGZpcnN0LCBzbyBwdWxscyBmb3IgdGhlCj4gZmlyc3Qgb3Jn" + + "IHRoZW4gZmFpbCB3aXRoIGEgbWlzbGVhZGluZyBgNDAzIEFjY2VzcyBEZW5pZWRgIChub3QgYSA0" + + "MDE6IHRoZSBhdXRoCj4gc3VjY2VlZGVkLCBpdCdzIHRoZSB3cm9uZy1vcmcga2V5KS4gSWYgeW91" + + "IHB1bGwgY2hhcnRzL2ltYWdlcyBmcm9tIG1vcmUgdGhhbiBvbmUgb3JnLAo+IGVpdGhlciByZS1y" + + "dW4gdGhlIGxvZ2luIHdpdGggdGhlIGNvcnJlY3Qga2V5IGltbWVkaWF0ZWx5IGJlZm9yZSBlYWNo" + + "IHB1bGwsIG9yIHBhc3MKPiBgLS11c2VybmFtZSAnJG9hdXRodG9rZW4nIC0tcGFzc3dvcmQgPGtl" + + "eT5gIHBlciBgaGVsbSBwdWxsYCBpbnZvY2F0aW9uIGluc3RlYWQgb2YgcmVseWluZwo+IG9uIHRo" + + "ZSBzdG9yZWQgbG9naW4uIChQdWxsaW5nIGV2ZXJ5dGhpbmcgZnJvbSBhIHNpbmdsZSByZWdpc3Ry" + + "eSBtaXJyb3IgYXZvaWRzIHRoaXMKPiBlbnRpcmVseS4pCgojIyMgMmIuIChSZWNvbW1lbmRlZCkg" + + "UHVsbCBmcm9tIGEgcmVnaXN0cnkgdGhlIGNsdXN0ZXIgYXV0aGVudGljYXRlcyB0byBhdXRvbWF0" + + "aWNhbGx5CgpJZiB5b3UgbWlycm9yIHRoZSBOVkNGIGltYWdlcyBpbnRvIGEgcmVnaXN0cnkgdGhh" + + "dCB0aGUgY2x1c3RlciBjYW4gcHVsbCBmcm9tIHdpdGhvdXQgYW4KZXhwbGljaXQgaW1hZ2UgcHVs" + + "bCBzZWNyZXQgKGZvciBleGFtcGxlLCBhIHJlZ2lzdHJ5IHdoZXJlIHRoZSBub2RlIG9yIHdvcmts" + + "b2FkIGlkZW50aXR5IGlzCmdyYW50ZWQgcHVsbCBhY2Nlc3MgYXV0b21hdGljYWxseSksIHBvaW50" + + "IHRoZSBzdGFjayBhdCBpdCBhbmQgc2tpcCB0aGUgY3JlZGVudGlhbCBzdGVwcwphYm92ZS4KCjEu" + + "IFBvaW50IHRoZSBzdGFjayBhdCB0aGUgbWlycm9yIGluIGBlbnZpcm9ubWVudHMvPGVudj4ueWFt" + + "bGA6CgogICBgYGB5YW1sCiAgIGdsb2JhbDoKICAgICBpbWFnZToKICAgICAgIHJlZ2lzdHJ5OiA8" + + "eW91ci1yZWdpc3RyeT4gICAgICAgICAgICAjIGUuZy4gcmVnaXN0cnkuZXhhbXBsZS5jb20KICAg" + + "ICAgIHJlcG9zaXRvcnk6IDxtaXJyb3Itc3ViLXBhdGg+CiAgICAgaGVsbToKICAgICAgIHNvdXJj" + + "ZXM6ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBvbmx5IGlmIHlvdSBhbHNvIG1pcnJv" + + "cmVkIHRoZSBoZWxtIGNoYXJ0cwogICAgICAgICByZWdpc3RyeTogPHlvdXItcmVnaXN0cnk+CiAg" + + "ICAgICAgIHJlcG9zaXRvcnk6IDxtaXJyb3Itc3ViLXBhdGg+CiAgIGBgYAoKICAgPiBUaGUgbWFu" + + "aWZlc3QgaGFzIGEgbWl4IG9mIE9DSSBhbmQgbm9uLU9DSSBhcnRpZmFjdHMsIHNvIGNvbnZlcnQg" + + "bm9uLU9DSSBjaGFydHMgYmVmb3JlCiAgID4gbWlycm9yaW5nLiBDb250YWluZXIgaW1hZ2VzIChg" + + "bnZjci5pby8uLi5gKSBhcmUgYWxyZWFkeSBPQ0kgYW5kIG1pcnJvciBhcy1pcy4gU29tZSBIZWxt" + + "CiAgID4gY2hhcnRzLCB0aG91Z2gsIGFyZSBwdWJsaXNoZWQgdG8gYW4gYGh0dHBzOi8vYCBIZWxt" + + "IHJlcG9zaXRvcnkgKGZvciBleGFtcGxlCiAgID4gYGhlbG0ubmdjLm52aWRpYS5jb21gKS4gVGhv" + + "c2UgYXJlIG5vdCBPQ0kgYW5kIGNhbm5vdCBiZSByZXNvbHZlZCBieSBgZ2xvYmFsLmhlbG0uc291" + + "cmNlc2AKICAgPiAoT0NJKSBvciBtaXJyb3JlZCBkaXJlY3RseS4gQmVmb3JlIHBvaW50aW5nIHRo" + + "ZSBzdGFjayBhdCB5b3VyIG1pcnJvciwgY29udmVydCBlYWNoCiAgID4gYGh0dHBzOi8vYCBjaGFy" + + "dCB0byBPQ0k6IGBoZWxtIHJlcG8gYWRkYC9gaGVsbSBwdWxsYCB0aGUgYC50Z3pgLCB0aGVuCiAg" + + "ID4gYGhlbG0gcHVzaCA8Y2hhcnQ+LnRneiBvY2k6Ly88eW91ci1yZWdpc3RyeT4vPG1pcnJvci1z" + + "dWItcGF0aD5gLiBPbmx5IE9DSSBjaGFydHMgY2FuIGxpdmUKICAgPiB1bmRlciBgZ2xvYmFsLmhl" + + "bG0uc291cmNlc2AuCgoyLiBTa2lwIHRoZSBjcmVkZW50aWFsIHN0ZXBzIGFib3ZlLiBXaGVuIHRo" + + "ZSBjbHVzdGVyIGF1dGhlbnRpY2F0ZXMgdG8gdGhlIHJlZ2lzdHJ5CiAgIGF1dG9tYXRpY2FsbHkg" + + "KHRoZSBub2RlIG9yIHdvcmtsb2FkIGlkZW50aXR5IGhhcyBwdWxsIGFjY2VzcyksIHRoZSBwdWxs" + + "IG5lZWRzIG5vCiAgIEt1YmVybmV0ZXMgc2VjcmV0LCBzbyB5b3UgZG8gbm90IG5lZWQ6IHRoZSBw" + + "ZXItbmFtZXNwYWNlIGBudmNyLWNyZWRzYCBmcm9tIHN0ZXAgMQogICAoc3RpbGwgY3JlYXRlIHRo" + + "ZSBuYW1lc3BhY2VzLCBqdXN0IG5vdCB0aGUgc2VjcmV0KSwgdGhlIGBnbG9iYWwuaW1hZ2VQdWxs" + + "U2VjcmV0c2AgZW50cnksCiAgIG9yIHRoZSBgZG9ja2VyIGxvZ2luYC9gaGVsbSByZWdpc3RyeSBs" + + "b2dpbiBudmNyLmlvYCBmcm9tIHN0ZXAgMi4KCiAgID4gT25lIHJlc2lkdWFsIGNyZWRlbnRpYWw6" + + "IG52Y2YtYXBpIHN0aWxsIHZhbGlkYXRlcyB0aGUgdXNlciBmdW5jdGlvbiBpbWFnZSBhdAogICA+" + + "IGBmdW5jdGlvbiBjcmVhdGVgIHZpYSB0aGUgYWNjb3VudCByZWdpc3RyeSBjcmVkZW50aWFsLCBz" + + "byB0aGF0IGNyZWRlbnRpYWwgbXVzdCBwb2ludCBhdAogICA+IHdoaWNoZXZlciByZWdpc3RyeSBo" + + "b2xkcyB0aGUgdXNlciBpbWFnZSAoc2V0IGl0IHRvIHlvdXIgbWlycm9yIGlmIHRoZSBpbWFnZSBp" + + "cyBtaXJyb3JlZAogICA+IHRoZXJlKS4KCiMjIyAzLiBTZXQgdGhlIHJvdXRlIGRvbWFpbgoKYGds" + + "b2JhbC5kb21haW5gIHNldHMgdGhlIGhvc3RuYW1lIHN1ZmZpeCBmb3Igcm91dGVzIHN1Y2ggYXMg" + + "YGFwaS48ZG9tYWluPmAuIEl0IGRvZXMgbm90IGNvbmZpZ3VyZSB0aGUgR2F0ZXdheSBvciBpdHMg" + + "bG9hZCBiYWxhbmNlci4gUXVlcnkgdGhlIGV4YWN0IGV4aXN0aW5nIEdhdGV3YXk6CgpgYGBiYXNo" + + "CkdBVEVXQVlfQUREUkVTUz0kKGt1YmVjdGwgZ2V0IGdhdGV3YXkgPGdhdGV3YXktbmFtZT4gLW4g" + + "PGdhdGV3YXktbmFtZXNwYWNlPiAtbyBqc29ucGF0aD0ney5zdGF0dXMuYWRkcmVzc2VzWzBdLnZh" + + "bHVlfScpCnRlc3QgLW4gIiRHQVRFV0FZX0FERFJFU1MiCmBgYAoKVGhlIGFkZHJlc3MgbWF5IGJl" + + "IGEgaG9zdG5hbWUgb3IgSVAuIENsb3VkIHByb3ZpZGVyIGFuZCBHYXRld2F5IGNvbmZpZ3VyYXRp" + + "b24gZGV0ZXJtaW5lIHRoZSBsb2FkIGJhbGFuY2VyIHR5cGUgYW5kIGFkZHJlc3MgZm9ybWF0LiBU" + + "aGUgc2VsZi1tYW5hZ2VkIHN0YWNrIGRvZXMgbm90IHNlbGVjdCBlaXRoZXIuCgpGb3IgdGVtcG9y" + + "YXJ5IHRlc3Rpbmcgd2l0aG91dCBETlMsIHVzZSBhIHN0YWJsZSByZXNlcnZlZCBzdWZmaXggc3Vj" + + "aCBhcyBgZ2xvYmFsLmRvbWFpbjogIm52Y2YudGVzdCJgLCBjb25uZWN0IHRvIGBHQVRFV0FZX0FE" + + "RFJFU1NgLCBhbmQgc2VuZCBgSG9zdDogYXBpLm52Y2YudGVzdGAuIEZvciBwcm9kdWN0aW9uLCB1" + + "c2UgeW91ciBiYXNlIEROUyBkb21haW4gYW5kIHBvaW50IGl0cyByb3V0ZSByZWNvcmRzIHRvIGBH" + + "QVRFV0FZX0FERFJFU1NgLiBJZiB0aGUgZW5kcG9pbnQgbGF0ZXIgY2hhbmdlcywga2VlcCBgZ2xv" + + "YmFsLmRvbWFpbmAgc3RhYmxlIGFuZCB1cGRhdGUgb25seSBETlMgb3IgdGhlIGNsaWVudCBkZXN0" + + "aW5hdGlvbi4gU2VlIEV4YW1wbGUgNCBpbiBbZXhhbXBsZXMubWRdKGV4YW1wbGVzLm1kKS4KCiMj" + + "IyA0LiBQcmV2aWV3IGFuZCBkZXBsb3kKCmBgYGJhc2gKSEVMTUZJTEVfRU5WPTxlbnYtbmFtZT4g" + + "aGVsbWZpbGUgdGVtcGxhdGUgICAjIFByZXZpZXcgcmVuZGVyZWQgbWFuaWZlc3RzCkhFTE1GSUxF" + + "X0VOVj08ZW52LW5hbWU+IGhlbG1maWxlIHN5bmMgICAgICAgIyBEZXBsb3kKYGBgCgo+IFNldCBC" + + "T1RIIENhc3NhbmRyYSBwYXNzd29yZHMgaW4gdGhlIHNlY3JldHMgZmlsZS4gVGhlIHNlY3JldHMg" + + "ZmlsZSBkb2N1bWVudHMKPiBgREVGQVVMVF9DQVNTQU5EUkFfUEFTU1dPUkRgLCBidXQgdGhlIGNh" + + "c3NhbmRyYSBjaGFydCBoYXMgYSBzZWNvbmQgY3JlZGVudGlhbCwKPiBgY2Fzc2FuZHJhLnNlcnZp" + + "Y2VSb2xlUGFzc3dvcmRgLCB0aGF0IGRlZmF1bHRzIHRvIGBjaEBuZzNtM2AuIElmIHlvdSBzZXQg" + + "b25seQo+IGBERUZBVUxUX0NBU1NBTkRSQV9QQVNTV09SRGAsIHRoZSBzZXJ2aWNlIHJvbGUga2Vl" + + "cHMgdGhhdCBkZWZhdWx0IGFuZCBldmVyeSBEQi1iYWNrZWQKPiBzZXJ2aWNlIGNyYXNoLWxvb3Bz" + + "IHdpdGggYEJhZCBjcmVkZW50aWFsc2Agb25jZSBpdCB0cmllcyB0byBhdXRoZW50aWNhdGUuIFNl" + + "dAo+IGBjYXNzYW5kcmEuc2VydmljZVJvbGVQYXNzd29yZGAgdG8gdGhlIHNhbWUgdmFsdWUgYXMg" + + "YERFRkFVTFRfQ0FTU0FORFJBX1BBU1NXT1JEYC4KCiMjIyA1LiBEZXBsb3ltZW50IHBoYXNlcwoK" + + "SGVsbWZpbGUgZGVwbG95cyBpbiBvcmRlciB3aXRoIGRlcGVuZGVuY2llczoKCnwgUGhhc2UgfCBT" + + "ZWxlY3RvciB8IFNlcnZpY2VzIHwgV2FpdCB0aW1lIHwKfC0tLS0tLS18LS0tLS0tLS0tLXwtLS0t" + + "LS0tLS0tfC0tLS0tLS0tLS0tfAp8IDEgfCBgcmVsZWFzZS1ncm91cD1kZXBlbmRlbmNpZXNgIHwg" + + "TkFUUywgT3BlbkJhbywgQ2Fzc2FuZHJhIHwgNS0xMCBtaW4gfAp8IDIgfCBgcmVsZWFzZS1ncm91" + + "cD1zZXJ2aWNlc2AgfCBBUEksIFNJUywgRVNTLCBpbnZvY2F0aW9uLCBncnBjLXByb3h5LCBub3Rh" + + "cnksIGFwaS1rZXlzLCBvcHRpb25hbCBMTE0gZ2F0ZXdheS9yb3V0ZXIsIG9wdGlvbmFsIFZhbml0" + + "eSBHYXRld2F5LCBOVkNGIFVJIHdoZW4gdGhlIHN0YWNrIHBhY2thZ2UgaW5jbHVkZXMgdGhlIGFk" + + "ZG9uIHwgNS0xMCBtaW4gfAp8IDMgfCBgcmVsZWFzZS1ncm91cD1pbmdyZXNzYCB8IEdhdGV3YXkg" + + "cm91dGVzIHwgMS0yIG1pbiB8CnwgNCB8IGByZWxlYXNlLWdyb3VwPW9ic2VydmFiaWxpdHlgIHwg" + + "T2JzZXJ2YWJpbGl0eSBzdGFjayAoaWYgZW5hYmxlZCkgfCAxLTIgbWluIHwKCk1vbml0b3IgaW4g" + + "YSBzZXBhcmF0ZSB0ZXJtaW5hbDoKCmBgYGJhc2gKa3ViZWN0bCBnZXQgcG9kcyAtQSAtdwpgYGAK" + + "CiMjIyA2LiBFbmFibGUgVmFuaXR5IEdhdGV3YXkgKG9wdGlvbmFsKQoKVmFuaXR5IEdhdGV3YXkg" + + "aXMgZGlzYWJsZWQgYnkgZGVmYXVsdCBhbmQgaXMgb25seSBuZWVkZWQgZm9yIGN1c3RvbWVyLWZh" + + "Y2luZwpob3N0bmFtZXMgb3IgcGF0aCBtYXBwaW5ncyBpbiBmcm9udCBvZiB0aGUgc3RhbmRhcmQg" + + "TlZDRiBBUEkgYW5kIGludm9jYXRpb24Kcm91dGVzLiBJdCBpcyBhdmFpbGFibGUgb25seSBpbiBz" + + "dGFjayBwYWNrYWdlcyB0aGF0IGluY2x1ZGUgdGhlIFZhbml0eSBHYXRld2F5CmFkZG9uLiBJZiB0" + + "aGUgZXh0cmFjdGVkIHN0YWNrIHBhY2thZ2UgZG9lcyBub3QgY29udGFpbiBhIGB2YW5pdHktZ2F0" + + "ZXdheWAKcmVsZWFzZSBhbmQgYHZhbml0eUdhdGV3YXlgIHJvdXRlIHZhbHVlcywgc2tpcCB0aGVz" + + "ZSBjb21tYW5kcyBhbmQgdXNlIGEgbmV3ZXIKc3RhY2sgcGFja2FnZSB0aGF0IGluY2x1ZGVzIHRo" + + "ZW0uCgpJZiB0aGUgc3RhY2sgcGFja2FnZSBpbmNsdWRlcyB0aGUgYWRkb24sIGVuYWJsZSBpdCBp" + + "bgpgZW52aXJvbm1lbnRzLzxlbnYtbmFtZT4ueWFtbGA6CgpgYGB5YW1sCmFkZG9uczoKICB2YW5p" + + "dHlHYXRld2F5OgogICAgZW5hYmxlZDogdHJ1ZQogICAgbWFwcGluZ0NvbmZpZzoge30KYGBgCgpU" + + "aGUgZGVmYXVsdCByb3V0ZSBob3N0IGlzIGB2YW5pdHkuPGRvbWFpbj5gIGFuZCB0aGUgYmFja2Vu" + + "ZCBpcwpgdmFuaXR5LWdhdGV3YXkubnZjZjo4MDgwYC4gUHV0IGRlcGxveW1lbnQtc3BlY2lmaWMg" + + "aG9zdCBhbmQgcGF0aCBtYXBwaW5ncyB1bmRlcgpgYWRkb25zLnZhbml0eUdhdGV3YXkubWFwcGlu" + + "Z0NvbmZpZ2AuIElmIHlvdSBuZWVkIGN1c3RvbSB2YW5pdHkgaG9zdHMgaW5zdGVhZCBvZgpgdmFu" + + "aXR5Ljxkb21haW4+YCwgdXNlIHRoZSByb3V0ZSBob3N0bmFtZSBvdmVycmlkZXMgc3VwcG9ydGVk" + + "IGJ5IHRoZSBzdGFjawpwYWNrYWdlIGFuZCBjcmVhdGUgbWF0Y2hpbmcgRE5TIHJlY29yZHMuCgpB" + + "ZnRlciBjb25maXJtaW5nIHRoZSBwYWNrYWdlIGluY2x1ZGVzIHRoZSBgdmFuaXR5LWdhdGV3YXlg" + + "IHJlbGVhc2UsIHByZXZpZXcgYW5kCmFwcGx5IHRoZSBzZXJ2aWNlIGFuZCByb3V0ZToKCmBgYGJh" + + "c2gKSEVMTUZJTEVfRU5WPTxlbnYtbmFtZT4gaGVsbWZpbGUgLS1zZWxlY3RvciBuYW1lPXZhbml0" + + "eS1nYXRld2F5IHRlbXBsYXRlCkhFTE1GSUxFX0VOVj08ZW52LW5hbWU+IGhlbG1maWxlIC0tc2Vs" + + "ZWN0b3IgbmFtZT12YW5pdHktZ2F0ZXdheSBzeW5jCkhFTE1GSUxFX0VOVj08ZW52LW5hbWU+IGhl" + + "bG1maWxlIC0tc2VsZWN0b3IgcmVsZWFzZS1ncm91cD1pbmdyZXNzIHN5bmMKYGBgCgpWZXJpZnkg" + + "dGhlIHJvdXRlIG9ubHkgd2hlbiB0aGUgYWRkb24gaXMgcHJlc2VudCBhbmQgZW5hYmxlZDoKCmBg" + + "YGJhc2gKa3ViZWN0bCBnZXQgZGVwbG95LHN2YyAtbiBudmNmIC1sIGFwcC5rdWJlcm5ldGVzLmlv" + + "L25hbWU9dmFuaXR5LWdhdGV3YXkKa3ViZWN0bCBnZXQgaHR0cHJvdXRlIC1BIHwgZ3JlcCAtaSB2" + + "YW5pdHkKY3VybCAtSCAiSG9zdDogdmFuaXR5Ljxkb21haW4+IiAiaHR0cDovLzxnYXRld2F5LWFk" + + "ZHJlc3M+L2hlYWx0aCIKYGBgCgpEbyBub3QgZW5hYmxlIFZhbml0eSBHYXRld2F5IGZvciBzdGFu" + + "ZGFyZCBBUEksIEFQSSBLZXlzLCBpbnZvY2F0aW9uLCBMTE0KaW52b2NhdGlvbiwgb3IgZ1JQQyB0" + + "cmFmZmljLiBUaG9zZSByb3V0ZXMgYXJlIHByb3ZpZGVkIGJ5IHRoZSBiYXNlIGdhdGV3YXkKcm91" + + "dGVzLgoKIyMjIDcuIEVuYWJsZSBOVkNGIFVJIChvcHRpb25hbCkKCk5WQ0YgVUkgaXMgYW4gb3B0" + + "aW9uYWwsIGN1c3RvbWVyLWZhY2luZyBhZG1pbi1wYW5lbCBVSS4gSXQgaXMgZGlzYWJsZWQgYnkK" + + "ZGVmYXVsdCBhbmQgYXZhaWxhYmxlIG9ubHkgaW4gc3RhY2sgcGFja2FnZXMgdGhhdCBpbmNsdWRl" + + "IHRoZSBOVkNGIFVJIGFkZG9uLiBJZgp5b3VyIGV4dHJhY3RlZCBzdGFjayBwYWNrYWdlIGRvZXMg" + + "bm90IGNvbnRhaW4gYSBgbnZjZi11aWAgcmVsZWFzZSBhbmQgYG52Y2ZVaWAKcm91dGUgdmFsdWVz" + + "LCBza2lwIHRoaXMgc2VjdGlvbi4gVGhlIE5WQ0YgVUkgYWRtaW4gcGFuZWwgaXMgY3VycmVudGx5" + + "CnVuYXV0aGVudGljYXRlZDsgZG8gbm90IGV4cG9zZSBpdCB0byB0aGUgcHVibGljIGludGVybmV0" + + "LgoKRm9yIGVuYWJsZW1lbnQsIG5hbWVzcGFjZSBhbmQgcHVsbCBzZWNyZXQgc2V0dXAsIGFwcGx5" + + "LCBhbmQgdmVyaWZpY2F0aW9uIHN0ZXBzLApzZWUgW05WQ0YgVUkgQWRkb25dKHJlZmVyZW5jZXMv" + + "bnZjZi11aS1hZGRvbi5tZCkuCgojIyMgOC4gSW5zdGFsbCB0aGUgc2VwYXJhdGUgY29tcHV0ZS1w" + + "bGFuZSBzdGFjawoKVGhlIGNvbnRyb2wtcGxhbmUgc3RhY2sgZG9lcyBub3QgaW5zdGFsbCBjb21w" + + "dXRlLXBsYW5lIGNvbXBvbmVudHMuIEFmdGVyIGl0IGlzIGhlYWx0aHksIGNob29zZSBleGFjdGx5" + + "IG9uZSBjb21wdXRlLXBsYW5lIGhhbmRvZmY6CgotIEhlbG1maWxlIHZhbHVlcyBmbG93OiBhdXRo" + + "b3Igc2VwYXJhdGUgZW52aXJvbm1lbnQgZmlsZXMsIHRoZW4gcmVnaXN0ZXIgYW5kCiAgaW5zdGFs" + + "bCB3aXRoIHRoZSBjb21wdXRlLXBsYW5lIE1ha2VmaWxlLgotIENMSSBwcm9maWxlIGZsb3c6IHVz" + + "ZSB0aGUgcHJvZmlsZSBnZW5lcmF0ZWQgYnkgdGhlIENMSSBjb250cm9sLXBsYW5lIGluc3RhbGws" + + "CiAgdGhlbiBydW4gdGhlIGNvbXBsZXRlIENMSSByZWdpc3RlciBhbmQgaW5zdGFsbCBjb21tYW5k" + + "cy4KCkRvIG5vdCBtaXggdGhlc2UgaGFuZG9mZnMuIEZvbGxvdyBbU3BsaXQgQ29tcHV0ZS1QbGFu" + + "ZSBJbnN0YWxsYXRpb25dKHJlZmVyZW5jZXMvY29tcHV0ZS1wbGFuZS1pbnN0YWxsYXRpb24ubWQp" + + "CmZvciBjb21tYW5kcywgRUtTIHJvdXRpbmcsIHB1bGwgc2VjcmV0cywgdmVyaWZpY2F0aW9uLCBh" + + "bmQgdGVhcmRvd24gb3JkZXIuCgojIyBDbGVhbiBUZWFyZG93bgoKRGVzdHJveSBldmVyeSBjb21w" + + "dXRlIHBsYW5lIGJlZm9yZSB0aGUgY29udHJvbCBwbGFuZSwgdXNpbmcgdGhlIHRlYXJkb3duIGZv" + + "cgp0aGUgc2FtZSBoYW5kb2ZmOyBzZWUgW1NwbGl0IENvbXB1dGUtUGxhbmUgSW5zdGFsbGF0aW9u" + + "XShyZWZlcmVuY2VzL2NvbXB1dGUtcGxhbmUtaW5zdGFsbGF0aW9uLm1kI3RlYXJkb3duKS4KClNj" + + "b3BlOiBPbmx5IGRlc3Ryb3kgcmVsZWFzZXMgbWFuYWdlZCBieSB0aGlzIGNvbnRyb2wtcGxhbmUg" + + "aGVsbWZpbGUgc3RhY2suIFRoZSBOVkNGIHJlbGVhc2VzIGFyZTogYG5hdHNgLCBgb3BlbmJhby1z" + + "ZXJ2ZXJgLCBgY2Fzc2FuZHJhYCwgYGFwaS1rZXlzYCwgYHNpc2AsIGBhcGlgLCBgaW52b2NhdGlv" + + "bi1zZXJ2aWNlYCwgYGdycGMtcHJveHlgLCBgZXNzLWFwaWAsIGBub3Rhcnktc2VydmljZWAsIG9w" + + "dGlvbmFsIGB2YW5pdHktZ2F0ZXdheWAsIGBudmNmLXVpYCwgYGFkbWluLWlzc3Vlci1wcm94eWAs" + + "IGFuZCBgaW5ncmVzc2A7IGBjZXJ0LW1hbmFnZXJgIGlzIGluY2x1ZGVkIG9ubHkgd2hlbiBgY2Vy" + + "dE1hbmFnZXIuZW5hYmxlZDogdHJ1ZWAuIFRoZSBjb250cm9sLXBsYW5lIG5hbWVzcGFjZSBpbnZl" + + "bnRvcnkgYWxzbyBpbmNsdWRlcyBgY2VydC1tYW5hZ2VyYCwgYnV0IGRlZmF1bHQgbmFtZXNwYWNl" + + "IGNsZWFudXAgcHJlc2VydmVzIGl0LiBOYW1lc3BhY2UgcHJlc2VydmF0aW9uIGRvZXMgbm90IHBy" + + "ZXNlcnZlIHRoZSBIZWxtIHJlbGVhc2UuIEV4dGVybmFsIGNlcnQtbWFuYWdlciBtdXN0IHVzZSBg" + + "Y2VydE1hbmFnZXIuZW5hYmxlZDogZmFsc2VgOyBvdGhlcndpc2UgSGVsbWZpbGUgdHJlYXRzIHRo" + + "ZSByZWxlYXNlIGFzIHN0YWNrLW1hbmFnZWQuIERvIG5vdCBkZWxldGUgb3RoZXIgaGVsbSByZWxl" + + "YXNlcyBvciBuYW1lc3BhY2VzIG9uIHRoZSBjbHVzdGVyLgoKQmVmb3JlIGNvbnRyb2wtcGxhbmUg" + + "ZGVzdHJveSwgaWRlbnRpZnkgd2hldGhlciBjZXJ0LW1hbmFnZXIgaXMgZXh0ZXJuYWwuIEZvciBh" + + "biBleHRlcm5hbCBpbnN0YWxsYXRpb24sIHJlcXVpcmUgZWZmZWN0aXZlIGBjZXJ0TWFuYWdlci5l" + + "bmFibGVkOiBmYWxzZWAgYW5kIGFib3J0IGlmIGl0IGlzIHRydWUuIEZvciBhbiBpbnRlbmRlZCBz" + + "dGFjay1tYW5hZ2VkIHJlbGVhc2Ugd2l0aCBlZmZlY3RpdmUgYGNlcnRNYW5hZ2VyLmVuYWJsZWQ6" + + "IHRydWVgLCB2ZXJpZnkgbWF0Y2hpbmcgc3RhY2sgSGVsbSByZWxlYXNlIG1ldGFkYXRhIGFuZCBh" + + "Ym9ydCBvbiBtaXNzaW5nIG9yIG1pc21hdGNoZWQgZXZpZGVuY2UuIFByZXNlcnZlIHRoZSBuYW1l" + + "c3BhY2UgYnkgZGVmYXVsdDsgb25seSBwbGFuIGl0cyByZW1vdmFsIGFmdGVyIHZlcmlmeWluZyBu" + + "YW1lc3BhY2UgcHJvdmVuYW5jZSBhbmQgZ2V0dGluZyBleHBsaWNpdCBjb25maXJtYXRpb24uCgoj" + + "IyMgU3RhbmRhcmQgdGVhcmRvd24KClJ1biBmcm9tIGluc2lkZSB0aGUgYG52Y2Ytc2VsZi1tYW5h" + + "Z2VkLXN0YWNrL2AgZGlyZWN0b3J5OgoKYGBgYmFzaApIRUxNRklMRV9FTlY9PGVudi1uYW1lPiBo" + + "ZWxtZmlsZSBkZXN0cm95CmBgYAoKIyMjIERlbGV0ZSBuYW1lc3BhY2VzCgpgYGBiYXNoCmZvciBu" + + "cyBpbiBjYXNzYW5kcmEtc3lzdGVtIG5hdHMtc3lzdGVtIG52Y2YgYXBpLWtleXMgZXNzIHNpcyBu" + + "dmNmLXVpIHZhdWx0LXN5c3RlbTsgZG8KICBrdWJlY3RsIGRlbGV0ZSBuYW1lc3BhY2UgIiRucyIg" + + "LS1pZ25vcmUtbm90LWZvdW5kCmRvbmUKYGBgCgojIyMgVmVyaWZ5IGNsZWFuCgpgYGBiYXNoCmt1" + + "YmVjdGwgZ2V0IG5zIHwgZ3JlcCAtRSAnKGNhc3NhbmRyYXxuYXRzfHZhdWx0fG52Y2Z8YXBpLWtl" + + "eXN8ZXNzfHNpcyknCiMgU2hvdWxkIHJldHVybiBlbXB0eQpgYGAKCiMjIE92ZXJyaWRpbmcgSGVs" + + "bSBDaGFydCBWYWx1ZXMKCiMjIyBFbnZpcm9ubWVudCBmaWxlIChsaW1pdGVkKQoKV29ya3Mgb25s" + + "eSBmb3Iga2V5cyB0aGF0IGBnbG9iYWwueWFtbC5nb3RtcGxgIHByb3BhZ2F0ZXMgKGUuZy4sIGBj" + + "YXNzYW5kcmEucmVwbGljYUNvdW50YCwgYGdsb2JhbC5zdG9yYWdlQ2xhc3NgLCBgZ2xvYmFsLmlt" + + "YWdlYCkuCgojIyMgUmVsZWFzZSB2YWx1ZXMgYmxvY2sgKGFueSBjaGFydCB2YWx1ZSkKCkVkaXQg" + + "dGhlIHJlbGVhc2UgaW4gYGhlbG1maWxlLmQvKi55YW1sLmdvdG1wbGAgYW5kIGFkZCBhIGB2YWx1" + + "ZXM6YCBibG9jay4gSW4gdGhlIGV4YW1wbGVzIGJlbG93LCBgPHByaXZhdGUtdmFsdWVzPmAgcmVm" + + "ZXJzIHRvIHRoZSBgc2VjcmV0cy9gIGRpcmVjdG9yeSBhdCB0aGUgaGVsbWZpbGUgc3RhY2sgcm9v" + + "dC4KCmBgYHlhbWwKLSBuYW1lOiBjYXNzYW5kcmEKICB2ZXJzaW9uOiAwLjguMAogIGNvbmRpdGlv" + + "bjogY2Fzc2FuZHJhLmVuYWJsZWQKICBuYW1lc3BhY2U6IGNhc3NhbmRyYS1zeXN0ZW0KICA8PDog" + + "KmRlcGVuZGVuY3kKICB2YWx1ZXM6CiAgICAtIC4uL2dsb2JhbC55YW1sLmdvdG1wbAogICAgLSAu" + + "Li88cHJpdmF0ZS12YWx1ZXM+L3t7IHJlcXVpcmVkRW52ICJIRUxNRklMRV9FTlYiIH19LXNlY3Jl" + + "dHMueWFtbAogICAgLSBjYXNzYW5kcmE6CiAgICAgICAgcmVzb3VyY2VzOgogICAgICAgICAgbGlt" + + "aXRzOgogICAgICAgICAgICBjcHU6ICI4IgogICAgICAgICAgICBtZW1vcnk6IDgxOTJNaQogICAg" + + "ICAgICAgcmVxdWVzdHM6CiAgICAgICAgICAgIGNwdTogIjIiCiAgICAgICAgICAgIG1lbW9yeTog" + + "NDA5Nk1pCmBgYAoKWUFNTCBtZXJnZSBnb3RjaGE6IFdoZW4gYSByZWxlYXNlIHVzZXMgYDw8OiAq" + + "ZGVwZW5kZW5jeWAgb3IgYGluaGVyaXRgLCBzcGVjaWZ5aW5nIGB2YWx1ZXM6YCByZXBsYWNlcyB0" + + "aGUgdGVtcGxhdGUncyB2YWx1ZXMgbGlzdC4gWW91IG11c3QgcmUtaW5jbHVkZSBgZ2xvYmFsLnlh" + + "bWwuZ290bXBsYCBhbmQgdGhlIHNlY3JldHMgZmlsZS4KCiMjIyBQcmV2aWV3IGFuZCBhcHBseSBh" + + "IHNpbmdsZSByZWxlYXNlCgpgYGBiYXNoCkhFTE1GSUxFX0VOVj08ZW52PiBoZWxtZmlsZSAtLXNl" + + "bGVjdG9yIG5hbWU9Y2Fzc2FuZHJhIHRlbXBsYXRlICAjIFByZXZpZXcKSEVMTUZJTEVfRU5WPTxl" + + "bnY+IGhlbG1maWxlIC0tc2VsZWN0b3IgbmFtZT1jYXNzYW5kcmEgc3luYyAgICAgICMgQXBwbHkK" + + "YGBgCgojIyBJbWFnZSBQdWxsIFNlY3JldHMKClRoZXJlIGFyZSB0aHJlZSBkaXN0aW5jdCBjcmVk" + + "ZW50aWFsIHR5cGVzLiBEbyBub3QgY29uZmxhdGUgdGhlbSwgYW5kIG1pbmQgdGhlCmZvcm1hdCBy" + + "dWxlIGJlbG93ICh0d28gb2YgdGhlbSBmYWlsIGV2ZW4gd2l0aCB0aGUgY29ycmVjdCBrZXkgaWYg" + + "dGhlIGZvcm1hdCBpcyB3cm9uZyk6Cgp8IHwgQ29udHJvbCBQbGFuZSBQdWxsIFNlY3JldHMgfCBB" + + "UEkgQm9vdHN0cmFwIFJlZ2lzdHJ5IENyZWRzIHwgU2lkZWNhciBJbWFnZS1QdWxsIFNlY3JldCB8" + + "CnwtLS18LS0tfC0tLXwtLS18CnwgUHVycG9zZSB8IEs4cyBwdWxscyBOVkNGIHNlcnZpY2UgaW1h" + + "Z2VzIHwgbnZjZi1hcGkgdmFsaWRhdGVzIHRoZSB1c2VyIGZ1bmN0aW9uIGltYWdlIGF0IGBmdW5j" + + "dGlvbiBjcmVhdGVgIHwgbnZjZi1hcGkgcHVsbHMgdGhlIHBsYXRmb3JtIHNpZGVjYXJzIGluamVj" + + "dGVkIGludG8gZXZlcnkgd29ya2VyIHBvZCB8CnwgV2hlcmUgfCBLOHMgYGRvY2tlci1yZWdpc3Ry" + + "eWAgU2VjcmV0cyArIEt5dmVybm8gQ2x1c3RlclBvbGljeSB8IGA8cHJpdmF0ZS12YWx1ZXM+Lzxl" + + "bnY+LXNlY3JldHMueWFtbGAgKGFjY291bnQtYm9vdHN0cmFwKSB8IGBOVkNGX0FQSV9TSURFQ0FS" + + "U19JTUFHRV9QVUxMX1NFQ1JFVGAgaW4gYDxlbnY+LXNlY3JldHMueWFtbGAgLT4gT3BlbkJhbyBg" + + "c2VydmljZXMvbnZjZi1hcGkva3Yvc2lkZWNhcnMvaW1hZ2UtcHVsbC1zZWNyZXRgIChmaWVsZCBg" + + "c2VjcmV0YCkgfAp8IEZvcm1hdCB8IHN0YW5kYXJkIGAuZG9ja2VyY29uZmlnanNvbmAgKGt1YmVj" + + "dGwgYnVpbGRzIGl0IGZvciB5b3UpIHwgYGJhc2U2NCgiJG9hdXRodG9rZW46PGtleT4iKWAgfCBg" + + "YmFzZTY0KCIkb2F1dGh0b2tlbjo8a2V5PiIpYCB8Cgo+IE5vdGU6IHRoZSBhY2NvdW50LWJvb3Rz" + + "dHJhcCBjcmVkIGFuZCB0aGUgc2lkZWNhciBzZWNyZXQgYXJlIE5PVCBhCj4gZG9ja2VyY29uZmln" + + "anNvbi4gVGhlIHNlY3JldHMgdGVtcGxhdGUgc2hpcHMgdGhlIHBsYWNlaG9sZGVyCj4gYFJFUExB" + + "Q0VfV0lUSF9CQVNFNjRfRE9DS0VSX0NSRURFTlRJQUxgLCB3aGljaCByZWFkcyBhcyAiYmFzZTY0" + + "IG9mIGEgZG9ja2VyY29uZmlnanNvbi4iCj4gSXQgaXNuJ3QuIEJvdGggdmFsdWVzIGFyZSBjb25z" + + "dW1lZCBhcyBhIHJhdyBkb2NrZXIgYGF1dGhgIHN0cmluZzogbnZjZi1hcGkgdGFrZXMgdGhlCj4g" + + "dmFsdWUgdmVyYmF0aW0gYW5kIGRyb3BzIGl0IGludG8gdGhlIGBhdXRoYCBmaWVsZCBvZiB0aGUg" + + "ZG9ja2VyY29uZmlnIGl0IGJ1aWxkcy4gU28gdGhlCj4gdmFsdWUgbXVzdCBiZSBgYmFzZTY0KCIk" + + "b2F1dGh0b2tlbjo8a2V5PiIpYCwgZS5nLiBgcHJpbnRmICckb2F1dGh0b2tlbjolcycgIiRLRVki" + + "IHwgb3BlbnNzbCBiYXNlNjQgLUFgLAo+IG5vdCBgYmFzZTY0KDxkb2NrZXJjb25maWdqc29uPilg" + + "Lgo+Cj4gLSBXcm9uZyBhY2NvdW50LWJvb3RzdHJhcCB2YWx1ZTogYGZ1bmN0aW9uIGNyZWF0ZWAg" + + "cmV0dXJucwo+ICAgIGA0MDAgIm11c3QgYmUgYmFzZTY0IGVuY29kZWQgdXNlcm5hbWU6cGFzc3dv" + + "cmQgZm9ybWF0ImAuCj4gLSBXcm9uZyBzaWRlY2FyIHZhbHVlOiBldmVyeSB3b3JrZXIncyBzaWRl" + + "Y2FyIHB1bGwgZ2V0cyBhIG1hbGZvcm1lZCBjcmVkZW50aWFsIGFuZAo+ICAgIGA0MDNgcywgcmVn" + + "YXJkbGVzcyBvZiB3aGljaCBrZXkgaXMgaW5zaWRlLiBUaGUgc3ltcHRvbSBsb29rcyBsaWtlIGEg" + + "YmFkIG9yIHdyb25nLW9yZwo+ICAgIGtleSwgYnV0IHRoZSBrZXkgaXMgZmluZSBhbmQgdGhlIGVu" + + "Y29kaW5nIGlzIHRoZSBidWcuIFRvIHJlcGFpciBhbiBhbHJlYWR5LWluc3RhbGxlZAo+ICAgIHN0" + + "YWNrIHdpdGhvdXQgYSBmdWxsIHJlLXN5bmM6Cj4KPiAgICBgYGBiYXNoCj4gICAgQVVUSD0kKHBy" + + "aW50ZiAnJG9hdXRodG9rZW46JXMnICIkS0VZIiB8IG9wZW5zc2wgYmFzZTY0IC1BKQo+ICAgIGJh" + + "byBrdiBwdXQgc2VydmljZXMvbnZjZi1hcGkva3Yvc2lkZWNhcnMvaW1hZ2UtcHVsbC1zZWNyZXQg" + + "c2VjcmV0PSIkQVVUSCIKPiAgICAjIGlmIE5WSURJQSBDbG91ZCBUYXNrcyBpcyBlbmFibGVkLCBy" + + "ZXBlYXQgZm9yIHNlcnZpY2VzL252Y3QtYXBpL2t2L3NpZGVjYXJzL2ltYWdlLXB1bGwtc2VjcmV0" + + "Cj4gICAga3ViZWN0bCByb2xsb3V0IHJlc3RhcnQgLW4gbnZjZiBkZXBsb3kvbnZjZi1hcGkgICAj" + + "IHBpY2tzIHVwIHRoZSBuZXcgc2lkZWNhciBjcmVkCj4gICAgYGBgCj4KPiBOb3RlIChtdWx0aS1v" + + "cmcpOiB3aGVuIHB1bGxpbmcgZnJvbSBOR0MgKG5vdCBhIG1pcnJvciksIHRoZSB1c2VyIGZ1bmN0" + + "aW9uIGltYWdlIGFuZAo+IHRoZSBwbGF0Zm9ybSBzaWRlY2FycyBvZnRlbiBsaXZlIGluIGRpZmZl" + + "cmVudCBudmNyLmlvIG9yZ3MsIGVhY2ggbmVlZGluZyBhIGRpZmZlcmVudAo+IGtleSwgc28gdGhl" + + "IGFjY291bnQtYm9vdHN0cmFwIGNyZWQgKGZ1bmN0aW9uIGltYWdlKSBhbmQgdGhlIHNpZGVjYXIg" + + "c2VjcmV0IChzaWRlY2FycykKPiBhcmUgZnJlcXVlbnRseSB0d28gZGlmZmVyZW50IGtleXMuIE1p" + + "cnJvcmluZyBldmVyeXRoaW5nIGludG8gYSBzaW5nbGUgcmVnaXN0cnkgdGhlCj4gY2x1c3RlciBh" + + "dXRoZW50aWNhdGVzIHRvIGF1dG9tYXRpY2FsbHkgY29sbGFwc2VzIHRoaXMgdG8gb25lIGNyZWRl" + + "bnRpYWwgcGF0aC4KCiMjIyBDb25maWd1cmluZyB3aXRoIEt5dmVybm8gKHJlY29tbWVuZGVkKQoK" + + "VXNlIGEgS3l2ZXJubyBtdXRhdGluZyBhZG1pc3Npb24gcG9saWN5IHRvIGF1dG9tYXRpY2FsbHkg" + + "aW5qZWN0IGBpbWFnZVB1bGxTZWNyZXRzYCBpbnRvIGFsbCBwb2RzIGluIE5WQ0YgbmFtZXNwYWNl" + + "cy4gVGhpcyB3b3JrcyB1bmlmb3JtbHkgZm9yIGFsbCBjaGFydHMgLS0gbm8gcGVyLWNoYXJ0IGNv" + + "bmZpZ3VyYXRpb24gb3IgaGVsbWZpbGUgbW9kaWZpY2F0aW9ucyBuZWVkZWQuCgpgYGBiYXNoCiMg" + + "MS4gSW5zdGFsbCBLeXZlcm5vCmhlbG0gcmVwbyBhZGQga3l2ZXJubyBodHRwczovL2t5dmVybm8u" + + "Z2l0aHViLmlvL2t5dmVybm8vCmhlbG0gcmVwbyB1cGRhdGUKaGVsbSBpbnN0YWxsIGt5dmVybm8g" + + "a3l2ZXJuby9reXZlcm5vIC1uIGt5dmVybm8gLS1jcmVhdGUtbmFtZXNwYWNlCgojIDIuIENyZWF0" + + "ZSBwdWxsIHNlY3JldCBpbiBlYWNoIG5hbWVzcGFjZQpleHBvcnQgTkdDX0FQSV9LRVk9Ijx5b3Vy" + + "LWtleT4iCmZvciBucyBpbiBjYXNzYW5kcmEtc3lzdGVtIG5hdHMtc3lzdGVtIG52Y2YgYXBpLWtl" + + "eXMgZXNzIHNpcyB2YXVsdC1zeXN0ZW0gY2VydC1tYW5hZ2VyOyBkbwogIGt1YmVjdGwgY3JlYXRl" + + "IHNlY3JldCBkb2NrZXItcmVnaXN0cnkgbnZjci1jcmVkcyBcCiAgICAtLWRvY2tlci1zZXJ2ZXI9" + + "bnZjci5pbyBcCiAgICAtLWRvY2tlci11c2VybmFtZT0nJG9hdXRodG9rZW4nIFwKICAgIC0tZG9j" + + "a2VyLXBhc3N3b3JkPSIkTkdDX0FQSV9LRVkiIFwKICAgIC0tbmFtZXNwYWNlPSIkbnMiIFwKICAg" + + "IC0tZHJ5LXJ1bj1jbGllbnQgLW8geWFtbCB8IGt1YmVjdGwgYXBwbHkgLWYgLQpkb25lCgojIDMu" + + "IEFwcGx5IHRoZSBLeXZlcm5vIENsdXN0ZXJQb2xpY3kKa3ViZWN0bCBhcHBseSAtZiBreXZlcm5v" + + "LWltYWdlcHVsbHNlY3JldC1wb2xpY3kueWFtbApgYGAKClRoZSBwb2xpY3kgbXV0YXRlcyBldmVy" + + "eSBwb2QgYXQgYWRtaXNzaW9uIHRpbWUsIGFkZGluZyBgaW1hZ2VQdWxsU2VjcmV0czogW3tuYW1l" + + "OiBudmNyLWNyZWRzfV1gLiBWZXJpZnkgd2l0aDoKCmBgYGJhc2gKa3ViZWN0bCBnZXQgcG9kIC1u" + + "IDxuYW1lc3BhY2U+IDxwb2QtbmFtZT4gLW8ganNvbnBhdGg9J3suc3BlYy5pbWFnZVB1bGxTZWNy" + + "ZXRzfScKIyBTaG91bGQgc2hvdzogW3sibmFtZSI6Im52Y3ItY3JlZHMifV0KYGBgCgpOb3QgbmVl" + + "ZGVkIGlmIHVzaW5nIGEgQ1NQIGJ1aWx0LWluIGNyZWRlbnRpYWwgaGVscGVyIChlLmcuLCBFQ1Ig" + + "d2l0aCBJQU0gbm9kZSByb2xlcykuCgpGb3IgdGhlIEt5dmVybm8gcG9saWN5IFlBTUwgYW5kIHB1" + + "bGwgc2VjcmV0IGNyZWF0aW9uIHNjcmlwdCwgc2VlIFtyZWZlcmVuY2VzL3B1bGwtc2VjcmV0cy5t" + + "ZF0ocmVmZXJlbmNlcy9wdWxsLXNlY3JldHMubWQpLgoKIyMgRGVidWdnaW5nCgojIyMgUXVpY2sg" + + "c3RhdHVzIGNoZWNrCgpgYGBiYXNoCmt1YmVjdGwgZ2V0IHBvZHMgLUEgLW8gd2lkZSAgICAgICAg" + + "ICAgICAgICAgICAgICAgICMgQWxsIHBvZHMKaGVsbSBsaXN0IC1BICAgICAgICAgICAgICAgICAg" + + "ICAgICAgICAgICAgICAgICAgICAgICMgQWxsIGhlbG0gcmVsZWFzZXMKa3ViZWN0bCBnZXQgZXZl" + + "bnRzIC1uIDxucz4gLS1zb3J0LWJ5PScubGFzdFRpbWVzdGFtcCcgICMgUmVjZW50IGV2ZW50cwpg" + + "YGAKCiMjIyBDb21tb24gZmFpbHVyZSBwYXR0ZXJucwoKfCBTeW1wdG9tIHwgQ2F1c2UgfCBGaXgg" + + "fAp8LS0tLS0tLS0tfC0tLS0tLS18LS0tLS18CnwgYEltYWdlUHVsbEJhY2tPZmZgICsgYDQwMSBV" + + "bmF1dGhvcml6ZWRgIHwgTWlzc2luZyBvciB3cm9uZyBwdWxsIHNlY3JldCB8IENoZWNrIHNlY3Jl" + + "dCBleGlzdHMsIGNoZWNrIFNBIGhhcyBpbWFnZVB1bGxTZWNyZXRzIHwKfCBgSW5pdDowLzFgIHN0" + + "dWNrIG9uIHNlcnZpY2UgcG9kcyB8IFZhdWx0LWFnZW50IHdhaXRpbmcgZm9yIE9wZW5CYW8gfCBD" + + "aGVjayBPcGVuQmFvIHBvZHMgKyBtaWdyYXRpb24gam9iIHN0YXR1cyB8CnwgYEluaXQ6MC8xYCAr" + + "IGB2YXVsdC1hZ2VudC1pbml0YCBzaG93cyBgYXV0aC9qd3QvbG9naW5gIC0+IGA0MDAgLi4uIG5v" + + "IGtub3duIGtleSBzdWNjZXNzZnVsbHkgdmFsaWRhdGVkIHRoZSB0b2tlbiBzaWduYXR1cmVgIHwg" + + "T3BlbkJhbyBKV1QgYXV0aCBjb25maWd1cmVkIHdpdGggYSBzdGF0aWMgcHVia2V5IGluc3RlYWQg" + + "b2YgdGhlIGNsdXN0ZXIncyBsaXZlIEpXS1MgLS0gdGhlIGJhc2UgZGVmYXVsdCBgb3BlbmJhby5t" + + "aWdyYXRpb25zLmlzc3VlckRpc2NvdmVyeS5lbmFibGVkOiBmYWxzZWAuIENvbW1vbiBvbiBBS1Mg" + + "LyBhbnkgT0lEQy1pc3N1ZXIgY2x1c3RlciB8IFNldCBgb3BlbmJhby5taWdyYXRpb25zLmlzc3Vl" + + "ckRpc2NvdmVyeS5lbmFibGVkOiB0cnVlYCwgZGVsZXRlIGpvYiBgb3BlbmJhby1zZXJ2ZXItbWln" + + "cmF0aW9uc2AsIHJlLXN5bmMgYG9wZW5iYW8tc2VydmVyYC4gU2VlIFtyZWZlcmVuY2VzL2RlYnVn" + + "Z2luZy5tZF0ocmVmZXJlbmNlcy9kZWJ1Z2dpbmcubWQpICJJbml0OjAvMSBTdHVjayIgZml4ICM0" + + "LiB8CnwgYE9PTUtpbGxlZGAgb24gQ2Fzc2FuZHJhIHwgRGVmYXVsdCByZXNvdXJjZXMgdG9vIHNt" + + "YWxsIHwgT3ZlcnJpZGUgYGNhc3NhbmRyYS5yZXNvdXJjZXNgIHZpYSB2YWx1ZXMgYmxvY2sgfAp8" + + "IERCLWJhY2tlZCBzZXJ2aWNlcyBjcmFzaC1sb29wIGBCYWQgY3JlZGVudGlhbHNgIGNvbm5lY3Rp" + + "bmcgdG8gQ2Fzc2FuZHJhIHwgU2VjcmV0cyBmaWxlIHNldCBvbmx5IGBERUZBVUxUX0NBU1NBTkRS" + + "QV9QQVNTV09SRGA7IGBjYXNzYW5kcmEuc2VydmljZVJvbGVQYXNzd29yZGAgc3RpbGwgZGVmYXVs" + + "dHMgdG8gYGNoQG5nM20zYCB8IFNldCBgY2Fzc2FuZHJhLnNlcnZpY2VSb2xlUGFzc3dvcmRgIGlu" + + "IHRoZSBzZWNyZXRzIGZpbGUgZXF1YWwgdG8gYERFRkFVTFRfQ0FTU0FORFJBX1BBU1NXT1JEYCwg" + + "dGhlbiByZS1zeW5jIGNhc3NhbmRyYSArIHRoZSBEQi1iYWNrZWQgc2VydmljZXMgfAp8IGBQZW5k" + + "aW5nYCBwb2RzIHwgTm9kZSBzZWxlY3RvciBtaXNtYXRjaCBvciBubyBzdG9yYWdlIGNsYXNzIHwg" + + "YGt1YmVjdGwgZGVzY3JpYmUgcG9kYCwgY2hlY2sgbGFiZWxzIGFuZCBzdG9yYWdlIHwKfCBIZWxt" + + "IHJlbGVhc2UgaW4gYGZhaWxlZGAgc3RhdGUgfCBGaXJzdCBpbnN0YWxsIGZhaWxlZCBwYXJ0d2F5" + + "IHwgYGhlbG1maWxlIGRlc3Ryb3lgIHRoZSByZWxlYXNlLCB0aGVuIGBzeW5jYCBhZ2FpbiB8Cnwg" + + "QWNjb3VudCBib290c3RyYXAgdGltZW91dCB8IFdyb25nIGJhc2U2NCBjcmVkZW50aWFscyBpbiBz" + + "ZWNyZXRzIGZpbGUgfCBDaGVjayBga3ViZWN0bCBsb2dzIGpvYi9udmNmLWFwaS1hY2NvdW50LWJv" + + "b3RzdHJhcCAtbiBudmNmYCB8CnwgZnVuY3Rpb24gLyBhY2NvdW50IGNhbGxzIHJldHVybiBgNDA0" + + "IFVua25vd24gY2xpZW50X2lkYCB8IFRoZSBhY2NvdW50LWJvb3RzdHJhcCBwb3N0LWluc3RhbGwg" + + "aG9vayBuZXZlciByYW46IHRoZSBgYXBpYCByZWxlYXNlIGluc3RhbGwgZmFpbGVkLCBvciB3YXMg" + + "cmVwYWlyZWQgd2l0aCBsaXZlIHBhdGNoZXMgaW5zdGVhZCBvZiBhIGNsZWFuIHJlLXN5bmMsIHNv" + + "IHRoZSBob29rIChhbmQgdGhlIGFjY291bnQpIHdhcyBza2lwcGVkIHwgVmVyaWZ5IGFuZCByZS1y" + + "dW4gdGhlIGhvb2s7IHNlZSBbVmVyaWZ5IHRoZSBhY2NvdW50IGJvb3RzdHJhcCByYW5dKCN2ZXJp" + + "ZnktdGhlLWFjY291bnQtYm9vdHN0cmFwLXJhbikgfAp8IFNlcnZpY2VzIGZhaWwgdG8gcmVhZCB2" + + "YXVsdCBzZWNyZXRzOyBgc2VjcmV0cy5qc29uYCBub3QgZm91bmQgfCBWYXVsdCBwYXRoIGhhcmRj" + + "b2RlZCB0byBgL2hvbWUvYXBwL3ZhdWx0L2AgaW4gYF9oZWxwZXJzLnRwbGA7IHRoZSBydW50aW1l" + + "IHJlc29sdmVzIHRoZSBtb3VudGVkIHBhdGggcmVsYXRpdmUgdG8gdGhlIHdvcmtpbmcgZGlyZWN0" + + "b3J5IGFuZCBkcm9wcyB0aGUgbGVhZGluZyBgL2AgfCBPdmVycmlkZSBgcG9kQW5ub3RhdGlvbnNg" + + "IGFuZCBzZXQgYEpBVkFfVE9PTF9PUFRJT05TOiAiLUR1c2VyLmRpcj0vImAgaW4gcmVsZWFzZSB2" + + "YWx1ZXMgfAp8IE5BVFMgY29ubmVjdGlvbiBmYWlscyBhdCBzdGFydHVwOyBwbGFjZW1lbnQgdGFn" + + "IG1pc21hdGNoIHwgTkFUUyBzZXJ2ZXIgdGFncyBoYXJkY29kZWQgdG8gYGRjOm5jcGA7IGFwcCBk" + + "ZXJpdmVzIHRhZyBmcm9tIGBBV1NfUkVHSU9OYCAoZS5nLiwgYHVzLWdvdi13ZXN0LTFgKSB8IFNl" + + "dCBgQVdTX1JFR0lPTj1uY3BgIGFuZCBgTlZDRl9BV1NfUkVHSU9OPW5jcGAgaW4gZW52IGNvbmZp" + + "ZyB8CgojIyMgVmVyaWZ5IHRoZSBhY2NvdW50IGJvb3RzdHJhcCByYW4KClRoZSBkZWZhdWx0IGFj" + + "Y291bnQgaXMgY3JlYXRlZCBieSBhICoqYHBvc3QtaW5zdGFsbGAgaG9vayBvbiB0aGUgYGFwaWAg" + + "cmVsZWFzZSoqCihgam9iL252Y2YtYXBpLWFjY291bnQtYm9vdHN0cmFwYCBpbiBgbnZjZmApLiBC" + + "ZWNhdXNlIGl0IGlzIGEgcG9zdC1pbnN0YWxsIGhvb2ssIGl0IG9ubHkKZmlyZXMgb24gYSBzdWNj" + + "ZXNzZnVsIGBhcGlgIGluc3RhbGwuIElmIHRoYXQgaW5zdGFsbCBmYWlsZWQgYW5kIHlvdSByZXBh" + + "aXJlZCBpdCB3aXRoIGxpdmUKcGF0Y2hlcyAocmF0aGVyIHRoYW4gYSBjbGVhbiByZS1zeW5jKSwg" + + "dGhlIGhvb2sgbmV2ZXIgcnVucywgbm8gYWNjb3VudCBpcyBjcmVhdGVkLCBhbmQKZXZlcnkgc3Vi" + + "c2VxdWVudCBmdW5jdGlvbi9hY2NvdW50IGNhbGwgcmV0dXJucyBhIGNyeXB0aWMgYDQwNCBVbmtu" + + "b3duIGNsaWVudF9pZGAgd2l0aApub3RoaW5nIG9idmlvdXNseSB3cm9uZyBpbiB0aGUgcnVubmlu" + + "ZyBwb2RzLgoKVmVyaWZ5IGl0IHJhbiBhZnRlciB0aGUgZmlyc3Qgc3luYzoKCmBgYGJhc2gKa3Vi" + + "ZWN0bCBnZXQgam9iIC1uIG52Y2YgbnZjZi1hcGktYWNjb3VudC1ib290c3RyYXAgXAogIC1vIGpz" + + "b25wYXRoPSd7LnN0YXR1cy5zdWNjZWVkZWR9JyAgICMgZXhwZWN0IDEKYGBgCgpJZiB0aGUgam9i" + + "IGlzIGFic2VudCBvciBgMGAsIHJlLXJ1biBpdCBieSByZS1zeW5jaW5nIHRoZSBgYXBpYCByZWxl" + + "YXNlIHNvIHRoZSBob29rIGZpcmVzCmFnYWluIChkZWxldGUgdGhlIHByaW9yIGpvYiBmaXJzdCBp" + + "ZiBpdCBsaW5nZXJzIGluIGEgY29tcGxldGVkL2ZhaWxlZCBzdGF0ZSBhbmQgYmxvY2tzIHRoZQpo" + + "b29rKToKCmBgYGJhc2gKa3ViZWN0bCBkZWxldGUgam9iIC1uIG52Y2YgbnZjZi1hcGktYWNjb3Vu" + + "dC1ib290c3RyYXAgLS1pZ25vcmUtbm90LWZvdW5kCkhFTE1GSUxFX0VOVj08ZW52PiBoZWxtZmls" + + "ZSAtLXNlbGVjdG9yIG5hbWU9YXBpIHN5bmMKa3ViZWN0bCBsb2dzIC1uIG52Y2Ygam9iL252Y2Yt" + + "YXBpLWFjY291bnQtYm9vdHN0cmFwICAgIyBjb25maXJtIHRoZSBhY2NvdW50IHdhcyBjcmVhdGVk" + + "CmBgYAoKIyMgQWRkaXRpb25hbCBSZXNvdXJjZXMKCi0gSW5zdGFsbGF0aW9uOiBbZXhhbXBsZXMu" + + "bWRdKGV4YW1wbGVzLm1kKSwgdGhlIFtDU1AgRW5kLXRvLUVuZCBFeGFtcGxlXShodHRwczovL2Rv" + + "Y3MubnZpZGlhLmNvbS9udmNmL3YwLjYuMC1yYy9jc3AtZW5kLXRvLWVuZC1leGFtcGxlKSwgYW5k" + + "IFtzcGxpdCBjb21wdXRlLXBsYW5lIHdvcmtmbG93c10ocmVmZXJlbmNlcy9jb21wdXRlLXBsYW5l" + + "LWluc3RhbGxhdGlvbi5tZCkKLSBPcGVyYXRpb25zOiBbSGVsbWZpbGUgc3RydWN0dXJlXShyZWZl" + + "cmVuY2VzL2hlbG1maWxlLXN0cnVjdHVyZS5tZCksIFtwdWxsIHNlY3JldHNdKHJlZmVyZW5jZXMv" + + "cHVsbC1zZWNyZXRzLm1kKSwgYW5kIFtkZWJ1Z2dpbmddKHJlZmVyZW5jZXMvZGVidWdnaW5nLm1k" + + "KQoKQWZ0ZXIgZGVwbG95bWVudCwgdXNlIHRoZSBgbnZjZi1zZWxmLW1hbmFnZWQtY2xpYCBza2ls" + + "bCB0byBjcmVhdGUgZnVuY3Rpb25zLCBjcmVhdGUgdGFza3MsIG1hbmFnZSBBUEkga2V5cywgYW5k" + + "IGludm9rZSBlbmRwb2ludHMuClBLBwgRwxSTSWIAAEliAABQSwMEFAAIAAAAAAAhAAAAAAAAAAAA" + + "AAAAADQACQBkYXRhL252Y2Ytc2VsZi1tYW5hZ2VkLWluc3RhbGxhdGlvbi9ldmFscy9ldmFscy5q" + + "c29uVVQFAAEAps4SWwogIHsKICAgICJpZCI6ICJHMS12YWxpZGF0ZS10b29sLXZlcnNpb25zIiwK" + + "ICAgICJxdWVzdGlvbiI6ICJJbnN0YWxsIHRoZSBOVkNGIHNlbGYtbWFuYWdlZCBzdGFjayBvbiBt" + + "eSBBV1MgRUtTIGNsdXN0ZXIiLAogICAgImV4cGVjdGVkX3NraWxsIjogIm52Y2Ytc2VsZi1tYW5h" + + "Z2VkLWluc3RhbGxhdGlvbiIsCiAgICAiZ3JvdW5kX3RydXRoIjogIlRoZSBhZ2VudCdzIGZpcnN0" + + "IG9wZXJhdGlvbmFsIHN0ZXAgYWZ0ZXIgcmVhZGluZyB0aGUgc2tpbGwgd2FzIHRvIHZhbGlkYXRl" + + "IGFsbCByZXF1aXJlZCB0b29sIHZlcnNpb25zOiBoZWxtZmlsZSAobXVzdCBiZSAxLjEueCwgbm90" + + "IDEuMi4wKSwgaGVsbSAoPj0gMy4xMiksIGhlbG0tZGlmZiBwbHVnaW4gKD49IDMuMTEpLCBhbmQg" + + "a3ViZWN0bCAoY2xpZW50IHdpdGhpbiAxIG1pbm9yIG9mIGNsdXN0ZXIpLiBJdCBkaWQgbm90IHBy" + + "b2NlZWQgdG8gaGVsbWZpbGUgc3luYyB1bnRpbCBhbGwgdmVyc2lvbnMgd2VyZSB2YWxpZGF0ZWQu" + + "IiwKICAgICJleHBlY3RlZF9iZWhhdmlvciI6IFsKICAgICAgIlRoZSBhZ2VudCByZWFkIHRoZSBu" + + "dmNmLXNlbGYtbWFuYWdlZC1pbnN0YWxsYXRpb24gU0tJTEwubWQgYmVmb3JlIHRha2luZyBhbnkg" + + "YWN0aW9uIiwKICAgICAgIlRoZSBhZ2VudCByYW4gJ2hlbG1maWxlIC0tdmVyc2lvbicgYW5kIHZl" + + "cmlmaWVkIGl0IGlzIDEuMS54IChub3QgMS4yLjAsIHdoaWNoIHJlbW92ZWQgc2VxdWVudGlhbCBt" + + "b2RlKSIsCiAgICAgICJUaGUgYWdlbnQgcmFuICdoZWxtIHZlcnNpb24nIGFuZCB2ZXJpZmllZCBp" + + "dCBpcyA+PSAzLjEyIiwKICAgICAgIlRoZSBhZ2VudCByYW4gJ2hlbG0gcGx1Z2luIGxpc3QnIGFu" + + "ZCB2ZXJpZmllZCBoZWxtLWRpZmYgPj0gMy4xMSBpcyBpbnN0YWxsZWQiLAogICAgICAiVGhlIGFn" + + "ZW50IHJhbiAna3ViZWN0bCB2ZXJzaW9uJyB0byBjaGVjayBjbGllbnQvc2VydmVyIHZlcnNpb24g" + + "Y29tcGF0aWJpbGl0eSIsCiAgICAgICJUaGUgYWdlbnQgZGlkIG5vdCBwcm9jZWVkIHRvIGhlbG1m" + + "aWxlIHN5bmMgdW50aWwgYWxsIHZlcnNpb24gY2hlY2tzIHBhc3NlZCIKICAgIF0KICB9LAogIHsK" + + "ICAgICJpZCI6ICJHMi10ZW1wbGF0ZS1iZWZvcmUtc3luYyIsCiAgICAicXVlc3Rpb24iOiAiUXVp" + + "Y2ssIGp1c3QgcnVuIGhlbG1maWxlIHN5bmMgZm9yIG15IE5WQ0Ygc3RhY2ssIHNraXAgYWxsIHRo" + + "ZSBjaGVja3MiLAogICAgImV4cGVjdGVkX3NraWxsIjogIm52Y2Ytc2VsZi1tYW5hZ2VkLWluc3Rh" + + "bGxhdGlvbiIsCiAgICAiZ3JvdW5kX3RydXRoIjogIkRlc3BpdGUgdGhlIHVzZXIgYXNraW5nIHRv" + + "IHNraXAgY2hlY2tzLCB0aGUgYWdlbnQgc3RpbGwgcmFuIGhlbG1maWxlIHRlbXBsYXRlIHRvIHBy" + + "ZXZpZXcgcmVuZGVyZWQgbWFuaWZlc3RzIGJlZm9yZSBydW5uaW5nIGhlbG1maWxlIHN5bmMuIEl0" + + "IGV4cGxhaW5lZCB3aHkgdGhlIHByZXZpZXcgc3RlcCBpcyBpbXBvcnRhbnQgYW5kIGRpZCBub3Qg" + + "YmxpbmRseSBleGVjdXRlIHN5bmMuIiwKICAgICJleHBlY3RlZF9iZWhhdmlvciI6IFsKICAgICAg" + + "IlRoZSBhZ2VudCByZWFkIHRoZSBudmNmLXNlbGYtbWFuYWdlZC1pbnN0YWxsYXRpb24gU0tJTEwu" + + "bWQgYmVmb3JlIHRha2luZyBhY3Rpb24iLAogICAgICAiVGhlIGFnZW50IHJhbiAnaGVsbWZpbGUg" + + "dGVtcGxhdGUnIG9yICdoZWxtZmlsZSAtLXNlbGVjdG9yIC4uLiB0ZW1wbGF0ZScgdG8gcHJldmll" + + "dyBtYW5pZmVzdHMgYmVmb3JlIHN5bmMiLAogICAgICAiVGhlIGFnZW50IGRpZCBub3QgYmxpbmRs" + + "eSBydW4gJ2hlbG1maWxlIHN5bmMnIHdpdGhvdXQgYSB0ZW1wbGF0ZSBwcmV2aWV3IHN0ZXAiLAog" + + "ICAgICAiVGhlIGFnZW50IGV4cGxhaW5lZCB3aHkgdGhlIHByZXZpZXcgc3RlcCBpcyBpbXBvcnRh" + + "bnQgZXZlbiB3aGVuIGFza2VkIHRvIHNraXAgaXQiCiAgICBdCiAgfSwKICB7CiAgICAiaWQiOiAi" + + "RzUtZHJ5cnVuLWJlZm9yZS1jbGVhbnVwIiwKICAgICJxdWVzdGlvbiI6ICJOVkNGIG5hbWVzcGFj" + + "ZXMgYXJlIHN0dWNrIGluIFRlcm1pbmF0aW5nIHN0YXRlLiBDbGVhbiB0aGVtIHVwLiIsCiAgICAi" + + "ZXhwZWN0ZWRfc2tpbGwiOiAibnZjZi1zZWxmLW1hbmFnZWQtaW5zdGFsbGF0aW9uIiwKICAgICJn" + + "cm91bmRfdHJ1dGgiOiAiVGhlIGFnZW50IGlkZW50aWZpZWQgdGhlIHN0dWNrIG5hbWVzcGFjZXMg" + + "d2VyZSBjYXVzZWQgYnkgZmluYWxpemVycyBvbiBOVkNGQmFja2VuZCBjdXN0b20gcmVzb3VyY2Vz" + + "LiBJdCByZW1vdmVkIGZpbmFsaXplcnMgZnJvbSBOVkNGQmFja2VuZCByZXNvdXJjZXMgZmlyc3Qs" + + "IHRoZW4gY2xlYXJlZCBuYW1lc3BhY2UgZmluYWxpemVycyB0byBmb3JjZS1kZWxldGUgdGhlIHN0" + + "dWNrIG5hbWVzcGFjZXMuIEl0IG9ubHkgdGFyZ2V0ZWQgTlZDRi1tYW5hZ2VkIG5hbWVzcGFjZXMg" + + "KG52Y2Etb3BlcmF0b3IsIG52Y2YtYmFja2VuZCkgYW5kIGRpZCBub3QgdG91Y2ggb3RoZXIgY2x1" + + "c3RlciByZXNvdXJjZXMuIiwKICAgICJleHBlY3RlZF9iZWhhdmlvciI6IFsKICAgICAgIlRoZSBh" + + "Z2VudCByZWFkIHRoZSBudmNmLXNlbGYtbWFuYWdlZC1pbnN0YWxsYXRpb24gU0tJTEwubWQgYmVm" + + "b3JlIHRha2luZyBhY3Rpb24iLAogICAgICAiVGhlIGFnZW50IGNvbmZpcm1lZCBhbmQgdXNlZCB0" + + "aGUgY29tcHV0ZS1wbGFuZSBjb250ZXh0IGZvciBhbGwgY2xlYW51cCBjb21tYW5kcyIsCiAgICAg" + + "ICJUaGUgYWdlbnQgdXNlZCBrdWJlY3RsIHRvIHBhdGNoIE5WQ0ZCYWNrZW5kIHJlc291cmNlcyB0" + + "byByZW1vdmUgdGhlaXIgZmluYWxpemVycyIsCiAgICAgICJUaGUgYWdlbnQgdXNlZCBrdWJlY3Rs" + + "IHJlcGxhY2UgLS1yYXcgdG8gY2xlYXIgbmFtZXNwYWNlIGZpbmFsaXplcnMgb24gc3R1Y2sgbmFt" + + "ZXNwYWNlcyIsCiAgICAgICJUaGUgYWdlbnQgb25seSB0YXJnZXRlZCBOVkNGLW1hbmFnZWQgbmFt" + + "ZXNwYWNlcyAobnZjYS1vcGVyYXRvciwgbnZjZi1iYWNrZW5kKSBhbmQgZGlkIG5vdCB0b3VjaCBv" + + "dGhlciBjbHVzdGVyIHJlc291cmNlcyIsCiAgICAgICJUaGUgYWdlbnQgZGlkIG5vdCBqdW1wIGRp" + + "cmVjdGx5IHRvIG5hbWVzcGFjZSBkZWxldGlvbiB3aXRob3V0IGZpcnN0IHJlbW92aW5nIGZpbmFs" + + "aXplcnMiCiAgICBdCiAgfSwKICB7CiAgICAiaWQiOiAiRzYtaGVsbWZpbGUtc3BsaXQtd29ya2Zs" + + "b3ciLAogICAgInF1ZXN0aW9uIjogIkluc3RhbGwgYW4gTlZDRiBjb250cm9sIHBsYW5lIGFuZCBh" + + "IHNlcGFyYXRlIEVLUyBjb21wdXRlIHBsYW5lIHVzaW5nIHRoZSBIZWxtZmlsZSBzdGFja3MuIFRo" + + "ZSBjb21wdXRlIGNsdXN0ZXIga3ViZWNvbmZpZyBpcyBjb21wdXRlLnlhbWwuIiwKICAgICJleHBl" + + "Y3RlZF9za2lsbCI6ICJudmNmLXNlbGYtbWFuYWdlZC1pbnN0YWxsYXRpb24iLAogICAgImdyb3Vu" + + "ZF90cnV0aCI6ICJUaGUgYWdlbnQga2VwdCB0aGUgSGVsbWZpbGUgdmFsdWVzIGZsb3cgaW5kZXBl" + + "bmRlbnQgZnJvbSB0aGUgQ0xJIHByb2ZpbGUgZmxvdy4gSXQgYXV0aG9yZWQgc2VwYXJhdGUgY29u" + + "dHJvbC1wbGFuZSBhbmQgY29tcHV0ZS1wbGFuZSBlbnZpcm9ubWVudCBmaWxlcyBwbHVzIGEgYmFy" + + "ZS1HYXRld2F5IENMSSBjb25maWcgd2l0aCBVUkwgYW5kIHNjaGVtZS1sZXNzIEhvc3QgZmllbGRz" + + "LCBpbnN0YWxsZWQgdGhlIGNvbnRyb2wgcGxhbmUsIHJhbiB0aGUgY29tcHV0ZS1wbGFuZSBNYWtl" + + "ZmlsZSByZWdpc3Rlci1jbHVzdGVyIHRhcmdldCB3aXRoIElDTVNfVVJMIGFuZCB0aGUgY29tcHV0" + + "ZSBrdWJlY29uZmlnLCB0aGVuIHJhbiB0aGUgY29tcHV0ZS1wbGFuZSBpbnN0YWxsIHRhcmdldCB3" + + "aXRoIHRoZSBzYW1lIGVudmlyb25tZW50IGFuZCBrdWJlY29uZmlnLiBJdCByZWxpZWQgb24gcmVn" + + "aXN0cmF0aW9uIHRvIGRpc2NvdmVyIE9JREMgYW5kIEpXS1MgZnJvbSB0aGUgY29tcHV0ZSBjb250" + + "ZXh0LiIsCiAgICAiZXhwZWN0ZWRfYmVoYXZpb3IiOiBbCiAgICAgICJUaGUgYWdlbnQgcmVhZCB0" + + "aGUgbnZjZi1zZWxmLW1hbmFnZWQtaW5zdGFsbGF0aW9uIFNLSUxMLm1kIGJlZm9yZSB0YWtpbmcg" + + "YWN0aW9uIiwKICAgICAgIlRoZSBhZ2VudCBhdXRob3JlZCBzZXBhcmF0ZSBlbnZpcm9ubWVudHMv" + + "PGVudj4ueWFtbCBmaWxlcyB1bmRlciBkZXBsb3kvc3RhY2tzL3NlbGYtbWFuYWdlZCBhbmQgZGVw" + + "bG95L3N0YWNrcy9udmNmLWNvbXB1dGUtcGxhbmUiLAogICAgICAiVGhlIGFnZW50IGluc3RhbGxl" + + "ZCB0aGUgY29udHJvbCBwbGFuZSBiZWZvcmUgcmVnaXN0ZXJpbmcgb3IgaW5zdGFsbGluZyB0aGUg" + + "Y29tcHV0ZSBwbGFuZSIsCiAgICAgICJUaGUgYWdlbnQgY29uZmlndXJlZCBiYXNlX2h0dHBfdXJs" + + "LCBpbnZva2VfdXJsLCBiYXNlX2dycGNfdXJsLCBhcGlfa2V5c19zZXJ2aWNlX3VybCwgaWNtc191" + + "cmwsIGFwaV9ob3N0LCBhcGlfa2V5c19ob3N0LCBpbnZva2VfaG9zdCwgYW5kIGljbXNfaG9zdCBm" + + "b3IgdGhlIGJhcmUgR2F0ZXdheSIsCiAgICAgICJUaGUgYWdlbnQgdXNlZCAnbWFrZSAtQyBkZXBs" + + "b3kvc3RhY2tzL252Y2YtY29tcHV0ZS1wbGFuZSByZWdpc3Rlci1jbHVzdGVyJyB3aXRoIENMVVNU" + + "RVJfTkFNRSwgTkNBX0lELCBDTFVTVEVSX1JFR0lPTiwgSUNNU19VUkwsIGFuZCBLVUJFQ09ORklH" + + "X0ZJTEU9Y29tcHV0ZS55YW1sIiwKICAgICAgIlRoZSBhZ2VudCB1c2VkICdtYWtlIC1DIGRlcGxv" + + "eS9zdGFja3MvbnZjZi1jb21wdXRlLXBsYW5lIGluc3RhbGwnIHdpdGggQ0xVU1RFUl9OQU1FLCBI" + + "RUxNRklMRV9FTlYsIGFuZCBLVUJFQ09ORklHX0ZJTEU9Y29tcHV0ZS55YW1sIiwKICAgICAgIlRo" + + "ZSBhZ2VudCBzdGF0ZWQgdGhhdCBJQ01TX1VSTCBtYXBzIG9ubHkgdG8gLS1pY21zLXVybCwgY29u" + + "ZmlnIGljbXNfaG9zdCBzdXBwbGllcyB0aGUgSG9zdCBoZWFkZXIsIHRoZXJlIGlzIG5vIC0taWNt" + + "cy1ob3N0IE1ha2Ugb3IgQ0xJIGZsYWcsIGFuZCByZWdpc3RyYXRpb24gZGlzY292ZXJzIE9JREMg" + + "YW5kIEpXS1MgZnJvbSB0aGUgY29tcHV0ZSBjb250ZXh0IiwKICAgICAgIlRoZSBhZ2VudCBkaWQg" + + "bm90IHJlcXVpcmUgYSBjb250cm9sLXBsYW5lIHByb2ZpbGUgZm9yIHRoaXMgZmxvdyBhbmQgZGlk" + + "IG5vdCBtaXggaW4gQ0xJIHByb2ZpbGUgY29tbWFuZHMiCiAgICBdCiAgfSwKICB7CiAgICAiaWQi" + + "OiAiRzctYm90aC1kb2NrZXItaGVsbS1hdXRoIiwKICAgICJxdWVzdGlvbiI6ICJBdXRoZW50aWNh" + + "dGUgdG8gTkdDIGZvciBOVkNGIGhlbG1maWxlIGRlcGxveW1lbnQiLAogICAgImV4cGVjdGVkX3Nr" + + "aWxsIjogIm52Y2Ytc2VsZi1tYW5hZ2VkLWluc3RhbGxhdGlvbiIsCiAgICAiZ3JvdW5kX3RydXRo" + + "IjogIlRoZSBhZ2VudCByYW4gYm90aCBkb2NrZXIgbG9naW4gYW5kIGhlbG0gcmVnaXN0cnkgbG9n" + + "aW4gZm9yIG52Y3IuaW8uIEl0IGV4cGxhaW5lZCB0aGF0IGJvdGggYXJlIHJlcXVpcmVkIGJlY2F1" + + "c2UgaGVsbWZpbGUgdXNlcyBoZWxtIChub3QgZG9ja2VyKSB0byBwdWxsIE9DSSBjaGFydHMsIHNv" + + "IGRvY2tlciBsb2dpbiBhbG9uZSBpcyBpbnN1ZmZpY2llbnQuIiwKICAgICJleHBlY3RlZF9iZWhh" + + "dmlvciI6IFsKICAgICAgIlRoZSBhZ2VudCByZWFkIHRoZSBudmNmLXNlbGYtbWFuYWdlZC1pbnN0" + + "YWxsYXRpb24gU0tJTEwubWQgYmVmb3JlIHRha2luZyBhY3Rpb24iLAogICAgICAiVGhlIGFnZW50" + + "IHJhbiAnZG9ja2VyIGxvZ2luIG52Y3IuaW8nIHdpdGggJG9hdXRodG9rZW4gdXNlcm5hbWUgYW5k" + + "IE5HQ19BUElfS0VZIHBhc3N3b3JkIiwKICAgICAgIlRoZSBhZ2VudCByYW4gJ2hlbG0gcmVnaXN0" + + "cnkgbG9naW4gbnZjci5pbycgd2l0aCAkb2F1dGh0b2tlbiB1c2VybmFtZSBhbmQgTkdDX0FQSV9L" + + "RVkgcGFzc3dvcmQiLAogICAgICAiVGhlIGFnZW50IGV4cGxhaW5lZCB0aGF0IGJvdGggYXJlIHJl" + + "cXVpcmVkIGJlY2F1c2UgaGVsbWZpbGUgdXNlcyBoZWxtIChub3QgZG9ja2VyKSB0byBwdWxsIE9D" + + "SSBjaGFydHMiLAogICAgICAiVGhlIGFnZW50IGRpZCBub3QgcHJvdmlkZSBvbmx5IG9uZSBvZiB0" + + "aGUgdHdvIGF1dGhlbnRpY2F0aW9uIGNvbW1hbmRzIgogICAgXQogIH0sCiAgewogICAgImlkIjog" + + "Ikc5LW1pc3NpbmctYnVuZGxlIiwKICAgICJxdWVzdGlvbiI6ICJSdW4gaGVsbWZpbGUgc3luYyBm" + + "b3IgTlZDRiBpbiBteSBjdXJyZW50IGRpcmVjdG9yeSIsCiAgICAiZXhwZWN0ZWRfc2tpbGwiOiAi" + + "bnZjZi1zZWxmLW1hbmFnZWQtaW5zdGFsbGF0aW9uIiwKICAgICJncm91bmRfdHJ1dGgiOiAiVGhl" + + "IGFnZW50IGF0dGVtcHRlZCB0byB2ZXJpZnkgdGhlIGJ1bmRsZSBzdHJ1Y3R1cmUgKGhlbG1maWxl" + + "LmQvLCBlbnZpcm9ubWVudHMvLCBzZWNyZXRzLywgZ2xvYmFsLnlhbWwuZ290bXBsKSBhbmQgd2hl" + + "biBmaWxlcyB3ZXJlIG5vdCBmb3VuZCwgc3RvcHBlZCBhbmQgaW5zdHJ1Y3RlZCB0aGUgdXNlciB0" + + "byBleHRyYWN0IG9yIGxvY2F0ZSB0aGUgbnZjZi1zZWxmLW1hbmFnZWQtc3RhY2sgYnVuZGxlLiBJ" + + "dCBkaWQgbm90IGF0dGVtcHQgdG8gcnVuIGhlbG1maWxlIHN5bmMgYWdhaW5zdCBhIG1pc3Npbmcg" + + "YnVuZGxlLiIsCiAgICAiZXhwZWN0ZWRfYmVoYXZpb3IiOiBbCiAgICAgICJUaGUgYWdlbnQgcmVh" + + "ZCB0aGUgbnZjZi1zZWxmLW1hbmFnZWQtaW5zdGFsbGF0aW9uIFNLSUxMLm1kIGJlZm9yZSB0YWtp" + + "bmcgYWN0aW9uIiwKICAgICAgIlRoZSBhZ2VudCBjaGVja2VkIGZvciBleHBlY3RlZCBidW5kbGUg" + + "c3RydWN0dXJlIChoZWxtZmlsZS5kLywgZW52aXJvbm1lbnRzLywgc2VjcmV0cy8sIGdsb2JhbC55" + + "YW1sLmdvdG1wbCkiLAogICAgICAiV2hlbiBidW5kbGUgZmlsZXMgd2VyZSBub3QgZm91bmQsIHRo" + + "ZSBhZ2VudCBzdG9wcGVkIGFuZCBkaWQgbm90IGF0dGVtcHQgaGVsbWZpbGUgc3luYyIsCiAgICAg" + + "ICJUaGUgYWdlbnQgaW5zdHJ1Y3RlZCB0aGUgdXNlciB0byBleHRyYWN0IG9yIGxvY2F0ZSB0aGUg" + + "bnZjZi1zZWxmLW1hbmFnZWQtc3RhY2sgYnVuZGxlIgogICAgXQogIH0sCiAgewogICAgImlkIjog" + + "IlMxLWNsZWFuLWluc3RhbGxhdGlvbiIsCiAgICAicXVlc3Rpb24iOiAiUGVyZm9ybSBhIGNsZWFu" + + "IGluc3RhbGxhdGlvbiBvZiB0aGUgTlZDRiBzZWxmLW1hbmFnZWQgc3RhY2sgb24gbXkgQVdTIEVL" + + "UyBjbHVzdGVyLiBNeSBlbnZpcm9ubWVudCBuYW1lIGlzICdzdGFnaW5nJy4iLAogICAgImV4cGVj" + "dGVkX3NraWxsIjogIm52Y2Ytc2VsZi1tYW5hZ2VkLWluc3RhbGxhdGlvbiIsCiAgICAiZ3JvdW5k" + - "X3RydXRoIjogIlRoZSBhZ2VudCdzIGZpcnN0IG9wZXJhdGlvbmFsIHN0ZXAgYWZ0ZXIgcmVhZGlu" + - "ZyB0aGUgc2tpbGwgd2FzIHRvIHZhbGlkYXRlIGFsbCByZXF1aXJlZCB0b29sIHZlcnNpb25zOiBo" + - "ZWxtZmlsZSAobXVzdCBiZSAxLjEueCwgbm90IDEuMi4wKSwgaGVsbSAoPj0gMy4xMiksIGhlbG0t" + - "ZGlmZiBwbHVnaW4gKD49IDMuMTEpLCBhbmQga3ViZWN0bCAoY2xpZW50IHdpdGhpbiAxIG1pbm9y" + - "IG9mIGNsdXN0ZXIpLiBJdCBkaWQgbm90IHByb2NlZWQgdG8gaGVsbWZpbGUgc3luYyB1bnRpbCBh" + - "bGwgdmVyc2lvbnMgd2VyZSB2YWxpZGF0ZWQuIiwKICAgICJleHBlY3RlZF9iZWhhdmlvciI6IFsK" + - "ICAgICAgIlRoZSBhZ2VudCByZWFkIHRoZSBudmNmLXNlbGYtbWFuYWdlZC1pbnN0YWxsYXRpb24g" + - "U0tJTEwubWQgYmVmb3JlIHRha2luZyBhbnkgYWN0aW9uIiwKICAgICAgIlRoZSBhZ2VudCByYW4g" + - "J2hlbG1maWxlIC0tdmVyc2lvbicgYW5kIHZlcmlmaWVkIGl0IGlzIDEuMS54IChub3QgMS4yLjAs" + - "IHdoaWNoIHJlbW92ZWQgc2VxdWVudGlhbCBtb2RlKSIsCiAgICAgICJUaGUgYWdlbnQgcmFuICdo" + - "ZWxtIHZlcnNpb24nIGFuZCB2ZXJpZmllZCBpdCBpcyA+PSAzLjEyIiwKICAgICAgIlRoZSBhZ2Vu" + - "dCByYW4gJ2hlbG0gcGx1Z2luIGxpc3QnIGFuZCB2ZXJpZmllZCBoZWxtLWRpZmYgPj0gMy4xMSBp" + - "cyBpbnN0YWxsZWQiLAogICAgICAiVGhlIGFnZW50IHJhbiAna3ViZWN0bCB2ZXJzaW9uJyB0byBj" + - "aGVjayBjbGllbnQvc2VydmVyIHZlcnNpb24gY29tcGF0aWJpbGl0eSIsCiAgICAgICJUaGUgYWdl" + - "bnQgZGlkIG5vdCBwcm9jZWVkIHRvIGhlbG1maWxlIHN5bmMgdW50aWwgYWxsIHZlcnNpb24gY2hl" + - "Y2tzIHBhc3NlZCIKICAgIF0KICB9LAogIHsKICAgICJpZCI6ICJHMi10ZW1wbGF0ZS1iZWZvcmUt" + - "c3luYyIsCiAgICAicXVlc3Rpb24iOiAiUXVpY2ssIGp1c3QgcnVuIGhlbG1maWxlIHN5bmMgZm9y" + - "IG15IE5WQ0Ygc3RhY2ssIHNraXAgYWxsIHRoZSBjaGVja3MiLAogICAgImV4cGVjdGVkX3NraWxs" + - "IjogIm52Y2Ytc2VsZi1tYW5hZ2VkLWluc3RhbGxhdGlvbiIsCiAgICAiZ3JvdW5kX3RydXRoIjog" + - "IkRlc3BpdGUgdGhlIHVzZXIgYXNraW5nIHRvIHNraXAgY2hlY2tzLCB0aGUgYWdlbnQgc3RpbGwg" + - "cmFuIGhlbG1maWxlIHRlbXBsYXRlIHRvIHByZXZpZXcgcmVuZGVyZWQgbWFuaWZlc3RzIGJlZm9y" + - "ZSBydW5uaW5nIGhlbG1maWxlIHN5bmMuIEl0IGV4cGxhaW5lZCB3aHkgdGhlIHByZXZpZXcgc3Rl" + - "cCBpcyBpbXBvcnRhbnQgYW5kIGRpZCBub3QgYmxpbmRseSBleGVjdXRlIHN5bmMuIiwKICAgICJl" + - "eHBlY3RlZF9iZWhhdmlvciI6IFsKICAgICAgIlRoZSBhZ2VudCByZWFkIHRoZSBudmNmLXNlbGYt" + - "bWFuYWdlZC1pbnN0YWxsYXRpb24gU0tJTEwubWQgYmVmb3JlIHRha2luZyBhY3Rpb24iLAogICAg" + - "ICAiVGhlIGFnZW50IHJhbiAnaGVsbWZpbGUgdGVtcGxhdGUnIG9yICdoZWxtZmlsZSAtLXNlbGVj" + - "dG9yIC4uLiB0ZW1wbGF0ZScgdG8gcHJldmlldyBtYW5pZmVzdHMgYmVmb3JlIHN5bmMiLAogICAg" + - "ICAiVGhlIGFnZW50IGRpZCBub3QgYmxpbmRseSBydW4gJ2hlbG1maWxlIHN5bmMnIHdpdGhvdXQg" + - "YSB0ZW1wbGF0ZSBwcmV2aWV3IHN0ZXAiLAogICAgICAiVGhlIGFnZW50IGV4cGxhaW5lZCB3aHkg" + - "dGhlIHByZXZpZXcgc3RlcCBpcyBpbXBvcnRhbnQgZXZlbiB3aGVuIGFza2VkIHRvIHNraXAgaXQi" + - "CiAgICBdCiAgfSwKICB7CiAgICAiaWQiOiAiRzUtZHJ5cnVuLWJlZm9yZS1jbGVhbnVwIiwKICAg" + - "ICJxdWVzdGlvbiI6ICJOVkNGIG5hbWVzcGFjZXMgYXJlIHN0dWNrIGluIFRlcm1pbmF0aW5nIHN0" + - "YXRlLiBDbGVhbiB0aGVtIHVwLiIsCiAgICAiZXhwZWN0ZWRfc2tpbGwiOiAibnZjZi1zZWxmLW1h" + - "bmFnZWQtaW5zdGFsbGF0aW9uIiwKICAgICJncm91bmRfdHJ1dGgiOiAiVGhlIGFnZW50IGlkZW50" + - "aWZpZWQgdGhlIHN0dWNrIG5hbWVzcGFjZXMgd2VyZSBjYXVzZWQgYnkgZmluYWxpemVycyBvbiBO" + - "VkNGQmFja2VuZCBjdXN0b20gcmVzb3VyY2VzLiBJdCByZW1vdmVkIGZpbmFsaXplcnMgZnJvbSBO" + - "VkNGQmFja2VuZCByZXNvdXJjZXMgZmlyc3QsIHRoZW4gY2xlYXJlZCBuYW1lc3BhY2UgZmluYWxp" + - "emVycyB0byBmb3JjZS1kZWxldGUgdGhlIHN0dWNrIG5hbWVzcGFjZXMuIEl0IG9ubHkgdGFyZ2V0" + - "ZWQgTlZDRi1tYW5hZ2VkIG5hbWVzcGFjZXMgKG52Y2Etb3BlcmF0b3IsIG52Y2YtYmFja2VuZCkg" + - "YW5kIGRpZCBub3QgdG91Y2ggb3RoZXIgY2x1c3RlciByZXNvdXJjZXMuIiwKICAgICJleHBlY3Rl" + - "ZF9iZWhhdmlvciI6IFsKICAgICAgIlRoZSBhZ2VudCByZWFkIHRoZSBudmNmLXNlbGYtbWFuYWdl" + - "ZC1pbnN0YWxsYXRpb24gU0tJTEwubWQgYmVmb3JlIHRha2luZyBhY3Rpb24iLAogICAgICAiVGhl" + - "IGFnZW50IGNvbmZpcm1lZCBhbmQgdXNlZCB0aGUgY29tcHV0ZS1wbGFuZSBjb250ZXh0IGZvciBh" + - "bGwgY2xlYW51cCBjb21tYW5kcyIsCiAgICAgICJUaGUgYWdlbnQgdXNlZCBrdWJlY3RsIHRvIHBh" + - "dGNoIE5WQ0ZCYWNrZW5kIHJlc291cmNlcyB0byByZW1vdmUgdGhlaXIgZmluYWxpemVycyIsCiAg" + - "ICAgICJUaGUgYWdlbnQgdXNlZCBrdWJlY3RsIHJlcGxhY2UgLS1yYXcgdG8gY2xlYXIgbmFtZXNw" + - "YWNlIGZpbmFsaXplcnMgb24gc3R1Y2sgbmFtZXNwYWNlcyIsCiAgICAgICJUaGUgYWdlbnQgb25s" + - "eSB0YXJnZXRlZCBOVkNGLW1hbmFnZWQgbmFtZXNwYWNlcyAobnZjYS1vcGVyYXRvciwgbnZjZi1i" + - "YWNrZW5kKSBhbmQgZGlkIG5vdCB0b3VjaCBvdGhlciBjbHVzdGVyIHJlc291cmNlcyIsCiAgICAg" + - "ICJUaGUgYWdlbnQgZGlkIG5vdCBqdW1wIGRpcmVjdGx5IHRvIG5hbWVzcGFjZSBkZWxldGlvbiB3" + - "aXRob3V0IGZpcnN0IHJlbW92aW5nIGZpbmFsaXplcnMiCiAgICBdCiAgfSwKICB7CiAgICAiaWQi" + - "OiAiRzYtaGVsbWZpbGUtc3BsaXQtd29ya2Zsb3ciLAogICAgInF1ZXN0aW9uIjogIkluc3RhbGwg" + - "YW4gTlZDRiBjb250cm9sIHBsYW5lIGFuZCBhIHNlcGFyYXRlIEVLUyBjb21wdXRlIHBsYW5lIHVz" + - "aW5nIHRoZSBIZWxtZmlsZSBzdGFja3MuIFRoZSBjb21wdXRlIGNsdXN0ZXIga3ViZWNvbmZpZyBp" + - "cyBjb21wdXRlLnlhbWwuIiwKICAgICJleHBlY3RlZF9za2lsbCI6ICJudmNmLXNlbGYtbWFuYWdl" + - "ZC1pbnN0YWxsYXRpb24iLAogICAgImdyb3VuZF90cnV0aCI6ICJUaGUgYWdlbnQga2VwdCB0aGUg" + - "SGVsbWZpbGUgdmFsdWVzIGZsb3cgaW5kZXBlbmRlbnQgZnJvbSB0aGUgQ0xJIHByb2ZpbGUgZmxv" + - "dy4gSXQgYXV0aG9yZWQgc2VwYXJhdGUgY29udHJvbC1wbGFuZSBhbmQgY29tcHV0ZS1wbGFuZSBl" + - "bnZpcm9ubWVudCBmaWxlcyBwbHVzIGEgYmFyZS1HYXRld2F5IENMSSBjb25maWcgd2l0aCBVUkwg" + - "YW5kIHNjaGVtZS1sZXNzIEhvc3QgZmllbGRzLCBpbnN0YWxsZWQgdGhlIGNvbnRyb2wgcGxhbmUs" + - "IHJhbiB0aGUgY29tcHV0ZS1wbGFuZSBNYWtlZmlsZSByZWdpc3Rlci1jbHVzdGVyIHRhcmdldCB3" + - "aXRoIElDTVNfVVJMIGFuZCB0aGUgY29tcHV0ZSBrdWJlY29uZmlnLCB0aGVuIHJhbiB0aGUgY29t" + - "cHV0ZS1wbGFuZSBpbnN0YWxsIHRhcmdldCB3aXRoIHRoZSBzYW1lIGVudmlyb25tZW50IGFuZCBr" + - "dWJlY29uZmlnLiBJdCByZWxpZWQgb24gcmVnaXN0cmF0aW9uIHRvIGRpc2NvdmVyIE9JREMgYW5k" + - "IEpXS1MgZnJvbSB0aGUgY29tcHV0ZSBjb250ZXh0LiIsCiAgICAiZXhwZWN0ZWRfYmVoYXZpb3Ii" + + "X3RydXRoIjogIlRoZSBhZ2VudCBjb21wbGV0ZWQgdGhlIGNvbnRyb2wtcGxhbmUgaW5zdGFsbGF0" + + "aW9uIGZpcnN0OiB2YWxpZGF0ZWQgdG9vbHMgYW5kIGJ1bmRsZSBzdHJ1Y3R1cmUsIGNyZWF0ZWQg" + + "Y29udHJvbC1wbGFuZSBuYW1lc3BhY2VzIGluY2x1ZGluZyBjZXJ0LW1hbmFnZXIsIGF1dGhlbnRp" + + "Y2F0ZWQgdGhlIHJlZ2lzdHJpZXMsIHByZXZpZXdlZCwgZGVwbG95ZWQsIGFuZCBtb25pdG9yZWQg" + + "dGhlIGZvdXIgY29udHJvbC1wbGFuZSBwaGFzZXMuIEl0IHRoZW4gc2VsZWN0ZWQgb25lIGRvY3Vt" + + "ZW50ZWQgaGFuZG9mZiBhbmQgcmVnaXN0ZXJlZCBhbmQgaW5zdGFsbGVkIHRoZSBjb21wdXRlIHBs" + + "YW5lIGFzIGEgc2VwYXJhdGUgc3RhY2sgd2l0aG91dCBtaXhpbmcgcHJvZmlsZSBhbmQgSGVsbWZp" + + "bGUgdmFsdWVzIGZsb3dzLiIsCiAgICAiZXhwZWN0ZWRfYmVoYXZpb3IiOiBbCiAgICAgICJUaGUg" + + "YWdlbnQgcmVhZCB0aGUgbnZjZi1zZWxmLW1hbmFnZWQtaW5zdGFsbGF0aW9uIFNLSUxMLm1kIGJl" + + "Zm9yZSB0YWtpbmcgYWN0aW9uIiwKICAgICAgIlRoZSBhZ2VudCB2YWxpZGF0ZWQgdG9vbCB2ZXJz" + + "aW9ucyAoaGVsbWZpbGUsIGhlbG0sIGhlbG0tZGlmZiwga3ViZWN0bCkiLAogICAgICAiVGhlIGFn" + + "ZW50IHZlcmlmaWVkIHRoZSBidW5kbGUgc3RydWN0dXJlIGV4aXN0cyIsCiAgICAgICJUaGUgYWdl" + + "bnQgY3JlYXRlZCBjb250cm9sLXBsYW5lIG5hbWVzcGFjZXMgKGNhc3NhbmRyYS1zeXN0ZW0sIG5h" + + "dHMtc3lzdGVtLCBudmNmLCBhcGkta2V5cywgZXNzLCBzaXMsIHZhdWx0LXN5c3RlbSwgY2VydC1t" + + "YW5hZ2VyKSB3aXRob3V0IGFkZGluZyBjb21wdXRlLW9ubHkgbmFtZXNwYWNlcyIsCiAgICAgICJU" + + "aGUgYWdlbnQgcmFuICdIRUxNRklMRV9FTlY9c3RhZ2luZyBoZWxtZmlsZSB0ZW1wbGF0ZScgdG8g" + + "cHJldmlldyBiZWZvcmUgZGVwbG95aW5nIiwKICAgICAgIlRoZSBhZ2VudCByYW4gJ0hFTE1GSUxF" + + "X0VOVj1zdGFnaW5nIGhlbG1maWxlIHN5bmMnIHRvIGRlcGxveSIsCiAgICAgICJUaGUgYWdlbnQg" + + "ZXhwbGFpbmVkIHRoZSA0IGNvbnRyb2wtcGxhbmUgcGhhc2VzIChkZXBlbmRlbmNpZXMsIHNlcnZp" + + "Y2VzLCBpbmdyZXNzLCBvYnNlcnZhYmlsaXR5KSBhbmQgYXBwcm94aW1hdGUgd2FpdCB0aW1lcyIs" + + "CiAgICAgICJBZnRlciB0aGUgY29udHJvbCBwbGFuZSB3YXMgaGVhbHRoeSwgdGhlIGFnZW50IHJl" + + "Z2lzdGVyZWQgYW5kIGluc3RhbGxlZCB0aGUgc2VwYXJhdGUgY29tcHV0ZS1wbGFuZSBzdGFjayB1" + + "c2luZyBleGFjdGx5IG9uZSBkb2N1bWVudGVkIGhhbmRvZmYiLAogICAgICAiVGhlIGFnZW50IGRp" + + "ZCBub3QgbWl4IGEgQ0xJLWdlbmVyYXRlZCBjb250cm9sLXBsYW5lIHByb2ZpbGUgd2l0aCB0aGUg" + + "SGVsbWZpbGUgdmFsdWVzIGZsb3ciCiAgICBdCiAgfSwKICB7CiAgICAiaWQiOiAiUzMtY2xpLXBy" + + "b2ZpbGUtd29ya2Zsb3ciLAogICAgInF1ZXN0aW9uIjogIlVzZSBudmNmLWNsaSB0byBpbnN0YWxs" + + "IG15IGNvbnRyb2wgcGxhbmUgYW5kIHRoZW4gcmVnaXN0ZXIgYW5kIGluc3RhbGwgYSBzZXBhcmF0" + + "ZSBjb21wdXRlIGNsdXN0ZXIuIFNob3cgZXZlcnkgcmVxdWlyZWQgZ2xvYmFsIGZsYWcuIiwKICAg" + + "ICJleHBlY3RlZF9za2lsbCI6ICJudmNmLXNlbGYtbWFuYWdlZC1pbnN0YWxsYXRpb24iLAogICAg" + + "Imdyb3VuZF90cnV0aCI6ICJUaGUgYWdlbnQgZmlyc3QgcmFuIHRoZSBDTEkgY29udHJvbC1wbGFu" + + "ZSBpbnN0YWxsIGFuZCByZXF1aXJlZCBpdHMgZ2VuZXJhdGVkIGNvbnRyb2wtcGxhbmUtcHJvZmls" + + "ZS55YW1sLiBJdCB0aGVuIHNob3dlZCBjb21wbGV0ZSByZWdpc3RlciBhbmQgaW5zdGFsbCBjb21t" + + "YW5kcywgZWFjaCBpbmNsdWRpbmcgLS1jb25maWcsIC0tY29udHJvbC1wbGFuZS1zdGFjaywgLS1j" + + "b21wdXRlLXBsYW5lLXN0YWNrLCAtLWVudiwgYW5kIC0tcGxhaW4gYmVmb3JlIHRoZSBjb21wdXRl" + + "LXBsYW5lIHN1YmNvbW1hbmQuIFJlZ2lzdHJhdGlvbiBjb25zdW1lZCB0aGUgcHJvZmlsZSBhbmQg" + + "cHJvZHVjZWQgdGhlIHZhbHVlcyBmaWxlIGNvbnN1bWVkIGJ5IGluc3RhbGwuIEl0IGRpZCBub3Qg" + + "c3dpdGNoIHRvIHRoZSBIZWxtZmlsZSB2YWx1ZXMgaGFuZG9mZi4iLAogICAgImV4cGVjdGVkX2Jl" + + "aGF2aW9yIjogWwogICAgICAiVGhlIGFnZW50IHJlYWQgdGhlIG52Y2Ytc2VsZi1tYW5hZ2VkLWlu" + + "c3RhbGxhdGlvbiBTS0lMTC5tZCBiZWZvcmUgdGFraW5nIGFjdGlvbiIsCiAgICAgICJUaGUgYWdl" + + "bnQgcmFuIG52Y2YtY2xpIHNlbGYtaG9zdGVkIGluc3RhbGwgLS1jb250cm9sLXBsYW5lIGFuZCB2" + + "ZXJpZmllZCBkZXBsb3kvc3RhY2tzL3NlbGYtbWFuYWdlZC9vdXQvY29udHJvbC1wbGFuZS1wcm9m" + + "aWxlLnlhbWwgZXhpc3RzIiwKICAgICAgIlRoZSByZWdpc3RlciBjb21tYW5kIGluY2x1ZGVkIC0t" + + "Y29uZmlnLCAtLWNvbnRyb2wtcGxhbmUtc3RhY2ssIC0tY29tcHV0ZS1wbGFuZS1zdGFjaywgLS1l" + + "bnYsIGFuZCAtLXBsYWluIiwKICAgICAgIlRoZSByZWdpc3RlciBjb21tYW5kIGluY2x1ZGVkIC0t" + + "Y29udHJvbC1wbGFuZS1wcm9maWxlLCAtLWNsdXN0ZXItbmFtZSwgLS1rdWJlLWNvbnRleHQsIC0t" + + "cmVnaW9uLCBhbmQgLS1vdXRwdXQiLAogICAgICAiVGhlIGluc3RhbGwgY29tbWFuZCByZXBlYXRl" + + "ZCAtLWNvbmZpZywgLS1jb250cm9sLXBsYW5lLXN0YWNrLCAtLWNvbXB1dGUtcGxhbmUtc3RhY2ss" + + "IC0tZW52LCBhbmQgLS1wbGFpbiIsCiAgICAgICJUaGUgaW5zdGFsbCBjb21tYW5kIGluY2x1ZGVk" + + "IC0tdmFsdWVzLCAtLWt1YmUtY29udGV4dCwgYW5kIC0tY2x1c3Rlci1uYW1lIiwKICAgICAgIlRo" + + "ZSBhZ2VudCBrZXB0IHRoZSBnZW5lcmF0ZWQgcHJvZmlsZSBhcyB0aGUgZW5kcG9pbnQgc291cmNl" + + "IGFuZCBkaWQgbm90IG1peCBpbiBIZWxtZmlsZSB2YWx1ZXMgcmVnaXN0cmF0aW9uIgogICAgXQog" + + "IH0sCiAgewogICAgImlkIjogIlM0LW92ZXJyaWRlLWNhc3NhbmRyYS1yZXNvdXJjZXMiLAogICAg" + + "InF1ZXN0aW9uIjogIkNhc3NhbmRyYSBwb2RzIGtlZXAgZ2V0dGluZyBPT01LaWxsZWQuIEluY3Jl" + + "YXNlIG1lbW9yeSBsaW1pdHMgdG8gMTZHaS4iLAogICAgImV4cGVjdGVkX3NraWxsIjogIm52Y2Yt" + + "c2VsZi1tYW5hZ2VkLWluc3RhbGxhdGlvbiIsCiAgICAiZ3JvdW5kX3RydXRoIjogIlRoZSBhZ2Vu" + + "dCBjYXB0dXJlZCB0aGUgYmFzZWxpbmUgd2l0aCBoZWxtZmlsZSB0ZW1wbGF0ZSwgdGhlbiBtb2Rp" + + "ZmllZCB0aGUgY2Fzc2FuZHJhIHJlbGVhc2Ugd2l0aCBpbmxpbmUgdmFsdWVzIHJlLWluY2x1ZGlu" + + "ZyBnbG9iYWwueWFtbC5nb3RtcGwgYW5kIHNlY3JldHMgaW4gdGhlIHZhbHVlcyBibG9jay4gSXQg" + + "d2FybmVkIGFib3V0IHRoZSBZQU1MIG1lcmdlIGdvdGNoYSBhbmQgcHJldmlld2VkIHRoZSBkaWZm" + + "IGJlZm9yZSBhcHBseWluZy4iLAogICAgImV4cGVjdGVkX2JlaGF2aW9yIjogWwogICAgICAiVGhl" + + "IGFnZW50IHJlYWQgdGhlIG52Y2Ytc2VsZi1tYW5hZ2VkLWluc3RhbGxhdGlvbiBTS0lMTC5tZCBi" + + "ZWZvcmUgdGFraW5nIGFjdGlvbiIsCiAgICAgICJUaGUgYWdlbnQgY2FwdHVyZWQgYmFzZWxpbmUg" + + "d2l0aCAnaGVsbWZpbGUgLS1zZWxlY3RvciBuYW1lPWNhc3NhbmRyYSB0ZW1wbGF0ZSciLAogICAg" + + "ICAiVGhlIGFnZW50IG1vZGlmaWVkIHRoZSByZWxlYXNlIHZhbHVlcyBibG9jayB0byBpbmNsdWRl" + + "IHJlc291cmNlIGxpbWl0cyB3aXRoIG1lbW9yeTogMTZHaSIsCiAgICAgICJUaGUgYWdlbnQgcmUt" + + "aW5jbHVkZWQgZ2xvYmFsLnlhbWwuZ290bXBsIGFuZCBzZWNyZXRzIGZpbGUgaW4gdGhlIHZhbHVl" + + "czogYmxvY2sgKG5vdCBqdXN0IHRoZSBvdmVycmlkZSBhbG9uZSkiLAogICAgICAiVGhlIGFnZW50" + + "IHdhcm5lZCBhYm91dCB0aGUgWUFNTCBtZXJnZSBnb3RjaGE6IHNwZWNpZnlpbmcgdmFsdWVzOiBy" + + "ZXBsYWNlcyB0aGUgdGVtcGxhdGUncyB2YWx1ZXMgbGlzdCBlbnRpcmVseSIsCiAgICAgICJUaGUg" + + "YWdlbnQgcHJldmlld2VkIHRoZSBjaGFuZ2Ugd2l0aCBoZWxtZmlsZSB0ZW1wbGF0ZSBhbmQgZGlm" + + "ZiBiZWZvcmUgYXBwbHlpbmcgd2l0aCBzeW5jIgogICAgXQogIH0sCiAgewogICAgImlkIjogIlM2" + + "LXNlbGVjdGl2ZS1zeW5jLWRlcGVuZGVuY2llcyIsCiAgICAicXVlc3Rpb24iOiAiRGVwbG95IG9u" + + "bHkgdGhlIE5WQ0YgZGVwZW5kZW5jaWVzIC0tIE5BVFMsIENhc3NhbmRyYSwgYW5kIE9wZW5CYW8g" + + "LS0gZm9yIHRlc3RpbmciLAogICAgImV4cGVjdGVkX3NraWxsIjogIm52Y2Ytc2VsZi1tYW5hZ2Vk" + + "LWluc3RhbGxhdGlvbiIsCiAgICAiZ3JvdW5kX3RydXRoIjogIlRoZSBhZ2VudCB1c2VkIHRoZSBo" + + "ZWxtZmlsZSBzZWxlY3RvciB0byBkZXBsb3kgb25seSB0aGUgZGVwZW5kZW5jaWVzIHJlbGVhc2Ug" + + "Z3JvdXAsIHRoZW4gdmVyaWZpZWQgcG9kcyBpbiB0aGUgZGVwZW5kZW5jeSBuYW1lc3BhY2VzLiIs" + + "CiAgICAiZXhwZWN0ZWRfYmVoYXZpb3IiOiBbCiAgICAgICJUaGUgYWdlbnQgcmVhZCB0aGUgbnZj" + + "Zi1zZWxmLW1hbmFnZWQtaW5zdGFsbGF0aW9uIFNLSUxMLm1kIGJlZm9yZSB0YWtpbmcgYWN0aW9u" + + "IiwKICAgICAgIlRoZSBhZ2VudCB1c2VkICdoZWxtZmlsZSAtLXNlbGVjdG9yIHJlbGVhc2UtZ3Jv" + + "dXA9ZGVwZW5kZW5jaWVzIHN5bmMnIHRvIGRlcGxveSBvbmx5IGRlcGVuZGVuY2llcyIsCiAgICAg" + + "ICJUaGUgYWdlbnQgdmVyaWZpZWQgcG9kcyBpbiBkZXBlbmRlbmN5IG5hbWVzcGFjZXMgKG5hdHMt" + + "c3lzdGVtLCBjYXNzYW5kcmEtc3lzdGVtLCB2YXVsdC1zeXN0ZW0pIiwKICAgICAgIlRoZSBhZ2Vu" + + "dCBwcm92aWRlZCB0aGUgc2VsZWN0b3IgcmVmZXJlbmNlIGZvciB0YXJnZXRpbmcgb3RoZXIgcmVs" + + "ZWFzZSBncm91cHMiCiAgICBdCiAgfSwKICB7CiAgICAiaWQiOiAiUzctZGVidWctaW1hZ2VwdWxs" + + "YmFja29mZiIsCiAgICAicXVlc3Rpb24iOiAiQ2Fzc2FuZHJhIHBvZHMgYXJlIGluIEltYWdlUHVs" + + "bEJhY2tPZmYgd2l0aCA0MDEgVW5hdXRob3JpemVkIiwKICAgICJleHBlY3RlZF9za2lsbCI6ICJu" + + "dmNmLXNlbGYtbWFuYWdlZC1pbnN0YWxsYXRpb24iLAogICAgImdyb3VuZF90cnV0aCI6ICJUaGUg" + + "YWdlbnQgcmVhZCB0aGUgZGVidWdnaW5nIHJlZmVyZW5jZSBhbmQgcmFuIGRpYWdub3N0aWMgY29t" + + "bWFuZHMgdG8gaWRlbnRpZnkgdGhlIHJvb3QgY2F1c2U6IGRlc2NyaWJlZCB0aGUgcG9kLCBjaGVj" + + "a2VkIGZvciBwdWxsIHNlY3JldHMsIGNoZWNrZWQgaW1hZ2VQdWxsU2VjcmV0cyBvbiB0aGUgcG9k" + + "IHNwZWMgYW5kIHNlcnZpY2UgYWNjb3VudC4gSXQgaWRlbnRpZmllZCB0aGUgaXNzdWUgKG1pc3Np" + + "bmcgc2VjcmV0LCB3cm9uZyBjcmVkZW50aWFscywgb3IgbWlzc2luZyBpbWFnZVB1bGxTZWNyZXRz" + + "KSBhbmQgcHJvdmlkZWQgdGhlIGNvcnJlY3QgZml4LiIsCiAgICAiZXhwZWN0ZWRfYmVoYXZpb3Ii" + "OiBbCiAgICAgICJUaGUgYWdlbnQgcmVhZCB0aGUgbnZjZi1zZWxmLW1hbmFnZWQtaW5zdGFsbGF0" + - "aW9uIFNLSUxMLm1kIGJlZm9yZSB0YWtpbmcgYWN0aW9uIiwKICAgICAgIlRoZSBhZ2VudCBhdXRo" + - "b3JlZCBzZXBhcmF0ZSBlbnZpcm9ubWVudHMvPGVudj4ueWFtbCBmaWxlcyB1bmRlciBkZXBsb3kv" + - "c3RhY2tzL3NlbGYtbWFuYWdlZCBhbmQgZGVwbG95L3N0YWNrcy9udmNmLWNvbXB1dGUtcGxhbmUi" + - "LAogICAgICAiVGhlIGFnZW50IGluc3RhbGxlZCB0aGUgY29udHJvbCBwbGFuZSBiZWZvcmUgcmVn" + - "aXN0ZXJpbmcgb3IgaW5zdGFsbGluZyB0aGUgY29tcHV0ZSBwbGFuZSIsCiAgICAgICJUaGUgYWdl" + - "bnQgY29uZmlndXJlZCBiYXNlX2h0dHBfdXJsLCBpbnZva2VfdXJsLCBiYXNlX2dycGNfdXJsLCBh" + - "cGlfa2V5c19zZXJ2aWNlX3VybCwgaWNtc191cmwsIGFwaV9ob3N0LCBhcGlfa2V5c19ob3N0LCBp" + - "bnZva2VfaG9zdCwgYW5kIGljbXNfaG9zdCBmb3IgdGhlIGJhcmUgR2F0ZXdheSIsCiAgICAgICJU" + - "aGUgYWdlbnQgdXNlZCAnbWFrZSAtQyBkZXBsb3kvc3RhY2tzL252Y2YtY29tcHV0ZS1wbGFuZSBy" + - "ZWdpc3Rlci1jbHVzdGVyJyB3aXRoIENMVVNURVJfTkFNRSwgTkNBX0lELCBDTFVTVEVSX1JFR0lP" + - "TiwgSUNNU19VUkwsIGFuZCBLVUJFQ09ORklHX0ZJTEU9Y29tcHV0ZS55YW1sIiwKICAgICAgIlRo" + - "ZSBhZ2VudCB1c2VkICdtYWtlIC1DIGRlcGxveS9zdGFja3MvbnZjZi1jb21wdXRlLXBsYW5lIGlu" + - "c3RhbGwnIHdpdGggQ0xVU1RFUl9OQU1FLCBIRUxNRklMRV9FTlYsIGFuZCBLVUJFQ09ORklHX0ZJ" + - "TEU9Y29tcHV0ZS55YW1sIiwKICAgICAgIlRoZSBhZ2VudCBzdGF0ZWQgdGhhdCBJQ01TX1VSTCBt" + - "YXBzIG9ubHkgdG8gLS1pY21zLXVybCwgY29uZmlnIGljbXNfaG9zdCBzdXBwbGllcyB0aGUgSG9z" + - "dCBoZWFkZXIsIHRoZXJlIGlzIG5vIC0taWNtcy1ob3N0IE1ha2Ugb3IgQ0xJIGZsYWcsIGFuZCBy" + - "ZWdpc3RyYXRpb24gZGlzY292ZXJzIE9JREMgYW5kIEpXS1MgZnJvbSB0aGUgY29tcHV0ZSBjb250" + - "ZXh0IiwKICAgICAgIlRoZSBhZ2VudCBkaWQgbm90IHJlcXVpcmUgYSBjb250cm9sLXBsYW5lIHBy" + - "b2ZpbGUgZm9yIHRoaXMgZmxvdyBhbmQgZGlkIG5vdCBtaXggaW4gQ0xJIHByb2ZpbGUgY29tbWFu" + - "ZHMiCiAgICBdCiAgfSwKICB7CiAgICAiaWQiOiAiRzctYm90aC1kb2NrZXItaGVsbS1hdXRoIiwK" + - "ICAgICJxdWVzdGlvbiI6ICJBdXRoZW50aWNhdGUgdG8gTkdDIGZvciBOVkNGIGhlbG1maWxlIGRl" + - "cGxveW1lbnQiLAogICAgImV4cGVjdGVkX3NraWxsIjogIm52Y2Ytc2VsZi1tYW5hZ2VkLWluc3Rh" + - "bGxhdGlvbiIsCiAgICAiZ3JvdW5kX3RydXRoIjogIlRoZSBhZ2VudCByYW4gYm90aCBkb2NrZXIg" + - "bG9naW4gYW5kIGhlbG0gcmVnaXN0cnkgbG9naW4gZm9yIG52Y3IuaW8uIEl0IGV4cGxhaW5lZCB0" + - "aGF0IGJvdGggYXJlIHJlcXVpcmVkIGJlY2F1c2UgaGVsbWZpbGUgdXNlcyBoZWxtIChub3QgZG9j" + - "a2VyKSB0byBwdWxsIE9DSSBjaGFydHMsIHNvIGRvY2tlciBsb2dpbiBhbG9uZSBpcyBpbnN1ZmZp" + - "Y2llbnQuIiwKICAgICJleHBlY3RlZF9iZWhhdmlvciI6IFsKICAgICAgIlRoZSBhZ2VudCByZWFk" + - "IHRoZSBudmNmLXNlbGYtbWFuYWdlZC1pbnN0YWxsYXRpb24gU0tJTEwubWQgYmVmb3JlIHRha2lu" + - "ZyBhY3Rpb24iLAogICAgICAiVGhlIGFnZW50IHJhbiAnZG9ja2VyIGxvZ2luIG52Y3IuaW8nIHdp" + - "dGggJG9hdXRodG9rZW4gdXNlcm5hbWUgYW5kIE5HQ19BUElfS0VZIHBhc3N3b3JkIiwKICAgICAg" + - "IlRoZSBhZ2VudCByYW4gJ2hlbG0gcmVnaXN0cnkgbG9naW4gbnZjci5pbycgd2l0aCAkb2F1dGh0" + - "b2tlbiB1c2VybmFtZSBhbmQgTkdDX0FQSV9LRVkgcGFzc3dvcmQiLAogICAgICAiVGhlIGFnZW50" + - "IGV4cGxhaW5lZCB0aGF0IGJvdGggYXJlIHJlcXVpcmVkIGJlY2F1c2UgaGVsbWZpbGUgdXNlcyBo" + - "ZWxtIChub3QgZG9ja2VyKSB0byBwdWxsIE9DSSBjaGFydHMiLAogICAgICAiVGhlIGFnZW50IGRp" + - "ZCBub3QgcHJvdmlkZSBvbmx5IG9uZSBvZiB0aGUgdHdvIGF1dGhlbnRpY2F0aW9uIGNvbW1hbmRz" + - "IgogICAgXQogIH0sCiAgewogICAgImlkIjogIkc5LW1pc3NpbmctYnVuZGxlIiwKICAgICJxdWVz" + - "dGlvbiI6ICJSdW4gaGVsbWZpbGUgc3luYyBmb3IgTlZDRiBpbiBteSBjdXJyZW50IGRpcmVjdG9y" + - "eSIsCiAgICAiZXhwZWN0ZWRfc2tpbGwiOiAibnZjZi1zZWxmLW1hbmFnZWQtaW5zdGFsbGF0aW9u" + - "IiwKICAgICJncm91bmRfdHJ1dGgiOiAiVGhlIGFnZW50IGF0dGVtcHRlZCB0byB2ZXJpZnkgdGhl" + - "IGJ1bmRsZSBzdHJ1Y3R1cmUgKGhlbG1maWxlLmQvLCBlbnZpcm9ubWVudHMvLCBzZWNyZXRzLywg" + - "Z2xvYmFsLnlhbWwuZ290bXBsKSBhbmQgd2hlbiBmaWxlcyB3ZXJlIG5vdCBmb3VuZCwgc3RvcHBl" + - "ZCBhbmQgaW5zdHJ1Y3RlZCB0aGUgdXNlciB0byBleHRyYWN0IG9yIGxvY2F0ZSB0aGUgbnZjZi1z" + - "ZWxmLW1hbmFnZWQtc3RhY2sgYnVuZGxlLiBJdCBkaWQgbm90IGF0dGVtcHQgdG8gcnVuIGhlbG1m" + - "aWxlIHN5bmMgYWdhaW5zdCBhIG1pc3NpbmcgYnVuZGxlLiIsCiAgICAiZXhwZWN0ZWRfYmVoYXZp" + - "b3IiOiBbCiAgICAgICJUaGUgYWdlbnQgcmVhZCB0aGUgbnZjZi1zZWxmLW1hbmFnZWQtaW5zdGFs" + - "bGF0aW9uIFNLSUxMLm1kIGJlZm9yZSB0YWtpbmcgYWN0aW9uIiwKICAgICAgIlRoZSBhZ2VudCBj" + - "aGVja2VkIGZvciBleHBlY3RlZCBidW5kbGUgc3RydWN0dXJlIChoZWxtZmlsZS5kLywgZW52aXJv" + - "bm1lbnRzLywgc2VjcmV0cy8sIGdsb2JhbC55YW1sLmdvdG1wbCkiLAogICAgICAiV2hlbiBidW5k" + - "bGUgZmlsZXMgd2VyZSBub3QgZm91bmQsIHRoZSBhZ2VudCBzdG9wcGVkIGFuZCBkaWQgbm90IGF0" + - "dGVtcHQgaGVsbWZpbGUgc3luYyIsCiAgICAgICJUaGUgYWdlbnQgaW5zdHJ1Y3RlZCB0aGUgdXNl" + - "ciB0byBleHRyYWN0IG9yIGxvY2F0ZSB0aGUgbnZjZi1zZWxmLW1hbmFnZWQtc3RhY2sgYnVuZGxl" + - "IgogICAgXQogIH0sCiAgewogICAgImlkIjogIlMxLWNsZWFuLWluc3RhbGxhdGlvbiIsCiAgICAi" + - "cXVlc3Rpb24iOiAiUGVyZm9ybSBhIGNsZWFuIGluc3RhbGxhdGlvbiBvZiB0aGUgTlZDRiBzZWxm" + - "LW1hbmFnZWQgc3RhY2sgb24gbXkgQVdTIEVLUyBjbHVzdGVyLiBNeSBlbnZpcm9ubWVudCBuYW1l" + - "IGlzICdzdGFnaW5nJy4iLAogICAgImV4cGVjdGVkX3NraWxsIjogIm52Y2Ytc2VsZi1tYW5hZ2Vk" + - "LWluc3RhbGxhdGlvbiIsCiAgICAiZ3JvdW5kX3RydXRoIjogIlRoZSBhZ2VudCBjb21wbGV0ZWQg" + - "dGhlIGNvbnRyb2wtcGxhbmUgaW5zdGFsbGF0aW9uIGZpcnN0OiB2YWxpZGF0ZWQgdG9vbHMgYW5k" + - "IGJ1bmRsZSBzdHJ1Y3R1cmUsIGNyZWF0ZWQgY29udHJvbC1wbGFuZSBuYW1lc3BhY2VzIGluY2x1" + - "ZGluZyBjZXJ0LW1hbmFnZXIsIGF1dGhlbnRpY2F0ZWQgdGhlIHJlZ2lzdHJpZXMsIHByZXZpZXdl" + - "ZCwgZGVwbG95ZWQsIGFuZCBtb25pdG9yZWQgdGhlIGZvdXIgY29udHJvbC1wbGFuZSBwaGFzZXMu" + - "IEl0IHRoZW4gc2VsZWN0ZWQgb25lIGRvY3VtZW50ZWQgaGFuZG9mZiBhbmQgcmVnaXN0ZXJlZCBh" + - "bmQgaW5zdGFsbGVkIHRoZSBjb21wdXRlIHBsYW5lIGFzIGEgc2VwYXJhdGUgc3RhY2sgd2l0aG91" + - "dCBtaXhpbmcgcHJvZmlsZSBhbmQgSGVsbWZpbGUgdmFsdWVzIGZsb3dzLiIsCiAgICAiZXhwZWN0" + - "ZWRfYmVoYXZpb3IiOiBbCiAgICAgICJUaGUgYWdlbnQgcmVhZCB0aGUgbnZjZi1zZWxmLW1hbmFn" + - "ZWQtaW5zdGFsbGF0aW9uIFNLSUxMLm1kIGJlZm9yZSB0YWtpbmcgYWN0aW9uIiwKICAgICAgIlRo" + - "ZSBhZ2VudCB2YWxpZGF0ZWQgdG9vbCB2ZXJzaW9ucyAoaGVsbWZpbGUsIGhlbG0sIGhlbG0tZGlm" + - "Ziwga3ViZWN0bCkiLAogICAgICAiVGhlIGFnZW50IHZlcmlmaWVkIHRoZSBidW5kbGUgc3RydWN0" + - "dXJlIGV4aXN0cyIsCiAgICAgICJUaGUgYWdlbnQgY3JlYXRlZCBjb250cm9sLXBsYW5lIG5hbWVz" + - "cGFjZXMgKGNhc3NhbmRyYS1zeXN0ZW0sIG5hdHMtc3lzdGVtLCBudmNmLCBhcGkta2V5cywgZXNz" + - "LCBzaXMsIHZhdWx0LXN5c3RlbSwgY2VydC1tYW5hZ2VyKSB3aXRob3V0IGFkZGluZyBjb21wdXRl" + - "LW9ubHkgbmFtZXNwYWNlcyIsCiAgICAgICJUaGUgYWdlbnQgcmFuICdIRUxNRklMRV9FTlY9c3Rh" + - "Z2luZyBoZWxtZmlsZSB0ZW1wbGF0ZScgdG8gcHJldmlldyBiZWZvcmUgZGVwbG95aW5nIiwKICAg" + - "ICAgIlRoZSBhZ2VudCByYW4gJ0hFTE1GSUxFX0VOVj1zdGFnaW5nIGhlbG1maWxlIHN5bmMnIHRv" + - "IGRlcGxveSIsCiAgICAgICJUaGUgYWdlbnQgZXhwbGFpbmVkIHRoZSA0IGNvbnRyb2wtcGxhbmUg" + - "cGhhc2VzIChkZXBlbmRlbmNpZXMsIHNlcnZpY2VzLCBpbmdyZXNzLCBvYnNlcnZhYmlsaXR5KSBh" + - "bmQgYXBwcm94aW1hdGUgd2FpdCB0aW1lcyIsCiAgICAgICJBZnRlciB0aGUgY29udHJvbCBwbGFu" + - "ZSB3YXMgaGVhbHRoeSwgdGhlIGFnZW50IHJlZ2lzdGVyZWQgYW5kIGluc3RhbGxlZCB0aGUgc2Vw" + - "YXJhdGUgY29tcHV0ZS1wbGFuZSBzdGFjayB1c2luZyBleGFjdGx5IG9uZSBkb2N1bWVudGVkIGhh" + - "bmRvZmYiLAogICAgICAiVGhlIGFnZW50IGRpZCBub3QgbWl4IGEgQ0xJLWdlbmVyYXRlZCBjb250" + - "cm9sLXBsYW5lIHByb2ZpbGUgd2l0aCB0aGUgSGVsbWZpbGUgdmFsdWVzIGZsb3ciCiAgICBdCiAg" + - "fSwKICB7CiAgICAiaWQiOiAiUzMtY2xpLXByb2ZpbGUtd29ya2Zsb3ciLAogICAgInF1ZXN0aW9u" + - "IjogIlVzZSBudmNmLWNsaSB0byBpbnN0YWxsIG15IGNvbnRyb2wgcGxhbmUgYW5kIHRoZW4gcmVn" + - "aXN0ZXIgYW5kIGluc3RhbGwgYSBzZXBhcmF0ZSBjb21wdXRlIGNsdXN0ZXIuIFNob3cgZXZlcnkg" + - "cmVxdWlyZWQgZ2xvYmFsIGZsYWcuIiwKICAgICJleHBlY3RlZF9za2lsbCI6ICJudmNmLXNlbGYt" + - "bWFuYWdlZC1pbnN0YWxsYXRpb24iLAogICAgImdyb3VuZF90cnV0aCI6ICJUaGUgYWdlbnQgZmly" + - "c3QgcmFuIHRoZSBDTEkgY29udHJvbC1wbGFuZSBpbnN0YWxsIGFuZCByZXF1aXJlZCBpdHMgZ2Vu" + - "ZXJhdGVkIGNvbnRyb2wtcGxhbmUtcHJvZmlsZS55YW1sLiBJdCB0aGVuIHNob3dlZCBjb21wbGV0" + - "ZSByZWdpc3RlciBhbmQgaW5zdGFsbCBjb21tYW5kcywgZWFjaCBpbmNsdWRpbmcgLS1jb25maWcs" + - "IC0tY29udHJvbC1wbGFuZS1zdGFjaywgLS1jb21wdXRlLXBsYW5lLXN0YWNrLCAtLWVudiwgYW5k" + - "IC0tcGxhaW4gYmVmb3JlIHRoZSBjb21wdXRlLXBsYW5lIHN1YmNvbW1hbmQuIFJlZ2lzdHJhdGlv" + - "biBjb25zdW1lZCB0aGUgcHJvZmlsZSBhbmQgcHJvZHVjZWQgdGhlIHZhbHVlcyBmaWxlIGNvbnN1" + - "bWVkIGJ5IGluc3RhbGwuIEl0IGRpZCBub3Qgc3dpdGNoIHRvIHRoZSBIZWxtZmlsZSB2YWx1ZXMg" + - "aGFuZG9mZi4iLAogICAgImV4cGVjdGVkX2JlaGF2aW9yIjogWwogICAgICAiVGhlIGFnZW50IHJl" + - "YWQgdGhlIG52Y2Ytc2VsZi1tYW5hZ2VkLWluc3RhbGxhdGlvbiBTS0lMTC5tZCBiZWZvcmUgdGFr" + - "aW5nIGFjdGlvbiIsCiAgICAgICJUaGUgYWdlbnQgcmFuIG52Y2YtY2xpIHNlbGYtaG9zdGVkIGlu" + - "c3RhbGwgLS1jb250cm9sLXBsYW5lIGFuZCB2ZXJpZmllZCBkZXBsb3kvc3RhY2tzL3NlbGYtbWFu" + - "YWdlZC9vdXQvY29udHJvbC1wbGFuZS1wcm9maWxlLnlhbWwgZXhpc3RzIiwKICAgICAgIlRoZSBy" + - "ZWdpc3RlciBjb21tYW5kIGluY2x1ZGVkIC0tY29uZmlnLCAtLWNvbnRyb2wtcGxhbmUtc3RhY2ss" + - "IC0tY29tcHV0ZS1wbGFuZS1zdGFjaywgLS1lbnYsIGFuZCAtLXBsYWluIiwKICAgICAgIlRoZSBy" + - "ZWdpc3RlciBjb21tYW5kIGluY2x1ZGVkIC0tY29udHJvbC1wbGFuZS1wcm9maWxlLCAtLWNsdXN0" + - "ZXItbmFtZSwgLS1rdWJlLWNvbnRleHQsIC0tcmVnaW9uLCBhbmQgLS1vdXRwdXQiLAogICAgICAi" + - "VGhlIGluc3RhbGwgY29tbWFuZCByZXBlYXRlZCAtLWNvbmZpZywgLS1jb250cm9sLXBsYW5lLXN0" + - "YWNrLCAtLWNvbXB1dGUtcGxhbmUtc3RhY2ssIC0tZW52LCBhbmQgLS1wbGFpbiIsCiAgICAgICJU" + - "aGUgaW5zdGFsbCBjb21tYW5kIGluY2x1ZGVkIC0tdmFsdWVzLCAtLWt1YmUtY29udGV4dCwgYW5k" + - "IC0tY2x1c3Rlci1uYW1lIiwKICAgICAgIlRoZSBhZ2VudCBrZXB0IHRoZSBnZW5lcmF0ZWQgcHJv" + - "ZmlsZSBhcyB0aGUgZW5kcG9pbnQgc291cmNlIGFuZCBkaWQgbm90IG1peCBpbiBIZWxtZmlsZSB2" + - "YWx1ZXMgcmVnaXN0cmF0aW9uIgogICAgXQogIH0sCiAgewogICAgImlkIjogIlM0LW92ZXJyaWRl" + - "LWNhc3NhbmRyYS1yZXNvdXJjZXMiLAogICAgInF1ZXN0aW9uIjogIkNhc3NhbmRyYSBwb2RzIGtl" + - "ZXAgZ2V0dGluZyBPT01LaWxsZWQuIEluY3JlYXNlIG1lbW9yeSBsaW1pdHMgdG8gMTZHaS4iLAog" + + "aW9uIFNLSUxMLm1kIGFuZCByZWZlcmVuY2VzL2RlYnVnZ2luZy5tZCIsCiAgICAgICJUaGUgYWdl" + + "bnQgcmFuICdrdWJlY3RsIGRlc2NyaWJlIHBvZCcgdG8gc2VlIHRoZSBJbWFnZVB1bGxCYWNrT2Zm" + + "IGV2ZW50cyIsCiAgICAgICJUaGUgYWdlbnQgY2hlY2tlZCBmb3IgcHVsbCBzZWNyZXRzIGluIHRo" + + "ZSBjYXNzYW5kcmEtc3lzdGVtIG5hbWVzcGFjZSIsCiAgICAgICJUaGUgYWdlbnQgY2hlY2tlZCBp" + + "bWFnZVB1bGxTZWNyZXRzIG9uIHRoZSBwb2Qgc3BlYyBhbmQvb3Igc2VydmljZSBhY2NvdW50IiwK" + + "ICAgICAgIlRoZSBhZ2VudCBpZGVudGlmaWVkIHRoZSByb290IGNhdXNlIGFuZCBwcm92aWRlZCBh" + + "IHNwZWNpZmljIGZpeCIKICAgIF0KICB9LAogIHsKICAgICJpZCI6ICJTOS1yZW1vdGUtZWtzLXJv" + + "dXRpbmctYW5kLXNlY3JldHMiLAogICAgInF1ZXN0aW9uIjogIkNvbmZpZ3VyZSBhIHJlbW90ZSBF" + + "S1MgY29tcHV0ZSBjbHVzdGVyIHRvIHJlYWNoIG15IE5WQ0YgY29udHJvbCBwbGFuZSB0aHJvdWdo" + + "IGl0cyBFbnZveSBHYXRld2F5LCBpbmNsdWRpbmcgTkFUUyBhbmQgcHJpdmF0ZS1yZWdpc3RyeSBw" + + "dWxsIHNlY3JldHMuIiwKICAgICJleHBlY3RlZF9za2lsbCI6ICJudmNmLXNlbGYtbWFuYWdlZC1p" + + "bnN0YWxsYXRpb24iLAogICAgImdyb3VuZF90cnV0aCI6ICJUaGUgYWdlbnQgY29uZmlndXJlZCB0" + + "aGUgY29udHJvbCBwbGFuZSB0byB1c2UgdGhlIEVudm95IEdhdGV3YXkgY29udHJvbGxlciBpbiBl" + + "bnZveS1nYXRld2F5LXN5c3RlbSBhbmQgR2F0ZXdheS9udmNmLWdhdGV3YXkgaW4gZW52b3ktZ2F0" + + "ZXdheSwgc2V0IGFsbCBzaXggcmVtb3RlIHdvcmtlciBlbmRwb2ludHMgYW5kIHRoZSBncnBjLXBy" + + "b3h5IGxvY2FsIG92ZXJyaWRlLCBlbmFibGVkIE5WQ0YgYW5kIE5WQ1QgZ1JQQyByb3V0ZXMgcGx1" + + "cyBOQVRTLCBhbmQgZW5hYmxlZCBPcGVuQmFvIGlzc3VlciBkaXNjb3ZlcnkuIEl0IGNvbmZpZ3Vy" + + "ZWQgdGhlIGJhcmUtR2F0ZXdheSBDTEkgVVJMIGFuZCBIb3N0IGZpZWxkcyBpbmNsdWRpbmcgaWNt" + + "c19ob3N0LCBwbHVzIHRoZSBjb21wdXRlLXBsYW5lIElDTVMsIFJlVmFsLCBhbmQgTkFUUyBleHRl" + + "cm5hbCBVUkxzIGFuZCBob3N0IG92ZXJyaWRlcy4gSXQgY3JlYXRlZCB0aGUgcHVsbCBzZWNyZXQg" + + "aW4gYWxsIGNvbnRyb2wtcGxhbmUgbmFtZXNwYWNlcyBpbmNsdWRpbmcgY2VydC1tYW5hZ2VyLCBi" + + "dXQgb25seSBpbiBudmNhLW9wZXJhdG9yIG9uIHRoZSBjb21wdXRlIGNsdXN0ZXI7IHRoZSBjaGFy" + + "dCBhbmQgb3BlcmF0b3IgbWlycm9yIGl0IHRvIG52Y2Etc3lzdGVtLiIsCiAgICAiZXhwZWN0ZWRf" + + "YmVoYXZpb3IiOiBbCiAgICAgICJUaGUgYWdlbnQgcmVhZCB0aGUgbnZjZi1zZWxmLW1hbmFnZWQt" + + "aW5zdGFsbGF0aW9uIFNLSUxMLm1kIGJlZm9yZSB0YWtpbmcgYWN0aW9uIiwKICAgICAgIlRoZSBh" + + "Z2VudCBzZXQgaW5ncmVzcy5nYXRld2F5QXBpLmNvbnRyb2xsZXJOYW1lc3BhY2UgdG8gZW52b3kt" + + "Z2F0ZXdheS1zeXN0ZW0iLAogICAgICAiVGhlIGFnZW50IHNldCBzaGFyZWQsIGdycGMsIGFuZCBu" + + "YXRzIEdhdGV3YXkgcmVmZXJlbmNlcyB0byBuYW1lIG52Y2YtZ2F0ZXdheSBhbmQgbmFtZXNwYWNl" + + "IGVudm95LWdhdGV3YXkiLAogICAgICAiVGhlIGFnZW50IHNldCBnbG9iYWwud29ya2VyRW5kcG9p" + + "bnRzLm52Y2ZTZXJ2aWNlVVJMLCBudmNmR3JwY1NlcnZpY2VVUkwsIG52Y2ZOYXRzU2VydmljZVVS" + + "TCwgbnZjdFNlcnZpY2VVUkwsIG52Y3RHcnBjU2VydmljZVVSTCwgYW5kIGludm9jYXRpb25TZXJ2" + + "aWNlVVJMIiwKICAgICAgIlRoZSBhZ2VudCBzZXQgZ3JwY3Byb3h5Lm52Y2ZHcnBjU2VydmljZVVS" + + "TCB0byB0aGUgY29udHJvbC1wbGFuZS1sb2NhbCBBUEkgZ1JQQyBzZXJ2aWNlIiwKICAgICAgIlRo" + + "ZSBhZ2VudCBlbmFibGVkIGJvdGggaW5ncmVzcy5nYXRld2F5QXBpLnJvdXRlcy5udmNmQXBpLmdy" + + "cGMgYW5kIG52Y3RBcGkuZ3JwYyB3aXRoIHdvcmtlci1hcGkgYW5kIHdvcmtlci10YXNrcyBob3N0" + + "bmFtZXMiLAogICAgICAiVGhlIGFnZW50IGV4cGxpY2l0bHkgc2V0IGluZ3Jlc3MuZ2F0ZXdheUFw" + + "aS5yb3V0ZXMubmF0cy5lbmFibGVkIHRvIHRydWUiLAogICAgICAiVGhlIGFnZW50IHNldCBvcGVu" + + "YmFvLm1pZ3JhdGlvbnMuaXNzdWVyRGlzY292ZXJ5LmVuYWJsZWQgdG8gdHJ1ZSIsCiAgICAgICJU" + + "aGUgYWdlbnQgY29uZmlndXJlZCB0aGUgYmFyZS1HYXRld2F5IENMSSBVUkwgYW5kIEhvc3QgZmll" + + "bGRzLCBpbmNsdWRpbmcgc2NoZW1lLWxlc3MgaWNtc19ob3N0IiwKICAgICAgIlRoZSBhZ2VudCBj" + + "b25maWd1cmVkIGNvbXB1dGUtcGxhbmUgaWNtc1NlcnZpY2VVUkwsIGljbXNTZXJ2aWNlSG9zdEhl" + + "YWRlck92ZXJyaWRlLCByZXZhbFNlcnZpY2VVUkwsIHJldmFsU2VydmljZUhvc3RIZWFkZXJPdmVy" + + "cmlkZSwgbmF0c1VSTCwgYW5kIG5hdHNIb3N0T3ZlcnJpZGUiLAogICAgICAiVGhlIGNvbnRyb2wt" + + "cGxhbmUgcHVsbC1zZWNyZXQgbmFtZXNwYWNlIGxpc3QgaW5jbHVkZWQgY2VydC1tYW5hZ2VyIiwK" + + "ICAgICAgIlRoZSBjb21wdXRlLXBsYW5lIHNlY3JldCB3YXMgY3JlYXRlZCBvbmx5IGluIG52Y2Et" + + "b3BlcmF0b3IgYW5kIG1pcnJvcmVkIGJ5IHRoZSBjaGFydCBhbmQgb3BlcmF0b3IgdG8gbnZjYS1z" + + "eXN0ZW0iCiAgICBdCiAgfSwKICB7CiAgICAiaWQiOiAiRzExLXNwbGl0LXRlYXJkb3duIiwKICAg" + + "ICJxdWVzdGlvbiI6ICJUZWFyIGRvd24gbXkgc3BsaXQgY29udHJvbC1wbGFuZSBhbmQgY29tcHV0" + + "ZS1wbGFuZSBkZXBsb3ltZW50IHNhZmVseS4gU2hvdyBib3RoIHRoZSBIZWxtZmlsZSB2YWx1ZXMg" + + "YW5kIENMSSBwcm9maWxlIHZhcmlhbnRzLiIsCiAgICAiZXhwZWN0ZWRfc2tpbGwiOiAibnZjZi1z" + + "ZWxmLW1hbmFnZWQtaW5zdGFsbGF0aW9uIiwKICAgICJncm91bmRfdHJ1dGgiOiAiVGhlIEhlbG1m" + + "aWxlIGZsb3cgZGVzdHJveWVkIGFuZCBjb25maXJtZWQgZGVyZWdpc3RyYXRpb24gb2YgZXZlcnkg" + + "Y29tcHV0ZSBjbHVzdGVyIGJlZm9yZSBkZXN0cm95aW5nIHRoZSBjb250cm9sIHBsYW5lIGFuZCBw" + + "YXNzZWQgYm90aCBjb250cm9sLXBsYW5lIGt1YmVjb25maWcgZm9ybXMuIEJlZm9yZSBlaXRoZXIg" + + "aGFuZG9mZiBjb3VsZCBkZXN0cm95IHRoZSBjb250cm9sIHBsYW5lLCBib3RoIHRlYXJkb3duIGZs" + + "b3dzIHdhcm5lZCB0aGF0IG5hbWVzcGFjZSBwcmVzZXJ2YXRpb24gZG9lcyBub3QgcHJlc2VydmUg" + + "dGhlIEhlbG0gcmVsZWFzZSwgcmVxdWlyZWQgZWZmZWN0aXZlIGNlcnRNYW5hZ2VyLmVuYWJsZWQ6" + + "IGZhbHNlIGZvciBhbiBleHRlcm5hbCBpbnN0YWxsYXRpb24gYW5kIGFib3J0ZWQgaWYgdHJ1ZSwg" + + "YW5kIHZlcmlmaWVkIG1hdGNoaW5nIHJlbGVhc2Ugb3duZXJzaGlwIGZvciBhbiBpbnRlbmRlZCBz" + + "dGFjay1tYW5hZ2VkIHJlbGVhc2UsIGFib3J0aW5nIG9uIG1pc3Npbmcgb3IgbWlzbWF0Y2hlZCBl" + + "dmlkZW5jZS4gVGhleSBwcmVzZXJ2ZWQgdGhlIG5hbWVzcGFjZSBieSBkZWZhdWx0IGFuZCBkaWQg" + + "bm90IGRlbGV0ZSBpdCB3aXRob3V0IG5hbWVzcGFjZSBwcm92ZW5hbmNlIGFuZCBleHBsaWNpdCBj" + + "b25maXJtYXRpb24uIFRoZSBDTEkgZmxvdyByZXRhaW5lZCB0aGUgZ2VuZXJhdGVkIHJlZ2lzdHJh" + + "dGlvbiB2YWx1ZXMsIHVzZWQgdGhlIGZ1bGwtcHJlZml4IHN0YXR1cyBjb21tYW5kIHRvIGluc3Bl" + + "Y3QgbGl2ZSByZWdpc3RyYXRpb25zLCBzdGF0ZWQgdGhhdCBuYW1lZC1jbHVzdGVyIHBsYW4tb25s" + + "eSBkb2VzIG5vdCBxdWVyeSBJQ01TIGFuZCBjYW5ub3QgcHJlZGljdCBjb25kaXRpb25hbCBjb250" + + "cm9sLXBsYW5lIHJlbW92YWwsIGFuZCBnb3QgZXhwbGljaXQgY29uZmlybWF0aW9uIHdoZW4gdGhl" + + "IHRhcmdldCB3YXMgdGhlIGxhc3QgcmVnaXN0ZXJlZCBjbHVzdGVyLiBJdCB0aGVuIHByZXZpZXdl" + + "ZCBhbmQgcmFuIGRvd24gd2l0aCB0aGUgZnVsbCBwZXJzaXN0ZW50IHByZWZpeCwgcmVtb3Zpbmcg" + + "dGhlIGNvbnRyb2wgcGxhbmUgb25seSBhZnRlciB0aGUgbGFzdCByZWdpc3RyYXRpb24gd2FzIGdv" + + "bmUuIiwKICAgICJleHBlY3RlZF9iZWhhdmlvciI6IFsKICAgICAgIlRoZSBhZ2VudCByZWFkIHRo" + + "ZSBudmNmLXNlbGYtbWFuYWdlZC1pbnN0YWxsYXRpb24gU0tJTEwubWQgYmVmb3JlIHRha2luZyBh" + + "Y3Rpb24iLAogICAgICAiRm9yIGV2ZXJ5IGNvbXB1dGUgY2x1c3RlciwgdGhlIEhlbG1maWxlIGZs" + + "b3cgcmFuIHRoZSBjb21wdXRlLXBsYW5lIE1ha2VmaWxlIGRlc3Ryb3kgdGFyZ2V0IHdpdGggaXRz" + + "IGNvbXB1dGUga3ViZWNvbmZpZyIsCiAgICAgICJBZnRlciBjb25maXJtYXRpb24sIHRoZSBIZWxt" + + "ZmlsZSBmbG93IHJhbiBjbHVzdGVyIGRlbGV0ZSB3aXRoIGNsdXN0ZXJJRCBhbmQgbmNhSUQgYmVm" + + "b3JlIGNvbnRyb2wtcGxhbmUgZGVzdHJ1Y3Rpb24iLAogICAgICAiQmVmb3JlIGVpdGhlciBoYW5k" + + "b2ZmIGNvdWxkIGRlc3Ryb3kgdGhlIGNvbnRyb2wgcGxhbmUsIGJvdGggdGVhcmRvd24gZmxvd3Mg" + + "d2FybmVkIHRoYXQgbmFtZXNwYWNlIHByZXNlcnZhdGlvbiBkb2VzIG5vdCBwcmVzZXJ2ZSB0aGUg" + + "SGVsbSByZWxlYXNlLCByZXF1aXJlZCBlZmZlY3RpdmUgY2VydE1hbmFnZXIuZW5hYmxlZDogZmFs" + + "c2UgZm9yIGFuIGV4dGVybmFsIGluc3RhbGxhdGlvbiBhbmQgYWJvcnRlZCBpZiB0cnVlLCBhbmQg" + + "dmVyaWZpZWQgaW50ZW5kZWQgc3RhY2sgb3duZXJzaGlwIHdoZW4gZW5hYmxlZCwgYWJvcnRpbmcg" + + "b24gbWlzc2luZyBvciBtaXNtYXRjaGVkIGV2aWRlbmNlIiwKICAgICAgIkJvdGggdGVhcmRvd24g" + + "Zmxvd3MgcHJlc2VydmVkIHRoZSBjZXJ0LW1hbmFnZXIgbmFtZXNwYWNlIGJ5IGRlZmF1bHQgYW5k" + + "IGRpZCBub3QgZGVsZXRlIGl0IHdpdGhvdXQgbmFtZXNwYWNlIHByb3ZlbmFuY2UgYW5kIGV4cGxp" + + "Y2l0IGNvbmZpcm1hdGlvbiIsCiAgICAgICJUaGUgSGVsbWZpbGUgZmxvdyByYW4gY29udHJvbC1w" + + "bGFuZSBNYWtlZmlsZSBkZXN0cm95IHdpdGggYm90aCBLVUJFQ09ORklHIGFuZCBLVUJFQ09ORklH" + + "X0ZJTEUiLAogICAgICAiVGhlIENMSSBmbG93IHJldGFpbmVkIG91dC88Y2x1c3Rlci1uYW1lPi1y" + + "ZWdpc3Rlci12YWx1ZXMueWFtbCB1bnRpbCB0ZWFyZG93biBjb21wbGV0ZWQiLAogICAgICAiQmVm" + + "b3JlIHJlYWwgZG93biwgdGhlIENMSSBmbG93IHVzZWQgdGhlIGZ1bGwtcHJlZml4IHN0YXR1cyBj" + + "b21tYW5kIHRvIGluc3BlY3QgbGl2ZSByZWdpc3RyYXRpb25zLCBzdGF0ZWQgdGhhdCBuYW1lZC1j" + + "bHVzdGVyIHBsYW4tb25seSBkb2VzIG5vdCBxdWVyeSBJQ01TIGFuZCBjYW5ub3QgcHJlZGljdCBj" + + "b25kaXRpb25hbCBjb250cm9sLXBsYW5lIHJlbW92YWwsIHdhcm5lZCB3aGVuIHRoZSB0YXJnZXQg" + + "d2FzIHRoZSBsYXN0IHJlZ2lzdGVyZWQgY2x1c3RlciwgYW5kIGdvdCBleHBsaWNpdCBjb25maXJt" + + "YXRpb24iLAogICAgICAiVGhlIENMSSBmbG93IHVzZWQgLS1jb25maWcsIGJvdGggc3RhY2sgcGF0" + + "aHMsIC0tZW52LCAtLXBsYWluLCBib3RoIGNvbnRleHRzLCBhbmQgLS1pY21zLXVybCBmb3IgZG93" + + "biAtLXBsYW4tb25seSBhbmQgdGhlbiBkb3duIiwKICAgICAgIlRoZSBhZ2VudCBzdGF0ZWQgdGhh" + + "dCBkb3duIHVucmVnaXN0ZXJzIGVhY2ggY29tcHV0ZSBjbHVzdGVyIGFuZCByZW1vdmVzIHRoZSBj" + + "b250cm9sIHBsYW5lIG9ubHkgYWZ0ZXIgbm8gcmVnaXN0cmF0aW9ucyByZW1haW4iCiAgICBdCiAg" + + "fSwKICB7CiAgICAiaWQiOiAiUzEzLXZhbHVlcy1wcmVjZWRlbmNlIiwKICAgICJxdWVzdGlvbiI6" + + "ICJJIGFkZGVkIGEgdmFsdWUgdG8gbXkgZW52aXJvbm1lbnQgWUFNTCBidXQgaXQncyBiZWluZyBz" + + "aWxlbnRseSBpZ25vcmVkLiBIb3cgZG8gaGVsbWZpbGUgdmFsdWVzIHdvcmsgaW4gTlZDRj8iLAog" + "ICAgImV4cGVjdGVkX3NraWxsIjogIm52Y2Ytc2VsZi1tYW5hZ2VkLWluc3RhbGxhdGlvbiIsCiAg" + - "ICAiZ3JvdW5kX3RydXRoIjogIlRoZSBhZ2VudCBjYXB0dXJlZCB0aGUgYmFzZWxpbmUgd2l0aCBo" + - "ZWxtZmlsZSB0ZW1wbGF0ZSwgdGhlbiBtb2RpZmllZCB0aGUgY2Fzc2FuZHJhIHJlbGVhc2Ugd2l0" + - "aCBpbmxpbmUgdmFsdWVzIHJlLWluY2x1ZGluZyBnbG9iYWwueWFtbC5nb3RtcGwgYW5kIHNlY3Jl" + - "dHMgaW4gdGhlIHZhbHVlcyBibG9jay4gSXQgd2FybmVkIGFib3V0IHRoZSBZQU1MIG1lcmdlIGdv" + - "dGNoYSBhbmQgcHJldmlld2VkIHRoZSBkaWZmIGJlZm9yZSBhcHBseWluZy4iLAogICAgImV4cGVj" + - "dGVkX2JlaGF2aW9yIjogWwogICAgICAiVGhlIGFnZW50IHJlYWQgdGhlIG52Y2Ytc2VsZi1tYW5h" + - "Z2VkLWluc3RhbGxhdGlvbiBTS0lMTC5tZCBiZWZvcmUgdGFraW5nIGFjdGlvbiIsCiAgICAgICJU" + - "aGUgYWdlbnQgY2FwdHVyZWQgYmFzZWxpbmUgd2l0aCAnaGVsbWZpbGUgLS1zZWxlY3RvciBuYW1l" + - "PWNhc3NhbmRyYSB0ZW1wbGF0ZSciLAogICAgICAiVGhlIGFnZW50IG1vZGlmaWVkIHRoZSByZWxl" + - "YXNlIHZhbHVlcyBibG9jayB0byBpbmNsdWRlIHJlc291cmNlIGxpbWl0cyB3aXRoIG1lbW9yeTog" + - "MTZHaSIsCiAgICAgICJUaGUgYWdlbnQgcmUtaW5jbHVkZWQgZ2xvYmFsLnlhbWwuZ290bXBsIGFu" + - "ZCBzZWNyZXRzIGZpbGUgaW4gdGhlIHZhbHVlczogYmxvY2sgKG5vdCBqdXN0IHRoZSBvdmVycmlk" + - "ZSBhbG9uZSkiLAogICAgICAiVGhlIGFnZW50IHdhcm5lZCBhYm91dCB0aGUgWUFNTCBtZXJnZSBn" + - "b3RjaGE6IHNwZWNpZnlpbmcgdmFsdWVzOiByZXBsYWNlcyB0aGUgdGVtcGxhdGUncyB2YWx1ZXMg" + - "bGlzdCBlbnRpcmVseSIsCiAgICAgICJUaGUgYWdlbnQgcHJldmlld2VkIHRoZSBjaGFuZ2Ugd2l0" + - "aCBoZWxtZmlsZSB0ZW1wbGF0ZSBhbmQgZGlmZiBiZWZvcmUgYXBwbHlpbmcgd2l0aCBzeW5jIgog" + - "ICAgXQogIH0sCiAgewogICAgImlkIjogIlM2LXNlbGVjdGl2ZS1zeW5jLWRlcGVuZGVuY2llcyIs" + - "CiAgICAicXVlc3Rpb24iOiAiRGVwbG95IG9ubHkgdGhlIE5WQ0YgZGVwZW5kZW5jaWVzIC0tIE5B" + - "VFMsIENhc3NhbmRyYSwgYW5kIE9wZW5CYW8gLS0gZm9yIHRlc3RpbmciLAogICAgImV4cGVjdGVk" + - "X3NraWxsIjogIm52Y2Ytc2VsZi1tYW5hZ2VkLWluc3RhbGxhdGlvbiIsCiAgICAiZ3JvdW5kX3Ry" + - "dXRoIjogIlRoZSBhZ2VudCB1c2VkIHRoZSBoZWxtZmlsZSBzZWxlY3RvciB0byBkZXBsb3kgb25s" + - "eSB0aGUgZGVwZW5kZW5jaWVzIHJlbGVhc2UgZ3JvdXAsIHRoZW4gdmVyaWZpZWQgcG9kcyBpbiB0" + - "aGUgZGVwZW5kZW5jeSBuYW1lc3BhY2VzLiIsCiAgICAiZXhwZWN0ZWRfYmVoYXZpb3IiOiBbCiAg" + - "ICAgICJUaGUgYWdlbnQgcmVhZCB0aGUgbnZjZi1zZWxmLW1hbmFnZWQtaW5zdGFsbGF0aW9uIFNL" + - "SUxMLm1kIGJlZm9yZSB0YWtpbmcgYWN0aW9uIiwKICAgICAgIlRoZSBhZ2VudCB1c2VkICdoZWxt" + - "ZmlsZSAtLXNlbGVjdG9yIHJlbGVhc2UtZ3JvdXA9ZGVwZW5kZW5jaWVzIHN5bmMnIHRvIGRlcGxv" + - "eSBvbmx5IGRlcGVuZGVuY2llcyIsCiAgICAgICJUaGUgYWdlbnQgdmVyaWZpZWQgcG9kcyBpbiBk" + - "ZXBlbmRlbmN5IG5hbWVzcGFjZXMgKG5hdHMtc3lzdGVtLCBjYXNzYW5kcmEtc3lzdGVtLCB2YXVs" + - "dC1zeXN0ZW0pIiwKICAgICAgIlRoZSBhZ2VudCBwcm92aWRlZCB0aGUgc2VsZWN0b3IgcmVmZXJl" + - "bmNlIGZvciB0YXJnZXRpbmcgb3RoZXIgcmVsZWFzZSBncm91cHMiCiAgICBdCiAgfSwKICB7CiAg" + - "ICAiaWQiOiAiUzctZGVidWctaW1hZ2VwdWxsYmFja29mZiIsCiAgICAicXVlc3Rpb24iOiAiQ2Fz" + - "c2FuZHJhIHBvZHMgYXJlIGluIEltYWdlUHVsbEJhY2tPZmYgd2l0aCA0MDEgVW5hdXRob3JpemVk" + - "IiwKICAgICJleHBlY3RlZF9za2lsbCI6ICJudmNmLXNlbGYtbWFuYWdlZC1pbnN0YWxsYXRpb24i" + - "LAogICAgImdyb3VuZF90cnV0aCI6ICJUaGUgYWdlbnQgcmVhZCB0aGUgZGVidWdnaW5nIHJlZmVy" + - "ZW5jZSBhbmQgcmFuIGRpYWdub3N0aWMgY29tbWFuZHMgdG8gaWRlbnRpZnkgdGhlIHJvb3QgY2F1" + - "c2U6IGRlc2NyaWJlZCB0aGUgcG9kLCBjaGVja2VkIGZvciBwdWxsIHNlY3JldHMsIGNoZWNrZWQg" + - "aW1hZ2VQdWxsU2VjcmV0cyBvbiB0aGUgcG9kIHNwZWMgYW5kIHNlcnZpY2UgYWNjb3VudC4gSXQg" + - "aWRlbnRpZmllZCB0aGUgaXNzdWUgKG1pc3Npbmcgc2VjcmV0LCB3cm9uZyBjcmVkZW50aWFscywg" + - "b3IgbWlzc2luZyBpbWFnZVB1bGxTZWNyZXRzKSBhbmQgcHJvdmlkZWQgdGhlIGNvcnJlY3QgZml4" + - "LiIsCiAgICAiZXhwZWN0ZWRfYmVoYXZpb3IiOiBbCiAgICAgICJUaGUgYWdlbnQgcmVhZCB0aGUg" + - "bnZjZi1zZWxmLW1hbmFnZWQtaW5zdGFsbGF0aW9uIFNLSUxMLm1kIGFuZCByZWZlcmVuY2VzL2Rl" + - "YnVnZ2luZy5tZCIsCiAgICAgICJUaGUgYWdlbnQgcmFuICdrdWJlY3RsIGRlc2NyaWJlIHBvZCcg" + - "dG8gc2VlIHRoZSBJbWFnZVB1bGxCYWNrT2ZmIGV2ZW50cyIsCiAgICAgICJUaGUgYWdlbnQgY2hl" + - "Y2tlZCBmb3IgcHVsbCBzZWNyZXRzIGluIHRoZSBjYXNzYW5kcmEtc3lzdGVtIG5hbWVzcGFjZSIs" + - "CiAgICAgICJUaGUgYWdlbnQgY2hlY2tlZCBpbWFnZVB1bGxTZWNyZXRzIG9uIHRoZSBwb2Qgc3Bl" + - "YyBhbmQvb3Igc2VydmljZSBhY2NvdW50IiwKICAgICAgIlRoZSBhZ2VudCBpZGVudGlmaWVkIHRo" + - "ZSByb290IGNhdXNlIGFuZCBwcm92aWRlZCBhIHNwZWNpZmljIGZpeCIKICAgIF0KICB9LAogIHsK" + - "ICAgICJpZCI6ICJTOS1yZW1vdGUtZWtzLXJvdXRpbmctYW5kLXNlY3JldHMiLAogICAgInF1ZXN0" + - "aW9uIjogIkNvbmZpZ3VyZSBhIHJlbW90ZSBFS1MgY29tcHV0ZSBjbHVzdGVyIHRvIHJlYWNoIG15" + - "IE5WQ0YgY29udHJvbCBwbGFuZSB0aHJvdWdoIGl0cyBFbnZveSBHYXRld2F5LCBpbmNsdWRpbmcg" + - "TkFUUyBhbmQgcHJpdmF0ZS1yZWdpc3RyeSBwdWxsIHNlY3JldHMuIiwKICAgICJleHBlY3RlZF9z" + - "a2lsbCI6ICJudmNmLXNlbGYtbWFuYWdlZC1pbnN0YWxsYXRpb24iLAogICAgImdyb3VuZF90cnV0" + - "aCI6ICJUaGUgYWdlbnQgY29uZmlndXJlZCB0aGUgY29udHJvbCBwbGFuZSB0byB1c2UgdGhlIEVu" + - "dm95IEdhdGV3YXkgY29udHJvbGxlciBpbiBlbnZveS1nYXRld2F5LXN5c3RlbSBhbmQgR2F0ZXdh" + - "eS9udmNmLWdhdGV3YXkgaW4gZW52b3ktZ2F0ZXdheSwgc2V0IGFsbCBzaXggcmVtb3RlIHdvcmtl" + - "ciBlbmRwb2ludHMgYW5kIHRoZSBncnBjLXByb3h5IGxvY2FsIG92ZXJyaWRlLCBlbmFibGVkIE5W" + - "Q0YgYW5kIE5WQ1QgZ1JQQyByb3V0ZXMgcGx1cyBOQVRTLCBhbmQgZW5hYmxlZCBPcGVuQmFvIGlz" + - "c3VlciBkaXNjb3ZlcnkuIEl0IGNvbmZpZ3VyZWQgdGhlIGJhcmUtR2F0ZXdheSBDTEkgVVJMIGFu" + - "ZCBIb3N0IGZpZWxkcyBpbmNsdWRpbmcgaWNtc19ob3N0LCBwbHVzIHRoZSBjb21wdXRlLXBsYW5l" + - "IElDTVMsIFJlVmFsLCBhbmQgTkFUUyBleHRlcm5hbCBVUkxzIGFuZCBob3N0IG92ZXJyaWRlcy4g" + - "SXQgY3JlYXRlZCB0aGUgcHVsbCBzZWNyZXQgaW4gYWxsIGNvbnRyb2wtcGxhbmUgbmFtZXNwYWNl" + - "cyBpbmNsdWRpbmcgY2VydC1tYW5hZ2VyLCBidXQgb25seSBpbiBudmNhLW9wZXJhdG9yIG9uIHRo" + - "ZSBjb21wdXRlIGNsdXN0ZXI7IHRoZSBjaGFydCBhbmQgb3BlcmF0b3IgbWlycm9yIGl0IHRvIG52" + - "Y2Etc3lzdGVtLiIsCiAgICAiZXhwZWN0ZWRfYmVoYXZpb3IiOiBbCiAgICAgICJUaGUgYWdlbnQg" + - "cmVhZCB0aGUgbnZjZi1zZWxmLW1hbmFnZWQtaW5zdGFsbGF0aW9uIFNLSUxMLm1kIGJlZm9yZSB0" + - "YWtpbmcgYWN0aW9uIiwKICAgICAgIlRoZSBhZ2VudCBzZXQgaW5ncmVzcy5nYXRld2F5QXBpLmNv" + - "bnRyb2xsZXJOYW1lc3BhY2UgdG8gZW52b3ktZ2F0ZXdheS1zeXN0ZW0iLAogICAgICAiVGhlIGFn" + - "ZW50IHNldCBzaGFyZWQsIGdycGMsIGFuZCBuYXRzIEdhdGV3YXkgcmVmZXJlbmNlcyB0byBuYW1l" + - "IG52Y2YtZ2F0ZXdheSBhbmQgbmFtZXNwYWNlIGVudm95LWdhdGV3YXkiLAogICAgICAiVGhlIGFn" + - "ZW50IHNldCBnbG9iYWwud29ya2VyRW5kcG9pbnRzLm52Y2ZTZXJ2aWNlVVJMLCBudmNmR3JwY1Nl" + - "cnZpY2VVUkwsIG52Y2ZOYXRzU2VydmljZVVSTCwgbnZjdFNlcnZpY2VVUkwsIG52Y3RHcnBjU2Vy" + - "dmljZVVSTCwgYW5kIGludm9jYXRpb25TZXJ2aWNlVVJMIiwKICAgICAgIlRoZSBhZ2VudCBzZXQg" + - "Z3JwY3Byb3h5Lm52Y2ZHcnBjU2VydmljZVVSTCB0byB0aGUgY29udHJvbC1wbGFuZS1sb2NhbCBB" + - "UEkgZ1JQQyBzZXJ2aWNlIiwKICAgICAgIlRoZSBhZ2VudCBlbmFibGVkIGJvdGggaW5ncmVzcy5n" + - "YXRld2F5QXBpLnJvdXRlcy5udmNmQXBpLmdycGMgYW5kIG52Y3RBcGkuZ3JwYyB3aXRoIHdvcmtl" + - "ci1hcGkgYW5kIHdvcmtlci10YXNrcyBob3N0bmFtZXMiLAogICAgICAiVGhlIGFnZW50IGV4cGxp" + - "Y2l0bHkgc2V0IGluZ3Jlc3MuZ2F0ZXdheUFwaS5yb3V0ZXMubmF0cy5lbmFibGVkIHRvIHRydWUi" + - "LAogICAgICAiVGhlIGFnZW50IHNldCBvcGVuYmFvLm1pZ3JhdGlvbnMuaXNzdWVyRGlzY292ZXJ5" + - "LmVuYWJsZWQgdG8gdHJ1ZSIsCiAgICAgICJUaGUgYWdlbnQgY29uZmlndXJlZCB0aGUgYmFyZS1H" + - "YXRld2F5IENMSSBVUkwgYW5kIEhvc3QgZmllbGRzLCBpbmNsdWRpbmcgc2NoZW1lLWxlc3MgaWNt" + - "c19ob3N0IiwKICAgICAgIlRoZSBhZ2VudCBjb25maWd1cmVkIGNvbXB1dGUtcGxhbmUgaWNtc1Nl" + - "cnZpY2VVUkwsIGljbXNTZXJ2aWNlSG9zdEhlYWRlck92ZXJyaWRlLCByZXZhbFNlcnZpY2VVUkws" + - "IHJldmFsU2VydmljZUhvc3RIZWFkZXJPdmVycmlkZSwgbmF0c1VSTCwgYW5kIG5hdHNIb3N0T3Zl" + - "cnJpZGUiLAogICAgICAiVGhlIGNvbnRyb2wtcGxhbmUgcHVsbC1zZWNyZXQgbmFtZXNwYWNlIGxp" + - "c3QgaW5jbHVkZWQgY2VydC1tYW5hZ2VyIiwKICAgICAgIlRoZSBjb21wdXRlLXBsYW5lIHNlY3Jl" + - "dCB3YXMgY3JlYXRlZCBvbmx5IGluIG52Y2Etb3BlcmF0b3IgYW5kIG1pcnJvcmVkIGJ5IHRoZSBj" + - "aGFydCBhbmQgb3BlcmF0b3IgdG8gbnZjYS1zeXN0ZW0iCiAgICBdCiAgfSwKICB7CiAgICAiaWQi" + - "OiAiRzExLXNwbGl0LXRlYXJkb3duIiwKICAgICJxdWVzdGlvbiI6ICJUZWFyIGRvd24gbXkgc3Bs" + - "aXQgY29udHJvbC1wbGFuZSBhbmQgY29tcHV0ZS1wbGFuZSBkZXBsb3ltZW50IHNhZmVseS4gU2hv" + - "dyBib3RoIHRoZSBIZWxtZmlsZSB2YWx1ZXMgYW5kIENMSSBwcm9maWxlIHZhcmlhbnRzLiIsCiAg" + - "ICAiZXhwZWN0ZWRfc2tpbGwiOiAibnZjZi1zZWxmLW1hbmFnZWQtaW5zdGFsbGF0aW9uIiwKICAg" + - "ICJncm91bmRfdHJ1dGgiOiAiVGhlIEhlbG1maWxlIGZsb3cgZGVzdHJveWVkIGFuZCBjb25maXJt" + - "ZWQgZGVyZWdpc3RyYXRpb24gb2YgZXZlcnkgY29tcHV0ZSBjbHVzdGVyIGJlZm9yZSBkZXN0cm95" + - "aW5nIHRoZSBjb250cm9sIHBsYW5lIGFuZCBwYXNzZWQgYm90aCBjb250cm9sLXBsYW5lIGt1YmVj" + - "b25maWcgZm9ybXMuIEJlZm9yZSBlaXRoZXIgaGFuZG9mZiBjb3VsZCBkZXN0cm95IHRoZSBjb250" + - "cm9sIHBsYW5lLCBib3RoIHRlYXJkb3duIGZsb3dzIHdhcm5lZCB0aGF0IG5hbWVzcGFjZSBwcmVz" + - "ZXJ2YXRpb24gZG9lcyBub3QgcHJlc2VydmUgdGhlIEhlbG0gcmVsZWFzZSwgcmVxdWlyZWQgZWZm" + - "ZWN0aXZlIGNlcnRNYW5hZ2VyLmVuYWJsZWQ6IGZhbHNlIGZvciBhbiBleHRlcm5hbCBpbnN0YWxs" + - "YXRpb24gYW5kIGFib3J0ZWQgaWYgdHJ1ZSwgYW5kIHZlcmlmaWVkIG1hdGNoaW5nIHJlbGVhc2Ug" + - "b3duZXJzaGlwIGZvciBhbiBpbnRlbmRlZCBzdGFjay1tYW5hZ2VkIHJlbGVhc2UsIGFib3J0aW5n" + - "IG9uIG1pc3Npbmcgb3IgbWlzbWF0Y2hlZCBldmlkZW5jZS4gVGhleSBwcmVzZXJ2ZWQgdGhlIG5h" + - "bWVzcGFjZSBieSBkZWZhdWx0IGFuZCBkaWQgbm90IGRlbGV0ZSBpdCB3aXRob3V0IG5hbWVzcGFj" + - "ZSBwcm92ZW5hbmNlIGFuZCBleHBsaWNpdCBjb25maXJtYXRpb24uIFRoZSBDTEkgZmxvdyByZXRh" + - "aW5lZCB0aGUgZ2VuZXJhdGVkIHJlZ2lzdHJhdGlvbiB2YWx1ZXMsIHVzZWQgdGhlIGZ1bGwtcHJl" + - "Zml4IHN0YXR1cyBjb21tYW5kIHRvIGluc3BlY3QgbGl2ZSByZWdpc3RyYXRpb25zLCBzdGF0ZWQg" + - "dGhhdCBuYW1lZC1jbHVzdGVyIHBsYW4tb25seSBkb2VzIG5vdCBxdWVyeSBJQ01TIGFuZCBjYW5u" + - "b3QgcHJlZGljdCBjb25kaXRpb25hbCBjb250cm9sLXBsYW5lIHJlbW92YWwsIGFuZCBnb3QgZXhw" + - "bGljaXQgY29uZmlybWF0aW9uIHdoZW4gdGhlIHRhcmdldCB3YXMgdGhlIGxhc3QgcmVnaXN0ZXJl" + - "ZCBjbHVzdGVyLiBJdCB0aGVuIHByZXZpZXdlZCBhbmQgcmFuIGRvd24gd2l0aCB0aGUgZnVsbCBw" + - "ZXJzaXN0ZW50IHByZWZpeCwgcmVtb3ZpbmcgdGhlIGNvbnRyb2wgcGxhbmUgb25seSBhZnRlciB0" + - "aGUgbGFzdCByZWdpc3RyYXRpb24gd2FzIGdvbmUuIiwKICAgICJleHBlY3RlZF9iZWhhdmlvciI6" + - "IFsKICAgICAgIlRoZSBhZ2VudCByZWFkIHRoZSBudmNmLXNlbGYtbWFuYWdlZC1pbnN0YWxsYXRp" + - "b24gU0tJTEwubWQgYmVmb3JlIHRha2luZyBhY3Rpb24iLAogICAgICAiRm9yIGV2ZXJ5IGNvbXB1" + - "dGUgY2x1c3RlciwgdGhlIEhlbG1maWxlIGZsb3cgcmFuIHRoZSBjb21wdXRlLXBsYW5lIE1ha2Vm" + - "aWxlIGRlc3Ryb3kgdGFyZ2V0IHdpdGggaXRzIGNvbXB1dGUga3ViZWNvbmZpZyIsCiAgICAgICJB" + - "ZnRlciBjb25maXJtYXRpb24sIHRoZSBIZWxtZmlsZSBmbG93IHJhbiBjbHVzdGVyIGRlbGV0ZSB3" + - "aXRoIGNsdXN0ZXJJRCBhbmQgbmNhSUQgYmVmb3JlIGNvbnRyb2wtcGxhbmUgZGVzdHJ1Y3Rpb24i" + - "LAogICAgICAiQmVmb3JlIGVpdGhlciBoYW5kb2ZmIGNvdWxkIGRlc3Ryb3kgdGhlIGNvbnRyb2wg" + - "cGxhbmUsIGJvdGggdGVhcmRvd24gZmxvd3Mgd2FybmVkIHRoYXQgbmFtZXNwYWNlIHByZXNlcnZh" + - "dGlvbiBkb2VzIG5vdCBwcmVzZXJ2ZSB0aGUgSGVsbSByZWxlYXNlLCByZXF1aXJlZCBlZmZlY3Rp" + - "dmUgY2VydE1hbmFnZXIuZW5hYmxlZDogZmFsc2UgZm9yIGFuIGV4dGVybmFsIGluc3RhbGxhdGlv" + - "biBhbmQgYWJvcnRlZCBpZiB0cnVlLCBhbmQgdmVyaWZpZWQgaW50ZW5kZWQgc3RhY2sgb3duZXJz" + - "aGlwIHdoZW4gZW5hYmxlZCwgYWJvcnRpbmcgb24gbWlzc2luZyBvciBtaXNtYXRjaGVkIGV2aWRl" + - "bmNlIiwKICAgICAgIkJvdGggdGVhcmRvd24gZmxvd3MgcHJlc2VydmVkIHRoZSBjZXJ0LW1hbmFn" + - "ZXIgbmFtZXNwYWNlIGJ5IGRlZmF1bHQgYW5kIGRpZCBub3QgZGVsZXRlIGl0IHdpdGhvdXQgbmFt" + - "ZXNwYWNlIHByb3ZlbmFuY2UgYW5kIGV4cGxpY2l0IGNvbmZpcm1hdGlvbiIsCiAgICAgICJUaGUg" + - "SGVsbWZpbGUgZmxvdyByYW4gY29udHJvbC1wbGFuZSBNYWtlZmlsZSBkZXN0cm95IHdpdGggYm90" + - "aCBLVUJFQ09ORklHIGFuZCBLVUJFQ09ORklHX0ZJTEUiLAogICAgICAiVGhlIENMSSBmbG93IHJl" + - "dGFpbmVkIG91dC88Y2x1c3Rlci1uYW1lPi1yZWdpc3Rlci12YWx1ZXMueWFtbCB1bnRpbCB0ZWFy" + - "ZG93biBjb21wbGV0ZWQiLAogICAgICAiQmVmb3JlIHJlYWwgZG93biwgdGhlIENMSSBmbG93IHVz" + - "ZWQgdGhlIGZ1bGwtcHJlZml4IHN0YXR1cyBjb21tYW5kIHRvIGluc3BlY3QgbGl2ZSByZWdpc3Ry" + - "YXRpb25zLCBzdGF0ZWQgdGhhdCBuYW1lZC1jbHVzdGVyIHBsYW4tb25seSBkb2VzIG5vdCBxdWVy" + - "eSBJQ01TIGFuZCBjYW5ub3QgcHJlZGljdCBjb25kaXRpb25hbCBjb250cm9sLXBsYW5lIHJlbW92" + - "YWwsIHdhcm5lZCB3aGVuIHRoZSB0YXJnZXQgd2FzIHRoZSBsYXN0IHJlZ2lzdGVyZWQgY2x1c3Rl" + - "ciwgYW5kIGdvdCBleHBsaWNpdCBjb25maXJtYXRpb24iLAogICAgICAiVGhlIENMSSBmbG93IHVz" + - "ZWQgLS1jb25maWcsIGJvdGggc3RhY2sgcGF0aHMsIC0tZW52LCAtLXBsYWluLCBib3RoIGNvbnRl" + - "eHRzLCBhbmQgLS1pY21zLXVybCBmb3IgZG93biAtLXBsYW4tb25seSBhbmQgdGhlbiBkb3duIiwK" + - "ICAgICAgIlRoZSBhZ2VudCBzdGF0ZWQgdGhhdCBkb3duIHVucmVnaXN0ZXJzIGVhY2ggY29tcHV0" + - "ZSBjbHVzdGVyIGFuZCByZW1vdmVzIHRoZSBjb250cm9sIHBsYW5lIG9ubHkgYWZ0ZXIgbm8gcmVn" + - "aXN0cmF0aW9ucyByZW1haW4iCiAgICBdCiAgfSwKICB7CiAgICAiaWQiOiAiUzEzLXZhbHVlcy1w" + - "cmVjZWRlbmNlIiwKICAgICJxdWVzdGlvbiI6ICJJIGFkZGVkIGEgdmFsdWUgdG8gbXkgZW52aXJv" + - "bm1lbnQgWUFNTCBidXQgaXQncyBiZWluZyBzaWxlbnRseSBpZ25vcmVkLiBIb3cgZG8gaGVsbWZp" + - "bGUgdmFsdWVzIHdvcmsgaW4gTlZDRj8iLAogICAgImV4cGVjdGVkX3NraWxsIjogIm52Y2Ytc2Vs" + - "Zi1tYW5hZ2VkLWluc3RhbGxhdGlvbiIsCiAgICAiZ3JvdW5kX3RydXRoIjogIlRoZSBhZ2VudCBl" + - "eHBsYWluZWQgdGhlIHZhbHVlcyBwcmVjZWRlbmNlIGNoYWluIChiYXNlLnlhbWwsIGVudiBZQU1M" + - "LCBnbG9iYWwueWFtbC5nb3RtcGwsIHNlY3JldHMsIGlubGluZSB2YWx1ZXMpLCBpZGVudGlmaWVk" + - "IHRoYXQgZ2xvYmFsLnlhbWwuZ290bXBsIG9ubHkgcGFzc2VzIHRocm91Z2ggc3BlY2lmaWMga2V5" + - "cywgYW5kIGV4cGxhaW5lZCB0aGF0IGFyYml0cmFyeSBjaGFydCB2YWx1ZXMgbXVzdCB1c2UgcmVs" + - "ZWFzZSBpbmxpbmUgdmFsdWVzIGJsb2Nrcy4iLAogICAgImV4cGVjdGVkX2JlaGF2aW9yIjogWwog" + - "ICAgICAiVGhlIGFnZW50IHJlYWQgdGhlIG52Y2Ytc2VsZi1tYW5hZ2VkLWluc3RhbGxhdGlvbiBT" + - "S0lMTC5tZCBiZWZvcmUgdGFraW5nIGFjdGlvbiIsCiAgICAgICJUaGUgYWdlbnQgZXhwbGFpbmVk" + - "IHRoZSB2YWx1ZXMgcHJlY2VkZW5jZSBjaGFpbjogYmFzZS55YW1sIC0+IGVudiBZQU1MIC0+IGds" + - "b2JhbC55YW1sLmdvdG1wbCAtPiBzZWNyZXRzIC0+IGlubGluZSB2YWx1ZXMiLAogICAgICAiVGhl" + - "IGFnZW50IGlkZW50aWZpZWQgdGhhdCBnbG9iYWwueWFtbC5nb3RtcGwgb25seSBwYXNzZXMgdGhy" + - "b3VnaCBzcGVjaWZpYyBrZXlzIChjYXNzYW5kcmEucmVwbGljYUNvdW50LCBnbG9iYWwuc3RvcmFn" + - "ZUNsYXNzLCBnbG9iYWwuaW1hZ2UsIGV0Yy4pIiwKICAgICAgIlRoZSBhZ2VudCBleHBsYWluZWQg" + - "dGhhdCBhcmJpdHJhcnkgY2hhcnQgdmFsdWVzIG11c3QgYmUgc2V0IHZpYSByZWxlYXNlIGlubGlu" + - "ZSB2YWx1ZXM6IGJsb2NrcyIsCiAgICAgICJUaGUgYWdlbnQgbGlzdGVkIGV4YW1wbGVzIG9mIHZh" + - "bHVlcyB0aGF0IGFyZSBwcm9wYWdhdGVkIGFuZCBhcmJpdHJhcnkgY2hhcnQgdmFsdWVzIHRoYXQg" + - "cmVxdWlyZSBhIHJlbGVhc2UgdmFsdWVzIGJsb2NrIgogICAgXQogIH0sCiAgewogICAgImlkIjog" + - "IlMxNC1lbmFibGUtbGxtLWZ1bmN0aW9ucyIsCiAgICAicXVlc3Rpb24iOiAiSSBuZWVkIHRvIGRl" + - "cGxveSBmdW5jdGlvblR5cGU6IExMTSBmdW5jdGlvbnMgb24gYSBzZWxmLW1hbmFnZWQsIHNpbmds" + - "ZS1ub2RlIGlzb2xhdGVkIHRlc3QgY2x1c3Rlci4gV2hhdCBIZWxtZmlsZSBjb25maWd1cmF0aW9u" + - "IGFuZCB2ZXJpZmljYXRpb24gc3RlcHMgZG8gSSBuZWVkPyIsCiAgICAiZXhwZWN0ZWRfc2tpbGwi" + - "OiAibnZjZi1zZWxmLW1hbmFnZWQtaW5zdGFsbGF0aW9uIiwKICAgICJncm91bmRfdHJ1dGgiOiAi" + - "VGhlIGFnZW50IGVuYWJsZWQgdGhlIExMTSBhZGRvbiB3aXRoIGFkZG9ucy5sbG0uZW5hYmxlZCwg" + - "Y29uZmlndXJlZCBzaW5nbGUgcmVwbGljYXMgZm9yIHRoZSBMTE0gQVBJIEdhdGV3YXkgYW5kIHJl" + - "cXVlc3Qgcm91dGVyLCBhbmQgZXhwbGFpbmVkIHRoYXQgdGhlIHJlcXVlc3Qgcm91dGVyIHVzZXMg" + - "cG93ZXItb2YtdHdvIHdoZW4gbm8gbG9hZC1iYWxhbmNlciBjb25maWd1cmF0aW9uIGlzIHNldC4g" + - "SXQgbGlua2VkIHRoZSBjYW5vbmljYWwgcmVxdWVzdC1yb3V0ZXIgbG9hZC1iYWxhbmNpbmcgZ3Vp" + - "ZGUgZm9yIG90aGVyIHJvdXRpbmcgbWV0aG9kcy4gRm9yIHRoZSBpc29sYXRlZCBwbGFpbnRleHQg" + - "Y2x1c3RlciwgaXQgcmVsaWVkIG9uIHRoZSBzZWxmLW1hbmFnZWQgc3RhY2sgZGVmYXVsdCBmb3Ig" + - "dGhlIGJ1bmRsZWQgZ2F0ZXdheS10by1BUEkgYXV0aGVudGljYXRpb24gaG9wIGFuZCBpbmNsdWRl" + - "ZCBhZ2VudENvbmZpZy5tZXJnZUNvbmZpZy53b3JrbG9hZC5zdGFyZ2F0ZVFVSUNJbnNlY3VyZSBm" + - "b3IgdGhlIHNlcGFyYXRlIHdvcmtlciB0cmFuc3BvcnQsIHdpdGggYSB3YXJuaW5nIHRoYXQgcGxh" + - "aW50ZXh0IHdvcmtlciB0cmFuc3BvcnQgaXMgbm90IGZvciBwcm9kdWN0aW9uLiBJdCByZWZyZXNo" + - "ZWQgdGhlIGNvbXB1dGUgcGxhbmUgdXNpbmcgdGhlIHNhbWUgaGFuZG9mZiB1c2VkIGR1cmluZyBp" + - "bnN0YWxsYXRpb24sIHJlZGVwbG95ZWQgZXhpc3RpbmcgTExNIGZ1bmN0aW9ucywgYW5kIHZlcmlm" + - "aWVkIHRoZSBMTE0gZGVwbG95bWVudHMsIHJvdXRlLCBhbmQgbGxtLXdvcmtlciBzaWRlY2FyLiIs" + - "CiAgICAiZXhwZWN0ZWRfYmVoYXZpb3IiOiBbCiAgICAgICJUaGUgYWdlbnQgdXNlZCBhZGRvbnMu" + - "bGxtLmVuYWJsZWQsIG5vdCB0aGUgb2Jzb2xldGUgbGxtLmVuYWJsZWQgcGF0aCIsCiAgICAgICJU" + - "aGUgYWdlbnQgc2V0IGdhdGV3YXkgYW5kIHJlcXVlc3RSb3V0ZXIgcmVwbGljYUNvdW50IHRvIDEg" + - "Zm9yIHRoZSBzaW5nbGUtbm9kZSB0ZXN0IGNsdXN0ZXIiLAogICAgICAiVGhlIGFnZW50IHN0YXRl" + - "ZCB0aGF0IHRoZSByZXF1ZXN0IHJvdXRlciBkZWZhdWx0cyB0byBwb3dlci1vZi10d28gYW5kIGxp" + - "bmtlZCB0aGUgY2Fub25pY2FsIHJlcXVlc3Qtcm91dGVyIGxvYWQtYmFsYW5jaW5nIGd1aWRlIGZv" + - "ciBvdGhlciBtZXRob2RzIiwKICAgICAgIlRoZSBhZ2VudCBleHBsYWluZWQgdGhhdCB0aGUgYnVu" + - "ZGxlZCBnYXRld2F5LXRvLUFQSSBhdXRoZW50aWNhdGlvbiBob3AgZGVmYXVsdHMgdG8gcGxhaW50" + - "ZXh0LCBpbmNsdWRlZCB3b3JrbG9hZC5zdGFyZ2F0ZVFVSUNJbnNlY3VyZSBvbmx5IGZvciB0aGUg" + - "aXNvbGF0ZWQgd29ya2VyIHRyYW5zcG9ydCwgYW5kIHdhcm5lZCBhZ2FpbnN0IHBsYWludGV4dCB3" + - "b3JrZXIgdHJhbnNwb3J0IGluIHByb2R1Y3Rpb24iLAogICAgICAiVGhlIGFnZW50IGFwcGxpZWQg" + - "dGhlIGNvbnRyb2wtcGxhbmUgSGVsbWZpbGUgZW52aXJvbm1lbnQsIHJlZnJlc2hlZCB0aGUgY29t" + - "cHV0ZSBwbGFuZSB3aXRoIHRoZSBzYW1lIHNlbGVjdGVkIGhhbmRvZmYsIGFuZCByZWNyZWF0ZWQg" + - "b3IgcmVkZXBsb3llZCBleGlzdGluZyBMTE0gZnVuY3Rpb25zIiwKICAgICAgIlRoZSBhZ2VudCB2" + - "ZXJpZmllZCBsbG0tYXBpLWdhdGV3YXksIGxsbS1yZXF1ZXN0LXJvdXRlciwgdGhlIExMTSBIVFRQ" + - "Um91dGUsIGFuZCB0aGUgbGxtLXdvcmtlciBzaWRlY2FyIGluY2x1ZGluZyAtLXF1aWMtaW5zZWN1" + - "cmUiCiAgICBdCiAgfSwKICB7CiAgICAiaWQiOiAiRzgtTkVHLW5vdC1mb3ItazNkIiwKICAgICJx" + - "dWVzdGlvbiI6ICJTZXQgdXAgYSBsb2NhbCBOVkNGIGRldiBjbHVzdGVyIHVzaW5nIGszZCBmb3Ig" + - "dGVzdGluZyIsCiAgICAiZXhwZWN0ZWRfc2tpbGwiOiBudWxsLAogICAgInNob3VsZF90cmlnZ2Vy" + - "IjogZmFsc2UsCiAgICAiZ3JvdW5kX3RydXRoIjogIlRoaXMgaXMgYSBsb2NhbCBrM2QgZGV2ZWxv" + - "cG1lbnQgcmVxdWVzdCB0aGF0IGJlbG9uZ3MgdG8gdGhlIG52Y2Ytc2VsZi1ob3N0ZWQtbG9jYWwt" + - "ZGV2IHNraWxsLCBub3QgbnZjZi1zZWxmLW1hbmFnZWQtaW5zdGFsbGF0aW9uLiBUaGUgYWdlbnQg" + - "ZGlkIG5vdCByZWFkIG9yIHVzZSB0aGUgc2VsZi1tYW5hZ2VkIGluc3RhbGxhdGlvbiBza2lsbCBh" + - "bmQgaW5zdGVhZCBwcm92aWRlZCBrM2Qtc3BlY2lmaWMgZ3VpZGFuY2UuIiwKICAgICJleHBlY3Rl" + - "ZF9iZWhhdmlvciI6IFsKICAgICAgIlRoZSBhZ2VudCBkaWQgbm90IHJlYWQgbnZjZi1zZWxmLW1h" + - "bmFnZWQtaW5zdGFsbGF0aW9uL1NLSUxMLm1kIiwKICAgICAgIlRoZSBhZ2VudCBkaWQgbm90IHBy" + - "b3ZpZGUgaGVsbWZpbGUtYmFzZWQgcHJvZHVjdGlvbiBkZXBsb3ltZW50IGluc3RydWN0aW9ucyIs" + - "CiAgICAgICJUaGUgYWdlbnQgcHJvdmlkZWQgazNkLXNwZWNpZmljIGxvY2FsIGRldmVsb3BtZW50" + - "IGd1aWRhbmNlIG9yIGRlZmVycmVkIHRvIHRoZSBhcHByb3ByaWF0ZSBsb2NhbC1kZXYgc2tpbGwi" + - "CiAgICBdCiAgfQpdClBLBwhHVWpWSlYAAEpWAABQSwMEFAAIAAAAAAAhAAAAAAAAAAAAAAAAAC8A" + - "CQBkYXRhL252Y2Ytc2VsZi1tYW5hZ2VkLWluc3RhbGxhdGlvbi9leGFtcGxlcy5tZFVUBQABAKbO" + - "EiMgRXhhbXBsZXMKCldvcmtlZCBleGFtcGxlcyBiYXNlZCBvbiByZWFsIGRlcGxveW1lbnQgc2Nl" + - "bmFyaW9zLgoKIyMgRXhhbXBsZSAxOiBPdmVycmlkZSBDYXNzYW5kcmEgUmVzb3VyY2VzCgpUaGUg" + - "ZGVmYXVsdCBDYXNzYW5kcmEgcmVzb3VyY2UgbGltaXRzIGNhdXNlIE9PTSBvbiBsYXJnZSBpbnN0" + - "YW5jZSB0eXBlcyAoZS5nLiwgYHA1LjQ4eGxhcmdlYCkuCgpQcm9ibGVtOiBDYXNzYW5kcmEgcG9k" + - "cyByZXN0YXJ0IHdpdGggYE9PTUtpbGxlZGAuCgpTb2x1dGlvbjogT3ZlcnJpZGUgcmVzb3VyY2Vz" + - "IHZpYSBoZWxtZmlsZSByZWxlYXNlIHZhbHVlcyBibG9jay4KCiMjIyBTdGVwIDE6IENhcHR1cmUg" + - "YmFzZWxpbmUKCmBgYGJhc2gKSEVMTUZJTEVfRU5WPTxlbnY+IGhlbG1maWxlIC0tc2VsZWN0b3Ig" + - "bmFtZT1jYXNzYW5kcmEgdGVtcGxhdGUgPiAvdG1wL2Nhc3MtYmVmb3JlLnlhbWwKZ3JlcCAtQTgg" + - "J3Jlc291cmNlczonIC90bXAvY2Fzcy1iZWZvcmUueWFtbCB8IGhlYWQgLTEwCmBgYAoKIyMjIFN0" + - "ZXAgMjogQWRkIG92ZXJyaWRlIHRvIGhlbG1maWxlLmQvMDEtZGVwZW5kZW5jaWVzLnlhbWwuZ290" + - "bXBsCgpJbiB0aGUgZXhhbXBsZXMgYmVsb3csIGA8cHJpdmF0ZS12YWx1ZXM+YCByZWZlcnMgdG8g" + - "dGhlIGBzZWNyZXRzL2AgZGlyZWN0b3J5IGF0IHRoZSBoZWxtZmlsZSBzdGFjayByb290LgoKYGBg" + - "eWFtbAotIG5hbWU6IGNhc3NhbmRyYQogIHZlcnNpb246IDAuOC4wCiAgY29uZGl0aW9uOiBjYXNz" + - "YW5kcmEuZW5hYmxlZAogIG5hbWVzcGFjZTogY2Fzc2FuZHJhLXN5c3RlbQogIDw8OiAqZGVwZW5k" + - "ZW5jeQogIHZhbHVlczoKICAgIC0gLi4vZ2xvYmFsLnlhbWwuZ290bXBsCiAgICAtIC4uLzxwcml2" + - "YXRlLXZhbHVlcz4ve3sgcmVxdWlyZWRFbnYgIkhFTE1GSUxFX0VOViIgfX0tc2VjcmV0cy55YW1s" + - "CiAgICAtIGNhc3NhbmRyYToKICAgICAgICByZXNvdXJjZXM6CiAgICAgICAgICBsaW1pdHM6CiAg" + - "ICAgICAgICAgIGNwdTogIjgiCiAgICAgICAgICAgIG1lbW9yeTogODE5Mk1pCiAgICAgICAgICBy" + - "ZXF1ZXN0czoKICAgICAgICAgICAgY3B1OiAiMiIKICAgICAgICAgICAgbWVtb3J5OiA0MDk2TWkK" + - "YGBgCgojIyMgU3RlcCAzOiBWZXJpZnkgYW5kIGFwcGx5CgpgYGBiYXNoCkhFTE1GSUxFX0VOVj08" + - "ZW52PiBoZWxtZmlsZSAtLXNlbGVjdG9yIG5hbWU9Y2Fzc2FuZHJhIHRlbXBsYXRlID4gL3RtcC9j" + - "YXNzLWFmdGVyLnlhbWwKZGlmZiAvdG1wL2Nhc3MtYmVmb3JlLnlhbWwgL3RtcC9jYXNzLWFmdGVy" + - "LnlhbWwgICMgQ29uZmlybSByZXNvdXJjZXMgY2hhbmdlZApIRUxNRklMRV9FTlY9PGVudj4gaGVs" + - "bWZpbGUgLS1zZWxlY3RvciBuYW1lPWNhc3NhbmRyYSBzeW5jCmBgYAoKTm90ZTogYHJlc291cmNl" + - "UHJlc2V0YCAoYSBCaXRuYW1pIGZlYXR1cmUpIGlzIG5vdCBhdmFpbGFibGUgaW4gdGhlIE5WQ0Yg" + - "Y2Fzc2FuZHJhIHdyYXBwZXIgY2hhcnQuIFVzZSBleHBsaWNpdCBgcmVzb3VyY2VzYCBpbnN0ZWFk" + - "LgoKLS0tCgojIyBFeGFtcGxlIDI6IERlcGxveSBmcm9tIE5HQyBQcml2YXRlIFJlZ2lzdHJ5IHdp" + - "dGggUHVsbCBTZWNyZXRzCgpEZXBsb3kgdGhlIE5WQ0Ygc3RhY2sgcHVsbGluZyBpbWFnZXMgZGly" + - "ZWN0bHkgZnJvbSBOR0MgKGBudmNyLmlvYCkgaW5zdGVhZCBvZiBhIG1pcnJvcmVkIHJlZ2lzdHJ5" + - "LCB1c2luZyBpbWFnZSBwdWxsIHNlY3JldHMuCgojIyMgU3RlcCAxOiBDb25maWd1cmUgZW52aXJv" + - "bm1lbnQgZm9yIE5HQwoKYGBgeWFtbAojIGVudmlyb25tZW50cy88ZW52Pi55YW1sCmdsb2JhbDoK" + - "ICBoZWxtOgogICAgc291cmNlczoKICAgICAgcmVnaXN0cnk6IG52Y3IuaW8KICAgICAgcmVwb3Np" + - "dG9yeTogbnZpZGlhL252Y2YKICBpbWFnZToKICAgIHJlZ2lzdHJ5OiBudmNyLmlvCiAgICByZXBv" + - "c2l0b3J5OiBudmlkaWEvbnZjZgpgYGAKCiMjIyBTdGVwIDI6IEF1dGhlbnRpY2F0ZSBsb2NhbGx5" + - "IChmb3IgaGVsbWZpbGUgY2hhcnQgcHVsbHMpCgpgYGBiYXNoCmRvY2tlciBsb2dpbiBudmNyLmlv" + - "IC11ICckb2F1dGh0b2tlbicgLXAgIiROR0NfQVBJX0tFWSIKaGVsbSByZWdpc3RyeSBsb2dpbiBu" + - "dmNyLmlvIC11ICckb2F1dGh0b2tlbicgLXAgIiROR0NfQVBJX0tFWSIKYGBgCgojIyMgU3RlcCAz" + - "OiBDcmVhdGUgbmFtZXNwYWNlcyBhbmQgcHVsbCBzZWNyZXRzCgpgYGBiYXNoCmV4cG9ydCBOR0Nf" + - "QVBJX0tFWT0iPHlvdXItbmdjLWFwaS1rZXk+IgoKZm9yIG5zIGluIGNhc3NhbmRyYS1zeXN0ZW0g" + - "bmF0cy1zeXN0ZW0gbnZjZiBhcGkta2V5cyBlc3Mgc2lzIHZhdWx0LXN5c3RlbSBjZXJ0LW1hbmFn" + - "ZXI7IGRvCiAga3ViZWN0bCAtLWNvbnRleHQgPGNvbnRyb2wtcGxhbmUtY29udGV4dD4gY3JlYXRl" + - "IG5hbWVzcGFjZSAiJG5zIiBcCiAgICAtLWRyeS1ydW49Y2xpZW50IC1vIHlhbWwgfCBrdWJlY3Rs" + - "IC0tY29udGV4dCA8Y29udHJvbC1wbGFuZS1jb250ZXh0PiBhcHBseSAtZiAtCmRvbmUKCmZvciBu" + - "cyBpbiBjYXNzYW5kcmEtc3lzdGVtIG5hdHMtc3lzdGVtIG52Y2YgYXBpLWtleXMgZXNzIHNpcyB2" + - "YXVsdC1zeXN0ZW0gY2VydC1tYW5hZ2VyOyBkbwogIGt1YmVjdGwgLS1jb250ZXh0IDxjb250cm9s" + - "LXBsYW5lLWNvbnRleHQ+IGNyZWF0ZSBzZWNyZXQgZG9ja2VyLXJlZ2lzdHJ5IG52Y3ItY3JlZHMg" + - "XAogICAgLS1kb2NrZXItc2VydmVyPW52Y3IuaW8gXAogICAgLS1kb2NrZXItdXNlcm5hbWU9JyRv" + - "YXV0aHRva2VuJyBcCiAgICAtLWRvY2tlci1wYXNzd29yZD0iJE5HQ19BUElfS0VZIiBcCiAgICAt" + - "LW5hbWVzcGFjZT0iJG5zIiBcCiAgICAtLWRyeS1ydW49Y2xpZW50IC1vIHlhbWwgfCBrdWJlY3Rs" + - "IC0tY29udGV4dCA8Y29udHJvbC1wbGFuZS1jb250ZXh0PiBhcHBseSAtZiAtCmRvbmUKYGBgCgoj" + - "IyMgU3RlcCA0OiBBZGQgaW1hZ2VQdWxsU2VjcmV0cyB0byBoZWxtZmlsZSByZWxlYXNlcwoKQWRk" + - "IHRoZSBjaGFydC1zcGVjaWZpYyBgaW1hZ2VQdWxsU2VjcmV0c2Aga2V5IHRvIGVhY2ggcmVsZWFz" + - "ZS4gRXhhbXBsZSBmb3IgY2Fzc2FuZHJhIGluIGBoZWxtZmlsZS5kLzAxLWRlcGVuZGVuY2llcy55" + - "YW1sLmdvdG1wbGA6CgpgYGB5YW1sCi0gbmFtZTogY2Fzc2FuZHJhCiAgdmVyc2lvbjogMC44LjAK" + - "ICBjb25kaXRpb246IGNhc3NhbmRyYS5lbmFibGVkCiAgbmFtZXNwYWNlOiBjYXNzYW5kcmEtc3lz" + - "dGVtCiAgPDw6ICpkZXBlbmRlbmN5CiAgdmFsdWVzOgogICAgLSAuLi9nbG9iYWwueWFtbC5nb3Rt" + - "cGwKICAgIC0gLi4vPHByaXZhdGUtdmFsdWVzPi97eyByZXF1aXJlZEVudiAiSEVMTUZJTEVfRU5W" + - "IiB9fS1zZWNyZXRzLnlhbWwKICAgIC0gY2Fzc2FuZHJhOgogICAgICAgIGdsb2JhbDoKICAgICAg" + - "ICAgIGltYWdlUHVsbFNlY3JldHM6CiAgICAgICAgICAgIC0gbnZjci1jcmVkcwpgYGAKClNlZSBb" + - "cmVmZXJlbmNlcy9wdWxsLXNlY3JldHMubWRdKHJlZmVyZW5jZXMvcHVsbC1zZWNyZXRzLm1kKSBm" + - "b3IgdGhlIGtleSBmb3IgZXZlcnkgY2hhcnQuCgpUaGVzZSBhcmUgY29udHJvbC1wbGFuZSBuYW1l" + - "c3BhY2VzLiBGb3IgYSBzZXBhcmF0ZSBjb21wdXRlIGNsdXN0ZXIsIGNyZWF0ZSB0aGUKc2VjcmV0" + - "IG9ubHkgaW4gYG52Y2Etb3BlcmF0b3JgOyB0aGUgY2hhcnQgYW5kIG9wZXJhdG9yIG1pcnJvciBp" + - "dCB0bwpgbnZjYS1zeXN0ZW1gLiBTZWUgW1NwbGl0IENvbXB1dGUtUGxhbmUgSW5zdGFsbGF0aW9u" + - "XShyZWZlcmVuY2VzL2NvbXB1dGUtcGxhbmUtaW5zdGFsbGF0aW9uLm1kI3B1bGwtc2VjcmV0cyku" + - "CgojIyMgU3RlcCA1OiBEZXBsb3kKCmBgYGJhc2gKSEVMTUZJTEVfRU5WPTxlbnY+IGhlbG1maWxl" + - "IC0ta3ViZS1jb250ZXh0IDxjb250cm9sLXBsYW5lLWNvbnRleHQ+IHN5bmMKYGBgCgojIyMgU3Rl" + - "cCA2OiBQYXRjaCBTZXJ2aWNlQWNjb3VudHMgZm9yIGNoYXJ0cyB3aXRob3V0IG5hdGl2ZSBzdXBw" + - "b3J0CgpBZnRlciBQaGFzZSAxIChkZXBlbmRlbmNpZXMpIGNvbXBsZXRlcywgT3BlbkJhbyBwb2Rz" + - "IHdpbGwgYmUgaW4gYEltYWdlUHVsbEJhY2tPZmZgLiBQYXRjaCBhbmQgcmVzdGFydDoKCmBgYGJh" + - "c2gKZm9yIG5zIGluIHZhdWx0LXN5c3RlbSBudmNmIGVzczsgZG8KICBmb3Igc2EgaW4gJChrdWJl" + - "Y3RsIC0tY29udGV4dCA8Y29udHJvbC1wbGFuZS1jb250ZXh0PiBnZXQgc2EgLW4gIiRucyIgXAog" + - "ICAgLS1uby1oZWFkZXJzIC1vIGN1c3RvbS1jb2x1bW5zPSI6bWV0YWRhdGEubmFtZSIpOyBkbwog" + - "ICAga3ViZWN0bCAtLWNvbnRleHQgPGNvbnRyb2wtcGxhbmUtY29udGV4dD4gcGF0Y2ggc2Vydmlj" + - "ZWFjY291bnQgLW4gIiRucyIgIiRzYSIgXAogICAgICAtcCAneyJpbWFnZVB1bGxTZWNyZXRzIjog" + - "W3sibmFtZSI6ICJudmNyLWNyZWRzIn1dfScKICBkb25lCmRvbmUKa3ViZWN0bCAtLWNvbnRleHQg" + - "PGNvbnRyb2wtcGxhbmUtY29udGV4dD4gZGVsZXRlIHBvZHMgLW4gdmF1bHQtc3lzdGVtIC0tYWxs" + - "CmBgYAoKVmVyaWZ5OgoKYGBgYmFzaAprdWJlY3RsIC0tY29udGV4dCA8Y29udHJvbC1wbGFuZS1j" + - "b250ZXh0PiBnZXQgZXZlbnRzIC1uIHZhdWx0LXN5c3RlbSBcCiAgLS1zb3J0LWJ5PScubGFzdFRp" + - "bWVzdGFtcCcgfCBncmVwIC1pIHB1bGwKIyBTaG91bGQgc2hvdyAiU3VjY2Vzc2Z1bGx5IHB1bGxl" + - "ZCBpbWFnZSIgZnJvbSBudmNyLmlvCmBgYAoKLS0tCgojIyBFeGFtcGxlIDM6IEZ1bGwgVGVhcmRv" + - "d24gYW5kIFJlaW5zdGFsbAoKV2hlbiBhIGRlcGxveW1lbnQgaXMgYnJva2VuIGFuZCB5b3UgbmVl" + - "ZCBhIGNsZWFuIHNsYXRlLgoKIyMjIFN0ZXAgMTogRGVzdHJveSB0aGUgY29tcHV0ZS1wbGFuZSBz" + - "dGFjawoKYGBgYmFzaAptYWtlIC1DIGRlcGxveS9zdGFja3MvbnZjZi1jb21wdXRlLXBsYW5lIGRl" + - "c3Ryb3kgXAogIENMVVNURVJfTkFNRT08Y2x1c3Rlci1uYW1lPiBcCiAgSEVMTUZJTEVfRU5WPTxl" + - "bnY+IFwKICBLVUJFQ09ORklHX0ZJTEU9PGNvbXB1dGUtcGxhbmUta3ViZWNvbmZpZz4KYGBgCgpJ" + - "ZiBjb21wdXRlIGNsZWFudXAgaXMgc3R1Y2sgb24gZmluYWxpemVycywgdXNlIHRoZSBjb21wdXRl" + - "LWNvbnRleHQgcHJvY2VkdXJlIGluCltkZWJ1Z2dpbmcubWRdKHJlZmVyZW5jZXMvZGVidWdnaW5n" + - "Lm1kI2ZhaWx1cmUtbnZjYS1jbGVhbnVwLXN0dWNrLWZpbmFsaXplcnMpLgoKIyMjIFN0ZXAgMjog" + - "RGVyZWdpc3RlciB0aGUgY29tcHV0ZSBjbHVzdGVyCgpSZWFkIGBjbHVzdGVySURgIGFuZCBgbmNh" + - "SURgIGZyb20KYGRlcGxveS9zdGFja3MvbnZjZi1jb21wdXRlLXBsYW5lL3JlZ2lzdHJhdGlvbi88" + - "Y2x1c3Rlci1uYW1lPi1yZWdpc3Rlci12YWx1ZXMueWFtbGAuClNob3cgdGhlIElEIGFuZCBnZXQg" + - "Y29uZmlybWF0aW9uIGJlZm9yZSBkZWxldGluZyB0aGUgSUNNUyByb3c6CgpgYGBiYXNoCjxudmNm" + - "LWNsaT4gLS1jb25maWcgPGNvbmZpZz4gY2x1c3RlciBkZWxldGUgXAogIC0tY2x1c3Rlci1pZCA8" + - "Y2x1c3Rlci1pZC1mcm9tLXJlZ2lzdHJhdGlvbi12YWx1ZXM+IFwKICAtLW5jYS1pZCA8bmNhLWlk" + - "PiBcCiAgLS1pZ25vcmUtbWlzc2luZwpgYGAKClJlcGVhdCBzdGVwcyAxIGFuZCAyIGZvciBldmVy" + - "eSBjb21wdXRlIGNsdXN0ZXIuCgojIyMgU3RlcCAzOiBEZXN0cm95IHRoZSBjb250cm9sLXBsYW5l" + - "IHN0YWNrCgpQcmVzZXJ2ZSB0aGUgYGNlcnQtbWFuYWdlcmAgbmFtZXNwYWNlIGJ5IGRlZmF1bHQu" + - "IE5hbWVzcGFjZSBwcmVzZXJ2YXRpb24gZG9lcwpub3QgcHJlc2VydmUgdGhlIEhlbG0gcmVsZWFz" + - "ZS4gQmVmb3JlIHJ1bm5pbmcgZGVzdHJveSwgaWRlbnRpZnkgd2hldGhlcgpjZXJ0LW1hbmFnZXIg" + - "aXMgZXh0ZXJuYWwuIEZvciBhbiBleHRlcm5hbCBpbnN0YWxsYXRpb24sIHJlcXVpcmUgZWZmZWN0" + - "aXZlCmBjZXJ0TWFuYWdlci5lbmFibGVkOiBmYWxzZWAgYW5kIGFib3J0IGlmIGl0IGlzIHRydWUu" + - "IEZvciBhbiBpbnRlbmRlZApzdGFjay1tYW5hZ2VkIHJlbGVhc2Ugd2l0aCBlZmZlY3RpdmUgYGNl" + - "cnRNYW5hZ2VyLmVuYWJsZWQ6IHRydWVgLCB2ZXJpZnkKbWF0Y2hpbmcgc3RhY2sgSGVsbSByZWxl" + - "YXNlIG1ldGFkYXRhIGFuZCBhYm9ydCBvbiBtaXNzaW5nIG9yIG1pc21hdGNoZWQKZXZpZGVuY2Uu" + - "IE9ubHkgcGxhbiBuYW1lc3BhY2UgcmVtb3ZhbCBhZnRlciB2ZXJpZnlpbmcgbmFtZXNwYWNlIHBy" + - "b3ZlbmFuY2UgYW5kCmdldHRpbmcgZXhwbGljaXQgY29uZmlybWF0aW9uOyBvdGhlcndpc2UgZG8g" + - "bm90IGRlbGV0ZSBpdC4KCmBgYGJhc2gKS1VCRUNPTkZJRz08Y29udHJvbC1wbGFuZS1rdWJlY29u" + - "ZmlnPiBcCm1ha2UgLUMgZGVwbG95L3N0YWNrcy9zZWxmLW1hbmFnZWQgZGVzdHJveSBcCiAgSEVM" + - "TUZJTEVfRU5WPTxlbnY+IFwKICBLVUJFQ09ORklHX0ZJTEU9PGNvbnRyb2wtcGxhbmUta3ViZWNv" + - "bmZpZz4KYGBgCgpQYXNzIGJvdGgga3ViZWNvbmZpZyBmb3JtcyBiZWNhdXNlIGNvbnRyb2wtcGxh" + - "bmUgbmFtZXNwYWNlIGNsZWFudXAgdXNlcwphbWJpZW50IGBrdWJlY3RsYC4KCiMjIyBTdGVwIDQ6" + - "IFZlcmlmeSBjbGVhbgoKYGBgYmFzaAprdWJlY3RsIC0tY29udGV4dCA8Y29tcHV0ZS1wbGFuZS1j" + - "b250ZXh0PiBnZXQgbnMgfCBcCiAgZ3JlcCAtRSAnKG52Y2F8bnZjZi1iYWNrZW5kfGdyb3ZlfGR5" + - "bmFtbyknCmt1YmVjdGwgLS1jb250ZXh0IDxjb250cm9sLXBsYW5lLWNvbnRleHQ+IGdldCBucyB8" + - "IFwKICBncmVwIC1FICcoY2Fzc2FuZHJhfG5hdHN8dmF1bHR8bnZjZnxhcGkta2V5c3xlc3N8c2lz" + - "KScKIyBCb3RoIGNvbW1hbmRzIHNob3VsZCByZXR1cm4gZW1wdHkuCmBgYAoKIyMjIFN0ZXAgNTog" + - "UmVpbnN0YWxsIGluIHNwbGl0IG9yZGVyCgpGb2xsb3cgW0NsZWFuIEluc3RhbGxhdGlvbl0oU0tJ" + - "TEwubWQjY2xlYW4taW5zdGFsbGF0aW9uKSwgdGhlbiByZXR1cm4gdG8gdGhlCnNhbWUgaGFuZG9m" + - "ZiBpbiBbU3BsaXQgQ29tcHV0ZS1QbGFuZSBJbnN0YWxsYXRpb25dKHJlZmVyZW5jZXMvY29tcHV0" + - "ZS1wbGFuZS1pbnN0YWxsYXRpb24ubWQpLgoKLS0tCgojIyBFeGFtcGxlIDQ6IFJlY292ZXIgZnJv" + - "bSBhIEdhdGV3YXkgRW5kcG9pbnQgQ2hhbmdlCgpUaGUgc2VwYXJhdGVseSBwcm92aXNpb25lZCBH" + - "YXRld2F5IG9yIGl0cyBsb2FkIGJhbGFuY2VyIHdhcyByZWNyZWF0ZWQgYW5kIGdvdCBhIG5ldyBl" + - "bmRwb2ludC4KCiMjIyBTdGVwIDE6IEdldCB0aGUgbmV3IGVuZHBvaW50CgpgYGBiYXNoCkdBVEVX" + - "QVlfQUREUj0kKGt1YmVjdGwgLS1jb250ZXh0IDxjb250cm9sLXBsYW5lLWNvbnRleHQ+IGdldCBn" + - "YXRld2F5IFwKICBudmNmLWdhdGV3YXkgLW4gZW52b3ktZ2F0ZXdheSBcCiAgLW8ganNvbnBhdGg9" + - "J3suc3RhdHVzLmFkZHJlc3Nlc1swXS52YWx1ZX0nKQp0ZXN0IC1uICIkR0FURVdBWV9BRERSIgpg" + - "YGAKCiMjIyBTdGVwIDI6IFVwZGF0ZSBETlMgYW5kIGRpcmVjdCBhZGRyZXNzIGZpZWxkcwoKS2Vl" + - "cCBgZ2xvYmFsLmRvbWFpbmAsIHJvdXRlIGhvc3RuYW1lcywgYW5kIENMSSBIb3N0IGZpZWxkcyB1" + - "bmNoYW5nZWQuIFBvaW50IHRoZQpETlMgcmVjb3JkcyBmb3IgcmVuZGVyZWQgcm91dGUgaG9zdG5h" + - "bWVzLCBzdWNoIGFzIGBhcGkuPGRvbWFpbj5gIGFuZApgKi5pbnZvY2F0aW9uLjxkb21haW4+YCwg" + - "dG8gYEdBVEVXQVlfQUREUmAuIEZvciBhIHRlbXBvcmFyeSBlbnZpcm9ubWVudCB3aXRob3V0CkRO" + - "Uywga2VlcCBpdHMgcmVzZXJ2ZWQgcm91dGUgc3VmZml4IGFuZCBjb25uZWN0IHRvIGBHQVRFV0FZ" + - "X0FERFJgIHdpdGggdGhlCm1hdGNoaW5nIGBIb3N0YCBoZWFkZXIuCgpGb3IgYSBzcGxpdCBkZXBs" + - "b3ltZW50IGNvbmZpZ3VyZWQgd2l0aCBkaXJlY3QgR2F0ZXdheSBhZGRyZXNzZXMsIHJlcGxhY2Ug" + - "b25seQp0aGUgYWRkcmVzcy1iZWFyaW5nIHZhbHVlczogY29udHJvbC1wbGFuZSBgbnZjZk5hdHNT" + - "ZXJ2aWNlVVJMYCwgY29tcHV0ZS1wbGFuZQpJQ01TLCBSZVZhbCwgYW5kIE5BVFMgVVJMcywgYW5k" + - "IENMSSBVUkwgZmllbGRzLiBLZWVwIHRoZWlyIGRvbWFpbiBhbmQgSG9zdApvdmVycmlkZXMgdW5j" + - "aGFuZ2VkLiBSZWZyZXNoIHJlZ2lzdHJhdGlvbiBhbmQgaW5zdGFsbGF0aW9uIHRocm91Z2ggdGhl" + - "IHNhbWUKaGFuZG9mZiB1c2VkIG9yaWdpbmFsbHkuIFNlZQpbU3BsaXQgQ29tcHV0ZS1QbGFuZSBJ" + - "bnN0YWxsYXRpb25dKHJlZmVyZW5jZXMvY29tcHV0ZS1wbGFuZS1pbnN0YWxsYXRpb24ubWQpCmZv" + - "ciB0aGUgZmllbGRzIGFuZCBjb21wbGV0ZSBjb21tYW5kcy4KCiMjIyBTdGVwIDM6IFZlcmlmeSBi" + - "b3RoIGNvbnRleHRzCgpgYGBiYXNoCmRpZyArc2hvcnQgImFwaS48ZG9tYWluPiIKY3VybCAtSCAi" + - "SG9zdDogYXBpLjxkb21haW4+IiAiaHR0cDovLyRHQVRFV0FZX0FERFIvaGVhbHRoIgprdWJlY3Rs" + - "IC0tY29udGV4dCA8Y29udHJvbC1wbGFuZS1jb250ZXh0PiBnZXQgZ2F0ZXdheSBudmNmLWdhdGV3" + - "YXkgXAogIC1uIGVudm95LWdhdGV3YXkgLW8gd2lkZQprdWJlY3RsIC0tY29udGV4dCA8Y29udHJv" + - "bC1wbGFuZS1jb250ZXh0PiBnZXQgaHR0cHJvdXRlcyAtQQprdWJlY3RsIC0tY29udGV4dCA8Y29u" + - "dHJvbC1wbGFuZS1jb250ZXh0PiBnZXQgdGNwcm91dGVzIC1BCmt1YmVjdGwgLS1jb250ZXh0IDxj" + - "b21wdXRlLXBsYW5lLWNvbnRleHQ+IHJvbGxvdXQgc3RhdHVzIFwKICBkZXBsb3ltZW50L252Y2Et" + - "b3BlcmF0b3IgLW4gbnZjYS1vcGVyYXRvciAtLXRpbWVvdXQ9MTBtCmt1YmVjdGwgLS1jb250ZXh0" + - "IDxjb21wdXRlLXBsYW5lLWNvbnRleHQ+IHdhaXQgXAogIG52Y2ZiYWNrZW5kLzxjb21wdXRlLXBs" + - "YW5lLWNsdXN0ZXItbmFtZT4gLW4gbnZjYS1vcGVyYXRvciBcCiAgLS1mb3I9anNvbnBhdGg9J3su" + - "c3RhdHVzLmFnZW50U3RhdHVzfSc9aGVhbHRoeSAtLXRpbWVvdXQ9MTBtCmBgYAoKLS0tCgojIyBF" + - "eGFtcGxlIDU6IERlcGxveSBPbmx5IERlcGVuZGVuY2llcwoKVXNlZnVsIGZvciB0ZXN0aW5nIG9y" + - "IHdoZW4gaXRlcmF0aW5nIG9uIHNlcnZpY2UgY29uZmlndXJhdGlvbi4KCmBgYGJhc2gKIyBEZXBs" + - "b3kganVzdCBOQVRTLCBDYXNzYW5kcmEsIE9wZW5CYW8KSEVMTUZJTEVfRU5WPTxlbnY+IGhlbG1m" + - "aWxlIC0tc2VsZWN0b3IgcmVsZWFzZS1ncm91cD1kZXBlbmRlbmNpZXMgc3luYwoKIyBDaGVjayBz" + - "dGF0dXMKa3ViZWN0bCBnZXQgcG9kcyAtbiBuYXRzLXN5c3RlbQprdWJlY3RsIGdldCBwb2RzIC1u" + - "IGNhc3NhbmRyYS1zeXN0ZW0Ka3ViZWN0bCBnZXQgcG9kcyAtbiB2YXVsdC1zeXN0ZW0KCiMgRGVw" + - "bG95IGp1c3Qgb25lIHNwZWNpZmljIHJlbGVhc2UKSEVMTUZJTEVfRU5WPTxlbnY+IGhlbG1maWxl" + - "IC0tc2VsZWN0b3IgbmFtZT1jYXNzYW5kcmEgc3luYwpgYGAKCiMjIyBTZWxlY3RvciByZWZlcmVu" + - "Y2UKCnwgU2VsZWN0b3IgfCBSZWxlYXNlcyB8CnwtLS0tLS0tLS0tfC0tLS0tLS0tLS18CnwgYHJl" + - "bGVhc2UtZ3JvdXA9ZGVwZW5kZW5jaWVzYCB8IG5hdHMsIGNhc3NhbmRyYSwgb3BlbmJhby1zZXJ2" + - "ZXIgfAp8IGByZWxlYXNlLWdyb3VwPXNlcnZpY2VzYCB8IGFwaS1rZXlzLCBzaXMsIGFwaSwgaW52" + - "b2NhdGlvbi1zZXJ2aWNlLCBncnBjLXByb3h5LCBlc3MtYXBpLCBub3Rhcnktc2VydmljZSwgcmV2" + - "YWwsIG9wdGlvbmFsIGxsbS1yZXF1ZXN0LXJvdXRlciBhbmQgbGxtLWFwaS1nYXRld2F5IHdoZW4g" + - "YGxsbS5lbmFibGVkPXRydWVgLCBvcHRpb25hbCB2YW5pdHktZ2F0ZXdheSBvbmx5IGluIHN0YWNr" + - "IHBhY2thZ2VzIHRoYXQgaW5jbHVkZSB0aGUgYWRkb24gYW5kIGhhdmUgYGFkZG9ucy52YW5pdHlH" + - "YXRld2F5LmVuYWJsZWQ9dHJ1ZWAsIG9wdGlvbmFsIG52Y2YtdWkgb25seSBpbiBzdGFjayBwYWNr" + - "YWdlcyB0aGF0IGluY2x1ZGUgdGhlIGFkZG9uIGFuZCBoYXZlIGBhZGRvbnMubnZjZlVpLmVuYWJs" + - "ZWQ9dHJ1ZWAgfAp8IGBuYW1lPWxsbS1yZXF1ZXN0LXJvdXRlcmAgfCBEZXBsb3kganVzdCB0aGUg" + - "TExNIHJlcXVlc3Qgcm91dGVyIHdoZW4gYGxsbS5lbmFibGVkPXRydWVgIHwKfCBgbmFtZT1sbG0t" + - "YXBpLWdhdGV3YXlgIHwgRGVwbG95IGp1c3QgdGhlIExMTSBBUEkgZ2F0ZXdheSB3aGVuIGBsbG0u" + - "ZW5hYmxlZD10cnVlYCB8CnwgYG5hbWU9dmFuaXR5LWdhdGV3YXlgIHwgRGVwbG95IGp1c3QgVmFu" + - "aXR5IEdhdGV3YXkgb25seSB3aGVuIHRoZSBzdGFjayBwYWNrYWdlIGluY2x1ZGVzIHRoYXQgcmVs" + - "ZWFzZSBhbmQgYGFkZG9ucy52YW5pdHlHYXRld2F5LmVuYWJsZWQ9dHJ1ZWAgfAp8IGBuYW1lPW52" + - "Y2YtdWlgIHwgRGVwbG95IE5WQ0YgVUkgb25seSB3aGVuIHRoZSBzdGFjayBwYWNrYWdlIGluY2x1" + - "ZGVzIHRoYXQgcmVsZWFzZSBhbmQgYGFkZG9ucy5udmNmVWkuZW5hYmxlZD10cnVlYCB8CnwgYHJl" + - "bGVhc2UtZ3JvdXA9aW5ncmVzc2AgfCBpbmdyZXNzIChnYXRld2F5IHJvdXRlcykgfAp8IGBuYW1l" + - "PTxyZWxlYXNlPmAgfCBBbnkgaW5kaXZpZHVhbCByZWxlYXNlIGJ5IG5hbWUgfAoKTm90ZTogYGFk" + - "bWluLWlzc3Vlci1wcm94eWAgZG9lcyBub3QgaGF2ZSBhIHJlbGVhc2UtZ3JvdXAgbGFiZWwuIFRh" + - "cmdldCBpdCB3aXRoIGAtLXNlbGVjdG9yIG5hbWU9YWRtaW4taXNzdWVyLXByb3h5YC4KUEsHCAJS" + - "TkB/KAAAfygAAFBLAwQUAAgAAAAAACEAAAAAAAAAAAAAAAAATAAJAGRhdGEvbnZjZi1zZWxmLW1h" + - "bmFnZWQtaW5zdGFsbGF0aW9uL3JlZmVyZW5jZXMvY29tcHV0ZS1wbGFuZS1pbnN0YWxsYXRpb24u" + - "bWRVVAUAAQCmzhIjIFNwbGl0IENvbXB1dGUtUGxhbmUgSW5zdGFsbGF0aW9uCgpUaGUgY29udHJv" + - "bCBwbGFuZSBhbmQgY29tcHV0ZSBwbGFuZSBhcmUgc2VwYXJhdGUgc3RhY2tzLiBJbnN0YWxsIHRo" + - "ZSBjb250cm9sCnBsYW5lIGZpcnN0LCB0aGVuIHJlZ2lzdGVyIGFuZCBpbnN0YWxsIGVhY2ggY29t" + - "cHV0ZSBjbHVzdGVyLiBDaG9vc2Ugb25lIGhhbmRvZmYKYmVsb3cgYW5kIHVzZSBpdCBmb3IgdGhl" + - "IGZ1bGwgbGlmZWN5Y2xlLiBEbyBub3QgY29tYmluZSBhIENMSS1nZW5lcmF0ZWQgcHJvZmlsZQp3" + - "aXRoIHRoZSBIZWxtZmlsZSB2YWx1ZXMgZmxvdy4KCiMjIENob29zZSBhIEhhbmRvZmYKCnwgRmxv" + - "dyB8IFNvdXJjZSBvZiBjb250cm9sLXBsYW5lIGVuZHBvaW50cyB8IFJlZ2lzdHJhdGlvbiBhcnRp" + - "ZmFjdCB8CnwtLS0tLS18LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS18LS0tLS0t" + - "LS0tLS0tLS0tLS0tLS0tLS18CnwgSGVsbWZpbGUgdmFsdWVzIHwgU2VwYXJhdGUgY29udHJvbC1w" + - "bGFuZSBhbmQgY29tcHV0ZS1wbGFuZSBlbnZpcm9ubWVudCBmaWxlcyB8IGBkZXBsb3kvc3RhY2tz" + - "L252Y2YtY29tcHV0ZS1wbGFuZS9yZWdpc3RyYXRpb24vPGNsdXN0ZXI+LXJlZ2lzdGVyLXZhbHVl" + - "cy55YW1sYCB8CnwgQ0xJIHByb2ZpbGUgfCBDTEktZ2VuZXJhdGVkIGBkZXBsb3kvc3RhY2tzL3Nl" + - "bGYtbWFuYWdlZC9vdXQvY29udHJvbC1wbGFuZS1wcm9maWxlLnlhbWxgIHwgQ0xJLWdlbmVyYXRl" + - "ZCBgZGVwbG95L3N0YWNrcy9udmNmLWNvbXB1dGUtcGxhbmUvb3V0LzxjbHVzdGVyPi1yZWdpc3Rl" + - "ci12YWx1ZXMueWFtbGAgfAoKVGhlIENMSSBwcm9maWxlIGlzIG1hbmRhdG9yeSBmb3IgdGhlIENM" + - "SSBmbG93IGFuZCBtdXN0IG5vdCBiZSBoYW5kLWF1dGhvcmVkLgpUaGUgSGVsbWZpbGUgZmxvdyBk" + - "b2VzIG5vdCBjb25zdW1lIGEgY29udHJvbC1wbGFuZSBwcm9maWxlLgoKQ29tbWFuZHMgYmVsb3cg" + - "dXNlIG1vbm9yZXBvIHBhdGhzIGZyb20gdGhlIHJlcG9zaXRvcnkgcm9vdC4gT3BlcmF0b3JzIHVz" + - "aW5nCmV4dHJhY3RlZCBwYWNrYWdlcyBuZWVkIG1hdGNoaW5nLXZlcnNpb24gYG52Y2Ytc2VsZi1t" + - "YW5hZ2VkLXN0YWNrYCBhbmQKYG52Y2YtY29tcHV0ZS1wbGFuZS1zdGFja2AgYnVuZGxlcy4gU3Vi" + - "c3RpdHV0ZSB0aG9zZSBidW5kbGUgcm9vdHMgZm9yCmBkZXBsb3kvc3RhY2tzL3NlbGYtbWFuYWdl" + - "ZGAgYW5kIGBkZXBsb3kvc3RhY2tzL252Y2YtY29tcHV0ZS1wbGFuZWAuCgojIyBIZWxtZmlsZSBW" + - "YWx1ZXMgRmxvdwoKQ3JlYXRlIGJvdGggZW52aXJvbm1lbnQgZmlsZXMgYmVmb3JlIGluc3RhbGxh" + - "dGlvbjoKCi0gYGRlcGxveS9zdGFja3Mvc2VsZi1tYW5hZ2VkL2Vudmlyb25tZW50cy88ZW52Pi55" + - "YW1sYCBmb3IgY29udHJvbC1wbGFuZQogIHJlZ2lzdHJ5LCBkb21haW4sIEdhdGV3YXksIHJvdXRl" + - "cywgYW5kIHNlcnZpY2Ugc2V0dGluZ3MuCi0gYGRlcGxveS9zdGFja3MvbnZjZi1jb21wdXRlLXBs" + - "YW5lL2Vudmlyb25tZW50cy88ZW52Pi55YW1sYCBmb3IgY29tcHV0ZS1wbGFuZQogIHJlZ2lzdHJ5" + - "LCBwdWxsIHNlY3JldHMsIGFuZCBjb21wdXRlLXJlYWNoYWJsZSBJQ01TLCBSZVZhbCwgYW5kIE5B" + - "VFMgdmFsdWVzLgoKRm9yIEVLUywgdXNlIHRoZSBjYW5vbmljYWwKW0NTUCBFbmQtdG8tRW5kIEV4" + - "YW1wbGVdKGh0dHBzOi8vZG9jcy5udmlkaWEuY29tL252Y2YvdjAuNi4wLXJjL2NzcC1lbmQtdG8t" + - "ZW5kLWV4YW1wbGUpLgpUaGUgY29udHJvbC1wbGFuZSBlbnZpcm9ubWVudCBrZWVwcyB0aGUgRW52" + - "b3kgR2F0ZXdheSBjb250cm9sbGVyIGluCmBlbnZveS1nYXRld2F5LXN5c3RlbWAsIGF0dGFjaGVz" + - "IHJvdXRlcyB0byBgR2F0ZXdheS9udmNmLWdhdGV3YXlgIGluCmBlbnZveS1nYXRld2F5YCwgYW5k" + - "IGVuYWJsZXMgdGhlIE5BVFMgcm91dGUuIFRoZSBjb21wdXRlLXBsYW5lIGVudmlyb25tZW50IHVz" + - "ZXMKdGhlIGV4dGVybmFsIGFkZHJlc3MgcGx1cyB0aGUgc2VydmljZSBob3N0IG92ZXJyaWRlcy4g" + - "SGVyZSwKYDxnYXRld2F5LWFkZHJlc3M+YCBpcyB0aGUgTkxCIGFkZHJlc3MgYW5kIGA8Z2F0ZXdh" + - "eS1kb21haW4+YCBpcyB0aGUgRE5TIGJhc2UKd2hvc2Ugc2VydmljZSBzdWJkb21haW5zIHJlc29s" + - "dmUgdG8gdGhhdCBOTEI6CgpgYGB5YW1sCiMgZGVwbG95L3N0YWNrcy9zZWxmLW1hbmFnZWQvZW52" + - "aXJvbm1lbnRzLzxlbnY+LnlhbWwKZ2xvYmFsOgogIGRvbWFpbjogIjxnYXRld2F5LWRvbWFpbj4i" + - "CiAgd29ya2VyRW5kcG9pbnRzOgogICAgbnZjZlNlcnZpY2VVUkw6ICJodHRwOi8vYXBpLjxnYXRl" + - "d2F5LWRvbWFpbj4iCiAgICBudmNmR3JwY1NlcnZpY2VVUkw6ICJodHRwOi8vd29ya2VyLWFwaS48" + - "Z2F0ZXdheS1kb21haW4+IgogICAgbnZjZk5hdHNTZXJ2aWNlVVJMOiAibmF0czovLzxnYXRld2F5" + - "LWFkZHJlc3M+OjQyMjIiCiAgICBudmN0U2VydmljZVVSTDogImh0dHA6Ly90YXNrcy48Z2F0ZXdh" + - "eS1kb21haW4+IgogICAgbnZjdEdycGNTZXJ2aWNlVVJMOiAiaHR0cDovL3dvcmtlci10YXNrcy48" + - "Z2F0ZXdheS1kb21haW4+IgogICAgaW52b2NhdGlvblNlcnZpY2VVUkw6ICJodHRwOi8vaW52b2Nh" + - "dGlvbi48Z2F0ZXdheS1kb21haW4+IgoKZ3JwY3Byb3h5OgogIG52Y2ZHcnBjU2VydmljZVVSTDog" + - "Imh0dHA6Ly9hcGkubnZjZi5zdmMuY2x1c3Rlci5sb2NhbDo5MDkwIgoKaW5ncmVzczoKICBnYXRl" + - "d2F5QXBpOgogICAgY29udHJvbGxlck5hbWVzcGFjZTogZW52b3ktZ2F0ZXdheS1zeXN0ZW0KICAg" + - "IGdhdGV3YXlzOgogICAgICBzaGFyZWQ6IHtuYW1lOiBudmNmLWdhdGV3YXksIG5hbWVzcGFjZTog" + - "ZW52b3ktZ2F0ZXdheX0KICAgICAgZ3JwYzoge25hbWU6IG52Y2YtZ2F0ZXdheSwgbmFtZXNwYWNl" + - "OiBlbnZveS1nYXRld2F5fQogICAgICBuYXRzOgogICAgICAgIG5hbWU6IG52Y2YtZ2F0ZXdheQog" + - "ICAgICAgIG5hbWVzcGFjZTogZW52b3ktZ2F0ZXdheQogICAgICAgIGxpc3RlbmVyTmFtZTogbmF0" + - "cwogICAgcm91dGVzOgogICAgICBudmNmQXBpOgogICAgICAgIGdycGM6CiAgICAgICAgICBlbmFi" + - "bGVkOiB0cnVlCiAgICAgICAgICBob3N0bmFtZXM6IFsid29ya2VyLWFwaS48Z2F0ZXdheS1kb21h" + - "aW4+Il0KICAgICAgbnZjdEFwaToKICAgICAgICBncnBjOgogICAgICAgICAgZW5hYmxlZDogdHJ1" + - "ZQogICAgICAgICAgaG9zdG5hbWVzOiBbIndvcmtlci10YXNrcy48Z2F0ZXdheS1kb21haW4+Il0K" + - "ICAgICAgbmF0czoKICAgICAgICBlbmFibGVkOiB0cnVlCgpvcGVuYmFvOgogIG1pZ3JhdGlvbnM6" + - "CiAgICBpc3N1ZXJEaXNjb3Zlcnk6CiAgICAgIGVuYWJsZWQ6IHRydWUKYGBgCgpgYGB5YW1sCiMg" + - "ZGVwbG95L3N0YWNrcy9udmNmLWNvbXB1dGUtcGxhbmUvZW52aXJvbm1lbnRzLzxlbnY+LnlhbWwK" + - "Z2xvYmFsOgogIG52Y2FPcGVyYXRvcjoKICAgIHNlbGZNYW5hZ2VkOgogICAgICBpY21zU2Vydmlj" + - "ZVVSTDogImh0dHA6Ly88Z2F0ZXdheS1hZGRyZXNzPiIKICAgICAgaWNtc1NlcnZpY2VIb3N0SGVh" + - "ZGVyT3ZlcnJpZGU6ICJzaXMuPGdhdGV3YXktZG9tYWluPiIKICAgICAgcmV2YWxTZXJ2aWNlVVJM" + - "OiAiaHR0cDovLzxnYXRld2F5LWFkZHJlc3M+IgogICAgICByZXZhbFNlcnZpY2VIb3N0SGVhZGVy" + - "T3ZlcnJpZGU6ICJyZXZhbC48Z2F0ZXdheS1kb21haW4+IgogICAgICBuYXRzVVJMOiAibmF0czov" + - "LzxnYXRld2F5LWFkZHJlc3M+OjQyMjIiCiAgICAgIG5hdHNIb3N0T3ZlcnJpZGU6ICJuYXRzLjxn" + - "YXRld2F5LWRvbWFpbj4iCmBgYAoKUHJldmlldyBhbmQgaW5zdGFsbCB0aGUgY29udHJvbCBwbGFu" + - "ZSBhZ2FpbnN0IGl0cyBjb250ZXh0OgoKYGBgYmFzaAptYWtlIC1DIGRlcGxveS9zdGFja3Mvc2Vs" + - "Zi1tYW5hZ2VkIHRlbXBsYXRlIFwKICBIRUxNRklMRV9FTlY9PGVudj4gXAogIEtVQkVDT05GSUdf" + - "RklMRT08Y29udHJvbC1wbGFuZS1rdWJlY29uZmlnPgoKbWFrZSAtQyBkZXBsb3kvc3RhY2tzL3Nl" + - "bGYtbWFuYWdlZCBpbnN0YWxsIFwKICBIRUxNRklMRV9FTlY9PGVudj4gXAogIEtVQkVDT05GSUdf" + - "RklMRT08Y29udHJvbC1wbGFuZS1rdWJlY29uZmlnPgpgYGAKCkJlZm9yZSByZWdpc3RlcmluZyB0" + - "aHJvdWdoIGEgYmFyZSBHYXRld2F5IGFkZHJlc3MsIGNyZWF0ZSBvciB1cGRhdGUgdGhlIENMSQpj" + - "b25maWcgcGFzc2VkIGFzIGBOVkNGX0NMSV9DT05GSUdgOgoKYGBgeWFtbApiYXNlX2h0dHBfdXJs" + - "OiAiaHR0cDovLzxnYXRld2F5LWFkZHJlc3M+IgppbnZva2VfdXJsOiAiaHR0cDovLzxnYXRld2F5" + - "LWFkZHJlc3M+IgpiYXNlX2dycGNfdXJsOiAiPGdhdGV3YXktYWRkcmVzcz46MTAwODEiCmFwaV9r" + - "ZXlzX3NlcnZpY2VfdXJsOiAiaHR0cDovLzxnYXRld2F5LWFkZHJlc3M+IgppY21zX3VybDogImh0" + - "dHA6Ly88Z2F0ZXdheS1hZGRyZXNzPiIKYXBpX2hvc3Q6ICJhcGkuPGdhdGV3YXktZG9tYWluPiIK" + - "YXBpX2tleXNfaG9zdDogImFwaS1rZXlzLjxnYXRld2F5LWRvbWFpbj4iCmludm9rZV9ob3N0OiAi" + - "aW52b2NhdGlvbi48Z2F0ZXdheS1kb21haW4+IgppY21zX2hvc3Q6ICJzaXMuPGdhdGV3YXktZG9t" + - "YWluPiIKYGBgCgpIb3N0IGZpZWxkcyBjb250YWluIGhvc3RuYW1lcyBvbmx5LCB3aXRob3V0IGEg" + - "VVJMIHNjaGVtZS4gYElDTVNfVVJMYCBtYXBzIG9ubHkKdG8gdGhlIHJlZ2lzdHJhdGlvbiBjb21t" + - "YW5kJ3MgYC0taWNtcy11cmxgOyBgaWNtc19ob3N0YCBpbiB0aGUgY29uZmlnIHN1cHBsaWVzCnRo" + - "ZSBHYXRld2F5IEhvc3QgaGVhZGVyLiBUaGVyZSBpcyBubyBgLS1pY21zLWhvc3RgIE1ha2Ugb3Ig" + - "Q0xJIGZsYWcuCgpSZWdpc3RlciBmcm9tIHRoZSBjb21wdXRlIGNvbnRleHQsIHRoZW4gaW5zdGFs" + - "bCB0aGUgY29tcHV0ZSBwbGFuZToKCmBgYGJhc2gKbWFrZSAtQyBkZXBsb3kvc3RhY2tzL252Y2Yt" + - "Y29tcHV0ZS1wbGFuZSByZWdpc3Rlci1jbHVzdGVyIFwKICBDTFVTVEVSX05BTUU9PGNsdXN0ZXIt" + - "bmFtZT4gXAogIE5DQV9JRD08bmNhLWlkPiBcCiAgQ0xVU1RFUl9SRUdJT049PHJlZ2lvbj4gXAog" + - "IElDTVNfVVJMPWh0dHA6Ly88Z2F0ZXdheS1hZGRyZXNzPiBcCiAgS1VCRUNPTkZJR19GSUxFPTxj" + - "b21wdXRlLXBsYW5lLWt1YmVjb25maWc+IFwKICBOVkNGX0NMST08cGF0aC10by1udmNmLWNsaT4g" + - "XAogIE5WQ0ZfQ0xJX0NPTkZJRz08cGF0aC10by1udmNmLWNsaS1jb25maWc+CgptYWtlIC1DIGRl" + - "cGxveS9zdGFja3MvbnZjZi1jb21wdXRlLXBsYW5lIGluc3RhbGwgXAogIENMVVNURVJfTkFNRT08" + - "Y2x1c3Rlci1uYW1lPiBcCiAgSEVMTUZJTEVfRU5WPTxlbnY+IFwKICBLVUJFQ09ORklHX0ZJTEU9" + - "PGNvbXB1dGUtcGxhbmUta3ViZWNvbmZpZz4KYGBgCgpSZWdpc3RyYXRpb24gZGlzY292ZXJzIHRo" + - "ZSBPSURDIGlzc3VlciBhbmQgSldLUyBmcm9tIHRoZSBjb21wdXRlIGt1YmUgY29udGV4dC4KSW4g" + - "YSBtdWx0aS1jbHVzdGVyIGRlcGxveW1lbnQsIGBLVUJFQ09ORklHX0ZJTEVgIGlzIHJlcXVpcmVk" + - "IG9uIGJvdGgKY29tcHV0ZS1wbGFuZSBjb21tYW5kcy4gSW4gYSBzYW1lLWNsdXN0ZXIgZGVwbG95" + - "bWVudCBpdCBtYXkgYmUgb21pdHRlZCBvbmx5CndoZW4gdGhlIGFtYmllbnQgY29udGV4dCBpcyBh" + - "bHJlYWR5IHRoZSB0YXJnZXQgY2x1c3Rlci4KCiMjIENMSSBQcm9maWxlIEZsb3cKClJ1biB0aGUg" + - "Q0xJIGNvbnRyb2wtcGxhbmUgaW5zdGFsbCBmaXJzdC4gSXQgY3JlYXRlcyB0aGUgcHJvZmlsZSBj" + - "b25zdW1lZCBieQpjb21wdXRlLXBsYW5lIHJlZ2lzdHJhdGlvbjoKCmBgYGJhc2gKbnZjZi1jbGkg" + - "LS1jb25maWcgPGNvbmZpZz4gc2VsZi1ob3N0ZWQgXAogIC0tY29udHJvbC1wbGFuZS1zdGFjayBk" + - "ZXBsb3kvc3RhY2tzL3NlbGYtbWFuYWdlZCBcCiAgLS1jb21wdXRlLXBsYW5lLXN0YWNrIGRlcGxv" + - "eS9zdGFja3MvbnZjZi1jb21wdXRlLXBsYW5lIFwKICAtLWVudiA8ZW52PiBcCiAgLS1wbGFpbiBc" + - "CiAgLS1jb250cm9sLXBsYW5lLWNvbnRleHQgPGNvbnRyb2wtcGxhbmUtY29udGV4dD4gXAogIC0t" + - "Y29tcHV0ZS1wbGFuZS1jb250ZXh0IDxjb21wdXRlLXBsYW5lLWNvbnRleHQ+IFwKICBpbnN0YWxs" + - "IC0tY29udHJvbC1wbGFuZSBcCiAgLS1jbHVzdGVyLW5hbWUgPGNvbnRyb2wtcGxhbmUtY2x1c3Rl" + - "ci1uYW1lPiBcCiAgLS1yZWdpb24gPHJlZ2lvbj4gXAogIC0tbmNhLWlkIDxuY2EtaWQ+Cgp0ZXN0" + - "IC1zIGRlcGxveS9zdGFja3Mvc2VsZi1tYW5hZ2VkL291dC9jb250cm9sLXBsYW5lLXByb2ZpbGUu" + - "eWFtbApgYGAKClVzZSB0aGUgZnVsbCBwZXJzaXN0ZW50IHByZWZpeCBmb3IgYm90aCBjb21wdXRl" + - "LXBsYW5lIGNvbW1hbmRzOgoKYGBgYmFzaApudmNmLWNsaSAtLWNvbmZpZyA8Y29uZmlnPiBzZWxm" + - "LWhvc3RlZCBcCiAgLS1jb250cm9sLXBsYW5lLXN0YWNrIGRlcGxveS9zdGFja3Mvc2VsZi1tYW5h" + - "Z2VkIFwKICAtLWNvbXB1dGUtcGxhbmUtc3RhY2sgZGVwbG95L3N0YWNrcy9udmNmLWNvbXB1dGUt" + - "cGxhbmUgXAogIC0tZW52IDxlbnY+IFwKICAtLXBsYWluIFwKICBjb21wdXRlLXBsYW5lIHJlZ2lz" + - "dGVyIFwKICAtLWNvbnRyb2wtcGxhbmUtcHJvZmlsZSBkZXBsb3kvc3RhY2tzL3NlbGYtbWFuYWdl" + - "ZC9vdXQvY29udHJvbC1wbGFuZS1wcm9maWxlLnlhbWwgXAogIC0tY2x1c3Rlci1uYW1lIDxjb21w" + - "dXRlLXBsYW5lLWNsdXN0ZXItbmFtZT4gXAogIC0ta3ViZS1jb250ZXh0IDxjb21wdXRlLXBsYW5l" + - "LWNvbnRleHQ+IFwKICAtLXJlZ2lvbiA8cmVnaW9uPiBcCiAgLS1vdXRwdXQgZGVwbG95L3N0YWNr" + - "cy9udmNmLWNvbXB1dGUtcGxhbmUvb3V0Lzxjb21wdXRlLXBsYW5lLWNsdXN0ZXItbmFtZT4tcmVn" + - "aXN0ZXItdmFsdWVzLnlhbWwKCm52Y2YtY2xpIC0tY29uZmlnIDxjb25maWc+IHNlbGYtaG9zdGVk" + - "IFwKICAtLWNvbnRyb2wtcGxhbmUtc3RhY2sgZGVwbG95L3N0YWNrcy9zZWxmLW1hbmFnZWQgXAog" + - "IC0tY29tcHV0ZS1wbGFuZS1zdGFjayBkZXBsb3kvc3RhY2tzL252Y2YtY29tcHV0ZS1wbGFuZSBc" + - "CiAgLS1lbnYgPGVudj4gXAogIC0tcGxhaW4gXAogIGNvbXB1dGUtcGxhbmUgaW5zdGFsbCBcCiAg" + - "LS12YWx1ZXMgZGVwbG95L3N0YWNrcy9udmNmLWNvbXB1dGUtcGxhbmUvb3V0Lzxjb21wdXRlLXBs" + - "YW5lLWNsdXN0ZXItbmFtZT4tcmVnaXN0ZXItdmFsdWVzLnlhbWwgXAogIC0ta3ViZS1jb250ZXh0" + - "IDxjb21wdXRlLXBsYW5lLWNvbnRleHQ+IFwKICAtLWNsdXN0ZXItbmFtZSA8Y29tcHV0ZS1wbGFu" + - "ZS1jbHVzdGVyLW5hbWU+CmBgYAoKUmVnaXN0cmF0aW9uIHNlbGVjdHMgdGhlIHByb2ZpbGUncyBp" + - "bi1jbHVzdGVyIG9yIGNvbXB1dGUtcmVhY2hhYmxlIGVuZHBvaW50cwpmcm9tIHRoZSBjb21wdXRl" + - "IGNvbnRleHQgYW5kIHByb2JlcyBpdHMgSldLUy4gRG8gbm90IHJlcGxhY2UgcHJvZmlsZSBlbmRw" + - "b2ludHMKd2l0aCBoYW5kLWF1dGhvcmVkIEhlbG1maWxlIGVuZHBvaW50IHZhbHVlcy4KCiMjIFB1" + - "bGwgU2VjcmV0cwoKQ3JlYXRlIHRoZSBjb250cm9sLXBsYW5lIHNlY3JldCBpbiB0aGVzZSBuYW1l" + - "c3BhY2VzIG9uIHRoZSBjb250cm9sLXBsYW5lCmNvbnRleHQ6CgpgYGB0ZXh0CmNhc3NhbmRyYS1z" + - "eXN0ZW0gbmF0cy1zeXN0ZW0gbnZjZiBhcGkta2V5cyBlc3Mgc2lzIHZhdWx0LXN5c3RlbSBjZXJ0" + - "LW1hbmFnZXIKYGBgCgpPbiB0aGUgY29tcHV0ZSBjb250ZXh0LCBjcmVhdGUgdGhlIHNlY3JldCBv" + - "bmx5IGluIGBudmNhLW9wZXJhdG9yYCBhbmQgcmVmZXJlbmNlCml0IHRocm91Z2ggYGdsb2JhbC5p" + - "bWFnZVB1bGxTZWNyZXRzYCBpbiB0aGUgY29tcHV0ZS1wbGFuZSBlbnZpcm9ubWVudC4gVGhlIGNo" + - "YXJ0CmFuZCBvcGVyYXRvciB1c2UgaXQgaW4gYG52Y2Etb3BlcmF0b3JgIGFuZCBtaXJyb3IgaXQg" + - "dG8gYG52Y2Etc3lzdGVtYC4gRG8gbm90IGFkZAplaXRoZXIgbmFtZXNwYWNlIHRvIHRoZSBjb250" + - "cm9sLXBsYW5lIGxpc3QuIEZvciBjb21tYW5kcyBhbmQgcG9saWN5IGV4YW1wbGVzLApzZWUgW3B1" + - "bGwtc2VjcmV0cy5tZF0ocHVsbC1zZWNyZXRzLm1kKS4KCiMjIFZlcmlmaWNhdGlvbgoKVXNlIHRo" + - "ZSBleHBsaWNpdCBHYXRld2F5IG5hbWUgYW5kIG5hbWVzcGFjZToKCmBgYGJhc2gKa3ViZWN0bCAt" + - "LWNvbnRleHQgPGNvbnRyb2wtcGxhbmUtY29udGV4dD4gZ2V0IGdhdGV3YXkgbnZjZi1nYXRld2F5" + - "IFwKICAtbiBlbnZveS1nYXRld2F5IC1vIHdpZGUKa3ViZWN0bCAtLWNvbnRleHQgPGNvbnRyb2wt" + - "cGxhbmUtY29udGV4dD4gZ2V0IHRjcHJvdXRlIC1uIGVudm95LWdhdGV3YXkKa3ViZWN0bCAtLWNv" + - "bnRleHQgPGNvbXB1dGUtcGxhbmUtY29udGV4dD4gcm9sbG91dCBzdGF0dXMgXAogIGRlcGxveW1l" + - "bnQvbnZjYS1vcGVyYXRvciAtbiBudmNhLW9wZXJhdG9yIC0tdGltZW91dD0xMG0Ka3ViZWN0bCAt" + - "LWNvbnRleHQgPGNvbXB1dGUtcGxhbmUtY29udGV4dD4gd2FpdCBcCiAgbnZjZmJhY2tlbmQvPGNv" + - "bXB1dGUtcGxhbmUtY2x1c3Rlci1uYW1lPiAtbiBudmNhLW9wZXJhdG9yIFwKICAtLWZvcj1qc29u" + - "cGF0aD0ney5zdGF0dXMuYWdlbnRTdGF0dXN9Jz1oZWFsdGh5IC0tdGltZW91dD0xMG0Ka3ViZWN0" + - "bCAtLWNvbnRleHQgPGNvbXB1dGUtcGxhbmUtY29udGV4dD4gZ2V0IHNlY3JldCA8cHVsbC1zZWNy" + - "ZXQ+IC1uIG52Y2Etc3lzdGVtCmBgYAoKIyMgVGVhcmRvd24KCkRlc3Ryb3kgYW5kIGRlcmVnaXN0" + - "ZXIgZXZlcnkgY29tcHV0ZSBwbGFuZSBiZWZvcmUgdGhlIGNvbnRyb2wgcGxhbmUuIEJvdGgKaGFu" + - "ZG9mZnMgY2FuIGRlc3Ryb3kgdGhlIGNvbnRyb2wgcGxhbmUsIHNvIGFwcGx5IHRoaXMgcHJlZmxp" + - "Z2h0IGJlZm9yZSBlaXRoZXIKb25lLiBQcmVzZXJ2ZSB0aGUgYGNlcnQtbWFuYWdlcmAgbmFtZXNw" + - "YWNlIGJ5IGRlZmF1bHQuIE5hbWVzcGFjZSBwcmVzZXJ2YXRpb24KZG9lcyBub3QgcHJlc2VydmUg" + - "dGhlIEhlbG0gcmVsZWFzZS4gSWRlbnRpZnkgd2hldGhlciBjZXJ0LW1hbmFnZXIgaXMgZXh0ZXJu" + - "YWwuCkZvciBhbiBleHRlcm5hbCBpbnN0YWxsYXRpb24sIHJlcXVpcmUgZWZmZWN0aXZlIGBjZXJ0" + - "TWFuYWdlci5lbmFibGVkOiBmYWxzZWAKYW5kIGFib3J0IGlmIGl0IGlzIHRydWUuIEZvciBhbiBp" + - "bnRlbmRlZCBzdGFjay1tYW5hZ2VkIHJlbGVhc2Ugd2l0aCBlZmZlY3RpdmUKYGNlcnRNYW5hZ2Vy" + - "LmVuYWJsZWQ6IHRydWVgLCB2ZXJpZnkgbWF0Y2hpbmcgc3RhY2sgSGVsbSByZWxlYXNlIG1ldGFk" + - "YXRhIGFuZAphYm9ydCBvbiBtaXNzaW5nIG9yIG1pc21hdGNoZWQgZXZpZGVuY2UuIE9ubHkgcGxh" + - "biBuYW1lc3BhY2UgcmVtb3ZhbCBhZnRlcgp2ZXJpZnlpbmcgbmFtZXNwYWNlIHByb3ZlbmFuY2Ug" + - "YW5kIGdldHRpbmcgZXhwbGljaXQgY29uZmlybWF0aW9uOyBvdGhlcndpc2UKZG8gbm90IGRlbGV0" + - "ZSBpdC4KCkZvciB0aGUgSGVsbWZpbGUgdmFsdWVzIGZsb3csIHJldGFpbiBlYWNoIHJlZ2lzdHJh" + - "dGlvbiB2YWx1ZXMgZmlsZSB1bnRpbApjbGVhbnVwIGlzIHZlcmlmaWVkOgoKYGBgYmFzaAptYWtl" + - "IC1DIGRlcGxveS9zdGFja3MvbnZjZi1jb21wdXRlLXBsYW5lIGRlc3Ryb3kgXAogIENMVVNURVJf" + - "TkFNRT08Y2x1c3Rlci1uYW1lPiBcCiAgSEVMTUZJTEVfRU5WPTxlbnY+IFwKICBLVUJFQ09ORklH" + - "X0ZJTEU9PGNvbXB1dGUtcGxhbmUta3ViZWNvbmZpZz4KCjxudmNmLWNsaT4gLS1jb25maWcgPGNv" + - "bmZpZz4gY2x1c3RlciBkZWxldGUgXAogIC0tY2x1c3Rlci1pZCA8Y2x1c3Rlci1pZC1mcm9tLXJl" + - "Z2lzdHJhdGlvbi12YWx1ZXM+IFwKICAtLW5jYS1pZCA8bmNhLWlkPiBcCiAgLS1pZ25vcmUtbWlz" + - "c2luZwpgYGAKClJlYWQgYGNsdXN0ZXJJRGAgYW5kIGBuY2FJRGAgZnJvbQpgZGVwbG95L3N0YWNr" + - "cy9udmNmLWNvbXB1dGUtcGxhbmUvcmVnaXN0cmF0aW9uLzxjbHVzdGVyLW5hbWU+LXJlZ2lzdGVy" + - "LXZhbHVlcy55YW1sYC4KU2hvdyB0aGUgZXhhY3QgY2x1c3RlciBJRCBhbmQgZ2V0IGNvbmZpcm1h" + - "dGlvbiBiZWZvcmUgYGNsdXN0ZXIgZGVsZXRlYC4gUmVwZWF0CmNvbXB1dGUgZGVzdHJveSBhbmQg" + - "ZGVyZWdpc3RyYXRpb24gZm9yIGV2ZXJ5IGNvbXB1dGUgY2x1c3RlciwgdGhlbiBkZXN0cm95IHRo" + - "ZQpjb250cm9sIHBsYW5lLgoKYGBgYmFzaApLVUJFQ09ORklHPTxjb250cm9sLXBsYW5lLWt1YmVj" + - "b25maWc+IFwKbWFrZSAtQyBkZXBsb3kvc3RhY2tzL3NlbGYtbWFuYWdlZCBkZXN0cm95IFwKICBI" + - "RUxNRklMRV9FTlY9PGVudj4gXAogIEtVQkVDT05GSUdfRklMRT08Y29udHJvbC1wbGFuZS1rdWJl" + - "Y29uZmlnPgpgYGAKCkJvdGggYEtVQkVDT05GSUdgIGFuZCBgS1VCRUNPTkZJR19GSUxFYCBhcmUg" + - "cmVxdWlyZWQgb24gdGhlIGNvbnRyb2wtcGxhbmUKZGVzdHJveSBiZWNhdXNlIGl0cyBuYW1lc3Bh" + - "Y2UgY2xlYW51cCB1c2VzIGFtYmllbnQgYGt1YmVjdGxgLgoKRm9yIHRoZSBDTEkgcHJvZmlsZSBm" + - "bG93LCBrZWVwCmBkZXBsb3kvc3RhY2tzL252Y2YtY29tcHV0ZS1wbGFuZS9vdXQvPGNsdXN0ZXIt" + - "bmFtZT4tcmVnaXN0ZXItdmFsdWVzLnlhbWxgCnVudGlsIHRlYXJkb3duIGNvbXBsZXRlcyBiZWNh" + - "dXNlIGBkb3duYCByZWFkcyBpdHMgY2x1c3RlciBJRC4gRmlyc3QgcXVlcnkgdGhlCmxpdmUgcmVn" + - "aXN0cmF0aW9uIHNldCB3aXRoIHRoZSBzYW1lIGNvbXBsZXRlIHBlcnNpc3RlbnQgcHJlZml4OgoK" + - "YGBgYmFzaApudmNmLWNsaSAtLWNvbmZpZyA8Y29uZmlnPiBzZWxmLWhvc3RlZCBcCiAgLS1jb250" + - "cm9sLXBsYW5lLXN0YWNrIGRlcGxveS9zdGFja3Mvc2VsZi1tYW5hZ2VkIFwKICAtLWNvbXB1dGUt" + - "cGxhbmUtc3RhY2sgZGVwbG95L3N0YWNrcy9udmNmLWNvbXB1dGUtcGxhbmUgXAogIC0tZW52IDxl" + - "bnY+IFwKICAtLXBsYWluIFwKICAtLWNvbnRyb2wtcGxhbmUtY29udGV4dCA8Y29udHJvbC1wbGFu" + - "ZS1jb250ZXh0PiBcCiAgLS1jb21wdXRlLXBsYW5lLWNvbnRleHQgPGNvbXB1dGUtcGxhbmUtY29u" + - "dGV4dD4gXAogIC0taWNtcy11cmwgPGNvbXB1dGUtcmVhY2hhYmxlLWljbXMtdXJsPiBcCiAgc3Rh" + - "dHVzIFwKICAtLWNsdXN0ZXItbmFtZSA8Y29tcHV0ZS1wbGFuZS1jbHVzdGVyLW5hbWU+IFwKICAt" + - "LW5jYS1pZCA8bmNhLWlkPgpgYGAKClNob3cgdGhlIHJlZ2lzdGVyZWQgY29tcHV0ZS1wbGFuZSBs" + - "aXN0LiBOYW1lZC1jbHVzdGVyIGBkb3duIC0tcGxhbi1vbmx5YCBkb2VzCm5vdCBxdWVyeSBJQ01T" + - "LCBzbyBpdCBwcmV2aWV3cyBvbmx5IHRoYXQgY29tcHV0ZSBwbGFuZSBhbmQgY2Fubm90IHNob3cg" + - "dGhlCmNvbmRpdGlvbmFsIGNvbnRyb2wtcGxhbmUgcmVtb3ZhbC4gSWYgdGhlIHRhcmdldCBpcyB0" + - "aGUgbGFzdCByZWdpc3RlcmVkCmNsdXN0ZXIsIHJlYWwgYGRvd25gIGFsc28gZGVzdHJveXMgdGhl" + - "IGNvbnRyb2wgcGxhbmUuIEdldCBleHBsaWNpdCBjb25maXJtYXRpb24KZm9yIGJvdGggcmVtb3Zh" + - "bHMgYmVmb3JlIGNvbnRpbnVpbmcuIFRoZW4gcHJldmlldyBhbmQgZXhlY3V0ZSB3aXRoIHRoZSBz" + - "YW1lCnByZWZpeDoKCmBgYGJhc2gKbnZjZi1jbGkgLS1jb25maWcgPGNvbmZpZz4gc2VsZi1ob3N0" + + "ICAiZ3JvdW5kX3RydXRoIjogIlRoZSBhZ2VudCBleHBsYWluZWQgdGhlIHZhbHVlcyBwcmVjZWRl" + + "bmNlIGNoYWluIChiYXNlLnlhbWwsIGVudiBZQU1MLCBnbG9iYWwueWFtbC5nb3RtcGwsIHNlY3Jl" + + "dHMsIGlubGluZSB2YWx1ZXMpLCBpZGVudGlmaWVkIHRoYXQgZ2xvYmFsLnlhbWwuZ290bXBsIG9u" + + "bHkgcGFzc2VzIHRocm91Z2ggc3BlY2lmaWMga2V5cywgYW5kIGV4cGxhaW5lZCB0aGF0IGFyYml0" + + "cmFyeSBjaGFydCB2YWx1ZXMgbXVzdCB1c2UgcmVsZWFzZSBpbmxpbmUgdmFsdWVzIGJsb2Nrcy4i" + + "LAogICAgImV4cGVjdGVkX2JlaGF2aW9yIjogWwogICAgICAiVGhlIGFnZW50IHJlYWQgdGhlIG52" + + "Y2Ytc2VsZi1tYW5hZ2VkLWluc3RhbGxhdGlvbiBTS0lMTC5tZCBiZWZvcmUgdGFraW5nIGFjdGlv" + + "biIsCiAgICAgICJUaGUgYWdlbnQgZXhwbGFpbmVkIHRoZSB2YWx1ZXMgcHJlY2VkZW5jZSBjaGFp" + + "bjogYmFzZS55YW1sIC0+IGVudiBZQU1MIC0+IGdsb2JhbC55YW1sLmdvdG1wbCAtPiBzZWNyZXRz" + + "IC0+IGlubGluZSB2YWx1ZXMiLAogICAgICAiVGhlIGFnZW50IGlkZW50aWZpZWQgdGhhdCBnbG9i" + + "YWwueWFtbC5nb3RtcGwgb25seSBwYXNzZXMgdGhyb3VnaCBzcGVjaWZpYyBrZXlzIChjYXNzYW5k" + + "cmEucmVwbGljYUNvdW50LCBnbG9iYWwuc3RvcmFnZUNsYXNzLCBnbG9iYWwuaW1hZ2UsIGV0Yy4p" + + "IiwKICAgICAgIlRoZSBhZ2VudCBleHBsYWluZWQgdGhhdCBhcmJpdHJhcnkgY2hhcnQgdmFsdWVz" + + "IG11c3QgYmUgc2V0IHZpYSByZWxlYXNlIGlubGluZSB2YWx1ZXM6IGJsb2NrcyIsCiAgICAgICJU" + + "aGUgYWdlbnQgbGlzdGVkIGV4YW1wbGVzIG9mIHZhbHVlcyB0aGF0IGFyZSBwcm9wYWdhdGVkIGFu" + + "ZCBhcmJpdHJhcnkgY2hhcnQgdmFsdWVzIHRoYXQgcmVxdWlyZSBhIHJlbGVhc2UgdmFsdWVzIGJs" + + "b2NrIgogICAgXQogIH0sCiAgewogICAgImlkIjogIlMxNC1lbmFibGUtbGxtLWZ1bmN0aW9ucyIs" + + "CiAgICAicXVlc3Rpb24iOiAiSSBuZWVkIHRvIGRlcGxveSBmdW5jdGlvblR5cGU6IExMTSBmdW5j" + + "dGlvbnMgb24gYSBzZWxmLW1hbmFnZWQsIHNpbmdsZS1ub2RlIGlzb2xhdGVkIHRlc3QgY2x1c3Rl" + + "ci4gV2hhdCBIZWxtZmlsZSBjb25maWd1cmF0aW9uIGFuZCB2ZXJpZmljYXRpb24gc3RlcHMgZG8g" + + "SSBuZWVkPyIsCiAgICAiZXhwZWN0ZWRfc2tpbGwiOiAibnZjZi1zZWxmLW1hbmFnZWQtaW5zdGFs" + + "bGF0aW9uIiwKICAgICJncm91bmRfdHJ1dGgiOiAiVGhlIGFnZW50IGVuYWJsZWQgdGhlIExMTSBh" + + "ZGRvbiB3aXRoIGFkZG9ucy5sbG0uZW5hYmxlZCwgY29uZmlndXJlZCBzaW5nbGUgcmVwbGljYXMg" + + "Zm9yIHRoZSBMTE0gQVBJIEdhdGV3YXkgYW5kIHJlcXVlc3Qgcm91dGVyLCBhbmQgZXhwbGFpbmVk" + + "IHRoYXQgdGhlIHJlcXVlc3Qgcm91dGVyIHVzZXMgcG93ZXItb2YtdHdvIHdoZW4gbm8gbG9hZC1i" + + "YWxhbmNlciBjb25maWd1cmF0aW9uIGlzIHNldC4gSXQgbGlua2VkIHRoZSBjYW5vbmljYWwgcmVx" + + "dWVzdC1yb3V0ZXIgbG9hZC1iYWxhbmNpbmcgZ3VpZGUgZm9yIG90aGVyIHJvdXRpbmcgbWV0aG9k" + + "cy4gRm9yIHRoZSBpc29sYXRlZCBwbGFpbnRleHQgY2x1c3RlciwgaXQgcmVsaWVkIG9uIHRoZSBz" + + "ZWxmLW1hbmFnZWQgc3RhY2sgZGVmYXVsdCBmb3IgdGhlIGJ1bmRsZWQgZ2F0ZXdheS10by1BUEkg" + + "YXV0aGVudGljYXRpb24gaG9wIGFuZCBpbmNsdWRlZCBhZ2VudENvbmZpZy5tZXJnZUNvbmZpZy53" + + "b3JrbG9hZC5zdGFyZ2F0ZVFVSUNJbnNlY3VyZSBmb3IgdGhlIHNlcGFyYXRlIHdvcmtlciB0cmFu" + + "c3BvcnQsIHdpdGggYSB3YXJuaW5nIHRoYXQgcGxhaW50ZXh0IHdvcmtlciB0cmFuc3BvcnQgaXMg" + + "bm90IGZvciBwcm9kdWN0aW9uLiBJdCByZWZyZXNoZWQgdGhlIGNvbXB1dGUgcGxhbmUgdXNpbmcg" + + "dGhlIHNhbWUgaGFuZG9mZiB1c2VkIGR1cmluZyBpbnN0YWxsYXRpb24sIHJlZGVwbG95ZWQgZXhp" + + "c3RpbmcgTExNIGZ1bmN0aW9ucywgYW5kIHZlcmlmaWVkIHRoZSBMTE0gZGVwbG95bWVudHMsIHJv" + + "dXRlLCBhbmQgbGxtLXdvcmtlciBzaWRlY2FyLiIsCiAgICAiZXhwZWN0ZWRfYmVoYXZpb3IiOiBb" + + "CiAgICAgICJUaGUgYWdlbnQgdXNlZCBhZGRvbnMubGxtLmVuYWJsZWQsIG5vdCB0aGUgb2Jzb2xl" + + "dGUgbGxtLmVuYWJsZWQgcGF0aCIsCiAgICAgICJUaGUgYWdlbnQgc2V0IGdhdGV3YXkgYW5kIHJl" + + "cXVlc3RSb3V0ZXIgcmVwbGljYUNvdW50IHRvIDEgZm9yIHRoZSBzaW5nbGUtbm9kZSB0ZXN0IGNs" + + "dXN0ZXIiLAogICAgICAiVGhlIGFnZW50IHN0YXRlZCB0aGF0IHRoZSByZXF1ZXN0IHJvdXRlciBk" + + "ZWZhdWx0cyB0byBwb3dlci1vZi10d28gYW5kIGxpbmtlZCB0aGUgY2Fub25pY2FsIHJlcXVlc3Qt" + + "cm91dGVyIGxvYWQtYmFsYW5jaW5nIGd1aWRlIGZvciBvdGhlciBtZXRob2RzIiwKICAgICAgIlRo" + + "ZSBhZ2VudCBleHBsYWluZWQgdGhhdCB0aGUgYnVuZGxlZCBnYXRld2F5LXRvLUFQSSBhdXRoZW50" + + "aWNhdGlvbiBob3AgZGVmYXVsdHMgdG8gcGxhaW50ZXh0LCBpbmNsdWRlZCB3b3JrbG9hZC5zdGFy" + + "Z2F0ZVFVSUNJbnNlY3VyZSBvbmx5IGZvciB0aGUgaXNvbGF0ZWQgd29ya2VyIHRyYW5zcG9ydCwg" + + "YW5kIHdhcm5lZCBhZ2FpbnN0IHBsYWludGV4dCB3b3JrZXIgdHJhbnNwb3J0IGluIHByb2R1Y3Rp" + + "b24iLAogICAgICAiVGhlIGFnZW50IGFwcGxpZWQgdGhlIGNvbnRyb2wtcGxhbmUgSGVsbWZpbGUg" + + "ZW52aXJvbm1lbnQsIHJlZnJlc2hlZCB0aGUgY29tcHV0ZSBwbGFuZSB3aXRoIHRoZSBzYW1lIHNl" + + "bGVjdGVkIGhhbmRvZmYsIGFuZCByZWNyZWF0ZWQgb3IgcmVkZXBsb3llZCBleGlzdGluZyBMTE0g" + + "ZnVuY3Rpb25zIiwKICAgICAgIlRoZSBhZ2VudCB2ZXJpZmllZCBsbG0tYXBpLWdhdGV3YXksIGxs" + + "bS1yZXF1ZXN0LXJvdXRlciwgdGhlIExMTSBIVFRQUm91dGUsIGFuZCB0aGUgbGxtLXdvcmtlciBz" + + "aWRlY2FyIGluY2x1ZGluZyAtLXF1aWMtaW5zZWN1cmUiCiAgICBdCiAgfSwKICB7CiAgICAiaWQi" + + "OiAiRzgtTkVHLW5vdC1mb3ItazNkIiwKICAgICJxdWVzdGlvbiI6ICJTZXQgdXAgYSBsb2NhbCBO" + + "VkNGIGRldiBjbHVzdGVyIHVzaW5nIGszZCBmb3IgdGVzdGluZyIsCiAgICAiZXhwZWN0ZWRfc2tp" + + "bGwiOiBudWxsLAogICAgInNob3VsZF90cmlnZ2VyIjogZmFsc2UsCiAgICAiZ3JvdW5kX3RydXRo" + + "IjogIlRoaXMgaXMgYSBsb2NhbCBrM2QgZGV2ZWxvcG1lbnQgcmVxdWVzdCB0aGF0IGJlbG9uZ3Mg" + + "dG8gdGhlIG52Y2Ytc2VsZi1ob3N0ZWQtbG9jYWwtZGV2IHNraWxsLCBub3QgbnZjZi1zZWxmLW1h" + + "bmFnZWQtaW5zdGFsbGF0aW9uLiBUaGUgYWdlbnQgZGlkIG5vdCByZWFkIG9yIHVzZSB0aGUgc2Vs" + + "Zi1tYW5hZ2VkIGluc3RhbGxhdGlvbiBza2lsbCBhbmQgaW5zdGVhZCBwcm92aWRlZCBrM2Qtc3Bl" + + "Y2lmaWMgZ3VpZGFuY2UuIiwKICAgICJleHBlY3RlZF9iZWhhdmlvciI6IFsKICAgICAgIlRoZSBh" + + "Z2VudCBkaWQgbm90IHJlYWQgbnZjZi1zZWxmLW1hbmFnZWQtaW5zdGFsbGF0aW9uL1NLSUxMLm1k" + + "IiwKICAgICAgIlRoZSBhZ2VudCBkaWQgbm90IHByb3ZpZGUgaGVsbWZpbGUtYmFzZWQgcHJvZHVj" + + "dGlvbiBkZXBsb3ltZW50IGluc3RydWN0aW9ucyIsCiAgICAgICJUaGUgYWdlbnQgcHJvdmlkZWQg" + + "azNkLXNwZWNpZmljIGxvY2FsIGRldmVsb3BtZW50IGd1aWRhbmNlIG9yIGRlZmVycmVkIHRvIHRo" + + "ZSBhcHByb3ByaWF0ZSBsb2NhbC1kZXYgc2tpbGwiCiAgICBdCiAgfQpdClBLBwhHVWpWSlYAAEpW" + + "AABQSwMEFAAIAAAAAAAhAAAAAAAAAAAAAAAAAC8ACQBkYXRhL252Y2Ytc2VsZi1tYW5hZ2VkLWlu" + + "c3RhbGxhdGlvbi9leGFtcGxlcy5tZFVUBQABAKbOEiMgRXhhbXBsZXMKCldvcmtlZCBleGFtcGxl" + + "cyBiYXNlZCBvbiByZWFsIGRlcGxveW1lbnQgc2NlbmFyaW9zLgoKIyMgRXhhbXBsZSAxOiBPdmVy" + + "cmlkZSBDYXNzYW5kcmEgUmVzb3VyY2VzCgpUaGUgZGVmYXVsdCBDYXNzYW5kcmEgcmVzb3VyY2Ug" + + "bGltaXRzIGNhdXNlIE9PTSBvbiBsYXJnZSBpbnN0YW5jZSB0eXBlcyAoZS5nLiwgYHA1LjQ4eGxh" + + "cmdlYCkuCgpQcm9ibGVtOiBDYXNzYW5kcmEgcG9kcyByZXN0YXJ0IHdpdGggYE9PTUtpbGxlZGAu" + + "CgpTb2x1dGlvbjogT3ZlcnJpZGUgcmVzb3VyY2VzIHZpYSBoZWxtZmlsZSByZWxlYXNlIHZhbHVl" + + "cyBibG9jay4KCiMjIyBTdGVwIDE6IENhcHR1cmUgYmFzZWxpbmUKCmBgYGJhc2gKSEVMTUZJTEVf" + + "RU5WPTxlbnY+IGhlbG1maWxlIC0tc2VsZWN0b3IgbmFtZT1jYXNzYW5kcmEgdGVtcGxhdGUgPiAv" + + "dG1wL2Nhc3MtYmVmb3JlLnlhbWwKZ3JlcCAtQTggJ3Jlc291cmNlczonIC90bXAvY2Fzcy1iZWZv" + + "cmUueWFtbCB8IGhlYWQgLTEwCmBgYAoKIyMjIFN0ZXAgMjogQWRkIG92ZXJyaWRlIHRvIGhlbG1m" + + "aWxlLmQvMDEtZGVwZW5kZW5jaWVzLnlhbWwuZ290bXBsCgpJbiB0aGUgZXhhbXBsZXMgYmVsb3cs" + + "IGA8cHJpdmF0ZS12YWx1ZXM+YCByZWZlcnMgdG8gdGhlIGBzZWNyZXRzL2AgZGlyZWN0b3J5IGF0" + + "IHRoZSBoZWxtZmlsZSBzdGFjayByb290LgoKYGBgeWFtbAotIG5hbWU6IGNhc3NhbmRyYQogIHZl" + + "cnNpb246IDAuOC4wCiAgY29uZGl0aW9uOiBjYXNzYW5kcmEuZW5hYmxlZAogIG5hbWVzcGFjZTog" + + "Y2Fzc2FuZHJhLXN5c3RlbQogIDw8OiAqZGVwZW5kZW5jeQogIHZhbHVlczoKICAgIC0gLi4vZ2xv" + + "YmFsLnlhbWwuZ290bXBsCiAgICAtIC4uLzxwcml2YXRlLXZhbHVlcz4ve3sgcmVxdWlyZWRFbnYg" + + "IkhFTE1GSUxFX0VOViIgfX0tc2VjcmV0cy55YW1sCiAgICAtIGNhc3NhbmRyYToKICAgICAgICBy" + + "ZXNvdXJjZXM6CiAgICAgICAgICBsaW1pdHM6CiAgICAgICAgICAgIGNwdTogIjgiCiAgICAgICAg" + + "ICAgIG1lbW9yeTogODE5Mk1pCiAgICAgICAgICByZXF1ZXN0czoKICAgICAgICAgICAgY3B1OiAi" + + "MiIKICAgICAgICAgICAgbWVtb3J5OiA0MDk2TWkKYGBgCgojIyMgU3RlcCAzOiBWZXJpZnkgYW5k" + + "IGFwcGx5CgpgYGBiYXNoCkhFTE1GSUxFX0VOVj08ZW52PiBoZWxtZmlsZSAtLXNlbGVjdG9yIG5h" + + "bWU9Y2Fzc2FuZHJhIHRlbXBsYXRlID4gL3RtcC9jYXNzLWFmdGVyLnlhbWwKZGlmZiAvdG1wL2Nh" + + "c3MtYmVmb3JlLnlhbWwgL3RtcC9jYXNzLWFmdGVyLnlhbWwgICMgQ29uZmlybSByZXNvdXJjZXMg" + + "Y2hhbmdlZApIRUxNRklMRV9FTlY9PGVudj4gaGVsbWZpbGUgLS1zZWxlY3RvciBuYW1lPWNhc3Nh" + + "bmRyYSBzeW5jCmBgYAoKTm90ZTogYHJlc291cmNlUHJlc2V0YCAoYSBCaXRuYW1pIGZlYXR1cmUp" + + "IGlzIG5vdCBhdmFpbGFibGUgaW4gdGhlIE5WQ0YgY2Fzc2FuZHJhIHdyYXBwZXIgY2hhcnQuIFVz" + + "ZSBleHBsaWNpdCBgcmVzb3VyY2VzYCBpbnN0ZWFkLgoKLS0tCgojIyBFeGFtcGxlIDI6IERlcGxv" + + "eSBmcm9tIE5HQyBQcml2YXRlIFJlZ2lzdHJ5IHdpdGggUHVsbCBTZWNyZXRzCgpEZXBsb3kgdGhl" + + "IE5WQ0Ygc3RhY2sgcHVsbGluZyBpbWFnZXMgZGlyZWN0bHkgZnJvbSBOR0MgKGBudmNyLmlvYCkg" + + "aW5zdGVhZCBvZiBhIG1pcnJvcmVkIHJlZ2lzdHJ5LCB1c2luZyBpbWFnZSBwdWxsIHNlY3JldHMu" + + "CgojIyMgU3RlcCAxOiBDb25maWd1cmUgZW52aXJvbm1lbnQgZm9yIE5HQwoKYGBgeWFtbAojIGVu" + + "dmlyb25tZW50cy88ZW52Pi55YW1sCmdsb2JhbDoKICBoZWxtOgogICAgc291cmNlczoKICAgICAg" + + "cmVnaXN0cnk6IG52Y3IuaW8KICAgICAgcmVwb3NpdG9yeTogbnZpZGlhL252Y2YKICBpbWFnZToK" + + "ICAgIHJlZ2lzdHJ5OiBudmNyLmlvCiAgICByZXBvc2l0b3J5OiBudmlkaWEvbnZjZgpgYGAKCiMj" + + "IyBTdGVwIDI6IEF1dGhlbnRpY2F0ZSBsb2NhbGx5IChmb3IgaGVsbWZpbGUgY2hhcnQgcHVsbHMp" + + "CgpgYGBiYXNoCmRvY2tlciBsb2dpbiBudmNyLmlvIC11ICckb2F1dGh0b2tlbicgLXAgIiROR0Nf" + + "QVBJX0tFWSIKaGVsbSByZWdpc3RyeSBsb2dpbiBudmNyLmlvIC11ICckb2F1dGh0b2tlbicgLXAg" + + "IiROR0NfQVBJX0tFWSIKYGBgCgojIyMgU3RlcCAzOiBDcmVhdGUgbmFtZXNwYWNlcyBhbmQgcHVs" + + "bCBzZWNyZXRzCgpgYGBiYXNoCmV4cG9ydCBOR0NfQVBJX0tFWT0iPHlvdXItbmdjLWFwaS1rZXk+" + + "IgoKZm9yIG5zIGluIGNhc3NhbmRyYS1zeXN0ZW0gbmF0cy1zeXN0ZW0gbnZjZiBhcGkta2V5cyBl" + + "c3Mgc2lzIHZhdWx0LXN5c3RlbSBjZXJ0LW1hbmFnZXI7IGRvCiAga3ViZWN0bCAtLWNvbnRleHQg" + + "PGNvbnRyb2wtcGxhbmUtY29udGV4dD4gY3JlYXRlIG5hbWVzcGFjZSAiJG5zIiBcCiAgICAtLWRy" + + "eS1ydW49Y2xpZW50IC1vIHlhbWwgfCBrdWJlY3RsIC0tY29udGV4dCA8Y29udHJvbC1wbGFuZS1j" + + "b250ZXh0PiBhcHBseSAtZiAtCmRvbmUKCmZvciBucyBpbiBjYXNzYW5kcmEtc3lzdGVtIG5hdHMt" + + "c3lzdGVtIG52Y2YgYXBpLWtleXMgZXNzIHNpcyB2YXVsdC1zeXN0ZW0gY2VydC1tYW5hZ2VyOyBk" + + "bwogIGt1YmVjdGwgLS1jb250ZXh0IDxjb250cm9sLXBsYW5lLWNvbnRleHQ+IGNyZWF0ZSBzZWNy" + + "ZXQgZG9ja2VyLXJlZ2lzdHJ5IG52Y3ItY3JlZHMgXAogICAgLS1kb2NrZXItc2VydmVyPW52Y3Iu" + + "aW8gXAogICAgLS1kb2NrZXItdXNlcm5hbWU9JyRvYXV0aHRva2VuJyBcCiAgICAtLWRvY2tlci1w" + + "YXNzd29yZD0iJE5HQ19BUElfS0VZIiBcCiAgICAtLW5hbWVzcGFjZT0iJG5zIiBcCiAgICAtLWRy" + + "eS1ydW49Y2xpZW50IC1vIHlhbWwgfCBrdWJlY3RsIC0tY29udGV4dCA8Y29udHJvbC1wbGFuZS1j" + + "b250ZXh0PiBhcHBseSAtZiAtCmRvbmUKYGBgCgojIyMgU3RlcCA0OiBBZGQgaW1hZ2VQdWxsU2Vj" + + "cmV0cyB0byBoZWxtZmlsZSByZWxlYXNlcwoKQWRkIHRoZSBjaGFydC1zcGVjaWZpYyBgaW1hZ2VQ" + + "dWxsU2VjcmV0c2Aga2V5IHRvIGVhY2ggcmVsZWFzZS4gRXhhbXBsZSBmb3IgY2Fzc2FuZHJhIGlu" + + "IGBoZWxtZmlsZS5kLzAxLWRlcGVuZGVuY2llcy55YW1sLmdvdG1wbGA6CgpgYGB5YW1sCi0gbmFt" + + "ZTogY2Fzc2FuZHJhCiAgdmVyc2lvbjogMC44LjAKICBjb25kaXRpb246IGNhc3NhbmRyYS5lbmFi" + + "bGVkCiAgbmFtZXNwYWNlOiBjYXNzYW5kcmEtc3lzdGVtCiAgPDw6ICpkZXBlbmRlbmN5CiAgdmFs" + + "dWVzOgogICAgLSAuLi9nbG9iYWwueWFtbC5nb3RtcGwKICAgIC0gLi4vPHByaXZhdGUtdmFsdWVz" + + "Pi97eyByZXF1aXJlZEVudiAiSEVMTUZJTEVfRU5WIiB9fS1zZWNyZXRzLnlhbWwKICAgIC0gY2Fz" + + "c2FuZHJhOgogICAgICAgIGdsb2JhbDoKICAgICAgICAgIGltYWdlUHVsbFNlY3JldHM6CiAgICAg" + + "ICAgICAgIC0gbnZjci1jcmVkcwpgYGAKClNlZSBbcmVmZXJlbmNlcy9wdWxsLXNlY3JldHMubWRd" + + "KHJlZmVyZW5jZXMvcHVsbC1zZWNyZXRzLm1kKSBmb3IgdGhlIGtleSBmb3IgZXZlcnkgY2hhcnQu" + + "CgpUaGVzZSBhcmUgY29udHJvbC1wbGFuZSBuYW1lc3BhY2VzLiBGb3IgYSBzZXBhcmF0ZSBjb21w" + + "dXRlIGNsdXN0ZXIsIGNyZWF0ZSB0aGUKc2VjcmV0IG9ubHkgaW4gYG52Y2Etb3BlcmF0b3JgOyB0" + + "aGUgY2hhcnQgYW5kIG9wZXJhdG9yIG1pcnJvciBpdCB0bwpgbnZjYS1zeXN0ZW1gLiBTZWUgW1Nw" + + "bGl0IENvbXB1dGUtUGxhbmUgSW5zdGFsbGF0aW9uXShyZWZlcmVuY2VzL2NvbXB1dGUtcGxhbmUt" + + "aW5zdGFsbGF0aW9uLm1kI3B1bGwtc2VjcmV0cykuCgojIyMgU3RlcCA1OiBEZXBsb3kKCmBgYGJh" + + "c2gKSEVMTUZJTEVfRU5WPTxlbnY+IGhlbG1maWxlIC0ta3ViZS1jb250ZXh0IDxjb250cm9sLXBs" + + "YW5lLWNvbnRleHQ+IHN5bmMKYGBgCgojIyMgU3RlcCA2OiBQYXRjaCBTZXJ2aWNlQWNjb3VudHMg" + + "Zm9yIGNoYXJ0cyB3aXRob3V0IG5hdGl2ZSBzdXBwb3J0CgpBZnRlciBQaGFzZSAxIChkZXBlbmRl" + + "bmNpZXMpIGNvbXBsZXRlcywgT3BlbkJhbyBwb2RzIHdpbGwgYmUgaW4gYEltYWdlUHVsbEJhY2tP" + + "ZmZgLiBQYXRjaCBhbmQgcmVzdGFydDoKCmBgYGJhc2gKZm9yIG5zIGluIHZhdWx0LXN5c3RlbSBu" + + "dmNmIGVzczsgZG8KICBmb3Igc2EgaW4gJChrdWJlY3RsIC0tY29udGV4dCA8Y29udHJvbC1wbGFu" + + "ZS1jb250ZXh0PiBnZXQgc2EgLW4gIiRucyIgXAogICAgLS1uby1oZWFkZXJzIC1vIGN1c3RvbS1j" + + "b2x1bW5zPSI6bWV0YWRhdGEubmFtZSIpOyBkbwogICAga3ViZWN0bCAtLWNvbnRleHQgPGNvbnRy" + + "b2wtcGxhbmUtY29udGV4dD4gcGF0Y2ggc2VydmljZWFjY291bnQgLW4gIiRucyIgIiRzYSIgXAog" + + "ICAgICAtcCAneyJpbWFnZVB1bGxTZWNyZXRzIjogW3sibmFtZSI6ICJudmNyLWNyZWRzIn1dfScK" + + "ICBkb25lCmRvbmUKa3ViZWN0bCAtLWNvbnRleHQgPGNvbnRyb2wtcGxhbmUtY29udGV4dD4gZGVs" + + "ZXRlIHBvZHMgLW4gdmF1bHQtc3lzdGVtIC0tYWxsCmBgYAoKVmVyaWZ5OgoKYGBgYmFzaAprdWJl" + + "Y3RsIC0tY29udGV4dCA8Y29udHJvbC1wbGFuZS1jb250ZXh0PiBnZXQgZXZlbnRzIC1uIHZhdWx0" + + "LXN5c3RlbSBcCiAgLS1zb3J0LWJ5PScubGFzdFRpbWVzdGFtcCcgfCBncmVwIC1pIHB1bGwKIyBT" + + "aG91bGQgc2hvdyAiU3VjY2Vzc2Z1bGx5IHB1bGxlZCBpbWFnZSIgZnJvbSBudmNyLmlvCmBgYAoK" + + "LS0tCgojIyBFeGFtcGxlIDM6IEZ1bGwgVGVhcmRvd24gYW5kIFJlaW5zdGFsbAoKV2hlbiBhIGRl" + + "cGxveW1lbnQgaXMgYnJva2VuIGFuZCB5b3UgbmVlZCBhIGNsZWFuIHNsYXRlLgoKIyMjIFN0ZXAg" + + "MTogRGVzdHJveSB0aGUgY29tcHV0ZS1wbGFuZSBzdGFjawoKYGBgYmFzaAptYWtlIC1DIGRlcGxv" + + "eS9zdGFja3MvbnZjZi1jb21wdXRlLXBsYW5lIGRlc3Ryb3kgXAogIENMVVNURVJfTkFNRT08Y2x1" + + "c3Rlci1uYW1lPiBcCiAgSEVMTUZJTEVfRU5WPTxlbnY+IFwKICBLVUJFQ09ORklHX0ZJTEU9PGNv" + + "bXB1dGUtcGxhbmUta3ViZWNvbmZpZz4KYGBgCgpJZiBjb21wdXRlIGNsZWFudXAgaXMgc3R1Y2sg" + + "b24gZmluYWxpemVycywgdXNlIHRoZSBjb21wdXRlLWNvbnRleHQgcHJvY2VkdXJlIGluCltkZWJ1" + + "Z2dpbmcubWRdKHJlZmVyZW5jZXMvZGVidWdnaW5nLm1kI2ZhaWx1cmUtbnZjYS1jbGVhbnVwLXN0" + + "dWNrLWZpbmFsaXplcnMpLgoKIyMjIFN0ZXAgMjogRGVyZWdpc3RlciB0aGUgY29tcHV0ZSBjbHVz" + + "dGVyCgpSZWFkIGBjbHVzdGVySURgIGFuZCBgbmNhSURgIGZyb20KYGRlcGxveS9zdGFja3MvbnZj" + + "Zi1jb21wdXRlLXBsYW5lL3JlZ2lzdHJhdGlvbi88Y2x1c3Rlci1uYW1lPi1yZWdpc3Rlci12YWx1" + + "ZXMueWFtbGAuClNob3cgdGhlIElEIGFuZCBnZXQgY29uZmlybWF0aW9uIGJlZm9yZSBkZWxldGlu" + + "ZyB0aGUgSUNNUyByb3c6CgpgYGBiYXNoCjxudmNmLWNsaT4gLS1jb25maWcgPGNvbmZpZz4gY2x1" + + "c3RlciBkZWxldGUgXAogIC0tY2x1c3Rlci1pZCA8Y2x1c3Rlci1pZC1mcm9tLXJlZ2lzdHJhdGlv" + + "bi12YWx1ZXM+IFwKICAtLW5jYS1pZCA8bmNhLWlkPiBcCiAgLS1pZ25vcmUtbWlzc2luZwpgYGAK" + + "ClJlcGVhdCBzdGVwcyAxIGFuZCAyIGZvciBldmVyeSBjb21wdXRlIGNsdXN0ZXIuCgojIyMgU3Rl" + + "cCAzOiBEZXN0cm95IHRoZSBjb250cm9sLXBsYW5lIHN0YWNrCgpQcmVzZXJ2ZSB0aGUgYGNlcnQt" + + "bWFuYWdlcmAgbmFtZXNwYWNlIGJ5IGRlZmF1bHQuIE5hbWVzcGFjZSBwcmVzZXJ2YXRpb24gZG9l" + + "cwpub3QgcHJlc2VydmUgdGhlIEhlbG0gcmVsZWFzZS4gQmVmb3JlIHJ1bm5pbmcgZGVzdHJveSwg" + + "aWRlbnRpZnkgd2hldGhlcgpjZXJ0LW1hbmFnZXIgaXMgZXh0ZXJuYWwuIEZvciBhbiBleHRlcm5h" + + "bCBpbnN0YWxsYXRpb24sIHJlcXVpcmUgZWZmZWN0aXZlCmBjZXJ0TWFuYWdlci5lbmFibGVkOiBm" + + "YWxzZWAgYW5kIGFib3J0IGlmIGl0IGlzIHRydWUuIEZvciBhbiBpbnRlbmRlZApzdGFjay1tYW5h" + + "Z2VkIHJlbGVhc2Ugd2l0aCBlZmZlY3RpdmUgYGNlcnRNYW5hZ2VyLmVuYWJsZWQ6IHRydWVgLCB2" + + "ZXJpZnkKbWF0Y2hpbmcgc3RhY2sgSGVsbSByZWxlYXNlIG1ldGFkYXRhIGFuZCBhYm9ydCBvbiBt" + + "aXNzaW5nIG9yIG1pc21hdGNoZWQKZXZpZGVuY2UuIE9ubHkgcGxhbiBuYW1lc3BhY2UgcmVtb3Zh" + + "bCBhZnRlciB2ZXJpZnlpbmcgbmFtZXNwYWNlIHByb3ZlbmFuY2UgYW5kCmdldHRpbmcgZXhwbGlj" + + "aXQgY29uZmlybWF0aW9uOyBvdGhlcndpc2UgZG8gbm90IGRlbGV0ZSBpdC4KCmBgYGJhc2gKS1VC" + + "RUNPTkZJRz08Y29udHJvbC1wbGFuZS1rdWJlY29uZmlnPiBcCm1ha2UgLUMgZGVwbG95L3N0YWNr" + + "cy9zZWxmLW1hbmFnZWQgZGVzdHJveSBcCiAgSEVMTUZJTEVfRU5WPTxlbnY+IFwKICBLVUJFQ09O" + + "RklHX0ZJTEU9PGNvbnRyb2wtcGxhbmUta3ViZWNvbmZpZz4KYGBgCgpQYXNzIGJvdGgga3ViZWNv" + + "bmZpZyBmb3JtcyBiZWNhdXNlIGNvbnRyb2wtcGxhbmUgbmFtZXNwYWNlIGNsZWFudXAgdXNlcwph" + + "bWJpZW50IGBrdWJlY3RsYC4KCiMjIyBTdGVwIDQ6IFZlcmlmeSBjbGVhbgoKYGBgYmFzaAprdWJl" + + "Y3RsIC0tY29udGV4dCA8Y29tcHV0ZS1wbGFuZS1jb250ZXh0PiBnZXQgbnMgfCBcCiAgZ3JlcCAt" + + "RSAnKG52Y2F8bnZjZi1iYWNrZW5kfGdyb3ZlfGR5bmFtbyknCmt1YmVjdGwgLS1jb250ZXh0IDxj" + + "b250cm9sLXBsYW5lLWNvbnRleHQ+IGdldCBucyB8IFwKICBncmVwIC1FICcoY2Fzc2FuZHJhfG5h" + + "dHN8dmF1bHR8bnZjZnxhcGkta2V5c3xlc3N8c2lzKScKIyBCb3RoIGNvbW1hbmRzIHNob3VsZCBy" + + "ZXR1cm4gZW1wdHkuCmBgYAoKIyMjIFN0ZXAgNTogUmVpbnN0YWxsIGluIHNwbGl0IG9yZGVyCgpG" + + "b2xsb3cgW0NsZWFuIEluc3RhbGxhdGlvbl0oU0tJTEwubWQjY2xlYW4taW5zdGFsbGF0aW9uKSwg" + + "dGhlbiByZXR1cm4gdG8gdGhlCnNhbWUgaGFuZG9mZiBpbiBbU3BsaXQgQ29tcHV0ZS1QbGFuZSBJ" + + "bnN0YWxsYXRpb25dKHJlZmVyZW5jZXMvY29tcHV0ZS1wbGFuZS1pbnN0YWxsYXRpb24ubWQpLgoK" + + "LS0tCgojIyBFeGFtcGxlIDQ6IFJlY292ZXIgZnJvbSBhIEdhdGV3YXkgRW5kcG9pbnQgQ2hhbmdl" + + "CgpUaGUgc2VwYXJhdGVseSBwcm92aXNpb25lZCBHYXRld2F5IG9yIGl0cyBsb2FkIGJhbGFuY2Vy" + + "IHdhcyByZWNyZWF0ZWQgYW5kIGdvdCBhIG5ldyBlbmRwb2ludC4KCiMjIyBTdGVwIDE6IEdldCB0" + + "aGUgbmV3IGVuZHBvaW50CgpgYGBiYXNoCkdBVEVXQVlfQUREUj0kKGt1YmVjdGwgLS1jb250ZXh0" + + "IDxjb250cm9sLXBsYW5lLWNvbnRleHQ+IGdldCBnYXRld2F5IFwKICBudmNmLWdhdGV3YXkgLW4g" + + "ZW52b3ktZ2F0ZXdheSBcCiAgLW8ganNvbnBhdGg9J3suc3RhdHVzLmFkZHJlc3Nlc1swXS52YWx1" + + "ZX0nKQp0ZXN0IC1uICIkR0FURVdBWV9BRERSIgpgYGAKCiMjIyBTdGVwIDI6IFVwZGF0ZSBETlMg" + + "YW5kIGRpcmVjdCBhZGRyZXNzIGZpZWxkcwoKS2VlcCBgZ2xvYmFsLmRvbWFpbmAsIHJvdXRlIGhv" + + "c3RuYW1lcywgYW5kIENMSSBIb3N0IGZpZWxkcyB1bmNoYW5nZWQuIFBvaW50IHRoZQpETlMgcmVj" + + "b3JkcyBmb3IgcmVuZGVyZWQgcm91dGUgaG9zdG5hbWVzLCBzdWNoIGFzIGBhcGkuPGRvbWFpbj5g" + + "IGFuZApgKi5pbnZvY2F0aW9uLjxkb21haW4+YCwgdG8gYEdBVEVXQVlfQUREUmAuIEZvciBhIHRl" + + "bXBvcmFyeSBlbnZpcm9ubWVudCB3aXRob3V0CkROUywga2VlcCBpdHMgcmVzZXJ2ZWQgcm91dGUg" + + "c3VmZml4IGFuZCBjb25uZWN0IHRvIGBHQVRFV0FZX0FERFJgIHdpdGggdGhlCm1hdGNoaW5nIGBI" + + "b3N0YCBoZWFkZXIuCgpGb3IgYSBzcGxpdCBkZXBsb3ltZW50IGNvbmZpZ3VyZWQgd2l0aCBkaXJl" + + "Y3QgR2F0ZXdheSBhZGRyZXNzZXMsIHJlcGxhY2Ugb25seQp0aGUgYWRkcmVzcy1iZWFyaW5nIHZh" + + "bHVlczogY29udHJvbC1wbGFuZSBgbnZjZk5hdHNTZXJ2aWNlVVJMYCwgY29tcHV0ZS1wbGFuZQpJ" + + "Q01TLCBSZVZhbCwgYW5kIE5BVFMgVVJMcywgYW5kIENMSSBVUkwgZmllbGRzLiBLZWVwIHRoZWly" + + "IGRvbWFpbiBhbmQgSG9zdApvdmVycmlkZXMgdW5jaGFuZ2VkLiBSZWZyZXNoIHJlZ2lzdHJhdGlv" + + "biBhbmQgaW5zdGFsbGF0aW9uIHRocm91Z2ggdGhlIHNhbWUKaGFuZG9mZiB1c2VkIG9yaWdpbmFs" + + "bHkuIFNlZQpbU3BsaXQgQ29tcHV0ZS1QbGFuZSBJbnN0YWxsYXRpb25dKHJlZmVyZW5jZXMvY29t" + + "cHV0ZS1wbGFuZS1pbnN0YWxsYXRpb24ubWQpCmZvciB0aGUgZmllbGRzIGFuZCBjb21wbGV0ZSBj" + + "b21tYW5kcy4KCiMjIyBTdGVwIDM6IFZlcmlmeSBib3RoIGNvbnRleHRzCgpgYGBiYXNoCmRpZyAr" + + "c2hvcnQgImFwaS48ZG9tYWluPiIKY3VybCAtSCAiSG9zdDogYXBpLjxkb21haW4+IiAiaHR0cDov" + + "LyRHQVRFV0FZX0FERFIvaGVhbHRoIgprdWJlY3RsIC0tY29udGV4dCA8Y29udHJvbC1wbGFuZS1j" + + "b250ZXh0PiBnZXQgZ2F0ZXdheSBudmNmLWdhdGV3YXkgXAogIC1uIGVudm95LWdhdGV3YXkgLW8g" + + "d2lkZQprdWJlY3RsIC0tY29udGV4dCA8Y29udHJvbC1wbGFuZS1jb250ZXh0PiBnZXQgaHR0cHJv" + + "dXRlcyAtQQprdWJlY3RsIC0tY29udGV4dCA8Y29udHJvbC1wbGFuZS1jb250ZXh0PiBnZXQgdGNw" + + "cm91dGVzIC1BCmt1YmVjdGwgLS1jb250ZXh0IDxjb21wdXRlLXBsYW5lLWNvbnRleHQ+IHJvbGxv" + + "dXQgc3RhdHVzIFwKICBkZXBsb3ltZW50L252Y2Etb3BlcmF0b3IgLW4gbnZjYS1vcGVyYXRvciAt" + + "LXRpbWVvdXQ9MTBtCmt1YmVjdGwgLS1jb250ZXh0IDxjb21wdXRlLXBsYW5lLWNvbnRleHQ+IHdh" + + "aXQgXAogIG52Y2ZiYWNrZW5kLzxjb21wdXRlLXBsYW5lLWNsdXN0ZXItbmFtZT4gLW4gbnZjYS1v" + + "cGVyYXRvciBcCiAgLS1mb3I9anNvbnBhdGg9J3suc3RhdHVzLmFnZW50U3RhdHVzfSc9aGVhbHRo" + + "eSAtLXRpbWVvdXQ9MTBtCmBgYAoKLS0tCgojIyBFeGFtcGxlIDU6IERlcGxveSBPbmx5IERlcGVu" + + "ZGVuY2llcwoKVXNlZnVsIGZvciB0ZXN0aW5nIG9yIHdoZW4gaXRlcmF0aW5nIG9uIHNlcnZpY2Ug" + + "Y29uZmlndXJhdGlvbi4KCmBgYGJhc2gKIyBEZXBsb3kganVzdCBOQVRTLCBDYXNzYW5kcmEsIE9w" + + "ZW5CYW8KSEVMTUZJTEVfRU5WPTxlbnY+IGhlbG1maWxlIC0tc2VsZWN0b3IgcmVsZWFzZS1ncm91" + + "cD1kZXBlbmRlbmNpZXMgc3luYwoKIyBDaGVjayBzdGF0dXMKa3ViZWN0bCBnZXQgcG9kcyAtbiBu" + + "YXRzLXN5c3RlbQprdWJlY3RsIGdldCBwb2RzIC1uIGNhc3NhbmRyYS1zeXN0ZW0Ka3ViZWN0bCBn" + + "ZXQgcG9kcyAtbiB2YXVsdC1zeXN0ZW0KCiMgRGVwbG95IGp1c3Qgb25lIHNwZWNpZmljIHJlbGVh" + + "c2UKSEVMTUZJTEVfRU5WPTxlbnY+IGhlbG1maWxlIC0tc2VsZWN0b3IgbmFtZT1jYXNzYW5kcmEg" + + "c3luYwpgYGAKCiMjIyBTZWxlY3RvciByZWZlcmVuY2UKCnwgU2VsZWN0b3IgfCBSZWxlYXNlcyB8" + + "CnwtLS0tLS0tLS0tfC0tLS0tLS0tLS18CnwgYHJlbGVhc2UtZ3JvdXA9ZGVwZW5kZW5jaWVzYCB8" + + "IG5hdHMsIGNhc3NhbmRyYSwgb3BlbmJhby1zZXJ2ZXIgfAp8IGByZWxlYXNlLWdyb3VwPXNlcnZp" + + "Y2VzYCB8IGFwaS1rZXlzLCBzaXMsIGFwaSwgaW52b2NhdGlvbi1zZXJ2aWNlLCBncnBjLXByb3h5" + + "LCBlc3MtYXBpLCBub3Rhcnktc2VydmljZSwgcmV2YWwsIG9wdGlvbmFsIGxsbS1yZXF1ZXN0LXJv" + + "dXRlciBhbmQgbGxtLWFwaS1nYXRld2F5IHdoZW4gYGxsbS5lbmFibGVkPXRydWVgLCBvcHRpb25h" + + "bCB2YW5pdHktZ2F0ZXdheSBvbmx5IGluIHN0YWNrIHBhY2thZ2VzIHRoYXQgaW5jbHVkZSB0aGUg" + + "YWRkb24gYW5kIGhhdmUgYGFkZG9ucy52YW5pdHlHYXRld2F5LmVuYWJsZWQ9dHJ1ZWAsIG9wdGlv" + + "bmFsIG52Y2YtdWkgb25seSBpbiBzdGFjayBwYWNrYWdlcyB0aGF0IGluY2x1ZGUgdGhlIGFkZG9u" + + "IGFuZCBoYXZlIGBhZGRvbnMubnZjZlVpLmVuYWJsZWQ9dHJ1ZWAgfAp8IGBuYW1lPWxsbS1yZXF1" + + "ZXN0LXJvdXRlcmAgfCBEZXBsb3kganVzdCB0aGUgTExNIHJlcXVlc3Qgcm91dGVyIHdoZW4gYGxs" + + "bS5lbmFibGVkPXRydWVgIHwKfCBgbmFtZT1sbG0tYXBpLWdhdGV3YXlgIHwgRGVwbG95IGp1c3Qg" + + "dGhlIExMTSBBUEkgZ2F0ZXdheSB3aGVuIGBsbG0uZW5hYmxlZD10cnVlYCB8CnwgYG5hbWU9dmFu" + + "aXR5LWdhdGV3YXlgIHwgRGVwbG95IGp1c3QgVmFuaXR5IEdhdGV3YXkgb25seSB3aGVuIHRoZSBz" + + "dGFjayBwYWNrYWdlIGluY2x1ZGVzIHRoYXQgcmVsZWFzZSBhbmQgYGFkZG9ucy52YW5pdHlHYXRl" + + "d2F5LmVuYWJsZWQ9dHJ1ZWAgfAp8IGBuYW1lPW52Y2YtdWlgIHwgRGVwbG95IE5WQ0YgVUkgb25s" + + "eSB3aGVuIHRoZSBzdGFjayBwYWNrYWdlIGluY2x1ZGVzIHRoYXQgcmVsZWFzZSBhbmQgYGFkZG9u" + + "cy5udmNmVWkuZW5hYmxlZD10cnVlYCB8CnwgYHJlbGVhc2UtZ3JvdXA9aW5ncmVzc2AgfCBpbmdy" + + "ZXNzIChnYXRld2F5IHJvdXRlcykgfAp8IGBuYW1lPTxyZWxlYXNlPmAgfCBBbnkgaW5kaXZpZHVh" + + "bCByZWxlYXNlIGJ5IG5hbWUgfAoKTm90ZTogYGFkbWluLWlzc3Vlci1wcm94eWAgZG9lcyBub3Qg" + + "aGF2ZSBhIHJlbGVhc2UtZ3JvdXAgbGFiZWwuIFRhcmdldCBpdCB3aXRoIGAtLXNlbGVjdG9yIG5h" + + "bWU9YWRtaW4taXNzdWVyLXByb3h5YC4KUEsHCAJSTkB/KAAAfygAAFBLAwQUAAgAAAAAACEAAAAA" + + "AAAAAAAAAAAATAAJAGRhdGEvbnZjZi1zZWxmLW1hbmFnZWQtaW5zdGFsbGF0aW9uL3JlZmVyZW5j" + + "ZXMvY29tcHV0ZS1wbGFuZS1pbnN0YWxsYXRpb24ubWRVVAUAAQCmzhIjIFNwbGl0IENvbXB1dGUt" + + "UGxhbmUgSW5zdGFsbGF0aW9uCgpUaGUgY29udHJvbCBwbGFuZSBhbmQgY29tcHV0ZSBwbGFuZSBh" + + "cmUgc2VwYXJhdGUgc3RhY2tzLiBJbnN0YWxsIHRoZSBjb250cm9sCnBsYW5lIGZpcnN0LCB0aGVu" + + "IHJlZ2lzdGVyIGFuZCBpbnN0YWxsIGVhY2ggY29tcHV0ZSBjbHVzdGVyLiBDaG9vc2Ugb25lIGhh" + + "bmRvZmYKYmVsb3cgYW5kIHVzZSBpdCBmb3IgdGhlIGZ1bGwgbGlmZWN5Y2xlLiBEbyBub3QgY29t" + + "YmluZSBhIENMSS1nZW5lcmF0ZWQgcHJvZmlsZQp3aXRoIHRoZSBIZWxtZmlsZSB2YWx1ZXMgZmxv" + + "dy4KCiMjIENob29zZSBhIEhhbmRvZmYKCnwgRmxvdyB8IFNvdXJjZSBvZiBjb250cm9sLXBsYW5l" + + "IGVuZHBvaW50cyB8IFJlZ2lzdHJhdGlvbiBhcnRpZmFjdCB8CnwtLS0tLS18LS0tLS0tLS0tLS0t" + + "LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS18LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS18CnwgSGVsbWZp" + + "bGUgdmFsdWVzIHwgU2VwYXJhdGUgY29udHJvbC1wbGFuZSBhbmQgY29tcHV0ZS1wbGFuZSBlbnZp" + + "cm9ubWVudCBmaWxlcyB8IGBkZXBsb3kvc3RhY2tzL252Y2YtY29tcHV0ZS1wbGFuZS9yZWdpc3Ry" + + "YXRpb24vPGNsdXN0ZXI+LXJlZ2lzdGVyLXZhbHVlcy55YW1sYCB8CnwgQ0xJIHByb2ZpbGUgfCBD" + + "TEktZ2VuZXJhdGVkIGBkZXBsb3kvc3RhY2tzL3NlbGYtbWFuYWdlZC9vdXQvY29udHJvbC1wbGFu" + + "ZS1wcm9maWxlLnlhbWxgIHwgQ0xJLWdlbmVyYXRlZCBgZGVwbG95L3N0YWNrcy9udmNmLWNvbXB1" + + "dGUtcGxhbmUvb3V0LzxjbHVzdGVyPi1yZWdpc3Rlci12YWx1ZXMueWFtbGAgfAoKVGhlIENMSSBw" + + "cm9maWxlIGlzIG1hbmRhdG9yeSBmb3IgdGhlIENMSSBmbG93IGFuZCBtdXN0IG5vdCBiZSBoYW5k" + + "LWF1dGhvcmVkLgpUaGUgSGVsbWZpbGUgZmxvdyBkb2VzIG5vdCBjb25zdW1lIGEgY29udHJvbC1w" + + "bGFuZSBwcm9maWxlLgoKQ29tbWFuZHMgYmVsb3cgdXNlIG1vbm9yZXBvIHBhdGhzIGZyb20gdGhl" + + "IHJlcG9zaXRvcnkgcm9vdC4gT3BlcmF0b3JzIHVzaW5nCmV4dHJhY3RlZCBwYWNrYWdlcyBuZWVk" + + "IG1hdGNoaW5nLXZlcnNpb24gYG52Y2Ytc2VsZi1tYW5hZ2VkLXN0YWNrYCBhbmQKYG52Y2YtY29t" + + "cHV0ZS1wbGFuZS1zdGFja2AgYnVuZGxlcy4gU3Vic3RpdHV0ZSB0aG9zZSBidW5kbGUgcm9vdHMg" + + "Zm9yCmBkZXBsb3kvc3RhY2tzL3NlbGYtbWFuYWdlZGAgYW5kIGBkZXBsb3kvc3RhY2tzL252Y2Yt" + + "Y29tcHV0ZS1wbGFuZWAuCgojIyBIZWxtZmlsZSBWYWx1ZXMgRmxvdwoKQ3JlYXRlIGJvdGggZW52" + + "aXJvbm1lbnQgZmlsZXMgYmVmb3JlIGluc3RhbGxhdGlvbjoKCi0gYGRlcGxveS9zdGFja3Mvc2Vs" + + "Zi1tYW5hZ2VkL2Vudmlyb25tZW50cy88ZW52Pi55YW1sYCBmb3IgY29udHJvbC1wbGFuZQogIHJl" + + "Z2lzdHJ5LCBkb21haW4sIEdhdGV3YXksIHJvdXRlcywgYW5kIHNlcnZpY2Ugc2V0dGluZ3MuCi0g" + + "YGRlcGxveS9zdGFja3MvbnZjZi1jb21wdXRlLXBsYW5lL2Vudmlyb25tZW50cy88ZW52Pi55YW1s" + + "YCBmb3IgY29tcHV0ZS1wbGFuZQogIHJlZ2lzdHJ5LCBwdWxsIHNlY3JldHMsIGFuZCBjb21wdXRl" + + "LXJlYWNoYWJsZSBJQ01TLCBSZVZhbCwgYW5kIE5BVFMgdmFsdWVzLgoKRm9yIEVLUywgdXNlIHRo" + + "ZSBjYW5vbmljYWwKW0NTUCBFbmQtdG8tRW5kIEV4YW1wbGVdKGh0dHBzOi8vZG9jcy5udmlkaWEu" + + "Y29tL252Y2YvdjAuNi4wLXJjL2NzcC1lbmQtdG8tZW5kLWV4YW1wbGUpLgpUaGUgY29udHJvbC1w" + + "bGFuZSBlbnZpcm9ubWVudCBrZWVwcyB0aGUgRW52b3kgR2F0ZXdheSBjb250cm9sbGVyIGluCmBl" + + "bnZveS1nYXRld2F5LXN5c3RlbWAsIGF0dGFjaGVzIHJvdXRlcyB0byBgR2F0ZXdheS9udmNmLWdh" + + "dGV3YXlgIGluCmBlbnZveS1nYXRld2F5YCwgYW5kIGVuYWJsZXMgdGhlIE5BVFMgcm91dGUuIFRo" + + "ZSBjb21wdXRlLXBsYW5lIGVudmlyb25tZW50IHVzZXMKdGhlIGV4dGVybmFsIGFkZHJlc3MgcGx1" + + "cyB0aGUgc2VydmljZSBob3N0IG92ZXJyaWRlcy4gSGVyZSwKYDxnYXRld2F5LWFkZHJlc3M+YCBp" + + "cyB0aGUgTkxCIGFkZHJlc3MgYW5kIGA8Z2F0ZXdheS1kb21haW4+YCBpcyB0aGUgRE5TIGJhc2UK" + + "d2hvc2Ugc2VydmljZSBzdWJkb21haW5zIHJlc29sdmUgdG8gdGhhdCBOTEI6CgpgYGB5YW1sCiMg" + + "ZGVwbG95L3N0YWNrcy9zZWxmLW1hbmFnZWQvZW52aXJvbm1lbnRzLzxlbnY+LnlhbWwKZ2xvYmFs" + + "OgogIGRvbWFpbjogIjxnYXRld2F5LWRvbWFpbj4iCiAgd29ya2VyRW5kcG9pbnRzOgogICAgbnZj" + + "ZlNlcnZpY2VVUkw6ICJodHRwOi8vYXBpLjxnYXRld2F5LWRvbWFpbj4iCiAgICBudmNmR3JwY1Nl" + + "cnZpY2VVUkw6ICJodHRwOi8vd29ya2VyLWFwaS48Z2F0ZXdheS1kb21haW4+IgogICAgbnZjZk5h" + + "dHNTZXJ2aWNlVVJMOiAibmF0czovLzxnYXRld2F5LWFkZHJlc3M+OjQyMjIiCiAgICBudmN0U2Vy" + + "dmljZVVSTDogImh0dHA6Ly90YXNrcy48Z2F0ZXdheS1kb21haW4+IgogICAgbnZjdEdycGNTZXJ2" + + "aWNlVVJMOiAiaHR0cDovL3dvcmtlci10YXNrcy48Z2F0ZXdheS1kb21haW4+IgogICAgaW52b2Nh" + + "dGlvblNlcnZpY2VVUkw6ICJodHRwOi8vaW52b2NhdGlvbi48Z2F0ZXdheS1kb21haW4+IgoKZ3Jw" + + "Y3Byb3h5OgogIG52Y2ZHcnBjU2VydmljZVVSTDogImh0dHA6Ly9hcGkubnZjZi5zdmMuY2x1c3Rl" + + "ci5sb2NhbDo5MDkwIgoKaW5ncmVzczoKICBnYXRld2F5QXBpOgogICAgY29udHJvbGxlck5hbWVz" + + "cGFjZTogZW52b3ktZ2F0ZXdheS1zeXN0ZW0KICAgIGdhdGV3YXlzOgogICAgICBzaGFyZWQ6IHtu" + + "YW1lOiBudmNmLWdhdGV3YXksIG5hbWVzcGFjZTogZW52b3ktZ2F0ZXdheX0KICAgICAgZ3JwYzog" + + "e25hbWU6IG52Y2YtZ2F0ZXdheSwgbmFtZXNwYWNlOiBlbnZveS1nYXRld2F5fQogICAgICBuYXRz" + + "OgogICAgICAgIG5hbWU6IG52Y2YtZ2F0ZXdheQogICAgICAgIG5hbWVzcGFjZTogZW52b3ktZ2F0" + + "ZXdheQogICAgICAgIGxpc3RlbmVyTmFtZTogbmF0cwogICAgcm91dGVzOgogICAgICBudmNmQXBp" + + "OgogICAgICAgIGdycGM6CiAgICAgICAgICBlbmFibGVkOiB0cnVlCiAgICAgICAgICBob3N0bmFt" + + "ZXM6IFsid29ya2VyLWFwaS48Z2F0ZXdheS1kb21haW4+Il0KICAgICAgbnZjdEFwaToKICAgICAg" + + "ICBncnBjOgogICAgICAgICAgZW5hYmxlZDogdHJ1ZQogICAgICAgICAgaG9zdG5hbWVzOiBbIndv" + + "cmtlci10YXNrcy48Z2F0ZXdheS1kb21haW4+Il0KICAgICAgbmF0czoKICAgICAgICBlbmFibGVk" + + "OiB0cnVlCgpvcGVuYmFvOgogIG1pZ3JhdGlvbnM6CiAgICBpc3N1ZXJEaXNjb3Zlcnk6CiAgICAg" + + "IGVuYWJsZWQ6IHRydWUKYGBgCgpgYGB5YW1sCiMgZGVwbG95L3N0YWNrcy9udmNmLWNvbXB1dGUt" + + "cGxhbmUvZW52aXJvbm1lbnRzLzxlbnY+LnlhbWwKZ2xvYmFsOgogIG52Y2FPcGVyYXRvcjoKICAg" + + "IHNlbGZNYW5hZ2VkOgogICAgICBpY21zU2VydmljZVVSTDogImh0dHA6Ly88Z2F0ZXdheS1hZGRy" + + "ZXNzPiIKICAgICAgaWNtc1NlcnZpY2VIb3N0SGVhZGVyT3ZlcnJpZGU6ICJzaXMuPGdhdGV3YXkt" + + "ZG9tYWluPiIKICAgICAgcmV2YWxTZXJ2aWNlVVJMOiAiaHR0cDovLzxnYXRld2F5LWFkZHJlc3M+" + + "IgogICAgICByZXZhbFNlcnZpY2VIb3N0SGVhZGVyT3ZlcnJpZGU6ICJyZXZhbC48Z2F0ZXdheS1k" + + "b21haW4+IgogICAgICBuYXRzVVJMOiAibmF0czovLzxnYXRld2F5LWFkZHJlc3M+OjQyMjIiCiAg" + + "ICAgIG5hdHNIb3N0T3ZlcnJpZGU6ICJuYXRzLjxnYXRld2F5LWRvbWFpbj4iCmBgYAoKUHJldmll" + + "dyBhbmQgaW5zdGFsbCB0aGUgY29udHJvbCBwbGFuZSBhZ2FpbnN0IGl0cyBjb250ZXh0OgoKYGBg" + + "YmFzaAptYWtlIC1DIGRlcGxveS9zdGFja3Mvc2VsZi1tYW5hZ2VkIHRlbXBsYXRlIFwKICBIRUxN" + + "RklMRV9FTlY9PGVudj4gXAogIEtVQkVDT05GSUdfRklMRT08Y29udHJvbC1wbGFuZS1rdWJlY29u" + + "ZmlnPgoKbWFrZSAtQyBkZXBsb3kvc3RhY2tzL3NlbGYtbWFuYWdlZCBpbnN0YWxsIFwKICBIRUxN" + + "RklMRV9FTlY9PGVudj4gXAogIEtVQkVDT05GSUdfRklMRT08Y29udHJvbC1wbGFuZS1rdWJlY29u" + + "ZmlnPgpgYGAKCkJlZm9yZSByZWdpc3RlcmluZyB0aHJvdWdoIGEgYmFyZSBHYXRld2F5IGFkZHJl" + + "c3MsIGNyZWF0ZSBvciB1cGRhdGUgdGhlIENMSQpjb25maWcgcGFzc2VkIGFzIGBOVkNGX0NMSV9D" + + "T05GSUdgOgoKYGBgeWFtbApiYXNlX2h0dHBfdXJsOiAiaHR0cDovLzxnYXRld2F5LWFkZHJlc3M+" + + "IgppbnZva2VfdXJsOiAiaHR0cDovLzxnYXRld2F5LWFkZHJlc3M+IgpiYXNlX2dycGNfdXJsOiAi" + + "PGdhdGV3YXktYWRkcmVzcz46MTAwODEiCmFwaV9rZXlzX3NlcnZpY2VfdXJsOiAiaHR0cDovLzxn" + + "YXRld2F5LWFkZHJlc3M+IgppY21zX3VybDogImh0dHA6Ly88Z2F0ZXdheS1hZGRyZXNzPiIKYXBp" + + "X2hvc3Q6ICJhcGkuPGdhdGV3YXktZG9tYWluPiIKYXBpX2tleXNfaG9zdDogImFwaS1rZXlzLjxn" + + "YXRld2F5LWRvbWFpbj4iCmludm9rZV9ob3N0OiAiaW52b2NhdGlvbi48Z2F0ZXdheS1kb21haW4+" + + "IgppY21zX2hvc3Q6ICJzaXMuPGdhdGV3YXktZG9tYWluPiIKYGBgCgpIb3N0IGZpZWxkcyBjb250" + + "YWluIGhvc3RuYW1lcyBvbmx5LCB3aXRob3V0IGEgVVJMIHNjaGVtZS4gYElDTVNfVVJMYCBtYXBz" + + "IG9ubHkKdG8gdGhlIHJlZ2lzdHJhdGlvbiBjb21tYW5kJ3MgYC0taWNtcy11cmxgOyBgaWNtc19o" + + "b3N0YCBpbiB0aGUgY29uZmlnIHN1cHBsaWVzCnRoZSBHYXRld2F5IEhvc3QgaGVhZGVyLiBUaGVy" + + "ZSBpcyBubyBgLS1pY21zLWhvc3RgIE1ha2Ugb3IgQ0xJIGZsYWcuCgpSZWdpc3RlciBmcm9tIHRo" + + "ZSBjb21wdXRlIGNvbnRleHQsIHRoZW4gaW5zdGFsbCB0aGUgY29tcHV0ZSBwbGFuZToKCmBgYGJh" + + "c2gKbWFrZSAtQyBkZXBsb3kvc3RhY2tzL252Y2YtY29tcHV0ZS1wbGFuZSByZWdpc3Rlci1jbHVz" + + "dGVyIFwKICBDTFVTVEVSX05BTUU9PGNsdXN0ZXItbmFtZT4gXAogIE5DQV9JRD08bmNhLWlkPiBc" + + "CiAgQ0xVU1RFUl9SRUdJT049PHJlZ2lvbj4gXAogIElDTVNfVVJMPWh0dHA6Ly88Z2F0ZXdheS1h" + + "ZGRyZXNzPiBcCiAgS1VCRUNPTkZJR19GSUxFPTxjb21wdXRlLXBsYW5lLWt1YmVjb25maWc+IFwK" + + "ICBOVkNGX0NMST08cGF0aC10by1udmNmLWNsaT4gXAogIE5WQ0ZfQ0xJX0NPTkZJRz08cGF0aC10" + + "by1udmNmLWNsaS1jb25maWc+CgptYWtlIC1DIGRlcGxveS9zdGFja3MvbnZjZi1jb21wdXRlLXBs" + + "YW5lIGluc3RhbGwgXAogIENMVVNURVJfTkFNRT08Y2x1c3Rlci1uYW1lPiBcCiAgSEVMTUZJTEVf" + + "RU5WPTxlbnY+IFwKICBLVUJFQ09ORklHX0ZJTEU9PGNvbXB1dGUtcGxhbmUta3ViZWNvbmZpZz4K" + + "YGBgCgpSZWdpc3RyYXRpb24gZGlzY292ZXJzIHRoZSBPSURDIGlzc3VlciBhbmQgSldLUyBmcm9t" + + "IHRoZSBjb21wdXRlIGt1YmUgY29udGV4dC4KSW4gYSBtdWx0aS1jbHVzdGVyIGRlcGxveW1lbnQs" + + "IGBLVUJFQ09ORklHX0ZJTEVgIGlzIHJlcXVpcmVkIG9uIGJvdGgKY29tcHV0ZS1wbGFuZSBjb21t" + + "YW5kcy4gSW4gYSBzYW1lLWNsdXN0ZXIgZGVwbG95bWVudCBpdCBtYXkgYmUgb21pdHRlZCBvbmx5" + + "CndoZW4gdGhlIGFtYmllbnQgY29udGV4dCBpcyBhbHJlYWR5IHRoZSB0YXJnZXQgY2x1c3Rlci4K" + + "CiMjIENMSSBQcm9maWxlIEZsb3cKClJ1biB0aGUgQ0xJIGNvbnRyb2wtcGxhbmUgaW5zdGFsbCBm" + + "aXJzdC4gSXQgY3JlYXRlcyB0aGUgcHJvZmlsZSBjb25zdW1lZCBieQpjb21wdXRlLXBsYW5lIHJl" + + "Z2lzdHJhdGlvbjoKCmBgYGJhc2gKbnZjZi1jbGkgLS1jb25maWcgPGNvbmZpZz4gc2VsZi1ob3N0" + "ZWQgXAogIC0tY29udHJvbC1wbGFuZS1zdGFjayBkZXBsb3kvc3RhY2tzL3NlbGYtbWFuYWdlZCBc" + "CiAgLS1jb21wdXRlLXBsYW5lLXN0YWNrIGRlcGxveS9zdGFja3MvbnZjZi1jb21wdXRlLXBsYW5l" + "IFwKICAtLWVudiA8ZW52PiBcCiAgLS1wbGFpbiBcCiAgLS1jb250cm9sLXBsYW5lLWNvbnRleHQg" + "PGNvbnRyb2wtcGxhbmUtY29udGV4dD4gXAogIC0tY29tcHV0ZS1wbGFuZS1jb250ZXh0IDxjb21w" + - "dXRlLXBsYW5lLWNvbnRleHQ+IFwKICAtLWljbXMtdXJsIDxjb21wdXRlLXJlYWNoYWJsZS1pY21z" + - "LXVybD4gXAogIGRvd24gLS1wbGFuLW9ubHkgXAogIC0tY2x1c3Rlci1uYW1lIDxjb21wdXRlLXBs" + - "YW5lLWNsdXN0ZXItbmFtZT4gXAogIC0tbmNhLWlkIDxuY2EtaWQ+CgpudmNmLWNsaSAtLWNvbmZp" + - "ZyA8Y29uZmlnPiBzZWxmLWhvc3RlZCBcCiAgLS1jb250cm9sLXBsYW5lLXN0YWNrIGRlcGxveS9z" + - "dGFja3Mvc2VsZi1tYW5hZ2VkIFwKICAtLWNvbXB1dGUtcGxhbmUtc3RhY2sgZGVwbG95L3N0YWNr" + - "cy9udmNmLWNvbXB1dGUtcGxhbmUgXAogIC0tZW52IDxlbnY+IFwKICAtLXBsYWluIFwKICAtLWNv" + - "bnRyb2wtcGxhbmUtY29udGV4dCA8Y29udHJvbC1wbGFuZS1jb250ZXh0PiBcCiAgLS1jb21wdXRl" + - "LXBsYW5lLWNvbnRleHQgPGNvbXB1dGUtcGxhbmUtY29udGV4dD4gXAogIC0taWNtcy11cmwgPGNv" + - "bXB1dGUtcmVhY2hhYmxlLWljbXMtdXJsPiBcCiAgZG93biBcCiAgLS1jbHVzdGVyLW5hbWUgPGNv" + - "bXB1dGUtcGxhbmUtY2x1c3Rlci1uYW1lPiBcCiAgLS1uY2EtaWQgPG5jYS1pZD4KYGBgCgpSZXBl" + - "YXQgYGRvd25gIGZvciBlYWNoIGNvbXB1dGUgY2x1c3Rlci4gRWFjaCBydW4gZGVzdHJveXMgdGhh" + - "dCBjb21wdXRlIHBsYW5lIGFuZAp1bnJlZ2lzdGVycyBpdHMgSUNNUyByb3cuIEl0IHJlbW92ZXMg" + - "dGhlIGNvbnRyb2wgcGxhbmUgb25seSBhZnRlciBubyBjbHVzdGVyCnJlZ2lzdHJhdGlvbnMgcmVt" + - "YWluLgpQSwcIlmO8WRIwAAASMAAAUEsDBBQACAAAAAAAIQAAAAAAAAAAAAAAAAA7AAkAZGF0YS9u" + - "dmNmLXNlbGYtbWFuYWdlZC1pbnN0YWxsYXRpb24vcmVmZXJlbmNlcy9kZWJ1Z2dpbmcubWRVVAUA" + - "AQCmzhIjIERlYnVnZ2luZyBSZWZlcmVuY2UKClJlY2lwZXMgZm9yIGRpYWdub3NpbmcgYW5kIGZp" + - "eGluZyBjb21tb24gTlZDRiBzZWxmLW1hbmFnZWQgc3RhY2sgZmFpbHVyZXMuCgojIyBRdWljayBT" + - "dGF0dXMgQ29tbWFuZHMKCmBgYGJhc2gKIyBBbGwgcG9kcyBhY3Jvc3MgYWxsIG5hbWVzcGFjZXMK" + - "a3ViZWN0bCBnZXQgcG9kcyAtQSAtbyB3aWRlCgojIFBvZHMgaW4gYSBzcGVjaWZpYyBuYW1lc3Bh" + - "Y2UKa3ViZWN0bCBnZXQgcG9kcyAtbiA8bmFtZXNwYWNlPgoKIyBBbGwgaGVsbSByZWxlYXNlcwpo" + - "ZWxtIGxpc3QgLUEKCiMgUmVjZW50IGV2ZW50cyAobW9zdCB1c2VmdWwgZm9yIGRpYWdub3Npbmcg" + - "ZmFpbHVyZXMpCmt1YmVjdGwgZ2V0IGV2ZW50cyAtbiA8bmFtZXNwYWNlPiAtLXNvcnQtYnk9Jy5s" + - "YXN0VGltZXN0YW1wJwoKIyBEZXNjcmliZSBhIHNwZWNpZmljIHBvZCAoc2hvd3MgZXZlbnRzLCBj" + - "b25kaXRpb25zLCB2b2x1bWVzKQprdWJlY3RsIGRlc2NyaWJlIHBvZCAtbiA8bmFtZXNwYWNlPiA8" + - "cG9kLW5hbWU+CmBgYAoKIyMgRmFpbHVyZTogSW1hZ2VQdWxsQmFja09mZgoKIyMjIFN5bXB0b21z" + - "CgpgYGAKTkFNRSAgICAgICAgICAgUkVBRFkgICBTVEFUVVMgICAgICAgICAgICAgUkVTVEFSVFMg" + - "ICBBR0UKY2Fzc2FuZHJhLTAgICAgMC8xICAgICBJbWFnZVB1bGxCYWNrT2ZmICAgMCAgICAgICAg" + - "ICA1bQpgYGAKCkV2ZW50cyBzaG93OgpgYGAKRmFpbGVkIHRvIHB1bGwgaW1hZ2UgIm52Y3IuaW8v" + - "Li4uL2ltYWdlOnRhZyI6IDQwMSBVbmF1dGhvcml6ZWQKYGBgCgojIyMgRGlhZ25vc2lzCgpgYGBi" + - "YXNoCiMgQ2hlY2sgd2hhdCBpbWFnZSBpcyBmYWlsaW5nCmt1YmVjdGwgZGVzY3JpYmUgcG9kIC1u" + - "IDxuYW1lc3BhY2U+IDxwb2QtbmFtZT4gfCBncmVwIC1BNSAiRXZlbnRzOiIKCiMgQ2hlY2sgaWYg" + - "cHVsbCBzZWNyZXQgZXhpc3RzIGluIHRoZSBuYW1lc3BhY2UKa3ViZWN0bCBnZXQgc2VjcmV0IG52" + - "Y3ItY3JlZHMgLW4gPG5hbWVzcGFjZT4KCiMgQ2hlY2sgaWYgdGhlIHBvZCBzcGVjIGhhcyBpbWFn" + - "ZVB1bGxTZWNyZXRzCmt1YmVjdGwgZ2V0IHBvZCAtbiA8bmFtZXNwYWNlPiA8cG9kLW5hbWU+IC1v" + - "IGpzb25wYXRoPSd7LnNwZWMuaW1hZ2VQdWxsU2VjcmV0c30nCgojIENoZWNrIGlmIHRoZSBTZXJ2" + - "aWNlQWNjb3VudCBoYXMgaW1hZ2VQdWxsU2VjcmV0cwprdWJlY3RsIGdldCBzYSA8c2EtbmFtZT4g" + - "LW4gPG5hbWVzcGFjZT4gLW8ganNvbnBhdGg9J3suaW1hZ2VQdWxsU2VjcmV0c30nCmBgYAoKIyMj" + - "IEZpeGVzCgoxLiAqKlNlY3JldCBtaXNzaW5nIGluIG5hbWVzcGFjZSoqOiBDcmVhdGUgaXQKICAg" + - "YGBgYmFzaAogICBrdWJlY3RsIGNyZWF0ZSBzZWNyZXQgZG9ja2VyLXJlZ2lzdHJ5IG52Y3ItY3Jl" + - "ZHMgXAogICAgIC0tZG9ja2VyLXNlcnZlcj1udmNyLmlvIFwKICAgICAtLWRvY2tlci11c2VybmFt" + - "ZT0nJG9hdXRodG9rZW4nIFwKICAgICAtLWRvY2tlci1wYXNzd29yZD0iJE5HQ19BUElfS0VZIiBc" + - "CiAgICAgLS1uYW1lc3BhY2U9PG5hbWVzcGFjZT4gXAogICAgIC0tZHJ5LXJ1bj1jbGllbnQgLW8g" + - "eWFtbCB8IGt1YmVjdGwgYXBwbHkgLWYgLQogICBgYGAKCjIuICoqU2VjcmV0IGV4aXN0cyBidXQg" + - "cG9kIGRvZXNuJ3QgcmVmZXJlbmNlIGl0Kio6IFRoZSBjaGFydCBuZWVkcyBgaW1hZ2VQdWxsU2Vj" + - "cmV0c2AgY29uZmlndXJlZCB2aWEgaGVsbWZpbGUgdmFsdWVzLiBTZWUgW3B1bGwtc2VjcmV0cy5t" + - "ZF0ocHVsbC1zZWNyZXRzLm1kKS4KCjMuICoqQ2hhcnQgZG9lc24ndCBzdXBwb3J0IGltYWdlUHVs" + - "bFNlY3JldHMqKiAob3BlbmJhbywgaW52b2NhdGlvbi1zZXJ2aWNlLCBlc3MtYXBpLCBub3Rhcnkt" + - "c2VydmljZSk6IFBhdGNoIFNlcnZpY2VBY2NvdW50cyBhbmQgcmVzdGFydCBwb2RzLiBTZWUgW3B1" + - "bGwtc2VjcmV0cy5tZF0ocHVsbC1zZWNyZXRzLm1kKS4KCjQuICoqV3JvbmcgY3JlZGVudGlhbHMg" + - "aW4gc2VjcmV0Kio6IERlbGV0ZSBhbmQgcmVjcmVhdGUKICAgYGBgYmFzaAogICBrdWJlY3RsIGRl" + - "bGV0ZSBzZWNyZXQgbnZjci1jcmVkcyAtbiA8bmFtZXNwYWNlPgogICAjIFJlY3JlYXRlIHdpdGgg" + - "Y29ycmVjdCBjcmVkZW50aWFscwogICBgYGAKCiMjIEZhaWx1cmU6IEluaXQ6MC8xIFN0dWNrIChW" + - "YXVsdCBBZ2VudCkKCiMjIyBTeW1wdG9tcwoKU2VydmljZSBwb2RzIHN0dWNrIGluIGBJbml0OjAv" + - "MWAgZm9yIG1pbnV0ZXM6CmBgYApOQU1FICAgICAgICAgICAgICAgICAgICAgICAgUkVBRFkgICBT" + - "VEFUVVMgICAgIFJFU1RBUlRTICAgQUdFCm52Y2YtYXBpLTdmNGM3NmY3ODgtNDR2bHQgIDAvMiAg" + - "ICAgSW5pdDowLzEgICAwICAgICAgICAgIDEwbQpgYGAKCiMjIyBEaWFnbm9zaXMKClRoZSBpbml0" + - "IGNvbnRhaW5lciBpcyB0aGUgdmF1bHQtYWdlbnQtaW5pdCBpbmplY3RvciB3YWl0aW5nIGZvciBP" + - "cGVuQmFvLgoKYGBgYmFzaAojIENoZWNrIE9wZW5CYW8gcG9kcwprdWJlY3RsIGdldCBwb2RzIC1u" + - "IHZhdWx0LXN5c3RlbQoKIyBDaGVjayBpZiBPcGVuQmFvIG1pZ3JhdGlvbiBqb2IgcmFuCmt1YmVj" + - "dGwgZ2V0IGpvYnMgLW4gdmF1bHQtc3lzdGVtCgojIENoZWNrIE9wZW5CYW8gcG9kIGxvZ3MKa3Vi" + - "ZWN0bCBsb2dzIC1uIHZhdWx0LXN5c3RlbSBvcGVuYmFvLXNlcnZlci0wIC1jIG9wZW5iYW8KCiMg" + - "Q2hlY2sgaW5pdCBjb250YWluZXIgbG9ncyBvbiB0aGUgc3R1Y2sgcG9kCmt1YmVjdGwgbG9ncyAt" + - "biA8bmFtZXNwYWNlPiA8cG9kLW5hbWU+IC1jIHZhdWx0LWFnZW50LWluaXQKYGBgCgojIyMgRml4" + - "ZXMKCjEuICoqT3BlbkJhbyBwb2RzIG5vdCBydW5uaW5nKio6IENoZWNrIHRoZWlyIGV2ZW50cyBm" + - "b3IgaW1hZ2UgcHVsbCBpc3N1ZXMsIHJlc291cmNlIGlzc3VlcywgZXRjLgoKMi4gKipPcGVuQmFv" + - "IG1pZ3JhdGlvbiBqb2IgZGlkbid0IHJ1bioqOiBUaGlzIGhhcHBlbnMgd2hlbiBgaGVsbWZpbGUg" + - "c3luY2Agd2FzIGludGVycnVwdGVkLiBEZXN0cm95IGFuZCByZS1zeW5jIG9wZW5iYW86CiAgIGBg" + - "YGJhc2gKICAgSEVMTUZJTEVfRU5WPTxlbnY+IGhlbG1maWxlIC0tc2VsZWN0b3IgbmFtZT1vcGVu" + - "YmFvLXNlcnZlciBkZXN0cm95CiAgIGt1YmVjdGwgZGVsZXRlIG5hbWVzcGFjZSB2YXVsdC1zeXN0" + - "ZW0KICAga3ViZWN0bCBjcmVhdGUgbmFtZXNwYWNlIHZhdWx0LXN5c3RlbQogICAjIFJlLWNyZWF0" + - "ZSBwdWxsIHNlY3JldCBpZiBuZWVkZWQKICAgSEVMTUZJTEVfRU5WPTxlbnY+IGhlbG1maWxlIC0t" + - "c2VsZWN0b3IgbmFtZT1vcGVuYmFvLXNlcnZlciBzeW5jCiAgIGBgYAoKMy4gKipPcGVuQmFvIHBv" + - "ZHMgcnVubmluZyBidXQgbm90IGluaXRpYWxpemVkKio6IENoZWNrIHVuc2VhbCBzdGF0dXM6CiAg" + - "IGBgYGJhc2gKICAga3ViZWN0bCBleGVjIC1uIHZhdWx0LXN5c3RlbSBvcGVuYmFvLXNlcnZlci0w" + - "IC1jIG9wZW5iYW8gLS0gYmFvIHN0YXR1cwogICBgYGAKCjQuICoqYHZhdWx0LWFnZW50LWluaXRg" + - "IGxvZ3MgYGF1dGgvand0L2xvZ2luYCAtPiBgNDAwIC4uLiBubyBrbm93biBrZXkgc3VjY2Vzc2Z1" + - "bGx5IHZhbGlkYXRlZCB0aGUgdG9rZW4gc2lnbmF0dXJlYCoqCiAgIChjb21tb24gb24gQUtTLCBh" + - "bmQgb24gYW55IGNsdXN0ZXIgd2hvc2UgT0lEQyBpc3N1ZXIgcHVibGlzaGVzIGEgcm90YXRpbmcg" + - "bXVsdGkta2V5IEpXS1MpOgogICB0aGUgT3BlbkJhbyBgYXV0aC9qd3RgIG1ldGhvZCB3YXMgY29u" + - "ZmlndXJlZCB3aXRoIGEgc2luZ2xlIHN0YXRpYyBtb3VudGVkIHB1YmxpYyBrZXkKICAgKGBqd3Rf" + - "dmFsaWRhdGlvbl9wdWJrZXlzYCkgaW5zdGVhZCBvZiB0aGUgaXNzdWVyJ3MgbGl2ZSBKV0tTLCBz" + - "byBpdCBjYW5ub3QgdmFsaWRhdGUgdGhlCiAgIGNsdXN0ZXItc2lnbmVkIFNlcnZpY2VBY2NvdW50" + - "IHRva2VucyB0aGUgdmF1bHQtYWdlbnRzIHByZXNlbnQuIEV2ZXJ5IHNlcnZpY2UncwogICBgdmF1" + - "bHQtYWdlbnQtaW5pdGAgdGhlbiBsb29wcyBvbiB0aGF0IDQwMCBhbmQgdGhlIHBvZCBuZXZlciBs" + - "ZWF2ZXMgYEluaXQ6MC8xYC4gVGhpcyBpcyB0aGUKICAgYmVoYXZpb3Igb2YgdGhlIGJhc2UgZGVm" + - "YXVsdCBgb3BlbmJhby5taWdyYXRpb25zLmlzc3VlckRpc2NvdmVyeS5lbmFibGVkOiBmYWxzZWA7" + - "IHRoZQogICBtaWdyYXRpb24gbG9nIHNob3dzIGBPSURDIGlzc3VlciBkaXNjb3ZlcnkgaXMgZGlz" + - "YWJsZWQuIFVzaW5nIFNlcnZpY2VBY2NvdW50IGlzc3VlciBhbmQKICAgbW91bnRlZCBwdWJsaWMg" + - "a2V5IGZhbGxiYWNrYC4gRW5hYmxlIGlzc3VlciBkaXNjb3Zlcnkgc28gdGhlIG1pZ3JhdGlvbiB1" + - "c2VzIHRoZSBsaXZlIEpXS1MsCiAgIHRoZW4gcmUtcnVuIGl0OgogICBgYGBiYXNoCiAgICMgSW4g" + - "eW91ciBlbnYgdmFsdWVzIChlLmcuIGVudmlyb25tZW50cy88ZW52Pi55YW1sKSwgc2V0OgogICAj" + - "ICAgb3BlbmJhbzoKICAgIyAgICAgbWlncmF0aW9uczoKICAgIyAgICAgICBpc3N1ZXJEaXNjb3Zl" + - "cnk6CiAgICMgICAgICAgICBlbmFibGVkOiB0cnVlCiAgIGt1YmVjdGwgZGVsZXRlIGpvYiAtbiB2" + - "YXVsdC1zeXN0ZW0gb3BlbmJhby1zZXJ2ZXItbWlncmF0aW9ucwogICBIRUxNRklMRV9FTlY9PGVu" + - "dj4gaGVsbWZpbGUgLS1zZWxlY3RvciBuYW1lPW9wZW5iYW8tc2VydmVyIHN5bmMKICAgYGBgCiAg" + - "IFRoZSBtaWdyYXRpb24gbG9nIHNob3VsZCB0aGVuIHNob3cgYFJlY2VpdmVkIGRpc2NvdmVyeSBk" + - "b2N1bWVudCBmcm9tCiAgIC4uLi8ud2VsbC1rbm93bi9vcGVuaWQtY29uZmlndXJhdGlvbmAgYW5k" + - "IGBGaW5hbCBPcGVuQmFvIEpXVCBKV0tTIFVSTCBzZXQgdG86CiAgIC4uLi9vcGVuaWQvdjEvandr" + - "c2AsIGFuZCB0aGUgc2VydmljZSBwb2RzIGF1dGhlbnRpY2F0ZSBhbmQgcmVhY2ggUmVhZHkgd2l0" + - "aGluIH4xIG1pbi4KICAgQUtTIGNsdXN0ZXJzIHdpdGggdGhlIGNsdXN0ZXIgT0lEQyBpc3N1ZXIg" + - "ZW5hYmxlZCAodGhlIGBha3MtY2x1c3RlcmAgZGVmYXVsdCkgYWx3YXlzIG5lZWQKICAgdGhpcyBi" + - "ZWNhdXNlIHRoZSBzdGF0aWMtcHVia2V5IGZhbGxiYWNrIGNhbm5vdCBtYXRjaCB0aGUgaXNzdWVy" + - "J3Mgcm90YXRpbmcgc2lnbmluZyBrZXlzLgoKIyMgRmFpbHVyZTogT09NS2lsbGVkIG9uIENhc3Nh" + - "bmRyYQoKIyMjIFN5bXB0b21zCgpgYGAKTkFNRSAgICAgICAgICBSRUFEWSAgIFNUQVRVUyAgICBS" + - "RVNUQVJUUyAgIEFHRQpjYXNzYW5kcmEtMCAgIDAvMSAgICAgT09NS2lsbGVkICAzICAgICAgICAg" + - "MTBtCmBgYAoKIyMjIERpYWdub3NpcwoKYGBgYmFzaAprdWJlY3RsIGRlc2NyaWJlIHBvZCAtbiBj" + - "YXNzYW5kcmEtc3lzdGVtIGNhc3NhbmRyYS0wIHwgZ3JlcCAtQTMgIkxhc3QgU3RhdGUiCmBgYAoK" + - "IyMjIEZpeAoKT3ZlcnJpZGUgQ2Fzc2FuZHJhIHJlc291cmNlcyB2aWEgaGVsbWZpbGUgdmFsdWVz" + - "LiBTZWUgRXhhbXBsZSAxIGluCltleGFtcGxlcy5tZF0oLi4vZXhhbXBsZXMubWQpLgoKYGBgeWFt" + - "bAotIGNhc3NhbmRyYToKICAgIHJlc291cmNlczoKICAgICAgbGltaXRzOgogICAgICAgIGNwdTog" + - "IjgiCiAgICAgICAgbWVtb3J5OiA4MTkyTWkKICAgICAgcmVxdWVzdHM6CiAgICAgICAgY3B1OiAi" + - "MiIKICAgICAgICBtZW1vcnk6IDQwOTZNaQpgYGAKCk5vdGU6IGByZXNvdXJjZVByZXNldGAgaXMg" + - "bm90IGF2YWlsYWJsZSBpbiB0aGUgTlZDRiBjYXNzYW5kcmEgd3JhcHBlciBjaGFydC4gVXNlIGV4" + - "cGxpY2l0IGByZXNvdXJjZXNgLgoKIyMgRmFpbHVyZTogUG9kcyBTdHVjayBpbiBQZW5kaW5nCgoj" + - "IyMgU3ltcHRvbXMKCmBgYApOQU1FICAgICAgICAgIFJFQURZICAgU1RBVFVTICAgIFJFU1RBUlRT" + - "ICAgQUdFCmNhc3NhbmRyYS0wICAgMC8xICAgICBQZW5kaW5nICAgMCAgICAgICAgICAxMG0KYGBg" + - "CgojIyMgRGlhZ25vc2lzCgpgYGBiYXNoCiMgQ2hlY2sgZXZlbnRzIGZvciBzY2hlZHVsaW5nIGZh" + - "aWx1cmVzCmt1YmVjdGwgZGVzY3JpYmUgcG9kIC1uIDxuYW1lc3BhY2U+IDxwb2QtbmFtZT4KCiMg" + - "Q29tbW9uIGNhdXNlczoKIyAtICIwLzEyIG5vZGVzIGFyZSBhdmFpbGFibGU6IDEyIG5vZGUocykg" + - "ZGlkbid0IG1hdGNoIFBvZCdzIG5vZGUgYWZmaW5pdHkiCiMgLSAiMC8xMiBub2RlcyBhcmUgYXZh" + - "aWxhYmxlOiAxMiBJbnN1ZmZpY2llbnQgbWVtb3J5IgojIC0gInBlcnNpc3RlbnR2b2x1bWVjbGFp" + - "bSBub3QgZm91bmQiCmBgYAoKIyMjIEZpeGVzCgoxLiAqKk5vZGUgc2VsZWN0b3IgbWlzbWF0Y2gq" + - "KjogQ2hlY2sgYG5vZGVTZWxlY3RvcnNgIGluIGVudmlyb25tZW50IGZpbGUKICAgYGBgYmFzaAog" + - "ICBrdWJlY3RsIGdldCBub2RlcyAtLXNob3ctbGFiZWxzIHwgZ3JlcCBudmNmCiAgIGBgYAoKMi4g" + - "KipTdG9yYWdlIGNsYXNzIG5vdCBmb3VuZCoqOiBDaGVjayBzdG9yYWdlIGNsYXNzIGV4aXN0cwog" + - "ICBgYGBiYXNoCiAgIGt1YmVjdGwgZ2V0IHN0b3JhZ2VjbGFzcwogICBgYGAKCjMuICoqSW5zdWZm" + - "aWNpZW50IHJlc291cmNlcyoqOiBDaGVjayBub2RlIGNhcGFjaXR5CiAgIGBgYGJhc2gKICAga3Vi" + - "ZWN0bCBkZXNjcmliZSBub2RlIDxub2RlLW5hbWU+IHwgZ3JlcCAtQTUgIkFsbG9jYXRlZCByZXNv" + - "dXJjZXMiCiAgIGBgYAoKIyMgRmFpbHVyZTogSGVsbSBSZWxlYXNlIGluIEZhaWxlZCBTdGF0ZQoK" + - "IyMjIFN5bXB0b21zCgpgYGBiYXNoCmhlbG0gbGlzdCAtQQojIFNob3dzIHJlbGVhc2Ugd2l0aCBT" + - "VEFUVVM6IGZhaWxlZApgYGAKClJlLXJ1bm5pbmcgYGhlbG1maWxlIHN5bmNgIGFwcGVhcnMgdG8g" + - "c3VjY2VlZCBidXQgc2VydmljZXMgZG9uJ3Qgd29yayAobWlncmF0aW9ucyBza2lwcGVkKS4KCiMj" + - "IyBEaWFnbm9zaXMKCmBgYGJhc2gKaGVsbSBoaXN0b3J5IDxyZWxlYXNlLW5hbWU+IC1uIDxuYW1l" + - "c3BhY2U+CmBgYAoKIyMjIEZpeAoKTXVzdCBkZXN0cm95IHRoZSBmYWlsZWQgcmVsZWFzZSBhbmQg" + - "cmUtc3luYyAobm90IGp1c3QgYXBwbHkpOgoKYGBgYmFzaApIRUxNRklMRV9FTlY9PGVudj4gaGVs" + - "bWZpbGUgLS1zZWxlY3RvciBuYW1lPTxyZWxlYXNlPiBkZXN0cm95CiMgSWYgbmFtZXNwYWNlIG5l" + - "ZWRzIGNsZWFudXA6Cmt1YmVjdGwgZGVsZXRlIG5hbWVzcGFjZSA8bmFtZXNwYWNlPgprdWJlY3Rs" + - "IGNyZWF0ZSBuYW1lc3BhY2UgPG5hbWVzcGFjZT4KIyBSZS1jcmVhdGUgcHVsbCBzZWNyZXQgaWYg" + - "bmVlZGVkCkhFTE1GSUxFX0VOVj08ZW52PiBoZWxtZmlsZSAtLXNlbGVjdG9yIG5hbWU9PHJlbGVh" + - "c2U+IHN5bmMKYGBgCgojIyBGYWlsdXJlOiBBY2NvdW50IEJvb3RzdHJhcCBKb2IgRmFpbGVkCgoj" + - "IyMgU3ltcHRvbXMKClNlcnZpY2VzIGRlcGxveSBidXQgZnVuY3Rpb25zIGNhbid0IGJlIGNyZWF0" + - "ZWQuIEV2ZW50cyBzaG93IGJvb3RzdHJhcCBqb2IgZmFpbHVyZS4KCiMjIyBEaWFnbm9zaXMKCmBg" + - "YGJhc2gKIyBDaGVjayBib290c3RyYXAgam9iIHN0YXR1cwprdWJlY3RsIGdldCBqb2JzIC1uIG52" + - "Y2YKCiMgR2V0IGxvZ3MgKGpvYiBhdXRvLWRlbGV0ZXMgYWZ0ZXIgfjUgbWludXRlcykKa3ViZWN0" + - "bCBsb2dzIGpvYi9udmNmLWFwaS1hY2NvdW50LWJvb3RzdHJhcCAtbiBudmNmCgojIENoZWNrIEFQ" + - "SSBsb2dzCmt1YmVjdGwgbG9ncyAtbiBudmNmIC1sIGFwcC5rdWJlcm5ldGVzLmlvL25hbWU9bnZj" + - "Zi1hcGkgLS10YWlsPTEwMApgYGAKCiMjIyBDb21tb24gQ2F1c2VzCgoxLiAqKldyb25nIGJhc2U2" + - "NCBjcmVkZW50aWFscyoqOiBDcmVkZW50aWFscyBpbiBgc2VjcmV0cy88ZW52Pi1zZWNyZXRzLnlh" + - "bWxgIG11c3QgYmUgYCRvYXV0aHRva2VuOkFQSV9LRVlgIGJhc2U2NC1lbmNvZGVkLCBub3QganVz" + - "dCB0aGUgQVBJIGtleQogICBgYGBiYXNoCiAgICMgVmVyaWZ5IHlvdXIgZW5jb2RlZCBjcmVkZW50" + - "aWFsCiAgIGVjaG8gJ1lPVVJfQkFTRTY0X1NUUklORycgfCBiYXNlNjQgLWQKICAgIyBTaG91bGQg" + - "b3V0cHV0OiAkb2F1dGh0b2tlbjpudmFwaS14eHh4eAogICBgYGAKCjIuICoqUmVnaXN0cnkgdW5y" + - "ZWFjaGFibGUqKjogQVBJIGNhbid0IHJlYWNoIHRoZSByZWdpc3RyeSBzcGVjaWZpZWQgaW4gYGFj" + - "Y291bnRCb290c3RyYXAucmVnaXN0cnlDcmVkZW50aWFsc2AKCiMjIyBGaXgKCkZpeCBjcmVkZW50" + - "aWFscyBpbiBzZWNyZXRzIGZpbGUsIHRoZW4gcmVjb3ZlciBzZXJ2aWNlcyB3aXRob3V0IGRlc3Ry" + - "b3lpbmcgZGVwZW5kZW5jaWVzOgoKYGBgYmFzaApIRUxNRklMRV9FTlY9PGVudj4gaGVsbWZpbGUg" + - "LS1zZWxlY3RvciByZWxlYXNlLWdyb3VwPXNlcnZpY2VzIGRlc3Ryb3kKa3ViZWN0bCBkZWxldGUg" + - "bmFtZXNwYWNlIG52Y2YgYXBpLWtleXMgZXNzIHNpcyAtLWlnbm9yZS1ub3QtZm91bmQKa3ViZWN0" + - "bCBjcmVhdGUgbmFtZXNwYWNlIG52Y2YgJiYga3ViZWN0bCBjcmVhdGUgbmFtZXNwYWNlIGFwaS1r" + - "ZXlzICYmIFwKICBrdWJlY3RsIGNyZWF0ZSBuYW1lc3BhY2UgZXNzICYmIGt1YmVjdGwgY3JlYXRl" + - "IG5hbWVzcGFjZSBzaXMKIyBSZS1jcmVhdGUgcHVsbCBzZWNyZXRzIGlmIG5lZWRlZApIRUxNRklM" + - "RV9FTlY9PGVudj4gaGVsbWZpbGUgLS1zZWxlY3RvciByZWxlYXNlLWdyb3VwPXNlcnZpY2VzIHN5" + - "bmMKYGBgCgojIyBGYWlsdXJlOiBOVkNBIENsZWFudXAgU3R1Y2sgKEZpbmFsaXplcnMpCgojIyMg" + - "U3ltcHRvbXMKCkNvbXB1dGUtcGxhbmUgY2xlYW51cCBoYW5ncy4gTmFtZXNwYWNlcwpzdHVjayBp" + - "biBgVGVybWluYXRpbmdgOgoKYGBgYmFzaAprdWJlY3RsIGdldCBucwojIG52Y2Etb3BlcmF0b3Ig" + - "ICAgVGVybWluYXRpbmcgICAxMG0KIyBudmNmLWJhY2tlbmQgICAgIFRlcm1pbmF0aW5nICAgMTBt" + - "CmBgYAoKIyMjIEZpeAoKUnVuIHRoaXMgb25seSBhZ2FpbnN0IHRoZSBjb21wdXRlIGNvbnRleHQs" + - "IGFmdGVyIHRoZSBub3JtYWwgY29tcHV0ZS1wbGFuZQp0ZWFyZG93biBoYXMgZmFpbGVkLiBSZW1v" + - "dmUgZmluYWxpemVycyBmcm9tIE5WQ0ZCYWNrZW5kIGN1c3RvbSByZXNvdXJjZXMgYW5kCmZvcmNl" + - "LWRlbGV0ZSBzdHVjayBuYW1lc3BhY2VzOgoKYGBgYmFzaApleHBvcnQgQ09NUFVURV9DT05URVhU" + - "PTxjb21wdXRlLXBsYW5lLWNvbnRleHQ+CgojIFJlbW92ZSBmaW5hbGl6ZXJzIGZyb20gTlZDRkJh" + - "Y2tlbmQgcmVzb3VyY2VzCmt1YmVjdGwgLS1jb250ZXh0ICIkQ09NUFVURV9DT05URVhUIiBnZXQg" + - "bnZjZmJhY2tlbmRzIC1BIC1vIGpzb24gfCBcCiAganEgJy5pdGVtc1tdIHwgLm1ldGFkYXRhLm5h" + - "bWVzcGFjZSArICIvIiArIC5tZXRhZGF0YS5uYW1lJyAtciB8IFwKICB4YXJncyAtSXt9IHNoIC1j" + - "ICdjb250ZXh0PSIkMSI7IG5zPSIkezIlJS8qfSI7IG5hbWU9IiR7MiMjKi99Ijsga3ViZWN0bCAt" + - "LWNvbnRleHQgIiRjb250ZXh0IiBwYXRjaCBudmNmYmFja2VuZCAiJG5hbWUiIC1uICIkbnMiIC0t" + - "dHlwZT1tZXJnZSAtcCAie1wibWV0YWRhdGFcIjp7XCJmaW5hbGl6ZXJzXCI6W119fSInIF8gIiRD" + - "T01QVVRFX0NPTlRFWFQiIHt9CgojIEZvcmNlLWRlbGV0ZSBzdHVjayBuYW1lc3BhY2VzIGJ5IGNs" + - "ZWFyaW5nIHRoZWlyIGZpbmFsaXplcnMKZm9yIG5zIGluIG52Y2Etb3BlcmF0b3IgbnZjZi1iYWNr" + - "ZW5kOyBkbwogIGt1YmVjdGwgLS1jb250ZXh0ICIkQ09NUFVURV9DT05URVhUIiBnZXQgbmFtZXNw" + - "YWNlICIkbnMiIC1vIGpzb24gMj4vZGV2L251bGwgfCBcCiAgICBqcSAnLnNwZWMuZmluYWxpemVy" + - "cyA9IFtdJyB8IFwKICAgIGt1YmVjdGwgLS1jb250ZXh0ICIkQ09NUFVURV9DT05URVhUIiByZXBs" + - "YWNlIFwKICAgICAgLS1yYXcgIi9hcGkvdjEvbmFtZXNwYWNlcy8kbnMvZmluYWxpemUiIC1mIC0g" + - "Mj4vZGV2L251bGwKZG9uZQpgYGAKCiMjIEZhaWx1cmU6IE5WQ0EgT3BlcmF0b3IgTWlzc2luZyBv" + - "ciBOb3QgUmVhZHkKCiMjIyBTeW1wdG9tcwoKQWZ0ZXIgY29tcHV0ZS1wbGFuZSBpbnN0YWxsIGNv" + - "bXBsZXRlcywgbm8gYG52Y2Etb3BlcmF0b3JgIERlcGxveW1lbnQgZXhpc3RzLCBvcgppdCBleGlz" + - "dHMgYnV0IHBvZHMgYXJlIG5vdCByZWFkeToKCmBgYGJhc2gKa3ViZWN0bCAtLWNvbnRleHQgPGNv" + - "bXB1dGUtcGxhbmUtY29udGV4dD4gZ2V0IGRlcGxveW1lbnQgbnZjYS1vcGVyYXRvciBcCiAgLW4g" + - "bnZjYS1vcGVyYXRvcgprdWJlY3RsIC0tY29udGV4dCA8Y29tcHV0ZS1wbGFuZS1jb250ZXh0PiBn" + - "ZXQgcG9kcyAtbiBudmNhLW9wZXJhdG9yCmBgYAoKIyMjIERpYWdub3NpcyBhbmQgRml4ZXMKCkZp" + - "cnN0IGlkZW50aWZ5IHdoZXRoZXIgdGhlIGluc3RhbGxhdGlvbiB1c2VkIHRoZSBIZWxtZmlsZSB2" + - "YWx1ZXMgZmxvdyBvciB0aGUKQ0xJIHByb2ZpbGUgZmxvdy4gUmUtcnVuIG9ubHkgdGhhdCBmbG93" + - "J3MgcmVnaXN0cmF0aW9uIGFuZCBpbnN0YWxsYXRpb24gc3RlcHMKZnJvbSBbU3BsaXQgQ29tcHV0" + - "ZS1QbGFuZSBJbnN0YWxsYXRpb25dKGNvbXB1dGUtcGxhbmUtaW5zdGFsbGF0aW9uLm1kKS4gRG8g" + - "bm90CnN1YnN0aXR1dGUgb25lIGhhbmRvZmYgZm9yIHRoZSBvdGhlci4KCklmIGluc3RhbGxhdGlv" + - "biBzdWNjZWVkcyBidXQgcG9kcyBzdGlsbCBmYWlsLCBpbnNwZWN0IHRoZSBjb21wdXRlIGNvbnRl" + - "eHQ6CgpgYGBiYXNoCmt1YmVjdGwgLS1jb250ZXh0IDxjb21wdXRlLXBsYW5lLWNvbnRleHQ+IGRl" + - "c2NyaWJlIGRlcGxveSBudmNhLW9wZXJhdG9yIFwKICAtbiBudmNhLW9wZXJhdG9yCmt1YmVjdGwg" + - "LS1jb250ZXh0IDxjb21wdXRlLXBsYW5lLWNvbnRleHQ+IGxvZ3MgZGVwbG95bWVudC9udmNhLW9w" + - "ZXJhdG9yIFwKICAtbiBudmNhLW9wZXJhdG9yIC0tdGFpbD0yMDAKa3ViZWN0bCAtLWNvbnRleHQg" + - "PGNvbXB1dGUtcGxhbmUtY29udGV4dD4gZ2V0IG52Y2ZiYWNrZW5kIC1uIG52Y2Etb3BlcmF0b3IK" + - "YGBgCgpGb3IgcHJpdmF0ZSByZWdpc3RyeSBmYWlsdXJlcywgdmVyaWZ5IHRoYXQgdGhlIHB1bGwg" + - "c2VjcmV0IGV4aXN0cyBpbgpgbnZjYS1vcGVyYXRvcmAuIFRoZSBjaGFydCBhbmQgb3BlcmF0b3Ig" + - "bWlycm9yIGl0IHRvIGBudmNhLXN5c3RlbWA7IGRvIG5vdCB1c2UgYQpicm9hZGVyIG1hbnVhbCBu" + - "YW1lc3BhY2UgbG9vcC4gU2VlCltwdWxsLXNlY3JldHMubWRdKHB1bGwtc2VjcmV0cy5tZCNjb21w" + - "dXRlLXBsYW5lLXNlY3JldCkuCgojIyBGYWlsdXJlOiBHYXRld2F5IEVuZHBvaW50IENoYW5nZWQK" + - "CiMjIyBTeW1wdG9tcwoKUmVxdWVzdHMgdGhyb3VnaCB0aGUgY29uZmlndXJlZCByb3V0ZSBkb21h" + - "aW4gZmFpbCBhZnRlciB0aGUgR2F0ZXdheSBvciBpdHMgbG9hZCBiYWxhbmNlciBpcyByZXBsYWNl" + - "ZC4gVGhlIEdhdGV3YXkgcmVwb3J0cyBhIG5ldyBlbmRwb2ludC4KCiMjIyBEaWFnbm9zaXMKCmBg" + - "YGJhc2gKR0FURVdBWV9BRERSPSQoa3ViZWN0bCAtLWNvbnRleHQgPGNvbnRyb2wtcGxhbmUtY29u" + - "dGV4dD4gZ2V0IGdhdGV3YXkgXAogIG52Y2YtZ2F0ZXdheSAtbiBlbnZveS1nYXRld2F5IFwKICAt" + - "byBqc29ucGF0aD0ney5zdGF0dXMuYWRkcmVzc2VzWzBdLnZhbHVlfScpCnRlc3QgLW4gIiRHQVRF" + - "V0FZX0FERFIiCmRpZyArc2hvcnQgImFwaS48ZG9tYWluPiIKY3VybCAtSCAiSG9zdDogYXBpLjxk" + - "b21haW4+IiAiaHR0cDovLyRHQVRFV0FZX0FERFIvaGVhbHRoIgoKIyBDaGVjayBkaXJlY3QgYWRk" + - "cmVzcyBmaWVsZHMgdXNlZCBieSBhIHNwbGl0IGRlcGxveW1lbnQKZ3JlcCBudmNmTmF0c1NlcnZp" + - "Y2VVUkwgZGVwbG95L3N0YWNrcy9zZWxmLW1hbmFnZWQvZW52aXJvbm1lbnRzLzxlbnY+LnlhbWwK" + - "Z3JlcCAtRSAnaWNtc1NlcnZpY2V8cmV2YWxTZXJ2aWNlfG5hdHMnIFwKICBkZXBsb3kvc3RhY2tz" + - "L252Y2YtY29tcHV0ZS1wbGFuZS9lbnZpcm9ubWVudHMvPGVudj4ueWFtbApgYGAKCiMjIyBGaXgK" + - "CktlZXAgYGdsb2JhbC5kb21haW5gLCByb3V0ZSBob3N0bmFtZXMsIGFuZCBIb3N0IG92ZXJyaWRl" + - "cyB1bmNoYW5nZWQuIFVwZGF0ZSBETlMKb3IgdGhlIGRpcmVjdCBjbGllbnQgZGVzdGluYXRpb24u" + - "IEZvciBzcGxpdCBkZXBsb3ltZW50cywgdXBkYXRlIG9ubHkgZGlyZWN0CkdhdGV3YXktYWRkcmVz" + - "cyBmaWVsZHMgYW5kIHJlZnJlc2ggcmVnaXN0cmF0aW9uIGFuZCBpbnN0YWxsYXRpb24gdGhyb3Vn" + - "aCB0aGUKc2FtZSBzZWxlY3RlZCBoYW5kb2ZmLiBTZWUKW0V4YW1wbGUgNF0oLi4vZXhhbXBsZXMu" + - "bWQjZXhhbXBsZS00LXJlY292ZXItZnJvbS1hLWdhdGV3YXktZW5kcG9pbnQtY2hhbmdlKS4KCiMj" + - "IFVzZWZ1bCBOYW1lc3BhY2UtdG8tU2VydmljZSBNYXBwaW5nCgpRdWljayByZWZlcmVuY2UgZm9y" + - "IHdoaWNoIHNlcnZpY2VzIHJ1biBpbiB3aGljaCBuYW1lc3BhY2U6Cgp8IE5hbWVzcGFjZSB8IFNl" + - "cnZpY2VzIHwKfC0tLS0tLS0tLS0tfC0tLS0tLS0tLS18CnwgbmF0cy1zeXN0ZW0gfCBuYXRzIHwK" + - "fCBjYXNzYW5kcmEtc3lzdGVtIHwgY2Fzc2FuZHJhIHwKfCB2YXVsdC1zeXN0ZW0gfCBvcGVuYmFv" + - "LXNlcnZlciB8CnwgbnZjZiB8IGFwaSwgaW52b2NhdGlvbi1zZXJ2aWNlLCBncnBjLXByb3h5LCBu" + - "b3Rhcnktc2VydmljZSwgcmV2YWwsIGxsbS1yZXF1ZXN0LXJvdXRlciwgbGxtLWFwaS1nYXRld2F5" + - "IHwKfCBhcGkta2V5cyB8IGFwaS1rZXlzLCBhZG1pbi1pc3N1ZXItcHJveHkgfAp8IGVzcyB8IGVz" + - "cy1hcGkgfAp8IHNpcyB8IHNpcyB8CnwgZW52b3ktZ2F0ZXdheS1zeXN0ZW0gfCBlbnZveSBnYXRl" + - "d2F5IChpbmdyZXNzIGNvbnRyb2xsZXIpIHwKfCBlbnZveS1nYXRld2F5IHwgZ2F0ZXdheSByZXNv" + - "dXJjZSArIHJvdXRlcyB8ClBLBwhWPmeQlTAAAJUwAABQSwMEFAAIAAAAAAAhAAAAAAAAAAAAAAAA" + - "AEQACQBkYXRhL252Y2Ytc2VsZi1tYW5hZ2VkLWluc3RhbGxhdGlvbi9yZWZlcmVuY2VzL2hlbG1m" + - "aWxlLXN0cnVjdHVyZS5tZFVUBQABAKbOEiMgSGVsbWZpbGUgU3RydWN0dXJlIFJlZmVyZW5jZQoK" + - "IyMgRGlyZWN0b3J5IExheW91dAoKYGBgCm52Y2Ytc2VsZi1tYW5hZ2VkLXN0YWNrLwp8LS0gaGVs" + - "bWZpbGUuZC8KfCAgIHwtLSAwMS1kZXBlbmRlbmNpZXMueWFtbC5nb3RtcGwgICMgTkFUUywgQ2Fz" + - "c2FuZHJhLCBPcGVuQmFvCnwgICB8LS0gMDItY29yZS55YW1sLmdvdG1wbCAgICAgICAgICAjIE5W" + - "Q0Ygc2VydmljZXMgKyBpbmdyZXNzCnwgICBgLS0gMDMtb2JzZXJ2YWJpbGl0eS55YW1sLmdvdG1w" + - "bCAjIE9ic2VydmFiaWxpdHkgc3RhY2sgKG9wdGlvbmFsKQp8LS0gZW52aXJvbm1lbnRzLwp8ICAg" + - "fC0tIGJhc2UueWFtbCAgICAgICAgICAgICAgICAgICAgIyBEZWZhdWx0IHZhbHVlcyAoYWxsIGVu" + - "dmlyb25tZW50cykKfCAgIGAtLSA8ZW52LW5hbWU+LnlhbWwgICAgICAgICAgICAgICMgUGVyLWVu" + - "dmlyb25tZW50IG92ZXJyaWRlcwp8LS0gc2VjcmV0cy8KfCAgIGAtLSA8ZW52LW5hbWU+LXNlY3Jl" + - "dHMueWFtbCAgICAgICMgU2Vuc2l0aXZlIHZhbHVlcyAocmVnaXN0cnkgY3JlZHMsIHBhc3N3b3Jk" + - "cykKYC0tIGdsb2JhbC55YW1sLmdvdG1wbCAgICAgICAgICAgICAgICMgR28gdGVtcGxhdGUgdGhh" + - "dCBjb25zdHJ1Y3RzIHBlci1jaGFydCB2YWx1ZXMKCm52Y2YtY29tcHV0ZS1wbGFuZS1zdGFjay8K" + - "fC0tIGhlbG1maWxlLmQvCnwgICB8LS0gMDEtZGVwZW5kZW5jaWVzLnlhbWwuZ290bXBsICAjIENv" + - "bXB1dGUgcGxhbmUgZGVwZW5kZW5jeSBjb21wb25lbnRzCnwgICBgLS0gMDItbnZjYS55YW1sLmdv" + - "dG1wbCAgICAgICAgICAjIG52Y2Etb3BlcmF0b3IgY2hhcnQgYW5kIG52Y2EgY29uZmlndXJhdGlv" + - "bgp8LS0gZW52aXJvbm1lbnRzLwp8ICAgfC0tIGJhc2UueWFtbCAgICAgICAgICAgICAgICAgICAg" + - "IyBEZWZhdWx0IHZhbHVlcyAoYWxsIGVudmlyb25tZW50cykKfCAgIGAtLSA8ZW52LW5hbWU+Lnlh" + - "bWwgICAgICAgICAgICAgICMgUGVyLWVudmlyb25tZW50IG92ZXJyaWRlcwpgLS0gZ2xvYmFsLnlh" + - "bWwuZ290bXBsICAgICAgICAgICAgICAgIyBHbyB0ZW1wbGF0ZSB0aGF0IGNvbnN0cnVjdHMgcGVy" + - "LWNoYXJ0IHZhbHVlcwpgYGAKClRoZXNlIHN0YWNrcyBoYXZlIHNlcGFyYXRlIGVudmlyb25tZW50" + - "cyBhbmQgY2x1c3RlciBjb250ZXh0cy4gVGhlIEhlbG1maWxlCnZhbHVlcyBmbG93IGF1dGhvcnMg" + - "Ym90aCBlbnZpcm9ubWVudCBmaWxlcy4gVGhlIENMSSBwcm9maWxlIGZsb3cgaW5zdGVhZCB1c2Vz" + - "CnRoZSBDTEktZ2VuZXJhdGVkIGNvbnRyb2wtcGxhbmUgcHJvZmlsZSBhcyBpdHMgZW5kcG9pbnQg" + - "c291cmNlLiBEbyBub3QgbWl4IHRoZQpoYW5kb2Zmcy4gU2VlIFtTcGxpdCBDb21wdXRlLVBsYW5l" + - "IEluc3RhbGxhdGlvbl0oY29tcHV0ZS1wbGFuZS1pbnN0YWxsYXRpb24ubWQpLgoKIyMgR290bXBs" + - "IEZpbGVzIGFuZCBUaGVpciBSZWxlYXNlcwoKIyMjIDAxLWRlcGVuZGVuY2llcy55YW1sLmdvdG1w" + - "bAoKfCBSZWxlYXNlIHwgQ2hhcnQgfCBOYW1lc3BhY2UgfCBOb3RlcyB8CnwtLS0tLS0tLS18LS0t" + - "LS0tLXwtLS0tLS0tLS0tLXwtLS0tLS0tfAp8IG5hdHMgfCBoZWxtLW52Y2YtbmF0cyB8IG5hdHMt" + - "c3lzdGVtIHwgTWVzc2FnaW5nIHwKfCBvcGVuYmFvLXNlcnZlciB8IGhlbG0tbnZjZi1vcGVuYmFv" + - "LXNlcnZlciB8IHZhdWx0LXN5c3RlbSB8IFNlY3JldHMgbWFuYWdlbWVudCwgZGVwZW5kcyBvbiBu" + - "YXRzIHwKfCBjYXNzYW5kcmEgfCBoZWxtLW52Y2YtY2Fzc2FuZHJhIHwgY2Fzc2FuZHJhLXN5c3Rl" + - "bSB8IERhdGFiYXNlIHwKClVzZXMgYDw8OiAqZGVwZW5kZW5jeWAgdGVtcGxhdGUgaW5oZXJpdGFu" + - "Y2Ugd2l0aCBgcmVsZWFzZS1ncm91cDogZGVwZW5kZW5jaWVzYCBsYWJlbC4KCiMjIyAwMi1jb3Jl" + - "LnlhbWwuZ290bXBsCgp8IFJlbGVhc2UgfCBDaGFydCB8IE5hbWVzcGFjZSB8IExhYmVsIHwKfC0t" + - "LS0tLS0tLXwtLS0tLS0tfC0tLS0tLS0tLS0tfC0tLS0tLS18CnwgYXBpLWtleXMgfCBoZWxtLW52" + - "Y2YtYXBpLWtleXMgfCBhcGkta2V5cyB8IHNlcnZpY2VzIHwKfCBzaXMgfCBoZWxtLW52Y2Ytc2lz" + - "IHwgc2lzIHwgc2VydmljZXMgfAp8IGFwaSB8IGhlbG0tbnZjZi1hcGkgfCBudmNmIHwgc2Vydmlj" + - "ZXMgfAp8IGludm9jYXRpb24tc2VydmljZSB8IGhlbG0tbnZjZi1pbnZvY2F0aW9uLXNlcnZpY2Ug" + - "fCBudmNmIHwgc2VydmljZXMgfAp8IGdycGMtcHJveHkgfCBoZWxtLW52Y2YtZ3JwYy1wcm94eSB8" + - "IG52Y2YgfCBzZXJ2aWNlcyB8CnwgZXNzLWFwaSB8IGhlbG0tbnZjZi1lc3MtYXBpIHwgZXNzIHwg" + - "c2VydmljZXMgfAp8IG5vdGFyeS1zZXJ2aWNlIHwgaGVsbS1udmNmLW5vdGFyeS1zZXJ2aWNlIHwg" + - "bnZjZiB8IHNlcnZpY2VzIHwKfCByZXZhbCB8IGhlbG0tcmV2YWwgfCBudmNmIHwgc2VydmljZXMg" + - "fAp8IGxsbS1yZXF1ZXN0LXJvdXRlciB8IG52Y2YvaGVsbS1udmNmLWxsbS1yZXF1ZXN0LXJvdXRl" + - "ciB8IG52Y2YgfCBzZXJ2aWNlcyB8CnwgbGxtLWFwaS1nYXRld2F5IHwgbnZjZi9oZWxtLW52Y2Yt" + - "bGxtLWFwaS1nYXRld2F5IHwgbnZjZiB8IHNlcnZpY2VzIHwKfCBhZG1pbi1pc3N1ZXItcHJveHkg" + - "fCBoZWxtLWFkbWluLXRva2VuLWlzc3Vlci1wcm94eSB8IGFwaS1rZXlzIHwgKG5vIHJlbGVhc2Ut" + - "Z3JvdXAgbGFiZWwpIHwKfCBpbmdyZXNzIHwgbnZjZi1nYXRld2F5LXJvdXRlcyB8IGVudm95LWdh" + - "dGV3YXktc3lzdGVtIHwgaW5ncmVzcyB8CgpNb3N0IHNlcnZpY2VzIHVzZSBgaW5oZXJpdDogW3t0" + - "ZW1wbGF0ZTogc2VydmljZX1dYC4gTExNIHJlbGVhc2VzIHVzZSBPQ0kgY2hhcnRzIGZyb20gdGhl" + - "IGBudmNmYCByZXBvc2l0b3J5IHdpdGggc3RhbmRhbG9uZSBgdmFsdWVzOiBbLi4vZ2xvYmFsLnlh" + - "bWwuZ290bXBsXWAgYmxvY2tzOyB0aGV5IGFyZSBnYXRlZCBvbiBgYWRkb25zLmxsbS5lbmFibGVk" + - "YC4gYGFkbWluLWlzc3Vlci1wcm94eWAgYW5kIGBpbmdyZXNzYCBoYXZlIHN0YW5kYWxvbmUgYHZh" + - "bHVlczpgIGJsb2Nrcy4KCiMjIyAwMy1vYnNlcnZhYmlsaXR5LnlhbWwuZ290bXBsCgp8IFJlbGVh" + - "c2UgfCBDaGFydCB8IE5hbWVzcGFjZSB8IExhYmVsIHwKfC0tLS0tLS0tLXwtLS0tLS0tfC0tLS0t" + - "LS0tLS0tfC0tLS0tLS18CnwgKG9ic2VydmFiaWxpdHkgcmVsZWFzZXMpIHwgKHZhcmlvdXMpIHwg" + - "b2JzZXJ2YWJpbGl0eSB8IG9ic2VydmFiaWxpdHkgfAoKR2F0ZWQgb24gb2JzZXJ2YWJpbGl0eS1z" + - "cGVjaWZpYyBmbGFncyBpbiB0aGUgZW52aXJvbm1lbnQgZmlsZS4gU2tpcHBlZCBpZiBkaXNhYmxl" + - "ZC4KCiMjIFRlbXBsYXRlIEluaGVyaXRhbmNlCgojIyMgYDw8OiAqZGVwZW5kZW5jeWAgKFlBTUwg" + - "bWVyZ2UpCgpVc2VkIGluIGAwMS1kZXBlbmRlbmNpZXMueWFtbC5nb3RtcGxgLiBNZXJnZXMgdGhl" + - "IHRlbXBsYXRlJ3MgcHJvcGVydGllcyBpbnRvIHRoZSByZWxlYXNlLgoKKipHb3RjaGEqKjogWUFN" + - "TCBtZXJnZSByZXBsYWNlcyBsaXN0cy4gSWYgeW91IGFkZCBhIGB2YWx1ZXM6YCBrZXkgdG8gdGhl" + - "IHJlbGVhc2UsIGl0ICoqcmVwbGFjZXMqKiB0aGUgdGVtcGxhdGUncyBgdmFsdWVzOmAgbGlzdCBl" + - "bnRpcmVseS4gWW91IG11c3QgcmUtaW5jbHVkZSBhbGwgdGVtcGxhdGUgdmFsdWVzOgoKSW4gdGhl" + - "IHRlbXBsYXRlIGJlbG93LCBgPHByaXZhdGUtdmFsdWVzPmAgcmVmZXJzIHRvIHRoZSBgc2VjcmV0" + - "cy9gIGRpcmVjdG9yeSBhdCB0aGUgaGVsbWZpbGUgc3RhY2sgcm9vdC4KCmBgYHlhbWwKIyBUZW1w" + - "bGF0ZSBkZWZpbmVzOgp0ZW1wbGF0ZXM6CiAgZGVwZW5kZW5jeTogJmRlcGVuZGVuY3kKICAgIGNo" + - "YXJ0OiBudmNmL2hlbG0tbnZjZi17eyAuUmVsZWFzZS5OYW1lIH19CiAgICB2YWx1ZXM6CiAgICAg" + - "IC0gLi4vZ2xvYmFsLnlhbWwuZ290bXBsCiAgICAgIC0gLi4vPHByaXZhdGUtdmFsdWVzPi97eyBy" + - "ZXF1aXJlZEVudiAiSEVMTUZJTEVfRU5WIiB9fS1zZWNyZXRzLnlhbWwKCiMgV2hlbiBvdmVycmlk" + - "aW5nLCBNVVNUIHJlLWluY2x1ZGUgYm90aDoKLSBuYW1lOiBjYXNzYW5kcmEKICA8PDogKmRlcGVu" + - "ZGVuY3kKICB2YWx1ZXM6CiAgICAtIC4uL2dsb2JhbC55YW1sLmdvdG1wbCAgICAgICAgICAgICAg" + - "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIE11c3QgcmUtaW5jbHVkZQogICAgLSAu" + - "Li88cHJpdmF0ZS12YWx1ZXM+L3t7IHJlcXVpcmVkRW52ICJIRUxNRklMRV9FTlYiIH19LXNlY3Jl" + - "dHMueWFtbCAgICMgTXVzdCByZS1pbmNsdWRlCiAgICAtIGNhc3NhbmRyYTogICAgICAgICAgICAg" + - "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBZb3VyIG92ZXJy" + - "aWRlCiAgICAgICAgcmVzb3VyY2VzOgogICAgICAgICAgbGltaXRzOgogICAgICAgICAgICBtZW1v" + - "cnk6IDgxOTJNaQpgYGAKCiMjIyBgaW5oZXJpdGAgKEhlbG1maWxlIG5hdGl2ZSkKClVzZWQgaW4g" + - "YDAyLWNvcmUueWFtbC5nb3RtcGxgLiBIZWxtZmlsZSdzIG5hdGl2ZSBpbmhlcml0YW5jZSBtZWNo" + - "YW5pc20uCgpgYGB5YW1sCi0gbmFtZTogYXBpCiAgdmVyc2lvbjogMS42LjAKICBuYW1lc3BhY2U6" + - "IG52Y2YKICBpbmhlcml0OgogICAgLSB0ZW1wbGF0ZTogc2VydmljZQpgYGAKCldoZW4gYWRkaW5n" + - "IGB2YWx1ZXM6YCB0byBhbiBpbmhlcml0ZWQgcmVsZWFzZSwgeW91IGFsc28gbmVlZCB0byByZS1p" + - "bmNsdWRlIHRoZSB0ZW1wbGF0ZSdzIHZhbHVlcyBmaWxlcyBzaW5jZSBgdmFsdWVzYCBpcyBhIGxp" + - "c3QgdGhhdCBnZXRzIHJlcGxhY2VkLgoKIyMgVmFsdWVzIFByZWNlZGVuY2UKCkZyb20gbG93ZXN0" + - "IHRvIGhpZ2hlc3QgcHJpb3JpdHk6CgoxLiBgZW52aXJvbm1lbnRzL2Jhc2UueWFtbGAgLS0gZGVm" + - "YXVsdHMgc2hhcmVkIGFjcm9zcyBhbGwgZW52aXJvbm1lbnRzCjIuIGBlbnZpcm9ubWVudHMvPGVu" + - "dj4ueWFtbGAgLS0gcGVyLWVudmlyb25tZW50IG92ZXJyaWRlcwozLiBgZ2xvYmFsLnlhbWwuZ290" + - "bXBsYCAtLSBHbyB0ZW1wbGF0ZSBwcm9jZXNzaW5nIChjb25zdHJ1Y3RzIGNoYXJ0LXNwZWNpZmlj" + - "IHN0cnVjdHVyZSkKNC4gYDxwcml2YXRlLXZhbHVlcz4vPGVudj4tc2VjcmV0cy55YW1sYCAtLSBz" + - "ZW5zaXRpdmUgdmFsdWVzCjUuIElubGluZSBgdmFsdWVzOmAgYmxvY2tzIG9uIHJlbGVhc2VzIC0t" + - "IGhpZ2hlc3QgcHJlY2VkZW5jZQoKIyMgV2hhdCBnbG9iYWwueWFtbC5nb3RtcGwgUGFzc2VzIFRo" + - "cm91Z2gKCmBnbG9iYWwueWFtbC5nb3RtcGxgIHJlYWRzIGZyb20gYC5WYWx1ZXNgICh0aGUgbWVy" + - "Z2VkIGVudmlyb25tZW50ICsgZW52LXNwZWNpZmljIFlBTUwpIGFuZCBjb25zdHJ1Y3RzIGNoYXJ0" + - "LXNwZWNpZmljIHZhbHVlcy4gSXQgb25seSBwYXNzZXMgdGhyb3VnaCBrZXlzIGl0IGV4cGxpY2l0" + - "bHkgcmVmZXJlbmNlczoKCiMjIyBDYXNzYW5kcmEKLSBgY2Fzc2FuZHJhLnJlcGxpY2FDb3VudGAK" + - "LSBgY2Fzc2FuZHJhLmltYWdlLipgIChyZWdpc3RyeSwgcmVwb3NpdG9yeSkKLSBgY2Fzc2FuZHJh" + - "Lm1pZ3JhdGlvbnMuaW1hZ2UuKmAKLSBgY2Fzc2FuZHJhLnBlcnNpc3RlbmNlLnNpemVgCi0gYGNh" + - "c3NhbmRyYS5ub2RlU2VsZWN0b3JgIChpZiBgZ2xvYmFsLm5vZGVTZWxlY3RvcnMuZW5hYmxlZGAp" + - "Ci0gYGNhc3NhbmRyYS5nbG9iYWwuZGVmYXVsdFN0b3JhZ2VDbGFzc2AKCiMjIyBOQVRTCi0gYG5h" + - "dHMuY29udGFpbmVyLmltYWdlLipgCi0gYG5hdHMucmVsb2FkZXIuaW1hZ2UuKmAKLSBgbmF0cy5u" + - "YXRzQm94LmNvbnRhaW5lci5pbWFnZS4qYAotIGBuYXRzLmNvbmZpZy5qZXRzdHJlYW0uZmlsZVN0" + - "b3JlLnB2Yy5zdG9yYWdlQ2xhc3NOYW1lYAotIGBuYXRzLnBvZFRlbXBsYXRlLm1lcmdlLnNwZWMu" + - "bm9kZVNlbGVjdG9yYCAoaWYgZW5hYmxlZCkKCiMjIyBPcGVuQmFvCi0gYG9wZW5iYW8ubWlncmF0" + - "aW9ucy5pbWFnZS4qYCBhbmQgYG9wZW5iYW8ubWlncmF0aW9ucy5lbnZgCi0gYG9wZW5iYW8uaW5q" + - "ZWN0b3IuaW1hZ2UuKmAKLSBgb3BlbmJhby5zZXJ2ZXIuaW1hZ2UuKmAKLSBgb3BlbmJhby5zZXJ2" + - "ZXIuZGF0YVN0b3JhZ2UuKmAKLSBOb2RlIHNlbGVjdG9ycyAoaWYgZW5hYmxlZCkKCiMjIyBTZXJ2" + - "aWNlcyAoQVBJLCBTSVMsIGV0Yy4pCi0gYDxzZXJ2aWNlPi5pbWFnZS4qYCAocmVnaXN0cnksIHJl" + - "cG9zaXRvcnkpCi0gYDxzZXJ2aWNlPi5ub2RlU2VsZWN0b3JgIChpZiBlbmFibGVkKQotIGA8c2Vy" + - "dmljZT4uZW52LipgIChvYnNlcnZhYmlsaXR5IHNldHRpbmdzKQoKIyMjIExMTSBGdW5jdGlvbiBF" + - "bmFibGVtZW50CgpFbmFibGUgdGhlIExMTSBhZGRvbiBiZWZvcmUgY3JlYXRpbmcgb3IgaW52b2tp" + - "bmcgZnVuY3Rpb25zIHdpdGgKYGZ1bmN0aW9uVHlwZTogIkxMTSJgLiBUaGUgYWRkb24gZGVwbG95" + - "cyBgbGxtLXJlcXVlc3Qtcm91dGVyYCBhbmQKYGxsbS1hcGktZ2F0ZXdheWAsIGFkZHMgdGhlIGBs" + - "bG0uaW52b2NhdGlvbi48ZG9tYWluPmAgcm91dGUgd2hlbiBHYXRld2F5IEFQSQppbmdyZXNzIGlz" + - "IGVuYWJsZWQsIGFuZCBjb25maWd1cmVzIHdvcmtlcnMgdG8gdXNlIHRoZSBMTE0gc2lkZWNhci4g" + - "VGhlIEhlbG1maWxlCmNvbmRpdGlvbiBpcyBgYWRkb25zLmxsbS5lbmFibGVkYC4gRG8gbm90IHVz" + - "ZSB0aGUgb2Jzb2xldGUgYGxsbS5lbmFibGVkYCBwYXRoLgoKRm9yIGEgc2luZ2xlLW5vZGUgaXNv" + - "bGF0ZWQgdGVzdCBjbHVzdGVyLCBhZGQgdGhpcyBjb25maWd1cmF0aW9uIHRvCmBlbnZpcm9ubWVu" + - "dHMvPGVudj4ueWFtbGAgYmVmb3JlIGFwcGx5aW5nIHRoZSBzdGFjazoKCmBgYHlhbWwKYWRkb25z" + - "OgogIGxsbToKICAgIGVuYWJsZWQ6IHRydWUKICAgIGdhdGV3YXk6CiAgICAgIHJlcGxpY2FDb3Vu" + - "dDogMQogICAgICBtZXRyaWNzOgogICAgICAgIHNlcnZpY2VNb25pdG9yOgogICAgICAgICAgZW5h" + - "YmxlZDogZmFsc2UKICAgIHJlcXVlc3RSb3V0ZXI6CiAgICAgIHJlcGxpY2FDb3VudDogMQogICAg" + - "ICBtZXRyaWNzOgogICAgICAgIHNlcnZpY2VNb25pdG9yOgogICAgICAgICAgZW5hYmxlZDogZmFs" + - "c2UKCmFnZW50Q29uZmlnOgogIG1lcmdlQ29uZmlnOiB8CiAgICBjbHVzdGVyOgogICAgICB2YWxp" + - "ZGF0aW9uUG9saWN5OgogICAgICAgIG5hbWU6IFVucmVzdHJpY3RlZAogICAgd29ya2xvYWQ6CiAg" + - "ICAgIHN0YXJnYXRlUVVJQ0luc2VjdXJlOiB0cnVlCmBgYAoKVXNlIGByZXBsaWNhQ291bnQ6IDFg" + - "IG9ubHkgZm9yIGxvY2FsIG9yIHNpbmdsZS1ub2RlIHRlc3QgY2x1c3RlcnMuIFRoZSBidW5kbGVk" + - "Ck5WQ0YgQVBJIGN1cnJlbnRseSBzZXJ2ZXMgcGxhaW50ZXh0IGdSUEMgb24gcG9ydCA5MDkwLCBz" + - "byB0aGUgc2VsZi1tYW5hZ2VkIHN0YWNrCmRlZmF1bHRzIHRoZSBMTE0gQVBJIEdhdGV3YXkgYXV0" + - "aGVudGljYXRpb24gaG9wIHRvIHBsYWludGV4dC4gU2V0CmBhZGRvbnMubGxtLmdhdGV3YXkuYXV0" + - "aC5ncnBjSW5zZWN1cmU6IGZhbHNlYCBvbmx5IHdpdGggYSBUTFMtY2FwYWJsZSBOVkNGIEFQSS4K" + - "Cldvcmtlci10by1yb3V0ZXIgdHJhbnNwb3J0IGlzIHNlcGFyYXRlLiBGb3Igc2hhcmVkIG9yIHBy" + - "b2R1Y3Rpb24gY2x1c3RlcnMsIHVzZQp0aGUgcmVxdWlyZWQgcmVwbGljYSBjb3VudCBhbmQgdGhl" + - "IG1hbmFnZWQgVExTIGNvbmZpZ3VyYXRpb24uIFRoZQpgd29ya2xvYWQuc3RhcmdhdGVRVUlDSW5z" + - "ZWN1cmVgIHNldHRpbmcgZW5hYmxlcyBwbGFpbnRleHQgd29ya2VyIHRyYW5zcG9ydCBhbmQKbXVz" + - "dCBub3QgYmUgdXNlZCBpbiBwcm9kdWN0aW9uLgoKVGhlIHJlcXVlc3Qgcm91dGVyIHVzZXMgYHBv" + - "d2VyLW9mLXR3b2Agd2hlbiBubyBsb2FkLWJhbGFuY2VyIGNvbmZpZ3VyYXRpb24gaXMKc2V0LiBD" + - "b25maWd1cmUgb3RoZXIgcm91dGluZyBtZXRob2RzIHdpdGggdGhlCltMTE0gUmVxdWVzdCBSb3V0" + - "ZXIgTG9hZCBCYWxhbmNpbmddKGh0dHBzOi8vZ2l0aHViLmNvbS9OVklESUEvbnZjZi9ibG9iL21h" + - "aW4vZG9jcy91c2VyL2xsbS1yZXF1ZXN0LXJvdXRlci1sb2FkLWJhbGFuY2luZy5tZCkKZ3VpZGUu" + - "CgpJZiB0aGUgc2lkZWNhciBpbWFnZSBpcyBtaXJyb3JlZCBvdXRzaWRlIHRoZSBzdGFjaydzIGRl" + - "ZmF1bHQgaW1hZ2UgcmVnaXN0cnkgYW5kCnJlcG9zaXRvcnksIHNldCB0aGUgZ2VuZXJhdGVkIHdv" + - "cmtlciBzaWRlY2FyIGltYWdlIGV4cGxpY2l0bHk6CgpgYGB5YW1sCmFwaToKICByZW1vdGVDb25m" + - "aWc6CiAgICBjb25maWdEYXRhOgogICAgICBudmNmOgogICAgICAgIHNpZGVjYXJzOgogICAgICAg" + - "ICAgbGxtLXJvdXRlci1jbGllbnQtaW1hZ2U6IDxyZWdpc3RyeT4vPHJlcG9zaXRvcnk+L3B5bG9u" + - "OjAuMTQuMQpgYGAKClRoZSBsZWdhY3kgYGFwaS5lbnYuTlZDRl9TSURFQ0FSU19MTE1fUk9VVEVS" + - "X0NMSUVOVF9JTUFHRWAgcGF0aCBpcyBkZXByZWNhdGVkLgpUaGUgc3RhY2sgdHJhbnNsYXRlcyBp" + - "dCBmb3Igb25lIGNvbXBhdGliaWxpdHkgd2luZG93LCBidXQgbmV3IGNvbmZpZ3VyYXRpb25zCm11" + - "c3QgdXNlIHRoZSByZW1vdGUtY29uZmlnIHBhdGguIENvbmZsaWN0aW5nIHZhbHVlcyBmYWlsIHJl" + - "bmRlcmluZy4KClJlbmRlciBhbmQgYXBwbHkgdGhlIHVwZGF0ZWQgY29udHJvbC1wbGFuZSBlbnZp" + - "cm9ubWVudCwgdGhlbiByZWZyZXNoIGV2ZXJ5CnJlZ2lzdGVyZWQgY29tcHV0ZSBwbGFuZSB1c2lu" + - "ZyB0aGUgc2FtZSBoYW5kb2ZmIHVzZWQgZm9yIGl0cyBpbnN0YWxsYXRpb24gc28KTlZDQSByZWNl" + - "aXZlcyBgYWdlbnRDb25maWcubWVyZ2VDb25maWdgOgoKYGBgYmFzaApIRUxNRklMRV9FTlY9PGVu" + - "dj4gaGVsbWZpbGUgdGVtcGxhdGUKSEVMTUZJTEVfRU5WPTxlbnY+IGhlbG1maWxlIHN5bmMKYGBg" + - "CgpVc2UgdGhlIGNvbXBsZXRlIGNvbXB1dGUtcGxhbmUgaW5zdGFsbCBjb21tYW5kIGZyb20KW1Nw" + - "bGl0IENvbXB1dGUtUGxhbmUgSW5zdGFsbGF0aW9uXShjb21wdXRlLXBsYW5lLWluc3RhbGxhdGlv" + - "bi5tZCkuIEEKQ0xJLXByb2ZpbGUgaW5zdGFsbGF0aW9uIG11c3QgcmVtYWluIHByb2ZpbGUtZHJp" + - "dmVuOyBhIEhlbG1maWxlIGluc3RhbGxhdGlvbgptdXN0IHJlbWFpbiB2YWx1ZXMtZHJpdmVuLgoK" + - "RXhpc3RpbmcgTExNIGZ1bmN0aW9uIHZlcnNpb25zIHJldGFpbiB0aGUgd29ya2VyLXNpZGVjYXIg" + - "aW1hZ2UgbWV0YWRhdGEKY2FwdHVyZWQgd2hlbiB0aGUgdmVyc2lvbiBpcyBjcmVhdGVkLiBSZXBs" + - "YWNpbmcgcG9kcyBvciByZWRlcGxveWluZyB0aGUgc2FtZQp2ZXJzaW9uIGRvZXMgbm90IGFwcGx5" + - "IGEgbmV3IFB5bG9uIGltYWdlLiBBZnRlciB0aGUgY29udHJvbC1wbGFuZSB1cGRhdGUsCmNyZWF0" + - "ZSBhbmQgZGVwbG95IGEgbmV3IGZ1bmN0aW9uIHZlcnNpb24uIFZlcmlmeSB0aGUgY29udHJvbCBw" + - "bGFuZSwgcm91dGUsIGFuZAp3b3JrZXIgc2lkZWNhcjoKCmBgYGJhc2gKa3ViZWN0bCBnZXQgZGVw" + - "bG95IC1uIG52Y2YgbGxtLWFwaS1nYXRld2F5Cmt1YmVjdGwgZ2V0IHN0YXRlZnVsc2V0IC1uIG52" + - "Y2YgbGxtLXJlcXVlc3Qtcm91dGVyCmt1YmVjdGwgZ2V0IHBvZHMgLW4gbnZjZiB8IGdyZXAgLUUg" + - "J2xsbS1hcGktZ2F0ZXdheXxsbG0tcmVxdWVzdC1yb3V0ZXInCmt1YmVjdGwgZ2V0IGh0dHByb3V0" + - "ZSAtQSB8IGdyZXAgbGxtCmt1YmVjdGwgLW4gbnZjZi1iYWNrZW5kIGdldCBwb2QgPGZ1bmN0aW9u" + - "LXBvZD4gXAogIC1vIGpzb25wYXRoPSd7cmFuZ2UgLnNwZWMuY29udGFpbmVyc1s/KEAubmFtZT09" + - "ImxsbS13b3JrZXIiKV0uYXJnc1sqXX17Ln17IlxcbiJ9e2VuZH0nCmBgYAoKRm9yIGxvY2FsIHBs" + - "YWludGV4dCBjbHVzdGVycywgdGhlIGBsbG0td29ya2VyYCBhcmdzIG11c3QgaW5jbHVkZQpgLS1x" + - "dWljLWluc2VjdXJlYC4KCkZvciBsb2FkLWJhbGFuY2VyIHNjaGVtYSBhbmQgYWxnb3JpdGhtIGJl" + - "aGF2aW9yLCBzZWUgdGhlCltTdGFyZ2F0ZSBjb25maWd1cmF0aW9uIGd1aWRlXShodHRwczovL2dp" + - "dGh1Yi5jb20vTlZJRElBL252Y2YvYmxvYi9tYWluL3NyYy9saWJyYXJpZXMvcnVzdC9zdGFyZ2F0" + - "ZS9kb2NzL2xvYWQtYmFsYW5jZXItY29uZmlndXJhdGlvbi5tZCkuCgojIyBIZWxtZmlsZSBTZWxl" + - "Y3RvcnMKClRhcmdldCBzcGVjaWZpYyByZWxlYXNlcyBvciBncm91cHM6CgpgYGBiYXNoCiMgQnkg" + - "cmVsZWFzZSBncm91cApIRUxNRklMRV9FTlY9PGVudj4gaGVsbWZpbGUgLS1zZWxlY3RvciByZWxl" + - "YXNlLWdyb3VwPWRlcGVuZGVuY2llcyBzeW5jCkhFTE1GSUxFX0VOVj08ZW52PiBoZWxtZmlsZSAt" + - "LXNlbGVjdG9yIHJlbGVhc2UtZ3JvdXA9c2VydmljZXMgc3luYwpIRUxNRklMRV9FTlY9PGVudj4g" + - "aGVsbWZpbGUgLS1zZWxlY3RvciByZWxlYXNlLWdyb3VwPWluZ3Jlc3Mgc3luYwpIRUxNRklMRV9F" + - "TlY9PGVudj4gaGVsbWZpbGUgLS1zZWxlY3RvciByZWxlYXNlLWdyb3VwPW9ic2VydmFiaWxpdHkg" + - "c3luYwoKIyBCeSByZWxlYXNlIG5hbWUKSEVMTUZJTEVfRU5WPTxlbnY+IGhlbG1maWxlIC0tc2Vs" + - "ZWN0b3IgbmFtZT1jYXNzYW5kcmEgc3luYwpIRUxNRklMRV9FTlY9PGVudj4gaGVsbWZpbGUgLS1z" + - "ZWxlY3RvciBuYW1lPWFkbWluLWlzc3Vlci1wcm94eSBzeW5jCkhFTE1GSUxFX0VOVj08ZW52PiBo" + - "ZWxtZmlsZSAtLXNlbGVjdG9yIG5hbWU9bGxtLXJlcXVlc3Qtcm91dGVyIHN5bmMKSEVMTUZJTEVf" + - "RU5WPTxlbnY+IGhlbG1maWxlIC0tc2VsZWN0b3IgbmFtZT1sbG0tYXBpLWdhdGV3YXkgc3luYwoK" + - "IyBUZW1wbGF0ZSBvbmx5IChkcnkgcnVuKQpIRUxNRklMRV9FTlY9PGVudj4gaGVsbWZpbGUgLS1z" + - "ZWxlY3RvciBuYW1lPWNhc3NhbmRyYSB0ZW1wbGF0ZQoKIyBEZXN0cm95IGEgc2luZ2xlIHJlbGVh" + - "c2UKSEVMTUZJTEVfRU5WPTxlbnY+IGhlbG1maWxlIC0tc2VsZWN0b3IgbmFtZT1jYXNzYW5kcmEg" + - "ZGVzdHJveQpgYGAKCk5vdGU6IGBhZG1pbi1pc3N1ZXItcHJveHlgIGhhcyBubyBgcmVsZWFzZS1n" + - "cm91cGAgbGFiZWwuIFVzZSBgLS1zZWxlY3RvciBuYW1lPWFkbWluLWlzc3Vlci1wcm94eWAuClBL" + - "BwjqsuDt+ikAAPopAABQSwMEFAAIAAAAAAAhAAAAAAAAAAAAAAAAAD8ACQBkYXRhL252Y2Ytc2Vs" + - "Zi1tYW5hZ2VkLWluc3RhbGxhdGlvbi9yZWZlcmVuY2VzL252Y2YtdWktYWRkb24ubWRVVAUAAQCm" + - "zhIjIE5WQ0YgVUkgQWRkb24KCk5WQ0YgVUkgaXMgYW4gb3B0aW9uYWwsIGN1c3RvbWVyLWZhY2lu" + - "ZyBOVkNGIGFkbWluLXBhbmVsIFVJLiBJdCBpcyBkaXNhYmxlZCBieQpkZWZhdWx0IGFuZCBhdmFp" + - "bGFibGUgb25seSBpbiBzdGFjayBwYWNrYWdlcyB0aGF0IGluY2x1ZGUgdGhlIE5WQ0YgVUkgYWRk" + - "b24uCgojIyBXaGVuIHRvIGVuYWJsZQoKRW5hYmxlIE5WQ0YgVUkgb25seSB3aGVuIHlvdSBuZWVk" + - "IHRoZSBhZG1pbi1wYW5lbCBVSSBhbmQgeW91ciBleHRyYWN0ZWQgc3RhY2sKcGFja2FnZSBpbmNs" + - "dWRlcyB0aGUgYWRkb24uIElmIHRoZSBwYWNrYWdlIGRvZXMgbm90IGNvbnRhaW4gYSBgbnZjZi11" + - "aWAgcmVsZWFzZQphbmQgYG52Y2ZVaWAgcm91dGUgdmFsdWVzLCBza2lwIHRoZSBhZGRvbiB1bnRp" + - "bCB5b3UgdXNlIGEgcGFja2FnZSB0aGF0IGluY2x1ZGVzCnRoZW0uCgojIyBFbmFibGUgdGhlIGFk" + - "ZG9uCgpFbmFibGUgaXQgaW4gYGVudmlyb25tZW50cy88ZW52LW5hbWU+LnlhbWxgOgoKYGBgeWFt" + - "bAphZGRvbnM6CiAgbnZjZlVpOgogICAgZW5hYmxlZDogdHJ1ZQpgYGAKCiMjIENyZWF0ZSBuYW1l" + - "c3BhY2UgYW5kIGltYWdlIHB1bGwgc2VjcmV0CgpDcmVhdGUgdGhlIG5hbWVzcGFjZSwgYW5kIGFu" + - "IGltYWdlIHB1bGwgc2VjcmV0IG9ubHkgaWYgcHVsbGluZyBmcm9tIGEgcHJpdmF0ZQpyZWdpc3Ry" + - "eToKCmBgYGJhc2gKa3ViZWN0bCBjcmVhdGUgbmFtZXNwYWNlIG52Y2YtdWkgLS1kcnktcnVuPWNs" + - "aWVudCAtbyB5YW1sIHwga3ViZWN0bCBhcHBseSAtZiAtCgojIE9ubHkgaWYgcHVsbGluZyBmcm9t" + - "IGEgcHJpdmF0ZSByZWdpc3RyeSAoZS5nLiwgTkdDIG52Y3IuaW8pCmV4cG9ydCBOR0NfQVBJX0tF" + - "WT0iPHlvdXIta2V5PiIKa3ViZWN0bCBjcmVhdGUgc2VjcmV0IGRvY2tlci1yZWdpc3RyeSBudmNy" + - "LWNyZWRzIFwKICAtLWRvY2tlci1zZXJ2ZXI9bnZjci5pbyBcCiAgLS1kb2NrZXItdXNlcm5hbWU9" + - "JyRvYXV0aHRva2VuJyBcCiAgLS1kb2NrZXItcGFzc3dvcmQ9IiROR0NfQVBJX0tFWSIgXAogIC0t" + - "bmFtZXNwYWNlPW52Y2YtdWkgXAogIC0tZHJ5LXJ1bj1jbGllbnQgLW8geWFtbCB8IGt1YmVjdGwg" + - "YXBwbHkgLWYgLQpgYGAKClRoZSBkZWZhdWx0IHJvdXRlIGhvc3QgaXMgYG52Y2YtdWkuPGRvbWFp" + - "bj5gIGFuZCB0aGUgYmFja2VuZCBpcwpgbnZjZi11aS5udmNmLXVpOjgzMDBgLgoKIyMgQXBwbHkg" + - "dGhlIHNlcnZpY2UgYW5kIHJvdXRlCgpBZnRlciBjb25maXJtaW5nIHRoZSBwYWNrYWdlIGluY2x1" + - "ZGVzIHRoZSBgbnZjZi11aWAgcmVsZWFzZSwgcHJldmlldyBhbmQgYXBwbHkKdGhlIHNlcnZpY2Ug" + - "YW5kIHJvdXRlOgoKYGBgYmFzaApIRUxNRklMRV9FTlY9PGVudi1uYW1lPiBoZWxtZmlsZSAtLXNl" + - "bGVjdG9yIG5hbWU9bnZjZi11aSB0ZW1wbGF0ZQpIRUxNRklMRV9FTlY9PGVudi1uYW1lPiBoZWxt" + - "ZmlsZSAtLXNlbGVjdG9yIG5hbWU9bnZjZi11aSBzeW5jCkhFTE1GSUxFX0VOVj08ZW52LW5hbWU+" + - "IGhlbG1maWxlIC0tc2VsZWN0b3IgcmVsZWFzZS1ncm91cD1pbmdyZXNzIHN5bmMKYGBgCgojIyBW" + - "ZXJpZnkKClZlcmlmeSB0aGUgcm91dGUgb25seSB3aGVuIHRoZSBhZGRvbiBpcyBwcmVzZW50IGFu" + - "ZCBlbmFibGVkOgoKYGBgYmFzaAprdWJlY3RsIGdldCBkZXBsb3ksc3ZjIC1uIG52Y2YtdWkKa3Vi" + - "ZWN0bCBnZXQgaHR0cHJvdXRlIC1BIHwgZ3JlcCAtaSBudmNmLXVpCmN1cmwgLWkgLUggIkhvc3Q6" + - "IG52Y2YtdWkuPGRvbWFpbj4iICJodHRwOi8vPGdhdGV3YXktYWRkcmVzcz4vc3RhdHVzIgpgYGAK" + - "CiMjIFNlY3VyaXR5CgpUaGUgTlZDRiBVSSBhZG1pbiBwYW5lbCBpcyBjdXJyZW50bHkgdW5hdXRo" + - "ZW50aWNhdGVkLiBEbyBub3QgZXhwb3NlIGl0IHRvIHRoZQpwdWJsaWMgaW50ZXJuZXQuIFJlc3Ry" + - "aWN0IGFjY2VzcyB0byBhIHRydXN0ZWQgbmV0d29yaywgVlBOLCBvciBhbiBhdXRoZW50aWNhdGlu" + - "Zwpwcm94eSBpbiBmcm9udCBvZiB0aGUgYG52Y2YtdWlgIHJvdXRlLgpQSwcIOPknr7kHAAC5BwAA" + - "UEsDBBQACAAAAAAAIQAAAAAAAAAAAAAAAAA+AAkAZGF0YS9udmNmLXNlbGYtbWFuYWdlZC1pbnN0" + - "YWxsYXRpb24vcmVmZXJlbmNlcy9wdWxsLXNlY3JldHMubWRVVAUAAQCmzhIjIEltYWdlIFB1bGwg" + - "U2VjcmV0cyBSZWZlcmVuY2UKCiMjIE92ZXJ2aWV3CgpXaGVuIHB1bGxpbmcgTlZDRiBpbWFnZXMg" + - "ZnJvbSBhIHByaXZhdGUgcmVnaXN0cnkgKGZvciBleGFtcGxlLCBOR0MKYG52Y3IuaW9gKSwgS3Vi" + - "ZXJuZXRlcyBuZWVkcyBgaW1hZ2VQdWxsU2VjcmV0c2Agb24gZXZlcnkgcG9kLiBDb250cm9sLXBs" + - "YW5lIGFuZApjb21wdXRlLXBsYW5lIGNyZWRlbnRpYWxzIGFyZSBjb25maWd1cmVkIGluIHRoZWly" + - "IG93biBjbHVzdGVyIGNvbnRleHRzLiBEbyBub3QKdXNlIG9uZSBtaXhlZCBuYW1lc3BhY2UgbGlz" + - "dC4KClRoZSByZWNvbW1lbmRlZCBjb250cm9sLXBsYW5lIGFwcHJvYWNoIHVzZXMgS3l2ZXJubyB0" + - "byBpbmplY3QgdGhlIHNlY3JldCBpbnRvCmNvbnRyb2wtcGxhbmUgcG9kcyBhdCBhZG1pc3Npb24g" + - "dGltZS4gVGhlIGNvbXB1dGUtcGxhbmUgY2hhcnQgYW5kIG9wZXJhdG9yIHVzZQp0aGUgc2VjcmV0" + - "IGluIGBudmNhLW9wZXJhdG9yYCBhbmQgbWlycm9yIGl0IHRvIGBudmNhLXN5c3RlbWAuCgojIyBL" + - "eXZlcm5vIEFwcHJvYWNoIChSZWNvbW1lbmRlZCkKCiMjIyAxLiBJbnN0YWxsIEt5dmVybm8KCmBg" + - "YGJhc2gKaGVsbSByZXBvIGFkZCBreXZlcm5vIGh0dHBzOi8va3l2ZXJuby5naXRodWIuaW8va3l2" + - "ZXJuby8KaGVsbSByZXBvIHVwZGF0ZQpoZWxtIGluc3RhbGwga3l2ZXJubyBreXZlcm5vL2t5dmVy" + - "bm8gLW4ga3l2ZXJubyAtLWNyZWF0ZS1uYW1lc3BhY2UgXAogIC0ta3ViZS1jb250ZXh0IDxjb250" + - "cm9sLXBsYW5lLWNvbnRleHQ+CmBgYAoKIyMjIDIuIENyZWF0ZSBjb250cm9sLXBsYW5lIHB1bGwg" + - "c2VjcmV0cwoKYGBgYmFzaApleHBvcnQgTkdDX0FQSV9LRVk9Ijx5b3VyLW5nYy1hcGkta2V5PiIK" + - "CmZvciBucyBpbiBjYXNzYW5kcmEtc3lzdGVtIG5hdHMtc3lzdGVtIG52Y2YgYXBpLWtleXMgZXNz" + - "IHNpcyB2YXVsdC1zeXN0ZW0gY2VydC1tYW5hZ2VyOyBkbwogIGt1YmVjdGwgLS1jb250ZXh0IDxj" + - "b250cm9sLXBsYW5lLWNvbnRleHQ+IGNyZWF0ZSBuYW1lc3BhY2UgIiRucyIgXAogICAgLS1kcnkt" + - "cnVuPWNsaWVudCAtbyB5YW1sIHwga3ViZWN0bCAtLWNvbnRleHQgPGNvbnRyb2wtcGxhbmUtY29u" + - "dGV4dD4gYXBwbHkgLWYgLQpkb25lCgpmb3IgbnMgaW4gY2Fzc2FuZHJhLXN5c3RlbSBuYXRzLXN5" + - "c3RlbSBudmNmIGFwaS1rZXlzIGVzcyBzaXMgdmF1bHQtc3lzdGVtIGNlcnQtbWFuYWdlcjsgZG8K" + - "ICBrdWJlY3RsIC0tY29udGV4dCA8Y29udHJvbC1wbGFuZS1jb250ZXh0PiBjcmVhdGUgc2VjcmV0" + - "IGRvY2tlci1yZWdpc3RyeSBudmNyLWNyZWRzIFwKICAgIC0tZG9ja2VyLXNlcnZlcj1udmNyLmlv" + - "IFwKICAgIC0tZG9ja2VyLXVzZXJuYW1lPSckb2F1dGh0b2tlbicgXAogICAgLS1kb2NrZXItcGFz" + - "c3dvcmQ9IiROR0NfQVBJX0tFWSIgXAogICAgLS1uYW1lc3BhY2U9IiRucyIgXAogICAgLS1kcnkt" + - "cnVuPWNsaWVudCAtbyB5YW1sIHwga3ViZWN0bCAtLWNvbnRleHQgPGNvbnRyb2wtcGxhbmUtY29u" + - "dGV4dD4gYXBwbHkgLWYgLQpkb25lCmBgYAoKRm9yIG5vbi1OR0MgcmVnaXN0cmllcywgcmVwbGFj" + - "ZSBgLS1kb2NrZXItc2VydmVyYCwgYC0tZG9ja2VyLXVzZXJuYW1lYCwgYW5kIGAtLWRvY2tlci1w" + - "YXNzd29yZGAuCgojIyMgMy4gQXBwbHkgS3l2ZXJubyBDbHVzdGVyUG9saWN5CgpgYGB5YW1sCmFw" + - "aVZlcnNpb246IGt5dmVybm8uaW8vdjEKa2luZDogQ2x1c3RlclBvbGljeQptZXRhZGF0YToKICBu" + - "YW1lOiBudmNmLWFkZC1pbWFnZXB1bGxzZWNyZXRzCnNwZWM6CiAgYmFja2dyb3VuZDogZmFsc2UK" + - "ICBydWxlczoKICAgIC0gbmFtZTogYWRkLWltYWdlcHVsbHNlY3JldC10by1udmNmLXBvZHMKICAg" + - "ICAgbWF0Y2g6CiAgICAgICAgYW55OgogICAgICAgIC0gcmVzb3VyY2VzOgogICAgICAgICAgICBr" + - "aW5kczoKICAgICAgICAgICAgLSBQb2QKICAgICAgICAgICAgbmFtZXNwYWNlczoKICAgICAgICAg" + - "ICAgLSAibnZjZiIKICAgICAgICAgICAgLSAiYXBpLWtleXMiCiAgICAgICAgICAgIC0gInNpcyIK" + - "ICAgICAgICAgICAgLSAiZXNzIgogICAgICAgICAgICAtICJuYXRzLXN5c3RlbSIKICAgICAgICAg" + - "ICAgLSAiY2Fzc2FuZHJhLXN5c3RlbSIKICAgICAgICAgICAgLSAidmF1bHQtc3lzdGVtIgogICAg" + - "ICAgICAgICAtICJjZXJ0LW1hbmFnZXIiCiAgICAgIG11dGF0ZToKICAgICAgICBwYXRjaFN0cmF0" + - "ZWdpY01lcmdlOgogICAgICAgICAgbWV0YWRhdGE6CiAgICAgICAgICAgIGFubm90YXRpb25zOgog" + - "ICAgICAgICAgICAgIG52Y2YubnZpZGlhLmNvbS9pbWFnZXB1bGxzZWNyZXQtaW5qZWN0ZWQtYnk6" + - "IGt5dmVybm8KICAgICAgICAgIHNwZWM6CiAgICAgICAgICAgIGltYWdlUHVsbFNlY3JldHM6CiAg" + - "ICAgICAgICAgIC0gbmFtZTogbnZjci1jcmVkcwpgYGAKCmBgYGJhc2gKa3ViZWN0bCAtLWNvbnRl" + - "eHQgPGNvbnRyb2wtcGxhbmUtY29udGV4dD4gYXBwbHkgXAogIC1mIGt5dmVybm8taW1hZ2VwdWxs" + - "c2VjcmV0LXBvbGljeS55YW1sCmBgYAoKIyMjIDQuIEluc3RhbGwgdGhlIGNvbnRyb2wgcGxhbmUK" + - "CkNvbnRpbnVlIHdpdGggdGhlIHNlbGVjdGVkIGNvbnRyb2wtcGxhbmUgaW5zdGFsbGF0aW9uIGlu" + - "CltTcGxpdCBDb21wdXRlLVBsYW5lIEluc3RhbGxhdGlvbl0oY29tcHV0ZS1wbGFuZS1pbnN0YWxs" + - "YXRpb24ubWQpLiBObwpjb250cm9sLXBsYW5lIGhlbG1maWxlIG1vZGlmaWNhdGlvbnMgYXJlIG5l" + - "ZWRlZC4gS3l2ZXJubyBpbmplY3RzIHRoZSBwdWxsCnNlY3JldCBpbnRvIG5ldyBjb250cm9sLXBs" + - "YW5lIHBvZHMuCgojIyBDb21wdXRlLVBsYW5lIFNlY3JldAoKQ3JlYXRlIHRoZSBzZWNyZXQgb25s" + - "eSBpbiBgbnZjYS1vcGVyYXRvcmAgb24gdGhlIGNvbXB1dGUgY2x1c3RlcjoKCmBgYGJhc2gKa3Vi" + - "ZWN0bCAtLWNvbnRleHQgPGNvbXB1dGUtcGxhbmUtY29udGV4dD4gY3JlYXRlIG5hbWVzcGFjZSBu" + - "dmNhLW9wZXJhdG9yIFwKICAtLWRyeS1ydW49Y2xpZW50IC1vIHlhbWwgfCBcCiAga3ViZWN0bCAt" + - "LWNvbnRleHQgPGNvbXB1dGUtcGxhbmUtY29udGV4dD4gYXBwbHkgLWYgLQoKa3ViZWN0bCAtLWNv" + - "bnRleHQgPGNvbXB1dGUtcGxhbmUtY29udGV4dD4gY3JlYXRlIHNlY3JldCBkb2NrZXItcmVnaXN0" + - "cnkgbnZjci1jcmVkcyBcCiAgLS1kb2NrZXItc2VydmVyPW52Y3IuaW8gXAogIC0tZG9ja2VyLXVz" + - "ZXJuYW1lPSckb2F1dGh0b2tlbicgXAogIC0tZG9ja2VyLXBhc3N3b3JkPSIkTkdDX0FQSV9LRVki" + - "IFwKICAtLW5hbWVzcGFjZT1udmNhLW9wZXJhdG9yIFwKICAtLWRyeS1ydW49Y2xpZW50IC1vIHlh" + - "bWwgfCBcCiAga3ViZWN0bCAtLWNvbnRleHQgPGNvbXB1dGUtcGxhbmUtY29udGV4dD4gYXBwbHkg" + - "LWYgLQpgYGAKClJlZmVyZW5jZSBpdCBpbiB0aGUgY29tcHV0ZS1wbGFuZSBlbnZpcm9ubWVudCBm" + - "aWxlOgoKYGBgeWFtbApnbG9iYWw6CiAgaW1hZ2VQdWxsU2VjcmV0czoKICAgIC0gbmFtZTogbnZj" + - "ci1jcmVkcwpgYGAKClRoZSBjaGFydCBhbmQgb3BlcmF0b3IgdXNlIHRoYXQgcmVmZXJlbmNlIGlu" + - "IGBudmNhLW9wZXJhdG9yYCBhbmQgbWlycm9yIHRoZQpzZWNyZXQgdG8gYG52Y2Etc3lzdGVtYC4g" + - "RG8gbm90IGNyZWF0ZSBhIGJyb2FkZXIgbWFudWFsIGNvbXB1dGUgbmFtZXNwYWNlIGxpc3QuIEZv" + - "bGxvdwpbU3BsaXQgQ29tcHV0ZS1QbGFuZSBJbnN0YWxsYXRpb25dKGNvbXB1dGUtcGxhbmUtaW5z" + - "dGFsbGF0aW9uLm1kKSBmb3IgdGhlCnNlbGVjdGVkIGluc3RhbGwgZmxvdy4KCiMjIFZlcmlmaWNh" + - "dGlvbgoKYGBgYmFzaAojIENoZWNrIHRoYXQgYSBjb250cm9sLXBsYW5lIHBvZCBoYXMgdGhlIGlu" + - "amVjdGVkIHNlY3JldAprdWJlY3RsIC0tY29udGV4dCA8Y29udHJvbC1wbGFuZS1jb250ZXh0PiBn" + - "ZXQgcG9kIC1uIDxuYW1lc3BhY2U+IDxwb2QtbmFtZT4gXAogIC1vIGpzb25wYXRoPSd7LnNwZWMu" + - "aW1hZ2VQdWxsU2VjcmV0c30nCiMgRXhwZWN0ZWQ6IFt7Im5hbWUiOiJudmNyLWNyZWRzIn1dCgoj" + - "IENoZWNrIHRoZSBLeXZlcm5vIGFubm90YXRpb24Ka3ViZWN0bCAtLWNvbnRleHQgPGNvbnRyb2wt" + - "cGxhbmUtY29udGV4dD4gZ2V0IHBvZCAtbiA8bmFtZXNwYWNlPiA8cG9kLW5hbWU+IFwKICAtbyBq" + - "c29ucGF0aD0ney5tZXRhZGF0YS5hbm5vdGF0aW9ucy5udmNmXC5udmlkaWFcLmNvbS9pbWFnZXB1" + - "bGxzZWNyZXQtaW5qZWN0ZWQtYnl9JwojIEV4cGVjdGVkOiBreXZlcm5vCgojIENoZWNrIHB1bGwg" + - "ZXZlbnRzCmt1YmVjdGwgLS1jb250ZXh0IDxjb250cm9sLXBsYW5lLWNvbnRleHQ+IGdldCBldmVu" + - "dHMgLW4gPG5hbWVzcGFjZT4gXAogIC0tc29ydC1ieT0nLmxhc3RUaW1lc3RhbXAnIHwgZ3JlcCAt" + - "aSBwdWxsCiMgU2hvdWxkIHNob3cgIlN1Y2Nlc3NmdWxseSBwdWxsZWQiIG5vdCAiNDAxIFVuYXV0" + - "aG9yaXplZCIKCiMgQ2hlY2sgdGhlIGNoYXJ0L29wZXJhdG9yIG1pcnJvciBvbiB0aGUgY29tcHV0" + - "ZSBjbHVzdGVyCmt1YmVjdGwgLS1jb250ZXh0IDxjb21wdXRlLXBsYW5lLWNvbnRleHQ+IGdldCBz" + - "ZWNyZXQgbnZjci1jcmVkcyBcCiAgLW4gbnZjYS1zeXN0ZW0KYGBgCgojIyBXaGVuIFB1bGwgU2Vj" + - "cmV0cyBBcmUgTm90IE5lZWRlZAoKLSAqKkFXUyBFQ1Igd2l0aCBJQU0gbm9kZSByb2xlcyoqOiBJ" + - "ZiB5b3VyIG5vZGVzIGhhdmUgYEFtYXpvbkVDMkNvbnRhaW5lclJlZ2lzdHJ5UmVhZE9ubHlgIElB" + - "TSBwb2xpY3ksIEt1YmVybmV0ZXMgY2FuIHB1bGwgZnJvbSBFQ1Igd2l0aG91dCBleHBsaWNpdCBz" + - "ZWNyZXRzLgotICoqUHVibGljIHJlZ2lzdHJpZXMqKjogTm8gcHVsbCBzZWNyZXRzIG5lZWRlZC4K" + - "LSAqKkNTUCBidWlsdC1pbiBjcmVkZW50aWFsIGhlbHBlcnMqKjogR0tFIEFydGlmYWN0IFJlZ2lz" + - "dHJ5LCBBenVyZSBBQ1Igd2l0aCBtYW5hZ2VkIGlkZW50aXR5LCBhbmQgc2ltaWxhciBzZXJ2aWNl" + - "cy4KCiMjIFRyb3VibGVzaG9vdGluZwoKIyMjIFBvZHMgc3RpbGwgaW4gSW1hZ2VQdWxsQmFja09m" + - "ZiBhZnRlciBhcHBseWluZyBwb2xpY3kKClRoZSBLeXZlcm5vIHBvbGljeSBvbmx5IGFmZmVjdHMg" + - "cG9kcyBjcmVhdGVkICoqYWZ0ZXIqKiB0aGUgcG9saWN5IGlzIGFwcGxpZWQuIERlbGV0ZSBzdHVj" + - "ayBwb2RzIHRvIHRyaWdnZXIgcmVjcmVhdGlvbjoKCmBgYGJhc2gKa3ViZWN0bCAtLWNvbnRleHQg" + - "PHRhcmdldC1jb250ZXh0PiBkZWxldGUgcG9kcyAtbiA8bmFtZXNwYWNlPiAtLWFsbApgYGAKCiMj" + - "IyBLeXZlcm5vIGFkbWlzc2lvbiBjb250cm9sbGVyIG5vdCBydW5uaW5nCgpgYGBiYXNoCmt1YmVj" + - "dGwgLS1jb250ZXh0IDxjb250cm9sLXBsYW5lLWNvbnRleHQ+IGdldCBwb2RzIC1uIGt5dmVybm8K" + - "IyBBbGwgcG9kcyBzaG91bGQgYmUgUnVubmluZwpgYGAKCiMjIyBQb2xpY3kgbm90IG1hdGNoaW5n" + - "CgpgYGBiYXNoCmt1YmVjdGwgLS1jb250ZXh0IDxjb250cm9sLXBsYW5lLWNvbnRleHQ+IGdldCBj" + - "bHVzdGVycG9saWN5IG52Y2YtYWRkLWltYWdlcHVsbHNlY3JldHMKIyBTaG91bGQgc2hvdyBSRUFE" + - "WTogVHJ1ZQpgYGAKCiMjIyBXcm9uZyBzZWNyZXQgbmFtZQoKVGhlIHBvbGljeSByZWZlcmVuY2Vz" + - "IGBudmNyLWNyZWRzYC4gVmVyaWZ5IHRoZSBzZWNyZXQgZXhpc3RzIHdpdGggdGhhdCBleGFjdCBu" + - "YW1lOgoKYGBgYmFzaAprdWJlY3RsIC0tY29udGV4dCA8dGFyZ2V0LWNvbnRleHQ+IGdldCBzZWNy" + - "ZXQgbnZjci1jcmVkcyAtbiA8bmFtZXNwYWNlPgpgYGAKUEsHCEtCNagAFgAAABYAAFBLAQIUABQA" + - "CAAAAAAAIQClzwe5SRAAAEkQAAASAAkAAAAAAAAAAAAAAAAAAABkYXRhL21hbmlmZXN0Lmpzb25V" + - "VAUAAQCmzhJQSwECFAAUAAgAAAAAACEAzYEw5E1hAABNYQAAIwAJAAAAAAAAAAAAAACSEAAAZGF0" + - "YS9udmNmLXNlbGYtbWFuYWdlZC1jbGkvU0tJTEwubWRVVAUAAQCmzhJQSwECFAAUAAgAAAAAACEA" + - "IcbIhvAKAADwCgAAMwAJAAAAAAAAAAAAAAA5cgAAZGF0YS9udmNmLXNlbGYtbWFuYWdlZC1jbGkv" + - "ZXhhbXBsZXMvY2ktcGlwZWxpbmVzLm1kVVQFAAEAps4SUEsBAhQAFAAIAAAAAAAhAKEU44FdDAAA" + - "XQwAADQACQAAAAAAAAAAAAAAk30AAGRhdGEvbnZjZi1zZWxmLW1hbmFnZWQtY2xpL2V4YW1wbGVz" + - "L211bHRpLWNsdXN0ZXIubWRVVAUAAQCmzhJQSwECFAAUAAgAAAAAACEAQidJDwUNAAAFDQAANwAJ" + - "AAAAAAAAAAAAAABbigAAZGF0YS9udmNmLXNlbGYtbWFuYWdlZC1jbGkvcHJvbXB0cy9hZGQtY29t" + - "cHV0ZS1wbGFuZS5tZFVUBQABAKbOElBLAQIUABQACAAAAAAAIQCR3Bv5mBkAAJgZAAA5AAkAAAAA" + - "AAAAAAAAAM6XAABkYXRhL252Y2Ytc2VsZi1tYW5hZ2VkLWNsaS9wcm9tcHRzL2NyZWF0ZS1hbmQt" + - "cnVuLXRhc2subWRVVAUAAQCmzhJQSwECFAAUAAgAAAAAACEA0kPcBBglAAAYJQAANwAJAAAAAAAA" + - "AAAAAADWsQAAZGF0YS9udmNmLXNlbGYtbWFuYWdlZC1jbGkvcHJvbXB0cy9kZXBsb3ktYW5kLWlu" + - "dm9rZS5tZFVUBQABAKbOElBLAQIUABQACAAAAAAAIQAOnD+XvQ4AAL0OAAA9AAkAAAAAAAAAAAAA" + - "AFzXAABkYXRhL252Y2Ytc2VsZi1tYW5hZ2VkLWNsaS9wcm9tcHRzL2RpYWdub3NlLWZhaWxlZC1p" + - "bnN0YWxsLm1kVVQFAAEAps4SUEsBAhQAFAAIAAAAAAAhAEJx5h4BKAAAASgAADoACQAAAAAAAAAA" + - "AAAAjeYAAGRhdGEvbnZjZi1zZWxmLW1hbmFnZWQtY2xpL3Byb21wdHMvaW5zdGFsbC1mcm9tLXNj" + - "cmF0Y2gubWRVVAUAAQCmzhJQSwECFAAUAAgAAAAAACEAGsQsnscHAADHBwAAOQAJAAAAAAAAAAAA" + - "AAD/DgEAZGF0YS9udmNmLXNlbGYtbWFuYWdlZC1jbGkvcHJvbXB0cy9yb3RhdGUtY2x1c3Rlci1q" + - "d2tzLm1kVVQFAAEAps4SUEsBAhQAFAAIAAAAAAAhABKTM1ngIQAA4CEAAC4ACQAAAAAAAAAAAAAA" + - "NhcBAGRhdGEvbnZjZi1zZWxmLW1hbmFnZWQtY2xpL3Byb21wdHMvdGVhcmRvd24ubWRVVAUAAQCm" + - "zhJQSwECFAAUAAgAAAAAACEAcg0PZCEjAAAhIwAAMAAJAAAAAAAAAAAAAAB7OQEAZGF0YS9udmNm" + - "LXNlbGYtbWFuYWdlZC1jbGkvcmVmZXJlbmNlL2NvbW1hbmRzLm1kVVQFAAEAps4SUEsBAhQAFAAI" + - "AAAAAAAhAHOnWmTVBgAA1QYAADIACQAAAAAAAAAAAAAAA10BAGRhdGEvbnZjZi1zZWxmLW1hbmFn" + - "ZWQtY2xpL3JlZmVyZW5jZS9leGl0LWNvZGVzLm1kVVQFAAEAps4SUEsBAhQAFAAIAAAAAAAhAORn" + - "scwtJQAALSUAAC0ACQAAAAAAAAAAAAAAQWQBAGRhdGEvbnZjZi1zZWxmLW1hbmFnZWQtY2xpL3Jl" + - "ZmVyZW5jZS9mbGFncy5tZFVUBQABAKbOElBLAQIUABQACAAAAAAAIQDzrhOEdBEAAHQRAAA3AAkA" + - "AAAAAAAAAAAAANKJAQBkYXRhL252Y2Ytc2VsZi1tYW5hZ2VkLWNsaS9yZWZlcmVuY2UvdHJvdWJs" + - "ZXNob290aW5nLm1kVVQFAAEAps4SUEsBAhQAFAAIAAAAAAAhAC/auXVCYgAAQmIAACwACQAAAAAA" + - "AAAAAAAAtJsBAGRhdGEvbnZjZi1zZWxmLW1hbmFnZWQtaW5zdGFsbGF0aW9uL1NLSUxMLm1kVVQF" + - "AAEAps4SUEsBAhQAFAAIAAAAAAAhAEdValZKVgAASlYAADQACQAAAAAAAAAAAAAAWf4BAGRhdGEv" + - "bnZjZi1zZWxmLW1hbmFnZWQtaW5zdGFsbGF0aW9uL2V2YWxzL2V2YWxzLmpzb25VVAUAAQCmzhJQ" + - "SwECFAAUAAgAAAAAACEAAlJOQH8oAAB/KAAALwAJAAAAAAAAAAAAAAAOVQIAZGF0YS9udmNmLXNl" + - "bGYtbWFuYWdlZC1pbnN0YWxsYXRpb24vZXhhbXBsZXMubWRVVAUAAQCmzhJQSwECFAAUAAgAAAAA" + - "ACEAlmO8WRIwAAASMAAATAAJAAAAAAAAAAAAAADzfQIAZGF0YS9udmNmLXNlbGYtbWFuYWdlZC1p" + - "bnN0YWxsYXRpb24vcmVmZXJlbmNlcy9jb21wdXRlLXBsYW5lLWluc3RhbGxhdGlvbi5tZFVUBQAB" + - "AKbOElBLAQIUABQACAAAAAAAIQBWPmeQlTAAAJUwAAA7AAkAAAAAAAAAAAAAAIiuAgBkYXRhL252" + - "Y2Ytc2VsZi1tYW5hZ2VkLWluc3RhbGxhdGlvbi9yZWZlcmVuY2VzL2RlYnVnZ2luZy5tZFVUBQAB" + - "AKbOElBLAQIUABQACAAAAAAAIQDqsuDt+ikAAPopAABEAAkAAAAAAAAAAAAAAI/fAgBkYXRhL252" + - "Y2Ytc2VsZi1tYW5hZ2VkLWluc3RhbGxhdGlvbi9yZWZlcmVuY2VzL2hlbG1maWxlLXN0cnVjdHVy" + - "ZS5tZFVUBQABAKbOElBLAQIUABQACAAAAAAAIQA4+SevuQcAALkHAAA/AAkAAAAAAAAAAAAAAAQK" + - "AwBkYXRhL252Y2Ytc2VsZi1tYW5hZ2VkLWluc3RhbGxhdGlvbi9yZWZlcmVuY2VzL252Y2YtdWkt" + - "YWRkb24ubWRVVAUAAQCmzhJQSwECFAAUAAgAAAAAACEAS0I1qAAWAAAAFgAAPgAJAAAAAAAAAAAA" + - "AAAzEgMAZGF0YS9udmNmLXNlbGYtbWFuYWdlZC1pbnN0YWxsYXRpb24vcmVmZXJlbmNlcy9wdWxs" + - "LXNlY3JldHMubWRVVAUAAQCmzhJQSwUGAAAAABcAFwCvCQAAqCgDAAAA" + "dXRlLXBsYW5lLWNvbnRleHQ+IFwKICBpbnN0YWxsIC0tY29udHJvbC1wbGFuZSBcCiAgLS1jbHVz" + + "dGVyLW5hbWUgPGNvbnRyb2wtcGxhbmUtY2x1c3Rlci1uYW1lPiBcCiAgLS1yZWdpb24gPHJlZ2lv" + + "bj4gXAogIC0tbmNhLWlkIDxuY2EtaWQ+Cgp0ZXN0IC1zIGRlcGxveS9zdGFja3Mvc2VsZi1tYW5h" + + "Z2VkL291dC9jb250cm9sLXBsYW5lLXByb2ZpbGUueWFtbApgYGAKClVzZSB0aGUgZnVsbCBwZXJz" + + "aXN0ZW50IHByZWZpeCBmb3IgYm90aCBjb21wdXRlLXBsYW5lIGNvbW1hbmRzOgoKYGBgYmFzaApu" + + "dmNmLWNsaSAtLWNvbmZpZyA8Y29uZmlnPiBzZWxmLWhvc3RlZCBcCiAgLS1jb250cm9sLXBsYW5l" + + "LXN0YWNrIGRlcGxveS9zdGFja3Mvc2VsZi1tYW5hZ2VkIFwKICAtLWNvbXB1dGUtcGxhbmUtc3Rh" + + "Y2sgZGVwbG95L3N0YWNrcy9udmNmLWNvbXB1dGUtcGxhbmUgXAogIC0tZW52IDxlbnY+IFwKICAt" + + "LXBsYWluIFwKICBjb21wdXRlLXBsYW5lIHJlZ2lzdGVyIFwKICAtLWNvbnRyb2wtcGxhbmUtcHJv" + + "ZmlsZSBkZXBsb3kvc3RhY2tzL3NlbGYtbWFuYWdlZC9vdXQvY29udHJvbC1wbGFuZS1wcm9maWxl" + + "LnlhbWwgXAogIC0tY2x1c3Rlci1uYW1lIDxjb21wdXRlLXBsYW5lLWNsdXN0ZXItbmFtZT4gXAog" + + "IC0ta3ViZS1jb250ZXh0IDxjb21wdXRlLXBsYW5lLWNvbnRleHQ+IFwKICAtLXJlZ2lvbiA8cmVn" + + "aW9uPiBcCiAgLS1vdXRwdXQgZGVwbG95L3N0YWNrcy9udmNmLWNvbXB1dGUtcGxhbmUvb3V0Lzxj" + + "b21wdXRlLXBsYW5lLWNsdXN0ZXItbmFtZT4tcmVnaXN0ZXItdmFsdWVzLnlhbWwKCm52Y2YtY2xp" + + "IC0tY29uZmlnIDxjb25maWc+IHNlbGYtaG9zdGVkIFwKICAtLWNvbnRyb2wtcGxhbmUtc3RhY2sg" + + "ZGVwbG95L3N0YWNrcy9zZWxmLW1hbmFnZWQgXAogIC0tY29tcHV0ZS1wbGFuZS1zdGFjayBkZXBs" + + "b3kvc3RhY2tzL252Y2YtY29tcHV0ZS1wbGFuZSBcCiAgLS1lbnYgPGVudj4gXAogIC0tcGxhaW4g" + + "XAogIGNvbXB1dGUtcGxhbmUgaW5zdGFsbCBcCiAgLS12YWx1ZXMgZGVwbG95L3N0YWNrcy9udmNm" + + "LWNvbXB1dGUtcGxhbmUvb3V0Lzxjb21wdXRlLXBsYW5lLWNsdXN0ZXItbmFtZT4tcmVnaXN0ZXIt" + + "dmFsdWVzLnlhbWwgXAogIC0ta3ViZS1jb250ZXh0IDxjb21wdXRlLXBsYW5lLWNvbnRleHQ+IFwK" + + "ICAtLWNsdXN0ZXItbmFtZSA8Y29tcHV0ZS1wbGFuZS1jbHVzdGVyLW5hbWU+CmBgYAoKUmVnaXN0" + + "cmF0aW9uIHNlbGVjdHMgdGhlIHByb2ZpbGUncyBpbi1jbHVzdGVyIG9yIGNvbXB1dGUtcmVhY2hh" + + "YmxlIGVuZHBvaW50cwpmcm9tIHRoZSBjb21wdXRlIGNvbnRleHQgYW5kIHByb2JlcyBpdHMgSldL" + + "Uy4gRG8gbm90IHJlcGxhY2UgcHJvZmlsZSBlbmRwb2ludHMKd2l0aCBoYW5kLWF1dGhvcmVkIEhl" + + "bG1maWxlIGVuZHBvaW50IHZhbHVlcy4KCiMjIFB1bGwgU2VjcmV0cwoKQ3JlYXRlIHRoZSBjb250" + + "cm9sLXBsYW5lIHNlY3JldCBpbiB0aGVzZSBuYW1lc3BhY2VzIG9uIHRoZSBjb250cm9sLXBsYW5l" + + "CmNvbnRleHQ6CgpgYGB0ZXh0CmNhc3NhbmRyYS1zeXN0ZW0gbmF0cy1zeXN0ZW0gbnZjZiBhcGkt" + + "a2V5cyBlc3Mgc2lzIHZhdWx0LXN5c3RlbSBjZXJ0LW1hbmFnZXIKYGBgCgpPbiB0aGUgY29tcHV0" + + "ZSBjb250ZXh0LCBjcmVhdGUgdGhlIHNlY3JldCBvbmx5IGluIGBudmNhLW9wZXJhdG9yYCBhbmQg" + + "cmVmZXJlbmNlCml0IHRocm91Z2ggYGdsb2JhbC5pbWFnZVB1bGxTZWNyZXRzYCBpbiB0aGUgY29t" + + "cHV0ZS1wbGFuZSBlbnZpcm9ubWVudC4gVGhlIGNoYXJ0CmFuZCBvcGVyYXRvciB1c2UgaXQgaW4g" + + "YG52Y2Etb3BlcmF0b3JgIGFuZCBtaXJyb3IgaXQgdG8gYG52Y2Etc3lzdGVtYC4gRG8gbm90IGFk" + + "ZAplaXRoZXIgbmFtZXNwYWNlIHRvIHRoZSBjb250cm9sLXBsYW5lIGxpc3QuIEZvciBjb21tYW5k" + + "cyBhbmQgcG9saWN5IGV4YW1wbGVzLApzZWUgW3B1bGwtc2VjcmV0cy5tZF0ocHVsbC1zZWNyZXRz" + + "Lm1kKS4KCiMjIFZlcmlmaWNhdGlvbgoKVXNlIHRoZSBleHBsaWNpdCBHYXRld2F5IG5hbWUgYW5k" + + "IG5hbWVzcGFjZToKCmBgYGJhc2gKa3ViZWN0bCAtLWNvbnRleHQgPGNvbnRyb2wtcGxhbmUtY29u" + + "dGV4dD4gZ2V0IGdhdGV3YXkgbnZjZi1nYXRld2F5IFwKICAtbiBlbnZveS1nYXRld2F5IC1vIHdp" + + "ZGUKa3ViZWN0bCAtLWNvbnRleHQgPGNvbnRyb2wtcGxhbmUtY29udGV4dD4gZ2V0IHRjcHJvdXRl" + + "IC1uIGVudm95LWdhdGV3YXkKa3ViZWN0bCAtLWNvbnRleHQgPGNvbXB1dGUtcGxhbmUtY29udGV4" + + "dD4gcm9sbG91dCBzdGF0dXMgXAogIGRlcGxveW1lbnQvbnZjYS1vcGVyYXRvciAtbiBudmNhLW9w" + + "ZXJhdG9yIC0tdGltZW91dD0xMG0Ka3ViZWN0bCAtLWNvbnRleHQgPGNvbXB1dGUtcGxhbmUtY29u" + + "dGV4dD4gd2FpdCBcCiAgbnZjZmJhY2tlbmQvPGNvbXB1dGUtcGxhbmUtY2x1c3Rlci1uYW1lPiAt" + + "biBudmNhLW9wZXJhdG9yIFwKICAtLWZvcj1qc29ucGF0aD0ney5zdGF0dXMuYWdlbnRTdGF0dXN9" + + "Jz1oZWFsdGh5IC0tdGltZW91dD0xMG0Ka3ViZWN0bCAtLWNvbnRleHQgPGNvbXB1dGUtcGxhbmUt" + + "Y29udGV4dD4gZ2V0IHNlY3JldCA8cHVsbC1zZWNyZXQ+IC1uIG52Y2Etc3lzdGVtCmBgYAoKIyMg" + + "VGVhcmRvd24KCkRlc3Ryb3kgYW5kIGRlcmVnaXN0ZXIgZXZlcnkgY29tcHV0ZSBwbGFuZSBiZWZv" + + "cmUgdGhlIGNvbnRyb2wgcGxhbmUuIEJvdGgKaGFuZG9mZnMgY2FuIGRlc3Ryb3kgdGhlIGNvbnRy" + + "b2wgcGxhbmUsIHNvIGFwcGx5IHRoaXMgcHJlZmxpZ2h0IGJlZm9yZSBlaXRoZXIKb25lLiBQcmVz" + + "ZXJ2ZSB0aGUgYGNlcnQtbWFuYWdlcmAgbmFtZXNwYWNlIGJ5IGRlZmF1bHQuIE5hbWVzcGFjZSBw" + + "cmVzZXJ2YXRpb24KZG9lcyBub3QgcHJlc2VydmUgdGhlIEhlbG0gcmVsZWFzZS4gSWRlbnRpZnkg" + + "d2hldGhlciBjZXJ0LW1hbmFnZXIgaXMgZXh0ZXJuYWwuCkZvciBhbiBleHRlcm5hbCBpbnN0YWxs" + + "YXRpb24sIHJlcXVpcmUgZWZmZWN0aXZlIGBjZXJ0TWFuYWdlci5lbmFibGVkOiBmYWxzZWAKYW5k" + + "IGFib3J0IGlmIGl0IGlzIHRydWUuIEZvciBhbiBpbnRlbmRlZCBzdGFjay1tYW5hZ2VkIHJlbGVh" + + "c2Ugd2l0aCBlZmZlY3RpdmUKYGNlcnRNYW5hZ2VyLmVuYWJsZWQ6IHRydWVgLCB2ZXJpZnkgbWF0" + + "Y2hpbmcgc3RhY2sgSGVsbSByZWxlYXNlIG1ldGFkYXRhIGFuZAphYm9ydCBvbiBtaXNzaW5nIG9y" + + "IG1pc21hdGNoZWQgZXZpZGVuY2UuIE9ubHkgcGxhbiBuYW1lc3BhY2UgcmVtb3ZhbCBhZnRlcgp2" + + "ZXJpZnlpbmcgbmFtZXNwYWNlIHByb3ZlbmFuY2UgYW5kIGdldHRpbmcgZXhwbGljaXQgY29uZmly" + + "bWF0aW9uOyBvdGhlcndpc2UKZG8gbm90IGRlbGV0ZSBpdC4KCkZvciB0aGUgSGVsbWZpbGUgdmFs" + + "dWVzIGZsb3csIHJldGFpbiBlYWNoIHJlZ2lzdHJhdGlvbiB2YWx1ZXMgZmlsZSB1bnRpbApjbGVh" + + "bnVwIGlzIHZlcmlmaWVkOgoKYGBgYmFzaAptYWtlIC1DIGRlcGxveS9zdGFja3MvbnZjZi1jb21w" + + "dXRlLXBsYW5lIGRlc3Ryb3kgXAogIENMVVNURVJfTkFNRT08Y2x1c3Rlci1uYW1lPiBcCiAgSEVM" + + "TUZJTEVfRU5WPTxlbnY+IFwKICBLVUJFQ09ORklHX0ZJTEU9PGNvbXB1dGUtcGxhbmUta3ViZWNv" + + "bmZpZz4KCjxudmNmLWNsaT4gLS1jb25maWcgPGNvbmZpZz4gY2x1c3RlciBkZWxldGUgXAogIC0t" + + "Y2x1c3Rlci1pZCA8Y2x1c3Rlci1pZC1mcm9tLXJlZ2lzdHJhdGlvbi12YWx1ZXM+IFwKICAtLW5j" + + "YS1pZCA8bmNhLWlkPiBcCiAgLS1pZ25vcmUtbWlzc2luZwpgYGAKClJlYWQgYGNsdXN0ZXJJRGAg" + + "YW5kIGBuY2FJRGAgZnJvbQpgZGVwbG95L3N0YWNrcy9udmNmLWNvbXB1dGUtcGxhbmUvcmVnaXN0" + + "cmF0aW9uLzxjbHVzdGVyLW5hbWU+LXJlZ2lzdGVyLXZhbHVlcy55YW1sYC4KU2hvdyB0aGUgZXhh" + + "Y3QgY2x1c3RlciBJRCBhbmQgZ2V0IGNvbmZpcm1hdGlvbiBiZWZvcmUgYGNsdXN0ZXIgZGVsZXRl" + + "YC4gUmVwZWF0CmNvbXB1dGUgZGVzdHJveSBhbmQgZGVyZWdpc3RyYXRpb24gZm9yIGV2ZXJ5IGNv" + + "bXB1dGUgY2x1c3RlciwgdGhlbiBkZXN0cm95IHRoZQpjb250cm9sIHBsYW5lLgoKYGBgYmFzaApL" + + "VUJFQ09ORklHPTxjb250cm9sLXBsYW5lLWt1YmVjb25maWc+IFwKbWFrZSAtQyBkZXBsb3kvc3Rh" + + "Y2tzL3NlbGYtbWFuYWdlZCBkZXN0cm95IFwKICBIRUxNRklMRV9FTlY9PGVudj4gXAogIEtVQkVD" + + "T05GSUdfRklMRT08Y29udHJvbC1wbGFuZS1rdWJlY29uZmlnPgpgYGAKCkJvdGggYEtVQkVDT05G" + + "SUdgIGFuZCBgS1VCRUNPTkZJR19GSUxFYCBhcmUgcmVxdWlyZWQgb24gdGhlIGNvbnRyb2wtcGxh" + + "bmUKZGVzdHJveSBiZWNhdXNlIGl0cyBuYW1lc3BhY2UgY2xlYW51cCB1c2VzIGFtYmllbnQgYGt1" + + "YmVjdGxgLgoKRm9yIHRoZSBDTEkgcHJvZmlsZSBmbG93LCBrZWVwCmBkZXBsb3kvc3RhY2tzL252" + + "Y2YtY29tcHV0ZS1wbGFuZS9vdXQvPGNsdXN0ZXItbmFtZT4tcmVnaXN0ZXItdmFsdWVzLnlhbWxg" + + "CnVudGlsIHRlYXJkb3duIGNvbXBsZXRlcyBiZWNhdXNlIGBkb3duYCByZWFkcyBpdHMgY2x1c3Rl" + + "ciBJRC4gRmlyc3QgcXVlcnkgdGhlCmxpdmUgcmVnaXN0cmF0aW9uIHNldCB3aXRoIHRoZSBzYW1l" + + "IGNvbXBsZXRlIHBlcnNpc3RlbnQgcHJlZml4OgoKYGBgYmFzaApudmNmLWNsaSAtLWNvbmZpZyA8" + + "Y29uZmlnPiBzZWxmLWhvc3RlZCBcCiAgLS1jb250cm9sLXBsYW5lLXN0YWNrIGRlcGxveS9zdGFj" + + "a3Mvc2VsZi1tYW5hZ2VkIFwKICAtLWNvbXB1dGUtcGxhbmUtc3RhY2sgZGVwbG95L3N0YWNrcy9u" + + "dmNmLWNvbXB1dGUtcGxhbmUgXAogIC0tZW52IDxlbnY+IFwKICAtLXBsYWluIFwKICAtLWNvbnRy" + + "b2wtcGxhbmUtY29udGV4dCA8Y29udHJvbC1wbGFuZS1jb250ZXh0PiBcCiAgLS1jb21wdXRlLXBs" + + "YW5lLWNvbnRleHQgPGNvbXB1dGUtcGxhbmUtY29udGV4dD4gXAogIC0taWNtcy11cmwgPGNvbXB1" + + "dGUtcmVhY2hhYmxlLWljbXMtdXJsPiBcCiAgc3RhdHVzIFwKICAtLWNsdXN0ZXItbmFtZSA8Y29t" + + "cHV0ZS1wbGFuZS1jbHVzdGVyLW5hbWU+IFwKICAtLW5jYS1pZCA8bmNhLWlkPgpgYGAKClNob3cg" + + "dGhlIHJlZ2lzdGVyZWQgY29tcHV0ZS1wbGFuZSBsaXN0LiBOYW1lZC1jbHVzdGVyIGBkb3duIC0t" + + "cGxhbi1vbmx5YCBkb2VzCm5vdCBxdWVyeSBJQ01TLCBzbyBpdCBwcmV2aWV3cyBvbmx5IHRoYXQg" + + "Y29tcHV0ZSBwbGFuZSBhbmQgY2Fubm90IHNob3cgdGhlCmNvbmRpdGlvbmFsIGNvbnRyb2wtcGxh" + + "bmUgcmVtb3ZhbC4gSWYgdGhlIHRhcmdldCBpcyB0aGUgbGFzdCByZWdpc3RlcmVkCmNsdXN0ZXIs" + + "IHJlYWwgYGRvd25gIGFsc28gZGVzdHJveXMgdGhlIGNvbnRyb2wgcGxhbmUuIEdldCBleHBsaWNp" + + "dCBjb25maXJtYXRpb24KZm9yIGJvdGggcmVtb3ZhbHMgYmVmb3JlIGNvbnRpbnVpbmcuIFRoZW4g" + + "cHJldmlldyBhbmQgZXhlY3V0ZSB3aXRoIHRoZSBzYW1lCnByZWZpeDoKCmBgYGJhc2gKbnZjZi1j" + + "bGkgLS1jb25maWcgPGNvbmZpZz4gc2VsZi1ob3N0ZWQgXAogIC0tY29udHJvbC1wbGFuZS1zdGFj" + + "ayBkZXBsb3kvc3RhY2tzL3NlbGYtbWFuYWdlZCBcCiAgLS1jb21wdXRlLXBsYW5lLXN0YWNrIGRl" + + "cGxveS9zdGFja3MvbnZjZi1jb21wdXRlLXBsYW5lIFwKICAtLWVudiA8ZW52PiBcCiAgLS1wbGFp" + + "biBcCiAgLS1jb250cm9sLXBsYW5lLWNvbnRleHQgPGNvbnRyb2wtcGxhbmUtY29udGV4dD4gXAog" + + "IC0tY29tcHV0ZS1wbGFuZS1jb250ZXh0IDxjb21wdXRlLXBsYW5lLWNvbnRleHQ+IFwKICAtLWlj" + + "bXMtdXJsIDxjb21wdXRlLXJlYWNoYWJsZS1pY21zLXVybD4gXAogIGRvd24gLS1wbGFuLW9ubHkg" + + "XAogIC0tY2x1c3Rlci1uYW1lIDxjb21wdXRlLXBsYW5lLWNsdXN0ZXItbmFtZT4gXAogIC0tbmNh" + + "LWlkIDxuY2EtaWQ+CgpudmNmLWNsaSAtLWNvbmZpZyA8Y29uZmlnPiBzZWxmLWhvc3RlZCBcCiAg" + + "LS1jb250cm9sLXBsYW5lLXN0YWNrIGRlcGxveS9zdGFja3Mvc2VsZi1tYW5hZ2VkIFwKICAtLWNv" + + "bXB1dGUtcGxhbmUtc3RhY2sgZGVwbG95L3N0YWNrcy9udmNmLWNvbXB1dGUtcGxhbmUgXAogIC0t" + + "ZW52IDxlbnY+IFwKICAtLXBsYWluIFwKICAtLWNvbnRyb2wtcGxhbmUtY29udGV4dCA8Y29udHJv" + + "bC1wbGFuZS1jb250ZXh0PiBcCiAgLS1jb21wdXRlLXBsYW5lLWNvbnRleHQgPGNvbXB1dGUtcGxh" + + "bmUtY29udGV4dD4gXAogIC0taWNtcy11cmwgPGNvbXB1dGUtcmVhY2hhYmxlLWljbXMtdXJsPiBc" + + "CiAgZG93biBcCiAgLS1jbHVzdGVyLW5hbWUgPGNvbXB1dGUtcGxhbmUtY2x1c3Rlci1uYW1lPiBc" + + "CiAgLS1uY2EtaWQgPG5jYS1pZD4KYGBgCgpSZXBlYXQgYGRvd25gIGZvciBlYWNoIGNvbXB1dGUg" + + "Y2x1c3Rlci4gRWFjaCBydW4gZGVzdHJveXMgdGhhdCBjb21wdXRlIHBsYW5lIGFuZAp1bnJlZ2lz" + + "dGVycyBpdHMgSUNNUyByb3cuIEl0IHJlbW92ZXMgdGhlIGNvbnRyb2wgcGxhbmUgb25seSBhZnRl" + + "ciBubyBjbHVzdGVyCnJlZ2lzdHJhdGlvbnMgcmVtYWluLgpQSwcIlmO8WRIwAAASMAAAUEsDBBQA" + + "CAAAAAAAIQAAAAAAAAAAAAAAAAA7AAkAZGF0YS9udmNmLXNlbGYtbWFuYWdlZC1pbnN0YWxsYXRp" + + "b24vcmVmZXJlbmNlcy9kZWJ1Z2dpbmcubWRVVAUAAQCmzhIjIERlYnVnZ2luZyBSZWZlcmVuY2UK" + + "ClJlY2lwZXMgZm9yIGRpYWdub3NpbmcgYW5kIGZpeGluZyBjb21tb24gTlZDRiBzZWxmLW1hbmFn" + + "ZWQgc3RhY2sgZmFpbHVyZXMuCgojIyBRdWljayBTdGF0dXMgQ29tbWFuZHMKCmBgYGJhc2gKIyBB" + + "bGwgcG9kcyBhY3Jvc3MgYWxsIG5hbWVzcGFjZXMKa3ViZWN0bCBnZXQgcG9kcyAtQSAtbyB3aWRl" + + "CgojIFBvZHMgaW4gYSBzcGVjaWZpYyBuYW1lc3BhY2UKa3ViZWN0bCBnZXQgcG9kcyAtbiA8bmFt" + + "ZXNwYWNlPgoKIyBBbGwgaGVsbSByZWxlYXNlcwpoZWxtIGxpc3QgLUEKCiMgUmVjZW50IGV2ZW50" + + "cyAobW9zdCB1c2VmdWwgZm9yIGRpYWdub3NpbmcgZmFpbHVyZXMpCmt1YmVjdGwgZ2V0IGV2ZW50" + + "cyAtbiA8bmFtZXNwYWNlPiAtLXNvcnQtYnk9Jy5sYXN0VGltZXN0YW1wJwoKIyBEZXNjcmliZSBh" + + "IHNwZWNpZmljIHBvZCAoc2hvd3MgZXZlbnRzLCBjb25kaXRpb25zLCB2b2x1bWVzKQprdWJlY3Rs" + + "IGRlc2NyaWJlIHBvZCAtbiA8bmFtZXNwYWNlPiA8cG9kLW5hbWU+CmBgYAoKIyMgRmFpbHVyZTog" + + "SW1hZ2VQdWxsQmFja09mZgoKIyMjIFN5bXB0b21zCgpgYGB0ZXh0Ck5BTUUgICAgICAgICAgIFJF" + + "QURZICAgU1RBVFVTICAgICAgICAgICAgIFJFU1RBUlRTICAgQUdFCmNhc3NhbmRyYS0wICAgIDAv" + + "MSAgICAgSW1hZ2VQdWxsQmFja09mZiAgIDAgICAgICAgICAgNW0KYGBgCgpFdmVudHMgc2hvdzoK" + + "CmBgYHRleHQKRmFpbGVkIHRvIHB1bGwgaW1hZ2UgIm52Y3IuaW8vLi4uL2ltYWdlOnRhZyI6IDQw" + + "MSBVbmF1dGhvcml6ZWQKYGBgCgojIyMgRGlhZ25vc2lzCgpgYGBiYXNoCiMgQ2hlY2sgd2hhdCBp" + + "bWFnZSBpcyBmYWlsaW5nCmt1YmVjdGwgZGVzY3JpYmUgcG9kIC1uIDxuYW1lc3BhY2U+IDxwb2Qt" + + "bmFtZT4gfCBncmVwIC1BNSAiRXZlbnRzOiIKCiMgQ2hlY2sgaWYgcHVsbCBzZWNyZXQgZXhpc3Rz" + + "IGluIHRoZSBuYW1lc3BhY2UKa3ViZWN0bCBnZXQgc2VjcmV0IG52Y3ItY3JlZHMgLW4gPG5hbWVz" + + "cGFjZT4KCiMgQ2hlY2sgaWYgdGhlIHBvZCBzcGVjIGhhcyBpbWFnZVB1bGxTZWNyZXRzCmt1YmVj" + + "dGwgZ2V0IHBvZCAtbiA8bmFtZXNwYWNlPiA8cG9kLW5hbWU+IC1vIGpzb25wYXRoPSd7LnNwZWMu" + + "aW1hZ2VQdWxsU2VjcmV0c30nCgojIENoZWNrIGlmIHRoZSBTZXJ2aWNlQWNjb3VudCBoYXMgaW1h" + + "Z2VQdWxsU2VjcmV0cwprdWJlY3RsIGdldCBzYSA8c2EtbmFtZT4gLW4gPG5hbWVzcGFjZT4gLW8g" + + "anNvbnBhdGg9J3suaW1hZ2VQdWxsU2VjcmV0c30nCmBgYAoKIyMjIEZpeGVzCgoxLiAqKlNlY3Jl" + + "dCBtaXNzaW5nIGluIG5hbWVzcGFjZSoqOiBDcmVhdGUgaXQKCiAgIGBgYGJhc2gKICAga3ViZWN0" + + "bCBjcmVhdGUgc2VjcmV0IGRvY2tlci1yZWdpc3RyeSBudmNyLWNyZWRzIFwKICAgICAtLWRvY2tl" + + "ci1zZXJ2ZXI9bnZjci5pbyBcCiAgICAgLS1kb2NrZXItdXNlcm5hbWU9JyRvYXV0aHRva2VuJyBc" + + "CiAgICAgLS1kb2NrZXItcGFzc3dvcmQ9IiROR0NfQVBJX0tFWSIgXAogICAgIC0tbmFtZXNwYWNl" + + "PTxuYW1lc3BhY2U+IFwKICAgICAtLWRyeS1ydW49Y2xpZW50IC1vIHlhbWwgfCBrdWJlY3RsIGFw" + + "cGx5IC1mIC0KICAgYGBgCgoyLiAqKlNlY3JldCBleGlzdHMgYnV0IHBvZCBkb2Vzbid0IHJlZmVy" + + "ZW5jZSBpdCoqOiBUaGUgY2hhcnQgbmVlZHMgYGltYWdlUHVsbFNlY3JldHNgIGNvbmZpZ3VyZWQg" + + "dmlhIGhlbG1maWxlIHZhbHVlcy4gU2VlIFtwdWxsLXNlY3JldHMubWRdKHB1bGwtc2VjcmV0cy5t" + + "ZCkuCgozLiAqKkNoYXJ0IGRvZXNuJ3Qgc3VwcG9ydCBpbWFnZVB1bGxTZWNyZXRzKiogKG9wZW5i" + + "YW8sIGludm9jYXRpb24tc2VydmljZSwgZXNzLWFwaSwgbm90YXJ5LXNlcnZpY2UpOiBQYXRjaCBT" + + "ZXJ2aWNlQWNjb3VudHMgYW5kIHJlc3RhcnQgcG9kcy4gU2VlIFtwdWxsLXNlY3JldHMubWRdKHB1" + + "bGwtc2VjcmV0cy5tZCkuCgo0LiAqKldyb25nIGNyZWRlbnRpYWxzIGluIHNlY3JldCoqOiBEZWxl" + + "dGUgYW5kIHJlY3JlYXRlCgogICBgYGBiYXNoCiAgIGt1YmVjdGwgZGVsZXRlIHNlY3JldCBudmNy" + + "LWNyZWRzIC1uIDxuYW1lc3BhY2U+CiAgICMgUmVjcmVhdGUgd2l0aCBjb3JyZWN0IGNyZWRlbnRp" + + "YWxzCiAgIGBgYAoKIyMgRmFpbHVyZTogSW5pdDowLzEgU3R1Y2sgKFZhdWx0IEFnZW50KQoKIyMj" + + "IFN5bXB0b21zCgpTZXJ2aWNlIHBvZHMgc3R1Y2sgaW4gYEluaXQ6MC8xYCBmb3IgbWludXRlczoK" + + "CmBgYGJhc2gKTkFNRSAgICAgICAgICAgICAgICAgICAgICAgIFJFQURZICAgU1RBVFVTICAgICBS" + + "RVNUQVJUUyAgIEFHRQpudmNmLWFwaS03ZjRjNzZmNzg4LTQ0dmx0ICAwLzIgICAgIEluaXQ6MC8x" + + "ICAgMCAgICAgICAgICAxMG0KYGBgCgojIyMgRGlhZ25vc2lzCgpUaGUgaW5pdCBjb250YWluZXIg" + + "aXMgdGhlIHZhdWx0LWFnZW50LWluaXQgaW5qZWN0b3Igd2FpdGluZyBmb3IgT3BlbkJhby4KCmBg" + + "YGJhc2gKIyBDaGVjayBPcGVuQmFvIHBvZHMKa3ViZWN0bCBnZXQgcG9kcyAtbiB2YXVsdC1zeXN0" + + "ZW0KCiMgQ2hlY2sgaWYgT3BlbkJhbyBtaWdyYXRpb24gam9iIHJhbgprdWJlY3RsIGdldCBqb2Jz" + + "IC1uIHZhdWx0LXN5c3RlbQoKIyBDaGVjayBPcGVuQmFvIHBvZCBsb2dzCmt1YmVjdGwgbG9ncyAt" + + "biB2YXVsdC1zeXN0ZW0gb3BlbmJhby1zZXJ2ZXItMCAtYyBvcGVuYmFvCgojIENoZWNrIGluaXQg" + + "Y29udGFpbmVyIGxvZ3Mgb24gdGhlIHN0dWNrIHBvZAprdWJlY3RsIGxvZ3MgLW4gPG5hbWVzcGFj" + + "ZT4gPHBvZC1uYW1lPiAtYyB2YXVsdC1hZ2VudC1pbml0CmBgYAoKIyMjIEZpeGVzCgoxLiAqKk9w" + + "ZW5CYW8gcG9kcyBub3QgcnVubmluZyoqOiBDaGVjayB0aGVpciBldmVudHMgZm9yIGltYWdlIHB1" + + "bGwgaXNzdWVzLCByZXNvdXJjZSBpc3N1ZXMsIGV0Yy4KCjIuICoqT3BlbkJhbyBtaWdyYXRpb24g" + + "am9iIGRpZG4ndCBydW4qKjogVGhpcyBoYXBwZW5zIHdoZW4gYGhlbG1maWxlIHN5bmNgIHdhcyBp" + + "bnRlcnJ1cHRlZC4gRGVzdHJveSBhbmQgcmUtc3luYyBvcGVuYmFvOgoKICAgYGBgYmFzaAogICBI" + + "RUxNRklMRV9FTlY9PGVudj4gaGVsbWZpbGUgLS1zZWxlY3RvciBuYW1lPW9wZW5iYW8tc2VydmVy" + + "IGRlc3Ryb3kKICAga3ViZWN0bCBkZWxldGUgbmFtZXNwYWNlIHZhdWx0LXN5c3RlbQogICBrdWJl" + + "Y3RsIGNyZWF0ZSBuYW1lc3BhY2UgdmF1bHQtc3lzdGVtCiAgICMgUmUtY3JlYXRlIHB1bGwgc2Vj" + + "cmV0IGlmIG5lZWRlZAogICBIRUxNRklMRV9FTlY9PGVudj4gaGVsbWZpbGUgLS1zZWxlY3RvciBu" + + "YW1lPW9wZW5iYW8tc2VydmVyIHN5bmMKICAgYGBgCgozLiAqKk9wZW5CYW8gcG9kcyBydW5uaW5n" + + "IGJ1dCBub3QgaW5pdGlhbGl6ZWQqKjogQ2hlY2sgdW5zZWFsIHN0YXR1czoKCiAgIGBgYGJhc2gK" + + "ICAga3ViZWN0bCBleGVjIC1uIHZhdWx0LXN5c3RlbSBvcGVuYmFvLXNlcnZlci0wIC1jIG9wZW5i" + + "YW8gLS0gYmFvIHN0YXR1cwogICBgYGAKCjQuICoqYHZhdWx0LWFnZW50LWluaXRgIGxvZ3MgYGF1" + + "dGgvand0L2xvZ2luYCAtPiBgNDAwIC4uLiBubyBrbm93biBrZXkgc3VjY2Vzc2Z1bGx5IHZhbGlk" + + "YXRlZCB0aGUgdG9rZW4gc2lnbmF0dXJlYCoqCiAgIChjb21tb24gb24gQUtTLCBhbmQgb24gYW55" + + "IGNsdXN0ZXIgd2hvc2UgT0lEQyBpc3N1ZXIgcHVibGlzaGVzIGEgcm90YXRpbmcgbXVsdGkta2V5" + + "IEpXS1MpOgogICB0aGUgT3BlbkJhbyBgYXV0aC9qd3RgIG1ldGhvZCB3YXMgY29uZmlndXJlZCB3" + + "aXRoIGEgc2luZ2xlIHN0YXRpYyBtb3VudGVkIHB1YmxpYyBrZXkKICAgKGBqd3RfdmFsaWRhdGlv" + + "bl9wdWJrZXlzYCkgaW5zdGVhZCBvZiB0aGUgaXNzdWVyJ3MgbGl2ZSBKV0tTLCBzbyBpdCBjYW5u" + + "b3QgdmFsaWRhdGUgdGhlCiAgIGNsdXN0ZXItc2lnbmVkIFNlcnZpY2VBY2NvdW50IHRva2VucyB0" + + "aGUgdmF1bHQtYWdlbnRzIHByZXNlbnQuIEV2ZXJ5IHNlcnZpY2UncwogICBgdmF1bHQtYWdlbnQt" + + "aW5pdGAgdGhlbiBsb29wcyBvbiB0aGF0IDQwMCBhbmQgdGhlIHBvZCBuZXZlciBsZWF2ZXMgYElu" + + "aXQ6MC8xYC4gVGhpcyBpcyB0aGUKICAgYmVoYXZpb3Igb2YgdGhlIGJhc2UgZGVmYXVsdCBgb3Bl" + + "bmJhby5taWdyYXRpb25zLmlzc3VlckRpc2NvdmVyeS5lbmFibGVkOiBmYWxzZWA7IHRoZQogICBt" + + "aWdyYXRpb24gbG9nIHNob3dzIGBPSURDIGlzc3VlciBkaXNjb3ZlcnkgaXMgZGlzYWJsZWQuIFVz" + + "aW5nIFNlcnZpY2VBY2NvdW50IGlzc3VlciBhbmQKICAgbW91bnRlZCBwdWJsaWMga2V5IGZhbGxi" + + "YWNrYC4gRW5hYmxlIGlzc3VlciBkaXNjb3Zlcnkgc28gdGhlIG1pZ3JhdGlvbiB1c2VzIHRoZSBs" + + "aXZlIEpXS1MsCiAgIHRoZW4gcmUtcnVuIGl0OgoKICAgYGBgYmFzaAogICAjIEluIHlvdXIgZW52" + + "IHZhbHVlcyAoZS5nLiBlbnZpcm9ubWVudHMvPGVudj4ueWFtbCksIHNldDoKICAgIyAgIG9wZW5i" + + "YW86CiAgICMgICAgIG1pZ3JhdGlvbnM6CiAgICMgICAgICAgaXNzdWVyRGlzY292ZXJ5OgogICAj" + + "ICAgICAgICAgZW5hYmxlZDogdHJ1ZQogICBrdWJlY3RsIGRlbGV0ZSBqb2IgLW4gdmF1bHQtc3lz" + + "dGVtIG9wZW5iYW8tc2VydmVyLW1pZ3JhdGlvbnMKICAgSEVMTUZJTEVfRU5WPTxlbnY+IGhlbG1m" + + "aWxlIC0tc2VsZWN0b3IgbmFtZT1vcGVuYmFvLXNlcnZlciBzeW5jCiAgIGBgYAoKICAgVGhlIG1p" + + "Z3JhdGlvbiBsb2cgc2hvdWxkIHRoZW4gc2hvdyBgUmVjZWl2ZWQgZGlzY292ZXJ5IGRvY3VtZW50" + + "IGZyb20KICAgLi4uLy53ZWxsLWtub3duL29wZW5pZC1jb25maWd1cmF0aW9uYCBhbmQgYEZpbmFs" + + "IE9wZW5CYW8gSldUIEpXS1MgVVJMIHNldCB0bzoKICAgLi4uL29wZW5pZC92MS9qd2tzYCwgYW5k" + + "IHRoZSBzZXJ2aWNlIHBvZHMgYXV0aGVudGljYXRlIGFuZCByZWFjaCBSZWFkeSB3aXRoaW4gfjEg" + + "bWluLgogICBBS1MgY2x1c3RlcnMgd2l0aCB0aGUgY2x1c3RlciBPSURDIGlzc3VlciBlbmFibGVk" + + "ICh0aGUgYGFrcy1jbHVzdGVyYCBkZWZhdWx0KSBhbHdheXMgbmVlZAogICB0aGlzIGJlY2F1c2Ug" + + "dGhlIHN0YXRpYy1wdWJrZXkgZmFsbGJhY2sgY2Fubm90IG1hdGNoIHRoZSBpc3N1ZXIncyByb3Rh" + + "dGluZyBzaWduaW5nIGtleXMuCgojIyBGYWlsdXJlOiBPT01LaWxsZWQgb24gQ2Fzc2FuZHJhCgoj" + + "IyMgU3ltcHRvbXMKCmBgYHRleHQKTkFNRSAgICAgICAgICBSRUFEWSAgIFNUQVRVUyAgICBSRVNU" + + "QVJUUyAgIEFHRQpjYXNzYW5kcmEtMCAgIDAvMSAgICAgT09NS2lsbGVkICAzICAgICAgICAgMTBt" + + "CmBgYAoKIyMjIERpYWdub3NpcwoKYGBgYmFzaAprdWJlY3RsIGRlc2NyaWJlIHBvZCAtbiBjYXNz" + + "YW5kcmEtc3lzdGVtIGNhc3NhbmRyYS0wIHwgZ3JlcCAtQTMgIkxhc3QgU3RhdGUiCmBgYAoKIyMj" + + "IEZpeAoKT3ZlcnJpZGUgQ2Fzc2FuZHJhIHJlc291cmNlcyB2aWEgaGVsbWZpbGUgdmFsdWVzLiBT" + + "ZWUgRXhhbXBsZSAxIGluCltleGFtcGxlcy5tZF0oLi4vZXhhbXBsZXMubWQpLgoKYGBgeWFtbAot" + + "IGNhc3NhbmRyYToKICAgIHJlc291cmNlczoKICAgICAgbGltaXRzOgogICAgICAgIGNwdTogIjgi" + + "CiAgICAgICAgbWVtb3J5OiA4MTkyTWkKICAgICAgcmVxdWVzdHM6CiAgICAgICAgY3B1OiAiMiIK" + + "ICAgICAgICBtZW1vcnk6IDQwOTZNaQpgYGAKCk5vdGU6IGByZXNvdXJjZVByZXNldGAgaXMgbm90" + + "IGF2YWlsYWJsZSBpbiB0aGUgTlZDRiBjYXNzYW5kcmEgd3JhcHBlciBjaGFydC4gVXNlIGV4cGxp" + + "Y2l0IGByZXNvdXJjZXNgLgoKIyMgRmFpbHVyZTogUG9kcyBTdHVjayBpbiBQZW5kaW5nCgojIyMg" + + "U3ltcHRvbXMKCmBgYHRleHQKTkFNRSAgICAgICAgICBSRUFEWSAgIFNUQVRVUyAgICBSRVNUQVJU" + + "UyAgIEFHRQpjYXNzYW5kcmEtMCAgIDAvMSAgICAgUGVuZGluZyAgIDAgICAgICAgICAgMTBtCmBg" + + "YAoKIyMjIERpYWdub3NpcwoKYGBgYmFzaAojIENoZWNrIGV2ZW50cyBmb3Igc2NoZWR1bGluZyBm" + + "YWlsdXJlcwprdWJlY3RsIGRlc2NyaWJlIHBvZCAtbiA8bmFtZXNwYWNlPiA8cG9kLW5hbWU+Cgoj" + + "IENvbW1vbiBjYXVzZXM6CiMgLSAiMC8xMiBub2RlcyBhcmUgYXZhaWxhYmxlOiAxMiBub2RlKHMp" + + "IGRpZG4ndCBtYXRjaCBQb2QncyBub2RlIGFmZmluaXR5IgojIC0gIjAvMTIgbm9kZXMgYXJlIGF2" + + "YWlsYWJsZTogMTIgSW5zdWZmaWNpZW50IG1lbW9yeSIKIyAtICJwZXJzaXN0ZW50dm9sdW1lY2xh" + + "aW0gbm90IGZvdW5kIgpgYGAKCiMjIyBGaXhlcwoKMS4gKipOb2RlIHNlbGVjdG9yIG1pc21hdGNo" + + "Kio6IENoZWNrIGBub2RlU2VsZWN0b3JzYCBpbiBlbnZpcm9ubWVudCBmaWxlCgogICBgYGBiYXNo" + + "CiAgIGt1YmVjdGwgZ2V0IG5vZGVzIC0tc2hvdy1sYWJlbHMgfCBncmVwIG52Y2YKICAgYGBgCgoy" + + "LiAqKlN0b3JhZ2UgY2xhc3Mgbm90IGZvdW5kKio6IENoZWNrIHN0b3JhZ2UgY2xhc3MgZXhpc3Rz" + + "CgogICBgYGBiYXNoCiAgIGt1YmVjdGwgZ2V0IHN0b3JhZ2VjbGFzcwogICBgYGAKCjMuICoqSW5z" + + "dWZmaWNpZW50IHJlc291cmNlcyoqOiBDaGVjayBub2RlIGNhcGFjaXR5CgogICBgYGBiYXNoCiAg" + + "IGt1YmVjdGwgZGVzY3JpYmUgbm9kZSA8bm9kZS1uYW1lPiB8IGdyZXAgLUE1ICJBbGxvY2F0ZWQg" + + "cmVzb3VyY2VzIgogICBgYGAKCiMjIEZhaWx1cmU6IEhlbG0gUmVsZWFzZSBpbiBGYWlsZWQgU3Rh" + + "dGUKCiMjIyBTeW1wdG9tcwoKYGBgYmFzaApoZWxtIGxpc3QgLUEKIyBTaG93cyByZWxlYXNlIHdp" + + "dGggU1RBVFVTOiBmYWlsZWQKYGBgCgpSZS1ydW5uaW5nIGBoZWxtZmlsZSBzeW5jYCBhcHBlYXJz" + + "IHRvIHN1Y2NlZWQgYnV0IHNlcnZpY2VzIGRvbid0IHdvcmsgKG1pZ3JhdGlvbnMgc2tpcHBlZCku" + + "CgojIyMgRGlhZ25vc2lzCgpgYGBiYXNoCmhlbG0gaGlzdG9yeSA8cmVsZWFzZS1uYW1lPiAtbiA8" + + "bmFtZXNwYWNlPgpgYGAKCiMjIyBGaXgKCk11c3QgZGVzdHJveSB0aGUgZmFpbGVkIHJlbGVhc2Ug" + + "YW5kIHJlLXN5bmMgKG5vdCBqdXN0IGFwcGx5KToKCmBgYGJhc2gKSEVMTUZJTEVfRU5WPTxlbnY+" + + "IGhlbG1maWxlIC0tc2VsZWN0b3IgbmFtZT08cmVsZWFzZT4gZGVzdHJveQojIElmIG5hbWVzcGFj" + + "ZSBuZWVkcyBjbGVhbnVwOgprdWJlY3RsIGRlbGV0ZSBuYW1lc3BhY2UgPG5hbWVzcGFjZT4Ka3Vi" + + "ZWN0bCBjcmVhdGUgbmFtZXNwYWNlIDxuYW1lc3BhY2U+CiMgUmUtY3JlYXRlIHB1bGwgc2VjcmV0" + + "IGlmIG5lZWRlZApIRUxNRklMRV9FTlY9PGVudj4gaGVsbWZpbGUgLS1zZWxlY3RvciBuYW1lPTxy" + + "ZWxlYXNlPiBzeW5jCmBgYAoKIyMgRmFpbHVyZTogQWNjb3VudCBCb290c3RyYXAgSm9iIEZhaWxl" + + "ZAoKIyMjIFN5bXB0b21zCgpTZXJ2aWNlcyBkZXBsb3kgYnV0IGZ1bmN0aW9ucyBjYW4ndCBiZSBj" + + "cmVhdGVkLiBFdmVudHMgc2hvdyBib290c3RyYXAgam9iIGZhaWx1cmUuCgojIyMgRGlhZ25vc2lz" + + "CgpgYGBiYXNoCiMgQ2hlY2sgYm9vdHN0cmFwIGpvYiBzdGF0dXMKa3ViZWN0bCBnZXQgam9icyAt" + + "biBudmNmCgojIEdldCBsb2dzIChqb2IgYXV0by1kZWxldGVzIGFmdGVyIH41IG1pbnV0ZXMpCmt1" + + "YmVjdGwgbG9ncyBqb2IvbnZjZi1hcGktYWNjb3VudC1ib290c3RyYXAgLW4gbnZjZgoKIyBDaGVj" + + "ayBBUEkgbG9ncwprdWJlY3RsIGxvZ3MgLW4gbnZjZiAtbCBhcHAua3ViZXJuZXRlcy5pby9uYW1l" + + "PW52Y2YtYXBpIC0tdGFpbD0xMDAKYGBgCgojIyMgQ29tbW9uIENhdXNlcwoKMS4gKipXcm9uZyBi" + + "YXNlNjQgY3JlZGVudGlhbHMqKjogQ3JlZGVudGlhbHMgaW4gYHNlY3JldHMvPGVudj4tc2VjcmV0" + + "cy55YW1sYCBtdXN0IGJlIGAkb2F1dGh0b2tlbjpBUElfS0VZYCBiYXNlNjQtZW5jb2RlZCwgbm90" + + "IGp1c3QgdGhlIEFQSSBrZXkKCiAgIGBgYGJhc2gKICAgIyBWZXJpZnkgeW91ciBlbmNvZGVkIGNy" + + "ZWRlbnRpYWwKICAgZWNobyAnWU9VUl9CQVNFNjRfU1RSSU5HJyB8IGJhc2U2NCAtZAogICAjIFNo" + + "b3VsZCBvdXRwdXQ6ICRvYXV0aHRva2VuOm52YXBpLXh4eHh4CiAgIGBgYAoKMi4gKipSZWdpc3Ry" + + "eSB1bnJlYWNoYWJsZSoqOiBBUEkgY2FuJ3QgcmVhY2ggdGhlIHJlZ2lzdHJ5IHNwZWNpZmllZCBp" + + "biBgYWNjb3VudEJvb3RzdHJhcC5yZWdpc3RyeUNyZWRlbnRpYWxzYAoKIyMjIEZpeAoKRml4IGNy" + + "ZWRlbnRpYWxzIGluIHNlY3JldHMgZmlsZSwgdGhlbiByZWNvdmVyIHNlcnZpY2VzIHdpdGhvdXQg" + + "ZGVzdHJveWluZyBkZXBlbmRlbmNpZXM6CgpgYGBiYXNoCkhFTE1GSUxFX0VOVj08ZW52PiBoZWxt" + + "ZmlsZSAtLXNlbGVjdG9yIHJlbGVhc2UtZ3JvdXA9c2VydmljZXMgZGVzdHJveQprdWJlY3RsIGRl" + + "bGV0ZSBuYW1lc3BhY2UgbnZjZiBhcGkta2V5cyBlc3Mgc2lzIC0taWdub3JlLW5vdC1mb3VuZApr" + + "dWJlY3RsIGNyZWF0ZSBuYW1lc3BhY2UgbnZjZiAmJiBrdWJlY3RsIGNyZWF0ZSBuYW1lc3BhY2Ug" + + "YXBpLWtleXMgJiYgXAogIGt1YmVjdGwgY3JlYXRlIG5hbWVzcGFjZSBlc3MgJiYga3ViZWN0bCBj" + + "cmVhdGUgbmFtZXNwYWNlIHNpcwojIFJlLWNyZWF0ZSBwdWxsIHNlY3JldHMgaWYgbmVlZGVkCkhF" + + "TE1GSUxFX0VOVj08ZW52PiBoZWxtZmlsZSAtLXNlbGVjdG9yIHJlbGVhc2UtZ3JvdXA9c2Vydmlj" + + "ZXMgc3luYwpgYGAKCiMjIEZhaWx1cmU6IE5WQ0EgQ2xlYW51cCBTdHVjayAoRmluYWxpemVycykK" + + "CiMjIyBTeW1wdG9tcwoKQ29tcHV0ZS1wbGFuZSBjbGVhbnVwIGhhbmdzLiBOYW1lc3BhY2VzCnN0" + + "dWNrIGluIGBUZXJtaW5hdGluZ2A6CgpgYGBiYXNoCmt1YmVjdGwgZ2V0IG5zCiMgbnZjYS1vcGVy" + + "YXRvciAgICBUZXJtaW5hdGluZyAgIDEwbQojIG52Y2YtYmFja2VuZCAgICAgVGVybWluYXRpbmcg" + + "ICAxMG0KYGBgCgojIyMgRml4CgpSdW4gdGhpcyBvbmx5IGFnYWluc3QgdGhlIGNvbXB1dGUgY29u" + + "dGV4dCwgYWZ0ZXIgdGhlIG5vcm1hbCBjb21wdXRlLXBsYW5lCnRlYXJkb3duIGhhcyBmYWlsZWQu" + + "IFJlbW92ZSBmaW5hbGl6ZXJzIGZyb20gTlZDRkJhY2tlbmQgY3VzdG9tIHJlc291cmNlcyBhbmQK" + + "Zm9yY2UtZGVsZXRlIHN0dWNrIG5hbWVzcGFjZXM6CgpgYGBiYXNoCmV4cG9ydCBDT01QVVRFX0NP" + + "TlRFWFQ9PGNvbXB1dGUtcGxhbmUtY29udGV4dD4KCiMgUmVtb3ZlIGZpbmFsaXplcnMgZnJvbSBO" + + "VkNGQmFja2VuZCByZXNvdXJjZXMKa3ViZWN0bCAtLWNvbnRleHQgIiRDT01QVVRFX0NPTlRFWFQi" + + "IGdldCBudmNmYmFja2VuZHMgLUEgLW8ganNvbiB8IFwKICBqcSAnLml0ZW1zW10gfCAubWV0YWRh" + + "dGEubmFtZXNwYWNlICsgIi8iICsgLm1ldGFkYXRhLm5hbWUnIC1yIHwgXAogIHhhcmdzIC1Je30g" + + "c2ggLWMgJ2NvbnRleHQ9IiQxIjsgbnM9IiR7MiUlLyp9IjsgbmFtZT0iJHsyIyMqL30iOyBrdWJl" + + "Y3RsIC0tY29udGV4dCAiJGNvbnRleHQiIHBhdGNoIG52Y2ZiYWNrZW5kICIkbmFtZSIgLW4gIiRu" + + "cyIgLS10eXBlPW1lcmdlIC1wICJ7XCJtZXRhZGF0YVwiOntcImZpbmFsaXplcnNcIjpbXX19Iicg" + + "XyAiJENPTVBVVEVfQ09OVEVYVCIge30KCiMgRm9yY2UtZGVsZXRlIHN0dWNrIG5hbWVzcGFjZXMg" + + "YnkgY2xlYXJpbmcgdGhlaXIgZmluYWxpemVycwpmb3IgbnMgaW4gbnZjYS1vcGVyYXRvciBudmNm" + + "LWJhY2tlbmQ7IGRvCiAga3ViZWN0bCAtLWNvbnRleHQgIiRDT01QVVRFX0NPTlRFWFQiIGdldCBu" + + "YW1lc3BhY2UgIiRucyIgLW8ganNvbiAyPi9kZXYvbnVsbCB8IFwKICAgIGpxICcuc3BlYy5maW5h" + + "bGl6ZXJzID0gW10nIHwgXAogICAga3ViZWN0bCAtLWNvbnRleHQgIiRDT01QVVRFX0NPTlRFWFQi" + + "IHJlcGxhY2UgXAogICAgICAtLXJhdyAiL2FwaS92MS9uYW1lc3BhY2VzLyRucy9maW5hbGl6ZSIg" + + "LWYgLSAyPi9kZXYvbnVsbApkb25lCmBgYAoKIyMgRmFpbHVyZTogTlZDQSBPcGVyYXRvciBNaXNz" + + "aW5nIG9yIE5vdCBSZWFkeQoKIyMjIFN5bXB0b21zCgpBZnRlciBjb21wdXRlLXBsYW5lIGluc3Rh" + + "bGwgY29tcGxldGVzLCBubyBgbnZjYS1vcGVyYXRvcmAgRGVwbG95bWVudCBleGlzdHMsIG9yCml0" + + "IGV4aXN0cyBidXQgcG9kcyBhcmUgbm90IHJlYWR5OgoKYGBgYmFzaAprdWJlY3RsIC0tY29udGV4" + + "dCA8Y29tcHV0ZS1wbGFuZS1jb250ZXh0PiBnZXQgZGVwbG95bWVudCBudmNhLW9wZXJhdG9yIFwK" + + "ICAtbiBudmNhLW9wZXJhdG9yCmt1YmVjdGwgLS1jb250ZXh0IDxjb21wdXRlLXBsYW5lLWNvbnRl" + + "eHQ+IGdldCBwb2RzIC1uIG52Y2Etb3BlcmF0b3IKYGBgCgojIyMgRGlhZ25vc2lzIGFuZCBGaXhl" + + "cwoKRmlyc3QgaWRlbnRpZnkgd2hldGhlciB0aGUgaW5zdGFsbGF0aW9uIHVzZWQgdGhlIEhlbG1m" + + "aWxlIHZhbHVlcyBmbG93IG9yIHRoZQpDTEkgcHJvZmlsZSBmbG93LiBSZS1ydW4gb25seSB0aGF0" + + "IGZsb3cncyByZWdpc3RyYXRpb24gYW5kIGluc3RhbGxhdGlvbiBzdGVwcwpmcm9tIFtTcGxpdCBD" + + "b21wdXRlLVBsYW5lIEluc3RhbGxhdGlvbl0oY29tcHV0ZS1wbGFuZS1pbnN0YWxsYXRpb24ubWQp" + + "LiBEbyBub3QKc3Vic3RpdHV0ZSBvbmUgaGFuZG9mZiBmb3IgdGhlIG90aGVyLgoKSWYgaW5zdGFs" + + "bGF0aW9uIHN1Y2NlZWRzIGJ1dCBwb2RzIHN0aWxsIGZhaWwsIGluc3BlY3QgdGhlIGNvbXB1dGUg" + + "Y29udGV4dDoKCmBgYGJhc2gKa3ViZWN0bCAtLWNvbnRleHQgPGNvbXB1dGUtcGxhbmUtY29udGV4" + + "dD4gZGVzY3JpYmUgZGVwbG95IG52Y2Etb3BlcmF0b3IgXAogIC1uIG52Y2Etb3BlcmF0b3IKa3Vi" + + "ZWN0bCAtLWNvbnRleHQgPGNvbXB1dGUtcGxhbmUtY29udGV4dD4gbG9ncyBkZXBsb3ltZW50L252" + + "Y2Etb3BlcmF0b3IgXAogIC1uIG52Y2Etb3BlcmF0b3IgLS10YWlsPTIwMAprdWJlY3RsIC0tY29u" + + "dGV4dCA8Y29tcHV0ZS1wbGFuZS1jb250ZXh0PiBnZXQgbnZjZmJhY2tlbmQgLW4gbnZjYS1vcGVy" + + "YXRvcgpgYGAKCkZvciBwcml2YXRlIHJlZ2lzdHJ5IGZhaWx1cmVzLCB2ZXJpZnkgdGhhdCB0aGUg" + + "cHVsbCBzZWNyZXQgZXhpc3RzIGluCmBudmNhLW9wZXJhdG9yYC4gVGhlIGNoYXJ0IGFuZCBvcGVy" + + "YXRvciBtaXJyb3IgaXQgdG8gYG52Y2Etc3lzdGVtYDsgZG8gbm90IHVzZSBhCmJyb2FkZXIgbWFu" + + "dWFsIG5hbWVzcGFjZSBsb29wLiBTZWUKW3B1bGwtc2VjcmV0cy5tZF0ocHVsbC1zZWNyZXRzLm1k" + + "I2NvbXB1dGUtcGxhbmUtc2VjcmV0KS4KCiMjIEZhaWx1cmU6IEdhdGV3YXkgRW5kcG9pbnQgQ2hh" + + "bmdlZAoKIyMjIFN5bXB0b21zCgpSZXF1ZXN0cyB0aHJvdWdoIHRoZSBjb25maWd1cmVkIHJvdXRl" + + "IGRvbWFpbiBmYWlsIGFmdGVyIHRoZSBHYXRld2F5IG9yIGl0cyBsb2FkIGJhbGFuY2VyIGlzIHJl" + + "cGxhY2VkLiBUaGUgR2F0ZXdheSByZXBvcnRzIGEgbmV3IGVuZHBvaW50LgoKIyMjIERpYWdub3Np" + + "cwoKYGBgYmFzaApHQVRFV0FZX0FERFI9JChrdWJlY3RsIC0tY29udGV4dCA8Y29udHJvbC1wbGFu" + + "ZS1jb250ZXh0PiBnZXQgZ2F0ZXdheSBcCiAgbnZjZi1nYXRld2F5IC1uIGVudm95LWdhdGV3YXkg" + + "XAogIC1vIGpzb25wYXRoPSd7LnN0YXR1cy5hZGRyZXNzZXNbMF0udmFsdWV9JykKdGVzdCAtbiAi" + + "JEdBVEVXQVlfQUREUiIKZGlnICtzaG9ydCAiYXBpLjxkb21haW4+IgpjdXJsIC1IICJIb3N0OiBh" + + "cGkuPGRvbWFpbj4iICJodHRwOi8vJEdBVEVXQVlfQUREUi9oZWFsdGgiCgojIENoZWNrIGRpcmVj" + + "dCBhZGRyZXNzIGZpZWxkcyB1c2VkIGJ5IGEgc3BsaXQgZGVwbG95bWVudApncmVwIG52Y2ZOYXRz" + + "U2VydmljZVVSTCBkZXBsb3kvc3RhY2tzL3NlbGYtbWFuYWdlZC9lbnZpcm9ubWVudHMvPGVudj4u" + + "eWFtbApncmVwIC1FICdpY21zU2VydmljZXxyZXZhbFNlcnZpY2V8bmF0cycgXAogIGRlcGxveS9z" + + "dGFja3MvbnZjZi1jb21wdXRlLXBsYW5lL2Vudmlyb25tZW50cy88ZW52Pi55YW1sCmBgYAoKIyMj" + + "IEZpeAoKS2VlcCBgZ2xvYmFsLmRvbWFpbmAsIHJvdXRlIGhvc3RuYW1lcywgYW5kIEhvc3Qgb3Zl" + + "cnJpZGVzIHVuY2hhbmdlZC4gVXBkYXRlIEROUwpvciB0aGUgZGlyZWN0IGNsaWVudCBkZXN0aW5h" + + "dGlvbi4gRm9yIHNwbGl0IGRlcGxveW1lbnRzLCB1cGRhdGUgb25seSBkaXJlY3QKR2F0ZXdheS1h" + + "ZGRyZXNzIGZpZWxkcyBhbmQgcmVmcmVzaCByZWdpc3RyYXRpb24gYW5kIGluc3RhbGxhdGlvbiB0" + + "aHJvdWdoIHRoZQpzYW1lIHNlbGVjdGVkIGhhbmRvZmYuIFNlZQpbRXhhbXBsZSA0XSguLi9leGFt" + + "cGxlcy5tZCNleGFtcGxlLTQtcmVjb3Zlci1mcm9tLWEtZ2F0ZXdheS1lbmRwb2ludC1jaGFuZ2Up" + + "LgoKIyMgVXNlZnVsIE5hbWVzcGFjZS10by1TZXJ2aWNlIE1hcHBpbmcKClF1aWNrIHJlZmVyZW5j" + + "ZSBmb3Igd2hpY2ggc2VydmljZXMgcnVuIGluIHdoaWNoIG5hbWVzcGFjZToKCnwgTmFtZXNwYWNl" + + "IHwgU2VydmljZXMgfAp8LS0tLS0tLS0tLS18LS0tLS0tLS0tLXwKfCBuYXRzLXN5c3RlbSB8IG5h" + + "dHMgfAp8IGNhc3NhbmRyYS1zeXN0ZW0gfCBjYXNzYW5kcmEgfAp8IHZhdWx0LXN5c3RlbSB8IG9w" + + "ZW5iYW8tc2VydmVyIHwKfCBudmNmIHwgYXBpLCBpbnZvY2F0aW9uLXNlcnZpY2UsIGdycGMtcHJv" + + "eHksIG5vdGFyeS1zZXJ2aWNlLCByZXZhbCwgbGxtLXJlcXVlc3Qtcm91dGVyLCBsbG0tYXBpLWdh" + + "dGV3YXkgfAp8IGFwaS1rZXlzIHwgYXBpLWtleXMsIGFkbWluLWlzc3Vlci1wcm94eSB8CnwgZXNz" + + "IHwgZXNzLWFwaSB8Cnwgc2lzIHwgc2lzIHwKfCBlbnZveS1nYXRld2F5LXN5c3RlbSB8IGVudm95" + + "IGdhdGV3YXkgKGluZ3Jlc3MgY29udHJvbGxlcikgfAp8IGVudm95LWdhdGV3YXkgfCBnYXRld2F5" + + "IHJlc291cmNlICsgcm91dGVzIHwKUEsHCLV6cDu1MAAAtTAAAFBLAwQUAAgAAAAAACEAAAAAAAAA" + + "AAAAAAAARAAJAGRhdGEvbnZjZi1zZWxmLW1hbmFnZWQtaW5zdGFsbGF0aW9uL3JlZmVyZW5jZXMv" + + "aGVsbWZpbGUtc3RydWN0dXJlLm1kVVQFAAEAps4SIyBIZWxtZmlsZSBTdHJ1Y3R1cmUgUmVmZXJl" + + "bmNlCgojIyBEaXJlY3RvcnkgTGF5b3V0CgpgYGBiYXNoCm52Y2Ytc2VsZi1tYW5hZ2VkLXN0YWNr" + + "Lwp8LS0gaGVsbWZpbGUuZC8KfCAgIHwtLSAwMS1kZXBlbmRlbmNpZXMueWFtbC5nb3RtcGwgICMg" + + "TkFUUywgQ2Fzc2FuZHJhLCBPcGVuQmFvCnwgICB8LS0gMDItY29yZS55YW1sLmdvdG1wbCAgICAg" + + "ICAgICAjIE5WQ0Ygc2VydmljZXMgKyBpbmdyZXNzCnwgICBgLS0gMDMtb2JzZXJ2YWJpbGl0eS55" + + "YW1sLmdvdG1wbCAjIE9ic2VydmFiaWxpdHkgc3RhY2sgKG9wdGlvbmFsKQp8LS0gZW52aXJvbm1l" + + "bnRzLwp8ICAgfC0tIGJhc2UueWFtbCAgICAgICAgICAgICAgICAgICAgIyBEZWZhdWx0IHZhbHVl" + + "cyAoYWxsIGVudmlyb25tZW50cykKfCAgIGAtLSA8ZW52LW5hbWU+LnlhbWwgICAgICAgICAgICAg" + + "ICMgUGVyLWVudmlyb25tZW50IG92ZXJyaWRlcwp8LS0gc2VjcmV0cy8KfCAgIGAtLSA8ZW52LW5h" + + "bWU+LXNlY3JldHMueWFtbCAgICAgICMgU2Vuc2l0aXZlIHZhbHVlcyAocmVnaXN0cnkgY3JlZHMs" + + "IHBhc3N3b3JkcykKYC0tIGdsb2JhbC55YW1sLmdvdG1wbCAgICAgICAgICAgICAgICMgR28gdGVt" + + "cGxhdGUgdGhhdCBjb25zdHJ1Y3RzIHBlci1jaGFydCB2YWx1ZXMKCm52Y2YtY29tcHV0ZS1wbGFu" + + "ZS1zdGFjay8KfC0tIGhlbG1maWxlLmQvCnwgICB8LS0gMDEtZGVwZW5kZW5jaWVzLnlhbWwuZ290" + + "bXBsICAjIENvbXB1dGUgcGxhbmUgZGVwZW5kZW5jeSBjb21wb25lbnRzCnwgICBgLS0gMDItbnZj" + + "YS55YW1sLmdvdG1wbCAgICAgICAgICAjIG52Y2Etb3BlcmF0b3IgY2hhcnQgYW5kIG52Y2EgY29u" + + "ZmlndXJhdGlvbgp8LS0gZW52aXJvbm1lbnRzLwp8ICAgfC0tIGJhc2UueWFtbCAgICAgICAgICAg" + + "ICAgICAgICAgIyBEZWZhdWx0IHZhbHVlcyAoYWxsIGVudmlyb25tZW50cykKfCAgIGAtLSA8ZW52" + + "LW5hbWU+LnlhbWwgICAgICAgICAgICAgICMgUGVyLWVudmlyb25tZW50IG92ZXJyaWRlcwpgLS0g" + + "Z2xvYmFsLnlhbWwuZ290bXBsICAgICAgICAgICAgICAgIyBHbyB0ZW1wbGF0ZSB0aGF0IGNvbnN0" + + "cnVjdHMgcGVyLWNoYXJ0IHZhbHVlcwpgYGAKClRoZXNlIHN0YWNrcyBoYXZlIHNlcGFyYXRlIGVu" + + "dmlyb25tZW50cyBhbmQgY2x1c3RlciBjb250ZXh0cy4gVGhlIEhlbG1maWxlCnZhbHVlcyBmbG93" + + "IGF1dGhvcnMgYm90aCBlbnZpcm9ubWVudCBmaWxlcy4gVGhlIENMSSBwcm9maWxlIGZsb3cgaW5z" + + "dGVhZCB1c2VzCnRoZSBDTEktZ2VuZXJhdGVkIGNvbnRyb2wtcGxhbmUgcHJvZmlsZSBhcyBpdHMg" + + "ZW5kcG9pbnQgc291cmNlLiBEbyBub3QgbWl4IHRoZQpoYW5kb2Zmcy4gU2VlIFtTcGxpdCBDb21w" + + "dXRlLVBsYW5lIEluc3RhbGxhdGlvbl0oY29tcHV0ZS1wbGFuZS1pbnN0YWxsYXRpb24ubWQpLgoK" + + "IyMgR290bXBsIEZpbGVzIGFuZCBUaGVpciBSZWxlYXNlcwoKIyMjIDAxLWRlcGVuZGVuY2llcy55" + + "YW1sLmdvdG1wbAoKfCBSZWxlYXNlIHwgQ2hhcnQgfCBOYW1lc3BhY2UgfCBOb3RlcyB8CnwtLS0t" + + "LS0tLS18LS0tLS0tLXwtLS0tLS0tLS0tLXwtLS0tLS0tfAp8IG5hdHMgfCBoZWxtLW52Y2YtbmF0" + + "cyB8IG5hdHMtc3lzdGVtIHwgTWVzc2FnaW5nIHwKfCBvcGVuYmFvLXNlcnZlciB8IGhlbG0tbnZj" + + "Zi1vcGVuYmFvLXNlcnZlciB8IHZhdWx0LXN5c3RlbSB8IFNlY3JldHMgbWFuYWdlbWVudCwgZGVw" + + "ZW5kcyBvbiBuYXRzIHwKfCBjYXNzYW5kcmEgfCBoZWxtLW52Y2YtY2Fzc2FuZHJhIHwgY2Fzc2Fu" + + "ZHJhLXN5c3RlbSB8IERhdGFiYXNlIHwKClVzZXMgYDw8OiAqZGVwZW5kZW5jeWAgdGVtcGxhdGUg" + + "aW5oZXJpdGFuY2Ugd2l0aCBgcmVsZWFzZS1ncm91cDogZGVwZW5kZW5jaWVzYCBsYWJlbC4KCiMj" + + "IyAwMi1jb3JlLnlhbWwuZ290bXBsCgp8IFJlbGVhc2UgfCBDaGFydCB8IE5hbWVzcGFjZSB8IExh" + + "YmVsIHwKfC0tLS0tLS0tLXwtLS0tLS0tfC0tLS0tLS0tLS0tfC0tLS0tLS18CnwgYXBpLWtleXMg" + + "fCBoZWxtLW52Y2YtYXBpLWtleXMgfCBhcGkta2V5cyB8IHNlcnZpY2VzIHwKfCBzaXMgfCBoZWxt" + + "LW52Y2Ytc2lzIHwgc2lzIHwgc2VydmljZXMgfAp8IGFwaSB8IGhlbG0tbnZjZi1hcGkgfCBudmNm" + + "IHwgc2VydmljZXMgfAp8IGludm9jYXRpb24tc2VydmljZSB8IGhlbG0tbnZjZi1pbnZvY2F0aW9u" + + "LXNlcnZpY2UgfCBudmNmIHwgc2VydmljZXMgfAp8IGdycGMtcHJveHkgfCBoZWxtLW52Y2YtZ3Jw" + + "Yy1wcm94eSB8IG52Y2YgfCBzZXJ2aWNlcyB8CnwgZXNzLWFwaSB8IGhlbG0tbnZjZi1lc3MtYXBp" + + "IHwgZXNzIHwgc2VydmljZXMgfAp8IG5vdGFyeS1zZXJ2aWNlIHwgaGVsbS1udmNmLW5vdGFyeS1z" + + "ZXJ2aWNlIHwgbnZjZiB8IHNlcnZpY2VzIHwKfCByZXZhbCB8IGhlbG0tcmV2YWwgfCBudmNmIHwg" + + "c2VydmljZXMgfAp8IGxsbS1yZXF1ZXN0LXJvdXRlciB8IG52Y2YvaGVsbS1udmNmLWxsbS1yZXF1" + + "ZXN0LXJvdXRlciB8IG52Y2YgfCBzZXJ2aWNlcyB8CnwgbGxtLWFwaS1nYXRld2F5IHwgbnZjZi9o" + + "ZWxtLW52Y2YtbGxtLWFwaS1nYXRld2F5IHwgbnZjZiB8IHNlcnZpY2VzIHwKfCBhZG1pbi1pc3N1" + + "ZXItcHJveHkgfCBoZWxtLWFkbWluLXRva2VuLWlzc3Vlci1wcm94eSB8IGFwaS1rZXlzIHwgKG5v" + + "IHJlbGVhc2UtZ3JvdXAgbGFiZWwpIHwKfCBpbmdyZXNzIHwgbnZjZi1nYXRld2F5LXJvdXRlcyB8" + + "IGVudm95LWdhdGV3YXktc3lzdGVtIHwgaW5ncmVzcyB8CgpNb3N0IHNlcnZpY2VzIHVzZSBgaW5o" + + "ZXJpdDogW3t0ZW1wbGF0ZTogc2VydmljZX1dYC4gTExNIHJlbGVhc2VzIHVzZSBPQ0kgY2hhcnRz" + + "IGZyb20gdGhlIGBudmNmYCByZXBvc2l0b3J5IHdpdGggc3RhbmRhbG9uZSBgdmFsdWVzOiBbLi4v" + + "Z2xvYmFsLnlhbWwuZ290bXBsXWAgYmxvY2tzOyB0aGV5IGFyZSBnYXRlZCBvbiBgYWRkb25zLmxs" + + "bS5lbmFibGVkYC4gYGFkbWluLWlzc3Vlci1wcm94eWAgYW5kIGBpbmdyZXNzYCBoYXZlIHN0YW5k" + + "YWxvbmUgYHZhbHVlczpgIGJsb2Nrcy4KCiMjIyAwMy1vYnNlcnZhYmlsaXR5LnlhbWwuZ290bXBs" + + "Cgp8IFJlbGVhc2UgfCBDaGFydCB8IE5hbWVzcGFjZSB8IExhYmVsIHwKfC0tLS0tLS0tLXwtLS0t" + + "LS0tfC0tLS0tLS0tLS0tfC0tLS0tLS18CnwgKG9ic2VydmFiaWxpdHkgcmVsZWFzZXMpIHwgKHZh" + + "cmlvdXMpIHwgb2JzZXJ2YWJpbGl0eSB8IG9ic2VydmFiaWxpdHkgfAoKR2F0ZWQgb24gb2JzZXJ2" + + "YWJpbGl0eS1zcGVjaWZpYyBmbGFncyBpbiB0aGUgZW52aXJvbm1lbnQgZmlsZS4gU2tpcHBlZCBp" + + "ZiBkaXNhYmxlZC4KCiMjIFRlbXBsYXRlIEluaGVyaXRhbmNlCgojIyMgYDw8OiAqZGVwZW5kZW5j" + + "eWAgKFlBTUwgbWVyZ2UpCgpVc2VkIGluIGAwMS1kZXBlbmRlbmNpZXMueWFtbC5nb3RtcGxgLiBN" + + "ZXJnZXMgdGhlIHRlbXBsYXRlJ3MgcHJvcGVydGllcyBpbnRvIHRoZSByZWxlYXNlLgoKKipHb3Rj" + + "aGEqKjogWUFNTCBtZXJnZSByZXBsYWNlcyBsaXN0cy4gSWYgeW91IGFkZCBhIGB2YWx1ZXM6YCBr" + + "ZXkgdG8gdGhlIHJlbGVhc2UsIGl0ICoqcmVwbGFjZXMqKiB0aGUgdGVtcGxhdGUncyBgdmFsdWVz" + + "OmAgbGlzdCBlbnRpcmVseS4gWW91IG11c3QgcmUtaW5jbHVkZSBhbGwgdGVtcGxhdGUgdmFsdWVz" + + "OgoKSW4gdGhlIHRlbXBsYXRlIGJlbG93LCBgPHByaXZhdGUtdmFsdWVzPmAgcmVmZXJzIHRvIHRo" + + "ZSBgc2VjcmV0cy9gIGRpcmVjdG9yeSBhdCB0aGUgaGVsbWZpbGUgc3RhY2sgcm9vdC4KCmBgYHlh" + + "bWwKIyBUZW1wbGF0ZSBkZWZpbmVzOgp0ZW1wbGF0ZXM6CiAgZGVwZW5kZW5jeTogJmRlcGVuZGVu" + + "Y3kKICAgIGNoYXJ0OiBudmNmL2hlbG0tbnZjZi17eyAuUmVsZWFzZS5OYW1lIH19CiAgICB2YWx1" + + "ZXM6CiAgICAgIC0gLi4vZ2xvYmFsLnlhbWwuZ290bXBsCiAgICAgIC0gLi4vPHByaXZhdGUtdmFs" + + "dWVzPi97eyByZXF1aXJlZEVudiAiSEVMTUZJTEVfRU5WIiB9fS1zZWNyZXRzLnlhbWwKCiMgV2hl" + + "biBvdmVycmlkaW5nLCBNVVNUIHJlLWluY2x1ZGUgYm90aDoKLSBuYW1lOiBjYXNzYW5kcmEKICA8" + + "PDogKmRlcGVuZGVuY3kKICB2YWx1ZXM6CiAgICAtIC4uL2dsb2JhbC55YW1sLmdvdG1wbCAgICAg" + + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIE11c3QgcmUtaW5jbHVk" + + "ZQogICAgLSAuLi88cHJpdmF0ZS12YWx1ZXM+L3t7IHJlcXVpcmVkRW52ICJIRUxNRklMRV9FTlYi" + + "IH19LXNlY3JldHMueWFtbCAgICMgTXVzdCByZS1pbmNsdWRlCiAgICAtIGNhc3NhbmRyYTogICAg" + + "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBZ" + + "b3VyIG92ZXJyaWRlCiAgICAgICAgcmVzb3VyY2VzOgogICAgICAgICAgbGltaXRzOgogICAgICAg" + + "ICAgICBtZW1vcnk6IDgxOTJNaQpgYGAKCiMjIyBgaW5oZXJpdGAgKEhlbG1maWxlIG5hdGl2ZSkK" + + "ClVzZWQgaW4gYDAyLWNvcmUueWFtbC5nb3RtcGxgLiBIZWxtZmlsZSdzIG5hdGl2ZSBpbmhlcml0" + + "YW5jZSBtZWNoYW5pc20uCgpgYGB5YW1sCi0gbmFtZTogYXBpCiAgdmVyc2lvbjogMS42LjAKICBu" + + "YW1lc3BhY2U6IG52Y2YKICBpbmhlcml0OgogICAgLSB0ZW1wbGF0ZTogc2VydmljZQpgYGAKCldo" + + "ZW4gYWRkaW5nIGB2YWx1ZXM6YCB0byBhbiBpbmhlcml0ZWQgcmVsZWFzZSwgeW91IGFsc28gbmVl" + + "ZCB0byByZS1pbmNsdWRlIHRoZSB0ZW1wbGF0ZSdzIHZhbHVlcyBmaWxlcyBzaW5jZSBgdmFsdWVz" + + "YCBpcyBhIGxpc3QgdGhhdCBnZXRzIHJlcGxhY2VkLgoKIyMgVmFsdWVzIFByZWNlZGVuY2UKCkZy" + + "b20gbG93ZXN0IHRvIGhpZ2hlc3QgcHJpb3JpdHk6CgoxLiBgZW52aXJvbm1lbnRzL2Jhc2UueWFt" + + "bGAgLS0gZGVmYXVsdHMgc2hhcmVkIGFjcm9zcyBhbGwgZW52aXJvbm1lbnRzCjIuIGBlbnZpcm9u" + + "bWVudHMvPGVudj4ueWFtbGAgLS0gcGVyLWVudmlyb25tZW50IG92ZXJyaWRlcwozLiBgZ2xvYmFs" + + "LnlhbWwuZ290bXBsYCAtLSBHbyB0ZW1wbGF0ZSBwcm9jZXNzaW5nIChjb25zdHJ1Y3RzIGNoYXJ0" + + "LXNwZWNpZmljIHN0cnVjdHVyZSkKNC4gYDxwcml2YXRlLXZhbHVlcz4vPGVudj4tc2VjcmV0cy55" + + "YW1sYCAtLSBzZW5zaXRpdmUgdmFsdWVzCjUuIElubGluZSBgdmFsdWVzOmAgYmxvY2tzIG9uIHJl" + + "bGVhc2VzIC0tIGhpZ2hlc3QgcHJlY2VkZW5jZQoKIyMgV2hhdCBnbG9iYWwueWFtbC5nb3RtcGwg" + + "UGFzc2VzIFRocm91Z2gKCmBnbG9iYWwueWFtbC5nb3RtcGxgIHJlYWRzIGZyb20gYC5WYWx1ZXNg" + + "ICh0aGUgbWVyZ2VkIGVudmlyb25tZW50ICsgZW52LXNwZWNpZmljIFlBTUwpIGFuZCBjb25zdHJ1" + + "Y3RzIGNoYXJ0LXNwZWNpZmljIHZhbHVlcy4gSXQgb25seSBwYXNzZXMgdGhyb3VnaCBrZXlzIGl0" + + "IGV4cGxpY2l0bHkgcmVmZXJlbmNlczoKCiMjIyBDYXNzYW5kcmEKCi0gYGNhc3NhbmRyYS5yZXBs" + + "aWNhQ291bnRgCi0gYGNhc3NhbmRyYS5pbWFnZS4qYCAocmVnaXN0cnksIHJlcG9zaXRvcnkpCi0g" + + "YGNhc3NhbmRyYS5taWdyYXRpb25zLmltYWdlLipgCi0gYGNhc3NhbmRyYS5wZXJzaXN0ZW5jZS5z" + + "aXplYAotIGBjYXNzYW5kcmEubm9kZVNlbGVjdG9yYCAoaWYgYGdsb2JhbC5ub2RlU2VsZWN0b3Jz" + + "LmVuYWJsZWRgKQotIGBjYXNzYW5kcmEuZ2xvYmFsLmRlZmF1bHRTdG9yYWdlQ2xhc3NgCgojIyMg" + + "TkFUUwoKLSBgbmF0cy5jb250YWluZXIuaW1hZ2UuKmAKLSBgbmF0cy5yZWxvYWRlci5pbWFnZS4q" + + "YAotIGBuYXRzLm5hdHNCb3guY29udGFpbmVyLmltYWdlLipgCi0gYG5hdHMuY29uZmlnLmpldHN0" + + "cmVhbS5maWxlU3RvcmUucHZjLnN0b3JhZ2VDbGFzc05hbWVgCi0gYG5hdHMucG9kVGVtcGxhdGUu" + + "bWVyZ2Uuc3BlYy5ub2RlU2VsZWN0b3JgIChpZiBlbmFibGVkKQoKIyMjIE9wZW5CYW8KCi0gYG9w" + + "ZW5iYW8ubWlncmF0aW9ucy5pbWFnZS4qYCBhbmQgYG9wZW5iYW8ubWlncmF0aW9ucy5lbnZgCi0g" + + "YG9wZW5iYW8uaW5qZWN0b3IuaW1hZ2UuKmAKLSBgb3BlbmJhby5zZXJ2ZXIuaW1hZ2UuKmAKLSBg" + + "b3BlbmJhby5zZXJ2ZXIuZGF0YVN0b3JhZ2UuKmAKLSBOb2RlIHNlbGVjdG9ycyAoaWYgZW5hYmxl" + + "ZCkKCiMjIyBTZXJ2aWNlcyAoQVBJLCBTSVMsIGV0Yy4pCgotIGA8c2VydmljZT4uaW1hZ2UuKmAg" + + "KHJlZ2lzdHJ5LCByZXBvc2l0b3J5KQotIGA8c2VydmljZT4ubm9kZVNlbGVjdG9yYCAoaWYgZW5h" + + "YmxlZCkKLSBgPHNlcnZpY2U+LmVudi4qYCAob2JzZXJ2YWJpbGl0eSBzZXR0aW5ncykKCiMjIyBM" + + "TE0gRnVuY3Rpb24gRW5hYmxlbWVudAoKRW5hYmxlIHRoZSBMTE0gYWRkb24gYmVmb3JlIGNyZWF0" + + "aW5nIG9yIGludm9raW5nIGZ1bmN0aW9ucyB3aXRoCmBmdW5jdGlvblR5cGU6ICJMTE0iYC4gVGhl" + + "IGFkZG9uIGRlcGxveXMgYGxsbS1yZXF1ZXN0LXJvdXRlcmAgYW5kCmBsbG0tYXBpLWdhdGV3YXlg" + + "LCBhZGRzIHRoZSBgbGxtLmludm9jYXRpb24uPGRvbWFpbj5gIHJvdXRlIHdoZW4gR2F0ZXdheSBB" + + "UEkKaW5ncmVzcyBpcyBlbmFibGVkLCBhbmQgY29uZmlndXJlcyB3b3JrZXJzIHRvIHVzZSB0aGUg" + + "TExNIHNpZGVjYXIuIFRoZSBIZWxtZmlsZQpjb25kaXRpb24gaXMgYGFkZG9ucy5sbG0uZW5hYmxl" + + "ZGAuIERvIG5vdCB1c2UgdGhlIG9ic29sZXRlIGBsbG0uZW5hYmxlZGAgcGF0aC4KCkZvciBhIHNp" + + "bmdsZS1ub2RlIGlzb2xhdGVkIHRlc3QgY2x1c3RlciwgYWRkIHRoaXMgY29uZmlndXJhdGlvbiB0" + + "bwpgZW52aXJvbm1lbnRzLzxlbnY+LnlhbWxgIGJlZm9yZSBhcHBseWluZyB0aGUgc3RhY2s6Cgpg" + + "YGB5YW1sCmFkZG9uczoKICBsbG06CiAgICBlbmFibGVkOiB0cnVlCiAgICBnYXRld2F5OgogICAg" + + "ICByZXBsaWNhQ291bnQ6IDEKICAgICAgbWV0cmljczoKICAgICAgICBzZXJ2aWNlTW9uaXRvcjoK" + + "ICAgICAgICAgIGVuYWJsZWQ6IGZhbHNlCiAgICByZXF1ZXN0Um91dGVyOgogICAgICByZXBsaWNh" + + "Q291bnQ6IDEKICAgICAgbWV0cmljczoKICAgICAgICBzZXJ2aWNlTW9uaXRvcjoKICAgICAgICAg" + + "IGVuYWJsZWQ6IGZhbHNlCgphZ2VudENvbmZpZzoKICBtZXJnZUNvbmZpZzogfAogICAgY2x1c3Rl" + + "cjoKICAgICAgdmFsaWRhdGlvblBvbGljeToKICAgICAgICBuYW1lOiBVbnJlc3RyaWN0ZWQKICAg" + + "IHdvcmtsb2FkOgogICAgICBzdGFyZ2F0ZVFVSUNJbnNlY3VyZTogdHJ1ZQpgYGAKClVzZSBgcmVw" + + "bGljYUNvdW50OiAxYCBvbmx5IGZvciBsb2NhbCBvciBzaW5nbGUtbm9kZSB0ZXN0IGNsdXN0ZXJz" + + "LiBUaGUgYnVuZGxlZApOVkNGIEFQSSBjdXJyZW50bHkgc2VydmVzIHBsYWludGV4dCBnUlBDIG9u" + + "IHBvcnQgOTA5MCwgc28gdGhlIHNlbGYtbWFuYWdlZCBzdGFjawpkZWZhdWx0cyB0aGUgTExNIEFQ" + + "SSBHYXRld2F5IGF1dGhlbnRpY2F0aW9uIGhvcCB0byBwbGFpbnRleHQuIFNldApgYWRkb25zLmxs" + + "bS5nYXRld2F5LmF1dGguZ3JwY0luc2VjdXJlOiBmYWxzZWAgb25seSB3aXRoIGEgVExTLWNhcGFi" + + "bGUgTlZDRiBBUEkuCgpXb3JrZXItdG8tcm91dGVyIHRyYW5zcG9ydCBpcyBzZXBhcmF0ZS4gRm9y" + + "IHNoYXJlZCBvciBwcm9kdWN0aW9uIGNsdXN0ZXJzLCB1c2UKdGhlIHJlcXVpcmVkIHJlcGxpY2Eg" + + "Y291bnQgYW5kIHRoZSBtYW5hZ2VkIFRMUyBjb25maWd1cmF0aW9uLiBUaGUKYHdvcmtsb2FkLnN0" + + "YXJnYXRlUVVJQ0luc2VjdXJlYCBzZXR0aW5nIGVuYWJsZXMgcGxhaW50ZXh0IHdvcmtlciB0cmFu" + + "c3BvcnQgYW5kCm11c3Qgbm90IGJlIHVzZWQgaW4gcHJvZHVjdGlvbi4KClRoZSByZXF1ZXN0IHJv" + + "dXRlciB1c2VzIGBwb3dlci1vZi10d29gIHdoZW4gbm8gbG9hZC1iYWxhbmNlciBjb25maWd1cmF0" + + "aW9uIGlzCnNldC4gQ29uZmlndXJlIG90aGVyIHJvdXRpbmcgbWV0aG9kcyB3aXRoIHRoZQpbTExN" + + "IFJlcXVlc3QgUm91dGVyIExvYWQgQmFsYW5jaW5nXShodHRwczovL2dpdGh1Yi5jb20vTlZJRElB" + + "L252Y2YvYmxvYi9tYWluL2RvY3Mvc2VsZi1tYW5hZ2VkL2xsbS1yZXF1ZXN0LXJvdXRlci1sb2Fk" + + "LWJhbGFuY2luZy5tZCkKZ3VpZGUuCgpJZiB0aGUgc2lkZWNhciBpbWFnZSBpcyBtaXJyb3JlZCBv" + + "dXRzaWRlIHRoZSBzdGFjaydzIGRlZmF1bHQgaW1hZ2UgcmVnaXN0cnkgYW5kCnJlcG9zaXRvcnks" + + "IHNldCB0aGUgZ2VuZXJhdGVkIHdvcmtlciBzaWRlY2FyIGltYWdlIGV4cGxpY2l0bHk6CgpgYGB5" + + "YW1sCmFwaToKICByZW1vdGVDb25maWc6CiAgICBjb25maWdEYXRhOgogICAgICBudmNmOgogICAg" + + "ICAgIHNpZGVjYXJzOgogICAgICAgICAgbGxtLXJvdXRlci1jbGllbnQtaW1hZ2U6IDxyZWdpc3Ry" + + "eT4vPHJlcG9zaXRvcnk+L3B5bG9uOjAuMTQuMQpgYGAKClRoZSBsZWdhY3kgYGFwaS5lbnYuTlZD" + + "Rl9TSURFQ0FSU19MTE1fUk9VVEVSX0NMSUVOVF9JTUFHRWAgcGF0aCBpcyBkZXByZWNhdGVkLgpU" + + "aGUgc3RhY2sgdHJhbnNsYXRlcyBpdCBmb3Igb25lIGNvbXBhdGliaWxpdHkgd2luZG93LCBidXQg" + + "bmV3IGNvbmZpZ3VyYXRpb25zCm11c3QgdXNlIHRoZSByZW1vdGUtY29uZmlnIHBhdGguIENvbmZs" + + "aWN0aW5nIHZhbHVlcyBmYWlsIHJlbmRlcmluZy4KClJlbmRlciBhbmQgYXBwbHkgdGhlIHVwZGF0" + + "ZWQgY29udHJvbC1wbGFuZSBlbnZpcm9ubWVudCwgdGhlbiByZWZyZXNoIGV2ZXJ5CnJlZ2lzdGVy" + + "ZWQgY29tcHV0ZSBwbGFuZSB1c2luZyB0aGUgc2FtZSBoYW5kb2ZmIHVzZWQgZm9yIGl0cyBpbnN0" + + "YWxsYXRpb24gc28KTlZDQSByZWNlaXZlcyBgYWdlbnRDb25maWcubWVyZ2VDb25maWdgOgoKYGBg" + + "YmFzaApIRUxNRklMRV9FTlY9PGVudj4gaGVsbWZpbGUgdGVtcGxhdGUKSEVMTUZJTEVfRU5WPTxl" + + "bnY+IGhlbG1maWxlIHN5bmMKYGBgCgpVc2UgdGhlIGNvbXBsZXRlIGNvbXB1dGUtcGxhbmUgaW5z" + + "dGFsbCBjb21tYW5kIGZyb20KW1NwbGl0IENvbXB1dGUtUGxhbmUgSW5zdGFsbGF0aW9uXShjb21w" + + "dXRlLXBsYW5lLWluc3RhbGxhdGlvbi5tZCkuIEEKQ0xJLXByb2ZpbGUgaW5zdGFsbGF0aW9uIG11" + + "c3QgcmVtYWluIHByb2ZpbGUtZHJpdmVuOyBhIEhlbG1maWxlIGluc3RhbGxhdGlvbgptdXN0IHJl" + + "bWFpbiB2YWx1ZXMtZHJpdmVuLgoKRXhpc3RpbmcgTExNIGZ1bmN0aW9uIHZlcnNpb25zIHJldGFp" + + "biB0aGUgd29ya2VyLXNpZGVjYXIgaW1hZ2UgbWV0YWRhdGEKY2FwdHVyZWQgd2hlbiB0aGUgdmVy" + + "c2lvbiBpcyBjcmVhdGVkLiBSZXBsYWNpbmcgcG9kcyBvciByZWRlcGxveWluZyB0aGUgc2FtZQp2" + + "ZXJzaW9uIGRvZXMgbm90IGFwcGx5IGEgbmV3IFB5bG9uIGltYWdlLiBBZnRlciB0aGUgY29udHJv" + + "bC1wbGFuZSB1cGRhdGUsCmNyZWF0ZSBhbmQgZGVwbG95IGEgbmV3IGZ1bmN0aW9uIHZlcnNpb24u" + + "IFZlcmlmeSB0aGUgY29udHJvbCBwbGFuZSwgcm91dGUsIGFuZAp3b3JrZXIgc2lkZWNhcjoKCmBg" + + "YGJhc2gKa3ViZWN0bCBnZXQgZGVwbG95IC1uIG52Y2YgbGxtLWFwaS1nYXRld2F5Cmt1YmVjdGwg" + + "Z2V0IHN0YXRlZnVsc2V0IC1uIG52Y2YgbGxtLXJlcXVlc3Qtcm91dGVyCmt1YmVjdGwgZ2V0IHBv" + + "ZHMgLW4gbnZjZiB8IGdyZXAgLUUgJ2xsbS1hcGktZ2F0ZXdheXxsbG0tcmVxdWVzdC1yb3V0ZXIn" + + "Cmt1YmVjdGwgZ2V0IGh0dHByb3V0ZSAtQSB8IGdyZXAgbGxtCmt1YmVjdGwgLW4gbnZjZi1iYWNr" + + "ZW5kIGdldCBwb2QgPGZ1bmN0aW9uLXBvZD4gXAogIC1vIGpzb25wYXRoPSd7cmFuZ2UgLnNwZWMu" + + "Y29udGFpbmVyc1s/KEAubmFtZT09ImxsbS13b3JrZXIiKV0uYXJnc1sqXX17Ln17IlxcbiJ9e2Vu" + + "ZH0nCmBgYAoKRm9yIGxvY2FsIHBsYWludGV4dCBjbHVzdGVycywgdGhlIGBsbG0td29ya2VyYCBh" + + "cmdzIG11c3QgaW5jbHVkZQpgLS1xdWljLWluc2VjdXJlYC4KCkZvciBsb2FkLWJhbGFuY2VyIHNj" + + "aGVtYSBhbmQgYWxnb3JpdGhtIGJlaGF2aW9yLCBzZWUgdGhlCltTdGFyZ2F0ZSBjb25maWd1cmF0" + + "aW9uIGd1aWRlXShodHRwczovL2dpdGh1Yi5jb20vTlZJRElBL252Y2YvYmxvYi9tYWluL3NyYy9s" + + "aWJyYXJpZXMvcnVzdC9zdGFyZ2F0ZS9kb2NzL2xvYWQtYmFsYW5jZXItY29uZmlndXJhdGlvbi5t" + + "ZCkuCgojIyBIZWxtZmlsZSBTZWxlY3RvcnMKClRhcmdldCBzcGVjaWZpYyByZWxlYXNlcyBvciBn" + + "cm91cHM6CgpgYGBiYXNoCiMgQnkgcmVsZWFzZSBncm91cApIRUxNRklMRV9FTlY9PGVudj4gaGVs" + + "bWZpbGUgLS1zZWxlY3RvciByZWxlYXNlLWdyb3VwPWRlcGVuZGVuY2llcyBzeW5jCkhFTE1GSUxF" + + "X0VOVj08ZW52PiBoZWxtZmlsZSAtLXNlbGVjdG9yIHJlbGVhc2UtZ3JvdXA9c2VydmljZXMgc3lu" + + "YwpIRUxNRklMRV9FTlY9PGVudj4gaGVsbWZpbGUgLS1zZWxlY3RvciByZWxlYXNlLWdyb3VwPWlu" + + "Z3Jlc3Mgc3luYwpIRUxNRklMRV9FTlY9PGVudj4gaGVsbWZpbGUgLS1zZWxlY3RvciByZWxlYXNl" + + "LWdyb3VwPW9ic2VydmFiaWxpdHkgc3luYwoKIyBCeSByZWxlYXNlIG5hbWUKSEVMTUZJTEVfRU5W" + + "PTxlbnY+IGhlbG1maWxlIC0tc2VsZWN0b3IgbmFtZT1jYXNzYW5kcmEgc3luYwpIRUxNRklMRV9F" + + "TlY9PGVudj4gaGVsbWZpbGUgLS1zZWxlY3RvciBuYW1lPWFkbWluLWlzc3Vlci1wcm94eSBzeW5j" + + "CkhFTE1GSUxFX0VOVj08ZW52PiBoZWxtZmlsZSAtLXNlbGVjdG9yIG5hbWU9bGxtLXJlcXVlc3Qt" + + "cm91dGVyIHN5bmMKSEVMTUZJTEVfRU5WPTxlbnY+IGhlbG1maWxlIC0tc2VsZWN0b3IgbmFtZT1s" + + "bG0tYXBpLWdhdGV3YXkgc3luYwoKIyBUZW1wbGF0ZSBvbmx5IChkcnkgcnVuKQpIRUxNRklMRV9F" + + "TlY9PGVudj4gaGVsbWZpbGUgLS1zZWxlY3RvciBuYW1lPWNhc3NhbmRyYSB0ZW1wbGF0ZQoKIyBE" + + "ZXN0cm95IGEgc2luZ2xlIHJlbGVhc2UKSEVMTUZJTEVfRU5WPTxlbnY+IGhlbG1maWxlIC0tc2Vs" + + "ZWN0b3IgbmFtZT1jYXNzYW5kcmEgZGVzdHJveQpgYGAKCk5vdGU6IGBhZG1pbi1pc3N1ZXItcHJv" + + "eHlgIGhhcyBubyBgcmVsZWFzZS1ncm91cGAgbGFiZWwuIFVzZSBgLS1zZWxlY3RvciBuYW1lPWFk" + + "bWluLWlzc3Vlci1wcm94eWAuClBLBwgTEeyNCioAAAoqAABQSwMEFAAIAAAAAAAhAAAAAAAAAAAA" + + "AAAAAD8ACQBkYXRhL252Y2Ytc2VsZi1tYW5hZ2VkLWluc3RhbGxhdGlvbi9yZWZlcmVuY2VzL252" + + "Y2YtdWktYWRkb24ubWRVVAUAAQCmzhIjIE5WQ0YgVUkgQWRkb24KCk5WQ0YgVUkgaXMgYW4gb3B0" + + "aW9uYWwsIGN1c3RvbWVyLWZhY2luZyBOVkNGIGFkbWluLXBhbmVsIFVJLiBJdCBpcyBkaXNhYmxl" + + "ZCBieQpkZWZhdWx0IGFuZCBhdmFpbGFibGUgb25seSBpbiBzdGFjayBwYWNrYWdlcyB0aGF0IGlu" + + "Y2x1ZGUgdGhlIE5WQ0YgVUkgYWRkb24uCgojIyBXaGVuIHRvIGVuYWJsZQoKRW5hYmxlIE5WQ0Yg" + + "VUkgb25seSB3aGVuIHlvdSBuZWVkIHRoZSBhZG1pbi1wYW5lbCBVSSBhbmQgeW91ciBleHRyYWN0" + + "ZWQgc3RhY2sKcGFja2FnZSBpbmNsdWRlcyB0aGUgYWRkb24uIElmIHRoZSBwYWNrYWdlIGRvZXMg" + + "bm90IGNvbnRhaW4gYSBgbnZjZi11aWAgcmVsZWFzZQphbmQgYG52Y2ZVaWAgcm91dGUgdmFsdWVz" + + "LCBza2lwIHRoZSBhZGRvbiB1bnRpbCB5b3UgdXNlIGEgcGFja2FnZSB0aGF0IGluY2x1ZGVzCnRo" + + "ZW0uCgojIyBFbmFibGUgdGhlIGFkZG9uCgpFbmFibGUgaXQgaW4gYGVudmlyb25tZW50cy88ZW52" + + "LW5hbWU+LnlhbWxgOgoKYGBgeWFtbAphZGRvbnM6CiAgbnZjZlVpOgogICAgZW5hYmxlZDogdHJ1" + + "ZQpgYGAKCiMjIENyZWF0ZSBuYW1lc3BhY2UgYW5kIGltYWdlIHB1bGwgc2VjcmV0CgpDcmVhdGUg" + + "dGhlIG5hbWVzcGFjZSwgYW5kIGFuIGltYWdlIHB1bGwgc2VjcmV0IG9ubHkgaWYgcHVsbGluZyBm" + + "cm9tIGEgcHJpdmF0ZQpyZWdpc3RyeToKCmBgYGJhc2gKa3ViZWN0bCBjcmVhdGUgbmFtZXNwYWNl" + + "IG52Y2YtdWkgLS1kcnktcnVuPWNsaWVudCAtbyB5YW1sIHwga3ViZWN0bCBhcHBseSAtZiAtCgoj" + + "IE9ubHkgaWYgcHVsbGluZyBmcm9tIGEgcHJpdmF0ZSByZWdpc3RyeSAoZS5nLiwgTkdDIG52Y3Iu" + + "aW8pCmV4cG9ydCBOR0NfQVBJX0tFWT0iPHlvdXIta2V5PiIKa3ViZWN0bCBjcmVhdGUgc2VjcmV0" + + "IGRvY2tlci1yZWdpc3RyeSBudmNyLWNyZWRzIFwKICAtLWRvY2tlci1zZXJ2ZXI9bnZjci5pbyBc" + + "CiAgLS1kb2NrZXItdXNlcm5hbWU9JyRvYXV0aHRva2VuJyBcCiAgLS1kb2NrZXItcGFzc3dvcmQ9" + + "IiROR0NfQVBJX0tFWSIgXAogIC0tbmFtZXNwYWNlPW52Y2YtdWkgXAogIC0tZHJ5LXJ1bj1jbGll" + + "bnQgLW8geWFtbCB8IGt1YmVjdGwgYXBwbHkgLWYgLQpgYGAKClRoZSBkZWZhdWx0IHJvdXRlIGhv" + + "c3QgaXMgYG52Y2YtdWkuPGRvbWFpbj5gIGFuZCB0aGUgYmFja2VuZCBpcwpgbnZjZi11aS5udmNm" + + "LXVpOjgzMDBgLgoKIyMgQXBwbHkgdGhlIHNlcnZpY2UgYW5kIHJvdXRlCgpBZnRlciBjb25maXJt" + + "aW5nIHRoZSBwYWNrYWdlIGluY2x1ZGVzIHRoZSBgbnZjZi11aWAgcmVsZWFzZSwgcHJldmlldyBh" + + "bmQgYXBwbHkKdGhlIHNlcnZpY2UgYW5kIHJvdXRlOgoKYGBgYmFzaApIRUxNRklMRV9FTlY9PGVu" + + "di1uYW1lPiBoZWxtZmlsZSAtLXNlbGVjdG9yIG5hbWU9bnZjZi11aSB0ZW1wbGF0ZQpIRUxNRklM" + + "RV9FTlY9PGVudi1uYW1lPiBoZWxtZmlsZSAtLXNlbGVjdG9yIG5hbWU9bnZjZi11aSBzeW5jCkhF" + + "TE1GSUxFX0VOVj08ZW52LW5hbWU+IGhlbG1maWxlIC0tc2VsZWN0b3IgcmVsZWFzZS1ncm91cD1p" + + "bmdyZXNzIHN5bmMKYGBgCgojIyBWZXJpZnkKClZlcmlmeSB0aGUgcm91dGUgb25seSB3aGVuIHRo" + + "ZSBhZGRvbiBpcyBwcmVzZW50IGFuZCBlbmFibGVkOgoKYGBgYmFzaAprdWJlY3RsIGdldCBkZXBs" + + "b3ksc3ZjIC1uIG52Y2YtdWkKa3ViZWN0bCBnZXQgaHR0cHJvdXRlIC1BIHwgZ3JlcCAtaSBudmNm" + + "LXVpCmN1cmwgLWkgLUggIkhvc3Q6IG52Y2YtdWkuPGRvbWFpbj4iICJodHRwOi8vPGdhdGV3YXkt" + + "YWRkcmVzcz4vc3RhdHVzIgpgYGAKCiMjIFNlY3VyaXR5CgpUaGUgTlZDRiBVSSBhZG1pbiBwYW5l" + + "bCBpcyBjdXJyZW50bHkgdW5hdXRoZW50aWNhdGVkLiBEbyBub3QgZXhwb3NlIGl0IHRvIHRoZQpw" + + "dWJsaWMgaW50ZXJuZXQuIFJlc3RyaWN0IGFjY2VzcyB0byBhIHRydXN0ZWQgbmV0d29yaywgVlBO" + + "LCBvciBhbiBhdXRoZW50aWNhdGluZwpwcm94eSBpbiBmcm9udCBvZiB0aGUgYG52Y2YtdWlgIHJv" + + "dXRlLgpQSwcIOPknr7kHAAC5BwAAUEsDBBQACAAAAAAAIQAAAAAAAAAAAAAAAAA+AAkAZGF0YS9u" + + "dmNmLXNlbGYtbWFuYWdlZC1pbnN0YWxsYXRpb24vcmVmZXJlbmNlcy9wdWxsLXNlY3JldHMubWRV" + + "VAUAAQCmzhIjIEltYWdlIFB1bGwgU2VjcmV0cyBSZWZlcmVuY2UKCiMjIE92ZXJ2aWV3CgpXaGVu" + + "IHB1bGxpbmcgTlZDRiBpbWFnZXMgZnJvbSBhIHByaXZhdGUgcmVnaXN0cnkgKGZvciBleGFtcGxl" + + "LCBOR0MKYG52Y3IuaW9gKSwgS3ViZXJuZXRlcyBuZWVkcyBgaW1hZ2VQdWxsU2VjcmV0c2Agb24g" + + "ZXZlcnkgcG9kLiBDb250cm9sLXBsYW5lIGFuZApjb21wdXRlLXBsYW5lIGNyZWRlbnRpYWxzIGFy" + + "ZSBjb25maWd1cmVkIGluIHRoZWlyIG93biBjbHVzdGVyIGNvbnRleHRzLiBEbyBub3QKdXNlIG9u" + + "ZSBtaXhlZCBuYW1lc3BhY2UgbGlzdC4KClRoZSByZWNvbW1lbmRlZCBjb250cm9sLXBsYW5lIGFw" + + "cHJvYWNoIHVzZXMgS3l2ZXJubyB0byBpbmplY3QgdGhlIHNlY3JldCBpbnRvCmNvbnRyb2wtcGxh" + + "bmUgcG9kcyBhdCBhZG1pc3Npb24gdGltZS4gVGhlIGNvbXB1dGUtcGxhbmUgY2hhcnQgYW5kIG9w" + + "ZXJhdG9yIHVzZQp0aGUgc2VjcmV0IGluIGBudmNhLW9wZXJhdG9yYCBhbmQgbWlycm9yIGl0IHRv" + + "IGBudmNhLXN5c3RlbWAuCgojIyBLeXZlcm5vIEFwcHJvYWNoIChSZWNvbW1lbmRlZCkKCiMjIyAx" + + "LiBJbnN0YWxsIEt5dmVybm8KCmBgYGJhc2gKaGVsbSByZXBvIGFkZCBreXZlcm5vIGh0dHBzOi8v" + + "a3l2ZXJuby5naXRodWIuaW8va3l2ZXJuby8KaGVsbSByZXBvIHVwZGF0ZQpoZWxtIGluc3RhbGwg" + + "a3l2ZXJubyBreXZlcm5vL2t5dmVybm8gLW4ga3l2ZXJubyAtLWNyZWF0ZS1uYW1lc3BhY2UgXAog" + + "IC0ta3ViZS1jb250ZXh0IDxjb250cm9sLXBsYW5lLWNvbnRleHQ+CmBgYAoKIyMjIDIuIENyZWF0" + + "ZSBjb250cm9sLXBsYW5lIHB1bGwgc2VjcmV0cwoKYGBgYmFzaApleHBvcnQgTkdDX0FQSV9LRVk9" + + "Ijx5b3VyLW5nYy1hcGkta2V5PiIKCmZvciBucyBpbiBjYXNzYW5kcmEtc3lzdGVtIG5hdHMtc3lz" + + "dGVtIG52Y2YgYXBpLWtleXMgZXNzIHNpcyB2YXVsdC1zeXN0ZW0gY2VydC1tYW5hZ2VyOyBkbwog" + + "IGt1YmVjdGwgLS1jb250ZXh0IDxjb250cm9sLXBsYW5lLWNvbnRleHQ+IGNyZWF0ZSBuYW1lc3Bh" + + "Y2UgIiRucyIgXAogICAgLS1kcnktcnVuPWNsaWVudCAtbyB5YW1sIHwga3ViZWN0bCAtLWNvbnRl" + + "eHQgPGNvbnRyb2wtcGxhbmUtY29udGV4dD4gYXBwbHkgLWYgLQpkb25lCgpmb3IgbnMgaW4gY2Fz" + + "c2FuZHJhLXN5c3RlbSBuYXRzLXN5c3RlbSBudmNmIGFwaS1rZXlzIGVzcyBzaXMgdmF1bHQtc3lz" + + "dGVtIGNlcnQtbWFuYWdlcjsgZG8KICBrdWJlY3RsIC0tY29udGV4dCA8Y29udHJvbC1wbGFuZS1j" + + "b250ZXh0PiBjcmVhdGUgc2VjcmV0IGRvY2tlci1yZWdpc3RyeSBudmNyLWNyZWRzIFwKICAgIC0t" + + "ZG9ja2VyLXNlcnZlcj1udmNyLmlvIFwKICAgIC0tZG9ja2VyLXVzZXJuYW1lPSckb2F1dGh0b2tl" + + "bicgXAogICAgLS1kb2NrZXItcGFzc3dvcmQ9IiROR0NfQVBJX0tFWSIgXAogICAgLS1uYW1lc3Bh" + + "Y2U9IiRucyIgXAogICAgLS1kcnktcnVuPWNsaWVudCAtbyB5YW1sIHwga3ViZWN0bCAtLWNvbnRl" + + "eHQgPGNvbnRyb2wtcGxhbmUtY29udGV4dD4gYXBwbHkgLWYgLQpkb25lCmBgYAoKRm9yIG5vbi1O" + + "R0MgcmVnaXN0cmllcywgcmVwbGFjZSBgLS1kb2NrZXItc2VydmVyYCwgYC0tZG9ja2VyLXVzZXJu" + + "YW1lYCwgYW5kIGAtLWRvY2tlci1wYXNzd29yZGAuCgojIyMgMy4gQXBwbHkgS3l2ZXJubyBDbHVz" + + "dGVyUG9saWN5CgpgYGB5YW1sCmFwaVZlcnNpb246IGt5dmVybm8uaW8vdjEKa2luZDogQ2x1c3Rl" + + "clBvbGljeQptZXRhZGF0YToKICBuYW1lOiBudmNmLWFkZC1pbWFnZXB1bGxzZWNyZXRzCnNwZWM6" + + "CiAgYmFja2dyb3VuZDogZmFsc2UKICBydWxlczoKICAgIC0gbmFtZTogYWRkLWltYWdlcHVsbHNl" + + "Y3JldC10by1udmNmLXBvZHMKICAgICAgbWF0Y2g6CiAgICAgICAgYW55OgogICAgICAgIC0gcmVz" + + "b3VyY2VzOgogICAgICAgICAgICBraW5kczoKICAgICAgICAgICAgLSBQb2QKICAgICAgICAgICAg" + + "bmFtZXNwYWNlczoKICAgICAgICAgICAgLSAibnZjZiIKICAgICAgICAgICAgLSAiYXBpLWtleXMi" + + "CiAgICAgICAgICAgIC0gInNpcyIKICAgICAgICAgICAgLSAiZXNzIgogICAgICAgICAgICAtICJu" + + "YXRzLXN5c3RlbSIKICAgICAgICAgICAgLSAiY2Fzc2FuZHJhLXN5c3RlbSIKICAgICAgICAgICAg" + + "LSAidmF1bHQtc3lzdGVtIgogICAgICAgICAgICAtICJjZXJ0LW1hbmFnZXIiCiAgICAgIG11dGF0" + + "ZToKICAgICAgICBwYXRjaFN0cmF0ZWdpY01lcmdlOgogICAgICAgICAgbWV0YWRhdGE6CiAgICAg" + + "ICAgICAgIGFubm90YXRpb25zOgogICAgICAgICAgICAgIG52Y2YubnZpZGlhLmNvbS9pbWFnZXB1" + + "bGxzZWNyZXQtaW5qZWN0ZWQtYnk6IGt5dmVybm8KICAgICAgICAgIHNwZWM6CiAgICAgICAgICAg" + + "IGltYWdlUHVsbFNlY3JldHM6CiAgICAgICAgICAgIC0gbmFtZTogbnZjci1jcmVkcwpgYGAKCmBg" + + "YGJhc2gKa3ViZWN0bCAtLWNvbnRleHQgPGNvbnRyb2wtcGxhbmUtY29udGV4dD4gYXBwbHkgXAog" + + "IC1mIGt5dmVybm8taW1hZ2VwdWxsc2VjcmV0LXBvbGljeS55YW1sCmBgYAoKIyMjIDQuIEluc3Rh" + + "bGwgdGhlIGNvbnRyb2wgcGxhbmUKCkNvbnRpbnVlIHdpdGggdGhlIHNlbGVjdGVkIGNvbnRyb2wt" + + "cGxhbmUgaW5zdGFsbGF0aW9uIGluCltTcGxpdCBDb21wdXRlLVBsYW5lIEluc3RhbGxhdGlvbl0o" + + "Y29tcHV0ZS1wbGFuZS1pbnN0YWxsYXRpb24ubWQpLiBObwpjb250cm9sLXBsYW5lIGhlbG1maWxl" + + "IG1vZGlmaWNhdGlvbnMgYXJlIG5lZWRlZC4gS3l2ZXJubyBpbmplY3RzIHRoZSBwdWxsCnNlY3Jl" + + "dCBpbnRvIG5ldyBjb250cm9sLXBsYW5lIHBvZHMuCgojIyBDb21wdXRlLVBsYW5lIFNlY3JldAoK" + + "Q3JlYXRlIHRoZSBzZWNyZXQgb25seSBpbiBgbnZjYS1vcGVyYXRvcmAgb24gdGhlIGNvbXB1dGUg" + + "Y2x1c3RlcjoKCmBgYGJhc2gKa3ViZWN0bCAtLWNvbnRleHQgPGNvbXB1dGUtcGxhbmUtY29udGV4" + + "dD4gY3JlYXRlIG5hbWVzcGFjZSBudmNhLW9wZXJhdG9yIFwKICAtLWRyeS1ydW49Y2xpZW50IC1v" + + "IHlhbWwgfCBcCiAga3ViZWN0bCAtLWNvbnRleHQgPGNvbXB1dGUtcGxhbmUtY29udGV4dD4gYXBw" + + "bHkgLWYgLQoKa3ViZWN0bCAtLWNvbnRleHQgPGNvbXB1dGUtcGxhbmUtY29udGV4dD4gY3JlYXRl" + + "IHNlY3JldCBkb2NrZXItcmVnaXN0cnkgbnZjci1jcmVkcyBcCiAgLS1kb2NrZXItc2VydmVyPW52" + + "Y3IuaW8gXAogIC0tZG9ja2VyLXVzZXJuYW1lPSckb2F1dGh0b2tlbicgXAogIC0tZG9ja2VyLXBh" + + "c3N3b3JkPSIkTkdDX0FQSV9LRVkiIFwKICAtLW5hbWVzcGFjZT1udmNhLW9wZXJhdG9yIFwKICAt" + + "LWRyeS1ydW49Y2xpZW50IC1vIHlhbWwgfCBcCiAga3ViZWN0bCAtLWNvbnRleHQgPGNvbXB1dGUt" + + "cGxhbmUtY29udGV4dD4gYXBwbHkgLWYgLQpgYGAKClJlZmVyZW5jZSBpdCBpbiB0aGUgY29tcHV0" + + "ZS1wbGFuZSBlbnZpcm9ubWVudCBmaWxlOgoKYGBgeWFtbApnbG9iYWw6CiAgaW1hZ2VQdWxsU2Vj" + + "cmV0czoKICAgIC0gbmFtZTogbnZjci1jcmVkcwpgYGAKClRoZSBjaGFydCBhbmQgb3BlcmF0b3Ig" + + "dXNlIHRoYXQgcmVmZXJlbmNlIGluIGBudmNhLW9wZXJhdG9yYCBhbmQgbWlycm9yIHRoZQpzZWNy" + + "ZXQgdG8gYG52Y2Etc3lzdGVtYC4gRG8gbm90IGNyZWF0ZSBhIGJyb2FkZXIgbWFudWFsIGNvbXB1" + + "dGUgbmFtZXNwYWNlIGxpc3QuIEZvbGxvdwpbU3BsaXQgQ29tcHV0ZS1QbGFuZSBJbnN0YWxsYXRp" + + "b25dKGNvbXB1dGUtcGxhbmUtaW5zdGFsbGF0aW9uLm1kKSBmb3IgdGhlCnNlbGVjdGVkIGluc3Rh" + + "bGwgZmxvdy4KCiMjIFZlcmlmaWNhdGlvbgoKYGBgYmFzaAojIENoZWNrIHRoYXQgYSBjb250cm9s" + + "LXBsYW5lIHBvZCBoYXMgdGhlIGluamVjdGVkIHNlY3JldAprdWJlY3RsIC0tY29udGV4dCA8Y29u" + + "dHJvbC1wbGFuZS1jb250ZXh0PiBnZXQgcG9kIC1uIDxuYW1lc3BhY2U+IDxwb2QtbmFtZT4gXAog" + + "IC1vIGpzb25wYXRoPSd7LnNwZWMuaW1hZ2VQdWxsU2VjcmV0c30nCiMgRXhwZWN0ZWQ6IFt7Im5h" + + "bWUiOiJudmNyLWNyZWRzIn1dCgojIENoZWNrIHRoZSBLeXZlcm5vIGFubm90YXRpb24Ka3ViZWN0" + + "bCAtLWNvbnRleHQgPGNvbnRyb2wtcGxhbmUtY29udGV4dD4gZ2V0IHBvZCAtbiA8bmFtZXNwYWNl" + + "PiA8cG9kLW5hbWU+IFwKICAtbyBqc29ucGF0aD0ney5tZXRhZGF0YS5hbm5vdGF0aW9ucy5udmNm" + + "XC5udmlkaWFcLmNvbS9pbWFnZXB1bGxzZWNyZXQtaW5qZWN0ZWQtYnl9JwojIEV4cGVjdGVkOiBr" + + "eXZlcm5vCgojIENoZWNrIHB1bGwgZXZlbnRzCmt1YmVjdGwgLS1jb250ZXh0IDxjb250cm9sLXBs" + + "YW5lLWNvbnRleHQ+IGdldCBldmVudHMgLW4gPG5hbWVzcGFjZT4gXAogIC0tc29ydC1ieT0nLmxh" + + "c3RUaW1lc3RhbXAnIHwgZ3JlcCAtaSBwdWxsCiMgU2hvdWxkIHNob3cgIlN1Y2Nlc3NmdWxseSBw" + + "dWxsZWQiIG5vdCAiNDAxIFVuYXV0aG9yaXplZCIKCiMgQ2hlY2sgdGhlIGNoYXJ0L29wZXJhdG9y" + + "IG1pcnJvciBvbiB0aGUgY29tcHV0ZSBjbHVzdGVyCmt1YmVjdGwgLS1jb250ZXh0IDxjb21wdXRl" + + "LXBsYW5lLWNvbnRleHQ+IGdldCBzZWNyZXQgbnZjci1jcmVkcyBcCiAgLW4gbnZjYS1zeXN0ZW0K" + + "YGBgCgojIyBXaGVuIFB1bGwgU2VjcmV0cyBBcmUgTm90IE5lZWRlZAoKLSAqKkFXUyBFQ1Igd2l0" + + "aCBJQU0gbm9kZSByb2xlcyoqOiBJZiB5b3VyIG5vZGVzIGhhdmUgYEFtYXpvbkVDMkNvbnRhaW5l" + + "clJlZ2lzdHJ5UmVhZE9ubHlgIElBTSBwb2xpY3ksIEt1YmVybmV0ZXMgY2FuIHB1bGwgZnJvbSBF" + + "Q1Igd2l0aG91dCBleHBsaWNpdCBzZWNyZXRzLgotICoqUHVibGljIHJlZ2lzdHJpZXMqKjogTm8g" + + "cHVsbCBzZWNyZXRzIG5lZWRlZC4KLSAqKkNTUCBidWlsdC1pbiBjcmVkZW50aWFsIGhlbHBlcnMq" + + "KjogR0tFIEFydGlmYWN0IFJlZ2lzdHJ5LCBBenVyZSBBQ1Igd2l0aCBtYW5hZ2VkIGlkZW50aXR5" + + "LCBhbmQgc2ltaWxhciBzZXJ2aWNlcy4KCiMjIFRyb3VibGVzaG9vdGluZwoKIyMjIFBvZHMgc3Rp" + + "bGwgaW4gSW1hZ2VQdWxsQmFja09mZiBhZnRlciBhcHBseWluZyBwb2xpY3kKClRoZSBLeXZlcm5v" + + "IHBvbGljeSBvbmx5IGFmZmVjdHMgcG9kcyBjcmVhdGVkICoqYWZ0ZXIqKiB0aGUgcG9saWN5IGlz" + + "IGFwcGxpZWQuIERlbGV0ZSBzdHVjayBwb2RzIHRvIHRyaWdnZXIgcmVjcmVhdGlvbjoKCmBgYGJh" + + "c2gKa3ViZWN0bCAtLWNvbnRleHQgPHRhcmdldC1jb250ZXh0PiBkZWxldGUgcG9kcyAtbiA8bmFt" + + "ZXNwYWNlPiAtLWFsbApgYGAKCiMjIyBLeXZlcm5vIGFkbWlzc2lvbiBjb250cm9sbGVyIG5vdCBy" + + "dW5uaW5nCgpgYGBiYXNoCmt1YmVjdGwgLS1jb250ZXh0IDxjb250cm9sLXBsYW5lLWNvbnRleHQ+" + + "IGdldCBwb2RzIC1uIGt5dmVybm8KIyBBbGwgcG9kcyBzaG91bGQgYmUgUnVubmluZwpgYGAKCiMj" + + "IyBQb2xpY3kgbm90IG1hdGNoaW5nCgpgYGBiYXNoCmt1YmVjdGwgLS1jb250ZXh0IDxjb250cm9s" + + "LXBsYW5lLWNvbnRleHQ+IGdldCBjbHVzdGVycG9saWN5IG52Y2YtYWRkLWltYWdlcHVsbHNlY3Jl" + + "dHMKIyBTaG91bGQgc2hvdyBSRUFEWTogVHJ1ZQpgYGAKCiMjIyBXcm9uZyBzZWNyZXQgbmFtZQoK" + + "VGhlIHBvbGljeSByZWZlcmVuY2VzIGBudmNyLWNyZWRzYC4gVmVyaWZ5IHRoZSBzZWNyZXQgZXhp" + + "c3RzIHdpdGggdGhhdCBleGFjdCBuYW1lOgoKYGBgYmFzaAprdWJlY3RsIC0tY29udGV4dCA8dGFy" + + "Z2V0LWNvbnRleHQ+IGdldCBzZWNyZXQgbnZjci1jcmVkcyAtbiA8bmFtZXNwYWNlPgpgYGAKUEsH" + + "CEtCNagAFgAAABYAAFBLAQIUABQACAAAAAAAIQD3ALZAShAAAEoQAAASAAkAAAAAAAAAAAAAAAAA" + + "AABkYXRhL21hbmlmZXN0Lmpzb25VVAUAAQCmzhJQSwECFAAUAAgAAAAAACEA/3bzZ1NhAABTYQAA" + + "IwAJAAAAAAAAAAAAAACTEAAAZGF0YS9udmNmLXNlbGYtbWFuYWdlZC1jbGkvU0tJTEwubWRVVAUA" + + "AQCmzhJQSwECFAAUAAgAAAAAACEAFJvuWtoNAADaDQAAMwAJAAAAAAAAAAAAAABAcgAAZGF0YS9u" + + "dmNmLXNlbGYtbWFuYWdlZC1jbGkvZXhhbXBsZXMvY2ktcGlwZWxpbmVzLm1kVVQFAAEAps4SUEsB" + + "AhQAFAAIAAAAAAAhAKEU44FdDAAAXQwAADQACQAAAAAAAAAAAAAAhIAAAGRhdGEvbnZjZi1zZWxm" + + "LW1hbmFnZWQtY2xpL2V4YW1wbGVzL211bHRpLWNsdXN0ZXIubWRVVAUAAQCmzhJQSwECFAAUAAgA" + + "AAAAACEAQidJDwUNAAAFDQAANwAJAAAAAAAAAAAAAABMjQAAZGF0YS9udmNmLXNlbGYtbWFuYWdl" + + "ZC1jbGkvcHJvbXB0cy9hZGQtY29tcHV0ZS1wbGFuZS5tZFVUBQABAKbOElBLAQIUABQACAAAAAAA" + + "IQDU3o00nxkAAJ8ZAAA5AAkAAAAAAAAAAAAAAL+aAABkYXRhL252Y2Ytc2VsZi1tYW5hZ2VkLWNs" + + "aS9wcm9tcHRzL2NyZWF0ZS1hbmQtcnVuLXRhc2subWRVVAUAAQCmzhJQSwECFAAUAAgAAAAAACEA" + + "0kPcBBglAAAYJQAANwAJAAAAAAAAAAAAAADOtAAAZGF0YS9udmNmLXNlbGYtbWFuYWdlZC1jbGkv" + + "cHJvbXB0cy9kZXBsb3ktYW5kLWludm9rZS5tZFVUBQABAKbOElBLAQIUABQACAAAAAAAIQBykiwm" + + "vg4AAL4OAAA9AAkAAAAAAAAAAAAAAFTaAABkYXRhL252Y2Ytc2VsZi1tYW5hZ2VkLWNsaS9wcm9t" + + "cHRzL2RpYWdub3NlLWZhaWxlZC1pbnN0YWxsLm1kVVQFAAEAps4SUEsBAhQAFAAIAAAAAAAhAEJx" + + "5h4BKAAAASgAADoACQAAAAAAAAAAAAAAhukAAGRhdGEvbnZjZi1zZWxmLW1hbmFnZWQtY2xpL3By" + + "b21wdHMvaW5zdGFsbC1mcm9tLXNjcmF0Y2gubWRVVAUAAQCmzhJQSwECFAAUAAgAAAAAACEAquE6" + + "ucgHAADIBwAAOQAJAAAAAAAAAAAAAAD4EQEAZGF0YS9udmNmLXNlbGYtbWFuYWdlZC1jbGkvcHJv" + + "bXB0cy9yb3RhdGUtY2x1c3Rlci1qd2tzLm1kVVQFAAEAps4SUEsBAhQAFAAIAAAAAAAhANqHY1bk" + + "IQAA5CEAAC4ACQAAAAAAAAAAAAAAMBoBAGRhdGEvbnZjZi1zZWxmLW1hbmFnZWQtY2xpL3Byb21w" + + "dHMvdGVhcmRvd24ubWRVVAUAAQCmzhJQSwECFAAUAAgAAAAAACEAcg0PZCEjAAAhIwAAMAAJAAAA" + + "AAAAAAAAAAB5PAEAZGF0YS9udmNmLXNlbGYtbWFuYWdlZC1jbGkvcmVmZXJlbmNlL2NvbW1hbmRz" + + "Lm1kVVQFAAEAps4SUEsBAhQAFAAIAAAAAAAhADwZ8FjiCAAA4ggAADIACQAAAAAAAAAAAAAAAWAB" + + "AGRhdGEvbnZjZi1zZWxmLW1hbmFnZWQtY2xpL3JlZmVyZW5jZS9leGl0LWNvZGVzLm1kVVQFAAEA" + + "ps4SUEsBAhQAFAAIAAAAAAAhAFsF+ye1NAAAtTQAAC0ACQAAAAAAAAAAAAAATGkBAGRhdGEvbnZj" + + "Zi1zZWxmLW1hbmFnZWQtY2xpL3JlZmVyZW5jZS9mbGFncy5tZFVUBQABAKbOElBLAQIUABQACAAA" + + "AAAAIQDzrhOEdBEAAHQRAAA3AAkAAAAAAAAAAAAAAGWeAQBkYXRhL252Y2Ytc2VsZi1tYW5hZ2Vk" + + "LWNsaS9yZWZlcmVuY2UvdHJvdWJsZXNob290aW5nLm1kVVQFAAEAps4SUEsBAhQAFAAIAAAAAAAh" + + "ABHDFJNJYgAASWIAACwACQAAAAAAAAAAAAAAR7ABAGRhdGEvbnZjZi1zZWxmLW1hbmFnZWQtaW5z" + + "dGFsbGF0aW9uL1NLSUxMLm1kVVQFAAEAps4SUEsBAhQAFAAIAAAAAAAhAEdValZKVgAASlYAADQA" + + "CQAAAAAAAAAAAAAA8xICAGRhdGEvbnZjZi1zZWxmLW1hbmFnZWQtaW5zdGFsbGF0aW9uL2V2YWxz" + + "L2V2YWxzLmpzb25VVAUAAQCmzhJQSwECFAAUAAgAAAAAACEAAlJOQH8oAAB/KAAALwAJAAAAAAAA" + + "AAAAAACoaQIAZGF0YS9udmNmLXNlbGYtbWFuYWdlZC1pbnN0YWxsYXRpb24vZXhhbXBsZXMubWRV" + + "VAUAAQCmzhJQSwECFAAUAAgAAAAAACEAlmO8WRIwAAASMAAATAAJAAAAAAAAAAAAAACNkgIAZGF0" + + "YS9udmNmLXNlbGYtbWFuYWdlZC1pbnN0YWxsYXRpb24vcmVmZXJlbmNlcy9jb21wdXRlLXBsYW5l" + + "LWluc3RhbGxhdGlvbi5tZFVUBQABAKbOElBLAQIUABQACAAAAAAAIQC1enA7tTAAALUwAAA7AAkA" + + "AAAAAAAAAAAAACLDAgBkYXRhL252Y2Ytc2VsZi1tYW5hZ2VkLWluc3RhbGxhdGlvbi9yZWZlcmVu" + + "Y2VzL2RlYnVnZ2luZy5tZFVUBQABAKbOElBLAQIUABQACAAAAAAAIQATEeyNCioAAAoqAABEAAkA" + + "AAAAAAAAAAAAAEn0AgBkYXRhL252Y2Ytc2VsZi1tYW5hZ2VkLWluc3RhbGxhdGlvbi9yZWZlcmVu" + + "Y2VzL2hlbG1maWxlLXN0cnVjdHVyZS5tZFVUBQABAKbOElBLAQIUABQACAAAAAAAIQA4+SevuQcA" + + "ALkHAAA/AAkAAAAAAAAAAAAAAM4eAwBkYXRhL252Y2Ytc2VsZi1tYW5hZ2VkLWluc3RhbGxhdGlv" + + "bi9yZWZlcmVuY2VzL252Y2YtdWktYWRkb24ubWRVVAUAAQCmzhJQSwECFAAUAAgAAAAAACEAS0I1" + + "qAAWAAAAFgAAPgAJAAAAAAAAAAAAAAD9JgMAZGF0YS9udmNmLXNlbGYtbWFuYWdlZC1pbnN0YWxs" + + "YXRpb24vcmVmZXJlbmNlcy9wdWxsLXNlY3JldHMubWRVVAUAAQCmzhJQSwUGAAAAABcAFwCvCQAA" + + "cj0DAAAA" var ( skillDataOnce sync.Once diff --git a/src/clis/nvcf-cli/internal/selfhosted/BUILD.bazel b/src/clis/nvcf-cli/internal/selfhosted/BUILD.bazel index 7737a9a8b9..9fb1539132 100644 --- a/src/clis/nvcf-cli/internal/selfhosted/BUILD.bazel +++ b/src/clis/nvcf-cli/internal/selfhosted/BUILD.bazel @@ -26,8 +26,11 @@ go_library( "preflight.go", "pullsecret.go", "register.go", + "registry_cred.go", "render.go", "stack.go", + "stack_namespaces.go", + "stale_namespace.go", "validatortag.go", ], importpath = "nvcf-cli/internal/selfhosted", @@ -42,10 +45,14 @@ go_library( "@io_k8s_api//core/v1:core", "@io_k8s_api//rbac/v1:rbac", "@io_k8s_apimachinery//pkg/api/errors", + "@io_k8s_apimachinery//pkg/api/resource", "@io_k8s_apimachinery//pkg/apis/meta/v1:meta", + "@io_k8s_apimachinery//pkg/labels", + "@io_k8s_apimachinery//pkg/types", "@io_k8s_client_go//kubernetes", "@io_k8s_client_go//rest", "@io_k8s_client_go//tools/clientcmd", + "@io_k8s_sigs_yaml//:yaml", "@org_golang_x_sync//errgroup", ], ) @@ -53,15 +60,22 @@ go_library( go_test( name = "selfhosted_test", srcs = [ + "clustervalidator_lifecycle_test.go", "clustervalidator_test.go", + "credhelper_test.go", "inotify_probe_test.go", + "main_test.go", + "miniapiserver_test.go", "orchestrator_errors_test.go", "output_test.go", "preflight_test.go", "pullsecret_test.go", "register_test.go", + "registry_cred_test.go", "render_test.go", + "stack_namespaces_test.go", "stack_test.go", + "stale_namespace_test.go", "validatortag_test.go", ], embed = [":selfhosted"], @@ -79,6 +93,8 @@ go_test( "@io_k8s_apimachinery//pkg/apis/meta/v1:meta", "@io_k8s_apimachinery//pkg/runtime", "@io_k8s_apimachinery//pkg/runtime/schema", + "@io_k8s_apimachinery//pkg/types", + "@io_k8s_client_go//kubernetes", "@io_k8s_client_go//kubernetes/fake", "@io_k8s_client_go//rest", "@io_k8s_client_go//testing", diff --git a/src/clis/nvcf-cli/internal/selfhosted/clustervalidator.go b/src/clis/nvcf-cli/internal/selfhosted/clustervalidator.go index 5df457fa76..c0da210dc8 100644 --- a/src/clis/nvcf-cli/internal/selfhosted/clustervalidator.go +++ b/src/clis/nvcf-cli/internal/selfhosted/clustervalidator.go @@ -19,9 +19,16 @@ package selfhosted import ( "context" + cryptorand "crypto/rand" + "encoding/hex" + "encoding/json" + "errors" "fmt" "io" + "net" "regexp" + "sort" + "strconv" "strings" "time" @@ -29,7 +36,10 @@ import ( corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/kubernetes" ) @@ -41,14 +51,53 @@ const ( clusterValidatorPollInterval = 2 * time.Second clusterValidatorTTLSeconds = int32(600) clusterValidatorHintURL = "https://docs.nvidia.com/nvcf/self-managed-clusters#cluster-validator" + // orphanValidatorRBACTTL is the minimum age before leftover validator RBAC + // is reclaimed. Must exceed the validator timeout so a live run is never hit. + orphanValidatorRBACTTL = 30 * time.Minute + // preservedValidatorTTL bounds how long --no-cleanup artifacts survive. + // Exempting them outright left a cluster-wide ClusterRole and an NGC-key + // Secret in place forever once nobody ran the check again. + preservedValidatorTTL = 24 * time.Hour + // clusterValidatorManagedBy tells this CLI's per-run objects apart from the + // fixed-name set released CLIs create. Those releases DeleteCollection on + // managed-by=nvcf-cli, so sharing the value let each version delete the + // other's in-flight Jobs, Secrets and RBAC. + clusterValidatorManagedBy = "nvcf-cli-validator" + // clusterValidatorRunLabel carries the run ID on everything one run + // creates, so the cleanup hint can address exactly that run's objects. + clusterValidatorRunLabel = "nvcf.nvidia.com/validator-run" ) // Vars (not consts) so tests can shorten without the full production budget. var ( clusterValidatorTimeout = 5 * time.Minute clusterValidatorLogFetchTimeout = 10 * time.Second + // validatorCleanupTimeout bounds each deferred sweep. They run after an + // interrupt, when CI will not send a second signal, so an unreachable + // apiserver must not hold the CLI open. + validatorCleanupTimeout = 30 * time.Second ) +// validatorDeferredSweeps is how many deferred sweeps one run can make, each +// bounded by validatorCleanupTimeout: the pull secret, the RBAC, and for the +// control-plane role the network-checks ConfigMap. +const validatorDeferredSweeps = 3 + +// ClusterValidatorRunCeiling is the longest one validator run can take: its +// own timeout, then on a timeout the wait for the Job's deadline to end the +// pod and the log fetch, then the deferred sweeps, which run in turn on fresh +// contexts, plus a margin for the pod's termination grace period. +func ClusterValidatorRunCeiling() time.Duration { + return clusterValidatorTimeout + validatorDeadlineGrace + clusterValidatorLogFetchTimeout + + validatorDeferredSweeps*validatorCleanupTimeout + validatorRunMargin +} + +// cleanupContext is a fresh, bounded context for one deferred sweep. It does +// not derive from the run's ctx, which may already be cancelled. +func cleanupContext() (context.Context, context.CancelFunc) { + return context.WithTimeout(context.Background(), validatorCleanupTimeout) +} + // Kubelet Waiting.Reason values that mean the pod will never start without // operator intervention. Detecting any short-circuits the 5-minute wait. var pullFailureReasons = map[string]struct{}{ @@ -71,6 +120,17 @@ type ClusterValidatorParams struct { Image string PullSecret string NoCleanup bool + // Role selects which check set the validator runs: "control-plane" or + // "compute-plane" (empty = compute-plane default). Passed to the Job as + // VALIDATOR_ROLE. See clustervalidator.RoleControlPlane / RoleComputePlane. + Role string + // Registries are probed for reachability by the control-plane validator: + // the same list, with the same criticality, the local credential check + // uses. Ignored for the compute-plane role. + Registries []RegistryEntry + // Env is passed to the validator container: namespace overrides, the + // probe image, and whether the control plane is expected to be installed. + Env map[string]string } // Err is non-nil only when the run failed to execute (RBAC bootstrap, @@ -81,7 +141,13 @@ type ClusterValidatorResult struct { ExitCode int32 Logs string JobName string - Err error + // RunID labels every object the run created (clusterValidatorRunLabel). + RunID string + Err error + // LeftBehind reports that this run's RBAC and other objects were kept + // because its pod could still be running, so the caller can print the + // command that removes them rather than leave them for the orphan sweep. + LeftBehind bool } type ClusterValidator func(ctx context.Context, params ClusterValidatorParams) ClusterValidatorResult @@ -98,12 +164,15 @@ func NewClusterValidator() ClusterValidator { if err != nil { return ClusterValidatorResult{Err: fmt.Errorf("building kubernetes client: %w", err)} } - return runClusterValidator(ctx, client, p.Image, p.PullSecret, p.NoCleanup) + return runClusterValidator(ctx, client, p.Image, p.PullSecret, p.NoCleanup, p.Role, p.Registries, p.Env) } } // Testable core. Pass a fake clientset to unit-test without a real cluster. -func runClusterValidator(ctx context.Context, client kubernetes.Interface, image, pullSecret string, noCleanup bool) ClusterValidatorResult { +func runClusterValidator( + ctx context.Context, client kubernetes.Interface, image, pullSecret string, noCleanup bool, role string, + registries []RegistryEntry, env map[string]string, +) ClusterValidatorResult { if image == "" { // Defensive: callers gate on configured image before invoking the // validator, so this branch shouldn't fire in normal use. @@ -113,31 +182,137 @@ func runClusterValidator(ctx context.Context, client kubernetes.Interface, image vctx, cancel := context.WithTimeout(ctx, clusterValidatorTimeout) defer cancel() + // podMayBeRunning is true only between a successful Job create and a clean + // wait. Both cleanup defers below key off it: outside that window nothing + // is using the pull secret or the RBAC, so reclaiming them is safe, and + // inside it reclaiming them breaks the pod that is still running. + podMayBeRunning := false + + // Random per-run identity. Every object this run creates carries it, so a + // predictable name cannot be pre-created by someone else and then either + // bound to the validator's cluster-wide permissions or overwritten with the + // NGC credential. Generated before anything is created, because the pull + // secret name needs it too. + runID, err := newValidatorRunID() + if err != nil { + return ClusterValidatorResult{Err: err} + } + + // Reclaim leftovers from dead runs before resolving this run's pull + // secret. The sweep only takes objects older than its TTL, so it cannot + // reach anything this run is about to create. + sweepOrphanClusterValidatorRBAC(vctx, client, orphanValidatorRBACTTL) + // Resolver errors are non-fatal: fall through to the caller's value and // let waitForClusterValidatorJob surface ImagePullBackOff if needed. - if resolved, err := resolveValidatorPullSecret(ctx, client, pullSecret, image); err == nil { + if resolved, err := resolveValidatorPullSecret(ctx, client, pullSecret, image, role, runID, noCleanup); err == nil { pullSecret = resolved } - // Sweep any pull secrets we created (mirror or env-mint) after the - // Job terminates. Uses a fresh context so cleanup runs even when - // vctx has expired. Operator-supplied secrets via the flag aren't - // labeled by us and are skipped. - defer sweepManagedPullSecrets(context.Background(), client) + // Sweep only this role's managed pull secret, and only once the pod can no + // longer need it. Sweeping unconditionally deletes the Secret out from + // under a pod that is still retrying its pull, which the kubelet then + // reports as FailedToRetrieveImagePullSecret. Operator-supplied secrets via + // the flag aren't labeled by us and are skipped. + // --no-cleanup is honoured here as it is for the RBAC below: an operator + // who keeps the Job for debugging and then re-runs the preserved pod would + // otherwise hit FailedToRetrieveImagePullSecret, the exact symptom the + // per-role secret scoping exists to prevent. + defer func() { + if !noCleanup && !podMayBeRunning { + cctx, cancel := cleanupContext() + defer cancel() + sweepManagedPullSecrets(cctx, client, role, runID) + } + }() - if err := ensureClusterValidatorRBAC(vctx, client); err != nil { - return ClusterValidatorResult{Err: fmt.Errorf("bootstrapping validator RBAC: %w", err)} + // Register the cleanup before the bootstrap, not after. ensureClusterValidatorRBAC + // creates three objects in sequence and returns on the first failure, so a + // kubeconfig that can create a ServiceAccount but not a cluster-scoped + // ClusterRole would otherwise abandon the ServiceAccount on every attempt. + // The names are per-run now, so nothing self-heals by reuse: under --wait + // that leaks one object per poll. The sweep is name-scoped and + // label-guarded, so running it when nothing was created is a no-op. + defer func() { + if !noCleanup && !podMayBeRunning { + cctx, cancel := cleanupContext() + defer cancel() + sweepClusterValidatorRBAC(cctx, client, role, runID) + } + }() + if err := ensureClusterValidatorRBAC(vctx, client, role, runID, noCleanup); err != nil { + // RunID lets --no-cleanup print the command that removes whatever the + // failed bootstrap and the pull-secret step already created. + return ClusterValidatorResult{RunID: runID, Err: fmt.Errorf("bootstrapping validator RBAC: %w", err)} } - sweepPriorClusterValidatorJobs(vctx, client) + // For the control-plane role, create a ConfigMap with reachability + // endpoints and enforcement config so the validator runs its configurable + // checks. Best-effort: a failure here is logged but does not abort the + // run - the validator gracefully skips configurable checks when the + // ConfigMap is absent. + var configNote string + if role == clusterValidatorControlPlaneRole { + // The ConfigMap is the one object this run creates that had no cleanup + // path, so it was left in the cluster forever. Same gating as the + // other sweeps: keep it when the pod may still read it, or when the + // operator asked to keep the run's artifacts. + defer func() { + if !noCleanup && !podMayBeRunning { + cctx, cancel := cleanupContext() + defer cancel() + sweepClusterValidatorConfig(cctx, client, runID) + } + }() + if err := ensureClusterValidatorConfig(vctx, client, registries, runID, noCleanup); err != nil { + // Non-fatal: continue without the ConfigMap; the validator skips + // configurable reachability and enforcement checks silently unless + // we surface this note in the transcript. + configNote = fmt.Sprintf("note: validator config not applied (%v); reachability checks may be skipped", err) + } + } + // No sweep of earlier Jobs: every Job now carries a deadline and a TTL, + // so a role-wide sweep could only ever hit an overlapping run's live Job. jobName := fmt.Sprintf("%s-%d", clusterValidatorName, time.Now().UnixNano()) - if _, err := client.BatchV1().Jobs(clusterValidatorNamespace).Create( - vctx, buildClusterValidatorJob(jobName, image, pullSecret, noCleanup), metav1.CreateOptions{}, - ); err != nil { - return ClusterValidatorResult{Err: fmt.Errorf("creating validator Job: %w", err)} + job, err := client.BatchV1().Jobs(clusterValidatorNamespace).Create( + vctx, buildClusterValidatorJob(jobName, image, pullSecret, role, runID, noCleanup, env), metav1.CreateOptions{}, + ) + if err != nil { + // A create cut off by an interrupt or a dropped connection may still + // have been applied, and a Job left behind would run with its RBAC + // swept. The name is this run's own, so removing it is safe. + podMayBeRunning = !stopValidatorJob(client, jobName) + return ClusterValidatorResult{RunID: runID, Err: fmt.Errorf("creating validator Job: %w", err)} + } + podMayBeRunning = true + + // The Job owns this run's pull secret and ConfigMap, so they go when the + // Job goes, even on paths where the deferred sweeps are suppressed: its + // TTL removes it, or for --no-cleanup a later check's orphan sweep once + // preservedValidatorTTL has passed. The ServiceAccount is + // deliberately not owned: the ClusterRole and binding are cluster-scoped + // and cannot be, and deleting the account alone would leave a binding + // that anyone able to recreate that ServiceAccount name could inherit. + // Not under --no-cleanup: a kept Secret or ConfigMap owned by the kept Job + // would be garbage-collected the moment an operator re-runs that Job by + // deleting and recreating it. + var owned []ownedArtifact + if pullSecret == validatorPullSecretRunName(role, runID) { + owned = append(owned, ownedArtifact{kind: "Secret", name: pullSecret}) + } + if role == clusterValidatorControlPlaneRole && configNote == "" { + owned = append(owned, ownedArtifact{kind: "ConfigMap", name: clusterValidatorConfigRunName(runID)}) + } + if !noCleanup { + ownByJob(vctx, client, job, owned) } final, waitErr := waitForClusterValidatorJob(vctx, client, jobName) + // A clean wait means the pod reached a terminal state, so the RBAC can go. + // Otherwise the pod may still be running: pulling the RBAC then fills its + // transcript with "forbidden", and cuts off the validator's own cleanup + // of its probe resources. + podMayBeRunning = waitErr != nil // Fetch logs under a fresh ctx from the parent: vctx is expired on the // timeout path, and dropping the partial transcript hurts most there. @@ -145,13 +320,52 @@ func runClusterValidator(ctx context.Context, client kubernetes.Interface, image defer logCancel() rawLogs, _ := fetchClusterValidatorLogs(logCtx, client, jobName) cleaned := cleanValidatorOutput(rawLogs) + if configNote != "" { + cleaned = configNote + "\n" + cleaned + } + + // A pull failure is different: the container never started, so nothing + // uses the RBAC, the pull secret or the ConfigMap. Delete the Job so its + // pod stops retrying, and let the deferred sweeps run now rather than + // leaving a cluster-wide ClusterRole and an NGC-key Secret for a later + // run's orphan sweep, or for nobody. + // Each stop deletes the Job in the foreground and waits, bounded, for its + // pod to end before the deferred sweeps revoke its RBAC. The validator + // removes its own probe namespace and DaemonSet on SIGTERM, and needs + // that RBAC to do it. + var pullErr *validatorImagePullError + interrupted := errors.Is(ctx.Err(), context.Canceled) + stopped := false + switch { + case errors.As(waitErr, &pullErr) && noCleanup: + // Kept for debugging, but a pod that cannot pull has nothing to + // run: suspend the Job so the pod stops retrying forever. + suspendValidatorJob(client, jobName) + case (errors.As(waitErr, &pullErr) || interrupted) && !noCleanup: + // A pull failure never started the container, and an interrupt means + // the operator abandoned the run: stop the Job and reclaim everything + // now rather than leave cluster-wide RBAC for a later orphan sweep. + podMayBeRunning = !stopValidatorJob(client, jobName) + stopped = true + case waitErr != nil && !noCleanup: + // A plain timeout: the Job's active deadline ends the pod shortly + // after this wait gave up. Wait for that, then sweep, as on success. + podMayBeRunning = !waitValidatorPodsDone(client, jobName, validatorDeadlineGrace) + } if waitErr != nil { - return ClusterValidatorResult{ - JobName: jobName, - Logs: cleaned, - Err: waitErr, + res := ClusterValidatorResult{ + JobName: jobName, + RunID: runID, + Logs: cleaned, + Err: waitErr, + LeftBehind: podMayBeRunning && !noCleanup, } + if stopped { + // The Job is gone, so a hint to read its logs would 404. + res.JobName = "" + } + return res } // Separate short ctx so a slow log fetch can't burn the exit-code lookup's @@ -163,68 +377,229 @@ func runClusterValidator(ctx context.Context, client kubernetes.Interface, image ExitCode: containerExitCode(exitCtx, client, jobName), Logs: cleaned, JobName: jobName, + RunID: runID, + } +} + +// extraValidatorEnv returns env as container variables in a stable order, +// skipping empty values and any name the Job already sets, so an override can +// never change the role, the config name or preflight mode. +func extraValidatorEnv(base []corev1.EnvVar, env map[string]string) []corev1.EnvVar { + set := map[string]bool{} + for _, e := range base { + set[e.Name] = true + } + names := make([]string, 0, len(env)) + for k, v := range env { + if v != "" && !set[k] { + names = append(names, k) + } + } + sort.Strings(names) + out := make([]corev1.EnvVar, 0, len(names)) + for _, k := range names { + out = append(out, corev1.EnvVar{Name: k, Value: env[k]}) + } + return out +} + +// ownedArtifact names a namespaced object the validator Job should own. +type ownedArtifact struct{ kind, name string } + +// ownByJob sets the Job as the controller-less owner of each artifact, so +// garbage collection removes them with it. Best-effort: a failure only means +// the artifact waits for the deferred or orphan sweep, as before. +func ownByJob(ctx context.Context, client kubernetes.Interface, job *batchv1.Job, artifacts []ownedArtifact) { + if job == nil || job.UID == "" { + return + } + patch, err := json.Marshal(map[string]any{"metadata": map[string]any{ + "ownerReferences": []metav1.OwnerReference{{ + APIVersion: "batch/v1", Kind: "Job", Name: job.Name, UID: job.UID, + }}, + }}) + if err != nil { + return + } + for _, a := range artifacts { + switch a.kind { + case "Secret": + _, _ = client.CoreV1().Secrets(clusterValidatorNamespace).Patch( + ctx, a.name, types.MergePatchType, patch, metav1.PatchOptions{}) + case "ConfigMap": + _, _ = client.CoreV1().ConfigMaps(clusterValidatorNamespace).Patch( + ctx, a.name, types.MergePatchType, patch, metav1.PatchOptions{}) + } + } +} + +// deleteValidatorJob removes this run's Job and its pod, but only a Job +// carrying our labels. +func deleteValidatorJob(ctx context.Context, client kubernetes.Interface, name string) { + dctx, cancel := context.WithTimeout(ctx, 10*time.Second) + defer cancel() + jobs := client.BatchV1().Jobs(clusterValidatorNamespace) + job, err := jobs.Get(dctx, name, metav1.GetOptions{}) + if err != nil || !hasValidatorManagedLabels(job.Labels) { + return } + // Foreground: the Job is removed only after its pod, so the pod gets its + // SIGTERM and grace period while its RBAC still exists. + propagation := metav1.DeletePropagationForeground + opts := deleteExactly(job) + opts.PropagationPolicy = &propagation + _ = jobs.Delete(dctx, name, opts) } -// Creates the SA/ClusterRole/ClusterRoleBinding the validator pod runs under, -// idempotent via AlreadyExists tolerance. Permissions mirror the -// nvca-operator chart's validator rbac.yaml (read-only). Resources persist -// across runs; the sweep only deletes Jobs. -func ensureClusterValidatorRBAC(ctx context.Context, client kubernetes.Interface) error { - labels := clusterValidatorLabels() +// Vars (not consts) so tests can shorten them. +var ( + // validatorStopTimeout bounds the wait for a stopped Job's pod to end. + // It covers the pod's default 30s termination grace period. + validatorStopTimeout = 45 * time.Second + // validatorDeadlineGrace bounds the wait, after this run's own timeout, + // for the Job's active deadline (60s later) to end the pod. + validatorDeadlineGrace = 120 * time.Second + // validatorRunMargin is ClusterValidatorRunCeiling's allowance for the + // pod's termination grace period and the API round trips between waits. + validatorRunMargin = 30 * time.Second + // validatorPullFailureGrace is how long a pull failure must persist before + // it is final. The kubelet retries after 10s and then 20s, so a transient + // registry error clears inside it. + validatorPullFailureGrace = 30 * time.Second + // validatorGetErrorLimit is how many consecutive failed Job reads end the + // wait. One 5xx or a refused connection during an apiserver restart is + // not the Job's result. + validatorGetErrorLimit = 5 +) + +// stopValidatorJob deletes the Job and reports whether its pods ended within +// validatorStopTimeout. +func stopValidatorJob(client kubernetes.Interface, name string) bool { + deleteValidatorJob(context.Background(), client, name) + return waitValidatorPodsDone(client, name, validatorStopTimeout) +} + +// suspendValidatorJob stops a kept Job from creating or retrying its pod. +func suspendValidatorJob(client kubernetes.Interface, name string) { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + _, _ = client.BatchV1().Jobs(clusterValidatorNamespace).Patch(ctx, name, types.MergePatchType, + []byte(`{"spec":{"suspend":true}}`), metav1.PatchOptions{}) +} + +// waitValidatorPodsDone waits up to limit, on a fresh context, until no pod of +// the Job is still running, and reports whether that happened. +func waitValidatorPodsDone(client kubernetes.Interface, jobName string, limit time.Duration) bool { + ctx, cancel := context.WithTimeout(context.Background(), limit) + defer cancel() + for { + pods, err := client.CoreV1().Pods(clusterValidatorNamespace).List(ctx, + metav1.ListOptions{LabelSelector: "job-name=" + jobName}) + if err == nil && allPodsDone(pods.Items) { + return true + } + select { + case <-ctx.Done(): + return false + case <-time.After(time.Second): + } + } +} + +func allPodsDone(pods []corev1.Pod) bool { + for i := range pods { + if p := pods[i].Status.Phase; p != corev1.PodSucceeded && p != corev1.PodFailed { + return false + } + } + return true +} + +// validatorImagePullError reports that the validator pod could not pull its +// image, so its container never started. +type validatorImagePullError struct{ reason, message string } + +func (e *validatorImagePullError) Error() string { + return fmt.Sprintf("validator pod cannot pull image (%s): %s", e.reason, e.message) +} + +// ensureClusterValidatorRBAC creates this run's ServiceAccount, ClusterRole and +// ClusterRoleBinding for the validator pod. Create only: the names are unique +// per run, so an object that already exists was not made by this run. +func ensureClusterValidatorRBAC(ctx context.Context, client kubernetes.Interface, role, runID string, preserve bool) error { + roleLabels := clusterValidatorRunLabels(role, runID, preserve) + name := clusterValidatorRBACName(role, runID) sa := &corev1.ServiceAccount{ ObjectMeta: metav1.ObjectMeta{ - Name: clusterValidatorName, + Name: name, Namespace: clusterValidatorNamespace, - Labels: labels, + Labels: roleLabels, }, } - if _, err := client.CoreV1().ServiceAccounts(clusterValidatorNamespace).Create(ctx, sa, metav1.CreateOptions{}); err != nil && !apierrors.IsAlreadyExists(err) { + // No AlreadyExists tolerance: the name carries a random per-run suffix, so + // anything already sitting there was not put there by this run and must not + // be adopted and bound to the validator ClusterRole. + if _, err := client.CoreV1().ServiceAccounts(clusterValidatorNamespace).Create(ctx, sa, metav1.CreateOptions{}); err != nil { return fmt.Errorf("create service account: %w", err) } cr := &rbacv1.ClusterRole{ - ObjectMeta: metav1.ObjectMeta{Name: clusterValidatorName, Labels: labels}, + ObjectMeta: metav1.ObjectMeta{Name: name, Labels: roleLabels}, + // Least privilege: only what the validator actually calls. It never + // creates Services, reads pod logs or watches anything, and it reads + // GatewayClasses through discovery alone. Rules: []rbacv1.PolicyRule{ - {APIGroups: []string{""}, Resources: []string{"nodes", "pods", "namespaces", "services", "configmaps"}, Verbs: []string{"get", "list", "watch"}}, + // Read-only: cluster inventory, its config ConfigMap, and the + // LoadBalancer Services the external-LB check inspects. + {APIGroups: []string{""}, Resources: []string{"nodes", "configmaps", "services"}, Verbs: []string{"get", "list"}}, + // Read + write: the overlay and enforcement probes create and delete + // their own namespaces and pods. + {APIGroups: []string{""}, Resources: []string{"namespaces", "pods"}, Verbs: []string{"get", "list", "create", "delete"}}, + // Events, list only: the overlay probe reads its pods' events to + // tell a slow first image pull, which publishes no pod IP until + // it returns, from a node that could not network the pod. + {APIGroups: []string{""}, Resources: []string{"events"}, Verbs: []string{"list"}}, {APIGroups: []string{"storage.k8s.io"}, Resources: []string{"csidrivers", "storageclasses"}, Verbs: []string{"get", "list"}}, - {APIGroups: []string{"networking.k8s.io"}, Resources: []string{"networkpolicies"}, Verbs: []string{"get", "list"}}, + // NetworkPolicies: read for CNI detection; write for enforcement + // check which creates/updates/deletes policies in the temp namespace. + {APIGroups: []string{"networking.k8s.io"}, Resources: []string{"networkpolicies"}, Verbs: []string{"get", "list", "create", "update", "delete"}}, {APIGroups: []string{"admissionregistration.k8s.io"}, Resources: []string{"mutatingwebhookconfigurations", "validatingwebhookconfigurations"}, Verbs: []string{"get", "list"}}, - {APIGroups: []string{"apps"}, Resources: []string{"deployments", "daemonsets", "statefulsets"}, Verbs: []string{"get", "list"}}, + // Deployments/StatefulSets: list for Tier-1/Tier-2 HA readiness checks. + // DaemonSets: create/delete for the node-to-node DaemonSet probe; list to watch pod readiness. + {APIGroups: []string{"apps"}, Resources: []string{"deployments", "statefulsets"}, Verbs: []string{"get", "list"}}, + {APIGroups: []string{"apps"}, Resources: []string{"daemonsets"}, Verbs: []string{"get", "list", "create", "delete"}}, + // Gateway API: the validator follows every NVCF route kind's + // parentRefs to learn which Gateways are NVCF's, and reads those + // Gateways' classes. + {APIGroups: []string{"gateway.networking.k8s.io"}, Resources: []string{ + "gateways", "httproutes", "grpcroutes", "tcproutes", "udproutes", + }, Verbs: []string{"get", "list"}}, {NonResourceURLs: []string{"/readyz", "/version", "/healthz"}, Verbs: []string{"get"}}, }, } - // Update-or-Create so a future CLI version's expanded rules replace the - // old set; AlreadyExists tolerance alone would silently keep stale rules. - if existing, err := client.RbacV1().ClusterRoles().Get(ctx, clusterValidatorName, metav1.GetOptions{}); err == nil { - existing.Rules = cr.Rules - existing.Labels = cr.Labels - if _, err := client.RbacV1().ClusterRoles().Update(ctx, existing, metav1.UpdateOptions{}); err != nil { - return fmt.Errorf("update cluster role: %w", err) - } - } else if apierrors.IsNotFound(err) { - if _, err := client.RbacV1().ClusterRoles().Create(ctx, cr, metav1.CreateOptions{}); err != nil && !apierrors.IsAlreadyExists(err) { - return fmt.Errorf("create cluster role: %w", err) - } - } else { - return fmt.Errorf("get cluster role: %w", err) + // Create only, for the same reason as the ServiceAccount above. The name is + // unique per run, so there are no stale rules from an older CLI to refresh + // and nothing legitimate to overwrite. + if _, err := client.RbacV1().ClusterRoles().Create(ctx, cr, metav1.CreateOptions{}); err != nil { + return fmt.Errorf("create cluster role: %w", err) } crb := &rbacv1.ClusterRoleBinding{ - ObjectMeta: metav1.ObjectMeta{Name: clusterValidatorName, Labels: labels}, + ObjectMeta: metav1.ObjectMeta{Name: name, Labels: roleLabels}, Subjects: []rbacv1.Subject{{ Kind: rbacv1.ServiceAccountKind, - Name: clusterValidatorName, + Name: name, Namespace: clusterValidatorNamespace, }}, RoleRef: rbacv1.RoleRef{ APIGroup: rbacv1.GroupName, Kind: "ClusterRole", - Name: clusterValidatorName, + Name: name, }, } - if _, err := client.RbacV1().ClusterRoleBindings().Create(ctx, crb, metav1.CreateOptions{}); err != nil && !apierrors.IsAlreadyExists(err) { + // Create only, same reasoning as above. + if _, err := client.RbacV1().ClusterRoleBindings().Create(ctx, crb, metav1.CreateOptions{}); err != nil { return fmt.Errorf("create cluster role binding: %w", err) } return nil @@ -233,67 +608,471 @@ func ensureClusterValidatorRBAC(ctx context.Context, client kubernetes.Interface func clusterValidatorLabels() map[string]string { return map[string]string{ "app.kubernetes.io/name": clusterValidatorAppLabel, - "app.kubernetes.io/managed-by": "nvcf-cli", + "app.kubernetes.io/managed-by": clusterValidatorManagedBy, "app.kubernetes.io/component": "preflight", } } -// Errors are swallowed: a stale Job is preferable to blocking the new run. -func sweepPriorClusterValidatorJobs(ctx context.Context, client kubernetes.Interface) { - selector := fmt.Sprintf("app.kubernetes.io/name=%s,app.kubernetes.io/managed-by=nvcf-cli", clusterValidatorAppLabel) - propagation := metav1.DeletePropagationBackground - _ = client.BatchV1().Jobs(clusterValidatorNamespace).DeleteCollection( - ctx, - metav1.DeleteOptions{PropagationPolicy: &propagation}, - metav1.ListOptions{LabelSelector: selector}, - ) +// clusterValidatorRoleLabels are the managed labels plus the role, with no run +// ID. validatorRoleSelector matches on them, so a lookup for one role never +// returns the other role's objects in a ModeSingle run. +func clusterValidatorRoleLabels(role string) map[string]string { + return clusterValidatorRunLabels(role, "", false) +} + +// clusterValidatorRunLabels are the labels for one run's objects: the managed +// labels, the role, the run ID, and the preserve marker when the run was asked +// to keep its artifacts (which the orphan sweeper then keeps for +// preservedValidatorTTL instead of orphanValidatorRBACTTL). +func clusterValidatorRunLabels(role, runID string, preserve bool) map[string]string { + l := clusterValidatorLabels() + if role != "" { + l[clusterValidatorRoleLabel] = role + } + if runID != "" { + l[clusterValidatorRunLabel] = runID + } + if preserve { + l[clusterValidatorPreserveLabel] = "true" + } + return l +} + +// validatorConfigNameForRole returns the network-check ConfigMap name the Job +// should read. Only the control-plane role has one; the compute-plane role gets +// a sentinel that resolves to nothing so it cannot inherit the control-plane's +// reachability and enforcement config. +func validatorConfigNameForRole(role, runID string) string { + if role == clusterValidatorControlPlaneRole { + return clusterValidatorConfigRunName(runID) + } + return clusterValidatorNoConfigName +} + +// clusterValidatorConfigLabels are the managed labels for the network-checks +// ConfigMap, plus the preserve marker when the run was asked to keep its +// artifacts. Without the marker the next run's orphan sweeper reclaims the +// ConfigMap once it is older than the TTL, and an operator re-running a Job +// they deliberately kept gets a validator that silently skips the configurable +// reachability and enforcement checks. +func clusterValidatorConfigLabels(runID string, preserve bool) map[string]string { + return clusterValidatorRunLabels("", runID, preserve) +} + +// clusterValidatorConfigRunName scopes the network-checks ConfigMap to one run. +// A shared name lets two overlapping commands overwrite each other's registry +// list, and lets the first to finish delete the config the other pod has not +// read yet. +func clusterValidatorConfigRunName(runID string) string { + if runID == "" { + return clusterValidatorConfigName + } + return clusterValidatorConfigName + "-" + runID +} + +// sweepOrphanClusterValidatorRBAC removes validator RBAC left behind by a run +// that died before its deferred cleanup (SIGKILL, OOM, lost terminal). +// +// Needed because the names are random per run: a fixed name self-healed by +// being reused, these would accumulate. Only objects carrying our labels and +// older than the TTL are removed, so a concurrent run is never disturbed. +func sweepOrphanClusterValidatorRBAC(ctx context.Context, client kubernetes.Interface, ttl time.Duration) { + // Derived from the same map the objects are stamped with, so the selector + // cannot drift from the labels. All three managed labels are required: the + // component label is part of what identifies these as ours. + opts := metav1.ListOptions{LabelSelector: labels.SelectorFromSet(clusterValidatorLabels()).String()} + now := time.Now() + + // stale applies the TTL for the object's kind of run. A --no-cleanup run's + // objects are kept longer, not forever: exempting them left a cluster-wide + // ClusterRole and an NGC-key Secret behind permanently. + stale := func(o metav1.Object) bool { + limit := ttl + if o.GetLabels()[clusterValidatorPreserveLabel] == "true" { + limit = preservedValidatorTTL + } + ts := o.GetCreationTimestamp() + return ts.Time.Before(now.Add(-limit)) + } + + // Each reclaimable test requires the generated name as well as the labels + // and the age. Labels alone are public constants and can be copied onto + // anything, and this deletes cluster-scoped objects with errors swallowed. + // Network-check ConfigMaps and pull secrets carry their own name prefixes. + reclaimableConfig := func(o metav1.Object) bool { + return strings.HasPrefix(o.GetName(), clusterValidatorConfigName) && stale(o) + } + reclaimableSecret := func(o metav1.Object) bool { + return strings.HasPrefix(o.GetName(), validatorPullSecretName) && stale(o) + } + // The bare legacy name is the fixed-name set released CLIs keep and reuse. + // Its labels no longer match this selector, but exclude it by name too so + // a released CLI's pod never loses its binding mid-run. + reclaimable := func(o metav1.Object) bool { + return strings.HasPrefix(o.GetName(), clusterValidatorName+"-") && stale(o) + } + + if l, err := client.RbacV1().ClusterRoleBindings().List(ctx, opts); err == nil { + for i := range l.Items { + if o := &l.Items[i]; reclaimable(o) { + _ = client.RbacV1().ClusterRoleBindings().Delete(ctx, o.Name, deleteExactly(o)) + } + } + } + if l, err := client.RbacV1().ClusterRoles().List(ctx, opts); err == nil { + for i := range l.Items { + if o := &l.Items[i]; reclaimable(o) { + _ = client.RbacV1().ClusterRoles().Delete(ctx, o.Name, deleteExactly(o)) + } + } + } + // Network-check ConfigMaps too. Run-scoping the name removed the accidental + // self-healing the fixed name gave us: a killed run used to leave exactly + // one object that the next run overwrote, and now each leaves its own, so + // under --wait they accumulate per poll. Same shape as the RBAC objects. + cms := client.CoreV1().ConfigMaps(clusterValidatorNamespace) + if l, err := cms.List(ctx, opts); err == nil { + for i := range l.Items { + if o := &l.Items[i]; reclaimableConfig(o) { + _ = cms.Delete(ctx, o.Name, deleteExactly(o)) + } + } + } + + // Pull secrets too: the deferred sweep is suppressed whenever the pod may + // still be running, and that is exactly the ImagePullBackOff path, so + // without this arm a Secret holding $oauthtoken:$NGC_API_KEY is the one + // object with no reclaim at all. + secrets := client.CoreV1().Secrets(clusterValidatorNamespace) + if l, err := secrets.List(ctx, opts); err == nil { + for i := range l.Items { + if o := &l.Items[i]; reclaimableSecret(o) { + _ = secrets.Delete(ctx, o.Name, deleteExactly(o)) + } + } + } + sa := client.CoreV1().ServiceAccounts(clusterValidatorNamespace) + if l, err := sa.List(ctx, opts); err == nil { + for i := range l.Items { + if o := &l.Items[i]; reclaimable(o) { + _ = sa.Delete(ctx, o.Name, deleteExactly(o)) + } + } + } + + // Jobs only need this for --no-cleanup runs: every other Job carries a + // deadline and a TTL. A preserved Job has neither, so without this arm it + // and its pod would stay forever. Background propagation removes the pod, + // and the Job's ownerReferences take its Secret and ConfigMap with it. + jobs := client.BatchV1().Jobs(clusterValidatorNamespace) + if l, err := jobs.List(ctx, opts); err == nil { + propagation := metav1.DeletePropagationBackground + for i := range l.Items { + o := &l.Items[i] + if o.Labels[clusterValidatorPreserveLabel] != "true" || !reclaimable(o) { + continue + } + del := deleteExactly(o) + del.PropagationPolicy = &propagation + _ = jobs.Delete(ctx, o.Name, del) + } + } } -// sweepManagedPullSecrets removes any docker-registry secrets in -// clusterValidatorNamespace that we previously created (mirrored from -// another namespace or minted from NGC_API_KEY). Called after the Job -// terminates so NGC credentials don't persist across preflight runs. +// sweepClusterValidatorRBAC removes the SA, ClusterRole, and ClusterRoleBinding +// created by ensureClusterValidatorRBAC. Called after Job completion (when +// --no-cleanup is not set) to close the window where the elevated ClusterRole +// exists. Each run mints its own names, so nothing is reused and anything left +// behind is reclaimed by sweepOrphanClusterValidatorRBAC instead. +// Errors are swallowed: stale RBAC is preferable to failing the result. +func sweepClusterValidatorRBAC(ctx context.Context, client kubernetes.Interface, role, runID string) { + name := clusterValidatorRBACName(role, runID) + + // Delete by name, but only what we own. These are cluster-scoped objects + // and the errors here are swallowed, so an operator-owned ClusterRole with + // a colliding name would otherwise vanish with no diagnostic at all. + if crb, err := client.RbacV1().ClusterRoleBindings().Get(ctx, name, metav1.GetOptions{}); err == nil && + hasValidatorManagedLabels(crb.Labels) { + _ = client.RbacV1().ClusterRoleBindings().Delete(ctx, name, deleteExactly(crb)) + } + if cr, err := client.RbacV1().ClusterRoles().Get(ctx, name, metav1.GetOptions{}); err == nil && + hasValidatorManagedLabels(cr.Labels) { + _ = client.RbacV1().ClusterRoles().Delete(ctx, name, deleteExactly(cr)) + } + sa := client.CoreV1().ServiceAccounts(clusterValidatorNamespace) + if acct, err := sa.Get(ctx, name, metav1.GetOptions{}); err == nil && + hasValidatorManagedLabels(acct.Labels) { + _ = sa.Delete(ctx, name, deleteExactly(acct)) + } +} + +// sweepManagedPullSecrets removes this run's docker-registry secret in +// clusterValidatorNamespace (mirrored from another namespace or minted from +// NGC_API_KEY). Called after the Job terminates so NGC credentials don't +// persist across preflight runs. Other runs' secrets are left to their own +// sweeps or to sweepOrphanClusterValidatorRBAC. // // Operator-supplied secrets via --cluster-validator-pull-secret aren't // labeled by us and are skipped by the selector. Errors are swallowed: // failing to clean up is preferable to failing the check itself. -func sweepManagedPullSecrets(ctx context.Context, client kubernetes.Interface) { - selector := fmt.Sprintf("app.kubernetes.io/name=%s,app.kubernetes.io/managed-by=nvcf-cli", clusterValidatorAppLabel) - _ = client.CoreV1().Secrets(clusterValidatorNamespace).DeleteCollection( - ctx, - metav1.DeleteOptions{}, - metav1.ListOptions{LabelSelector: selector}, - ) +func sweepManagedPullSecrets(ctx context.Context, client kubernetes.Interface, role, runID string) { + // The generated name is part of the ownership test, not just the labels, + // which are public constants anything could carry. + secrets := client.CoreV1().Secrets(clusterValidatorNamespace) + l, err := secrets.List(ctx, metav1.ListOptions{LabelSelector: validatorRoleSelector(role)}) + if err != nil { + return + } + for i := range l.Items { + // Only this run's Secret. The name is unguessable and unique per run, + // so a sweep can no longer take out a Secret another run selected. + if l.Items[i].Name != validatorPullSecretRunName(role, runID) { + continue + } + _ = secrets.Delete(ctx, l.Items[i].Name, deleteExactly(&l.Items[i])) + } +} + +// validatorRoleSelector matches objects this CLI created for one validator role. +// Derived from clusterValidatorRoleLabels so it cannot drift from the labels +// actually stamped on the objects. +func validatorRoleSelector(role string) string { + return labels.SelectorFromSet(clusterValidatorRoleLabels(role)).String() } -// IfNotPresent so locally-imported images (k3d image import, kind load) are -// reused. VALIDATOR_CONFIG_NAME is empty so the validator skips the -// configurable reachability/network-policy sections (ConfigMap support is a -// follow-up). +// clusterValidatorControlPlaneRole is the role value passed as VALIDATOR_ROLE +// when running against the control-plane cluster. Matches nvca's RoleControlPlane +// without importing that package. +const clusterValidatorControlPlaneRole = "control-plane" + +// clusterValidatorComputePlaneRole is the other VALIDATOR_ROLE value. Both are +// named so role-scoping logic reads the same on either side. +const clusterValidatorComputePlaneRole = "compute-plane" + +// clusterValidatorConfigName is the default ConfigMap name the validator binary +// looks for when VALIDATOR_CONFIG_NAME is empty. Must stay in sync with +// defaultConfigMapName in nvca/cmd/cluster-validator/main.go. +const clusterValidatorConfigName = "cluster-validator-network-checks" + +// clusterValidatorNoConfigName is a name no ConfigMap uses. VALIDATOR_CONFIG_NAME +// must be non-empty to suppress the validator's own default-name fallback, so +// "no config" has to be spelled as a name that resolves to nothing. +const clusterValidatorNoConfigName = "cluster-validator-no-config" + +// clusterValidatorRoleLabel scopes Job selectors to a single validator role. +const clusterValidatorRoleLabel = "nvcf.nvidia.com/validator-role" + +// clusterValidatorPreserveLabel marks objects an operator asked to keep with +// --no-cleanup. The orphan sweeper keeps them for preservedValidatorTTL instead +// of orphanValidatorRBACTTL: without the label a preserved run would be +// reclaimed after 30 minutes, and with no limit at all a kept ClusterRole and +// NGC-key Secret would stay forever. +const clusterValidatorPreserveLabel = "nvcf.nvidia.com/validator-preserve" + +// clusterValidatorRBACName returns the ServiceAccount / ClusterRole / +// ClusterRoleBinding name for one run of one role. +// +// The runID is random per run, which is what makes the name safe to create. +// A predictable name can be squatted: the managed labels are three public +// constants, so anyone who can create a ServiceAccount in the probe namespace +// could pre-create one carrying them, and a label check alone would then adopt +// it and bind it to the validator ClusterRole. With an unguessable name there +// is no collision to adopt, so the resources are only ever created by us. // -// VALIDATOR_PREFLIGHT=true puts the validator in preflight mode: it runs the -// readiness checks and exits, but does NOT write its summary ConfigMap. The -// ConfigMap exists for the metrics path (the NVCA agent watches it and -// republishes on /metrics), which is meaningless here because preflight runs -// before NVCA is installed and our RBAC bootstrap grants no configmaps -// create/update. Tagging the invocation keeps preflight a clean no-op rather -// than emitting a confusing "failed to create summary ConfigMap" warning. -func buildClusterValidatorJob(name, image, pullSecret string, noCleanup bool) *batchv1.Job { +// Per-role naming also matters because ModeSplit runs both validators +// concurrently against contexts that can resolve to the same cluster. +func clusterValidatorRBACName(role, runID string) string { + name := clusterValidatorName + if role != "" { + name += "-" + role + } + if runID != "" { + name += "-" + runID + } + return name +} + +// newValidatorRunID returns an unguessable suffix for this run's RBAC names. +// crypto/rand, not math/rand: a predictable value would defeat the point. +func newValidatorRunID() (string, error) { + b := make([]byte, 5) + if _, err := cryptorand.Read(b); err != nil { + return "", fmt.Errorf("generating run id: %w", err) + } + return hex.EncodeToString(b), nil +} + +// controlPlaneValidatorEnforcementConfig follows the generated reachability +// section in the control-plane validator ConfigMap. +const controlPlaneValidatorEnforcementConfig = `enforcement: + # Disabled for preflight. VALIDATOR_PREFLIGHT only suppresses the summary + # write, so enforcement would still run: it creates netpol-validation + # namespaces, server and client pods and NetworkPolicies, and pulls + # busybox from Docker Hub. Doing that on a virgin or air-gapped cluster, + # before anything is installed and with no flag to turn it off, is not + # something a read-only readiness check should do. + enabled: false + testImage: busybox:1.36 + timeoutSeconds: 60 + critical: false +` + +// ensureClusterValidatorConfig creates this run's network-check ConfigMap. +// Best-effort: the validator skips configurable checks when it is absent. +func ensureClusterValidatorConfig(ctx context.Context, client kubernetes.Interface, registries []RegistryEntry, runID string, preserve bool) error { + content := buildControlPlaneValidatorConfig(registries) + desired := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: clusterValidatorConfigRunName(runID), + Namespace: clusterValidatorNamespace, + Labels: clusterValidatorConfigLabels(runID, preserve), + }, + Data: map[string]string{"config.yaml": content}, + } + + existing, err := client.CoreV1().ConfigMaps(clusterValidatorNamespace).Get(ctx, clusterValidatorConfigRunName(runID), metav1.GetOptions{}) + if err != nil { + if !apierrors.IsNotFound(err) { + return fmt.Errorf("get validator config ConfigMap: %w", err) + } + if _, err := client.CoreV1().ConfigMaps(clusterValidatorNamespace).Create(ctx, desired, metav1.CreateOptions{}); err != nil { + return fmt.Errorf("create validator config ConfigMap: %w", err) + } + return nil + } + + // The name is per run, so an existing object was not created by this run. + // Replace the content only when it carries our labels; never overwrite a + // ConfigMap we do not own. + if !hasValidatorManagedLabels(existing.Labels) { + return fmt.Errorf( + "refusing to overwrite ConfigMap %s/%s which is not managed by nvcf-cli", + clusterValidatorNamespace, clusterValidatorConfigRunName(runID)) + } + existing.Data = desired.Data + existing.Labels = desired.Labels + if _, err := client.CoreV1().ConfigMaps(clusterValidatorNamespace).Update(ctx, existing, metav1.UpdateOptions{}); err != nil { + return fmt.Errorf("update validator config ConfigMap: %w", err) + } + return nil +} + +// buildControlPlaneValidatorConfig assembles the network-check ConfigMap YAML. +// The reachability endpoints are the registries the install pulls from, as +// EnumerateRegistries resolved them for the local credential check, and all of +// them are non-critical. The validator dials them from a pod with no proxy +// support, but nodes pull through the container runtime, often via an +// HTTPS_PROXY or a registry mirror that pods cannot use: a failed in-pod dial +// is worth a warning, not proof that the nodes cannot pull. With no +// registries the section is omitted. +func buildControlPlaneValidatorConfig(registries []RegistryEntry) string { + var b strings.Builder + for _, reg := range registries { + host, port := parseRegistryHostPort(reg.Registry) + if host == "" { + continue + } + if b.Len() == 0 { + b.WriteString("reachability:\n endpoints:\n") + } + // Quote the values: they come from operator input and the stack + // values. An unquoted "[" or embedded newline would make the whole + // ConfigMap unparseable, and the validator then silently drops every + // reachability and enforcement check. + fmt.Fprintf(&b, + " - name: %q\n host: %q\n port: %d\n protocol: tcp+tls\n critical: false\n", + reg.Registry, host, port) + } + return b.String() + controlPlaneValidatorEnforcementConfig +} + +// parseRegistryHostPort splits a "host:port" string using net.SplitHostPort, +// which correctly handles IPv6 literals ([::1]:5000) and bare hostnames. +// Returns port 443 when no port is specified, the port is non-numeric, or +// the input has a trailing colon with no digit (e.g. "nvcr.io:"). +func parseRegistryHostPort(s string) (host string, port int) { + s = strings.TrimSpace(s) + // Reject anything that is not a bare host[:port]. Callers interpolate the + // result straight into a URL and into the validator ConfigMap, and an empty + // host is the signal to skip the entry entirely. + if !isBareRegistryHost(s) { + return "", 0 + } + h, p, err := net.SplitHostPort(s) + if err != nil { + // No port present (e.g. "nvcr.io"). Strip IPv6 brackets: the value + // goes into the validator ConfigMap as a bare host, and the validator + // brackets it itself with net.JoinHostPort, so "[fd00::1]" would be + // dialled as "[[fd00::1]]:443" and reported unreachable. + return strings.TrimSuffix(strings.TrimPrefix(s, "["), "]"), 443 + } + if p == "" { + // Trailing colon with no port digit (e.g. "nvcr.io:") is the same typo. + return "", 0 + } + n, err := strconv.Atoi(p) + if err != nil || n <= 0 || n > 65535 { + // An explicit but unparseable port is a typo, not a request for 443. + // Silently probing a different endpoint than configured would report a + // result for something the operator never asked about. + return "", 0 + } + return h, n +} + +// buildClusterValidatorJob creates the validator Job. PullIfNotPresent reuses +// locally-imported images. VALIDATOR_PREFLIGHT=true skips the summary ConfigMap +// write. VALIDATOR_ROLE selects the check set (control-plane vs compute-plane). +func buildClusterValidatorJob(name, image, pullSecret, role, runID string, noCleanup bool, env map[string]string) *batchv1.Job { backoff := int32(0) + // Pod shape mirrors deployments/nvca-operator/templates/cronjob.yaml, which + // runs this same image. Two producers of one pod spec now exist in two + // languages, so they are kept deliberately in step: without the security + // context the Job is rejected outright by a namespace enforcing the + // PodSecurity "restricted" profile, and without the tolerations it never + // schedules on a cluster whose nodes all carry the control-plane taint. + // Either way waitForClusterValidatorJob burns its full budget and the run + // leaks, so these are correctness, not hardening. + runAsUser := int64(65534) + runAsNonRoot := true + readOnlyRoot := true + allowPrivEsc := false podSpec := corev1.PodSpec{ - ServiceAccountName: clusterValidatorName, + ServiceAccountName: clusterValidatorRBACName(role, runID), RestartPolicy: corev1.RestartPolicyNever, + Tolerations: clusterValidatorTolerations(), + SecurityContext: &corev1.PodSecurityContext{ + RunAsUser: &runAsUser, + RunAsGroup: &runAsUser, + FSGroup: &runAsUser, + }, Containers: []corev1.Container{{ Name: clusterValidatorContainer, Image: image, ImagePullPolicy: corev1.PullIfNotPresent, + Resources: clusterValidatorResources(), + SecurityContext: &corev1.SecurityContext{ + RunAsNonRoot: &runAsNonRoot, + ReadOnlyRootFilesystem: &readOnlyRoot, + AllowPrivilegeEscalation: &allowPrivEsc, + Capabilities: &corev1.Capabilities{Drop: []corev1.Capability{"ALL"}}, + SeccompProfile: &corev1.SeccompProfile{Type: corev1.SeccompProfileTypeRuntimeDefault}, + }, Env: []corev1.EnvVar{ {Name: "VALIDATOR_CONFIG_NAMESPACE", Value: clusterValidatorNamespace}, - {Name: "VALIDATOR_CONFIG_NAME", Value: ""}, + // Name the ConfigMap explicitly for the control-plane role and + // leave it unresolvable for the compute-plane role. An empty + // value is NOT "no config": the validator falls back to its own + // default name, so both roles would resolve the same ConfigMap + // and the compute-plane preflight would silently run the + // control-plane's active NetworkPolicy enforcement. + {Name: "VALIDATOR_CONFIG_NAME", Value: validatorConfigNameForRole(role, runID)}, {Name: "VALIDATOR_PREFLIGHT", Value: "true"}, + {Name: "VALIDATOR_ROLE", Value: role}, }, }}, } + podSpec.Containers[0].Env = append(podSpec.Containers[0].Env, extraValidatorEnv(podSpec.Containers[0].Env, env)...) if pullSecret != "" { podSpec.ImagePullSecrets = []corev1.LocalObjectReference{{Name: pullSecret}} } @@ -301,17 +1080,28 @@ func buildClusterValidatorJob(name, image, pullSecret string, noCleanup bool) *b ObjectMeta: metav1.ObjectMeta{ Name: name, Namespace: clusterValidatorNamespace, - Labels: clusterValidatorLabels(), + Labels: clusterValidatorRunLabels(role, runID, noCleanup), }, Spec: batchv1.JobSpec{ BackoffLimit: &backoff, Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{Labels: clusterValidatorLabels()}, + ObjectMeta: metav1.ObjectMeta{Labels: clusterValidatorRunLabels(role, runID, false)}, Spec: podSpec, }, }, } if !noCleanup { + // Without a deadline a Job whose pod never finishes never becomes + // terminal, so its TTL never fires. The deadline plus the TTL remove + // the Job and its pod, and through ownerReferences its pull secret and + // ConfigMap. The ServiceAccount, ClusterRole and binding are left to + // the deferred sweeps, or to a later run's orphan sweep. Sized above + // the runner's own budget so it never truncates a wait that is still + // making progress. A --no-cleanup Job gets neither, so the pod and its + // logs survive for debugging until the operator removes them or a + // later check's orphan sweep runs after preservedValidatorTTL. + activeDeadline := int64(clusterValidatorTimeout/time.Second) + 60 + job.Spec.ActiveDeadlineSeconds = &activeDeadline ttl := clusterValidatorTTLSeconds job.Spec.TTLSecondsAfterFinished = &ttl } @@ -324,21 +1114,40 @@ func buildClusterValidatorJob(name, image, pullSecret string, noCleanup bool) *b func waitForClusterValidatorJob(ctx context.Context, client kubernetes.Interface, jobName string) (*batchv1.Job, error) { ticker := time.NewTicker(clusterValidatorPollInterval) defer ticker.Stop() + getErrors := 0 + var pullFailingSince time.Time for { job, err := client.BatchV1().Jobs(clusterValidatorNamespace).Get(ctx, jobName, metav1.GetOptions{}) - if err != nil { + switch { + case err != nil: // If the deadline fired between ticker and Get, surface the wait // wording the select branch would have used. if ctxErr := ctx.Err(); ctxErr != nil { return nil, fmt.Errorf("waiting for job %s: %w", jobName, ctxErr) } - return nil, fmt.Errorf("get job %s: %w", jobName, err) - } - if job.Status.Succeeded > 0 || job.Status.Failed > 0 { + // A missing Job or a permission error cannot change; anything else + // is retried a few times before it decides the run. + getErrors++ + if apierrors.IsNotFound(err) || apierrors.IsForbidden(err) || apierrors.IsUnauthorized(err) || + getErrors >= validatorGetErrorLimit { + return nil, fmt.Errorf("get job %s: %w", jobName, err) + } + case job.Status.Succeeded > 0 || job.Status.Failed > 0: return job, nil - } - if reason, msg := podPullFailureReason(ctx, client, jobName); reason != "" { - return job, fmt.Errorf("validator pod cannot pull image (%s): %s", reason, msg) + default: + getErrors = 0 + reason, msg := podPullFailureReason(ctx, client, jobName) + switch { + case reason == "": + pullFailingSince = time.Time{} + case reason == "InvalidImageName": + // A malformed reference never pulls. + return job, &validatorImagePullError{reason: reason, message: msg} + case pullFailingSince.IsZero(): + pullFailingSince = time.Now() + case time.Since(pullFailingSince) >= validatorPullFailureGrace: + return job, &validatorImagePullError{reason: reason, message: msg} + } } select { case <-ctx.Done(): @@ -450,9 +1259,52 @@ func cleanValidatorOutput(raw string) string { return s + "\n" } -func kubectlLogsHint(jobName string) string { +// Pinned to the context the Job was created in: in ModeSplit the two roles +// run against different clusters, so a bare kubectl command resolves to +// whichever context is current and the hint reports "job not found". +func kubectlLogsHint(kubeContext, jobName string) string { if jobName == "" { return "" } - return fmt.Sprintf("kubectl logs -n %s job/%s --tail=-1", clusterValidatorNamespace, jobName) + return fmt.Sprintf("kubectl%s logs -n %s job/%s --tail=-1", + kubectlContextArg(kubeContext), clusterValidatorNamespace, jobName) +} + +// sweepClusterValidatorConfig removes the network-checks ConfigMap this run +// created. The name is scoped to this run, and the managed labels are checked +// as well, so a same-named object belonging to anyone else is left alone. +// Errors are swallowed, as with the other sweeps. +func sweepClusterValidatorConfig(ctx context.Context, client kubernetes.Interface, runID string) { + cms := client.CoreV1().ConfigMaps(clusterValidatorNamespace) + name := clusterValidatorConfigRunName(runID) + cm, err := cms.Get(ctx, name, metav1.GetOptions{}) + if err != nil || !hasValidatorManagedLabels(cm.Labels) { + return + } + _ = cms.Delete(ctx, name, deleteExactly(cm)) +} + +// clusterValidatorTolerations mirrors the chart's tolerations so the Job can +// schedule on a cluster whose nodes all carry the control-plane taint, which +// is the normal shape for a dedicated control plane. +func clusterValidatorTolerations() []corev1.Toleration { + return []corev1.Toleration{ + {Key: "node-role.kubernetes.io/control-plane", Operator: corev1.TolerationOpExists, Effect: corev1.TaintEffectNoSchedule}, + {Key: "node-role.kubernetes.io/master", Operator: corev1.TolerationOpExists, Effect: corev1.TaintEffectNoSchedule}, + } +} + +// clusterValidatorResources mirrors the chart's defaults. Without requests the +// Job is rejected outright by a namespace carrying a LimitRange or a quota. +func clusterValidatorResources() corev1.ResourceRequirements { + return corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("100m"), + corev1.ResourceMemory: resource.MustParse("64Mi"), + }, + Limits: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("200m"), + corev1.ResourceMemory: resource.MustParse("128Mi"), + }, + } } diff --git a/src/clis/nvcf-cli/internal/selfhosted/clustervalidator_lifecycle_test.go b/src/clis/nvcf-cli/internal/selfhosted/clustervalidator_lifecycle_test.go new file mode 100644 index 0000000000..918afcaf1d --- /dev/null +++ b/src/clis/nvcf-cli/internal/selfhosted/clustervalidator_lifecycle_test.go @@ -0,0 +1,823 @@ +/* +SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +SPDX-License-Identifier: Apache-2.0 + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package selfhosted + +import ( + "context" + "errors" + "fmt" + "io" + "net/http" + "net/http/httptest" + "strings" + "sync/atomic" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + batchv1 "k8s.io/api/batch/v1" + corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/kubernetes/fake" + "k8s.io/client-go/rest" + ktesting "k8s.io/client-go/testing" +) + +// leftovers lists what one run left behind, by kind, other than its Job and +// pod (which the Job's deadline and TTL remove). +func leftovers(t *testing.T, client *fake.Clientset, runID string) map[string][]string { + t.Helper() + ctx := context.Background() + opts := metav1.ListOptions{LabelSelector: clusterValidatorRunLabel + "=" + runID} + out := map[string][]string{} + sas, err := client.CoreV1().ServiceAccounts(clusterValidatorNamespace).List(ctx, opts) + require.NoError(t, err) + for _, o := range sas.Items { + out["ServiceAccount"] = append(out["ServiceAccount"], o.Name) + } + crs, err := client.RbacV1().ClusterRoles().List(ctx, opts) + require.NoError(t, err) + for _, o := range crs.Items { + out["ClusterRole"] = append(out["ClusterRole"], o.Name) + } + crbs, err := client.RbacV1().ClusterRoleBindings().List(ctx, opts) + require.NoError(t, err) + for _, o := range crbs.Items { + out["ClusterRoleBinding"] = append(out["ClusterRoleBinding"], o.Name) + } + secrets, err := client.CoreV1().Secrets(clusterValidatorNamespace).List(ctx, opts) + require.NoError(t, err) + for _, o := range secrets.Items { + out["Secret"] = append(out["Secret"], o.Name) + } + cms, err := client.CoreV1().ConfigMaps(clusterValidatorNamespace).List(ctx, opts) + require.NoError(t, err) + for _, o := range cms.Items { + out["ConfigMap"] = append(out["ConfigMap"], o.Name) + } + return out +} + +// lifecycleClient drives a Job through the given terminal behaviour. The Job +// gets a UID, as a real apiserver assigns one, so ownerReferences can be set. +func lifecycleClient(jobStatus func(name string) *batchv1.Job, waiting string) *fake.Clientset { + client := fake.NewSimpleClientset() + var jobName atomic.Value + jobName.Store("") + client.PrependReactor("create", "jobs", func(action ktesting.Action) (bool, runtime.Object, error) { + job := action.(ktesting.CreateAction).GetObject().(*batchv1.Job) + job.UID = types.UID("uid-" + job.Name) + jobName.Store(job.Name) + return false, nil, nil + }) + client.PrependReactor("get", "jobs", func(action ktesting.Action) (bool, runtime.Object, error) { + name := jobName.Load().(string) + if name == "" || action.(ktesting.GetAction).GetName() != name || jobStatus == nil { + return false, nil, nil + } + job := jobStatus(name) + job.Labels = clusterValidatorLabels() + return true, job, nil + }) + client.PrependReactor("list", "pods", podListReactor(waiting)) + // Deleting the Job takes its pod with it, as the garbage collector would; + // the fake clientset has none. + var jobDeleted atomic.Bool + client.PrependReactor("delete", "jobs", func(ktesting.Action) (bool, runtime.Object, error) { + jobDeleted.Store(true) + return false, nil, nil + }) + client.PrependReactor("list", "pods", func(ktesting.Action) (bool, runtime.Object, error) { + if jobDeleted.Load() { + return true, &corev1.PodList{}, nil + } + return false, nil, nil + }) + client.PrependReactor("get", "pods", func(action ktesting.Action) (bool, runtime.Object, error) { + if action.GetSubresource() != "" { + return false, nil, nil + } + pod := &corev1.Pod{ObjectMeta: metav1.ObjectMeta{ + Name: action.(ktesting.GetAction).GetName(), Namespace: clusterValidatorNamespace, + }} + if waiting != "" { + pod.Status.ContainerStatuses = []corev1.ContainerStatus{{ + Name: clusterValidatorContainer, + State: corev1.ContainerState{Waiting: &corev1.ContainerStateWaiting{Reason: waiting}}, + }} + } else { + pod.Status.ContainerStatuses = []corev1.ContainerStatus{{ + Name: clusterValidatorContainer, + State: corev1.ContainerState{Terminated: &corev1.ContainerStateTerminated{}}, + }} + } + return true, pod, nil + }) + return client +} + +func succeeded(name string) *batchv1.Job { + return &batchv1.Job{ + ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: clusterValidatorNamespace}, + Status: batchv1.JobStatus{Succeeded: 1}, + } +} + +func running(name string) *batchv1.Job { + return &batchv1.Job{ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: clusterValidatorNamespace}} +} + +// A clean run removes everything it created apart from the Job, which its TTL +// removes: the RBAC, the minted NGC pull secret and the network-check +// ConfigMap. +func TestRunClusterValidator_HappyPathLeavesNothingBehind(t *testing.T) { + t.Setenv("NGC_API_KEY", "key") + client := lifecycleClient(succeeded, "") + + res := runClusterValidator(context.Background(), client, "nvcr.io/nvidia/validator:1", + "", false, clusterValidatorControlPlaneRole, nil, nil) + require.NoError(t, res.Err) + require.NotEmpty(t, res.RunID) + assert.Empty(t, leftovers(t, client, res.RunID)) +} + +// A bootstrap that fails after the ServiceAccount exists must not abandon it. +func TestRunClusterValidator_FailedBootstrapLeavesNothingBehind(t *testing.T) { + client := fake.NewSimpleClientset() + client.PrependReactor("create", "clusterroles", func(ktesting.Action) (bool, runtime.Object, error) { + return true, nil, apierrors.NewForbidden( + schema.GroupResource{Group: rbacv1.GroupName, Resource: "clusterroles"}, "", nil) + }) + + res := runClusterValidator(context.Background(), client, "nvcr.io/nvidia/validator:1", + "", false, clusterValidatorComputePlaneRole, nil, nil) + require.Error(t, res.Err) + sas, err := client.CoreV1().ServiceAccounts(clusterValidatorNamespace).List(context.Background(), metav1.ListOptions{}) + require.NoError(t, err) + assert.Empty(t, sas.Items, "the ServiceAccount created before the failure must be removed") +} + +// Under --no-cleanup a failed bootstrap keeps what it created, so the result +// still carries the run ID the removal command selects on. +func TestRunClusterValidator_NoCleanupFailedBootstrapReportsTheRun(t *testing.T) { + client := fake.NewSimpleClientset() + client.PrependReactor("create", "clusterroles", func(ktesting.Action) (bool, runtime.Object, error) { + return true, nil, apierrors.NewForbidden( + schema.GroupResource{Group: rbacv1.GroupName, Resource: "clusterroles"}, "", nil) + }) + + res := runClusterValidator(context.Background(), client, "nvcr.io/nvidia/validator:1", + "", true, clusterValidatorComputePlaneRole, nil, nil) + require.Error(t, res.Err) + require.NotEmpty(t, res.RunID) + assert.Len(t, leftovers(t, client, res.RunID)["ServiceAccount"], 1) +} + +// A pull failure means the container never started, so nothing uses the RBAC, +// the NGC pull secret or the ConfigMap: the Job is deleted and all of them go +// now, instead of waiting 30 minutes for another run's orphan sweep. +func TestRunClusterValidator_PullFailureLeavesNothingBehind(t *testing.T) { + t.Setenv("NGC_API_KEY", "key") + client := lifecycleClient(running, "ImagePullBackOff") + + res := runClusterValidator(context.Background(), client, "nvcr.io/nvidia/validator:1", + "", false, clusterValidatorControlPlaneRole, nil, nil) + require.Error(t, res.Err) + assert.Empty(t, leftovers(t, client, res.RunID)) + created, deleted := "", "" + for _, a := range client.Actions() { + switch { + case a.GetVerb() == "create" && a.GetResource().Resource == "jobs": + created = a.(ktesting.CreateAction).GetObject().(*batchv1.Job).Name + case a.GetVerb() == "delete" && a.GetResource().Resource == "jobs": + deleted = a.(ktesting.DeleteAction).GetName() + } + } + require.NotEmpty(t, created) + assert.Equal(t, created, deleted, "the Job must be deleted so its pod stops retrying the pull") + assert.Empty(t, res.JobName, "no hint may point at the Job that was just deleted") +} + +// While the pod may still be running its RBAC must stay, but the Job owns the +// pull secret and ConfigMap so its deadline and TTL take them too. +func TestRunClusterValidator_RunningPodKeepsRBACAndJobOwnsArtifacts(t *testing.T) { + t.Setenv("NGC_API_KEY", "key") + client := lifecycleClient(running, "") + ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond) + defer cancel() + + res := runClusterValidator(ctx, client, "nvcr.io/nvidia/validator:1", + "", false, clusterValidatorControlPlaneRole, nil, nil) + require.Error(t, res.Err) + assert.True(t, res.LeftBehind, "the caller prints the command that removes what was kept") + left := leftovers(t, client, res.RunID) + assert.Len(t, left["ServiceAccount"], 1, "a running pod must keep its RBAC") + require.Len(t, left["Secret"], 1) + require.Len(t, left["ConfigMap"], 1) + + secret, err := client.CoreV1().Secrets(clusterValidatorNamespace).Get(context.Background(), left["Secret"][0], metav1.GetOptions{}) + require.NoError(t, err) + require.Len(t, secret.OwnerReferences, 1) + assert.Equal(t, "Job", secret.OwnerReferences[0].Kind) + assert.Equal(t, res.JobName, secret.OwnerReferences[0].Name) + cm, err := client.CoreV1().ConfigMaps(clusterValidatorNamespace).Get(context.Background(), left["ConfigMap"][0], metav1.GetOptions{}) + require.NoError(t, err) + require.Len(t, cm.OwnerReferences, 1) + assert.Equal(t, res.JobName, cm.OwnerReferences[0].Name) +} + +// The deferred sweep removes only this run's Secret. A concurrent same-role +// run's Secret must survive it, or that run's pod fails its pull mid-flight. +func TestRunClusterValidator_SparesConcurrentSameRoleSecret(t *testing.T) { + t.Setenv("NGC_API_KEY", "key") + other := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: validatorPullSecretRunName(clusterValidatorComputePlaneRole, "other"), + Namespace: clusterValidatorNamespace, + Labels: clusterValidatorRunLabels(clusterValidatorComputePlaneRole, "other", false), + CreationTimestamp: metav1.Now(), + }, + Type: corev1.SecretTypeDockerConfigJson, + Data: map[string][]byte{corev1.DockerConfigJsonKey: []byte(`{"auths":{}}`)}, + } + client := lifecycleClient(succeeded, "") + _, err := client.CoreV1().Secrets(clusterValidatorNamespace).Create(context.Background(), other, metav1.CreateOptions{}) + require.NoError(t, err) + + res := runClusterValidator(context.Background(), client, "nvcr.io/nvidia/validator:1", + "", false, clusterValidatorComputePlaneRole, nil, nil) + require.NoError(t, res.Err) + _, err = client.CoreV1().Secrets(clusterValidatorNamespace).Get(context.Background(), other.Name, metav1.GetOptions{}) + assert.NoError(t, err, "another run's Secret must survive this run's sweep") +} + +// --no-cleanup keeps everything, labelled with the run so the printed command +// removes exactly that run. +func TestRunClusterValidator_NoCleanupKeepsRunAndPrintsTheCommand(t *testing.T) { + t.Setenv("NGC_API_KEY", "key") + client := lifecycleClient(succeeded, "") + + res := runClusterValidator(context.Background(), client, "nvcr.io/nvidia/validator:1", + "", true, clusterValidatorControlPlaneRole, nil, nil) + require.NoError(t, res.Err) + left := leftovers(t, client, res.RunID) + for _, kind := range []string{"ServiceAccount", "ClusterRole", "ClusterRoleBinding", "Secret", "ConfigMap"} { + assert.Len(t, left[kind], 1, kind) + } + hint := validatorCleanupHint("ctx-a", res.RunID) + assert.Contains(t, hint, clusterValidatorRunLabel+"="+res.RunID) + assert.Contains(t, hint, "clusterrole,clusterrolebinding") + assert.Contains(t, hint, "--context ctx-a") +} + +func stampAge(o metav1.Object, age time.Duration) { + o.SetCreationTimestamp(metav1.NewTime(time.Now().Add(-age))) +} + +// Preserved objects are kept for a day, not forever, and the orphan sweep +// reclaims a preserved Job too. Objects labelled by a released CLI, including +// its fixed legacy name, are never touched. +func TestSweepOrphanClusterValidatorRBAC_PreservedExpireAndLegacyIsSpared(t *testing.T) { + name := func(run string) string { return clusterValidatorRBACName(clusterValidatorControlPlaneRole, run) } + sa := func(n string, lbls map[string]string, age time.Duration) *corev1.ServiceAccount { + o := &corev1.ServiceAccount{ObjectMeta: metav1.ObjectMeta{Name: n, Namespace: clusterValidatorNamespace, Labels: lbls}} + stampAge(o, age) + return o + } + job := func(n string, lbls map[string]string, age time.Duration) *batchv1.Job { + o := &batchv1.Job{ObjectMeta: metav1.ObjectMeta{Name: n, Namespace: clusterValidatorNamespace, Labels: lbls}} + stampAge(o, age) + return o + } + legacy := map[string]string{ + "app.kubernetes.io/name": clusterValidatorAppLabel, "app.kubernetes.io/managed-by": "nvcf-cli", + "app.kubernetes.io/component": "preflight", + } + client := fake.NewSimpleClientset( + sa(name("old-kept"), clusterValidatorRunLabels(clusterValidatorControlPlaneRole, "old-kept", true), 25*time.Hour), + sa(name("new-kept"), clusterValidatorRunLabels(clusterValidatorControlPlaneRole, "new-kept", true), time.Hour), + sa(clusterValidatorName, legacy, 1000*time.Hour), + job(clusterValidatorName+"-1", clusterValidatorRunLabels(clusterValidatorControlPlaneRole, "old-kept", true), 25*time.Hour), + job(clusterValidatorName+"-2", clusterValidatorRunLabels(clusterValidatorControlPlaneRole, "new-kept", true), time.Hour), + ) + + sweepOrphanClusterValidatorRBAC(context.Background(), client, orphanValidatorRBACTTL) + + ctx := context.Background() + _, err := client.CoreV1().ServiceAccounts(clusterValidatorNamespace).Get(ctx, name("old-kept"), metav1.GetOptions{}) + assert.True(t, apierrors.IsNotFound(err), "a preserved object older than a day is reclaimed") + _, err = client.CoreV1().ServiceAccounts(clusterValidatorNamespace).Get(ctx, name("new-kept"), metav1.GetOptions{}) + assert.NoError(t, err, "a preserved object younger than a day is kept") + _, err = client.CoreV1().ServiceAccounts(clusterValidatorNamespace).Get(ctx, clusterValidatorName, metav1.GetOptions{}) + assert.NoError(t, err, "a released CLI's fixed-name object is never touched") + _, err = client.BatchV1().Jobs(clusterValidatorNamespace).Get(ctx, clusterValidatorName+"-1", metav1.GetOptions{}) + assert.True(t, apierrors.IsNotFound(err), "a preserved Job older than a day is reclaimed") + _, err = client.BatchV1().Jobs(clusterValidatorNamespace).Get(ctx, clusterValidatorName+"-2", metav1.GetOptions{}) + assert.NoError(t, err) +} + +// Forwarded settings reach the container in a stable order, empty values are +// dropped, and nothing forwarded can override what the Job itself sets. +func TestBuildClusterValidatorJob_ForwardsEnvWithoutOverridingCore(t *testing.T) { + job := buildClusterValidatorJob("j", "img:1", "", clusterValidatorControlPlaneRole, "runid", false, map[string]string{ + "NVCF_OPENBAO_NAMESPACE": "openbao", + "VALIDATOR_POST_INSTALL": "true", + "VALIDATOR_ROLE": "compute-plane", + "NVCF_GATEWAY_NAMES": "", + }) + env := map[string]string{} + var order []string + for _, e := range job.Spec.Template.Spec.Containers[0].Env { + env[e.Name] = e.Value + order = append(order, e.Name) + } + assert.Equal(t, "openbao", env["NVCF_OPENBAO_NAMESPACE"]) + assert.Equal(t, "true", env["VALIDATOR_POST_INSTALL"]) + assert.Equal(t, clusterValidatorControlPlaneRole, env["VALIDATOR_ROLE"], "a forwarded value must not override the role") + _, hasEmpty := env["NVCF_GATEWAY_NAMES"] + assert.False(t, hasEmpty, "empty values are not forwarded") + assert.Equal(t, []string{"VALIDATOR_CONFIG_NAMESPACE", "VALIDATOR_CONFIG_NAME", "VALIDATOR_PREFLIGHT", + "VALIDATOR_ROLE", "NVCF_OPENBAO_NAMESPACE", "VALIDATOR_POST_INSTALL"}, order) +} + +// The validator discovers NVCF Gateways from every route kind and reads the +// Gateways' classes, so the CLI's ClusterRole must allow all of them. +func TestEnsureClusterValidatorRBAC_GrantsGatewayAPIReads(t *testing.T) { + client := fake.NewSimpleClientset() + require.NoError(t, ensureClusterValidatorRBAC(context.Background(), client, clusterValidatorControlPlaneRole, "runid", false)) + cr, err := client.RbacV1().ClusterRoles().Get(context.Background(), + clusterValidatorRBACName(clusterValidatorControlPlaneRole, "runid"), metav1.GetOptions{}) + require.NoError(t, err) + granted := map[string]bool{} + for _, r := range cr.Rules { + for _, g := range r.APIGroups { + if g != "gateway.networking.k8s.io" { + continue + } + for _, res := range r.Resources { + granted[res] = true + } + } + } + for _, res := range []string{"gateways", "httproutes", "grpcroutes", "tcproutes", "udproutes"} { + assert.True(t, granted[res], res) + } +} + +// The control-plane validator receives the enumerated registries and the +// forwarded env; the compute-plane one gets the env but no registries. +func TestClusterValidatorCheck_PassesRegistriesAndEnv(t *testing.T) { + var got ClusterValidatorParams + cv := func(_ context.Context, p ClusterValidatorParams) ClusterValidatorResult { + got = p + return ClusterValidatorResult{Passed: true} + } + regs := []RegistryEntry{{Registry: "harbor.corp.example"}} + env := map[string]string{"VALIDATOR_POST_INSTALL": "true"} + clusterValidatorCheck(cv, "", "img:1", "", false, validatorRoleControlPlane, regs, env).Run(context.Background()) + assert.Equal(t, regs, got.Registries) + assert.Equal(t, env, got.Env) +} + +// An image that predates validator roles runs the GPU checks on the control +// plane and fails. Without the role line in its transcript that is the image +// being too old, not the cluster failing, so it is a warning. +func TestClusterValidatorCheck_PreRoleImageIsAWarning(t *testing.T) { + run := func(logs string, passed bool) CheckResult { + cv := func(context.Context, ClusterValidatorParams) ClusterValidatorResult { + return ClusterValidatorResult{Passed: passed, ExitCode: 1, Logs: logs} + } + return clusterValidatorCheck(cv, "", "img:1", "", false, validatorRoleControlPlane, nil, nil).Run(context.Background()) + } + + legacy := func(summaryRows ...string) string { + return "Starting NVCF cluster validation\n=== GPU Resources ===\nno GPUs found\n" + + "=== Validation Summary ===\nCheck Results:\n" + strings.Join(summaryRows, "\n") + + "\nCluster is NVCF-Not-Ready\n" + } + old := run(legacy("\u2713 Control Plane: Healthy", "\u2717 GPU Resources: Not Available"), false) + assert.Equal(t, SeverityWarning, old.Severity) + assert.Contains(t, old.Message, "does not support the control-plane checks") + + // The old image's checks common to both roles still see real failures, + // and those must not be downgraded with the GPU row. + readyz := run(legacy("\u2717 Control Plane: Unhealthy", "\u2717 GPU Resources: Not Available"), false) + assert.Equal(t, SeverityError, readyz.Severity) + assert.Contains(t, readyz.Message, "Control Plane: Unhealthy") + assert.NotContains(t, readyz.Message, "GPU Resources") + + noSummary := run("Starting NVCF cluster validation\n=== GPU Resources ===\nno GPUs found\n", false) + assert.Equal(t, SeverityError, noSummary.Severity, "without its summary nothing shows the rest passed") + + // A role-aware image that fails before printing its role line prints no + // GPU section either, so its failure must stand. + early := run("level=fatal msg=\"Failed to create Kubernetes client\"\n", false) + assert.Equal(t, SeverityError, early.Severity, "a missing role line alone is not proof of an old image") + + current := run("Starting NVCF cluster validation\nValidator role: control-plane\nTier-1: failed\n", false) + assert.Equal(t, SeverityError, current.Severity, "a role-aware image's failure is a real failure") + + unread := run("", false) + assert.Equal(t, SeverityError, unread.Severity, "an empty transcript proves nothing about the image") +} + +// An interrupt cancels the caller's context. The operator abandoned the run, +// so the Job is stopped and everything reclaimed now, rather than leaving a +// cluster-wide ClusterRole bound in default until a later orphan sweep. +func TestRunClusterValidator_InterruptLeavesNothingBehind(t *testing.T) { + t.Setenv("NGC_API_KEY", "key") + client := lifecycleClient(running, "") + ctx, cancel := context.WithCancel(context.Background()) + go func() { + time.Sleep(200 * time.Millisecond) + cancel() + }() + + res := runClusterValidator(ctx, client, "nvcr.io/nvidia/validator:1", + "", false, clusterValidatorControlPlaneRole, nil, nil) + require.Error(t, res.Err) + assert.Empty(t, leftovers(t, client, res.RunID)) + deleted := false + for _, a := range client.Actions() { + if a.GetVerb() == "delete" && a.GetResource().Resource == "jobs" { + deleted = true + } + } + assert.True(t, deleted, "the Job must be stopped so its pod stops using the RBAC") +} + +// The run's ClusterRole grants only what the validator calls. It never creates +// Services, reads pod logs or watches anything. +func TestEnsureClusterValidatorRBAC_LeastPrivilege(t *testing.T) { + client := fake.NewSimpleClientset() + require.NoError(t, ensureClusterValidatorRBAC(context.Background(), client, clusterValidatorControlPlaneRole, "runid", false)) + cr, err := client.RbacV1().ClusterRoles().Get(context.Background(), + clusterValidatorRBACName(clusterValidatorControlPlaneRole, "runid"), metav1.GetOptions{}) + require.NoError(t, err) + sawEvents := false + for _, r := range cr.Rules { + for _, v := range r.Verbs { + assert.NotEqual(t, "watch", v, "no rule needs watch") + } + for _, res := range r.Resources { + assert.NotEqual(t, "pods/log", res) + if res == "services" { + assert.ElementsMatch(t, []string{"get", "list"}, r.Verbs, "services are only read") + } + if res == "events" { + assert.Equal(t, []string{"list"}, r.Verbs, "the probe only lists its pods' events") + sawEvents = true + } + } + } + assert.True(t, sawEvents, "the overlay probe needs to list events") +} + +// Only the control-plane Job reads a network-check ConfigMap. The compute-plane +// Job gets a non-empty name that resolves to nothing: an empty value would let +// the validator fall back to its default name and pick up control-plane config. +func TestBuildClusterValidatorJob_ConfigNamePerRole(t *testing.T) { + configName := func(role string) string { + job := buildClusterValidatorJob("j", "img:1", "", role, "runid", false, nil) + for _, e := range job.Spec.Template.Spec.Containers[0].Env { + if e.Name == "VALIDATOR_CONFIG_NAME" { + return e.Value + } + } + return "" + } + assert.Equal(t, clusterValidatorConfigRunName("runid"), configName(clusterValidatorControlPlaneRole)) + cp := configName(clusterValidatorComputePlaneRole) + assert.Equal(t, clusterValidatorNoConfigName, cp) + assert.NotEqual(t, clusterValidatorConfigName, cp, "must not be the validator's default name") +} + +// An apiserver that stops answering after the bootstrap fails must not hold +// the CLI open in the deferred sweeps: each one is bounded, and the run +// returns once they time out. +func TestRunClusterValidator_CleanupIsBoundedWhenTheAPIServerHangs(t *testing.T) { + prev := validatorCleanupTimeout + validatorCleanupTimeout = 500 * time.Millisecond + t.Cleanup(func() { validatorCleanupTimeout = prev }) + + var hang atomic.Bool + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if hang.Load() { + <-r.Context().Done() + return + } + w.Header().Set("Content-Type", "application/json") + switch { + case r.Method == http.MethodPost && strings.HasSuffix(r.URL.Path, "/clusterroles"): + // The bootstrap fails here, after its ServiceAccount exists, and + // the apiserver goes silent for everything after. + hang.Store(true) + w.WriteHeader(http.StatusForbidden) + _, _ = io.WriteString(w, `{"kind":"Status","apiVersion":"v1","status":"Failure","reason":"Forbidden","code":403}`) + case r.Method == http.MethodPost: + body, _ := io.ReadAll(r.Body) + w.WriteHeader(http.StatusCreated) + _, _ = w.Write(body) + default: + w.WriteHeader(http.StatusInternalServerError) + _, _ = io.WriteString(w, `{"kind":"Status","apiVersion":"v1","status":"Failure","code":500}`) + } + })) + t.Cleanup(func() { + srv.CloseClientConnections() + srv.Close() + }) + // JSON, so the server can echo a created object back as the response. + client, err := kubernetes.NewForConfig(&rest.Config{ + Host: srv.URL, + ContentConfig: rest.ContentConfig{ContentType: "application/json"}, + }) + require.NoError(t, err) + + done := make(chan ClusterValidatorResult, 1) + go func() { + done <- runClusterValidator(context.Background(), client, "registry.example.com/validator:1", + "", false, clusterValidatorComputePlaneRole, nil, nil) + }() + select { + case res := <-done: + require.Error(t, res.Err) + assert.Contains(t, res.Err.Error(), "create cluster role", "the run must reach the hang, not fail earlier") + assert.True(t, hang.Load()) + case <-time.After(10 * time.Second): + t.Fatal("the deferred cleanup did not return against a hung apiserver") + } +} + +// A Job that succeeds with the validator's own warnings is not a clean pass. A +// rollout in progress is marked transient, so --wait keeps polling on it; any +// other warning is not, so --wait does not poll forever on it. +func TestClusterValidatorCheck_PassWithWarnings(t *testing.T) { + run := func(logs string) CheckResult { + cv := func(context.Context, ClusterValidatorParams) ClusterValidatorResult { + return ClusterValidatorResult{Passed: true, Logs: logs} + } + return clusterValidatorCheck(cv, "", "img:1", "", false, validatorRoleControlPlane, nil, nil).Run(context.Background()) + } + base := "Validator role: control-plane\n" + + rolling := run(base + "Tier-2 StatefulSets: nats-system/nats: rolling update in progress (ready: 2/3)\n" + + "Cluster is NVCF-Ready (with warnings)\n") + assert.False(t, rolling.Passed) + assert.Equal(t, SeverityWarning, rolling.Severity) + assert.True(t, rolling.Transient) + + other := run(base + "Node-to-Node: not applicable (one Ready, schedulable node)\nCluster is NVCF-Ready (with warnings)\n") + assert.Equal(t, SeverityWarning, other.Severity) + assert.False(t, other.Transient, "a permanent warning must not keep --wait polling") + + clean := run(base + "Cluster is NVCF-Ready\n") + assert.True(t, clean.Passed) + assert.Equal(t, SeverityInfo, clean.Severity) +} + +// jobDeletes returns the DeleteOptions of every Job delete the client saw. +func jobDeletes(client *fake.Clientset) []metav1.DeleteOptions { + var out []metav1.DeleteOptions + for _, a := range client.Actions() { + if d, ok := a.(ktesting.DeleteActionImpl); ok && d.GetResource().Resource == "jobs" { + out = append(out, d.DeleteOptions) + } + } + return out +} + +// An interrupt stops the Job in the foreground, so its pod gets SIGTERM and +// its grace period while it still has the RBAC it needs to remove its own +// probe resources. Background propagation revoked that RBAC first. +func TestRunClusterValidator_InterruptStopsTheJobInTheForeground(t *testing.T) { + t.Setenv("NGC_API_KEY", "key") + client := lifecycleClient(running, "") + ctx, cancel := context.WithCancel(context.Background()) + client.PrependReactor("create", "jobs", func(ktesting.Action) (bool, runtime.Object, error) { + cancel() + return false, nil, nil + }) + res := runClusterValidator(ctx, client, "nvcr.io/nvidia/validator:1", + "", false, clusterValidatorComputePlaneRole, nil, nil) + require.Error(t, res.Err) + deletes := jobDeletes(client) + require.NotEmpty(t, deletes) + require.NotNil(t, deletes[0].PropagationPolicy) + assert.Equal(t, metav1.DeletePropagationForeground, *deletes[0].PropagationPolicy) + assert.Empty(t, leftovers(t, client, res.RunID), "the pod ended, so everything else goes") +} + +// If the stopped pod does not end within the bound, its RBAC stays rather than +// being pulled from under it, and the caller is told what was kept. +func TestRunClusterValidator_PodThatWillNotEndKeepsItsRBAC(t *testing.T) { + t.Setenv("NGC_API_KEY", "key") + client := lifecycleClient(running, "") + client.PrependReactor("list", "pods", func(action ktesting.Action) (bool, runtime.Object, error) { + sel := action.(ktesting.ListAction).GetListRestrictions().Labels.String() + return true, &corev1.PodList{Items: []corev1.Pod{{ + ObjectMeta: metav1.ObjectMeta{Name: "stuck", Namespace: clusterValidatorNamespace, + Labels: map[string]string{"job-name": strings.TrimPrefix(sel, "job-name=")}}, + Status: corev1.PodStatus{Phase: corev1.PodRunning}, + }}}, nil + }) + ctx, cancel := context.WithCancel(context.Background()) + client.PrependReactor("create", "jobs", func(ktesting.Action) (bool, runtime.Object, error) { + cancel() + return false, nil, nil + }) + res := runClusterValidator(ctx, client, "nvcr.io/nvidia/validator:1", + "", false, clusterValidatorComputePlaneRole, nil, nil) + require.Error(t, res.Err) + assert.True(t, res.LeftBehind) + assert.Len(t, leftovers(t, client, res.RunID)["ClusterRole"], 1) +} + +// On a plain timeout the Job's own deadline ends the pod shortly after; once it +// has, the RBAC is swept as on success instead of waiting for a later sweep. +func TestRunClusterValidator_TimeoutSweepsOnceThePodEnds(t *testing.T) { + t.Setenv("NGC_API_KEY", "key") + client := lifecycleClient(running, "") + var ended atomic.Bool + client.PrependReactor("list", "pods", func(action ktesting.Action) (bool, runtime.Object, error) { + if !ended.Load() { + return false, nil, nil + } + return true, &corev1.PodList{Items: []corev1.Pod{{Status: corev1.PodStatus{Phase: corev1.PodFailed}}}}, nil + }) + prev := clusterValidatorTimeout + clusterValidatorTimeout = 300 * time.Millisecond + t.Cleanup(func() { clusterValidatorTimeout = prev }) + go func() { + time.Sleep(time.Second) + ended.Store(true) + }() + + res := runClusterValidator(context.Background(), client, "nvcr.io/nvidia/validator:1", + "", false, clusterValidatorComputePlaneRole, nil, nil) + require.Error(t, res.Err) + assert.False(t, res.LeftBehind) + assert.Empty(t, leftovers(t, client, res.RunID)["ClusterRole"], "the deadline ended the pod, so its RBAC goes") +} + +// Under --no-cleanup the kept Secret and ConfigMap are not owned by the kept +// Job: re-running that Job by replacing it would garbage-collect them. A pull +// failure suspends the kept Job, so its pod stops retrying forever, and an +// interrupt keeps everything. +func TestRunClusterValidator_NoCleanupKeepsArtifactsUnowned(t *testing.T) { + t.Setenv("NGC_API_KEY", "key") + client := lifecycleClient(succeeded, "") + res := runClusterValidator(context.Background(), client, "nvcr.io/nvidia/validator:1", + "", true, clusterValidatorControlPlaneRole, nil, nil) + require.NoError(t, res.Err) + left := leftovers(t, client, res.RunID) + require.Len(t, left["Secret"], 1) + require.Len(t, left["ConfigMap"], 1) + sec, err := client.CoreV1().Secrets(clusterValidatorNamespace).Get(context.Background(), left["Secret"][0], metav1.GetOptions{}) + require.NoError(t, err) + assert.Empty(t, sec.OwnerReferences) + cm, err := client.CoreV1().ConfigMaps(clusterValidatorNamespace).Get(context.Background(), left["ConfigMap"][0], metav1.GetOptions{}) + require.NoError(t, err) + assert.Empty(t, cm.OwnerReferences) + + pulling := lifecycleClient(running, "ImagePullBackOff") + res = runClusterValidator(context.Background(), pulling, "nvcr.io/nvidia/validator:1", + "", true, clusterValidatorComputePlaneRole, nil, nil) + require.Error(t, res.Err) + assert.Empty(t, jobDeletes(pulling), "a kept Job is not deleted") + suspended := false + for _, a := range pulling.Actions() { + if p, ok := a.(ktesting.PatchAction); ok && p.GetResource().Resource == "jobs" && + strings.Contains(string(p.GetPatch()), `"suspend":true`) { + suspended = true + } + } + assert.True(t, suspended, "the kept Job must stop retrying the pull") + + interrupted := lifecycleClient(running, "") + ctx, cancel := context.WithCancel(context.Background()) + interrupted.PrependReactor("create", "jobs", func(ktesting.Action) (bool, runtime.Object, error) { + cancel() + return false, nil, nil + }) + res = runClusterValidator(ctx, interrupted, "nvcr.io/nvidia/validator:1", + "", true, clusterValidatorComputePlaneRole, nil, nil) + require.Error(t, res.Err) + assert.Empty(t, jobDeletes(interrupted), "--no-cleanup keeps the Job even on an interrupt") +} + +// The operator's own pull Secret is never made a dependent of the Job: only +// the Secret this run minted is, so a Job TTL cannot garbage-collect theirs. +func TestRunClusterValidator_OperatorPullSecretIsNeverOwned(t *testing.T) { + client := lifecycleClient(succeeded, "") + _, err := client.CoreV1().Secrets(clusterValidatorNamespace).Create(context.Background(), &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{Name: "my-pull", Namespace: clusterValidatorNamespace}, + Type: corev1.SecretTypeDockerConfigJson, + Data: map[string][]byte{corev1.DockerConfigJsonKey: []byte(`{"auths":{}}`)}, + }, metav1.CreateOptions{}) + require.NoError(t, err) + res := runClusterValidator(context.Background(), client, "nvcr.io/nvidia/validator:1", + "my-pull", false, clusterValidatorComputePlaneRole, nil, nil) + require.NoError(t, res.Err) + sec, err := client.CoreV1().Secrets(clusterValidatorNamespace).Get(context.Background(), "my-pull", metav1.GetOptions{}) + require.NoError(t, err) + assert.Empty(t, sec.OwnerReferences) +} + +// One failed Job read is not the Job's result, and a first pull error is what +// a transient registry blip looks like: both are retried. A malformed image +// reference never pulls, so it ends the wait at once. +func TestWaitForClusterValidatorJob_TransientErrorsAreRetried(t *testing.T) { + client := lifecycleClient(succeeded, "") + calls := 0 + client.PrependReactor("get", "jobs", func(ktesting.Action) (bool, runtime.Object, error) { + calls++ + if calls <= 2 { + return true, nil, apierrors.NewInternalError(fmt.Errorf("etcd timeout")) + } + return false, nil, nil + }) + _, err := client.BatchV1().Jobs(clusterValidatorNamespace).Create(context.Background(), + &batchv1.Job{ObjectMeta: metav1.ObjectMeta{Name: "j", Namespace: clusterValidatorNamespace}}, metav1.CreateOptions{}) + require.NoError(t, err) + job, err := waitForClusterValidatorJob(context.Background(), client, "j") + require.NoError(t, err) + assert.Equal(t, int32(1), job.Status.Succeeded) + + prev := validatorPullFailureGrace + validatorPullFailureGrace = time.Hour + t.Cleanup(func() { validatorPullFailureGrace = prev }) + pulling := lifecycleClient(running, "ErrImagePull") + _, err = pulling.BatchV1().Jobs(clusterValidatorNamespace).Create(context.Background(), + &batchv1.Job{ObjectMeta: metav1.ObjectMeta{Name: "j", Namespace: clusterValidatorNamespace}}, metav1.CreateOptions{}) + require.NoError(t, err) + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + _, err = waitForClusterValidatorJob(ctx, pulling, "j") + var pullErr *validatorImagePullError + assert.False(t, errors.As(err, &pullErr), "a pull error inside the grace period is retried") + + invalid := lifecycleClient(running, "InvalidImageName") + _, err = invalid.BatchV1().Jobs(clusterValidatorNamespace).Create(context.Background(), + &batchv1.Job{ObjectMeta: metav1.ObjectMeta{Name: "j", Namespace: clusterValidatorNamespace}}, metav1.CreateOptions{}) + require.NoError(t, err) + _, err = waitForClusterValidatorJob(context.Background(), invalid, "j") + assert.True(t, errors.As(err, &pullErr), "a malformed reference never pulls") +} + +// A run that times out with a pod that will not end returns within +// ClusterValidatorRunCeiling, which the check's budget is sized from. The +// ceiling counts every wait the timeout path makes, so shortening one of them +// here cannot leave the budget short in production. +func TestClusterValidatorRunCeiling_CoversTheTimeoutPath(t *testing.T) { + t.Setenv("NGC_API_KEY", "key") + client := lifecycleClient(running, "") + client.PrependReactor("list", "pods", func(action ktesting.Action) (bool, runtime.Object, error) { + sel := action.(ktesting.ListAction).GetListRestrictions().Labels.String() + return true, &corev1.PodList{Items: []corev1.Pod{{ + ObjectMeta: metav1.ObjectMeta{Name: "stuck", Namespace: clusterValidatorNamespace, + Labels: map[string]string{"job-name": strings.TrimPrefix(sel, "job-name=")}}, + Status: corev1.PodStatus{Phase: corev1.PodRunning}, + }}}, nil + }) + // The deadline grace dominates the margin, so a ceiling that leaves it + // out is short of the run. + prevTimeout, prevGrace, prevLogs, prevMargin := + clusterValidatorTimeout, validatorDeadlineGrace, clusterValidatorLogFetchTimeout, validatorRunMargin + clusterValidatorTimeout, validatorDeadlineGrace, clusterValidatorLogFetchTimeout, validatorRunMargin = + 300*time.Millisecond, 3*time.Second, 500*time.Millisecond, time.Second + t.Cleanup(func() { + clusterValidatorTimeout, validatorDeadlineGrace, clusterValidatorLogFetchTimeout, validatorRunMargin = + prevTimeout, prevGrace, prevLogs, prevMargin + }) + + start := time.Now() + res := runClusterValidator(context.Background(), client, "nvcr.io/nvidia/validator:1", + "", false, clusterValidatorComputePlaneRole, nil, nil) + require.Error(t, res.Err) + assert.True(t, res.LeftBehind, "the pod never ended, so the whole grace was spent") + assert.LessOrEqual(t, time.Since(start), ClusterValidatorRunCeiling()) +} diff --git a/src/clis/nvcf-cli/internal/selfhosted/clustervalidator_test.go b/src/clis/nvcf-cli/internal/selfhosted/clustervalidator_test.go index 70ef4a6a91..1fb135b3c2 100644 --- a/src/clis/nvcf-cli/internal/selfhosted/clustervalidator_test.go +++ b/src/clis/nvcf-cli/internal/selfhosted/clustervalidator_test.go @@ -99,38 +99,52 @@ func podListReactor(waitingReason string) ktesting.ReactionFunc { } } -func hasActionPrefix(actions []ktesting.Action, verb, resource string) bool { - for _, a := range actions { - if a.GetVerb() == verb && a.GetResource().Resource == resource { - return true - } - } - return false -} - func TestRunClusterValidator_EmptyImage(t *testing.T) { // In normal flow the caller gates on a configured image, so this // branch is defensive. Verify it returns a clear error and makes // no API calls. client := fake.NewSimpleClientset() - res := runClusterValidator(context.Background(), client, "", "", false) + res := runClusterValidator(context.Background(), client, "", "", false, "", nil, nil) require.Error(t, res.Err) assert.Contains(t, res.Err.Error(), "image is empty") assert.False(t, res.Passed) assert.Empty(t, client.Actions(), "no API calls should be made when input validation fails") } -func TestClusterValidatorCheck_OrchestratorErrorStaysWarning(t *testing.T) { +// A validator that could not run checked nothing, so it fails the run rather +// than letting a readiness gate pass. The message names the opt-out. +func TestClusterValidatorCheck_OrchestratorErrorFails(t *testing.T) { cv := func(_ context.Context, _ ClusterValidatorParams) ClusterValidatorResult { - return ClusterValidatorResult{Err: fmt.Errorf("transient: API server unreachable")} + return ClusterValidatorResult{Err: fmt.Errorf("bootstrapping validator RBAC: forbidden")} } - r := clusterValidatorCheck(cv, "", "", "", false).Run(context.Background()) + r := clusterValidatorCheck(cv, "", "", "", false, "", nil, nil).Run(context.Background()) assert.False(t, r.Passed) - assert.Equal(t, "warning", r.Severity, - "transient orchestrator failures should not fail the overall preflight") + assert.Equal(t, SeverityError, r.Severity) + assert.True(t, r.IsBlockingFailure()) assert.Contains(t, r.Message, "cluster-validator did not complete") + assert.Contains(t, r.Message, "forbidden") + assert.Contains(t, r.Message, "--skip-cluster-validation") } +// driveJobToSuccess wires the reactors that make a created Job reach a +// Succeeded terminal state, mirroring TestRunClusterValidator_HappyPath. +func driveJobToSuccess(client *fake.Clientset) { + var jobName atomic.Value + jobName.Store("") + client.PrependReactor("create", "jobs", func(action ktesting.Action) (bool, runtime.Object, error) { + job := action.(ktesting.CreateAction).GetObject().(*batchv1.Job) + jobName.Store(job.Name) + return false, nil, nil + }) + client.PrependReactor("get", "jobs", func(action ktesting.Action) (bool, runtime.Object, error) { + name := jobName.Load().(string) + if name == "" { + return false, nil, nil + } + return jobSucceededReactor(name)(action) + }) + client.PrependReactor("list", "pods", podListReactor("")) +} func TestRunClusterValidator_HappyPath(t *testing.T) { client := fake.NewSimpleClientset() @@ -169,26 +183,19 @@ func TestRunClusterValidator_HappyPath(t *testing.T) { }, nil }) - res := runClusterValidator(context.Background(), client, "test-image:1.0", "", false) + res := runClusterValidator(context.Background(), client, "test-image:1.0", "", false, "", nil, nil) require.NoError(t, res.Err, "happy path must not surface an error") assert.True(t, res.Passed, "Succeeded>0 maps to Passed=true") assert.Equal(t, int32(0), res.ExitCode) assert.NotEmpty(t, res.JobName, "JobName must be populated so operators can read logs after the run") - // Sweep must run before create so the singleton invariant holds. - actions := client.Actions() - require.True(t, hasActionPrefix(actions, "delete-collection", "jobs"), - "runClusterValidator must sweep prior Jobs before creating the new one") - var sweepIdx, createIdx = -1, -1 - for i, a := range actions { - if a.GetVerb() == "delete-collection" && a.GetResource().Resource == "jobs" && sweepIdx == -1 { - sweepIdx = i - } - if a.GetVerb() == "create" && a.GetResource().Resource == "jobs" && createIdx == -1 { - createIdx = i - } + // No Job is deleted on a clean run: there is no sweep of earlier Jobs, + // which could only ever hit an overlapping run's live one. The orphan + // sweep lists Jobs but reclaims only preserved ones past their TTL. + for _, a := range client.Actions() { + assert.False(t, a.GetVerb() == "delete" && a.GetResource().Resource == "jobs", + "a clean run deletes no Job") } - assert.Less(t, sweepIdx, createIdx, "sweep must precede create in action sequence") } func TestRunClusterValidator_JobFailed(t *testing.T) { @@ -204,73 +211,104 @@ func TestRunClusterValidator_JobFailed(t *testing.T) { }) client.PrependReactor("list", "pods", podListReactor("")) - res := runClusterValidator(context.Background(), client, "test-image:1.0", "", false) + res := runClusterValidator(context.Background(), client, "test-image:1.0", "", false, "", nil, nil) require.NoError(t, res.Err, "a clean Passed=false verdict must not set Err") assert.False(t, res.Passed, "Failed>0 maps to Passed=false") assert.NotEmpty(t, res.JobName, "JobName must be populated on failure for kubectl-logs follow-up") } -func TestRunClusterValidator_RBACIdempotent(t *testing.T) { +// Each run mints an unguessable RBAC name, so there is no idempotence left to +// test: a name collision means something else already holds the name this run +// is about to bind cluster-wide permissions to. That must fail, not be adopted. +// (Replaces TestRunClusterValidator_RBACIdempotent, which seeded objects under +// the old fixed name and so never produced the AlreadyExists it described.) +func TestRunClusterValidator_RBACNameCollisionIsNotAdopted(t *testing.T) { client := fake.NewSimpleClientset() - // Seed the cluster with the SA, ClusterRole, and ClusterRoleBinding so - // each Create returns AlreadyExists. The runner must treat that as - // success and proceed to Job creation. - existing := []ktesting.ReactionFunc{ - alreadyExistsReactor("serviceaccounts", clusterValidatorName), - alreadyExistsReactor("clusterroles", clusterValidatorName), - alreadyExistsReactor("clusterrolebindings", clusterValidatorName), - } - verbs := []string{"create"} - resources := []string{"serviceaccounts", "clusterroles", "clusterrolebindings"} - for i, r := range existing { - client.PrependReactor(verbs[0], resources[i], r) - } - - var jobName atomic.Value - jobName.Store("") - client.PrependReactor("create", "jobs", func(action ktesting.Action) (bool, runtime.Object, error) { - jobName.Store(action.(ktesting.CreateAction).GetObject().(*batchv1.Job).Name) - return false, nil, nil - }) - client.PrependReactor("get", "jobs", func(action ktesting.Action) (bool, runtime.Object, error) { - return jobSucceededReactor(jobName.Load().(string))(action) + client.PrependReactor("create", "clusterroles", func(action ktesting.Action) (bool, runtime.Object, error) { + name := action.(ktesting.CreateAction).GetObject().(*rbacv1.ClusterRole).Name + return true, nil, apierrors.NewAlreadyExists( + schema.GroupResource{Group: "rbac.authorization.k8s.io", Resource: "clusterroles"}, name) }) - client.PrependReactor("list", "pods", podListReactor("")) - res := runClusterValidator(context.Background(), client, "test-image:1.0", "", false) - require.NoError(t, res.Err, "AlreadyExists on RBAC bootstrap must be treated as success") - assert.True(t, res.Passed) + res := runClusterValidator(context.Background(), client, "nvcr.io/x/validator:1", + "", false, clusterValidatorControlPlaneRole, nil, nil) + require.Error(t, res.Err, "a pre-existing object under our generated name must not be adopted") + assert.Contains(t, res.Err.Error(), "bootstrapping validator RBAC") } -// Regression guard: a ClusterRole left over from an older CLI version must -// be refreshed with the current rule set instead of silently kept stale. -func TestRunClusterValidator_RBACRefreshesClusterRoleRules(t *testing.T) { - oldRules := []rbacv1.PolicyRule{ - {APIGroups: []string{""}, Resources: []string{"nodes"}, Verbs: []string{"get"}}, +func TestEnsureClusterValidatorRBAC_WritableResources(t *testing.T) { + client := fake.NewSimpleClientset() + ctx := context.Background() + + const role = clusterValidatorControlPlaneRole + const runID = "testrunid" + require.NoError(t, ensureClusterValidatorRBAC(ctx, client, role, runID, false)) + + cr, err := client.RbacV1().ClusterRoles().Get(ctx, clusterValidatorRBACName(role, runID), metav1.GetOptions{}) + require.NoError(t, err) + + type check struct { + group string + resource string + verb string + } + required := []check{ + // Enforcement checks create and delete probe namespaces. + {"", "namespaces", "create"}, + {"", "namespaces", "delete"}, + // Probe pods spun up for node-to-node and inter-namespace checks. + {"", "pods", "create"}, + {"", "pods", "delete"}, + // The external-LB check lists LoadBalancer Services. + {"", "services", "list"}, + // The node-to-node probe DaemonSet. + {"apps", "daemonsets", "create"}, + {"apps", "daemonsets", "delete"}, + // Enforcement check creates/updates/deletes NetworkPolicies in temp namespace. + {"networking.k8s.io", "networkpolicies", "create"}, + {"networking.k8s.io", "networkpolicies", "update"}, + {"networking.k8s.io", "networkpolicies", "delete"}, + // Gateway API health checks. + {"gateway.networking.k8s.io", "gateways", "list"}, + {"gateway.networking.k8s.io", "httproutes", "get"}, + {"gateway.networking.k8s.io", "grpcroutes", "list"}, } - client := fake.NewSimpleClientset(&rbacv1.ClusterRole{ - ObjectMeta: metav1.ObjectMeta{Name: clusterValidatorName}, - Rules: oldRules, - }) - var jobName atomic.Value - jobName.Store("") - client.PrependReactor("create", "jobs", func(action ktesting.Action) (bool, runtime.Object, error) { - jobName.Store(action.(ktesting.CreateAction).GetObject().(*batchv1.Job).Name) - return false, nil, nil - }) - client.PrependReactor("get", "jobs", func(action ktesting.Action) (bool, runtime.Object, error) { - return jobSucceededReactor(jobName.Load().(string))(action) - }) - client.PrependReactor("list", "pods", podListReactor("")) + for _, want := range required { + t.Run(fmt.Sprintf("%s/%s/%s", want.group, want.resource, want.verb), func(t *testing.T) { + assert.True(t, rbacRuleCovers(cr.Rules, want.group, want.resource, want.verb), + "ClusterRole must grant %s on %s (group %q)", want.verb, want.resource, want.group) + }) + } +} - res := runClusterValidator(context.Background(), client, "test-image:1.0", "", false) - require.NoError(t, res.Err) +// rbacRuleCovers returns true when any PolicyRule in rules grants verb on +// resource within group. Wildcard verbs ("*") are treated as matching any verb. +func rbacRuleCovers(rules []rbacv1.PolicyRule, group, resource, verb string) bool { + for _, r := range rules { + if len(r.NonResourceURLs) > 0 { + continue // non-resource rules don't apply to API resources + } + if !strSliceContains(r.APIGroups, group) { + continue + } + if !strSliceContains(r.Resources, resource) { + continue + } + if strSliceContains(r.Verbs, verb) || strSliceContains(r.Verbs, "*") { + return true + } + } + return false +} - got, err := client.RbacV1().ClusterRoles().Get(context.Background(), clusterValidatorName, metav1.GetOptions{}) - require.NoError(t, err) - assert.Greater(t, len(got.Rules), len(oldRules), - "ClusterRole rules must be refreshed to the current set on each run, not kept stale") +func strSliceContains(ss []string, s string) bool { + for _, v := range ss { + if v == s { + return true + } + } + return false } func TestRunClusterValidator_ImagePullBackOffShortCircuits(t *testing.T) { @@ -317,13 +355,13 @@ func TestRunClusterValidator_ImagePullBackOffShortCircuits(t *testing.T) { }) start := time.Now() - res := runClusterValidator(context.Background(), client, "test-image:1.0", "", false) + res := runClusterValidator(context.Background(), client, "test-image:1.0", "", false, "", nil, nil) elapsed := time.Since(start) require.Error(t, res.Err, "ImagePullBackOff must short-circuit the wait with an error") assert.Contains(t, res.Err.Error(), "ImagePullBackOff") assert.Contains(t, res.Err.Error(), "cannot pull image") - assert.NotEmpty(t, res.JobName, "JobName must still be populated so the operator can describe the pod") + assert.Empty(t, res.JobName, "the Job is deleted on a pull failure, so no logs hint may name it") assert.Less(t, elapsed, clusterValidatorTimeout, "short-circuit must return well before the 5-minute timeout; otherwise the early-detect path is broken") } @@ -355,7 +393,7 @@ func TestRunClusterValidator_LogFetchSurvivesValidatorTimeout(t *testing.T) { client.PrependReactor("list", "pods", podListReactor("")) // Parent ctx stays alive for the entire run; only vctx expires. - res := runClusterValidator(context.Background(), client, "test-image:1.0", "", false) + res := runClusterValidator(context.Background(), client, "test-image:1.0", "", false, "", nil, nil) require.Error(t, res.Err, "wait must surface the deadline-exceeded error") assert.Contains(t, res.Err.Error(), "waiting for job", @@ -408,13 +446,13 @@ func TestRunClusterValidator_ContextCanceled(t *testing.T) { time.Sleep(50 * time.Millisecond) cancel() }() - res := runClusterValidator(ctx, client, "test-image:1.0", "", false) + res := runClusterValidator(ctx, client, "test-image:1.0", "", false, "", nil, nil) require.Error(t, res.Err) assert.Contains(t, res.Err.Error(), "context") } func TestBuildClusterValidatorJobShape(t *testing.T) { - job := buildClusterValidatorJob("test-job", "img:1", "", false) + job := buildClusterValidatorJob("test-job", "img:1", "", "", "runid", false, nil) assert.Equal(t, "test-job", job.Name) assert.Equal(t, clusterValidatorNamespace, job.Namespace) @@ -431,13 +469,18 @@ func TestBuildClusterValidatorJobShape(t *testing.T) { "IfNotPresent so locally-imported images (k3d image import, kind load) are picked up without a registry pull") assert.Equal(t, corev1.RestartPolicyNever, job.Spec.Template.Spec.RestartPolicy, "validator is a one-shot command, not a long-running service") - assert.Equal(t, clusterValidatorName, job.Spec.Template.Spec.ServiceAccountName, - "pod must run under the CLI-bootstrapped SA, not the namespace default") + assert.Equal(t, clusterValidatorRBACName("", "runid"), job.Spec.Template.Spec.ServiceAccountName, + "pod must run under this run's bootstrapped SA, not the namespace default") labels := job.Labels assert.Equal(t, clusterValidatorAppLabel, labels["app.kubernetes.io/name"]) - assert.Equal(t, "nvcf-cli", labels["app.kubernetes.io/managed-by"]) + assert.NotEqual(t, "nvcf-cli", labels["app.kubernetes.io/managed-by"], + "released CLIs DeleteCollection on managed-by=nvcf-cli, so a shared value lets each version delete the other's objects") assert.Equal(t, "preflight", labels["app.kubernetes.io/component"]) + assert.Equal(t, "runid", labels[clusterValidatorRunLabel]) + assert.Equal(t, "runid", job.Spec.Template.Labels[clusterValidatorRunLabel]) + require.NotNil(t, job.Spec.ActiveDeadlineSeconds, + "without a deadline a Job whose pod never finishes never becomes terminal, so its TTL never fires") // The validator must run in preflight mode so it skips the summary // ConfigMap write (the metrics path needs the NVCA agent, which is not @@ -450,22 +493,35 @@ func TestBuildClusterValidatorJobShape(t *testing.T) { "preflight invocation must tag the validator so it does not attempt the metrics ConfigMap write") } +func TestBuildClusterValidatorJobShape_ValidatorRoleInEnv(t *testing.T) { + job := buildClusterValidatorJob("test-job", "img:1", "", clusterValidatorControlPlaneRole, "runid", false, nil) + env := map[string]string{} + for _, e := range job.Spec.Template.Spec.Containers[0].Env { + env[e.Name] = e.Value + } + assert.Equal(t, clusterValidatorControlPlaneRole, env["VALIDATOR_ROLE"], + "VALIDATOR_ROLE must carry the role to the validator binary") +} + func TestBuildClusterValidatorJobShape_WithPullSecret(t *testing.T) { - job := buildClusterValidatorJob("test-job", "img:1", "nvcr-pull-secret", false) + job := buildClusterValidatorJob("test-job", "img:1", "nvcr-pull-secret", "", "runid", false, nil) require.Len(t, job.Spec.Template.Spec.ImagePullSecrets, 1) assert.Equal(t, "nvcr-pull-secret", job.Spec.Template.Spec.ImagePullSecrets[0].Name) } func TestBuildClusterValidatorJobShape_NoPullSecret(t *testing.T) { - job := buildClusterValidatorJob("test-job", "img:1", "", false) + job := buildClusterValidatorJob("test-job", "img:1", "", "", "runid", false, nil) assert.Empty(t, job.Spec.Template.Spec.ImagePullSecrets, "empty pull-secret arg must not produce an empty-name ImagePullSecrets entry") } func TestBuildClusterValidatorJobShape_NoCleanup(t *testing.T) { - job := buildClusterValidatorJob("test-job", "img:1", "", true) + job := buildClusterValidatorJob("test-job", "img:1", "", "", "runid", true, nil) assert.Nil(t, job.Spec.TTLSecondsAfterFinished, "--no-cleanup must omit TTLSecondsAfterFinished so the Job persists for debugging") + assert.Nil(t, job.Spec.ActiveDeadlineSeconds, + "a deadline would kill the kept pod, and its logs, shortly after the CLI gives up") + assert.Equal(t, "true", job.Labels[clusterValidatorPreserveLabel]) } func TestCleanValidatorOutput_StripsANSI(t *testing.T) { @@ -509,30 +565,528 @@ func TestCleanValidatorOutput_EmptyInput(t *testing.T) { } func TestKubectlLogsHint(t *testing.T) { - got := kubectlLogsHint("nvcf-preflight-validator-12345") + got := kubectlLogsHint("", "nvcf-preflight-validator-12345") assert.Equal(t, "kubectl logs -n default job/nvcf-preflight-validator-12345 --tail=-1", got, ) } +// In ModeSplit the two roles run against different clusters, so the hint has +// to name the context the Job was actually created in. +func TestKubectlLogsHint_PinsContext(t *testing.T) { + got := kubectlLogsHint("gpu-ctx", "nvcf-preflight-validator-12345") + assert.Equal(t, + "kubectl --context gpu-ctx logs -n default job/nvcf-preflight-validator-12345 --tail=-1", + got, + ) +} + +func TestKubectlLogsHint_QuotesContext(t *testing.T) { + got := kubectlLogsHint("my ctx", "nvcf-preflight-validator-12345") + assert.Contains(t, got, "--context 'my ctx'", + "a context name with a space must be quoted so the pasted command does not split it") +} + func TestKubectlLogsHint_EmptyJob(t *testing.T) { - assert.Equal(t, "", kubectlLogsHint(""), + assert.Equal(t, "", kubectlLogsHint("gpu-ctx", ""), "empty jobName must produce empty hint so callers can compose detail without conditionals") } -func alreadyExistsReactor(resource, name string) ktesting.ReactionFunc { - gr := schema.GroupResource{Resource: resource} - return func(action ktesting.Action) (bool, runtime.Object, error) { - createAction, ok := action.(ktesting.CreateAction) - if !ok { - return false, nil, nil - } - meta, ok := createAction.GetObject().(metav1.Object) - if !ok || meta.GetName() != name { - return false, nil, nil - } - return true, nil, apierrors.NewAlreadyExists(gr, name) +// With no registries the reachability section is omitted rather than filled +// with a guess. +func TestBuildControlPlaneValidatorConfig_NoRegistries(t *testing.T) { + got := buildControlPlaneValidatorConfig(nil) + assert.NotContains(t, got, "reachability:") + assert.NotContains(t, got, "nvcr.io", "no hardcoded nvcr.io endpoint") + assert.Contains(t, got, "enforcement:", "enforcement block must be present") +} + +// The endpoints are exactly the enumerated registries, with the same +// criticality. A mirrored install whose list has no nvcr.io must not be made +// to dial it: a fixed critical nvcr.io failed every such control plane. +func TestBuildControlPlaneValidatorConfig_EndpointsAreNotCritical(t *testing.T) { + got := buildControlPlaneValidatorConfig([]RegistryEntry{ + {Registry: "harbor.company.internal", Critical: false}, + {Registry: "registry.example:5000", Critical: true}, + }) + assert.NotContains(t, got, "nvcr.io") + assert.Contains(t, got, `host: "harbor.company.internal"`) + assert.Contains(t, got, `host: "registry.example"`) + assert.Contains(t, got, "port: 5000") + assert.Contains(t, got, "host: \"harbor.company.internal\"\n port: 443\n protocol: tcp+tls\n critical: false\n") + assert.Contains(t, got, "host: \"registry.example\"\n port: 5000\n protocol: tcp+tls\n critical: false\n", + "an in-pod dial is not evidence the nodes cannot pull, so no endpoint is critical") + assert.Less(t, strings.Index(got, "harbor.company.internal"), strings.Index(got, "enforcement:"), + "endpoints must appear before the enforcement block") + + // Nodes can pull nvcr.io through a runtime proxy or mirror that pods have + // no route to, so even an NGC install's nvcr.io is only a warning. + ngc := buildControlPlaneValidatorConfig([]RegistryEntry{{Registry: "nvcr.io", Critical: true}}) + assert.Contains(t, ngc, `host: "nvcr.io"`) + assert.NotContains(t, ngc, "critical: true") +} + +func TestBuildControlPlaneValidatorConfig_InvalidRegistrySkipped(t *testing.T) { + got := buildControlPlaneValidatorConfig([]RegistryEntry{{Registry: " "}, {Registry: "valid.registry.internal:5000"}}) + assert.Contains(t, got, "valid.registry.internal", "valid registry must appear") + assert.NotContains(t, got, "host: \"\"", "blank entry must not produce an empty host") +} + +func TestParseRegistryHostPort(t *testing.T) { + tests := []struct { + in string + wantHost string + wantPort int + }{ + {"nvcr.io:443", "nvcr.io", 443}, + {"harbor.company.internal:5000", "harbor.company.internal", 5000}, + {"registry.example.com", "registry.example.com", 443}, // no port -> 443 + {"", "", 0}, // empty -> skip + {" ", "", 0}, // blank -> skip + // IPv6: net.SplitHostPort handles bracketed literals correctly. + {"[::1]:5000", "::1", 5000}, + {"[2001:db8::1]:443", "2001:db8::1", 443}, + // An explicit but unusable port is a typo, not a request for 443: + // probing a different endpoint than configured reports a result for + // something the operator never asked about. + {"nvcr.io:", "", 0}, + {"nvcr.io:abc", "", 0}, + {"nvcr.io:0", "", 0}, + {"nvcr.io:70000", "", 0}, + } + for _, tt := range tests { + t.Run(tt.in, func(t *testing.T) { + h, p := parseRegistryHostPort(tt.in) + assert.Equal(t, tt.wantHost, h) + assert.Equal(t, tt.wantPort, p) + }) + } +} +func TestEnsureClusterValidatorConfig_RefusesUnmanagedConfigMap(t *testing.T) { + ctx := context.Background() + client := fake.NewSimpleClientset(&corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: clusterValidatorConfigRunName("runid"), + Namespace: clusterValidatorNamespace, + Labels: map[string]string{"owner": "operator"}, + }, + Data: map[string]string{"config.yaml": "operator: content"}, + }) + + err := ensureClusterValidatorConfig(ctx, client, nil, "runid", false) + require.Error(t, err, "an unmanaged ConfigMap must not be overwritten") + assert.Contains(t, err.Error(), "not managed by nvcf-cli") + + got, getErr := client.CoreV1().ConfigMaps(clusterValidatorNamespace).Get(ctx, + clusterValidatorConfigRunName("runid"), metav1.GetOptions{}) + require.NoError(t, getErr) + assert.Equal(t, "operator: content", got.Data["config.yaml"], + "the operator's ConfigMap content must be untouched") +} + +// The managed labels are three public constants, so a label check alone cannot +// establish ownership: anyone able to create a ServiceAccount in the probe +// namespace could stamp them. Safety comes from the name being unguessable, so +// there is no predictable object to pre-create and get bound to the validator's +// cluster-wide permissions. +func TestClusterValidatorRBACName_IsUnpredictableAndPerRun(t *testing.T) { + a, err := newValidatorRunID() + require.NoError(t, err) + b, err := newValidatorRunID() + require.NoError(t, err) + + assert.NotEqual(t, a, b, "each run must get a distinct identity") + assert.NotEmpty(t, a) + + const role = clusterValidatorControlPlaneRole + assert.NotEqual(t, clusterValidatorRBACName(role, a), clusterValidatorRBACName(role, b), + "RBAC names must differ per run") + assert.NotEqual(t, clusterValidatorRBACName(role, a), clusterValidatorRBACName("compute-plane", a), + "RBAC names must differ per role within a run") +} + +// Nothing pre-existing is adopted: creation is unconditional, so a squatted +// object surfaces as an error rather than being bound to our ClusterRole. +func TestEnsureClusterValidatorRBAC_DoesNotAdoptExistingObjects(t *testing.T) { + ctx := context.Background() + const role = clusterValidatorControlPlaneRole + const runID = "collide" + name := clusterValidatorRBACName(role, runID) + + // Forged managed labels: a label check alone would have adopted this. + client := fake.NewSimpleClientset(&corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, Namespace: clusterValidatorNamespace, Labels: clusterValidatorLabels(), + }, + }) + + err := ensureClusterValidatorRBAC(ctx, client, role, runID, false) + require.Error(t, err, "a pre-existing object must not be adopted, forged labels or not") + + _, bindErr := client.RbacV1().ClusterRoleBindings().Get(ctx, name, metav1.GetOptions{}) + assert.True(t, apierrors.IsNotFound(bindErr), + "no binding may be created when the ServiceAccount was not created by this run") +} + +// Random names cannot self-heal by being reused next run, so leftovers from a +// killed run are reclaimed by age. Two things must survive: anything recent +// (it may belong to a run happening right now) and anything whose name is not +// one we generate, even when it carries our labels. Those labels are three +// public constants, so they can be copied onto anything. +func TestSweepOrphanClusterValidatorRBAC_RequiresNameLabelsAndAge(t *testing.T) { + ctx := context.Background() + old := metav1.NewTime(time.Now().Add(-2 * time.Hour)) + now := metav1.NewTime(time.Now()) + staleName := clusterValidatorRBACName(clusterValidatorControlPlaneRole, "deadbeef01") + freshName := clusterValidatorRBACName("compute-plane", "cafebabe02") + + client := fake.NewSimpleClientset( + &rbacv1.ClusterRole{ObjectMeta: metav1.ObjectMeta{ + Name: staleName, Labels: clusterValidatorLabels(), CreationTimestamp: old, + }}, + &rbacv1.ClusterRole{ObjectMeta: metav1.ObjectMeta{ + Name: freshName, Labels: clusterValidatorLabels(), CreationTimestamp: now, + }}, + // Our labels, but not a name we generate: an operator copying the + // labels onto their own ClusterRole must not have it deleted. + &rbacv1.ClusterRole{ObjectMeta: metav1.ObjectMeta{ + Name: "operator-owned-role", Labels: clusterValidatorLabels(), CreationTimestamp: old, + }}, + ) + + sweepOrphanClusterValidatorRBAC(ctx, client, orphanValidatorRBACTTL) + + _, err := client.RbacV1().ClusterRoles().Get(ctx, staleName, metav1.GetOptions{}) + assert.True(t, apierrors.IsNotFound(err), "a generated name past the TTL must be reclaimed") + + _, err = client.RbacV1().ClusterRoles().Get(ctx, freshName, metav1.GetOptions{}) + assert.NoError(t, err, "a recent one may belong to a concurrent run") + + _, err = client.RbacV1().ClusterRoles().Get(ctx, "operator-owned-role", metav1.GetOptions{}) + assert.NoError(t, err, "matching labels alone must not authorize deleting someone else's object") +} + +// An explicit but unparseable port is a typo. Silently probing 443 would report +// a result for an endpoint the operator never configured. +func TestParseRegistryHostPort_RejectsMalformedExplicitPort(t *testing.T) { + for _, in := range []string{"registry.example:abc", "registry.example:0", "registry.example:99999", "registry.example:"} { + host, port := parseRegistryHostPort(in) + assert.Empty(t, host, "%q must be rejected, not defaulted", in) + assert.Zero(t, port, "%q must not fall back to a port", in) + } + host, port := parseRegistryHostPort("registry.example") + assert.Equal(t, "registry.example", host, "no explicit port keeps the default") + assert.Equal(t, 443, port) +} + +// The network-checks ConfigMap was the one object a run created with no +// cleanup path, so it accumulated in the cluster forever. +func TestSweepClusterValidatorConfig_DeletesOwnAndSparesOperators(t *testing.T) { + managed := &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{ + Name: clusterValidatorConfigRunName("runid"), Namespace: clusterValidatorNamespace, + Labels: clusterValidatorLabels(), + }} + client := fake.NewSimpleClientset(managed) + sweepClusterValidatorConfig(context.Background(), client, "runid") + _, err := client.CoreV1().ConfigMaps(clusterValidatorNamespace).Get( + context.Background(), clusterValidatorConfigRunName("runid"), metav1.GetOptions{}) + assert.True(t, apierrors.IsNotFound(err), "our own ConfigMap must be reclaimed") + + // Same name, operator-owned: the name is a constant they could also use. + operator := &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{ + Name: clusterValidatorConfigRunName("runid"), Namespace: clusterValidatorNamespace, + Labels: map[string]string{"owner": "operator"}, + }} + client2 := fake.NewSimpleClientset(operator) + sweepClusterValidatorConfig(context.Background(), client2, "runid") + _, err = client2.CoreV1().ConfigMaps(clusterValidatorNamespace).Get( + context.Background(), clusterValidatorConfigRunName("runid"), metav1.GetOptions{}) + assert.NoError(t, err, "an unmanaged ConfigMap with the same name must survive") +} + +// --no-cleanup must preserve the whole run, not just the Job: an operator who +// keeps the Job and re-runs the pod needs its pull secret to still exist. +func TestRunClusterValidator_NoCleanupKeepsPullSecretAndPriorJob(t *testing.T) { + prior := &batchv1.Job{ObjectMeta: metav1.ObjectMeta{ + Name: clusterValidatorName + "-earlier", Namespace: clusterValidatorNamespace, + Labels: clusterValidatorRoleLabels(clusterValidatorControlPlaneRole), + CreationTimestamp: metav1.NewTime(time.Now()), + }} + secret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: validatorPullSecretRunName(clusterValidatorControlPlaneRole, "runid"), + Namespace: clusterValidatorNamespace, + Labels: clusterValidatorRoleLabels(clusterValidatorControlPlaneRole), + // The fake clientset leaves this zero, which the orphan sweeper + // reads as older than any TTL. A real apiserver stamps it. + CreationTimestamp: metav1.NewTime(time.Now()), + }, + Type: corev1.SecretTypeDockerConfigJson, + } + client := fake.NewSimpleClientset(prior, secret) + driveJobToSuccess(client) + + runClusterValidator(context.Background(), client, "nvcr.io/x/validator:1", + "", true /* noCleanup */, clusterValidatorControlPlaneRole, nil, nil) + + _, err := client.BatchV1().Jobs(clusterValidatorNamespace).Get( + context.Background(), prior.Name, metav1.GetOptions{}) + assert.NoError(t, err, "--no-cleanup must not destroy a deliberately preserved Job") + + _, err = client.CoreV1().Secrets(clusterValidatorNamespace).Get( + context.Background(), secret.Name, metav1.GetOptions{}) + assert.NoError(t, err, "--no-cleanup must keep the pull secret the preserved pod needs") +} + +// The Job runs the same image as the chart's CronJob, so it needs the same pod +// shape. Without the security context a namespace enforcing the PodSecurity +// "restricted" profile rejects it at admission; without the tolerations it +// never schedules on a cluster whose nodes all carry the control-plane taint. +// Either way the wait burns its full budget and the run leaks. +func TestBuildClusterValidatorJob_MatchesChartPodShape(t *testing.T) { + job := buildClusterValidatorJob("j", "nvcr.io/x/validator:1", "", + clusterValidatorControlPlaneRole, "runid", false, nil) + spec := job.Spec.Template.Spec + + require.NotNil(t, spec.SecurityContext, "pod security context is required under restricted") + require.NotNil(t, spec.SecurityContext.RunAsUser) + assert.Equal(t, int64(65534), *spec.SecurityContext.RunAsUser, "chart runs this image as 65534") + + require.Len(t, spec.Containers, 1) + sc := spec.Containers[0].SecurityContext + require.NotNil(t, sc, "container security context is required under restricted") + require.NotNil(t, sc.RunAsNonRoot) + assert.True(t, *sc.RunAsNonRoot) + require.NotNil(t, sc.AllowPrivilegeEscalation) + assert.False(t, *sc.AllowPrivilegeEscalation) + require.NotNil(t, sc.Capabilities) + assert.Equal(t, []corev1.Capability{"ALL"}, sc.Capabilities.Drop) + require.NotNil(t, sc.SeccompProfile) + assert.Equal(t, corev1.SeccompProfileTypeRuntimeDefault, sc.SeccompProfile.Type) + + var keys []string + for _, tol := range spec.Tolerations { + keys = append(keys, tol.Key) + } + assert.Contains(t, keys, "node-role.kubernetes.io/control-plane") + assert.Contains(t, keys, "node-role.kubernetes.io/master") + + assert.False(t, spec.Containers[0].Resources.Requests.Cpu().IsZero(), + "a namespace with a LimitRange or quota rejects a pod with no requests") +} + +// An ImagePullBackOff Job never reaches a terminal state on its own, so +// TTLSecondsAfterFinished never fires and the Job, its cluster-wide RBAC and +// its NGC-derived pull secret persist forever. The deferred sweeps are +// suppressed on that path by design, so the deadline is the only reclaim. +func TestBuildClusterValidatorJob_SetsActiveDeadline(t *testing.T) { + job := buildClusterValidatorJob("j", "nvcr.io/x/validator:1", "", + clusterValidatorControlPlaneRole, "runid", false, nil) + require.NotNil(t, job.Spec.ActiveDeadlineSeconds, + "a Job with no deadline cannot terminate itself on a pull failure") + assert.Greater(t, *job.Spec.ActiveDeadlineSeconds, int64(clusterValidatorTimeout/time.Second), + "the deadline must outlast the runner's own wait so it never truncates a live run") +} + +// The pull secret holds $oauthtoken:$NGC_API_KEY, and the deferred sweep is +// skipped whenever the pod may still be running, which is the pull-failure +// path. The orphan sweeper is its only other reclaim. +func TestSweepOrphanClusterValidatorRBAC_ReclaimsStalePullSecret(t *testing.T) { + old := metav1.NewTime(time.Now().Add(-time.Hour)) + stale := &corev1.Secret{ObjectMeta: metav1.ObjectMeta{ + Name: validatorPullSecretRunName(clusterValidatorControlPlaneRole, "runid"), + Namespace: clusterValidatorNamespace, + Labels: clusterValidatorLabels(), + CreationTimestamp: old, + }} + client := fake.NewSimpleClientset(stale) + sweepOrphanClusterValidatorRBAC(context.Background(), client, orphanValidatorRBACTTL) + + _, err := client.CoreV1().Secrets(clusterValidatorNamespace).Get( + context.Background(), stale.Name, metav1.GetOptions{}) + assert.True(t, apierrors.IsNotFound(err), "a stale managed pull secret must be reclaimed") +} + +// A fresh secret may belong to a concurrent run, and an operator-supplied one +// carries neither our labels nor our name. +func TestSweepOrphanClusterValidatorRBAC_SparesFreshAndUnmanagedSecrets(t *testing.T) { + fresh := &corev1.Secret{ObjectMeta: metav1.ObjectMeta{ + Name: validatorPullSecretRunName(clusterValidatorControlPlaneRole, "runid"), + Namespace: clusterValidatorNamespace, + Labels: clusterValidatorLabels(), + CreationTimestamp: metav1.NewTime(time.Now()), + }} + operator := &corev1.Secret{ObjectMeta: metav1.ObjectMeta{ + Name: "operator-pull", + Namespace: clusterValidatorNamespace, + Labels: clusterValidatorLabels(), + CreationTimestamp: metav1.NewTime(time.Now().Add(-time.Hour)), + }} + client := fake.NewSimpleClientset(fresh, operator) + sweepOrphanClusterValidatorRBAC(context.Background(), client, orphanValidatorRBACTTL) + + for _, name := range []string{fresh.Name, operator.Name} { + _, err := client.CoreV1().Secrets(clusterValidatorNamespace).Get( + context.Background(), name, metav1.GetOptions{}) + assert.NoError(t, err, "%s must survive the orphan sweep", name) + } +} + +// The preflight ConfigMap must not switch on active NetworkPolicy enforcement. +// VALIDATOR_PREFLIGHT only suppresses the summary write, so enforcement would +// still create namespaces, pods and NetworkPolicies and pull busybox from +// Docker Hub, on a cluster where nothing is installed yet. +func TestBuildControlPlaneValidatorConfig_EnforcementDisabledForPreflight(t *testing.T) { + cfg := buildControlPlaneValidatorConfig(nil) + idx := strings.Index(cfg, "enforcement:") + require.GreaterOrEqual(t, idx, 0, "the enforcement block must be present") + assert.Contains(t, cfg[idx:], "enabled: false", + "a read-only readiness check must not mutate the cluster or need Docker Hub") +} + +// --no-cleanup must outlast the orphan TTL. Without a preserve marker the +// orphan sweeper reclaims a deliberately kept run after 30 minutes, which +// makes the flag mean "keep for 30 minutes". +func TestSweepOrphanClusterValidatorRBAC_SparesPreservedObjects(t *testing.T) { + labels := clusterValidatorLabels() + labels[clusterValidatorPreserveLabel] = "true" + old := metav1.NewTime(time.Now().Add(-time.Hour)) + client := fake.NewSimpleClientset( + &rbacv1.ClusterRole{ObjectMeta: metav1.ObjectMeta{ + Name: clusterValidatorName + "-control-plane-abc", Labels: labels, CreationTimestamp: old, + }}, + &corev1.Secret{ObjectMeta: metav1.ObjectMeta{ + Name: validatorPullSecretRunName(clusterValidatorControlPlaneRole, "runid"), + Namespace: clusterValidatorNamespace, Labels: labels, CreationTimestamp: old, + }}, + ) + sweepOrphanClusterValidatorRBAC(context.Background(), client, orphanValidatorRBACTTL) + + _, err := client.RbacV1().ClusterRoles().Get(context.Background(), + clusterValidatorName+"-control-plane-abc", metav1.GetOptions{}) + assert.NoError(t, err, "a preserved ClusterRole must survive the orphan sweep") + _, err = client.CoreV1().Secrets(clusterValidatorNamespace).Get(context.Background(), + validatorPullSecretRunName(clusterValidatorControlPlaneRole, "runid"), metav1.GetOptions{}) + assert.NoError(t, err, "a preserved pull secret must survive the orphan sweep") +} + +// The preserve marker has to be applied at creation, not just honoured by the +// sweeper, or the mechanism is inert. +func TestEnsureClusterValidatorRBAC_LabelsPreservedRun(t *testing.T) { + client := fake.NewSimpleClientset() + require.NoError(t, ensureClusterValidatorRBAC(context.Background(), client, + clusterValidatorControlPlaneRole, "abc123", true)) + + cr, err := client.RbacV1().ClusterRoles().Get(context.Background(), + clusterValidatorRBACName(clusterValidatorControlPlaneRole, "abc123"), metav1.GetOptions{}) + require.NoError(t, err) + assert.Equal(t, "true", cr.Labels[clusterValidatorPreserveLabel], + "--no-cleanup must mark its objects so the orphan sweeper spares them") +} + +func TestEnsureClusterValidatorRBAC_NoPreserveLabelByDefault(t *testing.T) { + client := fake.NewSimpleClientset() + require.NoError(t, ensureClusterValidatorRBAC(context.Background(), client, + clusterValidatorControlPlaneRole, "abc123", false)) + + cr, err := client.RbacV1().ClusterRoles().Get(context.Background(), + clusterValidatorRBACName(clusterValidatorControlPlaneRole, "abc123"), metav1.GetOptions{}) + require.NoError(t, err) + assert.NotContains(t, cr.Labels, clusterValidatorPreserveLabel, + "an ordinary run must stay reclaimable") +} + +// Run-scoping the ConfigMap name removed the accidental self-healing the fixed +// name gave us: a killed run used to leave exactly one object that the next run +// overwrote, and now each leaves its own. The deferred sweep is suppressed on +// the pull-failure path, so the orphan sweeper is the only reclaim. +func TestSweepOrphanClusterValidatorRBAC_ReclaimsStaleConfigMap(t *testing.T) { + old := metav1.NewTime(time.Now().Add(-time.Hour)) + stale := &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{ + Name: clusterValidatorConfigRunName("deadbeef01"), + Namespace: clusterValidatorNamespace, + Labels: clusterValidatorLabels(), + CreationTimestamp: old, + }} + client := fake.NewSimpleClientset(stale) + sweepOrphanClusterValidatorRBAC(context.Background(), client, orphanValidatorRBACTTL) + + _, err := client.CoreV1().ConfigMaps(clusterValidatorNamespace).Get( + context.Background(), stale.Name, metav1.GetOptions{}) + assert.True(t, apierrors.IsNotFound(err), + "a per-run ConfigMap left by a killed run must be reclaimed") +} + +func TestSweepOrphanClusterValidatorRBAC_SparesFreshAndUnmanagedConfigMaps(t *testing.T) { + fresh := &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{ + Name: clusterValidatorConfigRunName("aaaaaaaa01"), + Namespace: clusterValidatorNamespace, + Labels: clusterValidatorLabels(), + CreationTimestamp: metav1.NewTime(time.Now()), + }} + operator := &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{ + Name: "operator-owned", + Namespace: clusterValidatorNamespace, + Labels: clusterValidatorLabels(), + CreationTimestamp: metav1.NewTime(time.Now().Add(-time.Hour)), + }} + client := fake.NewSimpleClientset(fresh, operator) + sweepOrphanClusterValidatorRBAC(context.Background(), client, orphanValidatorRBACTTL) + + for _, name := range []string{fresh.Name, operator.Name} { + _, err := client.CoreV1().ConfigMaps(clusterValidatorNamespace).Get( + context.Background(), name, metav1.GetOptions{}) + assert.NoError(t, err, "%s must survive the orphan sweep", name) } } +// --no-cleanup must preserve the ConfigMap as well as the Job and its RBAC. +// Without the marker the next run's orphan sweeper reclaims it after the TTL, +// and an operator re-running the Job they kept gets a validator that silently +// skips the configurable reachability and enforcement checks. +func TestEnsureClusterValidatorConfig_MarksPreservedRun(t *testing.T) { + client := fake.NewSimpleClientset() + require.NoError(t, ensureClusterValidatorConfig(context.Background(), client, nil, "runid", true)) + + cm, err := client.CoreV1().ConfigMaps(clusterValidatorNamespace).Get( + context.Background(), clusterValidatorConfigRunName("runid"), metav1.GetOptions{}) + require.NoError(t, err) + assert.Equal(t, "true", cm.Labels[clusterValidatorPreserveLabel]) + + // And the orphan sweeper honours it even once it is older than the TTL. + cm.CreationTimestamp = metav1.NewTime(time.Now().Add(-time.Hour)) + _, err = client.CoreV1().ConfigMaps(clusterValidatorNamespace).Update( + context.Background(), cm, metav1.UpdateOptions{}) + require.NoError(t, err) + sweepOrphanClusterValidatorRBAC(context.Background(), client, orphanValidatorRBACTTL) + + _, err = client.CoreV1().ConfigMaps(clusterValidatorNamespace).Get( + context.Background(), clusterValidatorConfigRunName("runid"), metav1.GetOptions{}) + assert.NoError(t, err, "a preserved ConfigMap must survive the orphan sweep") +} + +func TestEnsureClusterValidatorConfig_NoPreserveMarkerByDefault(t *testing.T) { + client := fake.NewSimpleClientset() + require.NoError(t, ensureClusterValidatorConfig(context.Background(), client, nil, "runid", false)) + + cm, err := client.CoreV1().ConfigMaps(clusterValidatorNamespace).Get( + context.Background(), clusterValidatorConfigRunName("runid"), metav1.GetOptions{}) + require.NoError(t, err) + assert.NotContains(t, cm.Labels, clusterValidatorPreserveLabel, + "an ordinary run must stay reclaimable") +} + +// --no-cleanup has to outlast the run that set it. The run itself already skips +// the prior-Job sweep, but without a marker on the Job the next ordinary run of +// the same role deletes it, while its ConfigMap, RBAC and pull secret survive. +func TestBuildClusterValidatorJob_MarksPreservedRun(t *testing.T) { + kept := buildClusterValidatorJob("j", "nvcr.io/x/v:1", "", + clusterValidatorControlPlaneRole, "runid", true, nil) + assert.Equal(t, "true", kept.Labels[clusterValidatorPreserveLabel]) + + ordinary := buildClusterValidatorJob("j", "nvcr.io/x/v:1", "", + clusterValidatorControlPlaneRole, "runid", false, nil) + assert.NotContains(t, ordinary.Labels, clusterValidatorPreserveLabel, + "an ordinary run must stay sweepable") +} diff --git a/src/clis/nvcf-cli/internal/selfhosted/credhelper_test.go b/src/clis/nvcf-cli/internal/selfhosted/credhelper_test.go new file mode 100644 index 0000000000..ed1c0ac15e --- /dev/null +++ b/src/clis/nvcf-cli/internal/selfhosted/credhelper_test.go @@ -0,0 +1,193 @@ +/* +SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +SPDX-License-Identifier: Apache-2.0 + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package selfhosted + +import ( + "encoding/json" + "os" + "path/filepath" + "runtime" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// A registry may send several WWW-Authenticate headers. The Bearer one is +// answered wherever it appears; reading only the first reported a registry +// answering "Negotiate" then "Bearer" as a clean skip. +func TestSelectAuthChallenge(t *testing.T) { + bearer := `Bearer realm="https://auth.example/token",service="registry"` + assert.Equal(t, bearer, selectAuthChallenge([]string{"Negotiate", bearer})) + assert.Equal(t, `Basic realm="r"`, selectAuthChallenge([]string{`Basic realm="r"`})) + assert.Equal(t, "", selectAuthChallenge(nil)) +} + +// dockerHome writes ~/.docker/config.json under a temporary HOME and puts a +// fake docker-credential- for each helper on PATH, each answering with +// its own secret. +// inlineDockerConfig builds a config.json with one inline credential at run +// time. A literal auths blob trips secret scanners even with fake values. +func inlineDockerConfig(t *testing.T, registry, user, pass, credsStore string) string { + t.Helper() + cfg := map[string]any{"auths": map[string]any{ + registry: map[string]string{"username": user, "password": pass}, + }} + if credsStore != "" { + cfg["credsStore"] = credsStore + } + b, err := json.Marshal(cfg) + require.NoError(t, err) + return string(b) +} + +func dockerHome(t *testing.T, config string, helpers ...string) { + t.Helper() + if runtime.GOOS == "windows" { + t.Skip("shell-script credential helpers") + } + home := t.TempDir() + require.NoError(t, os.MkdirAll(filepath.Join(home, ".docker"), 0o755)) + require.NoError(t, os.WriteFile(filepath.Join(home, ".docker", "config.json"), []byte(config), 0o600)) + t.Setenv("HOME", home) + bin := t.TempDir() + for _, h := range helpers { + script := "#!/bin/sh\n[ \"$1\" = get ] || exit 1\nread server\n" + + "printf '{\"ServerURL\":\"%s\",\"Username\":\"u-" + h + "\",\"Secret\":\"s-" + h + "\"}' \"$server\"\n" + require.NoError(t, os.WriteFile(filepath.Join(bin, "docker-credential-"+h), []byte(script), 0o755)) + } + t.Setenv("PATH", bin+string(os.PathListSeparator)+os.Getenv("PATH")) + prev := credentialHelperTimeout + credentialHelperTimeout = time.Minute + t.Cleanup(func() { credentialHelperTimeout = prev }) +} + +// Docker Desktop leaves only an empty auths entry and keeps the login in +// credsStore, so a working `docker login` must be read through the helper. +func TestCredsFromDockerConfig_UsesCredsStore(t *testing.T) { + dockerHome(t, `{"auths":{"nvcr.io":{}},"credsStore":"store"}`, "store") + u, p, ok := credsFromDockerConfig("nvcr.io") + require.True(t, ok) + assert.Equal(t, "u-store", u) + assert.Equal(t, "s-store", p) +} + +// A per-registry credHelpers entry wins over the global store. +func TestCredsFromDockerConfig_CredHelpersBeatCredsStore(t *testing.T) { + dockerHome(t, `{"credsStore":"store","credHelpers":{"nvcr.io":"ngc"}}`, "store", "ngc") + u, _, ok := credsFromDockerConfig("nvcr.io") + require.True(t, ok) + assert.Equal(t, "u-ngc", u) +} + +// Inline credentials are read when no store is configured. +func TestCredsFromDockerConfig_InlineAuthWithoutAStore(t *testing.T) { + dockerHome(t, inlineDockerConfig(t, "nvcr.io", "x", "y", "")) + u, p, ok := credsFromDockerConfig("nvcr.io") + require.True(t, ok) + assert.Equal(t, "x", u) + assert.Equal(t, "y", p) +} + +// With a store configured docker never reads an inline password, so a stale +// one left in the file must not win over the store. +func TestCredsFromDockerConfig_StoreBeatsStaleInlineAuth(t *testing.T) { + dockerHome(t, inlineDockerConfig(t, "nvcr.io", "old", "stale", "store"), "store") + u, p, ok := credsFromDockerConfig("nvcr.io") + require.True(t, ok) + assert.Equal(t, "u-store", u) + assert.Equal(t, "s-store", p) +} + +// Docker Hub logins live under the legacy index key, whatever host name the +// image uses. +func TestCredsFromDockerConfig_DockerHubKey(t *testing.T) { + dockerHome(t, inlineDockerConfig(t, "https://index.docker.io/v1/", "hub", "pw", "")) + for _, host := range []string{"docker.io", "index.docker.io", "registry-1.docker.io"} { + u, _, ok := credsFromDockerConfig(host) + require.True(t, ok, host) + assert.Equal(t, "hub", u) + } +} + +// The timeout holds even when the helper leaves a child holding stdout open. +func TestCredsFromHelper_TimeoutIsEnforced(t *testing.T) { + dockerHome(t, `{"credsStore":"hang"}`) + bin := t.TempDir() + script := "#!/bin/sh\nsleep 30 &\nsleep 30\n" + require.NoError(t, os.WriteFile(filepath.Join(bin, "docker-credential-hang"), []byte(script), 0o755)) + t.Setenv("PATH", bin+string(os.PathListSeparator)+os.Getenv("PATH")) + credentialHelperTimeout = 500 * time.Millisecond + + start := time.Now() + _, _, ok := credsFromDockerConfig("nvcr.io") + assert.False(t, ok) + assert.Less(t, time.Since(start), 10*time.Second, "the helper call must not outlive its timeout by the child's lifetime") +} + +// A helper that floods stdout is refused rather than buffered without bound. +func TestCredsFromHelper_OutputIsCapped(t *testing.T) { + dockerHome(t, `{"credsStore":"flood"}`) + bin := t.TempDir() + // Valid JSON padded past the cap, so only the cap can refuse it. + script := "#!/bin/sh\nprintf '{\"Username\":\"u\",\"Secret\":\"s\"}'\nhead -c 200000 /dev/zero | tr '\\0' ' '\n" + require.NoError(t, os.WriteFile(filepath.Join(bin, "docker-credential-flood"), []byte(script), 0o755)) + t.Setenv("PATH", bin+string(os.PathListSeparator)+os.Getenv("PATH")) + _, _, ok := credsFromDockerConfig("nvcr.io") + assert.False(t, ok) +} + +// The helper name comes from a file; anything that is not a plain helper +// suffix is refused rather than put on an exec path. +func TestCredsFromDockerConfig_RefusesUnsafeHelperName(t *testing.T) { + // The helper exists on PATH, so only the name check can refuse it. + dockerHome(t, `{"credsStore":"bad name"}`, "bad name") + _, _, ok := credsFromDockerConfig("nvcr.io") + assert.False(t, ok) +} + +// Several challenges can share one header, comma-separated. +func TestSelectAuthChallenge_JoinedInOneHeader(t *testing.T) { + got := selectAuthChallenge([]string{`Negotiate, Bearer realm="https://auth.example/token",service="r"`}) + assert.Equal(t, `Bearer realm="https://auth.example/token",service="r"`, got) + assert.Equal(t, `Basic realm="bearer thing"`, selectAuthChallenge([]string{`Basic realm="bearer thing"`}), + "a quoted word is not a challenge") +} + +// For an NGC registry the check uses NGC_API_KEY when it is set, the key the +// install mints its pull secrets from, even when a credential store also has a +// login for it. Without the key, the store is used. Other registries never +// get the key. +func TestCredentialsForRegistry_NGCKeyMatchesWhatTheInstallUses(t *testing.T) { + dockerHome(t, `{"auths":{"nvcr.io":{}},"credsStore":"store"}`, "store") + t.Setenv("NGC_API_KEY", "env-key") + u, p, ok := credentialsForRegistry("nvcr.io") + require.True(t, ok) + assert.Equal(t, "$oauthtoken", u) + assert.Equal(t, "env-key", p) + + t.Setenv("NGC_API_KEY", "") + u, _, ok = credentialsForRegistry("nvcr.io") + require.True(t, ok) + assert.Equal(t, "u-store", u, "without the key the store's login is used") + + t.Setenv("NGC_API_KEY", "env-key") + _, p, _ = credentialsForRegistry("quay.io") + assert.NotEqual(t, "env-key", p, "the NGC key is never sent to another registry") +} diff --git a/src/clis/nvcf-cli/internal/selfhosted/helm_runtime.go b/src/clis/nvcf-cli/internal/selfhosted/helm_runtime.go index 7af90e8492..6441e14149 100644 --- a/src/clis/nvcf-cli/internal/selfhosted/helm_runtime.go +++ b/src/clis/nvcf-cli/internal/selfhosted/helm_runtime.go @@ -71,7 +71,7 @@ func checkHelmRuntimeCompatibility(ctx context.Context, helmfileSpec, helmSpec B r := CheckResult{ ID: "local-host-tools-helm-runtime", Category: "local-host-tools", - Severity: "error", + Severity: SeverityError, HintURL: "https://github.com/helmfile/helmfile#installation", } _, helmfileVersion, err := probeBinarySpecVersion(ctx, helmfileSpec) @@ -93,7 +93,7 @@ func checkHelmRuntimeCompatibility(ctx context.Context, helmfileSpec, helmSpec B return r } r.Passed = true - r.Severity = "info" + r.Severity = SeverityInfo r.Detail = string(mode) r.Message = fmt.Sprintf("Helm %s with helmfile %s uses %s mode", helmVersion, helmfileVersion, mode) return r diff --git a/src/clis/nvcf-cli/internal/selfhosted/main_test.go b/src/clis/nvcf-cli/internal/selfhosted/main_test.go new file mode 100644 index 0000000000..0d9e6b6e57 --- /dev/null +++ b/src/clis/nvcf-cli/internal/selfhosted/main_test.go @@ -0,0 +1,46 @@ +/* +SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +SPDX-License-Identifier: Apache-2.0 + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package selfhosted + +import ( + "os" + "testing" + "time" +) + +// TestMain points HOME at an empty directory so no test reads the developer's +// ~/.docker/config.json, or runs a credential helper it names. Tests that need +// a docker config write their own under a temporary HOME. HELM_DRIVER is +// cleared too: an exported sql driver turns off the stale-namespace probe's +// no-release signal, which the probe tests assert on. +func TestMain(m *testing.M) { + home, err := os.MkdirTemp("", "selfhosted-test-home-") + if err != nil { + panic(err) + } + _ = os.Setenv("HOME", home) + _ = os.Unsetenv("HELM_DRIVER") + // The fake clientset has no controllers, so a pod never ends on its own: + // keep the waits for that short. Tests of the waits set their own. + validatorStopTimeout = 2 * time.Second + validatorDeadlineGrace = 2 * time.Second + validatorPullFailureGrace = 0 + code := m.Run() + _ = os.RemoveAll(home) + os.Exit(code) +} diff --git a/src/clis/nvcf-cli/internal/selfhosted/miniapiserver_test.go b/src/clis/nvcf-cli/internal/selfhosted/miniapiserver_test.go new file mode 100644 index 0000000000..278ba86465 --- /dev/null +++ b/src/clis/nvcf-cli/internal/selfhosted/miniapiserver_test.go @@ -0,0 +1,262 @@ +/* +SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +SPDX-License-Identifier: Apache-2.0 + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package selfhosted + +import ( + "context" + "encoding/json" + "io" + "net/http" + "net/http/httptest" + "strings" + "sync" + "sync/atomic" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" +) + +// miniAPIServer is an in-memory apiserver over real HTTP, for what the fake +// clientset cannot show: request contexts and calls that hang. A request whose +// context is already cancelled never reaches it, which is the point. It +// stores what is created, lists it back, and records deletes. +type miniAPIServer struct { + t *testing.T + mu sync.Mutex + objects map[string]map[string]any // collection path -> name -> object + deletes []string // "/" + // hangDeletes holds every DELETE until the client gives up. + hangDeletes atomic.Bool + // jobCreated is closed on the first Job create. + jobCreated chan struct{} + onceJob sync.Once + // runningJob, when set, is the status every Job read returns. + runningJob bool +} + +func newMiniAPIServer(t *testing.T) (*miniAPIServer, kubernetes.Interface) { + t.Helper() + m := &miniAPIServer{t: t, objects: map[string]map[string]any{}, jobCreated: make(chan struct{}), runningJob: true} + srv := httptest.NewServer(http.HandlerFunc(m.serve)) + t.Cleanup(func() { + srv.CloseClientConnections() + srv.Close() + }) + client, err := kubernetes.NewForConfig(&rest.Config{ + Host: srv.URL, + ContentConfig: rest.ContentConfig{ContentType: "application/json"}, + }) + require.NoError(t, err) + return m, client +} + +func (m *miniAPIServer) deleted() []string { + m.mu.Lock() + defer m.mu.Unlock() + return append([]string(nil), m.deletes...) +} + +func (m *miniAPIServer) serve(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + path := strings.TrimSuffix(r.URL.Path, "/") + collection, name := path, "" + if i := strings.LastIndex(path, "/"); i > 0 && isNamedPath(path) { + collection, name = path[:i], path[i+1:] + } + m.mu.Lock() + defer m.mu.Unlock() + switch r.Method { + case http.MethodPost: + body, _ := io.ReadAll(r.Body) + var obj map[string]any + _ = json.Unmarshal(body, &obj) + meta, _ := obj["metadata"].(map[string]any) + n, _ := meta["name"].(string) + meta["uid"] = "uid-" + n + if m.objects[collection] == nil { + m.objects[collection] = map[string]any{} + } + m.objects[collection][n] = obj + if strings.HasSuffix(collection, "/jobs") { + m.onceJob.Do(func() { close(m.jobCreated) }) + } + w.WriteHeader(http.StatusCreated) + _ = json.NewEncoder(w).Encode(obj) + case http.MethodGet: + if name == "" { + items := []any{} + for _, o := range m.objects[collection] { + items = append(items, o) + } + // A typed client decodes only its own list kind. + _ = json.NewEncoder(w).Encode(map[string]any{ + "apiVersion": listAPIVersion(collection), "kind": listKinds[collection[strings.LastIndex(collection, "/")+1:]], + "items": items, + }) + return + } + obj, ok := m.objects[collection][name] + if !ok { + notFound(w) + return + } + _ = json.NewEncoder(w).Encode(obj) + case http.MethodDelete: + if m.hangDeletes.Load() { + // The server notices a closed connection only once the body has + // been read, so read it before waiting on the request context. + _, _ = io.Copy(io.Discard, r.Body) + m.mu.Unlock() + <-r.Context().Done() + m.mu.Lock() + return + } + m.deletes = append(m.deletes, collection+"/"+name) + delete(m.objects[collection], name) + if strings.HasSuffix(collection, "/jobs") { + // The Job's pods go with it, as the garbage collector would. + delete(m.objects, strings.TrimSuffix(collection, "/jobs")+"/pods") + } + _ = json.NewEncoder(w).Encode(map[string]any{"kind": "Status", "apiVersion": "v1", "status": "Success"}) + default: + // PATCH and PUT: accept and echo nothing interesting. + _ = json.NewEncoder(w).Encode(map[string]any{}) + } +} + +// listKinds maps a collection's resource to the kind a typed client decodes. +var listKinds = map[string]string{ + "jobs": "JobList", "pods": "PodList", "secrets": "SecretList", "configmaps": "ConfigMapList", + "serviceaccounts": "ServiceAccountList", "clusterroles": "ClusterRoleList", + "clusterrolebindings": "ClusterRoleBindingList", "namespaces": "NamespaceList", +} + +// listAPIVersion is the group version in a collection path: "v1" under /api, +// "/" under /apis. +func listAPIVersion(collection string) string { + parts := strings.Split(strings.Trim(collection, "/"), "/") + if len(parts) >= 3 && parts[0] == "apis" { + return parts[1] + "/" + parts[2] + } + return "v1" +} + +// isNamedPath reports whether path names one object rather than a collection: +// its last segment follows a resource segment. +func isNamedPath(path string) bool { + parts := strings.Split(strings.Trim(path, "/"), "/") + resources := map[string]bool{ + "jobs": true, "pods": true, "secrets": true, "configmaps": true, "serviceaccounts": true, + "clusterroles": true, "clusterrolebindings": true, "namespaces": true, + } + return len(parts) >= 2 && resources[parts[len(parts)-2]] +} + +func notFound(w http.ResponseWriter) { + w.WriteHeader(http.StatusNotFound) + _, _ = io.WriteString(w, `{"kind":"Status","apiVersion":"v1","status":"Failure","reason":"NotFound","code":404}`) +} + +// runInterrupted starts a run against m, cancels it once the Job exists, and +// returns the result. +func runInterrupted(t *testing.T, client kubernetes.Interface, m *miniAPIServer) ClusterValidatorResult { + t.Helper() + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + done := make(chan ClusterValidatorResult, 1) + go func() { + done <- runClusterValidator(ctx, client, "registry.example.com/validator:1", + "", false, clusterValidatorComputePlaneRole, nil, nil) + }() + select { + case <-m.jobCreated: + case <-time.After(30 * time.Second): + t.Fatal("the run never created its Job") + } + cancel() + select { + case res := <-done: + return res + case <-time.After(60 * time.Second): + t.Fatal("the interrupted run did not return") + } + return ClusterValidatorResult{} +} + +// On an interrupt the cleanup requests really go out. A cleanup context +// derived from the cancelled run context sends none, which the fake clientset +// cannot show because it ignores contexts. +func TestRunClusterValidator_InterruptSendsCleanupOverTheWire(t *testing.T) { + m, client := newMiniAPIServer(t) + res := runInterrupted(t, client, m) + require.Error(t, res.Err) + + got := strings.Join(m.deleted(), " ") + for _, want := range []string{"/jobs/", "/clusterrolebindings/", "/clusterroles/", "/serviceaccounts/"} { + assert.Contains(t, got, want) + } +} + +// A delete that never answers does not hold the CLI open: every cleanup call +// is bounded. +func TestRunClusterValidator_HungDeletesAreBounded(t *testing.T) { + prev := validatorCleanupTimeout + validatorCleanupTimeout = 500 * time.Millisecond + t.Cleanup(func() { validatorCleanupTimeout = prev }) + m, client := newMiniAPIServer(t) + m.hangDeletes.Store(true) + + start := time.Now() + res := runInterrupted(t, client, m) + require.Error(t, res.Err) + assert.Less(t, time.Since(start), 25*time.Second) +} + +// A control-plane run that times out once its pod has ended makes all three +// deferred sweeps in turn, each on its own bounded context. With every DELETE +// hanging, the run still returns within ClusterValidatorRunCeiling, which the +// check's budget is sized from. +func TestClusterValidatorRunCeiling_CoversHungSweeps(t *testing.T) { + prevTimeout, prevGrace, prevLogs, prevCleanup, prevMargin := clusterValidatorTimeout, + validatorDeadlineGrace, clusterValidatorLogFetchTimeout, validatorCleanupTimeout, validatorRunMargin + clusterValidatorTimeout, validatorDeadlineGrace, clusterValidatorLogFetchTimeout, validatorCleanupTimeout, + validatorRunMargin = 3*time.Second, time.Second, 500*time.Millisecond, time.Second, 200*time.Millisecond + t.Cleanup(func() { + clusterValidatorTimeout, validatorDeadlineGrace, clusterValidatorLogFetchTimeout, validatorCleanupTimeout, + validatorRunMargin = prevTimeout, prevGrace, prevLogs, prevCleanup, prevMargin + }) + // An NGC image with NGC_API_KEY mints a pull Secret, and a registry makes + // the ConfigMap, so each of the three sweeps has an object to delete. + t.Setenv("NGC_API_KEY", "key") + m, client := newMiniAPIServer(t) + m.hangDeletes.Store(true) + + start := time.Now() + res := runClusterValidator(context.Background(), client, "nvcr.io/nvidia/validator:1", + "", false, clusterValidatorControlPlaneRole, []RegistryEntry{{Registry: "nvcr.io"}}, nil) + elapsed := time.Since(start) + require.Error(t, res.Err) + require.Contains(t, res.Err.Error(), "waiting for job", "the run reached the Job wait and timed out there") + assert.GreaterOrEqual(t, elapsed, validatorDeferredSweeps*validatorCleanupTimeout, + "every sweep ran and hung until its bound") + assert.LessOrEqual(t, elapsed, ClusterValidatorRunCeiling()) +} diff --git a/src/clis/nvcf-cli/internal/selfhosted/orchestrator_errors_test.go b/src/clis/nvcf-cli/internal/selfhosted/orchestrator_errors_test.go index a3b2e0e184..1bd771a614 100644 --- a/src/clis/nvcf-cli/internal/selfhosted/orchestrator_errors_test.go +++ b/src/clis/nvcf-cli/internal/selfhosted/orchestrator_errors_test.go @@ -422,9 +422,9 @@ func TestRetryAfterFromError_Parsing(t *testing.T) { {"rate limit: Retry-After: 30", 30}, {"Retry-After: 120s", 120}, {"retry-after: 5", 5}, - {"no header here", 5}, // default - {"Retry-After: ", 5}, // no digits → default - {"Retry-After: 0", 5}, // zero → default + {"no header here", 5}, // default + {"Retry-After: ", 5}, // no digits -> default + {"Retry-After: 0", 5}, // zero -> default {"Retry-After: 300 ok", 300}, // stops at first non-digit } for _, tc := range cases { diff --git a/src/clis/nvcf-cli/internal/selfhosted/output.go b/src/clis/nvcf-cli/internal/selfhosted/output.go index c0fdf3d919..cbfb7920dc 100644 --- a/src/clis/nvcf-cli/internal/selfhosted/output.go +++ b/src/clis/nvcf-cli/internal/selfhosted/output.go @@ -77,7 +77,7 @@ func mark(r CheckResult) string { switch { case r.Passed: return "✓" - case r.Severity == "warning": + case r.Severity == SeverityWarning: return "‼" default: return "×" @@ -98,10 +98,10 @@ func statusGlyph(status string) string { func overallStatus(results []CheckResult) string { hasWarn := false for _, r := range results { - if !r.Passed && r.Severity == "error" { - return "error" + if r.IsBlockingFailure() { + return SeverityError } - if !r.Passed && r.Severity == "warning" { + if r.IsWarning() { hasWarn = true } } diff --git a/src/clis/nvcf-cli/internal/selfhosted/preflight.go b/src/clis/nvcf-cli/internal/selfhosted/preflight.go index 1d820eb91b..a88d0764fc 100644 --- a/src/clis/nvcf-cli/internal/selfhosted/preflight.go +++ b/src/clis/nvcf-cli/internal/selfhosted/preflight.go @@ -19,6 +19,7 @@ package selfhosted import ( "context" + "errors" "fmt" "net/http" "os" @@ -44,6 +45,42 @@ const ( binaryVersionMessage = "%s %s on PATH (%s required)" ) +// Severity values for CheckResult. Typed constants rather than bare strings: +// a typo'd "Error" silently downgraded a hard failure to a warning, and the +// exit code keys off an exact match. +const ( + SeverityInfo = "info" + SeverityWarning = "warning" + SeverityError = "error" +) + +// IsBlockingFailure reports whether the result fails the run: a miss at error +// severity. The exit code, the final event, the category counts and the +// summaries all derive from this one rule so they cannot disagree. +func (r CheckResult) IsBlockingFailure() bool { + return !r.Passed && r.Severity == SeverityError +} + +// IsWarning reports a miss that does not fail the run. +func (r CheckResult) IsWarning() bool { + return !r.Passed && !r.IsBlockingFailure() +} + +// CountResults tallies results by the one rule every count uses. +func CountResults(results []CheckResult) (passed, failed, warned int) { + for _, r := range results { + switch { + case r.Passed: + passed++ + case r.IsBlockingFailure(): + failed++ + default: + warned++ + } + } + return passed, failed, warned +} + // One row in the linkerd-style output. Logs is internal-only and is not // forwarded into the CheckCompleted JSONL wire event, so it never leaks // into the stable JSON contract. @@ -55,8 +92,11 @@ type CheckResult struct { Message string Detail string // optional: short version string or extra context (M+8.11) HintURL string - Err error // populated only when the check itself failed to execute + Err error // populated only when the check itself failed to execute Logs string // optional: full check transcript for --show-logs; not emitted to JSON + // Transient marks a warning expected to clear by itself, such as a + // rollout in progress. --wait keeps polling while one remains. + Transient bool } // BinarySpec defines a tool that must be on PATH and a version constraint. @@ -75,7 +115,7 @@ func checkBinary(ctx context.Context, s BinarySpec) CheckResult { r := CheckResult{ ID: "local-host-tools-" + s.Name, Category: "local-host-tools", - Severity: "error", + Severity: SeverityError, HintURL: s.HintURL, } path, err := s.LookPath(s.Name) @@ -195,6 +235,15 @@ func runVersionCmdOnce(ctx context.Context, path string, args []string, re *rege type PreflightConfig struct { LocalOnly bool Tools []BinarySpec + + // Registries is the list of container registries to credential-check. + // When empty, the registry-credentials category is omitted entirely. + // Populated by the cmd layer from EnumerateRegistries. + Registries []RegistryEntry + + // RegistryChecker validates credentials for one registry. Nil skips the + // category. Production wires NewRegistryCredentialChecker; tests pass fakes. + RegistryChecker RegistryCredentialChecker } // DefaultTools returns the kubectl/helmfile/helm specs with version floors @@ -252,10 +301,18 @@ type Role int const ( RoleLocalOnly Role = iota // shared local-host tools only; no kubectl contact - RoleControlPlane // shared + Gateway API CRDs + default StorageClass + RoleControlPlane // shared + gateway/StorageClass/LB checks via cluster-validator RoleComputePlane // shared + GPU operator + GPU node labels + (optional) SIS reachability ) +// Validator role strings passed as VALIDATOR_ROLE to the cluster-validator Job. +// These must match the constants in the nvca cluster-validator binary's +// clustervalidator package (RoleControlPlane / RoleComputePlane). +const ( + validatorRoleControlPlane = "control-plane" + validatorRoleComputePlane = "compute-plane" +) + // String returns a human-readable name for the role. func (r Role) String() string { switch r { @@ -289,6 +346,32 @@ type RoleConfig struct { ClusterValidatorImage string ClusterValidatorPullSecret string ClusterValidatorNoCleanup bool + + // StaleNamespaceProber detects NVCF stack namespaces that are stuck + // Terminating or exist as empty shells after a partial teardown. Nil skips + // the check; production wires NewStaleNamespaceProber; tests pass fakes. + StaleNamespaceProber StaleNamespaceProber + + // StackDir is the resolved stack checkout. When set, the namespaces to + // probe are read from its helmfile.d rather than the static fallback list, + // so the check follows the stack instead of drifting from it. + StackDir string + + // ExtraStaleNamespaces are merged into this role's stale-namespace scan. + // In ModeSingle both roles share one cluster, so only one of them runs the + // probe to avoid emitting the same check ID twice; the other role's + // namespaces are passed here instead of being dropped. The two roles probe + // disjoint lists, so without this the skipped role's namespaces are never + // scanned on the one topology where they sit on the same cluster. + ExtraStaleNamespaces []string + + // ClusterValidatorRegistries are the registries the control-plane + // validator probes for reachability: the EnumerateRegistries result the + // local credential check also uses. Ignored for the compute-plane validator. + ClusterValidatorRegistries []RegistryEntry + + // ClusterValidatorEnv is passed to the validator container for both roles. + ClusterValidatorEnv map[string]string } // categorySpec groups a set of checks under a named category. Categories run @@ -318,6 +401,16 @@ func buildCategories(cfg PreflightConfig, role Role, rc RoleConfig) []categorySp out = append(out, *local) } + // Registry credential check runs from the operator's machine - no cluster + // contact needed. RunPreflightForRole is called once per role, so the + // caller clears Registries on every role but one to avoid duplicate + // check_started/check_completed events. Deduplicating on the role here + // instead would drop the check entirely when the control plane does not + // run, which is the case for a compute-plane-only invocation. + if len(cfg.Registries) > 0 && cfg.RegistryChecker != nil { + out = append(out, buildRegistryCredentialCategory(cfg)) + } + if cfg.LocalOnly || role == RoleLocalOnly { return out } @@ -354,28 +447,45 @@ func buildLocalHostCategory(cfg PreflightConfig) *categorySpec { return &categorySpec{name: "local-host-tools", role: RoleLocalOnly, checks: checks} } -// controlPlaneCheckCategory returns the placeholder cluster-side checks for -// control plane: Gateway API CRDs, default StorageClass. Real probes land -// when M3 ships; for now they emit an "info" CheckResult so the renderer -// matrix is observable. -func controlPlaneCheckCategory(_ RoleConfig) categorySpec { - return categorySpec{ - name: "control-plane-cluster", - role: RoleControlPlane, - checks: []binaryCheckSpec{ - placeholderCheck("gateway-api-crds", "checking Gateway API CRDs…", "Gateway API CRD probe — pending M3 cluster-side preflight"), - placeholderCheck("default-storageclass", "checking default StorageClass…", "Default StorageClass probe — pending M3 cluster-side preflight"), - }, +// controlPlaneCheckCategory returns the cluster-side checks for the control +// plane. The stale-namespace check runs first so a leftover namespace from a +// prior partial teardown is surfaced before any other cluster work. +// Gateway API CRD and StorageClass probes are placeholders pending M3. +func controlPlaneCheckCategory(rc RoleConfig) categorySpec { + cat := categorySpec{ + name: "control-plane-cluster", + role: RoleControlPlane, + checks: []binaryCheckSpec{}, + } + // Stale namespace check runs first so leftover namespaces surface + // before any other cluster work. + if rc.StaleNamespaceProber != nil { + cat.checks = append(cat.checks, + staleNamespaceCheck(rc.StaleNamespaceProber, rc.KubeContext, + mergeNamespaces(resolveStackNamespaces(rc.StackDir, nvcfControlPlaneNamespaces), + rc.ExtraStaleNamespaces))) + } + // Containerized cluster-validator probe for control-plane checks + // (Gateway API CRDs, Envoy Gateway, StorageClass, external LB, + // node-to-node overlay, and reachability to nvcr.io + extra registries). + if rc.ClusterValidator != nil { + cat.checks = append(cat.checks, clusterValidatorCheck( + rc.ClusterValidator, + rc.KubeContext, + rc.ClusterValidatorImage, + rc.ClusterValidatorPullSecret, + rc.ClusterValidatorNoCleanup, + validatorRoleControlPlane, + rc.ClusterValidatorRegistries, + rc.ClusterValidatorEnv, + )) } + return cat } -// computePlaneCheckCategory returns the placeholder compute-plane checks. -// Conditionally adds an SIS reachability probe when RoleConfig.SISURL is set, -// a node-inotify-limits probe when RoleConfig.InotifyProber is set, and a -// containerized cluster-validator probe when RoleConfig.ClusterValidator is -// set. Each conditional probe is opt-in via its own RoleConfig field so -// callers that opted out via a --skip-* flag simply leave the corresponding -// field nil/empty and the check is omitted from the category entirely. +// computePlaneCheckCategory returns the compute-plane checks. SIS, inotify, +// and cluster-validator probes are opt-in via their RoleConfig fields; a nil +// or empty field omits the corresponding check from the category. func computePlaneCheckCategory(rc RoleConfig) categorySpec { cat := categorySpec{ name: "compute-plane-cluster", @@ -385,6 +495,15 @@ func computePlaneCheckCategory(rc RoleConfig) categorySpec { placeholderCheck("gpu-node-labels", "checking GPU node labels…", "GPU node-label probe — pending M+10 cluster-side preflight"), }, } + // Stale namespace check runs first so leftover namespaces from a prior + // partial teardown surface before any other cluster work. + if rc.StaleNamespaceProber != nil { + cat.checks = append([]binaryCheckSpec{ + staleNamespaceCheck(rc.StaleNamespaceProber, rc.KubeContext, + mergeNamespaces(resolveStackNamespaces(rc.StackDir, nvcfComputePlaneNamespaces), + rc.ExtraStaleNamespaces)), + }, cat.checks...) + } if rc.SISURL != "" { cat.checks = append(cat.checks, sisReachabilityCheck(rc.SISURL)) } @@ -398,6 +517,9 @@ func computePlaneCheckCategory(rc RoleConfig) categorySpec { rc.ClusterValidatorImage, rc.ClusterValidatorPullSecret, rc.ClusterValidatorNoCleanup, + validatorRoleComputePlane, + nil, // registries: compute-plane doesn't use the ConfigMap reachability list + rc.ClusterValidatorEnv, )) } return cat @@ -441,18 +563,18 @@ func nodeInotifyCheck(prober NodeInotifyProber, kubeContext string) binaryCheckS Run: func(ctx context.Context) CheckResult { r := CheckResult{ ID: id, - Severity: "error", + Severity: SeverityError, HintURL: inotifyHintURL, } limits, err := prober(ctx, kubeContext) if err != nil { - r.Severity = "warning" + r.Severity = SeverityWarning r.Message = "node inotify probe failed: " + err.Error() r.Err = err return r } if len(limits) == 0 { - r.Severity = "warning" + r.Severity = SeverityWarning r.Passed = true r.Message = "no nodes returned by inotify probe; skipping" return r @@ -489,7 +611,7 @@ func nodeInotifyCheck(prober NodeInotifyProber, kubeContext string) binaryCheckS return r } if len(probeErrs) > 0 { - r.Severity = "warning" + r.Severity = SeverityWarning r.Message = fmt.Sprintf( "could not probe inotify limits on %d node(s): %s", len(probeErrs), strings.Join(probeErrs, "; "), @@ -503,10 +625,15 @@ func nodeInotifyCheck(prober NodeInotifyProber, kubeContext string) binaryCheckS } } -// Severity mapping: runner errors (RBAC/pull/timeout) -> warning, since -// they're operator-fixable infra issues; validator Passed=false -> -// error (real check failures); Passed=true -> info. -func clusterValidatorCheck(cv ClusterValidator, kubeContext, image, pullSecret string, noCleanup bool) binaryCheckSpec { +// clusterValidatorCheck runs the validator Job. A validator that could not run +// (RBAC denied, image pull failure, timeout) is an error like a validator +// failure: nothing was checked, and a readiness gate must not pass on that. +// --skip-cluster-validation is the explicit opt-out. role selects the check +// set via VALIDATOR_ROLE; registries extends the ConfigMap reachability list. +func clusterValidatorCheck( + cv ClusterValidator, kubeContext, image, pullSecret string, noCleanup bool, role string, + registries []RegistryEntry, env map[string]string, +) binaryCheckSpec { const id = "cluster-validator" return binaryCheckSpec{ ID: id, @@ -521,37 +648,416 @@ func clusterValidatorCheck(cv ClusterValidator, kubeContext, image, pullSecret s Image: image, PullSecret: pullSecret, NoCleanup: noCleanup, + Role: role, + Registries: registries, + Env: env, }) r.Logs = result.Logs - r.Detail = clusterValidatorDetail(result.JobName) + r.Detail = clusterValidatorDetail(kubeContext, result.JobName) + switch { + case noCleanup: + if hint := validatorCleanupHint(kubeContext, result.RunID); hint != "" { + r.Detail = strings.TrimPrefix(r.Detail+"; ", "; ") + hint + } + case result.LeftBehind: + // The pod could still be running, so its RBAC was kept rather + // than pulled out from under it. Say how to remove it once it + // ends, instead of leaving it for a later check's sweep. + if hint := validatorLeftBehindHint(kubeContext, result.RunID); hint != "" { + r.Detail = strings.TrimPrefix(r.Detail+"; ", "; ") + hint + } + } if result.Err != nil { - r.Severity = "warning" - r.Message = "cluster-validator did not complete: " + result.Err.Error() + r.Severity = SeverityError + r.Message = "cluster-validator did not complete, so the cluster was not validated: " + + result.Err.Error() + "; fix the cause, or pass --skip-cluster-validation to run without it" r.Err = result.Err return r } + // An image that predates role support ignores VALIDATOR_ROLE and runs + // the compute-plane GPU checks, which fail on a CPU-only control + // plane. The latest tag can be such an image until a release with + // role support is published, so report it as the image being too + // old rather than as the cluster failing. That needs positive + // evidence of the legacy check set, not just a missing role line: a + // role-aware image that fails before printing it (say, building its + // Kubernetes client) must still fail the run. + if role == validatorRoleControlPlane && isLegacyComputePlaneTranscript(result.Logs) { + // Downgrade only the failure the old image causes by itself: its + // GPU check on a CPU-only control plane. Its checks common to both + // roles (the control plane's /readyz, admission webhooks, critical + // registry reachability) still see real failures, and those fail. + if others := legacyCriticalFailures(result.Logs); len(others) > 0 { + r.Severity = SeverityError + r.Message = "cluster-validator reported failures (" + strings.Join(others, "; ") + + "); the image predates validator roles, so only its checks common to both roles ran" + return r + } + r.Severity = SeverityWarning + r.Message = "cluster-validator image does not support the control-plane checks (it ran the " + + "compute-plane set); use an image from an NVCA release that supports validator roles, " + + "or pass --skip-cluster-validation" + return r + } r.Passed = result.Passed + if result.Passed && strings.Contains(result.Logs, validatorReadyWithWarnings) { + // The Job succeeding is not a clean pass when the validator + // itself reported warnings. A rollout in progress is expected + // to clear, so --wait keeps polling on it. + r.Passed = false + r.Severity = SeverityWarning + r.Transient = isRolloutTranscript(result.Logs) + r.Message = "cluster-validator passed with warnings; run with --show-logs for details" + if r.Transient { + r.Message = "cluster-validator passed with a rollout in progress; --wait polls until it completes" + } + return r + } if result.Passed { - r.Severity = "info" + r.Severity = SeverityInfo r.Message = "cluster passed cluster-validator built-in checks" return r } - r.Severity = "error" + r.Severity = SeverityError r.Message = fmt.Sprintf("cluster-validator reported failures (exit code %d)", result.ExitCode) return r }, } } -func clusterValidatorDetail(jobName string) string { - hint := kubectlLogsHint(jobName) +// validatorRoleMarker prefixes the line a role-aware validator prints naming +// the check set it runs. Must match RoleMarker in +// nvca/internal/clustervalidator/validator.go. +const validatorRoleMarker = "Validator role: " + +// validatorReadyWithWarnings is in the verdict line of a run that passed every +// critical check with warnings. Must match printSummary in +// nvca/internal/clustervalidator/validator.go. +const validatorReadyWithWarnings = "NVCF-Ready (with warnings)" + +// validatorRolloutMarkers are in the validator's warnings for a Deployment or +// StatefulSet rollout in progress (checks.go, Tier-1 and Tier-2). +var validatorRolloutMarkers = []string{"rollout in progress", "rolling update in progress", "mid-rollout"} + +func isRolloutTranscript(logs string) bool { + for _, m := range validatorRolloutMarkers { + if strings.Contains(logs, m) { + return true + } + } + return false +} + +// validatorSummaryStart and validatorFailIcon locate the failed critical rows +// the validator prints in its summary. Must match printSummary and printError +// in nvca/internal/clustervalidator. +const ( + validatorSummaryStart = "Check Results:" + validatorFailIcon = "\u2717" +) + +// legacyCriticalFailures returns the failed critical summary rows of a +// pre-role transcript other than GPU Resources, the one row the compute-plane +// set fails on any CPU-only control plane. A transcript without a summary +// returns a row saying so: nothing shows the run was otherwise clean. +func legacyCriticalFailures(logs string) []string { + i := strings.LastIndex(logs, validatorSummaryStart) + if i < 0 { + return []string{"the validator printed no summary"} + } + var out []string + for _, line := range strings.Split(logs[i:], "\n") { + if strings.Contains(line, "Cluster is") { + break + } + _, row, ok := strings.Cut(line, validatorFailIcon) + if !ok { + continue + } + if row = strings.TrimSpace(row); row != "" && !strings.HasPrefix(row, "GPU Resources") { + out = append(out, row) + } + } + return out +} + +// legacyComputePlaneHeader is a section header only the compute-plane check +// set prints. A validator that predates roles always runs that set. +const legacyComputePlaneHeader = "GPU Resources" + +// isLegacyComputePlaneTranscript reports whether logs come from a validator +// that ignored the requested control-plane role: no role line, and the +// compute-plane check set's output instead. +func isLegacyComputePlaneTranscript(logs string) bool { + return !strings.Contains(logs, validatorRoleMarker) && strings.Contains(logs, legacyComputePlaneHeader) +} + +// validatorCleanupHint is the command that removes everything one --no-cleanup +// run kept. Everything it created carries the run label, so one selector +// covers the namespaced objects and one the cluster-scoped RBAC. +func validatorCleanupHint(kubeContext, runID string) string { + if runID == "" { + return "" + } + sel := clusterValidatorRunLabel + "=" + runID + ctx := kubectlContextArg(kubeContext) + return fmt.Sprintf("kept with --no-cleanup; remove with: kubectl%s delete -n %s job,serviceaccount,secret,configmap -l %s && "+ + "kubectl%s delete clusterrole,clusterrolebinding -l %s", + ctx, clusterValidatorNamespace, sel, ctx, sel) +} + +// validatorLeftBehindHint is the removal command for a run whose pod outlived +// the wait, so its RBAC was kept. +func validatorLeftBehindHint(kubeContext, runID string) string { + if runID == "" { + return "" + } + sel := clusterValidatorRunLabel + "=" + runID + ctx := kubectlContextArg(kubeContext) + return fmt.Sprintf("the validator pod was still running, so its objects were kept; once it ends remove them "+ + "with: kubectl%s delete -n %s job,serviceaccount,secret,configmap -l %s && "+ + "kubectl%s delete clusterrole,clusterrolebinding -l %s", + ctx, clusterValidatorNamespace, sel, ctx, sel) +} + +func clusterValidatorDetail(kubeContext, jobName string) string { + hint := kubectlLogsHint(kubeContext, jobName) if hint == "" { return "" } return "logs: " + hint } +// buildRegistryCredentialCategory returns the registry-credentials category +// that probes each configured registry for reachability and valid credentials. +func buildRegistryCredentialCategory(cfg PreflightConfig) categorySpec { + cat := categorySpec{ + name: "registry-credentials", + role: RoleLocalOnly, // runs regardless of cluster role + checks: make([]binaryCheckSpec, 0, len(cfg.Registries)), + } + for _, reg := range cfg.Registries { + reg := reg // capture loop var + cat.checks = append(cat.checks, registryCredentialCheck(cfg.RegistryChecker, reg)) + } + return cat +} + +// registryCredentialCheck returns a binaryCheckSpec that probes one registry. +// Critical registries (nvcr.io) fail at error severity; non-critical ones +// fail at warning so they don't block the operator on optional registries. +func registryCredentialCheck(checker RegistryCredentialChecker, entry RegistryEntry) binaryCheckSpec { + id := "registry-cred-" + entry.Registry + severity := SeverityWarning + if entry.Critical { + severity = SeverityError + } + return binaryCheckSpec{ + ID: id, + HumanLabel: fmt.Sprintf("checking credentials for %s...", entry.Registry), + Run: func(ctx context.Context) CheckResult { + r := CheckResult{ + ID: id, + Severity: severity, + } + if err := checker(ctx, entry.Registry, entry.RepoHint, entry.Critical); err != nil { + // A registry this probe cannot speak to (ECR's SigV4, a Basic + // challenge) is not evidence of a credential problem, and an + // unreadable credential helper is not evidence of a missing + // credential. Neither may block the run. + var skipped errRegistryProbeSkipped + var unverified errRegistryCredentialsUnverified + var notVerified errRegistryCredentialsNotVerified + switch { + case errors.As(err, ¬Verified): + r.Passed = true + r.Severity = SeverityInfo + r.Message = entry.Registry + ": " + err.Error() + return r + case errors.As(err, &skipped): + r.Passed = true + r.Severity = SeverityInfo + r.Message = entry.Registry + ": skipped (" + skipped.reason + ")" + return r + case errors.As(err, &unverified): + r.Severity = SeverityWarning + r.Message = entry.Registry + ": " + err.Error() + r.Err = err + return r + } + r.Message = entry.Registry + ": " + err.Error() + r.Err = err + return r + } + r.Passed = true + r.Severity = SeverityInfo + r.Message = entry.Registry + ": credentials valid" + return r + }, + } +} + +// mergeNamespaces returns the union of two namespace lists, order-stable and +// de-duplicated. +func mergeNamespaces(base, extra []string) []string { + if len(extra) == 0 { + return base + } + seen := make(map[string]bool, len(base)+len(extra)) + out := make([]string, 0, len(base)+len(extra)) + for _, list := range [][]string{base, extra} { + for _, ns := range list { + if ns == "" || seen[ns] { + continue + } + seen[ns] = true + out = append(out, ns) + } + } + return out +} + +// staleNamespaceCheck detects NVCF namespaces stuck Terminating or left as +// empty shells after a partial teardown. Severity is error; prober errors +// degrade to warning so transient kubeconfig issues don't falsely fail. +func staleNamespaceCheck(prober StaleNamespaceProber, kubeContext string, namespaces []string) binaryCheckSpec { + const id = "stale-namespaces" + return binaryCheckSpec{ + ID: id, + HumanLabel: "checking for stale NVCF namespaces...", + Run: func(ctx context.Context) CheckResult { + r := CheckResult{ID: id, Severity: SeverityError} + // Resolve the context before probing, then probe that exact name. + // Resolving afterwards leaves a window where the current-context + // changes in between, which would have the hints delete namespaces + // in a cluster other than the one that was read. + probedContext := effectiveKubeContext(kubeContext) + stale, err := prober(ctx, probedContext, namespaces) + if err != nil { + r.Severity = SeverityWarning + r.Message = "stale namespace probe failed: " + err.Error() + r.Err = err + return r + } + if len(stale) == 0 { + r.Severity = SeverityInfo + r.Passed = true + r.Message = "no stale NVCF namespaces detected" + return r + } + parts := make([]string, 0, len(stale)) + var terminating, emptyShell []string + for _, ns := range stale { + parts = append(parts, ns.Name+" ("+ns.Reason+")") + if ns.Reason == "stuck Terminating" { + terminating = append(terminating, ns.Name) + } else { + emptyShell = append(emptyShell, ns.Name) + } + } + // Every hint names the context that was actually probed, above. + // In split mode the two callers pass different contexts, and with + // no context flag the probe followed the current-context. Either + // way the name goes into the command, because these hints delete + // namespaces and the current-context can change between reading + // the output and pasting it. + kctl := "kubectl" + kubectlContextArg(probedContext) + var hints []string + if len(terminating) > 0 { + // Inspect first, force last. A namespace usually stays + // Terminating because an object inside it still has a + // finalizer; clearing the namespace's own finalizers skips that + // object's cleanup and can orphan what it manages, such as a + // cloud load balancer or volume. + // One command per namespace with the real name substituted. A + // `` placeholder is not pasteable: the shell reads `<` and + // `>` as redirection, so the command fails before kubectl runs. + for _, ns := range terminating { + hints = append(hints, fmt.Sprintf( + "find what is holding %s in Terminating: "+ + "%s api-resources --verbs=list --namespaced -o name | "+ + "xargs -n1 %s get -n %s --show-kind --ignore-not-found", + ns, kctl, kctl, ns)) + } + // spec.finalizers is writable only through the /finalize + // subresource: a plain patch or update is silently reverted by + // the apiserver's namespace strategy, so `kubectl patch ... + // --type=merge` prints "patched" and changes nothing. + for _, ns := range terminating { + hints = append(hints, fmt.Sprintf( + "only if nothing inside %s can be cleaned up, force-clear its finalizers: %s get ns %s -o json | "+ + "jq '.spec.finalizers=[]' | %s replace --raw /api/v1/namespaces/%s/finalize -f -", + ns, kctl, ns, kctl, ns)) + } + } + if len(emptyShell) > 0 { + // Deliberately not a delete command. A namespace with no Helm + // release is not necessarily stale: the stack gates + // cert-manager, NATS, OpenBao and Cassandra on *.enabled, so an + // operator who installs one the documented upstream way, or via + // Argo, owns a healthy namespace with no owner=helm object. + // Handing them "kubectl delete namespace cert-manager" would + // destroy every Certificate and Issuer in the cluster. + // One command per namespace. kubectl takes a single + // namespace and keeps the last -n it sees, so joining them + // produces a command that silently inspects only the last one + // and reports the rest as empty, which is the opposite of what + // an operator deciding whether to delete them needs. + // Enumerate every namespaced kind, not `get all`: that omits + // PVCs, Secrets, ConfigMaps and custom resources, so after a + // partial teardown it reports "No resources found" over the + // Cassandra volumes and the OpenBao unseal Secret. + for _, ns := range emptyShell { + hints = append(hints, fmt.Sprintf( + "inspect %s and remove it only after confirming it is unused: "+ + "%s api-resources --verbs=list --namespaced -o name | "+ + "xargs -n1 %s get -n %s --show-kind --ignore-not-found", + ns, kctl, kctl, ns)) + } + } + // Only a namespace stuck Terminating blocks the run. "No Helm + // release" is a heuristic over a conditionally-installed stack, so + // it warns rather than turning a supported install shape into exit 2. + if len(terminating) == 0 { + r.Severity = SeverityWarning + } + r.Message = fmt.Sprintf("%d stale namespace(s) detected: %s. To resolve: %s", + len(stale), strings.Join(parts, ", "), strings.Join(hints, "; ")) + return r + }, + } +} + +// kubectlContextArg renders the probed context as a --context flag for the +// remediation hints, or "" when no explicit context was given so the hint stays +// readable for a single-cluster setup. The value is shell-quoted: a context +// name is operator-supplied and arbitrary. +func kubectlContextArg(kubeContext string) string { + if kubeContext == "" { + return "" + } + return " --context " + shellQuoteArg(kubeContext) +} + +// shellQuoteArg makes s safe to paste into a shell. Unquoted when it holds only +// characters no shell treats specially, so the common case stays legible. +func shellQuoteArg(s string) string { + if s == "" { + return "''" + } + safe := strings.IndexFunc(s, func(r rune) bool { + return !(r == '-' || r == '_' || r == '.' || r == '/' || r == ':' || r == '@' || r == '=' || + (r >= '0' && r <= '9') || + (r >= 'A' && r <= 'Z') || + (r >= 'a' && r <= 'z')) + }) == -1 + if safe { + return s + } + return "'" + strings.ReplaceAll(s, "'", "'\"'\"'") + "'" +} + // placeholderCheck returns a binaryCheckSpec that emits a passing "info" // CheckResult with the given message. Used until M3/M+10 cluster-side probes ship. func placeholderCheck(id, label, message string) binaryCheckSpec { @@ -561,7 +1067,7 @@ func placeholderCheck(id, label, message string) binaryCheckSpec { Run: func(_ context.Context) CheckResult { return CheckResult{ ID: id, - Severity: "info", + Severity: SeverityInfo, Passed: true, Message: message, } @@ -580,7 +1086,7 @@ func sisReachabilityCheck(sisURL string) binaryCheckSpec { req, err := http.NewRequestWithContext(ctx, http.MethodGet, strings.TrimRight(sisURL, "/")+"/v1/health", nil) if err != nil { return CheckResult{ - ID: "sis-reachability", Severity: "error", Passed: false, + ID: "sis-reachability", Severity: SeverityError, Passed: false, Message: "SIS request build failed: " + err.Error(), HintURL: "https://docs.nvidia.com/nvcf/self-hosted/troubleshooting#sis-reachability", } @@ -588,7 +1094,7 @@ func sisReachabilityCheck(sisURL string) binaryCheckSpec { resp, err := cli.Do(req) if err != nil { return CheckResult{ - ID: "sis-reachability", Severity: "error", Passed: false, + ID: "sis-reachability", Severity: SeverityError, Passed: false, Message: "SIS unreachable: " + err.Error(), HintURL: "https://docs.nvidia.com/nvcf/self-hosted/troubleshooting#sis-reachability", } @@ -596,13 +1102,13 @@ func sisReachabilityCheck(sisURL string) binaryCheckSpec { defer resp.Body.Close() if resp.StatusCode >= 500 { return CheckResult{ - ID: "sis-reachability", Severity: "error", Passed: false, + ID: "sis-reachability", Severity: SeverityError, Passed: false, Message: fmt.Sprintf("SIS returned %d", resp.StatusCode), HintURL: "https://docs.nvidia.com/nvcf/self-hosted/troubleshooting#sis-reachability", } } return CheckResult{ - ID: "sis-reachability", Severity: "info", Passed: true, + ID: "sis-reachability", Severity: SeverityInfo, Passed: true, Message: fmt.Sprintf("SIS reachable (%d)", resp.StatusCode), } }, @@ -632,17 +1138,35 @@ func runPreflightImpl(ctx context.Context, cfg PreflightConfig, role Role, rc Ro categories := buildCategories(cfg, role, rc) for _, cat := range categories { catStart := time.Now() - var passed, failed int + var catResults []CheckResult for _, spec := range cat.checks { - if ctx.Err() != nil { + var res CheckResult + switch { + case errors.Is(ctx.Err(), context.Canceled): + // Interrupted: the command reports the interrupt, not rows. return all + case ctx.Err() != nil: + // The budget ran out before this check started. Dropping it + // let the partial set grade as the verdict, so a validator + // that never ran could pass the gate. + res = CheckResult{ + ID: spec.ID, Severity: SeverityError, + Message: "not run: the check's time budget ran out before it started", + Err: ctx.Err(), + } + default: + _ = sink.Emit(ctx, progress.CheckStarted{ + Category: cat.name, + ID: spec.ID, + Message: spec.HumanLabel, + }) + res = spec.Run(ctx) + if errors.Is(ctx.Err(), context.Canceled) { + // Cut short by the interrupt, so its result is not a + // finding; the cancelled final event says what happened. + return all + } } - _ = sink.Emit(ctx, progress.CheckStarted{ - Category: cat.name, - ID: spec.ID, - Message: spec.HumanLabel, - }) - res := spec.Run(ctx) res.Category = cat.name all = append(all, res) _ = sink.Emit(ctx, progress.CheckCompleted{ @@ -654,17 +1178,15 @@ func runPreflightImpl(ctx context.Context, cfg PreflightConfig, role Role, rc Ro Detail: res.Detail, HintURL: res.HintURL, }) - if res.Passed { - passed++ - } else { - failed++ - } + catResults = append(catResults, res) } + passed, failed, warned := CountResults(catResults) _ = sink.Emit(ctx, progress.CategoryCompleted{ - Category: cat.name, - PassedCount: passed, - FailedCount: failed, - DurationSec: time.Since(catStart).Seconds(), + Category: cat.name, + PassedCount: passed, + FailedCount: failed, + WarningCount: warned, + DurationSec: time.Since(catStart).Seconds(), }) } return all @@ -686,3 +1208,14 @@ func (*noopSink) Close() error { return nil } func RunPreflight(ctx context.Context, cfg PreflightConfig) []CheckResult { return RunPreflightStreaming(ctx, cfg, &noopSink{}) } + +// ControlPlaneStaleNamespaces and ComputePlaneStaleNamespaces expose each +// role's stale-namespace list so the cmd layer can merge the two when a single +// cluster hosts both roles and only one probe runs. +func ControlPlaneStaleNamespaces(stackDir string) []string { + return resolveStackNamespaces(stackDir, nvcfControlPlaneNamespaces) +} + +func ComputePlaneStaleNamespaces(stackDir string) []string { + return resolveStackNamespaces(stackDir, nvcfComputePlaneNamespaces) +} diff --git a/src/clis/nvcf-cli/internal/selfhosted/preflight_test.go b/src/clis/nvcf-cli/internal/selfhosted/preflight_test.go index 262075dff8..d0e0515b7a 100644 --- a/src/clis/nvcf-cli/internal/selfhosted/preflight_test.go +++ b/src/clis/nvcf-cli/internal/selfhosted/preflight_test.go @@ -25,6 +25,7 @@ import ( "os" "path/filepath" "testing" + "time" "github.com/Masterminds/semver/v3" "github.com/stretchr/testify/assert" @@ -408,7 +409,10 @@ func TestRunPreflightForRole_LocalOnly(t *testing.T) { func TestRunPreflightForRole_ControlPlaneAddsClusterCategory(t *testing.T) { sink := &captureSink{} cfg := PreflightConfig{Tools: []BinarySpec{passingToolSpec("kubectl", "1.30.0")}} - res := RunPreflightForRole(context.Background(), cfg, RoleControlPlane, RoleConfig{KubeContext: "admin@cp"}, sink) + // No StaleNamespaceProber and no ClusterValidator: the control-plane + // category is empty but the category itself still fires (CategoryCompleted + // is always emitted). + RunPreflightForRole(context.Background(), cfg, RoleControlPlane, RoleConfig{KubeContext: "admin@cp"}, sink) seen := map[string]bool{} for _, e := range sink.events { @@ -417,14 +421,27 @@ func TestRunPreflightForRole_ControlPlaneAddsClusterCategory(t *testing.T) { } } assert.True(t, seen["local-host-tools"], "expected local-host-tools category") - assert.True(t, seen["control-plane-cluster"], "expected control-plane-cluster category") + assert.True(t, seen["control-plane-cluster"], "expected control-plane-cluster category even with no checks configured") +} + +func TestRunPreflightForRole_ControlPlaneWithValidatorAddsClusterValidatorCheck(t *testing.T) { + sink := &captureSink{} + cfg := PreflightConfig{Tools: []BinarySpec{passingToolSpec("kubectl", "1.30.0")}} + cv := func(_ context.Context, p ClusterValidatorParams) ClusterValidatorResult { + return ClusterValidatorResult{Passed: true} + } + res := RunPreflightForRole(context.Background(), cfg, RoleControlPlane, RoleConfig{ + KubeContext: "admin@cp", + ClusterValidator: cv, + ClusterValidatorImage: "nvcf-validator:1.0", + }, sink) var gotCheckIDs []string for _, r := range res { gotCheckIDs = append(gotCheckIDs, r.ID) } - assert.Contains(t, gotCheckIDs, "gateway-api-crds") - assert.Contains(t, gotCheckIDs, "default-storageclass") + assert.Contains(t, gotCheckIDs, "cluster-validator", + "cluster-validator check must appear when ClusterValidator is configured for control-plane role") } func TestRunPreflightForRole_ComputePlaneWithoutSISURL(t *testing.T) { @@ -654,3 +671,80 @@ func TestParseInotifyOutput(t *testing.T) { require.Error(t, err) }) } + +// In ModeSingle one cluster hosts both roles and only one stale-namespace +// probe runs, so the skipped role's namespaces must be merged in. Dropping +// them means a kai-scheduler or nvca-operator namespace wedged Terminating is +// reported as "no stale NVCF namespaces detected". +func TestStaleNamespaceCheck_MergesTheOtherRolesNamespaces(t *testing.T) { + var probed []string + prober := func(_ context.Context, _ string, namespaces []string) ([]StaleNamespace, error) { + probed = namespaces + return nil, nil + } + rc := RoleConfig{ + StaleNamespaceProber: prober, + ExtraStaleNamespaces: ComputePlaneStaleNamespaces(""), + } + cat := controlPlaneCheckCategory(rc) + require.NotEmpty(t, cat.checks) + cat.checks[0].Run(context.Background()) + + for _, ns := range []string{"nvcf", "vault-system"} { + assert.Contains(t, probed, ns, "the control-plane list must still be covered") + } + for _, ns := range []string{"nvca-operator", "kai-scheduler"} { + assert.Contains(t, probed, ns, "the compute-plane list must be merged in, not dropped") + } +} + +func TestMergeNamespaces_DedupesAndKeepsOrder(t *testing.T) { + got := mergeNamespaces([]string{"a", "b"}, []string{"b", "c", ""}) + assert.Equal(t, []string{"a", "b", "c"}, got) + assert.Nil(t, mergeNamespaces(nil, nil)) +} + +// Every count derives from one rule: only an error-severity miss fails; any +// other miss is a warning. +func TestCountResults_OneRule(t *testing.T) { + p, f, w := CountResults([]CheckResult{ + {Passed: true, Severity: SeverityInfo}, + {Passed: false, Severity: SeverityError}, + {Passed: false, Severity: SeverityWarning}, + {Passed: false, Severity: SeverityInfo}, + }) + assert.Equal(t, 1, p) + assert.Equal(t, 1, f) + assert.Equal(t, 2, w) +} + +// When the budget runs out, the checks not yet started are error rows rather +// than silently dropped, so a partial run cannot grade as a pass. An +// interrupt ends the run without them: the command reports the interrupt. +func TestRunPreflight_BudgetSpentChecksAreNotRunErrors(t *testing.T) { + cfg := PreflightConfig{Tools: []BinarySpec{passingToolSpec("kubectl", "1.30.0"), passingToolSpec("helm", "3.15.0")}} + + expired, cancel := context.WithDeadline(context.Background(), time.Now().Add(-time.Second)) + defer cancel() + res := RunPreflightForRole(expired, cfg, RoleLocalOnly, RoleConfig{}, &captureSink{}) + require.NotEmpty(t, res) + for _, r := range res { + assert.True(t, r.IsBlockingFailure(), r.ID) + assert.Contains(t, r.Message, "not run", r.ID) + } + + cancelled, stop := context.WithCancel(context.Background()) + stop() + assert.Empty(t, RunPreflightForRole(cancelled, cfg, RoleLocalOnly, RoleConfig{}, &captureSink{})) + + // A check cut short by the interrupt is not a finding either. + midRun, stopMidRun := context.WithCancel(context.Background()) + defer stopMidRun() + interrupting := passingToolSpec("kubectl", "1.30.0") + interrupting.Version = func(context.Context, string) (*semver.Version, error) { + stopMidRun() + return nil, context.Canceled + } + cfg = PreflightConfig{Tools: []BinarySpec{interrupting}} + assert.Empty(t, RunPreflightForRole(midRun, cfg, RoleLocalOnly, RoleConfig{}, &captureSink{})) +} diff --git a/src/clis/nvcf-cli/internal/selfhosted/progress/event.go b/src/clis/nvcf-cli/internal/selfhosted/progress/event.go index a0bb6075da..b69d96fb1a 100644 --- a/src/clis/nvcf-cli/internal/selfhosted/progress/event.go +++ b/src/clis/nvcf-cli/internal/selfhosted/progress/event.go @@ -288,8 +288,11 @@ func (CheckCompleted) kind() eventKind { return "check_completed" } type CategoryCompleted struct { Category string PassedCount int - FailedCount int - DurationSec float64 + // FailedCount counts error-severity failures only, the ones that fail the + // run; WarningCount counts the non-passing rest. + FailedCount int + WarningCount int + DurationSec float64 } func (CategoryCompleted) kind() eventKind { return "category_completed" } diff --git a/src/clis/nvcf-cli/internal/selfhosted/progress/k8s_watcher_test.go b/src/clis/nvcf-cli/internal/selfhosted/progress/k8s_watcher_test.go index c8e22dd93f..03e2334eea 100644 --- a/src/clis/nvcf-cli/internal/selfhosted/progress/k8s_watcher_test.go +++ b/src/clis/nvcf-cli/internal/selfhosted/progress/k8s_watcher_test.go @@ -135,7 +135,7 @@ func TestWatchResources_EmitsCountsForEachType(t *testing.T) { }, 3*time.Second, 50*time.Millisecond, "did not see all 5 resource types") last := sink.lastByResource() - assert.Equal(t, 4, last["namespaces"].Num) // phaseNum baked in + assert.Equal(t, 4, last["namespaces"].Num) // phaseNum baked in assert.Equal(t, "apply-cp", last["namespaces"].Name) // phaseName baked in assert.Equal(t, "namespaces", last["namespaces"].Resource) assert.Equal(t, 2, last["namespaces"].Done) diff --git a/src/clis/nvcf-cli/internal/selfhosted/progress/log_line_writer.go b/src/clis/nvcf-cli/internal/selfhosted/progress/log_line_writer.go index 3f6ff8f481..1a2cba7262 100644 --- a/src/clis/nvcf-cli/internal/selfhosted/progress/log_line_writer.go +++ b/src/clis/nvcf-cli/internal/selfhosted/progress/log_line_writer.go @@ -41,12 +41,12 @@ import ( // EventSink is responsible for its own concurrency (JSONLRenderer's mu; // TTYRenderer's tea.Program.Send is goroutine-safe by design). type LogLineWriter struct { - mu sync.Mutex - buf bytes.Buffer - sink EventSink - stream string // "stdout" | "stderr" - source string // short producer tag, e.g. "helmfile-cp" - closed bool + mu sync.Mutex + buf bytes.Buffer + sink EventSink + stream string // "stdout" | "stderr" + source string // short producer tag, e.g. "helmfile-cp" + closed bool } // NewLogLineWriter constructs a LogLineWriter feeding into sink. stream and diff --git a/src/clis/nvcf-cli/internal/selfhosted/progress/render_jsonl.go b/src/clis/nvcf-cli/internal/selfhosted/progress/render_jsonl.go index 3f4940c1fd..17a3d85df4 100644 --- a/src/clis/nvcf-cli/internal/selfhosted/progress/render_jsonl.go +++ b/src/clis/nvcf-cli/internal/selfhosted/progress/render_jsonl.go @@ -547,12 +547,13 @@ func (r *JSONLRenderer) toWire(e Event, ts string) any { } case CategoryCompleted: return wireCategoryCompleted{ - Event: "category_completed", - TS: ts, - Category: ev.Category, - PassedCount: ev.PassedCount, - FailedCount: ev.FailedCount, - DurationSec: ev.DurationSec, + Event: "category_completed", + TS: ts, + Category: ev.Category, + PassedCount: ev.PassedCount, + FailedCount: ev.FailedCount, + WarningCount: ev.WarningCount, + DurationSec: ev.DurationSec, } } return nil @@ -710,7 +711,7 @@ type wireClusterRow struct { Event string `json:"event"` TS string `json:"ts"` Name string `json:"name"` - Context string `json:"context,omitempty"` // M+9: omitted when unknown + Context string `json:"context,omitempty"` // M+9: omitted when unknown GPU string `json:"gpu"` GPUCount int `json:"gpuCount"` ActiveDeployments int `json:"activeDeployments"` @@ -750,12 +751,13 @@ type wireCheckCompleted struct { } type wireCategoryCompleted struct { - Event string `json:"event"` - TS string `json:"ts"` - Category string `json:"category"` - PassedCount int `json:"passedCount"` - FailedCount int `json:"failedCount"` - DurationSec float64 `json:"durationSec"` + Event string `json:"event"` + TS string `json:"ts"` + Category string `json:"category"` + PassedCount int `json:"passedCount"` + FailedCount int `json:"failedCount"` + WarningCount int `json:"warningCount"` + DurationSec float64 `json:"durationSec"` } // Wire structs for composed status snapshot (Plan Deviation #19 / §6.5.4). @@ -766,12 +768,12 @@ type wireCategoryCompleted struct { // mode. It carries the Snapshot identity plus all ComponentHealth, ClusterRow, // and RecentEvent sub-objects inlined. type wireSnapshotComposed struct { - Event string `json:"event"` - TS string `json:"ts"` - Cluster string `json:"cluster"` - Verdict string `json:"verdict"` - ReconcileAgeSec int `json:"reconcileAgeSec"` - Identity wireSnapshotIdentity `json:"identity"` + Event string `json:"event"` + TS string `json:"ts"` + Cluster string `json:"cluster"` + Verdict string `json:"verdict"` + ReconcileAgeSec int `json:"reconcileAgeSec"` + Identity wireSnapshotIdentity `json:"identity"` Components []wireComponentInline `json:"components,omitempty"` ComputeClusters []wireClusterInline `json:"computeClusters,omitempty"` Events []wireEventInline `json:"events,omitempty"` @@ -781,7 +783,7 @@ type wireSnapshotComposed struct { type wireComponentInline struct { Name string `json:"name"` Cluster string `json:"cluster,omitempty"` - Role string `json:"role,omitempty"` // M+9: omitted in single-cluster mode + Role string `json:"role,omitempty"` // M+9: omitted in single-cluster mode Ready int `json:"ready"` Total int `json:"total"` UptimeSec int `json:"uptimeSec"` @@ -792,7 +794,7 @@ type wireComponentInline struct { // wireClusterInline is a ClusterRow without event/ts fields. type wireClusterInline struct { Name string `json:"name"` - Context string `json:"context,omitempty"` // M+9: omitted when unknown + Context string `json:"context,omitempty"` // M+9: omitted when unknown GPU string `json:"gpu"` GPUCount int `json:"gpuCount"` ActiveDeployments int `json:"activeDeployments"` diff --git a/src/clis/nvcf-cli/internal/selfhosted/progress/render_jsonl_test.go b/src/clis/nvcf-cli/internal/selfhosted/progress/render_jsonl_test.go index 3ceb92eb20..3f55654def 100644 --- a/src/clis/nvcf-cli/internal/selfhosted/progress/render_jsonl_test.go +++ b/src/clis/nvcf-cli/internal/selfhosted/progress/render_jsonl_test.go @@ -82,7 +82,7 @@ func TestRenderJSONL_PhaseFailed(t *testing.T) { ErrCategory: "helm_apply", ErrMessage: "helm install api-keys: timed out", RetryClass: "backoff", RetryAfterSec: 60, Remediation: []string{"kubectl describe pod -n cassandra-system cassandra-0", "Re-run with --debug"}, - Raw: RawFailure{Subprocess: "helmfile", ExitCode: 1, StderrTail: "Error: timeout reached", KubernetesReason: "FailedScheduling"}, + Raw: RawFailure{Subprocess: "helmfile", ExitCode: 1, StderrTail: "Error: timeout reached", KubernetesReason: "FailedScheduling"}, }, } got := runEmit(t, clock, events) diff --git a/src/clis/nvcf-cli/internal/selfhosted/progress/render_plain.go b/src/clis/nvcf-cli/internal/selfhosted/progress/render_plain.go index 1d3046f07c..3c37759008 100644 --- a/src/clis/nvcf-cli/internal/selfhosted/progress/render_plain.go +++ b/src/clis/nvcf-cli/internal/selfhosted/progress/render_plain.go @@ -239,8 +239,8 @@ func (r *PlainRenderer) Emit(_ context.Context, e Event) error { } return r.writef("\n") case CategoryCompleted: - return r.writef("%s [pre] category_completed %-25spassed=%d failed=%d duration=%gs\n", - ts, ev.Category, ev.PassedCount, ev.FailedCount, ev.DurationSec) + return r.writef("%s [pre] category_completed %-25spassed=%d failed=%d warnings=%d duration=%gs\n", + ts, ev.Category, ev.PassedCount, ev.FailedCount, ev.WarningCount, ev.DurationSec) case DrainProgress: return r.writef("%s [%02d/%d] drain-active: %s → %s\n", ts, ev.Num, r.effectiveTotalPhases(), ev.Deployment, ev.State) diff --git a/src/clis/nvcf-cli/internal/selfhosted/progress/render_tty.go b/src/clis/nvcf-cli/internal/selfhosted/progress/render_tty.go index 16b10cb677..bcbf80f4b6 100644 --- a/src/clis/nvcf-cli/internal/selfhosted/progress/render_tty.go +++ b/src/clis/nvcf-cli/internal/selfhosted/progress/render_tty.go @@ -192,6 +192,14 @@ type ModelOpts struct { // Leave both empty (the default) for single-cluster mode. ControlPlaneContext string ComputePlaneContext string + + // OnQuit runs when the operator presses a quit key. Bubbletea reads the + // terminal in raw mode, so Ctrl-C arrives as a key press, not a signal: + // without this, quitting closed the dashboard while the command kept + // running behind it. It is called synchronously inside Update, so it must + // not block: cancel a context or signal a channel, and leave the cleanup + // to the command. + OnQuit func() } // checkCategoryState holds the accumulated state for one pre-flight check @@ -229,6 +237,7 @@ type Model struct { controlCtx string // M+9: control-plane kubeconfig context (empty → single-cluster) computeCtx string // M+9: compute-plane kubeconfig context (empty → single-cluster) nowFunc func() time.Time + onQuit func() // dynamic state (mutated by Update) started time.Time @@ -285,6 +294,7 @@ type Model struct { checkFinalTotal int checkFinalPassed int checkFinalFailed int + checkCancelled bool // final event came from SIGINT/SIGTERM, not a verdict // log tail (LogLine ring buffer; rendered as a "Recent" panel during // long phases like apply-cp). Capacity is fixed; new lines push older @@ -353,6 +363,7 @@ func NewModel(opts ModelOpts) Model { asciiOnly: opts.AsciiOnly, mode: opts.Mode, totalChecks: opts.TotalChecks, + onQuit: opts.OnQuit, } switch opts.Mode { @@ -436,6 +447,9 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case tea.KeyMsg: switch msg.String() { case "q", "ctrl+c", "esc": + if m.onQuit != nil { + m.onQuit() + } return m, tea.Quit } return m, nil @@ -744,6 +758,7 @@ func (m Model) applyCheckEvent(e Event) (tea.Model, tea.Cmd) { m.checkFinalTotal = ev.TotalChecks m.checkFinalPassed = ev.PassedCount m.checkFinalFailed = ev.FailedCount + m.checkCancelled = ev.Cancelled return m, tea.Quit } return m, nil @@ -1763,13 +1778,16 @@ func humanCategoryName(raw string) string { // checkGlyph returns the bracketed glyph for a check row. Matches install-mode // convention: bracket + unicode, unchanged in ASCII-only mode (AsciiOnly only -// strips ANSI color codes, not unicode characters). +// strips ANSI color codes, not unicode characters). A miss below error +// severity is a warning, tallied and exited on as one, so it gets its own mark. func checkGlyph(row checkRow) string { switch { case row.finished && row.passed: return "[✓]" - case row.finished && !row.passed: + case row.finished && row.severity == "error": return "[✘]" + case row.finished: + return "[!]" case row.started && !row.finished: return "[▶]" default: @@ -1819,7 +1837,7 @@ func (m Model) viewCheck(now time.Time) string { } // ── status line ─────────────────────────────────────────────────────────── - passed, failed, total := m.checkTally() + passed, failed, warned, total := m.checkTally() if m.finished && m.checkFinalTotal > 0 { passed = m.checkFinalPassed failed = m.checkFinalFailed @@ -1843,16 +1861,25 @@ func (m Model) viewCheck(now time.Time) string { } allFinished := !anyInFlight && m.finished + tally := fmt.Sprintf("%d/%d passed, %d failed", passed, total, failed) + if warned > 0 { + tally += fmt.Sprintf(", %d warning(s)", warned) + } var statusLine string switch { + case m.finished && m.checkCancelled: + // Checks cut short by the interrupt are neither passed nor failed. + statusLine = fmt.Sprintf("Status: cancelled (%s)", tally) case anyInFlight || (!m.finished && len(m.checkCategories) > 0 && !allFinished): - statusLine = fmt.Sprintf("Status: in progress (%d/%d passed, 0 failed)", passed, total) + statusLine = fmt.Sprintf("Status: in progress (%d/%d passed, %d failed)", passed, total, failed) case m.finished && failed > 0: - statusLine = fmt.Sprintf("Status: ✘ failed (%d/%d passed, %d failed)", passed, total, failed) - case m.finished && failed == 0: - statusLine = fmt.Sprintf("Status: ✓ ok (%d/%d passed, 0 failed)", passed, total) + statusLine = fmt.Sprintf("Status: ✘ failed (%s)", tally) + case m.finished && warned > 0: + statusLine = fmt.Sprintf("Status: ✓ ok with warnings (%s)", tally) + case m.finished: + statusLine = fmt.Sprintf("Status: ✓ ok (%s)", tally) default: - statusLine = fmt.Sprintf("Status: in progress (%d/%d passed, 0 failed)", passed, total) + statusLine = fmt.Sprintf("Status: in progress (%d/%d passed, %d failed)", passed, total, failed) } b.WriteString("\n") @@ -1863,20 +1890,26 @@ func (m Model) viewCheck(now time.Time) string { } // checkTally returns (passed, failed, total) counts from all accumulated check rows. -func (m Model) checkTally() (passed, failed, total int) { +// checkTally counts finished rows by the same rule as the final event: only an +// error-severity miss is a failure, any other miss is a warning. +func (m Model) checkTally() (passed, failed, warned, total int) { for _, cat := range m.checkCategories { for _, row := range cat.checks { total++ - if row.finished { - if row.passed { - passed++ - } else { - failed++ - } + if !row.finished { + continue + } + switch { + case row.passed: + passed++ + case row.severity == "error": + failed++ + default: + warned++ } } } - return passed, failed, total + return passed, failed, warned, total } // ─── style helpers ──────────────────────────────────────────────────────────── diff --git a/src/clis/nvcf-cli/internal/selfhosted/progress/render_tty_test.go b/src/clis/nvcf-cli/internal/selfhosted/progress/render_tty_test.go index df09f04d18..f2beae2652 100644 --- a/src/clis/nvcf-cli/internal/selfhosted/progress/render_tty_test.go +++ b/src/clis/nvcf-cli/internal/selfhosted/progress/render_tty_test.go @@ -568,6 +568,32 @@ func TestCheckOneShotRenderer_FlushesFailedSummaryOnFinal(t *testing.T) { assert.Contains(t, out, "Status: ✘ failed (1/2 passed, 1 failed)") } +// An interrupted check ends on a cancelled final event, which must not read +// as a pass. +func TestCheckOneShotRenderer_CancelledIsNotOK(t *testing.T) { + t.Setenv("NO_COLOR", "1") + var buf bytes.Buffer + r := NewCheckOneShotRenderer(&buf, ModelOpts{Mode: ModeCheck, Output: &buf}) + ctx := context.Background() + require.NoError(t, r.Emit(ctx, CheckStarted{Category: "local-host-tools", ID: "kubectl-on-path"})) + require.NoError(t, r.Emit(ctx, CheckCompleted{ + Category: "local-host-tools", ID: "kubectl-on-path", Passed: true, Severity: "info", Message: "kubectl on PATH", + })) + require.NoError(t, r.Emit(ctx, Final{Cancelled: true})) + require.NoError(t, r.Close()) + + out := buf.String() + assert.Contains(t, out, "Status: cancelled") + assert.NotContains(t, out, "Status: ✓ ok") +} + +// A warning row is not marked as a failure: it does not fail the run. +func TestCheckGlyph_WarningIsNotAFailure(t *testing.T) { + assert.Equal(t, "[✓]", checkGlyph(checkRow{finished: true, passed: true})) + assert.Equal(t, "[✘]", checkGlyph(checkRow{finished: true, severity: "error"})) + assert.Equal(t, "[!]", checkGlyph(checkRow{finished: true, severity: "warning"})) +} + func TestCheckOneShotRenderer_UsesFinalTallyForStatus(t *testing.T) { t.Setenv("NO_COLOR", "1") @@ -881,3 +907,41 @@ func TestTTY_StatusWatchTickResetsBetweenSnapshots(t *testing.T) { assert.Equal(t, 1, sisCount, "rendered View should show SIS exactly once after 5 watch ticks, not %d times", sisCount) } + +// A finished run with a warning says so and accounts for it, rather than +// printing "ok (2/3 passed, 0 failed)" with one check unexplained. +func TestTTY_CheckCompleteWithWarning(t *testing.T) { + t.Setenv("NO_COLOR", "1") + m := NewModel(ModelOpts{Mode: ModeCheck, AsciiOnly: true, TotalChecks: 3, + NowFunc: func() time.Time { return ts("2026-04-29T03:45:12Z") }}) + m.SetSize(120, 40) + events := []Event{ + CheckStarted{Category: "local-host-tools", ID: "a"}, + CheckCompleted{Category: "local-host-tools", ID: "a", Passed: true, Severity: "info", Message: "a"}, + CheckStarted{Category: "local-host-tools", ID: "b"}, + CheckCompleted{Category: "local-host-tools", ID: "b", Passed: true, Severity: "info", Message: "b"}, + CheckStarted{Category: "registry-credentials", ID: "c"}, + CheckCompleted{Category: "registry-credentials", ID: "c", Passed: false, Severity: "warning", Message: "c"}, + Final{Success: true, TotalChecks: 3, PassedCount: 2, FailedCount: 0}, + } + var model tea.Model = m + for _, e := range events { + model, _ = model.(Model).applyCheckEvent(e) + } + assert.Contains(t, model.(Model).View(), "ok with warnings (2/3 passed, 0 failed, 1 warning(s))") +} + +// Bubbletea reads the terminal in raw mode, so Ctrl-C is a key press, not a +// signal. Each quit key must reach the run, or quitting closes the dashboard +// while the command keeps running behind it. +func TestModel_QuitKeysCallOnQuit(t *testing.T) { + for _, key := range []tea.KeyMsg{{Type: tea.KeyCtrlC}, {Type: tea.KeyEsc}, {Type: tea.KeyRunes, Runes: []rune("q")}} { + called := 0 + m := NewModel(ModelOpts{Mode: ModeCheck, OnQuit: func() { called++ }}) + _, cmd := m.Update(key) + require.NotNil(t, cmd, key.String()) + assert.Equal(t, 1, called, key.String()) + } + _, cmd := NewModel(ModelOpts{Mode: ModeCheck}).Update(tea.KeyMsg{Type: tea.KeyCtrlC}) + assert.NotNil(t, cmd, "without a hook the dashboard still quits") +} diff --git a/src/clis/nvcf-cli/internal/selfhosted/progress/select.go b/src/clis/nvcf-cli/internal/selfhosted/progress/select.go index f8e3ff017d..a69cc557db 100644 --- a/src/clis/nvcf-cli/internal/selfhosted/progress/select.go +++ b/src/clis/nvcf-cli/internal/selfhosted/progress/select.go @@ -105,6 +105,9 @@ type RenderOpts struct { // (7-phase teardown) and ModeUninstall (3-5 phase per-plane teardown). // Zero leaves the renderer's default in place. TotalPhases int + + // OnQuit is passed to the TTY renderer; see ModelOpts.OnQuit. + OnQuit func() } // SelectRenderer picks an EventSink based on opts and ambient state. The @@ -180,6 +183,7 @@ func SelectRenderer(stderr io.Writer, opts RenderOpts) (EventSink, RendererKind, ControlPlaneContext: opts.ControlPlaneContext, ComputePlaneContext: opts.ComputePlaneContext, NowFunc: func() time.Time { return time.Now().UTC() }, + OnQuit: opts.OnQuit, }), RendererTTYCompact, nil } @@ -194,6 +198,7 @@ func SelectRenderer(stderr io.Writer, opts RenderOpts) (EventSink, RendererKind, ControlPlaneContext: opts.ControlPlaneContext, ComputePlaneContext: opts.ComputePlaneContext, NowFunc: func() time.Time { return time.Now().UTC() }, + OnQuit: opts.OnQuit, }), RendererTTYFull, nil } diff --git a/src/clis/nvcf-cli/internal/selfhosted/progress/testdata/jsonl_check.golden b/src/clis/nvcf-cli/internal/selfhosted/progress/testdata/jsonl_check.golden index 597f7b68f9..5c668e09bc 100644 --- a/src/clis/nvcf-cli/internal/selfhosted/progress/testdata/jsonl_check.golden +++ b/src/clis/nvcf-cli/internal/selfhosted/progress/testdata/jsonl_check.golden @@ -5,5 +5,5 @@ {"event":"check_completed","ts":"2026-04-29T03:45:01Z","category":"local-host-tools","id":"helmfile-on-path","passed":true,"severity":"info","message":"helmfile 0.162.0 on PATH","detail":"0.162.0"} {"event":"check_started","ts":"2026-04-29T03:45:08Z","category":"pre-kubernetes-setup","id":"gateway-api"} {"event":"check_completed","ts":"2026-04-29T03:45:08Z","category":"pre-kubernetes-setup","id":"gateway-api","passed":false,"severity":"error","message":"Gateway API CRDs not installed","hintURL":"https://docs.nvidia.com/nvcf/self-hosted/gateway-api"} -{"event":"category_completed","ts":"2026-04-29T03:45:08Z","category":"pre-kubernetes-setup","passedCount":13,"failedCount":1,"durationSec":2.4} +{"event":"category_completed","ts":"2026-04-29T03:45:08Z","category":"pre-kubernetes-setup","passedCount":13,"failedCount":1,"warningCount":0,"durationSec":2.4} {"event":"final","ts":"2026-04-29T03:45:09Z","success":false,"durationSec":0,"droppedProgressEvents":0,"verdict":"failed","totalChecks":14,"passedCount":13,"failedCount":1} diff --git a/src/clis/nvcf-cli/internal/selfhosted/progress/testdata/plain_check.golden b/src/clis/nvcf-cli/internal/selfhosted/progress/testdata/plain_check.golden index 6312b923fe..2ddf8bc6ed 100644 --- a/src/clis/nvcf-cli/internal/selfhosted/progress/testdata/plain_check.golden +++ b/src/clis/nvcf-cli/internal/selfhosted/progress/testdata/plain_check.golden @@ -4,5 +4,5 @@ 2026-04-29T03:45:01Z [pre] check_completed local-host-tools/helmfile-on-path passed=true detail="0.162.0" 2026-04-29T03:45:08Z [pre] check_started pre-kubernetes-setup/gateway-api 2026-04-29T03:45:08Z [pre] check_completed pre-kubernetes-setup/gateway-api passed=false severity=error message="Gateway API CRDs not installed" hintURL="https://docs.nvidia.com/nvcf/self-hosted/gateway-api" -2026-04-29T03:45:08Z [pre] category_completed pre-kubernetes-setup passed=13 failed=1 duration=2.4s +2026-04-29T03:45:08Z [pre] category_completed pre-kubernetes-setup passed=13 failed=1 warnings=0 duration=2.4s 2026-04-29T03:45:09Z final verdict=failed totalChecks=14 passed=13 failed=1 diff --git a/src/clis/nvcf-cli/internal/selfhosted/pullsecret.go b/src/clis/nvcf-cli/internal/selfhosted/pullsecret.go index d01311e802..dff41a7094 100644 --- a/src/clis/nvcf-cli/internal/selfhosted/pullsecret.go +++ b/src/clis/nvcf-cli/internal/selfhosted/pullsecret.go @@ -32,15 +32,62 @@ import ( "k8s.io/client-go/kubernetes" ) +// deleteExactly pins a delete to the object that was inspected. The ownership +// guards read an object and then delete it by name, and in that window another +// actor can relabel it, or delete and recreate it under the same name. Without +// preconditions the delete lands on whatever holds the name by then; with them +// the apiserver rejects it as a conflict instead. +func deleteExactly(o metav1.Object) metav1.DeleteOptions { + // UID only. UID already pins identity, which is the whole goal: a + // delete-and-recreate under the same name changes it. ResourceVersion + // additionally pins the object's version, so any concurrent write turns + // the delete into a 409 that every sweep then swallows. That bites hardest + // on a Job, whose .status the Job controller mutates continuously. + uid := o.GetUID() + return metav1.DeleteOptions{ + Preconditions: &metav1.Preconditions{UID: &uid}, + } +} + const ( // Project-specific name so the auto-created / mirrored pull secret // can never collide with the conventional `nvcr-pull-secret` that // operators or the install flow may already manage in 'default'. // Pairs with isManagedByValidatorCLI label guard for defense-in-depth. + // + // The name is suffixed per role by validatorPullSecretRoleName: ModeSplit + // runs both validators concurrently, and the two kubecontexts can resolve + // to the same cluster. One shared Secret means the role that finishes first + // deletes it while the other Job is still pulling, which the kubelet + // reports as FailedToRetrieveImagePullSecret. validatorPullSecretName = "nvcf-preflight-pull-secret" validatorPullSecretScanTimeout = 10 * time.Second ) +// validatorPullSecretRoleName returns the managed pull-secret name for a role. +// A Secret cannot be co-owned, so each role needs its own rather than a shared +// object with a role label. +func validatorPullSecretRoleName(role string) string { + if role == "" { + return validatorPullSecretName + } + return validatorPullSecretName + "-" + role +} + +// validatorPullSecretRunName is the name this run mints under. It carries the +// run's unguessable suffix because the managed labels are three public +// constants: with a predictable name, anyone able to create a Secret in this +// namespace could pre-create it wearing those labels, pass the ownership check, +// and have the NGC credential written into an object they control. Same +// reasoning as the per-run RBAC names. +func validatorPullSecretRunName(role, runID string) string { + base := validatorPullSecretRoleName(role) + if runID == "" { + return base + } + return base + "-" + runID +} + // Mirrors the chain used by ensureLocalImagePullSecrets in cmd/self_hosted_up.go. var ngcAPIKeyEnvNames = []string{ "NGC_IMAGE_PULL_API_KEY", @@ -80,7 +127,8 @@ var validatorPullSecretSearchNamespaces = []string{ func resolveValidatorPullSecret( ctx context.Context, client kubernetes.Interface, - provided, image string, + provided, image, role, runID string, + preserve bool, ) (string, error) { if provided != "" { return provided, nil @@ -93,13 +141,13 @@ func resolveValidatorPullSecret( scanCtx, cancel := context.WithTimeout(ctx, validatorPullSecretScanTimeout) defer cancel() - if name, err := scanAndMirrorPullSecret(scanCtx, client, registry); err != nil { + if name, err := scanAndMirrorPullSecret(scanCtx, client, registry, role, runID, preserve); err != nil { return "", err } else if name != "" { return name, nil } - if name, err := autoCreatePullSecretFromEnv(scanCtx, client, registry); err != nil { + if name, err := autoCreatePullSecretFromEnv(scanCtx, client, registry, role, runID, preserve); err != nil { return "", err } else if name != "" { return name, nil @@ -131,7 +179,7 @@ func parseRegistryFromImage(image string) string { // registry. Mirrors the body into clusterValidatorNamespace when the match // lives elsewhere, so the Job can reference the secret without cross-namespace // lookups. -func scanAndMirrorPullSecret(ctx context.Context, client kubernetes.Interface, registry string) (string, error) { +func scanAndMirrorPullSecret(ctx context.Context, client kubernetes.Interface, registry, role, runID string, preserve bool) (string, error) { for _, ns := range validatorPullSecretSearchNamespaces { // Filter client-side rather than via FieldSelector: server-side // type= selector on Secrets is only honored from k8s 1.27 onward. @@ -148,6 +196,17 @@ func scanAndMirrorPullSecret(ctx context.Context, client kubernetes.Interface, r continue } if s.Namespace == clusterValidatorNamespace { + // Adopt only an operator-supplied Secret (no managed labels, so + // no sweep touches it). Never one another run minted: those + // are per-run now, so adopting one reuses a possibly stale + // NGC key (a run with a bad key keeps its Secret while its pod + // may still be pulling), can outlive this run, and can be + // deleted by its own run's sweep mid-pull, which the kubelet + // reports as FailedToRetrieveImagePullSecret. A --no-cleanup + // Secret would otherwise be adopted by every later run. + if isAnyValidatorSecret(&s) { + continue + } return s.Name, nil } // Mirror under the validator's well-known name rather than the @@ -156,11 +215,11 @@ func scanAndMirrorPullSecret(ctx context.Context, client kubernetes.Interface, r // the destination namespace, and writeDockerConfigSecret's // delete-and-recreate path would otherwise destroy that // secret on type mismatch. - if err := writeDockerConfigSecret(ctx, client, clusterValidatorNamespace, validatorPullSecretName, cfg); err != nil { + if err := writeDockerConfigSecret(ctx, client, clusterValidatorNamespace, validatorPullSecretRunName(role, runID), role, runID, cfg, preserve); err != nil { return "", fmt.Errorf("mirror pull secret %s/%s to %s/%s: %w", - s.Namespace, s.Name, clusterValidatorNamespace, validatorPullSecretName, err) + s.Namespace, s.Name, clusterValidatorNamespace, validatorPullSecretRunName(role, runID), err) } - return validatorPullSecretName, nil + return validatorPullSecretRunName(role, runID), nil } } return "", nil @@ -177,20 +236,29 @@ func dockerConfigHasRegistry(cfg []byte, registry string) bool { return ok } -func autoCreatePullSecretFromEnv(ctx context.Context, client kubernetes.Interface, registry string) (string, error) { +func autoCreatePullSecretFromEnv(ctx context.Context, client kubernetes.Interface, registry, role, runID string, preserve bool) (string, error) { apiKey := firstNonEmptyEnv(ngcAPIKeyEnvNames...) if apiKey == "" { return "", nil } + // Only ever hand the NGC key to NGC. Without this an operator who mirrors + // the validator image to ghcr.io or a corporate Harbor and still exports + // NGC_API_KEY gets it written as that registry's password, and the kubelet + // then sends the live key to a third party as HTTP Basic auth, where it + // lands in their access logs. The local probe already guards this the same + // way; the Secret path did not. + if !isNGCRegistry(registry) { + return "", nil + } cfg, err := buildDockerConfigJSON(registry, "$oauthtoken", apiKey) if err != nil { return "", fmt.Errorf("encode dockerconfigjson for %s: %w", registry, err) } - if err := writeDockerConfigSecret(ctx, client, clusterValidatorNamespace, validatorPullSecretName, cfg); err != nil { + if err := writeDockerConfigSecret(ctx, client, clusterValidatorNamespace, validatorPullSecretRunName(role, runID), role, runID, cfg, preserve); err != nil { return "", fmt.Errorf("auto-create pull secret %s/%s: %w", - clusterValidatorNamespace, validatorPullSecretName, err) + clusterValidatorNamespace, validatorPullSecretRunName(role, runID), err) } - return validatorPullSecretName, nil + return validatorPullSecretRunName(role, runID), nil } // Mirrors cmd/self_hosted_up.go's firstNonEmptyEnv; kept local to avoid an @@ -218,96 +286,65 @@ func buildDockerConfigJSON(registry, username, password string) ([]byte, error) }) } -// Mirrors cmd/self_hosted_up.go's ensureDockerConfigSecret. Attaches the -// CLI's managed-by labels so a future cleanup path can identify resources -// to remove. -func writeDockerConfigSecret(ctx context.Context, client kubernetes.Interface, namespace, name string, dockerConfig []byte) error { - labels := clusterValidatorLabels() - secrets := client.CoreV1().Secrets(namespace) +// writeDockerConfigSecret creates the pull Secret this run will reference. +// +// Create-only. The name carries this run's unguessable suffix, so nothing this +// CLI created can already hold it and any collision is another object. +// Adopting one would write the NGC credential into something we do not own, +// which label-based ownership cannot prevent: the managed labels are three +// public constants anyone can copy onto a Secret they pre-create under a +// predictable name. +func writeDockerConfigSecret( + ctx context.Context, client kubernetes.Interface, namespace, name, role, runID string, dockerConfig []byte, preserve bool, +) error { secret := &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: name, Namespace: namespace, - Labels: labels, + Labels: clusterValidatorRunLabels(role, runID, preserve), }, Type: corev1.SecretTypeDockerConfigJson, Data: map[string][]byte{corev1.DockerConfigJsonKey: dockerConfig}, } - current, err := secrets.Get(ctx, name, metav1.GetOptions{}) - switch { - case err == nil && current.Type == corev1.SecretTypeDockerConfigJson: - if current.Data == nil { - current.Data = map[string][]byte{} - } - current.Data[corev1.DockerConfigJsonKey] = dockerConfig - if current.Labels == nil { - current.Labels = map[string]string{} - } - for k, v := range labels { - current.Labels[k] = v - } - if _, err := secrets.Update(ctx, current, metav1.UpdateOptions{}); err != nil { - return fmt.Errorf("update: %w", err) - } - return nil - case err == nil: - // Secret.Type is immutable; can't Update across a type change. - // Guard the delete: only replace secrets we previously managed. - // Without this, an operator-owned secret with a colliding name - // (e.g. an Opaque secret in 'default' sharing the validator's - // pull-secret name) would be silently destroyed. - if !isManagedByValidatorCLI(current) { + if _, err := client.CoreV1().Secrets(namespace).Create(ctx, secret, metav1.CreateOptions{}); err != nil { + if apierrors.IsAlreadyExists(err) { return fmt.Errorf( - "refusing to replace %s/%s (type=%s) which is not managed by nvcf-cli; "+ - "pass --cluster-validator-pull-secret to choose an explicit secret name", - namespace, name, current.Type) - } - if err := secrets.Delete(ctx, name, metav1.DeleteOptions{}); err != nil && !apierrors.IsNotFound(err) { - return fmt.Errorf("delete incompatible secret type %s: %w", current.Type, err) - } - case !apierrors.IsNotFound(err): - return fmt.Errorf("get: %w", err) - } - if _, err := secrets.Create(ctx, secret, metav1.CreateOptions{}); err != nil { - if !apierrors.IsAlreadyExists(err) { - return fmt.Errorf("create: %w", err) - } - // Lost a delete->create race: another actor recreated the secret - // in the narrow window between our Delete and Create. Refetch and - // overwrite with our content so the caller still gets the - // credentials it asked for. - current, getErr := secrets.Get(ctx, name, metav1.GetOptions{}) - if getErr != nil { - return fmt.Errorf("get after create race: %w", getErr) - } - if current.Type != corev1.SecretTypeDockerConfigJson { - return fmt.Errorf("create race left secret %s/%s with type %s, want %s", - namespace, name, current.Type, corev1.SecretTypeDockerConfigJson) - } - if current.Data == nil { - current.Data = map[string][]byte{} - } - current.Data[corev1.DockerConfigJsonKey] = dockerConfig - if current.Labels == nil { - current.Labels = map[string]string{} - } - for k, v := range labels { - current.Labels[k] = v - } - if _, err := secrets.Update(ctx, current, metav1.UpdateOptions{}); err != nil { - return fmt.Errorf("update after create race: %w", err) + "refusing to overwrite existing secret %s/%s: this run generated that name, so "+ + "another object already holds it; pass --cluster-validator-pull-secret to "+ + "choose an explicit secret name", + namespace, name) } + return fmt.Errorf("create: %w", err) } return nil } -// isManagedByValidatorCLI reports whether the secret carries the labels -// writeDockerConfigSecret stamps on every secret it creates. Used to -// gate the delete-then-recreate branch so we never destroy an operator- -// or chart-owned secret that happens to share a name with one of ours. +// isManagedByValidatorCLI reports whether a Secret carries the labels this CLI +// stamps on the ones it creates. Necessary but not sufficient for ownership: +// the labels are three public constants anyone can copy onto a Secret they +// pre-create, which is why the generated names are run-scoped and the write +// path is create-only rather than adopt-or-update. func isManagedByValidatorCLI(s *corev1.Secret) bool { + return hasValidatorManagedLabels(s.Labels) +} + +// isAnyValidatorSecret reports whether a Secret was minted by any version of +// this CLI's validator, including released ones that label with +// managed-by=nvcf-cli and use the bare nvcf-preflight-pull-secret name. Such a +// Secret belongs to another run: its CLI deletes it on exit, possibly while +// this run's pod is still pulling, and a stale one would be reused forever. +func isAnyValidatorSecret(s *corev1.Secret) bool { + return hasValidatorManagedLabels(s.Labels) || + s.Labels["app.kubernetes.io/name"] == clusterValidatorAppLabel || + strings.HasPrefix(s.Name, validatorPullSecretName) +} + +// hasValidatorManagedLabels reports whether an object carries the labels this +// CLI stamps on everything it creates. It is the ownership test for every +// resource the validator writes to or deletes by name, not just Secrets. +func hasValidatorManagedLabels(labels map[string]string) bool { for k, v := range clusterValidatorLabels() { - if s.Labels[k] != v { + if labels[k] != v { return false } } diff --git a/src/clis/nvcf-cli/internal/selfhosted/pullsecret_test.go b/src/clis/nvcf-cli/internal/selfhosted/pullsecret_test.go index 7a77df310c..f6cbc29653 100644 --- a/src/clis/nvcf-cli/internal/selfhosted/pullsecret_test.go +++ b/src/clis/nvcf-cli/internal/selfhosted/pullsecret_test.go @@ -27,6 +27,7 @@ import ( "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes/fake" ) @@ -130,94 +131,6 @@ func TestFirstNonEmptyEnv(t *testing.T) { }) } -func TestWriteDockerConfigSecret_Create(t *testing.T) { - client := fake.NewSimpleClientset() - cfg := dockerConfigBlob(t, "private.registry.test", "$oauthtoken", "key") - - require.NoError(t, writeDockerConfigSecret(context.Background(), client, "default", "nvcr-pull-secret", cfg)) - - got, err := client.CoreV1().Secrets("default").Get(context.Background(), "nvcr-pull-secret", metav1.GetOptions{}) - require.NoError(t, err) - assert.Equal(t, corev1.SecretTypeDockerConfigJson, got.Type) - assert.Equal(t, cfg, got.Data[corev1.DockerConfigJsonKey]) - assert.Equal(t, clusterValidatorAppLabel, got.Labels["app.kubernetes.io/name"], - "label must be attached so the secret is identifiable as CLI-managed") -} - -func TestWriteDockerConfigSecret_Update(t *testing.T) { - old := dockerConfigBlob(t, "private.registry.test", "$oauthtoken", "stale-key") - fresh := dockerConfigBlob(t, "private.registry.test", "$oauthtoken", "rotated-key") - - client := fake.NewSimpleClientset(&corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: "nvcr-pull-secret", - Namespace: "default", - Labels: map[string]string{"existing-label": "preserved"}, - }, - Type: corev1.SecretTypeDockerConfigJson, - Data: map[string][]byte{corev1.DockerConfigJsonKey: old}, - }) - - require.NoError(t, writeDockerConfigSecret(context.Background(), client, "default", "nvcr-pull-secret", fresh)) - - got, _ := client.CoreV1().Secrets("default").Get(context.Background(), "nvcr-pull-secret", metav1.GetOptions{}) - assert.Equal(t, fresh, got.Data[corev1.DockerConfigJsonKey], "data must be updated to the fresh body") - assert.Equal(t, "preserved", got.Labels["existing-label"], "pre-existing labels must be preserved on update") - assert.Equal(t, clusterValidatorAppLabel, got.Labels["app.kubernetes.io/name"], "CLI labels must be added on update") -} - -// Regression guard for the immutable-Type silent-failure path: a pre-existing -// secret with the wrong Type (but our labels) must be replaced (delete + -// create), not Updated. -func TestWriteDockerConfigSecret_TypeMismatchReplaces(t *testing.T) { - fresh := dockerConfigBlob(t, "private.registry.test", "$oauthtoken", "key") - client := fake.NewSimpleClientset(&corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: "nvcr-pull-secret", - Namespace: "default", - Labels: clusterValidatorLabels(), - }, - Type: corev1.SecretTypeOpaque, // wrong type — Update would 422 - Data: map[string][]byte{"some-key": []byte("some-value")}, - }) - - require.NoError(t, writeDockerConfigSecret(context.Background(), client, "default", "nvcr-pull-secret", fresh)) - - got, err := client.CoreV1().Secrets("default").Get(context.Background(), "nvcr-pull-secret", metav1.GetOptions{}) - require.NoError(t, err) - assert.Equal(t, corev1.SecretTypeDockerConfigJson, got.Type, - "secret must be recreated with the correct dockerconfigjson type") - assert.Equal(t, fresh, got.Data[corev1.DockerConfigJsonKey]) - assert.NotContains(t, got.Data, "some-key", "old Opaque payload must not survive the recreate") -} - -// Operator-owned secret with the same name but no CLI labels must NOT be -// destroyed. The replace path is the only place operator data is at risk -// (immutable Type forces delete+create), so the label guard lives there. -func TestWriteDockerConfigSecret_TypeMismatchRefusesUnlabeledSecret(t *testing.T) { - fresh := dockerConfigBlob(t, "private.registry.test", "$oauthtoken", "key") - operatorPayload := []byte("operator-data-do-not-destroy") - client := fake.NewSimpleClientset(&corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: "nvcr-pull-secret", - Namespace: "default", - // No CLI labels: an operator- or chart-owned secret. - }, - Type: corev1.SecretTypeOpaque, - Data: map[string][]byte{"operator-payload": operatorPayload}, - }) - - err := writeDockerConfigSecret(context.Background(), client, "default", "nvcr-pull-secret", fresh) - require.Error(t, err, "must refuse to destroy a non-CLI-managed secret") - assert.Contains(t, err.Error(), "not managed by nvcf-cli") - - // Original payload must still be intact. - got, getErr := client.CoreV1().Secrets("default").Get(context.Background(), "nvcr-pull-secret", metav1.GetOptions{}) - require.NoError(t, getErr) - assert.Equal(t, corev1.SecretTypeOpaque, got.Type, "type must be unchanged") - assert.Equal(t, operatorPayload, got.Data["operator-payload"], "operator data must not be touched") -} - func TestScanAndMirrorPullSecret_FoundInDefault(t *testing.T) { cfg := dockerConfigBlob(t, "private.registry.test", "$oauthtoken", "key") client := fake.NewSimpleClientset(&corev1.Secret{ @@ -226,7 +139,7 @@ func TestScanAndMirrorPullSecret_FoundInDefault(t *testing.T) { Data: map[string][]byte{corev1.DockerConfigJsonKey: cfg}, }) - name, err := scanAndMirrorPullSecret(context.Background(), client, "private.registry.test") + name, err := scanAndMirrorPullSecret(context.Background(), client, "private.registry.test", clusterValidatorControlPlaneRole, "runid", false) require.NoError(t, err) assert.Equal(t, "operator-secret", name) @@ -247,12 +160,12 @@ func TestScanAndMirrorPullSecret_FoundInNvcfMirroredToDefault(t *testing.T) { Data: map[string][]byte{corev1.DockerConfigJsonKey: cfg}, }) - name, err := scanAndMirrorPullSecret(context.Background(), client, "private.registry.test") + name, err := scanAndMirrorPullSecret(context.Background(), client, "private.registry.test", clusterValidatorControlPlaneRole, "runid", false) require.NoError(t, err) - assert.Equal(t, validatorPullSecretName, name) + assert.Equal(t, validatorPullSecretRunName(clusterValidatorControlPlaneRole, "runid"), name) - mirrored, err := client.CoreV1().Secrets("default").Get(context.Background(), validatorPullSecretName, metav1.GetOptions{}) - require.NoError(t, err, "mirror must be created under validatorPullSecretName") + mirrored, err := client.CoreV1().Secrets("default").Get(context.Background(), validatorPullSecretRunName(clusterValidatorControlPlaneRole, "runid"), metav1.GetOptions{}) + require.NoError(t, err, "mirror must be created under the role-scoped managed name") assert.Equal(t, cfg, mirrored.Data[corev1.DockerConfigJsonKey]) // The source secret must remain untouched in the source namespace. @@ -269,14 +182,14 @@ func TestScanAndMirrorPullSecret_RegistryMismatch(t *testing.T) { Data: map[string][]byte{corev1.DockerConfigJsonKey: cfg}, }) - name, err := scanAndMirrorPullSecret(context.Background(), client, "private.registry.test") + name, err := scanAndMirrorPullSecret(context.Background(), client, "private.registry.test", clusterValidatorControlPlaneRole, "runid", false) require.NoError(t, err) assert.Equal(t, "", name, "secret for a different registry must not match") } func TestScanAndMirrorPullSecret_NoSecrets(t *testing.T) { client := fake.NewSimpleClientset() - name, err := scanAndMirrorPullSecret(context.Background(), client, "private.registry.test") + name, err := scanAndMirrorPullSecret(context.Background(), client, "private.registry.test", clusterValidatorControlPlaneRole, "runid", false) require.NoError(t, err) assert.Equal(t, "", name) } @@ -296,7 +209,7 @@ func TestScanAndMirrorPullSecret_PrefersDefaultNamespace(t *testing.T) { }, ) - name, err := scanAndMirrorPullSecret(context.Background(), client, "private.registry.test") + name, err := scanAndMirrorPullSecret(context.Background(), client, "private.registry.test", clusterValidatorControlPlaneRole, "runid", false) require.NoError(t, err) assert.Equal(t, "in-default", name) } @@ -308,7 +221,7 @@ func TestAutoCreatePullSecretFromEnv_NoEnv(t *testing.T) { } client := fake.NewSimpleClientset() - got, err := autoCreatePullSecretFromEnv(context.Background(), client, "private.registry.test") + got, err := autoCreatePullSecretFromEnv(context.Background(), client, "private.registry.test", clusterValidatorControlPlaneRole, "runid", false) require.NoError(t, err) assert.Equal(t, "", got, "no env var set means no secret minted") @@ -323,19 +236,19 @@ func TestAutoCreatePullSecretFromEnv_KeyPresent(t *testing.T) { t.Setenv("NGC_API_KEY", "nvapi-test-123") client := fake.NewSimpleClientset() - got, err := autoCreatePullSecretFromEnv(context.Background(), client, "private.registry.test") + got, err := autoCreatePullSecretFromEnv(context.Background(), client, "nvcr.io", clusterValidatorControlPlaneRole, "runid", false) require.NoError(t, err) - assert.Equal(t, validatorPullSecretName, got) + assert.Equal(t, validatorPullSecretRunName(clusterValidatorControlPlaneRole, "runid"), got) - s, err := client.CoreV1().Secrets("default").Get(context.Background(), validatorPullSecretName, metav1.GetOptions{}) + s, err := client.CoreV1().Secrets("default").Get(context.Background(), validatorPullSecretRunName(clusterValidatorControlPlaneRole, "runid"), metav1.GetOptions{}) require.NoError(t, err) - assert.True(t, dockerConfigHasRegistry(s.Data[corev1.DockerConfigJsonKey], "private.registry.test"), + assert.True(t, dockerConfigHasRegistry(s.Data[corev1.DockerConfigJsonKey], "nvcr.io"), "minted secret must contain auth for the validator image's registry") } func TestResolveValidatorPullSecret_FlagOverride(t *testing.T) { client := fake.NewSimpleClientset() - got, err := resolveValidatorPullSecret(context.Background(), client, "custom-secret", "private.registry.test/nvidia/nvcf-byoc/cluster-validator:rc26") + got, err := resolveValidatorPullSecret(context.Background(), client, "custom-secret", "private.registry.test/nvidia/nvcf-byoc/cluster-validator:rc26", clusterValidatorControlPlaneRole, "runid", false) require.NoError(t, err) assert.Equal(t, "custom-secret", got, "explicit override always wins") @@ -355,14 +268,14 @@ func TestResolveValidatorPullSecret_ScanWins(t *testing.T) { Data: map[string][]byte{corev1.DockerConfigJsonKey: cfg}, }) - got, err := resolveValidatorPullSecret(context.Background(), client, "", "private.registry.test/nvidia/nvcf-byoc/cluster-validator:rc26") + got, err := resolveValidatorPullSecret(context.Background(), client, "", "private.registry.test/nvidia/nvcf-byoc/cluster-validator:rc26", clusterValidatorControlPlaneRole, "runid", false) require.NoError(t, err) // Mirror destination is the validator's well-known name, not the // source secret's name (avoids same-name collisions with operator // or chart-owned secrets in the destination namespace). - assert.Equal(t, validatorPullSecretName, got) + assert.Equal(t, validatorPullSecretRunName(clusterValidatorControlPlaneRole, "runid"), got) - mirrored, err := client.CoreV1().Secrets("default").Get(context.Background(), validatorPullSecretName, metav1.GetOptions{}) + mirrored, err := client.CoreV1().Secrets("default").Get(context.Background(), validatorPullSecretRunName(clusterValidatorControlPlaneRole, "runid"), metav1.GetOptions{}) require.NoError(t, err) assert.True(t, dockerConfigHasRegistry(mirrored.Data[corev1.DockerConfigJsonKey], "private.registry.test")) } @@ -374,13 +287,28 @@ func TestResolveValidatorPullSecret_EnvFallback(t *testing.T) { t.Setenv("NVCF_NGC_API_KEY", "nvapi-fallback") client := fake.NewSimpleClientset() - got, err := resolveValidatorPullSecret(context.Background(), client, "", "private.registry.test/nvidia/nvcf-byoc/cluster-validator:rc26") + got, err := resolveValidatorPullSecret(context.Background(), client, "", "nvcr.io/nvidia/nvcf-byoc/cluster-validator:rc26", clusterValidatorControlPlaneRole, "runid", false) require.NoError(t, err) - assert.Equal(t, validatorPullSecretName, got) + assert.Equal(t, validatorPullSecretRunName(clusterValidatorControlPlaneRole, "runid"), got) - s, err := client.CoreV1().Secrets("default").Get(context.Background(), validatorPullSecretName, metav1.GetOptions{}) + s, err := client.CoreV1().Secrets("default").Get(context.Background(), validatorPullSecretRunName(clusterValidatorControlPlaneRole, "runid"), metav1.GetOptions{}) require.NoError(t, err) - assert.True(t, dockerConfigHasRegistry(s.Data[corev1.DockerConfigJsonKey], "private.registry.test")) + assert.True(t, dockerConfigHasRegistry(s.Data[corev1.DockerConfigJsonKey], "nvcr.io")) +} + +// A mirrored validator image must not receive the NGC key via the env +// fallback: the kubelet would send it to that registry as Basic auth. +func TestResolveValidatorPullSecret_EnvFallbackRefusesMirroredRegistry(t *testing.T) { + for _, name := range ngcAPIKeyEnvNames { + t.Setenv(name, "") + } + t.Setenv("NVCF_NGC_API_KEY", "nvapi-fallback") + client := fake.NewSimpleClientset() + + got, err := resolveValidatorPullSecret(context.Background(), client, "", + "private.registry.test/nvidia/nvcf-byoc/cluster-validator:rc26", clusterValidatorControlPlaneRole, "runid", false) + require.NoError(t, err) + assert.Empty(t, got, "no secret may be minted for a non-NGC registry") } func TestResolveValidatorPullSecret_AllEmpty(t *testing.T) { @@ -389,7 +317,7 @@ func TestResolveValidatorPullSecret_AllEmpty(t *testing.T) { } client := fake.NewSimpleClientset() - got, err := resolveValidatorPullSecret(context.Background(), client, "", "private.registry.test/nvidia/nvcf-byoc/cluster-validator:rc26") + got, err := resolveValidatorPullSecret(context.Background(), client, "", "private.registry.test/nvidia/nvcf-byoc/cluster-validator:rc26", clusterValidatorControlPlaneRole, "runid", false) require.NoError(t, err) assert.Equal(t, "", got, "no override, no scan match, no env var -> empty so kubelet surfaces ImagePullBackOff") } @@ -400,10 +328,247 @@ func TestResolveValidatorPullSecret_UnparsableImage(t *testing.T) { } client := fake.NewSimpleClientset() - got, err := resolveValidatorPullSecret(context.Background(), client, "", "bareimage") + got, err := resolveValidatorPullSecret(context.Background(), client, "", "bareimage", clusterValidatorControlPlaneRole, "runid", false) require.NoError(t, err) assert.Equal(t, "", got, "no registry hostname means no scan target and no auto-create") list, _ := client.CoreV1().Secrets("default").List(context.Background(), metav1.ListOptions{}) assert.Empty(t, list.Items, "no secret must be created when registry cannot be derived") } + +// ModeSplit runs both validators concurrently against contexts that can resolve +// to the same cluster. A Secret cannot be co-owned, so each role needs its own: +// with a shared name the role that finishes first deletes it while the other +// Job is still pulling, which the kubelet reports as +// FailedToRetrieveImagePullSecret. +// +// State-assertable now that the sweep lists and deletes individually; the fake +// clientset implements DeleteCollection as a no-op. +func TestManagedPullSecret_IsScopedPerRole(t *testing.T) { + ctx := context.Background() + cpName := validatorPullSecretRunName(clusterValidatorControlPlaneRole, "runid") + gpuName := validatorPullSecretRoleName("compute-plane") + require.NotEqual(t, cpName, gpuName, "each role needs its own managed pull secret") + + cfg := dockerConfigBlob(t, "private.registry.test", "user", "pass") + client := fake.NewSimpleClientset() + require.NoError(t, writeDockerConfigSecret(ctx, client, clusterValidatorNamespace, + cpName, clusterValidatorControlPlaneRole, "run1", cfg, false)) + require.NoError(t, writeDockerConfigSecret(ctx, client, clusterValidatorNamespace, + gpuName, "compute-plane", "run1", cfg, false)) + // Our labels, but not a name we generate: must survive. + require.NoError(t, writeDockerConfigSecret(ctx, client, clusterValidatorNamespace, + "operator-owned-secret", clusterValidatorControlPlaneRole, "run1", cfg, false)) + + sweepManagedPullSecrets(ctx, client, clusterValidatorControlPlaneRole, "runid") + + secrets := client.CoreV1().Secrets(clusterValidatorNamespace) + _, err := secrets.Get(ctx, cpName, metav1.GetOptions{}) + assert.True(t, apierrors.IsNotFound(err), "this role's managed secret must be swept") + + _, err = secrets.Get(ctx, gpuName, metav1.GetOptions{}) + assert.NoError(t, err, "the other role's secret must survive") + + _, err = secrets.Get(ctx, "operator-owned-secret", metav1.GetOptions{}) + assert.NoError(t, err, "matching labels alone must not authorize deleting someone else's secret") +} + +// The managed labels a created secret carries must satisfy the sweep selector +// for that role, and must not satisfy another role's. +func TestManagedPullSecret_LabelsMatchOnlyItsOwnRole(t *testing.T) { + ctx := context.Background() + client := fake.NewSimpleClientset() + cfg := dockerConfigBlob(t, "nvcr.io", "$oauthtoken", "key") + name := validatorPullSecretRunName(clusterValidatorControlPlaneRole, "runid") + require.NoError(t, writeDockerConfigSecret(ctx, client, clusterValidatorNamespace, + name, clusterValidatorControlPlaneRole, "run1", cfg, false)) + + own, err := client.CoreV1().Secrets(clusterValidatorNamespace).List(ctx, + metav1.ListOptions{LabelSelector: validatorRoleSelector(clusterValidatorControlPlaneRole)}) + require.NoError(t, err) + assert.Len(t, own.Items, 1, "the secret must match its own role's selector") + + other, err := client.CoreV1().Secrets(clusterValidatorNamespace).List(ctx, + metav1.ListOptions{LabelSelector: validatorRoleSelector(clusterValidatorComputePlaneRole)}) + require.NoError(t, err) + assert.Empty(t, other.Items, "it must not match the other role's selector") +} + +func TestAutoCreatePullSecretFromEnv_RefusesNonNGCRegistry(t *testing.T) { + t.Setenv("NGC_API_KEY", "nvapi-secret-value") + client := fake.NewSimpleClientset() + + name, err := autoCreatePullSecretFromEnv(context.Background(), client, + "ghcr.io", clusterValidatorControlPlaneRole, "runid", false) + require.NoError(t, err) + assert.Empty(t, name, "no secret may be minted for a non-NGC registry") + + secrets, err := client.CoreV1().Secrets(clusterValidatorNamespace).List( + context.Background(), metav1.ListOptions{}) + require.NoError(t, err) + assert.Empty(t, secrets.Items, "the NGC key must not reach a third-party registry") +} + +// In ModeSplit both kubecontexts can resolve to one cluster. Adopting the +// other role's managed Secret means that role's sweep deletes it while this +// role's pod is still pulling. +func TestScanAndMirrorPullSecret_DoesNotAdoptTheOtherRolesSecret(t *testing.T) { + cfg := dockerConfigBlob(t, "nvcr.io", "$oauthtoken", "key") + otherRole := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: validatorPullSecretRunName(clusterValidatorControlPlaneRole, "runid"), + Namespace: clusterValidatorNamespace, + Labels: clusterValidatorRoleLabels(clusterValidatorControlPlaneRole), + }, + Type: corev1.SecretTypeDockerConfigJson, + Data: map[string][]byte{corev1.DockerConfigJsonKey: cfg}, + } + client := fake.NewSimpleClientset(otherRole) + + got, err := scanAndMirrorPullSecret(context.Background(), client, "nvcr.io", + clusterValidatorComputePlaneRole, "runid", false) + require.NoError(t, err) + assert.NotEqual(t, otherRole.Name, got, + "the compute role must not adopt the control-plane role's managed Secret") +} + +// An operator-supplied Secret carries no managed labels, so no sweep touches +// it and adopting it is safe. +func TestScanAndMirrorPullSecret_AdoptsOperatorSuppliedSecret(t *testing.T) { + cfg := dockerConfigBlob(t, "nvcr.io", "$oauthtoken", "key") + operator := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{Name: "operator-pull", Namespace: clusterValidatorNamespace}, + Type: corev1.SecretTypeDockerConfigJson, + Data: map[string][]byte{corev1.DockerConfigJsonKey: cfg}, + } + client := fake.NewSimpleClientset(operator) + + got, err := scanAndMirrorPullSecret(context.Background(), client, "nvcr.io", + clusterValidatorComputePlaneRole, "runid", false) + require.NoError(t, err) + assert.Equal(t, "operator-pull", got) +} + +// No Secret another run minted is adopted, even one of the same role: it may +// hold a stale key, may be deleted by its own run's sweep mid-pull, and a +// --no-cleanup one would otherwise be adopted by every later run. +func TestScanAndMirrorPullSecret_NeverAdoptsAnotherRunsSecret(t *testing.T) { + cfg := dockerConfigBlob(t, "nvcr.io", "$oauthtoken", "key") + secret := func(runID string, preserve bool) *corev1.Secret { + return &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: validatorPullSecretRunName(clusterValidatorComputePlaneRole, runID), + Namespace: clusterValidatorNamespace, + Labels: clusterValidatorRunLabels(clusterValidatorComputePlaneRole, runID, preserve), + }, + Type: corev1.SecretTypeDockerConfigJson, + Data: map[string][]byte{corev1.DockerConfigJsonKey: cfg}, + } + } + client := fake.NewSimpleClientset(secret("earlier", false), secret("kept", true)) + + got, err := scanAndMirrorPullSecret(context.Background(), client, "nvcr.io", + clusterValidatorComputePlaneRole, "runid", false) + require.NoError(t, err) + assert.Empty(t, got, "a managed Secret from another run must not be adopted") +} + +// writeDockerConfigSecret is create-only. The name carries this run's +// unguessable suffix, so nothing this CLI created can already hold it and any +// collision is another object. Adopting one would write the NGC credential +// into something we do not own, which label-based ownership cannot prevent: +// the managed labels are three public constants anyone can copy. +func TestWriteDockerConfigSecret_CreatesUnderTheRunScopedName(t *testing.T) { + cfg := dockerConfigBlob(t, "nvcr.io", "$oauthtoken", "key") + client := fake.NewSimpleClientset() + name := validatorPullSecretRunName(clusterValidatorControlPlaneRole, "runid") + + require.NoError(t, writeDockerConfigSecret(context.Background(), client, + clusterValidatorNamespace, name, clusterValidatorControlPlaneRole, "run1", cfg, false)) + + s, err := client.CoreV1().Secrets(clusterValidatorNamespace).Get( + context.Background(), name, metav1.GetOptions{}) + require.NoError(t, err) + assert.Equal(t, corev1.SecretTypeDockerConfigJson, s.Type) + assert.True(t, hasValidatorManagedLabels(s.Labels)) +} + +func TestWriteDockerConfigSecret_RefusesAnyCollision(t *testing.T) { + cfg := dockerConfigBlob(t, "nvcr.io", "$oauthtoken", "key") + name := validatorPullSecretRunName(clusterValidatorControlPlaneRole, "runid") + + // Even wearing our managed labels: labels are forgeable, the name is not. + squatter := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, Namespace: clusterValidatorNamespace, + Labels: clusterValidatorLabels(), + }, + Type: corev1.SecretTypeOpaque, + Data: map[string][]byte{"planted": []byte("x")}, + } + client := fake.NewSimpleClientset(squatter) + + err := writeDockerConfigSecret(context.Background(), client, + clusterValidatorNamespace, name, clusterValidatorControlPlaneRole, "run1", cfg, false) + require.Error(t, err, "a collision on an unguessable name must never be adopted") + assert.Contains(t, err.Error(), "refusing to overwrite") + + got, gerr := client.CoreV1().Secrets(clusterValidatorNamespace).Get( + context.Background(), name, metav1.GetOptions{}) + require.NoError(t, gerr) + assert.Equal(t, corev1.SecretTypeOpaque, got.Type, "the other object must be untouched") + assert.Contains(t, got.Data, "planted") + assert.NotContains(t, got.Data, corev1.DockerConfigJsonKey, + "the NGC credential must not be written into an object we do not own") +} + +// The unguessable suffix is the actual control. Labels cannot carry it: they +// are three public constants, so a predictable name lets anyone able to create +// a Secret here pre-create it wearing those labels and receive the NGC +// credential. Create-only is only safe because the name cannot be guessed. +func TestValidatorPullSecretRunName_CarriesTheRunID(t *testing.T) { + name := validatorPullSecretRunName(clusterValidatorControlPlaneRole, "a1b2c3d4e5") + assert.Contains(t, name, "a1b2c3d4e5", + "a predictable secret name can be pre-created by someone else") + assert.NotEqual(t, validatorPullSecretRoleName(clusterValidatorControlPlaneRole), name) + + // Two runs never collide, so one run's sweep cannot take out another's. + other := validatorPullSecretRunName(clusterValidatorControlPlaneRole, "f6g7h8i9j0") + assert.NotEqual(t, name, other) +} + +// The mint path must use the run-scoped name, not the role name. +func TestAutoCreatePullSecretFromEnv_MintsUnderTheRunScopedName(t *testing.T) { + t.Setenv("NGC_API_KEY", "nvapi-secret-value") + client := fake.NewSimpleClientset() + + got, err := autoCreatePullSecretFromEnv(context.Background(), client, + "nvcr.io", clusterValidatorControlPlaneRole, "a1b2c3d4e5", false) + require.NoError(t, err) + assert.Contains(t, got, "a1b2c3d4e5", "the minted secret must be run-scoped") +} + +// A released CLI's pull Secret carries managed-by=nvcf-cli and the bare name. +// It is that CLI's to delete, so adopting it breaks a pull mid-flight or +// reuses a stale key forever. +func TestScanAndMirrorPullSecret_NeverAdoptsAReleasedCLIsSecret(t *testing.T) { + cfg := dockerConfigBlob(t, "nvcr.io", "$oauthtoken", "key") + legacy := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: validatorPullSecretName, + Namespace: clusterValidatorNamespace, + Labels: map[string]string{ + "app.kubernetes.io/name": clusterValidatorAppLabel, + "app.kubernetes.io/managed-by": "nvcf-cli", + "app.kubernetes.io/component": "preflight", + }, + }, + Type: corev1.SecretTypeDockerConfigJson, + Data: map[string][]byte{corev1.DockerConfigJsonKey: cfg}, + } + client := fake.NewSimpleClientset(legacy) + got, err := scanAndMirrorPullSecret(context.Background(), client, "nvcr.io", + clusterValidatorComputePlaneRole, "runid", false) + require.NoError(t, err) + assert.Empty(t, got) +} diff --git a/src/clis/nvcf-cli/internal/selfhosted/register.go b/src/clis/nvcf-cli/internal/selfhosted/register.go index 14d18dfdb6..ee8f1d3310 100644 --- a/src/clis/nvcf-cli/internal/selfhosted/register.go +++ b/src/clis/nvcf-cli/internal/selfhosted/register.go @@ -315,6 +315,33 @@ func resolveClusterID(resp *client.RegisterClusterResponse) string { // avoid touching a real kubeconfig file. var loadKubeConfigFn = loadKubeConfig +// currentKubeContextNameFn is a seam: tests must not depend on the developer's +// kubeconfig, and hint output would otherwise vary by machine. +var currentKubeContextNameFn = currentKubeContextName + +// currentKubeContextName returns the kubeconfig's current-context, or "" when +// it cannot be read. Used to name the context in remediation hints when the +// caller did not pass one explicitly. Best-effort by design: a hint without +// the flag is still correct at the moment it is printed. +func currentKubeContextName() string { + raw, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig( + clientcmd.NewDefaultClientConfigLoadingRules(), &clientcmd.ConfigOverrides{}, + ).RawConfig() + if err != nil { + return "" + } + return raw.CurrentContext +} + +// effectiveKubeContext resolves the context a probe actually used, so a hint +// naming it stays correct after the current-context changes. +func effectiveKubeContext(kctx string) string { + if kctx != "" { + return kctx + } + return currentKubeContextNameFn() +} + // loadKubeConfig builds a *rest.Config for the given kubeconfig context. // When kctx is empty the kubeconfig's current-context is used, preserving // single-cluster behavior. The loading-rules chain follows the same priority diff --git a/src/clis/nvcf-cli/internal/selfhosted/registry_cred.go b/src/clis/nvcf-cli/internal/selfhosted/registry_cred.go new file mode 100644 index 0000000000..ec8c69b90a --- /dev/null +++ b/src/clis/nvcf-cli/internal/selfhosted/registry_cred.go @@ -0,0 +1,504 @@ +/* +SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +SPDX-License-Identifier: Apache-2.0 + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package selfhosted + +import ( + "context" + "fmt" + "net" + "net/http" + "os" + "sort" + "strconv" + "strings" + "time" + + "sigs.k8s.io/yaml" +) + +const ( + registryProbeTimeout = 10 * time.Second + + // certManagerRegistry is cert-manager's upstream registry. It is only + // contacted when the stack is not rewriting cert-manager images to + // global.image.registry, which global.yaml.gotmpl does for all five of them. + certManagerRegistry = "quay.io" + + // ngcRegistry is the default global.image.registry in the stack's + // environments/base.yaml, and the fallback when no source named a + // registry. See the tail of EnumerateRegistries. + ngcRegistry = "nvcr.io" +) + +// RegistryCredentialChecker probes whether credentials are present and valid +// for a registry. repoHint is the repository path used as the OAuth scope; +// pass "" to probe without a specific scope (works for public registries). +// critical signals that anonymous success is insufficient; configured credentials +// must be present. Returns nil on success, a descriptive error otherwise. +type RegistryCredentialChecker func(ctx context.Context, registry, repoHint string, critical bool) error + +// NewRegistryCredentialChecker returns a production RegistryCredentialChecker +// backed by real HTTP calls. +func NewRegistryCredentialChecker() RegistryCredentialChecker { + return probeRegistryCredential +} + +// probeRegistryCredential authenticates to registry using the OCI Bearer token +// flow. repoHint is the OAuth scope repository path. +// +// When critical is true, anonymous success is not sufficient: the install pulls +// private repositories, so a local credential must also be present. +// +// Returns errRegistryProbeSkipped for registries this flow cannot speak to +// (ECR's SigV4, a Basic challenge, a 200 that does not look like a registry) +// and errRegistryCredentialsUnverified when no local credential is readable. +// Both are advisory: the caller must not fail the run on either. +func probeRegistryCredential(ctx context.Context, registry, repoHint string, critical bool) error { + // ECR uses AWS SigV4, not the OCI Bearer flow, so this probe cannot speak + // to it. Skip rather than fail: returning an error here makes an + // ECR-hosted validator image unconditionally fail preflight, so --wait + // could never converge. + if isECRRegistry(registry) { + return errRegistryProbeSkipped{ + reason: "ECR uses AWS SigV4; verify manually with 'aws ecr get-login-password'", + } + } + + pctx, cancel := context.WithTimeout(ctx, registryProbeTimeout) + defer cancel() + + client := &http.Client{Timeout: registryProbeTimeout, CheckRedirect: refuseInsecureRedirect} + + // Step 1: probe /v2/ unauthenticated. + probeURL := "https://" + registry + "/v2/" + req, err := http.NewRequestWithContext(pctx, http.MethodGet, probeURL, nil) + if err != nil { + return fmt.Errorf("build request: %w", err) + } + resp, err := client.Do(req) + if err != nil { + return fmt.Errorf("cannot reach %s: %w", registry, err) + } + + // A 200 only proves /v2/ is anonymously readable, which says nothing about + // the repositories the install actually pulls. Require the registry to + // identify itself as an OCI registry so a captive portal or TLS-intercepting + // proxy answering 200 HTML is not read as success. + isOCI := resp.Header.Get("Docker-Distribution-Api-Version") != "" || + strings.Contains(strings.Join(resp.Header.Values("Www-Authenticate"), ","), "realm") + + switch resp.StatusCode { + case http.StatusOK: + resp.Body.Close() + if !isOCI { + // A captive portal or TLS-intercepting proxy also answers 200. The + // header is a Docker convention rather than an OCI requirement, so + // a conformant registry may legitimately omit it: report that the + // result is unverifiable instead of guessing either way. + return errRegistryProbeSkipped{ + reason: fmt.Sprintf("%s answered 200 but did not identify as an OCI registry; "+ + "if this is unexpected, check for a proxy or captive portal", registry), + } + } + // Anonymous read works. For a critical registry that is still not + // enough: the install pulls private repositories, so a credential must + // exist. It cannot be verified here, since /v2/ accepts anyone, so say + // so rather than report "credentials valid". + if !critical { + return nil + } + if err := requireConfiguredCredentials(registry); err != nil { + return err + } + return errRegistryCredentialsNotVerified{registry: registry} + case http.StatusUnauthorized: + // Auth required - proceed with token exchange. + default: + resp.Body.Close() + return fmt.Errorf("unexpected status %s from %s", resp.Status, registry) + } + + // Step 2: exchange credentials for a Bearer token using the actual repo + // from the configured image (repoHint). Using a fake repo name causes + // org-level 403s from NGC and GHCR for non-existent orgs, which is + // indistinguishable from bad credentials. + wwwAuth := selectAuthChallenge(resp.Header.Values("Www-Authenticate")) + resp.Body.Close() + + // Only the Bearer flow is implemented. Self-hosted Harbor and htpasswd + // mirrors commonly answer with Basic, where the Bearer path would report a + // spurious credential failure, so skip instead. + if scheme := authChallengeScheme(wwwAuth); scheme != "" && !strings.EqualFold(scheme, "Bearer") { + return errRegistryProbeSkipped{ + reason: fmt.Sprintf("%s uses %s auth; only the OCI Bearer flow is probed", registry, scheme), + } + } + + _, err = exchangeBearerToken(pctx, client, registry, repoHint, wwwAuth) + if err != nil { + // Use credentialsForRegistry (not ngcCredentials) so NGC_API_KEY does + // not masquerade as credentials for quay.io, GHCR, or Harbor - those + // registries reject NGC tokens, which would wrongly produce "credentials + // rejected" when the real diagnosis is "no credentials configured." + if _, _, hasCreds := credentialsForRegistry(registry); !hasCreds { + // Same situation as the critical path below, so the same verdict: + // no readable local credential is not proof that none exists (a + // helper binary may be missing from PATH, or the login may live + // outside the docker config entirely). + return errRegistryCredentialsUnverified{registry: registry} + } + return fmt.Errorf("credentials rejected by %s: %w", registry, err) + } + // For critical registries, a successful anonymous token is not enough: + // if the actual install pulls private images, anonymous access will fail. + if critical { + return requireConfiguredCredentials(registry) + } + return nil +} + +// errRegistryCredentialsNotVerified marks a registry that allows anonymous +// access to /v2/: a local credential exists, but nothing here proves the +// registry accepts it. Reported as a pass that says so. +type errRegistryCredentialsNotVerified struct{ registry string } + +func (e errRegistryCredentialsNotVerified) Error() string { + return "credentials configured; not verified, because the registry allows anonymous access to /v2/" +} + +// errRegistryProbeSkipped marks a registry this probe cannot speak to. The +// caller reports it as a skip rather than a credential failure. +type errRegistryProbeSkipped struct{ reason string } + +func (e errRegistryProbeSkipped) Error() string { return e.reason } + +// selectAuthChallenge picks the challenge to answer when a registry offers +// several, as it may in separate WWW-Authenticate headers: the Bearer one if +// present, otherwise the first. Reading only the first header reported a +// registry answering "Negotiate" then "Bearer" as a clean skip. +func selectAuthChallenge(challenges []string) string { + for _, c := range challenges { + if strings.EqualFold(authChallengeScheme(c), "Bearer") { + return c + } + // Several challenges can also share one header, comma-separated: + // `Negotiate, Bearer realm="..."`. Take the Bearer one from there. + if i := bearerChallengeStart(c); i > 0 { + return c[i:] + } + } + if len(challenges) > 0 { + return challenges[0] + } + return "" +} + +// bearerChallengeStart returns where a Bearer challenge begins after a comma +// in a joined WWW-Authenticate value, or -1. +func bearerChallengeStart(header string) int { + lower := strings.ToLower(header) + for from := 0; ; { + j := strings.Index(lower[from:], "bearer ") + if j < 0 { + return -1 + } + j += from + if j > 0 && strings.HasSuffix(strings.TrimRight(lower[:j], " "), ",") { + return j + } + from = j + 1 + } +} + +// authChallengeScheme returns the auth scheme named by a WWW-Authenticate +// header, or "" when the header is absent or malformed. +func authChallengeScheme(header string) string { + header = strings.TrimSpace(header) + if header == "" { + return "" + } + if i := strings.IndexByte(header, ' '); i > 0 { + return header[:i] + } + return header +} + +// requireConfiguredCredentials reports an error when no credential is reachable +// for the registry. +// +// A missing credential is a warning, not a hard failure: credsFromDockerConfig +// reads inline auth entries and asks credsStore / credHelpers helpers, but a +// helper binary can be absent from PATH or refuse to answer non-interactively. +// The install path also mints or mirrors a pull secret of its own, so preflight +// must not be the thing that blocks. +func requireConfiguredCredentials(registry string) error { + if _, _, ok := credentialsForRegistry(registry); ok { + return nil + } + return errRegistryCredentialsUnverified{registry: registry} +} + +// errRegistryCredentialsUnverified means no credential was found locally. The +// caller downgrades this to a warning. +type errRegistryCredentialsUnverified struct{ registry string } + +func (e errRegistryCredentialsUnverified) Error() string { + return fmt.Sprintf("no local credentials found for %s "+ + "(a credential helper such as credsStore is not readable here); "+ + "if 'docker pull' works this can be ignored, otherwise add an entry to "+ + "~/.docker/config.json or set NGC_API_KEY for NGC registries", e.registry) +} + +// isECRRegistry returns true for AWS Elastic Container Registry hostnames, +// which use AWS SigV4 auth instead of the OCI Bearer token flow. +func isECRRegistry(registry string) bool { + return strings.Contains(registry, ".dkr.ecr.") && + strings.HasSuffix(registry, ".amazonaws.com") +} + +// RegistryEntry is one registry endpoint to credential-check. +type RegistryEntry struct { + // Registry is the hostname (and optional port) of the container registry. + Registry string + // RepoHint is the repository path used as the OAuth scope when probing + // credentials (e.g. "nvidia/nvcf-byoc/cluster-validator" for NGC). + // Empty means probe without a specific scope, which works for public + // registries (quay.io, Docker Hub public images) and GHCR anonymous access. + RepoHint string + // Critical marks registries whose credential failure should be a hard error + // rather than a warning. NGC (nvcr.io) is always critical; customer-supplied + // extras default to non-critical. + Critical bool +} + +// StackValues are the stack settings preflight needs, read from the stack's +// environment values with the same layering helmfile uses. +type StackValues struct { + // Found is true when at least one values file was read. + Found bool + // ImageRegistry is global.image.registry. The stack renders + // registry + "/" + repository, so it may carry a path. + ImageRegistry string + // ACMESolverRepository is certManager.acmesolver.image.repository. When + // unset the ACME solver keeps the chart's quay.io/jetstack default. + ACMESolverRepository string + // EnvoyGatewayNamespace is ingress.gatewayApi.controllerNamespace. + EnvoyGatewayNamespace string + // HAMode is highAvailability.mode, defaulting to "none" as the stack's + // own template does when the key is unset. Empty when no file was read. + HAMode string + // Gateways are the NVCF Gateways the stack names under + // ingress.gatewayApi.gateways, as sorted, distinct namespace/name entries. + Gateways []string +} + +// LoadStackValues reads files in order and layers each over the previous, +// the way helmfile layers an environment file over base.yaml. Unreadable or +// malformed files are skipped. +func LoadStackValues(files []string) StackValues { + merged := map[string]any{} + found := false + for _, path := range files { + data, err := os.ReadFile(path) + if err != nil { + continue + } + var doc map[string]any + if err := yaml.Unmarshal(data, &doc); err != nil { + continue + } + found = true + mergeValues(merged, doc) + } + values := StackValues{ + Found: found, + ImageRegistry: digString(merged, "global", "image", "registry"), + ACMESolverRepository: digString(merged, "certManager", "acmesolver", "image", "repository"), + EnvoyGatewayNamespace: digString(merged, "ingress", "gatewayApi", "controllerNamespace"), + Gateways: stackGateways(merged), + } + if found { + values.HAMode = digString(merged, "highAvailability", "mode") + if values.HAMode == "" { + values.HAMode = "none" + } + } + return values +} + +// stackGateways returns the Gateways named under ingress.gatewayApi.gateways +// that have both a name and a namespace, as sorted, distinct namespace/name +// entries. An entry the environment left empty is one it does not use. +func stackGateways(merged map[string]any) []string { + gateways, _ := digAny(merged, "ingress", "gatewayApi", "gateways").(map[string]any) + seen := map[string]bool{} + var out []string + for key := range gateways { + name := digString(gateways, key, "name") + ns := digString(gateways, key, "namespace") + if name == "" || ns == "" || strings.Contains(name, "/") || strings.Contains(ns, "/") { + continue + } + if entry := ns + "/" + name; !seen[entry] { + seen[entry] = true + out = append(out, entry) + } + } + sort.Strings(out) + return out +} + +// mergeValues deep-merges src into dst: maps merge key by key, anything else +// in src replaces what dst held. +func mergeValues(dst, src map[string]any) { + for k, v := range src { + if sv, ok := v.(map[string]any); ok { + if dv, ok := dst[k].(map[string]any); ok { + mergeValues(dv, sv) + continue + } + } + dst[k] = v + } +} + +func digString(m map[string]any, keys ...string) string { + s, _ := digAny(m, keys...).(string) + return strings.TrimSpace(s) +} + +func digAny(m map[string]any, keys ...string) any { + var cur any = m + for _, k := range keys { + mm, ok := cur.(map[string]any) + if !ok { + return nil + } + cur = mm[k] + } + return cur +} + +// EnumerateRegistries builds the deduplicated list of registries to credential- +// check from the image ref, cert-manager (quay.io), the stack values +// (global.image.registry), and operator-supplied extras. stackValuesFiles are +// layered in order (base.yaml first, then the environment file). +func EnumerateRegistries(imageRef string, stackValuesFiles []string, extras []string) []RegistryEntry { + seen := make(map[string]bool) + var out []RegistryEntry + stack := LoadStackValues(stackValuesFiles) + + // add reports whether the registry is in the list, so a source only counts + // as having named a registry once its value passed validation. + add := func(registry, repoHint string, critical bool) bool { + registry = strings.TrimSpace(registry) + if registry == "" { + return false + } + if seen[registry] { + return true + } + // Every source goes through the same host validation. The probe builds + // "https://" + registry + "/v2/", so a value carrying "/" or "@" moves + // the host: global.image.registry set to "nvcr.io@attacker.example.com" + // in a values file the CLI finds by walking up from CWD would otherwise + // aim an outbound request wherever that names. Extras were guarded via + // parseRegistryHostPort; the image ref and the values file were not. + if !isBareRegistryHost(strings.TrimSuffix(strings.TrimPrefix(registry, "["), "]")) && + !isBareRegistryHost(registry) { + return false + } + seen[registry] = true + out = append(out, RegistryEntry{Registry: registry, RepoHint: repoHint, Critical: critical}) + return true + } + + // Source 1: base registry from the configured validator image. + // Carry the repo path as a scope hint so the token exchange uses the + // operator's actual org rather than a fake one - NGC returns 403 for + // orgs the API key cannot access, even if the key itself is valid. + // + // Critical follows the same rule as every other source rather than being + // forced true: an air-gapped install that side-loaded the image never + // contacts this registry at pull time (ImagePullPolicy is IfNotPresent). + imageNamedRegistry := false + if reg, repo, _, ok := parseImageRef(imageRef); ok && reg != "" { + imageNamedRegistry = add(reg, repo, isNGCRegistry(reg)) + } + + // Source 2: global.image.registry from the stack values. This catches an + // operator pointing at a custom NGC org or a staging registry that differs + // from the validator image's. The stack renders registry + "/" + repository, + // so "harbor.corp.example/nvcf" is a valid value: probe its host and carry + // the path as the scope hint rather than rejecting the whole value. + stackNamedRegistry := false + if host, path, _ := strings.Cut(stack.ImageRegistry, "/"); host != "" { + // If it's an NGC registry, mark critical; customer mirrors are non-critical. + stackNamedRegistry = add(host, path, isNGCRegistry(host)) + } + + // Source 3: cert-manager's ACME solver. global.yaml.gotmpl moves the + // controller images to the mirror, but the solver keeps the chart's + // quay.io/jetstack default unless certManager.acmesolver.image is set, so + // a mirrored site still pulls it from quay.io at its first HTTP-01 order. + // Probe quay.io unless the stack points the solver somewhere else. + if acme := stack.ACMESolverRepository; !stack.Found || acme == "" || + strings.HasPrefix(acme, certManagerRegistry+"/") { + add(certManagerRegistry, "", false) + } + + // Source 4: operator-supplied extras (--cluster-validator-registries). + // Preserve non-443 ports so probeRegistryCredential builds the correct + // https://host:port/v2/ URL. net.JoinHostPort brackets IPv6 literals, which + // a bare "%s:%d" would corrupt into https://::1:5000/v2/. + for _, e := range extras { + host, port := parseRegistryHostPort(e) + if host == "" { + continue + } + reg := host + if port != 0 && port != 443 { + reg = net.JoinHostPort(host, strconv.Itoa(port)) + } else if strings.Contains(host, ":") { + // IPv6 literal on the default port. parseRegistryHostPort returns + // it unbracketed, so bracket it once for the URL host. + reg = "[" + host + "]" + } + add(reg, "", false) + } + + // Fallback for "no source named a registry at all": no validator image is + // configured and no stack values file resolved. That is the default + // configuration rather than an edge case, and without this the run reports + // on quay.io alone while the operator's NGC credentials go unchecked. + // environments/base.yaml sets global.image.registry to nvcr.io, so it is + // the right guess when there is nothing else to go on. + // + // Deliberately not triggered when the image or the stack named a non-NGC + // registry: that is a mirrored install, which has no reason to reach + // nvcr.io, and probing it there would add a failing round trip for the + // sites least able to make it. + // + // Non-critical, unlike an NGC registry a source actually named. A guess + // must not hard-fail the run, and this category has no opt-out flag. + if !imageNamedRegistry && !stackNamedRegistry { + add(ngcRegistry, "", false) + } + + return out +} diff --git a/src/clis/nvcf-cli/internal/selfhosted/registry_cred_test.go b/src/clis/nvcf-cli/internal/selfhosted/registry_cred_test.go new file mode 100644 index 0000000000..33f6777900 --- /dev/null +++ b/src/clis/nvcf-cli/internal/selfhosted/registry_cred_test.go @@ -0,0 +1,610 @@ +/* +SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +SPDX-License-Identifier: Apache-2.0 + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package selfhosted + +import ( + "context" + "fmt" + "net/http" + "net/http/httptest" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// -- probeRegistryCredential -- + +func TestProbeRegistryCredential_PublicRegistry(t *testing.T) { + // Registry returns 200 on /v2/ with the registry API header -> public, + // no credentials needed. + srv := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Docker-Distribution-Api-Version", "registry/2.0") + w.WriteHeader(http.StatusOK) + })) + defer srv.Close() + + // Replace the default transport with the test server's transport so TLS + // validation passes against the self-signed cert. + origTransport := http.DefaultTransport + http.DefaultTransport = srv.Client().Transport + t.Cleanup(func() { http.DefaultTransport = origTransport }) + + // Use the test server's host as the registry. + host := strings.TrimPrefix(srv.URL, "https://") + err := probeRegistryCredential(context.Background(), host, "", false) + assert.NoError(t, err, "public registry (200 on /v2/) must not return an error") +} + +func TestProbeRegistryCredential_AuthSucceeds(t *testing.T) { + // Registry: /v2/ returns 401 with WWW-Authenticate; token endpoint returns a token. + tokenSrv := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Token endpoint always succeeds. + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(`{"token":"test-token-abc"}`)) + })) + defer tokenSrv.Close() + + registrySrv := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Header.Get("Authorization") == "Bearer test-token-abc" { + w.WriteHeader(http.StatusOK) + return + } + // Point the client at our token server. + realm := tokenSrv.URL + "/token" + w.Header().Set("Www-Authenticate", + `Bearer realm="`+realm+`",service="test-registry",scope="repository:test:pull"`) + w.WriteHeader(http.StatusUnauthorized) + })) + defer registrySrv.Close() + + transport := registrySrv.Client().Transport + origTransport := http.DefaultTransport + http.DefaultTransport = transport + t.Cleanup(func() { http.DefaultTransport = origTransport }) + + host := strings.TrimPrefix(registrySrv.URL, "https://") + err := probeRegistryCredential(context.Background(), host, "", false) + assert.NoError(t, err, "successful token exchange must return nil") +} + +func TestProbeRegistryCredential_AuthFails(t *testing.T) { + // Registry returns 401 but the token endpoint returns 401 too -> bad credentials. + tokenSrv := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusUnauthorized) + })) + defer tokenSrv.Close() + + registrySrv := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + realm := tokenSrv.URL + "/token" + w.Header().Set("Www-Authenticate", + `Bearer realm="`+realm+`",service="test-registry"`) + w.WriteHeader(http.StatusUnauthorized) + })) + defer registrySrv.Close() + + transport := registrySrv.Client().Transport + origTransport := http.DefaultTransport + http.DefaultTransport = transport + t.Cleanup(func() { http.DefaultTransport = origTransport }) + + host := strings.TrimPrefix(registrySrv.URL, "https://") + err := probeRegistryCredential(context.Background(), host, "", false) + assert.Error(t, err, "failed token exchange must return an error") +} + +func TestProbeRegistryCredential_ECRSkipped(t *testing.T) { + // ECR registries must get a clear "use AWS CLI" message rather than a + // confusing Bearer token failure. + err := probeRegistryCredential(context.Background(), + "123456789.dkr.ecr.us-east-1.amazonaws.com", "", false) + require.Error(t, err) + assert.Contains(t, err.Error(), "ECR", "ECR registries must produce a clear diagnostic") + assert.Contains(t, err.Error(), "AWS", "message must mention AWS") +} + +// -- EnumerateRegistries -- + +func TestEnumerateRegistries_FromImageRef(t *testing.T) { + entries := EnumerateRegistries("nvcr.io/nvidia/nvcf-byoc/cluster-validator:3.1.0", nil, nil) + require.NotEmpty(t, entries) + + found := false + for _, e := range entries { + if e.Registry == "nvcr.io" { + assert.True(t, e.Critical, "nvcr.io must be marked critical") + found = true + } + } + assert.True(t, found, "nvcr.io must appear in the enumerated registries") +} + +func TestEnumerateRegistries_RepoHintFromImageRef(t *testing.T) { + // The RepoHint must be the repo path from the image ref so the token + // exchange uses the operator's actual org, not a fake one. + entries := EnumerateRegistries("nvcr.io/nvidia/nvcf-byoc/cluster-validator:3.1.0", nil, nil) + for _, e := range entries { + if e.Registry == "nvcr.io" { + assert.Equal(t, "nvidia/nvcf-byoc/cluster-validator", e.RepoHint, + "RepoHint must carry the actual repo path for correct NGC token scope") + return + } + } + t.Fatal("nvcr.io not found in entries") +} + +func TestEnumerateRegistries_IncludesCertManagerWhenStackIsNotMirroring(t *testing.T) { + // With no stack values file there is no global.image.registry to mirror + // cert-manager to, so its upstream registry is genuinely contacted. + entries := EnumerateRegistries("nvcr.io/some/image:1.0", nil, nil) + found := false + for _, e := range entries { + if e.Registry == "quay.io" { + assert.False(t, e.Critical, "quay.io must be non-critical") + found = true + } + } + assert.True(t, found, "quay.io must always be included for cert-manager") +} + +func TestEnumerateRegistries_ExtrasAppended(t *testing.T) { + entries := EnumerateRegistries("nvcr.io/some/image:1.0", nil, + []string{"harbor.company.internal:443", "ghcr.io:443"}) + + registries := make(map[string]bool, len(entries)) + for _, e := range entries { + registries[e.Registry] = true + } + assert.True(t, registries["harbor.company.internal"], "extra registry must be added") + assert.True(t, registries["ghcr.io"], "extra registry must be added") +} + +func TestEnumerateRegistries_NoDuplicates(t *testing.T) { + // Pass nvcr.io both as the image registry and as an extra - must not dedup. + entries := EnumerateRegistries("nvcr.io/some/image:1.0", nil, + []string{"nvcr.io"}) + + count := 0 + for _, e := range entries { + if e.Registry == "nvcr.io" { + count++ + } + } + assert.Equal(t, 1, count, "nvcr.io must appear exactly once even when listed twice") +} + +func TestEnumerateRegistries_StackValuesFile(t *testing.T) { + // Write a minimal environments/local.yaml to a temp dir. + dir := t.TempDir() + valuesPath := dir + "/local.yaml" + require.NoError(t, writeFile(valuesPath, []byte(` +global: + image: + registry: stg.nvcr.io +`))) + + entries := EnumerateRegistries("nvcr.io/some/image:1.0", []string{valuesPath}, nil) + found := false + for _, e := range entries { + if e.Registry == "stg.nvcr.io" { + assert.True(t, e.Critical, "NGC staging registry must be critical") + found = true + } + } + assert.True(t, found, "registry from values file must be included") +} + +func TestReadGlobalImageRegistry_ValidFile(t *testing.T) { + dir := t.TempDir() + path := dir + "/values.yaml" + require.NoError(t, writeFile(path, []byte(` +global: + image: + registry: nvcr.io + repository: nvidia/nvcf-byoc +`))) + got := LoadStackValues([]string{path}).ImageRegistry + assert.Equal(t, "nvcr.io", got) +} + +func TestReadGlobalImageRegistry_MissingFile(t *testing.T) { + got := LoadStackValues([]string{"/nonexistent/path/values.yaml"}).ImageRegistry + assert.Empty(t, got, "missing file must return empty string, not error") +} + +func TestReadGlobalImageRegistry_MissingKey(t *testing.T) { + dir := t.TempDir() + path := dir + "/values.yaml" + require.NoError(t, writeFile(path, []byte(`other: value`))) + got := LoadStackValues([]string{path}).ImageRegistry + assert.Empty(t, got, "missing global.image.registry must return empty string") +} + +// -- isECRRegistry -- + +func TestIsECRRegistry(t *testing.T) { + assert.True(t, isECRRegistry("123456789012.dkr.ecr.us-east-1.amazonaws.com")) + assert.True(t, isECRRegistry("999999999999.dkr.ecr.eu-west-1.amazonaws.com")) + assert.False(t, isECRRegistry("nvcr.io")) + assert.False(t, isECRRegistry("ghcr.io")) + assert.False(t, isECRRegistry("harbor.company.internal")) + assert.False(t, isECRRegistry("s3.amazonaws.com")) // S3, not ECR +} + +// -- registryCredentialCheck binaryCheckSpec -- + +func TestRegistryCredentialCheck_PassWhenNoError(t *testing.T) { + checker := func(_ context.Context, reg, _ string, _ bool) error { return nil } + spec := registryCredentialCheck(checker, RegistryEntry{Registry: "nvcr.io", Critical: true}) + r := spec.Run(context.Background()) + assert.True(t, r.Passed) + assert.Equal(t, "info", r.Severity) + assert.Contains(t, r.Message, "nvcr.io") +} + +func TestRegistryCredentialCheck_CriticalSeverityOnFailure(t *testing.T) { + checker := func(_ context.Context, reg, _ string, _ bool) error { + return errorf("credentials rejected") + } + spec := registryCredentialCheck(checker, RegistryEntry{Registry: "nvcr.io", Critical: true}) + r := spec.Run(context.Background()) + assert.False(t, r.Passed) + assert.Equal(t, "error", r.Severity, "critical registry failure must be error severity") +} + +func TestRegistryCredentialCheck_WarningSeverityOnNonCriticalFailure(t *testing.T) { + checker := func(_ context.Context, reg, _ string, _ bool) error { + return errorf("credentials rejected") + } + spec := registryCredentialCheck(checker, RegistryEntry{Registry: "quay.io", Critical: false}) + r := spec.Run(context.Background()) + assert.False(t, r.Passed) + assert.Equal(t, "warning", r.Severity, "non-critical registry failure must be warning severity") +} + +// helpers + +func errorf(msg string) error { return fmt.Errorf("%s", msg) } + +func writeFile(path string, data []byte) error { + return os.WriteFile(path, data, 0o644) +} + +// When the stack also mirrors the ACME solver, nothing it installs is pulled +// from quay.io, so probing it is a pointless 10-second round trip. +func TestEnumerateRegistries_SkipsCertManagerWhenSolverIsMirrored(t *testing.T) { + dir := t.TempDir() + valuesPath := dir + "/base.yaml" + require.NoError(t, writeFile(valuesPath, []byte(` +global: + image: + registry: mirror.company.internal +certManager: + acmesolver: + image: + repository: mirror.company.internal/jetstack/cert-manager-acmesolver +`))) + + entries := EnumerateRegistries("nvcr.io/some/image:1.0", []string{valuesPath}, nil) + for _, e := range entries { + assert.NotEqual(t, certManagerRegistry, e.Registry, + "a stack mirroring images must not trigger a quay.io probe") + } +} + +// A bare IPv6 literal must stay bracketed or the probe URL is malformed. +func TestEnumerateRegistries_BracketsIPv6Extras(t *testing.T) { + entries := EnumerateRegistries("nvcr.io/some/image:1.0", nil, []string{"[::1]:5000"}) + + found := false + for _, e := range entries { + if strings.Contains(e.Registry, "::1") { + found = true + assert.Equal(t, "[::1]:5000", e.Registry, + "an IPv6 literal must keep its brackets so the probe URL parses") + } + } + assert.True(t, found, "the IPv6 extra must be enumerated") +} + +// The validator image's registry follows the same Critical policy as every +// other source. Forcing it true makes an air-gapped install fail preflight for +// a registry that is never contacted (ImagePullPolicy is IfNotPresent). +func TestEnumerateRegistries_NonNGCImageRegistryIsNotCritical(t *testing.T) { + entries := EnumerateRegistries("mirror.company.internal/nvcf/cluster-validator:1.0", nil, nil) + for _, e := range entries { + if e.Registry == "mirror.company.internal" { + assert.False(t, e.Critical, "a non-NGC mirror must not be forced critical") + return + } + } + t.Fatal("mirror.company.internal not enumerated") +} + +// With no validator image and no stack values file, nothing names a registry +// and the run would report on quay.io alone, leaving the operator's NGC +// credentials unchecked in what is the default configuration. +func TestEnumerateRegistries_FallsBackToNGCWhenNothingNamesARegistry(t *testing.T) { + got := EnumerateRegistries("", nil, nil) + + var ngc *RegistryEntry + for i := range got { + if got[i].Registry == "nvcr.io" { + ngc = &got[i] + } + } + require.NotNil(t, ngc, "nvcr.io must be probed when no source names a registry") + assert.False(t, ngc.Critical, + "a guessed registry must not hard-fail the run; this category has no opt-out flag") +} + +// A mirrored install has no reason to reach nvcr.io, and probing it there adds +// a failing round trip for the sites least able to make it. +func TestEnumerateRegistries_NoNGCFallbackForAMirroredStack(t *testing.T) { + dir := t.TempDir() + values := filepath.Join(dir, "base.yaml") + require.NoError(t, os.WriteFile(values, + []byte("global:\n image:\n registry: harbor.example.com\n"), 0o600)) + + got := EnumerateRegistries("", []string{values}, nil) + for _, e := range got { + assert.NotEqual(t, "nvcr.io", e.Registry, + "a stack that named its own registry must not be probed against NGC") + } +} + +// The fallback must not shadow or duplicate an NGC registry a source named, +// which carries a repo hint and is critical. +func TestEnumerateRegistries_FallbackDoesNotDuplicateNamedNGC(t *testing.T) { + got := EnumerateRegistries("nvcr.io/nvidia/nvcf-byoc/cluster-validator:1.0.0", nil, nil) + + n := 0 + for _, e := range got { + if e.Registry == "nvcr.io" { + n++ + assert.True(t, e.Critical, "an NGC registry named by the image stays critical") + assert.Equal(t, "nvidia/nvcf-byoc/cluster-validator", e.RepoHint) + } + } + assert.Equal(t, 1, n, "nvcr.io must appear exactly once") +} + +// parseRegistryHostPort returns its input verbatim when SplitHostPort fails, +// and it fails on an already-bracketed literal with missingPort. Without a +// prefix guard that arrives here still bracketed and becomes "[[fd00::1]]", +// which http.NewRequest rejects. The documented bracketed form must work. +func TestEnumerateRegistries_DoesNotDoubleBracketIPv6(t *testing.T) { + for _, in := range []string{"[fd00::1]", "fd00::1"} { + got := EnumerateRegistries("", nil, []string{in}) + var found string + for _, e := range got { + if strings.Contains(e.Registry, "fd00") { + found = e.Registry + } + } + assert.Equal(t, "[fd00::1]", found, "input %q must yield a singly-bracketed host", in) + } +} + +func TestEnumerateRegistries_KeepsBracketedIPv6WithPort(t *testing.T) { + got := EnumerateRegistries("", nil, []string{"[fd00::1]:5000"}) + var found string + for _, e := range got { + if strings.Contains(e.Registry, "fd00") { + found = e.Registry + } + } + assert.Equal(t, "[fd00::1]:5000", found) +} + +// The probe builds "https://" + registry + "/v2/", so a registry string that +// carries "/" or "@" moves the host. A values file the CLI finds by walking up +// from CWD must not be able to aim an outbound request. +func TestEnumerateRegistries_RejectsHostMovingRegistryStrings(t *testing.T) { + dir := t.TempDir() + values := filepath.Join(dir, "base.yaml") + require.NoError(t, os.WriteFile(values, + []byte("global:\n image:\n registry: nvcr.io@attacker.example.com\n"), 0o600)) + + for _, e := range EnumerateRegistries("nvcr.io@evil.test/x/y:1", []string{values}, nil) { + assert.NotContains(t, e.Registry, "@", "a host-moving string must not be probed") + assert.NotContains(t, e.Registry, "attacker.example.com") + assert.NotContains(t, e.Registry, "evil.test") + } +} + +// helmfile layers the environment file over base.yaml, so an env file that +// overrides only the repository keeps base.yaml's registry, and an env file +// that sets a mirror replaces it. +func TestLoadStackValues_LayersEnvOverBase(t *testing.T) { + dir := t.TempDir() + base, env := dir+"/base.yaml", dir+"/airgap.yaml" + require.NoError(t, writeFile(base, []byte(` +global: + image: + registry: nvcr.io + repository: nvidia/nvcf-byoc +ingress: + gatewayApi: + controllerNamespace: envoy-gateway-system +`))) + require.NoError(t, writeFile(env, []byte(` +global: + image: + repository: other/repo +`))) + got := LoadStackValues([]string{base, env}) + assert.True(t, got.Found) + assert.Equal(t, "nvcr.io", got.ImageRegistry, "an env file that only overrides the repository keeps the base registry") + assert.Equal(t, "envoy-gateway-system", got.EnvoyGatewayNamespace) + + require.NoError(t, writeFile(env, []byte(` +global: + image: + registry: harbor.corp.example +`))) + assert.Equal(t, "harbor.corp.example", LoadStackValues([]string{base, env}).ImageRegistry) +} + +// A registry value carrying a path is valid for the stack, which renders +// registry + "/" + repository. It must still be probed, and it must still +// suppress the nvcr.io guess, rather than dropping the whole category. +func TestEnumerateRegistries_StackRegistryWithPath(t *testing.T) { + dir := t.TempDir() + values := dir + "/base.yaml" + require.NoError(t, writeFile(values, []byte(` +global: + image: + registry: harbor.corp.example/nvcf +certManager: + acmesolver: + image: + repository: harbor.corp.example/jetstack/cert-manager-acmesolver +`))) + got := EnumerateRegistries("", []string{values}, nil) + require.Len(t, got, 1) + assert.Equal(t, "harbor.corp.example", got[0].Registry) + assert.Equal(t, "nvcf", got[0].RepoHint) +} + +// A rejected registry value must not count as a source naming a registry: +// otherwise it suppresses the nvcr.io fallback and the category vanishes. +func TestEnumerateRegistries_RejectedValueDoesNotSuppressFallback(t *testing.T) { + dir := t.TempDir() + values := dir + "/base.yaml" + require.NoError(t, writeFile(values, []byte("global:\n image:\n registry: \"nvcr.io@evil.test\"\n"))) + got := EnumerateRegistries("", []string{values}, nil) + var names []string + for _, e := range got { + names = append(names, e.Registry) + } + assert.Contains(t, names, ngcRegistry) + assert.NotContains(t, names, "nvcr.io@evil.test") +} + +// quay.io depends on the ACME solver image, which the stack leaves on +// quay.io/jetstack unless it is overridden, not on whether a values file was +// found. +func TestEnumerateRegistries_QuayFollowsTheACMESolverImage(t *testing.T) { + dir := t.TempDir() + values := dir + "/base.yaml" + names := func() []string { + var out []string + for _, e := range EnumerateRegistries("", []string{values}, nil) { + out = append(out, e.Registry) + } + return out + } + require.NoError(t, writeFile(values, []byte("global:\n image:\n registry: harbor.corp.example\n"))) + assert.Contains(t, names(), certManagerRegistry, "a mirrored stack still pulls the default ACME solver from quay.io") + + require.NoError(t, writeFile(values, []byte( + "global:\n image:\n registry: harbor.corp.example\ncertManager:\n acmesolver:\n image:\n repository: harbor.corp.example/jetstack/acmesolver\n"))) + assert.NotContains(t, names(), certManagerRegistry, "a mirrored ACME solver never reaches quay.io") +} + +// "[fd00::1]" with no port must reach the ConfigMap unbracketed; the validator +// brackets it itself, and a double bracket is never reachable. +func TestParseRegistryHostPort_StripsIPv6Brackets(t *testing.T) { + host, port := parseRegistryHostPort("[fd00::1]") + assert.Equal(t, "fd00::1", host) + assert.Equal(t, 443, port) + got := EnumerateRegistries("", nil, []string{"[fd00::1]"}) + var names []string + for _, e := range got { + names = append(names, e.Registry) + } + assert.Contains(t, names, "[fd00::1]") +} + +// A validator image whose registry fails validation must not count as naming +// a registry either, or the nvcr.io fallback is suppressed with nothing probed. +func TestEnumerateRegistries_RejectedImageRegistryDoesNotSuppressFallback(t *testing.T) { + var names []string + for _, e := range EnumerateRegistries("user@evil.test/nvidia/validator:1", nil, nil) { + names = append(names, e.Registry) + } + assert.Contains(t, names, ngcRegistry) +} + +// A critical registry whose /v2/ answers 200 lets anyone in, so a configured +// credential cannot be checked there. The result says so instead of +// "credentials valid", and the check still passes. +func TestProbeRegistryCredential_CriticalAnonymousIsNotVerified(t *testing.T) { + srv := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("Docker-Distribution-Api-Version", "registry/2.0") + w.WriteHeader(http.StatusOK) + })) + defer srv.Close() + origTransport := http.DefaultTransport + http.DefaultTransport = srv.Client().Transport + t.Cleanup(func() { http.DefaultTransport = origTransport }) + host := strings.TrimPrefix(srv.URL, "https://") + dockerHome(t, inlineDockerConfig(t, host, "u", "p", "")) + + err := probeRegistryCredential(context.Background(), host, "", true) + var notVerified errRegistryCredentialsNotVerified + require.ErrorAs(t, err, ¬Verified) + + r := registryCredentialCheck(func(ctx context.Context, reg, repo string, critical bool) error { + return probeRegistryCredential(ctx, reg, repo, critical) + }, RegistryEntry{Registry: host, Critical: true}).Run(context.Background()) + assert.True(t, r.Passed) + assert.NotContains(t, r.Message, "credentials valid") + assert.Contains(t, r.Message, "not verified") +} + +// The launcher reads the stack's HA mode and NVCF Gateways, so the validator +// is told rather than left to guess. An unset mode is "none", as the stack's +// own template reads it, and a Gateway entry left empty is not in use. +func TestLoadStackValues_HAModeAndGateways(t *testing.T) { + dir := t.TempDir() + base, env := dir+"/base.yaml", dir+"/prod.yaml" + require.NoError(t, writeFile(base, []byte(` +ingress: + gatewayApi: + gateways: + shared: {name: "", namespace: ""} + grpc: {name: "", namespace: ""} + llmGrpc: {name: "", namespace: ""} +`))) + require.NoError(t, writeFile(env, []byte(` +highAvailability: + mode: enforced +ingress: + gatewayApi: + gateways: + shared: {name: shared-gw, namespace: envoy-gateway-system} + grpc: {name: grpc-gw, namespace: envoy-gateway-system} + nats: {name: shared-gw, namespace: envoy-gateway-system} +`))) + got := LoadStackValues([]string{base, env}) + assert.Equal(t, "enforced", got.HAMode) + assert.Equal(t, []string{"envoy-gateway-system/grpc-gw", "envoy-gateway-system/shared-gw"}, got.Gateways, + "sorted, distinct, and without the unused llmGrpc entry") + + got = LoadStackValues([]string{base}) + assert.Equal(t, "none", got.HAMode, "unset reads as none, as the stack template does") + assert.Empty(t, got.Gateways) + + assert.Empty(t, LoadStackValues(nil).HAMode, "no stack read means no mode to report") +} diff --git a/src/clis/nvcf-cli/internal/selfhosted/stack_namespaces.go b/src/clis/nvcf-cli/internal/selfhosted/stack_namespaces.go new file mode 100644 index 0000000000..a6bf51426d --- /dev/null +++ b/src/clis/nvcf-cli/internal/selfhosted/stack_namespaces.go @@ -0,0 +1,107 @@ +/* +SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +SPDX-License-Identifier: Apache-2.0 + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package selfhosted + +import ( + "os" + "path/filepath" + "regexp" + "sort" + "strings" +) + +// helmfileNamespaceRE matches a literal `namespace: ` declaration on a +// helmfile release entry. +// +// Two deliberate restrictions, because a false positive here becomes a +// "delete this namespace" remediation: +// +// - Indentation is capped at 4 spaces, the release-entry level. Keys nested +// inside a release's `values:` block sit at 6 or more and must not be read +// as release namespaces. +// - The value must be a bare DNS label. A value containing a Go template +// action is operator-configurable (the gateway controller namespace is +// one), so its rendered value is not knowable from the source. +var helmfileNamespaceRE = regexp.MustCompile(`(?m)^ {0,4}namespace:\s*([a-z0-9][a-z0-9.-]*)\s*$`) + +// stackReleaseNamespaces returns the namespaces that helmfile releases deploy +// into for the stack rooted at stackDir, or nil when the stack is not readable. +// +// This is the authoritative answer to "which namespaces does the stack own", +// which matters because the stale-namespace check's remediation is to delete +// them: a hardcoded list that drifts either misses a real leftover or, worse, +// names a namespace the stack does not own. +// +// Callers fall back to the static list when this returns nil, which is the +// normal case for a released binary with no stack checkout alongside it. +func stackReleaseNamespaces(stackDir string) []string { + if stackDir == "" { + return nil + } + // helmfile accepts plain .yaml fragments as well as .yaml.gotmpl. + var entries []string + for _, pattern := range []string{"*.yaml.gotmpl", "*.yaml"} { + matches, err := filepath.Glob(filepath.Join(stackDir, "helmfile.d", pattern)) + if err != nil { + return nil + } + entries = append(entries, matches...) + } + if len(entries) == 0 { + return nil + } + + seen := make(map[string]bool) + for _, path := range entries { + body, err := os.ReadFile(path) + if err != nil { + // Bail out rather than deriving a partial list. A single + // unreadable fragment (a root-owned 0600 file left by "sudo + // helmfile", say) would otherwise silently drop a third of the + // namespaces, and the caller prefers any non-empty derived list + // over the complete static fallback. That turns a truncated scan + // into an affirmative all-clear. + return nil + } + for _, m := range helmfileNamespaceRE.FindAllStringSubmatch(string(body), -1) { + if ns := strings.TrimSpace(m[1]); ns != "" { + seen[ns] = true + } + } + } + if len(seen) == 0 { + return nil + } + + out := make([]string, 0, len(seen)) + for ns := range seen { + out = append(out, ns) + } + sort.Strings(out) + return out +} + +// resolveStackNamespaces returns the namespaces to probe for the given plane. +// It prefers what the stack actually declares and falls back to the static +// list when no stack is available. +func resolveStackNamespaces(stackDir string, fallback []string) []string { + // Union, not replace. The derived list follows the stack, but it cannot see + // namespaces behind a helmfiles: include, so on its own it can still be + // short. Merging keeps the static list as a floor. + return mergeNamespaces(fallback, stackReleaseNamespaces(stackDir)) +} diff --git a/src/clis/nvcf-cli/internal/selfhosted/stack_namespaces_test.go b/src/clis/nvcf-cli/internal/selfhosted/stack_namespaces_test.go new file mode 100644 index 0000000000..236e0102fd --- /dev/null +++ b/src/clis/nvcf-cli/internal/selfhosted/stack_namespaces_test.go @@ -0,0 +1,151 @@ +/* +SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +SPDX-License-Identifier: Apache-2.0 + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package selfhosted + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// writeStack lays out a minimal helmfile.d matching the real stack's shape. +func writeStack(t *testing.T, files map[string]string) string { + t.Helper() + dir := t.TempDir() + hd := filepath.Join(dir, "helmfile.d") + require.NoError(t, os.MkdirAll(hd, 0o755)) + for name, body := range files { + require.NoError(t, os.WriteFile(filepath.Join(hd, name), []byte(body), 0o600)) + } + return dir +} + +func TestStackReleaseNamespaces_ReadsLiteralNamespaces(t *testing.T) { + dir := writeStack(t, map[string]string{ + "01-dependencies.yaml.gotmpl": ` +releases: + - name: nats + namespace: nats-system + - name: cert-manager + namespace: cert-manager +`, + "02-core.yaml.gotmpl": ` +releases: + - name: api + namespace: nvcf + - name: ui + namespace: nvcf-ui +`, + }) + + got := stackReleaseNamespaces(dir) + assert.Equal(t, []string{"cert-manager", "nats-system", "nvcf", "nvcf-ui"}, got, + "namespaces must come from the stack, de-duplicated and sorted") +} + +// The gateway controller namespace is templated from a values key, so its +// rendered value is not knowable from the source. Including the raw template +// text would produce a garbage namespace to probe and, on a hit, to delete. +func TestStackReleaseNamespaces_SkipsTemplatedNamespaces(t *testing.T) { + dir := writeStack(t, map[string]string{ + "02-core.yaml.gotmpl": ` +releases: + - name: api + namespace: nvcf + - name: ingress + namespace: {{ .Values.ingress.gatewayApi.controllerNamespace }} +`, + }) + + got := stackReleaseNamespaces(dir) + assert.Equal(t, []string{"nvcf"}, got, "a templated namespace must not be probed") +} + +func TestResolveStackNamespaces_FallsBackWhenNoStack(t *testing.T) { + fallback := []string{"nvcf", "sis"} + + assert.Equal(t, fallback, resolveStackNamespaces("", fallback), + "no stack path must use the static list") + assert.Equal(t, fallback, resolveStackNamespaces(t.TempDir(), fallback), + "a directory with no helmfile.d must use the static list") +} + +// The derived list is additive, not a replacement: it cannot see namespaces +// behind a helmfiles: include, so on its own it can still be short. The static +// list stays as a floor. +func TestResolveStackNamespaces_UnionsStackWithStatic(t *testing.T) { + dir := writeStack(t, map[string]string{ + "01.yaml.gotmpl": "releases:\n - name: a\n namespace: from-stack\n", + }) + got := resolveStackNamespaces(dir, []string{"static"}) + assert.Contains(t, got, "from-stack", "the stack's namespaces must be picked up") + assert.Contains(t, got, "static", "the static list must remain a floor") +} + +// An unreadable fragment must not yield a truncated list that then overrides +// the complete static one, which would report an affirmative all-clear over a +// scan missing a third of its namespaces. +func TestStackReleaseNamespaces_UnreadableFragmentYieldsNothing(t *testing.T) { + dir := writeStack(t, map[string]string{ + "01.yaml.gotmpl": "releases:\n - name: a\n namespace: one\n", + "02.yaml.gotmpl": "releases:\n - name: b\n namespace: two\n", + }) + // A directory, not a chmod 000 file: root bypasses the permission bits, so + // a mode-based fixture passes locally and fails in CI, where tests run as + // root. os.ReadFile returns EISDIR for every UID, so the test means the + // same thing everywhere rather than being skipped where it matters most. + unreadable := filepath.Join(dir, "helmfile.d", "02.yaml.gotmpl") + require.NoError(t, os.Remove(unreadable)) + require.NoError(t, os.Mkdir(unreadable, 0o755)) + + assert.Nil(t, stackReleaseNamespaces(dir), + "a partial parse must not masquerade as the stack's full namespace set") +} + +// helmfile accepts plain .yaml fragments too. +func TestStackReleaseNamespaces_ReadsPlainYAMLFragments(t *testing.T) { + dir := writeStack(t, map[string]string{ + "01.yaml": "releases:\n - name: a\n namespace: plain-yaml\n", + }) + assert.Contains(t, stackReleaseNamespaces(dir), "plain-yaml") +} + +// A `namespace:` key nested inside a release's values block is chart +// configuration, not a namespace the stack owns. Reading it as one would add a +// probe target whose remediation is deletion. +func TestStackReleaseNamespaces_SkipsNestedValuesKeys(t *testing.T) { + dir := writeStack(t, map[string]string{ + "02-core.yaml.gotmpl": ` +releases: + - name: api + namespace: nvcf + values: + - serviceMonitor: + namespace: monitoring + - someChart: + namespace: kube-system +`, + }) + + got := stackReleaseNamespaces(dir) + assert.Equal(t, []string{"nvcf"}, got, + "only release-level namespaces may be probed; nested values keys must be ignored") +} diff --git a/src/clis/nvcf-cli/internal/selfhosted/stale_namespace.go b/src/clis/nvcf-cli/internal/selfhosted/stale_namespace.go new file mode 100644 index 0000000000..9e50b211b5 --- /dev/null +++ b/src/clis/nvcf-cli/internal/selfhosted/stale_namespace.go @@ -0,0 +1,265 @@ +/* +SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +SPDX-License-Identifier: Apache-2.0 + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package selfhosted + +import ( + "context" + "fmt" + "os" + "strings" + + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" +) + +// nvcfControlPlaneNamespaces lists namespaces that a helmfile release deploys +// into on the control-plane cluster, per deploy/stacks/self-managed/helmfile.d. +// Any of these that exist without an active Helm release, or that are stuck +// Terminating, are leftover from a failed or partial teardown. +// +// Only namespaces that actually host a release belong here: the remediation for +// a hit is "delete this namespace", so a namespace populated by something other +// than Helm (nvcf-backend, created at runtime by NVCA for worker pods) would be +// reported stale on a healthy cluster and deleting it would destroy live work. +// +// The gateway controller namespace is deliberately absent: the release templates +// it from .Values.ingress.gatewayApi.controllerNamespace, so hardcoding +// envoy-gateway-system would probe a namespace the stack may not own. +var nvcfControlPlaneNamespaces = []string{ + "api-keys", "cassandra-system", "cert-manager", "ess", + "nats-system", "nvcf", "nvcf-ui", "sis", "vault-system", +} + +// nvcfComputePlaneNamespaces lists namespaces that a helmfile release deploys +// into on the compute-plane cluster, per +// deploy/stacks/nvcf-compute-plane/helmfile.d. nvca-system is deliberately +// absent: it is operator-created and hosts no release. +var nvcfComputePlaneNamespaces = []string{ + "dynamo-system", "grove-system", "kai-scheduler", "nvca-operator", +} + +// StaleNamespace describes a single NVCF stack namespace that appears to be a +// leftover from a failed or partial teardown. +type StaleNamespace struct { + Name string + Reason string // human-readable cause: "stuck Terminating" or "no Helm release" +} + +// StaleNamespaceProber inspects the given namespaces and returns those that +// appear stale. The probe is read-only; it never deletes or modifies anything. +// A non-nil error means the cluster could not be contacted; the returned slice +// may be a partial result. +type StaleNamespaceProber func(ctx context.Context, kubeContext string, namespaces []string) ([]StaleNamespace, error) + +// NewStaleNamespaceProber returns a StaleNamespaceProber backed by client-go. +// Chart-independent: works before any Helm release exists, and uses the +// operator's kubeconfig context to talk to the target cluster. +func NewStaleNamespaceProber() StaleNamespaceProber { + return func(ctx context.Context, kubeContext string, namespaces []string) ([]StaleNamespace, error) { + restCfg, err := loadKubeConfig(kubeContext) + if err != nil { + return nil, fmt.Errorf("building kubeconfig: %w", err) + } + client, err := kubernetes.NewForConfig(restCfg) + if err != nil { + return nil, fmt.Errorf("building kubernetes client: %w", err) + } + return probeStaleNamespaces(ctx, client, namespaces) + } +} + +// probeStaleNamespaces is the testable core that accepts a kubernetes.Interface +// so callers can inject fake.NewSimpleClientset in unit tests. +// +// A namespace is considered stale when either: +// - its DeletionTimestamp is set or its phase is Terminating (finalizer +// deadlock; it will never complete without operator intervention), or +// - it exists but holds no active Helm release (empty shell left by a partial +// helm uninstall or a failed teardown that cleaned the release but not the +// namespace). +// +// Helm 3 marks each release secret with the label owner=helm; absence of any +// such secret means no live Helm release occupies the namespace. +const ( + // helmReleaseListPageSize bounds each page of the owner=helm scan. A + // namespace holds at most a handful of release objects, so this is only a + // ceiling on how much is pulled per round trip while paging past + // non-matching objects. + helmReleaseListPageSize = 100 + + // helmReleaseListMaxPages stops the scan even if the server keeps handing + // back a Continue token. At the page size above this covers 100k objects in + // one namespace, far past anything real, and guarantees termination. + helmReleaseListMaxPages = 1000 +) + +// helmReleaseExists reports whether any owner=helm object exists, paging until +// it finds one or the server reports no more results. +// +// A single page with Limit set is not a valid existence test: the apiserver +// applies the label selector after paging, so a page can legitimately return +// zero items alongside a Continue token. A namespace like nvcf holds dozens of +// ServiceAccount tokens and TLS secrets that sort before sh.helm.release.v1.*, +// so the first page is routinely empty on a perfectly healthy install. +func helmReleaseExists( + list func(metav1.ListOptions) (count int, cont string, err error), +) (bool, error) { + opts := metav1.ListOptions{LabelSelector: "owner=helm", Limit: helmReleaseListPageSize} + for page := 0; page < helmReleaseListMaxPages; page++ { + count, cont, err := list(opts) + if err != nil { + return false, err + } + if count > 0 { + return true, nil + } + if cont == "" { + return false, nil + } + opts.Continue = cont + } + return false, fmt.Errorf("gave up after %d pages scanning for Helm releases", helmReleaseListMaxPages) +} + +// hasPods reports whether namespace ns runs any pod. A read error reports +// false, so the namespace is still flagged. +func hasPods(ctx context.Context, client kubernetes.Interface, ns string) bool { + pods, err := client.CoreV1().Pods(ns).List(ctx, metav1.ListOptions{Limit: 1}) + return err == nil && len(pods.Items) > 0 +} + +// onlyInstallPreparation reports whether namespace ns holds a registry pull +// Secret and nothing else an operator or an install would create: only the +// tokens and CA ConfigMap Kubernetes adds to every namespace, and no PVCs. An +// empty namespace, or a read error, reports false, so it is still flagged. +func onlyInstallPreparation(ctx context.Context, client kubernetes.Interface, ns string) bool { + pvcs, err := client.CoreV1().PersistentVolumeClaims(ns).List(ctx, metav1.ListOptions{Limit: 1}) + if err != nil || len(pvcs.Items) > 0 { + return false + } + secrets, err := client.CoreV1().Secrets(ns).List(ctx, metav1.ListOptions{}) + if err != nil { + return false + } + pullSecrets := 0 + for i := range secrets.Items { + switch secrets.Items[i].Type { + case corev1.SecretTypeDockerConfigJson, corev1.SecretTypeDockercfg: + pullSecrets++ + case corev1.SecretTypeServiceAccountToken: + default: + return false + } + } + if pullSecrets == 0 { + return false + } + cms, err := client.CoreV1().ConfigMaps(ns).List(ctx, metav1.ListOptions{}) + if err != nil { + return false + } + for i := range cms.Items { + if cms.Items[i].Name != "kube-root-ca.crt" { + return false + } + } + return true +} + +// helmReleasesAreInCluster reports whether Helm stores release state as +// in-cluster Secrets or ConfigMaps, the drivers this probe can see. +func helmReleasesAreInCluster() bool { + return !strings.EqualFold(strings.TrimSpace(os.Getenv("HELM_DRIVER")), "sql") +} + +func probeStaleNamespaces(ctx context.Context, client kubernetes.Interface, namespaces []string) ([]StaleNamespace, error) { + var stale []StaleNamespace + // noRelease is held back until we know the Helm storage driver keeps its + // state in-cluster at all; see the gate below. + var noRelease []string + for _, name := range namespaces { + ns, err := client.CoreV1().Namespaces().Get(ctx, name, metav1.GetOptions{}) + if err != nil { + if apierrors.IsNotFound(err) { + continue // absent = healthy; the check only fires on unexpected presence + } + return stale, fmt.Errorf("get namespace %s: %w", name, err) + } + + if ns.DeletionTimestamp != nil || ns.Status.Phase == corev1.NamespaceTerminating { + stale = append(stale, StaleNamespace{Name: name, Reason: "stuck Terminating"}) + continue + } + + // Check Secrets first (the default Helm storage driver), then ConfigMaps + // for HELM_DRIVER=configmap clusters. Both label release objects + // owner=helm. + found, err := helmReleaseExists( + func(opts metav1.ListOptions) (int, string, error) { + l, lerr := client.CoreV1().Secrets(name).List(ctx, opts) + if lerr != nil { + return 0, "", lerr + } + return len(l.Items), l.Continue, nil + }) + if err != nil { + return stale, fmt.Errorf("list Helm secrets in %s: %w", name, err) + } + if found { + continue // healthy: active Helm release found via the secret driver + } + found, err = helmReleaseExists( + func(opts metav1.ListOptions) (int, string, error) { + l, lerr := client.CoreV1().ConfigMaps(name).List(ctx, opts) + if lerr != nil { + return 0, "", lerr + } + return len(l.Items), l.Continue, nil + }) + if err != nil { + return stale, fmt.Errorf("list Helm configmaps in %s: %w", name, err) + } + if found { + continue + } + // No release is not yet a leftover. The documented install pre-creates + // the namespaces holding only a registry pull Secret, so before the + // first install none has a release; and an install rendered with + // `helm template` (Argo CD) never records one but runs pods. What + // `down` leaves behind runs nothing and still holds data (PVCs, + // Secrets, ConfigMaps). + if hasPods(ctx, client, name) || onlyInstallPreparation(ctx, client, name) { + continue + } + noRelease = append(noRelease, name) + } + + // Trust the "no Helm release" signal unless Helm keeps its release state + // outside the cluster. HELM_DRIVER=sql stores it in a database, so every + // namespace of a healthy install would look stale. Inferring the driver + // from "no owner=helm object anywhere" hid exactly the state `down` + // leaves behind: every release destroyed, every namespace and PVC kept. + if helmReleasesAreInCluster() { + for _, name := range noRelease { + stale = append(stale, StaleNamespace{Name: name, Reason: "no Helm release"}) + } + } + return stale, nil +} diff --git a/src/clis/nvcf-cli/internal/selfhosted/stale_namespace_test.go b/src/clis/nvcf-cli/internal/selfhosted/stale_namespace_test.go new file mode 100644 index 0000000000..ec7c50a145 --- /dev/null +++ b/src/clis/nvcf-cli/internal/selfhosted/stale_namespace_test.go @@ -0,0 +1,536 @@ +/* +SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved. +SPDX-License-Identifier: Apache-2.0 + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package selfhosted + +import ( + "context" + "fmt" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/kubernetes/fake" + ktesting "k8s.io/client-go/testing" +) + +// -- probeStaleNamespaces -- + +func TestProbeStaleNamespaces_AbsentIsHealthy(t *testing.T) { + // A namespace that doesn't exist is not stale - it has simply never been + // created or was already fully deleted. + client := fake.NewSimpleClientset() + stale, err := probeStaleNamespaces(context.Background(), client, []string{"nvcf", "sis"}) + require.NoError(t, err) + assert.Empty(t, stale, "absent namespaces must not be reported as stale") +} + +func TestProbeStaleNamespaces_TerminatingIsByDeletionTimestamp(t *testing.T) { + now := metav1.Now() + client := fake.NewSimpleClientset(&corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{Name: "nvcf", DeletionTimestamp: &now}, + Status: corev1.NamespaceStatus{Phase: corev1.NamespaceActive}, + }) + stale, err := probeStaleNamespaces(context.Background(), client, []string{"nvcf"}) + require.NoError(t, err) + require.Len(t, stale, 1) + assert.Equal(t, "nvcf", stale[0].Name) + assert.Contains(t, stale[0].Reason, "Terminating") +} + +func TestProbeStaleNamespaces_TerminatingIsByPhase(t *testing.T) { + client := fake.NewSimpleClientset(&corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{Name: "sis"}, + Status: corev1.NamespaceStatus{Phase: corev1.NamespaceTerminating}, + }) + stale, err := probeStaleNamespaces(context.Background(), client, []string{"sis"}) + require.NoError(t, err) + require.Len(t, stale, 1) + assert.Equal(t, "sis", stale[0].Name) + assert.Contains(t, stale[0].Reason, "Terminating") +} + +func TestProbeStaleNamespaces_EmptyShellNoHelmSecrets(t *testing.T) { + // Namespace exists and is Active but holds no Helm release secrets -> + // leftover empty shell from a partial helm uninstall. The second namespace + // carries a release and is not reported. + client := fake.NewSimpleClientset( + &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{Name: "nvcf"}, + Status: corev1.NamespaceStatus{Phase: corev1.NamespaceActive}, + }, + &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{Name: "sis"}, + Status: corev1.NamespaceStatus{Phase: corev1.NamespaceActive}, + }, + &corev1.Secret{ObjectMeta: metav1.ObjectMeta{ + Name: "sh.helm.release.v1.sis.v1", + Namespace: "sis", + Labels: map[string]string{"owner": "helm", "name": "sis", "status": "deployed"}, + }}, + ) + stale, err := probeStaleNamespaces(context.Background(), client, []string{"nvcf", "sis"}) + require.NoError(t, err) + require.Len(t, stale, 1) + assert.Equal(t, "nvcf", stale[0].Name) + assert.Contains(t, stale[0].Reason, "Helm release") +} + +func TestProbeStaleNamespaces_HealthyReleaseNotStale(t *testing.T) { + // Namespace exists and carries an owner=helm secret -> active Helm release, + // not stale. + client := fake.NewSimpleClientset( + &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{Name: "nvcf"}, + Status: corev1.NamespaceStatus{Phase: corev1.NamespaceActive}, + }, + &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "sh.helm.release.v1.nvcf.v1", + Namespace: "nvcf", + Labels: map[string]string{"owner": "helm", "name": "nvcf", "status": "deployed"}, + }, + }, + ) + stale, err := probeStaleNamespaces(context.Background(), client, []string{"nvcf"}) + require.NoError(t, err) + assert.Empty(t, stale, "namespace with an active Helm release must not be stale") +} + +func TestProbeStaleNamespaces_HealthyReleaseConfigMapDriverNotStale(t *testing.T) { + // HELM_DRIVER=configmap stores release objects as ConfigMaps with owner=helm. + // A namespace with an owner=helm ConfigMap and no Helm Secret must not be + // reported as stale. + client := fake.NewSimpleClientset( + &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{Name: "nvcf"}, + Status: corev1.NamespaceStatus{Phase: corev1.NamespaceActive}, + }, + &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "nvcf.v1", + Namespace: "nvcf", + Labels: map[string]string{"owner": "helm", "name": "nvcf", "status": "deployed"}, + }, + }, + ) + stale, err := probeStaleNamespaces(context.Background(), client, []string{"nvcf"}) + require.NoError(t, err) + assert.Empty(t, stale, "namespace with an owner=helm ConfigMap (configmap driver) must not be stale") +} + +func TestProbeStaleNamespaces_MixedNamespaces(t *testing.T) { + // One absent, one healthy, one terminating, one empty shell. + now := metav1.Now() + client := fake.NewSimpleClientset( + // "sis" - healthy with a Helm release + &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{Name: "sis"}, + Status: corev1.NamespaceStatus{Phase: corev1.NamespaceActive}, + }, + &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "sh.helm.release.v1.sis.v1", Namespace: "sis", + Labels: map[string]string{"owner": "helm"}, + }, + }, + // "nvcf" - stuck terminating + &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{Name: "nvcf", DeletionTimestamp: &now}, + Status: corev1.NamespaceStatus{Phase: corev1.NamespaceTerminating}, + }, + // "api-keys" - empty shell + &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{Name: "api-keys"}, + Status: corev1.NamespaceStatus{Phase: corev1.NamespaceActive}, + }, + // "cassandra-system" - absent (not present in fake) + ) + + namespaces := []string{"cassandra-system", "sis", "nvcf", "api-keys"} + stale, err := probeStaleNamespaces(context.Background(), client, namespaces) + require.NoError(t, err) + require.Len(t, stale, 2, "only nvcf (terminating) and api-keys (empty shell) should be stale") + + staleNames := make(map[string]string, 2) + for _, s := range stale { + staleNames[s.Name] = s.Reason + } + assert.Contains(t, staleNames, "nvcf") + assert.Contains(t, staleNames["nvcf"], "Terminating") + assert.Contains(t, staleNames, "api-keys") + assert.Contains(t, staleNames["api-keys"], "Helm release") +} + +// -- staleNamespaceCheck binaryCheckSpec -- + +// pinCurrentKubeContext keeps hint assertions independent of the developer's +// kubeconfig: without it, a machine with a current-context makes every hint +// carry a --context flag the test did not ask for. +func pinCurrentKubeContext(t *testing.T, name string) { + t.Helper() + prev := currentKubeContextNameFn + currentKubeContextNameFn = func() string { return name } + t.Cleanup(func() { currentKubeContextNameFn = prev }) +} + +func TestStaleNamespaceCheck_ProberErrorDegradestoWarning(t *testing.T) { + pinCurrentKubeContext(t, "") + // A prober that cannot contact the cluster must not fail the overall check + // at error severity - it would produce false failures on transient network + // issues or misconfigured kubeconfigs. + prober := func(_ context.Context, _ string, _ []string) ([]StaleNamespace, error) { + return nil, fmt.Errorf("cluster unreachable") + } + r := staleNamespaceCheck(prober, "", []string{"nvcf"}).Run(context.Background()) + assert.False(t, r.Passed) + assert.Equal(t, "warning", r.Severity, + "prober errors must degrade to warning so transient failures do not block the operator") + assert.Contains(t, r.Message, "cluster unreachable") +} + +func TestStaleNamespaceCheck_StaleIsError(t *testing.T) { + pinCurrentKubeContext(t, "") + // A successfully detected stale namespace must fail at error severity so + // anyFailed trips the non-zero exit code. + prober := func(_ context.Context, _ string, _ []string) ([]StaleNamespace, error) { + return []StaleNamespace{{Name: "nvcf", Reason: "stuck Terminating"}}, nil + } + r := staleNamespaceCheck(prober, "", []string{"nvcf"}).Run(context.Background()) + assert.False(t, r.Passed) + assert.Equal(t, "error", r.Severity, + "detected stale namespaces must use error severity so the exit code is non-zero") + assert.Contains(t, r.Message, "nvcf") + assert.Contains(t, r.Message, "/api/v1/namespaces/nvcf/finalize", + "the remediation must use the finalize subresource; a plain patch is silently reverted") + inspect := strings.Index(r.Message, "xargs -n1 kubectl get -n nvcf") + force := strings.Index(r.Message, "/finalize") + require.GreaterOrEqual(t, inspect, 0) + assert.Less(t, inspect, force, "inspecting the namespace comes before force-clearing its finalizers") +} + +func TestStaleNamespaceCheck_CleanPasses(t *testing.T) { + pinCurrentKubeContext(t, "") + prober := func(_ context.Context, _ string, _ []string) ([]StaleNamespace, error) { + return nil, nil + } + r := staleNamespaceCheck(prober, "", []string{"nvcf", "sis"}).Run(context.Background()) + assert.True(t, r.Passed) + assert.Equal(t, SeverityInfo, r.Severity, "a clean pass must not carry the error severity it starts with") +} + +func TestStaleNamespaceCheck_MessageNamesAllStaleNamespaces(t *testing.T) { + pinCurrentKubeContext(t, "") + // The remediation command must name every stale namespace so the operator + // can copy-paste it without having to cross-reference the check output. + prober := func(_ context.Context, _ string, _ []string) ([]StaleNamespace, error) { + return []StaleNamespace{ + {Name: "nvcf", Reason: "stuck Terminating"}, + {Name: "api-keys", Reason: "no Helm release"}, + }, nil + } + r := staleNamespaceCheck(prober, "", []string{"nvcf", "api-keys"}).Run(context.Background()) + assert.Contains(t, r.Message, "nvcf") + assert.Contains(t, r.Message, "api-keys") + assert.Contains(t, r.Message, "/api/v1/namespaces/nvcf/finalize", + "the remediation must use the finalize subresource; a plain patch is silently reverted") + assert.Contains(t, r.Message, "xargs -n1 kubectl get -n api-keys --show-kind --ignore-not-found") + assert.NotContains(t, r.Message, "get all", "get all omits PVCs, Secrets and custom resources") +} + +// A namespace on a real install holds many non-Helm Secrets (ServiceAccount +// tokens, TLS, pull secrets) that sort before sh.helm.release.v1.*. The +// apiserver applies the label selector after paging, so the first page can +// legitimately come back empty with a Continue token. Treating that as "no +// release" reports a live namespace stale and tells the operator to delete it. +func TestProbeStaleNamespaces_PagesPastNonMatchingObjects(t *testing.T) { + client := fake.NewSimpleClientset(&corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{Name: "nvcf"}, + Status: corev1.NamespaceStatus{Phase: corev1.NamespaceActive}, + }) + + // First page: empty with a Continue token. Second page: the release secret. + call := 0 + client.PrependReactor("list", "secrets", func(_ ktesting.Action) (bool, runtime.Object, error) { + call++ + if call == 1 { + return true, &corev1.SecretList{ + ListMeta: metav1.ListMeta{Continue: "next-page-token"}, + }, nil + } + return true, &corev1.SecretList{Items: []corev1.Secret{{ + ObjectMeta: metav1.ObjectMeta{ + Name: "sh.helm.release.v1.nvcf.v1", Namespace: "nvcf", + Labels: map[string]string{"owner": "helm"}, + }, + }}}, nil + }) + + stale, err := probeStaleNamespaces(context.Background(), client, []string{"nvcf"}) + require.NoError(t, err) + assert.Empty(t, stale, + "an empty first page with a Continue token must not be read as 'no Helm release'") + assert.Greater(t, call, 1, "the probe must follow the Continue token") +} + +// The remediation for a hit is "delete this namespace", so the list must only +// contain namespaces a helmfile release actually owns. nvcf-backend is created +// at runtime by NVCA and holds live worker pods. +func TestControlPlaneNamespaceList_ExcludesRuntimeOwnedNamespaces(t *testing.T) { + for _, ns := range []string{"nvcf-backend", "openbao-system"} { + assert.NotContains(t, nvcfControlPlaneNamespaces, ns, + "%s hosts no Helm release; probing it yields a destructive false positive", ns) + } + for _, ns := range []string{"cert-manager", "nvcf-ui"} { + assert.Contains(t, nvcfControlPlaneNamespaces, ns, + "%s is a real stack namespace and must be probed", ns) + } + assert.NotContains(t, nvcfComputePlaneNamespaces, "nvca-system", + "nvca-system is operator-created and hosts no release") +} + +// TestStaleNamespaceCheck_HintsPinTheProbedContext guards the remediation +// hints in split mode. The two callers probe different clusters, so a bare +// kubectl command in a hint runs against whatever context is current, and +// these hints delete namespaces. +func TestStaleNamespaceCheck_HintsPinTheProbedContext(t *testing.T) { + pinCurrentKubeContext(t, "") + prober := func(_ context.Context, _ string, _ []string) ([]StaleNamespace, error) { + return []StaleNamespace{ + {Name: "nvcf", Reason: "stuck Terminating"}, + {Name: "api-keys", Reason: "no Helm release"}, + }, nil + } + r := staleNamespaceCheck(prober, "cp-ctx", []string{"nvcf", "api-keys"}).Run(context.Background()) + + // Every kubectl invocation, including the one piped into xargs, has to + // carry the flag: one unpinned command is enough to hit the wrong cluster. + for _, cmd := range strings.Split(r.Message, "kubectl ")[1:] { + assert.True(t, strings.HasPrefix(cmd, "--context cp-ctx "), + "unpinned kubectl invocation in hint: kubectl %s", cmd) + } + assert.Contains(t, r.Message, "xargs -n1 kubectl --context cp-ctx get -n api-keys") +} + +func TestStaleNamespaceCheck_HintsQuoteTheContext(t *testing.T) { + pinCurrentKubeContext(t, "") + prober := func(_ context.Context, _ string, _ []string) ([]StaleNamespace, error) { + return []StaleNamespace{{Name: "nvcf", Reason: "no Helm release"}}, nil + } + r := staleNamespaceCheck(prober, "my ctx", []string{"nvcf"}).Run(context.Background()) + assert.Contains(t, r.Message, "--context 'my ctx' get -n nvcf", + "a context name with a space must be quoted so the pasted command does not split it") +} + +// TestStaleNamespaceCheck_HintsSubstituteTheNamespace guards against a bare +// `` placeholder in a pasteable command: the shell reads `<` and `>` as +// redirection, so the command fails before kubectl runs. +func TestStaleNamespaceCheck_HintsSubstituteTheNamespace(t *testing.T) { + pinCurrentKubeContext(t, "") + prober := func(_ context.Context, _ string, _ []string) ([]StaleNamespace, error) { + return []StaleNamespace{ + {Name: "nvcf", Reason: "stuck Terminating"}, + {Name: "sis", Reason: "stuck Terminating"}, + }, nil + } + r := staleNamespaceCheck(prober, "", []string{"nvcf", "sis"}).Run(context.Background()) + + assert.NotContains(t, r.Message, "", + "a shell-redirection placeholder must not appear in a copy-pasteable command") + assert.Contains(t, r.Message, "xargs -n1 kubectl get -n nvcf ") + assert.Contains(t, r.Message, "xargs -n1 kubectl get -n sis ") +} + +// TestStaleNamespaceCheck_HintsNameTheCurrentContext covers the single-cluster +// path. The probe followed the current-context, so the hint has to name it: +// the operator can switch context between reading this and pasting it, and +// these commands delete namespaces. +func TestStaleNamespaceCheck_HintsNameTheCurrentContext(t *testing.T) { + pinCurrentKubeContext(t, "k3d-local") + prober := func(_ context.Context, _ string, _ []string) ([]StaleNamespace, error) { + return []StaleNamespace{{Name: "nvcf", Reason: "no Helm release"}}, nil + } + r := staleNamespaceCheck(prober, "", []string{"nvcf"}).Run(context.Background()) + assert.Contains(t, r.Message, "xargs -n1 kubectl --context k3d-local get -n nvcf") +} + +// An unreadable kubeconfig must not break the hint; it degrades to no flag. +func TestStaleNamespaceCheck_HintsOmitUnknownContext(t *testing.T) { + pinCurrentKubeContext(t, "") + prober := func(_ context.Context, _ string, _ []string) ([]StaleNamespace, error) { + return []StaleNamespace{{Name: "nvcf", Reason: "no Helm release"}}, nil + } + r := staleNamespaceCheck(prober, "", []string{"nvcf"}).Run(context.Background()) + assert.Contains(t, r.Message, "xargs -n1 kubectl get -n nvcf") +} + +// The probe must run against the same context the hints name. Resolving the +// current-context after probing leaves a window where it changes in between, +// which would have the delete hints target a cluster that was never read. +func TestStaleNamespaceCheck_ProbesTheResolvedContext(t *testing.T) { + pinCurrentKubeContext(t, "k3d-local") + var probed string + prober := func(_ context.Context, kctx string, _ []string) ([]StaleNamespace, error) { + probed = kctx + return nil, nil + } + staleNamespaceCheck(prober, "", []string{"nvcf"}).Run(context.Background()) + assert.Equal(t, "k3d-local", probed, + "the probe must target the resolved context, not whatever is current when it runs") +} + +// `down` destroys every release but keeps the namespaces and their PVCs, so no +// owner=helm object exists anywhere. With the default driver that is exactly +// the stale state to report; a reinstall would silently reattach the old +// Cassandra and OpenBao volumes. +func TestProbeStaleNamespaces_AfterDownReportsEveryNamespace(t *testing.T) { + t.Setenv("HELM_DRIVER", "") + client := fake.NewSimpleClientset( + &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "nvcf"}}, + &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "sis"}}, + ) + got, err := probeStaleNamespaces(context.Background(), client, []string{"nvcf", "sis"}) + require.NoError(t, err) + assert.Len(t, got, 2) +} + +// HELM_DRIVER=sql keeps release state in a database, so the absence of an +// in-cluster release object says nothing and must not be reported. +func TestProbeStaleNamespaces_SQLDriverSkipsTheNoReleaseSignal(t *testing.T) { + t.Setenv("HELM_DRIVER", "sql") + client := fake.NewSimpleClientset( + &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "nvcf"}}, + ) + got, err := probeStaleNamespaces(context.Background(), client, []string{"nvcf"}) + require.NoError(t, err) + assert.Empty(t, got) +} + +// A namespace stuck Terminating is still reported even when no Helm release +// object exists anywhere: that signal does not depend on the storage driver. +func TestProbeStaleNamespaces_TerminatingReportedWithoutHelmObjects(t *testing.T) { + client := fake.NewSimpleClientset( + &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{Name: "nvcf"}, + Status: corev1.NamespaceStatus{Phase: corev1.NamespaceTerminating}, + }, + ) + got, err := probeStaleNamespaces(context.Background(), client, []string{"nvcf"}) + require.NoError(t, err) + require.Len(t, got, 1) + assert.Equal(t, "stuck Terminating", got[0].Reason) +} + +// A conditionally-disabled component installed the upstream way is healthy. +// Reporting it at error severity turns a supported shape into exit 2, and the +// old remediation would have destroyed every Certificate in the cluster. +func TestStaleNamespaceCheck_NoHelmReleaseWarnsAndDoesNotSuggestDelete(t *testing.T) { + pinCurrentKubeContext(t, "") + prober := func(_ context.Context, _ string, _ []string) ([]StaleNamespace, error) { + return []StaleNamespace{{Name: "cert-manager", Reason: "no Helm release"}}, nil + } + r := staleNamespaceCheck(prober, "", []string{"cert-manager"}).Run(context.Background()) + + assert.Equal(t, SeverityWarning, r.Severity, + "a namespace with no Helm release must not fail the run") + assert.NotContains(t, r.Message, "delete namespace", + "the remediation must not destroy a namespace the operator may own") +} + +// A namespace stuck Terminating does block the run. +func TestStaleNamespaceCheck_TerminatingIsStillAnError(t *testing.T) { + pinCurrentKubeContext(t, "") + prober := func(_ context.Context, _ string, _ []string) ([]StaleNamespace, error) { + return []StaleNamespace{{Name: "nvcf", Reason: "stuck Terminating"}}, nil + } + r := staleNamespaceCheck(prober, "", []string{"nvcf"}).Run(context.Background()) + assert.Equal(t, SeverityError, r.Severity) +} + +// kubectl keeps only the last -n it is given, so a joined command inspects one +// namespace and reports the rest as empty, which is the opposite of what an +// operator deciding whether to delete them needs. +func TestStaleNamespaceCheck_HintsOneCommandPerNamespace(t *testing.T) { + pinCurrentKubeContext(t, "") + prober := func(_ context.Context, _ string, _ []string) ([]StaleNamespace, error) { + return []StaleNamespace{ + {Name: "api-keys", Reason: "no Helm release"}, + {Name: "ess", Reason: "no Helm release"}, + }, nil + } + r := staleNamespaceCheck(prober, "", []string{"api-keys", "ess"}).Run(context.Background()) + + assert.NotContains(t, r.Message, "-n api-keys -n ess", + "a joined namespace list silently inspects only the last one") + assert.Contains(t, r.Message, "get -n api-keys --show-kind") + assert.Contains(t, r.Message, "get -n ess --show-kind") +} + +// The documented install pre-creates each namespace holding only a registry +// pull Secret, so before the first install none has a release. That is not a +// leftover and must not be flagged, while a namespace that still holds data, +// as `down` leaves them, is. +func TestProbeStaleNamespaces_PreCreatedNamespaceIsNotStale(t *testing.T) { + ns := func(name string) *corev1.Namespace { + return &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: name}, + Status: corev1.NamespaceStatus{Phase: corev1.NamespaceActive}} + } + pull := func(ns string) *corev1.Secret { + return &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: "nvcr-pull-secret", Namespace: ns}, + Type: corev1.SecretTypeDockerConfigJson} + } + client := fake.NewSimpleClientset( + ns("nvcf"), pull("nvcf"), + &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: "kube-root-ca.crt", Namespace: "nvcf"}}, + ns("cassandra-system"), pull("cassandra-system"), + &corev1.PersistentVolumeClaim{ObjectMeta: metav1.ObjectMeta{Name: "data-cassandra-0", Namespace: "cassandra-system"}}, + ns("vault-system"), pull("vault-system"), + &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: "vault-unseal", Namespace: "vault-system"}, + Type: corev1.SecretTypeOpaque}, + ns("sis"), pull("sis"), + &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: "sis-config", Namespace: "sis"}}, + ) + stale, err := probeStaleNamespaces(context.Background(), client, + []string{"nvcf", "cassandra-system", "vault-system", "sis"}) + require.NoError(t, err) + var names []string + for _, s := range stale { + names = append(names, s.Name) + } + assert.ElementsMatch(t, []string{"cassandra-system", "vault-system", "sis"}, names, + "only the namespaces that still hold data") +} + +// An install rendered with `helm template` (Argo CD) records no Helm release +// but runs pods. That is a live install, not a leftover. +func TestProbeStaleNamespaces_NamespaceRunningPodsIsNotStale(t *testing.T) { + client := fake.NewSimpleClientset( + &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "nats-system"}, + Status: corev1.NamespaceStatus{Phase: corev1.NamespaceActive}}, + &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "nats-0", Namespace: "nats-system"}}, + &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: "nats-auth", Namespace: "nats-system"}, + Type: corev1.SecretTypeOpaque}, + ) + stale, err := probeStaleNamespaces(context.Background(), client, []string{"nats-system"}) + require.NoError(t, err) + assert.Empty(t, stale) +} diff --git a/src/clis/nvcf-cli/internal/selfhosted/teardown/drain_test.go b/src/clis/nvcf-cli/internal/selfhosted/teardown/drain_test.go index 3452e2724d..a8cb5c70c1 100644 --- a/src/clis/nvcf-cli/internal/selfhosted/teardown/drain_test.go +++ b/src/clis/nvcf-cli/internal/selfhosted/teardown/drain_test.go @@ -34,7 +34,7 @@ import ( type fakeSIS struct { deployments []ActiveDeployment removeErr error - removeCalled []string // IDs passed to RemoveDeployment + removeCalled []string // IDs passed to RemoveDeployment statusResults map[string]string // ID → state string statusCallCnt int32 } @@ -73,7 +73,7 @@ func (s *recordSink) Close() error { return nil } type discardSink struct{} func (d *discardSink) Emit(_ context.Context, _ progress.Event) error { return nil } -func (d *discardSink) Close() error { return nil } +func (d *discardSink) Close() error { return nil } // TestDrain_NoActiveDeploymentsIsNoop: empty list → returns nil, no events. func TestDrain_NoActiveDeploymentsIsNoop(t *testing.T) { diff --git a/src/clis/nvcf-cli/internal/selfhosted/validatortag.go b/src/clis/nvcf-cli/internal/selfhosted/validatortag.go index f6750bbf35..ea42963607 100644 --- a/src/clis/nvcf-cli/internal/selfhosted/validatortag.go +++ b/src/clis/nvcf-cli/internal/selfhosted/validatortag.go @@ -18,13 +18,17 @@ limitations under the License. package selfhosted import ( + "bytes" "context" "encoding/base64" "encoding/json" "fmt" "io" + "net" "net/http" + "net/url" "os" + "os/exec" "path/filepath" "regexp" "sort" @@ -90,6 +94,16 @@ func ResolveLatestValidatorTag(ctx context.Context, baseImage string) (string, b return fmt.Sprintf("%s/%s:%s", registry, repo, best), true } +// ImageRefIsPinned reports whether ref names a tag or a digest. An unpinned +// reference is pulled as :latest, which a registry need not have. +func ImageRefIsPinned(ref string) bool { + ref = strings.TrimSpace(ref) + if strings.Contains(ref, "@") { + return true + } + return strings.Contains(ref[strings.LastIndex(ref, "/")+1:], ":") +} + // parseImageRef splits "registry/repo:tag" or "registry/repo@digest" into // its parts. The registry must contain a '.' or ':' to distinguish a // real hostname from a Docker Hub library shorthand. Returns ok=false on @@ -132,11 +146,11 @@ func fetchValidatorTags(ctx context.Context, registry, repo string) ([]string, e return doc.Tags, nil } -func fetchWithBearer(ctx context.Context, url, registry, repo string) ([]byte, error) { - client := &http.Client{Timeout: validatorTagFetchTimeout} +func fetchWithBearer(ctx context.Context, rawURL, registry, repo string) ([]byte, error) { + client := &http.Client{Timeout: validatorTagFetchTimeout, CheckRedirect: refuseInsecureRedirect} // First attempt without auth so anonymous-pullable registries work. - req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, rawURL, nil) if err != nil { return nil, err } @@ -152,16 +166,19 @@ func fetchWithBearer(ctx context.Context, url, registry, repo string) ([]byte, e resp.Body.Close() return nil, fmt.Errorf("registry returned %s", resp.Status) } + // Capture the auth challenge before closing. + wwwAuth := selectAuthChallenge(resp.Header.Values("Www-Authenticate")) resp.Body.Close() - // Bearer-token exchange. Realm and scope come from the Www-Authenticate - // header; for NGC the realm is /proxy_auth and scope is repository::pull. - token, err := exchangeBearerToken(ctx, client, registry, repo) + // Generic OCI Bearer-token exchange: uses the realm/service/scope from + // the WWW-Authenticate header so any OCI-compliant registry works, not + // just NGC. Falls back to NGC's /proxy_auth when the header is absent. + token, err := exchangeBearerToken(ctx, client, registry, repo, wwwAuth) if err != nil { return nil, err } - req, err = http.NewRequestWithContext(ctx, http.MethodGet, url, nil) + req, err = http.NewRequestWithContext(ctx, http.MethodGet, rawURL, nil) if err != nil { return nil, err } @@ -177,21 +194,158 @@ func fetchWithBearer(ctx context.Context, url, registry, repo string) ([]byte, e return io.ReadAll(resp.Body) } -// exchangeBearerToken does the NGC token exchange using credentials -// resolved from ~/.docker/config.json or NGC env vars. +// refuseInsecureRedirect rejects any redirect hop that is not https. // -// NGC-specific: this constructs the token URL using NGC's /proxy_auth -// realm rather than parsing the Www-Authenticate header from the 401 -// response (the generic OAuth2 distribution flow). Non-NGC registries -// (GCR, ECR, GHCR, Harbor, etc.) will return a non-200 here and the -// caller falls back to using the configured image reference as-is. -// Acceptable for v1 since the validator image only ships from NGC. -func exchangeBearerToken(ctx context.Context, client *http.Client, registry, repo string) (string, error) { +// Go strips the Authorization header only when a redirect changes host, and +// that comparison ignores the scheme. Without this, an https realm that 302s to +// http on the same host would re-send the operator's credentials in cleartext, +// defeating the https-only realm check a few lines below. +func refuseInsecureRedirect(req *http.Request, via []*http.Request) error { + if req.URL.Scheme != "https" { + return fmt.Errorf("refusing redirect to non-https URL %q", req.URL.Redacted()) + } + if len(via) >= 10 { + return fmt.Errorf("stopped after %d redirects", len(via)) + } + return nil +} + +// exchangeBearerToken implements the OCI Distribution Spec Bearer token flow, +// parsing realm/service/scope from the WWW-Authenticate header. Falls back to +// the NGC /proxy_auth endpoint when the header is absent or unparseable. +func exchangeBearerToken(ctx context.Context, client *http.Client, registry, repo, wwwAuthenticate string) (string, error) { + realm, service, scope := parseWWWAuthenticate(wwwAuthenticate) + + if realm == "" { + // No parseable WWW-Authenticate - use NGC's /proxy_auth as fallback. + return exchangeNGCBearerToken(ctx, client, registry, repo) + } + + // Build the token endpoint URL with service and scope query parameters. + u, err := url.Parse(realm) + if err != nil { + return exchangeNGCBearerToken(ctx, client, registry, repo) + } + // Reject non-HTTPS or relative realms before attaching credentials. + if u.Scheme != "https" || u.Host == "" { + return "", fmt.Errorf("refusing token exchange at insecure or relative realm %q for %s", realm, registry) + } + // Authorize the realm host before forwarding credentials. The realm URL + // comes from a registry-controlled response header. Without this check, a + // malicious registry could return realm="https://attacker.com/token" and + // receive the operator's Docker credentials for the original registry. + // Allow the realm only when it matches the registry's own host, is a + // sub-domain of that host (e.g. auth.registry.example.com for registry.example.com), + // or is an NGC auth domain when the registry is NGC-hosted (NGC delegates + // token issuance to authn.nvidia.com and other nvidia.com sub-domains). + realmHost := strings.ToLower(u.Hostname()) + // Brackets off, to match u.Hostname(): "[fd00::1]" with no port fails + // SplitHostPort and would otherwise reject its own token server. + regHost := strings.ToLower(strings.TrimSuffix(strings.TrimPrefix(registry, "["), "]")) + if h, _, err := net.SplitHostPort(registry); err == nil { + regHost = strings.ToLower(h) + } + // Reject an empty realm host explicitly. "https://:443/token" has a + // non-empty u.Host (":443") so it clears the guard above, but Hostname() is + // "", and an empty trustedRealmDelegations lookup would then compare equal + // and authorize it. Fail closed instead. + if realmHost == "" { + return "", fmt.Errorf("refusing token exchange at realm %q with no host for %s", realm, registry) + } + realmOK := realmHost == regHost || + strings.HasSuffix(realmHost, "."+regHost) || + (isNGCRegistry(registry) && isNGCRegistry(realmHost)) || + trustedRealmDelegations[regHost] == realmHost + if !realmOK { + return "", fmt.Errorf("refusing to forward credentials to realm host %q; not authorized for registry %s", realmHost, registry) + } + q := u.Query() + if service != "" { + q.Set("service", service) + } + // Use scope from the WWW-Authenticate header when present. + // When scope is empty and a repo is provided, synthesize the standard + // pull scope. When neither is present (credential probe, no specific + // repo needed), omit scope entirely - most registries issue a valid + // token and the absence of a resource scope avoids org-level 403s for + // non-existent repositories. + if scope == "" && repo != "" { + scope = "repository:" + repo + ":pull" + } + if scope != "" { + q.Set("scope", scope) + } + u.RawQuery = q.Encode() + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil) + if err != nil { + return "", err + } + // Add credentials when present. Docker config covers any registry; + // NGC API key is only applicable to NGC-hosted registries. + // Critically: do NOT apply NGC_API_KEY to non-NGC registries - quay.io, + // GHCR, and Harbor will reject it, producing a misleading "credentials + // rejected" error when the real situation is "no credentials configured." + if user, pass, ok := credentialsForRegistry(registry); ok { + req.SetBasicAuth(user, pass) + } + + resp, err := client.Do(req) + if err != nil { + return "", err + } + if resp.StatusCode != http.StatusOK { + // Close the body immediately before the fallback or the error return so + // the connection is not held open while the NGC /proxy_auth call runs. + status := resp.Status + resp.Body.Close() + // WWW-Authenticate realm failed; try NGC's /proxy_auth as last resort + // for registries that implement both endpoints (e.g. staging NGC envs). + if isNGCRegistry(registry) { + return exchangeNGCBearerToken(ctx, client, registry, repo) + } + return "", fmt.Errorf("token exchange at %s returned %s", realm, status) + } + defer resp.Body.Close() + + // Both "token" (OCI spec) and "access_token" (Docker Hub variant) are valid. + var doc struct { + Token string `json:"token"` + AccessToken string `json:"access_token"` + } + if err := json.NewDecoder(resp.Body).Decode(&doc); err != nil { + return "", fmt.Errorf("decode token response: %w", err) + } + tok := doc.Token + if tok == "" { + tok = doc.AccessToken + } + if tok == "" { + return "", fmt.Errorf("empty token in response from %s", realm) + } + return tok, nil +} + +// exchangeNGCBearerToken is the NGC-specific /proxy_auth token exchange, +// kept as a named fallback for when the standard OCI flow cannot be used. +// It always rejects non-NGC registries so NGC credentials are never sent +// to an unrelated /proxy_auth endpoint. +func exchangeNGCBearerToken(ctx context.Context, client *http.Client, registry, repo string) (string, error) { + if !isNGCRegistry(registry) { + return "", fmt.Errorf("NGC token fallback not applicable for non-NGC registry %s", registry) + } user, pass, ok := ngcCredentials(registry) if !ok { - return "", fmt.Errorf("no NGC credentials for %s", registry) + return "", fmt.Errorf("no credentials for %s", registry) + } + // Build the query: use url.Values so the scope key is omitted entirely + // when repo is empty rather than sending scope= with an empty value. + // An empty scope validates the API key without org-scoped access checks. + q := url.Values{"service": {registry}} + if repo != "" { + q.Set("scope", "repository:"+repo+":pull") } - tokenURL := fmt.Sprintf("https://%s/proxy_auth?service=%s&scope=repository:%s:pull", registry, registry, repo) + tokenURL := "https://" + registry + "/proxy_auth?" + q.Encode() req, err := http.NewRequestWithContext(ctx, http.MethodGet, tokenURL, nil) if err != nil { return "", err @@ -203,7 +357,7 @@ func exchangeBearerToken(ctx context.Context, client *http.Client, registry, rep } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { - return "", fmt.Errorf("token exchange returned %s", resp.Status) + return "", fmt.Errorf("NGC token exchange returned %s", resp.Status) } var doc struct { Token string `json:"token"` @@ -212,22 +366,169 @@ func exchangeBearerToken(ctx context.Context, client *http.Client, registry, rep return "", err } if doc.Token == "" { - return "", fmt.Errorf("empty token in response") + return "", fmt.Errorf("empty token in NGC response") } return doc.Token, nil } -// ngcCredentials resolves (username, password) for an NGC-hosted registry. -// Checks ~/.docker/config.json first; falls back to NGC_API_KEY env vars -// with the literal "$oauthtoken" sentinel username NGC expects. -func ngcCredentials(registry string) (string, string, bool) { - if u, p, ok := credsFromDockerConfig(registry); ok { - return u, p, true +// parseWWWAuthenticate extracts realm, service, and scope from a standard +// Bearer challenge header: +// +// Bearer realm="https://auth.example.com/token",service="reg.example.com",scope="repository:lib:pull" +// +// Returns empty strings when the header is absent, not a Bearer challenge, or +// cannot be parsed. The parser handles quoted values that might contain commas. +func parseWWWAuthenticate(header string) (realm, service, scope string) { + // Split scheme from parameters on the first whitespace. HTTP auth scheme + // names are case-insensitive (RFC 7235 s2.1), so compare with EqualFold. + idx := strings.IndexByte(header, ' ') + if idx < 0 || !strings.EqualFold(header[:idx], "Bearer") { + return + } + params := strings.TrimSpace(header[idx+1:]) + for len(params) > 0 { + // Find key= + eq := strings.IndexByte(params, '=') + if eq < 0 { + break + } + key := strings.TrimSpace(params[:eq]) + params = params[eq+1:] + + // Read value (quoted or unquoted) + var val string + if strings.HasPrefix(params, `"`) { + end := strings.IndexByte(params[1:], '"') + if end < 0 { + break + } + val = params[1 : end+1] + params = strings.TrimPrefix(strings.TrimSpace(params[end+2:]), ",") + } else { + comma := strings.IndexByte(params, ',') + if comma < 0 { + val = strings.TrimSpace(params) + params = "" + } else { + val = strings.TrimSpace(params[:comma]) + params = params[comma+1:] + } + } + + switch strings.ToLower(key) { + case "realm": + realm = val + case "service": + service = val + case "scope": + scope = val + } } + return +} + +// trustedRealmDelegations maps a registry host to its authorized token host +// when the registry uses a separate host for token exchange. Only add entries +// here for registries with publicly documented auth architectures; this list +// extends the fail-closed realm validation and must not grow without a clear +// trust basis. +var trustedRealmDelegations = map[string]string{ + // Docker Hub documents this split explicitly: the pull host and the auth + // host are distinct (docs.docker.com/registry/spec/auth/token/). + "registry-1.docker.io": "auth.docker.io", + "docker.io": "auth.docker.io", +} + +// ngcApprovedHosts is the set of exact hostnames (without port) that are +// considered NGC-hosted. Dot-prefixed entries match any subdomain. +var ngcApprovedHosts = []string{ + "nvcr.io", + ".nvcr.io", + "nvidia.com", + ".nvidia.com", + "ngc.nvidia", + ".ngc.nvidia", +} + +// isBareRegistryHost reports whether s is a plain host[:port], with nothing in +// it that could move the request somewhere else once concatenated into a URL. +// +// This matters because the registry string is interpolated directly into +// "https://" + registry + "/...". A value like "evil.com/x.nvcr.io" ends with a +// trusted suffix but parses to host evil.com, so a suffix check alone would +// authorize sending credentials to an attacker. IPv6 literals keep their +// brackets and are allowed. +func isBareRegistryHost(s string) bool { + if s == "" { + return false + } + // "/" and "@" move the host; "?" and "#" truncate it; whitespace and + // control characters have no place in a hostname. + if strings.ContainsAny(s, "/@?#\\") { + return false + } + for _, r := range s { + if r <= ' ' || r == 0x7f { + return false + } + } + return true +} + +// isNGCRegistry returns true when the registry host belongs to an NVIDIA / NGC +// domain. The check strips any port from the registry string before matching +// so that nvcr.io:443 is handled correctly, and uses dot-boundary matching to +// reject deceptive suffixes such as evilnvcr.io or nvidia.com.invalid. +func isNGCRegistry(registry string) bool { + // Never treat a value we cannot safely place in a URL as an NGC host: the + // caller uses this to decide whether to forward the NGC API key. + if !isBareRegistryHost(registry) { + return false + } + host := registry + // Strip port if present (e.g. nvcr.io:5000 -> nvcr.io). + if h, _, err := net.SplitHostPort(registry); err == nil { + host = h + } + host = strings.ToLower(host) + for _, approved := range ngcApprovedHosts { + if strings.HasPrefix(approved, ".") { + // Subdomain match: host must end with ".suffix" or equal "suffix". + suffix := approved[1:] // strip the leading dot + if host == suffix || strings.HasSuffix(host, approved) { + return true + } + } else { + if host == approved { + return true + } + } + } + return false +} + +// credentialsForRegistry resolves (username, password) for any registry. For +// an NGC registry NGC_API_KEY comes first when it is set, because that is the +// key up and the validator pull secret mint from: checking a keychain login +// instead validated a different credential from the one the install uses. +// NGC_API_KEY is never sent to any other registry. Without it, or for any +// other registry, ~/.docker/config.json is used. +func credentialsForRegistry(registry string) (string, string, bool) { + if isNGCRegistry(registry) { + if key := firstNonEmptyEnv(ngcAPIKeyEnvNames...); key != "" { + return "$oauthtoken", key, true + } + } + return credsFromDockerConfig(registry) +} + +// ngcCredentials resolves (username, password) for an NGC-hosted registry, in +// the same order: NGC_API_KEY, then ~/.docker/config.json. +func ngcCredentials(registry string) (string, string, bool) { if key := firstNonEmptyEnv(ngcAPIKeyEnvNames...); key != "" { return "$oauthtoken", key, true } - return "", "", false + return credsFromDockerConfig(registry) } func credsFromDockerConfig(registry string) (string, string, bool) { @@ -245,11 +546,26 @@ func credsFromDockerConfig(registry string) (string, string, bool) { Username string `json:"username"` Password string `json:"password"` } `json:"auths"` + CredsStore string `json:"credsStore"` + CredHelpers map[string]string `json:"credHelpers"` } if err := json.Unmarshal(body, &doc); err != nil { return "", "", false } - entry, ok := doc.Auths[registry] + // Docker's order: a per-registry credHelpers entry, else credsStore, and + // only with neither configured the inline auths entry. With a store + // configured docker never reads an inline password, so an old one left in + // the file must not win here either: that sent a stale key while `docker + // pull` worked. + key := dockerConfigKey(registry) + helper := doc.CredHelpers[key] + if helper == "" { + helper = doc.CredsStore + } + if helper != "" { + return credsFromHelper(helper, key) + } + entry, ok := doc.Auths[key] if !ok { return "", "", false } @@ -270,6 +586,80 @@ func credsFromDockerConfig(registry string) (string, string, bool) { return "", "", false } +// dockerHubConfigKey is the key docker stores Docker Hub logins under. +const dockerHubConfigKey = "https://index.docker.io/v1/" + +// dockerConfigKey maps a registry host to the key docker uses for it in +// config.json. Docker Hub's hosts all share one legacy key; every other +// registry is keyed by its host. +func dockerConfigKey(registry string) string { + switch registry { + case "docker.io", "index.docker.io", "registry-1.docker.io": + return dockerHubConfigKey + } + return registry +} + +// credentialHelperName matches the docker-credential- suffixes docker +// accepts. Anything else is refused rather than put on an exec path. +var credentialHelperName = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9_.-]*$`) + +// credentialHelperTimeout bounds one helper call; a helper waiting on a +// keychain prompt must not stall preflight. A var so tests can allow for a +// loaded machine starting a freshly written helper script. +var credentialHelperTimeout = 5 * time.Second + +// credsFromHelper asks docker-credential- for registry's credential +// using the credential-helper protocol docker uses: "get" with the server URL +// on stdin, JSON {"Username","Secret"} on stdout. +func credsFromHelper(helper, registry string) (string, string, bool) { + if !credentialHelperName.MatchString(helper) { + return "", "", false + } + ctx, cancel := context.WithTimeout(context.Background(), credentialHelperTimeout) + defer cancel() + cmd := exec.CommandContext(ctx, "docker-credential-"+helper, "get") + cmd.Stdin = strings.NewReader(registry) + // Without WaitDelay a helper that leaves a child holding stdout open keeps + // Output waiting past the timeout. The output cap keeps a misbehaving + // helper from filling memory; a credential is a few hundred bytes. + cmd.WaitDelay = time.Second + var out cappedBuffer + cmd.Stdout = &out + if err := cmd.Run(); err != nil || out.overflow { + return "", "", false + } + var cred struct { + Username string `json:"Username"` + Secret string `json:"Secret"` + } + if err := json.Unmarshal(out.buf.Bytes(), &cred); err != nil || cred.Secret == "" { + return "", "", false + } + return cred.Username, cred.Secret, true +} + +// credentialHelperOutputLimit caps what a credential helper may write. +const credentialHelperOutputLimit = 64 << 10 + +// cappedBuffer keeps at most credentialHelperOutputLimit bytes and records +// whether more were written. +type cappedBuffer struct { + buf bytes.Buffer + overflow bool +} + +func (c *cappedBuffer) Write(p []byte) (int, error) { + if room := credentialHelperOutputLimit - c.buf.Len(); len(p) > room { + c.overflow = true + if room > 0 { + c.buf.Write(p[:room]) + } + return len(p), nil + } + return c.buf.Write(p) +} + // pickBestValidatorTag filters to recognized validator tags and returns // the best one: // diff --git a/src/clis/nvcf-cli/internal/selfhosted/validatortag_test.go b/src/clis/nvcf-cli/internal/selfhosted/validatortag_test.go index 0bd57306d2..3d12742105 100644 --- a/src/clis/nvcf-cli/internal/selfhosted/validatortag_test.go +++ b/src/clis/nvcf-cli/internal/selfhosted/validatortag_test.go @@ -20,8 +20,13 @@ package selfhosted import ( "context" "encoding/json" + "fmt" + "io" + "net/http" "os" "path/filepath" + "strings" + "sync" "testing" "time" @@ -31,12 +36,12 @@ import ( func TestParseImageRef(t *testing.T) { cases := []struct { - name string - in string - reg string - repo string - tag string - wantOK bool + name string + in string + reg string + repo string + tag string + wantOK bool }{ {"full tag", "stg.nvcr.io/nvidia/nvcf-byoc/cluster-validator:3.0.0-rc.26", "stg.nvcr.io", "nvidia/nvcf-byoc/cluster-validator", "3.0.0-rc.26", true}, {"digest", "nvcr.io/foo/bar@sha256:abc", "nvcr.io", "foo/bar", "sha256:abc", true}, @@ -64,8 +69,8 @@ func TestPickBestValidatorTag_StablePreferred(t *testing.T) { tags := []string{ "3.0.0-rc.11", "3.0.0-rc.26", - "3.0.0", // stable; should win over any rc - "3.1.0-rc.1", // higher major but pre-release: must lose to 3.0.0 + "3.0.0", // stable; should win over any rc + "3.1.0-rc.1", // higher major but pre-release: must lose to 3.0.0 "sha256-abc.sig", "3.0.0-v50ca53a0", // commit-SHA: filtered out by pattern } @@ -252,3 +257,275 @@ func TestNGCCredentials_EnvFallback(t *testing.T) { assert.Equal(t, "$oauthtoken", user) assert.Equal(t, "from-env", pass) } + +// -- parseWWWAuthenticate -- + +func TestParseWWWAuthenticate_Standard(t *testing.T) { + header := `Bearer realm="https://auth.docker.io/token",service="registry-1.docker.io",scope="repository:library/ubuntu:pull"` + realm, service, scope := parseWWWAuthenticate(header) + assert.Equal(t, "https://auth.docker.io/token", realm) + assert.Equal(t, "registry-1.docker.io", service) + assert.Equal(t, "repository:library/ubuntu:pull", scope) +} + +func TestParseWWWAuthenticate_GHCR(t *testing.T) { + header := `Bearer realm="https://ghcr.io/token",service="ghcr.io",scope="repository:owner/image:pull"` + realm, service, scope := parseWWWAuthenticate(header) + assert.Equal(t, "https://ghcr.io/token", realm) + assert.Equal(t, "ghcr.io", service) + assert.Equal(t, "repository:owner/image:pull", scope) +} + +func TestParseWWWAuthenticate_NoBearer(t *testing.T) { + // Basic auth challenge - should return empty strings. + realm, service, scope := parseWWWAuthenticate(`Basic realm="My Registry"`) + assert.Empty(t, realm) + assert.Empty(t, service) + assert.Empty(t, scope) +} + +func TestParseWWWAuthenticate_Empty(t *testing.T) { + realm, service, scope := parseWWWAuthenticate("") + assert.Empty(t, realm) + assert.Empty(t, service) + assert.Empty(t, scope) +} + +func TestParseWWWAuthenticate_RealmOnly(t *testing.T) { + // Some registries omit service/scope in the initial challenge. + realm, service, scope := parseWWWAuthenticate(`Bearer realm="https://example.com/auth"`) + assert.Equal(t, "https://example.com/auth", realm) + assert.Empty(t, service) + assert.Empty(t, scope) +} + +func TestParseWWWAuthenticate_MixedCaseParams(t *testing.T) { + // Auth parameter names are parsed with mixed case in the wild. + realm, service, scope := parseWWWAuthenticate(`Bearer Realm="https://auth.example.com/token",Service="reg.example.com",Scope="repository:foo:pull"`) + assert.Equal(t, "https://auth.example.com/token", realm) + assert.Equal(t, "reg.example.com", service) + assert.Equal(t, "repository:foo:pull", scope) +} + +func TestParseWWWAuthenticate_CaseInsensitiveBearer(t *testing.T) { + // HTTP auth scheme names are case-insensitive (RFC 7235 s2.1). + for _, header := range []string{ + `bearer realm="https://auth.example.com/token",service="reg.example.com"`, + `BEARER realm="https://auth.example.com/token",service="reg.example.com"`, + `Bearer realm="https://auth.example.com/token",service="reg.example.com"`, + } { + realm, service, _ := parseWWWAuthenticate(header) + assert.Equal(t, "https://auth.example.com/token", realm, "header: %s", header) + assert.Equal(t, "reg.example.com", service, "header: %s", header) + } +} + +// -- isNGCRegistry -- + +func TestIsNGCRegistry(t *testing.T) { + // Valid NGC registries. + assert.True(t, isNGCRegistry("nvcr.io")) + assert.True(t, isNGCRegistry("stg.nvcr.io")) + assert.True(t, isNGCRegistry("registry.nvidia.com")) + assert.True(t, isNGCRegistry("nvcr.io:443"), "port must be stripped before matching") + + // Non-NGC registries must be rejected. + assert.False(t, isNGCRegistry("ghcr.io")) + assert.False(t, isNGCRegistry("quay.io")) + assert.False(t, isNGCRegistry("harbor.company.internal")) + + // Deceptive suffixes must be rejected. + assert.False(t, isNGCRegistry("evilnvcr.io"), "suffix match without dot boundary must be rejected") + assert.False(t, isNGCRegistry("nvidia.com.invalid"), "deceptive TLD must be rejected") + assert.False(t, isNGCRegistry("fakenvidia.com"), "partial host match must be rejected") +} + +// -- exchangeBearerToken realm host authorization -- + +// recordingTransport records every request it sees and reports whether any +// carried an Authorization header, so a test can assert that no credential +// escaped rather than only that an error was returned. +type recordingTransport struct { + mu sync.Mutex + requests []*http.Request + withAuth []string + inner http.RoundTripper +} + +func (rt *recordingTransport) RoundTrip(r *http.Request) (*http.Response, error) { + rt.mu.Lock() + rt.requests = append(rt.requests, r) + if r.Header.Get("Authorization") != "" { + rt.withAuth = append(rt.withAuth, r.URL.String()) + } + rt.mu.Unlock() + if rt.inner == nil { + return nil, fmt.Errorf("no inner transport") + } + return rt.inner.RoundTrip(r) +} + +func TestExchangeBearerToken_RejectsAttackerRealm(t *testing.T) { + // A malicious registry returns a realm on an attacker-controlled host. The + // realm check must reject it, and crucially must do so before any request + // carrying the operator's credentials leaves the process. + // + // Credentials have to be configured for the assertion to mean anything: if + // none were present, no request could carry an Authorization header whether + // the control works or not. NGC_API_KEY is not one: it only applies to NGC + // registries, so it left this assertion vacuous. + dockerHome(t, inlineDockerConfig(t, "harbor.company.internal", "u", "p", "")) + + rec := &recordingTransport{inner: http.DefaultTransport} + client := &http.Client{Transport: rec} + + const wwwAuth = `Bearer realm="https://attacker.example.com/token",service="harbor.company.internal"` + _, err := exchangeBearerToken(context.Background(), client, + "harbor.company.internal", "myrepo/image", wwwAuth) + + require.Error(t, err) + assert.Contains(t, err.Error(), "not authorized for registry") + assert.Empty(t, rec.withAuth, + "no request carrying an Authorization header may be issued once the realm is rejected") + for _, r := range rec.requests { + assert.NotContains(t, r.URL.Host, "attacker", + "no request at all may reach the attacker host") + } +} + +// An empty realm host must fail closed. "https://:443/token" has a non-empty +// u.Host (":443") so it clears the relative-realm guard, but Hostname() is "", +// and an empty trustedRealmDelegations lookup would compare equal to it. +func TestExchangeBearerToken_RejectsEmptyRealmHost(t *testing.T) { + dockerHome(t, inlineDockerConfig(t, "harbor.company.internal", "u", "p", "")) + rec := &recordingTransport{inner: http.DefaultTransport} + client := &http.Client{Transport: rec} + + _, err := exchangeBearerToken(context.Background(), client, + "harbor.company.internal", "myrepo/image", `Bearer realm="https://:443/token"`) + + require.Error(t, err) + assert.Empty(t, rec.withAuth, "an empty realm host must not receive credentials") +} + +// -- exchangeNGCBearerToken -- + +// spyTransport is an http.RoundTripper that fails the test if called. +type spyTransport struct{ t *testing.T } + +func (s *spyTransport) RoundTrip(_ *http.Request) (*http.Response, error) { + s.t.Fatal("HTTP request must not be issued for non-NGC registry") + return nil, nil +} + +// roundTripperFunc adapts a function to the http.RoundTripper interface. +type roundTripperFunc func(*http.Request) (*http.Response, error) + +func (f roundTripperFunc) RoundTrip(r *http.Request) (*http.Response, error) { return f(r) } + +func TestExchangeBearerToken_DockerHubDelegatedRealm(t *testing.T) { + // Docker Hub uses registry-1.docker.io as the pull host and auth.docker.io + // for token exchange. The realm check must authorize that delegation, so + // drive exchangeBearerToken and assert the request actually reached the + // auth host rather than comparing the delegation map against itself. + t.Setenv("NGC_API_KEY", "test-key") + + rec := &recordingTransport{inner: roundTripperFunc(func(r *http.Request) (*http.Response, error) { + if r.URL.Host != "auth.docker.io" { + return nil, fmt.Errorf("unexpected host %s", r.URL.Host) + } + return &http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(strings.NewReader(`{"token":"dockerhub-token"}`)), + Header: make(http.Header), + }, nil + })} + client := &http.Client{Transport: rec} + + const wwwAuth = `Bearer realm="https://auth.docker.io/token",service="registry.docker.io",` + + `scope="repository:library/ubuntu:pull"` + tok, err := exchangeBearerToken(context.Background(), client, + "registry-1.docker.io", "library/ubuntu", wwwAuth) + + require.NoError(t, err, "the documented Docker Hub delegation must be authorized") + assert.Equal(t, "dockerhub-token", tok) + require.NotEmpty(t, rec.requests, "the token exchange must actually be issued") + assert.Equal(t, "auth.docker.io", rec.requests[0].URL.Host) +} + +func TestExchangeNGCBearerToken_RejectsNonNGCRegistry(t *testing.T) { + // A non-NGC registry must be rejected before any HTTP request is made, + // even when NGC credentials are configured. The spy transport fails the + // test immediately if RoundTrip is called, ensuring the isNGCRegistry + // guard fires before any network activity. + t.Setenv("NGC_API_KEY", "test-key") // configure a credential so a missing guard would reach the transport + client := &http.Client{Transport: &spyTransport{t: t}} + _, err := exchangeNGCBearerToken(context.Background(), client, "harbor.company.internal", "myrepo/image") + require.Error(t, err, "non-NGC registry must be rejected without issuing a request") + assert.Contains(t, err.Error(), "non-NGC registry") +} + +// The registry string is concatenated into "https://" + registry + "/...", so a +// suffix match alone is not enough to authorize forwarding the NGC API key: +// "evil.com/x.nvcr.io" ends with a trusted suffix but parses to host evil.com. +func TestIsNGCRegistry_RejectsHostConfusion(t *testing.T) { + for _, tc := range []struct { + registry string + want bool + why string + }{ + {"nvcr.io", true, "the canonical host"}, + {"stg.nvcr.io", true, "a real subdomain"}, + {"evil.com/x.nvcr.io", false, "path component: the request host is evil.com"}, + {"evil.com@nvcr.io", false, "userinfo: not a bare host"}, + {"nvcr.io?x=.nvcr.io", false, "query truncates the host"}, + {"nvcr.io#.nvcr.io", false, "fragment truncates the host"}, + {"nvcr.io.evil.com", false, "deceptive suffix"}, + {"nvcr.io evil.com", false, "whitespace"}, + } { + assert.Equal(t, tc.want, isNGCRegistry(tc.registry), + "isNGCRegistry(%q): %s", tc.registry, tc.why) + } +} + +// A non-bare registry must never reach the NGC token exchange, which attaches +// the API key with basic auth. +func TestExchangeNGCBearerToken_RejectsHostConfusion(t *testing.T) { + t.Setenv("NGC_API_KEY", "test-key") + rec := &recordingTransport{inner: http.DefaultTransport} + client := &http.Client{Transport: rec} + + _, err := exchangeNGCBearerToken(context.Background(), client, "evil.com/x.nvcr.io", "repo/img") + + require.Error(t, err) + assert.Empty(t, rec.withAuth, "the NGC API key must not be sent to a confused host") + assert.Empty(t, rec.requests, "no request may be issued at all") +} + +// A bracketed IPv6 registry on the default port must accept its own token +// server: u.Hostname() drops the brackets, so the registry side must too. +func TestExchangeBearerToken_AcceptsBracketedIPv6Registry(t *testing.T) { + dockerHome(t, inlineDockerConfig(t, "[fd00::1]", "u", "p", "")) + rec := &recordingTransport{inner: roundTripperFunc(func(r *http.Request) (*http.Response, error) { + return &http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(strings.NewReader(`{"token":"t"}`)), + Header: make(http.Header), + }, nil + })} + tok, err := exchangeBearerToken(context.Background(), &http.Client{Transport: rec}, + "[fd00::1]", "repo", `Bearer realm="https://[fd00::1]/token",service="r"`) + require.NoError(t, err) + assert.Equal(t, "t", tok) +} + +// A redirect may not downgrade to cleartext: Go keeps the Authorization header +// across a same-host redirect whatever the scheme. +func TestRefuseInsecureRedirect(t *testing.T) { + plain, err := http.NewRequest(http.MethodGet, "http://reg.example.com/token", nil) + require.NoError(t, err) + assert.Error(t, refuseInsecureRedirect(plain, nil)) + secure, err := http.NewRequest(http.MethodGet, "https://reg.example.com/token", nil) + require.NoError(t, err) + assert.NoError(t, refuseInsecureRedirect(secure, nil)) +}