From b695809548696b3ab0873cef5f978f21afe375c8 Mon Sep 17 00:00:00 2001 From: Philip Laine Date: Wed, 25 Sep 2024 12:51:58 +0200 Subject: [PATCH] refactor: creator Signed-off-by: Philip Laine --- src/internal/packager2/layout/create.go | 467 ++++++++++++++++++ src/internal/packager2/layout/create_test.go | 109 ++++ src/internal/packager2/layout/import.go | 423 ++++++++++++++++ .../packager2/layout/testdata/cosign.key | 11 + .../packager2/layout/testdata/cosign.pub | 4 + .../layout/testdata/zarf-package/archive.tar | Bin 0 -> 20480 bytes .../testdata/zarf-package/chart/.helmignore | 21 + .../testdata/zarf-package/chart/Chart.yaml | 13 + .../testdata/zarf-package/chart/LICENSE | 201 ++++++++ .../layout/testdata/zarf-package/chart/NOTICE | 1 + .../zarf-package/chart/templates/NOTES.txt | 20 + .../zarf-package/chart/templates/_helpers.tpl | 69 +++ .../chart/templates/deployment.yaml | 205 ++++++++ .../zarf-package/chart/templates/hpa.yaml | 41 ++ .../zarf-package/chart/templates/ingress.yaml | 41 ++ .../zarf-package/chart/templates/service.yaml | 36 ++ .../chart/templates/serviceaccount.yaml | 12 + .../testdata/zarf-package/chart/values.yaml | 164 ++++++ .../layout/testdata/zarf-package/data.txt | 1 + .../testdata/zarf-package/deployment.yaml | 21 + .../testdata/zarf-package/injection/data.txt | 1 + .../zarf-package/kustomize/kustomization.yaml | 2 + .../zarf-package/kustomize/namespace.yaml | 4 + .../layout/testdata/zarf-package/values.yaml | 5 + .../layout/testdata/zarf-package/zarf.yaml | 41 ++ src/pkg/lint/schema.go | 18 + src/pkg/packager/publish.go | 44 +- src/test/e2e/14_oci_compose_test.go | 2 - .../packages/14-import-everything/zarf.yaml | 24 +- 29 files changed, 1971 insertions(+), 30 deletions(-) create mode 100644 src/internal/packager2/layout/create.go create mode 100644 src/internal/packager2/layout/create_test.go create mode 100644 src/internal/packager2/layout/import.go create mode 100644 src/internal/packager2/layout/testdata/cosign.key create mode 100644 src/internal/packager2/layout/testdata/cosign.pub create mode 100644 src/internal/packager2/layout/testdata/zarf-package/archive.tar create mode 100644 src/internal/packager2/layout/testdata/zarf-package/chart/.helmignore create mode 100644 src/internal/packager2/layout/testdata/zarf-package/chart/Chart.yaml create mode 100644 src/internal/packager2/layout/testdata/zarf-package/chart/LICENSE create mode 100644 src/internal/packager2/layout/testdata/zarf-package/chart/NOTICE create mode 100644 src/internal/packager2/layout/testdata/zarf-package/chart/templates/NOTES.txt create mode 100644 src/internal/packager2/layout/testdata/zarf-package/chart/templates/_helpers.tpl create mode 100644 src/internal/packager2/layout/testdata/zarf-package/chart/templates/deployment.yaml create mode 100644 src/internal/packager2/layout/testdata/zarf-package/chart/templates/hpa.yaml create mode 100644 src/internal/packager2/layout/testdata/zarf-package/chart/templates/ingress.yaml create mode 100644 src/internal/packager2/layout/testdata/zarf-package/chart/templates/service.yaml create mode 100644 src/internal/packager2/layout/testdata/zarf-package/chart/templates/serviceaccount.yaml create mode 100644 src/internal/packager2/layout/testdata/zarf-package/chart/values.yaml create mode 100644 src/internal/packager2/layout/testdata/zarf-package/data.txt create mode 100644 src/internal/packager2/layout/testdata/zarf-package/deployment.yaml create mode 100644 src/internal/packager2/layout/testdata/zarf-package/injection/data.txt create mode 100644 src/internal/packager2/layout/testdata/zarf-package/kustomize/kustomization.yaml create mode 100644 src/internal/packager2/layout/testdata/zarf-package/kustomize/namespace.yaml create mode 100644 src/internal/packager2/layout/testdata/zarf-package/values.yaml create mode 100644 src/internal/packager2/layout/testdata/zarf-package/zarf.yaml diff --git a/src/internal/packager2/layout/create.go b/src/internal/packager2/layout/create.go new file mode 100644 index 0000000000..d3e82876cc --- /dev/null +++ b/src/internal/packager2/layout/create.go @@ -0,0 +1,467 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2021-Present The Zarf Authors + +package layout + +import ( + "archive/tar" + "context" + "crypto/sha256" + "encoding/hex" + "fmt" + "io" + "io/fs" + "os" + "path/filepath" + "runtime" + "slices" + "strconv" + "strings" + "time" + + "github.com/defenseunicorns/pkg/helpers/v2" + goyaml "github.com/goccy/go-yaml" + "github.com/mholt/archiver/v3" + "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" + "github.com/sigstore/cosign/v2/cmd/cosign/cli/sign" + + "github.com/zarf-dev/zarf/src/api/v1alpha1" + "github.com/zarf-dev/zarf/src/config" + "github.com/zarf-dev/zarf/src/config/lang" + "github.com/zarf-dev/zarf/src/internal/packager/helm" + "github.com/zarf-dev/zarf/src/internal/packager/kustomize" + "github.com/zarf-dev/zarf/src/pkg/lint" + "github.com/zarf-dev/zarf/src/pkg/packager/deprecated" + "github.com/zarf-dev/zarf/src/pkg/utils" + "github.com/zarf-dev/zarf/src/pkg/zoci" +) + +// CreateOptions are the options for creating a skeleton package. +type CreateOptions struct { + Flavor string + RegistryOverrides map[string]string + SigningKeyPath string + SigningKeyPassword string + SetVariables map[string]string +} + +// CreateSkeleton creates a skeleton package and returns the path to the created package. +func CreateSkeleton(ctx context.Context, packagePath string, opt CreateOptions) (string, error) { + b, err := os.ReadFile(filepath.Join(packagePath, ZarfYAML)) + if err != nil { + return "", err + } + var pkg v1alpha1.ZarfPackage + err = goyaml.Unmarshal(b, &pkg) + if err != nil { + return "", err + } + buildPath, err := utils.MakeTempDir(config.CommonOptions.TempDirectory) + if err != nil { + return "", err + } + + pkg.Metadata.Architecture = config.GetArch() + + pkg, err = resolveImports(ctx, pkg, packagePath, pkg.Metadata.Architecture, opt.Flavor) + if err != nil { + return "", err + } + + pkg.Metadata.Architecture = zoci.SkeletonArch + + err = validate(pkg, packagePath, opt.SetVariables) + if err != nil { + return "", err + } + + for _, component := range pkg.Components { + err := assembleComponent(component, packagePath, buildPath) + if err != nil { + return "", err + } + } + + checksumContent, checksumSha, err := getChecksum(buildPath) + if err != nil { + return "", err + } + checksumPath := filepath.Join(buildPath, Checksums) + err = os.WriteFile(checksumPath, []byte(checksumContent), helpers.ReadWriteUser) + if err != nil { + return "", err + } + pkg.Metadata.AggregateChecksum = checksumSha + + pkg = recordPackageMetadata(pkg, opt.Flavor, opt.RegistryOverrides) + + b, err = goyaml.Marshal(pkg) + if err != nil { + return "", err + } + err = os.WriteFile(filepath.Join(buildPath, ZarfYAML), b, helpers.ReadUser) + if err != nil { + return "", err + } + + err = signPackage(buildPath, opt.SigningKeyPath, opt.SigningKeyPassword) + if err != nil { + return "", err + } + + return buildPath, nil +} + +func validate(pkg v1alpha1.ZarfPackage, packagePath string, setVariables map[string]string) error { + err := lint.ValidatePackage(pkg) + if err != nil { + return fmt.Errorf("package validation failed: %w", err) + } + findings, err := lint.ValidatePackageSchemaAtPath(packagePath, setVariables) + if err != nil { + return fmt.Errorf("unable to check schema: %w", err) + } + if len(findings) == 0 { + return nil + } + return &lint.LintError{ + BaseDir: packagePath, + PackageName: pkg.Metadata.Name, + Findings: findings, + } +} + +func assembleComponent(component v1alpha1.ZarfComponent, packagePath, buildPath string) error { + tmpBuildPath, err := utils.MakeTempDir(config.CommonOptions.TempDirectory) + if err != nil { + return err + } + defer os.RemoveAll(tmpBuildPath) + compBuildPath := filepath.Join(tmpBuildPath, component.Name) + err = os.MkdirAll(compBuildPath, 0o700) + if err != nil { + return err + } + + for chartIdx, chart := range component.Charts { + if chart.LocalPath != "" { + rel := filepath.Join(string(ChartsComponentDir), fmt.Sprintf("%s-%d", chart.Name, chartIdx)) + dst := filepath.Join(compBuildPath, rel) + + err := helpers.CreatePathAndCopy(filepath.Join(packagePath, chart.LocalPath), dst) + if err != nil { + return err + } + + component.Charts[chartIdx].LocalPath = rel + } + + for valuesIdx, path := range chart.ValuesFiles { + if helpers.IsURL(path) { + continue + } + + rel := fmt.Sprintf("%s-%d", helm.StandardName(string(ValuesComponentDir), chart), valuesIdx) + component.Charts[chartIdx].ValuesFiles[valuesIdx] = rel + + if err := helpers.CreatePathAndCopy(filepath.Join(packagePath, path), filepath.Join(compBuildPath, rel)); err != nil { + return fmt.Errorf("unable to copy chart values file %s: %w", path, err) + } + } + } + + for filesIdx, file := range component.Files { + if helpers.IsURL(file.Source) { + continue + } + + rel := filepath.Join(string(FilesComponentDir), strconv.Itoa(filesIdx), filepath.Base(file.Target)) + dst := filepath.Join(compBuildPath, rel) + destinationDir := filepath.Dir(dst) + + if file.ExtractPath != "" { + if err := archiver.Extract(filepath.Join(packagePath, file.Source), file.ExtractPath, destinationDir); err != nil { + return fmt.Errorf(lang.ErrFileExtract, file.ExtractPath, file.Source, err.Error()) + } + + // Make sure dst reflects the actual file or directory. + updatedExtractedFileOrDir := filepath.Join(destinationDir, file.ExtractPath) + if updatedExtractedFileOrDir != dst { + if err := os.Rename(updatedExtractedFileOrDir, dst); err != nil { + return fmt.Errorf(lang.ErrWritingFile, dst, err) + } + } + } else { + if err := helpers.CreatePathAndCopy(filepath.Join(packagePath, file.Source), dst); err != nil { + return fmt.Errorf("unable to copy file %s: %w", file.Source, err) + } + } + + // Change the source to the new relative source directory (any remote files will have been skipped above) + component.Files[filesIdx].Source = rel + + // Remove the extractPath from a skeleton since it will already extract it + component.Files[filesIdx].ExtractPath = "" + + // Abort packaging on invalid shasum (if one is specified). + if file.Shasum != "" { + if err := helpers.SHAsMatch(dst, file.Shasum); err != nil { + return err + } + } + + if file.Executable || helpers.IsDir(dst) { + err = os.Chmod(dst, helpers.ReadWriteExecuteUser) + if err != nil { + return err + } + } else { + err = os.Chmod(dst, helpers.ReadWriteUser) + if err != nil { + return err + } + } + } + + for dataIdx, data := range component.DataInjections { + rel := filepath.Join(string(DataComponentDir), strconv.Itoa(dataIdx), filepath.Base(data.Target.Path)) + dst := filepath.Join(compBuildPath, rel) + + if err := helpers.CreatePathAndCopy(filepath.Join(packagePath, data.Source), dst); err != nil { + return fmt.Errorf("unable to copy data injection %s: %s", data.Source, err.Error()) + } + + component.DataInjections[dataIdx].Source = rel + } + + // Iterate over all manifests. + for manifestIdx, manifest := range component.Manifests { + for fileIdx, path := range manifest.Files { + rel := filepath.Join(string(ManifestsComponentDir), fmt.Sprintf("%s-%d.yaml", manifest.Name, fileIdx)) + dst := filepath.Join(compBuildPath, rel) + + // Copy manifests without any processing. + if err := helpers.CreatePathAndCopy(filepath.Join(packagePath, path), dst); err != nil { + return fmt.Errorf("unable to copy manifest %s: %w", path, err) + } + + component.Manifests[manifestIdx].Files[fileIdx] = rel + } + + for kustomizeIdx, path := range manifest.Kustomizations { + // Generate manifests from kustomizations and place in the package. + kname := fmt.Sprintf("kustomization-%s-%d.yaml", manifest.Name, kustomizeIdx) + rel := filepath.Join(string(ManifestsComponentDir), kname) + dst := filepath.Join(compBuildPath, rel) + + if err := kustomize.Build(filepath.Join(packagePath, path), dst, manifest.KustomizeAllowAnyDirectory); err != nil { + return fmt.Errorf("unable to build kustomization %s: %w", path, err) + } + } + + // remove kustomizations + component.Manifests[manifestIdx].Kustomizations = nil + } + + // Write the tar component. + size, err := helpers.GetDirSize(compBuildPath) + if err != nil { + return err + } + if size == 0 { + return nil + } + err = os.MkdirAll(filepath.Join(compBuildPath, "temp"), 0o700) + if err != nil { + return err + } + tarPath := filepath.Join(buildPath, "components", fmt.Sprintf("%s.tar", component.Name)) + err = os.MkdirAll(filepath.Join(buildPath, "components"), 0o700) + if err != nil { + return err + } + err = createReproducibleTarballFromDir(compBuildPath, component.Name, tarPath) + if err != nil { + return err + } + return nil +} + +func recordPackageMetadata(pkg v1alpha1.ZarfPackage, flavor string, registryOverrides map[string]string) v1alpha1.ZarfPackage { + now := time.Now() + // Just use $USER env variable to avoid CGO issue. + // https://groups.google.com/g/golang-dev/c/ZFDDX3ZiJ84. + // Record the name of the user creating the package. + if runtime.GOOS == "windows" { + pkg.Build.User = os.Getenv("USERNAME") + } else { + pkg.Build.User = os.Getenv("USER") + } + + // Record the hostname of the package creation terminal. + // The error here is ignored because the hostname is not critical to the package creation. + hostname, _ := os.Hostname() + pkg.Build.Terminal = hostname + + if pkg.IsInitConfig() { + pkg.Metadata.Version = config.CLIVersion + } + + pkg.Build.Architecture = pkg.Metadata.Architecture + + // Record the Zarf Version the CLI was built with. + pkg.Build.Version = config.CLIVersion + + // Record the time of package creation. + pkg.Build.Timestamp = now.Format(time.RFC1123Z) + + // Record the migrations that will be ran on the package. + pkg.Build.Migrations = []string{ + deprecated.ScriptsToActionsMigrated, + deprecated.PluralizeSetVariable, + } + + // Record the flavor of Zarf used to build this package (if any). + pkg.Build.Flavor = flavor + + pkg.Build.RegistryOverrides = registryOverrides + + // Record the latest version of Zarf without breaking changes to the package structure. + pkg.Build.LastNonBreakingVersion = deprecated.LastNonBreakingVersion + + return pkg +} + +func getChecksum(dirPath string) (string, string, error) { + checksumData := []string{} + err := filepath.Walk(dirPath, func(path string, info fs.FileInfo, err error) error { + if err != nil { + return err + } + if info.IsDir() { + return nil + } + rel, err := filepath.Rel(dirPath, path) + if err != nil { + return err + } + if rel == ZarfYAML || rel == Checksums { + return nil + } + sum, err := helpers.GetSHA256OfFile(path) + if err != nil { + return err + } + checksumData = append(checksumData, fmt.Sprintf("%s %s", sum, filepath.ToSlash(rel))) + return nil + }) + if err != nil { + return "", "", err + } + slices.Sort(checksumData) + + checksumContent := strings.Join(checksumData, "\n") + "\n" + sha := sha256.Sum256([]byte(checksumContent)) + return checksumContent, hex.EncodeToString(sha[:]), nil +} + +func signPackage(dirPath, signingKeyPath, signingKeyPassword string) error { + if signingKeyPath == "" { + return nil + } + passFunc := func(_ bool) ([]byte, error) { + return []byte(signingKeyPassword), nil + } + keyOpts := options.KeyOpts{ + KeyRef: signingKeyPath, + PassFunc: passFunc, + } + rootOpts := &options.RootOptions{ + Verbose: false, + Timeout: options.DefaultTimeout, + } + _, err := sign.SignBlobCmd( + rootOpts, + keyOpts, + filepath.Join(dirPath, ZarfYAML), + true, + filepath.Join(dirPath, Signature), + "", + false) + if err != nil { + return err + } + return nil +} + +func createReproducibleTarballFromDir(dirPath, dirPrefix, tarballPath string) error { + tb, err := os.Create(tarballPath) + if err != nil { + return fmt.Errorf("error creating tarball: %w", err) + } + defer tb.Close() + + tw := tar.NewWriter(tb) + defer tw.Close() + + // Walk through the directory and process each file + return filepath.Walk(dirPath, func(filePath string, info os.FileInfo, err error) error { + if err != nil { + return err + } + + link := "" + if info.Mode().Type() == os.ModeSymlink { + link, err = os.Readlink(filePath) + if err != nil { + return fmt.Errorf("error reading symlink: %w", err) + } + } + + // Create a new header + header, err := tar.FileInfoHeader(info, link) + if err != nil { + return fmt.Errorf("error creating tar header: %w", err) + } + + // Strip non-deterministic header data + header.ModTime = time.Time{} + header.AccessTime = time.Time{} + header.ChangeTime = time.Time{} + header.Uid = 0 + header.Gid = 0 + header.Uname = "" + header.Gname = "" + + header.Mode = header.Mode &^ 0o077 + + // Ensure the header's name is correctly set relative to the base directory + name, err := filepath.Rel(dirPath, filePath) + if err != nil { + return fmt.Errorf("error getting relative path: %w", err) + } + name = filepath.Join(dirPrefix, name) + name = filepath.ToSlash(name) + header.Name = name + + // Write the header to the tarball + if err := tw.WriteHeader(header); err != nil { + return fmt.Errorf("error writing header: %w", err) + } + + // If it's a file, write its content + if info.Mode().IsRegular() { + file, err := os.Open(filePath) + if err != nil { + return fmt.Errorf("error opening file: %w", err) + } + defer file.Close() + + if _, err := io.Copy(tw, file); err != nil { + return fmt.Errorf("error writing file to tarball: %w", err) + } + } + + return nil + }) +} diff --git a/src/internal/packager2/layout/create_test.go b/src/internal/packager2/layout/create_test.go new file mode 100644 index 0000000000..7e29bb875a --- /dev/null +++ b/src/internal/packager2/layout/create_test.go @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2021-Present The Zarf Authors + +package layout + +import ( + "os" + "path/filepath" + "testing" + + "github.com/defenseunicorns/pkg/helpers/v2" + "github.com/stretchr/testify/require" + + "github.com/zarf-dev/zarf/src/pkg/layout" + "github.com/zarf-dev/zarf/src/pkg/lint" + "github.com/zarf-dev/zarf/src/test/testutil" +) + +func TestCreateSkeleton(t *testing.T) { + t.Parallel() + + ctx := testutil.TestContext(t) + + lint.ZarfSchema = testutil.LoadSchema(t, "../../../../zarf.schema.json") + + opt := CreateOptions{} + path, err := CreateSkeleton(ctx, "./testdata/zarf-package", opt) + require.NoError(t, err) + + pkgPath := layout.New(path) + _, warnings, err := pkgPath.ReadZarfYAML() + require.NoError(t, err) + require.Empty(t, warnings) + b, err := os.ReadFile(filepath.Join(pkgPath.Base, "checksums.txt")) + require.NoError(t, err) + expectedChecksum := `54f657b43323e1ebecb0758835b8d01a0113b61b7bab0f4a8156f031128d00f9 components/data-injections.tar +879bfe82d20f7bdcd60f9e876043cc4343af4177a6ee8b2660c304a5b6c70be7 components/files.tar +c497f1a56559ea0a9664160b32e4b377df630454ded6a3787924130c02f341a6 components/manifests.tar +fb7ebee94a4479bacddd71195030a483b0b0b96d4f73f7fcd2c2c8e0fce0c5c6 components/helm-charts.tar +` + require.Equal(t, expectedChecksum, string(b)) +} + +func TestGetChecksum(t *testing.T) { + t.Parallel() + + tmpDir := t.TempDir() + files := map[string]string{ + "empty.txt": "", + "foo": "bar", + "zarf.yaml": "Zarf Yaml Data", + "checksums.txt": "Old Checksum Data", + "nested/directory/file.md": "nested", + } + for k, v := range files { + err := os.MkdirAll(filepath.Join(tmpDir, filepath.Dir(k)), 0o700) + require.NoError(t, err) + err = os.WriteFile(filepath.Join(tmpDir, k), []byte(v), 0o600) + require.NoError(t, err) + } + + checksumContent, checksumHash, err := getChecksum(tmpDir) + require.NoError(t, err) + + expectedContent := `233562de1a0288b139c4fa40b7d189f806e906eeb048517aeb67f34ac0e2faf1 nested/directory/file.md +e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 empty.txt +fcde2b2edba56bf408601fb721fe9b5c338d10ee429ea04fae5511b68fbf8fb9 foo +` + require.Equal(t, expectedContent, checksumContent) + require.Equal(t, "7c554cf67e1c2b50a1b728299c368cd56d53588300c37479623f29a52812ca3f", checksumHash) +} + +func TestSignPackage(t *testing.T) { + t.Parallel() + + tmpDir := t.TempDir() + yamlPath := filepath.Join(tmpDir, "zarf.yaml") + signedPath := filepath.Join(tmpDir, "zarf.yaml.sig") + + err := os.WriteFile(yamlPath, []byte("foobar"), 0o644) + require.NoError(t, err) + + err = signPackage(tmpDir, "", "") + require.NoError(t, err) + require.NoFileExists(t, signedPath) + + err = signPackage(tmpDir, "./testdata/cosign.key", "wrongpassword") + require.EqualError(t, err, "reading key: decrypt: encrypted: decryption failed") + + err = signPackage(tmpDir, "./testdata/cosign.key", "test") + require.NoError(t, err) + require.FileExists(t, signedPath) +} + +func TestCreateReproducibleTarballFromDir(t *testing.T) { + t.Parallel() + + tmpDir := t.TempDir() + err := os.WriteFile(filepath.Join(tmpDir, "test.txt"), []byte("hello world"), 0o600) + require.NoError(t, err) + tarPath := filepath.Join(t.TempDir(), "data.tar") + + err = createReproducibleTarballFromDir(tmpDir, "", tarPath) + require.NoError(t, err) + + shaSum, err := helpers.GetSHA256OfFile(tarPath) + require.NoError(t, err) + require.Equal(t, "c09d17f612f241cdf549e5fb97c9e063a8ad18ae7a9f3af066332ed6b38556ad", shaSum) +} diff --git a/src/internal/packager2/layout/import.go b/src/internal/packager2/layout/import.go new file mode 100644 index 0000000000..44bd824a28 --- /dev/null +++ b/src/internal/packager2/layout/import.go @@ -0,0 +1,423 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2021-Present The Zarf Authors + +package layout + +import ( + "context" + "crypto/sha256" + "errors" + "fmt" + "path/filepath" + "slices" + + "github.com/defenseunicorns/pkg/helpers/v2" + "github.com/defenseunicorns/pkg/oci" + "github.com/mholt/archiver/v3" + ocispec "github.com/opencontainers/image-spec/specs-go/v1" + ocistore "oras.land/oras-go/v2/content/oci" + + "github.com/zarf-dev/zarf/src/api/v1alpha1" + "github.com/zarf-dev/zarf/src/config" + "github.com/zarf-dev/zarf/src/pkg/layout" + "github.com/zarf-dev/zarf/src/pkg/message" + "github.com/zarf-dev/zarf/src/pkg/utils" + "github.com/zarf-dev/zarf/src/pkg/zoci" +) + +func resolveImports(ctx context.Context, pkg v1alpha1.ZarfPackage, packagePath, arch, flavor string) (v1alpha1.ZarfPackage, error) { + variables := pkg.Variables + constants := pkg.Constants + components := []v1alpha1.ZarfComponent{} + + for _, component := range pkg.Components { + if !compatibleComponent(component, pkg.Metadata.Architecture, flavor) { + continue + } + + // Skip as component does not have any imports. + if component.Import.Path == "" && component.Import.URL == "" { + components = append(components, component) + continue + } + + if err := validateComponentCompose(component); err != nil { + return v1alpha1.ZarfPackage{}, fmt.Errorf("invalid imported definition for %s: %w", component.Name, err) + } + + var importedPkg v1alpha1.ZarfPackage + if component.Import.Path != "" { + err := utils.ReadYaml(filepath.Join(packagePath, component.Import.Path, layout.ZarfYAML), &importedPkg) + if err != nil { + return v1alpha1.ZarfPackage{}, err + } + } else if component.Import.URL != "" { + remote, err := zoci.NewRemote(component.Import.URL, zoci.PlatformForSkeleton()) + if err != nil { + return v1alpha1.ZarfPackage{}, err + } + _, err = remote.ResolveRoot(ctx) + if err != nil { + return v1alpha1.ZarfPackage{}, err + } + importedPkg, err = remote.FetchZarfYAML(ctx) + if err != nil { + return v1alpha1.ZarfPackage{}, err + } + } + + name := component.Name + if component.Import.Name != "" { + name = component.Import.Name + } + found := []v1alpha1.ZarfComponent{} + for _, component := range importedPkg.Components { + if component.Name == name && compatibleComponent(component, arch, flavor) { + found = append(found, component) + } + } + if len(found) == 0 { + return v1alpha1.ZarfPackage{}, fmt.Errorf("component %s not found", name) + } else if len(found) > 1 { + return v1alpha1.ZarfPackage{}, fmt.Errorf("multiple components named %s found", name) + } + importedComponent := found[0] + if importedComponent.Import.Path != "" || importedComponent.Import.URL != "" { + return v1alpha1.ZarfPackage{}, fmt.Errorf("imported component %s has imports which is not supported", importedComponent.Name) + } + + err := fetchOCISkeleton(ctx, importedComponent) + if err != nil { + return v1alpha1.ZarfPackage{}, err + } + importedComponent = fixPaths(importedComponent, component.Import.Path, packagePath) + composed, err := overrideComponent(importedComponent, component) + if err != nil { + return v1alpha1.ZarfPackage{}, err + } + + components = append(components, composed) + variables = append(variables, importedPkg.Variables...) + constants = append(constants, importedPkg.Constants...) + } + + pkg.Components = components + pkg.Variables = slices.CompactFunc(variables, func(l, r v1alpha1.InteractiveVariable) bool { + return l.Name == r.Name + }) + pkg.Constants = slices.CompactFunc(constants, func(l, r v1alpha1.Constant) bool { + return l.Name == r.Name + }) + + return pkg, nil +} + +func validateComponentCompose(c v1alpha1.ZarfComponent) error { + var err error + path := c.Import.Path + url := c.Import.URL + + // ensure path or url is provided + if path == "" && url == "" { + err = errors.Join(err, errors.New("neither a path nor a URL was provided")) + } + + // ensure path and url are not both provided + if path != "" && url != "" { + err = errors.Join(err, errors.New("both a path and a URL were provided")) + } + + // validation for path + if url == "" && path != "" { + // ensure path is not an absolute path + if filepath.IsAbs(path) { + err = errors.Join(err, errors.New("path cannot be an absolute path")) + } + } + + // validation for url + if url != "" && path == "" { + ok := helpers.IsOCIURL(url) + if !ok { + err = errors.Join(err, errors.New("URL is not a valid OCI URL")) + } + } + + return err +} + +func compatibleComponent(c v1alpha1.ZarfComponent, arch, flavor string) bool { + satisfiesArch := c.Only.Cluster.Architecture == "" || c.Only.Cluster.Architecture == arch + satisfiesFlavor := c.Only.Flavor == "" || c.Only.Flavor == flavor + return satisfiesArch && satisfiesFlavor +} + +func fetchOCISkeleton(ctx context.Context, component v1alpha1.ZarfComponent) error { + if component.Import.URL == "" { + return nil + } + + name := component.Name + if component.Import.Name != "" { + name = component.Import.Name + } + + absCachePath, err := config.GetAbsCachePath() + if err != nil { + return err + } + cache := filepath.Join(absCachePath, "oci") + if err := helpers.CreateDirectory(cache, helpers.ReadWriteExecuteUser); err != nil { + return err + } + + // Get the descriptor for the component. + remote, err := zoci.NewRemote(component.Import.URL, zoci.PlatformForSkeleton()) + if err != nil { + return err + } + _, err = remote.ResolveRoot(ctx) + if err != nil { + return fmt.Errorf("published skeleton package for %s does not exist: %w", component.Import.URL, err) + } + manifest, err := remote.FetchRoot(ctx) + if err != nil { + return err + } + componentDesc := manifest.Locate(filepath.Join(layout.ComponentsDir, fmt.Sprintf("%s.tar", name))) + + // If there is not a tarball to fetch, create a directory named based upon the import url and the component name. + var tb, dir string + if oci.IsEmptyDescriptor(componentDesc) { + h := sha256.New() + h.Write([]byte(component.Import.URL + name)) + id := fmt.Sprintf("%x", h.Sum(nil)) + dir = filepath.Join(cache, "dirs", id) + message.Debug("creating empty directory for remote component:", filepath.Join("", "oci", "dirs", id)) + } else { + tb = filepath.Join(cache, "blobs", "sha256", componentDesc.Digest.Encoded()) + dir = filepath.Join(cache, "dirs", componentDesc.Digest.Encoded()) + + store, err := ocistore.New(cache) + if err != nil { + return err + } + + // ensure the tarball is in the cache + exists, err := store.Exists(ctx, componentDesc) + if err != nil { + return err + } else if !exists { + doneSaving := make(chan error) + successText := fmt.Sprintf("Pulling %q", helpers.OCIURLPrefix+remote.Repo().Reference.String()) + go utils.RenderProgressBarForLocalDirWrite(cache, componentDesc.Size, doneSaving, "Pulling", successText) + err = remote.CopyToTarget(ctx, []ocispec.Descriptor{componentDesc}, store, remote.GetDefaultCopyOpts()) + doneSaving <- err + <-doneSaving + if err != nil { + return err + } + } + } + + if err := helpers.CreateDirectory(dir, helpers.ReadWriteExecuteUser); err != nil { + return err + } + if oci.IsEmptyDescriptor(componentDesc) { + // nothing was fetched, nothing to extract + return nil + } + tu := archiver.Tar{ + OverwriteExisting: true, + // removes // from the paths + StripComponents: 1, + } + return tu.Unarchive(tb, dir) +} +func overrideComponent(c v1alpha1.ZarfComponent, override v1alpha1.ZarfComponent) (v1alpha1.ZarfComponent, error) { + // Metadata + c.Name = override.Name + c.Default = override.Default + c.Required = override.Required + + // Override description if it was provided. + if override.Description != "" { + c.Description = override.Description + } + + if override.Only.LocalOS != "" { + if c.Only.LocalOS != "" { + return v1alpha1.ZarfComponent{}, fmt.Errorf("component %q: \"only.localOS\" %q cannot be redefined as %q during compose", c.Name, c.Only.LocalOS, override.Only.LocalOS) + } + c.Only.LocalOS = override.Only.LocalOS + } + + // Deprecated + // Override cosign key path if it was provided. + if override.DeprecatedCosignKeyPath != "" { + c.DeprecatedCosignKeyPath = override.DeprecatedCosignKeyPath + } + + c.DeprecatedGroup = override.DeprecatedGroup + + // Merge deprecated scripts for backwards compatibility with older zarf binaries. + c.DeprecatedScripts.Before = append(c.DeprecatedScripts.Before, override.DeprecatedScripts.Before...) + c.DeprecatedScripts.After = append(c.DeprecatedScripts.After, override.DeprecatedScripts.After...) + + if override.DeprecatedScripts.Retry { + c.DeprecatedScripts.Retry = true + } + if override.DeprecatedScripts.ShowOutput { + c.DeprecatedScripts.ShowOutput = true + } + if override.DeprecatedScripts.TimeoutSeconds > 0 { + c.DeprecatedScripts.TimeoutSeconds = override.DeprecatedScripts.TimeoutSeconds + } + + // Actions + // Merge create actions. + c.Actions.OnCreate.Defaults = override.Actions.OnCreate.Defaults + c.Actions.OnCreate.Before = append(c.Actions.OnCreate.Before, override.Actions.OnCreate.Before...) + c.Actions.OnCreate.After = append(c.Actions.OnCreate.After, override.Actions.OnCreate.After...) + c.Actions.OnCreate.OnFailure = append(c.Actions.OnCreate.OnFailure, override.Actions.OnCreate.OnFailure...) + c.Actions.OnCreate.OnSuccess = append(c.Actions.OnCreate.OnSuccess, override.Actions.OnCreate.OnSuccess...) + + // Merge deploy actions. + c.Actions.OnDeploy.Defaults = override.Actions.OnDeploy.Defaults + c.Actions.OnDeploy.Before = append(c.Actions.OnDeploy.Before, override.Actions.OnDeploy.Before...) + c.Actions.OnDeploy.After = append(c.Actions.OnDeploy.After, override.Actions.OnDeploy.After...) + c.Actions.OnDeploy.OnFailure = append(c.Actions.OnDeploy.OnFailure, override.Actions.OnDeploy.OnFailure...) + c.Actions.OnDeploy.OnSuccess = append(c.Actions.OnDeploy.OnSuccess, override.Actions.OnDeploy.OnSuccess...) + + // Merge remove actions. + c.Actions.OnRemove.Defaults = override.Actions.OnRemove.Defaults + c.Actions.OnRemove.Before = append(c.Actions.OnRemove.Before, override.Actions.OnRemove.Before...) + c.Actions.OnRemove.After = append(c.Actions.OnRemove.After, override.Actions.OnRemove.After...) + c.Actions.OnRemove.OnFailure = append(c.Actions.OnRemove.OnFailure, override.Actions.OnRemove.OnFailure...) + c.Actions.OnRemove.OnSuccess = append(c.Actions.OnRemove.OnSuccess, override.Actions.OnRemove.OnSuccess...) + + // Resources + c.DataInjections = append(c.DataInjections, override.DataInjections...) + c.Files = append(c.Files, override.Files...) + c.Images = append(c.Images, override.Images...) + c.Repos = append(c.Repos, override.Repos...) + + // Merge charts with the same name to keep them unique + for _, overrideChart := range override.Charts { + existing := false + for idx := range c.Charts { + if c.Charts[idx].Name == overrideChart.Name { + if overrideChart.Namespace != "" { + c.Charts[idx].Namespace = overrideChart.Namespace + } + if overrideChart.ReleaseName != "" { + c.Charts[idx].ReleaseName = overrideChart.ReleaseName + } + c.Charts[idx].ValuesFiles = append(c.Charts[idx].ValuesFiles, overrideChart.ValuesFiles...) + c.Charts[idx].Variables = append(c.Charts[idx].Variables, overrideChart.Variables...) + existing = true + } + } + + if !existing { + c.Charts = append(c.Charts, overrideChart) + } + } + + // Merge manifests with the same name to keep them unique + for _, overrideManifest := range override.Manifests { + existing := false + for idx := range c.Manifests { + if c.Manifests[idx].Name == overrideManifest.Name { + if overrideManifest.Namespace != "" { + c.Manifests[idx].Namespace = overrideManifest.Namespace + } + c.Manifests[idx].Files = append(c.Manifests[idx].Files, overrideManifest.Files...) + c.Manifests[idx].Kustomizations = append(c.Manifests[idx].Kustomizations, overrideManifest.Kustomizations...) + + existing = true + } + } + + if !existing { + c.Manifests = append(c.Manifests, overrideManifest) + } + } + + c.HealthChecks = append(c.HealthChecks, override.HealthChecks...) + + return c, nil +} + +func makePathRelativeTo(path, relativeTo string) string { + if helpers.IsURL(path) { + return path + } + return filepath.Join(relativeTo, path) +} + +func fixPaths(child v1alpha1.ZarfComponent, relativeToHead, packagePath string) v1alpha1.ZarfComponent { + for fileIdx, file := range child.Files { + composed := makePathRelativeTo(file.Source, relativeToHead) + child.Files[fileIdx].Source = composed + } + + for chartIdx, chart := range child.Charts { + for valuesIdx, valuesFile := range chart.ValuesFiles { + composed := makePathRelativeTo(valuesFile, relativeToHead) + child.Charts[chartIdx].ValuesFiles[valuesIdx] = composed + } + if child.Charts[chartIdx].LocalPath != "" { + composed := makePathRelativeTo(chart.LocalPath, relativeToHead) + child.Charts[chartIdx].LocalPath = composed + } + } + + for manifestIdx, manifest := range child.Manifests { + for fileIdx, file := range manifest.Files { + composed := makePathRelativeTo(file, relativeToHead) + child.Manifests[manifestIdx].Files[fileIdx] = composed + } + for kustomizeIdx, kustomization := range manifest.Kustomizations { + composed := makePathRelativeTo(kustomization, relativeToHead) + // kustomizations can use non-standard urls, so we need to check if the composed path exists on the local filesystem + invalid := helpers.InvalidPath(filepath.Join(packagePath, composed)) + if !invalid { + child.Manifests[manifestIdx].Kustomizations[kustomizeIdx] = composed + } + } + } + + for dataInjectionsIdx, dataInjection := range child.DataInjections { + composed := makePathRelativeTo(dataInjection.Source, relativeToHead) + child.DataInjections[dataInjectionsIdx].Source = composed + } + + defaultDir := child.Actions.OnCreate.Defaults.Dir + child.Actions.OnCreate.Before = fixActionPaths(child.Actions.OnCreate.Before, defaultDir, relativeToHead) + child.Actions.OnCreate.After = fixActionPaths(child.Actions.OnCreate.After, defaultDir, relativeToHead) + child.Actions.OnCreate.OnFailure = fixActionPaths(child.Actions.OnCreate.OnFailure, defaultDir, relativeToHead) + child.Actions.OnCreate.OnSuccess = fixActionPaths(child.Actions.OnCreate.OnSuccess, defaultDir, relativeToHead) + + // deprecated + if child.DeprecatedCosignKeyPath != "" { + composed := makePathRelativeTo(child.DeprecatedCosignKeyPath, relativeToHead) + child.DeprecatedCosignKeyPath = composed + } + + return child +} + +// fixActionPaths takes a slice of actions and mutates the Dir to be relative to the head node +func fixActionPaths(actions []v1alpha1.ZarfComponentAction, defaultDir, relativeToHead string) []v1alpha1.ZarfComponentAction { + for actionIdx, action := range actions { + var composed string + if action.Dir != nil { + composed = makePathRelativeTo(*action.Dir, relativeToHead) + } else { + composed = makePathRelativeTo(defaultDir, relativeToHead) + } + actions[actionIdx].Dir = &composed + } + return actions +} diff --git a/src/internal/packager2/layout/testdata/cosign.key b/src/internal/packager2/layout/testdata/cosign.key new file mode 100644 index 0000000000..90fe1f9dfb --- /dev/null +++ b/src/internal/packager2/layout/testdata/cosign.key @@ -0,0 +1,11 @@ +-----BEGIN ENCRYPTED SIGSTORE PRIVATE KEY----- +eyJrZGYiOnsibmFtZSI6InNjcnlwdCIsInBhcmFtcyI6eyJOIjo2NTUzNiwiciI6 +OCwicCI6MX0sInNhbHQiOiJEM1h4S3huclZqU3JjSkdvYTZIcTVWYkEwYUhwUldW +akJKR3F2L0pHZDMwPSJ9LCJjaXBoZXIiOnsibmFtZSI6Im5hY2wvc2VjcmV0Ym94 +Iiwibm9uY2UiOiJSOGZWZzlIczVIdFZKWENDVmJnODhwVFFObTRsQnh0RCJ9LCJj +aXBoZXJ0ZXh0IjoiclNHS3A0RGpMQzdnd0RnU0F6SnIwQXhVbmxxeG1EVVZ2ci9p +MzRHTk8vaGRCblRTVEpQYU5YRWJiZDd3R1hDMlVUeU9QOS92Q2NBUUI0dVBFNnZD +V3ZzSFVwOWYyZlJoazY1TXVFQkFLWStVaE1uQ0QzcGlueWhGNktOUmxEaG1tZCtZ +SnI4ZW4rczBMZnFQREJWRkRFb2lLVlJENEMxYVF5eTdveGJJOEZDWG9FSStTd284 +WnpsK2F1anpxdlYxTlg0NHJaeU9sZVRyV3c9PSJ9 +-----END ENCRYPTED SIGSTORE PRIVATE KEY----- diff --git a/src/internal/packager2/layout/testdata/cosign.pub b/src/internal/packager2/layout/testdata/cosign.pub new file mode 100644 index 0000000000..da98deb626 --- /dev/null +++ b/src/internal/packager2/layout/testdata/cosign.pub @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEWa56xczL+HvqDx5tUg9ThYzIAGcc +Geic52+Ajs65OgUKePRK49fki3cSZpqV1yCfqHUPnU+SaQjAiCPK3SAW9g== +-----END PUBLIC KEY----- diff --git a/src/internal/packager2/layout/testdata/zarf-package/archive.tar b/src/internal/packager2/layout/testdata/zarf-package/archive.tar new file mode 100644 index 0000000000000000000000000000000000000000..cbbd80680cd84b59686c8b82b86a1b1e1ea4b20f GIT binary patch literal 20480 zcmeIu!3x4K3;@u6%6>p;-PV0iQDFlQV@~n&?P&-*c-rmdkd!2Z^2%}@r}zDID{DEg zpEVp1u}1E?P)i#6_*-l1($y4Fmxun6V{TK3sqIq=O+4T)yOXcyT9&2>^Ef@Gc{kn~ z|K{7G2LS>E2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk u1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBn=3w#07z!#MO literal 0 HcmV?d00001 diff --git a/src/internal/packager2/layout/testdata/zarf-package/chart/.helmignore b/src/internal/packager2/layout/testdata/zarf-package/chart/.helmignore new file mode 100644 index 0000000000..f0c1319444 --- /dev/null +++ b/src/internal/packager2/layout/testdata/zarf-package/chart/.helmignore @@ -0,0 +1,21 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*~ +# Various IDEs +.project +.idea/ +*.tmproj diff --git a/src/internal/packager2/layout/testdata/zarf-package/chart/Chart.yaml b/src/internal/packager2/layout/testdata/zarf-package/chart/Chart.yaml new file mode 100644 index 0000000000..0ae3bfd45f --- /dev/null +++ b/src/internal/packager2/layout/testdata/zarf-package/chart/Chart.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +version: 6.4.0 +appVersion: 6.4.0 +name: podinfo +engine: gotpl +description: Podinfo Helm chart for Kubernetes +home: https://github.com/stefanprodan/podinfo +maintainers: +- email: stefanprodan@users.noreply.github.com + name: stefanprodan +sources: +- https://github.com/stefanprodan/podinfo +kubeVersion: ">=1.23.0-0" diff --git a/src/internal/packager2/layout/testdata/zarf-package/chart/LICENSE b/src/internal/packager2/layout/testdata/zarf-package/chart/LICENSE new file mode 100644 index 0000000000..1b92ec15f9 --- /dev/null +++ b/src/internal/packager2/layout/testdata/zarf-package/chart/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2018 Stefan Prodan. All rights reserved. + + 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. diff --git a/src/internal/packager2/layout/testdata/zarf-package/chart/NOTICE b/src/internal/packager2/layout/testdata/zarf-package/chart/NOTICE new file mode 100644 index 0000000000..5b0414f8c2 --- /dev/null +++ b/src/internal/packager2/layout/testdata/zarf-package/chart/NOTICE @@ -0,0 +1 @@ +All files from this chart are from https://github.com/stefanprodan/podinfo/tree/6.4.0/charts/podinfo. diff --git a/src/internal/packager2/layout/testdata/zarf-package/chart/templates/NOTES.txt b/src/internal/packager2/layout/testdata/zarf-package/chart/templates/NOTES.txt new file mode 100644 index 0000000000..d8329725ef --- /dev/null +++ b/src/internal/packager2/layout/testdata/zarf-package/chart/templates/NOTES.txt @@ -0,0 +1,20 @@ +1. Get the application URL by running these commands: +{{- if .Values.ingress.enabled }} +{{- range $host := .Values.ingress.hosts }} + {{- range .paths }} + http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} + {{- end }} +{{- end }} +{{- else if contains "NodePort" .Values.service.type }} + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ template "podinfo.fullname" . }}) + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + echo http://$NODE_IP:$NODE_PORT +{{- else if contains "LoadBalancer" .Values.service.type }} + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + You can watch the status of by running 'kubectl get svc -w {{ template "podinfo.fullname" . }}' + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ template "podinfo.fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}') + echo http://$SERVICE_IP:{{ .Values.service.externalPort }} +{{- else if contains "ClusterIP" .Values.service.type }} + echo "Visit http://127.0.0.1:8080 to use your application" + kubectl -n {{ .Release.Namespace }} port-forward deploy/{{ template "podinfo.fullname" . }} 8080:{{ .Values.service.externalPort }} +{{- end }} diff --git a/src/internal/packager2/layout/testdata/zarf-package/chart/templates/_helpers.tpl b/src/internal/packager2/layout/testdata/zarf-package/chart/templates/_helpers.tpl new file mode 100644 index 0000000000..1f5a052871 --- /dev/null +++ b/src/internal/packager2/layout/testdata/zarf-package/chart/templates/_helpers.tpl @@ -0,0 +1,69 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "podinfo.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "podinfo.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "podinfo.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "podinfo.labels" -}} +helm.sh/chart: {{ include "podinfo.chart" . }} +{{ include "podinfo.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "podinfo.selectorLabels" -}} +app.kubernetes.io/name: {{ include "podinfo.fullname" . }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "podinfo.serviceAccountName" -}} +{{- if .Values.serviceAccount.enabled }} +{{- default (include "podinfo.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} + +{{/* +Create the name of the tls secret for secure port +*/}} +{{- define "podinfo.tlsSecretName" -}} +{{- $fullname := include "podinfo.fullname" . -}} +{{- default (printf "%s-tls" $fullname) .Values.tls.secretName }} +{{- end }} diff --git a/src/internal/packager2/layout/testdata/zarf-package/chart/templates/deployment.yaml b/src/internal/packager2/layout/testdata/zarf-package/chart/templates/deployment.yaml new file mode 100644 index 0000000000..87ed373534 --- /dev/null +++ b/src/internal/packager2/layout/testdata/zarf-package/chart/templates/deployment.yaml @@ -0,0 +1,205 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ template "podinfo.fullname" . }} + labels: + {{- include "podinfo.labels" . | nindent 4 }} +spec: + {{- if not .Values.hpa.enabled }} + replicas: {{ .Values.replicaCount }} + {{- end }} + strategy: + type: RollingUpdate + rollingUpdate: + maxUnavailable: 1 + selector: + matchLabels: + {{- include "podinfo.selectorLabels" . | nindent 6 }} + template: + metadata: + labels: + {{- include "podinfo.selectorLabels" . | nindent 8 }} + annotations: + prometheus.io/scrape: "true" + prometheus.io/port: "{{ .Values.service.httpPort }}" + {{- range $key, $value := .Values.podAnnotations }} + {{ $key }}: {{ $value | quote }} + {{- end }} + spec: + terminationGracePeriodSeconds: 30 + {{- if .Values.serviceAccount.enabled }} + serviceAccountName: {{ template "podinfo.serviceAccountName" . }} + {{- end }} + containers: + - name: {{ .Chart.Name }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + {{- if .Values.securityContext }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + {{- else if (or .Values.service.hostPort .Values.tls.hostPort) }} + securityContext: + allowPrivilegeEscalation: true + capabilities: + drop: + - ALL + add: + - NET_BIND_SERVICE + {{- end }} + command: + - ./podinfo + - --port={{ .Values.service.httpPort | default 9898 }} + {{- if .Values.host }} + - --host={{ .Values.host }} + {{- end }} + {{- if .Values.tls.enabled }} + - --secure-port={{ .Values.tls.port }} + {{- end }} + {{- if .Values.tls.certPath }} + - --cert-path={{ .Values.tls.certPath }} + {{- end }} + {{- if .Values.service.metricsPort }} + - --port-metrics={{ .Values.service.metricsPort }} + {{- end }} + {{- if .Values.service.grpcPort }} + - --grpc-port={{ .Values.service.grpcPort }} + {{- end }} + {{- if .Values.service.grpcService }} + - --grpc-service-name={{ .Values.service.grpcService }} + {{- end }} + {{- range .Values.backends }} + - --backend-url={{ . }} + {{- end }} + {{- if .Values.cache }} + - --cache-server={{ .Values.cache }} + {{- else if .Values.redis.enabled }} + - --cache-server=tcp://{{ template "podinfo.fullname" . }}-redis:6379 + {{- end }} + - --level={{ .Values.logLevel }} + - --random-delay={{ .Values.faults.delay }} + - --random-error={{ .Values.faults.error }} + {{- if .Values.faults.unhealthy }} + - --unhealthy + {{- end }} + {{- if .Values.faults.unready }} + - --unready + {{- end }} + {{- if .Values.h2c.enabled }} + - --h2c + {{- end }} + env: + {{- if .Values.ui.message }} + - name: PODINFO_UI_MESSAGE + value: {{ quote .Values.ui.message }} + {{- end }} + {{- if .Values.ui.logo }} + - name: PODINFO_UI_LOGO + value: {{ .Values.ui.logo }} + {{- end }} + {{- if .Values.ui.color }} + - name: PODINFO_UI_COLOR + value: {{ quote .Values.ui.color }} + {{- end }} + {{- if .Values.backend }} + - name: PODINFO_BACKEND_URL + value: {{ .Values.backend }} + {{- end }} + ports: + - name: http + containerPort: {{ .Values.service.httpPort | default 9898 }} + protocol: TCP + {{- if .Values.service.hostPort }} + hostPort: {{ .Values.service.hostPort }} + {{- end }} + {{- if .Values.tls.enabled }} + - name: https + containerPort: {{ .Values.tls.port | default 9899 }} + protocol: TCP + {{- if .Values.tls.hostPort }} + hostPort: {{ .Values.tls.hostPort }} + {{- end }} + {{- end }} + {{- if .Values.service.metricsPort }} + - name: http-metrics + containerPort: {{ .Values.service.metricsPort }} + protocol: TCP + {{- end }} + {{- if .Values.service.grpcPort }} + - name: grpc + containerPort: {{ .Values.service.grpcPort }} + protocol: TCP + {{- end }} + {{- if .Values.probes.startup.enable }} + startupProbe: + exec: + command: + - podcli + - check + - http + - localhost:{{ .Values.service.httpPort | default 9898 }}/healthz + {{- with .Values.probes.startup }} + initialDelaySeconds: {{ .initialDelaySeconds | default 1 }} + timeoutSeconds: {{ .timeoutSeconds | default 5 }} + failureThreshold: {{ .failureThreshold | default 3 }} + successThreshold: {{ .successThreshold | default 1 }} + periodSeconds: {{ .periodSeconds | default 10 }} + {{- end }} + {{- end }} + livenessProbe: + exec: + command: + - podcli + - check + - http + - localhost:{{ .Values.service.httpPort | default 9898 }}/healthz + {{- with .Values.probes.liveness }} + initialDelaySeconds: {{ .initialDelaySeconds | default 1 }} + timeoutSeconds: {{ .timeoutSeconds | default 5 }} + failureThreshold: {{ .failureThreshold | default 3 }} + successThreshold: {{ .successThreshold | default 1 }} + periodSeconds: {{ .periodSeconds | default 10 }} + {{- end }} + readinessProbe: + exec: + command: + - podcli + - check + - http + - localhost:{{ .Values.service.httpPort | default 9898 }}/readyz + {{- with .Values.probes.readiness }} + initialDelaySeconds: {{ .initialDelaySeconds | default 1 }} + timeoutSeconds: {{ .timeoutSeconds | default 5 }} + failureThreshold: {{ .failureThreshold | default 3 }} + successThreshold: {{ .successThreshold | default 1 }} + periodSeconds: {{ .periodSeconds | default 10 }} + {{- end }} + volumeMounts: + - name: data + mountPath: /data + {{- if .Values.tls.enabled }} + - name: tls + mountPath: {{ .Values.tls.certPath | default "/data/cert" }} + readOnly: true + {{- end }} + resources: +{{ toYaml .Values.resources | indent 12 }} + {{- with .Values.nodeSelector }} + nodeSelector: +{{ toYaml . | indent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: +{{ toYaml . | indent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: +{{ toYaml . | indent 8 }} + {{- end }} + volumes: + - name: data + emptyDir: {} + {{- if .Values.tls.enabled }} + - name: tls + secret: + secretName: {{ template "podinfo.tlsSecretName" . }} + {{- end }} diff --git a/src/internal/packager2/layout/testdata/zarf-package/chart/templates/hpa.yaml b/src/internal/packager2/layout/testdata/zarf-package/chart/templates/hpa.yaml new file mode 100644 index 0000000000..f2fb8df1b8 --- /dev/null +++ b/src/internal/packager2/layout/testdata/zarf-package/chart/templates/hpa.yaml @@ -0,0 +1,41 @@ +{{- if .Values.hpa.enabled -}} +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: {{ template "podinfo.fullname" . }} + labels: + {{- include "podinfo.labels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ template "podinfo.fullname" . }} + minReplicas: {{ .Values.replicaCount }} + maxReplicas: {{ .Values.hpa.maxReplicas }} + metrics: + {{- if .Values.hpa.cpu }} + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: {{ .Values.hpa.cpu }} + {{- end }} + {{- if .Values.hpa.memory }} + - type: Resource + resource: + name: memory + target: + type: AverageValue + averageValue: {{ .Values.hpa.memory }} + {{- end }} + {{- if .Values.hpa.requests }} + - type: Pods + pods: + metric: + name: http_requests + target: + type: AverageValue + averageValue: {{ .Values.hpa.requests }} + {{- end }} +{{- end }} diff --git a/src/internal/packager2/layout/testdata/zarf-package/chart/templates/ingress.yaml b/src/internal/packager2/layout/testdata/zarf-package/chart/templates/ingress.yaml new file mode 100644 index 0000000000..93f9ae437a --- /dev/null +++ b/src/internal/packager2/layout/testdata/zarf-package/chart/templates/ingress.yaml @@ -0,0 +1,41 @@ +{{- if .Values.ingress.enabled -}} +{{- $fullName := include "podinfo.fullname" . -}} +{{- $svcPort := .Values.service.externalPort -}} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ $fullName }} + labels: + {{- include "podinfo.labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + ingressClassName: {{ .Values.ingress.className }} + {{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + pathType: {{ .pathType }} + backend: + service: + name: {{ $fullName }} + port: + number: {{ $svcPort }} + {{- end }} + {{- end }} +{{- end }} diff --git a/src/internal/packager2/layout/testdata/zarf-package/chart/templates/service.yaml b/src/internal/packager2/layout/testdata/zarf-package/chart/templates/service.yaml new file mode 100644 index 0000000000..6014e78853 --- /dev/null +++ b/src/internal/packager2/layout/testdata/zarf-package/chart/templates/service.yaml @@ -0,0 +1,36 @@ +{{- if .Values.service.enabled -}} +apiVersion: v1 +kind: Service +metadata: + name: {{ template "podinfo.fullname" . }} + labels: + {{- include "podinfo.labels" . | nindent 4 }} +{{- with .Values.service.annotations }} + annotations: +{{ toYaml . | indent 4 }} +{{- end }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.externalPort }} + targetPort: http + protocol: TCP + name: http + {{- if (and (eq .Values.service.type "NodePort") (not (empty .Values.service.nodePort))) }} + nodePort: {{ .Values.service.nodePort }} + {{- end }} + {{- if .Values.tls.enabled }} + - port: {{ .Values.tls.port | default 9899 }} + targetPort: https + protocol: TCP + name: https + {{- end }} + {{- if .Values.service.grpcPort }} + - port: {{ .Values.service.grpcPort }} + targetPort: grpc + protocol: TCP + name: grpc + {{- end }} + selector: + {{- include "podinfo.selectorLabels" . | nindent 4 }} +{{- end }} diff --git a/src/internal/packager2/layout/testdata/zarf-package/chart/templates/serviceaccount.yaml b/src/internal/packager2/layout/testdata/zarf-package/chart/templates/serviceaccount.yaml new file mode 100644 index 0000000000..d39b798967 --- /dev/null +++ b/src/internal/packager2/layout/testdata/zarf-package/chart/templates/serviceaccount.yaml @@ -0,0 +1,12 @@ +{{- if .Values.serviceAccount.enabled -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ template "podinfo.serviceAccountName" . }} + labels: + {{- include "podinfo.labels" . | nindent 4 }} +{{- with .Values.serviceAccount.imagePullSecrets }} +imagePullSecrets: + {{- toYaml . | nindent 2 }} +{{- end -}} +{{- end -}} diff --git a/src/internal/packager2/layout/testdata/zarf-package/chart/values.yaml b/src/internal/packager2/layout/testdata/zarf-package/chart/values.yaml new file mode 100644 index 0000000000..89b2bd9129 --- /dev/null +++ b/src/internal/packager2/layout/testdata/zarf-package/chart/values.yaml @@ -0,0 +1,164 @@ +# Default values for podinfo. + +replicaCount: 1 +logLevel: info +host: #0.0.0.0 +backend: #http://backend-podinfo:9898/echo +backends: [] + +image: + repository: ghcr.io/stefanprodan/podinfo + tag: 6.4.0 + pullPolicy: IfNotPresent + +ui: + color: "#34577c" + message: "" + logo: "" + +# failure conditions +faults: + delay: false + error: false + unhealthy: false + unready: false + testFail: false + testTimeout: false + +# Kubernetes Service settings +service: + enabled: true + annotations: {} + type: ClusterIP + metricsPort: 9797 + httpPort: 9898 + externalPort: 9898 + grpcPort: 9999 + grpcService: podinfo + nodePort: 31198 + # the port used to bind the http port to the host + # NOTE: requires privileged container with NET_BIND_SERVICE capability -- this is useful for testing + # in local clusters such as kind without port forwarding + hostPort: + +# enable h2c protocol (non-TLS version of HTTP/2) +h2c: + enabled: false + +# enable tls on the podinfo service +tls: + enabled: false + # the name of the secret used to mount the certificate key pair + secretName: + # the path where the certificate key pair will be mounted + certPath: /data/cert + # the port used to host the tls endpoint on the service + port: 9899 + # the port used to bind the tls port to the host + # NOTE: requires privileged container with NET_BIND_SERVICE capability -- this is useful for testing + # in local clusters such as kind without port forwarding + hostPort: + +# create a certificate manager certificate (cert-manager required) +certificate: + create: false + # the issuer used to issue the certificate + issuerRef: + kind: ClusterIssuer + name: self-signed + # the hostname / subject alternative names for the certificate + dnsNames: + - podinfo + +# metrics-server add-on required +hpa: + enabled: false + maxReplicas: 10 + # average total CPU usage per pod (1-100) + cpu: + # average memory usage per pod (100Mi-1Gi) + memory: + # average http requests per second per pod (k8s-prometheus-adapter) + requests: + +# Redis address in the format tcp://: +cache: "" +# Redis deployment +redis: + enabled: false + repository: redis + tag: 7.0.7 + +serviceAccount: + # Specifies whether a service account should be created + enabled: false + # The name of the service account to use. + # If not set and create is true, a name is generated using the fullname template + name: + # List of image pull secrets if pulling from private registries + imagePullSecrets: [] + +# set container security context +securityContext: {} + +ingress: + enabled: false + className: "" + annotations: {} + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" + hosts: + - host: podinfo.local + paths: + - path: / + pathType: ImplementationSpecific + tls: [] + # - secretName: chart-example-tls + # hosts: + # - chart-example.local + +linkerd: + profile: + enabled: false + +# create Prometheus Operator monitor +serviceMonitor: + enabled: false + interval: 15s + additionalLabels: {} + +resources: + limits: + requests: + cpu: 1m + memory: 16Mi + +nodeSelector: {} + +tolerations: [] + +affinity: {} + +podAnnotations: {} + +# https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes +probes: + readiness: + initialDelaySeconds: 1 + timeoutSeconds: 5 + failureThreshold: 3 + successThreshold: 1 + periodSeconds: 10 + liveness: + initialDelaySeconds: 1 + timeoutSeconds: 5 + failureThreshold: 3 + successThreshold: 1 + periodSeconds: 10 + startup: + enable: false + initialDelaySeconds: 10 + timeoutSeconds: 5 + failureThreshold: 20 + successThreshold: 1 + periodSeconds: 10 diff --git a/src/internal/packager2/layout/testdata/zarf-package/data.txt b/src/internal/packager2/layout/testdata/zarf-package/data.txt new file mode 100644 index 0000000000..557db03de9 --- /dev/null +++ b/src/internal/packager2/layout/testdata/zarf-package/data.txt @@ -0,0 +1 @@ +Hello World diff --git a/src/internal/packager2/layout/testdata/zarf-package/deployment.yaml b/src/internal/packager2/layout/testdata/zarf-package/deployment.yaml new file mode 100644 index 0000000000..685c17aa68 --- /dev/null +++ b/src/internal/packager2/layout/testdata/zarf-package/deployment.yaml @@ -0,0 +1,21 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment + labels: + app: nginx +spec: + replicas: 3 + selector: + matchLabels: + app: nginx + template: + metadata: + labels: + app: nginx + spec: + containers: + - name: nginx + image: nginx:1.14.2 + ports: + - containerPort: 80 diff --git a/src/internal/packager2/layout/testdata/zarf-package/injection/data.txt b/src/internal/packager2/layout/testdata/zarf-package/injection/data.txt new file mode 100644 index 0000000000..1269488f7f --- /dev/null +++ b/src/internal/packager2/layout/testdata/zarf-package/injection/data.txt @@ -0,0 +1 @@ +data diff --git a/src/internal/packager2/layout/testdata/zarf-package/kustomize/kustomization.yaml b/src/internal/packager2/layout/testdata/zarf-package/kustomize/kustomization.yaml new file mode 100644 index 0000000000..736967b1a3 --- /dev/null +++ b/src/internal/packager2/layout/testdata/zarf-package/kustomize/kustomization.yaml @@ -0,0 +1,2 @@ +resources: + - namespace.yaml diff --git a/src/internal/packager2/layout/testdata/zarf-package/kustomize/namespace.yaml b/src/internal/packager2/layout/testdata/zarf-package/kustomize/namespace.yaml new file mode 100644 index 0000000000..7c265c0193 --- /dev/null +++ b/src/internal/packager2/layout/testdata/zarf-package/kustomize/namespace.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: test diff --git a/src/internal/packager2/layout/testdata/zarf-package/values.yaml b/src/internal/packager2/layout/testdata/zarf-package/values.yaml new file mode 100644 index 0000000000..f86a45afe7 --- /dev/null +++ b/src/internal/packager2/layout/testdata/zarf-package/values.yaml @@ -0,0 +1,5 @@ +ui: + color: "#0d133d" + message: "greetings from podinfo (as deployed by Zarf)" + # Replace the githubusercontent URL for the airgap + logo: "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhLS0gQ3JlYXRlZCB3aXRoIFZlY3Rvcm5hdG9yIChodHRwOi8vdmVjdG9ybmF0b3IuaW8vKSAtLT4KCjxzdmcKICAgaGVpZ2h0PSI2NCIKICAgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIgogICBzdHlsZT0iY2xpcC1ydWxlOmV2ZW5vZGQ7ZmlsbC1ydWxlOm5vbnplcm87c3Ryb2tlLWxpbmVjYXA6cm91bmQ7c3Ryb2tlLWxpbmVqb2luOnJvdW5kIgogICB2ZXJzaW9uPSIxLjEiCiAgIHZpZXdCb3g9IjAgMCA2NCA2NCIKICAgd2lkdGg9IjY0IgogICB4bWw6c3BhY2U9InByZXNlcnZlIgogICBpZD0ic3ZnODY1IgogICBzb2RpcG9kaTpkb2NuYW1lPSJaYXJmLXJlZG8uc3ZnIgogICBpbmtzY2FwZTp2ZXJzaW9uPSIxLjIuMiAoYjBhODQ4NjU0MSwgMjAyMi0xMi0wMSkiCiAgIHhtbG5zOmlua3NjYXBlPSJodHRwOi8vd3d3Lmlua3NjYXBlLm9yZy9uYW1lc3BhY2VzL2lua3NjYXBlIgogICB4bWxuczpzb2RpcG9kaT0iaHR0cDovL3NvZGlwb2RpLnNvdXJjZWZvcmdlLm5ldC9EVEQvc29kaXBvZGktMC5kdGQiCiAgIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIgogICB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zOnZlY3Rvcm5hdG9yPSJodHRwOi8vdmVjdG9ybmF0b3IuaW8iPjxzb2RpcG9kaTpuYW1lZHZpZXcKICAgaWQ9Im5hbWVkdmlldzg2NyIKICAgcGFnZWNvbG9yPSIjNTA1MDUwIgogICBib3JkZXJjb2xvcj0iI2VlZWVlZSIKICAgYm9yZGVyb3BhY2l0eT0iMSIKICAgaW5rc2NhcGU6c2hvd3BhZ2VzaGFkb3c9IjAiCiAgIGlua3NjYXBlOnBhZ2VvcGFjaXR5PSIwIgogICBpbmtzY2FwZTpwYWdlY2hlY2tlcmJvYXJkPSIwIgogICBpbmtzY2FwZTpkZXNrY29sb3I9IiM1MDUwNTAiCiAgIHNob3dncmlkPSJmYWxzZSIKICAgaW5rc2NhcGU6em9vbT0iMTYiCiAgIGlua3NjYXBlOmN4PSIzMi42NTYyNSIKICAgaW5rc2NhcGU6Y3k9IjM2LjUiCiAgIGlua3NjYXBlOndpbmRvdy13aWR0aD0iMjU2MCIKICAgaW5rc2NhcGU6d2luZG93LWhlaWdodD0iMTM3MSIKICAgaW5rc2NhcGU6d2luZG93LXg9IjAiCiAgIGlua3NjYXBlOndpbmRvdy15PSIzMiIKICAgaW5rc2NhcGU6d2luZG93LW1heGltaXplZD0iMSIKICAgaW5rc2NhcGU6Y3VycmVudC1sYXllcj0iZzQzNjEiIC8+CjxkZWZzCiAgIGlkPSJkZWZzNjE0Ij4KPHBhdGgKICAgZD0ibSA0OC45MTY2LDI1LjYzMDIgYyAwLjc1ODYsMC4zODc5IDQuMzU1MiwxLjk3MjQgOC44NDc4LC0wLjE3OTggLTAuNDAxNSwtMC42MzY2IC0xLjkxMTYsLTIuNjgzNSAtNC45OTI1LC00LjA2NSAtMS4zNTQ1LC0wLjQyMjEgLTIuNTIxOSwtMC40MzI2IC0zLjM4MzYsLTAuMjAwNiAtMS4yMTE4LDEuNDM4MyAtMS40Nzk2LDIuOTc4OSAtMC40NzE3LDQuNDQ1NCB6IgogICBpZD0iRmlsbCIgLz4KPGxpbmVhckdyYWRpZW50CiAgIGdyYWRpZW50VHJhbnNmb3JtPSJ0cmFuc2xhdGUoMC4wNjA0OTU1LDAuMDkwNzQzMikiCiAgIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIgogICBpZD0iTGluZWFyR3JhZGllbnQiCiAgIHgxPSIzMS4xOTYxIgogICB4Mj0iMzEuMTk2MSIKICAgeTE9IjYxLjM3NjQ5OSIKICAgeTI9IjEuODA0OTk5OSI+CjxzdG9wCiAgIG9mZnNldD0iMCIKICAgc3RvcC1jb2xvcj0iIzdiZDVmNSIKICAgaWQ9InN0b3A1NTAiIC8+CjxzdG9wCiAgIG9mZnNldD0iMSIKICAgc3RvcC1jb2xvcj0iI2RjY2ZlNSIKICAgaWQ9InN0b3A1NTIiIC8+CjwvbGluZWFyR3JhZGllbnQ+CjxwYXRoCiAgIGQ9Im0gNDkuMzczNCwyMC42NjA4IGMgMC44NTA3LDAuMjg5NyA2LjAxMzcsMS42NzI1IDExLjE3NTEsLTQuOTYxNSAtMS41OTM0LC0xLjIzNTYgLTkuMDkzOCwtMi4zNzA5IC0xMi4xODAyLDAuNTM4IC0xLjEyMjYsMS41MDczIC0wLjk2MjEsMy4yNTg3IDEuMDAzNiw0LjM5OTMgeiIKICAgaWQ9IkZpbGxfMiIgLz4KPGxpbmVhckdyYWRpZW50CiAgIGdyYWRpZW50VHJhbnNmb3JtPSJ0cmFuc2xhdGUoMC4wNjMyNDQ1LC0wLjAwNTQ5OTU5KSIKICAgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiCiAgIGlkPSJMaW5lYXJHcmFkaWVudF8yIgogICB4MT0iMzEuMTk2MSIKICAgeDI9IjMxLjE5NjEiCiAgIHkxPSI2MS4zNzY0OTkiCiAgIHkyPSIxLjgwNDk5OTkiPgo8c3RvcAogICBvZmZzZXQ9IjAiCiAgIHN0b3AtY29sb3I9IiM3YmQ1ZjUiCiAgIGlkPSJzdG9wNTU2IiAvPgo8c3RvcAogICBvZmZzZXQ9IjEiCiAgIHN0b3AtY29sb3I9IiNkY2NmZTUiCiAgIGlkPSJzdG9wNTU4IiAvPgo8L2xpbmVhckdyYWRpZW50Pgo8cGF0aAogICBkPSJNIDQ4LjE3NTYsMTUuODIzMyBDIDU4LjM0ODYsMTMuMzc4NSA1OS4zNzc5LDcuNjE5NzcgNTkuNjEzNyw2LjI3NjQzIDQ5LjczNyw1Ljk1ODEgNDUuNzg2MywxMC4zMjg4IDQ1LjAzNzYsMTEuMzAyOSBjIDAuMjUyNCwyLjE3OTYgMC42MDM1LDMuNDU2MiAzLjEzNSw0LjUxMzggeiIKICAgaWQ9IkZpbGxfMyIgLz4KPGxpbmVhckdyYWRpZW50CiAgIGdyYWRpZW50VHJhbnNmb3JtPSJ0cmFuc2xhdGUoMC4wMzYzOTMxLC0wLjAyMTgzNTgpIgogICBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIKICAgaWQ9IkxpbmVhckdyYWRpZW50XzMiCiAgIHgxPSIzMS4xOTYxIgogICB4Mj0iMzEuMTk2MSIKICAgeTE9IjYxLjM3NjQ5OSIKICAgeTI9IjEuODA0OTk5OSI+CjxzdG9wCiAgIG9mZnNldD0iMCIKICAgc3RvcC1jb2xvcj0iIzdiZDVmNSIKICAgaWQ9InN0b3A1NjIiIC8+CjxzdG9wCiAgIG9mZnNldD0iMSIKICAgc3RvcC1jb2xvcj0iI2RjY2ZlNSIKICAgaWQ9InN0b3A1NjQiIC8+CjwvbGluZWFyR3JhZGllbnQ+CjxwYXRoCiAgIGQ9Im0gMjUuODQ3NCw1MC43MDM2IGMgLTAuNjE1MywwLjg3NTYgLTIuMzk2LDIuMzk0OCAtMy4zMjY5LDIuOTIzNiAtMS4wNzIsMC42MDY1IC0yLjI3NzMsMC45MzkzIC0zLjUwODksMC45Njg3IC0xLjIzMTYsMC4wMjk1IC0yLjQ1MTUsLTAuMjQ1NCAtMy41NTEzLC0wLjggLTIuMjkwNywtMS4xNjgzIC00LjE0OTksLTMuNjY0NCAtNC44NzE3LC03LjYzMzYgLTAuMDUzMSwtMC4yOTE5IC0wLjUzNTIsLTAuNTA4IC0wLjU0NzksLTAuMDQwMSAwLjA0NzcsNS42Njk0IDIuMzExNiw5LjM3MTEgNS40MjEyLDExLjMxOTIgMC41MDg3LDAuMzcyIDEuMDQzMiwwLjU2NTcgMS42MjY1LDAuODA0NyA3LjY0NjQsMi4zMzk3IDEyLjgwNzgsLTIuNDM1MiAxNC44Nzg3LC00Ljk1MzIgLTAuNjEzNywxLjU0OTYgLTEuNTUxLDIuOTUwNiAtMi43NDk1LDQuMTA5NiAtMC45MjgxLDAuODk3NiAtMS45ODczLDEuNjQ4OSAtMy4xNDE0LDIuMjI4MyAtMS40MjUyLDAuNzEzNiAtMi45NjM1LDEuMTc0NSAtNC41NDY1LDEuMzYyNSAtMS41NzU0LDAuMTkwNiAtMy4xNzIyLDAuMDk4OSAtNC43MTUzLC0wLjI3MDYgQyAxNS4xNTEyLDYwLjMzNDMgMTMuNTk0OSw1OS41ODIxIDEyLjI1NzgsNTguNTIwMyAxMC45MjA2LDU3LjQ1ODQgOS44MzU5NCw1Ni4xMTM0IDkuMDgxODgsNTQuNTgyMiA2LjkwNTgyLDUwLjEyNTYgNy4yNjkwMyw0NS41MDY2IDguMzkwNTEsNDIuOTA5MSA4LjYzOTQ3LDQyLjI0MTggOS4wNDYwNiw0MS42NDQ0IDkuNTc1NzIsNDEuMTY3OCA5LjcxNDQzLDQxLjAzMjkgOS44OTM1OSw0MC45NDcgMTAuMDg1OCw0MC45MjM1IGMgMC4xOTIyLC0wLjAyMzYgMC4zODY4LDAuMDE2NCAwLjU1NCwwLjExMzggMC41Mzg1LDAuMzQzOCAxLjAxNjQsMS4zMjQzIDEuMjk5OSwzLjEyNiAwLjg0NDQsNC43NjUgMi43NDY1LDYuOTk3NCA1LjgwNjQsOC43ODQyIDAuNjk0MiwwLjM1OSAxLjc5MjUsMC42MzIzIDIuNTc0MSwwLjY0MjIgMC43ODE1LDAuMDEgMS41NTQ2LC0wLjE2MjUgMi4yNTc3LC0wLjUwMzcgMC44NDQyLC0wLjQwNDIgMi4yODYxLC0xLjAwMTYgMy4yNjk1LC0yLjM4MjQgeiIKICAgaWQ9IkZpbGxfNCIgLz4KPHBhdGgKICAgZD0ibSAzMS43MTIyLDQyLjcxMTcgYyAtMC4zNDE5LC0xLjQzNDYgLTAuNjgxOCwtMi44NzEzIC0xLjAxOTUsLTQuMzEwMSBoIC0wLjAxOTEgYyAwLjA2MzcsLTAuOTU1IDAuNDIzNywtMi4wNzg3IDAuMTg4LC0yLjg2NSBsIC0wLjYxMTcsMi44MDc3IHYgMC4wOTIzIGwgMS4wNTEzLDQuNDU2NiBjIDAuMDMxOSwwLjA0MTQgMC4wNjM4LDAuMDg1OSAwLjA5MjQsMC4xMzA1IGwgMS41MjkzLDAuOTU1IC0xLjQ5MSwwLjI0ODMgMC4wNjY5LDAuMjI5MiBjIDAsMC4wMDMxIDAuMzMxMywxLjEwNzcgLTAuMDcwMSwxLjg1OSAtMC4yMDM5LC0wLjM5MTYgLTAuNjIxMywtMS4wNiAtMC44NDExLC0xLjQ1MTYgLTAuMDc2NSwwLjAxMjggLTAuNDY4NCwxLjI3NjUgLTEuMzE5LDEuNjkwMyAtMC4wMTI4LC0wLjU3OTMgLTAuMDM1MSwtMS4xNTg3IC0wLjAzNTEsLTEuNzM4IGwgLTAuMzUwNSwwLjM0MzggYyAtMC4zNDQ1LDAuMzEyMSAtMC43NzU0LDAuNTEzIC0xLjIzNjEsMC41NzYxIDAuNDk3LC0wLjUwMjkgMS4zMDk0LC0xLjM4NzkgMS4yNzQ0LC0xLjkwOTkgLTAuMDE1NiwtMC4yMzk5IC0wLjM0NTUsLTAuNzI5OCAtMC44MDE2LC0xLjQwNzEgbCAtMC4wMywtMC4wNDQ1IGMgLTAuOTIzOSwtMS4zMzA2IC0yLjc3NSwtNC4xMjg3IC0zLjE1NDEsLTYuNDIzOCAtMC4xNjU3LDIuMjYzMyAxLjY4ODYsNS4wMTA0IDIuODAzNyw2LjY1OTQgMC4yOTk2LDAuMzg0NSAwLjU1NCwwLjgwMjEgMC43NTgyLDEuMjQ0NiAwLjAzNTEsMC41NDQ0IC0xLjUyOTMsMS45MjU5IC0xLjUyOTMsMS45MjU5IDAsMCAtMC4zNTY4LDAuMzE1MSAwLjExNDcsMC4zNjkzIDAuNjExNiwwLjA2NjkgMS4yMjczLC0wLjA4MyAxLjczOTYsLTAuNDIzNCAwLjAyMjMsMC4zNzg4IDAuMDIyMywwLjc1NDQgMC4wMjIzLDEuMTMzMiAwLDAgLTAuMDM4MiwwLjMxMiAwLjI4MDQsMC4yMDA2IDAuMzI1MiwtMC4xMDc0IDAuNjI1NCwtMC4yNzkyIDAuODgyNywtMC41MDUgMC4yNTczLC0wLjIyNTkgMC40NjYzLC0wLjUwMTMgMC42MTQ3LC0wLjgwOTcgbCAwLjU1NzYsMC45OTMyIGMgMC4wMjU1LDAuMDQ3NyAwLjEyMSwwLjM4NTEgMC4zNzU5LDAuMTA4MiAwLjYzNzIsLTAuNjk0IDAuNDk3LC0xLjgwODEgMC4zOTgzLC0yLjI4NTYgbCAxLjU5MywtMC4yNjQyIGMgMC4wNzY1LC0wLjAwOTYgMC4yMTY2LC0wLjI5MjkgMC4wNzY1LC0wLjM4ODQgeiBNIDE3LjI1NCw1Ny43MjA5IGMgOS42OCwyLjk2NTEgMTUuMTgyMywtNS45ODM0IDE1LjI1MDksLTYuMDk1IGwgN2UtNCwtMC4wMDEgYyAwLjA0LC0wLjA2MyAwLjEwMzIsLTAuMTA3NyAwLjE3NiwtMC4xMjQ0IDAuMDcyOCwtMC4wMTY3IDAuMTQ5MiwtMC4wMDQgMC4yMTI3LDAuMDM1MyAwLjA1MjEsMC4wMzI3IDAuMDkyNCwwLjA4MTMgMC4xMTQ4LDAuMTM4NiAwLjAyMjQsMC4wNTczIDAuMDI1NywwLjEyMDMgMC4wMDk0LDAuMTc5NyAtMC40NDAzLDEuODQwNyAtMS4yOTk0LDMuNTU1MyAtMi41MTA2LDUuMDEwNSAzLjg3MSwtMS44NTkxIDcuMDA5MywtNC45NDA1IDcuODA1OCwtOC42MjM1IC0wLjI2MTMsLTAuMDc5NiAtMC45NTU4LC0wLjg4MTggLTAuOTk3MiwtMC45NTUgMS40MzY5LDAuODM0IDIuMDgzNiwwLjg0MDQgMy43OTQ1LDEuMDc1OSBsIDAuMTcyMSwwLjAyNTUgdiAwLjE3MTkgYyAtMC4wMDE0LDAuNDMxIDAuMTYzMywwLjg0NTkgMC40NTk5LDEuMTU4OSAwLjI5NjYsMC4zMTI5IDAuNzAyMywwLjQ5OTggMS4xMzMxLDAuNTIxOSAxLjI3NTUsLTAuNTE0OCA1LjI1MjEsLTEuMDgyMiA2LjUzODgsLTEuMjY1OCBsIDAuMDUzMSwtMC4wMDc1IGMgLTAuMjIxMiwtMC4yOTUxIC0wLjkyNzIsLTAuNTU0IC0xLjQxNzgsLTAuNzc5OSBsIC0wLjM4ODcsLTAuMTg0NyAwLjM4MjMsLTAuMTk0MiBjIDAuNTk2MiwtMC4yNjA2IDEuNTkxLC0wLjU1MTMgMS44MjU2LC0wLjY4NzUgLTAuODQ0MiwtMC41MTY2IC0yLjYwOTMsLTAuMjM4OCAtMi42MTI1LC0wLjIzODggbCAtMC40MTQyLDAuMDczMiBjIDAuNDc3OSwtMC45NTUgMi4xNzIzLC0xLjkwMTYgMi4zNzY4LC0yLjA5NzcgLTEuMDg4MywtMC40Mjc2IC0zLjU4NzUsMC45NzQgLTQuMDQzMSwxLjIxNiAtMC4wNjY5LDAuMzUwMSAtMC4xNDY2LDAuODM0IC0wLjM3OTEsMS4wNzI3IDAuMDI4NiwtMC43NTEyIDAuMTgzOCwtMi4xODM5IDAuMTU5MywtMi41MTc5IC0wLjE0NTIsMC4xMDA5IC0yLjI3NDksMi40OTg4IC0yLjI4MTIsMi41MDIgMC4wMjE4LC0wLjQzMTkgMC4yMDM1LC0wLjg0MDMgMC41MDk3LC0xLjE0NiAwLjM3OTIsLTIuNzQ3MSAtMS40OTEsLTUuMzA2NSAtMi4wMTA0LC01Ljk1NTkgLTAuMTg0NywwLjM3ODggLTAuMzg4NiwwLjc2NzIgLTAuNTgzLDEuMTM5NiBsIC0wLjAxNjMsMC4wMzE0IGMgLTAuNDQ1OCwwLjg1OTYgLTAuODQ0MiwxLjYyNzUgLTAuODYzLDEuOTE2OCBsIDEuMTQwNiwyLjIyODMgYyAwLjAyNiwwLjA0OSAwLjAzMTUsMC4xMDYzIDAuMDE1NCwwLjE1OTQgLTAuMDE2MSwwLjA1MyAtMC4wNTI2LDAuMDk3NiAtMC4xMDE1LDAuMTIzOSAtMC4wMjkxLDAuMDE0OCAtMC4wNjEzLDAuMDIyNSAtMC4wOTQsMC4wMjI1IC0wLjAzMjYsMCAtMC4wNjQ4LC0wLjAwNzcgLTAuMDkzOSwtMC4wMjI1IGwgLTEuNDA1MSwtMC42MzY3IGMgLTAuMDc5NiwwLjcyNTggLTAuMTYyNSwxLjQ1MTYgLTAuMjQyMSwyLjE3NzQgMCwwLjE5MSAtMC4zMTg2LDAuMTgxNSAtMC4zOTgzLDAuMDczMiBsIC0wLjk1NTgsLTEuOTI5IC0xLjI3NDQsMS44ODEzIGMgLTAuMDkyNCwwLjEzMzcgLTAuMzkxOSwwLjAyMjMgLTAuMzg1NSwtMC4xMjEgMCwtMC43MzUzIDAsLTEuNDY3NSAwLjAyMjMsLTIuMjAyOCBsIC0xLjQ4MTUsMC4xNjU1IGMgLTAuMDI4NSwwLjAwMTMgLTAuMDU2OSwtMC4wMDMgLTAuMDgzNywtMC4wMTI4IC0wLjAyNjcsLTAuMDA5NyAtMC4wNTEzLC0wLjAyNDYgLTAuMDcyNCwtMC4wNDM4IC0wLjAyMSwtMC4wMTkyIC0wLjAzOCwtMC4wNDI0IC0wLjA1LC0wLjA2ODEgLTAuMDEyMSwtMC4wMjU4IC0wLjAxODksLTAuMDUzOCAtMC4wMjAxLC0wLjA4MjIgLTAuMDAxNiwtMC4wMjkzIDAuMDAzLC0wLjA1ODYgMC4wMTM0LC0wLjA4NiAwLjAxMDQsLTAuMDI3NCAwLjAyNjUsLTAuMDUyMyAwLjA0NzEsLTAuMDczMiAwLDAgMS44NjcsLTEuNjk2NiAyLjM5NTksLTMuMjk3OCAwLjUyODksLTEuNjAxMiAwLjMxODYsLTUuNDg0OCAwLjMxODYsLTUuNDk3NSAwLjU2MDgsMC45NTQ5IDAuMzQ0MSw0LjgyOSAwLjA3OTcsNS42MzEyIC0wLjM5NTEsMS4xOTA1IC0xLjQ2NTYsMi40MTI5IC0yLjA3NDEsMy4wNDMyIGwgMS4zNTA4LC0wLjE0OTYgYyAwLDAuNjU4OSAwLDEuMzIxIC0wLjAyMjMsMS45OCBsIDEuMjk5OSwtMS45MSBjIDAuMzE4NiwwLjYzNjYgMC42MzcyLDEuMjczMyAwLjk1NTgsMS45MzIyIGwgMC4yMjk0LC0yLjAzNDEgMS4zOTU1LDAuNjA4IC0wLjg5ODQsLTEuNzUzOSBjIDAuMDAzMiwtMC4wMjk1IC0wLjAwNDgsLTAuMDU5IC0wLjAyMjMsLTAuMDgyOCAtMC4wMjU1LC0wLjM3MjUgMC40MjY5LC0xLjI0MTUgMC45MzAzLC0yLjIwNiAwLjUwMzQsLTAuOTY0NiAxLjEwNTUsLTIuMTIwMSAxLjIyMzQsLTIuNzg1NCAwLjIwMDcsLTEuMTE3MyAtMC43NzQyLC00LjE3MzMgLTEuMDM1NCwtNC45NjI3IC0yLjQ5ODEsMC42Mjc0IC01LjA5MjEsMC43Nzk2IC03LjY0NjUsMC40NDg4IC0yLjU4MTcsLTAuMzA2NSAtNS4wOTYzLC0xLjAzMDEgLTcuNDQ1OCwtMi4xNDIzIC0wLjQzMDEsMC43MzIxIC0yLjExNTUsMy44ODM2IC0xLjU5Myw3LjQ5OTggdiAwLjEwNSBsIC0wLjA3MzMsMC4wNzMyIGMgMCwwIC00LjM0NTcsNC4zOTMgLTIuNTk2Niw3Ljc3NjggLTAuMDIyMiwtMC43NDU0IDAuMDIzNiwtMS40OTEzIDAuMTM3LC0yLjIyODMgMC4wMjkzLC0wLjE5MDYgMC4wODg0LC0wLjM3NTQgMC4xNzUzLC0wLjU0NzUgMC4xMjQyLC0wLjIxNjUgMC4yODk5LC0wLjI5NjEgMC41MDAyLC0wLjE4MTUgMC4xNDM4LDAuMDk5IDAuMjU0OCwwLjIzODcgMC4zMTg2LDAuNDAxMSAwLjcyOTYsMS40Mzg4IDAuODk1Myw0LjUwNDMgMC44OTg0LDQuNTEzOSAwLjAyMjMsMC4yMTAxIC0wLjMxODYsMC41MjUyIC0wLjQyMzcsMC4wMjU1IDAsMCAtMC4xNjI1LC0yLjk4OTEgLTAuODUzOSwtNC4zNDg0IC0wLjAyODcsLTAuMDU3OSAtMC4wNjA2LC0wLjExNDMgLTAuMDk1NSwtMC4xNjg3IC0wLjA1MTYsMC4xMjM4IC0wLjA4OSwwLjI1MyAtMC4xMTE2LDAuMzg1MiAtMC4xMjczLDAuODUxMSAtMC4xNjI2LDEuNzEzNCAtMC4xMDUxLDIuNTcyMSAwLjAyMjMsMC4yMSAtMC4wNDc4LDAuMzE4MyAtMC4yMTAzLDAuMzE4MyAtMC4xNjI1LDAgLTAuMzYsLTAuMjEzMyAtMC42MzcyLC0wLjU1NzEgbCAtMC4wMDE2LC0wLjAwMiBjIC0wLjcwMTUsLTAuODY5OCAtMi4xODUsLTIuNTIxMiAtMy41MzQ5LC0yLjE0MDMgMC4wODQ5LDAuMTMxMSAyLjE0MSwyLjA5MTQgMi4wOTMzLDIuODY0OSB2IDAuMzg1MiBsIC0wLjMxODcsLTAuMTkxIGMgMCwwIC0yLjEzNjIsLTAuOTY5OCAtMy4zMDcsLTAuNzcwNCAwLjA2NjUsMC4xMzA3IDIuMDQ4NiwxLjMyNzUgMi42MTg5LDEuMzM3IDAuMjk5NSwwLjEzMDUgMC4yNDIxLDAuMzg1MiAwLjAyNTUsMC40MjM0IC0wLjY0NTIsMC4wNTA5IC0xLjc1NzIsMC4zMzgzIC0xLjg3NjYsMC40NDg4IDAuMTY0MiwwLjE3MzkgMy41MDQ2LDAuNjAxNyAzLjk1NywxLjE0MjggMCwwIDAuMDMxNiwwLjAzODUgMC4wODksMC4xMDE1IDAuMzQ1NSwwLjM3ODkgMS42MjY2LDEuNjQ0NyAyLjU5MzcsMC43NTQ4IDAuMDcwMSwtMC4wODU5IDAuNjE0OSwtMC43ODYyIDAuNTY3MSwtMS4xNjE5IGwgMC4wMTkxLC0wLjExNzcgYyAwLjc1NTEsLTEuNTcyNiAzLjYwNjYsLTMuNzExNyA0LjM5MzUsLTIuNTQ2NyAtMC40NjA5LC0wLjA0NTUgLTAuOTI2MywwLjAwOTkgLTEuMzYzNiwwLjE2MjQgLTAuMDg2NiwxLjI2OTMgLTAuNDg2NywyLjQ5NzQgLTEuMTY0NCwzLjU3NDUgLTAuNjc3NywxLjA3NzEgLTEuNjEyLDEuOTY5NyAtMi43MTk0LDIuNTk3OCAtMS4xMTY2LDAuNjMwMiAtMi4zNjk0LDAuOTgwOCAtMy42NTExLDEuMDIxOSAtMS4zMTg3LDAuMDQ0NCAtMi42MjcyLC0wLjI0NDcgLTMuODA0MiwtMC44NDA0IC0xLjkxMTYsLTAuOTcwOSAtMy42MzIzLC0yLjg1MjQgLTQuNTg0OSwtNS42NzI4IDAuNTUxMiw0LjExNiAyLjQzOTMsNi45MTA1IDQuOTgsOC40NjQ1IDAuNTAyNywwLjMwNzUgMS40OTgyLDAuNzIxNCAxLjQ5ODIsMC43MjE0IHoiCiAgIGlkPSJGaWxsXzUiIC8+CjxsaW5lYXJHcmFkaWVudAogICBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIKICAgaWQ9IkxpbmVhckdyYWRpZW50XzQiCiAgIHgxPSIzMS4xOTYxIgogICB4Mj0iMzEuMTk2MSIKICAgeTE9IjYxLjM3NjQ5OSIKICAgeTI9IjEuODA0OTk5OSI+CjxzdG9wCiAgIG9mZnNldD0iMCIKICAgc3RvcC1jb2xvcj0iIzdiZDVmNSIKICAgaWQ9InN0b3A1NjkiIC8+CjxzdG9wCiAgIG9mZnNldD0iMSIKICAgc3RvcC1jb2xvcj0iI2RjY2ZlNSIKICAgaWQ9InN0b3A1NzEiIC8+CjwvbGluZWFyR3JhZGllbnQ+CjxwYXRoCiAgIGQ9Im0gMTguNzYzMiwxNC43OTA0IGMgMC4wMjg2LC0wLjEzMDUgLTAuNjgwOSwyLjE0OTIgLTEuMDMzMiw2LjAzMDYgMC4wODYsMS42MDA3IDAuNzY2OCwzLjUxMDggMS4zMTE0LDQuNjM0MiAwLjk0ODQsMS42NjQxIDIuMjc1NiwzLjA4MTggMy44NzQyLDQuMTM4MyAyLjY5ODYsMS44ODQ1IDYuMjUxLDMuMTY0MSA5Ljg3NjcsMy41ODExIDMuNjI1NywwLjQxNzEgNy4zMTUxLDAuMDE5MSAxMC4yODQ1LC0xLjQ5MjkgMS42MzQxLC0wLjgxMTQgMy4wNDE3LC0yLjAxNDQgNC4wOTcyLC0zLjUwMTYgMS41MTM0LC0yLjE3NDIgMi4wNzQxLC01LjQyNDMgMS41NzM5LC04LjY1ODUgQyA0OC4yODYsMTYuNTYxMSA0Ni45NDE1LDEzLjYwMzkgNDQuNTgwNiwxMS41MTU3IDQzLjY2NjIsMTAuNjc1MyAzOS4xNzA3LDcuODA3MTQgMzMuMzI3Niw3LjUyMzgzIDI5LjQ3MDgsNy4zMjgzMyAyNS42NTA0LDguMzUxOTQgMjIuNDA5LDEwLjQ0OTMgYyAwLDAgLTMuMDM5MiwyLjk5ODcgLTMuNTQ5Niw0LjEzIC0wLjAzMTIsMC4wNjkxIDEuMDE4OCwtMS4zNTc5IC0wLjA5NjIsMC4yMTExIHoiCiAgIGlkPSJGaWxsXzYiIC8+CjxsaW5lYXJHcmFkaWVudAogICBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIKICAgaWQ9IkxpbmVhckdyYWRpZW50XzUiCiAgIHgxPSIzMS4xOTYxIgogICB4Mj0iMzEuMTk2MSIKICAgeTE9IjYxLjM3NjQ5OSIKICAgeTI9IjEuODA0OTk5OSI+CjxzdG9wCiAgIG9mZnNldD0iMCIKICAgc3RvcC1jb2xvcj0iIzdiZDVmNSIKICAgaWQ9InN0b3A1NzUiIC8+CjxzdG9wCiAgIG9mZnNldD0iMSIKICAgc3RvcC1jb2xvcj0iI2RjY2ZlNSIKICAgaWQ9InN0b3A1NzciIC8+CjwvbGluZWFyR3JhZGllbnQ+CjxwYXRoCiAgIGQ9Im0gMTAuODY5MSwyMS4yOTM4IGMgMi40ODIxLC0xLjU5MSA1Ljk2NTIsLTIuMzUzMiA5Ljk3MDQsLTEuOTgzNyAwLjI0NTMsMC4yOTkzIDAuNTMwOCwwLjYwNTQgMC42ODA1LDAuNzY3NyAtMC40MzAxLDAuMzg4NCAtMS40OTQyLDEuNDUxNiAtMS4xODIsMi4yMjgzIDAuMDA2NCwwLjAzOSAwLjAwNjQsMC4wNzg4IDAsMC4xMTc4IDAsMC40MTA2IC0wLjMxODYsMS4yNzMzIC0xLjEzNzQsMi4yOTgzIC0wLjgxODgsMS4wMjUgLTIuMjMwMiwyLjE2NzggLTQuNDI1NCwzLjA2MjMgLTIuMjcxNiwwLjkyNjQgLTUuMzkwNzYsMS41OTE3IC05LjYzMTM3LDEuNTYzIDAuNTM1MjYsLTEuMjczMyAyLjgyMjgyLC02LjM2NjUgNS43MTI1NywtOC4wNDA5IHoiCiAgIGlkPSJGaWxsXzciIC8+CjxsaW5lYXJHcmFkaWVudAogICBncmFkaWVudFRyYW5zZm9ybT0idHJhbnNsYXRlKC0wLjA3MDIxMjQsMC4xMjY1MzUpIgogICBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIKICAgaWQ9IkxpbmVhckdyYWRpZW50XzYiCiAgIHgxPSIzMS4xOTYxIgogICB4Mj0iMzEuMTk2MSIKICAgeTE9IjYxLjM3NjQ5OSIKICAgeTI9IjEuODA0OTk5OSI+CjxzdG9wCiAgIG9mZnNldD0iMCIKICAgc3RvcC1jb2xvcj0iIzdiZDVmNSIKICAgaWQ9InN0b3A1ODEiIC8+CjxzdG9wCiAgIG9mZnNldD0iMSIKICAgc3RvcC1jb2xvcj0iI2RjY2ZlNSIKICAgaWQ9InN0b3A1ODMiIC8+CjwvbGluZWFyR3JhZGllbnQ+CjxwYXRoCiAgIGQ9Im0gMTIuNTQ4OSwxMi4yMzMxIGMgNS4xMDgsMy45MDUzIDguMTUwNSwzLjQ1ODEgOC43MjY2LDMuNjU0NiAyLjQzMDUsLTAuMzI5IDAuODg1NywtNS4zNDQ5IDAuODg1NywtNS4zNDQ5IGwgLTAuMTAyLC0wLjIxOTcgQyAyMi4wNTQsMTAuMzE0MyAyMS4xNzk3LDguMzU3OTcgMTguMzgxNyw2LjM4NjMzIDE2LjEwNTcsNC43ODI1MiAxMi41NTY5LDMuMTY4NiA3LjE2NzcyLDIuNTg0NTkgNy4zNzQ4MSwzLjg2NzQ1IDguNTA5MDQsOS40NzMxOSAxMi41NDg5LDEyLjIzMzEgWiIKICAgaWQ9IkZpbGxfOCIgLz4KPGxpbmVhckdyYWRpZW50CiAgIGdyYWRpZW50VHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTAuMDU5ODg5MSwtMC4wMzY5OTk4KSIKICAgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiCiAgIGlkPSJMaW5lYXJHcmFkaWVudF83IgogICB4MT0iMzEuMTk2MSIKICAgeDI9IjMxLjE5NjEiCiAgIHkxPSI2MS4zNzY0OTkiCiAgIHkyPSIxLjgwNDk5OTkiPgo8c3RvcAogICBvZmZzZXQ9IjAiCiAgIHN0b3AtY29sb3I9IiM3YmQ1ZjUiCiAgIGlkPSJzdG9wNTg3IiAvPgo8c3RvcAogICBvZmZzZXQ9IjEiCiAgIHN0b3AtY29sb3I9IiNkY2NmZTUiCiAgIGlkPSJzdG9wNTg5IiAvPgo8L2xpbmVhckdyYWRpZW50Pgo8cGF0aAogICBkPSJtIDEyLjQ0NDksMTIuNjUxIGMgLTIuOTI3OTUsLTAuMDYwNSAtNi40OTYzLDAuODcyMiAtMTAuNTUyMTEsMy44ODM1IDEuMjc0NDEsMS4xMzk3IDkuMTQwNzEsNy40ODA3IDE4LjczNzExLDIuMzA3OSAyLjM1NzUsLTEuNTUwNiAxLjkwMTgsLTEuMzY2NCAxLjkxMTMsLTEuMzY2OCAwLjA2NzMsMC4wMTAyIC0wLjY4MzQsLTAuNTYzOCAtMS41NjAzLC0xLjMyNTcgLTIuMjc1NSwtMi4xNTY1IC01LjI0ODcsLTMuMzgzNiAtOC4zODMsLTMuNDk1OCB6IgogICBpZD0iRmlsbF85IiAvPgo8bGluZWFyR3JhZGllbnQKICAgZ3JhZGllbnRUcmFuc2Zvcm09InRyYW5zbGF0ZSgtMC4wNDI4MjY4LC0wLjA0NTY4MikiCiAgIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIgogICBpZD0iTGluZWFyR3JhZGllbnRfOCIKICAgeDE9IjMxLjE5NjEiCiAgIHgyPSIzMS4xOTYxIgogICB5MT0iNjEuMzc2NDk5IgogICB5Mj0iMS44MDQ5OTk5Ij4KPHN0b3AKICAgb2Zmc2V0PSIwIgogICBzdG9wLWNvbG9yPSIjN2JkNWY1IgogICBpZD0ic3RvcDU5MyIgLz4KPHN0b3AKICAgb2Zmc2V0PSIxIgogICBzdG9wLWNvbG9yPSIjZGNjZmU1IgogICBpZD0ic3RvcDU5NSIgLz4KPC9saW5lYXJHcmFkaWVudD4KPGxpbmVhckdyYWRpZW50CiAgIGdyYWRpZW50VHJhbnNmb3JtPSJ0cmFuc2xhdGUoNC4yMDA2OCwwLjg2ODQ1OSkiCiAgIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIgogICBpZD0iTGluZWFyR3JhZGllbnRfOSIKICAgeDE9IjMxLjE5NjEiCiAgIHgyPSIzMS4xOTYxIgogICB5MT0iNjEuMzc2NDk5IgogICB5Mj0iMS44MDQ5OTk5Ij4KPHN0b3AKICAgb2Zmc2V0PSIwIgogICBzdG9wLWNvbG9yPSIjN2JkNWY1IgogICBpZD0ic3RvcDU5OCIgLz4KPHN0b3AKICAgb2Zmc2V0PSIxIgogICBzdG9wLWNvbG9yPSIjZGNjZmU1IgogICBpZD0ic3RvcDYwMCIgLz4KPC9saW5lYXJHcmFkaWVudD4KPHBhdGgKICAgZD0ibSAyMy42MTE0LDEyLjg0NjkgYyAtMC4wMDMyLDAgLTEuNzEwOSwwLjU0MTIgLTEuODI1NiwwLjk3NDEgLTAuMTE0NywwLjQzMjkgMC4zODU1LDEuNjU4NSAwLjM4NTUsMS42NTg1IGwgMC4wNTc0LDAuMTQ2NCAtMC4xMjExLDAuMDk4NyBjIC0wLjAwNjQsMC4wMDMyIC0xLjc4MSwxLjQxOTcgLTEuNjUwNCwxLjk5NTkgMC4xNTYxLDAuNjQ2MiAxLjM2NiwxLjkyNTYgMS4zNjYsMS45MjU2IDAsMCAzLjU4MTUsLTAuNjg5MiA0LjI4MzYsLTMuMDA5MSAwLjc2MywtMi41MjA5IC0yLjE4MDQsLTQuNTM0OCAtMi4yNTI5LC00LjU1NzEgLTAuMDcyNSwtMC4wMjIzIC0xLjMzODUsMC4yNjQxIC0wLjI0MjUsMC43NjcgeiIKICAgaWQ9IkZpbGxfMTAiIC8+CjxsaW5lYXJHcmFkaWVudAogICBncmFkaWVudFRyYW5zZm9ybT0idHJhbnNsYXRlKC0wLjA0MDE3NzcsLTAuMDQ3MTE4MikiCiAgIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIgogICBpZD0iTGluZWFyR3JhZGllbnRfMTAiCiAgIHgxPSIzMS4xOTYxIgogICB4Mj0iMzEuMTk2MSIKICAgeTE9IjYxLjM3NjQ5OSIKICAgeTI9IjEuODA0OTk5OSI+CjxzdG9wCiAgIG9mZnNldD0iMCIKICAgc3RvcC1jb2xvcj0iIzdiZDVmNSIKICAgaWQ9InN0b3A2MDQiIC8+CjxzdG9wCiAgIG9mZnNldD0iMSIKICAgc3RvcC1jb2xvcj0iI2RjY2ZlNSIKICAgaWQ9InN0b3A2MDYiIC8+CjwvbGluZWFyR3JhZGllbnQ+CjxsaW5lYXJHcmFkaWVudAogICBncmFkaWVudFRyYW5zZm9ybT0idHJhbnNsYXRlKDAuMzEyNjQyLC0wLjA3OTc5MzUpIgogICBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIKICAgaWQ9IkxpbmVhckdyYWRpZW50XzExIgogICB4MT0iMzEuMTk2MSIKICAgeDI9IjMxLjE5NjEiCiAgIHkxPSI2MS4zNzY0OTkiCiAgIHkyPSIxLjgwNDk5OTkiPgo8c3RvcAogICBvZmZzZXQ9IjAiCiAgIHN0b3AtY29sb3I9IiM3YmQ1ZjUiCiAgIGlkPSJzdG9wNjA5IiAvPgo8c3RvcAogICBvZmZzZXQ9IjEiCiAgIHN0b3AtY29sb3I9IiNkY2NmZTUiCiAgIGlkPSJzdG9wNjExIiAvPgo8L2xpbmVhckdyYWRpZW50PgoKCgoKPC9kZWZzPgo8ZwogICBpZD0iVW50aXRsZWQiCiAgIHZlY3Rvcm5hdG9yOmxheWVyTmFtZT0iVW50aXRsZWQiPgo8ZwogICBvcGFjaXR5PSIxIgogICBpZD0iZzYyOSI+CjxnCiAgIG9wYWNpdHk9IjEiCiAgIGlkPSJnNjI1Ij4KPHVzZQogICBmaWxsPSJ1cmwoI0xpbmVhckdyYWRpZW50KSIKICAgZmlsbC1ydWxlPSJub256ZXJvIgogICBzdHJva2U9Im5vbmUiCiAgIHhsaW5rOmhyZWY9IiNGaWxsIgogICBpZD0idXNlNjE2IgogICBzdHlsZT0iZmlsbDp1cmwoI0xpbmVhckdyYWRpZW50KSIgLz4KPG1hc2sKICAgaGVpZ2h0PSI2LjQ4MjgzIgogICBpZD0iU3Ryb2tlTWFzayIKICAgbWFza1VuaXRzPSJ1c2VyU3BhY2VPblVzZSIKICAgd2lkdGg9IjEwLjY0MDgiCiAgIHg9IjQ3LjgxNzIiCiAgIHk9IjIwLjU1MzMiPgo8cmVjdAogICBmaWxsPSIjZmZmZmZmIgogICBoZWlnaHQ9IjYuNDgyODMiCiAgIHN0cm9rZT0ibm9uZSIKICAgd2lkdGg9IjEwLjY0MDgiCiAgIHg9IjQ3LjgxNzIiCiAgIHk9IjIwLjU1MzMwMSIKICAgaWQ9InJlY3Q2MTgiIC8+Cjx1c2UKICAgZmlsbD0iIzAwMDAwMCIKICAgZmlsbC1ydWxlPSJldmVub2RkIgogICBzdHJva2U9Im5vbmUiCiAgIHhsaW5rOmhyZWY9IiNGaWxsIgogICBpZD0idXNlNjIwIiAvPgo8L21hc2s+Cjx1c2UKICAgZmlsbD0ibm9uZSIKICAgbWFzaz0idXJsKCNTdHJva2VNYXNrKSIKICAgc3Ryb2tlPSIjNTUyZjgyIgogICBzdHJva2UtbGluZWNhcD0iYnV0dCIKICAgc3Ryb2tlLWxpbmVqb2luPSJtaXRlciIKICAgc3Ryb2tlLXdpZHRoPSIwLjk2IgogICB4bGluazpocmVmPSIjRmlsbCIKICAgaWQ9InVzZTYyMyIgLz4KPC9nPgo8cGF0aAogICBkPSJtIDU3Ljc0MjMsMjUuNDYxIGMgMCwwIC00LjM1MzMsLTIuODY5NiAtOC43NzU3LC0zLjcxNzYgLTAuNTc1OSwwLjY3NzkgLTEuMTQ3OCwyLjQyNzkgLTAuMDQ2OSwzLjg4MzMgMC43NTIsMC4zODQgNC4yODc2LDIuMDEyNyA4LjgwOTIsLTAuMTUzNyB6IgogICBmaWxsPSIjNzc3YWJhIgogICBmaWxsLXJ1bGU9ImV2ZW5vZGQiCiAgIG9wYWNpdHk9IjEiCiAgIHN0cm9rZT0ibm9uZSIKICAgaWQ9InBhdGg2MjciIC8+CjwvZz4KPGcKICAgb3BhY2l0eT0iMSIKICAgaWQ9Imc2NDQiPgo8ZwogICBvcGFjaXR5PSIxIgogICBpZD0iZzY0MCI+Cjx1c2UKICAgZmlsbD0idXJsKCNMaW5lYXJHcmFkaWVudF8yKSIKICAgZmlsbC1ydWxlPSJub256ZXJvIgogICBzdHJva2U9Im5vbmUiCiAgIHhsaW5rOmhyZWY9IiNGaWxsXzIiCiAgIGlkPSJ1c2U2MzEiCiAgIHN0eWxlPSJmaWxsOnVybCgjTGluZWFyR3JhZGllbnRfMikiIC8+CjxtYXNrCiAgIGhlaWdodD0iNy41MzEyNyIKICAgaWQ9IlN0cm9rZU1hc2tfMiIKICAgbWFza1VuaXRzPSJ1c2VyU3BhY2VPblVzZSIKICAgd2lkdGg9IjE0LjAzMjIiCiAgIHg9IjQ3LjE5MDQiCiAgIHk9IjEzLjkyNjIiPgo8cmVjdAogICBmaWxsPSIjZmZmZmZmIgogICBoZWlnaHQ9IjcuNTMxMjciCiAgIHN0cm9rZT0ibm9uZSIKICAgd2lkdGg9IjE0LjAzMjIiCiAgIHg9IjQ3LjE5MDM5OSIKICAgeT0iMTMuOTI2MiIKICAgaWQ9InJlY3Q2MzMiIC8+Cjx1c2UKICAgZmlsbD0iIzAwMDAwMCIKICAgZmlsbC1ydWxlPSJldmVub2RkIgogICBzdHJva2U9Im5vbmUiCiAgIHhsaW5rOmhyZWY9IiNGaWxsXzIiCiAgIGlkPSJ1c2U2MzUiIC8+CjwvbWFzaz4KPHVzZQogICBmaWxsPSJub25lIgogICBtYXNrPSJ1cmwoI1N0cm9rZU1hc2tfMikiCiAgIHN0cm9rZT0iIzU1MmY4MiIKICAgc3Ryb2tlLWxpbmVjYXA9ImJ1dHQiCiAgIHN0cm9rZS1saW5lam9pbj0ibWl0ZXIiCiAgIHN0cm9rZS13aWR0aD0iMC45NiIKICAgeGxpbms6aHJlZj0iI0ZpbGxfMiIKICAgaWQ9InVzZTYzOCIgLz4KPC9nPgo8cGF0aAogICBkPSJtIDU5LjY1OTMsMTUuMjM5NiBjIDAsMCAtNi42NjY5LDIuNTY1MyAtMTEuNjMwMSwxLjQ3NzMgLTEuMDc2NCwyLjI2MzUgMC40MjA1LDMuMjkyMSAxLjMzNzUsMy45NDA1IDAuODU3NiwwLjI5MTIgNi4wNzQzLDEuNzE4OCAxMS4yNTgzLC00Ljk1IC0wLjMwODYsLTAuMTY4OSAtMC42MjcsLTAuMzQ5NyAtMC45NTc1LC0wLjQ3MDUgeiIKICAgZmlsbD0iIzc3N2FiYSIKICAgZmlsbC1ydWxlPSJldmVub2RkIgogICBvcGFjaXR5PSIxIgogICBzdHJva2U9Im5vbmUiCiAgIGlkPSJwYXRoNjQyIiAvPgo8L2c+CjxnCiAgIG9wYWNpdHk9IjEiCiAgIGlkPSJnNjU5Ij4KPGcKICAgb3BhY2l0eT0iMSIKICAgaWQ9Imc2NTUiPgo8dXNlCiAgIGZpbGw9InVybCgjTGluZWFyR3JhZGllbnRfMykiCiAgIGZpbGwtcnVsZT0ibm9uemVybyIKICAgc3Ryb2tlPSJub25lIgogICB4bGluazpocmVmPSIjRmlsbF8zIgogICBpZD0idXNlNjQ2IgogICBzdHlsZT0iZmlsbDp1cmwoI0xpbmVhckdyYWRpZW50XzMpIiAvPgo8bWFzawogICBoZWlnaHQ9IjEwLjYwMjUiCiAgIGlkPSJTdHJva2VNYXNrXzMiCiAgIG1hc2tVbml0cz0idXNlclNwYWNlT25Vc2UiCiAgIHdpZHRoPSIxNS42NDM3IgogICB4PSI0NC41Mzg0IgogICB5PSI1Ljc3OTkiPgo8cmVjdAogICBmaWxsPSIjZmZmZmZmIgogICBoZWlnaHQ9IjEwLjYwMjUiCiAgIHN0cm9rZT0ibm9uZSIKICAgd2lkdGg9IjE1LjY0MzciCiAgIHg9IjQ0LjUzODM5OSIKICAgeT0iNS43Nzk5MDAxIgogICBpZD0icmVjdDY0OCIgLz4KPHVzZQogICBmaWxsPSIjMDAwMDAwIgogICBmaWxsLXJ1bGU9ImV2ZW5vZGQiCiAgIHN0cm9rZT0ibm9uZSIKICAgeGxpbms6aHJlZj0iI0ZpbGxfMyIKICAgaWQ9InVzZTY1MCIgLz4KPC9tYXNrPgo8dXNlCiAgIGZpbGw9Im5vbmUiCiAgIG1hc2s9InVybCgjU3Ryb2tlTWFza18zKSIKICAgc3Ryb2tlPSIjNTUyZjgyIgogICBzdHJva2UtbGluZWNhcD0iYnV0dCIKICAgc3Ryb2tlLWxpbmVqb2luPSJtaXRlciIKICAgc3Ryb2tlLXdpZHRoPSIwLjk2IgogICB4bGluazpocmVmPSIjRmlsbF8zIgogICBpZD0idXNlNjUzIiAvPgo8L2c+CjxwYXRoCiAgIGQ9Im0gNTkuNTk3LDYuMjk0ODMgYyAwLDAgLTYuOTc0MSwwLjY1MTc2IC0xMy4zNzQxLDguMjkwMTcgMC41NTg4LDAuNjQzNiAwLjk0MiwwLjczMzggMS45NTEsMS4yNDg0IDEwLjIyMDgsLTIuNDU3NiAxMS4yNTk3LC04LjI4NjY0IDExLjQzMzMsLTkuNTI0MDIgeiIKICAgZmlsbD0iIzc3N2FiYSIKICAgZmlsbC1ydWxlPSJldmVub2RkIgogICBvcGFjaXR5PSIxIgogICBzdHJva2U9Im5vbmUiCiAgIGlkPSJwYXRoNjU3IiAvPgo8L2c+CjxnCiAgIG9wYWNpdHk9IjEiCiAgIGlkPSJnNjg5Ij4KPGcKICAgb3BhY2l0eT0iMSIKICAgaWQ9Imc2NzAiPgo8dXNlCiAgIGZpbGw9IiM3NzdhYmEiCiAgIGZpbGwtcnVsZT0ibm9uemVybyIKICAgc3Ryb2tlPSJub25lIgogICB4bGluazpocmVmPSIjRmlsbF80IgogICBpZD0idXNlNjYxIiAvPgo8bWFzawogICBoZWlnaHQ9IjIxLjEzMzEiCiAgIGlkPSJTdHJva2VNYXNrXzQiCiAgIG1hc2tVbml0cz0idXNlclNwYWNlT25Vc2UiCiAgIHdpZHRoPSIyNi41MjM2IgogICB4PSI3LjAxNjUzIgogICB5PSI0MC40MzY5Ij4KPHJlY3QKICAgZmlsbD0iI2ZmZmZmZiIKICAgaGVpZ2h0PSIyMS4xMzMxMDEiCiAgIHN0cm9rZT0ibm9uZSIKICAgd2lkdGg9IjI2LjUyMzYiCiAgIHg9IjcuMDE2NTMiCiAgIHk9IjQwLjQzNjkwMSIKICAgaWQ9InJlY3Q2NjMiIC8+Cjx1c2UKICAgZmlsbD0iIzAwMDAwMCIKICAgZmlsbC1ydWxlPSJldmVub2RkIgogICBzdHJva2U9Im5vbmUiCiAgIHhsaW5rOmhyZWY9IiNGaWxsXzQiCiAgIGlkPSJ1c2U2NjUiIC8+CjwvbWFzaz4KPHVzZQogICBmaWxsPSJub25lIgogICBtYXNrPSJ1cmwoI1N0cm9rZU1hc2tfNCkiCiAgIHN0cm9rZT0iIzU1MmY4MiIKICAgc3Ryb2tlLWxpbmVjYXA9ImJ1dHQiCiAgIHN0cm9rZS1saW5lam9pbj0ibWl0ZXIiCiAgIHN0cm9rZS13aWR0aD0iMC45NiIKICAgeGxpbms6aHJlZj0iI0ZpbGxfNCIKICAgaWQ9InVzZTY2OCIgLz4KPC9nPgo8ZwogICBvcGFjaXR5PSIxIgogICBpZD0iZzY4MSI+Cjx1c2UKICAgZmlsbD0idXJsKCNMaW5lYXJHcmFkaWVudF80KSIKICAgZmlsbC1ydWxlPSJldmVub2RkIgogICBzdHJva2U9Im5vbmUiCiAgIHhsaW5rOmhyZWY9IiNGaWxsXzUiCiAgIGlkPSJ1c2U2NzIiCiAgIHN0eWxlPSJmaWxsOnVybCgjTGluZWFyR3JhZGllbnRfNCkiIC8+CjxtYXNrCiAgIGhlaWdodD0iMjcuOTMzOCIKICAgaWQ9IlN0cm9rZU1hc2tfNSIKICAgbWFza1VuaXRzPSJ1c2VyU3BhY2VPblVzZSIKICAgd2lkdGg9IjQwLjU5OTQiCiAgIHg9IjEwLjIwNzQiCiAgIHk9IjMwLjg2OSI+CjxyZWN0CiAgIGZpbGw9IiNmZmZmZmYiCiAgIGhlaWdodD0iMjcuOTMzOCIKICAgc3Ryb2tlPSJub25lIgogICB3aWR0aD0iNDAuNTk5NCIKICAgeD0iMTAuMjA3NCIKICAgeT0iMzAuODY4OTk5IgogICBpZD0icmVjdDY3NCIgLz4KPHVzZQogICBmaWxsPSIjMDAwMDAwIgogICBmaWxsLXJ1bGU9ImV2ZW5vZGQiCiAgIHN0cm9rZT0ibm9uZSIKICAgeGxpbms6aHJlZj0iI0ZpbGxfNSIKICAgaWQ9InVzZTY3NiIgLz4KPC9tYXNrPgo8dXNlCiAgIGZpbGw9Im5vbmUiCiAgIG1hc2s9InVybCgjU3Ryb2tlTWFza181KSIKICAgc3Ryb2tlPSIjNTUyZjgyIgogICBzdHJva2UtbGluZWNhcD0iYnV0dCIKICAgc3Ryb2tlLWxpbmVqb2luPSJtaXRlciIKICAgc3Ryb2tlLXdpZHRoPSIwLjk2IgogICB4bGluazpocmVmPSIjRmlsbF81IgogICBpZD0idXNlNjc5IiAvPgo8L2c+CjxwYXRoCiAgIGQ9Im0gMjMuNjk3NSwzNi40MDczIGMgLTAuMDk2MSwwLjg1OSAtMC4wODMyLDEuNzI2NiAwLjAzODQsMi41ODI0IHYgMC4xMDU2IGwgLTAuMDczNiwwLjA3MzYgYyAwLDAgLTMuOTIsMy45NjQ4IC0yLjg0NDgsNy4yNzM2IC0wLjM1NTIsLTIuMzI5NiAxLjkyLC01LjQ3MiAzLjQ2MjQsLTcuMTg3MiAtMC4yNTkyLC0wLjkwMjQgLTAuMzIsLTMuOTcxMiAtMC4yNzg0LC00LjQ1MTIgLTAuMTQ2NCwwLjUyMzYgLTAuMjQ3LDEuMDU5IC0wLjMwMDgsMS42IHoiCiAgIGZpbGw9IiM3NzdhYmEiCiAgIGZpbGwtcnVsZT0iZXZlbm9kZCIKICAgb3BhY2l0eT0iMSIKICAgc3Ryb2tlPSJub25lIgogICBpZD0icGF0aDY4MyIgLz4KPHBhdGgKICAgZD0ibSAyNS4yMDExLDM3LjA3MjIgYyAwLjY3NTIsMi4wMTI4IDIuMTAyNCw0LjE2IDIuODgsNS4yNzM2IDAuNDczNiwwLjcwNCAwLjgxNiwxLjIxMjggMC44MzIsMS40NTkyIDAuMDM1MiwwLjUyNDggLTAuNzc3NiwxLjQxNDQgLTEuMjgsMS45MiAwLDAgMS43NTA0LC0wLjg1NzYgMS42LC0yLjI3MiAwLDAgLTMuNTg3MiwtNS44MDggLTMuMzk4NCwtNi44MTI4IDAsMCAwLjU0NzIsLTEuNjk5MiAwLjg4OTYsLTEuOTc0NCAwLDAgLTEuNzEyLDAuOTM0NCAtMS41MDA4LDIuNDEyOCB6IgogICBmaWxsPSIjNzc3YWJhIgogICBmaWxsLXJ1bGU9ImV2ZW5vZGQiCiAgIG9wYWNpdHk9IjEiCiAgIHN0cm9rZT0ibm9uZSIKICAgaWQ9InBhdGg2ODUiIC8+CjxwYXRoCiAgIGQ9Im0gMzYuOTk1OCwzNS44MzA1IGMgMC41NjMyLDAuOTYgMC4zNDU2LDQuODUxMiAwLjA3NjgsNS42NTc2IC0wLjM5NjgsMS4yIC0xLjQ3MiwyLjQyODggLTIuMDgzMiwzLjA2MjQgMS4yMTYsLTAuNTEyIDIuNTEyLC0zLjA5NzYgMi41MTIsLTMuMDk3NiAwLjI1OTIsLTEuNjggMC41MTUyLC00LjE2IC0wLjUwNTYsLTUuNjIyNCB6IgogICBmaWxsPSIjNzc3YWJhIgogICBmaWxsLXJ1bGU9ImV2ZW5vZGQiCiAgIG9wYWNpdHk9IjEiCiAgIHN0cm9rZT0ibm9uZSIKICAgaWQ9InBhdGg2ODciIC8+CjwvZz4KPGcKICAgb3BhY2l0eT0iMSIKICAgaWQ9Imc3MDQiPgo8ZwogICBvcGFjaXR5PSIxIgogICBpZD0iZzcwMCI+Cjx1c2UKICAgZmlsbD0idXJsKCNMaW5lYXJHcmFkaWVudF81KSIKICAgZmlsbC1ydWxlPSJub256ZXJvIgogICBzdHJva2U9Im5vbmUiCiAgIHhsaW5rOmhyZWY9IiNGaWxsXzYiCiAgIGlkPSJ1c2U2OTEiCiAgIHN0eWxlPSJmaWxsOnVybCgjTGluZWFyR3JhZGllbnRfNSkiIC8+CjxtYXNrCiAgIGhlaWdodD0iMjYuNzg5OCIKICAgaWQ9IlN0cm9rZU1hc2tfNiIKICAgbWFza1VuaXRzPSJ1c2VyU3BhY2VPblVzZSIKICAgd2lkdGg9IjMyLjE1NzMiCiAgIHg9IjE3LjI0ODkiCiAgIHk9IjcuMDIwMjQiPgo8cmVjdAogICBmaWxsPSIjZmZmZmZmIgogICBoZWlnaHQ9IjI2Ljc4OTgwMSIKICAgc3Ryb2tlPSJub25lIgogICB3aWR0aD0iMzIuMTU3Mjk5IgogICB4PSIxNy4yNDg4OTkiCiAgIHk9IjcuMDIwMjM5OCIKICAgaWQ9InJlY3Q2OTMiIC8+Cjx1c2UKICAgZmlsbD0iIzAwMDAwMCIKICAgZmlsbC1ydWxlPSJldmVub2RkIgogICBzdHJva2U9Im5vbmUiCiAgIHhsaW5rOmhyZWY9IiNGaWxsXzYiCiAgIGlkPSJ1c2U2OTUiIC8+CjwvbWFzaz4KPHVzZQogICBmaWxsPSJub25lIgogICBtYXNrPSJ1cmwoI1N0cm9rZU1hc2tfNikiCiAgIHN0cm9rZT0iIzU1MmY4MiIKICAgc3Ryb2tlLWxpbmVjYXA9ImJ1dHQiCiAgIHN0cm9rZS1saW5lam9pbj0ibWl0ZXIiCiAgIHN0cm9rZS13aWR0aD0iMC45NiIKICAgeGxpbms6aHJlZj0iI0ZpbGxfNiIKICAgaWQ9InVzZTY5OCIgLz4KPC9nPgo8cGF0aAogICBkPSJNIDM4LjEzNjUsMzMuMTMzMSBDIDIyLjM5NTcsMzEuODkxNSAyMC44OTQ4LDIyLjY2MjMgMjAuODUsMjIuMzU1MSBjIC0yLjEzNDUsLTEuMzkwNyAtMy4wMTc3LDAuNTYzOCAtMS44MDQ1LDMuMTczNiAwLjk1MTYsMS42NzM2IDIuMzE5LDMuMDY5OCAzLjkyNTQsNC4xMzA5IDIuOTc4MiwxLjk4ODggNi4zNjg2LDMuMTM0NyA5LjkyODYsMy41MjM2IDEuNzQ5MiwwLjIxNDQgMy41MDA3LDAuMTgzOCA1LjI0OTksLTAuMDMwNiB6IgogICBmaWxsPSIjNzc3YWJhIgogICBmaWxsLXJ1bGU9ImV2ZW5vZGQiCiAgIG9wYWNpdHk9IjEiCiAgIHN0cm9rZT0ibm9uZSIKICAgaWQ9InBhdGg3MDIiIC8+CjwvZz4KPGcKICAgb3BhY2l0eT0iMSIKICAgaWQ9Imc3MTAiPgo8cGF0aAogICBkPSJtIDMyLjM5NDUsMjguNzk5OCBjIDEuMTIsMS4xMTY4IDkuNTEwNCwxLjAxNDQgOS45NTg0LDAuMDIyNCAtMiw1LjYxMjggLTkuNDE0NCwzLjI2NCAtOS45NTg0LC0wLjAyMjQgeiIKICAgZmlsbD0iIzU1MmY4MiIKICAgZmlsbC1ydWxlPSJldmVub2RkIgogICBvcGFjaXR5PSIxIgogICBzdHJva2U9Im5vbmUiCiAgIGlkPSJwYXRoNzA2IiAvPgo8cGF0aAogICBkPSJtIDQwLjY0MDUsMzAuNzE5NSBjIC0wLjA3NjgsMC40MTI4IC0zLjM2MzIsMi4yMTQ0IC02LjA0NDgsMC4xMTIgMCwwIDEuNzk1MiwtMS4xMiA2LjA0NDgsLTAuMTEyIHoiCiAgIGZpbGw9IiNjM2EzY2QiCiAgIGZpbGwtcnVsZT0iZXZlbm9kZCIKICAgb3BhY2l0eT0iMSIKICAgc3Ryb2tlPSJub25lIgogICBpZD0icGF0aDcwOCIgLz4KPC9nPgo8ZwogICBvcGFjaXR5PSIxIgogICBpZD0iZzcxNiI+CjxwYXRoCiAgIGQ9Im0gNDAuMDczLDI2LjY2MjQgYyAtMC4yMzQyLDAuMjk4NSAtMC40MjU5LDAuNjI4MSAtMC41Njk2LDAuOTc5MiAtMC4wMzIsMC4yNDMyIDAuNDY3MiwtMC41Mzc2IDEuMTM5MiwtMC41ODU2IDAuMTMsLTAuMDE1NSAwLjI1NDcsLTAuMDYwNCAwLjM2NDgsLTAuMTMxMiAwLjA1NDEsLTAuMDQzOCAwLjA5MjQsLTAuMTA0IDAuMTA5MiwtMC4xNzE2IDAuMDE2NywtMC4wNjc2IDAuMDExLC0wLjEzODggLTAuMDE2NCwtMC4yMDI4IC0wLjA0MzcsLTAuMDY3OCAtMC4xMDMyLC0wLjEyMzkgLTAuMTczNSwtMC4xNjM1IC0wLjA3MDIsLTAuMDM5NiAtMC4xNDkxLC0wLjA2MTQgLTAuMjI5NywtMC4wNjM3IC0wLjEyNDMsMC4wMDk1IC0wLjI0NTMsMC4wNDQ1IC0wLjM1NTQsMC4xMDI5IC0wLjExMDEsMC4wNTgzIC0wLjIwNywwLjEzODggLTAuMjg0NiwwLjIzNjMgeiIKICAgZmlsbD0iIzU1MmY4MiIKICAgZmlsbC1ydWxlPSJldmVub2RkIgogICBvcGFjaXR5PSIxIgogICBzdHJva2U9Im5vbmUiCiAgIGlkPSJwYXRoNzEyIiAvPgo8cGF0aAogICBkPSJtIDM3LjQzOTksMjcuNjYzNSBjIC0wLjEzNjIsLTAuMzk5NSAtMC4zMzAzLC0wLjc3NjggLTAuNTc2LC0xLjEyIC0wLjA3NDQsLTAuMTA1OCAtMC4xNjk1LC0wLjE5NTYgLTAuMjc5NSwtMC4yNjM3IC0wLjExLC0wLjA2ODIgLTAuMjMyNiwtMC4xMTM1IC0wLjM2MDUsLTAuMTMzMSAtMC4wOTU5LC0wLjAwMjggLTAuMTkwOSwwLjAxOSAtMC4yNzYsMC4wNjMzIC0wLjA4NTIsMC4wNDQyIC0wLjE1NzYsMC4xMDk0IC0wLjIxMDQsMC4xODk1IC0wLjAzODQsMC4wNjcyIC0wLjA1MTIsMC4xNDYxIC0wLjAzNTksMC4yMjIgMC4wMTUzLDAuMDc1OSAwLjA1NzYsMC4xNDM3IDAuMTE5MSwwLjE5MDggMC4xMTg2LDAuMDg0NSAwLjI1NTgsMC4xMzk0IDAuNCwwLjE2IDAuNzQ4OCwwLjA3NjggMS4yNTQ0LDAuOTYgMS4yMzUyLDAuNjkxMiB6IgogICBmaWxsPSIjNTUyZjgyIgogICBmaWxsLXJ1bGU9ImV2ZW5vZGQiCiAgIG9wYWNpdHk9IjEiCiAgIHN0cm9rZT0ibm9uZSIKICAgaWQ9InBhdGg3MTQiIC8+CjwvZz4KPGcKICAgb3BhY2l0eT0iMSIKICAgaWQ9Imc3MjYiPgo8cGF0aAogICBkPSJtIDQzLjEwOCwxOC41Njg2IGMgMC43MzI4LC0xLjk2NjQgMi4yNzMsLTIuMTU1MyAzLjQ3NTMsLTAuNDI2OSAwLjY0NzMsMSAxLjA0OTEsMi4xMzQzIDEuMTczOCwzLjMxMzQgMC4xNzY5LDEuMTczOCAwLjA3MjEsMi4zNzIgLTAuMzA1OSwzLjQ5ODkgLTAuNzExNCwxLjk2MjggLTIuMjczLDIuMTUxOCAtMy40NzUzLDAuNDI2OCAtMC42NDQyLC0xLjAwMTQgLTEuMDQ1OCwtMi4xMzUgLTEuMTczOCwtMy4zMTM0IC0wLjE3MzUsLTEuMTczOSAtMC4wNjg4LC0yLjM3MTMgMC4zMDU5LC0zLjQ5ODggeiBtIDEuOTEzNywwLjY0NzIgYyAtMC4zNTU3LDAuMDUyNSAtMC42NTQ1LDAuMzkxOSAtMC44MzU5LDAuODk1NyAtMC4zNzM1LDEuMDQ5NyAtMC4yMzgzLDIuNjk0MiAwLjQwMiwzLjYyODQgMC42NDAyLDAuOTM0MiAxLjQyMjgsMC42OTk3IDEuNzc4NSwtMC4zNDk5IDAuMzU1NywtMS4wNDk3IDAuMjM4MywtMi42OTA2IC0wLjQwMTksLTMuNjI4MyAtMC4wODY4LC0wLjE3MzMgLTAuMjIyNiwtMC4zMTgzIC0wLjM5MTEsLTAuNDE3NiAtMC4xNjg0LC0wLjA5OTMgLTAuMzYyNCwtMC4xNDg3IC0wLjU1ODcsLTAuMTQyMyB6IgogICBmaWxsPSIjNTUyZjgyIgogICBmaWxsLXJ1bGU9ImV2ZW5vZGQiCiAgIG9wYWNpdHk9IjEiCiAgIHN0cm9rZT0ibm9uZSIKICAgaWQ9InBhdGg3MTgiIC8+CjxwYXRoCiAgIGQ9Im0gNDMuMzQ2NSwxOC44ODA0IGMgMC42NTkyLC0xLjc5ODQgMi4wNDQ4LC0xLjk3MTIgMy4xMjY0LC0wLjM5MDQgMC41ODIzLDAuOTE0NSAwLjk0MzgsMS45NTIgMS4wNTYsMy4wMzA0IDAuMTU5MSwxLjA3MzUgMC4wNjQ4LDIuMTY5NCAtMC4yNzUyLDMuMiAtMC42NCwxLjc5NTIgLTIuMDQ0OCwxLjk2OCAtMy4xMjY0LDAuMzkwNCAtMC41Nzk2LC0wLjkxNTkgLTAuOTQwOSwtMS45NTI3IC0xLjA1NiwtMy4wMzA0IC0wLjE1NjEsLTEuMDczNyAtMC4wNjE5LC0yLjE2ODggMC4yNzUyLC0zLjIgeiBtIDEuNzIxNiwwLjU5MiBjIC0wLjMyLDAuMDQ4IC0wLjU4ODgsMC4zNTg0IC0wLjc1MiwwLjgxOTIgLTAuMzM2LDAuOTYgLTAuMjE0NCwyLjQ2NCAwLjM2MTYsMy4zMTg0IDAuNTc2LDAuODU0NCAxLjI4LDAuNjQgMS42LC0wLjMyIDAuMzIsLTAuOTYgMC4yMTQ0LC0yLjQ2MDggLTAuMzYxNiwtMy4zMTg0IC0wLjA3ODEsLTAuMTU4NSAtMC4yMDAzLC0wLjI5MTEgLTAuMzUxOCwtMC4zODE5IC0wLjE1MTUsLTAuMDkwOCAtMC4zMjYxLC0wLjEzNiAtMC41MDI2LC0wLjEzMDEgeiIKICAgZmlsbD0iI2ZmZmZmZiIKICAgZmlsbC1ydWxlPSJldmVub2RkIgogICBvcGFjaXR5PSIxIgogICBzdHJva2U9Im5vbmUiCiAgIGlkPSJwYXRoNzIwIiAvPgo8cGF0aAogICBkPSJtIDQ0Ljc2NzgsMjMuNzU0IGMgLTAuNjgxNiwtMS4wMTc2IC0wLjgyNTYsLTIuODM1MiAtMC40Mjg4LC0zLjk1ODQgMC4zNjE2LC0xLjAyNzIgMS4wODQ4LC0xLjI2MDggMS42OTkyLC0wLjM0MjQgMC42ODQ4LDEuMDE3NiAwLjgyODgsMi44MzUyIDAuNDMyLDMuOTU4NCAtMC4zNjQ4LDEuMDI3MiAtMS4wODQ4LDEuMjYwOCAtMS43MDI0LDAuMzQyNCB6IgogICBmaWxsPSIjNTUyZjgyIgogICBmaWxsLXJ1bGU9ImV2ZW5vZGQiCiAgIG9wYWNpdHk9IjEiCiAgIHN0cm9rZT0ibm9uZSIKICAgaWQ9InBhdGg3MjIiIC8+CjxwYXRoCiAgIGQ9Im0gNDQuOTQ0OSwxOC4wNzYzIGMgLTAuNDY0LDAuMDQxNiAtMC43NzQ0LDAuNzgwOCAtMC42OTEyLDEuNjUxMiAwLjA4MzIsMC44NzA0IDAuNTI0OCwxLjUzMjggMC45ODg4LDEuNDkxMiAwLjQ2NCwtMC4wNDE2IDAuNzcxMiwtMC43ODQgMC42OTEyLC0xLjY1MTIgLTAuMDgsLTAuODY3MiAtMC41MjQ4LC0xLjUzNiAtMC45ODg4LC0xLjQ5MTIgeiIKICAgZmlsbD0iI2ZmZmZmZiIKICAgZmlsbC1ydWxlPSJldmVub2RkIgogICBvcGFjaXR5PSIxIgogICBzdHJva2U9Im5vbmUiCiAgIGlkPSJwYXRoNzI0IiAvPgo8L2c+CjxnCiAgIG9wYWNpdHk9IjEiCiAgIGlkPSJnNzM2Ij4KPHBhdGgKICAgZD0ibSAzMi4wMzgxLDE4LjA1IGMgLTAuNzA3OSwtMi4zNjI0IC0yLjU2NiwtMi43NDM3IC00LjE3OTksLTAuODI2OCAtMC44NzQzLDEuMTE3MSAtMS40NjIyLDIuNDEzOSAtMS43MTY2LDMuNzg2NSAtMC4zMTQ3LDEuMzY2MyAtMC4yOTQxLDIuNzgzNSAwLjA2MDIsNC4xNDA5IDAuNzA3OCwyLjM2MjQgMi41NjYsMi43NDM3IDQuMTc5OSwwLjgyMzUgMC44NzM4LC0xLjExOTkgMS40NjE2LC0yLjQxODggMS43MTY2LC0zLjc5MzMgMC4zMTQ2LC0xLjM2NTIgMC4yOTQsLTIuNzgxMiAtMC4wNjAyLC00LjEzNzUgeiBtIC0yLjM3ODQsMC41ODA1IGMgMC40MzE4LDAuMDk3OCAwLjc1NzQsMC41MjMxIDAuOTM0NCwxLjEzMzkgMC4zNTM5LDEuMjUyMSAwLjA0NiwzLjE4OTIgLTAuODEwNSw0LjIzMiAtMC44NTY2LDEuMDQyOCAtMS43Njk3LDAuNjc1IC0yLjEyMzYsLTAuNTYzNiAtMC4zNTQsLTEuMjM4NSAtMC4wNjczLC0zLjE4NTggMC43OTYzLC00LjIyODYgMC4zNTM5LC0wLjQ0NTUgMC43NzUxLC0wLjY3NSAxLjIwMzQsLTAuNTczNyB6IgogICBmaWxsPSIjNTUyZjgyIgogICBmaWxsLXJ1bGU9ImV2ZW5vZGQiCiAgIG9wYWNpdHk9IjEiCiAgIHN0cm9rZT0ibm9uZSIKICAgaWQ9InBhdGg3MjgiIC8+CjxwYXRoCiAgIGQ9Im0gMzEuNzQ2NiwxOC4yNTMyIGMgLTAuNjM3MSwtMi4yMjc0IC0yLjMwOTQsLTIuNTg3IC0zLjc2MTksLTAuNzc5NiAtMC43ODY5LDEuMDUzMyAtMS4zMTYsMi4yNzYgLTEuNTQ1LDMuNTcwMiAtMC4yODMyLDEuMjg4MiAtMC4yNjQ2LDIuNjI0MyAwLjA1NDIsMy45MDQyIDAuNjM3MSwyLjIyNzQgMi4zMDk0LDIuNTg3IDMuNzYxOSwwLjc3NjQgMC43ODY1LC0xLjA1NTkgMS4zMTU1LC0yLjI4MDUgMS41NDUsLTMuNTc2NSAwLjI4MzEsLTEuMjg3MSAwLjI2NDYsLTIuNjIyMyAtMC4wNTQyLC0zLjkwMTEgeiBtIC0yLjE0MDYsMC41NDczIGMgMC4zODg2LDAuMDkyMyAwLjY4MTcsMC40OTMyIDAuODQxLDEuMDY5MSAwLjMxODUsMS4xODA1IDAuMDQxNCwzLjAwNyAtMC43Mjk1LDMuOTkwMiAtMC43NzA5LDAuOTgzMiAtMS41OTI3LDAuNjM2NCAtMS45MTEyLC0wLjUzMTQgLTAuMzE4NiwtMS4xNjc4IC0wLjA2MDUsLTMuMDAzOCAwLjcxNjcsLTMuOTg3IDAuMzE4NSwtMC40MiAwLjY5NzYsLTAuNjM2NCAxLjA4MywtMC41NDA5IHoiCiAgIGZpbGw9IiNmZmZmZmYiCiAgIGZpbGwtcnVsZT0iZXZlbm9kZCIKICAgb3BhY2l0eT0iMSIKICAgc3Ryb2tlPSJub25lIgogICBpZD0icGF0aDczMCIgLz4KPHBhdGgKICAgZD0ibSAyOS43NTk1LDIzLjkxNjkgYyAwLjkwODgsLTEuMTYxNiAxLjI0MTYsLTMuMzM3NiAwLjg2MDgsLTQuNzI5NiAtMC4zNDg4LC0xLjI3OTkgLTEuMiwtMS42Mjg3IC0yLjAyMjQsLTAuNTgyMyAtMC45MTIsMS4xNjE1IC0xLjI0NDgsMy4zMzc1IC0wLjg2MDgsNC43Mjk1IDAuMzQ4OCwxLjI4IDEuMTk2OCwxLjYyNTYgMi4wMjI0LDAuNTgyNCB6IgogICBmaWxsPSIjNTUyZjgyIgogICBmaWxsLXJ1bGU9ImV2ZW5vZGQiCiAgIG9wYWNpdHk9IjEiCiAgIHN0cm9rZT0ibm9uZSIKICAgaWQ9InBhdGg3MzIiIC8+CjxwYXRoCiAgIGQ9Im0gMjkuOTc4LDE2Ljk3OTMgYyAwLjU2LDAuMDk5MiAwLjg3MzYsMS4wMzA0IDAuNjk3NiwyLjA4IC0wLjE3NiwxLjA0OTYgLTAuNzc0NCwxLjgyMDggLTEuMzM0NCwxLjcyMTYgLTAuNTYsLTAuMDk5MiAtMC44NjA4LC0xLjAzMzYgLTAuNzAwOCwtMi4wODMyIDAuMTYsLTEuMDQ5NiAwLjc3NDQsLTEuODE3NiAxLjMzNzYsLTEuNzE4NCB6IgogICBmaWxsPSIjZmZmZmZmIgogICBmaWxsLXJ1bGU9ImV2ZW5vZGQiCiAgIG9wYWNpdHk9IjEiCiAgIHN0cm9rZT0ibm9uZSIKICAgaWQ9InBhdGg3MzQiIC8+CjwvZz4KPGcKICAgb3BhY2l0eT0iMSIKICAgaWQ9Imc3NDQiPgo8cGF0aAogICBkPSJtIDUzLjYyNTcsMzYuNjQ5NCBjIDAuNDcwNiwtNmUtNCAwLjkzMDgsMC4xMzgyIDEuMzIyNSwwLjM5OSAwLjM5MTgsMC4yNjA4IDAuNjk3NCwwLjYzMTkgMC44Nzg0LDEuMDY2MyAwLjE4MSwwLjQzNDQgMC4yMjkxLDAuOTEyNyAwLjEzODQsMS4zNzQ1IC0wLjA5MDcsMC40NjE4IC0wLjMxNjMsMC44ODYzIC0wLjY0ODIsMS4yMiAtMC4zMzE4LDAuMzMzNiAtMC43NTUxLDAuNTYxNSAtMS4yMTY0LDAuNjU0NyAtMC40NjEzLDAuMDkzMiAtMC45Mzk5LDAuMDQ3NiAtMS4zNzUzLC0wLjEzMTEgLTAuNDM1MywtMC4xNzg2IC0wLjgwOCwtMC40ODIzIC0xLjA3MDksLTAuODcyNiAtMC4yNjI5LC0wLjM5MDMgLTAuNDA0MiwtMC44NDk4IC0wLjQwNjEsLTEuMzIwNCAtMC4wMDEzLC0wLjMxMzIgMC4wNTkyLC0wLjYyMzUgMC4xNzgxLC0wLjkxMzMgMC4xMTg5LC0wLjI4OTcgMC4yOTM4LC0wLjU1MzIgMC41MTQ3LC0wLjc3NTIgMC4yMjA4LC0wLjIyMjEgMC40ODMzLC0wLjM5ODQgMC43NzI0LC0wLjUxODggMC4yODkxLC0wLjEyMDUgMC41OTkyLC0wLjE4MjcgMC45MTI0LC0wLjE4MzEgeiIKICAgZmlsbD0iIzhiZDRmMyIKICAgZmlsbC1ydWxlPSJldmVub2RkIgogICBvcGFjaXR5PSIxIgogICBzdHJva2U9Im5vbmUiCiAgIGlkPSJwYXRoNzM4IiAvPgo8cGF0aAogICBkPSJtIDUzLjA2OTMsMzcuNDM5NSBjIDAuMDkzNywwIDAuMTg1MiwwLjAyNzcgMC4yNjMxLDAuMDc5OCAwLjA3NzksMC4wNTIgMC4xMzg2LDAuMTI2IDAuMTc0NSwwLjIxMjUgMC4wMzU4LDAuMDg2NiAwLjA0NTIsMC4xODE4IDAuMDI2OSwwLjI3MzYgLTAuMDE4MywwLjA5MTkgLTAuMDYzNCwwLjE3NjMgLTAuMTI5NiwwLjI0MjUgLTAuMDY2MiwwLjA2NjMgLTAuMTUwNiwwLjExMTQgLTAuMjQyNSwwLjEyOTYgLTAuMDkxOSwwLjAxODMgLTAuMTg3MSwwLjAwODkgLTAuMjczNiwtMC4wMjY5IC0wLjA4NjYsLTAuMDM1OCAtMC4xNjA1LC0wLjA5NjYgLTAuMjEyNiwtMC4xNzQ0IC0wLjA1MiwtMC4wNzc5IC0wLjA3OTgsLTAuMTY5NSAtMC4wNzk4LC0wLjI2MzIgLTRlLTQsLTAuMDYyIDAuMDExNCwtMC4xMjM1IDAuMDM0OSwtMC4xODA5IDAuMDIzNCwtMC4wNTc1IDAuMDU4LC0wLjEwOTcgMC4xMDE4LC0wLjE1MzcgMC4wNDM3LC0wLjA0NDEgMC4wOTU3LC0wLjA3OSAwLjE1MywtMC4xMDI4IDAuMDU3MiwtMC4wMjM5IDAuMTE4NywtMC4wMzYxIDAuMTgwNywtMC4wMzYxIHoiCiAgIGZpbGw9IiNmZmZmZmYiCiAgIGZpbGwtcnVsZT0iZXZlbm9kZCIKICAgb3BhY2l0eT0iMSIKICAgc3Ryb2tlPSJub25lIgogICBpZD0icGF0aDc0MCIgLz4KPHBhdGgKICAgZD0ibSA1Mi42MTIyLDM4Ljg5MjYgYyAwLjA0NjcsMCAwLjA5MTUsMC4wMTg1IDAuMTI0NSwwLjA1MTUgMC4wMzMsMC4wMzMgMC4wNTE1LDAuMDc3OCAwLjA1MTUsMC4xMjQ1IC0wLjAwMzEsMC4wMjI5IC0wLjAxMDcsMC4wNDQ5IC0wLjAyMjQsMC4wNjQ5IC0wLjAxMTcsMC4wMiAtMC4wMjcyLDAuMDM3NCAtMC4wNDU2LDAuMDUxNCAtMC4wMTg0LDAuMDE0IC0wLjAzOTQsMC4wMjQyIC0wLjA2MTgsMC4wMyAtMC4wMjIzLDAuMDA1OSAtMC4wNDU3LDAuMDA3MiAtMC4wNjg2LDAuMDA0MSAtMC4wMzgyLC0wLjAwNDcgLTAuMDczOCwtMC4wMjIxIC0wLjEwMSwtMC4wNDkzIC0wLjAyNzMsLTAuMDI3MyAtMC4wNDQ2LC0wLjA2MjkgLTAuMDQ5MywtMC4xMDExIC0xZS00LC0wLjA0NjEgMC4wMTgsLTAuMDkwNCAwLjA1MDQsLTAuMTIzNCAwLjAzMjMsLTAuMDMyOSAwLjA3NjIsLTAuMDUxOCAwLjEyMjMsLTAuMDUyNiB6IgogICBmaWxsPSIjZmZmZmZmIgogICBmaWxsLXJ1bGU9ImV2ZW5vZGQiCiAgIG9wYWNpdHk9IjEiCiAgIHN0cm9rZT0ibm9uZSIKICAgaWQ9InBhdGg3NDIiIC8+CjwvZz4KPGcKICAgb3BhY2l0eT0iMSIKICAgaWQ9Imc3NTIiPgo8cGF0aAogICBkPSJtIDU0LjYwMjIsNDQuMjUyOSBjIDAuMjY5LDAgMC41MzE5LDAuMDc5OCAwLjc1NTYsMC4yMjkyIDAuMjIzNiwwLjE0OTUgMC4zOTc5LDAuMzYxOSAwLjUwMDksMC42MTA0IDAuMTAyOSwwLjI0ODUgMC4xMjk4LDAuNTIxOSAwLjA3NzQsMC43ODU3IC0wLjA1MjUsMC4yNjM5IC0wLjE4MiwwLjUwNjIgLTAuMzcyMiwwLjY5NjQgLTAuMTkwMiwwLjE5MDIgLTAuNDMyNiwwLjMxOTcgLTAuNjk2NCwwLjM3MjIgLTAuMjYzOCwwLjA1MjUgLTAuNTM3MiwwLjAyNTUgLTAuNzg1OCwtMC4wNzc0IC0wLjI0ODUsLTAuMTAyOSAtMC40NjA5LC0wLjI3NzMgLTAuNjEwMywtMC41MDA5IC0wLjE0OTQsLTAuMjIzNyAtMC4yMjkyLC0wLjQ4NjYgLTAuMjI5MiwtMC43NTU2IDAsLTAuMzYwNyAwLjE0MzMsLTAuNzA2NiAwLjM5ODMsLTAuOTYxNiAwLjI1NTEsLTAuMjU1MSAwLjYwMSwtMC4zOTg0IDAuOTYxNywtMC4zOTg0IHoiCiAgIGZpbGw9IiM4YmQ0ZjMiCiAgIGZpbGwtcnVsZT0iZXZlbm9kZCIKICAgb3BhY2l0eT0iMSIKICAgc3Ryb2tlPSJub25lIgogICBpZD0icGF0aDc0NiIgLz4KPHBhdGgKICAgZD0ibSA1NC40MDAxLDQ0LjU3MzMgYyAwLjA2OTUsNmUtNCAwLjEzNzIsMC4wMjE4IDAuMTk0NywwLjA2MDggMC4wNTc1LDAuMDM5MSAwLjEwMjEsMC4wOTQzIDAuMTI4MiwwLjE1ODcgMC4wMjYyLDAuMDY0MyAwLjAzMjcsMC4xMzUgMC4wMTg4LDAuMjAzMSAtMC4wMTQsMC4wNjggLTAuMDQ3OCwwLjEzMDUgLTAuMDk3MSwwLjE3OTQgLTAuMDQ5NCwwLjA0ODkgLTAuMTEyMSwwLjA4MjEgLTAuMTgwMywwLjA5NTQgLTAuMDY4MiwwLjAxMzMgLTAuMTM4OCwwLjAwNjIgLTAuMjAyOSwtMC4wMjA2IC0wLjA2NDEsLTAuMDI2NyAtMC4xMTg5LC0wLjA3MTggLTAuMTU3NSwtMC4xMjk2IC0wLjAzODUsLTAuMDU3OCAtMC4wNTkxLC0wLjEyNTggLTAuMDU5MSwtMC4xOTUyIDAsLTAuMDQ2NSAwLjAwOTIsLTAuMDkyNiAwLjAyNzEsLTAuMTM1NSAwLjAxNzksLTAuMDQyOSAwLjA0NDEsLTAuMDgxOSAwLjA3NzIsLTAuMTE0NiAwLjAzMywtMC4wMzI3IDAuMDcyMiwtMC4wNTg2IDAuMTE1MiwtMC4wNzYxIDAuMDQzMSwtMC4wMTc1IDAuMDg5MiwtMC4wMjYzIDAuMTM1NywtMC4wMjU4IHoiCiAgIGZpbGw9IiNmZmZmZmYiCiAgIGZpbGwtcnVsZT0iZXZlbm9kZCIKICAgb3BhY2l0eT0iMSIKICAgc3Ryb2tlPSJub25lIgogICBpZD0icGF0aDc0OCIgLz4KPHBhdGgKICAgZD0ibSA1NC4wNjA5LDQ1LjY0ODQgYyAwLjAzNDgsMCAwLjA2ODIsMC4wMTM5IDAuMDkyOCwwLjAzODUgMC4wMjQ2LDAuMDI0NiAwLjAzODQsMC4wNTc5IDAuMDM4NCwwLjA5MjcgMCwwLjAzNDggLTAuMDEzOCwwLjA2ODIgLTAuMDM4NCwwLjA5MjggLTAuMDI0NiwwLjAyNDYgLTAuMDU4LDAuMDM4NCAtMC4wOTI4LDAuMDM4NCAtMC4wMzQ4LDAgLTAuMDY4MiwtMC4wMTM4IC0wLjA5MjgsLTAuMDM4NCAtMC4wMjQ2LC0wLjAyNDYgLTAuMDM4NCwtMC4wNTggLTAuMDM4NCwtMC4wOTI4IDAuMDAxNSwtMC4wMzQzIDAuMDE1OSwtMC4wNjY3IDAuMDQwMSwtMC4wOTEgMC4wMjQzLC0wLjAyNDMgMC4wNTY4LC0wLjAzODYgMC4wOTExLC0wLjA0MDIgeiIKICAgZmlsbD0iI2ZmZmZmZiIKICAgZmlsbC1ydWxlPSJldmVub2RkIgogICBvcGFjaXR5PSIxIgogICBzdHJva2U9Im5vbmUiCiAgIGlkPSJwYXRoNzUwIiAvPgo8L2c+CjxnCiAgIG9wYWNpdHk9IjEiCiAgIGlkPSJnNzYwIj4KPHBhdGgKICAgZD0ibSAxNS4xODE2LDI5Ljc1OTggYyAwLjQ3MDksMCAwLjkzMTIsMC4xMzk2IDEuMzIyNywwLjQwMTIgMC4zOTE1LDAuMjYxNiAwLjY5NjcsMC42MzM0IDAuODc2OSwxLjA2ODUgMC4xODAxLDAuNDM1IDAuMjI3MywwLjkxMzcgMC4xMzU0LDEuMzc1NSAtMC4wOTE4LDAuNDYxOSAtMC4zMTg2LDAuODg2MSAtMC42NTE1LDEuMjE5IC0wLjMzMywwLjMzMyAtMC43NTcyLDAuNTU5OCAtMS4yMTksMC42NTE2IC0wLjQ2MTksMC4wOTE5IC0wLjk0MDYsMC4wNDQ3IC0xLjM3NTYsLTAuMTM1NSAtMC40MzUsLTAuMTgwMiAtMC44MDY5LC0wLjQ4NTMgLTEuMDY4NSwtMC44NzY4IC0wLjI2MTYsLTAuMzkxNiAtMC40MDEyLC0wLjg1MTkgLTAuNDAxMiwtMS4zMjI3IDAsLTAuNjMxNSAwLjI1MDgsLTEuMjM3IDAuNjk3MywtMS42ODM1IDAuNDQ2NSwtMC40NDY1IDEuMDUyMSwtMC42OTczIDEuNjgzNSwtMC42OTczIHoiCiAgIGZpbGw9IiM4YmQ0ZjMiCiAgIGZpbGwtcnVsZT0iZXZlbm9kZCIKICAgb3BhY2l0eT0iMSIKICAgc3Ryb2tlPSJub25lIgogICBpZD0icGF0aDc1NCIgLz4KPHBhdGgKICAgZD0ibSAxNC42MjIxLDMwLjU1OTYgYyAwLjA5MzQsMCAwLjE4NDcsMC4wMjc2IDAuMjYyNCwwLjA3OTQgMC4wNzc4LDAuMDUxNyAwLjEzODUsMC4xMjUzIDAuMTc0NSwwLjIxMTUgMC4wMzYsMC4wODYyIDAuMDQ1OCwwLjE4MSAwLjAyOCwwLjI3MjcgLTAuMDE3OCwwLjA5MTcgLTAuMDYyMiwwLjE3NjEgLTAuMTI3OCwwLjI0MjYgLTAuMDY1NiwwLjA2NjUgLTAuMTQ5NCwwLjExMjEgLTAuMjQwOSwwLjEzMTEgLTAuMDkxNCwwLjAxOSAtMC4xODY0LDAuMDEwNSAtMC4yNzMxLC0wLjAyNDQgLTAuMDg2NiwtMC4wMzQ4IC0wLjE2MSwtMC4wOTQ1IC0wLjIxMzgsLTAuMTcxNiAtMC4wNTI4LC0wLjA3NyAtMC4wODE3LC0wLjE2NzkgLTAuMDgyOSwtMC4yNjEzIC05ZS00LC0wLjA2MjggMC4wMTA4LC0wLjEyNSAwLjAzNDIsLTAuMTgzMiAwLjAyMzQsLTAuMDU4MiAwLjA1ODEsLTAuMTExMiAwLjEwMjIsLTAuMTU1OSAwLjA0NDEsLTAuMDQ0NiAwLjA5NjYsLTAuMDgwMSAwLjE1NDUsLTAuMTA0MyAwLjA1NzgsLTAuMDI0MiAwLjExOTksLTAuMDM2NiAwLjE4MjcsLTAuMDM2NiB6IgogICBmaWxsPSIjZmZmZmZmIgogICBmaWxsLXJ1bGU9ImV2ZW5vZGQiCiAgIG9wYWNpdHk9IjEiCiAgIHN0cm9rZT0ibm9uZSIKICAgaWQ9InBhdGg3NTYiIC8+CjxwYXRoCiAgIGQ9Im0gMTQuMTY2MywzMiBjIDAuMDM0OCwwIDAuMDY4OCwwLjAxMDMgMC4wOTc3LDAuMDI5NyAwLjAyOSwwLjAxOTMgMC4wNTE1LDAuMDQ2OCAwLjA2NDksMC4wNzg5IDAuMDEzMywwLjAzMjIgMC4wMTY4LDAuMDY3NiAwLjAxLDAuMTAxNyAtMC4wMDY4LDAuMDM0MiAtMC4wMjM2LDAuMDY1NSAtMC4wNDgyLDAuMDkwMSAtMC4wMjQ2LDAuMDI0NyAtMC4wNTYsMC4wNDE0IC0wLjA5MDEsMC4wNDgyIC0wLjAzNDEsMC4wMDY4IC0wLjA2OTUsMC4wMDMzIC0wLjEwMTcsLTAuMDEgLTAuMDMyMSwtMC4wMTMzIC0wLjA1OTYsLTAuMDM1OSAtMC4wNzksLTAuMDY0OCAtMC4wMTkzLC0wLjAyOSAtMC4wMjk2LC0wLjA2MyAtMC4wMjk2LC0wLjA5NzggLTVlLTQsLTAuMDIyNyAwLjAwMzYsLTAuMDQ1MyAwLjAxMTksLTAuMDY2NCAwLjAwODMsLTAuMDIxMSAwLjAyMDcsLTAuMDQwNCAwLjAzNjQsLTAuMDU2NyAwLjAxNTgsLTAuMDE2NCAwLjAzNDYsLTAuMDI5NSAwLjA1NTQsLTAuMDM4NSAwLjAyMDgsLTAuMDA5MSAwLjA0MzIsLTAuMDE0IDAuMDY1OSwtMC4wMTQ0IHoiCiAgIGZpbGw9IiNmZmZmZmYiCiAgIGZpbGwtcnVsZT0iZXZlbm9kZCIKICAgb3BhY2l0eT0iMSIKICAgc3Ryb2tlPSJub25lIgogICBpZD0icGF0aDc1OCIgLz4KPC9nPgo8ZwogICBvcGFjaXR5PSIxIgogICBpZD0iZzc2OCI+CjxwYXRoCiAgIGQ9Im0gMTguMzMwOCwzOC4zMjkxIGMgMC4xNzc5LDAgMC4zNTE3LDAuMDUyNyAwLjQ5OTYsMC4xNTE1IDAuMTQ3OSwwLjA5ODggMC4yNjMxLDAuMjM5MyAwLjMzMTIsMC40MDM2IDAuMDY4MSwwLjE2NDMgMC4wODU5LDAuMzQ1MSAwLjA1MTIsMC41MTk1IC0wLjAzNDcsMC4xNzQ1IC0wLjEyMDQsMC4zMzQ3IC0wLjI0NjEsMC40NjA0IC0wLjEyNTgsMC4xMjU4IC0wLjI4NiwwLjIxMTQgLTAuNDYwNCwwLjI0NjEgLTAuMTc0NSwwLjAzNDcgLTAuMzU1MywwLjAxNjkgLTAuNTE5NiwtMC4wNTExIC0wLjE2NDMsLTAuMDY4MSAtMC4zMDQ3LC0wLjE4MzQgLTAuNDAzNSwtMC4zMzEyIC0wLjA5ODgsLTAuMTQ3OSAtMC4xNTE2LC0wLjMyMTggLTAuMTUxNiwtMC40OTk2IDAsLTAuMjM3NiAwLjA5NDQsLTAuNDY1NSAwLjI2MjUsLTAuNjMzNiAwLjE2OCwtMC4xNjggMC4zOTU5LC0wLjI2MjQgMC42MzM1LC0wLjI2MjQgeiIKICAgZmlsbD0iIzhiZDRmMyIKICAgZmlsbC1ydWxlPSJldmVub2RkIgogICBvcGFjaXR5PSIxIgogICBzdHJva2U9Im5vbmUiCiAgIGlkPSJwYXRoNzYyIiAvPgo8cGF0aAogICBkPSJtIDE4LjE3NDcsMzguNjg3NSBjIDAuMDQzLDAgMC4wODUxLDAuMDEyOCAwLjEyMDgsMC4wMzY3IDAuMDM1OCwwLjAyMzkgMC4wNjM3LDAuMDU3OSAwLjA4MDIsMC4wOTc3IDAuMDE2NSwwLjAzOTcgMC4wMjA4LDAuMDgzNSAwLjAxMjQsMC4xMjU3IC0wLjAwODQsMC4wNDIyIC0wLjAyOTEsMC4wODEgLTAuMDU5NiwwLjExMTQgLTAuMDMwNCwwLjAzMDQgLTAuMDY5MiwwLjA1MTIgLTAuMTExNCwwLjA1OTYgLTAuMDQyMiwwLjAwODMgLTAuMDg2LDAuMDA0IC0wLjEyNTcsLTAuMDEyNCAtMC4wMzk4LC0wLjAxNjUgLTAuMDczOCwtMC4wNDQ0IC0wLjA5NzcsLTAuMDgwMiAtMC4wMjM5LC0wLjAzNTggLTAuMDM2NiwtMC4wNzc4IC0wLjAzNjYsLTAuMTIwOSAtNWUtNCwtMC4wMjg3IDAuMDA0OSwtMC4wNTcyIDAuMDE1NiwtMC4wODM4IDAuMDEwOCwtMC4wMjY1IDAuMDI2OCwtMC4wNTA3IDAuMDQ3MSwtMC4wNzEgMC4wMjAzLC0wLjAyMDMgMC4wNDQ1LC0wLjAzNjMgMC4wNzExLC0wLjA0NzEgMC4wMjY2LC0wLjAxMDggMC4wNTUxLC0wLjAxNjEgMC4wODM4LC0wLjAxNTcgeiIKICAgZmlsbD0iI2ZmZmZmZiIKICAgZmlsbC1ydWxlPSJldmVub2RkIgogICBvcGFjaXR5PSIxIgogICBzdHJva2U9Im5vbmUiCiAgIGlkPSJwYXRoNzY0IiAvPgo8cGF0aAogICBkPSJtIDE3Ljk2NjcsMzkuMzU5NCBjIDAuMDE1OCwwIDAuMDMxMywwLjAwNDcgMC4wNDQ1LDAuMDEzNSAwLjAxMzEsMC4wMDg3IDAuMDIzNCwwLjAyMTIgMC4wMjk0LDAuMDM1OSAwLjAwNjEsMC4wMTQ2IDAuMDA3NywwLjAzMDcgMC4wMDQ2LDAuMDQ2MiAtMC4wMDMxLDAuMDE1NSAtMC4wMTA3LDAuMDI5OCAtMC4wMjE5LDAuMDQwOSAtMC4wMTEyLDAuMDExMiAtMC4wMjU1LDAuMDE4OSAtMC4wNDEsMC4wMjE5IC0wLjAxNTUsMC4wMDMxIC0wLjAzMTYsMC4wMDE1IC0wLjA0NjIsLTAuMDA0NSAtMC4wMTQ2LC0wLjAwNjEgLTAuMDI3MSwtMC4wMTYzIC0wLjAzNTksLTAuMDI5NSAtMC4wMDg4LC0wLjAxMzEgLTAuMDEzNSwtMC4wMjg2IC0wLjAxMzUsLTAuMDQ0NCAwLC0wLjAyMTIgMC4wMDg0LC0wLjA0MTYgMC4wMjM1LC0wLjA1NjYgMC4wMTUsLTAuMDE1IDAuMDM1MywtMC4wMjM0IDAuMDU2NSwtMC4wMjM0IHoiCiAgIGZpbGw9IiNmZmZmZmYiCiAgIGZpbGwtcnVsZT0iZXZlbm9kZCIKICAgb3BhY2l0eT0iMSIKICAgc3Ryb2tlPSJub25lIgogICBpZD0icGF0aDc2NiIgLz4KPC9nPgo8ZwogICBvcGFjaXR5PSIxIgogICBpZD0iZzc3NiI+CjxwYXRoCiAgIGQ9Im0gMTUuNzQwOSwzNS44Mzk4IGMgMC4yNjk2LC02ZS00IDAuNTMzMywwLjA3ODcgMC43NTc5LDAuMjI4IDAuMjI0NSwwLjE0OTMgMC4zOTk3LDAuMzYxOCAwLjUwMzUsMC42MTA2IDAuMTAzNywwLjI0ODkgMC4xMzE0LDAuNTIyOSAwLjA3OTQsMC43ODc0IC0wLjA1MiwwLjI2NDYgLTAuMTgxMywwLjUwNzggLTAuMzcxNSwwLjY5ODkgLTAuMTkwMiwwLjE5MTEgLTAuNDMyNywwLjMyMTUgLTAuNjk3MSwwLjM3NDcgLTAuMjY0MywwLjA1MzIgLTAuNTM4NCwwLjAyNjkgLTAuNzg3OCwtMC4wNzU3IC0wLjI0OTMsLTAuMTAyNiAtMC40NjI2LC0wLjI3NjggLTAuNjEyOSwtMC41MDA2IC0wLjE1MDMsLTAuMjIzOSAtMC4yMzA5LC0wLjQ4NzIgLTAuMjMxNSwtMC43NTY5IC01ZS00LC0wLjE3OSAwLjAzNDQsLTAuMzU2MyAwLjEwMjUsLTAuNTIxOSAwLjA2ODEsLTAuMTY1NSAwLjE2ODIsLTAuMzE2IDAuMjk0NSwtMC40NDI5IDAuMTI2MywtMC4xMjY5IDAuMjc2MywtMC4yMjc3IDAuNDQxNSwtMC4yOTY2IDAuMTY1MywtMC4wNjg5IDAuMzQyNCwtMC4xMDQ1IDAuNTIxNSwtMC4xMDUgeiIKICAgZmlsbD0iIzhiZDRmMyIKICAgZmlsbC1ydWxlPSJldmVub2RkIgogICBvcGFjaXR5PSIxIgogICBzdHJva2U9Im5vbmUiCiAgIGlkPSJwYXRoNzcwIiAvPgo8cGF0aAogICBkPSJtIDE1LjU0MjcsMzYuMTYwMiBjIDAuMDcwMywwIDAuMTM4OSwwLjAyMDggMC4xOTczLDAuMDU5OCAwLjA1ODUsMC4wMzkgMC4xMDQsMC4wOTQ1IDAuMTMwOSwwLjE1OTQgMC4wMjY4LDAuMDY0OSAwLjAzMzksMC4xMzYzIDAuMDIwMiwwLjIwNTMgLTAuMDEzNywwLjA2ODkgLTAuMDQ3NiwwLjEzMjEgLTAuMDk3MiwwLjE4MTggLTAuMDQ5NywwLjA0OTcgLTAuMTEzLDAuMDgzNSAtMC4xODE5LDAuMDk3MiAtMC4wNjg5LDAuMDEzNyAtMC4xNDAzLDAuMDA2NyAtMC4yMDUyLC0wLjAyMDIgLTAuMDY0OSwtMC4wMjY5IC0wLjEyMDQsLTAuMDcyNCAtMC4xNTk0LC0wLjEzMDggLTAuMDM5MSwtMC4wNTg0IC0wLjA1OTksLTAuMTI3MSAtMC4wNTk5LC0wLjE5NzMgMCwtMC4wOTQyIDAuMDM3NCwtMC4xODQ2IDAuMTA0LC0wLjI1MTIgMC4wNjY2LC0wLjA2NjYgMC4xNTcsLTAuMTA0IDAuMjUxMiwtMC4xMDQgeiIKICAgZmlsbD0iI2ZmZmZmZiIKICAgZmlsbC1ydWxlPSJldmVub2RkIgogICBvcGFjaXR5PSIxIgogICBzdHJva2U9Im5vbmUiCiAgIGlkPSJwYXRoNzcyIiAvPgo8cGF0aAogICBkPSJtIDE1LjIwMDcsMzcuMjM4MyBjIDAuMDM0OCwwIDAuMDY4MSwwLjAxMzggMC4wOTI3LDAuMDM4NCAwLjAyNDcsMC4wMjQ2IDAuMDM4NSwwLjA1OCAwLjAzODUsMC4wOTI4IDAuMDAyNywwLjAxNyAwLjAwMjEsMC4wMzQ0IC0wLjAwMTksMC4wNTEyIC0wLjAwNCwwLjAxNjcgLTAuMDExMywwLjAzMjYgLTAuMDIxNCwwLjA0NjUgLTAuMDEwMSwwLjAxNCAtMC4wMjI4LDAuMDI1OCAtMC4wMzc1LDAuMDM0OSAtMC4wMTQ3LDAuMDA5IC0wLjAzMSwwLjAxNTEgLTAuMDQ4LDAuMDE3OCAtMC4wMzQzLDAuMDA0NyAtMC4wNjksLTAuMDA0IC0wLjA5NywtMC4wMjQzIC0wLjAyOCwtMC4wMjAyIC0wLjA0NzIsLTAuMDUwNSAtMC4wNTM0LC0wLjA4NDUgLTAuMDAzMiwtMC4wMTM3IC0wLjAwMzIsLTAuMDI3OSAwLC0wLjA0MTYgLTVlLTQsLTAuMDE2OCAwLjAwMjQsLTAuMDMzNiAwLjAwODUsLTAuMDQ5MyAwLjAwNiwtMC4wMTU2IDAuMDE1MSwtMC4wMyAwLjAyNjcsLTAuMDQyMiAwLjAxMTYsLTAuMDEyMiAwLjAyNTUsLTAuMDIxOSAwLjA0MDgsLTAuMDI4OCAwLjAxNTQsLTAuMDA2OCAwLjAzMiwtMC4wMTA1IDAuMDQ4OCwtMC4wMTA5IHoiCiAgIGZpbGw9IiNmZmZmZmYiCiAgIGZpbGwtcnVsZT0iZXZlbm9kZCIKICAgb3BhY2l0eT0iMSIKICAgc3Ryb2tlPSJub25lIgogICBpZD0icGF0aDc3NCIgLz4KPC9nPgo8ZwogICBvcGFjaXR5PSIxIgogICBpZD0iZzc4NCI+CjxwYXRoCiAgIGQ9Im0gNDUuNTk0LDUzLjYwOTQgYyAtMC4yNTg5LDAgLTAuNTExOSwwLjA3NjcgLTAuNzI3MiwwLjIyMDUgLTAuMjE1MiwwLjE0MzkgLTAuMzgzLDAuMzQ4MyAtMC40ODIsMC41ODc0IC0wLjA5OTEsMC4yMzkyIC0wLjEyNSwwLjUwMjMgLTAuMDc0NSwwLjc1NjIgMC4wNTA1LDAuMjUzOSAwLjE3NTIsMC40ODcxIDAuMzU4MiwwLjY3MDEgMC4xODMsMC4xODMxIDAuNDE2MiwwLjMwNzcgMC42NzAxLDAuMzU4MiAwLjI1MzksMC4wNTA1IDAuNTE3MSwwLjAyNDYgMC43NTYyLC0wLjA3NDQgMC4yMzkyLC0wLjA5OTEgMC40NDM2LC0wLjI2NjkgMC41ODc0LC0wLjQ4MjEgMC4xNDM4LC0wLjIxNTIgMC4yMjA1LC0wLjQ2ODMgMC4yMjA1LC0wLjcyNzEgMCwtMC4zNDcxIC0wLjEzNzgsLTAuNjggLTAuMzgzMywtMC45MjU1IC0wLjI0NTQsLTAuMjQ1NCAtMC41NzgzLC0wLjM4MzMgLTAuOTI1NCwtMC4zODMzIHoiCiAgIGZpbGw9IiM4YmQ0ZjMiCiAgIGZpbGwtcnVsZT0iZXZlbm9kZCIKICAgb3BhY2l0eT0iMSIKICAgc3Ryb2tlPSJub25lIgogICBpZD0icGF0aDc3OCIgLz4KPHBhdGgKICAgZD0ibSA0NS45MDE4LDU0LjA1MDggYyAtMC4wNTEzLDAgLTAuMTAxNCwwLjAxNTIgLTAuMTQ0LDAuMDQzNyAtMC4wNDI3LDAuMDI4NCAtMC4wNzU5LDAuMDY4OSAtMC4wOTU1LDAuMTE2MyAtMC4wMTk2LDAuMDQ3NCAtMC4wMjQ3LDAuMDk5NSAtMC4wMTQ3LDAuMTQ5NyAwLjAxLDAuMDUwMyAwLjAzNDYsMC4wOTY1IDAuMDcwOSwwLjEzMjggMC4wMzYyLDAuMDM2MiAwLjA4MjQsMC4wNjA5IDAuMTMyNywwLjA3MDkgMC4wNTAzLDAuMDEgMC4xMDI0LDAuMDA0OSAwLjE0OTgsLTAuMDE0NyAwLjA0NzMsLTAuMDE5NyAwLjA4NzgsLTAuMDUyOSAwLjExNjMsLTAuMDk1NSAwLjAyODUsLTAuMDQyNiAwLjA0MzcsLTAuMDkyOCAwLjA0MzcsLTAuMTQ0IDAsLTAuMDY4OCAtMC4wMjczLC0wLjEzNDcgLTAuMDc1OSwtMC4xODMzIC0wLjA0ODcsLTAuMDQ4NiAtMC4xMTQ2LC0wLjA3NTkgLTAuMTgzMywtMC4wNzU5IHoiCiAgIGZpbGw9IiNmZmZmZmYiCiAgIGZpbGwtcnVsZT0iZXZlbm9kZCIKICAgb3BhY2l0eT0iMSIKICAgc3Ryb2tlPSJub25lIgogICBpZD0icGF0aDc4MCIgLz4KPHBhdGgKICAgZD0ibSA0Ni4xNTA4LDU0Ljg0MTggYyAtMC4wMjUsMC4wMDE1IC0wLjA0ODYsMC4wMTIxIC0wLjA2NjIsMC4wMjk4IC0wLjAxNzcsMC4wMTc3IC0wLjAyODMsMC4wNDEyIC0wLjAyOTksMC4wNjYyIC00ZS00LDAuMDEyNyAwLjAwMTgsMC4wMjU0IDAuMDA2NCwwLjAzNzMgMC4wMDQ3LDAuMDExOCAwLjAxMTgsMC4wMjI2IDAuMDIwOCwwLjAzMTYgMC4wMDksMC4wMDkgMC4wMTk3LDAuMDE2IDAuMDMxNiwwLjAyMDcgMC4wMTE4LDAuMDA0NyAwLjAyNDUsMC4wMDY4IDAuMDM3MywwLjAwNjQgMC4wMTIxLDRlLTQgMC4wMjQzLC0wLjAwMTYgMC4wMzU3LC0wLjAwNTggMC4wMTE0LC0wLjAwNDMgMC4wMjE5LC0wLjAxMDggMC4wMzA4LC0wLjAxOTEgMC4wMDg5LC0wLjAwODMgMC4wMTYxLC0wLjAxODMgMC4wMjEyLC0wLjAyOTQgMC4wMDUsLTAuMDExMSAwLjAwNzgsLTAuMDIzMSAwLjAwODMsLTAuMDM1MyA0ZS00LC0wLjAxMjcgLTAuMDAxOCwtMC4wMjU0IC0wLjAwNjQsLTAuMDM3MyAtMC4wMDQ3LC0wLjAxMTggLTAuMDExOCwtMC4wMjI2IC0wLjAyMDgsLTAuMDMxNiAtMC4wMDksLTAuMDA5IC0wLjAxOTcsLTAuMDE2IC0wLjAzMTYsLTAuMDIwNyAtMC4wMTE4LC0wLjAwNDcgLTAuMDI0NSwtMC4wMDY4IC0wLjAzNzIsLTAuMDA2NCB6IgogICBmaWxsPSIjZmZmZmZmIgogICBmaWxsLXJ1bGU9ImV2ZW5vZGQiCiAgIG9wYWNpdHk9IjEiCiAgIHN0cm9rZT0ibm9uZSIKICAgaWQ9InBhdGg3ODIiIC8+CjwvZz4KPGcKICAgb3BhY2l0eT0iMSIKICAgaWQ9Imc3OTIiPgo8cGF0aAogICBkPSJtIDQ0LjExLDU2LjA1NzYgYyAtMC4wOTc1LDAgLTAuMTkyOCwwLjAyODkgLTAuMjczOCwwLjA4MzEgLTAuMDgxLDAuMDU0MSAtMC4xNDQyLDAuMTMxMSAtMC4xODE1LDAuMjIxMSAtMC4wMzczLDAuMDkwMSAtMC4wNDcxLDAuMTg5MiAtMC4wMjgsMC4yODQ4IDAuMDE5LDAuMDk1NSAwLjA2NTksMC4xODM0IDAuMTM0OCwwLjI1MjMgMC4wNjg5LDAuMDY4OSAwLjE1NjgsMC4xMTU4IDAuMjUyMywwLjEzNDggMC4wOTU2LDAuMDE5MSAwLjE5NDcsMC4wMDkzIDAuMjg0OCwtMC4wMjggMC4wOSwtMC4wMzczIDAuMTY3LC0wLjEwMDUgMC4yMjExLC0wLjE4MTUgMC4wNTQyLC0wLjA4MSAwLjA4MzEsLTAuMTc2MyAwLjA4MzEsLTAuMjczOCAwLC0wLjEzMDcgLTAuMDUxOSwtMC4yNTYgLTAuMTQ0NCwtMC4zNDg0IEMgNDQuMzY2LDU2LjEwOTUgNDQuMjQwNyw1Ni4wNTc2IDQ0LjExLDU2LjA1NzYgWiIKICAgZmlsbD0iIzhiZDRmMyIKICAgZmlsbC1ydWxlPSJldmVub2RkIgogICBvcGFjaXR5PSIxIgogICBzdHJva2U9Im5vbmUiCiAgIGlkPSJwYXRoNzg2IiAvPgo8cGF0aAogICBkPSJtIDQ0LjE5NjgsNTYuMjUzMyBjIC0wLjAzMTgsLTAuMDA0NyAtMC4wNjQxLDAuMDAzIC0wLjA5MDMsMC4wMjE0IC0wLjAyNjIsMC4wMTg1IC0wLjA0NDMsMC4wNDY0IC0wLjA1MDUsMC4wNzc4IC0wLjAwNDcsMC4wMzIyIDAuMDAzNCwwLjA2NDkgMC4wMjI2LDAuMDkxMiAwLjAxOTEsMC4wMjYzIDAuMDQ3NywwLjA0NDEgMC4wNzk4LDAuMDQ5NiBoIDAuMDM4NCBjIDAuMDI4NCwtMC4wMDQ3IDAuMDU0MywtMC4wMTkzIDAuMDczLC0wLjA0MTMgMC4wMTg3LC0wLjAyMTkgMC4wMjg5LC0wLjA0OTggMC4wMjg5LC0wLjA3ODcgMCwtMC4wMjg4IC0wLjAxMDIsLTAuMDU2NyAtMC4wMjg5LC0wLjA3ODcgLTAuMDE4NywtMC4wMjIgLTAuMDQ0NiwtMC4wMzY2IC0wLjA3MywtMC4wNDEzIHoiCiAgIGZpbGw9IiNmZmZmZmYiCiAgIGZpbGwtcnVsZT0iZXZlbm9kZCIKICAgb3BhY2l0eT0iMSIKICAgc3Ryb2tlPSJub25lIgogICBpZD0icGF0aDc4OCIgLz4KPHBhdGgKICAgZD0ibSA0NC4zMTI0LDU2LjYxNzIgYyAtMC4wMDg5LDAgLTAuMDE3NSwwLjAwMjYgLTAuMDI0OSwwLjAwNzUgLTAuMDA3NCwwLjAwNSAtMC4wMTMxLDAuMDEyIC0wLjAxNjUsMC4wMjAxIC0wLjAwMzQsMC4wMDgyIC0wLjAwNDMsMC4wMTcyIC0wLjAwMjYsMC4wMjU5IDAuMDAxOCwwLjAwODcgMC4wMDYsMC4wMTY3IDAuMDEyMywwLjAyMyAwLjAwNjMsMC4wMDYyIDAuMDE0MiwwLjAxMDUgMC4wMjI5LDAuMDEyMiAwLjAwODcsMC4wMDE4IDAuMDE3Nyw5ZS00IDAuMDI1OSwtMC4wMDI1IDAuMDA4MiwtMC4wMDM0IDAuMDE1MiwtMC4wMDkyIDAuMDIwMSwtMC4wMTY1IDAuMDA1LC0wLjAwNzQgMC4wMDc2LC0wLjAxNjEgMC4wMDc2LC0wLjAyNDkgMCwtMC4wMTE5IC0wLjAwNDcsLTAuMDIzMyAtMC4wMTMxLC0wLjAzMTcgLTAuMDA4NCwtMC4wMDg0IC0wLjAxOTgsLTAuMDEzMSAtMC4wMzE3LC0wLjAxMzEgeiIKICAgZmlsbD0iI2ZmZmZmZiIKICAgZmlsbC1ydWxlPSJldmVub2RkIgogICBvcGFjaXR5PSIxIgogICBzdHJva2U9Im5vbmUiCiAgIGlkPSJwYXRoNzkwIiAvPgo8L2c+CjxnCiAgIG9wYWNpdHk9IjEiCiAgIGlkPSJnODAwIj4KPHBhdGgKICAgZD0ibSA0NS4wNTkzLDU3Ljc4NTIgYyAtMC4xNDgxLDAgLTAuMjkyOCwwLjA0MzkgLTAuNDE2LDAuMTI2MiAtMC4xMjMxLDAuMDgyMiAtMC4yMTkxLDAuMTk5MiAtMC4yNzU4LDAuMzM2IC0wLjA1NjYsMC4xMzY4IC0wLjA3MTUsMC4yODc0IC0wLjA0MjYsMC40MzI2IDAuMDI4OSwwLjE0NTMgMC4xMDAyLDAuMjc4NyAwLjIwNSwwLjM4MzQgMC4xMDQ3LDAuMTA0OCAwLjIzODEsMC4xNzYxIDAuMzgzNCwwLjIwNSAwLjE0NTIsMC4wMjg5IDAuMjk1OCwwLjAxNCAwLjQzMjYsLTAuMDQyNiAwLjEzNjgsLTAuMDU2NyAwLjI1MzgsLTAuMTUyNyAwLjMzNiwtMC4yNzU4IDAuMDgyMywtMC4xMjMyIDAuMTI2MiwtMC4yNjc5IDAuMTI2MiwtMC40MTYgMCwtMC4xOTg2IC0wLjA3ODgsLTAuMzg5MSAtMC4yMTkzLC0wLjUyOTUgLTAuMTQwNCwtMC4xNDA1IC0wLjMzMDksLTAuMjE5MyAtMC41Mjk1LC0wLjIxOTMgeiIKICAgZmlsbD0iIzhiZDRmMyIKICAgZmlsbC1ydWxlPSJldmVub2RkIgogICBvcGFjaXR5PSIxIgogICBzdHJva2U9Im5vbmUiCiAgIGlkPSJwYXRoNzk0IiAvPgo8cGF0aAogICBkPSJtIDQ1LjE2ODYsNTcuOTYxMiBjIC0wLjA1MSwwIC0wLjA5OTgsMC4wMjAyIC0wLjEzNTgsMC4wNTYyIC0wLjAzNiwwLjAzNiAtMC4wNTYyLDAuMDg0OSAtMC4wNTYyLDAuMTM1OCAwLDAuMDM4NiAwLjAxMTQsMC4wNzY0IDAuMDMyOSwwLjEwODUgMC4wMjE0LDAuMDMyMSAwLjA1MTksMC4wNTcxIDAuMDg3NiwwLjA3MTggMC4wMzU2LDAuMDE0OCAwLjA3NDksMC4wMTg3IDAuMTEyNywwLjAxMTIgMC4wMzc5LC0wLjAwNzYgMC4wNzI3LC0wLjAyNjIgMC4xLC0wLjA1MzUgMC4wMjczLC0wLjAyNzMgMC4wNDU5LC0wLjA2MiAwLjA1MzQsLTAuMDk5OSAwLjAwNzYsLTAuMDM3OSAwLjAwMzcsLTAuMDc3MSAtMC4wMTExLC0wLjExMjggLTAuMDE0OCwtMC4wMzU3IC0wLjAzOTgsLTAuMDY2MSAtMC4wNzE5LC0wLjA4NzYgLTAuMDMyMSwtMC4wMjE0IC0wLjA2OTgsLTAuMDMyOSAtMC4xMDg0LC0wLjAzMjkgeiIKICAgZmlsbD0iI2ZmZmZmZiIKICAgZmlsbC1ydWxlPSJldmVub2RkIgogICBvcGFjaXR5PSIxIgogICBzdHJva2U9Im5vbmUiCiAgIGlkPSJwYXRoNzk2IiAvPgo8cGF0aAogICBkPSJtIDQ1LjM1NzUsNTguNTU5NyBjIC0wLjAwOTUsLTAuMDAyNCAtMC4wMTk0LC0wLjAwMjcgLTAuMDI5LC0xMGUtNCAtMC4wMDk2LDAuMDAxNyAtMC4wMTg4LDAuMDA1MyAtMC4wMjY5LDAuMDEwNyAtMC4wMDgxLDAuMDA1NSAtMC4wMTUsMC4wMTI1IC0wLjAyMDMsMC4wMjA3IC0wLjAwNTMsMC4wMDgzIC0wLjAwODcsMC4wMTc1IC0wLjAxMDIsMC4wMjcyIC0wLjAwMzksMC4wMTg2IC00ZS00LDAuMDM4IDAuMDA5NywwLjA1NDEgMC4wMTAyLDAuMDE2MSAwLjAyNjIsMC4wMjc3IDAuMDQ0NywwLjAzMjMgaCAwLjAzMiBjIDAuMDE4NiwwIDAuMDM2NSwtMC4wMDc1IDAuMDQ5NywtMC4wMjA3IDAuMDEzMiwtMC4wMTMyIDAuMDIwNywtMC4wMzExIDAuMDIwNywtMC4wNDk3IDRlLTQsLTAuMDA5MyAtMTBlLTQsLTAuMDE4NSAtMC4wMDQyLC0wLjAyNzIgLTAuMDAzMSwtMC4wMDg3IC0wLjAwNzksLTAuMDE2NyAtMC4wMTQyLC0wLjAyMzYgLTAuMDA2MiwtMC4wMDY4IC0wLjAxMzcsLTAuMDEyNCAtMC4wMjIxLC0wLjAxNjMgLTAuMDA4NCwtMC4wMDM5IC0wLjAxNzUsLTAuMDA2MSAtMC4wMjY3LC0wLjAwNjUgeiIKICAgZmlsbD0iI2ZmZmZmZiIKICAgZmlsbC1ydWxlPSJldmVub2RkIgogICBvcGFjaXR5PSIxIgogICBzdHJva2U9Im5vbmUiCiAgIGlkPSJwYXRoNzk4IiAvPgo8L2c+CjxnCiAgIG9wYWNpdHk9IjEiCiAgIGlkPSJnODE1Ij4KPGcKICAgb3BhY2l0eT0iMSIKICAgaWQ9Imc4MTEiPgo8dXNlCiAgIGZpbGw9InVybCgjTGluZWFyR3JhZGllbnRfNikiCiAgIGZpbGwtcnVsZT0ibm9uemVybyIKICAgc3Ryb2tlPSJub25lIgogICB4bGluazpocmVmPSIjRmlsbF83IgogICBpZD0idXNlODAyIgogICBzdHlsZT0iZmlsbDp1cmwoI0xpbmVhckdyYWRpZW50XzYpIiAvPgo8bWFzawogICBoZWlnaHQ9IjExLjA5MDIiCiAgIGlkPSJTdHJva2VNYXNrXzciCiAgIG1hc2tVbml0cz0idXNlclNwYWNlT25Vc2UiCiAgIHdpZHRoPSIxNy43NzgzIgogICB4PSI0LjQyMzQxIgogICB5PSIxOC43MzgyIj4KPHJlY3QKICAgZmlsbD0iI2ZmZmZmZiIKICAgaGVpZ2h0PSIxMS4wOTAyIgogICBzdHJva2U9Im5vbmUiCiAgIHdpZHRoPSIxNy43NzgyOTkiCiAgIHg9IjQuNDIzNDA5OSIKICAgeT0iMTguNzM4MTk5IgogICBpZD0icmVjdDgwNCIgLz4KPHVzZQogICBmaWxsPSIjMDAwMDAwIgogICBmaWxsLXJ1bGU9ImV2ZW5vZGQiCiAgIHN0cm9rZT0ibm9uZSIKICAgeGxpbms6aHJlZj0iI0ZpbGxfNyIKICAgaWQ9InVzZTgwNiIgLz4KPC9tYXNrPgo8dXNlCiAgIGZpbGw9Im5vbmUiCiAgIG1hc2s9InVybCgjU3Ryb2tlTWFza183KSIKICAgc3Ryb2tlPSIjNTUyZjgyIgogICBzdHJva2UtbGluZWNhcD0iYnV0dCIKICAgc3Ryb2tlLWxpbmVqb2luPSJtaXRlciIKICAgc3Ryb2tlLXdpZHRoPSIwLjk2IgogICB4bGluazpocmVmPSIjRmlsbF83IgogICBpZD0idXNlODA5IiAvPgo8L2c+CjxwYXRoCiAgIGQ9Im0gMTYuMDI1OSwyMS4xOTk4IGMgMCwwIC02LjY1MTUzLDcuMjgwNCAtMTAuNTM5NTQsNy40ODUyIC0wLjE0NCwwLjMyIC0wLjI2MDE3LDAuNTc3OCAtMC4zMjQxNywwLjc2MDIgNC4yNTkyMSwwLjAxOTIgNy4zNDY5MSwtMC42NDg2IDkuNjI1MzEsLTEuNTgzIDIuMTk1MiwtMC44OTYgMy41OTM2LC0yLjA0NDggNC40NDgsLTMuMDc4NCAwLjg1NDQsLTEuMDMzNiAxLjA4MjUsLTEuODk5NyAxLjA4NTcsLTIuMzE1NyAwLjAwNjQsLTAuMDM5MiAwLjAyMzksLTAuMTI4MiAwLjAxNzUsLTAuMTY3NCAtMC4zMiwtMC43NjggMC43Nzg0LC0xLjc3OTMgMS4yMDcyLC0yLjE4MjUgLTAuMTUwNCwtMC4xNjMyIC0wLjQzMzUsLTAuNTE1MyAtMC42ODMxLC0wLjgxNjEgLTIuMzc5MiwtMC4wNTg2IC0yLjg2NzcsLTAuNDQ4OCAtNC44MzA1LDEuODg0OSAtMS45NjI4LDIuMzMzNiAtMC4wMDY0LDAuMDEyOCAtMC4wMDY0LDAuMDEyOCB6IgogICBmaWxsPSIjNzc3YWJhIgogICBmaWxsLXJ1bGU9ImV2ZW5vZGQiCiAgIG9wYWNpdHk9IjEiCiAgIHN0cm9rZT0ibm9uZSIKICAgaWQ9InBhdGg4MTMiIC8+CjwvZz4KPGcKICAgb3BhY2l0eT0iMSIKICAgaWQ9Imc4MzAiPgo8ZwogICBvcGFjaXR5PSIxIgogICBpZD0iZzgyNiI+Cjx1c2UKICAgZmlsbD0idXJsKCNMaW5lYXJHcmFkaWVudF83KSIKICAgZmlsbC1ydWxlPSJub256ZXJvIgogICBzdHJva2U9Im5vbmUiCiAgIHhsaW5rOmhyZWY9IiNGaWxsXzgiCiAgIGlkPSJ1c2U4MTciCiAgIHN0eWxlPSJmaWxsOnVybCgjTGluZWFyR3JhZGllbnRfNykiIC8+CjxtYXNrCiAgIGhlaWdodD0iMTQuMzM5IgogICBpZD0iU3Ryb2tlTWFza184IgogICBtYXNrVW5pdHM9InVzZXJTcGFjZU9uVXNlIgogICB3aWR0aD0iMTYuNTMxNiIKICAgeD0iNi41OTM1MiIKICAgeT0iMi4wMzk1NiI+CjxyZWN0CiAgIGZpbGw9IiNmZmZmZmYiCiAgIGhlaWdodD0iMTQuMzM5IgogICBzdHJva2U9Im5vbmUiCiAgIHdpZHRoPSIxNi41MzE2MDEiCiAgIHg9IjYuNTkzNTIwMiIKICAgeT0iMi4wMzk1NjAxIgogICBpZD0icmVjdDgxOSIgLz4KPHVzZQogICBmaWxsPSIjMDAwMDAwIgogICBmaWxsLXJ1bGU9ImV2ZW5vZGQiCiAgIHN0cm9rZT0ibm9uZSIKICAgeGxpbms6aHJlZj0iI0ZpbGxfOCIKICAgaWQ9InVzZTgyMSIgLz4KPC9tYXNrPgo8dXNlCiAgIGZpbGw9Im5vbmUiCiAgIG1hc2s9InVybCgjU3Ryb2tlTWFza184KSIKICAgc3Ryb2tlPSIjNTUyZjgyIgogICBzdHJva2UtbGluZWNhcD0iYnV0dCIKICAgc3Ryb2tlLWxpbmVqb2luPSJtaXRlciIKICAgc3Ryb2tlLXdpZHRoPSIwLjk2IgogICB4bGluazpocmVmPSIjRmlsbF84IgogICBpZD0idXNlODI0IiAvPgo8L2c+CjxwYXRoCiAgIGQ9Im0gMjEuNDg4MywxNC42NCBjIDAsMCAtOS43NDA4LC0xMC4zODA3NSAtMTMuOTE5OTYsLTExLjkwMDc1IDAsMCAzLjYxNTk2LDAuNDk5MiA1LjU4MDc2LDEuMDM2OCAtMS45NDk4LC0wLjYyMzY2IC0zLjk1OTc3LC0xLjA0MDg4IC01Ljk5Njc2LC0xLjI0NDggMC4yMTEyLDEuMjggMS4zNDQsNi45MjQ4IDUuNDA0NzYsOS42OTkyNSAzLjQ2ODIsMi44NTU1IDcuMDk3OSwzLjcxMDIgOC43OTM4LDMuNjg4IDAuNTIzNCwtMC4xMDEgMC44OTY0LC0wLjU1OTkgMC44ODYsLTAuNTQ1OSAtMC4wMTYxLDAuMDA4NCAtMC42MjIsLTAuNTcxMiAtMC43NDg2LC0wLjczMjYgeiIKICAgZmlsbD0iIzc3N2FiYSIKICAgZmlsbC1ydWxlPSJldmVub2RkIgogICBvcGFjaXR5PSIxIgogICBzdHJva2U9Im5vbmUiCiAgIGlkPSJwYXRoODI4IiAvPgo8L2c+CjxnCiAgIG9wYWNpdHk9IjEiCiAgIGlkPSJnODQ1Ij4KPGcKICAgb3BhY2l0eT0iMSIKICAgaWQ9Imc4NDEiPgo8dXNlCiAgIGZpbGw9InVybCgjTGluZWFyR3JhZGllbnRfOCkiCiAgIGZpbGwtcnVsZT0ibm9uemVybyIKICAgc3Ryb2tlPSJub25lIgogICB4bGluazpocmVmPSIjRmlsbF85IgogICBpZD0idXNlODMyIgogICBzdHlsZT0iZmlsbDp1cmwoI0xpbmVhckdyYWRpZW50XzgpIiAvPgo8bWFzawogICBoZWlnaHQ9IjkuMTU4MzEiCiAgIGlkPSJTdHJva2VNYXNrXzkiCiAgIG1hc2tVbml0cz0idXNlclNwYWNlT25Vc2UiCiAgIHdpZHRoPSIyMS45MTY2IgogICB4PSIxLjEzNDExIgogICB5PSIxMi4xNjgzIj4KPHJlY3QKICAgZmlsbD0iI2ZmZmZmZiIKICAgaGVpZ2h0PSI5LjE1ODMwOTkiCiAgIHN0cm9rZT0ibm9uZSIKICAgd2lkdGg9IjIxLjkxNjU5OSIKICAgeD0iMS4xMzQxMSIKICAgeT0iMTIuMTY4MyIKICAgaWQ9InJlY3Q4MzQiIC8+Cjx1c2UKICAgZmlsbD0iIzAwMDAwMCIKICAgZmlsbC1ydWxlPSJldmVub2RkIgogICBzdHJva2U9Im5vbmUiCiAgIHhsaW5rOmhyZWY9IiNGaWxsXzkiCiAgIGlkPSJ1c2U4MzYiIC8+CjwvbWFzaz4KPHVzZQogICBmaWxsPSJub25lIgogICBtYXNrPSJ1cmwoI1N0cm9rZU1hc2tfOSkiCiAgIHN0cm9rZT0iIzU1MmY4MiIKICAgc3Ryb2tlLWxpbmVjYXA9ImJ1dHQiCiAgIHN0cm9rZS1saW5lam9pbj0ibWl0ZXIiCiAgIHN0cm9rZS13aWR0aD0iMC45NiIKICAgeGxpbms6aHJlZj0iI0ZpbGxfOSIKICAgaWQ9InVzZTgzOSIgLz4KPC9nPgo8cGF0aAogICBkPSJtIDIwLjM2MjYsMTYuODc4NCBjIDAsMCAtMTUuMTE5NDYsLTEuMDU4IC0xOC4yMzYyNiwtMC40MDg0IDAuNjM0NTUsLTAuNTMwMSAxLjI5Nzk2LC0xLjAyNDggMS45ODcyLC0xLjQ4MTYgLTAuNzkzOTQsMC40NjQxIC0xLjU2MTAzLDAuOTcyNiAtMi4yOTc2LDEuNTIzMiAxLjI4LDEuMTQ1NiA5LjE3NzU2LDcuNTIgMTguODE5MTYsMi4zMiAxLjk1NjEsLTEuMjg3MiAxLjk2NDksLTEuMzYwMiAxLjk0NDksLTEuMzQ1NCAtMC4wMiwwLjAxNDcgLTAuNzQ1MSwtMC40NDIxIC0yLjIwMzksLTAuNjAxNiB6IgogICBmaWxsPSIjNzc3YWJhIgogICBmaWxsLXJ1bGU9ImV2ZW5vZGQiCiAgIG9wYWNpdHk9IjEiCiAgIHN0cm9rZT0ibm9uZSIKICAgaWQ9InBhdGg4NDMiIC8+CjwvZz4KPGcKICAgaWQ9Imc0MzYxIgogICBzdHlsZT0iZGlzcGxheTppbmxpbmUiPjxwYXRoCiAgICAgZD0ibSAyMi44NzUyLDE0LjYyMTIgYyAwLjAyNDgsLTEuNDA1NiAyLjYyNCwtNi4zOTgyMSAtMC44ODM3LC0zLjkxNTUgLTIuMDAxLDEuNDE2MyAwLjkwNDUsMy45MTggMC44ODM3LDMuOTE1NSB6IgogICAgIGZpbGw9InVybCgjTGluZWFyR3JhZGllbnRfOSkiCiAgICAgZmlsbC1ydWxlPSJub256ZXJvIgogICAgIG9wYWNpdHk9IjEiCiAgICAgc3Ryb2tlPSJub25lIgogICAgIGlkPSJwYXRoODQ3IgogICAgIGlua3NjYXBlOmxhYmVsPSJwYXRoODQ3IgogICAgIHN0eWxlPSJjbGlwLXJ1bGU6ZXZlbm9kZDtmaWxsOnVybCgjTGluZWFyR3JhZGllbnRfOSk7ZmlsbC1ydWxlOm5vbnplcm87c3Ryb2tlLWxpbmVjYXA6cm91bmQ7c3Ryb2tlLWxpbmVqb2luOnJvdW5kIiAvPjxnCiAgICAgb3BhY2l0eT0iMSIKICAgICBpZD0iZzg1OCI+Cjx1c2UKICAgZmlsbD0idXJsKCNMaW5lYXJHcmFkaWVudF8xMCkiCiAgIGZpbGwtcnVsZT0ibm9uemVybyIKICAgc3Ryb2tlPSJub25lIgogICB4bGluazpocmVmPSIjRmlsbF8xMCIKICAgaWQ9InVzZTg0OSIKICAgc3R5bGU9ImZpbGw6dXJsKCNMaW5lYXJHcmFkaWVudF8xMCkiIC8+CjxtYXNrCiAgIGhlaWdodD0iOC41Njg2IgogICBpZD0iU3Ryb2tlTWFza18xMCIKICAgbWFza1VuaXRzPSJ1c2VyU3BhY2VPblVzZSIKICAgd2lkdGg9IjYuNzQyNjMiCiAgIHg9IjE5Ljk3MDQiCiAgIHk9IjExLjU5ODYiPgo8cmVjdAogICBmaWxsPSIjZmZmZmZmIgogICBoZWlnaHQ9IjguNTY4NTk5NyIKICAgc3Ryb2tlPSJub25lIgogICB3aWR0aD0iNi43NDI2MyIKICAgeD0iMTkuOTcwNCIKICAgeT0iMTEuNTk4NiIKICAgaWQ9InJlY3Q4NTEiIC8+Cjx1c2UKICAgZmlsbD0iIzAwMDAwMCIKICAgZmlsbC1ydWxlPSJldmVub2RkIgogICBzdHJva2U9Im5vbmUiCiAgIHhsaW5rOmhyZWY9IiNGaWxsXzEwIgogICBpZD0idXNlODUzIiAvPgo8L21hc2s+Cjx1c2UKICAgZmlsbD0ibm9uZSIKICAgbWFzaz0idXJsKCNTdHJva2VNYXNrXzEwKSIKICAgc3Ryb2tlPSIjNTUyZjgyIgogICBzdHJva2UtbGluZWNhcD0iYnV0dCIKICAgc3Ryb2tlLWxpbmVqb2luPSJtaXRlciIKICAgc3Ryb2tlLXdpZHRoPSIwLjk2IgogICB4bGluazpocmVmPSIjRmlsbF8xMCIKICAgaWQ9InVzZTg1NiIgLz4KPC9nPjxwYXRoCiAgICAgZD0ibSAyMy42MzY3LDEyLjgzNzggYyAtMC4wMDMyLDAgLTEuMzgzNCwwLjUxNzYgLTEuNDk4MSwwLjk1MDUgLTAuMTE0NywwLjQzMjkgMC4zODU1LDEuNjU4NSAwLjM4NTUsMS42NTg1IGwgMC4wNTc0LDAuMTQ2NCAtMC4xMjExLDAuMDk4NyBjIC0wLjAwNjQsMC4wMDMyIC0xLjk1NzcsMS43MjA0IC0xLjgyNzEsMi4yOTY2IDAuMTU2MSwwLjY0NjIgMS44NTI3LDIuNDY4NSAxLjg1MjcsMi40Njg1IDAsMCA0LjQ2NjcsLTEuMzQ5NSA1LjA0OSwtNC4xNzU1IDAuNTMxNiwtMi41Nzk2IC0zLjQ3MDYsLTUuMDczNSAtNC4zMTQyLC00Ljc3NzggLTAuMDQ3LDAuMDE2NSAtMS43ODA0LDAuNzA4MyAtMS43MDIxLDEuNDczIDAuMDQyMSwwLjQxMDkgMC43MzA5LC0wLjM0MTQgMi4xMTgsLTAuMTM4OSB6IgogICAgIGZpbGw9InVybCgjTGluZWFyR3JhZGllbnRfMTEpIgogICAgIGZpbGwtcnVsZT0ibm9uemVybyIKICAgICBvcGFjaXR5PSIxIgogICAgIHN0cm9rZT0ibm9uZSIKICAgICBpZD0icGF0aDg2MCIKICAgICBzdHlsZT0iZmlsbDp1cmwoI0xpbmVhckdyYWRpZW50XzExKSIgLz48L2c+CjwvZz4KPC9zdmc+Cg==" diff --git a/src/internal/packager2/layout/testdata/zarf-package/zarf.yaml b/src/internal/packager2/layout/testdata/zarf-package/zarf.yaml new file mode 100644 index 0000000000..695ac11604 --- /dev/null +++ b/src/internal/packager2/layout/testdata/zarf-package/zarf.yaml @@ -0,0 +1,41 @@ +kind: ZarfPackageConfig +metadata: + name: test + version: v0.0.1 +components: + - name: helm-charts + required: true + charts: + - name: podinfo-local + version: 6.4.0 + namespace: podinfo-from-local-chart + localPath: chart + valuesFiles: + - values.yaml + - name: files + required: true + files: + - source: data.txt + target: data.txt + - source: archive.tar + extractPath: archive-data.txt + target: archive-data.txt + - name: data-injections + required: true + dataInjections: + - source: injection + target: + namespace: test + selector: app=test + container: test + path: /test + compress: true + - name: manifests + required: true + manifests: + - name: deployment + namespace: httpd + files: + - deployment.yaml + kustomizations: + - kustomize diff --git a/src/pkg/lint/schema.go b/src/pkg/lint/schema.go index b6cb5f6e3e..a5f9009dc9 100644 --- a/src/pkg/lint/schema.go +++ b/src/pkg/lint/schema.go @@ -7,6 +7,7 @@ package lint import ( "fmt" "io/fs" + "path/filepath" "regexp" "github.com/xeipuuv/gojsonschema" @@ -17,6 +18,23 @@ import ( // ZarfSchema is exported so main.go can embed the schema file var ZarfSchema fs.ReadFileFS +// ValidatePackageSchemaAtPath checks the Zarf package in the current directory against the Zarf schema +func ValidatePackageSchemaAtPath(path string, setVariables map[string]string) ([]PackageFinding, error) { + var untypedZarfPackage interface{} + if err := utils.ReadYaml(filepath.Join(path, layout.ZarfYAML), &untypedZarfPackage); err != nil { + return nil, err + } + jsonSchema, err := ZarfSchema.ReadFile("zarf.schema.json") + if err != nil { + return nil, err + } + _, err = templateZarfObj(&untypedZarfPackage, setVariables) + if err != nil { + return nil, err + } + return getSchemaFindings(jsonSchema, untypedZarfPackage) +} + // ValidatePackageSchema checks the Zarf package in the current directory against the Zarf schema func ValidatePackageSchema(setVariables map[string]string) ([]PackageFinding, error) { var untypedZarfPackage interface{} diff --git a/src/pkg/packager/publish.go b/src/pkg/packager/publish.go index 6e421efd1f..684a42b177 100644 --- a/src/pkg/packager/publish.go +++ b/src/pkg/packager/publish.go @@ -7,17 +7,19 @@ package packager import ( "context" "fmt" - "os" + "io/fs" + "path/filepath" "strings" "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/pkg/oci" ocispec "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/zarf-dev/zarf/src/api/v1alpha1" "github.com/zarf-dev/zarf/src/config" + layout2 "github.com/zarf-dev/zarf/src/internal/packager2/layout" "github.com/zarf-dev/zarf/src/pkg/layout" "github.com/zarf-dev/zarf/src/pkg/message" - "github.com/zarf-dev/zarf/src/pkg/packager/creator" "github.com/zarf-dev/zarf/src/pkg/packager/filters" "github.com/zarf-dev/zarf/src/pkg/packager/sources" "github.com/zarf-dev/zarf/src/pkg/utils" @@ -48,26 +50,36 @@ func (p *Packager) Publish(ctx context.Context) (err error) { } if p.cfg.CreateOpts.IsSkeleton { - if err := os.Chdir(p.cfg.CreateOpts.BaseDir); err != nil { - return fmt.Errorf("unable to access directory %q: %w", p.cfg.CreateOpts.BaseDir, err) - } - - sc := creator.NewSkeletonCreator(p.cfg.CreateOpts, p.cfg.PublishOpts) - - if err := helpers.CreatePathAndCopy(layout.ZarfYAML, p.layout.ZarfYAML); err != nil { - return err + skeletonOpt := layout2.CreateOptions{ + Flavor: p.cfg.CreateOpts.Flavor, + RegistryOverrides: p.cfg.CreateOpts.RegistryOverrides, + SigningKeyPath: p.cfg.CreateOpts.SigningKeyPath, + SigningKeyPassword: p.cfg.CreateOpts.SigningKeyPassword, + SetVariables: p.cfg.CreateOpts.SetVariables, } - - p.cfg.Pkg, _, err = sc.LoadPackageDefinition(ctx, p.layout) + skeletonPath, err := layout2.CreateSkeleton(ctx, p.cfg.CreateOpts.BaseDir, skeletonOpt) if err != nil { return err } - - if err := sc.Assemble(ctx, p.layout, p.cfg.Pkg.Components, ""); err != nil { + p.layout = layout.New(skeletonPath) + layoutPaths := []string{} + err = filepath.Walk(skeletonPath, func(path string, _ fs.FileInfo, err error) error { + if err != nil { + return err + } + rel, err := filepath.Rel(skeletonPath, path) + if err != nil { + return err + } + layoutPaths = append(layoutPaths, rel) + return nil + }) + if err != nil { return err } - - if err := sc.Output(ctx, p.layout, &p.cfg.Pkg); err != nil { + p.layout.SetFromPaths(layoutPaths) + p.cfg.Pkg, _, err = p.layout.ReadZarfYAML() + if err != nil { return err } } else { diff --git a/src/test/e2e/14_oci_compose_test.go b/src/test/e2e/14_oci_compose_test.go index e186a6e076..6a983c7fc3 100644 --- a/src/test/e2e/14_oci_compose_test.go +++ b/src/test/e2e/14_oci_compose_test.go @@ -104,8 +104,6 @@ func (suite *PublishCopySkeletonSuite) Test_1_Compose_Everything_Inception() { suite.NoError(err) targets := []string{ - "import-component-local == import-component-local", - "import-component-oci == import-component-oci", "file-imports == file-imports", "local-chart-import == local-chart-import", } diff --git a/src/test/packages/14-import-everything/zarf.yaml b/src/test/packages/14-import-everything/zarf.yaml index 5a096ae3db..0ca7f80a3a 100644 --- a/src/test/packages/14-import-everything/zarf.yaml +++ b/src/test/packages/14-import-everything/zarf.yaml @@ -6,20 +6,20 @@ metadata: components: # Test every simple primitive that Zarf has through a local import - - name: import-component-local - description: "import-component-local == ###ZARF_COMPONENT_NAME###" - required: false - import: - path: ../09-composable-packages - name: test-compose-package + # - name: import-component-local + # description: "import-component-local == ###ZARF_COMPONENT_NAME###" + # required: false + # import: + # path: ../09-composable-packages + # name: test-compose-package # Test nested local to oci imports - - name: import-component-oci - description: "import-component-oci == ###ZARF_COMPONENT_NAME###" - required: false - import: - name: import-component-oci - path: oci-import + # - name: import-component-oci + # description: "import-component-oci == ###ZARF_COMPONENT_NAME###" + # required: false + # import: + # name: import-component-oci + # path: oci-import # Test file imports including cosignKeyPath - name: file-imports