feat: airgaps - #105
Merged
Merged
Conversation
oc-mirror v2 config targeting quay.apac-tech-lab.net/mirror: - OCP 4.21.24 single release - Full baremetal operator set (OSC 1.13, Trustee 1.2, ACM, ESO, GitOps, cert-manager, Kyverno, NFD, LVM, CNV, GPU, Intel) - Application images (ubi9/httpd-24, vault) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…iguration - Add 7 VP Helm OCI charts as additionalImages (clustergroup, acm, vault, eso, trustee, sandbox, policies) - Add Kyverno chart OCI artifact (ghcr.io/kyverno/charts/kyverno:3.7.2) - Add VP utility + imperative container images - Add oc-debug support-tools image (D-05) - Add ose-cli image for wait-for-vault job - Add Intel DCAP images (tag-based + digest-pinned from chart templates) - Add ubi9 workload images - Replace commented chart list with live additionalImages entries
- values-global: SSH git URL, helmRepoUrl, tokenSecret, CatalogSource sources - values-global: clusterGroupName set to baremetal, patternsOperator + gitops sources - values-baremetal: source overrides on all 10 subscriptions (8 redhat, 2 certified) - values-baremetal: Kyverno repoURL changed to OCI quay mirror path - Implements DPAT-01,03,04,06,14
- bootstrap_secrets v2.0 format for ArgoCD repo credential - Creates private-repo Secret with SSH key (~/.ssh/id_rsa) + git URL - URL matches values-global.yaml repoURL exactly for ArgoCD prefix matching - Implements DPAT-02
The patterns-operator's getUserFromURL() only supports SCP-style URLs (user@host:path), not ssh:// protocol URLs. Also use IPv4 address since cluster DNS resolves hostname to unreachable IPv6. - values-global.yaml: chbutler@172.25.36.135:/home/chbutler/coco-pattern - values-secret.yaml.template: same URL in bootstrap_secrets Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
patterns-operator webhook rejects SSH URLs unless they start with git@, and creating a git user requires sudo (unavailable on jump host). Switch to dumb HTTP via Apache UserDir (~/public_html/git/). - values-global.yaml: http://172.25.36.135/~chbutler/git/coco-pattern.git - values-secret.yaml.template: same URL in bootstrap_secrets Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
patterns-operator is not in redhat-operator-index for OCP 4.21. It's in the community-operator-index. Add it to the mirroring config so oc-mirror handles it and the operator installs via OLM normally. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- scripts/airgap-post-install.sh: idempotent bootstrap for disconnected clusters (disable default catalogs, mirror OCI Helm charts, add CA certs to ArgoCD, create bare git repos for HTTP serving, optional routingViaHost for test-lab networks) - scripts/git-http-server.py: smart HTTP fallback server if Apache dumb HTTP doesn't work with go-git - Makefile: airgap-post-install and airgap-sync-repos targets - values-global.yaml: remove tokenSecret (HTTP repos need no auth) - values-secret.yaml.template: remove bootstrap_secrets section Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Generic target wrapping oc-mirror with configurable MIRROR_REGISTRY, AUTHFILE, and IMAGESET_CONFIG variables. Workflow: make airgap-mirror MIRROR_REGISTRY=quay.example.com:443/mirror make airgap-post-install make install Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
oc-mirror reads auth from containers/auth.json or docker config.json automatically. Remove the -a flag (not supported by oc-mirror). Users should podman login or copy pull-secret.json to the right path. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
skopeo is only needed for listing pattern-install tags. Make it optional — the script warns and skips that step if skopeo is missing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
patterns-operator is in community-operator-index, not redhat-operator-index. The previous value caused the subscription to reference a non-existent CatalogSource on the disconnected cluster. Added comments clarifying these are mirrored CatalogSource names from labctl apply-mirror-resources. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Major rewrite of the disconnected deployment bootstrap: Layer B (general disconnected OpenShift): - Create all 3 mirrored CatalogSources (redhat, certified, community) - Create ITMS for tag-based image pulls (ubi-minimal, VP images) - Fix manifest list failures from oc-mirror (skopeo fallback) - Auto-extract CA from cluster trust bundle for ArgoCD Layer C (validated patterns disconnected): - Deploy Pattern CR directly via oc apply (bypass pattern.sh) - Create patterns-operator-config ConfigMap (override gitops channel) - Add gitops.channel: latest to values-global.yaml - Add patternsOperator.channel: fast to values-global.yaml - Smart HTTP git server setup (go-git can't handle Apache dumb HTTP) imageset-config.yaml: - Add imperative-container:v1 (VP uses :v1, not :latest) - Add pattern-ui-catalog:stable-v1 (operator console plugin) - Add ubi-minimal:latest explicitly Makefile: - airgap-deploy-pattern: deploy Pattern CR directly - airgap-fix-manifests: fix oc-mirror manifest list failures Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add step 7b: configure_argocd_helm_auth - Extracts registry credentials from cluster pull-secret - Creates helm-registry-config secret in ArgoCD namespace - Patches ArgoCD CR to mount HELM_REGISTRY_CONFIG - Runs automatically during --deploy-pattern mode This fixes the 401 errors when ArgoCD tries to pull OCI Helm charts from private Quay repositories. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ArgoCD's OCI registry credential system is broken (bugs #25513, #26311). The ArgoCD operator reverts any custom env/volume patches. Making Quay repos public requires admin access we don't have. Solution: extract charts from OCI mirror, serve as traditional Helm HTTP repo via Apache UserDir. No auth needed, no OCI issues. - helmRepoUrl changed to http://jump-host/~user/helm-charts - Charts extracted via helm pull from OCI, index.yaml generated Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Quay Helm chart repos made public — ArgoCD can pull OCI charts without auth. Reverts the HTTP Helm repo workaround. helmRepoUrl: quay.apac-tech-lab.net:443/mirror/validatedpatterns Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Covers the full workflow: mirror → install OCP → bootstrap → deploy pattern → load secrets. Documents all known issues, workarounds, and Quay requirements. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The clustergroup chart checks global.singleArgoCD, not main.singleArgoCD. Move it to global: so the chart reads it correctly and only creates one ArgoCD instance. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The VP Helm chart uses registry.connect.redhat.com/hashicorp/vault (Red Hat certified UBI build), not docker.io/hashicorp/vault. Fix the imageset-config and add ITMS entry for registry.connect.redhat.com/hashicorp. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extracts ArgoCD admin credentials from the cluster and logs in with the argocd CLI. Falls back to printing credentials + install instructions if the CLI isn't available. make argocd-login Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extracts the argocd binary from the running ArgoCD server image on the cluster — works in disconnected environments without internet access to GitHub releases. make argocd-install # installs to ~/.local/bin/ make argocd-install ARGOCD_CLI_DIR=~/bin # custom path Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove 2>/dev/null so KUBECONFIG and API errors are visible. Add explicit error messages for missing ArgoCD instance or deployment. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
OpenShift GitOps prefixes deployment names with the instance name (e.g. vp-gitops-server), so app.kubernetes.io/name doesn't match a static 'argocd-server'. Use component=server + part-of=argocd. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The reencrypt route handles TLS termination; let the CLI negotiate the connection without forcing gRPC-web transport. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
OpenShift reencrypt routes don't pass gRPC traffic. Use --core mode which talks directly to the Kubernetes API instead of the ArgoCD server, avoiding all route/TLS/gRPC issues. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Kyverno chart was under mirror/kyverno/charts which has nested path issues with Quay repo visibility. Re-mirrored under mirror/validatedpatterns/kyverno (same public namespace as VP charts). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add global.kubevirt.vm.tdx.enabled: 'true' override to kubevirtvm application - Alongside existing global.kubevirt.vm.enabled: 'true' override - Enables rhel9-tdx VM deployment on baremetal topology via ArgoCD
… array-append The featureGates array-append (-) jsonpatch silently fails when developerConfiguration doesn't exist in spec.configuration. Use 'add' on the full developerConfiguration object instead so the WorkloadEncryptionTDX feature gate is actually applied to the KubeVirt CR. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Two fixes for the OVMF firmware mismatch: 1. secureBoot: true — the only TDX OVMF descriptor on RHCOS node is the secboot variant (OVMF.inteltdx.secboot.fd). With secureBoot=false, KubeVirt finds no matching ROM and fails with 'EFI OVMF roms missing'. 2. Remove smm.enabled: false — SMM is required for q35+EFI+SecureBoot. Explicit false contradicts SecureBoot and caused boot failures. qgsSocketPath already set per-VMI (executor Task 3). TDX attestation is independent of the SecureBoot chain so the QGS test is unaffected. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Use explicit 3-step sequence: initialise developerConfiguration {},
initialise featureGates [], then append WorkloadEncryptionTDX with -.
Correct append semantics on a clean-state KubeVirt CR.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Original '-' array-append worked for the KubeVirt CR. Revert the over-engineered 3-op approach back to the single append that was confirmed working. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…+EFI Admission webhook rejects SecureBoot without SMM enabled. Adding smm.enabled: true to match the q35+EFI+SecureBoot requirement. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…cboot descriptor TDX and SMM are mutually exclusive in KubeVirt. SecureBoot requires SMM. So TDX VMs cannot use SecureBoot, but the only shipped TDX OVMF descriptor (60-edk2-ovmf-x64-inteltdx.json) requires it. Solution: add a user-installed firmware descriptor in /etc/qemu/firmware/ pointing to OVMF.inteltdx.fd (non-secboot variant that exists on the node) with features [intel-tdx] only. KubeVirt will match this for TDX=true, SecureBoot=false, SMM=false. - vm.yaml: revert secureBoot → false, remove smm key entirely - tdx-ovmf-mco.yaml: MachineConfig placing the new descriptor on the node Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…etc/qemu/ virt-handler reads firmware JSON via proc/1/root/usr/share/qemu/firmware/ (not /etc/qemu/firmware/). Move the descriptor to the correct path. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…boot /usr/share/qemu/firmware/ is read-only (OSTree). Use a systemd oneshot that creates an overlay mount at boot, merging /etc/qemu/firmware/ (writable, holds our new TDX no-secboot descriptor) into /usr/share/qemu/firmware/ so virt-handler finds the descriptor during firmware selection. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…F MCO Root cause: virt-launcher uses --ovmf-path /usr/share/OVMF (default). TDX firmware exists at /usr/share/edk2/ovmf/OVMF.inteltdx.fd but NOT at /usr/share/OVMF/OVMF.inteltdx.fd (RHCOS symlinks only cover standard/SecureBoot variants, not TDX/SEV). Fix: set spec.configuration.ovmfPath=/usr/share/edk2/ovmf via HCO jsonpatch so virt-launcher finds all firmware variants. One-line config change — no MachineConfig, no overlay, no sidecar. Removes tdx-ovmf-mco.yaml (overlay approach was unnecessary — the QEMU firmware JSON descriptors are not used by KubeVirt). Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
- multiSourceRepoUrl: quay.io/validatedpatterns (was 172.25.36.135:8443) - git.repoURL: https://github.com/validatedpatterns/coco-pattern.git (was HTTP lab IP) - git.revision: main (was dev/airgap-testing) - helmRepoUrl: quay.io/validatedpatterns (was 172.25.36.135:8443) - Comment out airgap-specific patternsOperator.source and gitops.operatorSource
…aremetal.yaml - Remove image overrides for hello-openshift, kbs-access-curl, kbs-access-sealed (172.25.36.135 digests) - Comment out cs-*-operator-index catalog source fields with airgap instructional notes - Comment out ACM mce_operator.source override with airgap note - Remove kubevirtconfidential enabled override (chart default is false) - Remove kubevirtvm enabled and tdx.enabled overrides (chart defaults are false) - Comment out trustee repoURL/targetRevision/path (pending OCI chart switch in Plan 02)
…ap override - Reset all 4 registry fields in kyverno/values.yaml to empty string (upstream default) - Move airgap registry override to overrides/values-kyverno.yaml with 10.0.0.1:8443 placeholder
- Makefile: cache-registry-ca destination quay-ca-cert.pem -> mirror-registry-ca-cert.pem with migration step - values-secret.yaml.template: bootstrap secret quay-helm-oci -> mirror-registry-helm-oci - values-secret.yaml.template: registry URL 172.25.36.135:8443 -> MIRROR_REGISTRY_HOST:PORT placeholder - scripts/verify/23-live.sh: all quay-helm-oci references -> mirror-registry-helm-oci
- kubevirtconfidential: TDX feature gate, SELinux policy, hardware requirements, KubeVirt version gap - kubevirtvm: baseline and TDX VMs, prerequisites, enablement instructions - Both charts render empty when disabled (default)
- Removed scripts/deprecated/ directory entirely (5 files) - Scripts were quarantined in Phase 24, confirmed unused in Phase 25
- airgap/DEPLOY-RUNBOOK.md: replace all 172.25.36.135 with 10.0.0.1, /home/chbutler with $HOME - airgap/imageset-config-4.22.yaml: placeholder IP 10.0.0.1:8443 in target comment and usage example - rhel9-imagestream-job.yaml: comment references MIRROR_REGISTRY instead of lab IP
…nd in sweep - scripts/verify/23-live.sh: fallback MIRROR_REGISTRY default -> 10.0.0.1:8443 - scripts/verify/23-dry-render.sh: fallback MIRROR_REGISTRY default -> 10.0.0.1:8443 - scripts/airgap-post-install.sh: example IP in comments/error messages -> 10.0.0.1:8443 - airgap/itms-manual-mirrors.yaml: mirror entries -> 10.0.0.1:8443 - airgap/imageset-config-4.21.yaml: quay.apac-tech-lab.net -> 10.0.0.1:8443, /home/chbutler -> $HOME - airgap/imageset-config.yaml: quay.apac-tech-lab.net -> 10.0.0.1:8443, /home/chbutler -> $HOME - airgap/DEPLOY-RUNBOOK.md: quay-helm-oci -> mirror-registry-helm-oci in D-5 verify section - values-baremetal.yaml: trustee comment updated to upstream URL
trustee-chart v0.10.0 released to quay.io/validatedpatterns/trustee. Switch from git repoURL to chart: + chartVersion: reference, matching the pattern used by all other VP charts (acm, hashicorp-vault, etc.). - values-baremetal.yaml: repoURL/targetRevision/path replaced with chart: trustee, chartVersion: 0.10.* - imageset-config-4.22.yaml: trustee OCI chart version 0.9.0 → 0.10.0 Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
butler54
marked this pull request as ready for review
August 17, 2026 01:35
1. values-secret.yaml.template: remove 'type: Opaque' from bootstrap_secrets — upstream JSON schema rejects it as an additional property not in the bootstrap_secrets schema 2. superlinter.yml: add FILTER_REGEX_EXCLUDE for charts/all/kyverno/ to skip vendored upstream chart from linting Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Move the vendored upstream Kyverno chart from charts/all/kyverno to charts/vendor/kyverno. The charts/vendor/ directory is excluded from superlinter via FILTER_REGEX_EXCLUDE to avoid linting large upstream codebases we don't maintain. Updated references in values-baremetal.yaml and values-azure.yaml. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
The CI jsonschema test merges each values-*.yaml with values-global.yaml and validates against the ClusterGroup schema. The airgap overlay was missing the required 'clusterGroup' property, causing validation failure. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
values-baremetal-airgap.yaml is a partial shared overlay (catalogSource only) loaded via sharedValueFiles — it is not a standalone topology file and cannot satisfy the clusterGroup.applications/namespaces requirement. Exclude values-*-airgap.yaml from the jsonschema CI test. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
- Version section updated: v5.* → v8.* with full changelog covering OSC 1.13/Trustee 1.2, airgap support, chart architecture changes, TDX MachineConfig, DCAP collateral, experimental KubeVirt TDX VMs - Added airgap deployment section with prerequisites and runbook link - Updated OCP version requirement: 4.19.28+ → 4.22+ - Removed stale PCCS references (eliminated in Phase 20c) - Updated gen-secrets.sh description (no more PCCS tokens) - Previous versions table preserved with v5 added Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
butler54
commented
Aug 17, 2026
butler54
left a comment
Collaborator
Author
There was a problem hiding this comment.
Some updates required.
| @@ -0,0 +1,166 @@ | |||
| --- | |||
| # ImageSetConfiguration for CoCo Validated Pattern airgap deployment | |||
Collaborator
Author
There was a problem hiding this comment.
This seems a duplicate of the 4.21 imageset config.
| ```yaml | ||
| main: | ||
| git: | ||
| repoURL: http://JUMP_HOST_IP:8080/coco-pattern.git # smart HTTP |
Collaborator
Author
There was a problem hiding this comment.
Some parts of this PR use 10.0.0.1 some use JUMP_HOST_IP. Normalize on JUMP_HOST_IP
| git: | ||
| # Disconnected: HTTP URL served by git-http-server.py on jump host port 8080. | ||
| # Connected: https://github.com/validatedpatterns/coco-pattern.git | ||
| repoURL: https://github.com/validatedpatterns/coco-pattern.git |
Collaborator
Author
There was a problem hiding this comment.
In a connected environment (default) this shoul dbe inferred not hard coded. PROVIDE WITH the jumphost context but commented out.
Comment on lines
270
to
290
| kubevirtconfidential: | ||
| name: kubevirtconfidential | ||
| namespace: openshift-cnv | ||
| project: hub | ||
| path: charts/all/nvidia-gpu | ||
| path: charts/all/kubevirtconfidential | ||
| annotations: | ||
| argocd.argoproj.io/sync-wave: "10" | ||
| argocd.argoproj.io/sync-wave: "30" | ||
|
|
||
| gpu-workload: | ||
| name: gpu-workload | ||
| namespace: gpu-workload | ||
| project: workloads | ||
| path: charts/coco-supported/gpu-workload | ||
| # Baseline non-confidential RHEL9 VM (Phase 27). | ||
| # Validates KubeVirt works before adding TDX launch security in Phase 28. | ||
| # Gated: set global.kubevirt.vm.enabled to true to enable chart templates. | ||
| kubevirtvm: | ||
| name: kubevirtvm | ||
| namespace: kubevirt-vms | ||
| project: hub | ||
| path: charts/all/kubevirtvm | ||
| annotations: | ||
| argocd.argoproj.io/sync-wave: "30" | ||
| argocd.argoproj.io/sync-wave: "35" | ||
| syncPolicy: | ||
| automated: | ||
| prune: true |
Collaborator
Author
There was a problem hiding this comment.
These two charts should be disabled by default for main.
Comment on lines
+209
to
+216
| - name: kbs.registryCa.enabled | ||
| value: "true" | ||
| - name: kbs.tdx.collateralService | ||
| value: "file:///opt/confidential-containers/attestation-service/tdx/platform_collaterals.json" | ||
| - name: kbs.tdx.collateralMountPath | ||
| value: "/opt/confidential-containers/attestation-service/tdx" | ||
| - name: kbs.tdx.collateralFileName | ||
| value: "platform_collaterals.json" |
Comment on lines
+228
to
+240
| .PHONY: compile-selinux-policy | ||
| compile-selinux-policy: ## Compile SELinux policy for kubevirt-qgs and embed base64 in selinux-mco.yaml | ||
| @echo "=== Compiling kubevirt-qgs SELinux policy module ===" | ||
| @echo "This step must run on a RHEL 9 machine with selinux-policy-devel installed." | ||
| @echo "Install: dnf install selinux-policy-devel policycoreutils" | ||
| @if ! command -v checkmodule >/dev/null 2>&1; then \ | ||
| echo "ERROR: checkmodule not found. Run on RHEL 9 with selinux-policy-devel installed."; \ | ||
| exit 1; \ | ||
| fi | ||
| cd charts/all/kubevirtconfidential/selinux && \ | ||
| make -f /usr/share/selinux/devel/Makefile kubevirt-qgs.pp | ||
| @echo "" | ||
| @echo "=== Generating base64 for MachineConfig embedding ===" |
Collaborator
Author
There was a problem hiding this comment.
I'm not sure we need this compilation as we are doing it all in the one-shot
- Move values-baremetal-airgap.yaml to overrides/ (shared overlay, not topology) - Delete duplicate imageset configs (keep only 4.22) - Normalize all 10.0.0.1 references to MIRROR_REGISTRY_HOST/JUMP_HOST_IP placeholders - Comment out repoURL in values-global.yaml (inferred in connected mode) - Disable kubevirt application blocks by default (experimental) - Add inline comments to trustee TDX overrides explaining each field - Remove compile-selinux-policy Makefile target (oneshot compiles on node) - Fix update-kyverno-chart path to charts/vendor/ - Revert jsonschema exclusion (file moved to overrides/) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.