From 287ae4645cf60c5c4f52bdbc4c6cf6cb7b3564da Mon Sep 17 00:00:00 2001 From: Benny Zlotnik Date: Wed, 7 Jan 2026 13:23:10 +0200 Subject: [PATCH 1/7] clean caib API Signed-off-by: Benny Zlotnik --- .claude/skills/test-bootc-build/SKILL.md | 49 +- api/v1alpha1/imagebuild_types.go | 4 + bundle.Dockerfile | 4 - ...ve-dev-operator.clusterserviceversion.yaml | 40 +- ...ve.sdv.cloud.redhat.com_catalogimages.yaml | 25 +- ...tive.sdv.cloud.redhat.com_imagebuilds.yaml | 5 + bundle/metadata/annotations.yaml | 4 - cmd/caib/main.go | 644 ++++++++---------- ...tive.sdv.cloud.redhat.com_imagebuilds.yaml | 5 + config/manager/kustomization.yaml | 4 +- internal/buildapi/server.go | 15 +- internal/buildapi/types.go | 7 +- .../common/tasks/scripts/build_builder.sh | 6 +- internal/common/tasks/scripts/build_image.sh | 29 +- internal/common/tasks/tasks.go | 49 ++ internal/controller/imagebuild/controller.go | 13 +- 16 files changed, 467 insertions(+), 436 deletions(-) diff --git a/.claude/skills/test-bootc-build/SKILL.md b/.claude/skills/test-bootc-build/SKILL.md index 35121abf..086bb5a6 100644 --- a/.claude/skills/test-bootc-build/SKILL.md +++ b/.claude/skills/test-bootc-build/SKILL.md @@ -58,23 +58,27 @@ REGISTRY_PASSWORD="${REGISTRY_PASSWORD:-$(cat registry-pass 2>/dev/null)}" ### 5. Submit Build with Download +The `build` command creates a bootc container image and optionally a disk image. + ```bash BUILD_NAME="$ARGUMENTS" PUSH_IMAGE="${BOOTC_REGISTRY}:latest" DISK_IMAGE="${BOOTC_REGISTRY}:latest-disk" OUTPUT_FILE="./output/${BUILD_NAME}.qcow2" -bin/caib build-bootc simple.aib.yml \ +bin/caib build simple.aib.yml \ --server "$CAIB_SERVER" \ --token "$TOKEN" \ --name "$BUILD_NAME" \ --arch arm64 \ --target qemu \ --push "$PUSH_IMAGE" \ - --build-disk-image \ + --disk \ --format qcow2 \ - --export-oci "$DISK_IMAGE" \ - --download "$OUTPUT_FILE" \ + --push-disk "$DISK_IMAGE" \ + --output "$OUTPUT_FILE" \ + --registry-username "$REGISTRY_USERNAME" \ + --registry-password "$REGISTRY_PASSWORD" \ --follow ``` @@ -121,6 +125,42 @@ Provide a summary with: - If failed: the specific error and relevant log snippets - Suggestions for fixes based on the error type +## CLI Command Reference + +### build (bootc - default) +Creates a bootc container image from an AIB manifest. + +```bash +caib build \ + --push # Required: push container to registry + --disk # Also build disk image from container + --format # Disk image format (default: qcow2) + --push-disk # Push disk as OCI artifact + --output # Download disk image to file + --follow # Follow build logs + --wait # Wait for build to complete +``` + +### disk +Creates a disk image from an existing bootc container. + +```bash +caib disk \ + --push # Push disk as OCI artifact + --output # Download disk image to file + --format # Disk format (default: qcow2) +``` + +### build-legacy +Traditional ostree/package-based builds (for backwards compatibility). + +```bash +caib build-legacy \ + --mode # Required: build mode + --format # Required: export format + --name # Required: build name +``` + ## Common Error Patterns - "Builder image not found" - prepare-builder task failed or result not passed @@ -128,6 +168,7 @@ Provide a summary with: - "setfiles: Operation not supported" - SELinux context issues in osbuild - "unauthorized" - Token or registry auth issues - "BOOTC_REGISTRY not set" - Environment variable missing +- "container-ref is required for disk mode" - Missing container reference for disk command ## Alternative: Download Only (for completed builds) diff --git a/api/v1alpha1/imagebuild_types.go b/api/v1alpha1/imagebuild_types.go index a3956af2..0eb1ac67 100644 --- a/api/v1alpha1/imagebuild_types.go +++ b/api/v1alpha1/imagebuild_types.go @@ -89,6 +89,10 @@ type ImageBuildSpec struct { // BuilderImage is a custom builder image to use BuilderImage string `json:"builderImage,omitempty"` + + // ContainerRef is the reference to an existing bootc container image + // Used with mode=disk to create a disk image from an existing container + ContainerRef string `json:"containerRef,omitempty"` } // Publishers defines the configuration for artifact publishing diff --git a/bundle.Dockerfile b/bundle.Dockerfile index cc49f856..df67655c 100644 --- a/bundle.Dockerfile +++ b/bundle.Dockerfile @@ -6,7 +6,6 @@ LABEL operators.operatorframework.io.bundle.manifests.v1=manifests/ LABEL operators.operatorframework.io.bundle.metadata.v1=metadata/ LABEL operators.operatorframework.io.bundle.package.v1=automotive-dev-operator LABEL operators.operatorframework.io.bundle.channels.v1=alpha -LABEL operators.operatorframework.io.bundle.channel.default.v1=alpha LABEL operators.operatorframework.io.metrics.builder=operator-sdk-v1.42.0 LABEL operators.operatorframework.io.metrics.mediatype.v1=metrics+v1 LABEL operators.operatorframework.io.metrics.project_layout=go.kubebuilder.io/v4 @@ -15,9 +14,6 @@ LABEL operators.operatorframework.io.metrics.project_layout=go.kubebuilder.io/v4 LABEL operators.operatorframework.io.test.mediatype.v1=scorecard+v1 LABEL operators.operatorframework.io.test.config.v1=tests/scorecard/ -# OpenShift labels -LABEL com.redhat.openshift.versions="v4.17" - # Copy files to locations specified by labels. COPY bundle/manifests /manifests/ COPY bundle/metadata /metadata/ diff --git a/bundle/manifests/automotive-dev-operator.clusterserviceversion.yaml b/bundle/manifests/automotive-dev-operator.clusterserviceversion.yaml index 7f6e915f..e5819a8a 100644 --- a/bundle/manifests/automotive-dev-operator.clusterserviceversion.yaml +++ b/bundle/manifests/automotive-dev-operator.clusterserviceversion.yaml @@ -53,12 +53,10 @@ metadata: capabilities: Basic Install categories: Developer Tools containerImage: quay.io/rh-sdv-cloud/automotive-dev-operator:latest - createdAt: "2026-01-04T16:02:41Z" + createdAt: "2026-01-07T10:28:46Z" operatorframework.io/suggested-namespace: automotive-dev-operator-system operators.operatorframework.io/builder: operator-sdk-v1.42.0 operators.operatorframework.io/project_layout: go.kubebuilder.io/v4 - operators.openshift.io/infrastructure-features: '["disconnected"]' - operators.openshift.io/valid-subscription: '["OpenShift Container Platform"]' repository: https://github.com/centos-automotive-suite/automotive-dev-operator support: Red Hat name: automotive-dev-operator.v0.0.1 @@ -67,6 +65,9 @@ spec: apiservicedefinitions: {} customresourcedefinitions: owned: + - kind: CatalogImage + name: catalogimages.automotive.sdv.cloud.redhat.com + version: v1alpha1 - description: ImageBuild is the Schema for the imagebuilds API displayName: Image Build kind: ImageBuild @@ -82,29 +83,7 @@ spec: kind: OperatorConfig name: operatorconfigs.automotive.sdv.cloud.redhat.com version: v1alpha1 - description: | - The CentOS Automotive Suite Operator provides a Kubernetes-native way to build automotive OS images on OpenShift. - - ## Features - - * **ImageBuild Custom Resource**: Define and trigger automotive OS image builds declaratively - * **Multiple Build Modes**: Support for traditional AIB manifests and bootc container builds - * **Web UI**: Optional web interface for managing builds and viewing artifacts - * **Artifact Management**: Serve built images via Routes or push to OCI registries - - ## Prerequisites - - * OpenShift Pipelines Operator (Tekton) must be installed - - ## Getting Started - - 1. Install the operator from OperatorHub - 2. Create an OperatorConfig resource to configure the operator - 3. Create an ImageBuild resource to trigger a build - - ## Documentation - - For detailed documentation, visit the [project repository](https://github.com/centos-automotive-suite/automotive-dev-operator). + description: CentOS Automotive Suite displayName: CentOS Automotive Suite icon: - base64data: "" @@ -318,8 +297,8 @@ spec: fieldRef: fieldPath: metadata.namespace - name: OPERATOR_IMAGE - value: quay.io/rh-sdv-cloud/automotive-dev-operator:latest - image: quay.io/rh-sdv-cloud/automotive-dev-operator:latest + value: image-registry.openshift-image-registry.svc:5000/automotive-dev-operator-system/automotive-dev-operator:latest + image: default-route-openshift-image-registry.apps.automotive1.ocp.automotive.sig.centos.org/automotive-dev-operator-system/automotive-dev-operator:latest imagePullPolicy: IfNotPresent livenessProbe: httpGet: @@ -357,7 +336,7 @@ spec: valueFrom: fieldRef: fieldPath: metadata.namespace - image: quay.io/rh-sdv-cloud/automotive-dev-operator:latest + image: default-route-openshift-image-registry.apps.automotive1.ocp.automotive.sig.centos.org/automotive-dev-operator-system/automotive-dev-operator:latest imagePullPolicy: IfNotPresent name: init-oauth-secrets resources: @@ -429,7 +408,4 @@ spec: minKubeVersion: 1.26.0 provider: name: Red Hat - relatedImages: - - image: quay.io/rh-sdv-cloud/automotive-dev-operator:latest - name: manager version: 0.0.1 diff --git a/bundle/manifests/automotive.sdv.cloud.redhat.com_catalogimages.yaml b/bundle/manifests/automotive.sdv.cloud.redhat.com_catalogimages.yaml index 30f7147e..e3d04710 100644 --- a/bundle/manifests/automotive.sdv.cloud.redhat.com_catalogimages.yaml +++ b/bundle/manifests/automotive.sdv.cloud.redhat.com_catalogimages.yaml @@ -79,12 +79,9 @@ spec: description: Metadata contains automotive-specific image metadata properties: architecture: - description: Architecture is the CPU architecture (amd64, arm64) - enum: - - amd64 - - arm64 - - x86_64 - - aarch64 + description: |- + Architecture is the CPU architecture + Supports both AIB canonical values (x86_64, aarch64) and OCI standard names (amd64, arm64) type: string bootc: description: Bootc indicates if this is a bootc-compatible image @@ -98,11 +95,19 @@ spec: - package type: string distro: - description: Distro is the distribution identifier (cs9, autosd10-sig) + description: |- + Distro is the distribution identifier + Common values include: cs9, autosd10-sig, autosd9, cs8, fedora-39, fedora-40, rhel9, rhel8 + Run 'aib list-dist' to see all available distributions type: string distroVersion: description: DistroVersion is the distribution version type: string + exportFormat: + description: |- + ExportFormat indicates the disk image format produced by AIB + Common values include: qcow2, raw, image, vmdk, iso, vhd, tar + type: string os: default: linux description: OS is the operating system (defaults to linux) @@ -114,8 +119,10 @@ spec: image supports properties: name: - description: Name is the target hardware identifier (qemu, - raspberry-pi, beaglebone) + description: |- + Name is the target hardware identifier + Common values include: qemu, raspberry-pi, beaglebone, generic + Run 'aib list-targets' to see all available hardware targets type: string notes: description: Notes contains target-specific information diff --git a/bundle/manifests/automotive.sdv.cloud.redhat.com_imagebuilds.yaml b/bundle/manifests/automotive.sdv.cloud.redhat.com_imagebuilds.yaml index 76656eff..e530fbd7 100644 --- a/bundle/manifests/automotive.sdv.cloud.redhat.com_imagebuilds.yaml +++ b/bundle/manifests/automotive.sdv.cloud.redhat.com_imagebuilds.yaml @@ -65,6 +65,11 @@ spec: description: ContainerPush is the registry URL to push the bootc container image type: string + containerRef: + description: |- + ContainerRef is the reference to an existing bootc container image + Used with mode=disk to create a disk image from an existing container + type: string distro: description: Distro specifies the distribution to build for (e.g., "cs9") diff --git a/bundle/metadata/annotations.yaml b/bundle/metadata/annotations.yaml index 3174e01b..710e3f41 100644 --- a/bundle/metadata/annotations.yaml +++ b/bundle/metadata/annotations.yaml @@ -5,7 +5,6 @@ annotations: operators.operatorframework.io.bundle.metadata.v1: metadata/ operators.operatorframework.io.bundle.package.v1: automotive-dev-operator operators.operatorframework.io.bundle.channels.v1: alpha - operators.operatorframework.io.bundle.channel.default.v1: alpha operators.operatorframework.io.metrics.builder: operator-sdk-v1.42.0 operators.operatorframework.io.metrics.mediatype.v1: metrics+v1 operators.operatorframework.io.metrics.project_layout: go.kubebuilder.io/v4 @@ -13,6 +12,3 @@ annotations: # Annotations for testing. operators.operatorframework.io.test.mediatype.v1: scorecard+v1 operators.operatorframework.io.test.config.v1: tests/scorecard/ - - # OpenShift annotations - com.redhat.openshift.versions: "v4.17" diff --git a/cmd/caib/main.go b/cmd/caib/main.go index edbc87bb..99fff51f 100644 --- a/cmd/caib/main.go +++ b/cmd/caib/main.go @@ -12,6 +12,7 @@ import ( "os" "os/exec" "path/filepath" + "runtime" "strings" "time" @@ -30,6 +31,18 @@ import ( "k8s.io/client-go/tools/clientcmd" ) +// getDefaultArch returns the current system architecture in caib format +func getDefaultArch() string { + switch runtime.GOARCH { + case "amd64": + return "amd64" + case "arm64": + return "arm64" + default: + return "amd64" + } +} + var ( serverURL string imageBuildCfg string @@ -50,7 +63,6 @@ var ( followLogs bool version string aibExtraArgs string - aibOverrideArgs string compressArtifacts bool compressionAlgo string authToken string @@ -66,6 +78,7 @@ var ( builderImage string downloadFile string + containerRef string ) func main() { @@ -78,27 +91,79 @@ func main() { rootCmd.InitDefaultVersionFlag() rootCmd.SetVersionTemplate("caib version: {{.Version}}\n") - // New subcommands - buildBootcCmd := &cobra.Command{ - Use: "build-bootc ", + // Main build command (bootc - the default, future-focused approach) + buildCmd := &cobra.Command{ + Use: "build ", Short: "Build bootc container image with optional disk image", - Args: cobra.ExactArgs(1), - Run: runBuildBootc, + Long: `Build creates a bootc container image from an AIB manifest. + +Bootc images are immutable, atomically updatable OS images based on +container technology. This is the recommended approach for production. + +Examples: + # Build and push container to registry + caib build manifest.aib.yml --push quay.io/org/my-os:v1 + + # Build container + create disk image + caib build manifest.aib.yml --push quay.io/org/my-os:v1 --disk -o disk.qcow2`, + Args: cobra.ExactArgs(1), + Run: runBuild, + } + + // Disk command - create disk from existing container + diskCmd := &cobra.Command{ + Use: "disk ", + Short: "Create disk image from existing bootc container", + Long: `Create a disk image from an existing bootc container in a registry. + +This uses 'aib to-disk-image' to convert a bootc container to a disk +image that can be flashed onto hardware. + +Examples: + # Create disk image from container + caib disk quay.io/org/my-os:v1 -o disk.qcow2 --format qcow2 + + # Push disk as OCI artifact instead of downloading + caib disk quay.io/org/my-os:v1 --push quay.io/org/my-disk:v1`, + Args: cobra.ExactArgs(1), + Run: runDisk, } - buildTraditionalCmd := &cobra.Command{ - Use: "build-traditional ", + // Legacy build command (traditional ostree/package-based) + buildLegacyCmd := &cobra.Command{ + Use: "build-legacy ", Short: "Build traditional disk image (ostree or package-based)", - Args: cobra.ExactArgs(1), - Run: runBuildTraditional, + Long: `Build a traditional disk image using ostree or package-based mode. + +This is for legacy workflows. For new projects, use 'caib build' (bootc). + +Examples: + # Ostree-based image + caib build-legacy manifest.aib.yml --mode image --format qcow2 -o disk.qcow2 + + # Package-based image + caib build-legacy manifest.aib.yml --mode package --format raw -o disk.raw`, + Args: cobra.ExactArgs(1), + Run: runBuildLegacy, } - // Legacy build command (deprecated) - buildCmd := &cobra.Command{ - Use: "build", - Short: "Create an ImageBuild resource (deprecated, use build-bootc or build-traditional)", + // Deprecated aliases (hidden but functional for backwards compatibility) + buildBootcAliasCmd := &cobra.Command{ + Use: "build-bootc ", + Short: "Build bootc container image (deprecated: use 'build' instead)", + Args: cobra.ExactArgs(1), Run: runBuild, - Deprecated: "use 'build-bootc' or 'build-traditional' instead", + Deprecated: "use 'build' instead (bootc is now the default)", + Hidden: true, + } + + buildTraditionalAliasCmd := &cobra.Command{ + Use: "build-traditional ", + Short: "Build traditional disk image (deprecated: use 'build-legacy' instead)", + Args: cobra.ExactArgs(1), + Run: runBuildLegacy, + Deprecated: "use 'build-legacy' instead", + Hidden: true, } downloadCmd := &cobra.Command{ @@ -113,32 +178,29 @@ func main() { Run: runList, } - buildCmd.Flags().StringVar(&serverURL, "server", os.Getenv("CAIB_SERVER"), "REST API server base URL (e.g. https://api.example)") - buildCmd.Flags().StringVar(&authToken, "token", os.Getenv("CAIB_TOKEN"), "Bearer token for authentication (e.g., OpenShift access token)") - buildCmd.Flags().StringVar(&imageBuildCfg, "config", "", "path to ImageBuild YAML configuration file") - buildCmd.Flags().StringVar(&manifest, "manifest", "", "path to manifest YAML file for the build") - buildCmd.Flags().StringVar(&buildName, "name", "", "name for the ImageBuild") - buildCmd.Flags().StringVar(&distro, "distro", "autosd", "distribution to build") - buildCmd.Flags().StringVar(&target, "target", "qemu", "target platform (qemu, etc)") - buildCmd.Flags().StringVar(&architecture, "arch", "arm64", "architecture (amd64, arm64)") - buildCmd.Flags().StringVar(&exportFormat, "format", "qcow2", "disk image format (qcow2, raw, etc)") - buildCmd.Flags().StringVar(&mode, "mode", "bootc", "build mode (bootc, image, package)") - buildCmd.Flags().StringVar(&automotiveImageBuilder, "automotive-image-builder", "quay.io/centos-sig-automotive/automotive-image-builder:1.0.0", "container image for automotive-image-builder") - buildCmd.Flags().StringVar(&storageClass, "storage-class", "", "storage class to use for build workspace PVC") - buildCmd.Flags().IntVar(&timeout, "timeout", 60, "timeout in minutes when waiting for build completion") - buildCmd.Flags().BoolVarP(&waitForBuild, "wait", "w", false, "wait for the build to complete") - buildCmd.Flags().BoolVarP(&download, "download", "d", false, "automatically download artifacts when build completes") - buildCmd.Flags().BoolVar(&compressArtifacts, "compress", true, "compress directory artifacts (tar.gz). For directories, server always compresses.") - buildCmd.Flags().BoolVarP(&followLogs, "follow", "f", false, "follow logs of the build") - buildCmd.Flags().StringArrayVar(&customDefs, "define", []string{}, "Custom definition in KEY=VALUE format (can be specified multiple times)") - buildCmd.Flags().StringVar(&aibExtraArgs, "aib-args", "", "extra arguments passed to automotive-image-builder (space-separated)") - buildCmd.Flags().StringVar(&aibOverrideArgs, "override", "", "override arguments passed as-is to automotive-image-builder") - buildCmd.Flags().StringVar(&compressionAlgo, "compression", "gzip", "artifact compression algorithm (lz4|gzip)") - buildCmd.Flags().StringVar(&pushRepository, "push", "", "push artifact to OCI registry (e.g., quay.io/myorg/myimage:tag)") - buildCmd.Flags().StringVar(®istryURL, "registry-url", "", "registry URL for authentication (optional, extracted from --push if not set)") - buildCmd.Flags().StringVar(®istryUsername, "registry-username", "", "registry username for push authentication") - buildCmd.Flags().StringVar(®istryPassword, "registry-password", "", "registry password for push authentication") - _ = buildCmd.MarkFlagRequired("arch") + // build command flags (bootc - the default) + buildCmd.Flags().StringVar(&serverURL, "server", os.Getenv("CAIB_SERVER"), "REST API server base URL") + buildCmd.Flags().StringVar(&authToken, "token", os.Getenv("CAIB_TOKEN"), "Bearer token for authentication") + buildCmd.Flags().StringVarP(&buildName, "name", "n", "", "name for the ImageBuild (auto-generated if omitted)") + buildCmd.Flags().StringVarP(&distro, "distro", "d", "autosd", "distribution to build") + buildCmd.Flags().StringVarP(&target, "target", "t", "qemu", "target platform") + buildCmd.Flags().StringVarP(&architecture, "arch", "a", getDefaultArch(), "architecture (amd64, arm64)") + buildCmd.Flags().StringVar(&containerPush, "push", "", "push bootc container to registry (required)") + buildCmd.Flags().BoolVar(&buildDiskImage, "disk", false, "also build disk image from container") + buildCmd.Flags().StringVarP(&outputDir, "output", "o", "", "download disk image to file (requires --disk)") + buildCmd.Flags().StringVar(&diskFormat, "format", "qcow2", "disk image format (qcow2, raw, simg)") + buildCmd.Flags().StringVar(&compressionAlgo, "compress", "gzip", "compression algorithm (gzip, lz4, xz)") + buildCmd.Flags().StringVar(&exportOCI, "push-disk", "", "push disk image as OCI artifact to registry") + buildCmd.Flags().StringVar(®istryUsername, "registry-username", "", "registry username (or REGISTRY_USERNAME env)") + buildCmd.Flags().StringVar(®istryPassword, "registry-password", "", "registry password (or REGISTRY_PASSWORD env)") + buildCmd.Flags().StringVar(&automotiveImageBuilder, "aib-image", "quay.io/centos-sig-automotive/automotive-image-builder:latest", "AIB container image") + buildCmd.Flags().StringVar(&builderImage, "builder-image", "", "custom builder container") + buildCmd.Flags().StringVar(&storageClass, "storage-class", "", "Kubernetes storage class for build workspace") + buildCmd.Flags().StringArrayVarP(&customDefs, "define", "D", []string{}, "custom definition KEY=VALUE") + buildCmd.Flags().IntVar(&timeout, "timeout", 60, "timeout in minutes") + buildCmd.Flags().BoolVarP(&waitForBuild, "wait", "w", false, "wait for build to complete") + buildCmd.Flags().BoolVarP(&followLogs, "follow", "f", false, "follow build logs") + _ = buildCmd.MarkFlagRequired("push") downloadCmd.Flags().StringVar(&serverURL, "server", os.Getenv("CAIB_SERVER"), "REST API server base URL (e.g. https://api.example)") downloadCmd.Flags().StringVar(&authToken, "token", os.Getenv("CAIB_TOKEN"), "Bearer token for authentication (e.g., OpenShift access token)") @@ -150,55 +212,52 @@ func main() { listCmd.Flags().StringVar(&serverURL, "server", os.Getenv("CAIB_SERVER"), "REST API server base URL (e.g. https://api.example)") listCmd.Flags().StringVar(&authToken, "token", os.Getenv("CAIB_TOKEN"), "Bearer token for authentication (e.g., OpenShift access token)") - // build-bootc flags - buildBootcCmd.Flags().StringVar(&serverURL, "server", os.Getenv("CAIB_SERVER"), "REST API server base URL") - buildBootcCmd.Flags().StringVar(&authToken, "token", os.Getenv("CAIB_TOKEN"), "Bearer token for authentication") - buildBootcCmd.Flags().StringVar(&buildName, "name", "", "name for the ImageBuild") - buildBootcCmd.Flags().StringVar(&distro, "distro", "autosd", "distribution to build") - buildBootcCmd.Flags().StringVar(&target, "target", "qemu", "target platform") - buildBootcCmd.Flags().StringVar(&architecture, "arch", "arm64", "architecture (amd64, arm64)") - buildBootcCmd.Flags().StringVar(&containerPush, "push", "", "push bootc container to registry (e.g., quay.io/org/image:tag)") - buildBootcCmd.Flags().BoolVar(&buildDiskImage, "build-disk-image", false, "also build disk image from container") - buildBootcCmd.Flags().StringVar(&diskFormat, "format", "qcow2", "disk image format (qcow2, raw, simg)") - buildBootcCmd.Flags().StringVar(&compressionAlgo, "compress", "gzip", "compression algorithm (gzip, lz4, xz)") - buildBootcCmd.Flags().StringVar(&exportOCI, "export-oci", "", "push disk image as OCI artifact to registry") - buildBootcCmd.Flags().StringVar(®istryUsername, "registry-username", "", "registry username for push/export (or set REGISTRY_USERNAME env var)") - buildBootcCmd.Flags().StringVar(®istryPassword, "registry-password", "", "registry password for push/export (or set REGISTRY_PASSWORD env var, or use docker/podman auth)") - buildBootcCmd.Flags().StringVar(&automotiveImageBuilder, "automotive-image-builder", "quay.io/centos-sig-automotive/automotive-image-builder:latest", "container image for aib") - buildBootcCmd.Flags().StringVar(&builderImage, "builder-image", "", "custom aib-build container") - buildBootcCmd.Flags().StringVar(&storageClass, "storage-class", "", "storage class for build workspace PVC") - buildBootcCmd.Flags().StringArrayVar(&customDefs, "define", []string{}, "custom definition KEY=VALUE") - buildBootcCmd.Flags().IntVar(&timeout, "timeout", 60, "timeout in minutes") - buildBootcCmd.Flags().BoolVarP(&waitForBuild, "wait", "w", false, "wait for build to complete") - buildBootcCmd.Flags().BoolVarP(&followLogs, "follow", "f", false, "follow build logs") - buildBootcCmd.Flags().StringVar(&downloadFile, "download", "", "download disk image artifact to local file (requires --export-oci)") - _ = buildBootcCmd.MarkFlagRequired("name") - _ = buildBootcCmd.MarkFlagRequired("arch") - - // build-traditional flags - buildTraditionalCmd.Flags().StringVar(&serverURL, "server", os.Getenv("CAIB_SERVER"), "REST API server base URL") - buildTraditionalCmd.Flags().StringVar(&authToken, "token", os.Getenv("CAIB_TOKEN"), "Bearer token for authentication") - buildTraditionalCmd.Flags().StringVar(&buildName, "name", "", "name for the ImageBuild") - buildTraditionalCmd.Flags().StringVar(&distro, "distro", "autosd", "distribution to build") - buildTraditionalCmd.Flags().StringVar(&target, "target", "qemu", "target platform") - buildTraditionalCmd.Flags().StringVar(&architecture, "arch", "arm64", "architecture (amd64, arm64)") - buildTraditionalCmd.Flags().StringVar(&mode, "mode", "image", "traditional mode (image, package)") - buildTraditionalCmd.Flags().StringVar(&exportFormat, "export", "qcow2", "export format (qcow2, raw, simg)") - buildTraditionalCmd.Flags().StringVar(&compressionAlgo, "compress", "gzip", "compression algorithm (gzip, lz4, xz)") - buildTraditionalCmd.Flags().StringVar(&downloadFile, "download", "", "download artifact to local file") - buildTraditionalCmd.Flags().StringVar(&exportOCI, "push", "", "push disk image as OCI artifact to registry") - buildTraditionalCmd.Flags().StringVar(®istryUsername, "registry-username", "", "registry username for push (or set REGISTRY_USERNAME env var)") - buildTraditionalCmd.Flags().StringVar(®istryPassword, "registry-password", "", "registry password for push (or set REGISTRY_PASSWORD env var, or use docker/podman auth)") - buildTraditionalCmd.Flags().StringVar(&automotiveImageBuilder, "automotive-image-builder", "quay.io/centos-sig-automotive/automotive-image-builder:latest", "container image for aib") - buildTraditionalCmd.Flags().StringVar(&storageClass, "storage-class", "", "storage class for build workspace PVC") - buildTraditionalCmd.Flags().StringArrayVar(&customDefs, "define", []string{}, "custom definition KEY=VALUE") - buildTraditionalCmd.Flags().IntVar(&timeout, "timeout", 60, "timeout in minutes") - buildTraditionalCmd.Flags().BoolVarP(&waitForBuild, "wait", "w", false, "wait for build to complete") - buildTraditionalCmd.Flags().BoolVarP(&followLogs, "follow", "f", false, "follow build logs") - _ = buildTraditionalCmd.MarkFlagRequired("name") - _ = buildTraditionalCmd.MarkFlagRequired("arch") - - rootCmd.AddCommand(buildBootcCmd, buildTraditionalCmd, buildCmd, downloadCmd, listCmd, catalog.NewCatalogCmd()) + // disk command flags (create disk from existing container) + diskCmd.Flags().StringVar(&serverURL, "server", os.Getenv("CAIB_SERVER"), "REST API server base URL") + diskCmd.Flags().StringVar(&authToken, "token", os.Getenv("CAIB_TOKEN"), "Bearer token for authentication") + diskCmd.Flags().StringVarP(&buildName, "name", "n", "", "name for the build job (auto-generated if omitted)") + diskCmd.Flags().StringVarP(&outputDir, "output", "o", "", "download disk image to file") + diskCmd.Flags().StringVar(&diskFormat, "format", "qcow2", "disk image format (qcow2, raw, simg)") + diskCmd.Flags().StringVar(&compressionAlgo, "compress", "gzip", "compression algorithm (gzip, lz4, xz)") + diskCmd.Flags().StringVar(&exportOCI, "push", "", "push disk image as OCI artifact to registry") + diskCmd.Flags().StringVar(®istryUsername, "registry-username", "", "registry username (or REGISTRY_USERNAME env)") + diskCmd.Flags().StringVar(®istryPassword, "registry-password", "", "registry password (or REGISTRY_PASSWORD env)") + diskCmd.Flags().StringVarP(&distro, "distro", "d", "autosd", "distribution") + diskCmd.Flags().StringVarP(&target, "target", "t", "qemu", "target platform") + diskCmd.Flags().StringVarP(&architecture, "arch", "a", getDefaultArch(), "architecture (amd64, arm64)") + diskCmd.Flags().StringVar(&automotiveImageBuilder, "aib-image", "quay.io/centos-sig-automotive/automotive-image-builder:latest", "AIB container image") + diskCmd.Flags().StringVar(&storageClass, "storage-class", "", "Kubernetes storage class") + diskCmd.Flags().IntVar(&timeout, "timeout", 60, "timeout in minutes") + diskCmd.Flags().BoolVarP(&waitForBuild, "wait", "w", false, "wait for build to complete") + diskCmd.Flags().BoolVarP(&followLogs, "follow", "f", false, "follow build logs") + + // build-legacy command flags (traditional ostree/package builds) + buildLegacyCmd.Flags().StringVar(&serverURL, "server", os.Getenv("CAIB_SERVER"), "REST API server base URL") + buildLegacyCmd.Flags().StringVar(&authToken, "token", os.Getenv("CAIB_TOKEN"), "Bearer token for authentication") + buildLegacyCmd.Flags().StringVarP(&buildName, "name", "n", "", "name for the ImageBuild") + buildLegacyCmd.Flags().StringVarP(&distro, "distro", "d", "autosd", "distribution to build") + buildLegacyCmd.Flags().StringVarP(&target, "target", "t", "qemu", "target platform") + buildLegacyCmd.Flags().StringVarP(&architecture, "arch", "a", getDefaultArch(), "architecture (amd64, arm64)") + buildLegacyCmd.Flags().StringVar(&mode, "mode", "", "build mode: image (ostree) or package (required)") + buildLegacyCmd.Flags().StringVar(&exportFormat, "format", "", "export format: qcow2, raw, simg, etc. (required)") + buildLegacyCmd.Flags().StringVarP(&outputDir, "output", "o", "", "download artifact to file") + buildLegacyCmd.Flags().StringVar(&compressionAlgo, "compress", "gzip", "compression algorithm (gzip, lz4, xz)") + buildLegacyCmd.Flags().StringVar(&exportOCI, "push", "", "push disk image as OCI artifact to registry") + buildLegacyCmd.Flags().StringVar(®istryUsername, "registry-username", "", "registry username (or REGISTRY_USERNAME env)") + buildLegacyCmd.Flags().StringVar(®istryPassword, "registry-password", "", "registry password (or REGISTRY_PASSWORD env)") + buildLegacyCmd.Flags().StringVar(&automotiveImageBuilder, "aib-image", "quay.io/centos-sig-automotive/automotive-image-builder:latest", "AIB container image") + buildLegacyCmd.Flags().StringVar(&storageClass, "storage-class", "", "Kubernetes storage class") + buildLegacyCmd.Flags().StringArrayVarP(&customDefs, "define", "D", []string{}, "custom definition KEY=VALUE") + buildLegacyCmd.Flags().IntVar(&timeout, "timeout", 60, "timeout in minutes") + buildLegacyCmd.Flags().BoolVarP(&waitForBuild, "wait", "w", false, "wait for build to complete") + buildLegacyCmd.Flags().BoolVarP(&followLogs, "follow", "f", false, "follow build logs") + _ = buildLegacyCmd.MarkFlagRequired("mode") + _ = buildLegacyCmd.MarkFlagRequired("format") + + // Add all commands + rootCmd.AddCommand(buildCmd, diskCmd, buildLegacyCmd, downloadCmd, listCmd, catalog.NewCatalogCmd()) + // Add deprecated aliases for backwards compatibility + rootCmd.AddCommand(buildBootcAliasCmd, buildTraditionalAliasCmd) if err := rootCmd.Execute(); err != nil { fmt.Println(err) @@ -206,18 +265,31 @@ func main() { } } -func runBuildBootc(cmd *cobra.Command, args []string) { +// runBuild handles the main 'build' command (bootc builds) +func runBuild(cmd *cobra.Command, args []string) { ctx := context.Background() manifest = args[0] if serverURL == "" { - handleError(fmt.Errorf("--server is required")) + handleError(fmt.Errorf("--server is required (or set CAIB_SERVER env)")) } + + // Auto-generate build name if not provided if buildName == "" { - handleError(fmt.Errorf("--name is required")) + base := strings.TrimSuffix(filepath.Base(manifest), ".aib.yml") + base = strings.TrimSuffix(base, ".yml") + buildName = fmt.Sprintf("%s-%s", base, time.Now().Format("20060102-150405")) + fmt.Printf("Auto-generated build name: %s\n", buildName) + } + + // Validate: if --output is specified, --disk must also be specified + if outputDir != "" && !buildDiskImage { + buildDiskImage = true // imply --disk when --output is specified } - if downloadFile != "" && exportOCI == "" { - handleError(fmt.Errorf("--download requires --export-oci (the disk image must be pushed to a registry first)")) + + // Validate: if downloading disk, need push-disk destination + if outputDir != "" && exportOCI == "" { + handleError(fmt.Errorf("--output requires --push-disk (disk image must be pushed to registry first for download)")) } if strings.TrimSpace(authToken) == "" { @@ -316,8 +388,111 @@ func runBuildBootc(cmd *cobra.Command, args []string) { waitForBuildCompletion(ctx, api, resp.Name, "") } - if downloadFile != "" { - if err := pullOCIArtifact(exportOCI, downloadFile, registryUsername, registryPassword); err != nil { + if outputDir != "" { + if err := pullOCIArtifact(exportOCI, outputDir, registryUsername, registryPassword); err != nil { + handleError(fmt.Errorf("failed to download OCI artifact: %w", err)) + } + } +} + +// runDisk handles the 'disk' command (create disk from existing container) +func runDisk(cmd *cobra.Command, args []string) { + ctx := context.Background() + containerRef = args[0] + + if serverURL == "" { + handleError(fmt.Errorf("--server is required (or set CAIB_SERVER env)")) + } + + // Validate: need either --output or --push + if outputDir == "" && exportOCI == "" { + handleError(fmt.Errorf("either --output or --push is required")) + } + + // Auto-generate build name if not provided + if buildName == "" { + // Extract image name from container ref for the build name + parts := strings.Split(containerRef, "/") + imagePart := parts[len(parts)-1] + imagePart = strings.Split(imagePart, ":")[0] // remove tag + buildName = fmt.Sprintf("disk-%s-%s", imagePart, time.Now().Format("20060102-150405")) + fmt.Printf("Auto-generated build name: %s\n", buildName) + } + + if strings.TrimSpace(authToken) == "" { + if tok, err := loadTokenFromKubeconfig(); err == nil && strings.TrimSpace(tok) != "" { + authToken = tok + } + } + + var opts []buildapiclient.Option + if strings.TrimSpace(authToken) != "" { + opts = append(opts, buildapiclient.WithAuthToken(strings.TrimSpace(authToken))) + } + api, err := buildapiclient.New(serverURL, opts...) + if err != nil { + handleError(err) + } + + // Get registry credentials from env if not provided + if registryUsername == "" { + registryUsername = os.Getenv("REGISTRY_USERNAME") + } + if registryPassword == "" { + registryPassword = os.Getenv("REGISTRY_PASSWORD") + } + + // Extract registry URL for authentication + effectiveRegistryURL := "" + if containerRef != "" || exportOCI != "" { + ref := containerRef + if ref == "" { + ref = exportOCI + } + parts := strings.SplitN(ref, "/", 2) + if len(parts) > 0 && strings.Contains(parts[0], ".") { + effectiveRegistryURL = parts[0] + } else { + effectiveRegistryURL = "docker.io" + } + } + + req := buildapitypes.BuildRequest{ + Name: buildName, + ContainerRef: containerRef, + Distro: buildapitypes.Distro(distro), + Target: buildapitypes.Target(target), + Architecture: buildapitypes.Architecture(architecture), + ExportFormat: buildapitypes.ExportFormat(diskFormat), + Mode: buildapitypes.ModeDisk, + AutomotiveImageBuilder: automotiveImageBuilder, + StorageClass: storageClass, + Compression: compressionAlgo, + ExportOCI: exportOCI, + } + + if effectiveRegistryURL != "" && registryUsername != "" && registryPassword != "" { + req.RegistryCredentials = &buildapitypes.RegistryCredentials{ + Enabled: true, + AuthType: "username-password", + RegistryURL: effectiveRegistryURL, + Username: registryUsername, + Password: registryPassword, + } + } + + resp, err := api.CreateBuild(ctx, req) + if err != nil { + handleError(err) + } + fmt.Printf("Build %s accepted: %s - %s\n", resp.Name, resp.Phase, resp.Message) + + if waitForBuild || followLogs || outputDir != "" { + waitForBuildCompletion(ctx, api, resp.Name, "") + } + + if outputDir != "" && exportOCI != "" { + if err := pullOCIArtifact(exportOCI, outputDir, registryUsername, registryPassword); err != nil { handleError(fmt.Errorf("failed to download OCI artifact: %w", err)) } } @@ -466,7 +641,8 @@ func extractOCIArtifactBlob(ociLayoutPath, destPath string) error { return nil } -func runBuildTraditional(cmd *cobra.Command, args []string) { +// runBuildLegacy handles the 'build-legacy' command (traditional ostree/package builds) +func runBuildLegacy(cmd *cobra.Command, args []string) { ctx := context.Background() manifest = args[0] @@ -711,278 +887,6 @@ func waitForBuildCompletion(ctx context.Context, api *buildapiclient.Client, nam } } -func runBuild(cmd *cobra.Command, args []string) { - ctx := context.Background() - - if err := validateBuildRequirements(); err != nil { - handleError(err) - } - - if serverURL == "" { - handleError(fmt.Errorf("--server is required")) - } - - if serverURL != "" { - if strings.TrimSpace(authToken) == "" { - if tok, err := loadTokenFromKubeconfig(); err == nil && strings.TrimSpace(tok) != "" { - authToken = tok - } - } - var opts []buildapiclient.Option - if strings.TrimSpace(authToken) != "" { - opts = append(opts, buildapiclient.WithAuthToken(strings.TrimSpace(authToken))) - } - api, err := buildapiclient.New(serverURL, opts...) - if err != nil { - handleError(err) - } - - manifestBytes, err := os.ReadFile(manifest) - if err != nil { - handleError(fmt.Errorf("error reading manifest: %w", err)) - } - - parsedDistro, err := buildapitypes.ParseDistro(distro) - if err != nil { - handleError(err) - } - parsedTarget, err := buildapitypes.ParseTarget(target) - if err != nil { - handleError(err) - } - parsedArch, err := buildapitypes.ParseArchitecture(architecture) - if err != nil { - handleError(err) - } - parsedExportFormat, err := buildapitypes.ParseExportFormat(exportFormat) - if err != nil { - handleError(err) - } - parsedMode, err := buildapitypes.ParseMode(mode) - if err != nil { - handleError(err) - } - - var aibArgsArray []string - var aibOverrideArray []string - if strings.TrimSpace(aibExtraArgs) != "" { - aibArgsArray = strings.Fields(aibExtraArgs) - } - if strings.TrimSpace(aibOverrideArgs) != "" { - aibOverrideArray = strings.Fields(aibOverrideArgs) - } - - req := buildapitypes.BuildRequest{ - Name: buildName, - Manifest: string(manifestBytes), - ManifestFileName: filepath.Base(manifest), - Distro: parsedDistro, - Target: parsedTarget, - Architecture: parsedArch, - ExportFormat: parsedExportFormat, - Mode: parsedMode, - AutomotiveImageBuilder: automotiveImageBuilder, - StorageClass: storageClass, - CustomDefs: customDefs, - AIBExtraArgs: aibArgsArray, - AIBOverrideArgs: aibOverrideArray, - ServeArtifact: download, - Compression: compressionAlgo, - PushRepository: pushRepository, - } - - // Add registry credentials if push is configured - if pushRepository != "" { - if registryUsername == "" || registryPassword == "" { - handleError(fmt.Errorf("--registry-username and --registry-password are required when --push is specified")) - } - // Extract registry URL from push repository if not explicitly provided - effectiveRegistryURL := registryURL - if effectiveRegistryURL == "" { - // Extract registry from push repository (e.g., "quay.io/org/image:tag" -> "quay.io") - parts := strings.SplitN(pushRepository, "/", 2) - if len(parts) > 0 && strings.Contains(parts[0], ".") { - effectiveRegistryURL = parts[0] - } else { - // Default to docker.io for short names - effectiveRegistryURL = "docker.io" - } - } - req.RegistryCredentials = &buildapitypes.RegistryCredentials{ - Enabled: true, - AuthType: "username-password", - RegistryURL: effectiveRegistryURL, - Username: registryUsername, - Password: registryPassword, - } - } - - resp, err := api.CreateBuild(ctx, req) - if err != nil { - handleError(err) - } - fmt.Printf("Build %s accepted: %s - %s\n", resp.Name, resp.Phase, resp.Message) - // If manifest references local files, upload them via the API - localRefs, err := findLocalFileReferences(string(manifestBytes)) - if err != nil { - handleError(fmt.Errorf("manifest file reference error: %w", err)) - } - if len(localRefs) > 0 { - for _, ref := range localRefs { - if _, err := os.Stat(ref["source_path"]); err != nil { - handleError(fmt.Errorf("referenced file %s does not exist: %w", ref["source_path"], err)) - } - } - - fmt.Println("Waiting for upload server to be ready...") - readyCtx, cancel := context.WithTimeout(ctx, 10*time.Minute) - defer cancel() - for { - if err := readyCtx.Err(); err != nil { - handleError(fmt.Errorf("timed out waiting for upload server to be ready")) - } - reqCtx, c := context.WithTimeout(ctx, 15*time.Second) - st, err := api.GetBuild(reqCtx, resp.Name) - c() - if err == nil { - if st.Phase == "Uploading" { - break - } - if st.Phase == "Failed" { - handleError(fmt.Errorf("build failed while waiting for upload server: %s", st.Message)) - } - } - time.Sleep(3 * time.Second) - } - - uploads := make([]buildapiclient.Upload, 0, len(localRefs)) - for _, ref := range localRefs { - uploads = append(uploads, buildapiclient.Upload{SourcePath: ref["source_path"], DestPath: ref["source_path"]}) - } - - uploadDeadline := time.Now().Add(10 * time.Minute) - for { - if err := api.UploadFiles(ctx, resp.Name, uploads); err != nil { - lower := strings.ToLower(err.Error()) - if time.Now().After(uploadDeadline) { - handleError(fmt.Errorf("upload files failed: %w", err)) - } - if strings.Contains(lower, "503") || strings.Contains(lower, "service unavailable") || strings.Contains(lower, "upload pod not ready") { - fmt.Println("Upload server not ready yet. Retrying...") - time.Sleep(5 * time.Second) - continue - } - handleError(fmt.Errorf("upload files failed: %w", err)) - } - break - } - fmt.Println("Local files uploaded. Build will proceed.") - } - - if waitForBuild || followLogs || download { - fmt.Println("Waiting for build to complete...") - timeoutCtx, cancel := context.WithTimeout(ctx, time.Duration(timeout)*time.Minute) - defer cancel() - ticker := time.NewTicker(5 * time.Second) - defer ticker.Stop() - userFollowRequested := followLogs - var lastPhase, lastMessage string - logFollowWarned := false - - logClient := &http.Client{ - Timeout: 10 * time.Minute, - Transport: &http.Transport{ - ResponseHeaderTimeout: 30 * time.Second, - IdleConnTimeout: 2 * time.Minute, - }, - } - - for { - select { - case <-timeoutCtx.Done(): - handleError(fmt.Errorf("timed out waiting for build")) - case <-ticker.C: - if followLogs { - req, _ := http.NewRequestWithContext(ctx, http.MethodGet, strings.TrimRight(serverURL, "/")+"/v1/builds/"+url.PathEscape(resp.Name)+"/logs?follow=1", nil) - if strings.TrimSpace(authToken) != "" { - req.Header.Set("Authorization", "Bearer "+strings.TrimSpace(authToken)) - } - resp2, err := logClient.Do(req) - if err == nil && resp2.StatusCode == http.StatusOK { - fmt.Println("Streaming logs...") - io.Copy(os.Stdout, resp2.Body) - resp2.Body.Close() - followLogs = userFollowRequested - } else if resp2 != nil { - body, _ := io.ReadAll(resp2.Body) - msg := strings.TrimSpace(string(body)) - if resp2.StatusCode == http.StatusServiceUnavailable || resp2.StatusCode == http.StatusGatewayTimeout { - if !logFollowWarned { - fmt.Println("log stream not ready (HTTP", resp2.StatusCode, "). Retrying…") - logFollowWarned = true - } - // treat as transient; keep trying silently afterwards - } else { - if msg != "" { - fmt.Printf("log stream error (%d): %s\n", resp2.StatusCode, msg) - } else { - fmt.Printf("log stream error: HTTP %d\n", resp2.StatusCode) - } - followLogs = false - } - resp2.Body.Close() - } - } - reqCtx, cancel := context.WithTimeout(ctx, 2*time.Minute) - st, err := api.GetBuild(reqCtx, resp.Name) - cancel() - if err != nil { - fmt.Printf("status check failed: %v\n", err) - continue - } - if !userFollowRequested { - if st.Phase != lastPhase || st.Message != lastMessage { - fmt.Printf("status: %s - %s\n", st.Phase, st.Message) - lastPhase = st.Phase - lastMessage = st.Message - } - } - if st.Phase == "Completed" { - if download { - if err := downloadArtifactViaAPI(ctx, serverURL, resp.Name, outputDir); err != nil { - fmt.Printf("Download via API failed: %v\n", err) - } - return - } - return - } - if st.Phase == "Failed" { - handleError(fmt.Errorf("build failed: %s", st.Message)) - } - } - } - } - return - } - -} - -func validateBuildRequirements() error { - if manifest == "" { - return fmt.Errorf("--manifest is required") - } - - if buildName == "" { - return fmt.Errorf("name is required") - } - - if strings.TrimSpace(architecture) == "" { - return fmt.Errorf("--arch is required") - } - - return nil -} - func handleError(err error) { fmt.Printf("Error: %v\n", err) os.Exit(1) diff --git a/config/crd/bases/automotive.sdv.cloud.redhat.com_imagebuilds.yaml b/config/crd/bases/automotive.sdv.cloud.redhat.com_imagebuilds.yaml index 7e721bb7..dd4ee6b4 100644 --- a/config/crd/bases/automotive.sdv.cloud.redhat.com_imagebuilds.yaml +++ b/config/crd/bases/automotive.sdv.cloud.redhat.com_imagebuilds.yaml @@ -65,6 +65,11 @@ spec: description: ContainerPush is the registry URL to push the bootc container image type: string + containerRef: + description: |- + ContainerRef is the reference to an existing bootc container image + Used with mode=disk to create a disk image from an existing container + type: string distro: description: Distro specifies the distribution to build for (e.g., "cs9") diff --git a/config/manager/kustomization.yaml b/config/manager/kustomization.yaml index fb392b10..9ee89e41 100644 --- a/config/manager/kustomization.yaml +++ b/config/manager/kustomization.yaml @@ -6,5 +6,5 @@ configurations: - kustomizeconfig.yaml images: - name: controller - newName: example.com/automotive-dev-operator - newTag: v0.0.1 + newName: default-route-openshift-image-registry.apps.automotive1.ocp.automotive.sig.centos.org/automotive-dev-operator-system/automotive-dev-operator + newTag: latest diff --git a/internal/buildapi/server.go b/internal/buildapi/server.go index 25f27b9c..604c1dc2 100644 --- a/internal/buildapi/server.go +++ b/internal/buildapi/server.go @@ -824,8 +824,18 @@ func createBuild(c *gin.Context) { needsUpload := strings.Contains(req.Manifest, "source_path") - if req.Name == "" || req.Manifest == "" { - c.JSON(http.StatusBadRequest, gin.H{"error": "name and manifest are required"}) + // Disk mode uses ContainerRef instead of a manifest + if req.Name == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "name is required"}) + return + } + if req.Mode == ModeDisk { + if req.ContainerRef == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "container-ref is required for disk mode"}) + return + } + } else if req.Manifest == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "manifest is required"}) return } @@ -1011,6 +1021,7 @@ func createBuild(c *gin.Context) { BuildDiskImage: req.BuildDiskImage, ExportOCI: req.ExportOCI, BuilderImage: req.BuilderImage, + ContainerRef: req.ContainerRef, }, } if err := k8sClient.Create(ctx, imageBuild); err != nil { diff --git a/internal/buildapi/types.go b/internal/buildapi/types.go index 122b646b..604b7110 100644 --- a/internal/buildapi/types.go +++ b/internal/buildapi/types.go @@ -38,6 +38,8 @@ const ( ModeImage Mode = "image" // ModePackage creates traditional, mutable, package-based disk images ModePackage Mode = "package" + // ModeDisk creates a disk image from an existing bootc container + ModeDisk Mode = "disk" ) func (m Mode) IsValid() bool { @@ -98,8 +100,9 @@ func ParseMode(s string) (Mode, error) { // BuildRequest is the payload to create a build via the REST API type BuildRequest struct { Name string `json:"name"` - Manifest string `json:"manifest"` - ManifestFileName string `json:"manifestFileName"` + Manifest string `json:"manifest,omitempty"` + ManifestFileName string `json:"manifestFileName,omitempty"` + ContainerRef string `json:"containerRef,omitempty"` // For disk mode: existing container to convert Distro Distro `json:"distro"` Target Target `json:"target"` Architecture Architecture `json:"architecture"` diff --git a/internal/common/tasks/scripts/build_builder.sh b/internal/common/tasks/scripts/build_builder.sh index ff7cfa17..5c65c356 100644 --- a/internal/common/tasks/scripts/build_builder.sh +++ b/internal/common/tasks/scripts/build_builder.sh @@ -1,7 +1,7 @@ #!/bin/sh set -e -echo "Prepare builder for distro: $DISTRO" +echo "Prepare builder for distro: $DISTRO, arch: $TARGET_ARCH" # If BUILDER_IMAGE is provided, use it directly if [ -n "$BUILDER_IMAGE" ]; then @@ -21,7 +21,7 @@ else REGISTRY="image-registry.openshift-image-registry.svc:5000" fi -TARGET_IMAGE="${REGISTRY}/${NAMESPACE}/aib-build:$DISTRO" +TARGET_IMAGE="${REGISTRY}/${NAMESPACE}/aib-build:${DISTRO}-${TARGET_ARCH}" mkdir -p $HOME/.config cat > $HOME/.authjson < Date: Thu, 8 Jan 2026 12:19:59 +0200 Subject: [PATCH 2/7] fix disk image push Signed-off-by: Benny Zlotnik --- ...ve-dev-operator.clusterserviceversion.yaml | 2 +- catalog/automotive-dev-operator.yaml | 22 ++++-- cmd/caib/main.go | 40 +++++++---- internal/buildapi/server.go | 3 + internal/common/tasks/scripts/build_image.sh | 69 +++++++++++++++---- 5 files changed, 104 insertions(+), 32 deletions(-) diff --git a/bundle/manifests/automotive-dev-operator.clusterserviceversion.yaml b/bundle/manifests/automotive-dev-operator.clusterserviceversion.yaml index e5819a8a..0f206c12 100644 --- a/bundle/manifests/automotive-dev-operator.clusterserviceversion.yaml +++ b/bundle/manifests/automotive-dev-operator.clusterserviceversion.yaml @@ -53,7 +53,7 @@ metadata: capabilities: Basic Install categories: Developer Tools containerImage: quay.io/rh-sdv-cloud/automotive-dev-operator:latest - createdAt: "2026-01-07T10:28:46Z" + createdAt: "2026-01-08T09:47:15Z" operatorframework.io/suggested-namespace: automotive-dev-operator-system operators.operatorframework.io/builder: operator-sdk-v1.42.0 operators.operatorframework.io/project_layout: go.kubebuilder.io/v4 diff --git a/catalog/automotive-dev-operator.yaml b/catalog/automotive-dev-operator.yaml index b3bc7616..9740f357 100644 --- a/catalog/automotive-dev-operator.yaml +++ b/catalog/automotive-dev-operator.yaml @@ -10,10 +10,15 @@ entries: - name: automotive-dev-operator.v0.0.1 --- --- -image: quay.io/rh-sdv-cloud/automotive-dev-operator-bundle:v0.0.1 +image: image-registry.openshift-image-registry.svc:5000/openshift-marketplace/automotive-dev-operator-bundle:v0.0.1 name: automotive-dev-operator.v0.0.1 package: automotive-dev-operator properties: +- type: olm.gvk + value: + group: automotive.sdv.cloud.redhat.com + kind: CatalogImage + version: v1alpha1 - type: olm.gvk value: group: automotive.sdv.cloud.redhat.com @@ -37,9 +42,16 @@ properties: value: packageName: openshift-pipelines-operator-rh versionRange: ">=1.12.0" +- type: olm.package.required + value: + packageName: openshift-pipelines-operator-rh + versionRange: '>=1.12.0' +- type: olm.bundle.object + value: + data: eyJhcGlWZXJzaW9uIjoiYXBpZXh0ZW5zaW9ucy5rOHMuaW8vdjEiLCJraW5kIjoiQ3VzdG9tUmVzb3VyY2VEZWZpbml0aW9uIiwibWV0YWRhdGEiOnsiYW5ub3RhdGlvbnMiOnsiY29udHJvbGxlci1nZW4ua3ViZWJ1aWxkZXIuaW8vdmVyc2lvbiI6InYwLjE5LjAifSwiY3JlYXRpb25UaW1lc3RhbXAiOm51bGwsIm5hbWUiOiJjYXRhbG9naW1hZ2VzLmF1dG9tb3RpdmUuc2R2LmNsb3VkLnJlZGhhdC5jb20ifSwic3BlYyI6eyJncm91cCI6ImF1dG9tb3RpdmUuc2R2LmNsb3VkLnJlZGhhdC5jb20iLCJuYW1lcyI6eyJraW5kIjoiQ2F0YWxvZ0ltYWdlIiwibGlzdEtpbmQiOiJDYXRhbG9nSW1hZ2VMaXN0IiwicGx1cmFsIjoiY2F0YWxvZ2ltYWdlcyIsInNpbmd1bGFyIjoiY2F0YWxvZ2ltYWdlIn0sInNjb3BlIjoiTmFtZXNwYWNlZCIsInZlcnNpb25zIjpbeyJhZGRpdGlvbmFsUHJpbnRlckNvbHVtbnMiOlt7Impzb25QYXRoIjoiLnNwZWMucmVnaXN0cnlVcmwiLCJuYW1lIjoiUmVnaXN0cnkiLCJ0eXBlIjoic3RyaW5nIn0seyJqc29uUGF0aCI6Ii5zcGVjLm1ldGFkYXRhLmFyY2hpdGVjdHVyZSIsIm5hbWUiOiJBcmNoaXRlY3R1cmUiLCJ0eXBlIjoic3RyaW5nIn0seyJqc29uUGF0aCI6Ii5zcGVjLm1ldGFkYXRhLmRpc3RybyIsIm5hbWUiOiJEaXN0cm8iLCJ0eXBlIjoic3RyaW5nIn0seyJqc29uUGF0aCI6Ii5zdGF0dXMucGhhc2UiLCJuYW1lIjoiUGhhc2UiLCJ0eXBlIjoic3RyaW5nIn0seyJqc29uUGF0aCI6Ii5tZXRhZGF0YS5jcmVhdGlvblRpbWVzdGFtcCIsIm5hbWUiOiJBZ2UiLCJ0eXBlIjoiZGF0ZSJ9XSwibmFtZSI6InYxYWxwaGExIiwic2NoZW1hIjp7Im9wZW5BUElWM1NjaGVtYSI6eyJkZXNjcmlwdGlvbiI6IkNhdGFsb2dJbWFnZSBpcyB0aGUgU2NoZW1hIGZvciB0aGUgY2F0YWxvZ2ltYWdlcyBBUEkiLCJwcm9wZXJ0aWVzIjp7ImFwaVZlcnNpb24iOnsiZGVzY3JpcHRpb24iOiJBUElWZXJzaW9uIGRlZmluZXMgdGhlIHZlcnNpb25lZCBzY2hlbWEgb2YgdGhpcyByZXByZXNlbnRhdGlvbiBvZiBhbiBvYmplY3QuXG5TZXJ2ZXJzIHNob3VsZCBjb252ZXJ0IHJlY29nbml6ZWQgc2NoZW1hcyB0byB0aGUgbGF0ZXN0IGludGVybmFsIHZhbHVlLCBhbmRcbm1heSByZWplY3QgdW5yZWNvZ25pemVkIHZhbHVlcy5cbk1vcmUgaW5mbzogaHR0cHM6Ly9naXQuazhzLmlvL2NvbW11bml0eS9jb250cmlidXRvcnMvZGV2ZWwvc2lnLWFyY2hpdGVjdHVyZS9hcGktY29udmVudGlvbnMubWQjcmVzb3VyY2VzIiwidHlwZSI6InN0cmluZyJ9LCJraW5kIjp7ImRlc2NyaXB0aW9uIjoiS2luZCBpcyBhIHN0cmluZyB2YWx1ZSByZXByZXNlbnRpbmcgdGhlIFJFU1QgcmVzb3VyY2UgdGhpcyBvYmplY3QgcmVwcmVzZW50cy5cblNlcnZlcnMgbWF5IGluZmVyIHRoaXMgZnJvbSB0aGUgZW5kcG9pbnQgdGhlIGNsaWVudCBzdWJtaXRzIHJlcXVlc3RzIHRvLlxuQ2Fubm90IGJlIHVwZGF0ZWQuXG5JbiBDYW1lbENhc2UuXG5Nb3JlIGluZm86IGh0dHBzOi8vZ2l0Lms4cy5pby9jb21tdW5pdHkvY29udHJpYnV0b3JzL2RldmVsL3NpZy1hcmNoaXRlY3R1cmUvYXBpLWNvbnZlbnRpb25zLm1kI3R5cGVzLWtpbmRzIiwidHlwZSI6InN0cmluZyJ9LCJtZXRhZGF0YSI6eyJ0eXBlIjoib2JqZWN0In0sInNwZWMiOnsiZGVzY3JpcHRpb24iOiJDYXRhbG9nSW1hZ2VTcGVjIGRlZmluZXMgdGhlIGRlc2lyZWQgc3RhdGUgb2YgQ2F0YWxvZ0ltYWdlIiwicHJvcGVydGllcyI6eyJhdXRoU2VjcmV0UmVmIjp7ImRlc2NyaXB0aW9uIjoiQXV0aFNlY3JldFJlZiByZWZlcmVuY2VzIHRoZSBzZWNyZXQgY29udGFpbmluZyByZWdpc3RyeSBjcmVkZW50aWFscyIsInByb3BlcnRpZXMiOnsibmFtZSI6eyJkZXNjcmlwdGlvbiI6Ik5hbWUgaXMgdGhlIG5hbWUgb2YgdGhlIHNlY3JldCBjb250YWluaW5nIHJlZ2lzdHJ5IGNyZWRlbnRpYWxzIiwidHlwZSI6InN0cmluZyJ9LCJuYW1lc3BhY2UiOnsiZGVzY3JpcHRpb24iOiJOYW1lc3BhY2UgaXMgdGhlIG5hbWVzcGFjZSBvZiB0aGUgc2VjcmV0IChkZWZhdWx0cyB0byBDYXRhbG9nSW1hZ2UgbmFtZXNwYWNlKSIsInR5cGUiOiJzdHJpbmcifX0sInJlcXVpcmVkIjpbIm5hbWUiXSwidHlwZSI6Im9iamVjdCJ9LCJkaWdlc3QiOnsiZGVzY3JpcHRpb24iOiJEaWdlc3QgaXMgdGhlIGltbXV0YWJsZSBjb250ZW50LWFkZHJlc3NhYmxlIGlkZW50aWZpZXIgKHNoYTI1NjouLi4pIiwicGF0dGVybiI6Il5zaGEyNTY6W2EtZjAtOV17NjR9JCIsInR5cGUiOiJzdHJpbmcifSwibWV0YWRhdGEiOnsiZGVzY3JpcHRpb24iOiJNZXRhZGF0YSBjb250YWlucyBhdXRvbW90aXZlLXNwZWNpZmljIGltYWdlIG1ldGFkYXRhIiwicHJvcGVydGllcyI6eyJhcmNoaXRlY3R1cmUiOnsiZGVzY3JpcHRpb24iOiJBcmNoaXRlY3R1cmUgaXMgdGhlIENQVSBhcmNoaXRlY3R1cmVcblN1cHBvcnRzIGJvdGggQUlCIGNhbm9uaWNhbCB2YWx1ZXMgKHg4Nl82NCwgYWFyY2g2NCkgYW5kIE9DSSBzdGFuZGFyZCBuYW1lcyAoYW1kNjQsIGFybTY0KSIsInR5cGUiOiJzdHJpbmcifSwiYm9vdGMiOnsiZGVzY3JpcHRpb24iOiJCb290YyBpbmRpY2F0ZXMgaWYgdGhpcyBpcyBhIGJvb3RjLWNvbXBhdGlibGUgaW1hZ2UiLCJ0eXBlIjoiYm9vbGVhbiJ9LCJidWlsZE1vZGUiOnsiZGVzY3JpcHRpb24iOiJCdWlsZE1vZGUgaW5kaWNhdGVzIHRoZSBBSUIgYnVpbGQgbW9kZSB1c2VkIChib290YywgaW1hZ2UsIHBhY2thZ2UpIiwiZW51bSI6WyJib290YyIsImltYWdlIiwicGFja2FnZSJdLCJ0eXBlIjoic3RyaW5nIn0sImRpc3RybyI6eyJkZXNjcmlwdGlvbiI6IkRpc3RybyBpcyB0aGUgZGlzdHJpYnV0aW9uIGlkZW50aWZpZXJcbkNvbW1vbiB2YWx1ZXMgaW5jbHVkZTogY3M5LCBhdXRvc2QxMC1zaWcsIGF1dG9zZDksIGNzOCwgZmVkb3JhLTM5LCBmZWRvcmEtNDAsIHJoZWw5LCByaGVsOFxuUnVuICdhaWIgbGlzdC1kaXN0JyB0byBzZWUgYWxsIGF2YWlsYWJsZSBkaXN0cmlidXRpb25zIiwidHlwZSI6InN0cmluZyJ9LCJkaXN0cm9WZXJzaW9uIjp7ImRlc2NyaXB0aW9uIjoiRGlzdHJvVmVyc2lvbiBpcyB0aGUgZGlzdHJpYnV0aW9uIHZlcnNpb24iLCJ0eXBlIjoic3RyaW5nIn0sImV4cG9ydEZvcm1hdCI6eyJkZXNjcmlwdGlvbiI6IkV4cG9ydEZvcm1hdCBpbmRpY2F0ZXMgdGhlIGRpc2sgaW1hZ2UgZm9ybWF0IHByb2R1Y2VkIGJ5IEFJQlxuQ29tbW9uIHZhbHVlcyBpbmNsdWRlOiBxY293MiwgcmF3LCBpbWFnZSwgdm1kaywgaXNvLCB2aGQsIHRhciIsInR5cGUiOiJzdHJpbmcifSwib3MiOnsiZGVmYXVsdCI6ImxpbnV4IiwiZGVzY3JpcHRpb24iOiJPUyBpcyB0aGUgb3BlcmF0aW5nIHN5c3RlbSAoZGVmYXVsdHMgdG8gbGludXgpIiwidHlwZSI6InN0cmluZyJ9LCJ0YXJnZXRzIjp7ImRlc2NyaXB0aW9uIjoiVGFyZ2V0cyBsaXN0cyBjb21wYXRpYmxlIGhhcmR3YXJlIHRhcmdldHMiLCJpdGVtcyI6eyJkZXNjcmlwdGlvbiI6IkhhcmR3YXJlVGFyZ2V0IHJlcHJlc2VudHMgYSBoYXJkd2FyZSBwbGF0Zm9ybSB0aGUgaW1hZ2Ugc3VwcG9ydHMiLCJwcm9wZXJ0aWVzIjp7Im5hbWUiOnsiZGVzY3JpcHRpb24iOiJOYW1lIGlzIHRoZSB0YXJnZXQgaGFyZHdhcmUgaWRlbnRpZmllclxuQ29tbW9uIHZhbHVlcyBpbmNsdWRlOiBxZW11LCByYXNwYmVycnktcGksIGJlYWdsZWJvbmUsIGdlbmVyaWNcblJ1biAnYWliIGxpc3QtdGFyZ2V0cycgdG8gc2VlIGFsbCBhdmFpbGFibGUgaGFyZHdhcmUgdGFyZ2V0cyIsInR5cGUiOiJzdHJpbmcifSwibm90ZXMiOnsiZGVzY3JpcHRpb24iOiJOb3RlcyBjb250YWlucyB0YXJnZXQtc3BlY2lmaWMgaW5mb3JtYXRpb24iLCJ0eXBlIjoic3RyaW5nIn0sInZlcmlmaWVkIjp7ImRlZmF1bHQiOmZhbHNlLCJkZXNjcmlwdGlvbiI6IlZlcmlmaWVkIGluZGljYXRlcyBpZiB0aGUgaW1hZ2UgaGFzIGJlZW4gdGVzdGVkIG9uIHRoaXMgdGFyZ2V0IiwidHlwZSI6ImJvb2xlYW4ifX0sInJlcXVpcmVkIjpbIm5hbWUiXSwidHlwZSI6Im9iamVjdCJ9LCJ0eXBlIjoiYXJyYXkifSwidmFyaWFudCI6eyJkZXNjcmlwdGlvbiI6IlZhcmlhbnQgaXMgdGhlIGFyY2hpdGVjdHVyZSB2YXJpYW50IChlLmcuLCB2NyBmb3IgYXJtdjcpIiwidHlwZSI6InN0cmluZyJ9fSwidHlwZSI6Im9iamVjdCJ9LCJyZWdpc3RyeVVybCI6eyJkZXNjcmlwdGlvbiI6IlJlZ2lzdHJ5VVJMIGlzIHRoZSBmdWxsIFVSTCB0byB0aGUgaW1hZ2UgaW4gdGhlIGNvbnRhaW5lciByZWdpc3RyeSIsInBhdHRlcm4iOiJeW2EtejAtOV0rKFsuXy1dW2EtejAtOV0rKSooL1thLXowLTldKyhbLl8tXVthLXowLTldKykqKSo6W2EtekEtWjAtOV9dW2EtekEtWjAtOS5fLV0qJHxeW2EtejAtOV0rKFsuXy1dW2EtejAtOV0rKSpcXC5bYS16XXsyLH0oOlswLTldezEsNX0pPygvW2EtejAtOV0rKFsuXy1dW2EtejAtOV0rKSopKjpbYS16QS1aMC05X11bYS16QS1aMC05Ll8tXSokfF5bYS16MC05XSsoWy5fLV1bYS16MC05XSspKlxcLlthLXpdezIsfSg6WzAtOV17MSw1fSk/KC9bYS16MC05XSsoWy5fLV1bYS16MC05XSspKikqQHNoYTI1NjpbYS1mMC05XXs2NH0kIiwidHlwZSI6InN0cmluZyJ9LCJ0YWdzIjp7ImRlc2NyaXB0aW9uIjoiVGFncyBhcmUgbXV0YWJsZSBsYWJlbHMgZm9yIGNhdGVnb3JpemF0aW9uIiwiaXRlbXMiOnsidHlwZSI6InN0cmluZyJ9LCJ0eXBlIjoiYXJyYXkifSwidmVyaWZpY2F0aW9uSW50ZXJ2YWwiOnsiZGVmYXVsdCI6IjFoIiwiZGVzY3JpcHRpb24iOiJWZXJpZmljYXRpb25JbnRlcnZhbCBzcGVjaWZpZXMgaG93IG9mdGVuIHRvIHZlcmlmeSByZWdpc3RyeSBhY2Nlc3NpYmlsaXR5IiwicGF0dGVybiI6Il4oWzAtOV0rKFxcLlswLTldKyk/KG5zfHVzfMK1c3xtc3xzfG18aCkpKyQiLCJ0eXBlIjoic3RyaW5nIn19LCJyZXF1aXJlZCI6WyJyZWdpc3RyeVVybCJdLCJ0eXBlIjoib2JqZWN0In0sInN0YXR1cyI6eyJkZXNjcmlwdGlvbiI6IkNhdGFsb2dJbWFnZVN0YXR1cyBkZWZpbmVzIHRoZSBvYnNlcnZlZCBzdGF0ZSBvZiBDYXRhbG9nSW1hZ2UiLCJwcm9wZXJ0aWVzIjp7ImFjY2Vzc0NvdW50Ijp7ImRlc2NyaXB0aW9uIjoiQWNjZXNzQ291bnQgdHJhY2tzIGhvdyBtYW55IHRpbWVzIHRoaXMgaW1hZ2UgaGFzIGJlZW4gYWNjZXNzZWQiLCJmb3JtYXQiOiJpbnQ2NCIsInR5cGUiOiJpbnRlZ2VyIn0sImFydGlmYWN0UmVmcyI6eyJkZXNjcmlwdGlvbiI6IkFydGlmYWN0UmVmcyBjb250YWlucyByZWZlcmVuY2VzIHRvIGRvd25sb2FkYWJsZSBhcnRpZmFjdHMiLCJpdGVtcyI6eyJkZXNjcmlwdGlvbiI6IkFydGlmYWN0UmVmZXJlbmNlIHJlcHJlc2VudHMgYSBkb3dubG9hZGFibGUgYXJ0aWZhY3QgYXNzb2NpYXRlZCB3aXRoIHRoZSBpbWFnZSIsInByb3BlcnRpZXMiOnsiZGlnZXN0Ijp7ImRlc2NyaXB0aW9uIjoiRGlnZXN0IGlzIHRoZSBjb250ZW50IGRpZ2VzdCBmb3IgdmVyaWZpY2F0aW9uIChzaGEyNTY6Li4uKSIsInR5cGUiOiJzdHJpbmcifSwiZm9ybWF0Ijp7ImRlc2NyaXB0aW9uIjoiRm9ybWF0IGNvbnRhaW5zIGZvcm1hdC1zcGVjaWZpYyBpbmZvcm1hdGlvbiIsInR5cGUiOiJzdHJpbmcifSwic2l6ZUJ5dGVzIjp7ImRlc2NyaXB0aW9uIjoiU2l6ZUJ5dGVzIGlzIHRoZSBzaXplIG9mIHRoZSBhcnRpZmFjdCBpbiBieXRlcyIsImZvcm1hdCI6ImludDY0IiwidHlwZSI6ImludGVnZXIifSwidHlwZSI6eyJkZXNjcmlwdGlvbiI6IlR5cGUgaXMgdGhlIGFydGlmYWN0IHR5cGUgKGUuZy4sIHFjb3cyLCByYXcsIHZtZGssIGNvbnRhaW5lciwgaXNvKSIsInR5cGUiOiJzdHJpbmcifSwidXJsIjp7ImRlc2NyaXB0aW9uIjoiVVJMIGlzIHRoZSBkb3dubG9hZCBVUkwgZm9yIHRoZSBhcnRpZmFjdCIsInR5cGUiOiJzdHJpbmcifX0sInJlcXVpcmVkIjpbInR5cGUiLCJ1cmwiXSwidHlwZSI6Im9iamVjdCJ9LCJ0eXBlIjoiYXJyYXkifSwiY29uZGl0aW9ucyI6eyJkZXNjcmlwdGlvbiI6IkNvbmRpdGlvbnMgcmVwcmVzZW50IHRoZSBsYXRlc3QgYXZhaWxhYmxlIG9ic2VydmF0aW9ucyIsIml0ZW1zIjp7ImRlc2NyaXB0aW9uIjoiQ29uZGl0aW9uIGNvbnRhaW5zIGRldGFpbHMgZm9yIG9uZSBhc3BlY3Qgb2YgdGhlIGN1cnJlbnQgc3RhdGUgb2YgdGhpcyBBUEkgUmVzb3VyY2UuIiwicHJvcGVydGllcyI6eyJsYXN0VHJhbnNpdGlvblRpbWUiOnsiZGVzY3JpcHRpb24iOiJsYXN0VHJhbnNpdGlvblRpbWUgaXMgdGhlIGxhc3QgdGltZSB0aGUgY29uZGl0aW9uIHRyYW5zaXRpb25lZCBmcm9tIG9uZSBzdGF0dXMgdG8gYW5vdGhlci5cblRoaXMgc2hvdWxkIGJlIHdoZW4gdGhlIHVuZGVybHlpbmcgY29uZGl0aW9uIGNoYW5nZWQuICBJZiB0aGF0IGlzIG5vdCBrbm93biwgdGhlbiB1c2luZyB0aGUgdGltZSB3aGVuIHRoZSBBUEkgZmllbGQgY2hhbmdlZCBpcyBhY2NlcHRhYmxlLiIsImZvcm1hdCI6ImRhdGUtdGltZSIsInR5cGUiOiJzdHJpbmcifSwibWVzc2FnZSI6eyJkZXNjcmlwdGlvbiI6Im1lc3NhZ2UgaXMgYSBodW1hbiByZWFkYWJsZSBtZXNzYWdlIGluZGljYXRpbmcgZGV0YWlscyBhYm91dCB0aGUgdHJhbnNpdGlvbi5cblRoaXMgbWF5IGJlIGFuIGVtcHR5IHN0cmluZy4iLCJtYXhMZW5ndGgiOjMyNzY4LCJ0eXBlIjoic3RyaW5nIn0sIm9ic2VydmVkR2VuZXJhdGlvbiI6eyJkZXNjcmlwdGlvbiI6Im9ic2VydmVkR2VuZXJhdGlvbiByZXByZXNlbnRzIHRoZSAubWV0YWRhdGEuZ2VuZXJhdGlvbiB0aGF0IHRoZSBjb25kaXRpb24gd2FzIHNldCBiYXNlZCB1cG9uLlxuRm9yIGluc3RhbmNlLCBpZiAubWV0YWRhdGEuZ2VuZXJhdGlvbiBpcyBjdXJyZW50bHkgMTIsIGJ1dCB0aGUgLnN0YXR1cy5jb25kaXRpb25zW3hdLm9ic2VydmVkR2VuZXJhdGlvbiBpcyA5LCB0aGUgY29uZGl0aW9uIGlzIG91dCBvZiBkYXRlXG53aXRoIHJlc3BlY3QgdG8gdGhlIGN1cnJlbnQgc3RhdGUgb2YgdGhlIGluc3RhbmNlLiIsImZvcm1hdCI6ImludDY0IiwibWluaW11bSI6MCwidHlwZSI6ImludGVnZXIifSwicmVhc29uIjp7ImRlc2NyaXB0aW9uIjoicmVhc29uIGNvbnRhaW5zIGEgcHJvZ3JhbW1hdGljIGlkZW50aWZpZXIgaW5kaWNhdGluZyB0aGUgcmVhc29uIGZvciB0aGUgY29uZGl0aW9uJ3MgbGFzdCB0cmFuc2l0aW9uLlxuUHJvZHVjZXJzIG9mIHNwZWNpZmljIGNvbmRpdGlvbiB0eXBlcyBtYXkgZGVmaW5lIGV4cGVjdGVkIHZhbHVlcyBhbmQgbWVhbmluZ3MgZm9yIHRoaXMgZmllbGQsXG5hbmQgd2hldGhlciB0aGUgdmFsdWVzIGFyZSBjb25zaWRlcmVkIGEgZ3VhcmFudGVlZCBBUEkuXG5UaGUgdmFsdWUgc2hvdWxkIGJlIGEgQ2FtZWxDYXNlIHN0cmluZy5cblRoaXMgZmllbGQgbWF5IG5vdCBiZSBlbXB0eS4iLCJtYXhMZW5ndGgiOjEwMjQsIm1pbkxlbmd0aCI6MSwicGF0dGVybiI6Il5bQS1aYS16XShbQS1aYS16MC05Xyw6XSpbQS1aYS16MC05X10pPyQiLCJ0eXBlIjoic3RyaW5nIn0sInN0YXR1cyI6eyJkZXNjcmlwdGlvbiI6InN0YXR1cyBvZiB0aGUgY29uZGl0aW9uLCBvbmUgb2YgVHJ1ZSwgRmFsc2UsIFVua25vd24uIiwiZW51bSI6WyJUcnVlIiwiRmFsc2UiLCJVbmtub3duIl0sInR5cGUiOiJzdHJpbmcifSwidHlwZSI6eyJkZXNjcmlwdGlvbiI6InR5cGUgb2YgY29uZGl0aW9uIGluIENhbWVsQ2FzZSBvciBpbiBmb28uZXhhbXBsZS5jb20vQ2FtZWxDYXNlLiIsIm1heExlbmd0aCI6MzE2LCJwYXR0ZXJuIjoiXihbYS16MC05XShbLWEtejAtOV0qW2EtejAtOV0pPyhcXC5bYS16MC05XShbLWEtejAtOV0qW2EtejAtOV0pPykqLyk/KChbQS1aYS16MC05XVstQS1aYS16MC05Xy5dKik/W0EtWmEtejAtOV0pJCIsInR5cGUiOiJzdHJpbmcifX0sInJlcXVpcmVkIjpbImxhc3RUcmFuc2l0aW9uVGltZSIsIm1lc3NhZ2UiLCJyZWFzb24iLCJzdGF0dXMiLCJ0eXBlIl0sInR5cGUiOiJvYmplY3QifSwidHlwZSI6ImFycmF5In0sImxhc3RWZXJpZmljYXRpb25UaW1lIjp7ImRlc2NyaXB0aW9uIjoiTGFzdFZlcmlmaWNhdGlvblRpbWUgaXMgd2hlbiB0aGUgcmVnaXN0cnkgd2FzIGxhc3QgdmVyaWZpZWQiLCJmb3JtYXQiOiJkYXRlLXRpbWUiLCJ0eXBlIjoic3RyaW5nIn0sIm9ic2VydmVkR2VuZXJhdGlvbiI6eyJkZXNjcmlwdGlvbiI6Ik9ic2VydmVkR2VuZXJhdGlvbiBpcyB0aGUgbW9zdCByZWNlbnQgZ2VuZXJhdGlvbiBvYnNlcnZlZCBieSB0aGUgY29udHJvbGxlciIsImZvcm1hdCI6ImludDY0IiwidHlwZSI6ImludGVnZXIifSwicGhhc2UiOnsiZGVzY3JpcHRpb24iOiJQaGFzZSByZXByZXNlbnRzIHRoZSBjdXJyZW50IGxpZmVjeWNsZSBwaGFzZSIsImVudW0iOlsiUGVuZGluZyIsIlZlcmlmeWluZyIsIkF2YWlsYWJsZSIsIlVuYXZhaWxhYmxlIiwiRmFpbGVkIl0sInR5cGUiOiJzdHJpbmcifSwicHVibGlzaGVkQXQiOnsiZGVzY3JpcHRpb24iOiJQdWJsaXNoZWRBdCBpcyB3aGVuIHRoaXMgaW1hZ2Ugd2FzIHB1Ymxpc2hlZCB0byB0aGUgY2F0YWxvZyIsImZvcm1hdCI6ImRhdGUtdGltZSIsInR5cGUiOiJzdHJpbmcifSwicmVnaXN0cnlNZXRhZGF0YSI6eyJkZXNjcmlwdGlvbiI6IlJlZ2lzdHJ5TWV0YWRhdGEgY29udGFpbnMgbWV0YWRhdGEgZXh0cmFjdGVkIGZyb20gdGhlIHJlZ2lzdHJ5IiwicHJvcGVydGllcyI6eyJjcmVhdGVkQXQiOnsiZGVzY3JpcHRpb24iOiJDcmVhdGVkQXQgaXMgd2hlbiB0aGUgaW1hZ2Ugd2FzIGNyZWF0ZWQgaW4gdGhlIHJlZ2lzdHJ5IiwiZm9ybWF0IjoiZGF0ZS10aW1lIiwidHlwZSI6InN0cmluZyJ9LCJpc011bHRpQXJjaCI6eyJkZXNjcmlwdGlvbiI6IklzTXVsdGlBcmNoIGluZGljYXRlcyBpZiB0aGlzIGlzIGEgbXVsdGktYXJjaGl0ZWN0dXJlIG1hbmlmZXN0IGxpc3QiLCJ0eXBlIjoiYm9vbGVhbiJ9LCJsYXllckNvdW50Ijp7ImRlc2NyaXB0aW9uIjoiTGF5ZXJDb3VudCBpcyB0aGUgbnVtYmVyIG9mIGltYWdlIGxheWVycyIsInR5cGUiOiJpbnRlZ2VyIn0sIm1lZGlhVHlwZSI6eyJkZXNjcmlwdGlvbiI6Ik1lZGlhVHlwZSBpcyB0aGUgT0NJIG1hbmlmZXN0IG1lZGlhIHR5cGUiLCJ0eXBlIjoic3RyaW5nIn0sInBsYXRmb3JtIjp7ImRlc2NyaXB0aW9uIjoiUGxhdGZvcm0gY29udGFpbnMgcGxhdGZvcm0gaW5mb3JtYXRpb24gZnJvbSB0aGUgbWFuaWZlc3QiLCJwcm9wZXJ0aWVzIjp7ImFyY2hpdGVjdHVyZSI6eyJkZXNjcmlwdGlvbiI6IkFyY2hpdGVjdHVyZSBpcyB0aGUgcGxhdGZvcm0gYXJjaGl0ZWN0dXJlIiwidHlwZSI6InN0cmluZyJ9LCJvcyI6eyJkZXNjcmlwdGlvbiI6Ik9TIGlzIHRoZSBwbGF0Zm9ybSBvcGVyYXRpbmcgc3lzdGVtIiwidHlwZSI6InN0cmluZyJ9LCJ2YXJpYW50Ijp7ImRlc2NyaXB0aW9uIjoiVmFyaWFudCBpcyB0aGUgcGxhdGZvcm0gdmFyaWFudCIsInR5cGUiOiJzdHJpbmcifX0sInR5cGUiOiJvYmplY3QifSwicGxhdGZvcm1WYXJpYW50cyI6eyJkZXNjcmlwdGlvbiI6IlBsYXRmb3JtVmFyaWFudHMgY29udGFpbnMgaW5mb3JtYXRpb24gYWJvdXQgYXZhaWxhYmxlIHBsYXRmb3JtIHZhcmlhbnRzXG5Pbmx5IHBvcHVsYXRlZCBmb3IgbXVsdGktYXJjaCBpbWFnZXMiLCJpdGVtcyI6eyJkZXNjcmlwdGlvbiI6IlBsYXRmb3JtVmFyaWFudCByZXByZXNlbnRzIGEgcGxhdGZvcm0tc3BlY2lmaWMgdmFyaWFudCBpbiBhIG11bHRpLWFyY2ggaW1hZ2UiLCJwcm9wZXJ0aWVzIjp7ImFyY2hpdGVjdHVyZSI6eyJkZXNjcmlwdGlvbiI6IkFyY2hpdGVjdHVyZSBpcyB0aGUgQ1BVIGFyY2hpdGVjdHVyZSAoYW1kNjQsIGFybTY0LCBldGMuKSIsInR5cGUiOiJzdHJpbmcifSwiZGlnZXN0Ijp7ImRlc2NyaXB0aW9uIjoiRGlnZXN0IGlzIHRoZSBkaWdlc3QgZm9yIHRoaXMgc3BlY2lmaWMgcGxhdGZvcm0gdmFyaWFudCIsInR5cGUiOiJzdHJpbmcifSwib3MiOnsiZGVzY3JpcHRpb24iOiJPUyBpcyB0aGUgb3BlcmF0aW5nIHN5c3RlbSIsInR5cGUiOiJzdHJpbmcifSwic2l6ZUJ5dGVzIjp7ImRlc2NyaXB0aW9uIjoiU2l6ZUJ5dGVzIGlzIHRoZSBzaXplIG9mIHRoaXMgdmFyaWFudCBpbiBieXRlcyIsImZvcm1hdCI6ImludDY0IiwidHlwZSI6ImludGVnZXIifSwidmFyaWFudCI6eyJkZXNjcmlwdGlvbiI6IlZhcmlhbnQgaXMgdGhlIGFyY2hpdGVjdHVyZSB2YXJpYW50ICh2NywgdjgsIGV0Yy4pIiwidHlwZSI6InN0cmluZyJ9fSwidHlwZSI6Im9iamVjdCJ9LCJ0eXBlIjoiYXJyYXkifSwicmVzb2x2ZWREaWdlc3QiOnsiZGVzY3JpcHRpb24iOiJSZXNvbHZlZERpZ2VzdCBpcyB0aGUgZGlnZXN0IHJlc29sdmVkIGZyb20gdGhlIHJlZ2lzdHJ5IiwidHlwZSI6InN0cmluZyJ9LCJzaXplQnl0ZXMiOnsiZGVzY3JpcHRpb24iOiJTaXplQnl0ZXMgaXMgdGhlIHRvdGFsIGltYWdlIHNpemUgaW4gYnl0ZXMiLCJmb3JtYXQiOiJpbnQ2NCIsInR5cGUiOiJpbnRlZ2VyIn19LCJ0eXBlIjoib2JqZWN0In0sInNvdXJjZUltYWdlQnVpbGQiOnsiZGVzY3JpcHRpb24iOiJTb3VyY2VJbWFnZUJ1aWxkIHJlZmVyZW5jZXMgdGhlIEltYWdlQnVpbGQgdGhhdCBjcmVhdGVkIHRoaXMgY2F0YWxvZyBlbnRyeSIsInR5cGUiOiJzdHJpbmcifX0sInR5cGUiOiJvYmplY3QifX0sInR5cGUiOiJvYmplY3QifX0sInNlcnZlZCI6dHJ1ZSwic3RvcmFnZSI6dHJ1ZSwic3VicmVzb3VyY2VzIjp7InN0YXR1cyI6e319fV19LCJzdGF0dXMiOnsiYWNjZXB0ZWROYW1lcyI6eyJraW5kIjoiIiwicGx1cmFsIjoiIn0sImNvbmRpdGlvbnMiOm51bGwsInN0b3JlZFZlcnNpb25zIjpudWxsfX0= - type: olm.bundle.object value: - data: eyJhcGlWZXJzaW9uIjoiYXBpZXh0ZW5zaW9ucy5rOHMuaW8vdjEiLCJraW5kIjoiQ3VzdG9tUmVzb3VyY2VEZWZpbml0aW9uIiwibWV0YWRhdGEiOnsiYW5ub3RhdGlvbnMiOnsiY29udHJvbGxlci1nZW4ua3ViZWJ1aWxkZXIuaW8vdmVyc2lvbiI6InYwLjE5LjAifSwiY3JlYXRpb25UaW1lc3RhbXAiOm51bGwsIm5hbWUiOiJpbWFnZWJ1aWxkcy5hdXRvbW90aXZlLnNkdi5jbG91ZC5yZWRoYXQuY29tIn0sInNwZWMiOnsiZ3JvdXAiOiJhdXRvbW90aXZlLnNkdi5jbG91ZC5yZWRoYXQuY29tIiwibmFtZXMiOnsia2luZCI6IkltYWdlQnVpbGQiLCJsaXN0S2luZCI6IkltYWdlQnVpbGRMaXN0IiwicGx1cmFsIjoiaW1hZ2VidWlsZHMiLCJzaW5ndWxhciI6ImltYWdlYnVpbGQifSwic2NvcGUiOiJOYW1lc3BhY2VkIiwidmVyc2lvbnMiOlt7Im5hbWUiOiJ2MWFscGhhMSIsInNjaGVtYSI6eyJvcGVuQVBJVjNTY2hlbWEiOnsiZGVzY3JpcHRpb24iOiJJbWFnZUJ1aWxkIGlzIHRoZSBTY2hlbWEgZm9yIHRoZSBpbWFnZWJ1aWxkcyBBUEkiLCJwcm9wZXJ0aWVzIjp7ImFwaVZlcnNpb24iOnsiZGVzY3JpcHRpb24iOiJBUElWZXJzaW9uIGRlZmluZXMgdGhlIHZlcnNpb25lZCBzY2hlbWEgb2YgdGhpcyByZXByZXNlbnRhdGlvbiBvZiBhbiBvYmplY3QuXG5TZXJ2ZXJzIHNob3VsZCBjb252ZXJ0IHJlY29nbml6ZWQgc2NoZW1hcyB0byB0aGUgbGF0ZXN0IGludGVybmFsIHZhbHVlLCBhbmRcbm1heSByZWplY3QgdW5yZWNvZ25pemVkIHZhbHVlcy5cbk1vcmUgaW5mbzogaHR0cHM6Ly9naXQuazhzLmlvL2NvbW11bml0eS9jb250cmlidXRvcnMvZGV2ZWwvc2lnLWFyY2hpdGVjdHVyZS9hcGktY29udmVudGlvbnMubWQjcmVzb3VyY2VzIiwidHlwZSI6InN0cmluZyJ9LCJraW5kIjp7ImRlc2NyaXB0aW9uIjoiS2luZCBpcyBhIHN0cmluZyB2YWx1ZSByZXByZXNlbnRpbmcgdGhlIFJFU1QgcmVzb3VyY2UgdGhpcyBvYmplY3QgcmVwcmVzZW50cy5cblNlcnZlcnMgbWF5IGluZmVyIHRoaXMgZnJvbSB0aGUgZW5kcG9pbnQgdGhlIGNsaWVudCBzdWJtaXRzIHJlcXVlc3RzIHRvLlxuQ2Fubm90IGJlIHVwZGF0ZWQuXG5JbiBDYW1lbENhc2UuXG5Nb3JlIGluZm86IGh0dHBzOi8vZ2l0Lms4cy5pby9jb21tdW5pdHkvY29udHJpYnV0b3JzL2RldmVsL3NpZy1hcmNoaXRlY3R1cmUvYXBpLWNvbnZlbnRpb25zLm1kI3R5cGVzLWtpbmRzIiwidHlwZSI6InN0cmluZyJ9LCJtZXRhZGF0YSI6eyJ0eXBlIjoib2JqZWN0In0sInNwZWMiOnsiZGVzY3JpcHRpb24iOiJJbWFnZUJ1aWxkU3BlYyBkZWZpbmVzIHRoZSBkZXNpcmVkIHN0YXRlIG9mIEltYWdlQnVpbGQiLCJwcm9wZXJ0aWVzIjp7ImFyY2hpdGVjdHVyZSI6eyJkZXNjcmlwdGlvbiI6IkFyY2hpdGVjdHVyZSBzcGVjaWZpZXMgdGhlIHRhcmdldCBhcmNoaXRlY3R1cmUiLCJ0eXBlIjoic3RyaW5nIn0sImF1dG9tb3RpdmVJbWFnZUJ1aWxkZXIiOnsiZGVzY3JpcHRpb24iOiJBdXRvbW90aXZlSW1hZ2VCdWlsZGVyIHNwZWNpZmllcyB0aGUgaW1hZ2UgdG8gdXNlIGZvciBidWlsZGluZyIsInR5cGUiOiJzdHJpbmcifSwiYnVpbGREaXNrSW1hZ2UiOnsiZGVzY3JpcHRpb24iOiJCdWlsZERpc2tJbWFnZSBpbmRpY2F0ZXMgd2hldGhlciB0byBidWlsZCBhIGRpc2sgaW1hZ2UgZnJvbSB0aGUgYm9vdGMgY29udGFpbmVyIiwidHlwZSI6ImJvb2xlYW4ifSwiYnVpbGRlckltYWdlIjp7ImRlc2NyaXB0aW9uIjoiQnVpbGRlckltYWdlIGlzIGEgY3VzdG9tIGJ1aWxkZXIgaW1hZ2UgdG8gdXNlIiwidHlwZSI6InN0cmluZyJ9LCJjb21wcmVzc2lvbiI6eyJkZWZhdWx0IjoiZ3ppcCIsImRlc2NyaXB0aW9uIjoiQ29tcHJlc3Npb24gc3BlY2lmaWVzIHRoZSBjb21wcmVzc2lvbiBhbGdvcml0aG0gZm9yIGFydGlmYWN0cyIsImVudW0iOlsibHo0IiwiZ3ppcCIsInh6Il0sInR5cGUiOiJzdHJpbmcifSwiY29udGFpbmVyUHVzaCI6eyJkZXNjcmlwdGlvbiI6IkNvbnRhaW5lclB1c2ggaXMgdGhlIHJlZ2lzdHJ5IFVSTCB0byBwdXNoIHRoZSBib290YyBjb250YWluZXIgaW1hZ2UiLCJ0eXBlIjoic3RyaW5nIn0sImRpc3RybyI6eyJkZXNjcmlwdGlvbiI6IkRpc3RybyBzcGVjaWZpZXMgdGhlIGRpc3RyaWJ1dGlvbiB0byBidWlsZCBmb3IgKGUuZy4sIFwiY3M5XCIpIiwidHlwZSI6InN0cmluZyJ9LCJlbnZTZWNyZXRSZWYiOnsiZGVzY3JpcHRpb24iOiJFbnZTZWNyZXRSZWYgaXMgdGhlIG5hbWUgb2YgdGhlIHNlY3JldCBjb250YWluaW5nIGVudmlyb25tZW50IHZhcmlhYmxlcyBmb3IgdGhlIGJ1aWxkXG5UaGVzZSBlbnZpcm9ubWVudCB2YXJpYWJsZXMgd2lsbCBiZSBhdmFpbGFibGUgZHVyaW5nIHRoZSBidWlsZCBwcm9jZXNzIGFuZCBjYW4gYmUgdXNlZFxuZm9yIHByaXZhdGUgcmVnaXN0cnkgYXV0aGVudGljYXRpb24gKGUuZy4sIFJFR0lTVFJZX1VTRVJOQU1FLCBSRUdJU1RSWV9QQVNTV09SRCwgUkVHSVNUUllfQVVUSF9GSUxFKSIsInR5cGUiOiJzdHJpbmcifSwiZXhwb3J0Rm9ybWF0Ijp7ImRlc2NyaXB0aW9uIjoiRXhwb3J0Rm9ybWF0IHNwZWNpZmllcyB0aGUgb3V0cHV0IGZvcm1hdCAoaW1hZ2UsIHFjb3cyKSIsInR5cGUiOiJzdHJpbmcifSwiZXhwb3J0T2NpIjp7ImRlc2NyaXB0aW9uIjoiRXhwb3J0T0NJIGlzIHRoZSByZWdpc3RyeSBVUkwgdG8gcHVzaCB0aGUgZGlzayBpbWFnZSBhcyBhbiBPQ0kgYXJ0aWZhY3QiLCJ0eXBlIjoic3RyaW5nIn0sImV4cG9zZVJvdXRlIjp7ImRlc2NyaXB0aW9uIjoiRXhwb3NlUm91dGUgaW5kaWNhdGVzIHdoZXRoZXIgdG8gZXhwb3NlIHRoZSBhIHJvdXRlIGZvciB0aGUgYXJ0aWZhY3RzIiwidHlwZSI6ImJvb2xlYW4ifSwiaW5wdXRGaWxlc1NlcnZlciI6eyJkZXNjcmlwdGlvbiI6IklucHV0RmlsZXNTZXJ2ZXIgaW5kaWNhdGVzIGlmIHRoZXJlJ3MgYSBzZXJ2ZXIgZm9yIGZpbGVzIHJlZmVyZW5jZWQgbG9jYWxseSBpbiB0aGUgbWFuaWZlc3QiLCJ0eXBlIjoiYm9vbGVhbiJ9LCJtYW5pZmVzdENvbmZpZ01hcCI6eyJkZXNjcmlwdGlvbiI6Ik1hbmlmZXN0Q29uZmlnTWFwIHNwZWNpZmllcyB0aGUgbmFtZSBvZiB0aGUgQ29uZmlnTWFwIGNvbnRhaW5pbmcgdGhlIG1hbmlmZXN0IGNvbmZpZ3VyYXRpb24iLCJ0eXBlIjoic3RyaW5nIn0sIm1vZGUiOnsiZGVzY3JpcHRpb24iOiJNb2RlIHNwZWNpZmllcyB0aGUgYnVpbGQgbW9kZSAocGFja2FnZSwgaW1hZ2UpIiwidHlwZSI6InN0cmluZyJ9LCJwdWJsaXNoZXJzIjp7ImRlc2NyaXB0aW9uIjoiUHVibGlzaGVycyBkZWZpbmVzIHdoZXJlIHRvIHB1Ymxpc2ggdGhlIGJ1aWx0IGFydGlmYWN0cyIsInByb3BlcnRpZXMiOnsicmVnaXN0cnkiOnsiZGVzY3JpcHRpb24iOiJSZWdpc3RyeSBjb25maWd1cmF0aW9uIGZvciBwdWJsaXNoaW5nIHRvIGFuIE9DSSByZWdpc3RyeSIsInByb3BlcnRpZXMiOnsicmVwb3NpdG9yeVVybCI6eyJkZXNjcmlwdGlvbiI6IlJlcG9zaXRvcnlVUkwgaXMgdGhlIFVSTCBvZiB0aGUgT0NJIHJlZ2lzdHJ5IHJlcG9zaXRvcnkiLCJ0eXBlIjoic3RyaW5nIn0sInNlY3JldCI6eyJkZXNjcmlwdGlvbiI6IlNlY3JldCBpcyB0aGUgbmFtZSBvZiB0aGUgc2VjcmV0IGNvbnRhaW5pbmcgcmVnaXN0cnkgY3JlZGVudGlhbHMiLCJ0eXBlIjoic3RyaW5nIn19LCJyZXF1aXJlZCI6WyJyZXBvc2l0b3J5VXJsIiwic2VjcmV0Il0sInR5cGUiOiJvYmplY3QifX0sInR5cGUiOiJvYmplY3QifSwicnVudGltZUNsYXNzTmFtZSI6eyJkZXNjcmlwdGlvbiI6IlJ1bnRpbWVDbGFzc05hbWUgc3BlY2lmaWVzIHRoZSBydW50aW1lIGNsYXNzIHRvIHVzZSBmb3IgdGhlIGJ1aWxkIHBvZCIsInR5cGUiOiJzdHJpbmcifSwic2VydmVBcnRpZmFjdCI6eyJkZXNjcmlwdGlvbiI6IlNlcnZlQXJ0aWZhY3QgZGV0ZXJtaW5lcyB3aGV0aGVyIHRvIG1ha2UgdGhlIGJ1aWx0IGFydGlmYWN0IGF2YWlsYWJsZSBmb3IgZG93bmxvYWQiLCJ0eXBlIjoiYm9vbGVhbiJ9LCJzZXJ2ZUV4cGlyeUhvdXJzIjp7ImRlc2NyaXB0aW9uIjoiU2VydmVFeHBpcnlIb3VycyBzcGVjaWZpZXMgaG93IGxvbmcgdG8gc2VydmUgdGhlIGFydGlmYWN0IGJlZm9yZSBjbGVhbnVwIChkZWZhdWx0OiAyNCkiLCJmb3JtYXQiOiJpbnQzMiIsInR5cGUiOiJpbnRlZ2VyIn0sInN0b3JhZ2VDbGFzcyI6eyJkZXNjcmlwdGlvbiI6IlN0b3JhZ2VDbGFzcyBpcyB0aGUgbmFtZSBvZiB0aGUgc3RvcmFnZSBjbGFzcyB0byB1c2UgZm9yIHRoZSBidWlsZCBQVkMiLCJ0eXBlIjoic3RyaW5nIn0sInRhcmdldCI6eyJkZXNjcmlwdGlvbiI6IlRhcmdldCBzcGVjaWZpZXMgdGhlIGJ1aWxkIHRhcmdldCAoZS5nLiwgXCJxZW11XCIpIiwidHlwZSI6InN0cmluZyJ9fSwidHlwZSI6Im9iamVjdCJ9LCJzdGF0dXMiOnsiZGVzY3JpcHRpb24iOiJJbWFnZUJ1aWxkU3RhdHVzIGRlZmluZXMgdGhlIG9ic2VydmVkIHN0YXRlIG9mIEltYWdlQnVpbGQiLCJwcm9wZXJ0aWVzIjp7ImFydGlmYWN0RmlsZU5hbWUiOnsiZGVzY3JpcHRpb24iOiJBcnRpZmFjdEZpbGVOYW1lIGlzIHRoZSBuYW1lIG9mIHRoZSBhcnRpZmFjdCBmaWxlIGluc2lkZSB0aGUgUFZDIiwidHlwZSI6InN0cmluZyJ9LCJhcnRpZmFjdFBhdGgiOnsiZGVzY3JpcHRpb24iOiJBcnRpZmFjdFBhdGggaXMgdGhlIHBhdGggaW5zaWRlIHRoZSBQVkMgd2hlcmUgdGhlIGFydGlmYWN0IGlzIHN0b3JlZCIsInR5cGUiOiJzdHJpbmcifSwiYXJ0aWZhY3RVUkwiOnsiZGVzY3JpcHRpb24iOiJBcnRpZmFjdFVSTCBpcyB0aGUgcm91dGUgVVJMIGNyZWF0ZWQgdG8gZXhwb3NlIHRoZSBhcnRpZmFjdHMiLCJ0eXBlIjoic3RyaW5nIn0sImNvbXBsZXRpb25UaW1lIjp7ImRlc2NyaXB0aW9uIjoiQ29tcGxldGlvblRpbWUgaXMgd2hlbiB0aGUgYnVpbGQgZmluaXNoZWQiLCJmb3JtYXQiOiJkYXRlLXRpbWUiLCJ0eXBlIjoic3RyaW5nIn0sImNvbmRpdGlvbnMiOnsiZGVzY3JpcHRpb24iOiJDb25kaXRpb25zIHJlcHJlc2VudCB0aGUgbGF0ZXN0IGF2YWlsYWJsZSBvYnNlcnZhdGlvbnMgb2YgdGhlIEltYWdlQnVpbGQncyBzdGF0ZSIsIml0ZW1zIjp7ImRlc2NyaXB0aW9uIjoiQ29uZGl0aW9uIGNvbnRhaW5zIGRldGFpbHMgZm9yIG9uZSBhc3BlY3Qgb2YgdGhlIGN1cnJlbnQgc3RhdGUgb2YgdGhpcyBBUEkgUmVzb3VyY2UuIiwicHJvcGVydGllcyI6eyJsYXN0VHJhbnNpdGlvblRpbWUiOnsiZGVzY3JpcHRpb24iOiJsYXN0VHJhbnNpdGlvblRpbWUgaXMgdGhlIGxhc3QgdGltZSB0aGUgY29uZGl0aW9uIHRyYW5zaXRpb25lZCBmcm9tIG9uZSBzdGF0dXMgdG8gYW5vdGhlci5cblRoaXMgc2hvdWxkIGJlIHdoZW4gdGhlIHVuZGVybHlpbmcgY29uZGl0aW9uIGNoYW5nZWQuICBJZiB0aGF0IGlzIG5vdCBrbm93biwgdGhlbiB1c2luZyB0aGUgdGltZSB3aGVuIHRoZSBBUEkgZmllbGQgY2hhbmdlZCBpcyBhY2NlcHRhYmxlLiIsImZvcm1hdCI6ImRhdGUtdGltZSIsInR5cGUiOiJzdHJpbmcifSwibWVzc2FnZSI6eyJkZXNjcmlwdGlvbiI6Im1lc3NhZ2UgaXMgYSBodW1hbiByZWFkYWJsZSBtZXNzYWdlIGluZGljYXRpbmcgZGV0YWlscyBhYm91dCB0aGUgdHJhbnNpdGlvbi5cblRoaXMgbWF5IGJlIGFuIGVtcHR5IHN0cmluZy4iLCJtYXhMZW5ndGgiOjMyNzY4LCJ0eXBlIjoic3RyaW5nIn0sIm9ic2VydmVkR2VuZXJhdGlvbiI6eyJkZXNjcmlwdGlvbiI6Im9ic2VydmVkR2VuZXJhdGlvbiByZXByZXNlbnRzIHRoZSAubWV0YWRhdGEuZ2VuZXJhdGlvbiB0aGF0IHRoZSBjb25kaXRpb24gd2FzIHNldCBiYXNlZCB1cG9uLlxuRm9yIGluc3RhbmNlLCBpZiAubWV0YWRhdGEuZ2VuZXJhdGlvbiBpcyBjdXJyZW50bHkgMTIsIGJ1dCB0aGUgLnN0YXR1cy5jb25kaXRpb25zW3hdLm9ic2VydmVkR2VuZXJhdGlvbiBpcyA5LCB0aGUgY29uZGl0aW9uIGlzIG91dCBvZiBkYXRlXG53aXRoIHJlc3BlY3QgdG8gdGhlIGN1cnJlbnQgc3RhdGUgb2YgdGhlIGluc3RhbmNlLiIsImZvcm1hdCI6ImludDY0IiwibWluaW11bSI6MCwidHlwZSI6ImludGVnZXIifSwicmVhc29uIjp7ImRlc2NyaXB0aW9uIjoicmVhc29uIGNvbnRhaW5zIGEgcHJvZ3JhbW1hdGljIGlkZW50aWZpZXIgaW5kaWNhdGluZyB0aGUgcmVhc29uIGZvciB0aGUgY29uZGl0aW9uJ3MgbGFzdCB0cmFuc2l0aW9uLlxuUHJvZHVjZXJzIG9mIHNwZWNpZmljIGNvbmRpdGlvbiB0eXBlcyBtYXkgZGVmaW5lIGV4cGVjdGVkIHZhbHVlcyBhbmQgbWVhbmluZ3MgZm9yIHRoaXMgZmllbGQsXG5hbmQgd2hldGhlciB0aGUgdmFsdWVzIGFyZSBjb25zaWRlcmVkIGEgZ3VhcmFudGVlZCBBUEkuXG5UaGUgdmFsdWUgc2hvdWxkIGJlIGEgQ2FtZWxDYXNlIHN0cmluZy5cblRoaXMgZmllbGQgbWF5IG5vdCBiZSBlbXB0eS4iLCJtYXhMZW5ndGgiOjEwMjQsIm1pbkxlbmd0aCI6MSwicGF0dGVybiI6Il5bQS1aYS16XShbQS1aYS16MC05Xyw6XSpbQS1aYS16MC05X10pPyQiLCJ0eXBlIjoic3RyaW5nIn0sInN0YXR1cyI6eyJkZXNjcmlwdGlvbiI6InN0YXR1cyBvZiB0aGUgY29uZGl0aW9uLCBvbmUgb2YgVHJ1ZSwgRmFsc2UsIFVua25vd24uIiwiZW51bSI6WyJUcnVlIiwiRmFsc2UiLCJVbmtub3duIl0sInR5cGUiOiJzdHJpbmcifSwidHlwZSI6eyJkZXNjcmlwdGlvbiI6InR5cGUgb2YgY29uZGl0aW9uIGluIENhbWVsQ2FzZSBvciBpbiBmb28uZXhhbXBsZS5jb20vQ2FtZWxDYXNlLiIsIm1heExlbmd0aCI6MzE2LCJwYXR0ZXJuIjoiXihbYS16MC05XShbLWEtejAtOV0qW2EtejAtOV0pPyhcXC5bYS16MC05XShbLWEtejAtOV0qW2EtejAtOV0pPykqLyk/KChbQS1aYS16MC05XVstQS1aYS16MC05Xy5dKik/W0EtWmEtejAtOV0pJCIsInR5cGUiOiJzdHJpbmcifX0sInJlcXVpcmVkIjpbImxhc3RUcmFuc2l0aW9uVGltZSIsIm1lc3NhZ2UiLCJyZWFzb24iLCJzdGF0dXMiLCJ0eXBlIl0sInR5cGUiOiJvYmplY3QifSwidHlwZSI6ImFycmF5In0sIm1lc3NhZ2UiOnsiZGVzY3JpcHRpb24iOiJNZXNzYWdlIHByb3ZpZGVzIG1vcmUgZGV0YWlsIGFib3V0IHRoZSBjdXJyZW50IHBoYXNlIiwidHlwZSI6InN0cmluZyJ9LCJvYnNlcnZlZEdlbmVyYXRpb24iOnsiZGVzY3JpcHRpb24iOiJPYnNlcnZlZEdlbmVyYXRpb24gaXMgdGhlIG1vc3QgcmVjZW50IGdlbmVyYXRpb24gb2JzZXJ2ZWQgYnkgdGhlIGNvbnRyb2xsZXIuIiwiZm9ybWF0IjoiaW50NjQiLCJ0eXBlIjoiaW50ZWdlciJ9LCJwaGFzZSI6eyJkZXNjcmlwdGlvbiI6IlBoYXNlIHJlcHJlc2VudHMgdGhlIGN1cnJlbnQgcGhhc2Ugb2YgdGhlIGJ1aWxkIChCdWlsZGluZywgQ29tcGxldGVkLCBGYWlsZWQpIiwiZW51bSI6WyJQZW5kaW5nIiwiVXBsb2FkaW5nIiwiQnVpbGRpbmciLCJQdXNoaW5nIiwiQ29tcGxldGVkIiwiRmFpbGVkIl0sInR5cGUiOiJzdHJpbmcifSwicGlwZWxpbmVSdW5OYW1lIjp7ImRlc2NyaXB0aW9uIjoiUGlwZWxpbmVSdW5OYW1lIGlzIHRoZSBuYW1lIG9mIHRoZSBhY3RpdmUgUGlwZWxpbmVSdW4gZm9yIHRoaXMgYnVpbGQiLCJ0eXBlIjoic3RyaW5nIn0sInB1c2hUYXNrUnVuTmFtZSI6eyJkZXNjcmlwdGlvbiI6IlB1c2hUYXNrUnVuTmFtZSBpcyB0aGUgbmFtZSBvZiB0aGUgVGFza1J1biBmb3IgcHVzaGluZyBhcnRpZmFjdHMgdG8gcmVnaXN0cnkiLCJ0eXBlIjoic3RyaW5nIn0sInB2Y05hbWUiOnsiZGVzY3JpcHRpb24iOiJQVkNOYW1lIGlzIHRoZSBuYW1lIG9mIHRoZSBQVkMgd2hlcmUgdGhlIGFydGlmYWN0IGlzIHN0b3JlZCIsInR5cGUiOiJzdHJpbmcifSwic3RhcnRUaW1lIjp7ImRlc2NyaXB0aW9uIjoiU3RhcnRUaW1lIGlzIHdoZW4gdGhlIGJ1aWxkIHN0YXJ0ZWQiLCJmb3JtYXQiOiJkYXRlLXRpbWUiLCJ0eXBlIjoic3RyaW5nIn19LCJ0eXBlIjoib2JqZWN0In19LCJ0eXBlIjoib2JqZWN0In19LCJzZXJ2ZWQiOnRydWUsInN0b3JhZ2UiOnRydWUsInN1YnJlc291cmNlcyI6eyJzdGF0dXMiOnt9fX1dfSwic3RhdHVzIjp7ImFjY2VwdGVkTmFtZXMiOnsia2luZCI6IiIsInBsdXJhbCI6IiJ9LCJjb25kaXRpb25zIjpudWxsLCJzdG9yZWRWZXJzaW9ucyI6bnVsbH19 + data: eyJhcGlWZXJzaW9uIjoiYXBpZXh0ZW5zaW9ucy5rOHMuaW8vdjEiLCJraW5kIjoiQ3VzdG9tUmVzb3VyY2VEZWZpbml0aW9uIiwibWV0YWRhdGEiOnsiYW5ub3RhdGlvbnMiOnsiY29udHJvbGxlci1nZW4ua3ViZWJ1aWxkZXIuaW8vdmVyc2lvbiI6InYwLjE5LjAifSwiY3JlYXRpb25UaW1lc3RhbXAiOm51bGwsIm5hbWUiOiJpbWFnZWJ1aWxkcy5hdXRvbW90aXZlLnNkdi5jbG91ZC5yZWRoYXQuY29tIn0sInNwZWMiOnsiZ3JvdXAiOiJhdXRvbW90aXZlLnNkdi5jbG91ZC5yZWRoYXQuY29tIiwibmFtZXMiOnsia2luZCI6IkltYWdlQnVpbGQiLCJsaXN0S2luZCI6IkltYWdlQnVpbGRMaXN0IiwicGx1cmFsIjoiaW1hZ2VidWlsZHMiLCJzaW5ndWxhciI6ImltYWdlYnVpbGQifSwic2NvcGUiOiJOYW1lc3BhY2VkIiwidmVyc2lvbnMiOlt7Im5hbWUiOiJ2MWFscGhhMSIsInNjaGVtYSI6eyJvcGVuQVBJVjNTY2hlbWEiOnsiZGVzY3JpcHRpb24iOiJJbWFnZUJ1aWxkIGlzIHRoZSBTY2hlbWEgZm9yIHRoZSBpbWFnZWJ1aWxkcyBBUEkiLCJwcm9wZXJ0aWVzIjp7ImFwaVZlcnNpb24iOnsiZGVzY3JpcHRpb24iOiJBUElWZXJzaW9uIGRlZmluZXMgdGhlIHZlcnNpb25lZCBzY2hlbWEgb2YgdGhpcyByZXByZXNlbnRhdGlvbiBvZiBhbiBvYmplY3QuXG5TZXJ2ZXJzIHNob3VsZCBjb252ZXJ0IHJlY29nbml6ZWQgc2NoZW1hcyB0byB0aGUgbGF0ZXN0IGludGVybmFsIHZhbHVlLCBhbmRcbm1heSByZWplY3QgdW5yZWNvZ25pemVkIHZhbHVlcy5cbk1vcmUgaW5mbzogaHR0cHM6Ly9naXQuazhzLmlvL2NvbW11bml0eS9jb250cmlidXRvcnMvZGV2ZWwvc2lnLWFyY2hpdGVjdHVyZS9hcGktY29udmVudGlvbnMubWQjcmVzb3VyY2VzIiwidHlwZSI6InN0cmluZyJ9LCJraW5kIjp7ImRlc2NyaXB0aW9uIjoiS2luZCBpcyBhIHN0cmluZyB2YWx1ZSByZXByZXNlbnRpbmcgdGhlIFJFU1QgcmVzb3VyY2UgdGhpcyBvYmplY3QgcmVwcmVzZW50cy5cblNlcnZlcnMgbWF5IGluZmVyIHRoaXMgZnJvbSB0aGUgZW5kcG9pbnQgdGhlIGNsaWVudCBzdWJtaXRzIHJlcXVlc3RzIHRvLlxuQ2Fubm90IGJlIHVwZGF0ZWQuXG5JbiBDYW1lbENhc2UuXG5Nb3JlIGluZm86IGh0dHBzOi8vZ2l0Lms4cy5pby9jb21tdW5pdHkvY29udHJpYnV0b3JzL2RldmVsL3NpZy1hcmNoaXRlY3R1cmUvYXBpLWNvbnZlbnRpb25zLm1kI3R5cGVzLWtpbmRzIiwidHlwZSI6InN0cmluZyJ9LCJtZXRhZGF0YSI6eyJ0eXBlIjoib2JqZWN0In0sInNwZWMiOnsiZGVzY3JpcHRpb24iOiJJbWFnZUJ1aWxkU3BlYyBkZWZpbmVzIHRoZSBkZXNpcmVkIHN0YXRlIG9mIEltYWdlQnVpbGQiLCJwcm9wZXJ0aWVzIjp7ImFyY2hpdGVjdHVyZSI6eyJkZXNjcmlwdGlvbiI6IkFyY2hpdGVjdHVyZSBzcGVjaWZpZXMgdGhlIHRhcmdldCBhcmNoaXRlY3R1cmUiLCJ0eXBlIjoic3RyaW5nIn0sImF1dG9tb3RpdmVJbWFnZUJ1aWxkZXIiOnsiZGVzY3JpcHRpb24iOiJBdXRvbW90aXZlSW1hZ2VCdWlsZGVyIHNwZWNpZmllcyB0aGUgaW1hZ2UgdG8gdXNlIGZvciBidWlsZGluZyIsInR5cGUiOiJzdHJpbmcifSwiYnVpbGREaXNrSW1hZ2UiOnsiZGVzY3JpcHRpb24iOiJCdWlsZERpc2tJbWFnZSBpbmRpY2F0ZXMgd2hldGhlciB0byBidWlsZCBhIGRpc2sgaW1hZ2UgZnJvbSB0aGUgYm9vdGMgY29udGFpbmVyIiwidHlwZSI6ImJvb2xlYW4ifSwiYnVpbGRlckltYWdlIjp7ImRlc2NyaXB0aW9uIjoiQnVpbGRlckltYWdlIGlzIGEgY3VzdG9tIGJ1aWxkZXIgaW1hZ2UgdG8gdXNlIiwidHlwZSI6InN0cmluZyJ9LCJjb21wcmVzc2lvbiI6eyJkZWZhdWx0IjoiZ3ppcCIsImRlc2NyaXB0aW9uIjoiQ29tcHJlc3Npb24gc3BlY2lmaWVzIHRoZSBjb21wcmVzc2lvbiBhbGdvcml0aG0gZm9yIGFydGlmYWN0cyIsImVudW0iOlsibHo0IiwiZ3ppcCIsInh6Il0sInR5cGUiOiJzdHJpbmcifSwiY29udGFpbmVyUHVzaCI6eyJkZXNjcmlwdGlvbiI6IkNvbnRhaW5lclB1c2ggaXMgdGhlIHJlZ2lzdHJ5IFVSTCB0byBwdXNoIHRoZSBib290YyBjb250YWluZXIgaW1hZ2UiLCJ0eXBlIjoic3RyaW5nIn0sImNvbnRhaW5lclJlZiI6eyJkZXNjcmlwdGlvbiI6IkNvbnRhaW5lclJlZiBpcyB0aGUgcmVmZXJlbmNlIHRvIGFuIGV4aXN0aW5nIGJvb3RjIGNvbnRhaW5lciBpbWFnZVxuVXNlZCB3aXRoIG1vZGU9ZGlzayB0byBjcmVhdGUgYSBkaXNrIGltYWdlIGZyb20gYW4gZXhpc3RpbmcgY29udGFpbmVyIiwidHlwZSI6InN0cmluZyJ9LCJkaXN0cm8iOnsiZGVzY3JpcHRpb24iOiJEaXN0cm8gc3BlY2lmaWVzIHRoZSBkaXN0cmlidXRpb24gdG8gYnVpbGQgZm9yIChlLmcuLCBcImNzOVwiKSIsInR5cGUiOiJzdHJpbmcifSwiZW52U2VjcmV0UmVmIjp7ImRlc2NyaXB0aW9uIjoiRW52U2VjcmV0UmVmIGlzIHRoZSBuYW1lIG9mIHRoZSBzZWNyZXQgY29udGFpbmluZyBlbnZpcm9ubWVudCB2YXJpYWJsZXMgZm9yIHRoZSBidWlsZFxuVGhlc2UgZW52aXJvbm1lbnQgdmFyaWFibGVzIHdpbGwgYmUgYXZhaWxhYmxlIGR1cmluZyB0aGUgYnVpbGQgcHJvY2VzcyBhbmQgY2FuIGJlIHVzZWRcbmZvciBwcml2YXRlIHJlZ2lzdHJ5IGF1dGhlbnRpY2F0aW9uIChlLmcuLCBSRUdJU1RSWV9VU0VSTkFNRSwgUkVHSVNUUllfUEFTU1dPUkQsIFJFR0lTVFJZX0FVVEhfRklMRSkiLCJ0eXBlIjoic3RyaW5nIn0sImV4cG9ydEZvcm1hdCI6eyJkZXNjcmlwdGlvbiI6IkV4cG9ydEZvcm1hdCBzcGVjaWZpZXMgdGhlIG91dHB1dCBmb3JtYXQgKGltYWdlLCBxY293MikiLCJ0eXBlIjoic3RyaW5nIn0sImV4cG9ydE9jaSI6eyJkZXNjcmlwdGlvbiI6IkV4cG9ydE9DSSBpcyB0aGUgcmVnaXN0cnkgVVJMIHRvIHB1c2ggdGhlIGRpc2sgaW1hZ2UgYXMgYW4gT0NJIGFydGlmYWN0IiwidHlwZSI6InN0cmluZyJ9LCJleHBvc2VSb3V0ZSI6eyJkZXNjcmlwdGlvbiI6IkV4cG9zZVJvdXRlIGluZGljYXRlcyB3aGV0aGVyIHRvIGV4cG9zZSB0aGUgYSByb3V0ZSBmb3IgdGhlIGFydGlmYWN0cyIsInR5cGUiOiJib29sZWFuIn0sImlucHV0RmlsZXNTZXJ2ZXIiOnsiZGVzY3JpcHRpb24iOiJJbnB1dEZpbGVzU2VydmVyIGluZGljYXRlcyBpZiB0aGVyZSdzIGEgc2VydmVyIGZvciBmaWxlcyByZWZlcmVuY2VkIGxvY2FsbHkgaW4gdGhlIG1hbmlmZXN0IiwidHlwZSI6ImJvb2xlYW4ifSwibWFuaWZlc3RDb25maWdNYXAiOnsiZGVzY3JpcHRpb24iOiJNYW5pZmVzdENvbmZpZ01hcCBzcGVjaWZpZXMgdGhlIG5hbWUgb2YgdGhlIENvbmZpZ01hcCBjb250YWluaW5nIHRoZSBtYW5pZmVzdCBjb25maWd1cmF0aW9uIiwidHlwZSI6InN0cmluZyJ9LCJtb2RlIjp7ImRlc2NyaXB0aW9uIjoiTW9kZSBzcGVjaWZpZXMgdGhlIGJ1aWxkIG1vZGUgKHBhY2thZ2UsIGltYWdlKSIsInR5cGUiOiJzdHJpbmcifSwicHVibGlzaGVycyI6eyJkZXNjcmlwdGlvbiI6IlB1Ymxpc2hlcnMgZGVmaW5lcyB3aGVyZSB0byBwdWJsaXNoIHRoZSBidWlsdCBhcnRpZmFjdHMiLCJwcm9wZXJ0aWVzIjp7InJlZ2lzdHJ5Ijp7ImRlc2NyaXB0aW9uIjoiUmVnaXN0cnkgY29uZmlndXJhdGlvbiBmb3IgcHVibGlzaGluZyB0byBhbiBPQ0kgcmVnaXN0cnkiLCJwcm9wZXJ0aWVzIjp7InJlcG9zaXRvcnlVcmwiOnsiZGVzY3JpcHRpb24iOiJSZXBvc2l0b3J5VVJMIGlzIHRoZSBVUkwgb2YgdGhlIE9DSSByZWdpc3RyeSByZXBvc2l0b3J5IiwidHlwZSI6InN0cmluZyJ9LCJzZWNyZXQiOnsiZGVzY3JpcHRpb24iOiJTZWNyZXQgaXMgdGhlIG5hbWUgb2YgdGhlIHNlY3JldCBjb250YWluaW5nIHJlZ2lzdHJ5IGNyZWRlbnRpYWxzIiwidHlwZSI6InN0cmluZyJ9fSwicmVxdWlyZWQiOlsicmVwb3NpdG9yeVVybCIsInNlY3JldCJdLCJ0eXBlIjoib2JqZWN0In19LCJ0eXBlIjoib2JqZWN0In0sInJ1bnRpbWVDbGFzc05hbWUiOnsiZGVzY3JpcHRpb24iOiJSdW50aW1lQ2xhc3NOYW1lIHNwZWNpZmllcyB0aGUgcnVudGltZSBjbGFzcyB0byB1c2UgZm9yIHRoZSBidWlsZCBwb2QiLCJ0eXBlIjoic3RyaW5nIn0sInNlcnZlQXJ0aWZhY3QiOnsiZGVzY3JpcHRpb24iOiJTZXJ2ZUFydGlmYWN0IGRldGVybWluZXMgd2hldGhlciB0byBtYWtlIHRoZSBidWlsdCBhcnRpZmFjdCBhdmFpbGFibGUgZm9yIGRvd25sb2FkIiwidHlwZSI6ImJvb2xlYW4ifSwic2VydmVFeHBpcnlIb3VycyI6eyJkZXNjcmlwdGlvbiI6IlNlcnZlRXhwaXJ5SG91cnMgc3BlY2lmaWVzIGhvdyBsb25nIHRvIHNlcnZlIHRoZSBhcnRpZmFjdCBiZWZvcmUgY2xlYW51cCAoZGVmYXVsdDogMjQpIiwiZm9ybWF0IjoiaW50MzIiLCJ0eXBlIjoiaW50ZWdlciJ9LCJzdG9yYWdlQ2xhc3MiOnsiZGVzY3JpcHRpb24iOiJTdG9yYWdlQ2xhc3MgaXMgdGhlIG5hbWUgb2YgdGhlIHN0b3JhZ2UgY2xhc3MgdG8gdXNlIGZvciB0aGUgYnVpbGQgUFZDIiwidHlwZSI6InN0cmluZyJ9LCJ0YXJnZXQiOnsiZGVzY3JpcHRpb24iOiJUYXJnZXQgc3BlY2lmaWVzIHRoZSBidWlsZCB0YXJnZXQgKGUuZy4sIFwicWVtdVwiKSIsInR5cGUiOiJzdHJpbmcifX0sInR5cGUiOiJvYmplY3QifSwic3RhdHVzIjp7ImRlc2NyaXB0aW9uIjoiSW1hZ2VCdWlsZFN0YXR1cyBkZWZpbmVzIHRoZSBvYnNlcnZlZCBzdGF0ZSBvZiBJbWFnZUJ1aWxkIiwicHJvcGVydGllcyI6eyJhcnRpZmFjdEZpbGVOYW1lIjp7ImRlc2NyaXB0aW9uIjoiQXJ0aWZhY3RGaWxlTmFtZSBpcyB0aGUgbmFtZSBvZiB0aGUgYXJ0aWZhY3QgZmlsZSBpbnNpZGUgdGhlIFBWQyIsInR5cGUiOiJzdHJpbmcifSwiYXJ0aWZhY3RQYXRoIjp7ImRlc2NyaXB0aW9uIjoiQXJ0aWZhY3RQYXRoIGlzIHRoZSBwYXRoIGluc2lkZSB0aGUgUFZDIHdoZXJlIHRoZSBhcnRpZmFjdCBpcyBzdG9yZWQiLCJ0eXBlIjoic3RyaW5nIn0sImFydGlmYWN0VVJMIjp7ImRlc2NyaXB0aW9uIjoiQXJ0aWZhY3RVUkwgaXMgdGhlIHJvdXRlIFVSTCBjcmVhdGVkIHRvIGV4cG9zZSB0aGUgYXJ0aWZhY3RzIiwidHlwZSI6InN0cmluZyJ9LCJjb21wbGV0aW9uVGltZSI6eyJkZXNjcmlwdGlvbiI6IkNvbXBsZXRpb25UaW1lIGlzIHdoZW4gdGhlIGJ1aWxkIGZpbmlzaGVkIiwiZm9ybWF0IjoiZGF0ZS10aW1lIiwidHlwZSI6InN0cmluZyJ9LCJjb25kaXRpb25zIjp7ImRlc2NyaXB0aW9uIjoiQ29uZGl0aW9ucyByZXByZXNlbnQgdGhlIGxhdGVzdCBhdmFpbGFibGUgb2JzZXJ2YXRpb25zIG9mIHRoZSBJbWFnZUJ1aWxkJ3Mgc3RhdGUiLCJpdGVtcyI6eyJkZXNjcmlwdGlvbiI6IkNvbmRpdGlvbiBjb250YWlucyBkZXRhaWxzIGZvciBvbmUgYXNwZWN0IG9mIHRoZSBjdXJyZW50IHN0YXRlIG9mIHRoaXMgQVBJIFJlc291cmNlLiIsInByb3BlcnRpZXMiOnsibGFzdFRyYW5zaXRpb25UaW1lIjp7ImRlc2NyaXB0aW9uIjoibGFzdFRyYW5zaXRpb25UaW1lIGlzIHRoZSBsYXN0IHRpbWUgdGhlIGNvbmRpdGlvbiB0cmFuc2l0aW9uZWQgZnJvbSBvbmUgc3RhdHVzIHRvIGFub3RoZXIuXG5UaGlzIHNob3VsZCBiZSB3aGVuIHRoZSB1bmRlcmx5aW5nIGNvbmRpdGlvbiBjaGFuZ2VkLiAgSWYgdGhhdCBpcyBub3Qga25vd24sIHRoZW4gdXNpbmcgdGhlIHRpbWUgd2hlbiB0aGUgQVBJIGZpZWxkIGNoYW5nZWQgaXMgYWNjZXB0YWJsZS4iLCJmb3JtYXQiOiJkYXRlLXRpbWUiLCJ0eXBlIjoic3RyaW5nIn0sIm1lc3NhZ2UiOnsiZGVzY3JpcHRpb24iOiJtZXNzYWdlIGlzIGEgaHVtYW4gcmVhZGFibGUgbWVzc2FnZSBpbmRpY2F0aW5nIGRldGFpbHMgYWJvdXQgdGhlIHRyYW5zaXRpb24uXG5UaGlzIG1heSBiZSBhbiBlbXB0eSBzdHJpbmcuIiwibWF4TGVuZ3RoIjozMjc2OCwidHlwZSI6InN0cmluZyJ9LCJvYnNlcnZlZEdlbmVyYXRpb24iOnsiZGVzY3JpcHRpb24iOiJvYnNlcnZlZEdlbmVyYXRpb24gcmVwcmVzZW50cyB0aGUgLm1ldGFkYXRhLmdlbmVyYXRpb24gdGhhdCB0aGUgY29uZGl0aW9uIHdhcyBzZXQgYmFzZWQgdXBvbi5cbkZvciBpbnN0YW5jZSwgaWYgLm1ldGFkYXRhLmdlbmVyYXRpb24gaXMgY3VycmVudGx5IDEyLCBidXQgdGhlIC5zdGF0dXMuY29uZGl0aW9uc1t4XS5vYnNlcnZlZEdlbmVyYXRpb24gaXMgOSwgdGhlIGNvbmRpdGlvbiBpcyBvdXQgb2YgZGF0ZVxud2l0aCByZXNwZWN0IHRvIHRoZSBjdXJyZW50IHN0YXRlIG9mIHRoZSBpbnN0YW5jZS4iLCJmb3JtYXQiOiJpbnQ2NCIsIm1pbmltdW0iOjAsInR5cGUiOiJpbnRlZ2VyIn0sInJlYXNvbiI6eyJkZXNjcmlwdGlvbiI6InJlYXNvbiBjb250YWlucyBhIHByb2dyYW1tYXRpYyBpZGVudGlmaWVyIGluZGljYXRpbmcgdGhlIHJlYXNvbiBmb3IgdGhlIGNvbmRpdGlvbidzIGxhc3QgdHJhbnNpdGlvbi5cblByb2R1Y2VycyBvZiBzcGVjaWZpYyBjb25kaXRpb24gdHlwZXMgbWF5IGRlZmluZSBleHBlY3RlZCB2YWx1ZXMgYW5kIG1lYW5pbmdzIGZvciB0aGlzIGZpZWxkLFxuYW5kIHdoZXRoZXIgdGhlIHZhbHVlcyBhcmUgY29uc2lkZXJlZCBhIGd1YXJhbnRlZWQgQVBJLlxuVGhlIHZhbHVlIHNob3VsZCBiZSBhIENhbWVsQ2FzZSBzdHJpbmcuXG5UaGlzIGZpZWxkIG1heSBub3QgYmUgZW1wdHkuIiwibWF4TGVuZ3RoIjoxMDI0LCJtaW5MZW5ndGgiOjEsInBhdHRlcm4iOiJeW0EtWmEtel0oW0EtWmEtejAtOV8sOl0qW0EtWmEtejAtOV9dKT8kIiwidHlwZSI6InN0cmluZyJ9LCJzdGF0dXMiOnsiZGVzY3JpcHRpb24iOiJzdGF0dXMgb2YgdGhlIGNvbmRpdGlvbiwgb25lIG9mIFRydWUsIEZhbHNlLCBVbmtub3duLiIsImVudW0iOlsiVHJ1ZSIsIkZhbHNlIiwiVW5rbm93biJdLCJ0eXBlIjoic3RyaW5nIn0sInR5cGUiOnsiZGVzY3JpcHRpb24iOiJ0eXBlIG9mIGNvbmRpdGlvbiBpbiBDYW1lbENhc2Ugb3IgaW4gZm9vLmV4YW1wbGUuY29tL0NhbWVsQ2FzZS4iLCJtYXhMZW5ndGgiOjMxNiwicGF0dGVybiI6Il4oW2EtejAtOV0oWy1hLXowLTldKlthLXowLTldKT8oXFwuW2EtejAtOV0oWy1hLXowLTldKlthLXowLTldKT8pKi8pPygoW0EtWmEtejAtOV1bLUEtWmEtejAtOV8uXSopP1tBLVphLXowLTldKSQiLCJ0eXBlIjoic3RyaW5nIn19LCJyZXF1aXJlZCI6WyJsYXN0VHJhbnNpdGlvblRpbWUiLCJtZXNzYWdlIiwicmVhc29uIiwic3RhdHVzIiwidHlwZSJdLCJ0eXBlIjoib2JqZWN0In0sInR5cGUiOiJhcnJheSJ9LCJtZXNzYWdlIjp7ImRlc2NyaXB0aW9uIjoiTWVzc2FnZSBwcm92aWRlcyBtb3JlIGRldGFpbCBhYm91dCB0aGUgY3VycmVudCBwaGFzZSIsInR5cGUiOiJzdHJpbmcifSwib2JzZXJ2ZWRHZW5lcmF0aW9uIjp7ImRlc2NyaXB0aW9uIjoiT2JzZXJ2ZWRHZW5lcmF0aW9uIGlzIHRoZSBtb3N0IHJlY2VudCBnZW5lcmF0aW9uIG9ic2VydmVkIGJ5IHRoZSBjb250cm9sbGVyLiIsImZvcm1hdCI6ImludDY0IiwidHlwZSI6ImludGVnZXIifSwicGhhc2UiOnsiZGVzY3JpcHRpb24iOiJQaGFzZSByZXByZXNlbnRzIHRoZSBjdXJyZW50IHBoYXNlIG9mIHRoZSBidWlsZCAoQnVpbGRpbmcsIENvbXBsZXRlZCwgRmFpbGVkKSIsImVudW0iOlsiUGVuZGluZyIsIlVwbG9hZGluZyIsIkJ1aWxkaW5nIiwiUHVzaGluZyIsIkNvbXBsZXRlZCIsIkZhaWxlZCJdLCJ0eXBlIjoic3RyaW5nIn0sInBpcGVsaW5lUnVuTmFtZSI6eyJkZXNjcmlwdGlvbiI6IlBpcGVsaW5lUnVuTmFtZSBpcyB0aGUgbmFtZSBvZiB0aGUgYWN0aXZlIFBpcGVsaW5lUnVuIGZvciB0aGlzIGJ1aWxkIiwidHlwZSI6InN0cmluZyJ9LCJwdXNoVGFza1J1bk5hbWUiOnsiZGVzY3JpcHRpb24iOiJQdXNoVGFza1J1bk5hbWUgaXMgdGhlIG5hbWUgb2YgdGhlIFRhc2tSdW4gZm9yIHB1c2hpbmcgYXJ0aWZhY3RzIHRvIHJlZ2lzdHJ5IiwidHlwZSI6InN0cmluZyJ9LCJwdmNOYW1lIjp7ImRlc2NyaXB0aW9uIjoiUFZDTmFtZSBpcyB0aGUgbmFtZSBvZiB0aGUgUFZDIHdoZXJlIHRoZSBhcnRpZmFjdCBpcyBzdG9yZWQiLCJ0eXBlIjoic3RyaW5nIn0sInN0YXJ0VGltZSI6eyJkZXNjcmlwdGlvbiI6IlN0YXJ0VGltZSBpcyB3aGVuIHRoZSBidWlsZCBzdGFydGVkIiwiZm9ybWF0IjoiZGF0ZS10aW1lIiwidHlwZSI6InN0cmluZyJ9fSwidHlwZSI6Im9iamVjdCJ9fSwidHlwZSI6Im9iamVjdCJ9fSwic2VydmVkIjp0cnVlLCJzdG9yYWdlIjp0cnVlLCJzdWJyZXNvdXJjZXMiOnsic3RhdHVzIjp7fX19XX0sInN0YXR1cyI6eyJhY2NlcHRlZE5hbWVzIjp7ImtpbmQiOiIiLCJwbHVyYWwiOiIifSwiY29uZGl0aW9ucyI6bnVsbCwic3RvcmVkVmVyc2lvbnMiOm51bGx9fQ== - type: olm.bundle.object value: data: eyJhcGlWZXJzaW9uIjoiYXBpZXh0ZW5zaW9ucy5rOHMuaW8vdjEiLCJraW5kIjoiQ3VzdG9tUmVzb3VyY2VEZWZpbml0aW9uIiwibWV0YWRhdGEiOnsiYW5ub3RhdGlvbnMiOnsiY29udHJvbGxlci1nZW4ua3ViZWJ1aWxkZXIuaW8vdmVyc2lvbiI6InYwLjE5LjAifSwiY3JlYXRpb25UaW1lc3RhbXAiOm51bGwsIm5hbWUiOiJpbWFnZXMuYXV0b21vdGl2ZS5zZHYuY2xvdWQucmVkaGF0LmNvbSJ9LCJzcGVjIjp7Imdyb3VwIjoiYXV0b21vdGl2ZS5zZHYuY2xvdWQucmVkaGF0LmNvbSIsIm5hbWVzIjp7ImtpbmQiOiJJbWFnZSIsImxpc3RLaW5kIjoiSW1hZ2VMaXN0IiwicGx1cmFsIjoiaW1hZ2VzIiwic2luZ3VsYXIiOiJpbWFnZSJ9LCJzY29wZSI6Ik5hbWVzcGFjZWQiLCJ2ZXJzaW9ucyI6W3siYWRkaXRpb25hbFByaW50ZXJDb2x1bW5zIjpbeyJqc29uUGF0aCI6Ii5zcGVjLmRpc3RybyIsIm5hbWUiOiJEaXN0cm8iLCJ0eXBlIjoic3RyaW5nIn0seyJqc29uUGF0aCI6Ii5zcGVjLmFyY2hpdGVjdHVyZSIsIm5hbWUiOiJBcmNoaXRlY3R1cmUiLCJ0eXBlIjoic3RyaW5nIn0seyJqc29uUGF0aCI6Ii5zcGVjLmV4cG9ydEZvcm1hdCIsIm5hbWUiOiJGb3JtYXQiLCJ0eXBlIjoic3RyaW5nIn0seyJqc29uUGF0aCI6Ii5zcGVjLmxvY2F0aW9uLnR5cGUiLCJuYW1lIjoiTG9jYXRpb24gVHlwZSIsInR5cGUiOiJzdHJpbmcifSx7Impzb25QYXRoIjoiLnN0YXR1cy5waGFzZSIsIm5hbWUiOiJQaGFzZSIsInR5cGUiOiJzdHJpbmcifSx7Impzb25QYXRoIjoiLnNwZWMudmVyc2lvbiIsIm5hbWUiOiJWZXJzaW9uIiwidHlwZSI6InN0cmluZyJ9LHsianNvblBhdGgiOiIubWV0YWRhdGEuY3JlYXRpb25UaW1lc3RhbXAiLCJuYW1lIjoiQWdlIiwidHlwZSI6ImRhdGUifV0sIm5hbWUiOiJ2MWFscGhhMSIsInNjaGVtYSI6eyJvcGVuQVBJVjNTY2hlbWEiOnsiZGVzY3JpcHRpb24iOiJJbWFnZSBpcyB0aGUgU2NoZW1hIGZvciB0aGUgaW1hZ2VzIEFQSSIsInByb3BlcnRpZXMiOnsiYXBpVmVyc2lvbiI6eyJkZXNjcmlwdGlvbiI6IkFQSVZlcnNpb24gZGVmaW5lcyB0aGUgdmVyc2lvbmVkIHNjaGVtYSBvZiB0aGlzIHJlcHJlc2VudGF0aW9uIG9mIGFuIG9iamVjdC5cblNlcnZlcnMgc2hvdWxkIGNvbnZlcnQgcmVjb2duaXplZCBzY2hlbWFzIHRvIHRoZSBsYXRlc3QgaW50ZXJuYWwgdmFsdWUsIGFuZFxubWF5IHJlamVjdCB1bnJlY29nbml6ZWQgdmFsdWVzLlxuTW9yZSBpbmZvOiBodHRwczovL2dpdC5rOHMuaW8vY29tbXVuaXR5L2NvbnRyaWJ1dG9ycy9kZXZlbC9zaWctYXJjaGl0ZWN0dXJlL2FwaS1jb252ZW50aW9ucy5tZCNyZXNvdXJjZXMiLCJ0eXBlIjoic3RyaW5nIn0sImtpbmQiOnsiZGVzY3JpcHRpb24iOiJLaW5kIGlzIGEgc3RyaW5nIHZhbHVlIHJlcHJlc2VudGluZyB0aGUgUkVTVCByZXNvdXJjZSB0aGlzIG9iamVjdCByZXByZXNlbnRzLlxuU2VydmVycyBtYXkgaW5mZXIgdGhpcyBmcm9tIHRoZSBlbmRwb2ludCB0aGUgY2xpZW50IHN1Ym1pdHMgcmVxdWVzdHMgdG8uXG5DYW5ub3QgYmUgdXBkYXRlZC5cbkluIENhbWVsQ2FzZS5cbk1vcmUgaW5mbzogaHR0cHM6Ly9naXQuazhzLmlvL2NvbW11bml0eS9jb250cmlidXRvcnMvZGV2ZWwvc2lnLWFyY2hpdGVjdHVyZS9hcGktY29udmVudGlvbnMubWQjdHlwZXMta2luZHMiLCJ0eXBlIjoic3RyaW5nIn0sIm1ldGFkYXRhIjp7InR5cGUiOiJvYmplY3QifSwic3BlYyI6eyJkZXNjcmlwdGlvbiI6IkltYWdlU3BlYyBkZWZpbmVzIHRoZSBkZXNpcmVkIHN0YXRlIG9mIEltYWdlIiwicHJvcGVydGllcyI6eyJhcmNoaXRlY3R1cmUiOnsiZGVzY3JpcHRpb24iOiJBcmNoaXRlY3R1cmUgc3BlY2lmaWVzIHRoZSB0YXJnZXQgYXJjaGl0ZWN0dXJlIiwidHlwZSI6InN0cmluZyJ9LCJkZXNjcmlwdGlvbiI6eyJkZXNjcmlwdGlvbiI6IkRlc2NyaXB0aW9uIHByb3ZpZGVzIGEgaHVtYW4tcmVhZGFibGUgZGVzY3JpcHRpb24gb2YgdGhlIGltYWdlIiwidHlwZSI6InN0cmluZyJ9LCJkaXN0cm8iOnsiZGVzY3JpcHRpb24iOiJEaXN0cm8gc3BlY2lmaWVzIHRoZSBkaXN0cmlidXRpb24iLCJ0eXBlIjoic3RyaW5nIn0sImV4cG9ydEZvcm1hdCI6eyJkZXNjcmlwdGlvbiI6IkV4cG9ydEZvcm1hdCBzcGVjaWZpZXMgdGhlIG91dHB1dCBmb3JtYXQiLCJ0eXBlIjoic3RyaW5nIn0sImxvY2F0aW9uIjp7ImRlc2NyaXB0aW9uIjoiTG9jYXRpb24gZGVmaW5lcyB3aGVyZSB0aGUgaW1hZ2UgaXMgc3RvcmVkIiwicHJvcGVydGllcyI6eyJyZWdpc3RyeSI6eyJkZXNjcmlwdGlvbiI6IlJlZ2lzdHJ5IGNvbnRhaW5zIGNvbmZpZ3VyYXRpb24gZm9yIGNvbnRhaW5lciByZWdpc3RyeSBzdG9yYWdlIiwicHJvcGVydGllcyI6eyJkaWdlc3QiOnsiZGVzY3JpcHRpb24iOiJEaWdlc3QgaXMgdGhlIGNvbnRlbnQtYWRkcmVzc2FibGUgZGlnZXN0IG9mIHRoZSBpbWFnZSIsInR5cGUiOiJzdHJpbmcifSwic2VjcmV0UmVmIjp7ImRlc2NyaXB0aW9uIjoiU2VjcmV0UmVmIGlzIHRoZSBuYW1lIG9mIHRoZSBzZWNyZXQgY29udGFpbmluZyByZWdpc3RyeSBjcmVkZW50aWFscyIsInR5cGUiOiJzdHJpbmcifSwidXJsIjp7ImRlc2NyaXB0aW9uIjoiVVJMIGlzIHRoZSBmdWxsIFVSTCB0byB0aGUgaW1hZ2UgaW4gdGhlIHJlZ2lzdHJ5IChlLmcuLCBcInF1YXkuaW8vbXlvcmcvbXlpbWFnZTp0YWdcIikiLCJ0eXBlIjoic3RyaW5nIn19LCJyZXF1aXJlZCI6WyJ1cmwiXSwidHlwZSI6Im9iamVjdCJ9LCJ0eXBlIjp7ImRlZmF1bHQiOiJyZWdpc3RyeSIsImRlc2NyaXB0aW9uIjoiVHlwZSBzcGVjaWZpZXMgdGhlIHN0b3JhZ2UgdHlwZSIsImVudW0iOlsicmVnaXN0cnkiXSwidHlwZSI6InN0cmluZyJ9fSwicmVxdWlyZWQiOlsidHlwZSJdLCJ0eXBlIjoib2JqZWN0In0sIm1ldGFkYXRhIjp7ImRlc2NyaXB0aW9uIjoiTWV0YWRhdGEgY29udGFpbnMgYWRkaXRpb25hbCBpbmZvcm1hdGlvbiBhYm91dCB0aGUgaW1hZ2UiLCJwcm9wZXJ0aWVzIjp7ImFubm90YXRpb25zIjp7ImFkZGl0aW9uYWxQcm9wZXJ0aWVzIjp7InR5cGUiOiJzdHJpbmcifSwiZGVzY3JpcHRpb24iOiJBbm5vdGF0aW9ucyBhcmUga2V5LXZhbHVlIHBhaXJzIGZvciBhZGRpdGlvbmFsIGFubm90YXRpb25zIiwidHlwZSI6Im9iamVjdCJ9LCJidWlsZERhdGUiOnsiZGVzY3JpcHRpb24iOiJCdWlsZERhdGUgaXMgd2hlbiB0aGUgaW1hZ2Ugd2FzIGJ1aWx0IiwiZm9ybWF0IjoiZGF0ZS10aW1lIiwidHlwZSI6InN0cmluZyJ9LCJjcmVhdGVkQnkiOnsiZGVzY3JpcHRpb24iOiJDcmVhdGVkQnkgaWRlbnRpZmllcyB3aG8gb3Igd2hhdCBjcmVhdGVkIHRoaXMgaW1hZ2UiLCJ0eXBlIjoic3RyaW5nIn0sImxhYmVscyI6eyJhZGRpdGlvbmFsUHJvcGVydGllcyI6eyJ0eXBlIjoic3RyaW5nIn0sImRlc2NyaXB0aW9uIjoiTGFiZWxzIGFyZSBrZXktdmFsdWUgcGFpcnMgZm9yIGFkZGl0aW9uYWwgbWV0YWRhdGEiLCJ0eXBlIjoib2JqZWN0In0sInNvdXJjZUltYWdlQnVpbGQiOnsiZGVzY3JpcHRpb24iOiJTb3VyY2VJbWFnZUJ1aWxkIHJlZmVyZW5jZXMgdGhlIEltYWdlQnVpbGQgdGhhdCBjcmVhdGVkIHRoaXMgaW1hZ2UiLCJ0eXBlIjoic3RyaW5nIn19LCJ0eXBlIjoib2JqZWN0In0sIm1vZGUiOnsiZGVzY3JpcHRpb24iOiJNb2RlIHNwZWNpZmllcyB0aGUgYnVpbGQgbW9kZSIsInR5cGUiOiJzdHJpbmcifSwic2l6ZSI6eyJkZXNjcmlwdGlvbiI6IlNpemUgc3BlY2lmaWVzIHRoZSBpbWFnZSBzaXplIGluZm9ybWF0aW9uIiwicHJvcGVydGllcyI6eyJjb21wcmVzc2VkQnl0ZXMiOnsiZGVzY3JpcHRpb24iOiJDb21wcmVzc2VkQnl0ZXMgaXMgdGhlIHNpemUgb2YgdGhlIGNvbXByZXNzZWQgaW1hZ2UgaW4gYnl0ZXMiLCJmb3JtYXQiOiJpbnQ2NCIsInR5cGUiOiJpbnRlZ2VyIn0sInVuY29tcHJlc3NlZEJ5dGVzIjp7ImRlc2NyaXB0aW9uIjoiVW5jb21wcmVzc2VkQnl0ZXMgaXMgdGhlIHNpemUgb2YgdGhlIHVuY29tcHJlc3NlZCBpbWFnZSBpbiBieXRlcyIsImZvcm1hdCI6ImludDY0IiwidHlwZSI6ImludGVnZXIifSwidmlydHVhbEJ5dGVzIjp7ImRlc2NyaXB0aW9uIjoiVmlydHVhbEJ5dGVzIGlzIHRoZSB2aXJ0dWFsIGRpc2sgc2l6ZSBmb3IgZGlzayBpbWFnZXMiLCJmb3JtYXQiOiJpbnQ2NCIsInR5cGUiOiJpbnRlZ2VyIn19LCJ0eXBlIjoib2JqZWN0In0sInRhZ3MiOnsiZGVzY3JpcHRpb24iOiJUYWdzIGFyZSBsYWJlbHMgdGhhdCBjYW4gYmUgdXNlZCB0byBjYXRlZ29yaXplIGFuZCBzZWFyY2ggaW1hZ2VzIiwiaXRlbXMiOnsidHlwZSI6InN0cmluZyJ9LCJ0eXBlIjoiYXJyYXkifSwidGFyZ2V0Ijp7ImRlc2NyaXB0aW9uIjoiVGFyZ2V0IHNwZWNpZmllcyB0aGUgYnVpbGQgdGFyZ2V0IiwidHlwZSI6InN0cmluZyJ9LCJ2ZXJzaW9uIjp7ImRlc2NyaXB0aW9uIjoiVmVyc2lvbiBzcGVjaWZpZXMgdGhlIHZlcnNpb24gb2YgdGhpcyBpbWFnZSIsInR5cGUiOiJzdHJpbmcifX0sInJlcXVpcmVkIjpbImFyY2hpdGVjdHVyZSIsImRpc3RybyIsImV4cG9ydEZvcm1hdCIsImxvY2F0aW9uIiwidGFyZ2V0Il0sInR5cGUiOiJvYmplY3QifSwic3RhdHVzIjp7ImRlc2NyaXB0aW9uIjoiSW1hZ2VTdGF0dXMgZGVmaW5lcyB0aGUgb2JzZXJ2ZWQgc3RhdGUgb2YgSW1hZ2UiLCJwcm9wZXJ0aWVzIjp7ImFjY2Vzc0NvdW50Ijp7ImRlc2NyaXB0aW9uIjoiQWNjZXNzQ291bnQgdHJhY2tzIGhvdyBtYW55IHRpbWVzIHRoaXMgaW1hZ2UgaGFzIGJlZW4gYWNjZXNzZWQvZG93bmxvYWRlZCIsImZvcm1hdCI6ImludDY0IiwidHlwZSI6ImludGVnZXIifSwiY29uZGl0aW9ucyI6eyJkZXNjcmlwdGlvbiI6IkNvbmRpdGlvbnMgcmVwcmVzZW50IHRoZSBsYXRlc3QgYXZhaWxhYmxlIG9ic2VydmF0aW9ucyBvZiB0aGUgaW1hZ2UncyBzdGF0ZSIsIml0ZW1zIjp7ImRlc2NyaXB0aW9uIjoiQ29uZGl0aW9uIGNvbnRhaW5zIGRldGFpbHMgZm9yIG9uZSBhc3BlY3Qgb2YgdGhlIGN1cnJlbnQgc3RhdGUgb2YgdGhpcyBBUEkgUmVzb3VyY2UuIiwicHJvcGVydGllcyI6eyJsYXN0VHJhbnNpdGlvblRpbWUiOnsiZGVzY3JpcHRpb24iOiJsYXN0VHJhbnNpdGlvblRpbWUgaXMgdGhlIGxhc3QgdGltZSB0aGUgY29uZGl0aW9uIHRyYW5zaXRpb25lZCBmcm9tIG9uZSBzdGF0dXMgdG8gYW5vdGhlci5cblRoaXMgc2hvdWxkIGJlIHdoZW4gdGhlIHVuZGVybHlpbmcgY29uZGl0aW9uIGNoYW5nZWQuICBJZiB0aGF0IGlzIG5vdCBrbm93biwgdGhlbiB1c2luZyB0aGUgdGltZSB3aGVuIHRoZSBBUEkgZmllbGQgY2hhbmdlZCBpcyBhY2NlcHRhYmxlLiIsImZvcm1hdCI6ImRhdGUtdGltZSIsInR5cGUiOiJzdHJpbmcifSwibWVzc2FnZSI6eyJkZXNjcmlwdGlvbiI6Im1lc3NhZ2UgaXMgYSBodW1hbiByZWFkYWJsZSBtZXNzYWdlIGluZGljYXRpbmcgZGV0YWlscyBhYm91dCB0aGUgdHJhbnNpdGlvbi5cblRoaXMgbWF5IGJlIGFuIGVtcHR5IHN0cmluZy4iLCJtYXhMZW5ndGgiOjMyNzY4LCJ0eXBlIjoic3RyaW5nIn0sIm9ic2VydmVkR2VuZXJhdGlvbiI6eyJkZXNjcmlwdGlvbiI6Im9ic2VydmVkR2VuZXJhdGlvbiByZXByZXNlbnRzIHRoZSAubWV0YWRhdGEuZ2VuZXJhdGlvbiB0aGF0IHRoZSBjb25kaXRpb24gd2FzIHNldCBiYXNlZCB1cG9uLlxuRm9yIGluc3RhbmNlLCBpZiAubWV0YWRhdGEuZ2VuZXJhdGlvbiBpcyBjdXJyZW50bHkgMTIsIGJ1dCB0aGUgLnN0YXR1cy5jb25kaXRpb25zW3hdLm9ic2VydmVkR2VuZXJhdGlvbiBpcyA5LCB0aGUgY29uZGl0aW9uIGlzIG91dCBvZiBkYXRlXG53aXRoIHJlc3BlY3QgdG8gdGhlIGN1cnJlbnQgc3RhdGUgb2YgdGhlIGluc3RhbmNlLiIsImZvcm1hdCI6ImludDY0IiwibWluaW11bSI6MCwidHlwZSI6ImludGVnZXIifSwicmVhc29uIjp7ImRlc2NyaXB0aW9uIjoicmVhc29uIGNvbnRhaW5zIGEgcHJvZ3JhbW1hdGljIGlkZW50aWZpZXIgaW5kaWNhdGluZyB0aGUgcmVhc29uIGZvciB0aGUgY29uZGl0aW9uJ3MgbGFzdCB0cmFuc2l0aW9uLlxuUHJvZHVjZXJzIG9mIHNwZWNpZmljIGNvbmRpdGlvbiB0eXBlcyBtYXkgZGVmaW5lIGV4cGVjdGVkIHZhbHVlcyBhbmQgbWVhbmluZ3MgZm9yIHRoaXMgZmllbGQsXG5hbmQgd2hldGhlciB0aGUgdmFsdWVzIGFyZSBjb25zaWRlcmVkIGEgZ3VhcmFudGVlZCBBUEkuXG5UaGUgdmFsdWUgc2hvdWxkIGJlIGEgQ2FtZWxDYXNlIHN0cmluZy5cblRoaXMgZmllbGQgbWF5IG5vdCBiZSBlbXB0eS4iLCJtYXhMZW5ndGgiOjEwMjQsIm1pbkxlbmd0aCI6MSwicGF0dGVybiI6Il5bQS1aYS16XShbQS1aYS16MC05Xyw6XSpbQS1aYS16MC05X10pPyQiLCJ0eXBlIjoic3RyaW5nIn0sInN0YXR1cyI6eyJkZXNjcmlwdGlvbiI6InN0YXR1cyBvZiB0aGUgY29uZGl0aW9uLCBvbmUgb2YgVHJ1ZSwgRmFsc2UsIFVua25vd24uIiwiZW51bSI6WyJUcnVlIiwiRmFsc2UiLCJVbmtub3duIl0sInR5cGUiOiJzdHJpbmcifSwidHlwZSI6eyJkZXNjcmlwdGlvbiI6InR5cGUgb2YgY29uZGl0aW9uIGluIENhbWVsQ2FzZSBvciBpbiBmb28uZXhhbXBsZS5jb20vQ2FtZWxDYXNlLiIsIm1heExlbmd0aCI6MzE2LCJwYXR0ZXJuIjoiXihbYS16MC05XShbLWEtejAtOV0qW2EtejAtOV0pPyhcXC5bYS16MC05XShbLWEtejAtOV0qW2EtejAtOV0pPykqLyk/KChbQS1aYS16MC05XVstQS1aYS16MC05Xy5dKik/W0EtWmEtejAtOV0pJCIsInR5cGUiOiJzdHJpbmcifX0sInJlcXVpcmVkIjpbImxhc3RUcmFuc2l0aW9uVGltZSIsIm1lc3NhZ2UiLCJyZWFzb24iLCJzdGF0dXMiLCJ0eXBlIl0sInR5cGUiOiJvYmplY3QifSwidHlwZSI6ImFycmF5In0sImxhc3RBY2Nlc3NlZCI6eyJkZXNjcmlwdGlvbiI6Ikxhc3RBY2Nlc3NlZCBpcyB3aGVuIHRoZSBpbWFnZSB3YXMgbGFzdCBhY2Nlc3NlZCIsImZvcm1hdCI6ImRhdGUtdGltZSIsInR5cGUiOiJzdHJpbmcifSwibGFzdFZlcmlmaWVkIjp7ImRlc2NyaXB0aW9uIjoiTGFzdFZlcmlmaWVkIGlzIHdoZW4gdGhlIGltYWdlIGxvY2F0aW9uIHdhcyBsYXN0IHZlcmlmaWVkIHRvIGJlIGFjY2Vzc2libGUiLCJmb3JtYXQiOiJkYXRlLXRpbWUiLCJ0eXBlIjoic3RyaW5nIn0sIm1lc3NhZ2UiOnsiZGVzY3JpcHRpb24iOiJNZXNzYWdlIHByb3ZpZGVzIG1vcmUgZGV0YWlsIGFib3V0IHRoZSBjdXJyZW50IHBoYXNlIiwidHlwZSI6InN0cmluZyJ9LCJvYnNlcnZlZEdlbmVyYXRpb24iOnsiZGVzY3JpcHRpb24iOiJPYnNlcnZlZEdlbmVyYXRpb24gaXMgdGhlIG1vc3QgcmVjZW50IGdlbmVyYXRpb24gb2JzZXJ2ZWQgYnkgdGhlIGNvbnRyb2xsZXIuIiwiZm9ybWF0IjoiaW50NjQiLCJ0eXBlIjoiaW50ZWdlciJ9LCJwaGFzZSI6eyJkZXNjcmlwdGlvbiI6IlBoYXNlIHJlcHJlc2VudHMgdGhlIGN1cnJlbnQgcGhhc2Ugb2YgdGhlIGltYWdlIChBdmFpbGFibGUsIFVuYXZhaWxhYmxlLCBWZXJpZnlpbmcpIiwidHlwZSI6InN0cmluZyJ9fSwidHlwZSI6Im9iamVjdCJ9fSwidHlwZSI6Im9iamVjdCJ9fSwic2VydmVkIjp0cnVlLCJzdG9yYWdlIjp0cnVlLCJzdWJyZXNvdXJjZXMiOnsic3RhdHVzIjp7fX19XX0sInN0YXR1cyI6eyJhY2NlcHRlZE5hbWVzIjp7ImtpbmQiOiIiLCJwbHVyYWwiOiIifSwiY29uZGl0aW9ucyI6bnVsbCwic3RvcmVkVmVyc2lvbnMiOm51bGx9fQ== @@ -48,7 +60,7 @@ properties: data: eyJhcGlWZXJzaW9uIjoiYXBpZXh0ZW5zaW9ucy5rOHMuaW8vdjEiLCJraW5kIjoiQ3VzdG9tUmVzb3VyY2VEZWZpbml0aW9uIiwibWV0YWRhdGEiOnsiYW5ub3RhdGlvbnMiOnsiY29udHJvbGxlci1nZW4ua3ViZWJ1aWxkZXIuaW8vdmVyc2lvbiI6InYwLjE5LjAifSwiY3JlYXRpb25UaW1lc3RhbXAiOm51bGwsIm5hbWUiOiJvcGVyYXRvcmNvbmZpZ3MuYXV0b21vdGl2ZS5zZHYuY2xvdWQucmVkaGF0LmNvbSJ9LCJzcGVjIjp7Imdyb3VwIjoiYXV0b21vdGl2ZS5zZHYuY2xvdWQucmVkaGF0LmNvbSIsIm5hbWVzIjp7ImtpbmQiOiJPcGVyYXRvckNvbmZpZyIsImxpc3RLaW5kIjoiT3BlcmF0b3JDb25maWdMaXN0IiwicGx1cmFsIjoib3BlcmF0b3Jjb25maWdzIiwic2luZ3VsYXIiOiJvcGVyYXRvcmNvbmZpZyJ9LCJzY29wZSI6Ik5hbWVzcGFjZWQiLCJ2ZXJzaW9ucyI6W3siYWRkaXRpb25hbFByaW50ZXJDb2x1bW5zIjpbeyJqc29uUGF0aCI6Ii5zcGVjLndlYlVJIiwibmFtZSI6IldlYlVJIiwidHlwZSI6ImJvb2xlYW4ifSx7Impzb25QYXRoIjoiLnNwZWMub3NCdWlsZHMuZW5hYmxlZCIsIm5hbWUiOiJPUyBCdWlsZHMiLCJ0eXBlIjoiYm9vbGVhbiJ9LHsianNvblBhdGgiOiIuc3RhdHVzLnBoYXNlIiwibmFtZSI6IlBoYXNlIiwidHlwZSI6InN0cmluZyJ9LHsianNvblBhdGgiOiIubWV0YWRhdGEuY3JlYXRpb25UaW1lc3RhbXAiLCJuYW1lIjoiQWdlIiwidHlwZSI6ImRhdGUifV0sIm5hbWUiOiJ2MWFscGhhMSIsInNjaGVtYSI6eyJvcGVuQVBJVjNTY2hlbWEiOnsiZGVzY3JpcHRpb24iOiJPcGVyYXRvckNvbmZpZyBpcyB0aGUgU2NoZW1hIGZvciB0aGUgb3BlcmF0b3Jjb25maWdzIEFQSSIsInByb3BlcnRpZXMiOnsiYXBpVmVyc2lvbiI6eyJkZXNjcmlwdGlvbiI6IkFQSVZlcnNpb24gZGVmaW5lcyB0aGUgdmVyc2lvbmVkIHNjaGVtYSBvZiB0aGlzIHJlcHJlc2VudGF0aW9uIG9mIGFuIG9iamVjdC5cblNlcnZlcnMgc2hvdWxkIGNvbnZlcnQgcmVjb2duaXplZCBzY2hlbWFzIHRvIHRoZSBsYXRlc3QgaW50ZXJuYWwgdmFsdWUsIGFuZFxubWF5IHJlamVjdCB1bnJlY29nbml6ZWQgdmFsdWVzLlxuTW9yZSBpbmZvOiBodHRwczovL2dpdC5rOHMuaW8vY29tbXVuaXR5L2NvbnRyaWJ1dG9ycy9kZXZlbC9zaWctYXJjaGl0ZWN0dXJlL2FwaS1jb252ZW50aW9ucy5tZCNyZXNvdXJjZXMiLCJ0eXBlIjoic3RyaW5nIn0sImtpbmQiOnsiZGVzY3JpcHRpb24iOiJLaW5kIGlzIGEgc3RyaW5nIHZhbHVlIHJlcHJlc2VudGluZyB0aGUgUkVTVCByZXNvdXJjZSB0aGlzIG9iamVjdCByZXByZXNlbnRzLlxuU2VydmVycyBtYXkgaW5mZXIgdGhpcyBmcm9tIHRoZSBlbmRwb2ludCB0aGUgY2xpZW50IHN1Ym1pdHMgcmVxdWVzdHMgdG8uXG5DYW5ub3QgYmUgdXBkYXRlZC5cbkluIENhbWVsQ2FzZS5cbk1vcmUgaW5mbzogaHR0cHM6Ly9naXQuazhzLmlvL2NvbW11bml0eS9jb250cmlidXRvcnMvZGV2ZWwvc2lnLWFyY2hpdGVjdHVyZS9hcGktY29udmVudGlvbnMubWQjdHlwZXMta2luZHMiLCJ0eXBlIjoic3RyaW5nIn0sIm1ldGFkYXRhIjp7InR5cGUiOiJvYmplY3QifSwic3BlYyI6eyJkZXNjcmlwdGlvbiI6Ik9wZXJhdG9yQ29uZmlnU3BlYyBkZWZpbmVzIHRoZSBkZXNpcmVkIHN0YXRlIG9mIE9wZXJhdG9yQ29uZmlnIiwicHJvcGVydGllcyI6eyJvc0J1aWxkcyI6eyJkZXNjcmlwdGlvbiI6Ik9TQnVpbGRzIGRlZmluZXMgdGhlIGNvbmZpZ3VyYXRpb24gZm9yIE9TIGJ1aWxkIG9wZXJhdGlvbnMiLCJwcm9wZXJ0aWVzIjp7ImNsdXN0ZXJSZWdpc3RyeVJvdXRlIjp7ImRlc2NyaXB0aW9uIjoiQ2x1c3RlclJlZ2lzdHJ5Um91dGUgaXMgdGhlIGV4dGVybmFsIHJvdXRlIGZvciB0aGUgY2x1c3RlcidzIGludGVybmFsIGltYWdlIHJlZ2lzdHJ5XG5SZXF1aXJlZCBmb3IgYm9vdGMgYnVpbGRzIHRvIGFsbG93IG5lc3RlZCBjb250YWluZXJzIHRvIHB1bGwgYnVpbGRlciBpbWFnZXNcbkV4YW1wbGU6IFwiZGVmYXVsdC1yb3V0ZS1vcGVuc2hpZnQtaW1hZ2UtcmVnaXN0cnkuYXBwcy5teWNsdXN0ZXIuZXhhbXBsZS5jb21cIiIsInR5cGUiOiJzdHJpbmcifSwiZW5hYmxlZCI6eyJkZWZhdWx0Ijp0cnVlLCJkZXNjcmlwdGlvbiI6IkVuYWJsZWQgZGV0ZXJtaW5lcyBpZiBUZWt0b24gdGFza3MgZm9yIE9TIGJ1aWxkcyBzaG91bGQgYmUgZGVwbG95ZWQiLCJ0eXBlIjoiYm9vbGVhbiJ9LCJtZW1vcnlWb2x1bWVTaXplIjp7ImRlc2NyaXB0aW9uIjoiTWVtb3J5Vm9sdW1lU2l6ZSBzcGVjaWZpZXMgdGhlIHNpemUgbGltaXQgZm9yIG1lbW9yeS1iYWNrZWQgdm9sdW1lcyAocmVxdWlyZWQgaWYgVXNlTWVtb3J5Vm9sdW1lcyBpcyB0cnVlKVxuRXhhbXBsZTogXCIyR2lcIiIsInR5cGUiOiJzdHJpbmcifSwicHZjU2l6ZSI6eyJkZXNjcmlwdGlvbiI6IlBWQ1NpemUgc3BlY2lmaWVzIHRoZSBzaXplIGZvciBwZXJzaXN0ZW50IHZvbHVtZSBjbGFpbXMgY3JlYXRlZCBmb3IgYnVpbGQgd29ya3NwYWNlc1xuRGVmYXVsdDogXCI4R2lcIiIsInR5cGUiOiJzdHJpbmcifSwicnVudGltZUNsYXNzTmFtZSI6eyJkZXNjcmlwdGlvbiI6IlJ1bnRpbWVDbGFzc05hbWUgc3BlY2lmaWVzIHRoZSBydW50aW1lIGNsYXNzIHRvIHVzZSBmb3IgdGhlIGJ1aWxkIHBvZFxuTW9yZSBpbmZvOiBodHRwczovL2t1YmVybmV0ZXMuaW8vZG9jcy9jb25jZXB0cy9jb250YWluZXJzL3J1bnRpbWUtY2xhc3MvIiwidHlwZSI6InN0cmluZyJ9LCJzZXJ2ZUV4cGlyeUhvdXJzIjp7ImRlc2NyaXB0aW9uIjoiU2VydmVFeHBpcnlIb3VycyBzcGVjaWZpZXMgaG93IGxvbmcgdG8gc2VydmUgYnVpbGQgYXJ0aWZhY3RzIGJlZm9yZSBhdXRvbWF0aWMgY2xlYW51cFxuRGVmYXVsdDogMjQiLCJmb3JtYXQiOiJpbnQzMiIsInR5cGUiOiJpbnRlZ2VyIn0sInVzZU1lbW9yeVZvbHVtZXMiOnsiZGVzY3JpcHRpb24iOiJVc2VNZW1vcnlWb2x1bWVzIGRldGVybWluZXMgd2hldGhlciB0byB1c2UgbWVtb3J5LWJhY2tlZCB2b2x1bWVzIGZvciBidWlsZCBvcGVyYXRpb25zIiwidHlwZSI6ImJvb2xlYW4ifX0sInJlcXVpcmVkIjpbImVuYWJsZWQiXSwidHlwZSI6Im9iamVjdCJ9LCJ3ZWJVSSI6eyJkZWZhdWx0Ijp0cnVlLCJkZXNjcmlwdGlvbiI6IldlYlVJIGRldGVybWluZXMgaWYgdGhlIHdlYiBVSSBzaG91bGQgYmUgZGVwbG95ZWQiLCJ0eXBlIjoiYm9vbGVhbiJ9fSwicmVxdWlyZWQiOlsid2ViVUkiXSwidHlwZSI6Im9iamVjdCJ9LCJzdGF0dXMiOnsiZGVzY3JpcHRpb24iOiJPcGVyYXRvckNvbmZpZ1N0YXR1cyBkZWZpbmVzIHRoZSBvYnNlcnZlZCBzdGF0ZSBvZiBPcGVyYXRvckNvbmZpZyIsInByb3BlcnRpZXMiOnsibWVzc2FnZSI6eyJkZXNjcmlwdGlvbiI6Ik1lc3NhZ2UgcHJvdmlkZXMgZGV0YWlsIGFib3V0IHRoZSBjdXJyZW50IHBoYXNlIiwidHlwZSI6InN0cmluZyJ9LCJvYnNlcnZlZEdlbmVyYXRpb24iOnsiZGVzY3JpcHRpb24iOiJPYnNlcnZlZEdlbmVyYXRpb24gaXMgdGhlIG1vc3QgcmVjZW50IGdlbmVyYXRpb24gb2JzZXJ2ZWQgYnkgdGhlIGNvbnRyb2xsZXIuIiwiZm9ybWF0IjoiaW50NjQiLCJ0eXBlIjoiaW50ZWdlciJ9LCJvc0J1aWxkc0RlcGxveWVkIjp7ImRlc2NyaXB0aW9uIjoiT1NCdWlsZHNEZXBsb3llZCBpbmRpY2F0ZXMgaWYgdGhlIE9TIEJ1aWxkcyBUZWt0b24gdGFza3MgYXJlIGN1cnJlbnRseSBkZXBsb3llZCIsInR5cGUiOiJib29sZWFuIn0sInBoYXNlIjp7ImRlc2NyaXB0aW9uIjoiUGhhc2UgcmVwcmVzZW50cyB0aGUgY3VycmVudCBwaGFzZSAoUmVhZHksIFJlY29uY2lsaW5nLCBGYWlsZWQpIiwidHlwZSI6InN0cmluZyJ9LCJ3ZWJVSURlcGxveWVkIjp7ImRlc2NyaXB0aW9uIjoiV2ViVUlEZXBsb3llZCBpbmRpY2F0ZXMgaWYgdGhlIFdlYlVJIGlzIGN1cnJlbnRseSBkZXBsb3llZCIsInR5cGUiOiJib29sZWFuIn19LCJ0eXBlIjoib2JqZWN0In19LCJ0eXBlIjoib2JqZWN0In19LCJzZXJ2ZWQiOnRydWUsInN0b3JhZ2UiOnRydWUsInN1YnJlc291cmNlcyI6eyJzdGF0dXMiOnt9fX1dfSwic3RhdHVzIjp7ImFjY2VwdGVkTmFtZXMiOnsia2luZCI6IiIsInBsdXJhbCI6IiJ9LCJjb25kaXRpb25zIjpudWxsLCJzdG9yZWRWZXJzaW9ucyI6bnVsbH19 - type: olm.bundle.object value: - data: eyJhcGlWZXJzaW9uIjoib3BlcmF0b3JzLmNvcmVvcy5jb20vdjFhbHBoYTEiLCJraW5kIjoiQ2x1c3RlclNlcnZpY2VWZXJzaW9uIiwibWV0YWRhdGEiOnsiYW5ub3RhdGlvbnMiOnsiYWxtLWV4YW1wbGVzIjoiW1xuICB7XG4gICAgXCJhcGlWZXJzaW9uXCI6IFwiYXV0b21vdGl2ZS5zZHYuY2xvdWQucmVkaGF0LmNvbS92MWFscGhhMVwiLFxuICAgIFwia2luZFwiOiBcIkltYWdlQnVpbGRcIixcbiAgICBcIm1ldGFkYXRhXCI6IHtcbiAgICAgIFwiYW5ub3RhdGlvbnNcIjoge1xuICAgICAgICBcImRlc2NyaXB0aW9uXCI6IFwiRXhhbXBsZSBJbWFnZUJ1aWxkIENSLiBUaGUgYXJjaGl0ZWN0dXJlIGNhbiBiZSBzZXQgdG8gYW55IHN1cHBvcnRlZCB2YWx1ZSBsaWtlIFxcXCJhYXJjaDY0XFxcIiwgXFxcIng4Nl82NFxcXCIsIGV0Yy4gU2VlIHlvdXIgYnVpbGQgcGxhdGZvcm0ncyBkb2N1bWVudGF0aW9uIGZvciBzdXBwb3J0ZWQgdmFsdWVzLlxcblwiXG4gICAgICB9LFxuICAgICAgXCJsYWJlbHNcIjoge1xuICAgICAgICBcImFwcC5rdWJlcm5ldGVzLmlvL21hbmFnZWQtYnlcIjogXCJrdXN0b21pemVcIixcbiAgICAgICAgXCJhcHAua3ViZXJuZXRlcy5pby9uYW1lXCI6IFwiYXV0b21vdGl2ZS1kZXYtb3BlcmF0b3JcIlxuICAgICAgfSxcbiAgICAgIFwibmFtZVwiOiBcImltYWdlYnVpbGQtc2FtcGxlXCJcbiAgICB9LFxuICAgIFwic3BlY1wiOiB7XG4gICAgICBcImFyY2hpdGVjdHVyZVwiOiBcImFtZDY0XCIsXG4gICAgICBcImF1dG9tb3RpdmVJbWFnZUJ1aWxkZXJcIjogXCJxdWF5LmlvL2NlbnRvcy1zaWctYXV0b21vdGl2ZS9hdXRvbW90aXZlLWltYWdlLWJ1aWxkZXI6MS4wLjBcIixcbiAgICAgIFwiZGlzdHJvXCI6IFwiY3M5XCIsXG4gICAgICBcImV4cG9ydEZvcm1hdFwiOiBcInFjb3cyXCIsXG4gICAgICBcIm1hbmlmZXN0Q29uZmlnTWFwXCI6IFwibXBwXCIsXG4gICAgICBcIm1vZGVcIjogXCJpbWFnZVwiLFxuICAgICAgXCJzZXJ2ZUFydGlmYWN0XCI6IGZhbHNlLFxuICAgICAgXCJzZXJ2ZUV4cGlyeUhvdXJzXCI6IDI0LFxuICAgICAgXCJ0YXJnZXRcIjogXCJxZW11XCJcbiAgICB9XG4gIH0sXG4gIHtcbiAgICBcImFwaVZlcnNpb25cIjogXCJhdXRvbW90aXZlLnNkdi5jbG91ZC5yZWRoYXQuY29tL3YxYWxwaGExXCIsXG4gICAgXCJraW5kXCI6IFwiT3BlcmF0b3JDb25maWdcIixcbiAgICBcIm1ldGFkYXRhXCI6IHtcbiAgICAgIFwibGFiZWxzXCI6IHtcbiAgICAgICAgXCJhcHAua3ViZXJuZXRlcy5pby9tYW5hZ2VkLWJ5XCI6IFwia3VzdG9taXplXCIsXG4gICAgICAgIFwiYXBwLmt1YmVybmV0ZXMuaW8vbmFtZVwiOiBcImF1dG9tb3RpdmUtZGV2LW9wZXJhdG9yXCJcbiAgICAgIH0sXG4gICAgICBcIm5hbWVcIjogXCJhdXRvbW90aXZlLWRldlwiLFxuICAgICAgXCJuYW1lc3BhY2VcIjogXCJhdXRvbW90aXZlLWRldi1vcGVyYXRvci1zeXN0ZW1cIlxuICAgIH0sXG4gICAgXCJzcGVjXCI6IHtcbiAgICAgIFwib3NCdWlsZHNcIjoge1xuICAgICAgICBcImVuYWJsZWRcIjogdHJ1ZSxcbiAgICAgICAgXCJwdmNTaXplXCI6IFwiOEdpXCIsXG4gICAgICAgIFwic2VydmVFeHBpcnlIb3Vyc1wiOiAyNFxuICAgICAgfSxcbiAgICAgIFwid2ViVUlcIjogdHJ1ZVxuICAgIH1cbiAgfVxuXSIsImNhcGFiaWxpdGllcyI6IkJhc2ljIEluc3RhbGwiLCJjYXRlZ29yaWVzIjoiRGV2ZWxvcGVyIFRvb2xzIiwiY29udGFpbmVySW1hZ2UiOiJxdWF5LmlvL3JoLXNkdi1jbG91ZC9hdXRvbW90aXZlLWRldi1vcGVyYXRvcjpsYXRlc3QiLCJjcmVhdGVkQXQiOiIyMDI2LTAxLTA0VDE2OjAyOjQxWiIsIm9wZXJhdG9yZnJhbWV3b3JrLmlvL3N1Z2dlc3RlZC1uYW1lc3BhY2UiOiJhdXRvbW90aXZlLWRldi1vcGVyYXRvci1zeXN0ZW0iLCJvcGVyYXRvcnMub3BlcmF0b3JmcmFtZXdvcmsuaW8vYnVpbGRlciI6Im9wZXJhdG9yLXNkay12MS40Mi4wIiwib3BlcmF0b3JzLm9wZXJhdG9yZnJhbWV3b3JrLmlvL3Byb2plY3RfbGF5b3V0IjoiZ28ua3ViZWJ1aWxkZXIuaW8vdjQiLCJyZXBvc2l0b3J5IjoiaHR0cHM6Ly9naXRodWIuY29tL2NlbnRvcy1hdXRvbW90aXZlLXN1aXRlL2F1dG9tb3RpdmUtZGV2LW9wZXJhdG9yIiwic3VwcG9ydCI6IlJlZCBIYXQifSwibmFtZSI6ImF1dG9tb3RpdmUtZGV2LW9wZXJhdG9yLnYwLjAuMSIsIm5hbWVzcGFjZSI6InBsYWNlaG9sZGVyIn0sInNwZWMiOnsiYXBpc2VydmljZWRlZmluaXRpb25zIjp7fSwiY3VzdG9tcmVzb3VyY2VkZWZpbml0aW9ucyI6eyJvd25lZCI6W3siZGVzY3JpcHRpb24iOiJJbWFnZUJ1aWxkIGlzIHRoZSBTY2hlbWEgZm9yIHRoZSBpbWFnZWJ1aWxkcyBBUEkiLCJkaXNwbGF5TmFtZSI6IkltYWdlIEJ1aWxkIiwia2luZCI6IkltYWdlQnVpbGQiLCJuYW1lIjoiaW1hZ2VidWlsZHMuYXV0b21vdGl2ZS5zZHYuY2xvdWQucmVkaGF0LmNvbSIsInZlcnNpb24iOiJ2MWFscGhhMSJ9LHsiZGVzY3JpcHRpb24iOiJJbWFnZSBpcyB0aGUgU2NoZW1hIGZvciB0aGUgaW1hZ2VzIEFQSSIsImRpc3BsYXlOYW1lIjoiSW1hZ2UiLCJraW5kIjoiSW1hZ2UiLCJuYW1lIjoiaW1hZ2VzLmF1dG9tb3RpdmUuc2R2LmNsb3VkLnJlZGhhdC5jb20iLCJ2ZXJzaW9uIjoidjFhbHBoYTEifSx7ImRlc2NyaXB0aW9uIjoiT3BlcmF0b3JDb25maWcgaXMgdGhlIFNjaGVtYSBmb3IgdGhlIG9wZXJhdG9yY29uZmlncyBBUEkiLCJkaXNwbGF5TmFtZSI6Ik9wZXJhdG9yIENvbmZpZyIsImtpbmQiOiJPcGVyYXRvckNvbmZpZyIsIm5hbWUiOiJvcGVyYXRvcmNvbmZpZ3MuYXV0b21vdGl2ZS5zZHYuY2xvdWQucmVkaGF0LmNvbSIsInZlcnNpb24iOiJ2MWFscGhhMSJ9XX0sImRlc2NyaXB0aW9uIjoiQ2VudE9TIEF1dG9tb3RpdmUgU3VpdGUiLCJkaXNwbGF5TmFtZSI6IkNlbnRPUyBBdXRvbW90aXZlIFN1aXRlIiwiaWNvbiI6W3siYmFzZTY0ZGF0YSI6IiIsIm1lZGlhdHlwZSI6IiJ9XSwiaW5zdGFsbCI6eyJzcGVjIjp7ImNsdXN0ZXJQZXJtaXNzaW9ucyI6W3sicnVsZXMiOlt7ImFwaUdyb3VwcyI6WyIiXSwicmVzb3VyY2VzIjpbImNvbmZpZ21hcHMiLCJwZXJzaXN0ZW50dm9sdW1lY2xhaW1zIiwicG9kcyIsInNlY3JldHMiLCJzZXJ2aWNlYWNjb3VudHMiLCJzZXJ2aWNlcyJdLCJ2ZXJicyI6WyJjcmVhdGUiLCJkZWxldGUiLCJnZXQiLCJsaXN0IiwicGF0Y2giLCJ1cGRhdGUiLCJ3YXRjaCJdfSx7ImFwaUdyb3VwcyI6WyIiXSwicmVzb3VyY2VzIjpbImV2ZW50cyJdLCJ2ZXJicyI6WyJjcmVhdGUiLCJwYXRjaCJdfSx7ImFwaUdyb3VwcyI6WyIiXSwicmVzb3VyY2VzIjpbIm5hbWVzcGFjZXMiXSwidmVyYnMiOlsiY3JlYXRlIiwiZ2V0IiwibGlzdCIsIndhdGNoIl19LHsiYXBpR3JvdXBzIjpbIiJdLCJyZXNvdXJjZXMiOlsicG9kcy9leGVjIl0sInZlcmJzIjpbImNyZWF0ZSJdfSx7ImFwaUdyb3VwcyI6WyIiXSwicmVzb3VyY2VzIjpbInBvZHMvbG9nIl0sInZlcmJzIjpbImdldCJdfSx7ImFwaUdyb3VwcyI6WyJhcHBzIl0sInJlc291cmNlcyI6WyJkZXBsb3ltZW50cyJdLCJ2ZXJicyI6WyJjcmVhdGUiLCJkZWxldGUiLCJnZXQiLCJsaXN0IiwicGF0Y2giLCJ1cGRhdGUiLCJ3YXRjaCJdfSx7ImFwaUdyb3VwcyI6WyJhdXRvbW90aXZlLnNkdi5jbG91ZC5yZWRoYXQuY29tIl0sInJlc291cmNlcyI6WyJjYXRhbG9naW1hZ2VzIiwiaW1hZ2VidWlsZHMiLCJpbWFnZXMiLCJvcGVyYXRvcmNvbmZpZ3MiXSwidmVyYnMiOlsiY3JlYXRlIiwiZGVsZXRlIiwiZ2V0IiwibGlzdCIsInBhdGNoIiwidXBkYXRlIiwid2F0Y2giXX0seyJhcGlHcm91cHMiOlsiYXV0b21vdGl2ZS5zZHYuY2xvdWQucmVkaGF0LmNvbSJdLCJyZXNvdXJjZXMiOlsiY2F0YWxvZ2ltYWdlcy9maW5hbGl6ZXJzIiwiaW1hZ2VidWlsZHMvZmluYWxpemVycyIsImltYWdlcy9maW5hbGl6ZXJzIiwib3BlcmF0b3Jjb25maWdzL2ZpbmFsaXplcnMiXSwidmVyYnMiOlsidXBkYXRlIl19LHsiYXBpR3JvdXBzIjpbImF1dG9tb3RpdmUuc2R2LmNsb3VkLnJlZGhhdC5jb20iXSwicmVzb3VyY2VzIjpbImNhdGFsb2dpbWFnZXMvc3RhdHVzIiwiaW1hZ2VidWlsZHMvc3RhdHVzIiwiaW1hZ2VzL3N0YXR1cyIsIm9wZXJhdG9yY29uZmlncy9zdGF0dXMiXSwidmVyYnMiOlsiZ2V0IiwicGF0Y2giLCJ1cGRhdGUiXX0seyJhcGlHcm91cHMiOlsibmV0d29ya2luZy5rOHMuaW8iXSwicmVzb3VyY2VzIjpbImluZ3Jlc3NlcyJdLCJ2ZXJicyI6WyJjcmVhdGUiLCJkZWxldGUiLCJnZXQiLCJsaXN0IiwicGF0Y2giLCJ1cGRhdGUiLCJ3YXRjaCJdfSx7ImFwaUdyb3VwcyI6WyJyYmFjLmF1dGhvcml6YXRpb24uazhzLmlvIl0sInJlc291cmNlcyI6WyJjbHVzdGVycm9sZWJpbmRpbmdzIiwiY2x1c3RlcnJvbGVzIiwicm9sZWJpbmRpbmdzIl0sInZlcmJzIjpbImNyZWF0ZSIsImRlbGV0ZSIsImdldCIsImxpc3QiLCJwYXRjaCIsInVwZGF0ZSIsIndhdGNoIl19LHsiYXBpR3JvdXBzIjpbInJvdXRlLm9wZW5zaGlmdC5pbyJdLCJyZXNvdXJjZXMiOlsicm91dGVzIl0sInZlcmJzIjpbImNyZWF0ZSIsImRlbGV0ZSIsImdldCIsImxpc3QiLCJwYXRjaCIsInVwZGF0ZSIsIndhdGNoIl19LHsiYXBpR3JvdXBzIjpbInNlY3VyaXR5Lm9wZW5zaGlmdC5pbyJdLCJyZXNvdXJjZXMiOlsic2VjdXJpdHljb250ZXh0Y29uc3RyYWludHMiXSwidmVyYnMiOlsiY3JlYXRlIiwiZGVsZXRlIiwiZ2V0IiwibGlzdCIsInBhdGNoIiwidXBkYXRlIiwidXNlIiwid2F0Y2giXX0seyJhcGlHcm91cHMiOlsidGVrdG9uLmRldiJdLCJyZXNvdXJjZXMiOlsicGlwZWxpbmVydW5zIiwicGlwZWxpbmVzIiwidGFza3J1bnMiLCJ0YXNrcyJdLCJ2ZXJicyI6WyJjcmVhdGUiLCJkZWxldGUiLCJnZXQiLCJsaXN0IiwicGF0Y2giLCJ1cGRhdGUiLCJ3YXRjaCJdfV0sInNlcnZpY2VBY2NvdW50TmFtZSI6ImFkby1jb250cm9sbGVyLW1hbmFnZXIifV0sImRlcGxveW1lbnRzIjpbeyJsYWJlbCI6eyJhcHAua3ViZXJuZXRlcy5pby9tYW5hZ2VkLWJ5Ijoia3VzdG9taXplIiwiYXBwLmt1YmVybmV0ZXMuaW8vbmFtZSI6ImF1dG9tb3RpdmUtZGV2LW9wZXJhdG9yIiwiY29udHJvbC1wbGFuZSI6ImNvbnRyb2xsZXItbWFuYWdlciJ9LCJuYW1lIjoiYWRvLWNvbnRyb2xsZXItbWFuYWdlciIsInNwZWMiOnsicmVwbGljYXMiOjEsInNlbGVjdG9yIjp7Im1hdGNoTGFiZWxzIjp7ImNvbnRyb2wtcGxhbmUiOiJjb250cm9sbGVyLW1hbmFnZXIifX0sInN0cmF0ZWd5Ijp7fSwidGVtcGxhdGUiOnsibWV0YWRhdGEiOnsiYW5ub3RhdGlvbnMiOnsia3ViZWN0bC5rdWJlcm5ldGVzLmlvL2RlZmF1bHQtY29udGFpbmVyIjoibWFuYWdlciJ9LCJsYWJlbHMiOnsiY29udHJvbC1wbGFuZSI6ImNvbnRyb2xsZXItbWFuYWdlciJ9fSwic3BlYyI6eyJhZmZpbml0eSI6eyJub2RlQWZmaW5pdHkiOnsicmVxdWlyZWREdXJpbmdTY2hlZHVsaW5nSWdub3JlZER1cmluZ0V4ZWN1dGlvbiI6eyJub2RlU2VsZWN0b3JUZXJtcyI6W3sibWF0Y2hFeHByZXNzaW9ucyI6W3sia2V5Ijoia3ViZXJuZXRlcy5pby9hcmNoIiwib3BlcmF0b3IiOiJJbiIsInZhbHVlcyI6WyJhcm02NCIsImFtZDY0Il19LHsia2V5Ijoia3ViZXJuZXRlcy5pby9vcyIsIm9wZXJhdG9yIjoiSW4iLCJ2YWx1ZXMiOlsibGludXgiXX1dfV19fX0sImNvbnRhaW5lcnMiOlt7ImFyZ3MiOlsiLS1sZWFkZXItZWxlY3QiLCItLWhlYWx0aC1wcm9iZS1iaW5kLWFkZHJlc3M9OjgwODEiLCItLW1ldHJpY3MtYmluZC1hZGRyZXNzPTAiXSwiY29tbWFuZCI6WyIvbWFuYWdlciJdLCJlbnYiOlt7Im5hbWUiOiJCVUlMRF9BUElfTkFNRVNQQUNFIiwidmFsdWVGcm9tIjp7ImZpZWxkUmVmIjp7ImZpZWxkUGF0aCI6Im1ldGFkYXRhLm5hbWVzcGFjZSJ9fX0seyJuYW1lIjoiT1BFUkFUT1JfSU1BR0UiLCJ2YWx1ZSI6ImltYWdlLXJlZ2lzdHJ5Lm9wZW5zaGlmdC1pbWFnZS1yZWdpc3RyeS5zdmM6NTAwMC9hdXRvbW90aXZlLWRldi1vcGVyYXRvci1zeXN0ZW0vYXV0b21vdGl2ZS1kZXYtb3BlcmF0b3I6bGF0ZXN0In1dLCJpbWFnZSI6ImRlZmF1bHQtcm91dGUtb3BlbnNoaWZ0LWltYWdlLXJlZ2lzdHJ5LmFwcHMuYXV0b21vdGl2ZTEub2NwLmF1dG9tb3RpdmUuc2lnLmNlbnRvcy5vcmcvYXV0b21vdGl2ZS1kZXYtb3BlcmF0b3Itc3lzdGVtL2F1dG9tb3RpdmUtZGV2LW9wZXJhdG9yOmxhdGVzdCIsImltYWdlUHVsbFBvbGljeSI6IklmTm90UHJlc2VudCIsImxpdmVuZXNzUHJvYmUiOnsiaHR0cEdldCI6eyJwYXRoIjoiL2hlYWx0aHoiLCJwb3J0Ijo4MDgxfSwiaW5pdGlhbERlbGF5U2Vjb25kcyI6MTUsInBlcmlvZFNlY29uZHMiOjIwfSwibmFtZSI6Im1hbmFnZXIiLCJwb3J0cyI6W3siY29udGFpbmVyUG9ydCI6ODA4MCwibmFtZSI6ImJ1aWxkLWFwaSJ9XSwicmVhZGluZXNzUHJvYmUiOnsiaHR0cEdldCI6eyJwYXRoIjoiL3JlYWR5eiIsInBvcnQiOjgwODF9LCJpbml0aWFsRGVsYXlTZWNvbmRzIjo1LCJwZXJpb2RTZWNvbmRzIjoxMH0sInJlc291cmNlcyI6eyJsaW1pdHMiOnsiY3B1IjoiMSIsIm1lbW9yeSI6IjUxMk1pIn0sInJlcXVlc3RzIjp7ImNwdSI6IjEwMG0iLCJtZW1vcnkiOiIyNTZNaSJ9fSwic2VjdXJpdHlDb250ZXh0Ijp7ImFsbG93UHJpdmlsZWdlRXNjYWxhdGlvbiI6ZmFsc2UsImNhcGFiaWxpdGllcyI6eyJkcm9wIjpbIkFMTCJdfX19XSwiaW5pdENvbnRhaW5lcnMiOlt7ImNvbW1hbmQiOlsiL2luaXQtc2VjcmV0cyJdLCJlbnYiOlt7Im5hbWUiOiJQT0RfTkFNRVNQQUNFIiwidmFsdWVGcm9tIjp7ImZpZWxkUmVmIjp7ImZpZWxkUGF0aCI6Im1ldGFkYXRhLm5hbWVzcGFjZSJ9fX1dLCJpbWFnZSI6ImRlZmF1bHQtcm91dGUtb3BlbnNoaWZ0LWltYWdlLXJlZ2lzdHJ5LmFwcHMuYXV0b21vdGl2ZTEub2NwLmF1dG9tb3RpdmUuc2lnLmNlbnRvcy5vcmcvYXV0b21vdGl2ZS1kZXYtb3BlcmF0b3Itc3lzdGVtL2F1dG9tb3RpdmUtZGV2LW9wZXJhdG9yOmxhdGVzdCIsImltYWdlUHVsbFBvbGljeSI6IklmTm90UHJlc2VudCIsIm5hbWUiOiJpbml0LW9hdXRoLXNlY3JldHMiLCJyZXNvdXJjZXMiOnsibGltaXRzIjp7ImNwdSI6IjEwMG0iLCJtZW1vcnkiOiI2NE1pIn0sInJlcXVlc3RzIjp7ImNwdSI6IjEwbSIsIm1lbW9yeSI6IjMyTWkifX0sInNlY3VyaXR5Q29udGV4dCI6eyJhbGxvd1ByaXZpbGVnZUVzY2FsYXRpb24iOmZhbHNlLCJjYXBhYmlsaXRpZXMiOnsiZHJvcCI6WyJBTEwiXX19fV0sInNlY3VyaXR5Q29udGV4dCI6eyJydW5Bc05vblJvb3QiOnRydWV9LCJzZXJ2aWNlQWNjb3VudE5hbWUiOiJhZG8tY29udHJvbGxlci1tYW5hZ2VyIiwidGVybWluYXRpb25HcmFjZVBlcmlvZFNlY29uZHMiOjEwfX19fV0sInBlcm1pc3Npb25zIjpbeyJydWxlcyI6W3siYXBpR3JvdXBzIjpbIiJdLCJyZXNvdXJjZXMiOlsiY29uZmlnbWFwcyJdLCJ2ZXJicyI6WyJnZXQiLCJsaXN0Iiwid2F0Y2giLCJjcmVhdGUiLCJ1cGRhdGUiLCJwYXRjaCIsImRlbGV0ZSJdfSx7ImFwaUdyb3VwcyI6WyJjb29yZGluYXRpb24uazhzLmlvIl0sInJlc291cmNlcyI6WyJsZWFzZXMiXSwidmVyYnMiOlsiZ2V0IiwibGlzdCIsIndhdGNoIiwiY3JlYXRlIiwidXBkYXRlIiwicGF0Y2giLCJkZWxldGUiXX0seyJhcGlHcm91cHMiOlsiIl0sInJlc291cmNlcyI6WyJldmVudHMiXSwidmVyYnMiOlsiY3JlYXRlIiwicGF0Y2giXX1dLCJzZXJ2aWNlQWNjb3VudE5hbWUiOiJhZG8tY29udHJvbGxlci1tYW5hZ2VyIn1dfSwic3RyYXRlZ3kiOiJkZXBsb3ltZW50In0sImluc3RhbGxNb2RlcyI6W3sic3VwcG9ydGVkIjp0cnVlLCJ0eXBlIjoiT3duTmFtZXNwYWNlIn0seyJzdXBwb3J0ZWQiOnRydWUsInR5cGUiOiJTaW5nbGVOYW1lc3BhY2UifSx7InN1cHBvcnRlZCI6ZmFsc2UsInR5cGUiOiJNdWx0aU5hbWVzcGFjZSJ9LHsic3VwcG9ydGVkIjpmYWxzZSwidHlwZSI6IkFsbE5hbWVzcGFjZXMifV0sImtleXdvcmRzIjpbImF1dG9tb3RpdmUiXSwibGlua3MiOlt7Im5hbWUiOiJDZW50T1MgQXV0b21vdGl2ZSBTdWl0ZSIsInVybCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9jZW50b3MtYXV0b21vdGl2ZS1zdWl0ZS9hdXRvbW90aXZlLWRldi1vcGVyYXRvciJ9XSwibWF0dXJpdHkiOiJhbHBoYSIsIm1pbkt1YmVWZXJzaW9uIjoiMS4yNi4wIiwicHJvdmlkZXIiOnsibmFtZSI6IlJlZCBIYXQifSwidmVyc2lvbiI6IjAuMC4xIn19 + data: eyJhcGlWZXJzaW9uIjoib3BlcmF0b3JzLmNvcmVvcy5jb20vdjFhbHBoYTEiLCJraW5kIjoiQ2x1c3RlclNlcnZpY2VWZXJzaW9uIiwibWV0YWRhdGEiOnsiYW5ub3RhdGlvbnMiOnsiYWxtLWV4YW1wbGVzIjoiW1xuICB7XG4gICAgXCJhcGlWZXJzaW9uXCI6IFwiYXV0b21vdGl2ZS5zZHYuY2xvdWQucmVkaGF0LmNvbS92MWFscGhhMVwiLFxuICAgIFwia2luZFwiOiBcIkltYWdlQnVpbGRcIixcbiAgICBcIm1ldGFkYXRhXCI6IHtcbiAgICAgIFwiYW5ub3RhdGlvbnNcIjoge1xuICAgICAgICBcImRlc2NyaXB0aW9uXCI6IFwiRXhhbXBsZSBJbWFnZUJ1aWxkIENSLiBUaGUgYXJjaGl0ZWN0dXJlIGNhbiBiZSBzZXQgdG8gYW55IHN1cHBvcnRlZCB2YWx1ZSBsaWtlIFxcXCJhYXJjaDY0XFxcIiwgXFxcIng4Nl82NFxcXCIsIGV0Yy4gU2VlIHlvdXIgYnVpbGQgcGxhdGZvcm0ncyBkb2N1bWVudGF0aW9uIGZvciBzdXBwb3J0ZWQgdmFsdWVzLlxcblwiXG4gICAgICB9LFxuICAgICAgXCJsYWJlbHNcIjoge1xuICAgICAgICBcImFwcC5rdWJlcm5ldGVzLmlvL21hbmFnZWQtYnlcIjogXCJrdXN0b21pemVcIixcbiAgICAgICAgXCJhcHAua3ViZXJuZXRlcy5pby9uYW1lXCI6IFwiYXV0b21vdGl2ZS1kZXYtb3BlcmF0b3JcIlxuICAgICAgfSxcbiAgICAgIFwibmFtZVwiOiBcImltYWdlYnVpbGQtc2FtcGxlXCJcbiAgICB9LFxuICAgIFwic3BlY1wiOiB7XG4gICAgICBcImFyY2hpdGVjdHVyZVwiOiBcImFtZDY0XCIsXG4gICAgICBcImF1dG9tb3RpdmVJbWFnZUJ1aWxkZXJcIjogXCJxdWF5LmlvL2NlbnRvcy1zaWctYXV0b21vdGl2ZS9hdXRvbW90aXZlLWltYWdlLWJ1aWxkZXI6MS4wLjBcIixcbiAgICAgIFwiZGlzdHJvXCI6IFwiY3M5XCIsXG4gICAgICBcImV4cG9ydEZvcm1hdFwiOiBcInFjb3cyXCIsXG4gICAgICBcIm1hbmlmZXN0Q29uZmlnTWFwXCI6IFwibXBwXCIsXG4gICAgICBcIm1vZGVcIjogXCJpbWFnZVwiLFxuICAgICAgXCJzZXJ2ZUFydGlmYWN0XCI6IGZhbHNlLFxuICAgICAgXCJzZXJ2ZUV4cGlyeUhvdXJzXCI6IDI0LFxuICAgICAgXCJ0YXJnZXRcIjogXCJxZW11XCJcbiAgICB9XG4gIH0sXG4gIHtcbiAgICBcImFwaVZlcnNpb25cIjogXCJhdXRvbW90aXZlLnNkdi5jbG91ZC5yZWRoYXQuY29tL3YxYWxwaGExXCIsXG4gICAgXCJraW5kXCI6IFwiT3BlcmF0b3JDb25maWdcIixcbiAgICBcIm1ldGFkYXRhXCI6IHtcbiAgICAgIFwibGFiZWxzXCI6IHtcbiAgICAgICAgXCJhcHAua3ViZXJuZXRlcy5pby9tYW5hZ2VkLWJ5XCI6IFwia3VzdG9taXplXCIsXG4gICAgICAgIFwiYXBwLmt1YmVybmV0ZXMuaW8vbmFtZVwiOiBcImF1dG9tb3RpdmUtZGV2LW9wZXJhdG9yXCJcbiAgICAgIH0sXG4gICAgICBcIm5hbWVcIjogXCJhdXRvbW90aXZlLWRldlwiLFxuICAgICAgXCJuYW1lc3BhY2VcIjogXCJhdXRvbW90aXZlLWRldi1vcGVyYXRvci1zeXN0ZW1cIlxuICAgIH0sXG4gICAgXCJzcGVjXCI6IHtcbiAgICAgIFwib3NCdWlsZHNcIjoge1xuICAgICAgICBcImVuYWJsZWRcIjogdHJ1ZSxcbiAgICAgICAgXCJwdmNTaXplXCI6IFwiOEdpXCIsXG4gICAgICAgIFwic2VydmVFeHBpcnlIb3Vyc1wiOiAyNFxuICAgICAgfSxcbiAgICAgIFwid2ViVUlcIjogdHJ1ZVxuICAgIH1cbiAgfVxuXSIsImNhcGFiaWxpdGllcyI6IkJhc2ljIEluc3RhbGwiLCJjYXRlZ29yaWVzIjoiRGV2ZWxvcGVyIFRvb2xzIiwiY29udGFpbmVySW1hZ2UiOiJxdWF5LmlvL3JoLXNkdi1jbG91ZC9hdXRvbW90aXZlLWRldi1vcGVyYXRvcjpsYXRlc3QiLCJjcmVhdGVkQXQiOiIyMDI2LTAxLTA4VDA5OjQ3OjE1WiIsIm9wZXJhdG9yZnJhbWV3b3JrLmlvL3N1Z2dlc3RlZC1uYW1lc3BhY2UiOiJhdXRvbW90aXZlLWRldi1vcGVyYXRvci1zeXN0ZW0iLCJvcGVyYXRvcnMub3BlcmF0b3JmcmFtZXdvcmsuaW8vYnVpbGRlciI6Im9wZXJhdG9yLXNkay12MS40Mi4wIiwib3BlcmF0b3JzLm9wZXJhdG9yZnJhbWV3b3JrLmlvL3Byb2plY3RfbGF5b3V0IjoiZ28ua3ViZWJ1aWxkZXIuaW8vdjQiLCJyZXBvc2l0b3J5IjoiaHR0cHM6Ly9naXRodWIuY29tL2NlbnRvcy1hdXRvbW90aXZlLXN1aXRlL2F1dG9tb3RpdmUtZGV2LW9wZXJhdG9yIiwic3VwcG9ydCI6IlJlZCBIYXQifSwibmFtZSI6ImF1dG9tb3RpdmUtZGV2LW9wZXJhdG9yLnYwLjAuMSIsIm5hbWVzcGFjZSI6InBsYWNlaG9sZGVyIn0sInNwZWMiOnsiYXBpc2VydmljZWRlZmluaXRpb25zIjp7fSwiY3VzdG9tcmVzb3VyY2VkZWZpbml0aW9ucyI6eyJvd25lZCI6W3sia2luZCI6IkNhdGFsb2dJbWFnZSIsIm5hbWUiOiJjYXRhbG9naW1hZ2VzLmF1dG9tb3RpdmUuc2R2LmNsb3VkLnJlZGhhdC5jb20iLCJ2ZXJzaW9uIjoidjFhbHBoYTEifSx7ImRlc2NyaXB0aW9uIjoiSW1hZ2VCdWlsZCBpcyB0aGUgU2NoZW1hIGZvciB0aGUgaW1hZ2VidWlsZHMgQVBJIiwiZGlzcGxheU5hbWUiOiJJbWFnZSBCdWlsZCIsImtpbmQiOiJJbWFnZUJ1aWxkIiwibmFtZSI6ImltYWdlYnVpbGRzLmF1dG9tb3RpdmUuc2R2LmNsb3VkLnJlZGhhdC5jb20iLCJ2ZXJzaW9uIjoidjFhbHBoYTEifSx7ImRlc2NyaXB0aW9uIjoiSW1hZ2UgaXMgdGhlIFNjaGVtYSBmb3IgdGhlIGltYWdlcyBBUEkiLCJkaXNwbGF5TmFtZSI6IkltYWdlIiwia2luZCI6IkltYWdlIiwibmFtZSI6ImltYWdlcy5hdXRvbW90aXZlLnNkdi5jbG91ZC5yZWRoYXQuY29tIiwidmVyc2lvbiI6InYxYWxwaGExIn0seyJkZXNjcmlwdGlvbiI6Ik9wZXJhdG9yQ29uZmlnIGlzIHRoZSBTY2hlbWEgZm9yIHRoZSBvcGVyYXRvcmNvbmZpZ3MgQVBJIiwiZGlzcGxheU5hbWUiOiJPcGVyYXRvciBDb25maWciLCJraW5kIjoiT3BlcmF0b3JDb25maWciLCJuYW1lIjoib3BlcmF0b3Jjb25maWdzLmF1dG9tb3RpdmUuc2R2LmNsb3VkLnJlZGhhdC5jb20iLCJ2ZXJzaW9uIjoidjFhbHBoYTEifV19LCJkZXNjcmlwdGlvbiI6IkNlbnRPUyBBdXRvbW90aXZlIFN1aXRlIiwiZGlzcGxheU5hbWUiOiJDZW50T1MgQXV0b21vdGl2ZSBTdWl0ZSIsImljb24iOlt7ImJhc2U2NGRhdGEiOiIiLCJtZWRpYXR5cGUiOiIifV0sImluc3RhbGwiOnsic3BlYyI6eyJjbHVzdGVyUGVybWlzc2lvbnMiOlt7InJ1bGVzIjpbeyJhcGlHcm91cHMiOlsiIl0sInJlc291cmNlcyI6WyJjb25maWdtYXBzIiwicGVyc2lzdGVudHZvbHVtZWNsYWltcyIsInBvZHMiLCJzZWNyZXRzIiwic2VydmljZWFjY291bnRzIiwic2VydmljZXMiXSwidmVyYnMiOlsiY3JlYXRlIiwiZGVsZXRlIiwiZ2V0IiwibGlzdCIsInBhdGNoIiwidXBkYXRlIiwid2F0Y2giXX0seyJhcGlHcm91cHMiOlsiIl0sInJlc291cmNlcyI6WyJldmVudHMiXSwidmVyYnMiOlsiY3JlYXRlIiwicGF0Y2giXX0seyJhcGlHcm91cHMiOlsiIl0sInJlc291cmNlcyI6WyJuYW1lc3BhY2VzIl0sInZlcmJzIjpbImNyZWF0ZSIsImdldCIsImxpc3QiLCJ3YXRjaCJdfSx7ImFwaUdyb3VwcyI6WyIiXSwicmVzb3VyY2VzIjpbInBvZHMvZXhlYyJdLCJ2ZXJicyI6WyJjcmVhdGUiXX0seyJhcGlHcm91cHMiOlsiIl0sInJlc291cmNlcyI6WyJwb2RzL2xvZyJdLCJ2ZXJicyI6WyJnZXQiXX0seyJhcGlHcm91cHMiOlsiYXBwcyJdLCJyZXNvdXJjZXMiOlsiZGVwbG95bWVudHMiXSwidmVyYnMiOlsiY3JlYXRlIiwiZGVsZXRlIiwiZ2V0IiwibGlzdCIsInBhdGNoIiwidXBkYXRlIiwid2F0Y2giXX0seyJhcGlHcm91cHMiOlsiYXV0b21vdGl2ZS5zZHYuY2xvdWQucmVkaGF0LmNvbSJdLCJyZXNvdXJjZXMiOlsiY2F0YWxvZ2ltYWdlcyIsImltYWdlYnVpbGRzIiwiaW1hZ2VzIiwib3BlcmF0b3Jjb25maWdzIl0sInZlcmJzIjpbImNyZWF0ZSIsImRlbGV0ZSIsImdldCIsImxpc3QiLCJwYXRjaCIsInVwZGF0ZSIsIndhdGNoIl19LHsiYXBpR3JvdXBzIjpbImF1dG9tb3RpdmUuc2R2LmNsb3VkLnJlZGhhdC5jb20iXSwicmVzb3VyY2VzIjpbImNhdGFsb2dpbWFnZXMvZmluYWxpemVycyIsImltYWdlYnVpbGRzL2ZpbmFsaXplcnMiLCJpbWFnZXMvZmluYWxpemVycyIsIm9wZXJhdG9yY29uZmlncy9maW5hbGl6ZXJzIl0sInZlcmJzIjpbInVwZGF0ZSJdfSx7ImFwaUdyb3VwcyI6WyJhdXRvbW90aXZlLnNkdi5jbG91ZC5yZWRoYXQuY29tIl0sInJlc291cmNlcyI6WyJjYXRhbG9naW1hZ2VzL3N0YXR1cyIsImltYWdlYnVpbGRzL3N0YXR1cyIsImltYWdlcy9zdGF0dXMiLCJvcGVyYXRvcmNvbmZpZ3Mvc3RhdHVzIl0sInZlcmJzIjpbImdldCIsInBhdGNoIiwidXBkYXRlIl19LHsiYXBpR3JvdXBzIjpbIm5ldHdvcmtpbmcuazhzLmlvIl0sInJlc291cmNlcyI6WyJpbmdyZXNzZXMiXSwidmVyYnMiOlsiY3JlYXRlIiwiZGVsZXRlIiwiZ2V0IiwibGlzdCIsInBhdGNoIiwidXBkYXRlIiwid2F0Y2giXX0seyJhcGlHcm91cHMiOlsicmJhYy5hdXRob3JpemF0aW9uLms4cy5pbyJdLCJyZXNvdXJjZXMiOlsiY2x1c3RlcnJvbGViaW5kaW5ncyIsImNsdXN0ZXJyb2xlcyIsInJvbGViaW5kaW5ncyJdLCJ2ZXJicyI6WyJjcmVhdGUiLCJkZWxldGUiLCJnZXQiLCJsaXN0IiwicGF0Y2giLCJ1cGRhdGUiLCJ3YXRjaCJdfSx7ImFwaUdyb3VwcyI6WyJyb3V0ZS5vcGVuc2hpZnQuaW8iXSwicmVzb3VyY2VzIjpbInJvdXRlcyJdLCJ2ZXJicyI6WyJjcmVhdGUiLCJkZWxldGUiLCJnZXQiLCJsaXN0IiwicGF0Y2giLCJ1cGRhdGUiLCJ3YXRjaCJdfSx7ImFwaUdyb3VwcyI6WyJzZWN1cml0eS5vcGVuc2hpZnQuaW8iXSwicmVzb3VyY2VzIjpbInNlY3VyaXR5Y29udGV4dGNvbnN0cmFpbnRzIl0sInZlcmJzIjpbImNyZWF0ZSIsImRlbGV0ZSIsImdldCIsImxpc3QiLCJwYXRjaCIsInVwZGF0ZSIsInVzZSIsIndhdGNoIl19LHsiYXBpR3JvdXBzIjpbInRla3Rvbi5kZXYiXSwicmVzb3VyY2VzIjpbInBpcGVsaW5lcnVucyIsInBpcGVsaW5lcyIsInRhc2tydW5zIiwidGFza3MiXSwidmVyYnMiOlsiY3JlYXRlIiwiZGVsZXRlIiwiZ2V0IiwibGlzdCIsInBhdGNoIiwidXBkYXRlIiwid2F0Y2giXX1dLCJzZXJ2aWNlQWNjb3VudE5hbWUiOiJhZG8tY29udHJvbGxlci1tYW5hZ2VyIn1dLCJkZXBsb3ltZW50cyI6W3sibGFiZWwiOnsiYXBwLmt1YmVybmV0ZXMuaW8vbWFuYWdlZC1ieSI6Imt1c3RvbWl6ZSIsImFwcC5rdWJlcm5ldGVzLmlvL25hbWUiOiJhdXRvbW90aXZlLWRldi1vcGVyYXRvciIsImNvbnRyb2wtcGxhbmUiOiJjb250cm9sbGVyLW1hbmFnZXIifSwibmFtZSI6ImFkby1jb250cm9sbGVyLW1hbmFnZXIiLCJzcGVjIjp7InJlcGxpY2FzIjoxLCJzZWxlY3RvciI6eyJtYXRjaExhYmVscyI6eyJjb250cm9sLXBsYW5lIjoiY29udHJvbGxlci1tYW5hZ2VyIn19LCJzdHJhdGVneSI6e30sInRlbXBsYXRlIjp7Im1ldGFkYXRhIjp7ImFubm90YXRpb25zIjp7Imt1YmVjdGwua3ViZXJuZXRlcy5pby9kZWZhdWx0LWNvbnRhaW5lciI6Im1hbmFnZXIifSwibGFiZWxzIjp7ImNvbnRyb2wtcGxhbmUiOiJjb250cm9sbGVyLW1hbmFnZXIifX0sInNwZWMiOnsiYWZmaW5pdHkiOnsibm9kZUFmZmluaXR5Ijp7InJlcXVpcmVkRHVyaW5nU2NoZWR1bGluZ0lnbm9yZWREdXJpbmdFeGVjdXRpb24iOnsibm9kZVNlbGVjdG9yVGVybXMiOlt7Im1hdGNoRXhwcmVzc2lvbnMiOlt7ImtleSI6Imt1YmVybmV0ZXMuaW8vYXJjaCIsIm9wZXJhdG9yIjoiSW4iLCJ2YWx1ZXMiOlsiYXJtNjQiLCJhbWQ2NCJdfSx7ImtleSI6Imt1YmVybmV0ZXMuaW8vb3MiLCJvcGVyYXRvciI6IkluIiwidmFsdWVzIjpbImxpbnV4Il19XX1dfX19LCJjb250YWluZXJzIjpbeyJhcmdzIjpbIi0tbGVhZGVyLWVsZWN0IiwiLS1oZWFsdGgtcHJvYmUtYmluZC1hZGRyZXNzPTo4MDgxIiwiLS1tZXRyaWNzLWJpbmQtYWRkcmVzcz0wIl0sImNvbW1hbmQiOlsiL21hbmFnZXIiXSwiZW52IjpbeyJuYW1lIjoiQlVJTERfQVBJX05BTUVTUEFDRSIsInZhbHVlRnJvbSI6eyJmaWVsZFJlZiI6eyJmaWVsZFBhdGgiOiJtZXRhZGF0YS5uYW1lc3BhY2UifX19LHsibmFtZSI6Ik9QRVJBVE9SX0lNQUdFIiwidmFsdWUiOiJpbWFnZS1yZWdpc3RyeS5vcGVuc2hpZnQtaW1hZ2UtcmVnaXN0cnkuc3ZjOjUwMDAvYXV0b21vdGl2ZS1kZXYtb3BlcmF0b3Itc3lzdGVtL2F1dG9tb3RpdmUtZGV2LW9wZXJhdG9yOmxhdGVzdCJ9XSwiaW1hZ2UiOiJkZWZhdWx0LXJvdXRlLW9wZW5zaGlmdC1pbWFnZS1yZWdpc3RyeS5hcHBzLmF1dG9tb3RpdmUxLm9jcC5hdXRvbW90aXZlLnNpZy5jZW50b3Mub3JnL2F1dG9tb3RpdmUtZGV2LW9wZXJhdG9yLXN5c3RlbS9hdXRvbW90aXZlLWRldi1vcGVyYXRvcjpsYXRlc3QiLCJpbWFnZVB1bGxQb2xpY3kiOiJJZk5vdFByZXNlbnQiLCJsaXZlbmVzc1Byb2JlIjp7Imh0dHBHZXQiOnsicGF0aCI6Ii9oZWFsdGh6IiwicG9ydCI6ODA4MX0sImluaXRpYWxEZWxheVNlY29uZHMiOjE1LCJwZXJpb2RTZWNvbmRzIjoyMH0sIm5hbWUiOiJtYW5hZ2VyIiwicG9ydHMiOlt7ImNvbnRhaW5lclBvcnQiOjgwODAsIm5hbWUiOiJidWlsZC1hcGkifV0sInJlYWRpbmVzc1Byb2JlIjp7Imh0dHBHZXQiOnsicGF0aCI6Ii9yZWFkeXoiLCJwb3J0Ijo4MDgxfSwiaW5pdGlhbERlbGF5U2Vjb25kcyI6NSwicGVyaW9kU2Vjb25kcyI6MTB9LCJyZXNvdXJjZXMiOnsibGltaXRzIjp7ImNwdSI6IjEiLCJtZW1vcnkiOiI1MTJNaSJ9LCJyZXF1ZXN0cyI6eyJjcHUiOiIxMDBtIiwibWVtb3J5IjoiMjU2TWkifX0sInNlY3VyaXR5Q29udGV4dCI6eyJhbGxvd1ByaXZpbGVnZUVzY2FsYXRpb24iOmZhbHNlLCJjYXBhYmlsaXRpZXMiOnsiZHJvcCI6WyJBTEwiXX19fV0sImluaXRDb250YWluZXJzIjpbeyJjb21tYW5kIjpbIi9pbml0LXNlY3JldHMiXSwiZW52IjpbeyJuYW1lIjoiUE9EX05BTUVTUEFDRSIsInZhbHVlRnJvbSI6eyJmaWVsZFJlZiI6eyJmaWVsZFBhdGgiOiJtZXRhZGF0YS5uYW1lc3BhY2UifX19XSwiaW1hZ2UiOiJkZWZhdWx0LXJvdXRlLW9wZW5zaGlmdC1pbWFnZS1yZWdpc3RyeS5hcHBzLmF1dG9tb3RpdmUxLm9jcC5hdXRvbW90aXZlLnNpZy5jZW50b3Mub3JnL2F1dG9tb3RpdmUtZGV2LW9wZXJhdG9yLXN5c3RlbS9hdXRvbW90aXZlLWRldi1vcGVyYXRvcjpsYXRlc3QiLCJpbWFnZVB1bGxQb2xpY3kiOiJJZk5vdFByZXNlbnQiLCJuYW1lIjoiaW5pdC1vYXV0aC1zZWNyZXRzIiwicmVzb3VyY2VzIjp7ImxpbWl0cyI6eyJjcHUiOiIxMDBtIiwibWVtb3J5IjoiNjRNaSJ9LCJyZXF1ZXN0cyI6eyJjcHUiOiIxMG0iLCJtZW1vcnkiOiIzMk1pIn19LCJzZWN1cml0eUNvbnRleHQiOnsiYWxsb3dQcml2aWxlZ2VFc2NhbGF0aW9uIjpmYWxzZSwiY2FwYWJpbGl0aWVzIjp7ImRyb3AiOlsiQUxMIl19fX1dLCJzZWN1cml0eUNvbnRleHQiOnsicnVuQXNOb25Sb290Ijp0cnVlfSwic2VydmljZUFjY291bnROYW1lIjoiYWRvLWNvbnRyb2xsZXItbWFuYWdlciIsInRlcm1pbmF0aW9uR3JhY2VQZXJpb2RTZWNvbmRzIjoxMH19fX1dLCJwZXJtaXNzaW9ucyI6W3sicnVsZXMiOlt7ImFwaUdyb3VwcyI6WyIiXSwicmVzb3VyY2VzIjpbImNvbmZpZ21hcHMiXSwidmVyYnMiOlsiZ2V0IiwibGlzdCIsIndhdGNoIiwiY3JlYXRlIiwidXBkYXRlIiwicGF0Y2giLCJkZWxldGUiXX0seyJhcGlHcm91cHMiOlsiY29vcmRpbmF0aW9uLms4cy5pbyJdLCJyZXNvdXJjZXMiOlsibGVhc2VzIl0sInZlcmJzIjpbImdldCIsImxpc3QiLCJ3YXRjaCIsImNyZWF0ZSIsInVwZGF0ZSIsInBhdGNoIiwiZGVsZXRlIl19LHsiYXBpR3JvdXBzIjpbIiJdLCJyZXNvdXJjZXMiOlsiZXZlbnRzIl0sInZlcmJzIjpbImNyZWF0ZSIsInBhdGNoIl19XSwic2VydmljZUFjY291bnROYW1lIjoiYWRvLWNvbnRyb2xsZXItbWFuYWdlciJ9XX0sInN0cmF0ZWd5IjoiZGVwbG95bWVudCJ9LCJpbnN0YWxsTW9kZXMiOlt7InN1cHBvcnRlZCI6dHJ1ZSwidHlwZSI6Ik93bk5hbWVzcGFjZSJ9LHsic3VwcG9ydGVkIjp0cnVlLCJ0eXBlIjoiU2luZ2xlTmFtZXNwYWNlIn0seyJzdXBwb3J0ZWQiOmZhbHNlLCJ0eXBlIjoiTXVsdGlOYW1lc3BhY2UifSx7InN1cHBvcnRlZCI6ZmFsc2UsInR5cGUiOiJBbGxOYW1lc3BhY2VzIn1dLCJrZXl3b3JkcyI6WyJhdXRvbW90aXZlIl0sImxpbmtzIjpbeyJuYW1lIjoiQ2VudE9TIEF1dG9tb3RpdmUgU3VpdGUiLCJ1cmwiOiJodHRwczovL2dpdGh1Yi5jb20vY2VudG9zLWF1dG9tb3RpdmUtc3VpdGUvYXV0b21vdGl2ZS1kZXYtb3BlcmF0b3IifV0sIm1hdHVyaXR5IjoiYWxwaGEiLCJtaW5LdWJlVmVyc2lvbiI6IjEuMjYuMCIsInByb3ZpZGVyIjp7Im5hbWUiOiJSZWQgSGF0In0sInZlcnNpb24iOiIwLjAuMSJ9fQ== - type: olm.bundle.object value: data: eyJhcGlWZXJzaW9uIjoicmJhYy5hdXRob3JpemF0aW9uLms4cy5pby92MSIsImtpbmQiOiJDbHVzdGVyUm9sZSIsIm1ldGFkYXRhIjp7ImNyZWF0aW9uVGltZXN0YW1wIjpudWxsLCJsYWJlbHMiOnsiYXBwLmt1YmVybmV0ZXMuaW8vbWFuYWdlZC1ieSI6Imt1c3RvbWl6ZSIsImFwcC5rdWJlcm5ldGVzLmlvL25hbWUiOiJhZG8ifSwibmFtZSI6ImFkby1pbWFnZS12aWV3ZXItcm9sZSJ9LCJydWxlcyI6W3siYXBpR3JvdXBzIjpbImF1dG9tb3RpdmUuc2R2LmNsb3VkLnJlZGhhdC5jb20iXSwicmVzb3VyY2VzIjpbImltYWdlcyJdLCJ2ZXJicyI6WyJnZXQiLCJsaXN0Iiwid2F0Y2giXX0seyJhcGlHcm91cHMiOlsiYXV0b21vdGl2ZS5zZHYuY2xvdWQucmVkaGF0LmNvbSJdLCJyZXNvdXJjZXMiOlsiaW1hZ2VzL3N0YXR1cyJdLCJ2ZXJicyI6WyJnZXQiXX1dfQ== @@ -83,6 +95,6 @@ properties: value: data: eyJhcGlWZXJzaW9uIjoidjEiLCJraW5kIjoiU2VydmljZSIsIm1ldGFkYXRhIjp7ImNyZWF0aW9uVGltZXN0YW1wIjpudWxsLCJsYWJlbHMiOnsiYXBwLmt1YmVybmV0ZXMuaW8vbWFuYWdlZC1ieSI6Imt1c3RvbWl6ZSIsImFwcC5rdWJlcm5ldGVzLmlvL25hbWUiOiJhdXRvbW90aXZlLWRldi1vcGVyYXRvciIsImNvbnRyb2wtcGxhbmUiOiJjb250cm9sbGVyLW1hbmFnZXIifSwibmFtZSI6ImFkby1jb250cm9sbGVyLW1hbmFnZXItbWV0cmljcy1zZXJ2aWNlIn0sInNwZWMiOnsicG9ydHMiOlt7Im5hbWUiOiJodHRwcyIsInBvcnQiOjg0NDMsInByb3RvY29sIjoiVENQIiwidGFyZ2V0UG9ydCI6ODQ0M31dLCJzZWxlY3RvciI6eyJjb250cm9sLXBsYW5lIjoiY29udHJvbGxlci1tYW5hZ2VyIn19LCJzdGF0dXMiOnsibG9hZEJhbGFuY2VyIjp7fX19 relatedImages: -- image: quay.io/rh-sdv-cloud/automotive-dev-operator:latest - name: manager +- image: default-route-openshift-image-registry.apps.automotive1.ocp.automotive.sig.centos.org/automotive-dev-operator-system/automotive-dev-operator:latest + name: "" schema: olm.bundle diff --git a/cmd/caib/main.go b/cmd/caib/main.go index 99fff51f..be9e6faa 100644 --- a/cmd/caib/main.go +++ b/cmd/caib/main.go @@ -188,7 +188,7 @@ Examples: buildCmd.Flags().StringVar(&containerPush, "push", "", "push bootc container to registry (required)") buildCmd.Flags().BoolVar(&buildDiskImage, "disk", false, "also build disk image from container") buildCmd.Flags().StringVarP(&outputDir, "output", "o", "", "download disk image to file (requires --disk)") - buildCmd.Flags().StringVar(&diskFormat, "format", "qcow2", "disk image format (qcow2, raw, simg)") + buildCmd.Flags().StringVar(&diskFormat, "format", "", "disk image format (qcow2, raw, image); inferred from output filename if not set") buildCmd.Flags().StringVar(&compressionAlgo, "compress", "gzip", "compression algorithm (gzip, lz4, xz)") buildCmd.Flags().StringVar(&exportOCI, "push-disk", "", "push disk image as OCI artifact to registry") buildCmd.Flags().StringVar(®istryUsername, "registry-username", "", "registry username (or REGISTRY_USERNAME env)") @@ -217,7 +217,7 @@ Examples: diskCmd.Flags().StringVar(&authToken, "token", os.Getenv("CAIB_TOKEN"), "Bearer token for authentication") diskCmd.Flags().StringVarP(&buildName, "name", "n", "", "name for the build job (auto-generated if omitted)") diskCmd.Flags().StringVarP(&outputDir, "output", "o", "", "download disk image to file") - diskCmd.Flags().StringVar(&diskFormat, "format", "qcow2", "disk image format (qcow2, raw, simg)") + diskCmd.Flags().StringVar(&diskFormat, "format", "", "disk image format (qcow2, raw, image); inferred from output filename if not set") diskCmd.Flags().StringVar(&compressionAlgo, "compress", "gzip", "compression algorithm (gzip, lz4, xz)") diskCmd.Flags().StringVar(&exportOCI, "push", "", "push disk image as OCI artifact to registry") diskCmd.Flags().StringVar(®istryUsername, "registry-username", "", "registry username (or REGISTRY_USERNAME env)") @@ -287,10 +287,7 @@ func runBuild(cmd *cobra.Command, args []string) { buildDiskImage = true // imply --disk when --output is specified } - // Validate: if downloading disk, need push-disk destination - if outputDir != "" && exportOCI == "" { - handleError(fmt.Errorf("--output requires --push-disk (disk image must be pushed to registry first for download)")) - } + // Note: diskFormat can be empty - AIB will default to raw (or infer from output filename extension) if strings.TrimSpace(authToken) == "" { if tok, err := loadTokenFromKubeconfig(); err == nil && strings.TrimSpace(tok) != "" { @@ -384,13 +381,21 @@ func runBuild(cmd *cobra.Command, args []string) { handleFileUploads(ctx, api, resp.Name, localRefs) } - if waitForBuild || followLogs || downloadFile != "" { + if waitForBuild || followLogs || outputDir != "" { waitForBuildCompletion(ctx, api, resp.Name, "") } if outputDir != "" { - if err := pullOCIArtifact(exportOCI, outputDir, registryUsername, registryPassword); err != nil { - handleError(fmt.Errorf("failed to download OCI artifact: %w", err)) + if exportOCI != "" { + // Download via OCI registry (pushed as artifact) + if err := pullOCIArtifact(exportOCI, outputDir, registryUsername, registryPassword); err != nil { + handleError(fmt.Errorf("failed to download OCI artifact: %w", err)) + } + } else { + // Download directly from cluster via artifact API + if err := downloadArtifactViaAPI(ctx, serverURL, buildName, filepath.Dir(outputDir)); err != nil { + handleError(fmt.Errorf("failed to download artifact: %w", err)) + } } } } @@ -419,6 +424,8 @@ func runDisk(cmd *cobra.Command, args []string) { fmt.Printf("Auto-generated build name: %s\n", buildName) } + // Note: diskFormat can be empty - AIB will default to raw (or infer from output filename extension) + if strings.TrimSpace(authToken) == "" { if tok, err := loadTokenFromKubeconfig(); err == nil && strings.TrimSpace(tok) != "" { authToken = tok @@ -469,6 +476,7 @@ func runDisk(cmd *cobra.Command, args []string) { StorageClass: storageClass, Compression: compressionAlgo, ExportOCI: exportOCI, + ServeArtifact: outputDir != "" && exportOCI == "", } if effectiveRegistryURL != "" && registryUsername != "" && registryPassword != "" { @@ -491,9 +499,17 @@ func runDisk(cmd *cobra.Command, args []string) { waitForBuildCompletion(ctx, api, resp.Name, "") } - if outputDir != "" && exportOCI != "" { - if err := pullOCIArtifact(exportOCI, outputDir, registryUsername, registryPassword); err != nil { - handleError(fmt.Errorf("failed to download OCI artifact: %w", err)) + if outputDir != "" { + if exportOCI != "" { + // Download via OCI registry + if err := pullOCIArtifact(exportOCI, outputDir, registryUsername, registryPassword); err != nil { + handleError(fmt.Errorf("failed to download OCI artifact: %w", err)) + } + } else { + // Download directly from cluster via artifact API + if err := downloadArtifactViaAPI(ctx, serverURL, buildName, filepath.Dir(outputDir)); err != nil { + handleError(fmt.Errorf("failed to download artifact: %w", err)) + } } } } diff --git a/internal/buildapi/server.go b/internal/buildapi/server.go index 604c1dc2..7a3ca047 100644 --- a/internal/buildapi/server.go +++ b/internal/buildapi/server.go @@ -822,6 +822,9 @@ func createBuild(c *gin.Context) { return } + // Debug logging for containerRef + fmt.Printf("DEBUG: Received request - Mode: %s, ContainerRef: %q\n", req.Mode, req.ContainerRef) + needsUpload := strings.Contains(req.Manifest, "source_path") // Disk mode uses ContainerRef instead of a manifest diff --git a/internal/common/tasks/scripts/build_image.sh b/internal/common/tasks/scripts/build_image.sh index 0350de87..9e531a6a 100644 --- a/internal/common/tasks/scripts/build_image.sh +++ b/internal/common/tasks/scripts/build_image.sh @@ -152,12 +152,26 @@ mount --bind "$destPath" "$osbuildPath" cd $(workspaces.shared-workspace.path) -if [ "$(params.export-format)" = "image" ]; then +EXPORT_FORMAT="$(params.export-format)" +# If format is empty, AIB defaults to raw +if [ -z "$EXPORT_FORMAT" ] || [ "$EXPORT_FORMAT" = "image" ]; then file_extension=".raw" -elif [ "$(params.export-format)" = "qcow2" ]; then +elif [ "$EXPORT_FORMAT" = "qcow2" ]; then file_extension=".qcow2" else - file_extension=".$(params.export-format)" + file_extension=".$EXPORT_FORMAT" +fi + +# Only pass --format to AIB if explicitly specified +# Note: to-disk-image accepts raw/qcow2/simg, not "image" +FORMAT_ARG="" +if [ -n "$EXPORT_FORMAT" ]; then + AIB_FORMAT="$EXPORT_FORMAT" + # Translate "image" to "raw" for AIB compatibility + if [ "$AIB_FORMAT" = "image" ]; then + AIB_FORMAT="raw" + fi + FORMAT_ARG="--format $AIB_FORMAT" fi cleanName=$(params.distro)-$(params.target) @@ -246,20 +260,27 @@ BUILDER_IMAGE="$(params.builder-image)" CLUSTER_REGISTRY_ROUTE="$(params.cluster-registry-route)" CONTAINER_REF="$(params.container-ref)" +echo "=== Build Configuration ===" +echo "BUILD_MODE: $BUILD_MODE" +echo "CONTAINER_PUSH: ${CONTAINER_PUSH:-}" +echo "BUILD_DISK_IMAGE: $BUILD_DISK_IMAGE" +echo "EXPORT_OCI: ${EXPORT_OCI:-}" +echo "===========================" + BOOTC_CONTAINER_NAME="localhost/aib-build:$(params.distro)-$(params.target)" BUILD_CONTAINER_ARG="" LOCAL_BUILDER_IMAGE="localhost/aib-build:$(params.distro)-$TARGET_ARCH" -# For bootc builds, if no builder-image is provided but cluster-registry-route is set, +# For bootc/disk builds, if no builder-image is provided but cluster-registry-route is set, # use the image that prepare-builder cached in the cluster registry -if [ -z "$BUILDER_IMAGE" ] && [ "$BUILD_MODE" = "bootc" ] && [ -n "$CLUSTER_REGISTRY_ROUTE" ]; then +if [ -z "$BUILDER_IMAGE" ] && { [ "$BUILD_MODE" = "bootc" ] || [ "$BUILD_MODE" = "disk" ]; } && [ -n "$CLUSTER_REGISTRY_ROUTE" ]; then NAMESPACE=$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace) BUILDER_IMAGE="${CLUSTER_REGISTRY_ROUTE}/${NAMESPACE}/aib-build:$(params.distro)-$TARGET_ARCH" echo "Using builder image from cluster registry: $BUILDER_IMAGE" fi -if [ -n "$BUILDER_IMAGE" ] && [ "$BUILD_MODE" = "bootc" ]; then +if [ -n "$BUILDER_IMAGE" ] && { [ "$BUILD_MODE" = "bootc" ] || [ "$BUILD_MODE" = "disk" ]; }; then echo "Pulling builder image to local storage: $BUILDER_IMAGE" TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token 2>/dev/null || echo "") @@ -301,18 +322,26 @@ if [ "$USE_OVERRIDE" = true ]; then else case "$BUILD_MODE" in bootc) + # Build bootc container and optionally disk image in a single command + # aib build takes: manifest out [disk] where disk is optional + DISK_OUTPUT="" + if [ "$BUILD_DISK_IMAGE" = "true" ]; then + DISK_OUTPUT="/output/${exportFile}" + fi + build_command="aib --verbose build \ --distro $(params.distro) \ --target $(params.target) \ --arch=${arch} \ --build-dir=/output/_build \ --osbuild-manifest=/output/image.json \ + $FORMAT_ARG \ $BUILD_CONTAINER_ARG \ $CUSTOM_DEFS \ $AIB_ARGS \ $MANIFEST_FILE \ $BOOTC_CONTAINER_NAME \ - /output/${exportFile}" + $DISK_OUTPUT" echo "Running bootc build: $build_command" eval "$build_command" @@ -324,6 +353,11 @@ else "docker://$CONTAINER_PUSH" echo "Container pushed successfully to $CONTAINER_PUSH" fi + + if [ "$BUILD_DISK_IMAGE" = "true" ]; then + echo "Disk image created: /output/${exportFile}" + # Note: Disk image push to OCI registry is handled by the separate push-disk-artifact task + fi ;; image) build_command="aib-dev --verbose \ @@ -332,7 +366,7 @@ else --distro $(params.distro) \ --target $(params.target) \ --arch=${arch} \ - --format $(params.export-format) \ + $FORMAT_ARG \ --build-dir=/output/_build \ --osbuild-manifest=/output/image.json \ $AIB_ARGS \ @@ -348,7 +382,7 @@ else --distro $(params.distro) \ --target $(params.target) \ --arch=${arch} \ - --format $(params.export-format) \ + $FORMAT_ARG \ --build-dir=/output/_build \ --osbuild-manifest=/output/image.json \ $AIB_ARGS \ @@ -367,17 +401,24 @@ else # Pull the container image first echo "Pulling container image..." - skopeo copy --authfile="$REGISTRY_AUTH_FILE" \ - "docker://$CONTAINER_REF" \ - "containers-storage:$CONTAINER_REF" + # Try without auth first (for public images), fall back to auth file if needed + if ! skopeo copy "docker://$CONTAINER_REF" "containers-storage:$CONTAINER_REF" 2>/dev/null; then + echo "Public pull failed, trying with auth..." + skopeo copy --authfile="$REGISTRY_AUTH_FILE" \ + "docker://$CONTAINER_REF" \ + "containers-storage:$CONTAINER_REF" + fi + # to-disk-image only accepts: --format, --build-container, src_container, out build_command="aib --verbose to-disk-image \ - --target $(params.target) \ - --build-dir=/output/_build \ + $FORMAT_ARG \ + $BUILD_CONTAINER_ARG \ $CONTAINER_REF \ /output/${exportFile}" echo "Running to-disk-image: $build_command" eval "$build_command" + + # Note: Disk image push to OCI registry is handled by the separate push-disk-artifact task ;; *) echo "Error: Unknown build mode '$BUILD_MODE'. Supported modes: bootc, image, package, disk" From 711611fcfe3bb439b377d42305590e814ccb3a58 Mon Sep 17 00:00:00 2001 From: Benny Zlotnik Date: Thu, 8 Jan 2026 15:36:55 +0200 Subject: [PATCH 3/7] fix arg text Signed-off-by: Benny Zlotnik --- ...ve-dev-operator.clusterserviceversion.yaml | 2 +- catalog/automotive-dev-operator.yaml | 2 +- cmd/caib/README.md | 153 +++++--- cmd/caib/main.go | 341 +++++++++++------- internal/buildapi/server.go | 241 ++++--------- internal/buildapi/types.go | 27 +- internal/common/tasks/scripts/build_image.sh | 25 +- internal/controller/imagebuild/controller.go | 57 ++- 8 files changed, 451 insertions(+), 397 deletions(-) diff --git a/bundle/manifests/automotive-dev-operator.clusterserviceversion.yaml b/bundle/manifests/automotive-dev-operator.clusterserviceversion.yaml index 0f206c12..58037f2c 100644 --- a/bundle/manifests/automotive-dev-operator.clusterserviceversion.yaml +++ b/bundle/manifests/automotive-dev-operator.clusterserviceversion.yaml @@ -53,7 +53,7 @@ metadata: capabilities: Basic Install categories: Developer Tools containerImage: quay.io/rh-sdv-cloud/automotive-dev-operator:latest - createdAt: "2026-01-08T09:47:15Z" + createdAt: "2026-01-09T11:11:25Z" operatorframework.io/suggested-namespace: automotive-dev-operator-system operators.operatorframework.io/builder: operator-sdk-v1.42.0 operators.operatorframework.io/project_layout: go.kubebuilder.io/v4 diff --git a/catalog/automotive-dev-operator.yaml b/catalog/automotive-dev-operator.yaml index 9740f357..e45a1647 100644 --- a/catalog/automotive-dev-operator.yaml +++ b/catalog/automotive-dev-operator.yaml @@ -60,7 +60,7 @@ properties: data: eyJhcGlWZXJzaW9uIjoiYXBpZXh0ZW5zaW9ucy5rOHMuaW8vdjEiLCJraW5kIjoiQ3VzdG9tUmVzb3VyY2VEZWZpbml0aW9uIiwibWV0YWRhdGEiOnsiYW5ub3RhdGlvbnMiOnsiY29udHJvbGxlci1nZW4ua3ViZWJ1aWxkZXIuaW8vdmVyc2lvbiI6InYwLjE5LjAifSwiY3JlYXRpb25UaW1lc3RhbXAiOm51bGwsIm5hbWUiOiJvcGVyYXRvcmNvbmZpZ3MuYXV0b21vdGl2ZS5zZHYuY2xvdWQucmVkaGF0LmNvbSJ9LCJzcGVjIjp7Imdyb3VwIjoiYXV0b21vdGl2ZS5zZHYuY2xvdWQucmVkaGF0LmNvbSIsIm5hbWVzIjp7ImtpbmQiOiJPcGVyYXRvckNvbmZpZyIsImxpc3RLaW5kIjoiT3BlcmF0b3JDb25maWdMaXN0IiwicGx1cmFsIjoib3BlcmF0b3Jjb25maWdzIiwic2luZ3VsYXIiOiJvcGVyYXRvcmNvbmZpZyJ9LCJzY29wZSI6Ik5hbWVzcGFjZWQiLCJ2ZXJzaW9ucyI6W3siYWRkaXRpb25hbFByaW50ZXJDb2x1bW5zIjpbeyJqc29uUGF0aCI6Ii5zcGVjLndlYlVJIiwibmFtZSI6IldlYlVJIiwidHlwZSI6ImJvb2xlYW4ifSx7Impzb25QYXRoIjoiLnNwZWMub3NCdWlsZHMuZW5hYmxlZCIsIm5hbWUiOiJPUyBCdWlsZHMiLCJ0eXBlIjoiYm9vbGVhbiJ9LHsianNvblBhdGgiOiIuc3RhdHVzLnBoYXNlIiwibmFtZSI6IlBoYXNlIiwidHlwZSI6InN0cmluZyJ9LHsianNvblBhdGgiOiIubWV0YWRhdGEuY3JlYXRpb25UaW1lc3RhbXAiLCJuYW1lIjoiQWdlIiwidHlwZSI6ImRhdGUifV0sIm5hbWUiOiJ2MWFscGhhMSIsInNjaGVtYSI6eyJvcGVuQVBJVjNTY2hlbWEiOnsiZGVzY3JpcHRpb24iOiJPcGVyYXRvckNvbmZpZyBpcyB0aGUgU2NoZW1hIGZvciB0aGUgb3BlcmF0b3Jjb25maWdzIEFQSSIsInByb3BlcnRpZXMiOnsiYXBpVmVyc2lvbiI6eyJkZXNjcmlwdGlvbiI6IkFQSVZlcnNpb24gZGVmaW5lcyB0aGUgdmVyc2lvbmVkIHNjaGVtYSBvZiB0aGlzIHJlcHJlc2VudGF0aW9uIG9mIGFuIG9iamVjdC5cblNlcnZlcnMgc2hvdWxkIGNvbnZlcnQgcmVjb2duaXplZCBzY2hlbWFzIHRvIHRoZSBsYXRlc3QgaW50ZXJuYWwgdmFsdWUsIGFuZFxubWF5IHJlamVjdCB1bnJlY29nbml6ZWQgdmFsdWVzLlxuTW9yZSBpbmZvOiBodHRwczovL2dpdC5rOHMuaW8vY29tbXVuaXR5L2NvbnRyaWJ1dG9ycy9kZXZlbC9zaWctYXJjaGl0ZWN0dXJlL2FwaS1jb252ZW50aW9ucy5tZCNyZXNvdXJjZXMiLCJ0eXBlIjoic3RyaW5nIn0sImtpbmQiOnsiZGVzY3JpcHRpb24iOiJLaW5kIGlzIGEgc3RyaW5nIHZhbHVlIHJlcHJlc2VudGluZyB0aGUgUkVTVCByZXNvdXJjZSB0aGlzIG9iamVjdCByZXByZXNlbnRzLlxuU2VydmVycyBtYXkgaW5mZXIgdGhpcyBmcm9tIHRoZSBlbmRwb2ludCB0aGUgY2xpZW50IHN1Ym1pdHMgcmVxdWVzdHMgdG8uXG5DYW5ub3QgYmUgdXBkYXRlZC5cbkluIENhbWVsQ2FzZS5cbk1vcmUgaW5mbzogaHR0cHM6Ly9naXQuazhzLmlvL2NvbW11bml0eS9jb250cmlidXRvcnMvZGV2ZWwvc2lnLWFyY2hpdGVjdHVyZS9hcGktY29udmVudGlvbnMubWQjdHlwZXMta2luZHMiLCJ0eXBlIjoic3RyaW5nIn0sIm1ldGFkYXRhIjp7InR5cGUiOiJvYmplY3QifSwic3BlYyI6eyJkZXNjcmlwdGlvbiI6Ik9wZXJhdG9yQ29uZmlnU3BlYyBkZWZpbmVzIHRoZSBkZXNpcmVkIHN0YXRlIG9mIE9wZXJhdG9yQ29uZmlnIiwicHJvcGVydGllcyI6eyJvc0J1aWxkcyI6eyJkZXNjcmlwdGlvbiI6Ik9TQnVpbGRzIGRlZmluZXMgdGhlIGNvbmZpZ3VyYXRpb24gZm9yIE9TIGJ1aWxkIG9wZXJhdGlvbnMiLCJwcm9wZXJ0aWVzIjp7ImNsdXN0ZXJSZWdpc3RyeVJvdXRlIjp7ImRlc2NyaXB0aW9uIjoiQ2x1c3RlclJlZ2lzdHJ5Um91dGUgaXMgdGhlIGV4dGVybmFsIHJvdXRlIGZvciB0aGUgY2x1c3RlcidzIGludGVybmFsIGltYWdlIHJlZ2lzdHJ5XG5SZXF1aXJlZCBmb3IgYm9vdGMgYnVpbGRzIHRvIGFsbG93IG5lc3RlZCBjb250YWluZXJzIHRvIHB1bGwgYnVpbGRlciBpbWFnZXNcbkV4YW1wbGU6IFwiZGVmYXVsdC1yb3V0ZS1vcGVuc2hpZnQtaW1hZ2UtcmVnaXN0cnkuYXBwcy5teWNsdXN0ZXIuZXhhbXBsZS5jb21cIiIsInR5cGUiOiJzdHJpbmcifSwiZW5hYmxlZCI6eyJkZWZhdWx0Ijp0cnVlLCJkZXNjcmlwdGlvbiI6IkVuYWJsZWQgZGV0ZXJtaW5lcyBpZiBUZWt0b24gdGFza3MgZm9yIE9TIGJ1aWxkcyBzaG91bGQgYmUgZGVwbG95ZWQiLCJ0eXBlIjoiYm9vbGVhbiJ9LCJtZW1vcnlWb2x1bWVTaXplIjp7ImRlc2NyaXB0aW9uIjoiTWVtb3J5Vm9sdW1lU2l6ZSBzcGVjaWZpZXMgdGhlIHNpemUgbGltaXQgZm9yIG1lbW9yeS1iYWNrZWQgdm9sdW1lcyAocmVxdWlyZWQgaWYgVXNlTWVtb3J5Vm9sdW1lcyBpcyB0cnVlKVxuRXhhbXBsZTogXCIyR2lcIiIsInR5cGUiOiJzdHJpbmcifSwicHZjU2l6ZSI6eyJkZXNjcmlwdGlvbiI6IlBWQ1NpemUgc3BlY2lmaWVzIHRoZSBzaXplIGZvciBwZXJzaXN0ZW50IHZvbHVtZSBjbGFpbXMgY3JlYXRlZCBmb3IgYnVpbGQgd29ya3NwYWNlc1xuRGVmYXVsdDogXCI4R2lcIiIsInR5cGUiOiJzdHJpbmcifSwicnVudGltZUNsYXNzTmFtZSI6eyJkZXNjcmlwdGlvbiI6IlJ1bnRpbWVDbGFzc05hbWUgc3BlY2lmaWVzIHRoZSBydW50aW1lIGNsYXNzIHRvIHVzZSBmb3IgdGhlIGJ1aWxkIHBvZFxuTW9yZSBpbmZvOiBodHRwczovL2t1YmVybmV0ZXMuaW8vZG9jcy9jb25jZXB0cy9jb250YWluZXJzL3J1bnRpbWUtY2xhc3MvIiwidHlwZSI6InN0cmluZyJ9LCJzZXJ2ZUV4cGlyeUhvdXJzIjp7ImRlc2NyaXB0aW9uIjoiU2VydmVFeHBpcnlIb3VycyBzcGVjaWZpZXMgaG93IGxvbmcgdG8gc2VydmUgYnVpbGQgYXJ0aWZhY3RzIGJlZm9yZSBhdXRvbWF0aWMgY2xlYW51cFxuRGVmYXVsdDogMjQiLCJmb3JtYXQiOiJpbnQzMiIsInR5cGUiOiJpbnRlZ2VyIn0sInVzZU1lbW9yeVZvbHVtZXMiOnsiZGVzY3JpcHRpb24iOiJVc2VNZW1vcnlWb2x1bWVzIGRldGVybWluZXMgd2hldGhlciB0byB1c2UgbWVtb3J5LWJhY2tlZCB2b2x1bWVzIGZvciBidWlsZCBvcGVyYXRpb25zIiwidHlwZSI6ImJvb2xlYW4ifX0sInJlcXVpcmVkIjpbImVuYWJsZWQiXSwidHlwZSI6Im9iamVjdCJ9LCJ3ZWJVSSI6eyJkZWZhdWx0Ijp0cnVlLCJkZXNjcmlwdGlvbiI6IldlYlVJIGRldGVybWluZXMgaWYgdGhlIHdlYiBVSSBzaG91bGQgYmUgZGVwbG95ZWQiLCJ0eXBlIjoiYm9vbGVhbiJ9fSwicmVxdWlyZWQiOlsid2ViVUkiXSwidHlwZSI6Im9iamVjdCJ9LCJzdGF0dXMiOnsiZGVzY3JpcHRpb24iOiJPcGVyYXRvckNvbmZpZ1N0YXR1cyBkZWZpbmVzIHRoZSBvYnNlcnZlZCBzdGF0ZSBvZiBPcGVyYXRvckNvbmZpZyIsInByb3BlcnRpZXMiOnsibWVzc2FnZSI6eyJkZXNjcmlwdGlvbiI6Ik1lc3NhZ2UgcHJvdmlkZXMgZGV0YWlsIGFib3V0IHRoZSBjdXJyZW50IHBoYXNlIiwidHlwZSI6InN0cmluZyJ9LCJvYnNlcnZlZEdlbmVyYXRpb24iOnsiZGVzY3JpcHRpb24iOiJPYnNlcnZlZEdlbmVyYXRpb24gaXMgdGhlIG1vc3QgcmVjZW50IGdlbmVyYXRpb24gb2JzZXJ2ZWQgYnkgdGhlIGNvbnRyb2xsZXIuIiwiZm9ybWF0IjoiaW50NjQiLCJ0eXBlIjoiaW50ZWdlciJ9LCJvc0J1aWxkc0RlcGxveWVkIjp7ImRlc2NyaXB0aW9uIjoiT1NCdWlsZHNEZXBsb3llZCBpbmRpY2F0ZXMgaWYgdGhlIE9TIEJ1aWxkcyBUZWt0b24gdGFza3MgYXJlIGN1cnJlbnRseSBkZXBsb3llZCIsInR5cGUiOiJib29sZWFuIn0sInBoYXNlIjp7ImRlc2NyaXB0aW9uIjoiUGhhc2UgcmVwcmVzZW50cyB0aGUgY3VycmVudCBwaGFzZSAoUmVhZHksIFJlY29uY2lsaW5nLCBGYWlsZWQpIiwidHlwZSI6InN0cmluZyJ9LCJ3ZWJVSURlcGxveWVkIjp7ImRlc2NyaXB0aW9uIjoiV2ViVUlEZXBsb3llZCBpbmRpY2F0ZXMgaWYgdGhlIFdlYlVJIGlzIGN1cnJlbnRseSBkZXBsb3llZCIsInR5cGUiOiJib29sZWFuIn19LCJ0eXBlIjoib2JqZWN0In19LCJ0eXBlIjoib2JqZWN0In19LCJzZXJ2ZWQiOnRydWUsInN0b3JhZ2UiOnRydWUsInN1YnJlc291cmNlcyI6eyJzdGF0dXMiOnt9fX1dfSwic3RhdHVzIjp7ImFjY2VwdGVkTmFtZXMiOnsia2luZCI6IiIsInBsdXJhbCI6IiJ9LCJjb25kaXRpb25zIjpudWxsLCJzdG9yZWRWZXJzaW9ucyI6bnVsbH19 - type: olm.bundle.object value: - data: eyJhcGlWZXJzaW9uIjoib3BlcmF0b3JzLmNvcmVvcy5jb20vdjFhbHBoYTEiLCJraW5kIjoiQ2x1c3RlclNlcnZpY2VWZXJzaW9uIiwibWV0YWRhdGEiOnsiYW5ub3RhdGlvbnMiOnsiYWxtLWV4YW1wbGVzIjoiW1xuICB7XG4gICAgXCJhcGlWZXJzaW9uXCI6IFwiYXV0b21vdGl2ZS5zZHYuY2xvdWQucmVkaGF0LmNvbS92MWFscGhhMVwiLFxuICAgIFwia2luZFwiOiBcIkltYWdlQnVpbGRcIixcbiAgICBcIm1ldGFkYXRhXCI6IHtcbiAgICAgIFwiYW5ub3RhdGlvbnNcIjoge1xuICAgICAgICBcImRlc2NyaXB0aW9uXCI6IFwiRXhhbXBsZSBJbWFnZUJ1aWxkIENSLiBUaGUgYXJjaGl0ZWN0dXJlIGNhbiBiZSBzZXQgdG8gYW55IHN1cHBvcnRlZCB2YWx1ZSBsaWtlIFxcXCJhYXJjaDY0XFxcIiwgXFxcIng4Nl82NFxcXCIsIGV0Yy4gU2VlIHlvdXIgYnVpbGQgcGxhdGZvcm0ncyBkb2N1bWVudGF0aW9uIGZvciBzdXBwb3J0ZWQgdmFsdWVzLlxcblwiXG4gICAgICB9LFxuICAgICAgXCJsYWJlbHNcIjoge1xuICAgICAgICBcImFwcC5rdWJlcm5ldGVzLmlvL21hbmFnZWQtYnlcIjogXCJrdXN0b21pemVcIixcbiAgICAgICAgXCJhcHAua3ViZXJuZXRlcy5pby9uYW1lXCI6IFwiYXV0b21vdGl2ZS1kZXYtb3BlcmF0b3JcIlxuICAgICAgfSxcbiAgICAgIFwibmFtZVwiOiBcImltYWdlYnVpbGQtc2FtcGxlXCJcbiAgICB9LFxuICAgIFwic3BlY1wiOiB7XG4gICAgICBcImFyY2hpdGVjdHVyZVwiOiBcImFtZDY0XCIsXG4gICAgICBcImF1dG9tb3RpdmVJbWFnZUJ1aWxkZXJcIjogXCJxdWF5LmlvL2NlbnRvcy1zaWctYXV0b21vdGl2ZS9hdXRvbW90aXZlLWltYWdlLWJ1aWxkZXI6MS4wLjBcIixcbiAgICAgIFwiZGlzdHJvXCI6IFwiY3M5XCIsXG4gICAgICBcImV4cG9ydEZvcm1hdFwiOiBcInFjb3cyXCIsXG4gICAgICBcIm1hbmlmZXN0Q29uZmlnTWFwXCI6IFwibXBwXCIsXG4gICAgICBcIm1vZGVcIjogXCJpbWFnZVwiLFxuICAgICAgXCJzZXJ2ZUFydGlmYWN0XCI6IGZhbHNlLFxuICAgICAgXCJzZXJ2ZUV4cGlyeUhvdXJzXCI6IDI0LFxuICAgICAgXCJ0YXJnZXRcIjogXCJxZW11XCJcbiAgICB9XG4gIH0sXG4gIHtcbiAgICBcImFwaVZlcnNpb25cIjogXCJhdXRvbW90aXZlLnNkdi5jbG91ZC5yZWRoYXQuY29tL3YxYWxwaGExXCIsXG4gICAgXCJraW5kXCI6IFwiT3BlcmF0b3JDb25maWdcIixcbiAgICBcIm1ldGFkYXRhXCI6IHtcbiAgICAgIFwibGFiZWxzXCI6IHtcbiAgICAgICAgXCJhcHAua3ViZXJuZXRlcy5pby9tYW5hZ2VkLWJ5XCI6IFwia3VzdG9taXplXCIsXG4gICAgICAgIFwiYXBwLmt1YmVybmV0ZXMuaW8vbmFtZVwiOiBcImF1dG9tb3RpdmUtZGV2LW9wZXJhdG9yXCJcbiAgICAgIH0sXG4gICAgICBcIm5hbWVcIjogXCJhdXRvbW90aXZlLWRldlwiLFxuICAgICAgXCJuYW1lc3BhY2VcIjogXCJhdXRvbW90aXZlLWRldi1vcGVyYXRvci1zeXN0ZW1cIlxuICAgIH0sXG4gICAgXCJzcGVjXCI6IHtcbiAgICAgIFwib3NCdWlsZHNcIjoge1xuICAgICAgICBcImVuYWJsZWRcIjogdHJ1ZSxcbiAgICAgICAgXCJwdmNTaXplXCI6IFwiOEdpXCIsXG4gICAgICAgIFwic2VydmVFeHBpcnlIb3Vyc1wiOiAyNFxuICAgICAgfSxcbiAgICAgIFwid2ViVUlcIjogdHJ1ZVxuICAgIH1cbiAgfVxuXSIsImNhcGFiaWxpdGllcyI6IkJhc2ljIEluc3RhbGwiLCJjYXRlZ29yaWVzIjoiRGV2ZWxvcGVyIFRvb2xzIiwiY29udGFpbmVySW1hZ2UiOiJxdWF5LmlvL3JoLXNkdi1jbG91ZC9hdXRvbW90aXZlLWRldi1vcGVyYXRvcjpsYXRlc3QiLCJjcmVhdGVkQXQiOiIyMDI2LTAxLTA4VDA5OjQ3OjE1WiIsIm9wZXJhdG9yZnJhbWV3b3JrLmlvL3N1Z2dlc3RlZC1uYW1lc3BhY2UiOiJhdXRvbW90aXZlLWRldi1vcGVyYXRvci1zeXN0ZW0iLCJvcGVyYXRvcnMub3BlcmF0b3JmcmFtZXdvcmsuaW8vYnVpbGRlciI6Im9wZXJhdG9yLXNkay12MS40Mi4wIiwib3BlcmF0b3JzLm9wZXJhdG9yZnJhbWV3b3JrLmlvL3Byb2plY3RfbGF5b3V0IjoiZ28ua3ViZWJ1aWxkZXIuaW8vdjQiLCJyZXBvc2l0b3J5IjoiaHR0cHM6Ly9naXRodWIuY29tL2NlbnRvcy1hdXRvbW90aXZlLXN1aXRlL2F1dG9tb3RpdmUtZGV2LW9wZXJhdG9yIiwic3VwcG9ydCI6IlJlZCBIYXQifSwibmFtZSI6ImF1dG9tb3RpdmUtZGV2LW9wZXJhdG9yLnYwLjAuMSIsIm5hbWVzcGFjZSI6InBsYWNlaG9sZGVyIn0sInNwZWMiOnsiYXBpc2VydmljZWRlZmluaXRpb25zIjp7fSwiY3VzdG9tcmVzb3VyY2VkZWZpbml0aW9ucyI6eyJvd25lZCI6W3sia2luZCI6IkNhdGFsb2dJbWFnZSIsIm5hbWUiOiJjYXRhbG9naW1hZ2VzLmF1dG9tb3RpdmUuc2R2LmNsb3VkLnJlZGhhdC5jb20iLCJ2ZXJzaW9uIjoidjFhbHBoYTEifSx7ImRlc2NyaXB0aW9uIjoiSW1hZ2VCdWlsZCBpcyB0aGUgU2NoZW1hIGZvciB0aGUgaW1hZ2VidWlsZHMgQVBJIiwiZGlzcGxheU5hbWUiOiJJbWFnZSBCdWlsZCIsImtpbmQiOiJJbWFnZUJ1aWxkIiwibmFtZSI6ImltYWdlYnVpbGRzLmF1dG9tb3RpdmUuc2R2LmNsb3VkLnJlZGhhdC5jb20iLCJ2ZXJzaW9uIjoidjFhbHBoYTEifSx7ImRlc2NyaXB0aW9uIjoiSW1hZ2UgaXMgdGhlIFNjaGVtYSBmb3IgdGhlIGltYWdlcyBBUEkiLCJkaXNwbGF5TmFtZSI6IkltYWdlIiwia2luZCI6IkltYWdlIiwibmFtZSI6ImltYWdlcy5hdXRvbW90aXZlLnNkdi5jbG91ZC5yZWRoYXQuY29tIiwidmVyc2lvbiI6InYxYWxwaGExIn0seyJkZXNjcmlwdGlvbiI6Ik9wZXJhdG9yQ29uZmlnIGlzIHRoZSBTY2hlbWEgZm9yIHRoZSBvcGVyYXRvcmNvbmZpZ3MgQVBJIiwiZGlzcGxheU5hbWUiOiJPcGVyYXRvciBDb25maWciLCJraW5kIjoiT3BlcmF0b3JDb25maWciLCJuYW1lIjoib3BlcmF0b3Jjb25maWdzLmF1dG9tb3RpdmUuc2R2LmNsb3VkLnJlZGhhdC5jb20iLCJ2ZXJzaW9uIjoidjFhbHBoYTEifV19LCJkZXNjcmlwdGlvbiI6IkNlbnRPUyBBdXRvbW90aXZlIFN1aXRlIiwiZGlzcGxheU5hbWUiOiJDZW50T1MgQXV0b21vdGl2ZSBTdWl0ZSIsImljb24iOlt7ImJhc2U2NGRhdGEiOiIiLCJtZWRpYXR5cGUiOiIifV0sImluc3RhbGwiOnsic3BlYyI6eyJjbHVzdGVyUGVybWlzc2lvbnMiOlt7InJ1bGVzIjpbeyJhcGlHcm91cHMiOlsiIl0sInJlc291cmNlcyI6WyJjb25maWdtYXBzIiwicGVyc2lzdGVudHZvbHVtZWNsYWltcyIsInBvZHMiLCJzZWNyZXRzIiwic2VydmljZWFjY291bnRzIiwic2VydmljZXMiXSwidmVyYnMiOlsiY3JlYXRlIiwiZGVsZXRlIiwiZ2V0IiwibGlzdCIsInBhdGNoIiwidXBkYXRlIiwid2F0Y2giXX0seyJhcGlHcm91cHMiOlsiIl0sInJlc291cmNlcyI6WyJldmVudHMiXSwidmVyYnMiOlsiY3JlYXRlIiwicGF0Y2giXX0seyJhcGlHcm91cHMiOlsiIl0sInJlc291cmNlcyI6WyJuYW1lc3BhY2VzIl0sInZlcmJzIjpbImNyZWF0ZSIsImdldCIsImxpc3QiLCJ3YXRjaCJdfSx7ImFwaUdyb3VwcyI6WyIiXSwicmVzb3VyY2VzIjpbInBvZHMvZXhlYyJdLCJ2ZXJicyI6WyJjcmVhdGUiXX0seyJhcGlHcm91cHMiOlsiIl0sInJlc291cmNlcyI6WyJwb2RzL2xvZyJdLCJ2ZXJicyI6WyJnZXQiXX0seyJhcGlHcm91cHMiOlsiYXBwcyJdLCJyZXNvdXJjZXMiOlsiZGVwbG95bWVudHMiXSwidmVyYnMiOlsiY3JlYXRlIiwiZGVsZXRlIiwiZ2V0IiwibGlzdCIsInBhdGNoIiwidXBkYXRlIiwid2F0Y2giXX0seyJhcGlHcm91cHMiOlsiYXV0b21vdGl2ZS5zZHYuY2xvdWQucmVkaGF0LmNvbSJdLCJyZXNvdXJjZXMiOlsiY2F0YWxvZ2ltYWdlcyIsImltYWdlYnVpbGRzIiwiaW1hZ2VzIiwib3BlcmF0b3Jjb25maWdzIl0sInZlcmJzIjpbImNyZWF0ZSIsImRlbGV0ZSIsImdldCIsImxpc3QiLCJwYXRjaCIsInVwZGF0ZSIsIndhdGNoIl19LHsiYXBpR3JvdXBzIjpbImF1dG9tb3RpdmUuc2R2LmNsb3VkLnJlZGhhdC5jb20iXSwicmVzb3VyY2VzIjpbImNhdGFsb2dpbWFnZXMvZmluYWxpemVycyIsImltYWdlYnVpbGRzL2ZpbmFsaXplcnMiLCJpbWFnZXMvZmluYWxpemVycyIsIm9wZXJhdG9yY29uZmlncy9maW5hbGl6ZXJzIl0sInZlcmJzIjpbInVwZGF0ZSJdfSx7ImFwaUdyb3VwcyI6WyJhdXRvbW90aXZlLnNkdi5jbG91ZC5yZWRoYXQuY29tIl0sInJlc291cmNlcyI6WyJjYXRhbG9naW1hZ2VzL3N0YXR1cyIsImltYWdlYnVpbGRzL3N0YXR1cyIsImltYWdlcy9zdGF0dXMiLCJvcGVyYXRvcmNvbmZpZ3Mvc3RhdHVzIl0sInZlcmJzIjpbImdldCIsInBhdGNoIiwidXBkYXRlIl19LHsiYXBpR3JvdXBzIjpbIm5ldHdvcmtpbmcuazhzLmlvIl0sInJlc291cmNlcyI6WyJpbmdyZXNzZXMiXSwidmVyYnMiOlsiY3JlYXRlIiwiZGVsZXRlIiwiZ2V0IiwibGlzdCIsInBhdGNoIiwidXBkYXRlIiwid2F0Y2giXX0seyJhcGlHcm91cHMiOlsicmJhYy5hdXRob3JpemF0aW9uLms4cy5pbyJdLCJyZXNvdXJjZXMiOlsiY2x1c3RlcnJvbGViaW5kaW5ncyIsImNsdXN0ZXJyb2xlcyIsInJvbGViaW5kaW5ncyJdLCJ2ZXJicyI6WyJjcmVhdGUiLCJkZWxldGUiLCJnZXQiLCJsaXN0IiwicGF0Y2giLCJ1cGRhdGUiLCJ3YXRjaCJdfSx7ImFwaUdyb3VwcyI6WyJyb3V0ZS5vcGVuc2hpZnQuaW8iXSwicmVzb3VyY2VzIjpbInJvdXRlcyJdLCJ2ZXJicyI6WyJjcmVhdGUiLCJkZWxldGUiLCJnZXQiLCJsaXN0IiwicGF0Y2giLCJ1cGRhdGUiLCJ3YXRjaCJdfSx7ImFwaUdyb3VwcyI6WyJzZWN1cml0eS5vcGVuc2hpZnQuaW8iXSwicmVzb3VyY2VzIjpbInNlY3VyaXR5Y29udGV4dGNvbnN0cmFpbnRzIl0sInZlcmJzIjpbImNyZWF0ZSIsImRlbGV0ZSIsImdldCIsImxpc3QiLCJwYXRjaCIsInVwZGF0ZSIsInVzZSIsIndhdGNoIl19LHsiYXBpR3JvdXBzIjpbInRla3Rvbi5kZXYiXSwicmVzb3VyY2VzIjpbInBpcGVsaW5lcnVucyIsInBpcGVsaW5lcyIsInRhc2tydW5zIiwidGFza3MiXSwidmVyYnMiOlsiY3JlYXRlIiwiZGVsZXRlIiwiZ2V0IiwibGlzdCIsInBhdGNoIiwidXBkYXRlIiwid2F0Y2giXX1dLCJzZXJ2aWNlQWNjb3VudE5hbWUiOiJhZG8tY29udHJvbGxlci1tYW5hZ2VyIn1dLCJkZXBsb3ltZW50cyI6W3sibGFiZWwiOnsiYXBwLmt1YmVybmV0ZXMuaW8vbWFuYWdlZC1ieSI6Imt1c3RvbWl6ZSIsImFwcC5rdWJlcm5ldGVzLmlvL25hbWUiOiJhdXRvbW90aXZlLWRldi1vcGVyYXRvciIsImNvbnRyb2wtcGxhbmUiOiJjb250cm9sbGVyLW1hbmFnZXIifSwibmFtZSI6ImFkby1jb250cm9sbGVyLW1hbmFnZXIiLCJzcGVjIjp7InJlcGxpY2FzIjoxLCJzZWxlY3RvciI6eyJtYXRjaExhYmVscyI6eyJjb250cm9sLXBsYW5lIjoiY29udHJvbGxlci1tYW5hZ2VyIn19LCJzdHJhdGVneSI6e30sInRlbXBsYXRlIjp7Im1ldGFkYXRhIjp7ImFubm90YXRpb25zIjp7Imt1YmVjdGwua3ViZXJuZXRlcy5pby9kZWZhdWx0LWNvbnRhaW5lciI6Im1hbmFnZXIifSwibGFiZWxzIjp7ImNvbnRyb2wtcGxhbmUiOiJjb250cm9sbGVyLW1hbmFnZXIifX0sInNwZWMiOnsiYWZmaW5pdHkiOnsibm9kZUFmZmluaXR5Ijp7InJlcXVpcmVkRHVyaW5nU2NoZWR1bGluZ0lnbm9yZWREdXJpbmdFeGVjdXRpb24iOnsibm9kZVNlbGVjdG9yVGVybXMiOlt7Im1hdGNoRXhwcmVzc2lvbnMiOlt7ImtleSI6Imt1YmVybmV0ZXMuaW8vYXJjaCIsIm9wZXJhdG9yIjoiSW4iLCJ2YWx1ZXMiOlsiYXJtNjQiLCJhbWQ2NCJdfSx7ImtleSI6Imt1YmVybmV0ZXMuaW8vb3MiLCJvcGVyYXRvciI6IkluIiwidmFsdWVzIjpbImxpbnV4Il19XX1dfX19LCJjb250YWluZXJzIjpbeyJhcmdzIjpbIi0tbGVhZGVyLWVsZWN0IiwiLS1oZWFsdGgtcHJvYmUtYmluZC1hZGRyZXNzPTo4MDgxIiwiLS1tZXRyaWNzLWJpbmQtYWRkcmVzcz0wIl0sImNvbW1hbmQiOlsiL21hbmFnZXIiXSwiZW52IjpbeyJuYW1lIjoiQlVJTERfQVBJX05BTUVTUEFDRSIsInZhbHVlRnJvbSI6eyJmaWVsZFJlZiI6eyJmaWVsZFBhdGgiOiJtZXRhZGF0YS5uYW1lc3BhY2UifX19LHsibmFtZSI6Ik9QRVJBVE9SX0lNQUdFIiwidmFsdWUiOiJpbWFnZS1yZWdpc3RyeS5vcGVuc2hpZnQtaW1hZ2UtcmVnaXN0cnkuc3ZjOjUwMDAvYXV0b21vdGl2ZS1kZXYtb3BlcmF0b3Itc3lzdGVtL2F1dG9tb3RpdmUtZGV2LW9wZXJhdG9yOmxhdGVzdCJ9XSwiaW1hZ2UiOiJkZWZhdWx0LXJvdXRlLW9wZW5zaGlmdC1pbWFnZS1yZWdpc3RyeS5hcHBzLmF1dG9tb3RpdmUxLm9jcC5hdXRvbW90aXZlLnNpZy5jZW50b3Mub3JnL2F1dG9tb3RpdmUtZGV2LW9wZXJhdG9yLXN5c3RlbS9hdXRvbW90aXZlLWRldi1vcGVyYXRvcjpsYXRlc3QiLCJpbWFnZVB1bGxQb2xpY3kiOiJJZk5vdFByZXNlbnQiLCJsaXZlbmVzc1Byb2JlIjp7Imh0dHBHZXQiOnsicGF0aCI6Ii9oZWFsdGh6IiwicG9ydCI6ODA4MX0sImluaXRpYWxEZWxheVNlY29uZHMiOjE1LCJwZXJpb2RTZWNvbmRzIjoyMH0sIm5hbWUiOiJtYW5hZ2VyIiwicG9ydHMiOlt7ImNvbnRhaW5lclBvcnQiOjgwODAsIm5hbWUiOiJidWlsZC1hcGkifV0sInJlYWRpbmVzc1Byb2JlIjp7Imh0dHBHZXQiOnsicGF0aCI6Ii9yZWFkeXoiLCJwb3J0Ijo4MDgxfSwiaW5pdGlhbERlbGF5U2Vjb25kcyI6NSwicGVyaW9kU2Vjb25kcyI6MTB9LCJyZXNvdXJjZXMiOnsibGltaXRzIjp7ImNwdSI6IjEiLCJtZW1vcnkiOiI1MTJNaSJ9LCJyZXF1ZXN0cyI6eyJjcHUiOiIxMDBtIiwibWVtb3J5IjoiMjU2TWkifX0sInNlY3VyaXR5Q29udGV4dCI6eyJhbGxvd1ByaXZpbGVnZUVzY2FsYXRpb24iOmZhbHNlLCJjYXBhYmlsaXRpZXMiOnsiZHJvcCI6WyJBTEwiXX19fV0sImluaXRDb250YWluZXJzIjpbeyJjb21tYW5kIjpbIi9pbml0LXNlY3JldHMiXSwiZW52IjpbeyJuYW1lIjoiUE9EX05BTUVTUEFDRSIsInZhbHVlRnJvbSI6eyJmaWVsZFJlZiI6eyJmaWVsZFBhdGgiOiJtZXRhZGF0YS5uYW1lc3BhY2UifX19XSwiaW1hZ2UiOiJkZWZhdWx0LXJvdXRlLW9wZW5zaGlmdC1pbWFnZS1yZWdpc3RyeS5hcHBzLmF1dG9tb3RpdmUxLm9jcC5hdXRvbW90aXZlLnNpZy5jZW50b3Mub3JnL2F1dG9tb3RpdmUtZGV2LW9wZXJhdG9yLXN5c3RlbS9hdXRvbW90aXZlLWRldi1vcGVyYXRvcjpsYXRlc3QiLCJpbWFnZVB1bGxQb2xpY3kiOiJJZk5vdFByZXNlbnQiLCJuYW1lIjoiaW5pdC1vYXV0aC1zZWNyZXRzIiwicmVzb3VyY2VzIjp7ImxpbWl0cyI6eyJjcHUiOiIxMDBtIiwibWVtb3J5IjoiNjRNaSJ9LCJyZXF1ZXN0cyI6eyJjcHUiOiIxMG0iLCJtZW1vcnkiOiIzMk1pIn19LCJzZWN1cml0eUNvbnRleHQiOnsiYWxsb3dQcml2aWxlZ2VFc2NhbGF0aW9uIjpmYWxzZSwiY2FwYWJpbGl0aWVzIjp7ImRyb3AiOlsiQUxMIl19fX1dLCJzZWN1cml0eUNvbnRleHQiOnsicnVuQXNOb25Sb290Ijp0cnVlfSwic2VydmljZUFjY291bnROYW1lIjoiYWRvLWNvbnRyb2xsZXItbWFuYWdlciIsInRlcm1pbmF0aW9uR3JhY2VQZXJpb2RTZWNvbmRzIjoxMH19fX1dLCJwZXJtaXNzaW9ucyI6W3sicnVsZXMiOlt7ImFwaUdyb3VwcyI6WyIiXSwicmVzb3VyY2VzIjpbImNvbmZpZ21hcHMiXSwidmVyYnMiOlsiZ2V0IiwibGlzdCIsIndhdGNoIiwiY3JlYXRlIiwidXBkYXRlIiwicGF0Y2giLCJkZWxldGUiXX0seyJhcGlHcm91cHMiOlsiY29vcmRpbmF0aW9uLms4cy5pbyJdLCJyZXNvdXJjZXMiOlsibGVhc2VzIl0sInZlcmJzIjpbImdldCIsImxpc3QiLCJ3YXRjaCIsImNyZWF0ZSIsInVwZGF0ZSIsInBhdGNoIiwiZGVsZXRlIl19LHsiYXBpR3JvdXBzIjpbIiJdLCJyZXNvdXJjZXMiOlsiZXZlbnRzIl0sInZlcmJzIjpbImNyZWF0ZSIsInBhdGNoIl19XSwic2VydmljZUFjY291bnROYW1lIjoiYWRvLWNvbnRyb2xsZXItbWFuYWdlciJ9XX0sInN0cmF0ZWd5IjoiZGVwbG95bWVudCJ9LCJpbnN0YWxsTW9kZXMiOlt7InN1cHBvcnRlZCI6dHJ1ZSwidHlwZSI6Ik93bk5hbWVzcGFjZSJ9LHsic3VwcG9ydGVkIjp0cnVlLCJ0eXBlIjoiU2luZ2xlTmFtZXNwYWNlIn0seyJzdXBwb3J0ZWQiOmZhbHNlLCJ0eXBlIjoiTXVsdGlOYW1lc3BhY2UifSx7InN1cHBvcnRlZCI6ZmFsc2UsInR5cGUiOiJBbGxOYW1lc3BhY2VzIn1dLCJrZXl3b3JkcyI6WyJhdXRvbW90aXZlIl0sImxpbmtzIjpbeyJuYW1lIjoiQ2VudE9TIEF1dG9tb3RpdmUgU3VpdGUiLCJ1cmwiOiJodHRwczovL2dpdGh1Yi5jb20vY2VudG9zLWF1dG9tb3RpdmUtc3VpdGUvYXV0b21vdGl2ZS1kZXYtb3BlcmF0b3IifV0sIm1hdHVyaXR5IjoiYWxwaGEiLCJtaW5LdWJlVmVyc2lvbiI6IjEuMjYuMCIsInByb3ZpZGVyIjp7Im5hbWUiOiJSZWQgSGF0In0sInZlcnNpb24iOiIwLjAuMSJ9fQ== + data: eyJhcGlWZXJzaW9uIjoib3BlcmF0b3JzLmNvcmVvcy5jb20vdjFhbHBoYTEiLCJraW5kIjoiQ2x1c3RlclNlcnZpY2VWZXJzaW9uIiwibWV0YWRhdGEiOnsiYW5ub3RhdGlvbnMiOnsiYWxtLWV4YW1wbGVzIjoiW1xuICB7XG4gICAgXCJhcGlWZXJzaW9uXCI6IFwiYXV0b21vdGl2ZS5zZHYuY2xvdWQucmVkaGF0LmNvbS92MWFscGhhMVwiLFxuICAgIFwia2luZFwiOiBcIkltYWdlQnVpbGRcIixcbiAgICBcIm1ldGFkYXRhXCI6IHtcbiAgICAgIFwiYW5ub3RhdGlvbnNcIjoge1xuICAgICAgICBcImRlc2NyaXB0aW9uXCI6IFwiRXhhbXBsZSBJbWFnZUJ1aWxkIENSLiBUaGUgYXJjaGl0ZWN0dXJlIGNhbiBiZSBzZXQgdG8gYW55IHN1cHBvcnRlZCB2YWx1ZSBsaWtlIFxcXCJhYXJjaDY0XFxcIiwgXFxcIng4Nl82NFxcXCIsIGV0Yy4gU2VlIHlvdXIgYnVpbGQgcGxhdGZvcm0ncyBkb2N1bWVudGF0aW9uIGZvciBzdXBwb3J0ZWQgdmFsdWVzLlxcblwiXG4gICAgICB9LFxuICAgICAgXCJsYWJlbHNcIjoge1xuICAgICAgICBcImFwcC5rdWJlcm5ldGVzLmlvL21hbmFnZWQtYnlcIjogXCJrdXN0b21pemVcIixcbiAgICAgICAgXCJhcHAua3ViZXJuZXRlcy5pby9uYW1lXCI6IFwiYXV0b21vdGl2ZS1kZXYtb3BlcmF0b3JcIlxuICAgICAgfSxcbiAgICAgIFwibmFtZVwiOiBcImltYWdlYnVpbGQtc2FtcGxlXCJcbiAgICB9LFxuICAgIFwic3BlY1wiOiB7XG4gICAgICBcImFyY2hpdGVjdHVyZVwiOiBcImFtZDY0XCIsXG4gICAgICBcImF1dG9tb3RpdmVJbWFnZUJ1aWxkZXJcIjogXCJxdWF5LmlvL2NlbnRvcy1zaWctYXV0b21vdGl2ZS9hdXRvbW90aXZlLWltYWdlLWJ1aWxkZXI6MS4wLjBcIixcbiAgICAgIFwiZGlzdHJvXCI6IFwiY3M5XCIsXG4gICAgICBcImV4cG9ydEZvcm1hdFwiOiBcInFjb3cyXCIsXG4gICAgICBcIm1hbmlmZXN0Q29uZmlnTWFwXCI6IFwibXBwXCIsXG4gICAgICBcIm1vZGVcIjogXCJpbWFnZVwiLFxuICAgICAgXCJzZXJ2ZUFydGlmYWN0XCI6IGZhbHNlLFxuICAgICAgXCJzZXJ2ZUV4cGlyeUhvdXJzXCI6IDI0LFxuICAgICAgXCJ0YXJnZXRcIjogXCJxZW11XCJcbiAgICB9XG4gIH0sXG4gIHtcbiAgICBcImFwaVZlcnNpb25cIjogXCJhdXRvbW90aXZlLnNkdi5jbG91ZC5yZWRoYXQuY29tL3YxYWxwaGExXCIsXG4gICAgXCJraW5kXCI6IFwiT3BlcmF0b3JDb25maWdcIixcbiAgICBcIm1ldGFkYXRhXCI6IHtcbiAgICAgIFwibGFiZWxzXCI6IHtcbiAgICAgICAgXCJhcHAua3ViZXJuZXRlcy5pby9tYW5hZ2VkLWJ5XCI6IFwia3VzdG9taXplXCIsXG4gICAgICAgIFwiYXBwLmt1YmVybmV0ZXMuaW8vbmFtZVwiOiBcImF1dG9tb3RpdmUtZGV2LW9wZXJhdG9yXCJcbiAgICAgIH0sXG4gICAgICBcIm5hbWVcIjogXCJhdXRvbW90aXZlLWRldlwiLFxuICAgICAgXCJuYW1lc3BhY2VcIjogXCJhdXRvbW90aXZlLWRldi1vcGVyYXRvci1zeXN0ZW1cIlxuICAgIH0sXG4gICAgXCJzcGVjXCI6IHtcbiAgICAgIFwib3NCdWlsZHNcIjoge1xuICAgICAgICBcImVuYWJsZWRcIjogdHJ1ZSxcbiAgICAgICAgXCJwdmNTaXplXCI6IFwiOEdpXCIsXG4gICAgICAgIFwic2VydmVFeHBpcnlIb3Vyc1wiOiAyNFxuICAgICAgfSxcbiAgICAgIFwid2ViVUlcIjogdHJ1ZVxuICAgIH1cbiAgfVxuXSIsImNhcGFiaWxpdGllcyI6IkJhc2ljIEluc3RhbGwiLCJjYXRlZ29yaWVzIjoiRGV2ZWxvcGVyIFRvb2xzIiwiY29udGFpbmVySW1hZ2UiOiJxdWF5LmlvL3JoLXNkdi1jbG91ZC9hdXRvbW90aXZlLWRldi1vcGVyYXRvcjpsYXRlc3QiLCJjcmVhdGVkQXQiOiIyMDI2LTAxLTA5VDExOjExOjI1WiIsIm9wZXJhdG9yZnJhbWV3b3JrLmlvL3N1Z2dlc3RlZC1uYW1lc3BhY2UiOiJhdXRvbW90aXZlLWRldi1vcGVyYXRvci1zeXN0ZW0iLCJvcGVyYXRvcnMub3BlcmF0b3JmcmFtZXdvcmsuaW8vYnVpbGRlciI6Im9wZXJhdG9yLXNkay12MS40Mi4wIiwib3BlcmF0b3JzLm9wZXJhdG9yZnJhbWV3b3JrLmlvL3Byb2plY3RfbGF5b3V0IjoiZ28ua3ViZWJ1aWxkZXIuaW8vdjQiLCJyZXBvc2l0b3J5IjoiaHR0cHM6Ly9naXRodWIuY29tL2NlbnRvcy1hdXRvbW90aXZlLXN1aXRlL2F1dG9tb3RpdmUtZGV2LW9wZXJhdG9yIiwic3VwcG9ydCI6IlJlZCBIYXQifSwibmFtZSI6ImF1dG9tb3RpdmUtZGV2LW9wZXJhdG9yLnYwLjAuMSIsIm5hbWVzcGFjZSI6InBsYWNlaG9sZGVyIn0sInNwZWMiOnsiYXBpc2VydmljZWRlZmluaXRpb25zIjp7fSwiY3VzdG9tcmVzb3VyY2VkZWZpbml0aW9ucyI6eyJvd25lZCI6W3sia2luZCI6IkNhdGFsb2dJbWFnZSIsIm5hbWUiOiJjYXRhbG9naW1hZ2VzLmF1dG9tb3RpdmUuc2R2LmNsb3VkLnJlZGhhdC5jb20iLCJ2ZXJzaW9uIjoidjFhbHBoYTEifSx7ImRlc2NyaXB0aW9uIjoiSW1hZ2VCdWlsZCBpcyB0aGUgU2NoZW1hIGZvciB0aGUgaW1hZ2VidWlsZHMgQVBJIiwiZGlzcGxheU5hbWUiOiJJbWFnZSBCdWlsZCIsImtpbmQiOiJJbWFnZUJ1aWxkIiwibmFtZSI6ImltYWdlYnVpbGRzLmF1dG9tb3RpdmUuc2R2LmNsb3VkLnJlZGhhdC5jb20iLCJ2ZXJzaW9uIjoidjFhbHBoYTEifSx7ImRlc2NyaXB0aW9uIjoiSW1hZ2UgaXMgdGhlIFNjaGVtYSBmb3IgdGhlIGltYWdlcyBBUEkiLCJkaXNwbGF5TmFtZSI6IkltYWdlIiwia2luZCI6IkltYWdlIiwibmFtZSI6ImltYWdlcy5hdXRvbW90aXZlLnNkdi5jbG91ZC5yZWRoYXQuY29tIiwidmVyc2lvbiI6InYxYWxwaGExIn0seyJkZXNjcmlwdGlvbiI6Ik9wZXJhdG9yQ29uZmlnIGlzIHRoZSBTY2hlbWEgZm9yIHRoZSBvcGVyYXRvcmNvbmZpZ3MgQVBJIiwiZGlzcGxheU5hbWUiOiJPcGVyYXRvciBDb25maWciLCJraW5kIjoiT3BlcmF0b3JDb25maWciLCJuYW1lIjoib3BlcmF0b3Jjb25maWdzLmF1dG9tb3RpdmUuc2R2LmNsb3VkLnJlZGhhdC5jb20iLCJ2ZXJzaW9uIjoidjFhbHBoYTEifV19LCJkZXNjcmlwdGlvbiI6IkNlbnRPUyBBdXRvbW90aXZlIFN1aXRlIiwiZGlzcGxheU5hbWUiOiJDZW50T1MgQXV0b21vdGl2ZSBTdWl0ZSIsImljb24iOlt7ImJhc2U2NGRhdGEiOiIiLCJtZWRpYXR5cGUiOiIifV0sImluc3RhbGwiOnsic3BlYyI6eyJjbHVzdGVyUGVybWlzc2lvbnMiOlt7InJ1bGVzIjpbeyJhcGlHcm91cHMiOlsiIl0sInJlc291cmNlcyI6WyJjb25maWdtYXBzIiwicGVyc2lzdGVudHZvbHVtZWNsYWltcyIsInBvZHMiLCJzZWNyZXRzIiwic2VydmljZWFjY291bnRzIiwic2VydmljZXMiXSwidmVyYnMiOlsiY3JlYXRlIiwiZGVsZXRlIiwiZ2V0IiwibGlzdCIsInBhdGNoIiwidXBkYXRlIiwid2F0Y2giXX0seyJhcGlHcm91cHMiOlsiIl0sInJlc291cmNlcyI6WyJldmVudHMiXSwidmVyYnMiOlsiY3JlYXRlIiwicGF0Y2giXX0seyJhcGlHcm91cHMiOlsiIl0sInJlc291cmNlcyI6WyJuYW1lc3BhY2VzIl0sInZlcmJzIjpbImNyZWF0ZSIsImdldCIsImxpc3QiLCJ3YXRjaCJdfSx7ImFwaUdyb3VwcyI6WyIiXSwicmVzb3VyY2VzIjpbInBvZHMvZXhlYyJdLCJ2ZXJicyI6WyJjcmVhdGUiXX0seyJhcGlHcm91cHMiOlsiIl0sInJlc291cmNlcyI6WyJwb2RzL2xvZyJdLCJ2ZXJicyI6WyJnZXQiXX0seyJhcGlHcm91cHMiOlsiYXBwcyJdLCJyZXNvdXJjZXMiOlsiZGVwbG95bWVudHMiXSwidmVyYnMiOlsiY3JlYXRlIiwiZGVsZXRlIiwiZ2V0IiwibGlzdCIsInBhdGNoIiwidXBkYXRlIiwid2F0Y2giXX0seyJhcGlHcm91cHMiOlsiYXV0b21vdGl2ZS5zZHYuY2xvdWQucmVkaGF0LmNvbSJdLCJyZXNvdXJjZXMiOlsiY2F0YWxvZ2ltYWdlcyIsImltYWdlYnVpbGRzIiwiaW1hZ2VzIiwib3BlcmF0b3Jjb25maWdzIl0sInZlcmJzIjpbImNyZWF0ZSIsImRlbGV0ZSIsImdldCIsImxpc3QiLCJwYXRjaCIsInVwZGF0ZSIsIndhdGNoIl19LHsiYXBpR3JvdXBzIjpbImF1dG9tb3RpdmUuc2R2LmNsb3VkLnJlZGhhdC5jb20iXSwicmVzb3VyY2VzIjpbImNhdGFsb2dpbWFnZXMvZmluYWxpemVycyIsImltYWdlYnVpbGRzL2ZpbmFsaXplcnMiLCJpbWFnZXMvZmluYWxpemVycyIsIm9wZXJhdG9yY29uZmlncy9maW5hbGl6ZXJzIl0sInZlcmJzIjpbInVwZGF0ZSJdfSx7ImFwaUdyb3VwcyI6WyJhdXRvbW90aXZlLnNkdi5jbG91ZC5yZWRoYXQuY29tIl0sInJlc291cmNlcyI6WyJjYXRhbG9naW1hZ2VzL3N0YXR1cyIsImltYWdlYnVpbGRzL3N0YXR1cyIsImltYWdlcy9zdGF0dXMiLCJvcGVyYXRvcmNvbmZpZ3Mvc3RhdHVzIl0sInZlcmJzIjpbImdldCIsInBhdGNoIiwidXBkYXRlIl19LHsiYXBpR3JvdXBzIjpbIm5ldHdvcmtpbmcuazhzLmlvIl0sInJlc291cmNlcyI6WyJpbmdyZXNzZXMiXSwidmVyYnMiOlsiY3JlYXRlIiwiZGVsZXRlIiwiZ2V0IiwibGlzdCIsInBhdGNoIiwidXBkYXRlIiwid2F0Y2giXX0seyJhcGlHcm91cHMiOlsicmJhYy5hdXRob3JpemF0aW9uLms4cy5pbyJdLCJyZXNvdXJjZXMiOlsiY2x1c3RlcnJvbGViaW5kaW5ncyIsImNsdXN0ZXJyb2xlcyIsInJvbGViaW5kaW5ncyJdLCJ2ZXJicyI6WyJjcmVhdGUiLCJkZWxldGUiLCJnZXQiLCJsaXN0IiwicGF0Y2giLCJ1cGRhdGUiLCJ3YXRjaCJdfSx7ImFwaUdyb3VwcyI6WyJyb3V0ZS5vcGVuc2hpZnQuaW8iXSwicmVzb3VyY2VzIjpbInJvdXRlcyJdLCJ2ZXJicyI6WyJjcmVhdGUiLCJkZWxldGUiLCJnZXQiLCJsaXN0IiwicGF0Y2giLCJ1cGRhdGUiLCJ3YXRjaCJdfSx7ImFwaUdyb3VwcyI6WyJzZWN1cml0eS5vcGVuc2hpZnQuaW8iXSwicmVzb3VyY2VzIjpbInNlY3VyaXR5Y29udGV4dGNvbnN0cmFpbnRzIl0sInZlcmJzIjpbImNyZWF0ZSIsImRlbGV0ZSIsImdldCIsImxpc3QiLCJwYXRjaCIsInVwZGF0ZSIsInVzZSIsIndhdGNoIl19LHsiYXBpR3JvdXBzIjpbInRla3Rvbi5kZXYiXSwicmVzb3VyY2VzIjpbInBpcGVsaW5lcnVucyIsInBpcGVsaW5lcyIsInRhc2tydW5zIiwidGFza3MiXSwidmVyYnMiOlsiY3JlYXRlIiwiZGVsZXRlIiwiZ2V0IiwibGlzdCIsInBhdGNoIiwidXBkYXRlIiwid2F0Y2giXX1dLCJzZXJ2aWNlQWNjb3VudE5hbWUiOiJhZG8tY29udHJvbGxlci1tYW5hZ2VyIn1dLCJkZXBsb3ltZW50cyI6W3sibGFiZWwiOnsiYXBwLmt1YmVybmV0ZXMuaW8vbWFuYWdlZC1ieSI6Imt1c3RvbWl6ZSIsImFwcC5rdWJlcm5ldGVzLmlvL25hbWUiOiJhdXRvbW90aXZlLWRldi1vcGVyYXRvciIsImNvbnRyb2wtcGxhbmUiOiJjb250cm9sbGVyLW1hbmFnZXIifSwibmFtZSI6ImFkby1jb250cm9sbGVyLW1hbmFnZXIiLCJzcGVjIjp7InJlcGxpY2FzIjoxLCJzZWxlY3RvciI6eyJtYXRjaExhYmVscyI6eyJjb250cm9sLXBsYW5lIjoiY29udHJvbGxlci1tYW5hZ2VyIn19LCJzdHJhdGVneSI6e30sInRlbXBsYXRlIjp7Im1ldGFkYXRhIjp7ImFubm90YXRpb25zIjp7Imt1YmVjdGwua3ViZXJuZXRlcy5pby9kZWZhdWx0LWNvbnRhaW5lciI6Im1hbmFnZXIifSwibGFiZWxzIjp7ImNvbnRyb2wtcGxhbmUiOiJjb250cm9sbGVyLW1hbmFnZXIifX0sInNwZWMiOnsiYWZmaW5pdHkiOnsibm9kZUFmZmluaXR5Ijp7InJlcXVpcmVkRHVyaW5nU2NoZWR1bGluZ0lnbm9yZWREdXJpbmdFeGVjdXRpb24iOnsibm9kZVNlbGVjdG9yVGVybXMiOlt7Im1hdGNoRXhwcmVzc2lvbnMiOlt7ImtleSI6Imt1YmVybmV0ZXMuaW8vYXJjaCIsIm9wZXJhdG9yIjoiSW4iLCJ2YWx1ZXMiOlsiYXJtNjQiLCJhbWQ2NCJdfSx7ImtleSI6Imt1YmVybmV0ZXMuaW8vb3MiLCJvcGVyYXRvciI6IkluIiwidmFsdWVzIjpbImxpbnV4Il19XX1dfX19LCJjb250YWluZXJzIjpbeyJhcmdzIjpbIi0tbGVhZGVyLWVsZWN0IiwiLS1oZWFsdGgtcHJvYmUtYmluZC1hZGRyZXNzPTo4MDgxIiwiLS1tZXRyaWNzLWJpbmQtYWRkcmVzcz0wIl0sImNvbW1hbmQiOlsiL21hbmFnZXIiXSwiZW52IjpbeyJuYW1lIjoiQlVJTERfQVBJX05BTUVTUEFDRSIsInZhbHVlRnJvbSI6eyJmaWVsZFJlZiI6eyJmaWVsZFBhdGgiOiJtZXRhZGF0YS5uYW1lc3BhY2UifX19LHsibmFtZSI6Ik9QRVJBVE9SX0lNQUdFIiwidmFsdWUiOiJpbWFnZS1yZWdpc3RyeS5vcGVuc2hpZnQtaW1hZ2UtcmVnaXN0cnkuc3ZjOjUwMDAvYXV0b21vdGl2ZS1kZXYtb3BlcmF0b3Itc3lzdGVtL2F1dG9tb3RpdmUtZGV2LW9wZXJhdG9yOmxhdGVzdCJ9XSwiaW1hZ2UiOiJkZWZhdWx0LXJvdXRlLW9wZW5zaGlmdC1pbWFnZS1yZWdpc3RyeS5hcHBzLmF1dG9tb3RpdmUxLm9jcC5hdXRvbW90aXZlLnNpZy5jZW50b3Mub3JnL2F1dG9tb3RpdmUtZGV2LW9wZXJhdG9yLXN5c3RlbS9hdXRvbW90aXZlLWRldi1vcGVyYXRvcjpsYXRlc3QiLCJpbWFnZVB1bGxQb2xpY3kiOiJJZk5vdFByZXNlbnQiLCJsaXZlbmVzc1Byb2JlIjp7Imh0dHBHZXQiOnsicGF0aCI6Ii9oZWFsdGh6IiwicG9ydCI6ODA4MX0sImluaXRpYWxEZWxheVNlY29uZHMiOjE1LCJwZXJpb2RTZWNvbmRzIjoyMH0sIm5hbWUiOiJtYW5hZ2VyIiwicG9ydHMiOlt7ImNvbnRhaW5lclBvcnQiOjgwODAsIm5hbWUiOiJidWlsZC1hcGkifV0sInJlYWRpbmVzc1Byb2JlIjp7Imh0dHBHZXQiOnsicGF0aCI6Ii9yZWFkeXoiLCJwb3J0Ijo4MDgxfSwiaW5pdGlhbERlbGF5U2Vjb25kcyI6NSwicGVyaW9kU2Vjb25kcyI6MTB9LCJyZXNvdXJjZXMiOnsibGltaXRzIjp7ImNwdSI6IjEiLCJtZW1vcnkiOiI1MTJNaSJ9LCJyZXF1ZXN0cyI6eyJjcHUiOiIxMDBtIiwibWVtb3J5IjoiMjU2TWkifX0sInNlY3VyaXR5Q29udGV4dCI6eyJhbGxvd1ByaXZpbGVnZUVzY2FsYXRpb24iOmZhbHNlLCJjYXBhYmlsaXRpZXMiOnsiZHJvcCI6WyJBTEwiXX19fV0sImluaXRDb250YWluZXJzIjpbeyJjb21tYW5kIjpbIi9pbml0LXNlY3JldHMiXSwiZW52IjpbeyJuYW1lIjoiUE9EX05BTUVTUEFDRSIsInZhbHVlRnJvbSI6eyJmaWVsZFJlZiI6eyJmaWVsZFBhdGgiOiJtZXRhZGF0YS5uYW1lc3BhY2UifX19XSwiaW1hZ2UiOiJkZWZhdWx0LXJvdXRlLW9wZW5zaGlmdC1pbWFnZS1yZWdpc3RyeS5hcHBzLmF1dG9tb3RpdmUxLm9jcC5hdXRvbW90aXZlLnNpZy5jZW50b3Mub3JnL2F1dG9tb3RpdmUtZGV2LW9wZXJhdG9yLXN5c3RlbS9hdXRvbW90aXZlLWRldi1vcGVyYXRvcjpsYXRlc3QiLCJpbWFnZVB1bGxQb2xpY3kiOiJJZk5vdFByZXNlbnQiLCJuYW1lIjoiaW5pdC1vYXV0aC1zZWNyZXRzIiwicmVzb3VyY2VzIjp7ImxpbWl0cyI6eyJjcHUiOiIxMDBtIiwibWVtb3J5IjoiNjRNaSJ9LCJyZXF1ZXN0cyI6eyJjcHUiOiIxMG0iLCJtZW1vcnkiOiIzMk1pIn19LCJzZWN1cml0eUNvbnRleHQiOnsiYWxsb3dQcml2aWxlZ2VFc2NhbGF0aW9uIjpmYWxzZSwiY2FwYWJpbGl0aWVzIjp7ImRyb3AiOlsiQUxMIl19fX1dLCJzZWN1cml0eUNvbnRleHQiOnsicnVuQXNOb25Sb290Ijp0cnVlfSwic2VydmljZUFjY291bnROYW1lIjoiYWRvLWNvbnRyb2xsZXItbWFuYWdlciIsInRlcm1pbmF0aW9uR3JhY2VQZXJpb2RTZWNvbmRzIjoxMH19fX1dLCJwZXJtaXNzaW9ucyI6W3sicnVsZXMiOlt7ImFwaUdyb3VwcyI6WyIiXSwicmVzb3VyY2VzIjpbImNvbmZpZ21hcHMiXSwidmVyYnMiOlsiZ2V0IiwibGlzdCIsIndhdGNoIiwiY3JlYXRlIiwidXBkYXRlIiwicGF0Y2giLCJkZWxldGUiXX0seyJhcGlHcm91cHMiOlsiY29vcmRpbmF0aW9uLms4cy5pbyJdLCJyZXNvdXJjZXMiOlsibGVhc2VzIl0sInZlcmJzIjpbImdldCIsImxpc3QiLCJ3YXRjaCIsImNyZWF0ZSIsInVwZGF0ZSIsInBhdGNoIiwiZGVsZXRlIl19LHsiYXBpR3JvdXBzIjpbIiJdLCJyZXNvdXJjZXMiOlsiZXZlbnRzIl0sInZlcmJzIjpbImNyZWF0ZSIsInBhdGNoIl19XSwic2VydmljZUFjY291bnROYW1lIjoiYWRvLWNvbnRyb2xsZXItbWFuYWdlciJ9XX0sInN0cmF0ZWd5IjoiZGVwbG95bWVudCJ9LCJpbnN0YWxsTW9kZXMiOlt7InN1cHBvcnRlZCI6dHJ1ZSwidHlwZSI6Ik93bk5hbWVzcGFjZSJ9LHsic3VwcG9ydGVkIjp0cnVlLCJ0eXBlIjoiU2luZ2xlTmFtZXNwYWNlIn0seyJzdXBwb3J0ZWQiOmZhbHNlLCJ0eXBlIjoiTXVsdGlOYW1lc3BhY2UifSx7InN1cHBvcnRlZCI6ZmFsc2UsInR5cGUiOiJBbGxOYW1lc3BhY2VzIn1dLCJrZXl3b3JkcyI6WyJhdXRvbW90aXZlIl0sImxpbmtzIjpbeyJuYW1lIjoiQ2VudE9TIEF1dG9tb3RpdmUgU3VpdGUiLCJ1cmwiOiJodHRwczovL2dpdGh1Yi5jb20vY2VudG9zLWF1dG9tb3RpdmUtc3VpdGUvYXV0b21vdGl2ZS1kZXYtb3BlcmF0b3IifV0sIm1hdHVyaXR5IjoiYWxwaGEiLCJtaW5LdWJlVmVyc2lvbiI6IjEuMjYuMCIsInByb3ZpZGVyIjp7Im5hbWUiOiJSZWQgSGF0In0sInZlcnNpb24iOiIwLjAuMSJ9fQ== - type: olm.bundle.object value: data: eyJhcGlWZXJzaW9uIjoicmJhYy5hdXRob3JpemF0aW9uLms4cy5pby92MSIsImtpbmQiOiJDbHVzdGVyUm9sZSIsIm1ldGFkYXRhIjp7ImNyZWF0aW9uVGltZXN0YW1wIjpudWxsLCJsYWJlbHMiOnsiYXBwLmt1YmVybmV0ZXMuaW8vbWFuYWdlZC1ieSI6Imt1c3RvbWl6ZSIsImFwcC5rdWJlcm5ldGVzLmlvL25hbWUiOiJhZG8ifSwibmFtZSI6ImFkby1pbWFnZS12aWV3ZXItcm9sZSJ9LCJydWxlcyI6W3siYXBpR3JvdXBzIjpbImF1dG9tb3RpdmUuc2R2LmNsb3VkLnJlZGhhdC5jb20iXSwicmVzb3VyY2VzIjpbImltYWdlcyJdLCJ2ZXJicyI6WyJnZXQiLCJsaXN0Iiwid2F0Y2giXX0seyJhcGlHcm91cHMiOlsiYXV0b21vdGl2ZS5zZHYuY2xvdWQucmVkaGF0LmNvbSJdLCJyZXNvdXJjZXMiOlsiaW1hZ2VzL3N0YXR1cyJdLCJ2ZXJicyI6WyJnZXQiXX1dfQ== diff --git a/cmd/caib/README.md b/cmd/caib/README.md index 2e1210eb..650094af 100644 --- a/cmd/caib/README.md +++ b/cmd/caib/README.md @@ -25,8 +25,7 @@ export CAIB_SERVER=https://your-build-api.example Build a bootc container and push it to a registry: ```bash -bin/caib build-bootc manifest.aib.yml \ - --name my-bootc-build \ +bin/caib build manifest.aib.yml \ --arch arm64 \ --push quay.io/myorg/automotive-os:latest \ --follow @@ -43,14 +42,12 @@ bootc switch quay.io/myorg/automotive-os:latest Build a bootc container and also create a disk image from it: ```bash -bin/caib build-bootc manifest.aib.yml \ - --name my-disk-build \ +bin/caib build manifest.aib.yml \ --arch arm64 \ --push quay.io/myorg/automotive-os:latest \ - --build-disk-image \ - --format qcow2 \ - --export-oci quay.io/myorg/automotive-disk:latest \ - --download ./output/disk.qcow2 \ + --disk \ + --push-disk quay.io/myorg/automotive-disk:latest \ + -o ./output/disk.qcow2 \ --follow ``` @@ -59,49 +56,48 @@ bin/caib build-bootc manifest.aib.yml \ Build an ostree-based or package-based disk image: ```bash -bin/caib build-traditional manifest.aib.yml \ - --name my-traditional-build \ +bin/caib build-legacy manifest.aib.yml \ --arch arm64 \ --mode image \ - --export qcow2 \ - --download ./output/disk.qcow2 \ + --format qcow2 \ + -o ./output/disk.qcow2 \ --follow ``` ## Commands -### build-bootc +### build -Builds a bootc container image with optional disk image creation. +Builds a bootc container image with optional disk image creation. This is the recommended approach for production. ```bash -bin/caib build-bootc [flags] +bin/caib build [flags] ``` **Required flags:** | Flag | Description | |------|-------------| -| `--name` | Unique build name | -| `--arch` | Architecture (`amd64`, `arm64`) | +| `--push` | Push bootc container to registry (e.g., `quay.io/org/image:tag`) | **Optional flags:** | Flag | Default | Description | |------|---------|-------------| | `--server` | `$CAIB_SERVER` | Build API server URL | | `--token` | `$CAIB_TOKEN` | Bearer token (auto-detected from kubeconfig) | -| `--distro` | `autosd` | Distribution to build | -| `--target` | `qemu` | Target platform | -| `--push` | | Push bootc container to registry (e.g., `quay.io/org/image:tag`) | -| `--build-disk-image` | `false` | Also build a disk image from the container | -| `--format` | `qcow2` | Disk image format (`qcow2`, `raw`, `simg`) | +| `-n`, `--name` | (auto-generated) | Unique build name | +| `-d`, `--distro` | `autosd` | Distribution to build | +| `-t`, `--target` | `qemu` | Target platform | +| `-a`, `--arch` | (current system) | Architecture (`amd64`, `arm64`) | +| `--disk` | `false` | Also build a disk image from the container | +| `--format` | (inferred from `-o`) | Disk image format (`qcow2`, `raw`, `simg`) | | `--compress` | `gzip` | Compression algorithm (`gzip`, `lz4`, `xz`) | -| `--export-oci` | | Push disk image as OCI artifact to registry | -| `--download` | | Download disk image to local file (requires `--export-oci`) | +| `--push-disk` | | Push disk image as OCI artifact to registry | +| `-o`, `--output` | | Download disk image to local file (implies `--disk`) | | `--builder-image` | | Custom aib-build container | -| `--automotive-image-builder` | `quay.io/.../automotive-image-builder:latest` | AIB container image | +| `--aib-image` | `quay.io/.../automotive-image-builder:latest` | AIB container image | | `--storage-class` | | Storage class for build workspace PVC | -| `--define` | | Custom definition `KEY=VALUE` (repeatable) | -| `--registry-username` | `$REGISTRY_USERNAME` | Registry username for push/export | +| `-D`, `--define` | | Custom definition `KEY=VALUE` (repeatable) | +| `--registry-username` | `$REGISTRY_USERNAME` | Registry username for push operations | | `--registry-password` | `$REGISTRY_PASSWORD` | Registry password (or use docker/podman auth) | | `--timeout` | `60` | Timeout in minutes | | `-w`, `--wait` | `false` | Wait for build to complete | @@ -111,61 +107,102 @@ bin/caib build-bootc [flags] ```bash # Build and push bootc container only -bin/caib build-bootc my-manifest.aib.yml \ - --name bootc-only \ +bin/caib build my-manifest.aib.yml \ --arch arm64 \ --push quay.io/myorg/automotive:v1.0 \ --follow # Build bootc container + qcow2 disk image, download locally -bin/caib build-bootc my-manifest.aib.yml \ - --name full-build \ +bin/caib build my-manifest.aib.yml \ --arch arm64 \ --push quay.io/myorg/automotive:v1.0 \ - --build-disk-image \ + --disk \ --format qcow2 \ - --export-oci quay.io/myorg/automotive-disk:v1.0 \ - --download ./my-image.qcow2 \ + --push-disk quay.io/myorg/automotive-disk:v1.0 \ + -o ./my-image.qcow2 \ --follow # Use custom builder image -bin/caib build-bootc my-manifest.aib.yml \ - --name custom-builder \ +bin/caib build my-manifest.aib.yml \ --arch amd64 \ --builder-image quay.io/myorg/my-aib-build:latest \ --push quay.io/myorg/result:latest \ --follow ``` -### build-traditional +### disk -Builds a traditional (ostree or package-based) disk image. +Creates a disk image from an existing bootc container in a registry. ```bash -bin/caib build-traditional [flags] +bin/caib disk [flags] +``` + +**Optional flags:** +| Flag | Default | Description | +|------|---------|-------------| +| `--server` | `$CAIB_SERVER` | Build API server URL | +| `--token` | `$CAIB_TOKEN` | Bearer token | +| `-n`, `--name` | (auto-generated) | Build job name | +| `-o`, `--output` | | Download disk image to local file | +| `--format` | (inferred from `-o`) | Disk image format (`qcow2`, `raw`, `simg`) | +| `--compress` | `gzip` | Compression algorithm (`gzip`, `lz4`, `xz`) | +| `--push` | | Push disk image as OCI artifact to registry | +| `-d`, `--distro` | `autosd` | Distribution | +| `-t`, `--target` | `qemu` | Target platform | +| `-a`, `--arch` | (current system) | Architecture (`amd64`, `arm64`) | +| `--aib-image` | `quay.io/.../automotive-image-builder:latest` | AIB container image | +| `--storage-class` | | Kubernetes storage class | +| `--registry-username` | `$REGISTRY_USERNAME` | Registry username | +| `--registry-password` | `$REGISTRY_PASSWORD` | Registry password | +| `--timeout` | `60` | Timeout in minutes | +| `-w`, `--wait` | `false` | Wait for build to complete | +| `-f`, `--follow` | `false` | Follow build logs | + +**Examples:** + +```bash +# Create disk image from container, download locally +bin/caib disk quay.io/myorg/my-os:v1 \ + -o ./disk.qcow2 \ + --format qcow2 \ + --wait + +# Push disk as OCI artifact instead of downloading +bin/caib disk quay.io/myorg/my-os:v1 \ + --push quay.io/myorg/my-disk:v1 \ + --follow +``` + +### build-legacy + +Builds a traditional (ostree or package-based) disk image. For legacy workflows; new projects should use `build`. + +```bash +bin/caib build-legacy [flags] ``` **Required flags:** | Flag | Description | |------|-------------| -| `--name` | Unique build name | -| `--arch` | Architecture (`amd64`, `arm64`) | +| `--mode` | Build mode: `image` (ostree) or `package` | +| `--format` | Export format: `qcow2`, `raw`, `simg`, etc. | **Optional flags:** | Flag | Default | Description | |------|---------|-------------| | `--server` | `$CAIB_SERVER` | Build API server URL | | `--token` | `$CAIB_TOKEN` | Bearer token (auto-detected from kubeconfig) | -| `--distro` | `autosd` | Distribution to build | -| `--target` | `qemu` | Target platform | -| `--mode` | `image` | Build mode (`image`, `package`) | -| `--export` | `qcow2` | Export format (`qcow2`, `raw`, `simg`) | +| `-n`, `--name` | | Unique build name | +| `-d`, `--distro` | `autosd` | Distribution to build | +| `-t`, `--target` | `qemu` | Target platform | +| `-a`, `--arch` | (current system) | Architecture (`amd64`, `arm64`) | | `--compress` | `gzip` | Compression algorithm (`gzip`, `lz4`, `xz`) | | `--push` | | Push disk image as OCI artifact to registry | -| `--download` | | Download artifact to local file | -| `--automotive-image-builder` | `quay.io/.../automotive-image-builder:latest` | AIB container image | +| `-o`, `--output` | | Download artifact to local file | +| `--aib-image` | `quay.io/.../automotive-image-builder:latest` | AIB container image | | `--storage-class` | | Storage class for build workspace PVC | -| `--define` | | Custom definition `KEY=VALUE` (repeatable) | +| `-D`, `--define` | | Custom definition `KEY=VALUE` (repeatable) | | `--registry-username` | `$REGISTRY_USERNAME` | Registry username | | `--registry-password` | `$REGISTRY_PASSWORD` | Registry password | | `--timeout` | `60` | Timeout in minutes | @@ -176,18 +213,18 @@ bin/caib build-traditional [flags] ```bash # Build ostree-based image and download -bin/caib build-traditional my-manifest.aib.yml \ - --name traditional-build \ +bin/caib build-legacy my-manifest.aib.yml \ --arch arm64 \ --mode image \ - --export qcow2 \ - --download ./disk.qcow2 \ + --format qcow2 \ + -o ./disk.qcow2 \ --follow # Build and push to OCI registry -bin/caib build-traditional my-manifest.aib.yml \ - --name pushed-build \ +bin/caib build-legacy my-manifest.aib.yml \ --arch arm64 \ + --mode image \ + --format qcow2 \ --push quay.io/myorg/disk-image:v1.0 \ --registry-username myuser \ --registry-password mypass \ @@ -223,10 +260,10 @@ bin/caib list [flags] | `--server` | `$CAIB_SERVER` | Build API server URL | | `--token` | `$CAIB_TOKEN` | Bearer token | -## Bootc vs Traditional Builds +## Bootc vs Legacy Builds -| Aspect | `build-bootc` | `build-traditional` | -|--------|---------------|---------------------| +| Aspect | `build` (bootc) | `build-legacy` | +|--------|-----------------|----------------| | Output | Container image (+ optional disk) | Disk image only | | Update mechanism | `bootc switch/upgrade` | Requires re-imaging | | Use case | OTA-updatable systems | Standalone disk images | @@ -241,7 +278,7 @@ The CLI automatically detects authentication in this order: 3. Bearer token from kubeconfig (OpenShift `oc login`, exec plugins) 4. `oc whoami -t` command (if `oc` is available) -For registry authentication (`--push`, `--export-oci`): +For registry authentication (`--push`, `--push-disk`): 1. `--registry-username` / `--registry-password` flags 2. `REGISTRY_USERNAME` / `REGISTRY_PASSWORD` environment variables diff --git a/cmd/caib/main.go b/cmd/caib/main.go index be9e6faa..bf707feb 100644 --- a/cmd/caib/main.go +++ b/cmd/caib/main.go @@ -81,6 +81,56 @@ var ( containerRef string ) +// createBuildAPIClient creates a build API client with authentication token from flags or kubeconfig +func createBuildAPIClient(serverURL string, authToken *string) (*buildapiclient.Client, error) { + if strings.TrimSpace(*authToken) == "" { + if tok, err := loadTokenFromKubeconfig(); err == nil && strings.TrimSpace(tok) != "" { + *authToken = tok + } + } + + var opts []buildapiclient.Option + if strings.TrimSpace(*authToken) != "" { + opts = append(opts, buildapiclient.WithAuthToken(strings.TrimSpace(*authToken))) + } + return buildapiclient.New(serverURL, opts...) +} + +// extractRegistryCredentials extracts registry URL and ensures credentials are loaded from env vars +func extractRegistryCredentials(primaryRef, secondaryRef string, username, password *string) string { + // Try environment variables if command line flags are empty + if *username == "" { + *username = os.Getenv("REGISTRY_USERNAME") + } + if *password == "" { + *password = os.Getenv("REGISTRY_PASSWORD") + } + + // Determine the reference to use for URL extraction + ref := primaryRef + if ref == "" { + ref = secondaryRef + } + + // If no reference, return empty + if ref == "" { + return "" + } + + // Warn if credentials missing (will fall back to Docker/Podman auth files) + if *username == "" || *password == "" { + fmt.Println("Warning: No registry credentials provided via flags or environment variables.") + fmt.Println("Will attempt to use Docker/Podman auth files as fallback.") + } + + // Extract registry URL from reference + parts := strings.SplitN(ref, "/", 2) + if len(parts) > 0 && strings.Contains(parts[0], ".") { + return parts[0] + } + return "docker.io" +} + func main() { rootCmd := &cobra.Command{ Use: "caib", @@ -188,7 +238,7 @@ Examples: buildCmd.Flags().StringVar(&containerPush, "push", "", "push bootc container to registry (required)") buildCmd.Flags().BoolVar(&buildDiskImage, "disk", false, "also build disk image from container") buildCmd.Flags().StringVarP(&outputDir, "output", "o", "", "download disk image to file (requires --disk)") - buildCmd.Flags().StringVar(&diskFormat, "format", "", "disk image format (qcow2, raw, image); inferred from output filename if not set") + buildCmd.Flags().StringVar(&diskFormat, "format", "", "disk image format (qcow2, raw, simg); inferred from output filename if not set") buildCmd.Flags().StringVar(&compressionAlgo, "compress", "gzip", "compression algorithm (gzip, lz4, xz)") buildCmd.Flags().StringVar(&exportOCI, "push-disk", "", "push disk image as OCI artifact to registry") buildCmd.Flags().StringVar(®istryUsername, "registry-username", "", "registry username (or REGISTRY_USERNAME env)") @@ -217,7 +267,7 @@ Examples: diskCmd.Flags().StringVar(&authToken, "token", os.Getenv("CAIB_TOKEN"), "Bearer token for authentication") diskCmd.Flags().StringVarP(&buildName, "name", "n", "", "name for the build job (auto-generated if omitted)") diskCmd.Flags().StringVarP(&outputDir, "output", "o", "", "download disk image to file") - diskCmd.Flags().StringVar(&diskFormat, "format", "", "disk image format (qcow2, raw, image); inferred from output filename if not set") + diskCmd.Flags().StringVar(&diskFormat, "format", "", "disk image format (qcow2, raw, simg); inferred from output filename if not set") diskCmd.Flags().StringVar(&compressionAlgo, "compress", "gzip", "compression algorithm (gzip, lz4, xz)") diskCmd.Flags().StringVar(&exportOCI, "push", "", "push disk image as OCI artifact to registry") diskCmd.Flags().StringVar(®istryUsername, "registry-username", "", "registry username (or REGISTRY_USERNAME env)") @@ -289,17 +339,7 @@ func runBuild(cmd *cobra.Command, args []string) { // Note: diskFormat can be empty - AIB will default to raw (or infer from output filename extension) - if strings.TrimSpace(authToken) == "" { - if tok, err := loadTokenFromKubeconfig(); err == nil && strings.TrimSpace(tok) != "" { - authToken = tok - } - } - - var opts []buildapiclient.Option - if strings.TrimSpace(authToken) != "" { - opts = append(opts, buildapiclient.WithAuthToken(strings.TrimSpace(authToken))) - } - api, err := buildapiclient.New(serverURL, opts...) + api, err := createBuildAPIClient(serverURL, &authToken) if err != nil { handleError(err) } @@ -309,33 +349,8 @@ func runBuild(cmd *cobra.Command, args []string) { handleError(fmt.Errorf("error reading manifest: %w", err)) } - // Extract registry URL from push if not empty - effectiveRegistryURL := "" - if containerPush != "" || exportOCI != "" { - // Try environment variables if command line flags are empty - if registryUsername == "" { - registryUsername = os.Getenv("REGISTRY_USERNAME") - } - if registryPassword == "" { - registryPassword = os.Getenv("REGISTRY_PASSWORD") - } - - // Note: Docker/Podman auth files will be tried as fallback in pullOCIArtifact - if registryUsername == "" || registryPassword == "" { - fmt.Println("Warning: No registry credentials provided via flags or environment variables.") - fmt.Println("Will attempt to use Docker/Podman auth files as fallback.") - } - pushTarget := containerPush - if pushTarget == "" { - pushTarget = exportOCI - } - parts := strings.SplitN(pushTarget, "/", 2) - if len(parts) > 0 && strings.Contains(parts[0], ".") { - effectiveRegistryURL = parts[0] - } else { - effectiveRegistryURL = "docker.io" - } - } + // Extract registry URL and credentials + effectiveRegistryURL := extractRegistryCredentials(containerPush, exportOCI, ®istryUsername, ®istryPassword) req := buildapitypes.BuildRequest{ Name: buildName, @@ -393,7 +408,7 @@ func runBuild(cmd *cobra.Command, args []string) { } } else { // Download directly from cluster via artifact API - if err := downloadArtifactViaAPI(ctx, serverURL, buildName, filepath.Dir(outputDir)); err != nil { + if err := downloadArtifactViaAPI(ctx, serverURL, buildName, outputDir); err != nil { handleError(fmt.Errorf("failed to download artifact: %w", err)) } } @@ -426,43 +441,13 @@ func runDisk(cmd *cobra.Command, args []string) { // Note: diskFormat can be empty - AIB will default to raw (or infer from output filename extension) - if strings.TrimSpace(authToken) == "" { - if tok, err := loadTokenFromKubeconfig(); err == nil && strings.TrimSpace(tok) != "" { - authToken = tok - } - } - - var opts []buildapiclient.Option - if strings.TrimSpace(authToken) != "" { - opts = append(opts, buildapiclient.WithAuthToken(strings.TrimSpace(authToken))) - } - api, err := buildapiclient.New(serverURL, opts...) + api, err := createBuildAPIClient(serverURL, &authToken) if err != nil { handleError(err) } - // Get registry credentials from env if not provided - if registryUsername == "" { - registryUsername = os.Getenv("REGISTRY_USERNAME") - } - if registryPassword == "" { - registryPassword = os.Getenv("REGISTRY_PASSWORD") - } - - // Extract registry URL for authentication - effectiveRegistryURL := "" - if containerRef != "" || exportOCI != "" { - ref := containerRef - if ref == "" { - ref = exportOCI - } - parts := strings.SplitN(ref, "/", 2) - if len(parts) > 0 && strings.Contains(parts[0], ".") { - effectiveRegistryURL = parts[0] - } else { - effectiveRegistryURL = "docker.io" - } - } + // Extract registry URL and credentials + effectiveRegistryURL := extractRegistryCredentials(containerRef, exportOCI, ®istryUsername, ®istryPassword) req := buildapitypes.BuildRequest{ Name: buildName, @@ -507,7 +492,7 @@ func runDisk(cmd *cobra.Command, args []string) { } } else { // Download directly from cluster via artifact API - if err := downloadArtifactViaAPI(ctx, serverURL, buildName, filepath.Dir(outputDir)); err != nil { + if err := downloadArtifactViaAPI(ctx, serverURL, buildName, outputDir); err != nil { handleError(fmt.Errorf("failed to download artifact: %w", err)) } } @@ -587,7 +572,22 @@ func pullOCIArtifact(ociRef, destPath, username, password string) error { return fmt.Errorf("extract artifact: %w", err) } - fmt.Printf("Downloaded to %s\n", destPath) + // Check if file is compressed and add appropriate extension if needed + finalPath := destPath + compression := detectFileCompression(destPath) + if compression != "" && !hasCompressionExtension(destPath) { + ext := compressionExtension(compression) + if ext != "" { + newPath := destPath + ext + fmt.Printf("Adding compression extension: %s -> %s\n", filepath.Base(destPath), filepath.Base(newPath)) + if err := os.Rename(destPath, newPath); err != nil { + return fmt.Errorf("rename file with compression extension: %w", err) + } + finalPath = newPath + } + } + + fmt.Printf("Downloaded to %s\n", finalPath) return nil } @@ -669,17 +669,7 @@ func runBuildLegacy(cmd *cobra.Command, args []string) { handleError(fmt.Errorf("--name is required")) } - if strings.TrimSpace(authToken) == "" { - if tok, err := loadTokenFromKubeconfig(); err == nil && strings.TrimSpace(tok) != "" { - authToken = tok - } - } - - var opts []buildapiclient.Option - if strings.TrimSpace(authToken) != "" { - opts = append(opts, buildapiclient.WithAuthToken(strings.TrimSpace(authToken))) - } - api, err := buildapiclient.New(serverURL, opts...) + api, err := createBuildAPIClient(serverURL, &authToken) if err != nil { handleError(err) } @@ -695,28 +685,8 @@ func runBuildLegacy(cmd *cobra.Command, args []string) { parsedMode = buildapitypes.ModePackage } - effectiveRegistryURL := "" - if exportOCI != "" { - // Try environment variables if command line flags are empty - if registryUsername == "" { - registryUsername = os.Getenv("REGISTRY_USERNAME") - } - if registryPassword == "" { - registryPassword = os.Getenv("REGISTRY_PASSWORD") - } - - // Note: Docker/Podman auth files will be tried as fallback - if registryUsername == "" || registryPassword == "" { - fmt.Println("Warning: No registry credentials provided via flags or environment variables.") - fmt.Println("Will attempt to use Docker/Podman auth files as fallback.") - } - parts := strings.SplitN(exportOCI, "/", 2) - if len(parts) > 0 && strings.Contains(parts[0], ".") { - effectiveRegistryURL = parts[0] - } else { - effectiveRegistryURL = "docker.io" - } - } + // Extract registry URL and credentials + effectiveRegistryURL := extractRegistryCredentials("", exportOCI, ®istryUsername, ®istryPassword) req := buildapitypes.BuildRequest{ Name: buildName, @@ -983,10 +953,122 @@ func findLocalFileReferences(manifestContent string) ([]map[string]string, error return localFiles, nil } -func downloadArtifactViaAPI(ctx context.Context, baseURL, name, outDir string) error { - if strings.TrimSpace(outDir) == "" { - outDir = "./output" +// isDirectory checks if a path exists and is a directory +func isDirectory(path string) bool { + stat, err := os.Stat(path) + return err == nil && stat.IsDir() +} + +// compressionExtension returns the file extension for a compression algorithm +func compressionExtension(algo string) string { + switch algo { + case "gzip": + return ".gz" + case "lz4": + return ".lz4" + case "xz": + return ".xz" + default: + return "" + } +} + +// hasCompressionExtension checks if a filename already has a compression extension +func hasCompressionExtension(filename string) bool { + lower := strings.ToLower(filename) + return strings.HasSuffix(lower, ".gz") || + strings.HasSuffix(lower, ".lz4") || + strings.HasSuffix(lower, ".xz") +} + +// parseOutputPath determines the output directory and optional user-specified filename +func parseOutputPath(outPath string) (outDir, userFilename string) { + outPath = strings.TrimSpace(outPath) + + // Empty path defaults to ./output + if outPath == "" { + return "./output", "" + } + + // Explicit directory paths (trailing slash or existing directory) + if strings.HasSuffix(outPath, "/") || isDirectory(outPath) { + return strings.TrimRight(outPath, "/"), "" + } + + // File path - extract directory and filename + dir := filepath.Dir(outPath) + filename := filepath.Base(outPath) + + // Current directory defaults to ./output for clarity + if dir == "." { + dir = "./output" + } + + return dir, filename +} + +// resolveFilename determines the final filename, adding compression extension if needed +func resolveFilename(userFilename, serverFilename, compression, fallbackName string) string { + // Priority: user-specified > server-provided > fallback + filename := fallbackName + if serverFilename != "" { + filename = serverFilename + } + if userFilename != "" { + filename = userFilename + } + + // Add compression extension if server indicates compression and filename lacks it + if compression != "" && !hasCompressionExtension(filename) { + if ext := compressionExtension(compression); ext != "" { + original := filename + filename += ext + if userFilename != "" { + fmt.Printf("Adding compression extension: %s -> %s\n", original, filename) + } + } + } + + return filename +} + +// detectFileCompression examines file magic bytes to determine compression type +func detectFileCompression(filePath string) string { + file, err := os.Open(filePath) + if err != nil { + return "" + } + defer file.Close() + + // Read first few bytes to check magic numbers + header := make([]byte, 10) + n, err := file.Read(header) + if err != nil || n < 3 { + return "" + } + + // Check for gzip magic number + if n >= 2 && header[0] == 0x1f && header[1] == 0x8b { + return "gzip" } + + // Check for lz4 magic number + if n >= 4 && header[0] == 0x04 && header[1] == 0x22 && header[2] == 0x4d && header[3] == 0x18 { + return "lz4" + } + + // Check for xz magic number + if n >= 6 && header[0] == 0xfd && header[1] == 0x37 && header[2] == 0x7a && + header[3] == 0x58 && header[4] == 0x5a && header[5] == 0x00 { + return "xz" + } + + return "" +} + +func downloadArtifactViaAPI(ctx context.Context, baseURL, name, outPath string) error { + outDir, userFilename := parseOutputPath(outPath) + if err := os.MkdirAll(outDir, 0755); err != nil { return fmt.Errorf("create output dir: %w", err) } @@ -1020,16 +1102,19 @@ func downloadArtifactViaAPI(ctx context.Context, baseURL, name, outDir string) e } if resp.StatusCode == http.StatusOK { - filename := name + ".artifact" contentType := resp.Header.Get("Content-Type") + + // Extract server-provided filename from Content-Disposition header + serverFilename := "" if cd := resp.Header.Get("Content-Disposition"); cd != "" { if i := strings.Index(cd, "filename="); i >= 0 { - f := strings.Trim(cd[i+9:], "\" ") - if f != "" { - filename = f - } + serverFilename = strings.Trim(cd[i+9:], "\" ") } } + + // Resolve final filename with compression handling + compression := strings.TrimSpace(resp.Header.Get("X-AIB-Compression")) + filename := resolveFilename(userFilename, serverFilename, compression, name+".artifact") if at := strings.TrimSpace(resp.Header.Get("X-AIB-Artifact-Type")); at != "" { fmt.Printf("Artifact type: %s\n", at) } @@ -1187,16 +1272,7 @@ func runDownload(cmd *cobra.Command, args []string) { os.Exit(1) } - if strings.TrimSpace(authToken) == "" { - if tok, err := loadTokenFromKubeconfig(); err == nil && strings.TrimSpace(tok) != "" { - authToken = tok - } - } - var opts []buildapiclient.Option - if strings.TrimSpace(authToken) != "" { - opts = append(opts, buildapiclient.WithAuthToken(strings.TrimSpace(authToken))) - } - api, err := buildapiclient.New(serverURL, opts...) + api, err := createBuildAPIClient(serverURL, &authToken) if err != nil { fmt.Printf("Error: %v\n", err) os.Exit(1) @@ -1224,16 +1300,7 @@ func runList(cmd *cobra.Command, args []string) { fmt.Println("Error: --server is required (or set CAIB_SERVER)") os.Exit(1) } - if strings.TrimSpace(authToken) == "" { - if tok, err := loadTokenFromKubeconfig(); err == nil && strings.TrimSpace(tok) != "" { - authToken = tok - } - } - var opts []buildapiclient.Option - if strings.TrimSpace(authToken) != "" { - opts = append(opts, buildapiclient.WithAuthToken(strings.TrimSpace(authToken))) - } - api, err := buildapiclient.New(serverURL, opts...) + api, err := createBuildAPIClient(serverURL, &authToken) if err != nil { fmt.Printf("Error: %v\n", err) os.Exit(1) diff --git a/internal/buildapi/server.go b/internal/buildapi/server.go index 7a3ca047..3c31274d 100644 --- a/internal/buildapi/server.go +++ b/internal/buildapi/server.go @@ -1374,41 +1374,10 @@ func (a *APIServer) listArtifacts(c *gin.Context, name string) { artifactFileName = fmt.Sprintf("%s-%s%s", build.Spec.Distro, build.Spec.Target, ext) } - var artifactPod *corev1.Pod - deadline := time.Now().Add(2 * time.Minute) - for { - podList := &corev1.PodList{} - if err := k8sClient.List(ctx, podList, - client.InNamespace(namespace), - client.MatchingLabels{ - "app.kubernetes.io/name": "artifact-pod", - "automotive.sdv.cloud.redhat.com/imagebuild-name": name, - }); err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("error listing artifact pods: %v", err)}) - return - } - for i := range podList.Items { - p := &podList.Items[i] - if p.Status.Phase == corev1.PodRunning { - for _, cs := range p.Status.ContainerStatuses { - if cs.Name == "fileserver" && cs.Ready { - artifactPod = p - break - } - } - } - if artifactPod != nil { - break - } - } - if artifactPod != nil { - break - } - if time.Now().After(deadline) { - c.JSON(http.StatusServiceUnavailable, gin.H{"error": "artifact pod not ready"}) - return - } - time.Sleep(2 * time.Second) + artifactPod, err := findReadyArtifactPod(ctx, k8sClient, namespace, name, time.Now().Add(2*time.Minute)) + if err != nil { + c.JSON(http.StatusServiceUnavailable, gin.H{"error": err.Error()}) + return } restCfg, err := getRESTConfigFromRequest(c) @@ -1510,41 +1479,10 @@ func (a *APIServer) streamArtifactPart(c *gin.Context, name, file string) { artifactFileName = fmt.Sprintf("%s-%s%s", build.Spec.Distro, build.Spec.Target, ext) } - var artifactPod *corev1.Pod - deadline := time.Now().Add(2 * time.Minute) - for { - podList := &corev1.PodList{} - if err := k8sClient.List(ctx, podList, - client.InNamespace(namespace), - client.MatchingLabels{ - "app.kubernetes.io/name": "artifact-pod", - "automotive.sdv.cloud.redhat.com/imagebuild-name": name, - }); err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("error listing artifact pods: %v", err)}) - return - } - for i := range podList.Items { - p := &podList.Items[i] - if p.Status.Phase == corev1.PodRunning { - for _, cs := range p.Status.ContainerStatuses { - if cs.Name == "fileserver" && cs.Ready { - artifactPod = p - break - } - } - } - if artifactPod != nil { - break - } - } - if artifactPod != nil { - break - } - if time.Now().After(deadline) { - c.JSON(http.StatusServiceUnavailable, gin.H{"error": "artifact pod not ready"}) - return - } - time.Sleep(2 * time.Second) + artifactPod, err := findReadyArtifactPod(ctx, k8sClient, namespace, name, time.Now().Add(2*time.Minute)) + if err != nil { + c.JSON(http.StatusServiceUnavailable, gin.H{"error": err.Error()}) + return } restCfg, err := getRESTConfigFromRequest(c) @@ -1680,43 +1618,10 @@ func (a *APIServer) streamDefaultArtifact(c *gin.Context, name string) { return } - var artifactPod *corev1.Pod - deadline := time.Now().Add(2 * time.Minute) - for { - podList := &corev1.PodList{} - if err := k8sClient.List(ctx, podList, - client.InNamespace(namespace), - client.MatchingLabels{ - "app.kubernetes.io/name": "artifact-pod", - "automotive.sdv.cloud.redhat.com/imagebuild-name": name, - }); err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("error listing artifact pods: %v", err)}) - return - } - - for i := range podList.Items { - p := &podList.Items[i] - if p.Status.Phase == corev1.PodRunning { - for _, cs := range p.Status.ContainerStatuses { - if cs.Name == "fileserver" && cs.Ready { - artifactPod = p - break - } - } - } - if artifactPod != nil { - break - } - } - - if artifactPod != nil { - break - } - if time.Now().After(deadline) { - c.JSON(http.StatusServiceUnavailable, gin.H{"error": "artifact pod not ready"}) - return - } - time.Sleep(2 * time.Second) + artifactPod, err := findReadyArtifactPod(ctx, k8sClient, namespace, name, time.Now().Add(2*time.Minute)) + if err != nil { + c.JSON(http.StatusServiceUnavailable, gin.H{"error": err.Error()}) + return } podPath := "/workspace/shared/" + artifactFileName @@ -1877,43 +1782,10 @@ func (a *APIServer) streamArtifactByFilename(c *gin.Context, name, filename stri return } - var artifactPod *corev1.Pod - deadline := time.Now().Add(2 * time.Minute) - for { - podList := &corev1.PodList{} - if err := k8sClient.List(ctx, podList, - client.InNamespace(namespace), - client.MatchingLabels{ - "app.kubernetes.io/name": "artifact-pod", - "automotive.sdv.cloud.redhat.com/imagebuild-name": name, - }); err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("error listing artifact pods: %v", err)}) - return - } - - for i := range podList.Items { - p := &podList.Items[i] - if p.Status.Phase == corev1.PodRunning { - for _, cs := range p.Status.ContainerStatuses { - if cs.Name == "fileserver" && cs.Ready { - artifactPod = p - break - } - } - } - if artifactPod != nil { - break - } - } - - if artifactPod != nil { - break - } - if time.Now().After(deadline) { - c.JSON(http.StatusServiceUnavailable, gin.H{"error": "artifact pod not ready"}) - return - } - time.Sleep(2 * time.Second) + artifactPod, err := findReadyArtifactPod(ctx, k8sClient, namespace, name, time.Now().Add(2*time.Minute)) + if err != nil { + c.JSON(http.StatusServiceUnavailable, gin.H{"error": err.Error()}) + return } podPath := "/workspace/shared/" + base @@ -2108,53 +1980,88 @@ func getClientFromRequest(c *gin.Context) (client.Client, error) { } func (a *APIServer) isAuthenticated(c *gin.Context) bool { - authHeader := c.Request.Header.Get("Authorization") - token := "" - token, _ = strings.CutPrefix(authHeader, "Bearer ") + token := extractBearerToken(c) if token == "" { - token = c.Request.Header.Get("X-Forwarded-Access-Token") - } - if strings.TrimSpace(token) == "" { return false } + cfg, err := getRESTConfigFromRequest(c) if err != nil { return false } + clientset, err := kubernetes.NewForConfig(cfg) if err != nil { return false } + tr := &authnv1.TokenReview{Spec: authnv1.TokenReviewSpec{Token: token}} res, err := clientset.AuthenticationV1().TokenReviews().Create(c.Request.Context(), tr, metav1.CreateOptions{}) - if err != nil { - return false - } - return res.Status.Authenticated + return err == nil && res.Status.Authenticated } -func resolveRequester(c *gin.Context) string { +// extractBearerToken extracts the bearer token from the request +func extractBearerToken(c *gin.Context) string { authHeader := c.Request.Header.Get("Authorization") - token := "" - token, _ = strings.CutPrefix(authHeader, "Bearer ") + token, _ := strings.CutPrefix(authHeader, "Bearer ") if token == "" { token = c.Request.Header.Get("X-Forwarded-Access-Token") } + return strings.TrimSpace(token) +} + +// findReadyArtifactPod finds a running and ready artifact pod for the given ImageBuild +func findReadyArtifactPod(ctx context.Context, k8sClient client.Client, namespace, buildName string, deadline time.Time) (*corev1.Pod, error) { + for { + podList := &corev1.PodList{} + if err := k8sClient.List(ctx, podList, + client.InNamespace(namespace), + client.MatchingLabels{ + "app.kubernetes.io/name": "artifact-pod", + "automotive.sdv.cloud.redhat.com/imagebuild-name": buildName, + }); err != nil { + return nil, fmt.Errorf("error listing artifact pods: %w", err) + } - if strings.TrimSpace(token) != "" { - cfg, err := getRESTConfigFromRequest(c) - if err == nil { - clientset, err := kubernetes.NewForConfig(cfg) - if err == nil { - tr := &authnv1.TokenReview{Spec: authnv1.TokenReviewSpec{Token: token}} - if res, err := clientset.AuthenticationV1().TokenReviews().Create(c.Request.Context(), tr, metav1.CreateOptions{}); err == nil { - if res.Status.Authenticated && res.Status.User.Username != "" { - return res.Status.User.Username + for i := range podList.Items { + p := &podList.Items[i] + if p.Status.Phase == corev1.PodRunning { + for _, cs := range p.Status.ContainerStatuses { + if cs.Name == "fileserver" && cs.Ready { + return p, nil } } } } + + if time.Now().After(deadline) { + return nil, fmt.Errorf("artifact pod not ready") + } + time.Sleep(2 * time.Second) + } +} + +func resolveRequester(c *gin.Context) string { + token := extractBearerToken(c) + if token == "" { + return "unknown" + } + + cfg, err := getRESTConfigFromRequest(c) + if err != nil { + return "unknown" + } + + clientset, err := kubernetes.NewForConfig(cfg) + if err != nil { + return "unknown" + } + + tr := &authnv1.TokenReview{Spec: authnv1.TokenReviewSpec{Token: token}} + res, err := clientset.AuthenticationV1().TokenReviews().Create(c.Request.Context(), tr, metav1.CreateOptions{}) + if err != nil || !res.Status.Authenticated || res.Status.User.Username == "" { + return "unknown" } - return "unknown" + return res.Status.User.Username } diff --git a/internal/buildapi/types.go b/internal/buildapi/types.go index 604b7110..974a44aa 100644 --- a/internal/buildapi/types.go +++ b/internal/buildapi/types.go @@ -7,28 +7,12 @@ import ( type Distro string -func (d Distro) IsValid() bool { - return strings.TrimSpace(string(d)) != "" -} - type Target string -func (t Target) IsValid() bool { - return strings.TrimSpace(string(t)) != "" -} - type Architecture string -func (a Architecture) IsValid() bool { - return strings.TrimSpace(string(a)) != "" -} - type ExportFormat string -func (e ExportFormat) IsValid() bool { - return strings.TrimSpace(string(e)) != "" -} - type Mode string const ( @@ -42,10 +26,17 @@ const ( ModeDisk Mode = "disk" ) -func (m Mode) IsValid() bool { - return strings.TrimSpace(string(m)) != "" +// IsValid checks if a string value is non-empty after trimming whitespace +func IsValid(s string) bool { + return strings.TrimSpace(s) != "" } +func (d Distro) IsValid() bool { return IsValid(string(d)) } +func (t Target) IsValid() bool { return IsValid(string(t)) } +func (a Architecture) IsValid() bool { return IsValid(string(a)) } +func (e ExportFormat) IsValid() bool { return IsValid(string(e)) } +func (m Mode) IsValid() bool { return IsValid(string(m)) } + // IsBootc returns true if this is bootc mode func (m Mode) IsBootc() bool { return m == ModeBootc diff --git a/internal/common/tasks/scripts/build_image.sh b/internal/common/tasks/scripts/build_image.sh index 9e531a6a..ac843219 100644 --- a/internal/common/tasks/scripts/build_image.sh +++ b/internal/common/tasks/scripts/build_image.sh @@ -585,10 +585,29 @@ elif [ -f "$(workspaces.shared-workspace.path)/${exportFile}" ]; then fi if [ -z "$final_name" ]; then - guess=$(ls -1 $(workspaces.shared-workspace.path)/${cleanName}* 2>/dev/null | head -n1) - if [ -n "$guess" ]; then - final_name=$(basename "$guess") + workspace_path=$(workspaces.shared-workspace.path) + + # Try to find artifact with priority: compressed file > compressed dir > any file + # This ensures we prefer compressed artifacts when compression is enabled + patterns_to_try=( + "${cleanName}*${EXT_FILE}" + "${cleanName}*${EXT_DIR}" + "${cleanName}*" + ) + + # If compression is disabled, only try the general pattern + if [ "$COMPRESSION" = "none" ]; then + patterns_to_try=("${cleanName}*") fi + + for pattern in "${patterns_to_try[@]}"; do + guess=$(ls -1 "${workspace_path}/${pattern}" 2>/dev/null | head -n1) + if [ -n "$guess" ]; then + final_name=$(basename "$guess") + echo "Fallback: using found artifact: $final_name" + break + fi + done fi if [ -n "$final_name" ]; then echo "Writing artifact filename to Tekton result: $final_name" diff --git a/internal/controller/imagebuild/controller.go b/internal/controller/imagebuild/controller.go index d775ca8d..96bfd9a8 100644 --- a/internal/controller/imagebuild/controller.go +++ b/internal/controller/imagebuild/controller.go @@ -27,6 +27,47 @@ const ( OperatorNamespace = "automotive-dev-operator-system" ) +// getFormatExtension returns the file extension for an export format +func getFormatExtension(format string) string { + switch format { + case "image": + return ".raw" + case "qcow2": + return ".qcow2" + default: + if format != "" { + return "." + format + } + return ".raw" + } +} + +// getCompressionExtension returns the file extension for a compression algorithm +func getCompressionExtension(compression string) string { + if compression == "" || compression == "none" { + return "" + } + + switch compression { + case "gzip": + return ".gz" + case "lz4": + return ".lz4" + case "xz": + return ".xz" + default: + return "" + } +} + +// buildArtifactFilename constructs the artifact filename with proper extensions +func buildArtifactFilename(distro, target, exportFormat, compression string) string { + baseFormat := getFormatExtension(exportFormat) + compressionExt := getCompressionExtension(compression) + + return fmt.Sprintf("%s-%s%s%s", distro, target, baseFormat, compressionExt) +} + // ImageBuildReconciler reconciles a ImageBuild object type ImageBuildReconciler struct { client.Client @@ -828,20 +869,12 @@ func (r *ImageBuildReconciler) updateArtifactInfo(ctx context.Context, imageBuil // Only set ArtifactFileName if it's not already set (from Tekton results) if latestImageBuild.Status.ArtifactFileName == "" { - var fileExtension string - switch latestImageBuild.Spec.ExportFormat { - case "image": - fileExtension = ".raw" - case "qcow2": - fileExtension = ".qcow2" - default: - fileExtension = fmt.Sprintf(".%s", latestImageBuild.Spec.ExportFormat) - } - - fileName := fmt.Sprintf("%s-%s%s", + fileName := buildArtifactFilename( latestImageBuild.Spec.Distro, latestImageBuild.Spec.Target, - fileExtension) + latestImageBuild.Spec.ExportFormat, + latestImageBuild.Spec.Compression, + ) latestImageBuild.Status.ArtifactFileName = fileName } From 5f28107bf1ecb89cea91005b36ac41841c145132 Mon Sep 17 00:00:00 2001 From: Benny Zlotnik Date: Fri, 9 Jan 2026 15:49:00 +0200 Subject: [PATCH 4/7] fix --follow Signed-off-by: Benny Zlotnik --- ...ve-dev-operator.clusterserviceversion.yaml | 2 +- catalog/automotive-dev-operator.yaml | 2 +- cmd/caib/main.go | 124 +++++++++++++----- internal/buildapi/server.go | 4 +- 4 files changed, 97 insertions(+), 35 deletions(-) diff --git a/bundle/manifests/automotive-dev-operator.clusterserviceversion.yaml b/bundle/manifests/automotive-dev-operator.clusterserviceversion.yaml index 58037f2c..511e99ea 100644 --- a/bundle/manifests/automotive-dev-operator.clusterserviceversion.yaml +++ b/bundle/manifests/automotive-dev-operator.clusterserviceversion.yaml @@ -53,7 +53,7 @@ metadata: capabilities: Basic Install categories: Developer Tools containerImage: quay.io/rh-sdv-cloud/automotive-dev-operator:latest - createdAt: "2026-01-09T11:11:25Z" + createdAt: "2026-01-09T13:33:39Z" operatorframework.io/suggested-namespace: automotive-dev-operator-system operators.operatorframework.io/builder: operator-sdk-v1.42.0 operators.operatorframework.io/project_layout: go.kubebuilder.io/v4 diff --git a/catalog/automotive-dev-operator.yaml b/catalog/automotive-dev-operator.yaml index e45a1647..962db742 100644 --- a/catalog/automotive-dev-operator.yaml +++ b/catalog/automotive-dev-operator.yaml @@ -60,7 +60,7 @@ properties: data: eyJhcGlWZXJzaW9uIjoiYXBpZXh0ZW5zaW9ucy5rOHMuaW8vdjEiLCJraW5kIjoiQ3VzdG9tUmVzb3VyY2VEZWZpbml0aW9uIiwibWV0YWRhdGEiOnsiYW5ub3RhdGlvbnMiOnsiY29udHJvbGxlci1nZW4ua3ViZWJ1aWxkZXIuaW8vdmVyc2lvbiI6InYwLjE5LjAifSwiY3JlYXRpb25UaW1lc3RhbXAiOm51bGwsIm5hbWUiOiJvcGVyYXRvcmNvbmZpZ3MuYXV0b21vdGl2ZS5zZHYuY2xvdWQucmVkaGF0LmNvbSJ9LCJzcGVjIjp7Imdyb3VwIjoiYXV0b21vdGl2ZS5zZHYuY2xvdWQucmVkaGF0LmNvbSIsIm5hbWVzIjp7ImtpbmQiOiJPcGVyYXRvckNvbmZpZyIsImxpc3RLaW5kIjoiT3BlcmF0b3JDb25maWdMaXN0IiwicGx1cmFsIjoib3BlcmF0b3Jjb25maWdzIiwic2luZ3VsYXIiOiJvcGVyYXRvcmNvbmZpZyJ9LCJzY29wZSI6Ik5hbWVzcGFjZWQiLCJ2ZXJzaW9ucyI6W3siYWRkaXRpb25hbFByaW50ZXJDb2x1bW5zIjpbeyJqc29uUGF0aCI6Ii5zcGVjLndlYlVJIiwibmFtZSI6IldlYlVJIiwidHlwZSI6ImJvb2xlYW4ifSx7Impzb25QYXRoIjoiLnNwZWMub3NCdWlsZHMuZW5hYmxlZCIsIm5hbWUiOiJPUyBCdWlsZHMiLCJ0eXBlIjoiYm9vbGVhbiJ9LHsianNvblBhdGgiOiIuc3RhdHVzLnBoYXNlIiwibmFtZSI6IlBoYXNlIiwidHlwZSI6InN0cmluZyJ9LHsianNvblBhdGgiOiIubWV0YWRhdGEuY3JlYXRpb25UaW1lc3RhbXAiLCJuYW1lIjoiQWdlIiwidHlwZSI6ImRhdGUifV0sIm5hbWUiOiJ2MWFscGhhMSIsInNjaGVtYSI6eyJvcGVuQVBJVjNTY2hlbWEiOnsiZGVzY3JpcHRpb24iOiJPcGVyYXRvckNvbmZpZyBpcyB0aGUgU2NoZW1hIGZvciB0aGUgb3BlcmF0b3Jjb25maWdzIEFQSSIsInByb3BlcnRpZXMiOnsiYXBpVmVyc2lvbiI6eyJkZXNjcmlwdGlvbiI6IkFQSVZlcnNpb24gZGVmaW5lcyB0aGUgdmVyc2lvbmVkIHNjaGVtYSBvZiB0aGlzIHJlcHJlc2VudGF0aW9uIG9mIGFuIG9iamVjdC5cblNlcnZlcnMgc2hvdWxkIGNvbnZlcnQgcmVjb2duaXplZCBzY2hlbWFzIHRvIHRoZSBsYXRlc3QgaW50ZXJuYWwgdmFsdWUsIGFuZFxubWF5IHJlamVjdCB1bnJlY29nbml6ZWQgdmFsdWVzLlxuTW9yZSBpbmZvOiBodHRwczovL2dpdC5rOHMuaW8vY29tbXVuaXR5L2NvbnRyaWJ1dG9ycy9kZXZlbC9zaWctYXJjaGl0ZWN0dXJlL2FwaS1jb252ZW50aW9ucy5tZCNyZXNvdXJjZXMiLCJ0eXBlIjoic3RyaW5nIn0sImtpbmQiOnsiZGVzY3JpcHRpb24iOiJLaW5kIGlzIGEgc3RyaW5nIHZhbHVlIHJlcHJlc2VudGluZyB0aGUgUkVTVCByZXNvdXJjZSB0aGlzIG9iamVjdCByZXByZXNlbnRzLlxuU2VydmVycyBtYXkgaW5mZXIgdGhpcyBmcm9tIHRoZSBlbmRwb2ludCB0aGUgY2xpZW50IHN1Ym1pdHMgcmVxdWVzdHMgdG8uXG5DYW5ub3QgYmUgdXBkYXRlZC5cbkluIENhbWVsQ2FzZS5cbk1vcmUgaW5mbzogaHR0cHM6Ly9naXQuazhzLmlvL2NvbW11bml0eS9jb250cmlidXRvcnMvZGV2ZWwvc2lnLWFyY2hpdGVjdHVyZS9hcGktY29udmVudGlvbnMubWQjdHlwZXMta2luZHMiLCJ0eXBlIjoic3RyaW5nIn0sIm1ldGFkYXRhIjp7InR5cGUiOiJvYmplY3QifSwic3BlYyI6eyJkZXNjcmlwdGlvbiI6Ik9wZXJhdG9yQ29uZmlnU3BlYyBkZWZpbmVzIHRoZSBkZXNpcmVkIHN0YXRlIG9mIE9wZXJhdG9yQ29uZmlnIiwicHJvcGVydGllcyI6eyJvc0J1aWxkcyI6eyJkZXNjcmlwdGlvbiI6Ik9TQnVpbGRzIGRlZmluZXMgdGhlIGNvbmZpZ3VyYXRpb24gZm9yIE9TIGJ1aWxkIG9wZXJhdGlvbnMiLCJwcm9wZXJ0aWVzIjp7ImNsdXN0ZXJSZWdpc3RyeVJvdXRlIjp7ImRlc2NyaXB0aW9uIjoiQ2x1c3RlclJlZ2lzdHJ5Um91dGUgaXMgdGhlIGV4dGVybmFsIHJvdXRlIGZvciB0aGUgY2x1c3RlcidzIGludGVybmFsIGltYWdlIHJlZ2lzdHJ5XG5SZXF1aXJlZCBmb3IgYm9vdGMgYnVpbGRzIHRvIGFsbG93IG5lc3RlZCBjb250YWluZXJzIHRvIHB1bGwgYnVpbGRlciBpbWFnZXNcbkV4YW1wbGU6IFwiZGVmYXVsdC1yb3V0ZS1vcGVuc2hpZnQtaW1hZ2UtcmVnaXN0cnkuYXBwcy5teWNsdXN0ZXIuZXhhbXBsZS5jb21cIiIsInR5cGUiOiJzdHJpbmcifSwiZW5hYmxlZCI6eyJkZWZhdWx0Ijp0cnVlLCJkZXNjcmlwdGlvbiI6IkVuYWJsZWQgZGV0ZXJtaW5lcyBpZiBUZWt0b24gdGFza3MgZm9yIE9TIGJ1aWxkcyBzaG91bGQgYmUgZGVwbG95ZWQiLCJ0eXBlIjoiYm9vbGVhbiJ9LCJtZW1vcnlWb2x1bWVTaXplIjp7ImRlc2NyaXB0aW9uIjoiTWVtb3J5Vm9sdW1lU2l6ZSBzcGVjaWZpZXMgdGhlIHNpemUgbGltaXQgZm9yIG1lbW9yeS1iYWNrZWQgdm9sdW1lcyAocmVxdWlyZWQgaWYgVXNlTWVtb3J5Vm9sdW1lcyBpcyB0cnVlKVxuRXhhbXBsZTogXCIyR2lcIiIsInR5cGUiOiJzdHJpbmcifSwicHZjU2l6ZSI6eyJkZXNjcmlwdGlvbiI6IlBWQ1NpemUgc3BlY2lmaWVzIHRoZSBzaXplIGZvciBwZXJzaXN0ZW50IHZvbHVtZSBjbGFpbXMgY3JlYXRlZCBmb3IgYnVpbGQgd29ya3NwYWNlc1xuRGVmYXVsdDogXCI4R2lcIiIsInR5cGUiOiJzdHJpbmcifSwicnVudGltZUNsYXNzTmFtZSI6eyJkZXNjcmlwdGlvbiI6IlJ1bnRpbWVDbGFzc05hbWUgc3BlY2lmaWVzIHRoZSBydW50aW1lIGNsYXNzIHRvIHVzZSBmb3IgdGhlIGJ1aWxkIHBvZFxuTW9yZSBpbmZvOiBodHRwczovL2t1YmVybmV0ZXMuaW8vZG9jcy9jb25jZXB0cy9jb250YWluZXJzL3J1bnRpbWUtY2xhc3MvIiwidHlwZSI6InN0cmluZyJ9LCJzZXJ2ZUV4cGlyeUhvdXJzIjp7ImRlc2NyaXB0aW9uIjoiU2VydmVFeHBpcnlIb3VycyBzcGVjaWZpZXMgaG93IGxvbmcgdG8gc2VydmUgYnVpbGQgYXJ0aWZhY3RzIGJlZm9yZSBhdXRvbWF0aWMgY2xlYW51cFxuRGVmYXVsdDogMjQiLCJmb3JtYXQiOiJpbnQzMiIsInR5cGUiOiJpbnRlZ2VyIn0sInVzZU1lbW9yeVZvbHVtZXMiOnsiZGVzY3JpcHRpb24iOiJVc2VNZW1vcnlWb2x1bWVzIGRldGVybWluZXMgd2hldGhlciB0byB1c2UgbWVtb3J5LWJhY2tlZCB2b2x1bWVzIGZvciBidWlsZCBvcGVyYXRpb25zIiwidHlwZSI6ImJvb2xlYW4ifX0sInJlcXVpcmVkIjpbImVuYWJsZWQiXSwidHlwZSI6Im9iamVjdCJ9LCJ3ZWJVSSI6eyJkZWZhdWx0Ijp0cnVlLCJkZXNjcmlwdGlvbiI6IldlYlVJIGRldGVybWluZXMgaWYgdGhlIHdlYiBVSSBzaG91bGQgYmUgZGVwbG95ZWQiLCJ0eXBlIjoiYm9vbGVhbiJ9fSwicmVxdWlyZWQiOlsid2ViVUkiXSwidHlwZSI6Im9iamVjdCJ9LCJzdGF0dXMiOnsiZGVzY3JpcHRpb24iOiJPcGVyYXRvckNvbmZpZ1N0YXR1cyBkZWZpbmVzIHRoZSBvYnNlcnZlZCBzdGF0ZSBvZiBPcGVyYXRvckNvbmZpZyIsInByb3BlcnRpZXMiOnsibWVzc2FnZSI6eyJkZXNjcmlwdGlvbiI6Ik1lc3NhZ2UgcHJvdmlkZXMgZGV0YWlsIGFib3V0IHRoZSBjdXJyZW50IHBoYXNlIiwidHlwZSI6InN0cmluZyJ9LCJvYnNlcnZlZEdlbmVyYXRpb24iOnsiZGVzY3JpcHRpb24iOiJPYnNlcnZlZEdlbmVyYXRpb24gaXMgdGhlIG1vc3QgcmVjZW50IGdlbmVyYXRpb24gb2JzZXJ2ZWQgYnkgdGhlIGNvbnRyb2xsZXIuIiwiZm9ybWF0IjoiaW50NjQiLCJ0eXBlIjoiaW50ZWdlciJ9LCJvc0J1aWxkc0RlcGxveWVkIjp7ImRlc2NyaXB0aW9uIjoiT1NCdWlsZHNEZXBsb3llZCBpbmRpY2F0ZXMgaWYgdGhlIE9TIEJ1aWxkcyBUZWt0b24gdGFza3MgYXJlIGN1cnJlbnRseSBkZXBsb3llZCIsInR5cGUiOiJib29sZWFuIn0sInBoYXNlIjp7ImRlc2NyaXB0aW9uIjoiUGhhc2UgcmVwcmVzZW50cyB0aGUgY3VycmVudCBwaGFzZSAoUmVhZHksIFJlY29uY2lsaW5nLCBGYWlsZWQpIiwidHlwZSI6InN0cmluZyJ9LCJ3ZWJVSURlcGxveWVkIjp7ImRlc2NyaXB0aW9uIjoiV2ViVUlEZXBsb3llZCBpbmRpY2F0ZXMgaWYgdGhlIFdlYlVJIGlzIGN1cnJlbnRseSBkZXBsb3llZCIsInR5cGUiOiJib29sZWFuIn19LCJ0eXBlIjoib2JqZWN0In19LCJ0eXBlIjoib2JqZWN0In19LCJzZXJ2ZWQiOnRydWUsInN0b3JhZ2UiOnRydWUsInN1YnJlc291cmNlcyI6eyJzdGF0dXMiOnt9fX1dfSwic3RhdHVzIjp7ImFjY2VwdGVkTmFtZXMiOnsia2luZCI6IiIsInBsdXJhbCI6IiJ9LCJjb25kaXRpb25zIjpudWxsLCJzdG9yZWRWZXJzaW9ucyI6bnVsbH19 - type: olm.bundle.object value: - data: eyJhcGlWZXJzaW9uIjoib3BlcmF0b3JzLmNvcmVvcy5jb20vdjFhbHBoYTEiLCJraW5kIjoiQ2x1c3RlclNlcnZpY2VWZXJzaW9uIiwibWV0YWRhdGEiOnsiYW5ub3RhdGlvbnMiOnsiYWxtLWV4YW1wbGVzIjoiW1xuICB7XG4gICAgXCJhcGlWZXJzaW9uXCI6IFwiYXV0b21vdGl2ZS5zZHYuY2xvdWQucmVkaGF0LmNvbS92MWFscGhhMVwiLFxuICAgIFwia2luZFwiOiBcIkltYWdlQnVpbGRcIixcbiAgICBcIm1ldGFkYXRhXCI6IHtcbiAgICAgIFwiYW5ub3RhdGlvbnNcIjoge1xuICAgICAgICBcImRlc2NyaXB0aW9uXCI6IFwiRXhhbXBsZSBJbWFnZUJ1aWxkIENSLiBUaGUgYXJjaGl0ZWN0dXJlIGNhbiBiZSBzZXQgdG8gYW55IHN1cHBvcnRlZCB2YWx1ZSBsaWtlIFxcXCJhYXJjaDY0XFxcIiwgXFxcIng4Nl82NFxcXCIsIGV0Yy4gU2VlIHlvdXIgYnVpbGQgcGxhdGZvcm0ncyBkb2N1bWVudGF0aW9uIGZvciBzdXBwb3J0ZWQgdmFsdWVzLlxcblwiXG4gICAgICB9LFxuICAgICAgXCJsYWJlbHNcIjoge1xuICAgICAgICBcImFwcC5rdWJlcm5ldGVzLmlvL21hbmFnZWQtYnlcIjogXCJrdXN0b21pemVcIixcbiAgICAgICAgXCJhcHAua3ViZXJuZXRlcy5pby9uYW1lXCI6IFwiYXV0b21vdGl2ZS1kZXYtb3BlcmF0b3JcIlxuICAgICAgfSxcbiAgICAgIFwibmFtZVwiOiBcImltYWdlYnVpbGQtc2FtcGxlXCJcbiAgICB9LFxuICAgIFwic3BlY1wiOiB7XG4gICAgICBcImFyY2hpdGVjdHVyZVwiOiBcImFtZDY0XCIsXG4gICAgICBcImF1dG9tb3RpdmVJbWFnZUJ1aWxkZXJcIjogXCJxdWF5LmlvL2NlbnRvcy1zaWctYXV0b21vdGl2ZS9hdXRvbW90aXZlLWltYWdlLWJ1aWxkZXI6MS4wLjBcIixcbiAgICAgIFwiZGlzdHJvXCI6IFwiY3M5XCIsXG4gICAgICBcImV4cG9ydEZvcm1hdFwiOiBcInFjb3cyXCIsXG4gICAgICBcIm1hbmlmZXN0Q29uZmlnTWFwXCI6IFwibXBwXCIsXG4gICAgICBcIm1vZGVcIjogXCJpbWFnZVwiLFxuICAgICAgXCJzZXJ2ZUFydGlmYWN0XCI6IGZhbHNlLFxuICAgICAgXCJzZXJ2ZUV4cGlyeUhvdXJzXCI6IDI0LFxuICAgICAgXCJ0YXJnZXRcIjogXCJxZW11XCJcbiAgICB9XG4gIH0sXG4gIHtcbiAgICBcImFwaVZlcnNpb25cIjogXCJhdXRvbW90aXZlLnNkdi5jbG91ZC5yZWRoYXQuY29tL3YxYWxwaGExXCIsXG4gICAgXCJraW5kXCI6IFwiT3BlcmF0b3JDb25maWdcIixcbiAgICBcIm1ldGFkYXRhXCI6IHtcbiAgICAgIFwibGFiZWxzXCI6IHtcbiAgICAgICAgXCJhcHAua3ViZXJuZXRlcy5pby9tYW5hZ2VkLWJ5XCI6IFwia3VzdG9taXplXCIsXG4gICAgICAgIFwiYXBwLmt1YmVybmV0ZXMuaW8vbmFtZVwiOiBcImF1dG9tb3RpdmUtZGV2LW9wZXJhdG9yXCJcbiAgICAgIH0sXG4gICAgICBcIm5hbWVcIjogXCJhdXRvbW90aXZlLWRldlwiLFxuICAgICAgXCJuYW1lc3BhY2VcIjogXCJhdXRvbW90aXZlLWRldi1vcGVyYXRvci1zeXN0ZW1cIlxuICAgIH0sXG4gICAgXCJzcGVjXCI6IHtcbiAgICAgIFwib3NCdWlsZHNcIjoge1xuICAgICAgICBcImVuYWJsZWRcIjogdHJ1ZSxcbiAgICAgICAgXCJwdmNTaXplXCI6IFwiOEdpXCIsXG4gICAgICAgIFwic2VydmVFeHBpcnlIb3Vyc1wiOiAyNFxuICAgICAgfSxcbiAgICAgIFwid2ViVUlcIjogdHJ1ZVxuICAgIH1cbiAgfVxuXSIsImNhcGFiaWxpdGllcyI6IkJhc2ljIEluc3RhbGwiLCJjYXRlZ29yaWVzIjoiRGV2ZWxvcGVyIFRvb2xzIiwiY29udGFpbmVySW1hZ2UiOiJxdWF5LmlvL3JoLXNkdi1jbG91ZC9hdXRvbW90aXZlLWRldi1vcGVyYXRvcjpsYXRlc3QiLCJjcmVhdGVkQXQiOiIyMDI2LTAxLTA5VDExOjExOjI1WiIsIm9wZXJhdG9yZnJhbWV3b3JrLmlvL3N1Z2dlc3RlZC1uYW1lc3BhY2UiOiJhdXRvbW90aXZlLWRldi1vcGVyYXRvci1zeXN0ZW0iLCJvcGVyYXRvcnMub3BlcmF0b3JmcmFtZXdvcmsuaW8vYnVpbGRlciI6Im9wZXJhdG9yLXNkay12MS40Mi4wIiwib3BlcmF0b3JzLm9wZXJhdG9yZnJhbWV3b3JrLmlvL3Byb2plY3RfbGF5b3V0IjoiZ28ua3ViZWJ1aWxkZXIuaW8vdjQiLCJyZXBvc2l0b3J5IjoiaHR0cHM6Ly9naXRodWIuY29tL2NlbnRvcy1hdXRvbW90aXZlLXN1aXRlL2F1dG9tb3RpdmUtZGV2LW9wZXJhdG9yIiwic3VwcG9ydCI6IlJlZCBIYXQifSwibmFtZSI6ImF1dG9tb3RpdmUtZGV2LW9wZXJhdG9yLnYwLjAuMSIsIm5hbWVzcGFjZSI6InBsYWNlaG9sZGVyIn0sInNwZWMiOnsiYXBpc2VydmljZWRlZmluaXRpb25zIjp7fSwiY3VzdG9tcmVzb3VyY2VkZWZpbml0aW9ucyI6eyJvd25lZCI6W3sia2luZCI6IkNhdGFsb2dJbWFnZSIsIm5hbWUiOiJjYXRhbG9naW1hZ2VzLmF1dG9tb3RpdmUuc2R2LmNsb3VkLnJlZGhhdC5jb20iLCJ2ZXJzaW9uIjoidjFhbHBoYTEifSx7ImRlc2NyaXB0aW9uIjoiSW1hZ2VCdWlsZCBpcyB0aGUgU2NoZW1hIGZvciB0aGUgaW1hZ2VidWlsZHMgQVBJIiwiZGlzcGxheU5hbWUiOiJJbWFnZSBCdWlsZCIsImtpbmQiOiJJbWFnZUJ1aWxkIiwibmFtZSI6ImltYWdlYnVpbGRzLmF1dG9tb3RpdmUuc2R2LmNsb3VkLnJlZGhhdC5jb20iLCJ2ZXJzaW9uIjoidjFhbHBoYTEifSx7ImRlc2NyaXB0aW9uIjoiSW1hZ2UgaXMgdGhlIFNjaGVtYSBmb3IgdGhlIGltYWdlcyBBUEkiLCJkaXNwbGF5TmFtZSI6IkltYWdlIiwia2luZCI6IkltYWdlIiwibmFtZSI6ImltYWdlcy5hdXRvbW90aXZlLnNkdi5jbG91ZC5yZWRoYXQuY29tIiwidmVyc2lvbiI6InYxYWxwaGExIn0seyJkZXNjcmlwdGlvbiI6Ik9wZXJhdG9yQ29uZmlnIGlzIHRoZSBTY2hlbWEgZm9yIHRoZSBvcGVyYXRvcmNvbmZpZ3MgQVBJIiwiZGlzcGxheU5hbWUiOiJPcGVyYXRvciBDb25maWciLCJraW5kIjoiT3BlcmF0b3JDb25maWciLCJuYW1lIjoib3BlcmF0b3Jjb25maWdzLmF1dG9tb3RpdmUuc2R2LmNsb3VkLnJlZGhhdC5jb20iLCJ2ZXJzaW9uIjoidjFhbHBoYTEifV19LCJkZXNjcmlwdGlvbiI6IkNlbnRPUyBBdXRvbW90aXZlIFN1aXRlIiwiZGlzcGxheU5hbWUiOiJDZW50T1MgQXV0b21vdGl2ZSBTdWl0ZSIsImljb24iOlt7ImJhc2U2NGRhdGEiOiIiLCJtZWRpYXR5cGUiOiIifV0sImluc3RhbGwiOnsic3BlYyI6eyJjbHVzdGVyUGVybWlzc2lvbnMiOlt7InJ1bGVzIjpbeyJhcGlHcm91cHMiOlsiIl0sInJlc291cmNlcyI6WyJjb25maWdtYXBzIiwicGVyc2lzdGVudHZvbHVtZWNsYWltcyIsInBvZHMiLCJzZWNyZXRzIiwic2VydmljZWFjY291bnRzIiwic2VydmljZXMiXSwidmVyYnMiOlsiY3JlYXRlIiwiZGVsZXRlIiwiZ2V0IiwibGlzdCIsInBhdGNoIiwidXBkYXRlIiwid2F0Y2giXX0seyJhcGlHcm91cHMiOlsiIl0sInJlc291cmNlcyI6WyJldmVudHMiXSwidmVyYnMiOlsiY3JlYXRlIiwicGF0Y2giXX0seyJhcGlHcm91cHMiOlsiIl0sInJlc291cmNlcyI6WyJuYW1lc3BhY2VzIl0sInZlcmJzIjpbImNyZWF0ZSIsImdldCIsImxpc3QiLCJ3YXRjaCJdfSx7ImFwaUdyb3VwcyI6WyIiXSwicmVzb3VyY2VzIjpbInBvZHMvZXhlYyJdLCJ2ZXJicyI6WyJjcmVhdGUiXX0seyJhcGlHcm91cHMiOlsiIl0sInJlc291cmNlcyI6WyJwb2RzL2xvZyJdLCJ2ZXJicyI6WyJnZXQiXX0seyJhcGlHcm91cHMiOlsiYXBwcyJdLCJyZXNvdXJjZXMiOlsiZGVwbG95bWVudHMiXSwidmVyYnMiOlsiY3JlYXRlIiwiZGVsZXRlIiwiZ2V0IiwibGlzdCIsInBhdGNoIiwidXBkYXRlIiwid2F0Y2giXX0seyJhcGlHcm91cHMiOlsiYXV0b21vdGl2ZS5zZHYuY2xvdWQucmVkaGF0LmNvbSJdLCJyZXNvdXJjZXMiOlsiY2F0YWxvZ2ltYWdlcyIsImltYWdlYnVpbGRzIiwiaW1hZ2VzIiwib3BlcmF0b3Jjb25maWdzIl0sInZlcmJzIjpbImNyZWF0ZSIsImRlbGV0ZSIsImdldCIsImxpc3QiLCJwYXRjaCIsInVwZGF0ZSIsIndhdGNoIl19LHsiYXBpR3JvdXBzIjpbImF1dG9tb3RpdmUuc2R2LmNsb3VkLnJlZGhhdC5jb20iXSwicmVzb3VyY2VzIjpbImNhdGFsb2dpbWFnZXMvZmluYWxpemVycyIsImltYWdlYnVpbGRzL2ZpbmFsaXplcnMiLCJpbWFnZXMvZmluYWxpemVycyIsIm9wZXJhdG9yY29uZmlncy9maW5hbGl6ZXJzIl0sInZlcmJzIjpbInVwZGF0ZSJdfSx7ImFwaUdyb3VwcyI6WyJhdXRvbW90aXZlLnNkdi5jbG91ZC5yZWRoYXQuY29tIl0sInJlc291cmNlcyI6WyJjYXRhbG9naW1hZ2VzL3N0YXR1cyIsImltYWdlYnVpbGRzL3N0YXR1cyIsImltYWdlcy9zdGF0dXMiLCJvcGVyYXRvcmNvbmZpZ3Mvc3RhdHVzIl0sInZlcmJzIjpbImdldCIsInBhdGNoIiwidXBkYXRlIl19LHsiYXBpR3JvdXBzIjpbIm5ldHdvcmtpbmcuazhzLmlvIl0sInJlc291cmNlcyI6WyJpbmdyZXNzZXMiXSwidmVyYnMiOlsiY3JlYXRlIiwiZGVsZXRlIiwiZ2V0IiwibGlzdCIsInBhdGNoIiwidXBkYXRlIiwid2F0Y2giXX0seyJhcGlHcm91cHMiOlsicmJhYy5hdXRob3JpemF0aW9uLms4cy5pbyJdLCJyZXNvdXJjZXMiOlsiY2x1c3RlcnJvbGViaW5kaW5ncyIsImNsdXN0ZXJyb2xlcyIsInJvbGViaW5kaW5ncyJdLCJ2ZXJicyI6WyJjcmVhdGUiLCJkZWxldGUiLCJnZXQiLCJsaXN0IiwicGF0Y2giLCJ1cGRhdGUiLCJ3YXRjaCJdfSx7ImFwaUdyb3VwcyI6WyJyb3V0ZS5vcGVuc2hpZnQuaW8iXSwicmVzb3VyY2VzIjpbInJvdXRlcyJdLCJ2ZXJicyI6WyJjcmVhdGUiLCJkZWxldGUiLCJnZXQiLCJsaXN0IiwicGF0Y2giLCJ1cGRhdGUiLCJ3YXRjaCJdfSx7ImFwaUdyb3VwcyI6WyJzZWN1cml0eS5vcGVuc2hpZnQuaW8iXSwicmVzb3VyY2VzIjpbInNlY3VyaXR5Y29udGV4dGNvbnN0cmFpbnRzIl0sInZlcmJzIjpbImNyZWF0ZSIsImRlbGV0ZSIsImdldCIsImxpc3QiLCJwYXRjaCIsInVwZGF0ZSIsInVzZSIsIndhdGNoIl19LHsiYXBpR3JvdXBzIjpbInRla3Rvbi5kZXYiXSwicmVzb3VyY2VzIjpbInBpcGVsaW5lcnVucyIsInBpcGVsaW5lcyIsInRhc2tydW5zIiwidGFza3MiXSwidmVyYnMiOlsiY3JlYXRlIiwiZGVsZXRlIiwiZ2V0IiwibGlzdCIsInBhdGNoIiwidXBkYXRlIiwid2F0Y2giXX1dLCJzZXJ2aWNlQWNjb3VudE5hbWUiOiJhZG8tY29udHJvbGxlci1tYW5hZ2VyIn1dLCJkZXBsb3ltZW50cyI6W3sibGFiZWwiOnsiYXBwLmt1YmVybmV0ZXMuaW8vbWFuYWdlZC1ieSI6Imt1c3RvbWl6ZSIsImFwcC5rdWJlcm5ldGVzLmlvL25hbWUiOiJhdXRvbW90aXZlLWRldi1vcGVyYXRvciIsImNvbnRyb2wtcGxhbmUiOiJjb250cm9sbGVyLW1hbmFnZXIifSwibmFtZSI6ImFkby1jb250cm9sbGVyLW1hbmFnZXIiLCJzcGVjIjp7InJlcGxpY2FzIjoxLCJzZWxlY3RvciI6eyJtYXRjaExhYmVscyI6eyJjb250cm9sLXBsYW5lIjoiY29udHJvbGxlci1tYW5hZ2VyIn19LCJzdHJhdGVneSI6e30sInRlbXBsYXRlIjp7Im1ldGFkYXRhIjp7ImFubm90YXRpb25zIjp7Imt1YmVjdGwua3ViZXJuZXRlcy5pby9kZWZhdWx0LWNvbnRhaW5lciI6Im1hbmFnZXIifSwibGFiZWxzIjp7ImNvbnRyb2wtcGxhbmUiOiJjb250cm9sbGVyLW1hbmFnZXIifX0sInNwZWMiOnsiYWZmaW5pdHkiOnsibm9kZUFmZmluaXR5Ijp7InJlcXVpcmVkRHVyaW5nU2NoZWR1bGluZ0lnbm9yZWREdXJpbmdFeGVjdXRpb24iOnsibm9kZVNlbGVjdG9yVGVybXMiOlt7Im1hdGNoRXhwcmVzc2lvbnMiOlt7ImtleSI6Imt1YmVybmV0ZXMuaW8vYXJjaCIsIm9wZXJhdG9yIjoiSW4iLCJ2YWx1ZXMiOlsiYXJtNjQiLCJhbWQ2NCJdfSx7ImtleSI6Imt1YmVybmV0ZXMuaW8vb3MiLCJvcGVyYXRvciI6IkluIiwidmFsdWVzIjpbImxpbnV4Il19XX1dfX19LCJjb250YWluZXJzIjpbeyJhcmdzIjpbIi0tbGVhZGVyLWVsZWN0IiwiLS1oZWFsdGgtcHJvYmUtYmluZC1hZGRyZXNzPTo4MDgxIiwiLS1tZXRyaWNzLWJpbmQtYWRkcmVzcz0wIl0sImNvbW1hbmQiOlsiL21hbmFnZXIiXSwiZW52IjpbeyJuYW1lIjoiQlVJTERfQVBJX05BTUVTUEFDRSIsInZhbHVlRnJvbSI6eyJmaWVsZFJlZiI6eyJmaWVsZFBhdGgiOiJtZXRhZGF0YS5uYW1lc3BhY2UifX19LHsibmFtZSI6Ik9QRVJBVE9SX0lNQUdFIiwidmFsdWUiOiJpbWFnZS1yZWdpc3RyeS5vcGVuc2hpZnQtaW1hZ2UtcmVnaXN0cnkuc3ZjOjUwMDAvYXV0b21vdGl2ZS1kZXYtb3BlcmF0b3Itc3lzdGVtL2F1dG9tb3RpdmUtZGV2LW9wZXJhdG9yOmxhdGVzdCJ9XSwiaW1hZ2UiOiJkZWZhdWx0LXJvdXRlLW9wZW5zaGlmdC1pbWFnZS1yZWdpc3RyeS5hcHBzLmF1dG9tb3RpdmUxLm9jcC5hdXRvbW90aXZlLnNpZy5jZW50b3Mub3JnL2F1dG9tb3RpdmUtZGV2LW9wZXJhdG9yLXN5c3RlbS9hdXRvbW90aXZlLWRldi1vcGVyYXRvcjpsYXRlc3QiLCJpbWFnZVB1bGxQb2xpY3kiOiJJZk5vdFByZXNlbnQiLCJsaXZlbmVzc1Byb2JlIjp7Imh0dHBHZXQiOnsicGF0aCI6Ii9oZWFsdGh6IiwicG9ydCI6ODA4MX0sImluaXRpYWxEZWxheVNlY29uZHMiOjE1LCJwZXJpb2RTZWNvbmRzIjoyMH0sIm5hbWUiOiJtYW5hZ2VyIiwicG9ydHMiOlt7ImNvbnRhaW5lclBvcnQiOjgwODAsIm5hbWUiOiJidWlsZC1hcGkifV0sInJlYWRpbmVzc1Byb2JlIjp7Imh0dHBHZXQiOnsicGF0aCI6Ii9yZWFkeXoiLCJwb3J0Ijo4MDgxfSwiaW5pdGlhbERlbGF5U2Vjb25kcyI6NSwicGVyaW9kU2Vjb25kcyI6MTB9LCJyZXNvdXJjZXMiOnsibGltaXRzIjp7ImNwdSI6IjEiLCJtZW1vcnkiOiI1MTJNaSJ9LCJyZXF1ZXN0cyI6eyJjcHUiOiIxMDBtIiwibWVtb3J5IjoiMjU2TWkifX0sInNlY3VyaXR5Q29udGV4dCI6eyJhbGxvd1ByaXZpbGVnZUVzY2FsYXRpb24iOmZhbHNlLCJjYXBhYmlsaXRpZXMiOnsiZHJvcCI6WyJBTEwiXX19fV0sImluaXRDb250YWluZXJzIjpbeyJjb21tYW5kIjpbIi9pbml0LXNlY3JldHMiXSwiZW52IjpbeyJuYW1lIjoiUE9EX05BTUVTUEFDRSIsInZhbHVlRnJvbSI6eyJmaWVsZFJlZiI6eyJmaWVsZFBhdGgiOiJtZXRhZGF0YS5uYW1lc3BhY2UifX19XSwiaW1hZ2UiOiJkZWZhdWx0LXJvdXRlLW9wZW5zaGlmdC1pbWFnZS1yZWdpc3RyeS5hcHBzLmF1dG9tb3RpdmUxLm9jcC5hdXRvbW90aXZlLnNpZy5jZW50b3Mub3JnL2F1dG9tb3RpdmUtZGV2LW9wZXJhdG9yLXN5c3RlbS9hdXRvbW90aXZlLWRldi1vcGVyYXRvcjpsYXRlc3QiLCJpbWFnZVB1bGxQb2xpY3kiOiJJZk5vdFByZXNlbnQiLCJuYW1lIjoiaW5pdC1vYXV0aC1zZWNyZXRzIiwicmVzb3VyY2VzIjp7ImxpbWl0cyI6eyJjcHUiOiIxMDBtIiwibWVtb3J5IjoiNjRNaSJ9LCJyZXF1ZXN0cyI6eyJjcHUiOiIxMG0iLCJtZW1vcnkiOiIzMk1pIn19LCJzZWN1cml0eUNvbnRleHQiOnsiYWxsb3dQcml2aWxlZ2VFc2NhbGF0aW9uIjpmYWxzZSwiY2FwYWJpbGl0aWVzIjp7ImRyb3AiOlsiQUxMIl19fX1dLCJzZWN1cml0eUNvbnRleHQiOnsicnVuQXNOb25Sb290Ijp0cnVlfSwic2VydmljZUFjY291bnROYW1lIjoiYWRvLWNvbnRyb2xsZXItbWFuYWdlciIsInRlcm1pbmF0aW9uR3JhY2VQZXJpb2RTZWNvbmRzIjoxMH19fX1dLCJwZXJtaXNzaW9ucyI6W3sicnVsZXMiOlt7ImFwaUdyb3VwcyI6WyIiXSwicmVzb3VyY2VzIjpbImNvbmZpZ21hcHMiXSwidmVyYnMiOlsiZ2V0IiwibGlzdCIsIndhdGNoIiwiY3JlYXRlIiwidXBkYXRlIiwicGF0Y2giLCJkZWxldGUiXX0seyJhcGlHcm91cHMiOlsiY29vcmRpbmF0aW9uLms4cy5pbyJdLCJyZXNvdXJjZXMiOlsibGVhc2VzIl0sInZlcmJzIjpbImdldCIsImxpc3QiLCJ3YXRjaCIsImNyZWF0ZSIsInVwZGF0ZSIsInBhdGNoIiwiZGVsZXRlIl19LHsiYXBpR3JvdXBzIjpbIiJdLCJyZXNvdXJjZXMiOlsiZXZlbnRzIl0sInZlcmJzIjpbImNyZWF0ZSIsInBhdGNoIl19XSwic2VydmljZUFjY291bnROYW1lIjoiYWRvLWNvbnRyb2xsZXItbWFuYWdlciJ9XX0sInN0cmF0ZWd5IjoiZGVwbG95bWVudCJ9LCJpbnN0YWxsTW9kZXMiOlt7InN1cHBvcnRlZCI6dHJ1ZSwidHlwZSI6Ik93bk5hbWVzcGFjZSJ9LHsic3VwcG9ydGVkIjp0cnVlLCJ0eXBlIjoiU2luZ2xlTmFtZXNwYWNlIn0seyJzdXBwb3J0ZWQiOmZhbHNlLCJ0eXBlIjoiTXVsdGlOYW1lc3BhY2UifSx7InN1cHBvcnRlZCI6ZmFsc2UsInR5cGUiOiJBbGxOYW1lc3BhY2VzIn1dLCJrZXl3b3JkcyI6WyJhdXRvbW90aXZlIl0sImxpbmtzIjpbeyJuYW1lIjoiQ2VudE9TIEF1dG9tb3RpdmUgU3VpdGUiLCJ1cmwiOiJodHRwczovL2dpdGh1Yi5jb20vY2VudG9zLWF1dG9tb3RpdmUtc3VpdGUvYXV0b21vdGl2ZS1kZXYtb3BlcmF0b3IifV0sIm1hdHVyaXR5IjoiYWxwaGEiLCJtaW5LdWJlVmVyc2lvbiI6IjEuMjYuMCIsInByb3ZpZGVyIjp7Im5hbWUiOiJSZWQgSGF0In0sInZlcnNpb24iOiIwLjAuMSJ9fQ== + data: eyJhcGlWZXJzaW9uIjoib3BlcmF0b3JzLmNvcmVvcy5jb20vdjFhbHBoYTEiLCJraW5kIjoiQ2x1c3RlclNlcnZpY2VWZXJzaW9uIiwibWV0YWRhdGEiOnsiYW5ub3RhdGlvbnMiOnsiYWxtLWV4YW1wbGVzIjoiW1xuICB7XG4gICAgXCJhcGlWZXJzaW9uXCI6IFwiYXV0b21vdGl2ZS5zZHYuY2xvdWQucmVkaGF0LmNvbS92MWFscGhhMVwiLFxuICAgIFwia2luZFwiOiBcIkltYWdlQnVpbGRcIixcbiAgICBcIm1ldGFkYXRhXCI6IHtcbiAgICAgIFwiYW5ub3RhdGlvbnNcIjoge1xuICAgICAgICBcImRlc2NyaXB0aW9uXCI6IFwiRXhhbXBsZSBJbWFnZUJ1aWxkIENSLiBUaGUgYXJjaGl0ZWN0dXJlIGNhbiBiZSBzZXQgdG8gYW55IHN1cHBvcnRlZCB2YWx1ZSBsaWtlIFxcXCJhYXJjaDY0XFxcIiwgXFxcIng4Nl82NFxcXCIsIGV0Yy4gU2VlIHlvdXIgYnVpbGQgcGxhdGZvcm0ncyBkb2N1bWVudGF0aW9uIGZvciBzdXBwb3J0ZWQgdmFsdWVzLlxcblwiXG4gICAgICB9LFxuICAgICAgXCJsYWJlbHNcIjoge1xuICAgICAgICBcImFwcC5rdWJlcm5ldGVzLmlvL21hbmFnZWQtYnlcIjogXCJrdXN0b21pemVcIixcbiAgICAgICAgXCJhcHAua3ViZXJuZXRlcy5pby9uYW1lXCI6IFwiYXV0b21vdGl2ZS1kZXYtb3BlcmF0b3JcIlxuICAgICAgfSxcbiAgICAgIFwibmFtZVwiOiBcImltYWdlYnVpbGQtc2FtcGxlXCJcbiAgICB9LFxuICAgIFwic3BlY1wiOiB7XG4gICAgICBcImFyY2hpdGVjdHVyZVwiOiBcImFtZDY0XCIsXG4gICAgICBcImF1dG9tb3RpdmVJbWFnZUJ1aWxkZXJcIjogXCJxdWF5LmlvL2NlbnRvcy1zaWctYXV0b21vdGl2ZS9hdXRvbW90aXZlLWltYWdlLWJ1aWxkZXI6MS4wLjBcIixcbiAgICAgIFwiZGlzdHJvXCI6IFwiY3M5XCIsXG4gICAgICBcImV4cG9ydEZvcm1hdFwiOiBcInFjb3cyXCIsXG4gICAgICBcIm1hbmlmZXN0Q29uZmlnTWFwXCI6IFwibXBwXCIsXG4gICAgICBcIm1vZGVcIjogXCJpbWFnZVwiLFxuICAgICAgXCJzZXJ2ZUFydGlmYWN0XCI6IGZhbHNlLFxuICAgICAgXCJzZXJ2ZUV4cGlyeUhvdXJzXCI6IDI0LFxuICAgICAgXCJ0YXJnZXRcIjogXCJxZW11XCJcbiAgICB9XG4gIH0sXG4gIHtcbiAgICBcImFwaVZlcnNpb25cIjogXCJhdXRvbW90aXZlLnNkdi5jbG91ZC5yZWRoYXQuY29tL3YxYWxwaGExXCIsXG4gICAgXCJraW5kXCI6IFwiT3BlcmF0b3JDb25maWdcIixcbiAgICBcIm1ldGFkYXRhXCI6IHtcbiAgICAgIFwibGFiZWxzXCI6IHtcbiAgICAgICAgXCJhcHAua3ViZXJuZXRlcy5pby9tYW5hZ2VkLWJ5XCI6IFwia3VzdG9taXplXCIsXG4gICAgICAgIFwiYXBwLmt1YmVybmV0ZXMuaW8vbmFtZVwiOiBcImF1dG9tb3RpdmUtZGV2LW9wZXJhdG9yXCJcbiAgICAgIH0sXG4gICAgICBcIm5hbWVcIjogXCJhdXRvbW90aXZlLWRldlwiLFxuICAgICAgXCJuYW1lc3BhY2VcIjogXCJhdXRvbW90aXZlLWRldi1vcGVyYXRvci1zeXN0ZW1cIlxuICAgIH0sXG4gICAgXCJzcGVjXCI6IHtcbiAgICAgIFwib3NCdWlsZHNcIjoge1xuICAgICAgICBcImVuYWJsZWRcIjogdHJ1ZSxcbiAgICAgICAgXCJwdmNTaXplXCI6IFwiOEdpXCIsXG4gICAgICAgIFwic2VydmVFeHBpcnlIb3Vyc1wiOiAyNFxuICAgICAgfSxcbiAgICAgIFwid2ViVUlcIjogdHJ1ZVxuICAgIH1cbiAgfVxuXSIsImNhcGFiaWxpdGllcyI6IkJhc2ljIEluc3RhbGwiLCJjYXRlZ29yaWVzIjoiRGV2ZWxvcGVyIFRvb2xzIiwiY29udGFpbmVySW1hZ2UiOiJxdWF5LmlvL3JoLXNkdi1jbG91ZC9hdXRvbW90aXZlLWRldi1vcGVyYXRvcjpsYXRlc3QiLCJjcmVhdGVkQXQiOiIyMDI2LTAxLTA5VDEzOjMzOjM5WiIsIm9wZXJhdG9yZnJhbWV3b3JrLmlvL3N1Z2dlc3RlZC1uYW1lc3BhY2UiOiJhdXRvbW90aXZlLWRldi1vcGVyYXRvci1zeXN0ZW0iLCJvcGVyYXRvcnMub3BlcmF0b3JmcmFtZXdvcmsuaW8vYnVpbGRlciI6Im9wZXJhdG9yLXNkay12MS40Mi4wIiwib3BlcmF0b3JzLm9wZXJhdG9yZnJhbWV3b3JrLmlvL3Byb2plY3RfbGF5b3V0IjoiZ28ua3ViZWJ1aWxkZXIuaW8vdjQiLCJyZXBvc2l0b3J5IjoiaHR0cHM6Ly9naXRodWIuY29tL2NlbnRvcy1hdXRvbW90aXZlLXN1aXRlL2F1dG9tb3RpdmUtZGV2LW9wZXJhdG9yIiwic3VwcG9ydCI6IlJlZCBIYXQifSwibmFtZSI6ImF1dG9tb3RpdmUtZGV2LW9wZXJhdG9yLnYwLjAuMSIsIm5hbWVzcGFjZSI6InBsYWNlaG9sZGVyIn0sInNwZWMiOnsiYXBpc2VydmljZWRlZmluaXRpb25zIjp7fSwiY3VzdG9tcmVzb3VyY2VkZWZpbml0aW9ucyI6eyJvd25lZCI6W3sia2luZCI6IkNhdGFsb2dJbWFnZSIsIm5hbWUiOiJjYXRhbG9naW1hZ2VzLmF1dG9tb3RpdmUuc2R2LmNsb3VkLnJlZGhhdC5jb20iLCJ2ZXJzaW9uIjoidjFhbHBoYTEifSx7ImRlc2NyaXB0aW9uIjoiSW1hZ2VCdWlsZCBpcyB0aGUgU2NoZW1hIGZvciB0aGUgaW1hZ2VidWlsZHMgQVBJIiwiZGlzcGxheU5hbWUiOiJJbWFnZSBCdWlsZCIsImtpbmQiOiJJbWFnZUJ1aWxkIiwibmFtZSI6ImltYWdlYnVpbGRzLmF1dG9tb3RpdmUuc2R2LmNsb3VkLnJlZGhhdC5jb20iLCJ2ZXJzaW9uIjoidjFhbHBoYTEifSx7ImRlc2NyaXB0aW9uIjoiSW1hZ2UgaXMgdGhlIFNjaGVtYSBmb3IgdGhlIGltYWdlcyBBUEkiLCJkaXNwbGF5TmFtZSI6IkltYWdlIiwia2luZCI6IkltYWdlIiwibmFtZSI6ImltYWdlcy5hdXRvbW90aXZlLnNkdi5jbG91ZC5yZWRoYXQuY29tIiwidmVyc2lvbiI6InYxYWxwaGExIn0seyJkZXNjcmlwdGlvbiI6Ik9wZXJhdG9yQ29uZmlnIGlzIHRoZSBTY2hlbWEgZm9yIHRoZSBvcGVyYXRvcmNvbmZpZ3MgQVBJIiwiZGlzcGxheU5hbWUiOiJPcGVyYXRvciBDb25maWciLCJraW5kIjoiT3BlcmF0b3JDb25maWciLCJuYW1lIjoib3BlcmF0b3Jjb25maWdzLmF1dG9tb3RpdmUuc2R2LmNsb3VkLnJlZGhhdC5jb20iLCJ2ZXJzaW9uIjoidjFhbHBoYTEifV19LCJkZXNjcmlwdGlvbiI6IkNlbnRPUyBBdXRvbW90aXZlIFN1aXRlIiwiZGlzcGxheU5hbWUiOiJDZW50T1MgQXV0b21vdGl2ZSBTdWl0ZSIsImljb24iOlt7ImJhc2U2NGRhdGEiOiIiLCJtZWRpYXR5cGUiOiIifV0sImluc3RhbGwiOnsic3BlYyI6eyJjbHVzdGVyUGVybWlzc2lvbnMiOlt7InJ1bGVzIjpbeyJhcGlHcm91cHMiOlsiIl0sInJlc291cmNlcyI6WyJjb25maWdtYXBzIiwicGVyc2lzdGVudHZvbHVtZWNsYWltcyIsInBvZHMiLCJzZWNyZXRzIiwic2VydmljZWFjY291bnRzIiwic2VydmljZXMiXSwidmVyYnMiOlsiY3JlYXRlIiwiZGVsZXRlIiwiZ2V0IiwibGlzdCIsInBhdGNoIiwidXBkYXRlIiwid2F0Y2giXX0seyJhcGlHcm91cHMiOlsiIl0sInJlc291cmNlcyI6WyJldmVudHMiXSwidmVyYnMiOlsiY3JlYXRlIiwicGF0Y2giXX0seyJhcGlHcm91cHMiOlsiIl0sInJlc291cmNlcyI6WyJuYW1lc3BhY2VzIl0sInZlcmJzIjpbImNyZWF0ZSIsImdldCIsImxpc3QiLCJ3YXRjaCJdfSx7ImFwaUdyb3VwcyI6WyIiXSwicmVzb3VyY2VzIjpbInBvZHMvZXhlYyJdLCJ2ZXJicyI6WyJjcmVhdGUiXX0seyJhcGlHcm91cHMiOlsiIl0sInJlc291cmNlcyI6WyJwb2RzL2xvZyJdLCJ2ZXJicyI6WyJnZXQiXX0seyJhcGlHcm91cHMiOlsiYXBwcyJdLCJyZXNvdXJjZXMiOlsiZGVwbG95bWVudHMiXSwidmVyYnMiOlsiY3JlYXRlIiwiZGVsZXRlIiwiZ2V0IiwibGlzdCIsInBhdGNoIiwidXBkYXRlIiwid2F0Y2giXX0seyJhcGlHcm91cHMiOlsiYXV0b21vdGl2ZS5zZHYuY2xvdWQucmVkaGF0LmNvbSJdLCJyZXNvdXJjZXMiOlsiY2F0YWxvZ2ltYWdlcyIsImltYWdlYnVpbGRzIiwiaW1hZ2VzIiwib3BlcmF0b3Jjb25maWdzIl0sInZlcmJzIjpbImNyZWF0ZSIsImRlbGV0ZSIsImdldCIsImxpc3QiLCJwYXRjaCIsInVwZGF0ZSIsIndhdGNoIl19LHsiYXBpR3JvdXBzIjpbImF1dG9tb3RpdmUuc2R2LmNsb3VkLnJlZGhhdC5jb20iXSwicmVzb3VyY2VzIjpbImNhdGFsb2dpbWFnZXMvZmluYWxpemVycyIsImltYWdlYnVpbGRzL2ZpbmFsaXplcnMiLCJpbWFnZXMvZmluYWxpemVycyIsIm9wZXJhdG9yY29uZmlncy9maW5hbGl6ZXJzIl0sInZlcmJzIjpbInVwZGF0ZSJdfSx7ImFwaUdyb3VwcyI6WyJhdXRvbW90aXZlLnNkdi5jbG91ZC5yZWRoYXQuY29tIl0sInJlc291cmNlcyI6WyJjYXRhbG9naW1hZ2VzL3N0YXR1cyIsImltYWdlYnVpbGRzL3N0YXR1cyIsImltYWdlcy9zdGF0dXMiLCJvcGVyYXRvcmNvbmZpZ3Mvc3RhdHVzIl0sInZlcmJzIjpbImdldCIsInBhdGNoIiwidXBkYXRlIl19LHsiYXBpR3JvdXBzIjpbIm5ldHdvcmtpbmcuazhzLmlvIl0sInJlc291cmNlcyI6WyJpbmdyZXNzZXMiXSwidmVyYnMiOlsiY3JlYXRlIiwiZGVsZXRlIiwiZ2V0IiwibGlzdCIsInBhdGNoIiwidXBkYXRlIiwid2F0Y2giXX0seyJhcGlHcm91cHMiOlsicmJhYy5hdXRob3JpemF0aW9uLms4cy5pbyJdLCJyZXNvdXJjZXMiOlsiY2x1c3RlcnJvbGViaW5kaW5ncyIsImNsdXN0ZXJyb2xlcyIsInJvbGViaW5kaW5ncyJdLCJ2ZXJicyI6WyJjcmVhdGUiLCJkZWxldGUiLCJnZXQiLCJsaXN0IiwicGF0Y2giLCJ1cGRhdGUiLCJ3YXRjaCJdfSx7ImFwaUdyb3VwcyI6WyJyb3V0ZS5vcGVuc2hpZnQuaW8iXSwicmVzb3VyY2VzIjpbInJvdXRlcyJdLCJ2ZXJicyI6WyJjcmVhdGUiLCJkZWxldGUiLCJnZXQiLCJsaXN0IiwicGF0Y2giLCJ1cGRhdGUiLCJ3YXRjaCJdfSx7ImFwaUdyb3VwcyI6WyJzZWN1cml0eS5vcGVuc2hpZnQuaW8iXSwicmVzb3VyY2VzIjpbInNlY3VyaXR5Y29udGV4dGNvbnN0cmFpbnRzIl0sInZlcmJzIjpbImNyZWF0ZSIsImRlbGV0ZSIsImdldCIsImxpc3QiLCJwYXRjaCIsInVwZGF0ZSIsInVzZSIsIndhdGNoIl19LHsiYXBpR3JvdXBzIjpbInRla3Rvbi5kZXYiXSwicmVzb3VyY2VzIjpbInBpcGVsaW5lcnVucyIsInBpcGVsaW5lcyIsInRhc2tydW5zIiwidGFza3MiXSwidmVyYnMiOlsiY3JlYXRlIiwiZGVsZXRlIiwiZ2V0IiwibGlzdCIsInBhdGNoIiwidXBkYXRlIiwid2F0Y2giXX1dLCJzZXJ2aWNlQWNjb3VudE5hbWUiOiJhZG8tY29udHJvbGxlci1tYW5hZ2VyIn1dLCJkZXBsb3ltZW50cyI6W3sibGFiZWwiOnsiYXBwLmt1YmVybmV0ZXMuaW8vbWFuYWdlZC1ieSI6Imt1c3RvbWl6ZSIsImFwcC5rdWJlcm5ldGVzLmlvL25hbWUiOiJhdXRvbW90aXZlLWRldi1vcGVyYXRvciIsImNvbnRyb2wtcGxhbmUiOiJjb250cm9sbGVyLW1hbmFnZXIifSwibmFtZSI6ImFkby1jb250cm9sbGVyLW1hbmFnZXIiLCJzcGVjIjp7InJlcGxpY2FzIjoxLCJzZWxlY3RvciI6eyJtYXRjaExhYmVscyI6eyJjb250cm9sLXBsYW5lIjoiY29udHJvbGxlci1tYW5hZ2VyIn19LCJzdHJhdGVneSI6e30sInRlbXBsYXRlIjp7Im1ldGFkYXRhIjp7ImFubm90YXRpb25zIjp7Imt1YmVjdGwua3ViZXJuZXRlcy5pby9kZWZhdWx0LWNvbnRhaW5lciI6Im1hbmFnZXIifSwibGFiZWxzIjp7ImNvbnRyb2wtcGxhbmUiOiJjb250cm9sbGVyLW1hbmFnZXIifX0sInNwZWMiOnsiYWZmaW5pdHkiOnsibm9kZUFmZmluaXR5Ijp7InJlcXVpcmVkRHVyaW5nU2NoZWR1bGluZ0lnbm9yZWREdXJpbmdFeGVjdXRpb24iOnsibm9kZVNlbGVjdG9yVGVybXMiOlt7Im1hdGNoRXhwcmVzc2lvbnMiOlt7ImtleSI6Imt1YmVybmV0ZXMuaW8vYXJjaCIsIm9wZXJhdG9yIjoiSW4iLCJ2YWx1ZXMiOlsiYXJtNjQiLCJhbWQ2NCJdfSx7ImtleSI6Imt1YmVybmV0ZXMuaW8vb3MiLCJvcGVyYXRvciI6IkluIiwidmFsdWVzIjpbImxpbnV4Il19XX1dfX19LCJjb250YWluZXJzIjpbeyJhcmdzIjpbIi0tbGVhZGVyLWVsZWN0IiwiLS1oZWFsdGgtcHJvYmUtYmluZC1hZGRyZXNzPTo4MDgxIiwiLS1tZXRyaWNzLWJpbmQtYWRkcmVzcz0wIl0sImNvbW1hbmQiOlsiL21hbmFnZXIiXSwiZW52IjpbeyJuYW1lIjoiQlVJTERfQVBJX05BTUVTUEFDRSIsInZhbHVlRnJvbSI6eyJmaWVsZFJlZiI6eyJmaWVsZFBhdGgiOiJtZXRhZGF0YS5uYW1lc3BhY2UifX19LHsibmFtZSI6Ik9QRVJBVE9SX0lNQUdFIiwidmFsdWUiOiJpbWFnZS1yZWdpc3RyeS5vcGVuc2hpZnQtaW1hZ2UtcmVnaXN0cnkuc3ZjOjUwMDAvYXV0b21vdGl2ZS1kZXYtb3BlcmF0b3Itc3lzdGVtL2F1dG9tb3RpdmUtZGV2LW9wZXJhdG9yOmxhdGVzdCJ9XSwiaW1hZ2UiOiJkZWZhdWx0LXJvdXRlLW9wZW5zaGlmdC1pbWFnZS1yZWdpc3RyeS5hcHBzLmF1dG9tb3RpdmUxLm9jcC5hdXRvbW90aXZlLnNpZy5jZW50b3Mub3JnL2F1dG9tb3RpdmUtZGV2LW9wZXJhdG9yLXN5c3RlbS9hdXRvbW90aXZlLWRldi1vcGVyYXRvcjpsYXRlc3QiLCJpbWFnZVB1bGxQb2xpY3kiOiJJZk5vdFByZXNlbnQiLCJsaXZlbmVzc1Byb2JlIjp7Imh0dHBHZXQiOnsicGF0aCI6Ii9oZWFsdGh6IiwicG9ydCI6ODA4MX0sImluaXRpYWxEZWxheVNlY29uZHMiOjE1LCJwZXJpb2RTZWNvbmRzIjoyMH0sIm5hbWUiOiJtYW5hZ2VyIiwicG9ydHMiOlt7ImNvbnRhaW5lclBvcnQiOjgwODAsIm5hbWUiOiJidWlsZC1hcGkifV0sInJlYWRpbmVzc1Byb2JlIjp7Imh0dHBHZXQiOnsicGF0aCI6Ii9yZWFkeXoiLCJwb3J0Ijo4MDgxfSwiaW5pdGlhbERlbGF5U2Vjb25kcyI6NSwicGVyaW9kU2Vjb25kcyI6MTB9LCJyZXNvdXJjZXMiOnsibGltaXRzIjp7ImNwdSI6IjEiLCJtZW1vcnkiOiI1MTJNaSJ9LCJyZXF1ZXN0cyI6eyJjcHUiOiIxMDBtIiwibWVtb3J5IjoiMjU2TWkifX0sInNlY3VyaXR5Q29udGV4dCI6eyJhbGxvd1ByaXZpbGVnZUVzY2FsYXRpb24iOmZhbHNlLCJjYXBhYmlsaXRpZXMiOnsiZHJvcCI6WyJBTEwiXX19fV0sImluaXRDb250YWluZXJzIjpbeyJjb21tYW5kIjpbIi9pbml0LXNlY3JldHMiXSwiZW52IjpbeyJuYW1lIjoiUE9EX05BTUVTUEFDRSIsInZhbHVlRnJvbSI6eyJmaWVsZFJlZiI6eyJmaWVsZFBhdGgiOiJtZXRhZGF0YS5uYW1lc3BhY2UifX19XSwiaW1hZ2UiOiJkZWZhdWx0LXJvdXRlLW9wZW5zaGlmdC1pbWFnZS1yZWdpc3RyeS5hcHBzLmF1dG9tb3RpdmUxLm9jcC5hdXRvbW90aXZlLnNpZy5jZW50b3Mub3JnL2F1dG9tb3RpdmUtZGV2LW9wZXJhdG9yLXN5c3RlbS9hdXRvbW90aXZlLWRldi1vcGVyYXRvcjpsYXRlc3QiLCJpbWFnZVB1bGxQb2xpY3kiOiJJZk5vdFByZXNlbnQiLCJuYW1lIjoiaW5pdC1vYXV0aC1zZWNyZXRzIiwicmVzb3VyY2VzIjp7ImxpbWl0cyI6eyJjcHUiOiIxMDBtIiwibWVtb3J5IjoiNjRNaSJ9LCJyZXF1ZXN0cyI6eyJjcHUiOiIxMG0iLCJtZW1vcnkiOiIzMk1pIn19LCJzZWN1cml0eUNvbnRleHQiOnsiYWxsb3dQcml2aWxlZ2VFc2NhbGF0aW9uIjpmYWxzZSwiY2FwYWJpbGl0aWVzIjp7ImRyb3AiOlsiQUxMIl19fX1dLCJzZWN1cml0eUNvbnRleHQiOnsicnVuQXNOb25Sb290Ijp0cnVlfSwic2VydmljZUFjY291bnROYW1lIjoiYWRvLWNvbnRyb2xsZXItbWFuYWdlciIsInRlcm1pbmF0aW9uR3JhY2VQZXJpb2RTZWNvbmRzIjoxMH19fX1dLCJwZXJtaXNzaW9ucyI6W3sicnVsZXMiOlt7ImFwaUdyb3VwcyI6WyIiXSwicmVzb3VyY2VzIjpbImNvbmZpZ21hcHMiXSwidmVyYnMiOlsiZ2V0IiwibGlzdCIsIndhdGNoIiwiY3JlYXRlIiwidXBkYXRlIiwicGF0Y2giLCJkZWxldGUiXX0seyJhcGlHcm91cHMiOlsiY29vcmRpbmF0aW9uLms4cy5pbyJdLCJyZXNvdXJjZXMiOlsibGVhc2VzIl0sInZlcmJzIjpbImdldCIsImxpc3QiLCJ3YXRjaCIsImNyZWF0ZSIsInVwZGF0ZSIsInBhdGNoIiwiZGVsZXRlIl19LHsiYXBpR3JvdXBzIjpbIiJdLCJyZXNvdXJjZXMiOlsiZXZlbnRzIl0sInZlcmJzIjpbImNyZWF0ZSIsInBhdGNoIl19XSwic2VydmljZUFjY291bnROYW1lIjoiYWRvLWNvbnRyb2xsZXItbWFuYWdlciJ9XX0sInN0cmF0ZWd5IjoiZGVwbG95bWVudCJ9LCJpbnN0YWxsTW9kZXMiOlt7InN1cHBvcnRlZCI6dHJ1ZSwidHlwZSI6Ik93bk5hbWVzcGFjZSJ9LHsic3VwcG9ydGVkIjp0cnVlLCJ0eXBlIjoiU2luZ2xlTmFtZXNwYWNlIn0seyJzdXBwb3J0ZWQiOmZhbHNlLCJ0eXBlIjoiTXVsdGlOYW1lc3BhY2UifSx7InN1cHBvcnRlZCI6ZmFsc2UsInR5cGUiOiJBbGxOYW1lc3BhY2VzIn1dLCJrZXl3b3JkcyI6WyJhdXRvbW90aXZlIl0sImxpbmtzIjpbeyJuYW1lIjoiQ2VudE9TIEF1dG9tb3RpdmUgU3VpdGUiLCJ1cmwiOiJodHRwczovL2dpdGh1Yi5jb20vY2VudG9zLWF1dG9tb3RpdmUtc3VpdGUvYXV0b21vdGl2ZS1kZXYtb3BlcmF0b3IifV0sIm1hdHVyaXR5IjoiYWxwaGEiLCJtaW5LdWJlVmVyc2lvbiI6IjEuMjYuMCIsInByb3ZpZGVyIjp7Im5hbWUiOiJSZWQgSGF0In0sInZlcnNpb24iOiIwLjAuMSJ9fQ== - type: olm.bundle.object value: data: eyJhcGlWZXJzaW9uIjoicmJhYy5hdXRob3JpemF0aW9uLms4cy5pby92MSIsImtpbmQiOiJDbHVzdGVyUm9sZSIsIm1ldGFkYXRhIjp7ImNyZWF0aW9uVGltZXN0YW1wIjpudWxsLCJsYWJlbHMiOnsiYXBwLmt1YmVybmV0ZXMuaW8vbWFuYWdlZC1ieSI6Imt1c3RvbWl6ZSIsImFwcC5rdWJlcm5ldGVzLmlvL25hbWUiOiJhZG8ifSwibmFtZSI6ImFkby1pbWFnZS12aWV3ZXItcm9sZSJ9LCJydWxlcyI6W3siYXBpR3JvdXBzIjpbImF1dG9tb3RpdmUuc2R2LmNsb3VkLnJlZGhhdC5jb20iXSwicmVzb3VyY2VzIjpbImltYWdlcyJdLCJ2ZXJicyI6WyJnZXQiLCJsaXN0Iiwid2F0Y2giXX0seyJhcGlHcm91cHMiOlsiYXV0b21vdGl2ZS5zZHYuY2xvdWQucmVkaGF0LmNvbSJdLCJyZXNvdXJjZXMiOlsiaW1hZ2VzL3N0YXR1cyJdLCJ2ZXJicyI6WyJnZXQiXX1dfQ== diff --git a/cmd/caib/main.go b/cmd/caib/main.go index bf707feb..25cd1bc8 100644 --- a/cmd/caib/main.go +++ b/cmd/caib/main.go @@ -796,6 +796,10 @@ func waitForBuildCompletion(ctx context.Context, api *buildapiclient.Client, nam userFollowRequested := followLogs var lastPhase, lastMessage string logFollowWarned := false + logRetryFailureWarned := false + logStreamActive := false + logRetryCount := 0 + const maxLogRetries = 24 // Try for ~2 minutes (24 * 5s = 120s) before giving up logClient := &http.Client{ Timeout: 10 * time.Minute, @@ -810,36 +814,7 @@ func waitForBuildCompletion(ctx context.Context, api *buildapiclient.Client, nam case <-timeoutCtx.Done(): handleError(fmt.Errorf("timed out waiting for build")) case <-ticker.C: - if followLogs { - req, _ := http.NewRequestWithContext(ctx, http.MethodGet, strings.TrimRight(serverURL, "/")+"/v1/builds/"+url.PathEscape(name)+"/logs?follow=1", nil) - if strings.TrimSpace(authToken) != "" { - req.Header.Set("Authorization", "Bearer "+strings.TrimSpace(authToken)) - } - resp, err := logClient.Do(req) - if err == nil && resp.StatusCode == http.StatusOK { - fmt.Println("Streaming logs...") - io.Copy(os.Stdout, resp.Body) - resp.Body.Close() - followLogs = userFollowRequested - } else if resp != nil { - body, _ := io.ReadAll(resp.Body) - msg := strings.TrimSpace(string(body)) - if resp.StatusCode == http.StatusServiceUnavailable || resp.StatusCode == http.StatusGatewayTimeout { - if !logFollowWarned { - fmt.Println("log stream not ready (HTTP", resp.StatusCode, "). Retrying...") - logFollowWarned = true - } - } else { - if msg != "" { - fmt.Printf("log stream error (%d): %s\n", resp.StatusCode, msg) - } else { - fmt.Printf("log stream error: HTTP %d\n", resp.StatusCode) - } - followLogs = false - } - resp.Body.Close() - } - } + // First, check build status reqCtx, cancelReq := context.WithTimeout(ctx, 2*time.Minute) st, err := api.GetBuild(reqCtx, name) cancelReq() @@ -847,13 +822,17 @@ func waitForBuildCompletion(ctx context.Context, api *buildapiclient.Client, nam fmt.Printf("status check failed: %v\n", err) continue } - if !userFollowRequested { + + // Update status display (only if not actively streaming logs) + if !logStreamActive && (!userFollowRequested || logRetryCount > maxLogRetries) { if st.Phase != lastPhase || st.Message != lastMessage { fmt.Printf("status: %s - %s\n", st.Phase, st.Message) lastPhase = st.Phase lastMessage = st.Message } } + + // Handle completed/failed builds if st.Phase == "Completed" { if downloadTo != "" { outDir := filepath.Dir(downloadTo) @@ -869,8 +848,91 @@ func waitForBuildCompletion(ctx context.Context, api *buildapiclient.Client, nam if st.Phase == "Failed" { handleError(fmt.Errorf("build failed: %s", st.Message)) } + + // Only attempt log streaming if: + // 1. User requested it + // 2. Build is in active phase (Building, Running, etc.) + // 3. We haven't exceeded retry limit + // 4. We're not already streaming + if followLogs && !logStreamActive && logRetryCount <= maxLogRetries { + buildIsActive := st.Phase == "Building" || st.Phase == "Running" || st.Phase == "Uploading" + if buildIsActive || (st.Phase != "Pending" && st.Phase != "Completed" && st.Phase != "Failed") { + // First attempt - show helpful message + if logRetryCount == 0 && buildIsActive { + fmt.Println("Build is active. Attempting to stream logs...") + logFollowWarned = false // Reset so we can show retry messages + } + + if err := tryLogStreaming(ctx, logClient, name, &logStreamActive, &logRetryCount, &logFollowWarned); err != nil { + // Log streaming failed, will retry next iteration if under limit + logRetryCount++ + if logRetryCount > maxLogRetries && !logRetryFailureWarned { + fmt.Printf("Log streaming failed after %d attempts (~2 minutes). Falling back to status updates only.\n", maxLogRetries) + logRetryFailureWarned = true + } + } else { + // Streaming succeeded, reset retry count + logRetryCount = 0 + followLogs = userFollowRequested // Reset for potential future streams + } + } else if st.Phase == "Pending" { + // Build not yet started, reset retry count and wait + logRetryCount = 0 + if userFollowRequested && !logFollowWarned { + fmt.Println("Waiting for build to start before streaming logs...") + logFollowWarned = true // Prevent spam + } + } + } + } + } +} + +// tryLogStreaming attempts to stream logs and returns error if it fails +func tryLogStreaming(ctx context.Context, logClient *http.Client, name string, logStreamActive *bool, logRetryCount *int, logFollowWarned *bool) error { + req, _ := http.NewRequestWithContext(ctx, http.MethodGet, strings.TrimRight(serverURL, "/")+"/v1/builds/"+url.PathEscape(name)+"/logs?follow=1", nil) + if strings.TrimSpace(authToken) != "" { + req.Header.Set("Authorization", "Bearer "+strings.TrimSpace(authToken)) + } + + resp, err := logClient.Do(req) + if err != nil { + return fmt.Errorf("log request failed: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode == http.StatusOK { + fmt.Println("Streaming logs...") + *logStreamActive = true + *logFollowWarned = false + *logRetryCount = 0 + _, copyErr := io.Copy(os.Stdout, resp.Body) + *logStreamActive = false + if copyErr != nil { + return fmt.Errorf("log stream interrupted: %w", copyErr) + } + return nil // Streaming completed successfully + } + + // Handle error responses + body, _ := io.ReadAll(resp.Body) + msg := strings.TrimSpace(string(body)) + + if resp.StatusCode == http.StatusServiceUnavailable || resp.StatusCode == http.StatusGatewayTimeout { + if !*logFollowWarned { + fmt.Printf("log stream not ready (HTTP %d). Retrying... (attempt %d/24)\n", resp.StatusCode, *logRetryCount+1) + *logFollowWarned = true } + return fmt.Errorf("log endpoint not ready (HTTP %d)", resp.StatusCode) + } + + // Other errors - show message and stop trying + if msg != "" { + fmt.Printf("log stream error (%d): %s\n", resp.StatusCode, msg) + } else { + fmt.Printf("log stream error: HTTP %d\n", resp.StatusCode) } + return fmt.Errorf("log stream failed with HTTP %d", resp.StatusCode) } func handleError(err error) { diff --git a/internal/buildapi/server.go b/internal/buildapi/server.go index 3c31274d..ac431ec5 100644 --- a/internal/buildapi/server.go +++ b/internal/buildapi/server.go @@ -288,7 +288,7 @@ func streamLogs(c *gin.Context, name string) { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } - pods, err := quickCS.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{LabelSelector: "tekton.dev/pipelineRun=" + tr + ",tekton.dev/taskRun"}) + pods, err := quickCS.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{LabelSelector: "tekton.dev/pipelineRun=" + tr + ",tekton.dev/memberOf=tasks"}) if err != nil || len(pods.Items) == 0 { c.JSON(http.StatusServiceUnavailable, gin.H{"error": "logs not available yet"}) return @@ -484,7 +484,7 @@ func streamLogsSSE(c *gin.Context, name string) { c.Writer.Flush() return } - pods, err := quickCS.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{LabelSelector: "tekton.dev/pipelineRun=" + tr + ",tekton.dev/taskRun"}) + pods, err := quickCS.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{LabelSelector: "tekton.dev/pipelineRun=" + tr + ",tekton.dev/memberOf=tasks"}) if err != nil || len(pods.Items) == 0 { sendSSEEvent(c, "waiting", "", "Build pods not ready yet, waiting for logs...") c.Writer.Flush() From 28099648343dc5980059ae1ea4d34bd51dac90c4 Mon Sep 17 00:00:00 2001 From: Benny Zlotnik Date: Sun, 11 Jan 2026 09:07:38 +0200 Subject: [PATCH 5/7] --follow by default Signed-off-by: Benny Zlotnik --- cmd/caib/main.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/caib/main.go b/cmd/caib/main.go index 25cd1bc8..d3138cb6 100644 --- a/cmd/caib/main.go +++ b/cmd/caib/main.go @@ -249,7 +249,7 @@ Examples: buildCmd.Flags().StringArrayVarP(&customDefs, "define", "D", []string{}, "custom definition KEY=VALUE") buildCmd.Flags().IntVar(&timeout, "timeout", 60, "timeout in minutes") buildCmd.Flags().BoolVarP(&waitForBuild, "wait", "w", false, "wait for build to complete") - buildCmd.Flags().BoolVarP(&followLogs, "follow", "f", false, "follow build logs") + buildCmd.Flags().BoolVarP(&followLogs, "follow", "f", true, "follow build logs") _ = buildCmd.MarkFlagRequired("push") downloadCmd.Flags().StringVar(&serverURL, "server", os.Getenv("CAIB_SERVER"), "REST API server base URL (e.g. https://api.example)") @@ -279,7 +279,7 @@ Examples: diskCmd.Flags().StringVar(&storageClass, "storage-class", "", "Kubernetes storage class") diskCmd.Flags().IntVar(&timeout, "timeout", 60, "timeout in minutes") diskCmd.Flags().BoolVarP(&waitForBuild, "wait", "w", false, "wait for build to complete") - diskCmd.Flags().BoolVarP(&followLogs, "follow", "f", false, "follow build logs") + diskCmd.Flags().BoolVarP(&followLogs, "follow", "f", true, "follow build logs") // build-legacy command flags (traditional ostree/package builds) buildLegacyCmd.Flags().StringVar(&serverURL, "server", os.Getenv("CAIB_SERVER"), "REST API server base URL") @@ -300,7 +300,7 @@ Examples: buildLegacyCmd.Flags().StringArrayVarP(&customDefs, "define", "D", []string{}, "custom definition KEY=VALUE") buildLegacyCmd.Flags().IntVar(&timeout, "timeout", 60, "timeout in minutes") buildLegacyCmd.Flags().BoolVarP(&waitForBuild, "wait", "w", false, "wait for build to complete") - buildLegacyCmd.Flags().BoolVarP(&followLogs, "follow", "f", false, "follow build logs") + buildLegacyCmd.Flags().BoolVarP(&followLogs, "follow", "f", true, "follow build logs") _ = buildLegacyCmd.MarkFlagRequired("mode") _ = buildLegacyCmd.MarkFlagRequired("format") From 95a57b1d7d8c8a37a8853042546b200a95f69e3f Mon Sep 17 00:00:00 2001 From: Benny Zlotnik Date: Sun, 11 Jan 2026 10:07:02 +0200 Subject: [PATCH 6/7] simplify CLI Signed-off-by: Benny Zlotnik --- cmd/caib/main.go | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/cmd/caib/main.go b/cmd/caib/main.go index d3138cb6..d0415827 100644 --- a/cmd/caib/main.go +++ b/cmd/caib/main.go @@ -235,7 +235,7 @@ Examples: buildCmd.Flags().StringVarP(&distro, "distro", "d", "autosd", "distribution to build") buildCmd.Flags().StringVarP(&target, "target", "t", "qemu", "target platform") buildCmd.Flags().StringVarP(&architecture, "arch", "a", getDefaultArch(), "architecture (amd64, arm64)") - buildCmd.Flags().StringVar(&containerPush, "push", "", "push bootc container to registry (required)") + buildCmd.Flags().StringVar(&containerPush, "push", "", "push bootc container to registry (optional if --disk is used)") buildCmd.Flags().BoolVar(&buildDiskImage, "disk", false, "also build disk image from container") buildCmd.Flags().StringVarP(&outputDir, "output", "o", "", "download disk image to file (requires --disk)") buildCmd.Flags().StringVar(&diskFormat, "format", "", "disk image format (qcow2, raw, simg); inferred from output filename if not set") @@ -250,7 +250,7 @@ Examples: buildCmd.Flags().IntVar(&timeout, "timeout", 60, "timeout in minutes") buildCmd.Flags().BoolVarP(&waitForBuild, "wait", "w", false, "wait for build to complete") buildCmd.Flags().BoolVarP(&followLogs, "follow", "f", true, "follow build logs") - _ = buildCmd.MarkFlagRequired("push") + // Note: --push is optional when --disk is used (disk image becomes the output) downloadCmd.Flags().StringVar(&serverURL, "server", os.Getenv("CAIB_SERVER"), "REST API server base URL (e.g. https://api.example)") downloadCmd.Flags().StringVar(&authToken, "token", os.Getenv("CAIB_TOKEN"), "Bearer token for authentication (e.g., OpenShift access token)") @@ -337,6 +337,12 @@ func runBuild(cmd *cobra.Command, args []string) { buildDiskImage = true // imply --disk when --output is specified } + // Validate: --push is required unless we're building a disk image + // (disk image becomes the output, so container push is optional) + if containerPush == "" && !buildDiskImage { + handleError(fmt.Errorf("--push is required when not building a disk image (use --disk or --output to create a disk image without pushing the container)")) + } + // Note: diskFormat can be empty - AIB will default to raw (or infer from output filename extension) api, err := createBuildAPIClient(serverURL, &authToken) @@ -369,6 +375,7 @@ func runBuild(cmd *cobra.Command, args []string) { BuildDiskImage: buildDiskImage, ExportOCI: exportOCI, BuilderImage: builderImage, + ServeArtifact: outputDir != "" && exportOCI == "", } if effectiveRegistryURL != "" { From 7345dc893e415acc4034e37c0103c4bbcaff5ff7 Mon Sep 17 00:00:00 2001 From: Benny Zlotnik Date: Sun, 11 Jan 2026 10:17:23 +0200 Subject: [PATCH 7/7] improve secret handling Signed-off-by: Benny Zlotnik --- internal/buildapi/server.go | 27 ++++++--- internal/controller/imagebuild/controller.go | 61 ++++++++++++-------- 2 files changed, 58 insertions(+), 30 deletions(-) diff --git a/internal/buildapi/server.go b/internal/buildapi/server.go index ac431ec5..141ff21f 100644 --- a/internal/buildapi/server.go +++ b/internal/buildapi/server.go @@ -8,6 +8,7 @@ import ( "encoding/json" "fmt" "io" + "log" "net/http" "os" "path" @@ -1032,19 +1033,20 @@ func createBuild(c *gin.Context) { return } - if err := setOwnerRef(ctx, k8sClient, namespace, cfgName, imageBuild); err != nil { - // best-effort + // Set owner references for cascading deletion + if err := setConfigMapOwnerRef(ctx, k8sClient, namespace, cfgName, imageBuild); err != nil { + log.Printf("WARNING: failed to set owner reference on ConfigMap %s: %v (cleanup may require manual intervention)", cfgName, err) } if envSecretRef != "" { - if err := setOwnerRef(ctx, k8sClient, namespace, envSecretRef, imageBuild); err != nil { - // best-effort + if err := setSecretOwnerRef(ctx, k8sClient, namespace, envSecretRef, imageBuild); err != nil { + log.Printf("WARNING: failed to set owner reference on registry secret %s: %v (cleanup may require manual intervention)", envSecretRef, err) } } if pushSecretName != "" { - if err := setOwnerRef(ctx, k8sClient, namespace, pushSecretName, imageBuild); err != nil { - // best-effort + if err := setSecretOwnerRef(ctx, k8sClient, namespace, pushSecretName, imageBuild); err != nil { + log.Printf("WARNING: failed to set owner reference on push secret %s: %v (cleanup may require manual intervention)", pushSecretName, err) } } @@ -1913,7 +1915,7 @@ func copyFileToPod(config *rest.Config, namespace, podName, containerName, local return executor.StreamWithContext(context.Background(), remotecommand.StreamOptions{Stdin: pr, Stdout: io.Discard, Stderr: io.Discard}) } -func setOwnerRef(ctx context.Context, c client.Client, namespace, configMapName string, owner *automotivev1alpha1.ImageBuild) error { +func setConfigMapOwnerRef(ctx context.Context, c client.Client, namespace, configMapName string, owner *automotivev1alpha1.ImageBuild) error { cm := &corev1.ConfigMap{} if err := c.Get(ctx, types.NamespacedName{Name: configMapName, Namespace: namespace}, cm); err != nil { return err @@ -1924,6 +1926,17 @@ func setOwnerRef(ctx context.Context, c client.Client, namespace, configMapName return c.Update(ctx, cm) } +func setSecretOwnerRef(ctx context.Context, c client.Client, namespace, secretName string, owner *automotivev1alpha1.ImageBuild) error { + secret := &corev1.Secret{} + if err := c.Get(ctx, types.NamespacedName{Name: secretName, Namespace: namespace}, secret); err != nil { + return err + } + secret.OwnerReferences = []metav1.OwnerReference{ + *metav1.NewControllerRef(owner, automotivev1alpha1.GroupVersion.WithKind("ImageBuild")), + } + return c.Update(ctx, secret) +} + func writeJSON(c *gin.Context, status int, v any) { c.Header("Cache-Control", "no-store") c.IndentedJSON(status, v) diff --git a/internal/controller/imagebuild/controller.go b/internal/controller/imagebuild/controller.go index 96bfd9a8..2dbe1f6f 100644 --- a/internal/controller/imagebuild/controller.go +++ b/internal/controller/imagebuild/controller.go @@ -1126,37 +1126,51 @@ server { } // cleanupTransientSecrets deletes any transient secrets created for this build +// Uses retry logic to handle transient API errors func (r *ImageBuildReconciler) cleanupTransientSecrets(ctx context.Context, imageBuild *automotivev1alpha1.ImageBuild, log logr.Logger) { // Cleanup registry auth secret (EnvSecretRef) if imageBuild.Spec.EnvSecretRef != "" { - secret := &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: imageBuild.Spec.EnvSecretRef, - Namespace: imageBuild.Namespace, - }, - } - if err := r.Delete(ctx, secret); err != nil && !errors.IsNotFound(err) { - log.Error(err, "Failed to delete registry auth secret", "secret", imageBuild.Spec.EnvSecretRef) - } else if err == nil { - log.Info("Deleted registry auth secret", "secret", imageBuild.Spec.EnvSecretRef) - } + r.deleteSecretWithRetry(ctx, imageBuild.Namespace, imageBuild.Spec.EnvSecretRef, "registry auth", log) } // Cleanup push secret (Publishers.Registry.Secret) if imageBuild.Spec.Publishers != nil && imageBuild.Spec.Publishers.Registry != nil { secretName := imageBuild.Spec.Publishers.Registry.Secret if secretName != "" { - secret := &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: secretName, - Namespace: imageBuild.Namespace, - }, - } - if err := r.Delete(ctx, secret); err != nil && !errors.IsNotFound(err) { - log.Error(err, "Failed to delete push secret", "secret", secretName) - } else if err == nil { - log.Info("Deleted push secret", "secret", secretName) - } + r.deleteSecretWithRetry(ctx, imageBuild.Namespace, secretName, "push", log) + } + } +} + +// deleteSecretWithRetry attempts to delete a secret with exponential backoff retry +func (r *ImageBuildReconciler) deleteSecretWithRetry(ctx context.Context, namespace, secretName, secretType string, log logr.Logger) { + maxRetries := 3 + backoff := 100 * time.Millisecond + + for attempt := 1; attempt <= maxRetries; attempt++ { + secret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: secretName, + Namespace: namespace, + }, + } + err := r.Delete(ctx, secret) + if err == nil { + log.Info("Deleted "+secretType+" secret", "secret", secretName) + return + } + if errors.IsNotFound(err) { + // Already deleted, nothing to do + return + } + + // Transient error - retry with backoff + if attempt < maxRetries { + log.V(1).Info("Retrying secret deletion", "secret", secretName, "attempt", attempt, "error", err.Error()) + time.Sleep(backoff) + backoff *= 2 // Exponential backoff + } else { + log.Error(err, "Failed to delete "+secretType+" secret after retries (manual cleanup may be required)", "secret", secretName, "attempts", maxRetries) } } } @@ -1169,7 +1183,8 @@ func (r *ImageBuildReconciler) SetupWithManager(mgr ctrl.Manager) error { Owns(&corev1.Pod{}). Owns(&corev1.PersistentVolumeClaim{}). Owns(&corev1.Service{}). - Owns(&corev1.ConfigMap{}) + Owns(&corev1.ConfigMap{}). + Owns(&corev1.Secret{}) // Only add Route ownership if the Route CRD is available (OpenShift only) _, err := mgr.GetRESTMapper().RESTMapping(routev1.GroupVersion.WithKind("Route").GroupKind())